github.1485827954.workers.dev/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 }