Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BEP-283: Segmented History Data Maintenance #2017

Draft
wants to merge 22 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0edbf96
geth: add export segment cmd;
0xbundler Nov 15, 2023
5f743c2
chaincmd: add export history segment tools;
0xbundler Nov 16, 2023
ac4624b
params: add history block segment config;
0xbundler Nov 16, 2023
dd3f969
params: add history block segment config;
0xbundler Nov 21, 2023
ede66f6
historysegment: add offline history segments tool;
0xbundler Nov 22, 2023
81c48ae
historysegment: add offline history segments tool;
0xbundler Nov 22, 2023
f1dfe1b
historysegment: add offline history segments tool;
0xbundler Nov 23, 2023
e5d0d24
dbcmd: opt ancient inspect tool;
0xbundler Nov 23, 2023
ff9e239
snapsync: ensure sync from last segment in snap sync when using histo…
0xbundler Nov 23, 2023
c8b2a04
snapsync: fix some sync error;
0xbundler Nov 27, 2023
eb1141c
historysegment: support parlia snapshot;
0xbundler Nov 28, 2023
3d8c301
parlia: support load snap from history segment;
0xbundler Nov 29, 2023
1d8fa8b
historysegment: simplify segment definition;
0xbundler Nov 30, 2023
dbe87c1
sync: make history segment more flexible in snap sync;
0xbundler Nov 30, 2023
ecdefc7
freezer: support reset ancient head+tail;
0xbundler Dec 7, 2023
924639c
historysegment: add testnet&mainnet hard code;
0xbundler Dec 8, 2023
78adfc7
freezerdb: fix migrate ancient data err when sync from history segment;
0xbundler Dec 18, 2023
ec5a4aa
historysegment: opt some naming;
0xbundler Dec 22, 2023
4cf9aeb
bloombits: support fast forward by history segment;
0xbundler Dec 26, 2023
15e4c1d
freezer: compatible with pruneancient when restart;
0xbundler Dec 27, 2023
1ffe199
flags: fix history flag not found issue;
0xbundler Dec 29, 2023
d54b026
fix: fix some cr comments;
0xbundler Jan 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions cmd/geth/chaincmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ import (
"sync/atomic"
"time"

"github.com/ethereum/go-ethereum/consensus"

"github.com/ethereum/go-ethereum/params"

"github.com/ethereum/go-ethereum/eth/ethconfig"

"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
Expand Down Expand Up @@ -191,6 +197,36 @@ It's deprecated, please use "geth db export" instead.
}, utils.DatabasePathFlags),
Description: `
This command dumps out the state for a given block (or latest, if none provided).
`,
}
exportSegmentCommand = &cli.Command{
Action: exportSegment,
Name: "export-segment",
Usage: "Export history segments from start block",
ArgsUsage: "",
Flags: flags.Merge([]cli.Flag{
utils.CacheFlag,
utils.SyncModeFlag,
utils.HistorySegOutputFlag,
utils.BoundStartBlockFlag,
utils.HistorySegmentLengthFlag,
}, utils.DatabasePathFlags),
Description: `
This command export history segments from start block.
`,
}
pruneHistorySegmentsCommand = &cli.Command{
Action: pruneHistorySegments,
Name: "prune-history-segments",
Usage: "Prune all history segments, it only keep latest 2 segments",
ArgsUsage: "",
Flags: flags.Merge([]cli.Flag{
utils.CacheFlag,
utils.SyncModeFlag,
utils.HistorySegCustomFlag,
}, utils.DatabasePathFlags),
Description: `
Prune all history segments, it only keep latest 2 segments.
`,
}
)
Expand Down Expand Up @@ -675,8 +711,206 @@ func dump(ctx *cli.Context) error {
return nil
}

