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