1
0
Fork 0
forked from forks/go-ethereum

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:
Felföldi Zsolt 2025-04-03 16:04:11 +02:00 committed by GitHub
parent 553183e5de
commit 9f83e9e673
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 79 additions and 8 deletions

View file

@ -23,9 +23,11 @@ import (
"github.com/ethereum/go-ethereum/beacon/light/sync"
"github.com/ethereum/go-ethereum/beacon/params"
"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/ethdb/memorydb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)
@ -46,7 +48,13 @@ func NewClient(config params.ClientConfig) *Client {
var (
db = memorydb.New()
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)

View file

@ -21,7 +21,9 @@ import (
"sync"
"time"
"github.com/ethereum/go-ethereum/beacon/params"
"github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
@ -38,13 +40,15 @@ type HeadTracker struct {
hasFinalityUpdate bool
prefetchHead types.HeadInfo
changeCounter uint64
saveCheckpoint func(common.Hash)
}
// 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{
committeeChain: committeeChain,
minSignerCount: minSignerCount,
saveCheckpoint: saveCheckpoint,
}
}
@ -100,6 +104,9 @@ func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error
if replace {
h.finalityUpdate, h.hasFinalityUpdate = update, true
h.changeCounter++
if h.saveCheckpoint != nil && update.Finalized.Slot%params.EpochLength == 0 {
h.saveCheckpoint(update.Finalized.Hash())
}
}
return replace, err
}

View file

@ -54,6 +54,7 @@ type ChainConfig struct {
GenesisValidatorsRoot common.Hash // Root hash of the genesis validator set, used for signature domain calculation
Forks Forks
Checkpoint common.Hash
CheckpointFile string
}
// 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
}
// 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
}

View file

@ -43,6 +43,7 @@ func main() {
utils.BeaconGenesisRootFlag,
utils.BeaconGenesisTimeFlag,
utils.BeaconCheckpointFlag,
utils.BeaconCheckpointFileFlag,
//TODO datadir for optional permanent database
utils.MainnetFlag,
utils.SepoliaFlag,

View file

@ -157,6 +157,7 @@ var (
utils.BeaconGenesisRootFlag,
utils.BeaconGenesisTimeFlag,
utils.BeaconCheckpointFlag,
utils.BeaconCheckpointFileFlag,
}, utils.NetworkFlags, utils.DatabaseFlags)
rpcFlags = []cli.Flag{

View file

@ -342,6 +342,11 @@ var (
Usage: "Beacon chain weak subjectivity checkpoint block hash",
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{
Name: "blsync.engine.api",
Usage: "Target EL engine API URL",
@ -1890,7 +1895,7 @@ func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig {
if !ctx.IsSet(BeaconGenesisTimeFlag.Name) {
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")
}
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
if ctx.IsSet(BeaconCheckpointFlag.Name) {
if c, err := hexutil.Decode(ctx.String(BeaconCheckpointFlag.Name)); err == nil && len(c) <= 32 {
copy(config.Checkpoint[:len(c)], c)
} else {
Fatalf("Invalid hex string", "beacon.checkpoint", ctx.String(BeaconCheckpointFlag.Name), "error", err)
// If both checkpoint block hash and checkpoint file are specified then the
// client is initialized with the specified block hash and new checkpoints
// are saved to the specified file.
if ctx.IsSet(BeaconCheckpointFileFlag.Name) {
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)
if config.Apis == nil {
Fatalf("Beacon node light client API URL not specified")