diff --git a/beacon-chain/builder/BUILD.bazel b/beacon-chain/builder/BUILD.bazel index adf23ff60..3cbffb830 100644 --- a/beacon-chain/builder/BUILD.bazel +++ b/beacon-chain/builder/BUILD.bazel @@ -12,6 +12,7 @@ go_library( deps = [ "//api/client/builder:go_default_library", "//beacon-chain/blockchain:go_default_library", + "//beacon-chain/cache:go_default_library", "//beacon-chain/db:go_default_library", "//cmd/beacon-chain/flags:go_default_library", "//consensus-types/interfaces:go_default_library", diff --git a/beacon-chain/builder/option.go b/beacon-chain/builder/option.go index a3edc3f9a..fa713bea9 100644 --- a/beacon-chain/builder/option.go +++ b/beacon-chain/builder/option.go @@ -3,6 +3,7 @@ package builder import ( "github.com/prysmaticlabs/prysm/v4/api/client/builder" "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/cache" "github.com/prysmaticlabs/prysm/v4/beacon-chain/db" "github.com/prysmaticlabs/prysm/v4/cmd/beacon-chain/flags" "github.com/urfave/cli/v2" @@ -50,3 +51,11 @@ func WithDatabase(beaconDB db.HeadAccessDatabase) Option { return nil } } + +// WithRegistrationCache uses a cache for the validator registrations instead of a persistent db. +func WithRegistrationCache() Option { + return func(s *Service) error { + s.registrationCache = cache.NewRegistrationCache() + return nil + } +} diff --git a/beacon-chain/builder/service.go b/beacon-chain/builder/service.go index 345d2fe70..ce301043b 100644 --- a/beacon-chain/builder/service.go +++ b/beacon-chain/builder/service.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v4/api/client/builder" "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/cache" "github.com/prysmaticlabs/prysm/v4/beacon-chain/db" "github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" @@ -26,6 +27,7 @@ type BlockBuilder interface { SubmitBlindedBlock(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, error) GetHeader(ctx context.Context, slot primitives.Slot, parentHash [32]byte, pubKey [48]byte) (builder.SignedBid, error) RegisterValidator(ctx context.Context, reg []*ethpb.SignedValidatorRegistrationV1) error + RegistrationByValidatorID(ctx context.Context, id primitives.ValidatorIndex) (*ethpb.ValidatorRegistrationV1, error) Configured() bool } @@ -38,10 +40,11 @@ type config struct { // Service defines a service that provides a client for interacting with the beacon chain and MEV relay network. type Service struct { - cfg *config - c builder.BuilderClient - ctx context.Context - cancel context.CancelFunc + cfg *config + c builder.BuilderClient + ctx context.Context + cancel context.CancelFunc + registrationCache *cache.RegistrationCache } // NewService instantiates a new service. @@ -139,8 +142,12 @@ func (s *Service) RegisterValidator(ctx context.Context, reg []*ethpb.SignedVali return ErrNoBuilder } + // should be removed if db is removed idxs := make([]primitives.ValidatorIndex, 0) msgs := make([]*ethpb.ValidatorRegistrationV1, 0) + + indexToRegistration := make(map[primitives.ValidatorIndex]*ethpb.ValidatorRegistrationV1) + valid := make([]*ethpb.SignedValidatorRegistrationV1, 0) for i := 0; i < len(reg); i++ { r := reg[i] @@ -154,12 +161,33 @@ func (s *Service) RegisterValidator(ctx context.Context, reg []*ethpb.SignedVali idxs = append(idxs, nx) msgs = append(msgs, r.Message) valid = append(valid, r) + indexToRegistration[nx] = r.Message } if err := s.c.RegisterValidator(ctx, valid); err != nil { return errors.Wrap(err, "could not register validator(s)") } - return s.cfg.beaconDB.SaveRegistrationsByValidatorIDs(ctx, idxs, msgs) + if len(indexToRegistration) != len(msgs) { + return errors.New("ids and registrations must be the same length") + } + if s.registrationCache != nil { + s.registrationCache.UpdateIndexToRegisteredMap(ctx, indexToRegistration) + return nil + } else { + return s.cfg.beaconDB.SaveRegistrationsByValidatorIDs(ctx, idxs, msgs) + } +} + +// RegistrationByValidatorID returns either the values from the cache or db. +func (s *Service) RegistrationByValidatorID(ctx context.Context, id primitives.ValidatorIndex) (*ethpb.ValidatorRegistrationV1, error) { + if s.registrationCache != nil { + return s.registrationCache.RegistrationByIndex(id) + } else { + if s.cfg == nil || s.cfg.beaconDB == nil { + return nil, errors.New("nil beacon db") + } + return s.cfg.beaconDB.RegistrationByValidatorID(ctx, id) + } } // Configured returns true if the user has configured a builder client. diff --git a/beacon-chain/builder/testing/BUILD.bazel b/beacon-chain/builder/testing/BUILD.bazel index 875465e09..7042cfad0 100644 --- a/beacon-chain/builder/testing/BUILD.bazel +++ b/beacon-chain/builder/testing/BUILD.bazel @@ -8,6 +8,8 @@ go_library( visibility = ["//visibility:public"], deps = [ "//api/client/builder:go_default_library", + "//beacon-chain/cache:go_default_library", + "//beacon-chain/db:go_default_library", "//config/params:go_default_library", "//consensus-types/blocks:go_default_library", "//consensus-types/interfaces:go_default_library", diff --git a/beacon-chain/builder/testing/mock.go b/beacon-chain/builder/testing/mock.go index d3f4b09cc..9c9caab33 100644 --- a/beacon-chain/builder/testing/mock.go +++ b/beacon-chain/builder/testing/mock.go @@ -5,6 +5,8 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v4/api/client/builder" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/cache" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/db" "github.com/prysmaticlabs/prysm/v4/config/params" "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces" @@ -14,6 +16,11 @@ import ( "github.com/prysmaticlabs/prysm/v4/time/slots" ) +// Config defines a config struct for dependencies into the service. +type Config struct { + BeaconDB db.HeadAccessDatabase +} + // MockBuilderService to mock builder. type MockBuilderService struct { HasConfigured bool @@ -22,8 +29,10 @@ type MockBuilderService struct { ErrSubmitBlindedBlock error Bid *ethpb.SignedBuilderBid BidCapella *ethpb.SignedBuilderBidCapella + RegistrationCache *cache.RegistrationCache ErrGetHeader error ErrRegisterValidator error + Cfg *Config } // Configured for mocking. @@ -48,7 +57,7 @@ func (s *MockBuilderService) SubmitBlindedBlock(_ context.Context, _ interfaces. } // GetHeader for mocking. -func (s *MockBuilderService) GetHeader(ctx context.Context, slot primitives.Slot, hr [32]byte, pb [48]byte) (builder.SignedBid, error) { +func (s *MockBuilderService) GetHeader(_ context.Context, slot primitives.Slot, _ [32]byte, _ [48]byte) (builder.SignedBid, error) { if slots.ToEpoch(slot) >= params.BeaconConfig().CapellaForkEpoch { return builder.WrappedSignedBuilderBidCapella(s.BidCapella) } @@ -59,6 +68,17 @@ func (s *MockBuilderService) GetHeader(ctx context.Context, slot primitives.Slot return w, s.ErrGetHeader } +// RegistrationByValidatorID returns either the values from the cache or db. +func (s *MockBuilderService) RegistrationByValidatorID(ctx context.Context, id primitives.ValidatorIndex) (*ethpb.ValidatorRegistrationV1, error) { + if s.RegistrationCache != nil { + return s.RegistrationCache.RegistrationByIndex(id) + } + if s.Cfg.BeaconDB != nil { + return s.Cfg.BeaconDB.RegistrationByValidatorID(ctx, id) + } + return nil, cache.ErrNotFoundRegistration +} + // RegisterValidator for mocking. func (s *MockBuilderService) RegisterValidator(context.Context, []*ethpb.SignedValidatorRegistrationV1) error { return s.ErrRegisterValidator diff --git a/beacon-chain/cache/BUILD.bazel b/beacon-chain/cache/BUILD.bazel index ebc364da2..957933b57 100644 --- a/beacon-chain/cache/BUILD.bazel +++ b/beacon-chain/cache/BUILD.bazel @@ -17,6 +17,7 @@ go_library( "proposer_indices.go", "proposer_indices_disabled.go", # keep "proposer_indices_type.go", + "registration.go", "skip_slot_cache.go", "subnet_ids.go", "sync_committee.go", @@ -65,6 +66,7 @@ go_test( "committee_test.go", "payload_id_test.go", "proposer_indices_test.go", + "registration_test.go", "skip_slot_cache_test.go", "subnet_ids_test.go", "sync_committee_head_state_test.go", @@ -83,7 +85,9 @@ go_test( "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", + "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_google_gofuzz//:go_default_library", + "@com_github_sirupsen_logrus//hooks/test:go_default_library", "@org_golang_google_protobuf//proto:go_default_library", ], ) diff --git a/beacon-chain/cache/error.go b/beacon-chain/cache/error.go index 34f9d89e2..2027273db 100644 --- a/beacon-chain/cache/error.go +++ b/beacon-chain/cache/error.go @@ -1,6 +1,6 @@ package cache -import "errors" +import "github.com/pkg/errors" var ( // ErrNilValueProvided for when we try to put a nil value in a cache. @@ -12,4 +12,6 @@ var ( // ErrNonExistingSyncCommitteeKey when sync committee key (root) does not exist in cache. ErrNonExistingSyncCommitteeKey = errors.New("does not exist sync committee key") errNotSyncCommitteeIndexPosition = errors.New("not syncCommitteeIndexPosition struct") + // ErrNotFoundRegistration when validator registration does not exist in cache. + ErrNotFoundRegistration = errors.Wrap(ErrNotFound, "no validator registered") ) diff --git a/beacon-chain/cache/registration.go b/beacon-chain/cache/registration.go new file mode 100644 index 000000000..16cd232f4 --- /dev/null +++ b/beacon-chain/cache/registration.go @@ -0,0 +1,82 @@ +package cache + +import ( + "context" + "sync" + "time" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v4/config/params" + "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" + "github.com/prysmaticlabs/prysm/v4/math" + ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" + log "github.com/sirupsen/logrus" + "go.opencensus.io/trace" +) + +// RegistrationCache is used to store the cached results of an Validator Registration request. +// beacon api /eth/v1/validator/register_validator +type RegistrationCache struct { + indexToRegistration map[primitives.ValidatorIndex]*ethpb.ValidatorRegistrationV1 + lock sync.RWMutex +} + +// NewRegistrationCache initializes the map and underlying cache. +func NewRegistrationCache() *RegistrationCache { + return &RegistrationCache{ + indexToRegistration: make(map[primitives.ValidatorIndex]*ethpb.ValidatorRegistrationV1), + lock: sync.RWMutex{}, + } +} + +// RegistrationByIndex returns the registration by index in the cache and also removes items in the cache if expired. +func (regCache *RegistrationCache) RegistrationByIndex(id primitives.ValidatorIndex) (*ethpb.ValidatorRegistrationV1, error) { + regCache.lock.RLock() + v, ok := regCache.indexToRegistration[id] + if !ok { + regCache.lock.RUnlock() + return nil, errors.Wrapf(ErrNotFoundRegistration, "validator id %d", id) + } + isExpired, err := RegistrationTimeStampExpired(v.Timestamp) + if err != nil { + return nil, errors.Wrapf(err, "failed to check registration expiration") + } + if isExpired { + regCache.lock.RUnlock() + regCache.lock.Lock() + defer regCache.lock.Unlock() + delete(regCache.indexToRegistration, id) + log.Warnf("registration for validator index %d expired at unix time %d", id, v.Timestamp) + return nil, errors.Wrapf(ErrNotFoundRegistration, "validator id %d", id) + } + regCache.lock.RUnlock() + return v, nil +} + +func RegistrationTimeStampExpired(ts uint64) (bool, error) { + // safely convert unint64 to int64 + i, err := math.Int(ts) + if err != nil { + return false, err + } + expiryDuration := params.BeaconConfig().RegistrationDuration + // registered time + expiration duration < current time = expired + return time.Unix(int64(i), 0).Add(expiryDuration).Before(time.Now()), nil +} + +// UpdateIndexToRegisteredMap adds or updates values in the cache based on the argument. +func (regCache *RegistrationCache) UpdateIndexToRegisteredMap(ctx context.Context, m map[primitives.ValidatorIndex]*ethpb.ValidatorRegistrationV1) { + _, span := trace.StartSpan(ctx, "RegistrationCache.UpdateIndexToRegisteredMap") + defer span.End() + regCache.lock.Lock() + defer regCache.lock.Unlock() + for key, value := range m { + regCache.indexToRegistration[key] = ðpb.ValidatorRegistrationV1{ + Pubkey: bytesutil.SafeCopyBytes(value.Pubkey), + FeeRecipient: bytesutil.SafeCopyBytes(value.FeeRecipient), + GasLimit: value.GasLimit, + Timestamp: value.Timestamp, + } + } +} diff --git a/beacon-chain/cache/registration_test.go b/beacon-chain/cache/registration_test.go new file mode 100644 index 000000000..d56b74156 --- /dev/null +++ b/beacon-chain/cache/registration_test.go @@ -0,0 +1,82 @@ +package cache + +import ( + "context" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/prysmaticlabs/prysm/v4/config/params" + "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" + ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v4/testing/require" + logTest "github.com/sirupsen/logrus/hooks/test" +) + +func TestRegistrationCache(t *testing.T) { + hook := logTest.NewGlobal() + pubkey, err := hexutil.Decode("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a") + require.NoError(t, err) + validatorIndex := primitives.ValidatorIndex(1) + cache := NewRegistrationCache() + m := make(map[primitives.ValidatorIndex]*ethpb.ValidatorRegistrationV1) + + m[validatorIndex] = ðpb.ValidatorRegistrationV1{ + FeeRecipient: []byte{}, + GasLimit: 100, + Timestamp: uint64(time.Now().Unix()), + Pubkey: pubkey, + } + cache.UpdateIndexToRegisteredMap(context.Background(), m) + reg, err := cache.RegistrationByIndex(validatorIndex) + require.NoError(t, err) + require.Equal(t, string(reg.Pubkey), string(pubkey)) + t.Run("Registration expired", func(t *testing.T) { + validatorIndex2 := primitives.ValidatorIndex(2) + overExpirationPadTime := time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot*uint64(params.BeaconConfig().SlotsPerEpoch)*4) // 4 epochs + m[validatorIndex2] = ðpb.ValidatorRegistrationV1{ + FeeRecipient: []byte{}, + GasLimit: 100, + Timestamp: uint64(time.Now().Add(-1 * overExpirationPadTime).Unix()), + Pubkey: pubkey, + } + cache.UpdateIndexToRegisteredMap(context.Background(), m) + _, err := cache.RegistrationByIndex(validatorIndex2) + require.ErrorContains(t, "no validator registered", err) + require.LogsContain(t, hook, "expired") + }) + t.Run("Registration close to expiration still passes", func(t *testing.T) { + pubkey, err := hexutil.Decode("0x88247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a") + require.NoError(t, err) + validatorIndex2 := primitives.ValidatorIndex(2) + overExpirationPadTime := time.Second * time.Duration((params.BeaconConfig().SecondsPerSlot*uint64(params.BeaconConfig().SlotsPerEpoch)*3)-5) // 3 epochs - 5 seconds + m[validatorIndex2] = ðpb.ValidatorRegistrationV1{ + FeeRecipient: []byte{}, + GasLimit: 100, + Timestamp: uint64(time.Now().Add(-1 * overExpirationPadTime).Unix()), + Pubkey: pubkey, + } + cache.UpdateIndexToRegisteredMap(context.Background(), m) + reg, err := cache.RegistrationByIndex(validatorIndex2) + require.NoError(t, err) + require.Equal(t, string(reg.Pubkey), string(pubkey)) + }) +} + +func Test_RegistrationTimeStampExpired(t *testing.T) { + // expiration set at 3 epochs + t.Run("expired registration", func(t *testing.T) { + overExpirationPadTime := time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot*uint64(params.BeaconConfig().SlotsPerEpoch)*4) // 4 epochs + ts := uint64(time.Now().Add(-1 * overExpirationPadTime).Unix()) + isExpired, err := RegistrationTimeStampExpired(ts) + require.NoError(t, err) + require.Equal(t, true, isExpired) + }) + t.Run("is not expired registration", func(t *testing.T) { + overExpirationPadTime := time.Second * time.Duration((params.BeaconConfig().SecondsPerSlot*uint64(params.BeaconConfig().SlotsPerEpoch)*3)-5) // 3 epochs -5 seconds + ts := uint64(time.Now().Add(-1 * overExpirationPadTime).Unix()) + isExpired, err := RegistrationTimeStampExpired(ts) + require.NoError(t, err) + require.Equal(t, false, isExpired) + }) +} diff --git a/beacon-chain/db/kv/blocks_test.go b/beacon-chain/db/kv/blocks_test.go index 5349f08c5..a90300ab9 100644 --- a/beacon-chain/db/kv/blocks_test.go +++ b/beacon-chain/db/kv/blocks_test.go @@ -3,6 +3,7 @@ package kv import ( "context" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" @@ -910,25 +911,25 @@ func TestStore_RegistrationsByValidatorID(t *testing.T) { ids := []primitives.ValidatorIndex{0, 0, 0} regs := []*ethpb.ValidatorRegistrationV1{{}, {}, {}, {}} require.ErrorContains(t, "ids and registrations must be the same length", db.SaveRegistrationsByValidatorIDs(ctx, ids, regs)) - + timestamp := time.Now().Unix() ids = []primitives.ValidatorIndex{0, 1, 2} regs = []*ethpb.ValidatorRegistrationV1{ { FeeRecipient: bytesutil.PadTo([]byte("a"), 20), GasLimit: 1, - Timestamp: 2, + Timestamp: uint64(timestamp), Pubkey: bytesutil.PadTo([]byte("b"), 48), }, { FeeRecipient: bytesutil.PadTo([]byte("c"), 20), GasLimit: 3, - Timestamp: 4, + Timestamp: uint64(timestamp), Pubkey: bytesutil.PadTo([]byte("d"), 48), }, { FeeRecipient: bytesutil.PadTo([]byte("e"), 20), GasLimit: 5, - Timestamp: 6, + Timestamp: uint64(timestamp), Pubkey: bytesutil.PadTo([]byte("f"), 48), }, } @@ -938,7 +939,7 @@ func TestStore_RegistrationsByValidatorID(t *testing.T) { require.DeepEqual(t, ðpb.ValidatorRegistrationV1{ FeeRecipient: bytesutil.PadTo([]byte("a"), 20), GasLimit: 1, - Timestamp: 2, + Timestamp: uint64(timestamp), Pubkey: bytesutil.PadTo([]byte("b"), 48), }, f) f, err = db.RegistrationByValidatorID(ctx, 1) @@ -946,7 +947,7 @@ func TestStore_RegistrationsByValidatorID(t *testing.T) { require.DeepEqual(t, ðpb.ValidatorRegistrationV1{ FeeRecipient: bytesutil.PadTo([]byte("c"), 20), GasLimit: 3, - Timestamp: 4, + Timestamp: uint64(timestamp), Pubkey: bytesutil.PadTo([]byte("d"), 48), }, f) f, err = db.RegistrationByValidatorID(ctx, 2) @@ -954,7 +955,7 @@ func TestStore_RegistrationsByValidatorID(t *testing.T) { require.DeepEqual(t, ðpb.ValidatorRegistrationV1{ FeeRecipient: bytesutil.PadTo([]byte("e"), 20), GasLimit: 5, - Timestamp: 6, + Timestamp: uint64(timestamp), Pubkey: bytesutil.PadTo([]byte("f"), 48), }, f) _, err = db.RegistrationByValidatorID(ctx, 3) diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index b51f44027..39dd04c78 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -249,7 +249,7 @@ func New(cliCtx *cli.Context, opts ...Option) (*BeaconNode, error) { } log.Debugln("Registering builder service") - if err := beacon.registerBuilderService(); err != nil { + if err := beacon.registerBuilderService(cliCtx); err != nil { return nil, err } @@ -961,7 +961,7 @@ func (b *BeaconNode) registerValidatorMonitorService() error { return b.services.RegisterService(svc) } -func (b *BeaconNode) registerBuilderService() error { +func (b *BeaconNode) registerBuilderService(cliCtx *cli.Context) error { var chainService *blockchain.Service if err := b.services.FetchService(&chainService); err != nil { return err @@ -970,6 +970,9 @@ func (b *BeaconNode) registerBuilderService() error { opts := append(b.serviceFlagOpts.builderOpts, builder.WithHeadFetcher(chainService), builder.WithDatabase(b.db)) + if cliCtx.Bool(flags.EnableRegistrationCache.Name) { + opts = append(opts, builder.WithRegistrationCache()) + } svc, err := builder.NewService(b.ctx, opts...) if err != nil { return err diff --git a/beacon-chain/rpc/eth/validator/validator.go b/beacon-chain/rpc/eth/validator/validator.go index c027ca009..4fa65b2f2 100644 --- a/beacon-chain/rpc/eth/validator/validator.go +++ b/beacon-chain/rpc/eth/validator/validator.go @@ -744,6 +744,7 @@ func (vs *Server) PrepareBeaconProposer( if err := vs.BeaconDB.SaveFeeRecipientsByValidatorIDs(ctx, validatorIndices, feeRecipients); err != nil { return nil, status.Errorf(codes.Internal, "Could not save fee recipients: %v", err) } + log.WithFields(log.Fields{ "validatorIndices": validatorIndices, }).Info("Updated fee recipient addresses for validator indices") diff --git a/beacon-chain/rpc/eth/validator/validator_test.go b/beacon-chain/rpc/eth/validator/validator_test.go index 05a0ff581..f3748fbbd 100644 --- a/beacon-chain/rpc/eth/validator/validator_test.go +++ b/beacon-chain/rpc/eth/validator/validator_test.go @@ -776,7 +776,6 @@ func TestProduceBlockV2(t *testing.T) { resp, err := server.ProduceBlockV2(ctx, ðpbv1.ProduceBlockRequest{}) require.NoError(t, err) - assert.Equal(t, ethpbv2.Version_CAPELLA, resp.Version) containerBlock, ok := resp.Data.Block.(*ethpbv2.BeaconBlockContainerV2_CapellaBlock) require.Equal(t, true, ok) diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go index 6c69bde66..a0a3514a0 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go @@ -69,7 +69,7 @@ func TestServer_setExecutionData(t *testing.T) { FinalizationFetcher: &blockchainTest.ChainService{}, BeaconDB: beaconDB, ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(), - BlockBuilder: &builderTest.MockBuilderService{HasConfigured: true}, + BlockBuilder: &builderTest.MockBuilderService{HasConfigured: true, Cfg: &builderTest.Config{BeaconDB: beaconDB}}, } t.Run("No builder configured. Use local block", func(t *testing.T) { @@ -84,7 +84,7 @@ func TestServer_setExecutionData(t *testing.T) { blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockCapella()) require.NoError(t, err) require.NoError(t, vs.BeaconDB.SaveRegistrationsByValidatorIDs(ctx, []primitives.ValidatorIndex{blk.Block().ProposerIndex()}, - []*ethpb.ValidatorRegistrationV1{{FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), Pubkey: make([]byte, fieldparams.BLSPubkeyLength)}})) + []*ethpb.ValidatorRegistrationV1{{FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), Timestamp: uint64(time.Now().Unix()), Pubkey: make([]byte, fieldparams.BLSPubkeyLength)}})) ti, err := slots.ToTime(uint64(time.Now().Unix()), 0) require.NoError(t, err) sk, err := bls.RandKey() @@ -119,6 +119,7 @@ func TestServer_setExecutionData(t *testing.T) { vs.BlockBuilder = &builderTest.MockBuilderService{ BidCapella: sBid, HasConfigured: true, + Cfg: &builderTest.Config{BeaconDB: beaconDB}, } wb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockBellatrix()) require.NoError(t, err) @@ -136,7 +137,7 @@ func TestServer_setExecutionData(t *testing.T) { blk, err := blocks.NewSignedBeaconBlock(util.NewBlindedBeaconBlockCapella()) require.NoError(t, err) require.NoError(t, vs.BeaconDB.SaveRegistrationsByValidatorIDs(ctx, []primitives.ValidatorIndex{blk.Block().ProposerIndex()}, - []*ethpb.ValidatorRegistrationV1{{FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), Pubkey: make([]byte, fieldparams.BLSPubkeyLength)}})) + []*ethpb.ValidatorRegistrationV1{{FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), Timestamp: uint64(time.Now().Unix()), Pubkey: make([]byte, fieldparams.BLSPubkeyLength)}})) ti, err := slots.ToTime(uint64(time.Now().Unix()), 0) require.NoError(t, err) sk, err := bls.RandKey() @@ -174,6 +175,7 @@ func TestServer_setExecutionData(t *testing.T) { vs.BlockBuilder = &builderTest.MockBuilderService{ BidCapella: sBid, HasConfigured: true, + Cfg: &builderTest.Config{BeaconDB: beaconDB}, } wb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockCapella()) require.NoError(t, err) @@ -219,6 +221,7 @@ func TestServer_setExecutionData(t *testing.T) { vs.BlockBuilder = &builderTest.MockBuilderService{ ErrGetHeader: errors.New("fault"), HasConfigured: true, + Cfg: &builderTest.Config{BeaconDB: beaconDB}, } vs.ExecutionEngineCaller = &powtesting.EngineClient{PayloadIDBytes: id, ExecutionPayloadCapella: &v1.ExecutionPayloadCapella{BlockNumber: 4}, BlockValue: 0} require.NoError(t, vs.setExecutionData(context.Background(), blk, capellaTransitionState)) @@ -227,7 +230,6 @@ func TestServer_setExecutionData(t *testing.T) { require.Equal(t, uint64(4), e.BlockNumber()) // Local block }) } - func TestServer_getPayloadHeader(t *testing.T) { params.SetupTestConfigCleanup(t) bc := params.BeaconConfig() diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_builder.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_builder.go index 065671aed..f2e8bb551 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_builder.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_builder.go @@ -4,6 +4,7 @@ import ( "context" "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/cache" "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/kv" "github.com/prysmaticlabs/prysm/v4/config/params" "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" @@ -36,12 +37,12 @@ func (vs *Server) canUseBuilder(ctx context.Context, slot primitives.Slot, idx p // validatorRegistered returns true if validator with index `id` was previously registered in the database. func (vs *Server) validatorRegistered(ctx context.Context, id primitives.ValidatorIndex) (bool, error) { - if vs.BeaconDB == nil { - return false, errors.New("nil beacon db") + if vs.BlockBuilder == nil { + return false, nil } - _, err := vs.BeaconDB.RegistrationByValidatorID(ctx, id) + _, err := vs.BlockBuilder.RegistrationByValidatorID(ctx, id) switch { - case errors.Is(err, kv.ErrNotFoundFeeRecipient): + case errors.Is(err, kv.ErrNotFoundFeeRecipient), errors.Is(err, cache.ErrNotFoundRegistration): return false, nil case err != nil: return false, err diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_builder_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_builder_test.go index 9bbc3dd08..d33703ae3 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_builder_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_builder_test.go @@ -6,6 +6,7 @@ import ( "time" blockchainTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/builder" testing2 "github.com/prysmaticlabs/prysm/v4/beacon-chain/builder/testing" dbTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing" doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree" @@ -71,22 +72,28 @@ func TestServer_circuitBreakBuilder(t *testing.T) { } func TestServer_validatorRegistered(t *testing.T) { - proposerServer := &Server{} + b, err := builder.NewService(context.Background()) + require.NoError(t, err) + proposerServer := &Server{ + BlockBuilder: b, + } ctx := context.Background() reg, err := proposerServer.validatorRegistered(ctx, 0) require.ErrorContains(t, "nil beacon db", err) require.Equal(t, false, reg) - - proposerServer.BeaconDB = dbTest.SetupDB(t) + db := dbTest.SetupDB(t) + realBuilder, err := builder.NewService(context.Background(), builder.WithDatabase(db)) + require.NoError(t, err) + proposerServer.BlockBuilder = realBuilder reg, err = proposerServer.validatorRegistered(ctx, 0) require.NoError(t, err) require.Equal(t, false, reg) f := bytesutil.PadTo([]byte{}, fieldparams.FeeRecipientLength) p := bytesutil.PadTo([]byte{}, fieldparams.BLSPubkeyLength) - require.NoError(t, proposerServer.BeaconDB.SaveRegistrationsByValidatorIDs(ctx, []primitives.ValidatorIndex{0, 1}, - []*ethpb.ValidatorRegistrationV1{{FeeRecipient: f, Pubkey: p}, {FeeRecipient: f, Pubkey: p}})) + require.NoError(t, db.SaveRegistrationsByValidatorIDs(ctx, []primitives.ValidatorIndex{0, 1}, + []*ethpb.ValidatorRegistrationV1{{FeeRecipient: f, Timestamp: uint64(time.Now().Unix()), Pubkey: p}, {FeeRecipient: f, Timestamp: uint64(time.Now().Unix()), Pubkey: p}})) reg, err = proposerServer.validatorRegistered(ctx, 0) require.NoError(t, err) @@ -94,6 +101,7 @@ func TestServer_validatorRegistered(t *testing.T) { reg, err = proposerServer.validatorRegistered(ctx, 1) require.NoError(t, err) require.Equal(t, true, reg) + } func TestServer_canUseBuilder(t *testing.T) { @@ -105,9 +113,6 @@ func TestServer_canUseBuilder(t *testing.T) { reg, err := proposerServer.canUseBuilder(context.Background(), 0, 0) require.NoError(t, err) require.Equal(t, false, reg) - proposerServer.BlockBuilder = &testing2.MockBuilderService{ - HasConfigured: true, - } ctx := context.Background() @@ -116,20 +121,21 @@ func TestServer_canUseBuilder(t *testing.T) { reg, err = proposerServer.canUseBuilder(ctx, params.BeaconConfig().MaxBuilderConsecutiveMissedSlots+1, 0) require.NoError(t, err) require.Equal(t, false, reg) + db := dbTest.SetupDB(t) - reg, err = proposerServer.validatorRegistered(ctx, 0) - require.ErrorContains(t, "nil beacon db", err) - require.Equal(t, false, reg) + proposerServer.BlockBuilder = &testing2.MockBuilderService{ + HasConfigured: true, + Cfg: &testing2.Config{BeaconDB: db}, + } - proposerServer.BeaconDB = dbTest.SetupDB(t) reg, err = proposerServer.canUseBuilder(ctx, 1, 0) require.NoError(t, err) require.Equal(t, false, reg) f := bytesutil.PadTo([]byte{}, fieldparams.FeeRecipientLength) p := bytesutil.PadTo([]byte{}, fieldparams.BLSPubkeyLength) - require.NoError(t, proposerServer.BeaconDB.SaveRegistrationsByValidatorIDs(ctx, []primitives.ValidatorIndex{0}, - []*ethpb.ValidatorRegistrationV1{{FeeRecipient: f, Pubkey: p}})) + require.NoError(t, db.SaveRegistrationsByValidatorIDs(ctx, []primitives.ValidatorIndex{0}, + []*ethpb.ValidatorRegistrationV1{{FeeRecipient: f, Timestamp: uint64(time.Now().Unix()), Pubkey: p}})) reg, err = proposerServer.canUseBuilder(ctx, params.BeaconConfig().MaxBuilderConsecutiveMissedSlots-1, 0) require.NoError(t, err) diff --git a/cmd/beacon-chain/flags/base.go b/cmd/beacon-chain/flags/base.go index 3c7e397c6..e94d4de46 100644 --- a/cmd/beacon-chain/flags/base.go +++ b/cmd/beacon-chain/flags/base.go @@ -3,8 +3,6 @@ package flags import ( - "strings" - "github.com/prysmaticlabs/prysm/v4/cmd" "github.com/prysmaticlabs/prysm/v4/config/params" "github.com/urfave/cli/v2" @@ -94,7 +92,7 @@ var ( HTTPModules = &cli.StringFlag{ Name: "http-modules", Usage: "Comma-separated list of API module names. Possible values: `" + PrysmAPIModule + `,` + EthAPIModule + "`.", - Value: strings.Join([]string{PrysmAPIModule, EthAPIModule}, ","), + Value: PrysmAPIModule + `,` + EthAPIModule, } // DisableGRPCGateway for JSON-HTTP requests to the beacon node. DisableGRPCGateway = &cli.BoolFlag{ @@ -204,6 +202,11 @@ var ( Usage: "Sets the maximum number of headers that a deposit log query can fetch.", Value: uint64(1000), } + // EnableRegistrationCache a temporary flag for enabling the validator registration cache instead of db. + EnableRegistrationCache = &cli.BoolFlag{ + Name: "enable-registration-cache", + Usage: "A temporary flag for enabling the validator registration cache instead of persisting in db. The cache will clear on restart.", + } // WeakSubjectivityCheckpoint defines the weak subjectivity checkpoint the node must sync through to defend against long range attacks. WeakSubjectivityCheckpoint = &cli.StringFlag{ Name: "weak-subjectivity-checkpoint", diff --git a/cmd/beacon-chain/main.go b/cmd/beacon-chain/main.go index d082200f9..b6bcfe578 100644 --- a/cmd/beacon-chain/main.go +++ b/cmd/beacon-chain/main.go @@ -58,6 +58,7 @@ var appFlags = []cli.Flag{ flags.InteropGenesisTimeFlag, flags.SlotsPerArchivedPoint, flags.EnableDebugRPCEndpoints, + flags.EnableRegistrationCache, flags.SubscribeToAllSubnets, flags.HistoricalSlasherNode, flags.ChainID, diff --git a/cmd/beacon-chain/usage.go b/cmd/beacon-chain/usage.go index d4dc4db14..db6e5dbf0 100644 --- a/cmd/beacon-chain/usage.go +++ b/cmd/beacon-chain/usage.go @@ -113,6 +113,7 @@ var appHelpFlagGroups = []flagGroup{ flags.BlockBatchLimit, flags.BlockBatchLimitBurstFactor, flags.EnableDebugRPCEndpoints, + flags.EnableRegistrationCache, flags.SubscribeToAllSubnets, flags.HistoricalSlasherNode, flags.ChainID, diff --git a/config/params/config.go b/config/params/config.go index f0b2295f3..e78ace590 100644 --- a/config/params/config.go +++ b/config/params/config.go @@ -209,6 +209,8 @@ type BeaconChainConfig struct { MaxBuilderEpochMissedSlots primitives.Slot // MaxBuilderEpochMissedSlots is defines the number of total skip slot (per epoch rolling windows) to fallback from using relay/builder to local execution engine for block construction. LocalBlockValueBoost uint64 // LocalBlockValueBoost is the value boost for local block construction. This is used to prioritize local block construction over relay/builder block construction. + // Mev validator registration + RegistrationDuration time.Duration // RegistrationDuration is the duration a validator registration is valid for. // Execution engine timeout value ExecutionEngineTimeoutValue uint64 // ExecutionEngineTimeoutValue defines the seconds to wait before timing out engine endpoints with execution payload execution semantics (newPayload, forkchoiceUpdated). } diff --git a/config/params/mainnet_config.go b/config/params/mainnet_config.go index 024ed7fa3..bba249f05 100644 --- a/config/params/mainnet_config.go +++ b/config/params/mainnet_config.go @@ -257,7 +257,8 @@ var mainnetBeaconConfig = &BeaconChainConfig{ // Mevboost circuit breaker MaxBuilderConsecutiveMissedSlots: 3, MaxBuilderEpochMissedSlots: 5, - + // Mev validator registration duration before expiration + RegistrationDuration: 12 * 32 * 3 * time.Second, // defaults to 3 Epochs // Execution engine timeout value ExecutionEngineTimeoutValue: 8, // 8 seconds default based on: https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#core }