Skip to content

Commit

Permalink
ACP-77: Update ConvertSubnetTx (#3397)
Browse files Browse the repository at this point in the history
Signed-off-by: Joshua Kim <[email protected]>
Co-authored-by: Joshua Kim <[email protected]>
  • Loading branch information
StephenButtolph and joshua-kim authored Nov 6, 2024
1 parent b51d94b commit c13c576
Show file tree
Hide file tree
Showing 16 changed files with 970 additions and 45 deletions.
86 changes: 86 additions & 0 deletions tests/e2e/p/l1.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,26 @@ import (
"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/api/info"
"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/tests/fixture/tmpnet"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/crypto/bls"
"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
"github.com/ava-labs/avalanchego/utils/units"
"github.com/ava-labs/avalanchego/vms/example/xsvm/genesis"
"github.com/ava-labs/avalanchego/vms/platformvm"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"

snowvalidators "github.com/ava-labs/avalanchego/snow/validators"
warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message"
)

const (
genesisWeight = units.Schmeckle
genesisBalance = units.Avax
)

var _ = e2e.DescribePChain("[L1]", func() {
Expand Down Expand Up @@ -111,18 +124,80 @@ var _ = e2e.DescribePChain("[L1]", func() {
chainID = chainTx.ID()
})

verifyValidatorSet := func(expectedValidators map[ids.NodeID]*snowvalidators.GetValidatorOutput) {
height, err := pClient.GetHeight(tc.DefaultContext())
require.NoError(err)

subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height)
require.NoError(err)
require.Equal(expectedValidators, subnetValidators)
}
tc.By("verifying the Permissioned Subnet is configured as expected", func() {
tc.By("verifying the subnet reports as permissioned", func() {
subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID)
require.NoError(err)
require.Equal(
platformvm.GetSubnetClientResponse{
IsPermissioned: true,
ControlKeys: []ids.ShortID{
keychain.Keys[0].Address(),
},
Threshold: 1,
},
subnet,
)
})

tc.By("verifying the validator set is empty", func() {
verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{})
})
})

tc.By("creating the genesis validator")
subnetGenesisNode := e2e.AddEphemeralNode(tc, env.GetNetwork(), tmpnet.FlagsMap{
config.TrackSubnetsKey: subnetID.String(),
})

genesisNodePoP, err := subnetGenesisNode.GetProofOfPossession()
require.NoError(err)

genesisNodePK, err := bls.PublicKeyFromCompressedBytes(genesisNodePoP.PublicKey[:])
require.NoError(err)

address := []byte{}
tc.By("issuing a ConvertSubnetTx", func() {
_, err := pWallet.IssueConvertSubnetTx(
subnetID,
chainID,
address,
[]*txs.ConvertSubnetValidator{
{
NodeID: subnetGenesisNode.NodeID.Bytes(),
Weight: genesisWeight,
Balance: genesisBalance,
Signer: *genesisNodePoP,
},
},
tc.WithDefaultContext(),
)
require.NoError(err)
})

tc.By("verifying the Permissioned Subnet was converted to an L1", func() {
expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{
SubnetID: subnetID,
ManagerChainID: chainID,
ManagerAddress: address,
Validators: []warpmessage.SubnetConversionValidatorData{
{
NodeID: subnetGenesisNode.NodeID.Bytes(),
BLSPublicKey: genesisNodePoP.PublicKey,
Weight: genesisWeight,
},
},
})
require.NoError(err)

tc.By("verifying the subnet reports as being converted", func() {
subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID)
require.NoError(err)
Expand All @@ -133,12 +208,23 @@ var _ = e2e.DescribePChain("[L1]", func() {
keychain.Keys[0].Address(),
},
Threshold: 1,
ConversionID: expectedConversionID,
ManagerChainID: chainID,
ManagerAddress: address,
},
subnet,
)
})

tc.By("verifying the validator set was updated", func() {
verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{
subnetGenesisNode.NodeID: {
NodeID: subnetGenesisNode.NodeID,
PublicKey: genesisNodePK,
Weight: genesisWeight,
},
})
})
})

_ = e2e.CheckBootstrapIsPossible(tc, env.GetNetwork())
Expand Down
73 changes: 70 additions & 3 deletions vms/platformvm/txs/convert_subnet_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,31 @@
package txs

import (
"bytes"
"errors"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow"
"github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/vms/components/verify"
"github.com/ava-labs/avalanchego/vms/platformvm/signer"
"github.com/ava-labs/avalanchego/vms/platformvm/warp/message"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
"github.com/ava-labs/avalanchego/vms/types"
)

