package v2 import ( "fmt" "github.com/tendermint/tendermint/p2p" tdState "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) type peerError struct { priorityHigh peerID p2p.ID } type pcDuplicateBlock struct { priorityNormal } type pcShortBlock struct { priorityNormal } type pcBlockVerificationFailure struct { priorityNormal height int64 firstPeerID p2p.ID secondPeerID p2p.ID } type pcBlockProcessed struct { priorityNormal height int64 peerID p2p.ID } type pcProcessBlock struct { priorityNormal } type pcStop struct { priorityNormal } type pcFinished struct { priorityNormal height int64 blocksSynced int64 } func (p pcFinished) Error() string { return "finished" } type queueItem struct { block *types.Block peerID p2p.ID } type blockQueue map[int64]queueItem type pcState struct { height int64 // height of the last synced block queue blockQueue // blocks waiting to be processed chainID string blocksSynced int64 draining bool tdState tdState.State context processorContext } func (state *pcState) String() string { return fmt.Sprintf("height: %d queue length: %d draining: %v blocks synced: %d", state.height, len(state.queue), state.draining, state.blocksSynced) } // newPcState returns a pcState initialized with the last verified block enqueued func newPcState(initHeight int64, tdState tdState.State, chainID string, context processorContext) *pcState { return &pcState{ height: initHeight, queue: blockQueue{}, chainID: chainID, draining: false, blocksSynced: 0, context: context, tdState: tdState, } } // nextTwo returns the next two unverified blocks func (state *pcState) nextTwo() (queueItem, queueItem, error) { if first, ok := state.queue[state.height+1]; ok { if second, ok := state.queue[state.height+2]; ok { return first, second, nil } } return queueItem{}, queueItem{}, fmt.Errorf("not found") } // synced returns true when at most the last verified block remains in the queue func (state *pcState) synced() bool { return len(state.queue) <= 1 } func (state *pcState) advance() { state.height++ delete(state.queue, state.height) state.blocksSynced++ } func (state *pcState) enqueue(peerID p2p.ID, block *types.Block, height int64) error { if _, ok := state.queue[height]; ok { return fmt.Errorf("duplicate queue item") } state.queue[height] = queueItem{block: block, peerID: peerID} return nil } // purgePeer moves all unprocessed blocks from the queue func (state *pcState) purgePeer(peerID p2p.ID) { // what if height is less than state.height? for height, item := range state.queue { if item.peerID == peerID { delete(state.queue, height) } } } // handle processes FSM events func (state *pcState) handle(event Event) (Event, error) { switch event := event.(type) { case *scBlockReceived: if event.block == nil { panic("processor received an event with a nil block") } if event.block.Height <= state.height { return pcShortBlock{}, nil } err := state.enqueue(event.peerID, event.block, event.block.Height) if err != nil { return pcDuplicateBlock{}, nil } case pcProcessBlock: firstItem, secondItem, err := state.nextTwo() if err != nil { if state.draining { return noOp, pcFinished{height: state.height} } return noOp, nil } first, second := firstItem.block, secondItem.block firstParts := first.MakePartSet(types.BlockPartSizeBytes) firstPartsHeader := firstParts.Header() firstID := types.BlockID{Hash: first.Hash(), PartsHeader: firstPartsHeader} err = state.context.verifyCommit(state.chainID, firstID, first.Height, second.LastCommit) if err != nil { state.purgePeer(firstItem.peerID) state.purgePeer(secondItem.peerID) return pcBlockVerificationFailure{ height: first.Height, firstPeerID: firstItem.peerID, secondPeerID: secondItem.peerID}, nil } state.context.saveBlock(first, firstParts, second.LastCommit) state.tdState, err = state.context.applyBlock(state.tdState, firstID, first) if err != nil { panic(fmt.Sprintf("failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err)) } state.advance() return pcBlockProcessed{height: first.Height, peerID: firstItem.peerID}, nil case *peerError: state.purgePeer(event.peerID) case pcStop: if state.synced() { return noOp, pcFinished{height: state.height, blocksSynced: state.blocksSynced} } state.draining = true } return noOp, nil }