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  }