diff --git a/tests/e2e/p/proposed_validators.go b/tests/e2e/p/proposed_validators.go new file mode 100644 index 00000000000..26658aec244 --- /dev/null +++ b/tests/e2e/p/proposed_validators.go @@ -0,0 +1,63 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package p + +import ( + "github.com/onsi/ginkgo/v2" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/config" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/tests/fixture/e2e" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/platformvm" +) + +var _ = e2e.DescribePChain("[Proposed Validators]", func() { + var ( + tc = e2e.NewTestContext() + require = require.New(tc) + ) + + ginkgo.It("should be able to fetch proposed validators", func() { + var ( + env = e2e.GetEnv(tc) + network = env.GetNetwork() + ) + + tc.By("fetching proposed validators", func() { + pvmClient := platformvm.NewClient(env.URIs[0].URI) + proposedVdrs, err := pvmClient.GetProposedValidators( + tc.DefaultContext(), + constants.PrimaryNetworkID, + ) + require.NoError(err) + + tc.By("confirming proposed validators are the same as current validators", func() { + // Ensure the network is configured to use the current height for the proposer + proposerVMUseCurrentHeight, err := network.DefaultFlags.GetBoolVal(config.ProposerVMUseCurrentHeightKey, true) + require.NoError(err) + require.True(proposerVMUseCurrentHeight) + + proposedVdrNodes := set.NewSet[ids.NodeID](len(proposedVdrs)) + for _, vdr := range proposedVdrs { + proposedVdrNodes.Add(vdr.NodeID) + } + currentVdrs, err := pvmClient.GetCurrentValidators( + tc.DefaultContext(), + constants.PrimaryNetworkID, + nil, + ) + require.NoError(err) + currentVdrNodes := set.NewSet[ids.NodeID](len(currentVdrs)) + for _, vdr := range currentVdrs { + currentVdrNodes.Add(vdr.NodeID) + } + require.Equal(proposedVdrNodes, currentVdrNodes) + }) + }) + _ = e2e.CheckBootstrapIsPossible(tc, network) + }) +}) diff --git a/vms/platformvm/client.go b/vms/platformvm/client.go index 505c545bdde..4670804f449 100644 --- a/vms/platformvm/client.go +++ b/vms/platformvm/client.go @@ -120,6 +120,13 @@ type Client interface { height uint64, options ...rpc.Option, ) (map[ids.NodeID]*validators.GetValidatorOutput, error) + // GetProposedValidators returns the weights of the validator set of a provided + // subnet at the proposer's height. + GetProposedValidators( + ctx context.Context, + subnetID ids.ID, + options ...rpc.Option, + ) (map[ids.NodeID]*validators.GetValidatorOutput, error) // GetBlock returns the block with the given id. GetBlock(ctx context.Context, blockID ids.ID, options ...rpc.Option) ([]byte, error) // GetBlockByHeight returns the block at the given [height]. @@ -506,6 +513,18 @@ func (c *client) GetValidatorsAt( return res.Validators, err } +func (c *client) GetProposedValidators( + ctx context.Context, + subnetID ids.ID, + options ...rpc.Option, +) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + res := &GetProposedValidatorsReply{} + err := c.requester.SendRequest(ctx, "platform.getProposedValidators", &GetProposedValidatorsArgs{ + SubnetID: subnetID, + }, res, options...) + return res.Validators, err +} + func (c *client) GetBlock(ctx context.Context, blockID ids.ID, options ...rpc.Option) ([]byte, error) { res := &api.FormattedBlock{} if err := c.requester.SendRequest(ctx, "platform.getBlock", &api.GetBlockArgs{ diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 22153a97ece..0d86509ed3a 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -1780,6 +1780,33 @@ func (s *Service) GetValidatorsAt(r *http.Request, args *GetValidatorsAtArgs, re return nil } +type GetProposedValidatorsArgs struct { + SubnetID ids.ID `json:"subnetID"` +} +type GetProposedValidatorsReply struct { + GetValidatorsAtReply +} + +func (s *Service) GetProposedValidators(r *http.Request, args *GetProposedValidatorsArgs, reply *GetProposedValidatorsReply) error { + s.vm.ctx.Log.Debug("API called", + zap.String("service", "platform"), + zap.String("method", "getProposedValidators"), + zap.Stringer("subnetID", args.SubnetID), + ) + s.vm.ctx.Lock.Lock() + defer s.vm.ctx.Lock.Unlock() + + ctx := r.Context() + proposerHeight, err := s.vm.GetMinimumHeight(ctx) + if err != nil { + return fmt.Errorf("failed to get proposer height: %w", err) + } + if reply.Validators, err = s.vm.GetValidatorSet(ctx, proposerHeight, args.SubnetID); err != nil { + return fmt.Errorf("failed to get validator set: %w", err) + } + return nil +} + func (s *Service) GetBlock(_ *http.Request, args *api.GetBlockArgs, response *api.GetBlockResponse) error { s.vm.ctx.Log.Debug("API called", zap.String("service", "platform"), diff --git a/vms/platformvm/service.md b/vms/platformvm/service.md index dbb12907694..f1961c5c7c6 100644 --- a/vms/platformvm/service.md +++ b/vms/platformvm/service.md @@ -1778,7 +1778,7 @@ platform.getValidatorsAt( ``` - `height` is the P-Chain height to get the validator set at. -- `subnetID` is the Subnet ID to get the validator set of. If not given, gets validator set of the +- `subnetID` is the Subnet ID to get the validator set of. If not given, gets the validator set of the Primary Network. **Example Call:** @@ -1812,6 +1812,52 @@ curl -X POST --data '{ } ``` +### `platform.getProposedValidators` + +Get the validators and their weights of a Subnet or the Primary Network at the current proposer VM height. + +**Signature:** + +```sh +platform.getProposedValidators( + { + subnetID: string, // optional + } +) +``` + +- `subnetID` is the Subnet ID to get the validator set of. If not given, gets the validator set of the + Primary Network. + +**Example Call:** + +```bash +curl -X POST --data '{ + "jsonrpc": "2.0", + "method": "platform.getValidatorsAt", + "params": {}, + "id": 1 +}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/P +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "result": { + "validators": { + "NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg": 2000000000000000, + "NodeID-GWPcbFJZFfZreETSoWjPimr846mXEKCtu": 2000000000000000, + "NodeID-MFrZFVCXPv5iCn6M9K6XduxGTYp891xXZ": 2000000000000000, + "NodeID-NFBbbJ4qCmNaCzeW7sxErhvWqvEQMnYcN": 2000000000000000, + "NodeID-P7oB2McjBGgW2NXXWVYjV8JEDFoW9xDE5": 2000000000000000 + } + }, + "id": 1 +} +``` + ### `platform.issueTx` Issue a transaction to the Platform Chain.