package v2
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/tendermint/tendermint/internal/p2p"
|
|
tmState "github.com/tendermint/tendermint/state"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
// Events generated by the processor:
|
|
// block execution failure, event will indicate the peer(s) that caused the error
|
|
type pcBlockVerificationFailure struct {
|
|
priorityNormal
|
|
height int64
|
|
firstPeerID p2p.NodeID
|
|
secondPeerID p2p.NodeID
|
|
}
|
|
|
|
func (e pcBlockVerificationFailure) String() string {
|
|
return fmt.Sprintf("pcBlockVerificationFailure{%d 1st peer: %v, 2nd peer: %v}",
|
|
e.height, e.firstPeerID, e.secondPeerID)
|
|
}
|
|
|
|
// successful block execution
|
|
type pcBlockProcessed struct {
|
|
priorityNormal
|
|
height int64
|
|
peerID p2p.NodeID
|
|
}
|
|
|
|
func (e pcBlockProcessed) String() string {
|
|
return fmt.Sprintf("pcBlockProcessed{%d peer: %v}", e.height, e.peerID)
|
|
}
|
|
|
|
// processor has finished
|
|
type pcFinished struct {
|
|
priorityNormal
|
|
blocksSynced int
|
|
tmState tmState.State
|
|
}
|
|
|
|
func (p pcFinished) Error() string {
|
|
return "finished"
|
|
}
|
|
|
|
type queueItem struct {
|
|
block *types.Block
|
|
peerID p2p.NodeID
|
|
}
|
|
|
|
type blockQueue map[int64]queueItem
|
|
|
|
type pcState struct {
|
|
// blocks waiting to be processed
|
|
queue blockQueue
|
|
|
|
// draining indicates that the next rProcessBlock event with a queue miss constitutes completion
|
|
draining bool
|
|
|
|
// the number of blocks successfully synced by the processor
|
|
blocksSynced int
|
|
|
|
// the processorContext which contains the processor dependencies
|
|
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(context processorContext) *pcState {
|
|
return &pcState{
|
|
queue: blockQueue{},
|
|
draining: false,
|
|
blocksSynced: 0,
|
|
context: context,
|
|
}
|
|
}
|
|
|
|
// 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) enqueue(peerID p2p.NodeID, block *types.Block, height int64) {
|
|
if item, ok := state.queue[height]; ok {
|
|
panic(fmt.Sprintf(
|
|
"duplicate block %d (%X) enqueued by processor (sent by %v; existing block %X from %v)",
|
|
height, block.Hash(), peerID, item.block.Hash(), item.peerID))
|
|
}
|
|
|
|
state.queue[height] = queueItem{block: block, peerID: peerID}
|
|
}
|
|
|
|
func (state *pcState) height() int64 {
|
|
return state.context.tmState().LastBlockHeight
|
|
}
|
|
|
|
// purgePeer moves all unprocessed blocks from the queue
|
|
func (state *pcState) purgePeer(peerID p2p.NodeID) {
|
|
// 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 bcResetState:
|
|
state.context.setState(event.state)
|
|
return noOp, nil
|
|
|
|
case scFinishedEv:
|
|
if state.synced() {
|
|
return pcFinished{tmState: state.context.tmState(), blocksSynced: state.blocksSynced}, nil
|
|
}
|
|
state.draining = true
|
|
return noOp, nil
|
|
|
|
case scPeerError:
|
|
state.purgePeer(event.peerID)
|
|
return noOp, nil
|
|
|
|
case scBlockReceived:
|
|
if event.block == nil {
|
|
return noOp, nil
|
|
}
|
|
|
|
// enqueue block if height is higher than state height, else ignore it
|
|
if event.block.Height > state.height() {
|
|
state.enqueue(event.peerID, event.block, event.block.Height)
|
|
}
|
|
return noOp, nil
|
|
|
|
case rProcessBlock:
|
|
tmState := state.context.tmState()
|
|
firstItem, secondItem, err := state.nextTwo()
|
|
if err != nil {
|
|
if state.draining {
|
|
return pcFinished{tmState: tmState, blocksSynced: state.blocksSynced}, nil
|
|
}
|
|
return noOp, nil
|
|
}
|
|
|
|
var (
|
|
first, second = firstItem.block, secondItem.block
|
|
firstParts = first.MakePartSet(types.BlockPartSizeBytes)
|
|
firstID = types.BlockID{Hash: first.Hash(), PartSetHeader: firstParts.Header()}
|
|
)
|
|
|
|
// verify if +second+ last commit "confirms" +first+ block
|
|
err = state.context.verifyCommit(tmState.ChainID, firstID, first.Height, second.LastCommit)
|
|
if err != nil {
|
|
state.purgePeer(firstItem.peerID)
|
|
if firstItem.peerID != secondItem.peerID {
|
|
state.purgePeer(secondItem.peerID)
|
|
}
|
|
return pcBlockVerificationFailure{
|
|
height: first.Height, firstPeerID: firstItem.peerID, secondPeerID: secondItem.peerID},
|
|
nil
|
|
}
|
|
|
|
state.context.saveBlock(first, firstParts, second.LastCommit)
|
|
|
|
if err := state.context.applyBlock(firstID, first); err != nil {
|
|
panic(fmt.Sprintf("failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err))
|
|
}
|
|
|
|
delete(state.queue, first.Height)
|
|
state.blocksSynced++
|
|
|
|
return pcBlockProcessed{height: first.Height, peerID: firstItem.peerID}, nil
|
|
}
|
|
|
|
return noOp, nil
|
|
}
|