From fca087650db8cac5f57d34362da145001c40b08a Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 15 Apr 2025 22:39:41 -0300 Subject: [PATCH 01/38] beacon/goclient: get beacon config from beacon node --- beacon/goclient/attest_test.go | 3 - beacon/goclient/events_test.go | 2 - beacon/goclient/goclient.go | 130 +++++++++++++++--------- beacon/goclient/goclient_test.go | 64 +----------- beacon/goclient/spec.go | 102 +++++++++++++++++++ cli/operator/node.go | 2 - config/config.exporter.example.yaml | 1 - networkconfig/beacon.go | 1 - networkconfig/holesky-e2e.go | 1 - networkconfig/holesky-stage.go | 1 - networkconfig/holesky.go | 1 - networkconfig/hoodi-stage.go | 1 - networkconfig/hoodi.go | 1 - networkconfig/local-testnet.go | 1 - networkconfig/mainnet.go | 1 - networkconfig/sepolia.go | 1 - networkconfig/test-network.go | 1 - protocol/v2/blockchain/beacon/client.go | 3 - 18 files changed, 186 insertions(+), 131 deletions(-) create mode 100644 beacon/goclient/spec.go diff --git a/beacon/goclient/attest_test.go b/beacon/goclient/attest_test.go index 4420d23255..0877a533fd 100644 --- a/beacon/goclient/attest_test.go +++ b/beacon/goclient/attest_test.go @@ -18,7 +18,6 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap" - "github.com/ssvlabs/ssv/networkconfig" operatordatastore "github.com/ssvlabs/ssv/operator/datastore" "github.com/ssvlabs/ssv/operator/slotticker" "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" @@ -194,7 +193,6 @@ func TestGoClient_GetAttestationData_Simple(t *testing.T) { zap.NewNop(), beacon.Options{ Context: ctx, - BeaconConfig: networkconfig.Mainnet.BeaconConfig, BeaconNodeAddr: server.URL, CommonTimeout: 1 * time.Second, LongTimeout: 1 * time.Second, @@ -452,7 +450,6 @@ func createClient( client, err := New(zap.NewNop(), beacon.Options{ Context: ctx, - BeaconConfig: networkconfig.Mainnet.BeaconConfig, BeaconNodeAddr: beaconServerURL, CommonTimeout: defaultHardTimeout, LongTimeout: time.Second, diff --git a/beacon/goclient/events_test.go b/beacon/goclient/events_test.go index 4c68cf1bf5..8b02687d42 100644 --- a/beacon/goclient/events_test.go +++ b/beacon/goclient/events_test.go @@ -14,7 +14,6 @@ import ( "go.uber.org/zap" "github.com/ssvlabs/ssv/beacon/goclient/tests" - "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" ) @@ -76,7 +75,6 @@ func eventsTestClient(t *testing.T, serverURL string) *GoClient { server, err := New(zap.NewNop(), beacon.Options{ BeaconNodeAddr: serverURL, Context: context.Background(), - BeaconConfig: networkconfig.Mainnet.BeaconConfig, }, tests.MockDataStore{}, tests.MockSlotTickerProvider) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index a8e3e72f81..78913bcfaa 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -2,6 +2,7 @@ package goclient import ( "context" + "encoding/json" "fmt" "math" "strings" @@ -123,11 +124,15 @@ const ( // GoClient implementing Beacon struct type GoClient struct { - log *zap.Logger - ctx context.Context - beaconConfig networkconfig.BeaconConfig - clients []Client - multiClient MultiClient + log *zap.Logger + ctx context.Context + + beaconConfigMu sync.Mutex + beaconConfig *networkconfig.BeaconConfig + beaconConfigInit chan struct{} + + clients []Client + multiClient MultiClient specssv.VersionCalls syncDistanceTolerance phase0.Slot @@ -173,7 +178,6 @@ type GoClient struct { lastProcessedHeadEventSlotLock sync.Mutex lastProcessedHeadEventSlot phase0.Slot - genesisForkVersion phase0.Version ForkLock sync.RWMutex ForkEpochElectra phase0.Epoch ForkEpochDeneb phase0.Epoch @@ -189,9 +193,7 @@ func New( operatorDataStore operatorDataStore, slotTickerProvider slotticker.Provider, ) (*GoClient, error) { - logger.Info("consensus client: connecting", - fields.Address(opt.BeaconNodeAddr), - fields.Network(opt.BeaconConfig.GetBeaconName())) + logger.Info("consensus client: connecting", fields.Address(opt.BeaconNodeAddr)) commonTimeout := opt.CommonTimeout if commonTimeout == 0 { @@ -203,27 +205,18 @@ func New( } client := &GoClient{ - log: logger.Named("consensus_client"), - ctx: opt.Context, - beaconConfig: opt.BeaconConfig, - syncDistanceTolerance: phase0.Slot(opt.SyncDistanceTolerance), - operatorDataStore: operatorDataStore, - registrations: map[phase0.BLSPubKey]*validatorRegistration{}, - attestationDataCache: ttlcache.New( - // we only fetch attestation data during the slot of the relevant duty (and never later), - // hence caching it for 2 slots is sufficient - ttlcache.WithTTL[phase0.Slot, *phase0.AttestationData](2 * opt.BeaconConfig.SlotDuration), - ), - blockRootToSlotCache: ttlcache.New(ttlcache.WithCapacity[phase0.Root, phase0.Slot]( - uint64(opt.BeaconConfig.SlotsPerEpoch) * BlockRootToSlotCacheCapacityEpochs), - ), + log: logger.Named("consensus_client"), + ctx: opt.Context, + beaconConfigInit: make(chan struct{}), + syncDistanceTolerance: phase0.Slot(opt.SyncDistanceTolerance), + operatorDataStore: operatorDataStore, + registrations: map[phase0.BLSPubKey]*validatorRegistration{}, commonTimeout: commonTimeout, longTimeout: longTimeout, withWeightedAttestationData: opt.WithWeightedAttestationData, weightedAttestationDataSoftTimeout: commonTimeout / 2, weightedAttestationDataHardTimeout: commonTimeout, supportedTopics: []EventTopic{EventTopicHead}, - genesisForkVersion: opt.BeaconConfig.ForkVersion, // Initialize forks with FAR_FUTURE_EPOCH. ForkEpochAltair: math.MaxUint64, ForkEpochBellatrix: math.MaxUint64, @@ -243,6 +236,9 @@ func New( } } + // Despite allowing delayed start, addSingleClient attempts to connect to node before returning, + // so active clients should be up before the initMultiClient + err := client.initMultiClient(opt.Context) if err != nil { logger.Error("Consensus multi client initialization failed", @@ -255,6 +251,29 @@ func New( client.nodeSyncingFn = client.nodeSyncing + ctx, cancel := context.WithTimeout(client.ctx, client.longTimeout) + defer cancel() + + select { + case <-ctx.Done(): + return nil, fmt.Errorf("timed out awaiting config initialization: %w", ctx.Err()) + case <-client.beaconConfigInit: + } + + if client.beaconConfig == nil { + return nil, fmt.Errorf("no beacon config set") + } + + client.blockRootToSlotCache = ttlcache.New(ttlcache.WithCapacity[phase0.Root, phase0.Slot]( + uint64(client.beaconConfig.SlotsPerEpoch) * BlockRootToSlotCacheCapacityEpochs), + ) + + client.attestationDataCache = ttlcache.New( + // we only fetch attestation data during the slot of the relevant duty (and never later), + // hence caching it for 2 slots is sufficient + ttlcache.WithTTL[phase0.Slot, *phase0.AttestationData](2 * client.beaconConfig.SlotDuration), + ) + go client.registrationSubmitter(slotTickerProvider) // Start automatic expired item deletion for attestationDataCache. go client.attestationDataCache.Start() @@ -316,11 +335,14 @@ func (gc *GoClient) addSingleClient(ctx context.Context, addr string) error { func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { return ð2clienthttp.Hooks{ OnActive: func(ctx context.Context, s *eth2clienthttp.Service) { + logger := gc.log.With( + fields.Name(s.Name()), + fields.Address(s.Address()), + ) // If err is nil, nodeVersionResp is never nil. nodeVersionResp, err := s.NodeVersion(ctx, &api.NodeVersionOpts{}) if err != nil { - gc.log.Error(clResponseErrMsg, - zap.String("address", s.Address()), + logger.Error(clResponseErrMsg, zap.String("api", "NodeVersion"), zap.Error(err), ) @@ -328,28 +350,24 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { } gc.log.Info("consensus client connected", - fields.Name(s.Name()), - fields.Address(s.Address()), zap.String("client", string(ParseNodeClient(nodeVersionResp.Data))), zap.String("version", nodeVersionResp.Data), ) - genesis, err := s.Genesis(ctx, &api.GenesisOpts{}) + beaconConfig, err := gc.fetchBeaconConfig(s) if err != nil { - gc.log.Error(clResponseErrMsg, - zap.String("address", s.Address()), - zap.String("api", "Genesis"), + logger.Error(clResponseErrMsg, + zap.String("api", "fetchBeaconConfig"), zap.Error(err), ) return } - if expected, err := gc.assertSameGenesisVersion(genesis.Data.GenesisForkVersion); err != nil { - gc.log.Fatal("client returned unexpected genesis fork version, make sure all clients use the same Ethereum network", - zap.String("address", s.Address()), - zap.Any("client_genesis", genesis.Data.GenesisForkVersion), - zap.Any("expected_genesis", expected), - zap.Error(err), + currentConfig, err := gc.applyBeaconConfig(s.Address(), beaconConfig) + if err != nil { + logger.Fatal("client returned unexpected beacon config, make sure all clients use the same Ethereum network", + zap.Any("client_config", beaconConfig), + zap.Any("expected_config", currentConfig), ) return // Tests may override Fatal's behavior } @@ -372,8 +390,7 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { return } gc.ForkLock.RLock() - gc.log.Info("retrieved fork epochs", - zap.String("node_addr", s.Address()), + logger.Info("retrieved fork epochs", zap.Uint64("current_data_version", uint64(gc.DataVersion(gc.beaconConfig.EstimatedCurrentEpoch()))), zap.Uint64("altair", uint64(gc.ForkEpochAltair)), zap.Uint64("bellatrix", uint64(gc.ForkEpochBellatrix)), @@ -404,18 +421,31 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { } } -// assertSameGenesis checks if genesis is same. -// Clients may have different values returned by Spec call, -// so we decided that it's best to assert that GenesisForkVersion is the same. -// To add more assertions, we check the whole apiv1.Genesis (GenesisTime and GenesisValidatorsRoot) -// as they should be same too. -func (gc *GoClient) assertSameGenesisVersion(genesisVersion phase0.Version) (phase0.Version, error) { - if gc.genesisForkVersion != genesisVersion { - fmt.Printf("genesis fork version mismatch, expected %v, got %v", gc.genesisForkVersion, genesisVersion) - return gc.genesisForkVersion, fmt.Errorf("genesis fork version mismatch, expected %v, got %v", gc.genesisForkVersion, genesisVersion) +func (gc *GoClient) applyBeaconConfig(nodeAddress string, beaconConfig networkconfig.BeaconConfig) (networkconfig.BeaconConfig, error) { + gc.beaconConfigMu.Lock() + defer gc.beaconConfigMu.Unlock() + + if gc.beaconConfig == nil { + gc.beaconConfig = &beaconConfig + close(gc.beaconConfigInit) + + configDump, err := json.Marshal(beaconConfig) + if err != nil { + return networkconfig.BeaconConfig{}, fmt.Errorf("marshal config: %w", err) + } + + gc.log.Info("beacon config has been initialized", + zap.String("beacon_config", string(configDump)), + fields.Address(nodeAddress), + ) + return beaconConfig, nil + } + + if *gc.beaconConfig != beaconConfig { + return *gc.beaconConfig, fmt.Errorf("beacon config misalign, current %v, got %v", gc.beaconConfig, beaconConfig) } - return gc.genesisForkVersion, nil + return *gc.beaconConfig, nil } func (gc *GoClient) nodeSyncing(ctx context.Context, opts *api.NodeSyncingOpts) (*api.Response[*apiv1.SyncState], error) { diff --git a/beacon/goclient/goclient_test.go b/beacon/goclient/goclient_test.go index 9b69a7202a..54c8500ae0 100644 --- a/beacon/goclient/goclient_test.go +++ b/beacon/goclient/goclient_test.go @@ -15,7 +15,6 @@ import ( "go.uber.org/zap" "github.com/ssvlabs/ssv/beacon/goclient/tests" - "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" ) @@ -138,6 +137,10 @@ func TestTimeouts(t *testing.T) { fastServer := tests.MockServer(func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { time.Sleep(commonTimeout / 2) switch r.URL.Path { + case "/eth/v1/config/spec": + case "/eth/v1/beacon/genesis": + case "/eth/v1/node/syncing": + case "/eth/v1/node/version": case "/eth/v2/debug/beacon/states/head": time.Sleep(longTimeout / 2) } @@ -156,70 +159,11 @@ func TestTimeouts(t *testing.T) { } } -func TestAssertSameGenesisVersionWhenSame(t *testing.T) { - networkConfigs := []networkconfig.NetworkConfig{ - networkconfig.Mainnet, - networkconfig.Holesky, - networkconfig.LocalTestnet, - networkconfig.TestNetwork, - } - - for _, netCfg := range networkConfigs { - ctx := context.Background() - callback := func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { - if r.URL.Path == "/eth/v1/beacon/genesis" { - resp2 := json.RawMessage(fmt.Sprintf(`{"data": { - "genesis_time": "1606824023", - "genesis_validators_root": "0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95", - "genesis_fork_version": "%s" - }}`, netCfg.ForkVersion)) - return resp2, nil - } - return resp, nil - } - - server := tests.MockServer(callback) - defer server.Close() - t.Run(fmt.Sprintf("When genesis versions are the same (%s)", netCfg.BeaconName), func(t *testing.T) { - c, err := mockClientWithNetwork(ctx, server.URL, 100*time.Millisecond, 500*time.Millisecond, netCfg.BeaconConfig) - require.NoError(t, err, "failed to create client") - client := c.(*GoClient) - - output, err := client.assertSameGenesisVersion(netCfg.ForkVersion) - require.Equal(t, netCfg.ForkVersion, output) - require.NoError(t, err, "failed to assert same genesis version: %s", err) - }) - } -} - -func TestAssertSameGenesisVersionWhenDifferent(t *testing.T) { - networkConfig := networkconfig.Mainnet - - t.Run("When genesis versions are different", func(t *testing.T) { - ctx := context.Background() - server := tests.MockServer(nil) - defer server.Close() - c, err := mockClientWithNetwork(ctx, server.URL, 100*time.Millisecond, 500*time.Millisecond, networkConfig.BeaconConfig) - require.NoError(t, err, "failed to create client") - client := c.(*GoClient) - forkVersion := phase0.Version{0x01, 0x02, 0x03, 0x04} - - output, err := client.assertSameGenesisVersion(forkVersion) - require.Equal(t, networkConfig.ForkVersion, output, "expected genesis version to be %s, got %s", networkConfig.ForkVersion, output) - require.Error(t, err, "expected error when genesis versions are different") - }) -} - func mockClient(ctx context.Context, serverURL string, commonTimeout, longTimeout time.Duration) (beacon.BeaconNode, error) { - return mockClientWithNetwork(ctx, serverURL, commonTimeout, longTimeout, networkconfig.Mainnet.BeaconConfig) -} - -func mockClientWithNetwork(ctx context.Context, serverURL string, commonTimeout, longTimeout time.Duration, beaconConfig networkconfig.BeaconConfig) (beacon.BeaconNode, error) { return New( zap.NewNop(), beacon.Options{ Context: ctx, - BeaconConfig: beaconConfig, BeaconNodeAddr: serverURL, CommonTimeout: commonTimeout, LongTimeout: longTimeout, diff --git a/beacon/goclient/spec.go b/beacon/goclient/spec.go new file mode 100644 index 0000000000..2249cdc949 --- /dev/null +++ b/beacon/goclient/spec.go @@ -0,0 +1,102 @@ +package goclient + +import ( + "fmt" + "net/http" + "time" + + "github.com/attestantio/go-eth2-client/api" + eth2clienthttp "github.com/attestantio/go-eth2-client/http" + "github.com/attestantio/go-eth2-client/spec/phase0" + "go.uber.org/zap" + + "github.com/ssvlabs/ssv/networkconfig" +) + +const ( + DefaultSlotDuration = 12 * time.Second + DefaultSlotsPerEpoch = phase0.Slot(32) +) + +// BeaconConfig must be called if GoClient is initialized (gc.beaconConfig is set) +func (gc *GoClient) BeaconConfig() networkconfig.BeaconConfig { + // It should always be non-nil for external calls because it's initialized in GoClient's constructor. + return *gc.beaconConfig +} + +// fetchBeaconConfig must be called once on GoClient's initialization +func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkconfig.BeaconConfig, error) { + start := time.Now() + specResponse, err := client.Spec(gc.ctx, &api.SpecOpts{}) + recordRequestDuration(gc.ctx, "Spec", client.Address(), http.MethodGet, time.Since(start), err) + if err != nil { + gc.log.Error(clResponseErrMsg, zap.String("api", "Spec"), zap.Error(err)) + return networkconfig.BeaconConfig{}, fmt.Errorf("failed to obtain spec response: %w", err) + } + if specResponse == nil { + gc.log.Error(clNilResponseErrMsg, zap.String("api", "Spec")) + return networkconfig.BeaconConfig{}, fmt.Errorf("spec response is nil") + } + if specResponse.Data == nil { + gc.log.Error(clNilResponseDataErrMsg, zap.String("api", "Spec")) + return networkconfig.BeaconConfig{}, fmt.Errorf("spec response data is nil") + } + + // types of most values are already cast: https://github.com/attestantio/go-eth2-client/blob/v0.21.7/http/spec.go#L78 + + networkNameRaw, ok := specResponse.Data["CONFIG_NAME"] + if !ok { + return networkconfig.BeaconConfig{}, fmt.Errorf("config name not known by chain") + } + + networkName, ok := networkNameRaw.(string) + if !ok { + return networkconfig.BeaconConfig{}, fmt.Errorf("failed to decode config name") + } + + slotDuration := DefaultSlotDuration + if slotDurationRaw, ok := specResponse.Data["SECONDS_PER_SLOT"]; ok { + if slotDurationDecoded, ok := slotDurationRaw.(time.Duration); ok { + slotDuration = slotDurationDecoded + } else { + gc.log.Warn("seconds per slot not known by chain, using default value", + zap.Any("value", slotDuration)) + } + } + + slotsPerEpoch := DefaultSlotsPerEpoch + if slotsPerEpochRaw, ok := specResponse.Data["SLOTS_PER_EPOCH"]; ok { + if slotsPerEpochDecoded, ok := slotsPerEpochRaw.(uint64); ok { + slotsPerEpoch = phase0.Slot(slotsPerEpochDecoded) + } else { + gc.log.Warn("slots per epoch not known by chain, using default value", + zap.Any("value", slotsPerEpoch)) + } + } + + start = time.Now() + genesisResponse, err := client.Genesis(gc.ctx, &api.GenesisOpts{}) + recordRequestDuration(gc.ctx, "Genesis", client.Address(), http.MethodGet, time.Since(start), err) + if err != nil { + gc.log.Error(clResponseErrMsg, zap.String("api", "Genesis"), zap.Error(err)) + return networkconfig.BeaconConfig{}, fmt.Errorf("failed to obtain genesis response: %w", err) + } + if genesisResponse == nil { + gc.log.Error(clNilResponseErrMsg, zap.String("api", "Genesis")) + return networkconfig.BeaconConfig{}, fmt.Errorf("genesis response is nil") + } + if genesisResponse.Data == nil { + gc.log.Error(clNilResponseDataErrMsg, zap.String("api", "Genesis")) + return networkconfig.BeaconConfig{}, fmt.Errorf("genesis response data is nil") + } + + beaconConfig := networkconfig.BeaconConfig{ + BeaconName: networkName, + SlotDuration: slotDuration, + SlotsPerEpoch: slotsPerEpoch, + ForkVersion: genesisResponse.Data.GenesisForkVersion, + GenesisTime: genesisResponse.Data.GenesisTime, + } + + return beaconConfig, nil +} diff --git a/cli/operator/node.go b/cli/operator/node.go index 3e68f43476..b570ce8d1e 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -202,7 +202,6 @@ var StartNodeCmd = &cobra.Command{ } cfg.ConsensusClient.Context = cmd.Context() - cfg.ConsensusClient.BeaconConfig = networkConfig.BeaconConfig consensusClient := setupConsensusClient(logger, operatorDataStore, slotTickerProvider) @@ -700,7 +699,6 @@ func setupSSVNetwork(logger *zap.Logger) (networkconfig.NetworkConfig, error) { fields.Domain(networkConfig.DomainType), zap.String("nodeType", nodeType), zap.Any("beaconNetwork", networkConfig.GetBeaconName()), - zap.Uint64("genesisEpoch", uint64(networkConfig.GenesisEpoch)), zap.String("registryContract", networkConfig.RegistryContractAddr), ) diff --git a/config/config.exporter.example.yaml b/config/config.exporter.example.yaml index 9953ad453a..9a44dc1b17 100644 --- a/config/config.exporter.example.yaml +++ b/config/config.exporter.example.yaml @@ -21,7 +21,6 @@ p2p: # Discovery: mdns ssv: - GenesisEpoch: ValidatorOptions: SignatureCollectionTimeout: 5s FullNode: true diff --git a/networkconfig/beacon.go b/networkconfig/beacon.go index bea4a5bdc9..512c8eb6f4 100644 --- a/networkconfig/beacon.go +++ b/networkconfig/beacon.go @@ -33,7 +33,6 @@ type Beacon interface { type BeaconConfig struct { BeaconName string - GenesisEpoch phase0.Epoch SlotDuration time.Duration SlotsPerEpoch phase0.Slot ForkVersion phase0.Version diff --git a/networkconfig/holesky-e2e.go b/networkconfig/holesky-e2e.go index 852b3c94b5..d005809910 100644 --- a/networkconfig/holesky-e2e.go +++ b/networkconfig/holesky-e2e.go @@ -12,7 +12,6 @@ var HoleskyE2E = NetworkConfig{ Name: "holesky-e2e", BeaconConfig: BeaconConfig{ BeaconName: string(spectypes.HoleskyNetwork), - GenesisEpoch: 1, SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), diff --git a/networkconfig/holesky-stage.go b/networkconfig/holesky-stage.go index 87c904a013..153c51265a 100644 --- a/networkconfig/holesky-stage.go +++ b/networkconfig/holesky-stage.go @@ -12,7 +12,6 @@ var HoleskyStage = NetworkConfig{ Name: "holesky-stage", BeaconConfig: BeaconConfig{ BeaconName: string(spectypes.HoleskyNetwork), - GenesisEpoch: 1, SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), diff --git a/networkconfig/holesky.go b/networkconfig/holesky.go index d1974200d6..77300907a1 100644 --- a/networkconfig/holesky.go +++ b/networkconfig/holesky.go @@ -12,7 +12,6 @@ var Holesky = NetworkConfig{ Name: "holesky", BeaconConfig: BeaconConfig{ BeaconName: string(spectypes.HoleskyNetwork), - GenesisEpoch: 1, SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), diff --git a/networkconfig/hoodi-stage.go b/networkconfig/hoodi-stage.go index 12a013aeff..4f3e1232a0 100644 --- a/networkconfig/hoodi-stage.go +++ b/networkconfig/hoodi-stage.go @@ -12,7 +12,6 @@ var HoodiStage = NetworkConfig{ Name: "hoodi-stage", BeaconConfig: BeaconConfig{ BeaconName: string(spectypes.HoleskyNetwork), - GenesisEpoch: 1, SlotDuration: spectypes.HoodiNetwork.SlotDurationSec(), SlotsPerEpoch: phase0.Slot(spectypes.HoodiNetwork.SlotsPerEpoch()), ForkVersion: spectypes.HoodiNetwork.ForkVersion(), diff --git a/networkconfig/hoodi.go b/networkconfig/hoodi.go index e3a1987c83..551d030301 100644 --- a/networkconfig/hoodi.go +++ b/networkconfig/hoodi.go @@ -12,7 +12,6 @@ var Hoodi = NetworkConfig{ Name: "hoodi", BeaconConfig: BeaconConfig{ BeaconName: string(spectypes.HoodiNetwork), - GenesisEpoch: 1, SlotDuration: spectypes.HoodiNetwork.SlotDurationSec(), SlotsPerEpoch: phase0.Slot(spectypes.HoodiNetwork.SlotsPerEpoch()), ForkVersion: spectypes.HoodiNetwork.ForkVersion(), diff --git a/networkconfig/local-testnet.go b/networkconfig/local-testnet.go index 868151da9b..7c96e2c4f8 100644 --- a/networkconfig/local-testnet.go +++ b/networkconfig/local-testnet.go @@ -11,7 +11,6 @@ var LocalTestnet = NetworkConfig{ Name: "local-testnet", BeaconConfig: BeaconConfig{ BeaconName: string(spectypes.PraterNetwork), - GenesisEpoch: 1, SlotDuration: spectypes.PraterNetwork.SlotDurationSec(), SlotsPerEpoch: phase0.Slot(spectypes.PraterNetwork.SlotsPerEpoch()), ForkVersion: spectypes.PraterNetwork.ForkVersion(), diff --git a/networkconfig/mainnet.go b/networkconfig/mainnet.go index 05f0432a59..56bdeb0b73 100644 --- a/networkconfig/mainnet.go +++ b/networkconfig/mainnet.go @@ -12,7 +12,6 @@ var Mainnet = NetworkConfig{ Name: "mainnet", BeaconConfig: BeaconConfig{ BeaconName: string(spectypes.MainNetwork), - GenesisEpoch: 218450, SlotDuration: spectypes.MainNetwork.SlotDurationSec(), SlotsPerEpoch: phase0.Slot(spectypes.MainNetwork.SlotsPerEpoch()), ForkVersion: spectypes.MainNetwork.ForkVersion(), diff --git a/networkconfig/sepolia.go b/networkconfig/sepolia.go index 7eb7133b65..5359d1059d 100644 --- a/networkconfig/sepolia.go +++ b/networkconfig/sepolia.go @@ -12,7 +12,6 @@ var Sepolia = NetworkConfig{ Name: "sepolia", BeaconConfig: BeaconConfig{ BeaconName: string(spectypes.SepoliaNetwork), - GenesisEpoch: 1, SlotDuration: spectypes.SepoliaNetwork.SlotDurationSec(), SlotsPerEpoch: phase0.Slot(spectypes.SepoliaNetwork.SlotsPerEpoch()), ForkVersion: spectypes.SepoliaNetwork.ForkVersion(), diff --git a/networkconfig/test-network.go b/networkconfig/test-network.go index 19659cec16..9bda6da4b0 100644 --- a/networkconfig/test-network.go +++ b/networkconfig/test-network.go @@ -12,7 +12,6 @@ var TestNetwork = NetworkConfig{ Name: "testnet", BeaconConfig: BeaconConfig{ BeaconName: string(spectypes.BeaconTestNetwork), - GenesisEpoch: 152834, SlotDuration: spectypes.BeaconTestNetwork.SlotDurationSec(), SlotsPerEpoch: phase0.Slot(spectypes.BeaconTestNetwork.SlotsPerEpoch()), ForkVersion: spectypes.BeaconTestNetwork.ForkVersion(), diff --git a/protocol/v2/blockchain/beacon/client.go b/protocol/v2/blockchain/beacon/client.go index b228cc52e6..dbb4d65449 100644 --- a/protocol/v2/blockchain/beacon/client.go +++ b/protocol/v2/blockchain/beacon/client.go @@ -8,8 +8,6 @@ import ( "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/phase0" specssv "github.com/ssvlabs/ssv-spec/ssv" - - "github.com/ssvlabs/ssv/networkconfig" ) // TODO: add missing tests @@ -70,7 +68,6 @@ type BeaconNode interface { // Options for controller struct creation type Options struct { Context context.Context - BeaconConfig networkconfig.BeaconConfig BeaconNodeAddr string `yaml:"BeaconNodeAddr" env:"BEACON_NODE_ADDR" env-required:"true" env-description:"Beacon node URL(s). Multiple nodes are supported via semicolon-separated URLs (e.g. 'http://localhost:5052;http://localhost:5053')"` SyncDistanceTolerance uint64 `yaml:"SyncDistanceTolerance" env:"BEACON_SYNC_DISTANCE_TOLERANCE" env-default:"4" env-description:"Maximum number of slots behind head considered in-sync"` WithWeightedAttestationData bool `yaml:"WithWeightedAttestationData" env:"WITH_WEIGHTED_ATTESTATION_DATA" env-default:"false" env-description:"Enable attestation data scoring across multiple beacon nodes"` From c3f352762d2c1d646d6fdc5d01ab9881da730f35 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 16 Apr 2025 20:02:01 -0300 Subject: [PATCH 02/38] use beacon config obtained from beacon node --- beacon/goclient/attest_test.go | 17 ----- beacon/goclient/events_test.go | 4 +- beacon/goclient/goclient.go | 17 ++--- beacon/goclient/goclient_test.go | 2 - cli/bootnode/boot_node.go | 4 +- cli/operator/node.go | 76 +++++++++---------- exporter/api/query_handlers_test.go | 12 +-- ...onfiglock_add_alan_fork_to_network_name.go | 7 -- networkconfig/network.go | 19 ----- networkconfig/ssv.go | 20 +++++ utils/boot_node/node.go | 12 +-- 11 files changed, 78 insertions(+), 112 deletions(-) diff --git a/beacon/goclient/attest_test.go b/beacon/goclient/attest_test.go index 0877a533fd..30be38ed19 100644 --- a/beacon/goclient/attest_test.go +++ b/beacon/goclient/attest_test.go @@ -18,10 +18,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap" - operatordatastore "github.com/ssvlabs/ssv/operator/datastore" - "github.com/ssvlabs/ssv/operator/slotticker" "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" - registrystorage "github.com/ssvlabs/ssv/registry/storage" "github.com/ssvlabs/ssv/utils/hashmap" ) @@ -197,13 +194,6 @@ func TestGoClient_GetAttestationData_Simple(t *testing.T) { CommonTimeout: 1 * time.Second, LongTimeout: 1 * time.Second, }, - operatordatastore.New(®istrystorage.OperatorData{ID: 1}), - func() slotticker.SlotTicker { - return slotticker.New(zap.NewNop(), slotticker.Config{ - SlotDuration: 12 * time.Second, - GenesisTime: time.Now(), - }) - }, ) require.NoError(t, err) @@ -455,13 +445,6 @@ func createClient( LongTimeout: time.Second, WithWeightedAttestationData: withWeightedAttestationData, }, - operatordatastore.New(®istrystorage.OperatorData{ID: 1}), - func() slotticker.SlotTicker { - return slotticker.New(zap.NewNop(), slotticker.Config{ - SlotDuration: 12 * time.Second, - GenesisTime: time.Now(), - }) - }, ) return client, err } diff --git a/beacon/goclient/events_test.go b/beacon/goclient/events_test.go index 8b02687d42..e2d16691ce 100644 --- a/beacon/goclient/events_test.go +++ b/beacon/goclient/events_test.go @@ -75,9 +75,7 @@ func eventsTestClient(t *testing.T, serverURL string) *GoClient { server, err := New(zap.NewNop(), beacon.Options{ BeaconNodeAddr: serverURL, Context: context.Background(), - }, - tests.MockDataStore{}, - tests.MockSlotTickerProvider) + }) require.NoError(t, err) return server diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 78913bcfaa..56df412931 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -20,7 +20,6 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog" specssv "github.com/ssvlabs/ssv-spec/ssv" - spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" "tailscale.com/util/singleflight" @@ -112,10 +111,6 @@ type MultiClient interface { eth2client.ValidatorLivenessProvider } -type operatorDataStore interface { - AwaitOperatorID() spectypes.OperatorID -} - type EventTopic string const ( @@ -138,8 +133,6 @@ type GoClient struct { syncDistanceTolerance phase0.Slot nodeSyncingFn func(ctx context.Context, opts *api.NodeSyncingOpts) (*api.Response[*apiv1.SyncState], error) - operatorDataStore operatorDataStore - // registrationMu synchronises access to registrations registrationMu sync.Mutex // registrations is a set of validator-registrations (their latest versions) to be sent to @@ -190,8 +183,6 @@ type GoClient struct { func New( logger *zap.Logger, opt beaconprotocol.Options, - operatorDataStore operatorDataStore, - slotTickerProvider slotticker.Provider, ) (*GoClient, error) { logger.Info("consensus client: connecting", fields.Address(opt.BeaconNodeAddr)) @@ -209,7 +200,6 @@ func New( ctx: opt.Context, beaconConfigInit: make(chan struct{}), syncDistanceTolerance: phase0.Slot(opt.SyncDistanceTolerance), - operatorDataStore: operatorDataStore, registrations: map[phase0.BLSPubKey]*validatorRegistration{}, commonTimeout: commonTimeout, longTimeout: longTimeout, @@ -274,6 +264,13 @@ func New( ttlcache.WithTTL[phase0.Slot, *phase0.AttestationData](2 * client.beaconConfig.SlotDuration), ) + slotTickerProvider := func() slotticker.SlotTicker { + return slotticker.New(logger, slotticker.Config{ + SlotDuration: client.beaconConfig.SlotDuration, + GenesisTime: client.beaconConfig.GenesisTime, + }) + } + go client.registrationSubmitter(slotTickerProvider) // Start automatic expired item deletion for attestationDataCache. go client.attestationDataCache.Start() diff --git a/beacon/goclient/goclient_test.go b/beacon/goclient/goclient_test.go index 54c8500ae0..671ed196cc 100644 --- a/beacon/goclient/goclient_test.go +++ b/beacon/goclient/goclient_test.go @@ -168,7 +168,5 @@ func mockClient(ctx context.Context, serverURL string, commonTimeout, longTimeou CommonTimeout: commonTimeout, LongTimeout: longTimeout, }, - tests.MockDataStore{}, - tests.MockSlotTickerProvider, ) } diff --git a/cli/bootnode/boot_node.go b/cli/bootnode/boot_node.go index fc9d7499ed..f8407861be 100644 --- a/cli/bootnode/boot_node.go +++ b/cli/bootnode/boot_node.go @@ -54,14 +54,16 @@ var StartBootNodeCmd = &cobra.Command{ logger.Info(fmt.Sprintf("starting %v", commons.GetBuildData())) - networkConfig, err := networkconfig.GetNetworkConfigByName(cfg.Options.Network) + networkConfig, err := networkconfig.GetSSVConfigByName(cfg.Options.Network) if err != nil { logger.Fatal("failed to get network config", zap.Error(err)) } + bootNode, err := bootnode.New(networkConfig, cfg.Options) if err != nil { logger.Fatal("failed to set up boot node", zap.Error(err)) } + if err := bootNode.Start(cmd.Context(), logger); err != nil { logger.Fatal("failed to start boot node", zap.Error(err)) } diff --git a/cli/operator/node.go b/cli/operator/node.go index b570ce8d1e..fcfaa0913e 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -129,10 +129,24 @@ var StartNodeCmd = &cobra.Command{ } }() - networkConfig, err := setupSSVNetwork(logger) + ssvNetworkConfig, err := setupSSVNetwork(logger) if err != nil { logger.Fatal("could not setup network", zap.Error(err)) } + + cfg.ConsensusClient.Context = cmd.Context() + + consensusClient, err := goclient.New(logger, cfg.ConsensusClient) + if err != nil { + logger.Fatal("failed to create beacon go-client", zap.Error(err), + fields.Address(cfg.ConsensusClient.BeaconNodeAddr)) + } + + networkConfig := networkconfig.NetworkConfig{ + SSVConfig: ssvNetworkConfig, + BeaconConfig: consensusClient.BeaconConfig(), + } + cfg.DBOptions.Ctx = cmd.Context() db, err := setupDB(logger, networkConfig.BeaconConfig) if err != nil { @@ -194,17 +208,6 @@ var StartNodeCmd = &cobra.Command{ cfg.P2pNetworkConfig.Ctx = cmd.Context() - slotTickerProvider := func() slotticker.SlotTicker { - return slotticker.New(logger, slotticker.Config{ - SlotDuration: networkConfig.SlotDuration, - GenesisTime: networkConfig.GenesisTime, - }) - } - - cfg.ConsensusClient.Context = cmd.Context() - - consensusClient := setupConsensusClient(logger, operatorDataStore, slotTickerProvider) - executionAddrList := strings.Split(cfg.ExecutionClient.Addr, ";") // TODO: Decide what symbol to use as a separator. Bootnodes are currently separated by ";". Deployment bot currently uses ",". if len(executionAddrList) == 0 { logger.Fatal("no execution node address provided") @@ -216,7 +219,7 @@ var StartNodeCmd = &cobra.Command{ ec, err := executionclient.New( cmd.Context(), executionAddrList[0], - ethcommon.HexToAddress(networkConfig.RegistryContractAddr), + ethcommon.HexToAddress(ssvNetworkConfig.RegistryContractAddr), executionclient.WithLogger(logger), executionclient.WithFollowDistance(executionclient.DefaultFollowDistance), executionclient.WithConnectionTimeout(cfg.ExecutionClient.ConnectionTimeout), @@ -234,7 +237,7 @@ var StartNodeCmd = &cobra.Command{ ec, err := executionclient.NewMulti( cmd.Context(), executionAddrList, - ethcommon.HexToAddress(networkConfig.RegistryContractAddr), + ethcommon.HexToAddress(ssvNetworkConfig.RegistryContractAddr), executionclient.WithLoggerMulti(logger), executionclient.WithFollowDistanceMulti(executionclient.DefaultFollowDistance), executionclient.WithConnectionTimeoutMulti(cfg.ExecutionClient.ConnectionTimeout), @@ -321,6 +324,13 @@ var StartNodeCmd = &cobra.Command{ storageMap.Add(storageRole, s) } + slotTickerProvider := func() slotticker.SlotTicker { + return slotticker.New(logger, slotticker.Config{ + SlotDuration: networkConfig.SlotDuration, + GenesisTime: networkConfig.GenesisTime, + }) + } + if cfg.SSVOptions.ValidatorOptions.Exporter { retain := cfg.SSVOptions.ValidatorOptions.ExporterRetainSlots threshold := cfg.SSVOptions.NetworkConfig.EstimatedCurrentSlot() @@ -662,30 +672,30 @@ func setupOperatorStorage(logger *zap.Logger, db basedb.Database, configPrivKey return nodeStorage, operatorData } -func setupSSVNetwork(logger *zap.Logger) (networkconfig.NetworkConfig, error) { - networkConfig, err := networkconfig.GetNetworkConfigByName(cfg.SSVOptions.NetworkName) +func setupSSVNetwork(logger *zap.Logger) (networkconfig.SSVConfig, error) { + ssvConfig, err := networkconfig.GetSSVConfigByName(cfg.SSVOptions.NetworkName) if err != nil { - return networkconfig.NetworkConfig{}, err + return networkconfig.SSVConfig{}, err } if cfg.SSVOptions.CustomDomainType != "" { if !strings.HasPrefix(cfg.SSVOptions.CustomDomainType, "0x") { - return networkconfig.NetworkConfig{}, errors.New("custom domain type must be a hex string") + return networkconfig.SSVConfig{}, errors.New("custom domain type must be a hex string") } domainBytes, err := hex.DecodeString(cfg.SSVOptions.CustomDomainType[2:]) if err != nil { - return networkconfig.NetworkConfig{}, errors.Wrap(err, "failed to decode custom domain type") + return networkconfig.SSVConfig{}, errors.Wrap(err, "failed to decode custom domain type") } if len(domainBytes) != 4 { - return networkconfig.NetworkConfig{}, errors.New("custom domain type must be 4 bytes") + return networkconfig.SSVConfig{}, errors.New("custom domain type must be 4 bytes") } // https://github.com/ssvlabs/ssv/pull/1808 incremented the post-fork domain type by 1, so we have to maintain the compatibility. postForkDomain := binary.BigEndian.Uint32(domainBytes) + 1 - binary.BigEndian.PutUint32(networkConfig.DomainType[:], postForkDomain) + binary.BigEndian.PutUint32(ssvConfig.DomainType[:], postForkDomain) logger.Info("running with custom domain type", - fields.Domain(networkConfig.DomainType), + fields.Domain(ssvConfig.DomainType), ) } @@ -695,14 +705,12 @@ func setupSSVNetwork(logger *zap.Logger) (networkconfig.NetworkConfig, error) { } logger.Info("setting ssv network", - fields.Network(networkConfig.Name), - fields.Domain(networkConfig.DomainType), + zap.Any("config", ssvConfig), zap.String("nodeType", nodeType), - zap.Any("beaconNetwork", networkConfig.GetBeaconName()), - zap.String("registryContract", networkConfig.RegistryContractAddr), + zap.String("registryContract", ssvConfig.RegistryContractAddr), ) - return networkConfig, nil + return ssvConfig, nil } func setupP2P(logger *zap.Logger, db basedb.Database) network.P2PNetwork { @@ -720,20 +728,6 @@ func setupP2P(logger *zap.Logger, db basedb.Database) network.P2PNetwork { return n } -func setupConsensusClient( - logger *zap.Logger, - operatorDataStore operatordatastore.OperatorDataStore, - slotTickerProvider slotticker.Provider, -) *goclient.GoClient { - cl, err := goclient.New(logger, cfg.ConsensusClient, operatorDataStore, slotTickerProvider) - if err != nil { - logger.Fatal("failed to create beacon go-client", zap.Error(err), - fields.Address(cfg.ConsensusClient.BeaconNodeAddr)) - } - - return cl -} - // syncContractEvents blocks until historical events are synced and then spawns a goroutine syncing ongoing events. func syncContractEvents( ctx context.Context, diff --git a/exporter/api/query_handlers_test.go b/exporter/api/query_handlers_test.go index 9712ea4bd5..db63823406 100644 --- a/exporter/api/query_handlers_test.go +++ b/exporter/api/query_handlers_test.go @@ -104,7 +104,7 @@ func TestHandleDecidedQuery(t *testing.T) { for _, role := range roles { pk := sks[1].GetPublicKey() - networkConfig, err := networkconfig.GetNetworkConfigByName(networkconfig.HoleskyStage.Name) + ssvConfig, err := networkconfig.GetSSVConfigByName(networkconfig.HoleskyStage.Name) require.NoError(t, err) decided250Seq, err := protocoltesting.CreateMultipleStoredInstances(rsaKeys, specqbft.Height(0), specqbft.Height(250), func(height specqbft.Height) ([]spectypes.OperatorID, *specqbft.Message) { return oids, &specqbft.Message{ @@ -129,7 +129,7 @@ func TestHandleDecidedQuery(t *testing.T) { t.Run("valid range", func(t *testing.T) { nm := newParticipantsAPIMsg(pk.SerializeToHexStr(), spectypes.BNRoleAttester, 0, 250) - HandleParticipantsQuery(l, ibftStorage, nm, networkConfig.DomainType) + HandleParticipantsQuery(l, ibftStorage, nm, ssvConfig.DomainType) require.NotNil(t, nm.Msg.Data) msgs, ok := nm.Msg.Data.([]*ParticipantsAPI) @@ -139,7 +139,7 @@ func TestHandleDecidedQuery(t *testing.T) { t.Run("invalid range", func(t *testing.T) { nm := newParticipantsAPIMsg(pk.SerializeToHexStr(), spectypes.BNRoleAttester, 400, 404) - HandleParticipantsQuery(l, ibftStorage, nm, networkConfig.DomainType) + HandleParticipantsQuery(l, ibftStorage, nm, ssvConfig.DomainType) require.NotNil(t, nm.Msg.Data) data, ok := nm.Msg.Data.([]string) require.True(t, ok) @@ -148,7 +148,7 @@ func TestHandleDecidedQuery(t *testing.T) { t.Run("non-existing validator", func(t *testing.T) { nm := newParticipantsAPIMsg("xxx", spectypes.BNRoleAttester, 400, 404) - HandleParticipantsQuery(l, ibftStorage, nm, networkConfig.DomainType) + HandleParticipantsQuery(l, ibftStorage, nm, ssvConfig.DomainType) require.NotNil(t, nm.Msg.Data) errs, ok := nm.Msg.Data.([]string) require.True(t, ok) @@ -157,7 +157,7 @@ func TestHandleDecidedQuery(t *testing.T) { t.Run("non-existing role", func(t *testing.T) { nm := newParticipantsAPIMsg(pk.SerializeToHexStr(), math.MaxUint64, 0, 250) - HandleParticipantsQuery(l, ibftStorage, nm, networkConfig.DomainType) + HandleParticipantsQuery(l, ibftStorage, nm, ssvConfig.DomainType) require.NotNil(t, nm.Msg.Data) errs, ok := nm.Msg.Data.([]string) require.True(t, ok) @@ -166,7 +166,7 @@ func TestHandleDecidedQuery(t *testing.T) { t.Run("non-existing storage", func(t *testing.T) { nm := newParticipantsAPIMsg(pk.SerializeToHexStr(), spectypes.BNRoleSyncCommitteeContribution, 0, 250) - HandleParticipantsQuery(l, ibftStorage, nm, networkConfig.DomainType) + HandleParticipantsQuery(l, ibftStorage, nm, ssvConfig.DomainType) require.NotNil(t, nm.Msg.Data) errs, ok := nm.Msg.Data.([]string) require.True(t, ok) diff --git a/migrations/migration_4_configlock_add_alan_fork_to_network_name.go b/migrations/migration_4_configlock_add_alan_fork_to_network_name.go index 6a49712c01..03e99cc5f9 100644 --- a/migrations/migration_4_configlock_add_alan_fork_to_network_name.go +++ b/migrations/migration_4_configlock_add_alan_fork_to_network_name.go @@ -6,7 +6,6 @@ import ( "go.uber.org/zap" - "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/storage/basedb" ) @@ -27,12 +26,6 @@ var migration_4_configlock_add_alan_fork_to_network_name = Migration{ // If config is not found, it means the node is not initialized yet if found { - networkConfig, err := networkconfig.GetNetworkConfigByName(config.NetworkName) - if err != nil { - return fmt.Errorf("failed to get network config by name: %w", err) - } - - config.NetworkName = networkConfig.NetworkName() if err := nodeStorage.SaveConfig(txn, config); err != nil { return fmt.Errorf("failed to save config: %w", err) } diff --git a/networkconfig/network.go b/networkconfig/network.go index e70bf5f839..67c6f791eb 100644 --- a/networkconfig/network.go +++ b/networkconfig/network.go @@ -7,27 +7,8 @@ import ( //go:generate go tool -modfile=../tool.mod mockgen -package=networkconfig -destination=./network_mock.go -source=./network.go -var SupportedConfigs = map[string]NetworkConfig{ - Mainnet.Name: Mainnet, - Holesky.Name: Holesky, - HoleskyStage.Name: HoleskyStage, - LocalTestnet.Name: LocalTestnet, - HoleskyE2E.Name: HoleskyE2E, - Hoodi.Name: Hoodi, - HoodiStage.Name: HoodiStage, - Sepolia.Name: Sepolia, -} - const forkName = "alan" -func GetNetworkConfigByName(name string) (NetworkConfig, error) { - if network, ok := SupportedConfigs[name]; ok { - return network, nil - } - - return NetworkConfig{}, fmt.Errorf("network not supported: %v", name) -} - type Network interface { NetworkName() string Beacon diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index 50ae99370e..6052257523 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -1,6 +1,7 @@ package networkconfig import ( + "fmt" "math/big" spectypes "github.com/ssvlabs/ssv-spec/types" @@ -8,6 +9,25 @@ import ( //go:generate go tool -modfile=../tool.mod mockgen -package=networkconfig -destination=./ssv_mock.go -source=./ssv.go +var SupportedSSVConfigs = map[string]SSVConfig{ + Mainnet.Name: Mainnet.SSVConfig, + Holesky.Name: Holesky.SSVConfig, + HoleskyStage.Name: HoleskyStage.SSVConfig, + LocalTestnet.Name: LocalTestnet.SSVConfig, + HoleskyE2E.Name: HoleskyE2E.SSVConfig, + Hoodi.Name: Hoodi.SSVConfig, + HoodiStage.Name: HoodiStage.SSVConfig, + Sepolia.Name: Sepolia.SSVConfig, +} + +func GetSSVConfigByName(name string) (SSVConfig, error) { + if network, ok := SupportedSSVConfigs[name]; ok { + return network, nil + } + + return SSVConfig{}, fmt.Errorf("network not supported: %v", name) +} + type SSV interface { GetDomainType() spectypes.DomainType } diff --git a/utils/boot_node/node.go b/utils/boot_node/node.go index b36c13c311..9f478a64c4 100644 --- a/utils/boot_node/node.go +++ b/utils/boot_node/node.go @@ -49,11 +49,11 @@ type bootNode struct { externalIP string tcpPort uint16 dbPath string - network networkconfig.NetworkConfig + ssvConfig networkconfig.SSVConfig } // New is the constructor of ssvNode -func New(networkConfig networkconfig.NetworkConfig, opts Options) (Node, error) { +func New(ssvConfig networkconfig.SSVConfig, opts Options) (Node, error) { return &bootNode{ privateKey: opts.PrivateKey, discv5port: opts.UDPPort, @@ -61,7 +61,7 @@ func New(networkConfig networkconfig.NetworkConfig, opts Options) (Node, error) externalIP: opts.ExternalIP, tcpPort: opts.TCPPort, dbPath: opts.DbPath, - network: networkConfig, + ssvConfig: ssvConfig, }, nil } @@ -107,8 +107,8 @@ func (n *bootNode) Start(ctx context.Context, logger *zap.Logger) error { node := listener.LocalNode().Node() logger.Info("Running", zap.String("node", node.String()), - zap.String("network", n.network.Name), - fields.ProtocolID(n.network.DiscoveryProtocolID), + zap.Any("config", n.ssvConfig), + fields.ProtocolID(n.ssvConfig.DiscoveryProtocolID), ) handler := &handler{ @@ -166,7 +166,7 @@ func (n *bootNode) createListener(logger *zap.Logger, ipAddr string, port uint16 listener, err := discover.ListenV5(conn, localNode, discover.Config{ PrivateKey: privateKey, - V5ProtocolID: &n.network.DiscoveryProtocolID, + V5ProtocolID: &n.ssvConfig.DiscoveryProtocolID, }) if err != nil { log.Fatal(err) From e57ef037e5a37b22f09a733e7727d6ec1d0a23fb Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 16 Apr 2025 21:08:02 -0300 Subject: [PATCH 03/38] log config as JSON --- beacon/goclient/goclient.go | 20 ++++++-------------- networkconfig/beacon.go | 10 ++++++++++ networkconfig/network.go | 6 +++--- networkconfig/ssv.go | 14 ++++++++++++-- utils/boot_node/node.go | 2 +- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 56df412931..a4b830a075 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -2,7 +2,6 @@ package goclient import ( "context" - "encoding/json" "fmt" "math" "strings" @@ -346,7 +345,7 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { return } - gc.log.Info("consensus client connected", + logger.Info("consensus client connected", zap.String("client", string(ParseNodeClient(nodeVersionResp.Data))), zap.String("version", nodeVersionResp.Data), ) @@ -363,16 +362,15 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { currentConfig, err := gc.applyBeaconConfig(s.Address(), beaconConfig) if err != nil { logger.Fatal("client returned unexpected beacon config, make sure all clients use the same Ethereum network", - zap.Any("client_config", beaconConfig), - zap.Any("expected_config", currentConfig), + zap.Stringer("client_config", beaconConfig), + zap.Stringer("expected_config", currentConfig), ) return // Tests may override Fatal's behavior } spec, err := s.Spec(ctx, &api.SpecOpts{}) if err != nil { - gc.log.Error(clResponseErrMsg, - zap.String("address", s.Address()), + logger.Error(clResponseErrMsg, zap.String("api", "Spec"), zap.Error(err), ) @@ -380,8 +378,7 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { } if err := gc.checkForkValues(spec); err != nil { - gc.log.Error("failed to check fork values", - zap.String("address", s.Address()), + logger.Error("failed to check fork values", zap.Error(err), ) return @@ -426,13 +423,8 @@ func (gc *GoClient) applyBeaconConfig(nodeAddress string, beaconConfig networkco gc.beaconConfig = &beaconConfig close(gc.beaconConfigInit) - configDump, err := json.Marshal(beaconConfig) - if err != nil { - return networkconfig.BeaconConfig{}, fmt.Errorf("marshal config: %w", err) - } - gc.log.Info("beacon config has been initialized", - zap.String("beacon_config", string(configDump)), + zap.Stringer("beacon_config", beaconConfig), fields.Address(nodeAddress), ) return beaconConfig, nil diff --git a/networkconfig/beacon.go b/networkconfig/beacon.go index 512c8eb6f4..ec0dec7eb9 100644 --- a/networkconfig/beacon.go +++ b/networkconfig/beacon.go @@ -1,6 +1,7 @@ package networkconfig import ( + "encoding/json" "math" "time" @@ -39,6 +40,15 @@ type BeaconConfig struct { GenesisTime time.Time } +func (b BeaconConfig) String() string { + marshaled, err := json.Marshal(b) + if err != nil { + panic(err) + } + + return string(marshaled) +} + // GetSlotStartTime returns the start time for the given slot func (b BeaconConfig) GetSlotStartTime(slot phase0.Slot) time.Time { if slot > math.MaxInt64 { diff --git a/networkconfig/network.go b/networkconfig/network.go index 67c6f791eb..cafc335ea1 100644 --- a/networkconfig/network.go +++ b/networkconfig/network.go @@ -22,12 +22,12 @@ type NetworkConfig struct { } func (n NetworkConfig) String() string { - b, err := json.MarshalIndent(n, "", "\t") + jsonBytes, err := json.Marshal(n) if err != nil { - return "" + panic(err) } - return string(b) + return string(jsonBytes) } func (n NetworkConfig) NetworkName() string { diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index 6052257523..14c245b041 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -1,6 +1,7 @@ package networkconfig import ( + "encoding/json" "fmt" "math/big" @@ -40,6 +41,15 @@ type SSVConfig struct { DiscoveryProtocolID [6]byte } -func (ssv SSVConfig) GetDomainType() spectypes.DomainType { - return ssv.DomainType +func (s SSVConfig) String() string { + marshaled, err := json.Marshal(s) + if err != nil { + panic(err) + } + + return string(marshaled) +} + +func (s SSVConfig) GetDomainType() spectypes.DomainType { + return s.DomainType } diff --git a/utils/boot_node/node.go b/utils/boot_node/node.go index 9f478a64c4..1f836ebd0e 100644 --- a/utils/boot_node/node.go +++ b/utils/boot_node/node.go @@ -107,7 +107,7 @@ func (n *bootNode) Start(ctx context.Context, logger *zap.Logger) error { node := listener.LocalNode().Node() logger.Info("Running", zap.String("node", node.String()), - zap.Any("config", n.ssvConfig), + zap.Stringer("config", n.ssvConfig), fields.ProtocolID(n.ssvConfig.DiscoveryProtocolID), ) From 3cdda27b6b4865baf3e74524860dc2f76ec2a6f8 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 16 Apr 2025 21:47:00 -0300 Subject: [PATCH 04/38] networkconfig: CLI for custom SSV config generation --- cli/GENERATE_CONFIG.md | 160 +++++++++++++++++++++++++++++++++ cli/generate_config.go | 149 ++++++++++++++++++++++++++++++ cli/operator/node.go | 27 ++++-- networkconfig/holesky-e2e.go | 3 +- networkconfig/holesky-stage.go | 3 +- networkconfig/holesky.go | 3 +- networkconfig/hoodi-stage.go | 3 +- networkconfig/hoodi.go | 3 +- networkconfig/local-testnet.go | 3 +- networkconfig/mainnet.go | 3 +- networkconfig/sepolia.go | 3 +- networkconfig/ssv.go | 3 +- networkconfig/test-network.go | 3 +- operator/node.go | 6 +- 14 files changed, 350 insertions(+), 22 deletions(-) create mode 100644 cli/GENERATE_CONFIG.md create mode 100644 cli/generate_config.go diff --git a/cli/GENERATE_CONFIG.md b/cli/GENERATE_CONFIG.md new file mode 100644 index 0000000000..5dc585a25d --- /dev/null +++ b/cli/GENERATE_CONFIG.md @@ -0,0 +1,160 @@ +# SSV Config Generator + +**SSV Config Generator** is a command-line tool designed to generate configuration file for SSV Node. It streamlines the setup process by allowing users to specify various parameters through flags, which are then compiled into a YAML configuration file. This tool ensures that the SSV network setup is consistent, customizable, and easy to manage. + +## Table of Contents + +- [Usage](#usage) + - [Flags](#flags) +- [Examples](#examples) +- [Configuration](#configuration) + +## Usage + +The `generate-config` command allows you to generate a YAML configuration file by specifying various parameters through command-line flags. + +### Syntax + +```bash + ssvnode generate-config [flags] +``` + +### Flags + +| Flag | Type | Default | Description | +|--------------------------------------|--------|-----------------------------------|----------------------------------------------------------------------------| +| `--output-path` | string | `./config/config.local.yaml` | Output path for the generated configuration file. | +| `--log-level` | string | `info` | Sets the logging level (e.g., `debug`, `info`, `warn`, `error`). | +| `--db-path` | string | `./data/db` | Path to the database directory. | +| `--discovery` | string | `mdns` | Discovery method. | +| `--consensus-client` | string | _Mandatory_ | Address of the consensus client (e.g., `http://localhost:9000`). | +| `--execution-client` | string | _Mandatory_ | Address of the execution client (e.g., `http://localhost:8545`). | +| `--operator-private-key` | string | | Secret key for the operator. | +| `--metrics-api-port` | int | `0` | Port number for the Metrics API (set to `0` to disable). | +| `--ssv-domain` | string | Derived from local testnet config | Hex-encoded domain type (prefixed with `0x`). | +| `--ssv-registry-sync-offset` | uint64 | Derived from local testnet config | Registry sync offset for the network. | +| `--ssv-registry-contract-addr` | string | Derived from local testnet config | Ethereum address of the network registry contract (e.g., `0xYourAddress`). | +| `--ssv-bootnodes` | string | Derived from local testnet config | Comma-separated list of network bootnodes. | +| `--ssv-discovery-protocol-id` | string | Derived from local testnet config | Hex-encoded discovery protocol ID (prefixed with `0x`). | +| `--ssv-alan-fork-epoch` | uint64 | Derived from local testnet config | Epoch at which the Alan fork occurs in the network. | +| `--ssv-max-validators-per-committee` | int | Derived from local testnet config | Max validators per committee. | + + +**Note:** The `--consensus-client` and `--execution-client` flags are mandatory and must be provided when running the CLI. + +## Examples + +### Basic Configuration + +Generate a configuration file with default settings, specifying only the mandatory flags: + +```bash +ssvnode generate-config \ + --consensus-client "http://localhost:9000" \ + --execution-client "http://localhost:8545" +``` + +This command generates a `config.local.yaml` file in the `./config` directory with default settings for all other parameters. + +### Custom Output Path and Log Level + +Generate a configuration file with a custom output path and set the log level to `debug`: + +```bash +ssvnode generate-config \ + --consensus-client "http://consensus.example.com:9000" \ + --execution-client "http://execution.example.com:8545" \ + --output-path "/etc/ssv/config.yaml" \ + --log-level "debug" +``` + +### Specify Operator Private Key and Enable Metrics API + +Generate a configuration with an operator's private key and enable the Metrics API on port `8080`: + +```bash +ssvnode generate-config \ + --consensus-client "http://consensus.example.com:9000" \ + --execution-client "http://execution.example.com:8545" \ + --operator-private-key "your-operator-private-key" \ + --metrics-api-port 8080 +``` + +### Advanced Network Configuration + +Customize network settings such as bootnodes, discovery protocol ID, fork epoch, etc: + +```bash +ssvnode generate-config \ + --consensus-client "http://consensus.example.com:9000" \ + --execution-client "http://execution.example.com:8545" \ + --ssv-domain "0x12345678" \ + --ssv-registry-sync-offset 50 \ + --ssv-registry-contract-addr "0xYourRegistryContractAddress" \ + --ssv-bootnodes "enode://bootnode1@127.0.0.1:30303,enode://bootnode2@127.0.0.1:30304" \ + --ssv-discovery-protocol-id "0x1234567890ab" \ + --ssv-max-validators-per-committee 560 +``` + +## Configuration + +The generated YAML configuration file (`config.local.yaml` by default) follows the structure defined by the `Config` struct. Below is an overview of each section and its corresponding fields: + +### YAML Structure + +```yaml +global: + LogLevel: info +db: + Path: ./data/db +eth2: + BeaconNodeAddr: http://localhost:9000 +eth1: + ETH1Addr: http://localhost:8545 +p2p: + Discovery: mdns +ssv: + Network: LocalTestnetSSV + CustomNetwork: + DomainType: "0x12345678" + RegistrySyncOffset: 0 + RegistryContractAddr: "0xYourRegistryContractAddress" + Bootnodes: + - "enode://bootnode1@127.0.0.1:30303" + - "enode://bootnode2@127.0.0.1:30304" + DiscoveryProtocolID: "0x1234567890ab" +OperatorPrivateKey: your-operator-private-key +MetricsAPIPort: 8080 +``` + +### Sections + +- **global** + - `LogLevel`: Specifies the logging level (e.g., `debug`, `info`, `warn`, `error`). + +- **db** + - `Path`: Path to the database directory. + +- **eth2** + - `BeaconNodeAddr`: Address of the consensus client (Beacon Node). + +- **eth1** + - `ETH1Addr`: Address of the execution client (ETH1 Node). + +- **p2p** + - `Discovery`: Peer-to-peer discovery method (e.g., `mdns`). + +- **ssv** + - `Network`: Name of the network. + - `CustomNetwork`: Contains custom network parameters. + - `DomainType`: Hex-encoded domain type (prefixed with `0x`). + - `RegistrySyncOffset`: Registry sync offset for the network. + - `RegistryContractAddr`: Ethereum address of the network registry contract. + - `Bootnodes`: List of network bootnodes. + - `DiscoveryProtocolID`: Hex-encoded discovery protocol ID (prefixed with `0x`). + +- **OperatorPrivateKey** + - `OperatorPrivateKey`: Secret key for the operator. + +- **MetricsAPIPort** + - `MetricsAPIPort`: Port number for the Metrics API. \ No newline at end of file diff --git a/cli/generate_config.go b/cli/generate_config.go new file mode 100644 index 0000000000..4ffc50f8b4 --- /dev/null +++ b/cli/generate_config.go @@ -0,0 +1,149 @@ +package cli + +import ( + "encoding/hex" + "fmt" + "log" + "math/big" + "os" + "strings" + + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/spf13/cobra" + spectypes "github.com/ssvlabs/ssv-spec/types" + "gopkg.in/yaml.v2" + + "github.com/ssvlabs/ssv/networkconfig" +) + +const ( + defaultOutputPath = "./config/config.local.yaml" + defaultLogLevel = "info" + defaultDBPath = "./data/db" + defaultDiscovery = "mdns" + sliceSeparator = "," + configFilePermissions = 0644 +) + +var ( + defaultNetwork = networkconfig.LocalTestnet +) + +var ( + outputPath string + logLevel string + dbPath string + discovery string + consensusClient string + executionClient string + operatorPrivateKey string + metricsAPIPort int + ssvDomain string + ssvRegistrySyncOffset uint64 + ssvRegistryContractAddr string + ssvBootnodes string + ssvDiscoveryProtocolID string +) + +type SSVConfig struct { + Global struct { + LogLevel string `yaml:"LogLevel,omitempty"` + } `yaml:"global,omitempty"` + DB struct { + Path string `yaml:"Path,omitempty"` + } `yaml:"db,omitempty"` + ConsensusClient struct { + Address string `yaml:"BeaconNodeAddr,omitempty"` + } `yaml:"eth2,omitempty"` + ExecutionClient struct { + Address string `yaml:"ETH1Addr,omitempty"` + } `yaml:"eth1,omitempty"` + P2P struct { + Discovery string `yaml:"Discovery,omitempty"` + } `yaml:"p2p,omitempty"` + SSV struct { + NetworkName string `yaml:"Network,omitempty" env:"NETWORK" env-description:"Network is the network of this node,omitempty"` + CustomNetwork *networkconfig.SSVConfig `yaml:"CustomNetwork,omitempty" env:"CUSTOM_NETWORK" env-description:"Custom network parameters,omitempty"` + } `yaml:"ssv,omitempty"` + OperatorPrivateKey string `yaml:"OperatorPrivateKey,omitempty"` + MetricsAPIPort int `yaml:"MetricsAPIPort,omitempty"` +} + +// generateConfigCmd is the command to generate ssv operator config. +var generateConfigCmd = &cobra.Command{ + Use: "generate-config", + Short: "generates ssv operator config", + Run: func(cmd *cobra.Command, args []string) { + parsedDomain, err := hex.DecodeString(strings.TrimPrefix(ssvDomain, "0x")) + if err != nil { + log.Fatalf("Failed to decode network domain: %v", err) + } + + parsedDiscoveryProtocolID, err := hex.DecodeString(strings.TrimPrefix(ssvDiscoveryProtocolID, "0x")) + if err != nil { + log.Fatalf("Failed to decode discovery protocol ID: %v", err) + } + + var parsedDiscoveryProtocolIDArr [6]byte + if len(parsedDiscoveryProtocolID) != 0 { + parsedDiscoveryProtocolIDArr = [6]byte(parsedDiscoveryProtocolID) + } + + var bootnodes []string + if ssvBootnodes != "" { + bootnodes = strings.Split(ssvBootnodes, sliceSeparator) + } + + var config SSVConfig + config.Global.LogLevel = logLevel + config.DB.Path = dbPath + config.ConsensusClient.Address = consensusClient + config.ExecutionClient.Address = executionClient + config.P2P.Discovery = discovery + config.OperatorPrivateKey = operatorPrivateKey + config.MetricsAPIPort = metricsAPIPort + config.SSV.CustomNetwork = &networkconfig.SSVConfig{ + DomainType: spectypes.DomainType(parsedDomain), + RegistrySyncOffset: new(big.Int).SetUint64(ssvRegistrySyncOffset), + RegistryContractAddr: ethcommon.HexToAddress(ssvRegistryContractAddr), + Bootnodes: bootnodes, + DiscoveryProtocolID: parsedDiscoveryProtocolIDArr, + } + + data, err := yaml.Marshal(&config) + if err != nil { + log.Fatalf("Failed to marshal YAML: %v", err) + } + + err = os.WriteFile(outputPath, data, configFilePermissions) + if err != nil { + log.Fatalf("Failed to write file: %v", err) + } + + log.Printf("Saved config into '%s':", outputPath) + fmt.Println(string(data)) + }, +} + +func init() { + generateConfigCmd.Flags().StringVarP(&outputPath, "output-path", "o", defaultOutputPath, "Output path for generated config") + generateConfigCmd.Flags().StringVar(&logLevel, "log-level", defaultLogLevel, "Log level") + generateConfigCmd.Flags().StringVar(&dbPath, "db-path", defaultDBPath, "DB path") + generateConfigCmd.Flags().StringVar(&discovery, "discovery", defaultDiscovery, "Discovery") + generateConfigCmd.Flags().StringVar(&consensusClient, "consensus-client", "", "Consensus client (required)") + _ = generateConfigCmd.MarkFlagRequired("consensus-client") + generateConfigCmd.Flags().StringVar(&executionClient, "execution-client", "", "Execution client (required)") + _ = generateConfigCmd.MarkFlagRequired("execution-client") + generateConfigCmd.Flags().StringVar(&operatorPrivateKey, "operator-private-key", "", "Secret key") + generateConfigCmd.Flags().IntVar(&metricsAPIPort, "metrics-api-port", 0, "Metrics API port") + + ssvDomainDefault := "0x" + hex.EncodeToString(defaultNetwork.DomainType[:]) + generateConfigCmd.Flags().StringVar(&ssvDomain, "ssv-domain", ssvDomainDefault, "SSV domain type") + generateConfigCmd.Flags().Uint64Var(&ssvRegistrySyncOffset, "ssv-registry-sync-offset", defaultNetwork.RegistrySyncOffset.Uint64(), "SSV registry sync offset") + generateConfigCmd.Flags().StringVar(&ssvRegistryContractAddr, "ssv-registry-contract-addr", defaultNetwork.RegistryContractAddr.String(), "SSV registry contract addr") + generateConfigCmd.Flags().StringVar(&ssvBootnodes, "ssv-bootnodes", strings.Join(defaultNetwork.Bootnodes, sliceSeparator), "SSV bootnodes (comma-separated)") + ssvDiscoveryProtocolIDDefault := "0x" + hex.EncodeToString(defaultNetwork.DiscoveryProtocolID[:]) + generateConfigCmd.Flags().StringVar(&ssvDiscoveryProtocolID, "ssv-discovery-protocol-id", ssvDiscoveryProtocolIDDefault, "SSV discovery protocol ID") + + RootCmd.AddCommand(generateConfigCmd) +} diff --git a/cli/operator/node.go b/cli/operator/node.go index fcfaa0913e..e17ad57b35 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -15,7 +15,6 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" - ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ilyakaznacheev/cleanenv" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -219,7 +218,7 @@ var StartNodeCmd = &cobra.Command{ ec, err := executionclient.New( cmd.Context(), executionAddrList[0], - ethcommon.HexToAddress(ssvNetworkConfig.RegistryContractAddr), + ssvNetworkConfig.RegistryContractAddr, executionclient.WithLogger(logger), executionclient.WithFollowDistance(executionclient.DefaultFollowDistance), executionclient.WithConnectionTimeout(cfg.ExecutionClient.ConnectionTimeout), @@ -237,7 +236,7 @@ var StartNodeCmd = &cobra.Command{ ec, err := executionclient.NewMulti( cmd.Context(), executionAddrList, - ethcommon.HexToAddress(ssvNetworkConfig.RegistryContractAddr), + ssvNetworkConfig.RegistryContractAddr, executionclient.WithLoggerMulti(logger), executionclient.WithFollowDistanceMulti(executionclient.DefaultFollowDistance), executionclient.WithConnectionTimeoutMulti(cfg.ExecutionClient.ConnectionTimeout), @@ -519,7 +518,6 @@ func validateConfig(nodeStorage operatorstorage.Storage, networkName string, usi return fmt.Errorf("incompatible config change: %w", err) } } else { - if err := nodeStorage.SaveConfig(nil, currentConfig); err != nil { return fmt.Errorf("failed to store config: %w", err) } @@ -673,9 +671,20 @@ func setupOperatorStorage(logger *zap.Logger, db basedb.Database, configPrivKey } func setupSSVNetwork(logger *zap.Logger) (networkconfig.SSVConfig, error) { - ssvConfig, err := networkconfig.GetSSVConfigByName(cfg.SSVOptions.NetworkName) - if err != nil { - return networkconfig.SSVConfig{}, err + var ssvConfig networkconfig.SSVConfig + + if cfg.SSVOptions.CustomNetwork != nil { + ssvConfig = *cfg.SSVOptions.CustomNetwork + logger.Info("using custom network config") + } else if cfg.SSVOptions.NetworkName != "" { + snc, err := networkconfig.GetSSVConfigByName(cfg.SSVOptions.NetworkName) + if err != nil { + return ssvConfig, err + } + ssvConfig = snc + logger.Info("found network config by name", + zap.String("name", cfg.SSVOptions.NetworkName), + ) } if cfg.SSVOptions.CustomDomainType != "" { @@ -694,7 +703,7 @@ func setupSSVNetwork(logger *zap.Logger) (networkconfig.SSVConfig, error) { postForkDomain := binary.BigEndian.Uint32(domainBytes) + 1 binary.BigEndian.PutUint32(ssvConfig.DomainType[:], postForkDomain) - logger.Info("running with custom domain type", + logger.Warn("running with custom domain type; it's deprecated, consider using custom network instead", fields.Domain(ssvConfig.DomainType), ) } @@ -707,7 +716,7 @@ func setupSSVNetwork(logger *zap.Logger) (networkconfig.SSVConfig, error) { logger.Info("setting ssv network", zap.Any("config", ssvConfig), zap.String("nodeType", nodeType), - zap.String("registryContract", ssvConfig.RegistryContractAddr), + zap.String("registryContract", ssvConfig.RegistryContractAddr.String()), ) return ssvConfig, nil diff --git a/networkconfig/holesky-e2e.go b/networkconfig/holesky-e2e.go index d005809910..36de1f300f 100644 --- a/networkconfig/holesky-e2e.go +++ b/networkconfig/holesky-e2e.go @@ -5,6 +5,7 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" + ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -19,7 +20,7 @@ var HoleskyE2E = NetworkConfig{ }, SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, 0xee, 0x1}, - RegistryContractAddr: "0x58410bef803ecd7e63b23664c586a6db72daf59c", + RegistryContractAddr: ethcommon.HexToAddress("0x58410bef803ecd7e63b23664c586a6db72daf59c"), RegistrySyncOffset: big.NewInt(405579), Bootnodes: []string{}, }, diff --git a/networkconfig/holesky-stage.go b/networkconfig/holesky-stage.go index 153c51265a..a9373a43c2 100644 --- a/networkconfig/holesky-stage.go +++ b/networkconfig/holesky-stage.go @@ -5,6 +5,7 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" + ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -20,7 +21,7 @@ var HoleskyStage = NetworkConfig{ SSVConfig: SSVConfig{ DomainType: [4]byte{0x00, 0x00, 0x31, 0x13}, RegistrySyncOffset: new(big.Int).SetInt64(84599), - RegistryContractAddr: "0x0d33801785340072C452b994496B19f196b7eE15", + RegistryContractAddr: ethcommon.HexToAddress("0x0d33801785340072C452b994496B19f196b7eE15"), DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, Bootnodes: []string{ // Public bootnode: diff --git a/networkconfig/holesky.go b/networkconfig/holesky.go index 77300907a1..6d409cdc93 100644 --- a/networkconfig/holesky.go +++ b/networkconfig/holesky.go @@ -5,6 +5,7 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" + ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -20,7 +21,7 @@ var Holesky = NetworkConfig{ SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x2}, RegistrySyncOffset: new(big.Int).SetInt64(181612), - RegistryContractAddr: "0x38A4794cCEd47d3baf7370CcC43B560D3a1beEFA", + RegistryContractAddr: ethcommon.HexToAddress("0x38A4794cCEd47d3baf7370CcC43B560D3a1beEFA"), DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, Bootnodes: []string{ // SSV Labs diff --git a/networkconfig/hoodi-stage.go b/networkconfig/hoodi-stage.go index 4f3e1232a0..24d1c8a38b 100644 --- a/networkconfig/hoodi-stage.go +++ b/networkconfig/hoodi-stage.go @@ -5,6 +5,7 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" + ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -20,7 +21,7 @@ var HoodiStage = NetworkConfig{ SSVConfig: SSVConfig{ DomainType: [4]byte{0x00, 0x00, 0x31, 0x14}, RegistrySyncOffset: new(big.Int).SetInt64(1004), - RegistryContractAddr: "0x0aaace4e8affc47c6834171c88d342a4abd8f105", + RegistryContractAddr: ethcommon.HexToAddress("0x0aaace4e8affc47c6834171c88d342a4abd8f105"), DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, Bootnodes: []string{ // SSV Labs diff --git a/networkconfig/hoodi.go b/networkconfig/hoodi.go index 551d030301..b8e648f134 100644 --- a/networkconfig/hoodi.go +++ b/networkconfig/hoodi.go @@ -5,6 +5,7 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" + ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -20,7 +21,7 @@ var Hoodi = NetworkConfig{ SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x3}, RegistrySyncOffset: new(big.Int).SetInt64(1065), - RegistryContractAddr: "0x58410Bef803ECd7E63B23664C586A6DB72DAf59c", + RegistryContractAddr: ethcommon.HexToAddress("0x58410Bef803ECd7E63B23664C586A6DB72DAf59c"), DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, Bootnodes: []string{ // SSV Labs diff --git a/networkconfig/local-testnet.go b/networkconfig/local-testnet.go index 7c96e2c4f8..a4f97cda7e 100644 --- a/networkconfig/local-testnet.go +++ b/networkconfig/local-testnet.go @@ -4,6 +4,7 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" + ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -18,7 +19,7 @@ var LocalTestnet = NetworkConfig{ }, SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoV2NetworkID.Byte(), 0x2}, - RegistryContractAddr: "0xC3CD9A0aE89Fff83b71b58b6512D43F8a41f363D", + RegistryContractAddr: ethcommon.HexToAddress("0xC3CD9A0aE89Fff83b71b58b6512D43F8a41f363D"), Bootnodes: []string{ "enr:-Li4QLR4Y1VbwiqFYKy6m-WFHRNDjhMDZ_qJwIABu2PY9BHjIYwCKpTvvkVmZhu43Q6zVA29sEUhtz10rQjDJkK3Hd-GAYiGrW2Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQJTcI7GHPw-ZqIflPZYYDK_guurp_gsAFF5Erns3-PAvIN0Y3CCE4mDdWRwgg-h", }, diff --git a/networkconfig/mainnet.go b/networkconfig/mainnet.go index 56bdeb0b73..36d95cb43b 100644 --- a/networkconfig/mainnet.go +++ b/networkconfig/mainnet.go @@ -5,6 +5,7 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" + ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -20,7 +21,7 @@ var Mainnet = NetworkConfig{ SSVConfig: SSVConfig{ DomainType: spectypes.AlanMainnet, RegistrySyncOffset: new(big.Int).SetInt64(17507487), - RegistryContractAddr: "0xDD9BC35aE942eF0cFa76930954a156B3fF30a4E1", + RegistryContractAddr: ethcommon.HexToAddress("0xDD9BC35aE942eF0cFa76930954a156B3fF30a4E1"), DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, Bootnodes: []string{ // SSV Labs diff --git a/networkconfig/sepolia.go b/networkconfig/sepolia.go index 5359d1059d..5ae09bf6db 100644 --- a/networkconfig/sepolia.go +++ b/networkconfig/sepolia.go @@ -5,6 +5,7 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" + ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -20,7 +21,7 @@ var Sepolia = NetworkConfig{ SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x69}, RegistrySyncOffset: new(big.Int).SetInt64(7795814), - RegistryContractAddr: "0x261419B48F36EdF420743E9f91bABF4856e76f99", + RegistryContractAddr: ethcommon.HexToAddress("0x261419B48F36EdF420743E9f91bABF4856e76f99"), DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, Bootnodes: []string{ // SSV Labs diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index 14c245b041..df82bdacf8 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -5,6 +5,7 @@ import ( "fmt" "math/big" + ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -36,7 +37,7 @@ type SSV interface { type SSVConfig struct { DomainType spectypes.DomainType RegistrySyncOffset *big.Int - RegistryContractAddr string // TODO: ethcommon.Address + RegistryContractAddr ethcommon.Address Bootnodes []string DiscoveryProtocolID [6]byte } diff --git a/networkconfig/test-network.go b/networkconfig/test-network.go index 9bda6da4b0..8435f0c459 100644 --- a/networkconfig/test-network.go +++ b/networkconfig/test-network.go @@ -5,6 +5,7 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" + ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -20,7 +21,7 @@ var TestNetwork = NetworkConfig{ SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoNetworkID.Byte(), 0x2}, RegistrySyncOffset: new(big.Int).SetInt64(9015219), - RegistryContractAddr: "0x4B133c68A084B8A88f72eDCd7944B69c8D545f03", + RegistryContractAddr: ethcommon.HexToAddress("0x4B133c68A084B8A88f72eDCd7944B69c8D545f03"), Bootnodes: []string{ "enr:-Li4QFIQzamdvTxGJhvcXG_DFmCeyggSffDnllY5DiU47pd_K_1MRnSaJimWtfKJ-MD46jUX9TwgW5Jqe0t4pH41RYWGAYuFnlyth2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQN4v-N9zFYwEqzGPBBX37q24QPFvAVUtokIo1fblIsmTIN0Y3CCE4uDdWRwgg-j", }, diff --git a/operator/node.go b/operator/node.go index b58d5b02ad..f94773ba49 100644 --- a/operator/node.go +++ b/operator/node.go @@ -26,9 +26,9 @@ import ( // Options contains options to create the node type Options struct { - // NetworkName is the network name of this node - NetworkName string `yaml:"Network" env:"NETWORK" env-default:"mainnet" env-description:"Ethereum network to connect to (mainnet, holesky, sepolia, etc.)"` - CustomDomainType string `yaml:"CustomDomainType" env:"CUSTOM_DOMAIN_TYPE" env-default:"" env-description:"Override SSV domain type for network isolation. Warning: Please modify only if you are certain of the implications. This would be incremented by 1 after Alan fork (e.g., 0x01020304 → 0x01020305 post-fork)"` + NetworkName string `yaml:"Network" env:"NETWORK" env-default:"mainnet" env-description:"Ethereum network to connect to (mainnet, holesky, sepolia, etc.). For backwards compatibility it's ignored if CustomNetwork is set"` + CustomNetwork *networkconfig.SSVConfig `yaml:"CustomNetwork" env:"CUSTOM_NETWORK" env-description:"Custom SSV network configuration"` + CustomDomainType string `yaml:"CustomDomainType" env:"CUSTOM_DOMAIN_TYPE" env-default:"" env-description:"Override SSV domain type for network isolation. Warning: Please modify only if you are certain of the implications. This would be incremented by 1 after Alan fork (e.g., 0x01020304 → 0x01020305 post-fork)"` // DEPRECATED: use CustomNetwork instead. NetworkConfig networkconfig.NetworkConfig BeaconNode beaconprotocol.BeaconNode // TODO: consider renaming to ConsensusClient ExecutionClient executionclient.Provider From 17eb427866dae85ee31e00f330d80e89ab1ce2d4 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 16 Apr 2025 22:23:56 -0300 Subject: [PATCH 05/38] implement JSON marshaling and YAML marshaling/unmarshaling --- networkconfig/ssv.go | 99 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index df82bdacf8..6ec6ac4e5d 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -1,9 +1,11 @@ package networkconfig import ( + "encoding/hex" "encoding/json" "fmt" "math/big" + "strings" ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" @@ -35,11 +37,12 @@ type SSV interface { } type SSVConfig struct { - DomainType spectypes.DomainType - RegistrySyncOffset *big.Int - RegistryContractAddr ethcommon.Address - Bootnodes []string - DiscoveryProtocolID [6]byte + DomainType spectypes.DomainType + RegistrySyncOffset *big.Int + RegistryContractAddr ethcommon.Address + Bootnodes []string + DiscoveryProtocolID [6]byte + TotalEthereumValidators int `yaml:"TotalEthereumValidators,omitempty"` // value needs to be maintained — consider getting it from external API with default or per-network value(s) as fallback } func (s SSVConfig) String() string { @@ -51,6 +54,92 @@ func (s SSVConfig) String() string { return string(marshaled) } +type marshaledConfig struct { + DomainType string `json:"DomainType,omitempty" yaml:"DomainType,omitempty"` + RegistrySyncOffset *big.Int `json:"RegistrySyncOffset,omitempty" yaml:"RegistrySyncOffset,omitempty"` + RegistryContractAddr string `json:"RegistryContractAddr,omitempty" yaml:"RegistryContractAddr,omitempty"` + Bootnodes []string `json:"Bootnodes,omitempty" yaml:"Bootnodes,omitempty"` + DiscoveryProtocolID string `json:"DiscoveryProtocolID,omitempty" yaml:"DiscoveryProtocolID,omitempty"` + TotalEthereumValidators int `json:"TotalEthereumValidators,omitempty" yaml:"TotalEthereumValidators,omitempty"` +} + +func (s *SSVConfig) marshal() (marshaledConfig, error) { + aux := marshaledConfig{ + DomainType: "0x" + hex.EncodeToString(s.DomainType[:]), + RegistrySyncOffset: s.RegistrySyncOffset, + RegistryContractAddr: s.RegistryContractAddr.String(), + Bootnodes: s.Bootnodes, + DiscoveryProtocolID: "0x" + hex.EncodeToString(s.DiscoveryProtocolID[:]), + TotalEthereumValidators: s.TotalEthereumValidators, + } + + return aux, nil +} + +func (s SSVConfig) MarshalJSON() ([]byte, error) { + aux, err := s.marshal() + if err != nil { + return nil, err + } + + return json.Marshal(aux) +} + +func (s SSVConfig) MarshalYAML() (interface{}, error) { + aux, err := s.marshal() + if err != nil { + return nil, err + } + + return aux, nil +} + +func (s *SSVConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + aux := &struct { + DomainType string `yaml:"DomainType,omitempty"` + RegistrySyncOffset *big.Int `yaml:"RegistrySyncOffset,omitempty"` + RegistryContractAddr string `yaml:"RegistryContractAddr,omitempty"` + Bootnodes []string `yaml:"Bootnodes,omitempty"` + DiscoveryProtocolID string `yaml:"DiscoveryProtocolID,omitempty"` + TotalEthereumValidators int `yaml:"TotalEthereumValidators,omitempty"` + }{} + + if err := unmarshal(aux); err != nil { + return err + } + + domain, err := hex.DecodeString(strings.TrimPrefix(aux.DomainType, "0x")) + if err != nil { + return fmt.Errorf("decode domain: %w", err) + } + + var domainArr spectypes.DomainType + if len(domain) != 0 { + domainArr = spectypes.DomainType(domain) + } + + discoveryProtocolID, err := hex.DecodeString(strings.TrimPrefix(aux.DiscoveryProtocolID, "0x")) + if err != nil { + return fmt.Errorf("decode discovery protocol ID: %w", err) + } + + var discoveryProtocolIDArr [6]byte + if len(discoveryProtocolID) != 0 { + discoveryProtocolIDArr = [6]byte(discoveryProtocolID) + } + + *s = SSVConfig{ + DomainType: domainArr, + RegistrySyncOffset: aux.RegistrySyncOffset, + RegistryContractAddr: ethcommon.HexToAddress(aux.RegistryContractAddr), + Bootnodes: aux.Bootnodes, + DiscoveryProtocolID: discoveryProtocolIDArr, + TotalEthereumValidators: aux.TotalEthereumValidators, + } + + return nil +} + func (s SSVConfig) GetDomainType() spectypes.DomainType { return s.DomainType } From cf5db7803464d200b2c39e85f244ecdf0fe7ab07 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 16 Apr 2025 22:24:47 -0300 Subject: [PATCH 06/38] remove TotalEthereumValidators --- networkconfig/ssv.go | 55 ++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index 6ec6ac4e5d..d2c77ea219 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -37,12 +37,11 @@ type SSV interface { } type SSVConfig struct { - DomainType spectypes.DomainType - RegistrySyncOffset *big.Int - RegistryContractAddr ethcommon.Address - Bootnodes []string - DiscoveryProtocolID [6]byte - TotalEthereumValidators int `yaml:"TotalEthereumValidators,omitempty"` // value needs to be maintained — consider getting it from external API with default or per-network value(s) as fallback + DomainType spectypes.DomainType + RegistrySyncOffset *big.Int + RegistryContractAddr ethcommon.Address + Bootnodes []string + DiscoveryProtocolID [6]byte } func (s SSVConfig) String() string { @@ -55,22 +54,20 @@ func (s SSVConfig) String() string { } type marshaledConfig struct { - DomainType string `json:"DomainType,omitempty" yaml:"DomainType,omitempty"` - RegistrySyncOffset *big.Int `json:"RegistrySyncOffset,omitempty" yaml:"RegistrySyncOffset,omitempty"` - RegistryContractAddr string `json:"RegistryContractAddr,omitempty" yaml:"RegistryContractAddr,omitempty"` - Bootnodes []string `json:"Bootnodes,omitempty" yaml:"Bootnodes,omitempty"` - DiscoveryProtocolID string `json:"DiscoveryProtocolID,omitempty" yaml:"DiscoveryProtocolID,omitempty"` - TotalEthereumValidators int `json:"TotalEthereumValidators,omitempty" yaml:"TotalEthereumValidators,omitempty"` + DomainType string `json:"DomainType,omitempty" yaml:"DomainType,omitempty"` + RegistrySyncOffset *big.Int `json:"RegistrySyncOffset,omitempty" yaml:"RegistrySyncOffset,omitempty"` + RegistryContractAddr string `json:"RegistryContractAddr,omitempty" yaml:"RegistryContractAddr,omitempty"` + Bootnodes []string `json:"Bootnodes,omitempty" yaml:"Bootnodes,omitempty"` + DiscoveryProtocolID string `json:"DiscoveryProtocolID,omitempty" yaml:"DiscoveryProtocolID,omitempty"` } func (s *SSVConfig) marshal() (marshaledConfig, error) { aux := marshaledConfig{ - DomainType: "0x" + hex.EncodeToString(s.DomainType[:]), - RegistrySyncOffset: s.RegistrySyncOffset, - RegistryContractAddr: s.RegistryContractAddr.String(), - Bootnodes: s.Bootnodes, - DiscoveryProtocolID: "0x" + hex.EncodeToString(s.DiscoveryProtocolID[:]), - TotalEthereumValidators: s.TotalEthereumValidators, + DomainType: "0x" + hex.EncodeToString(s.DomainType[:]), + RegistrySyncOffset: s.RegistrySyncOffset, + RegistryContractAddr: s.RegistryContractAddr.String(), + Bootnodes: s.Bootnodes, + DiscoveryProtocolID: "0x" + hex.EncodeToString(s.DiscoveryProtocolID[:]), } return aux, nil @@ -96,12 +93,11 @@ func (s SSVConfig) MarshalYAML() (interface{}, error) { func (s *SSVConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { aux := &struct { - DomainType string `yaml:"DomainType,omitempty"` - RegistrySyncOffset *big.Int `yaml:"RegistrySyncOffset,omitempty"` - RegistryContractAddr string `yaml:"RegistryContractAddr,omitempty"` - Bootnodes []string `yaml:"Bootnodes,omitempty"` - DiscoveryProtocolID string `yaml:"DiscoveryProtocolID,omitempty"` - TotalEthereumValidators int `yaml:"TotalEthereumValidators,omitempty"` + DomainType string `yaml:"DomainType,omitempty"` + RegistrySyncOffset *big.Int `yaml:"RegistrySyncOffset,omitempty"` + RegistryContractAddr string `yaml:"RegistryContractAddr,omitempty"` + Bootnodes []string `yaml:"Bootnodes,omitempty"` + DiscoveryProtocolID string `yaml:"DiscoveryProtocolID,omitempty"` }{} if err := unmarshal(aux); err != nil { @@ -129,12 +125,11 @@ func (s *SSVConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { } *s = SSVConfig{ - DomainType: domainArr, - RegistrySyncOffset: aux.RegistrySyncOffset, - RegistryContractAddr: ethcommon.HexToAddress(aux.RegistryContractAddr), - Bootnodes: aux.Bootnodes, - DiscoveryProtocolID: discoveryProtocolIDArr, - TotalEthereumValidators: aux.TotalEthereumValidators, + DomainType: domainArr, + RegistrySyncOffset: aux.RegistrySyncOffset, + RegistryContractAddr: ethcommon.HexToAddress(aux.RegistryContractAddr), + Bootnodes: aux.Bootnodes, + DiscoveryProtocolID: discoveryProtocolIDArr, } return nil From 72f4d8df076ea57734cff61e64b4e96fd560e19d Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 17 Apr 2025 14:47:06 -0300 Subject: [PATCH 07/38] simplify marshaling/unmarshaling logic --- networkconfig/ssv.go | 73 +++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 48 deletions(-) diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index d2c77ea219..932465ebe7 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -1,13 +1,12 @@ package networkconfig import ( - "encoding/hex" "encoding/json" "fmt" "math/big" - "strings" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -54,82 +53,60 @@ func (s SSVConfig) String() string { } type marshaledConfig struct { - DomainType string `json:"DomainType,omitempty" yaml:"DomainType,omitempty"` - RegistrySyncOffset *big.Int `json:"RegistrySyncOffset,omitempty" yaml:"RegistrySyncOffset,omitempty"` - RegistryContractAddr string `json:"RegistryContractAddr,omitempty" yaml:"RegistryContractAddr,omitempty"` - Bootnodes []string `json:"Bootnodes,omitempty" yaml:"Bootnodes,omitempty"` - DiscoveryProtocolID string `json:"DiscoveryProtocolID,omitempty" yaml:"DiscoveryProtocolID,omitempty"` + DomainType hexutil.Bytes `json:"DomainType,omitempty" yaml:"DomainType,omitempty"` + RegistrySyncOffset *big.Int `json:"RegistrySyncOffset,omitempty" yaml:"RegistrySyncOffset,omitempty"` + RegistryContractAddr ethcommon.Address `json:"RegistryContractAddr,omitempty" yaml:"RegistryContractAddr,omitempty"` + Bootnodes []string `json:"Bootnodes,omitempty" yaml:"Bootnodes,omitempty"` + DiscoveryProtocolID hexutil.Bytes `json:"DiscoveryProtocolID,omitempty" yaml:"DiscoveryProtocolID,omitempty"` } -func (s *SSVConfig) marshal() (marshaledConfig, error) { +func (s SSVConfig) marshal() marshaledConfig { aux := marshaledConfig{ - DomainType: "0x" + hex.EncodeToString(s.DomainType[:]), + DomainType: s.DomainType[:], RegistrySyncOffset: s.RegistrySyncOffset, - RegistryContractAddr: s.RegistryContractAddr.String(), + RegistryContractAddr: s.RegistryContractAddr, Bootnodes: s.Bootnodes, - DiscoveryProtocolID: "0x" + hex.EncodeToString(s.DiscoveryProtocolID[:]), + DiscoveryProtocolID: s.DiscoveryProtocolID[:], } - return aux, nil + return aux } func (s SSVConfig) MarshalJSON() ([]byte, error) { - aux, err := s.marshal() - if err != nil { - return nil, err - } - - return json.Marshal(aux) + return json.Marshal(s.marshal()) } func (s SSVConfig) MarshalYAML() (interface{}, error) { - aux, err := s.marshal() - if err != nil { - return nil, err - } - - return aux, nil + return s.marshal(), nil } func (s *SSVConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { aux := &struct { - DomainType string `yaml:"DomainType,omitempty"` - RegistrySyncOffset *big.Int `yaml:"RegistrySyncOffset,omitempty"` - RegistryContractAddr string `yaml:"RegistryContractAddr,omitempty"` - Bootnodes []string `yaml:"Bootnodes,omitempty"` - DiscoveryProtocolID string `yaml:"DiscoveryProtocolID,omitempty"` + DomainType hexutil.Bytes `yaml:"DomainType,omitempty"` + RegistrySyncOffset *big.Int `yaml:"RegistrySyncOffset,omitempty"` + RegistryContractAddr ethcommon.Address `yaml:"RegistryContractAddr,omitempty"` + Bootnodes []string `yaml:"Bootnodes,omitempty"` + DiscoveryProtocolID hexutil.Bytes `yaml:"DiscoveryProtocolID,omitempty"` }{} if err := unmarshal(aux); err != nil { return err } - domain, err := hex.DecodeString(strings.TrimPrefix(aux.DomainType, "0x")) - if err != nil { - return fmt.Errorf("decode domain: %w", err) - } - - var domainArr spectypes.DomainType - if len(domain) != 0 { - domainArr = spectypes.DomainType(domain) - } - - discoveryProtocolID, err := hex.DecodeString(strings.TrimPrefix(aux.DiscoveryProtocolID, "0x")) - if err != nil { - return fmt.Errorf("decode discovery protocol ID: %w", err) + if len(aux.DomainType) != 4 { + return fmt.Errorf("invalid domain type length: expected 4 bytes, got %d", len(aux.DomainType)) } - var discoveryProtocolIDArr [6]byte - if len(discoveryProtocolID) != 0 { - discoveryProtocolIDArr = [6]byte(discoveryProtocolID) + if len(aux.DiscoveryProtocolID) != 6 { + return fmt.Errorf("invalid discovery protocol ID length: expected 6 bytes, got %d", len(aux.DiscoveryProtocolID)) } *s = SSVConfig{ - DomainType: domainArr, + DomainType: spectypes.DomainType(aux.DomainType), RegistrySyncOffset: aux.RegistrySyncOffset, - RegistryContractAddr: ethcommon.HexToAddress(aux.RegistryContractAddr), + RegistryContractAddr: aux.RegistryContractAddr, Bootnodes: aux.Bootnodes, - DiscoveryProtocolID: discoveryProtocolIDArr, + DiscoveryProtocolID: [6]byte(aux.DiscoveryProtocolID), } return nil From 1337765365b7523150ccf50772cecef6f9293252 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 17 Apr 2025 14:50:28 -0300 Subject: [PATCH 08/38] fix markdown formatting --- cli/GENERATE_CONFIG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/GENERATE_CONFIG.md b/cli/GENERATE_CONFIG.md index 5dc585a25d..470b63d28a 100644 --- a/cli/GENERATE_CONFIG.md +++ b/cli/GENERATE_CONFIG.md @@ -5,7 +5,7 @@ ## Table of Contents - [Usage](#usage) - - [Flags](#flags) + - [Flags](#flags) - [Examples](#examples) - [Configuration](#configuration) From e226819613b83e89e879bc750bd17f855afd36eb Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 17 Apr 2025 14:50:38 -0300 Subject: [PATCH 09/38] use yaml@v3 --- cli/generate_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/generate_config.go b/cli/generate_config.go index 4ffc50f8b4..41de6e6d58 100644 --- a/cli/generate_config.go +++ b/cli/generate_config.go @@ -11,7 +11,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/spf13/cobra" spectypes "github.com/ssvlabs/ssv-spec/types" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" "github.com/ssvlabs/ssv/networkconfig" ) From b91c135ae3210f80c31793d8bdccc80b976833fd Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 17 Apr 2025 14:55:23 -0300 Subject: [PATCH 10/38] add tests --- networkconfig/ssv.go | 32 ++++++ networkconfig/ssv_test.go | 232 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 networkconfig/ssv_test.go diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index 932465ebe7..db1b66796c 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -112,6 +112,38 @@ func (s *SSVConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } +func (s *SSVConfig) UnmarshalJSON(data []byte) error { + aux := &struct { + DomainType hexutil.Bytes `json:"DomainType,omitempty"` + RegistrySyncOffset *big.Int `json:"RegistrySyncOffset,omitempty"` + RegistryContractAddr ethcommon.Address `json:"RegistryContractAddr,omitempty"` + Bootnodes []string `json:"Bootnodes,omitempty"` + DiscoveryProtocolID hexutil.Bytes `json:"DiscoveryProtocolID,omitempty"` + }{} + + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if len(aux.DomainType) != 4 { + return fmt.Errorf("invalid domain type length: expected 4 bytes, got %d", len(aux.DomainType)) + } + + if len(aux.DiscoveryProtocolID) != 6 { + return fmt.Errorf("invalid discovery protocol ID length: expected 6 bytes, got %d", len(aux.DiscoveryProtocolID)) + } + + *s = SSVConfig{ + DomainType: spectypes.DomainType(aux.DomainType), + RegistrySyncOffset: aux.RegistrySyncOffset, + RegistryContractAddr: aux.RegistryContractAddr, + Bootnodes: aux.Bootnodes, + DiscoveryProtocolID: [6]byte(aux.DiscoveryProtocolID), + } + + return nil +} + func (s SSVConfig) GetDomainType() spectypes.DomainType { return s.DomainType } diff --git a/networkconfig/ssv_test.go b/networkconfig/ssv_test.go new file mode 100644 index 0000000000..c72e3d868a --- /dev/null +++ b/networkconfig/ssv_test.go @@ -0,0 +1,232 @@ +package networkconfig + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "math/big" + "reflect" + "sort" + "testing" + + ethcommon "github.com/ethereum/go-ethereum/common" + spectypes "github.com/ssvlabs/ssv-spec/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestSSVConfig_MarshalUnmarshalJSON(t *testing.T) { + // Create a sample SSVConfig + originalConfig := SSVConfig{ + DomainType: spectypes.DomainType{0x01, 0x02, 0x03, 0x04}, + RegistrySyncOffset: big.NewInt(123456), + RegistryContractAddr: ethcommon.HexToAddress("0x123456789abcdef0123456789abcdef012345678"), + Bootnodes: []string{"bootnode1", "bootnode2"}, + DiscoveryProtocolID: [6]byte{0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}, + } + + // Marshal to JSON + jsonBytes, err := json.Marshal(originalConfig) + require.NoError(t, err) + + // Unmarshal from JSON + var unmarshaledConfig SSVConfig + err = json.Unmarshal(jsonBytes, &unmarshaledConfig) + require.NoError(t, err) + + // Marshal again after unmarshaling + remarshaledBytes, err := json.Marshal(unmarshaledConfig) + require.NoError(t, err) + + // Compare the original and remarshaled JSON bytes + assert.JSONEq(t, string(jsonBytes), string(remarshaledBytes)) + + // Compare the original and unmarshaled structs + assert.Equal(t, originalConfig.DomainType, unmarshaledConfig.DomainType) + assert.Equal(t, originalConfig.RegistrySyncOffset.Int64(), unmarshaledConfig.RegistrySyncOffset.Int64()) + assert.Equal(t, originalConfig.RegistryContractAddr, unmarshaledConfig.RegistryContractAddr) + assert.Equal(t, originalConfig.Bootnodes, unmarshaledConfig.Bootnodes) + assert.Equal(t, originalConfig.DiscoveryProtocolID, unmarshaledConfig.DiscoveryProtocolID) +} + +func TestSSVConfig_MarshalUnmarshalYAML(t *testing.T) { + // Create a sample SSVConfig + originalConfig := SSVConfig{ + DomainType: spectypes.DomainType{0x01, 0x02, 0x03, 0x04}, + RegistrySyncOffset: big.NewInt(123456), + RegistryContractAddr: ethcommon.HexToAddress("0x123456789abcdef0123456789abcdef012345678"), + Bootnodes: []string{"bootnode1", "bootnode2"}, + DiscoveryProtocolID: [6]byte{0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}, + } + + // Marshal to YAML + yamlBytes, err := yaml.Marshal(originalConfig) + require.NoError(t, err) + + // Unmarshal from YAML + var unmarshaledConfig SSVConfig + err = yaml.Unmarshal(yamlBytes, &unmarshaledConfig) + require.NoError(t, err) + + // Marshal again after unmarshaling + remarshaledBytes, err := yaml.Marshal(unmarshaledConfig) + require.NoError(t, err) + + // Compare the original and unmarshaled structs + assert.Equal(t, originalConfig.DomainType, unmarshaledConfig.DomainType) + assert.Equal(t, originalConfig.RegistrySyncOffset.Int64(), unmarshaledConfig.RegistrySyncOffset.Int64()) + assert.Equal(t, originalConfig.RegistryContractAddr, unmarshaledConfig.RegistryContractAddr) + assert.Equal(t, originalConfig.Bootnodes, unmarshaledConfig.Bootnodes) + assert.Equal(t, originalConfig.DiscoveryProtocolID, unmarshaledConfig.DiscoveryProtocolID) + + // Compare the original and remarshaled YAML bytes + // YAML doesn't preserve order by default, so we need to compare the unmarshaled content + var originalYAMLMap map[string]interface{} + var remarshaledYAMLMap map[string]interface{} + + err = yaml.Unmarshal(yamlBytes, &originalYAMLMap) + require.NoError(t, err) + + err = yaml.Unmarshal(remarshaledBytes, &remarshaledYAMLMap) + require.NoError(t, err) + + assert.Equal(t, originalYAMLMap, remarshaledYAMLMap) +} + +// hashStructJSON creates a deterministic hash of a struct by marshaling to sorted JSON +func hashStructJSON(v interface{}) (string, error) { + // Create a JSON encoder that sorts map keys + buffer := &bytes.Buffer{} + encoder := json.NewEncoder(buffer) + encoder.SetIndent("", "") + encoder.SetEscapeHTML(false) + + if err := encoder.Encode(v); err != nil { + return "", err + } + + // Compute SHA-256 hash + hasher := sha256.New() + hasher.Write(buffer.Bytes()) + return hex.EncodeToString(hasher.Sum(nil)), nil +} + +// TestFieldPreservation ensures that all fields are properly preserved during +// marshal/unmarshal operations and that we can detect changes to the struct +func TestFieldPreservation(t *testing.T) { + t.Run("test all fields are present after marshaling", func(t *testing.T) { + // Get all field names from SSVConfig + configType := reflect.TypeOf(SSVConfig{}) + marshaledType := reflect.TypeOf(marshaledConfig{}) + + var configFields, marshaledFields []string + + for i := 0; i < configType.NumField(); i++ { + configFields = append(configFields, configType.Field(i).Name) + } + + for i := 0; i < marshaledType.NumField(); i++ { + marshaledFields = append(marshaledFields, marshaledType.Field(i).Name) + } + + // Sort fields for deterministic comparison + sort.Strings(configFields) + sort.Strings(marshaledFields) + + // Ensure the same fields exist in both structs + assert.Equal(t, configFields, marshaledFields, "SSVConfig and marshaledConfig should have the same fields") + }) + + t.Run("hash comparison JSON", func(t *testing.T) { + // Create a sample config + config := SSVConfig{ + DomainType: spectypes.DomainType{0x01, 0x02, 0x03, 0x04}, + RegistrySyncOffset: big.NewInt(123456), + RegistryContractAddr: ethcommon.HexToAddress("0x123456789abcdef0123456789abcdef012345678"), + Bootnodes: []string{"bootnode1", "bootnode2"}, + DiscoveryProtocolID: [6]byte{0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}, + } + + // Marshal and unmarshal to test preservation + jsonBytes, err := json.Marshal(config) + require.NoError(t, err) + + var unmarshaled SSVConfig + err = json.Unmarshal(jsonBytes, &unmarshaled) + require.NoError(t, err) + + // Hash the original and unmarshaled struct + originalHash, err := hashStructJSON(config) + require.NoError(t, err) + + unmarshaledHash, err := hashStructJSON(unmarshaled) + require.NoError(t, err) + + // The hashes should match if all fields are preserved + assert.Equal(t, originalHash, unmarshaledHash, "Hash mismatch indicates fields weren't properly preserved in JSON") + + // Store the expected hash - this will fail if a new field is added without updating the tests + expectedJSONHash := "3afe88f355185266dfd842df18a096ea8f40dd28f8b022710aedca1d09d59cef" + assert.Equal(t, expectedJSONHash, originalHash, + "Hash has changed. If you've added a new field, please update the expected hash in this test.") + }) + + t.Run("hash comparison YAML", func(t *testing.T) { + // Create a sample config + config := SSVConfig{ + DomainType: spectypes.DomainType{0x01, 0x02, 0x03, 0x04}, + RegistrySyncOffset: big.NewInt(123456), + RegistryContractAddr: ethcommon.HexToAddress("0x123456789abcdef0123456789abcdef012345678"), + Bootnodes: []string{"bootnode1", "bootnode2"}, + DiscoveryProtocolID: [6]byte{0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}, + } + + // Marshal and unmarshal to test preservation + yamlBytes, err := yaml.Marshal(config) + require.NoError(t, err) + + var unmarshaled SSVConfig + err = yaml.Unmarshal(yamlBytes, &unmarshaled) + require.NoError(t, err) + + // For YAML, convert to JSON for consistent hashing + originalHash, err := hashStructJSON(config) + require.NoError(t, err) + + unmarshaledHash, err := hashStructJSON(unmarshaled) + require.NoError(t, err) + + // The hashes should match if all fields are preserved + assert.Equal(t, originalHash, unmarshaledHash, "Hash mismatch indicates fields weren't properly preserved in YAML") + }) +} + +// TestExistingNetworkConfigs validates that all predefined network configs +// can be marshaled and unmarshaled correctly +func TestExistingNetworkConfigs(t *testing.T) { + for networkName, config := range SupportedSSVConfigs { + t.Run(networkName, func(t *testing.T) { + // JSON test + jsonBytes, err := json.Marshal(config) + require.NoError(t, err) + + var jsonUnmarshaled SSVConfig + err = json.Unmarshal(jsonBytes, &jsonUnmarshaled) + require.NoError(t, err) + + assert.Equal(t, config.DomainType, jsonUnmarshaled.DomainType) + + // YAML test + yamlBytes, err := yaml.Marshal(config) + require.NoError(t, err) + + var yamlUnmarshaled SSVConfig + err = yaml.Unmarshal(yamlBytes, &yamlUnmarshaled) + require.NoError(t, err) + + assert.Equal(t, config.DomainType, yamlUnmarshaled.DomainType) + }) + } +} From a286f15f4d24706f2b7e12633139b6df0652dfa3 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 17 Apr 2025 14:59:06 -0300 Subject: [PATCH 11/38] simplify unmarshaling --- networkconfig/ssv.go | 50 ++++++++++++-------------------------------- 1 file changed, 13 insertions(+), 37 deletions(-) diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index db1b66796c..7d53d46810 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -60,6 +60,7 @@ type marshaledConfig struct { DiscoveryProtocolID hexutil.Bytes `json:"DiscoveryProtocolID,omitempty" yaml:"DiscoveryProtocolID,omitempty"` } +// Helper method to avoid duplication between MarshalJSON and MarshalYAML func (s SSVConfig) marshal() marshaledConfig { aux := marshaledConfig{ DomainType: s.DomainType[:], @@ -80,19 +81,8 @@ func (s SSVConfig) MarshalYAML() (interface{}, error) { return s.marshal(), nil } -func (s *SSVConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { - aux := &struct { - DomainType hexutil.Bytes `yaml:"DomainType,omitempty"` - RegistrySyncOffset *big.Int `yaml:"RegistrySyncOffset,omitempty"` - RegistryContractAddr ethcommon.Address `yaml:"RegistryContractAddr,omitempty"` - Bootnodes []string `yaml:"Bootnodes,omitempty"` - DiscoveryProtocolID hexutil.Bytes `yaml:"DiscoveryProtocolID,omitempty"` - }{} - - if err := unmarshal(aux); err != nil { - return err - } - +// Helper method to avoid duplication between UnmarshalJSON and UnmarshalYAML +func (s *SSVConfig) unmarshalFromConfig(aux marshaledConfig) error { if len(aux.DomainType) != 4 { return fmt.Errorf("invalid domain type length: expected 4 bytes, got %d", len(aux.DomainType)) } @@ -112,36 +102,22 @@ func (s *SSVConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } -func (s *SSVConfig) UnmarshalJSON(data []byte) error { - aux := &struct { - DomainType hexutil.Bytes `json:"DomainType,omitempty"` - RegistrySyncOffset *big.Int `json:"RegistrySyncOffset,omitempty"` - RegistryContractAddr ethcommon.Address `json:"RegistryContractAddr,omitempty"` - Bootnodes []string `json:"Bootnodes,omitempty"` - DiscoveryProtocolID hexutil.Bytes `json:"DiscoveryProtocolID,omitempty"` - }{} - - if err := json.Unmarshal(data, aux); err != nil { +func (s *SSVConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + var aux marshaledConfig + if err := unmarshal(&aux); err != nil { return err } - if len(aux.DomainType) != 4 { - return fmt.Errorf("invalid domain type length: expected 4 bytes, got %d", len(aux.DomainType)) - } - - if len(aux.DiscoveryProtocolID) != 6 { - return fmt.Errorf("invalid discovery protocol ID length: expected 6 bytes, got %d", len(aux.DiscoveryProtocolID)) - } + return s.unmarshalFromConfig(aux) +} - *s = SSVConfig{ - DomainType: spectypes.DomainType(aux.DomainType), - RegistrySyncOffset: aux.RegistrySyncOffset, - RegistryContractAddr: aux.RegistryContractAddr, - Bootnodes: aux.Bootnodes, - DiscoveryProtocolID: [6]byte(aux.DiscoveryProtocolID), +func (s *SSVConfig) UnmarshalJSON(data []byte) error { + var aux marshaledConfig + if err := json.Unmarshal(data, &aux); err != nil { + return err } - return nil + return s.unmarshalFromConfig(aux) } func (s SSVConfig) GetDomainType() spectypes.DomainType { From 65acd9b62598e36f26df8162e43c398882d1aeea Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 17 Apr 2025 15:34:08 -0300 Subject: [PATCH 12/38] delete a comment --- beacon/goclient/goclient.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index a4b830a075..acdb3838bc 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -225,9 +225,6 @@ func New( } } - // Despite allowing delayed start, addSingleClient attempts to connect to node before returning, - // so active clients should be up before the initMultiClient - err := client.initMultiClient(opt.Context) if err != nil { logger.Error("Consensus multi client initialization failed", From d5dea5a58ac0666969501d9efc36bc149dd8734e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 17 Apr 2025 15:39:13 -0300 Subject: [PATCH 13/38] use Stringer to log node --- utils/boot_node/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/boot_node/node.go b/utils/boot_node/node.go index 1f836ebd0e..2e7988ed6a 100644 --- a/utils/boot_node/node.go +++ b/utils/boot_node/node.go @@ -106,7 +106,7 @@ func (n *bootNode) Start(ctx context.Context, logger *zap.Logger) error { listener := n.createListener(logger, ipAddr, n.discv5port, privKey) node := listener.LocalNode().Node() logger.Info("Running", - zap.String("node", node.String()), + zap.Stringer("node", node), zap.Stringer("config", n.ssvConfig), fields.ProtocolID(n.ssvConfig.DiscoveryProtocolID), ) From 4fc30f22f582b64c0922c261e22f1acc981952fc Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 17 Apr 2025 16:31:38 -0300 Subject: [PATCH 14/38] guard beacon config reads with mutex --- beacon/goclient/aggregator.go | 6 ++--- beacon/goclient/goclient.go | 23 +++++++++++++------ beacon/goclient/spec.go | 8 ++++--- .../goclient/sync_committee_contribution.go | 5 ++-- beacon/goclient/validator.go | 5 ++-- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/beacon/goclient/aggregator.go b/beacon/goclient/aggregator.go index 41935c8aff..397a566a1d 100644 --- a/beacon/goclient/aggregator.go +++ b/beacon/goclient/aggregator.go @@ -193,9 +193,9 @@ func isAggregator(committeeCount uint64, slotSig []byte) (bool, error) { // waitToSlotTwoThirds waits until two-third of the slot has transpired (SECONDS_PER_SLOT * 2 / 3 seconds after the start of slot) func (gc *GoClient) waitToSlotTwoThirds(slot phase0.Slot) { - oneThird := gc.beaconConfig.SlotDuration / 3 /* one third of slot duration */ - - finalTime := gc.beaconConfig.GetSlotStartTime(slot).Add(2 * oneThird) + config := gc.getBeaconConfig() + oneThird := config.SlotDuration / 3 /* one third of slot duration */ + finalTime := config.GetSlotStartTime(slot).Add(2 * oneThird) wait := time.Until(finalTime) if wait <= 0 { return diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index d2c8aacab1..f2d0363205 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -120,7 +120,7 @@ type GoClient struct { log *zap.Logger ctx context.Context - beaconConfigMu sync.Mutex + beaconConfigMu sync.RWMutex beaconConfig *networkconfig.BeaconConfig beaconConfigInit chan struct{} @@ -245,24 +245,25 @@ func New( case <-client.beaconConfigInit: } - if client.beaconConfig == nil { + config := client.getBeaconConfig() + if config == nil { return nil, fmt.Errorf("no beacon config set") } client.blockRootToSlotCache = ttlcache.New(ttlcache.WithCapacity[phase0.Root, phase0.Slot]( - uint64(client.beaconConfig.SlotsPerEpoch) * BlockRootToSlotCacheCapacityEpochs), + uint64(config.SlotsPerEpoch) * BlockRootToSlotCacheCapacityEpochs), ) client.attestationDataCache = ttlcache.New( // we only fetch attestation data during the slot of the relevant duty (and never later), // hence caching it for 2 slots is sufficient - ttlcache.WithTTL[phase0.Slot, *phase0.AttestationData](2 * client.beaconConfig.SlotDuration), + ttlcache.WithTTL[phase0.Slot, *phase0.AttestationData](2 * config.SlotDuration), ) slotTickerProvider := func() slotticker.SlotTicker { return slotticker.New(logger, slotticker.Config{ - SlotDuration: client.beaconConfig.SlotDuration, - GenesisTime: client.beaconConfig.GenesisTime, + SlotDuration: config.SlotDuration, + GenesisTime: config.GenesisTime, }) } @@ -278,6 +279,13 @@ func New( return client, nil } +// getBeaconConfig provides thread-safe access to the beacon configuration +func (gc *GoClient) getBeaconConfig() *networkconfig.BeaconConfig { + gc.beaconConfigMu.RLock() + defer gc.beaconConfigMu.RUnlock() + return gc.beaconConfig +} + func (gc *GoClient) initMultiClient(ctx context.Context) error { var services []eth2client.Service for _, client := range gc.clients { @@ -380,8 +388,9 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { return } gc.ForkLock.RLock() + config := gc.getBeaconConfig() logger.Info("retrieved fork epochs", - zap.Uint64("current_data_version", uint64(gc.DataVersion(gc.beaconConfig.EstimatedCurrentEpoch()))), + zap.Uint64("current_data_version", uint64(gc.DataVersion(config.EstimatedCurrentEpoch()))), zap.Uint64("altair", uint64(gc.ForkEpochAltair)), zap.Uint64("bellatrix", uint64(gc.ForkEpochBellatrix)), zap.Uint64("capella", uint64(gc.ForkEpochCapella)), diff --git a/beacon/goclient/spec.go b/beacon/goclient/spec.go index 2249cdc949..4a8d4c8efd 100644 --- a/beacon/goclient/spec.go +++ b/beacon/goclient/spec.go @@ -18,10 +18,12 @@ const ( DefaultSlotsPerEpoch = phase0.Slot(32) ) -// BeaconConfig must be called if GoClient is initialized (gc.beaconConfig is set) +// BeaconConfig returns the network Beacon configuration func (gc *GoClient) BeaconConfig() networkconfig.BeaconConfig { - // It should always be non-nil for external calls because it's initialized in GoClient's constructor. - return *gc.beaconConfig + // BeaconConfig must be called if GoClient is initialized (gc.beaconConfig is set) + // It fails otherwise. + config := gc.getBeaconConfig() + return *config } // fetchBeaconConfig must be called once on GoClient's initialization diff --git a/beacon/goclient/sync_committee_contribution.go b/beacon/goclient/sync_committee_contribution.go index d594065bcc..43831b2bdb 100644 --- a/beacon/goclient/sync_committee_contribution.go +++ b/beacon/goclient/sync_committee_contribution.go @@ -144,8 +144,9 @@ func (gc *GoClient) SubmitSignedContributionAndProof(contribution *altair.Signed // waitForOneThirdSlotDuration waits until one-third of the slot has transpired (SECONDS_PER_SLOT / 3 seconds after the start of slot) func (gc *GoClient) waitForOneThirdSlotDuration(slot phase0.Slot) { - delay := gc.beaconConfig.SlotDuration / 3 /* a third of the slot duration */ - finalTime := gc.beaconConfig.GetSlotStartTime(slot).Add(delay) + config := gc.getBeaconConfig() + delay := config.SlotDuration / 3 /* a third of the slot duration */ + finalTime := config.GetSlotStartTime(slot).Add(delay) wait := time.Until(finalTime) if wait <= 0 { return diff --git a/beacon/goclient/validator.go b/beacon/goclient/validator.go index 7be8691709..9fb77e6b18 100644 --- a/beacon/goclient/validator.go +++ b/beacon/goclient/validator.go @@ -99,9 +99,10 @@ func (gc *GoClient) registrationSubmitter(slotTickerProvider slotticker.Provider } // Distribute the registrations evenly across the epoch based on the pubkeys. - slotInEpoch := currentSlot % gc.beaconConfig.SlotsPerEpoch + config := gc.getBeaconConfig() + slotInEpoch := currentSlot % config.SlotsPerEpoch validatorDescriptor := xxhash.Sum64(validatorPk[:]) - shouldSubmit := phase0.Slot(validatorDescriptor)%gc.beaconConfig.SlotsPerEpoch == slotInEpoch + shouldSubmit := phase0.Slot(validatorDescriptor)%config.SlotsPerEpoch == slotInEpoch if r.new || shouldSubmit { r.new = false From c04d2b98287b13c27087cc24bb479ca228a16179 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 17 Apr 2025 16:35:27 -0300 Subject: [PATCH 15/38] add a timeout log --- beacon/goclient/goclient.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index f2d0363205..ef56c365db 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -241,6 +241,10 @@ func New( select { case <-ctx.Done(): + logger.Warn("timeout occurred while waiting for beacon config initialization", + zap.Duration("timeout", client.longTimeout), + zap.Error(ctx.Err()), + ) return nil, fmt.Errorf("timed out awaiting config initialization: %w", ctx.Err()) case <-client.beaconConfigInit: } From c2e2f8f0124c0c65a088a3957fdeda4008f85bf2 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 18 Apr 2025 21:36:37 -0300 Subject: [PATCH 16/38] delete hardcoded beacon configs --- networkconfig/holesky-e2e.go | 23 +++++-------------- networkconfig/holesky-stage.go | 35 +++++++++-------------------- networkconfig/holesky.go | 29 ++++++++---------------- networkconfig/hoodi-stage.go | 31 ++++++++----------------- networkconfig/hoodi.go | 29 ++++++++---------------- networkconfig/local-testnet.go | 24 +++++--------------- networkconfig/mainnet.go | 41 +++++++++++++--------------------- networkconfig/sepolia.go | 29 ++++++++---------------- networkconfig/ssv.go | 17 +++++++------- networkconfig/test-network.go | 25 ++++++--------------- 10 files changed, 90 insertions(+), 193 deletions(-) diff --git a/networkconfig/holesky-e2e.go b/networkconfig/holesky-e2e.go index d005809910..3f3038d22e 100644 --- a/networkconfig/holesky-e2e.go +++ b/networkconfig/holesky-e2e.go @@ -2,25 +2,14 @@ package networkconfig import ( "math/big" - "time" - "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var HoleskyE2E = NetworkConfig{ - Name: "holesky-e2e", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.HoleskyNetwork), - SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.HoleskyNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, - SSVConfig: SSVConfig{ - DomainType: spectypes.DomainType{0x0, 0x0, 0xee, 0x1}, - RegistryContractAddr: "0x58410bef803ecd7e63b23664c586a6db72daf59c", - RegistrySyncOffset: big.NewInt(405579), - Bootnodes: []string{}, - }, +var HoleskyE2E = SSVConfig{ + SSVName: "holesky-e2e", + DomainType: spectypes.DomainType{0x0, 0x0, 0xee, 0x1}, + RegistryContractAddr: "0x58410bef803ecd7e63b23664c586a6db72daf59c", + RegistrySyncOffset: big.NewInt(405579), + Bootnodes: []string{}, } diff --git a/networkconfig/holesky-stage.go b/networkconfig/holesky-stage.go index 153c51265a..20b26496fb 100644 --- a/networkconfig/holesky-stage.go +++ b/networkconfig/holesky-stage.go @@ -2,32 +2,19 @@ package networkconfig import ( "math/big" - "time" - - "github.com/attestantio/go-eth2-client/spec/phase0" - spectypes "github.com/ssvlabs/ssv-spec/types" ) -var HoleskyStage = NetworkConfig{ - Name: "holesky-stage", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.HoleskyNetwork), - SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.HoleskyNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, - SSVConfig: SSVConfig{ - DomainType: [4]byte{0x00, 0x00, 0x31, 0x13}, - RegistrySyncOffset: new(big.Int).SetInt64(84599), - RegistryContractAddr: "0x0d33801785340072C452b994496B19f196b7eE15", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // Public bootnode: - // "enr:-Ja4QDYHVgUs9NvlMqq93ot6VNqbmrIlMrwKnq4X3DPRgyUNB4ospDp8ubMvsf-KsgqY8rzpZKy4GbE1DLphabpRBc-GAY_diLjngmlkgnY0gmlwhDQrLYqJc2VjcDI1NmsxoQKnAiuSlgSR8asjCH0aYoVKM8uPbi4noFuFHZHaAHqknYNzc3YBg3RjcIITiYN1ZHCCD6E", +var HoleskyStage = SSVConfig{ + SSVName: "holesky-stage", + DomainType: [4]byte{0x00, 0x00, 0x31, 0x13}, + RegistrySyncOffset: new(big.Int).SetInt64(84599), + RegistryContractAddr: "0x0d33801785340072C452b994496B19f196b7eE15", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // Public bootnode: + // "enr:-Ja4QDYHVgUs9NvlMqq93ot6VNqbmrIlMrwKnq4X3DPRgyUNB4ospDp8ubMvsf-KsgqY8rzpZKy4GbE1DLphabpRBc-GAY_diLjngmlkgnY0gmlwhDQrLYqJc2VjcDI1NmsxoQKnAiuSlgSR8asjCH0aYoVKM8uPbi4noFuFHZHaAHqknYNzc3YBg3RjcIITiYN1ZHCCD6E", - // Private bootnode: - "enr:-Ja4QDRUBjWOvVfGxpxvv3FqaCy3psm7IsKu5ETb1GXiexGYDFppD33t7AHRfmQddoAkBiyb7pt4t7ZN0sNB9CsW4I-GAZGOmChMgmlkgnY0gmlwhAorXxuJc2VjcDI1NmsxoQP_bBE-ZYvaXKBR3dRYMN5K_lZP-q-YsBzDZEtxH_4T_YNzc3YBg3RjcIITioN1ZHCCD6I", - }, + // Private bootnode: + "enr:-Ja4QDRUBjWOvVfGxpxvv3FqaCy3psm7IsKu5ETb1GXiexGYDFppD33t7AHRfmQddoAkBiyb7pt4t7ZN0sNB9CsW4I-GAZGOmChMgmlkgnY0gmlwhAorXxuJc2VjcDI1NmsxoQP_bBE-ZYvaXKBR3dRYMN5K_lZP-q-YsBzDZEtxH_4T_YNzc3YBg3RjcIITioN1ZHCCD6I", }, } diff --git a/networkconfig/holesky.go b/networkconfig/holesky.go index 77300907a1..9cd0f5eb37 100644 --- a/networkconfig/holesky.go +++ b/networkconfig/holesky.go @@ -2,29 +2,18 @@ package networkconfig import ( "math/big" - "time" - "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var Holesky = NetworkConfig{ - Name: "holesky", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.HoleskyNetwork), - SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.HoleskyNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, - SSVConfig: SSVConfig{ - DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x2}, - RegistrySyncOffset: new(big.Int).SetInt64(181612), - RegistryContractAddr: "0x38A4794cCEd47d3baf7370CcC43B560D3a1beEFA", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // SSV Labs - "enr:-Ja4QKFD3u5tZob7xukp-JKX9QJMFqqI68cItsE4tBbhsOyDR0M_1UUjb35hbrqvTP3bnXO_LnKh-jNLTeaUqN4xiduGAZKaP_sagmlkgnY0gmlwhDb0fh6Jc2VjcDI1NmsxoQMw_H2anuiqP9NmEaZwbUfdvPFog7PvcKmoVByDa576SINzc3YBg3RjcIITioN1ZHCCD6I", - }, +var Holesky = SSVConfig{ + SSVName: "holesky", + DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x2}, + RegistrySyncOffset: new(big.Int).SetInt64(181612), + RegistryContractAddr: "0x38A4794cCEd47d3baf7370CcC43B560D3a1beEFA", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // SSV Labs + "enr:-Ja4QKFD3u5tZob7xukp-JKX9QJMFqqI68cItsE4tBbhsOyDR0M_1UUjb35hbrqvTP3bnXO_LnKh-jNLTeaUqN4xiduGAZKaP_sagmlkgnY0gmlwhDb0fh6Jc2VjcDI1NmsxoQMw_H2anuiqP9NmEaZwbUfdvPFog7PvcKmoVByDa576SINzc3YBg3RjcIITioN1ZHCCD6I", }, } diff --git a/networkconfig/hoodi-stage.go b/networkconfig/hoodi-stage.go index d07daa5671..b98a9ec5ae 100644 --- a/networkconfig/hoodi-stage.go +++ b/networkconfig/hoodi-stage.go @@ -2,29 +2,16 @@ package networkconfig import ( "math/big" - "time" - - "github.com/attestantio/go-eth2-client/spec/phase0" - spectypes "github.com/ssvlabs/ssv-spec/types" ) -var HoodiStage = NetworkConfig{ - Name: "hoodi-stage", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.HoodiNetwork), - SlotDuration: spectypes.HoodiNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.HoodiNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.HoodiNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.HoodiNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, - SSVConfig: SSVConfig{ - DomainType: [4]byte{0x00, 0x00, 0x31, 0x14}, - RegistrySyncOffset: new(big.Int).SetInt64(1004), - RegistryContractAddr: "0x0aaace4e8affc47c6834171c88d342a4abd8f105", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // SSV Labs - "enr:-Ja4QJZcaYfS0GpX-5xREVBa26a-E-QHMFek-EndsJdgM6loIM7pfbJwPDCNK1VzPkUhMjwcTTuNASiHU6X-sjsrxFmGAZWjNu06gmlkgnY0gmlwhErcGnyJc2VjcDI1NmsxoQP_bBE-ZYvaXKBR3dRYMN5K_lZP-q-YsBzDZEtxH_4T_YNzc3YBg3RjcIITioN1ZHCCD6I", - }, +var HoodiStage = SSVConfig{ + SSVName: "hoodi-stage", + DomainType: [4]byte{0x00, 0x00, 0x31, 0x14}, + RegistrySyncOffset: new(big.Int).SetInt64(1004), + RegistryContractAddr: "0x0aaace4e8affc47c6834171c88d342a4abd8f105", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // SSV Labs + "enr:-Ja4QJZcaYfS0GpX-5xREVBa26a-E-QHMFek-EndsJdgM6loIM7pfbJwPDCNK1VzPkUhMjwcTTuNASiHU6X-sjsrxFmGAZWjNu06gmlkgnY0gmlwhErcGnyJc2VjcDI1NmsxoQP_bBE-ZYvaXKBR3dRYMN5K_lZP-q-YsBzDZEtxH_4T_YNzc3YBg3RjcIITioN1ZHCCD6I", }, } diff --git a/networkconfig/hoodi.go b/networkconfig/hoodi.go index 551d030301..30f1a81d65 100644 --- a/networkconfig/hoodi.go +++ b/networkconfig/hoodi.go @@ -2,29 +2,18 @@ package networkconfig import ( "math/big" - "time" - "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var Hoodi = NetworkConfig{ - Name: "hoodi", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.HoodiNetwork), - SlotDuration: spectypes.HoodiNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.HoodiNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.HoodiNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.HoodiNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, - SSVConfig: SSVConfig{ - DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x3}, - RegistrySyncOffset: new(big.Int).SetInt64(1065), - RegistryContractAddr: "0x58410Bef803ECd7E63B23664C586A6DB72DAf59c", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // SSV Labs - "enr:-Ja4QIKlyNFuFtTOnVoavqwmpgSJXfhSmhpdSDOUhf5-FBr7bBxQRvG6VrpUvlkr8MtpNNuMAkM33AseduSaOhd9IeWGAZWjRbnvgmlkgnY0gmlwhCNVVTCJc2VjcDI1NmsxoQNTTyiJPoZh502xOZpHSHAfR-94NaXLvi5J4CNHMh2tjoNzc3YBg3RjcIITioN1ZHCCD6I", - }, +var Hoodi = SSVConfig{ + SSVName: "hoodi", + DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x3}, + RegistrySyncOffset: new(big.Int).SetInt64(1065), + RegistryContractAddr: "0x58410Bef803ECd7E63B23664C586A6DB72DAf59c", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // SSV Labs + "enr:-Ja4QIKlyNFuFtTOnVoavqwmpgSJXfhSmhpdSDOUhf5-FBr7bBxQRvG6VrpUvlkr8MtpNNuMAkM33AseduSaOhd9IeWGAZWjRbnvgmlkgnY0gmlwhCNVVTCJc2VjcDI1NmsxoQNTTyiJPoZh502xOZpHSHAfR-94NaXLvi5J4CNHMh2tjoNzc3YBg3RjcIITioN1ZHCCD6I", }, } diff --git a/networkconfig/local-testnet.go b/networkconfig/local-testnet.go index 7c96e2c4f8..1cc9098fb6 100644 --- a/networkconfig/local-testnet.go +++ b/networkconfig/local-testnet.go @@ -1,26 +1,14 @@ package networkconfig import ( - "time" - - "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var LocalTestnet = NetworkConfig{ - Name: "local-testnet", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.PraterNetwork), - SlotDuration: spectypes.PraterNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.PraterNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.PraterNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.PraterNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, - SSVConfig: SSVConfig{ - DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoV2NetworkID.Byte(), 0x2}, - RegistryContractAddr: "0xC3CD9A0aE89Fff83b71b58b6512D43F8a41f363D", - Bootnodes: []string{ - "enr:-Li4QLR4Y1VbwiqFYKy6m-WFHRNDjhMDZ_qJwIABu2PY9BHjIYwCKpTvvkVmZhu43Q6zVA29sEUhtz10rQjDJkK3Hd-GAYiGrW2Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQJTcI7GHPw-ZqIflPZYYDK_guurp_gsAFF5Erns3-PAvIN0Y3CCE4mDdWRwgg-h", - }, +var LocalTestnet = SSVConfig{ + SSVName: "local-testnet", + DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoV2NetworkID.Byte(), 0x2}, + RegistryContractAddr: "0xC3CD9A0aE89Fff83b71b58b6512D43F8a41f363D", + Bootnodes: []string{ + "enr:-Li4QLR4Y1VbwiqFYKy6m-WFHRNDjhMDZ_qJwIABu2PY9BHjIYwCKpTvvkVmZhu43Q6zVA29sEUhtz10rQjDJkK3Hd-GAYiGrW2Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQJTcI7GHPw-ZqIflPZYYDK_guurp_gsAFF5Erns3-PAvIN0Y3CCE4mDdWRwgg-h", }, } diff --git a/networkconfig/mainnet.go b/networkconfig/mainnet.go index 56bdeb0b73..780f267d8b 100644 --- a/networkconfig/mainnet.go +++ b/networkconfig/mainnet.go @@ -2,38 +2,27 @@ package networkconfig import ( "math/big" - "time" - "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var Mainnet = NetworkConfig{ - Name: "mainnet", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.MainNetwork), - SlotDuration: spectypes.MainNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.MainNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.MainNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.MainNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, - SSVConfig: SSVConfig{ - DomainType: spectypes.AlanMainnet, - RegistrySyncOffset: new(big.Int).SetInt64(17507487), - RegistryContractAddr: "0xDD9BC35aE942eF0cFa76930954a156B3fF30a4E1", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // SSV Labs - "enr:-Ja4QAbDe5XANqJUDyJU1GmtS01qqMwDYx9JNZgymjBb55fMaha80E2HznRYoUGy6NFVSvs1u1cFqSM0MgJI-h1QKLeGAZKaTo7LgmlkgnY0gmlwhDQrfraJc2VjcDI1NmsxoQNEj0Pgq9-VxfeX83LPDOUPyWiTVzdI-DnfMdO1n468u4Nzc3YBg3RjcIITioN1ZHCCD6I", +var Mainnet = SSVConfig{ + SSVName: "mainnet", + DomainType: spectypes.AlanMainnet, + RegistrySyncOffset: new(big.Int).SetInt64(17507487), + RegistryContractAddr: "0xDD9BC35aE942eF0cFa76930954a156B3fF30a4E1", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // SSV Labs + "enr:-Ja4QAbDe5XANqJUDyJU1GmtS01qqMwDYx9JNZgymjBb55fMaha80E2HznRYoUGy6NFVSvs1u1cFqSM0MgJI-h1QKLeGAZKaTo7LgmlkgnY0gmlwhDQrfraJc2VjcDI1NmsxoQNEj0Pgq9-VxfeX83LPDOUPyWiTVzdI-DnfMdO1n468u4Nzc3YBg3RjcIITioN1ZHCCD6I", - // 0NEinfra bootnode - "enr:-Li4QDwrOuhEq5gBJBzFUPkezoYiy56SXZUwkSD7bxYo8RAhPnHyS0de0nOQrzl-cL47RY9Jg8k6Y_MgaUd9a5baYXeGAYnfZE76h2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhDaTS0mJc2VjcDI1NmsxoQMZzUHaN3eClRgF9NAqRNc-ilGpJDDJxdenfo4j-zWKKYN0Y3CCE4iDdWRwgg-g", + // 0NEinfra bootnode + "enr:-Li4QDwrOuhEq5gBJBzFUPkezoYiy56SXZUwkSD7bxYo8RAhPnHyS0de0nOQrzl-cL47RY9Jg8k6Y_MgaUd9a5baYXeGAYnfZE76h2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhDaTS0mJc2VjcDI1NmsxoQMZzUHaN3eClRgF9NAqRNc-ilGpJDDJxdenfo4j-zWKKYN0Y3CCE4iDdWRwgg-g", - // Eridian (eridianalpha.com) - "enr:-Li4QIzHQ2H82twhvsu8EePZ6CA1gl0_B0WWsKaT07245TkHUqXay-MXEgObJB7BxMFl8TylFxfnKNxQyGTXh-2nAlOGAYuraxUEh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBKCzUSJc2VjcDI1NmsxoQNKskkQ6-mBdBWr_ORJfyHai5uD0vL6Fuw90X0sPwmRsoN0Y3CCE4iDdWRwgg-g", + // Eridian (eridianalpha.com) + "enr:-Li4QIzHQ2H82twhvsu8EePZ6CA1gl0_B0WWsKaT07245TkHUqXay-MXEgObJB7BxMFl8TylFxfnKNxQyGTXh-2nAlOGAYuraxUEh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBKCzUSJc2VjcDI1NmsxoQNKskkQ6-mBdBWr_ORJfyHai5uD0vL6Fuw90X0sPwmRsoN0Y3CCE4iDdWRwgg-g", - // CryptoManufaktur - "enr:-Li4QH7FwJcL8gJj0zHAITXqghMkG-A5bfWh2-3Q7vosy9D1BS8HZk-1ITuhK_rfzG3v_UtBDI6uNJZWpdcWfrQFCxKGAYnQ1DRCh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLb3g2Jc2VjcDI1NmsxoQKeSDcZWSaY9FC723E9yYX1Li18bswhLNlxBZdLfgOKp4N0Y3CCE4mDdWRwgg-h", - }, + // CryptoManufaktur + "enr:-Li4QH7FwJcL8gJj0zHAITXqghMkG-A5bfWh2-3Q7vosy9D1BS8HZk-1ITuhK_rfzG3v_UtBDI6uNJZWpdcWfrQFCxKGAYnQ1DRCh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLb3g2Jc2VjcDI1NmsxoQKeSDcZWSaY9FC723E9yYX1Li18bswhLNlxBZdLfgOKp4N0Y3CCE4mDdWRwgg-h", }, } diff --git a/networkconfig/sepolia.go b/networkconfig/sepolia.go index 5359d1059d..d56a660895 100644 --- a/networkconfig/sepolia.go +++ b/networkconfig/sepolia.go @@ -2,29 +2,18 @@ package networkconfig import ( "math/big" - "time" - "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var Sepolia = NetworkConfig{ - Name: "sepolia", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.SepoliaNetwork), - SlotDuration: spectypes.SepoliaNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.SepoliaNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.SepoliaNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.SepoliaNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, - SSVConfig: SSVConfig{ - DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x69}, - RegistrySyncOffset: new(big.Int).SetInt64(7795814), - RegistryContractAddr: "0x261419B48F36EdF420743E9f91bABF4856e76f99", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // SSV Labs - "enr:-Ja4QIE0Ml0a8Pq9zD-0g9KYGN3jAMPJ0CAP0i16fK-PSHfLeORl-Z5p8odoP1oS5S2E8IsF5jNG7gqTKhjVsHR-Z_CGAZXrnTJrgmlkgnY0gmlwhCOjXGWJc2VjcDI1NmsxoQKCRDQsIdFsJDmu_ZU2H6b2_HRJbuUneDXHLfFkSQH9O4Nzc3YBg3RjcIITioN1ZHCCD6I", - }, +var Sepolia = SSVConfig{ + SSVName: "sepolia", + DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x69}, + RegistrySyncOffset: new(big.Int).SetInt64(7795814), + RegistryContractAddr: "0x261419B48F36EdF420743E9f91bABF4856e76f99", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // SSV Labs + "enr:-Ja4QIE0Ml0a8Pq9zD-0g9KYGN3jAMPJ0CAP0i16fK-PSHfLeORl-Z5p8odoP1oS5S2E8IsF5jNG7gqTKhjVsHR-Z_CGAZXrnTJrgmlkgnY0gmlwhCOjXGWJc2VjcDI1NmsxoQKCRDQsIdFsJDmu_ZU2H6b2_HRJbuUneDXHLfFkSQH9O4Nzc3YBg3RjcIITioN1ZHCCD6I", }, } diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index 14c245b041..dcffb63e6d 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -11,14 +11,14 @@ import ( //go:generate go tool -modfile=../tool.mod mockgen -package=networkconfig -destination=./ssv_mock.go -source=./ssv.go var SupportedSSVConfigs = map[string]SSVConfig{ - Mainnet.Name: Mainnet.SSVConfig, - Holesky.Name: Holesky.SSVConfig, - HoleskyStage.Name: HoleskyStage.SSVConfig, - LocalTestnet.Name: LocalTestnet.SSVConfig, - HoleskyE2E.Name: HoleskyE2E.SSVConfig, - Hoodi.Name: Hoodi.SSVConfig, - HoodiStage.Name: HoodiStage.SSVConfig, - Sepolia.Name: Sepolia.SSVConfig, + Mainnet.SSVName: Mainnet, + Holesky.SSVName: Holesky, + HoleskyStage.SSVName: HoleskyStage, + LocalTestnet.SSVName: LocalTestnet, + HoleskyE2E.SSVName: HoleskyE2E, + Hoodi.SSVName: Hoodi, + HoodiStage.SSVName: HoodiStage, + Sepolia.SSVName: Sepolia, } func GetSSVConfigByName(name string) (SSVConfig, error) { @@ -34,6 +34,7 @@ type SSV interface { } type SSVConfig struct { + SSVName string DomainType spectypes.DomainType RegistrySyncOffset *big.Int RegistryContractAddr string // TODO: ethcommon.Address diff --git a/networkconfig/test-network.go b/networkconfig/test-network.go index 9bda6da4b0..7cd490ec76 100644 --- a/networkconfig/test-network.go +++ b/networkconfig/test-network.go @@ -2,27 +2,16 @@ package networkconfig import ( "math/big" - "time" - "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var TestNetwork = NetworkConfig{ - Name: "testnet", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.BeaconTestNetwork), - SlotDuration: spectypes.BeaconTestNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.BeaconTestNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.BeaconTestNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.BeaconTestNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, - SSVConfig: SSVConfig{ - DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoNetworkID.Byte(), 0x2}, - RegistrySyncOffset: new(big.Int).SetInt64(9015219), - RegistryContractAddr: "0x4B133c68A084B8A88f72eDCd7944B69c8D545f03", - Bootnodes: []string{ - "enr:-Li4QFIQzamdvTxGJhvcXG_DFmCeyggSffDnllY5DiU47pd_K_1MRnSaJimWtfKJ-MD46jUX9TwgW5Jqe0t4pH41RYWGAYuFnlyth2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQN4v-N9zFYwEqzGPBBX37q24QPFvAVUtokIo1fblIsmTIN0Y3CCE4uDdWRwgg-j", - }, +var TestNetwork = SSVConfig{ + SSVName: "testnet", + DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoNetworkID.Byte(), 0x2}, + RegistrySyncOffset: new(big.Int).SetInt64(9015219), + RegistryContractAddr: "0x4B133c68A084B8A88f72eDCd7944B69c8D545f03", + Bootnodes: []string{ + "enr:-Li4QFIQzamdvTxGJhvcXG_DFmCeyggSffDnllY5DiU47pd_K_1MRnSaJimWtfKJ-MD46jUX9TwgW5Jqe0t4pH41RYWGAYuFnlyth2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQN4v-N9zFYwEqzGPBBX37q24QPFvAVUtokIo1fblIsmTIN0Y3CCE4uDdWRwgg-j", }, } From be8fbb70f20310bb4c984f41f479f0d47f8cde15 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 18 Apr 2025 21:39:00 -0300 Subject: [PATCH 17/38] Revert "delete hardcoded beacon configs" This reverts commit c2e2f8f0124c0c65a088a3957fdeda4008f85bf2. --- networkconfig/holesky-e2e.go | 23 ++++++++++++++----- networkconfig/holesky-stage.go | 35 ++++++++++++++++++++--------- networkconfig/holesky.go | 29 ++++++++++++++++-------- networkconfig/hoodi-stage.go | 31 +++++++++++++++++-------- networkconfig/hoodi.go | 29 ++++++++++++++++-------- networkconfig/local-testnet.go | 24 +++++++++++++++----- networkconfig/mainnet.go | 41 +++++++++++++++++++++------------- networkconfig/sepolia.go | 29 ++++++++++++++++-------- networkconfig/ssv.go | 17 +++++++------- networkconfig/test-network.go | 25 +++++++++++++++------ 10 files changed, 193 insertions(+), 90 deletions(-) diff --git a/networkconfig/holesky-e2e.go b/networkconfig/holesky-e2e.go index 3f3038d22e..d005809910 100644 --- a/networkconfig/holesky-e2e.go +++ b/networkconfig/holesky-e2e.go @@ -2,14 +2,25 @@ package networkconfig import ( "math/big" + "time" + "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var HoleskyE2E = SSVConfig{ - SSVName: "holesky-e2e", - DomainType: spectypes.DomainType{0x0, 0x0, 0xee, 0x1}, - RegistryContractAddr: "0x58410bef803ecd7e63b23664c586a6db72daf59c", - RegistrySyncOffset: big.NewInt(405579), - Bootnodes: []string{}, +var HoleskyE2E = NetworkConfig{ + Name: "holesky-e2e", + BeaconConfig: BeaconConfig{ + BeaconName: string(spectypes.HoleskyNetwork), + SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), + ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.HoleskyNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + }, + SSVConfig: SSVConfig{ + DomainType: spectypes.DomainType{0x0, 0x0, 0xee, 0x1}, + RegistryContractAddr: "0x58410bef803ecd7e63b23664c586a6db72daf59c", + RegistrySyncOffset: big.NewInt(405579), + Bootnodes: []string{}, + }, } diff --git a/networkconfig/holesky-stage.go b/networkconfig/holesky-stage.go index 20b26496fb..153c51265a 100644 --- a/networkconfig/holesky-stage.go +++ b/networkconfig/holesky-stage.go @@ -2,19 +2,32 @@ package networkconfig import ( "math/big" + "time" + + "github.com/attestantio/go-eth2-client/spec/phase0" + spectypes "github.com/ssvlabs/ssv-spec/types" ) -var HoleskyStage = SSVConfig{ - SSVName: "holesky-stage", - DomainType: [4]byte{0x00, 0x00, 0x31, 0x13}, - RegistrySyncOffset: new(big.Int).SetInt64(84599), - RegistryContractAddr: "0x0d33801785340072C452b994496B19f196b7eE15", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // Public bootnode: - // "enr:-Ja4QDYHVgUs9NvlMqq93ot6VNqbmrIlMrwKnq4X3DPRgyUNB4ospDp8ubMvsf-KsgqY8rzpZKy4GbE1DLphabpRBc-GAY_diLjngmlkgnY0gmlwhDQrLYqJc2VjcDI1NmsxoQKnAiuSlgSR8asjCH0aYoVKM8uPbi4noFuFHZHaAHqknYNzc3YBg3RjcIITiYN1ZHCCD6E", +var HoleskyStage = NetworkConfig{ + Name: "holesky-stage", + BeaconConfig: BeaconConfig{ + BeaconName: string(spectypes.HoleskyNetwork), + SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), + ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.HoleskyNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + }, + SSVConfig: SSVConfig{ + DomainType: [4]byte{0x00, 0x00, 0x31, 0x13}, + RegistrySyncOffset: new(big.Int).SetInt64(84599), + RegistryContractAddr: "0x0d33801785340072C452b994496B19f196b7eE15", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // Public bootnode: + // "enr:-Ja4QDYHVgUs9NvlMqq93ot6VNqbmrIlMrwKnq4X3DPRgyUNB4ospDp8ubMvsf-KsgqY8rzpZKy4GbE1DLphabpRBc-GAY_diLjngmlkgnY0gmlwhDQrLYqJc2VjcDI1NmsxoQKnAiuSlgSR8asjCH0aYoVKM8uPbi4noFuFHZHaAHqknYNzc3YBg3RjcIITiYN1ZHCCD6E", - // Private bootnode: - "enr:-Ja4QDRUBjWOvVfGxpxvv3FqaCy3psm7IsKu5ETb1GXiexGYDFppD33t7AHRfmQddoAkBiyb7pt4t7ZN0sNB9CsW4I-GAZGOmChMgmlkgnY0gmlwhAorXxuJc2VjcDI1NmsxoQP_bBE-ZYvaXKBR3dRYMN5K_lZP-q-YsBzDZEtxH_4T_YNzc3YBg3RjcIITioN1ZHCCD6I", + // Private bootnode: + "enr:-Ja4QDRUBjWOvVfGxpxvv3FqaCy3psm7IsKu5ETb1GXiexGYDFppD33t7AHRfmQddoAkBiyb7pt4t7ZN0sNB9CsW4I-GAZGOmChMgmlkgnY0gmlwhAorXxuJc2VjcDI1NmsxoQP_bBE-ZYvaXKBR3dRYMN5K_lZP-q-YsBzDZEtxH_4T_YNzc3YBg3RjcIITioN1ZHCCD6I", + }, }, } diff --git a/networkconfig/holesky.go b/networkconfig/holesky.go index 9cd0f5eb37..77300907a1 100644 --- a/networkconfig/holesky.go +++ b/networkconfig/holesky.go @@ -2,18 +2,29 @@ package networkconfig import ( "math/big" + "time" + "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var Holesky = SSVConfig{ - SSVName: "holesky", - DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x2}, - RegistrySyncOffset: new(big.Int).SetInt64(181612), - RegistryContractAddr: "0x38A4794cCEd47d3baf7370CcC43B560D3a1beEFA", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // SSV Labs - "enr:-Ja4QKFD3u5tZob7xukp-JKX9QJMFqqI68cItsE4tBbhsOyDR0M_1UUjb35hbrqvTP3bnXO_LnKh-jNLTeaUqN4xiduGAZKaP_sagmlkgnY0gmlwhDb0fh6Jc2VjcDI1NmsxoQMw_H2anuiqP9NmEaZwbUfdvPFog7PvcKmoVByDa576SINzc3YBg3RjcIITioN1ZHCCD6I", +var Holesky = NetworkConfig{ + Name: "holesky", + BeaconConfig: BeaconConfig{ + BeaconName: string(spectypes.HoleskyNetwork), + SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), + ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.HoleskyNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + }, + SSVConfig: SSVConfig{ + DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x2}, + RegistrySyncOffset: new(big.Int).SetInt64(181612), + RegistryContractAddr: "0x38A4794cCEd47d3baf7370CcC43B560D3a1beEFA", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // SSV Labs + "enr:-Ja4QKFD3u5tZob7xukp-JKX9QJMFqqI68cItsE4tBbhsOyDR0M_1UUjb35hbrqvTP3bnXO_LnKh-jNLTeaUqN4xiduGAZKaP_sagmlkgnY0gmlwhDb0fh6Jc2VjcDI1NmsxoQMw_H2anuiqP9NmEaZwbUfdvPFog7PvcKmoVByDa576SINzc3YBg3RjcIITioN1ZHCCD6I", + }, }, } diff --git a/networkconfig/hoodi-stage.go b/networkconfig/hoodi-stage.go index b98a9ec5ae..d07daa5671 100644 --- a/networkconfig/hoodi-stage.go +++ b/networkconfig/hoodi-stage.go @@ -2,16 +2,29 @@ package networkconfig import ( "math/big" + "time" + + "github.com/attestantio/go-eth2-client/spec/phase0" + spectypes "github.com/ssvlabs/ssv-spec/types" ) -var HoodiStage = SSVConfig{ - SSVName: "hoodi-stage", - DomainType: [4]byte{0x00, 0x00, 0x31, 0x14}, - RegistrySyncOffset: new(big.Int).SetInt64(1004), - RegistryContractAddr: "0x0aaace4e8affc47c6834171c88d342a4abd8f105", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // SSV Labs - "enr:-Ja4QJZcaYfS0GpX-5xREVBa26a-E-QHMFek-EndsJdgM6loIM7pfbJwPDCNK1VzPkUhMjwcTTuNASiHU6X-sjsrxFmGAZWjNu06gmlkgnY0gmlwhErcGnyJc2VjcDI1NmsxoQP_bBE-ZYvaXKBR3dRYMN5K_lZP-q-YsBzDZEtxH_4T_YNzc3YBg3RjcIITioN1ZHCCD6I", +var HoodiStage = NetworkConfig{ + Name: "hoodi-stage", + BeaconConfig: BeaconConfig{ + BeaconName: string(spectypes.HoodiNetwork), + SlotDuration: spectypes.HoodiNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.HoodiNetwork.SlotsPerEpoch()), + ForkVersion: spectypes.HoodiNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.HoodiNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + }, + SSVConfig: SSVConfig{ + DomainType: [4]byte{0x00, 0x00, 0x31, 0x14}, + RegistrySyncOffset: new(big.Int).SetInt64(1004), + RegistryContractAddr: "0x0aaace4e8affc47c6834171c88d342a4abd8f105", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // SSV Labs + "enr:-Ja4QJZcaYfS0GpX-5xREVBa26a-E-QHMFek-EndsJdgM6loIM7pfbJwPDCNK1VzPkUhMjwcTTuNASiHU6X-sjsrxFmGAZWjNu06gmlkgnY0gmlwhErcGnyJc2VjcDI1NmsxoQP_bBE-ZYvaXKBR3dRYMN5K_lZP-q-YsBzDZEtxH_4T_YNzc3YBg3RjcIITioN1ZHCCD6I", + }, }, } diff --git a/networkconfig/hoodi.go b/networkconfig/hoodi.go index 30f1a81d65..551d030301 100644 --- a/networkconfig/hoodi.go +++ b/networkconfig/hoodi.go @@ -2,18 +2,29 @@ package networkconfig import ( "math/big" + "time" + "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var Hoodi = SSVConfig{ - SSVName: "hoodi", - DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x3}, - RegistrySyncOffset: new(big.Int).SetInt64(1065), - RegistryContractAddr: "0x58410Bef803ECd7E63B23664C586A6DB72DAf59c", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // SSV Labs - "enr:-Ja4QIKlyNFuFtTOnVoavqwmpgSJXfhSmhpdSDOUhf5-FBr7bBxQRvG6VrpUvlkr8MtpNNuMAkM33AseduSaOhd9IeWGAZWjRbnvgmlkgnY0gmlwhCNVVTCJc2VjcDI1NmsxoQNTTyiJPoZh502xOZpHSHAfR-94NaXLvi5J4CNHMh2tjoNzc3YBg3RjcIITioN1ZHCCD6I", +var Hoodi = NetworkConfig{ + Name: "hoodi", + BeaconConfig: BeaconConfig{ + BeaconName: string(spectypes.HoodiNetwork), + SlotDuration: spectypes.HoodiNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.HoodiNetwork.SlotsPerEpoch()), + ForkVersion: spectypes.HoodiNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.HoodiNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + }, + SSVConfig: SSVConfig{ + DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x3}, + RegistrySyncOffset: new(big.Int).SetInt64(1065), + RegistryContractAddr: "0x58410Bef803ECd7E63B23664C586A6DB72DAf59c", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // SSV Labs + "enr:-Ja4QIKlyNFuFtTOnVoavqwmpgSJXfhSmhpdSDOUhf5-FBr7bBxQRvG6VrpUvlkr8MtpNNuMAkM33AseduSaOhd9IeWGAZWjRbnvgmlkgnY0gmlwhCNVVTCJc2VjcDI1NmsxoQNTTyiJPoZh502xOZpHSHAfR-94NaXLvi5J4CNHMh2tjoNzc3YBg3RjcIITioN1ZHCCD6I", + }, }, } diff --git a/networkconfig/local-testnet.go b/networkconfig/local-testnet.go index 1cc9098fb6..7c96e2c4f8 100644 --- a/networkconfig/local-testnet.go +++ b/networkconfig/local-testnet.go @@ -1,14 +1,26 @@ package networkconfig import ( + "time" + + "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var LocalTestnet = SSVConfig{ - SSVName: "local-testnet", - DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoV2NetworkID.Byte(), 0x2}, - RegistryContractAddr: "0xC3CD9A0aE89Fff83b71b58b6512D43F8a41f363D", - Bootnodes: []string{ - "enr:-Li4QLR4Y1VbwiqFYKy6m-WFHRNDjhMDZ_qJwIABu2PY9BHjIYwCKpTvvkVmZhu43Q6zVA29sEUhtz10rQjDJkK3Hd-GAYiGrW2Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQJTcI7GHPw-ZqIflPZYYDK_guurp_gsAFF5Erns3-PAvIN0Y3CCE4mDdWRwgg-h", +var LocalTestnet = NetworkConfig{ + Name: "local-testnet", + BeaconConfig: BeaconConfig{ + BeaconName: string(spectypes.PraterNetwork), + SlotDuration: spectypes.PraterNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.PraterNetwork.SlotsPerEpoch()), + ForkVersion: spectypes.PraterNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.PraterNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + }, + SSVConfig: SSVConfig{ + DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoV2NetworkID.Byte(), 0x2}, + RegistryContractAddr: "0xC3CD9A0aE89Fff83b71b58b6512D43F8a41f363D", + Bootnodes: []string{ + "enr:-Li4QLR4Y1VbwiqFYKy6m-WFHRNDjhMDZ_qJwIABu2PY9BHjIYwCKpTvvkVmZhu43Q6zVA29sEUhtz10rQjDJkK3Hd-GAYiGrW2Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQJTcI7GHPw-ZqIflPZYYDK_guurp_gsAFF5Erns3-PAvIN0Y3CCE4mDdWRwgg-h", + }, }, } diff --git a/networkconfig/mainnet.go b/networkconfig/mainnet.go index 780f267d8b..56bdeb0b73 100644 --- a/networkconfig/mainnet.go +++ b/networkconfig/mainnet.go @@ -2,27 +2,38 @@ package networkconfig import ( "math/big" + "time" + "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var Mainnet = SSVConfig{ - SSVName: "mainnet", - DomainType: spectypes.AlanMainnet, - RegistrySyncOffset: new(big.Int).SetInt64(17507487), - RegistryContractAddr: "0xDD9BC35aE942eF0cFa76930954a156B3fF30a4E1", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // SSV Labs - "enr:-Ja4QAbDe5XANqJUDyJU1GmtS01qqMwDYx9JNZgymjBb55fMaha80E2HznRYoUGy6NFVSvs1u1cFqSM0MgJI-h1QKLeGAZKaTo7LgmlkgnY0gmlwhDQrfraJc2VjcDI1NmsxoQNEj0Pgq9-VxfeX83LPDOUPyWiTVzdI-DnfMdO1n468u4Nzc3YBg3RjcIITioN1ZHCCD6I", +var Mainnet = NetworkConfig{ + Name: "mainnet", + BeaconConfig: BeaconConfig{ + BeaconName: string(spectypes.MainNetwork), + SlotDuration: spectypes.MainNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.MainNetwork.SlotsPerEpoch()), + ForkVersion: spectypes.MainNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.MainNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + }, + SSVConfig: SSVConfig{ + DomainType: spectypes.AlanMainnet, + RegistrySyncOffset: new(big.Int).SetInt64(17507487), + RegistryContractAddr: "0xDD9BC35aE942eF0cFa76930954a156B3fF30a4E1", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // SSV Labs + "enr:-Ja4QAbDe5XANqJUDyJU1GmtS01qqMwDYx9JNZgymjBb55fMaha80E2HznRYoUGy6NFVSvs1u1cFqSM0MgJI-h1QKLeGAZKaTo7LgmlkgnY0gmlwhDQrfraJc2VjcDI1NmsxoQNEj0Pgq9-VxfeX83LPDOUPyWiTVzdI-DnfMdO1n468u4Nzc3YBg3RjcIITioN1ZHCCD6I", - // 0NEinfra bootnode - "enr:-Li4QDwrOuhEq5gBJBzFUPkezoYiy56SXZUwkSD7bxYo8RAhPnHyS0de0nOQrzl-cL47RY9Jg8k6Y_MgaUd9a5baYXeGAYnfZE76h2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhDaTS0mJc2VjcDI1NmsxoQMZzUHaN3eClRgF9NAqRNc-ilGpJDDJxdenfo4j-zWKKYN0Y3CCE4iDdWRwgg-g", + // 0NEinfra bootnode + "enr:-Li4QDwrOuhEq5gBJBzFUPkezoYiy56SXZUwkSD7bxYo8RAhPnHyS0de0nOQrzl-cL47RY9Jg8k6Y_MgaUd9a5baYXeGAYnfZE76h2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhDaTS0mJc2VjcDI1NmsxoQMZzUHaN3eClRgF9NAqRNc-ilGpJDDJxdenfo4j-zWKKYN0Y3CCE4iDdWRwgg-g", - // Eridian (eridianalpha.com) - "enr:-Li4QIzHQ2H82twhvsu8EePZ6CA1gl0_B0WWsKaT07245TkHUqXay-MXEgObJB7BxMFl8TylFxfnKNxQyGTXh-2nAlOGAYuraxUEh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBKCzUSJc2VjcDI1NmsxoQNKskkQ6-mBdBWr_ORJfyHai5uD0vL6Fuw90X0sPwmRsoN0Y3CCE4iDdWRwgg-g", + // Eridian (eridianalpha.com) + "enr:-Li4QIzHQ2H82twhvsu8EePZ6CA1gl0_B0WWsKaT07245TkHUqXay-MXEgObJB7BxMFl8TylFxfnKNxQyGTXh-2nAlOGAYuraxUEh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBKCzUSJc2VjcDI1NmsxoQNKskkQ6-mBdBWr_ORJfyHai5uD0vL6Fuw90X0sPwmRsoN0Y3CCE4iDdWRwgg-g", - // CryptoManufaktur - "enr:-Li4QH7FwJcL8gJj0zHAITXqghMkG-A5bfWh2-3Q7vosy9D1BS8HZk-1ITuhK_rfzG3v_UtBDI6uNJZWpdcWfrQFCxKGAYnQ1DRCh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLb3g2Jc2VjcDI1NmsxoQKeSDcZWSaY9FC723E9yYX1Li18bswhLNlxBZdLfgOKp4N0Y3CCE4mDdWRwgg-h", + // CryptoManufaktur + "enr:-Li4QH7FwJcL8gJj0zHAITXqghMkG-A5bfWh2-3Q7vosy9D1BS8HZk-1ITuhK_rfzG3v_UtBDI6uNJZWpdcWfrQFCxKGAYnQ1DRCh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLb3g2Jc2VjcDI1NmsxoQKeSDcZWSaY9FC723E9yYX1Li18bswhLNlxBZdLfgOKp4N0Y3CCE4mDdWRwgg-h", + }, }, } diff --git a/networkconfig/sepolia.go b/networkconfig/sepolia.go index d56a660895..5359d1059d 100644 --- a/networkconfig/sepolia.go +++ b/networkconfig/sepolia.go @@ -2,18 +2,29 @@ package networkconfig import ( "math/big" + "time" + "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var Sepolia = SSVConfig{ - SSVName: "sepolia", - DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x69}, - RegistrySyncOffset: new(big.Int).SetInt64(7795814), - RegistryContractAddr: "0x261419B48F36EdF420743E9f91bABF4856e76f99", - DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, - Bootnodes: []string{ - // SSV Labs - "enr:-Ja4QIE0Ml0a8Pq9zD-0g9KYGN3jAMPJ0CAP0i16fK-PSHfLeORl-Z5p8odoP1oS5S2E8IsF5jNG7gqTKhjVsHR-Z_CGAZXrnTJrgmlkgnY0gmlwhCOjXGWJc2VjcDI1NmsxoQKCRDQsIdFsJDmu_ZU2H6b2_HRJbuUneDXHLfFkSQH9O4Nzc3YBg3RjcIITioN1ZHCCD6I", +var Sepolia = NetworkConfig{ + Name: "sepolia", + BeaconConfig: BeaconConfig{ + BeaconName: string(spectypes.SepoliaNetwork), + SlotDuration: spectypes.SepoliaNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.SepoliaNetwork.SlotsPerEpoch()), + ForkVersion: spectypes.SepoliaNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.SepoliaNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + }, + SSVConfig: SSVConfig{ + DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x69}, + RegistrySyncOffset: new(big.Int).SetInt64(7795814), + RegistryContractAddr: "0x261419B48F36EdF420743E9f91bABF4856e76f99", + DiscoveryProtocolID: [6]byte{'s', 's', 'v', 'd', 'v', '5'}, + Bootnodes: []string{ + // SSV Labs + "enr:-Ja4QIE0Ml0a8Pq9zD-0g9KYGN3jAMPJ0CAP0i16fK-PSHfLeORl-Z5p8odoP1oS5S2E8IsF5jNG7gqTKhjVsHR-Z_CGAZXrnTJrgmlkgnY0gmlwhCOjXGWJc2VjcDI1NmsxoQKCRDQsIdFsJDmu_ZU2H6b2_HRJbuUneDXHLfFkSQH9O4Nzc3YBg3RjcIITioN1ZHCCD6I", + }, }, } diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index dcffb63e6d..14c245b041 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -11,14 +11,14 @@ import ( //go:generate go tool -modfile=../tool.mod mockgen -package=networkconfig -destination=./ssv_mock.go -source=./ssv.go var SupportedSSVConfigs = map[string]SSVConfig{ - Mainnet.SSVName: Mainnet, - Holesky.SSVName: Holesky, - HoleskyStage.SSVName: HoleskyStage, - LocalTestnet.SSVName: LocalTestnet, - HoleskyE2E.SSVName: HoleskyE2E, - Hoodi.SSVName: Hoodi, - HoodiStage.SSVName: HoodiStage, - Sepolia.SSVName: Sepolia, + Mainnet.Name: Mainnet.SSVConfig, + Holesky.Name: Holesky.SSVConfig, + HoleskyStage.Name: HoleskyStage.SSVConfig, + LocalTestnet.Name: LocalTestnet.SSVConfig, + HoleskyE2E.Name: HoleskyE2E.SSVConfig, + Hoodi.Name: Hoodi.SSVConfig, + HoodiStage.Name: HoodiStage.SSVConfig, + Sepolia.Name: Sepolia.SSVConfig, } func GetSSVConfigByName(name string) (SSVConfig, error) { @@ -34,7 +34,6 @@ type SSV interface { } type SSVConfig struct { - SSVName string DomainType spectypes.DomainType RegistrySyncOffset *big.Int RegistryContractAddr string // TODO: ethcommon.Address diff --git a/networkconfig/test-network.go b/networkconfig/test-network.go index 7cd490ec76..9bda6da4b0 100644 --- a/networkconfig/test-network.go +++ b/networkconfig/test-network.go @@ -2,16 +2,27 @@ package networkconfig import ( "math/big" + "time" + "github.com/attestantio/go-eth2-client/spec/phase0" spectypes "github.com/ssvlabs/ssv-spec/types" ) -var TestNetwork = SSVConfig{ - SSVName: "testnet", - DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoNetworkID.Byte(), 0x2}, - RegistrySyncOffset: new(big.Int).SetInt64(9015219), - RegistryContractAddr: "0x4B133c68A084B8A88f72eDCd7944B69c8D545f03", - Bootnodes: []string{ - "enr:-Li4QFIQzamdvTxGJhvcXG_DFmCeyggSffDnllY5DiU47pd_K_1MRnSaJimWtfKJ-MD46jUX9TwgW5Jqe0t4pH41RYWGAYuFnlyth2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQN4v-N9zFYwEqzGPBBX37q24QPFvAVUtokIo1fblIsmTIN0Y3CCE4uDdWRwgg-j", +var TestNetwork = NetworkConfig{ + Name: "testnet", + BeaconConfig: BeaconConfig{ + BeaconName: string(spectypes.BeaconTestNetwork), + SlotDuration: spectypes.BeaconTestNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.BeaconTestNetwork.SlotsPerEpoch()), + ForkVersion: spectypes.BeaconTestNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.BeaconTestNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + }, + SSVConfig: SSVConfig{ + DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoNetworkID.Byte(), 0x2}, + RegistrySyncOffset: new(big.Int).SetInt64(9015219), + RegistryContractAddr: "0x4B133c68A084B8A88f72eDCd7944B69c8D545f03", + Bootnodes: []string{ + "enr:-Li4QFIQzamdvTxGJhvcXG_DFmCeyggSffDnllY5DiU47pd_K_1MRnSaJimWtfKJ-MD46jUX9TwgW5Jqe0t4pH41RYWGAYuFnlyth2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQN4v-N9zFYwEqzGPBBX37q24QPFvAVUtokIo1fblIsmTIN0Y3CCE4uDdWRwgg-j", + }, }, } From b43d3e56aa59f346dc5551cacc390f63468829cd Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 18 Apr 2025 22:34:07 -0300 Subject: [PATCH 18/38] networkconfig: make hardcoded values configurable --- beacon/goclient/aggregator.go | 10 +- beacon/goclient/spec.go | 87 ++++++++- .../goclient/sync_committee_contribution.go | 7 +- beacon/goclient/types.go | 8 +- message/validation/const.go | 1 - message/validation/partial_validation.go | 3 +- message/validation/validation.go | 4 +- network/topics/controller_test.go | 1 - network/topics/params/helpers.go | 4 - network/topics/params/message_rate.go | 179 +++++++++++------- network/topics/params/message_rate_test.go | 4 +- network/topics/params/peer_score.go | 21 +- network/topics/params/scores_test.go | 11 +- network/topics/params/topic_score.go | 19 +- network/topics/pubsub.go | 3 +- network/topics/scoring.go | 4 +- networkconfig/beacon.go | 50 +++-- networkconfig/beacon_mock.go | 70 +++++-- networkconfig/holesky-e2e.go | 9 +- networkconfig/holesky-stage.go | 1 + networkconfig/holesky.go | 1 + networkconfig/hoodi-stage.go | 1 + networkconfig/hoodi.go | 1 + networkconfig/local-testnet.go | 1 + networkconfig/mainnet.go | 1 + networkconfig/network_mock.go | 70 +++++-- networkconfig/sepolia.go | 1 + networkconfig/ssv.go | 55 +++--- networkconfig/test-network.go | 1 + operator/duties/committee_test.go | 4 +- operator/duties/scheduler.go | 6 +- operator/duties/scheduler_test.go | 2 +- operator/duties/sync_committee.go | 2 +- operator/duties/sync_committee_test.go | 4 +- operator/validator/controller.go | 2 +- 35 files changed, 441 insertions(+), 207 deletions(-) diff --git a/beacon/goclient/aggregator.go b/beacon/goclient/aggregator.go index 41935c8aff..24f216e0a5 100644 --- a/beacon/goclient/aggregator.go +++ b/beacon/goclient/aggregator.go @@ -22,7 +22,7 @@ func (gc *GoClient) SubmitAggregateSelectionProof(slot phase0.Slot, committeeInd gc.waitToSlotTwoThirds(slot) // differ from spec because we need to subscribe to subnet - isAggregator, err := isAggregator(committeeLength, slotSig) + isAggregator, err := gc.isAggregator(committeeLength, slotSig) if err != nil { return nil, DataVersionNil, fmt.Errorf("failed to check if validator is an aggregator: %w", err) } @@ -180,8 +180,8 @@ func (gc *GoClient) SubmitSignedAggregateSelectionProof(msg *spec.VersionedSigne // committee = get_beacon_committee(state, slot, index) // modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE) // return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 -func isAggregator(committeeCount uint64, slotSig []byte) (bool, error) { - modulo := committeeCount / TargetAggregatorsPerCommittee +func (gc *GoClient) isAggregator(committeeCount uint64, slotSig []byte) (bool, error) { + modulo := committeeCount / gc.beaconConfig.TargetAggregatorsPerCommittee if modulo == 0 { // Modulo must be at least 1. modulo = 1 @@ -193,9 +193,9 @@ func isAggregator(committeeCount uint64, slotSig []byte) (bool, error) { // waitToSlotTwoThirds waits until two-third of the slot has transpired (SECONDS_PER_SLOT * 2 / 3 seconds after the start of slot) func (gc *GoClient) waitToSlotTwoThirds(slot phase0.Slot) { - oneThird := gc.beaconConfig.SlotDuration / 3 /* one third of slot duration */ + oneInterval := gc.beaconConfig.IntervalDuration() /* one third of slot duration */ - finalTime := gc.beaconConfig.GetSlotStartTime(slot).Add(2 * oneThird) + finalTime := gc.beaconConfig.GetSlotStartTime(slot).Add(2 * oneInterval) wait := time.Until(finalTime) if wait <= 0 { return diff --git a/beacon/goclient/spec.go b/beacon/goclient/spec.go index 2249cdc949..a3253033b1 100644 --- a/beacon/goclient/spec.go +++ b/beacon/goclient/spec.go @@ -14,8 +14,14 @@ import ( ) const ( - DefaultSlotDuration = 12 * time.Second - DefaultSlotsPerEpoch = phase0.Slot(32) + DefaultSlotDuration = 12 * time.Second + DefaultSlotsPerEpoch = phase0.Slot(32) + DefaultEpochsPerSyncCommitteePeriod = phase0.Epoch(256) + DefaultSyncCommitteeSize = uint64(512) + DefaultSyncCommitteeSubnetCount = uint64(4) + DefaultTargetAggregatorsPerSyncSubcommittee = uint64(16) + DefaultTargetAggregatorsPerCommittee = uint64(16) + DefaultIntervalsPerSlot = uint64(3) ) // BeaconConfig must be called if GoClient is initialized (gc.beaconConfig is set) @@ -74,6 +80,66 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } } + epochsPerSyncCommitteePeriod := DefaultEpochsPerSyncCommitteePeriod + if epochsPerSyncCommitteePeriodRaw, ok := specResponse.Data["EPOCHS_PER_SYNC_COMMITTEE_PERIOD"]; ok { + if epochsPerSyncCommitteePeriodDecoded, ok := epochsPerSyncCommitteePeriodRaw.(uint64); ok { + epochsPerSyncCommitteePeriod = phase0.Epoch(epochsPerSyncCommitteePeriodDecoded) + } else { + gc.log.Warn("epochs per sync committee not known by chain, using default value", + zap.Any("value", epochsPerSyncCommitteePeriod)) + } + } + + syncCommitteeSize := DefaultSyncCommitteeSize + if syncCommitteeSizeRaw, ok := specResponse.Data["SYNC_COMMITTEE_SIZE"]; ok { + if syncCommitteeSizeDecoded, ok := syncCommitteeSizeRaw.(uint64); ok { + syncCommitteeSize = syncCommitteeSizeDecoded + } else { + gc.log.Warn("sync committee size not known by chain, using default value", + zap.Any("value", syncCommitteeSize)) + } + } + + targetAggregatorsPerCommittee := DefaultTargetAggregatorsPerCommittee + if targetAggregatorsPerCommitteeRaw, ok := specResponse.Data["TARGET_AGGREGATORS_PER_COMMITTEE"]; ok { + if targetAggregatorsPerCommitteeDecoded, ok := targetAggregatorsPerCommitteeRaw.(uint64); ok { + targetAggregatorsPerCommittee = targetAggregatorsPerCommitteeDecoded + } else { + gc.log.Warn("target aggregators per committee not known by chain, using default value", + zap.Any("value", targetAggregatorsPerCommittee)) + } + } + + targetAggregatorsPerSyncSubcommittee := DefaultTargetAggregatorsPerSyncSubcommittee + if targetAggregatorsPerSyncSubcommitteeRaw, ok := specResponse.Data["TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE"]; ok { + if targetAggregatorsPerSyncSubcommitteeDecoded, ok := targetAggregatorsPerSyncSubcommitteeRaw.(uint64); ok { + targetAggregatorsPerSyncSubcommittee = targetAggregatorsPerSyncSubcommitteeDecoded + } else { + gc.log.Warn("target aggregators per sync subcommittee not known by chain, using default value", + zap.Any("value", targetAggregatorsPerSyncSubcommittee)) + } + } + + intervalsPerSlot := DefaultIntervalsPerSlot + if intervalsPerSlotRaw, ok := specResponse.Data["INTERVALS_PER_SLOT"]; ok { + if intervalsPerSlotDecoded, ok := intervalsPerSlotRaw.(uint64); ok { + intervalsPerSlot = intervalsPerSlotDecoded + } else { + gc.log.Warn("intervals per slot not known by chain, using default value", + zap.Any("value", intervalsPerSlot)) + } + } + + syncCommitteeSubnetCount := DefaultSyncCommitteeSubnetCount + if syncCommitteeSubnetCountRaw, ok := specResponse.Data["SYNC_COMMITTEE_SUBNET_COUNT"]; ok { + if syncCommitteeSubnetCountDecoded, ok := syncCommitteeSubnetCountRaw.(uint64); ok { + syncCommitteeSubnetCount = syncCommitteeSubnetCountDecoded + } else { + gc.log.Warn("sync committee subnet count not known by chain, using default value", + zap.Any("value", syncCommitteeSubnetCount)) + } + } + start = time.Now() genesisResponse, err := client.Genesis(gc.ctx, &api.GenesisOpts{}) recordRequestDuration(gc.ctx, "Genesis", client.Address(), http.MethodGet, time.Since(start), err) @@ -91,11 +157,18 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } beaconConfig := networkconfig.BeaconConfig{ - BeaconName: networkName, - SlotDuration: slotDuration, - SlotsPerEpoch: slotsPerEpoch, - ForkVersion: genesisResponse.Data.GenesisForkVersion, - GenesisTime: genesisResponse.Data.GenesisTime, + BeaconName: networkName, + SlotDuration: slotDuration, + SlotsPerEpoch: slotsPerEpoch, + EpochsPerSyncCommitteePeriod: epochsPerSyncCommitteePeriod, + SyncCommitteeSize: syncCommitteeSize, + SyncCommitteeSubnetCount: syncCommitteeSubnetCount, + TargetAggregatorsPerSyncSubcommittee: targetAggregatorsPerSyncSubcommittee, + TargetAggregatorsPerCommittee: targetAggregatorsPerCommittee, + IntervalsPerSlot: intervalsPerSlot, + ForkVersion: genesisResponse.Data.GenesisForkVersion, + GenesisTime: genesisResponse.Data.GenesisTime, + GenesisValidatorsRoot: genesisResponse.Data.GenesisValidatorsRoot, } return beaconConfig, nil diff --git a/beacon/goclient/sync_committee_contribution.go b/beacon/goclient/sync_committee_contribution.go index d594065bcc..0d325dc57d 100644 --- a/beacon/goclient/sync_committee_contribution.go +++ b/beacon/goclient/sync_committee_contribution.go @@ -23,7 +23,8 @@ func (gc *GoClient) IsSyncCommitteeAggregator(proof []byte) (bool, error) { hash := sha256.Sum256(proof) // Keep the signature if it's an aggregator. - modulo := SyncCommitteeSize / SyncCommitteeSubnetCount / TargetAggregatorsPerSyncSubcommittee + cfg := gc.BeaconConfig() + modulo := cfg.SyncCommitteeSize / cfg.SyncCommitteeSubnetCount / cfg.TargetAggregatorsPerSyncSubcommittee if modulo == uint64(0) { // Modulo must be at least 1. modulo = 1 @@ -33,7 +34,7 @@ func (gc *GoClient) IsSyncCommitteeAggregator(proof []byte) (bool, error) { // SyncCommitteeSubnetID returns sync committee subnet ID from subcommittee index func (gc *GoClient) SyncCommitteeSubnetID(index phase0.CommitteeIndex) (uint64, error) { - return uint64(index) / (SyncCommitteeSize / SyncCommitteeSubnetCount), nil + return uint64(index) / (gc.BeaconConfig().SyncCommitteeSize / gc.BeaconConfig().SyncCommitteeSubnetCount), nil } // GetSyncCommitteeContribution returns @@ -144,7 +145,7 @@ func (gc *GoClient) SubmitSignedContributionAndProof(contribution *altair.Signed // waitForOneThirdSlotDuration waits until one-third of the slot has transpired (SECONDS_PER_SLOT / 3 seconds after the start of slot) func (gc *GoClient) waitForOneThirdSlotDuration(slot phase0.Slot) { - delay := gc.beaconConfig.SlotDuration / 3 /* a third of the slot duration */ + delay := gc.beaconConfig.IntervalDuration() /* a third of the slot duration */ finalTime := gc.beaconConfig.GetSlotStartTime(slot).Add(delay) wait := time.Until(finalTime) if wait <= 0 { diff --git a/beacon/goclient/types.go b/beacon/goclient/types.go index 9589c2a87c..28a4325674 100644 --- a/beacon/goclient/types.go +++ b/beacon/goclient/types.go @@ -7,12 +7,6 @@ import ( ) var ( - SyncCommitteeSize uint64 = 512 - SyncCommitteeSubnetCount uint64 = 4 - TargetAggregatorsPerSyncSubcommittee uint64 = 16 - EpochsPerSyncCommitteePeriod uint64 = 256 - TargetAggregatorsPerCommittee uint64 = 16 // FarFutureEpoch is the null representation of an epoch. - FarFutureEpoch phase0.Epoch = math.MaxUint64 - IntervalsPerSlot uint64 = 3 + FarFutureEpoch phase0.Epoch = math.MaxUint64 ) diff --git a/message/validation/const.go b/message/validation/const.go index 4efaee7d25..1f40abfc7d 100644 --- a/message/validation/const.go +++ b/message/validation/const.go @@ -14,7 +14,6 @@ const ( allowedRoundsInFuture = 1 allowedRoundsInPast = 2 LateSlotAllowance = 2 - syncCommitteeSize = 512 rsaSignatureSize = 256 operatorIDSize = 8 // uint64 slotSize = 8 // uint64 diff --git a/message/validation/partial_validation.go b/message/validation/partial_validation.go index e887f28ecb..6f03b5d770 100644 --- a/message/validation/partial_validation.go +++ b/message/validation/partial_validation.go @@ -193,7 +193,8 @@ func (mv *messageValidator) validatePartialSigMessagesByDutyLogic( if signedSSVMessage.SSVMessage.MsgID.GetRoleType() == spectypes.RoleCommittee { // Rule: The number of signatures must be <= min(2*V, V + SYNC_COMMITTEE_SIZE) where V is the number of validators assigned to the cluster - if partialSignatureMessageCount > min(2*clusterValidatorCount, clusterValidatorCount+syncCommitteeSize) { + // #nosec G115 + if partialSignatureMessageCount > min(2*clusterValidatorCount, clusterValidatorCount+int(mv.netCfg.GetSyncCommitteeSize())) { return ErrTooManyPartialSignatureMessages } diff --git a/message/validation/validation.go b/message/validation/validation.go index ee69de8171..edc156f74e 100644 --- a/message/validation/validation.go +++ b/message/validation/validation.go @@ -211,7 +211,6 @@ func (mv *messageValidator) getValidationLock(messageID spectypes.MessageID) *sy lock := &sync.Mutex{} - epochDuration := time.Duration(mv.netCfg.GetSlotsPerEpoch()) * mv.netCfg.GetSlotDuration() // #nosec G115 - slots per epoch never exceeds math.MaxInt64 // validationLockTTL specifies how much time a particular validation lock is meant to // live. It must be large enough for validation lock to never expire while we still are // expecting to process messages targeting that same validation lock. For a message @@ -220,8 +219,7 @@ func (mv *messageValidator) getValidationLock(messageID spectypes.MessageID) *sy // be allowed to take place). // 2 epoch duration is a safe TTL to use - message validation will reject processing // for any message older than that. - validationLockTTL := 2 * epochDuration - mv.validationLockCache.Set(messageID, lock, validationLockTTL) + mv.validationLockCache.Set(messageID, lock, 2*mv.netCfg.EpochDuration()) return lock, nil }) diff --git a/network/topics/controller_test.go b/network/topics/controller_test.go index 37ef279faf..d8b864ed65 100644 --- a/network/topics/controller_test.go +++ b/network/topics/controller_test.go @@ -393,7 +393,6 @@ func newPeer(ctx context.Context, logger *zap.Logger, t *testing.T, msgValidator Scoring: &topics.ScoringConfig{ IPWhitelist: nil, IPColocationWeight: 0, - OneEpochDuration: time.Minute, }, MsgValidator: msgValidator, ScoreInspector: scoreInspector, diff --git a/network/topics/params/helpers.go b/network/topics/params/helpers.go index cbbd47f502..e6efe59f65 100644 --- a/network/topics/params/helpers.go +++ b/network/topics/params/helpers.go @@ -7,10 +7,6 @@ import ( "github.com/pkg/errors" ) -const ( - oneEpochDuration = (12 * time.Second) * 32 -) - // scoreDecay determines the decay rate from the provided time period till // the decayToZero value. Ex: ( 1 -> 0.01) func scoreDecay(totalDecayDuration time.Duration, decayIntervalDuration time.Duration) float64 { diff --git a/network/topics/params/message_rate.go b/network/topics/params/message_rate.go index a64f4a07d8..92dfc8051c 100644 --- a/network/topics/params/message_rate.go +++ b/network/topics/params/message_rate.go @@ -2,49 +2,79 @@ package params import ( "math" + "time" + "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/registry/storage" ) -// Ethereum parameters +// Threshold and limit parameters const ( - EthereumValidators = 1000000.0 // TODO: get from network? - SyncCommitteeSize = 512.0 // TODO: get from network? - EstimatedAttestationCommitteeSize = EthereumValidators / 2048.0 - AggregatorProbability = 16.0 / EstimatedAttestationCommitteeSize - ProposalProbability = 1.0 / EthereumValidators - SyncCommitteeProbability = SyncCommitteeSize / EthereumValidators - SyncCommitteeAggProb = SyncCommitteeProbability * 16.0 / (SyncCommitteeSize / 4.0) - MaxValidatorsPerCommittee = 560.0 - SlotsPerEpoch = 32.0 // TODO: get from network? - MaxAttestationDutiesPerEpochForCommittee = SlotsPerEpoch - SingleSCDutiesLimit = 0 + // SingleSCDutiesLimit represents the limit of the number of committee duties in an epoch + // with only sync committee beacon duties (no attestation) taken for a very big number of validators. + // To help reasoning it, note that for a very big number of validators all slots in the epoch + // will have an attestation with high probability and, thus, + // the committee duties with only sync committee beacon duties tends to 0. + SingleSCDutiesLimit = 0 + // MaxValidatorsPerCommitteeListCut serves as a threshold size for creating a cache + // that computes the expected number of duties given a committee size. + // For each committee size, we can compute the precise expected number of duties. + // However, for big enough committees (considered as bigger than the following constant), + // results are pretty much the same. So we create a list of const values only up to the following value. + // For values that exceed it, the function shall return a default limit answer + // (e.g. number of committees duties per epoch -> 32). + // TODO: It depends on duties per epoch, 32 duties per epoch maps to MaxValidatorsPerCommitteeListCut=560. If the value of duties per epoch changes, this value needs to be adjusted (need to run Monte Carlo simulation for that number). + MaxValidatorsPerCommitteeListCut = 560 ) -// Expected number of messages per duty step - -func consensusMessages(n int) int { - return 1 + n + n + 2 // 1 Proposal + n Prepares + n Commits + 2 Decideds (average) +type rateCalculator struct { + netCfg networkconfig.NetworkConfig + generatedExpectedNumberOfCommitteeDutiesPerEpochDueToAttestation []float64 + generatedExpectedSingleSCCommitteeDutiesPerEpoch []float64 } -func partialSignatureMessages(n int) int { - return n -} +func newRateCalculator(netCfg networkconfig.NetworkConfig) *rateCalculator { + rc := &rateCalculator{ + netCfg: netCfg, + generatedExpectedNumberOfCommitteeDutiesPerEpochDueToAttestation: []float64{}, + generatedExpectedSingleSCCommitteeDutiesPerEpoch: []float64{}, + } -func dutyWithPreConsensus(n int) int { - // Pre-Consensus + Consensus + Post-Consensus - return partialSignatureMessages(n) + consensusMessages(n) + partialSignatureMessages(n) + rc.generateCachedValues() + + return rc } -func dutyWithoutPreConsensus(n int) int { - // Consensus + Post-Consensus - return consensusMessages(n) + partialSignatureMessages(n) +// Calculates the message rate for a topic given its committees' configurations (number of operators and number of validators) +func (rc *rateCalculator) calculateMessageRateForTopic(committees []*storage.Committee) float64 { + if len(committees) == 0 { + return 0 + } + + totalMsgRate := 0.0 + + for _, committee := range committees { + committeeSize := len(committee.Operators) + numValidators := len(committee.Validators) + + totalMsgRate += rc.expectedNumberOfCommitteeDutiesPerEpochDueToAttestationCached(numValidators) * float64(dutyWithoutPreConsensus(committeeSize)) + totalMsgRate += rc.expectedSingleSCCommitteeDutiesPerEpochCached(numValidators) * float64(dutyWithoutPreConsensus(committeeSize)) + totalMsgRate += float64(numValidators) * rc.AggregatorProbability() * float64(dutyWithPreConsensus(committeeSize)) + totalMsgRate += float64(numValidators) * float64(rc.netCfg.GetSlotsPerEpoch()) * rc.ProposalProbability() * float64(dutyWithPreConsensus(committeeSize)) + totalMsgRate += float64(numValidators) * float64(rc.netCfg.GetSlotsPerEpoch()) * rc.SyncCommitteeAggProb() * float64(dutyWithPreConsensus(committeeSize)) + } + + // Convert rate to seconds + totalEpochSeconds := float64(rc.netCfg.EpochDuration() / time.Second) + totalMsgRate = totalMsgRate / totalEpochSeconds + + return totalMsgRate } // Expected number of committee duties per epoch due to attestations -func expectedNumberOfCommitteeDutiesPerEpochDueToAttestation(numValidators int) float64 { +func (rc *rateCalculator) calcExpectedNumberOfCommitteeDutiesPerEpochDueToAttestation(numValidators int) float64 { k := float64(numValidators) - n := SlotsPerEpoch + n := float64(rc.netCfg.GetSlotsPerEpoch()) // Probability that all validators are not assigned to slot i probabilityAllNotOnSlotI := math.Pow((n-1)/n, k) @@ -59,77 +89,94 @@ func expectedNumberOfCommitteeDutiesPerEpochDueToAttestation(numValidators int) } // Expected committee duties per epoch that are due to only sync committee beacon duties -func expectedSingleSCCommitteeDutiesPerEpoch(numValidators int) float64 { +func (rc *rateCalculator) calcExpectedSingleSCCommitteeDutiesPerEpoch(numValidators int) float64 { // Probability that a validator is not in sync committee - chanceOfNotBeingInSyncCommittee := 1.0 - SyncCommitteeProbability + chanceOfNotBeingInSyncCommittee := 1.0 - rc.SyncCommitteeProbability() // Probability that all validators are not in sync committee chanceThatAllValidatorsAreNotInSyncCommittee := math.Pow(chanceOfNotBeingInSyncCommittee, float64(numValidators)) // Probability that at least one validator is in sync committee chanceOfAtLeastOneValidatorBeingInSyncCommittee := 1.0 - chanceThatAllValidatorsAreNotInSyncCommittee // Expected number of slots with no attestation duty - expectedSlotsWithNoDuty := 32.0 - expectedNumberOfCommitteeDutiesPerEpochDueToAttestationCached(numValidators) + expectedSlotsWithNoDuty := 32.0 - rc.calcExpectedNumberOfCommitteeDutiesPerEpochDueToAttestation(numValidators) // Expected number of committee duties per epoch created due to only sync committee duties return chanceOfAtLeastOneValidatorBeingInSyncCommittee * expectedSlotsWithNoDuty } -// Cache costly calculations +func (rc *rateCalculator) generateCachedValues() { + // Cache costly calculations -func generateCachedValues(generator func(int) float64, threshold int) []float64 { - results := make([]float64, 0, threshold) + expectedCommNumber := make([]float64, 0, MaxValidatorsPerCommitteeListCut) + expectedSingleSCC := make([]float64, 0, MaxValidatorsPerCommitteeListCut) - for i := 0; i < threshold; i++ { - results = append(results, generator(i)) + for i := 0; i < MaxValidatorsPerCommitteeListCut; i++ { + expectedCommNumber = append(expectedCommNumber, rc.calcExpectedNumberOfCommitteeDutiesPerEpochDueToAttestation(i)) + expectedSingleSCC = append(expectedSingleSCC, rc.calcExpectedSingleSCCommitteeDutiesPerEpoch(i)) } - return results + rc.generatedExpectedNumberOfCommitteeDutiesPerEpochDueToAttestation = expectedCommNumber + rc.generatedExpectedSingleSCCommitteeDutiesPerEpoch = expectedSingleSCC } -var generatedExpectedNumberOfCommitteeDutiesPerEpochDueToAttestation = generateCachedValues(expectedNumberOfCommitteeDutiesPerEpochDueToAttestation, MaxValidatorsPerCommittee) - -func expectedNumberOfCommitteeDutiesPerEpochDueToAttestationCached(numValidators int) float64 { +func (rc *rateCalculator) expectedNumberOfCommitteeDutiesPerEpochDueToAttestationCached(numValidators int) float64 { // If the committee has more validators than our computed cache, we return the limit value - if numValidators >= MaxValidatorsPerCommittee { - return MaxAttestationDutiesPerEpochForCommittee + if numValidators >= MaxValidatorsPerCommitteeListCut { + return float64(rc.MaxAttestationDutiesPerEpochForCommittee()) } - return generatedExpectedNumberOfCommitteeDutiesPerEpochDueToAttestation[numValidators] + return rc.generatedExpectedNumberOfCommitteeDutiesPerEpochDueToAttestation[numValidators] } -var generatedExpectedSingleSCCommitteeDutiesPerEpoch = generateCachedValues(expectedSingleSCCommitteeDutiesPerEpoch, MaxValidatorsPerCommittee) - -func expectedSingleSCCommitteeDutiesPerEpochCached(numValidators int) float64 { +func (rc *rateCalculator) expectedSingleSCCommitteeDutiesPerEpochCached(numValidators int) float64 { // If the committee has more validators than our computed cache, we return the limit value - if numValidators >= MaxValidatorsPerCommittee { + if numValidators >= MaxValidatorsPerCommitteeListCut { return SingleSCDutiesLimit } - return generatedExpectedSingleSCCommitteeDutiesPerEpoch[numValidators] + return rc.generatedExpectedSingleSCCommitteeDutiesPerEpoch[numValidators] } -// Calculates the message rate for a topic given its committees' configurations (number of operators and number of validators) -func calculateMessageRateForTopic(committees []*storage.Committee) float64 { - if len(committees) == 0 { - return 0 - } +func (rc *rateCalculator) AggregatorProbability() float64 { + return 16.0 / rc.EstimatedAttestationCommitteeSize() +} - totalMsgRate := 0.0 +func (rc *rateCalculator) ProposalProbability() float64 { + return 1.0 / float64(rc.netCfg.TotalEthereumValidators) +} - for _, committee := range committees { - committeeSize := len(committee.Operators) - numValidators := len(committee.Validators) +func (rc *rateCalculator) SyncCommitteeProbability() float64 { + return float64(rc.netCfg.GetSyncCommitteeSize()) / float64(rc.netCfg.TotalEthereumValidators) +} - totalMsgRate += expectedNumberOfCommitteeDutiesPerEpochDueToAttestationCached(numValidators) * float64(dutyWithoutPreConsensus(committeeSize)) - totalMsgRate += expectedSingleSCCommitteeDutiesPerEpochCached(numValidators) * float64(dutyWithoutPreConsensus(committeeSize)) - totalMsgRate += float64(numValidators) * AggregatorProbability * float64(dutyWithPreConsensus(committeeSize)) - totalMsgRate += float64(numValidators) * SlotsPerEpoch * ProposalProbability * float64(dutyWithPreConsensus(committeeSize)) - totalMsgRate += float64(numValidators) * SlotsPerEpoch * SyncCommitteeAggProb * float64(dutyWithPreConsensus(committeeSize)) - } +func (rc *rateCalculator) SyncCommitteeAggProb() float64 { + return rc.SyncCommitteeProbability() * 16.0 / (float64(rc.netCfg.GetSyncCommitteeSize()) / 4.0) +} - // Convert rate to seconds - totalEpochSeconds := float64(SlotsPerEpoch * 12) - totalMsgRate = totalMsgRate / totalEpochSeconds +func (rc *rateCalculator) MaxAttestationDutiesPerEpochForCommittee() uint64 { + return uint64(rc.netCfg.GetSlotsPerEpoch()) +} - return totalMsgRate +func (rc *rateCalculator) EstimatedAttestationCommitteeSize() float64 { + return float64(rc.netCfg.TotalEthereumValidators) / 2048.0 +} + +// Expected number of messages per duty step + +func consensusMessages(n int) int { + return 1 + n + n + 2 // 1 Proposal + n Prepares + n Commits + 2 Decideds (average) +} + +func partialSignatureMessages(n int) int { + return n +} + +func dutyWithPreConsensus(n int) int { + // Pre-Consensus + Consensus + Post-Consensus + return partialSignatureMessages(n) + consensusMessages(n) + partialSignatureMessages(n) +} + +func dutyWithoutPreConsensus(n int) int { + // Consensus + Post-Consensus + return consensusMessages(n) + partialSignatureMessages(n) } diff --git a/network/topics/params/message_rate_test.go b/network/topics/params/message_rate_test.go index 75db598f7a..03bb24ffbb 100644 --- a/network/topics/params/message_rate_test.go +++ b/network/topics/params/message_rate_test.go @@ -7,6 +7,7 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "github.com/stretchr/testify/require" + "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/protocol/v2/types" "github.com/ssvlabs/ssv/registry/storage" ) @@ -79,7 +80,8 @@ func TestCalculateMessageRateForTopic(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - msgRate := calculateMessageRateForTopic(tt.args.committees) + rc := newRateCalculator(networkconfig.TestNetwork) + msgRate := rc.calculateMessageRateForTopic(tt.args.committees) require.InDelta(t, tt.want, msgRate, tt.want*0.001) }) } diff --git a/network/topics/params/peer_score.go b/network/topics/params/peer_score.go index 323e53c6a7..c8e9d01921 100644 --- a/network/topics/params/peer_score.go +++ b/network/topics/params/peer_score.go @@ -6,6 +6,8 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/peer" + + "github.com/ssvlabs/ssv/networkconfig" ) const ( @@ -17,10 +19,9 @@ const ( opportunisticGraftThreshold = 5 // Overall parameters - topicScoreCap = 32.72 - decayInterval = 32 * (time.Second * 12) // One epoch - decayToZero = 0.01 - retainScore = 100 * 32 * 12 * time.Second + topicScoreCap = 32.72 + decayToZero = 0.01 + retainScoreEpochMultiplier = 100 // P5 appSpecificWeight = 0 @@ -45,13 +46,9 @@ func PeerScoreThresholds() *pubsub.PeerScoreThresholds { } // PeerScoreParams returns peer score params according to the given options -func PeerScoreParams(oneEpoch, msgIDCacheTTL time.Duration, disableColocation bool, ipWhilelist ...*net.IPNet) *pubsub.PeerScoreParams { - if oneEpoch == 0 { - oneEpoch = oneEpochDuration - } - +func PeerScoreParams(netCfg networkconfig.NetworkConfig, msgIDCacheTTL time.Duration, disableColocation bool, ipWhilelist ...*net.IPNet) *pubsub.PeerScoreParams { // P7 calculation - behaviourPenaltyDecay := scoreDecay(oneEpoch*10, decayInterval) + behaviourPenaltyDecay := scoreDecay(netCfg.EpochDuration()*10, netCfg.EpochDuration()) maxAllowedRatePerDecayInterval := 10.0 targetVal, _ := decayConvergence(behaviourPenaltyDecay, maxAllowedRatePerDecayInterval) targetVal = targetVal - behaviourPenaltyThreshold @@ -66,9 +63,9 @@ func PeerScoreParams(oneEpoch, msgIDCacheTTL time.Duration, disableColocation bo Topics: make(map[string]*pubsub.TopicScoreParams), // Overall parameters TopicScoreCap: topicScoreCap, - DecayInterval: decayInterval, + DecayInterval: netCfg.EpochDuration(), DecayToZero: decayToZero, - RetainScore: retainScore, + RetainScore: retainScoreEpochMultiplier * netCfg.EpochDuration(), SeenMsgTTL: msgIDCacheTTL, // P5 diff --git a/network/topics/params/scores_test.go b/network/topics/params/scores_test.go index acf9dc8f12..89f63bf808 100644 --- a/network/topics/params/scores_test.go +++ b/network/topics/params/scores_test.go @@ -9,6 +9,7 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/stretchr/testify/require" + "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/registry/storage" ) @@ -22,7 +23,7 @@ func TestTopicScoreParams(t *testing.T) { "subnet topic 0 validators", func() *Options { validators := uint64(0) - opts := NewSubnetTopicOpts(validators, 128, []*storage.Committee{}) + opts := NewSubnetTopicOpts(networkconfig.TestNetwork, validators, 128, []*storage.Committee{}) return opts }, nil, @@ -31,7 +32,7 @@ func TestTopicScoreParams(t *testing.T) { "subnet topic 1k validators", func() *Options { validators := uint64(1000) - opts := NewSubnetTopicOpts(validators, 128, createTestingSingleCommittees(validators)) + opts := NewSubnetTopicOpts(networkconfig.TestNetwork, validators, 128, createTestingSingleCommittees(validators)) return opts }, nil, @@ -40,7 +41,7 @@ func TestTopicScoreParams(t *testing.T) { "subnet topic 10k validators", func() *Options { validators := uint64(10_000) - opts := NewSubnetTopicOpts(validators, 128, createTestingSingleCommittees(validators)) + opts := NewSubnetTopicOpts(networkconfig.TestNetwork, validators, 128, createTestingSingleCommittees(validators)) return opts }, nil, @@ -49,7 +50,7 @@ func TestTopicScoreParams(t *testing.T) { "subnet topic 51k validators", func() *Options { validators := uint64(51_000) - opts := NewSubnetTopicOpts(validators, 128, createTestingSingleCommittees(validators)) + opts := NewSubnetTopicOpts(networkconfig.TestNetwork, validators, 128, createTestingSingleCommittees(validators)) return opts }, nil, @@ -77,7 +78,7 @@ func TestTopicScoreParams(t *testing.T) { } func TestPeerScoreParams(t *testing.T) { - peerScoreParams := PeerScoreParams(oneEpochDuration, 550*(time.Millisecond*700), false) + peerScoreParams := PeerScoreParams(networkconfig.TestNetwork, 550*(time.Millisecond*700), false) raw, err := peerScoreParamsString(peerScoreParams) require.NoError(t, err) require.NotNil(t, raw) diff --git a/network/topics/params/topic_score.go b/network/topics/params/topic_score.go index d8366ad288..fb9a8efad8 100644 --- a/network/topics/params/topic_score.go +++ b/network/topics/params/topic_score.go @@ -7,6 +7,7 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/pkg/errors" + "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/registry/storage" ) @@ -96,7 +97,7 @@ type Options struct { func (o *Options) defaults() { // Network if o.Network.OneEpochDuration == 0 { - o.Network.OneEpochDuration = oneEpochDuration + o.Network.OneEpochDuration = 12 * time.Second * 32 } if o.Network.TotalTopicsWeight == 0 { o.Network.TotalTopicsWeight = totalTopicsWeight @@ -150,35 +151,38 @@ func (o *Options) maxScore() float64 { } // NewOpts creates new TopicOpts instance -func NewOpts(activeValidators uint64, subnets int) *Options { +func NewOpts(epochDuration time.Duration, activeValidators uint64, subnets int) *Options { return &Options{ Network: NetworkOpts{ ActiveValidators: activeValidators, Subnets: subnets, + OneEpochDuration: epochDuration, }, Topic: TopicOpts{}, } } // NewSubnetTopicOpts creates new TopicOpts for a subnet topic -func NewSubnetTopicOpts(activeValidators uint64, subnets int, committees []*storage.Committee) *Options { +func NewSubnetTopicOpts(netCfg networkconfig.NetworkConfig, activeValidators uint64, subnets int, committees []*storage.Committee) *Options { // Create options with default values - opts := NewOpts(activeValidators, subnets) + opts := NewOpts(netCfg.EpochDuration(), activeValidators, subnets) opts.defaults() // Set topic weight with equal weights opts.Topic.TopicWeight = opts.Network.TotalTopicsWeight / float64(opts.Network.Subnets) + rc := newRateCalculator(netCfg) + // Set the expected message rate for the topic - opts.Topic.ExpectedMsgRate = calculateMessageRateForTopic(committees) + opts.Topic.ExpectedMsgRate = rc.calculateMessageRateForTopic(committees) return opts } // NewSubnetTopicOpts creates new TopicOpts for a subnet topic -func NewSubnetTopicOptsValidators(activeValidators uint64, subnets int) *Options { +func NewSubnetTopicOptsValidators(netCfg networkconfig.NetworkConfig, activeValidators uint64, subnets int) *Options { // Create options with default values - opts := NewOpts(activeValidators, subnets) + opts := NewOpts(netCfg.EpochDuration(), activeValidators, subnets) opts.defaults() // Set topic weight with equal weights @@ -201,6 +205,7 @@ func TopicParams(opts *Options) (*pubsub.TopicScoreParams, error) { // Set to default if not set opts.defaults() + decayInterval := opts.Network.OneEpochDuration expectedMessagesPerDecayInterval := opts.Topic.ExpectedMsgRate * decayInterval.Seconds() // P1 diff --git a/network/topics/pubsub.go b/network/topics/pubsub.go index 5154309918..9576c9f0bc 100644 --- a/network/topics/pubsub.go +++ b/network/topics/pubsub.go @@ -76,7 +76,6 @@ type PubSubConfig struct { type ScoringConfig struct { IPWhitelist []*net.IPNet IPColocationWeight float64 - OneEpochDuration time.Duration } // PubsubBundle includes the pubsub router, plus involved components @@ -167,7 +166,7 @@ func NewPubSub(ctx context.Context, logger *zap.Logger, cfg *PubSubConfig, commi } // Get overall score params - peerScoreParams := params.PeerScoreParams(cfg.Scoring.OneEpochDuration, cfg.MsgIDCacheTTL, cfg.DisableIPRateLimit, cfg.Scoring.IPWhitelist...) + peerScoreParams := params.PeerScoreParams(cfg.NetworkConfig, cfg.MsgIDCacheTTL, cfg.DisableIPRateLimit, cfg.Scoring.IPWhitelist...) // Define score inspector if inspector == nil { diff --git a/network/topics/scoring.go b/network/topics/scoring.go index 64e6a4cca8..272fd7fc27 100644 --- a/network/topics/scoring.go +++ b/network/topics/scoring.go @@ -5,7 +5,6 @@ import ( "math" "strconv" "strings" - "time" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/peer" @@ -22,7 +21,6 @@ import ( func DefaultScoringConfig() *ScoringConfig { return &ScoringConfig{ IPColocationWeight: -35.11, - OneEpochDuration: (12 * time.Second) * 32, } } @@ -207,7 +205,7 @@ func topicScoreParams(logger *zap.Logger, cfg *PubSubConfig, committeesProvider logger.Debug("got filtered committees for score params") // Create topic options - opts := params.NewSubnetTopicOpts(totalValidators, commons.SubnetsCount, topicCommittees) + opts := params.NewSubnetTopicOpts(cfg.NetworkConfig, totalValidators, commons.SubnetsCount, topicCommittees) // Generate topic parameters tp, err := params.TopicParams(opts) diff --git a/networkconfig/beacon.go b/networkconfig/beacon.go index ec0dec7eb9..2473095c15 100644 --- a/networkconfig/beacon.go +++ b/networkconfig/beacon.go @@ -19,25 +19,35 @@ type Beacon interface { EstimatedEpochAtSlot(slot phase0.Slot) phase0.Epoch IsFirstSlotOfEpoch(slot phase0.Slot) bool GetEpochFirstSlot(epoch phase0.Epoch) phase0.Slot - EpochsPerSyncCommitteePeriod() phase0.Epoch + GetEpochsPerSyncCommitteePeriod() phase0.Epoch EstimatedSyncCommitteePeriodAtEpoch(epoch phase0.Epoch) uint64 FirstEpochOfSyncPeriod(period uint64) phase0.Epoch LastSlotOfSyncPeriod(period uint64) phase0.Slot FirstSlotAtEpoch(epoch phase0.Epoch) phase0.Slot EpochStartTime(epoch phase0.Epoch) time.Time EstimatedTimeAtSlot(slot phase0.Slot) time.Time + IntervalDuration() time.Duration + EpochDuration() time.Duration GetSlotDuration() time.Duration GetSlotsPerEpoch() phase0.Slot GetGenesisTime() time.Time + GetSyncCommitteeSize() uint64 GetBeaconName() string } type BeaconConfig struct { - BeaconName string - SlotDuration time.Duration - SlotsPerEpoch phase0.Slot - ForkVersion phase0.Version - GenesisTime time.Time + BeaconName string + SlotDuration time.Duration + SlotsPerEpoch phase0.Slot + EpochsPerSyncCommitteePeriod phase0.Epoch + SyncCommitteeSize uint64 + SyncCommitteeSubnetCount uint64 + TargetAggregatorsPerSyncSubcommittee uint64 + TargetAggregatorsPerCommittee uint64 + IntervalsPerSlot uint64 + ForkVersion phase0.Version + GenesisTime time.Time + GenesisValidatorsRoot phase0.Root } func (b BeaconConfig) String() string { @@ -100,19 +110,19 @@ func (b BeaconConfig) GetEpochFirstSlot(epoch phase0.Epoch) phase0.Slot { return phase0.Slot(epoch) * b.SlotsPerEpoch } -// EpochsPerSyncCommitteePeriod returns the number of epochs per sync committee period. -func (b BeaconConfig) EpochsPerSyncCommitteePeriod() phase0.Epoch { - return 256 +// GetEpochsPerSyncCommitteePeriod returns the number of epochs per sync committee period. +func (b BeaconConfig) GetEpochsPerSyncCommitteePeriod() phase0.Epoch { + return b.EpochsPerSyncCommitteePeriod } // EstimatedSyncCommitteePeriodAtEpoch estimates the current sync committee period at the given Epoch func (b BeaconConfig) EstimatedSyncCommitteePeriodAtEpoch(epoch phase0.Epoch) uint64 { - return uint64(epoch / b.EpochsPerSyncCommitteePeriod()) + return uint64(epoch / b.GetEpochsPerSyncCommitteePeriod()) } // FirstEpochOfSyncPeriod calculates the first epoch of the given sync period. func (b BeaconConfig) FirstEpochOfSyncPeriod(period uint64) phase0.Epoch { - return phase0.Epoch(period) * b.EpochsPerSyncCommitteePeriod() + return phase0.Epoch(period) * b.GetEpochsPerSyncCommitteePeriod() } // LastSlotOfSyncPeriod calculates the first epoch of the given sync period. @@ -141,6 +151,20 @@ func (b BeaconConfig) EstimatedTimeAtSlot(slot phase0.Slot) time.Time { return b.GenesisTime.Add(d) } +func (b BeaconConfig) IntervalDuration() time.Duration { + if b.IntervalsPerSlot > math.MaxInt64 { + panic("intervals per slot out of range") + } + return b.SlotDuration / time.Duration(b.IntervalsPerSlot) // #nosec G115: intervals per slot cannot exceed math.MaxInt64 +} + +func (b BeaconConfig) EpochDuration() time.Duration { + if b.SlotsPerEpoch > math.MaxInt64 { + panic("slot out of range") + } + return b.SlotDuration * time.Duration(b.SlotsPerEpoch) // #nosec G115: slot cannot exceed math.MaxInt64 +} + func (b BeaconConfig) GetSlotDuration() time.Duration { return b.SlotDuration } @@ -153,6 +177,10 @@ func (b BeaconConfig) GetGenesisTime() time.Time { return b.GenesisTime } +func (b BeaconConfig) GetSyncCommitteeSize() uint64 { + return b.SyncCommitteeSize +} + func (b BeaconConfig) GetBeaconName() string { return b.BeaconName } diff --git a/networkconfig/beacon_mock.go b/networkconfig/beacon_mock.go index c89a5e8a35..d258d34698 100644 --- a/networkconfig/beacon_mock.go +++ b/networkconfig/beacon_mock.go @@ -41,32 +41,32 @@ func (m *MockBeacon) EXPECT() *MockBeaconMockRecorder { return m.recorder } -// EpochStartTime mocks base method. -func (m *MockBeacon) EpochStartTime(epoch phase0.Epoch) time.Time { +// EpochDuration mocks base method. +func (m *MockBeacon) EpochDuration() time.Duration { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EpochStartTime", epoch) - ret0, _ := ret[0].(time.Time) + ret := m.ctrl.Call(m, "EpochDuration") + ret0, _ := ret[0].(time.Duration) return ret0 } -// EpochStartTime indicates an expected call of EpochStartTime. -func (mr *MockBeaconMockRecorder) EpochStartTime(epoch any) *gomock.Call { +// EpochDuration indicates an expected call of EpochDuration. +func (mr *MockBeaconMockRecorder) EpochDuration() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EpochStartTime", reflect.TypeOf((*MockBeacon)(nil).EpochStartTime), epoch) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EpochDuration", reflect.TypeOf((*MockBeacon)(nil).EpochDuration)) } -// EpochsPerSyncCommitteePeriod mocks base method. -func (m *MockBeacon) EpochsPerSyncCommitteePeriod() phase0.Epoch { +// EpochStartTime mocks base method. +func (m *MockBeacon) EpochStartTime(epoch phase0.Epoch) time.Time { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EpochsPerSyncCommitteePeriod") - ret0, _ := ret[0].(phase0.Epoch) + ret := m.ctrl.Call(m, "EpochStartTime", epoch) + ret0, _ := ret[0].(time.Time) return ret0 } -// EpochsPerSyncCommitteePeriod indicates an expected call of EpochsPerSyncCommitteePeriod. -func (mr *MockBeaconMockRecorder) EpochsPerSyncCommitteePeriod() *gomock.Call { +// EpochStartTime indicates an expected call of EpochStartTime. +func (mr *MockBeaconMockRecorder) EpochStartTime(epoch any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EpochsPerSyncCommitteePeriod", reflect.TypeOf((*MockBeacon)(nil).EpochsPerSyncCommitteePeriod)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EpochStartTime", reflect.TypeOf((*MockBeacon)(nil).EpochStartTime), epoch) } // EstimatedCurrentEpoch mocks base method. @@ -209,6 +209,20 @@ func (mr *MockBeaconMockRecorder) GetEpochFirstSlot(epoch any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpochFirstSlot", reflect.TypeOf((*MockBeacon)(nil).GetEpochFirstSlot), epoch) } +// GetEpochsPerSyncCommitteePeriod mocks base method. +func (m *MockBeacon) GetEpochsPerSyncCommitteePeriod() phase0.Epoch { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEpochsPerSyncCommitteePeriod") + ret0, _ := ret[0].(phase0.Epoch) + return ret0 +} + +// GetEpochsPerSyncCommitteePeriod indicates an expected call of GetEpochsPerSyncCommitteePeriod. +func (mr *MockBeaconMockRecorder) GetEpochsPerSyncCommitteePeriod() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpochsPerSyncCommitteePeriod", reflect.TypeOf((*MockBeacon)(nil).GetEpochsPerSyncCommitteePeriod)) +} + // GetGenesisTime mocks base method. func (m *MockBeacon) GetGenesisTime() time.Time { m.ctrl.T.Helper() @@ -279,6 +293,34 @@ func (mr *MockBeaconMockRecorder) GetSlotsPerEpoch() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSlotsPerEpoch", reflect.TypeOf((*MockBeacon)(nil).GetSlotsPerEpoch)) } +// GetSyncCommitteeSize mocks base method. +func (m *MockBeacon) GetSyncCommitteeSize() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSyncCommitteeSize") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetSyncCommitteeSize indicates an expected call of GetSyncCommitteeSize. +func (mr *MockBeaconMockRecorder) GetSyncCommitteeSize() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSyncCommitteeSize", reflect.TypeOf((*MockBeacon)(nil).GetSyncCommitteeSize)) +} + +// IntervalDuration mocks base method. +func (m *MockBeacon) IntervalDuration() time.Duration { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IntervalDuration") + ret0, _ := ret[0].(time.Duration) + return ret0 +} + +// IntervalDuration indicates an expected call of IntervalDuration. +func (mr *MockBeaconMockRecorder) IntervalDuration() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntervalDuration", reflect.TypeOf((*MockBeacon)(nil).IntervalDuration)) +} + // IsFirstSlotOfEpoch mocks base method. func (m *MockBeacon) IsFirstSlotOfEpoch(slot phase0.Slot) bool { m.ctrl.T.Helper() diff --git a/networkconfig/holesky-e2e.go b/networkconfig/holesky-e2e.go index 36de1f300f..a5917d4193 100644 --- a/networkconfig/holesky-e2e.go +++ b/networkconfig/holesky-e2e.go @@ -19,9 +19,10 @@ var HoleskyE2E = NetworkConfig{ GenesisTime: time.Unix(int64(spectypes.HoleskyNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 }, SSVConfig: SSVConfig{ - DomainType: spectypes.DomainType{0x0, 0x0, 0xee, 0x1}, - RegistryContractAddr: ethcommon.HexToAddress("0x58410bef803ecd7e63b23664c586a6db72daf59c"), - RegistrySyncOffset: big.NewInt(405579), - Bootnodes: []string{}, + DomainType: spectypes.DomainType{0x0, 0x0, 0xee, 0x1}, + RegistryContractAddr: ethcommon.HexToAddress("0x58410bef803ecd7e63b23664c586a6db72daf59c"), + RegistrySyncOffset: big.NewInt(405579), + Bootnodes: []string{}, + TotalEthereumValidators: Holesky.TotalEthereumValidators, }, } diff --git a/networkconfig/holesky-stage.go b/networkconfig/holesky-stage.go index a9373a43c2..7b5f2b7430 100644 --- a/networkconfig/holesky-stage.go +++ b/networkconfig/holesky-stage.go @@ -30,5 +30,6 @@ var HoleskyStage = NetworkConfig{ // Private bootnode: "enr:-Ja4QDRUBjWOvVfGxpxvv3FqaCy3psm7IsKu5ETb1GXiexGYDFppD33t7AHRfmQddoAkBiyb7pt4t7ZN0sNB9CsW4I-GAZGOmChMgmlkgnY0gmlwhAorXxuJc2VjcDI1NmsxoQP_bBE-ZYvaXKBR3dRYMN5K_lZP-q-YsBzDZEtxH_4T_YNzc3YBg3RjcIITioN1ZHCCD6I", }, + TotalEthereumValidators: Holesky.TotalEthereumValidators, }, } diff --git a/networkconfig/holesky.go b/networkconfig/holesky.go index 6d409cdc93..00d2334241 100644 --- a/networkconfig/holesky.go +++ b/networkconfig/holesky.go @@ -27,5 +27,6 @@ var Holesky = NetworkConfig{ // SSV Labs "enr:-Ja4QKFD3u5tZob7xukp-JKX9QJMFqqI68cItsE4tBbhsOyDR0M_1UUjb35hbrqvTP3bnXO_LnKh-jNLTeaUqN4xiduGAZKaP_sagmlkgnY0gmlwhDb0fh6Jc2VjcDI1NmsxoQMw_H2anuiqP9NmEaZwbUfdvPFog7PvcKmoVByDa576SINzc3YBg3RjcIITioN1ZHCCD6I", }, + TotalEthereumValidators: 1757795, // active_validators from https://holesky.beaconcha.in/index/data on Nov 20, 2024 }, } diff --git a/networkconfig/hoodi-stage.go b/networkconfig/hoodi-stage.go index 24d1c8a38b..61520d3e6f 100644 --- a/networkconfig/hoodi-stage.go +++ b/networkconfig/hoodi-stage.go @@ -27,5 +27,6 @@ var HoodiStage = NetworkConfig{ // SSV Labs "enr:-Ja4QJZcaYfS0GpX-5xREVBa26a-E-QHMFek-EndsJdgM6loIM7pfbJwPDCNK1VzPkUhMjwcTTuNASiHU6X-sjsrxFmGAZWjNu06gmlkgnY0gmlwhErcGnyJc2VjcDI1NmsxoQP_bBE-ZYvaXKBR3dRYMN5K_lZP-q-YsBzDZEtxH_4T_YNzc3YBg3RjcIITioN1ZHCCD6I", }, + TotalEthereumValidators: Hoodi.TotalEthereumValidators, }, } diff --git a/networkconfig/hoodi.go b/networkconfig/hoodi.go index b8e648f134..a88699d729 100644 --- a/networkconfig/hoodi.go +++ b/networkconfig/hoodi.go @@ -27,5 +27,6 @@ var Hoodi = NetworkConfig{ // SSV Labs "enr:-Ja4QIKlyNFuFtTOnVoavqwmpgSJXfhSmhpdSDOUhf5-FBr7bBxQRvG6VrpUvlkr8MtpNNuMAkM33AseduSaOhd9IeWGAZWjRbnvgmlkgnY0gmlwhCNVVTCJc2VjcDI1NmsxoQNTTyiJPoZh502xOZpHSHAfR-94NaXLvi5J4CNHMh2tjoNzc3YBg3RjcIITioN1ZHCCD6I", }, + TotalEthereumValidators: 1107955, // active_validators from https://hoodi.beaconcha.in/index/data on Apr 18, 2025 }, } diff --git a/networkconfig/local-testnet.go b/networkconfig/local-testnet.go index a4f97cda7e..0d7f284e02 100644 --- a/networkconfig/local-testnet.go +++ b/networkconfig/local-testnet.go @@ -23,5 +23,6 @@ var LocalTestnet = NetworkConfig{ Bootnodes: []string{ "enr:-Li4QLR4Y1VbwiqFYKy6m-WFHRNDjhMDZ_qJwIABu2PY9BHjIYwCKpTvvkVmZhu43Q6zVA29sEUhtz10rQjDJkK3Hd-GAYiGrW2Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQJTcI7GHPw-ZqIflPZYYDK_guurp_gsAFF5Erns3-PAvIN0Y3CCE4mDdWRwgg-h", }, + TotalEthereumValidators: TestNetwork.TotalEthereumValidators, }, } diff --git a/networkconfig/mainnet.go b/networkconfig/mainnet.go index 36d95cb43b..075663a645 100644 --- a/networkconfig/mainnet.go +++ b/networkconfig/mainnet.go @@ -36,5 +36,6 @@ var Mainnet = NetworkConfig{ // CryptoManufaktur "enr:-Li4QH7FwJcL8gJj0zHAITXqghMkG-A5bfWh2-3Q7vosy9D1BS8HZk-1ITuhK_rfzG3v_UtBDI6uNJZWpdcWfrQFCxKGAYnQ1DRCh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLb3g2Jc2VjcDI1NmsxoQKeSDcZWSaY9FC723E9yYX1Li18bswhLNlxBZdLfgOKp4N0Y3CCE4mDdWRwgg-h", }, + TotalEthereumValidators: 1064860, // active_validators from https://mainnet.beaconcha.in/index/data on Apr 18, 2025 }, } diff --git a/networkconfig/network_mock.go b/networkconfig/network_mock.go index a3e3c24103..4ac18f6592 100644 --- a/networkconfig/network_mock.go +++ b/networkconfig/network_mock.go @@ -42,32 +42,32 @@ func (m *MockNetwork) EXPECT() *MockNetworkMockRecorder { return m.recorder } -// EpochStartTime mocks base method. -func (m *MockNetwork) EpochStartTime(epoch phase0.Epoch) time.Time { +// EpochDuration mocks base method. +func (m *MockNetwork) EpochDuration() time.Duration { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EpochStartTime", epoch) - ret0, _ := ret[0].(time.Time) + ret := m.ctrl.Call(m, "EpochDuration") + ret0, _ := ret[0].(time.Duration) return ret0 } -// EpochStartTime indicates an expected call of EpochStartTime. -func (mr *MockNetworkMockRecorder) EpochStartTime(epoch any) *gomock.Call { +// EpochDuration indicates an expected call of EpochDuration. +func (mr *MockNetworkMockRecorder) EpochDuration() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EpochStartTime", reflect.TypeOf((*MockNetwork)(nil).EpochStartTime), epoch) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EpochDuration", reflect.TypeOf((*MockNetwork)(nil).EpochDuration)) } -// EpochsPerSyncCommitteePeriod mocks base method. -func (m *MockNetwork) EpochsPerSyncCommitteePeriod() phase0.Epoch { +// EpochStartTime mocks base method. +func (m *MockNetwork) EpochStartTime(epoch phase0.Epoch) time.Time { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EpochsPerSyncCommitteePeriod") - ret0, _ := ret[0].(phase0.Epoch) + ret := m.ctrl.Call(m, "EpochStartTime", epoch) + ret0, _ := ret[0].(time.Time) return ret0 } -// EpochsPerSyncCommitteePeriod indicates an expected call of EpochsPerSyncCommitteePeriod. -func (mr *MockNetworkMockRecorder) EpochsPerSyncCommitteePeriod() *gomock.Call { +// EpochStartTime indicates an expected call of EpochStartTime. +func (mr *MockNetworkMockRecorder) EpochStartTime(epoch any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EpochsPerSyncCommitteePeriod", reflect.TypeOf((*MockNetwork)(nil).EpochsPerSyncCommitteePeriod)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EpochStartTime", reflect.TypeOf((*MockNetwork)(nil).EpochStartTime), epoch) } // EstimatedCurrentEpoch mocks base method. @@ -224,6 +224,20 @@ func (mr *MockNetworkMockRecorder) GetEpochFirstSlot(epoch any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpochFirstSlot", reflect.TypeOf((*MockNetwork)(nil).GetEpochFirstSlot), epoch) } +// GetEpochsPerSyncCommitteePeriod mocks base method. +func (m *MockNetwork) GetEpochsPerSyncCommitteePeriod() phase0.Epoch { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEpochsPerSyncCommitteePeriod") + ret0, _ := ret[0].(phase0.Epoch) + return ret0 +} + +// GetEpochsPerSyncCommitteePeriod indicates an expected call of GetEpochsPerSyncCommitteePeriod. +func (mr *MockNetworkMockRecorder) GetEpochsPerSyncCommitteePeriod() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpochsPerSyncCommitteePeriod", reflect.TypeOf((*MockNetwork)(nil).GetEpochsPerSyncCommitteePeriod)) +} + // GetGenesisTime mocks base method. func (m *MockNetwork) GetGenesisTime() time.Time { m.ctrl.T.Helper() @@ -294,6 +308,34 @@ func (mr *MockNetworkMockRecorder) GetSlotsPerEpoch() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSlotsPerEpoch", reflect.TypeOf((*MockNetwork)(nil).GetSlotsPerEpoch)) } +// GetSyncCommitteeSize mocks base method. +func (m *MockNetwork) GetSyncCommitteeSize() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSyncCommitteeSize") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetSyncCommitteeSize indicates an expected call of GetSyncCommitteeSize. +func (mr *MockNetworkMockRecorder) GetSyncCommitteeSize() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSyncCommitteeSize", reflect.TypeOf((*MockNetwork)(nil).GetSyncCommitteeSize)) +} + +// IntervalDuration mocks base method. +func (m *MockNetwork) IntervalDuration() time.Duration { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IntervalDuration") + ret0, _ := ret[0].(time.Duration) + return ret0 +} + +// IntervalDuration indicates an expected call of IntervalDuration. +func (mr *MockNetworkMockRecorder) IntervalDuration() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntervalDuration", reflect.TypeOf((*MockNetwork)(nil).IntervalDuration)) +} + // IsFirstSlotOfEpoch mocks base method. func (m *MockNetwork) IsFirstSlotOfEpoch(slot phase0.Slot) bool { m.ctrl.T.Helper() diff --git a/networkconfig/sepolia.go b/networkconfig/sepolia.go index 5ae09bf6db..a102d84ea5 100644 --- a/networkconfig/sepolia.go +++ b/networkconfig/sepolia.go @@ -27,5 +27,6 @@ var Sepolia = NetworkConfig{ // SSV Labs "enr:-Ja4QIE0Ml0a8Pq9zD-0g9KYGN3jAMPJ0CAP0i16fK-PSHfLeORl-Z5p8odoP1oS5S2E8IsF5jNG7gqTKhjVsHR-Z_CGAZXrnTJrgmlkgnY0gmlwhCOjXGWJc2VjcDI1NmsxoQKCRDQsIdFsJDmu_ZU2H6b2_HRJbuUneDXHLfFkSQH9O4Nzc3YBg3RjcIITioN1ZHCCD6I", }, + TotalEthereumValidators: 1781, // active_validators from https://sepolia.beaconcha.in/index/data on Mar 20, 2025 }, } diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index d2c77ea219..48e943a51d 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -37,11 +37,12 @@ type SSV interface { } type SSVConfig struct { - DomainType spectypes.DomainType - RegistrySyncOffset *big.Int - RegistryContractAddr ethcommon.Address - Bootnodes []string - DiscoveryProtocolID [6]byte + DomainType spectypes.DomainType + RegistrySyncOffset *big.Int + RegistryContractAddr ethcommon.Address + Bootnodes []string + DiscoveryProtocolID [6]byte + TotalEthereumValidators int // value needs to be maintained — consider getting it from external API with default or per-network value(s) as fallback } func (s SSVConfig) String() string { @@ -54,20 +55,22 @@ func (s SSVConfig) String() string { } type marshaledConfig struct { - DomainType string `json:"DomainType,omitempty" yaml:"DomainType,omitempty"` - RegistrySyncOffset *big.Int `json:"RegistrySyncOffset,omitempty" yaml:"RegistrySyncOffset,omitempty"` - RegistryContractAddr string `json:"RegistryContractAddr,omitempty" yaml:"RegistryContractAddr,omitempty"` - Bootnodes []string `json:"Bootnodes,omitempty" yaml:"Bootnodes,omitempty"` - DiscoveryProtocolID string `json:"DiscoveryProtocolID,omitempty" yaml:"DiscoveryProtocolID,omitempty"` + DomainType string `json:"DomainType,omitempty" yaml:"DomainType,omitempty"` + RegistrySyncOffset *big.Int `json:"RegistrySyncOffset,omitempty" yaml:"RegistrySyncOffset,omitempty"` + RegistryContractAddr string `json:"RegistryContractAddr,omitempty" yaml:"RegistryContractAddr,omitempty"` + Bootnodes []string `json:"Bootnodes,omitempty" yaml:"Bootnodes,omitempty"` + DiscoveryProtocolID string `json:"DiscoveryProtocolID,omitempty" yaml:"DiscoveryProtocolID,omitempty"` + TotalEthereumValidators int `json:"TotalEthereumValidators,omitempty" yaml:"TotalEthereumValidators,omitempty"` } func (s *SSVConfig) marshal() (marshaledConfig, error) { aux := marshaledConfig{ - DomainType: "0x" + hex.EncodeToString(s.DomainType[:]), - RegistrySyncOffset: s.RegistrySyncOffset, - RegistryContractAddr: s.RegistryContractAddr.String(), - Bootnodes: s.Bootnodes, - DiscoveryProtocolID: "0x" + hex.EncodeToString(s.DiscoveryProtocolID[:]), + DomainType: "0x" + hex.EncodeToString(s.DomainType[:]), + RegistrySyncOffset: s.RegistrySyncOffset, + RegistryContractAddr: s.RegistryContractAddr.String(), + Bootnodes: s.Bootnodes, + DiscoveryProtocolID: "0x" + hex.EncodeToString(s.DiscoveryProtocolID[:]), + TotalEthereumValidators: s.TotalEthereumValidators, } return aux, nil @@ -93,11 +96,12 @@ func (s SSVConfig) MarshalYAML() (interface{}, error) { func (s *SSVConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { aux := &struct { - DomainType string `yaml:"DomainType,omitempty"` - RegistrySyncOffset *big.Int `yaml:"RegistrySyncOffset,omitempty"` - RegistryContractAddr string `yaml:"RegistryContractAddr,omitempty"` - Bootnodes []string `yaml:"Bootnodes,omitempty"` - DiscoveryProtocolID string `yaml:"DiscoveryProtocolID,omitempty"` + DomainType string `yaml:"DomainType,omitempty"` + RegistrySyncOffset *big.Int `yaml:"RegistrySyncOffset,omitempty"` + RegistryContractAddr string `yaml:"RegistryContractAddr,omitempty"` + Bootnodes []string `yaml:"Bootnodes,omitempty"` + DiscoveryProtocolID string `yaml:"DiscoveryProtocolID,omitempty"` + TotalEthereumValidators int `yaml:"TotalEthereumValidators,omitempty"` }{} if err := unmarshal(aux); err != nil { @@ -125,11 +129,12 @@ func (s *SSVConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { } *s = SSVConfig{ - DomainType: domainArr, - RegistrySyncOffset: aux.RegistrySyncOffset, - RegistryContractAddr: ethcommon.HexToAddress(aux.RegistryContractAddr), - Bootnodes: aux.Bootnodes, - DiscoveryProtocolID: discoveryProtocolIDArr, + DomainType: domainArr, + RegistrySyncOffset: aux.RegistrySyncOffset, + RegistryContractAddr: ethcommon.HexToAddress(aux.RegistryContractAddr), + Bootnodes: aux.Bootnodes, + DiscoveryProtocolID: discoveryProtocolIDArr, + TotalEthereumValidators: aux.TotalEthereumValidators, } return nil diff --git a/networkconfig/test-network.go b/networkconfig/test-network.go index 8435f0c459..8c3e860da1 100644 --- a/networkconfig/test-network.go +++ b/networkconfig/test-network.go @@ -25,5 +25,6 @@ var TestNetwork = NetworkConfig{ Bootnodes: []string{ "enr:-Li4QFIQzamdvTxGJhvcXG_DFmCeyggSffDnllY5DiU47pd_K_1MRnSaJimWtfKJ-MD46jUX9TwgW5Jqe0t4pH41RYWGAYuFnlyth2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQN4v-N9zFYwEqzGPBBX37q24QPFvAVUtokIo1fblIsmTIN0Y3CCE4uDdWRwgg-j", }, + TotalEthereumValidators: 10, // just some random number }, } diff --git a/operator/duties/committee_test.go b/operator/duties/committee_test.go index eca85e4787..a22b456575 100644 --- a/operator/duties/committee_test.go +++ b/operator/duties/committee_test.go @@ -30,13 +30,13 @@ func setupCommitteeDutiesMock( s.beaconConfig.(*networkconfig.MockBeacon).EXPECT().EstimatedSyncCommitteePeriodAtEpoch(gomock.Any()).DoAndReturn( func(epoch phase0.Epoch) uint64 { - return uint64(epoch / s.beaconConfig.EpochsPerSyncCommitteePeriod()) + return uint64(epoch / s.beaconConfig.GetEpochsPerSyncCommitteePeriod()) }, ).AnyTimes() s.beaconConfig.(*networkconfig.MockBeacon).EXPECT().FirstEpochOfSyncPeriod(gomock.Any()).DoAndReturn( func(period uint64) phase0.Epoch { - return phase0.Epoch(period) * s.beaconConfig.EpochsPerSyncCommitteePeriod() + return phase0.Epoch(period) * s.beaconConfig.GetEpochsPerSyncCommitteePeriod() }, ).AnyTimes() diff --git a/operator/duties/scheduler.go b/operator/duties/scheduler.go index 0a0fb0f50a..6a7657698c 100644 --- a/operator/duties/scheduler.go +++ b/operator/duties/scheduler.go @@ -16,7 +16,6 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" - "github.com/ssvlabs/ssv/beacon/goclient" "github.com/ssvlabs/ssv/logging" "github.com/ssvlabs/ssv/logging/fields" "github.com/ssvlabs/ssv/network" @@ -24,7 +23,6 @@ import ( "github.com/ssvlabs/ssv/operator/duties/dutystore" "github.com/ssvlabs/ssv/operator/slotticker" "github.com/ssvlabs/ssv/protocol/v2/types" - "github.com/ssvlabs/ssv/utils/casts" ) //go:generate go tool -modfile=../../tool.mod mockgen -package=duties -destination=./scheduler_mock.go -source=./scheduler.go @@ -287,7 +285,7 @@ func (s *Scheduler) SlotTicker(ctx context.Context) { case <-s.ticker.Next(): slot := s.ticker.Slot() - delay := s.beaconConfig.GetSlotDuration() / casts.DurationFromUint64(goclient.IntervalsPerSlot) /* a third of the slot duration */ + delay := s.beaconConfig.IntervalDuration() /* a third of the slot duration */ finalTime := s.beaconConfig.GetSlotStartTime(slot).Add(delay) waitDuration := time.Until(finalTime) @@ -366,7 +364,7 @@ func (s *Scheduler) HandleHeadEvent(logger *zap.Logger) func(event *eth2apiv1.He s.currentDutyDependentRoot = event.CurrentDutyDependentRoot currentTime := time.Now() - delay := s.beaconConfig.GetSlotDuration() / casts.DurationFromUint64(goclient.IntervalsPerSlot) /* a third of the slot duration */ + delay := s.beaconConfig.IntervalDuration() /* a third of the slot duration */ slotStartTimeWithDelay := s.beaconConfig.GetSlotStartTime(event.Slot).Add(delay) if currentTime.Before(slotStartTimeWithDelay) { logger.Debug("🏁 Head event: Block arrived before 1/3 slot", zap.Duration("time_saved", slotStartTimeWithDelay.Sub(currentTime))) diff --git a/operator/duties/scheduler_test.go b/operator/duties/scheduler_test.go index c7f61affca..15917ce95c 100644 --- a/operator/duties/scheduler_test.go +++ b/operator/duties/scheduler_test.go @@ -134,7 +134,7 @@ func setupSchedulerAndMocks(t *testing.T, handlers []dutyHandler, currentSlot *S }, ).AnyTimes() - s.beaconConfig.(*networkconfig.MockBeacon).EXPECT().EpochsPerSyncCommitteePeriod().Return(phase0.Epoch(256)).AnyTimes() + s.beaconConfig.(*networkconfig.MockBeacon).EXPECT().GetEpochsPerSyncCommitteePeriod().Return(phase0.Epoch(256)).AnyTimes() // Create a pool to wait for the scheduler to finish. schedulerPool := pool.New().WithErrors().WithContext(ctx) diff --git a/operator/duties/sync_committee.go b/operator/duties/sync_committee.go index 052175b80a..b05c2bf04d 100644 --- a/operator/duties/sync_committee.go +++ b/operator/duties/sync_committee.go @@ -317,5 +317,5 @@ func (h *SyncCommitteeHandler) shouldFetchNextPeriod(slot phase0.Slot) bool { } func (h *SyncCommitteeHandler) slotsPerPeriod() phase0.Slot { - return phase0.Slot(h.beaconConfig.EpochsPerSyncCommitteePeriod()) * h.beaconConfig.GetSlotsPerEpoch() + return phase0.Slot(h.beaconConfig.GetEpochsPerSyncCommitteePeriod()) * h.beaconConfig.GetSlotsPerEpoch() } diff --git a/operator/duties/sync_committee_test.go b/operator/duties/sync_committee_test.go index 5441bda844..f75b088e4f 100644 --- a/operator/duties/sync_committee_test.go +++ b/operator/duties/sync_committee_test.go @@ -29,13 +29,13 @@ func setupSyncCommitteeDutiesMock( s.beaconConfig.(*networkconfig.MockBeacon).EXPECT().EstimatedSyncCommitteePeriodAtEpoch(gomock.Any()).DoAndReturn( func(epoch phase0.Epoch) uint64 { - return uint64(epoch / s.beaconConfig.EpochsPerSyncCommitteePeriod()) + return uint64(epoch / s.beaconConfig.GetEpochsPerSyncCommitteePeriod()) }, ).AnyTimes() s.beaconConfig.(*networkconfig.MockBeacon).EXPECT().FirstEpochOfSyncPeriod(gomock.Any()).DoAndReturn( func(period uint64) phase0.Epoch { - return phase0.Epoch(period) * s.beaconConfig.EpochsPerSyncCommitteePeriod() + return phase0.Epoch(period) * s.beaconConfig.GetEpochsPerSyncCommitteePeriod() }, ).AnyTimes() diff --git a/operator/validator/controller.go b/operator/validator/controller.go index a6289f4598..ceb873065c 100644 --- a/operator/validator/controller.go +++ b/operator/validator/controller.go @@ -229,7 +229,7 @@ func NewController(logger *zap.Logger, options ControllerOptions) Controller { } } - cacheTTL := options.NetworkConfig.GetSlotDuration() * time.Duration(options.NetworkConfig.GetSlotsPerEpoch()*2) // #nosec G115 + cacheTTL := 2 * options.NetworkConfig.EpochDuration() // #nosec G115 ctrl := controller{ logger: logger.Named(logging.NameController), From e8d1fa2d6975f2cfa56a59596b343d27bb82c7d0 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 23 Apr 2025 21:56:08 -0300 Subject: [PATCH 19/38] fix issues after merging --- beacon/goclient/current_fork_test.go | 3 -- beacon/goclient/genesis_test.go | 43 +++++++++++++----- beacon/goclient/goclient.go | 2 +- beacon/goclient/signing.go | 1 + beacon/goclient/signing_test.go | 1 - beacon/goclient/spec.go | 65 ++++++++++++++++------------ beacon/goclient/spec_test.go | 1 - 7 files changed, 71 insertions(+), 45 deletions(-) diff --git a/beacon/goclient/current_fork_test.go b/beacon/goclient/current_fork_test.go index ff2c571e4f..7adcf57980 100644 --- a/beacon/goclient/current_fork_test.go +++ b/beacon/goclient/current_fork_test.go @@ -58,7 +58,6 @@ func TestCurrentFork(t *testing.T) { CommonTimeout: 100 * time.Millisecond, LongTimeout: 500 * time.Millisecond, }, - tests.MockSlotTickerProvider, ) require.NoError(t, err) @@ -89,7 +88,6 @@ func TestCurrentFork(t *testing.T) { CommonTimeout: 100 * time.Millisecond, LongTimeout: 500 * time.Millisecond, }, - tests.MockSlotTickerProvider, ) require.NoError(t, err) @@ -129,7 +127,6 @@ func TestCurrentFork(t *testing.T) { CommonTimeout: 100 * time.Millisecond, LongTimeout: 500 * time.Millisecond, }, - tests.MockSlotTickerProvider, ) require.NoError(t, err) diff --git a/beacon/goclient/genesis_test.go b/beacon/goclient/genesis_test.go index 20a07b28d4..650e1372ab 100644 --- a/beacon/goclient/genesis_test.go +++ b/beacon/goclient/genesis_test.go @@ -14,7 +14,10 @@ import ( "github.com/ssvlabs/ssv/networkconfig" ) -const genesisPath = "/eth/v1/beacon/genesis" +const ( + genesisPath = "/eth/v1/beacon/genesis" + specPath = "/eth/v1/config/spec" +) func TestGenesis(t *testing.T) { ctx := context.Background() @@ -30,6 +33,29 @@ func TestGenesis(t *testing.T) { } }`), nil } + if r.URL.Path == specPath { + return json.RawMessage(`{ + "data": { + "CONFIG_NAME": "holesky", + "GENESIS_FORK_VERSION": "0x00000000", + "CAPELLA_FORK_VERSION": "0x04017000", + "MIN_GENESIS_TIME": "1695902100", + "SECONDS_PER_SLOT": "12", + "SLOTS_PER_EPOCH": "32", + "EPOCHS_PER_SYNC_COMMITTEE_PERIOD": "256", + "SYNC_COMMITTEE_SIZE": "512", + "SYNC_COMMITTEE_SUBNET_COUNT": "4", + "TARGET_AGGREGATORS_PER_COMMITTEE": "16", + "TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE": "16", + "INTERVALS_PER_SLOT": "3", + "ALTAIR_FORK_EPOCH": "74240", + "BELLATRIX_FORK_EPOCH": "144896", + "CAPELLA_FORK_EPOCH": "194048", + "DENEB_FORK_EPOCH": "269568", + "ELECTRA_FORK_EPOCH": "18446744073709551615" + } + }`), nil + } return resp, nil }) defer mockServer.Close() @@ -43,7 +69,6 @@ func TestGenesis(t *testing.T) { CommonTimeout: 100 * time.Millisecond, LongTimeout: 500 * time.Millisecond, }, - tests.MockSlotTickerProvider, ) require.NoError(t, err) @@ -73,13 +98,10 @@ func TestGenesis(t *testing.T) { CommonTimeout: 100 * time.Millisecond, LongTimeout: 500 * time.Millisecond, }, - tests.MockSlotTickerProvider, ) - require.NoError(t, err) - - _, err = client.Genesis(ctx) require.Error(t, err) - require.Contains(t, err.Error(), "genesis response data is nil") + require.Contains(t, err.Error(), "timed out awaiting config initialization") // node cannot initialize if it cannot get genesis + require.Nil(t, client) }) t.Run("error", func(t *testing.T) { @@ -100,12 +122,9 @@ func TestGenesis(t *testing.T) { CommonTimeout: 100 * time.Millisecond, LongTimeout: 500 * time.Millisecond, }, - tests.MockSlotTickerProvider, ) - require.NoError(t, err) - - _, err = client.Genesis(ctx) require.Error(t, err) - require.Contains(t, err.Error(), "failed to request genesis") + require.Contains(t, err.Error(), "timed out awaiting config initialization") // node cannot initialize if it cannot get genesis + require.Nil(t, client) }) } diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 928540144e..6379482911 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -377,7 +377,7 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { return // Tests may override Fatal's behavior } - spec, err := specImpl(ctx, gc.log, s) + spec, err := specForClient(ctx, logger, s) if err != nil { logger.Error(clResponseErrMsg, zap.String("api", "Spec"), diff --git a/beacon/goclient/signing.go b/beacon/goclient/signing.go index 2a94d453cf..f7e1e08dd0 100644 --- a/beacon/goclient/signing.go +++ b/beacon/goclient/signing.go @@ -17,6 +17,7 @@ import ( ) func (gc *GoClient) computeVoluntaryExitDomain(ctx context.Context) (phase0.Domain, error) { + // TODO: pull from beacon node specResponse, err := gc.Spec(ctx) if err != nil { return phase0.Domain{}, fmt.Errorf("fetch spec: %w", err) diff --git a/beacon/goclient/signing_test.go b/beacon/goclient/signing_test.go index 172a35def1..bfa14f2e0b 100644 --- a/beacon/goclient/signing_test.go +++ b/beacon/goclient/signing_test.go @@ -31,7 +31,6 @@ func Test_computeVoluntaryExitDomain(t *testing.T) { CommonTimeout: 100 * time.Millisecond, LongTimeout: 500 * time.Millisecond, }, - tests.MockSlotTickerProvider, ) require.NoError(t, err) diff --git a/beacon/goclient/spec.go b/beacon/goclient/spec.go index f4765f3fa2..3da95e0c57 100644 --- a/beacon/goclient/spec.go +++ b/beacon/goclient/spec.go @@ -30,25 +30,15 @@ func (gc *GoClient) BeaconConfig() networkconfig.BeaconConfig { // fetchBeaconConfig must be called once on GoClient's initialization func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkconfig.BeaconConfig, error) { - start := time.Now() - specResponse, err := client.Spec(gc.ctx, &api.SpecOpts{}) - recordRequestDuration(gc.ctx, "Spec", client.Address(), http.MethodGet, time.Since(start), err) + specResponse, err := specForClient(gc.ctx, gc.log, client) if err != nil { gc.log.Error(clResponseErrMsg, zap.String("api", "Spec"), zap.Error(err)) return networkconfig.BeaconConfig{}, fmt.Errorf("failed to obtain spec response: %w", err) } - if specResponse == nil { - gc.log.Error(clNilResponseErrMsg, zap.String("api", "Spec")) - return networkconfig.BeaconConfig{}, fmt.Errorf("spec response is nil") - } - if specResponse.Data == nil { - gc.log.Error(clNilResponseDataErrMsg, zap.String("api", "Spec")) - return networkconfig.BeaconConfig{}, fmt.Errorf("spec response data is nil") - } // types of most values are already cast: https://github.com/attestantio/go-eth2-client/blob/v0.21.7/http/spec.go#L78 - networkNameRaw, ok := specResponse.Data["CONFIG_NAME"] + networkNameRaw, ok := specResponse["CONFIG_NAME"] if !ok { return networkconfig.BeaconConfig{}, fmt.Errorf("config name not known by chain") } @@ -59,7 +49,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } slotDuration := DefaultSlotDuration - if slotDurationRaw, ok := specResponse.Data["SECONDS_PER_SLOT"]; ok { + if slotDurationRaw, ok := specResponse["SECONDS_PER_SLOT"]; ok { if slotDurationDecoded, ok := slotDurationRaw.(time.Duration); ok { slotDuration = slotDurationDecoded } else { @@ -69,7 +59,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } slotsPerEpoch := DefaultSlotsPerEpoch - if slotsPerEpochRaw, ok := specResponse.Data["SLOTS_PER_EPOCH"]; ok { + if slotsPerEpochRaw, ok := specResponse["SLOTS_PER_EPOCH"]; ok { if slotsPerEpochDecoded, ok := slotsPerEpochRaw.(uint64); ok { slotsPerEpoch = phase0.Slot(slotsPerEpochDecoded) } else { @@ -78,29 +68,50 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } } - start = time.Now() - genesisResponse, err := client.Genesis(gc.ctx, &api.GenesisOpts{}) - recordRequestDuration(gc.ctx, "Genesis", client.Address(), http.MethodGet, time.Since(start), err) + genesisResponse, err := genesisForClient(gc.ctx, gc.log, client) if err != nil { gc.log.Error(clResponseErrMsg, zap.String("api", "Genesis"), zap.Error(err)) return networkconfig.BeaconConfig{}, fmt.Errorf("failed to obtain genesis response: %w", err) } - if genesisResponse == nil { - gc.log.Error(clNilResponseErrMsg, zap.String("api", "Genesis")) - return networkconfig.BeaconConfig{}, fmt.Errorf("genesis response is nil") - } - if genesisResponse.Data == nil { - gc.log.Error(clNilResponseDataErrMsg, zap.String("api", "Genesis")) - return networkconfig.BeaconConfig{}, fmt.Errorf("genesis response data is nil") - } beaconConfig := networkconfig.BeaconConfig{ BeaconName: networkName, SlotDuration: slotDuration, SlotsPerEpoch: slotsPerEpoch, - ForkVersion: genesisResponse.Data.GenesisForkVersion, - GenesisTime: genesisResponse.Data.GenesisTime, + ForkVersion: genesisResponse.GenesisForkVersion, + GenesisTime: genesisResponse.GenesisTime, } return beaconConfig, nil } + +func (gc *GoClient) Spec(ctx context.Context) (map[string]any, error) { + return specForClient(ctx, gc.log, gc.multiClient) +} + +func specForClient(ctx context.Context, log *zap.Logger, provider client.Service) (map[string]any, error) { + start := time.Now() + specResponse, err := provider.(client.SpecProvider).Spec(ctx, &api.SpecOpts{}) + recordRequestDuration(ctx, "Spec", provider.Address(), http.MethodGet, time.Since(start), err) + if err != nil { + log.Error(clResponseErrMsg, + zap.String("api", "Spec"), + zap.Error(err), + ) + return nil, fmt.Errorf("failed to obtain spec response: %w", err) + } + if specResponse == nil { + log.Error(clNilResponseErrMsg, + zap.String("api", "Spec"), + ) + return nil, fmt.Errorf("spec response is nil") + } + if specResponse.Data == nil { + log.Error(clNilResponseDataErrMsg, + zap.String("api", "Spec"), + ) + return nil, fmt.Errorf("spec response data is nil") + } + + return specResponse.Data, nil +} diff --git a/beacon/goclient/spec_test.go b/beacon/goclient/spec_test.go index b54dd8afa8..5ff1272dee 100644 --- a/beacon/goclient/spec_test.go +++ b/beacon/goclient/spec_test.go @@ -28,7 +28,6 @@ func TestSpec(t *testing.T) { CommonTimeout: 100 * time.Millisecond, LongTimeout: 500 * time.Millisecond, }, - tests.MockSlotTickerProvider, ) require.NoError(t, err) From 301fe46c18e7f700316b0a548a5ad51fe8be9e1e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 23 Apr 2025 22:06:15 -0300 Subject: [PATCH 20/38] code review comments --- beacon/goclient/aggregator.go | 2 +- beacon/goclient/signing.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon/goclient/aggregator.go b/beacon/goclient/aggregator.go index 397a566a1d..9d4332bde7 100644 --- a/beacon/goclient/aggregator.go +++ b/beacon/goclient/aggregator.go @@ -34,7 +34,7 @@ func (gc *GoClient) SubmitAggregateSelectionProof(slot phase0.Slot, committeeInd if err != nil { return nil, DataVersionNil, fmt.Errorf("failed to get attestation data: %w", err) } - if gc.DataVersion(gc.beaconConfig.EstimatedEpochAtSlot(attData.Slot)) < spec.DataVersionElectra { + if gc.DataVersion(gc.getBeaconConfig().EstimatedEpochAtSlot(attData.Slot)) < spec.DataVersionElectra { attData.Index = committeeIndex } diff --git a/beacon/goclient/signing.go b/beacon/goclient/signing.go index f7e1e08dd0..d451e85842 100644 --- a/beacon/goclient/signing.go +++ b/beacon/goclient/signing.go @@ -62,7 +62,7 @@ func (gc *GoClient) DomainData(epoch phase0.Epoch, domain phase0.DomainType) (ph case spectypes.DomainApplicationBuilder: // no domain for DomainApplicationBuilder. need to create. https://github.com/bloxapp/ethereum2-validator/blob/v2-main/signing/keyvault/signer.go#L62 var appDomain phase0.Domain forkData := phase0.ForkData{ - CurrentVersion: gc.beaconConfig.ForkVersion, + CurrentVersion: gc.getBeaconConfig().ForkVersion, GenesisValidatorsRoot: phase0.Root{}, } root, err := forkData.HashTreeRoot() From ebde30648df72f3801a76a350b5dde238f1c53a1 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 23 Apr 2025 22:11:21 -0300 Subject: [PATCH 21/38] fix issues after merging --- beacon/goclient/spec.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/beacon/goclient/spec.go b/beacon/goclient/spec.go index ef4ace9fb1..f36cd343f1 100644 --- a/beacon/goclient/spec.go +++ b/beacon/goclient/spec.go @@ -75,7 +75,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } epochsPerSyncCommitteePeriod := DefaultEpochsPerSyncCommitteePeriod - if epochsPerSyncCommitteePeriodRaw, ok := specResponse.Data["EPOCHS_PER_SYNC_COMMITTEE_PERIOD"]; ok { + if epochsPerSyncCommitteePeriodRaw, ok := specResponse["EPOCHS_PER_SYNC_COMMITTEE_PERIOD"]; ok { if epochsPerSyncCommitteePeriodDecoded, ok := epochsPerSyncCommitteePeriodRaw.(uint64); ok { epochsPerSyncCommitteePeriod = phase0.Epoch(epochsPerSyncCommitteePeriodDecoded) } else { @@ -85,7 +85,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } syncCommitteeSize := DefaultSyncCommitteeSize - if syncCommitteeSizeRaw, ok := specResponse.Data["SYNC_COMMITTEE_SIZE"]; ok { + if syncCommitteeSizeRaw, ok := specResponse["SYNC_COMMITTEE_SIZE"]; ok { if syncCommitteeSizeDecoded, ok := syncCommitteeSizeRaw.(uint64); ok { syncCommitteeSize = syncCommitteeSizeDecoded } else { @@ -95,7 +95,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } targetAggregatorsPerCommittee := DefaultTargetAggregatorsPerCommittee - if targetAggregatorsPerCommitteeRaw, ok := specResponse.Data["TARGET_AGGREGATORS_PER_COMMITTEE"]; ok { + if targetAggregatorsPerCommitteeRaw, ok := specResponse["TARGET_AGGREGATORS_PER_COMMITTEE"]; ok { if targetAggregatorsPerCommitteeDecoded, ok := targetAggregatorsPerCommitteeRaw.(uint64); ok { targetAggregatorsPerCommittee = targetAggregatorsPerCommitteeDecoded } else { @@ -105,7 +105,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } targetAggregatorsPerSyncSubcommittee := DefaultTargetAggregatorsPerSyncSubcommittee - if targetAggregatorsPerSyncSubcommitteeRaw, ok := specResponse.Data["TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE"]; ok { + if targetAggregatorsPerSyncSubcommitteeRaw, ok := specResponse["TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE"]; ok { if targetAggregatorsPerSyncSubcommitteeDecoded, ok := targetAggregatorsPerSyncSubcommitteeRaw.(uint64); ok { targetAggregatorsPerSyncSubcommittee = targetAggregatorsPerSyncSubcommitteeDecoded } else { @@ -115,7 +115,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } intervalsPerSlot := DefaultIntervalsPerSlot - if intervalsPerSlotRaw, ok := specResponse.Data["INTERVALS_PER_SLOT"]; ok { + if intervalsPerSlotRaw, ok := specResponse["INTERVALS_PER_SLOT"]; ok { if intervalsPerSlotDecoded, ok := intervalsPerSlotRaw.(uint64); ok { intervalsPerSlot = intervalsPerSlotDecoded } else { @@ -125,7 +125,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } syncCommitteeSubnetCount := DefaultSyncCommitteeSubnetCount - if syncCommitteeSubnetCountRaw, ok := specResponse.Data["SYNC_COMMITTEE_SUBNET_COUNT"]; ok { + if syncCommitteeSubnetCountRaw, ok := specResponse["SYNC_COMMITTEE_SUBNET_COUNT"]; ok { if syncCommitteeSubnetCountDecoded, ok := syncCommitteeSubnetCountRaw.(uint64); ok { syncCommitteeSubnetCount = syncCommitteeSubnetCountDecoded } else { @@ -134,7 +134,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } } - start = time.Now() + start := time.Now() genesisResponse, err := genesisForClient(gc.ctx, gc.log, client) recordRequestDuration(gc.ctx, "Genesis", client.Address(), http.MethodGet, time.Since(start), err) if err != nil { @@ -154,7 +154,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco IntervalsPerSlot: intervalsPerSlot, ForkVersion: genesisResponse.GenesisForkVersion, GenesisTime: genesisResponse.GenesisTime, - GenesisValidatorsRoot: genesisResponse.Data.GenesisValidatorsRoot, + GenesisValidatorsRoot: genesisResponse.GenesisValidatorsRoot, } return beaconConfig, nil From bec420c07ce4d6650cf11e789de58dc43ed55527 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 23 Apr 2025 22:14:08 -0300 Subject: [PATCH 22/38] delete outer metrics for genesisForClient --- beacon/goclient/spec.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/beacon/goclient/spec.go b/beacon/goclient/spec.go index f36cd343f1..3fb379852e 100644 --- a/beacon/goclient/spec.go +++ b/beacon/goclient/spec.go @@ -134,9 +134,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco } } - start := time.Now() genesisResponse, err := genesisForClient(gc.ctx, gc.log, client) - recordRequestDuration(gc.ctx, "Genesis", client.Address(), http.MethodGet, time.Since(start), err) if err != nil { gc.log.Error(clResponseErrMsg, zap.String("api", "Genesis"), zap.Error(err)) return networkconfig.BeaconConfig{}, fmt.Errorf("failed to obtain genesis response: %w", err) From d1fd6929a6002267fc104c5904a82f41835648f3 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 23 Apr 2025 22:14:59 -0300 Subject: [PATCH 23/38] rewrite modulo calculation --- beacon/goclient/sync_committee_contribution.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon/goclient/sync_committee_contribution.go b/beacon/goclient/sync_committee_contribution.go index 88113a1983..4ae01fe2e0 100644 --- a/beacon/goclient/sync_committee_contribution.go +++ b/beacon/goclient/sync_committee_contribution.go @@ -24,7 +24,7 @@ func (gc *GoClient) IsSyncCommitteeAggregator(proof []byte) (bool, error) { // Keep the signature if it's an aggregator. cfg := gc.BeaconConfig() - modulo := cfg.SyncCommitteeSize / cfg.SyncCommitteeSubnetCount / cfg.TargetAggregatorsPerSyncSubcommittee + modulo := cfg.SyncCommitteeSize / (cfg.SyncCommitteeSubnetCount * cfg.TargetAggregatorsPerSyncSubcommittee) if modulo == uint64(0) { // Modulo must be at least 1. modulo = 1 From 9947640effdae56c13bdc698bbebc83ef0dd1ca8 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 23 Apr 2025 22:19:22 -0300 Subject: [PATCH 24/38] code review comments --- networkconfig/beacon.go | 2 +- networkconfig/test-network.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/networkconfig/beacon.go b/networkconfig/beacon.go index 60e3b18735..b4e3a1599e 100644 --- a/networkconfig/beacon.go +++ b/networkconfig/beacon.go @@ -160,7 +160,7 @@ func (b BeaconConfig) IntervalDuration() time.Duration { func (b BeaconConfig) EpochDuration() time.Duration { if b.SlotsPerEpoch > math.MaxInt64 { - panic("slot out of range") + panic("slots per epoch out of range") } return b.SlotDuration * time.Duration(b.SlotsPerEpoch) // #nosec G115: slot cannot exceed math.MaxInt64 } diff --git a/networkconfig/test-network.go b/networkconfig/test-network.go index 8c3e860da1..d879fdc3ce 100644 --- a/networkconfig/test-network.go +++ b/networkconfig/test-network.go @@ -25,6 +25,6 @@ var TestNetwork = NetworkConfig{ Bootnodes: []string{ "enr:-Li4QFIQzamdvTxGJhvcXG_DFmCeyggSffDnllY5DiU47pd_K_1MRnSaJimWtfKJ-MD46jUX9TwgW5Jqe0t4pH41RYWGAYuFnlyth2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhCLdu_SJc2VjcDI1NmsxoQN4v-N9zFYwEqzGPBBX37q24QPFvAVUtokIo1fblIsmTIN0Y3CCE4uDdWRwgg-j", }, - TotalEthereumValidators: 10, // just some random number + TotalEthereumValidators: 1_000_000, // just some high enough value, so we never accidentally reach the message-limits derived from it while testing something with local testnet }, } From a22b256754d244c5841373e2e2469479a2aa5cb9 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 24 Apr 2025 09:01:37 -0300 Subject: [PATCH 25/38] unexport supportedSSVConfigs --- networkconfig/ssv.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/networkconfig/ssv.go b/networkconfig/ssv.go index 14c245b041..5ca0ba4a32 100644 --- a/networkconfig/ssv.go +++ b/networkconfig/ssv.go @@ -10,7 +10,7 @@ import ( //go:generate go tool -modfile=../tool.mod mockgen -package=networkconfig -destination=./ssv_mock.go -source=./ssv.go -var SupportedSSVConfigs = map[string]SSVConfig{ +var supportedSSVConfigs = map[string]SSVConfig{ Mainnet.Name: Mainnet.SSVConfig, Holesky.Name: Holesky.SSVConfig, HoleskyStage.Name: HoleskyStage.SSVConfig, @@ -22,7 +22,7 @@ var SupportedSSVConfigs = map[string]SSVConfig{ } func GetSSVConfigByName(name string) (SSVConfig, error) { - if network, ok := SupportedSSVConfigs[name]; ok { + if network, ok := supportedSSVConfigs[name]; ok { return network, nil } From 34f8fc015ec319209ece8744536151b5bc4820cf Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 24 Apr 2025 09:15:46 -0300 Subject: [PATCH 26/38] revert the modulo calculation --- beacon/goclient/sync_committee_contribution.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon/goclient/sync_committee_contribution.go b/beacon/goclient/sync_committee_contribution.go index 4ae01fe2e0..88113a1983 100644 --- a/beacon/goclient/sync_committee_contribution.go +++ b/beacon/goclient/sync_committee_contribution.go @@ -24,7 +24,7 @@ func (gc *GoClient) IsSyncCommitteeAggregator(proof []byte) (bool, error) { // Keep the signature if it's an aggregator. cfg := gc.BeaconConfig() - modulo := cfg.SyncCommitteeSize / (cfg.SyncCommitteeSubnetCount * cfg.TargetAggregatorsPerSyncSubcommittee) + modulo := cfg.SyncCommitteeSize / cfg.SyncCommitteeSubnetCount / cfg.TargetAggregatorsPerSyncSubcommittee if modulo == uint64(0) { // Modulo must be at least 1. modulo = 1 From 06d5f8a4c96b3ac1a7ff324e94da72f66f3e1581 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 25 Apr 2025 13:25:08 -0300 Subject: [PATCH 27/38] add EpochDuration mock --- utils/testutils.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/testutils.go b/utils/testutils.go index 8eb07a4f3e..92713642d0 100644 --- a/utils/testutils.go +++ b/utils/testutils.go @@ -94,6 +94,8 @@ func SetupMockNetworkConfig(t *testing.T, domainType spectypes.DomainType, curre }, ).AnyTimes() + mockNetwork.EXPECT().EpochDuration().Return(time.Duration(mockNetwork.GetSlotsPerEpoch()) * mockNetwork.GetSlotDuration()).AnyTimes() + mockNetwork.EXPECT().GetDomainType().Return(domainType).AnyTimes() mockNetwork.EXPECT().GetBeaconName().Return(string(beaconNetwork)).AnyTimes() From 5f8dd7ee98d45c11775be838da60a1b6f89c0bd0 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 25 Apr 2025 13:31:09 -0300 Subject: [PATCH 28/38] G115 --- utils/testutils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/testutils.go b/utils/testutils.go index 92713642d0..4c6c957fe9 100644 --- a/utils/testutils.go +++ b/utils/testutils.go @@ -94,7 +94,7 @@ func SetupMockNetworkConfig(t *testing.T, domainType spectypes.DomainType, curre }, ).AnyTimes() - mockNetwork.EXPECT().EpochDuration().Return(time.Duration(mockNetwork.GetSlotsPerEpoch()) * mockNetwork.GetSlotDuration()).AnyTimes() + mockNetwork.EXPECT().EpochDuration().Return(time.Duration(mockNetwork.GetSlotsPerEpoch()) * mockNetwork.GetSlotDuration()).AnyTimes() // #nosec G115 mockNetwork.EXPECT().GetDomainType().Return(domainType).AnyTimes() From 36a7a312a840eac888c2e9f003ad3c74010ea5c1 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 25 Apr 2025 17:03:11 -0300 Subject: [PATCH 29/38] fill missing fields in configs --- networkconfig/holesky-e2e.go | 12 ++---------- networkconfig/holesky-stage.go | 13 ++----------- networkconfig/holesky.go | 18 +++++++++++++----- networkconfig/hoodi-stage.go | 13 ++----------- networkconfig/hoodi.go | 18 +++++++++++++----- networkconfig/local-testnet.go | 18 +++++++++++++----- networkconfig/mainnet.go | 18 +++++++++++++----- networkconfig/sepolia.go | 16 +++++++++++----- networkconfig/test-network.go | 18 +++++++++++++----- operator/duties/committee_test.go | 2 ++ operator/duties/scheduler_test.go | 2 ++ utils/testutils.go | 2 ++ 12 files changed, 88 insertions(+), 62 deletions(-) diff --git a/networkconfig/holesky-e2e.go b/networkconfig/holesky-e2e.go index a5917d4193..efa627fe98 100644 --- a/networkconfig/holesky-e2e.go +++ b/networkconfig/holesky-e2e.go @@ -2,22 +2,14 @@ package networkconfig import ( "math/big" - "time" - "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" spectypes "github.com/ssvlabs/ssv-spec/types" ) var HoleskyE2E = NetworkConfig{ - Name: "holesky-e2e", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.HoleskyNetwork), - SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.HoleskyNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, + Name: "holesky-e2e", + BeaconConfig: Holesky.BeaconConfig, SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, 0xee, 0x1}, RegistryContractAddr: ethcommon.HexToAddress("0x58410bef803ecd7e63b23664c586a6db72daf59c"), diff --git a/networkconfig/holesky-stage.go b/networkconfig/holesky-stage.go index 7b5f2b7430..219aef756a 100644 --- a/networkconfig/holesky-stage.go +++ b/networkconfig/holesky-stage.go @@ -2,22 +2,13 @@ package networkconfig import ( "math/big" - "time" - "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" - spectypes "github.com/ssvlabs/ssv-spec/types" ) var HoleskyStage = NetworkConfig{ - Name: "holesky-stage", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.HoleskyNetwork), - SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.HoleskyNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, + Name: "holesky-stage", + BeaconConfig: Holesky.BeaconConfig, SSVConfig: SSVConfig{ DomainType: [4]byte{0x00, 0x00, 0x31, 0x13}, RegistrySyncOffset: new(big.Int).SetInt64(84599), diff --git a/networkconfig/holesky.go b/networkconfig/holesky.go index 00d2334241..623ad81dce 100644 --- a/networkconfig/holesky.go +++ b/networkconfig/holesky.go @@ -6,17 +6,25 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" spectypes "github.com/ssvlabs/ssv-spec/types" ) var Holesky = NetworkConfig{ Name: "holesky", BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.HoleskyNetwork), - SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.HoleskyNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + BeaconName: string(spectypes.HoleskyNetwork), + SlotDuration: spectypes.HoleskyNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.HoleskyNetwork.SlotsPerEpoch()), + EpochsPerSyncCommitteePeriod: 256, + SyncCommitteeSize: 512, + SyncCommitteeSubnetCount: 4, + TargetAggregatorsPerSyncSubcommittee: 16, + TargetAggregatorsPerCommittee: 16, + IntervalsPerSlot: 3, + ForkVersion: spectypes.HoleskyNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.HoleskyNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + GenesisValidatorsRoot: phase0.Root(hexutil.MustDecode("0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1")), }, SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x2}, diff --git a/networkconfig/hoodi-stage.go b/networkconfig/hoodi-stage.go index 7f600427b9..36ee870d92 100644 --- a/networkconfig/hoodi-stage.go +++ b/networkconfig/hoodi-stage.go @@ -2,22 +2,13 @@ package networkconfig import ( "math/big" - "time" - "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" - spectypes "github.com/ssvlabs/ssv-spec/types" ) var HoodiStage = NetworkConfig{ - Name: "hoodi-stage", - BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.HoodiNetwork), - SlotDuration: spectypes.HoodiNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.HoodiNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.HoodiNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.HoodiNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 - }, + Name: "hoodi-stage", + BeaconConfig: Hoodi.BeaconConfig, SSVConfig: SSVConfig{ DomainType: [4]byte{0x00, 0x00, 0x31, 0x14}, RegistrySyncOffset: new(big.Int).SetInt64(1004), diff --git a/networkconfig/hoodi.go b/networkconfig/hoodi.go index a88699d729..efe17fb5b1 100644 --- a/networkconfig/hoodi.go +++ b/networkconfig/hoodi.go @@ -6,17 +6,25 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" spectypes "github.com/ssvlabs/ssv-spec/types" ) var Hoodi = NetworkConfig{ Name: "hoodi", BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.HoodiNetwork), - SlotDuration: spectypes.HoodiNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.HoodiNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.HoodiNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.HoodiNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + BeaconName: string(spectypes.HoodiNetwork), + SlotDuration: spectypes.HoodiNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.HoodiNetwork.SlotsPerEpoch()), + EpochsPerSyncCommitteePeriod: 256, + SyncCommitteeSize: 512, + SyncCommitteeSubnetCount: 4, + TargetAggregatorsPerSyncSubcommittee: 16, + TargetAggregatorsPerCommittee: 16, + IntervalsPerSlot: 3, + ForkVersion: spectypes.HoodiNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.HoodiNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + GenesisValidatorsRoot: phase0.Root(hexutil.MustDecode("0x212f13fc4df078b6cb7db228f1c8307566dcecf900867401a92023d7ba99cb5f")), }, SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x3}, diff --git a/networkconfig/local-testnet.go b/networkconfig/local-testnet.go index 0d7f284e02..cb6370df4e 100644 --- a/networkconfig/local-testnet.go +++ b/networkconfig/local-testnet.go @@ -5,17 +5,25 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" spectypes "github.com/ssvlabs/ssv-spec/types" ) var LocalTestnet = NetworkConfig{ Name: "local-testnet", BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.PraterNetwork), - SlotDuration: spectypes.PraterNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.PraterNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.PraterNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.PraterNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + BeaconName: string(spectypes.PraterNetwork), + SlotDuration: spectypes.PraterNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.PraterNetwork.SlotsPerEpoch()), + EpochsPerSyncCommitteePeriod: 256, + SyncCommitteeSize: 512, + SyncCommitteeSubnetCount: 4, + TargetAggregatorsPerSyncSubcommittee: 16, + TargetAggregatorsPerCommittee: 16, + IntervalsPerSlot: 3, + ForkVersion: spectypes.PraterNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.PraterNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + GenesisValidatorsRoot: phase0.Root(hexutil.MustDecode("0x043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb")), }, SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoV2NetworkID.Byte(), 0x2}, diff --git a/networkconfig/mainnet.go b/networkconfig/mainnet.go index 075663a645..8002d85419 100644 --- a/networkconfig/mainnet.go +++ b/networkconfig/mainnet.go @@ -6,17 +6,25 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" spectypes "github.com/ssvlabs/ssv-spec/types" ) var Mainnet = NetworkConfig{ Name: "mainnet", BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.MainNetwork), - SlotDuration: spectypes.MainNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.MainNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.MainNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.MainNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + BeaconName: string(spectypes.MainNetwork), + SlotDuration: spectypes.MainNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.MainNetwork.SlotsPerEpoch()), + EpochsPerSyncCommitteePeriod: 256, + SyncCommitteeSize: 512, + SyncCommitteeSubnetCount: 4, + TargetAggregatorsPerSyncSubcommittee: 16, + TargetAggregatorsPerCommittee: 16, + IntervalsPerSlot: 3, + ForkVersion: spectypes.MainNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.MainNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + GenesisValidatorsRoot: phase0.Root(hexutil.MustDecode("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95")), }, SSVConfig: SSVConfig{ DomainType: spectypes.AlanMainnet, diff --git a/networkconfig/sepolia.go b/networkconfig/sepolia.go index a102d84ea5..47afad9b99 100644 --- a/networkconfig/sepolia.go +++ b/networkconfig/sepolia.go @@ -12,11 +12,17 @@ import ( var Sepolia = NetworkConfig{ Name: "sepolia", BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.SepoliaNetwork), - SlotDuration: spectypes.SepoliaNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.SepoliaNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.SepoliaNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.SepoliaNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + BeaconName: string(spectypes.SepoliaNetwork), + SlotDuration: spectypes.SepoliaNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.SepoliaNetwork.SlotsPerEpoch()), + EpochsPerSyncCommitteePeriod: 256, + SyncCommitteeSize: 512, + SyncCommitteeSubnetCount: 4, + TargetAggregatorsPerSyncSubcommittee: 16, + TargetAggregatorsPerCommittee: 16, + IntervalsPerSlot: 3, + ForkVersion: spectypes.SepoliaNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.SepoliaNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 }, SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, 0x5, 0x69}, diff --git a/networkconfig/test-network.go b/networkconfig/test-network.go index d879fdc3ce..9eadf39f97 100644 --- a/networkconfig/test-network.go +++ b/networkconfig/test-network.go @@ -6,17 +6,25 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" spectypes "github.com/ssvlabs/ssv-spec/types" ) var TestNetwork = NetworkConfig{ Name: "testnet", BeaconConfig: BeaconConfig{ - BeaconName: string(spectypes.BeaconTestNetwork), - SlotDuration: spectypes.BeaconTestNetwork.SlotDurationSec(), - SlotsPerEpoch: phase0.Slot(spectypes.BeaconTestNetwork.SlotsPerEpoch()), - ForkVersion: spectypes.BeaconTestNetwork.ForkVersion(), - GenesisTime: time.Unix(int64(spectypes.BeaconTestNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + BeaconName: string(spectypes.BeaconTestNetwork), + SlotDuration: spectypes.BeaconTestNetwork.SlotDurationSec(), + SlotsPerEpoch: phase0.Slot(spectypes.BeaconTestNetwork.SlotsPerEpoch()), + EpochsPerSyncCommitteePeriod: 256, + SyncCommitteeSize: 512, + SyncCommitteeSubnetCount: 4, + TargetAggregatorsPerSyncSubcommittee: 16, + TargetAggregatorsPerCommittee: 16, + IntervalsPerSlot: 3, + ForkVersion: spectypes.BeaconTestNetwork.ForkVersion(), + GenesisTime: time.Unix(int64(spectypes.BeaconTestNetwork.MinGenesisTime()), 0), // #nosec G115 -- time should not exceed int64 + GenesisValidatorsRoot: phase0.Root(hexutil.MustDecode("0x043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb")), }, SSVConfig: SSVConfig{ DomainType: spectypes.DomainType{0x0, 0x0, spectypes.JatoNetworkID.Byte(), 0x2}, diff --git a/operator/duties/committee_test.go b/operator/duties/committee_test.go index 90af5c8da9..0d5e56c04a 100644 --- a/operator/duties/committee_test.go +++ b/operator/duties/committee_test.go @@ -55,6 +55,8 @@ func setupCommitteeDutiesMock( }, ).AnyTimes() + s.beaconConfig.(*networkconfig.MockBeacon).EXPECT().IntervalDuration().Return(s.beaconConfig.GetSlotDuration() / 3).AnyTimes() + s.beaconNode.(*MockBeaconNode).EXPECT().AttesterDuties(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( func(ctx context.Context, epoch phase0.Epoch, indices []phase0.ValidatorIndex) ([]*eth2apiv1.AttesterDuty, error) { if waitForDuties.Get() { diff --git a/operator/duties/scheduler_test.go b/operator/duties/scheduler_test.go index 15917ce95c..d29a4a5528 100644 --- a/operator/duties/scheduler_test.go +++ b/operator/duties/scheduler_test.go @@ -136,6 +136,8 @@ func setupSchedulerAndMocks(t *testing.T, handlers []dutyHandler, currentSlot *S s.beaconConfig.(*networkconfig.MockBeacon).EXPECT().GetEpochsPerSyncCommitteePeriod().Return(phase0.Epoch(256)).AnyTimes() + s.beaconConfig.(*networkconfig.MockBeacon).EXPECT().IntervalDuration().Return(s.beaconConfig.GetSlotDuration() / 3).AnyTimes() + // Create a pool to wait for the scheduler to finish. schedulerPool := pool.New().WithErrors().WithContext(ctx) diff --git a/utils/testutils.go b/utils/testutils.go index 4c6c957fe9..4c02c81609 100644 --- a/utils/testutils.go +++ b/utils/testutils.go @@ -96,6 +96,8 @@ func SetupMockNetworkConfig(t *testing.T, domainType spectypes.DomainType, curre mockNetwork.EXPECT().EpochDuration().Return(time.Duration(mockNetwork.GetSlotsPerEpoch()) * mockNetwork.GetSlotDuration()).AnyTimes() // #nosec G115 + mockNetwork.EXPECT().IntervalDuration().Return(mockNetwork.GetSlotDuration() / 3).AnyTimes() // #nosec G115 + mockNetwork.EXPECT().GetDomainType().Return(domainType).AnyTimes() mockNetwork.EXPECT().GetBeaconName().Return(string(beaconNetwork)).AnyTimes() From 70ce02ed2a969a9f07d38056b7ec9b66843cef79 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 25 Apr 2025 18:07:52 -0300 Subject: [PATCH 30/38] add a comment about eth spec --- beacon/goclient/sync_committee_contribution.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beacon/goclient/sync_committee_contribution.go b/beacon/goclient/sync_committee_contribution.go index 88113a1983..0f41f5753e 100644 --- a/beacon/goclient/sync_committee_contribution.go +++ b/beacon/goclient/sync_committee_contribution.go @@ -24,6 +24,8 @@ func (gc *GoClient) IsSyncCommitteeAggregator(proof []byte) (bool, error) { // Keep the signature if it's an aggregator. cfg := gc.BeaconConfig() + + // as per spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/validator.md#aggregation-selection modulo := cfg.SyncCommitteeSize / cfg.SyncCommitteeSubnetCount / cfg.TargetAggregatorsPerSyncSubcommittee if modulo == uint64(0) { // Modulo must be at least 1. From 5ccf73eb5205ddac17ae82d2000300cd69655ce1 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 25 Apr 2025 18:09:23 -0300 Subject: [PATCH 31/38] fix error text --- beacon/goclient/spec.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/beacon/goclient/spec.go b/beacon/goclient/spec.go index 3fb379852e..f6466d4906 100644 --- a/beacon/goclient/spec.go +++ b/beacon/goclient/spec.go @@ -46,7 +46,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco networkNameRaw, ok := specResponse["CONFIG_NAME"] if !ok { - return networkconfig.BeaconConfig{}, fmt.Errorf("config name not known by chain") + return networkconfig.BeaconConfig{}, fmt.Errorf("config name wasn't found in beacon node response") } networkName, ok := networkNameRaw.(string) @@ -59,7 +59,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco if slotDurationDecoded, ok := slotDurationRaw.(time.Duration); ok { slotDuration = slotDurationDecoded } else { - gc.log.Warn("seconds per slot not known by chain, using default value", + gc.log.Warn("seconds per slot wasn't found in beacon node response, using default value", zap.Any("value", slotDuration)) } } @@ -69,7 +69,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco if slotsPerEpochDecoded, ok := slotsPerEpochRaw.(uint64); ok { slotsPerEpoch = phase0.Slot(slotsPerEpochDecoded) } else { - gc.log.Warn("slots per epoch not known by chain, using default value", + gc.log.Warn("slots per epoch wasn't found in beacon node response, using default value", zap.Any("value", slotsPerEpoch)) } } @@ -79,7 +79,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco if epochsPerSyncCommitteePeriodDecoded, ok := epochsPerSyncCommitteePeriodRaw.(uint64); ok { epochsPerSyncCommitteePeriod = phase0.Epoch(epochsPerSyncCommitteePeriodDecoded) } else { - gc.log.Warn("epochs per sync committee not known by chain, using default value", + gc.log.Warn("epochs per sync committee wasn't found in beacon node response, using default value", zap.Any("value", epochsPerSyncCommitteePeriod)) } } @@ -89,7 +89,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco if syncCommitteeSizeDecoded, ok := syncCommitteeSizeRaw.(uint64); ok { syncCommitteeSize = syncCommitteeSizeDecoded } else { - gc.log.Warn("sync committee size not known by chain, using default value", + gc.log.Warn("sync committee size wasn't found in beacon node response, using default value", zap.Any("value", syncCommitteeSize)) } } @@ -99,7 +99,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco if targetAggregatorsPerCommitteeDecoded, ok := targetAggregatorsPerCommitteeRaw.(uint64); ok { targetAggregatorsPerCommittee = targetAggregatorsPerCommitteeDecoded } else { - gc.log.Warn("target aggregators per committee not known by chain, using default value", + gc.log.Warn("target aggregators per committee wasn't found in beacon node response, using default value", zap.Any("value", targetAggregatorsPerCommittee)) } } @@ -109,7 +109,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco if targetAggregatorsPerSyncSubcommitteeDecoded, ok := targetAggregatorsPerSyncSubcommitteeRaw.(uint64); ok { targetAggregatorsPerSyncSubcommittee = targetAggregatorsPerSyncSubcommitteeDecoded } else { - gc.log.Warn("target aggregators per sync subcommittee not known by chain, using default value", + gc.log.Warn("target aggregators per sync subcommittee wasn't found in beacon node response, using default value", zap.Any("value", targetAggregatorsPerSyncSubcommittee)) } } @@ -119,7 +119,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco if intervalsPerSlotDecoded, ok := intervalsPerSlotRaw.(uint64); ok { intervalsPerSlot = intervalsPerSlotDecoded } else { - gc.log.Warn("intervals per slot not known by chain, using default value", + gc.log.Warn("intervals per slot wasn't found in beacon node response, using default value", zap.Any("value", intervalsPerSlot)) } } @@ -129,7 +129,7 @@ func (gc *GoClient) fetchBeaconConfig(client *eth2clienthttp.Service) (networkco if syncCommitteeSubnetCountDecoded, ok := syncCommitteeSubnetCountRaw.(uint64); ok { syncCommitteeSubnetCount = syncCommitteeSubnetCountDecoded } else { - gc.log.Warn("sync committee subnet count not known by chain, using default value", + gc.log.Warn("sync committee subnet count wasn't found in beacon node response, using default value", zap.Any("value", syncCommitteeSubnetCount)) } } From 94aec3b04100b12448427058e9d9e1b223017440 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 25 Apr 2025 18:36:02 -0300 Subject: [PATCH 32/38] use genesis validators root from config in computeVoluntaryExitDomain --- beacon/goclient/signing.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/beacon/goclient/signing.go b/beacon/goclient/signing.go index d451e85842..abef143557 100644 --- a/beacon/goclient/signing.go +++ b/beacon/goclient/signing.go @@ -35,16 +35,10 @@ func (gc *GoClient) computeVoluntaryExitDomain(ctx context.Context) (phase0.Doma } forkData := &phase0.ForkData{ - CurrentVersion: forkVersion, + CurrentVersion: forkVersion, + GenesisValidatorsRoot: gc.getBeaconConfig().GenesisValidatorsRoot, } - genesis, err := gc.Genesis(ctx) - if err != nil { - return phase0.Domain{}, fmt.Errorf("failed to obtain genesis response: %w", err) - } - - forkData.GenesisValidatorsRoot = genesis.GenesisValidatorsRoot - root, err := forkData.HashTreeRoot() if err != nil { return phase0.Domain{}, fmt.Errorf("failed to calculate signature domain, err: %w", err) From 144e380d5f7228b062e48e2e8a883f1f67cde736 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 30 Apr 2025 10:29:26 -0300 Subject: [PATCH 33/38] fix linter --- networkconfig/ssv_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/networkconfig/ssv_test.go b/networkconfig/ssv_test.go index c72e3d868a..8872d6b4a3 100644 --- a/networkconfig/ssv_test.go +++ b/networkconfig/ssv_test.go @@ -206,7 +206,7 @@ func TestFieldPreservation(t *testing.T) { // TestExistingNetworkConfigs validates that all predefined network configs // can be marshaled and unmarshaled correctly func TestExistingNetworkConfigs(t *testing.T) { - for networkName, config := range SupportedSSVConfigs { + for networkName, config := range supportedSSVConfigs { t.Run(networkName, func(t *testing.T) { // JSON test jsonBytes, err := json.Marshal(config) From 5183f8bd6bbaf214efd3bbd26233f5b3acbf809f Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 12 May 2025 15:30:38 -0300 Subject: [PATCH 34/38] fix a typo --- network/topics/params/peer_score.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network/topics/params/peer_score.go b/network/topics/params/peer_score.go index c8e9d01921..7b0d063f16 100644 --- a/network/topics/params/peer_score.go +++ b/network/topics/params/peer_score.go @@ -46,7 +46,7 @@ func PeerScoreThresholds() *pubsub.PeerScoreThresholds { } // PeerScoreParams returns peer score params according to the given options -func PeerScoreParams(netCfg networkconfig.NetworkConfig, msgIDCacheTTL time.Duration, disableColocation bool, ipWhilelist ...*net.IPNet) *pubsub.PeerScoreParams { +func PeerScoreParams(netCfg networkconfig.NetworkConfig, msgIDCacheTTL time.Duration, disableColocation bool, ipWhitelist ...*net.IPNet) *pubsub.PeerScoreParams { // P7 calculation behaviourPenaltyDecay := scoreDecay(netCfg.EpochDuration()*10, netCfg.EpochDuration()) maxAllowedRatePerDecayInterval := 10.0 @@ -77,7 +77,7 @@ func PeerScoreParams(netCfg networkconfig.NetworkConfig, msgIDCacheTTL time.Dura // P6 IPColocationFactorWeight: finalIPColocationFactorWeight, IPColocationFactorThreshold: ipColocationFactorThreshold, - IPColocationFactorWhitelist: ipWhilelist, + IPColocationFactorWhitelist: ipWhitelist, // P7 BehaviourPenaltyWeight: behaviourPenaltyWeight, From 8b4d726468ec32bbdea0922cbf0fb12641746609 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 12 May 2025 15:31:59 -0300 Subject: [PATCH 35/38] remove redundant comments --- beacon/goclient/aggregator.go | 2 +- beacon/goclient/sync_committee_contribution.go | 2 +- operator/duties/scheduler.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/beacon/goclient/aggregator.go b/beacon/goclient/aggregator.go index 7f92c2236c..ae5e789df3 100644 --- a/beacon/goclient/aggregator.go +++ b/beacon/goclient/aggregator.go @@ -194,7 +194,7 @@ func (gc *GoClient) isAggregator(committeeCount uint64, slotSig []byte) (bool, e // waitToSlotTwoThirds waits until two-third of the slot has transpired (SECONDS_PER_SLOT * 2 / 3 seconds after the start of slot) func (gc *GoClient) waitToSlotTwoThirds(slot phase0.Slot) { config := gc.getBeaconConfig() - oneInterval := config.IntervalDuration() /* one third of slot duration */ + oneInterval := config.IntervalDuration() finalTime := config.GetSlotStartTime(slot).Add(2 * oneInterval) wait := time.Until(finalTime) if wait <= 0 { diff --git a/beacon/goclient/sync_committee_contribution.go b/beacon/goclient/sync_committee_contribution.go index 0f41f5753e..c688f7d292 100644 --- a/beacon/goclient/sync_committee_contribution.go +++ b/beacon/goclient/sync_committee_contribution.go @@ -148,7 +148,7 @@ func (gc *GoClient) SubmitSignedContributionAndProof(contribution *altair.Signed // waitForOneThirdSlotDuration waits until one-third of the slot has transpired (SECONDS_PER_SLOT / 3 seconds after the start of slot) func (gc *GoClient) waitForOneThirdSlotDuration(slot phase0.Slot) { config := gc.getBeaconConfig() - delay := config.IntervalDuration() /* a third of the slot duration */ + delay := config.IntervalDuration() finalTime := config.GetSlotStartTime(slot).Add(delay) wait := time.Until(finalTime) if wait <= 0 { diff --git a/operator/duties/scheduler.go b/operator/duties/scheduler.go index b3408c2c8c..884194098c 100644 --- a/operator/duties/scheduler.go +++ b/operator/duties/scheduler.go @@ -286,7 +286,7 @@ func (s *Scheduler) SlotTicker(ctx context.Context) { case <-s.ticker.Next(): slot := s.ticker.Slot() - delay := s.beaconConfig.IntervalDuration() /* a third of the slot duration */ + delay := s.beaconConfig.IntervalDuration() finalTime := s.beaconConfig.GetSlotStartTime(slot).Add(delay) waitDuration := time.Until(finalTime) @@ -365,7 +365,7 @@ func (s *Scheduler) HandleHeadEvent(logger *zap.Logger) func(event *eth2apiv1.He s.currentDutyDependentRoot = event.CurrentDutyDependentRoot currentTime := time.Now() - delay := s.beaconConfig.IntervalDuration() /* a third of the slot duration */ + delay := s.beaconConfig.IntervalDuration() slotStartTimeWithDelay := s.beaconConfig.GetSlotStartTime(event.Slot).Add(delay) if currentTime.Before(slotStartTimeWithDelay) { logger.Debug("🏁 Head event: Block arrived before 1/3 slot", zap.Duration("time_saved", slotStartTimeWithDelay.Sub(currentTime))) From 3ec364ec27013ac623a84944372dd26d2bdcb658 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 21 May 2025 22:53:43 -0300 Subject: [PATCH 36/38] fix a context bug --- beacon/goclient/goclient.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index e9f5e7f16b..0bd2a40347 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -240,16 +240,16 @@ func New( client.nodeSyncingFn = client.nodeSyncing - ctx, cancel := context.WithTimeout(ctx, client.longTimeout) - defer cancel() + initCtx, initCtxCancel := context.WithTimeout(ctx, client.longTimeout) + defer initCtxCancel() select { - case <-ctx.Done(): + case <-initCtx.Done(): logger.Warn("timeout occurred while waiting for beacon config initialization", zap.Duration("timeout", client.longTimeout), - zap.Error(ctx.Err()), + zap.Error(initCtx.Err()), ) - return nil, fmt.Errorf("timed out awaiting config initialization: %w", ctx.Err()) + return nil, fmt.Errorf("timed out awaiting config initialization: %w", initCtx.Err()) case <-client.beaconConfigInit: } From 5478ec138162980ea3ae82563c6c5a59340f38e2 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 22 May 2025 21:12:32 -0300 Subject: [PATCH 37/38] fix issues after merging --- beacon/goclient/spec.go | 4 ++-- network/topics/params/message_rate.go | 2 +- networkconfig/beacon.go | 2 +- networkconfig/beacon_mock.go | 4 ++-- networkconfig/holesky.go | 1 + networkconfig/hoodi.go | 1 + networkconfig/local-testnet.go | 1 + networkconfig/mainnet.go | 1 + networkconfig/network_mock.go | 4 ++-- networkconfig/test-network.go | 1 + 10 files changed, 13 insertions(+), 8 deletions(-) diff --git a/beacon/goclient/spec.go b/beacon/goclient/spec.go index 4e0ff52a0b..02f3809edd 100644 --- a/beacon/goclient/spec.go +++ b/beacon/goclient/spec.go @@ -17,7 +17,7 @@ import ( const ( DefaultSlotDuration = 12 * time.Second DefaultSlotsPerEpoch = uint64(32) - DefaultEpochsPerSyncCommitteePeriod = phase0.Epoch(256) + DefaultEpochsPerSyncCommitteePeriod = uint64(256) DefaultSyncCommitteeSize = uint64(512) DefaultSyncCommitteeSubnetCount = uint64(4) DefaultTargetAggregatorsPerSyncSubcommittee = uint64(16) @@ -76,7 +76,7 @@ func (gc *GoClient) fetchBeaconConfig(ctx context.Context, client *eth2clienthtt epochsPerSyncCommitteePeriod := DefaultEpochsPerSyncCommitteePeriod if epochsPerSyncCommitteePeriodRaw, ok := specResponse["EPOCHS_PER_SYNC_COMMITTEE_PERIOD"]; ok { if epochsPerSyncCommitteePeriodDecoded, ok := epochsPerSyncCommitteePeriodRaw.(uint64); ok { - epochsPerSyncCommitteePeriod = phase0.Epoch(epochsPerSyncCommitteePeriodDecoded) + epochsPerSyncCommitteePeriod = epochsPerSyncCommitteePeriodDecoded } else { gc.log.Warn("epochs per sync committee wasn't found in beacon node response, using default value", zap.Any("value", epochsPerSyncCommitteePeriod)) diff --git a/network/topics/params/message_rate.go b/network/topics/params/message_rate.go index 92dfc8051c..66e3d53b0d 100644 --- a/network/topics/params/message_rate.go +++ b/network/topics/params/message_rate.go @@ -154,7 +154,7 @@ func (rc *rateCalculator) SyncCommitteeAggProb() float64 { } func (rc *rateCalculator) MaxAttestationDutiesPerEpochForCommittee() uint64 { - return uint64(rc.netCfg.GetSlotsPerEpoch()) + return rc.netCfg.GetSlotsPerEpoch() } func (rc *rateCalculator) EstimatedAttestationCommitteeSize() float64 { diff --git a/networkconfig/beacon.go b/networkconfig/beacon.go index 9afe406ec9..94114ca754 100644 --- a/networkconfig/beacon.go +++ b/networkconfig/beacon.go @@ -40,7 +40,7 @@ type BeaconConfig struct { BeaconName string SlotDuration time.Duration SlotsPerEpoch uint64 - EpochsPerSyncCommitteePeriod phase0.Epoch + EpochsPerSyncCommitteePeriod uint64 SyncCommitteeSize uint64 SyncCommitteeSubnetCount uint64 TargetAggregatorsPerSyncSubcommittee uint64 diff --git a/networkconfig/beacon_mock.go b/networkconfig/beacon_mock.go index 3fc2a0d538..b889e605cc 100644 --- a/networkconfig/beacon_mock.go +++ b/networkconfig/beacon_mock.go @@ -210,10 +210,10 @@ func (mr *MockBeaconMockRecorder) GetEpochFirstSlot(epoch any) *gomock.Call { } // GetEpochsPerSyncCommitteePeriod mocks base method. -func (m *MockBeacon) GetEpochsPerSyncCommitteePeriod() phase0.Epoch { +func (m *MockBeacon) GetEpochsPerSyncCommitteePeriod() uint64 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEpochsPerSyncCommitteePeriod") - ret0, _ := ret[0].(phase0.Epoch) + ret0, _ := ret[0].(uint64) return ret0 } diff --git a/networkconfig/holesky.go b/networkconfig/holesky.go index cedfd58623..2aa8a9bc30 100644 --- a/networkconfig/holesky.go +++ b/networkconfig/holesky.go @@ -4,6 +4,7 @@ import ( "math/big" "time" + "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" spectypes "github.com/ssvlabs/ssv-spec/types" diff --git a/networkconfig/hoodi.go b/networkconfig/hoodi.go index dc35d47308..31af8e0c95 100644 --- a/networkconfig/hoodi.go +++ b/networkconfig/hoodi.go @@ -4,6 +4,7 @@ import ( "math/big" "time" + "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" spectypes "github.com/ssvlabs/ssv-spec/types" diff --git a/networkconfig/local-testnet.go b/networkconfig/local-testnet.go index 573cde0929..752df8aea0 100644 --- a/networkconfig/local-testnet.go +++ b/networkconfig/local-testnet.go @@ -3,6 +3,7 @@ package networkconfig import ( "time" + "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" spectypes "github.com/ssvlabs/ssv-spec/types" diff --git a/networkconfig/mainnet.go b/networkconfig/mainnet.go index 3a959890f4..995ff156d2 100644 --- a/networkconfig/mainnet.go +++ b/networkconfig/mainnet.go @@ -4,6 +4,7 @@ import ( "math/big" "time" + "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" spectypes "github.com/ssvlabs/ssv-spec/types" diff --git a/networkconfig/network_mock.go b/networkconfig/network_mock.go index 68aa51f52b..85e12572dc 100644 --- a/networkconfig/network_mock.go +++ b/networkconfig/network_mock.go @@ -225,10 +225,10 @@ func (mr *MockNetworkMockRecorder) GetEpochFirstSlot(epoch any) *gomock.Call { } // GetEpochsPerSyncCommitteePeriod mocks base method. -func (m *MockNetwork) GetEpochsPerSyncCommitteePeriod() phase0.Epoch { +func (m *MockNetwork) GetEpochsPerSyncCommitteePeriod() uint64 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEpochsPerSyncCommitteePeriod") - ret0, _ := ret[0].(phase0.Epoch) + ret0, _ := ret[0].(uint64) return ret0 } diff --git a/networkconfig/test-network.go b/networkconfig/test-network.go index 00b6b238fa..aa9922026d 100644 --- a/networkconfig/test-network.go +++ b/networkconfig/test-network.go @@ -4,6 +4,7 @@ import ( "math/big" "time" + "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" spectypes "github.com/ssvlabs/ssv-spec/types" From f790d99450d2d8cc158b7fbbf561b0c03cfe155b Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 26 May 2025 13:22:12 -0300 Subject: [PATCH 38/38] pass network config name --- cli/operator/node.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/operator/node.go b/cli/operator/node.go index 4ee5962f52..bc31112b63 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -154,6 +154,7 @@ var StartNodeCmd = &cobra.Command{ } networkConfig := networkconfig.NetworkConfig{ + Name: cfg.SSVOptions.NetworkName, SSVConfig: ssvNetworkConfig, BeaconConfig: consensusClient.BeaconConfig(), }