Skip to content
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

Add TransferSubnetOwnershipTx #2178

Merged
merged 19 commits into from
Oct 25, 2023
Merged
1 change: 1 addition & 0 deletions vms/platformvm/block/codec.go
Expand Up @@ -36,6 +36,7 @@ func init() {
RegisterApricotBlockTypes(c),
txs.RegisterUnsignedTxsTypes(c),
RegisterBanffBlockTypes(c),
txs.RegisterDUnsignedTxsTypes(c),
)
}
errs.Add(
Expand Down
9 changes: 8 additions & 1 deletion vms/platformvm/metrics/tx_metrics.go
Expand Up @@ -27,7 +27,8 @@ type txMetrics struct {
numRemoveSubnetValidatorTxs,
numTransformSubnetTxs,
numAddPermissionlessValidatorTxs,
numAddPermissionlessDelegatorTxs prometheus.Counter
numAddPermissionlessDelegatorTxs,
numTransferSubnetOwnershipTxs prometheus.Counter
}

func newTxMetrics(
Expand All @@ -49,6 +50,7 @@ func newTxMetrics(
numTransformSubnetTxs: newTxMetric(namespace, "transform_subnet", registerer, &errs),
numAddPermissionlessValidatorTxs: newTxMetric(namespace, "add_permissionless_validator", registerer, &errs),
numAddPermissionlessDelegatorTxs: newTxMetric(namespace, "add_permissionless_delegator", registerer, &errs),
numTransferSubnetOwnershipTxs: newTxMetric(namespace, "transfer_subnet_ownership", registerer, &errs),
}
return m, errs.Err
}
Expand Down Expand Up @@ -132,3 +134,8 @@ func (m *txMetrics) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegator
m.numAddPermissionlessDelegatorTxs.Inc()
return nil
}

func (m *txMetrics) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error {
m.numTransferSubnetOwnershipTxs.Inc()
return nil
}
52 changes: 52 additions & 0 deletions vms/platformvm/txs/builder/builder.go
Expand Up @@ -159,6 +159,19 @@ type ProposalTxBuilder interface {
changeAddr ids.ShortID,
) (*txs.Tx, error)

// Creates a transaction that transfers ownership of [subnetID]
// threshold: [threshold] of [ownerAddrs] needed to manage this subnet
// ownerAddrs: control addresses for the new subnet
// keys: keys to use for modifying the subnet
// changeAddr: address to send change to, if there is any
NewTransferSubnetOwnershipTx(
subnetID ids.ID,
threshold uint32,
ownerAddrs []ids.ShortID,
keys []*secp256k1.PrivateKey,
changeAddr ids.ShortID,
) (*txs.Tx, error)

// newAdvanceTimeTx creates a new tx that, if it is accepted and followed by a
// Commit block, will set the chain's timestamp to [timestamp].
NewAdvanceTimeTx(timestamp time.Time) (*txs.Tx, error)
Expand Down Expand Up @@ -609,3 +622,42 @@ func (b *builder) NewRewardValidatorTx(txID ids.ID) (*txs.Tx, error) {

return tx, tx.SyntacticVerify(b.ctx)
}

func (b *builder) NewTransferSubnetOwnershipTx(
subnetID ids.ID,
threshold uint32,
ownerAddrs []ids.ShortID,
keys []*secp256k1.PrivateKey,
changeAddr ids.ShortID,
) (*txs.Tx, error) {
ins, outs, _, signers, err := b.Spend(b.state, keys, 0, b.cfg.TxFee, changeAddr)
if err != nil {
return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err)
}

subnetAuth, subnetSigners, err := b.Authorize(b.state, subnetID, keys)
if err != nil {
return nil, fmt.Errorf("couldn't authorize tx's subnet restrictions: %w", err)
}
signers = append(signers, subnetSigners)