func exportSegment(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()

db := utils.MakeChainDatabase(ctx, stack, false, false)
defer db.Close()

genesisHash := rawdb.ReadCanonicalHash(db, 0)
td := rawdb.ReadTd(db, genesisHash, 0)
chainConfig, engine, headerChain, err := simpleHeaderChain(db, genesisHash)
if err != nil {
return err
}
latest := headerChain.CurrentHeader()
if _, ok := engine.(consensus.PoSA); !ok {
return errors.New("cannot generate history segment because consensus engine is not PoSA")
}
if !chainConfig.IsPlato(latest.Number) {
return errors.New("plato hard fork is not enabled , cannot generate history segment")
}

var (
boundStartBlock = params.BoundStartBlock
historySegmentLength = params.HistorySegmentLength
)
switch genesisHash {
case params.BSCGenesisHash, params.ChapelGenesisHash:
boundStartBlock = params.BoundStartBlock
Mister-EA marked this conversation as resolved.
Show resolved Hide resolved
historySegmentLength = params.HistorySegmentLength
default:
if ctx.IsSet(utils.BoundStartBlockFlag.Name) {
boundStartBlock = ctx.Uint64(utils.BoundStartBlockFlag.Name)
}
if ctx.IsSet(utils.HistorySegmentLengthFlag.Name) {
historySegmentLength = ctx.Uint64(utils.HistorySegmentLengthFlag.Name)
}
}
if boundStartBlock == 0 || historySegmentLength == 0 {
return fmt.Errorf("wrong params, boundStartBlock: %v, historySegmentLength: %v", boundStartBlock, historySegmentLength)
}
if chainConfig.Parlia != nil && (boundStartBlock%chainConfig.Parlia.Epoch != 0 || historySegmentLength%chainConfig.Parlia.Epoch != 0) {
return fmt.Errorf("please ensure that the parameters is an integer multiple of parlia epoch, boundStartBlock: %v, historySegmentLength: %v", boundStartBlock, historySegmentLength)
}

latestNum := latest.Number.Uint64()
if latestNum < params.FullImmutabilityThreshold || latestNum < boundStartBlock {
return errors.New("current chain is too short, less than BoundStartBlock")
}

start := time.Now()
target := latestNum - params.FullImmutabilityThreshold
log.Info("start export segment", "from", boundStartBlock, "to", target, "boundStartBlock", boundStartBlock,
"historySegmentLength", historySegmentLength, "chainCfg", chainConfig)
segments := []params.HistorySegment{
{
Index: 0,
ReGenesisNumber: 0,
ReGenesisHash: genesisHash,
TD: td.Uint64(),
},
}
// try find finalized block in every segment boundary
for num := boundStartBlock; num <= target; num += historySegmentLength {
// align the segment start at parlia's epoch
0xbundler marked this conversation as resolved.
Show resolved Hide resolved
if chainConfig.Parlia != nil {
num -= num % chainConfig.Parlia.Epoch
}
var fs, ft *types.Header
for next := num + 1; next <= target; next++ {
fs = headerChain.GetHeaderByNumber(next)
ft = headerChain.GetFinalizedHeader(fs)
if ft == nil {
continue
}
if ft.Number.Uint64() >= num {
break
}
}
if ft == nil || fs == nil {
// if there cannot found any finalized block, just skip
break
}
if ft.Number.Uint64() < num {
// cannot find expect finality blocks, just skip
break
}
log.Info("found segment boundary", "startAt", ft.Number, "FinalityAt", fs.Number)
segment := params.HistorySegment{
Index: uint64(len(segments)),
ReGenesisNumber: ft.Number.Uint64(),
ReGenesisHash: ft.Hash(),
}
segment.TD = headerChain.GetTd(segment.ReGenesisHash, segment.ReGenesisNumber).Uint64()
// if using posa consensus, just get snapshot as consensus data
if p, ok := engine.(consensus.PoSA); ok {
parent := headerChain.GetHeader(ft.ParentHash, ft.Number.Uint64()-1)
enc, err := p.GetConsensusData(headerChain, parent)
if err != nil {
return err
}
segment.ConsensusData = hexutil.Encode(enc)
}
segments = append(segments, segment)
}
if err = params.ValidateHistorySegments(params.NewHistoryBlock(0, genesisHash, td.Uint64()), segments); err != nil {
return err
}
output, err := json.MarshalIndent(segments, "", " ")
if err != nil {
return err
}
log.Info("Generate History Segment done", "count", len(segments), "elapsed", common.PrettyDuration(time.Since(start)))

out := ctx.String(utils.HistorySegOutputFlag.Name)
outFile, err := os.OpenFile(out, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer outFile.Close()
_, err = outFile.Write(output)
if err != nil {
return err
}
log.Info("write history segment success", "path", out)
return nil
}

func pruneHistorySegments(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()

db := utils.MakeChainDatabase(ctx, stack, false, false)
defer db.Close()

genesisHash := rawdb.ReadCanonicalHash(db, 0)
td := rawdb.ReadTd(db, genesisHash, 0)
_, _, headerChain, err := simpleHeaderChain(db, genesisHash)
if err != nil {
return err
}
cfg := &params.HistorySegmentConfig{
CustomPath: "",
Genesis: params.NewHistoryBlock(0, genesisHash, td.Uint64()),
}
if ctx.IsSet(utils.HistorySegCustomFlag.Name) {
cfg.CustomPath = ctx.String(utils.HistorySegCustomFlag.Name)
}
hsm, err := params.NewHistorySegmentManager(cfg)
if err != nil {
return err
}

// get latest 2 segments
latestHeader := headerChain.CurrentHeader()
curSegment := hsm.CurSegment(latestHeader.Number.Uint64())
prevSegment, ok := hsm.PrevSegment(curSegment)
if !ok {
return fmt.Errorf("there is no enough history to prune, cur: %v", &curSegment)
}

// check segment if match hard code
if err = rawdb.AvailableHistorySegment(db, curSegment, prevSegment); err != nil {
return err
}

pruneTail := prevSegment.ReGenesisNumber
log.Info("The older history will be pruned", "prevSegment", prevSegment, "curSegment", curSegment, "pruneTail", pruneTail)
if err = rawdb.PruneTxLookupToTail(db, pruneTail); err != nil {
return err
}

start := time.Now()
old, err := db.TruncateTail(pruneTail)
if err != nil {
return err
}
log.Info("TruncateTail in freezerDB", "old", old, "now", pruneTail, "elapsed", common.PrettyDuration(time.Since(start)))
return nil
}

// hashish returns true for strings that look like hashes.
func hashish(x string) bool {
_, err := strconv.Atoi(x)
return err != nil
}

func simpleHeaderChain(db ethdb.Database, genesisHash common.Hash) (*params.ChainConfig, consensus.Engine, *core.HeaderChain, error) {
chainConfig := rawdb.ReadChainConfig(db, genesisHash)
if chainConfig == nil {
return nil, nil, nil, errors.New("failed to load chainConfig")
}
engine, err := ethconfig.CreateConsensusEngine(chainConfig, db, nil, genesisHash)
if err != nil {
return nil, nil, nil, err
}
headerChain, err := core.NewHeaderChain(db, chainConfig, engine, func() bool {
return true
})
if err != nil {
return nil, nil, nil, err
}
return chainConfig, engine, headerChain, nil
}
2 changes: 1 addition & 1 deletion cmd/geth/dbcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ func ancientInspect(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()

db := utils.MakeChainDatabase(ctx, stack, true, true)
db := utils.MakeChainDatabase(ctx, stack, true, false)
defer db.Close()
return rawdb.AncientInspect(db)
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ var (
utils.BLSPasswordFileFlag,
utils.BLSWalletDirFlag,
utils.VoteJournalDirFlag,
utils.HistorySegEnabledFlag,
utils.HistorySegCustomFlag,
}, utils.NetworkFlags, utils.DatabasePathFlags)

rpcFlags = []cli.Flag{
Expand Down Expand Up @@ -261,6 +263,8 @@ func init() {
blsCommand,
// See verkle.go
verkleCommand,
exportSegmentCommand,
pruneHistorySegmentsCommand,
}
sort.Sort(cli.CommandsByName(app.Commands))

Expand Down
40 changes: 40 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,38 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
Usage: "Path for the voteJournal dir in fast finality feature (default = inside the datadir)",
Category: flags.FastFinalityCategory,
}

// History segment
HistorySegEnabledFlag = &cli.BoolFlag{
Name: "history-segment",
Usage: "Enable history segment feature, it will auto prune history segments by hard-code segment hash",
Value: false,
Category: flags.BlockHistoryCategory,
}
HistorySegCustomFlag = &cli.StringFlag{
Name: "history-segment.custom",
Usage: "Specific history segments custom definition",
Value: "./history_segments.json",
Category: flags.BlockHistoryCategory,
}
HistorySegOutputFlag = &cli.StringFlag{
Name: "history-segment.output",
Usage: "Specific history segments output file",
Value: "./history_segments.json",
Category: flags.BlockHistoryCategory,
}
BoundStartBlockFlag = &cli.Uint64Flag{
Name: "history-segment.boundstart",
Usage: "Specific history segments BoundStartBlock, it indicate segment1 start block",
Value: params.BoundStartBlock,
Category: flags.BlockHistoryCategory,
}
HistorySegmentLengthFlag = &cli.Uint64Flag{
Name: "history-segment.segmentlen",
Usage: "Specific history segments HistorySegmentLength",
Value: params.HistorySegmentLength,
Category: flags.BlockHistoryCategory,
}
)

