From 42efe6381a6d89e34704cbb764e36294472ef28e Mon Sep 17 00:00:00 2001 From: AntiD2ta Date: Fri, 26 Jun 2026 12:27:00 +0200 Subject: [PATCH 1/2] Add --custom-spec-support flag for non-mainnet SSZ presets Build the go-eth2-client HTTP client with WithCustomSpecSupport when the new --custom-spec-support persistent flag is set, switching from the static mainnet fastssz decoder to dynamic-ssz. Without it, decoding a non-mainnet preset beacon state (e.g. Gnosis) fails with: invalid ssz encoding. first variable element offset indexes into fixed value data because the static mainnet-preset decoder misreads the variable-length offsets of a Gnosis-preset BeaconState. This blocks "validator exit --prepare-offline" against a Gnosis node, which fetches the full beacon state via SSZ. Threads the flag through ConnectOpts and connectToBeaconNode, and wires it into the validator exit command's ConnectToBeaconNode call, mirroring the existing --allow-insecure-connections plumbing. --- cmd/root.go | 4 ++++ cmd/validator/exit/command.go | 2 ++ cmd/validator/exit/process.go | 9 +++++---- util/beaconnode.go | 18 ++++++++++-------- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 46d79070..fee44aeb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -253,6 +253,10 @@ func addPersistentFlags() { if err := viper.BindPFlag("allow-insecure-connections", RootCmd.PersistentFlags().Lookup("allow-insecure-connections")); err != nil { panic(err) } + RootCmd.PersistentFlags().Bool("custom-spec-support", false, "use dynamic SSZ to support non-mainnet presets (e.g. Gnosis); slower than the static decoder") + if err := viper.BindPFlag("custom-spec-support", RootCmd.PersistentFlags().Lookup("custom-spec-support")); err != nil { + panic(err) + } } // initConfig reads in config file and ENV variables if set. diff --git a/cmd/validator/exit/command.go b/cmd/validator/exit/command.go index b6a8d3ae..01fa159b 100644 --- a/cmd/validator/exit/command.go +++ b/cmd/validator/exit/command.go @@ -50,6 +50,7 @@ type command struct { timeout time.Duration connection string allowInsecureConnections bool + customSpecSupport bool // Information required to generate the operations. chainInfo *beacon.ChainInfo @@ -73,6 +74,7 @@ func newCommand(_ context.Context) (*command, error) { timeout: viper.GetDuration("timeout"), connection: viper.GetString("connection"), allowInsecureConnections: viper.GetBool("allow-insecure-connections"), + customSpecSupport: viper.GetBool("custom-spec-support"), prepareOffline: viper.GetBool("prepare-offline"), passphrases: util.GetPassphrases(), mnemonic: viper.GetString("mnemonic"), diff --git a/cmd/validator/exit/process.go b/cmd/validator/exit/process.go index c9bbc16e..e955e040 100644 --- a/cmd/validator/exit/process.go +++ b/cmd/validator/exit/process.go @@ -595,10 +595,11 @@ func (c *command) setup(ctx context.Context) error { // Connect to the consensus node. var err error c.consensusClient, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{ - Address: c.connection, - Timeout: c.timeout, - AllowInsecure: c.allowInsecureConnections, - LogFallback: !c.quiet, + Address: c.connection, + Timeout: c.timeout, + AllowInsecure: c.allowInsecureConnections, + CustomSpecSupport: c.customSpecSupport, + LogFallback: !c.quiet, }) if err != nil { return err diff --git a/util/beaconnode.go b/util/beaconnode.go index 54af7b64..7c09b15e 100644 --- a/util/beaconnode.go +++ b/util/beaconnode.go @@ -38,10 +38,11 @@ var defaultBeaconNodeAddresses = []string{ var fallbackBeaconNode = "http://mainnet-consensus.attestant.io/" type ConnectOpts struct { - Address string - Timeout time.Duration - AllowInsecure bool - LogFallback bool + Address string + Timeout time.Duration + AllowInsecure bool + CustomSpecSupport bool + LogFallback bool } // ConnectToBeaconNode connects to a beacon node at the given address. @@ -56,12 +57,12 @@ func ConnectToBeaconNode(ctx context.Context, opts *ConnectOpts) (eth2client.Ser if opts.Address != "" { // We have an explicit address; use it. - return connectToBeaconNode(ctx, opts.Address, opts.Timeout, opts.AllowInsecure) + return connectToBeaconNode(ctx, opts.Address, opts.Timeout, opts.AllowInsecure, opts.CustomSpecSupport) } // Try the defaults. for _, address := range defaultBeaconNodeAddresses { - client, err := connectToBeaconNode(ctx, address, opts.Timeout, opts.AllowInsecure) + client, err := connectToBeaconNode(ctx, address, opts.Timeout, opts.AllowInsecure, opts.CustomSpecSupport) if err == nil { return client, nil } @@ -71,7 +72,7 @@ func ConnectToBeaconNode(ctx context.Context, opts *ConnectOpts) (eth2client.Ser if opts.LogFallback { fmt.Fprintf(os.Stderr, "No connection supplied with --connection parameter and no local beacon node found, attempting to use mainnet fallback\n") } - client, err := connectToBeaconNode(ctx, fallbackBeaconNode, opts.Timeout, true) + client, err := connectToBeaconNode(ctx, fallbackBeaconNode, opts.Timeout, true, opts.CustomSpecSupport) if err == nil { return client, nil } @@ -79,7 +80,7 @@ func ConnectToBeaconNode(ctx context.Context, opts *ConnectOpts) (eth2client.Ser return nil, errors.New("failed to connect to any beacon node") } -func connectToBeaconNode(ctx context.Context, address string, timeout time.Duration, allowInsecure bool) (eth2client.Service, error) { +func connectToBeaconNode(ctx context.Context, address string, timeout time.Duration, allowInsecure bool, customSpecSupport bool) (eth2client.Service, error) { if !strings.HasPrefix(address, "http") { address = fmt.Sprintf("http://%s", address) } @@ -101,6 +102,7 @@ func connectToBeaconNode(ctx context.Context, address string, timeout time.Durat http.WithLogLevel(zerolog.Disabled), http.WithAddress(address), http.WithTimeout(timeout), + http.WithCustomSpecSupport(customSpecSupport), ) if err != nil { return nil, errors.Wrap(err, "failed to connect to beacon node") From 7e85a265b5e1f14b9b7b0f765a784a248577899e Mon Sep 17 00:00:00 2001 From: AntiD2ta Date: Fri, 26 Jun 2026 15:18:17 +0200 Subject: [PATCH 2/2] Refine --custom-spec-support flag help text and connection plumbing - Reword the --custom-spec-support help string to match the register of neighboring flag descriptions and disclose that it is currently honoured only by the 'validator exit' command. - Add a doc comment to the exported ConnectOpts type. - Replace the adjacent positional booleans in connectToBeaconNode with a named-field beaconNodeConnection struct so the three call sites are self-documenting; the mainnet fallback's forced allowInsecure is now explicit at the call site. Behaviour-preserving cleanup; no functional change to the feature. --- cmd/root.go | 2 +- util/beaconnode.go | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index fee44aeb..173d4591 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -253,7 +253,7 @@ func addPersistentFlags() { if err := viper.BindPFlag("allow-insecure-connections", RootCmd.PersistentFlags().Lookup("allow-insecure-connections")); err != nil { panic(err) } - RootCmd.PersistentFlags().Bool("custom-spec-support", false, "use dynamic SSZ to support non-mainnet presets (e.g. Gnosis); slower than the static decoder") + RootCmd.PersistentFlags().Bool("custom-spec-support", false, "use dynamic SSZ decoding for non-mainnet presets such as Gnosis (slower; currently honoured only by 'validator exit')") if err := viper.BindPFlag("custom-spec-support", RootCmd.PersistentFlags().Lookup("custom-spec-support")); err != nil { panic(err) } diff --git a/util/beaconnode.go b/util/beaconnode.go index 7c09b15e..01bc245d 100644 --- a/util/beaconnode.go +++ b/util/beaconnode.go @@ -37,6 +37,7 @@ var defaultBeaconNodeAddresses = []string{ // fallbackBeaconNode is used if no other connection is supplied. var fallbackBeaconNode = "http://mainnet-consensus.attestant.io/" +// ConnectOpts are the options for connecting to a beacon node. type ConnectOpts struct { Address string Timeout time.Duration @@ -57,12 +58,22 @@ func ConnectToBeaconNode(ctx context.Context, opts *ConnectOpts) (eth2client.Ser if opts.Address != "" { // We have an explicit address; use it. - return connectToBeaconNode(ctx, opts.Address, opts.Timeout, opts.AllowInsecure, opts.CustomSpecSupport) + return connectToBeaconNode(ctx, &beaconNodeConnection{ + address: opts.Address, + timeout: opts.Timeout, + allowInsecure: opts.AllowInsecure, + customSpecSupport: opts.CustomSpecSupport, + }) } // Try the defaults. for _, address := range defaultBeaconNodeAddresses { - client, err := connectToBeaconNode(ctx, address, opts.Timeout, opts.AllowInsecure, opts.CustomSpecSupport) + client, err := connectToBeaconNode(ctx, &beaconNodeConnection{ + address: address, + timeout: opts.Timeout, + allowInsecure: opts.AllowInsecure, + customSpecSupport: opts.CustomSpecSupport, + }) if err == nil { return client, nil } @@ -72,7 +83,12 @@ func ConnectToBeaconNode(ctx context.Context, opts *ConnectOpts) (eth2client.Ser if opts.LogFallback { fmt.Fprintf(os.Stderr, "No connection supplied with --connection parameter and no local beacon node found, attempting to use mainnet fallback\n") } - client, err := connectToBeaconNode(ctx, fallbackBeaconNode, opts.Timeout, true, opts.CustomSpecSupport) + client, err := connectToBeaconNode(ctx, &beaconNodeConnection{ + address: fallbackBeaconNode, + timeout: opts.Timeout, + allowInsecure: true, + customSpecSupport: opts.CustomSpecSupport, + }) if err == nil { return client, nil } @@ -80,11 +96,20 @@ func ConnectToBeaconNode(ctx context.Context, opts *ConnectOpts) (eth2client.Ser return nil, errors.New("failed to connect to any beacon node") } -func connectToBeaconNode(ctx context.Context, address string, timeout time.Duration, allowInsecure bool, customSpecSupport bool) (eth2client.Service, error) { +// beaconNodeConnection holds the parameters for a single beacon node connection attempt. +type beaconNodeConnection struct { + address string + timeout time.Duration + allowInsecure bool + customSpecSupport bool +} + +func connectToBeaconNode(ctx context.Context, conn *beaconNodeConnection) (eth2client.Service, error) { + address := conn.address if !strings.HasPrefix(address, "http") { address = fmt.Sprintf("http://%s", address) } - if !allowInsecure { + if !conn.allowInsecure { // Ensure the connection is either secure or local. connectionURL, err := url.Parse(address) if err != nil { @@ -101,8 +126,8 @@ func connectToBeaconNode(ctx context.Context, address string, timeout time.Durat eth2Client, err := http.New(ctx, http.WithLogLevel(zerolog.Disabled), http.WithAddress(address), - http.WithTimeout(timeout), - http.WithCustomSpecSupport(customSpecSupport), + http.WithTimeout(conn.timeout), + http.WithCustomSpecSupport(conn.customSpecSupport), ) if err != nil { return nil, errors.Wrap(err, "failed to connect to beacon node")