pruneblock_test.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Copyright 2016 The go-ethereum Authors
  2. // This file is part of go-ethereum.
  3. //
  4. // go-ethereum is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU 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. // go-ethereum 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 General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
  16. package main
  17. import (
  18. "bytes"
  19. "encoding/hex"
  20. "fmt"
  21. "io/ioutil"
  22. "math/big"
  23. "os"
  24. "path/filepath"
  25. "testing"
  26. "time"
  27. "github.com/ethereum/go-ethereum/cmd/utils"
  28. "github.com/ethereum/go-ethereum/common"
  29. "github.com/ethereum/go-ethereum/consensus"
  30. "github.com/ethereum/go-ethereum/consensus/ethash"
  31. "github.com/ethereum/go-ethereum/core"
  32. "github.com/ethereum/go-ethereum/core/rawdb"
  33. "github.com/ethereum/go-ethereum/core/state/pruner"
  34. "github.com/ethereum/go-ethereum/core/types"
  35. "github.com/ethereum/go-ethereum/core/vm"
  36. "github.com/ethereum/go-ethereum/crypto"
  37. "github.com/ethereum/go-ethereum/eth"
  38. "github.com/ethereum/go-ethereum/ethdb"
  39. "github.com/ethereum/go-ethereum/node"
  40. "github.com/ethereum/go-ethereum/params"
  41. "github.com/ethereum/go-ethereum/rlp"
  42. )
  43. var (
  44. canonicalSeed = 1
  45. blockPruneBackUpBlockNumber = 128
  46. key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
  47. address = crypto.PubkeyToAddress(key.PublicKey)
  48. balance = big.NewInt(10000000)
  49. gspec = &core.Genesis{Config: params.TestChainConfig, Alloc: core.GenesisAlloc{address: {Balance: balance}}}
  50. signer = types.LatestSigner(gspec.Config)
  51. config = &core.CacheConfig{
  52. TrieCleanLimit: 256,
  53. TrieDirtyLimit: 256,
  54. TrieTimeLimit: 5 * time.Minute,
  55. SnapshotLimit: 0, // Disable snapshot
  56. TriesInMemory: 128,
  57. }
  58. engine = ethash.NewFullFaker()
  59. )
  60. func TestOfflineBlockPrune(t *testing.T) {
  61. //Corner case for 0 remain in ancinetStore.
  62. testOfflineBlockPruneWithAmountReserved(t, 0)
  63. //General case.
  64. testOfflineBlockPruneWithAmountReserved(t, 100)
  65. }
  66. func testOfflineBlockPruneWithAmountReserved(t *testing.T, amountReserved uint64) {
  67. datadir, err := ioutil.TempDir("", "")
  68. if err != nil {
  69. t.Fatalf("Failed to create temporary datadir: %v", err)
  70. }
  71. os.RemoveAll(datadir)
  72. chaindbPath := filepath.Join(datadir, "chaindata")
  73. oldAncientPath := filepath.Join(chaindbPath, "ancient")
  74. newAncientPath := filepath.Join(chaindbPath, "ancient_back")
  75. db, blocks, blockList, receiptsList, externTdList, startBlockNumber, _ := BlockchainCreator(t, chaindbPath, oldAncientPath, amountReserved)
  76. node, _ := startEthService(t, gspec, blocks, chaindbPath)
  77. defer node.Close()
  78. //Initialize a block pruner for pruning, only remain amountReserved blocks backward.
  79. testBlockPruner := pruner.NewBlockPruner(db, node, oldAncientPath, newAncientPath, amountReserved)
  80. if err != nil {
  81. t.Fatalf("failed to make new blockpruner: %v", err)
  82. }
  83. if err := testBlockPruner.BlockPruneBackUp(chaindbPath, 512, utils.MakeDatabaseHandles(), "", false, false); err != nil {
  84. t.Fatalf("Failed to back up block: %v", err)
  85. }
  86. dbBack, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, newAncientPath, "", false, true, false)
  87. if err != nil {
  88. t.Fatalf("failed to create database with ancient backend")
  89. }
  90. defer dbBack.Close()
  91. //check against if the backup data matched original one
  92. for blockNumber := startBlockNumber; blockNumber < startBlockNumber+amountReserved; blockNumber++ {
  93. blockHash := rawdb.ReadCanonicalHash(dbBack, blockNumber)
  94. block := rawdb.ReadBlock(dbBack, blockHash, blockNumber)
  95. if block.Hash() != blockHash {
  96. t.Fatalf("block data did not match between oldDb and backupDb")
  97. }
  98. if blockList[blockNumber-startBlockNumber].Hash() != blockHash {
  99. t.Fatalf("block data did not match between oldDb and backupDb")
  100. }
  101. receipts := rawdb.ReadRawReceipts(dbBack, blockHash, blockNumber)
  102. if err := checkReceiptsRLP(receipts, receiptsList[blockNumber-startBlockNumber]); err != nil {
  103. t.Fatalf("receipts did not match between oldDb and backupDb")
  104. }
  105. // // Calculate the total difficulty of the block
  106. td := rawdb.ReadTd(dbBack, blockHash, blockNumber)
  107. if td == nil {
  108. t.Fatalf("Failed to ReadTd: %v", consensus.ErrUnknownAncestor)
  109. }
  110. if td.Cmp(externTdList[blockNumber-startBlockNumber]) != 0 {
  111. t.Fatalf("externTd did not match between oldDb and backupDb")
  112. }
  113. }
  114. //check if ancientDb freezer replaced successfully
  115. testBlockPruner.AncientDbReplacer()
  116. if _, err := os.Stat(newAncientPath); err != nil {
  117. if !os.IsNotExist(err) {
  118. t.Fatalf("ancientDb replaced unsuccessfully")
  119. }
  120. }
  121. if _, err := os.Stat(oldAncientPath); err != nil {
  122. t.Fatalf("ancientDb replaced unsuccessfully")
  123. }
  124. }
  125. func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string, blockRemain uint64) (ethdb.Database, []*types.Block, []*types.Block, []types.Receipts, []*big.Int, uint64, *core.BlockChain) {
  126. //create a database with ancient freezer
  127. db, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, AncientPath, "", false, false, false)
  128. if err != nil {
  129. t.Fatalf("failed to create database with ancient backend")
  130. }
  131. defer db.Close()
  132. genesis := gspec.MustCommit(db)
  133. // Initialize a fresh chain with only a genesis block
  134. blockchain, err := core.NewBlockChain(db, config, gspec.Config, engine, vm.Config{}, nil, nil)
  135. if err != nil {
  136. t.Fatalf("Failed to create chain: %v", err)
  137. }
  138. // Make chain starting from genesis
  139. blocks, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 500, func(i int, block *core.BlockGen) {
  140. block.SetCoinbase(common.Address{0: byte(canonicalSeed), 19: byte(i)})
  141. tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, nil, nil), signer, key)
  142. if err != nil {
  143. panic(err)
  144. }
  145. block.AddTx(tx)
  146. block.SetDifficulty(big.NewInt(1000000))
  147. })
  148. if _, err := blockchain.InsertChain(blocks); err != nil {
  149. t.Fatalf("Failed to import canonical chain start: %v", err)
  150. }
  151. // Force run a freeze cycle
  152. type freezer interface {
  153. Freeze(threshold uint64) error
  154. Ancients() (uint64, error)
  155. }
  156. db.(freezer).Freeze(10)
  157. frozen, err := db.Ancients()
  158. //make sure there're frozen items
  159. if err != nil || frozen == 0 {
  160. t.Fatalf("Failed to import canonical chain start: %v", err)
  161. }
  162. if frozen < blockRemain {
  163. t.Fatalf("block amount is not enough for pruning: %v", err)
  164. }
  165. oldOffSet := rawdb.ReadOffSetOfCurrentAncientFreezer(db)
  166. // Get the actual start block number.
  167. startBlockNumber := frozen - blockRemain + oldOffSet
  168. // Initialize the slice to buffer the block data left.
  169. blockList := make([]*types.Block, 0, blockPruneBackUpBlockNumber)
  170. receiptsList := make([]types.Receipts, 0, blockPruneBackUpBlockNumber)
  171. externTdList := make([]*big.Int, 0, blockPruneBackUpBlockNumber)
  172. // All ancient data within the most recent 128 blocks write into memory buffer for future new ancient_back directory usage.
  173. for blockNumber := startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ {
  174. blockHash := rawdb.ReadCanonicalHash(db, blockNumber)
  175. block := rawdb.ReadBlock(db, blockHash, blockNumber)
  176. blockList = append(blockList, block)
  177. receipts := rawdb.ReadRawReceipts(db, blockHash, blockNumber)
  178. receiptsList = append(receiptsList, receipts)
  179. // Calculate the total difficulty of the block
  180. td := rawdb.ReadTd(db, blockHash, blockNumber)
  181. if td == nil {
  182. t.Fatalf("Failed to ReadTd: %v", consensus.ErrUnknownAncestor)
  183. }
  184. externTdList = append(externTdList, td)
  185. }
  186. return db, blocks, blockList, receiptsList, externTdList, startBlockNumber, blockchain
  187. }
  188. func checkReceiptsRLP(have, want types.Receipts) error {
  189. if len(have) != len(want) {
  190. return fmt.Errorf("receipts sizes mismatch: have %d, want %d", len(have), len(want))
  191. }
  192. for i := 0; i < len(want); i++ {
  193. rlpHave, err := rlp.EncodeToBytes(have[i])
  194. if err != nil {
  195. return err
  196. }
  197. rlpWant, err := rlp.EncodeToBytes(want[i])
  198. if err != nil {
  199. return err
  200. }
  201. if !bytes.Equal(rlpHave, rlpWant) {
  202. return fmt.Errorf("receipt #%d: receipt mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant))
  203. }
  204. }
  205. return nil
  206. }
  207. // startEthService creates a full node instance for testing.
  208. func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block, chaindbPath string) (*node.Node, *eth.Ethereum) {
  209. t.Helper()
  210. n, err := node.New(&node.Config{DataDir: chaindbPath})
  211. if err != nil {
  212. t.Fatal("can't create node:", err)
  213. }
  214. if err := n.Start(); err != nil {
  215. t.Fatal("can't start node:", err)
  216. }
  217. return n, nil
  218. }