diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e7022b471..c3de04e900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/beacon-chain/core/light-client/lightclient.go b/beacon-chain/core/light-client/lightclient.go index afcb5c56f9..3ae0a7fb23 100644 --- a/beacon-chain/core/light-client/lightclient.go +++ b/beacon-chain/core/light-client/lightclient.go @@ -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 := ðpbv2.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 := ðpbv2.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, diff --git a/beacon-chain/rpc/eth/light-client/BUILD.bazel b/beacon-chain/rpc/eth/light-client/BUILD.bazel index 930c701901..85b46a07ec 100644 --- a/beacon-chain/rpc/eth/light-client/BUILD.bazel +++ b/beacon-chain/rpc/eth/light-client/BUILD.bazel @@ -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", diff --git a/beacon-chain/rpc/eth/light-client/handlers.go b/beacon-chain/rpc/eth/light-client/handlers.go index 4145d1cc7b..245d703eaa 100644 --- a/beacon-chain/rpc/eth/light-client/handlers.go +++ b/beacon-chain/rpc/eth/light-client/handlers.go @@ -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 diff --git a/beacon-chain/rpc/eth/light-client/handlers_test.go b/beacon-chain/rpc/eth/light-client/handlers_test.go index cac658e7ae..0ca25649cc 100644 --- a/beacon-chain/rpc/eth/light-client/handlers_test.go +++ b/beacon-chain/rpc/eth/light-client/handlers_test.go @@ -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) { diff --git a/beacon-chain/rpc/eth/light-client/helpers.go b/beacon-chain/rpc/eth/light-client/helpers.go index 81a551d157..a8b6632d4f 100644 --- a/beacon-chain/rpc/eth/light-client/helpers.go +++ b/beacon-chain/rpc/eth/light-client/helpers.go @@ -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, }