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  }