const MaxSubnetAddressLength = 4096

var (
_ UnsignedTx = (*TransferSubnetOwnershipTx)(nil)
_ UnsignedTx = (*ConvertSubnetTx)(nil)
_ utils.Sortable[*ConvertSubnetValidator] = (*ConvertSubnetValidator)(nil)

ErrConvertPermissionlessSubnet = errors.New("cannot convert a permissionless subnet")
ErrAddressTooLong = errors.New("address is too long")
ErrConvertPermissionlessSubnet = errors.New("cannot convert a permissionless subnet")
ErrAddressTooLong = errors.New("address is too long")
ErrConvertMustIncludeValidators = errors.New("conversion must include at least one validator")
ErrConvertValidatorsNotSortedAndUnique = errors.New("conversion validators must be sorted and unique")
ErrZeroWeight = errors.New("validator weight must be non-zero")
)

type ConvertSubnetTx struct {
Expand All @@ -31,6 +40,8 @@ type ConvertSubnetTx struct {
ChainID ids.ID `serialize:"true" json:"chainID"`
// Address of the Subnet manager
Address types.JSONByteSlice `serialize:"true" json:"address"`
// Initial pay-as-you-go validators for the Subnet
Validators []*ConvertSubnetValidator `serialize:"true" json:"validators"`
// Authorizes this conversion
SubnetAuth verify.Verifiable `serialize:"true" json:"subnetAuthorization"`
}
Expand All @@ -46,11 +57,20 @@ func (tx *ConvertSubnetTx) SyntacticVerify(ctx *snow.Context) error {
return ErrConvertPermissionlessSubnet
case len(tx.Address) > MaxSubnetAddressLength:
return ErrAddressTooLong
case len(tx.Validators) == 0:
return ErrConvertMustIncludeValidators
case !utils.IsSortedAndUnique(tx.Validators):
return ErrConvertValidatorsNotSortedAndUnique
}

if err := tx.BaseTx.SyntacticVerify(ctx); err != nil {
return err
}
for _, vdr := range tx.Validators {
if err := vdr.Verify(); err != nil {
return err
}
}
if err := tx.SubnetAuth.Verify(); err != nil {
return err
}
Expand All @@ -62,3 +82,50 @@ func (tx *ConvertSubnetTx) SyntacticVerify(ctx *snow.Context) error {
func (tx *ConvertSubnetTx) Visit(visitor Visitor) error {
return visitor.ConvertSubnetTx(tx)
}

type ConvertSubnetValidator struct {
// NodeID of this validator
NodeID types.JSONByteSlice `serialize:"true" json:"nodeID"`
// Weight of this validator used when sampling
Weight uint64 `serialize:"true" json:"weight"`
// Initial balance for this validator
Balance uint64 `serialize:"true" json:"balance"`
// [Signer] is the BLS key for this validator.
// Note: We do not enforce that the BLS key is unique across all validators.
// This means that validators can share a key if they so choose.
// However, a NodeID + Subnet does uniquely map to a BLS key
Signer signer.ProofOfPossession `serialize:"true" json:"signer"`
// Leftover $AVAX from the [Balance] will be issued to this owner once it is
// removed from the validator set.
RemainingBalanceOwner message.PChainOwner `serialize:"true" json:"remainingBalanceOwner"`
// This owner has the authority to manually deactivate this validator.
DeactivationOwner message.PChainOwner `serialize:"true" json:"deactivationOwner"`
}

func (v *ConvertSubnetValidator) Compare(o *ConvertSubnetValidator) int {
return bytes.Compare(v.NodeID, o.NodeID)
}

func (v *ConvertSubnetValidator) Verify() error {
if v.Weight == 0 {
return ErrZeroWeight
}
nodeID, err := ids.ToNodeID(v.NodeID)
if err != nil {
return err
}
if nodeID == ids.EmptyNodeID {
return errEmptyNodeID
}
return verify.All(
&v.Signer,
&secp256k1fx.OutputOwners{
Threshold: v.RemainingBalanceOwner.Threshold,
Addrs: v.RemainingBalanceOwner.Addresses,
},
&secp256k1fx.OutputOwners{
Threshold: v.DeactivationOwner.Threshold,
Addrs: v.DeactivationOwner.Addresses,
},
)
}
Loading

0 comments on commit c13c576

Please sign in to comment.