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 }