-
Notifications
You must be signed in to change notification settings - Fork 760
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
base: master
Are you sure you want to change the base?
Conversation
The code was changed without changing the behaviour. Instead of `os.Stat`ing the file, we just try to read the file (which returned the same error type).
Prior to this, we were checking if the file existed before attempting to readd it. It was unnecessary and the logic has been removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Treating the signer the same way as the other Node
composite fields makes sense to me. I left a few questions/comments about the config parsing pattern.
config/config.go
Outdated
|
||
default: | ||
return node.SignerPathConfig{ | ||
SigningKeyPath: getExpandedArg(v, StakingSignerKeyPathKey), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to confirm, if StakingSignerKeyPathKey
is not set, do we use the default path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's my understanding? But I'm not 100% how this expands to the default path - I just took the existing logic 😅
utils/crypto/bls/signer/signer.go
Outdated
if cfg.SignerPathIsSet { | ||
return nil, errMissingStakingSigningKeyFile | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love this pattern of embedding metadata into the config struct that's only used for internal control flow. One alternative would be to return a signerPathIsSet
bool from getStakingSignerConfig
and pass that around, but dedicating an entire return parameter that needs to be managed by the caller for one specific configuration type is also cumbersome. I mainly wanted to call this out as a slightly awkward implementation, that may not have a cleaner alternative.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I don't think passing a return parameter around is a good choice. Would renaming the boolean make more sense? I just kind of went off what was there, but I think "fromExistingFile" might make more sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a private field and a getter makes sense. I agree that I wouldn't add a return value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A private field in StakingConfig
or in SignerPathConfig
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking in SignerPathConfig
but I now realize that this is in node
package instead of config
which would require a constructor or a setter. I don't feel strongly about this either way but do prefer not serializing it (by omitting the json tag) if it's left public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another idea is to just make a different type called DefaultSignerConfig
so we could switch on that instead of having to do this hack + avoid the ok
var passing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic LGTM
config/config.go
Outdated
return config, nil | ||
} | ||
|
||
func getStakingSignerConfig(v *viper.Viper) (interface{}, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR uses this a lot, but as of go 1.18 it's idiomatic to use the any
alias for interface{}
utils/crypto/bls/signer/signer.go
Outdated
var errMissingStakingSigningKeyFile = errors.New("missing staking signing key file") | ||
|
||
// GetStakingSigner returns a BLS signer based on the provided configuration. | ||
func GetStakingSigner( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- go's idiom is to name factory functions
New*
, so I think this should beNewStakingSigner
- Location looks a bit awkward to me - the package is the unit of abstraction and this is the node deciding which signer to use based off of the user-provided config, so I think this should live as a non-exported function in
node
+ all of the config types we created should also be non-exported.
config/config_test.go
Outdated
@@ -549,6 +551,79 @@ func TestGetSubnetConfigsFromFlags(t *testing.T) { | |||
} | |||
} | |||
|
|||
func TestGetStakingSigner(t *testing.T) { | |||
testKey := "HLimS3vRibTMk9lZD4b+Z+GLuSBShvgbsu0WTLt2Kd4=" | |||
type cfg map[string]any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we don't need this type alias
config/config_test.go
Outdated
config, err := GetNodeConfig(v) | ||
|
||
require.ErrorIs(err, tt.expectedErr) | ||
require.IsType(tt.expectedSignerConfigType, config.StakingSignerConfig) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we just do an check on the expected config instead of just a type check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was slightly annoying because of the way temp dirs work in the test suite. Let me know if you think it's better or worse.
config/config_test.go
Outdated
expectedErr error | ||
}{ | ||
{ | ||
name: "default-signer", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove the kebab-casing in these test names? Plain strings work fine and there's no reason to format it this way IMO
} | ||
|
||
pkBytes := pubkeyResponse.GetPublicKey() | ||
pk, err := bls.PublicKeyFromCompressedBytes(pkBytes) | ||
if err != nil { | ||
return nil, err | ||
return nil, errors.Join(fmt.Errorf("failed to uncompress public key bytes: %w", err), conn.Close()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same nit
|
||
return bls.SignatureFromBytes(signature) | ||
sigBytes := resp.GetSignature() | ||
return bls.SignatureFromBytes(sigBytes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're now wrapping the previous error I feel like it's weird for us to not wrap this error
|
||
return bls.SignatureFromBytes(signature) | ||
sigBytes := resp.GetSignature() | ||
return bls.SignatureFromBytes(sigBytes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same nit here
} | ||
|
||
func (c *Client) Shutdown() error { | ||
return c.connection.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar nit here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does wrapping an error in a function that only has one possible return provide any meaningful information?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although there aren't any other errors in this scope, if we don't wrap this error it's not disambiguated from other grpc connection close errors. As an example, if we had another grpc conn in avalanchego and its conn close failed, we might lose visibility into which conn was the one we had to debug.
utils/crypto/bls/signer/signer.go
Outdated
func createSignerFromFile(signingKeyPath string) (bls.Signer, error) { | ||
signingKeyBytes, err := os.ReadFile(signingKeyPath) | ||
if err != nil { | ||
return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same wrapping nit
}, nil | ||
} | ||
|
||
func (c *Client) PublicKey() *bls.PublicKey { | ||
return c.pk | ||
} | ||
|
||
// Sign a message. The [Client] already handles transient connection errors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would move the comment w.r.t transient conn errors to NewClient
or Client
since it's applicable to the type and not just Sign
} | ||
|
||
// [SignProofOfPossession] has the same behavior as [Sign] but will product a different signature. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Square brackets are so confusing... my understanding is:
- This is a newer convention that auto links in godoc
- It only works if it's linked properly + exported out of the package, so stuff a package
foo
that can do[Foo]
(ifFoo
is an exported type infoo
) and[bar.Bar]
, but ifFoo.Baz
is a method then you have to specify the type and can't just referenceBaz
.
So if you want to use square brackets I think you need to prefix the type (i.e Client.SignProofOfPossession
and Client.Sign
), or it'll break in godoc. I think you could alternatively just not use square brackets.
Regarding the actual comment, I think we can just say it produces a ProofOfPossession
signature instead of different, since it's not clear what that difference is + we document PoP elsewhere in the repo iirc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I removed them all. I haven't used these at all before - they're from the portion of the code Richard wrote
Co-authored-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Signed-off-by: Geoff Stuart <geoff.vball@gmail.com>
… into signers-config-wip
Why this should be merged
The enables the rpc-signer configuration.
This is based off of this old PR.
How this works
I moved the instantiation of the signer from the
config
package tonode.New()
where we instantiate everything else that needs filesystem/networking. This way we don't need to propagatecleanup
all over the place.I also opted for a
Shutdown()
method onbls.Signer
, which is how the other resources attached tonode
are managed. Currently, resources are not shutdown if we fail in another part ofnode.New()
, but this is how the rest of the resources are also managed.Out of scope for this PR, but we may want to consider some sort of registry of cleanup/shutdown functions that can be unwound whenever we fail.
How this was tested
Unit tests
Need to be documented in RELEASES.md?
Yes?