mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 04:54:05 -05:00
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:
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -13,6 +13,7 @@ go_library(
|
||||
visibility = [
|
||||
"//beacon-chain:__subpackages__",
|
||||
"//cmd/beacon-chain:__subpackages__",
|
||||
"//genesis:__subpackages__",
|
||||
"//testing/slasher/simulator:__pkg__",
|
||||
"//tools:__subpackages__",
|
||||
],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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",
|
||||
|
||||
101
beacon-chain/node/clear_db.go
Normal file
101
beacon-chain/node/clear_db.go
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -351,3 +351,7 @@ type WriteOnlyDeposits interface {
|
||||
type WriteOnlyProposerLookahead interface {
|
||||
SetProposerLookahead([]primitives.ValidatorIndex) error
|
||||
}
|
||||
|
||||
func IsNil(s BeaconState) bool {
|
||||
return s == nil || s.IsNil()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
3
changelog/kasey_init-genesis-asap.md
Normal file
3
changelog/kasey_init-genesis-asap.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Fixed
|
||||
|
||||
- Genesis state, timestamp and validators root now ubiquitously available at node startup, supporting tech debt cleanup.
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
16
config/params/opts.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
49
genesis/BUILD.bazel
Normal file
49
genesis/BUILD.bazel
Normal 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
25
genesis/embedded.go
Normal 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
19
genesis/embedded_test.go
Normal 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
8
genesis/errors.go
Normal 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
111
genesis/initialize.go
Normal 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
312
genesis/initialize_test.go
Normal 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 := ð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())
|
||||
})
|
||||
}
|
||||
@@ -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",
|
||||
65
genesis/internal/embedded/lookup.go
Normal file
65
genesis/internal/embedded/lookup.go
Normal 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 := ð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{}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//go:build !noMainnetGenesis
|
||||
// +build !noMainnetGenesis
|
||||
|
||||
package genesis
|
||||
package embedded
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
77
genesis/providers.go
Normal file
77
genesis/providers.go
Normal 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
178
genesis/storage.go
Normal 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
38
genesis/testing.go
Normal 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)
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user