Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions beacon-chain/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,7 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error {
BlobStorage: b.BlobStorage,
DataColumnStorage: b.DataColumnStorage,
TrackedValidatorsCache: b.trackedValidatorsCache,
ProposerPreferencesCache: b.proposerPreferencesCache,
PayloadIDCache: b.payloadIDCache,
LCStore: b.lcStore,
GraffitiInfo: web3Service.GraffitiInfo(),
Expand Down
2 changes: 2 additions & 0 deletions beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ go_library(
"proposer_bid.go",
"proposer_payload_attestation.go",
"proposer_payload_envelope.go",
"proposer_preferences.go",
"proposer_slashings.go",
"proposer_sync_aggregate.go",
"server.go",
Expand Down Expand Up @@ -220,6 +221,7 @@ go_test(
"proposer_exits_test.go",
"proposer_payload_attestation_test.go",
"proposer_payload_envelope_test.go",
"proposer_preferences_test.go",
"proposer_slashings_test.go",
"proposer_sync_aggregate_test.go",
"proposer_test.go",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package validator

import (
"context"

"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/time/slots"
"github.com/ethereum/go-ethereum/common"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)

// SubmitSignedProposerPreferences broadcasts signed proposer preferences and
// caches them locally for subsequent bid validation.
// Local submissions intentionally bypass full gossip verification (proposer
// lookahead, signature) because the validator client is trusted.
func (vs *Server) SubmitSignedProposerPreferences(
ctx context.Context,
msg *ethpb.SignedProposerPreferences,
) (*emptypb.Empty, error) {
ctx, span := trace.StartSpan(ctx, "ValidatorServer.SubmitSignedProposerPreferences")
defer span.End()

if msg == nil || msg.Message == nil {
return nil, status.Errorf(codes.InvalidArgument, "signed proposer preferences message is nil")
}

if vs.SyncChecker.Syncing() {
return nil, status.Errorf(codes.Unavailable, "Syncing to latest head, not ready to respond")
}

proposalSlot := msg.Message.ProposalSlot
if slots.ToEpoch(proposalSlot) < params.BeaconConfig().GloasForkEpoch {
return nil, status.Errorf(
codes.InvalidArgument,
"signed proposer preferences are not supported before Gloas fork (slot %d)",
proposalSlot,
)
}

currentEpoch := slots.ToEpoch(vs.TimeFetcher.CurrentSlot())
if slots.ToEpoch(proposalSlot) != currentEpoch+1 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the intended use only for the next epoch? if you restart your validator client and it needs to propose in the epoch it restarted on this would fail?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2P will ignore it anyway so it's better to know that with an error

[IGNORE] preferences.proposal_slot is in the next epoch -- i.e. compute_epoch_at_slot(preferences.proposal_slot) == get_current_epoch(state) + 1.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will bring up this concern again ethereum/consensus-specs#4777 (comment)

return nil, status.Errorf(
codes.InvalidArgument,
"signed proposer preferences proposal slot must be in the next epoch: slot %d currentEpoch %d",
proposalSlot,
currentEpoch,
)
}

if vs.ProposerPreferencesCache.Has(proposalSlot) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we already proposed it aka in the cache, should we just be returning empty? what about a debut log here saying some kind of duplicate submission?

log.WithFields(logrus.Fields{
"slot": proposalSlot,
"validatorIndex": msg.Message.ValidatorIndex,
}).Debug("Ignoring duplicate signed proposer preferences submission")
return &emptypb.Empty{}, nil
}

if err := vs.P2P.Broadcast(ctx, msg); err != nil {
return nil, status.Errorf(codes.Internal, "Could not broadcast signed proposer preferences: %v", err)
}

vs.ProposerPreferencesCache.Add(proposalSlot, msg.Message.FeeRecipient, msg.Message.GasLimit)

log.WithFields(logrus.Fields{
"slot": proposalSlot,
"validatorIndex": msg.Message.ValidatorIndex,
"feeRecipient": common.BytesToAddress(msg.Message.FeeRecipient).Hex(),
"gasLimit": msg.Message.GasLimit,
}).Debug("Submitted signed proposer preferences")
return &emptypb.Empty{}, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package validator

import (
"testing"

chainMock "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
p2pmock "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/testing"
mockSync "github.com/OffchainLabs/prysm/v7/beacon-chain/sync/initial-sync/testing"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/testing/assert"
"github.com/OffchainLabs/prysm/v7/testing/require"
"google.golang.org/protobuf/types/known/emptypb"
)

func TestSubmitSignedProposerPreferences_OK(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 1
params.OverrideBeaconConfig(cfg)

currentSlot := primitives.Slot(31)
proposalSlot := currentSlot + 1
chain := &chainMock.ChainService{Slot: &currentSlot}
p2p := &p2pmock.MockBroadcaster{}
cache := cache.NewProposerPreferencesCache()
vs := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: false},
TimeFetcher: chain,
P2P: p2p,
ProposerPreferencesCache: cache,
}

msg := &ethpb.SignedProposerPreferences{
Message: &ethpb.ProposerPreferences{
ProposalSlot: proposalSlot,
ValidatorIndex: 2,
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
},
Signature: make([]byte, 96),
}

resp, err := vs.SubmitSignedProposerPreferences(t.Context(), msg)
require.NoError(t, err)
require.DeepEqual(t, &emptypb.Empty{}, resp)
assert.Equal(t, true, p2p.BroadcastCalled.Load())
pref, ok := cache.Get(proposalSlot)
require.Equal(t, true, ok)
require.DeepEqual(t, msg.Message.FeeRecipient, pref.FeeRecipient)
require.Equal(t, msg.Message.GasLimit, pref.GasLimit)
}

