github.com/dim4egster/coreth@v0.10.2/plugin/evm/block_builder.go (about) 1 // (c) 2019-2021, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package evm 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/dim4egster/coreth/params" 11 12 "github.com/dim4egster/qmallgo/snow" 13 commonEng "github.com/dim4egster/qmallgo/snow/engine/common" 14 "github.com/dim4egster/qmallgo/utils/timer" 15 "github.com/dim4egster/coreth/core" 16 "github.com/ethereum/go-ethereum/log" 17 ) 18 19 // buildingBlkStatus denotes the current status of the VM in block production. 20 type buildingBlkStatus uint8 21 22 var ( 23 // AP4 Params 24 minBlockTimeAP4 = 500 * time.Millisecond 25 ) 26 27 const ( 28 // waitBlockTime is the amount of time to wait for BuildBlock to be 29 // called by the engine before deciding whether or not to gossip the 30 // transaction that triggered the PendingTxs message to the engine. 31 // 32 // This is done to reduce contention in the network when there is no 33 // preferred producer. If we did not wait here, we may gossip a new 34 // transaction to a peer while building a block that will conflict with 35 // whatever the peer makes. 36 waitBlockTime = 100 * time.Millisecond 37 38 dontBuild buildingBlkStatus = iota 39 mayBuild 40 building 41 ) 42 43 type blockBuilder struct { 44 ctx *snow.Context 45 chainConfig *params.ChainConfig 46 47 txPool *core.TxPool 48 mempool *Mempool 49 gossiper Gossiper 50 51 shutdownChan <-chan struct{} 52 shutdownWg *sync.WaitGroup 53 54 // A message is sent on this channel when a new block 55 // is ready to be build. This notifies the consensus engine. 56 notifyBuildBlockChan chan<- commonEng.Message 57 58 // [buildBlockLock] must be held when accessing [buildStatus] 59 buildBlockLock sync.Mutex 60 61 // [buildBlockTimer] is a timer handling block production. 62 buildBlockTimer *timer.Timer 63 64 // buildStatus signals the phase of block building the VM is currently in. 65 // [dontBuild] indicates there's no need to build a block. 66 // [mayBuild] indicates the VM should proceed to build a block. 67 // [building] indicates the VM has sent a request to the engine to build a block. 68 buildStatus buildingBlkStatus 69 } 70 71 func (vm *VM) NewBlockBuilder(notifyBuildBlockChan chan<- commonEng.Message) *blockBuilder { 72 b := &blockBuilder{ 73 ctx: vm.ctx, 74 chainConfig: vm.chainConfig, 75 txPool: vm.txPool, 76 mempool: vm.mempool, 77 gossiper: vm.gossiper, 78 shutdownChan: vm.shutdownChan, 79 shutdownWg: &vm.shutdownWg, 80 notifyBuildBlockChan: notifyBuildBlockChan, 81 buildStatus: dontBuild, 82 } 83 84 b.handleBlockBuilding() 85 return b 86 } 87 88 func (b *blockBuilder) handleBlockBuilding() { 89 b.buildBlockTimer = timer.NewTimer(b.buildBlockTimerCallback) 90 go b.ctx.Log.RecoverAndPanic(b.buildBlockTimer.Dispatch) 91 } 92 93 // handleGenerateBlock should be called immediately after [BuildBlock]. 94 // [handleGenerateBlock] invocation could lead to quiesence, building a block with 95 // some delay, or attempting to build another block immediately. 96 func (b *blockBuilder) handleGenerateBlock() { 97 b.buildBlockLock.Lock() 98 defer b.buildBlockLock.Unlock() 99 100 // If we still need to build a block immediately after building, we let the 101 // engine know it [mayBuild] in [minBlockTimeAP4]. 102 // 103 // It is often the case in AP4 that a block (with the same txs) could be built 104 // after a few seconds of delay as the [baseFee] and/or [blockGasCost] decrease. 105 if b.needToBuild() { 106 b.buildStatus = mayBuild 107 b.buildBlockTimer.SetTimeoutIn(minBlockTimeAP4) 108 } else { 109 b.buildStatus = dontBuild 110 } 111 } 112 113 // needToBuild returns true if there are outstanding transactions to be issued 114 // into a block. 115 func (b *blockBuilder) needToBuild() bool { 116 size := b.txPool.PendingSize() 117 return size > 0 || b.mempool.Len() > 0 118 } 119 120 // buildBlockTimerCallback is the timer callback that sends a notification 121 // to the engine when the VM is ready to build a block. 122 func (b *blockBuilder) buildBlockTimerCallback() { 123 b.buildBlockLock.Lock() 124 defer b.buildBlockLock.Unlock() 125 126 switch b.buildStatus { 127 case dontBuild: 128 case mayBuild: 129 b.markBuilding() 130 case building: 131 // If the status has already been set to building, there is no need 132 // to send an additional request to the consensus engine until the call 133 // to BuildBlock resets the block status. 134 default: 135 // Log an error if an invalid status is found. 136 log.Error("Found invalid build status in build block timer", "buildStatus", b.buildStatus) 137 } 138 } 139 140 // markBuilding assumes the [buildBlockLock] is held. 141 func (b *blockBuilder) markBuilding() { 142 select { 143 case b.notifyBuildBlockChan <- commonEng.PendingTxs: 144 b.buildStatus = building 145 default: 146 log.Error("Failed to push PendingTxs notification to the consensus engine.") 147 } 148 } 149 150 // signalTxsReady notifies the engine and sets the status to [building] if the 151 // status is [dontBuild]. Otherwise, the attempt has already begun and this notification 152 // can be safely skipped. 153 func (b *blockBuilder) signalTxsReady() { 154 b.buildBlockLock.Lock() 155 defer b.buildBlockLock.Unlock() 156 157 if b.buildStatus != dontBuild { 158 return 159 } 160 161 // We take a naive approach here and signal the engine that we should build 162 // a block as soon as we receive at least one transaction. 163 // 164 // In the future, we may wish to add optimization here to only signal the 165 // engine if the sum of the projected tips in the mempool satisfies the 166 // required block fee. 167 b.markBuilding() 168 } 169 170 // awaitSubmittedTxs waits for new transactions to be submitted 171 // and notifies the VM when the tx pool has transactions to be 172 // put into a new block. 173 func (b *blockBuilder) awaitSubmittedTxs() { 174 // txSubmitChan is invoked when new transactions are issued as well as on re-orgs which 175 // may orphan transactions that were previously in a preferred block. 176 txSubmitChan := make(chan core.NewTxsEvent) 177 b.txPool.SubscribeNewTxsEvent(txSubmitChan) 178 179 b.shutdownWg.Add(1) 180 go b.ctx.Log.RecoverAndPanic(func() { 181 defer b.shutdownWg.Done() 182 183 for { 184 select { 185 case ethTxsEvent := <-txSubmitChan: 186 log.Trace("New tx detected, trying to generate a block") 187 b.signalTxsReady() 188 189 if b.gossiper != nil && len(ethTxsEvent.Txs) > 0 { 190 // Give time for this node to build a block before attempting to 191 // gossip 192 time.Sleep(waitBlockTime) 193 // [GossipEthTxs] will block unless [gossiper.ethTxsToGossipChan] (an 194 // unbuffered channel) is listened on 195 if err := b.gossiper.GossipEthTxs(ethTxsEvent.Txs); err != nil { 196 log.Warn( 197 "failed to gossip new eth transactions", 198 "err", err, 199 ) 200 } 201 } 202 case <-b.mempool.Pending: 203 log.Trace("New atomic Tx detected, trying to generate a block") 204 b.signalTxsReady() 205 206 newTxs := b.mempool.GetNewTxs() 207 if b.gossiper != nil && len(newTxs) > 0 { 208 // Give time for this node to build a block before attempting to 209 // gossip 210 time.Sleep(waitBlockTime) 211 if err := b.gossiper.GossipAtomicTxs(newTxs); err != nil { 212 log.Warn( 213 "failed to gossip new atomic transactions", 214 "err", err, 215 ) 216 } 217 } 218 case <-b.shutdownChan: 219 b.buildBlockTimer.Stop() 220 return 221 } 222 } 223 }) 224 }