github.com/pure-x-eth/consensus_tm@v0.0.0-20230502163723-e3c2ff987250/blockchain/v2/processor.go (about) 1 package v2 2 3 import ( 4 "fmt" 5 6 "github.com/pure-x-eth/consensus_tm/p2p" 7 tmState "github.com/pure-x-eth/consensus_tm/state" 8 "github.com/pure-x-eth/consensus_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 }