mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
beacon/blsync: add checkpoint import/export file feature (#31469)
This PR adds a new `--beacon.checkpoint.file` config flag to geth and blsync which specifies a checkpoint import/export file. If a file with an existing checkpoint is specified, it is used for initialization instead of the hardcoded one (except when `--beacon.checkpoint` is also specified simultaneously). Whenever the client encounters a new valid finality update with a suitable finalized beacon block root at an epoch boundary, it saves the block root in hex format to the checkpoint file.
This commit is contained in:
parent
553183e5de
commit
9f83e9e673
6 changed files with 79 additions and 8 deletions
|
|
@ -23,9 +23,11 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/beacon/light/sync"
|
"github.com/ethereum/go-ethereum/beacon/light/sync"
|
||||||
"github.com/ethereum/go-ethereum/beacon/params"
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
"github.com/ethereum/go-ethereum/beacon/types"
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -46,7 +48,13 @@ func NewClient(config params.ClientConfig) *Client {
|
||||||
var (
|
var (
|
||||||
db = memorydb.New()
|
db = memorydb.New()
|
||||||
committeeChain = light.NewCommitteeChain(db, &config.ChainConfig, config.Threshold, !config.NoFilter)
|
committeeChain = light.NewCommitteeChain(db, &config.ChainConfig, config.Threshold, !config.NoFilter)
|
||||||
headTracker = light.NewHeadTracker(committeeChain, config.Threshold)
|
headTracker = light.NewHeadTracker(committeeChain, config.Threshold, func(checkpoint common.Hash) {
|
||||||
|
if saved, err := config.SaveCheckpointToFile(checkpoint); saved {
|
||||||
|
log.Debug("Saved beacon checkpoint", "file", config.CheckpointFile, "checkpoint", checkpoint)
|
||||||
|
} else if err != nil {
|
||||||
|
log.Error("Failed to save beacon checkpoint", "file", config.CheckpointFile, "checkpoint", checkpoint, "error", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
)
|
)
|
||||||
headSync := sync.NewHeadSync(headTracker, committeeChain)
|
headSync := sync.NewHeadSync(headTracker, committeeChain)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
"github.com/ethereum/go-ethereum/beacon/types"
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -38,13 +40,15 @@ type HeadTracker struct {
|
||||||
hasFinalityUpdate bool
|
hasFinalityUpdate bool
|
||||||
prefetchHead types.HeadInfo
|
prefetchHead types.HeadInfo
|
||||||
changeCounter uint64
|
changeCounter uint64
|
||||||
|
saveCheckpoint func(common.Hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHeadTracker creates a new HeadTracker.
|
// NewHeadTracker creates a new HeadTracker.
|
||||||
func NewHeadTracker(committeeChain *CommitteeChain, minSignerCount int) *HeadTracker {
|
func NewHeadTracker(committeeChain *CommitteeChain, minSignerCount int, saveCheckpoint func(common.Hash)) *HeadTracker {
|
||||||
return &HeadTracker{
|
return &HeadTracker{
|
||||||
committeeChain: committeeChain,
|
committeeChain: committeeChain,
|
||||||
minSignerCount: minSignerCount,
|
minSignerCount: minSignerCount,
|
||||||
|
saveCheckpoint: saveCheckpoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,6 +104,9 @@ func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error
|
||||||
if replace {
|
if replace {
|
||||||
h.finalityUpdate, h.hasFinalityUpdate = update, true
|
h.finalityUpdate, h.hasFinalityUpdate = update, true
|
||||||
h.changeCounter++
|
h.changeCounter++
|
||||||
|
if h.saveCheckpoint != nil && update.Finalized.Slot%params.EpochLength == 0 {
|
||||||
|
h.saveCheckpoint(update.Finalized.Hash())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return replace, err
|
return replace, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ type ChainConfig struct {
|
||||||
GenesisValidatorsRoot common.Hash // Root hash of the genesis validator set, used for signature domain calculation
|
GenesisValidatorsRoot common.Hash // Root hash of the genesis validator set, used for signature domain calculation
|
||||||
Forks Forks
|
Forks Forks
|
||||||
Checkpoint common.Hash
|
Checkpoint common.Hash
|
||||||
|
CheckpointFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkAtEpoch returns the latest active fork at the given epoch.
|
// ForkAtEpoch returns the latest active fork at the given epoch.
|
||||||
|
|
@ -211,3 +212,36 @@ func (f Forks) Less(i, j int) bool {
|
||||||
}
|
}
|
||||||
return f[i].knownIndex < f[j].knownIndex
|
return f[i].knownIndex < f[j].knownIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCheckpointFile sets the checkpoint import/export file name and attempts to
|
||||||
|
// read the checkpoint from the file if it already exists. It returns true if
|
||||||
|
// a checkpoint has been loaded.
|
||||||
|
func (c *ChainConfig) SetCheckpointFile(checkpointFile string) (bool, error) {
|
||||||
|
c.CheckpointFile = checkpointFile
|
||||||
|
file, err := os.ReadFile(checkpointFile)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil // did not load checkpoint
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to read beacon checkpoint file: %v", err)
|
||||||
|
}
|
||||||
|
cp, err := hexutil.Decode(string(file))
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to decode hex string in beacon checkpoint file: %v", err)
|
||||||
|
}
|
||||||
|
if len(cp) != 32 {
|
||||||
|
return false, fmt.Errorf("invalid hex string length in beacon checkpoint file: %d", len(cp))
|
||||||
|
}
|
||||||
|
copy(c.Checkpoint[:len(cp)], cp)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveCheckpointToFile saves the given checkpoint to file if a checkpoint
|
||||||
|
// import/export file has been specified.
|
||||||
|
func (c *ChainConfig) SaveCheckpointToFile(checkpoint common.Hash) (bool, error) {
|
||||||
|
if c.CheckpointFile == "" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
err := os.WriteFile(c.CheckpointFile, []byte(checkpoint.Hex()), 0600)
|
||||||
|
return err == nil, err
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ func main() {
|
||||||
utils.BeaconGenesisRootFlag,
|
utils.BeaconGenesisRootFlag,
|
||||||
utils.BeaconGenesisTimeFlag,
|
utils.BeaconGenesisTimeFlag,
|
||||||
utils.BeaconCheckpointFlag,
|
utils.BeaconCheckpointFlag,
|
||||||
|
utils.BeaconCheckpointFileFlag,
|
||||||
//TODO datadir for optional permanent database
|
//TODO datadir for optional permanent database
|
||||||
utils.MainnetFlag,
|
utils.MainnetFlag,
|
||||||
utils.SepoliaFlag,
|
utils.SepoliaFlag,
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,7 @@ var (
|
||||||
utils.BeaconGenesisRootFlag,
|
utils.BeaconGenesisRootFlag,
|
||||||
utils.BeaconGenesisTimeFlag,
|
utils.BeaconGenesisTimeFlag,
|
||||||
utils.BeaconCheckpointFlag,
|
utils.BeaconCheckpointFlag,
|
||||||
|
utils.BeaconCheckpointFileFlag,
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags)
|
}, utils.NetworkFlags, utils.DatabaseFlags)
|
||||||
|
|
||||||
rpcFlags = []cli.Flag{
|
rpcFlags = []cli.Flag{
|
||||||
|
|
|
||||||
|
|
@ -342,6 +342,11 @@ var (
|
||||||
Usage: "Beacon chain weak subjectivity checkpoint block hash",
|
Usage: "Beacon chain weak subjectivity checkpoint block hash",
|
||||||
Category: flags.BeaconCategory,
|
Category: flags.BeaconCategory,
|
||||||
}
|
}
|
||||||
|
BeaconCheckpointFileFlag = &cli.StringFlag{
|
||||||
|
Name: "beacon.checkpoint.file",
|
||||||
|
Usage: "Beacon chain weak subjectivity checkpoint import/export file",
|
||||||
|
Category: flags.BeaconCategory,
|
||||||
|
}
|
||||||
BlsyncApiFlag = &cli.StringFlag{
|
BlsyncApiFlag = &cli.StringFlag{
|
||||||
Name: "blsync.engine.api",
|
Name: "blsync.engine.api",
|
||||||
Usage: "Target EL engine API URL",
|
Usage: "Target EL engine API URL",
|
||||||
|
|
@ -1890,7 +1895,7 @@ func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig {
|
||||||
if !ctx.IsSet(BeaconGenesisTimeFlag.Name) {
|
if !ctx.IsSet(BeaconGenesisTimeFlag.Name) {
|
||||||
Fatalf("Custom beacon chain config is specified but genesis time is missing")
|
Fatalf("Custom beacon chain config is specified but genesis time is missing")
|
||||||
}
|
}
|
||||||
if !ctx.IsSet(BeaconCheckpointFlag.Name) {
|
if !ctx.IsSet(BeaconCheckpointFlag.Name) && !ctx.IsSet(BeaconCheckpointFileFlag.Name) {
|
||||||
Fatalf("Custom beacon chain config is specified but checkpoint is missing")
|
Fatalf("Custom beacon chain config is specified but checkpoint is missing")
|
||||||
}
|
}
|
||||||
config.ChainConfig = bparams.ChainConfig{
|
config.ChainConfig = bparams.ChainConfig{
|
||||||
|
|
@ -1915,13 +1920,28 @@ func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Checkpoint is required with custom chain config and is optional with pre-defined config
|
// Checkpoint is required with custom chain config and is optional with pre-defined config
|
||||||
if ctx.IsSet(BeaconCheckpointFlag.Name) {
|
// If both checkpoint block hash and checkpoint file are specified then the
|
||||||
if c, err := hexutil.Decode(ctx.String(BeaconCheckpointFlag.Name)); err == nil && len(c) <= 32 {
|
// client is initialized with the specified block hash and new checkpoints
|
||||||
copy(config.Checkpoint[:len(c)], c)
|
// are saved to the specified file.
|
||||||
} else {
|
if ctx.IsSet(BeaconCheckpointFileFlag.Name) {
|
||||||
Fatalf("Invalid hex string", "beacon.checkpoint", ctx.String(BeaconCheckpointFlag.Name), "error", err)
|
if _, err := config.SetCheckpointFile(ctx.String(BeaconCheckpointFileFlag.Name)); err != nil {
|
||||||
|
Fatalf("Could not load beacon checkpoint file", "beacon.checkpoint.file", ctx.String(BeaconCheckpointFileFlag.Name), "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ctx.IsSet(BeaconCheckpointFlag.Name) {
|
||||||
|
hex := ctx.String(BeaconCheckpointFlag.Name)
|
||||||
|
c, err := hexutil.Decode(hex)
|
||||||
|
if err != nil {
|
||||||
|
Fatalf("Invalid hex string", "beacon.checkpoint", hex, "error", err)
|
||||||
|
}
|
||||||
|
if len(c) != 32 {
|
||||||
|
Fatalf("Invalid hex string length", "beacon.checkpoint", hex, "length", len(c))
|
||||||
|
}
|
||||||
|
copy(config.Checkpoint[:len(c)], c)
|
||||||
|
}
|
||||||
|
if config.Checkpoint == (common.Hash{}) {
|
||||||
|
Fatalf("Beacon checkpoint not specified")
|
||||||
|
}
|
||||||
config.Apis = ctx.StringSlice(BeaconApiFlag.Name)
|
config.Apis = ctx.StringSlice(BeaconApiFlag.Name)
|
||||||
if config.Apis == nil {
|
if config.Apis == nil {
|
||||||
Fatalf("Beacon node light client API URL not specified")
|
Fatalf("Beacon node light client API URL not specified")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue