-
Notifications
You must be signed in to change notification settings - Fork 129
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ace8c36
commit d324862
Showing
17 changed files
with
1,674 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package dispute | ||
|
||
import ( | ||
"fmt" | ||
"github.com/ChainSafe/gossamer/dot/parachain/dispute/types" | ||
"github.com/ChainSafe/gossamer/lib/common" | ||
"github.com/ChainSafe/gossamer/lib/parachain" | ||
"github.com/dgraph-io/badger/v4" | ||
"github.com/google/btree" | ||
"time" | ||
) | ||
|
||
const ( | ||
earliestSessionKey = "earliestSession" | ||
recentDisputesPrefix = "recentDisputes_" | ||
candidateVotesPrefix = "candidateVotes_" | ||
) | ||
|
||
// Backend is the backend for the dispute coordinator module. | ||
type Backend interface { | ||
// GetEarliestSession returns the earliest session index, if any. | ||
GetEarliestSession() (*parachain.SessionIndex, error) | ||
// GetRecentDisputes returns the recent disputes, if any. | ||
GetRecentDisputes() (*btree.BTree, error) | ||
// GetActiveDisputes returns the active disputes, if any. | ||
//GetActiveDisputes(now int64) (*btree.BTree, error) | ||
// GetCandidateVotes returns the votes for the given candidate for the specific session-candidate pair, if any. | ||
GetCandidateVotes(session parachain.SessionIndex, candidateHash common.Hash) (types.CandidateVotes, error) | ||
|
||
// SetEarliestSession sets the earliest session index. | ||
SetEarliestSession(session parachain.SessionIndex) error | ||
// SetRecentDisputes sets the recent disputes. | ||
SetRecentDisputes(recentDisputes *btree.BTree) error | ||
// SetCandidateVotes sets the votes for the given candidate for the specific session-candidate pair. | ||
SetCandidateVotes(session parachain.SessionIndex, candidateHash common.Hash, votes types.CandidateVotes) error | ||
} | ||
|
||
// DBBackend is the backend for the dispute coordinator module that uses a database. | ||
type DBBackend interface { | ||
Backend | ||
} | ||
|
||
// OverlayBackend implements Backend. | ||
type OverlayBackend struct { | ||
inner DBBackend | ||
earliestSession *parachain.SessionIndex | ||
recentDisputes *btree.BTree | ||
candidateVotes map[types.Comparator]types.CandidateVotes | ||
} | ||
|
||
func (b *OverlayBackend) GetEarliestSession() (*parachain.SessionIndex, error) { | ||
if b.earliestSession != nil { | ||
return b.earliestSession, nil | ||
} | ||
|
||
earliestSession, err := b.inner.GetEarliestSession() | ||
if err != nil { | ||
return nil, fmt.Errorf("get earliest session from db: %w", err) | ||
} | ||
|
||
b.earliestSession = earliestSession | ||
return b.earliestSession, nil | ||
} | ||
|
||
func (b *OverlayBackend) GetRecentDisputes() (*btree.BTree, error) { | ||
if b.recentDisputes.Len() > 0 { | ||
return b.recentDisputes, nil | ||
} | ||
|
||
recentDisputes, err := b.inner.GetRecentDisputes() | ||
if err != nil { | ||
return nil, fmt.Errorf("get recent disputes from db: %w", err) | ||
} | ||
|
||
return recentDisputes, nil | ||
} | ||
|
||
func (b *OverlayBackend) GetCandidateVotes(session parachain.SessionIndex, candidateHash common.Hash) (types.CandidateVotes, error) { | ||
key := types.Comparator{ | ||
SessionIndex: session, | ||
CandidateHash: candidateHash, | ||
} | ||
|
||
if v, ok := b.candidateVotes[key]; ok { | ||
return v, nil | ||
} | ||
|
||
votes, err := b.inner.GetCandidateVotes(session, candidateHash) | ||
if err != nil { | ||
return types.CandidateVotes{}, fmt.Errorf("get candidate votes from db: %w", err) | ||
} | ||
|
||
return votes, nil | ||
} | ||
|
||
func (b *OverlayBackend) SetEarliestSession(session parachain.SessionIndex) error { | ||
b.earliestSession = &session | ||
return nil | ||
} | ||
|
||
func (b *OverlayBackend) SetRecentDisputes(recentDisputes *btree.BTree) error { | ||
b.recentDisputes = recentDisputes | ||
return nil | ||
} | ||
|
||
func (b *OverlayBackend) SetCandidateVotes(session parachain.SessionIndex, candidateHash common.Hash, votes types.CandidateVotes) error { | ||
key := types.Comparator{ | ||
SessionIndex: session, | ||
CandidateHash: candidateHash, | ||
} | ||
b.candidateVotes[key] = votes | ||
return nil | ||
} | ||
|
||
func (b *OverlayBackend) getActiveDisputes(recentDisputes *btree.BTree) { | ||
b.recentDisputes = recentDisputes | ||
} | ||
|
||
// ActiveDuration an arbitrary duration for how long a dispute is considered active. | ||
const ActiveDuration = 180 * time.Second | ||
|
||
// GetActiveDisputes returns the active disputes, if any. | ||
func (b *OverlayBackend) GetActiveDisputes(now int64) (*btree.BTree, error) { | ||
activeDisputes := btree.New(32) | ||
|
||
b.recentDisputes.Ascend(func(i btree.Item) bool { | ||
dispute, ok := i.(*types.Dispute) | ||
if !ok { | ||
// TODO: do we want to panic here? | ||
panic("invalid dispute type") | ||
} | ||
concludedAt, err := dispute.DisputeStatus.ConcludedAt() | ||
if err == nil && concludedAt != nil && *concludedAt+uint64(ActiveDuration.Seconds()) > uint64(now) { | ||
activeDisputes.ReplaceOrInsert(dispute) | ||
} | ||
return true | ||
}) | ||
|
||
return activeDisputes, nil | ||
} | ||
|
||
var _ Backend = (*OverlayBackend)(nil) | ||
|
||
// NewOverlayBackend creates a new OverlayBackend. | ||
func NewOverlayBackend(db *badger.DB) *OverlayBackend { | ||
return &OverlayBackend{ | ||
inner: NewDBBackend(db), | ||
recentDisputes: btree.New(32), | ||
candidateVotes: make(map[types.Comparator]types.CandidateVotes), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package dispute | ||
|
||
import ( | ||
"fmt" | ||
"github.com/ChainSafe/gossamer/dot/parachain/dispute/types" | ||
"github.com/ChainSafe/gossamer/lib/common" | ||
"github.com/ChainSafe/gossamer/lib/parachain" | ||
"github.com/dgraph-io/badger/v4" | ||
"github.com/google/btree" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
) | ||
|
||
type disputeStatusEnum uint | ||
|
||
const ( | ||
DisputeStatusActive disputeStatusEnum = iota | ||
DisputeStatusConcludedFor | ||
DisputeStatusConcludedAgainst | ||
DisputeStatusConfirmed | ||
) | ||
|
||
func newTestDispute(session parachain.SessionIndex, candidateHash common.Hash, status disputeStatusEnum) (*types.Dispute, error) { | ||
disputeStatus, err := types.NewDisputeStatus() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
switch status { | ||
case DisputeStatusActive: | ||
err := disputeStatus.Set(types.ActiveStatus{}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
case DisputeStatusConcludedFor: | ||
err := disputeStatus.Set(types.ConcludedForStatus{}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
case DisputeStatusConcludedAgainst: | ||
err := disputeStatus.Set(types.ConcludedAgainstStatus{}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
case DisputeStatusConfirmed: | ||
err := disputeStatus.Set(types.ConfirmedStatus{}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
default: | ||
return nil, fmt.Errorf("invalid dispute status") | ||
} | ||
|
||
return &types.Dispute{ | ||
Comparator: types.Comparator{ | ||
SessionIndex: session, | ||
CandidateHash: candidateHash, | ||
}, | ||
DisputeStatus: disputeStatus, | ||
}, nil | ||
} | ||
|
||
func newTestCandidateVotes() types.CandidateVotes { | ||
receipt := parachain.CandidateReceipt{ | ||
Descriptor: parachain.CandidateDescriptor{ | ||
ParaID: 1, | ||
RelayParent: common.Hash{2}, | ||
Collator: parachain.CollatorID{2}, | ||
PersistedValidationDataHash: common.Hash{2}, | ||
PovHash: common.Hash{2}, | ||
ErasureRoot: common.Hash{2}, | ||
Signature: parachain.CollatorSignature{2}, | ||
ParaHead: common.Hash{2}, | ||
ValidationCodeHash: parachain.ValidationCodeHash{2}, | ||
}, | ||
CommitmentsHash: common.Hash{1}, | ||
} | ||
|
||
return types.CandidateVotes{ | ||
CandidateReceipt: receipt, | ||
} | ||
} | ||
|
||
func TestOverlayBackend_EarliestSession(t *testing.T) { | ||
t.Parallel() | ||
|
||
// with | ||
db, err := badger.Open(badger.DefaultOptions(t.TempDir())) | ||
require.NoError(t, err) | ||
|
||
// when | ||
backend := NewOverlayBackend(db) | ||
err = backend.SetEarliestSession(1) | ||
require.NoError(t, err) | ||
|
||
// then | ||
earliestSession, err := backend.GetEarliestSession() | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, parachain.SessionIndex(1), *earliestSession) | ||
} | ||
|
||
func TestOverlayBackend_RecentDisputes(t *testing.T) { | ||
t.Parallel() | ||
|
||
// with | ||
db, err := badger.Open(badger.DefaultOptions(t.TempDir())) | ||
require.NoError(t, err) | ||
|
||
disputes := btree.New(32) | ||
|
||
dispute1, err := newTestDispute(1, common.Hash{1}, DisputeStatusActive) | ||
require.NoError(t, err) | ||
disputes.ReplaceOrInsert(dispute1) | ||
|
||
dispute2, err := newTestDispute(2, common.Hash{2}, DisputeStatusConcludedFor) | ||
require.NoError(t, err) | ||
disputes.ReplaceOrInsert(dispute2) | ||
|
||
// when | ||
backend := NewOverlayBackend(db) | ||
err = backend.SetRecentDisputes(disputes) | ||
require.NoError(t, err) | ||
|
||
// then | ||
recentDisputes, err := backend.GetRecentDisputes() | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, disputes, recentDisputes) | ||
} | ||
|
||
func TestOverlayBackend_CandidateVotes(t *testing.T) { | ||
t.Parallel() | ||
|
||
// with | ||
db, err := badger.Open(badger.DefaultOptions(t.TempDir())) | ||
require.NoError(t, err) | ||
|
||
candidateVotes1 := newTestCandidateVotes() | ||
|
||
// when | ||
backend := NewOverlayBackend(db) | ||
err = backend.SetCandidateVotes(1, common.Hash{1}, candidateVotes1) | ||
require.NoError(t, err) | ||
|
||
// then | ||
candidateVotes, err := backend.GetCandidateVotes(1, common.Hash{1}) | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, candidateVotes1, candidateVotes) | ||
} |
Oops, something went wrong.