Browse Source

Merge pull request #3286 from tendermint/bucky/fix-duplicate-evidence

Fixes for duplicate evidence
release/v0.30.0
Ethan Buchman 6 years ago
committed by GitHub
parent
commit
6b1b595951
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 128 additions and 34 deletions
  1. +15
    -0
      CHANGELOG_PENDING.md
  2. +1
    -0
      consensus/reactor_test.go
  3. +8
    -1
      evidence/pool.go
  4. +23
    -2
      evidence/pool_test.go
  5. +2
    -2
      evidence/reactor_test.go
  6. +21
    -17
      evidence/store.go
  7. +19
    -3
      evidence/store_test.go
  8. +1
    -2
      node/node.go
  9. +2
    -4
      node/node_test.go
  10. +1
    -2
      state/execution.go
  11. +4
    -0
      state/services.go
  12. +6
    -1
      state/validation.go
  13. +25
    -0
      state/validation_test.go

+ 15
- 0
CHANGELOG_PENDING.md View File

@ -6,8 +6,23 @@ Special thanks to external contributors on this release:
### BREAKING CHANGES: ### BREAKING CHANGES:
* CLI/RPC/Config
* Apps
* Go API
* Blockchain Protocol
- [types] Reject blocks which contain already committed evidence
* P2P Protocol
### FEATURES: ### FEATURES:
### IMPROVEMENTS: ### IMPROVEMENTS:
### BUG FIXES: ### BUG FIXES:
- [evidence] Do not store evidence which was already marked as committed

+ 1
- 0
consensus/reactor_test.go View File

@ -211,6 +211,7 @@ func (m *mockEvidencePool) Update(block *types.Block, state sm.State) {
} }
m.height++ m.height++
} }
func (m *mockEvidencePool) IsCommitted(types.Evidence) bool { return false }
//------------------------------------ //------------------------------------


+ 8
- 1
evidence/pool.go View File

