|
72 | 72 | AcceptorQueueLimit: 64, |
73 | 73 | } |
74 | 74 |
|
75 | | - // Firewood should only be included for non-archive, snapshot disabled tests. |
| 75 | + // Firewood should only be included for snapshot disabled tests. |
76 | 76 | schemes = []string{rawdb.HashScheme, customrawdb.FirewoodScheme} |
77 | 77 | ) |
78 | 78 |
|
@@ -111,22 +111,36 @@ func TestArchiveBlockChain(t *testing.T) { |
111 | 111 | } |
112 | 112 |
|
113 | 113 | func TestArchiveBlockChainSnapsDisabled(t *testing.T) { |
114 | | - create := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash, _ string) (*BlockChain, error) { |
| 114 | + for _, scheme := range schemes { |
| 115 | + t.Run(scheme, func(t *testing.T) { |
| 116 | + testArchiveBlockChainSnapsDisabled(t, scheme) |
| 117 | + }) |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +func testArchiveBlockChainSnapsDisabled(t *testing.T, scheme string) { |
| 122 | + create := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash, dataPath string) (*BlockChain, error) { |
| 123 | + cacheConfig := &CacheConfig{ |
| 124 | + TrieCleanLimit: 256, |
| 125 | + TrieDirtyLimit: 256, |
| 126 | + TrieDirtyCommitTarget: 20, |
| 127 | + TriePrefetcherParallelism: 4, |
| 128 | + Pruning: false, // Archive mode |
| 129 | + StateHistory: 32, // Required for Firewood's minimum Revision count |
| 130 | + SnapshotLimit: 0, // Disable snapshots |
| 131 | + AcceptorQueueLimit: 64, |
| 132 | + StateScheme: scheme, |
| 133 | + ChainDataDir: dataPath, |
| 134 | + } |
| 135 | + |
115 | 136 | return createBlockChain( |
116 | 137 | db, |
117 | | - &CacheConfig{ |
118 | | - TrieCleanLimit: 256, |
119 | | - TrieDirtyLimit: 256, |
120 | | - TrieDirtyCommitTarget: 20, |
121 | | - TriePrefetcherParallelism: 4, |
122 | | - Pruning: false, // Archive mode |
123 | | - SnapshotLimit: 0, // Disable snapshots |
124 | | - AcceptorQueueLimit: 64, |
125 | | - }, |
| 138 | + cacheConfig, |
126 | 139 | gspec, |
127 | 140 | lastAcceptedHash, |
128 | 141 | ) |
129 | 142 | } |
| 143 | + |
130 | 144 | for _, tt := range tests { |
131 | 145 | t.Run(tt.Name, func(t *testing.T) { |
132 | 146 | tt.testFunc(t, create) |
@@ -409,6 +423,164 @@ func TestBlockChainOfflinePruningUngracefulShutdown(t *testing.T) { |
409 | 423 | } |
410 | 424 | } |
411 | 425 |
|
| 426 | +// TestPruningToNonPruning tests that opening a previously pruned database as a |
| 427 | +// non-pruned database is successful. |
| 428 | +func TestPruningToNonPruning(t *testing.T) { |
| 429 | + for _, scheme := range schemes { |
| 430 | + t.Run(scheme, func(t *testing.T) { |
| 431 | + testPruningToNonPruning(t, scheme) |
| 432 | + }) |
| 433 | + } |
| 434 | +} |
| 435 | + |
| 436 | +// testPruningToNonPruning tests that opening a previously pruned database as a |
| 437 | +// non-pruned database is successful. |
| 438 | +// |
| 439 | +// This test checks the following invariants: |
| 440 | +// 1. Verifies that a pruned node does not have the state for all blocks (except |
| 441 | +// the last accepted block) upon restart. |
| 442 | +// 2. Verify that a pruned => archival node has the state for all blocks |
| 443 | +// accepted during archival mode upon restart. |
| 444 | +func testPruningToNonPruning(t *testing.T, scheme string) { |
| 445 | + var ( |
| 446 | + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") |
| 447 | + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") |
| 448 | + addr1 = crypto.PubkeyToAddress(key1.PublicKey) |
| 449 | + addr2 = crypto.PubkeyToAddress(key2.PublicKey) |
| 450 | + chainDB = rawdb.NewMemoryDatabase() |
| 451 | + numStates = uint64(5) |
| 452 | + ) |
| 453 | + |
| 454 | + gspec := &Genesis{ |
| 455 | + Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, |
| 456 | + Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, |
| 457 | + } |
| 458 | + |
| 459 | + chainDataDir := t.TempDir() |
| 460 | + pruningConfig := &CacheConfig{ |
| 461 | + TrieCleanLimit: 256, |
| 462 | + TrieDirtyLimit: 256, |
| 463 | + TrieDirtyCommitTarget: 20, |
| 464 | + TriePrefetcherParallelism: 4, |
| 465 | + Pruning: true, // Enable pruning |
| 466 | + CommitInterval: 4096, |
| 467 | + StateHistory: numStates, |
| 468 | + AcceptorQueueLimit: 64, |
| 469 | + StateScheme: scheme, |
| 470 | + ChainDataDir: chainDataDir, |
| 471 | + } |
| 472 | + |
| 473 | + // Create a node in pruning mode. |
| 474 | + blockchain, err := createBlockChain(chainDB, pruningConfig, gspec, common.Hash{}) |
| 475 | + if err != nil { |
| 476 | + t.Fatal(err) |
| 477 | + } |
| 478 | + |
| 479 | + // Generate 10 (2 * numStates) blocks. |
| 480 | + signer := types.HomesteadSigner{} |
| 481 | + _, blocks, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 2*int(numStates), 10, func(i int, gen *BlockGen) { |
| 482 | + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), ethparams.TxGas, nil, nil), signer, key1) |
| 483 | + gen.AddTx(tx) |
| 484 | + }) |
| 485 | + if err != nil { |
| 486 | + t.Fatal(err) |
| 487 | + } |
| 488 | + |
| 489 | + prunedBlocks := blocks[:numStates] |
| 490 | + nonPrunedBlocks := blocks[numStates:] |
| 491 | + |
| 492 | + // Insert the first five blocks. |
| 493 | + // The states of the first four blocks will be lost upon restart. |
| 494 | + if _, err := blockchain.InsertChain(prunedBlocks); err != nil { |
| 495 | + t.Fatal(err) |
| 496 | + } |
| 497 | + for _, block := range prunedBlocks { |
| 498 | + if err := blockchain.Accept(block); err != nil { |
| 499 | + t.Fatal(err) |
| 500 | + } |
| 501 | + } |
| 502 | + blockchain.DrainAcceptorQueue() |
| 503 | + |
| 504 | + lastAcceptedHash := blockchain.LastConsensusAcceptedBlock().Hash() |
| 505 | + blockchain.Stop() |
| 506 | + |
| 507 | + // Reopen the node. |
| 508 | + blockchain, err = createBlockChain(chainDB, pruningConfig, gspec, lastAcceptedHash) |
| 509 | + if err != nil { |
| 510 | + t.Fatal(err) |
| 511 | + } |
| 512 | + |
| 513 | + // 1. Verify that a pruned node does not have the state for all blocks (except |
| 514 | + // the last accepted block) upon restart. |
| 515 | + for _, block := range prunedBlocks[:numStates-1] { |
| 516 | + if blockchain.HasState(block.Root()) { |
| 517 | + t.Fatalf("Expected blockchain to be missing state for intermediate block %d with pruning enabled", block.NumberU64()) |
| 518 | + } |
| 519 | + } |
| 520 | + |
| 521 | + blockchain.Stop() |
| 522 | + |
| 523 | + archiveConfig := &CacheConfig{ |
| 524 | + TrieCleanLimit: 256, |
| 525 | + TrieDirtyLimit: 256, |
| 526 | + TrieDirtyCommitTarget: 20, |
| 527 | + TriePrefetcherParallelism: 4, |
| 528 | + Pruning: false, // Archive mode |
| 529 | + AcceptorQueueLimit: 64, |
| 530 | + StateScheme: scheme, |
| 531 | + StateHistory: 32, |
| 532 | + ChainDataDir: chainDataDir, |
| 533 | + } |
| 534 | + |
| 535 | + // Reopen the node, but switch from pruning to archival mode. |
| 536 | + blockchain, err = createBlockChain( |
| 537 | + chainDB, |
| 538 | + archiveConfig, |
| 539 | + gspec, |
| 540 | + lastAcceptedHash, |
| 541 | + ) |
| 542 | + if err != nil { |
| 543 | + t.Fatal(err) |
| 544 | + } |
| 545 | + |
| 546 | + // Insert the remaining five blocks. |
| 547 | + // The states of all these blocks will still be accessible on restart |
| 548 | + // since we're now in archival mode. |
| 549 | + if _, err := blockchain.InsertChain(nonPrunedBlocks); err != nil { |
| 550 | + t.Fatal(err) |
| 551 | + } |
| 552 | + |
| 553 | + for _, block := range nonPrunedBlocks { |
| 554 | + if err := blockchain.Accept(block); err != nil { |
| 555 | + t.Fatal(err) |
| 556 | + } |
| 557 | + } |
| 558 | + blockchain.DrainAcceptorQueue() |
| 559 | + |
| 560 | + lastAcceptedHash = blockchain.LastConsensusAcceptedBlock().Hash() |
| 561 | + blockchain.Stop() |
| 562 | + |
| 563 | + // Reopen the archival node. |
| 564 | + blockchain, err = createBlockChain( |
| 565 | + chainDB, |
| 566 | + archiveConfig, |
| 567 | + gspec, |
| 568 | + lastAcceptedHash, |
| 569 | + ) |
| 570 | + if err != nil { |
| 571 | + t.Fatal(err) |
| 572 | + } |
| 573 | + defer blockchain.Stop() |
| 574 | + |
| 575 | + // 2. Verify that a pruned => archival node has the state for all blocks |
| 576 | + // accepted during archival mode upon restart. |
| 577 | + for _, block := range nonPrunedBlocks { |
| 578 | + if !blockchain.HasState(block.Root()) { |
| 579 | + t.Fatalf("Expected blockchain to have the state for block %d with pruning disabled", block.NumberU64()) |
| 580 | + } |
| 581 | + } |
| 582 | +} |
| 583 | + |
412 | 584 | func testRepopulateMissingTriesParallel(t *testing.T, parallelism int) { |
413 | 585 | var ( |
414 | 586 | key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") |
|
0 commit comments