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