github.com/jimmyx0x/go-ethereum@v1.10.28/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/common" 27 "github.com/ethereum/go-ethereum/core/beacon" 28 "github.com/ethereum/go-ethereum/core/types" 29 "github.com/ethereum/go-ethereum/log" 30 "github.com/ethereum/go-ethereum/params" 31 ) 32 33 // BuildPayloadArgs contains the provided parameters for building payload. 34 // Check engine-api specification for more details. 35 // https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#payloadattributesv1 36 type BuildPayloadArgs struct { 37 Parent common.Hash // The parent block to build payload on top 38 Timestamp uint64 // The provided timestamp of generated payload 39 FeeRecipient common.Address // The provided recipient address for collecting transaction fee 40 Random common.Hash // The provided randomness value 41 } 42 43 // Id computes an 8-byte identifier by hashing the components of the payload arguments. 44 func (args *BuildPayloadArgs) Id() beacon.PayloadID { 45 // Hash 46 hasher := sha256.New() 47 hasher.Write(args.Parent[:]) 48 binary.Write(hasher, binary.BigEndian, args.Timestamp) 49 hasher.Write(args.Random[:]) 50 hasher.Write(args.FeeRecipient[:]) 51 var out beacon.PayloadID 52 copy(out[:], hasher.Sum(nil)[:8]) 53 return out 54 } 55 56 // Payload wraps the built payload(block waiting for sealing). According to the 57 // engine-api specification, EL should build the initial version of the payload 58 // which has an empty transaction set and then keep update it in order to maximize 59 // the revenue. Therefore, the empty-block here is always available and full-block 60 // will be set/updated afterwards. 61 type Payload struct { 62 id beacon.PayloadID 63 empty *types.Block 64 full *types.Block 65 fullFees *big.Int 66 stop chan struct{} 67 lock sync.Mutex 68 cond *sync.Cond 69 } 70 71 // newPayload initializes the payload object. 72 func newPayload(empty *types.Block, id beacon.PayloadID) *Payload { 73 payload := &Payload{ 74 id: id, 75 empty: empty, 76 stop: make(chan struct{}), 77 } 78 log.Info("Starting work on payload", "id", payload.id) 79 payload.cond = sync.NewCond(&payload.lock) 80 return payload 81 } 82 83 // update updates the full-block with latest built version. 84 func (payload *Payload) update(block *types.Block, fees *big.Int, elapsed time.Duration) { 85 payload.lock.Lock() 86 defer payload.lock.Unlock() 87 88 select { 89 case <-payload.stop: 90 return // reject stale update 91 default: 92 } 93 // Ensure the newly provided full block has a higher transaction fee. 94 // In post-merge stage, there is no uncle reward anymore and transaction 95 // fee(apart from the mev revenue) is the only indicator for comparison. 96 if payload.full == nil || fees.Cmp(payload.fullFees) > 0 { 97 payload.full = block 98 payload.fullFees = fees 99 100 feesInEther := new(big.Float).Quo(new(big.Float).SetInt(fees), big.NewFloat(params.Ether)) 101 log.Info("Updated payload", "id", payload.id, "number", block.NumberU64(), "hash", block.Hash(), 102 "txs", len(block.Transactions()), "gas", block.GasUsed(), "fees", feesInEther, 103 "root", block.Root(), "elapsed", common.PrettyDuration(elapsed)) 104 } 105 payload.cond.Broadcast() // fire signal for notifying full block 106 } 107 108 // Resolve returns the latest built payload and also terminates the background 109 // thread for updating payload. It's safe to be called multiple times. 110 func (payload *Payload) Resolve() *beacon.ExecutableDataV1 { 111 payload.lock.Lock() 112 defer payload.lock.Unlock() 113 114 select { 115 case <-payload.stop: 116 default: 117 close(payload.stop) 118 } 119 if payload.full != nil { 120 return beacon.BlockToExecutableData(payload.full) 121 } 122 return beacon.BlockToExecutableData(payload.empty) 123 } 124 125 // ResolveEmpty is basically identical to Resolve, but it expects empty block only. 126 // It's only used in tests. 127 func (payload *Payload) ResolveEmpty() *beacon.ExecutableDataV1 { 128 payload.lock.Lock() 129 defer payload.lock.Unlock() 130 131 return beacon.BlockToExecutableData(payload.empty) 132 } 133 134 // ResolveFull is basically identical to Resolve, but it expects full block only. 135 // It's only used in tests. 136 func (payload *Payload) ResolveFull() *beacon.ExecutableDataV1 { 137 payload.lock.Lock() 138 defer payload.lock.Unlock() 139 140 if payload.full == nil { 141 select { 142 case <-payload.stop: 143 return nil 144 default: 145 } 146 payload.cond.Wait() 147 } 148 return beacon.BlockToExecutableData(payload.full) 149 } 150 151 // buildPayload builds the payload according to the provided parameters. 152 func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { 153 // Build the initial version with no transaction included. It should be fast 154 // enough to run. The empty payload can at least make sure there is something 155 // to deliver for not missing slot. 156 empty, _, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, true) 157 if err != nil { 158 return nil, err 159 } 160 // Construct a payload object for return. 161 payload := newPayload(empty, args.Id()) 162 163 // Spin up a routine for updating the payload in background. This strategy 164 // can maximum the revenue for including transactions with highest fee. 165 go func() { 166 // Setup the timer for re-building the payload. The initial clock is kept 167 // for triggering process immediately. 168 timer := time.NewTimer(0) 169 defer timer.Stop() 170 171 // Setup the timer for terminating the process if SECONDS_PER_SLOT (12s in 172 // the Mainnet configuration) have passed since the point in time identified 173 // by the timestamp parameter. 174 endTimer := time.NewTimer(time.Second * 12) 175 176 for { 177 select { 178 case <-timer.C: 179 start := time.Now() 180 block, fees, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, false) 181 if err == nil { 182 payload.update(block, fees, time.Since(start)) 183 } 184 timer.Reset(w.recommit) 185 case <-payload.stop: 186 log.Info("Stopping work on payload", "id", payload.id, "reason", "delivery") 187 return 188 case <-endTimer.C: 189 log.Info("Stopping work on payload", "id", payload.id, "reason", "timeout") 190 return 191 } 192 } 193 }() 194 return payload, nil 195 }