diff --git a/tools/docker-network/tests/accounttransition_test.go b/tools/docker-network/tests/accounttransition_test.go index 3dd4b6953..54f72324b 100644 --- a/tools/docker-network/tests/accounttransition_test.go +++ b/tools/docker-network/tests/accounttransition_test.go @@ -41,15 +41,15 @@ func Test_AccountTransitions(t *testing.T) { // create account1 fmt.Println("Creating account1") - wallet1, _ := d.CreateAccount() + wallet1, _ := d.CreateAccountFromFaucet() // create account2 fmt.Println("Creating account2") - wallet2, _ := d.CreateAccount() + wallet2, _ := d.CreateAccountFromFaucet() // allot 1000 mana from account1 to account2 fmt.Println("Allotting mana from account1 to account2") - d.AllotManaTo(wallet1, wallet2.BlockIssuer.AccountData, 1000) + d.RequestFaucetFundsAndAllotManaTo(wallet1, wallet2.BlockIssuer.AccountData, 1000) // create native token fmt.Println("Creating native token") diff --git a/tools/docker-network/tests/api_core_test.go b/tools/docker-network/tests/api_core_test.go index 8b5e22ce4..838f5d5b4 100644 --- a/tools/docker-network/tests/api_core_test.go +++ b/tools/docker-network/tests/api_core_test.go @@ -207,7 +207,7 @@ func prepareAssets(d *dockertestframework.DockerTestFramework, totalAssetsNum in for i := 0; i < totalAssetsNum; i++ { // account - wallet, account := d.CreateAccount() + wallet, account := d.CreateAccountFromFaucet() assets.setupAssetsForSlot(account.OutputID.Slot()) assets[account.OutputID.Slot()].accountAddress = account.Address @@ -301,7 +301,12 @@ func Test_ValidatorsAPI(t *testing.T) { wallet, implicitAccountOutputData := d.CreateImplicitAccount(ctx) // create account with staking feature for every validator - accountData := d.CreateAccountFromImplicitAccount(wallet, implicitAccountOutputData, wallet.GetNewBlockIssuanceResponse(), dockertestframework.WithStakingFeature(100, 1, currentEpoch)) + accountData := d.CreateAccountFromImplicitAccount(wallet, + implicitAccountOutputData, + wallet.GetNewBlockIssuanceResponse(), + dockertestframework.WithStakingFeature(100, 1, currentEpoch), + ) + expectedValidators = append(expectedValidators, accountData.Address.Bech32(hrp)) // issue candidacy payload in the next epoch (currentEpoch + 1), in order to issue it before epochNearingThreshold @@ -660,7 +665,7 @@ func Test_CoreAPI(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - d.RequestFromClients(test.testFunc) + d.RequestFromNodes(test.testFunc) }) } @@ -887,7 +892,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - d.RequestFromClients(test.testFunc) + d.RequestFromNodes(test.testFunc) }) } } diff --git a/tools/docker-network/tests/committeerotation_test.go b/tools/docker-network/tests/committeerotation_test.go index f3ea39970..6e117dcc4 100644 --- a/tools/docker-network/tests/committeerotation_test.go +++ b/tools/docker-network/tests/committeerotation_test.go @@ -214,7 +214,11 @@ func Test_Staking(t *testing.T) { wallet, implicitAccountOutputData := d.CreateImplicitAccount(ctx) // create account with staking feature for the validator - accountData := d.CreateAccountFromImplicitAccount(wallet, implicitAccountOutputData, wallet.GetNewBlockIssuanceResponse(), dockertestframework.WithStakingFeature(100, 1, 0)) + accountData := d.CreateAccountFromImplicitAccount(wallet, + implicitAccountOutputData, + wallet.GetNewBlockIssuanceResponse(), + dockertestframework.WithStakingFeature(100, 1, 0), + ) d.AssertValidatorExists(accountData.Address) } @@ -251,7 +255,7 @@ func Test_Delegation(t *testing.T) { d.WaitUntilNetworkReady() // create an account to perform delegation - wallet, _ := d.CreateAccount() + wallet, _ := d.CreateAccountFromFaucet() // delegate all faucet funds to V2, V2 should replace V3 //nolint:forcetypeassert diff --git a/tools/docker-network/tests/dockertestframework/accounts.go b/tools/docker-network/tests/dockertestframework/accounts.go new file mode 100644 index 000000000..d09293ee1 --- /dev/null +++ b/tools/docker-network/tests/dockertestframework/accounts.go @@ -0,0 +1,177 @@ +//go:build dockertests + +package dockertestframework + +import ( + "context" + "fmt" + + "github.com/stretchr/testify/require" + + "github.com/iotaledger/hive.go/runtime/options" + "github.com/iotaledger/iota-core/pkg/testsuite/mock" + iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/api" + "github.com/iotaledger/iota.go/v4/builder" +) + +func (d *DockerTestFramework) AccountsFromNodes(nodes ...*Node) []string { + var accounts []string + for _, node := range nodes { + if node.AccountAddressBech32 != "" { + accounts = append(accounts, node.AccountAddressBech32) + } + } + + return accounts +} + +func (d *DockerTestFramework) CheckAccountStatus(ctx context.Context, blkID iotago.BlockID, txID iotago.TransactionID, creationOutputID iotago.OutputID, accountAddress *iotago.AccountAddress, checkIndexer ...bool) { + // request by blockID if provided, otherwise use txID + // we take the slot from the blockID in case the tx is created earlier than the block. + clt := d.defaultWallet.Client + slot := blkID.Slot() + + if blkID == iotago.EmptyBlockID { + blkMetadata, err := clt.TransactionIncludedBlockMetadata(ctx, txID) + require.NoError(d.Testing, err) + + blkID = blkMetadata.BlockID + slot = blkMetadata.BlockID.Slot() + } + + d.AwaitTransactionPayloadAccepted(ctx, txID) + + // wait for the account to be committed + d.AwaitCommitment(slot) + + // Check the indexer + if len(checkIndexer) > 0 && checkIndexer[0] { + indexerClt, err := d.defaultWallet.Client.Indexer(ctx) + require.NoError(d.Testing, err) + + _, _, _, err = indexerClt.Account(ctx, accountAddress) + require.NoError(d.Testing, err) + } + + // check if the creation output exists + _, err := clt.OutputByID(ctx, creationOutputID) + require.NoError(d.Testing, err) +} + +// CreateImplicitAccount requests faucet funds and creates an implicit account. It already wait until the transaction is committed and the created account is useable. +func (d *DockerTestFramework) CreateImplicitAccount(ctx context.Context) (*mock.Wallet, *mock.OutputData) { + newWallet := mock.NewWallet(d.Testing, "", d.defaultWallet.Client, &DockerWalletClock{client: d.defaultWallet.Client}) + implicitAccountOutputData := d.RequestFaucetFunds(ctx, newWallet, iotago.AddressImplicitAccountCreation) + + accountID := iotago.AccountIDFromOutputID(implicitAccountOutputData.ID) + accountAddress, ok := accountID.ToAddress().(*iotago.AccountAddress) + require.True(d.Testing, ok) + + // make sure an implicit account is committed + d.CheckAccountStatus(ctx, iotago.EmptyBlockID, implicitAccountOutputData.ID.TransactionID(), implicitAccountOutputData.ID, accountAddress) + + // update the wallet with the new account data + newWallet.SetBlockIssuer(&mock.AccountData{ + ID: accountID, + Address: accountAddress, + OutputID: implicitAccountOutputData.ID, + AddressIndex: implicitAccountOutputData.AddressIndex, + }) + + return newWallet, implicitAccountOutputData +} + +// TransitionImplicitAccountToAccountOutputBlock consumes the given implicit account, then build the account transition block with the given account output options. +func (d *DockerTestFramework) TransitionImplicitAccountToAccountOutputBlock(accountWallet *mock.Wallet, implicitAccountOutputData *mock.OutputData, blockIssuance *api.IssuanceBlockHeaderResponse, opts ...options.Option[builder.AccountOutputBuilder]) (*mock.AccountData, *iotago.SignedTransaction, *iotago.Block) { + ctx := context.TODO() + + var implicitBlockIssuerKey iotago.BlockIssuerKey = iotago.Ed25519PublicKeyHashBlockIssuerKeyFromImplicitAccountCreationAddress(accountWallet.ImplicitAccountCreationAddress()) + opts = append(opts, mock.WithBlockIssuerFeature( + iotago.NewBlockIssuerKeys(implicitBlockIssuerKey), + iotago.MaxSlotIndex, + )) + + signedTx := accountWallet.TransitionImplicitAccountToAccountOutputWithBlockIssuance("", []*mock.OutputData{implicitAccountOutputData}, blockIssuance, opts...) + + // The account transition block should be issued by the implicit account block issuer key. + block, err := accountWallet.CreateBasicBlock(ctx, "", mock.WithPayload(signedTx)) + require.NoError(d.Testing, err) + accOutputID := iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) + accOutput := signedTx.Transaction.Outputs[0].(*iotago.AccountOutput) + accAddress := (accOutput.AccountID).ToAddress().(*iotago.AccountAddress) + + accountOutputData := &mock.AccountData{ + ID: accOutput.AccountID, + Address: accAddress, + Output: accOutput, + OutputID: accOutputID, + AddressIndex: implicitAccountOutputData.AddressIndex, + } + + return accountOutputData, signedTx, block.ProtocolBlock() +} + +// CreateAccountFromImplicitAccount transitions an account from the given implicit one to full one, it already wait until the transaction is committed and the created account is useable. +func (d *DockerTestFramework) CreateAccountFromImplicitAccount(accountWallet *mock.Wallet, implicitAccountOutputData *mock.OutputData, blockIssuance *api.IssuanceBlockHeaderResponse, opts ...options.Option[builder.AccountOutputBuilder]) *mock.AccountData { + ctx := context.TODO() + + accountData, signedTx, block := d.TransitionImplicitAccountToAccountOutputBlock(accountWallet, implicitAccountOutputData, blockIssuance, opts...) + + d.SubmitBlock(ctx, block) + d.CheckAccountStatus(ctx, block.MustID(), signedTx.Transaction.MustID(), accountData.OutputID, accountData.Address, true) + + // update the wallet with the new account data + accountWallet.SetBlockIssuer(accountData) + + fmt.Printf("Account created, Bech addr: %s\n", accountData.Address.Bech32(accountWallet.Client.CommittedAPI().ProtocolParameters().Bech32HRP())) + + return accountWallet.Account(accountData.ID) +} + +// CreateAccountFromFaucet creates a new account by requesting faucet funds to an implicit account address and then transitioning the new output to a full account output. +// It already waits until the transaction is committed and the created account is useable. +func (d *DockerTestFramework) CreateAccountFromFaucet() (*mock.Wallet, *mock.AccountData) { + ctx := context.TODO() + + newWallet, implicitAccountOutputData := d.CreateImplicitAccount(ctx) + + accountData, signedTx, block := d.TransitionImplicitAccountToAccountOutputBlock(newWallet, implicitAccountOutputData, d.defaultWallet.GetNewBlockIssuanceResponse()) + + d.SubmitBlock(ctx, block) + d.CheckAccountStatus(ctx, block.MustID(), signedTx.Transaction.MustID(), accountData.OutputID, accountData.Address, true) + + // update the wallet with the new account data + newWallet.SetBlockIssuer(accountData) + + fmt.Printf("Account created, Bech addr: %s\n", accountData.Address.Bech32(newWallet.Client.CommittedAPI().ProtocolParameters().Bech32HRP())) + + return newWallet, newWallet.Account(accountData.ID) +} + +// CreateNativeToken request faucet funds then use it to create native token for the account, and returns the updated Account. +func (d *DockerTestFramework) CreateNativeToken(fromWallet *mock.Wallet, mintedAmount iotago.BaseToken, maxSupply iotago.BaseToken) { + require.GreaterOrEqual(d.Testing, maxSupply, mintedAmount) + + ctx := context.TODO() + + // requesting faucet funds for native token creation + fundsOutputData := d.RequestFaucetFunds(ctx, fromWallet, iotago.AddressEd25519) + + signedTx := fromWallet.CreateFoundryAndNativeTokensFromInput(fundsOutputData, mintedAmount, maxSupply) + + block, err := fromWallet.CreateAndSubmitBasicBlock(ctx, "native_token", mock.WithPayload(signedTx)) + require.NoError(d.Testing, err) + + txID := signedTx.Transaction.MustID() + d.AwaitTransactionPayloadAccepted(ctx, txID) + + fmt.Println("Create native tokens transaction sent, blkID:", block.ID().ToHex(), ", txID:", signedTx.Transaction.MustID().ToHex(), ", slot:", block.ID().Slot()) + + // wait for the account to be committed + d.AwaitCommitment(block.ID().Slot()) + + d.AssertIndexerAccount(fromWallet.BlockIssuer.AccountData) + //nolint:forcetypeassert + d.AssertIndexerFoundry(signedTx.Transaction.Outputs[1].(*iotago.FoundryOutput).MustFoundryID()) +} diff --git a/tools/docker-network/tests/dockertestframework/asserts.go b/tools/docker-network/tests/dockertestframework/asserts.go new file mode 100644 index 000000000..3764ee2c3 --- /dev/null +++ b/tools/docker-network/tests/dockertestframework/asserts.go @@ -0,0 +1,121 @@ +//go:build dockertests + +package dockertestframework + +import ( + "context" + "fmt" + "sort" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/iotaledger/hive.go/ierrors" + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/iota-core/pkg/testsuite/mock" + iotago "github.com/iotaledger/iota.go/v4" +) + +func (d *DockerTestFramework) AssertIndexerAccount(account *mock.AccountData) { + d.Eventually(func() error { + ctx := context.TODO() + indexerClt, err := d.defaultWallet.Client.Indexer(ctx) + if err != nil { + return err + } + + outputID, output, _, err := indexerClt.Account(ctx, account.Address) + if err != nil { + return err + } + + assert.EqualValues(d.fakeTesting, account.OutputID, *outputID) + assert.EqualValues(d.fakeTesting, account.Output, output) + + return nil + }) +} + +func (d *DockerTestFramework) AssertIndexerFoundry(foundryID iotago.FoundryID) { + d.Eventually(func() error { + ctx := context.TODO() + indexerClt, err := d.defaultWallet.Client.Indexer(ctx) + if err != nil { + return err + } + + _, _, _, err = indexerClt.Foundry(ctx, foundryID) + if err != nil { + return err + } + + return nil + }) +} + +func (d *DockerTestFramework) AssertValidatorExists(accountAddr *iotago.AccountAddress) { + d.Eventually(func() error { + for _, node := range d.Nodes() { + _, err := d.Client(node.Name).Validator(context.TODO(), accountAddr) + if err != nil { + return err + } + } + + return nil + }) +} + +func (d *DockerTestFramework) AssertCommittee(expectedEpoch iotago.EpochIndex, expectedCommitteeMember []string) { + fmt.Println("Wait for committee selection..., expected epoch: ", expectedEpoch, ", expected committee size: ", len(expectedCommitteeMember)) + defer fmt.Println("Wait for committee selection......done") + + sort.Strings(expectedCommitteeMember) + + status := d.NodeStatus("V1") + testAPI := d.defaultWallet.Client.CommittedAPI() + expectedSlotStart := testAPI.TimeProvider().EpochStart(expectedEpoch) + require.Greater(d.Testing, expectedSlotStart, status.LatestAcceptedBlockSlot) + + if status.LatestAcceptedBlockSlot < expectedSlotStart { + slotToWait := expectedSlotStart - status.LatestAcceptedBlockSlot + secToWait := time.Duration(slotToWait) * time.Duration(testAPI.ProtocolParameters().SlotDurationInSeconds()) * time.Second + fmt.Println("Wait for ", secToWait, "until expected epoch: ", expectedEpoch) + time.Sleep(secToWait) + } + + d.Eventually(func() error { + for _, node := range d.Nodes() { + resp, err := d.Client(node.Name).Committee(context.TODO()) + if err != nil { + return err + } + + if resp.Epoch == expectedEpoch { + members := make([]string, len(resp.Committee)) + for i, member := range resp.Committee { + members[i] = member.AddressBech32 + } + + sort.Strings(members) + if match := lo.Equal(expectedCommitteeMember, members); match { + return nil + } + + return ierrors.Errorf("committee members does not match as expected, expected: %v, actual: %v", expectedCommitteeMember, members) + } + } + + return nil + }) +} + +func (d *DockerTestFramework) AssertFinalizedSlot(condition func(iotago.SlotIndex) error) { + for _, node := range d.Nodes() { + status := d.NodeStatus(node.Name) + + err := condition(status.LatestFinalizedSlot) + require.NoError(d.Testing, err) + } +} diff --git a/tools/docker-network/tests/dockertestframework/awaits.go b/tools/docker-network/tests/dockertestframework/awaits.go new file mode 100644 index 000000000..553134cfb --- /dev/null +++ b/tools/docker-network/tests/dockertestframework/awaits.go @@ -0,0 +1,146 @@ +//go:build dockertests + +package dockertestframework + +import ( + "context" + "time" + + "github.com/stretchr/testify/require" + + "github.com/iotaledger/hive.go/ierrors" + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/iota-core/pkg/testsuite/mock" + iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/api" +) + +func (d *DockerTestFramework) AwaitTransactionPayloadAccepted(ctx context.Context, txID iotago.TransactionID) { + clt := d.defaultWallet.Client + + d.Eventually(func() error { + resp, err := clt.TransactionMetadata(ctx, txID) + if err != nil { + return err + } + + if resp.TransactionState == api.TransactionStateAccepted || + resp.TransactionState == api.TransactionStateCommitted || + resp.TransactionState == api.TransactionStateFinalized { + if resp.TransactionFailureReason == api.TxFailureNone { + return nil + } + } + + return ierrors.Errorf("transaction %s is pending or having errors, state: %s, failure reason: %s, failure details: %s", txID.ToHex(), resp.TransactionState, resp.TransactionFailureReason, resp.TransactionFailureDetails) + }) +} + +func (d *DockerTestFramework) AwaitTransactionState(ctx context.Context, txID iotago.TransactionID, expectedState api.TransactionState) { + d.Eventually(func() error { + resp, err := d.defaultWallet.Client.TransactionMetadata(ctx, txID) + if err != nil { + return err + } + + if expectedState == resp.TransactionState { + return nil + } else { + if resp.TransactionState == api.TransactionStateFailed { + return ierrors.Errorf("expected transaction %s to have state '%s', got '%s' instead, failure reason: %s, failure details: %s", txID, expectedState, resp.TransactionState, resp.TransactionFailureReason, resp.TransactionFailureDetails) + } + return ierrors.Errorf("expected transaction %s to have state '%s', got '%s' instead", txID, expectedState, resp.TransactionState) + } + }) +} + +func (d *DockerTestFramework) AwaitTransactionFailure(ctx context.Context, txID iotago.TransactionID, expectedReason api.TransactionFailureReason) { + d.Eventually(func() error { + resp, err := d.defaultWallet.Client.TransactionMetadata(ctx, txID) + if err != nil { + return err + } + + if expectedReason == resp.TransactionFailureReason { + return nil + } else { + return ierrors.Errorf("expected transaction %s to have failure reason '%s', got '%s' instead, failure details: %s", txID, expectedReason, resp.TransactionFailureReason, resp.TransactionFailureDetails) + } + }) +} + +func (d *DockerTestFramework) AwaitCommitment(targetSlot iotago.SlotIndex) { + currentCommittedSlot := d.NodeStatus("V1").LatestCommitmentID.Slot() + + // we wait at max "targetSlot - currentCommittedSlot" times * slot duration + deadline := time.Duration(d.defaultWallet.Client.CommittedAPI().ProtocolParameters().SlotDurationInSeconds()) * time.Second + if currentCommittedSlot < targetSlot { + deadline *= time.Duration(targetSlot - currentCommittedSlot) + } + + // give some extra time for peering etc + deadline += 30 * time.Second + + d.EventuallyWithDurations(func() error { + latestCommittedSlot := d.NodeStatus("V1").LatestCommitmentID.Slot() + if targetSlot > latestCommittedSlot { + return ierrors.Errorf("committed slot %d is not reached yet, current committed slot %d", targetSlot, latestCommittedSlot) + } + + return nil + }, deadline, 1*time.Second) +} + +func (d *DockerTestFramework) AwaitFinalization(targetSlot iotago.SlotIndex) { + d.Eventually(func() error { + currentFinalisedSlot := d.NodeStatus("V1").LatestFinalizedSlot + if targetSlot > currentFinalisedSlot { + return ierrors.Errorf("finalized slot %d is not reached yet", targetSlot) + } + + return nil + }) +} + +func (d *DockerTestFramework) AwaitNextEpoch() { + //nolint:lostcancel + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + + info, err := d.defaultWallet.Client.Info(ctx) + require.NoError(d.Testing, err) + + currentEpoch := d.defaultWallet.Client.CommittedAPI().TimeProvider().EpochFromSlot(info.Status.LatestFinalizedSlot) + + // await the start slot of the next epoch + d.AwaitFinalization(d.defaultWallet.Client.CommittedAPI().TimeProvider().EpochStart(currentEpoch + 1)) +} + +func (d *DockerTestFramework) AwaitAddressUnspentOutputAccepted(ctx context.Context, wallet *mock.Wallet, addr iotago.Address) (outputID iotago.OutputID, output iotago.Output, err error) { + indexerClt, err := wallet.Client.Indexer(ctx) + require.NoError(d.Testing, err) + addrBech := addr.Bech32(d.defaultWallet.Client.CommittedAPI().ProtocolParameters().Bech32HRP()) + + for t := time.Now(); time.Since(t) < d.optsWaitFor; time.Sleep(d.optsTick) { + res, err := indexerClt.Outputs(ctx, &api.BasicOutputsQuery{ + AddressBech32: addrBech, + }) + if err != nil { + return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "indexer request failed in request faucet funds") + } + + for res.Next() { + unspents, err := res.Outputs(ctx) + if err != nil { + return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "failed to get faucet unspent outputs") + } + + if len(unspents) == 0 { + break + } + + return lo.Return1(res.Response.Items.OutputIDs())[0], unspents[0], nil + } + } + + return iotago.EmptyOutputID, nil, ierrors.Errorf("no unspent outputs found for address %s due to timeout", addrBech) +} diff --git a/tools/docker-network/tests/dockertestframework/blocks.go b/tools/docker-network/tests/dockertestframework/blocks.go new file mode 100644 index 000000000..c034c0a8f --- /dev/null +++ b/tools/docker-network/tests/dockertestframework/blocks.go @@ -0,0 +1,94 @@ +//go:build dockertests + +package dockertestframework + +import ( + "context" + + "github.com/stretchr/testify/require" + + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/hive.go/runtime/options" + "github.com/iotaledger/iota-core/pkg/testsuite/mock" + iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/builder" +) + +// CreateTaggedDataBlock creates and submits a block of a tagged data payload. +func (d *DockerTestFramework) CreateTaggedDataBlock(wallet *mock.Wallet, tag []byte) *iotago.Block { + ctx := context.TODO() + + return lo.PanicOnErr(wallet.CreateBasicBlock(ctx, "", mock.WithPayload(&iotago.TaggedData{ + Tag: tag, + }))).ProtocolBlock() +} + +func (d *DockerTestFramework) CreateBasicOutputBlock(wallet *mock.Wallet) (*iotago.Block, *iotago.SignedTransaction, *mock.OutputData) { + fundsOutputData := d.RequestFaucetFunds(context.Background(), wallet, iotago.AddressEd25519) + + signedTx := wallet.CreateBasicOutputFromInput(fundsOutputData) + block, err := wallet.CreateBasicBlock(context.Background(), "", mock.WithPayload(signedTx)) + require.NoError(d.Testing, err) + + return block.ProtocolBlock(), signedTx, fundsOutputData +} + +// CreateDelegationBlockFromInput consumes the given basic output, then build a block of a transaction that includes a delegation output, in order to delegate the given validator. +func (d *DockerTestFramework) CreateDelegationBlockFromInput(wallet *mock.Wallet, accountAdddress *iotago.AccountAddress, input *mock.OutputData) (iotago.DelegationID, iotago.OutputID, *iotago.Block) { + ctx := context.TODO() + clt := wallet.Client + + signedTx := wallet.CreateDelegationFromInput( + "", + input, + mock.WithDelegatedValidatorAddress(accountAdddress), + mock.WithDelegationStartEpoch(GetDelegationStartEpoch(clt.LatestAPI(), wallet.GetNewBlockIssuanceResponse().LatestCommitment.Slot)), + ) + outputID := iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) + + return iotago.DelegationIDFromOutputID(outputID), + outputID, + lo.PanicOnErr(wallet.CreateBasicBlock(ctx, "", mock.WithPayload(signedTx))).ProtocolBlock() +} + +// CreateFoundryBlockFromInput consumes the given basic output, then build a block of a transaction that includes a foundry output with the given mintedAmount and maxSupply. +func (d *DockerTestFramework) CreateFoundryBlockFromInput(wallet *mock.Wallet, inputID iotago.OutputID, mintedAmount iotago.BaseToken, maxSupply iotago.BaseToken) (iotago.FoundryID, iotago.OutputID, *iotago.Block) { + input := wallet.Output(inputID) + signedTx := wallet.CreateFoundryAndNativeTokensFromInput(input, mintedAmount, maxSupply) + txID, err := signedTx.Transaction.ID() + require.NoError(d.Testing, err) + + //nolint:forcetypeassert + return signedTx.Transaction.Outputs[1].(*iotago.FoundryOutput).MustFoundryID(), + iotago.OutputIDFromTransactionIDAndIndex(txID, 1), + lo.PanicOnErr(wallet.CreateBasicBlock(context.Background(), "", mock.WithPayload(signedTx))).ProtocolBlock() +} + +// CreateFoundryTransitionBlockFromInput consumes the given foundry output, then build block by increasing the minted amount by 1. +func (d *DockerTestFramework) CreateFoundryTransitionBlockFromInput(issuerID iotago.AccountID, foundryInput, accountInput *mock.OutputData) (iotago.FoundryID, iotago.OutputID, *iotago.Block) { + signedTx := d.defaultWallet.TransitionFoundry("", foundryInput, accountInput) + txID, err := signedTx.Transaction.ID() + require.NoError(d.Testing, err) + + //nolint:forcetypeassert + return signedTx.Transaction.Outputs[1].(*iotago.FoundryOutput).MustFoundryID(), + iotago.OutputIDFromTransactionIDAndIndex(txID, 1), + lo.PanicOnErr(d.defaultWallet.CreateAndSubmitBasicBlock(context.Background(), "foundry_transition", mock.WithPayload(signedTx))).ProtocolBlock() +} + +// CreateNFTBlockFromInput consumes the given basic output, then build a block of a transaction that includes a NFT output with the given NFT output options. +func (d *DockerTestFramework) CreateNFTBlockFromInput(wallet *mock.Wallet, input *mock.OutputData, opts ...options.Option[builder.NFTOutputBuilder]) (iotago.NFTID, iotago.OutputID, *iotago.Block) { + signedTx := wallet.CreateTaggedNFTFromInput("", input, opts...) + outputID := iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) + + return iotago.NFTIDFromOutputID(outputID), + outputID, + lo.PanicOnErr(wallet.CreateBasicBlock(context.Background(), "", mock.WithPayload(signedTx))).ProtocolBlock() +} + +func (d *DockerTestFramework) SubmitBlock(ctx context.Context, blk *iotago.Block) { + clt := d.defaultWallet.Client + + _, err := clt.SubmitBlock(ctx, blk) + require.NoError(d.Testing, err) +} diff --git a/tools/docker-network/tests/dockertestframework/clock.go b/tools/docker-network/tests/dockertestframework/clock.go new file mode 100644 index 000000000..730ac79fc --- /dev/null +++ b/tools/docker-network/tests/dockertestframework/clock.go @@ -0,0 +1,20 @@ +//go:build dockertests + +package dockertestframework + +import ( + "github.com/iotaledger/iota-core/pkg/testsuite/mock" + iotago "github.com/iotaledger/iota.go/v4" +) + +type DockerWalletClock struct { + client mock.Client +} + +func (c *DockerWalletClock) SetCurrentSlot(slot iotago.SlotIndex) { + panic("Cannot set current slot in DockerWalletClock, the slot is set by time.Now()") +} + +func (c *DockerWalletClock) CurrentSlot() iotago.SlotIndex { + return c.client.LatestAPI().TimeProvider().CurrentSlot() +} diff --git a/tools/docker-network/tests/dockertestframework/faucet.go b/tools/docker-network/tests/dockertestframework/faucet.go new file mode 100644 index 000000000..6ec732ce0 --- /dev/null +++ b/tools/docker-network/tests/dockertestframework/faucet.go @@ -0,0 +1,133 @@ +//go:build dockertests + +package dockertestframework + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/stretchr/testify/require" + + "github.com/iotaledger/hive.go/ierrors" + "github.com/iotaledger/iota-core/pkg/testsuite/mock" + iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/api" +) + +func (d *DockerTestFramework) WaitUntilFaucetHealthy() { + fmt.Println("Wait until the faucet is healthy...") + defer fmt.Println("Wait until the faucet is healthy......done") + + d.Eventually(func() error { + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, d.optsFaucetURL+"/health", nil) + if err != nil { + return err + } + + res, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return ierrors.Errorf("faucet is not healthy, status code: %d", res.StatusCode) + } + + return nil + }, true) +} + +func (d *DockerTestFramework) SendFaucetRequest(ctx context.Context, wallet *mock.Wallet, receiveAddr iotago.Address) { + cltAPI := wallet.Client.CommittedAPI() + addrBech := receiveAddr.Bech32(cltAPI.ProtocolParameters().Bech32HRP()) + + type EnqueueRequest struct { + Address string `json:"address"` + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, d.optsFaucetURL+"/api/enqueue", func() io.Reader { + jsonData, _ := json.Marshal(&EnqueueRequest{ + Address: addrBech, + }) + + return bytes.NewReader(jsonData) + }()) + require.NoError(d.Testing, err) + + req.Header.Set("Content-Type", api.MIMEApplicationJSON) + + res, err := http.DefaultClient.Do(req) + require.NoError(d.Testing, err) + defer res.Body.Close() + + require.Equal(d.Testing, http.StatusAccepted, res.StatusCode) +} + +// RequestFaucetFunds requests faucet funds for the given address type, and returns the outputID of the received funds. +func (d *DockerTestFramework) RequestFaucetFunds(ctx context.Context, wallet *mock.Wallet, addressType iotago.AddressType) *mock.OutputData { + var address iotago.Address + if addressType == iotago.AddressImplicitAccountCreation { + address = wallet.ImplicitAccountCreationAddress(wallet.BlockIssuer.AccountData.AddressIndex) + } else { + address = wallet.Address() + } + + d.SendFaucetRequest(ctx, wallet, address) + + outputID, output, err := d.AwaitAddressUnspentOutputAccepted(ctx, wallet, address) + require.NoError(d.Testing, err) + + outputData := &mock.OutputData{ + ID: outputID, + Address: address, + AddressIndex: wallet.BlockIssuer.AccountData.AddressIndex, + Output: output, + } + wallet.AddOutput("faucet funds", outputData) + + fmt.Printf("Faucet funds received, txID: %s, amount: %d, mana: %d\n", outputID.TransactionID().ToHex(), output.BaseTokenAmount(), output.StoredMana()) + + return outputData +} + +// RequestFaucetFundsAndAllotManaTo requests faucet funds then uses it to allots mana from one account to another. +func (d *DockerTestFramework) RequestFaucetFundsAndAllotManaTo(fromWallet *mock.Wallet, to *mock.AccountData, manaToAllot iotago.Mana) { + // requesting faucet funds for allotment + ctx := context.TODO() + fundsOutputID := d.RequestFaucetFunds(ctx, fromWallet, iotago.AddressEd25519) + clt := fromWallet.Client + + signedTx := fromWallet.AllotManaFromBasicOutput( + "allotment_tx", + fundsOutputID, + manaToAllot, + to.ID, + ) + preAllotmentCommitmentID := fromWallet.GetNewBlockIssuanceResponse().LatestCommitment.MustID() + block, err := fromWallet.CreateAndSubmitBasicBlock(ctx, "allotment", mock.WithPayload(signedTx)) + require.NoError(d.Testing, err) + fmt.Println("Allot mana transaction sent, blkID:", block.ID().ToHex(), ", txID:", signedTx.Transaction.MustID().ToHex(), ", slot:", block.ID().Slot()) + + d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) + + // allotment is updated when the transaction is committed + d.AwaitCommitment(block.ID().Slot()) + + // check if the mana is allotted + toCongestionResp, err := clt.Congestion(ctx, to.Address, 0, preAllotmentCommitmentID) + require.NoError(d.Testing, err) + oldBIC := toCongestionResp.BlockIssuanceCredits + + toCongestionResp, err = clt.Congestion(ctx, to.Address, 0) + require.NoError(d.Testing, err) + newBIC := toCongestionResp.BlockIssuanceCredits + + decayedOldBIC, err := clt.LatestAPI().ManaDecayProvider().DecayManaBySlots(iotago.Mana(oldBIC), preAllotmentCommitmentID.Slot(), block.ID().Slot()) + expectedBIC := iotago.BlockIssuanceCredits(decayedOldBIC + manaToAllot) + require.Equal(d.Testing, expectedBIC, newBIC) +} diff --git a/tools/docker-network/tests/dockertestframework/framework.go b/tools/docker-network/tests/dockertestframework/framework.go index 92c67b9f8..a6b203b77 100644 --- a/tools/docker-network/tests/dockertestframework/framework.go +++ b/tools/docker-network/tests/dockertestframework/framework.go @@ -3,11 +3,8 @@ package dockertestframework import ( - "context" "fmt" "log" - "math/rand" - "net/http" "os" "os/exec" "strings" @@ -18,23 +15,20 @@ import ( "github.com/stretchr/testify/require" "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/runtime/options" "github.com/iotaledger/hive.go/runtime/syncutils" "github.com/iotaledger/iota-core/pkg/protocol" "github.com/iotaledger/iota-core/pkg/testsuite/mock" "github.com/iotaledger/iota-core/pkg/testsuite/snapshotcreator" iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/api" - "github.com/iotaledger/iota.go/v4/builder" - "github.com/iotaledger/iota.go/v4/nodeclient" "github.com/iotaledger/iota.go/v4/wallet" ) var ( // need to build snapshotfile in tools/docker-network. snapshotFilePath = "../docker-network-snapshots/snapshot.bin" - keyManager = func() *wallet.KeyManager { + + keyManager = func() *wallet.KeyManager { genesisSeed, err := base58.Decode("7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih") if err != nil { log.Fatal(ierrors.Wrap(err, "failed to decode base58 seed")) @@ -48,39 +42,6 @@ var ( } ) -type Node struct { - Name string - ContainerName string - ClientURL string - AccountAddressBech32 string - ContainerConfigs string - PrivateKey string - IssueCandidacyPayload bool - DatabasePath string - SnapshotPath string -} - -func (n *Node) AccountAddress(t *testing.T) *iotago.AccountAddress { - _, addr, err := iotago.ParseBech32(n.AccountAddressBech32) - require.NoError(t, err) - accAddress, ok := addr.(*iotago.AccountAddress) - require.True(t, ok) - - return accAddress -} - -type DockerWalletClock struct { - client mock.Client -} - -func (c *DockerWalletClock) SetCurrentSlot(slot iotago.SlotIndex) { - panic("Cannot set current slot in DockerWalletClock, the slot is set by time.Now()") -} - -func (c *DockerWalletClock) CurrentSlot() iotago.SlotIndex { - return c.client.LatestAPI().TimeProvider().CurrentSlot() -} - type DockerTestFramework struct { Testing *testing.T // we use the fake testing so that actual tests don't fail if an assertion fails @@ -218,592 +179,6 @@ loop: return nil } -func (d *DockerTestFramework) DefaultWallet() *mock.Wallet { - return d.defaultWallet -} - -func (d *DockerTestFramework) waitForNodesAndGetClients() error { - nodes := d.Nodes() - - d.nodesLock.Lock() - defer d.nodesLock.Unlock() - for _, node := range nodes { - client, err := nodeclient.New(node.ClientURL) - if err != nil { - return ierrors.Wrapf(err, "failed to create node client for node %s", node.Name) - } - d.nodes[node.Name] = node - d.clients[node.Name] = client - } - d.defaultWallet = mock.NewWallet( - d.Testing, - "default", - d.clients["V1"], - &DockerWalletClock{client: d.clients["V1"]}, - lo.PanicOnErr(wallet.NewKeyManagerFromRandom(wallet.DefaultIOTAPath)), - ) - - return nil -} - -func (d *DockerTestFramework) WaitUntilNetworkReady() { - d.WaitUntilNetworkHealthy() - - // inx-faucet is up only when the node and indexer are healthy, thus need to check the faucet even after nodes are synced. - d.WaitUntilFaucetHealthy() - - d.DumpContainerLogsToFiles() -} - -func (d *DockerTestFramework) WaitUntilFaucetHealthy() { - fmt.Println("Wait until the faucet is healthy...") - defer fmt.Println("Wait until the faucet is healthy......done") - - d.Eventually(func() error { - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, d.optsFaucetURL+"/health", nil) - if err != nil { - return err - } - - res, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - return ierrors.Errorf("faucet is not healthy, status code: %d", res.StatusCode) - } - - return nil - }, true) -} - -func (d *DockerTestFramework) WaitUntilNetworkHealthy() { - fmt.Println("Wait until the network is healthy...") - defer fmt.Println("Wait until the network is healthy......done") - - d.Eventually(func() error { - for _, node := range d.Nodes() { - for { - info, err := d.Client(node.Name).Info(context.TODO()) - if err != nil { - return err - } - - if info.Status.IsNetworkHealthy { - fmt.Println("Node", node.Name, "is synced") - break - } - } - } - - return nil - }, true) -} - -func (d *DockerTestFramework) AddValidatorNode(name string, containerName string, clientURL string, accAddrBech32 string, optIssueCandidacyPayload ...bool) { - d.nodesLock.Lock() - defer d.nodesLock.Unlock() - - issueCandidacyPayload := true - if len(optIssueCandidacyPayload) > 0 { - issueCandidacyPayload = optIssueCandidacyPayload[0] - } - - d.nodes[name] = &Node{ - Name: name, - ContainerName: containerName, - ClientURL: clientURL, - AccountAddressBech32: accAddrBech32, - IssueCandidacyPayload: issueCandidacyPayload, - } -} - -func (d *DockerTestFramework) AddNode(name string, containerName string, clientURL string) { - d.nodesLock.Lock() - defer d.nodesLock.Unlock() - - d.nodes[name] = &Node{ - Name: name, - ContainerName: containerName, - ClientURL: clientURL, - } -} - -func (d *DockerTestFramework) Nodes(names ...string) []*Node { - d.nodesLock.RLock() - defer d.nodesLock.RUnlock() - - if len(names) == 0 { - nodes := make([]*Node, 0, len(d.nodes)) - for _, node := range d.nodes { - nodes = append(nodes, node) - } - - return nodes - } - - nodes := make([]*Node, len(names)) - for i, name := range names { - nodes[i] = d.Node(name) - } - - return nodes -} - -func (d *DockerTestFramework) Node(name string) *Node { - d.nodesLock.RLock() - defer d.nodesLock.RUnlock() - - node, exist := d.nodes[name] - require.True(d.Testing, exist) - - return node -} - -func (d *DockerTestFramework) ModifyNode(name string, fun func(*Node)) { - d.nodesLock.Lock() - defer d.nodesLock.Unlock() - - node, exist := d.nodes[name] - require.True(d.Testing, exist) - - fun(node) -} - -// Restarts a node with another database path, conceptually deleting the database and -// restarts it with the given snapshot path. -func (d *DockerTestFramework) ResetNode(alias string, newSnapshotPath string) { - fmt.Println("Reset node", alias) - - d.ModifyNode(alias, func(n *Node) { - n.DatabasePath = fmt.Sprintf("/app/database/database%d", rand.Int()) - n.SnapshotPath = newSnapshotPath - }) - d.DockerComposeUp(true) - d.DumpContainerLog(d.Node(alias).ContainerName, "reset1") - d.WaitUntilNetworkHealthy() -} - -func (d *DockerTestFramework) Clients(names ...string) map[string]mock.Client { - d.nodesLock.RLock() - defer d.nodesLock.RUnlock() - - if len(names) == 0 { - return d.clients - } - - clients := make(map[string]mock.Client, len(names)) - for _, name := range names { - client, exist := d.clients[name] - require.True(d.Testing, exist) - - clients[name] = client - } - - return clients -} - -func (d *DockerTestFramework) Client(name string) mock.Client { - d.nodesLock.RLock() - defer d.nodesLock.RUnlock() - - client, exist := d.clients[name] - require.True(d.Testing, exist) - - return client -} - -func (d *DockerTestFramework) NodeStatus(name string) *api.InfoResNodeStatus { - node := d.Node(name) - - info, err := d.Client(node.Name).Info(context.TODO()) - require.NoError(d.Testing, err) - - return info.Status -} - -func (d *DockerTestFramework) AccountsFromNodes(nodes ...*Node) []string { - var accounts []string - for _, node := range nodes { - if node.AccountAddressBech32 != "" { - accounts = append(accounts, node.AccountAddressBech32) - } - } - - return accounts -} - -func (d *DockerTestFramework) StartIssueCandidacyPayload(nodes ...*Node) { - if len(nodes) == 0 { - return - } - - for _, node := range nodes { - node.IssueCandidacyPayload = true - } - - err := d.DockerComposeUp(true) - require.NoError(d.Testing, err) -} - -func (d *DockerTestFramework) StopIssueCandidacyPayload(nodes ...*Node) { - if len(nodes) == 0 { - return - } - - for _, node := range nodes { - node.IssueCandidacyPayload = false - } - - err := d.DockerComposeUp(true) - require.NoError(d.Testing, err) -} - -func (d *DockerTestFramework) IssueCandidacyPayloadFromAccount(wallet *mock.Wallet) iotago.BlockID { - block, err := wallet.CreateAndSubmitBasicBlock(context.TODO(), "candidacy_payload", mock.WithPayload(&iotago.CandidacyAnnouncement{})) - require.NoError(d.Testing, err) - - return block.ID() -} - -// CreateTaggedDataBlock creates and submits a block of a tagged data payload. -func (d *DockerTestFramework) CreateTaggedDataBlock(wallet *mock.Wallet, tag []byte) *iotago.Block { - ctx := context.TODO() - - return lo.PanicOnErr(wallet.CreateBasicBlock(ctx, "", mock.WithPayload(&iotago.TaggedData{ - Tag: tag, - }))).ProtocolBlock() -} - -func (d *DockerTestFramework) CreateBasicOutputBlock(wallet *mock.Wallet) (*iotago.Block, *iotago.SignedTransaction, *mock.OutputData) { - fundsOutputData := d.RequestFaucetFunds(context.Background(), wallet, iotago.AddressEd25519) - - signedTx := wallet.CreateBasicOutputFromInput(fundsOutputData) - block, err := wallet.CreateBasicBlock(context.Background(), "", mock.WithPayload(signedTx)) - require.NoError(d.Testing, err) - - return block.ProtocolBlock(), signedTx, fundsOutputData -} - -// CreateDelegationBlockFromInput consumes the given basic output, then build a block of a transaction that includes a delegation output, in order to delegate the given validator. -func (d *DockerTestFramework) CreateDelegationBlockFromInput(wallet *mock.Wallet, accountAdddress *iotago.AccountAddress, input *mock.OutputData) (iotago.DelegationID, iotago.OutputID, *iotago.Block) { - ctx := context.TODO() - clt := wallet.Client - - signedTx := wallet.CreateDelegationFromInput( - "", - input, - mock.WithDelegatedValidatorAddress(accountAdddress), - mock.WithDelegationStartEpoch(GetDelegationStartEpoch(clt.LatestAPI(), wallet.GetNewBlockIssuanceResponse().LatestCommitment.Slot)), - ) - outputID := iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) - - return iotago.DelegationIDFromOutputID(outputID), - outputID, - lo.PanicOnErr(wallet.CreateBasicBlock(ctx, "", mock.WithPayload(signedTx))).ProtocolBlock() -} - -// CreateFoundryBlockFromInput consumes the given basic output, then build a block of a transaction that includes a foundry output with the given mintedAmount and maxSupply. -func (d *DockerTestFramework) CreateFoundryBlockFromInput(wallet *mock.Wallet, inputID iotago.OutputID, mintedAmount iotago.BaseToken, maxSupply iotago.BaseToken) (iotago.FoundryID, iotago.OutputID, *iotago.Block) { - input := wallet.Output(inputID) - signedTx := wallet.CreateFoundryAndNativeTokensFromInput(input, mintedAmount, maxSupply) - txID, err := signedTx.Transaction.ID() - require.NoError(d.Testing, err) - - //nolint:forcetypeassert - return signedTx.Transaction.Outputs[1].(*iotago.FoundryOutput).MustFoundryID(), - iotago.OutputIDFromTransactionIDAndIndex(txID, 1), - lo.PanicOnErr(wallet.CreateBasicBlock(context.Background(), "", mock.WithPayload(signedTx))).ProtocolBlock() -} - -// CreateNFTBlockFromInput consumes the given basic output, then build a block of a transaction that includes a NFT output with the given NFT output options. -func (d *DockerTestFramework) CreateNFTBlockFromInput(wallet *mock.Wallet, input *mock.OutputData, opts ...options.Option[builder.NFTOutputBuilder]) (iotago.NFTID, iotago.OutputID, *iotago.Block) { - signedTx := wallet.CreateTaggedNFTFromInput("", input, opts...) - outputID := iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) - - return iotago.NFTIDFromOutputID(outputID), - outputID, - lo.PanicOnErr(wallet.CreateBasicBlock(context.Background(), "", mock.WithPayload(signedTx))).ProtocolBlock() -} - -// CreateFoundryTransitionBlockFromInput consumes the given foundry output, then build block by increasing the minted amount by 1. -func (d *DockerTestFramework) CreateFoundryTransitionBlockFromInput(issuerID iotago.AccountID, foundryInput, accountInput *mock.OutputData) (iotago.FoundryID, iotago.OutputID, *iotago.Block) { - signedTx := d.defaultWallet.TransitionFoundry("", foundryInput, accountInput) - txID, err := signedTx.Transaction.ID() - require.NoError(d.Testing, err) - - //nolint:forcetypeassert - return signedTx.Transaction.Outputs[1].(*iotago.FoundryOutput).MustFoundryID(), - iotago.OutputIDFromTransactionIDAndIndex(txID, 1), - lo.PanicOnErr(d.defaultWallet.CreateAndSubmitBasicBlock(context.Background(), "foundry_transition", mock.WithPayload(signedTx))).ProtocolBlock() -} - -// CreateAccountBlockFromImplicit consumes the given implicit account, then build the account transition block with the given account output options. -func (d *DockerTestFramework) CreateAccountBlockFromImplicit(accountWallet *mock.Wallet, implicitAccountOutputData *mock.OutputData, blockIssuance *api.IssuanceBlockHeaderResponse, opts ...options.Option[builder.AccountOutputBuilder]) (*mock.AccountData, *iotago.SignedTransaction, *iotago.Block) { - // create an implicit account by requesting faucet funds - ctx := context.TODO() - - var implicitBlockIssuerKey iotago.BlockIssuerKey = iotago.Ed25519PublicKeyHashBlockIssuerKeyFromImplicitAccountCreationAddress(accountWallet.ImplicitAccountCreationAddress()) - opts = append(opts, mock.WithBlockIssuerFeature( - iotago.NewBlockIssuerKeys(implicitBlockIssuerKey), - iotago.MaxSlotIndex, - )) - - signedTx := accountWallet.TransitionImplicitAccountToAccountOutputWithBlockIssuance("", []*mock.OutputData{implicitAccountOutputData}, blockIssuance, opts...) - - // The account transition block should be issued by the implicit account block issuer key. - block, err := accountWallet.CreateBasicBlock(ctx, "", mock.WithPayload(signedTx)) - require.NoError(d.Testing, err) - accOutputID := iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) - accOutput := signedTx.Transaction.Outputs[0].(*iotago.AccountOutput) - accAddress := (accOutput.AccountID).ToAddress().(*iotago.AccountAddress) - - accountOutputData := &mock.AccountData{ - ID: accOutput.AccountID, - Address: accAddress, - Output: accOutput, - OutputID: accOutputID, - AddressIndex: implicitAccountOutputData.AddressIndex, - } - - return accountOutputData, signedTx, block.ProtocolBlock() -} - -// CreateImplicitAccount requests faucet funds and creates an implicit account. It already wait until the transaction is committed and the created account is useable. -func (d *DockerTestFramework) CreateImplicitAccount(ctx context.Context) (*mock.Wallet, *mock.OutputData) { - newWallet := mock.NewWallet(d.Testing, "", d.defaultWallet.Client, &DockerWalletClock{client: d.defaultWallet.Client}) - implicitAccountOutputData := d.RequestFaucetFunds(ctx, newWallet, iotago.AddressImplicitAccountCreation) - - accountID := iotago.AccountIDFromOutputID(implicitAccountOutputData.ID) - accountAddress, ok := accountID.ToAddress().(*iotago.AccountAddress) - require.True(d.Testing, ok) - - // make sure an implicit account is committed - d.CheckAccountStatus(ctx, iotago.EmptyBlockID, implicitAccountOutputData.ID.TransactionID(), implicitAccountOutputData.ID, accountAddress) - - // update the wallet with the new account data - newWallet.SetBlockIssuer(&mock.AccountData{ - ID: accountID, - Address: accountAddress, - OutputID: implicitAccountOutputData.ID, - AddressIndex: implicitAccountOutputData.AddressIndex, - }) - - return newWallet, implicitAccountOutputData -} - -// CreateAccount creates an new account from implicit one to full one, it already wait until the transaction is committed and the created account is useable. -func (d *DockerTestFramework) CreateAccount() (*mock.Wallet, *mock.AccountData) { - ctx := context.TODO() - - newWallet, implicitAccountOutputData := d.CreateImplicitAccount(ctx) - - accountData, signedTx, block := d.CreateAccountBlockFromImplicit(newWallet, implicitAccountOutputData, d.defaultWallet.GetNewBlockIssuanceResponse()) - - d.SubmitBlock(ctx, block) - d.CheckAccountStatus(ctx, block.MustID(), signedTx.Transaction.MustID(), accountData.OutputID, accountData.Address, true) - - // update the wallet with the new account data - newWallet.SetBlockIssuer(accountData) - - fmt.Printf("Account created, Bech addr: %s\n", accountData.Address.Bech32(newWallet.Client.CommittedAPI().ProtocolParameters().Bech32HRP())) - - return newWallet, newWallet.Account(accountData.ID) -} - -// CreateAccountFromImplicitAccount transitions an account from the given implicit one to full one, it already wait until the transaction is committed and the created account is useable. -func (d *DockerTestFramework) CreateAccountFromImplicitAccount(accountWallet *mock.Wallet, implicitAccountOutputData *mock.OutputData, blockIssuance *api.IssuanceBlockHeaderResponse, opts ...options.Option[builder.AccountOutputBuilder]) *mock.AccountData { - ctx := context.TODO() - accountData, signedTx, block := d.CreateAccountBlockFromImplicit(accountWallet, implicitAccountOutputData, blockIssuance, opts...) - d.SubmitBlock(ctx, block) - d.CheckAccountStatus(ctx, block.MustID(), signedTx.Transaction.MustID(), accountData.OutputID, accountData.Address, true) - - // update the wallet with the new account data - accountWallet.SetBlockIssuer(accountData) - - fmt.Printf("Account created, Bech addr: %s\n", accountData.Address.Bech32(accountWallet.Client.CommittedAPI().ProtocolParameters().Bech32HRP())) - - return accountWallet.Account(accountData.ID) -} - -func (d *DockerTestFramework) ClaimRewardsForValidator(ctx context.Context, validatorWallet *mock.Wallet) { - validatorAccountData := validatorWallet.BlockIssuer.AccountData - outputData := &mock.OutputData{ - ID: validatorAccountData.OutputID, - Address: validatorAccountData.Address, - AddressIndex: validatorAccountData.AddressIndex, - Output: validatorAccountData.Output, - } - signedTx := validatorWallet.ClaimValidatorRewards("", outputData) - - validatorWallet.CreateAndSubmitBasicBlock(ctx, "claim_rewards_validator", mock.WithPayload(signedTx)) - d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) - - // update account data of validator - validatorWallet.SetBlockIssuer(&mock.AccountData{ - ID: validatorWallet.BlockIssuer.AccountData.ID, - Address: validatorWallet.BlockIssuer.AccountData.Address, - AddressIndex: validatorWallet.BlockIssuer.AccountData.AddressIndex, - OutputID: iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), - Output: signedTx.Transaction.Outputs[0].(*iotago.AccountOutput), - }) -} - -func (d *DockerTestFramework) ClaimRewardsForDelegator(ctx context.Context, wallet *mock.Wallet, delegationOutputData *mock.OutputData) iotago.OutputID { - signedTx := wallet.ClaimDelegatorRewards("", delegationOutputData) - - wallet.CreateAndSubmitBasicBlock(ctx, "claim_rewards_delegator", mock.WithPayload(signedTx)) - d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) - - return iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) -} - -func (d *DockerTestFramework) DelayedClaimingTransition(ctx context.Context, wallet *mock.Wallet, delegationOutputData *mock.OutputData) *mock.OutputData { - signedTx := wallet.DelayedClaimingTransition("delayed_claim_tx", delegationOutputData) - - wallet.CreateAndSubmitBasicBlock(ctx, "delayed_claim", mock.WithPayload(signedTx)) - d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) - - return &mock.OutputData{ - ID: iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), - Address: wallet.Address(), - AddressIndex: 0, - Output: signedTx.Transaction.Outputs[0].(*iotago.DelegationOutput), - } -} - -// DelegateToValidator requests faucet funds and delegate the UTXO output to the validator. -func (d *DockerTestFramework) DelegateToValidator(fromWallet *mock.Wallet, accountAddress *iotago.AccountAddress) *mock.OutputData { - // requesting faucet funds as delegation input - ctx := context.TODO() - fundsOutputData := d.RequestFaucetFunds(ctx, fromWallet, iotago.AddressEd25519) - - signedTx := fromWallet.CreateDelegationFromInput( - "delegation_tx", - fundsOutputData, - mock.WithDelegatedValidatorAddress(accountAddress), - mock.WithDelegationStartEpoch(GetDelegationStartEpoch(fromWallet.Client.LatestAPI(), fromWallet.GetNewBlockIssuanceResponse().LatestCommitment.Slot)), - ) - - fromWallet.CreateAndSubmitBasicBlock(ctx, "delegation", mock.WithPayload(signedTx)) - d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) - - delegationOutput, ok := signedTx.Transaction.Outputs[0].(*iotago.DelegationOutput) - require.True(d.Testing, ok) - - return &mock.OutputData{ - ID: iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), - Address: fromWallet.Address(), - AddressIndex: 0, - Output: delegationOutput, - } - -} - -// PrepareBlockIssuance prepares the BlockIssuance and Congestion response, and increase BIC of the issuer if necessary. -func (d *DockerTestFramework) PrepareBlockIssuance(ctx context.Context, clt mock.Client, issuerAddress *iotago.AccountAddress) (*api.IssuanceBlockHeaderResponse, *api.CongestionResponse) { - issuerResp, err := clt.BlockIssuance(ctx) - require.NoError(d.Testing, err) - - congestionResp, err := clt.Congestion(ctx, issuerAddress, 0, lo.PanicOnErr(issuerResp.LatestCommitment.ID())) - require.NoError(d.Testing, err) - - return issuerResp, congestionResp -} - -// AllotManaTo requests faucet funds then uses it to allots mana from one account to another. -func (d *DockerTestFramework) AllotManaTo(fromWallet *mock.Wallet, to *mock.AccountData, manaToAllot iotago.Mana) { - // requesting faucet funds for allotment - ctx := context.TODO() - fundsOutputID := d.RequestFaucetFunds(ctx, fromWallet, iotago.AddressEd25519) - clt := fromWallet.Client - - signedTx := fromWallet.AllotManaFromBasicOutput( - "allotment_tx", - fundsOutputID, - manaToAllot, - to.ID, - ) - preAllotmentCommitmentID := fromWallet.GetNewBlockIssuanceResponse().LatestCommitment.MustID() - block, err := fromWallet.CreateAndSubmitBasicBlock(ctx, "allotment", mock.WithPayload(signedTx)) - require.NoError(d.Testing, err) - fmt.Println("Allot mana transaction sent, blkID:", block.ID().ToHex(), ", txID:", signedTx.Transaction.MustID().ToHex(), ", slot:", block.ID().Slot()) - - d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) - - // allotment is updated when the transaction is committed - d.AwaitCommitment(block.ID().Slot()) - - // check if the mana is allotted - toCongestionResp, err := clt.Congestion(ctx, to.Address, 0, preAllotmentCommitmentID) - require.NoError(d.Testing, err) - oldBIC := toCongestionResp.BlockIssuanceCredits - - toCongestionResp, err = clt.Congestion(ctx, to.Address, 0) - require.NoError(d.Testing, err) - newBIC := toCongestionResp.BlockIssuanceCredits - - decayedOldBIC, err := clt.LatestAPI().ManaDecayProvider().DecayManaBySlots(iotago.Mana(oldBIC), preAllotmentCommitmentID.Slot(), block.ID().Slot()) - expectedBIC := iotago.BlockIssuanceCredits(decayedOldBIC + manaToAllot) - require.Equal(d.Testing, expectedBIC, newBIC) -} - -// CreateNativeToken request faucet funds then use it to create native token for the account, and returns the updated Account. -func (d *DockerTestFramework) CreateNativeToken(fromWallet *mock.Wallet, mintedAmount iotago.BaseToken, maxSupply iotago.BaseToken) { - require.GreaterOrEqual(d.Testing, maxSupply, mintedAmount) - - ctx := context.TODO() - - // requesting faucet funds for native token creation - fundsOutputData := d.RequestFaucetFunds(ctx, fromWallet, iotago.AddressEd25519) - - signedTx := fromWallet.CreateFoundryAndNativeTokensFromInput(fundsOutputData, mintedAmount, maxSupply) - - block, err := fromWallet.CreateAndSubmitBasicBlock(ctx, "native_token", mock.WithPayload(signedTx)) - require.NoError(d.Testing, err) - - txID := signedTx.Transaction.MustID() - d.AwaitTransactionPayloadAccepted(ctx, txID) - - fmt.Println("Create native tokens transaction sent, blkID:", block.ID().ToHex(), ", txID:", signedTx.Transaction.MustID().ToHex(), ", slot:", block.ID().Slot()) - - // wait for the account to be committed - d.AwaitCommitment(block.ID().Slot()) - - d.AssertIndexerAccount(fromWallet.BlockIssuer.AccountData) - //nolint:forcetypeassert - d.AssertIndexerFoundry(signedTx.Transaction.Outputs[1].(*iotago.FoundryOutput).MustFoundryID()) -} - -// RequestFaucetFunds requests faucet funds for the given address type, and returns the outputID of the received funds. -func (d *DockerTestFramework) RequestFaucetFunds(ctx context.Context, wallet *mock.Wallet, addressType iotago.AddressType) *mock.OutputData { - var address iotago.Address - if addressType == iotago.AddressImplicitAccountCreation { - address = wallet.ImplicitAccountCreationAddress(wallet.BlockIssuer.AccountData.AddressIndex) - } else { - address = wallet.Address() - } - - d.SendFaucetRequest(ctx, wallet, address) - - outputID, output, err := d.AwaitAddressUnspentOutputAccepted(ctx, wallet, address) - require.NoError(d.Testing, err) - - outputData := &mock.OutputData{ - ID: outputID, - Address: address, - AddressIndex: wallet.BlockIssuer.AccountData.AddressIndex, - Output: output, - } - wallet.AddOutput("faucet funds", outputData) - - fmt.Printf("Faucet funds received, txID: %s, amount: %d, mana: %d\n", outputID.TransactionID().ToHex(), output.BaseTokenAmount(), output.StoredMana()) - - return outputData -} - func (d *DockerTestFramework) Stop() { fmt.Println("Stop the network...") defer fmt.Println("Stop the network.....done") @@ -871,6 +246,7 @@ func (d *DockerTestFramework) GetContainersConfigs() { d.nodesLock.Lock() defer d.nodesLock.Unlock() + for _, node := range nodes { cmd := fmt.Sprintf("docker inspect --format='{{.Config.Cmd}}' %s", node.ContainerName) containerConfigsBytes, err := exec.Command("bash", "-c", cmd).Output() @@ -894,9 +270,35 @@ func (d *DockerTestFramework) GetContainersConfigs() { } } -func (d *DockerTestFramework) SubmitBlock(ctx context.Context, blk *iotago.Block) { - clt := d.defaultWallet.Client +func (d *DockerTestFramework) DefaultWallet() *mock.Wallet { + return d.defaultWallet +} - _, err := clt.SubmitBlock(ctx, blk) - require.NoError(d.Testing, err) +func (d *DockerTestFramework) Clients(names ...string) map[string]mock.Client { + d.nodesLock.RLock() + defer d.nodesLock.RUnlock() + + if len(names) == 0 { + return d.clients + } + + clients := make(map[string]mock.Client, len(names)) + for _, name := range names { + client, exist := d.clients[name] + require.True(d.Testing, exist) + + clients[name] = client + } + + return clients +} + +func (d *DockerTestFramework) Client(name string) mock.Client { + d.nodesLock.RLock() + defer d.nodesLock.RUnlock() + + client, exist := d.clients[name] + require.True(d.Testing, exist) + + return client } diff --git a/tools/docker-network/tests/dockertestframework/framework_eventapi.go b/tools/docker-network/tests/dockertestframework/framework_eventapi.go index 92526d577..2652f84c3 100644 --- a/tools/docker-network/tests/dockertestframework/framework_eventapi.go +++ b/tools/docker-network/tests/dockertestframework/framework_eventapi.go @@ -20,6 +20,18 @@ import ( "github.com/iotaledger/iota.go/v4/tpkg" ) +func WithEventAPIWaitFor(waitFor time.Duration) options.Option[EventAPIDockerTestFramework] { + return func(d *EventAPIDockerTestFramework) { + d.optsWaitFor = waitFor + } +} + +func WithEventAPITick(tick time.Duration) options.Option[EventAPIDockerTestFramework] { + return func(d *EventAPIDockerTestFramework) { + d.optsTick = tick + } +} + type EventAPIDockerTestFramework struct { Testing *testing.T @@ -618,15 +630,3 @@ func (e *EventAPIDockerTestFramework) AwaitEventAPITopics(t *testing.T, cancleFu } } } - -func WithEventAPIWaitFor(waitFor time.Duration) options.Option[EventAPIDockerTestFramework] { - return func(d *EventAPIDockerTestFramework) { - d.optsWaitFor = waitFor - } -} - -func WithEventAPITick(tick time.Duration) options.Option[EventAPIDockerTestFramework] { - return func(d *EventAPIDockerTestFramework) { - d.optsTick = tick - } -} diff --git a/tools/docker-network/tests/dockertestframework/nodes.go b/tools/docker-network/tests/dockertestframework/nodes.go new file mode 100644 index 000000000..1dd7038a4 --- /dev/null +++ b/tools/docker-network/tests/dockertestframework/nodes.go @@ -0,0 +1,198 @@ +//go:build dockertests + +package dockertestframework + +import ( + "context" + "fmt" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/iotaledger/hive.go/ierrors" + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/iota-core/pkg/testsuite/mock" + iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/api" + "github.com/iotaledger/iota.go/v4/nodeclient" + "github.com/iotaledger/iota.go/v4/wallet" +) + +type Node struct { + Name string + ContainerName string + ClientURL string + AccountAddressBech32 string + ContainerConfigs string + PrivateKey string + IssueCandidacyPayload bool + DatabasePath string + SnapshotPath string +} + +func (n *Node) AccountAddress(t *testing.T) *iotago.AccountAddress { + _, addr, err := iotago.ParseBech32(n.AccountAddressBech32) + require.NoError(t, err) + accAddress, ok := addr.(*iotago.AccountAddress) + require.True(t, ok) + + return accAddress +} + +func (d *DockerTestFramework) NodeStatus(name string) *api.InfoResNodeStatus { + node := d.Node(name) + + info, err := d.Client(node.Name).Info(context.TODO()) + require.NoError(d.Testing, err) + + return info.Status +} + +func (d *DockerTestFramework) waitForNodesAndGetClients() error { + nodes := d.Nodes() + + d.nodesLock.Lock() + defer d.nodesLock.Unlock() + + for _, node := range nodes { + client, err := nodeclient.New(node.ClientURL) + if err != nil { + return ierrors.Wrapf(err, "failed to create node client for node %s", node.Name) + } + d.nodes[node.Name] = node + d.clients[node.Name] = client + } + + d.defaultWallet = mock.NewWallet( + d.Testing, + "default", + d.clients["V1"], + &DockerWalletClock{client: d.clients["V1"]}, + lo.PanicOnErr(wallet.NewKeyManagerFromRandom(wallet.DefaultIOTAPath)), + ) + + return nil +} + +func (d *DockerTestFramework) WaitUntilNetworkReady() { + d.WaitUntilNetworkHealthy() + + // inx-faucet is up only when the node and indexer are healthy, thus need to check the faucet even after nodes are synced. + d.WaitUntilFaucetHealthy() + + d.DumpContainerLogsToFiles() +} + +func (d *DockerTestFramework) WaitUntilNetworkHealthy() { + fmt.Println("Wait until the network is healthy...") + defer fmt.Println("Wait until the network is healthy......done") + + d.Eventually(func() error { + for _, node := range d.Nodes() { + for { + info, err := d.Client(node.Name).Info(context.TODO()) + if err != nil { + return err + } + + if info.Status.IsNetworkHealthy { + fmt.Println("Node", node.Name, "is synced") + break + } + } + } + + return nil + }, true) +} + +func (d *DockerTestFramework) AddValidatorNode(name string, containerName string, clientURL string, accAddrBech32 string, optIssueCandidacyPayload ...bool) { + d.nodesLock.Lock() + defer d.nodesLock.Unlock() + + issueCandidacyPayload := true + if len(optIssueCandidacyPayload) > 0 { + issueCandidacyPayload = optIssueCandidacyPayload[0] + } + + d.nodes[name] = &Node{ + Name: name, + ContainerName: containerName, + ClientURL: clientURL, + AccountAddressBech32: accAddrBech32, + IssueCandidacyPayload: issueCandidacyPayload, + } +} + +func (d *DockerTestFramework) AddNode(name string, containerName string, clientURL string) { + d.nodesLock.Lock() + defer d.nodesLock.Unlock() + + d.nodes[name] = &Node{ + Name: name, + ContainerName: containerName, + ClientURL: clientURL, + } +} + +func (d *DockerTestFramework) Nodes(names ...string) []*Node { + d.nodesLock.RLock() + defer d.nodesLock.RUnlock() + + if len(names) == 0 { + nodes := make([]*Node, 0, len(d.nodes)) + for _, node := range d.nodes { + nodes = append(nodes, node) + } + + return nodes + } + + nodes := make([]*Node, len(names)) + for i, name := range names { + nodes[i] = d.Node(name) + } + + return nodes +} + +func (d *DockerTestFramework) Node(name string) *Node { + d.nodesLock.RLock() + defer d.nodesLock.RUnlock() + + node, exist := d.nodes[name] + require.True(d.Testing, exist) + + return node +} + +func (d *DockerTestFramework) ModifyNode(name string, fun func(*Node)) { + d.nodesLock.Lock() + defer d.nodesLock.Unlock() + + node, exist := d.nodes[name] + require.True(d.Testing, exist) + + fun(node) +} + +// Restarts a node with another database path, conceptually deleting the database and +// restarts it with the given snapshot path. +func (d *DockerTestFramework) ResetNode(alias string, newSnapshotPath string) { + fmt.Println("Reset node", alias) + + d.ModifyNode(alias, func(n *Node) { + n.DatabasePath = fmt.Sprintf("/app/database/database%d", rand.Int()) + n.SnapshotPath = newSnapshotPath + }) + d.DockerComposeUp(true) + d.DumpContainerLog(d.Node(alias).ContainerName, "reset1") + d.WaitUntilNetworkHealthy() +} + +func (d *DockerTestFramework) RequestFromNodes(testFunc func(*testing.T, string)) { + for nodeAlias := range d.nodes { + testFunc(d.Testing, nodeAlias) + } +} diff --git a/tools/docker-network/tests/dockertestframework/rewards.go b/tools/docker-network/tests/dockertestframework/rewards.go new file mode 100644 index 000000000..257517aaf --- /dev/null +++ b/tools/docker-network/tests/dockertestframework/rewards.go @@ -0,0 +1,112 @@ +//go:build dockertests + +package dockertestframework + +import ( + "context" + + "github.com/stretchr/testify/require" + + "github.com/iotaledger/iota-core/pkg/testsuite/mock" + iotago "github.com/iotaledger/iota.go/v4" +) + +func (d *DockerTestFramework) ClaimRewardsForValidator(ctx context.Context, validatorWallet *mock.Wallet) { + validatorAccountData := validatorWallet.BlockIssuer.AccountData + outputData := &mock.OutputData{ + ID: validatorAccountData.OutputID, + Address: validatorAccountData.Address, + AddressIndex: validatorAccountData.AddressIndex, + Output: validatorAccountData.Output, + } + signedTx := validatorWallet.ClaimValidatorRewards("", outputData) + + validatorWallet.CreateAndSubmitBasicBlock(ctx, "claim_rewards_validator", mock.WithPayload(signedTx)) + d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) + + // update account data of validator + validatorWallet.SetBlockIssuer(&mock.AccountData{ + ID: validatorWallet.BlockIssuer.AccountData.ID, + Address: validatorWallet.BlockIssuer.AccountData.Address, + AddressIndex: validatorWallet.BlockIssuer.AccountData.AddressIndex, + OutputID: iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), + Output: signedTx.Transaction.Outputs[0].(*iotago.AccountOutput), + }) +} + +func (d *DockerTestFramework) ClaimRewardsForDelegator(ctx context.Context, wallet *mock.Wallet, delegationOutputData *mock.OutputData) iotago.OutputID { + signedTx := wallet.ClaimDelegatorRewards("", delegationOutputData) + + wallet.CreateAndSubmitBasicBlock(ctx, "claim_rewards_delegator", mock.WithPayload(signedTx)) + d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) + + return iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) +} + +func (d *DockerTestFramework) DelayedClaimingTransition(ctx context.Context, wallet *mock.Wallet, delegationOutputData *mock.OutputData) *mock.OutputData { + signedTx := wallet.DelayedClaimingTransition("delayed_claim_tx", delegationOutputData) + + wallet.CreateAndSubmitBasicBlock(ctx, "delayed_claim", mock.WithPayload(signedTx)) + d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) + + return &mock.OutputData{ + ID: iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), + Address: wallet.Address(), + AddressIndex: 0, + Output: signedTx.Transaction.Outputs[0].(*iotago.DelegationOutput), + } +} + +// DelegateToValidator requests faucet funds and delegate the UTXO output to the validator. +func (d *DockerTestFramework) DelegateToValidator(fromWallet *mock.Wallet, accountAddress *iotago.AccountAddress) *mock.OutputData { + // requesting faucet funds as delegation input + ctx := context.TODO() + fundsOutputData := d.RequestFaucetFunds(ctx, fromWallet, iotago.AddressEd25519) + + signedTx := fromWallet.CreateDelegationFromInput( + "delegation_tx", + fundsOutputData, + mock.WithDelegatedValidatorAddress(accountAddress), + mock.WithDelegationStartEpoch(GetDelegationStartEpoch(fromWallet.Client.LatestAPI(), fromWallet.GetNewBlockIssuanceResponse().LatestCommitment.Slot)), + ) + + fromWallet.CreateAndSubmitBasicBlock(ctx, "delegation", mock.WithPayload(signedTx)) + d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) + + delegationOutput, ok := signedTx.Transaction.Outputs[0].(*iotago.DelegationOutput) + require.True(d.Testing, ok) + + return &mock.OutputData{ + ID: iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), + Address: fromWallet.Address(), + AddressIndex: 0, + Output: delegationOutput, + } + +} + +func GetDelegationStartEpoch(api iotago.API, commitmentSlot iotago.SlotIndex) iotago.EpochIndex { + pastBoundedSlot := commitmentSlot + api.ProtocolParameters().MaxCommittableAge() + pastBoundedEpoch := api.TimeProvider().EpochFromSlot(pastBoundedSlot) + pastBoundedEpochEnd := api.TimeProvider().EpochEnd(pastBoundedEpoch) + registrationSlot := pastBoundedEpochEnd - api.ProtocolParameters().EpochNearingThreshold() + + if pastBoundedSlot <= registrationSlot { + return pastBoundedEpoch + 1 + } + + return pastBoundedEpoch + 2 +} + +func GetDelegationEndEpoch(api iotago.API, slot, latestCommitmentSlot iotago.SlotIndex) iotago.EpochIndex { + futureBoundedSlot := latestCommitmentSlot + api.ProtocolParameters().MinCommittableAge() + futureBoundedEpoch := api.TimeProvider().EpochFromSlot(futureBoundedSlot) + + registrationSlot := api.TimeProvider().EpochEnd(api.TimeProvider().EpochFromSlot(slot)) - api.ProtocolParameters().EpochNearingThreshold() + + if futureBoundedSlot <= registrationSlot { + return futureBoundedEpoch + } + + return futureBoundedEpoch + 1 +} diff --git a/tools/docker-network/tests/dockertestframework/utils.go b/tools/docker-network/tests/dockertestframework/utils.go index 39c390bb3..f96154048 100644 --- a/tools/docker-network/tests/dockertestframework/utils.go +++ b/tools/docker-network/tests/dockertestframework/utils.go @@ -3,165 +3,17 @@ package dockertestframework import ( - "bytes" - "context" - "encoding/json" "fmt" - "io" - "net/http" "os" "regexp" - "sort" "strconv" - "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/lo" - "github.com/iotaledger/iota-core/pkg/testsuite/mock" - iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/api" ) -func (d *DockerTestFramework) CheckAccountStatus(ctx context.Context, blkID iotago.BlockID, txID iotago.TransactionID, creationOutputID iotago.OutputID, accountAddress *iotago.AccountAddress, checkIndexer ...bool) { - // request by blockID if provided, otherwise use txID - // we take the slot from the blockID in case the tx is created earlier than the block. - clt := d.defaultWallet.Client - slot := blkID.Slot() - - if blkID == iotago.EmptyBlockID { - blkMetadata, err := clt.TransactionIncludedBlockMetadata(ctx, txID) - require.NoError(d.Testing, err) - - blkID = blkMetadata.BlockID - slot = blkMetadata.BlockID.Slot() - } - - d.AwaitTransactionPayloadAccepted(ctx, txID) - - // wait for the account to be committed - d.AwaitCommitment(slot) - - // Check the indexer - if len(checkIndexer) > 0 && checkIndexer[0] { - indexerClt, err := d.defaultWallet.Client.Indexer(ctx) - require.NoError(d.Testing, err) - - _, _, _, err = indexerClt.Account(ctx, accountAddress) - require.NoError(d.Testing, err) - } - - // check if the creation output exists - _, err := clt.OutputByID(ctx, creationOutputID) - require.NoError(d.Testing, err) -} - -func (d *DockerTestFramework) AssertIndexerAccount(account *mock.AccountData) { - d.Eventually(func() error { - ctx := context.TODO() - indexerClt, err := d.defaultWallet.Client.Indexer(ctx) - if err != nil { - return err - } - - outputID, output, _, err := indexerClt.Account(ctx, account.Address) - if err != nil { - return err - } - - assert.EqualValues(d.fakeTesting, account.OutputID, *outputID) - assert.EqualValues(d.fakeTesting, account.Output, output) - - return nil - }) -} - -func (d *DockerTestFramework) AssertIndexerFoundry(foundryID iotago.FoundryID) { - d.Eventually(func() error { - ctx := context.TODO() - indexerClt, err := d.defaultWallet.Client.Indexer(ctx) - if err != nil { - return err - } - - _, _, _, err = indexerClt.Foundry(ctx, foundryID) - if err != nil { - return err - } - - return nil - }) -} - -func (d *DockerTestFramework) AssertValidatorExists(accountAddr *iotago.AccountAddress) { - d.Eventually(func() error { - for _, node := range d.Nodes() { - _, err := d.Client(node.Name).Validator(context.TODO(), accountAddr) - if err != nil { - return err - } - } - - return nil - }) -} - -func (d *DockerTestFramework) AssertCommittee(expectedEpoch iotago.EpochIndex, expectedCommitteeMember []string) { - fmt.Println("Wait for committee selection..., expected epoch: ", expectedEpoch, ", expected committee size: ", len(expectedCommitteeMember)) - defer fmt.Println("Wait for committee selection......done") - - sort.Strings(expectedCommitteeMember) - - status := d.NodeStatus("V1") - testAPI := d.defaultWallet.Client.CommittedAPI() - expectedSlotStart := testAPI.TimeProvider().EpochStart(expectedEpoch) - require.Greater(d.Testing, expectedSlotStart, status.LatestAcceptedBlockSlot) - - if status.LatestAcceptedBlockSlot < expectedSlotStart { - slotToWait := expectedSlotStart - status.LatestAcceptedBlockSlot - secToWait := time.Duration(slotToWait) * time.Duration(testAPI.ProtocolParameters().SlotDurationInSeconds()) * time.Second - fmt.Println("Wait for ", secToWait, "until expected epoch: ", expectedEpoch) - time.Sleep(secToWait) - } - - d.Eventually(func() error { - for _, node := range d.Nodes() { - resp, err := d.Client(node.Name).Committee(context.TODO()) - if err != nil { - return err - } - - if resp.Epoch == expectedEpoch { - members := make([]string, len(resp.Committee)) - for i, member := range resp.Committee { - members[i] = member.AddressBech32 - } - - sort.Strings(members) - if match := lo.Equal(expectedCommitteeMember, members); match { - return nil - } - - return ierrors.Errorf("committee members does not match as expected, expected: %v, actual: %v", expectedCommitteeMember, members) - } - } - - return nil - }) -} - -func (d *DockerTestFramework) AssertFinalizedSlot(condition func(iotago.SlotIndex) error) { - for _, node := range d.Nodes() { - status := d.NodeStatus(node.Name) - - err := condition(status.LatestFinalizedSlot) - require.NoError(d.Testing, err) - } -} - // EventuallyWithDurations asserts that given condition will be met in deadline time, // periodically checking target function each tick. func (d *DockerTestFramework) EventuallyWithDurations(condition func() error, deadline time.Duration, tick time.Duration, waitForSync ...bool) { @@ -204,168 +56,6 @@ func (d *DockerTestFramework) Eventually(condition func() error, waitForSync ... d.EventuallyWithDurations(condition, deadline, d.optsTick) } -func (d *DockerTestFramework) AwaitTransactionPayloadAccepted(ctx context.Context, txID iotago.TransactionID) { - clt := d.defaultWallet.Client - - d.Eventually(func() error { - resp, err := clt.TransactionMetadata(ctx, txID) - if err != nil { - return err - } - - if resp.TransactionState == api.TransactionStateAccepted || - resp.TransactionState == api.TransactionStateCommitted || - resp.TransactionState == api.TransactionStateFinalized { - if resp.TransactionFailureReason == api.TxFailureNone { - return nil - } - } - - return ierrors.Errorf("transaction %s is pending or having errors, state: %s, failure reason: %s, failure details: %s", txID.ToHex(), resp.TransactionState, resp.TransactionFailureReason, resp.TransactionFailureDetails) - }) -} - -func (d *DockerTestFramework) AwaitTransactionState(ctx context.Context, txID iotago.TransactionID, expectedState api.TransactionState) { - d.Eventually(func() error { - resp, err := d.defaultWallet.Client.TransactionMetadata(ctx, txID) - if err != nil { - return err - } - - if expectedState == resp.TransactionState { - return nil - } else { - if resp.TransactionState == api.TransactionStateFailed { - return ierrors.Errorf("expected transaction %s to have state '%s', got '%s' instead, failure reason: %s, failure details: %s", txID, expectedState, resp.TransactionState, resp.TransactionFailureReason, resp.TransactionFailureDetails) - } - return ierrors.Errorf("expected transaction %s to have state '%s', got '%s' instead", txID, expectedState, resp.TransactionState) - } - }) -} - -func (d *DockerTestFramework) AwaitTransactionFailure(ctx context.Context, txID iotago.TransactionID, expectedReason api.TransactionFailureReason) { - d.Eventually(func() error { - resp, err := d.defaultWallet.Client.TransactionMetadata(ctx, txID) - if err != nil { - return err - } - - if expectedReason == resp.TransactionFailureReason { - return nil - } else { - return ierrors.Errorf("expected transaction %s to have failure reason '%s', got '%s' instead, failure details: %s", txID, expectedReason, resp.TransactionFailureReason, resp.TransactionFailureDetails) - } - }) -} - -func (d *DockerTestFramework) AwaitCommitment(targetSlot iotago.SlotIndex) { - currentCommittedSlot := d.NodeStatus("V1").LatestCommitmentID.Slot() - - // we wait at max "targetSlot - currentCommittedSlot" times * slot duration - deadline := time.Duration(d.defaultWallet.Client.CommittedAPI().ProtocolParameters().SlotDurationInSeconds()) * time.Second - if currentCommittedSlot < targetSlot { - deadline *= time.Duration(targetSlot - currentCommittedSlot) - } - - // give some extra time for peering etc - deadline += 30 * time.Second - - d.EventuallyWithDurations(func() error { - latestCommittedSlot := d.NodeStatus("V1").LatestCommitmentID.Slot() - if targetSlot > latestCommittedSlot { - return ierrors.Errorf("committed slot %d is not reached yet, current committed slot %d", targetSlot, latestCommittedSlot) - } - - return nil - }, deadline, 1*time.Second) -} - -func (d *DockerTestFramework) AwaitFinalization(targetSlot iotago.SlotIndex) { - d.Eventually(func() error { - currentFinalisedSlot := d.NodeStatus("V1").LatestFinalizedSlot - if targetSlot > currentFinalisedSlot { - return ierrors.Errorf("finalized slot %d is not reached yet", targetSlot) - } - - return nil - }) -} - -func (d *DockerTestFramework) AwaitNextEpoch() { - //nolint:lostcancel - ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) - - info, err := d.defaultWallet.Client.Info(ctx) - require.NoError(d.Testing, err) - - currentEpoch := d.defaultWallet.Client.CommittedAPI().TimeProvider().EpochFromSlot(info.Status.LatestFinalizedSlot) - - // await the start slot of the next epoch - d.AwaitFinalization(d.defaultWallet.Client.CommittedAPI().TimeProvider().EpochStart(currentEpoch + 1)) -} - -func (d *DockerTestFramework) AwaitAddressUnspentOutputAccepted(ctx context.Context, wallet *mock.Wallet, addr iotago.Address) (outputID iotago.OutputID, output iotago.Output, err error) { - indexerClt, err := wallet.Client.Indexer(ctx) - require.NoError(d.Testing, err) - addrBech := addr.Bech32(d.defaultWallet.Client.CommittedAPI().ProtocolParameters().Bech32HRP()) - - for t := time.Now(); time.Since(t) < d.optsWaitFor; time.Sleep(d.optsTick) { - res, err := indexerClt.Outputs(ctx, &api.BasicOutputsQuery{ - AddressBech32: addrBech, - }) - if err != nil { - return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "indexer request failed in request faucet funds") - } - - for res.Next() { - unspents, err := res.Outputs(ctx) - if err != nil { - return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "failed to get faucet unspent outputs") - } - - if len(unspents) == 0 { - break - } - - return lo.Return1(res.Response.Items.OutputIDs())[0], unspents[0], nil - } - } - - return iotago.EmptyOutputID, nil, ierrors.Errorf("no unspent outputs found for address %s due to timeout", addrBech) -} - -func (d *DockerTestFramework) SendFaucetRequest(ctx context.Context, wallet *mock.Wallet, receiveAddr iotago.Address) { - cltAPI := wallet.Client.CommittedAPI() - addrBech := receiveAddr.Bech32(cltAPI.ProtocolParameters().Bech32HRP()) - - type EnqueueRequest struct { - Address string `json:"address"` - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, d.optsFaucetURL+"/api/enqueue", func() io.Reader { - jsonData, _ := json.Marshal(&EnqueueRequest{ - Address: addrBech, - }) - - return bytes.NewReader(jsonData) - }()) - require.NoError(d.Testing, err) - - req.Header.Set("Content-Type", api.MIMEApplicationJSON) - - res, err := http.DefaultClient.Do(req) - require.NoError(d.Testing, err) - defer res.Body.Close() - - require.Equal(d.Testing, http.StatusAccepted, res.StatusCode) -} - -func (d *DockerTestFramework) RequestFromClients(testFunc func(*testing.T, string)) { - for alias := range d.nodes { - testFunc(d.Testing, alias) - } -} - func CreateLogDirectory(testName string) string { // make sure logs/ exists err := os.Mkdir("logs", 0755) @@ -388,32 +78,6 @@ func CreateLogDirectory(testName string) string { return dir } -func GetDelegationStartEpoch(api iotago.API, commitmentSlot iotago.SlotIndex) iotago.EpochIndex { - pastBoundedSlot := commitmentSlot + api.ProtocolParameters().MaxCommittableAge() - pastBoundedEpoch := api.TimeProvider().EpochFromSlot(pastBoundedSlot) - pastBoundedEpochEnd := api.TimeProvider().EpochEnd(pastBoundedEpoch) - registrationSlot := pastBoundedEpochEnd - api.ProtocolParameters().EpochNearingThreshold() - - if pastBoundedSlot <= registrationSlot { - return pastBoundedEpoch + 1 - } - - return pastBoundedEpoch + 2 -} - -func GetDelegationEndEpoch(api iotago.API, slot, latestCommitmentSlot iotago.SlotIndex) iotago.EpochIndex { - futureBoundedSlot := latestCommitmentSlot + api.ProtocolParameters().MinCommittableAge() - futureBoundedEpoch := api.TimeProvider().EpochFromSlot(futureBoundedSlot) - - registrationSlot := api.TimeProvider().EpochEnd(api.TimeProvider().EpochFromSlot(slot)) - api.ProtocolParameters().EpochNearingThreshold() - - if futureBoundedSlot <= registrationSlot { - return futureBoundedEpoch - } - - return futureBoundedEpoch + 1 -} - func IsStatusCode(err error, status int) bool { if err == nil { return false diff --git a/tools/docker-network/tests/dockertestframework/validator.go b/tools/docker-network/tests/dockertestframework/validator.go new file mode 100644 index 000000000..68c57335f --- /dev/null +++ b/tools/docker-network/tests/dockertestframework/validator.go @@ -0,0 +1,45 @@ +//go:build dockertests + +package dockertestframework + +import ( + "context" + + "github.com/stretchr/testify/require" + + "github.com/iotaledger/iota-core/pkg/testsuite/mock" + iotago "github.com/iotaledger/iota.go/v4" +) + +func (d *DockerTestFramework) StartIssueCandidacyPayload(nodes ...*Node) { + if len(nodes) == 0 { + return + } + + for _, node := range nodes { + node.IssueCandidacyPayload = true + } + + err := d.DockerComposeUp(true) + require.NoError(d.Testing, err) +} + +func (d *DockerTestFramework) StopIssueCandidacyPayload(nodes ...*Node) { + if len(nodes) == 0 { + return + } + + for _, node := range nodes { + node.IssueCandidacyPayload = false + } + + err := d.DockerComposeUp(true) + require.NoError(d.Testing, err) +} + +func (d *DockerTestFramework) IssueCandidacyPayloadFromAccount(wallet *mock.Wallet) iotago.BlockID { + block, err := wallet.CreateAndSubmitBasicBlock(context.TODO(), "candidacy_payload", mock.WithPayload(&iotago.CandidacyAnnouncement{})) + require.NoError(d.Testing, err) + + return block.ID() +} diff --git a/tools/docker-network/tests/eventapi_test.go b/tools/docker-network/tests/eventapi_test.go index ccf107e5c..7e2eb378c 100644 --- a/tools/docker-network/tests/eventapi_test.go +++ b/tools/docker-network/tests/eventapi_test.go @@ -127,7 +127,7 @@ func test_BasicTaggedDataBlocks(t *testing.T, e *dockertestframework.EventAPIDoc defer eventClt.Close() // create an account to issue blocks - wallet, _ := e.DockerTestFramework().CreateAccount() + wallet, _ := e.DockerTestFramework().CreateAccountFromFaucet() // prepare data blocks to send expectedBlocks := make(map[string]*iotago.Block) @@ -179,7 +179,7 @@ func test_DelegationTransactionBlocks(t *testing.T, e *dockertestframework.Event defer eventClt.Close() // create an account to issue blocks - wallet, _ := e.DockerTestFramework().CreateAccount() + wallet, _ := e.DockerTestFramework().CreateAccountFromFaucet() fundsOutputData := e.DockerTestFramework().RequestFaucetFunds(ctx, wallet, iotago.AddressEd25519) // prepare data blocks to send @@ -248,7 +248,7 @@ func test_AccountTransactionBlocks(t *testing.T, e *dockertestframework.EventAPI wallet, implicitAccountOutputData := e.DockerTestFramework().CreateImplicitAccount(ctx) // prepare account transition block - accountData, _, blk := e.DockerTestFramework().CreateAccountBlockFromImplicit(wallet, implicitAccountOutputData, wallet.GetNewBlockIssuanceResponse()) + accountData, _, blk := e.DockerTestFramework().TransitionImplicitAccountToAccountOutputBlock(wallet, implicitAccountOutputData, wallet.GetNewBlockIssuanceResponse()) expectedBlocks := map[string]*iotago.Block{ blk.MustID().ToHex(): blk, @@ -310,7 +310,7 @@ func test_FoundryTransactionBlocks(t *testing.T, e *dockertestframework.EventAPI defer eventClt.Close() { - wallet, account := e.DockerTestFramework().CreateAccount() + wallet, account := e.DockerTestFramework().CreateAccountFromFaucet() fundsOutputData := e.DockerTestFramework().RequestFaucetFunds(ctx, wallet, iotago.AddressEd25519) // prepare foundry output block @@ -375,10 +375,10 @@ func test_NFTTransactionBlocks(t *testing.T, e *dockertestframework.EventAPIDock defer eventClt.Close() { - wallet, _ := e.DockerTestFramework().CreateAccount() + wallet, _ := e.DockerTestFramework().CreateAccountFromFaucet() fundsOutputData := e.DockerTestFramework().RequestFaucetFunds(ctx, wallet, iotago.AddressEd25519) - // prepare foundry output block + // prepare NFT output block nftId, outputId, blk := e.DockerTestFramework().CreateNFTBlockFromInput(wallet, fundsOutputData) expectedBlocks := map[string]*iotago.Block{ blk.MustID().ToHex(): blk, @@ -438,7 +438,7 @@ func test_BlockMetadataMatchedCoreAPI(t *testing.T, e *dockertestframework.Event defer eventClt.Close() { - wallet, _ := e.DockerTestFramework().CreateAccount() + wallet, _ := e.DockerTestFramework().CreateAccountFromFaucet() assertions := []func(){ func() { e.AssertBlockMetadataStateAcceptedBlocks(ctx, eventClt) }, diff --git a/tools/docker-network/tests/mempool_invalid_signatures_test.go b/tools/docker-network/tests/mempool_invalid_signatures_test.go index 542051b35..feee13e79 100644 --- a/tools/docker-network/tests/mempool_invalid_signatures_test.go +++ b/tools/docker-network/tests/mempool_invalid_signatures_test.go @@ -37,7 +37,7 @@ func Test_MempoolInvalidSignatures(t *testing.T) { d.WaitUntilNetworkReady() - wallet, _ := d.CreateAccount() + wallet, _ := d.CreateAccountFromFaucet() ctx := context.Background() fundsOutputData := d.RequestFaucetFunds(ctx, wallet, iotago.AddressEd25519) diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go index c3e824cc6..aac4155c1 100644 --- a/tools/docker-network/tests/rewards_test.go +++ b/tools/docker-network/tests/rewards_test.go @@ -63,7 +63,12 @@ func Test_ValidatorRewards(t *testing.T) { // The earliest epoch in which we can remove the staking feature and claim rewards. claimingSlot := clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) - goodAccountData := d.CreateAccountFromImplicitAccount(goodWallet, goodAccountOutputData, blockIssuance, dockertestframework.WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) + goodAccountData := d.CreateAccountFromImplicitAccount(goodWallet, + goodAccountOutputData, + blockIssuance, + dockertestframework.WithStakingFeature(100, 1, stakingStartEpoch, endEpoch), + ) + initialMana := goodAccountData.Output.StoredMana() issueCandidacyPayloadInBackground(ctx, d, @@ -84,7 +89,11 @@ func Test_ValidatorRewards(t *testing.T) { endEpoch = stakingStartEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 claimingSlot = clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) - lazyAccountData := d.CreateAccountFromImplicitAccount(lazyWallet, lazyAccountOutputData, blockIssuance, dockertestframework.WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) + lazyAccountData := d.CreateAccountFromImplicitAccount(lazyWallet, + lazyAccountOutputData, + blockIssuance, + dockertestframework.WithStakingFeature(100, 1, stakingStartEpoch, endEpoch), + ) lazyInitialMana := lazyAccountData.Output.StoredMana() issueCandidacyPayloadInBackground(ctx, @@ -152,7 +161,7 @@ func Test_DelegatorRewards(t *testing.T) { d.WaitUntilNetworkReady() ctx := context.Background() - delegatorWallet, _ := d.CreateAccount() + delegatorWallet, _ := d.CreateAccountFromFaucet() clt := delegatorWallet.Client // delegate funds to V2 @@ -210,7 +219,7 @@ func Test_DelayedClaimingRewards(t *testing.T) { d.WaitUntilNetworkReady() ctx := context.Background() - delegatorWallet, _ := d.CreateAccount() + delegatorWallet, _ := d.CreateAccountFromFaucet() clt := delegatorWallet.Client {