github.com/calmw/ethereum@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/calmw/ethereum/beacon/engine" 27 "github.com/calmw/ethereum/common" 28 "github.com/calmw/ethereum/core/types" 29 "github.com/calmw/ethereum/log" 30 "github.com/calmw/ethereum/params" 31 "github.com/calmw/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/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 } 44 45 // Id computes an 8-byte identifier by hashing the components of the payload arguments. 46 func (args *BuildPayloadArgs) Id() engine.PayloadID { 47 // Hash 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(block *types.Block, fees *big.Int, 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 || fees.Cmp(payload.fullFees) > 0 { 100 payload.full = block 101 payload.fullFees = fees 102 103 feesInEther := new(big.Float).Quo(new(big.Float).SetInt(fees), big.NewFloat(params.Ether)) 104 log.Info("Updated payload", "id", payload.id, "number", block.NumberU64(), "hash", block.Hash(), 105 "txs", len(block.Transactions()), "gas", block.GasUsed(), "fees", feesInEther, 106 "root", block.Root(), "elapsed", common.PrettyDuration(elapsed)) 107 } 108 payload.cond.Broadcast() // fire signal for notifying full block 109 } 110 111 // Resolve returns the latest built payload and also terminates the background 112 // thread for updating payload. It's safe to be called multiple times. 113 func (payload *Payload) Resolve() *engine.ExecutionPayloadEnvelope { 114 payload.lock.Lock() 115 defer payload.lock.Unlock() 116 117 select { 118 case <-payload.stop: 119 default: 120 close(payload.stop) 121 } 122 if payload.full != nil { 123 return engine.BlockToExecutableData(payload.full, payload.fullFees) 124 } 125 return engine.BlockToExecutableData(payload.empty, big.NewInt(0)) 126 } 127 128 // ResolveEmpty is basically identical to Resolve, but it expects empty block only. 129 // It's only used in tests. 130 func (payload *Payload) ResolveEmpty() *engine.ExecutionPayloadEnvelope { 131 payload.lock.Lock() 132 defer payload.lock.Unlock() 133 134 return engine.BlockToExecutableData(payload.empty, big.NewInt(0)) 135 } 136 137 // ResolveFull is basically identical to Resolve, but it expects full block only. 138 // It's only used in tests. 139 func (payload *Payload) ResolveFull() *engine.ExecutionPayloadEnvelope { 140 payload.lock.Lock() 141 defer payload.lock.Unlock() 142 143 if payload.full == nil { 144 select { 145 case <-payload.stop: 146 return nil 147 default: 148 } 149 payload.cond.Wait() 150 } 151 return engine.BlockToExecutableData(payload.full, payload.fullFees) 152 } 153 154 // buildPayload builds the payload according to the provided parameters. 155 func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { 156 // Build the initial version with no transaction included. It should be fast 157 // enough to run. The empty payload can at least make sure there is something 158 // to deliver for not missing slot. 159 empty, _, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, args.Withdrawals, true) 160 if err != nil { 161 return nil, err 162 } 163 // Construct a payload object for return. 164 payload := newPayload(empty, args.Id()) 165 166 // Spin up a routine for updating the payload in background. This strategy 167 // can maximum the revenue for including transactions with highest fee. 168 go func() { 169 // Setup the timer for re-building the payload. The initial clock is kept 170 // for triggering process immediately. 171 timer := time.NewTimer(0) 172 defer timer.Stop() 173 174 // Setup the timer for terminating the process if SECONDS_PER_SLOT (12s in 175 // the Mainnet configuration) have passed since the point in time identified 176 // by the timestamp parameter. 177 endTimer := time.NewTimer(time.Second * 12) 178 179 for { 180 select { 181 case <-timer.C: 182 start := time.Now() 183 block, fees, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, args.Withdrawals, false) 184 if err == nil { 185 payload.update(block, fees, time.Since(start)) 186 } 187 timer.Reset(w.recommit) 188 case <-payload.stop: 189 log.Info("Stopping work on payload", "id", payload.id, "reason", "delivery") 190 return 191 case <-endTimer.C: 192 log.Info("Stopping work on payload", "id", payload.id, "reason", "timeout") 193 return 194 } 195 } 196 }() 197 return payload, nil 198 }