Update lc functions to use the dev branch of CL specs (#14471)

* create finalized header based on finalized block version

* changelog entry

* pass attested block from handlers

* fix core tests

* add test for attested header exectution fields

* changelog entry

* remove unused functions

* update `createLightClientBootstrapXXX` functions

* fix for getBootstrapAltair

* fix for getBootstrapAltair

* fix tests for Capella and Deneb

* update `CHANGELOG.md`

* remove Electra from `createLightClientBootstrap` switch case

* update dependencies

* minor fixes

* remove unnecessary comments

* replace `!reflect.DeepEqual` with `!=`

* replace `%s` with `%#x` for `[32]byte`

---------

Co-authored-by: Inspector-Butters <mohamadbastin@gmail.com>
Co-authored-by: Bastin <43618253+Inspector-Butters@users.noreply.github.com>
This commit is contained in:
Rupam Dey
2024-10-04 02:22:21 +05:30
committed by GitHub
parent 6af44a1466
commit 21ca4e008f
6 changed files with 161 additions and 385 deletions

View File

@@ -42,6 +42,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
- Updated k8s-io/client-go to v0.30.4 and k8s-io/apimachinery to v0.30.4
- Migrated tracing library from opencensus to opentelemetry for both the beacon node and validator.
- Refactored light client code to make it more readable and make future PRs easier.
- Update light client helper functions to reference `dev` branch of CL specs
- Updated Libp2p Dependencies to allow prysm to use gossipsub v1.2 .
- Updated Sepolia bootnodes.

View File

@@ -25,16 +25,6 @@ const (
executionBranchNumOfLeaves = 4
)
// createLightClientFinalityUpdate - implements https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/altair/light-client/full-node.md#create_light_client_finality_update
// def create_light_client_finality_update(update: LightClientUpdate) -> LightClientFinalityUpdate:
//
// return LightClientFinalityUpdate(
// attested_header=update.attested_header,
// finalized_header=update.finalized_header,
// finality_branch=update.finality_branch,
// sync_aggregate=update.sync_aggregate,
// signature_slot=update.signature_slot,
// )
func createLightClientFinalityUpdate(update *ethpbv2.LightClientUpdate) *ethpbv2.LightClientFinalityUpdate {
finalityUpdate := &ethpbv2.LightClientFinalityUpdate{
AttestedHeader: update.AttestedHeader,
@@ -47,14 +37,6 @@ func createLightClientFinalityUpdate(update *ethpbv2.LightClientUpdate) *ethpbv2
return finalityUpdate
}
// createLightClientOptimisticUpdate - implements https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/altair/light-client/full-node.md#create_light_client_optimistic_update
// def create_light_client_optimistic_update(update: LightClientUpdate) -> LightClientOptimisticUpdate:
//
// return LightClientOptimisticUpdate(
// attested_header=update.attested_header,
// sync_aggregate=update.sync_aggregate,
// signature_slot=update.signature_slot,
// )
func createLightClientOptimisticUpdate(update *ethpbv2.LightClientUpdate) *ethpbv2.LightClientOptimisticUpdate {
optimisticUpdate := &ethpbv2.LightClientOptimisticUpdate{
AttestedHeader: update.AttestedHeader,
@@ -96,63 +78,6 @@ func NewLightClientOptimisticUpdateFromBeaconState(
return createLightClientOptimisticUpdate(update), nil
}
// NewLightClientUpdateFromBeaconState implements https://github.com/ethereum/consensus-specs/blob/d70dcd9926a4bbe987f1b4e65c3e05bd029fcfb8/specs/altair/light-client/full-node.md#create_light_client_update
// def create_light_client_update(state: BeaconState,
//
// block: SignedBeaconBlock,
// attested_state: BeaconState,
// finalized_block: Optional[SignedBeaconBlock]) -> LightClientUpdate:
// assert compute_epoch_at_slot(attested_state.slot) >= ALTAIR_FORK_EPOCH
// assert sum(block.message.body.sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS
//
// assert state.slot == state.latest_block_header.slot
// header = state.latest_block_header.copy()
// header.state_root = hash_tree_root(state)
// assert hash_tree_root(header) == hash_tree_root(block.message)
// update_signature_period = compute_sync_committee_period(compute_epoch_at_slot(block.message.slot))
//
// assert attested_state.slot == attested_state.latest_block_header.slot
// attested_header = attested_state.latest_block_header.copy()
// attested_header.state_root = hash_tree_root(attested_state)
// assert hash_tree_root(attested_header) == block.message.parent_root
// update_attested_period = compute_sync_committee_period(compute_epoch_at_slot(attested_header.slot))
//
// # `next_sync_committee` is only useful if the message is signed by the current sync committee
// if update_attested_period == update_signature_period:
// next_sync_committee = attested_state.next_sync_committee
// next_sync_committee_branch = compute_merkle_proof_for_state(attested_state, NEXT_SYNC_COMMITTEE_INDEX)
// else:
// next_sync_committee = SyncCommittee()
// next_sync_committee_branch = [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))]
//
// # Indicate finality whenever possible
// if finalized_block is not None:
// if finalized_block.message.slot != GENESIS_SLOT:
// finalized_header = BeaconBlockHeader(
// slot=finalized_block.message.slot,
// proposer_index=finalized_block.message.proposer_index,
// parent_root=finalized_block.message.parent_root,
// state_root=finalized_block.message.state_root,
// body_root=hash_tree_root(finalized_block.message.body),
// )
// assert hash_tree_root(finalized_header) == attested_state.finalized_checkpoint.root
// else:
// assert attested_state.finalized_checkpoint.root == Bytes32()
// finalized_header = BeaconBlockHeader()
// finality_branch = compute_merkle_proof_for_state(attested_state, FINALIZED_ROOT_INDEX)
// else:
// finalized_header = BeaconBlockHeader()
// finality_branch = [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))]
//
// return LightClientUpdate(
// attested_header=attested_header,
// next_sync_committee=next_sync_committee,
// next_sync_committee_branch=next_sync_committee_branch,
// finalized_header=finalized_header,
// finality_branch=finality_branch,
// sync_aggregate=block.message.body.sync_aggregate,
// signature_slot=block.message.slot,
// )
func NewLightClientUpdateFromBeaconState(
ctx context.Context,
state state.BeaconState,

View File

@@ -20,14 +20,12 @@ go_library(
"//beacon-chain/state:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/ssz:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//network/httputil:go_default_library",
"//proto/eth/v2:go_default_library",
"//proto/migration:go_default_library",
"//runtime/version:go_default_library",
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
@@ -55,7 +53,6 @@ go_test(
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/eth/v2:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",

View File

@@ -47,7 +47,7 @@ func (s *Server) GetLightClientBootstrap(w http.ResponseWriter, req *http.Reques
return
}
bootstrap, err := createLightClientBootstrap(ctx, state, blk.Block())
bootstrap, err := createLightClientBootstrap(ctx, state, blk)
if err != nil {
httputil.HandleError(w, "could not get light client bootstrap: "+err.Error(), http.StatusInternalServerError)
return

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -19,49 +20,29 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
)
func TestLightClientHandler_GetLightClientBootstrap_Altair(t *testing.T) {
helpers.ClearCache()
slot := primitives.Slot(params.BeaconConfig().AltairForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
l := util.NewTestLightClient(t).SetupTestAltair()
b := util.NewBeaconBlockAltair()
b.Block.StateRoot = bytesutil.PadTo([]byte("foo"), 32)
b.Block.Slot = slot
signedBlock, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
header, err := signedBlock.Header()
slot := l.State.Slot()
stateRoot, err := l.State.HashTreeRoot(l.Ctx)
require.NoError(t, err)
r, err := b.Block.HashTreeRoot()
require.NoError(t, err)
bs, err := util.NewBeaconStateAltair(func(state *ethpb.BeaconStateAltair) error {
state.BlockRoots[0] = r[:]
return nil
})
require.NoError(t, err)
require.NoError(t, bs.SetSlot(slot))
require.NoError(t, bs.SetLatestBlockHeader(header.Header))
mockBlocker := &testutil.MockBlocker{BlockToReturn: signedBlock}
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block}
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot}
s := &Server{
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{
slot: bs,
slot: l.State,
}},
Blocker: mockBlocker,
HeadFetcher: mockChainService,
}
request := httptest.NewRequest("GET", "http://foo.com/", nil)
request.SetPathValue("block_root", hexutil.Encode(r[:]))
request.SetPathValue("block_root", hexutil.Encode(stateRoot[:]))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -74,47 +55,35 @@ func TestLightClientHandler_GetLightClientBootstrap_Altair(t *testing.T) {
err = json.Unmarshal(resp.Data.Header, &respHeader)
require.NoError(t, err)
require.Equal(t, "altair", resp.Version)
require.Equal(t, hexutil.Encode(header.Header.BodyRoot), respHeader.Beacon.BodyRoot)
require.NotNil(t, resp.Data)
blockHeader, err := l.Block.Header()
require.NoError(t, err)
require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot)
require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot)
require.NotNil(t, resp.Data.CurrentSyncCommittee)
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
}
func TestLightClientHandler_GetLightClientBootstrap_Capella(t *testing.T) {
helpers.ClearCache()
slot := primitives.Slot(params.BeaconConfig().CapellaForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
l := util.NewTestLightClient(t).SetupTestCapella(false) // result is same for true and false
b := util.NewBeaconBlockCapella()
b.Block.StateRoot = bytesutil.PadTo([]byte("foo"), 32)
b.Block.Slot = slot
signedBlock, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
header, err := signedBlock.Header()
slot := l.State.Slot()
stateRoot, err := l.State.HashTreeRoot(l.Ctx)
require.NoError(t, err)
r, err := b.Block.HashTreeRoot()
require.NoError(t, err)
bs, err := util.NewBeaconStateCapella(func(state *ethpb.BeaconStateCapella) error {
state.BlockRoots[0] = r[:]
return nil
})
require.NoError(t, err)
require.NoError(t, bs.SetSlot(slot))
require.NoError(t, bs.SetLatestBlockHeader(header.Header))
mockBlocker := &testutil.MockBlocker{BlockToReturn: signedBlock}
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block}
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot}
s := &Server{
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{
slot: bs,
slot: l.State,
}},
Blocker: mockBlocker,
HeadFetcher: mockChainService,
}
request := httptest.NewRequest("GET", "http://foo.com/", nil)
request.SetPathValue("block_root", hexutil.Encode(r[:]))
request.SetPathValue("block_root", hexutil.Encode(stateRoot[:]))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -123,51 +92,38 @@ func TestLightClientHandler_GetLightClientBootstrap_Capella(t *testing.T) {
var resp structs.LightClientBootstrapResponse
err = json.Unmarshal(writer.Body.Bytes(), &resp)
require.NoError(t, err)
var respHeader structs.LightClientHeaderCapella
var respHeader structs.LightClientHeader
err = json.Unmarshal(resp.Data.Header, &respHeader)
require.NoError(t, err)
require.Equal(t, "capella", resp.Version)
require.Equal(t, hexutil.Encode(header.Header.BodyRoot), respHeader.Beacon.BodyRoot)
require.NotNil(t, resp.Data)
blockHeader, err := l.Block.Header()
require.NoError(t, err)
require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot)
require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot)
require.NotNil(t, resp.Data.CurrentSyncCommittee)
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
}
func TestLightClientHandler_GetLightClientBootstrap_Deneb(t *testing.T) {
helpers.ClearCache()
slot := primitives.Slot(params.BeaconConfig().DenebForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
l := util.NewTestLightClient(t).SetupTestDeneb(false) // result is same for true and false
b := util.NewBeaconBlockDeneb()
b.Block.StateRoot = bytesutil.PadTo([]byte("foo"), 32)
b.Block.Slot = slot
signedBlock, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
header, err := signedBlock.Header()
slot := l.State.Slot()
stateRoot, err := l.State.HashTreeRoot(l.Ctx)
require.NoError(t, err)
r, err := b.Block.HashTreeRoot()
require.NoError(t, err)
bs, err := util.NewBeaconStateDeneb(func(state *ethpb.BeaconStateDeneb) error {
state.BlockRoots[0] = r[:]
return nil
})
require.NoError(t, err)
require.NoError(t, bs.SetSlot(slot))
require.NoError(t, bs.SetLatestBlockHeader(header.Header))
mockBlocker := &testutil.MockBlocker{BlockToReturn: signedBlock}
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block}
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot}
s := &Server{
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{
slot: bs,
slot: l.State,
}},
Blocker: mockBlocker,
HeadFetcher: mockChainService,
}
request := httptest.NewRequest("GET", "http://foo.com/", nil)
request.SetPathValue("block_root", hexutil.Encode(r[:]))
request.SetPathValue("block_root", hexutil.Encode(stateRoot[:]))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -176,12 +132,18 @@ func TestLightClientHandler_GetLightClientBootstrap_Deneb(t *testing.T) {
var resp structs.LightClientBootstrapResponse
err = json.Unmarshal(writer.Body.Bytes(), &resp)
require.NoError(t, err)
var respHeader structs.LightClientHeaderDeneb
var respHeader structs.LightClientHeader
err = json.Unmarshal(resp.Data.Header, &respHeader)
require.NoError(t, err)
require.Equal(t, "deneb", resp.Version)
require.Equal(t, hexutil.Encode(header.Header.BodyRoot), respHeader.Beacon.BodyRoot)
require.NotNil(t, resp.Data)
blockHeader, err := l.Block.Header()
require.NoError(t, err)
require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot)
require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot)
require.NotNil(t, resp.Data.CurrentSyncCommittee)
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
}
func TestLightClientHandler_GetLightClientUpdatesByRangeAltair(t *testing.T) {

View File

@@ -7,10 +7,9 @@ import (
"reflect"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/proto/migration"
lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client"
consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types"
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -18,44 +17,26 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
v2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
func createLightClientBootstrap(ctx context.Context, state state.BeaconState, blk interfaces.ReadOnlyBeaconBlock) (*structs.LightClientBootstrap, error) {
func createLightClientBootstrap(ctx context.Context, state state.BeaconState, blk interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) {
switch blk.Version() {
case version.Phase0:
return nil, fmt.Errorf("light client bootstrap is not supported for phase0")
case version.Altair, version.Bellatrix:
return createLightClientBootstrapAltair(ctx, state)
return createLightClientBootstrapAltair(ctx, state, blk)
case version.Capella:
return createLightClientBootstrapCapella(ctx, state, blk)
case version.Deneb, version.Electra:
case version.Deneb:
return createLightClientBootstrapDeneb(ctx, state, blk)
}
return nil, fmt.Errorf("unsupported block version %s", version.String(blk.Version()))
}
// createLightClientBootstrapAltair - implements https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/altair/light-client/full-node.md#create_light_client_bootstrap
// def create_light_client_bootstrap(state: BeaconState) -> LightClientBootstrap:
//
// assert compute_epoch_at_slot(state.slot) >= ALTAIR_FORK_EPOCH
// assert state.slot == state.latest_block_header.slot
//
// return LightClientBootstrap(
// header=BeaconBlockHeader(
// slot=state.latest_block_header.slot,
// proposer_index=state.latest_block_header.proposer_index,
// parent_root=state.latest_block_header.parent_root,
// state_root=hash_tree_root(state),
// body_root=state.latest_block_header.body_root,
// ),
// current_sync_committee=state.current_sync_committee,
// current_sync_committee_branch=compute_merkle_proof_for_state(state, CURRENT_SYNC_COMMITTEE_INDEX)
// )
func createLightClientBootstrapAltair(ctx context.Context, state state.BeaconState) (*structs.LightClientBootstrap, error) {
func createLightClientBootstrapAltair(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) {
// assert compute_epoch_at_slot(state.slot) >= ALTAIR_FORK_EPOCH
if slots.ToEpoch(state.Slot()) < params.BeaconConfig().AltairForkEpoch {
return nil, fmt.Errorf("light client bootstrap is not supported before Altair, invalid slot %d", state.Slot())
@@ -67,14 +48,43 @@ func createLightClientBootstrapAltair(ctx context.Context, state state.BeaconSta
return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot)
}
// Prepare data
// header.state_root = hash_tree_root(state)
stateRoot, err := state.HashTreeRoot(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get state root")
}
latestBlockHeader.StateRoot = stateRoot[:]
// assert hash_tree_root(header) == hash_tree_root(block.message)
latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not get latest block header root")
}
beaconBlockRoot, err := block.Block().HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not get block root")
}
if latestBlockHeaderRoot != beaconBlockRoot {
return nil, fmt.Errorf("latest block header root %#x not equal to block root %#x", latestBlockHeaderRoot, beaconBlockRoot)
}
lightClientHeader, err := lightclient.BlockToLightClientHeaderAltair(block)
if err != nil {
return nil, errors.Wrap(err, "could not convert block to light client header")
}
apiLightClientHeader := &structs.LightClientHeader{
Beacon: structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(lightClientHeader.Beacon)),
}
headerJSON, err := json.Marshal(apiLightClientHeader)
if err != nil {
return nil, errors.Wrap(err, "could not convert header to raw message")
}
currentSyncCommittee, err := state.CurrentSyncCommittee()
if err != nil {
return nil, errors.Wrap(err, "could not get current sync committee")
}
committee := structs.SyncCommitteeFromConsensus(currentSyncCommittee)
currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get current sync committee proof")
@@ -84,38 +94,16 @@ func createLightClientBootstrapAltair(ctx context.Context, state state.BeaconSta
for i, proof := range currentSyncCommitteeProof {
branch[i] = hexutil.Encode(proof)
}
beacon := structs.BeaconBlockHeaderFromConsensus(latestBlockHeader)
if beacon == nil {
return nil, fmt.Errorf("could not get beacon block header")
}
header := &structs.LightClientHeader{
Beacon: beacon,
}
// Above shared util function won't calculate state root, so we need to do it manually
stateRoot, err := state.HashTreeRoot(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get state root")
}
header.Beacon.StateRoot = hexutil.Encode(stateRoot[:])
headerJson, err := json.Marshal(header)
if err != nil {
return nil, errors.Wrap(err, "could not convert header to raw message")
}
// Return result
result := &structs.LightClientBootstrap{
Header: headerJson,
CurrentSyncCommittee: committee,
Header: headerJSON,
CurrentSyncCommittee: structs.SyncCommitteeFromConsensus(currentSyncCommittee),
CurrentSyncCommitteeBranch: branch,
}
return result, nil
}
func createLightClientBootstrapCapella(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlyBeaconBlock) (*structs.LightClientBootstrap, error) {
func createLightClientBootstrapCapella(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) {
// assert compute_epoch_at_slot(state.slot) >= CAPELLA_FORK_EPOCH
if slots.ToEpoch(state.Slot()) < params.BeaconConfig().CapellaForkEpoch {
return nil, fmt.Errorf("creating Capella light client bootstrap is not supported before Capella, invalid slot %d", state.Slot())
@@ -127,14 +115,43 @@ func createLightClientBootstrapCapella(ctx context.Context, state state.BeaconSt
return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot)
}
// Prepare data
// header.state_root = hash_tree_root(state)
stateRoot, err := state.HashTreeRoot(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get state root")
}
latestBlockHeader.StateRoot = stateRoot[:]
// assert hash_tree_root(header) == hash_tree_root(block.message)
latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not get latest block header root")
}
beaconBlockRoot, err := block.Block().HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not get block root")
}
if latestBlockHeaderRoot != beaconBlockRoot {
return nil, fmt.Errorf("latest block header root %#x not equal to block root %#x", latestBlockHeaderRoot, beaconBlockRoot)
}
lightClientHeader, err := lightclient.BlockToLightClientHeaderCapella(ctx, block)
if err != nil {
return nil, errors.Wrap(err, "could not convert block to light client header")
}
apiLightClientHeader := &structs.LightClientHeader{
Beacon: structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(lightClientHeader.Beacon)),
}
headerJSON, err := json.Marshal(apiLightClientHeader)
if err != nil {
return nil, errors.Wrap(err, "could not convert header to raw message")
}
currentSyncCommittee, err := state.CurrentSyncCommittee()
if err != nil {
return nil, errors.Wrap(err, "could not get current sync committee")
}
committee := structs.SyncCommitteeFromConsensus(currentSyncCommittee)
currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get current sync committee proof")
@@ -144,94 +161,16 @@ func createLightClientBootstrapCapella(ctx context.Context, state state.BeaconSt
for i, proof := range currentSyncCommitteeProof {
branch[i] = hexutil.Encode(proof)
}
beacon := structs.BeaconBlockHeaderFromConsensus(latestBlockHeader)
payloadInterface, err := block.Body().Execution()
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload")
}
transactionsRoot, err := payloadInterface.TransactionsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
transactions, err := payloadInterface.Transactions()
if err != nil {
return nil, errors.Wrap(err, "could not get transactions")
}
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
transactionsRoot = transactionsRootArray[:]
} else if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
withdrawals, err := payloadInterface.Withdrawals()
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals")
}
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals root")
}
withdrawalsRoot = withdrawalsRootArray[:]
}
executionPayloadHeader := &structs.ExecutionPayloadHeaderCapella{
ParentHash: hexutil.Encode(payloadInterface.ParentHash()),
FeeRecipient: hexutil.Encode(payloadInterface.FeeRecipient()),
StateRoot: hexutil.Encode(payloadInterface.StateRoot()),
ReceiptsRoot: hexutil.Encode(payloadInterface.ReceiptsRoot()),
LogsBloom: hexutil.Encode(payloadInterface.LogsBloom()),
PrevRandao: hexutil.Encode(payloadInterface.PrevRandao()),
BlockNumber: hexutil.EncodeUint64(payloadInterface.BlockNumber()),
GasLimit: hexutil.EncodeUint64(payloadInterface.GasLimit()),
GasUsed: hexutil.EncodeUint64(payloadInterface.GasUsed()),
Timestamp: hexutil.EncodeUint64(payloadInterface.Timestamp()),
ExtraData: hexutil.Encode(payloadInterface.ExtraData()),
BaseFeePerGas: hexutil.Encode(payloadInterface.BaseFeePerGas()),
BlockHash: hexutil.Encode(payloadInterface.BlockHash()),
TransactionsRoot: hexutil.Encode(transactionsRoot),
WithdrawalsRoot: hexutil.Encode(withdrawalsRoot),
}
executionPayloadProof, err := blocks.PayloadProof(ctx, block)
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload proof")
}
executionPayloadProofStr := make([]string, len(executionPayloadProof))
for i, proof := range executionPayloadProof {
executionPayloadProofStr[i] = hexutil.Encode(proof)
}
header := &structs.LightClientHeaderCapella{
Beacon: beacon,
Execution: executionPayloadHeader,
ExecutionBranch: executionPayloadProofStr,
}
// Above shared util function won't calculate state root, so we need to do it manually
stateRoot, err := state.HashTreeRoot(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get state root")
}
header.Beacon.StateRoot = hexutil.Encode(stateRoot[:])
headerJson, err := json.Marshal(header)
if err != nil {
return nil, errors.Wrap(err, "could not convert header to raw message")
}
// Return result
result := &structs.LightClientBootstrap{
Header: headerJson,
CurrentSyncCommittee: committee,
Header: headerJSON,
CurrentSyncCommittee: structs.SyncCommitteeFromConsensus(currentSyncCommittee),
CurrentSyncCommitteeBranch: branch,
}
return result, nil
}
func createLightClientBootstrapDeneb(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlyBeaconBlock) (*structs.LightClientBootstrap, error) {
func createLightClientBootstrapDeneb(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) {
// assert compute_epoch_at_slot(state.slot) >= DENEB_FORK_EPOCH
if slots.ToEpoch(state.Slot()) < params.BeaconConfig().DenebForkEpoch {
return nil, fmt.Errorf("creating Deneb light client bootstrap is not supported before Deneb, invalid slot %d", state.Slot())
@@ -243,14 +182,43 @@ func createLightClientBootstrapDeneb(ctx context.Context, state state.BeaconStat
return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot)
}
// Prepare data
// header.state_root = hash_tree_root(state)
stateRoot, err := state.HashTreeRoot(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get state root")
}
latestBlockHeader.StateRoot = stateRoot[:]
// assert hash_tree_root(header) == hash_tree_root(block.message)
latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not get latest block header root")
}
beaconBlockRoot, err := block.Block().HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not get block root")
}
if latestBlockHeaderRoot != beaconBlockRoot {
return nil, fmt.Errorf("latest block header root %#x not equal to block root %#x", latestBlockHeaderRoot, beaconBlockRoot)
}
lightClientHeader, err := lightclient.BlockToLightClientHeaderDeneb(ctx, block)
if err != nil {
return nil, errors.Wrap(err, "could not convert block to light client header")
}
apiLightClientHeader := &structs.LightClientHeader{
Beacon: structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(lightClientHeader.Beacon)),
}
headerJSON, err := json.Marshal(apiLightClientHeader)
if err != nil {
return nil, errors.Wrap(err, "could not convert header to raw message")
}
currentSyncCommittee, err := state.CurrentSyncCommittee()
if err != nil {
return nil, errors.Wrap(err, "could not get current sync committee")
}
committee := structs.SyncCommitteeFromConsensus(currentSyncCommittee)
currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get current sync committee proof")
@@ -260,86 +228,9 @@ func createLightClientBootstrapDeneb(ctx context.Context, state state.BeaconStat
for i, proof := range currentSyncCommitteeProof {
branch[i] = hexutil.Encode(proof)
}
beacon := structs.BeaconBlockHeaderFromConsensus(latestBlockHeader)
payloadInterface, err := block.Body().Execution()
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload")
}
transactionsRoot, err := payloadInterface.TransactionsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
transactions, err := payloadInterface.Transactions()
if err != nil {
return nil, errors.Wrap(err, "could not get transactions")
}
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
transactionsRoot = transactionsRootArray[:]
} else if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
withdrawals, err := payloadInterface.Withdrawals()
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals")
}
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals root")
}
withdrawalsRoot = withdrawalsRootArray[:]
}
executionPayloadHeader := &structs.ExecutionPayloadHeaderDeneb{
ParentHash: hexutil.Encode(payloadInterface.ParentHash()),
FeeRecipient: hexutil.Encode(payloadInterface.FeeRecipient()),
StateRoot: hexutil.Encode(payloadInterface.StateRoot()),
ReceiptsRoot: hexutil.Encode(payloadInterface.ReceiptsRoot()),
LogsBloom: hexutil.Encode(payloadInterface.LogsBloom()),
PrevRandao: hexutil.Encode(payloadInterface.PrevRandao()),
BlockNumber: hexutil.EncodeUint64(payloadInterface.BlockNumber()),
GasLimit: hexutil.EncodeUint64(payloadInterface.GasLimit()),
GasUsed: hexutil.EncodeUint64(payloadInterface.GasUsed()),
Timestamp: hexutil.EncodeUint64(payloadInterface.Timestamp()),
ExtraData: hexutil.Encode(payloadInterface.ExtraData()),
BaseFeePerGas: hexutil.Encode(payloadInterface.BaseFeePerGas()),
BlockHash: hexutil.Encode(payloadInterface.BlockHash()),
TransactionsRoot: hexutil.Encode(transactionsRoot),
WithdrawalsRoot: hexutil.Encode(withdrawalsRoot),
}
executionPayloadProof, err := blocks.PayloadProof(ctx, block)
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload proof")
}
executionPayloadProofStr := make([]string, len(executionPayloadProof))
for i, proof := range executionPayloadProof {
executionPayloadProofStr[i] = hexutil.Encode(proof)
}
header := &structs.LightClientHeaderDeneb{
Beacon: beacon,
Execution: executionPayloadHeader,
ExecutionBranch: executionPayloadProofStr,
}
// Above shared util function won't calculate state root, so we need to do it manually
stateRoot, err := state.HashTreeRoot(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get state root")
}
header.Beacon.StateRoot = hexutil.Encode(stateRoot[:])
headerJson, err := json.Marshal(header)
if err != nil {
return nil, errors.Wrap(err, "could not convert header to raw message")
}
// Return result
result := &structs.LightClientBootstrap{
Header: headerJson,
CurrentSyncCommittee: committee,
Header: headerJSON,
CurrentSyncCommittee: structs.SyncCommitteeFromConsensus(currentSyncCommittee),
CurrentSyncCommitteeBranch: branch,
}