@ -28,7 +28,8 @@ type EvidencePool struct {
state sm.State state sm.State
} }
func NewEvidencePool(stateDB dbm.DB, evidenceStore *EvidenceStore) *EvidencePool {
func NewEvidencePool(stateDB, evidenceDB dbm.DB) *EvidencePool {
evidenceStore := NewEvidenceStore(evidenceDB)
evpool := &EvidencePool{ evpool := &EvidencePool{
stateDB: stateDB, stateDB: stateDB,
state: sm.LoadState(stateDB), state: sm.LoadState(stateDB),
@ -132,6 +133,12 @@ func (evpool *EvidencePool) MarkEvidenceAsCommitted(height int64, evidence []typ
} }
// IsCommitted returns true if we have already seen this exact evidence and it is already marked as committed.
func (evpool *EvidencePool) IsCommitted(evidence types.Evidence) bool {
ei := evpool.evidenceStore.getEvidenceInfo(evidence)
return ei.Evidence != nil && ei.Committed
}
func (evpool *EvidencePool) removeEvidence(height, maxAge int64, blockEvidenceMap map[string]struct{}) { func (evpool *EvidencePool) removeEvidence(height, maxAge int64, blockEvidenceMap map[string]struct{}) {
for e := evpool.evidenceList.Front(); e != nil; e = e.Next() { for e := evpool.evidenceList.Front(); e != nil; e = e.Next() {
ev := e.Value.(types.Evidence) ev := e.Value.(types.Evidence)


+ 23
- 2
evidence/pool_test.go View File

@ -56,8 +56,8 @@ func TestEvidencePool(t *testing.T) {
valAddr := []byte("val1") valAddr := []byte("val1")
height := int64(5) height := int64(5)
stateDB := initializeValidatorState(valAddr, height) stateDB := initializeValidatorState(valAddr, height)
store := NewEvidenceStore(dbm.NewMemDB())
pool := NewEvidencePool(stateDB, store)
evidenceDB := dbm.NewMemDB()
pool := NewEvidencePool(stateDB, evidenceDB)
goodEvidence := types.NewMockGoodEvidence(height, 0, valAddr) goodEvidence := types.NewMockGoodEvidence(height, 0, valAddr)
badEvidence := types.MockBadEvidence{goodEvidence} badEvidence := types.MockBadEvidence{goodEvidence}
@ -84,3 +84,24 @@ func TestEvidencePool(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, pool.evidenceList.Len()) assert.Equal(t, 1, pool.evidenceList.Len())
} }
func TestEvidencePoolIsCommitted(t *testing.T) {
// Initialization:
valAddr := []byte("validator_address")
height := int64(42)
stateDB := initializeValidatorState(valAddr, height)
evidenceDB := dbm.NewMemDB()
pool := NewEvidencePool(stateDB, evidenceDB)
// evidence not seen yet:
evidence := types.NewMockGoodEvidence(height, 0, valAddr)
assert.False(t, pool.IsCommitted(evidence))
// evidence seen but not yet committed:
assert.NoError(t, pool.AddEvidence(evidence))
assert.False(t, pool.IsCommitted(evidence))
// evidence seen and committed:
pool.MarkEvidenceAsCommitted(height, []types.Evidence{evidence})
assert.True(t, pool.IsCommitted(evidence))
}

+ 2
- 2
evidence/reactor_test.go View File

@ -37,8 +37,8 @@ func makeAndConnectEvidenceReactors(config *cfg.Config, stateDBs []dbm.DB) []*Ev
logger := evidenceLogger() logger := evidenceLogger()
for i := 0; i < N; i++ { for i := 0; i < N; i++ {
store := NewEvidenceStore(dbm.NewMemDB())
pool := NewEvidencePool(stateDBs[i], store)
evidenceDB := dbm.NewMemDB()
pool := NewEvidencePool(stateDBs[i], evidenceDB)
reactors[i] = NewEvidenceReactor(pool) reactors[i] = NewEvidenceReactor(pool)
reactors[i].SetLogger(logger.With("validator", i)) reactors[i].SetLogger(logger.With("validator", i))
} }


+ 21
- 17
evidence/store.go View File

@ -117,32 +117,33 @@ func (store *EvidenceStore) listEvidence(prefixKey string, maxNum int64) (eviden
return evidence return evidence
} }
// GetEvidence fetches the evidence with the given height and hash.
func (store *EvidenceStore) GetEvidence(height int64, hash []byte) *EvidenceInfo {
// GetEvidenceInfo fetches the EvidenceInfo with the given height and hash.
// If not found, ei.Evidence is nil.
func (store *EvidenceStore) GetEvidenceInfo(height int64, 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 EvidenceInfo{}
} }
var ei EvidenceInfo var ei EvidenceInfo
err := cdc.UnmarshalBinaryBare(val, &ei) err := cdc.UnmarshalBinaryBare(val, &ei)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return &ei
return ei
} }
// AddNewEvidence adds the given evidence to the database. // AddNewEvidence adds the given evidence to the database.
// It returns false if the evidence is already stored. // It returns false if the evidence is already stored.
func (store *EvidenceStore) AddNewEvidence(evidence types.Evidence, priority int64) bool { func (store *EvidenceStore) AddNewEvidence(evidence types.Evidence, priority int64) bool {
// check if we already have seen it // check if we already have seen it
ei_ := store.GetEvidence(evidence.Height(), evidence.Hash())
if ei_ != nil && ei_.Evidence != nil {
ei := store.getEvidenceInfo(evidence)
if ei.Evidence != nil {
return false return false
} }
ei := EvidenceInfo{
ei = EvidenceInfo{
Committed: false, Committed: false,
Priority: priority, Priority: priority,
Evidence: evidence, Evidence: evidence,
@ -165,6 +166,11 @@ func (store *EvidenceStore) AddNewEvidence(evidence types.Evidence, priority int
// MarkEvidenceAsBroadcasted removes evidence from Outqueue. // MarkEvidenceAsBroadcasted removes evidence from Outqueue.
func (store *EvidenceStore) MarkEvidenceAsBroadcasted(evidence types.Evidence) { func (store *EvidenceStore) MarkEvidenceAsBroadcasted(evidence types.Evidence) {
ei := store.getEvidenceInfo(evidence) ei := store.getEvidenceInfo(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) key := keyOutqueue(evidence, ei.Priority)
store.db.Delete(key) store.db.Delete(key)
} }
@ -177,8 +183,12 @@ func (store *EvidenceStore) MarkEvidenceAsCommitted(evidence types.Evidence) {
pendingKey := keyPending(evidence) pendingKey := keyPending(evidence)
store.db.Delete(pendingKey) store.db.Delete(pendingKey)
ei := store.getEvidenceInfo(evidence)
ei.Committed = true
// committed EvidenceInfo doens't need priority
ei := EvidenceInfo{
Committed: true,
Evidence: evidence,
Priority: 0,
}
lookupKey := keyLookup(evidence) lookupKey := keyLookup(evidence)
store.db.SetSync(lookupKey, cdc.MustMarshalBinaryBare(ei)) store.db.SetSync(lookupKey, cdc.MustMarshalBinaryBare(ei))
@ -187,13 +197,7 @@ func (store *EvidenceStore) MarkEvidenceAsCommitted(evidence types.Evidence) {
//--------------------------------------------------- //---------------------------------------------------
// utils // utils
// getEvidenceInfo is convenience for calling GetEvidenceInfo if we have the full evidence.
func (store *EvidenceStore) getEvidenceInfo(evidence types.Evidence) EvidenceInfo { func (store *EvidenceStore) getEvidenceInfo(evidence types.Evidence) EvidenceInfo {
key := keyLookup(evidence)
var ei EvidenceInfo
b := store.db.Get(key)
err := cdc.UnmarshalBinaryBare(b, &ei)
if err != nil {
panic(err)
}
return ei
return store.GetEvidenceInfo(evidence.Height(), evidence.Hash())
} }

+ 19
- 3
evidence/store_test.go View File

@ -27,6 +27,21 @@ func TestStoreAddDuplicate(t *testing.T) {
assert.False(added) assert.False(added)
} }
func TestStoreCommitDuplicate(t *testing.T) {
assert := assert.New(t)
db := dbm.NewMemDB()
store := NewEvidenceStore(db)
priority := int64(10)
ev := types.NewMockGoodEvidence(2, 1, []byte("val1"))
store.MarkEvidenceAsCommitted(ev)
added := store.AddNewEvidence(ev, priority)
assert.False(added)
}
func TestStoreMark(t *testing.T) { func TestStoreMark(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
@ -46,7 +61,7 @@ func TestStoreMark(t *testing.T) {
assert.True(added) assert.True(added)
// get the evidence. verify. should be uncommitted // get the evidence. verify. should be uncommitted
ei := store.GetEvidence(ev.Height(), ev.Hash())
ei := store.GetEvidenceInfo(ev.Height(), ev.Hash())
assert.Equal(ev, ei.Evidence) assert.Equal(ev, ei.Evidence)
assert.Equal(priority, ei.Priority) assert.Equal(priority, ei.Priority)
assert.False(ei.Committed) assert.False(ei.Committed)
@ -72,9 +87,10 @@ func TestStoreMark(t *testing.T) {
assert.Equal(0, len(pendingEv)) assert.Equal(0, len(pendingEv))
// evidence should show committed // evidence should show committed
ei = store.GetEvidence(ev.Height(), ev.Hash())
newPriority := int64(0)
ei = store.GetEvidenceInfo(ev.Height(), ev.Hash())
assert.Equal(ev, ei.Evidence) assert.Equal(ev, ei.Evidence)
assert.Equal(priority, ei.Priority)
assert.Equal(newPriority, ei.Priority)
assert.True(ei.Committed) assert.True(ei.Committed)
} }


+ 1
- 2
node/node.go View File

@ -345,8 +345,7 @@ func NewNode(config *cfg.Config,
return nil, err return nil, err
} }
evidenceLogger := logger.With("module", "evidence") evidenceLogger := logger.With("module", "evidence")
evidenceStore := evidence.NewEvidenceStore(evidenceDB)
evidencePool := evidence.NewEvidencePool(stateDB, evidenceStore)
evidencePool := evidence.NewEvidencePool(stateDB, evidenceDB)
evidencePool.SetLogger(evidenceLogger) evidencePool.SetLogger(evidenceLogger)
evidenceReactor := evidence.NewEvidenceReactor(evidencePool) evidenceReactor := evidence.NewEvidenceReactor(evidencePool)
evidenceReactor.SetLogger(evidenceLogger) evidenceReactor.SetLogger(evidenceLogger)


+ 2
- 4
node/node_test.go View File

@ -227,11 +227,10 @@ func TestCreateProposalBlock(t *testing.T) {
mempool.SetLogger(logger) mempool.SetLogger(logger)
// Make EvidencePool // Make EvidencePool
types.RegisterMockEvidencesGlobal()
types.RegisterMockEvidencesGlobal() // XXX!
evidence.RegisterMockEvidences() evidence.RegisterMockEvidences()
evidenceDB := dbm.NewMemDB() evidenceDB := dbm.NewMemDB()
evidenceStore := evidence.NewEvidenceStore(evidenceDB)
evidencePool := evidence.NewEvidencePool(stateDB, evidenceStore)
evidencePool := evidence.NewEvidencePool(stateDB, evidenceDB)
evidencePool.SetLogger(logger) evidencePool.SetLogger(logger)
// fill the evidence pool with more evidence // fill the evidence pool with more evidence
@ -270,7 +269,6 @@ func TestCreateProposalBlock(t *testing.T) {
err = blockExec.ValidateBlock(state, block) err = blockExec.ValidateBlock(state, block)
assert.NoError(t, err) assert.NoError(t, err)
} }
func state(nVals int, height int64) (sm.State, dbm.DB) { func state(nVals int, height int64) (sm.State, dbm.DB) {


+ 1
- 2
state/execution.go View File

@ -94,7 +94,6 @@ func (blockExec *BlockExecutor) CreateProposalBlock(
txs := blockExec.mempool.ReapMaxBytesMaxGas(maxDataBytes, maxGas) txs := blockExec.mempool.ReapMaxBytesMaxGas(maxDataBytes, maxGas)
return state.MakeBlock(height, txs, commit, evidence, proposerAddr) return state.MakeBlock(height, txs, commit, evidence, proposerAddr)
} }
// ValidateBlock validates the given block against the given state. // ValidateBlock validates the given block against the given state.
@ -102,7 +101,7 @@ func (blockExec *BlockExecutor) CreateProposalBlock(
// Validation does not mutate state, but does require historical information from the stateDB, // Validation does not mutate state, but does require historical information from the stateDB,
// ie. to verify evidence from a validator at an old height. // ie. to verify evidence from a validator at an old height.
func (blockExec *BlockExecutor) ValidateBlock(state State, block *types.Block) error { func (blockExec *BlockExecutor) ValidateBlock(state State, block *types.Block) error {
return validateBlock(blockExec.db, state, block)
return validateBlock(blockExec.evpool, blockExec.db, state, block)
} }
// ApplyBlock validates the block against the state, executes it against the app, // ApplyBlock validates the block against the state, executes it against the app,


+ 4
- 0
state/services.go View File

@ -80,10 +80,13 @@ type BlockStore interface {
// evidence pool // evidence pool
// EvidencePool defines the EvidencePool interface used by the ConsensusState. // EvidencePool defines the EvidencePool interface used by the ConsensusState.
// Get/Set/Commit
type EvidencePool interface { type EvidencePool interface {
PendingEvidence(int64) []types.Evidence PendingEvidence(int64) []types.Evidence
AddEvidence(types.Evidence) error AddEvidence(types.Evidence) error
Update(*types.Block, State) Update(*types.Block, State)
// IsCommitted indicates if this evidence was already marked committed in another block.
IsCommitted(types.Evidence) bool
} }
// MockMempool is an empty implementation of a Mempool, useful for testing. // MockMempool is an empty implementation of a Mempool, useful for testing.
@ -92,3 +95,4 @@ type MockEvidencePool struct{}
func (m MockEvidencePool) PendingEvidence(int64) []types.Evidence { return nil } func (m MockEvidencePool) PendingEvidence(int64) []types.Evidence { return nil }
func (m MockEvidencePool) AddEvidence(types.Evidence) error { return nil } func (m MockEvidencePool) AddEvidence(types.Evidence) error { return nil }
func (m MockEvidencePool) Update(*types.Block, State) {} func (m MockEvidencePool) Update(*types.Block, State) {}
func (m MockEvidencePool) IsCommitted(types.Evidence) bool { return false }

+ 6
- 1
state/validation.go View File

@ -13,7 +13,7 @@ import (
//----------------------------------------------------- //-----------------------------------------------------
// Validate block // Validate block
func validateBlock(stateDB dbm.DB, state State, block *types.Block) error {
func validateBlock(evidencePool EvidencePool, stateDB dbm.DB, state State, block *types.Block) error {
// Validate internal consistency. // Validate internal consistency.
if err := block.ValidateBasic(); err != nil { if err := block.ValidateBasic(); err != nil {
return err return err
@ -145,6 +145,9 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error {
if err := VerifyEvidence(stateDB, state, ev); err != nil { if err := VerifyEvidence(stateDB, state, ev); err != nil {
return types.NewErrEvidenceInvalid(ev, err) return types.NewErrEvidenceInvalid(ev, err)
} }
if evidencePool != nil && evidencePool.IsCommitted(ev) {
return types.NewErrEvidenceInvalid(ev, errors.New("evidence was already committed"))
}
} }
// NOTE: We can't actually verify it's the right proposer because we dont // NOTE: We can't actually verify it's the right proposer because we dont
@ -185,6 +188,8 @@ func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence) error
// The address must have been an active validator at the height. // The address must have been an active validator at the height.
// NOTE: we will ignore evidence from H if the key was not a validator // NOTE: we will ignore evidence from H if the key was not a validator
// at H, even if it is a validator at some nearby H' // at H, even if it is a validator at some nearby H'
// XXX: this makes lite-client bisection as is unsafe
// See https://github.com/tendermint/tendermint/issues/3244
ev := evidence ev := evidence
height, addr := ev.Height(), ev.Address() height, addr := ev.Height(), ev.Address()
_, val := valset.GetByAddress(addr) _, val := valset.GetByAddress(addr)


+ 25
- 0
state/validation_test.go View File

@ -121,6 +121,31 @@ func TestValidateBlockEvidence(t *testing.T) {
require.True(t, ok) require.True(t, ok)
} }
// always returns true if asked if any evidence was already committed.
type mockEvPoolAlwaysCommitted struct{}
func (m mockEvPoolAlwaysCommitted) PendingEvidence(int64) []types.Evidence { return nil }
func (m mockEvPoolAlwaysCommitted) AddEvidence(types.Evidence) error { return nil }
func (m mockEvPoolAlwaysCommitted) Update(*types.Block, State) {}
func (m mockEvPoolAlwaysCommitted) IsCommitted(types.Evidence) bool { return true }
func TestValidateFailBlockOnCommittedEvidence(t *testing.T) {
var height int64 = 1
state, stateDB := state(1, int(height))
blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), nil, nil, mockEvPoolAlwaysCommitted{})
// A block with a couple pieces of evidence passes.
block := makeBlock(state, height)
addr, _ := state.Validators.GetByIndex(0)
alreadyCommittedEvidence := types.NewMockGoodEvidence(height, 0, addr)
block.Evidence.Evidence = []types.Evidence{alreadyCommittedEvidence}
block.EvidenceHash = block.Evidence.Hash()
err := blockExec.ValidateBlock(state, block)
require.Error(t, err)
require.IsType(t, err, &types.ErrEvidenceInvalid{})
}
/* /*
TODO(#2589): TODO(#2589):
- test unmarshalling BlockParts that are too big into a Block that - test unmarshalling BlockParts that are too big into a Block that


Loading…
Cancel
Save