github.com/vipernet-xyz/tm@v0.34.24/blockchain/v2/processor.go (about)

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