Skip to content

Commit

Permalink
Add transfer subnet ownership functionality to wallet (#2659)
Browse files Browse the repository at this point in the history
Co-authored-by: Stephen Buttolph <stephen@avalabs.org>
  • Loading branch information
felipemadero and StephenButtolph authored Jan 30, 2024
1 parent db9e96a commit 3059e51
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 45 deletions.
57 changes: 35 additions & 22 deletions wallet/chain/p/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/avalanchego/vms/components/avax"
"github.com/ava-labs/avalanchego/vms/platformvm/fx"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
"github.com/ava-labs/avalanchego/wallet/subnet/primary/common"
)
Expand All @@ -32,16 +33,30 @@ type backend struct {
Context
common.ChainUTXOs

txsLock sync.RWMutex
// txID -> tx
txs map[ids.ID]*txs.Tx
subnetOwnerLock sync.RWMutex
subnetOwner map[ids.ID]fx.Owner // subnetID -> owner
}

func NewBackend(ctx Context, utxos common.ChainUTXOs, txs map[ids.ID]*txs.Tx) Backend {
func NewBackend(ctx Context, utxos common.ChainUTXOs, subnetTxs map[ids.ID]*txs.Tx) Backend {
subnetOwner := make(map[ids.ID]fx.Owner)
for txID, tx := range subnetTxs { // first get owners from the CreateSubnetTx
createSubnetTx, ok := tx.Unsigned.(*txs.CreateSubnetTx)
if !ok {
continue
}
subnetOwner[txID] = createSubnetTx.Owner
}
for _, tx := range subnetTxs { // then check for TransferSubnetOwnershipTx
transferSubnetOwnershipTx, ok := tx.Unsigned.(*txs.TransferSubnetOwnershipTx)
if !ok {
continue
}
subnetOwner[transferSubnetOwnershipTx.Subnet] = transferSubnetOwnershipTx.Owner
}
return &backend{
Context: ctx,
ChainUTXOs: utxos,
txs: txs,
Context: ctx,
ChainUTXOs: utxos,
subnetOwner: subnetOwner,
}
}

Expand All @@ -57,16 +72,7 @@ func (b *backend) AcceptTx(ctx stdcontext.Context, tx *txs.Tx) error {
}

producedUTXOSlice := tx.UTXOs()
err = b.addUTXOs(ctx, constants.PlatformChainID, producedUTXOSlice)
if err != nil {
return err
}

b.txsLock.Lock()
defer b.txsLock.Unlock()

b.txs[txID] = tx
return nil
return b.addUTXOs(ctx, constants.PlatformChainID, producedUTXOSlice)
}

