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