package evidence
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
/*
|
|
Requirements:
|
|
- Valid new evidence must be persisted immediately and never forgotten
|
|
- Uncommitted evidence must be continuously broadcast
|
|
- Uncommitted evidence has a partial order, the evidence's priority
|
|
|
|
Impl:
|
|
- First commit atomically in outqueue, pending, lookup.
|
|
- Once broadcast, remove from outqueue. No need to sync
|
|
- Once committed, atomically remove from pending and update lookup.
|
|
|
|
Schema for indexing evidence (note you need both height and hash to find a piece of evidence):
|
|
|
|
"evidence-lookup"/<evidence-height>/<evidence-hash> -> Info
|
|
"evidence-outqueue"/<priority>/<evidence-height>/<evidence-hash> -> Info
|
|
"evidence-pending"/<evidence-height>/<evidence-hash> -> Info
|
|
*/
|
|
|
|
type Info struct {
|
|
Committed bool
|
|
Priority int64
|
|
Evidence types.Evidence
|
|
}
|
|
|
|
const (
|
|
baseKeyLookup = "evidence-lookup" // all evidence
|
|
baseKeyOutqueue = "evidence-outqueue" // not-yet broadcast
|
|
baseKeyPending = "evidence-pending" // broadcast but not committed
|
|
)
|
|
|
|
func keyLookup(evidence types.Evidence) []byte {
|
|
return keyLookupFromHeightAndHash(evidence.Height(), evidence.Hash())
|
|
}
|
|
|
|
// big endian padded hex
|
|
func bE(h int64) string {
|
|
return fmt.Sprintf("%0.16X", h)
|
|
}
|
|
|
|
func keyLookupFromHeightAndHash(height int64, hash []byte) []byte {
|
|
return _key("%s/%s/%X", baseKeyLookup, bE(height), hash)
|
|
}
|
|
|
|
func keyOutqueue(evidence types.Evidence, priority int64) []byte {
|
|
return _key("%s/%s/%s/%X", baseKeyOutqueue, bE(priority), bE(evidence.Height()), evidence.Hash())
|
|
}
|
|
|
|
func keyPending(evidence types.Evidence) []byte {
|
|
return _key("%s/%s/%X", baseKeyPending, bE(evidence.Height()), evidence.Hash())
|
|
}
|
|
|
|
func _key(format string, o ...interface{}) []byte {
|
|
return []byte(fmt.Sprintf(format, o...))
|
|
}
|
|
|
|
// Store is a store of all the evidence we've seen, including
|
|
// evidence that has been committed, evidence that has been verified but not broadcast,
|
|
// and evidence that has been broadcast but not yet committed.
|
|
type Store struct {
|
|
db dbm.DB
|
|
}
|
|
|
|
func NewStore(db dbm.DB) *Store {
|
|
return &Store{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// PriorityEvidence returns the evidence from the outqueue, sorted by highest priority.
|
|
func (store *Store) PriorityEvidence() (evidence []types.Evidence) {
|
|
// reverse the order so highest priority is first
|
|
l := store.listEvidence(baseKeyOutqueue, -1)
|
|
for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 {
|
|
l[i], l[j] = l[j], l[i]
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// PendingEvidence returns up to maxNum known, uncommitted evidence.
|
|
// If maxNum is -1, all evidence is returned.
|
|
func (store *Store) PendingEvidence(maxNum int64) (evidence []types.Evidence) {
|
|
return store.listEvidence(baseKeyPending, maxNum)
|
|
}
|
|
|
|
// listEvidence lists up to maxNum pieces of evidence for the given prefix key.
|
|
// It is wrapped by PriorityEvidence and PendingEvidence for convenience.
|
|
// If maxNum is -1, there's no cap on the size of returned evidence.
|
|
func (store *Store) listEvidence(prefixKey string, maxNum int64) (evidence []types.Evidence) {
|
|
var count int64
|
|
iter, err := dbm.IteratePrefix(store.db, []byte(prefixKey))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer iter.Close()
|
|
for ; iter.Valid(); iter.Next() {
|
|
val := iter.Value()
|
|
|
|
if count == maxNum {
|
|
return evidence
|
|
}
|
|
count++
|
|
|
|
var ei Info
|
|
err := cdc.UnmarshalBinaryBare(val, &ei)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
evidence = append(evidence, ei.Evidence)
|
|
}
|
|
return evidence
|
|
}
|
|
|
|
// GetInfo fetches the Info with the given height and hash.
|
|
// If not found, ei.Evidence is nil.
|
|
func (store *Store) GetInfo(height int64, hash []byte) Info {
|
|
key := keyLookupFromHeightAndHash(height, hash)
|
|
val, err := store.db.Get(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if len(val) == 0 {
|
|
return Info{}
|
|
}
|
|
var ei Info
|
|
err = cdc.UnmarshalBinaryBare(val, &ei)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return ei
|
|
}
|
|
|
|
// AddNewEvidence adds the given evidence to the database.
|
|
// It returns false if the evidence is already stored.
|
|
func (store *Store) AddNewEvidence(evidence types.Evidence, priority int64) bool {
|
|
// check if we already have seen it
|
|
ei := store.getInfo(evidence)
|
|
if ei.Evidence != nil {
|
|
return false
|
|
}
|
|
|
|
ei = Info{
|
|
Committed: false,
|
|
Priority: priority,
|
|
Evidence: evidence,
|
|
}
|
|
eiBytes := cdc.MustMarshalBinaryBare(ei)
|
|
|
|
// add it to the store
|
|
key := keyOutqueue(evidence, priority)
|
|
store.db.Set(key, eiBytes)
|
|
|
|
key = keyPending(evidence)
|
|
store.db.Set(key, eiBytes)
|
|
|
|
key = keyLookup(evidence)
|
|
store.db.SetSync(key, eiBytes)
|
|
|
|
return true
|
|
}
|
|
|
|
// MarkEvidenceAsBroadcasted removes evidence from Outqueue.
|
|
func (store *Store) MarkEvidenceAsBroadcasted(evidence types.Evidence) {
|
|
ei := store.getInfo(evidence)
|
|
if ei.Evidence == nil {
|
|
// nothing to do; we did not store the evidence yet (AddNewEvidence):
|
|
return
|
|
}
|
|
// remove from the outqueue
|
|
key := keyOutqueue(evidence, ei.Priority)
|
|
store.db.Delete(key)
|
|
}
|
|
|
|
// MarkEvidenceAsCommitted removes evidence from pending and outqueue and sets the state to committed.
|
|
func (store *Store) MarkEvidenceAsCommitted(evidence types.Evidence) {
|
|
// if its committed, its been broadcast
|
|
store.MarkEvidenceAsBroadcasted(evidence)
|
|
|
|
pendingKey := keyPending(evidence)
|
|
store.db.Delete(pendingKey)
|
|
|
|
// committed Info doens't need priority
|
|
ei := Info{
|
|
Committed: true,
|
|
Evidence: evidence,
|
|
Priority: 0,
|
|
}
|
|
|
|
lookupKey := keyLookup(evidence)
|
|
store.db.SetSync(lookupKey, cdc.MustMarshalBinaryBare(ei))
|
|
}
|
|
|
|
//---------------------------------------------------
|
|
// utils
|
|
|
|
// getInfo is convenience for calling GetInfo if we have the full evidence.
|
|
func (store *Store) getInfo(evidence types.Evidence) Info {
|
|
return store.GetInfo(evidence.Height(), evidence.Hash())
|
|
}
|