github.com/franono/tendermint@v0.32.2-0.20200527150959-749313264ce9/blockchain/v2/processor.go (about)

     1  package v2
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/franono/tendermint/p2p"
     7  	tmState "github.com/franono/tendermint/state"
     8  	"github.com/franono/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 bcResetState:
   114  		state.context.setState(event.state)
   115  		return noOp, nil
   116  
   117  	case scFinishedEv:
   118  		if state.synced() {
   119  			return pcFinished{tmState: state.context.tmState(), blocksSynced: state.blocksSynced}, nil
   120  		}
   121  		state.draining = true
   122  		return noOp, nil
   123  
   124  	case scPeerError:
   125  		state.purgePeer(event.peerID)
   126  		return noOp, nil
   127  
   128  	case scBlockReceived:
   129  		if event.block == nil {
   130  			return noOp, nil
   131  		}
   132  
   133  		// enqueue block if height is higher than state height, else ignore it
   134  		if event.block.Height > state.height() {
   135  			state.enqueue(event.peerID, event.block, event.block.Height)
   136  		}
   137  		return noOp, nil
   138  
   139  	case rProcessBlock:
   140  		tmState := state.context.tmState()
   141  		firstItem, secondItem, err := state.nextTwo()
   142  		if err != nil {
   143  			if state.draining {
   144  				return pcFinished{tmState: tmState, blocksSynced: state.blocksSynced}, nil
   145  			}
   146  			return noOp, nil
   147  		}
   148  		first, second := firstItem.block, secondItem.block
   149  
   150  		firstParts := first.MakePartSet(types.BlockPartSizeBytes)
   151  		firstPartsHeader := firstParts.Header()
   152  		firstID := types.BlockID{Hash: first.Hash(), PartsHeader: firstPartsHeader}
   153  
   154  		err = state.context.verifyCommit(tmState.ChainID, firstID, first.Height, second.LastCommit)
   155  		if err != nil {
   156  			state.purgePeer(firstItem.peerID)
   157  			state.purgePeer(secondItem.peerID)
   158  			return pcBlockVerificationFailure{
   159  					height: first.Height, firstPeerID: firstItem.peerID, secondPeerID: secondItem.peerID},
   160  				nil
   161  		}
   162  
   163  		state.context.saveBlock(first, firstParts, second.LastCommit)
   164  
   165  		if err := state.context.applyBlock(firstID, first); err != nil {
   166  			panic(fmt.Sprintf("failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err))
   167  		}
   168  
   169  		delete(state.queue, first.Height)
   170  		state.blocksSynced++
   171  
   172  		return pcBlockProcessed{height: first.Height, peerID: firstItem.peerID}, nil
   173  
   174  	}
   175  
   176  	return noOp, nil
   177  }