-
Notifications
You must be signed in to change notification settings - Fork 17
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
Allow public API usage for GetValidators #77
Changes from 8 commits
f93dc95
51b4680
a0a9ff6
5b83f0f
e398734
dc192e7
49ade24
55a0dc1
5b4f166
7bd663c
28efdef
294ce11
3410803
c73a5a1
a6a2bcb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,15 +8,20 @@ import ( | |
|
||
"github.com/ava-labs/avalanchego/ids" | ||
"github.com/ava-labs/avalanchego/snow/validators" | ||
"github.com/ava-labs/avalanchego/utils/logging" | ||
"github.com/ava-labs/avalanchego/vms/platformvm" | ||
"go.uber.org/zap" | ||
) | ||
|
||
var _ validators.State = (*CanonicalValidatorClient)(nil) | ||
|
||
// CanonicalValidatorClient wraps platformvm.Client and implements validators.State | ||
type CanonicalValidatorClient struct { | ||
client platformvm.Client | ||
logger logging.Logger | ||
} | ||
|
||
func NewCanonicalValidatorClient(client platformvm.Client) *CanonicalValidatorClient { | ||
func NewCanonicalValidatorClient(logger logging.Logger, client platformvm.Client) *CanonicalValidatorClient { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like the logger isn't being set in the object instantiation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yikes, my bad. Thanks for catching this. |
||
return &CanonicalValidatorClient{ | ||
client: client, | ||
} | ||
|
@@ -34,19 +39,17 @@ func (v *CanonicalValidatorClient) GetSubnetID(ctx context.Context, chainID ids. | |
return v.client.ValidatedBy(ctx, chainID) | ||
} | ||
|
||
// Gets the current validator set of the given subnet ID, include the validators' BLS public keys. | ||
// This implementation of GetValidatorSet currently makes two RPC requests, one to get the | ||
// subnet validators, and another to get their BLS public keys. This is necessary in order to enable | ||
// the use of the public APIs (which don't support "GetValidatorsAt") because BLS keys are currently | ||
// only associated with primary network validation periods. If ACP-13 is implementated in the future | ||
// (https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/13-subnet-only-validators.md), it may | ||
// become possible to reduce this to a single RPC request that returns both the subnet validators | ||
// Gets the current validator set of the given subnet ID, including the validators' BLS public | ||
// keys. The implementation currently makes two RPC requests, one to get the subnet validators, | ||
// and another to get their BLS public keys. This is necessary in order to enable the use of | ||
// the public APIs (which don't support "GetValidatorsAt") because BLS keys are currently only | ||
// associated with primary network validation periods. If ACP-13 is implementated in the future | ||
// (https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/13-subnet-only-validators.md), it | ||
// may become possible to reduce this to a single RPC request that returns both the subnet validators | ||
// as well as their BLS public keys. | ||
func (v *CanonicalValidatorClient) GetValidatorSet( | ||
func (v *CanonicalValidatorClient) getCurrentValidatorSet( | ||
ctx context.Context, | ||
height uint64, | ||
subnetID ids.ID, | ||
) (map[ids.NodeID]*validators.GetValidatorOutput, error) { | ||
subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { | ||
// Get the current subnet validators. These validators are not expected to include | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than fully replacing the call to For a switch, we could hardcode the public API endpoint and compare the config value against that. However, there's no guarantee that We could always try calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could also add a config option indicating which API methods are supported by the configured node and switch on that. Alternatively, we could handle that flag dynamically by calling this method on startup and looking for the "method not supported" error message, though that would increase the set of things that could break unexpectedly. I'm spitballing at this point, curious to hear your thoughts. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (For context, the reason I'm in favor of having some way to still call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All good thoughts...there are definitely trade offs one way or another at certain scales. My main considerations are:
I think this is a minute enough of a detail that adding configuration options for it is not worth the additional code complexity and mental bandwidth of those setting up nodes to try to properly understand what the proper value for their set up would be. Checking whether or not the API method is available at start up is a cool idea to get around this, but there's no guarantee than an API won't change it behavior at any point in time, so would be prone to breaking. That makes me think either always using the workaround method, or always trying to first use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That sounds good to me. Very good point that a relayer operator concerned with latency would likely roll their own API node; if they choose not to, then one extra round trip is likely not going to make a difference to them. |
||
// BLS signing information given that addPermissionlessValidatorTx is only used to | ||
// add primary network validators. | ||
|
@@ -73,12 +76,49 @@ func (v *CanonicalValidatorClient) GetValidatorSet( | |
|
||
// Set the BLS keys of the result. | ||
for _, primaryVdr := range primaryVdrs { | ||
// We expect all of the primary network validators to already be in `res` because | ||
// we filtered the request to node IDs that were identified as validators of the | ||
// specific subnet ID. | ||
vdr, ok := res[primaryVdr.NodeID] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we ever expecting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, I don't think we ever expect it currently. I'll add a warning log for this case. I think it still makes sense to Update: Realizing now this is separate from validators that don't have BLS keys. It's if the RPC doesn't apply the proper filter. That being said, I'll still add a warning log and still thinking There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, I agree. Can we add a check below that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, I think that proper check is already there now actually. I chose not to emit any log in that case because it's expected (or at least certainly possible) for the primary network validator to not have any BLS public key registered. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me know if you feel strongly otherwise though. I'm neither here or there on the log 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
EDIT: was looking at a stale commit |
||
if !ok { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we add documentation that if we have a validator taht we don't find it's public key in primary validator set we still include it for mapping, so it'd affect total stake weight, and need to check whether it has a public key There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, I'll add it below. Worth noting that this case here is actually just if the "GetCurrentValidators" returns an unexpected node ID that wasn't included in the filter in the request body. I'll document that also. |
||
v.logger.Warn( | ||
"Unexpected primary network validator returned by getCurrentValidators request", | ||
zap.String("subnetID", subnetID.String()), | ||
zap.String("nodeID", primaryVdr.NodeID.String())) | ||
continue | ||
} | ||
vdr.PublicKey = primaryVdr.Signer.Key() | ||
|
||
// Validators that do not have a BLS public key registered on the P-chain are still | ||
// included in the result because they affect the stake weight of the subnet validators. | ||
// Such validators will not be queried for BLS signatures of warp messages. As long as | ||
// sufficient stake percentage of subnet validators have registered BLS public keys, | ||
// messages can still be successfully relayed. | ||
if primaryVdr.Signer != nil { | ||
vdr.PublicKey = primaryVdr.Signer.Key() | ||
} | ||
} | ||
|
||
return res, nil | ||
} | ||
|
||
// Gets the validator set of the given subnet at the given P-chain block height. | ||
// Attempts to use the "getValidatorsAt" API first. If not available, falls back | ||
// to use "getCurrentValidators", ignoring the specified P-chain block height. | ||
func (v *CanonicalValidatorClient) GetValidatorSet( | ||
ctx context.Context, | ||
height uint64, | ||
subnetID ids.ID, | ||
) (map[ids.NodeID]*validators.GetValidatorOutput, error) { | ||
// First, attempt to use the "getValidatorsAt" RPC method. This method may not be available on | ||
// all API nodes, in which case we can fall back to using "getCurrentValidators" if needed. | ||
res, err := v.client.GetValidatorsAt(ctx, subnetID, height) | ||
if err != nil { | ||
v.logger.Debug( | ||
"P-chain RPC to getValidatorAt returned error. Falling back to getCurrentValidators", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we include the error in log? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call, added |
||
zap.String("subnetID", subnetID.String()), | ||
zap.Uint64("pChainHeight", height), | ||
zap.Error(err)) | ||
return v.getCurrentValidatorSet(ctx, subnetID) | ||
} | ||
return res, nil | ||
} |
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: I think the syntax
&CanonicalValidatorClient{}
on the rhs is a bit cleaner, and it's also what we've used in other places in our repos.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.
Sounds good to me. There was one other place in this repo that we were using the
(*Type)(nil)
syntax, so I updated it there as well 👍