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