From 6c4a0f936397cb4e2d1f0e6c3f7faf7904d37d1c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 19 Nov 2017 01:34:11 +0000 Subject: [PATCH] cleanup evidence pkg. state.VerifyEvidence --- evidence/reactor.go | 81 +++++++++++++++++++++++---------------------- evidence/store.go | 19 ++++------- state/execution.go | 2 +- state/state.go | 31 +++++++++++++++++ types/evidence.go | 28 +++++----------- types/services.go | 29 ++++++++++++++-- 6 files changed, 115 insertions(+), 75 deletions(-) diff --git a/evidence/reactor.go b/evidence/reactor.go index 7e0860793..f6e7501aa 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -9,44 +9,45 @@ import ( wire "github.com/tendermint/go-wire" "github.com/tendermint/tmlibs/log" + cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" ) const ( - EvidencePoolChannel = byte(0x38) + EvidenceChannel = byte(0x38) - maxEvidencePoolMessageSize = 1048576 // 1MB TODO make it configurable + maxEvidenceMessageSize = 1048576 // 1MB TODO make it configurable peerCatchupSleepIntervalMS = 100 // If peer is behind, sleep this amount broadcastEvidenceIntervalS = 60 // broadcast uncommitted evidence this often ) -// EvidencePoolReactor handles evpool evidence broadcasting amongst peers. -type EvidencePoolReactor struct { +// EvidenceReactor handles evpool evidence broadcasting amongst peers. +type EvidenceReactor struct { p2p.BaseReactor - config *EvidencePoolConfig - evpool *EvidencePool - evsw types.EventSwitch + config *cfg.EvidenceConfig + evpool *EvidencePool + eventBus *types.EventBus } -// NewEvidencePoolReactor returns a new EvidencePoolReactor with the given config and evpool. -func NewEvidencePoolReactor(config *EvidencePoolConfig, evpool *EvidencePool) *EvidencePoolReactor { - evR := &EvidencePoolReactor{ +// NewEvidenceReactor returns a new EvidenceReactor with the given config and evpool. +func NewEvidenceReactor(config *cfg.EvidenceConfig, evpool *EvidencePool) *EvidenceReactor { + evR := &EvidenceReactor{ config: config, evpool: evpool, } - evR.BaseReactor = *p2p.NewBaseReactor("EvidencePoolReactor", evR) + evR.BaseReactor = *p2p.NewBaseReactor("EvidenceReactor", evR) return evR } -// SetLogger sets the Logger on the reactor and the underlying EvidencePool. -func (evR *EvidencePoolReactor) SetLogger(l log.Logger) { +// SetLogger sets the Logger on the reactor and the underlying Evidence. +func (evR *EvidenceReactor) SetLogger(l log.Logger) { evR.Logger = l evR.evpool.SetLogger(l) } // OnStart implements cmn.Service -func (evR *EvidencePoolReactor) OnStart() error { +func (evR *EvidenceReactor) OnStart() error { if err := evR.BaseReactor.OnStart(); err != nil { return err } @@ -56,35 +57,35 @@ func (evR *EvidencePoolReactor) OnStart() error { // GetChannels implements Reactor. // It returns the list of channels for this reactor. -func (evR *EvidencePoolReactor) GetChannels() []*p2p.ChannelDescriptor { +func (evR *EvidenceReactor) GetChannels() []*p2p.ChannelDescriptor { return []*p2p.ChannelDescriptor{ &p2p.ChannelDescriptor{ - ID: EvidencePoolChannel, + ID: EvidenceChannel, Priority: 5, }, } } // AddPeer implements Reactor. -func (evR *EvidencePoolReactor) AddPeer(peer p2p.Peer) { +func (evR *EvidenceReactor) AddPeer(peer p2p.Peer) { // send the peer our high-priority evidence. // the rest will be sent by the broadcastRoutine evidence := evR.evpool.PriorityEvidence() - msg := EvidenceMessage{evidence} - success := peer.Send(EvidencePoolChannel, struct{ EvidencePoolMessage }{msg}) + msg := EvidenceListMessage{evidence} + success := peer.Send(EvidenceChannel, struct{ EvidenceMessage }{msg}) if !success { // TODO: remove peer ? } } // RemovePeer implements Reactor. -func (evR *EvidencePoolReactor) RemovePeer(peer p2p.Peer, reason interface{}) { +func (evR *EvidenceReactor) RemovePeer(peer p2p.Peer, reason interface{}) { // nothing to do } // Receive implements Reactor. // It adds any received evidence to the evpool. -func (evR *EvidencePoolReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { +func (evR *EvidenceReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { _, msg, err := DecodeMessage(msgBytes) if err != nil { evR.Logger.Error("Error decoding message", "err", err) @@ -93,7 +94,7 @@ func (evR *EvidencePoolReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte evR.Logger.Debug("Receive", "src", src, "chId", chID, "msg", msg) switch msg := msg.(type) { - case *EvidenceMessage: + case *EvidenceListMessage: for _, ev := range msg.Evidence { err := evR.evpool.AddEvidence(ev) if err != nil { @@ -107,28 +108,28 @@ func (evR *EvidencePoolReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte } // SetEventSwitch implements events.Eventable. -func (evR *EvidencePoolReactor) SetEventSwitch(evsw types.EventSwitch) { - evR.evsw = evsw +func (evR *EvidenceReactor) SetEventBus(b *types.EventBus) { + evR.eventBus = b } // broadcast new evidence to all peers. // broadcasts must be non-blocking so routine is always available to read off EvidenceChan. -func (evR *EvidencePoolReactor) broadcastRoutine() { +func (evR *EvidenceReactor) broadcastRoutine() { ticker := time.NewTicker(time.Second * broadcastEvidenceIntervalS) for { select { case evidence := <-evR.evpool.EvidenceChan(): // broadcast some new evidence - msg := EvidenceMessage{[]types.Evidence{evidence}} - evR.Switch.Broadcast(EvidencePoolChannel, struct{ EvidencePoolMessage }{msg}) + msg := EvidenceListMessage{[]types.Evidence{evidence}} + evR.Switch.Broadcast(EvidenceChannel, struct{ EvidenceMessage }{msg}) // TODO: Broadcast runs asynchronously, so this should wait on the successChan // in another routine before marking to be proper. evR.evpool.evidenceStore.MarkEvidenceAsBroadcasted(evidence) case <-ticker.C: // broadcast all pending evidence - msg := EvidenceMessage{evR.evpool.PendingEvidence()} - evR.Switch.Broadcast(EvidencePoolChannel, struct{ EvidencePoolMessage }{msg}) + msg := EvidenceListMessage{evR.evpool.PendingEvidence()} + evR.Switch.Broadcast(EvidenceChannel, struct{ EvidenceMessage }{msg}) case <-evR.Quit: return } @@ -142,31 +143,31 @@ const ( msgTypeEvidence = byte(0x01) ) -// EvidencePoolMessage is a message sent or received by the EvidencePoolReactor. -type EvidencePoolMessage interface{} +// EvidenceMessage is a message sent or received by the EvidenceReactor. +type EvidenceMessage interface{} var _ = wire.RegisterInterface( - struct{ EvidencePoolMessage }{}, - wire.ConcreteType{&EvidenceMessage{}, msgTypeEvidence}, + struct{ EvidenceMessage }{}, + wire.ConcreteType{&EvidenceListMessage{}, msgTypeEvidence}, ) -// DecodeMessage decodes a byte-array into a EvidencePoolMessage. -func DecodeMessage(bz []byte) (msgType byte, msg EvidencePoolMessage, err error) { +// DecodeMessage decodes a byte-array into a EvidenceMessage. +func DecodeMessage(bz []byte) (msgType byte, msg EvidenceMessage, err error) { msgType = bz[0] n := new(int) r := bytes.NewReader(bz) - msg = wire.ReadBinary(struct{ EvidencePoolMessage }{}, r, maxEvidencePoolMessageSize, n, &err).(struct{ EvidencePoolMessage }).EvidencePoolMessage + msg = wire.ReadBinary(struct{ EvidenceMessage }{}, r, maxEvidenceMessageSize, n, &err).(struct{ EvidenceMessage }).EvidenceMessage return } //------------------------------------- // EvidenceMessage contains a list of evidence. -type EvidenceMessage struct { +type EvidenceListMessage struct { Evidence []types.Evidence } -// String returns a string representation of the EvidenceMessage. -func (m *EvidenceMessage) String() string { - return fmt.Sprintf("[EvidenceMessage %v]", m.Evidence) +// String returns a string representation of the EvidenceListMessage. +func (m *EvidenceListMessage) String() string { + return fmt.Sprintf("[EvidenceListMessage %v]", m.Evidence) } diff --git a/evidence/store.go b/evidence/store.go index f4587dd79..0748a7217 100644 --- a/evidence/store.go +++ b/evidence/store.go @@ -55,14 +55,14 @@ type EvidenceStore struct { db dbm.DB // so we can verify evidence was from a real validator - historicalValidators types.HistoricalValidators + state types.State } -func NewEvidenceStore(chainID string, db dbm.DB, vals types.HistoricalValidators) *EvidenceStore { +func NewEvidenceStore(chainID string, db dbm.DB, state types.State) *EvidenceStore { return &EvidenceStore{ - chainID: chainID, - db: db, - historicalValidators: vals, + chainID: chainID, + db: db, + state: state, } } @@ -101,16 +101,11 @@ func (store *EvidenceStore) AddNewEvidence(evidence types.Evidence) (bool, error return false, nil } - // verify evidence consistency - if err := evidence.Verify(store.chainID, store.historicalValidators); err != nil { + priority, err := store.state.VerifyEvidence(evidence) + if err != nil { return false, err } - // TODO: or we let Verify return the val to avoid running this again? - valSet := store.historicalValidators.LoadValidators(evidence.Height()) - _, val := valSet.GetByAddress(evidence.Address()) - priority := int(val.VotingPower) - ei := evidenceInfo{ Committed: false, Priority: priority, diff --git a/state/execution.go b/state/execution.go index bdc4bd528..860c0caa6 100644 --- a/state/execution.go +++ b/state/execution.go @@ -310,7 +310,7 @@ func (s *State) validateBlock(b *types.Block) error { } for _, ev := range block.Evidence.Evidences { - if err := ev.Verify(s.ChainID, s); err != nil { + if _, err := s.VerifyEvidence(ev); err != nil { return types.NewEvidenceInvalidErr(ev, err) } // TODO: mark evidence as committed diff --git a/state/state.go b/state/state.go index 0dd105cbe..78888152d 100644 --- a/state/state.go +++ b/state/state.go @@ -195,6 +195,7 @@ func (s *State) LoadABCIResponses(height int64) (*ABCIResponses, error) { } // LoadValidators loads the ValidatorSet for a given height. +// Returns ErrNoValSetForHeight if the validator set can't be found for this height. func (s *State) LoadValidators(height int64) (*types.ValidatorSet, error) { valInfo := s.loadValidatorsInfo(height) if valInfo == nil { @@ -382,6 +383,36 @@ func (s *State) GetValidators() (last *types.ValidatorSet, current *types.Valida return s.LastValidators, s.Validators } +// VerifyEvidence verifies the evidence fully by checking it is internally +// consistent and corresponds to an existing or previous validator. +// It returns the priority of this evidence, or an error. +// NOTE: return error may be ErrLoadValidators, in which case the validator set +// for the evidence height could not be loaded. +func (s *State) VerifyEvidence(evidence types.Evidence) (priority int, err error) { + if err := evidence.Verify(s.ChainID); err != nil { + return priority, err + } + + // The address must have been an active validator at the height + ev := evidence + height, addr, idx := ev.Height(), ev.Address(), ev.Index() + valset, err := s.LoadValidators(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 priority, err + } + valIdx, val := valset.GetByAddress(addr) + if val == nil { + return priority, fmt.Errorf("Address %X was not a validator at height %d", addr, height) + } else if idx != valIdx { + return priority, fmt.Errorf("Address %X was validator %d at height %d, not %d", addr, valIdx, height, idx) + } + + priority = int(val.VotingPower) + return priority, nil +} + //------------------------------------------------------------------------ // ABCIResponses retains the responses diff --git a/types/evidence.go b/types/evidence.go index 2021269b4..42047fafa 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -27,17 +27,17 @@ func (err *ErrEvidenceInvalid) Error() string { //------------------------------------------- type HistoricalValidators interface { - LoadValidators(height int) *ValidatorSet + LoadValidators(height int) (*ValidatorSet, error) } // Evidence represents any provable malicious activity by a validator type Evidence interface { - Height() int // height of the equivocation - Address() []byte // address of the equivocating validator - Index() int // index of the validator in the validator set - Hash() []byte // hash of the evidence - Verify(chainID string, vals HistoricalValidators) error // verify the evidence - Equal(Evidence) bool // check equality of evidence + Height() int // height of the equivocation + Address() []byte // address of the equivocating validator + Index() int // index of the validator in the validator set + Hash() []byte // hash of the evidence + Verify(chainID string) error // verify the evidence + Equal(Evidence) bool // check equality of evidence String() string } @@ -178,7 +178,7 @@ func (dve *DuplicateVoteEvidence) Hash() []byte { // Verify returns an error if the two votes aren't conflicting. // To be conflicting, they must be from the same validator, for the same H/R/S, but for different blocks. -func (dve *DuplicateVoteEvidence) Verify(chainID string, vals HistoricalValidators) error { +func (dve *DuplicateVoteEvidence) Verify(chainID string) error { // TODO: verify (cs.Height - dve.Height) < MaxHeightDiff @@ -211,18 +211,6 @@ func (dve *DuplicateVoteEvidence) Verify(chainID string, vals HistoricalValidato return ErrVoteInvalidSignature } - // The address must have been an active validator at the height - height := dve.Height() - addr := dve.Address() - idx := dve.Index() - valset := vals.LoadValidators(height) - valIdx, val := valset.GetByAddress(addr) - if val == nil { - return fmt.Errorf("Address %X was not a validator at height %d", addr, height) - } else if idx != valIdx { - return fmt.Errorf("Address %X was validator %d at height %d, not %d", addr, valIdx, height, idx) - } - return nil } diff --git a/types/services.go b/types/services.go index 0e0075540..f1d8ddd59 100644 --- a/types/services.go +++ b/types/services.go @@ -14,7 +14,7 @@ import ( //------------------------------------------------------ // mempool -// Mempool defines the mempool interface. +// Mempool defines the mempool interface as used by the ConsensusState. // Updates to the mempool need to be synchronized with committing a block // so apps can reset their transient state on Commit // UNSTABLE @@ -63,9 +63,34 @@ type BlockStoreRPC interface { LoadSeenCommit(height int64) *Commit } -// BlockStore defines the BlockStore interface. +// BlockStore defines the BlockStore interface used by the ConsensusState. // UNSTABLE type BlockStore interface { BlockStoreRPC SaveBlock(block *Block, blockParts *PartSet, seenCommit *Commit) } + +//------------------------------------------------------ +// state + +type State interface { + VerifyEvidence(Evidence) (priority int, err error) +} + +//------------------------------------------------------ +// evidence pool + +// EvidencePool defines the EvidencePool interface used by the ConsensusState. +// UNSTABLE +type EvidencePool interface { + PendingEvidence() []Evidence + AddEvidence(Evidence) +} + +// MockMempool is an empty implementation of a Mempool, useful for testing. +// UNSTABLE +type MockEvidencePool struct { +} + +func (m MockEvidencePool) PendingEvidence() []Evidence { return nil } +func (m MockEvidencePool) AddEvidence(Evidence) {}