Skip to content

Enable Cubist Signer integration #3965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 65 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
dd5176e
Add comments to signer-config setup
richardpringle Feb 17, 2025
ed0c5f8
Extend siging configuration to include RPC
richardpringle Feb 14, 2025
dd99c36
Add default behaviour to switch
richardpringle Feb 19, 2025
9f61b1c
Add timeout to signer instantiation
richardpringle Feb 19, 2025
f106e92
Make rpc-signer client handle the connection
richardpringle Feb 20, 2025
de574a4
Fix linter error
richardpringle Feb 21, 2025
a7e895a
Close the connection on any error
richardpringle Feb 21, 2025
15a53f0
Remove special character from error message
richardpringle Mar 12, 2025
cc11cac
Use Go casing conventions in json config
richardpringle Mar 12, 2025
eeb3644
Fix default signing config behaviour
richardpringle Mar 12, 2025
47e37fa
Cleanup type-check in config-test
richardpringle Mar 13, 2025
505c7fb
Simply variable name for url
richardpringle Mar 13, 2025
0d66c3a
Only use public config api in test
richardpringle Mar 13, 2025
f8837e8
Add test for default config signer with multiple inits
richardpringle Mar 13, 2025
97ffb21
Merge remote-tracking branch 'origin/master' into signers-config-wip
richardpringle Mar 14, 2025
7327d0f
Merge remote-tracking branch 'origin/master' into signers-config-wip
richardpringle Mar 18, 2025
bb56fd5
Bubble up cleanup function
richardpringle Mar 18, 2025
0b22c0a
Merge remote-tracking branch 'origin/master' into signers-config-wip
richardpringle Mar 24, 2025
d3e2677
Merge remote-tracking branch 'origin/master' into signers-config-wip
richardpringle Mar 24, 2025
193ccd4
Merge remote-tracking branch 'origin/master' into signers-config-wip
richardpringle Mar 28, 2025
bd77f88
Simplify default signer creation code
richardpringle Mar 28, 2025
5bc1bc0
Cleanup signer creation when key path is set
richardpringle Mar 28, 2025
64f1a1d
Remove redundant word in error message
richardpringle Mar 28, 2025
b55f05d
Fix config test name
richardpringle Mar 28, 2025
e85e815
Small rpc-signer-client cleanup refactor
richardpringle Mar 28, 2025
c8b3714
Fix the min-connect-timeout for grpc signer
richardpringle Mar 28, 2025
c46209e
Merge branch 'master' into signers-config-wip
geoff-vball May 5, 2025
8a4cb7c
Merge branch 'master' into signers-config-wip
geoff-vball May 13, 2025
99c3a48
Reconfigure signer setup
geoff-vball May 15, 2025
e23e982
Merge branch 'master' into signers-config-wip
geoff-vball May 15, 2025
e408079
Refactor config
geoff-vball May 15, 2025
c34b691
Reduce diff
geoff-vball May 15, 2025
446b34f
Merge branch 'master' into signers-config-wip
geoff-vball May 27, 2025
de822f3
Fix tests
geoff-vball May 27, 2025
80ed485
lint
geoff-vball May 27, 2025
a6d8add
Add logging to signer creation
geoff-vball May 27, 2025
649447d
Merge branch 'master' into signers-config-wip
geoff-vball May 27, 2025
d79916b
Fix ordering
geoff-vball May 27, 2025
14c5525
Lint
geoff-vball May 27, 2025
fac9f79
Merge branch 'master' into signers-config-wip
geoff-vball May 28, 2025
8fa5525
Remove json tags from fields that are not user supplied
geoff-vball May 28, 2025
0c15985
Remove stray comment
geoff-vball May 29, 2025
15db2ad
Merge branch 'master' into signers-config-wip
geoff-vball Jun 3, 2025
58cc0c1
Update utils/crypto/bls/signer/rpcsigner/client.go
geoff-vball Jun 17, 2025
f92d3be
Review fixes
geoff-vball Jun 17, 2025
270d59a
Merge branch 'signers-config-wip' of github.com:ava-labs/avalanche-go…
geoff-vball Jun 17, 2025
60526b1
Move signer to node package
geoff-vball Jun 17, 2025
9791ff8
Merge branch 'master' into signers-config-wip
geoff-vball Jun 17, 2025
ba4977a
Add missing files
geoff-vball Jun 18, 2025
3283340
Wrap error
geoff-vball Jun 24, 2025
d6512c2
Lint
geoff-vball Jun 24, 2025
3aaeda8
Merge branch 'master' into signers-config-wip
geoff-vball Jul 1, 2025
7018c21
Review fixes
geoff-vball Jul 7, 2025
945b7a1
clean diff
joshua-kim Jul 8, 2025
a178493
Merge branch 'master' into signers-config-wip
geoff-vball Jul 10, 2025
931fb42
Defer cancel
geoff-vball Jul 10, 2025
bed4148
Move signer to node.go
geoff-vball Jul 15, 2025
d07341b
Merge branch 'master' into signers-config-wip
geoff-vball Jul 15, 2025
b769bb0
nits
joshua-kim Jul 16, 2025
dc213df
remove env var
joshua-kim Jul 16, 2025
872b353
style nits
joshua-kim Jul 16, 2025
8f15194
Update config/config_test.go
geoff-vball Jul 16, 2025
6170ab1
Update config/config_test.go
geoff-vball Jul 16, 2025
f33fbe7
Update config/config_test.go
geoff-vball Jul 16, 2025
1dbf5dd
dataDir
geoff-vball Jul 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions api/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package server

