github.com/ethereum/go-ethereum@v1.16.1/eth/catalyst/api_test.go (about) 1 // Copyright 2021 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 "bytes" 21 "context" 22 crand "crypto/rand" 23 "errors" 24 "fmt" 25 "math/big" 26 "math/rand" 27 "reflect" 28 "sync" 29 "testing" 30 "time" 31 32 "github.com/ethereum/go-ethereum/beacon/engine" 33 "github.com/ethereum/go-ethereum/common" 34 "github.com/ethereum/go-ethereum/common/hexutil" 35 "github.com/ethereum/go-ethereum/consensus/beacon" 36 "github.com/ethereum/go-ethereum/consensus/ethash" 37 "github.com/ethereum/go-ethereum/core" 38 "github.com/ethereum/go-ethereum/core/types" 39 "github.com/ethereum/go-ethereum/crypto" 40 "github.com/ethereum/go-ethereum/crypto/kzg4844" 41 "github.com/ethereum/go-ethereum/eth" 42 "github.com/ethereum/go-ethereum/eth/ethconfig" 43 "github.com/ethereum/go-ethereum/internal/version" 44 "github.com/ethereum/go-ethereum/miner" 45 "github.com/ethereum/go-ethereum/node" 46 "github.com/ethereum/go-ethereum/p2p" 47 "github.com/ethereum/go-ethereum/params" 48 "github.com/ethereum/go-ethereum/rpc" 49 "github.com/ethereum/go-ethereum/trie" 50 ) 51 52 var ( 53 // testKey is a private key to use for funding a tester account. 54 testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 55 56 // testAddr is the Ethereum address of the tester account. 57 testAddr = crypto.PubkeyToAddress(testKey.PublicKey) 58 59 testBalance = big.NewInt(2e18) 60 ) 61 62 func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { 63 config := *params.AllEthashProtocolChanges 64 engine := beacon.New(ethash.NewFaker()) 65 if merged { 66 config.TerminalTotalDifficulty = common.Big0 67 config.MergeNetsplitBlock = common.Big0 68 } else { 69 // When !merged, the tests expect the next block after the generated chain to be in PoS. 70 config.MergeNetsplitBlock = big.NewInt(int64(n + 1)) 71 } 72 73 genesis := &core.Genesis{ 74 Config: &config, 75 Alloc: types.GenesisAlloc{ 76 testAddr: {Balance: testBalance}, 77 params.BeaconRootsAddress: {Balance: common.Big0, Code: common.Hex2Bytes("3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500")}, 78 config.DepositContractAddress: { 79 // Simple deposit generator, source: https://gist.github.com/lightclient/54abb2af2465d6969fa6d1920b9ad9d7 80 Code: common.Hex2Bytes("6080604052366103aa575f603067ffffffffffffffff811115610025576100246103ae565b5b6040519080825280601f01601f1916602001820160405280156100575781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061007d5761007c6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f602067ffffffffffffffff8111156100c7576100c66103ae565b5b6040519080825280601f01601f1916602001820160405280156100f95781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061011f5761011e6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff811115610169576101686103ae565b5b6040519080825280601f01601f19166020018201604052801561019b5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f815181106101c1576101c06103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f606067ffffffffffffffff81111561020b5761020a6103ae565b5b6040519080825280601f01601f19166020018201604052801561023d5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610263576102626103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff8111156102ad576102ac6103ae565b5b6040519080825280601f01601f1916602001820160405280156102df5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610305576103046103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f8081819054906101000a900460ff168092919061035090610441565b91906101000a81548160ff021916908360ff160217905550507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c585858585856040516103a09594939291906104d9565b60405180910390a1005b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60ff82169050919050565b5f61044b82610435565b915060ff820361045e5761045d610408565b5b600182019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6104ab82610469565b6104b58185610473565b93506104c5818560208601610483565b6104ce81610491565b840191505092915050565b5f60a0820190508181035f8301526104f181886104a1565b9050818103602083015261050581876104a1565b9050818103604083015261051981866104a1565b9050818103606083015261052d81856104a1565b9050818103608083015261054181846104a1565b9050969550505050505056fea26469706673582212208569967e58690162d7d6fe3513d07b393b4c15e70f41505cbbfd08f53eba739364736f6c63430008190033"), 81 Nonce: 0, 82 Balance: big.NewInt(0), 83 }, 84 }, 85 ExtraData: []byte("test genesis"), 86 Timestamp: 9000, 87 BaseFee: big.NewInt(params.InitialBaseFee), 88 Difficulty: big.NewInt(0), 89 } 90 testNonce := uint64(0) 91 generate := func(i int, g *core.BlockGen) { 92 g.OffsetTime(5) 93 g.SetExtra([]byte("test")) 94 tx, _ := types.SignTx(types.NewTransaction(testNonce, common.HexToAddress("0x9a9070028361F7AAbeB3f2F2Dc07F82C4a98A02a"), big.NewInt(1), params.TxGas, big.NewInt(params.InitialBaseFee*2), nil), types.LatestSigner(&config), testKey) 95 g.AddTx(tx) 96 testNonce++ 97 } 98 _, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, n, generate) 99 100 if !merged { 101 totalDifficulty := big.NewInt(0) 102 for _, b := range blocks { 103 totalDifficulty.Add(totalDifficulty, b.Difficulty()) 104 } 105 config.TerminalTotalDifficulty = totalDifficulty 106 } 107 return genesis, blocks 108 } 109 110 func TestEth2AssembleBlock(t *testing.T) { 111 genesis, blocks := generateMergeChain(10, false) 112 n, ethservice := startEthService(t, genesis, blocks) 113 defer n.Close() 114 115 api := NewConsensusAPI(ethservice) 116 signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID) 117 tx, err := types.SignTx(types.NewTransaction(uint64(10), blocks[9].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) 118 if err != nil { 119 t.Fatalf("error signing transaction, err=%v", err) 120 } 121 ethservice.TxPool().Add([]*types.Transaction{tx}, true) 122 blockParams := engine.PayloadAttributes{ 123 Timestamp: blocks[9].Time() + 5, 124 } 125 // The miner needs to pick up on the txs in the pool, so a few retries might be 126 // needed. 127 if _, testErr := assembleWithTransactions(api, blocks[9].Hash(), &blockParams, 1); testErr != nil { 128 t.Fatal(testErr) 129 } 130 } 131 132 // assembleWithTransactions tries to assemble a block, retrying until it has 'want', 133 // number of transactions in it, or it has retried three times. 134 func assembleWithTransactions(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes, want int) (execData *engine.ExecutableData, err error) { 135 for retries := 3; retries > 0; retries-- { 136 execData, err = assembleBlock(api, parentHash, params) 137 if err != nil { 138 return nil, err 139 } 140 if have, want := len(execData.Transactions), want; have != want { 141 err = fmt.Errorf("invalid number of transactions, have %d want %d", have, want) 142 continue 143 } 144 return execData, nil 145 } 146 return nil, err 147 } 148 149 func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { 150 genesis, blocks := generateMergeChain(10, false) 151 n, ethservice := startEthService(t, genesis, blocks[:9]) 152 defer n.Close() 153 154 api := NewConsensusAPI(ethservice) 155 156 // Put the 10th block's tx in the pool and produce a new block 157 txs := blocks[9].Transactions() 158 api.eth.TxPool().Add(txs, true) 159 blockParams := engine.PayloadAttributes{ 160 Timestamp: blocks[8].Time() + 5, 161 } 162 // The miner needs to pick up on the txs in the pool, so a few retries might be 163 // needed. 164 if _, err := assembleWithTransactions(api, blocks[8].Hash(), &blockParams, blocks[9].Transactions().Len()); err != nil { 165 t.Fatal(err) 166 } 167 } 168 169 func TestEth2PrepareAndGetPayload(t *testing.T) { 170 genesis, blocks := generateMergeChain(10, false) 171 // We need to properly set the terminal total difficulty 172 genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[9].Difficulty()) 173 n, ethservice := startEthService(t, genesis, blocks[:9]) 174 defer n.Close() 175 176 api := NewConsensusAPI(ethservice) 177 178 // Put the 10th block's tx in the pool and produce a new block 179 txs := blocks[9].Transactions() 180 ethservice.TxPool().Add(txs, true) 181 blockParams := engine.PayloadAttributes{ 182 Timestamp: blocks[8].Time() + 5, 183 } 184 fcState := engine.ForkchoiceStateV1{ 185 HeadBlockHash: blocks[8].Hash(), 186 SafeBlockHash: common.Hash{}, 187 FinalizedBlockHash: common.Hash{}, 188 } 189 _, err := api.ForkchoiceUpdatedV1(fcState, &blockParams) 190 if err != nil { 191 t.Fatalf("error preparing payload, err=%v", err) 192 } 193 // give the payload some time to be built 194 payloadID := (&miner.BuildPayloadArgs{ 195 Parent: fcState.HeadBlockHash, 196 Timestamp: blockParams.Timestamp, 197 FeeRecipient: blockParams.SuggestedFeeRecipient, 198 Random: blockParams.Random, 199 BeaconRoot: blockParams.BeaconRoot, 200 Version: engine.PayloadV1, 201 }).Id() 202 execData, err := api.getPayload(payloadID, true) 203 if err != nil { 204 t.Fatalf("error getting payload, err=%v", err) 205 } 206 if len(execData.ExecutionPayload.Transactions) != blocks[9].Transactions().Len() { 207 t.Fatalf("invalid number of transactions %d != 1", len(execData.ExecutionPayload.Transactions)) 208 } 209 // Test invalid payloadID 210 var invPayload engine.PayloadID 211 copy(invPayload[:], payloadID[:]) 212 invPayload[0] = ^invPayload[0] 213 _, err = api.GetPayloadV1(invPayload) 214 if err == nil { 215 t.Fatal("expected error retrieving invalid payload") 216 } 217 } 218 219 func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan core.RemovedLogsEvent, wantNew, wantRemoved int) { 220 t.Helper() 221 222 if len(logsCh) != wantNew { 223 t.Fatalf("wrong number of log events: got %d, want %d", len(logsCh), wantNew) 224 } 225 if len(rmLogsCh) != wantRemoved { 226 t.Fatalf("wrong number of removed log events: got %d, want %d", len(rmLogsCh), wantRemoved) 227 } 228 // Drain events. 229 for i := 0; i < len(logsCh); i++ { 230 <-logsCh 231 } 232 for i := 0; i < len(rmLogsCh); i++ { 233 <-rmLogsCh 234 } 235 } 236 237 func TestInvalidPayloadTimestamp(t *testing.T) { 238 genesis, preMergeBlocks := generateMergeChain(10, false) 239 n, ethservice := startEthService(t, genesis, preMergeBlocks) 240 defer n.Close() 241 var ( 242 api = NewConsensusAPI(ethservice) 243 parent = ethservice.BlockChain().CurrentBlock() 244 ) 245 tests := []struct { 246 time uint64 247 shouldErr bool 248 }{ 249 {0, true}, 250 {parent.Time, true}, 251 {parent.Time - 1, true}, 252 {parent.Time + 1, false}, 253 {uint64(time.Now().Unix()) + uint64(time.Minute), false}, 254 } 255 256 for i, test := range tests { 257 t.Run(fmt.Sprintf("Timestamp test: %v", i), func(t *testing.T) { 258 params := engine.PayloadAttributes{ 259 Timestamp: test.time, 260 Random: crypto.Keccak256Hash([]byte{byte(123)}), 261 SuggestedFeeRecipient: parent.Coinbase, 262 } 263 fcState := engine.ForkchoiceStateV1{ 264 HeadBlockHash: parent.Hash(), 265 SafeBlockHash: common.Hash{}, 266 FinalizedBlockHash: common.Hash{}, 267 } 268 _, err := api.ForkchoiceUpdatedV1(fcState, ¶ms) 269 if test.shouldErr && err == nil { 270 t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err) 271 } else if !test.shouldErr && err != nil { 272 t.Fatalf("error preparing payload with valid timestamp, err=%v", err) 273 } 274 }) 275 } 276 } 277 278 func TestEth2NewBlock(t *testing.T) { 279 genesis, preMergeBlocks := generateMergeChain(10, false) 280 n, ethservice := startEthService(t, genesis, preMergeBlocks) 281 defer n.Close() 282 283 var ( 284 api = NewConsensusAPI(ethservice) 285 parent = preMergeBlocks[len(preMergeBlocks)-1] 286 287 // This EVM code generates a log when the contract is created. 288 logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") 289 ) 290 // The event channels. 291 newLogCh := make(chan []*types.Log, 10) 292 rmLogsCh := make(chan core.RemovedLogsEvent, 10) 293 ethservice.BlockChain().SubscribeLogsEvent(newLogCh) 294 ethservice.BlockChain().SubscribeRemovedLogsEvent(rmLogsCh) 295 296 for i := 0; i < 10; i++ { 297 statedb, _ := ethservice.BlockChain().StateAt(parent.Root()) 298 nonce := statedb.GetNonce(testAddr) 299 tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey) 300 ethservice.TxPool().Add([]*types.Transaction{tx}, true) 301 302 execData, err := assembleWithTransactions(api, parent.Hash(), &engine.PayloadAttributes{ 303 Timestamp: parent.Time() + 5, 304 }, 1) 305 if err != nil { 306 t.Fatalf("Failed to create the executable data, block %d: %v", i, err) 307 } 308 block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil) 309 if err != nil { 310 t.Fatalf("Failed to convert executable data to block %v", err) 311 } 312 newResp, err := api.NewPayloadV1(*execData) 313 switch { 314 case err != nil: 315 t.Fatalf("Failed to insert block: %v", err) 316 case newResp.Status != "VALID": 317 t.Fatalf("Failed to insert block: %v", newResp.Status) 318 case ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64()-1: 319 t.Fatalf("Chain head shouldn't be updated") 320 } 321 checkLogEvents(t, newLogCh, rmLogsCh, 0, 0) 322 fcState := engine.ForkchoiceStateV1{ 323 HeadBlockHash: block.Hash(), 324 SafeBlockHash: block.Hash(), 325 FinalizedBlockHash: block.Hash(), 326 } 327 if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { 328 t.Fatalf("Failed to insert block: %v", err) 329 } 330 if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want { 331 t.Fatalf("Chain head should be updated, have %d want %d", have, want) 332 } 333 checkLogEvents(t, newLogCh, rmLogsCh, 1, 0) 334 335 parent = block 336 } 337 338 // Introduce fork chain 339 var ( 340 head = ethservice.BlockChain().CurrentBlock().Number.Uint64() 341 ) 342 parent = preMergeBlocks[len(preMergeBlocks)-1] 343 for i := 0; i < 10; i++ { 344 execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{ 345 Timestamp: parent.Time() + 6, 346 }) 347 if err != nil { 348 t.Fatalf("Failed to create the executable data %v", err) 349 } 350 block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil) 351 if err != nil { 352 t.Fatalf("Failed to convert executable data to block %v", err) 353 } 354 newResp, err := api.NewPayloadV1(*execData) 355 if err != nil || newResp.Status != "VALID" { 356 t.Fatalf("Failed to insert block: %v", err) 357 } 358 if ethservice.BlockChain().CurrentBlock().Number.Uint64() != head { 359 t.Fatalf("Chain head shouldn't be updated") 360 } 361 362 fcState := engine.ForkchoiceStateV1{ 363 HeadBlockHash: block.Hash(), 364 SafeBlockHash: block.Hash(), 365 FinalizedBlockHash: block.Hash(), 366 } 367 if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { 368 t.Fatalf("Failed to insert block: %v", err) 369 } 370 if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64() { 371 t.Fatalf("Chain head should be updated") 372 } 373 parent, head = block, block.NumberU64() 374 } 375 } 376 377 func TestEth2DeepReorg(t *testing.T) { 378 // TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg 379 // before the totalTerminalDifficulty threshold 380 /* 381 genesis, preMergeBlocks := generateMergeChain(core.TriesInMemory * 2, false) 382 n, ethservice := startEthService(t, genesis, preMergeBlocks) 383 defer n.Close() 384 385 var ( 386 api = NewConsensusAPI(ethservice, nil) 387 parent = preMergeBlocks[len(preMergeBlocks)-core.TriesInMemory-1] 388 head = ethservice.BlockChain().CurrentBlock().Number.Uint64()() 389 ) 390 if ethservice.BlockChain().HasBlockAndState(parent.Hash(), parent.NumberU64()) { 391 t.Errorf("Block %d not pruned", parent.NumberU64()) 392 } 393 for i := 0; i < 10; i++ { 394 execData, err := api.assembleBlock(AssembleBlockParams{ 395 ParentHash: parent.Hash(), 396 Timestamp: parent.Time() + 5, 397 }) 398 if err != nil { 399 t.Fatalf("Failed to create the executable data %v", err) 400 } 401 block, err := ExecutableDataToBlock(ethservice.BlockChain().Config(), parent.Header(), *execData) 402 if err != nil { 403 t.Fatalf("Failed to convert executable data to block %v", err) 404 } 405 newResp, err := api.ExecutePayload(*execData) 406 if err != nil || newResp.Status != "VALID" { 407 t.Fatalf("Failed to insert block: %v", err) 408 } 409 if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != head { 410 t.Fatalf("Chain head shouldn't be updated") 411 } 412 if err := api.setHead(block.Hash()); err != nil { 413 t.Fatalf("Failed to set head: %v", err) 414 } 415 if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != block.NumberU64() { 416 t.Fatalf("Chain head should be updated") 417 } 418 parent, head = block, block.NumberU64() 419 } 420 */ 421 } 422 423 // startEthService creates a full node instance for testing. 424 func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) { 425 t.Helper() 426 427 n, err := node.New(&node.Config{ 428 P2P: p2p.Config{ 429 ListenAddr: "0.0.0.0:0", 430 NoDiscovery: true, 431 MaxPeers: 25, 432 }}) 433 if err != nil { 434 t.Fatal("can't create node:", err) 435 } 436 437 mcfg := miner.DefaultConfig 438 ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: ethconfig.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256, Miner: mcfg} 439 ethservice, err := eth.New(n, ethcfg) 440 if err != nil { 441 t.Fatal("can't create eth service:", err) 442 } 443 if err := n.Start(); err != nil { 444 t.Fatal("can't start node:", err) 445 } 446 if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil { 447 n.Close() 448 t.Fatal("can't import test blocks:", err) 449 } 450 if err := ethservice.TxPool().Sync(); err != nil { 451 t.Fatal("failed to sync txpool after initial blockchain import:", err) 452 } 453 454 ethservice.SetSynced() 455 return n, ethservice 456 } 457 458 func TestFullAPI(t *testing.T) { 459 genesis, preMergeBlocks := generateMergeChain(10, false) 460 n, ethservice := startEthService(t, genesis, preMergeBlocks) 461 defer n.Close() 462 var ( 463 parent = ethservice.BlockChain().CurrentBlock() 464 // This EVM code generates a log when the contract is created. 465 logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") 466 ) 467 468 callback := func(parent *types.Header) { 469 statedb, _ := ethservice.BlockChain().StateAt(parent.Root) 470 nonce := statedb.GetNonce(testAddr) 471 tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey) 472 ethservice.TxPool().Add([]*types.Transaction{tx}, false) 473 } 474 475 setupBlocks(t, ethservice, 10, parent, callback, nil, nil) 476 } 477 478 func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal, beaconRoots []common.Hash) []*types.Header { 479 api := NewConsensusAPI(ethservice) 480 var blocks []*types.Header 481 for i := 0; i < n; i++ { 482 callback(parent) 483 var w []*types.Withdrawal 484 if withdrawals != nil { 485 w = withdrawals[i] 486 } 487 var h *common.Hash 488 if beaconRoots != nil { 489 h = &beaconRoots[i] 490 } 491 492 envelope := getNewEnvelope(t, api, parent, w, h) 493 execResp, err := api.newPayload(*envelope.ExecutionPayload, []common.Hash{}, h, envelope.Requests, false) 494 if err != nil { 495 t.Fatalf("can't execute payload: %v", err) 496 } 497 if execResp.Status != engine.VALID { 498 t.Fatalf("invalid status: %v %s", execResp.Status, *execResp.ValidationError) 499 } 500 payload := envelope.ExecutionPayload 501 fcState := engine.ForkchoiceStateV1{ 502 HeadBlockHash: payload.BlockHash, 503 SafeBlockHash: payload.ParentHash, 504 FinalizedBlockHash: payload.ParentHash, 505 } 506 if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { 507 t.Fatalf("Failed to insert block: %v", err) 508 } 509 if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number { 510 t.Fatal("Chain head should be updated") 511 } 512 if ethservice.BlockChain().CurrentFinalBlock().Number.Uint64() != payload.Number-1 { 513 t.Fatal("Finalized block should be updated") 514 } 515 parent = ethservice.BlockChain().CurrentBlock() 516 blocks = append(blocks, parent) 517 } 518 return blocks 519 } 520 521 func TestExchangeTransitionConfig(t *testing.T) { 522 genesis, preMergeBlocks := generateMergeChain(10, false) 523 n, ethservice := startEthService(t, genesis, preMergeBlocks) 524 defer n.Close() 525 526 // invalid ttd 527 api := NewConsensusAPI(ethservice) 528 config := engine.TransitionConfigurationV1{ 529 TerminalTotalDifficulty: (*hexutil.Big)(big.NewInt(0)), 530 TerminalBlockHash: common.Hash{}, 531 TerminalBlockNumber: 0, 532 } 533 if _, err := api.ExchangeTransitionConfigurationV1(config); err == nil { 534 t.Fatal("expected error on invalid config, invalid ttd") 535 } 536 // invalid terminal block hash 537 config = engine.TransitionConfigurationV1{ 538 TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty), 539 TerminalBlockHash: common.Hash{1}, 540 TerminalBlockNumber: 0, 541 } 542 if _, err := api.ExchangeTransitionConfigurationV1(config); err == nil { 543 t.Fatal("expected error on invalid config, invalid hash") 544 } 545 // valid config 546 config = engine.TransitionConfigurationV1{ 547 TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty), 548 TerminalBlockHash: common.Hash{}, 549 TerminalBlockNumber: 0, 550 } 551 if _, err := api.ExchangeTransitionConfigurationV1(config); err != nil { 552 t.Fatalf("expected no error on valid config, got %v", err) 553 } 554 // valid config 555 config = engine.TransitionConfigurationV1{ 556 TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty), 557 TerminalBlockHash: preMergeBlocks[5].Hash(), 558 TerminalBlockNumber: 6, 559 } 560 if _, err := api.ExchangeTransitionConfigurationV1(config); err != nil { 561 t.Fatalf("expected no error on valid config, got %v", err) 562 } 563 } 564 565 /* 566 TestNewPayloadOnInvalidChain sets up a valid chain and tries to feed blocks 567 from an invalid chain to test if latestValidHash (LVH) works correctly. 568 569 We set up the following chain where P1 ... Pn and P1” are valid while 570 P1' is invalid. 571 We expect 572 (1) The LVH to point to the current inserted payload if it was valid. 573 (2) The LVH to point to the valid parent on an invalid payload (if the parent is available). 574 (3) If the parent is unavailable, the LVH should not be set. 575 576 CommonAncestor◄─▲── P1 ◄── P2 ◄─ P3 ◄─ ... ◄─ Pn 577 │ 578 └── P1' ◄─ P2' ◄─ P3' ◄─ ... ◄─ Pn' 579 │ 580 └── P1'' 581 */ 582 func TestNewPayloadOnInvalidChain(t *testing.T) { 583 genesis, preMergeBlocks := generateMergeChain(10, false) 584 n, ethservice := startEthService(t, genesis, preMergeBlocks) 585 defer n.Close() 586 587 var ( 588 api = NewConsensusAPI(ethservice) 589 parent = ethservice.BlockChain().CurrentBlock() 590 signer = types.LatestSigner(ethservice.BlockChain().Config()) 591 // This EVM code generates a log when the contract is created. 592 logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") 593 ) 594 for i := 0; i < 10; i++ { 595 statedb, _ := ethservice.BlockChain().StateAt(parent.Root) 596 tx := types.MustSignNewTx(testKey, signer, &types.LegacyTx{ 597 Nonce: statedb.GetNonce(testAddr), 598 Value: new(big.Int), 599 Gas: 1000000, 600 GasPrice: big.NewInt(2 * params.InitialBaseFee), 601 Data: logCode, 602 }) 603 ethservice.TxPool().Add([]*types.Transaction{tx}, true) 604 var ( 605 params = engine.PayloadAttributes{ 606 Timestamp: parent.Time + 1, 607 Random: crypto.Keccak256Hash([]byte{byte(i)}), 608 SuggestedFeeRecipient: parent.Coinbase, 609 } 610 fcState = engine.ForkchoiceStateV1{ 611 HeadBlockHash: parent.Hash(), 612 SafeBlockHash: common.Hash{}, 613 FinalizedBlockHash: common.Hash{}, 614 } 615 payload *engine.ExecutionPayloadEnvelope 616 resp engine.ForkChoiceResponse 617 err error 618 ) 619 for i := 0; ; i++ { 620 if resp, err = api.ForkchoiceUpdatedV1(fcState, ¶ms); err != nil { 621 t.Fatalf("error preparing payload, err=%v", err) 622 } 623 if resp.PayloadStatus.Status != engine.VALID { 624 t.Fatalf("error preparing payload, invalid status: %v", resp.PayloadStatus.Status) 625 } 626 // give the payload some time to be built 627 if payload, err = api.getPayload(*resp.PayloadID, true); err != nil { 628 t.Fatalf("can't get payload: %v", err) 629 } 630 if len(payload.ExecutionPayload.Transactions) > 0 { 631 break 632 } 633 // No luck this time we need to update the params and try again. 634 params.Timestamp = params.Timestamp + 1 635 if i > 10 { 636 t.Fatalf("payload should not be empty") 637 } 638 } 639 execResp, err := api.NewPayloadV1(*payload.ExecutionPayload) 640 if err != nil { 641 t.Fatalf("can't execute payload: %v", err) 642 } 643 if execResp.Status != engine.VALID { 644 t.Fatalf("invalid status: %v", execResp.Status) 645 } 646 fcState = engine.ForkchoiceStateV1{ 647 HeadBlockHash: payload.ExecutionPayload.BlockHash, 648 SafeBlockHash: payload.ExecutionPayload.ParentHash, 649 FinalizedBlockHash: payload.ExecutionPayload.ParentHash, 650 } 651 if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { 652 t.Fatalf("Failed to insert block: %v", err) 653 } 654 if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.ExecutionPayload.Number { 655 t.Fatalf("Chain head should be updated") 656 } 657 parent = ethservice.BlockChain().CurrentBlock() 658 } 659 } 660 661 func assembleEnvelope(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes) (*engine.ExecutionPayloadEnvelope, error) { 662 args := &miner.BuildPayloadArgs{ 663 Parent: parentHash, 664 Timestamp: params.Timestamp, 665 FeeRecipient: params.SuggestedFeeRecipient, 666 Random: params.Random, 667 Withdrawals: params.Withdrawals, 668 BeaconRoot: params.BeaconRoot, 669 } 670 payload, err := api.eth.Miner().BuildPayload(args, false) 671 if err != nil { 672 return nil, err 673 } 674 return payload.ResolveFull(), nil 675 } 676 677 func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes) (*engine.ExecutableData, error) { 678 envelope, err := assembleEnvelope(api, parentHash, params) 679 if err != nil { 680 return nil, err 681 } 682 return envelope.ExecutionPayload, nil 683 } 684 685 func TestEmptyBlocks(t *testing.T) { 686 genesis, preMergeBlocks := generateMergeChain(10, false) 687 n, ethservice := startEthService(t, genesis, preMergeBlocks) 688 defer n.Close() 689 690 commonAncestor := ethservice.BlockChain().CurrentBlock() 691 api := NewConsensusAPI(ethservice) 692 693 // Setup 10 blocks on the canonical chain 694 setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil) 695 696 // (1) check LatestValidHash by sending a normal payload (P1'') 697 payload := getNewPayload(t, api, commonAncestor, nil, nil) 698 699 status, err := api.NewPayloadV1(*payload) 700 if err != nil { 701 t.Fatal(err) 702 } 703 if status.Status != engine.VALID { 704 t.Errorf("invalid status: expected VALID got: %v", status.Status) 705 } 706 if !bytes.Equal(status.LatestValidHash[:], payload.BlockHash[:]) { 707 t.Fatalf("invalid LVH: got %v want %v", status.LatestValidHash, payload.BlockHash) 708 } 709 710 // (2) Now send P1' which is invalid 711 payload = getNewPayload(t, api, commonAncestor, nil, nil) 712 payload.GasUsed += 1 713 payload = setBlockhash(payload) 714 // Now latestValidHash should be the common ancestor 715 status, err = api.NewPayloadV1(*payload) 716 if err != nil { 717 t.Fatal(err) 718 } 719 if status.Status != engine.INVALID { 720 t.Errorf("invalid status: expected INVALID got: %v", status.Status) 721 } 722 // Expect 0x0 on INVALID block on top of PoW block 723 expected := common.Hash{} 724 if !bytes.Equal(status.LatestValidHash[:], expected[:]) { 725 t.Fatalf("invalid LVH: got %v want %v", status.LatestValidHash, expected) 726 } 727 728 // (3) Now send a payload with unknown parent 729 payload = getNewPayload(t, api, commonAncestor, nil, nil) 730 payload.ParentHash = common.Hash{1} 731 payload = setBlockhash(payload) 732 // Now latestValidHash should be the common ancestor 733 status, err = api.NewPayloadV1(*payload) 734 if err != nil { 735 t.Fatal(err) 736 } 737 if status.Status != engine.SYNCING { 738 t.Errorf("invalid status: expected SYNCING got: %v", status.Status) 739 } 740 if status.LatestValidHash != nil { 741 t.Fatalf("invalid LVH: got %v wanted nil", status.LatestValidHash) 742 } 743 } 744 745 func getNewEnvelope(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash) *engine.ExecutionPayloadEnvelope { 746 params := engine.PayloadAttributes{ 747 Timestamp: parent.Time + 1, 748 Random: crypto.Keccak256Hash([]byte{byte(1)}), 749 SuggestedFeeRecipient: parent.Coinbase, 750 Withdrawals: withdrawals, 751 BeaconRoot: beaconRoot, 752 } 753 754 envelope, err := assembleEnvelope(api, parent.Hash(), ¶ms) 755 if err != nil { 756 t.Fatal(err) 757 } 758 return envelope 759 } 760 761 func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash) *engine.ExecutableData { 762 return getNewEnvelope(t, api, parent, withdrawals, beaconRoot).ExecutionPayload 763 } 764 765 // setBlockhash sets the blockhash of a modified ExecutableData. 766 // Can be used to make modified payloads look valid. 767 func setBlockhash(data *engine.ExecutableData) *engine.ExecutableData { 768 txs, _ := decodeTransactions(data.Transactions) 769 number := big.NewInt(0) 770 number.SetUint64(data.Number) 771 header := &types.Header{ 772 ParentHash: data.ParentHash, 773 UncleHash: types.EmptyUncleHash, 774 Coinbase: data.FeeRecipient, 775 Root: data.StateRoot, 776 TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), 777 ReceiptHash: data.ReceiptsRoot, 778 Bloom: types.BytesToBloom(data.LogsBloom), 779 Difficulty: common.Big0, 780 Number: number, 781 GasLimit: data.GasLimit, 782 GasUsed: data.GasUsed, 783 Time: data.Timestamp, 784 BaseFee: data.BaseFeePerGas, 785 Extra: data.ExtraData, 786 MixDigest: data.Random, 787 } 788 block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs}) 789 data.BlockHash = block.Hash() 790 return data 791 } 792 793 func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { 794 var txs = make([]*types.Transaction, len(enc)) 795 for i, encTx := range enc { 796 var tx types.Transaction 797 if err := tx.UnmarshalBinary(encTx); err != nil { 798 return nil, fmt.Errorf("invalid transaction %d: %v", i, err) 799 } 800 txs[i] = &tx 801 } 802 return txs, nil 803 } 804 805 func TestTrickRemoteBlockCache(t *testing.T) { 806 // Setup two nodes 807 genesis, preMergeBlocks := generateMergeChain(10, false) 808 nodeA, ethserviceA := startEthService(t, genesis, preMergeBlocks) 809 nodeB, ethserviceB := startEthService(t, genesis, preMergeBlocks) 810 defer nodeA.Close() 811 defer nodeB.Close() 812 for nodeB.Server().NodeInfo().Ports.Listener == 0 { 813 time.Sleep(250 * time.Millisecond) 814 } 815 nodeA.Server().AddPeer(nodeB.Server().Self()) 816 nodeB.Server().AddPeer(nodeA.Server().Self()) 817 apiA := NewConsensusAPI(ethserviceA) 818 apiB := NewConsensusAPI(ethserviceB) 819 820 commonAncestor := ethserviceA.BlockChain().CurrentBlock() 821 822 // Setup 10 blocks on the canonical chain 823 setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}, nil, nil) 824 commonAncestor = ethserviceA.BlockChain().CurrentBlock() 825 826 var invalidChain []*engine.ExecutableData 827 // create a valid payload (P1) 828 //payload1 := getNewPayload(t, apiA, commonAncestor) 829 //invalidChain = append(invalidChain, payload1) 830 831 // create an invalid payload2 (P2) 832 payload2 := getNewPayload(t, apiA, commonAncestor, nil, nil) 833 //payload2.ParentHash = payload1.BlockHash 834 payload2.GasUsed += 1 835 payload2 = setBlockhash(payload2) 836 invalidChain = append(invalidChain, payload2) 837 838 head := payload2 839 // create some valid payloads on top 840 for i := 0; i < 10; i++ { 841 payload := getNewPayload(t, apiA, commonAncestor, nil, nil) 842 payload.ParentHash = head.BlockHash 843 payload = setBlockhash(payload) 844 invalidChain = append(invalidChain, payload) 845 head = payload 846 } 847 848 // feed the payloads to node B 849 for _, payload := range invalidChain { 850 status, err := apiB.NewPayloadV1(*payload) 851 if err != nil { 852 panic(err) 853 } 854 if status.Status == engine.VALID { 855 t.Error("invalid status: VALID on an invalid chain") 856 } 857 // Now reorg to the head of the invalid chain 858 resp, err := apiB.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{HeadBlockHash: payload.BlockHash, SafeBlockHash: payload.BlockHash, FinalizedBlockHash: payload.ParentHash}, nil) 859 if err != nil { 860 t.Fatal(err) 861 } 862 if resp.PayloadStatus.Status == engine.VALID { 863 t.Error("invalid status: VALID on an invalid chain") 864 } 865 time.Sleep(100 * time.Millisecond) 866 } 867 } 868 869 func TestInvalidBloom(t *testing.T) { 870 genesis, preMergeBlocks := generateMergeChain(10, false) 871 n, ethservice := startEthService(t, genesis, preMergeBlocks) 872 defer n.Close() 873 874 commonAncestor := ethservice.BlockChain().CurrentBlock() 875 api := NewConsensusAPI(ethservice) 876 877 // Setup 10 blocks on the canonical chain 878 setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil) 879 880 // (1) check LatestValidHash by sending a normal payload (P1'') 881 payload := getNewPayload(t, api, commonAncestor, nil, nil) 882 payload.LogsBloom = append(payload.LogsBloom, byte(1)) 883 status, err := api.NewPayloadV1(*payload) 884 if err != nil { 885 t.Fatal(err) 886 } 887 if status.Status != engine.INVALID { 888 t.Errorf("invalid status: expected INVALID got: %v", status.Status) 889 } 890 } 891 892 // TestSimultaneousNewBlock does several parallel inserts, both as 893 // newPayLoad and forkchoiceUpdate. This is to test that the api behaves 894 // well even of the caller is not being 'serial'. 895 func TestSimultaneousNewBlock(t *testing.T) { 896 genesis, preMergeBlocks := generateMergeChain(10, false) 897 n, ethservice := startEthService(t, genesis, preMergeBlocks) 898 defer n.Close() 899 900 var ( 901 api = NewConsensusAPI(ethservice) 902 parent = preMergeBlocks[len(preMergeBlocks)-1] 903 ) 904 for i := 0; i < 10; i++ { 905 execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{ 906 Timestamp: parent.Time() + 5, 907 }) 908 if err != nil { 909 t.Fatalf("Failed to create the executable data %v", err) 910 } 911 // Insert it 10 times in parallel. Should be ignored. 912 { 913 var ( 914 wg sync.WaitGroup 915 testErr error 916 errMu sync.Mutex 917 ) 918 wg.Add(10) 919 for ii := 0; ii < 10; ii++ { 920 go func() { 921 defer wg.Done() 922 if newResp, err := api.NewPayloadV1(*execData); err != nil { 923 errMu.Lock() 924 testErr = fmt.Errorf("failed to insert block: %w", err) 925 errMu.Unlock() 926 } else if newResp.Status != "VALID" { 927 errMu.Lock() 928 testErr = fmt.Errorf("failed to insert block: %v", newResp.Status) 929 errMu.Unlock() 930 } 931 }() 932 } 933 wg.Wait() 934 if testErr != nil { 935 t.Fatal(testErr) 936 } 937 } 938 block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil) 939 if err != nil { 940 t.Fatalf("Failed to convert executable data to block %v", err) 941 } 942 if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64()-1 { 943 t.Fatalf("Chain head shouldn't be updated") 944 } 945 fcState := engine.ForkchoiceStateV1{ 946 HeadBlockHash: block.Hash(), 947 SafeBlockHash: block.Hash(), 948 FinalizedBlockHash: block.Hash(), 949 } 950 { 951 var ( 952 wg sync.WaitGroup 953 testErr error 954 errMu sync.Mutex 955 ) 956 wg.Add(10) 957 // Do each FCU 10 times 958 for ii := 0; ii < 10; ii++ { 959 go func() { 960 defer wg.Done() 961 if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { 962 errMu.Lock() 963 testErr = fmt.Errorf("failed to insert block: %w", err) 964 errMu.Unlock() 965 } 966 }() 967 } 968 wg.Wait() 969 if testErr != nil { 970 t.Fatal(testErr) 971 } 972 } 973 if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want { 974 t.Fatalf("Chain head should be updated, have %d want %d", have, want) 975 } 976 parent = block 977 } 978 } 979 980 // TestWithdrawals creates and verifies two post-Shanghai blocks. The first 981 // includes zero withdrawals and the second includes two. 982 func TestWithdrawals(t *testing.T) { 983 genesis, blocks := generateMergeChain(10, true) 984 // Set shanghai time to last block + 5 seconds (first post-merge block) 985 time := blocks[len(blocks)-1].Time() + 5 986 genesis.Config.ShanghaiTime = &time 987 988 n, ethservice := startEthService(t, genesis, blocks) 989 defer n.Close() 990 991 api := NewConsensusAPI(ethservice) 992 993 // 10: Build Shanghai block with no withdrawals. 994 parent := ethservice.BlockChain().CurrentHeader() 995 blockParams := engine.PayloadAttributes{ 996 Timestamp: parent.Time + 5, 997 Withdrawals: make([]*types.Withdrawal, 0), 998 } 999 fcState := engine.ForkchoiceStateV1{ 1000 HeadBlockHash: parent.Hash(), 1001 } 1002 resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams) 1003 if err != nil { 1004 t.Fatalf("error preparing payload, err=%v", err) 1005 } 1006 if resp.PayloadStatus.Status != engine.VALID { 1007 t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID) 1008 } 1009 1010 // 10: verify state root is the same as parent 1011 payloadID := (&miner.BuildPayloadArgs{ 1012 Parent: fcState.HeadBlockHash, 1013 Timestamp: blockParams.Timestamp, 1014 FeeRecipient: blockParams.SuggestedFeeRecipient, 1015 Random: blockParams.Random, 1016 Withdrawals: blockParams.Withdrawals, 1017 BeaconRoot: blockParams.BeaconRoot, 1018 Version: engine.PayloadV2, 1019 }).Id() 1020 execData, err := api.GetPayloadV2(payloadID) 1021 if err != nil { 1022 t.Fatalf("error getting payload, err=%v", err) 1023 } 1024 if execData.ExecutionPayload.StateRoot != parent.Root { 1025 t.Fatalf("mismatch state roots (got: %s, want: %s)", execData.ExecutionPayload.StateRoot, blocks[8].Root()) 1026 } 1027 1028 // 10: verify locally built block 1029 if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil { 1030 t.Fatalf("error validating payload: %v", err) 1031 } else if status.Status != engine.VALID { 1032 t.Fatalf("invalid payload") 1033 } 1034 1035 // 11: build shanghai block with withdrawal 1036 aa := common.Address{0xaa} 1037 bb := common.Address{0xbb} 1038 blockParams = engine.PayloadAttributes{ 1039 Timestamp: execData.ExecutionPayload.Timestamp + 5, 1040 Withdrawals: []*types.Withdrawal{ 1041 { 1042 Index: 0, 1043 Address: aa, 1044 Amount: 32, 1045 }, 1046 { 1047 Index: 1, 1048 Address: bb, 1049 Amount: 33, 1050 }, 1051 }, 1052 } 1053 fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash 1054 _, err = api.ForkchoiceUpdatedV2(fcState, &blockParams) 1055 if err != nil { 1056 t.Fatalf("error preparing payload, err=%v", err) 1057 } 1058 1059 // 11: verify locally build block. 1060 payloadID = (&miner.BuildPayloadArgs{ 1061 Parent: fcState.HeadBlockHash, 1062 Timestamp: blockParams.Timestamp, 1063 FeeRecipient: blockParams.SuggestedFeeRecipient, 1064 Random: blockParams.Random, 1065 Withdrawals: blockParams.Withdrawals, 1066 BeaconRoot: blockParams.BeaconRoot, 1067 Version: engine.PayloadV2, 1068 }).Id() 1069 execData, err = api.GetPayloadV2(payloadID) 1070 if err != nil { 1071 t.Fatalf("error getting payload, err=%v", err) 1072 } 1073 if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil { 1074 t.Fatalf("error validating payload: %v", err) 1075 } else if status.Status != engine.VALID { 1076 t.Fatalf("invalid payload") 1077 } 1078 1079 // 11: set block as head. 1080 fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash 1081 _, err = api.ForkchoiceUpdatedV2(fcState, nil) 1082 if err != nil { 1083 t.Fatalf("error preparing payload, err=%v", err) 1084 } 1085 1086 // 11: verify withdrawals were processed. 1087 db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number)) 1088 if err != nil { 1089 t.Fatalf("unable to load db: %v", err) 1090 } 1091 for i, w := range blockParams.Withdrawals { 1092 // w.Amount is in gwei, balance in wei 1093 if db.GetBalance(w.Address).Uint64() != w.Amount*params.GWei { 1094 t.Fatalf("failed to process withdrawal %d", i) 1095 } 1096 } 1097 } 1098 1099 func TestNilWithdrawals(t *testing.T) { 1100 genesis, blocks := generateMergeChain(10, true) 1101 // Set shanghai time to last block + 4 seconds (first post-merge block) 1102 time := blocks[len(blocks)-1].Time() + 4 1103 genesis.Config.ShanghaiTime = &time 1104 1105 n, ethservice := startEthService(t, genesis, blocks) 1106 defer n.Close() 1107 1108 api := NewConsensusAPI(ethservice) 1109 parent := ethservice.BlockChain().CurrentHeader() 1110 aa := common.Address{0xaa} 1111 1112 type test struct { 1113 blockParams engine.PayloadAttributes 1114 wantErr bool 1115 } 1116 tests := []test{ 1117 // Before Shanghai 1118 { 1119 blockParams: engine.PayloadAttributes{ 1120 Timestamp: parent.Time + 2, 1121 Withdrawals: nil, 1122 }, 1123 wantErr: false, 1124 }, 1125 { 1126 blockParams: engine.PayloadAttributes{ 1127 Timestamp: parent.Time + 2, 1128 Withdrawals: make([]*types.Withdrawal, 0), 1129 }, 1130 wantErr: true, 1131 }, 1132 { 1133 blockParams: engine.PayloadAttributes{ 1134 Timestamp: parent.Time + 2, 1135 Withdrawals: []*types.Withdrawal{ 1136 { 1137 Index: 0, 1138 Address: aa, 1139 Amount: 32, 1140 }, 1141 }, 1142 }, 1143 wantErr: true, 1144 }, 1145 // After Shanghai 1146 { 1147 blockParams: engine.PayloadAttributes{ 1148 Timestamp: parent.Time + 5, 1149 Withdrawals: nil, 1150 }, 1151 wantErr: true, 1152 }, 1153 { 1154 blockParams: engine.PayloadAttributes{ 1155 Timestamp: parent.Time + 5, 1156 Withdrawals: make([]*types.Withdrawal, 0), 1157 }, 1158 wantErr: false, 1159 }, 1160 { 1161 blockParams: engine.PayloadAttributes{ 1162 Timestamp: parent.Time + 5, 1163 Withdrawals: []*types.Withdrawal{ 1164 { 1165 Index: 0, 1166 Address: aa, 1167 Amount: 32, 1168 }, 1169 }, 1170 }, 1171 wantErr: false, 1172 }, 1173 } 1174 1175 fcState := engine.ForkchoiceStateV1{ 1176 HeadBlockHash: parent.Hash(), 1177 } 1178 1179 for _, test := range tests { 1180 var ( 1181 err error 1182 payloadVersion engine.PayloadVersion 1183 shanghai = genesis.Config.IsShanghai(genesis.Config.LondonBlock, test.blockParams.Timestamp) 1184 ) 1185 if !shanghai { 1186 payloadVersion = engine.PayloadV1 1187 _, err = api.ForkchoiceUpdatedV1(fcState, &test.blockParams) 1188 } else { 1189 payloadVersion = engine.PayloadV2 1190 _, err = api.ForkchoiceUpdatedV2(fcState, &test.blockParams) 1191 } 1192 if test.wantErr { 1193 if err == nil { 1194 t.Fatal("wanted error on fcuv2 with invalid withdrawals") 1195 } 1196 continue 1197 } 1198 if err != nil { 1199 t.Fatalf("error preparing payload, err=%v", err) 1200 } 1201 1202 // 11: verify locally build block. 1203 payloadID := (&miner.BuildPayloadArgs{ 1204 Parent: fcState.HeadBlockHash, 1205 Timestamp: test.blockParams.Timestamp, 1206 FeeRecipient: test.blockParams.SuggestedFeeRecipient, 1207 Random: test.blockParams.Random, 1208 Version: payloadVersion, 1209 }).Id() 1210 execData, err := api.GetPayloadV2(payloadID) 1211 if err != nil { 1212 t.Fatalf("error getting payload, err=%v", err) 1213 } 1214 var status engine.PayloadStatusV1 1215 if !shanghai { 1216 status, err = api.NewPayloadV1(*execData.ExecutionPayload) 1217 } else { 1218 status, err = api.NewPayloadV2(*execData.ExecutionPayload) 1219 } 1220 if err != nil { 1221 t.Fatalf("error validating payload: %v", err.(*engine.EngineAPIError).ErrorData()) 1222 } else if status.Status != engine.VALID { 1223 t.Fatalf("invalid payload") 1224 } 1225 } 1226 } 1227 1228 func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) { 1229 genesis, blocks := generateMergeChain(10, true) 1230 1231 // Enable next forks on the last block. 1232 time := blocks[len(blocks)-1].Header().Time + 1 1233 genesis.Config.ShanghaiTime = &time 1234 genesis.Config.CancunTime = &time 1235 genesis.Config.PragueTime = &time 1236 genesis.Config.BlobScheduleConfig = params.DefaultBlobSchedule 1237 1238 n, ethservice := startEthService(t, genesis, blocks) 1239 1240 var ( 1241 // This EVM code generates a log when the contract is created. 1242 logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") 1243 parent = ethservice.BlockChain().CurrentBlock() 1244 ) 1245 1246 // Each block, this callback will include two txs that generate body values like logs and requests. 1247 callback := func(parent *types.Header) { 1248 var ( 1249 statedb, _ = ethservice.BlockChain().StateAt(parent.Root) 1250 // Create tx to trigger log generator. 1251 tx1, _ = types.SignTx(types.NewContractCreation(statedb.GetNonce(testAddr), new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey) 1252 // Create tx to trigger deposit generator. 1253 tx2, _ = types.SignTx(types.NewTransaction(statedb.GetNonce(testAddr)+1, ethservice.APIBackend.ChainConfig().DepositContractAddress, new(big.Int), 500000, big.NewInt(2*params.InitialBaseFee), nil), types.LatestSigner(ethservice.BlockChain().Config()), testKey) 1254 ) 1255 ethservice.TxPool().Add([]*types.Transaction{tx1}, false) 1256 ethservice.TxPool().Add([]*types.Transaction{tx2}, false) 1257 } 1258 1259 // Make some withdrawals to include. 1260 withdrawals := make([][]*types.Withdrawal, 10) 1261 withdrawals[0] = nil // should be filtered out by miner 1262 withdrawals[1] = make([]*types.Withdrawal, 0) 1263 for i := 2; i < len(withdrawals); i++ { 1264 addr := make([]byte, 20) 1265 crand.Read(addr) 1266 withdrawals[i] = []*types.Withdrawal{ 1267 {Index: rand.Uint64(), Validator: rand.Uint64(), Amount: rand.Uint64(), Address: common.BytesToAddress(addr)}, 1268 } 1269 } 1270 1271 // Make beacon root update for each block. 1272 beaconRoots := make([]common.Hash, 10) 1273 for i := 0; i < 10; i++ { 1274 beaconRoots[i] = common.Hash{byte(i)} 1275 } 1276 1277 // Create the blocks. 1278 newHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals, beaconRoots) 1279 newBlocks := make([]*types.Block, len(newHeaders)) 1280 for i, header := range newHeaders { 1281 newBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64()) 1282 } 1283 1284 return n, ethservice, append(blocks, newBlocks...) 1285 } 1286 1287 func allHashes(blocks []*types.Block) []common.Hash { 1288 var hashes []common.Hash 1289 for _, b := range blocks { 1290 hashes = append(hashes, b.Hash()) 1291 } 1292 return hashes 1293 } 1294 func allBodies(blocks []*types.Block) []*types.Body { 1295 var bodies []*types.Body 1296 for _, b := range blocks { 1297 bodies = append(bodies, b.Body()) 1298 } 1299 return bodies 1300 } 1301 1302 func TestGetBlockBodiesByHash(t *testing.T) { 1303 node, eth, blocks := setupBodies(t) 1304 api := NewConsensusAPI(eth) 1305 defer node.Close() 1306 1307 tests := []struct { 1308 results []*types.Body 1309 hashes []common.Hash 1310 }{ 1311 // First pow block 1312 { 1313 results: []*types.Body{eth.BlockChain().GetBlockByNumber(0).Body()}, 1314 hashes: []common.Hash{eth.BlockChain().GetBlockByNumber(0).Hash()}, 1315 }, 1316 // Last pow block 1317 { 1318 results: []*types.Body{blocks[9].Body()}, 1319 hashes: []common.Hash{blocks[9].Hash()}, 1320 }, 1321 // First post-merge block 1322 { 1323 results: []*types.Body{blocks[10].Body()}, 1324 hashes: []common.Hash{blocks[10].Hash()}, 1325 }, 1326 // Pre & post merge blocks 1327 { 1328 results: []*types.Body{blocks[0].Body(), blocks[9].Body(), blocks[14].Body()}, 1329 hashes: []common.Hash{blocks[0].Hash(), blocks[9].Hash(), blocks[14].Hash()}, 1330 }, 1331 // unavailable block 1332 { 1333 results: []*types.Body{blocks[0].Body(), nil, blocks[14].Body()}, 1334 hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[14].Hash()}, 1335 }, 1336 // same block multiple times 1337 { 1338 results: []*types.Body{blocks[0].Body(), nil, blocks[0].Body(), blocks[0].Body()}, 1339 hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[0].Hash(), blocks[0].Hash()}, 1340 }, 1341 // all blocks 1342 { 1343 results: allBodies(blocks), 1344 hashes: allHashes(blocks), 1345 }, 1346 } 1347 1348 for k, test := range tests { 1349 result := api.GetPayloadBodiesByHashV2(test.hashes) 1350 for i, r := range result { 1351 if err := checkEqualBody(test.results[i], r); err != nil { 1352 t.Fatalf("test %v: invalid response: %v\nexpected %+v\ngot %+v", k, err, test.results[i], r) 1353 } 1354 } 1355 } 1356 } 1357 1358 func TestGetBlockBodiesByRange(t *testing.T) { 1359 node, eth, blocks := setupBodies(t) 1360 api := NewConsensusAPI(eth) 1361 defer node.Close() 1362 1363 tests := []struct { 1364 results []*types.Body 1365 start hexutil.Uint64 1366 count hexutil.Uint64 1367 }{ 1368 { 1369 results: []*types.Body{blocks[9].Body()}, 1370 start: 10, 1371 count: 1, 1372 }, 1373 // Genesis 1374 { 1375 results: []*types.Body{blocks[0].Body()}, 1376 start: 1, 1377 count: 1, 1378 }, 1379 // First post-merge block 1380 { 1381 results: []*types.Body{blocks[9].Body()}, 1382 start: 10, 1383 count: 1, 1384 }, 1385 // Pre & post merge blocks 1386 { 1387 results: []*types.Body{blocks[7].Body(), blocks[8].Body(), blocks[9].Body(), blocks[10].Body()}, 1388 start: 8, 1389 count: 4, 1390 }, 1391 // unavailable block 1392 { 1393 results: []*types.Body{blocks[18].Body(), blocks[19].Body()}, 1394 start: 19, 1395 count: 3, 1396 }, 1397 // unavailable block 1398 { 1399 results: []*types.Body{blocks[19].Body()}, 1400 start: 20, 1401 count: 2, 1402 }, 1403 { 1404 results: []*types.Body{blocks[19].Body()}, 1405 start: 20, 1406 count: 1, 1407 }, 1408 // whole range unavailable 1409 { 1410 results: make([]*types.Body, 0), 1411 start: 22, 1412 count: 2, 1413 }, 1414 // allBlocks 1415 { 1416 results: allBodies(blocks), 1417 start: 1, 1418 count: hexutil.Uint64(len(blocks)), 1419 }, 1420 } 1421 1422 for k, test := range tests { 1423 result, err := api.GetPayloadBodiesByRangeV2(test.start, test.count) 1424 if err != nil { 1425 t.Fatal(err) 1426 } 1427 if len(result) == len(test.results) { 1428 for i, r := range result { 1429 if err := checkEqualBody(test.results[i], r); err != nil { 1430 t.Fatalf("test %d: invalid response: %v\nexpected %+v\ngot %+v", k, err, test.results[i], r) 1431 } 1432 } 1433 } else { 1434 t.Fatalf("test %d: invalid length want %v got %v", k, len(test.results), len(result)) 1435 } 1436 } 1437 } 1438 1439 func TestGetBlockBodiesByRangeInvalidParams(t *testing.T) { 1440 node, eth, _ := setupBodies(t) 1441 api := NewConsensusAPI(eth) 1442 defer node.Close() 1443 tests := []struct { 1444 start hexutil.Uint64 1445 count hexutil.Uint64 1446 want *engine.EngineAPIError 1447 }{ 1448 // Genesis 1449 { 1450 start: 0, 1451 count: 1, 1452 want: engine.InvalidParams, 1453 }, 1454 // No block requested 1455 { 1456 start: 1, 1457 count: 0, 1458 want: engine.InvalidParams, 1459 }, 1460 // Genesis & no block 1461 { 1462 start: 0, 1463 count: 0, 1464 want: engine.InvalidParams, 1465 }, 1466 // More than 1024 blocks 1467 { 1468 start: 1, 1469 count: 1025, 1470 want: engine.TooLargeRequest, 1471 }, 1472 } 1473 for i, tc := range tests { 1474 result, err := api.GetPayloadBodiesByRangeV2(tc.start, tc.count) 1475 if err == nil { 1476 t.Fatalf("test %d: expected error, got %v", i, result) 1477 } 1478 if have, want := err.Error(), tc.want.Error(); have != want { 1479 t.Fatalf("test %d: have %s, want %s", i, have, want) 1480 } 1481 } 1482 } 1483 1484 func checkEqualBody(a *types.Body, b *engine.ExecutionPayloadBody) error { 1485 if a == nil && b == nil { 1486 return nil 1487 } else if a == nil || b == nil { 1488 return errors.New("nil vs. non-nil") 1489 } 1490 if len(a.Transactions) != len(b.TransactionData) { 1491 return errors.New("transactions length mismatch") 1492 } 1493 for i, tx := range a.Transactions { 1494 data, _ := tx.MarshalBinary() 1495 if !bytes.Equal(data, b.TransactionData[i]) { 1496 return fmt.Errorf("transaction %d mismatch", i) 1497 } 1498 } 1499 if !reflect.DeepEqual(a.Withdrawals, b.Withdrawals) { 1500 return fmt.Errorf("withdrawals mismatch") 1501 } 1502 return nil 1503 } 1504 1505 func TestBlockToPayloadWithBlobs(t *testing.T) { 1506 header := types.Header{} 1507 var txs []*types.Transaction 1508 1509 inner := types.BlobTx{ 1510 BlobHashes: make([]common.Hash, 1), 1511 } 1512 1513 txs = append(txs, types.NewTx(&inner)) 1514 sidecars := []*types.BlobTxSidecar{ 1515 { 1516 Blobs: make([]kzg4844.Blob, 1), 1517 Commitments: make([]kzg4844.Commitment, 1), 1518 Proofs: make([]kzg4844.Proof, 1), 1519 }, 1520 } 1521 1522 block := types.NewBlock(&header, &types.Body{Transactions: txs}, nil, trie.NewStackTrie(nil)) 1523 envelope := engine.BlockToExecutableData(block, nil, sidecars, nil) 1524 var want int 1525 for _, tx := range txs { 1526 want += len(tx.BlobHashes()) 1527 } 1528 if got := len(envelope.BlobsBundle.Commitments); got != want { 1529 t.Fatalf("invalid number of commitments: got %v, want %v", got, want) 1530 } 1531 if got := len(envelope.BlobsBundle.Proofs); got != want { 1532 t.Fatalf("invalid number of proofs: got %v, want %v", got, want) 1533 } 1534 if got := len(envelope.BlobsBundle.Blobs); got != want { 1535 t.Fatalf("invalid number of blobs: got %v, want %v", got, want) 1536 } 1537 _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil, nil) 1538 if err != nil { 1539 t.Error(err) 1540 } 1541 } 1542 1543 // This checks that beaconRoot is applied to the state from the engine API. 1544 func TestParentBeaconBlockRoot(t *testing.T) { 1545 //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), log.LevelTrace, true))) 1546 1547 genesis, blocks := generateMergeChain(10, true) 1548 1549 // Set cancun time to last block + 5 seconds 1550 time := blocks[len(blocks)-1].Time() + 5 1551 genesis.Config.ShanghaiTime = &time 1552 genesis.Config.CancunTime = &time 1553 genesis.Config.BlobScheduleConfig = params.DefaultBlobSchedule 1554 1555 n, ethservice := startEthService(t, genesis, blocks) 1556 defer n.Close() 1557 1558 api := NewConsensusAPI(ethservice) 1559 1560 // 11: Build Shanghai block with no withdrawals. 1561 parent := ethservice.BlockChain().CurrentHeader() 1562 blockParams := engine.PayloadAttributes{ 1563 Timestamp: parent.Time + 5, 1564 Withdrawals: make([]*types.Withdrawal, 0), 1565 BeaconRoot: &common.Hash{42}, 1566 } 1567 fcState := engine.ForkchoiceStateV1{ 1568 HeadBlockHash: parent.Hash(), 1569 } 1570 resp, err := api.ForkchoiceUpdatedV3(fcState, &blockParams) 1571 if err != nil { 1572 t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData()) 1573 } 1574 if resp.PayloadStatus.Status != engine.VALID { 1575 t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID) 1576 } 1577 1578 // 11: verify state root is the same as parent 1579 payloadID := (&miner.BuildPayloadArgs{ 1580 Parent: fcState.HeadBlockHash, 1581 Timestamp: blockParams.Timestamp, 1582 FeeRecipient: blockParams.SuggestedFeeRecipient, 1583 Random: blockParams.Random, 1584 Withdrawals: blockParams.Withdrawals, 1585 BeaconRoot: blockParams.BeaconRoot, 1586 Version: engine.PayloadV3, 1587 }).Id() 1588 execData, err := api.GetPayloadV3(payloadID) 1589 if err != nil { 1590 t.Fatalf("error getting payload, err=%v", err) 1591 } 1592 1593 // 11: verify locally built block 1594 if status, err := api.NewPayloadV3(*execData.ExecutionPayload, []common.Hash{}, &common.Hash{42}); err != nil { 1595 t.Fatalf("error validating payload: %v", err) 1596 } else if status.Status != engine.VALID { 1597 t.Fatalf("invalid payload") 1598 } 1599 1600 fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash 1601 resp, err = api.ForkchoiceUpdatedV3(fcState, nil) 1602 if err != nil { 1603 t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData()) 1604 } 1605 if resp.PayloadStatus.Status != engine.VALID { 1606 t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID) 1607 } 1608 1609 // 11: verify beacon root was processed. 1610 db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number)) 1611 if err != nil { 1612 t.Fatalf("unable to load db: %v", err) 1613 } 1614 var ( 1615 timeIdx = common.BigToHash(big.NewInt(int64(execData.ExecutionPayload.Timestamp % 98304))) 1616 rootIdx = common.BigToHash(big.NewInt(int64((execData.ExecutionPayload.Timestamp % 98304) + 98304))) 1617 ) 1618 1619 if num := db.GetState(params.BeaconRootsAddress, timeIdx); num != timeIdx { 1620 t.Fatalf("incorrect number stored: want %s, got %s", timeIdx, num) 1621 } 1622 if root := db.GetState(params.BeaconRootsAddress, rootIdx); root != *blockParams.BeaconRoot { 1623 t.Fatalf("incorrect root stored: want %s, got %s", *blockParams.BeaconRoot, root) 1624 } 1625 } 1626 1627 func TestWitnessCreationAndConsumption(t *testing.T) { 1628 //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), log.LevelTrace, true))) 1629 1630 genesis, blocks := generateMergeChain(10, true) 1631 1632 // Set cancun time to semi-last block + 5 seconds 1633 timestamp := blocks[len(blocks)-2].Time() + 5 1634 genesis.Config.ShanghaiTime = ×tamp 1635 genesis.Config.CancunTime = ×tamp 1636 genesis.Config.BlobScheduleConfig = params.DefaultBlobSchedule 1637 1638 n, ethservice := startEthService(t, genesis, blocks[:9]) 1639 defer n.Close() 1640 1641 api := NewConsensusAPI(ethservice) 1642 1643 // Put the 10th block's tx in the pool and produce a new block 1644 txs := blocks[9].Transactions() 1645 1646 ethservice.TxPool().Add(txs, true) 1647 blockParams := engine.PayloadAttributes{ 1648 Timestamp: blocks[8].Time() + 5, 1649 Withdrawals: make([]*types.Withdrawal, 0), 1650 BeaconRoot: &common.Hash{42}, 1651 } 1652 fcState := engine.ForkchoiceStateV1{ 1653 HeadBlockHash: blocks[8].Hash(), 1654 SafeBlockHash: common.Hash{}, 1655 FinalizedBlockHash: common.Hash{}, 1656 } 1657 _, err := api.ForkchoiceUpdatedWithWitnessV3(fcState, &blockParams) 1658 if err != nil { 1659 t.Fatalf("error preparing payload, err=%v", err) 1660 } 1661 payloadID := (&miner.BuildPayloadArgs{ 1662 Parent: fcState.HeadBlockHash, 1663 Timestamp: blockParams.Timestamp, 1664 FeeRecipient: blockParams.SuggestedFeeRecipient, 1665 Random: blockParams.Random, 1666 Withdrawals: blockParams.Withdrawals, 1667 BeaconRoot: blockParams.BeaconRoot, 1668 Version: engine.PayloadV3, 1669 }).Id() 1670 envelope, err := api.getPayload(payloadID, true) 1671 if err != nil { 1672 t.Fatalf("error getting payload, err=%v", err) 1673 } 1674 if len(envelope.ExecutionPayload.Transactions) != blocks[9].Transactions().Len() { 1675 t.Fatalf("invalid number of transactions %d != %d", len(envelope.ExecutionPayload.Transactions), blocks[9].Transactions().Len()) 1676 } 1677 if envelope.Witness == nil { 1678 t.Fatalf("witness missing from payload") 1679 } 1680 // Test stateless execution of the created witness 1681 wantStateRoot := envelope.ExecutionPayload.StateRoot 1682 wantReceiptRoot := envelope.ExecutionPayload.ReceiptsRoot 1683 1684 envelope.ExecutionPayload.StateRoot = common.Hash{} 1685 envelope.ExecutionPayload.ReceiptsRoot = common.Hash{} 1686 1687 res, err := api.ExecuteStatelessPayloadV3(*envelope.ExecutionPayload, []common.Hash{}, &common.Hash{42}, *envelope.Witness) 1688 if err != nil { 1689 t.Fatalf("error executing stateless payload witness: %v", err) 1690 } 1691 if res.StateRoot != wantStateRoot { 1692 t.Fatalf("stateless state root mismatch: have %v, want %v", res.StateRoot, wantStateRoot) 1693 } 1694 if res.ReceiptsRoot != wantReceiptRoot { 1695 t.Fatalf("stateless receipt root mismatch: have %v, want %v", res.ReceiptsRoot, wantReceiptRoot) 1696 } 1697 // Test block insertion with witness creation 1698 envelope.ExecutionPayload.StateRoot = wantStateRoot 1699 envelope.ExecutionPayload.ReceiptsRoot = wantReceiptRoot 1700 1701 res2, err := api.NewPayloadWithWitnessV3(*envelope.ExecutionPayload, []common.Hash{}, &common.Hash{42}) 1702 if err != nil { 1703 t.Fatalf("error executing stateless payload witness: %v", err) 1704 } 1705 if res2.Witness == nil { 1706 t.Fatalf("witness missing from payload") 1707 } 1708 // Test stateless execution of the created witness 1709 wantStateRoot = envelope.ExecutionPayload.StateRoot 1710 wantReceiptRoot = envelope.ExecutionPayload.ReceiptsRoot 1711 1712 envelope.ExecutionPayload.StateRoot = common.Hash{} 1713 envelope.ExecutionPayload.ReceiptsRoot = common.Hash{} 1714 1715 res, err = api.ExecuteStatelessPayloadV3(*envelope.ExecutionPayload, []common.Hash{}, &common.Hash{42}, *res2.Witness) 1716 if err != nil { 1717 t.Fatalf("error executing stateless payload witness: %v", err) 1718 } 1719 if res.StateRoot != wantStateRoot { 1720 t.Fatalf("stateless state root mismatch: have %v, want %v", res.StateRoot, wantStateRoot) 1721 } 1722 if res.ReceiptsRoot != wantReceiptRoot { 1723 t.Fatalf("stateless receipt root mismatch: have %v, want %v", res.ReceiptsRoot, wantReceiptRoot) 1724 } 1725 } 1726 1727 // TestGetClientVersion verifies the expected version info is returned. 1728 func TestGetClientVersion(t *testing.T) { 1729 genesis, preMergeBlocks := generateMergeChain(10, false) 1730 n, ethservice := startEthService(t, genesis, preMergeBlocks) 1731 defer n.Close() 1732 1733 api := NewConsensusAPI(ethservice) 1734 info := engine.ClientVersionV1{ 1735 Code: "TT", 1736 Name: "test", 1737 Version: "1.1.1", 1738 Commit: "0x12345678", 1739 } 1740 infos := api.GetClientVersionV1(info) 1741 if len(infos) != 1 { 1742 t.Fatalf("expected only one returned client version, got %d", len(infos)) 1743 } 1744 info = infos[0] 1745 if info.Code != engine.ClientCode || info.Name != engine.ClientName || info.Version != version.WithMeta { 1746 t.Fatalf("client info does match expected, got %s", info.String()) 1747 } 1748 } 1749 1750 func TestValidateRequests(t *testing.T) { 1751 tests := []struct { 1752 name string 1753 requests [][]byte 1754 wantErr bool 1755 }{ 1756 { 1757 name: "valid ascending", 1758 requests: [][]byte{ 1759 {0x00, 0xAA, 0xBB}, // type 0x00 1760 {0x01, 0xCC}, // type 0x01 1761 {0x02, 0xDD}, // type 0x02 1762 }, 1763 wantErr: false, 1764 }, 1765 { 1766 name: "empty request (too short)", 1767 requests: [][]byte{ 1768 {0x00}, // only 1 byte: type with no data 1769 }, 1770 wantErr: true, 1771 }, 1772 { 1773 name: "duplicate type", 1774 requests: [][]byte{ 1775 {0x00, 0x11}, 1776 {0x01, 0x22}, 1777 {0x01, 0x33}, // duplicate type 0x01 1778 }, 1779 wantErr: true, 1780 }, 1781 { 1782 name: "out of order", 1783 requests: [][]byte{ 1784 {0x01, 0xAA}, // type 0x01 1785 {0x00, 0xBB}, // type 0x00 out of order (should be ascending) 1786 }, 1787 wantErr: true, 1788 }, 1789 { 1790 name: "single request valid", 1791 requests: [][]byte{ 1792 {0x01, 0xAB}, 1793 }, 1794 wantErr: false, 1795 }, 1796 } 1797 for _, tt := range tests { 1798 t.Run(tt.name, func(t *testing.T) { 1799 err := validateRequests(tt.requests) 1800 if (err != nil) != tt.wantErr { 1801 t.Errorf("validateRequests(%v) error = %v, wantErr = %v", 1802 tt.requests, err, tt.wantErr) 1803 } 1804 }) 1805 } 1806 }