var (
Expand Down Expand Up @@ -2148,6 +2180,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if err := kzg4844.UseCKZG(ctx.String(CryptoKZGFlag.Name) == "ckzg"); err != nil {
Fatalf("Failed to set KZG library implementation to %s: %v", ctx.String(CryptoKZGFlag.Name), err)
}

// parse History Segment flags
if ctx.IsSet(HistorySegEnabledFlag.Name) {
cfg.HistorySegmentEnabled = ctx.Bool(HistorySegEnabledFlag.Name)
}
if ctx.IsSet(HistorySegCustomFlag.Name) {
cfg.HistorySegmentCustomFile = ctx.String(HistorySegCustomFlag.Name)
}
}

// SetDNSDiscoveryDefaults configures DNS discovery with the given URL if
Expand Down
2 changes: 2 additions & 0 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,6 @@ type PoSA interface {
GetFinalizedHeader(chain ChainHeaderReader, header *types.Header) *types.Header
VerifyVote(chain ChainHeaderReader, vote *types.VoteEnvelope) error
IsActiveValidatorAt(chain ChainHeaderReader, header *types.Header, checkVoteKeyFn func(bLSPublicKey *types.BLSPublicKey) bool) bool
GetConsensusData(chain ChainHeaderReader, header *types.Header) ([]byte, error)
SetupHistorySegment(hsm *params.HistorySegmentManager)
}
Loading