Skip to content

Commit

Permalink
Merge pull request #36 from iotaledger/feat/destroy-accounts
Browse files Browse the repository at this point in the history
Destroy accounts
  • Loading branch information
daria305 authored Jan 15, 2024
2 parents e128180 + f13869a commit ac72e2c
Show file tree
Hide file tree
Showing 16 changed files with 245 additions and 89 deletions.
2 changes: 1 addition & 1 deletion components/eviltools/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func accountsSubcommands(ctx context.Context, wallet *accountwallet.AccountWalle

//nolint:all,forcetypassert
func accountsSubcommand(ctx context.Context, wallet *accountwallet.AccountWallet, subCommand accountwallet.AccountSubcommands) error {
Component.LogInfof("Run subcommand: %s, with parametetr set: %v", accountwallet.OperationCreateAccount.String(), subCommand)
Component.LogInfof("Run subcommand: %s, with parameter set: %v", subCommand.Type().String(), subCommand)

switch subCommand.Type() {
case accountwallet.OperationCreateAccount:
Expand Down
6 changes: 3 additions & 3 deletions components/eviltools/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,16 @@ func parseCreateAccountParams(paramsAccountCreate *accountwallet.ParametersAccou
return nil, ierrors.New("implicit account cannot be created without Block Issuance Feature")
}

if !paramsAccountCreate.Implicit && !paramsAccountCreate.NoTransition {
if !paramsAccountCreate.Implicit && !paramsAccountCreate.Transition {
Component.LogWarn("Implicit flag set to false, account will be created non-implicitly by Faucet, no need for transition, flag will be ignored")
paramsAccountCreate.NoTransition = true
paramsAccountCreate.Transition = true
}

return &accountwallet.CreateAccountParams{
Alias: paramsAccountCreate.Alias,
NoBIF: paramsAccountCreate.NoBlockIssuerFeature,
Implicit: paramsAccountCreate.Implicit,
Transition: paramsAccountCreate.NoTransition,
Transition: paramsAccountCreate.Transition,
}, nil
}

Expand Down
2 changes: 1 addition & 1 deletion config_defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"alias": "",
"noBlockIssuerFeature": false,
"implicit": false,
"noTransition": false
"transition": false
},
"convert": {
"alias": ""
Expand Down
14 changes: 7 additions & 7 deletions configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,12 @@ Example:

### <a id="eviltools_accounts_create"></a> Create

| Name | Description | Type | Default value |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------- |
| alias | Alias name of the account to create | string | "" |
| noBlockIssuerFeature | Create account without Block Issuer Feature, can only be set false no if implicit is false, as each account created implicitly needs to have BIF. | boolean | false |
| implicit | Create an implicit account | boolean | false |
| noTransition | Account should not be transitioned to a full account if created with implicit address. Transition enabled by default, to disable provide an empty flag. | boolean | false |
| Name | Description | Type | Default value |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------- |
| alias | Alias name of the account to create | string | "" |
| noBlockIssuerFeature | Create account without Block Issuer Feature, can only be set false no if implicit is false, as each account created implicitly needs to have BIF. | boolean | false |
| implicit | Create an implicit account | boolean | false |
| transition | Account should be transitioned to a full account if created with implicit address. Transition disabled by default, to enable provide an empty flag. | boolean | false |

### <a id="eviltools_accounts_convert"></a> Convert

Expand Down Expand Up @@ -239,7 +239,7 @@ Example:
"alias": "",
"noBlockIssuerFeature": false,
"implicit": false,
"noTransition": false
"transition": false
},
"convert": {
"alias": ""
Expand Down
14 changes: 8 additions & 6 deletions pkg/accountwallet/accountcreation.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
"github.com/iotaledger/iota.go/v4/wallet"
)

// createAccountImplicitly creates an implicit account by sending a faucet request for ImplicitAddressCreation funds.
func (a *AccountWallet) createAccountImplicitly(ctx context.Context, params *CreateAccountParams) (iotago.AccountID, error) {
// createImplicitAccount creates an implicit account by sending a faucet request for ImplicitAddressCreation funds.
func (a *AccountWallet) createImplicitAccount(ctx context.Context, params *CreateAccountParams) (iotago.AccountID, error) {
a.LogDebug("Creating an implicit account")
// An implicit account has an implicitly defined Block Issuer Key, corresponding to the address itself.
// Thus, implicit accounts can issue blocks by signing them with the private key corresponding to the public key
Expand All @@ -32,7 +32,8 @@ func (a *AccountWallet) createAccountImplicitly(ctx context.Context, params *Cre
if !ok {
return iotago.EmptyAccountID, ierrors.New("failed to convert account id to address")
}
a.LogDebug("created implicit account output with outputID: ", implicitOutputID.ToHex(), " accountAddress: ", accountAddress.Bech32(a.client.CommittedAPI().ProtocolParameters().Bech32HRP()))
a.LogDebugf("Created implicit account output, outputID: %s", implicitOutputID.ToHex())
a.LogInfof("Posted transaction: implicit account output from faucet\nBech addr: %s", accountAddress.Bech32(a.client.CommittedAPI().ProtocolParameters().Bech32HRP()))

err = a.checkAccountStatus(ctx, iotago.EmptyBlockID, implicitAccountOutput.OutputID.TransactionID(), implicitOutputID, accountAddress)
if err != nil {
Expand Down Expand Up @@ -66,7 +67,7 @@ func (a *AccountWallet) transitionImplicitAccount(ctx context.Context, implicitA

// transaction preparation, issue block with implicit account
implicitAccountIssuer := wallet.NewEd25519Account(accountID, implicitAccountOutput.PrivateKey)
congestionResp, issuerResp, version, err := a.RequestBlockBuiltData(ctx, a.client, implicitAccountIssuer)
congestionResp, issuerResp, version, err := a.RequestBlockIssuanceData(ctx, a.client, implicitAccountIssuer)
if err != nil {
return iotago.EmptyAccountID, ierrors.Wrap(err, "failed to request block built data for the implicit account")
}
Expand All @@ -75,7 +76,7 @@ func (a *AccountWallet) transitionImplicitAccount(ctx context.Context, implicitA
if err != nil {
return iotago.EmptyAccountID, ierrors.Wrap(err, "failed to create account creation transaction")
}
a.LogDebugf("Transition transaction created:\n%s\n", utils.SprintTransaction(a.client.LatestAPI(), signedTx))
a.LogDebugf("Implicit account transition transaction created:\n%s\n", utils.SprintTransaction(a.client.LatestAPI(), signedTx))

// post block with implicit account
blkID, err := a.PostWithBlock(ctx, a.client, signedTx, implicitAccountIssuer, congestionResp, issuerResp, version)
Expand All @@ -88,6 +89,7 @@ func (a *AccountWallet) transitionImplicitAccount(ctx context.Context, implicitA

// get OutputID of account output
accOutputID := iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(signedTx.Transaction.ID()), 0)
a.LogInfof("Posted transaction: transition implicit account to full account\nBech addr: %s", accountAddress.Bech32(a.client.CommittedAPI().ProtocolParameters().Bech32HRP()))
err = a.checkAccountStatus(ctx, blkID, lo.PanicOnErr(signedTx.Transaction.ID()), accOutputID, accountAddress, true)
if err != nil {
return iotago.EmptyAccountID, ierrors.Wrap(err, "failure in account creation")
Expand Down Expand Up @@ -117,7 +119,7 @@ func (a *AccountWallet) createAccountWithFaucet(ctx context.Context, params *Cre
// no accountID should be specified during the account creation
BlockIssuer(blockIssuerKeys, iotago.MaxSlotIndex).MustBuild()

congestionResp, issuerResp, version, err := a.RequestBlockBuiltData(ctx, a.client, a.GenesisAccount)
congestionResp, issuerResp, version, err := a.RequestBlockIssuanceData(ctx, a.client, a.GenesisAccount)
if err != nil {
return iotago.EmptyAccountID, ierrors.Wrap(err, "failed to request block built data for the faucet account")
}
Expand Down
182 changes: 182 additions & 0 deletions pkg/accountwallet/accountdestruction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package accountwallet

import (
"context"
"time"

"github.com/iotaledger/evil-tools/pkg/models"
"github.com/iotaledger/evil-tools/pkg/utils"
"github.com/iotaledger/hive.go/ierrors"
"github.com/iotaledger/hive.go/lo"
iotago "github.com/iotaledger/iota.go/v4"
"github.com/iotaledger/iota.go/v4/builder"
"github.com/iotaledger/iota.go/v4/wallet"
)

func (a *AccountWallet) destroyAccount(ctx context.Context, alias string) error {
accData, err := a.GetAccount(alias)
if err != nil {
return err
}
// get output from node
// From TIP42: Indexers and node plugins shall map the account address of the output derived with Account ID to the regular address -> output mapping table, so that given an Account Address, its most recent unspent account output can be retrieved.
// TODO: use correct outputID
accountOutput := a.client.GetOutput(ctx, accData.OutputID)
switch accountOutput.Type() {
case iotago.OutputBasic:
a.LogInfof("Cannot destroy implicit account %s", alias)

return nil
}

keyManager, err := wallet.NewKeyManager(a.seed[:], BIP32PathForIndex(accData.Index))
if err != nil {
return err
}
{
// first, transition the account so block issuer feature expires if it is not already.
issuingTime := time.Now()
issuingSlot := a.client.LatestAPI().TimeProvider().SlotFromTime(issuingTime)
apiForSlot := a.client.APIForSlot(issuingSlot)
// get the latest block issuance data from the node
congestionResp, issuerResp, version, err := a.RequestBlockIssuanceData(ctx, a.client, a.GenesisAccount)
if err != nil {
return ierrors.Wrap(err, "failed to request block built data for the faucet account")
}
commitmentID := lo.Return1(issuerResp.LatestCommitment.ID())
commitmentSlot := commitmentID.Slot()
// transition it to expire if it is not already expired relative to latest commitment
if accountOutput.FeatureSet().BlockIssuer().ExpirySlot > commitmentSlot {
pastBoundedSlot := commitmentSlot + apiForSlot.ProtocolParameters().MaxCommittableAge()
// change the expiry slot to expire as soon as possible
signedTx, err := a.changeExpirySlotTransaction(ctx, pastBoundedSlot, issuingSlot, accData, commitmentID, keyManager.AddressSigner())
if err != nil {
return ierrors.Wrap(err, "failed to build transaction")
}
// issue the transaction in a block
blockID, err := a.PostWithBlock(ctx, a.client, signedTx, a.GenesisAccount, congestionResp, issuerResp, version)
if err != nil {
return ierrors.Wrapf(err, "failed to post block with ID %s", blockID)
}
a.LogInfof("Posted transaction: transition account to expire in slot %d\nBech addr: %s", pastBoundedSlot, accData.Account.Address().Bech32(a.client.CommittedAPI().ProtocolParameters().Bech32HRP()))

// check the status of the transaction
expiredAccountOutputID := iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(signedTx.Transaction.ID()), 0)
err = a.checkAccountStatus(ctx, blockID, lo.PanicOnErr(signedTx.Transaction.ID()), expiredAccountOutputID, accData.Account.Address())
if err != nil {
return ierrors.Wrap(err, "failure checking for commitment of account transition")
}

// update the account output details in the wallet
a.registerAccount(alias, accData.Account.ID(), expiredAccountOutputID, accData.Index, accData.Account.PrivateKey())

// wait until the expiry slot has been committed
a.LogInfof("Waiting for expiry slot %d to be committed, 1 slot after expiry slot", pastBoundedSlot+1)
if err := utils.AwaitCommitment(ctx, a.Logger, a.client, pastBoundedSlot+1); err != nil {
return ierrors.Wrap(err, "failed to await commitment of expiry slot")
}
}

}
{
// next, issue a transaction to destroy the account output
issuingTime := time.Now()
issuingSlot := a.client.LatestAPI().TimeProvider().SlotFromTime(issuingTime)

// get the details of the expired account output
accData, err := a.GetAccount(alias)
if err != nil {
return err
}
// get the latest block issuance data from the node
congestionResp, issuerResp, version, err := a.RequestBlockIssuanceData(ctx, a.client, a.GenesisAccount)
if err != nil {
return ierrors.Wrap(err, "failed to request block built data for the faucet account")
}
commitmentID := lo.Return1(issuerResp.LatestCommitment.ID())

// create a transaction destroying the account
signedTx, err := a.destroyAccountTransaction(ctx, issuingSlot, accData, commitmentID, keyManager.AddressSigner())
if err != nil {
return ierrors.Wrap(err, "failed to build transaction")
}
// issue the transaction in a block
blockID, err := a.PostWithBlock(ctx, a.client, signedTx, a.GenesisAccount, congestionResp, issuerResp, version)
if err != nil {
return ierrors.Wrapf(err, "failed to post block with ID %s", blockID)
}
a.LogInfof("Posted transaction: destroy account\nBech addr: %s", accData.Account.Address().Bech32(a.client.CommittedAPI().ProtocolParameters().Bech32HRP()))

// check the status of the transaction
basicOutputID := iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(signedTx.Transaction.ID()), 0)
err = a.checkAccountStatus(ctx, blockID, lo.PanicOnErr(signedTx.Transaction.ID()), basicOutputID, nil)
if err != nil {
return ierrors.Wrap(err, "failure checking for commitment of account transition")
}

// remove account from wallet
a.deleteAccount(alias)

a.LogInfof("Account %s has been destroyed", alias)
}

return nil
}

func (a *AccountWallet) changeExpirySlotTransaction(ctx context.Context, newExpirySlot iotago.SlotIndex, issuingSlot iotago.SlotIndex, accData *models.AccountData, commitmentID iotago.CommitmentID, addressSigner iotago.AddressSigner) (*iotago.SignedTransaction, error) {
// start building the transaction
apiForSlot := a.client.APIForSlot(issuingSlot)
txBuilder := builder.NewTransactionBuilder(apiForSlot)
accountOutput := a.client.GetOutput(ctx, accData.OutputID)

// add the account output as input
txBuilder.AddInput(&builder.TxInput{
UnlockTarget: accountOutput.UnlockConditionSet().Address().Address,
InputID: accData.OutputID,
Input: accountOutput,
})
// create an account output with updated expiry slot set to commitment slot + MaxCommittableAge (pastBoundedSlot)
// nolint:forcetypeassert // we know that this is an account output
accountBuilder := builder.NewAccountOutputBuilderFromPrevious(accountOutput.(*iotago.AccountOutput))
accountBuilder.BlockIssuer(accountOutput.FeatureSet().BlockIssuer().BlockIssuerKeys, newExpirySlot)
expiredAccountOutput := accountBuilder.MustBuild()
// add the expired account output as output
txBuilder.AddOutput(expiredAccountOutput)
// add the commitment input
txBuilder.AddCommitmentInput(&iotago.CommitmentInput{CommitmentID: commitmentID})
// add a block issuance credit input
txBuilder.AddBlockIssuanceCreditInput(&iotago.BlockIssuanceCreditInput{AccountID: accData.Account.ID()})
// set the creation slot to the issuance slot
txBuilder.SetCreationSlot(issuingSlot)
// set the transaction capabilities to be able to do anything
txBuilder.WithTransactionCapabilities(iotago.TransactionCapabilitiesBitMaskWithCapabilities(iotago.WithTransactionCanDoAnything()))
// build the transaction
return txBuilder.Build(addressSigner)
}

func (a *AccountWallet) destroyAccountTransaction(ctx context.Context, issuingSlot iotago.SlotIndex, accData *models.AccountData, commitmentID iotago.CommitmentID, addressSigner iotago.AddressSigner) (*iotago.SignedTransaction, error) {
// start building the transaction
apiForSlot := a.client.APIForSlot(issuingSlot)
txBuilder := builder.NewTransactionBuilder(apiForSlot)
expiredAccountOutput := a.client.GetOutput(ctx, accData.OutputID)
txBuilder.AddInput(&builder.TxInput{
UnlockTarget: expiredAccountOutput.UnlockConditionSet().Address().Address,
InputID: accData.OutputID,
Input: expiredAccountOutput,
})
// add a basic output to output side
addr, _, _ := a.getAddress(iotago.AddressEd25519)
basicOutput := builder.NewBasicOutputBuilder(addr, expiredAccountOutput.BaseTokenAmount()).MustBuild()
txBuilder.AddOutput(basicOutput)
// set the creation slot to the issuance slot
txBuilder.SetCreationSlot(issuingSlot)
// add the commitment input
txBuilder.AddCommitmentInput(&iotago.CommitmentInput{CommitmentID: commitmentID})
// add a block issuance credit input
txBuilder.AddBlockIssuanceCreditInput(&iotago.BlockIssuanceCreditInput{AccountID: accData.Account.ID()})
// set the transaction capabilities to be able to do anything
txBuilder.WithTransactionCapabilities(iotago.TransactionCapabilitiesBitMaskWithCapabilities(iotago.WithTransactionCanDoAnything()))

// build the transaction
return txBuilder.Build(addressSigner)
}
2 changes: 1 addition & 1 deletion pkg/accountwallet/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
// CreateAccount creates an implicit account and immediately transition it to a regular account.
func (a *AccountWallet) CreateAccount(ctx context.Context, params *CreateAccountParams) (iotago.AccountID, error) {
if params.Implicit {
return a.createAccountImplicitly(ctx, params)
return a.createImplicitAccount(ctx, params)
}

return a.createAccountWithFaucet(ctx, params)
Expand Down
2 changes: 1 addition & 1 deletion pkg/accountwallet/faucet.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const (
GenesisAccountAlias = "genesis-account"
)

func (a *AccountWallet) RequestBlockBuiltData(ctx context.Context, clt models.Client, account wallet.Account) (*api.CongestionResponse, *api.IssuanceBlockHeaderResponse, iotago.Version, error) {
func (a *AccountWallet) RequestBlockIssuanceData(ctx context.Context, clt models.Client, account wallet.Account) (*api.CongestionResponse, *api.IssuanceBlockHeaderResponse, iotago.Version, error) {
issuerResp, err := clt.GetBlockIssuance(ctx)
if err != nil {
return nil, nil, 0, ierrors.Wrapf(err, "failed to get block issuance data for accID %s, addr %s", account.ID().ToHex(), account.Address().String())
Expand Down
2 changes: 1 addition & 1 deletion pkg/accountwallet/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type (
Alias string `default:"" usage:"Alias name of the account to create"`
NoBlockIssuerFeature bool `default:"false" usage:"Create account without Block Issuer Feature, can only be set false no if implicit is false, as each account created implicitly needs to have BIF."`
Implicit bool `default:"false" usage:"Create an implicit account"`
NoTransition bool `default:"false" usage:"account should not be transitioned to a full account if created with implicit address. Transition enabled by default, to disable provide an empty flag."`
Transition bool `default:"false" usage:"account should be transitioned to a full account if created with implicit address. Transition disabled by default, to enable provide an empty flag."`
}

ParametersAccountsConvert struct {
Expand Down
Loading

0 comments on commit ac72e2c

Please sign in to comment.