github.com/MetalBlockchain/subnet-evm@v0.4.9/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/MetalBlockchain/metalgo/utils/timer" 11 "github.com/MetalBlockchain/subnet-evm/core" 12 "github.com/MetalBlockchain/subnet-evm/params" 13 14 "github.com/MetalBlockchain/metalgo/snow" 15 commonEng "github.com/MetalBlockchain/metalgo/snow/engine/common" 16 "github.com/ethereum/go-ethereum/log" 17 ) 18 19 const ( 20 // waitBlockTime is the amount of time to wait for BuildBlock to be 21 // called by the engine before deciding whether or not to gossip the 22 // transaction that triggered the PendingTxs message to the engine. 23 // 24 // This is done to reduce contention in the network when there is no 25 // preferred producer. If we did not wait here, we may gossip a new 26 // transaction to a peer while building a block that will conflict with 27 // whatever the peer makes. 28 waitBlockTime = 100 * time.Millisecond 29 30 // Minimum amount of time to wait after building a block before attempting to build a block 31 // a second time without changing the contents of the mempool. 32 minBlockBuildingRetryDelay = 500 * time.Millisecond 33 ) 34 35 type blockBuilder struct { 36 ctx *snow.Context 37 chainConfig *params.ChainConfig 38 39 txPool *core.TxPool 40 gossiper Gossiper 41 42 shutdownChan <-chan struct{} 43 shutdownWg *sync.WaitGroup 44 45 // A message is sent on this channel when a new block 46 // is ready to be build. This notifies the consensus engine. 47 notifyBuildBlockChan chan<- commonEng.Message 48 49 // [buildBlockLock] must be held when accessing [buildSent] 50 buildBlockLock sync.Mutex 51 52 // buildSent is true iff we have sent a PendingTxs message to the consensus message and 53 // are still waiting for buildBlock to be called. 54 buildSent bool 55 56 // buildBlockTimer is a timer used to delay retrying block building a minimum amount of time 57 // with the same contents of the mempool. 58 // If the mempool receives a new transaction, the block builder will send a new notification to 59 // the engine and cancel the timer. 60 buildBlockTimer *timer.Timer 61 } 62 63 func (vm *VM) NewBlockBuilder(notifyBuildBlockChan chan<- commonEng.Message) *blockBuilder { 64 b := &blockBuilder{ 65 ctx: vm.ctx, 66 chainConfig: vm.chainConfig, 67 txPool: vm.txPool, 68 gossiper: vm.gossiper, 69 shutdownChan: vm.shutdownChan, 70 shutdownWg: &vm.shutdownWg, 71 notifyBuildBlockChan: notifyBuildBlockChan, 72 } 73 b.handleBlockBuilding() 74 return b 75 } 76 77 // handleBlockBuilding dispatches a timer used to delay block building retry attempts when the contents 78 // of the mempool has not been changed since the last attempt. 79 func (b *blockBuilder) handleBlockBuilding() { 80 b.buildBlockTimer = timer.NewTimer(b.buildBlockTimerCallback) 81 go b.ctx.Log.RecoverAndPanic(b.buildBlockTimer.Dispatch) 82 } 83 84 // buildBlockTimerCallback is the timer callback that will send a PendingTxs notification 85 // to the consensus engine if there are transactions in the mempool. 86 func (b *blockBuilder) buildBlockTimerCallback() { 87 b.buildBlockLock.Lock() 88 defer b.buildBlockLock.Unlock() 89 90 // If there are still transactions in the mempool, send another notification to 91 // the engine to retry BuildBlock. 92 if b.needToBuild() { 93 b.markBuilding() 94 } 95 } 96 97 // handleGenerateBlock is called from the VM immediately after BuildBlock. 98 func (b *blockBuilder) handleGenerateBlock() { 99 b.buildBlockLock.Lock() 100 defer b.buildBlockLock.Unlock() 101 102 // Reset buildSent now that the engine has called BuildBlock. 103 b.buildSent = false 104 105 // Set a timer to check if calling build block a second time is needed. 106 b.buildBlockTimer.SetTimeoutIn(minBlockBuildingRetryDelay) 107 } 108 109 // needToBuild returns true if there are outstanding transactions to be issued 110 // into a block. 111 func (b *blockBuilder) needToBuild() bool { 112 size := b.txPool.PendingSize() 113 return size > 0 114 } 115 116 // markBuilding adds a PendingTxs message to the toEngine channel. 117 // markBuilding assumes the [buildBlockLock] is held. 118 func (b *blockBuilder) markBuilding() { 119 // If the engine has not called BuildBlock, no need to send another message. 120 if b.buildSent { 121 return 122 } 123 b.buildBlockTimer.Cancel() // Cancel any future attempt from the timer to send a PendingTxs message 124 125 select { 126 case b.notifyBuildBlockChan <- commonEng.PendingTxs: 127 b.buildSent = true 128 default: 129 log.Error("Failed to push PendingTxs notification to the consensus engine.") 130 } 131 } 132 133 // signalTxsReady sends a PendingTxs notification to the consensus engine. 134 // If BuildBlock has not been called since the last PendingTxs message was sent, 135 // signalTxsReady will not send a duplicate. 136 func (b *blockBuilder) signalTxsReady() { 137 b.buildBlockLock.Lock() 138 defer b.buildBlockLock.Unlock() 139 140 // We take a naive approach here and signal the engine that we should build 141 // a block as soon as we receive at least one new transaction. 142 // 143 // In the future, we may wish to add optimization here to only signal the 144 // engine if the sum of the projected tips in the mempool satisfies the 145 // required block fee. 146 b.markBuilding() 147 } 148 149 // awaitSubmittedTxs waits for new transactions to be submitted 150 // and notifies the VM when the tx pool has transactions to be 151 // put into a new block. 152 func (b *blockBuilder) awaitSubmittedTxs() { 153 // txSubmitChan is invoked when new transactions are issued as well as on re-orgs which 154 // may orphan transactions that were previously in a preferred block. 155 txSubmitChan := make(chan core.NewTxsEvent) 156 b.txPool.SubscribeNewTxsEvent(txSubmitChan) 157 158 b.shutdownWg.Add(1) 159 go b.ctx.Log.RecoverAndPanic(func() { 160 defer b.shutdownWg.Done() 161 162 for { 163 select { 164 case ethTxsEvent := <-txSubmitChan: 165 log.Trace("New tx detected, trying to generate a block") 166 b.signalTxsReady() 167 168 if b.gossiper != nil && len(ethTxsEvent.Txs) > 0 { 169 // Give time for this node to build a block before attempting to 170 // gossip 171 time.Sleep(waitBlockTime) 172 // [GossipTxs] will block unless [gossiper.txsToGossipChan] (an 173 // unbuffered channel) is listened on 174 if err := b.gossiper.GossipTxs(ethTxsEvent.Txs); err != nil { 175 log.Warn( 176 "failed to gossip new eth transactions", 177 "err", err, 178 ) 179 } 180 } 181 case <-b.shutdownChan: 182 b.buildBlockTimer.Stop() 183 return 184 } 185 } 186 }) 187 }