-
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
6b71e22
commit 3f8174f
Showing
3 changed files
with
229 additions
and
1 deletion.
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,225 @@ | ||
package dispute | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"github.com/ChainSafe/gossamer/lib/common" | ||
"github.com/ChainSafe/gossamer/lib/parachain" | ||
"github.com/ChainSafe/gossamer/pkg/scale" | ||
"github.com/emirpasic/gods/maps/treemap" | ||
"github.com/google/btree" | ||
"time" | ||
) | ||
|
||
type CandidateHash common.Hash | ||
|
||
type Comparator struct { | ||
SessionIndex parachain.SessionIndex | ||
CandidateHash CandidateHash | ||
} | ||
|
||
// Less returns true if the current item is less than the other item | ||
// it uses the Key to determine the order | ||
func (d Dispute) Less(than btree.Item) bool { | ||
other := than.(*Dispute) | ||
|
||
if d.comparator.SessionIndex == other.comparator.SessionIndex { | ||
return bytes.Compare(d.comparator.CandidateHash[:], other.comparator.CandidateHash[:]) < 0 | ||
} | ||
|
||
return d.comparator.SessionIndex < other.comparator.SessionIndex | ||
} | ||
|
||
type CandidateVotes struct { | ||
candidateReceipt parachain.CandidateReceipt | ||
valid *treemap.Map | ||
invalid *treemap.Map | ||
} | ||
|
||
type Dispute struct { | ||
comparator *Comparator | ||
disputeStatus *Status | ||
} | ||
|
||
func NewCandidateVotes() *CandidateVotes { | ||
return &CandidateVotes{ | ||
valid: treemap.NewWithIntComparator(), | ||
invalid: treemap.NewWithIntComparator(), | ||
} | ||
} | ||
|
||
type Active struct{} | ||
|
||
func (Active) Index() uint { | ||
return 0 | ||
} | ||
|
||
type ConcludedFor struct { | ||
Since uint64 | ||
} | ||
|
||
func (ConcludedFor) Index() uint { | ||
return 1 | ||
} | ||
|
||
type ConcludedAgainst struct { | ||
Since uint64 | ||
} | ||
|
||
func (ConcludedAgainst) Index() uint { | ||
return 2 | ||
} | ||
|
||
type Confirmed struct{} | ||
|
||
func (Confirmed) Index() uint { | ||
return 3 | ||
} | ||
|
||
type Status scale.VaryingDataType | ||
|
||
// Set will set a VaryingDataTypeValue using the underlying VaryingDataType | ||
func (ds *Status) Set(val scale.VaryingDataTypeValue) (err error) { | ||
vdt := scale.VaryingDataType(*ds) | ||
err = vdt.Set(val) | ||
if err != nil { | ||
return fmt.Errorf("setting value to varying data type: %w", err) | ||
} | ||
*ds = Status(vdt) | ||
return nil | ||
} | ||
|
||
// Value returns the value from the underlying VaryingDataType | ||
func (ds *Status) Value() (scale.VaryingDataTypeValue, error) { | ||
vdt := scale.VaryingDataType(*ds) | ||
return vdt.Value() | ||
} | ||
|
||
// TODO: check if we can avoid using pointers | ||
func (ds *Status) ConcludedAt() (*uint64, error) { | ||
vdt := scale.VaryingDataType(*ds) | ||
val, err := vdt.Value() | ||
if err != nil { | ||
return nil, fmt.Errorf("getting value from DisputeStatus vdt: %w", err) | ||
} | ||
|
||
switch v := val.(type) { | ||
case Active, Confirmed: | ||
return nil, nil | ||
case ConcludedFor: | ||
return &v.Since, nil | ||
case ConcludedAgainst: | ||
return &v.Since, nil | ||
default: | ||
return nil, fmt.Errorf("invalid dispute status type") | ||
} | ||
} | ||
|
||
func NewDisputeStatus() (Status, error) { | ||
vdt, err := scale.NewVaryingDataType(Active{}, ConcludedFor{}, ConcludedAgainst{}, Confirmed{}) | ||
if err != nil { | ||
return Status{}, fmt.Errorf("creating new dispute status vdt: %w", err) | ||
} | ||
|
||
return Status(vdt), nil | ||
} | ||
|
||
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) | ||
// GetCandidateVotes returns the votes for the given candidate for the specific session-candidate pair, if any. | ||
GetCandidateVotes(session parachain.SessionIndex, candidateHash CandidateHash) (*CandidateVotes, error) | ||
|
||
// GetActiveDisputes returns the active disputes, if any. | ||
GetActiveDisputes(now int64) (*btree.BTree, error) | ||
|
||
// TODO: need to explore further to see if we need these | ||
SetEarliestSession(session parachain.SessionIndex) | ||
SetRecentDisputes(recentDisputes *btree.BTree) | ||
SetCandidateVotes(session parachain.SessionIndex, candidateHash CandidateHash, votes *CandidateVotes) | ||
} | ||
|
||
type backend struct { | ||
// TODO: db | ||
earliestSession *parachain.SessionIndex | ||
recentDisputes *btree.BTree | ||
candidateVotes map[Comparator]*CandidateVotes | ||
} | ||
|
||
func (b backend) GetEarliestSession() (*parachain.SessionIndex, error) { | ||
if b.earliestSession != nil { | ||
return b.earliestSession, nil | ||
} | ||
|
||
// TODO: read from db | ||
|
||
return nil, nil | ||
} | ||
|
||
func (b backend) GetRecentDisputes() (*btree.BTree, error) { | ||
if b.recentDisputes.Len() > 0 { | ||
return b.recentDisputes, nil | ||
} | ||
|
||
// TODO: read from db | ||
|
||
return nil, nil | ||
} | ||
|
||
func (b backend) GetCandidateVotes(session parachain.SessionIndex, candidateHash CandidateHash) (*CandidateVotes, error) { | ||
key := Comparator{ | ||
SessionIndex: session, | ||
CandidateHash: candidateHash, | ||
} | ||
|
||
if v, ok := b.candidateVotes[key]; ok { | ||
return v, nil | ||
} | ||
|
||
// TODO: read from db | ||
|
||
return nil, nil | ||
} | ||
|
||
func (b backend) SetEarliestSession(session parachain.SessionIndex) { | ||
b.earliestSession = &session | ||
} | ||
|
||
func (b backend) SetRecentDisputes(recentDisputes *btree.BTree) { | ||
b.recentDisputes = recentDisputes | ||
} | ||
|
||
func (b backend) SetCandidateVotes(session parachain.SessionIndex, candidateHash CandidateHash, votes *CandidateVotes) { | ||
key := Comparator{ | ||
SessionIndex: session, | ||
CandidateHash: candidateHash, | ||
} | ||
|
||
b.candidateVotes[key] = votes | ||
} | ||
|
||
const ActiveDuration = 180 * time.Second | ||
|
||
func (b backend) GetActiveDisputes(now int64) (*btree.BTree, error) { | ||
activeDisputes := btree.New(32) | ||
|
||
b.recentDisputes.Ascend(func(i btree.Item) bool { | ||
d := i.(*Dispute) | ||
concludedAt, err := d.disputeStatus.ConcludedAt() | ||
if err == nil && concludedAt != nil && *concludedAt+uint64(ActiveDuration.Seconds()) > uint64(now) { | ||
activeDisputes.ReplaceOrInsert(d) | ||
} | ||
return true | ||
}) | ||
|
||
return activeDisputes, nil | ||
} | ||
|
||
func NewBackend() Backend { | ||
return &backend{ | ||
recentDisputes: btree.New(32), | ||
candidateVotes: make(map[Comparator]*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
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