github.com/ethereum/go-ethereum@v1.14.3/miner/payload_building.go (about)

     1  // Copyright 2022 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
    16  
    17  package miner
    18  
    19  import (
    20  	"crypto/sha256"
    21  	"encoding/binary"
    22  	"math/big"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/beacon/engine"
    27  	"github.com/ethereum/go-ethereum/common"
    28  	"github.com/ethereum/go-ethereum/core/types"
    29  	"github.com/ethereum/go-ethereum/log"
    30  	"github.com/ethereum/go-ethereum/params"
    31  	"github.com/ethereum/go-ethereum/rlp"
    32  )
    33  
    34  // BuildPayloadArgs contains the provided parameters for building payload.
    35  // Check engine-api specification for more details.
    36  // https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3
    37  type BuildPayloadArgs struct {
    38  	Parent       common.Hash           // The parent block to build payload on top
    39  	Timestamp    uint64                // The provided timestamp of generated payload
    40  	FeeRecipient common.Address        // The provided recipient address for collecting transaction fee
    41  	Random       common.Hash           // The provided randomness value
    42  	Withdrawals  types.Withdrawals     // The provided withdrawals
    43  	BeaconRoot   *common.Hash          // The provided beaconRoot (Cancun)
    44  	Version      engine.PayloadVersion // Versioning byte for payload id calculation.
    45  }
    46  
    47  // Id computes an 8-byte identifier by hashing the components of the payload arguments.
    48  func (args *BuildPayloadArgs) Id() engine.PayloadID {
    49  	hasher := sha256.New()
    50  	hasher.Write(args.Parent[:])
    51  	binary.Write(hasher, binary.BigEndian, args.Timestamp)
    52  	hasher.Write(args.Random[:])
    53  	hasher.Write(args.FeeRecipient[:])
    54  	rlp.Encode(hasher, args.Withdrawals)
    55  	if args.BeaconRoot != nil {
    56  		hasher.Write(args.BeaconRoot[:])
    57  	}
    58  	var out engine.PayloadID
    59  	copy(out[:], hasher.Sum(nil)[:8])
    60  	out[0] = byte(args.Version)
    61  	return out
    62  }
    63  
    64  // Payload wraps the built payload(block waiting for sealing). According to the
    65  // engine-api specification, EL should build the initial version of the payload
    66  // which has an empty transaction set and then keep update it in order to maximize
    67  // the revenue. Therefore, the empty-block here is always available and full-block
    68  // will be set/updated afterwards.
    69  type Payload struct {
    70  	id       engine.PayloadID
    71  	empty    *types.Block
    72  	full     *types.Block
    73  	sidecars []*types.BlobTxSidecar
    74  	fullFees *big.Int
    75  	stop     chan struct{}
    76  	lock     sync.Mutex
    77  	cond     *sync.Cond
    78  }
    79  
    80  // newPayload initializes the payload object.
    81  func newPayload(empty *types.Block, id engine.PayloadID) *Payload {
    82  	payload := &Payload{
    83  		id:    id,
    84  		empty: empty,
    85  		stop:  make(chan struct{}),
    86  	}
    87  	log.Info("Starting work on payload", "id", payload.id)
    88  	payload.cond = sync.NewCond(&payload.lock)
    89  	return payload
    90  }
    91  
    92  // update updates the full-block with latest built version.
    93  func (payload *Payload) update(r *newPayloadResult, elapsed time.Duration) {
    94  	payload.lock.Lock()
    95  	defer payload.lock.Unlock()
    96  
    97  	select {
    98  	case <-payload.stop:
    99  		return // reject stale update
   100  	default:
   101  	}
   102  	// Ensure the newly provided full block has a higher transaction fee.
   103  	// In post-merge stage, there is no uncle reward anymore and transaction
   104  	// fee(apart from the mev revenue) is the only indicator for comparison.
   105  	if payload.full == nil || r.fees.Cmp(payload.fullFees) > 0 {
   106  		payload.full = r.block
   107  		payload.fullFees = r.fees
   108  		payload.sidecars = r.sidecars
   109  
   110  		feesInEther := new(big.Float).Quo(new(big.Float).SetInt(r.fees), big.NewFloat(params.Ether))
   111  		log.Info("Updated payload",
   112  			"id", payload.id,
   113  			"number", r.block.NumberU64(),
   114  			"hash", r.block.Hash(),
   115  			"txs", len(r.block.Transactions()),
   116  			"withdrawals", len(r.block.Withdrawals()),
   117  			"gas", r.block.GasUsed(),
   118  			"fees", feesInEther,
   119  			"root", r.block.Root(),
   120  			"elapsed", common.PrettyDuration(elapsed),
   121  		)
   122  	}
   123  	payload.cond.Broadcast() // fire signal for notifying full block
   124  }
   125  
   126  // Resolve returns the latest built payload and also terminates the background
   127  // thread for updating payload. It's safe to be called multiple times.
   128  func (payload *Payload) Resolve() *engine.ExecutionPayloadEnvelope {
   129  	payload.lock.Lock()
   130  	defer payload.lock.Unlock()
   131  
   132  	select {
   133  	case <-payload.stop:
   134  	default:
   135  		close(payload.stop)
   136  	}
   137  	if payload.full != nil {
   138  		return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars)
   139  	}
   140  	return engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil)
   141  }
   142  
   143  // ResolveEmpty is basically identical to Resolve, but it expects empty block only.
   144  // It's only used in tests.
   145  func (payload *Payload) ResolveEmpty() *engine.ExecutionPayloadEnvelope {
   146  	payload.lock.Lock()
   147  	defer payload.lock.Unlock()
   148  
   149  	return engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil)
   150  }
   151  
   152  // ResolveFull is basically identical to Resolve, but it expects full block only.
   153  // Don't call Resolve until ResolveFull returns, otherwise it might block forever.
   154  func (payload *Payload) ResolveFull() *engine.ExecutionPayloadEnvelope {
   155  	payload.lock.Lock()
   156  	defer payload.lock.Unlock()
   157  
   158  	if payload.full == nil {
   159  		select {
   160  		case <-payload.stop:
   161  			return nil
   162  		default:
   163  		}
   164  		// Wait the full payload construction. Note it might block
   165  		// forever if Resolve is called in the meantime which
   166  		// terminates the background construction process.
   167  		payload.cond.Wait()
   168  	}
   169  	// Terminate the background payload construction
   170  	select {
   171  	case <-payload.stop:
   172  	default:
   173  		close(payload.stop)
   174  	}
   175  	return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars)
   176  }
   177  
   178  // buildPayload builds the payload according to the provided parameters.
   179  func (miner *Miner) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
   180  	// Build the initial version with no transaction included. It should be fast
   181  	// enough to run. The empty payload can at least make sure there is something
   182  	// to deliver for not missing slot.
   183  	emptyParams := &generateParams{
   184  		timestamp:   args.Timestamp,
   185  		forceTime:   true,
   186  		parentHash:  args.Parent,
   187  		coinbase:    args.FeeRecipient,
   188  		random:      args.Random,
   189  		withdrawals: args.Withdrawals,
   190  		beaconRoot:  args.BeaconRoot,
   191  		noTxs:       true,
   192  	}
   193  	empty := miner.generateWork(emptyParams)
   194  	if empty.err != nil {
   195  		return nil, empty.err
   196  	}
   197  
   198  	// Construct a payload object for return.
   199  	payload := newPayload(empty.block, args.Id())
   200  
   201  	// Spin up a routine for updating the payload in background. This strategy
   202  	// can maximum the revenue for including transactions with highest fee.
   203  	go func() {
   204  		// Setup the timer for re-building the payload. The initial clock is kept
   205  		// for triggering process immediately.
   206  		timer := time.NewTimer(0)
   207  		defer timer.Stop()
   208  
   209  		// Setup the timer for terminating the process if SECONDS_PER_SLOT (12s in
   210  		// the Mainnet configuration) have passed since the point in time identified
   211  		// by the timestamp parameter.
   212  		endTimer := time.NewTimer(time.Second * 12)
   213  
   214  		fullParams := &generateParams{
   215  			timestamp:   args.Timestamp,
   216  			forceTime:   true,
   217  			parentHash:  args.Parent,
   218  			coinbase:    args.FeeRecipient,
   219  			random:      args.Random,
   220  			withdrawals: args.Withdrawals,
   221  			beaconRoot:  args.BeaconRoot,
   222  			noTxs:       false,
   223  		}
   224  
   225  		for {
   226  			select {
   227  			case <-timer.C:
   228  				start := time.Now()
   229  				r := miner.generateWork(fullParams)
   230  				if r.err == nil {
   231  					payload.update(r, time.Since(start))
   232  				} else {
   233  					log.Info("Error while generating work", "id", payload.id, "err", r.err)
   234  				}
   235  				timer.Reset(miner.config.Recommit)
   236  			case <-payload.stop:
   237  				log.Info("Stopping work on payload", "id", payload.id, "reason", "delivery")
   238  				return
   239  			case <-endTimer.C:
   240  				log.Info("Stopping work on payload", "id", payload.id, "reason", "timeout")
   241  				return
   242  			}
   243  		}
   244  	}()
   245  	return payload, nil
   246  }