utx := &txs.TransferSubnetOwnershipTx{
BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
NetworkID: b.ctx.NetworkID,
BlockchainID: b.ctx.ChainID,
Ins: ins,
Outs: outs,
}},
Subnet: subnetID,
SubnetAuth: subnetAuth,
Owner: &secp256k1fx.OutputOwners{
Threshold: threshold,
Addrs: ownerAddrs,
},
}
tx, err := txs.NewSigned(utx, txs.Codec, signers)
if err != nil {
return nil, err
}
return tx, tx.SyntacticVerify(b.ctx)
}
15 changes: 15 additions & 0 deletions vms/platformvm/txs/builder/mock_builder.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions vms/platformvm/txs/codec.go
Expand Up @@ -41,6 +41,10 @@ func init() {
c.SkipRegistrations(5)

errs.Add(RegisterUnsignedTxsTypes(c))

c.SkipRegistrations(4)

errs.Add(RegisterDUnsignedTxsTypes(c))
}
errs.Add(
Codec.RegisterCodec(Version, c),
Expand Down Expand Up @@ -97,3 +101,7 @@ func RegisterUnsignedTxsTypes(targetCodec linearcodec.Codec) error {
)
return errs.Err
}

func RegisterDUnsignedTxsTypes(targetCodec linearcodec.Codec) error {
return targetCodec.RegisterType(&TransferSubnetOwnershipTx{})
}
4 changes: 4 additions & 0 deletions vms/platformvm/txs/executor/atomic_tx_executor.go
Expand Up @@ -64,6 +64,10 @@ func (*AtomicTxExecutor) TransformSubnetTx(*txs.TransformSubnetTx) error {
return ErrWrongTxType
}

