diff --git a/evidence/store.go b/evidence/store.go index 4a6eb024..1b4708ea 100644 --- a/evidence/store.go +++ b/evidence/store.go @@ -25,12 +25,12 @@ Impl: Schema for indexing evidence (note you need both height and hash to find a piece of evidence): -"evidence-lookup"// -> evidenceInfo -"evidence-outqueue"/// -> evidenceInfo -"evidence-pending"// -> evidenceInfo +"evidence-lookup"// -> EvidenceInfo +"evidence-outqueue"/// -> EvidenceInfo +"evidence-pending"// -> EvidenceInfo */ -type evidenceInfo struct { +type EvidenceInfo struct { Committed bool Priority int Evidence types.Evidence @@ -46,16 +46,21 @@ func keyLookup(evidence types.Evidence) []byte { return keyLookupFromHeightAndHash(evidence.Height(), evidence.Hash()) } +// big endian padded hex +func be(h int) string { + return fmt.Sprintf("%0.16X", h) +} + func keyLookupFromHeightAndHash(height int, hash []byte) []byte { - return _key("%s/%d/%X", baseKeyLookup, height, hash) + return _key("%s/%s/%X", baseKeyLookup, be(height), hash) } func keyOutqueue(evidence types.Evidence, priority int) []byte { - return _key("%s/%d/%d/%X", baseKeyOutqueue, priority, evidence.Height(), evidence.Hash()) + return _key("%s/%s/%s/%X", baseKeyOutqueue, be(priority), be(evidence.Height()), evidence.Hash()) } func keyPending(evidence types.Evidence) []byte { - return _key("%s/%d/%X", baseKeyPending, evidence.Height(), evidence.Hash()) + return _key("%s/%s/%X", baseKeyPending, be(evidence.Height()), evidence.Hash()) } func _key(fmt_ string, o ...interface{}) []byte { @@ -77,8 +82,13 @@ func NewEvidenceStore(db dbm.DB) *EvidenceStore { // PriorityEvidence returns the evidence from the outqueue, sorted by highest priority. func (store *EvidenceStore) PriorityEvidence() (evidence []types.Evidence) { - // TODO: revert order for highest first - return store.ListEvidence(baseKeyOutqueue) + // reverse the order so highest priority is first + l := store.ListEvidence(baseKeyOutqueue) + l2 := make([]types.Evidence, len(l)) + for i, _ := range l { + l2[i] = l[len(l)-1-i] + } + return l2 } // PendingEvidence returns all known uncommitted evidence. @@ -93,7 +103,7 @@ func (store *EvidenceStore) ListEvidence(prefixKey string) (evidence []types.Evi for iter.Next() { val := iter.Value() - var ei evidenceInfo + var ei EvidenceInfo wire.ReadBinaryBytes(val, &ei) evidence = append(evidence, ei.Evidence) } @@ -101,26 +111,27 @@ func (store *EvidenceStore) ListEvidence(prefixKey string) (evidence []types.Evi } // GetEvidence fetches the evidence with the given height and hash. -func (store *EvidenceStore) GetEvidence(height int, hash []byte) types.Evidence { +func (store *EvidenceStore) GetEvidence(height int, hash []byte) *EvidenceInfo { key := keyLookupFromHeightAndHash(height, hash) val := store.db.Get(key) + if len(val) == 0 { return nil } - var ei evidenceInfo - wire.ReadBinaryBytes(val, &ei) - return ei.Evidence + ei := new(EvidenceInfo) + wire.ReadBinaryBytes(val, ei) + return ei } // AddNewEvidence adds the given evidence to the database. func (store *EvidenceStore) AddNewEvidence(evidence types.Evidence, priority int) (bool, error) { // check if we already have seen it - ev := store.GetEvidence(evidence.Height(), evidence.Hash()) - if ev != nil { + ei_ := store.GetEvidence(evidence.Height(), evidence.Hash()) + if ei_ != nil && ei_.Evidence != nil { return false, nil } - ei := evidenceInfo{ + ei := EvidenceInfo{ Committed: false, Priority: priority, Evidence: evidence, @@ -152,8 +163,8 @@ func (store *EvidenceStore) MarkEvidenceAsCommitted(evidence types.Evidence) { // if its committed, its been broadcast store.MarkEvidenceAsBroadcasted(evidence) - key := keyPending(evidence) - store.db.Delete(key) + pendingKey := keyPending(evidence) + store.db.Delete(pendingKey) ei := store.getEvidenceInfo(evidence) ei.Committed = true @@ -161,15 +172,16 @@ func (store *EvidenceStore) MarkEvidenceAsCommitted(evidence types.Evidence) { // TODO: we should use the state db and db.Sync in state.Save instead. // Else, if we call this before state.Save, we may never mark committed evidence as committed. // Else, if we call this after state.Save, we may get stuck broadcasting evidence we never know we committed. - store.db.SetSync(key, wire.BinaryBytes(ei)) + lookupKey := keyLookup(evidence) + store.db.SetSync(lookupKey, wire.BinaryBytes(ei)) } //--------------------------------------------------- // utils -func (store *EvidenceStore) getEvidenceInfo(evidence types.Evidence) evidenceInfo { +func (store *EvidenceStore) getEvidenceInfo(evidence types.Evidence) EvidenceInfo { key := keyLookup(evidence) - var ei evidenceInfo + var ei EvidenceInfo b := store.db.Get(key) wire.ReadBinaryBytes(b, &ei) return ei diff --git a/evidence/store_test.go b/evidence/store_test.go new file mode 100644 index 00000000..76cb794a --- /dev/null +++ b/evidence/store_test.go @@ -0,0 +1,166 @@ +package evpool + +import ( + "bytes" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + wire "github.com/tendermint/go-wire" + "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tmlibs/db" +) + +//------------------------------------------- + +func TestStoreAddDuplicate(t *testing.T) { + assert := assert.New(t) + + db := dbm.NewMemDB() + store := NewEvidenceStore(db) + + priority := 10 + ev := newMockGoodEvidence(2, 1, []byte("val1")) + + added, err := store.AddNewEvidence(ev, priority) + assert.Nil(err) + assert.True(added) + + // cant add twice + added, err = store.AddNewEvidence(ev, priority) + assert.Nil(err) + assert.False(added) +} + +func TestStoreMark(t *testing.T) { + assert := assert.New(t) + + db := dbm.NewMemDB() + store := NewEvidenceStore(db) + + // before we do anything, priority/pending are empty + priorityEv := store.PriorityEvidence() + pendingEv := store.PendingEvidence() + assert.Equal(0, len(priorityEv)) + assert.Equal(0, len(pendingEv)) + + priority := 10 + ev := newMockGoodEvidence(2, 1, []byte("val1")) + + added, err := store.AddNewEvidence(ev, priority) + assert.Nil(err) + assert.True(added) + + // get the evidence. verify. should be uncommitted + ei := store.GetEvidence(ev.Height(), ev.Hash()) + assert.Equal(ev, ei.Evidence) + assert.Equal(priority, ei.Priority) + assert.False(ei.Committed) + + // new evidence should be returns in priority/pending + priorityEv = store.PriorityEvidence() + pendingEv = store.PendingEvidence() + assert.Equal(1, len(priorityEv)) + assert.Equal(1, len(pendingEv)) + + // priority is now empty + store.MarkEvidenceAsBroadcasted(ev) + priorityEv = store.PriorityEvidence() + pendingEv = store.PendingEvidence() + assert.Equal(0, len(priorityEv)) + assert.Equal(1, len(pendingEv)) + + // priority and pending are now empty + store.MarkEvidenceAsCommitted(ev) + priorityEv = store.PriorityEvidence() + pendingEv = store.PendingEvidence() + assert.Equal(0, len(priorityEv)) + assert.Equal(0, len(pendingEv)) + + // evidence should show committed + ei = store.GetEvidence(ev.Height(), ev.Hash()) + assert.Equal(ev, ei.Evidence) + assert.Equal(priority, ei.Priority) + assert.True(ei.Committed) +} + +func TestStorePriority(t *testing.T) { + assert := assert.New(t) + + db := dbm.NewMemDB() + store := NewEvidenceStore(db) + + // sorted by priority and then height + cases := []struct { + ev MockGoodEvidence + priority int + }{ + {newMockGoodEvidence(2, 1, []byte("val1")), 17}, + {newMockGoodEvidence(5, 2, []byte("val2")), 15}, + {newMockGoodEvidence(10, 2, []byte("val2")), 13}, + {newMockGoodEvidence(100, 2, []byte("val2")), 11}, + {newMockGoodEvidence(90, 2, []byte("val2")), 11}, + {newMockGoodEvidence(80, 2, []byte("val2")), 11}, + } + + for _, c := range cases { + added, err := store.AddNewEvidence(c.ev, c.priority) + assert.True(added) + assert.Nil(err) + } + + evList := store.PriorityEvidence() + for i, ev := range evList { + assert.Equal(ev, cases[i].ev) + } +} + +//------------------------------------------- +const ( + evidenceTypeMock = byte(0x01) +) + +var _ = wire.RegisterInterface( + struct{ types.Evidence }{}, + wire.ConcreteType{MockGoodEvidence{}, evidenceTypeMock}, +) + +type MockGoodEvidence struct { + Height_ int + Address_ []byte + Index_ int +} + +func newMockGoodEvidence(height, index int, address []byte) MockGoodEvidence { + return MockGoodEvidence{height, address, index} +} + +func (e MockGoodEvidence) Height() int { return e.Height_ } +func (e MockGoodEvidence) Address() []byte { return e.Address_ } +func (e MockGoodEvidence) Index() int { return e.Index_ } +func (e MockGoodEvidence) Hash() []byte { return []byte{byte(e.Index_)} } +func (e MockGoodEvidence) Verify(chainID string) error { return nil } +func (e MockGoodEvidence) Equal(ev types.Evidence) bool { + e2 := ev.(MockGoodEvidence) + return e.Height_ == e2.Height_ && + bytes.Equal(e.Address_, e2.Address_) && + e.Index_ == e2.Index_ +} +func (e MockGoodEvidence) String() string { + return fmt.Sprintf("GoodEvidence: %d/%s/%d", e.Height_, e.Address_, e.Index_) +} + +type MockBadEvidence struct { + MockGoodEvidence +} + +func (e MockBadEvidence) Verify(chainID string) error { return fmt.Errorf("MockBadEvidence") } +func (e MockBadEvidence) Equal(ev types.Evidence) bool { + e2 := ev.(MockBadEvidence) + return e.Height_ == e2.Height_ && + bytes.Equal(e.Address_, e2.Address_) && + e.Index_ == e2.Index_ +} +func (e MockBadEvidence) String() string { + return fmt.Sprintf("BadEvidence: %d/%s/%d", e.Height_, e.Address_, e.Index_) +} diff --git a/types/evidence.go b/types/evidence.go index cb433ced..e270a0de 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" "github.com/tendermint/tmlibs/merkle" ) @@ -78,6 +79,17 @@ func (evl EvidenceList) Has(evidence Evidence) bool { //------------------------------------------- +const ( + evidenceTypeDuplicateVote = byte(0x01) +) + +var _ = wire.RegisterInterface( + struct{ Evidence }{}, + wire.ConcreteType{&DuplicateVoteEvidence{}, evidenceTypeDuplicateVote}, +) + +//------------------------------------------- + // DuplicateVoteEvidence contains evidence a validator signed two conflicting votes. type DuplicateVoteEvidence struct { PubKey crypto.PubKey