Read lc updates from LCStore in RPC (#15172)

* add lcstore to rpc config and lc server

* read updates from store

* remove unused func

* optimistic tests

* finality tests

* changelog entry

* Update changelog/bastin_read-updates-from-store-in-rpc.md

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* pass lcstore to rpc service

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
Bastin
2025-04-15 17:06:49 +02:00
committed by GitHub
parent c9e8701987
commit b99399c1f1
9 changed files with 157 additions and 1095 deletions

View File

@@ -1011,6 +1011,7 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error {
BlobStorage: b.BlobStorage,
TrackedValidatorsCache: b.trackedValidatorsCache,
PayloadIDCache: b.payloadIDCache,
LCStore: b.lcStore,
})
return b.services.RegisterService(rpcService)

View File

@@ -20,6 +20,7 @@ go_library(
"//beacon-chain/core/feed/block:go_default_library",
"//beacon-chain/core/feed/operation:go_default_library",
"//beacon-chain/core/feed/state:go_default_library",
"//beacon-chain/core/light-client:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/db/filesystem:go_default_library",
"//beacon-chain/execution:go_default_library",

View File

@@ -946,6 +946,7 @@ func (s *Service) lightClientEndpoints(blocker lookup.Blocker, stater lookup.Sta
HeadFetcher: s.cfg.HeadFetcher,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
BeaconDB: s.cfg.BeaconDB,
LCStore: s.cfg.LCStore,
}
const namespace = "lightclient"

View File

@@ -19,7 +19,6 @@ go_library(
"//beacon-chain/rpc/lookup:go_default_library",
"//config/features:go_default_library",
"//config/params:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//encoding/bytesutil:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//network/forks:go_default_library",
@@ -27,7 +26,6 @@ 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",
],
)
@@ -45,12 +43,10 @@ go_test(
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/core/light-client:go_default_library",
"//beacon-chain/db/testing:go_default_library",
"//beacon-chain/rpc/testutil:go_default_library",
"//beacon-chain/state:go_default_library",
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/light-client:go_default_library",
"//consensus-types/primitives:go_default_library",

View File

@@ -1,19 +1,15 @@
package lightclient
import (
"context"
"fmt"
"math"
"net/http"
"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/features"
"github.com/OffchainLabs/prysm/v6/config/params"
"github.com/OffchainLabs/prysm/v6/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v6/monitoring/tracing/trace"
"github.com/OffchainLabs/prysm/v6/network/forks"
@@ -21,7 +17,6 @@ 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"
)
@@ -197,70 +192,32 @@ func (s *Server) GetLightClientFinalityUpdate(w http.ResponseWriter, req *http.R
return
}
ctx, span := trace.StartSpan(req.Context(), "beacon.GetLightClientFinalityUpdate")
_, span := trace.StartSpan(req.Context(), "beacon.GetLightClientFinalityUpdate")
defer span.End()
// Finality update needs super majority of sync committee signatures
minSyncCommitteeParticipants := float64(params.BeaconConfig().MinSyncCommitteeParticipants)
minSignatures := uint64(math.Ceil(minSyncCommitteeParticipants * 2 / 3))
block, err := s.suitableBlock(ctx, minSignatures)
if !shared.WriteBlockFetchError(w, block, err) {
return
}
st, err := s.Stater.StateBySlot(ctx, block.Block().Slot())
if err != nil {
httputil.HandleError(w, "Could not get state: "+err.Error(), http.StatusInternalServerError)
return
}
attestedRoot := block.Block().ParentRoot()
attestedBlock, err := s.Blocker.Block(ctx, attestedRoot[:])
if !shared.WriteBlockFetchError(w, block, errors.Wrap(err, "could not get attested block")) {
return
}
attestedSlot := attestedBlock.Block().Slot()
attestedState, err := s.Stater.StateBySlot(ctx, attestedSlot)
if err != nil {
httputil.HandleError(w, "Could not get attested state: "+err.Error(), http.StatusInternalServerError)
return
}
var finalizedBlock interfaces.ReadOnlySignedBeaconBlock
finalizedCheckpoint := attestedState.FinalizedCheckpoint()
if finalizedCheckpoint == nil {
httputil.HandleError(w, "Attested state does not have a finalized checkpoint", http.StatusInternalServerError)
return
}
finalizedRoot := bytesutil.ToBytes32(finalizedCheckpoint.Root)
finalizedBlock, err = s.Blocker.Block(ctx, finalizedRoot[:])
if !shared.WriteBlockFetchError(w, block, errors.Wrap(err, "could not get finalized block")) {
return
}
update, err := lightclient.NewLightClientFinalityUpdateFromBeaconState(ctx, s.ChainInfoFetcher.CurrentSlot(), st, block, attestedState, attestedBlock, finalizedBlock)
if err != nil {
httputil.HandleError(w, "Could not get light client finality update: "+err.Error(), http.StatusInternalServerError)
update := s.LCStore.LastFinalityUpdate()
if update == nil {
httputil.HandleError(w, "No light client finality update available", http.StatusNotFound)
return
}
w.Header().Set(api.VersionHeader, version.String(update.Version()))
if httputil.RespondWithSsz(req) {
ssz, err := update.MarshalSSZ()
data, err := update.MarshalSSZ()
if err != nil {
httputil.HandleError(w, "Could not marshal finality update to SSZ: "+err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, ssz)
httputil.WriteSsz(w, data)
} else {
updateStruct, err := structs.LightClientFinalityUpdateFromConsensus(update)
data, err := structs.LightClientFinalityUpdateFromConsensus(update)
if err != nil {
httputil.HandleError(w, "Could not convert light client finality update to API struct: "+err.Error(), http.StatusInternalServerError)
return
}
response := &structs.LightClientFinalityUpdateResponse{
Version: version.String(attestedState.Version()),
Data: updateStruct,
Version: version.String(update.Version()),
Data: data,
}
httputil.WriteJson(w, response)
}
@@ -273,111 +230,33 @@ func (s *Server) GetLightClientOptimisticUpdate(w http.ResponseWriter, req *http
return
}
ctx, span := trace.StartSpan(req.Context(), "beacon.GetLightClientOptimisticUpdate")
_, span := trace.StartSpan(req.Context(), "beacon.GetLightClientOptimisticUpdate")
defer span.End()
block, err := s.suitableBlock(ctx, params.BeaconConfig().MinSyncCommitteeParticipants)
if !shared.WriteBlockFetchError(w, block, err) {
return
}
st, err := s.Stater.StateBySlot(ctx, block.Block().Slot())
if err != nil {
httputil.HandleError(w, "could not get state: "+err.Error(), http.StatusInternalServerError)
return
}
attestedRoot := block.Block().ParentRoot()
attestedBlock, err := s.Blocker.Block(ctx, attestedRoot[:])
if err != nil {
httputil.HandleError(w, "Could not get attested block: "+err.Error(), http.StatusInternalServerError)
return
}
if attestedBlock == nil {
httputil.HandleError(w, "Attested block is nil", http.StatusInternalServerError)
return
}
attestedSlot := attestedBlock.Block().Slot()
attestedState, err := s.Stater.StateBySlot(ctx, attestedSlot)
if err != nil {
httputil.HandleError(w, "Could not get attested state: "+err.Error(), http.StatusInternalServerError)
return
}
update, err := lightclient.NewLightClientOptimisticUpdateFromBeaconState(ctx, s.ChainInfoFetcher.CurrentSlot(), st, block, attestedState, attestedBlock)
if err != nil {
httputil.HandleError(w, "Could not get light client optimistic update: "+err.Error(), http.StatusInternalServerError)
update := s.LCStore.LastOptimisticUpdate()
if update == nil {
httputil.HandleError(w, "No light client optimistic update available", http.StatusNotFound)
return
}
w.Header().Set(api.VersionHeader, version.String(update.Version()))
if httputil.RespondWithSsz(req) {
ssz, err := update.MarshalSSZ()
data, err := update.MarshalSSZ()
if err != nil {
httputil.HandleError(w, "Could not marshal optimistic update to SSZ: "+err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, ssz)
httputil.WriteSsz(w, data)
} else {
updateStruct, err := structs.LightClientOptimisticUpdateFromConsensus(update)
data, err := structs.LightClientOptimisticUpdateFromConsensus(update)
if err != nil {
httputil.HandleError(w, "Could not convert light client optimistic update to API struct: "+err.Error(), http.StatusInternalServerError)
return
}
response := &structs.LightClientOptimisticUpdateResponse{
Version: version.String(attestedState.Version()),
Data: updateStruct,
Version: version.String(update.Version()),
Data: data,
}
httputil.WriteJson(w, response)
}
}
// suitableBlock returns the latest block that satisfies all criteria required for creating a new update
func (s *Server) suitableBlock(ctx context.Context, minSignaturesRequired uint64) (interfaces.ReadOnlySignedBeaconBlock, error) {
st, err := s.HeadFetcher.HeadState(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get head state")
}
latestBlockHeader := st.LatestBlockHeader()
stateRoot, err := st.HashTreeRoot(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get state root")
}
latestBlockHeader.StateRoot = stateRoot[:]
latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not get latest block header root")
}
block, err := s.Blocker.Block(ctx, latestBlockHeaderRoot[:])
if err != nil {
return nil, errors.Wrap(err, "could not get latest block")
}
if block == nil {
return nil, errors.New("latest block is nil")
}
// Loop through the blocks until we find a block that satisfies minSignaturesRequired requirement
var numOfSyncCommitteeSignatures uint64
if syncAggregate, err := block.Block().Body().SyncAggregate(); err == nil {
numOfSyncCommitteeSignatures = syncAggregate.SyncCommitteeBits.Count()
}
for numOfSyncCommitteeSignatures < minSignaturesRequired {
// Get the parent block
parentRoot := block.Block().ParentRoot()
block, err = s.Blocker.Block(ctx, parentRoot[:])
if err != nil {
return nil, errors.Wrap(err, "could not get parent block")
}
if block == nil {
return nil, errors.New("parent block is nil")
}
// Get the number of sync committee signatures
numOfSyncCommitteeSignatures = 0
if syncAggregate, err := block.Block().Body().SyncAggregate(); err == nil {
numOfSyncCommitteeSignatures = syncAggregate.SyncCommitteeBits.Count()
}
}
return block, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ package lightclient
import (
"github.com/OffchainLabs/prysm/v6/beacon-chain/blockchain"
lightClient "github.com/OffchainLabs/prysm/v6/beacon-chain/core/light-client"
"github.com/OffchainLabs/prysm/v6/beacon-chain/db"
"github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/lookup"
)
@@ -12,4 +13,5 @@ type Server struct {
HeadFetcher blockchain.HeadFetcher
ChainInfoFetcher blockchain.ChainInfoFetcher
BeaconDB db.HeadAccessDatabase
LCStore *lightClient.Store
}

View File

@@ -16,6 +16,7 @@ import (
blockfeed "github.com/OffchainLabs/prysm/v6/beacon-chain/core/feed/block"
opfeed "github.com/OffchainLabs/prysm/v6/beacon-chain/core/feed/operation"
statefeed "github.com/OffchainLabs/prysm/v6/beacon-chain/core/feed/state"
lightClient "github.com/OffchainLabs/prysm/v6/beacon-chain/core/light-client"
"github.com/OffchainLabs/prysm/v6/beacon-chain/db"
"github.com/OffchainLabs/prysm/v6/beacon-chain/db/filesystem"
"github.com/OffchainLabs/prysm/v6/beacon-chain/execution"
@@ -121,6 +122,7 @@ type Config struct {
BlobStorage *filesystem.BlobStorage
TrackedValidatorsCache *cache.TrackedValidatorsCache
PayloadIDCache *cache.PayloadIDCache
LCStore *lightClient.Store
}
// NewService instantiates a new RPC service instance that will

View File

@@ -0,0 +1,3 @@
### Added
- Read light client optimistic and finality updates from LCStore instead of computing them in the beacon API handlers.