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 }