Skip to content

Commit 9f83e9e

Browse files
authored
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.
1 parent 553183e commit 9f83e9e

File tree

6 files changed

+78
-7
lines changed

6 files changed

+78
-7
lines changed

beacon/blsync/client.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ import (
2323
"github.com/ethereum/go-ethereum/beacon/light/sync"
2424
"github.com/ethereum/go-ethereum/beacon/params"
2525
"github.com/ethereum/go-ethereum/beacon/types"
26+
"github.com/ethereum/go-ethereum/common"
2627
"github.com/ethereum/go-ethereum/common/mclock"
2728
"github.com/ethereum/go-ethereum/ethdb/memorydb"
2829
"github.com/ethereum/go-ethereum/event"
30+
"github.com/ethereum/go-ethereum/log"
2931
"github.com/ethereum/go-ethereum/rpc"
3032
)
3133

@@ -46,7 +48,13 @@ func NewClient(config params.ClientConfig) *Client {
4648
var (
4749
db = memorydb.New()
4850
committeeChain = light.NewCommitteeChain(db, &config.ChainConfig, config.Threshold, !config.NoFilter)
49-
headTracker = light.NewHeadTracker(committeeChain, config.Threshold)
51+
headTracker = light.NewHeadTracker(committeeChain, config.Threshold, func(checkpoint common.Hash) {
52+
if saved, err := config.SaveCheckpointToFile(checkpoint); saved {
53+
log.Debug("Saved beacon checkpoint", "file", config.CheckpointFile, "checkpoint", checkpoint)
54+
} else if err != nil {
55+
log.Error("Failed to save beacon checkpoint", "file", config.CheckpointFile, "checkpoint", checkpoint, "error", err)
56+
}
57+
})
5058
)
5159
headSync := sync.NewHeadSync(headTracker, committeeChain)
5260

beacon/light/head_tracker.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
"sync"
2222
"time"
2323

24+
"github.com/ethereum/go-ethereum/beacon/params"
2425
"github.com/ethereum/go-ethereum/beacon/types"
26+
"github.com/ethereum/go-ethereum/common"
2527
"github.com/ethereum/go-ethereum/log"
2628
)
2729

@@ -38,13 +40,15 @@ type HeadTracker struct {
3840
hasFinalityUpdate bool
3941
prefetchHead types.HeadInfo
4042
changeCounter uint64
43+
saveCheckpoint func(common.Hash)
4144
}
4245

4346
// NewHeadTracker creates a new HeadTracker.
44-
func NewHeadTracker(committeeChain *CommitteeChain, minSignerCount int) *HeadTracker {
47+
func NewHeadTracker(committeeChain *CommitteeChain, minSignerCount int, saveCheckpoint func(common.Hash)) *HeadTracker {
4548
return &HeadTracker{
4649
committeeChain: committeeChain,
4750
minSignerCount: minSignerCount,
51+
saveCheckpoint: saveCheckpoint,
4852
}
4953
}
5054

@@ -100,6 +104,9 @@ func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error
100104
if replace {
101105
h.finalityUpdate, h.hasFinalityUpdate = update, true
102106
h.changeCounter++
107+
if h.saveCheckpoint != nil && update.Finalized.Slot%params.EpochLength == 0 {
108+
h.saveCheckpoint(update.Finalized.Hash())
109+
}
103110
}
104111
return replace, err
105112
}

beacon/params/config.go

+34
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type ChainConfig struct {
5454
GenesisValidatorsRoot common.Hash // Root hash of the genesis validator set, used for signature domain calculation
5555
Forks Forks
5656
Checkpoint common.Hash
57+
CheckpointFile string
5758
}
5859

5960
// ForkAtEpoch returns the latest active fork at the given epoch.
@@ -211,3 +212,36 @@ func (f Forks) Less(i, j int) bool {
211212
}
212213
return f[i].knownIndex < f[j].knownIndex
213214
}
215+
216+
// SetCheckpointFile sets the checkpoint import/export file name and attempts to
217+
// read the checkpoint from the file if it already exists. It returns true if
218+
// a checkpoint has been loaded.
219+
func (c *ChainConfig) SetCheckpointFile(checkpointFile string) (bool, error) {
220+
c.CheckpointFile = checkpointFile
221+
file, err := os.ReadFile(checkpointFile)
222+
if os.IsNotExist(err) {
223+
return false, nil // did not load checkpoint
224+
}
225+
if err != nil {
226+
return false, fmt.Errorf("failed to read beacon checkpoint file: %v", err)
227+
}
228+
cp, err := hexutil.Decode(string(file))
229+
if err != nil {
230+
return false, fmt.Errorf("failed to decode hex string in beacon checkpoint file: %v", err)
231+
}
232+
if len(cp) != 32 {
233+
return false, fmt.Errorf("invalid hex string length in beacon checkpoint file: %d", len(cp))
234+
}
235+
copy(c.Checkpoint[:len(cp)], cp)
236+
return true, nil
237+
}
238+
239+
// SaveCheckpointToFile saves the given checkpoint to file if a checkpoint
240+
// import/export file has been specified.
241+
func (c *ChainConfig) SaveCheckpointToFile(checkpoint common.Hash) (bool, error) {
242+
if c.CheckpointFile == "" {
243+
return false, nil
244+
}
245+
err := os.WriteFile(c.CheckpointFile, []byte(checkpoint.Hex()), 0600)
246+
return err == nil, err
247+
}