import (
"context"
"errors"
"fmt"
"net"
"net/http"
Expand Down Expand Up @@ -287,12 +288,13 @@ func (s *server) AddAliasesWithReadLock(endpoint string, aliases ...string) erro

func (s *server) Shutdown() error {
ctx, cancel := context.WithTimeout(context.Background(), s.shutdownTimeout)
err := s.srv.Shutdown(ctx)
listenerErr := s.listener.Close()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't getting closed during node.Shutdown() which was causing an error starting up a new node in the same test.

serverErr := s.srv.Shutdown(ctx)
cancel()

// If shutdown times out, make sure the server is still shutdown.
_ = s.srv.Close()
return err
return errors.Join(listenerErr, serverErr)
}

type readPathAdder struct {
Expand Down
113 changes: 46 additions & 67 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"math"
"os"
"path/filepath"
Expand All @@ -34,10 +33,9 @@ import (
"github.com/ava-labs/avalanchego/subnets"
"github.com/ava-labs/avalanchego/trace"
"github.com/ava-labs/avalanchego/upgrade"
"github.com/ava-labs/avalanchego/utils/bag"
"github.com/ava-labs/avalanchego/utils/compression"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/crypto/bls"
"github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner"
"github.com/ava-labs/avalanchego/utils/ips"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/utils/perms"
Expand Down Expand Up @@ -78,11 +76,11 @@ var (
errCannotTrackPrimaryNetwork = errors.New("cannot track primary network")
errStakingKeyContentUnset = fmt.Errorf("%s key not set but %s set", StakingTLSKeyContentKey, StakingCertContentKey)
errStakingCertContentUnset = fmt.Errorf("%s key set but %s not set", StakingTLSKeyContentKey, StakingCertContentKey)
errMissingStakingSigningKeyFile = errors.New("missing staking signing key file")
errPluginDirNotADirectory = errors.New("plugin dir is not a directory")
errCannotReadDirectory = errors.New("cannot read directory")
errUnmarshalling = errors.New("unmarshalling failed")
errFileDoesNotExist = errors.New("file does not exist")
errInvalidSignerConfig = fmt.Errorf("only one of the following flags can be set: %s, %s, %s, %s", StakingEphemeralSignerEnabledKey, StakingSignerKeyContentKey, StakingSignerKeyPathKey, StakingRPCSignerEndpointKey)
)

func getConsensusConfig(v *viper.Viper) snowball.Parameters {
Expand Down Expand Up @@ -639,74 +637,15 @@ func getStakingTLSCert(v *viper.Viper) (tls.Certificate, error) {
}
}

func getStakingSigner(v *viper.Viper) (bls.Signer, error) {
if v.GetBool(StakingEphemeralSignerEnabledKey) {
key, err := localsigner.New()
if err != nil {
return nil, fmt.Errorf("couldn't generate ephemeral signing key: %w", err)
}
return key, nil
}

if v.IsSet(StakingSignerKeyContentKey) {
signerKeyRawContent := v.GetString(StakingSignerKeyContentKey)
signerKeyContent, err := base64.StdEncoding.DecodeString(signerKeyRawContent)
if err != nil {
return nil, fmt.Errorf("unable to decode base64 content: %w", err)
}
key, err := localsigner.FromBytes(signerKeyContent)
if err != nil {
return nil, fmt.Errorf("couldn't parse signing key: %w", err)
}
return key, nil
}

signingKeyPath := getExpandedArg(v, StakingSignerKeyPathKey)
_, err := os.Stat(signingKeyPath)
if !errors.Is(err, fs.ErrNotExist) {
signingKeyBytes, err := os.ReadFile(signingKeyPath)
if err != nil {
return nil, err
}
key, err := localsigner.FromBytes(signingKeyBytes)
if err != nil {
return nil, fmt.Errorf("couldn't parse signing key: %w", err)
}
return key, nil
}

if v.IsSet(StakingSignerKeyPathKey) {
return nil, errMissingStakingSigningKeyFile
}

key, err := localsigner.New()
if err != nil {
return nil, fmt.Errorf("couldn't generate new signing key: %w", err)
}

if err := os.MkdirAll(filepath.Dir(signingKeyPath), perms.ReadWriteExecute); err != nil {
return nil, fmt.Errorf("couldn't create path for signing key at %s: %w", signingKeyPath, err)
}

keyBytes := key.ToBytes()
if err := os.WriteFile(signingKeyPath, keyBytes, perms.ReadWrite); err != nil {
return nil, fmt.Errorf("couldn't write new signing key to %s: %w", signingKeyPath, err)
}
if err := os.Chmod(signingKeyPath, perms.ReadOnly); err != nil {
return nil, fmt.Errorf("couldn't restrict permissions on new signing key at %s: %w", signingKeyPath, err)
}
return key, nil
}

func getStakingConfig(v *viper.Viper, networkID uint32) (node.StakingConfig, error) {
config := node.StakingConfig{
SybilProtectionEnabled: v.GetBool(SybilProtectionEnabledKey),
SybilProtectionDisabledWeight: v.GetUint64(SybilProtectionDisabledWeightKey),
PartialSyncPrimaryNetwork: v.GetBool(PartialSyncPrimaryNetworkKey),
StakingKeyPath: getExpandedArg(v, StakingTLSKeyPathKey),
StakingCertPath: getExpandedArg(v, StakingCertPathKey),
StakingSignerPath: getExpandedArg(v, StakingSignerKeyPathKey),
StakingTLSKeyPath: getExpandedArg(v, StakingTLSKeyPathKey),
StakingTLSCertPath: getExpandedArg(v, StakingCertPathKey),
Comment on lines +645 to +646
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed these to disambiguate from the bls signing items.

}

if !config.SybilProtectionEnabled && config.SybilProtectionDisabledWeight == 0 {
return node.StakingConfig{}, errSybilProtectionDisabledStakerWeights
}
Expand All @@ -720,10 +659,12 @@ func getStakingConfig(v *viper.Viper, networkID uint32) (node.StakingConfig, err
if err != nil {
return node.StakingConfig{}, err
}
config.StakingSigningKey, err = getStakingSigner(v)

config.StakingSignerConfig, err = getStakingSignerConfig(v)
if err != nil {
return node.StakingConfig{}, err
}

if networkID != constants.MainnetID && networkID != constants.FujiID {
config.UptimeRequirement = v.GetFloat64(UptimeRequirementKey)
config.MinValidatorStake = v.GetUint64(MinValidatorStakeKey)
Expand Down Expand Up @@ -760,6 +701,44 @@ func getStakingConfig(v *viper.Viper, networkID uint32) (node.StakingConfig, err
return config, nil
}

func getStakingSignerConfig(v *viper.Viper) (any, error) {
// A maximum of one signer option can be set
bools := bag.Of(
v.GetBool(StakingEphemeralSignerEnabledKey),
v.IsSet(StakingSignerKeyContentKey),
v.IsSet(StakingSignerKeyPathKey),
v.IsSet(StakingRPCSignerEndpointKey),
)
if bools.Count(true) > 1 {
return node.StakingConfig{}, errInvalidSignerConfig
}

switch {
case v.GetBool(StakingEphemeralSignerEnabledKey):
return node.EphemeralSignerConfig{}, nil

case v.IsSet(StakingSignerKeyContentKey):
return node.ContentKeyConfig{
SignerKeyRawContent: getExpandedArg(v, StakingSignerKeyContentKey),
}, nil

case v.IsSet(StakingRPCSignerEndpointKey):
return node.RPCSignerConfig{
StakingSignerRPC: getExpandedArg(v, StakingRPCSignerEndpointKey),
}, nil

case v.IsSet(StakingSignerKeyPathKey):
return node.SignerPathConfig{
SignerKeyPath: getExpandedArg(v, StakingSignerKeyPathKey),
}, nil

default:
return node.DefaultSignerConfig{
SignerKeyPath: getExpandedArg(v, StakingSignerKeyPathKey),
}, nil
}
}

func getTxFeeConfig(v *viper.Viper, networkID uint32) genesis.TxFeeConfig {
if networkID != constants.MainnetID && networkID != constants.FujiID {
return genesis.TxFeeConfig{
Expand Down
81 changes: 81 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/chains"
"github.com/ava-labs/avalanchego/config/node"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow/consensus/snowball"
"github.com/ava-labs/avalanchego/subnets"
Expand Down Expand Up @@ -549,6 +550,86 @@ func TestGetSubnetConfigsFromFlags(t *testing.T) {
}
}

func TestGetStakingSigner(t *testing.T) {
testKey := "HLimS3vRibTMk9lZD4b+Z+GLuSBShvgbsu0WTLt2Kd4="
dataDir := t.TempDir()

fileKeyPath := filepath.Join(t.TempDir(), "foobar", "signer.key")
defaultSignerKeyPath := filepath.Join(
dataDir,
"staking",
"signer.key",
)

tests := []struct {
name string
viperKeys string
config map[string]any
expectedSignerConfig any
expectedErr error
}{
{
name: "default signer",
config: map[string]any{DataDirKey: dataDir},
expectedSignerConfig: node.DefaultSignerConfig{
SignerKeyPath: defaultSignerKeyPath,
},
},
{
name: "ephemeral signer",
config: map[string]any{StakingEphemeralSignerEnabledKey: true},
expectedSignerConfig: node.EphemeralSignerConfig{},
},
{
name: "content key",
config: map[string]any{StakingSignerKeyContentKey: testKey},
expectedSignerConfig: node.ContentKeyConfig{
SignerKeyRawContent: testKey,
},
},
{
name: "file key",
config: map[string]any{
StakingSignerKeyPathKey: fileKeyPath,
},
expectedSignerConfig: node.SignerPathConfig{
SignerKeyPath: fileKeyPath,
},
},
{
name: "rpc signer",
config: map[string]any{StakingRPCSignerEndpointKey: "localhost"},
expectedSignerConfig: node.RPCSignerConfig{
StakingSignerRPC: "localhost",
},
},
{
name: "multiple configurations set",
config: map[string]any{
StakingEphemeralSignerEnabledKey: true,
StakingSignerKeyContentKey: testKey,
},
expectedErr: errInvalidSignerConfig,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
v := setupViperFlags()

for key, value := range tt.config {
v.Set(key, value)
}

config, err := GetNodeConfig(v)

require.ErrorIs(err, tt.expectedErr)
require.Equal(tt.expectedSignerConfig, config.StakingSignerConfig)
})
}
}

// setups config json file and writes content
func setupConfigJSON(t *testing.T, rootPath string, value string) string {
configFilePath := filepath.Join(rootPath, "config.json")
Expand Down
1 change: 1 addition & 0 deletions config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ func addNodeFlags(fs *pflag.FlagSet) {
fs.Bool(StakingEphemeralSignerEnabledKey, false, "If true, the node uses an ephemeral staking signer key")
fs.String(StakingSignerKeyPathKey, defaultStakingSignerKeyPath, fmt.Sprintf("Path to the signer private key for staking. Ignored if %s is specified", StakingSignerKeyContentKey))
fs.String(StakingSignerKeyContentKey, "", "Specifies base64 encoded signer private key for staking")
fs.String(StakingRPCSignerEndpointKey, "", "Specifies the RPC endpoint of the staking signer")
fs.Bool(SybilProtectionEnabledKey, true, "Enables sybil protection. If enabled, Network TLS is required")
fs.Uint64(SybilProtectionDisabledWeightKey, 100, "Weight to provide to each peer when sybil protection is disabled")
fs.Bool(PartialSyncPrimaryNetworkKey, false, "Only sync the P-chain on the Primary Network. If the node is a Primary Network validator, it will report unhealthy")
Expand Down
1 change: 1 addition & 0 deletions config/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const (
StakingEphemeralSignerEnabledKey = "staking-ephemeral-signer-enabled"
StakingSignerKeyPathKey = "staking-signer-key-file"
StakingSignerKeyContentKey = "staking-signer-key-file-content"
StakingRPCSignerEndpointKey = "staking-rpc-signer-endpoint"
SybilProtectionEnabledKey = "sybil-protection-enabled"
SybilProtectionDisabledWeightKey = "sybil-protection-disabled-weight"
NetworkInitialTimeoutKey = "network-initial-timeout"
Expand Down
29 changes: 23 additions & 6 deletions config/node/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"github.com/ava-labs/avalanchego/subnets"
"github.com/ava-labs/avalanchego/trace"
"github.com/ava-labs/avalanchego/upgrade"
"github.com/ava-labs/avalanchego/utils/crypto/bls"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/utils/profiler"
"github.com/ava-labs/avalanchego/utils/set"
Expand Down Expand Up @@ -76,12 +75,30 @@ type StakingConfig struct {
SybilProtectionEnabled bool `json:"sybilProtectionEnabled"`
PartialSyncPrimaryNetwork bool `json:"partialSyncPrimaryNetwork"`
StakingTLSCert tls.Certificate `json:"-"`
StakingSigningKey bls.Signer `json:"-"`
SybilProtectionDisabledWeight uint64 `json:"sybilProtectionDisabledWeight"`
// not accessed but used for logging
StakingKeyPath string `json:"stakingKeyPath"`
StakingCertPath string `json:"stakingCertPath"`
StakingSignerPath string `json:"stakingSignerPath"`
StakingTLSKeyPath string `json:"stakingTLSKeyPath"`
StakingTLSCertPath string `json:"stakingTLSCertPath"`

// This is set in order to instatiate the correct signer type at runtime.
StakingSignerConfig any
}

type EphemeralSignerConfig struct{}

type ContentKeyConfig struct {
SignerKeyRawContent string
}

type SignerPathConfig struct {
SignerKeyPath string
}

type DefaultSignerConfig struct {
SignerKeyPath string
}

type RPCSignerConfig struct {
StakingSignerRPC string
}

type StateSyncConfig struct {
Expand Down
Loading