func (b *backend) addUTXOs(ctx stdcontext.Context, destinationChainID ids.ID, utxos []*avax.UTXO) error {
Expand All @@ -87,13 +93,20 @@ func (b *backend) removeUTXOs(ctx stdcontext.Context, sourceChain ids.ID, utxoID
return nil
}

func (b *backend) GetTx(_ stdcontext.Context, txID ids.ID) (*txs.Tx, error) {
b.txsLock.RLock()
defer b.txsLock.RUnlock()
func (b *backend) GetSubnetOwner(_ stdcontext.Context, subnetID ids.ID) (fx.Owner, error) {
b.subnetOwnerLock.RLock()
defer b.subnetOwnerLock.RUnlock()

tx, exists := b.txs[txID]
owner, exists := b.subnetOwner[subnetID]
if !exists {
return nil, database.ErrNotFound
}
return tx, nil
return owner, nil
}

func (b *backend) setSubnetOwner(subnetID ids.ID, owner fx.Owner) {
b.subnetOwnerLock.Lock()
defer b.subnetOwnerLock.Unlock()

b.subnetOwner[subnetID] = owner
}
9 changes: 8 additions & 1 deletion wallet/chain/p/backend_visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ func (b *backendVisitor) CreateChainTx(tx *txs.CreateChainTx) error {
}

func (b *backendVisitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error {
b.b.setSubnetOwner(
b.txID,
tx.Owner,
)
return b.baseTx(&tx.BaseTx)
}

Expand All @@ -54,7 +58,10 @@ func (b *backendVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx
}

func (b *backendVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error {
// TODO: Correctly track subnet owners in [getSubnetSigners]
b.b.setSubnetOwner(
tx.Subnet,
tx.Owner,
)
return b.baseTx(&tx.BaseTx)
}

Expand Down
62 changes: 52 additions & 10 deletions wallet/chain/p/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/ava-labs/avalanchego/utils/math"
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/avalanchego/vms/components/avax"
"github.com/ava-labs/avalanchego/vms/platformvm/fx"
"github.com/ava-labs/avalanchego/vms/platformvm/signer"
"github.com/ava-labs/avalanchego/vms/platformvm/stakeable"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
Expand All @@ -25,7 +26,6 @@ import (

var (
errNoChangeAddress = errors.New("no possible change address")
errWrongTxType = errors.New("wrong tx type")
errUnknownOwnerType = errors.New("unknown owner type")
errInsufficientAuthorization = errors.New("insufficient authorization")
errInsufficientFunds = errors.New("insufficient funds")
Expand Down Expand Up @@ -134,6 +134,17 @@ type Builder interface {
options ...common.Option,
) (*txs.CreateSubnetTx, error)

// NewTransferSubnetOwnershipTx changes the owner of the named subnet.
//
// - [subnetID] specifies the subnet to be modified
// - [owner] specifies who has the ability to create new chains and add new
// validators to the subnet.
NewTransferSubnetOwnershipTx(
subnetID ids.ID,
owner *secp256k1fx.OutputOwners,
options ...common.Option,
) (*txs.TransferSubnetOwnershipTx, error)

// NewImportTx creates an import transaction that attempts to consume all
// the available UTXOs and import the funds to [to].
//
Expand Down Expand Up @@ -250,7 +261,7 @@ type Builder interface {
type BuilderBackend interface {
Context
UTXOs(ctx stdcontext.Context, sourceChainID ids.ID) ([]*avax.UTXO, error)
GetTx(ctx stdcontext.Context, txID ids.ID) (*txs.Tx, error)
GetSubnetOwner(ctx stdcontext.Context, subnetID ids.ID) (fx.Owner, error)
}

type builder struct {
Expand Down Expand Up @@ -532,6 +543,42 @@ func (b *builder) NewCreateSubnetTx(
return tx, b.initCtx(tx)
}

func (b *builder) NewTransferSubnetOwnershipTx(
subnetID ids.ID,
owner *secp256k1fx.OutputOwners,
options ...common.Option,
) (*txs.TransferSubnetOwnershipTx, error) {
toBurn := map[ids.ID]uint64{
b.backend.AVAXAssetID(): b.backend.BaseTxFee(),
}
toStake := map[ids.ID]uint64{}
ops := common.NewOptions(options)
inputs, outputs, _, err := b.spend(toBurn, toStake, ops)
if err != nil {
return nil, err
}

subnetAuth, err := b.authorizeSubnet(subnetID, ops)
if err != nil {
return nil, err
}

utils.Sort(owner.Addrs)
tx := &txs.TransferSubnetOwnershipTx{
BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
NetworkID: b.backend.NetworkID(),
BlockchainID: constants.PlatformChainID,
Ins: inputs,
Outs: outputs,
Memo: ops.Memo(),
}},
Subnet: subnetID,
Owner: owner,
SubnetAuth: subnetAuth,
}
return tx, b.initCtx(tx)
}

func (b *builder) NewImportTx(
sourceChainID ids.ID,
to *secp256k1fx.OutputOwners,
Expand Down Expand Up @@ -1100,20 +1147,15 @@ func (b *builder) spend(
}

func (b *builder) authorizeSubnet(subnetID ids.ID, options *common.Options) (*secp256k1fx.Input, error) {
subnetTx, err := b.backend.GetTx(options.Context(), subnetID)
ownerIntf, err := b.backend.GetSubnetOwner(options.Context(), subnetID)
if err != nil {
return nil, fmt.Errorf(
"failed to fetch subnet %q: %w",
"failed to fetch subnet owner for %q: %w",
subnetID,
err,
)
}
subnet, ok := subnetTx.Unsigned.(*txs.CreateSubnetTx)
if !ok {
return nil, errWrongTxType
}

owner, ok := subnet.Owner.(*secp256k1fx.OutputOwners)
owner, ok := ownerIntf.(*secp256k1fx.OutputOwners)
if !ok {
return nil, errUnknownOwnerType
}
Expand Down
12 changes: 12 additions & 0 deletions wallet/chain/p/builder_with_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,18 @@ func (b *builderWithOptions) NewCreateSubnetTx(
)
}

func (b *builderWithOptions) NewTransferSubnetOwnershipTx(
subnetID ids.ID,
owner *secp256k1fx.OutputOwners,
options ...common.Option,
) (*txs.TransferSubnetOwnershipTx, error) {
return b.Builder.NewTransferSubnetOwnershipTx(
subnetID,
owner,
common.UnionOptions(b.options, options)...,
)
}

func (b *builderWithOptions) NewImportTx(
sourceChainID ids.ID,
to *secp256k1fx.OutputOwners,
Expand Down
3 changes: 2 additions & 1 deletion wallet/chain/p/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/crypto/keychain"
"github.com/ava-labs/avalanchego/vms/components/avax"
"github.com/ava-labs/avalanchego/vms/platformvm/fx"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
)

Expand All @@ -21,7 +22,7 @@ type Signer interface {

type SignerBackend interface {
GetUTXO(ctx stdcontext.Context, chainID, utxoID ids.ID) (*avax.UTXO, error)
GetTx(ctx stdcontext.Context, txID ids.ID) (*txs.Tx, error)
GetSubnetOwner(ctx stdcontext.Context, subnetID ids.ID) (fx.Owner, error)
}

type txSigner struct {
Expand Down
11 changes: 3 additions & 8 deletions wallet/chain/p/signer_visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,20 +246,15 @@ func (s *signerVisitor) getSubnetSigners(subnetID ids.ID, subnetAuth verify.Veri
return nil, errUnknownSubnetAuthType
}

subnetTx, err := s.backend.GetTx(s.ctx, subnetID)
ownerIntf, err := s.backend.GetSubnetOwner(s.ctx, subnetID)
if err != nil {
return nil, fmt.Errorf(
"failed to fetch subnet %q: %w",
"failed to fetch subnet owner for %q: %w",
subnetID,
err,
)
}
subnet, ok := subnetTx.Unsigned.(*txs.CreateSubnetTx)
if !ok {
return nil, errWrongTxType
}

owner, ok := subnet.Owner.(*secp256k1fx.OutputOwners)
owner, ok := ownerIntf.(*secp256k1fx.OutputOwners)
if !ok {
return nil, errUnknownOwnerType
}
Expand Down
24 changes: 24 additions & 0 deletions wallet/chain/p/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@ type Wallet interface {
options ...common.Option,
) (*txs.Tx, error)

// IssueTransferSubnetOwnershipTx creates, signs, and issues a transaction that
// changes the owner of the named subnet.
//
// - [subnetID] specifies the subnet to be modified
// - [owner] specifies who has the ability to create new chains and add new
// validators to the subnet.
IssueTransferSubnetOwnershipTx(
subnetID ids.ID,
owner *secp256k1fx.OutputOwners,
options ...common.Option,
) (*txs.Tx, error)

// IssueImportTx creates, signs, and issues an import transaction that
// attempts to consume all the available UTXOs and import the funds to [to].
//
Expand Down Expand Up @@ -359,6 +371,18 @@ func (w *wallet) IssueCreateSubnetTx(
return w.IssueUnsignedTx(utx, options...)
}

func (w *wallet) IssueTransferSubnetOwnershipTx(
subnetID ids.ID,
owner *secp256k1fx.OutputOwners,
options ...common.Option,
) (*txs.Tx, error) {
utx, err := w.builder.NewTransferSubnetOwnershipTx(subnetID, owner, options...)
if err != nil {
return nil, err
}
return w.IssueUnsignedTx(utx, options...)
}

func (w *wallet) IssueImportTx(
sourceChainID ids.ID,
to *secp256k1fx.OutputOwners,
Expand Down
12 changes: 12 additions & 0 deletions wallet/chain/p/wallet_with_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ func (w *walletWithOptions) IssueCreateSubnetTx(
)
}

func (w *walletWithOptions) IssueTransferSubnetOwnershipTx(
subnetID ids.ID,
owner *secp256k1fx.OutputOwners,
options ...common.Option,
) (*txs.Tx, error) {
return w.Wallet.IssueTransferSubnetOwnershipTx(
subnetID,
owner,
common.UnionOptions(w.options, options)...,
)
}

func (w *walletWithOptions) IssueImportTx(
sourceChainID ids.ID,
to *secp256k1fx.OutputOwners,
Expand Down
4 changes: 1 addition & 3 deletions wallet/subnet/primary/examples/get-p-chain-balance/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import (
"log"
"time"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/formatting/address"
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
"github.com/ava-labs/avalanchego/wallet/chain/p"
"github.com/ava-labs/avalanchego/wallet/subnet/primary"
)
Expand All @@ -38,7 +36,7 @@ func main() {
log.Printf("fetched state of %s in %s\n", addrStr, time.Since(fetchStartTime))

pUTXOs := primary.NewChainUTXOs(constants.PlatformChainID, state.UTXOs)
pBackend := p.NewBackend(state.PCTX, pUTXOs, make(map[ids.ID]*txs.Tx))
pBackend := p.NewBackend(state.PCTX, pUTXOs, nil)
pBuilder := p.NewBuilder(addresses, pBackend)

currentBalances, err := pBuilder.GetBalance()
Expand Down

0 comments on commit 3059e51

Please sign in to comment.