initialize genesis data asap at node start (#15470)

* initialize genesis data asap at node start

* add genesis validation tests with embedded state verification

* Add test for hardcoded mainnet genesis validator root and time from init() function

* Add test for UnmarshalState in encoding/ssz/detect/configfork.go

* Add tests for genesis.Initialize

* Move genesis/embedded to genesis/internal/embedded

* Gazelle / BUILD fix

* James feedback

* Fix lint

* Revert lock

---------

Co-authored-by: Kasey <kasey@users.noreply.github.com>
Co-authored-by: terence tsao <terence@prysmaticlabs.com>
Co-authored-by: Preston Van Loon <preston@pvl.dev>
This commit is contained in:
kasey
2025-08-09 19:09:40 -07:00
committed by GitHub
parent 921ff23c6b
commit 84c8653a52
54 changed files with 1303 additions and 433 deletions

View File

@@ -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",

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")

View File

@@ -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.

View File

@@ -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)

View File

@@ -13,6 +13,7 @@ go_library(
visibility = [
"//beacon-chain:__subpackages__",
"//cmd/beacon-chain:__subpackages__",
"//genesis:__subpackages__",
"//testing/slasher/simulator:__pkg__",
"//tools:__subpackages__",
],

View File

@@ -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",

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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,

View File

@@ -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

View File

@@ -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()

View File

@@ -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",

View File

@@ -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(), &ethpb.ETH1ChainData{

View File

@@ -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",

View File

@@ -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,
}
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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 := &ethpb.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)
}

View File

@@ -351,3 +351,7 @@ type WriteOnlyDeposits interface {
type WriteOnlyProposerLookahead interface {
SetProposerLookahead([]primitives.ValidatorIndex) error
}
func IsNil(s BeaconState) bool {
return s == nil || s.IsNil()
}

View File

@@ -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()
}

View File

@@ -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",
],
)

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -0,0 +1,3 @@
## Fixed
- Genesis state, timestamp and validators root now ubiquitously available at node startup, supporting tech debt cleanup.

View File

@@ -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",

View File

@@ -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",

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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",

View File

@@ -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.

View File

@@ -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) {

16
config/params/opts.go Normal file
View File

@@ -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")
}
}

View File

@@ -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)
}

View File

@@ -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(&ethpb.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)
})
}

49
genesis/BUILD.bazel Normal file
View File

@@ -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",
],
)

25
genesis/embedded.go Normal file
View File

@@ -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)
},
}
}

19
genesis/embedded_test.go Normal file
View File

@@ -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)
}

8
genesis/errors.go Normal file
View File

@@ -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")

111
genesis/initialize.go Normal file
View File

@@ -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
}

312
genesis/initialize_test.go Normal file
View File

@@ -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 := &ethpb.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: &ethpb.Fork{
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
Epoch: 0,
},
LatestBlockHeader: &ethpb.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: &ethpb.Checkpoint{Root: make([]byte, 32)},
CurrentJustifiedCheckpoint: &ethpb.Checkpoint{Root: make([]byte, 32)},
FinalizedCheckpoint: &ethpb.Checkpoint{Root: make([]byte, 32)},
}
// Initialize validators and balances
for i := uint64(0); i < numValidators; i++ {
pb.Validators[i] = &ethpb.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())
})
}

View File

@@ -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",

View File

@@ -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 := &ethpb.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{}

View File

@@ -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)
}

View File

@@ -1,7 +1,7 @@
//go:build !noMainnetGenesis
// +build !noMainnetGenesis
package genesis
package embedded
import (
_ "embed"

77
genesis/providers.go Normal file
View File

@@ -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
}

178
genesis/storage.go Normal file
View File

@@ -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)
}

38
genesis/testing.go Normal file
View File

@@ -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)
}

View File

@@ -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",

View File

@@ -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"