From af706777787eda7c2dbbc964a63867eb92af6b46 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:12:20 -0500 Subject: [PATCH] Deneb: Produce Block V3 - adding consensus block value (#12948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * adding in block rewards to represent consensus payload * Update beacon-chain/rpc/eth/validator/handlers_block.go Co-authored-by: RadosÅ‚aw Kapka * radek's comments * more review changes * adding more tests for forks * gaz * updating names * gaz * fixing imports * fixing variable name * gaz * fixing test * renaming variables to match data --------- Co-authored-by: RadosÅ‚aw Kapka --- api/headers.go | 1 + beacon-chain/rpc/eth/rewards/BUILD.bazel | 4 + beacon-chain/rpc/eth/rewards/handlers.go | 88 ++----- beacon-chain/rpc/eth/rewards/handlers_test.go | 214 +++++++++++++++--- beacon-chain/rpc/eth/rewards/server.go | 3 +- beacon-chain/rpc/eth/rewards/service.go | 124 ++++++++++ beacon-chain/rpc/eth/rewards/structs.go | 6 +- .../rpc/eth/rewards/testing/BUILD.bazel | 14 ++ beacon-chain/rpc/eth/rewards/testing/mock.go | 30 +++ beacon-chain/rpc/eth/shared/testing/json.go | 20 +- beacon-chain/rpc/eth/validator/BUILD.bazel | 5 + .../rpc/eth/validator/handlers_block.go | 117 ++++++++-- .../rpc/eth/validator/handlers_block_test.go | 67 +++++- beacon-chain/rpc/eth/validator/server.go | 2 + beacon-chain/rpc/eth/validator/structs.go | 1 + beacon-chain/rpc/service.go | 5 +- .../v1alpha1/attestation/attestation_utils.go | 4 +- 17 files changed, 547 insertions(+), 158 deletions(-) create mode 100644 beacon-chain/rpc/eth/rewards/service.go create mode 100644 beacon-chain/rpc/eth/rewards/testing/BUILD.bazel create mode 100644 beacon-chain/rpc/eth/rewards/testing/mock.go diff --git a/api/headers.go b/api/headers.go index cef8e941a8..5973b81408 100644 --- a/api/headers.go +++ b/api/headers.go @@ -4,6 +4,7 @@ const ( VersionHeader = "Eth-Consensus-Version" ExecutionPayloadBlindedHeader = "Eth-Execution-Payload-Blinded" ExecutionPayloadValueHeader = "Eth-Execution-Payload-Value" + ConsensusBlockValueHeader = "Eth-Consensus-Block-Value" JsonMediaType = "application/json" OctetStreamMediaType = "application/octet-stream" ) diff --git a/beacon-chain/rpc/eth/rewards/BUILD.bazel b/beacon-chain/rpc/eth/rewards/BUILD.bazel index ba347f3e10..4b158c8dfd 100644 --- a/beacon-chain/rpc/eth/rewards/BUILD.bazel +++ b/beacon-chain/rpc/eth/rewards/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "handlers.go", "server.go", + "service.go", "structs.go", ], importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards", @@ -21,11 +22,13 @@ go_library( "//beacon-chain/state/stategen:go_default_library", "//config/fieldparams:go_default_library", "//config/params:go_default_library", + "//consensus-types/interfaces:go_default_library", "//consensus-types/primitives:go_default_library", "//network/http:go_default_library", "//runtime/version:go_default_library", "//time/slots:go_default_library", "@com_github_wealdtech_go_bytesutil//:go_default_library", + "@io_opencensus_go//trace:go_default_library", ], ) @@ -54,6 +57,7 @@ go_test( "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", + "@com_github_pkg_errors//:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", ], ) diff --git a/beacon-chain/rpc/eth/rewards/handlers.go b/beacon-chain/rpc/eth/rewards/handlers.go index 34a3049d4e..4e963899fa 100644 --- a/beacon-chain/rpc/eth/rewards/handlers.go +++ b/beacon-chain/rpc/eth/rewards/handlers.go @@ -8,9 +8,7 @@ import ( "strings" "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/altair" - coreblocks "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/blocks" "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/epoch/precompute" - "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/validators" "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" "github.com/prysmaticlabs/prysm/v4/beacon-chain/state" fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" @@ -20,10 +18,13 @@ import ( "github.com/prysmaticlabs/prysm/v4/runtime/version" "github.com/prysmaticlabs/prysm/v4/time/slots" "github.com/wealdtech/go-bytesutil" + "go.opencensus.io/trace" ) // BlockRewards is an HTTP handler for Beacon API getBlockRewards. func (s *Server) BlockRewards(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "beacon.BlockRewards") + defer span.End() segments := strings.Split(r.URL.Path, "/") blockId := segments[len(segments)-1] @@ -36,63 +37,6 @@ func (s *Server) BlockRewards(w http.ResponseWriter, r *http.Request) { return } - // We want to run several block processing functions that update the proposer's balance. - // This will allow us to calculate proposer rewards for each operation (atts, slashings etc). - // To do this, we replay the state up to the block's slot, but before processing the block. - st, err := s.ReplayerBuilder.ReplayerForSlot(blk.Block().Slot()-1).ReplayToSlot(r.Context(), blk.Block().Slot()) - if err != nil { - http2.HandleError(w, "Could not get state: "+err.Error(), http.StatusInternalServerError) - return - } - - proposerIndex := blk.Block().ProposerIndex() - initBalance, err := st.BalanceAtIndex(proposerIndex) - if err != nil { - http2.HandleError(w, "Could not get proposer's balance: "+err.Error(), http.StatusInternalServerError) - return - } - st, err = altair.ProcessAttestationsNoVerifySignature(r.Context(), st, blk) - if err != nil { - http2.HandleError(w, "Could not get attestation rewards"+err.Error(), http.StatusInternalServerError) - return - } - attBalance, err := st.BalanceAtIndex(proposerIndex) - if err != nil { - http2.HandleError(w, "Could not get proposer's balance: "+err.Error(), http.StatusInternalServerError) - return - } - st, err = coreblocks.ProcessAttesterSlashings(r.Context(), st, blk.Block().Body().AttesterSlashings(), validators.SlashValidator) - if err != nil { - http2.HandleError(w, "Could not get attester slashing rewards: "+err.Error(), http.StatusInternalServerError) - return - } - attSlashingsBalance, err := st.BalanceAtIndex(proposerIndex) - if err != nil { - http2.HandleError(w, "Could not get proposer's balance: "+err.Error(), http.StatusInternalServerError) - return - } - st, err = coreblocks.ProcessProposerSlashings(r.Context(), st, blk.Block().Body().ProposerSlashings(), validators.SlashValidator) - if err != nil { - http2.HandleError(w, "Could not get proposer slashing rewards"+err.Error(), http.StatusInternalServerError) - return - } - proposerSlashingsBalance, err := st.BalanceAtIndex(proposerIndex) - if err != nil { - http2.HandleError(w, "Could not get proposer's balance: "+err.Error(), http.StatusInternalServerError) - return - } - sa, err := blk.Block().Body().SyncAggregate() - if err != nil { - http2.HandleError(w, "Could not get sync aggregate: "+err.Error(), http.StatusInternalServerError) - return - } - var syncCommitteeReward uint64 - _, syncCommitteeReward, err = altair.ProcessSyncAggregate(r.Context(), st, sa) - if err != nil { - http2.HandleError(w, "Could not get sync aggregate rewards: "+err.Error(), http.StatusInternalServerError) - return - } - optimistic, err := s.OptimisticModeFetcher.IsOptimistic(r.Context()) if err != nil { http2.HandleError(w, "Could not get optimistic mode info: "+err.Error(), http.StatusInternalServerError) @@ -103,18 +47,15 @@ func (s *Server) BlockRewards(w http.ResponseWriter, r *http.Request) { http2.HandleError(w, "Could not get block root: "+err.Error(), http.StatusInternalServerError) return } - + blockRewards, httpError := s.BlockRewardFetcher.GetBlockRewardsData(ctx, blk) + if httpError != nil { + http2.WriteError(w, httpError) + return + } response := &BlockRewardsResponse{ - Data: BlockRewards{ - ProposerIndex: strconv.FormatUint(uint64(proposerIndex), 10), - Total: strconv.FormatUint(proposerSlashingsBalance-initBalance+syncCommitteeReward, 10), - Attestations: strconv.FormatUint(attBalance-initBalance, 10), - SyncAggregate: strconv.FormatUint(syncCommitteeReward, 10), - ProposerSlashings: strconv.FormatUint(proposerSlashingsBalance-attSlashingsBalance, 10), - AttesterSlashings: strconv.FormatUint(attSlashingsBalance-attBalance, 10), - }, + Data: blockRewards, ExecutionOptimistic: optimistic, - Finalized: s.FinalizationFetcher.IsFinalized(r.Context(), blkRoot), + Finalized: s.FinalizationFetcher.IsFinalized(ctx, blkRoot), } http2.WriteJson(w, response) } @@ -165,6 +106,8 @@ func (s *Server) AttestationRewards(w http.ResponseWriter, r *http.Request) { // SyncCommitteeRewards retrieves rewards info for sync committee members specified by array of public keys or validator index. // If no array is provided, return reward info for every committee member. func (s *Server) SyncCommitteeRewards(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "beacon.SyncCommitteeRewards") + defer span.End() segments := strings.Split(r.URL.Path, "/") blockId := segments[len(segments)-1] @@ -176,9 +119,10 @@ func (s *Server) SyncCommitteeRewards(w http.ResponseWriter, r *http.Request) { http2.HandleError(w, "Sync committee rewards are not supported for Phase 0", http.StatusBadRequest) return } - st, err := s.ReplayerBuilder.ReplayerForSlot(blk.Block().Slot()-1).ReplayToSlot(r.Context(), blk.Block().Slot()) - if err != nil { - http2.HandleError(w, "Could not get state: "+err.Error(), http.StatusInternalServerError) + + st, httpErr := s.BlockRewardFetcher.GetStateForRewards(ctx, blk) + if httpErr != nil { + http2.WriteError(w, httpErr) return } sa, err := blk.Block().Body().SyncAggregate() diff --git a/beacon-chain/rpc/eth/rewards/handlers_test.go b/beacon-chain/rpc/eth/rewards/handlers_test.go index d4e6931dc8..b309dc3ac6 100644 --- a/beacon-chain/rpc/eth/rewards/handlers_test.go +++ b/beacon-chain/rpc/eth/rewards/handlers_test.go @@ -11,6 +11,7 @@ import ( "strings" "testing" + "github.com/pkg/errors" "github.com/prysmaticlabs/go-bitfield" mock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing" "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/altair" @@ -34,12 +35,42 @@ import ( "github.com/prysmaticlabs/prysm/v4/testing/util" ) -func TestBlockRewards(t *testing.T) { +func BlockRewardTestSetup(t *testing.T, forkName string) (state.BeaconState, interfaces.SignedBeaconBlock, error) { helpers.ClearCache() - + var sbb interfaces.SignedBeaconBlock + var st state.BeaconState + var err error + switch forkName { + case "phase0": + return nil, nil, errors.New("phase0 not supported") + case "altair": + st, err = util.NewBeaconStateAltair() + require.NoError(t, err) + b := util.HydrateSignedBeaconBlockAltair(util.NewBeaconBlockAltair()) + sbb, err = blocks.NewSignedBeaconBlock(b) + require.NoError(t, err) + case "bellatrix": + st, err = util.NewBeaconStateBellatrix() + require.NoError(t, err) + b := util.HydrateSignedBeaconBlockBellatrix(util.NewBeaconBlockBellatrix()) + sbb, err = blocks.NewSignedBeaconBlock(b) + require.NoError(t, err) + case "capella": + st, err = util.NewBeaconStateCapella() + require.NoError(t, err) + b := util.HydrateSignedBeaconBlockCapella(util.NewBeaconBlockCapella()) + sbb, err = blocks.NewSignedBeaconBlock(b) + require.NoError(t, err) + case "deneb": + st, err = util.NewBeaconStateDeneb() + require.NoError(t, err) + b := util.HydrateSignedBeaconBlockDeneb(util.NewBeaconBlockDeneb()) + sbb, err = blocks.NewSignedBeaconBlock(b) + require.NoError(t, err) + default: + return nil, nil, errors.New("fork is not supported") + } valCount := 64 - - st, err := util.NewBeaconStateCapella() require.NoError(t, st.SetSlot(1)) require.NoError(t, err) validators := make([]*eth.Validator, 0, valCount) @@ -68,11 +99,10 @@ func TestBlockRewards(t *testing.T) { bRoots[0] = slot0bRoot require.NoError(t, st.SetBlockRoots(bRoots)) - b := util.HydrateSignedBeaconBlockCapella(util.NewBeaconBlockCapella()) - b.Block.Slot = 2 + sbb.SetSlot(2) // we have to set the proposer index to the value that will be randomly chosen (fortunately it's deterministic) - b.Block.ProposerIndex = 12 - b.Block.Body.Attestations = []*eth.Attestation{ + sbb.SetProposerIndex(12) + sbb.SetAttestations([]*eth.Attestation{ { AggregationBits: bitfield.Bitlist{0b00000111}, Data: util.HydrateAttestationData(ð.AttestationData{}), @@ -83,7 +113,8 @@ func TestBlockRewards(t *testing.T) { Data: util.HydrateAttestationData(ð.AttestationData{}), Signature: make([]byte, fieldparams.BLSSignatureLength), }, - } + }) + attData1 := util.HydrateAttestationData(ð.AttestationData{BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32)}) attData2 := util.HydrateAttestationData(ð.AttestationData{BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32)}) domain, err := signing.Domain(st.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, st.GenesisValidatorsRoot()) @@ -92,7 +123,7 @@ func TestBlockRewards(t *testing.T) { require.NoError(t, err) sigRoot2, err := signing.ComputeSigningRoot(attData2, domain) require.NoError(t, err) - b.Block.Body.AttesterSlashings = []*eth.AttesterSlashing{ + sbb.SetAttesterSlashings([]*eth.AttesterSlashing{ { Attestation_1: ð.IndexedAttestation{ AttestingIndices: []uint64{0}, @@ -105,7 +136,7 @@ func TestBlockRewards(t *testing.T) { Signature: secretKeys[0].Sign(sigRoot2[:]).Marshal(), }, }, - } + }) header1 := ð.BeaconBlockHeader{ Slot: 0, ProposerIndex: 1, @@ -126,7 +157,7 @@ func TestBlockRewards(t *testing.T) { require.NoError(t, err) sigRoot2, err = signing.ComputeSigningRoot(header2, domain) require.NoError(t, err) - b.Block.Body.ProposerSlashings = []*eth.ProposerSlashing{ + sbb.SetProposerSlashings([]*eth.ProposerSlashing{ { Header_1: ð.SignedBeaconBlockHeader{ Header: header1, @@ -137,7 +168,7 @@ func TestBlockRewards(t *testing.T) { Signature: secretKeys[1].Sign(sigRoot2[:]).Marshal(), }, }, - } + }) scBits := bitfield.NewBitvector512() scBits.SetBitAt(10, true) scBits.SetBitAt(100, true) @@ -153,24 +184,51 @@ func TestBlockRewards(t *testing.T) { sig2, err := blst.SignatureFromBytes(secretKeys[19].Sign(r[:]).Marshal()) require.NoError(t, err) aggSig := bls.AggregateSignatures([]bls.Signature{sig1, sig2}).Marshal() - b.Block.Body.SyncAggregate = ð.SyncAggregate{SyncCommitteeBits: scBits, SyncCommitteeSignature: aggSig} - - sbb, err := blocks.NewSignedBeaconBlock(b) + err = sbb.SetSyncAggregate(ð.SyncAggregate{SyncCommitteeBits: scBits, SyncCommitteeSignature: aggSig}) require.NoError(t, err) + + return st, sbb, nil +} + +func TestBlockRewards(t *testing.T) { phase0block, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock()) require.NoError(t, err) - mockChainService := &mock.ChainService{Optimistic: true} - s := &Server{ - Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - 0: phase0block, - 2: sbb, - }}, - OptimisticModeFetcher: mockChainService, - FinalizationFetcher: mockChainService, - ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st)), - } + t.Run("phase 0", func(t *testing.T) { + mockChainService := &mock.ChainService{Optimistic: true} + s := &Server{ + Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ + 0: phase0block, + }}, + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + } + url := "http://only.the.slot.number.at.the.end.is.important/0" + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.BlockRewards(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &http2.DefaultErrorJson{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.Equal(t, "Block rewards are not supported for Phase 0 blocks", e.Message) + }) + t.Run("altair", func(t *testing.T) { + st, sbb, err := BlockRewardTestSetup(t, "altair") + require.NoError(t, err) + + mockChainService := &mock.ChainService{Optimistic: true} + s := &Server{ + Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ + 0: phase0block, + 2: sbb, + }}, + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + BlockRewardFetcher: &BlockRewardService{Replayer: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st))}, + } - t.Run("ok", func(t *testing.T) { url := "http://only.the.slot.number.at.the.end.is.important/2" request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() @@ -189,18 +247,104 @@ func TestBlockRewards(t *testing.T) { assert.Equal(t, true, resp.ExecutionOptimistic) assert.Equal(t, false, resp.Finalized) }) - t.Run("phase 0", func(t *testing.T) { - url := "http://only.the.slot.number.at.the.end.is.important/0" + t.Run("bellatrix", func(t *testing.T) { + st, sbb, err := BlockRewardTestSetup(t, "bellatrix") + require.NoError(t, err) + + mockChainService := &mock.ChainService{Optimistic: true} + s := &Server{ + Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ + 0: phase0block, + 2: sbb, + }}, + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + BlockRewardFetcher: &BlockRewardService{Replayer: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st))}, + } + + url := "http://only.the.slot.number.at.the.end.is.important/2" request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} s.BlockRewards(writer, request) - assert.Equal(t, http.StatusBadRequest, writer.Code) - e := &http2.DefaultErrorJson{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) - assert.Equal(t, http.StatusBadRequest, e.Code) - assert.Equal(t, "Block rewards are not supported for Phase 0 blocks", e.Message) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &BlockRewardsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, "12", resp.Data.ProposerIndex) + assert.Equal(t, "125089490", resp.Data.Total) + assert.Equal(t, "89442", resp.Data.Attestations) + assert.Equal(t, "48", resp.Data.SyncAggregate) + assert.Equal(t, "62500000", resp.Data.AttesterSlashings) + assert.Equal(t, "62500000", resp.Data.ProposerSlashings) + assert.Equal(t, true, resp.ExecutionOptimistic) + assert.Equal(t, false, resp.Finalized) + }) + t.Run("capella", func(t *testing.T) { + st, sbb, err := BlockRewardTestSetup(t, "capella") + require.NoError(t, err) + + mockChainService := &mock.ChainService{Optimistic: true} + s := &Server{ + Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ + 0: phase0block, + 2: sbb, + }}, + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + BlockRewardFetcher: &BlockRewardService{Replayer: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st))}, + } + + url := "http://only.the.slot.number.at.the.end.is.important/2" + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.BlockRewards(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &BlockRewardsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, "12", resp.Data.ProposerIndex) + assert.Equal(t, "125089490", resp.Data.Total) + assert.Equal(t, "89442", resp.Data.Attestations) + assert.Equal(t, "48", resp.Data.SyncAggregate) + assert.Equal(t, "62500000", resp.Data.AttesterSlashings) + assert.Equal(t, "62500000", resp.Data.ProposerSlashings) + assert.Equal(t, true, resp.ExecutionOptimistic) + assert.Equal(t, false, resp.Finalized) + }) + t.Run("deneb", func(t *testing.T) { + st, sbb, err := BlockRewardTestSetup(t, "deneb") + require.NoError(t, err) + + mockChainService := &mock.ChainService{Optimistic: true} + s := &Server{ + Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ + 0: phase0block, + 2: sbb, + }}, + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + BlockRewardFetcher: &BlockRewardService{Replayer: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st))}, + } + + url := "http://only.the.slot.number.at.the.end.is.important/2" + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.BlockRewards(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &BlockRewardsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, "12", resp.Data.ProposerIndex) + assert.Equal(t, "125089490", resp.Data.Total) + assert.Equal(t, "89442", resp.Data.Attestations) + assert.Equal(t, "48", resp.Data.SyncAggregate) + assert.Equal(t, "62500000", resp.Data.AttesterSlashings) + assert.Equal(t, "62500000", resp.Data.ProposerSlashings) + assert.Equal(t, true, resp.ExecutionOptimistic) + assert.Equal(t, false, resp.Finalized) }) } @@ -560,7 +704,7 @@ func TestSyncCommiteeRewards(t *testing.T) { }}, OptimisticModeFetcher: mockChainService, FinalizationFetcher: mockChainService, - ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st)), + BlockRewardFetcher: &BlockRewardService{Replayer: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st))}, } t.Run("ok - filtered vals", func(t *testing.T) { diff --git a/beacon-chain/rpc/eth/rewards/server.go b/beacon-chain/rpc/eth/rewards/server.go index 91d3023279..2dfee41c8e 100644 --- a/beacon-chain/rpc/eth/rewards/server.go +++ b/beacon-chain/rpc/eth/rewards/server.go @@ -3,15 +3,14 @@ package rewards import ( "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain" "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup" - "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen" ) type Server struct { Blocker lookup.Blocker OptimisticModeFetcher blockchain.OptimisticModeFetcher FinalizationFetcher blockchain.FinalizationFetcher - ReplayerBuilder stategen.ReplayerBuilder TimeFetcher blockchain.TimeFetcher Stater lookup.Stater HeadFetcher blockchain.HeadFetcher + BlockRewardFetcher BlockRewardsFetcher } diff --git a/beacon-chain/rpc/eth/rewards/service.go b/beacon-chain/rpc/eth/rewards/service.go new file mode 100644 index 0000000000..5fa4eb6c59 --- /dev/null +++ b/beacon-chain/rpc/eth/rewards/service.go @@ -0,0 +1,124 @@ +package rewards + +import ( + "context" + "net/http" + "strconv" + + "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/altair" + coreblocks "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/blocks" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/validators" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen" + "github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces" + http2 "github.com/prysmaticlabs/prysm/v4/network/http" +) + +// BlockRewardsFetcher is a interface that provides access to reward related responses +type BlockRewardsFetcher interface { + GetBlockRewardsData(context.Context, interfaces.ReadOnlySignedBeaconBlock) (*BlockRewards, *http2.DefaultErrorJson) + GetStateForRewards(context.Context, interfaces.ReadOnlySignedBeaconBlock) (state.BeaconState, *http2.DefaultErrorJson) +} + +// BlockRewardService implements BlockRewardsFetcher and can be declared to access the underlying functions +type BlockRewardService struct { + Replayer stategen.ReplayerBuilder +} + +// GetBlockRewardsData returns the BlockRewards Object which is used for the BlockRewardsResponse and ProduceBlockV3 +func (rs *BlockRewardService) GetBlockRewardsData(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (*BlockRewards, *http2.DefaultErrorJson) { + st, httpErr := rs.GetStateForRewards(ctx, blk) + if httpErr != nil { + return nil, httpErr + } + + proposerIndex := blk.Block().ProposerIndex() + initBalance, err := st.BalanceAtIndex(proposerIndex) + if err != nil { + return nil, &http2.DefaultErrorJson{ + Message: "Could not get proposer's balance: " + err.Error(), + Code: http.StatusInternalServerError, + } + } + st, err = altair.ProcessAttestationsNoVerifySignature(ctx, st, blk) + if err != nil { + return nil, &http2.DefaultErrorJson{ + Message: "Could not get attestation rewards: " + err.Error(), + Code: http.StatusInternalServerError, + } + } + attBalance, err := st.BalanceAtIndex(proposerIndex) + if err != nil { + return nil, &http2.DefaultErrorJson{ + Message: "Could not get proposer's balance: " + err.Error(), + Code: http.StatusInternalServerError, + } + } + st, err = coreblocks.ProcessAttesterSlashings(ctx, st, blk.Block().Body().AttesterSlashings(), validators.SlashValidator) + if err != nil { + return nil, &http2.DefaultErrorJson{ + Message: "Could not get attester slashing rewards: " + err.Error(), + Code: http.StatusInternalServerError, + } + } + attSlashingsBalance, err := st.BalanceAtIndex(proposerIndex) + if err != nil { + return nil, &http2.DefaultErrorJson{ + Message: "Could not get proposer's balance: " + err.Error(), + Code: http.StatusInternalServerError, + } + } + st, err = coreblocks.ProcessProposerSlashings(ctx, st, blk.Block().Body().ProposerSlashings(), validators.SlashValidator) + if err != nil { + return nil, &http2.DefaultErrorJson{ + Message: "Could not get proposer slashing rewards: " + err.Error(), + Code: http.StatusInternalServerError, + } + } + proposerSlashingsBalance, err := st.BalanceAtIndex(proposerIndex) + if err != nil { + return nil, &http2.DefaultErrorJson{ + Message: "Could not get proposer's balance: " + err.Error(), + Code: http.StatusInternalServerError, + } + } + sa, err := blk.Block().Body().SyncAggregate() + if err != nil { + return nil, &http2.DefaultErrorJson{ + Message: "Could not get sync aggregate: " + err.Error(), + Code: http.StatusInternalServerError, + } + } + var syncCommitteeReward uint64 + _, syncCommitteeReward, err = altair.ProcessSyncAggregate(ctx, st, sa) + if err != nil { + return nil, &http2.DefaultErrorJson{ + Message: "Could not get sync aggregate rewards: " + err.Error(), + Code: http.StatusInternalServerError, + } + } + + return &BlockRewards{ + ProposerIndex: strconv.FormatUint(uint64(proposerIndex), 10), + Total: strconv.FormatUint(proposerSlashingsBalance-initBalance+syncCommitteeReward, 10), + Attestations: strconv.FormatUint(attBalance-initBalance, 10), + SyncAggregate: strconv.FormatUint(syncCommitteeReward, 10), + ProposerSlashings: strconv.FormatUint(proposerSlashingsBalance-attSlashingsBalance, 10), + AttesterSlashings: strconv.FormatUint(attSlashingsBalance-attBalance, 10), + }, nil +} + +// GetStateForRewards returns the state replayed up to the block's slot +func (rs *BlockRewardService) GetStateForRewards(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (state.BeaconState, *http2.DefaultErrorJson) { + // We want to run several block processing functions that update the proposer's balance. + // This will allow us to calculate proposer rewards for each operation (atts, slashings etc). + // To do this, we replay the state up to the block's slot, but before processing the block. + st, err := rs.Replayer.ReplayerForSlot(blk.Block().Slot()-1).ReplayToSlot(ctx, blk.Block().Slot()) + if err != nil { + return nil, &http2.DefaultErrorJson{ + Message: "Could not get state: " + err.Error(), + Code: http.StatusInternalServerError, + } + } + return st, nil +} diff --git a/beacon-chain/rpc/eth/rewards/structs.go b/beacon-chain/rpc/eth/rewards/structs.go index 58e95786fb..39a0eddec4 100644 --- a/beacon-chain/rpc/eth/rewards/structs.go +++ b/beacon-chain/rpc/eth/rewards/structs.go @@ -1,9 +1,9 @@ package rewards type BlockRewardsResponse struct { - Data BlockRewards `json:"data"` - ExecutionOptimistic bool `json:"execution_optimistic"` - Finalized bool `json:"finalized"` + Data *BlockRewards `json:"data"` + ExecutionOptimistic bool `json:"execution_optimistic"` + Finalized bool `json:"finalized"` } type BlockRewards struct { diff --git a/beacon-chain/rpc/eth/rewards/testing/BUILD.bazel b/beacon-chain/rpc/eth/rewards/testing/BUILD.bazel new file mode 100644 index 0000000000..02176f7dc9 --- /dev/null +++ b/beacon-chain/rpc/eth/rewards/testing/BUILD.bazel @@ -0,0 +1,14 @@ +load("@prysm//tools/go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["mock.go"], + importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards/testing", + visibility = ["//visibility:public"], + deps = [ + "//beacon-chain/rpc/eth/rewards:go_default_library", + "//beacon-chain/state:go_default_library", + "//consensus-types/interfaces:go_default_library", + "//network/http:go_default_library", + ], +) diff --git a/beacon-chain/rpc/eth/rewards/testing/mock.go b/beacon-chain/rpc/eth/rewards/testing/mock.go new file mode 100644 index 0000000000..0887033930 --- /dev/null +++ b/beacon-chain/rpc/eth/rewards/testing/mock.go @@ -0,0 +1,30 @@ +package testing + +import ( + "context" + + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces" + http2 "github.com/prysmaticlabs/prysm/v4/network/http" +) + +type MockBlockRewardFetcher struct { + Rewards *rewards.BlockRewards + Error *http2.DefaultErrorJson + State state.BeaconState +} + +func (m *MockBlockRewardFetcher) GetBlockRewardsData(_ context.Context, _ interfaces.ReadOnlySignedBeaconBlock) (*rewards.BlockRewards, *http2.DefaultErrorJson) { + if m.Error != nil { + return nil, m.Error + } + return m.Rewards, nil +} + +func (m *MockBlockRewardFetcher) GetStateForRewards(_ context.Context, _ interfaces.ReadOnlySignedBeaconBlock) (state.BeaconState, *http2.DefaultErrorJson) { + if m.Error != nil { + return nil, m.Error + } + return m.State, nil +} diff --git a/beacon-chain/rpc/eth/shared/testing/json.go b/beacon-chain/rpc/eth/shared/testing/json.go index 4cad19d098..97814a489b 100644 --- a/beacon-chain/rpc/eth/shared/testing/json.go +++ b/beacon-chain/rpc/eth/shared/testing/json.go @@ -162,7 +162,7 @@ const ( }` AltairBlock = `{ "message": { - "slot": "1", + "slot": "2", "proposer_index": "1", "parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", @@ -204,7 +204,7 @@ const ( "attesting_indices": [ "1" ], - "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505", + "signature": "0xabb0124c7574f281a293f4185cad3cb22681d520917ce46665243eacb051000d8bacf75e1451870ca6b3b9e6c9d41a7b02ead2685a84188a4fafd3825daf6a989625d719ccd2d83a40101f4a453fca62878c890eca622363f9ddb8f367a91e84", "data": { "slot": "1", "index": "1", @@ -223,18 +223,18 @@ const ( "attesting_indices": [ "1" ], - "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505", + "signature": "0xabb0124c7574f281a293f4185cad3cb22681d520917ce46665243eacb051000d8bacf75e1451870ca6b3b9e6c9d41a7b02ead2685a84188a4fafd3825daf6a989625d719ccd2d83a40101f4a453fca62878c890eca622363f9ddb8f367a91e84", "data": { "slot": "1", "index": "1", "beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", "source": { "epoch": "1", - "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f3" }, "target": { "epoch": "1", - "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f3" } } } @@ -242,18 +242,18 @@ const ( ], "attestations": [ { - "aggregation_bits": "0xffffffffffffffffffffffffffffffffff3f", - "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505", + "aggregation_bits": "0x07", + "signature": "0xabb0124c7574f281a293f4185cad3cb22681d520917ce46665243eacb051000d8bacf75e1451870ca6b3b9e6c9d41a7b02ead2685a84188a4fafd3825daf6a989625d719ccd2d83a40101f4a453fca62878c890eca622363f9ddb8f367a91e84", "data": { - "slot": "1", - "index": "1", + "slot": "0", + "index": "0", "beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", "source": { "epoch": "1", "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" }, "target": { - "epoch": "1", + "epoch": "0", "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" } } diff --git a/beacon-chain/rpc/eth/validator/BUILD.bazel b/beacon-chain/rpc/eth/validator/BUILD.bazel index d79f652d7e..ed673036db 100644 --- a/beacon-chain/rpc/eth/validator/BUILD.bazel +++ b/beacon-chain/rpc/eth/validator/BUILD.bazel @@ -26,6 +26,7 @@ go_library( "//beacon-chain/p2p:go_default_library", "//beacon-chain/rpc/core:go_default_library", "//beacon-chain/rpc/eth/helpers:go_default_library", + "//beacon-chain/rpc/eth/rewards:go_default_library", "//beacon-chain/rpc/eth/shared:go_default_library", "//beacon-chain/rpc/lookup:go_default_library", "//beacon-chain/state:go_default_library", @@ -33,6 +34,8 @@ go_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", "//consensus-types/validator:go_default_library", "//encoding/bytesutil:go_default_library", @@ -75,6 +78,8 @@ go_test( "//beacon-chain/operations/synccommittee:go_default_library", "//beacon-chain/p2p/testing:go_default_library", "//beacon-chain/rpc/core:go_default_library", + "//beacon-chain/rpc/eth/rewards:go_default_library", + "//beacon-chain/rpc/eth/rewards/testing:go_default_library", "//beacon-chain/rpc/eth/shared:go_default_library", "//beacon-chain/rpc/eth/shared/testing:go_default_library", "//beacon-chain/rpc/testutil:go_default_library", diff --git a/beacon-chain/rpc/eth/validator/handlers_block.go b/beacon-chain/rpc/eth/validator/handlers_block.go index 9568d36654..14f59ac555 100644 --- a/beacon-chain/rpc/eth/validator/handlers_block.go +++ b/beacon-chain/rpc/eth/validator/handlers_block.go @@ -9,8 +9,11 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v4/api" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards" "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" + "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks" + "github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" http2 "github.com/prysmaticlabs/prysm/v4/network/http" eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" @@ -84,16 +87,26 @@ func (s *Server) produceBlockV3(ctx context.Context, w http.ResponseWriter, r *h http2.HandleError(w, err.Error(), http.StatusInternalServerError) return } + + consensusBlockValue, httpError := getConsensusBlockValue(ctx, s.BlockRewardFetcher, v1alpha1resp.Block) + if httpError != nil { + http2.WriteError(w, httpError) + return + } + w.Header().Set(api.ExecutionPayloadBlindedHeader, fmt.Sprintf("%v", v1alpha1resp.IsBlinded)) w.Header().Set(api.ExecutionPayloadValueHeader, fmt.Sprintf("%d", v1alpha1resp.PayloadValue)) + w.Header().Set(api.ConsensusBlockValueHeader, consensusBlockValue) + phase0Block, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Phase0) if ok { + // rewards aren't used in phase 0 handleProducePhase0V3(ctx, w, isSSZ, phase0Block, v1alpha1resp.PayloadValue) return } altairBlock, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Altair) if ok { - handleProduceAltairV3(ctx, w, isSSZ, altairBlock, v1alpha1resp.PayloadValue) + handleProduceAltairV3(ctx, w, isSSZ, altairBlock, v1alpha1resp.PayloadValue, consensusBlockValue) return } optimistic, err := s.OptimisticModeFetcher.IsOptimistic(ctx) @@ -107,36 +120,82 @@ func (s *Server) produceBlockV3(ctx context.Context, w http.ResponseWriter, r *h } blindedBellatrixBlock, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_BlindedBellatrix) if ok { - handleProduceBlindedBellatrixV3(ctx, w, isSSZ, blindedBellatrixBlock, v1alpha1resp.PayloadValue) + handleProduceBlindedBellatrixV3(ctx, w, isSSZ, blindedBellatrixBlock, v1alpha1resp.PayloadValue, consensusBlockValue) return } bellatrixBlock, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Bellatrix) if ok { - handleProduceBellatrixV3(ctx, w, isSSZ, bellatrixBlock, v1alpha1resp.PayloadValue) + handleProduceBellatrixV3(ctx, w, isSSZ, bellatrixBlock, v1alpha1resp.PayloadValue, consensusBlockValue) return } blindedCapellaBlock, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_BlindedCapella) if ok { - handleProduceBlindedCapellaV3(ctx, w, isSSZ, blindedCapellaBlock, v1alpha1resp.PayloadValue) + handleProduceBlindedCapellaV3(ctx, w, isSSZ, blindedCapellaBlock, v1alpha1resp.PayloadValue, consensusBlockValue) return } capellaBlock, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Capella) if ok { - handleProduceCapellaV3(ctx, w, isSSZ, capellaBlock, v1alpha1resp.PayloadValue) + handleProduceCapellaV3(ctx, w, isSSZ, capellaBlock, v1alpha1resp.PayloadValue, consensusBlockValue) return } blindedDenebBlockContents, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_BlindedDeneb) if ok { - handleProduceBlindedDenebV3(ctx, w, isSSZ, blindedDenebBlockContents, v1alpha1resp.PayloadValue) + handleProduceBlindedDenebV3(ctx, w, isSSZ, blindedDenebBlockContents, v1alpha1resp.PayloadValue, consensusBlockValue) return } denebBlockContents, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Deneb) if ok { - handleProduceDenebV3(ctx, w, isSSZ, denebBlockContents, v1alpha1resp.PayloadValue) + handleProduceDenebV3(ctx, w, isSSZ, denebBlockContents, v1alpha1resp.PayloadValue, consensusBlockValue) return } } +func getConsensusBlockValue(ctx context.Context, blockRewardsFetcher rewards.BlockRewardsFetcher, i interface{} /* block as argument */) (string, *http2.DefaultErrorJson) { + var wrapper interfaces.ReadOnlySignedBeaconBlock + var err error + + // TODO: we should not require this fake signed wrapper and fix associated functions in the future. + switch b := i.(type) { + case *eth.GenericBeaconBlock_Phase0: + //ignore for phase0 + return "", nil + case *eth.GenericBeaconBlock_Altair: + wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_Altair{Altair: ð.SignedBeaconBlockAltair{Block: b.Altair}}) + case *eth.GenericBeaconBlock_Bellatrix: + wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_Bellatrix{Bellatrix: ð.SignedBeaconBlockBellatrix{Block: b.Bellatrix}}) + case *eth.GenericBeaconBlock_BlindedBellatrix: + wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_BlindedBellatrix{BlindedBellatrix: ð.SignedBlindedBeaconBlockBellatrix{Block: b.BlindedBellatrix}}) + case *eth.GenericBeaconBlock_Capella: + wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_Capella{Capella: ð.SignedBeaconBlockCapella{Block: b.Capella}}) + case *eth.GenericBeaconBlock_BlindedCapella: + wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_BlindedCapella{BlindedCapella: ð.SignedBlindedBeaconBlockCapella{Block: b.BlindedCapella}}) + case *eth.GenericBeaconBlock_Deneb: + // no need for sidecar + wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_Deneb{Deneb: ð.SignedBeaconBlockAndBlobsDeneb{Block: ð.SignedBeaconBlockDeneb{Block: b.Deneb.Block}}}) + case *eth.GenericBeaconBlock_BlindedDeneb: + // no need for sidecar + wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_BlindedDeneb{BlindedDeneb: ð.SignedBlindedBeaconBlockAndBlobsDeneb{SignedBlindedBlock: ð.SignedBlindedBeaconBlockDeneb{Message: b.BlindedDeneb.Block}}}) + default: + return "", &http2.DefaultErrorJson{ + Message: fmt.Errorf("type %T is not supported", b).Error(), + Code: http.StatusInternalServerError, + } + } + if err != nil { + return "", &http2.DefaultErrorJson{ + Message: err.Error(), + Code: http.StatusInternalServerError, + } + } + + //get consensus payload value which is the same as the total from the block rewards api + blockRewards, httpError := blockRewardsFetcher.GetBlockRewardsData(ctx, wrapper) + if httpError != nil { + return "", httpError + } + return blockRewards.Total, nil +} + func handleProducePhase0V3( ctx context.Context, w http.ResponseWriter, @@ -169,6 +228,7 @@ func handleProducePhase0V3( Version: version.String(version.Phase0), ExecutionPayloadBlinded: false, ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), // mev not available at this point + ConsensusBlockValue: "", // rewards not applicable before altair Data: jsonBytes, }) } @@ -178,10 +238,12 @@ func handleProduceAltairV3( w http.ResponseWriter, isSSZ bool, blk *eth.GenericBeaconBlock_Altair, - payloadValue uint64, + executionPayloadValue uint64, + consensusPayloadValue string, ) { _, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceAltairV3") defer span.End() + if isSSZ { sszResp, err := blk.Altair.MarshalSSZ() if err != nil { @@ -204,7 +266,8 @@ func handleProduceAltairV3( http2.WriteJson(w, &ProduceBlockV3Response{ Version: version.String(version.Altair), ExecutionPayloadBlinded: false, - ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), // mev not available at this point + ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue), // mev not available at this point + ConsensusBlockValue: consensusPayloadValue, Data: jsonBytes, }) } @@ -214,7 +277,8 @@ func handleProduceBellatrixV3( w http.ResponseWriter, isSSZ bool, blk *eth.GenericBeaconBlock_Bellatrix, - payloadValue uint64, + executionPayloadValue uint64, + consensusPayloadValue string, ) { _, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceBellatrixV3") defer span.End() @@ -240,7 +304,8 @@ func handleProduceBellatrixV3( http2.WriteJson(w, &ProduceBlockV3Response{ Version: version.String(version.Bellatrix), ExecutionPayloadBlinded: false, - ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), // mev not available at this point + ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue), // mev not available at this point + ConsensusBlockValue: consensusPayloadValue, Data: jsonBytes, }) } @@ -250,7 +315,8 @@ func handleProduceBlindedBellatrixV3( w http.ResponseWriter, isSSZ bool, blk *eth.GenericBeaconBlock_BlindedBellatrix, - payloadValue uint64, + executionPayloadValue uint64, + consensusPayloadValue string, ) { _, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceBlindedBellatrixV3") defer span.End() @@ -276,7 +342,8 @@ func handleProduceBlindedBellatrixV3( http2.WriteJson(w, &ProduceBlockV3Response{ Version: version.String(version.Bellatrix), ExecutionPayloadBlinded: true, - ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), + ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue), + ConsensusBlockValue: consensusPayloadValue, Data: jsonBytes, }) } @@ -286,7 +353,8 @@ func handleProduceBlindedCapellaV3( w http.ResponseWriter, isSSZ bool, blk *eth.GenericBeaconBlock_BlindedCapella, - payloadValue uint64, + executionPayloadValue uint64, + consensusPayloadValue string, ) { _, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceBlindedCapellaV3") defer span.End() @@ -312,7 +380,8 @@ func handleProduceBlindedCapellaV3( http2.WriteJson(w, &ProduceBlockV3Response{ Version: version.String(version.Capella), ExecutionPayloadBlinded: true, - ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), + ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue), + ConsensusBlockValue: consensusPayloadValue, Data: jsonBytes, }) } @@ -322,7 +391,8 @@ func handleProduceCapellaV3( w http.ResponseWriter, isSSZ bool, blk *eth.GenericBeaconBlock_Capella, - payloadValue uint64, + executionPayloadValue uint64, + consensusPayloadValue string, ) { _, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceCapellaV3") defer span.End() @@ -348,7 +418,8 @@ func handleProduceCapellaV3( http2.WriteJson(w, &ProduceBlockV3Response{ Version: version.String(version.Capella), ExecutionPayloadBlinded: false, - ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), // mev not available at this point + ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue), // mev not available at this point + ConsensusBlockValue: consensusPayloadValue, Data: jsonBytes, }) } @@ -358,7 +429,8 @@ func handleProduceBlindedDenebV3( w http.ResponseWriter, isSSZ bool, blk *eth.GenericBeaconBlock_BlindedDeneb, - payloadValue uint64, + executionPayloadValue uint64, + consensusPayloadValue string, ) { _, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceBlindedDenebV3") defer span.End() @@ -384,7 +456,8 @@ func handleProduceBlindedDenebV3( http2.WriteJson(w, &ProduceBlockV3Response{ Version: version.String(version.Deneb), ExecutionPayloadBlinded: true, - ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), + ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue), + ConsensusBlockValue: consensusPayloadValue, Data: jsonBytes, }) } @@ -394,7 +467,8 @@ func handleProduceDenebV3( w http.ResponseWriter, isSSZ bool, blk *eth.GenericBeaconBlock_Deneb, - payloadValue uint64, + executionPayloadValue uint64, + consensusBlockValue string, ) { _, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceDenebV3") defer span.End() @@ -420,7 +494,8 @@ func handleProduceDenebV3( http2.WriteJson(w, &ProduceBlockV3Response{ Version: version.String(version.Deneb), ExecutionPayloadBlinded: false, - ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), // mev not available at this point + ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue), // mev not available at this point + ConsensusBlockValue: consensusBlockValue, Data: jsonBytes, }) } diff --git a/beacon-chain/rpc/eth/validator/handlers_block_test.go b/beacon-chain/rpc/eth/validator/handlers_block_test.go index 65d19abd9a..861af5bb5f 100644 --- a/beacon-chain/rpc/eth/validator/handlers_block_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_block_test.go @@ -12,6 +12,8 @@ import ( "github.com/golang/mock/gomock" "github.com/prysmaticlabs/prysm/v4/api" blockchainTesting "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards" + rewardtesting "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards/testing" "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" rpctesting "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared/testing" mockSync "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/initial-sync/testing" @@ -46,26 +48,31 @@ func TestProduceBlockV3(t *testing.T) { writer.Body = &bytes.Buffer{} server.ProduceBlockV3(writer, request) assert.Equal(t, http.StatusOK, writer.Code) - want := fmt.Sprintf(`{"version":"phase0","execution_payload_blinded":false,"execution_payload_value":"0","data":%s}`, string(jsonBytes)) + want := fmt.Sprintf(`{"version":"phase0","execution_payload_blinded":false,"execution_payload_value":"0","consensus_block_value":"","data":%s}`, string(jsonBytes)) body := strings.ReplaceAll(writer.Body.String(), "\n", "") require.Equal(t, want, body) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "", true) }) t.Run("Altair", func(t *testing.T) { var block *shared.SignedBeaconBlockAltair err := json.Unmarshal([]byte(rpctesting.AltairBlock), &block) + require.NoError(t, err) jsonBytes, err := json.Marshal(block.Message) require.NoError(t, err) v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl) v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), gomock.Any()).Return( func() (*eth.GenericBeaconBlock, error) { + return block.Message.ToGeneric() }()) + mockRewards := &rewards.BlockRewards{Total: "10"} server := &Server{ - V1Alpha1Server: v1alpha1Server, - SyncChecker: &mockSync.Sync{IsSyncing: false}, + V1Alpha1Server: v1alpha1Server, + SyncChecker: &mockSync.Sync{IsSyncing: false}, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -74,11 +81,12 @@ func TestProduceBlockV3(t *testing.T) { writer.Body = &bytes.Buffer{} server.ProduceBlockV3(writer, request) assert.Equal(t, http.StatusOK, writer.Code) - want := fmt.Sprintf(`{"version":"altair","execution_payload_blinded":false,"execution_payload_value":"0","data":%s}`, string(jsonBytes)) + want := fmt.Sprintf(`{"version":"altair","execution_payload_blinded":false,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes)) body := strings.ReplaceAll(writer.Body.String(), "\n", "") require.Equal(t, want, body) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("Bellatrix", func(t *testing.T) { var block *shared.SignedBeaconBlockBellatrix @@ -92,10 +100,12 @@ func TestProduceBlockV3(t *testing.T) { return block.Message.ToGeneric() }()) mockChainService := &blockchainTesting.ChainService{} + mockRewards := &rewards.BlockRewards{Total: "10"} server := &Server{ V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -104,11 +114,12 @@ func TestProduceBlockV3(t *testing.T) { writer.Body = &bytes.Buffer{} server.ProduceBlockV3(writer, request) assert.Equal(t, http.StatusOK, writer.Code) - want := fmt.Sprintf(`{"version":"bellatrix","execution_payload_blinded":false,"execution_payload_value":"0","data":%s}`, string(jsonBytes)) + want := fmt.Sprintf(`{"version":"bellatrix","execution_payload_blinded":false,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes)) body := strings.ReplaceAll(writer.Body.String(), "\n", "") require.Equal(t, want, body) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("BlindedBellatrix", func(t *testing.T) { var block *shared.SignedBlindedBeaconBlockBellatrix @@ -122,10 +133,12 @@ func TestProduceBlockV3(t *testing.T) { return block.Message.ToGeneric() }()) mockChainService := &blockchainTesting.ChainService{} + mockRewards := &rewards.BlockRewards{Total: "10"} server := &Server{ V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -134,11 +147,12 @@ func TestProduceBlockV3(t *testing.T) { writer.Body = &bytes.Buffer{} server.ProduceBlockV3(writer, request) assert.Equal(t, http.StatusOK, writer.Code) - want := fmt.Sprintf(`{"version":"bellatrix","execution_payload_blinded":true,"execution_payload_value":"0","data":%s}`, string(jsonBytes)) + want := fmt.Sprintf(`{"version":"bellatrix","execution_payload_blinded":true,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes)) body := strings.ReplaceAll(writer.Body.String(), "\n", "") require.Equal(t, want, body) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("Capella", func(t *testing.T) { var block *shared.SignedBeaconBlockCapella @@ -152,10 +166,12 @@ func TestProduceBlockV3(t *testing.T) { return block.Message.ToGeneric() }()) mockChainService := &blockchainTesting.ChainService{} + mockRewards := &rewards.BlockRewards{Total: "10"} server := &Server{ V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -164,11 +180,12 @@ func TestProduceBlockV3(t *testing.T) { writer.Body = &bytes.Buffer{} server.ProduceBlockV3(writer, request) assert.Equal(t, http.StatusOK, writer.Code) - want := fmt.Sprintf(`{"version":"capella","execution_payload_blinded":false,"execution_payload_value":"0","data":%s}`, string(jsonBytes)) + want := fmt.Sprintf(`{"version":"capella","execution_payload_blinded":false,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes)) body := strings.ReplaceAll(writer.Body.String(), "\n", "") require.Equal(t, want, body) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("Blinded Capella", func(t *testing.T) { var block *shared.SignedBlindedBeaconBlockCapella @@ -185,10 +202,12 @@ func TestProduceBlockV3(t *testing.T) { return g, err }()) mockChainService := &blockchainTesting.ChainService{} + mockRewards := &rewards.BlockRewards{Total: "10"} server := &Server{ V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -197,11 +216,12 @@ func TestProduceBlockV3(t *testing.T) { writer.Body = &bytes.Buffer{} server.ProduceBlockV3(writer, request) assert.Equal(t, http.StatusOK, writer.Code) - want := fmt.Sprintf(`{"version":"capella","execution_payload_blinded":true,"execution_payload_value":"2000","data":%s}`, string(jsonBytes)) + want := fmt.Sprintf(`{"version":"capella","execution_payload_blinded":true,"execution_payload_value":"2000","consensus_block_value":"10","data":%s}`, string(jsonBytes)) body := strings.ReplaceAll(writer.Body.String(), "\n", "") require.Equal(t, want, body) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "2000", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("Deneb", func(t *testing.T) { var block *shared.SignedBeaconBlockContentsDeneb @@ -215,10 +235,12 @@ func TestProduceBlockV3(t *testing.T) { return block.ToUnsigned().ToGeneric() }()) mockChainService := &blockchainTesting.ChainService{} + mockRewards := &rewards.BlockRewards{Total: "10"} server := &Server{ V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -227,11 +249,12 @@ func TestProduceBlockV3(t *testing.T) { writer.Body = &bytes.Buffer{} server.ProduceBlockV3(writer, request) assert.Equal(t, http.StatusOK, writer.Code) - want := fmt.Sprintf(`{"version":"deneb","execution_payload_blinded":false,"execution_payload_value":"0","data":%s}`, string(jsonBytes)) + want := fmt.Sprintf(`{"version":"deneb","execution_payload_blinded":false,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes)) body := strings.ReplaceAll(writer.Body.String(), "\n", "") require.Equal(t, want, body) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("Blinded Deneb", func(t *testing.T) { var block *shared.SignedBlindedBeaconBlockContentsDeneb @@ -245,10 +268,12 @@ func TestProduceBlockV3(t *testing.T) { return block.ToUnsigned().ToGeneric() }()) mockChainService := &blockchainTesting.ChainService{} + mockRewards := &rewards.BlockRewards{Total: "10"} server := &Server{ V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -257,11 +282,12 @@ func TestProduceBlockV3(t *testing.T) { writer.Body = &bytes.Buffer{} server.ProduceBlockV3(writer, request) assert.Equal(t, http.StatusOK, writer.Code) - want := fmt.Sprintf(`{"version":"deneb","execution_payload_blinded":true,"execution_payload_value":"0","data":%s}`, string(jsonBytes)) + want := fmt.Sprintf(`{"version":"deneb","execution_payload_blinded":true,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes)) body := strings.ReplaceAll(writer.Body.String(), "\n", "") require.Equal(t, want, body) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("invalid query parameter slot empty", func(t *testing.T) { v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl) @@ -321,6 +347,7 @@ func TestProduceBlockV3(t *testing.T) { func TestProduceBlockV3SSZ(t *testing.T) { ctrl := gomock.NewController(t) + mockRewards := &rewards.BlockRewards{Total: "10"} t.Run("Phase 0", func(t *testing.T) { var block *shared.SignedBeaconBlock err := json.Unmarshal([]byte(rpctesting.Phase0Block), &block) @@ -351,6 +378,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { require.Equal(t, string(ssz), writer.Body.String()) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "", true) }) t.Run("Altair", func(t *testing.T) { var block *shared.SignedBeaconBlockAltair @@ -361,9 +389,11 @@ func TestProduceBlockV3SSZ(t *testing.T) { func() (*eth.GenericBeaconBlock, error) { return block.Message.ToGeneric() }()) + server := &Server{ - V1Alpha1Server: v1alpha1Server, - SyncChecker: &mockSync.Sync{IsSyncing: false}, + V1Alpha1Server: v1alpha1Server, + SyncChecker: &mockSync.Sync{IsSyncing: false}, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -382,6 +412,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { require.Equal(t, string(ssz), writer.Body.String()) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("Bellatrix", func(t *testing.T) { var block *shared.SignedBeaconBlockBellatrix @@ -397,6 +428,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -415,6 +447,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { require.Equal(t, string(ssz), writer.Body.String()) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("BlindedBellatrix", func(t *testing.T) { var block *shared.SignedBlindedBeaconBlockBellatrix @@ -430,6 +463,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -448,6 +482,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { require.Equal(t, string(ssz), writer.Body.String()) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("Capella", func(t *testing.T) { var block *shared.SignedBeaconBlockCapella @@ -463,6 +498,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -481,6 +517,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { require.Equal(t, string(ssz), writer.Body.String()) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("Blinded Capella", func(t *testing.T) { var block *shared.SignedBlindedBeaconBlockCapella @@ -499,6 +536,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -517,6 +555,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { require.Equal(t, string(ssz), writer.Body.String()) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "2000", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("Deneb", func(t *testing.T) { var block *shared.SignedBeaconBlockContentsDeneb @@ -532,6 +571,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -550,6 +590,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { require.Equal(t, string(ssz), writer.Body.String()) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) t.Run("Blinded Deneb", func(t *testing.T) { var block *shared.SignedBlindedBeaconBlockContentsDeneb @@ -565,6 +606,7 @@ func TestProduceBlockV3SSZ(t *testing.T) { V1Alpha1Server: v1alpha1Server, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: mockChainService, + BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards}, } rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + "&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" @@ -583,5 +625,6 @@ func TestProduceBlockV3SSZ(t *testing.T) { require.Equal(t, string(ssz), writer.Body.String()) require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true) require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true) + require.Equal(t, writer.Header().Get(api.ConsensusBlockValueHeader) == "10", true) }) } diff --git a/beacon-chain/rpc/eth/validator/server.go b/beacon-chain/rpc/eth/validator/server.go index 780387104d..13afa5dadd 100644 --- a/beacon-chain/rpc/eth/validator/server.go +++ b/beacon-chain/rpc/eth/validator/server.go @@ -10,6 +10,7 @@ import ( "github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/synccommittee" "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p" "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/core" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards" "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup" "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync" eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" @@ -34,4 +35,5 @@ type Server struct { BlockBuilder builder.BlockBuilder OperationNotifier operation.Notifier CoreService *core.Service + BlockRewardFetcher rewards.BlockRewardsFetcher } diff --git a/beacon-chain/rpc/eth/validator/structs.go b/beacon-chain/rpc/eth/validator/structs.go index 3c7ae45319..fb310b3bb6 100644 --- a/beacon-chain/rpc/eth/validator/structs.go +++ b/beacon-chain/rpc/eth/validator/structs.go @@ -78,6 +78,7 @@ type ProduceBlockV3Response struct { Version string `json:"version"` ExecutionPayloadBlinded bool `json:"execution_payload_blinded"` ExecutionPayloadValue string `json:"execution_payload_value"` + ConsensusBlockValue string `json:"consensus_block_value"` Data json.RawMessage `json:"data"` // represents the block values based on the version } diff --git a/beacon-chain/rpc/service.go b/beacon-chain/rpc/service.go index 73f076c61d..a9efe06156 100644 --- a/beacon-chain/rpc/service.go +++ b/beacon-chain/rpc/service.go @@ -211,15 +211,15 @@ func (s *Service) Start() { BeaconDB: s.cfg.BeaconDB, ChainInfoFetcher: s.cfg.ChainInfoFetcher, } - + rewardFetcher := &rewards.BlockRewardService{Replayer: ch} rewardsServer := &rewards.Server{ Blocker: blocker, OptimisticModeFetcher: s.cfg.OptimisticModeFetcher, FinalizationFetcher: s.cfg.FinalizationFetcher, - ReplayerBuilder: ch, TimeFetcher: s.cfg.GenesisTimeFetcher, Stater: stater, HeadFetcher: s.cfg.HeadFetcher, + BlockRewardFetcher: rewardFetcher, } s.cfg.Router.HandleFunc("/eth/v1/beacon/rewards/blocks/{block_id}", rewardsServer.BlockRewards).Methods(http.MethodGet) @@ -304,6 +304,7 @@ func (s *Service) Start() { BlockBuilder: s.cfg.BlockBuilder, OperationNotifier: s.cfg.OperationNotifier, CoreService: coreService, + BlockRewardFetcher: rewardFetcher, } s.cfg.Router.HandleFunc("/eth/v1/validator/aggregate_attestation", validatorServerV1.GetAggregateAttestation).Methods(http.MethodGet) diff --git a/proto/prysm/v1alpha1/attestation/attestation_utils.go b/proto/prysm/v1alpha1/attestation/attestation_utils.go index fc82f58999..c90579579e 100644 --- a/proto/prysm/v1alpha1/attestation/attestation_utils.go +++ b/proto/prysm/v1alpha1/attestation/attestation_utils.go @@ -73,7 +73,8 @@ func AttestingIndices(bf bitfield.Bitfield, committee []primitives.ValidatorInde return nil, fmt.Errorf("bitfield length %d is not equal to committee length %d", bf.Len(), len(committee)) } indices := make([]uint64, 0, bf.Count()) - for _, idx := range bf.BitIndices() { + p := bf.BitIndices() + for _, idx := range p { if idx < len(committee) { indices = append(indices, uint64(committee[idx])) } @@ -104,6 +105,7 @@ func VerifyIndexedAttestationSig(ctx context.Context, indexedAtt *ethpb.IndexedA ctx, span := trace.StartSpan(ctx, "attestationutil.VerifyIndexedAttestationSig") defer span.End() indices := indexedAtt.AttestingIndices + messageHash, err := signing.ComputeSigningRoot(indexedAtt.Data, domain) if err != nil { return errors.Wrap(err, "could not get signing root of object")