cmd/blsync/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func main() {
4343
utils.BeaconGenesisRootFlag,
4444
utils.BeaconGenesisTimeFlag,
4545
utils.BeaconCheckpointFlag,
46+
utils.BeaconCheckpointFileFlag,
4647
//TODO datadir for optional permanent database
4748
utils.MainnetFlag,
4849
utils.SepoliaFlag,

cmd/geth/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ var (
157157
utils.BeaconGenesisRootFlag,
158158
utils.BeaconGenesisTimeFlag,
159159
utils.BeaconCheckpointFlag,
160+
utils.BeaconCheckpointFileFlag,
160161
}, utils.NetworkFlags, utils.DatabaseFlags)
161162

162163
rpcFlags = []cli.Flag{

cmd/utils/flags.go

+25-5
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,11 @@ var (
342342
Usage: "Beacon chain weak subjectivity checkpoint block hash",
343343
Category: flags.BeaconCategory,
344344
}
345+
BeaconCheckpointFileFlag = &cli.StringFlag{
346+
Name: "beacon.checkpoint.file",
347+
Usage: "Beacon chain weak subjectivity checkpoint import/export file",
348+
Category: flags.BeaconCategory,
349+
}
345350
BlsyncApiFlag = &cli.StringFlag{
346351
Name: "blsync.engine.api",
347352
Usage: "Target EL engine API URL",
@@ -1890,7 +1895,7 @@ func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig {
18901895
if !ctx.IsSet(BeaconGenesisTimeFlag.Name) {
18911896
Fatalf("Custom beacon chain config is specified but genesis time is missing")
18921897
}
1893-
if !ctx.IsSet(BeaconCheckpointFlag.Name) {
1898+
if !ctx.IsSet(BeaconCheckpointFlag.Name) && !ctx.IsSet(BeaconCheckpointFileFlag.Name) {
18941899
Fatalf("Custom beacon chain config is specified but checkpoint is missing")
18951900
}
18961901
config.ChainConfig = bparams.ChainConfig{
@@ -1915,12 +1920,27 @@ func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig {
19151920
}
19161921
}
19171922
// Checkpoint is required with custom chain config and is optional with pre-defined config
1923+
// If both checkpoint block hash and checkpoint file are specified then the
1924+
// client is initialized with the specified block hash and new checkpoints
1925+
// are saved to the specified file.
1926+
if ctx.IsSet(BeaconCheckpointFileFlag.Name) {
1927+
if _, err := config.SetCheckpointFile(ctx.String(BeaconCheckpointFileFlag.Name)); err != nil {
1928+
Fatalf("Could not load beacon checkpoint file", "beacon.checkpoint.file", ctx.String(BeaconCheckpointFileFlag.Name), "error", err)
1929+
}
1930+
}
19181931
if ctx.IsSet(BeaconCheckpointFlag.Name) {
1919-
if c, err := hexutil.Decode(ctx.String(BeaconCheckpointFlag.Name)); err == nil && len(c) <= 32 {
1920-
copy(config.Checkpoint[:len(c)], c)
1921-
} else {
1922-
Fatalf("Invalid hex string", "beacon.checkpoint", ctx.String(BeaconCheckpointFlag.Name), "error", err)
1932+
hex := ctx.String(BeaconCheckpointFlag.Name)
1933+
c, err := hexutil.Decode(hex)
1934+
if err != nil {
1935+
Fatalf("Invalid hex string", "beacon.checkpoint", hex, "error", err)
19231936
}
1937+
if len(c) != 32 {
1938+
Fatalf("Invalid hex string length", "beacon.checkpoint", hex, "length", len(c))
1939+
}
1940+
copy(config.Checkpoint[:len(c)], c)
1941+
}
1942+
if config.Checkpoint == (common.Hash{}) {
1943+
Fatalf("Beacon checkpoint not specified")
19241944
}
19251945
config.Apis = ctx.StringSlice(BeaconApiFlag.Name)
19261946
if config.Apis == nil {

0 commit comments

Comments
 (0)