func TestSubmitSignedProposerPreferences_DuplicateSlot(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 1
params.OverrideBeaconConfig(cfg)

currentSlot := primitives.Slot(31)
proposalSlot := currentSlot + 1
chain := &chainMock.ChainService{Slot: &currentSlot}
p2p := &p2pmock.MockBroadcaster{}
c := cache.NewProposerPreferencesCache()
c.Add(proposalSlot, make([]byte, 20), 30_000_000)
vs := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: false},
TimeFetcher: chain,
P2P: p2p,
ProposerPreferencesCache: c,
}

msg := &ethpb.SignedProposerPreferences{
Message: &ethpb.ProposerPreferences{
ProposalSlot: proposalSlot,
ValidatorIndex: 2,
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
},
Signature: make([]byte, 96),
}

resp, err := vs.SubmitSignedProposerPreferences(t.Context(), msg)
require.NoError(t, err)
require.DeepEqual(t, &emptypb.Empty{}, resp)
assert.Equal(t, false, p2p.BroadcastCalled.Load())
}

func TestSubmitSignedProposerPreferences_InvalidEpoch(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 1
params.OverrideBeaconConfig(cfg)

currentSlot := primitives.Slot(31)
chain := &chainMock.ChainService{Slot: &currentSlot}
vs := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: false},
TimeFetcher: chain,
P2P: &p2pmock.MockBroadcaster{},
ProposerPreferencesCache: cache.NewProposerPreferencesCache(),
}

// Same epoch (current), not next epoch.
msg := &ethpb.SignedProposerPreferences{
Message: &ethpb.ProposerPreferences{
ProposalSlot: currentSlot,
ValidatorIndex: 2,
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
},
Signature: make([]byte, 96),
}
_, err := vs.SubmitSignedProposerPreferences(t.Context(), msg)
require.ErrorContains(t, "next epoch", err)

// Two epochs ahead.
msg.Message.ProposalSlot = currentSlot + primitives.Slot(2*params.BeaconConfig().SlotsPerEpoch)
_, err = vs.SubmitSignedProposerPreferences(t.Context(), msg)
require.ErrorContains(t, "next epoch", err)
}

func TestSubmitSignedProposerPreferences_Syncing(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 1
params.OverrideBeaconConfig(cfg)

currentSlot := primitives.Slot(31)
chain := &chainMock.ChainService{Slot: &currentSlot}
vs := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: true},
TimeFetcher: chain,
P2P: &p2pmock.MockBroadcaster{},
ProposerPreferencesCache: cache.NewProposerPreferencesCache(),
}

msg := &ethpb.SignedProposerPreferences{
Message: &ethpb.ProposerPreferences{
ProposalSlot: currentSlot + 1,
ValidatorIndex: 2,
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
},
Signature: make([]byte, 96),
}

_, err := vs.SubmitSignedProposerPreferences(t.Context(), msg)
require.ErrorContains(t, "not ready to respond", err)
}
1 change: 1 addition & 0 deletions beacon-chain/rpc/prysm/v1alpha1/validator/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type Server struct {
Ctx context.Context
PayloadIDCache *cache.PayloadIDCache
TrackedValidatorsCache *cache.TrackedValidatorsCache
ProposerPreferencesCache *cache.ProposerPreferencesCache
executionPayloadEnvelopeMu sync.RWMutex
executionPayloadEnvelope *ethpb.ExecutionPayloadEnvelope
HeadFetcher blockchain.HeadFetcher
Expand Down
2 changes: 2 additions & 0 deletions beacon-chain/rpc/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ type Config struct {
BlobStorage *filesystem.BlobStorage
DataColumnStorage *filesystem.DataColumnStorage
TrackedValidatorsCache *cache.TrackedValidatorsCache
ProposerPreferencesCache *cache.ProposerPreferencesCache
PayloadIDCache *cache.PayloadIDCache
LCStore *lightClient.Store
GraffitiInfo *execution.GraffitiInfo
Expand Down Expand Up @@ -263,6 +264,7 @@ func NewService(ctx context.Context, cfg *Config) *Service {
ClockWaiter: s.cfg.ClockWaiter,
CoreService: coreService,
TrackedValidatorsCache: s.cfg.TrackedValidatorsCache,
ProposerPreferencesCache: s.cfg.ProposerPreferencesCache,
PayloadIDCache: s.cfg.PayloadIDCache,
AttestationStateFetcher: s.cfg.AttestationReceiver,
GraffitiInfo: s.cfg.GraffitiInfo,
Expand Down
3 changes: 3 additions & 0 deletions changelog/t_gloas-proposer-preferences-rpc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- Add gRPC endpoint `SubmitSignedProposerPreferences` for validators to broadcast proposer preferences
Loading
Loading