github.com/okex/exchain@v1.8.0/libs/tendermint/blockchain/v2/processor.go (about)

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