Skip to content

Commit 7537790

Browse files
Add platform.getSubnetOnlyValidator API (#3540)
1 parent 44947ef commit 7537790

File tree

5 files changed

+323
-0
lines changed

5 files changed

+323
-0
lines changed

RELEASES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Pending Release
44

5+
### APIs
6+
7+
- Added `platform.getSubnetOnlyValidator`
8+
59
### Configs
610

711
- Added P-chain configs

tests/e2e/p/l1.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ var _ = e2e.DescribePChain("[L1]", func() {
224224
)
225225
require.NoError(err)
226226
})
227+
genesisValidationID := subnetID.Append(0)
227228

228229
tc.By("verifying the Permissioned Subnet was converted to an L1", func() {
229230
expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{
@@ -267,6 +268,31 @@ var _ = e2e.DescribePChain("[L1]", func() {
267268
},
268269
})
269270
})
271+
272+
tc.By("verifying the SoV can be fetched", func() {
273+
sov, _, err := pClient.GetSubnetOnlyValidator(tc.DefaultContext(), genesisValidationID)
274+
require.NoError(err)
275+
require.LessOrEqual(sov.Balance, genesisBalance)
276+
277+
sov.StartTime = 0
278+
sov.Balance = 0
279+
require.Equal(
280+
platformvm.SubnetOnlyValidator{
281+
SubnetID: subnetID,
282+
NodeID: subnetGenesisNode.NodeID,
283+
PublicKey: genesisNodePK,
284+
RemainingBalanceOwner: &secp256k1fx.OutputOwners{
285+
Addrs: []ids.ShortID{},
286+
},
287+
DeactivationOwner: &secp256k1fx.OutputOwners{
288+
Addrs: []ids.ShortID{},
289+
},
290+
Weight: genesisWeight,
291+
MinNonce: 0,
292+
},
293+
sov,
294+
)
295+
})
270296
})
271297

272298
advanceProposerVMPChainHeight := func() {
@@ -284,6 +310,9 @@ var _ = e2e.DescribePChain("[L1]", func() {
284310
registerNodePoP, err := subnetRegisterNode.GetProofOfPossession()
285311
require.NoError(err)
286312

313+
registerNodePK, err := bls.PublicKeyFromCompressedBytes(registerNodePoP.PublicKey[:])
314+
require.NoError(err)
315+
287316
tc.By("ensuring the subnet nodes are healthy", func() {
288317
e2e.WaitForHealthy(tc, subnetGenesisNode)
289318
e2e.WaitForHealthy(tc, subnetRegisterNode)
@@ -365,6 +394,30 @@ var _ = e2e.DescribePChain("[L1]", func() {
365394
},
366395
})
367396
})
397+
398+
tc.By("verifying the SoV can be fetched", func() {
399+
sov, _, err := pClient.GetSubnetOnlyValidator(tc.DefaultContext(), registerValidationID)
400+
require.NoError(err)
401+
402+
sov.StartTime = 0
403+
require.Equal(
404+
platformvm.SubnetOnlyValidator{
405+
SubnetID: subnetID,
406+
NodeID: subnetRegisterNode.NodeID,
407+
PublicKey: registerNodePK,
408+
RemainingBalanceOwner: &secp256k1fx.OutputOwners{
409+
Addrs: []ids.ShortID{},
410+
},
411+
DeactivationOwner: &secp256k1fx.OutputOwners{
412+
Addrs: []ids.ShortID{},
413+
},
414+
Weight: registerWeight,
415+
MinNonce: 0,
416+
Balance: 0,
417+
},
418+
sov,
419+
)
420+
})
368421
})
369422

370423
var nextNonce uint64
@@ -438,6 +491,30 @@ var _ = e2e.DescribePChain("[L1]", func() {
438491
},
439492
})
440493
})
494+
495+
tc.By("verifying the SoV can be fetched", func() {
496+
sov, _, err := pClient.GetSubnetOnlyValidator(tc.DefaultContext(), registerValidationID)
497+
require.NoError(err)
498+
499+
sov.StartTime = 0
500+
require.Equal(
501+
platformvm.SubnetOnlyValidator{
502+
SubnetID: subnetID,
503+
NodeID: subnetRegisterNode.NodeID,
504+
PublicKey: registerNodePK,
505+
RemainingBalanceOwner: &secp256k1fx.OutputOwners{
506+
Addrs: []ids.ShortID{},
507+
},
508+
DeactivationOwner: &secp256k1fx.OutputOwners{
509+
Addrs: []ids.ShortID{},
510+
},
511+
Weight: updatedWeight,
512+
MinNonce: nextNonce,
513+
Balance: 0,
514+
},
515+
sov,
516+
)
517+
})
441518
})
442519

443520
tc.By("advancing the proposervm P-chain height", advanceProposerVMPChainHeight)

