github.com/vipernet-xyz/tendermint-core@v0.32.0/blockchain/v2/processor.go (about)

     1  package v2
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/tendermint/tendermint/p2p"
     7  	tmState "github.com/tendermint/tendermint/state"
     8  	"github.com/tendermint/tendermint/types"
     9  )
    10  
    11  // Events generated by the processor:
    12  // block execution failure, event will indicate the peer(s) that caused the error
    13  type pcBlockVerificationFailure struct {
    14  	priorityNormal
    15  	height       int64
    16  	firstPeerID  p2p.ID
    17  	secondPeerID p2p.ID
    18  }
    19  
    20  // successful block execution
    21  type pcBlockProcessed struct {
    22  	priorityNormal
    23  	height int64
    24  	peerID p2p.ID
    25  }
    26  
    27  // processor has finished
    28  type pcFinished struct {
    29  	priorityNormal
    30  	blocksSynced int
    31  	tmState      tmState.State
    32  }
    33  
    34  func (p pcFinished) Error() string {
    35  	return "finished"
    36  }
    37  
    38  type queueItem struct {
    39  	block  *types.Block
    40  	peerID p2p.ID
    41  }
    42  
    43  type blockQueue map[int64]queueItem
    44  
    45  type pcState struct {
    46  	// blocks waiting to be processed
    47  	queue blockQueue
    48  
    49  	// draining indicates that the next rProcessBlock event with a queue miss constitutes completion
    50  	draining bool
    51  
    52  	// the number of blocks successfully synced by the processor
    53  	blocksSynced int
    54  
    55  	// the processorContext which contains the processor dependencies
    56  	context processorContext
    57  }
    58  
    59  func (state *pcState) String() string {
    60  	return fmt.Sprintf("height: %d queue length: %d draining: %v blocks synced: %d",
    61  		state.height(), len(state.queue), state.draining, state.blocksSynced)
    62  }
    63  
    64  // newPcState returns a pcState initialized with the last verified block enqueued
    65  func newPcState(context processorContext) *pcState {
    66  	return &pcState{
    67  		queue:        blockQueue{},
    68  		draining:     false,
    69  		blocksSynced: 0,
    70  		context:      context,
    71  	}
    72  }
    73  
    74  // nextTwo returns the next two unverified blocks
    75  func (state *pcState) nextTwo() (queueItem, queueItem, error) {
    76  	if first, ok := state.queue[state.height()+1]; ok {
    77  		if second, ok := state.queue[state.height()+2]; ok {
    78  			return first, second, nil
    79  		}
    80  	}
    81  	return queueItem{}, queueItem{}, fmt.Errorf("not found")
    82  }
    83  
    84  // synced returns true when at most the last verified block remains in the queue
    85  func (state *pcState) synced() bool {
    86  	return len(state.queue) <= 1
    87  }
    88  
    89  func (state *pcState) enqueue(peerID p2p.ID, block *types.Block, height int64) {
    90  	if _, ok := state.queue[height]; ok {
    91  		panic("duplicate block enqueued by processor")
    92  	}
    93  	state.queue[height] = queueItem{block: block, peerID: peerID}
    94  }
    95  
    96  func (state *pcState) height() int64 {
    97  	return state.context.tmState().LastBlockHeight
    98  }
    99  
   100  // purgePeer moves all unprocessed blocks from the queue
   101  func (state *pcState) purgePeer(peerID p2p.ID) {
   102  	// what if height is less than state.height?
   103  	for height, item := range state.queue {
   104  		if item.peerID == peerID {
   105  			delete(state.queue, height)
   106  		}
   107  	}
   108  }
   109  
   110  // handle processes FSM events
   111  func (state *pcState) handle(event Event) (Event, error) {
   112  	switch event := event.(type) {
   113  	case scFinishedEv:
   114  		if state.synced() {
   115  			return pcFinished{tmState: state.context.tmState(), blocksSynced: state.blocksSynced}, nil
   116  		}
   117  		state.draining = true
   118  		return noOp, nil
   119  
   120  	case scPeerError:
   121  		state.purgePeer(event.peerID)
   122  		return noOp, nil
   123  
   124  	case scBlockReceived:
   125  		if event.block == nil {
   126  			return noOp, nil
   127  		}
   128  
   129  		// enqueue block if height is higher than state height, else ignore it
   130  		if event.block.Height > state.height() {
   131  			state.enqueue(event.peerID, event.block, event.block.Height)
   132  		}
   133  		return noOp, nil
   134  
   135  	case rProcessBlock:
   136  		tmState := state.context.tmState()
   137  		firstItem, secondItem, err := state.nextTwo()
   138  		if err != nil {
   139  			if state.draining {
   140  				return pcFinished{tmState: tmState, blocksSynced: state.blocksSynced}, nil
   141  			}
   142  			return noOp, nil
   143  		}
   144  		first, second := firstItem.block, secondItem.block
   145  
   146  		firstParts := first.MakePartSet(types.BlockPartSizeBytes)
   147  		firstPartsHeader := firstParts.Header()
   148  		firstID := types.BlockID{Hash: first.Hash(), PartsHeader: firstPartsHeader}
   149  
   150  		err = state.context.verifyCommit(tmState.ChainID, firstID, first.Height, second.LastCommit)
   151  		if err != nil {
   152  			state.purgePeer(firstItem.peerID)
   153  			state.purgePeer(secondItem.peerID)
   154  			return pcBlockVerificationFailure{
   155  					height: first.Height, firstPeerID: firstItem.peerID, secondPeerID: secondItem.peerID},
   156  				nil
   157  		}
   158  
   159  		state.context.saveBlock(first, firstParts, second.LastCommit)
   160  
   161  		if err := state.context.applyBlock(firstID, first); err != nil {
   162  			panic(fmt.Sprintf("failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err))
   163  		}
   164  
   165  		delete(state.queue, first.Height)
   166  		state.blocksSynced++
   167  
   168  		return pcBlockProcessed{height: first.Height, peerID: firstItem.peerID}, nil
   169  
   170  	}
   171  
   172  	return noOp, nil
   173  }