Skip to content

Commit

Permalink
add backend
Browse files Browse the repository at this point in the history
  • Loading branch information
kanishkatn committed Jul 20, 2023
1 parent ace8c36 commit d324862
Show file tree
Hide file tree
Showing 17 changed files with 1,674 additions and 64 deletions.
151 changes: 151 additions & 0 deletions dot/parachain/dispute/backend.go
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),
}
}
151 changes: 151 additions & 0 deletions dot/parachain/dispute/backend_test.go
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)
}
Loading

0 comments on commit d324862

Please sign in to comment.