From 498ee635e17bcecf585e975dc4036758f3b91f84 Mon Sep 17 00:00:00 2001 From: terence Date: Mon, 15 Jul 2024 08:23:09 -0700 Subject: [PATCH 01/17] Remove warning eip_7251.proto is unused (#14213) --- proto/prysm/v1alpha1/beacon_block.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/proto/prysm/v1alpha1/beacon_block.proto b/proto/prysm/v1alpha1/beacon_block.proto index dec981c59adb..7b93a4ae1678 100644 --- a/proto/prysm/v1alpha1/beacon_block.proto +++ b/proto/prysm/v1alpha1/beacon_block.proto @@ -19,7 +19,6 @@ import "proto/eth/ext/options.proto"; import "proto/prysm/v1alpha1/attestation.proto"; import "proto/prysm/v1alpha1/withdrawals.proto"; import "proto/engine/v1/execution_engine.proto"; -import "proto/prysm/v1alpha1/eip_7251.proto"; option csharp_namespace = "Ethereum.Eth.v1alpha1"; option go_package = "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1;eth"; From e5b25071f9b7b59ae9827c14a2529b72fe66f2e5 Mon Sep 17 00:00:00 2001 From: Potuz Date: Mon, 15 Jul 2024 12:28:56 -0300 Subject: [PATCH 02/17] Fix effective balance updates in Electra (#14215) --- beacon-chain/core/electra/effective_balance_updates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon-chain/core/electra/effective_balance_updates.go b/beacon-chain/core/electra/effective_balance_updates.go index 0458d3550e88..60627d378ad1 100644 --- a/beacon-chain/core/electra/effective_balance_updates.go +++ b/beacon-chain/core/electra/effective_balance_updates.go @@ -56,7 +56,7 @@ func ProcessEffectiveBalanceUpdates(state state.BeaconState) error { if balance+downwardThreshold < val.EffectiveBalance || val.EffectiveBalance+upwardThreshold < balance { effectiveBal := min(balance-balance%effBalanceInc, effectiveBalanceLimit) val.EffectiveBalance = effectiveBal - return false, val, nil + return true, val, nil } return false, val, nil } From 422438f515cddfb69c799c144feef9bfacad7ecc Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Mon, 15 Jul 2024 11:22:20 -0500 Subject: [PATCH 03/17] Electra: ProcessConsolidationRequests (#14219) --- beacon-chain/core/electra/transition_no_verify_sig.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/beacon-chain/core/electra/transition_no_verify_sig.go b/beacon-chain/core/electra/transition_no_verify_sig.go index 55ed84c2b3e6..79e34c4f9a0e 100644 --- a/beacon-chain/core/electra/transition_no_verify_sig.go +++ b/beacon-chain/core/electra/transition_no_verify_sig.go @@ -2,6 +2,7 @@ package electra import ( "context" + "fmt" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks" @@ -93,6 +94,8 @@ func ProcessOperations( return nil, errors.Wrap(err, "could not process deposit receipts") } - // TODO: Process consolidations from execution header. + if err := ProcessConsolidationRequests(ctx, st, exe.ConsolidationRequests()); err != nil { + return nil, fmt.Errorf("could not process consolidation requests: %w", err) + } return st, nil } From d6f86269a4e145fbec66aad153a59866417fee64 Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Mon, 15 Jul 2024 12:53:34 -0500 Subject: [PATCH 04/17] Electra: process_registry_updates handle exiting validators (#14221) * Handle case where validator is already exited * unit test for a slashed validator --- beacon-chain/core/electra/registry_updates.go | 3 ++- .../core/electra/registry_updates_test.go | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/beacon-chain/core/electra/registry_updates.go b/beacon-chain/core/electra/registry_updates.go index 89824fb84fbe..5a85f41cb1be 100644 --- a/beacon-chain/core/electra/registry_updates.go +++ b/beacon-chain/core/electra/registry_updates.go @@ -2,6 +2,7 @@ package electra import ( "context" + "errors" "fmt" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" @@ -84,7 +85,7 @@ func ProcessRegistryUpdates(ctx context.Context, st state.BeaconState) error { var err error // exitQueueEpoch and churn arguments are not used in electra. st, _, err = validators.InitiateValidatorExit(ctx, st, idx, 0 /*exitQueueEpoch*/, 0 /*churn*/) - if err != nil { + if err != nil && !errors.Is(err, validators.ErrValidatorAlreadyExited) { return fmt.Errorf("failed to initiate validator exit at index %d: %w", idx, err) } } diff --git a/beacon-chain/core/electra/registry_updates_test.go b/beacon-chain/core/electra/registry_updates_test.go index 44449d66ca89..cec76adc27a1 100644 --- a/beacon-chain/core/electra/registry_updates_test.go +++ b/beacon-chain/core/electra/registry_updates_test.go @@ -101,6 +101,32 @@ func TestProcessRegistryUpdates(t *testing.T) { } }, }, + { + name: "Validators are exiting", + state: func() state.BeaconState { + base := ð.BeaconStateElectra{ + Slot: 5 * params.BeaconConfig().SlotsPerEpoch, + FinalizedCheckpoint: ð.Checkpoint{Epoch: finalizedEpoch, Root: make([]byte, fieldparams.RootLength)}, + } + for i := uint64(0); i < 10; i++ { + base.Validators = append(base.Validators, ð.Validator{ + EffectiveBalance: params.BeaconConfig().EjectionBalance - 1, + ExitEpoch: 10, + WithdrawableEpoch: 20, + }) + } + st, err := state_native.InitializeFromProtoElectra(base) + require.NoError(t, err) + return st + }(), + check: func(t *testing.T, st state.BeaconState) { + // All validators should be exited + for i, val := range st.Validators() { + require.NotEqual(t, params.BeaconConfig().FarFutureEpoch, val.ExitEpoch, "failed to update exit epoch on validator %d", i) + require.NotEqual(t, params.BeaconConfig().FarFutureEpoch, val.WithdrawableEpoch, "failed to update withdrawable epoch on validator %d", i) + } + }, + }, } for _, tt := range tests { From 5a48e002dd3621ea94d148f0347a1fc064669d77 Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Mon, 15 Jul 2024 14:14:32 -0500 Subject: [PATCH 05/17] Unskip electra spectests (#14220) --- .../spectest/mainnet/electra/fork_transition/transition_test.go | 1 - testing/spectest/mainnet/electra/forkchoice/forkchoice_test.go | 1 - testing/spectest/mainnet/electra/random/random_test.go | 1 - testing/spectest/minimal/electra/finality/finality_test.go | 1 - .../spectest/minimal/electra/fork_transition/transition_test.go | 1 - testing/spectest/minimal/electra/forkchoice/forkchoice_test.go | 1 - testing/spectest/minimal/electra/random/random_test.go | 1 - testing/spectest/minimal/electra/sanity/blocks_test.go | 1 - testing/spectest/shared/electra/fork/upgrade_to_electra.go | 1 - testing/spectest/shared/electra/operations/block_header.go | 1 - testing/spectest/shared/electra/operations/execution_payload.go | 1 - testing/spectest/shared/electra/operations/helpers.go | 1 - testing/spectest/shared/electra/operations/withdrawals.go | 1 - 13 files changed, 13 deletions(-) diff --git a/testing/spectest/mainnet/electra/fork_transition/transition_test.go b/testing/spectest/mainnet/electra/fork_transition/transition_test.go index bfde60ba213e..0c0622b0aaed 100644 --- a/testing/spectest/mainnet/electra/fork_transition/transition_test.go +++ b/testing/spectest/mainnet/electra/fork_transition/transition_test.go @@ -7,6 +7,5 @@ import ( ) func TestMainnet_Electra_Transition(t *testing.T) { - t.Skip("TODO: Electra") fork.RunForkTransitionTest(t, "mainnet") } diff --git a/testing/spectest/mainnet/electra/forkchoice/forkchoice_test.go b/testing/spectest/mainnet/electra/forkchoice/forkchoice_test.go index e46b5557a8a3..50f73e858f6f 100644 --- a/testing/spectest/mainnet/electra/forkchoice/forkchoice_test.go +++ b/testing/spectest/mainnet/electra/forkchoice/forkchoice_test.go @@ -8,6 +8,5 @@ import ( ) func TestMainnet_Electra_Forkchoice(t *testing.T) { - t.Skip("TODO: Electra") forkchoice.Run(t, "mainnet", version.Electra) } diff --git a/testing/spectest/mainnet/electra/random/random_test.go b/testing/spectest/mainnet/electra/random/random_test.go index 02c8c474f083..c7404380a1d7 100644 --- a/testing/spectest/mainnet/electra/random/random_test.go +++ b/testing/spectest/mainnet/electra/random/random_test.go @@ -7,6 +7,5 @@ import ( ) func TestMainnet_Electra_Random(t *testing.T) { - t.Skip("TODO: Electra") sanity.RunBlockProcessingTest(t, "mainnet", "random/random/pyspec_tests") } diff --git a/testing/spectest/minimal/electra/finality/finality_test.go b/testing/spectest/minimal/electra/finality/finality_test.go index 05b498488e09..8e53a2f926f3 100644 --- a/testing/spectest/minimal/electra/finality/finality_test.go +++ b/testing/spectest/minimal/electra/finality/finality_test.go @@ -7,6 +7,5 @@ import ( ) func TestMinimal_Electra_Finality(t *testing.T) { - t.Skip("TODO: Electra") finality.RunFinalityTest(t, "minimal") } diff --git a/testing/spectest/minimal/electra/fork_transition/transition_test.go b/testing/spectest/minimal/electra/fork_transition/transition_test.go index 547dc4e66e8a..2cb5dfc39091 100644 --- a/testing/spectest/minimal/electra/fork_transition/transition_test.go +++ b/testing/spectest/minimal/electra/fork_transition/transition_test.go @@ -7,6 +7,5 @@ import ( ) func TestMinimal_Electra_Transition(t *testing.T) { - t.Skip("TODO: Electra") fork.RunForkTransitionTest(t, "minimal") } diff --git a/testing/spectest/minimal/electra/forkchoice/forkchoice_test.go b/testing/spectest/minimal/electra/forkchoice/forkchoice_test.go index aa04917b5ae6..19c0acd78ad4 100644 --- a/testing/spectest/minimal/electra/forkchoice/forkchoice_test.go +++ b/testing/spectest/minimal/electra/forkchoice/forkchoice_test.go @@ -8,6 +8,5 @@ import ( ) func TestMinimal_Electra_Forkchoice(t *testing.T) { - t.Skip("TODO: Electra") forkchoice.Run(t, "minimal", version.Electra) } diff --git a/testing/spectest/minimal/electra/random/random_test.go b/testing/spectest/minimal/electra/random/random_test.go index d454d894bcd0..8ac53e297f2d 100644 --- a/testing/spectest/minimal/electra/random/random_test.go +++ b/testing/spectest/minimal/electra/random/random_test.go @@ -7,6 +7,5 @@ import ( ) func TestMinimal_Electra_Random(t *testing.T) { - t.Skip("TODO: Electra") sanity.RunBlockProcessingTest(t, "minimal", "random/random/pyspec_tests") } diff --git a/testing/spectest/minimal/electra/sanity/blocks_test.go b/testing/spectest/minimal/electra/sanity/blocks_test.go index a6a7b8188764..5d2e27fcd24e 100644 --- a/testing/spectest/minimal/electra/sanity/blocks_test.go +++ b/testing/spectest/minimal/electra/sanity/blocks_test.go @@ -7,6 +7,5 @@ import ( ) func TestMinimal_Electra_Sanity_Blocks(t *testing.T) { - t.Skip("TODO: Electra") sanity.RunBlockProcessingTest(t, "minimal", "sanity/blocks/pyspec_tests") } diff --git a/testing/spectest/shared/electra/fork/upgrade_to_electra.go b/testing/spectest/shared/electra/fork/upgrade_to_electra.go index ad36b2dfe02a..a17b42765c69 100644 --- a/testing/spectest/shared/electra/fork/upgrade_to_electra.go +++ b/testing/spectest/shared/electra/fork/upgrade_to_electra.go @@ -20,7 +20,6 @@ import ( // RunUpgradeToElectra is a helper function that runs Electra's fork spec tests. // It unmarshals a pre- and post-state to check `UpgradeToElectra` comply with spec implementation. func RunUpgradeToElectra(t *testing.T, config string) { - t.Skip("Failing until spectests are updated to v1.5.0-alpha.3") require.NoError(t, utils.SetConfig(t, config)) testFolders, testsFolderPath := utils.TestFolders(t, config, "electra", "fork/fork/pyspec_tests") diff --git a/testing/spectest/shared/electra/operations/block_header.go b/testing/spectest/shared/electra/operations/block_header.go index 55abe1522d0f..f0d141e3dc21 100644 --- a/testing/spectest/shared/electra/operations/block_header.go +++ b/testing/spectest/shared/electra/operations/block_header.go @@ -22,7 +22,6 @@ import ( ) func RunBlockHeaderTest(t *testing.T, config string) { - t.Skip("Failing until spectests are updated to v1.5.0-alpha.3") require.NoError(t, utils.SetConfig(t, config)) testFolders, testsFolderPath := utils.TestFolders(t, config, "electra", "operations/block_header/pyspec_tests") for _, folder := range testFolders { diff --git a/testing/spectest/shared/electra/operations/execution_payload.go b/testing/spectest/shared/electra/operations/execution_payload.go index a2b5604f32d5..dc00fc5173eb 100644 --- a/testing/spectest/shared/electra/operations/execution_payload.go +++ b/testing/spectest/shared/electra/operations/execution_payload.go @@ -23,7 +23,6 @@ import ( ) func RunExecutionPayloadTest(t *testing.T, config string) { - t.Skip("Failing until spectests are updated to v1.5.0-alpha.3") require.NoError(t, utils.SetConfig(t, config)) testFolders, testsFolderPath := utils.TestFolders(t, config, "electra", "operations/execution_payload/pyspec_tests") if len(testFolders) == 0 { diff --git a/testing/spectest/shared/electra/operations/helpers.go b/testing/spectest/shared/electra/operations/helpers.go index ee26ab3a0621..fe42a2c83e6f 100644 --- a/testing/spectest/shared/electra/operations/helpers.go +++ b/testing/spectest/shared/electra/operations/helpers.go @@ -32,7 +32,6 @@ func RunBlockOperationTest( body *ethpb.BeaconBlockBodyElectra, operationFn blockOperation, ) { - t.Skip("Failing until spectests are updated to v1.5.0-alpha.3") preBeaconStateFile, err := util.BazelFileBytes(path.Join(folderPath, "pre.ssz_snappy")) require.NoError(t, err) preBeaconStateSSZ, err := snappy.Decode(nil /* dst */, preBeaconStateFile) diff --git a/testing/spectest/shared/electra/operations/withdrawals.go b/testing/spectest/shared/electra/operations/withdrawals.go index 778509b94472..5ef4fcd2ee09 100644 --- a/testing/spectest/shared/electra/operations/withdrawals.go +++ b/testing/spectest/shared/electra/operations/withdrawals.go @@ -18,7 +18,6 @@ import ( ) func RunWithdrawalsTest(t *testing.T, config string) { - t.Skip("Failing until spectests are updated to v1.5.0-alpha.3") require.NoError(t, utils.SetConfig(t, config)) testFolders, testsFolderPath := utils.TestFolders(t, config, "electra", "operations/withdrawals/pyspec_tests") for _, folder := range testFolders { From 05784a6c28d479dfaa209f2e27aba66a00369d4f Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Mon, 15 Jul 2024 15:47:31 -0500 Subject: [PATCH 06/17] Electra: Add minimal spectests for sync_committee_updates (#14224) --- .../electra/epoch_processing/BUILD.bazel | 1 + .../sync_committee_updates_test.go | 11 +++++++ .../electra/epoch_processing/BUILD.bazel | 1 + .../sync_committee_updates.go | 30 +++++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 testing/spectest/minimal/electra/epoch_processing/sync_committee_updates_test.go create mode 100644 testing/spectest/shared/electra/epoch_processing/sync_committee_updates.go diff --git a/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel b/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel index 66efa96a1037..2e85ef9c939c 100644 --- a/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel +++ b/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel @@ -16,6 +16,7 @@ go_test( "rewards_and_penalties_test.go", "slashings_reset_test.go", "slashings_test.go", + "sync_committee_updates_test.go", ], data = glob(["*.yaml"]) + [ "@consensus_spec_tests_minimal//:test_data", diff --git a/testing/spectest/minimal/electra/epoch_processing/sync_committee_updates_test.go b/testing/spectest/minimal/electra/epoch_processing/sync_committee_updates_test.go new file mode 100644 index 000000000000..91bfb6a8c9c9 --- /dev/null +++ b/testing/spectest/minimal/electra/epoch_processing/sync_committee_updates_test.go @@ -0,0 +1,11 @@ +package epoch_processing + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/v5/testing/spectest/shared/electra/epoch_processing" +) + +func TestMinimal_Electra_EpochProcessing_SyncCommitteeUpdates(t *testing.T) { + epoch_processing.RunSyncCommitteeUpdatesTests(t, "minimal") +} diff --git a/testing/spectest/shared/electra/epoch_processing/BUILD.bazel b/testing/spectest/shared/electra/epoch_processing/BUILD.bazel index f9b321866e33..b82d6a6ba643 100644 --- a/testing/spectest/shared/electra/epoch_processing/BUILD.bazel +++ b/testing/spectest/shared/electra/epoch_processing/BUILD.bazel @@ -18,6 +18,7 @@ go_library( "rewards_and_penalties.go", "slashings.go", "slashings_reset.go", + "sync_committee_updates.go", ], importpath = "github.com/prysmaticlabs/prysm/v5/testing/spectest/shared/electra/epoch_processing", visibility = ["//visibility:public"], diff --git a/testing/spectest/shared/electra/epoch_processing/sync_committee_updates.go b/testing/spectest/shared/electra/epoch_processing/sync_committee_updates.go new file mode 100644 index 000000000000..7c4f18143266 --- /dev/null +++ b/testing/spectest/shared/electra/epoch_processing/sync_committee_updates.go @@ -0,0 +1,30 @@ +package epoch_processing + +import ( + "context" + "path" + "testing" + + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/spectest/utils" +) + +func RunSyncCommitteeUpdatesTests(t *testing.T, config string) { + require.NoError(t, utils.SetConfig(t, config)) + + testFolders, testsFolderPath := utils.TestFolders(t, config, "electra", "epoch_processing/sync_committee_updates/pyspec_tests") + for _, folder := range testFolders { + t.Run(folder.Name(), func(t *testing.T) { + helpers.ClearCache() + folderPath := path.Join(testsFolderPath, folder.Name()) + RunEpochOperationTest(t, folderPath, processSyncCommitteeUpdates) + }) + } +} + +func processSyncCommitteeUpdates(t *testing.T, st state.BeaconState) (state.BeaconState, error) { + return electra.ProcessSyncCommitteeUpdates(context.Background(), st) +} From fadff022a07f69ae970fbb9aaca04aa355360365 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:38:15 -0500 Subject: [PATCH 07/17] Payload attributes misleading value fix (#14209) * draft solution to corrected suggested fee recipient * removing electra from this PR * gaz * adding test for endpoint * gaz --- beacon-chain/rpc/endpoints.go | 9 ++-- beacon-chain/rpc/eth/events/BUILD.bazel | 3 ++ beacon-chain/rpc/eth/events/events.go | 12 +++-- beacon-chain/rpc/eth/events/events_test.go | 52 ++++++++++++++++++---- beacon-chain/rpc/eth/events/server.go | 10 +++-- 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index 8c5f6a9fae0b..f603f75e45d4 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -904,10 +904,11 @@ func (s *Service) debugEndpoints(stater lookup.Stater) []endpoint { func (s *Service) eventsEndpoints() []endpoint { server := &events.Server{ - StateNotifier: s.cfg.StateNotifier, - OperationNotifier: s.cfg.OperationNotifier, - HeadFetcher: s.cfg.HeadFetcher, - ChainInfoFetcher: s.cfg.ChainInfoFetcher, + StateNotifier: s.cfg.StateNotifier, + OperationNotifier: s.cfg.OperationNotifier, + HeadFetcher: s.cfg.HeadFetcher, + ChainInfoFetcher: s.cfg.ChainInfoFetcher, + TrackedValidatorsCache: s.cfg.TrackedValidatorsCache, } const namespace = "events" diff --git a/beacon-chain/rpc/eth/events/BUILD.bazel b/beacon-chain/rpc/eth/events/BUILD.bazel index 419848a71cb2..37d627b50820 100644 --- a/beacon-chain/rpc/eth/events/BUILD.bazel +++ b/beacon-chain/rpc/eth/events/BUILD.bazel @@ -12,6 +12,7 @@ go_library( "//api:go_default_library", "//api/server/structs:go_default_library", "//beacon-chain/blockchain:go_default_library", + "//beacon-chain/cache:go_default_library", "//beacon-chain/core/feed:go_default_library", "//beacon-chain/core/feed/operation:go_default_library", "//beacon-chain/core/feed/state:go_default_library", @@ -37,6 +38,7 @@ go_test( embed = [":go_default_library"], deps = [ "//beacon-chain/blockchain/testing:go_default_library", + "//beacon-chain/cache:go_default_library", "//beacon-chain/core/feed:go_default_library", "//beacon-chain/core/feed/operation:go_default_library", "//beacon-chain/core/feed/state:go_default_library", @@ -50,5 +52,6 @@ go_test( "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", + "@com_github_ethereum_go_ethereum//common:go_default_library", ], ) diff --git a/beacon-chain/rpc/eth/events/events.go b/beacon-chain/rpc/eth/events/events.go index 205f29a46901..c4782f8c14b3 100644 --- a/beacon-chain/rpc/eth/events/events.go +++ b/beacon-chain/rpc/eth/events/events.go @@ -439,14 +439,18 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite if err != nil { return write(w, flusher, "Could not get head state proposer index: "+err.Error()) } - + feeRecipient := params.BeaconConfig().DefaultFeeRecipient.Bytes() + tValidator, exists := s.TrackedValidatorsCache.Validator(proposerIndex) + if exists { + feeRecipient = tValidator.FeeRecipient[:] + } var attributes interface{} switch headState.Version() { case version.Bellatrix: attributes = &structs.PayloadAttributesV1{ Timestamp: fmt.Sprintf("%d", t.Unix()), PrevRandao: hexutil.Encode(prevRando), - SuggestedFeeRecipient: hexutil.Encode(headPayload.FeeRecipient()), + SuggestedFeeRecipient: hexutil.Encode(feeRecipient), } case version.Capella: withdrawals, _, err := headState.ExpectedWithdrawals() @@ -456,7 +460,7 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite attributes = &structs.PayloadAttributesV2{ Timestamp: fmt.Sprintf("%d", t.Unix()), PrevRandao: hexutil.Encode(prevRando), - SuggestedFeeRecipient: hexutil.Encode(headPayload.FeeRecipient()), + SuggestedFeeRecipient: hexutil.Encode(feeRecipient), Withdrawals: structs.WithdrawalsFromConsensus(withdrawals), } case version.Deneb, version.Electra: @@ -471,7 +475,7 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite attributes = &structs.PayloadAttributesV3{ Timestamp: fmt.Sprintf("%d", t.Unix()), PrevRandao: hexutil.Encode(prevRando), - SuggestedFeeRecipient: hexutil.Encode(headPayload.FeeRecipient()), + SuggestedFeeRecipient: hexutil.Encode(feeRecipient), Withdrawals: structs.WithdrawalsFromConsensus(withdrawals), ParentBeaconBlockRoot: hexutil.Encode(parentRoot[:]), } diff --git a/beacon-chain/rpc/eth/events/events_test.go b/beacon-chain/rpc/eth/events/events_test.go index 9f9f6590fc09..d44ca7e04938 100644 --- a/beacon-chain/rpc/eth/events/events_test.go +++ b/beacon-chain/rpc/eth/events/events_test.go @@ -10,7 +10,9 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" mockChain "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/cache" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/operation" statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state" @@ -280,10 +282,11 @@ func TestStreamEvents_OperationsEvents(t *testing.T) { }) t.Run("payload attributes", func(t *testing.T) { type testCase struct { - name string - getState func() state.BeaconState - getBlock func() interfaces.SignedBeaconBlock - expected string + name string + getState func() state.BeaconState + getBlock func() interfaces.SignedBeaconBlock + expected string + SetTrackedValidatorsCache func(*cache.TrackedValidatorsCache) } testCases := []testCase{ { @@ -328,6 +331,27 @@ func TestStreamEvents_OperationsEvents(t *testing.T) { }, expected: payloadAttributesDenebResult, }, + { + name: "electra", + getState: func() state.BeaconState { + st, err := util.NewBeaconStateElectra() + require.NoError(t, err) + return st + }, + getBlock: func() interfaces.SignedBeaconBlock { + b, err := blocks.NewSignedBeaconBlock(util.HydrateSignedBeaconBlockElectra(ð.SignedBeaconBlockElectra{})) + require.NoError(t, err) + return b + }, + expected: payloadAttributesElectraResultWithTVC, + SetTrackedValidatorsCache: func(c *cache.TrackedValidatorsCache) { + c.Set(cache.TrackedValidator{ + Active: true, + Index: 0, + FeeRecipient: primitives.ExecutionAddress(common.HexToAddress("0xd2DBd02e4efe087d7d195de828b9Dd25f19A89C9").Bytes()), + }) + }, + }, } for _, tc := range testCases { st := tc.getState() @@ -343,11 +367,16 @@ func TestStreamEvents_OperationsEvents(t *testing.T) { Block: b, Slot: ¤tSlot, } + s := &Server{ - StateNotifier: &mockChain.MockStateNotifier{}, - OperationNotifier: &mockChain.MockOperationNotifier{}, - HeadFetcher: mockChainService, - ChainInfoFetcher: mockChainService, + StateNotifier: &mockChain.MockStateNotifier{}, + OperationNotifier: &mockChain.MockOperationNotifier{}, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainService, + TrackedValidatorsCache: cache.NewTrackedValidatorsCache(), + } + if tc.SetTrackedValidatorsCache != nil { + tc.SetTrackedValidatorsCache(s.TrackedValidatorsCache) } request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com/eth/v1/events?topics=%s", PayloadAttributesTopic), nil) @@ -439,3 +468,10 @@ event: payload_attributes data: {"version":"deneb","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0x0000000000000000000000000000000000000000","withdrawals":[],"parent_beacon_block_root":"0xbef96cb938fd48b2403d3e662664325abb0102ed12737cbb80d717520e50cf4a"}}} ` + +const payloadAttributesElectraResultWithTVC = `: + +event: payload_attributes +data: {"version":"electra","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0xd2dbd02e4efe087d7d195de828b9dd25f19a89c9","withdrawals":[],"parent_beacon_block_root":"0x66d641f7eae038f2dd28081b09d2ba279462cc47655c7b7e1fd1159a50c8eb32"}}} + +` diff --git a/beacon-chain/rpc/eth/events/server.go b/beacon-chain/rpc/eth/events/server.go index 653bab5137e4..58cd671e9c4b 100644 --- a/beacon-chain/rpc/eth/events/server.go +++ b/beacon-chain/rpc/eth/events/server.go @@ -5,6 +5,7 @@ package events import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/cache" opfeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/operation" statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state" ) @@ -12,8 +13,9 @@ import ( // Server defines a server implementation of the gRPC events service, // providing RPC endpoints to subscribe to events from the beacon node. type Server struct { - StateNotifier statefeed.Notifier - OperationNotifier opfeed.Notifier - HeadFetcher blockchain.HeadFetcher - ChainInfoFetcher blockchain.ChainInfoFetcher + StateNotifier statefeed.Notifier + OperationNotifier opfeed.Notifier + HeadFetcher blockchain.HeadFetcher + ChainInfoFetcher blockchain.ChainInfoFetcher + TrackedValidatorsCache *cache.TrackedValidatorsCache } From 637cbc88e80af24d6d7df449cfc657475ba7929b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kapka?= Date: Tue, 16 Jul 2024 21:33:40 +0200 Subject: [PATCH 08/17] Add missing pieces of Electra (#14227) * Add missing pieces of Electra * update mock --- beacon-chain/blockchain/execution_engine.go | 2 +- .../blockchain/execution_engine_test.go | 88 ++++++++++++------- beacon-chain/db/kv/encoding.go | 4 +- beacon-chain/rpc/core/validator.go | 11 ++- .../prysm/v1alpha1/validator/attester_test.go | 68 ++++++++++++++ .../db/filesystem/attester_protection.go | 14 +-- validator/db/iface/interface.go | 4 +- validator/db/kv/attester_protection.go | 40 ++++----- validator/helpers/metadata_test.go | 4 +- 9 files changed, 165 insertions(+), 70 deletions(-) diff --git a/beacon-chain/blockchain/execution_engine.go b/beacon-chain/blockchain/execution_engine.go index f6e287f4c525..db911b31ab4e 100644 --- a/beacon-chain/blockchain/execution_engine.go +++ b/beacon-chain/blockchain/execution_engine.go @@ -323,7 +323,7 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, var attr payloadattribute.Attributer switch st.Version() { - case version.Deneb: + case version.Deneb, version.Electra: withdrawals, _, err := st.ExpectedWithdrawals() if err != nil { log.WithError(err).Error("Could not get expected withdrawals to get payload attribute") diff --git a/beacon-chain/blockchain/execution_engine_test.go b/beacon-chain/blockchain/execution_engine_test.go index aee047ca0f1d..be5d700d52ea 100644 --- a/beacon-chain/blockchain/execution_engine_test.go +++ b/beacon-chain/blockchain/execution_engine_test.go @@ -855,41 +855,63 @@ func Test_GetPayloadAttributeV2(t *testing.T) { require.Equal(t, 0, len(a)) } -func Test_GetPayloadAttributeDeneb(t *testing.T) { - service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache())) - ctx := tr.ctx - - st, _ := util.DeterministicGenesisStateDeneb(t, 1) - attr := service.getPayloadAttribute(ctx, st, 0, []byte{}) - require.Equal(t, true, attr.IsEmpty()) - - // Cache hit, advance state, no fee recipient - slot := primitives.Slot(1) - service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, Index: 0}) - service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{}) - attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:]) - require.Equal(t, false, attr.IsEmpty()) - require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String()) - a, err := attr.Withdrawals() - require.NoError(t, err) - require.Equal(t, 0, len(a)) - - // Cache hit, advance state, has fee recipient - suggestedAddr := common.HexToAddress("123") - service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(suggestedAddr), Index: 0}) - service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{}) - attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:]) - require.Equal(t, false, attr.IsEmpty()) - require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient())) - a, err = attr.Withdrawals() - require.NoError(t, err) - require.Equal(t, 0, len(a)) +func Test_GetPayloadAttributeV3(t *testing.T) { + var testCases = []struct { + name string + st bstate.BeaconState + }{ + { + name: "deneb", + st: func() bstate.BeaconState { + st, _ := util.DeterministicGenesisStateDeneb(t, 1) + return st + }(), + }, + { + name: "electra", + st: func() bstate.BeaconState { + st, _ := util.DeterministicGenesisStateElectra(t, 1) + return st + }(), + }, + } - attrV3, err := attr.PbV3() - require.NoError(t, err) - hr := service.headRoot() - require.Equal(t, hr, [32]byte(attrV3.ParentBeaconBlockRoot)) + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache())) + ctx := tr.ctx + + attr := service.getPayloadAttribute(ctx, test.st, 0, []byte{}) + require.Equal(t, true, attr.IsEmpty()) + + // Cache hit, advance state, no fee recipient + slot := primitives.Slot(1) + service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, Index: 0}) + service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{}) + attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:]) + require.Equal(t, false, attr.IsEmpty()) + require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String()) + a, err := attr.Withdrawals() + require.NoError(t, err) + require.Equal(t, 0, len(a)) + + // Cache hit, advance state, has fee recipient + suggestedAddr := common.HexToAddress("123") + service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(suggestedAddr), Index: 0}) + service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{}) + attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:]) + require.Equal(t, false, attr.IsEmpty()) + require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient())) + a, err = attr.Withdrawals() + require.NoError(t, err) + require.Equal(t, 0, len(a)) + attrV3, err := attr.PbV3() + require.NoError(t, err) + hr := service.headRoot() + require.Equal(t, hr, [32]byte(attrV3.ParentBeaconBlockRoot)) + }) + } } func Test_UpdateLastValidatedCheckpoint(t *testing.T) { diff --git a/beacon-chain/db/kv/encoding.go b/beacon-chain/db/kv/encoding.go index 1a2e01fc2b77..8af8efa035d3 100644 --- a/beacon-chain/db/kv/encoding.go +++ b/beacon-chain/db/kv/encoding.go @@ -68,11 +68,11 @@ func isSSZStorageFormat(obj interface{}) bool { return true case *ethpb.BeaconBlock: return true - case *ethpb.Attestation: + case *ethpb.Attestation, *ethpb.AttestationElectra: return true case *ethpb.Deposit: return true - case *ethpb.AttesterSlashing: + case *ethpb.AttesterSlashing, *ethpb.AttesterSlashingElectra: return true case *ethpb.ProposerSlashing: return true diff --git a/beacon-chain/rpc/core/validator.go b/beacon-chain/rpc/core/validator.go index 4ab23e317594..44c534f83776 100644 --- a/beacon-chain/rpc/core/validator.go +++ b/beacon-chain/rpc/core/validator.go @@ -368,13 +368,18 @@ func (s *Service) GetAttestationData( return nil, &RpcError{Reason: BadRequest, Err: errors.Errorf("invalid request: %v", err)} } + committeeIndex := primitives.CommitteeIndex(0) + if slots.ToEpoch(req.Slot) < params.BeaconConfig().ElectraForkEpoch { + committeeIndex = req.CommitteeIndex + } + s.AttestationCache.RLock() res := s.AttestationCache.Get() if res != nil && res.Slot == req.Slot { s.AttestationCache.RUnlock() return ðpb.AttestationData{ Slot: res.Slot, - CommitteeIndex: req.CommitteeIndex, + CommitteeIndex: committeeIndex, BeaconBlockRoot: res.HeadRoot, Source: ðpb.Checkpoint{ Epoch: res.Source.Epoch, @@ -398,7 +403,7 @@ func (s *Service) GetAttestationData( if res != nil && res.Slot == req.Slot { return ðpb.AttestationData{ Slot: res.Slot, - CommitteeIndex: req.CommitteeIndex, + CommitteeIndex: committeeIndex, BeaconBlockRoot: res.HeadRoot, Source: ðpb.Checkpoint{ Epoch: res.Source.Epoch, @@ -458,7 +463,7 @@ func (s *Service) GetAttestationData( return ðpb.AttestationData{ Slot: req.Slot, - CommitteeIndex: req.CommitteeIndex, + CommitteeIndex: committeeIndex, BeaconBlockRoot: headRoot, Source: ðpb.Checkpoint{ Epoch: justifiedCheckpoint.Epoch, diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/attester_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/attester_test.go index 7b6e87c53451..6fe5e884d4f9 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/attester_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/attester_test.go @@ -475,6 +475,74 @@ func TestGetAttestationData_SucceedsInFirstEpoch(t *testing.T) { } } +func TestGetAttestationData_CommitteeIndexIsZeroPostElectra(t *testing.T) { + params.SetupTestConfigCleanup(t) + cfg := params.BeaconConfig().Copy() + cfg.ElectraForkEpoch = 0 + params.OverrideBeaconConfig(cfg) + + block := util.NewBeaconBlock() + block.Block.Slot = 3*params.BeaconConfig().SlotsPerEpoch + 1 + targetBlock := util.NewBeaconBlock() + targetBlock.Block.Slot = params.BeaconConfig().SlotsPerEpoch + targetRoot, err := targetBlock.Block.HashTreeRoot() + require.NoError(t, err) + + justifiedBlock := util.NewBeaconBlock() + justifiedBlock.Block.Slot = 2 * params.BeaconConfig().SlotsPerEpoch + blockRoot, err := block.Block.HashTreeRoot() + require.NoError(t, err) + justifiedRoot, err := justifiedBlock.Block.HashTreeRoot() + require.NoError(t, err) + slot := 3*params.BeaconConfig().SlotsPerEpoch + 1 + beaconState, err := util.NewBeaconState() + require.NoError(t, err) + require.NoError(t, beaconState.SetSlot(slot)) + justifiedCheckpoint := ðpb.Checkpoint{ + Epoch: 2, + Root: justifiedRoot[:], + } + require.NoError(t, beaconState.SetCurrentJustifiedCheckpoint(justifiedCheckpoint)) + offset := int64(slot.Mul(params.BeaconConfig().SecondsPerSlot)) + attesterServer := &Server{ + SyncChecker: &mockSync.Sync{IsSyncing: false}, + OptimisticModeFetcher: &mock.ChainService{Optimistic: false}, + TimeFetcher: &mock.ChainService{Genesis: time.Now().Add(time.Duration(-1*offset) * time.Second)}, + CoreService: &core.Service{ + HeadFetcher: &mock.ChainService{TargetRoot: targetRoot, Root: blockRoot[:], State: beaconState}, + GenesisTimeFetcher: &mock.ChainService{ + Genesis: time.Now().Add(time.Duration(-1*offset) * time.Second), + }, + FinalizedFetcher: &mock.ChainService{CurrentJustifiedCheckPoint: justifiedCheckpoint}, + AttestationCache: cache.NewAttestationCache(), + OptimisticModeFetcher: &mock.ChainService{Optimistic: false}, + }, + } + + req := ðpb.AttestationDataRequest{ + CommitteeIndex: 123, // set non-zero committee index + Slot: 3*params.BeaconConfig().SlotsPerEpoch + 1, + } + res, err := attesterServer.GetAttestationData(context.Background(), req) + require.NoError(t, err) + + expected := ðpb.AttestationData{ + Slot: 3*params.BeaconConfig().SlotsPerEpoch + 1, + CommitteeIndex: 0, + BeaconBlockRoot: blockRoot[:], + Source: ðpb.Checkpoint{ + Epoch: 2, + Root: justifiedRoot[:], + }, + Target: ðpb.Checkpoint{ + Epoch: 3, + Root: targetRoot[:], + }, + } + + assert.DeepEqual(t, expected, res) +} + func TestServer_SubscribeCommitteeSubnets_NoSlots(t *testing.T) { attesterServer := &Server{ HeadFetcher: &mock.ChainService{}, diff --git a/validator/db/filesystem/attester_protection.go b/validator/db/filesystem/attester_protection.go index d6e9e1cea47c..996538e0137d 100644 --- a/validator/db/filesystem/attester_protection.go +++ b/validator/db/filesystem/attester_protection.go @@ -99,7 +99,7 @@ func (s *Store) AttestedPublicKeys(_ context.Context) ([][fieldparams.BLSPubkeyL // If it is not, it updates the database. func (s *Store) SlashableAttestationCheck( ctx context.Context, - indexedAtt *ethpb.IndexedAttestation, + indexedAtt ethpb.IndexedAtt, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot32 [32]byte, _ bool, @@ -128,10 +128,10 @@ func (s *Store) SaveAttestationForPubKey( _ context.Context, pubkey [fieldparams.BLSPubkeyLength]byte, _ [32]byte, - att *ethpb.IndexedAttestation, + att ethpb.IndexedAtt, ) error { // If there is no attestation, return on error. - if att == nil || att.Data == nil || att.Data.Source == nil || att.Data.Target == nil { + if att == nil || att.GetData() == nil || att.GetData().Source == nil || att.GetData().Target == nil { return errors.New("incoming attestation does not contain source and/or target epoch") } @@ -141,8 +141,8 @@ func (s *Store) SaveAttestationForPubKey( return errors.Wrap(err, "could not get validator slashing protection") } - incomingSourceEpochUInt64 := uint64(att.Data.Source.Epoch) - incomingTargetEpochUInt64 := uint64(att.Data.Target.Epoch) + incomingSourceEpochUInt64 := uint64(att.GetData().Source.Epoch) + incomingTargetEpochUInt64 := uint64(att.GetData().Target.Epoch) if validatorSlashingProtection == nil { // If there is no validator slashing protection, create one. @@ -167,7 +167,7 @@ func (s *Store) SaveAttestationForPubKey( if incomingSourceEpochUInt64 < savedSourceEpoch { return errors.Errorf( "could not sign attestation with source lower than recorded source epoch, %d < %d", - att.Data.Source.Epoch, + att.GetData().Source.Epoch, validatorSlashingProtection.LastSignedAttestationSourceEpoch, ) } @@ -177,7 +177,7 @@ func (s *Store) SaveAttestationForPubKey( if savedTargetEpoch != nil && incomingTargetEpochUInt64 <= *savedTargetEpoch { return errors.Errorf( "could not sign attestation with target lower than or equal to recorded target epoch, %d <= %d", - att.Data.Target.Epoch, + att.GetData().Target.Epoch, *savedTargetEpoch, ) } diff --git a/validator/db/iface/interface.go b/validator/db/iface/interface.go index 25391e645d6f..7c68eb166a8d 100644 --- a/validator/db/iface/interface.go +++ b/validator/db/iface/interface.go @@ -55,13 +55,13 @@ type ValidatorDB interface { LowestSignedSourceEpoch(ctx context.Context, publicKey [fieldparams.BLSPubkeyLength]byte) (primitives.Epoch, bool, error) AttestedPublicKeys(ctx context.Context) ([][fieldparams.BLSPubkeyLength]byte, error) SlashableAttestationCheck( - ctx context.Context, indexedAtt *ethpb.IndexedAttestation, pubKey [fieldparams.BLSPubkeyLength]byte, + ctx context.Context, indexedAtt ethpb.IndexedAtt, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot32 [32]byte, emitAccountMetrics bool, validatorAttestFailVec *prometheus.CounterVec, ) error SaveAttestationForPubKey( - ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot [fieldparams.RootLength]byte, att *ethpb.IndexedAttestation, + ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot [fieldparams.RootLength]byte, att ethpb.IndexedAtt, ) error SaveAttestationsForPubKey( ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoots [][]byte, atts []*ethpb.IndexedAttestation, diff --git a/validator/db/kv/attester_protection.go b/validator/db/kv/attester_protection.go index 4e29af3fbf72..5187b2f30f74 100644 --- a/validator/db/kv/attester_protection.go +++ b/validator/db/kv/attester_protection.go @@ -139,7 +139,7 @@ func (s *Store) AttestationHistoryForPubKey(ctx context.Context, pubKey [fieldpa // If it is not, it updates the database. func (s *Store) SlashableAttestationCheck( ctx context.Context, - indexedAtt *ethpb.IndexedAttestation, + indexedAtt ethpb.IndexedAtt, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot32 [32]byte, emitAccountMetrics bool, @@ -156,14 +156,14 @@ func (s *Store) SlashableAttestationCheck( if err != nil { return err } - if exists && indexedAtt.Data.Source.Epoch < lowestSourceEpoch { + if exists && indexedAtt.GetData().Source.Epoch < lowestSourceEpoch { return fmt.Errorf( "could not sign attestation lower than lowest source epoch in db, %d < %d", - indexedAtt.Data.Source.Epoch, + indexedAtt.GetData().Source.Epoch, lowestSourceEpoch, ) } - existingSigningRoot, err := s.SigningRootAtTargetEpoch(ctx, pubKey, indexedAtt.Data.Target.Epoch) + existingSigningRoot, err := s.SigningRootAtTargetEpoch(ctx, pubKey, indexedAtt.GetData().Target.Epoch) if err != nil { return err } @@ -176,10 +176,10 @@ func (s *Store) SlashableAttestationCheck( if err != nil { return err } - if signingRootsDiffer && exists && indexedAtt.Data.Target.Epoch <= lowestTargetEpoch { + if signingRootsDiffer && exists && indexedAtt.GetData().Target.Epoch <= lowestTargetEpoch { return fmt.Errorf( "could not sign attestation lower than or equal to lowest target epoch in db if signing roots differ, %d <= %d", - indexedAtt.Data.Target.Epoch, + indexedAtt.GetData().Target.Epoch, lowestTargetEpoch, ) } @@ -210,7 +210,7 @@ func (s *Store) SlashableAttestationCheck( // CheckSlashableAttestation verifies an incoming attestation is // not a double vote for a validator public key nor a surround vote. func (s *Store) CheckSlashableAttestation( - ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot []byte, att *ethpb.IndexedAttestation, + ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot []byte, att ethpb.IndexedAtt, ) (SlashingKind, error) { ctx, span := trace.StartSpan(ctx, "Validator.CheckSlashableAttestation") defer span.End() @@ -228,14 +228,14 @@ func (s *Store) CheckSlashableAttestation( // First we check for double votes. signingRootsBucket := pkBucket.Bucket(attestationSigningRootsBucket) if signingRootsBucket != nil { - targetEpochBytes := bytesutil.EpochToBytesBigEndian(att.Data.Target.Epoch) + targetEpochBytes := bytesutil.EpochToBytesBigEndian(att.GetData().Target.Epoch) existingSigningRoot := signingRootsBucket.Get(targetEpochBytes) // If a signing root exists in the database, and if this database signing root is empty => We consider the new attestation as a double vote. // If a signing root exists in the database, and if this database signing differs from the signing root of the new attestation => We consider the new attestation as a double vote. if existingSigningRoot != nil && (len(existingSigningRoot) == 0 || slashings.SigningRootsDiffer(existingSigningRoot, signingRoot)) { slashKind = DoubleVote - return fmt.Errorf(doubleVoteMessage, att.Data.Target.Epoch, existingSigningRoot) + return fmt.Errorf(doubleVoteMessage, att.GetData().Target.Epoch, existingSigningRoot) } } @@ -269,12 +269,12 @@ func (s *Store) CheckSlashableAttestation( // Iterate from the back of the bucket since we are looking for target_epoch > att.target_epoch func (*Store) checkSurroundedVote( - targetEpochsBucket *bolt.Bucket, att *ethpb.IndexedAttestation, + targetEpochsBucket *bolt.Bucket, att ethpb.IndexedAtt, ) (SlashingKind, error) { c := targetEpochsBucket.Cursor() for k, v := c.Last(); k != nil; k, v = c.Prev() { existingTargetEpoch := bytesutil.BytesToEpochBigEndian(k) - if existingTargetEpoch <= att.Data.Target.Epoch { + if existingTargetEpoch <= att.GetData().Target.Epoch { break } @@ -296,8 +296,8 @@ func (*Store) checkSurroundedVote( if surrounded { return SurroundedVote, fmt.Errorf( surroundedVoteMessage, - att.Data.Source.Epoch, - att.Data.Target.Epoch, + att.GetData().Source.Epoch, + att.GetData().Target.Epoch, existingSourceEpoch, existingTargetEpoch, ) @@ -309,12 +309,12 @@ func (*Store) checkSurroundedVote( // Iterate from the back of the bucket since we are looking for source_epoch > att.source_epoch func (*Store) checkSurroundingVote( - sourceEpochsBucket *bolt.Bucket, att *ethpb.IndexedAttestation, + sourceEpochsBucket *bolt.Bucket, att ethpb.IndexedAtt, ) (SlashingKind, error) { c := sourceEpochsBucket.Cursor() for k, v := c.Last(); k != nil; k, v = c.Prev() { existingSourceEpoch := bytesutil.BytesToEpochBigEndian(k) - if existingSourceEpoch <= att.Data.Source.Epoch { + if existingSourceEpoch <= att.GetData().Source.Epoch { break } @@ -336,8 +336,8 @@ func (*Store) checkSurroundingVote( if surrounding { return SurroundingVote, fmt.Errorf( surroundingVoteMessage, - att.Data.Source.Epoch, - att.Data.Target.Epoch, + att.GetData().Source.Epoch, + att.GetData().Target.Epoch, existingSourceEpoch, existingTargetEpoch, ) @@ -375,7 +375,7 @@ func (s *Store) SaveAttestationsForPubKey( // SaveAttestationForPubKey saves an attestation for a validator public // key for local validator slashing protection. func (s *Store) SaveAttestationForPubKey( - ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot [fieldparams.RootLength]byte, att *ethpb.IndexedAttestation, + ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot [fieldparams.RootLength]byte, att ethpb.IndexedAtt, ) error { ctx, span := trace.StartSpan(ctx, "Validator.SaveAttestationForPubKey") defer span.End() @@ -383,8 +383,8 @@ func (s *Store) SaveAttestationForPubKey( ctx: ctx, record: &common.AttestationRecord{ PubKey: pubKey, - Source: att.Data.Source.Epoch, - Target: att.Data.Target.Epoch, + Source: att.GetData().Source.Epoch, + Target: att.GetData().Target.Epoch, SigningRoot: signingRoot[:], }, } diff --git a/validator/helpers/metadata_test.go b/validator/helpers/metadata_test.go index 0df70229c697..90c6cd455c9b 100644 --- a/validator/helpers/metadata_test.go +++ b/validator/helpers/metadata_test.go @@ -105,7 +105,7 @@ func (db *ValidatorDBMock) AttestedPublicKeys(ctx context.Context) ([][fieldpara } func (db *ValidatorDBMock) SlashableAttestationCheck( - ctx context.Context, indexedAtt *ethpb.IndexedAttestation, pubKey [fieldparams.BLSPubkeyLength]byte, + ctx context.Context, indexedAtt ethpb.IndexedAtt, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot32 [32]byte, emitAccountMetrics bool, validatorAttestFailVec *prometheus.CounterVec, @@ -114,7 +114,7 @@ func (db *ValidatorDBMock) SlashableAttestationCheck( } func (db *ValidatorDBMock) SaveAttestationForPubKey( - ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot [fieldparams.RootLength]byte, att *ethpb.IndexedAttestation, + ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, signingRoot [fieldparams.RootLength]byte, att ethpb.IndexedAtt, ) error { panic("not implemented") } From 2f76ba542f961a5aea0f507d564daefca7a156f6 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Wed, 17 Jul 2024 08:24:15 -0500 Subject: [PATCH 09/17] adding consolidation requests to json serialization (#14229) * adding consolidation requests to json serialization * fixing test * missed file checkin * fixing unit tests --- beacon-chain/execution/engine_client.go | 43 ++--- beacon-chain/execution/engine_client_test.go | 57 ++++--- beacon-chain/execution/payload_body_test.go | 1 + proto/engine/v1/json_marshal_unmarshal.go | 151 +++++++++++++----- .../engine/v1/json_marshal_unmarshal_test.go | 21 ++- testing/util/electra.go | 7 + 6 files changed, 196 insertions(+), 84 deletions(-) diff --git a/beacon-chain/execution/engine_client.go b/beacon-chain/execution/engine_client.go index 17a4c74c17d3..6d604065d70d 100644 --- a/beacon-chain/execution/engine_client.go +++ b/beacon-chain/execution/engine_client.go @@ -612,27 +612,32 @@ func fullPayloadFromPayloadBody( if err != nil { return nil, err } + cr, err := pb.JsonConsolidationRequestsToProto(body.ConsolidationRequests) + if err != nil { + return nil, err + } return blocks.WrappedExecutionPayloadElectra( &pb.ExecutionPayloadElectra{ - ParentHash: header.ParentHash(), - FeeRecipient: header.FeeRecipient(), - StateRoot: header.StateRoot(), - ReceiptsRoot: header.ReceiptsRoot(), - LogsBloom: header.LogsBloom(), - PrevRandao: header.PrevRandao(), - BlockNumber: header.BlockNumber(), - GasLimit: header.GasLimit(), - GasUsed: header.GasUsed(), - Timestamp: header.Timestamp(), - ExtraData: header.ExtraData(), - BaseFeePerGas: header.BaseFeePerGas(), - BlockHash: header.BlockHash(), - Transactions: pb.RecastHexutilByteSlice(body.Transactions), - Withdrawals: body.Withdrawals, - ExcessBlobGas: ebg, - BlobGasUsed: bgu, - DepositRequests: dr, - WithdrawalRequests: wr, + ParentHash: header.ParentHash(), + FeeRecipient: header.FeeRecipient(), + StateRoot: header.StateRoot(), + ReceiptsRoot: header.ReceiptsRoot(), + LogsBloom: header.LogsBloom(), + PrevRandao: header.PrevRandao(), + BlockNumber: header.BlockNumber(), + GasLimit: header.GasLimit(), + GasUsed: header.GasUsed(), + Timestamp: header.Timestamp(), + ExtraData: header.ExtraData(), + BaseFeePerGas: header.BaseFeePerGas(), + BlockHash: header.BlockHash(), + Transactions: pb.RecastHexutilByteSlice(body.Transactions), + Withdrawals: body.Withdrawals, + ExcessBlobGas: ebg, + BlobGasUsed: bgu, + DepositRequests: dr, + WithdrawalRequests: wr, + ConsolidationRequests: cr, }) // We can't get the block value and don't care about the block value for this instance default: return nil, fmt.Errorf("unknown execution block version for payload %d", bVersion) diff --git a/beacon-chain/execution/engine_client_test.go b/beacon-chain/execution/engine_client_test.go index 246085a932e3..2977249c0a7b 100644 --- a/beacon-chain/execution/engine_client_test.go +++ b/beacon-chain/execution/engine_client_test.go @@ -1533,6 +1533,20 @@ func fixturesStruct() *payloadFixtures { Index: &idx, } } + consolidationRequests := make([]pb.ConsolidationRequestV1, 1) + for i := range consolidationRequests { + address := &common.Address{} + address.SetBytes([]byte{0, 0, byte(i)}) + sPubkey := pb.BlsPubkey{} + copy(sPubkey[:], []byte{0, byte(i)}) + tPubkey := pb.BlsPubkey{} + copy(tPubkey[:], []byte{0, byte(i)}) + consolidationRequests[i] = pb.ConsolidationRequestV1{ + SourceAddress: address, + SourcePubkey: &sPubkey, + TargetPubkey: &tPubkey, + } + } dr, err := pb.JsonDepositRequestsToProto(depositRequests) if err != nil { panic(err) @@ -1541,26 +1555,31 @@ func fixturesStruct() *payloadFixtures { if err != nil { panic(err) } + cr, err := pb.JsonConsolidationRequestsToProto(consolidationRequests) + if err != nil { + panic(err) + } executionPayloadFixtureElectra := &pb.ExecutionPayloadElectra{ - ParentHash: foo[:], - FeeRecipient: bar, - StateRoot: foo[:], - ReceiptsRoot: foo[:], - LogsBloom: baz, - PrevRandao: foo[:], - BlockNumber: 1, - GasLimit: 1, - GasUsed: 1, - Timestamp: 1, - ExtraData: foo[:], - BaseFeePerGas: bytesutil.PadTo(baseFeePerGas.Bytes(), fieldparams.RootLength), - BlockHash: foo[:], - Transactions: [][]byte{foo[:]}, - Withdrawals: []*pb.Withdrawal{}, - BlobGasUsed: 2, - ExcessBlobGas: 3, - DepositRequests: dr, - WithdrawalRequests: wr, + ParentHash: foo[:], + FeeRecipient: bar, + StateRoot: foo[:], + ReceiptsRoot: foo[:], + LogsBloom: baz, + PrevRandao: foo[:], + BlockNumber: 1, + GasLimit: 1, + GasUsed: 1, + Timestamp: 1, + ExtraData: foo[:], + BaseFeePerGas: bytesutil.PadTo(baseFeePerGas.Bytes(), fieldparams.RootLength), + BlockHash: foo[:], + Transactions: [][]byte{foo[:]}, + Withdrawals: []*pb.Withdrawal{}, + BlobGasUsed: 2, + ExcessBlobGas: 3, + DepositRequests: dr, + WithdrawalRequests: wr, + ConsolidationRequests: cr, } hexUint := hexutil.Uint64(1) executionPayloadWithValueFixtureCapella := &pb.GetPayloadV2ResponseJson{ diff --git a/beacon-chain/execution/payload_body_test.go b/beacon-chain/execution/payload_body_test.go index f94192dec00f..ab447502c054 100644 --- a/beacon-chain/execution/payload_body_test.go +++ b/beacon-chain/execution/payload_body_test.go @@ -68,6 +68,7 @@ func payloadToBody(t *testing.T, ed interfaces.ExecutionData) *pb.ExecutionPaylo if isElectra { body.DepositRequests = pb.ProtoDepositRequestsToJson(eed.DepositRequests()) body.WithdrawalRequests = pb.ProtoWithdrawalRequestsToJson(eed.WithdrawalRequests()) + body.ConsolidationRequests = pb.ProtoConsolidationRequestsToJson(eed.ConsolidationRequests()) } return body } diff --git a/proto/engine/v1/json_marshal_unmarshal.go b/proto/engine/v1/json_marshal_unmarshal.go index 61466a7d7ca5..8e489154f4d6 100644 --- a/proto/engine/v1/json_marshal_unmarshal.go +++ b/proto/engine/v1/json_marshal_unmarshal.go @@ -306,33 +306,35 @@ type GetPayloadV4ResponseJson struct { // ExecutionPayloadElectraJSON represents the engine API ExecutionPayloadV4 type. type ExecutionPayloadElectraJSON struct { - ParentHash *common.Hash `json:"parentHash"` - FeeRecipient *common.Address `json:"feeRecipient"` - StateRoot *common.Hash `json:"stateRoot"` - ReceiptsRoot *common.Hash `json:"receiptsRoot"` - LogsBloom *hexutil.Bytes `json:"logsBloom"` - PrevRandao *common.Hash `json:"prevRandao"` - BlockNumber *hexutil.Uint64 `json:"blockNumber"` - GasLimit *hexutil.Uint64 `json:"gasLimit"` - GasUsed *hexutil.Uint64 `json:"gasUsed"` - Timestamp *hexutil.Uint64 `json:"timestamp"` - ExtraData hexutil.Bytes `json:"extraData"` - BaseFeePerGas string `json:"baseFeePerGas"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - BlockHash *common.Hash `json:"blockHash"` - Transactions []hexutil.Bytes `json:"transactions"` - Withdrawals []*Withdrawal `json:"withdrawals"` - WithdrawalRequests []WithdrawalRequestV1 `json:"withdrawalRequests"` - DepositRequests []DepositRequestV1 `json:"depositRequests"` + ParentHash *common.Hash `json:"parentHash"` + FeeRecipient *common.Address `json:"feeRecipient"` + StateRoot *common.Hash `json:"stateRoot"` + ReceiptsRoot *common.Hash `json:"receiptsRoot"` + LogsBloom *hexutil.Bytes `json:"logsBloom"` + PrevRandao *common.Hash `json:"prevRandao"` + BlockNumber *hexutil.Uint64 `json:"blockNumber"` + GasLimit *hexutil.Uint64 `json:"gasLimit"` + GasUsed *hexutil.Uint64 `json:"gasUsed"` + Timestamp *hexutil.Uint64 `json:"timestamp"` + ExtraData hexutil.Bytes `json:"extraData"` + BaseFeePerGas string `json:"baseFeePerGas"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` + BlockHash *common.Hash `json:"blockHash"` + Transactions []hexutil.Bytes `json:"transactions"` + Withdrawals []*Withdrawal `json:"withdrawals"` + WithdrawalRequests []WithdrawalRequestV1 `json:"withdrawalRequests"` + DepositRequests []DepositRequestV1 `json:"depositRequests"` + ConsolidationRequests []ConsolidationRequestV1 `json:"consolidationRequests"` } // ExecutionPayloadBody represents the engine API ExecutionPayloadV1 or ExecutionPayloadV2 type. type ExecutionPayloadBody struct { - Transactions []hexutil.Bytes `json:"transactions"` - Withdrawals []*Withdrawal `json:"withdrawals"` - WithdrawalRequests []WithdrawalRequestV1 `json:"withdrawalRequests"` - DepositRequests []DepositRequestV1 `json:"depositRequests"` + Transactions []hexutil.Bytes `json:"transactions"` + Withdrawals []*Withdrawal `json:"withdrawals"` + WithdrawalRequests []WithdrawalRequestV1 `json:"withdrawalRequests"` + DepositRequests []DepositRequestV1 `json:"depositRequests"` + ConsolidationRequests []ConsolidationRequestV1 `json:"consolidationRequests"` } // Validate returns an error if key fields in GetPayloadV4ResponseJson are nil or invalid. @@ -468,6 +470,30 @@ func (r DepositRequestV1) Validate() error { return nil } +// ConsolidationRequestV1 represents an execution engine ConsolidationRequestV1 value +// https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#consolidationrequestv1 +type ConsolidationRequestV1 struct { + // sourceAddress: DATA, 20 Bytes + SourceAddress *common.Address `json:"sourceAddress"` + // sourcePubkey: DATA, 48 Bytes + SourcePubkey *BlsPubkey `json:"sourcePubkey"` + // targetPubkey: DATA, 48 Bytes + TargetPubkey *BlsPubkey `json:"targetPubkey"` +} + +func (r ConsolidationRequestV1) Validate() error { + if r.SourceAddress == nil { + return errors.Wrap(errJsonNilField, "missing required field 'sourceAddress' for ConsolidationRequestV1") + } + if r.SourcePubkey == nil { + return errors.Wrap(errJsonNilField, "missing required field 'sourcePubkey' for ConsolidationRequestV1") + } + if r.TargetPubkey == nil { + return errors.Wrap(errJsonNilField, "missing required field 'targetPubkey' for ConsolidationRequestV1") + } + return nil +} + // MarshalJSON -- func (e *ExecutionPayload) MarshalJSON() ([]byte, error) { transactions := make([]hexutil.Bytes, len(e.Transactions)) @@ -1065,6 +1091,42 @@ func ProtoWithdrawalRequestsToJson(reqs []*WithdrawalRequest) []WithdrawalReques return j } +func JsonConsolidationRequestsToProto(j []ConsolidationRequestV1) ([]*ConsolidationRequest, error) { + reqs := make([]*ConsolidationRequest, len(j)) + + for i := range j { + req := j[i] + if err := req.Validate(); err != nil { + return nil, err + } + reqs[i] = &ConsolidationRequest{ + SourceAddress: req.SourceAddress.Bytes(), + SourcePubkey: req.SourcePubkey.Bytes(), + TargetPubkey: req.TargetPubkey.Bytes(), + } + } + + return reqs, nil +} + +func ProtoConsolidationRequestsToJson(reqs []*ConsolidationRequest) []ConsolidationRequestV1 { + j := make([]ConsolidationRequestV1, len(reqs)) + for i := range reqs { + r := reqs[i] + spk := BlsPubkey{} + copy(spk[:], r.SourcePubkey) + tpk := BlsPubkey{} + copy(tpk[:], r.TargetPubkey) + address := common.BytesToAddress(r.SourceAddress) + j[i] = ConsolidationRequestV1{ + SourceAddress: &address, + SourcePubkey: &spk, + TargetPubkey: &tpk, + } + } + return j +} + func (j *ExecutionPayloadElectraJSON) ElectraPayload() (*ExecutionPayloadElectra, error) { baseFeeBigEnd, err := hexutil.DecodeBig(j.BaseFeePerGas) if err != nil { @@ -1087,26 +1149,31 @@ func (j *ExecutionPayloadElectraJSON) ElectraPayload() (*ExecutionPayloadElectra if err != nil { return nil, err } + cr, err := JsonConsolidationRequestsToProto(j.ConsolidationRequests) + if err != nil { + return nil, err + } return &ExecutionPayloadElectra{ - ParentHash: j.ParentHash.Bytes(), - FeeRecipient: j.FeeRecipient.Bytes(), - StateRoot: j.StateRoot.Bytes(), - ReceiptsRoot: j.ReceiptsRoot.Bytes(), - LogsBloom: *j.LogsBloom, - PrevRandao: j.PrevRandao.Bytes(), - BlockNumber: uint64(*j.BlockNumber), - GasLimit: uint64(*j.GasLimit), - GasUsed: uint64(*j.GasUsed), - Timestamp: uint64(*j.Timestamp), - ExtraData: j.ExtraData, - BaseFeePerGas: baseFee, - BlockHash: j.BlockHash.Bytes(), - Transactions: transactions, - Withdrawals: j.Withdrawals, - BlobGasUsed: uint64(*j.BlobGasUsed), - ExcessBlobGas: uint64(*j.ExcessBlobGas), - DepositRequests: dr, - WithdrawalRequests: wr, + ParentHash: j.ParentHash.Bytes(), + FeeRecipient: j.FeeRecipient.Bytes(), + StateRoot: j.StateRoot.Bytes(), + ReceiptsRoot: j.ReceiptsRoot.Bytes(), + LogsBloom: *j.LogsBloom, + PrevRandao: j.PrevRandao.Bytes(), + BlockNumber: uint64(*j.BlockNumber), + GasLimit: uint64(*j.GasLimit), + GasUsed: uint64(*j.GasUsed), + Timestamp: uint64(*j.Timestamp), + ExtraData: j.ExtraData, + BaseFeePerGas: baseFee, + BlockHash: j.BlockHash.Bytes(), + Transactions: transactions, + Withdrawals: j.Withdrawals, + BlobGasUsed: uint64(*j.BlobGasUsed), + ExcessBlobGas: uint64(*j.ExcessBlobGas), + DepositRequests: dr, + WithdrawalRequests: wr, + ConsolidationRequests: cr, }, nil } diff --git a/proto/engine/v1/json_marshal_unmarshal_test.go b/proto/engine/v1/json_marshal_unmarshal_test.go index f7aaf66d2161..bc1d8dab6fd6 100644 --- a/proto/engine/v1/json_marshal_unmarshal_test.go +++ b/proto/engine/v1/json_marshal_unmarshal_test.go @@ -330,6 +330,14 @@ func TestJsonMarshalUnmarshal(t *testing.T) { }, } + consolidationReq := []*enginev1.ConsolidationRequest{ + { + SourceAddress: bytesutil.PadTo([]byte("sourceAddress-1"), 20), + SourcePubkey: bytesutil.PadTo([]byte("s-pubKey-1"), 48), + TargetPubkey: bytesutil.PadTo([]byte("t-pubKey-1"), 48), + }, + } + resp := &enginev1.GetPayloadV4ResponseJson{ BlobsBundle: &enginev1.BlobBundleJSON{ Commitments: []hexutil.Bytes{{'a'}, {'b'}, {'c'}, {'d'}}, @@ -358,10 +366,11 @@ func TestJsonMarshalUnmarshal(t *testing.T) { Address: bytesutil.PadTo([]byte("address"), 20), Amount: 1, }}, - BlobGasUsed: &bgu, - ExcessBlobGas: &ebg, - WithdrawalRequests: enginev1.ProtoWithdrawalRequestsToJson(withdrawalReq), - DepositRequests: enginev1.ProtoDepositRequestsToJson(depositReq), + BlobGasUsed: &bgu, + ExcessBlobGas: &ebg, + WithdrawalRequests: enginev1.ProtoWithdrawalRequestsToJson(withdrawalReq), + DepositRequests: enginev1.ProtoDepositRequestsToJson(depositReq), + ConsolidationRequests: enginev1.ProtoConsolidationRequestsToJson(consolidationReq), }, } enc, err := json.Marshal(resp) @@ -414,6 +423,10 @@ func TestJsonMarshalUnmarshal(t *testing.T) { for i := range pb.Payload.DepositRequests { require.DeepEqual(t, pb.Payload.DepositRequests[i], depositReq[i]) } + require.Equal(t, len(pb.Payload.ConsolidationRequests), len(consolidationReq)) + for i := range pb.Payload.ConsolidationRequests { + require.DeepEqual(t, pb.Payload.ConsolidationRequests[i], consolidationReq[i]) + } }) t.Run("execution block", func(t *testing.T) { baseFeePerGas := big.NewInt(1770307273) diff --git a/testing/util/electra.go b/testing/util/electra.go index 59889a0e1215..dc009427bac1 100644 --- a/testing/util/electra.go +++ b/testing/util/electra.go @@ -116,6 +116,13 @@ func GenerateTestElectraBlockWithSidecar(t *testing.T, parent [32]byte, slot pri ExcessBlobGas: 0, DepositRequests: generateTestDepositRequests(uint64(g.slot), 4), WithdrawalRequests: generateTestWithdrawalRequests(uint64(g.slot), 4), + ConsolidationRequests: []*enginev1.ConsolidationRequest{ + { + SourceAddress: make([]byte, 20), + SourcePubkey: make([]byte, 48), + TargetPubkey: make([]byte, 48), + }, + }, } } From 8b4b3a269b92c77946dda1d3576df91699d04acf Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Wed, 17 Jul 2024 15:52:36 -0500 Subject: [PATCH 10/17] Fix typo (#14233) --- beacon-chain/execution/engine_client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon-chain/execution/engine_client.go b/beacon-chain/execution/engine_client.go index 6d604065d70d..7875fa6c2d77 100644 --- a/beacon-chain/execution/engine_client.go +++ b/beacon-chain/execution/engine_client.go @@ -170,7 +170,7 @@ func (s *Service) NewPayload(ctx context.Context, payload interfaces.ExecutionDa case *pb.ExecutionPayloadElectra: payloadPb, ok := payload.Proto().(*pb.ExecutionPayloadElectra) if !ok { - return nil, errors.New("execution data must be a Deneb execution payload") + return nil, errors.New("execution data must be a Electra execution payload") } err := s.rpcClient.CallContext(ctx, result, NewPayloadMethodV4, payloadPb, versionedHashes, parentBlockRoot) if err != nil { From c7e2d709cf04649f13ccc57dd855d64a6ece6097 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Thu, 18 Jul 2024 09:25:54 -0500 Subject: [PATCH 11/17] electra payload fix for devnet1 (#14235) * fixing marshalling and adding more to unit test * updating missed consolidation requests * renaming variables * adding test gen * reverting config change --- beacon-chain/execution/engine_client_test.go | 48 ++++++++++------- proto/engine/v1/json_marshal_unmarshal.go | 39 +++++++------- proto/prysm/v1alpha1/cloners.go | 55 +++++++++++++------- proto/prysm/v1alpha1/cloners_test.go | 55 +++++++++++++------- 4 files changed, 122 insertions(+), 75 deletions(-) diff --git a/beacon-chain/execution/engine_client_test.go b/beacon-chain/execution/engine_client_test.go index 2977249c0a7b..2e3e29b1defb 100644 --- a/beacon-chain/execution/engine_client_test.go +++ b/beacon-chain/execution/engine_client_test.go @@ -374,6 +374,17 @@ func TestClient_HTTP(t *testing.T) { require.DeepEqual(t, proofs, resp.BlobsBundle.Proofs) blobs := [][]byte{bytesutil.PadTo([]byte("a"), fieldparams.BlobLength), bytesutil.PadTo([]byte("b"), fieldparams.BlobLength)} require.DeepEqual(t, blobs, resp.BlobsBundle.Blobs) + ede, ok := resp.ExecutionData.(interfaces.ExecutionDataElectra) + require.Equal(t, true, ok) + require.NotNil(t, ede.WithdrawalRequests()) + wrequestsNotOverMax := len(ede.WithdrawalRequests()) <= int(params.BeaconConfig().MaxWithdrawalRequestsPerPayload) + require.Equal(t, true, wrequestsNotOverMax) + require.NotNil(t, ede.DepositRequests()) + drequestsNotOverMax := len(ede.DepositRequests()) <= int(params.BeaconConfig().MaxDepositRequestsPerPayload) + require.Equal(t, true, drequestsNotOverMax) + require.NotNil(t, ede.ConsolidationRequests()) + consolidationsNotOverMax := len(ede.ConsolidationRequests()) <= int(params.BeaconConfig().MaxConsolidationsRequestsPerPayload) + require.Equal(t, true, consolidationsNotOverMax) }) t.Run(ForkchoiceUpdatedMethod+" VALID status", func(t *testing.T) { forkChoiceState := &pb.ForkchoiceState{ @@ -1633,24 +1644,25 @@ func fixturesStruct() *payloadFixtures { 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, - DepositRequests: depositRequests, - WithdrawalRequests: withdrawalRequests, + 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, + ConsolidationRequests: consolidationRequests, }, BlockValue: "0x11fffffffff", BlobsBundle: &pb.BlobBundleJSON{ diff --git a/proto/engine/v1/json_marshal_unmarshal.go b/proto/engine/v1/json_marshal_unmarshal.go index 8e489154f4d6..3a7de3a5efe4 100644 --- a/proto/engine/v1/json_marshal_unmarshal.go +++ b/proto/engine/v1/json_marshal_unmarshal.go @@ -992,25 +992,26 @@ func (e *ExecutionPayloadElectra) MarshalJSON() ([]byte, error) { excessBlobGas := hexutil.Uint64(e.ExcessBlobGas) return json.Marshal(ExecutionPayloadElectraJSON{ - ParentHash: &pHash, - FeeRecipient: &recipient, - StateRoot: &sRoot, - ReceiptsRoot: &recRoot, - LogsBloom: &logsBloom, - PrevRandao: &prevRan, - BlockNumber: &blockNum, - GasLimit: &gasLimit, - GasUsed: &gasUsed, - Timestamp: &timeStamp, - ExtraData: e.ExtraData, - BaseFeePerGas: baseFeeHex, - BlockHash: &bHash, - Transactions: transactions, - Withdrawals: withdrawals, - BlobGasUsed: &blobGasUsed, - ExcessBlobGas: &excessBlobGas, - WithdrawalRequests: ProtoWithdrawalRequestsToJson(e.WithdrawalRequests), - DepositRequests: ProtoDepositRequestsToJson(e.DepositRequests), + ParentHash: &pHash, + FeeRecipient: &recipient, + StateRoot: &sRoot, + ReceiptsRoot: &recRoot, + LogsBloom: &logsBloom, + PrevRandao: &prevRan, + BlockNumber: &blockNum, + GasLimit: &gasLimit, + GasUsed: &gasUsed, + Timestamp: &timeStamp, + ExtraData: e.ExtraData, + BaseFeePerGas: baseFeeHex, + BlockHash: &bHash, + Transactions: transactions, + Withdrawals: withdrawals, + BlobGasUsed: &blobGasUsed, + ExcessBlobGas: &excessBlobGas, + WithdrawalRequests: ProtoWithdrawalRequestsToJson(e.WithdrawalRequests), + DepositRequests: ProtoDepositRequestsToJson(e.DepositRequests), + ConsolidationRequests: ProtoConsolidationRequestsToJson(e.ConsolidationRequests), }) } diff --git a/proto/prysm/v1alpha1/cloners.go b/proto/prysm/v1alpha1/cloners.go index 8e51844d7127..1727c0ab0af0 100644 --- a/proto/prysm/v1alpha1/cloners.go +++ b/proto/prysm/v1alpha1/cloners.go @@ -1002,25 +1002,26 @@ func CopyExecutionPayloadElectra(payload *enginev1.ExecutionPayloadElectra) *eng return nil } return &enginev1.ExecutionPayloadElectra{ - ParentHash: bytesutil.SafeCopyBytes(payload.ParentHash), - FeeRecipient: bytesutil.SafeCopyBytes(payload.FeeRecipient), - StateRoot: bytesutil.SafeCopyBytes(payload.StateRoot), - ReceiptsRoot: bytesutil.SafeCopyBytes(payload.ReceiptsRoot), - LogsBloom: bytesutil.SafeCopyBytes(payload.LogsBloom), - PrevRandao: bytesutil.SafeCopyBytes(payload.PrevRandao), - BlockNumber: payload.BlockNumber, - GasLimit: payload.GasLimit, - GasUsed: payload.GasUsed, - Timestamp: payload.Timestamp, - ExtraData: bytesutil.SafeCopyBytes(payload.ExtraData), - BaseFeePerGas: bytesutil.SafeCopyBytes(payload.BaseFeePerGas), - BlockHash: bytesutil.SafeCopyBytes(payload.BlockHash), - Transactions: bytesutil.SafeCopy2dBytes(payload.Transactions), - Withdrawals: CopyWithdrawalSlice(payload.Withdrawals), - BlobGasUsed: payload.BlobGasUsed, - ExcessBlobGas: payload.ExcessBlobGas, - DepositRequests: CopyDepositRequests(payload.DepositRequests), - WithdrawalRequests: CopyWithdrawalRequests(payload.WithdrawalRequests), + ParentHash: bytesutil.SafeCopyBytes(payload.ParentHash), + FeeRecipient: bytesutil.SafeCopyBytes(payload.FeeRecipient), + StateRoot: bytesutil.SafeCopyBytes(payload.StateRoot), + ReceiptsRoot: bytesutil.SafeCopyBytes(payload.ReceiptsRoot), + LogsBloom: bytesutil.SafeCopyBytes(payload.LogsBloom), + PrevRandao: bytesutil.SafeCopyBytes(payload.PrevRandao), + BlockNumber: payload.BlockNumber, + GasLimit: payload.GasLimit, + GasUsed: payload.GasUsed, + Timestamp: payload.Timestamp, + ExtraData: bytesutil.SafeCopyBytes(payload.ExtraData), + BaseFeePerGas: bytesutil.SafeCopyBytes(payload.BaseFeePerGas), + BlockHash: bytesutil.SafeCopyBytes(payload.BlockHash), + Transactions: bytesutil.SafeCopy2dBytes(payload.Transactions), + Withdrawals: CopyWithdrawalSlice(payload.Withdrawals), + BlobGasUsed: payload.BlobGasUsed, + ExcessBlobGas: payload.ExcessBlobGas, + DepositRequests: CopyDepositRequests(payload.DepositRequests), + WithdrawalRequests: CopyWithdrawalRequests(payload.WithdrawalRequests), + ConsolidationRequests: CopyConsolidationRequests(payload.ConsolidationRequests), } } @@ -1058,6 +1059,22 @@ func CopyWithdrawalRequests(wr []*enginev1.WithdrawalRequest) []*enginev1.Withdr return newWr } +func CopyConsolidationRequests(cr []*enginev1.ConsolidationRequest) []*enginev1.ConsolidationRequest { + if cr == nil { + return nil + } + newCr := make([]*enginev1.ConsolidationRequest, len(cr)) + for i, w := range cr { + newCr[i] = &enginev1.ConsolidationRequest{ + SourceAddress: bytesutil.SafeCopyBytes(w.SourceAddress), + SourcePubkey: bytesutil.SafeCopyBytes(w.SourcePubkey), + TargetPubkey: bytesutil.SafeCopyBytes(w.TargetPubkey), + } + } + + return newCr +} + func CopyExecutionPayloadHeaderElectra(payload *enginev1.ExecutionPayloadHeaderElectra) *enginev1.ExecutionPayloadHeaderElectra { if payload == nil { return nil diff --git a/proto/prysm/v1alpha1/cloners_test.go b/proto/prysm/v1alpha1/cloners_test.go index e408599cf50d..5383970c9dec 100644 --- a/proto/prysm/v1alpha1/cloners_test.go +++ b/proto/prysm/v1alpha1/cloners_test.go @@ -1465,25 +1465,26 @@ func genBeaconBlockBodyElectra() *v1alpha1.BeaconBlockBodyElectra { func genExecutionPayloadElectra() *enginev1.ExecutionPayloadElectra { return &enginev1.ExecutionPayloadElectra{ - ParentHash: bytes(32), - FeeRecipient: bytes(20), - StateRoot: bytes(32), - ReceiptsRoot: bytes(32), - LogsBloom: bytes(256), - PrevRandao: bytes(32), - BlockNumber: 1, - GasLimit: 2, - GasUsed: 3, - Timestamp: 4, - ExtraData: bytes(32), - BaseFeePerGas: bytes(32), - BlockHash: bytes(32), - Transactions: [][]byte{{'a'}, {'b'}, {'c'}}, - Withdrawals: genWithdrawals(10), - BlobGasUsed: 5, - ExcessBlobGas: 6, - DepositRequests: genDepositRequests(10), - WithdrawalRequests: genWithdrawalRequests(10), + ParentHash: bytes(32), + FeeRecipient: bytes(20), + StateRoot: bytes(32), + ReceiptsRoot: bytes(32), + LogsBloom: bytes(256), + PrevRandao: bytes(32), + BlockNumber: 1, + GasLimit: 2, + GasUsed: 3, + Timestamp: 4, + ExtraData: bytes(32), + BaseFeePerGas: bytes(32), + BlockHash: bytes(32), + Transactions: [][]byte{{'a'}, {'b'}, {'c'}}, + Withdrawals: genWithdrawals(10), + BlobGasUsed: 5, + ExcessBlobGas: 6, + DepositRequests: genDepositRequests(10), + WithdrawalRequests: genWithdrawalRequests(10), + ConsolidationRequests: genConsolidationRequests(10), } } @@ -1521,6 +1522,22 @@ func genWithdrawalRequest() *enginev1.WithdrawalRequest { } } +func genConsolidationRequests(num int) []*enginev1.ConsolidationRequest { + crs := make([]*enginev1.ConsolidationRequest, num) + for i := 0; i < num; i++ { + crs[i] = genConsolidationRequest() + } + return crs +} + +func genConsolidationRequest() *enginev1.ConsolidationRequest { + return &enginev1.ConsolidationRequest{ + SourceAddress: bytes(20), + SourcePubkey: bytes(48), + TargetPubkey: bytes(48), + } +} + func genPendingPartialWithdrawals(num int) []*v1alpha1.PendingPartialWithdrawal { ppws := make([]*v1alpha1.PendingPartialWithdrawal, num) for i := 0; i < num; i++ { From 3a734f51e0ae85eaab08fdf610ac5d80d25113ef Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:40:06 -0500 Subject: [PATCH 12/17] removing unused naming convention (#14241) --- config/params/loader_test.go | 9 ++++----- testing/spectest/exclusions.txt | 2 -- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/config/params/loader_test.go b/config/params/loader_test.go index 3045bb4a5e78..4f8872b29235 100644 --- a/config/params/loader_test.go +++ b/config/params/loader_test.go @@ -35,11 +35,10 @@ var placeholderFields = []string{ "FIELD_ELEMENTS_PER_BLOB", // Compile time constant. "KZG_COMMITMENT_INCLUSION_PROOF_DEPTH", // Compile time constant on BlobSidecar.commitment_inclusion_proof. "MAX_BLOBS_PER_BLOCK", - "MAX_BLOB_COMMITMENTS_PER_BLOCK", // Compile time constant on BeaconBlockBodyDeneb.blob_kzg_commitments. - "MAX_BYTES_PER_TRANSACTION", // Used for ssz of EL transactions. Unused in Prysm. - "MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD", // Compile time constant on ExecutionPayload.deposit_receipts. TODO: rename when updating spec configs - "MAX_EXTRA_DATA_BYTES", // Compile time constant on ExecutionPayload.extra_data. - "MAX_TRANSACTIONS_PER_PAYLOAD", // Compile time constant on ExecutionPayload.transactions. + "MAX_BLOB_COMMITMENTS_PER_BLOCK", // Compile time constant on BeaconBlockBodyDeneb.blob_kzg_commitments. + "MAX_BYTES_PER_TRANSACTION", // Used for ssz of EL transactions. Unused in Prysm. + "MAX_EXTRA_DATA_BYTES", // Compile time constant on ExecutionPayload.extra_data. + "MAX_TRANSACTIONS_PER_PAYLOAD", // Compile time constant on ExecutionPayload.transactions. "REORG_HEAD_WEIGHT_THRESHOLD", "SAMPLES_PER_SLOT", "TARGET_NUMBER_OF_PEERS", diff --git a/testing/spectest/exclusions.txt b/testing/spectest/exclusions.txt index 8e12f2dbcbb3..6da3a2125169 100644 --- a/testing/spectest/exclusions.txt +++ b/testing/spectest/exclusions.txt @@ -47,7 +47,6 @@ tests/mainnet/eip6110/operations/attester_slashing tests/mainnet/eip6110/operations/block_header tests/mainnet/eip6110/operations/bls_to_execution_change tests/mainnet/eip6110/operations/deposit -tests/mainnet/eip6110/operations/deposit_receipt tests/mainnet/eip6110/operations/execution_payload tests/mainnet/eip6110/operations/proposer_slashing tests/mainnet/eip6110/operations/sync_aggregate @@ -136,7 +135,6 @@ tests/minimal/eip6110/operations/attester_slashing tests/minimal/eip6110/operations/block_header tests/minimal/eip6110/operations/bls_to_execution_change tests/minimal/eip6110/operations/deposit -tests/minimal/eip6110/operations/deposit_receipt tests/minimal/eip6110/operations/execution_payload tests/minimal/eip6110/operations/proposer_slashing tests/minimal/eip6110/operations/sync_aggregate From 0e8f98b2a4e2313e9ed0e5ca3cf6d2a40fed314a Mon Sep 17 00:00:00 2001 From: terence Date: Thu, 18 Jul 2024 21:29:53 -0700 Subject: [PATCH 13/17] Remove electra todo for process deposit requests (#14243) --- beacon-chain/core/electra/transition_no_verify_sig.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon-chain/core/electra/transition_no_verify_sig.go b/beacon-chain/core/electra/transition_no_verify_sig.go index 79e34c4f9a0e..12da1a6c6fe5 100644 --- a/beacon-chain/core/electra/transition_no_verify_sig.go +++ b/beacon-chain/core/electra/transition_no_verify_sig.go @@ -89,7 +89,7 @@ func ProcessOperations( return nil, errors.Wrap(err, "could not process execution layer withdrawal requests") } - st, err = ProcessDepositRequests(ctx, st, exe.DepositRequests()) // TODO: EIP-6110 deposit changes. + st, err = ProcessDepositRequests(ctx, st, exe.DepositRequests()) if err != nil { return nil, errors.Wrap(err, "could not process deposit receipts") } From d066480a514908bb62a7d1129b5e185ef73cab70 Mon Sep 17 00:00:00 2001 From: Sammy Rosso <15244892+saolyn@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:56:13 +0200 Subject: [PATCH 14/17] GetValidatorPerformance empty body bug (#14240) * fix possible empty body panic bug * gaz * fix test --- beacon-chain/rpc/prysm/validator/BUILD.bazel | 1 + .../prysm/validator/validator_performance.go | 23 ++++++++++++------- .../validator/validator_performance_test.go | 2 +- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/beacon-chain/rpc/prysm/validator/BUILD.bazel b/beacon-chain/rpc/prysm/validator/BUILD.bazel index 1f8767bc55b8..1c0e4f9368cb 100644 --- a/beacon-chain/rpc/prysm/validator/BUILD.bazel +++ b/beacon-chain/rpc/prysm/validator/BUILD.bazel @@ -13,6 +13,7 @@ go_library( "//beacon-chain/rpc/core:go_default_library", "//network/httputil:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "@com_github_pkg_errors//:go_default_library", "@io_opencensus_go//trace:go_default_library", ], ) diff --git a/beacon-chain/rpc/prysm/validator/validator_performance.go b/beacon-chain/rpc/prysm/validator/validator_performance.go index fbd7547ad273..2af8d00414e1 100644 --- a/beacon-chain/rpc/prysm/validator/validator_performance.go +++ b/beacon-chain/rpc/prysm/validator/validator_performance.go @@ -2,8 +2,10 @@ package validator import ( "encoding/json" + "io" "net/http" + "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/api/server/structs" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core" "github.com/prysmaticlabs/prysm/v5/network/httputil" @@ -17,21 +19,26 @@ func (s *Server) GetValidatorPerformance(w http.ResponseWriter, r *http.Request) defer span.End() var req structs.GetValidatorPerformanceRequest - if r.Body != http.NoBody { - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - handleHTTPError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest) - return - } + + err := json.NewDecoder(r.Body).Decode(&req) + switch { + case errors.Is(err, io.EOF): + httputil.HandleError(w, "No data submitted", http.StatusBadRequest) + return + case err != nil: + httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest) + return } - computed, err := s.CoreService.ComputeValidatorPerformance( + + computed, rpcError := s.CoreService.ComputeValidatorPerformance( ctx, ðpb.ValidatorPerformanceRequest{ PublicKeys: req.PublicKeys, Indices: req.Indices, }, ) - if err != nil { - handleHTTPError(w, "Could not compute validator performance: "+err.Err.Error(), core.ErrorReasonToHTTP(err.Reason)) + if rpcError != nil { + handleHTTPError(w, "Could not compute validator performance: "+rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason)) return } response := &structs.GetValidatorPerformanceResponse{ diff --git a/beacon-chain/rpc/prysm/validator/validator_performance_test.go b/beacon-chain/rpc/prysm/validator/validator_performance_test.go index 98b3aa9390b9..379f5669a9d0 100644 --- a/beacon-chain/rpc/prysm/validator/validator_performance_test.go +++ b/beacon-chain/rpc/prysm/validator/validator_performance_test.go @@ -41,7 +41,7 @@ func TestServer_GetValidatorPerformance(t *testing.T) { client := &http.Client{} rawResp, err := client.Post(srv.URL, "application/json", req.Body) require.NoError(t, err) - require.Equal(t, http.StatusServiceUnavailable, rawResp.StatusCode) + require.Equal(t, http.StatusBadRequest, rawResp.StatusCode) }) t.Run("OK", func(t *testing.T) { helpers.ClearCache() From 57ffc12f1716c6271909dff23f91ccc958f482d1 Mon Sep 17 00:00:00 2001 From: Sammy Rosso <15244892+saolyn@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:23:36 +0200 Subject: [PATCH 15/17] HTTP endpoint `GetIndividualVotes` (#14198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add http endpoint * add tests * Gaz * Add pointers * add endpoint to test * Electra: EIP-7251 Update `process_voluntary_exit` (#14176) * Electra: EIP-7251 Update `process_voluntary_exit` * Add unit test for VerifyExitAndSignature EIP-7251 * @potuz peer feedback * Avoid Cloning When Creating a New Gossip Message (#14201) * Add Current Changes * add back check * Avoid a Panic * fix: Multiple network flags should prevent the BN to start (#14169) * Implement Initial Logic * Include check in main.go * Add tests for multiple flags * remove usage of append * remove config/features dependency * Move ValidateNetworkFlags to config/features * Nit * removed NetworkFlags from cmd * remove usage of empty string literal * add comment * add flag validation to prysctl validator-exit --------- Co-authored-by: Manu NALEPA * fix tests * Radek' review + tests * fix tests * Radek' review * forgot one * almost forgot the tests --------- Co-authored-by: Preston Van Loon Co-authored-by: Nishant Das Co-authored-by: kira Co-authored-by: Manu NALEPA Co-authored-by: RadosÅ‚aw Kapka --- api/server/structs/endpoints_beacon.go | 29 + beacon-chain/rpc/core/service.go | 1 + beacon-chain/rpc/core/validator.go | 125 ++++ beacon-chain/rpc/endpoints.go | 20 +- beacon-chain/rpc/endpoints_test.go | 1 + beacon-chain/rpc/prysm/beacon/BUILD.bazel | 17 +- beacon-chain/rpc/prysm/beacon/handlers.go | 85 +++ .../rpc/prysm/beacon/handlers_test.go | 660 ++++++++++++++++++ beacon-chain/rpc/prysm/beacon/server.go | 2 + .../rpc/prysm/v1alpha1/beacon/assignments.go | 2 +- .../prysm/v1alpha1/beacon/committees_test.go | 3 + .../rpc/prysm/v1alpha1/beacon/validators.go | 100 +-- .../prysm/v1alpha1/beacon/validators_test.go | 49 +- 13 files changed, 976 insertions(+), 118 deletions(-) create mode 100644 beacon-chain/rpc/prysm/beacon/handlers_test.go diff --git a/api/server/structs/endpoints_beacon.go b/api/server/structs/endpoints_beacon.go index f7a3116572cf..0ee92bc0d5ab 100644 --- a/api/server/structs/endpoints_beacon.go +++ b/api/server/structs/endpoints_beacon.go @@ -196,3 +196,32 @@ type DepositSnapshot struct { ExecutionBlockHash string `json:"execution_block_hash"` ExecutionBlockHeight string `json:"execution_block_height"` } + +type GetIndividualVotesRequest struct { + Epoch string `json:"epoch"` + PublicKeys []string `json:"public_keys,omitempty"` + Indices []string `json:"indices,omitempty"` +} + +type GetIndividualVotesResponse struct { + IndividualVotes []*IndividualVote `json:"individual_votes"` +} + +type IndividualVote struct { + Epoch string `json:"epoch"` + PublicKey string `json:"public_keys,omitempty"` + ValidatorIndex string `json:"validator_index"` + IsSlashed bool `json:"is_slashed"` + IsWithdrawableInCurrentEpoch bool `json:"is_withdrawable_in_current_epoch"` + IsActiveInCurrentEpoch bool `json:"is_active_in_current_epoch"` + IsActiveInPreviousEpoch bool `json:"is_active_in_previous_epoch"` + IsCurrentEpochAttester bool `json:"is_current_epoch_attester"` + IsCurrentEpochTargetAttester bool `json:"is_current_epoch_target_attester"` + IsPreviousEpochAttester bool `json:"is_previous_epoch_attester"` + IsPreviousEpochTargetAttester bool `json:"is_previous_epoch_target_attester"` + IsPreviousEpochHeadAttester bool `json:"is_previous_epoch_head_attester"` + CurrentEpochEffectiveBalanceGwei string `json:"current_epoch_effective_balance_gwei"` + InclusionSlot string `json:"inclusion_slot"` + InclusionDistance string `json:"inclusion_distance"` + InactivityScore string `json:"inactivity_score"` +} diff --git a/beacon-chain/rpc/core/service.go b/beacon-chain/rpc/core/service.go index ad37804886c9..d407c881f4cd 100644 --- a/beacon-chain/rpc/core/service.go +++ b/beacon-chain/rpc/core/service.go @@ -21,5 +21,6 @@ type Service struct { AttestationCache *cache.AttestationCache StateGen stategen.StateManager P2P p2p.Broadcaster + ReplayerBuilder stategen.ReplayerBuilder OptimisticModeFetcher blockchain.OptimisticModeFetcher } diff --git a/beacon-chain/rpc/core/validator.go b/beacon-chain/rpc/core/validator.go index 44c534f83776..251e29ca68e1 100644 --- a/beacon-chain/rpc/core/validator.go +++ b/beacon-chain/rpc/core/validator.go @@ -211,6 +211,131 @@ func (s *Service) ComputeValidatorPerformance( }, nil } +// IndividualVotes retrieves individual voting status of validators. +func (s *Service) IndividualVotes( + ctx context.Context, + req *ethpb.IndividualVotesRequest, +) (*ethpb.IndividualVotesRespond, *RpcError) { + currentEpoch := slots.ToEpoch(s.GenesisTimeFetcher.CurrentSlot()) + if req.Epoch > currentEpoch { + return nil, &RpcError{ + Err: fmt.Errorf("cannot retrieve information about an epoch in the future, current epoch %d, requesting %d\n", currentEpoch, req.Epoch), + Reason: BadRequest, + } + } + + slot, err := slots.EpochEnd(req.Epoch) + if err != nil { + return nil, &RpcError{Err: err, Reason: Internal} + } + st, err := s.ReplayerBuilder.ReplayerForSlot(slot).ReplayBlocks(ctx) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "failed to replay blocks for state at epoch %d", req.Epoch), + Reason: Internal, + } + } + // Track filtered validators to prevent duplication in the response. + filtered := map[primitives.ValidatorIndex]bool{} + filteredIndices := make([]primitives.ValidatorIndex, 0) + votes := make([]*ethpb.IndividualVotesRespond_IndividualVote, 0, len(req.Indices)+len(req.PublicKeys)) + // Filter out assignments by public keys. + for _, pubKey := range req.PublicKeys { + index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey)) + if !ok { + votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{PublicKey: pubKey, ValidatorIndex: primitives.ValidatorIndex(^uint64(0))}) + continue + } + filtered[index] = true + filteredIndices = append(filteredIndices, index) + } + // Filter out assignments by validator indices. + for _, index := range req.Indices { + if !filtered[index] { + filteredIndices = append(filteredIndices, index) + } + } + sort.Slice(filteredIndices, func(i, j int) bool { + return filteredIndices[i] < filteredIndices[j] + }) + + var v []*precompute.Validator + var bal *precompute.Balance + if st.Version() == version.Phase0 { + v, bal, err = precompute.New(ctx, st) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "could not set up pre compute instance"), + Reason: Internal, + } + } + v, _, err = precompute.ProcessAttestations(ctx, st, v, bal) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "could not pre compute attestations"), + Reason: Internal, + } + } + } else if st.Version() >= version.Altair { + v, bal, err = altair.InitializePrecomputeValidators(ctx, st) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "could not set up altair pre compute instance"), + Reason: Internal, + } + } + v, _, err = altair.ProcessEpochParticipation(ctx, st, bal, v) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "could not pre compute attestations"), + Reason: Internal, + } + } + } else { + return nil, &RpcError{ + Err: errors.Wrapf(err, "invalid state type retrieved with a version of %d", st.Version()), + Reason: Internal, + } + } + + for _, index := range filteredIndices { + if uint64(index) >= uint64(len(v)) { + votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ValidatorIndex: index}) + continue + } + val, err := st.ValidatorAtIndexReadOnly(index) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "could not retrieve validator"), + Reason: Internal, + } + } + pb := val.PublicKey() + votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ + Epoch: req.Epoch, + PublicKey: pb[:], + ValidatorIndex: index, + IsSlashed: v[index].IsSlashed, + IsWithdrawableInCurrentEpoch: v[index].IsWithdrawableCurrentEpoch, + IsActiveInCurrentEpoch: v[index].IsActiveCurrentEpoch, + IsActiveInPreviousEpoch: v[index].IsActivePrevEpoch, + IsCurrentEpochAttester: v[index].IsCurrentEpochAttester, + IsCurrentEpochTargetAttester: v[index].IsCurrentEpochTargetAttester, + IsPreviousEpochAttester: v[index].IsPrevEpochAttester, + IsPreviousEpochTargetAttester: v[index].IsPrevEpochTargetAttester, + IsPreviousEpochHeadAttester: v[index].IsPrevEpochHeadAttester, + CurrentEpochEffectiveBalanceGwei: v[index].CurrentEpochEffectiveBalance, + InclusionSlot: v[index].InclusionSlot, + InclusionDistance: v[index].InclusionDistance, + InactivityScore: v[index].InactivityScore, + }) + } + + return ðpb.IndividualVotesRespond{ + IndividualVotes: votes, + }, nil +} + // SubmitSignedContributionAndProof is called by a sync committee aggregator // to submit signed contribution and proof object. func (s *Service) SubmitSignedContributionAndProof( diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index f603f75e45d4..c8a15a3b4e09 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -68,7 +68,7 @@ func (s *Service) endpoints( endpoints = append(endpoints, s.configEndpoints()...) endpoints = append(endpoints, s.lightClientEndpoints(blocker, stater)...) endpoints = append(endpoints, s.eventsEndpoints()...) - endpoints = append(endpoints, s.prysmBeaconEndpoints(ch, stater)...) + endpoints = append(endpoints, s.prysmBeaconEndpoints(ch, stater, coreService)...) endpoints = append(endpoints, s.prysmNodeEndpoints()...) endpoints = append(endpoints, s.prysmValidatorEndpoints(coreService)...) if enableDebug { @@ -926,8 +926,11 @@ func (s *Service) eventsEndpoints() []endpoint { } // Prysm custom endpoints - -func (s *Service) prysmBeaconEndpoints(ch *stategen.CanonicalHistory, stater lookup.Stater) []endpoint { +func (s *Service) prysmBeaconEndpoints( + ch *stategen.CanonicalHistory, + stater lookup.Stater, + coreService *core.Service, +) []endpoint { server := &beaconprysm.Server{ SyncChecker: s.cfg.SyncService, HeadFetcher: s.cfg.HeadFetcher, @@ -938,6 +941,7 @@ func (s *Service) prysmBeaconEndpoints(ch *stategen.CanonicalHistory, stater loo Stater: stater, ChainInfoFetcher: s.cfg.ChainInfoFetcher, FinalizationFetcher: s.cfg.FinalizationFetcher, + CoreService: coreService, } const namespace = "prysm.beacon" @@ -969,6 +973,16 @@ func (s *Service) prysmBeaconEndpoints(ch *stategen.CanonicalHistory, stater loo handler: server.GetValidatorCount, methods: []string{http.MethodGet}, }, + { + template: "/prysm/v1/beacon/individual_votes", + name: namespace + ".GetIndividualVotes", + middleware: []mux.MiddlewareFunc{ + middleware.ContentTypeHandler([]string{api.JsonMediaType}), + middleware.AcceptHeaderHandler([]string{api.JsonMediaType}), + }, + handler: server.GetIndividualVotes, + methods: []string{http.MethodPost}, + }, } } diff --git a/beacon-chain/rpc/endpoints_test.go b/beacon-chain/rpc/endpoints_test.go index aa9b0f069f6d..e3d8d753b114 100644 --- a/beacon-chain/rpc/endpoints_test.go +++ b/beacon-chain/rpc/endpoints_test.go @@ -44,6 +44,7 @@ func Test_endpoints(t *testing.T) { "/eth/v1/beacon/pool/sync_committees": {http.MethodPost}, "/eth/v1/beacon/pool/voluntary_exits": {http.MethodGet, http.MethodPost}, "/eth/v1/beacon/pool/bls_to_execution_changes": {http.MethodGet, http.MethodPost}, + "/prysm/v1/beacon/individual_votes": {http.MethodPost}, } lightClientRoutes := map[string][]string{ diff --git a/beacon-chain/rpc/prysm/beacon/BUILD.bazel b/beacon-chain/rpc/prysm/beacon/BUILD.bazel index 54c4a6b9223c..0dd081b0a8f8 100644 --- a/beacon-chain/rpc/prysm/beacon/BUILD.bazel +++ b/beacon-chain/rpc/prysm/beacon/BUILD.bazel @@ -14,6 +14,7 @@ go_library( "//beacon-chain/blockchain:go_default_library", "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/db:go_default_library", + "//beacon-chain/rpc/core:go_default_library", "//beacon-chain/rpc/eth/helpers:go_default_library", "//beacon-chain/rpc/eth/shared:go_default_library", "//beacon-chain/rpc/lookup:go_default_library", @@ -29,26 +30,40 @@ go_library( "//time/slots:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_gorilla_mux//:go_default_library", + "@com_github_pkg_errors//:go_default_library", "@io_opencensus_go//trace:go_default_library", ], ) go_test( name = "go_default_test", - srcs = ["validator_count_test.go"], + srcs = [ + "handlers_test.go", + "validator_count_test.go", + ], embed = [":go_default_library"], deps = [ "//api/server/structs:go_default_library", "//beacon-chain/blockchain/testing:go_default_library", + "//beacon-chain/core/helpers:go_default_library", + "//beacon-chain/db/testing:go_default_library", + "//beacon-chain/forkchoice/doubly-linked-tree:go_default_library", + "//beacon-chain/rpc/core:go_default_library", "//beacon-chain/rpc/lookup:go_default_library", "//beacon-chain/rpc/testutil:go_default_library", "//beacon-chain/state:go_default_library", + "//beacon-chain/state/stategen:go_default_library", + "//beacon-chain/state/stategen/mock:go_default_library", "//config/params:go_default_library", "//consensus-types/primitives:go_default_library", "//network/httputil:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", + "//time/slots:go_default_library", + "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_gorilla_mux//:go_default_library", + "@com_github_prysmaticlabs_go_bitfield//:go_default_library", ], ) diff --git a/beacon-chain/rpc/prysm/beacon/handlers.go b/beacon-chain/rpc/prysm/beacon/handlers.go index dc60043c7d6f..eca22946a95f 100644 --- a/beacon-chain/rpc/prysm/beacon/handlers.go +++ b/beacon-chain/rpc/prysm/beacon/handlers.go @@ -1,17 +1,23 @@ package beacon import ( + "encoding/json" "fmt" + "io" "log" "net/http" "strconv" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/api/server/structs" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared" "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/network/httputil" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/time/slots" "go.opencensus.io/trace" ) @@ -69,3 +75,82 @@ func (s *Server) GetWeakSubjectivity(w http.ResponseWriter, r *http.Request) { } httputil.WriteJson(w, resp) } + +// GetIndividualVotes returns a list of validators individual vote status of a given epoch. +func (s *Server) GetIndividualVotes(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "validator.GetIndividualVotes") + defer span.End() + + var req structs.GetIndividualVotesRequest + err := json.NewDecoder(r.Body).Decode(&req) + switch { + case errors.Is(err, io.EOF): + httputil.HandleError(w, "No data submitted", http.StatusBadRequest) + return + case err != nil: + httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest) + return + } + + publicKeyBytes := make([][]byte, len(req.PublicKeys)) + for i, s := range req.PublicKeys { + bs, err := hexutil.Decode(s) + if err != nil { + httputil.HandleError(w, "could not decode public keys: "+err.Error(), http.StatusBadRequest) + return + } + publicKeyBytes[i] = bs + } + epoch, err := strconv.ParseUint(req.Epoch, 10, 64) + if err != nil { + httputil.HandleError(w, "invalid epoch: "+err.Error(), http.StatusBadRequest) + return + } + var indices []primitives.ValidatorIndex + for _, i := range req.Indices { + u, err := strconv.ParseUint(i, 10, 64) + if err != nil { + httputil.HandleError(w, "invalid indices: "+err.Error(), http.StatusBadRequest) + return + } + indices = append(indices, primitives.ValidatorIndex(u)) + } + votes, rpcError := s.CoreService.IndividualVotes( + ctx, + ðpb.IndividualVotesRequest{ + Epoch: primitives.Epoch(epoch), + PublicKeys: publicKeyBytes, + Indices: indices, + }, + ) + + if rpcError != nil { + httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason)) + return + } + v := make([]*structs.IndividualVote, 0, len(votes.IndividualVotes)) + for _, vote := range votes.IndividualVotes { + v = append(v, &structs.IndividualVote{ + Epoch: fmt.Sprintf("%d", vote.Epoch), + PublicKey: hexutil.Encode(vote.PublicKey), + ValidatorIndex: fmt.Sprintf("%d", vote.ValidatorIndex), + IsSlashed: vote.IsSlashed, + IsWithdrawableInCurrentEpoch: vote.IsWithdrawableInCurrentEpoch, + IsActiveInCurrentEpoch: vote.IsActiveInCurrentEpoch, + IsActiveInPreviousEpoch: vote.IsActiveInPreviousEpoch, + IsCurrentEpochAttester: vote.IsCurrentEpochAttester, + IsCurrentEpochTargetAttester: vote.IsCurrentEpochTargetAttester, + IsPreviousEpochAttester: vote.IsPreviousEpochAttester, + IsPreviousEpochTargetAttester: vote.IsPreviousEpochTargetAttester, + IsPreviousEpochHeadAttester: vote.IsPreviousEpochHeadAttester, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", vote.CurrentEpochEffectiveBalanceGwei), + InclusionSlot: fmt.Sprintf("%d", vote.InclusionSlot), + InclusionDistance: fmt.Sprintf("%d", vote.InclusionDistance), + InactivityScore: fmt.Sprintf("%d", vote.InactivityScore), + }) + } + response := &structs.GetIndividualVotesResponse{ + IndividualVotes: v, + } + httputil.WriteJson(w, response) +} diff --git a/beacon-chain/rpc/prysm/beacon/handlers_test.go b/beacon-chain/rpc/prysm/beacon/handlers_test.go new file mode 100644 index 000000000000..521126548fce --- /dev/null +++ b/beacon-chain/rpc/prysm/beacon/handlers_test.go @@ -0,0 +1,660 @@ +package beacon + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "math" + "net/http" + "net/http/httptest" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/prysmaticlabs/go-bitfield" + "github.com/prysmaticlabs/prysm/v5/api/server/structs" + chainMock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + dbTest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing" + doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen" + mockstategen "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen/mock" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/assert" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" + "github.com/prysmaticlabs/prysm/v5/time/slots" +) + +func individualVotesHelper(t *testing.T, request *structs.GetIndividualVotesRequest, s *Server) (string, *structs.GetIndividualVotesResponse) { + var buf bytes.Buffer + err := json.NewEncoder(&buf).Encode(request) + require.NoError(t, err) + + srv := httptest.NewServer(http.HandlerFunc(s.GetIndividualVotes)) + defer srv.Close() + req := httptest.NewRequest( + http.MethodGet, + "http://example.com/eth/v1/beacon/individual_votes", + &buf, + ) + client := &http.Client{} + rawResp, err := client.Post(srv.URL, "application/json", req.Body) + require.NoError(t, err) + defer func() { + if err := rawResp.Body.Close(); err != nil { + t.Fatal(err) + } + }() + body, err := io.ReadAll(rawResp.Body) + require.NoError(t, err) + type ErrorResponse struct { + Message string `json:"message"` + } + if rawResp.StatusCode != 200 { + var errorResponse ErrorResponse + err = json.Unmarshal(body, &errorResponse) + require.NoError(t, err) + return errorResponse.Message, &structs.GetIndividualVotesResponse{} + } + var votes *structs.GetIndividualVotesResponse + err = json.Unmarshal(body, &votes) + require.NoError(t, err) + return "", votes +} + +func TestServer_GetIndividualVotes_RequestFutureSlot(t *testing.T) { + s := &Server{ + CoreService: &core.Service{ + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + request := &structs.GetIndividualVotesRequest{ + Epoch: fmt.Sprintf("%d", slots.ToEpoch(s.CoreService.GenesisTimeFetcher.CurrentSlot())+1), + } + errorResp, _ := individualVotesHelper(t, request, s) + require.StringContains(t, "cannot retrieve information about an epoch in the future", errorResp) +} + +func addDefaultReplayerBuilder(s *Server, h stategen.HistoryAccessor) { + cc := &mockstategen.CanonicalChecker{Is: true, Err: nil} + cs := &mockstategen.CurrentSlotter{Slot: math.MaxUint64 - 1} + s.CoreService.ReplayerBuilder = stategen.NewCanonicalHistory(h, cc, cs) +} + +func TestServer_GetIndividualVotes_ValidatorsDontExist(t *testing.T) { + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + var slot primitives.Slot = 0 + validators := uint64(64) + stateWithValidators, _ := util.DeterministicGenesisState(t, validators) + beaconState, err := util.NewBeaconState() + require.NoError(t, err) + require.NoError(t, beaconState.SetValidators(stateWithValidators.Validators())) + require.NoError(t, beaconState.SetSlot(slot)) + + b := util.NewBeaconBlock() + b.Block.Slot = slot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + // Test non exist public key. + request := &structs.GetIndividualVotesRequest{ + PublicKeys: []string{"0xaa"}, + Epoch: "0", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "0", + PublicKey: "0xaa", + ValidatorIndex: fmt.Sprintf("%d", ^uint64(0)), + CurrentEpochEffectiveBalanceGwei: "0", + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") + + // Test non-existent validator index. + request = &structs.GetIndividualVotesRequest{ + Indices: []string{"100"}, + Epoch: "0", + } + errStr, resp = individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want = &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "0", + PublicKey: "0x", + ValidatorIndex: "100", + CurrentEpochEffectiveBalanceGwei: "0", + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") + + // Test both. + request = &structs.GetIndividualVotesRequest{ + PublicKeys: []string{"0xaa", "0xbb"}, + Indices: []string{"100", "101"}, + Epoch: "0", + } + errStr, resp = individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want = &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + {Epoch: "0", PublicKey: "0xaa", ValidatorIndex: fmt.Sprintf("%d", ^uint64(0)), CurrentEpochEffectiveBalanceGwei: "0", InclusionSlot: "0", InclusionDistance: "0", InactivityScore: "0"}, + { + Epoch: "0", + PublicKey: "0xbb", + ValidatorIndex: fmt.Sprintf("%d", ^uint64(0)), + CurrentEpochEffectiveBalanceGwei: "0", + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "0", + PublicKey: "0x", + ValidatorIndex: "100", + CurrentEpochEffectiveBalanceGwei: "0", + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "0", + PublicKey: "0x", + ValidatorIndex: "101", + CurrentEpochEffectiveBalanceGwei: "0", + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} + +func TestServer_GetIndividualVotes_Working(t *testing.T) { + helpers.ClearCache() + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + validators := uint64(32) + stateWithValidators, _ := util.DeterministicGenesisState(t, validators) + beaconState, err := util.NewBeaconState() + require.NoError(t, err) + require.NoError(t, beaconState.SetValidators(stateWithValidators.Validators())) + + bf := bitfield.NewBitlist(validators / uint64(params.BeaconConfig().SlotsPerEpoch)) + att1 := util.NewAttestation() + att1.AggregationBits = bf + att2 := util.NewAttestation() + att2.AggregationBits = bf + rt := [32]byte{'A'} + att1.Data.Target.Root = rt[:] + att1.Data.BeaconBlockRoot = rt[:] + br := beaconState.BlockRoots() + newRt := [32]byte{'B'} + br[0] = newRt[:] + require.NoError(t, beaconState.SetBlockRoots(br)) + att2.Data.Target.Root = rt[:] + att2.Data.BeaconBlockRoot = newRt[:] + err = beaconState.AppendPreviousEpochAttestations(ð.PendingAttestation{ + Data: att1.Data, AggregationBits: bf, InclusionDelay: 1, + }) + require.NoError(t, err) + err = beaconState.AppendCurrentEpochAttestations(ð.PendingAttestation{ + Data: att2.Data, AggregationBits: bf, InclusionDelay: 1, + }) + require.NoError(t, err) + + b := util.NewBeaconBlock() + b.Block.Slot = 0 + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + request := &structs.GetIndividualVotesRequest{ + Indices: []string{"0", "1"}, + Epoch: "0", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "0", + ValidatorIndex: "0", + PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot), + InclusionDistance: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot), + InactivityScore: "0", + }, + { + Epoch: "0", + ValidatorIndex: "1", + PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot), + InclusionDistance: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot), + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} + +func TestServer_GetIndividualVotes_WorkingAltair(t *testing.T) { + helpers.ClearCache() + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + var slot primitives.Slot = 0 + validators := uint64(32) + beaconState, _ := util.DeterministicGenesisStateAltair(t, validators) + require.NoError(t, beaconState.SetSlot(slot)) + + pb, err := beaconState.CurrentEpochParticipation() + require.NoError(t, err) + for i := range pb { + pb[i] = 0xff + } + require.NoError(t, beaconState.SetCurrentParticipationBits(pb)) + require.NoError(t, beaconState.SetPreviousParticipationBits(pb)) + + b := util.NewBeaconBlock() + b.Block.Slot = slot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + request := &structs.GetIndividualVotesRequest{ + Indices: []string{"0", "1"}, + Epoch: "0", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "0", + ValidatorIndex: "0", + PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "0", + ValidatorIndex: "1", + PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} + +func TestServer_GetIndividualVotes_AltairEndOfEpoch(t *testing.T) { + helpers.ClearCache() + params.SetupTestConfigCleanup(t) + params.OverrideBeaconConfig(params.BeaconConfig()) + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + validators := uint64(32) + beaconState, _ := util.DeterministicGenesisStateAltair(t, validators) + startSlot, err := slots.EpochStart(1) + assert.NoError(t, err) + require.NoError(t, beaconState.SetSlot(startSlot)) + + b := util.NewBeaconBlock() + b.Block.Slot = startSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + // Save State at the end of the epoch: + endSlot, err := slots.EpochEnd(1) + assert.NoError(t, err) + + beaconState, _ = util.DeterministicGenesisStateAltair(t, validators) + require.NoError(t, beaconState.SetSlot(endSlot)) + + pb, err := beaconState.CurrentEpochParticipation() + require.NoError(t, err) + for i := range pb { + pb[i] = 0xff + } + require.NoError(t, beaconState.SetCurrentParticipationBits(pb)) + require.NoError(t, beaconState.SetPreviousParticipationBits(pb)) + + b.Block.Slot = endSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err = b.Block.HashTreeRoot() + require.NoError(t, err) + + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + request := &structs.GetIndividualVotesRequest{ + Indices: []string{"0", "1"}, + Epoch: "1", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "1", + ValidatorIndex: "0", + PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "1", + ValidatorIndex: "1", + PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} + +func TestServer_GetIndividualVotes_BellatrixEndOfEpoch(t *testing.T) { + helpers.ClearCache() + params.SetupTestConfigCleanup(t) + params.OverrideBeaconConfig(params.BeaconConfig()) + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + validators := uint64(32) + beaconState, _ := util.DeterministicGenesisStateBellatrix(t, validators) + startSlot, err := slots.EpochStart(1) + assert.NoError(t, err) + require.NoError(t, beaconState.SetSlot(startSlot)) + + b := util.NewBeaconBlock() + b.Block.Slot = startSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + // Save State at the end of the epoch: + endSlot, err := slots.EpochEnd(1) + assert.NoError(t, err) + + beaconState, _ = util.DeterministicGenesisStateBellatrix(t, validators) + require.NoError(t, beaconState.SetSlot(endSlot)) + + pb, err := beaconState.CurrentEpochParticipation() + require.NoError(t, err) + for i := range pb { + pb[i] = 0xff + } + require.NoError(t, beaconState.SetCurrentParticipationBits(pb)) + require.NoError(t, beaconState.SetPreviousParticipationBits(pb)) + + b.Block.Slot = endSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err = b.Block.HashTreeRoot() + require.NoError(t, err) + + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + request := &structs.GetIndividualVotesRequest{ + Indices: []string{"0", "1"}, + Epoch: "1", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "1", + ValidatorIndex: "0", + PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "1", + ValidatorIndex: "1", + PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} + +func TestServer_GetIndividualVotes_CapellaEndOfEpoch(t *testing.T) { + helpers.ClearCache() + params.SetupTestConfigCleanup(t) + params.OverrideBeaconConfig(params.BeaconConfig()) + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + validators := uint64(32) + beaconState, _ := util.DeterministicGenesisStateCapella(t, validators) + startSlot, err := slots.EpochStart(1) + assert.NoError(t, err) + require.NoError(t, beaconState.SetSlot(startSlot)) + + b := util.NewBeaconBlock() + b.Block.Slot = startSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + // Save State at the end of the epoch: + endSlot, err := slots.EpochEnd(1) + assert.NoError(t, err) + + beaconState, _ = util.DeterministicGenesisStateCapella(t, validators) + require.NoError(t, beaconState.SetSlot(endSlot)) + + pb, err := beaconState.CurrentEpochParticipation() + require.NoError(t, err) + for i := range pb { + pb[i] = 0xff + } + require.NoError(t, beaconState.SetCurrentParticipationBits(pb)) + require.NoError(t, beaconState.SetPreviousParticipationBits(pb)) + + b.Block.Slot = endSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err = b.Block.HashTreeRoot() + require.NoError(t, err) + + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + request := &structs.GetIndividualVotesRequest{ + Indices: []string{"0", "1"}, + Epoch: "1", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "1", + ValidatorIndex: "0", + PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "1", + ValidatorIndex: "1", + PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} diff --git a/beacon-chain/rpc/prysm/beacon/server.go b/beacon-chain/rpc/prysm/beacon/server.go index a7f4616ee089..5af654712f69 100644 --- a/beacon-chain/rpc/prysm/beacon/server.go +++ b/beacon-chain/rpc/prysm/beacon/server.go @@ -3,6 +3,7 @@ package beacon import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain" beacondb "github.com/prysmaticlabs/prysm/v5/beacon-chain/db" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen" "github.com/prysmaticlabs/prysm/v5/beacon-chain/sync" @@ -18,4 +19,5 @@ type Server struct { Stater lookup.Stater ChainInfoFetcher blockchain.ChainInfoFetcher FinalizationFetcher blockchain.FinalizationFetcher + CoreService *core.Service } diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go index 26ff052b4458..ed1965710557 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go @@ -16,7 +16,7 @@ import ( "google.golang.org/grpc/status" ) -const errEpoch = "Cannot retrieve information about an epoch in the future, current epoch %d, requesting %d" +const errEpoch = "cannot retrieve information about an epoch in the future, current epoch %d, requesting %d" // ListValidatorAssignments retrieves the validator assignments for a given epoch, // optional validator indices or public keys may be included to filter validator assignments. diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go index 7d2fa10c1dd5..be1beb965eda 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go @@ -79,6 +79,9 @@ func addDefaultReplayerBuilder(s *Server, h stategen.HistoryAccessor) { cc := &mockstategen.CanonicalChecker{Is: true, Err: nil} cs := &mockstategen.CurrentSlotter{Slot: math.MaxUint64 - 1} s.ReplayerBuilder = stategen.NewCanonicalHistory(h, cc, cs) + if s.CoreService != nil { + s.CoreService.ReplayerBuilder = stategen.NewCanonicalHistory(h, cc, cs) + } } func TestServer_ListBeaconCommittees_PreviousEpoch(t *testing.T) { diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/validators.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/validators.go index efad2fa89da6..4b4e59b04802 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/validators.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/validators.go @@ -672,105 +672,11 @@ func (bs *Server) GetIndividualVotes( ctx context.Context, req *ethpb.IndividualVotesRequest, ) (*ethpb.IndividualVotesRespond, error) { - currentEpoch := slots.ToEpoch(bs.GenesisTimeFetcher.CurrentSlot()) - if req.Epoch > currentEpoch { - return nil, status.Errorf( - codes.InvalidArgument, - errEpoch, - currentEpoch, - req.Epoch, - ) - } - - s, err := slots.EpochEnd(req.Epoch) - if err != nil { - return nil, err - } - st, err := bs.ReplayerBuilder.ReplayerForSlot(s).ReplayBlocks(ctx) + response, err := bs.CoreService.IndividualVotes(ctx, req) if err != nil { - return nil, status.Errorf(codes.Internal, "failed to replay blocks for state at epoch %d: %v", req.Epoch, err) - } - // Track filtered validators to prevent duplication in the response. - filtered := map[primitives.ValidatorIndex]bool{} - filteredIndices := make([]primitives.ValidatorIndex, 0) - votes := make([]*ethpb.IndividualVotesRespond_IndividualVote, 0, len(req.Indices)+len(req.PublicKeys)) - // Filter out assignments by public keys. - for _, pubKey := range req.PublicKeys { - index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey)) - if !ok { - votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{PublicKey: pubKey, ValidatorIndex: primitives.ValidatorIndex(^uint64(0))}) - continue - } - filtered[index] = true - filteredIndices = append(filteredIndices, index) - } - // Filter out assignments by validator indices. - for _, index := range req.Indices { - if !filtered[index] { - filteredIndices = append(filteredIndices, index) - } - } - sort.Slice(filteredIndices, func(i, j int) bool { - return filteredIndices[i] < filteredIndices[j] - }) - - var v []*precompute.Validator - var bal *precompute.Balance - if st.Version() == version.Phase0 { - v, bal, err = precompute.New(ctx, st) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not set up pre compute instance: %v", err) - } - v, _, err = precompute.ProcessAttestations(ctx, st, v, bal) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err) - } - } else if st.Version() >= version.Altair { - v, bal, err = altair.InitializePrecomputeValidators(ctx, st) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not set up altair pre compute instance: %v", err) - } - v, _, err = altair.ProcessEpochParticipation(ctx, st, bal, v) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err) - } - } else { - return nil, status.Errorf(codes.Internal, "Invalid state type retrieved with a version of %d", st.Version()) - } - - for _, index := range filteredIndices { - if uint64(index) >= uint64(len(v)) { - votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ValidatorIndex: index}) - continue - } - val, err := st.ValidatorAtIndexReadOnly(index) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not retrieve validator: %v", err) - } - pb := val.PublicKey() - votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ - Epoch: req.Epoch, - PublicKey: pb[:], - ValidatorIndex: index, - IsSlashed: v[index].IsSlashed, - IsWithdrawableInCurrentEpoch: v[index].IsWithdrawableCurrentEpoch, - IsActiveInCurrentEpoch: v[index].IsActiveCurrentEpoch, - IsActiveInPreviousEpoch: v[index].IsActivePrevEpoch, - IsCurrentEpochAttester: v[index].IsCurrentEpochAttester, - IsCurrentEpochTargetAttester: v[index].IsCurrentEpochTargetAttester, - IsPreviousEpochAttester: v[index].IsPrevEpochAttester, - IsPreviousEpochTargetAttester: v[index].IsPrevEpochTargetAttester, - IsPreviousEpochHeadAttester: v[index].IsPrevEpochHeadAttester, - CurrentEpochEffectiveBalanceGwei: v[index].CurrentEpochEffectiveBalance, - InclusionSlot: v[index].InclusionSlot, - InclusionDistance: v[index].InclusionDistance, - InactivityScore: v[index].InactivityScore, - }) + return nil, status.Errorf(core.ErrorReasonToGRPC(err.Reason), "Could not retrieve individual votes: %v", err.Err) } - - return ðpb.IndividualVotesRespond{ - IndividualVotes: votes, - }, nil + return response, nil } // Determines whether a validator has already exited. diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go index 56a6a3f3e516..83abeb5ae372 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go @@ -44,7 +44,7 @@ import ( ) const ( - errNoEpochInfoError = "Cannot retrieve information about an epoch in the future" + errNoEpochInfoError = "cannot retrieve information about an epoch in the future" ) func TestServer_GetValidatorActiveSetChanges_CannotRequestFutureEpoch(t *testing.T) { @@ -2258,12 +2258,17 @@ func setupValidators(t testing.TB, _ db.Database, count int) ([]*ethpb.Validator } func TestServer_GetIndividualVotes_RequestFutureSlot(t *testing.T) { - ds := &Server{GenesisTimeFetcher: &mock.ChainService{}} + bs := &Server{ + CoreService: &core.Service{ + GenesisTimeFetcher: &mock.ChainService{}, + }, + } + req := ðpb.IndividualVotesRequest{ - Epoch: slots.ToEpoch(ds.GenesisTimeFetcher.CurrentSlot()) + 1, + Epoch: slots.ToEpoch(bs.CoreService.GenesisTimeFetcher.CurrentSlot()) + 1, } wanted := errNoEpochInfoError - _, err := ds.GetIndividualVotes(context.Background(), req) + _, err := bs.GetIndividualVotes(context.Background(), req) assert.ErrorContains(t, wanted, err) } @@ -2292,8 +2297,10 @@ func TestServer_GetIndividualVotes_ValidatorsDontExist(t *testing.T) { require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB) @@ -2388,8 +2395,10 @@ func TestServer_GetIndividualVotes_Working(t *testing.T) { require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB) @@ -2451,8 +2460,10 @@ func TestServer_GetIndividualVotes_WorkingAltair(t *testing.T) { require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB) @@ -2537,8 +2548,10 @@ func TestServer_GetIndividualVotes_AltairEndOfEpoch(t *testing.T) { require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB) @@ -2625,8 +2638,10 @@ func TestServer_GetIndividualVotes_BellatrixEndOfEpoch(t *testing.T) { require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB) @@ -2713,8 +2728,10 @@ func TestServer_GetIndividualVotes_CapellaEndOfEpoch(t *testing.T) { require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB) From 49055acf811fa8792640389fefb7897d61a4fbdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kapka?= Date: Fri, 19 Jul 2024 16:08:39 +0200 Subject: [PATCH 16/17] EIP-7549: Attestation packing (#14238) * EIP-7549: Attestation packing * new files * change var name * test fixes * enhance comment * unit test for Deneb state --- .../attestations/prepare_forkchoice.go | 6 +- .../rpc/prysm/v1alpha1/validator/BUILD.bazel | 3 + .../validator/proposer_attestations.go | 32 +++- .../proposer_attestations_electra.go | 98 +++++++++++ .../proposer_attestations_electra_test.go | 163 ++++++++++++++++++ .../validator/proposer_attestations_test.go | 20 ++- 6 files changed, 313 insertions(+), 9 deletions(-) create mode 100644 beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_electra.go create mode 100644 beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_electra_test.go diff --git a/beacon-chain/operations/attestations/prepare_forkchoice.go b/beacon-chain/operations/attestations/prepare_forkchoice.go index 34a561066ac2..38b69b9583dc 100644 --- a/beacon-chain/operations/attestations/prepare_forkchoice.go +++ b/beacon-chain/operations/attestations/prepare_forkchoice.go @@ -67,7 +67,7 @@ func (s *Service) batchForkChoiceAtts(ctx context.Context) error { atts := append(s.cfg.Pool.AggregatedAttestations(), s.cfg.Pool.BlockAttestations()...) atts = append(atts, s.cfg.Pool.ForkchoiceAttestations()...) - attsByVerAndDataRoot := make(map[attestation.Id][]ethpb.Att, len(atts)) + attsById := make(map[attestation.Id][]ethpb.Att, len(atts)) // Consolidate attestations by aggregating them by similar data root. for _, att := range atts { @@ -83,10 +83,10 @@ func (s *Service) batchForkChoiceAtts(ctx context.Context) error { if err != nil { return errors.Wrap(err, "could not create attestation ID") } - attsByVerAndDataRoot[id] = append(attsByVerAndDataRoot[id], att) + attsById[id] = append(attsById[id], att) } - for _, atts := range attsByVerAndDataRoot { + for _, atts := range attsById { if err := s.aggregateAndSaveForkChoiceAtts(atts); err != nil { return err } diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel b/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel index f8acf2305e1c..34ef244e1977 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel @@ -13,6 +13,7 @@ go_library( "proposer.go", "proposer_altair.go", "proposer_attestations.go", + "proposer_attestations_electra.go", "proposer_bellatrix.go", "proposer_builder.go", "proposer_capella.go", @@ -147,6 +148,7 @@ common_deps = [ "//consensus-types/primitives:go_default_library", "//container/trie:go_default_library", "//crypto/bls:go_default_library", + "//crypto/bls/blst:go_default_library", "//encoding/bytesutil:go_default_library", "//encoding/ssz:go_default_library", "//proto/engine/v1:go_default_library", @@ -186,6 +188,7 @@ go_test( "duties_test.go", "exit_test.go", "proposer_altair_test.go", + "proposer_attestations_electra_test.go", "proposer_attestations_test.go", "proposer_bellatrix_test.go", "proposer_builder_test.go", diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations.go index c6138e211361..7ae8e3662c8b 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations.go @@ -42,6 +42,8 @@ func (vs *Server) packAttestations(ctx context.Context, latestState state.Beacon } atts = append(atts, uAtts...) + // Checking the state's version here will give the wrong result if the last slot of Deneb is missed. + // The head state will still be in Deneb while we are trying to build an Electra block. postElectra := slots.ToEpoch(blkSlot) >= params.BeaconConfig().ElectraForkEpoch versionAtts := make([]ethpb.Att, 0, len(atts)) @@ -66,23 +68,43 @@ func (vs *Server) packAttestations(ctx context.Context, latestState state.Beacon return nil, err } - attsByDataRoot := make(map[attestation.Id][]ethpb.Att, len(versionAtts)) + attsById := make(map[attestation.Id][]ethpb.Att, len(versionAtts)) for _, att := range versionAtts { id, err := attestation.NewId(att, attestation.Data) if err != nil { return nil, errors.Wrap(err, "could not create attestation ID") } - attsByDataRoot[id] = append(attsByDataRoot[id], att) + attsById[id] = append(attsById[id], att) } - attsForInclusion := proposerAtts(make([]ethpb.Att, 0)) - for _, as := range attsByDataRoot { + for id, as := range attsById { as, err := attaggregation.Aggregate(as) if err != nil { return nil, err } - attsForInclusion = append(attsForInclusion, as...) + attsById[id] = as + } + + var attsForInclusion proposerAtts + if postElectra { + // TODO: hack for Electra devnet-1, take only one aggregate per ID + // (which essentially means one aggregate for an attestation_data+committee combination + topAggregates := make([]ethpb.Att, 0) + for _, v := range attsById { + topAggregates = append(topAggregates, v[0]) + } + + attsForInclusion, err = computeOnChainAggregate(topAggregates) + if err != nil { + return nil, err + } + } else { + attsForInclusion = make([]ethpb.Att, 0) + for _, as := range attsById { + attsForInclusion = append(attsForInclusion, as...) + } } + deduped, err := attsForInclusion.dedup() if err != nil { return nil, err diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_electra.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_electra.go new file mode 100644 index 000000000000..e15df73bcaa4 --- /dev/null +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_electra.go @@ -0,0 +1,98 @@ +package validator + +import ( + "slices" + + "github.com/prysmaticlabs/go-bitfield" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/crypto/bls" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" +) + +// computeOnChainAggregate constructs a final aggregate form a list of network aggregates with equal attestation data. +// It assumes that each network aggregate has exactly one committee bit set. +// +// Spec definition: +// +// def compute_on_chain_aggregate(network_aggregates: Sequence[Attestation]) -> Attestation: +// aggregates = sorted(network_aggregates, key=lambda a: get_committee_indices(a.committee_bits)[0]) +// +// data = aggregates[0].data +// aggregation_bits = Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]() +// for a in aggregates: +// for b in a.aggregation_bits: +// aggregation_bits.append(b) +// +// signature = bls.Aggregate([a.signature for a in aggregates]) +// +// committee_indices = [get_committee_indices(a.committee_bits)[0] for a in aggregates] +// committee_flags = [(index in committee_indices) for index in range(0, MAX_COMMITTEES_PER_SLOT)] +// committee_bits = Bitvector[MAX_COMMITTEES_PER_SLOT](committee_flags) +// +// return Attestation( +// aggregation_bits=aggregation_bits, +// data=data, +// committee_bits=committee_bits, +// signature=signature, +// ) +func computeOnChainAggregate(aggregates []ethpb.Att) ([]ethpb.Att, error) { + aggsByDataRoot := make(map[[32]byte][]ethpb.Att) + for _, agg := range aggregates { + key, err := agg.GetData().HashTreeRoot() + if err != nil { + return nil, err + } + existing, ok := aggsByDataRoot[key] + if ok { + aggsByDataRoot[key] = append(existing, agg) + } else { + aggsByDataRoot[key] = []ethpb.Att{agg} + } + } + + result := make([]ethpb.Att, 0) + + for _, aggs := range aggsByDataRoot { + slices.SortFunc(aggs, func(a, b ethpb.Att) int { + return a.CommitteeBitsVal().BitIndices()[0] - b.CommitteeBitsVal().BitIndices()[0] + }) + + sigs := make([]bls.Signature, len(aggs)) + committeeIndices := make([]primitives.CommitteeIndex, len(aggs)) + aggBitsIndices := make([]uint64, 0) + aggBitsOffset := uint64(0) + var err error + for i, a := range aggs { + for _, bi := range a.GetAggregationBits().BitIndices() { + aggBitsIndices = append(aggBitsIndices, uint64(bi)+aggBitsOffset) + } + sigs[i], err = bls.SignatureFromBytes(a.GetSignature()) + if err != nil { + return nil, err + } + committeeIndices[i] = helpers.CommitteeIndices(a.CommitteeBitsVal())[0] + + aggBitsOffset += a.GetAggregationBits().Len() + } + + aggregationBits := bitfield.NewBitlist(aggBitsOffset) + for _, bi := range aggBitsIndices { + aggregationBits.SetBitAt(bi, true) + } + + cb := primitives.NewAttestationCommitteeBits() + att := ðpb.AttestationElectra{ + AggregationBits: aggregationBits, + Data: aggs[0].GetData(), + CommitteeBits: cb, + Signature: bls.AggregateSignatures(sigs).Marshal(), + } + for _, ci := range committeeIndices { + att.CommitteeBits.SetBitAt(uint64(ci), true) + } + result = append(result, att) + } + + return result, nil +} diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_electra_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_electra_test.go new file mode 100644 index 000000000000..b28dca4c296c --- /dev/null +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_electra_test.go @@ -0,0 +1,163 @@ +package validator + +import ( + "reflect" + "testing" + + "github.com/prysmaticlabs/go-bitfield" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/crypto/bls/blst" + "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/assert" + "github.com/prysmaticlabs/prysm/v5/testing/require" +) + +func Test_computeOnChainAggregate(t *testing.T) { + params.SetupTestConfigCleanup(t) + cfg := params.MainnetConfig().Copy() + cfg.MaxCommitteesPerSlot = 64 + params.OverrideBeaconConfig(cfg) + + key, err := blst.RandKey() + require.NoError(t, err) + sig := key.Sign([]byte{'X'}) + + data1 := ðpb.AttestationData{ + Slot: 123, + CommitteeIndex: 123, + BeaconBlockRoot: bytesutil.PadTo([]byte("root"), 32), + Source: ðpb.Checkpoint{ + Epoch: 123, + Root: bytesutil.PadTo([]byte("root"), 32), + }, + Target: ðpb.Checkpoint{ + Epoch: 123, + Root: bytesutil.PadTo([]byte("root"), 32), + }, + } + data2 := ðpb.AttestationData{ + Slot: 456, + CommitteeIndex: 456, + BeaconBlockRoot: bytesutil.PadTo([]byte("root"), 32), + Source: ðpb.Checkpoint{ + Epoch: 456, + Root: bytesutil.PadTo([]byte("root"), 32), + }, + Target: ðpb.Checkpoint{ + Epoch: 456, + Root: bytesutil.PadTo([]byte("root"), 32), + }, + } + + t.Run("single aggregate", func(t *testing.T) { + cb := primitives.NewAttestationCommitteeBits() + cb.SetBitAt(0, true) + att := ðpb.AttestationElectra{ + AggregationBits: bitfield.Bitlist{0b00011111}, + Data: data1, + CommitteeBits: cb, + Signature: sig.Marshal(), + } + result, err := computeOnChainAggregate([]ethpb.Att{att}) + require.NoError(t, err) + require.Equal(t, 1, len(result)) + assert.DeepEqual(t, att.AggregationBits, result[0].GetAggregationBits()) + assert.DeepEqual(t, att.Data, result[0].GetData()) + assert.DeepEqual(t, att.CommitteeBits, result[0].CommitteeBitsVal()) + }) + t.Run("all aggregates for one root", func(t *testing.T) { + cb := primitives.NewAttestationCommitteeBits() + cb.SetBitAt(0, true) + att1 := ðpb.AttestationElectra{ + AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1 + Data: data1, + CommitteeBits: cb, + Signature: sig.Marshal(), + } + cb = primitives.NewAttestationCommitteeBits() + cb.SetBitAt(1, true) + att2 := ðpb.AttestationElectra{ + AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1 + Data: data1, + CommitteeBits: cb, + Signature: sig.Marshal(), + } + result, err := computeOnChainAggregate([]ethpb.Att{att1, att2}) + require.NoError(t, err) + require.Equal(t, 1, len(result)) + assert.DeepEqual(t, bitfield.Bitlist{0b00110011, 0b00000001}, result[0].GetAggregationBits()) + assert.DeepEqual(t, data1, result[0].GetData()) + cb = primitives.NewAttestationCommitteeBits() + cb.SetBitAt(0, true) + cb.SetBitAt(1, true) + assert.DeepEqual(t, cb, result[0].CommitteeBitsVal()) + }) + t.Run("aggregates for multiple roots", func(t *testing.T) { + cb := primitives.NewAttestationCommitteeBits() + cb.SetBitAt(0, true) + att1 := ðpb.AttestationElectra{ + AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1 + Data: data1, + CommitteeBits: cb, + Signature: sig.Marshal(), + } + cb = primitives.NewAttestationCommitteeBits() + cb.SetBitAt(1, true) + att2 := ðpb.AttestationElectra{ + AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1 + Data: data1, + CommitteeBits: cb, + Signature: sig.Marshal(), + } + cb = primitives.NewAttestationCommitteeBits() + cb.SetBitAt(0, true) + att3 := ðpb.AttestationElectra{ + AggregationBits: bitfield.Bitlist{0b00011001}, // aggregation bits 0,3 + Data: data2, + CommitteeBits: cb, + Signature: sig.Marshal(), + } + cb = primitives.NewAttestationCommitteeBits() + cb.SetBitAt(1, true) + att4 := ðpb.AttestationElectra{ + AggregationBits: bitfield.Bitlist{0b00010010}, // aggregation bits 1 + Data: data2, + CommitteeBits: cb, + Signature: sig.Marshal(), + } + result, err := computeOnChainAggregate([]ethpb.Att{att1, att2, att3, att4}) + require.NoError(t, err) + require.Equal(t, 2, len(result)) + cb = primitives.NewAttestationCommitteeBits() + cb.SetBitAt(0, true) + cb.SetBitAt(1, true) + + expectedAggBits := bitfield.Bitlist{0b00110011, 0b00000001} + expectedData := data1 + found := false + for _, a := range result { + if reflect.DeepEqual(expectedAggBits, a.GetAggregationBits()) && reflect.DeepEqual(expectedData, a.GetData()) && reflect.DeepEqual(cb, a.CommitteeBitsVal()) { + found = true + break + } + } + if !found { + t.Error("Expected aggregate not found") + } + + expectedAggBits = bitfield.Bitlist{0b00101001, 0b00000001} + expectedData = data2 + found = false + for _, a := range result { + if reflect.DeepEqual(expectedAggBits, a.GetAggregationBits()) && reflect.DeepEqual(expectedData, a.GetData()) && reflect.DeepEqual(cb, a.CommitteeBitsVal()) { + found = true + break + } + } + if !found { + t.Error("Expected aggregate not found") + } + }) +} diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_test.go index fbbf62d7992d..84c0c9449af9 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_test.go @@ -10,6 +10,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/crypto/bls/blst" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" @@ -446,6 +447,9 @@ func Test_packAttestations(t *testing.T) { } cb := primitives.NewAttestationCommitteeBits() cb.SetBitAt(0, true) + key, err := blst.RandKey() + require.NoError(t, err) + sig := key.Sign([]byte{'X'}) electraAtt := ðpb.AttestationElectra{ AggregationBits: bitfield.Bitlist{0b11111}, CommitteeBits: cb, @@ -460,7 +464,7 @@ func Test_packAttestations(t *testing.T) { Root: make([]byte, 32), }, }, - Signature: make([]byte, 96), + Signature: sig.Marshal(), } pool := attestations.NewPool() require.NoError(t, pool.SaveAggregatedAttestations([]ethpb.Att{phase0Att, electraAtt})) @@ -484,6 +488,20 @@ func Test_packAttestations(t *testing.T) { st, _ := util.DeterministicGenesisStateElectra(t, 64) require.NoError(t, st.SetSlot(params.BeaconConfig().SlotsPerEpoch+1)) + atts, err := s.packAttestations(ctx, st, params.BeaconConfig().SlotsPerEpoch) + require.NoError(t, err) + require.Equal(t, 1, len(atts)) + assert.DeepEqual(t, electraAtt, atts[0]) + }) + t.Run("Electra block with Deneb state", func(t *testing.T) { + params.SetupTestConfigCleanup(t) + cfg := params.BeaconConfig().Copy() + cfg.ElectraForkEpoch = 1 + params.OverrideBeaconConfig(cfg) + + st, _ := util.DeterministicGenesisStateDeneb(t, 64) + require.NoError(t, st.SetSlot(params.BeaconConfig().SlotsPerEpoch+1)) + atts, err := s.packAttestations(ctx, st, params.BeaconConfig().SlotsPerEpoch) require.NoError(t, err) require.Equal(t, 1, len(atts)) From 8364226b689d4bb23a97988cc074fbe872b0aa42 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:07:30 -0500 Subject: [PATCH 17/17] payload electra cloning (#14239) * poc for payload electra cloning * partial fixes * fixing build * addressing kasey's comment * forgot to unexport interface * making test more generic * making fuzzing slightly more robust * renaming based on kasey's comment * using fuzz test in same package to avoid exporting interface * fixing build --- proto/engine/v1/BUILD.bazel | 6 +- proto/engine/v1/execution_engine.go | 92 +++++++++++++ proto/engine/v1/execution_engine_fuzz_test.go | 30 +++++ proto/engine/v1/export_test.go | 3 + proto/prysm/v1alpha1/cloners.go | 125 +++--------------- proto/prysm/v1alpha1/cloners_test.go | 47 ------- 6 files changed, 146 insertions(+), 157 deletions(-) create mode 100644 proto/engine/v1/execution_engine.go create mode 100644 proto/engine/v1/execution_engine_fuzz_test.go create mode 100644 proto/engine/v1/export_test.go diff --git a/proto/engine/v1/BUILD.bazel b/proto/engine/v1/BUILD.bazel index bc9138b82dc5..a7c1615015f8 100644 --- a/proto/engine/v1/BUILD.bazel +++ b/proto/engine/v1/BUILD.bazel @@ -75,6 +75,7 @@ go_proto_library( go_library( name = "go_default_library", srcs = [ + "execution_engine.go", "json_marshal_unmarshal.go", ":ssz_generated_files", # keep ], @@ -121,15 +122,18 @@ ssz_proto_files( go_test( name = "go_default_test", srcs = [ + "export_test.go", + "execution_engine_fuzz_test.go", "json_marshal_unmarshal_test.go", ], + embed = [":go_default_library"], deps = [ - ":go_default_library", "//config/fieldparams:go_default_library", "//config/params:go_default_library", "//consensus-types/primitives:go_default_library", "//encoding/bytesutil:go_default_library", "//testing/require:go_default_library", + "@com_github_google_gofuzz//:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_ethereum_go_ethereum//core/types:go_default_library", diff --git a/proto/engine/v1/execution_engine.go b/proto/engine/v1/execution_engine.go new file mode 100644 index 000000000000..fb03e21af6ea --- /dev/null +++ b/proto/engine/v1/execution_engine.go @@ -0,0 +1,92 @@ +package enginev1 + +import "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" + +type copier[T any] interface { + Copy() T +} + +func copySlice[T any, C copier[T]](original []C) []T { + // Create a new slice with the same length as the original + newSlice := make([]T, len(original)) + for i := 0; i < len(newSlice); i++ { + newSlice[i] = original[i].Copy() + } + return newSlice +} + +func (w *Withdrawal) Copy() *Withdrawal { + if w == nil { + return nil + } + + return &Withdrawal{ + Index: w.Index, + ValidatorIndex: w.ValidatorIndex, + Address: bytesutil.SafeCopyBytes(w.Address), + Amount: w.Amount, + } +} + +func (d *DepositRequest) Copy() *DepositRequest { + if d == nil { + return nil + } + return &DepositRequest{ + Pubkey: bytesutil.SafeCopyBytes(d.Pubkey), + WithdrawalCredentials: bytesutil.SafeCopyBytes(d.WithdrawalCredentials), + Amount: d.Amount, + Signature: bytesutil.SafeCopyBytes(d.Signature), + Index: d.Index, + } +} + +func (wr *WithdrawalRequest) Copy() *WithdrawalRequest { + if wr == nil { + return nil + } + return &WithdrawalRequest{ + SourceAddress: bytesutil.SafeCopyBytes(wr.SourceAddress), + ValidatorPubkey: bytesutil.SafeCopyBytes(wr.ValidatorPubkey), + Amount: wr.Amount, + } +} + +func (cr *ConsolidationRequest) Copy() *ConsolidationRequest { + if cr == nil { + return nil + } + return &ConsolidationRequest{ + SourceAddress: bytesutil.SafeCopyBytes(cr.SourceAddress), + SourcePubkey: bytesutil.SafeCopyBytes(cr.SourcePubkey), + TargetPubkey: bytesutil.SafeCopyBytes(cr.TargetPubkey), + } +} + +func (payload *ExecutionPayloadElectra) Copy() *ExecutionPayloadElectra { + if payload == nil { + return nil + } + return &ExecutionPayloadElectra{ + ParentHash: bytesutil.SafeCopyBytes(payload.ParentHash), + FeeRecipient: bytesutil.SafeCopyBytes(payload.FeeRecipient), + StateRoot: bytesutil.SafeCopyBytes(payload.StateRoot), + ReceiptsRoot: bytesutil.SafeCopyBytes(payload.ReceiptsRoot), + LogsBloom: bytesutil.SafeCopyBytes(payload.LogsBloom), + PrevRandao: bytesutil.SafeCopyBytes(payload.PrevRandao), + BlockNumber: payload.BlockNumber, + GasLimit: payload.GasLimit, + GasUsed: payload.GasUsed, + Timestamp: payload.Timestamp, + ExtraData: bytesutil.SafeCopyBytes(payload.ExtraData), + BaseFeePerGas: bytesutil.SafeCopyBytes(payload.BaseFeePerGas), + BlockHash: bytesutil.SafeCopyBytes(payload.BlockHash), + Transactions: bytesutil.SafeCopy2dBytes(payload.Transactions), + Withdrawals: copySlice(payload.Withdrawals), + BlobGasUsed: payload.BlobGasUsed, + ExcessBlobGas: payload.ExcessBlobGas, + DepositRequests: copySlice(payload.DepositRequests), + WithdrawalRequests: copySlice(payload.WithdrawalRequests), + ConsolidationRequests: copySlice(payload.ConsolidationRequests), + } +} diff --git a/proto/engine/v1/execution_engine_fuzz_test.go b/proto/engine/v1/execution_engine_fuzz_test.go new file mode 100644 index 000000000000..28d4fb9fa964 --- /dev/null +++ b/proto/engine/v1/execution_engine_fuzz_test.go @@ -0,0 +1,30 @@ +package enginev1_test + +import ( + "fmt" + "testing" + + fuzz "github.com/google/gofuzz" + enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" + "github.com/prysmaticlabs/prysm/v5/testing/require" +) + +func TestCopyExecutionPayload_Fuzz(t *testing.T) { + fuzzCopies(t, &enginev1.ExecutionPayloadElectra{}) +} + +func fuzzCopies[T any, C enginev1.Copier[T]](t *testing.T, obj C) { + fuzzer := fuzz.NewWithSeed(0) + amount := 1000 + t.Run(fmt.Sprintf("%T", obj), func(t *testing.T) { + for i := 0; i < amount; i++ { + fuzzer.Fuzz(obj) // Populate thing with random values + got := obj.Copy() + require.DeepEqual(t, obj, got) + // check shallow copy working + fuzzer.Fuzz(got) + require.DeepNotEqual(t, obj, got) + // TODO: think of deeper not equal fuzzing + } + }) +} diff --git a/proto/engine/v1/export_test.go b/proto/engine/v1/export_test.go new file mode 100644 index 000000000000..17f4cde64607 --- /dev/null +++ b/proto/engine/v1/export_test.go @@ -0,0 +1,3 @@ +package enginev1 + +type Copier[T any] copier[T] diff --git a/proto/prysm/v1alpha1/cloners.go b/proto/prysm/v1alpha1/cloners.go index 1727c0ab0af0..04009820059e 100644 --- a/proto/prysm/v1alpha1/cloners.go +++ b/proto/prysm/v1alpha1/cloners.go @@ -705,7 +705,7 @@ func CopyExecutionPayloadCapella(payload *enginev1.ExecutionPayloadCapella) *eng BaseFeePerGas: bytesutil.SafeCopyBytes(payload.BaseFeePerGas), BlockHash: bytesutil.SafeCopyBytes(payload.BlockHash), Transactions: bytesutil.SafeCopy2dBytes(payload.Transactions), - Withdrawals: CopyWithdrawalSlice(payload.Withdrawals), + Withdrawals: copySlice(payload.Withdrawals), } } @@ -800,33 +800,6 @@ func CopyBlindedBeaconBlockBodyBellatrix(body *BlindedBeaconBlockBodyBellatrix) } } -// CopyWithdrawalSlice copies the provided slice of withdrawals. -func CopyWithdrawalSlice(withdrawals []*enginev1.Withdrawal) []*enginev1.Withdrawal { - if withdrawals == nil { - return nil - } - - res := make([]*enginev1.Withdrawal, len(withdrawals)) - for i := 0; i < len(res); i++ { - res[i] = CopyWithdrawal(withdrawals[i]) - } - return res -} - -// CopyWithdrawal copies the provided withdrawal object. -func CopyWithdrawal(withdrawal *enginev1.Withdrawal) *enginev1.Withdrawal { - if withdrawal == nil { - return nil - } - - return &enginev1.Withdrawal{ - Index: withdrawal.Index, - ValidatorIndex: withdrawal.ValidatorIndex, - Address: bytesutil.SafeCopyBytes(withdrawal.Address), - Amount: withdrawal.Amount, - } -} - func CopyBLSToExecutionChanges(changes []*SignedBLSToExecutionChange) []*SignedBLSToExecutionChange { if changes == nil { return nil @@ -944,7 +917,7 @@ func CopyExecutionPayloadDeneb(payload *enginev1.ExecutionPayloadDeneb) *enginev BaseFeePerGas: bytesutil.SafeCopyBytes(payload.BaseFeePerGas), BlockHash: bytesutil.SafeCopyBytes(payload.BlockHash), Transactions: bytesutil.SafeCopy2dBytes(payload.Transactions), - Withdrawals: CopyWithdrawalSlice(payload.Withdrawals), + Withdrawals: copySlice(payload.Withdrawals), BlobGasUsed: payload.BlobGasUsed, ExcessBlobGas: payload.ExcessBlobGas, } @@ -990,91 +963,12 @@ func CopyBeaconBlockBodyElectra(body *BeaconBlockBodyElectra) *BeaconBlockBodyEl Deposits: CopyDeposits(body.Deposits), VoluntaryExits: CopySignedVoluntaryExits(body.VoluntaryExits), SyncAggregate: CopySyncAggregate(body.SyncAggregate), - ExecutionPayload: CopyExecutionPayloadElectra(body.ExecutionPayload), + ExecutionPayload: body.ExecutionPayload.Copy(), BlsToExecutionChanges: CopyBLSToExecutionChanges(body.BlsToExecutionChanges), BlobKzgCommitments: CopyBlobKZGs(body.BlobKzgCommitments), } } -// CopyExecutionPayloadElectra copies the provided execution payload. -func CopyExecutionPayloadElectra(payload *enginev1.ExecutionPayloadElectra) *enginev1.ExecutionPayloadElectra { - if payload == nil { - return nil - } - return &enginev1.ExecutionPayloadElectra{ - ParentHash: bytesutil.SafeCopyBytes(payload.ParentHash), - FeeRecipient: bytesutil.SafeCopyBytes(payload.FeeRecipient), - StateRoot: bytesutil.SafeCopyBytes(payload.StateRoot), - ReceiptsRoot: bytesutil.SafeCopyBytes(payload.ReceiptsRoot), - LogsBloom: bytesutil.SafeCopyBytes(payload.LogsBloom), - PrevRandao: bytesutil.SafeCopyBytes(payload.PrevRandao), - BlockNumber: payload.BlockNumber, - GasLimit: payload.GasLimit, - GasUsed: payload.GasUsed, - Timestamp: payload.Timestamp, - ExtraData: bytesutil.SafeCopyBytes(payload.ExtraData), - BaseFeePerGas: bytesutil.SafeCopyBytes(payload.BaseFeePerGas), - BlockHash: bytesutil.SafeCopyBytes(payload.BlockHash), - Transactions: bytesutil.SafeCopy2dBytes(payload.Transactions), - Withdrawals: CopyWithdrawalSlice(payload.Withdrawals), - BlobGasUsed: payload.BlobGasUsed, - ExcessBlobGas: payload.ExcessBlobGas, - DepositRequests: CopyDepositRequests(payload.DepositRequests), - WithdrawalRequests: CopyWithdrawalRequests(payload.WithdrawalRequests), - ConsolidationRequests: CopyConsolidationRequests(payload.ConsolidationRequests), - } -} - -func CopyDepositRequests(dr []*enginev1.DepositRequest) []*enginev1.DepositRequest { - if dr == nil { - return nil - } - - newDr := make([]*enginev1.DepositRequest, len(dr)) - for i, d := range dr { - newDr[i] = &enginev1.DepositRequest{ - Pubkey: bytesutil.SafeCopyBytes(d.Pubkey), - WithdrawalCredentials: bytesutil.SafeCopyBytes(d.WithdrawalCredentials), - Amount: d.Amount, - Signature: bytesutil.SafeCopyBytes(d.Signature), - Index: d.Index, - } - } - return newDr -} - -func CopyWithdrawalRequests(wr []*enginev1.WithdrawalRequest) []*enginev1.WithdrawalRequest { - if wr == nil { - return nil - } - newWr := make([]*enginev1.WithdrawalRequest, len(wr)) - for i, w := range wr { - newWr[i] = &enginev1.WithdrawalRequest{ - SourceAddress: bytesutil.SafeCopyBytes(w.SourceAddress), - ValidatorPubkey: bytesutil.SafeCopyBytes(w.ValidatorPubkey), - Amount: w.Amount, - } - } - - return newWr -} - -func CopyConsolidationRequests(cr []*enginev1.ConsolidationRequest) []*enginev1.ConsolidationRequest { - if cr == nil { - return nil - } - newCr := make([]*enginev1.ConsolidationRequest, len(cr)) - for i, w := range cr { - newCr[i] = &enginev1.ConsolidationRequest{ - SourceAddress: bytesutil.SafeCopyBytes(w.SourceAddress), - SourcePubkey: bytesutil.SafeCopyBytes(w.SourcePubkey), - TargetPubkey: bytesutil.SafeCopyBytes(w.TargetPubkey), - } - } - - return newCr -} - func CopyExecutionPayloadHeaderElectra(payload *enginev1.ExecutionPayloadHeaderElectra) *enginev1.ExecutionPayloadHeaderElectra { if payload == nil { return nil @@ -1161,3 +1055,16 @@ func CopyPendingBalanceDeposits(pbd []*PendingBalanceDeposit) []*PendingBalanceD } return newPbd } + +type cloneable[T any] interface { + Copy() T +} + +func copySlice[T any, C cloneable[T]](original []C) []T { + // Create a new slice with the same length as the original + newSlice := make([]T, len(original)) + for i := 0; i < len(newSlice); i++ { + newSlice[i] = original[i].Copy() + } + return newSlice +} diff --git a/proto/prysm/v1alpha1/cloners_test.go b/proto/prysm/v1alpha1/cloners_test.go index 5383970c9dec..22a348f31660 100644 --- a/proto/prysm/v1alpha1/cloners_test.go +++ b/proto/prysm/v1alpha1/cloners_test.go @@ -527,26 +527,6 @@ func bytes(length int) []byte { return b } -func TestCopyWithdrawals(t *testing.T) { - ws := genWithdrawals(10) - - got := v1alpha1.CopyWithdrawalSlice(ws) - if !reflect.DeepEqual(got, ws) { - t.Errorf("TestCopyWithdrawals() = %v, want %v", got, ws) - } - assert.NotEmpty(t, got, "Copied withdrawals have empty fields") -} - -func TestCopyWithdrawal(t *testing.T) { - w := genWithdrawal() - - got := v1alpha1.CopyWithdrawal(w) - if !reflect.DeepEqual(got, w) { - t.Errorf("TestCopyWithdrawal() = %v, want %v", got, w) - } - assert.NotEmpty(t, got, "Copied withdrawal has empty fields") -} - func TestCopyBLSToExecutionChanges(t *testing.T) { changes := genBLSToExecutionChanges(10) @@ -658,33 +638,6 @@ func TestCopyBeaconBlockBodyElectra(t *testing.T) { } } -func TestCopyExecutionPayloadElectra(t *testing.T) { - p := genExecutionPayloadElectra() - - got := v1alpha1.CopyExecutionPayloadElectra(p) - if !reflect.DeepEqual(got, p) { - t.Errorf("TestCopyExecutionPayloadElectra() = %v, want %v", got, p) - } -} - -func TestCopyDepositRequests(t *testing.T) { - drs := genDepositRequests(10) - - got := v1alpha1.CopyDepositRequests(drs) - if !reflect.DeepEqual(got, drs) { - t.Errorf("TestCopyDepositRequests() = %v, want %v", got, drs) - } -} - -func TestCopyWithdrawalRequests(t *testing.T) { - wrs := genWithdrawalRequests(10) - - got := v1alpha1.CopyWithdrawalRequests(wrs) - if !reflect.DeepEqual(got, wrs) { - t.Errorf("TestCopyWithdrawalRequests() = %v, want %v", got, wrs) - } -} - func TestCopyExecutionPayloadHeaderElectra(t *testing.T) { p := genExecutionPayloadHeaderElectra()