diff --git a/beacon-chain/execution/engine_client_test.go b/beacon-chain/execution/engine_client_test.go index 281af5cd4f2c..38d405874441 100644 --- a/beacon-chain/execution/engine_client_test.go +++ b/beacon-chain/execution/engine_client_test.go @@ -1400,6 +1400,31 @@ func newTestIPCServer(t *testing.T) *rpc.Server { } func fixtures() map[string]interface{} { + s := fixturesStruct() + return map[string]interface{}{ + "ExecutionBlock": s.ExecutionBlock, + "ExecutionPayloadBody": s.ExecutionPayloadBody, + "ExecutionPayload": s.ExecutionPayload, + "ExecutionPayloadCapella": s.ExecutionPayloadCapella, + "ExecutionPayloadDeneb": s.ExecutionPayloadDeneb, + "ExecutionPayloadElectra": s.ExecutionPayloadElectra, + "ExecutionPayloadCapellaWithValue": s.ExecutionPayloadWithValueCapella, + "ExecutionPayloadDenebWithValue": s.ExecutionPayloadWithValueDeneb, + "ExecutionPayloadElectraWithValue": s.ExecutionPayloadWithValueElectra, + "ValidPayloadStatus": s.ValidPayloadStatus, + "InvalidBlockHashStatus": s.InvalidBlockHashStatus, + "AcceptedStatus": s.AcceptedStatus, + "SyncingStatus": s.SyncingStatus, + "InvalidStatus": s.InvalidStatus, + "UnknownStatus": s.UnknownStatus, + "ForkchoiceUpdatedResponse": s.ForkchoiceUpdatedResponse, + "ForkchoiceUpdatedSyncingResponse": s.ForkchoiceUpdatedSyncingResponse, + "ForkchoiceUpdatedAcceptedResponse": s.ForkchoiceUpdatedAcceptedResponse, + "ForkchoiceUpdatedInvalidResponse": s.ForkchoiceUpdatedInvalidResponse, + } +} + +func fixturesStruct() *payloadFixtures { foo := bytesutil.ToBytes32([]byte("foo")) bar := bytesutil.PadTo([]byte("bar"), 20) baz := bytesutil.PadTo([]byte("baz"), 256) @@ -1441,7 +1466,7 @@ func fixtures() map[string]interface{} { Transactions: [][]byte{foo[:]}, Withdrawals: []*pb.Withdrawal{}, } - executionPayloadFixtureDeneb := &pb.ExecutionPayloadDeneb{ + emptyExecutionPayloadDeneb := &pb.ExecutionPayloadDeneb{ ParentHash: foo[:], FeeRecipient: bar, StateRoot: foo[:], @@ -1455,11 +1480,29 @@ func fixtures() map[string]interface{} { ExtraData: foo[:], BaseFeePerGas: bytesutil.PadTo(baseFeePerGas.Bytes(), fieldparams.RootLength), BlockHash: foo[:], - Transactions: [][]byte{foo[:]}, - Withdrawals: []*pb.Withdrawal{}, BlobGasUsed: 2, ExcessBlobGas: 3, } + executionPayloadFixtureDeneb := &pb.ExecutionPayloadDeneb{ + ParentHash: emptyExecutionPayloadDeneb.ParentHash, + FeeRecipient: emptyExecutionPayloadDeneb.FeeRecipient, + StateRoot: emptyExecutionPayloadDeneb.StateRoot, + ReceiptsRoot: emptyExecutionPayloadDeneb.ReceiptsRoot, + LogsBloom: emptyExecutionPayloadDeneb.LogsBloom, + PrevRandao: emptyExecutionPayloadDeneb.PrevRandao, + BlockNumber: emptyExecutionPayloadDeneb.BlockNumber, + GasLimit: emptyExecutionPayloadDeneb.GasLimit, + GasUsed: emptyExecutionPayloadDeneb.GasUsed, + Timestamp: emptyExecutionPayloadDeneb.Timestamp, + ExtraData: emptyExecutionPayloadDeneb.ExtraData, + BaseFeePerGas: emptyExecutionPayloadDeneb.BaseFeePerGas, + BlockHash: emptyExecutionPayloadDeneb.BlockHash, + BlobGasUsed: emptyExecutionPayloadDeneb.BlobGasUsed, + ExcessBlobGas: emptyExecutionPayloadDeneb.ExcessBlobGas, + // added on top of the empty payload + Transactions: [][]byte{foo[:]}, + Withdrawals: []*pb.Withdrawal{}, + } withdrawalRequests := make([]pb.WithdrawalRequestV1, 3) for i := range withdrawalRequests { amount := hexutil.Uint64(i) @@ -1572,22 +1615,24 @@ func fixtures() map[string]interface{} { executionPayloadWithValueFixtureElectra := &pb.GetPayloadV4ResponseJson{ ShouldOverrideBuilder: true, ExecutionPayload: &pb.ExecutionPayloadElectraJSON{ - ParentHash: &common.Hash{'a'}, - FeeRecipient: &common.Address{'b'}, - StateRoot: &common.Hash{'c'}, - ReceiptsRoot: &common.Hash{'d'}, - LogsBloom: &hexutil.Bytes{'e'}, - PrevRandao: &common.Hash{'f'}, - BaseFeePerGas: "0x123", - BlockHash: &common.Hash{'g'}, - Transactions: []hexutil.Bytes{{'h'}}, - Withdrawals: []*pb.Withdrawal{}, - BlockNumber: &hexUint, - GasLimit: &hexUint, - GasUsed: &hexUint, - Timestamp: &hexUint, - BlobGasUsed: &bgu, - ExcessBlobGas: &ebg, + ParentHash: &common.Hash{'a'}, + FeeRecipient: &common.Address{'b'}, + StateRoot: &common.Hash{'c'}, + ReceiptsRoot: &common.Hash{'d'}, + LogsBloom: &hexutil.Bytes{'e'}, + PrevRandao: &common.Hash{'f'}, + BaseFeePerGas: "0x123", + BlockHash: &common.Hash{'g'}, + Transactions: []hexutil.Bytes{{'h'}}, + Withdrawals: []*pb.Withdrawal{}, + BlockNumber: &hexUint, + GasLimit: &hexUint, + GasUsed: &hexUint, + Timestamp: &hexUint, + BlobGasUsed: &bgu, + ExcessBlobGas: &ebg, + DepositRequests: depositRequests, + WithdrawalRequests: withdrawalRequests, }, BlockValue: "0x11fffffffff", BlobsBundle: &pb.BlobBundleJSON{ @@ -1680,27 +1725,52 @@ func fixtures() map[string]interface{} { Status: pb.PayloadStatus_UNKNOWN, LatestValidHash: foo[:], } - return map[string]interface{}{ - "ExecutionBlock": executionBlock, - "ExecutionPayloadBody": executionPayloadBodyFixture, - "ExecutionPayload": executionPayloadFixture, - "ExecutionPayloadCapella": executionPayloadFixtureCapella, - "ExecutionPayloadDeneb": executionPayloadFixtureDeneb, - "ExecutionPayloadElectra": executionPayloadFixtureElectra, - "ExecutionPayloadCapellaWithValue": executionPayloadWithValueFixtureCapella, - "ExecutionPayloadDenebWithValue": executionPayloadWithValueFixtureDeneb, - "ExecutionPayloadElectraWithValue": executionPayloadWithValueFixtureElectra, - "ValidPayloadStatus": validStatus, - "InvalidBlockHashStatus": inValidBlockHashStatus, - "AcceptedStatus": acceptedStatus, - "SyncingStatus": syncingStatus, - "InvalidStatus": invalidStatus, - "UnknownStatus": unknownStatus, - "ForkchoiceUpdatedResponse": forkChoiceResp, - "ForkchoiceUpdatedSyncingResponse": forkChoiceSyncingResp, - "ForkchoiceUpdatedAcceptedResponse": forkChoiceAcceptedResp, - "ForkchoiceUpdatedInvalidResponse": forkChoiceInvalidResp, + return &payloadFixtures{ + ExecutionBlock: executionBlock, + ExecutionPayloadBody: executionPayloadBodyFixture, + ExecutionPayload: executionPayloadFixture, + ExecutionPayloadCapella: executionPayloadFixtureCapella, + ExecutionPayloadDeneb: executionPayloadFixtureDeneb, + EmptyExecutionPayloadDeneb: emptyExecutionPayloadDeneb, + ExecutionPayloadElectra: executionPayloadFixtureElectra, + ExecutionPayloadWithValueCapella: executionPayloadWithValueFixtureCapella, + ExecutionPayloadWithValueDeneb: executionPayloadWithValueFixtureDeneb, + ExecutionPayloadWithValueElectra: executionPayloadWithValueFixtureElectra, + ValidPayloadStatus: validStatus, + InvalidBlockHashStatus: inValidBlockHashStatus, + AcceptedStatus: acceptedStatus, + SyncingStatus: syncingStatus, + InvalidStatus: invalidStatus, + UnknownStatus: unknownStatus, + ForkchoiceUpdatedResponse: forkChoiceResp, + ForkchoiceUpdatedSyncingResponse: forkChoiceSyncingResp, + ForkchoiceUpdatedAcceptedResponse: forkChoiceAcceptedResp, + ForkchoiceUpdatedInvalidResponse: forkChoiceInvalidResp, } + +} + +type payloadFixtures struct { + ExecutionBlock *pb.ExecutionBlock + ExecutionPayloadBody *pb.ExecutionPayloadBodyV1Or2 + ExecutionPayload *pb.ExecutionPayload + ExecutionPayloadCapella *pb.ExecutionPayloadCapella + EmptyExecutionPayloadDeneb *pb.ExecutionPayloadDeneb + ExecutionPayloadDeneb *pb.ExecutionPayloadDeneb + ExecutionPayloadElectra *pb.ExecutionPayloadElectra + ExecutionPayloadWithValueCapella *pb.GetPayloadV2ResponseJson + ExecutionPayloadWithValueDeneb *pb.GetPayloadV3ResponseJson + ExecutionPayloadWithValueElectra *pb.GetPayloadV4ResponseJson + ValidPayloadStatus *pb.PayloadStatus + InvalidBlockHashStatus *pb.PayloadStatus + AcceptedStatus *pb.PayloadStatus + SyncingStatus *pb.PayloadStatus + InvalidStatus *pb.PayloadStatus + UnknownStatus *pb.PayloadStatus + ForkchoiceUpdatedResponse *ForkchoiceUpdatedResponse + ForkchoiceUpdatedSyncingResponse *ForkchoiceUpdatedResponse + ForkchoiceUpdatedAcceptedResponse *ForkchoiceUpdatedResponse + ForkchoiceUpdatedInvalidResponse *ForkchoiceUpdatedResponse } func TestHeaderByHash_NotFound(t *testing.T) { diff --git a/beacon-chain/execution/payload_body.go b/beacon-chain/execution/payload_body.go index 313e1bb57b51..c0fa762d925e 100644 --- a/beacon-chain/execution/payload_body.go +++ b/beacon-chain/execution/payload_body.go @@ -142,32 +142,37 @@ func (r *blindedBlockReconstructor) requestBodiesByHash(ctx context.Context, cli return nilBodies, nil } +func (r *blindedBlockReconstructor) payloadForHeader(header interfaces.ExecutionData, v int) (proto.Message, error) { + bodyKey := bytesutil.ToBytes32(header.BlockHash()) + if bodyKey == params.BeaconConfig().ZeroHash { + payload, err := buildEmptyExecutionPayload(v) + if err != nil { + return nil, errors.Wrapf(err, "failed to reconstruct payload for body hash %#x", bodyKey) + } + return payload, nil + } + body, ok := r.bodies[bodyKey] + if !ok { + return nil, errors.Wrapf(errNilPayloadBody, "hash %#x", bodyKey) + } + ed, err := fullPayloadFromPayloadBody(header, body, v) + if err != nil { + return nil, errors.Wrapf(err, "failed to reconstruct payload for body hash %#x", bodyKey) + } + return ed.Proto(), nil +} + func (r *blindedBlockReconstructor) unblinded() ([]interfaces.SignedBeaconBlock, error) { unblinded := make([]interfaces.SignedBeaconBlock, len(r.orderedBlocks)) for i := range r.orderedBlocks { blk, header := r.orderedBlocks[i].block, r.orderedBlocks[i].header - bodyKey := bytesutil.ToBytes32(header.BlockHash()) - var payload proto.Message - if bodyKey == params.BeaconConfig().ZeroHash { - var err error - payload, err = buildEmptyExecutionPayload(blk.Version()) - if err != nil { - return nil, errors.Wrapf(err, "failed to reconstruct payload for body hash %#x", bodyKey) - } - } else { - body, ok := r.bodies[bodyKey] - if !ok { - return nil, errors.Wrapf(errNilPayloadBody, "hash %#x", bodyKey) - } - ed, err := fullPayloadFromPayloadBody(header, body, blk.Version()) - if err != nil { - return nil, errors.Wrapf(err, "failed to reconstruct payload for body hash %#x", bodyKey) - } - payload = ed.Proto() + payload, err := r.payloadForHeader(header, blk.Version()) + if err != nil { + return nil, err } full, err := blocks.BuildSignedBeaconBlockFromExecutionPayload(blk, payload) if err != nil { - return nil, errors.Wrapf(err, "failed to build full block from execution payload for block hash %#x", bodyKey) + return nil, errors.Wrapf(err, "failed to build full block from execution payload for block hash %#x", header.BlockHash()) } unblinded[i] = full } diff --git a/beacon-chain/execution/payload_body_test.go b/beacon-chain/execution/payload_body_test.go index 03fc2213f4da..6065e1503595 100644 --- a/beacon-chain/execution/payload_body_test.go +++ b/beacon-chain/execution/payload_body_test.go @@ -7,13 +7,17 @@ import ( "net/http/httptest" "testing" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" + "github.com/prysmaticlabs/prysm/v5/config/params" + "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" pb "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" + "github.com/prysmaticlabs/prysm/v5/time/slots" ) type versioner struct { @@ -51,39 +55,82 @@ func TestPayloadBodyMethodForBlock(t *testing.T) { }) } -func testBlindWithPayload(t *testing.T, b interfaces.ReadOnlySignedBeaconBlock) (interfaces.ReadOnlySignedBeaconBlock, *pb.ExecutionPayloadBodyV1Or2, error) { - if b.IsNil() { - t.Fatal("cannot get payload body for nil block") +func payloadToBody(t *testing.T, ed interfaces.ExecutionData) *pb.ExecutionPayloadBodyV1Or2 { + body := &pb.ExecutionPayloadBodyV1Or2{} + txs, err := ed.Transactions() + require.NoError(t, err) + wd, err := ed.Withdrawals() + // Bellatrix does not have withdrawals and will return an error. + if err == nil { + body.Withdrawals = wd } - if !b.Block().IsBlinded() { - t.Fatal() + for i := range txs { + body.Transactions = append(body.Transactions, txs[i]) } + eed, isElectra := ed.(interfaces.ExecutionDataElectra) + if isElectra { + body.DepositRequests = pb.ProtoDepositRequestsToJson(eed.DepositReceipts()) + body.WithdrawalRequests = pb.ProtoWithdrawalRequestsToJson(eed.WithdrawalRequests()) + } + return body +} - return b.Block().PayloadBody(), nil +type blindedBlockFixtures struct { + denebBlock *fullAndBlinded + emptyDenebBlock *fullAndBlinded } -func TestPayloadBodiesViaUnblinder(t *testing.T) { +type fullAndBlinded struct { + full interfaces.ReadOnlySignedBeaconBlock + blinded *blockWithHeader +} + +func blindedBlockWithHeader(t *testing.T, b interfaces.ReadOnlySignedBeaconBlock) *fullAndBlinded { + header, err := b.Block().Body().Execution() + require.NoError(t, err) + blinded, err := b.ToBlinded() + require.NoError(t, err) + return &fullAndBlinded{ + full: b, + blinded: &blockWithHeader{ + block: blinded, + header: header, + }} +} +func denebSlot(t *testing.T) primitives.Slot { + s, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch) + require.NoError(t, err) + return s +} + +func testBlindedBlockFixtures(t *testing.T) *blindedBlockFixtures { + pfx := fixturesStruct() + fx := &blindedBlockFixtures{} + full := pfx.ExecutionPayloadDeneb + // this func overrides fixture blockhashes to ensure they are unique + full.BlockHash = bytesutil.PadTo([]byte("full"), 32) + denebBlock, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, denebSlot(t), 0, util.WithGeneratorOptionPayload(full)) + fx.denebBlock = blindedBlockWithHeader(t, denebBlock) + + empty := pfx.EmptyExecutionPayloadDeneb + empty.BlockHash = bytesutil.PadTo([]byte("empty"), 32) + emptyDenebBlock, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, denebSlot(t), 0, util.WithGeneratorOptionPayload(empty)) + fx.emptyDenebBlock = blindedBlockWithHeader(t, emptyDenebBlock) + return fx +} + +func TestPayloadBodiesViaUnblinder(t *testing.T) { + fx := testBlindedBlockFixtures(t) t.Run("empty, null, full works", func(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") defer func() { require.NoError(t, r.Body.Close()) }() - executionPayloadBodies := make([]*pb.ExecutionPayloadBodyV1Or2, 3) - executionPayloadBodies[0] = &pb.ExecutionPayloadBodyV1Or2{ - Transactions: []hexutil.Bytes{}, - Withdrawals: []*pb.Withdrawal{}, - } - executionPayloadBodies[1] = nil - executionPayloadBodies[2] = &pb.ExecutionPayloadBodyV1Or2{ - Transactions: []hexutil.Bytes{hexutil.MustDecode("0x02f878831469668303f51d843b9ac9f9843b9aca0082520894c93269b73096998db66be0441e836d873535cb9c8894a19041886f000080c001a031cc29234036afbf9a1fb9476b463367cb1f957ac0b919b69bbc798436e604aaa018c4e9c3914eb27aadd0b91e10b18655739fcf8c1fc398763a9f1beecb8ddc86")}, - Withdrawals: []*pb.Withdrawal{{ - Index: 1, - ValidatorIndex: 1, - Address: hexutil.MustDecode("0xcf8e0d4e9587369b2301d0790347320302cc0943"), - Amount: 1, - }}, + executionPayloadBodies := []*pb.ExecutionPayloadBodyV1Or2{ + payloadToBody(t, fx.denebBlock.blinded.header), + payloadToBody(t, fx.emptyDenebBlock.blinded.header), } resp := map[string]interface{}{ @@ -96,19 +143,33 @@ func TestPayloadBodiesViaUnblinder(t *testing.T) { })) ctx := context.Background() - rpcClient, err := rpc.DialHTTP(srv.URL) + client, err := rpc.DialHTTP(srv.URL) require.NoError(t, err) - service := &Service{} - service.rpcClient = rpcClient - - bRoot := [32]byte{} - copy(bRoot[:], "hash") - results, err := service.GetPayloadBodiesByHash(ctx, []common.Hash{bRoot, bRoot, bRoot}) + toUnblind := []interfaces.ReadOnlySignedBeaconBlock{ + fx.denebBlock.blinded.block, + fx.emptyDenebBlock.blinded.block, + } + bbr, err := newBlindedBlockReconstructor(toUnblind) + require.NoError(t, err) + require.NoError(t, bbr.requestBodies(ctx, client)) + payload, err := bbr.payloadForHeader(fx.denebBlock.blinded.header, fx.denebBlock.blinded.block.Version()) + require.NoError(t, err) + _, err = blocks.BuildSignedBeaconBlockFromExecutionPayload(fx.denebBlock.blinded.block, payload) + require.NoError(t, err) + emptyPayload, err := bbr.payloadForHeader(fx.emptyDenebBlock.blinded.header, fx.emptyDenebBlock.blinded.block.Version()) + require.NoError(t, err) + _, err = blocks.BuildSignedBeaconBlockFromExecutionPayload(fx.emptyDenebBlock.blinded.block, emptyPayload) + require.NoError(t, err) + fullHtr, err := fx.denebBlock.full.Block().HashTreeRoot() + require.NoError(t, err) + blindedHtr, err := fx.denebBlock.blinded.block.Block().HashTreeRoot() + require.NoError(t, err) + require.Equal(t, fullHtr, blindedHtr) + fullEmptyHtr, err := fx.emptyDenebBlock.full.Block().HashTreeRoot() + require.NoError(t, err) + blindedEmptyHtr, err := fx.emptyDenebBlock.blinded.block.Block().HashTreeRoot() require.NoError(t, err) - require.Equal(t, 3, len(results)) - require.NotNil(t, results[0]) - require.IsNil(t, results[1]) - require.NotNil(t, results[2]) + require.Equal(t, fullEmptyHtr, blindedEmptyHtr) }) } diff --git a/testing/util/deneb.go b/testing/util/deneb.go index 12a888bf9d11..7549bb821814 100644 --- a/testing/util/deneb.go +++ b/testing/util/deneb.go @@ -43,7 +43,7 @@ func WithProposerSigning(idx primitives.ValidatorIndex, sk bls.SecretKey, valRoo } } -func WithPayloadSetter(p *enginev1.ExecutionPayloadDeneb) DenebBlockGeneratorOption { +func WithGeneratorOptionPayload(p *enginev1.ExecutionPayloadDeneb) DenebBlockGeneratorOption { return func(g *denebBlockGenerator) { g.payload = p }