func (*AtomicTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error {
return ErrWrongTxType
}

func (*AtomicTxExecutor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error {
return ErrWrongTxType
}
Expand Down
4 changes: 4 additions & 0 deletions vms/platformvm/txs/executor/proposal_tx_executor.go
Expand Up @@ -97,6 +97,10 @@ func (*ProposalTxExecutor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDe
return ErrWrongTxType
}

func (*ProposalTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error {
return ErrWrongTxType
}

func (e *ProposalTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error {
// AddValidatorTx is a proposal transaction until the Banff fork
// activation. Following the activation, AddValidatorTxs must be issued into
Expand Down
48 changes: 48 additions & 0 deletions vms/platformvm/txs/executor/staker_tx_verification.go
Expand Up @@ -37,6 +37,7 @@ var (
ErrDuplicateValidator = errors.New("duplicate validator")
ErrDelegateToPermissionedValidator = errors.New("delegation to permissioned validator")
ErrWrongStakedAssetID = errors.New("incorrect staked assetID")
ErrDUpgradeNotActive = errors.New("attempting to use a D-upgrade feature prior to activation")
)

// verifySubnetValidatorPrimaryNetworkRequirements verifies the primary
Expand Down Expand Up @@ -714,3 +715,50 @@ func verifyAddPermissionlessDelegatorTx(

return nil
}

// Returns an error if the given tx is invalid.
// The transaction is valid if:
// * [sTx]'s creds authorize it to spend the stated inputs.
// * [sTx]'s creds authorize it to transfer ownership of [tx.Subnet].
// * The flow checker passes.
func verifyTransferSubnetOwnershipTx(
backend *Backend,
chainState state.Chain,
sTx *txs.Tx,
tx *txs.TransferSubnetOwnershipTx,
) error {
if !backend.Config.IsDActivated(chainState.GetTimestamp()) {
return ErrDUpgradeNotActive
}

// Verify the tx is well-formed
if err := sTx.SyntacticVerify(backend.Ctx); err != nil {
return err
}

if !backend.Bootstrapped.Get() {
// Not bootstrapped yet -- don't need to do full verification.
return nil
}

baseTxCreds, err := verifySubnetAuthorization(backend, chainState, sTx, tx.Subnet, tx.SubnetAuth)
if err != nil {
return err
}

// Verify the flowcheck
if err := backend.FlowChecker.VerifySpend(
tx,
chainState,
tx.Ins,
tx.Outs,
baseTxCreds,
map[ids.ID]uint64{
backend.Ctx.AVAXAssetID: backend.Config.TxFee,
},
); err != nil {
return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err)
}

return nil
}
24 changes: 24 additions & 0 deletions vms/platformvm/txs/executor/standard_tx_executor.go
Expand Up @@ -490,3 +490,27 @@ func (e *StandardTxExecutor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionl

return nil
}

// Verifies a [*txs.TransferSubnetOwnershipTx] and, if it passes, executes it on
// [e.State]. For verification rules, see [verifyTransferSubnetOwnershipTx].
// This transaction will result in the ownership of [tx.Subnet] being transferred
// to [tx.Owner].
func (e *StandardTxExecutor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error {
err := verifyTransferSubnetOwnershipTx(
e.Backend,
e.State,
e.Tx,
tx,
)
if err != nil {
return err
}

e.State.SetSubnetOwner(tx.Subnet, tx.Owner)

txID := e.Tx.ID()
avax.Consume(e.State, tx.Ins)
avax.Produce(e.State, txID, tx.Outs)

return nil
}
4 changes: 4 additions & 0 deletions vms/platformvm/txs/executor/tx_mempool_verifier.go
Expand Up @@ -74,6 +74,10 @@ func (v *MempoolTxVerifier) AddPermissionlessDelegatorTx(tx *txs.AddPermissionle
return v.standardTx(tx)
}

func (v *MempoolTxVerifier) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error {
return v.standardTx(tx)
}

func (v *MempoolTxVerifier) standardTx(tx txs.UnsignedTx) error {
baseState, err := v.standardBaseState()
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions vms/platformvm/txs/mempool/issuer.go
Expand Up @@ -74,6 +74,11 @@ func (i *issuer) TransformSubnetTx(*txs.TransformSubnetTx) error {
return nil
}

func (i *issuer) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error {
i.m.addDecisionTx(i.tx)
return nil
}

func (i *issuer) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error {
i.m.addStakerTx(i.tx)
return nil
Expand Down
5 changes: 5 additions & 0 deletions vms/platformvm/txs/mempool/remover.go
Expand Up @@ -57,6 +57,11 @@ func (r *remover) TransformSubnetTx(*txs.TransformSubnetTx) error {
return nil
}

func (r *remover) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error {
r.m.removeDecisionTxs([]*txs.Tx{r.tx})
return nil
}

func (r *remover) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error {
r.m.removeStakerTx(r.tx)
return nil
Expand Down
65 changes: 65 additions & 0 deletions vms/platformvm/txs/transfer_subnet_ownership_tx.go
@@ -0,0 +1,65 @@
// Copyright (C) 2019-2023, 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/platformvm/fx"
)

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

ErrTransferPermissionlessSubnet = errors.New("cannot transfer ownership of a permissionless subnet")
)

type TransferSubnetOwnershipTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// ID of the subnet this tx is modifying
Subnet ids.ID `serialize:"true" json:"subnetID"`
// Proves that the issuer has the right to remove the node from the subnet.
SubnetAuth verify.Verifiable `serialize:"true" json:"subnetAuthorization"`
// Who is now authorized to manage this subnet
Owner fx.Owner `serialize:"true" json:"newOwner"`
}

// InitCtx sets the FxID fields in the inputs and outputs of this
// [TransferSubnetOwnershipTx]. Also sets the [ctx] to the given [vm.ctx] so
// that the addresses can be json marshalled into human readable format
func (tx *TransferSubnetOwnershipTx) InitCtx(ctx *snow.Context) {
tx.BaseTx.InitCtx(ctx)
dhrubabasu marked this conversation as resolved.
Show resolved Hide resolved
tx.Owner.InitCtx(ctx)
}

func (tx *TransferSubnetOwnershipTx) 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 ErrTransferPermissionlessSubnet
}

if err := tx.BaseTx.SyntacticVerify(ctx); err != nil {
return err
}
if err := verify.All(tx.SubnetAuth, tx.Owner); err != nil {
return err
}

tx.SyntacticallyVerified = true
return nil
}

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