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