package evidence import ( "fmt" "reflect" "time" amino "github.com/tendermint/go-amino" clist "github.com/tendermint/tendermint/libs/clist" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" ) const ( EvidenceChannel = byte(0x38) maxMsgSize = 1048576 // 1MB TODO make it configurable broadcastEvidenceIntervalS = 60 // broadcast uncommitted evidence this often peerCatchupSleepIntervalMS = 100 // If peer is behind, sleep this amount ) // Reactor handles evpool evidence broadcasting amongst peers. type Reactor struct { p2p.BaseReactor evpool *Pool eventBus *types.EventBus } // NewReactor returns a new Reactor with the given config and evpool. func NewReactor(evpool *Pool) *Reactor { evR := &Reactor{ evpool: evpool, } evR.BaseReactor = *p2p.NewBaseReactor("Evidence", evR) return evR } // SetLogger sets the Logger on the reactor and the underlying Evidence. func (evR *Reactor) SetLogger(l log.Logger) { evR.Logger = l evR.evpool.SetLogger(l) } // GetChannels implements Reactor. // It returns the list of channels for this reactor. func (evR *Reactor) GetChannels() []*p2p.ChannelDescriptor { return []*p2p.ChannelDescriptor{ { ID: EvidenceChannel, Priority: 5, }, } } // AddPeer implements Reactor. func (evR *Reactor) AddPeer(peer p2p.Peer) { go evR.broadcastEvidenceRoutine(peer) } // Receive implements Reactor. // It adds any received evidence to the evpool. func (evR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { msg, err := decodeMsg(msgBytes) if err != nil { evR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) evR.Switch.StopPeerForError(src, err) return } if err = msg.ValidateBasic(); err != nil { evR.Logger.Error("Peer sent us invalid msg", "peer", src, "msg", msg, "err", err) evR.Switch.StopPeerForError(src, err) return } evR.Logger.Debug("Receive", "src", src, "chId", chID, "msg", msg) switch msg := msg.(type) { case *ListMessage: for _, ev := range msg.Evidence { err := evR.evpool.AddEvidence(ev) switch err.(type) { case ErrInvalidEvidence: evR.Logger.Error("Evidence is not valid", "evidence", msg.Evidence, "err", err) // punish peer evR.Switch.StopPeerForError(src, err) return case nil: default: evR.Logger.Error("Evidence has not been added", "evidence", msg.Evidence, "err", err) return } } default: evR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) } } // SetEventBus implements events.Eventable. func (evR *Reactor) SetEventBus(b *types.EventBus) { evR.eventBus = b } // Modeled after the mempool routine. // - Evidence accumulates in a clist. // - Each peer has a routine that iterates through the clist, // sending available evidence to the peer. // - If we're waiting for new evidence and the list is not empty, // start iterating from the beginning again. func (evR *Reactor) broadcastEvidenceRoutine(peer p2p.Peer) { var next *clist.CElement for { // This happens because the CElement we were looking at got garbage // collected (removed). That is, .NextWait() returned nil. Go ahead and // start from the beginning. if next == nil { select { case <-evR.evpool.EvidenceWaitChan(): // Wait until evidence is available if next = evR.evpool.EvidenceFront(); next == nil { continue } case <-peer.Quit(): return case <-evR.Quit(): return } } ev := next.Value.(types.Evidence) msg, retry := evR.checkSendEvidenceMessage(peer, ev) if msg != nil { success := peer.Send(EvidenceChannel, cdc.MustMarshalBinaryBare(msg)) retry = !success } if retry { time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) continue } afterCh := time.After(time.Second * broadcastEvidenceIntervalS) select { case <-afterCh: // start from the beginning every tick. // TODO: only do this if we're at the end of the list! next = nil case <-next.NextWaitChan(): // see the start of the for loop for nil check next = next.Next() case <-peer.Quit(): return case <-evR.Quit(): return } } } // Returns the message to send the peer, or nil if the evidence is invalid for the peer. // If message is nil, return true if we should sleep and try again. func (evR Reactor) checkSendEvidenceMessage( peer p2p.Peer, ev types.Evidence, ) (msg Message, retry bool) { // make sure the peer is up to date evHeight := ev.Height() peerState, ok := peer.Get(types.PeerStateKey).(PeerState) if !ok { // Peer does not have a state yet. We set it in the consensus reactor, but // when we add peer in Switch, the order we call reactors#AddPeer is // different every time due to us using a map. Sometimes other reactors // will be initialized before the consensus reactor. We should wait a few // milliseconds and retry. return nil, true } // NOTE: We only send evidence to peers where // peerHeight - maxAge < evidenceHeight < peerHeight // and // lastBlockTime - maxDuration < evidenceTime var ( peerHeight = peerState.GetHeight() params = evR.evpool.State().ConsensusParams.Evidence ageDuration = evR.evpool.State().LastBlockTime.Sub(ev.Time()) ageNumBlocks = peerHeight - evHeight ) if peerHeight < evHeight { // peer is behind. sleep while he catches up return nil, true } else if ageNumBlocks > params.MaxAgeNumBlocks && ageDuration > params.MaxAgeDuration { // evidence is too old, skip // NOTE: if evidence is too old for an honest peer, then we're behind and // either it already got committed or it never will! evR.Logger.Info("Not sending peer old evidence", "peerHeight", peerHeight, "evHeight", evHeight, "maxAgeNumBlocks", params.MaxAgeNumBlocks, "lastBlockTime", evR.evpool.State().LastBlockTime, "evTime", ev.Time(), "maxAgeDuration", params.MaxAgeDuration, "peer", peer, ) return nil, false } // send evidence msg = &ListMessage{[]types.Evidence{ev}} return msg, false } // PeerState describes the state of a peer. type PeerState interface { GetHeight() int64 } //----------------------------------------------------------------------------- // Messages // Message is a message sent or received by the Reactor. type Message interface { ValidateBasic() error } func RegisterMessages(cdc *amino.Codec) { cdc.RegisterInterface((*Message)(nil), nil) cdc.RegisterConcrete(&ListMessage{}, "tendermint/evidence/ListMessage", nil) } func decodeMsg(bz []byte) (msg Message, err error) { if len(bz) > maxMsgSize { return msg, fmt.Errorf("msg exceeds max size (%d > %d)", len(bz), maxMsgSize) } err = cdc.UnmarshalBinaryBare(bz, &msg) return } //------------------------------------- // ListMessage contains a list of evidence. type ListMessage struct { Evidence []types.Evidence } // ValidateBasic performs basic validation. func (m *ListMessage) ValidateBasic() error { for i, ev := range m.Evidence { if err := ev.ValidateBasic(); err != nil { return fmt.Errorf("invalid evidence (#%d): %v", i, err) } } return nil } // String returns a string representation of the ListMessage. func (m *ListMessage) String() string { return fmt.Sprintf("[ListMessage %v]", m.Evidence) }