Browse Source

evidence: store tests and fixes

pull/592/head
Ethan Buchman 7 years ago
parent
commit
c13e93d63e
3 changed files with 212 additions and 22 deletions
  1. +34
    -22
      evidence/store.go
  2. +166
    -0
      evidence/store_test.go
  3. +12
    -0
      types/evidence.go

+ 34
- 22
evidence/store.go View File

@ -25,12 +25,12 @@ Impl:
Schema for indexing evidence (note you need both height and hash to find a piece of evidence): Schema for indexing evidence (note you need both height and hash to find a piece of evidence):
"evidence-lookup"/<evidence-height>/<evidence-hash> -> evidenceInfo
"evidence-outqueue"/<priority>/<evidence-height>/<evidence-hash> -> evidenceInfo
"evidence-pending"/<evidence-height>/<evidence-hash> -> evidenceInfo
"evidence-lookup"/<evidence-height>/<evidence-hash> -> EvidenceInfo
"evidence-outqueue"/<priority>/<evidence-height>/<evidence-hash> -> EvidenceInfo
"evidence-pending"/<evidence-height>/<evidence-hash> -> EvidenceInfo
*/ */
type evidenceInfo struct {
type EvidenceInfo struct {
Committed bool Committed bool
Priority int Priority int
Evidence types.Evidence Evidence types.Evidence
@ -46,16 +46,21 @@ func keyLookup(evidence types.Evidence) []byte {
return keyLookupFromHeightAndHash(evidence.Height(), evidence.Hash()) 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 { 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 { 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 { 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 { 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. // PriorityEvidence returns the evidence from the outqueue, sorted by highest priority.
func (store *EvidenceStore) PriorityEvidence() (evidence []types.Evidence) { 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. // PendingEvidence returns all known uncommitted evidence.
@ -93,7 +103,7 @@ func (store *EvidenceStore) ListEvidence(prefixKey string) (evidence []types.Evi
for iter.Next() { for iter.Next() {
val := iter.Value() val := iter.Value()
var ei evidenceInfo
var ei EvidenceInfo
wire.ReadBinaryBytes(val, &ei) wire.ReadBinaryBytes(val, &ei)
evidence = append(evidence, ei.Evidence) 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. // 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) key := keyLookupFromHeightAndHash(height, hash)
val := store.db.Get(key) val := store.db.Get(key)
if len(val) == 0 { if len(val) == 0 {
return nil 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. // AddNewEvidence adds the given evidence to the database.
func (store *EvidenceStore) AddNewEvidence(evidence types.Evidence, priority int) (bool, error) { func (store *EvidenceStore) AddNewEvidence(evidence types.Evidence, priority int) (bool, error) {
// check if we already have seen it // 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 return false, nil
} }
ei := evidenceInfo{
ei := EvidenceInfo{
Committed: false, Committed: false,
Priority: priority, Priority: priority,
Evidence: evidence, Evidence: evidence,
@ -152,8 +163,8 @@ func (store *EvidenceStore) MarkEvidenceAsCommitted(evidence types.Evidence) {
// if its committed, its been broadcast // if its committed, its been broadcast
store.MarkEvidenceAsBroadcasted(evidence) store.MarkEvidenceAsBroadcasted(evidence)
key := keyPending(evidence)
store.db.Delete(key)
pendingKey := keyPending(evidence)
store.db.Delete(pendingKey)
ei := store.getEvidenceInfo(evidence) ei := store.getEvidenceInfo(evidence)
ei.Committed = true 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. // 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 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. // 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 // utils
func (store *EvidenceStore) getEvidenceInfo(evidence types.Evidence) evidenceInfo {
func (store *EvidenceStore) getEvidenceInfo(evidence types.Evidence) EvidenceInfo {
key := keyLookup(evidence) key := keyLookup(evidence)
var ei evidenceInfo
var ei EvidenceInfo
b := store.db.Get(key) b := store.db.Get(key)
wire.ReadBinaryBytes(b, &ei) wire.ReadBinaryBytes(b, &ei)
return ei return ei


+ 166
- 0
evidence/store_test.go View File

@ -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_)
}

+ 12
- 0
types/evidence.go View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/tendermint/go-crypto" "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/tmlibs/merkle" "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. // DuplicateVoteEvidence contains evidence a validator signed two conflicting votes.
type DuplicateVoteEvidence struct { type DuplicateVoteEvidence struct {
PubKey crypto.PubKey PubKey crypto.PubKey


Loading…
Cancel
Save