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