From 9297a7b7212acf7bd0cb14a78a5796665c3823d7 Mon Sep 17 00:00:00 2001 From: Fornax <23104993+0xfornax@users.noreply.github.com> Date: Tue, 6 Oct 2026 00:02:22 -0300 Subject: [PATCH 1/6] Support the Plataberget network --- .../service/config/choice-wizard-step.go | 5 + rocketpool-cli/service/config/config-form.go | 12 +- .../service/config/network-clients_test.go | 107 +++++++++++++ .../service/config/settings-home.go | 4 +- .../service/config/standard-layout.go | 16 ++ .../service/config/step-external-cc.go | 4 +- .../service/config/step-local-cc.go | 4 +- .../service/config/step-local-ec.go | 4 +- .../service/config/step-native-cc.go | 4 +- rocketpool-cli/service/network-config_test.go | 56 +++++++ rocketpool-cli/service/service.go | 33 +++- rocketpool/node/node.go | 17 +- rocketpool/watchtower/watchtower.go | 11 +- .../beacon/client/slot-duration_test.go | 47 ++++++ .../services/beacon/client/std-http-client.go | 9 ++ shared/services/beacon/client/types.go | 1 + shared/services/config/network-clients.go | 145 ++++++++++++++++++ .../services/config/network-clients_test.go | 136 ++++++++++++++++ shared/services/config/networks-config.go | 8 +- .../services/config/networks-config_test.go | 2 +- shared/services/config/rocket-pool-config.go | 20 ++- shared/services/ec-manager.go | 2 + shared/services/rewards/ssz_types/json.go | 5 +- .../rewards/ssz_types/network_test.go | 21 +++ .../assets/install/networks-default.yml | 37 ++++- shared/services/rocketpool/client.go | 9 ++ .../services/rocketpool/plataberget_test.go | 65 ++++++++ shared/services/services.go | 6 + shared/types/config/network.go | 7 + 29 files changed, 758 insertions(+), 39 deletions(-) create mode 100644 rocketpool-cli/service/config/network-clients_test.go create mode 100644 rocketpool-cli/service/network-config_test.go create mode 100644 shared/services/beacon/client/slot-duration_test.go create mode 100644 shared/services/config/network-clients.go create mode 100644 shared/services/config/network-clients_test.go create mode 100644 shared/services/rewards/ssz_types/network_test.go create mode 100644 shared/services/rocketpool/plataberget_test.go diff --git a/rocketpool-cli/service/config/choice-wizard-step.go b/rocketpool-cli/service/config/choice-wizard-step.go index 19ecc58569..a3649efa29 100644 --- a/rocketpool-cli/service/config/choice-wizard-step.go +++ b/rocketpool-cli/service/config/choice-wizard-step.go @@ -6,6 +6,7 @@ type choiceWizardStep struct { wiz *wizard modal *choiceModalLayout showImpl func(*choiceModalLayout) + rebuild func() *choiceWizardStep } func newChoiceStep(wiz *wizard, currentStep int, totalSteps int, helperText string, names []string, descriptions []string, width int, title string, direction int, showImpl func(*choiceModalLayout), done func(int, string), back func(), pageID string) *choiceWizardStep { @@ -40,5 +41,9 @@ func newChoiceStep(wiz *wizard, currentStep int, totalSteps int, helperText stri } func (step *choiceWizardStep) show() { + // Client choices depend on the network selected since the wizard was built. + if step.rebuild != nil { + *step = *step.rebuild() + } step.showImpl(step.modal) } diff --git a/rocketpool-cli/service/config/config-form.go b/rocketpool-cli/service/config/config-form.go index 5642b42b4d..3debc4affa 100644 --- a/rocketpool-cli/service/config/config-form.go +++ b/rocketpool-cli/service/config/config-form.go @@ -155,20 +155,20 @@ func createParameterizedStringField(param *cfgtypes.Parameter) *parameterizedFor func createParameterizedDropDown(param *cfgtypes.Parameter, descriptionBox *tview.TextView) *parameterizedFormItem { // Create the list of options options := []string{} - descriptions := []string{} - values := []interface{}{} for _, option := range param.Options { options = append(options, option.Name) - descriptions = append(descriptions, option.Description) - values = append(values, option.Value) } item := NewDropDown(). SetLabel(param.Name). SetOptions(options, func(text string, index int) { - param.Value = values[index] + if index >= 0 && index < len(param.Options) { + param.Value = param.Options[index].Value + } }). SetChangedFunc(func(index int, mainText, secondaryText string, shortcut rune) { - descriptionBox.SetText(descriptions[index]) + if index >= 0 && index < len(param.Options) { + descriptionBox.SetText(param.Options[index].Description) + } }) item.SetTextOptions(" ", " ", "", "", "") list := item.GetList() diff --git a/rocketpool-cli/service/config/network-clients_test.go b/rocketpool-cli/service/config/network-clients_test.go new file mode 100644 index 0000000000..e555334e98 --- /dev/null +++ b/rocketpool-cli/service/config/network-clients_test.go @@ -0,0 +1,107 @@ +package config + +import ( + "reflect" + "strings" + "testing" + + "github.com/rivo/tview" + serviceconfig "github.com/rocket-pool/smartnode/shared/services/config" + cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" +) + +func TestWizardRefreshesNetworkClientChoices(t *testing.T) { + cfg, err := serviceconfig.NewRocketPoolConfig(t.TempDir(), false) + if err != nil { + t.Fatal(err) + } + md := &MainDisplay{Config: cfg, app: tview.NewApplication(), pages: tview.NewPages(), navHeader: tview.NewTextView(), isNew: true} + wiz := newWizard(md) + for _, network := range []string{"plataberget", "testnet", "plataberget"} { + // Use the network wizard action to exercise a change after modals exist. + for i, option := range cfg.Smartnode.Network.Options { + if string(option.Value.(cfgtypes.Network)) == network { + wiz.networkModal.modal.done(i, option.Name) + break + } + } + for _, tc := range []struct { + step *choiceWizardStep + plataberget []string + }{ + {wiz.executionLocalModal, []string{"Random (Recommended)", "Nethermind"}}, + {wiz.consensusLocalModal, []string{"Random (Recommended)", "Lodestar", "Nimbus", "Teku"}}, + {wiz.consensusExternalSelectModal, []string{"Lodestar", "Nimbus", "Teku"}}, + {wiz.nativeCcModal, []string{"Lodestar", "Nimbus", "Teku"}}, + } { + tc.step.show() + var labels []string + for _, form := range tc.step.modal.forms { + for i := 0; i < form.GetButtonCount(); i++ { + labels = append(labels, strings.TrimSpace(form.GetButton(i).GetLabel())) + } + } + if network == "plataberget" && !reflect.DeepEqual(labels, tc.plataberget) { + t.Fatalf("stale wizard choices: %v, want %v", labels, tc.plataberget) + } + if network == "testnet" && len(labels) < 5 { + t.Fatalf("Hoodi choices not restored: %v", labels) + } + } + } +} + +func TestSettingsRefreshesNetworkClientChoices(t *testing.T) { + cfg, err := serviceconfig.NewRocketPoolConfig(t.TempDir(), true) + if err != nil { + t.Fatal(err) + } + layout := newStandardLayout() + layout.createForm(&cfg.Smartnode.Network, "Native client") + items := createParameterizedFormItems([]*cfgtypes.Parameter{&cfg.Native.ConsensusClient}, layout) + layout.mapParameterizedFormItems(items...) + layout.form.AddFormItem(items[0].item) + dropDown := items[0].item.(*DropDown) + for _, network := range []cfgtypes.Network{"plataberget", "testnet", "plataberget"} { + cfg.ChangeNetwork(network) + layout.refresh() + for i, option := range cfg.Native.ConsensusClient.Options { + if dropDown.options[i].Text != option.Name { + t.Fatal("settings dropdown has stale options") + } + dropDown.SetCurrentOption(i) + if cfg.Native.ConsensusClient.Value != option.Value { + t.Fatalf("selected %s but saved %v", option.Name, cfg.Native.ConsensusClient.Value) + } + if strings.TrimSpace(layout.descriptionBox.GetText(false)) != option.Description { + t.Fatalf("%s/%s: description %q, want %q", network, option.Name, layout.descriptionBox.GetText(false), option.Description) + } + } + } +} + +func TestSettingsPageRebuildsAfterNetworkChange(t *testing.T) { + cfg, err := serviceconfig.NewRocketPoolConfig(t.TempDir(), false) + if err != nil { + t.Fatal(err) + } + md := NewMainDisplay(tview.NewApplication(), nil, cfg, false, false, false) + cfg.ChangeNetwork("plataberget") + md.settingsHome.refresh() + ec := md.settingsHome.ecPage + if len(ec.ecDropdown.item.(*DropDown).options) != 1 { + t.Fatal("execution settings retained unsupported clients") + } + // The parameter section must switch from Geth to Nethermind as well. + foundNethermind := false + for i := 0; i < ec.layout.form.GetFormItemCount(); i++ { + for _, item := range ec.nethermindItems { + if ec.layout.form.GetFormItem(i) == item.item { + foundNethermind = true + } + } + } + if !foundNethermind { + t.Fatal("execution settings still show the previous client's parameters") + } +} diff --git a/rocketpool-cli/service/config/settings-home.go b/rocketpool-cli/service/config/settings-home.go index cd75c8b0ce..4d8a81049f 100644 --- a/rocketpool-cli/service/config/settings-home.go +++ b/rocketpool-cli/service/config/settings-home.go @@ -231,11 +231,11 @@ func (home *settingsHome) refresh() { } if home.ecPage != nil { - home.ecPage.layout.refresh() + home.ecPage.handleLayoutChanged() } if home.ccPage != nil { - home.ccPage.layout.refresh() + home.ccPage.handleLayoutChanged() } if home.fallbackPage != nil { diff --git a/rocketpool-cli/service/config/standard-layout.go b/rocketpool-cli/service/config/standard-layout.go index 7609efd7cc..4a63e6629f 100644 --- a/rocketpool-cli/service/config/standard-layout.go +++ b/rocketpool-cli/service/config/standard-layout.go @@ -132,6 +132,22 @@ func (layout *standardLayout) refresh() { formItem.(*tview.InputField).SetText(fmt.Sprint(param.Value)) case cfgtypes.ParameterType_Choice: + dropDown := formItem.(*DropDown) + optionsChanged := len(dropDown.options) != len(param.Options) + for i, option := range param.Options { + if i >= len(dropDown.options) || dropDown.options[i].Text != option.Name { + optionsChanged = true + break + } + } + if optionsChanged { + names := make([]string, len(param.Options)) + for i, option := range param.Options { + names[i] = option.Name + } + dropDown.SetOptions(names, dropDown.selected) + dropDown.currentOption = -1 + } for i := 0; i < len(param.Options); i++ { if param.Options[i].Value == param.Value { formItem.(*DropDown).SetCurrentOption(i) diff --git a/rocketpool-cli/service/config/step-external-cc.go b/rocketpool-cli/service/config/step-external-cc.go index 68e149f34c..a4822bf128 100644 --- a/rocketpool-cli/service/config/step-external-cc.go +++ b/rocketpool-cli/service/config/step-external-cc.go @@ -48,7 +48,7 @@ func createExternalCcStep(wiz *wizard, currentStep int, totalSteps int) *choiceW wiz.modeModal.show() } - return newChoiceStep( + step := newChoiceStep( wiz, currentStep, totalSteps, @@ -63,5 +63,7 @@ func createExternalCcStep(wiz *wizard, currentStep int, totalSteps int) *choiceW back, "step-external-cc", ) + step.rebuild = func() *choiceWizardStep { return createExternalCcStep(wiz, currentStep, totalSteps) } + return step } diff --git a/rocketpool-cli/service/config/step-local-cc.go b/rocketpool-cli/service/config/step-local-cc.go index 0704a618cf..d3e0b251d8 100644 --- a/rocketpool-cli/service/config/step-local-cc.go +++ b/rocketpool-cli/service/config/step-local-cc.go @@ -86,7 +86,7 @@ func createLocalCcStep(wiz *wizard, currentStep int, totalSteps int) *choiceWiza wiz.modeModal.show() } - return newChoiceStep( + step := newChoiceStep( wiz, currentStep, totalSteps, @@ -101,6 +101,8 @@ func createLocalCcStep(wiz *wizard, currentStep int, totalSteps int) *choiceWiza back, localCcStepID, ) + step.rebuild = func() *choiceWizardStep { return createLocalCcStep(wiz, currentStep, totalSteps) } + return step } diff --git a/rocketpool-cli/service/config/step-local-ec.go b/rocketpool-cli/service/config/step-local-ec.go index 16395262ea..bbe52e2a0c 100644 --- a/rocketpool-cli/service/config/step-local-ec.go +++ b/rocketpool-cli/service/config/step-local-ec.go @@ -79,7 +79,7 @@ func createLocalEcStep(wiz *wizard, currentStep int, totalSteps int) *choiceWiza wiz.modeModal.show() } - return newChoiceStep( + step := newChoiceStep( wiz, currentStep, totalSteps, @@ -94,6 +94,8 @@ func createLocalEcStep(wiz *wizard, currentStep int, totalSteps int) *choiceWiza back, "step-ec-local", ) + step.rebuild = func() *choiceWizardStep { return createLocalEcStep(wiz, currentStep, totalSteps) } + return step } // Get a random execution client diff --git a/rocketpool-cli/service/config/step-native-cc.go b/rocketpool-cli/service/config/step-native-cc.go index 7af3279417..c73d1bfa16 100644 --- a/rocketpool-cli/service/config/step-native-cc.go +++ b/rocketpool-cli/service/config/step-native-cc.go @@ -41,7 +41,7 @@ func createNativeCcStep(wiz *wizard, currentStep int, totalSteps int) *choiceWiz wiz.nativeEcModal.show() } - return newChoiceStep( + step := newChoiceStep( wiz, currentStep, totalSteps, @@ -56,5 +56,7 @@ func createNativeCcStep(wiz *wizard, currentStep int, totalSteps int) *choiceWiz back, "step-native-cc", ) + step.rebuild = func() *choiceWizardStep { return createNativeCcStep(wiz, currentStep, totalSteps) } + return step } diff --git a/rocketpool-cli/service/network-config_test.go b/rocketpool-cli/service/network-config_test.go new file mode 100644 index 0000000000..0b7d5fef23 --- /dev/null +++ b/rocketpool-cli/service/network-config_test.go @@ -0,0 +1,56 @@ +package service + +import ( + "context" + "strings" + "testing" + + "github.com/rocket-pool/smartnode/shared/services/config" + cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" + "github.com/urfave/cli/v3" +) + +func TestHeadlessNetworkClientSelection(t *testing.T) { + for _, tc := range []struct { + name, from, to, client string + wantError bool + }{ + {"defaults", "mainnet", "plataberget", "", false}, + {"supported", "testnet", "plataberget", "nethermind", false}, + {"unsupported", "mainnet", "plataberget", "geth", true}, + {"restore", "plataberget", "testnet", "geth", false}, + } { + t.Run(tc.name, func(t *testing.T) { + cfg, err := config.NewRocketPoolConfig(t.TempDir(), false) + if err != nil { + t.Fatal(err) + } + cfg.ChangeNetwork(cfgtypes.Network(tc.from)) + cmd := &cli.Command{ + Name: "config", + Flags: []cli.Flag{&cli.StringFlag{Name: "smartnode-network"}, &cli.StringFlag{Name: "executionClient"}}, + Action: func(_ context.Context, c *cli.Command) error { return updateConfigFromCliArgs(c, cfg) }, + } + args := []string{"config", "--smartnode-network", tc.to} + if tc.client != "" { + args = append(args, "--executionClient", tc.client) + } + err = cmd.Run(context.Background(), args) + if tc.wantError { + if err == nil || !strings.Contains(err.Error(), "valid options") { + t.Fatalf("unsupported client accepted: %v", err) + } + return + } + if err != nil { + t.Fatal(err) + } + if cfg.GetNetwork() != cfgtypes.Network(tc.to) { + t.Fatal("network was not changed") + } + if tc.to == "plataberget" && cfg.ExecutionClient.Value != cfgtypes.ExecutionClient_Nethermind { + t.Fatal("headless selection did not apply the network's default client") + } + }) + } +} diff --git a/rocketpool-cli/service/service.go b/rocketpool-cli/service/service.go index 1c7bd3799e..2fc2ad8869 100644 --- a/rocketpool-cli/service/service.go +++ b/rocketpool-cli/service/service.go @@ -228,6 +228,22 @@ func configureServiceHeadless(c *cli.Command) error { if err != nil { return err } + if err := updateConfigFromCliArgs(c, cfg); err != nil { + return err + } + return rp.SaveConfig(cfg) +} + +func updateConfigFromCliArgs(c *cli.Command, cfg *config.RocketPoolConfig) error { + // The selected network determines valid client choices and their defaults. + // Apply it before client arguments, independently of subconfig map order. + if c.IsSet("smartnode-network") { + network := cfgtypes.Network(c.String("smartnode-network")) + if cfg.LoadedNetworks().GetNetwork(network) == nil { + return fmt.Errorf("unknown network %q", network) + } + cfg.ChangeNetwork(network) + } // Root params for _, param := range cfg.GetParameters() { @@ -247,7 +263,7 @@ func configureServiceHeadless(c *cli.Command) error { } } - return rp.SaveConfig(cfg) + return cfg.ValidateNetworkClients() } // Configure the service @@ -285,6 +301,10 @@ func configureService(configPath string, isNative, yes bool, composeFiles []stri return fmt.Errorf("error saving config: %w", err) } fmt.Println("Your changes have been saved!") + if err := md.Config.ValidateNetworkForStart(); err != nil { + fmt.Println(err) + return nil + } // Exit immediately if we're in native mode if isNative { @@ -295,8 +315,10 @@ func configureService(configPath string, isNative, yes bool, composeFiles []stri // Handle network changes prefix := fmt.Sprint(md.PreviousConfig.Smartnode.ProjectName.Value) if md.ChangeNetworks { - // Remove the checkpoint sync provider - md.Config.ConsensusCommon.CheckpointSyncProvider.Value = "" + // Use a checkpoint provider for the new network, never the old chain. + if err := md.Config.ConsensusCommon.CheckpointSyncProvider.SetToDefault(md.Config.GetNetwork()); err != nil { + return err + } err = rp.SaveConfig(md.Config) if err != nil { return fmt.Errorf("error saving config: %w", err) @@ -304,7 +326,7 @@ func configureService(configPath string, isNative, yes bool, composeFiles []stri color.YellowPrintln("WARNING: You have requested to change networks.") fmt.Println() - color.YellowPrintln("All of your existing chain data, your node wallet, and your validator keys will be removed. If you had a Checkpoint Sync URL provided for your Consensus client, it will be removed and you will need to specify a different one that supports the new network.") + color.YellowPrintln("All of your existing chain data, your node wallet, and your validator keys will be removed. Your Checkpoint Sync URL will be reset to the new network's default.") fmt.Println() color.YellowPrintln("Please confirm you have backed up everything you want to keep, because it will be deleted if you answer `y` to the prompt below.") fmt.Println() @@ -632,6 +654,9 @@ func startService(params startServiceParams) error { if isNew { return fmt.Errorf("No configuration detected. Please run `rocketpool service config` to set up your Smart Node before running it.") } + if err := cfg.ValidateNetworkForStart(); err != nil { + return err + } // Warn if IPv6 is enabled but no public IPv6 address is available if cfg.IsIPv6Enabled() && cfg.GetExternalIpv6() == "" { diff --git a/rocketpool/node/node.go b/rocketpool/node/node.go index 7a891f9879..a02fdd8044 100644 --- a/rocketpool/node/node.go +++ b/rocketpool/node/node.go @@ -79,8 +79,16 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { // Run daemon func run(c *cli.Command) error { + cfg, err := services.GetConfig(c) + if err != nil { + return err + } + if err := cfg.ValidateNetworkForStart(); err != nil { + return err + } + // Handle the initial fee recipient file deployment - err := deployDefaultFeeRecipientFile(c) + err = deployDefaultFeeRecipientFile(c) if err != nil { return err } @@ -94,13 +102,6 @@ func run(c *cli.Command) error { // Configure configureHTTP() - // Load config early so we can start the HTTP API server before blocking - // on wallet/service readiness. - cfg, err := services.GetConfig(c) - if err != nil { - return err - } - // Print the current mode if cfg.IsNativeMode { fmt.Println("Starting node daemon in Native Mode.") diff --git a/rocketpool/watchtower/watchtower.go b/rocketpool/watchtower/watchtower.go index db4565826e..23d29af924 100644 --- a/rocketpool/watchtower/watchtower.go +++ b/rocketpool/watchtower/watchtower.go @@ -71,6 +71,13 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { // Run daemon func run(c *cli.Command) error { + cfg, err := services.GetConfig(c) + if err != nil { + return err + } + if err := cfg.ValidateNetworkForStart(); err != nil { + return err + } // Configure configureHTTP() @@ -115,10 +122,6 @@ func run(c *cli.Command) error { } // Get services - cfg, err := services.GetConfig(c) - if err != nil { - return err - } rp, err := services.GetRocketPool(c) if err != nil { return err diff --git a/shared/services/beacon/client/slot-duration_test.go b/shared/services/beacon/client/slot-duration_test.go new file mode 100644 index 0000000000..21dd6aa143 --- /dev/null +++ b/shared/services/beacon/client/slot-duration_test.go @@ -0,0 +1,47 @@ +package client + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" +) + +func TestBeaconSlotDuration(t *testing.T) { + for _, tc := range []struct { + name, fields string + wantError bool + }{ + {"legacy", `"SECONDS_PER_SLOT":"12"`, false}, + {"plataberget", `"SLOT_DURATION_MS":"12000"`, false}, + {"both", `"SECONDS_PER_SLOT":"12","SLOT_DURATION_MS":"12000"`, false}, + {"missing", `"SLOT_DURATION_MS":"0"`, true}, + {"fractional", `"SLOT_DURATION_MS":"12500"`, true}, + } { + t.Run(tc.name, func(t *testing.T) { + eth2ConfigCache.Store(nil) + t.Cleanup(func() { eth2ConfigCache.Store(nil) }) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == RequestEth2ConfigPath { + fmt.Fprintf(w, `{"data":{%s,"SLOTS_PER_EPOCH":"32","GLOAS_FORK_EPOCH":"1536"}}`, tc.fields) + } else { + fmt.Fprint(w, `{"data":{"genesis_time":"1786622400","genesis_fork_version":"0x10733183","genesis_validators_root":"0xbb4a1a9e3f7f4e10edcd734e4acc3b5ffd4f830efe0af2748fa458cfee5d2658"}}`) + } + })) + defer server.Close() + cfg, err := NewStandardHttpClient(server.URL).GetEth2Config() + if tc.wantError { + if err == nil { + t.Fatal("invalid slot duration accepted") + } + return + } + if err != nil { + t.Fatal(err) + } + if cfg.SecondsPerSlot != 12 || cfg.SecondsPerEpoch != 384 || cfg.GloasForkEpoch != 1536 { + t.Fatalf("incorrect slot/fork config: %+v", cfg) + } + }) + } +} diff --git a/shared/services/beacon/client/std-http-client.go b/shared/services/beacon/client/std-http-client.go index f501d841a2..177be12bf3 100644 --- a/shared/services/beacon/client/std-http-client.go +++ b/shared/services/beacon/client/std-http-client.go @@ -838,6 +838,15 @@ func (c *StandardHttpClient) getEth2Config() (Eth2ConfigResponse, error) { if err := json.Unmarshal(responseBody, ð2Config); err != nil { return Eth2ConfigResponse{}, fmt.Errorf("Could not decode eth2 config: %w", err) } + // Glamsterdam clients may expose SLOT_DURATION_MS instead of SECONDS_PER_SLOT. + // Internal slot arithmetic uses whole seconds; never silently round a duration. + if eth2Config.Data.SecondsPerSlot == 0 { + milliseconds := eth2Config.Data.SlotDurationMs + if milliseconds == 0 || milliseconds%1000 != 0 { + return Eth2ConfigResponse{}, fmt.Errorf("unsupported beacon slot duration: SECONDS_PER_SLOT=%d, SLOT_DURATION_MS=%d", eth2Config.Data.SecondsPerSlot, milliseconds) + } + eth2Config.Data.SecondsPerSlot = milliseconds / 1000 + } return eth2Config, nil } diff --git a/shared/services/beacon/client/types.go b/shared/services/beacon/client/types.go index cff7b06792..659268e73d 100644 --- a/shared/services/beacon/client/types.go +++ b/shared/services/beacon/client/types.go @@ -42,6 +42,7 @@ type SyncStatusResponse struct { type Eth2ConfigResponse struct { Data struct { SecondsPerSlot uinteger `json:"SECONDS_PER_SLOT"` + SlotDurationMs uinteger `json:"SLOT_DURATION_MS"` SlotsPerEpoch uinteger `json:"SLOTS_PER_EPOCH"` CapellaForkVersion byteArray `json:"CAPELLA_FORK_VERSION"` CapellaForkEpoch *uinteger `json:"CAPELLA_FORK_EPOCH"` diff --git a/shared/services/config/network-clients.go b/shared/services/config/network-clients.go new file mode 100644 index 0000000000..ea213b2816 --- /dev/null +++ b/shared/services/config/network-clients.go @@ -0,0 +1,145 @@ +package config + +import ( + "errors" + "fmt" + "slices" + + "github.com/rocket-pool/smartnode/shared/types/config" +) + +func validateClientList[T ~string](name string, clients, known []T) error { + seen := make(map[T]bool) + for _, client := range clients { + if !slices.Contains(known, client) || seen[client] { + return fmt.Errorf("invalid or duplicate %s client %q", name, client) + } + seen[client] = true + } + return nil +} + +func validateNetworkClients(n *config.NetworkInfo) error { + return errors.Join( + validateClientList("execution", n.ExecutionClients, []config.ExecutionClient{ + config.ExecutionClient_Geth, config.ExecutionClient_Nethermind, config.ExecutionClient_Besu, + config.ExecutionClient_Reth, config.ExecutionClient_Erigon, + }), + validateClientList("consensus", n.ConsensusClients, []config.ConsensusClient{ + config.ConsensusClient_Lighthouse, config.ConsensusClient_Lodestar, config.ConsensusClient_Nimbus, + config.ConsensusClient_Prysm, config.ConsensusClient_Teku, + }), + ) +} + +func supportsNetworkClient(n *config.NetworkInfo, client any) bool { + if n == nil { + return true + } + switch client := client.(type) { + case config.ExecutionClient: + return len(n.ExecutionClients) == 0 || slices.Contains(n.ExecutionClients, client) + case config.ConsensusClient: + return len(n.ConsensusClients) == 0 || slices.Contains(n.ConsensusClients, client) + default: + return false + } +} + +func (cfg *RocketPoolConfig) initializeNetworkClientDefaults() { + cfg.clientOptions = make(map[*config.Parameter][]config.ParameterOption) + for _, param := range []*config.Parameter{&cfg.ExecutionClient, &cfg.ConsensusClient, &cfg.ExternalConsensusClient, &cfg.Native.ConsensusClient} { + cfg.clientOptions[param] = param.Options + for _, network := range cfg.networks.AllNetworks() { + if supportsNetworkClient(network, param.Default[config.Network_All]) { + continue + } + for _, option := range param.Options { + if supportsNetworkClient(network, option.Value) { + param.Default[network.ID()] = option.Value + break + } + } + } + } + for _, network := range cfg.networks.AllNetworks() { + if network.CheckpointSyncUrl != "" { + cfg.ConsensusCommon.CheckpointSyncProvider.Default[network.ID()] = network.CheckpointSyncUrl + } + if !network.SupportsMevBoost { + cfg.EnableMevBoost.Default[network.ID()] = false + } + } +} + +// Refresh choices after loading or switching networks. Only an interactive network +// switch replaces an unsupported selection; loading a saved config preserves it. +func (cfg *RocketPoolConfig) refreshNetworkClientOptions(resetUnsupported bool) { + network := cfg.GetNetworkInfo() + for param, allOptions := range cfg.clientOptions { + param.Options = nil + for _, option := range allOptions { + if supportsNetworkClient(network, option.Value) { + param.Options = append(param.Options, option) + } + } + if resetUnsupported && !supportsNetworkClient(network, param.Value) { + _ = param.SetToDefault(cfg.GetNetwork()) + } + } +} + +// ValidateNetworkClients also covers saved/headless settings that bypass the TUI. +// External execution endpoints have no client-family setting to validate. +func (cfg *RocketPoolConfig) ValidateNetworkClients() error { + network := cfg.GetNetworkInfo() + if network == nil { + return fmt.Errorf("unknown network %q", cfg.GetNetwork()) + } + var params []*config.Parameter + if cfg.IsNativeMode { + params = append(params, &cfg.Native.ConsensusClient) + } else { + if cfg.ExecutionClientMode.Value == config.Mode_Local { + params = append(params, &cfg.ExecutionClient) + } + if cfg.ConsensusClientMode.Value == config.Mode_Local { + params = append(params, &cfg.ConsensusClient) + } else { + params = append(params, &cfg.ExternalConsensusClient) + } + } + var problems []error + for _, param := range params { + if !supportsNetworkClient(network, param.Value) { + problems = append(problems, fmt.Errorf("%s %q is not supported on %s", param.Name, param.Value, network.Label)) + } + } + if !network.SupportsMevBoost && (cfg.EnableMevBoost.Value == true || cfg.EnableCommitBoost.Value == true) { + problems = append(problems, fmt.Errorf("MEV-Boost and Commit-Boost are not supported on %s", network.Label)) + } + return errors.Join(problems...) +} + +// RequireRocketPoolContracts prevents accidentally using the zero storage address. +func (cfg *RocketPoolConfig) RequireRocketPoolContracts() error { + network := cfg.GetNetworkInfo() + if network == nil { + return fmt.Errorf("unknown network %q", cfg.GetNetwork()) + } + if network.ContractsPending || network.Addresses.Storage == "" { + return fmt.Errorf("Rocket Pool contracts are not configured for %s; supply the deployment addresses and set contractsPending to false in the network definition before starting Rocket Pool", network.Label) + } + return nil +} + +// ValidateNetworkForStart allows pending networks to be saved, but not started. +func (cfg *RocketPoolConfig) ValidateNetworkForStart() error { + if err := cfg.ValidateNetworkClients(); err != nil { + return err + } + if cfg.GetNetworkInfo().ContractsPending { + return cfg.RequireRocketPoolContracts() + } + return nil +} diff --git a/shared/services/config/network-clients_test.go b/shared/services/config/network-clients_test.go new file mode 100644 index 0000000000..14b06b6246 --- /dev/null +++ b/shared/services/config/network-clients_test.go @@ -0,0 +1,136 @@ +package config + +import ( + "reflect" + "strings" + "testing" + + "github.com/rocket-pool/smartnode/shared/types/config" +) + +func optionValues(param *config.Parameter) []any { + values := make([]any, len(param.Options)) + for i, option := range param.Options { + values[i] = option.Value + } + return values +} + +func TestPlatabergetConfiguration(t *testing.T) { + cfg := mustNewRocketPoolConfig(t, "", false) + cfg.ExecutionClient.Value = config.ExecutionClient_Besu + cfg.ConsensusClient.Value = config.ConsensusClient_Prysm + cfg.EnableCommitBoost.Value = true + cfg.ChangeNetwork("plataberget") + if got := cfg.Smartnode.GetChainID(); got != 7091047534 { + t.Fatalf("chain ID: %d", got) + } + if cfg.Smartnode.GetBeaconNetwork() != "plataberget" || cfg.Smartnode.GetCustomChainConfigDir() != "" { + t.Fatal("Plataberget must use a named network without custom chain files") + } + if cfg.ExecutionClient.Value != config.ExecutionClient_Nethermind || cfg.ConsensusClient.Value != config.ConsensusClient_Nimbus { + t.Fatalf("unsupported selections were not replaced: %v / %v", cfg.ExecutionClient.Value, cfg.ConsensusClient.Value) + } + if cfg.EnableMevBoost.Value != false || cfg.EnableCommitBoost.Value != false { + t.Fatal("PBS must be disabled") + } + if cfg.ConsensusCommon.CheckpointSyncProvider.Value != "https://checkpoint-sync.plataberget.ethpandaops.io" { + t.Fatal("wrong checkpoint sync default") + } + if problems := cfg.Validate(); len(problems) != 0 { + t.Fatalf("pending contracts must not prevent saving settings: %v", problems) + } + if err := cfg.ValidateNetworkForStart(); err == nil || !strings.Contains(err.Error(), "contracts are not configured") { + t.Fatalf("startup should report pending contracts: %v", err) + } + if cfg.Smartnode.GetStorageAddress() != "" || cfg.GetNetworkInfo().Addresses.Reth != "" { + t.Fatal("contract addresses must remain unset") + } + wantEC := []any{config.ExecutionClient_Nethermind} + wantCC := []any{config.ConsensusClient_Lodestar, config.ConsensusClient_Nimbus, config.ConsensusClient_Teku} + for _, candidate := range []*RocketPoolConfig{cfg, cfg.CreateCopy()} { + if !reflect.DeepEqual(optionValues(&candidate.ExecutionClient), wantEC) { + t.Fatalf("execution choices: %v", optionValues(&candidate.ExecutionClient)) + } + for _, param := range []*config.Parameter{&candidate.ConsensusClient, &candidate.ExternalConsensusClient, &candidate.Native.ConsensusClient} { + if !reflect.DeepEqual(optionValues(param), wantCC) { + t.Fatalf("%s choices: %v", param.ID, optionValues(param)) + } + } + } + loaded := mustNewRocketPoolConfig(t, "", false) + if err := loaded.Deserialize(cfg.Serialize()); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(optionValues(&loaded.ConsensusClient), wantCC) || loaded.GetNetwork() != "plataberget" { + t.Fatal("saved network restrictions were lost") + } + loaded.ChangeNetwork("testnet") + if len(loaded.ExecutionClient.Options) != 5 || len(loaded.ConsensusClient.Options) != 5 || len(loaded.Native.ConsensusClient.Options) != 5 { + t.Fatal("switching to Hoodi did not restore unrestricted choices") + } + if loaded.Smartnode.GetBeaconNetwork() != "hoodi" || loaded.ConsensusCommon.CheckpointSyncProvider.Value != "" { + t.Fatal("Hoodi defaults were not restored") + } +} + +func TestPlatabergetSavedUnsupportedClients(t *testing.T) { + for _, mode := range []string{"local", "external", "native"} { + t.Run(mode, func(t *testing.T) { + cfg := mustNewRocketPoolConfig(t, "", mode == "native") + cfg.ChangeNetwork("plataberget") + switch mode { + case "local": + cfg.ExecutionClient.Value = config.ExecutionClient_Geth + case "external": + cfg.ExecutionClientMode.Value = config.Mode_External + cfg.ConsensusClientMode.Value = config.Mode_External + cfg.ExternalConsensusClient.Value = config.ConsensusClient_Lighthouse + case "native": + cfg.Native.ConsensusClient.Value = config.ConsensusClient_Prysm + } + loaded := mustNewRocketPoolConfig(t, "", false) + if err := loaded.Deserialize(cfg.Serialize()); err != nil { + t.Fatal(err) + } + if err := loaded.ValidateNetworkClients(); err == nil || !strings.Contains(err.Error(), "not supported on Platåberget") { + t.Fatalf("unsupported saved client accepted: %v", err) + } + }) + } +} + +func TestPlatabergetSupportedClientsAndDeployment(t *testing.T) { + cfg := mustNewRocketPoolConfig(t, "", false) + cfg.ChangeNetwork("plataberget") + // A deployment replaces the pending network entry; never ship this fixture address. + network := cfg.GetNetworkInfo() + network.Addresses.Storage = "0x1111111111111111111111111111111111111111" + if err := cfg.ValidateNetworkForStart(); err == nil { + t.Fatal("pending flag must still block partial deployments") + } + network.ContractsPending = false + for _, cc := range []config.ConsensusClient{config.ConsensusClient_Lodestar, config.ConsensusClient_Nimbus, config.ConsensusClient_Teku} { + cfg.ConsensusClient.Value = cc + if err := cfg.ValidateNetworkForStart(); err != nil { + t.Fatalf("%s: %v", cc, err) + } + } + cfg.EnableMevBoost.Value = true + if err := cfg.ValidateNetworkClients(); err == nil { + t.Fatal("unsupported PBS configuration accepted") + } +} + +func TestNetworkClientSchemaValidation(t *testing.T) { + for _, extra := range []string{ + "executionClients: [unknown]", "consensusClients: [unknown]", + "executionClients: [nethermind, nethermind]", "consensusClients: [teku, teku]", + "isProduction: true", "contractsPending: false", + } { + body := "version: 1\nnetworks:\n - name: pending\n label: Pending\n description: Pending network\n chainID: 123\n beaconNetwork: pending\n contractsPending: true\n " + extra + "\n" + if _, err := parseNetworksYAML([]byte(body), "test", true); err == nil { + t.Fatalf("invalid network accepted: %s", extra) + } + } +} diff --git a/shared/services/config/networks-config.go b/shared/services/config/networks-config.go index bc046cb4b7..104041b67a 100644 --- a/shared/services/config/networks-config.go +++ b/shared/services/config/networks-config.go @@ -176,7 +176,10 @@ func validateNetworks(networks []*config.NetworkInfo, source string, requireStor if n.Addresses.Storage != "" && !common.IsHexAddress(n.Addresses.Storage) { return fmt.Errorf("%s: network %q has invalid storage address %q", source, n.Name, n.Addresses.Storage) } - if requireStorage && n.Addresses.Storage == "" { + if n.ContractsPending && n.IsProduction { + return fmt.Errorf("%s: production network %q cannot have contractsPending", source, n.Name) + } + if requireStorage && n.Addresses.Storage == "" && !n.ContractsPending { return fmt.Errorf("%s: network %q is missing a valid storage address", source, n.Name) } if n.Label == "" || n.Description == "" { @@ -194,6 +197,9 @@ func validateNetworks(networks []*config.NetworkInfo, source string, requireStor if err := validateMevRelays(n.MevRelays, source, n.Name); err != nil { return err } + if err := validateNetworkClients(n); err != nil { + return fmt.Errorf("%s: network %q: %w", source, n.Name, err) + } if n.Default { defaults++ } diff --git a/shared/services/config/networks-config_test.go b/shared/services/config/networks-config_test.go index 4497d92b86..c9ce4b1af5 100644 --- a/shared/services/config/networks-config_test.go +++ b/shared/services/config/networks-config_test.go @@ -14,7 +14,7 @@ func TestLoadEmbeddedOfficialNetworks(t *testing.T) { t.Fatal(err) } - for _, name := range []config.Network{"mainnet", "testnet", "devnet"} { + for _, name := range []config.Network{"mainnet", "testnet", "devnet", "plataberget"} { if networks.GetNetwork(name) == nil { t.Fatalf("missing official network %s", name) } diff --git a/shared/services/config/rocket-pool-config.go b/shared/services/config/rocket-pool-config.go index 9b7a783e67..9c993eda56 100644 --- a/shared/services/config/rocket-pool-config.go +++ b/shared/services/config/rocket-pool-config.go @@ -67,9 +67,10 @@ type RocketPoolConfig struct { // IsCLI is true when this config was loaded by the rocketpool CLI (or TUI) not by the node daemon. IsCLI bool `yaml:"-"` - networks *NetworksConfig `yaml:"-"` - imagesMainnet *ImageCatalog `yaml:"-"` - imagesByNetwork map[config.Network]*ImageCatalog `yaml:"-"` + networks *NetworksConfig `yaml:"-"` + imagesMainnet *ImageCatalog `yaml:"-"` + imagesByNetwork map[config.Network]*ImageCatalog `yaml:"-"` + clientOptions map[*config.Parameter][]config.ParameterOption `yaml:"-"` // Execution client settings ExecutionClientMode config.Parameter `yaml:"executionClientMode,omitempty"` @@ -624,6 +625,7 @@ func newRocketPoolConfig(rpDir string, isNativeMode bool, networks *NetworksConf // Addons cfg.GraffitiWallWriter = addons.NewGraffitiWallWriter(mainnet.Must(ImageGWW)) cfg.RescueNode = addons.NewRescueNode() + cfg.initializeNetworkClientDefaults() // Apply the default values for the default network from YAML cfg.Smartnode.Network.Value = cfg.networks.DefaultNetwork() @@ -631,6 +633,7 @@ func newRocketPoolConfig(rpDir string, isNativeMode bool, networks *NetworksConf if err != nil { return nil, fmt.Errorf("error applying default settings: %w", err) } + cfg.refreshNetworkClientOptions(false) return cfg, nil } @@ -682,6 +685,7 @@ func (cfg *RocketPoolConfig) CreateCopy() *RocketPoolConfig { } } + newConfig.refreshNetworkClientOptions(false) return newConfig } @@ -775,6 +779,11 @@ func (cfg *RocketPoolConfig) ChangeNetwork(newNetwork config.Network) { param.ChangeNetwork(oldNetwork, newNetwork) } } + cfg.refreshNetworkClientOptions(true) + if !cfg.SupportsMevBoost() { + cfg.EnableMevBoost.Value = false + cfg.EnableCommitBoost.Value = false + } } @@ -991,6 +1000,8 @@ func (cfg *RocketPoolConfig) Deserialize(masterMap map[string]map[string]string) } } } + // Keep explicitly saved unsupported selections so validation can report them. + cfg.refreshNetworkClientOptions(false) return nil } @@ -1796,6 +1807,9 @@ func (cfg *RocketPoolConfig) GetChanges(oldConfig *RocketPoolConfig) (map[string // Checks to see if the current configuration is valid; if not, returns a list of errors func (cfg *RocketPoolConfig) Validate() []string { errors := []string{} + if err := cfg.ValidateNetworkClients(); err != nil { + errors = append(errors, err.Error()) + } if cfg.GetNetworkInfo() == nil { errors = append(errors, fmt.Sprintf("Unknown network %q. Add it to networks-extra.yml or pick an official network.", cfg.GetNetwork())) diff --git a/shared/services/ec-manager.go b/shared/services/ec-manager.go index b029181d5d..90c14ef966 100644 --- a/shared/services/ec-manager.go +++ b/shared/services/ec-manager.go @@ -519,6 +519,8 @@ func getNetworkNameFromId(networkId uint) string { return "Ethereum Mainnet" case 560048: return "Hoodi Testnet" + case 7091047534: + return "Platåberget Testnet" default: return "Unknown Network" } diff --git a/shared/services/rewards/ssz_types/json.go b/shared/services/rewards/ssz_types/json.go index e2ff355d2d..889aa058c1 100644 --- a/shared/services/rewards/ssz_types/json.go +++ b/shared/services/rewards/ssz_types/json.go @@ -11,8 +11,9 @@ import ( ) var networkMap = map[string]Network{ - "mainnet": 1, - "testnet": 560048, + "mainnet": 1, + "testnet": 560048, + "plataberget": 7091047534, } // internal use only diff --git a/shared/services/rewards/ssz_types/network_test.go b/shared/services/rewards/ssz_types/network_test.go new file mode 100644 index 0000000000..13f3f869f6 --- /dev/null +++ b/shared/services/rewards/ssz_types/network_test.go @@ -0,0 +1,21 @@ +package ssz_types + +import ( + "encoding/json" + "testing" +) + +func TestPlatabergetNetworkRoundTrip(t *testing.T) { + network, ok := NetworkFromString("plataberget") + if !ok || network != 7091047534 { + t.Fatalf("wrong rewards network: %d, %v", network, ok) + } + encoded, err := json.Marshal(network) + if err != nil || string(encoded) != `"plataberget"` { + t.Fatalf("marshal network: %s, %v", encoded, err) + } + var decoded Network + if err := json.Unmarshal(encoded, &decoded); err != nil || decoded != network { + t.Fatalf("unmarshal network: %d, %v", decoded, err) + } +} diff --git a/shared/services/rocketpool/assets/install/networks-default.yml b/shared/services/rocketpool/assets/install/networks-default.yml index ca4eb3f886..dd736b1001 100644 --- a/shared/services/rocketpool/assets/install/networks-default.yml +++ b/shared/services/rocketpool/assets/install/networks-default.yml @@ -7,6 +7,8 @@ # appended and appear in `rocketpool service config`. # Extra networks may omit addresses to run execution/consensus clients on a # chain that has no Rocket Pool contracts. +# Retain replaced exit contracts under addresses.previousRocketNetworkExit so +# their outstanding requests remain discoverable after an upgrade. version: 1 networks: - name: mainnet @@ -67,6 +69,7 @@ networks: - "0xEE4d2A71cF479e0D3d0c3c2C923dbfEB57E73111" previousRocketDAOProtocolVerifier: - "0xd1f7e573cdC64FC0B201ca37aB50bC7Dd880040A" + previousRocketNetworkExit: [] optimismPriceMessenger: "0x12759f8Df234f8f2cDdb3d2Ed5604adF9ACCfc9F" polygonPriceMessenger: "0xb1029Ac2Be4e08516697093e2AFeC435057f3511" arbitrumPriceMessenger: "0x05330300f829AD3fC8f33838BC88CFC4093baD53" @@ -118,6 +121,7 @@ networks: reth: "0x7322c24752f79c05FFD1E2a6FCB97020C1C264F1" previousRewardsPools: - "0x4a625C617a44E60F74E3fe3bf6d6333b63766e91" + previousRocketNetworkExit: [] rplTwapPool: "0x0ca239d8AC5E49E3203d60eaf86Baa6712E5b454" multicall: "0xc5fA61aA6Ec012d1A2Ea38f31ADAf4D06c8725E7" balancebatcher: "0xB80b500CF68a956b6f149F1C48E8F07EEF4486Ce" @@ -149,9 +153,34 @@ networks: titanRegional: https://0xaa58208899c6105603b74396734a6263cc7d947f444f396a90f7b7d3e65d102aec7e5e5291b27e08d02c50a050825c2f@hoodi.titanrelay.xyz addresses: storage: "0xb4B46bdAA835F8E4b4d8e208B6559cD267851051" - rocketSignerRegistry: "0x8f22b1Cd26efe83ADf8Da87789fB66EeD917FCAa" + rocketSignerRegistry: "0xdF321a81D594c6eD056E0DC3CB74AAEE4DcD91Fb" rplToken: "0x2b45cD38B213Bbd3A1A848bf2467927c976877Cb" reth: "0x80741a37E3644612F0465145C9709a90B6D77Ee3" - rplTwapPool: "0xdF321a81D594c6eD056E0DC3CB74AAEE4DcD91Fb" - multicall: "0x2f06c1dA6987BfD39A0539c03F9274fFb1F5fa19" - balancebatcher: "0x1a0E9eF9Cc41f4CcD377979C0e6DC8dBe4E2858C" + previousRocketNetworkExit: [] + rplTwapPool: "0xdC9B41F5aCEc76f842A1d31CDeC3EE528a0aD57c" + multicall: "0x1a0E9eF9Cc41f4CcD377979C0e6DC8dBe4E2858C" + balancebatcher: "0x8f22b1Cd26efe83ADf8Da87789fB66EeD917FCAa" + + # Named-network presets only: Nethermind >=2.0.0 (>=2.1.0 for archive), + # Lodestar >=1.47.0, Nimbus >=26.8.0, Teku >=26.9.0. + # Current mainnet.env pins meet these requirements; no custom-chain directory. + - name: plataberget + label: Platåberget Testnet + description: | + Ethereum's Glamsterdam testnet (glamsterdam-devnet-8), using test ETH. + Supported clients: Nethermind with Lodestar, Nimbus, or Teku. + Rocket Pool contract addresses are pending; configuration is available but service startup is disabled until deployment. + chainID: 7091047534 + beaconNetwork: plataberget + beaconExplorerUrl: https://dora.plataberget.ethpandaops.io + checkpointSyncUrl: https://checkpoint-sync.plataberget.ethpandaops.io + clientTagSet: test + executionClients: [nethermind] + consensusClients: [lodestar, nimbus, teku] + contractsPending: true + supportsMevBoost: false + allowNonBlsWithdrawalCredentials: false + isProduction: false + nethermindPruneThresholdMb: 51200 + # Add the verified Rocket Pool/helper deployments here before enabling startup. + addresses: {} diff --git a/shared/services/rocketpool/client.go b/shared/services/rocketpool/client.go index 498be34d3d..1f9f09a0c6 100644 --- a/shared/services/rocketpool/client.go +++ b/shared/services/rocketpool/client.go @@ -470,6 +470,15 @@ func (c *Client) InstallUpdateTracker(verbose bool) error { // Start the Rocket Pool service func (c *Client) StartService(composeFiles []string) error { + cfg, isNew, err := c.LoadConfig() + if err != nil { + return err + } + if !isNew { + if err := cfg.ValidateNetworkForStart(); err != nil { + return err + } + } // Start all of the containers cmd, err := c.compose(composeFiles, "up -d --remove-orphans --quiet-pull") diff --git a/shared/services/rocketpool/plataberget_test.go b/shared/services/rocketpool/plataberget_test.go new file mode 100644 index 0000000000..d9c9425c72 --- /dev/null +++ b/shared/services/rocketpool/plataberget_test.go @@ -0,0 +1,65 @@ +package rocketpool + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" + "text/template" + + "github.com/rocket-pool/smartnode/shared/services/config" + cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" + "gopkg.in/yaml.v2" +) + +// Rendering must not query public IP services during a unit test. +type offlineNetworkTemplateConfig struct{ *config.RocketPoolConfig } + +func (offlineNetworkTemplateConfig) GetExternalIp() string { return "192.0.2.1" } + +func TestPlatabergetNamedNetworkTemplates(t *testing.T) { + cfg, err := config.NewRocketPoolConfig(t.TempDir(), false) + if err != nil { + t.Fatal(err) + } + cfg.ChangeNetwork("plataberget") + // Match service startup, which loads the serialized choice types from disk. + if err := cfg.Deserialize(cfg.Serialize()); err != nil { + t.Fatal(err) + } + for _, cc := range []cfgtypes.ConsensusClient{cfgtypes.ConsensusClient_Lodestar, cfgtypes.ConsensusClient_Nimbus, cfgtypes.ConsensusClient_Teku} { + t.Run(string(cc), func(t *testing.T) { + cfg.ConsensusClient.Value = cc + for _, service := range []string{"eth1", "eth2", "validator"} { + body, err := os.ReadFile(filepath.Join("assets", "install", "templates", service+".tmpl")) + if err != nil { + t.Fatal(err) + } + tmpl, err := template.New(service).Parse(string(body)) + if err != nil { + t.Fatal(err) + } + var rendered bytes.Buffer + if err := tmpl.Execute(&rendered, offlineNetworkTemplateConfig{cfg}); err != nil { + t.Fatal(err) + } + var compose map[string]any + if err := yaml.Unmarshal(rendered.Bytes(), &compose); err != nil { + t.Fatalf("invalid %s YAML: %v", service, err) + } + text := rendered.String() + if !strings.Contains(text, "BEACON_NETWORK=plataberget") || strings.Contains(text, "CUSTOM_CHAIN_DIR") || strings.Contains(text, "/custom-chain") { + t.Fatalf("%s did not use the named preset", service) + } + client := "CLIENT=nethermind" + if service != "eth1" { + client = "CC_CLIENT=" + string(cc) + } + if !strings.Contains(text, client) || strings.Contains(text, "devnet") { + t.Fatalf("unexpected client or devnet startup in %s", service) + } + } + }) + } +} diff --git a/shared/services/services.go b/shared/services/services.go index 87937931bc..47d571d65d 100644 --- a/shared/services/services.go +++ b/shared/services/services.go @@ -119,6 +119,9 @@ func GetRocketPool(c *cli.Command) (*rocketpool.RocketPool, error) { if err != nil { return nil, err } + if err := cfg.RequireRocketPoolContracts(); err != nil { + return nil, err + } var ec rocketpool.ExecutionClient if IsStaticStateMode(c) { ec, err = getStaticExecutionClient(c, cfg) @@ -326,6 +329,9 @@ func getStaticExecutionClient(c *cli.Command, cfg *config.RocketPoolConfig) (*st } func getRocketPool(cfg *config.RocketPoolConfig, client rocketpool.ExecutionClient) (*rocketpool.RocketPool, error) { + if err := cfg.RequireRocketPoolContracts(); err != nil { + return nil, err + } var err error initRocketPool.Do(func() { rocketPool, err = rocketpool.NewRocketPool(client, common.HexToAddress(cfg.Smartnode.GetStorageAddress())) diff --git a/shared/types/config/network.go b/shared/types/config/network.go index cc5a116d47..fb6b97bdf1 100644 --- a/shared/types/config/network.go +++ b/shared/types/config/network.go @@ -28,6 +28,13 @@ type NetworkInfo struct { CommitBoostChainName string `yaml:"commitBoostChainName"` ClientTagSet ClientTagSet `yaml:"clientTagSet"` CustomChainConfigDir string `yaml:"customChainConfigDir"` + CheckpointSyncUrl string `yaml:"checkpointSyncUrl"` + + // Empty client lists preserve the unrestricted choices on existing networks. + ExecutionClients []ExecutionClient `yaml:"executionClients"` + ConsensusClients []ConsensusClient `yaml:"consensusClients"` + // ContractsPending allows a test network to be configured before its Rocket Pool deployment. + ContractsPending bool `yaml:"contractsPending"` SupportsMevBoost bool `yaml:"supportsMevBoost"` AllowNonBlsWithdrawalCredentials bool `yaml:"allowNonBlsWithdrawalCredentials"` From 901f76e22c9ecfa8a63bb12422ba78bb3d900b2f Mon Sep 17 00:00:00 2001 From: Fornax <23104993+0xfornax@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:03:51 -0300 Subject: [PATCH 2/6] Allow running clients on a network without RP contract deployment --- rocketpool-cli/node/sync.go | 44 ++++++++++--------- rocketpool-cli/service/service.go | 14 +++++- rocketpool/node/node.go | 26 +++++++---- rocketpool/watchtower/watchtower.go | 3 ++ shared/services/config/network-clients.go | 20 ++++----- .../assets/install/networks-default.yml | 11 +++-- .../assets/install/scripts/start-bn.sh | 11 ++--- .../assets/install/templates/eth2.tmpl | 3 +- .../assets/install/templates/node.tmpl | 3 ++ shared/services/rocketpool/client.go | 9 ++-- 10 files changed, 86 insertions(+), 58 deletions(-) diff --git a/rocketpool-cli/node/sync.go b/rocketpool-cli/node/sync.go index 7c7e7fb310..e16389d780 100644 --- a/rocketpool-cli/node/sync.go +++ b/rocketpool-cli/node/sync.go @@ -60,27 +60,31 @@ func getSyncProgress() error { return err } - // Make sure ETH2 is on the correct chain - depositContractInfo, err := rp.DepositContractInfo() - if err != nil { - return err - } - if !depositContractInfo.SufficientSync { - color.YellowPrintln("Your execution client hasn't synced enough to determine if your execution and consensus clients are on the same network.") - color.YellowPrintln("To run this safety check, try again later when the execution client has made more sync progress.") - fmt.Println() - return nil - } - if depositContractInfo.RPNetwork != depositContractInfo.BeaconNetwork || - depositContractInfo.RPDepositContract != depositContractInfo.BeaconDepositContract { - cliutils.PrintDepositMismatchError( - depositContractInfo.RPNetwork, - depositContractInfo.BeaconNetwork, - depositContractInfo.RPDepositContract, - depositContractInfo.BeaconDepositContract) - return nil + if cfg.HasRocketPoolContracts() { + // Make sure ETH2 is on the correct chain using Rocket Pool's deposit contract. + depositContractInfo, err := rp.DepositContractInfo() + if err != nil { + return err + } + if !depositContractInfo.SufficientSync { + color.YellowPrintln("Your execution client hasn't synced enough to determine if your execution and consensus clients are on the same network.") + color.YellowPrintln("To run this safety check, try again later when the execution client has made more sync progress.") + fmt.Println() + return nil + } + if depositContractInfo.RPNetwork != depositContractInfo.BeaconNetwork || + depositContractInfo.RPDepositContract != depositContractInfo.BeaconDepositContract { + cliutils.PrintDepositMismatchError( + depositContractInfo.RPNetwork, + depositContractInfo.BeaconNetwork, + depositContractInfo.RPDepositContract, + depositContractInfo.BeaconDepositContract) + return nil + } + fmt.Println("Your consensus client is on the correct network.") + } else { + fmt.Println("Rocket Pool contracts are not configured; skipping the deposit contract network check.") } - fmt.Println("Your consensus client is on the correct network.") fmt.Println() // Get node status diff --git a/rocketpool-cli/service/service.go b/rocketpool-cli/service/service.go index 2fc2ad8869..ceb573245f 100644 --- a/rocketpool-cli/service/service.go +++ b/rocketpool-cli/service/service.go @@ -242,7 +242,15 @@ func updateConfigFromCliArgs(c *cli.Command, cfg *config.RocketPoolConfig) error if cfg.LoadedNetworks().GetNetwork(network) == nil { return fmt.Errorf("unknown network %q", network) } + // Match the TUI: a checkpoint URL from the previous chain must not be + // kept. Flags are applied after this, so an explicit URL still wins. + changed := cfg.GetNetwork() != network cfg.ChangeNetwork(network) + if changed { + if err := cfg.ConsensusCommon.CheckpointSyncProvider.SetToDefault(network); err != nil { + return err + } + } } // Root params @@ -716,7 +724,9 @@ func startService(params startServiceParams) error { return nil } - if !params.ignoreSlashTimer { + if !cfg.HasRocketPoolContracts() { + fmt.Println("Rocket Pool contracts are not configured. Starting configured services; validator and watchtower services are disabled.") + } else if !params.ignoreSlashTimer { // Do the client swap check err := checkForValidatorChange(rp, cfg) if err != nil { @@ -770,7 +780,7 @@ func startService(params startServiceParams) error { color.YellowPrintf("Couldn't check if you have Doppelganger Protection enabled: %s\n", err.Error()) color.YellowPrintln("If you do, your validator will miss up to 3 attestations when it starts.") color.YellowPrintln("This is *intentional* and does not indicate a problem with your node.") - } else if doppelgangerEnabled { + } else if cfg.HasRocketPoolContracts() && doppelgangerEnabled { color.YellowPrintln("NOTE: You currently have Doppelganger Protection enabled.") color.YellowPrintln("Your validator will miss up to 3 attestations when it starts.") color.YellowPrintln("This is *intentional* and does not indicate a problem with your node.") diff --git a/rocketpool/node/node.go b/rocketpool/node/node.go index a02fdd8044..24525f949f 100644 --- a/rocketpool/node/node.go +++ b/rocketpool/node/node.go @@ -87,16 +87,18 @@ func run(c *cli.Command) error { return err } - // Handle the initial fee recipient file deployment - err = deployDefaultFeeRecipientFile(c) - if err != nil { - return err - } + if cfg.HasRocketPoolContracts() { + // Handle the initial fee recipient file deployment + err = deployDefaultFeeRecipientFile(c) + if err != nil { + return err + } - // Clean up old fee recipient files - err = removeLegacyFeeRecipientFiles(c) - if err != nil { - return err + // Clean up old fee recipient files + err = removeLegacyFeeRecipientFiles(c) + if err != nil { + return err + } } // Configure @@ -118,6 +120,12 @@ func run(c *cli.Command) error { // the daemon waits for the wallet and services to become ready. startHTTP(ctx, c, cfg) + if !cfg.HasRocketPoolContracts() { + fmt.Println("Rocket Pool contracts are not configured; running the HTTP API without node background tasks.") + <-ctx.Done() + return ctx.Err() + } + for { // Exit if the process received SIGINT/SIGTERM select { diff --git a/rocketpool/watchtower/watchtower.go b/rocketpool/watchtower/watchtower.go index 23d29af924..7c0bdc94f5 100644 --- a/rocketpool/watchtower/watchtower.go +++ b/rocketpool/watchtower/watchtower.go @@ -78,6 +78,9 @@ func run(c *cli.Command) error { if err := cfg.ValidateNetworkForStart(); err != nil { return err } + if err := cfg.RequireRocketPoolContracts(); err != nil { + return err + } // Configure configureHTTP() diff --git a/shared/services/config/network-clients.go b/shared/services/config/network-clients.go index ea213b2816..398de8afe0 100644 --- a/shared/services/config/network-clients.go +++ b/shared/services/config/network-clients.go @@ -121,25 +121,25 @@ func (cfg *RocketPoolConfig) ValidateNetworkClients() error { return errors.Join(problems...) } +// HasRocketPoolContracts reports whether contract-dependent services can run. +func (cfg *RocketPoolConfig) HasRocketPoolContracts() bool { + network := cfg.GetNetworkInfo() + return network != nil && !network.ContractsPending && network.Addresses.Storage != "" +} + // RequireRocketPoolContracts prevents accidentally using the zero storage address. func (cfg *RocketPoolConfig) RequireRocketPoolContracts() error { network := cfg.GetNetworkInfo() if network == nil { return fmt.Errorf("unknown network %q", cfg.GetNetwork()) } - if network.ContractsPending || network.Addresses.Storage == "" { - return fmt.Errorf("Rocket Pool contracts are not configured for %s; supply the deployment addresses and set contractsPending to false in the network definition before starting Rocket Pool", network.Label) + if !cfg.HasRocketPoolContracts() { + return fmt.Errorf("Rocket Pool contracts are not configured for %s; supply the deployment addresses and set contractsPending to false in the network definition before using contract-dependent services", network.Label) } return nil } -// ValidateNetworkForStart allows pending networks to be saved, but not started. +// ValidateNetworkForStart permits client startup before Rocket Pool is deployed. func (cfg *RocketPoolConfig) ValidateNetworkForStart() error { - if err := cfg.ValidateNetworkClients(); err != nil { - return err - } - if cfg.GetNetworkInfo().ContractsPending { - return cfg.RequireRocketPoolContracts() - } - return nil + return cfg.ValidateNetworkClients() } diff --git a/shared/services/rocketpool/assets/install/networks-default.yml b/shared/services/rocketpool/assets/install/networks-default.yml index dd736b1001..ea7c272b23 100644 --- a/shared/services/rocketpool/assets/install/networks-default.yml +++ b/shared/services/rocketpool/assets/install/networks-default.yml @@ -169,7 +169,6 @@ networks: description: | Ethereum's Glamsterdam testnet (glamsterdam-devnet-8), using test ETH. Supported clients: Nethermind with Lodestar, Nimbus, or Teku. - Rocket Pool contract addresses are pending; configuration is available but service startup is disabled until deployment. chainID: 7091047534 beaconNetwork: plataberget beaconExplorerUrl: https://dora.plataberget.ethpandaops.io @@ -177,10 +176,14 @@ networks: clientTagSet: test executionClients: [nethermind] consensusClients: [lodestar, nimbus, teku] - contractsPending: true + contractsPending: false supportsMevBoost: false allowNonBlsWithdrawalCredentials: false isProduction: false nethermindPruneThresholdMb: 51200 - # Add the verified Rocket Pool/helper deployments here before enabling startup. - addresses: {} + addresses: + storage: "0xd4E96eF8eee8678dBFf4d535E033Ed1a4F7605b7" + rplToken: "0x762e79b27feE0C0975f9caBEa9E9976006A7aD98" + reth: "0x4640b8610f3eFdeb8d44834ADb3228d0E79eaa09" + multicall: "0xCe085c899295aCaE769b8dbB525B5f0Ff7F2E128" + balancebatcher: "0xA6f1DeC523211141Ff633665f951a890ed653fea" diff --git a/shared/services/rocketpool/assets/install/scripts/start-bn.sh b/shared/services/rocketpool/assets/install/scripts/start-bn.sh index 9db383af44..c5707cf72d 100755 --- a/shared/services/rocketpool/assets/install/scripts/start-bn.sh +++ b/shared/services/rocketpool/assets/install/scripts/start-bn.sh @@ -35,12 +35,6 @@ if [ ! -s "/secrets/jwtsecret" ]; then exit 1 fi -# Report a missing fee recipient file -if [ ! -f "/validators/$FEE_RECIPIENT_FILE" ]; then - echo "Fee recipient file not found, please wait for the rocketpool_node process to create one." - exit 1 -fi - # Lighthouse startup if [ "$CC_CLIENT" = "lighthouse" ]; then @@ -276,10 +270,13 @@ if [ "$CC_CLIENT" = "teku" ]; then --log-destination=CONSOLE \ --ee-jwt-secret-file=/secrets/jwtsecret \ --beacon-liveness-tracking-enabled \ - --validators-proposer-default-fee-recipient=$RETH_ADDRESS \ --validators-graffiti-client-append-format=DISABLED \ $BN_ADDITIONAL_FLAGS" + if [ -n "$RETH_ADDRESS" ]; then + CMD="$CMD --validators-proposer-default-fee-recipient=$RETH_ADDRESS" + fi + if [ ! -z "$BN_SUGGESTED_BLOCK_GAS_LIMIT" ]; then CMD="$CMD --validators-builder-registration-default-gas-limit=$BN_SUGGESTED_BLOCK_GAS_LIMIT" fi diff --git a/shared/services/rocketpool/assets/install/templates/eth2.tmpl b/shared/services/rocketpool/assets/install/templates/eth2.tmpl index 1cd84b5384..75790243e9 100644 --- a/shared/services/rocketpool/assets/install/templates/eth2.tmpl +++ b/shared/services/rocketpool/assets/install/templates/eth2.tmpl @@ -93,13 +93,14 @@ services: - CHECKPOINT_SYNC_URL={{.ConsensusCommon.CheckpointSyncProvider}} - DOPPELGANGER_DETECTION={{.IsDoppelgangerEnabled}} - BN_ADDITIONAL_FLAGS={{.GetBNAdditionalFlags}} - - FEE_RECIPIENT_FILE={{.FeeRecipientFile}} - ENABLE_BITFLY_NODE_METRICS={{.EnableBitflyNodeMetrics}} - BITFLY_NODE_METRICS_SECRET={{.BitflyNodeMetrics.Secret}} - BITFLY_NODE_METRICS_ENDPOINT={{.BitflyNodeMetrics.Endpoint}} - BITFLY_NODE_METRICS_MACHINE_NAME={{.BitflyNodeMetrics.MachineName}} - PBS_URL={{.PbsUrl}} + {{- if .HasRocketPoolContracts}} - RETH_ADDRESS={{.Smartnode.GetRethAddress.Hex}} + {{- end}} - GRAFFITI={{.Graffiti}} - ADDON_GWW_ENABLED={{.GraffitiWallWriter.GetEnabledParameter}} {{- /* Client-specific values */}} diff --git a/shared/services/rocketpool/assets/install/templates/node.tmpl b/shared/services/rocketpool/assets/install/templates/node.tmpl index 8beb416a3c..bac2d172a9 100644 --- a/shared/services/rocketpool/assets/install/templates/node.tmpl +++ b/shared/services/rocketpool/assets/install/templates/node.tmpl @@ -12,6 +12,9 @@ services: restart: unless-stopped tty: true ports: [{{.GetNodeOpenPorts}}] + # Recreate the daemon when a network gains its contract deployment. + environment: + - ROCKET_POOL_CONTRACTS_CONFIGURED={{.HasRocketPoolContracts}} volumes: - /var/run/docker.sock:/var/run/docker.sock - {{.RocketPoolDirectory}}:/.rocketpool diff --git a/shared/services/rocketpool/client.go b/shared/services/rocketpool/client.go index 1f9f09a0c6..9dc91bbf3c 100644 --- a/shared/services/rocketpool/client.go +++ b/shared/services/rocketpool/client.go @@ -1335,11 +1335,10 @@ func (c *Client) deployTemplates(cfg *config.RocketPoolConfig, rocketpoolDir str // Read and substitute the templates deployedContainers := []string{} - // These containers always run - toDeploy := []string{ - config.NodeContainerName, - config.WatchtowerContainerName, - config.ValidatorContainerName, + // Keep the HTTP API available for client status and service management. + toDeploy := []string{config.NodeContainerName} + if cfg.HasRocketPoolContracts() { + toDeploy = append(toDeploy, config.WatchtowerContainerName, config.ValidatorContainerName) } // Check if we are running the Execution Layer locally From 9ba717c543544d37c3cf8719d1e04b1609b4c566 Mon Sep 17 00:00:00 2001 From: Fornax <23104993+0xfornax@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:55:26 -0300 Subject: [PATCH 3/6] Report sync before contract check --- rocketpool-cli/node/sync.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/rocketpool-cli/node/sync.go b/rocketpool-cli/node/sync.go index e16389d780..465b6d8b67 100644 --- a/rocketpool-cli/node/sync.go +++ b/rocketpool-cli/node/sync.go @@ -60,7 +60,21 @@ func getSyncProgress() error { return err } + // Report progress before checking contracts. The deposit-contract endpoint + // requires a synced EC and can wait behind the daemon's background sync loop. + status, err := rp.NodeSync() + if err != nil { + return err + } + printSyncProgress(&status.EcStatus, "execution") + printSyncProgress(&status.BcStatus, "consensus") + fmt.Println() + if cfg.HasRocketPoolContracts() { + if !hasSyncedExecutionClient(&status.EcStatus) { + color.YellowPrintln("The deposit contract network check is unavailable until an execution client is synced and ready.") + return nil + } // Make sure ETH2 is on the correct chain using Rocket Pool's deposit contract. depositContractInfo, err := rp.DepositContractInfo() if err != nil { @@ -87,19 +101,14 @@ func getSyncProgress() error { } fmt.Println() - // Get node status - status, err := rp.NodeSync() - if err != nil { - return err - } - - // Print EC status - printSyncProgress(&status.EcStatus, "execution") - - // Print CC status - printSyncProgress(&status.BcStatus, "consensus") - // Return return nil } + +func hasSyncedExecutionClient(status *api.ClientManagerStatus) bool { + ready := func(client api.ClientStatus) bool { + return client.IsWorking && client.IsSynced && client.Error == "" + } + return ready(status.PrimaryClientStatus) || (status.FallbackEnabled && ready(status.FallbackClientStatus)) +} From 2bfbc168fbc1efd08ae47f5bbb11a0ce20b591b0 Mon Sep 17 00:00:00 2001 From: Fornax <23104993+0xfornax@users.noreply.github.com> Date: Wed, 7 Oct 2026 09:58:12 -0300 Subject: [PATCH 4/6] Added contract addresses --- rocketpool/api/node/status.go | 6 +----- shared/services/config/images.go | 11 ++++++----- .../rocketpool/assets/install/networks-default.yml | 9 +++++---- .../rocketpool/assets/install/plataberget.env | 3 +++ shared/services/rocketpool/client.go | 2 +- 5 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 shared/services/rocketpool/assets/install/plataberget.env diff --git a/rocketpool/api/node/status.go b/rocketpool/api/node/status.go index a4187d298d..febc5d49a1 100644 --- a/rocketpool/api/node/status.go +++ b/rocketpool/api/node/status.go @@ -31,7 +31,6 @@ import ( "github.com/rocket-pool/smartnode/shared/services" "github.com/rocket-pool/smartnode/shared/services/beacon" "github.com/rocket-pool/smartnode/shared/types/api" - cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" ) func getStatus(c *cli.Command) (*api.NodeStatusResponse, error) { @@ -66,9 +65,6 @@ func getStatus(c *cli.Command) (*api.NodeStatusResponse, error) { if err != nil { return nil, err } - if reg == nil { - return nil, fmt.Errorf("Error getting the signer registry on network [%v].", cfg.Smartnode.Network.Value.(cfgtypes.Network)) - } // Response response := api.NodeStatusResponse{} @@ -288,7 +284,7 @@ func getStatus(c *cli.Command) (*api.NodeStatusResponse, error) { return err }) - // Get RocketDash proposals and votes, but treat errors as non-fatal + // Get RocketDash proposals and votes when a signer registry is configured, but treat errors as non-fatal if reg != nil { wg.Go(func() error { var err error diff --git a/shared/services/config/images.go b/shared/services/config/images.go index d22e1c759e..07ba071b50 100644 --- a/shared/services/config/images.go +++ b/shared/services/config/images.go @@ -15,11 +15,12 @@ import ( ) const ( - ImagesMainnetFile = "mainnet.env" - ImagesTestnetFile = "testnet.env" - ImagesDevnetFile = "devnet.env" - ComposeMainFile = "compose.yml" - ComposeImageEnvFile = "image-tags.env" + ImagesMainnetFile = "mainnet.env" + ImagesTestnetFile = "testnet.env" + ImagesDevnetFile = "devnet.env" + ImagesPlatabergetFile = "plataberget.env" + ComposeMainFile = "compose.yml" + ComposeImageEnvFile = "image-tags.env" ImageSmartnode = "RP_IMAGE_SMARTNODE" diff --git a/shared/services/rocketpool/assets/install/networks-default.yml b/shared/services/rocketpool/assets/install/networks-default.yml index ea7c272b23..8ff199a53e 100644 --- a/shared/services/rocketpool/assets/install/networks-default.yml +++ b/shared/services/rocketpool/assets/install/networks-default.yml @@ -161,20 +161,21 @@ networks: multicall: "0x1a0E9eF9Cc41f4CcD377979C0e6DC8dBe4E2858C" balancebatcher: "0x8f22b1Cd26efe83ADf8Da87789fB66EeD917FCAa" - # Named-network presets only: Nethermind >=2.0.0 (>=2.1.0 for archive), + # Geth uses the packaged genesis/bootnodes and the plataberget.env image pin. + # Named-network presets: Nethermind >=2.0.0 (>=2.1.0 for archive), # Lodestar >=1.47.0, Nimbus >=26.8.0, Teku >=26.9.0. - # Current mainnet.env pins meet these requirements; no custom-chain directory. + # Consensus clients continue to use their named-network presets. - name: plataberget label: Platåberget Testnet description: | Ethereum's Glamsterdam testnet (glamsterdam-devnet-8), using test ETH. - Supported clients: Nethermind with Lodestar, Nimbus, or Teku. + Supported clients: Geth or Nethermind with Lodestar, Nimbus, or Teku. chainID: 7091047534 beaconNetwork: plataberget beaconExplorerUrl: https://dora.plataberget.ethpandaops.io checkpointSyncUrl: https://checkpoint-sync.plataberget.ethpandaops.io clientTagSet: test - executionClients: [nethermind] + executionClients: [geth, nethermind] consensusClients: [lodestar, nimbus, teku] contractsPending: false supportsMevBoost: false diff --git a/shared/services/rocketpool/assets/install/plataberget.env b/shared/services/rocketpool/assets/install/plataberget.env new file mode 100644 index 0000000000..dcd437fe8f --- /dev/null +++ b/shared/services/rocketpool/assets/install/plataberget.env @@ -0,0 +1,3 @@ +# Match the Geth build used by the ethPandaOps Plataberget inventory, pinned +# on 2026-10-06. Includes linux/amd64 and linux/arm64 manifests. +RP_IMAGE_GETH=ethpandaops/geth:master@sha256:a435823a87ee35d5744f7101db6cd5f2bec63ee2ddd907afa4f43ba98a61ee79 diff --git a/shared/services/rocketpool/client.go b/shared/services/rocketpool/client.go index 9dc91bbf3c..bd4d54374e 100644 --- a/shared/services/rocketpool/client.go +++ b/shared/services/rocketpool/client.go @@ -1233,7 +1233,7 @@ func (c *Client) compose(composeFiles []string, args string) (string, error) { } func ensureImageEnvFiles(dir string) error { - official := []string{config.ImagesMainnetFile, config.ImagesTestnetFile, config.ImagesDevnetFile} + official := []string{config.ImagesMainnetFile, config.ImagesTestnetFile, config.ImagesDevnetFile, config.ImagesPlatabergetFile} for _, name := range official { data, ok := assets.EmbeddedNetworkEnv(name) if !ok && name == config.ImagesMainnetFile { From 86515597b05b57ef150cd649eb2a31e97d3cc78c Mon Sep 17 00:00:00 2001 From: Fornax <23104993+0xfornax@users.noreply.github.com> Date: Wed, 7 Oct 2026 10:00:08 -0300 Subject: [PATCH 5/6] Add Geth with a custom dir --- .../assets/install/plataberget/el_enrs.txt | 20 + .../assets/install/plataberget/genesis.json | 966 ++++++++++++++++++ .../assets/install/scripts/start-ec.sh | 19 + .../assets/install/templates/eth1.tmpl | 3 + .../rocketpool/assets/scripts/install.sh | 2 + 5 files changed, 1010 insertions(+) create mode 100644 shared/services/rocketpool/assets/install/plataberget/el_enrs.txt create mode 100644 shared/services/rocketpool/assets/install/plataberget/genesis.json diff --git a/shared/services/rocketpool/assets/install/plataberget/el_enrs.txt b/shared/services/rocketpool/assets/install/plataberget/el_enrs.txt new file mode 100644 index 0000000000..3d83e69e89 --- /dev/null +++ b/shared/services/rocketpool/assets/install/plataberget/el_enrs.txt @@ -0,0 +1,20 @@ +enr:-Iu4QATktytLE0lWrdBaJbNMrbJrkTQ_5C1ubV_9Ks56qmMPIWKZan8rjIj85ANUGBbl4sDuk54GX9ZszLCp0eMLCuaAgmlkgnY0gmlwhI5djeSJc2VjcDI1NmsxoQLNXzvrs1bTDHsLDB2EYQQJ8qpPvSxcmLX-bU8294Tc7YN0Y3CCdl-DdWRwgnZf +enr:-Iu4QBKN484eGMOXj9wQhiZe86hRtNtfaBd1xFryTlv8ebSBWz6LLpDaXu6RBl784YUm9XgVkoscCJ_vS-BEUEWHYUOAgmlkgnY0gmlwhKiQtFiJc2VjcDI1NmsxoQOh6CqrY7JeXiZ4_mVo3VY6V2-vWfhmeSp7LhOJiUcgGoN0Y3CCdl-DdWRwgnZf +enr:-Iu4QAFIXq-pYrShJPnY5vjsetC4Kbi9lD6BYtUIclhfZz7tdbs-S4K0CM9euHeXMTLZbMK3E9pllM4Ieq4K6dohJ52AgmlkgnY0gmlwhKpApPSJc2VjcDI1NmsxoQMCm9Rnge4cwnAS2NL5LQn6lzuKueOcSBcB1UAmx9O6BYN0Y3CCdl-DdWRwgnZf +enr:-Iu4QMXvMaCsEKX9zG4q4Moz1dsL1zeAF3RH1xxtS4iFrzM1HsL8r8csW6S4D2-GAEXUVGguhDQ4Acfj4zLO0k1Xg1GAgmlkgnY0gmlwhC5lSYWJc2VjcDI1NmsxoQMiSBTcsTfmhNMiCPZrksv0QnUzdcdogUtQlwqdMmfrvIN0Y3CCdl-DdWRwgnZf +enr:-Iu4QBSoSmrdDLZOhYKy16V3oxqhf1I9OPNOQ4jPN4C_nL-dbyGu_9ksJhdd1Ii8kvAmXrmyy33rNHC-CKdDde3cq1uAgmlkgnY0gmlwhKiQED2Jc2VjcDI1NmsxoQMJzgQBzqO4HUUq5rsG3R7eDIjOSyijPQ6dwvRIYrgK14N0Y3CCdl-DdWRwgnZf +enr:-Iu4QIShwyfxHOKt635flWb5EOUX6MNOcdB5lQU-0d3X4e_MTUIu5R5bNsQDqMAhSnVg6wW-g0zQjJThHAgH0vuVDnaAgmlkgnY0gmlwhKpAhXOJc2VjcDI1NmsxoQJ6J08cIZw68nVvh3VdrhsFGWqm_4ttGyRzTguQGn_2kIN0Y3CCdl-DdWRwgnZf +enr:-Iu4QKvPdaAhx-dp9u0m1lL4MBo_igtk8A3THTvF-jkkcDOePKenb-TZQfE1EhVP0FEfe3g16_ZL7OY-2sUeAGJJi2iAgmlkgnY0gmlwhKpA04yJc2VjcDI1NmsxoQK3bgmk2K_t41NZBsPdK7Zh7Pj6oeSs8mnSzqratDFHdIN0Y3CCdl-DdWRwgnZf +enr:-Iu4QHWf0I0uQ2ln7pwsUQck3oXSGLeLz5gjiFpIyutmsDFNEUHNhXytKjxDPT7BR_LKY3zh1r9srKeZpHdNzd0UeZiAgmlkgnY0gmlwhKiQK52Jc2VjcDI1NmsxoQIs4IuqTKCtnrya9nwyzQTLY25akfyJKLvXciPS18dM6oN0Y3CCdl-DdWRwgnZf +enr:-Iu4QErZ4u0-aLcwjF-RJEtH8JZne0kItIpR_mjwKeFXPAjadf4Gx_WCR2l2w5NssVKFZr9WyG16jC5iJt9IhzAXGQyAgmlkgnY0gmlwhEAX10yJc2VjcDI1NmsxoQN1rFkYDqrK_T1iRMHes1pS9MUhbzrILiy4n-65J1eMh4N0Y3CCdl-DdWRwgnZf +enr:-Iu4QCV4ZQWzRWyT0omjHe7QfJVgTfIj3b3bqLre7F10qtvmcxKJ3efcxJ29w4yDnlhhvdicDKvJTSL8ufLg3M-fiyWAgmlkgnY0gmlwhES3yVCJc2VjcDI1NmsxoQKltJUGilA_q8fxA8CG7VigxBOvCA1kjNtRsSbkqQnwYYN0Y3CCdl-DdWRwgnZf +enr:-Iu4QNqs-s3sQ8Zw9udVYB-GgK0jfbflQRzNNFtAmh7Wn-DuPxKzQJTgqr5bOpEaLna8G_oHS6j2B4tnTEx7jWQVmLyAgmlkgnY0gmlwhJ_LBKmJc2VjcDI1NmsxoQPjz7jODFxsWi4T8ZBEozNDHu1AAjXODWpCkuNRuSesjIN0Y3CCdl-DdWRwgnZf +enr:-Iu4QHt62QsAkSCptwIKyGLZmwY23YXhhapQ2mN-IT5bRFfHC2M8DH5CwdAj_H-NqgoKHP5rOgmkC5Y8H2kPihGnWGmAgmlkgnY0gmlwhJ31aSGJc2VjcDI1NmsxoQLLsuZbMrZYmRwrP3LqeYtSe5Wj75ERBg9nRdp-2MO2TYN0Y3CCdl-DdWRwgnZf +enr:-Iu4QG4t9Xr9jyvqTwxcHsBNknR_Cm0ZyNtg4-oB288lHaDcMab9QJdK7i-SJV-N1HjEnCvkcoBuV1NFBR8Pm_42CymAgmlkgnY0gmlwhIbHn9OJc2VjcDI1NmsxoQKoiq04bvJTA0Nfztz3yC5MUDaq5P-yjr-mnakr7zEMXYN0Y3CCdl-DdWRwgnZf +enr:-Iu4QG9qdbvTjBVw61BHljUrzB6D72meUGwV7LS06AHr__ycM-5GQ7f1d_kd91HUKCMXaRsMYd2rYG-1DsPVV1DDsxeAgmlkgnY0gmlwhC5l_2aJc2VjcDI1NmsxoQNaqiAn5vI-8h-7ywmxfGv1mTCIH_DuiPr2k3j4KfFVX4N0Y3CCdl-DdWRwgnZf +enr:-Iu4QG1__cxzkZwqxH5WcEf5G_aU88T5uWSBTPczZJ1_ZhUxOcn_1l-tmjWGzGx1W_-fyJLyxFFbDwBvl9MTAiOb72SAgmlkgnY0gmlwhIs7IY-Jc2VjcDI1NmsxoQOj6mkChCr-uYho6uAJ4uTS7FJ88dZrrexjpe9mZ90kJ4N0Y3CCdl-DdWRwgnZf +enr:-Iu4QHe6F8AXpWfWOdSAHWtKKq08e2o1vqVNvu9c65S0SP4TEzFIf7BFtEQWGuPeQfIJOMYjrhotQ-pOsR5KoQybsmOAgmlkgnY0gmlwhIm4emeJc2VjcDI1NmsxoQNOd7BR6TdWYYhdHTkMvJJEmjSI3Vmfy9do4lLoGr2bmYN0Y3CCdl-DdWRwgnZf +enr:-Iu4QM8SrTpBa4Xz1lPlygWQtS3UNP0wYpMeCPzQTygZoiK5QmdU9vKw2dV3mTOpebhe_a7uL_9rKzj2IiRvPi2JGI6AgmlkgnY0gmlwhKiQtOyJc2VjcDI1NmsxoQLzBQO352FOUwgp5wa5659aHqdhcAqHN9TRNq9g2oRdy4N0Y3CCdl-DdWRwgnZf +enr:-Iu4QGZ3W6x1Wbm-2X6IBUMZyxeA6EAoS3VWcZ4wqmK6VxsOKYPPstWZtkME-x1aXuY6ri0olVmteqkTAn80MDYBmaGAgmlkgnY0gmlwhKpAoDGJc2VjcDI1NmsxoQJY_BkQsvWymw0pMT9c1GROTWOmeShyrix9RJXxLCzFnIN0Y3CCdl-DdWRwgnZf +enr:-Iu4QBW7t9-I0I8pjSZ_ANIgxryNv95kzWOrdblP_5_7XIseMESRUoNuxUYN0QbkmjxbeQtH8PA7gMEP3F0VQlVMTuuAgmlkgnY0gmlwhKiQdc2Jc2VjcDI1NmsxoQOmGj3K_AfVEA9EQJdG-r6UMoh4cdwx_n9UeSEDEHCst4N0Y3CCdl-DdWRwgnZf +enr:-Iu4QK84B4c14boqxw84czq0CVONhX_0hU20_uA2cnZxX1t6dt2GjpeOoBTOmUj0OfyFTzI8WGVMDfu5F5CiPl1N5QKAgmlkgnY0gmlwhI_GaY2Jc2VjcDI1NmsxoQPXh2LsT24BU2ZODMs43_CdySu6ckoAvJenSsGIOYPus4N0Y3CCdl-DdWRwgnZf \ No newline at end of file diff --git a/shared/services/rocketpool/assets/install/plataberget/genesis.json b/shared/services/rocketpool/assets/install/plataberget/genesis.json new file mode 100644 index 0000000000..242b974cbd --- /dev/null +++ b/shared/services/rocketpool/assets/install/plataberget/genesis.json @@ -0,0 +1,966 @@ +{ + "config": { + "chainId": 7091047534, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "mergeNetsplitBlock": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "shanghaiTime": 0, + "cancunTime": 0, + "blobSchedule": { + "cancun": { + "target": 3, + "max": 6, + "baseFeeUpdateFraction": 3338477 + }, + "prague": { + "target": 6, + "max": 9, + "baseFeeUpdateFraction": 5007716 + }, + "bpo1": { + "target": 10, + "max": 15, + "baseFeeUpdateFraction": 8346193 + }, + "bpo2": { + "target": 14, + "max": 21, + "baseFeeUpdateFraction": 11684671 + } + }, + "depositContractAddress": "0x00000000219ab540356cBB839Cbe05303d7705Fa", + "pragueTime": 0, + "osakaTime": 0, + "bpo1Time": 0, + "bpo2Time": 0, + "amsterdamTime": 1787212224 + }, + "alloc": { + "0x0000000000000000000000000000000000000000": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000001": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000002": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000003": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000004": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000005": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000006": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000007": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000008": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000009": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000010": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000011": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000012": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000013": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000014": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000015": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000016": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000017": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000018": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000019": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000020": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000021": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000022": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000023": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000024": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000025": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000026": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000027": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000028": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000029": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000030": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000031": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000032": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000033": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000034": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000035": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000036": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000037": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000038": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000039": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000040": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000041": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000042": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000043": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000044": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000045": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000046": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000047": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000048": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000049": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000050": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000051": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000052": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000053": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000054": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000055": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000056": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000057": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000058": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000059": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000060": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000061": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000062": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000063": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000064": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000065": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000066": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000067": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000068": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000069": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000070": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000071": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000072": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000073": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000074": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000075": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000076": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000077": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000078": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000079": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000080": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000081": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000082": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000083": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000084": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000085": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000086": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000087": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000088": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000089": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000090": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000091": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000092": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000093": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000094": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000095": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000096": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000097": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000098": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000099": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009f": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000aa": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ab": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ac": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ad": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ae": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000af": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ba": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000be": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bf": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ca": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ce": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cf": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000da": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000db": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000dc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000dd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000de": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000df": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ea": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000eb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ec": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ed": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ee": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ef": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fa": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fe": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ff": { + "balance": "1" + }, + "0x00000000219ab540356cBB839Cbe05303d7705Fa": { + "balance": "0", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + } + }, + "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02": { + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500" + }, + "0x0000F90827F1C53a10cb7A02335B175320002935": { + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500" + }, + "0x00000961Ef480Eb55e80D19ad83579A64c007002": { + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101f457600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603814608857366101f457346101f4575f5260205ff35b34106101f457600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260385f601437604c5fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101835782810160030260040181604c02815460601b8152601401816001015481526020019060020154807fffffffffffffffffffffffffffffffff00000000000000000000000000000000168252906010019060401c908160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360010160e1565b910180921461019557906002556101a0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14156101cd57505f5b6001546002828201116101e25750505f6101e8565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + "0x0000BBdDc7CE488642fb579F8B00f3a590007251": { + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + "0x4e59b44847b379578588920cA78FbF26c0B4956C": { + "balance": "0", + "nonce": "1", + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" + }, + "0x454b0EA7d8aD3C56D0CF2e44Ed97b2Feab4D7AF2": { + "balance": "1000000000000000000000000000" + }, + "0xd3248BA3E5492D767F8e427Cb9C7B9D5C3972D7B": { + "balance": "1000000000000000000000000000" + }, + "0xAD01b55d7c3448B8899862eb335FBb17075d8DE2": { + "balance": "1000000000000000000000000000" + }, + "0x7e454a14B8e7528465eeF86f0DC1da4f235d9D79": { + "balance": "1000000000000000000000000000" + }, + "0x7a40026A3b9A41754a95EeC8c92C6B99886f440C": { + "balance": "1000000000000000000000000000" + }, + "0x8c4D8CDD1f474510Dd70D66F2785a3a38a29AC1A": { + "balance": "1000000000000000000000000000" + }, + "0xfC7360b3b28cf4204268A8354dbEc60720d155D2": { + "balance": "1000000000000000000000000000" + }, + "0x2F7626bBDb8c0f9071bC98046Ef6fDed2167F97F": { + "balance": "1000000000000000000000000000" + }, + "0x752CE31Dec0dde7D1563CdF6438d892De2D4FBee": { + "balance": "1000000000000000000000000000" + }, + "0x455f42d91096c4Aa708D7Cbcb2DC499dE89C402c": { + "balance": "1000000000000000000000000000" + }, + "0x85154341488732D57a97F54AB9706Bc4B71B8636": { + "balance": "1000000000000000000000000000" + }, + "0x6a9CcA73d4Ff3a249fa778C7651f4Df8B9fFa0Df": { + "balance": "1000000000000000000000000000" + }, + "0xee2d0567AAe8080CA269b7908F4aF8BBb59A6804": { + "balance": "1000000000000000000000000000" + }, + "0xDd8D4027078a471816e4Ef7F69aFc0A5d2947dDc": { + "balance": "1000000000000000000000000000" + }, + "0x20466E9A67f299F6056bE52A50ea324FA6Bd05D5": { + "balance": "1000000000000000000000000000" + }, + "0x03F24BB0C9cfb30217Ff992A36ae9230F2A1697f": { + "balance": "1000000000000000000000000000" + }, + "0x032d8372C519c3927b87BDe4479E846a81EF2d10": { + "balance": "1000000000000000000000000000" + }, + "0xF863DF14954df73804b3150F3754a8F98CBB1D0d": { + "balance": "1000000000000000000000000000" + }, + "0xbe918A6aef1920F3706E23d153146aA6C5982620": { + "balance": "1000000000000000000000000000" + }, + "0xA0c7edA3CE474BC670A11EA9537cBEfd36331123": { + "balance": "1000000000000000000000000000" + }, + "0xF03b43BeB861044492Eb43E247bEE2AC6C80c651": { + "balance": "1000000000000000000000000000" + }, + "0x9a97ee9d32a0d68406e32b34c92afb81ce2bc467": { + "balance": "100000000000000000000000" + }, + "0x107781Bc6FA8f66B843f4216fd6D5862D3aa4fcd": { + "balance": "100000000000000000000000" + }, + "0x118A1030baf8Fa56f9B8235C163d3112FA8c3b89": { + "balance": "100000000000000000000000" + } + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x0", + "extraData": "", + "gasLimit": "0x3938700", + "nonce": "0x1234", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "1786622400" +} diff --git a/shared/services/rocketpool/assets/install/scripts/start-ec.sh b/shared/services/rocketpool/assets/install/scripts/start-ec.sh index 235f2028fc..4e305c239b 100755 --- a/shared/services/rocketpool/assets/install/scripts/start-ec.sh +++ b/shared/services/rocketpool/assets/install/scripts/start-ec.sh @@ -54,6 +54,21 @@ fi # Geth startup if [ "$CLIENT" = "geth" ]; then + # Geth has no --plataberget preset. Keep this EL-only config separate from + # CUSTOM_CHAIN_DIR so consensus clients can still use their named presets. + if [ "$BEACON_NETWORK" = "plataberget" ]; then + if [ ! -s /network-config/genesis.json ] || [ ! -s /network-config/el_enrs.txt ]; then + echo "Plataberget Geth network files are missing; run rocketpool service install to install them" + exit 1 + fi + GETH_NETWORK="--networkid 7091047534 --override.genesis /network-config/genesis.json" + GETH_BOOTNODES=$(tr -d '\r' < /network-config/el_enrs.txt | awk 'NF { printf "%s%s", sep, $0; sep="," }') + if [ -z "$GETH_BOOTNODES" ]; then + echo "Plataberget Geth bootnodes are empty" + exit 1 + fi + fi + # Performance tuning for ARM systems UNAME_VAL=$(uname -m) if [ "$UNAME_VAL" = "arm64" ] || [ "$UNAME_VAL" = "aarch64" ]; then @@ -113,6 +128,10 @@ if [ "$CLIENT" = "geth" ]; then CMD="$CMD --nat stun" fi + if [ "$BEACON_NETWORK" = "plataberget" ]; then + CMD="$CMD --bootnodes $GETH_BOOTNODES --discovery.v4=false --discovery.v5=true" + fi + if [ -n "$CUSTOM_CHAIN_DIR" ]; then\ CMD="$CMD --bootnodes $BOOTNODE_ENODE_LIST" fi diff --git a/shared/services/rocketpool/assets/install/templates/eth1.tmpl b/shared/services/rocketpool/assets/install/templates/eth1.tmpl index beab2cd080..d4274b9700 100644 --- a/shared/services/rocketpool/assets/install/templates/eth1.tmpl +++ b/shared/services/rocketpool/assets/install/templates/eth1.tmpl @@ -19,6 +19,9 @@ services: volumes: - eth1clientdata:/ethclient - {{.RocketPoolDirectory}}/scripts:/setup:ro + {{- if and (eq .ExecutionClient.String "geth") (eq .Smartnode.GetBeaconNetwork "plataberget")}} + - {{.RocketPoolDirectory}}/plataberget:/network-config:ro + {{- end}} {{- if .Smartnode.GetCustomChainConfigDir}} - {{.RocketPoolDirectory}}/{{.Smartnode.GetCustomChainConfigDir}}:/custom-chain:ro {{- end}} diff --git a/shared/services/rocketpool/assets/scripts/install.sh b/shared/services/rocketpool/assets/scripts/install.sh index 3a99f9dbea..4ec58df51f 100755 --- a/shared/services/rocketpool/assets/scripts/install.sh +++ b/shared/services/rocketpool/assets/scripts/install.sh @@ -409,9 +409,11 @@ progress 7 "Copying package files to Rocket Pool user data directory..." { cp -r "$PACKAGE_FILES_PATH/templates" "$RP_PATH" || fail "Could not copy templates folder to the Rocket Pool user data directory."; } >&2 { cp -r "$PACKAGE_FILES_PATH/alerting" "$RP_PATH" || fail "Could not copy alerting folder to the Rocket Pool user data directory."; } >&2 { cp -r "$PACKAGE_FILES_PATH/devnet" "$RP_PATH" || fail "Could not copy devnet folder to the Rocket Pool user data directory."; } >&2 +{ cp -r "$PACKAGE_FILES_PATH/plataberget" "$RP_PATH" || fail "Could not copy Plataberget network files to the Rocket Pool user data directory."; } >&2 { cp "$PACKAGE_FILES_PATH/mainnet.env" "$RP_PATH/mainnet.env" || fail "Could not copy mainnet.env to the Rocket Pool user data directory."; } >&2 { cp "$PACKAGE_FILES_PATH/testnet.env" "$RP_PATH/testnet.env" || fail "Could not copy testnet.env to the Rocket Pool user data directory."; } >&2 { cp "$PACKAGE_FILES_PATH/devnet.env" "$RP_PATH/devnet.env" || fail "Could not copy devnet.env to the Rocket Pool user data directory."; } >&2 +{ cp "$PACKAGE_FILES_PATH/plataberget.env" "$RP_PATH/plataberget.env" || fail "Could not copy plataberget.env to the Rocket Pool user data directory."; } >&2 { cp \ "$PACKAGE_FILES_PATH/grafana-prometheus-datasource.yml" \ "$PACKAGE_FILES_PATH/prometheus.tmpl" \ From fc70634418cffbd01e60004997ba95427d634be3 Mon Sep 17 00:00:00 2001 From: Fornax <23104993+0xfornax@users.noreply.github.com> Date: Wed, 7 Oct 2026 10:10:43 -0300 Subject: [PATCH 6/6] fix lint --- .../service/config/network-clients_test.go | 107 -------------- rocketpool-cli/service/network-config_test.go | 56 -------- .../beacon/client/slot-duration_test.go | 8 +- .../services/config/network-clients_test.go | 136 ------------------ .../services/rocketpool/plataberget_test.go | 65 --------- 5 files changed, 6 insertions(+), 366 deletions(-) delete mode 100644 rocketpool-cli/service/config/network-clients_test.go delete mode 100644 rocketpool-cli/service/network-config_test.go delete mode 100644 shared/services/config/network-clients_test.go delete mode 100644 shared/services/rocketpool/plataberget_test.go diff --git a/rocketpool-cli/service/config/network-clients_test.go b/rocketpool-cli/service/config/network-clients_test.go deleted file mode 100644 index e555334e98..0000000000 --- a/rocketpool-cli/service/config/network-clients_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package config - -import ( - "reflect" - "strings" - "testing" - - "github.com/rivo/tview" - serviceconfig "github.com/rocket-pool/smartnode/shared/services/config" - cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" -) - -func TestWizardRefreshesNetworkClientChoices(t *testing.T) { - cfg, err := serviceconfig.NewRocketPoolConfig(t.TempDir(), false) - if err != nil { - t.Fatal(err) - } - md := &MainDisplay{Config: cfg, app: tview.NewApplication(), pages: tview.NewPages(), navHeader: tview.NewTextView(), isNew: true} - wiz := newWizard(md) - for _, network := range []string{"plataberget", "testnet", "plataberget"} { - // Use the network wizard action to exercise a change after modals exist. - for i, option := range cfg.Smartnode.Network.Options { - if string(option.Value.(cfgtypes.Network)) == network { - wiz.networkModal.modal.done(i, option.Name) - break - } - } - for _, tc := range []struct { - step *choiceWizardStep - plataberget []string - }{ - {wiz.executionLocalModal, []string{"Random (Recommended)", "Nethermind"}}, - {wiz.consensusLocalModal, []string{"Random (Recommended)", "Lodestar", "Nimbus", "Teku"}}, - {wiz.consensusExternalSelectModal, []string{"Lodestar", "Nimbus", "Teku"}}, - {wiz.nativeCcModal, []string{"Lodestar", "Nimbus", "Teku"}}, - } { - tc.step.show() - var labels []string - for _, form := range tc.step.modal.forms { - for i := 0; i < form.GetButtonCount(); i++ { - labels = append(labels, strings.TrimSpace(form.GetButton(i).GetLabel())) - } - } - if network == "plataberget" && !reflect.DeepEqual(labels, tc.plataberget) { - t.Fatalf("stale wizard choices: %v, want %v", labels, tc.plataberget) - } - if network == "testnet" && len(labels) < 5 { - t.Fatalf("Hoodi choices not restored: %v", labels) - } - } - } -} - -func TestSettingsRefreshesNetworkClientChoices(t *testing.T) { - cfg, err := serviceconfig.NewRocketPoolConfig(t.TempDir(), true) - if err != nil { - t.Fatal(err) - } - layout := newStandardLayout() - layout.createForm(&cfg.Smartnode.Network, "Native client") - items := createParameterizedFormItems([]*cfgtypes.Parameter{&cfg.Native.ConsensusClient}, layout) - layout.mapParameterizedFormItems(items...) - layout.form.AddFormItem(items[0].item) - dropDown := items[0].item.(*DropDown) - for _, network := range []cfgtypes.Network{"plataberget", "testnet", "plataberget"} { - cfg.ChangeNetwork(network) - layout.refresh() - for i, option := range cfg.Native.ConsensusClient.Options { - if dropDown.options[i].Text != option.Name { - t.Fatal("settings dropdown has stale options") - } - dropDown.SetCurrentOption(i) - if cfg.Native.ConsensusClient.Value != option.Value { - t.Fatalf("selected %s but saved %v", option.Name, cfg.Native.ConsensusClient.Value) - } - if strings.TrimSpace(layout.descriptionBox.GetText(false)) != option.Description { - t.Fatalf("%s/%s: description %q, want %q", network, option.Name, layout.descriptionBox.GetText(false), option.Description) - } - } - } -} - -func TestSettingsPageRebuildsAfterNetworkChange(t *testing.T) { - cfg, err := serviceconfig.NewRocketPoolConfig(t.TempDir(), false) - if err != nil { - t.Fatal(err) - } - md := NewMainDisplay(tview.NewApplication(), nil, cfg, false, false, false) - cfg.ChangeNetwork("plataberget") - md.settingsHome.refresh() - ec := md.settingsHome.ecPage - if len(ec.ecDropdown.item.(*DropDown).options) != 1 { - t.Fatal("execution settings retained unsupported clients") - } - // The parameter section must switch from Geth to Nethermind as well. - foundNethermind := false - for i := 0; i < ec.layout.form.GetFormItemCount(); i++ { - for _, item := range ec.nethermindItems { - if ec.layout.form.GetFormItem(i) == item.item { - foundNethermind = true - } - } - } - if !foundNethermind { - t.Fatal("execution settings still show the previous client's parameters") - } -} diff --git a/rocketpool-cli/service/network-config_test.go b/rocketpool-cli/service/network-config_test.go deleted file mode 100644 index 0b7d5fef23..0000000000 --- a/rocketpool-cli/service/network-config_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package service - -import ( - "context" - "strings" - "testing" - - "github.com/rocket-pool/smartnode/shared/services/config" - cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" - "github.com/urfave/cli/v3" -) - -func TestHeadlessNetworkClientSelection(t *testing.T) { - for _, tc := range []struct { - name, from, to, client string - wantError bool - }{ - {"defaults", "mainnet", "plataberget", "", false}, - {"supported", "testnet", "plataberget", "nethermind", false}, - {"unsupported", "mainnet", "plataberget", "geth", true}, - {"restore", "plataberget", "testnet", "geth", false}, - } { - t.Run(tc.name, func(t *testing.T) { - cfg, err := config.NewRocketPoolConfig(t.TempDir(), false) - if err != nil { - t.Fatal(err) - } - cfg.ChangeNetwork(cfgtypes.Network(tc.from)) - cmd := &cli.Command{ - Name: "config", - Flags: []cli.Flag{&cli.StringFlag{Name: "smartnode-network"}, &cli.StringFlag{Name: "executionClient"}}, - Action: func(_ context.Context, c *cli.Command) error { return updateConfigFromCliArgs(c, cfg) }, - } - args := []string{"config", "--smartnode-network", tc.to} - if tc.client != "" { - args = append(args, "--executionClient", tc.client) - } - err = cmd.Run(context.Background(), args) - if tc.wantError { - if err == nil || !strings.Contains(err.Error(), "valid options") { - t.Fatalf("unsupported client accepted: %v", err) - } - return - } - if err != nil { - t.Fatal(err) - } - if cfg.GetNetwork() != cfgtypes.Network(tc.to) { - t.Fatal("network was not changed") - } - if tc.to == "plataberget" && cfg.ExecutionClient.Value != cfgtypes.ExecutionClient_Nethermind { - t.Fatal("headless selection did not apply the network's default client") - } - }) - } -} diff --git a/shared/services/beacon/client/slot-duration_test.go b/shared/services/beacon/client/slot-duration_test.go index 21dd6aa143..6b60618bf4 100644 --- a/shared/services/beacon/client/slot-duration_test.go +++ b/shared/services/beacon/client/slot-duration_test.go @@ -22,10 +22,14 @@ func TestBeaconSlotDuration(t *testing.T) { eth2ConfigCache.Store(nil) t.Cleanup(func() { eth2ConfigCache.Store(nil) }) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var err error if r.URL.Path == RequestEth2ConfigPath { - fmt.Fprintf(w, `{"data":{%s,"SLOTS_PER_EPOCH":"32","GLOAS_FORK_EPOCH":"1536"}}`, tc.fields) + _, err = fmt.Fprintf(w, `{"data":{%s,"SLOTS_PER_EPOCH":"32","GLOAS_FORK_EPOCH":"1536"}}`, tc.fields) } else { - fmt.Fprint(w, `{"data":{"genesis_time":"1786622400","genesis_fork_version":"0x10733183","genesis_validators_root":"0xbb4a1a9e3f7f4e10edcd734e4acc3b5ffd4f830efe0af2748fa458cfee5d2658"}}`) + _, err = fmt.Fprint(w, `{"data":{"genesis_time":"1786622400","genesis_fork_version":"0x10733183","genesis_validators_root":"0xbb4a1a9e3f7f4e10edcd734e4acc3b5ffd4f830efe0af2748fa458cfee5d2658"}}`) + } + if err != nil { + t.Errorf("write beacon response: %v", err) } })) defer server.Close() diff --git a/shared/services/config/network-clients_test.go b/shared/services/config/network-clients_test.go deleted file mode 100644 index 14b06b6246..0000000000 --- a/shared/services/config/network-clients_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package config - -import ( - "reflect" - "strings" - "testing" - - "github.com/rocket-pool/smartnode/shared/types/config" -) - -func optionValues(param *config.Parameter) []any { - values := make([]any, len(param.Options)) - for i, option := range param.Options { - values[i] = option.Value - } - return values -} - -func TestPlatabergetConfiguration(t *testing.T) { - cfg := mustNewRocketPoolConfig(t, "", false) - cfg.ExecutionClient.Value = config.ExecutionClient_Besu - cfg.ConsensusClient.Value = config.ConsensusClient_Prysm - cfg.EnableCommitBoost.Value = true - cfg.ChangeNetwork("plataberget") - if got := cfg.Smartnode.GetChainID(); got != 7091047534 { - t.Fatalf("chain ID: %d", got) - } - if cfg.Smartnode.GetBeaconNetwork() != "plataberget" || cfg.Smartnode.GetCustomChainConfigDir() != "" { - t.Fatal("Plataberget must use a named network without custom chain files") - } - if cfg.ExecutionClient.Value != config.ExecutionClient_Nethermind || cfg.ConsensusClient.Value != config.ConsensusClient_Nimbus { - t.Fatalf("unsupported selections were not replaced: %v / %v", cfg.ExecutionClient.Value, cfg.ConsensusClient.Value) - } - if cfg.EnableMevBoost.Value != false || cfg.EnableCommitBoost.Value != false { - t.Fatal("PBS must be disabled") - } - if cfg.ConsensusCommon.CheckpointSyncProvider.Value != "https://checkpoint-sync.plataberget.ethpandaops.io" { - t.Fatal("wrong checkpoint sync default") - } - if problems := cfg.Validate(); len(problems) != 0 { - t.Fatalf("pending contracts must not prevent saving settings: %v", problems) - } - if err := cfg.ValidateNetworkForStart(); err == nil || !strings.Contains(err.Error(), "contracts are not configured") { - t.Fatalf("startup should report pending contracts: %v", err) - } - if cfg.Smartnode.GetStorageAddress() != "" || cfg.GetNetworkInfo().Addresses.Reth != "" { - t.Fatal("contract addresses must remain unset") - } - wantEC := []any{config.ExecutionClient_Nethermind} - wantCC := []any{config.ConsensusClient_Lodestar, config.ConsensusClient_Nimbus, config.ConsensusClient_Teku} - for _, candidate := range []*RocketPoolConfig{cfg, cfg.CreateCopy()} { - if !reflect.DeepEqual(optionValues(&candidate.ExecutionClient), wantEC) { - t.Fatalf("execution choices: %v", optionValues(&candidate.ExecutionClient)) - } - for _, param := range []*config.Parameter{&candidate.ConsensusClient, &candidate.ExternalConsensusClient, &candidate.Native.ConsensusClient} { - if !reflect.DeepEqual(optionValues(param), wantCC) { - t.Fatalf("%s choices: %v", param.ID, optionValues(param)) - } - } - } - loaded := mustNewRocketPoolConfig(t, "", false) - if err := loaded.Deserialize(cfg.Serialize()); err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(optionValues(&loaded.ConsensusClient), wantCC) || loaded.GetNetwork() != "plataberget" { - t.Fatal("saved network restrictions were lost") - } - loaded.ChangeNetwork("testnet") - if len(loaded.ExecutionClient.Options) != 5 || len(loaded.ConsensusClient.Options) != 5 || len(loaded.Native.ConsensusClient.Options) != 5 { - t.Fatal("switching to Hoodi did not restore unrestricted choices") - } - if loaded.Smartnode.GetBeaconNetwork() != "hoodi" || loaded.ConsensusCommon.CheckpointSyncProvider.Value != "" { - t.Fatal("Hoodi defaults were not restored") - } -} - -func TestPlatabergetSavedUnsupportedClients(t *testing.T) { - for _, mode := range []string{"local", "external", "native"} { - t.Run(mode, func(t *testing.T) { - cfg := mustNewRocketPoolConfig(t, "", mode == "native") - cfg.ChangeNetwork("plataberget") - switch mode { - case "local": - cfg.ExecutionClient.Value = config.ExecutionClient_Geth - case "external": - cfg.ExecutionClientMode.Value = config.Mode_External - cfg.ConsensusClientMode.Value = config.Mode_External - cfg.ExternalConsensusClient.Value = config.ConsensusClient_Lighthouse - case "native": - cfg.Native.ConsensusClient.Value = config.ConsensusClient_Prysm - } - loaded := mustNewRocketPoolConfig(t, "", false) - if err := loaded.Deserialize(cfg.Serialize()); err != nil { - t.Fatal(err) - } - if err := loaded.ValidateNetworkClients(); err == nil || !strings.Contains(err.Error(), "not supported on Platåberget") { - t.Fatalf("unsupported saved client accepted: %v", err) - } - }) - } -} - -func TestPlatabergetSupportedClientsAndDeployment(t *testing.T) { - cfg := mustNewRocketPoolConfig(t, "", false) - cfg.ChangeNetwork("plataberget") - // A deployment replaces the pending network entry; never ship this fixture address. - network := cfg.GetNetworkInfo() - network.Addresses.Storage = "0x1111111111111111111111111111111111111111" - if err := cfg.ValidateNetworkForStart(); err == nil { - t.Fatal("pending flag must still block partial deployments") - } - network.ContractsPending = false - for _, cc := range []config.ConsensusClient{config.ConsensusClient_Lodestar, config.ConsensusClient_Nimbus, config.ConsensusClient_Teku} { - cfg.ConsensusClient.Value = cc - if err := cfg.ValidateNetworkForStart(); err != nil { - t.Fatalf("%s: %v", cc, err) - } - } - cfg.EnableMevBoost.Value = true - if err := cfg.ValidateNetworkClients(); err == nil { - t.Fatal("unsupported PBS configuration accepted") - } -} - -func TestNetworkClientSchemaValidation(t *testing.T) { - for _, extra := range []string{ - "executionClients: [unknown]", "consensusClients: [unknown]", - "executionClients: [nethermind, nethermind]", "consensusClients: [teku, teku]", - "isProduction: true", "contractsPending: false", - } { - body := "version: 1\nnetworks:\n - name: pending\n label: Pending\n description: Pending network\n chainID: 123\n beaconNetwork: pending\n contractsPending: true\n " + extra + "\n" - if _, err := parseNetworksYAML([]byte(body), "test", true); err == nil { - t.Fatalf("invalid network accepted: %s", extra) - } - } -} diff --git a/shared/services/rocketpool/plataberget_test.go b/shared/services/rocketpool/plataberget_test.go deleted file mode 100644 index d9c9425c72..0000000000 --- a/shared/services/rocketpool/plataberget_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package rocketpool - -import ( - "bytes" - "os" - "path/filepath" - "strings" - "testing" - "text/template" - - "github.com/rocket-pool/smartnode/shared/services/config" - cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" - "gopkg.in/yaml.v2" -) - -// Rendering must not query public IP services during a unit test. -type offlineNetworkTemplateConfig struct{ *config.RocketPoolConfig } - -func (offlineNetworkTemplateConfig) GetExternalIp() string { return "192.0.2.1" } - -func TestPlatabergetNamedNetworkTemplates(t *testing.T) { - cfg, err := config.NewRocketPoolConfig(t.TempDir(), false) - if err != nil { - t.Fatal(err) - } - cfg.ChangeNetwork("plataberget") - // Match service startup, which loads the serialized choice types from disk. - if err := cfg.Deserialize(cfg.Serialize()); err != nil { - t.Fatal(err) - } - for _, cc := range []cfgtypes.ConsensusClient{cfgtypes.ConsensusClient_Lodestar, cfgtypes.ConsensusClient_Nimbus, cfgtypes.ConsensusClient_Teku} { - t.Run(string(cc), func(t *testing.T) { - cfg.ConsensusClient.Value = cc - for _, service := range []string{"eth1", "eth2", "validator"} { - body, err := os.ReadFile(filepath.Join("assets", "install", "templates", service+".tmpl")) - if err != nil { - t.Fatal(err) - } - tmpl, err := template.New(service).Parse(string(body)) - if err != nil { - t.Fatal(err) - } - var rendered bytes.Buffer - if err := tmpl.Execute(&rendered, offlineNetworkTemplateConfig{cfg}); err != nil { - t.Fatal(err) - } - var compose map[string]any - if err := yaml.Unmarshal(rendered.Bytes(), &compose); err != nil { - t.Fatalf("invalid %s YAML: %v", service, err) - } - text := rendered.String() - if !strings.Contains(text, "BEACON_NETWORK=plataberget") || strings.Contains(text, "CUSTOM_CHAIN_DIR") || strings.Contains(text, "/custom-chain") { - t.Fatalf("%s did not use the named preset", service) - } - client := "CLIENT=nethermind" - if service != "eth1" { - client = "CC_CLIENT=" + string(cc) - } - if !strings.Contains(text, client) || strings.Contains(text, "devnet") { - t.Fatalf("unexpected client or devnet startup in %s", service) - } - } - }) - } -}