Skip to content

Commit

Permalink
Implement ConvertSubnetTx (#3217)
Browse files Browse the repository at this point in the history
Signed-off-by: Dhruba Basu <[email protected]>
Co-authored-by: Stephen Buttolph <[email protected]>
  • Loading branch information
dhrubabasu and StephenButtolph authored Sep 4, 2024
1 parent d3c09eb commit ccf2612
Show file tree
Hide file tree
Showing 25 changed files with 1,395 additions and 2 deletions.
101 changes: 101 additions & 0 deletions tests/e2e/p/permissionless_layer_one.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package p

import (
"time"

"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/api/info"
"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/vms/platformvm"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"

ginkgo "github.com/onsi/ginkgo/v2"
)

var _ = e2e.DescribePChain("[Permissionless L1]", func() {
tc := e2e.NewTestContext()
require := require.New(tc)

ginkgo.It("creates a Permissionless L1", func() {
env := e2e.GetEnv(tc)
nodeURI := env.GetRandomNodeURI()
infoClient := info.NewClient(nodeURI.URI)

tc.By("fetching upgrade config")
upgrades, err := infoClient.Upgrades(tc.DefaultContext())
require.NoError(err)

tc.By("verifying Etna is activated")
now := time.Now()
if !upgrades.IsEtnaActivated(now) {
ginkgo.Skip("Etna is not activated. Permissionless L1s are enabled post-Etna, skipping test.")
}

keychain := env.NewKeychain()
baseWallet := e2e.NewWallet(tc, keychain, nodeURI)

pWallet := baseWallet.P()
pClient := platformvm.NewClient(nodeURI.URI)

owner := &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{
keychain.Keys[0].Address(),
},
}

tc.By("issuing a CreateSubnetTx")
subnetTx, err := pWallet.IssueCreateSubnetTx(
owner,
tc.WithDefaultContext(),
)
require.NoError(err)

tc.By("verifying a Permissioned Subnet was successfully created")
subnetID := subnetTx.ID()
require.NotEqual(subnetID, constants.PrimaryNetworkID)

res, err := pClient.GetSubnet(tc.DefaultContext(), subnetID)
require.NoError(err)

require.Equal(platformvm.GetSubnetClientResponse{
IsPermissioned: true,
ControlKeys: []ids.ShortID{
keychain.Keys[0].Address(),
},
Threshold: 1,
}, res)

chainID := ids.GenerateTestID()
address := []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}

tc.By("issuing a ConvertSubnetTx")
_, err = pWallet.IssueConvertSubnetTx(
subnetID,
chainID,
address,
tc.WithDefaultContext(),
)
require.NoError(err)

tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1")
res, err = pClient.GetSubnet(tc.DefaultContext(), subnetID)
require.NoError(err)

require.Equal(platformvm.GetSubnetClientResponse{
IsPermissioned: false,
ControlKeys: []ids.ShortID{
keychain.Keys[0].Address(),
},
Threshold: 1,
ManagerChainID: chainID,
ManagerAddress: address,
}, res)
})
})
7 changes: 7 additions & 0 deletions vms/platformvm/block/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func init() {
RegisterApricotTypes(c),
RegisterBanffTypes(c),
RegisterDurangoTypes(c),
RegisterEtnaTypes(c),
)
}

Expand Down Expand Up @@ -79,3 +80,9 @@ func RegisterBanffTypes(targetCodec linearcodec.Codec) error {
func RegisterDurangoTypes(targetCodec linearcodec.Codec) error {
return txs.RegisterDurangoTypes(targetCodec)
}

// RegisterEtnaTypes registers the type information for blocks that were valid
// during the Etna series of upgrades.
func RegisterEtnaTypes(targetCodec linearcodec.Codec) error {
return txs.RegisterEtnaTypes(targetCodec)
}
5 changes: 5 additions & 0 deletions vms/platformvm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ type GetSubnetClientResponse struct {
Locktime uint64
// subnet transformation tx ID for a permissionless subnet
SubnetTransformationTxID ids.ID
// subnet manager information for a permissionless L1
ManagerChainID ids.ID
ManagerAddress []byte
}

func (c *client) GetSubnet(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (GetSubnetClientResponse, error) {
Expand All @@ -256,6 +259,8 @@ func (c *client) GetSubnet(ctx context.Context, subnetID ids.ID, options ...rpc.
Threshold: uint32(res.Threshold),
Locktime: uint64(res.Locktime),
SubnetTransformationTxID: res.SubnetTransformationTxID,
ManagerChainID: res.ManagerChainID,
ManagerAddress: res.ManagerAddress,
}, nil
}

Expand Down
7 changes: 7 additions & 0 deletions vms/platformvm/metrics/tx_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ func (m *txMetrics) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) er
return nil
}

