Browse Source

fix EvidencePool and VerifyEvidence

pull/1015/head
Ethan Buchman 7 years ago
parent
commit
cb845ebff5
8 changed files with 134 additions and 73 deletions
  1. +42
    -21
      evidence/pool.go
  2. +36
    -3
      evidence/pool_test.go
  3. +20
    -9
      evidence/reactor_test.go
  4. +1
    -1
      node/node.go
  5. +5
    -3
      state/execution.go
  6. +12
    -22
      state/validation.go
  7. +14
    -10
      state/validation_test.go
  8. +4
    -4
      types/services.go

+ 42
- 21
evidence/pool.go View File

@ -1,6 +1,10 @@
package evidence package evidence
import ( import (
"fmt"
"sync"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
sm "github.com/tendermint/tendermint/state" sm "github.com/tendermint/tendermint/state"
@ -14,17 +18,21 @@ type EvidencePool struct {
evidenceStore *EvidenceStore evidenceStore *EvidenceStore
state sm.State
params types.EvidenceParams
// needed to load validators to verify evidence
stateDB dbm.DB
// latest state
mtx sync.Mutex
state sm.State
// never close // never close
evidenceChan chan types.Evidence evidenceChan chan types.Evidence
} }
func NewEvidencePool(params types.EvidenceParams, evidenceStore *EvidenceStore, state sm.State) *EvidencePool {
func NewEvidencePool(stateDB dbm.DB, evidenceStore *EvidenceStore) *EvidencePool {
evpool := &EvidencePool{ evpool := &EvidencePool{
state: state,
params: params,
stateDB: stateDB,
state: sm.LoadState(stateDB),
logger: log.NewNopLogger(), logger: log.NewNopLogger(),
evidenceStore: evidenceStore, evidenceStore: evidenceStore,
evidenceChan: make(chan types.Evidence), evidenceChan: make(chan types.Evidence),
@ -52,31 +60,44 @@ func (evpool *EvidencePool) PendingEvidence() []types.Evidence {
return evpool.evidenceStore.PendingEvidence() return evpool.evidenceStore.PendingEvidence()
} }
// State returns the current state of the evpool.
func (evpool *EvidencePool) State() sm.State {
evpool.mtx.Lock()
defer evpool.mtx.Unlock()
return evpool.state
}
// Update loads the latest
func (evpool *EvidencePool) Update(block *types.Block) {
evpool.mtx.Lock()
defer evpool.mtx.Unlock()
state := sm.LoadState(evpool.stateDB)
if state.LastBlockHeight != block.Height {
panic(fmt.Sprintf("EvidencePool.Update: loaded state with height %d when block.Height=%d", state.LastBlockHeight, block.Height))
}
evpool.state = state
// NOTE: shouldn't need the mutex
evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence)
}
// AddEvidence checks the evidence is valid and adds it to the pool. // AddEvidence checks the evidence is valid and adds it to the pool.
// Blocks on the EvidenceChan. // Blocks on the EvidenceChan.
func (evpool *EvidencePool) AddEvidence(evidence types.Evidence) (err error) { func (evpool *EvidencePool) AddEvidence(evidence types.Evidence) (err error) {
// TODO: check if we already have evidence for this // TODO: check if we already have evidence for this
// validator at this height so we dont get spammed // validator at this height so we dont get spammed
if err := sm.VerifyEvidence(evpool.state, evidence); err != nil {
if err := sm.VerifyEvidence(evpool.stateDB, evpool.State(), evidence); err != nil {
return err return err
} }
var priority int64
/* // Needs a db ...
// TODO: if err is just that we cant find it cuz we pruned, ignore.
// TODO: if its actually bad evidence, punish peer
valset, err := LoadValidators(s.db, ev.Height())
if err != nil {
// XXX/TODO: what do we do if we can't load the valset?
// eg. if we have pruned the state or height is too high?
return err
}
if err := VerifyEvidenceValidator(valSet, ev); err != nil {
return types.NewEvidenceInvalidErr(ev, err)
}
*/
// fetch the validator and return its voting power as its priority
// TODO: something better ?
valset, _ := sm.LoadValidators(evpool.stateDB, evidence.Height())
_, val := valset.GetByAddress(evidence.Address())
priority := val.VotingPower
added := evpool.evidenceStore.AddNewEvidence(evidence, priority) added := evpool.evidenceStore.AddNewEvidence(evidence, priority)
if !added { if !added {


+ 36
- 3
evidence/pool_test.go View File

@ -3,6 +3,7 @@ package evidence
import ( import (
"sync" "sync"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -13,14 +14,46 @@ import (
var mockState = sm.State{} var mockState = sm.State{}
func initializeValidatorState(valAddr []byte, height int64) dbm.DB {
stateDB := dbm.NewMemDB()
// create validator set and state
valSet := &types.ValidatorSet{
Validators: []*types.Validator{
{Address: valAddr},
},
}
state := sm.State{
LastBlockHeight: 0,
LastBlockTime: time.Now(),
Validators: valSet,
LastHeightValidatorsChanged: 1,
ConsensusParams: types.ConsensusParams{
EvidenceParams: types.EvidenceParams{
MaxAge: 1000000,
},
},
}
// save all states up to height
for i := int64(0); i < height; i++ {
state.LastBlockHeight = i
sm.SaveState(stateDB, state)
}
return stateDB
}
func TestEvidencePool(t *testing.T) { func TestEvidencePool(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
params := types.EvidenceParams{}
valAddr := []byte("val1")
height := int64(5)
stateDB := initializeValidatorState(valAddr, height)
store := NewEvidenceStore(dbm.NewMemDB()) store := NewEvidenceStore(dbm.NewMemDB())
pool := NewEvidencePool(params, store, mockState)
pool := NewEvidencePool(stateDB, store)
goodEvidence := newMockGoodEvidence(5, 1, []byte("val1"))
goodEvidence := newMockGoodEvidence(height, 0, valAddr)
badEvidence := MockBadEvidence{goodEvidence} badEvidence := MockBadEvidence{goodEvidence}
err := pool.AddEvidence(badEvidence) err := pool.AddEvidence(badEvidence)


+ 20
- 9
evidence/reactor_test.go View File

@ -32,14 +32,14 @@ func evidenceLogger() log.Logger {
} }
// connect N evidence reactors through N switches // connect N evidence reactors through N switches
func makeAndConnectEvidenceReactors(config *cfg.Config, N int) []*EvidenceReactor {
func makeAndConnectEvidenceReactors(config *cfg.Config, stateDBs []dbm.DB) []*EvidenceReactor {
N := len(stateDBs)
reactors := make([]*EvidenceReactor, N) reactors := make([]*EvidenceReactor, N)
logger := evidenceLogger() logger := evidenceLogger()
for i := 0; i < N; i++ { for i := 0; i < N; i++ {
params := types.EvidenceParams{}
store := NewEvidenceStore(dbm.NewMemDB()) store := NewEvidenceStore(dbm.NewMemDB())
pool := NewEvidencePool(params, store, mockState)
pool := NewEvidencePool(stateDBs[i], store)
reactors[i] = NewEvidenceReactor(pool) reactors[i] = NewEvidenceReactor(pool)
reactors[i].SetLogger(logger.With("validator", i)) reactors[i].SetLogger(logger.With("validator", i))
} }
@ -98,10 +98,10 @@ func _waitForEvidence(t *testing.T, wg *sync.WaitGroup, evs types.EvidenceList,
wg.Done() wg.Done()
} }
func sendEvidence(t *testing.T, evpool *EvidencePool, n int) types.EvidenceList {
func sendEvidence(t *testing.T, evpool *EvidencePool, valAddr []byte, n int) types.EvidenceList {
evList := make([]types.Evidence, n) evList := make([]types.Evidence, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
ev := newMockGoodEvidence(int64(i), 2, []byte("val"))
ev := newMockGoodEvidence(int64(i+1), 0, valAddr)
err := evpool.AddEvidence(ev) err := evpool.AddEvidence(ev)
assert.Nil(t, err) assert.Nil(t, err)
evList[i] = ev evList[i] = ev
@ -110,17 +110,28 @@ func sendEvidence(t *testing.T, evpool *EvidencePool, n int) types.EvidenceList
} }
var ( var (
NUM_EVIDENCE = 1000
NUM_EVIDENCE = 1
TIMEOUT = 120 * time.Second // ridiculously high because CircleCI is slow TIMEOUT = 120 * time.Second // ridiculously high because CircleCI is slow
) )
func TestReactorBroadcastEvidence(t *testing.T) { func TestReactorBroadcastEvidence(t *testing.T) {
config := cfg.TestConfig() config := cfg.TestConfig()
N := 7 N := 7
reactors := makeAndConnectEvidenceReactors(config, N)
// send a bunch of evidence to the first reactor's evpool
// create statedb for everyone
stateDBs := make([]dbm.DB, N)
valAddr := []byte("myval")
// we need validators saved for heights at least as high as we have evidence for
height := int64(NUM_EVIDENCE) + 10
for i := 0; i < N; i++ {
stateDBs[i] = initializeValidatorState(valAddr, height)
}
// make reactors from statedb
reactors := makeAndConnectEvidenceReactors(config, stateDBs)
// send a bunch of valid evidence to the first reactor's evpool
// and wait for them all to be received in the others // and wait for them all to be received in the others
evList := sendEvidence(t, reactors[0].evpool, NUM_EVIDENCE)
evList := sendEvidence(t, reactors[0].evpool, valAddr, NUM_EVIDENCE)
waitForEvidence(t, evList, reactors) waitForEvidence(t, evList, reactors)
} }

+ 1
- 1
node/node.go View File

@ -208,7 +208,7 @@ func NewNode(config *cfg.Config,
} }
evidenceLogger := logger.With("module", "evidence") evidenceLogger := logger.With("module", "evidence")
evidenceStore := evidence.NewEvidenceStore(evidenceDB) evidenceStore := evidence.NewEvidenceStore(evidenceDB)
evidencePool := evidence.NewEvidencePool(state.ConsensusParams.EvidenceParams, evidenceStore, state.Copy())
evidencePool := evidence.NewEvidencePool(stateDB, evidenceStore)
evidencePool.SetLogger(evidenceLogger) evidencePool.SetLogger(evidenceLogger)
evidenceReactor := evidence.NewEvidenceReactor(evidencePool) evidenceReactor := evidence.NewEvidenceReactor(evidencePool)
evidenceReactor.SetLogger(evidenceLogger) evidenceReactor.SetLogger(evidenceLogger)


+ 5
- 3
state/execution.go View File

@ -107,6 +107,11 @@ func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block
fail.Fail() // XXX fail.Fail() // XXX
// Update evpool now that state is saved
// TODO: handle the crash/recover scenario
// ie. (may need to call Update for last block)
blockExec.evpool.Update(block)
// events are fired after everything else // events are fired after everything else
// NOTE: if we crash between Commit and Save, events wont be fired during replay // NOTE: if we crash between Commit and Save, events wont be fired during replay
fireEvents(blockExec.logger, blockExec.eventBus, block, abciResponses) fireEvents(blockExec.logger, blockExec.eventBus, block, abciResponses)
@ -138,9 +143,6 @@ func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) {
blockExec.logger.Info("Committed state", "height", block.Height, "txs", block.NumTxs, "appHash", res.Data) blockExec.logger.Info("Committed state", "height", block.Height, "txs", block.NumTxs, "appHash", res.Data)
// Update evpool
blockExec.evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence)
// Update mempool. // Update mempool.
if err := blockExec.mempool.Update(block.Height, block.Txs); err != nil { if err := blockExec.mempool.Update(block.Height, block.Txs); err != nil {
return nil, err return nil, err


+ 12
- 22
state/validation.go View File

@ -79,20 +79,9 @@ func validateBlock(stateDB dbm.DB, s State, b *types.Block) error {
} }
for _, ev := range b.Evidence.Evidence { for _, ev := range b.Evidence.Evidence {
if err := VerifyEvidence(s, ev); err != nil {
if err := VerifyEvidence(stateDB, s, ev); err != nil {
return types.NewEvidenceInvalidErr(ev, err) return types.NewEvidenceInvalidErr(ev, err)
} }
/* // Needs a db ...
valset, err := LoadValidators(s.db, ev.Height())
if err != nil {
// XXX/TODO: what do we do if we can't load the valset?
// eg. if we have pruned the state or height is too high?
return err
}
if err := VerifyEvidenceValidator(valSet, ev); err != nil {
return types.NewEvidenceInvalidErr(ev, err)
}
*/
} }
return nil return nil
@ -103,7 +92,7 @@ func validateBlock(stateDB dbm.DB, s State, b *types.Block) error {
// VerifyEvidence verifies the evidence fully by checking it is internally // VerifyEvidence verifies the evidence fully by checking it is internally
// consistent and sufficiently recent. // consistent and sufficiently recent.
func VerifyEvidence(s State, evidence types.Evidence) error {
func VerifyEvidence(stateDB dbm.DB, s State, evidence types.Evidence) error {
height := s.LastBlockHeight height := s.LastBlockHeight
evidenceAge := height - evidence.Height() evidenceAge := height - evidence.Height()
@ -116,22 +105,23 @@ func VerifyEvidence(s State, evidence types.Evidence) error {
if err := evidence.Verify(s.ChainID); err != nil { if err := evidence.Verify(s.ChainID); err != nil {
return err return err
} }
return nil
}
// VerifyEvidenceValidator returns the voting power of the validator at the height of the evidence.
// It returns an error if the validator did not exist or does not match that loaded from the historical validator set.
func VerifyEvidenceValidator(valset *types.ValidatorSet, evidence types.Evidence) (priority int64, err error) {
valset, err := LoadValidators(stateDB, evidence.Height())
if err != nil {
// TODO: if err is just that we cant find it cuz we pruned, ignore.
// TODO: if its actually bad evidence, punish peer
return err
}
// The address must have been an active validator at the height // The address must have been an active validator at the height
ev := evidence ev := evidence
height, addr, idx := ev.Height(), ev.Address(), ev.Index() height, addr, idx := ev.Height(), ev.Address(), ev.Index()
valIdx, val := valset.GetByAddress(addr) valIdx, val := valset.GetByAddress(addr)
if val == nil { if val == nil {
return priority, fmt.Errorf("Address %X was not a validator at height %d", addr, height)
return fmt.Errorf("Address %X was not a validator at height %d", addr, height)
} else if idx != valIdx { } else if idx != valIdx {
return priority, fmt.Errorf("Address %X was validator %d at height %d, not %d", addr, valIdx, height, idx)
return fmt.Errorf("Address %X was validator %d at height %d, not %d", addr, valIdx, height, idx)
} }
priority = val.VotingPower
return priority, nil
return nil
} }

+ 14
- 10
state/validation_test.go View File

@ -4,61 +4,65 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
) )
func _TestValidateBlock(t *testing.T) {
func TestValidateBlock(t *testing.T) {
state := state() state := state()
blockExec := NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nil, nil, nil)
// proper block must pass // proper block must pass
block := makeBlock(state, 1) block := makeBlock(state, 1)
err := ValidateBlock(state, block)
err := blockExec.ValidateBlock(state, block)
require.NoError(t, err) require.NoError(t, err)
// wrong chain fails // wrong chain fails
block = makeBlock(state, 1) block = makeBlock(state, 1)
block.ChainID = "not-the-real-one" block.ChainID = "not-the-real-one"
err = ValidateBlock(state, block)
err = blockExec.ValidateBlock(state, block)
require.Error(t, err) require.Error(t, err)
// wrong height fails // wrong height fails
block = makeBlock(state, 1) block = makeBlock(state, 1)
block.Height += 10 block.Height += 10
err = ValidateBlock(state, block)
err = blockExec.ValidateBlock(state, block)
require.Error(t, err) require.Error(t, err)
// wrong total tx fails // wrong total tx fails
block = makeBlock(state, 1) block = makeBlock(state, 1)
block.TotalTxs += 10 block.TotalTxs += 10
err = ValidateBlock(state, block)
err = blockExec.ValidateBlock(state, block)
require.Error(t, err) require.Error(t, err)
// wrong blockid fails // wrong blockid fails
block = makeBlock(state, 1) block = makeBlock(state, 1)
block.LastBlockID.PartsHeader.Total += 10 block.LastBlockID.PartsHeader.Total += 10
err = ValidateBlock(state, block)
err = blockExec.ValidateBlock(state, block)
require.Error(t, err) require.Error(t, err)
// wrong app hash fails // wrong app hash fails
block = makeBlock(state, 1) block = makeBlock(state, 1)
block.AppHash = []byte("wrong app hash") block.AppHash = []byte("wrong app hash")
err = ValidateBlock(state, block)
err = blockExec.ValidateBlock(state, block)
require.Error(t, err) require.Error(t, err)
// wrong consensus hash fails // wrong consensus hash fails
block = makeBlock(state, 1) block = makeBlock(state, 1)
block.ConsensusHash = []byte("wrong consensus hash") block.ConsensusHash = []byte("wrong consensus hash")
err = ValidateBlock(state, block)
err = blockExec.ValidateBlock(state, block)
require.Error(t, err) require.Error(t, err)
// wrong results hash fails // wrong results hash fails
block = makeBlock(state, 1) block = makeBlock(state, 1)
block.LastResultsHash = []byte("wrong results hash") block.LastResultsHash = []byte("wrong results hash")
err = ValidateBlock(state, block)
err = blockExec.ValidateBlock(state, block)
require.Error(t, err) require.Error(t, err)
// wrong validators hash fails // wrong validators hash fails
block = makeBlock(state, 1) block = makeBlock(state, 1)
block.ValidatorsHash = []byte("wrong validators hash") block.ValidatorsHash = []byte("wrong validators hash")
err = ValidateBlock(state, block)
err = blockExec.ValidateBlock(state, block)
require.Error(t, err) require.Error(t, err)
} }

+ 4
- 4
types/services.go View File

@ -78,7 +78,7 @@ type BlockStore interface {
type EvidencePool interface { type EvidencePool interface {
PendingEvidence() []Evidence PendingEvidence() []Evidence
AddEvidence(Evidence) error AddEvidence(Evidence) error
MarkEvidenceAsCommitted([]Evidence)
Update(*Block)
} }
// MockMempool is an empty implementation of a Mempool, useful for testing. // MockMempool is an empty implementation of a Mempool, useful for testing.
@ -86,6 +86,6 @@ type EvidencePool interface {
type MockEvidencePool struct { type MockEvidencePool struct {
} }
func (m MockEvidencePool) PendingEvidence() []Evidence { return nil }
func (m MockEvidencePool) AddEvidence(Evidence) error { return nil }
func (m MockEvidencePool) MarkEvidenceAsCommitted([]Evidence) {}
func (m MockEvidencePool) PendingEvidence() []Evidence { return nil }
func (m MockEvidencePool) AddEvidence(Evidence) error { return nil }
func (m MockEvidencePool) Update(*Block) {}

Loading…
Cancel
Save