github.com/fff-chain/go-fff@v0.0.0-20220726032732-1c84420b8a99/cmd/geth/pruneblock_test.go (about)

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