github.com/tacshi/go-ethereum@v0.0.0-20230616113857-84a434e20921/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/tacshi/go-ethereum/beacon/engine" 32 "github.com/tacshi/go-ethereum/common" 33 "github.com/tacshi/go-ethereum/common/hexutil" 34 "github.com/tacshi/go-ethereum/consensus" 35 beaconConsensus "github.com/tacshi/go-ethereum/consensus/beacon" 36 "github.com/tacshi/go-ethereum/consensus/ethash" 37 "github.com/tacshi/go-ethereum/core" 38 "github.com/tacshi/go-ethereum/core/types" 39 "github.com/tacshi/go-ethereum/crypto" 40 "github.com/tacshi/go-ethereum/eth" 41 "github.com/tacshi/go-ethereum/eth/downloader" 42 "github.com/tacshi/go-ethereum/eth/ethconfig" 43 "github.com/tacshi/go-ethereum/miner" 44 "github.com/tacshi/go-ethereum/node" 45 "github.com/tacshi/go-ethereum/p2p" 46 "github.com/tacshi/go-ethereum/params" 47 "github.com/tacshi/go-ethereum/rpc" 48 "github.com/tacshi/go-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, Ethash: ethash.Config{PowMode: ethash.ModeFake}, 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 883 ethservice.BlockChain().Config().TerminalTotalDifficulty = preMergeBlocks[0].Difficulty() //.Sub(genesis.Config.TerminalTotalDifficulty, preMergeBlocks[len(preMergeBlocks)-1].Difficulty()) 884 885 var ( 886 api = NewConsensusAPI(ethservice) 887 parent = preMergeBlocks[len(preMergeBlocks)-1] 888 ) 889 890 // Test parent already post TTD in FCU 891 fcState := engine.ForkchoiceStateV1{ 892 HeadBlockHash: parent.Hash(), 893 SafeBlockHash: common.Hash{}, 894 FinalizedBlockHash: common.Hash{}, 895 } 896 resp, err := api.ForkchoiceUpdatedV1(fcState, nil) 897 if err != nil { 898 t.Fatalf("error sending forkchoice, err=%v", err) 899 } 900 if resp.PayloadStatus != engine.INVALID_TERMINAL_BLOCK { 901 t.Fatalf("error sending invalid forkchoice, invalid status: %v", resp.PayloadStatus.Status) 902 } 903 904 // Test parent already post TTD in NewPayload 905 args := &miner.BuildPayloadArgs{ 906 Parent: parent.Hash(), 907 Timestamp: parent.Time() + 1, 908 Random: crypto.Keccak256Hash([]byte{byte(1)}), 909 FeeRecipient: parent.Coinbase(), 910 } 911 payload, err := api.eth.Miner().BuildPayload(args) 912 if err != nil { 913 t.Fatalf("error preparing payload, err=%v", err) 914 } 915 data := *payload.Resolve().ExecutionPayload 916 resp2, err := api.NewPayloadV1(data) 917 if err != nil { 918 t.Fatalf("error sending NewPayload, err=%v", err) 919 } 920 if resp2 != engine.INVALID_TERMINAL_BLOCK { 921 t.Fatalf("error sending invalid forkchoice, invalid status: %v", resp.PayloadStatus.Status) 922 } 923 } 924 925 // TestSimultaneousNewBlock does several parallel inserts, both as 926 // newPayLoad and forkchoiceUpdate. This is to test that the api behaves 927 // well even of the caller is not being 'serial'. 928 func TestSimultaneousNewBlock(t *testing.T) { 929 genesis, preMergeBlocks := generateMergeChain(10, false) 930 n, ethservice := startEthService(t, genesis, preMergeBlocks) 931 defer n.Close() 932 933 var ( 934 api = NewConsensusAPI(ethservice) 935 parent = preMergeBlocks[len(preMergeBlocks)-1] 936 ) 937 for i := 0; i < 10; i++ { 938 execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{ 939 Timestamp: parent.Time() + 5, 940 }) 941 if err != nil { 942 t.Fatalf("Failed to create the executable data %v", err) 943 } 944 // Insert it 10 times in parallel. Should be ignored. 945 { 946 var ( 947 wg sync.WaitGroup 948 testErr error 949 errMu sync.Mutex 950 ) 951 wg.Add(10) 952 for ii := 0; ii < 10; ii++ { 953 go func() { 954 defer wg.Done() 955 if newResp, err := api.NewPayloadV1(*execData); err != nil { 956 errMu.Lock() 957 testErr = fmt.Errorf("Failed to insert block: %w", err) 958 errMu.Unlock() 959 } else if newResp.Status != "VALID" { 960 errMu.Lock() 961 testErr = fmt.Errorf("Failed to insert block: %v", newResp.Status) 962 errMu.Unlock() 963 } 964 }() 965 } 966 wg.Wait() 967 if testErr != nil { 968 t.Fatal(testErr) 969 } 970 } 971 block, err := engine.ExecutableDataToBlock(*execData) 972 if err != nil { 973 t.Fatalf("Failed to convert executable data to block %v", err) 974 } 975 if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64()-1 { 976 t.Fatalf("Chain head shouldn't be updated") 977 } 978 fcState := engine.ForkchoiceStateV1{ 979 HeadBlockHash: block.Hash(), 980 SafeBlockHash: block.Hash(), 981 FinalizedBlockHash: block.Hash(), 982 } 983 { 984 var ( 985 wg sync.WaitGroup 986 testErr error 987 errMu sync.Mutex 988 ) 989 wg.Add(10) 990 // Do each FCU 10 times 991 for ii := 0; ii < 10; ii++ { 992 go func() { 993 defer wg.Done() 994 if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { 995 errMu.Lock() 996 testErr = fmt.Errorf("Failed to insert block: %w", err) 997 errMu.Unlock() 998 } 999 }() 1000 } 1001 wg.Wait() 1002 if testErr != nil { 1003 t.Fatal(testErr) 1004 } 1005 } 1006 if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want { 1007 t.Fatalf("Chain head should be updated, have %d want %d", have, want) 1008 } 1009 parent = block 1010 } 1011 } 1012 1013 // TestWithdrawals creates and verifies two post-Shanghai blocks. The first 1014 // includes zero withdrawals and the second includes two. 1015 func TestWithdrawals(t *testing.T) { 1016 genesis, blocks := generateMergeChain(10, true) 1017 // Set shanghai time to last block + 5 seconds (first post-merge block) 1018 time := blocks[len(blocks)-1].Time() + 5 1019 genesis.Config.ShanghaiTime = &time 1020 1021 n, ethservice := startEthService(t, genesis, blocks) 1022 ethservice.Merger().ReachTTD() 1023 defer n.Close() 1024 1025 api := NewConsensusAPI(ethservice) 1026 1027 // 10: Build Shanghai block with no withdrawals. 1028 parent := ethservice.BlockChain().CurrentHeader() 1029 blockParams := engine.PayloadAttributes{ 1030 Timestamp: parent.Time + 5, 1031 Withdrawals: make([]*types.Withdrawal, 0), 1032 } 1033 fcState := engine.ForkchoiceStateV1{ 1034 HeadBlockHash: parent.Hash(), 1035 } 1036 resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams) 1037 if err != nil { 1038 t.Fatalf("error preparing payload, err=%v", err) 1039 } 1040 if resp.PayloadStatus.Status != engine.VALID { 1041 t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID) 1042 } 1043 1044 // 10: verify state root is the same as parent 1045 payloadID := (&miner.BuildPayloadArgs{ 1046 Parent: fcState.HeadBlockHash, 1047 Timestamp: blockParams.Timestamp, 1048 FeeRecipient: blockParams.SuggestedFeeRecipient, 1049 Random: blockParams.Random, 1050 Withdrawals: blockParams.Withdrawals, 1051 }).Id() 1052 execData, err := api.GetPayloadV2(payloadID) 1053 if err != nil { 1054 t.Fatalf("error getting payload, err=%v", err) 1055 } 1056 if execData.ExecutionPayload.StateRoot != parent.Root { 1057 t.Fatalf("mismatch state roots (got: %s, want: %s)", execData.ExecutionPayload.StateRoot, blocks[8].Root()) 1058 } 1059 1060 // 10: verify locally built block 1061 if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil { 1062 t.Fatalf("error validating payload: %v", err) 1063 } else if status.Status != engine.VALID { 1064 t.Fatalf("invalid payload") 1065 } 1066 1067 // 11: build shanghai block with withdrawal 1068 aa := common.Address{0xaa} 1069 bb := common.Address{0xbb} 1070 blockParams = engine.PayloadAttributes{ 1071 Timestamp: execData.ExecutionPayload.Timestamp + 5, 1072 Withdrawals: []*types.Withdrawal{ 1073 { 1074 Index: 0, 1075 Address: aa, 1076 Amount: 32, 1077 }, 1078 { 1079 Index: 1, 1080 Address: bb, 1081 Amount: 33, 1082 }, 1083 }, 1084 } 1085 fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash 1086 _, err = api.ForkchoiceUpdatedV2(fcState, &blockParams) 1087 if err != nil { 1088 t.Fatalf("error preparing payload, err=%v", err) 1089 } 1090 1091 // 11: verify locally build block. 1092 payloadID = (&miner.BuildPayloadArgs{ 1093 Parent: fcState.HeadBlockHash, 1094 Timestamp: blockParams.Timestamp, 1095 FeeRecipient: blockParams.SuggestedFeeRecipient, 1096 Random: blockParams.Random, 1097 Withdrawals: blockParams.Withdrawals, 1098 }).Id() 1099 execData, err = api.GetPayloadV2(payloadID) 1100 if err != nil { 1101 t.Fatalf("error getting payload, err=%v", err) 1102 } 1103 if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil { 1104 t.Fatalf("error validating payload: %v", err) 1105 } else if status.Status != engine.VALID { 1106 t.Fatalf("invalid payload") 1107 } 1108 1109 // 11: set block as head. 1110 fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash 1111 _, err = api.ForkchoiceUpdatedV2(fcState, nil) 1112 if err != nil { 1113 t.Fatalf("error preparing payload, err=%v", err) 1114 } 1115 1116 // 11: verify withdrawals were processed. 1117 db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number)) 1118 if err != nil { 1119 t.Fatalf("unable to load db: %v", err) 1120 } 1121 for i, w := range blockParams.Withdrawals { 1122 // w.Amount is in gwei, balance in wei 1123 if db.GetBalance(w.Address).Uint64() != w.Amount*params.GWei { 1124 t.Fatalf("failed to process withdrawal %d", i) 1125 } 1126 } 1127 } 1128 1129 func TestNilWithdrawals(t *testing.T) { 1130 genesis, blocks := generateMergeChain(10, true) 1131 // Set shanghai time to last block + 4 seconds (first post-merge block) 1132 time := blocks[len(blocks)-1].Time() + 4 1133 genesis.Config.ShanghaiTime = &time 1134 1135 n, ethservice := startEthService(t, genesis, blocks) 1136 ethservice.Merger().ReachTTD() 1137 defer n.Close() 1138 1139 api := NewConsensusAPI(ethservice) 1140 parent := ethservice.BlockChain().CurrentHeader() 1141 aa := common.Address{0xaa} 1142 1143 type test struct { 1144 blockParams engine.PayloadAttributes 1145 wantErr bool 1146 } 1147 tests := []test{ 1148 // Before Shanghai 1149 { 1150 blockParams: engine.PayloadAttributes{ 1151 Timestamp: parent.Time + 2, 1152 Withdrawals: nil, 1153 }, 1154 wantErr: false, 1155 }, 1156 { 1157 blockParams: engine.PayloadAttributes{ 1158 Timestamp: parent.Time + 2, 1159 Withdrawals: make([]*types.Withdrawal, 0), 1160 }, 1161 wantErr: true, 1162 }, 1163 { 1164 blockParams: engine.PayloadAttributes{ 1165 Timestamp: parent.Time + 2, 1166 Withdrawals: []*types.Withdrawal{ 1167 { 1168 Index: 0, 1169 Address: aa, 1170 Amount: 32, 1171 }, 1172 }, 1173 }, 1174 wantErr: true, 1175 }, 1176 // After Shanghai 1177 { 1178 blockParams: engine.PayloadAttributes{ 1179 Timestamp: parent.Time + 5, 1180 Withdrawals: nil, 1181 }, 1182 wantErr: true, 1183 }, 1184 { 1185 blockParams: engine.PayloadAttributes{ 1186 Timestamp: parent.Time + 5, 1187 Withdrawals: make([]*types.Withdrawal, 0), 1188 }, 1189 wantErr: false, 1190 }, 1191 { 1192 blockParams: engine.PayloadAttributes{ 1193 Timestamp: parent.Time + 5, 1194 Withdrawals: []*types.Withdrawal{ 1195 { 1196 Index: 0, 1197 Address: aa, 1198 Amount: 32, 1199 }, 1200 }, 1201 }, 1202 wantErr: false, 1203 }, 1204 } 1205 1206 fcState := engine.ForkchoiceStateV1{ 1207 HeadBlockHash: parent.Hash(), 1208 } 1209 1210 for _, test := range tests { 1211 _, err := api.ForkchoiceUpdatedV2(fcState, &test.blockParams) 1212 if test.wantErr { 1213 if err == nil { 1214 t.Fatal("wanted error on fcuv2 with invalid withdrawals") 1215 } 1216 continue 1217 } 1218 if err != nil { 1219 t.Fatalf("error preparing payload, err=%v", err) 1220 } 1221 1222 // 11: verify locally build block. 1223 payloadID := (&miner.BuildPayloadArgs{ 1224 Parent: fcState.HeadBlockHash, 1225 Timestamp: test.blockParams.Timestamp, 1226 FeeRecipient: test.blockParams.SuggestedFeeRecipient, 1227 Random: test.blockParams.Random, 1228 }).Id() 1229 execData, err := api.GetPayloadV2(payloadID) 1230 if err != nil { 1231 t.Fatalf("error getting payload, err=%v", err) 1232 } 1233 if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil { 1234 t.Fatalf("error validating payload: %v", err) 1235 } else if status.Status != engine.VALID { 1236 t.Fatalf("invalid payload") 1237 } 1238 } 1239 } 1240 1241 func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) { 1242 genesis, blocks := generateMergeChain(10, true) 1243 n, ethservice := startEthService(t, genesis, blocks) 1244 // enable shanghai on the last block 1245 ethservice.BlockChain().Config().ShanghaiTime = &blocks[len(blocks)-1].Header().Time 1246 1247 var ( 1248 parent = ethservice.BlockChain().CurrentBlock() 1249 // This EVM code generates a log when the contract is created. 1250 logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") 1251 ) 1252 1253 callback := func(parent *types.Header) { 1254 statedb, _ := ethservice.BlockChain().StateAt(parent.Root) 1255 nonce := statedb.GetNonce(testAddr) 1256 tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey) 1257 ethservice.TxPool().AddLocal(tx) 1258 } 1259 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 postShanghaiHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals) 1272 postShanghaiBlocks := make([]*types.Block, len(postShanghaiHeaders)) 1273 for i, header := range postShanghaiHeaders { 1274 postShanghaiBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64()) 1275 } 1276 return n, ethservice, append(blocks, postShanghaiBlocks...) 1277 } 1278 1279 func allHashes(blocks []*types.Block) []common.Hash { 1280 var hashes []common.Hash 1281 for _, b := range blocks { 1282 hashes = append(hashes, b.Hash()) 1283 } 1284 return hashes 1285 } 1286 func allBodies(blocks []*types.Block) []*types.Body { 1287 var bodies []*types.Body 1288 for _, b := range blocks { 1289 bodies = append(bodies, b.Body()) 1290 } 1291 return bodies 1292 } 1293 1294 func TestGetBlockBodiesByHash(t *testing.T) { 1295 node, eth, blocks := setupBodies(t) 1296 api := NewConsensusAPI(eth) 1297 defer node.Close() 1298 1299 tests := []struct { 1300 results []*types.Body 1301 hashes []common.Hash 1302 }{ 1303 // First pow block 1304 { 1305 results: []*types.Body{eth.BlockChain().GetBlockByNumber(0).Body()}, 1306 hashes: []common.Hash{eth.BlockChain().GetBlockByNumber(0).Hash()}, 1307 }, 1308 // Last pow block 1309 { 1310 results: []*types.Body{blocks[9].Body()}, 1311 hashes: []common.Hash{blocks[9].Hash()}, 1312 }, 1313 // First post-merge block 1314 { 1315 results: []*types.Body{blocks[10].Body()}, 1316 hashes: []common.Hash{blocks[10].Hash()}, 1317 }, 1318 // Pre & post merge blocks 1319 { 1320 results: []*types.Body{blocks[0].Body(), blocks[9].Body(), blocks[14].Body()}, 1321 hashes: []common.Hash{blocks[0].Hash(), blocks[9].Hash(), blocks[14].Hash()}, 1322 }, 1323 // unavailable block 1324 { 1325 results: []*types.Body{blocks[0].Body(), nil, blocks[14].Body()}, 1326 hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[14].Hash()}, 1327 }, 1328 // same block multiple times 1329 { 1330 results: []*types.Body{blocks[0].Body(), nil, blocks[0].Body(), blocks[0].Body()}, 1331 hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[0].Hash(), blocks[0].Hash()}, 1332 }, 1333 // all blocks 1334 { 1335 results: allBodies(blocks), 1336 hashes: allHashes(blocks), 1337 }, 1338 } 1339 1340 for k, test := range tests { 1341 result := api.GetPayloadBodiesByHashV1(test.hashes) 1342 for i, r := range result { 1343 if !equalBody(test.results[i], r) { 1344 t.Fatalf("test %v: invalid response: expected %+v got %+v", k, test.results[i], r) 1345 } 1346 } 1347 } 1348 } 1349 1350 func TestGetBlockBodiesByRange(t *testing.T) { 1351 node, eth, blocks := setupBodies(t) 1352 api := NewConsensusAPI(eth) 1353 defer node.Close() 1354 1355 tests := []struct { 1356 results []*types.Body 1357 start hexutil.Uint64 1358 count hexutil.Uint64 1359 }{ 1360 { 1361 results: []*types.Body{blocks[9].Body()}, 1362 start: 10, 1363 count: 1, 1364 }, 1365 // Genesis 1366 { 1367 results: []*types.Body{blocks[0].Body()}, 1368 start: 1, 1369 count: 1, 1370 }, 1371 // First post-merge block 1372 { 1373 results: []*types.Body{blocks[9].Body()}, 1374 start: 10, 1375 count: 1, 1376 }, 1377 // Pre & post merge blocks 1378 { 1379 results: []*types.Body{blocks[7].Body(), blocks[8].Body(), blocks[9].Body(), blocks[10].Body()}, 1380 start: 8, 1381 count: 4, 1382 }, 1383 // unavailable block 1384 { 1385 results: []*types.Body{blocks[18].Body(), blocks[19].Body()}, 1386 start: 19, 1387 count: 3, 1388 }, 1389 // unavailable block 1390 { 1391 results: []*types.Body{blocks[19].Body()}, 1392 start: 20, 1393 count: 2, 1394 }, 1395 { 1396 results: []*types.Body{blocks[19].Body()}, 1397 start: 20, 1398 count: 1, 1399 }, 1400 // whole range unavailable 1401 { 1402 results: make([]*types.Body, 0), 1403 start: 22, 1404 count: 2, 1405 }, 1406 // allBlocks 1407 { 1408 results: allBodies(blocks), 1409 start: 1, 1410 count: hexutil.Uint64(len(blocks)), 1411 }, 1412 } 1413 1414 for k, test := range tests { 1415 result, err := api.GetPayloadBodiesByRangeV1(test.start, test.count) 1416 if err != nil { 1417 t.Fatal(err) 1418 } 1419 if len(result) == len(test.results) { 1420 for i, r := range result { 1421 if !equalBody(test.results[i], r) { 1422 t.Fatalf("test %d: invalid response: expected \n%+v\ngot\n%+v", k, test.results[i], r) 1423 } 1424 } 1425 } else { 1426 t.Fatalf("test %d: invalid length want %v got %v", k, len(test.results), len(result)) 1427 } 1428 } 1429 } 1430 1431 func TestGetBlockBodiesByRangeInvalidParams(t *testing.T) { 1432 node, eth, _ := setupBodies(t) 1433 api := NewConsensusAPI(eth) 1434 defer node.Close() 1435 tests := []struct { 1436 start hexutil.Uint64 1437 count hexutil.Uint64 1438 want *engine.EngineAPIError 1439 }{ 1440 // Genesis 1441 { 1442 start: 0, 1443 count: 1, 1444 want: engine.InvalidParams, 1445 }, 1446 // No block requested 1447 { 1448 start: 1, 1449 count: 0, 1450 want: engine.InvalidParams, 1451 }, 1452 // Genesis & no block 1453 { 1454 start: 0, 1455 count: 0, 1456 want: engine.InvalidParams, 1457 }, 1458 // More than 1024 blocks 1459 { 1460 start: 1, 1461 count: 1025, 1462 want: engine.TooLargeRequest, 1463 }, 1464 } 1465 for i, tc := range tests { 1466 result, err := api.GetPayloadBodiesByRangeV1(tc.start, tc.count) 1467 if err == nil { 1468 t.Fatalf("test %d: expected error, got %v", i, result) 1469 } 1470 if have, want := err.Error(), tc.want.Error(); have != want { 1471 t.Fatalf("test %d: have %s, want %s", i, have, want) 1472 } 1473 } 1474 } 1475 1476 func equalBody(a *types.Body, b *engine.ExecutionPayloadBodyV1) bool { 1477 if a == nil && b == nil { 1478 return true 1479 } else if a == nil || b == nil { 1480 return false 1481 } 1482 if len(a.Transactions) != len(b.TransactionData) { 1483 return false 1484 } 1485 for i, tx := range a.Transactions { 1486 data, _ := tx.MarshalBinary() 1487 if !bytes.Equal(data, b.TransactionData[i]) { 1488 return false 1489 } 1490 } 1491 return reflect.DeepEqual(a.Withdrawals, b.Withdrawals) 1492 }