func (m *txMetrics) ConvertSubnetTx(*txs.ConvertSubnetTx) error {
m.numTxs.With(prometheus.Labels{
txLabel: "convert_subnet",
}).Inc()
return nil
}

func (m *txMetrics) BaseTx(*txs.BaseTx) error {
m.numTxs.With(prometheus.Labels{
txLabel: "base",
Expand Down
18 changes: 17 additions & 1 deletion vms/platformvm/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/ava-labs/avalanchego/vms/platformvm/status"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
"github.com/ava-labs/avalanchego/vms/types"

avajson "github.com/ava-labs/avalanchego/utils/json"
safemath "github.com/ava-labs/avalanchego/utils/math"
Expand Down Expand Up @@ -436,8 +437,11 @@ type GetSubnetResponse struct {
ControlKeys []string `json:"controlKeys"`
Threshold avajson.Uint32 `json:"threshold"`
Locktime avajson.Uint64 `json:"locktime"`
// subnet transformation tx ID for a permissionless subnet
// subnet transformation tx ID for an elastic subnet
SubnetTransformationTxID ids.ID `json:"subnetTransformationTxID"`
// subnet manager information for a permissionless L1
ManagerChainID ids.ID `json:"managerChainID"`
ManagerAddress types.JSONByteSlice `json:"managerAddress"`
}

func (s *Service) GetSubnet(_ *http.Request, args *GetSubnetArgs, response *GetSubnetResponse) error {
Expand Down Expand Up @@ -486,6 +490,18 @@ func (s *Service) GetSubnet(_ *http.Request, args *GetSubnetArgs, response *GetS
return err
}

switch chainID, addr, err := s.vm.state.GetSubnetManager(args.SubnetID); err {
case nil:
response.IsPermissioned = false
response.ManagerChainID = chainID
response.ManagerAddress = addr
case database.ErrNotFound:
response.ManagerChainID = ids.Empty
response.ManagerAddress = []byte(nil)
default:
return err
}

return nil
}

Expand Down
13 changes: 12 additions & 1 deletion vms/platformvm/txs/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ func init() {

c.SkipRegistrations(4)

errs.Add(RegisterDurangoTypes(c))
errs.Add(
RegisterDurangoTypes(c),
RegisterEtnaTypes(c),
)
}

Codec = codec.NewDefaultManager()
Expand Down Expand Up @@ -114,3 +117,11 @@ func RegisterDurangoTypes(targetCodec linearcodec.Codec) error {
targetCodec.RegisterType(&BaseTx{}),
)
}

// RegisterEtnaTypes registers the type information for transactions that
// were valid during the Etna series of upgrades.
func RegisterEtnaTypes(targetCodec linearcodec.Codec) error {
return errors.Join(
targetCodec.RegisterType(&ConvertSubnetTx{}),
)
}
64 changes: 64 additions & 0 deletions vms/platformvm/txs/convert_subnet_tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package txs

import (
"errors"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/vms/components/verify"
"github.com/ava-labs/avalanchego/vms/types"
)

const MaxSubnetAddressLength = 4096

var (
_ UnsignedTx = (*TransferSubnetOwnershipTx)(nil)

ErrConvertPermissionlessSubnet = errors.New("cannot convert a permissionless subnet")
ErrAddressTooLong = errors.New("address is too long")
)

type ConvertSubnetTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// ID of the Subnet to transform
Subnet ids.ID `serialize:"true" json:"subnetID"`
// Chain where the Subnet manager lives
ChainID ids.ID `serialize:"true" json:"chainID"`
// Address of the Subnet manager
Address types.JSONByteSlice `serialize:"true" json:"address"`
// Authorizes this conversion
SubnetAuth verify.Verifiable `serialize:"true" json:"subnetAuthorization"`
}

func (tx *ConvertSubnetTx) SyntacticVerify(ctx *snow.Context) error {
switch {
case tx == nil:
return ErrNilTx
case tx.SyntacticallyVerified:
// already passed syntactic verification
return nil
case tx.Subnet == constants.PrimaryNetworkID:
return ErrConvertPermissionlessSubnet
case len(tx.Address) > MaxSubnetAddressLength:
return ErrAddressTooLong
}

if err := tx.BaseTx.SyntacticVerify(ctx); err != nil {
return err
}
if err := tx.SubnetAuth.Verify(); err != nil {
return err
}

tx.SyntacticallyVerified = true
return nil
}

func (tx *ConvertSubnetTx) Visit(visitor Visitor) error {
return visitor.ConvertSubnetTx(tx)
}
Loading

0 comments on commit ccf2612

Please sign in to comment.