mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
Allow validators registration batching on Builder API /eth/v1/builder/validators (#13178)
* builder `NewClient`: Simplify + fix some typos. * Validator client: Implement `validator-registration-batch-size` option * Address Potuz comments * Address Potuz's comments --------- Co-authored-by: Radosław Kapka <rkapka@wp.pl> Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com> Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
This commit is contained in:
@@ -16,11 +16,13 @@ import (
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// SubmitValidatorRegistrations signs validator registration objects and submits it to the beacon node.
|
||||
// SubmitValidatorRegistrations signs validator registration objects and submits it to the beacon node by batch of validatorRegsBatchSize size maximum.
|
||||
// If at least one error occurs during a registration call to the beacon node, the last error is returned.
|
||||
func SubmitValidatorRegistrations(
|
||||
ctx context.Context,
|
||||
validatorClient iface.ValidatorClient,
|
||||
signedRegs []*ethpb.SignedValidatorRegistrationV1,
|
||||
validatorRegsBatchSize int,
|
||||
) error {
|
||||
ctx, span := trace.StartSpan(ctx, "validator.SubmitValidatorRegistrations")
|
||||
defer span.End()
|
||||
@@ -29,17 +31,33 @@ func SubmitValidatorRegistrations(
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := validatorClient.SubmitValidatorRegistrations(ctx, ðpb.SignedValidatorRegistrationsV1{
|
||||
Messages: signedRegs,
|
||||
}); err != nil {
|
||||
if strings.Contains(err.Error(), builder.ErrNoBuilder.Error()) {
|
||||
log.Warnln("Beacon node does not utilize a custom builder via the --http-mev-relay flag. Validator registration skipped.")
|
||||
return nil
|
||||
chunks := chunkSignedValidatorRegistrationV1(signedRegs, validatorRegsBatchSize)
|
||||
var lastErr error
|
||||
|
||||
for _, chunk := range chunks {
|
||||
innerSignerRegs := ethpb.SignedValidatorRegistrationsV1{
|
||||
Messages: chunk,
|
||||
}
|
||||
|
||||
if _, err := validatorClient.SubmitValidatorRegistrations(ctx, &innerSignerRegs); err != nil {
|
||||
lastErr = errors.Wrap(err, "could not submit signed registrations to beacon node")
|
||||
|
||||
if strings.Contains(err.Error(), builder.ErrNoBuilder.Error()) {
|
||||
log.Warnln("Beacon node does not utilize a custom builder via the --http-mev-relay flag. Validator registration skipped.")
|
||||
|
||||
// We stop early the loop here, since if the builder endpoint is not configured for this chunk, it is useless to check the following chunks
|
||||
break
|
||||
}
|
||||
}
|
||||
return errors.Wrap(err, "could not submit signed registrations to beacon node")
|
||||
}
|
||||
log.Infoln("Submitted builder validator registration settings for custom builders")
|
||||
return nil
|
||||
|
||||
if lastErr == nil {
|
||||
log.Infoln("Submitted builder validator registration settings for custom builders")
|
||||
} else {
|
||||
log.WithError(lastErr).Warn("Could not submit all signed registrations to beacon node")
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// Sings validator registration obj with the proposer domain and private key.
|
||||
@@ -100,3 +118,35 @@ func isValidatorRegistrationSame(cachedVR *ethpb.ValidatorRegistrationV1, newVR
|
||||
}
|
||||
return isSame
|
||||
}
|
||||
|
||||
// chunkSignedValidatorRegistrationV1 chunks regs into chunks of size chunkSize (the last chunk may be smaller). If chunkSize is non-positive, returns only one chunk.
|
||||
func chunkSignedValidatorRegistrationV1(regs []*ethpb.SignedValidatorRegistrationV1, chunkSize int) [][]*ethpb.SignedValidatorRegistrationV1 {
|
||||
if chunkSize <= 0 {
|
||||
chunkSize = len(regs)
|
||||
}
|
||||
|
||||
regsCount := len(regs)
|
||||
|
||||
chunksCount := (regsCount + chunkSize - 1) / chunkSize
|
||||
lastChunkSize := regsCount % chunkSize
|
||||
|
||||
if lastChunkSize == 0 {
|
||||
lastChunkSize = chunkSize
|
||||
}
|
||||
|
||||
chunks := make([][]*ethpb.SignedValidatorRegistrationV1, chunksCount)
|
||||
|
||||
for i := 0; i < chunksCount-1; i++ {
|
||||
chunks[i] = make([]*ethpb.SignedValidatorRegistrationV1, chunkSize)
|
||||
}
|
||||
|
||||
chunks[chunksCount-1] = make([]*ethpb.SignedValidatorRegistrationV1, lastChunkSize)
|
||||
|
||||
for i, reg := range regs {
|
||||
chunkIndex := i / chunkSize
|
||||
chunkOffset := i % chunkSize
|
||||
chunks[chunkIndex][chunkOffset] = reg
|
||||
}
|
||||
|
||||
return chunks
|
||||
}
|
||||
|
||||
@@ -21,27 +21,76 @@ func TestSubmitValidatorRegistrations(t *testing.T) {
|
||||
defer finish()
|
||||
|
||||
ctx := context.Background()
|
||||
require.NoError(t, nil, SubmitValidatorRegistrations(ctx, m.validatorClient, []*ethpb.SignedValidatorRegistrationV1{}))
|
||||
validatorRegsBatchSize := 2
|
||||
require.NoError(t, nil, SubmitValidatorRegistrations(ctx, m.validatorClient, []*ethpb.SignedValidatorRegistrationV1{}, validatorRegsBatchSize))
|
||||
|
||||
reg := ðpb.ValidatorRegistrationV1{
|
||||
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
|
||||
GasLimit: 123456,
|
||||
Timestamp: uint64(time.Now().Unix()),
|
||||
Pubkey: validatorKey.PublicKey().Marshal(),
|
||||
regs := [...]*ethpb.ValidatorRegistrationV1{
|
||||
{
|
||||
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
|
||||
GasLimit: 123,
|
||||
Timestamp: uint64(time.Now().Unix()),
|
||||
Pubkey: validatorKey.PublicKey().Marshal(),
|
||||
},
|
||||
{
|
||||
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
|
||||
GasLimit: 456,
|
||||
Timestamp: uint64(time.Now().Unix()),
|
||||
Pubkey: validatorKey.PublicKey().Marshal(),
|
||||
},
|
||||
{
|
||||
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
|
||||
GasLimit: 789,
|
||||
Timestamp: uint64(time.Now().Unix()),
|
||||
Pubkey: validatorKey.PublicKey().Marshal(),
|
||||
},
|
||||
}
|
||||
|
||||
m.validatorClient.EXPECT().
|
||||
SubmitValidatorRegistrations(gomock.Any(), ðpb.SignedValidatorRegistrationsV1{
|
||||
Messages: []*ethpb.SignedValidatorRegistrationV1{
|
||||
{Message: reg,
|
||||
Signature: params.BeaconConfig().ZeroHash[:]},
|
||||
gomock.InOrder(
|
||||
m.validatorClient.EXPECT().
|
||||
SubmitValidatorRegistrations(gomock.Any(), ðpb.SignedValidatorRegistrationsV1{
|
||||
Messages: []*ethpb.SignedValidatorRegistrationV1{
|
||||
{
|
||||
Message: regs[0],
|
||||
Signature: params.BeaconConfig().ZeroHash[:],
|
||||
},
|
||||
{
|
||||
Message: regs[1],
|
||||
Signature: params.BeaconConfig().ZeroHash[:],
|
||||
},
|
||||
},
|
||||
}).
|
||||
Return(nil, nil),
|
||||
|
||||
m.validatorClient.EXPECT().
|
||||
SubmitValidatorRegistrations(gomock.Any(), ðpb.SignedValidatorRegistrationsV1{
|
||||
Messages: []*ethpb.SignedValidatorRegistrationV1{
|
||||
{
|
||||
Message: regs[2],
|
||||
Signature: params.BeaconConfig().ZeroHash[:],
|
||||
},
|
||||
},
|
||||
}).
|
||||
Return(nil, nil),
|
||||
)
|
||||
|
||||
require.NoError(t, nil, SubmitValidatorRegistrations(
|
||||
ctx, m.validatorClient,
|
||||
[]*ethpb.SignedValidatorRegistrationV1{
|
||||
{
|
||||
Message: regs[0],
|
||||
Signature: params.BeaconConfig().ZeroHash[:],
|
||||
},
|
||||
}).
|
||||
Return(nil, nil)
|
||||
require.NoError(t, nil, SubmitValidatorRegistrations(ctx, m.validatorClient, []*ethpb.SignedValidatorRegistrationV1{
|
||||
{Message: reg,
|
||||
Signature: params.BeaconConfig().ZeroHash[:]},
|
||||
}))
|
||||
{
|
||||
Message: regs[1],
|
||||
Signature: params.BeaconConfig().ZeroHash[:],
|
||||
},
|
||||
{
|
||||
Message: regs[2],
|
||||
Signature: params.BeaconConfig().ZeroHash[:],
|
||||
},
|
||||
},
|
||||
validatorRegsBatchSize,
|
||||
))
|
||||
}
|
||||
|
||||
func TestSubmitValidatorRegistration_CantSign(t *testing.T) {
|
||||
@@ -49,6 +98,7 @@ func TestSubmitValidatorRegistration_CantSign(t *testing.T) {
|
||||
defer finish()
|
||||
|
||||
ctx := context.Background()
|
||||
validatorRegsBatchSize := 500
|
||||
reg := ðpb.ValidatorRegistrationV1{
|
||||
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
|
||||
GasLimit: 123456,
|
||||
@@ -67,7 +117,7 @@ func TestSubmitValidatorRegistration_CantSign(t *testing.T) {
|
||||
require.ErrorContains(t, "could not sign", SubmitValidatorRegistrations(ctx, m.validatorClient, []*ethpb.SignedValidatorRegistrationV1{
|
||||
{Message: reg,
|
||||
Signature: params.BeaconConfig().ZeroHash[:]},
|
||||
}))
|
||||
}, validatorRegsBatchSize))
|
||||
}
|
||||
|
||||
func Test_signValidatorRegistration(t *testing.T) {
|
||||
@@ -225,3 +275,113 @@ func TestValidator_SignValidatorRegistrationRequest(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChunkSignedValidatorRegistrationV1(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
regs []*ethpb.SignedValidatorRegistrationV1
|
||||
chunkSize int
|
||||
expected [][]*ethpb.SignedValidatorRegistrationV1
|
||||
}{
|
||||
"All buckets are full": {
|
||||
regs: []*ethpb.SignedValidatorRegistrationV1{
|
||||
{Signature: []byte("1")},
|
||||
{Signature: []byte("2")},
|
||||
{Signature: []byte("3")},
|
||||
{Signature: []byte("4")},
|
||||
{Signature: []byte("5")},
|
||||
{Signature: []byte("6")},
|
||||
},
|
||||
chunkSize: 3,
|
||||
expected: [][]*ethpb.SignedValidatorRegistrationV1{
|
||||
{
|
||||
{Signature: []byte("1")},
|
||||
{Signature: []byte("2")},
|
||||
{Signature: []byte("3")},
|
||||
},
|
||||
{
|
||||
{Signature: []byte("4")},
|
||||
{Signature: []byte("5")},
|
||||
{Signature: []byte("6")},
|
||||
},
|
||||
},
|
||||
},
|
||||
"Last bucket is not full": {
|
||||
regs: []*ethpb.SignedValidatorRegistrationV1{
|
||||
{Signature: []byte("1")},
|
||||
{Signature: []byte("2")},
|
||||
{Signature: []byte("3")},
|
||||
{Signature: []byte("4")},
|
||||
{Signature: []byte("5")},
|
||||
{Signature: []byte("6")},
|
||||
{Signature: []byte("7")},
|
||||
},
|
||||
chunkSize: 3,
|
||||
expected: [][]*ethpb.SignedValidatorRegistrationV1{
|
||||
{
|
||||
{Signature: []byte("1")},
|
||||
{Signature: []byte("2")},
|
||||
{Signature: []byte("3")},
|
||||
},
|
||||
{
|
||||
{Signature: []byte("4")},
|
||||
{Signature: []byte("5")},
|
||||
{Signature: []byte("6")},
|
||||
},
|
||||
{
|
||||
{Signature: []byte("7")},
|
||||
},
|
||||
},
|
||||
},
|
||||
"Not enough items": {
|
||||
regs: []*ethpb.SignedValidatorRegistrationV1{
|
||||
{Signature: []byte("1")},
|
||||
{Signature: []byte("2")},
|
||||
{Signature: []byte("3")},
|
||||
},
|
||||
chunkSize: 42,
|
||||
expected: [][]*ethpb.SignedValidatorRegistrationV1{
|
||||
{
|
||||
{Signature: []byte("1")},
|
||||
{Signature: []byte("2")},
|
||||
{Signature: []byte("3")},
|
||||
},
|
||||
},
|
||||
},
|
||||
"Null chunk size": {
|
||||
regs: []*ethpb.SignedValidatorRegistrationV1{
|
||||
{Signature: []byte("1")},
|
||||
{Signature: []byte("2")},
|
||||
{Signature: []byte("3")},
|
||||
},
|
||||
chunkSize: 0,
|
||||
expected: [][]*ethpb.SignedValidatorRegistrationV1{
|
||||
{
|
||||
{Signature: []byte("1")},
|
||||
{Signature: []byte("2")},
|
||||
{Signature: []byte("3")},
|
||||
},
|
||||
},
|
||||
},
|
||||
"Negative chunk size": {
|
||||
regs: []*ethpb.SignedValidatorRegistrationV1{
|
||||
{Signature: []byte("1")},
|
||||
{Signature: []byte("2")},
|
||||
{Signature: []byte("3")},
|
||||
},
|
||||
chunkSize: -1,
|
||||
expected: [][]*ethpb.SignedValidatorRegistrationV1{
|
||||
{
|
||||
{Signature: []byte("1")},
|
||||
{Signature: []byte("2")},
|
||||
{Signature: []byte("3")},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
require.DeepEqual(t, test.expected, chunkSignedValidatorRegistrationV1(test.regs, test.chunkSize))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ type ValidatorService struct {
|
||||
graffiti []byte
|
||||
Web3SignerConfig *remoteweb3signer.SetupConfig
|
||||
proposerSettings *validatorserviceconfig.ProposerSettings
|
||||
validatorRegBatchSize int
|
||||
}
|
||||
|
||||
// Config for the validator service.
|
||||
@@ -99,6 +100,7 @@ type Config struct {
|
||||
ProposerSettings *validatorserviceconfig.ProposerSettings
|
||||
BeaconApiEndpoint string
|
||||
BeaconApiTimeout time.Duration
|
||||
ValidatorRegBatchSize int
|
||||
}
|
||||
|
||||
// NewValidatorService creates a new validator service for the service
|
||||
@@ -127,6 +129,7 @@ func NewValidatorService(ctx context.Context, cfg *Config) (*ValidatorService, e
|
||||
graffitiStruct: cfg.GraffitiStruct,
|
||||
Web3SignerConfig: cfg.Web3SignerConfig,
|
||||
proposerSettings: cfg.ProposerSettings,
|
||||
validatorRegBatchSize: cfg.ValidatorRegBatchSize,
|
||||
}
|
||||
|
||||
dialOpts := ConstructDialOptions(
|
||||
@@ -220,6 +223,7 @@ func (v *ValidatorService) Start() {
|
||||
proposerSettings: v.proposerSettings,
|
||||
walletInitializedChannel: make(chan *wallet.Wallet, 1),
|
||||
prysmBeaconClient: prysmBeaconClient,
|
||||
validatorRegBatchSize: v.validatorRegBatchSize,
|
||||
}
|
||||
|
||||
// To resolve a race condition at startup due to the interface
|
||||
|
||||
@@ -105,6 +105,7 @@ type validator struct {
|
||||
proposerSettings *validatorserviceconfig.ProposerSettings
|
||||
walletInitializedChannel chan *wallet.Wallet
|
||||
prysmBeaconClient iface.PrysmBeaconChainClient
|
||||
validatorRegBatchSize int
|
||||
}
|
||||
|
||||
type validatorStatus struct {
|
||||
@@ -1043,7 +1044,7 @@ func (v *validator) PushProposerSettings(ctx context.Context, km keymanager.IKey
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := SubmitValidatorRegistrations(ctx, v.validatorClient, signedRegReqs); err != nil {
|
||||
if err := SubmitValidatorRegistrations(ctx, v.validatorClient, signedRegReqs, v.validatorRegBatchSize); err != nil {
|
||||
return errors.Wrap(ErrBuilderValidatorRegistration, err.Error())
|
||||
}
|
||||
|
||||
|
||||
@@ -439,6 +439,7 @@ func (c *ValidatorClient) registerValidatorService(cliCtx *cli.Context) error {
|
||||
ProposerSettings: bpc,
|
||||
BeaconApiTimeout: time.Second * 30,
|
||||
BeaconApiEndpoint: c.cliCtx.String(flags.BeaconRESTApiProviderFlag.Name),
|
||||
ValidatorRegBatchSize: c.cliCtx.Int(flags.ValidatorRegistrationBatchSizeFlag.Name),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize validator service")
|
||||
|
||||
Reference in New Issue
Block a user