From 5c1d82733538e69bf4bfe8b77822393b2bb6e93e Mon Sep 17 00:00:00 2001 From: Bastin <43618253+Inspector-Butters@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:07:46 +0200 Subject: [PATCH] Unify LC API 1/2 (bootstrap) (#15476) * add versiotToForkEpoch map * Unify LC API (bootstrap) --- beacon-chain/blockchain/options.go | 8 ++ .../blockchain/process_block_helpers.go | 2 +- beacon-chain/blockchain/process_block_test.go | 131 +++++------------- beacon-chain/core/light-client/BUILD.bazel | 1 + beacon-chain/core/light-client/store.go | 39 +++++- beacon-chain/node/node.go | 2 +- beacon-chain/rpc/eth/light-client/BUILD.bazel | 1 + beacon-chain/rpc/eth/light-client/handlers.go | 14 +- .../rpc/eth/light-client/handlers_test.go | 17 ++- beacon-chain/sync/rpc_light_client.go | 2 +- beacon-chain/sync/rpc_light_client_test.go | 4 +- changelog/bastin_unify-lc-api-bootstrap.md | 3 + 12 files changed, 102 insertions(+), 122 deletions(-) create mode 100644 changelog/bastin_unify-lc-api-bootstrap.md diff --git a/beacon-chain/blockchain/options.go b/beacon-chain/blockchain/options.go index 5d15f94b0d..d628d8d42e 100644 --- a/beacon-chain/blockchain/options.go +++ b/beacon-chain/blockchain/options.go @@ -33,6 +33,14 @@ func WithMaxGoroutines(x int) Option { } } +// WithLCStore for light client store access. +func WithLCStore() Option { + return func(s *Service) error { + s.lcStore = lightclient.NewLightClientStore(s.cfg.BeaconDB) + return nil + } +} + // WithWeakSubjectivityCheckpoint for checkpoint sync. func WithWeakSubjectivityCheckpoint(c *ethpb.Checkpoint) Option { return func(s *Service) error { diff --git a/beacon-chain/blockchain/process_block_helpers.go b/beacon-chain/blockchain/process_block_helpers.go index 082b37d6dc..d96cf5e92f 100644 --- a/beacon-chain/blockchain/process_block_helpers.go +++ b/beacon-chain/blockchain/process_block_helpers.go @@ -226,7 +226,7 @@ func (s *Service) processLightClientBootstrap(cfg *postBlockProcessConfig) error if err != nil { return errors.Wrapf(err, "could not create light client bootstrap") } - if err := s.cfg.BeaconDB.SaveLightClientBootstrap(cfg.ctx, blockRoot[:], bootstrap); err != nil { + if err := s.lcStore.SaveLightClientBootstrap(cfg.ctx, blockRoot, bootstrap); err != nil { return errors.Wrapf(err, "could not save light client bootstrap") } return nil diff --git a/beacon-chain/blockchain/process_block_test.go b/beacon-chain/blockchain/process_block_test.go index be045c9e2e..a1937f84d2 100644 --- a/beacon-chain/blockchain/process_block_test.go +++ b/beacon-chain/blockchain/process_block_test.go @@ -3194,116 +3194,47 @@ func TestProcessLightClientBootstrap(t *testing.T) { featCfg := &features.Flags{} featCfg.EnableLightClient = true reset := features.InitWithReset(featCfg) + defer reset() - s, tr := minimalTestService(t) + s, tr := minimalTestService(t, WithLCStore()) ctx := tr.ctx - t.Run("Altair", func(t *testing.T) { - l := util.NewTestLightClient(t, version.Altair) + for testVersion := version.Altair; testVersion <= version.Electra; testVersion++ { + t.Run(version.String(testVersion), func(t *testing.T) { + l := util.NewTestLightClient(t, testVersion) - s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().AltairForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0) + s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().VersionToForkEpochMap()[testVersion])*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0) - currentBlockRoot, err := l.Block.Block().HashTreeRoot() - require.NoError(t, err) - roblock, err := consensusblocks.NewROBlockWithRoot(l.Block, currentBlockRoot) - require.NoError(t, err) + currentBlockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + roblock, err := consensusblocks.NewROBlockWithRoot(l.Block, currentBlockRoot) + require.NoError(t, err) - err = s.cfg.BeaconDB.SaveBlock(ctx, roblock) - require.NoError(t, err) - err = s.cfg.BeaconDB.SaveState(ctx, l.State, currentBlockRoot) - require.NoError(t, err) + err = s.cfg.BeaconDB.SaveBlock(ctx, roblock) + require.NoError(t, err) + err = s.cfg.BeaconDB.SaveState(ctx, l.State, currentBlockRoot) + require.NoError(t, err) - cfg := &postBlockProcessConfig{ - ctx: ctx, - roblock: roblock, - postState: l.State, - isValidPayload: true, - } + cfg := &postBlockProcessConfig{ + ctx: ctx, + roblock: roblock, + postState: l.State, + isValidPayload: true, + } - require.NoError(t, s.processLightClientBootstrap(cfg)) + require.NoError(t, s.processLightClientBootstrap(cfg)) - // Check that the light client bootstrap is saved - b, err := s.cfg.BeaconDB.LightClientBootstrap(ctx, currentBlockRoot[:]) - require.NoError(t, err) - require.NotNil(t, b) + // Check that the light client bootstrap is saved + b, err := s.lcStore.LightClientBootstrap(ctx, currentBlockRoot) + require.NoError(t, err) + require.NotNil(t, b) - stateRoot, err := l.State.HashTreeRoot(ctx) - require.NoError(t, err) - require.Equal(t, stateRoot, [32]byte(b.Header().Beacon().StateRoot)) - require.Equal(t, b.Version(), version.Altair) - }) - - t.Run("Capella", func(t *testing.T) { - l := util.NewTestLightClient(t, version.Capella) - - s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().CapellaForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0) - - currentBlockRoot, err := l.Block.Block().HashTreeRoot() - require.NoError(t, err) - roblock, err := consensusblocks.NewROBlockWithRoot(l.Block, currentBlockRoot) - require.NoError(t, err) - - err = s.cfg.BeaconDB.SaveBlock(ctx, roblock) - require.NoError(t, err) - err = s.cfg.BeaconDB.SaveState(ctx, l.State, currentBlockRoot) - require.NoError(t, err) - - cfg := &postBlockProcessConfig{ - ctx: ctx, - roblock: roblock, - postState: l.State, - isValidPayload: true, - } - - require.NoError(t, s.processLightClientBootstrap(cfg)) - - // Check that the light client bootstrap is saved - b, err := s.cfg.BeaconDB.LightClientBootstrap(ctx, currentBlockRoot[:]) - require.NoError(t, err) - require.NotNil(t, b) - - stateRoot, err := l.State.HashTreeRoot(ctx) - require.NoError(t, err) - require.Equal(t, stateRoot, [32]byte(b.Header().Beacon().StateRoot)) - require.Equal(t, b.Version(), version.Capella) - }) - - t.Run("Deneb", func(t *testing.T) { - l := util.NewTestLightClient(t, version.Deneb) - - s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().DenebForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0) - - currentBlockRoot, err := l.Block.Block().HashTreeRoot() - require.NoError(t, err) - roblock, err := consensusblocks.NewROBlockWithRoot(l.Block, currentBlockRoot) - require.NoError(t, err) - - err = s.cfg.BeaconDB.SaveBlock(ctx, roblock) - require.NoError(t, err) - err = s.cfg.BeaconDB.SaveState(ctx, l.State, currentBlockRoot) - require.NoError(t, err) - - cfg := &postBlockProcessConfig{ - ctx: ctx, - roblock: roblock, - postState: l.State, - isValidPayload: true, - } - - require.NoError(t, s.processLightClientBootstrap(cfg)) - - // Check that the light client bootstrap is saved - b, err := s.cfg.BeaconDB.LightClientBootstrap(ctx, currentBlockRoot[:]) - require.NoError(t, err) - require.NotNil(t, b) - - stateRoot, err := l.State.HashTreeRoot(ctx) - require.NoError(t, err) - require.Equal(t, stateRoot, [32]byte(b.Header().Beacon().StateRoot)) - require.Equal(t, b.Version(), version.Deneb) - }) - - reset() + stateRoot, err := l.State.HashTreeRoot(ctx) + require.NoError(t, err) + require.Equal(t, stateRoot, [32]byte(b.Header().Beacon().StateRoot)) + require.Equal(t, b.Version(), testVersion) + }) + } } type testIsAvailableParams struct { diff --git a/beacon-chain/core/light-client/BUILD.bazel b/beacon-chain/core/light-client/BUILD.bazel index 223c5e53c5..c3ce3f88eb 100644 --- a/beacon-chain/core/light-client/BUILD.bazel +++ b/beacon-chain/core/light-client/BUILD.bazel @@ -10,6 +10,7 @@ go_library( importpath = "github.com/OffchainLabs/prysm/v6/beacon-chain/core/light-client", visibility = ["//visibility:public"], deps = [ + "//beacon-chain/db/iface:go_default_library", "//beacon-chain/execution:go_default_library", "//beacon-chain/state:go_default_library", "//config/fieldparams:go_default_library", diff --git a/beacon-chain/core/light-client/store.go b/beacon-chain/core/light-client/store.go index a122321835..88f26ac159 100644 --- a/beacon-chain/core/light-client/store.go +++ b/beacon-chain/core/light-client/store.go @@ -1,20 +1,55 @@ package light_client import ( + "context" "sync" + "github.com/OffchainLabs/prysm/v6/beacon-chain/db/iface" "github.com/OffchainLabs/prysm/v6/consensus-types/interfaces" + "github.com/pkg/errors" ) +var ErrLightClientBootstrapNotFound = errors.New("light client bootstrap not found") + type Store struct { mu sync.RWMutex + beaconDB iface.HeadAccessDatabase lastFinalityUpdate interfaces.LightClientFinalityUpdate lastOptimisticUpdate interfaces.LightClientOptimisticUpdate } -func NewLightClientStore() *Store { - return &Store{} +func NewLightClientStore(db iface.HeadAccessDatabase) *Store { + return &Store{ + beaconDB: db, + } +} + +func (s *Store) LightClientBootstrap(ctx context.Context, blockRoot [32]byte) (interfaces.LightClientBootstrap, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + // Fetch the light client bootstrap from the database + bootstrap, err := s.beaconDB.LightClientBootstrap(ctx, blockRoot[:]) + if err != nil { + return nil, err + } + if bootstrap == nil { // not found + return nil, ErrLightClientBootstrapNotFound + } + + return bootstrap, nil +} + +func (s *Store) SaveLightClientBootstrap(ctx context.Context, blockRoot [32]byte, bootstrap interfaces.LightClientBootstrap) error { + s.mu.Lock() + defer s.mu.Unlock() + + // Save the light client bootstrap to the database + if err := s.beaconDB.SaveLightClientBootstrap(ctx, blockRoot[:], bootstrap); err != nil { + return err + } + return nil } func (s *Store) SetLastFinalityUpdate(update interfaces.LightClientFinalityUpdate) { diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index 2b9a2f189e..bd95f4678e 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -235,7 +235,7 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco beacon.finalizedStateAtStartUp = nil if features.Get().EnableLightClient { - beacon.lcStore = lightclient.NewLightClientStore() + beacon.lcStore = lightclient.NewLightClientStore(beacon.db) } return beacon, nil diff --git a/beacon-chain/rpc/eth/light-client/BUILD.bazel b/beacon-chain/rpc/eth/light-client/BUILD.bazel index b3a9ccac93..61d8e17457 100644 --- a/beacon-chain/rpc/eth/light-client/BUILD.bazel +++ b/beacon-chain/rpc/eth/light-client/BUILD.bazel @@ -25,6 +25,7 @@ go_library( "//runtime/version:go_default_library", "//time/slots:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", + "@com_github_pkg_errors//:go_default_library", "@com_github_prysmaticlabs_fastssz//:go_default_library", ], ) diff --git a/beacon-chain/rpc/eth/light-client/handlers.go b/beacon-chain/rpc/eth/light-client/handlers.go index 428a03f7d5..44e4468037 100644 --- a/beacon-chain/rpc/eth/light-client/handlers.go +++ b/beacon-chain/rpc/eth/light-client/handlers.go @@ -6,6 +6,7 @@ import ( "github.com/OffchainLabs/prysm/v6/api" "github.com/OffchainLabs/prysm/v6/api/server/structs" + lightclient "github.com/OffchainLabs/prysm/v6/beacon-chain/core/light-client" "github.com/OffchainLabs/prysm/v6/beacon-chain/core/signing" "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared" "github.com/OffchainLabs/prysm/v6/config/params" @@ -16,6 +17,7 @@ import ( "github.com/OffchainLabs/prysm/v6/runtime/version" "github.com/OffchainLabs/prysm/v6/time/slots" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" ssz "github.com/prysmaticlabs/fastssz" ) @@ -33,13 +35,13 @@ func (s *Server) GetLightClientBootstrap(w http.ResponseWriter, req *http.Reques } blockRoot := bytesutil.ToBytes32(blockRootParam) - bootstrap, err := s.BeaconDB.LightClientBootstrap(ctx, blockRoot[:]) + bootstrap, err := s.LCStore.LightClientBootstrap(ctx, blockRoot) if err != nil { - httputil.HandleError(w, "Could not get light client bootstrap: "+err.Error(), http.StatusInternalServerError) - return - } - if bootstrap == nil { - httputil.HandleError(w, "Light client bootstrap not found", http.StatusNotFound) + if errors.Is(err, lightclient.ErrLightClientBootstrapNotFound) { + httputil.HandleError(w, "Light client bootstrap not found", http.StatusNotFound) + } else { + httputil.HandleError(w, "Could not get light client bootstrap: "+err.Error(), http.StatusInternalServerError) + } return } diff --git a/beacon-chain/rpc/eth/light-client/handlers_test.go b/beacon-chain/rpc/eth/light-client/handlers_test.go index 75f1680710..790a1beb78 100644 --- a/beacon-chain/rpc/eth/light-client/handlers_test.go +++ b/beacon-chain/rpc/eth/light-client/handlers_test.go @@ -53,13 +53,13 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) { bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block) require.NoError(t, err) - db := dbtesting.SetupDB(t) + lcStore := lightclient.NewLightClientStore(dbtesting.SetupDB(t)) - err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[:], bootstrap) + err = lcStore.SaveLightClientBootstrap(l.Ctx, blockRoot, bootstrap) require.NoError(t, err) s := &Server{ - BeaconDB: db, + LCStore: lcStore, } request := httptest.NewRequest("GET", "http://foo.com/", nil) request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) @@ -85,6 +85,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) { require.NotNil(t, resp.Data.CurrentSyncCommittee) require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) }) + t.Run(version.String(testVersion)+"SSZ", func(t *testing.T) { l := util.NewTestLightClient(t, testVersion) @@ -95,13 +96,13 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) { bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block) require.NoError(t, err) - db := dbtesting.SetupDB(t) + lcStore := lightclient.NewLightClientStore(dbtesting.SetupDB(t)) - err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[:], bootstrap) + err = lcStore.SaveLightClientBootstrap(l.Ctx, blockRoot, bootstrap) require.NoError(t, err) s := &Server{ - BeaconDB: db, + LCStore: lcStore, } request := httptest.NewRequest("GET", "http://foo.com/", nil) request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) @@ -138,10 +139,8 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) { } t.Run("no bootstrap found", func(t *testing.T) { - db := dbtesting.SetupDB(t) - s := &Server{ - BeaconDB: db, + LCStore: lightclient.NewLightClientStore(dbtesting.SetupDB(t)), } request := httptest.NewRequest("GET", "http://foo.com/", nil) request.SetPathValue("block_root", hexutil.Encode([]byte{0x00, 0x01, 0x02})) diff --git a/beacon-chain/sync/rpc_light_client.go b/beacon-chain/sync/rpc_light_client.go index f63a1044f2..a7a7c8bfd2 100644 --- a/beacon-chain/sync/rpc_light_client.go +++ b/beacon-chain/sync/rpc_light_client.go @@ -38,7 +38,7 @@ func (s *Service) lightClientBootstrapRPCHandler(ctx context.Context, msg interf } blkRoot := *rawMsg - bootstrap, err := s.cfg.beaconDB.LightClientBootstrap(ctx, blkRoot[:]) + bootstrap, err := s.lcStore.LightClientBootstrap(ctx, blkRoot) if err != nil { s.writeErrorResponseToStream(responseCodeServerError, types.ErrGeneric.Error(), stream) tracing.AnnotateError(span, err) diff --git a/beacon-chain/sync/rpc_light_client_test.go b/beacon-chain/sync/rpc_light_client_test.go index 8a24a737c3..a2f06a2f71 100644 --- a/beacon-chain/sync/rpc_light_client_test.go +++ b/beacon-chain/sync/rpc_light_client_test.go @@ -54,7 +54,7 @@ func TestRPC_LightClientBootstrap(t *testing.T) { stateNotifier: &mockChain.MockStateNotifier{}, }, chainStarted: abool.New(), - lcStore: &lightClient.Store{}, + lcStore: lightClient.NewLightClientStore(d), subHandler: newSubTopicHandler(), rateLimiter: newRateLimiter(p1), } @@ -81,7 +81,7 @@ func TestRPC_LightClientBootstrap(t *testing.T) { blockRoot, err := l.Block.Block().HashTreeRoot() require.NoError(t, err) - require.NoError(t, r.cfg.beaconDB.SaveLightClientBootstrap(ctx, blockRoot[:], bootstrap)) + require.NoError(t, r.lcStore.SaveLightClientBootstrap(ctx, blockRoot, bootstrap)) var wg sync.WaitGroup wg.Add(1) diff --git a/changelog/bastin_unify-lc-api-bootstrap.md b/changelog/bastin_unify-lc-api-bootstrap.md new file mode 100644 index 0000000000..45653278f2 --- /dev/null +++ b/changelog/bastin_unify-lc-api-bootstrap.md @@ -0,0 +1,3 @@ +### Changed + +- Move setter/getter functions for LC Bootstrap into LcStore for a unified interface. \ No newline at end of file