diff --git a/beacon-chain/blockchain/BUILD.bazel b/beacon-chain/blockchain/BUILD.bazel index edb62b52b7..a520194c97 100644 --- a/beacon-chain/blockchain/BUILD.bazel +++ b/beacon-chain/blockchain/BUILD.bazel @@ -182,6 +182,7 @@ go_test( "//container/trie:go_default_library", "//crypto/bls:go_default_library", "//encoding/bytesutil:go_default_library", + "//genesis:go_default_library", "//proto/engine/v1:go_default_library", "//proto/eth/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", diff --git a/beacon-chain/blockchain/chain_info_test.go b/beacon-chain/blockchain/chain_info_test.go index 36d7465dd1..89f0d0e408 100644 --- a/beacon-chain/blockchain/chain_info_test.go +++ b/beacon-chain/blockchain/chain_info_test.go @@ -14,6 +14,7 @@ import ( "github.com/OffchainLabs/prysm/v6/consensus-types/blocks" "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" "github.com/OffchainLabs/prysm/v6/encoding/bytesutil" + "github.com/OffchainLabs/prysm/v6/genesis" enginev1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1" ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v6/testing/assert" @@ -624,6 +625,7 @@ func Test_hashForGenesisRoot(t *testing.T) { ctx := t.Context() c := setupBeaconChain(t, beaconDB) st, _ := util.DeterministicGenesisStateElectra(t, 10) + genesis.StoreDuringTest(t, genesis.GenesisData{State: st}) require.NoError(t, c.cfg.BeaconDB.SaveGenesisData(ctx, st)) root, err := beaconDB.GenesisBlockRoot(ctx) require.NoError(t, err) diff --git a/beacon-chain/blockchain/execution_engine_test.go b/beacon-chain/blockchain/execution_engine_test.go index 885c1e680a..7feece767a 100644 --- a/beacon-chain/blockchain/execution_engine_test.go +++ b/beacon-chain/blockchain/execution_engine_test.go @@ -19,6 +19,7 @@ import ( "github.com/OffchainLabs/prysm/v6/consensus-types/interfaces" "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" "github.com/OffchainLabs/prysm/v6/encoding/bytesutil" + "github.com/OffchainLabs/prysm/v6/genesis" v1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1" ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v6/testing/assert" @@ -309,6 +310,7 @@ func Test_NotifyForkchoiceUpdate_NIlLVH(t *testing.T) { block: wba, } + genesis.StoreStateDuringTest(t, st) require.NoError(t, beaconDB.SaveState(ctx, st, bra)) require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bra)) a := &fcuConfig{ @@ -403,6 +405,7 @@ func Test_NotifyForkchoiceUpdateRecursive_DoublyLinkedTree(t *testing.T) { require.NoError(t, err) bState, _ := util.DeterministicGenesisState(t, 10) + genesis.StoreStateDuringTest(t, bState) require.NoError(t, beaconDB.SaveState(ctx, bState, bra)) require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) state, blkRoot, err = prepareForkchoiceState(ctx, 2, brb, bra, [32]byte{'B'}, ojc, ofc) diff --git a/beacon-chain/blockchain/process_block_test.go b/beacon-chain/blockchain/process_block_test.go index cd8782f650..59c9ac6e8a 100644 --- a/beacon-chain/blockchain/process_block_test.go +++ b/beacon-chain/blockchain/process_block_test.go @@ -35,6 +35,7 @@ import ( "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" "github.com/OffchainLabs/prysm/v6/crypto/bls" "github.com/OffchainLabs/prysm/v6/encoding/bytesutil" + "github.com/OffchainLabs/prysm/v6/genesis" enginev1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1" ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v6/runtime/version" @@ -1980,14 +1981,15 @@ func TestNoViableHead_Reboot(t *testing.T) { genesisState, keys := util.DeterministicGenesisState(t, 64) stateRoot, err := genesisState.HashTreeRoot(ctx) require.NoError(t, err, "Could not hash genesis state") - genesis := blocks.NewGenesisBlock(stateRoot[:]) - wsb, err := consensusblocks.NewSignedBeaconBlock(genesis) + gb := blocks.NewGenesisBlock(stateRoot[:]) + wsb, err := consensusblocks.NewSignedBeaconBlock(gb) require.NoError(t, err) - genesisRoot, err := genesis.Block.HashTreeRoot() + genesisRoot, err := gb.Block.HashTreeRoot() require.NoError(t, err, "Could not get signing root") require.NoError(t, service.cfg.BeaconDB.SaveBlock(ctx, wsb), "Could not save genesis block") require.NoError(t, service.saveGenesisData(ctx, genesisState)) + genesis.StoreStateDuringTest(t, genesisState) require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, genesisState, genesisRoot), "Could not save genesis state") require.NoError(t, service.cfg.BeaconDB.SaveHeadBlockRoot(ctx, genesisRoot), "Could not save genesis state") require.NoError(t, service.cfg.BeaconDB.SaveGenesisBlockRoot(ctx, genesisRoot), "Could not save genesis state") diff --git a/beacon-chain/blockchain/service.go b/beacon-chain/blockchain/service.go index 7991cd5206..7affa49ecc 100644 --- a/beacon-chain/blockchain/service.go +++ b/beacon-chain/blockchain/service.go @@ -12,7 +12,6 @@ import ( "github.com/OffchainLabs/prysm/v6/async/event" "github.com/OffchainLabs/prysm/v6/beacon-chain/blockchain/kzg" "github.com/OffchainLabs/prysm/v6/beacon-chain/cache" - "github.com/OffchainLabs/prysm/v6/beacon-chain/core/feed" statefeed "github.com/OffchainLabs/prysm/v6/beacon-chain/core/feed/state" "github.com/OffchainLabs/prysm/v6/beacon-chain/core/helpers" lightClient "github.com/OffchainLabs/prysm/v6/beacon-chain/core/light-client" @@ -207,17 +206,9 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) { // Start a blockchain service's main event loop. func (s *Service) Start() { - saved := s.cfg.FinalizedStateAtStartUp defer s.removeStartupState() - - if saved != nil && !saved.IsNil() { - if err := s.StartFromSavedState(saved); err != nil { - log.Fatal(err) - } - } else { - if err := s.startFromExecutionChain(); err != nil { - log.Fatal(err) - } + if err := s.StartFromSavedState(s.cfg.FinalizedStateAtStartUp); err != nil { + log.Fatal(err) } s.spawnProcessAttestationsRoutine() go s.runLateBlockTasks() @@ -266,6 +257,9 @@ func (s *Service) Status() error { // StartFromSavedState initializes the blockchain using a previously saved finalized checkpoint. func (s *Service) StartFromSavedState(saved state.BeaconState) error { + if state.IsNil(saved) { + return errors.New("Last finalized state at startup is nil") + } log.Info("Blockchain data already exists in DB, initializing...") s.genesisTime = saved.GenesisTime() s.cfg.AttService.SetGenesisTime(saved.GenesisTime()) @@ -371,62 +365,6 @@ func (s *Service) initializeHead(ctx context.Context, st state.BeaconState) erro return nil } -func (s *Service) startFromExecutionChain() error { - log.Info("Waiting to reach the validator deposit threshold to start the beacon chain...") - if s.cfg.ChainStartFetcher == nil { - return errors.New("not configured execution chain") - } - go func() { - stateChannel := make(chan *feed.Event, 1) - stateSub := s.cfg.StateNotifier.StateFeed().Subscribe(stateChannel) - defer stateSub.Unsubscribe() - for { - select { - case e := <-stateChannel: - if e.Type == statefeed.ChainStarted { - data, ok := e.Data.(*statefeed.ChainStartedData) - if !ok { - log.Error("Event data is not type *statefeed.ChainStartedData") - return - } - log.WithField("startTime", data.StartTime).Debug("Received chain start event") - s.onExecutionChainStart(s.ctx, data.StartTime) - return - } - case <-s.ctx.Done(): - log.Debug("Context closed, exiting goroutine") - return - case err := <-stateSub.Err(): - log.WithError(err).Error("Subscription to state forRoot failed") - return - } - } - }() - - return nil -} - -// onExecutionChainStart initializes a series of deposits from the ChainStart deposits in the eth1 -// deposit contract, initializes the beacon chain's state, and kicks off the beacon chain. -func (s *Service) onExecutionChainStart(ctx context.Context, genesisTime time.Time) { - preGenesisState := s.cfg.ChainStartFetcher.PreGenesisState() - initializedState, err := s.initializeBeaconChain(ctx, genesisTime, preGenesisState, s.cfg.ChainStartFetcher.ChainStartEth1Data()) - if err != nil { - log.WithError(err).Fatal("Could not initialize beacon chain") - } - // We start a counter to genesis, if needed. - gRoot, err := initializedState.HashTreeRoot(s.ctx) - if err != nil { - log.WithError(err).Fatal("Could not hash tree root genesis state") - } - go slots.CountdownToGenesis(ctx, genesisTime, uint64(initializedState.NumValidators()), gRoot) - - vr := bytesutil.ToBytes32(initializedState.GenesisValidatorsRoot()) - if err := s.clockSetter.SetClock(startup.NewClock(genesisTime, vr)); err != nil { - log.WithError(err).Fatal("Failed to initialize blockchain service from execution start event") - } -} - // initializes the state and genesis block of the beacon chain to persistent storage // based on a genesis timestamp value obtained from the ChainStart event emitted // by the ETH1.0 Deposit Contract and the POWChain service of the node. diff --git a/beacon-chain/blockchain/service_test.go b/beacon-chain/blockchain/service_test.go index d43debbd71..19c951e1ab 100644 --- a/beacon-chain/blockchain/service_test.go +++ b/beacon-chain/blockchain/service_test.go @@ -31,6 +31,7 @@ import ( "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" "github.com/OffchainLabs/prysm/v6/container/trie" "github.com/OffchainLabs/prysm/v6/encoding/bytesutil" + "github.com/OffchainLabs/prysm/v6/genesis" ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v6/testing/assert" "github.com/OffchainLabs/prysm/v6/testing/require" @@ -51,6 +52,7 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service { srv.Stop() }) bState, _ := util.DeterministicGenesisState(t, 10) + genesis.StoreStateDuringTest(t, bState) pbState, err := state_native.ProtobufBeaconStatePhase0(bState.ToProtoUnsafe()) require.NoError(t, err) mockTrie, err := trie.NewTrie(0) @@ -71,20 +73,22 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service { DepositContainers: []*ethpb.DepositContainer{}, }) require.NoError(t, err) + + depositCache, err := depositsnapshot.New() + require.NoError(t, err) + web3Service, err = execution.NewService( ctx, execution.WithDatabase(beaconDB), execution.WithHttpEndpoint(endpoint), execution.WithDepositContractAddress(common.Address{}), + execution.WithDepositCache(depositCache), ) require.NoError(t, err, "Unable to set up web3 service") attService, err := attestations.NewService(ctx, &attestations.Config{Pool: attestations.NewPool()}) require.NoError(t, err) - depositCache, err := depositsnapshot.New() - require.NoError(t, err) - fc := doublylinkedtree.New() stateGen := stategen.New(beaconDB, fc) // Safe a state in stategen to purposes of testing a service stop / shutdown. @@ -396,24 +400,6 @@ func TestServiceStop_SaveCachedBlocks(t *testing.T) { require.Equal(t, true, s.cfg.BeaconDB.HasBlock(s.ctx, r)) } -func TestProcessChainStartTime_ReceivedFeed(t *testing.T) { - ctx := t.Context() - beaconDB := testDB.SetupDB(t) - service := setupBeaconChain(t, beaconDB) - mgs := &MockClockSetter{} - service.clockSetter = mgs - gt := time.Now() - service.onExecutionChainStart(t.Context(), gt) - gs, err := beaconDB.GenesisState(ctx) - require.NoError(t, err) - require.NotEqual(t, nil, gs) - require.Equal(t, 32, len(gs.GenesisValidatorsRoot())) - var zero [32]byte - require.DeepNotEqual(t, gs.GenesisValidatorsRoot(), zero[:]) - require.Equal(t, gt, mgs.G.GenesisTime()) - require.Equal(t, bytesutil.ToBytes32(gs.GenesisValidatorsRoot()), mgs.G.GenesisValidatorsRoot()) -} - func BenchmarkHasBlockDB(b *testing.B) { ctx := b.Context() s := testServiceWithDB(b) diff --git a/beacon-chain/db/BUILD.bazel b/beacon-chain/db/BUILD.bazel index 5ffe08e9ea..e5502d7cf7 100644 --- a/beacon-chain/db/BUILD.bazel +++ b/beacon-chain/db/BUILD.bazel @@ -13,6 +13,7 @@ go_library( visibility = [ "//beacon-chain:__subpackages__", "//cmd/beacon-chain:__subpackages__", + "//genesis:__subpackages__", "//testing/slasher/simulator:__pkg__", "//tools:__subpackages__", ], diff --git a/beacon-chain/db/kv/BUILD.bazel b/beacon-chain/db/kv/BUILD.bazel index aa3db3caaa..acbaa50fa2 100644 --- a/beacon-chain/db/kv/BUILD.bazel +++ b/beacon-chain/db/kv/BUILD.bazel @@ -40,7 +40,6 @@ go_library( "//beacon-chain/db/filters:go_default_library", "//beacon-chain/db/iface:go_default_library", "//beacon-chain/state:go_default_library", - "//beacon-chain/state/genesis:go_default_library", "//beacon-chain/state/state-native:go_default_library", "//config/features:go_default_library", "//config/fieldparams:go_default_library", @@ -52,6 +51,7 @@ go_library( "//container/slice:go_default_library", "//encoding/bytesutil:go_default_library", "//encoding/ssz/detect:go_default_library", + "//genesis:go_default_library", "//io/file:go_default_library", "//monitoring/progress:go_default_library", "//monitoring/tracing:go_default_library", @@ -110,7 +110,6 @@ go_test( "//beacon-chain/db/filters:go_default_library", "//beacon-chain/db/iface:go_default_library", "//beacon-chain/state:go_default_library", - "//beacon-chain/state/genesis:go_default_library", "//beacon-chain/state/state-native:go_default_library", "//config/features:go_default_library", "//config/fieldparams:go_default_library", @@ -120,6 +119,7 @@ go_test( "//consensus-types/light-client:go_default_library", "//consensus-types/primitives:go_default_library", "//encoding/bytesutil:go_default_library", + "//genesis:go_default_library", "//proto/dbval:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", diff --git a/beacon-chain/db/kv/genesis.go b/beacon-chain/db/kv/genesis.go index ff9c859de1..297103f1fb 100644 --- a/beacon-chain/db/kv/genesis.go +++ b/beacon-chain/db/kv/genesis.go @@ -8,6 +8,7 @@ import ( dbIface "github.com/OffchainLabs/prysm/v6/beacon-chain/db/iface" "github.com/OffchainLabs/prysm/v6/beacon-chain/state" "github.com/OffchainLabs/prysm/v6/encoding/ssz/detect" + "github.com/OffchainLabs/prysm/v6/genesis" ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" "github.com/pkg/errors" ) @@ -97,8 +98,22 @@ func (s *Store) EnsureEmbeddedGenesis(ctx context.Context) error { if err != nil { return err } - if gs != nil && !gs.IsNil() { + if !state.IsNil(gs) { return s.SaveGenesisData(ctx, gs) } return nil } + +type LegacyGenesisProvider struct { + store *Store +} + +func NewLegacyGenesisProvider(store *Store) *LegacyGenesisProvider { + return &LegacyGenesisProvider{store: store} +} + +var _ genesis.Provider = &LegacyGenesisProvider{} + +func (p *LegacyGenesisProvider) Genesis(ctx context.Context) (state.BeaconState, error) { + return p.store.LegacyGenesisState(ctx) +} diff --git a/beacon-chain/db/kv/genesis_test.go b/beacon-chain/db/kv/genesis_test.go index 3ed25f3b1f..d79ce95873 100644 --- a/beacon-chain/db/kv/genesis_test.go +++ b/beacon-chain/db/kv/genesis_test.go @@ -8,6 +8,7 @@ import ( "github.com/OffchainLabs/prysm/v6/beacon-chain/db/iface" "github.com/OffchainLabs/prysm/v6/config/params" "github.com/OffchainLabs/prysm/v6/encoding/bytesutil" + "github.com/OffchainLabs/prysm/v6/genesis" "github.com/OffchainLabs/prysm/v6/testing/assert" "github.com/OffchainLabs/prysm/v6/testing/require" "github.com/OffchainLabs/prysm/v6/testing/util" @@ -152,6 +153,7 @@ func TestEnsureEmbeddedGenesis(t *testing.T) { require.NoError(t, undo()) }() + genesis.StoreEmbeddedDuringTest(t, params.BeaconConfig().ConfigName) ctx := t.Context() db := setupDB(t) diff --git a/beacon-chain/db/kv/state.go b/beacon-chain/db/kv/state.go index cd21e7842e..b6ef40dad2 100644 --- a/beacon-chain/db/kv/state.go +++ b/beacon-chain/db/kv/state.go @@ -6,14 +6,12 @@ import ( "fmt" "github.com/OffchainLabs/prysm/v6/beacon-chain/state" - "github.com/OffchainLabs/prysm/v6/beacon-chain/state/genesis" statenative "github.com/OffchainLabs/prysm/v6/beacon-chain/state/state-native" "github.com/OffchainLabs/prysm/v6/config/features" - "github.com/OffchainLabs/prysm/v6/config/params" "github.com/OffchainLabs/prysm/v6/consensus-types/blocks" "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" "github.com/OffchainLabs/prysm/v6/encoding/bytesutil" - "github.com/OffchainLabs/prysm/v6/monitoring/tracing" + "github.com/OffchainLabs/prysm/v6/genesis" "github.com/OffchainLabs/prysm/v6/monitoring/tracing/trace" ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v6/runtime/version" @@ -65,21 +63,21 @@ func (s *Store) StateOrError(ctx context.Context, blockRoot [32]byte) (state.Bea return st, nil } -// GenesisState returns the genesis state in beacon chain. func (s *Store) GenesisState(ctx context.Context) (state.BeaconState, error) { - ctx, span := trace.StartSpan(ctx, "BeaconDB.GenesisState") + st, err := genesis.State() + if errors.Is(err, genesis.ErrGenesisStateNotInitialized) { + log.WithError(err).Error("genesis state not initialized, returning nil state. this should only happen in tests") + return nil, nil + } + return st, err +} + +// GenesisState returns the genesis state in beacon chain. +func (s *Store) LegacyGenesisState(ctx context.Context) (state.BeaconState, error) { + ctx, span := trace.StartSpan(ctx, "BeaconDB.LegacyGenesisState") defer span.End() - cached, err := genesis.State(params.BeaconConfig().ConfigName) - if err != nil { - tracing.AnnotateError(span, err) - return nil, err - } - span.SetAttributes(trace.BoolAttribute("cache_hit", cached != nil)) - if cached != nil { - return cached, nil - } - + var err error var st state.BeaconState err = s.db.View(func(tx *bolt.Tx) error { // Retrieve genesis block's signing root from blocks bucket, diff --git a/beacon-chain/db/kv/state_test.go b/beacon-chain/db/kv/state_test.go index 4e425e4c8d..ebb29bf9c4 100644 --- a/beacon-chain/db/kv/state_test.go +++ b/beacon-chain/db/kv/state_test.go @@ -15,6 +15,7 @@ import ( "github.com/OffchainLabs/prysm/v6/consensus-types/interfaces" "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" "github.com/OffchainLabs/prysm/v6/encoding/bytesutil" + "github.com/OffchainLabs/prysm/v6/genesis" enginev1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1" ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v6/testing/assert" @@ -488,7 +489,7 @@ func TestGenesisState_CanSaveRetrieve(t *testing.T) { require.NoError(t, err) require.NoError(t, st.SetSlot(1)) require.NoError(t, db.SaveGenesisBlockRoot(t.Context(), headRoot)) - require.NoError(t, db.SaveState(t.Context(), st, headRoot)) + genesis.StoreStateDuringTest(t, st) savedGenesisS, err := db.GenesisState(t.Context()) require.NoError(t, err) @@ -661,7 +662,7 @@ func TestStore_GenesisState_CanGetHighestBelow(t *testing.T) { require.NoError(t, err) genesisRoot := [32]byte{'a'} require.NoError(t, db.SaveGenesisBlockRoot(t.Context(), genesisRoot)) - require.NoError(t, db.SaveState(t.Context(), genesisState, genesisRoot)) + genesis.StoreStateDuringTest(t, genesisState) b := util.NewBeaconBlock() b.Block.Slot = 1 diff --git a/beacon-chain/db/kv/wss_test.go b/beacon-chain/db/kv/wss_test.go index cf31228fbc..818bfe43e1 100644 --- a/beacon-chain/db/kv/wss_test.go +++ b/beacon-chain/db/kv/wss_test.go @@ -3,9 +3,9 @@ package kv import ( "testing" - "github.com/OffchainLabs/prysm/v6/beacon-chain/state/genesis" "github.com/OffchainLabs/prysm/v6/config/params" "github.com/OffchainLabs/prysm/v6/consensus-types/blocks" + "github.com/OffchainLabs/prysm/v6/genesis" "github.com/OffchainLabs/prysm/v6/testing/require" "github.com/OffchainLabs/prysm/v6/testing/util" ) @@ -18,7 +18,11 @@ func TestSaveOrigin(t *testing.T) { ctx := t.Context() db := setupDB(t) - st, err := genesis.State(params.MainnetName) + // Initialize genesis with mainnet config - this will load the embedded mainnet state + require.NoError(t, genesis.Initialize(ctx, t.TempDir())) + + // Get the initialized genesis state + st, err := genesis.State() require.NoError(t, err) sb, err := st.MarshalSSZ() diff --git a/beacon-chain/execution/BUILD.bazel b/beacon-chain/execution/BUILD.bazel index e8133d404b..01ccb96807 100644 --- a/beacon-chain/execution/BUILD.bazel +++ b/beacon-chain/execution/BUILD.bazel @@ -125,6 +125,7 @@ go_test( "//contracts/deposit/mock:go_default_library", "//crypto/bls:go_default_library", "//encoding/bytesutil:go_default_library", + "//genesis:go_default_library", "//monitoring/clientstats:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", diff --git a/beacon-chain/execution/service_test.go b/beacon-chain/execution/service_test.go index 288467da17..60f2785a70 100644 --- a/beacon-chain/execution/service_test.go +++ b/beacon-chain/execution/service_test.go @@ -22,6 +22,7 @@ import ( contracts "github.com/OffchainLabs/prysm/v6/contracts/deposit" "github.com/OffchainLabs/prysm/v6/contracts/deposit/mock" "github.com/OffchainLabs/prysm/v6/encoding/bytesutil" + "github.com/OffchainLabs/prysm/v6/genesis" "github.com/OffchainLabs/prysm/v6/monitoring/clientstats" ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v6/testing/assert" @@ -381,6 +382,7 @@ func TestInitDepositCache_OK(t *testing.T) { require.NoError(t, err) require.NoError(t, s.cfg.beaconDB.SaveGenesisBlockRoot(t.Context(), blockRootA)) require.NoError(t, s.cfg.beaconDB.SaveState(t.Context(), emptyState, blockRootA)) + genesis.StoreStateDuringTest(t, emptyState) s.chainStartData.Chainstarted = true require.NoError(t, s.initDepositCaches(t.Context(), ctrs)) require.Equal(t, 3, len(s.cfg.depositCache.PendingContainers(t.Context(), nil))) @@ -446,6 +448,7 @@ func TestInitDepositCacheWithFinalization_OK(t *testing.T) { require.NoError(t, s.cfg.beaconDB.SaveGenesisBlockRoot(t.Context(), headRoot)) require.NoError(t, s.cfg.beaconDB.SaveState(t.Context(), emptyState, headRoot)) require.NoError(t, stateGen.SaveState(t.Context(), headRoot, emptyState)) + genesis.StoreStateDuringTest(t, emptyState) s.cfg.stateGen = stateGen require.NoError(t, emptyState.SetEth1DepositIndex(3)) @@ -594,6 +597,7 @@ func TestService_EnsureConsistentPowchainData(t *testing.T) { require.NoError(t, err) assert.NoError(t, genState.SetSlot(1000)) + genesis.StoreStateDuringTest(t, genState) require.NoError(t, s1.cfg.beaconDB.SaveGenesisData(t.Context(), genState)) _, err = s1.validPowchainData(t.Context()) require.NoError(t, err) @@ -655,6 +659,7 @@ func TestService_EnsureValidPowchainData(t *testing.T) { require.NoError(t, err) assert.NoError(t, genState.SetSlot(1000)) + genesis.StoreStateDuringTest(t, genState) require.NoError(t, s1.cfg.beaconDB.SaveGenesisData(t.Context(), genState)) err = s1.cfg.beaconDB.SaveExecutionChainData(t.Context(), ðpb.ETH1ChainData{ diff --git a/beacon-chain/node/BUILD.bazel b/beacon-chain/node/BUILD.bazel index 76fe2d15e0..cd03ce63fc 100644 --- a/beacon-chain/node/BUILD.bazel +++ b/beacon-chain/node/BUILD.bazel @@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "clear_db.go", "config.go", "log.go", "node.go", @@ -49,7 +50,6 @@ go_library( "//beacon-chain/sync/backfill:go_default_library", "//beacon-chain/sync/backfill/coverage:go_default_library", "//beacon-chain/sync/checkpoint:go_default_library", - "//beacon-chain/sync/genesis:go_default_library", "//beacon-chain/sync/initial-sync:go_default_library", "//beacon-chain/verification:go_default_library", "//cmd:go_default_library", @@ -59,6 +59,7 @@ go_library( "//consensus-types/primitives:go_default_library", "//container/slice:go_default_library", "//encoding/bytesutil:go_default_library", + "//genesis:go_default_library", "//monitoring/prometheus:go_default_library", "//monitoring/tracing:go_default_library", "//runtime:go_default_library", diff --git a/beacon-chain/node/clear_db.go b/beacon-chain/node/clear_db.go new file mode 100644 index 0000000000..ce9e8a24a7 --- /dev/null +++ b/beacon-chain/node/clear_db.go @@ -0,0 +1,101 @@ +package node + +import ( + "context" + + "github.com/OffchainLabs/prysm/v6/beacon-chain/db/filesystem" + "github.com/OffchainLabs/prysm/v6/beacon-chain/db/kv" + "github.com/OffchainLabs/prysm/v6/beacon-chain/db/slasherkv" + "github.com/OffchainLabs/prysm/v6/cmd" + "github.com/pkg/errors" + "github.com/urfave/cli/v2" +) + +type dbClearer struct { + shouldClear bool + force bool + confirmed bool +} + +const ( + clearConfirmation = "This will delete your beacon chain database stored in your data directory. " + + "Your database backups will not be removed - do you want to proceed? (Y/N)" + + clearDeclined = "Database will not be deleted. No changes have been made." +) + +func (c *dbClearer) clearKV(ctx context.Context, db *kv.Store) (*kv.Store, error) { + if !c.shouldProceed() { + return db, nil + } + + log.Warning("Removing database") + if err := db.ClearDB(); err != nil { + return nil, errors.Wrap(err, "could not clear database") + } + return kv.NewKVStore(ctx, db.DatabasePath()) +} + +func (c *dbClearer) clearBlobs(bs *filesystem.BlobStorage) error { + if !c.shouldProceed() { + return nil + } + + log.Warning("Removing blob storage") + if err := bs.Clear(); err != nil { + return errors.Wrap(err, "could not clear blob storage") + } + + return nil +} + +func (c *dbClearer) clearColumns(cs *filesystem.DataColumnStorage) error { + if !c.shouldProceed() { + return nil + } + + log.Warning("Removing data columns storage") + if err := cs.Clear(); err != nil { + return errors.Wrap(err, "could not clear data columns storage") + } + + return nil +} + +func (c *dbClearer) clearSlasher(ctx context.Context, db *slasherkv.Store) (*slasherkv.Store, error) { + if !c.shouldProceed() { + return db, nil + } + + log.Warning("Removing slasher database") + if err := db.ClearDB(); err != nil { + return nil, errors.Wrap(err, "could not clear slasher database") + } + return slasherkv.NewKVStore(ctx, db.DatabasePath()) +} + +func (c *dbClearer) shouldProceed() bool { + if !c.shouldClear { + return false + } + if c.force { + return true + } + if !c.confirmed { + confirmed, err := cmd.ConfirmAction(clearConfirmation, clearDeclined) + if err != nil { + log.WithError(err).Error("Not clearing db due to confirmation error") + return false + } + c.confirmed = confirmed + } + return c.confirmed +} + +func newDbClearer(cliCtx *cli.Context) *dbClearer { + force := cliCtx.Bool(cmd.ForceClearDB.Name) + return &dbClearer{ + shouldClear: cliCtx.Bool(cmd.ClearDB.Name) || force, + force: force, + } +} diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index de490cb94e..6fef5c7223 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -52,7 +52,6 @@ import ( "github.com/OffchainLabs/prysm/v6/beacon-chain/sync/backfill" "github.com/OffchainLabs/prysm/v6/beacon-chain/sync/backfill/coverage" "github.com/OffchainLabs/prysm/v6/beacon-chain/sync/checkpoint" - "github.com/OffchainLabs/prysm/v6/beacon-chain/sync/genesis" initialsync "github.com/OffchainLabs/prysm/v6/beacon-chain/sync/initial-sync" "github.com/OffchainLabs/prysm/v6/beacon-chain/verification" "github.com/OffchainLabs/prysm/v6/cmd" @@ -62,6 +61,7 @@ import ( "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" "github.com/OffchainLabs/prysm/v6/container/slice" "github.com/OffchainLabs/prysm/v6/encoding/bytesutil" + "github.com/OffchainLabs/prysm/v6/genesis" "github.com/OffchainLabs/prysm/v6/monitoring/prometheus" "github.com/OffchainLabs/prysm/v6/runtime" "github.com/OffchainLabs/prysm/v6/runtime/prereqs" @@ -113,7 +113,7 @@ type BeaconNode struct { slasherAttestationsFeed *event.Feed finalizedStateAtStartUp state.BeaconState serviceFlagOpts *serviceFlagOpts - GenesisInitializer genesis.Initializer + GenesisProviders []genesis.Provider CheckpointInitializer checkpoint.Initializer forkChoicer forkchoice.ForkChoicer clockWaiter startup.ClockWaiter @@ -127,6 +127,7 @@ type BeaconNode struct { syncChecker *initialsync.SyncChecker slasherEnabled bool lcStore *lightclient.Store + ConfigOptions []params.Option } // New creates a new node instance, sets up configuration options, and registers @@ -135,18 +136,13 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco if err := configureBeacon(cliCtx); err != nil { return nil, errors.Wrap(err, "could not set beacon configuration options") } - - // Initializes any forks here. - params.BeaconConfig().InitializeForkSchedule() - - registry := runtime.NewServiceRegistry() ctx := cliCtx.Context beacon := &BeaconNode{ cliCtx: cliCtx, ctx: ctx, cancel: cancel, - services: registry, + services: runtime.NewServiceRegistry(), stop: make(chan struct{}), stateFeed: new(event.Feed), blockFeed: new(event.Feed), @@ -173,6 +169,24 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco } } + dbClearer := newDbClearer(cliCtx) + dataDir := cliCtx.String(cmd.DataDirFlag.Name) + boltFname := filepath.Join(dataDir, kv.BeaconNodeDbDirName) + kvdb, err := openDB(ctx, boltFname, dbClearer) + if err != nil { + return nil, errors.Wrap(err, "could not open database") + } + beacon.db = kvdb + + providers := append(beacon.GenesisProviders, kv.NewLegacyGenesisProvider(kvdb)) + if err := genesis.Initialize(ctx, dataDir, providers...); err != nil { + return nil, errors.Wrap(err, "could not initialize genesis state") + } + + beacon.ConfigOptions = append([]params.Option{params.WithGenesisValidatorsRoot(genesis.ValidatorsRoot())}, beacon.ConfigOptions...) + params.BeaconConfig().ApplyOptions(beacon.ConfigOptions...) + params.BeaconConfig().InitializeForkSchedule() + synchronizer := startup.NewClockSynchronizer() beacon.clockWaiter = synchronizer beacon.forkChoicer = doublylinkedtree.New() @@ -191,6 +205,9 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco } beacon.BlobStorage = blobs } + if err := dbClearer.clearBlobs(beacon.BlobStorage); err != nil { + return nil, errors.Wrap(err, "could not clear blob storage") + } if beacon.DataColumnStorage == nil { dataColumnStorage, err := filesystem.NewDataColumnStorage(cliCtx.Context, beacon.DataColumnStorageOptions...) @@ -200,8 +217,11 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco beacon.DataColumnStorage = dataColumnStorage } + if err := dbClearer.clearColumns(beacon.DataColumnStorage); err != nil { + return nil, errors.Wrap(err, "could not clear data column storage") + } - bfs, err := startBaseServices(cliCtx, beacon, depositAddress) + bfs, err := startBaseServices(cliCtx, beacon, depositAddress, dbClearer) if err != nil { return nil, errors.Wrap(err, "could not start modules") } @@ -289,7 +309,7 @@ func configureBeacon(cliCtx *cli.Context) error { return nil } -func startBaseServices(cliCtx *cli.Context, beacon *BeaconNode, depositAddress string) (*backfill.Store, error) { +func startBaseServices(cliCtx *cli.Context, beacon *BeaconNode, depositAddress string, clearer *dbClearer) (*backfill.Store, error) { ctx := cliCtx.Context log.Debugln("Starting DB") if err := beacon.startDB(cliCtx, depositAddress); err != nil { @@ -299,7 +319,7 @@ func startBaseServices(cliCtx *cli.Context, beacon *BeaconNode, depositAddress s beacon.BlobStorage.WarmCache() log.Debugln("Starting Slashing DB") - if err := beacon.startSlasherDB(cliCtx); err != nil { + if err := beacon.startSlasherDB(cliCtx, clearer); err != nil { return nil, errors.Wrap(err, "could not start slashing DB") } @@ -479,43 +499,6 @@ func (b *BeaconNode) Close() { close(b.stop) } -func (b *BeaconNode) clearDB(clearDB, forceClearDB bool, d *kv.Store, dbPath string) (*kv.Store, error) { - var err error - clearDBConfirmed := false - - if clearDB && !forceClearDB { - const ( - actionText = "This will delete your beacon chain database stored in your data directory. " + - "Your database backups will not be removed - do you want to proceed? (Y/N)" - - deniedText = "Database will not be deleted. No changes have been made." - ) - - clearDBConfirmed, err = cmd.ConfirmAction(actionText, deniedText) - if err != nil { - return nil, errors.Wrapf(err, "could not confirm action") - } - } - - if clearDBConfirmed || forceClearDB { - log.Warning("Removing database") - if err := d.ClearDB(); err != nil { - return nil, errors.Wrap(err, "could not clear database") - } - - if err := b.BlobStorage.Clear(); err != nil { - return nil, errors.Wrap(err, "could not clear blob storage") - } - - d, err = kv.NewKVStore(b.ctx, dbPath) - if err != nil { - return nil, errors.Wrap(err, "could not create new database") - } - } - - return d, nil -} - func (b *BeaconNode) checkAndSaveDepositContract(depositAddress string) error { knownContract, err := b.db.DepositContractAddress(b.ctx) if err != nil { @@ -539,60 +522,36 @@ func (b *BeaconNode) checkAndSaveDepositContract(depositAddress string) error { return nil } -func (b *BeaconNode) startDB(cliCtx *cli.Context, depositAddress string) error { - var depositCache cache.DepositCache - - baseDir := cliCtx.String(cmd.DataDirFlag.Name) - dbPath := filepath.Join(baseDir, kv.BeaconNodeDbDirName) - clearDBRequired := cliCtx.Bool(cmd.ClearDB.Name) - forceClearDBRequired := cliCtx.Bool(cmd.ForceClearDB.Name) - +func openDB(ctx context.Context, dbPath string, clearer *dbClearer) (*kv.Store, error) { log.WithField("databasePath", dbPath).Info("Checking DB") - d, err := kv.NewKVStore(b.ctx, dbPath) + d, err := kv.NewKVStore(ctx, dbPath) if err != nil { - return errors.Wrapf(err, "could not create database at %s", dbPath) + return nil, errors.Wrapf(err, "could not create database at %s", dbPath) } - if clearDBRequired || forceClearDBRequired { - d, err = b.clearDB(clearDBRequired, forceClearDBRequired, d, dbPath) - if err != nil { - return errors.Wrap(err, "could not clear database") - } + d, err = clearer.clearKV(ctx, d) + if err != nil { + return nil, errors.Wrap(err, "could not clear database") } - if err := d.RunMigrations(b.ctx); err != nil { - return err - } + return d, d.RunMigrations(ctx) +} - b.db = d - - depositCache, err = depositsnapshot.New() +func (b *BeaconNode) startDB(cliCtx *cli.Context, depositAddress string) error { + depositCache, err := depositsnapshot.New() if err != nil { return errors.Wrap(err, "could not create deposit cache") } - b.depositCache = depositCache - if b.GenesisInitializer != nil { - if err := b.GenesisInitializer.Initialize(b.ctx, d); err != nil { - if errors.Is(err, db.ErrExistingGenesisState) { - return errors.Errorf("Genesis state flag specified but a genesis state "+ - "exists already. Run again with --%s and/or ensure you are using the "+ - "appropriate testnet flag to load the given genesis state.", cmd.ClearDB.Name) - } - - return errors.Wrap(err, "could not load genesis from file") - } - } - if err := b.db.EnsureEmbeddedGenesis(b.ctx); err != nil { return errors.Wrap(err, "could not ensure embedded genesis") } if b.CheckpointInitializer != nil { log.Info("Checkpoint sync - Downloading origin state and block") - if err := b.CheckpointInitializer.Initialize(b.ctx, d); err != nil { + if err := b.CheckpointInitializer.Initialize(b.ctx, b.db); err != nil { return err } } @@ -604,49 +563,25 @@ func (b *BeaconNode) startDB(cliCtx *cli.Context, depositAddress string) error { log.WithField("address", depositAddress).Info("Deposit contract") return nil } - -func (b *BeaconNode) startSlasherDB(cliCtx *cli.Context) error { +func (b *BeaconNode) startSlasherDB(cliCtx *cli.Context, clearer *dbClearer) error { if !b.slasherEnabled { return nil } baseDir := cliCtx.String(cmd.DataDirFlag.Name) - if cliCtx.IsSet(flags.SlasherDirFlag.Name) { baseDir = cliCtx.String(flags.SlasherDirFlag.Name) } dbPath := filepath.Join(baseDir, kv.BeaconNodeDbDirName) - clearDB := cliCtx.Bool(cmd.ClearDB.Name) - forceClearDB := cliCtx.Bool(cmd.ForceClearDB.Name) - log.WithField("databasePath", dbPath).Info("Checking DB") - d, err := slasherkv.NewKVStore(b.ctx, dbPath) if err != nil { return err } - clearDBConfirmed := false - if clearDB && !forceClearDB { - actionText := "This will delete your beacon chain database stored in your data directory. " + - "Your database backups will not be removed - do you want to proceed? (Y/N)" - deniedText := "Database will not be deleted. No changes have been made." - clearDBConfirmed, err = cmd.ConfirmAction(actionText, deniedText) - if err != nil { - return err - } + d, err = clearer.clearSlasher(b.ctx, d) + if err != nil { + return errors.Wrap(err, "could not clear slasher database") } - if clearDBConfirmed || forceClearDB { - log.Warning("Removing database") - if err := d.ClearDB(); err != nil { - return errors.Wrap(err, "could not clear database") - } - - d, err = slasherkv.NewKVStore(b.ctx, dbPath) - if err != nil { - return errors.Wrap(err, "could not create new database") - } - } - b.slasherDB = d return nil } diff --git a/beacon-chain/node/options.go b/beacon-chain/node/options.go index 052d759bb1..522ebd2787 100644 --- a/beacon-chain/node/options.go +++ b/beacon-chain/node/options.go @@ -5,6 +5,7 @@ import ( "github.com/OffchainLabs/prysm/v6/beacon-chain/builder" "github.com/OffchainLabs/prysm/v6/beacon-chain/db/filesystem" "github.com/OffchainLabs/prysm/v6/beacon-chain/execution" + "github.com/OffchainLabs/prysm/v6/config/params" ) // Option for beacon node configuration. @@ -51,6 +52,13 @@ func WithBlobStorageOptions(opt ...filesystem.BlobStorageOption) Option { } } +func WithConfigOptions(opt ...params.Option) Option { + return func(bn *BeaconNode) error { + bn.ConfigOptions = append(bn.ConfigOptions, opt...) + return nil + } +} + // WithDataColumnStorage sets the DataColumnStorage backend for the BeaconNode func WithDataColumnStorage(bs *filesystem.DataColumnStorage) Option { return func(bn *BeaconNode) error { diff --git a/beacon-chain/state/genesis/genesis.go b/beacon-chain/state/genesis/genesis.go deleted file mode 100644 index f6a00e4184..0000000000 --- a/beacon-chain/state/genesis/genesis.go +++ /dev/null @@ -1,34 +0,0 @@ -package genesis - -import ( - _ "embed" - - "github.com/OffchainLabs/prysm/v6/beacon-chain/state" - state_native "github.com/OffchainLabs/prysm/v6/beacon-chain/state/state-native" - ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" - "github.com/golang/snappy" -) - -var embeddedStates = map[string]*[]byte{} - -// State returns a copy of the genesis state from a hardcoded value. -func State(name string) (state.BeaconState, error) { - sb, exists := embeddedStates[name] - if exists { - return load(*sb) - } - return nil, nil -} - -// load a compressed ssz state file into a beacon state struct. -func load(b []byte) (state.BeaconState, error) { - st := ðpb.BeaconState{} - b, err := snappy.Decode(nil /*dst*/, b) - if err != nil { - return nil, err - } - if err := st.UnmarshalSSZ(b); err != nil { - return nil, err - } - return state_native.InitializeFromProtoUnsafePhase0(st) -} diff --git a/beacon-chain/state/interfaces.go b/beacon-chain/state/interfaces.go index 0d4e6dd5f8..ae1e78e863 100644 --- a/beacon-chain/state/interfaces.go +++ b/beacon-chain/state/interfaces.go @@ -351,3 +351,7 @@ type WriteOnlyDeposits interface { type WriteOnlyProposerLookahead interface { SetProposerLookahead([]primitives.ValidatorIndex) error } + +func IsNil(s BeaconState) bool { + return s == nil || s.IsNil() +} diff --git a/beacon-chain/state/state-native/types.go b/beacon-chain/state/state-native/types.go index ee26e75d28..768b5d3a46 100644 --- a/beacon-chain/state/state-native/types.go +++ b/beacon-chain/state/state-native/types.go @@ -34,3 +34,7 @@ var fieldMap map[types.FieldIndex]types.DataType func errNotSupported(funcName string, ver int) error { return fmt.Errorf("%s is not supported for %s", funcName, version.String(ver)) } + +func IsNil(s state.BeaconState) bool { + return s == nil || s.IsNil() +} diff --git a/beacon-chain/sync/genesis/BUILD.bazel b/beacon-chain/sync/genesis/BUILD.bazel deleted file mode 100644 index 3c999b1f76..0000000000 --- a/beacon-chain/sync/genesis/BUILD.bazel +++ /dev/null @@ -1,21 +0,0 @@ -load("@prysm//tools/go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "api.go", - "file.go", - "log.go", - ], - importpath = "github.com/OffchainLabs/prysm/v6/beacon-chain/sync/genesis", - visibility = ["//visibility:public"], - deps = [ - "//api/client:go_default_library", - "//api/client/beacon:go_default_library", - "//beacon-chain/db:go_default_library", - "//crypto/hash:go_default_library", - "//io/file:go_default_library", - "@com_github_pkg_errors//:go_default_library", - "@com_github_sirupsen_logrus//:go_default_library", - ], -) diff --git a/beacon-chain/sync/genesis/api.go b/beacon-chain/sync/genesis/api.go deleted file mode 100644 index f308560166..0000000000 --- a/beacon-chain/sync/genesis/api.go +++ /dev/null @@ -1,48 +0,0 @@ -package genesis - -import ( - "context" - - "github.com/OffchainLabs/prysm/v6/api/client" - "github.com/OffchainLabs/prysm/v6/api/client/beacon" - "github.com/OffchainLabs/prysm/v6/beacon-chain/db" - "github.com/pkg/errors" -) - -// APIInitializer manages initializing the genesis state and block to prepare the beacon node for syncing. -// The genesis state is retrieved from the remote beacon node api, using the debug state retrieval endpoint. -type APIInitializer struct { - c *beacon.Client -} - -// NewAPIInitializer creates an APIInitializer, handling the set up of a beacon node api client -// using the provided host string. -func NewAPIInitializer(beaconNodeHost string) (*APIInitializer, error) { - c, err := beacon.NewClient(beaconNodeHost, client.WithMaxBodySize(client.MaxBodySizeState)) - if err != nil { - return nil, errors.Wrapf(err, "unable to parse beacon node url or hostname - %s", beaconNodeHost) - } - return &APIInitializer{c: c}, nil -} - -// Initialize downloads origin state and block for checkpoint sync and initializes database records to -// prepare the node to begin syncing from that point. -func (dl *APIInitializer) Initialize(ctx context.Context, d db.Database) error { - existing, err := d.GenesisState(ctx) - if err != nil { - return err - } - if existing != nil && !existing.IsNil() { - htr, err := existing.HashTreeRoot(ctx) - if err != nil { - return errors.Wrap(err, "error while computing hash_tree_root of existing genesis state") - } - log.Warnf("database contains genesis with htr=%#x, ignoring remote genesis state parameter", htr) - return nil - } - sb, err := dl.c.GetState(ctx, beacon.IdGenesis) - if err != nil { - return errors.Wrapf(err, "Error retrieving genesis state from %s", dl.c.NodeURL()) - } - return d.LoadGenesis(ctx, sb) -} diff --git a/beacon-chain/sync/genesis/file.go b/beacon-chain/sync/genesis/file.go deleted file mode 100644 index bf9affa9ff..0000000000 --- a/beacon-chain/sync/genesis/file.go +++ /dev/null @@ -1,62 +0,0 @@ -package genesis - -import ( - "context" - "fmt" - "os" - - "github.com/OffchainLabs/prysm/v6/beacon-chain/db" - "github.com/OffchainLabs/prysm/v6/crypto/hash" - "github.com/OffchainLabs/prysm/v6/io/file" - "github.com/pkg/errors" -) - -// Initializer describes a type that is able to obtain the checkpoint sync data (BeaconState and SignedBeaconBlock) -// in some way and perform database setup to prepare the beacon node for syncing from the given checkpoint. -// See FileInitializer and APIInitializer. -type Initializer interface { - Initialize(ctx context.Context, d db.Database) error -} - -// NewFileInitializer validates the given path information and creates an Initializer which will -// use the provided state and block files to prepare the node for checkpoint sync. -func NewFileInitializer(statePath string) (*FileInitializer, error) { - var err error - if err = existsAndIsFile(statePath); err != nil { - return nil, err - } - // stat just to make sure it actually exists and is a file - return &FileInitializer{statePath: statePath}, nil -} - -// FileInitializer initializes a beacon-node database genesis state and block -// using ssz-encoded state data stored in files on the local filesystem. -type FileInitializer struct { - statePath string -} - -// Initialize is called in the BeaconNode db startup code if an Initializer is present. -// Initialize prepares the beacondb using the provided genesis state. -func (fi *FileInitializer) Initialize(ctx context.Context, d db.Database) error { - serState, err := file.ReadFileAsBytes(fi.statePath) - if err != nil { - return errors.Wrapf(err, "error reading state file %s for checkpoint sync init", fi.statePath) - } - log.WithField( - "hash", fmt.Sprintf("%#x", hash.FastSum256(serState)), - ).Info("Loading genesis state from disk.") - return d.LoadGenesis(ctx, serState) -} - -var _ Initializer = &FileInitializer{} - -func existsAndIsFile(path string) error { - info, err := os.Stat(path) - if err != nil { - return errors.Wrapf(err, "error checking existence of ssz-encoded file %s for genesis state init", path) - } - if info.IsDir() { - return fmt.Errorf("%s is a directory, please specify full path to file", path) - } - return nil -} diff --git a/changelog/kasey_init-genesis-asap.md b/changelog/kasey_init-genesis-asap.md new file mode 100644 index 0000000000..a0d44edfc5 --- /dev/null +++ b/changelog/kasey_init-genesis-asap.md @@ -0,0 +1,3 @@ +## Fixed + +- Genesis state, timestamp and validators root now ubiquitously available at node startup, supporting tech debt cleanup. diff --git a/cmd/beacon-chain/BUILD.bazel b/cmd/beacon-chain/BUILD.bazel index 723d9a3007..fcc9fadc08 100644 --- a/cmd/beacon-chain/BUILD.bazel +++ b/cmd/beacon-chain/BUILD.bazel @@ -19,12 +19,12 @@ go_library( "//cmd/beacon-chain/db:go_default_library", "//cmd/beacon-chain/execution:go_default_library", "//cmd/beacon-chain/flags:go_default_library", + "//cmd/beacon-chain/genesis:go_default_library", "//cmd/beacon-chain/jwt:go_default_library", "//cmd/beacon-chain/storage:go_default_library", "//cmd/beacon-chain/sync/backfill:go_default_library", "//cmd/beacon-chain/sync/backfill/flags:go_default_library", "//cmd/beacon-chain/sync/checkpoint:go_default_library", - "//cmd/beacon-chain/sync/genesis:go_default_library", "//config/features:go_default_library", "//io/file:go_default_library", "//io/logs:go_default_library", diff --git a/cmd/beacon-chain/sync/genesis/BUILD.bazel b/cmd/beacon-chain/genesis/BUILD.bazel similarity index 87% rename from cmd/beacon-chain/sync/genesis/BUILD.bazel rename to cmd/beacon-chain/genesis/BUILD.bazel index 0a8aa698e1..d7aad35df6 100644 --- a/cmd/beacon-chain/sync/genesis/BUILD.bazel +++ b/cmd/beacon-chain/genesis/BUILD.bazel @@ -3,12 +3,12 @@ load("@prysm//tools/go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["options.go"], - importpath = "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/sync/genesis", + importpath = "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/genesis", visibility = ["//visibility:public"], deps = [ "//beacon-chain/node:go_default_library", - "//beacon-chain/sync/genesis:go_default_library", "//cmd/beacon-chain/sync/checkpoint:go_default_library", + "//genesis:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@com_github_urfave_cli_v2//:go_default_library", diff --git a/cmd/beacon-chain/sync/genesis/options.go b/cmd/beacon-chain/genesis/options.go similarity index 64% rename from cmd/beacon-chain/sync/genesis/options.go rename to cmd/beacon-chain/genesis/options.go index f6b35f890c..765e084143 100644 --- a/cmd/beacon-chain/sync/genesis/options.go +++ b/cmd/beacon-chain/genesis/options.go @@ -2,8 +2,8 @@ package genesis import ( "github.com/OffchainLabs/prysm/v6/beacon-chain/node" - "github.com/OffchainLabs/prysm/v6/beacon-chain/sync/genesis" "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/sync/checkpoint" + "github.com/OffchainLabs/prysm/v6/genesis" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" @@ -24,11 +24,21 @@ var ( } ) -// BeaconNodeOptions is responsible for determining if the checkpoint sync options have been used, and if so, -// reading the block and state ssz-serialized values from the filesystem locations specified and preparing a -// checkpoint.Initializer, which uses the provided io.ReadClosers to initialize the beacon node database. +// BeaconNodeOptions handles options for customizing the source of the genesis state. func BeaconNodeOptions(c *cli.Context) ([]node.Option, error) { statePath := c.Path(StatePath.Name) + if statePath != "" { + opt := func(node *node.BeaconNode) (err error) { + provider, err := genesis.NewFileProvider(statePath) + if err != nil { + return errors.Wrap(err, "error preparing to initialize genesis db state from local ssz files") + } + node.GenesisProviders = append(node.GenesisProviders, provider) + return nil + } + return []node.Option{opt}, nil + } + remoteURL := c.String(BeaconAPIURL.Name) if remoteURL == "" && c.String(checkpoint.RemoteURL.Name) != "" { log.Infof("Using checkpoint sync url %s for value in --%s flag", c.String(checkpoint.RemoteURL.Name), BeaconAPIURL.Name) @@ -36,26 +46,16 @@ func BeaconNodeOptions(c *cli.Context) ([]node.Option, error) { } if remoteURL != "" { opt := func(node *node.BeaconNode) error { - var err error - node.GenesisInitializer, err = genesis.NewAPIInitializer(remoteURL) + provider, err := genesis.NewAPIProvider(remoteURL) if err != nil { return errors.Wrap(err, "error constructing beacon node api client for genesis state init") } + + node.GenesisProviders = append(node.GenesisProviders, provider) return nil } return []node.Option{opt}, nil } - if statePath == "" { - return nil, nil - } - - opt := func(node *node.BeaconNode) (err error) { - node.GenesisInitializer, err = genesis.NewFileInitializer(statePath) - if err != nil { - return errors.Wrap(err, "error preparing to initialize genesis db state from local ssz files") - } - return nil - } - return []node.Option{opt}, nil + return nil, nil } diff --git a/cmd/beacon-chain/main.go b/cmd/beacon-chain/main.go index 2e63486266..80df071880 100644 --- a/cmd/beacon-chain/main.go +++ b/cmd/beacon-chain/main.go @@ -15,12 +15,12 @@ import ( dbcommands "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/db" "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/execution" "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/flags" + "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/genesis" jwtcommands "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/jwt" "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/storage" backfill "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/sync/backfill" bflags "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/sync/backfill/flags" "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/sync/checkpoint" - "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/sync/genesis" "github.com/OffchainLabs/prysm/v6/config/features" "github.com/OffchainLabs/prysm/v6/io/file" "github.com/OffchainLabs/prysm/v6/io/logs" diff --git a/cmd/beacon-chain/usage.go b/cmd/beacon-chain/usage.go index eb3ea6cd51..fa1363243a 100644 --- a/cmd/beacon-chain/usage.go +++ b/cmd/beacon-chain/usage.go @@ -7,10 +7,10 @@ import ( "github.com/OffchainLabs/prysm/v6/cmd" "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/flags" + "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/genesis" "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/storage" backfill "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/sync/backfill/flags" "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/sync/checkpoint" - "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/sync/genesis" "github.com/OffchainLabs/prysm/v6/config/features" "github.com/OffchainLabs/prysm/v6/runtime/debug" "github.com/urfave/cli/v2" diff --git a/config/params/BUILD.bazel b/config/params/BUILD.bazel index 1a29916c2f..f0f3719ff6 100644 --- a/config/params/BUILD.bazel +++ b/config/params/BUILD.bazel @@ -14,6 +14,7 @@ go_library( "mainnet_config.go", "minimal_config.go", "network_config.go", + "opts.go", "testnet_custom_network_config.go", "testnet_e2e_config.go", "testnet_holesky_config.go", @@ -70,9 +71,9 @@ go_test( gotags = ["develop"], tags = ["CI_race_detection"], deps = [ - "//beacon-chain/state/genesis:go_default_library", "//consensus-types/primitives:go_default_library", "//encoding/bytesutil:go_default_library", + "//genesis:go_default_library", "//io/file:go_default_library", "//testing/assert:go_default_library", "//testing/require:go_default_library", diff --git a/config/params/config.go b/config/params/config.go index b2e5f8572c..86ed15fd59 100644 --- a/config/params/config.go +++ b/config/params/config.go @@ -340,6 +340,12 @@ type BlobScheduleEntry struct { MaxBlobsPerBlock uint64 `yaml:"MAX_BLOBS_PER_BLOCK" json:"MAX_BLOBS_PER_BLOCK"` } +func (b *BeaconChainConfig) ApplyOptions(opts ...Option) { + for _, opt := range opts { + opt(b) + } +} + // InitializeForkSchedule initializes the schedules forks baked into the config. func (b *BeaconChainConfig) InitializeForkSchedule() { // Reset Fork Version Schedule. diff --git a/config/params/config_test.go b/config/params/config_test.go index 45187c49a9..95e52f401a 100644 --- a/config/params/config_test.go +++ b/config/params/config_test.go @@ -6,9 +6,9 @@ import ( "sync" "testing" - "github.com/OffchainLabs/prysm/v6/beacon-chain/state/genesis" "github.com/OffchainLabs/prysm/v6/config/params" "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" + "github.com/OffchainLabs/prysm/v6/genesis" "github.com/OffchainLabs/prysm/v6/testing/require" ) @@ -97,14 +97,14 @@ func TestConfig_WithinDAPeriod(t *testing.T) { } func TestConfigGenesisValidatorRoot(t *testing.T) { - g, err := genesis.State(params.MainnetName) - require.NoError(t, err) - - gvr := g.GenesisValidatorsRoot() - - if !bytes.Equal(gvr, params.BeaconConfig().GenesisValidatorsRoot[:]) { + params.SetActiveTestCleanup(t, params.MainnetBeaconConfig) + genesis.StoreEmbeddedDuringTest(t, params.BeaconConfig().ConfigName) + g, err := genesis.State() + require.NoError(t, err, "failed to load genesis state") + if !bytes.Equal(g.GenesisValidatorsRoot(), params.BeaconConfig().GenesisValidatorsRoot[:]) { t.Fatal("mainnet params genesis validator root does not match the mainnet genesis state value") } + require.Equal(t, params.BeaconConfig().GenesisValidatorsRoot, genesis.ValidatorsRoot()) } func TestMaxBlobsPerBlock(t *testing.T) { diff --git a/config/params/opts.go b/config/params/opts.go new file mode 100644 index 0000000000..fc2fd4e686 --- /dev/null +++ b/config/params/opts.go @@ -0,0 +1,16 @@ +package params + +import ( + "fmt" + + log "github.com/sirupsen/logrus" +) + +type Option func(*BeaconChainConfig) + +func WithGenesisValidatorsRoot(gvr [32]byte) Option { + return func(cfg *BeaconChainConfig) { + cfg.GenesisValidatorsRoot = gvr + log.WithField("genesis_validators_root", fmt.Sprintf("%#x", gvr)).Info("Overriding genesis validators root") + } +} diff --git a/encoding/ssz/detect/configfork.go b/encoding/ssz/detect/configfork.go index accf3e06b8..22f366d8b0 100644 --- a/encoding/ssz/detect/configfork.go +++ b/encoding/ssz/detect/configfork.go @@ -292,3 +292,11 @@ func (cf *VersionedUnmarshaler) validateVersion(slot primitives.Slot) error { } return nil } + +func UnmarshalState(marshaled []byte) (state.BeaconState, error) { + vu, err := FromState(marshaled) + if err != nil { + return nil, errors.Wrap(err, "failed to detect version from state") + } + return vu.UnmarshalBeaconState(marshaled) +} diff --git a/encoding/ssz/detect/configfork_test.go b/encoding/ssz/detect/configfork_test.go index cffda3eb24..15432d047a 100644 --- a/encoding/ssz/detect/configfork_test.go +++ b/encoding/ssz/detect/configfork_test.go @@ -728,3 +728,111 @@ func signedTestBlindedBlockFulu(t *testing.T, slot primitives.Slot) interfaces.R require.NoError(t, err) return s } + +func TestUnmarshalStateStandalone(t *testing.T) { + ctx := t.Context() + defer util.HackForksMaxuint(t, []int{version.Electra, version.Fulu})() + + bc := params.BeaconConfig() + altairSlot, err := slots.EpochStart(bc.AltairForkEpoch) + require.NoError(t, err) + bellaSlot, err := slots.EpochStart(bc.BellatrixForkEpoch) + require.NoError(t, err) + capellaSlot, err := slots.EpochStart(bc.CapellaForkEpoch) + require.NoError(t, err) + denebSlot, err := slots.EpochStart(bc.DenebForkEpoch) + require.NoError(t, err) + electraSlot, err := slots.EpochStart(bc.ElectraForkEpoch) + require.NoError(t, err) + fuluSlot, err := slots.EpochStart(bc.FuluForkEpoch) + require.NoError(t, err) + + cases := []struct { + name string + version int + slot primitives.Slot + forkversion [4]byte + }{ + { + name: "phase0", + version: version.Phase0, + slot: 0, + forkversion: bytesutil.ToBytes4(bc.GenesisForkVersion), + }, + { + name: "altair", + version: version.Altair, + slot: altairSlot, + forkversion: bytesutil.ToBytes4(bc.AltairForkVersion), + }, + { + name: "bellatrix", + version: version.Bellatrix, + slot: bellaSlot, + forkversion: bytesutil.ToBytes4(bc.BellatrixForkVersion), + }, + { + name: "capella", + version: version.Capella, + slot: capellaSlot, + forkversion: bytesutil.ToBytes4(bc.CapellaForkVersion), + }, + { + name: "deneb", + version: version.Deneb, + slot: denebSlot, + forkversion: bytesutil.ToBytes4(bc.DenebForkVersion), + }, + { + name: "electra", + version: version.Electra, + slot: electraSlot, + forkversion: bytesutil.ToBytes4(bc.ElectraForkVersion), + }, + { + name: "fulu", + version: version.Fulu, + slot: fuluSlot, + forkversion: bytesutil.ToBytes4(bc.FuluForkVersion), + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + // Create a state for the specific version + originalState, err := stateForVersion(c.version) + require.NoError(t, err) + require.NoError(t, originalState.SetFork(ðpb.Fork{ + PreviousVersion: make([]byte, 4), + CurrentVersion: c.forkversion[:], + Epoch: 0, + })) + require.NoError(t, originalState.SetSlot(c.slot)) + + marshaled, err := originalState.MarshalSSZ() + require.NoError(t, err) + + unmarshaledState, err := UnmarshalState(marshaled) + require.NoError(t, err) + require.NotNil(t, unmarshaledState) + + // Verify the unmarshaled state matches the original + expectedRoot, err := originalState.HashTreeRoot(ctx) + require.NoError(t, err) + actualRoot, err := unmarshaledState.HashTreeRoot(ctx) + require.NoError(t, err) + require.DeepEqual(t, expectedRoot, actualRoot) + + // Verify basic state properties + require.Equal(t, c.slot, unmarshaledState.Slot()) + fork := unmarshaledState.Fork() + require.DeepEqual(t, c.forkversion[:], fork.CurrentVersion) + }) + } + + t.Run("invalid state data", func(t *testing.T) { + invalidData := []byte("bad") + _, err := UnmarshalState(invalidData) + require.ErrorContains(t, "failed to detect version from state", err) + }) +} diff --git a/genesis/BUILD.bazel b/genesis/BUILD.bazel new file mode 100644 index 0000000000..22e4cfe67f --- /dev/null +++ b/genesis/BUILD.bazel @@ -0,0 +1,49 @@ +load("@prysm//tools/go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "embedded.go", + "errors.go", + "initialize.go", + "log.go", + "providers.go", + "storage.go", + "testing.go", + ], + importpath = "github.com/OffchainLabs/prysm/v6/genesis", + visibility = ["//visibility:public"], + deps = [ + "//api/client:go_default_library", + "//api/client/beacon:go_default_library", + "//beacon-chain/state:go_default_library", + "//config/params:go_default_library", + "//encoding/bytesutil:go_default_library", + "//encoding/ssz/detect:go_default_library", + "//genesis/internal/embedded:go_default_library", + "//io/file:go_default_library", + "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", + "@com_github_pkg_errors//:go_default_library", + "@com_github_sirupsen_logrus//:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "embedded_test.go", + "initialize_test.go", + ], + embed = [":go_default_library"], + deps = [ + "//beacon-chain/state:go_default_library", + "//beacon-chain/state/state-native:go_default_library", + "//config/params:go_default_library", + "//consensus-types/primitives:go_default_library", + "//genesis/internal/embedded:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//testing/require:go_default_library", + "//testing/util:go_default_library", + "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", + ], +) diff --git a/genesis/embedded.go b/genesis/embedded.go new file mode 100644 index 0000000000..185491eec9 --- /dev/null +++ b/genesis/embedded.go @@ -0,0 +1,25 @@ +package genesis + +import ( + "time" + + "github.com/OffchainLabs/prysm/v6/beacon-chain/state" + "github.com/OffchainLabs/prysm/v6/config/params" + "github.com/OffchainLabs/prysm/v6/genesis/internal/embedded" +) + +var embeddedGenesisData map[string]GenesisData + +func init() { + embeddedGenesisData = make(map[string]GenesisData) + embeddedGenesisData[params.MainnetName] = GenesisData{ + ValidatorsRoot: [32]byte{75, 54, 61, 185, 78, 40, 97, 32, 215, 110, 185, 5, 52, 15, 221, 78, 84, 191, 233, 240, 107, 243, 63, 246, 207, 90, 210, 127, 81, 27, 254, 149}, + Time: time.Unix(1606824023, 0), + embeddedBytes: func() ([]byte, error) { + return embedded.BytesByName(params.MainnetName) + }, + embeddedState: func() (state.BeaconState, error) { + return embedded.ByName(params.MainnetName) + }, + } +} diff --git a/genesis/embedded_test.go b/genesis/embedded_test.go new file mode 100644 index 0000000000..7b7ed87243 --- /dev/null +++ b/genesis/embedded_test.go @@ -0,0 +1,19 @@ +package genesis + +import ( + "testing" + + "github.com/OffchainLabs/prysm/v6/config/params" + "github.com/OffchainLabs/prysm/v6/genesis/internal/embedded" + "github.com/OffchainLabs/prysm/v6/testing/require" +) + +func TestEmbededGenesisDataMatchesMainnet(t *testing.T) { + st, err := embedded.ByName(params.MainnetName) + require.NoError(t, err) + gvr := st.GenesisValidatorsRoot() + + data := embeddedGenesisData[params.MainnetName] + require.DeepEqual(t, gvr, data.ValidatorsRoot[:]) + require.Equal(t, st.GenesisTime(), data.Time) +} diff --git a/genesis/errors.go b/genesis/errors.go new file mode 100644 index 0000000000..d9daadcf43 --- /dev/null +++ b/genesis/errors.go @@ -0,0 +1,8 @@ +package genesis + +import "errors" + +var ErrFilePathUnset = errors.New("path to genesis data directory is not set") +var ErrGenesisStateNotInitialized = errors.New("genesis state has not been initialized") +var ErrNotGenesisStateFile = errors.New("file is not a genesis state file") +var ErrGenesisFileNotFound = errors.New("genesis state file not found") diff --git a/genesis/initialize.go b/genesis/initialize.go new file mode 100644 index 0000000000..e5690387de --- /dev/null +++ b/genesis/initialize.go @@ -0,0 +1,111 @@ +package genesis + +import ( + "context" + "fmt" + "os" + "strconv" + "strings" + "time" + + "github.com/OffchainLabs/prysm/v6/beacon-chain/state" + "github.com/OffchainLabs/prysm/v6/config/params" + "github.com/OffchainLabs/prysm/v6/encoding/bytesutil" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" +) + +// Initialize is mainly exported for the node initialization process to specify providers of the genesis data +// and the path to the local storage location via cli flags. +func Initialize(ctx context.Context, dir string, providers ...Provider) error { + emb, ok := embeddedGenesisData[params.BeaconConfig().ConfigName] + if ok { + setPkgVar(emb, true) + return nil + } + gd, err := findGenesisFile(dir) + if err == nil { + setPkgVar(gd, true) + return nil + } + if !errors.Is(err, ErrGenesisFileNotFound) { + return err + } + return initializeFromProviders(ctx, dir, providers...) +} + +func initializeFromProviders(ctx context.Context, dir string, providers ...Provider) error { + for _, get := range providers { + gs, err := get.Genesis(ctx) + if err != nil { + log.WithField("provider", fmt.Sprintf("%T", get)).Warn("genesis provider failed") + continue + } + gd, err := newGenesisData(gs, dir) + if err != nil { + return errors.Wrapf(err, "new genesis data") + } + return Store(gd) + } + return ErrGenesisStateNotInitialized +} + +func newGenesisData(st state.BeaconState, dir string) (GenesisData, error) { + if state.IsNil(st) { + return GenesisData{}, ErrGenesisStateNotInitialized + } + if dir == "" { + return GenesisData{}, ErrFilePathUnset + } + return GenesisData{ + FileDir: dir, + State: st, + ValidatorsRoot: bytesutil.ToBytes32(st.GenesisValidatorsRoot()), + Time: st.GenesisTime(), + }, nil +} + +func findGenesisFile(dir string) (GenesisData, error) { + if dir == "" { + return GenesisData{}, ErrFilePathUnset + } + files, err := os.ReadDir(dir) + if err != nil { + return GenesisData{}, fmt.Errorf("%w: %w", ErrGenesisFileNotFound, err) + } + for _, f := range files { + gd, err := tryParseFname(dir, f) + if err != nil { + continue + } + return gd, nil + } + return GenesisData{}, ErrGenesisFileNotFound +} + +func tryParseFname(dir string, f os.DirEntry) (GenesisData, error) { + gd := GenesisData{FileDir: dir} + if f.IsDir() { + return gd, ErrNotGenesisStateFile + } + extParts := strings.Split(f.Name(), ".") + if len(extParts) != 2 || extParts[1] != "ssz" { + return gd, ErrNotGenesisStateFile + } + parts := strings.Split(extParts[0], "-") + if len(parts) != 3 || parts[genesisPart] != "genesis" { + return gd, ErrNotGenesisStateFile + } + ts, err := strconv.ParseInt(parts[timePart], 10, 64) + if err != nil { + return gd, errors.Wrap(err, "parse genesis time") + } + if ts < 0 { + return gd, errors.New("genesis time cannot be negative") + } + gd.Time = time.Unix(ts, 0) + if err := hexutil.UnmarshalFixedText("genesis_validators_root", []byte(parts[gvrPart]), gd.ValidatorsRoot[:]); err != nil { + return gd, errors.Wrap(err, "unmarshal genesis validators root") + } + return gd, nil +} diff --git a/genesis/initialize_test.go b/genesis/initialize_test.go new file mode 100644 index 0000000000..36e6b54535 --- /dev/null +++ b/genesis/initialize_test.go @@ -0,0 +1,312 @@ +package genesis_test + +import ( + "context" + "errors" + "os" + "path/filepath" + "testing" + "time" + + "github.com/OffchainLabs/prysm/v6/beacon-chain/state" + state_native "github.com/OffchainLabs/prysm/v6/beacon-chain/state/state-native" + "github.com/OffchainLabs/prysm/v6/config/params" + "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" + "github.com/OffchainLabs/prysm/v6/genesis" + ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" + "github.com/OffchainLabs/prysm/v6/testing/require" + "github.com/OffchainLabs/prysm/v6/testing/util" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +func TestInitialize(t *testing.T) { + require.NoError(t, genesis.Initialize(t.Context(), "testdata")) + require.Equal(t, params.MainnetName, params.BeaconConfig().ConfigName) + +} + +func TestEmbeddedMainnetHardcodedValues(t *testing.T) { + // Initialize genesis with mainnet config to load embedded state + require.NoError(t, genesis.Initialize(t.Context(), t.TempDir())) + + // Get the initialized genesis state + state, err := genesis.State() + require.NoError(t, err) + require.NotNil(t, state) + + // Verify hardcoded validators root matches the computed value from the state + expectedValidatorsRoot := [32]byte{75, 54, 61, 185, 78, 40, 97, 32, 215, 110, 185, 5, 52, 15, 221, 78, 84, 191, 233, 240, 107, 243, 63, 246, 207, 90, 210, 127, 81, 27, 254, 149} + actualValidatorsRoot := state.GenesisValidatorsRoot() + require.Equal(t, expectedValidatorsRoot, [32]byte(actualValidatorsRoot), "hardcoded validators root does not match embedded state") + + // Verify hardcoded genesis time matches the computed value from the state + expectedTime := time.Unix(1606824023, 0) + actualTime := state.GenesisTime() + require.Equal(t, expectedTime, actualTime, "hardcoded genesis time does not match embedded state") +} + +// mockProvider is a test provider for genesis state +type mockProvider struct { + name string + err error + state state.BeaconState +} + +func (m *mockProvider) Genesis(context.Context) (state.BeaconState, error) { + if m.err != nil { + return nil, m.err + } + return m.state, nil +} + +// createTestGenesisState creates a deterministic genesis state for testing. +// This avoids using the embedded mainnet state which could cause false positives. +func createTestGenesisState(t *testing.T, numValidators uint64, slot primitives.Slot) state.BeaconState { + // Create a deterministic genesis state using test utilities + deposits, _, err := util.DeterministicDepositsAndKeys(numValidators) + require.NoError(t, err) + eth1Data, err := util.DeterministicEth1Data(len(deposits)) + require.NoError(t, err) + + // Create a minimal beacon state directly + pb := ðpb.BeaconState{ + Slot: slot, + GenesisTime: uint64(time.Unix(2000000000, 0).Unix()), // Use a different time than mainnet + GenesisValidatorsRoot: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, + Eth1Data: eth1Data, + Validators: make([]*ethpb.Validator, numValidators), + Balances: make([]uint64, numValidators), + Fork: ðpb.Fork{ + PreviousVersion: params.BeaconConfig().GenesisForkVersion, + CurrentVersion: params.BeaconConfig().GenesisForkVersion, + Epoch: 0, + }, + LatestBlockHeader: ðpb.BeaconBlockHeader{ + Slot: 0, + ParentRoot: make([]byte, 32), + StateRoot: make([]byte, 32), + BodyRoot: make([]byte, 32), + }, + BlockRoots: make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot), + StateRoots: make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot), + RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), + Slashings: make([]uint64, params.BeaconConfig().EpochsPerSlashingsVector), + JustificationBits: []byte{0}, + PreviousJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)}, + CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)}, + FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)}, + } + + // Initialize validators and balances + for i := uint64(0); i < numValidators; i++ { + pb.Validators[i] = ðpb.Validator{ + PublicKey: deposits[i].Data.PublicKey, + WithdrawalCredentials: deposits[i].Data.WithdrawalCredentials, + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 0, + ActivationEpoch: 0, + ExitEpoch: params.BeaconConfig().FarFutureEpoch, + WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch, + } + pb.Balances[i] = params.BeaconConfig().MaxEffectiveBalance + } + + // Initialize arrays with proper sizes + for i := 0; i < len(pb.BlockRoots); i++ { + pb.BlockRoots[i] = make([]byte, 32) + } + for i := 0; i < len(pb.StateRoots); i++ { + pb.StateRoots[i] = make([]byte, 32) + } + for i := 0; i < len(pb.RandaoMixes); i++ { + pb.RandaoMixes[i] = make([]byte, 32) + } + + st, err := state_native.InitializeFromProtoUnsafePhase0(pb) + require.NoError(t, err) + return st +} + +func TestInitializeWithProviders(t *testing.T) { + originalConfig := params.BeaconConfig().Copy() + defer params.OverrideBeaconConfig(originalConfig) + + t.Run("providers_used_when_no_embedded_or_file", func(t *testing.T) { + // Use a custom config that won't have embedded data + customConfig := params.MainnetConfig().Copy() + customConfig.ConfigName = "test-config-no-embedded" + params.OverrideBeaconConfig(customConfig) + + // Use a deterministic test state instead of mainnet to avoid false positives + testState := createTestGenesisState(t, 64, 0) + + provider := &mockProvider{ + state: testState, + name: "test-provider", + } + + err := genesis.Initialize(t.Context(), t.TempDir(), provider) + require.NoError(t, err) + + // Verify the state was stored + storedState, err := genesis.State() + require.NoError(t, err) + require.NotNil(t, storedState) + require.DeepEqual(t, testState.GenesisValidatorsRoot(), storedState.GenesisValidatorsRoot()) + // Verify it's not the mainnet state + require.NotEqual(t, uint64(1606824023), storedState.GenesisTime().Unix()) + }) + + t.Run("multiple_providers_first_success_wins", func(t *testing.T) { + // Use a custom config that won't have embedded data + customConfig := params.MainnetConfig().Copy() + customConfig.ConfigName = "test-config-multiple-providers" + params.OverrideBeaconConfig(customConfig) + + // Use deterministic test states with different slots + state1 := createTestGenesisState(t, 64, 50) + state2 := createTestGenesisState(t, 32, 100) + + // Create providers - first fails, second succeeds + failingProvider := &mockProvider{ + err: errors.New("provider failed"), + name: "failing-provider", + } + successProvider1 := &mockProvider{ + state: state1, + name: "success-provider-1", + } + successProvider2 := &mockProvider{ + state: state2, + name: "success-provider-2", + } + + // Initialize with multiple providers + err := genesis.Initialize(t.Context(), t.TempDir(), failingProvider, successProvider1, successProvider2) + require.NoError(t, err) + + // Verify first successful provider's state was used + storedState, err := genesis.State() + require.NoError(t, err) + require.NotNil(t, storedState) + // state1 has slot 50, state2 has slot 100 + require.Equal(t, primitives.Slot(50), storedState.Slot()) + // Verify it's state1 by checking validator count + require.Equal(t, 64, storedState.NumValidators()) + }) + + t.Run("all_providers_fail_returns_error", func(t *testing.T) { + // Use a custom config that won't have embedded data + customConfig := params.MinimalSpecConfig().Copy() + customConfig.ConfigName = "test-config-all-fail" + params.OverrideBeaconConfig(customConfig) + + // Create failing providers + provider1 := &mockProvider{ + err: errors.New("provider 1 failed"), + name: "failing-provider-1", + } + provider2 := &mockProvider{ + err: errors.New("provider 2 failed"), + name: "failing-provider-2", + } + + // Initialize should fail when all providers fail + err := genesis.Initialize(t.Context(), t.TempDir(), provider1, provider2) + require.ErrorIs(t, err, genesis.ErrGenesisStateNotInitialized) + }) + + t.Run("no_providers_and_no_data_returns_error", func(t *testing.T) { + // Use a custom config that won't have embedded data + customConfig := params.MinimalSpecConfig().Copy() + customConfig.ConfigName = "test-config-no-providers" + params.OverrideBeaconConfig(customConfig) + + // Initialize with no providers should fail + err := genesis.Initialize(t.Context(), t.TempDir()) + require.ErrorIs(t, err, genesis.ErrGenesisStateNotInitialized) + }) + + t.Run("provider_returns_nil_state", func(t *testing.T) { + // Use a custom config that won't have embedded data + customConfig := params.MinimalSpecConfig().Copy() + customConfig.ConfigName = "test-config-nil-state" + params.OverrideBeaconConfig(customConfig) + + // Create provider that returns nil state + provider := &mockProvider{ + state: nil, + name: "nil-state-provider", + } + + // Initialize should fail + err := genesis.Initialize(t.Context(), t.TempDir(), provider) + require.ErrorIs(t, err, genesis.ErrGenesisStateNotInitialized) + }) + + t.Run("empty_dir_path_with_providers", func(t *testing.T) { + // Use a custom config that won't have embedded data + customConfig := params.MainnetConfig().Copy() + customConfig.ConfigName = "test-config-empty-dir" + params.OverrideBeaconConfig(customConfig) + + // Use a deterministic test state + testState := createTestGenesisState(t, 16, 0) + + // Create successful provider + provider := &mockProvider{ + state: testState, + name: "test-provider", + } + + // Initialize with empty dir should fail + err := genesis.Initialize(t.Context(), "", provider) + require.ErrorIs(t, err, genesis.ErrFilePathUnset) + }) + + t.Run("genesis_file_takes_precedence_over_providers", func(t *testing.T) { + // Create temp directory with genesis file + tmpDir := t.TempDir() + + // Use a custom config that won't have embedded data + customConfig := params.MainnetConfig().Copy() + customConfig.ConfigName = "test-config-file-precedence" + params.OverrideBeaconConfig(customConfig) + + // Create deterministic test states for file and provider + fileState := createTestGenesisState(t, 128, 75) + fileTime := time.Unix(1234567890, 0) + require.NoError(t, fileState.SetGenesisTime(fileTime)) + + // Save state to file with proper naming convention + marshaled, err := fileState.MarshalSSZ() + require.NoError(t, err) + + gvr := fileState.GenesisValidatorsRoot() + gvrHex := hexutil.Encode(gvr) + filename := filepath.Join(tmpDir, "genesis-1234567890-"+gvrHex+".ssz") + err = os.WriteFile(filename, marshaled, 0644) + require.NoError(t, err) + + // Create a provider with different state + providerState := createTestGenesisState(t, 256, 200) + provider := &mockProvider{ + state: providerState, + name: "test-provider", + } + + // Initialize should use file, not provider + err = genesis.Initialize(t.Context(), tmpDir, provider) + require.NoError(t, err) + + // Verify file state was used, not provider state + storedState, err := genesis.State() + require.NoError(t, err) + require.NotNil(t, storedState) + // fileState has slot 75 and 128 validators, providerState has slot 200 and 256 validators + require.Equal(t, primitives.Slot(75), storedState.Slot()) + require.Equal(t, 128, storedState.NumValidators()) + require.Equal(t, fileTime, storedState.GenesisTime()) + }) +} diff --git a/beacon-chain/state/genesis/BUILD.bazel b/genesis/internal/embedded/BUILD.bazel similarity index 71% rename from beacon-chain/state/genesis/BUILD.bazel rename to genesis/internal/embedded/BUILD.bazel index 13730fb33a..957c7a63db 100644 --- a/beacon-chain/state/genesis/BUILD.bazel +++ b/genesis/internal/embedded/BUILD.bazel @@ -3,14 +3,13 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ - "genesis.go", - "genesis_mainnet.go", + "lookup.go", + "mainnet.go", ], embedsrcs = ["mainnet.ssz.snappy"], - importpath = "github.com/OffchainLabs/prysm/v6/beacon-chain/state/genesis", + importpath = "github.com/OffchainLabs/prysm/v6/genesis/internal/embedded", visibility = [ - "//beacon-chain/db:__subpackages__", - "//config/params:__pkg__", + "//genesis:__pkg__", ], deps = [ "//beacon-chain/state:go_default_library", @@ -23,7 +22,7 @@ go_library( go_test( name = "go_default_test", - srcs = ["genesis_test.go"], + srcs = ["lookup_test.go"], deps = [ ":go_default_library", "//config/params:go_default_library", diff --git a/genesis/internal/embedded/lookup.go b/genesis/internal/embedded/lookup.go new file mode 100644 index 0000000000..975963ef8d --- /dev/null +++ b/genesis/internal/embedded/lookup.go @@ -0,0 +1,65 @@ +package embedded + +import ( + "context" + _ "embed" + "errors" + + "github.com/OffchainLabs/prysm/v6/beacon-chain/state" + state_native "github.com/OffchainLabs/prysm/v6/beacon-chain/state/state-native" + "github.com/OffchainLabs/prysm/v6/config/params" + ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" + "github.com/golang/snappy" +) + +var ErrNotFound = errors.New("embedded genesis state not found") + +var embeddedStates = map[string]*[]byte{} + +// ByName returns a copy of the genesis state from a hardcoded value. +func ByName(name string) (state.BeaconState, error) { + sb, exists := embeddedStates[name] + if exists { + return load(*sb) + } + return nil, nil +} + +func BytesByName(name string) ([]byte, error) { + sb, exists := embeddedStates[name] + if exists { + return *sb, nil + } + return nil, ErrNotFound +} + +func Has(name string) bool { + _, exists := embeddedStates[name] + return exists +} + +// load a compressed ssz state file into a beacon state struct. +func load(b []byte) (state.BeaconState, error) { + st := ðpb.BeaconState{} + b, err := snappy.Decode(nil /*dst*/, b) + if err != nil { + return nil, err + } + if err := st.UnmarshalSSZ(b); err != nil { + return nil, err + } + return state_native.InitializeFromProtoUnsafePhase0(st) +} + +type embeddedProvider struct{} + +func (p embeddedProvider) Genesis(ctx context.Context) (state.BeaconState, error) { + // Use the mainnet genesis state as default + st, err := ByName(params.BeaconConfig().ConfigName) + if err == nil && st == nil { + return nil, ErrNotFound + } + return st, nil +} + +var EmbeddedProvider = &embeddedProvider{} diff --git a/beacon-chain/state/genesis/genesis_test.go b/genesis/internal/embedded/lookup_test.go similarity index 78% rename from beacon-chain/state/genesis/genesis_test.go rename to genesis/internal/embedded/lookup_test.go index 24ab28de9a..4be626a703 100644 --- a/beacon-chain/state/genesis/genesis_test.go +++ b/genesis/internal/embedded/lookup_test.go @@ -1,10 +1,10 @@ -package genesis_test +package embedded_test import ( "testing" - "github.com/OffchainLabs/prysm/v6/beacon-chain/state/genesis" "github.com/OffchainLabs/prysm/v6/config/params" + "github.com/OffchainLabs/prysm/v6/genesis/internal/embedded" ) func TestGenesisState(t *testing.T) { @@ -17,7 +17,7 @@ func TestGenesisState(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - st, err := genesis.State(tt.name) + st, err := embedded.ByName(tt.name) if err != nil { t.Fatal(err) } diff --git a/beacon-chain/state/genesis/genesis_mainnet.go b/genesis/internal/embedded/mainnet.go similarity index 94% rename from beacon-chain/state/genesis/genesis_mainnet.go rename to genesis/internal/embedded/mainnet.go index 4f0850db78..30d45291b4 100644 --- a/beacon-chain/state/genesis/genesis_mainnet.go +++ b/genesis/internal/embedded/mainnet.go @@ -1,7 +1,7 @@ //go:build !noMainnetGenesis // +build !noMainnetGenesis -package genesis +package embedded import ( _ "embed" diff --git a/beacon-chain/state/genesis/mainnet.ssz.snappy b/genesis/internal/embedded/mainnet.ssz.snappy similarity index 100% rename from beacon-chain/state/genesis/mainnet.ssz.snappy rename to genesis/internal/embedded/mainnet.ssz.snappy diff --git a/beacon-chain/sync/genesis/log.go b/genesis/log.go similarity index 100% rename from beacon-chain/sync/genesis/log.go rename to genesis/log.go diff --git a/genesis/providers.go b/genesis/providers.go new file mode 100644 index 0000000000..b0fe1d0c1b --- /dev/null +++ b/genesis/providers.go @@ -0,0 +1,77 @@ +package genesis + +import ( + "context" + "fmt" + "os" + + "github.com/OffchainLabs/prysm/v6/api/client" + "github.com/OffchainLabs/prysm/v6/api/client/beacon" + "github.com/OffchainLabs/prysm/v6/beacon-chain/state" + "github.com/OffchainLabs/prysm/v6/encoding/ssz/detect" + "github.com/pkg/errors" +) + +// Provider is a type that can provide the genesis state for the initialization of the genesis package. +// Examples are getting the state from a beacon node API, reading it from a file, or from the legacy database. +type Provider interface { + Genesis(context.Context) (state.BeaconState, error) +} + +var _ Provider = &FileProvider{} +var _ Provider = &APIProvider{} + +// APIProvider provides a genesis state using the given beacon node API url. +type APIProvider struct { + c *beacon.Client +} + +// NewAPIProvider creates an APIProvider, handling the set up of a beacon node api client. +func NewAPIProvider(beaconNodeHost string) (*APIProvider, error) { + c, err := beacon.NewClient(beaconNodeHost, client.WithMaxBodySize(client.MaxBodySizeState)) + if err != nil { + return nil, errors.Wrapf(err, "unable to parse beacon node url or hostname - %s", beaconNodeHost) + } + return &APIProvider{c: c}, nil +} + +// Genesis satisfies the Provider interface by retrieving the genesis state from the beacon node API and unmarshaling it into a phase0 beacon state. +func (dl *APIProvider) Genesis(ctx context.Context) (state.BeaconState, error) { + sb, err := dl.c.GetState(ctx, beacon.IdGenesis) + if err != nil { + return nil, err + } + return detect.UnmarshalState(sb) +} + +// FileProvider provides the genesis state by reading the given ssz-encoded beacon state file path. +type FileProvider struct { + statePath string +} + +// NewFileProvider validates the given path information and creates a Provider which sources +// the genesis state from an ssz-encoded file on the local filesystem. +func NewFileProvider(statePath string) (*FileProvider, error) { + var err error + if err = existsAndIsFile(statePath); err != nil { + return nil, err + } + // stat just to make sure it actually exists and is a file + return &FileProvider{statePath: statePath}, nil +} + +// Genesis satisfies the Provider interface by reading the genesis state from a file and unmarshaling it. +func (fi *FileProvider) Genesis(_ context.Context) (state.BeaconState, error) { + return stateFromFile(fi.statePath) +} + +func existsAndIsFile(path string) error { + info, err := os.Stat(path) + if err != nil { + return errors.Wrapf(err, "error checking existence of ssz-encoded file %s for genesis state init", path) + } + if info.IsDir() { + return fmt.Errorf("%s is a directory, please specify full path to file", path) + } + return nil +} diff --git a/genesis/storage.go b/genesis/storage.go new file mode 100644 index 0000000000..8879b1077a --- /dev/null +++ b/genesis/storage.go @@ -0,0 +1,178 @@ +package genesis + +import ( + "fmt" + "os" + "path" + "strconv" + "strings" + "sync" + "time" + + "github.com/OffchainLabs/prysm/v6/beacon-chain/state" + "github.com/OffchainLabs/prysm/v6/config/params" + "github.com/OffchainLabs/prysm/v6/encoding/ssz/detect" + "github.com/OffchainLabs/prysm/v6/io/file" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" +) + +// ValidatorsRoot returns the genesis validators root. +func ValidatorsRoot() [32]byte { + return data.ValidatorsRoot +} + +// Time returns the genesis time. +func Time() time.Time { + return data.Time +} + +// State returns the full genesis BeaconState. It can return an error because this value is lazy loaded. +// The returned value will always be a copy of the underlying value. +func State() (state.BeaconState, error) { + st, err := stateInternal() + if state.IsNil(st) || err != nil { + return nil, err + } + return st.Copy(), nil +} + +func stateInternal() (state.BeaconState, error) { + gd := getPkgVar() + if !gd.initialized { + // If the state is not explicitly initialized, try to load embedded states if available. + name := params.BeaconConfig().ConfigName + if ed, ok := embeddedGenesisData[name]; ok { + return ed.embeddedState() + } + return nil, ErrGenesisStateNotInitialized + } + if !state.IsNil(gd.State) { + return gd.State, nil + } + if gd.embeddedState != nil { + st, err := gd.embeddedState() + if err != nil { + return nil, errors.Wrap(err, "load embedded genesis state") + } + if !state.IsNil(st) { + gd.State = st + return gd.State, nil + } + } + return loadState() +} + +// Store is an exported method that allows another package to set the genesis data value and persist it to disk. +// It is exported to be used by implementations of the Provider interface. +func Store(d GenesisData) error { + if err := ensureWritable(d.FileDir); err != nil { + return err + } + if err := persist(d); err != nil { + return errors.Wrap(err, "persist genesis data") + } + setPkgVar(d, true) + return nil +} + +type fnamePart int + +const ( + genesisPart fnamePart = 0 + timePart fnamePart = 1 + gvrPart fnamePart = 2 +) + +// data is a private package level variable that holds the genesis data. +// Other packages interact with it via wrapper functions like Set() and State(). +var data GenesisData +var stateMu sync.Mutex + +// GenesisData bundles all the package level data. It is exported to allow implementations of the Provider interface to set genesis data. +type GenesisData struct { + ValidatorsRoot [32]byte + Time time.Time + FileDir string + State state.BeaconState + embeddedBytes func() ([]byte, error) + embeddedState func() (state.BeaconState, error) + initialized bool +} + +func (d GenesisData) filePath() string { + parts := [3]string{} + parts[genesisPart] = "genesis" + parts[timePart] = strconv.FormatInt(d.Time.Unix(), 10) + parts[gvrPart] = hexutil.Encode(d.ValidatorsRoot[:]) + return path.Join(d.FileDir, strings.Join(parts[:], "-")+".ssz") +} + +func persist(d GenesisData) error { + if state.IsNil(d.State) { + return ErrGenesisStateNotInitialized + } + if d.FileDir == "" { + return ErrFilePathUnset + } + fpath := d.filePath() + sb, err := d.State.MarshalSSZ() + if err != nil { + return errors.Wrap(err, "marshal ssz") + } + if err := file.WriteFile(fpath, sb); err != nil { + return fmt.Errorf("error writing genesis state to %s: %w", fpath, err) + } + log.WithField("filePath", fpath).Info("Genesis state written to disk.") + return nil +} + +func getPkgVar() GenesisData { + stateMu.Lock() + defer stateMu.Unlock() + return data +} + +func setPkgVar(d GenesisData, initialized bool) { + stateMu.Lock() + defer stateMu.Unlock() + d.initialized = initialized + data = d +} + +func loadState() (state.BeaconState, error) { + stateMu.Lock() + defer stateMu.Unlock() + + s, err := stateFromFile(data.filePath()) + if err != nil { + return nil, errors.Wrapf(err, "InitializeFromProtoUnsafePhase0") + } + + data.State = s + return data.State, nil +} + +func stateFromFile(fpath string) (state.BeaconState, error) { + sb, err := file.ReadFileAsBytes(fpath) + if err != nil { + return nil, errors.Wrapf(err, "error reading genesis state from %s", fpath) + } + return detect.UnmarshalState(sb) +} + +func ensureWritable(dir string) (err error) { + if dir == "" { + return ErrFilePathUnset + } + if err := file.MkdirAll(dir); err != nil { + return errors.Wrapf(err, "error creating genesis data directory %s", dir) + } + lockPath := path.Join(dir, "genesis.lock") + defer func() { + if err == nil { + err = os.Remove(lockPath) + } + }() + return os.WriteFile(lockPath, []byte{1}, 0600) +} diff --git a/genesis/testing.go b/genesis/testing.go new file mode 100644 index 0000000000..957642a004 --- /dev/null +++ b/genesis/testing.go @@ -0,0 +1,38 @@ +package genesis + +import ( + "testing" + + "github.com/OffchainLabs/prysm/v6/beacon-chain/state" +) + +// StoreDuringTest temporarily replaces the package level GenesisData with the provided GenesisData +func StoreDuringTest(t *testing.T, gd GenesisData) { + prev := getPkgVar() + t.Cleanup(func() { + setPkgVar(prev, prev.initialized) + }) + setPkgVar(gd, true) +} + +// StoreEmbeddedDuringTest sets the named embedded genesis file as the genesis data for the lifecycle of the current test. +func StoreEmbeddedDuringTest(t *testing.T, name string) { + gd, ok := embeddedGenesisData[name] + if !ok { + t.Fatalf("embedded genesis data for %s not found", name) + } + StoreDuringTest(t, gd) +} + +// StoreStateDuringTest creates and stores genesis data from a beacon state for the duration of a test. +// This is essential for testing components that depend on genesis information being globally available, +// The function automatically cleans up after the test completes, restoring the previous +// genesis state to prevent test interference. Without this setup, many blockchain +// components would fail during testing due to uninitialized genesis data. +func StoreStateDuringTest(t *testing.T, st state.BeaconState) { + gd, err := newGenesisData(st, "testdata") + if err != nil { + t.Fatalf("failed to create genesis data: %v", err) + } + StoreDuringTest(t, gd) +} diff --git a/testing/endtoend/components/BUILD.bazel b/testing/endtoend/components/BUILD.bazel index 7c8601ff6d..4ef4f36620 100644 --- a/testing/endtoend/components/BUILD.bazel +++ b/testing/endtoend/components/BUILD.bazel @@ -24,7 +24,7 @@ go_library( "//beacon-chain/state:go_default_library", "//cmd:go_default_library", "//cmd/beacon-chain/flags:go_default_library", - "//cmd/beacon-chain/sync/genesis:go_default_library", + "//cmd/beacon-chain/genesis:go_default_library", "//cmd/validator/flags:go_default_library", "//config/features:go_default_library", "//config/fieldparams:go_default_library", diff --git a/testing/endtoend/components/beacon_node.go b/testing/endtoend/components/beacon_node.go index 829e77c9bc..0a245afc01 100644 --- a/testing/endtoend/components/beacon_node.go +++ b/testing/endtoend/components/beacon_node.go @@ -15,7 +15,7 @@ import ( "github.com/OffchainLabs/prysm/v6/beacon-chain/state" cmdshared "github.com/OffchainLabs/prysm/v6/cmd" "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/flags" - "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/sync/genesis" + "github.com/OffchainLabs/prysm/v6/cmd/beacon-chain/genesis" "github.com/OffchainLabs/prysm/v6/config/features" "github.com/OffchainLabs/prysm/v6/config/params" "github.com/OffchainLabs/prysm/v6/io/file"