Skip to content

Commit 476d520

Browse files
authored
[R4R]offline block prune (bnb-chain#543)
* offline block prune * update * update * update and add unit test * addressed comments from walt * Addressed comments from walt and Igor * ensure MPT and snapshot matched * add one more parameter to indicate blockprune * update the logic of creating freezerDb * update flag command description * expose the function for db inspect the offset/startBlockNumber * add flags to inspect prune info * rename flag of reserved-recent-blocks to block-amount-reserved * addressed comments from walt * handle the case of command interruption * refined goimports * addressed comments from walt * change the logic as restarting prune after interruption * addressed comments * reclaimed freezer logic * introduce flag to enable/disable check between MPT and snapshot * update the logic of frozen field in freezerDB * update the code in all places related to freezer change * addressed comments from dylan * update the logic for backup block difficulty * addressed comments from dylan
1 parent ebc3933 commit 476d520

25 files changed

+879
-65
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,5 @@ profile.cov
4848
/dashboard/assets/package-lock.json
4949

5050
**/yarn-error.log
51+
cmd/geth/node/
52+
cmd/geth/__debug_bin

cmd/geth/chaincmd.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ func importPreimages(ctx *cli.Context) error {
458458
stack, _ := makeConfigNode(ctx)
459459
defer stack.Close()
460460

461-
db := utils.MakeChainDatabase(ctx, stack, false)
461+
db := utils.MakeChainDatabase(ctx, stack, false, false)
462462
start := time.Now()
463463

464464
if err := utils.ImportPreimages(db, ctx.Args().First()); err != nil {
@@ -477,7 +477,7 @@ func exportPreimages(ctx *cli.Context) error {
477477
stack, _ := makeConfigNode(ctx)
478478
defer stack.Close()
479479

480-
db := utils.MakeChainDatabase(ctx, stack, true)
480+
db := utils.MakeChainDatabase(ctx, stack, true, false)
481481
start := time.Now()
482482

483483
if err := utils.ExportPreimages(db, ctx.Args().First()); err != nil {
@@ -491,7 +491,7 @@ func dump(ctx *cli.Context) error {
491491
stack, _ := makeConfigNode(ctx)
492492
defer stack.Close()
493493

494-
db := utils.MakeChainDatabase(ctx, stack, true)
494+
db := utils.MakeChainDatabase(ctx, stack, true, false)
495495
for _, arg := range ctx.Args() {
496496
var header *types.Header
497497
if hashish(arg) {

cmd/geth/dbcmd.go

+27-7
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Remove blockchain and state databases`,
6262
dbPutCmd,
6363
dbGetSlotsCmd,
6464
dbDumpFreezerIndex,
65+
ancientInspectCmd,
6566
},
6667
}
6768
dbInspectCmd = cli.Command{
@@ -195,6 +196,16 @@ WARNING: This is a low-level operation which may cause database corruption!`,
195196
},
196197
Description: "This command displays information about the freezer index.",
197198
}
199+
ancientInspectCmd = cli.Command{
200+
Action: utils.MigrateFlags(ancientInspect),
201+
Name: "inspect-reserved-oldest-blocks",
202+
Flags: []cli.Flag{
203+
utils.DataDirFlag,
204+
},
205+
Usage: "Inspect the ancientStore information",
206+
Description: `This commands will read current offset from kvdb, which is the current offset and starting BlockNumber
207+
of ancientStore, will also displays the reserved number of blocks in ancientStore `,
208+
}
198209
)
199210

200211
func removeDB(ctx *cli.Context) error {
@@ -282,12 +293,21 @@ func inspect(ctx *cli.Context) error {
282293
stack, _ := makeConfigNode(ctx)
283294
defer stack.Close()
284295

285-
db := utils.MakeChainDatabase(ctx, stack, true)
296+
db := utils.MakeChainDatabase(ctx, stack, true, false)
286297
defer db.Close()
287298

288299
return rawdb.InspectDatabase(db, prefix, start)
289300
}
290301

302+
func ancientInspect(ctx *cli.Context) error {
303+
stack, _ := makeConfigNode(ctx)
304+
defer stack.Close()
305+
306+
db := utils.MakeChainDatabase(ctx, stack, true, true)
307+
defer db.Close()
308+
return rawdb.AncientInspect(db)
309+
}
310+
291311
func showLeveldbStats(db ethdb.Stater) {
292312
if stats, err := db.Stat("leveldb.stats"); err != nil {
293313
log.Warn("Failed to read database stats", "error", err)
@@ -305,7 +325,7 @@ func dbStats(ctx *cli.Context) error {
305325
stack, _ := makeConfigNode(ctx)
306326
defer stack.Close()
307327

308-
db := utils.MakeChainDatabase(ctx, stack, true)
328+
db := utils.MakeChainDatabase(ctx, stack, true, false)
309329
defer db.Close()
310330

311331
showLeveldbStats(db)
@@ -316,7 +336,7 @@ func dbCompact(ctx *cli.Context) error {
316336
stack, _ := makeConfigNode(ctx)
317337
defer stack.Close()
318338

319-
db := utils.MakeChainDatabase(ctx, stack, false)
339+
db := utils.MakeChainDatabase(ctx, stack, false, false)
320340
defer db.Close()
321341

322342
log.Info("Stats before compaction")
@@ -340,7 +360,7 @@ func dbGet(ctx *cli.Context) error {
340360
stack, _ := makeConfigNode(ctx)
341361
defer stack.Close()
342362

343-
db := utils.MakeChainDatabase(ctx, stack, true)
363+
db := utils.MakeChainDatabase(ctx, stack, true, false)
344364
defer db.Close()
345365

346366
key, err := hexutil.Decode(ctx.Args().Get(0))
@@ -365,7 +385,7 @@ func dbDelete(ctx *cli.Context) error {
365385
stack, _ := makeConfigNode(ctx)
366386
defer stack.Close()
367387

368-
db := utils.MakeChainDatabase(ctx, stack, false)
388+
db := utils.MakeChainDatabase(ctx, stack, false, false)
369389
defer db.Close()
370390

371391
key, err := hexutil.Decode(ctx.Args().Get(0))
@@ -392,7 +412,7 @@ func dbPut(ctx *cli.Context) error {
392412
stack, _ := makeConfigNode(ctx)
393413
defer stack.Close()
394414

395-
db := utils.MakeChainDatabase(ctx, stack, false)
415+
db := utils.MakeChainDatabase(ctx, stack, false, false)
396416
defer db.Close()
397417

398418
var (
@@ -426,7 +446,7 @@ func dbDumpTrie(ctx *cli.Context) error {
426446
stack, _ := makeConfigNode(ctx)
427447
defer stack.Close()
428448

429-
db := utils.MakeChainDatabase(ctx, stack, true)
449+
db := utils.MakeChainDatabase(ctx, stack, true, false)
430450
defer db.Close()
431451
var (
432452
root []byte

cmd/geth/main.go

+2
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ var (
165165
utils.MinerNotifyFullFlag,
166166
configFileFlag,
167167
utils.CatalystFlag,
168+
utils.BlockAmountReserved,
169+
utils.CheckSnapshotWithMPT,
168170
}
169171

170172
rpcFlags = []cli.Flag{

cmd/geth/pruneblock_test.go

+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
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/ethereum/go-ethereum/cmd/utils"
31+
"github.com/ethereum/go-ethereum/common"
32+
"github.com/ethereum/go-ethereum/consensus"
33+
"github.com/ethereum/go-ethereum/consensus/ethash"
34+
"github.com/ethereum/go-ethereum/core"
35+
"github.com/ethereum/go-ethereum/core/rawdb"
36+
"github.com/ethereum/go-ethereum/core/state/pruner"
37+
"github.com/ethereum/go-ethereum/core/types"
38+
"github.com/ethereum/go-ethereum/core/vm"
39+
"github.com/ethereum/go-ethereum/crypto"
40+
"github.com/ethereum/go-ethereum/eth"
41+
"github.com/ethereum/go-ethereum/ethdb"
42+
"github.com/ethereum/go-ethereum/node"
43+
"github.com/ethereum/go-ethereum/params"
44+
"github.com/ethereum/go-ethereum/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+
}

0 commit comments

Comments
 (0)