mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 21:38:05 -05:00
Compare commits
10 Commits
bal-devnet
...
flag-sync-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18cced70ec | ||
|
|
196e457450 | ||
|
|
00f441e7e2 | ||
|
|
6f7e7f5885 | ||
|
|
bb666833c5 | ||
|
|
334eb40576 | ||
|
|
097605b45d | ||
|
|
8f68e224d9 | ||
|
|
9b2ee0f720 | ||
|
|
dcf9379dd2 |
@@ -86,7 +86,9 @@ go_test(
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/builder:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/filesystem:go_default_library",
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/execution:go_default_library",
|
||||
"//beacon-chain/execution/testing:go_default_library",
|
||||
"//beacon-chain/monitor:go_default_library",
|
||||
@@ -99,6 +101,7 @@ go_test(
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
|
||||
@@ -550,6 +550,11 @@ func (b *BeaconNode) startDB(cliCtx *cli.Context, depositAddress string) error {
|
||||
return errors.Wrap(err, "could not ensure embedded genesis")
|
||||
}
|
||||
|
||||
// Validate sync options when starting with an empty database
|
||||
if err := b.validateSyncFlags(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.CheckpointInitializer != nil {
|
||||
log.Info("Checkpoint sync - Downloading origin state and block")
|
||||
if err := b.CheckpointInitializer.Initialize(b.ctx, b.db); err != nil {
|
||||
@@ -564,6 +569,52 @@ func (b *BeaconNode) startDB(cliCtx *cli.Context, depositAddress string) error {
|
||||
log.WithField("address", depositAddress).Info("Deposit contract")
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSyncFlags ensures that when starting with an empty database,
|
||||
// the user has explicitly chosen either genesis sync or checkpoint sync.
|
||||
func (b *BeaconNode) validateSyncFlags() error {
|
||||
// Check if database has an origin checkpoint (indicating it's not empty)
|
||||
_, err := b.db.OriginCheckpointBlockRoot(b.ctx)
|
||||
if err == nil {
|
||||
// Database is not empty, validation is not needed
|
||||
return nil
|
||||
}
|
||||
if !errors.Is(err, db.ErrNotFoundOriginBlockRoot) {
|
||||
// Some other error occurred
|
||||
return errors.Wrap(err, "could not check origin checkpoint block root")
|
||||
}
|
||||
|
||||
// if genesis exists, also consider DB non-empty.
|
||||
if gb, err := b.db.GenesisBlock(b.ctx); err == nil && gb != nil && !gb.IsNil() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Database is empty, check if user has provided required flags
|
||||
syncFromGenesis := b.cliCtx.Bool(flags.SyncFromGenesis.Name)
|
||||
hasCheckpointSync := b.CheckpointInitializer != nil
|
||||
|
||||
if !syncFromGenesis && !hasCheckpointSync {
|
||||
return errors.New("when starting with an empty database, you must specify either:\n" +
|
||||
" --sync-from-genesis (to sync from genesis)\n" +
|
||||
" --checkpoint-sync-url <url> (to sync from a remote beacon node)\n" +
|
||||
" --checkpoint-state <path> and --checkpoint-block <path> (to sync from local files)\n\n" +
|
||||
"Checkpoint sync is recommended for faster syncing.")
|
||||
}
|
||||
|
||||
// Check for conflicting sync options
|
||||
if syncFromGenesis && hasCheckpointSync {
|
||||
return errors.New("conflicting sync options: cannot use both --sync-from-genesis and checkpoint sync flags. " +
|
||||
"Please choose either genesis sync or checkpoint sync, not both.")
|
||||
}
|
||||
|
||||
if syncFromGenesis {
|
||||
log.Warn("Syncing from genesis is enabled. This will take a very long time and is not recommended. " +
|
||||
"Consider using checkpoint sync instead with --checkpoint-sync-url.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BeaconNode) startSlasherDB(cliCtx *cli.Context, clearer *dbClearer) error {
|
||||
if !b.slasherEnabled {
|
||||
return nil
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -14,15 +15,19 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v6/beacon-chain/blockchain"
|
||||
"github.com/OffchainLabs/prysm/v6/beacon-chain/builder"
|
||||
statefeed "github.com/OffchainLabs/prysm/v6/beacon-chain/core/feed/state"
|
||||
"github.com/OffchainLabs/prysm/v6/beacon-chain/db"
|
||||
"github.com/OffchainLabs/prysm/v6/beacon-chain/db/filesystem"
|
||||
testDB "github.com/OffchainLabs/prysm/v6/beacon-chain/db/testing"
|
||||
"github.com/OffchainLabs/prysm/v6/beacon-chain/execution"
|
||||
mockExecution "github.com/OffchainLabs/prysm/v6/beacon-chain/execution/testing"
|
||||
"github.com/OffchainLabs/prysm/v6/beacon-chain/monitor"
|
||||
"github.com/OffchainLabs/prysm/v6/cmd"
|
||||
"github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/flags"
|
||||
"github.com/OffchainLabs/prysm/v6/config/features"
|
||||
"github.com/OffchainLabs/prysm/v6/runtime"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/require"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@@ -49,6 +54,7 @@ func TestNodeClose_OK(t *testing.T) {
|
||||
set.Bool("demo-config", true, "demo configuration")
|
||||
set.String("deposit-contract", "0x0000000000000000000000000000000000000000", "deposit contract address")
|
||||
set.String("suggested-fee-recipient", "0x6e35733c5af9B61374A128e6F85f553aF09ff89A", "fee recipient")
|
||||
set.Bool("sync-from-genesis", true, "sync from genesis")
|
||||
require.NoError(t, set.Set("suggested-fee-recipient", "0x6e35733c5af9B61374A128e6F85f553aF09ff89A"))
|
||||
cmd.ValidatorMonitorIndicesFlag.Value = &cli.IntSlice{}
|
||||
cmd.ValidatorMonitorIndicesFlag.Value.SetInt(1)
|
||||
@@ -74,6 +80,7 @@ func TestNodeStart_Ok(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.String("datadir", tmp, "node data directory")
|
||||
set.String("suggested-fee-recipient", "0x6e35733c5af9B61374A128e6F85f553aF09ff89A", "fee recipient")
|
||||
set.Bool("sync-from-genesis", true, "sync from genesis")
|
||||
require.NoError(t, set.Set("suggested-fee-recipient", "0x6e35733c5af9B61374A128e6F85f553aF09ff89A"))
|
||||
|
||||
ctx, cancel := newCliContextWithCancel(&app, set)
|
||||
@@ -104,6 +111,7 @@ func TestNodeStart_SyncChecker(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.String("datadir", tmp, "node data directory")
|
||||
set.String("suggested-fee-recipient", "0x6e35733c5af9B61374A128e6F85f553aF09ff89A", "fee recipient")
|
||||
set.Bool("sync-from-genesis", true, "sync from genesis")
|
||||
require.NoError(t, set.Set("suggested-fee-recipient", "0x6e35733c5af9B61374A128e6F85f553aF09ff89A"))
|
||||
|
||||
ctx, cancel := newCliContextWithCancel(&app, set)
|
||||
@@ -143,6 +151,7 @@ func TestClearDB(t *testing.T) {
|
||||
set.String("datadir", tmp, "node data directory")
|
||||
set.Bool(cmd.ForceClearDB.Name, true, "force clear db")
|
||||
set.String("suggested-fee-recipient", "0x6e35733c5af9B61374A128e6F85f553aF09ff89A", "fee recipient")
|
||||
set.Bool("sync-from-genesis", true, "sync from genesis")
|
||||
require.NoError(t, set.Set("suggested-fee-recipient", "0x6e35733c5af9B61374A128e6F85f553aF09ff89A"))
|
||||
context, cancel := newCliContextWithCancel(&app, set)
|
||||
|
||||
@@ -262,3 +271,128 @@ func TestCORS(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateSyncFlags tests the validateSyncFlags function with real database instances
|
||||
func TestValidateSyncFlags(t *testing.T) {
|
||||
tests := []struct {
|
||||
expectWarning bool
|
||||
expectError bool
|
||||
hasCheckpointInitializer bool
|
||||
syncFromGenesis bool
|
||||
dbHasOriginCheckpoint bool
|
||||
expectedErrorContains string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "Database not empty - validation skipped",
|
||||
dbHasOriginCheckpoint: true,
|
||||
syncFromGenesis: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Empty DB, no sync flags - should fail",
|
||||
dbHasOriginCheckpoint: false,
|
||||
syncFromGenesis: false,
|
||||
expectError: true,
|
||||
expectedErrorContains: "when starting with an empty database, you must specify either",
|
||||
},
|
||||
{
|
||||
name: "Empty DB, sync from genesis - should succeed with warning",
|
||||
dbHasOriginCheckpoint: false,
|
||||
syncFromGenesis: true,
|
||||
expectError: false,
|
||||
expectWarning: true,
|
||||
},
|
||||
{
|
||||
name: "Empty DB, checkpoint sync - should succeed",
|
||||
dbHasOriginCheckpoint: false,
|
||||
hasCheckpointInitializer: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Empty DB, conflicting sync options - should fail",
|
||||
dbHasOriginCheckpoint: false,
|
||||
syncFromGenesis: true,
|
||||
hasCheckpointInitializer: true,
|
||||
expectError: true,
|
||||
expectedErrorContains: "conflicting sync options",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Isolate Prometheus metrics per subtest to avoid duplicate registration across DB setups.
|
||||
reg := prometheus.NewRegistry()
|
||||
prometheus.DefaultRegisterer = reg
|
||||
prometheus.DefaultGatherer = reg
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Set up real database for testing (empty to start).
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
|
||||
|
||||
// Populate database if needed (simulate "non-empty" via origin checkpoint).
|
||||
if tt.dbHasOriginCheckpoint {
|
||||
err := beaconDB.SaveOriginCheckpointBlockRoot(ctx, [32]byte{0x01})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Set up CLI flags
|
||||
flagSet := flag.NewFlagSet("test", flag.ContinueOnError)
|
||||
flagSet.Bool(flags.SyncFromGenesis.Name, tt.syncFromGenesis, "")
|
||||
|
||||
app := cli.App{}
|
||||
cliCtx := cli.NewContext(&app, flagSet, nil)
|
||||
|
||||
// Create BeaconNode with test setup
|
||||
beaconNode := &BeaconNode{
|
||||
ctx: ctx,
|
||||
db: beaconDB,
|
||||
cliCtx: cliCtx,
|
||||
}
|
||||
|
||||
// Set CheckpointInitializer if needed
|
||||
if tt.hasCheckpointInitializer {
|
||||
beaconNode.CheckpointInitializer = &mockCheckpointInitializer{}
|
||||
}
|
||||
|
||||
// Capture log output for warning detection
|
||||
hook := logTest.NewGlobal()
|
||||
defer hook.Reset()
|
||||
|
||||
// Call the function under test
|
||||
err := beaconNode.validateSyncFlags()
|
||||
|
||||
// Validate results
|
||||
if tt.expectError {
|
||||
require.NotNil(t, err)
|
||||
if tt.expectedErrorContains != "" {
|
||||
require.ErrorContains(t, tt.expectedErrorContains, err)
|
||||
}
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Check for warning log if expected
|
||||
if tt.expectWarning {
|
||||
found := false
|
||||
for _, entry := range hook.Entries {
|
||||
if entry.Level.String() == "warning" &&
|
||||
strings.Contains(entry.Message, "Syncing from genesis is enabled") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.Equal(t, true, found, "Expected warning log about genesis sync")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// mockCheckpointInitializer is a simple mock for testing
|
||||
type mockCheckpointInitializer struct{}
|
||||
|
||||
func (m *mockCheckpointInitializer) Initialize(ctx context.Context, db db.Database) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
3
changelog/satushh-dont-sync-from-genesis-bydefault.md
Normal file
3
changelog/satushh-dont-sync-from-genesis-bydefault.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
- A new flag to ensure you have to specify whether sync from genesis or checkpoint in case of empty DB.
|
||||
- Addressing this issue: https://github.com/OffchainLabs/prysm/issues/13020
|
||||
@@ -271,6 +271,13 @@ var (
|
||||
"If such a sync is not possible, the node will treat it as a critical and irrecoverable failure",
|
||||
Value: "",
|
||||
}
|
||||
// SyncFromGenesis enables syncing from genesis when starting with an empty database.
|
||||
SyncFromGenesis = &cli.BoolFlag{
|
||||
Name: "sync-from-genesis",
|
||||
Usage: "Explicitly enables syncing from genesis when starting with an empty database. " +
|
||||
"Alternately you can checkpoint sync instead with " +
|
||||
"--checkpoint-sync-url, --checkpoint-state, or --checkpoint-block flags.",
|
||||
}
|
||||
// MinPeersPerSubnet defines a flag to set the minimum number of peers that a node will attempt to peer with for a subnet.
|
||||
MinPeersPerSubnet = &cli.Uint64Flag{
|
||||
Name: "minimum-peers-per-subnet",
|
||||
|
||||
@@ -69,6 +69,7 @@ var appFlags = []cli.Flag{
|
||||
flags.ChainID,
|
||||
flags.NetworkID,
|
||||
flags.WeakSubjectivityCheckpoint,
|
||||
flags.SyncFromGenesis,
|
||||
flags.Eth1HeaderReqLimit,
|
||||
flags.MinPeersPerSubnet,
|
||||
flags.MaxConcurrentDials,
|
||||
|
||||
@@ -148,6 +148,7 @@ var appHelpFlagGroups = []flagGroup{
|
||||
checkpoint.BlockPath,
|
||||
checkpoint.RemoteURL,
|
||||
checkpoint.StatePath,
|
||||
flags.SyncFromGenesis,
|
||||
flags.WeakSubjectivityCheckpoint,
|
||||
genesis.BeaconAPIURL,
|
||||
genesis.StatePath,
|
||||
|
||||
Reference in New Issue
Block a user