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