github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/eth/catalyst/simulated_beacon.go (about) 1 // Copyright 2023 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 catalyst 18 19 import ( 20 "crypto/rand" 21 "crypto/sha256" 22 "errors" 23 "math/big" 24 "sync" 25 "time" 26 27 "github.com/ethereum/go-ethereum/beacon/engine" 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/core/txpool" 30 "github.com/ethereum/go-ethereum/core/types" 31 "github.com/ethereum/go-ethereum/crypto/kzg4844" 32 "github.com/ethereum/go-ethereum/eth" 33 "github.com/ethereum/go-ethereum/log" 34 "github.com/ethereum/go-ethereum/node" 35 "github.com/ethereum/go-ethereum/params" 36 "github.com/ethereum/go-ethereum/rpc" 37 ) 38 39 const devEpochLength = 32 40 41 // withdrawalQueue implements a FIFO queue which holds withdrawals that are 42 // pending inclusion. 43 type withdrawalQueue struct { 44 pending chan *types.Withdrawal 45 } 46 47 // add queues a withdrawal for future inclusion. 48 func (w *withdrawalQueue) add(withdrawal *types.Withdrawal) error { 49 select { 50 case w.pending <- withdrawal: 51 break 52 default: 53 return errors.New("withdrawal queue full") 54 } 55 return nil 56 } 57 58 // gatherPending returns a number of queued withdrawals up to a maximum count. 59 func (w *withdrawalQueue) gatherPending(maxCount int) []*types.Withdrawal { 60 withdrawals := []*types.Withdrawal{} 61 for { 62 select { 63 case withdrawal := <-w.pending: 64 withdrawals = append(withdrawals, withdrawal) 65 if len(withdrawals) == maxCount { 66 return withdrawals 67 } 68 default: 69 return withdrawals 70 } 71 } 72 } 73 74 type SimulatedBeacon struct { 75 shutdownCh chan struct{} 76 eth *eth.Ethereum 77 period uint64 78 withdrawals withdrawalQueue 79 80 feeRecipient common.Address 81 feeRecipientLock sync.Mutex // lock gates concurrent access to the feeRecipient 82 83 engineAPI *ConsensusAPI 84 curForkchoiceState engine.ForkchoiceStateV1 85 lastBlockTime uint64 86 } 87 88 // NewSimulatedBeacon constructs a new simulated beacon chain. 89 // Period sets the period in which blocks should be produced. 90 // 91 // - If period is set to 0, a block is produced on every transaction. 92 // via Commit, Fork and AdjustTime. 93 func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, error) { 94 block := eth.BlockChain().CurrentBlock() 95 current := engine.ForkchoiceStateV1{ 96 HeadBlockHash: block.Hash(), 97 SafeBlockHash: block.Hash(), 98 FinalizedBlockHash: block.Hash(), 99 } 100 engineAPI := newConsensusAPIWithoutHeartbeat(eth) 101 102 // if genesis block, send forkchoiceUpdated to trigger transition to PoS 103 if block.Number.Sign() == 0 { 104 if _, err := engineAPI.ForkchoiceUpdatedV2(current, nil); err != nil { 105 return nil, err 106 } 107 } 108 return &SimulatedBeacon{ 109 eth: eth, 110 period: period, 111 shutdownCh: make(chan struct{}), 112 engineAPI: engineAPI, 113 lastBlockTime: block.Time, 114 curForkchoiceState: current, 115 withdrawals: withdrawalQueue{make(chan *types.Withdrawal, 20)}, 116 }, nil 117 } 118 119 func (c *SimulatedBeacon) setFeeRecipient(feeRecipient common.Address) { 120 c.feeRecipientLock.Lock() 121 c.feeRecipient = feeRecipient 122 c.feeRecipientLock.Unlock() 123 } 124 125 // Start invokes the SimulatedBeacon life-cycle function in a goroutine. 126 func (c *SimulatedBeacon) Start() error { 127 if c.period == 0 { 128 // if period is set to 0, do not mine at all 129 // this is used in the simulated backend where blocks 130 // are explicitly mined via Commit, AdjustTime and Fork 131 } else { 132 go c.loop() 133 } 134 return nil 135 } 136 137 // Stop halts the SimulatedBeacon service. 138 func (c *SimulatedBeacon) Stop() error { 139 close(c.shutdownCh) 140 return nil 141 } 142 143 // sealBlock initiates payload building for a new block and creates a new block 144 // with the completed payload. 145 func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp uint64) error { 146 if timestamp <= c.lastBlockTime { 147 timestamp = c.lastBlockTime + 1 148 } 149 c.feeRecipientLock.Lock() 150 feeRecipient := c.feeRecipient 151 c.feeRecipientLock.Unlock() 152 153 // Reset to CurrentBlock in case of the chain was rewound 154 if header := c.eth.BlockChain().CurrentBlock(); c.curForkchoiceState.HeadBlockHash != header.Hash() { 155 finalizedHash := c.finalizedBlockHash(header.Number.Uint64()) 156 c.setCurrentState(header.Hash(), *finalizedHash) 157 } 158 159 var random [32]byte 160 rand.Read(random[:]) 161 fcResponse, err := c.engineAPI.forkchoiceUpdated(c.curForkchoiceState, &engine.PayloadAttributes{ 162 Timestamp: timestamp, 163 SuggestedFeeRecipient: feeRecipient, 164 Withdrawals: withdrawals, 165 Random: random, 166 BeaconRoot: &common.Hash{}, 167 }, engine.PayloadV3, true) 168 if err != nil { 169 return err 170 } 171 if fcResponse == engine.STATUS_SYNCING { 172 return errors.New("chain rewind prevented invocation of payload creation") 173 } 174 envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true) 175 if err != nil { 176 return err 177 } 178 payload := envelope.ExecutionPayload 179 180 var finalizedHash common.Hash 181 if payload.Number%devEpochLength == 0 { 182 finalizedHash = payload.BlockHash 183 } else { 184 if fh := c.finalizedBlockHash(payload.Number); fh == nil { 185 return errors.New("chain rewind interrupted calculation of finalized block hash") 186 } else { 187 finalizedHash = *fh 188 } 189 } 190 191 // Independently calculate the blob hashes from sidecars. 192 blobHashes := make([]common.Hash, 0) 193 if envelope.BlobsBundle != nil { 194 hasher := sha256.New() 195 for _, commit := range envelope.BlobsBundle.Commitments { 196 var c kzg4844.Commitment 197 if len(commit) != len(c) { 198 return errors.New("invalid commitment length") 199 } 200 copy(c[:], commit) 201 blobHashes = append(blobHashes, kzg4844.CalcBlobHashV1(hasher, &c)) 202 } 203 } 204 // Mark the payload as canon 205 if _, err = c.engineAPI.NewPayloadV3(*payload, blobHashes, &common.Hash{}); err != nil { 206 return err 207 } 208 c.setCurrentState(payload.BlockHash, finalizedHash) 209 210 // Mark the block containing the payload as canonical 211 if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil { 212 return err 213 } 214 c.lastBlockTime = payload.Timestamp 215 return nil 216 } 217 218 // loop runs the block production loop for non-zero period configuration 219 func (c *SimulatedBeacon) loop() { 220 timer := time.NewTimer(0) 221 for { 222 select { 223 case <-c.shutdownCh: 224 return 225 case <-timer.C: 226 withdrawals := c.withdrawals.gatherPending(10) 227 if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil { 228 log.Warn("Error performing sealing work", "err", err) 229 } else { 230 timer.Reset(time.Second * time.Duration(c.period)) 231 } 232 } 233 } 234 } 235 236 // finalizedBlockHash returns the block hash of the finalized block corresponding 237 // to the given number or nil if doesn't exist in the chain. 238 func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash { 239 var finalizedNumber uint64 240 if number%devEpochLength == 0 { 241 finalizedNumber = number 242 } else { 243 finalizedNumber = (number - 1) / devEpochLength * devEpochLength 244 } 245 if finalizedBlock := c.eth.BlockChain().GetBlockByNumber(finalizedNumber); finalizedBlock != nil { 246 fh := finalizedBlock.Hash() 247 return &fh 248 } 249 return nil 250 } 251 252 // setCurrentState sets the current forkchoice state 253 func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) { 254 c.curForkchoiceState = engine.ForkchoiceStateV1{ 255 HeadBlockHash: headHash, 256 SafeBlockHash: headHash, 257 FinalizedBlockHash: finalizedHash, 258 } 259 } 260 261 // Commit seals a block on demand. 262 func (c *SimulatedBeacon) Commit() common.Hash { 263 withdrawals := c.withdrawals.gatherPending(10) 264 if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil { 265 log.Warn("Error performing sealing work", "err", err) 266 } 267 return c.eth.BlockChain().CurrentBlock().Hash() 268 } 269 270 // Rollback un-sends previously added transactions. 271 func (c *SimulatedBeacon) Rollback() { 272 // Flush all transactions from the transaction pools 273 maxUint256 := new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1) 274 c.eth.TxPool().SetGasTip(maxUint256) 275 // Set the gas tip back to accept new transactions 276 // TODO (Marius van der Wijden): set gas tip to parameter passed by config 277 c.eth.TxPool().SetGasTip(big.NewInt(params.GWei)) 278 } 279 280 // Fork sets the head to the provided hash. 281 func (c *SimulatedBeacon) Fork(parentHash common.Hash) error { 282 if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 { 283 return errors.New("pending block dirty") 284 } 285 parent := c.eth.BlockChain().GetBlockByHash(parentHash) 286 if parent == nil { 287 return errors.New("parent not found") 288 } 289 return c.eth.BlockChain().SetHead(parent.NumberU64()) 290 } 291 292 // AdjustTime creates a new block with an adjusted timestamp. 293 func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error { 294 if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 { 295 return errors.New("could not adjust time on non-empty block") 296 } 297 parent := c.eth.BlockChain().CurrentBlock() 298 if parent == nil { 299 return errors.New("parent not found") 300 } 301 withdrawals := c.withdrawals.gatherPending(10) 302 return c.sealBlock(withdrawals, parent.Time+uint64(adjustment)) 303 } 304 305 func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) { 306 api := &api{sim} 307 if sim.period == 0 { 308 // mine on demand if period is set to 0 309 go api.loop() 310 } 311 stack.RegisterAPIs([]rpc.API{ 312 { 313 Namespace: "dev", 314 Service: api, 315 Version: "1.0", 316 }, 317 }) 318 }