vms/platformvm/client.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/ava-labs/avalanchego/ids"
1212
"github.com/ava-labs/avalanchego/snow/validators"
1313
"github.com/ava-labs/avalanchego/utils/constants"
14+
"github.com/ava-labs/avalanchego/utils/crypto/bls"
1415
"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
1516
"github.com/ava-labs/avalanchego/utils/formatting"
1617
"github.com/ava-labs/avalanchego/utils/formatting/address"
@@ -71,6 +72,9 @@ type Client interface {
7172
GetStakingAssetID(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (ids.ID, error)
7273
// GetCurrentValidators returns the list of current validators for subnet with ID [subnetID]
7374
GetCurrentValidators(ctx context.Context, subnetID ids.ID, nodeIDs []ids.NodeID, options ...rpc.Option) ([]ClientPermissionlessValidator, error)
75+
// GetSubnetOnlyValidator returns the requested SoV with [validationID] and
76+
// the height at which it was calculated.
77+
GetSubnetOnlyValidator(ctx context.Context, validationID ids.ID, options ...rpc.Option) (SubnetOnlyValidator, uint64, error)
7478
// GetCurrentSupply returns an upper bound on the supply of AVAX in the system along with the P-chain height
7579
GetCurrentSupply(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (uint64, uint64, error)
7680
// SampleValidators returns the nodeIDs of a sample of [sampleSize] validators from the current validator set for subnet with ID [subnetID]
@@ -326,6 +330,72 @@ func (c *client) GetCurrentValidators(
326330
return getClientPermissionlessValidators(res.Validators)
327331
}
328332

333+
// SubnetOnlyValidator is the response from calling GetSubnetOnlyValidator on
334+
// the API client.
335+
type SubnetOnlyValidator struct {
336+
SubnetID ids.ID
337+
NodeID ids.NodeID
338+
PublicKey *bls.PublicKey
339+
RemainingBalanceOwner *secp256k1fx.OutputOwners
340+
DeactivationOwner *secp256k1fx.OutputOwners
341+
StartTime uint64
342+
Weight uint64
343+
MinNonce uint64
344+
// Balance is the remaining amount of AVAX this SoV has for paying the
345+
// continuous fee.
346+
Balance uint64
347+
}
348+
349+
func (c *client) GetSubnetOnlyValidator(
350+
ctx context.Context,
351+
validationID ids.ID,
352+
options ...rpc.Option,
353+
) (SubnetOnlyValidator, uint64, error) {
354+
res := &GetSubnetOnlyValidatorReply{}
355+
err := c.requester.SendRequest(ctx, "platform.getSubnetOnlyValidator",
356+
&GetSubnetOnlyValidatorArgs{
357+
ValidationID: validationID,
358+
},
359+
res, options...,
360+
)
361+
if err != nil {
362+
return SubnetOnlyValidator{}, 0, err
363+
}
364+
365+
pk, err := bls.PublicKeyFromCompressedBytes(res.PublicKey)
366+
if err != nil {
367+
return SubnetOnlyValidator{}, 0, err
368+
}
369+
remainingBalanceOwnerAddrs, err := address.ParseToIDs(res.RemainingBalanceOwner.Addresses)
370+
if err != nil {
371+
return SubnetOnlyValidator{}, 0, err
372+
}
373+
deactivationOwnerAddrs, err := address.ParseToIDs(res.DeactivationOwner.Addresses)
374+
if err != nil {
375+
return SubnetOnlyValidator{}, 0, err
376+
}
377+
378+
return SubnetOnlyValidator{
379+
SubnetID: res.SubnetID,
380+
NodeID: res.NodeID,
381+
PublicKey: pk,
382+
RemainingBalanceOwner: &secp256k1fx.OutputOwners{
383+
Locktime: uint64(res.RemainingBalanceOwner.Locktime),
384+
Threshold: uint32(res.RemainingBalanceOwner.Threshold),
385+
Addrs: remainingBalanceOwnerAddrs,
386+
},
387+
DeactivationOwner: &secp256k1fx.OutputOwners{
388+
Locktime: uint64(res.DeactivationOwner.Locktime),
389+
Threshold: uint32(res.DeactivationOwner.Threshold),
390+
Addrs: deactivationOwnerAddrs,
391+
},
392+
StartTime: uint64(res.StartTime),
393+
Weight: uint64(res.Weight),
394+
MinNonce: uint64(res.MinNonce),
395+
Balance: uint64(res.Balance),
396+
}, uint64(res.Height), err
397+
}
398+
329399
func (c *client) GetCurrentSupply(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (uint64, uint64, error) {
330400
res := &GetCurrentSupplyReply{}
331401
err := c.requester.SendRequest(ctx, "platform.getCurrentSupply", &GetCurrentSupplyArgs{

vms/platformvm/service.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/ava-labs/avalanchego/vms/platformvm/state"
3838
"github.com/ava-labs/avalanchego/vms/platformvm/status"
3939
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
40+
"github.com/ava-labs/avalanchego/vms/platformvm/warp/message"
4041
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
4142
"github.com/ava-labs/avalanchego/vms/types"
4243

@@ -969,6 +970,93 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato
969970
return nil
970971
}
971972

973+
type GetSubnetOnlyValidatorArgs struct {
974+
ValidationID ids.ID `json:"validationID"`
975+
}
976+
977+
type GetSubnetOnlyValidatorReply struct {
978+
SubnetID ids.ID `json:"subnetID"`
979+
NodeID ids.NodeID `json:"nodeID"`
980+
// PublicKey is the compressed BLS public key of the validator
981+
PublicKey types.JSONByteSlice `json:"publicKey"`
982+
RemainingBalanceOwner platformapi.Owner `json:"remainingBalanceOwner"`
983+
DeactivationOwner platformapi.Owner `json:"deactivationOwner"`
984+
StartTime avajson.Uint64 `json:"startTime"`
985+
Weight avajson.Uint64 `json:"weight"`
986+
MinNonce avajson.Uint64 `json:"minNonce"`
987+
// Balance is the remaining amount of AVAX this SoV has for paying the
988+
// continuous fee, according to the last accepted state. If the validator is
989+
// inactive, the balance will be 0.
990+
Balance avajson.Uint64 `json:"balance"`
991+
// Height is the height of the last accepted block
992+
Height avajson.Uint64 `json:"height"`
993+
}
994+
995+
// GetSubnetOnlyValidator returns the SoV if it exists
996+
func (s *Service) GetSubnetOnlyValidator(r *http.Request, args *GetSubnetOnlyValidatorArgs, reply *GetSubnetOnlyValidatorReply) error {
997+
s.vm.ctx.Log.Debug("API called",
998+
zap.String("service", "platform"),
999+
zap.String("method", "getSubnetOnlyValidator"),
1000+
zap.Stringer("validationID", args.ValidationID),
1001+
)
1002+
1003+
s.vm.ctx.Lock.Lock()
1004+
defer s.vm.ctx.Lock.Unlock()
1005+
1006+
sov, err := s.vm.state.GetSubnetOnlyValidator(args.ValidationID)
1007+
if err != nil {
1008+
return fmt.Errorf("fetching SoV %q failed: %w", args.ValidationID, err)
1009+
}
1010+
1011+
var remainingBalanceOwner message.PChainOwner
1012+
if _, err := txs.Codec.Unmarshal(sov.RemainingBalanceOwner, &remainingBalanceOwner); err != nil {
1013+
return fmt.Errorf("failed unmarshalling remaining balance owner: %w", err)
1014+
}
1015+
remainingBalanceAPIOwner, err := s.getAPIOwner(&secp256k1fx.OutputOwners{
1016+
Threshold: remainingBalanceOwner.Threshold,
1017+
Addrs: remainingBalanceOwner.Addresses,
1018+
})
1019+
if err != nil {
1020+
return fmt.Errorf("failed formatting remaining balance owner: %w", err)
1021+
}
1022+
1023+
var deactivationOwner message.PChainOwner
1024+
if _, err := txs.Codec.Unmarshal(sov.DeactivationOwner, &deactivationOwner); err != nil {
1025+
return fmt.Errorf("failed unmarshalling deactivation owner: %w", err)
1026+
}
1027+
deactivationAPIOwner, err := s.getAPIOwner(&secp256k1fx.OutputOwners{
1028+
Threshold: deactivationOwner.Threshold,
1029+
Addrs: deactivationOwner.Addresses,
1030+
})
1031+
if err != nil {
1032+
return fmt.Errorf("failed formatting deactivation owner: %w", err)
1033+
}
1034+
1035+
ctx := r.Context()
1036+
height, err := s.vm.GetCurrentHeight(ctx)
1037+
if err != nil {
1038+
return fmt.Errorf("failed getting current height: %w", err)
1039+
}
1040+
1041+
reply.SubnetID = sov.SubnetID
1042+
reply.NodeID = sov.NodeID
1043+
reply.PublicKey = bls.PublicKeyToCompressedBytes(
1044+
bls.PublicKeyFromValidUncompressedBytes(sov.PublicKey),
1045+
)
1046+
reply.RemainingBalanceOwner = *remainingBalanceAPIOwner
1047+
reply.DeactivationOwner = *deactivationAPIOwner
1048+
reply.StartTime = avajson.Uint64(sov.StartTime)
1049+
reply.Weight = avajson.Uint64(sov.Weight)
1050+
reply.MinNonce = avajson.Uint64(sov.MinNonce)
1051+
if sov.EndAccumulatedFee != 0 {
1052+
accruedFees := s.vm.state.GetAccruedFees()
1053+
reply.Balance = avajson.Uint64(sov.EndAccumulatedFee - accruedFees)
1054+
}
1055+
reply.Height = avajson.Uint64(height)
1056+
1057+
return nil
1058+
}
1059+
9721060
// GetCurrentSupplyArgs are the arguments for calling GetCurrentSupply
9731061
type GetCurrentSupplyArgs struct {
9741062
SubnetID ids.ID `json:"subnetID"`

0 commit comments

Comments
 (0)