diff --git a/CHANGELOG.md b/CHANGELOG.md index 481dcc1d3b23..d55a57b4953c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Added a Prometheus error counter metric for HTTP requests to track beacon node requests. - Added a Prometheus error counter metric for SSE requests. - Save light client updates and bootstraps in DB. +- Added more comprehensive tests for `BlockToLightClientHeader`. [PR](https://github.com/prysmaticlabs/prysm/pull/14699) ### Changed @@ -112,6 +113,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - P2P: Avoid infinite loop when looking for peers in small networks. - Fixed another rollback bug due to a context deadline. - Fix checkpoint sync bug on holesky. [pr](https://github.com/prysmaticlabs/prysm/pull/14689) +- Fix segmentation fault in E2E when light-client feature flag is enabled. [PR](https://github.com/prysmaticlabs/prysm/pull/14699) ### Security diff --git a/beacon-chain/core/light-client/BUILD.bazel b/beacon-chain/core/light-client/BUILD.bazel index ecf6b8f71a75..7459468fe27a 100644 --- a/beacon-chain/core/light-client/BUILD.bazel +++ b/beacon-chain/core/light-client/BUILD.bazel @@ -6,7 +6,6 @@ go_library( importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client", visibility = ["//visibility:public"], deps = [ - "//beacon-chain/execution:go_default_library", "//beacon-chain/state:go_default_library", "//config/fieldparams:go_default_library", "//config/params:go_default_library", @@ -31,8 +30,10 @@ go_test( deps = [ ":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/primitives:go_default_library", "//encoding/ssz:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", diff --git a/beacon-chain/core/light-client/lightclient.go b/beacon-chain/core/light-client/lightclient.go index 54932094b27b..a179bc5b501c 100644 --- a/beacon-chain/core/light-client/lightclient.go +++ b/beacon-chain/core/light-client/lightclient.go @@ -7,7 +7,6 @@ import ( "reflect" "github.com/pkg/errors" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" @@ -473,15 +472,18 @@ func BlockToLightClientHeader( var payloadProof [][]byte if blockEpoch < params.BeaconConfig().CapellaForkEpoch { - var ok bool - - p, err := execution.EmptyExecutionPayload(version.Deneb) - if err != nil { - return nil, errors.Wrap(err, "could not get payload header") - } - payloadHeader, ok = p.(*enginev1.ExecutionPayloadHeaderDeneb) - if !ok { - return nil, errors.Wrapf(err, "payload header type %T is not %T", payloadHeader, &enginev1.ExecutionPayloadHeaderDeneb{}) + payloadHeader = &enginev1.ExecutionPayloadHeaderDeneb{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, fieldparams.LogsBloomLength), + PrevRandao: make([]byte, fieldparams.RootLength), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + TransactionsRoot: make([]byte, fieldparams.RootLength), + WithdrawalsRoot: make([]byte, fieldparams.RootLength), } payloadProof = emptyPayloadProof() } else { diff --git a/beacon-chain/core/light-client/lightclient_test.go b/beacon-chain/core/light-client/lightclient_test.go index e26a4d2f7daf..c9660e5d25c1 100644 --- a/beacon-chain/core/light-client/lightclient_test.go +++ b/beacon-chain/core/light-client/lightclient_test.go @@ -7,8 +7,10 @@ import ( "github.com/pkg/errors" lightClient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" + "github.com/prysmaticlabs/prysm/v5/config/params" consensustypes "github.com/prysmaticlabs/prysm/v5/consensus-types" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/ssz" v11 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" pb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -357,7 +359,11 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { t.Run("Altair", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestAltair() - header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) + header, err := lightClient.BlockToLightClientHeader( + l.Ctx, + primitives.Slot(params.BeaconConfig().AltairForkEpoch)*params.BeaconConfig().SlotsPerEpoch, + l.Block, + ) require.NoError(t, err) require.NotNil(t, header, "header is nil") @@ -376,7 +382,11 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { t.Run("Bellatrix", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestBellatrix() - header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) + header, err := lightClient.BlockToLightClientHeader( + l.Ctx, + primitives.Slot(params.BeaconConfig().BellatrixForkEpoch)*params.BeaconConfig().SlotsPerEpoch, + l.Block, + ) require.NoError(t, err) require.NotNil(t, header, "header is nil") @@ -396,7 +406,11 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { t.Run("Non-Blinded Beacon Block", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestCapella(false) - header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) + header, err := lightClient.BlockToLightClientHeader( + l.Ctx, + primitives.Slot(params.BeaconConfig().CapellaForkEpoch)*params.BeaconConfig().SlotsPerEpoch, + l.Block, + ) require.NoError(t, err) require.NotNil(t, header, "header is nil") @@ -453,7 +467,11 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { t.Run("Blinded Beacon Block", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestCapella(true) - header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) + header, err := lightClient.BlockToLightClientHeader( + l.Ctx, + primitives.Slot(params.BeaconConfig().CapellaForkEpoch)*params.BeaconConfig().SlotsPerEpoch, + l.Block, + ) require.NoError(t, err) require.NotNil(t, header, "header is nil") @@ -512,7 +530,11 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { t.Run("Non-Blinded Beacon Block", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestDeneb(false) - header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) + header, err := lightClient.BlockToLightClientHeader( + l.Ctx, + primitives.Slot(params.BeaconConfig().DenebForkEpoch)*params.BeaconConfig().SlotsPerEpoch, + l.Block, + ) require.NoError(t, err) require.NotNil(t, header, "header is nil") @@ -577,7 +599,11 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { t.Run("Blinded Beacon Block", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestDeneb(true) - header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) + header, err := lightClient.BlockToLightClientHeader( + l.Ctx, + primitives.Slot(params.BeaconConfig().DenebForkEpoch)*params.BeaconConfig().SlotsPerEpoch, + l.Block, + ) require.NoError(t, err) require.NotNil(t, header, "header is nil") @@ -771,6 +797,172 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { require.DeepSSZEqual(t, executionPayloadProof, convertArrayToSlice(headerExecutionBranch), "Execution payload proofs are not equal") }) }) + + t.Run("Capella fork with Altair block", func(t *testing.T) { + l := util.NewTestLightClient(t).SetupTestAltair() + + header, err := lightClient.BlockToLightClientHeader( + l.Ctx, + primitives.Slot(params.BeaconConfig().CapellaForkEpoch)*params.BeaconConfig().SlotsPerEpoch, + l.Block) + require.NoError(t, err) + require.NotNil(t, header, "header is nil") + + parentRoot := l.Block.Block().ParentRoot() + stateRoot := l.Block.Block().StateRoot() + bodyRoot, err := l.Block.Block().Body().HashTreeRoot() + require.NoError(t, err) + + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") + }) + + t.Run("Deneb fork with Altair block", func(t *testing.T) { + l := util.NewTestLightClient(t).SetupTestAltair() + + header, err := lightClient.BlockToLightClientHeader( + l.Ctx, + primitives.Slot(params.BeaconConfig().DenebForkEpoch)*params.BeaconConfig().SlotsPerEpoch, + l.Block) + require.NoError(t, err) + require.NotNil(t, header, "header is nil") + + parentRoot := l.Block.Block().ParentRoot() + stateRoot := l.Block.Block().StateRoot() + bodyRoot, err := l.Block.Block().Body().HashTreeRoot() + require.NoError(t, err) + + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") + }) + + t.Run("Deneb fork with Capella block", func(t *testing.T) { + t.Run("Non-Blinded Beacon Block", func(t *testing.T) { + l := util.NewTestLightClient(t).SetupTestCapella(false) + + header, err := lightClient.BlockToLightClientHeader( + l.Ctx, + primitives.Slot(params.BeaconConfig().DenebForkEpoch)*params.BeaconConfig().SlotsPerEpoch, + l.Block) + require.NoError(t, err) + require.NotNil(t, header, "header is nil") + + parentRoot := l.Block.Block().ParentRoot() + stateRoot := l.Block.Block().StateRoot() + bodyRoot, err := l.Block.Block().Body().HashTreeRoot() + require.NoError(t, err) + + payload, err := l.Block.Block().Body().Execution() + require.NoError(t, err) + + transactionsRoot, err := lightClient.ComputeTransactionsRoot(payload) + require.NoError(t, err) + + withdrawalsRoot, err := lightClient.ComputeWithdrawalsRoot(payload) + require.NoError(t, err) + + executionHeader := &v11.ExecutionPayloadHeaderDeneb{ + ParentHash: payload.ParentHash(), + FeeRecipient: payload.FeeRecipient(), + StateRoot: payload.StateRoot(), + ReceiptsRoot: payload.ReceiptsRoot(), + LogsBloom: payload.LogsBloom(), + PrevRandao: payload.PrevRandao(), + BlockNumber: payload.BlockNumber(), + GasLimit: payload.GasLimit(), + GasUsed: payload.GasUsed(), + Timestamp: payload.Timestamp(), + ExtraData: payload.ExtraData(), + BaseFeePerGas: payload.BaseFeePerGas(), + BlockHash: payload.BlockHash(), + TransactionsRoot: transactionsRoot, + WithdrawalsRoot: withdrawalsRoot, + } + + executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block()) + require.NoError(t, err) + + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") + + headerExecution, err := header.Execution() + require.NoError(t, err) + require.DeepSSZEqual(t, executionHeader, headerExecution.Proto(), "Execution headers are not equal") + + headerExecutionBranch, err := header.ExecutionBranch() + require.NoError(t, err) + require.DeepSSZEqual(t, executionPayloadProof, convertArrayToSlice(headerExecutionBranch), "Execution payload proofs are not equal") + }) + + t.Run("Blinded Beacon Block", func(t *testing.T) { + l := util.NewTestLightClient(t).SetupTestCapella(true) + + header, err := lightClient.BlockToLightClientHeader( + l.Ctx, + primitives.Slot(params.BeaconConfig().DenebForkEpoch)*params.BeaconConfig().SlotsPerEpoch, + l.Block) + require.NoError(t, err) + require.NotNil(t, header, "header is nil") + + parentRoot := l.Block.Block().ParentRoot() + stateRoot := l.Block.Block().StateRoot() + bodyRoot, err := l.Block.Block().Body().HashTreeRoot() + require.NoError(t, err) + + payload, err := l.Block.Block().Body().Execution() + require.NoError(t, err) + + transactionsRoot, err := payload.TransactionsRoot() + require.NoError(t, err) + + withdrawalsRoot, err := payload.WithdrawalsRoot() + require.NoError(t, err) + + executionHeader := &v11.ExecutionPayloadHeaderDeneb{ + ParentHash: payload.ParentHash(), + FeeRecipient: payload.FeeRecipient(), + StateRoot: payload.StateRoot(), + ReceiptsRoot: payload.ReceiptsRoot(), + LogsBloom: payload.LogsBloom(), + PrevRandao: payload.PrevRandao(), + BlockNumber: payload.BlockNumber(), + GasLimit: payload.GasLimit(), + GasUsed: payload.GasUsed(), + Timestamp: payload.Timestamp(), + ExtraData: payload.ExtraData(), + BaseFeePerGas: payload.BaseFeePerGas(), + BlockHash: payload.BlockHash(), + TransactionsRoot: transactionsRoot, + WithdrawalsRoot: withdrawalsRoot, + } + + executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block()) + require.NoError(t, err) + + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") + + headerExecution, err := header.Execution() + require.NoError(t, err) + require.DeepSSZEqual(t, executionHeader, headerExecution.Proto(), "Execution headers are not equal") + + headerExecutionBranch, err := header.ExecutionBranch() + require.NoError(t, err) + require.DeepSSZEqual(t, executionPayloadProof, convertArrayToSlice(headerExecutionBranch), "Execution payload proofs are not equal") + }) + }) } func convertArrayToSlice(arr [4][32]uint8) [][]uint8 { diff --git a/consensus-types/light-client/bootstrap.go b/consensus-types/light-client/bootstrap.go index be943d4ddc1f..2aa058cd5784 100644 --- a/consensus-types/light-client/bootstrap.go +++ b/consensus-types/light-client/bootstrap.go @@ -90,7 +90,7 @@ func (h *bootstrapAltair) Header() interfaces.LightClientHeader { func (h *bootstrapAltair) SetHeader(header interfaces.LightClientHeader) error { p, ok := (header.Proto()).(*pb.LightClientHeaderAltair) if !ok { - return fmt.Errorf("header type %T is not %T", p, &pb.LightClientHeaderAltair{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderAltair{}) } h.p.Header = p h.header = header @@ -188,7 +188,7 @@ func (h *bootstrapCapella) Header() interfaces.LightClientHeader { func (h *bootstrapCapella) SetHeader(header interfaces.LightClientHeader) error { p, ok := (header.Proto()).(*pb.LightClientHeaderCapella) if !ok { - return fmt.Errorf("header type %T is not %T", p, &pb.LightClientHeaderCapella{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderCapella{}) } h.p.Header = p h.header = header @@ -286,7 +286,7 @@ func (h *bootstrapDeneb) Header() interfaces.LightClientHeader { func (h *bootstrapDeneb) SetHeader(header interfaces.LightClientHeader) error { p, ok := (header.Proto()).(*pb.LightClientHeaderDeneb) if !ok { - return fmt.Errorf("header type %T is not %T", p, &pb.LightClientHeaderDeneb{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderDeneb{}) } h.p.Header = p h.header = header @@ -384,7 +384,7 @@ func (h *bootstrapElectra) Header() interfaces.LightClientHeader { func (h *bootstrapElectra) SetHeader(header interfaces.LightClientHeader) error { p, ok := (header.Proto()).(*pb.LightClientHeaderDeneb) if !ok { - return fmt.Errorf("header type %T is not %T", p, &pb.LightClientHeaderDeneb{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderDeneb{}) } h.p.Header = p h.header = header diff --git a/consensus-types/light-client/update.go b/consensus-types/light-client/update.go index 84e7afbecb12..3eccf1af3c2d 100644 --- a/consensus-types/light-client/update.go +++ b/consensus-types/light-client/update.go @@ -115,7 +115,7 @@ func (u *updateAltair) AttestedHeader() interfaces.LightClientHeader { func (u *updateAltair) SetAttestedHeader(header interfaces.LightClientHeader) error { proto, ok := header.Proto().(*pb.LightClientHeaderAltair) if !ok { - return fmt.Errorf("header type %T is not %T", proto, &pb.LightClientHeaderAltair{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderAltair{}) } u.p.AttestedHeader = proto u.attestedHeader = header @@ -157,7 +157,7 @@ func (u *updateAltair) FinalizedHeader() interfaces.LightClientHeader { func (u *updateAltair) SetFinalizedHeader(header interfaces.LightClientHeader) error { proto, ok := header.Proto().(*pb.LightClientHeaderAltair) if !ok { - return fmt.Errorf("header type %T is not %T", proto, &pb.LightClientHeaderAltair{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderAltair{}) } u.p.FinalizedHeader = proto u.finalizedHeader = header @@ -282,7 +282,7 @@ func (u *updateCapella) AttestedHeader() interfaces.LightClientHeader { func (u *updateCapella) SetAttestedHeader(header interfaces.LightClientHeader) error { proto, ok := header.Proto().(*pb.LightClientHeaderCapella) if !ok { - return fmt.Errorf("header type %T is not %T", proto, &pb.LightClientHeaderCapella{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderCapella{}) } u.p.AttestedHeader = proto u.attestedHeader = header @@ -324,7 +324,7 @@ func (u *updateCapella) FinalizedHeader() interfaces.LightClientHeader { func (u *updateCapella) SetFinalizedHeader(header interfaces.LightClientHeader) error { proto, ok := header.Proto().(*pb.LightClientHeaderCapella) if !ok { - return fmt.Errorf("header type %T is not %T", proto, &pb.LightClientHeaderCapella{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderCapella{}) } u.p.FinalizedHeader = proto u.finalizedHeader = header @@ -449,7 +449,7 @@ func (u *updateDeneb) AttestedHeader() interfaces.LightClientHeader { func (u *updateDeneb) SetAttestedHeader(header interfaces.LightClientHeader) error { proto, ok := header.Proto().(*pb.LightClientHeaderDeneb) if !ok { - return fmt.Errorf("header type %T is not %T", proto, &pb.LightClientHeaderDeneb{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderDeneb{}) } u.p.AttestedHeader = proto u.attestedHeader = header @@ -491,7 +491,7 @@ func (u *updateDeneb) FinalizedHeader() interfaces.LightClientHeader { func (u *updateDeneb) SetFinalizedHeader(header interfaces.LightClientHeader) error { proto, ok := header.Proto().(*pb.LightClientHeaderDeneb) if !ok { - return fmt.Errorf("header type %T is not %T", proto, &pb.LightClientHeaderDeneb{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderDeneb{}) } u.p.FinalizedHeader = proto u.finalizedHeader = header @@ -617,7 +617,7 @@ func (u *updateElectra) AttestedHeader() interfaces.LightClientHeader { func (u *updateElectra) SetAttestedHeader(header interfaces.LightClientHeader) error { proto, ok := header.Proto().(*pb.LightClientHeaderDeneb) if !ok { - return fmt.Errorf("header type %T is not %T", proto, &pb.LightClientHeaderDeneb{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderDeneb{}) } u.p.AttestedHeader = proto u.attestedHeader = header @@ -659,7 +659,7 @@ func (u *updateElectra) FinalizedHeader() interfaces.LightClientHeader { func (u *updateElectra) SetFinalizedHeader(header interfaces.LightClientHeader) error { proto, ok := header.Proto().(*pb.LightClientHeaderDeneb) if !ok { - return fmt.Errorf("header type %T is not %T", proto, &pb.LightClientHeaderDeneb{}) + return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderDeneb{}) } u.p.FinalizedHeader = proto u.finalizedHeader = header