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:
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/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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ func main() {
|
|||
utils.BeaconGenesisRootFlag,
|
||||
utils.BeaconGenesisTimeFlag,
|
||||
utils.BeaconCheckpointFlag,
|
||||
utils.BeaconCheckpointFileFlag,
|
||||
//TODO datadir for optional permanent database
|
||||
utils.MainnetFlag,
|
||||
utils.SepoliaFlag,
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ var (
|
|||
utils.BeaconGenesisRootFlag,
|
||||
utils.BeaconGenesisTimeFlag,
|
||||
utils.BeaconCheckpointFlag,
|
||||
utils.BeaconCheckpointFileFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags)
|
||||
|
||||
rpcFlags = []cli.Flag{
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in a new issue