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 }