From f862aaa7f7f241ecd49ef5b3494a9126745522dd Mon Sep 17 00:00:00 2001 From: jkrvivian Date: Thu, 2 May 2024 21:20:01 +0800 Subject: [PATCH 01/16] AwaitCommitment with shorter sleep --- tools/docker-network/tests/utils.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/docker-network/tests/utils.go b/tools/docker-network/tests/utils.go index ab4bd38eb..2ad456404 100644 --- a/tools/docker-network/tests/utils.go +++ b/tools/docker-network/tests/utils.go @@ -252,13 +252,14 @@ func (d *DockerTestFramework) AwaitCommitment(targetSlot iotago.SlotIndex) { currentCommittedSlot := d.NodeStatus("V1").LatestCommitmentID.Slot() for t := currentCommittedSlot; t <= targetSlot; t++ { - latestCommittedSlot := d.NodeStatus("V1").LatestCommitmentID.Slot() + for { + latestCommittedSlot := d.NodeStatus("V1").LatestCommitmentID.Slot() - if targetSlot <= latestCommittedSlot { - return + if latestCommittedSlot >= t { + break + } + time.Sleep(2 * time.Second) } - - time.Sleep(10 * time.Second) } } From 7cfc919e6e04737d789292842deb1a87db034ab9 Mon Sep 17 00:00:00 2001 From: muXxer Date: Fri, 3 May 2024 10:00:37 +0200 Subject: [PATCH 02/16] Refactor AwaitCommitment --- tools/docker-network/tests/utils.go | 51 ++++++++++++++++++----------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/tools/docker-network/tests/utils.go b/tools/docker-network/tests/utils.go index 2ad456404..ca0e422e5 100644 --- a/tools/docker-network/tests/utils.go +++ b/tools/docker-network/tests/utils.go @@ -161,22 +161,15 @@ func (d *DockerTestFramework) AssertFinalizedSlot(condition func(iotago.SlotInde } } -// Eventually asserts that given condition will be met in opts.waitFor time, -// periodically checking target function each opts.tick. -// -// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) -func (d *DockerTestFramework) Eventually(condition func() error, waitForSync ...bool) { +// 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) { ch := make(chan error, 1) - deadline := d.optsWaitFor - if len(waitForSync) > 0 && waitForSync[0] { - deadline = d.optsWaitForSync - } - timer := time.NewTimer(deadline) defer timer.Stop() - ticker := time.NewTicker(d.optsTick) + ticker := time.NewTicker(tick) defer ticker.Stop() var lastErr error @@ -197,6 +190,19 @@ func (d *DockerTestFramework) Eventually(condition func() error, waitForSync ... } } +// Eventually asserts that given condition will be met in opts.waitFor time, +// periodically checking target function each opts.tick. +// +// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +func (d *DockerTestFramework) Eventually(condition func() error, waitForSync ...bool) { + deadline := d.optsWaitFor + if len(waitForSync) > 0 && waitForSync[0] { + deadline = d.optsWaitForSync + } + + d.EventuallyWithDurations(condition, deadline, d.optsTick) +} + func (d *DockerTestFramework) AwaitTransactionPayloadAccepted(ctx context.Context, txID iotago.TransactionID) { clt := d.defaultWallet.Client @@ -251,16 +257,23 @@ func (d *DockerTestFramework) AwaitTransactionFailure(ctx context.Context, txID func (d *DockerTestFramework) AwaitCommitment(targetSlot iotago.SlotIndex) { currentCommittedSlot := d.NodeStatus("V1").LatestCommitmentID.Slot() - for t := currentCommittedSlot; t <= targetSlot; t++ { - for { - latestCommittedSlot := 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) + } - if latestCommittedSlot >= t { - break - } - time.Sleep(2 * time.Second) + // 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) { From f88acaf821f1c26ccc58c76440f302a45f27374e Mon Sep 17 00:00:00 2001 From: muXxer Date: Fri, 3 May 2024 10:00:50 +0200 Subject: [PATCH 03/16] Wait until network healthy --- tools/docker-network/tests/dockerframework.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/docker-network/tests/dockerframework.go b/tools/docker-network/tests/dockerframework.go index 7b9543f94..96a7911c4 100644 --- a/tools/docker-network/tests/dockerframework.go +++ b/tools/docker-network/tests/dockerframework.go @@ -243,7 +243,7 @@ func (d *DockerTestFramework) waitForNodesAndGetClients() error { } func (d *DockerTestFramework) WaitUntilNetworkReady() { - d.WaitUntilSync() + 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() @@ -275,19 +275,19 @@ func (d *DockerTestFramework) WaitUntilFaucetHealthy() { }, true) } -func (d *DockerTestFramework) WaitUntilSync() { - fmt.Println("Wait until the nodes are synced...") - defer fmt.Println("Wait until the nodes are synced......done") +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 { - synced, err := d.Client(node.Name).Health(context.TODO()) + info, err := d.Client(node.Name).Info(context.TODO()) if err != nil { return err } - if synced { + if info.Status.IsNetworkHealthy { fmt.Println("Node", node.Name, "is synced") break } @@ -379,7 +379,7 @@ func (d *DockerTestFramework) ResetNode(alias string, newSnapshotPath string) { }) d.DockerComposeUp(true) d.DumpContainerLog(d.Node(alias).ContainerName, "reset1") - d.WaitUntilSync() + d.WaitUntilNetworkHealthy() } func (d *DockerTestFramework) Clients(names ...string) map[string]mock.Client { From e7e7412bdc8f7b6cd203dfebabab4a4b481ffb2e Mon Sep 17 00:00:00 2001 From: muXxer Date: Fri, 3 May 2024 10:21:33 +0200 Subject: [PATCH 04/16] Increase maxPeers to 5 in docker network to fix randomly eclipsed nodes --- tools/docker-network/.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/docker-network/.env b/tools/docker-network/.env index 0de9f4407..283f70767 100644 --- a/tools/docker-network/.env +++ b/tools/docker-network/.env @@ -8,12 +8,12 @@ COMMON_CONFIG=" --profiling.bindAddress=0.0.0.0:6061 --restAPI.publicRoutes=/health,/api/routes,/api/core/v3/info,/api/core/v3/network*,/api/core/v3/blocks*,/api/core/v3/transactions*,/api/core/v3/commitments*,/api/core/v3/outputs*,/api/core/v3/accounts*,/api/core/v3/validators*,/api/core/v3/rewards*,/api/core/v3/committee*,/api/debug/v2/*,/api/indexer/v2/*,/api/mqtt/v2,/api/blockissuer/v1/*,/api/management/v1/* --debugAPI.enabled=false +--p2p.autopeering.maxPeers=5 --p2p.autopeering.allowLocalIPs=true " AUTOPEERING_CONFIG=" --p2p.autopeering.bootstrapPeers=/dns/node-1-validator/tcp/15600/p2p/12D3KooWRVt4Engu27jHnF2RjfX48EqiAqJbgLfFdHNt3Vn6BtJK ---p2p.autopeering.maxPeers=3 " # admin/admin From 150497f3fdc244efb6f7184801e421b483fd131b Mon Sep 17 00:00:00 2001 From: muXxer Date: Fri, 3 May 2024 10:37:31 +0200 Subject: [PATCH 05/16] Cleanup running go routines in docker network tests --- tools/docker-network/tests/eventapi_test.go | 28 +++++++++++++- tools/docker-network/tests/rewards_test.go | 41 +++++++++++++++++---- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/tools/docker-network/tests/eventapi_test.go b/tools/docker-network/tests/eventapi_test.go index 1a337943a..1ba3815fe 100644 --- a/tools/docker-network/tests/eventapi_test.go +++ b/tools/docker-network/tests/eventapi_test.go @@ -156,7 +156,12 @@ func test_BasicTaggedDataBlocks(t *testing.T, e *EventAPIDockerTestFramework) { // issue blocks go func() { for _, blk := range expectedBlocks { - fmt.Println("submitting a block") + if ctx.Err() != nil { + // context is canceled + return + } + + fmt.Println("submitting a block: ", blk.MustID().ToHex()) e.dockerFramework.SubmitBlock(context.Background(), blk) } }() @@ -213,6 +218,11 @@ func test_DelegationTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramewo // issue blocks go func() { for _, blk := range expectedBlocks { + if ctx.Err() != nil { + // context is canceled + return + } + fmt.Println("submitting a block: ", blk.MustID().ToHex()) e.dockerFramework.SubmitBlock(context.Background(), blk) } @@ -269,6 +279,11 @@ func test_AccountTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) // issue blocks go func() { for _, blk := range expectedBlocks { + if ctx.Err() != nil { + // context is canceled + return + } + fmt.Println("submitting a block: ", blk.MustID().ToHex()) e.dockerFramework.SubmitBlock(context.Background(), blk) } @@ -329,6 +344,11 @@ func test_FoundryTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) // issue blocks go func() { for _, blk := range expectedBlocks { + if ctx.Err() != nil { + // context is canceled + return + } + fmt.Println("submitting a block: ", blk.MustID().ToHex()) e.dockerFramework.SubmitBlock(context.Background(), blk) } @@ -387,6 +407,12 @@ func test_NFTTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) { // issue blocks go func() { for _, blk := range expectedBlocks { + if ctx.Err() != nil { + // context is canceled + return + } + + fmt.Println("submitting a block: ", blk.MustID().ToHex()) e.dockerFramework.SubmitBlock(context.Background(), blk) } }() diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go index 35ab84c60..36c4bea5a 100644 --- a/tools/docker-network/tests/rewards_test.go +++ b/tools/docker-network/tests/rewards_test.go @@ -40,7 +40,11 @@ func Test_ValidatorRewards(t *testing.T) { d.WaitUntilNetworkReady() - ctx := context.Background() + ctx, cancel := context.WithCancel(context.Background()) + + // cancel the context when the test is done + t.Cleanup(cancel) + clt := d.defaultWallet.Client status := d.NodeStatus("V1") currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(status.LatestAcceptedBlockSlot) @@ -54,12 +58,20 @@ func Test_ValidatorRewards(t *testing.T) { // create accounts and continue issuing candidacy payload for account in the background goodWallet, goodAccountData := d.CreateAccount(WithStakingFeature(100, 1, currentEpoch, endEpoch)) initialMana := goodAccountData.Output.StoredMana() - issueCandidacyPayloadInBackground(d, goodWallet, clt.CommittedAPI().TimeProvider().CurrentSlot(), claimingSlot, + issueCandidacyPayloadInBackground(ctx, + d, + goodWallet, + clt.CommittedAPI().TimeProvider().CurrentSlot(), + claimingSlot, slotsDuration) lazyWallet, lazyAccountData := d.CreateAccount(WithStakingFeature(100, 1, currentEpoch, endEpoch)) lazyInitialMana := lazyAccountData.Output.StoredMana() - issueCandidacyPayloadInBackground(d, lazyWallet, clt.CommittedAPI().TimeProvider().CurrentSlot(), claimingSlot, + issueCandidacyPayloadInBackground(ctx, + d, + lazyWallet, + clt.CommittedAPI().TimeProvider().CurrentSlot(), + claimingSlot, slotsDuration) // make sure the account is in the committee, so it can issue validation blocks @@ -74,8 +86,8 @@ func Test_ValidatorRewards(t *testing.T) { fmt.Println("Wait for ", secToWait, "until expected slot: ", claimingSlot) var wg sync.WaitGroup - issueValidationBlockInBackground(&wg, goodWallet, currentSlot, claimingSlot, 5) - issueValidationBlockInBackground(&wg, lazyWallet, currentSlot, claimingSlot, 1) + issueValidationBlockInBackground(ctx, &wg, goodWallet, currentSlot, claimingSlot, 5) + issueValidationBlockInBackground(ctx, &wg, lazyWallet, currentSlot, claimingSlot, 1) wg.Wait() } @@ -235,19 +247,24 @@ func Test_DelayedClaimingRewards(t *testing.T) { } } -func issueCandidacyPayloadInBackground(d *DockerTestFramework, wallet *mock.Wallet, startSlot, endSlot iotago.SlotIndex, slotDuration uint8) { +func issueCandidacyPayloadInBackground(ctx context.Context, d *DockerTestFramework, wallet *mock.Wallet, startSlot, endSlot iotago.SlotIndex, slotDuration uint8) { go func() { fmt.Println("Issuing candidacy payloads for account", wallet.BlockIssuer.AccountData.ID, "in the background...") defer fmt.Println("Issuing candidacy payloads for account", wallet.BlockIssuer.AccountData.ID, "in the background......done") for i := startSlot; i < endSlot; i++ { + if ctx.Err() != nil { + // context is canceled + return + } + d.IssueCandidacyPayloadFromAccount(wallet) time.Sleep(time.Duration(slotDuration) * time.Second) } }() } -func issueValidationBlockInBackground(wg *sync.WaitGroup, wallet *mock.Wallet, startSlot, endSlot iotago.SlotIndex, blocksPerSlot int) { +func issueValidationBlockInBackground(ctx context.Context, wg *sync.WaitGroup, wallet *mock.Wallet, startSlot, endSlot iotago.SlotIndex, blocksPerSlot int) { wg.Add(1) go func() { @@ -258,6 +275,11 @@ func issueValidationBlockInBackground(wg *sync.WaitGroup, wallet *mock.Wallet, s for i := startSlot; i < endSlot; i++ { // wait until the slot is reached for { + if ctx.Err() != nil { + // context is canceled + return + } + if wallet.CurrentSlot() == i { break } @@ -265,6 +287,11 @@ func issueValidationBlockInBackground(wg *sync.WaitGroup, wallet *mock.Wallet, s } for range blocksPerSlot { + if ctx.Err() != nil { + // context is canceled + return + } + wallet.CreateAndSubmitValidationBlock(context.Background(), "", nil) time.Sleep(1 * time.Second) } From 81ce1581c744f375924bc70c641b132ff22822b1 Mon Sep 17 00:00:00 2001 From: muXxer Date: Fri, 3 May 2024 11:53:26 +0200 Subject: [PATCH 06/16] Fix the staking start epoch in `Test_ValidatorRewards` --- tools/docker-network/tests/rewards_test.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go index 36c4bea5a..4d06dab0c 100644 --- a/tools/docker-network/tests/rewards_test.go +++ b/tools/docker-network/tests/rewards_test.go @@ -46,17 +46,25 @@ func Test_ValidatorRewards(t *testing.T) { t.Cleanup(cancel) clt := d.defaultWallet.Client - status := d.NodeStatus("V1") - currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(status.LatestAcceptedBlockSlot) + + blockIssuance, err := clt.BlockIssuance(ctx) + require.NoError(t, err) + + latestCommitmentSlot := blockIssuance.LatestCommitment.Slot + + pastBoundedSlot := latestCommitmentSlot + clt.CommittedAPI().ProtocolParameters().MaxCommittableAge() + pastBoundedEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(pastBoundedSlot) + + currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(latestCommitmentSlot) slotsDuration := clt.CommittedAPI().ProtocolParameters().SlotDurationInSeconds() // Set end epoch so the staking feature can be removed as soon as possible. - endEpoch := currentEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 + endEpoch := pastBoundedEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 // The earliest epoch in which we can remove the staking feature and claim rewards. claimingSlot := clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) // create accounts and continue issuing candidacy payload for account in the background - goodWallet, goodAccountData := d.CreateAccount(WithStakingFeature(100, 1, currentEpoch, endEpoch)) + goodWallet, goodAccountData := d.CreateAccount(WithStakingFeature(100, 1, pastBoundedEpoch, endEpoch)) initialMana := goodAccountData.Output.StoredMana() issueCandidacyPayloadInBackground(ctx, d, @@ -65,7 +73,7 @@ func Test_ValidatorRewards(t *testing.T) { claimingSlot, slotsDuration) - lazyWallet, lazyAccountData := d.CreateAccount(WithStakingFeature(100, 1, currentEpoch, endEpoch)) + lazyWallet, lazyAccountData := d.CreateAccount(WithStakingFeature(100, 1, pastBoundedEpoch, endEpoch)) lazyInitialMana := lazyAccountData.Output.StoredMana() issueCandidacyPayloadInBackground(ctx, d, From 910ff5f4aacfcceedcbac16bfbf3792b980f620b Mon Sep 17 00:00:00 2001 From: muXxer Date: Fri, 3 May 2024 12:04:05 +0200 Subject: [PATCH 07/16] Rename SlotIndex and EpochIndex occurrences --- pkg/protocol/engine/ledger/ledger/vm.go | 8 +++--- pkg/requesthandler/accounts.go | 8 +++--- pkg/tests/reward_test.go | 10 ++++---- pkg/testsuite/mock/wallet_transactions.go | 30 +++++++++++------------ tools/docker-network/tests/utils.go | 10 ++++---- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/pkg/protocol/engine/ledger/ledger/vm.go b/pkg/protocol/engine/ledger/ledger/vm.go index 8f5ea4c1a..6574906ee 100644 --- a/pkg/protocol/engine/ledger/ledger/vm.go +++ b/pkg/protocol/engine/ledger/ledger/vm.go @@ -126,8 +126,8 @@ func (v *VM) ValidateSignatures(signedTransaction mempool.SignedTransaction, res } apiForSlot := v.ledger.apiProvider.APIForSlot(commitmentInput.Slot) - futureBoundedSlotIndex := commitmentInput.Slot + apiForSlot.ProtocolParameters().MinCommittableAge() - claimingEpoch := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlotIndex) + futureBoundedSlot := commitmentInput.Slot + apiForSlot.ProtocolParameters().MinCommittableAge() + claimingEpoch := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlot) reward, _, _, rewardErr := v.ledger.sybilProtection.ValidatorReward(accountID, stakingFeature, claimingEpoch) if rewardErr != nil { @@ -141,8 +141,8 @@ func (v *VM) ValidateSignatures(signedTransaction mempool.SignedTransaction, res delegationEnd := castOutput.EndEpoch apiForSlot := v.ledger.apiProvider.APIForSlot(commitmentInput.Slot) - futureBoundedSlotIndex := commitmentInput.Slot + apiForSlot.ProtocolParameters().MinCommittableAge() - claimingEpoch := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlotIndex) + futureBoundedSlot := commitmentInput.Slot + apiForSlot.ProtocolParameters().MinCommittableAge() + claimingEpoch := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlot) if delegationID.Empty() { delegationID = iotago.DelegationIDFromOutputID(outputID) diff --git a/pkg/requesthandler/accounts.go b/pkg/requesthandler/accounts.go index 4e7e3d823..ce989c6c6 100644 --- a/pkg/requesthandler/accounts.go +++ b/pkg/requesthandler/accounts.go @@ -170,8 +170,8 @@ func (r *RequestHandler) RewardsByOutputID(outputID iotago.OutputID, optSlot ... //nolint:forcetypeassert stakingFeature := feature.(*iotago.StakingFeature) - futureBoundedSlotIndex := slot + apiForSlot.ProtocolParameters().MinCommittableAge() - claimingEpoch := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlotIndex) + futureBoundedSlot := slot + apiForSlot.ProtocolParameters().MinCommittableAge() + claimingEpoch := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlot) stakingPoolValidatorAccountID = accountOutput.AccountID // check if the account is a validator @@ -185,8 +185,8 @@ func (r *RequestHandler) RewardsByOutputID(outputID iotago.OutputID, optSlot ... //nolint:forcetypeassert delegationOutput := utxoOutput.Output().(*iotago.DelegationOutput) delegationEnd := delegationOutput.EndEpoch - futureBoundedSlotIndex := slot + apiForSlot.ProtocolParameters().MinCommittableAge() - claimingEpoch := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlotIndex) + futureBoundedSlot := slot + apiForSlot.ProtocolParameters().MinCommittableAge() + claimingEpoch := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlot) // If Delegation ID is zeroed, the output is in delegating state, which means its End Epoch is not set and we must use the // "last epoch" for the rewards calculation. diff --git a/pkg/tests/reward_test.go b/pkg/tests/reward_test.go index c24d45937..a637987ff 100644 --- a/pkg/tests/reward_test.go +++ b/pkg/tests/reward_test.go @@ -113,13 +113,13 @@ func Test_Delegation_DelayedClaimingDestroyOutputWithoutRewards(t *testing.T) { latestCommitment := blockIssuanceInfo.LatestCommitment apiForSlot := ts.DefaultWallet().Client.APIForSlot(block1_2Slot) - futureBoundedSlotIndex := latestCommitment.Slot + apiForSlot.ProtocolParameters().MinCommittableAge() - futureBoundedEpochIndex := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlotIndex) + futureBoundedSlot := latestCommitment.Slot + apiForSlot.ProtocolParameters().MinCommittableAge() + futureBoundedEpoch := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlot) registrationSlot := apiForSlot.TimeProvider().EpochEnd(apiForSlot.TimeProvider().EpochFromSlot(block1_2Slot)) - delegationEndEpoch := futureBoundedEpochIndex - if futureBoundedSlotIndex > registrationSlot { - delegationEndEpoch = futureBoundedEpochIndex + 1 + delegationEndEpoch := futureBoundedEpoch + if futureBoundedSlot > registrationSlot { + delegationEndEpoch = futureBoundedEpoch + 1 } tx2 := ts.DefaultWallet().DelayedClaimingTransition("TX2", ts.DefaultWallet().OutputData("TX1:0"), delegationEndEpoch) diff --git a/pkg/testsuite/mock/wallet_transactions.go b/pkg/testsuite/mock/wallet_transactions.go index 8c82224e9..f1c508d5f 100644 --- a/pkg/testsuite/mock/wallet_transactions.go +++ b/pkg/testsuite/mock/wallet_transactions.go @@ -139,31 +139,31 @@ func (w *Wallet) CreateDelegationFromInput(transactionName string, input *Output func (w *Wallet) DelegationStartFromSlot(slot, latestCommitmentSlot iotago.SlotIndex) iotago.EpochIndex { apiForSlot := w.Client.APIForSlot(slot) - pastBoundedSlotIndex := latestCommitmentSlot + apiForSlot.ProtocolParameters().MaxCommittableAge() - pastBoundedEpochIndex := apiForSlot.TimeProvider().EpochFromSlot(pastBoundedSlotIndex) + pastBoundedSlot := latestCommitmentSlot + apiForSlot.ProtocolParameters().MaxCommittableAge() + pastBoundedEpoch := apiForSlot.TimeProvider().EpochFromSlot(pastBoundedSlot) registrationSlot := w.registrationSlot(slot) - if pastBoundedSlotIndex <= registrationSlot { - return pastBoundedEpochIndex + 1 + if pastBoundedSlot <= registrationSlot { + return pastBoundedEpoch + 1 } - return pastBoundedEpochIndex + 2 + return pastBoundedEpoch + 2 } func (w *Wallet) DelegationEndFromSlot(slot, latestCommitmentSlot iotago.SlotIndex) iotago.EpochIndex { apiForSlot := w.Client.APIForSlot(slot) - futureBoundedSlotIndex := latestCommitmentSlot + apiForSlot.ProtocolParameters().MinCommittableAge() - futureBoundedEpochIndex := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlotIndex) + futureBoundedSlot := latestCommitmentSlot + apiForSlot.ProtocolParameters().MinCommittableAge() + futureBoundedEpoch := apiForSlot.TimeProvider().EpochFromSlot(futureBoundedSlot) registrationSlot := w.registrationSlot(slot) - if futureBoundedSlotIndex <= registrationSlot { - return futureBoundedEpochIndex + if futureBoundedSlot <= registrationSlot { + return futureBoundedEpoch } - return futureBoundedEpochIndex + 1 + return futureBoundedEpoch + 1 } // Returns the registration slot in the epoch X corresponding to the given slot. @@ -185,15 +185,15 @@ func (w *Wallet) DelayedClaimingTransition(transactionName string, input *Output if len(optDelegationEndEpoch) == 0 { api := w.Client.LatestAPI() latestCommitmentSlot := w.GetNewBlockIssuanceResponse().LatestCommitment.Slot - futureBoundedSlotIndex := latestCommitmentSlot + api.ProtocolParameters().MinCommittableAge() - futureBoundedEpochIndex := api.TimeProvider().EpochFromSlot(futureBoundedSlotIndex) + futureBoundedSlot := latestCommitmentSlot + api.ProtocolParameters().MinCommittableAge() + futureBoundedEpoch := api.TimeProvider().EpochFromSlot(futureBoundedSlot) registrationSlot := api.TimeProvider().EpochEnd(api.TimeProvider().EpochFromSlot(w.CurrentSlot())) - api.ProtocolParameters().EpochNearingThreshold() - if futureBoundedSlotIndex <= registrationSlot { - delegationEndEpoch = futureBoundedEpochIndex + if futureBoundedSlot <= registrationSlot { + delegationEndEpoch = futureBoundedEpoch } else { - delegationEndEpoch = futureBoundedEpochIndex + 1 + delegationEndEpoch = futureBoundedEpoch + 1 } } else { delegationEndEpoch = optDelegationEndEpoch[0] diff --git a/tools/docker-network/tests/utils.go b/tools/docker-network/tests/utils.go index ca0e422e5..f8870fd1a 100644 --- a/tools/docker-network/tests/utils.go +++ b/tools/docker-network/tests/utils.go @@ -392,16 +392,16 @@ func getDelegationStartEpoch(api iotago.API, commitmentSlot iotago.SlotIndex) io } func getDelegationEndEpoch(api iotago.API, slot, latestCommitmentSlot iotago.SlotIndex) iotago.EpochIndex { - futureBoundedSlotIndex := latestCommitmentSlot + api.ProtocolParameters().MinCommittableAge() - futureBoundedEpochIndex := api.TimeProvider().EpochFromSlot(futureBoundedSlotIndex) + futureBoundedSlot := latestCommitmentSlot + api.ProtocolParameters().MinCommittableAge() + futureBoundedEpoch := api.TimeProvider().EpochFromSlot(futureBoundedSlot) registrationSlot := api.TimeProvider().EpochEnd(api.TimeProvider().EpochFromSlot(slot)) - api.ProtocolParameters().EpochNearingThreshold() - if futureBoundedSlotIndex <= registrationSlot { - return futureBoundedEpochIndex + if futureBoundedSlot <= registrationSlot { + return futureBoundedEpoch } - return futureBoundedEpochIndex + 1 + return futureBoundedEpoch + 1 } func isStatusCode(err error, status int) bool { From c77e8364e8eb7feecd467250edd5bcfada215474 Mon Sep 17 00:00:00 2001 From: muXxer Date: Fri, 3 May 2024 12:05:11 +0200 Subject: [PATCH 08/16] Add `StakingStartEpochFromSlot` to wallet --- pkg/testsuite/mock/wallet_transactions.go | 9 +++++++++ tools/docker-network/tests/rewards_test.go | 9 ++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pkg/testsuite/mock/wallet_transactions.go b/pkg/testsuite/mock/wallet_transactions.go index f1c508d5f..33b8daf58 100644 --- a/pkg/testsuite/mock/wallet_transactions.go +++ b/pkg/testsuite/mock/wallet_transactions.go @@ -136,6 +136,15 @@ func (w *Wallet) CreateDelegationFromInput(transactionName string, input *Output return signedTransaction } +func (w *Wallet) StakingStartEpochFromSlot(latestCommitmentSlot iotago.SlotIndex) iotago.EpochIndex { + apiForSlot := w.Client.APIForSlot(latestCommitmentSlot) + + pastBoundedSlot := latestCommitmentSlot + apiForSlot.ProtocolParameters().MaxCommittableAge() + pastBoundedEpoch := apiForSlot.TimeProvider().EpochFromSlot(pastBoundedSlot) + + return pastBoundedEpoch +} + func (w *Wallet) DelegationStartFromSlot(slot, latestCommitmentSlot iotago.SlotIndex) iotago.EpochIndex { apiForSlot := w.Client.APIForSlot(slot) diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go index 4d06dab0c..87b53c040 100644 --- a/tools/docker-network/tests/rewards_test.go +++ b/tools/docker-network/tests/rewards_test.go @@ -52,19 +52,18 @@ func Test_ValidatorRewards(t *testing.T) { latestCommitmentSlot := blockIssuance.LatestCommitment.Slot - pastBoundedSlot := latestCommitmentSlot + clt.CommittedAPI().ProtocolParameters().MaxCommittableAge() - pastBoundedEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(pastBoundedSlot) + stakingStartEpoch := d.defaultWallet.StakingStartEpochFromSlot(latestCommitmentSlot) currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(latestCommitmentSlot) slotsDuration := clt.CommittedAPI().ProtocolParameters().SlotDurationInSeconds() // Set end epoch so the staking feature can be removed as soon as possible. - endEpoch := pastBoundedEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 + endEpoch := stakingStartEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 // The earliest epoch in which we can remove the staking feature and claim rewards. claimingSlot := clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) // create accounts and continue issuing candidacy payload for account in the background - goodWallet, goodAccountData := d.CreateAccount(WithStakingFeature(100, 1, pastBoundedEpoch, endEpoch)) + goodWallet, goodAccountData := d.CreateAccount(WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) initialMana := goodAccountData.Output.StoredMana() issueCandidacyPayloadInBackground(ctx, d, @@ -73,7 +72,7 @@ func Test_ValidatorRewards(t *testing.T) { claimingSlot, slotsDuration) - lazyWallet, lazyAccountData := d.CreateAccount(WithStakingFeature(100, 1, pastBoundedEpoch, endEpoch)) + lazyWallet, lazyAccountData := d.CreateAccount(WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) lazyInitialMana := lazyAccountData.Output.StoredMana() issueCandidacyPayloadInBackground(ctx, d, From b818d1ecd26ddc723f72a9da5d4d53074c63bcb8 Mon Sep 17 00:00:00 2001 From: muXxer Date: Fri, 3 May 2024 13:00:00 +0200 Subject: [PATCH 09/16] Add more detailed tx error messages in docker test framework --- go.mod | 2 +- go.sum | 4 ++-- tools/docker-network/tests/utils.go | 9 ++++++--- tools/gendoc/go.mod | 2 +- tools/gendoc/go.sum | 4 ++-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index f0483172b..b9e63ff97 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/iotaledger/hive.go/stringify v0.0.0-20240425095808-113b21573349 github.com/iotaledger/inx-app v1.0.0-rc.3.0.20240425100742-5c85b6d16701 github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240425100432-05e1bf8fc089 - github.com/iotaledger/iota.go/v4 v4.0.0-20240425100055-540c74851d65 + github.com/iotaledger/iota.go/v4 v4.0.0-20240503105040-c86882e71808 github.com/labstack/echo/v4 v4.12.0 github.com/labstack/gommon v0.4.2 github.com/libp2p/go-libp2p v0.33.2 diff --git a/go.sum b/go.sum index 0b4a86a70..588c3cdc7 100644 --- a/go.sum +++ b/go.sum @@ -327,8 +327,8 @@ github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240425100432-05e1bf8fc089 h1:+NRPSb github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240425100432-05e1bf8fc089/go.mod h1:+iSOmdi7LSd1pXMThZsQk4YDbCSlvVomJUqbRhp3+Nk= github.com/iotaledger/iota-crypto-demo v0.0.0-20240419094816-40260bb800f7 h1:R7ogCKTQ2D5SfVoE6n9GQUsKwm4dcxqwnU863JVlVbw= github.com/iotaledger/iota-crypto-demo v0.0.0-20240419094816-40260bb800f7/go.mod h1:ntqq5J5Fu2SijiqPsjjdFkMm96UhGU/K0z3j6ARpHec= -github.com/iotaledger/iota.go/v4 v4.0.0-20240425100055-540c74851d65 h1:cKn39WbYZrBbGIeK5SZyu1Eukh1IOq8ZdBh7jC2/9Gg= -github.com/iotaledger/iota.go/v4 v4.0.0-20240425100055-540c74851d65/go.mod h1:2/gBFmGlXzZLcpOqTQTl2GqXtoe/aec6Fu9QTooQPZQ= +github.com/iotaledger/iota.go/v4 v4.0.0-20240503105040-c86882e71808 h1:ruI9Xk8g4xbCFsXBBvIXkOi03WprGJyHkmERGSizFTk= +github.com/iotaledger/iota.go/v4 v4.0.0-20240503105040-c86882e71808/go.mod h1:2/gBFmGlXzZLcpOqTQTl2GqXtoe/aec6Fu9QTooQPZQ= github.com/ipfs/boxo v0.19.0 h1:UbX9FBJQF19ACLqRZOgdEla6jR/sC4H1O+iGE0NToXA= github.com/ipfs/boxo v0.19.0/go.mod h1:V5gJzbIMwKEXrg3IdvAxIdF7UPgU4RsXmNGS8MQ/0D4= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= diff --git a/tools/docker-network/tests/utils.go b/tools/docker-network/tests/utils.go index f8870fd1a..268f3ef7e 100644 --- a/tools/docker-network/tests/utils.go +++ b/tools/docker-network/tests/utils.go @@ -220,7 +220,7 @@ func (d *DockerTestFramework) AwaitTransactionPayloadAccepted(ctx context.Contex } } - return ierrors.Errorf("transaction %s is pending or having errors, state: %s, failure reason: %d", txID.ToHex(), resp.TransactionState.String(), resp.TransactionFailureReason) + 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) }) } @@ -234,7 +234,10 @@ func (d *DockerTestFramework) AwaitTransactionState(ctx context.Context, txID io if expectedState == resp.TransactionState { return nil } else { - return ierrors.Errorf("expected transaction %s to have state %s, got %s instead", txID, expectedState, resp.TransactionState) + 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) } }) } @@ -249,7 +252,7 @@ func (d *DockerTestFramework) AwaitTransactionFailure(ctx context.Context, txID if expectedReason == resp.TransactionFailureReason { return nil } else { - return ierrors.Errorf("expected transaction %s to have failure reason %T, got %s instead, failure details: %s", txID, expectedReason, resp.TransactionState, resp.TransactionFailureDetails) + return ierrors.Errorf("expected transaction %s to have failure reason '%s', got '%s' instead, failure details: %s", txID, expectedReason, resp.TransactionFailureReason, resp.TransactionFailureDetails) } }) } diff --git a/tools/gendoc/go.mod b/tools/gendoc/go.mod index 2cb447865..125f00667 100644 --- a/tools/gendoc/go.mod +++ b/tools/gendoc/go.mod @@ -74,7 +74,7 @@ require ( github.com/iotaledger/inx-app v1.0.0-rc.3.0.20240425100742-5c85b6d16701 // indirect github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240425100432-05e1bf8fc089 // indirect github.com/iotaledger/iota-crypto-demo v0.0.0-20240419094816-40260bb800f7 // indirect - github.com/iotaledger/iota.go/v4 v4.0.0-20240425100055-540c74851d65 // indirect + github.com/iotaledger/iota.go/v4 v4.0.0-20240503105040-c86882e71808 // indirect github.com/ipfs/boxo v0.19.0 // indirect github.com/ipfs/go-cid v0.4.1 // indirect github.com/ipfs/go-datastore v0.6.0 // indirect diff --git a/tools/gendoc/go.sum b/tools/gendoc/go.sum index 966ed7410..623804d16 100644 --- a/tools/gendoc/go.sum +++ b/tools/gendoc/go.sum @@ -331,8 +331,8 @@ github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240425100432-05e1bf8fc089 h1:+NRPSb github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240425100432-05e1bf8fc089/go.mod h1:+iSOmdi7LSd1pXMThZsQk4YDbCSlvVomJUqbRhp3+Nk= github.com/iotaledger/iota-crypto-demo v0.0.0-20240419094816-40260bb800f7 h1:R7ogCKTQ2D5SfVoE6n9GQUsKwm4dcxqwnU863JVlVbw= github.com/iotaledger/iota-crypto-demo v0.0.0-20240419094816-40260bb800f7/go.mod h1:ntqq5J5Fu2SijiqPsjjdFkMm96UhGU/K0z3j6ARpHec= -github.com/iotaledger/iota.go/v4 v4.0.0-20240425100055-540c74851d65 h1:cKn39WbYZrBbGIeK5SZyu1Eukh1IOq8ZdBh7jC2/9Gg= -github.com/iotaledger/iota.go/v4 v4.0.0-20240425100055-540c74851d65/go.mod h1:2/gBFmGlXzZLcpOqTQTl2GqXtoe/aec6Fu9QTooQPZQ= +github.com/iotaledger/iota.go/v4 v4.0.0-20240503105040-c86882e71808 h1:ruI9Xk8g4xbCFsXBBvIXkOi03WprGJyHkmERGSizFTk= +github.com/iotaledger/iota.go/v4 v4.0.0-20240503105040-c86882e71808/go.mod h1:2/gBFmGlXzZLcpOqTQTl2GqXtoe/aec6Fu9QTooQPZQ= github.com/ipfs/boxo v0.19.0 h1:UbX9FBJQF19ACLqRZOgdEla6jR/sC4H1O+iGE0NToXA= github.com/ipfs/boxo v0.19.0/go.mod h1:V5gJzbIMwKEXrg3IdvAxIdF7UPgU4RsXmNGS8MQ/0D4= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= From e010c72864af0f869bb782994afd81c91ace3a89 Mon Sep 17 00:00:00 2001 From: jkrvivian Date: Mon, 6 May 2024 14:50:32 +0800 Subject: [PATCH 10/16] Add methods to create an account with the provided BlockIssuance --- pkg/testsuite/mock/wallet_transactions.go | 11 ++++- tools/docker-network/tests/dockerframework.go | 36 +++++++++++++--- tools/docker-network/tests/rewards_test.go | 43 ++++++++++++------- 3 files changed, 67 insertions(+), 23 deletions(-) diff --git a/pkg/testsuite/mock/wallet_transactions.go b/pkg/testsuite/mock/wallet_transactions.go index 33b8daf58..34e67e9e5 100644 --- a/pkg/testsuite/mock/wallet_transactions.go +++ b/pkg/testsuite/mock/wallet_transactions.go @@ -12,6 +12,7 @@ import ( "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/runtime/options" 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/tpkg" "github.com/iotaledger/iota.go/v4/vm" @@ -403,7 +404,7 @@ func (w *Wallet) CreateImplicitAccountAndBasicOutputFromInput(transactionName st return signedTransaction } -func (w *Wallet) TransitionImplicitAccountToAccountOutput(transactionName string, inputs []*OutputData, opts ...options.Option[builder.AccountOutputBuilder]) *iotago.SignedTransaction { +func (w *Wallet) TransitionImplicitAccountToAccountOutputWithBlockIssuance(transactionName string, inputs []*OutputData, blockIssuance *api.IssuanceBlockHeaderResponse, opts ...options.Option[builder.AccountOutputBuilder]) *iotago.SignedTransaction { var implicitAccountOutput *OutputData var baseTokenAmount iotago.BaseToken for _, input := range inputs { @@ -435,7 +436,7 @@ func (w *Wallet) TransitionImplicitAccountToAccountOutput(transactionName string AccountID: implicitAccountID, }), WithCommitmentInput(&iotago.CommitmentInput{ - CommitmentID: w.GetNewBlockIssuanceResponse().LatestCommitment.MustID(), + CommitmentID: blockIssuance.LatestCommitment.MustID(), }), WithInputs(inputs...), WithOutputs(accountOutput), @@ -446,6 +447,12 @@ func (w *Wallet) TransitionImplicitAccountToAccountOutput(transactionName string return signedTransaction } +func (w *Wallet) TransitionImplicitAccountToAccountOutput(transactionName string, inputs []*OutputData, opts ...options.Option[builder.AccountOutputBuilder]) *iotago.SignedTransaction { + issuance := w.GetNewBlockIssuanceResponse() + + return w.TransitionImplicitAccountToAccountOutputWithBlockIssuance(transactionName, inputs, issuance, opts...) +} + func (w *Wallet) CreateFoundryAndNativeTokensFromInput(input *OutputData, mintedAmount iotago.BaseToken, maxSupply iotago.BaseToken) *iotago.SignedTransaction { issuer := w.BlockIssuer.AccountData currentSlot := w.Client.LatestAPI().TimeProvider().CurrentSlot() diff --git a/tools/docker-network/tests/dockerframework.go b/tools/docker-network/tests/dockerframework.go index 96a7911c4..44a9fdd01 100644 --- a/tools/docker-network/tests/dockerframework.go +++ b/tools/docker-network/tests/dockerframework.go @@ -536,21 +536,32 @@ func (d *DockerTestFramework) CreateFoundryTransitionBlockFromInput(issuerID iot lo.PanicOnErr(d.defaultWallet.CreateAndSubmitBasicBlock(context.Background(), "foundry_transition", mock.WithPayload(signedTx))).ProtocolBlock() } -// CreateAccountBlockFromInput consumes the given output, which should be either an basic output with implicit address, then build block with the given account output options. +// CreateAccountBlock creates an new account from implicit one to full one. func (d *DockerTestFramework) CreateAccountBlock(opts ...options.Option[builder.AccountOutputBuilder]) (*mock.AccountData, *mock.Wallet, *iotago.SignedTransaction, *iotago.Block) { // create an implicit account by requesting faucet funds ctx := context.TODO() newWallet, implicitAccountOutputData := d.CreateImplicitAccount(ctx) - var implicitBlockIssuerKey iotago.BlockIssuerKey = iotago.Ed25519PublicKeyHashBlockIssuerKeyFromImplicitAccountCreationAddress(newWallet.ImplicitAccountCreationAddress()) + accountData, tx, blk := d.CreateAccountBlockFromImplicit(newWallet, implicitAccountOutputData, d.defaultWallet.GetNewBlockIssuanceResponse(), opts...) + + return accountData, newWallet, tx, blk +} + +// 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 := newWallet.TransitionImplicitAccountToAccountOutput("", []*mock.OutputData{implicitAccountOutputData}, opts...) + + signedTx := accountWallet.TransitionImplicitAccountToAccountOutputWithBlockIssuance("", []*mock.OutputData{implicitAccountOutputData}, blockIssuance, opts...) // The account transition block should be issued by the implicit account block issuer key. - block, err := newWallet.CreateBasicBlock(ctx, "", mock.WithPayload(signedTx)) + 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) @@ -564,7 +575,7 @@ func (d *DockerTestFramework) CreateAccountBlock(opts ...options.Option[builder. AddressIndex: implicitAccountOutputData.AddressIndex, } - return accountOutputData, newWallet, signedTx, block.ProtocolBlock() + 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. @@ -605,6 +616,21 @@ func (d *DockerTestFramework) CreateAccount(opts ...options.Option[builder.Accou 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{ diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go index 87b53c040..b5fee342f 100644 --- a/tools/docker-network/tests/rewards_test.go +++ b/tools/docker-network/tests/rewards_test.go @@ -46,24 +46,23 @@ func Test_ValidatorRewards(t *testing.T) { t.Cleanup(cancel) clt := d.defaultWallet.Client + slotsDuration := clt.CommittedAPI().ProtocolParameters().SlotDurationInSeconds() + + // create good account + goodWallet, goodAccountOutputData := d.CreateImplicitAccount(ctx) blockIssuance, err := clt.BlockIssuance(ctx) require.NoError(t, err) latestCommitmentSlot := blockIssuance.LatestCommitment.Slot - stakingStartEpoch := d.defaultWallet.StakingStartEpochFromSlot(latestCommitmentSlot) - currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(latestCommitmentSlot) - slotsDuration := clt.CommittedAPI().ProtocolParameters().SlotDurationInSeconds() - // Set end epoch so the staking feature can be removed as soon as possible. endEpoch := stakingStartEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 // The earliest epoch in which we can remove the staking feature and claim rewards. claimingSlot := clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) - // create accounts and continue issuing candidacy payload for account in the background - goodWallet, goodAccountData := d.CreateAccount(WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) + goodAccountData := d.CreateAccountFromImplicitAccount(goodWallet, goodAccountOutputData, blockIssuance, WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) initialMana := goodAccountData.Output.StoredMana() issueCandidacyPayloadInBackground(ctx, d, @@ -72,7 +71,20 @@ func Test_ValidatorRewards(t *testing.T) { claimingSlot, slotsDuration) - lazyWallet, lazyAccountData := d.CreateAccount(WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) + // create lazy account + lazyWallet, lazyAccountOutputData := d.CreateImplicitAccount(ctx) + + blockIssuance, err = clt.BlockIssuance(ctx) + require.NoError(t, err) + + latestCommitmentSlot = blockIssuance.LatestCommitment.Slot + stakingStartEpoch = d.defaultWallet.StakingStartEpochFromSlot(latestCommitmentSlot) + currentEpoch = clt.CommittedAPI().TimeProvider().EpochFromSlot(latestCommitmentSlot) + endEpoch = stakingStartEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 + claimingSlot = clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) + + lazyAccountData := d.CreateAccountFromImplicitAccount(lazyWallet, lazyAccountOutputData, blockIssuance, WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) + lazyInitialMana := lazyAccountData.Output.StoredMana() issueCandidacyPayloadInBackground(ctx, d, @@ -87,17 +99,16 @@ func Test_ValidatorRewards(t *testing.T) { d.AssertCommittee(currentEpoch+2, append(d.AccountsFromNodes(d.Nodes("V1", "V3", "V2", "V4")...), goodAccountAddrBech32, lazyAccountAddrBech32)) // issue validation blocks to have performance - if currentSlot := clt.CommittedAPI().TimeProvider().CurrentSlot(); currentSlot < claimingSlot { - slotToWait := claimingSlot - currentSlot - secToWait := time.Duration(slotToWait) * time.Duration(slotsDuration) * time.Second - fmt.Println("Wait for ", secToWait, "until expected slot: ", claimingSlot) + currentSlot := clt.CommittedAPI().TimeProvider().CurrentSlot() + slotToWait := claimingSlot - currentSlot + secToWait := time.Duration(slotToWait) * time.Duration(slotsDuration) * time.Second + fmt.Println("Wait for ", secToWait, "until expected slot: ", claimingSlot) - var wg sync.WaitGroup - issueValidationBlockInBackground(ctx, &wg, goodWallet, currentSlot, claimingSlot, 5) - issueValidationBlockInBackground(ctx, &wg, lazyWallet, currentSlot, claimingSlot, 1) + var wg sync.WaitGroup + issueValidationBlockInBackground(ctx, &wg, goodWallet, currentSlot, claimingSlot, 5) + issueValidationBlockInBackground(ctx, &wg, lazyWallet, currentSlot, claimingSlot, 1) - wg.Wait() - } + wg.Wait() // claim rewards that put to the account output d.AwaitCommitment(claimingSlot) From 7c69a5381322aa9606696679c19a4621d03dc830 Mon Sep 17 00:00:00 2001 From: muXxer Date: Mon, 6 May 2024 11:36:47 +0200 Subject: [PATCH 11/16] Remove opts from `CreateAccount` and remove `CreateAccountBlock` --- tools/docker-network/tests/api_core.go | 2 +- tools/docker-network/tests/api_core_test.go | 12 +++++++++++- .../tests/committeerotation_test.go | 12 ++++++++++-- tools/docker-network/tests/dockerframework.go | 19 ++++++------------- tools/docker-network/tests/eventapi_test.go | 9 ++++++++- 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/tools/docker-network/tests/api_core.go b/tools/docker-network/tests/api_core.go index 7aa93897e..f15cddb8b 100644 --- a/tools/docker-network/tests/api_core.go +++ b/tools/docker-network/tests/api_core.go @@ -233,7 +233,7 @@ func (d *DockerTestFramework) prepareAssets(totalAssetsNum int) (coreAPIAssets, // delegation //nolint:forcetypeassert - delegationOutputData:= d.DelegateToValidator(wallet, d.Node("V1").AccountAddress(d.Testing)) + delegationOutputData := d.DelegateToValidator(wallet, d.Node("V1").AccountAddress(d.Testing)) assets.setupAssetsForSlot(delegationOutputData.ID.CreationSlot()) assets[delegationOutputData.ID.CreationSlot()].delegationOutputs[delegationOutputData.ID] = delegationOutputData.Output.(*iotago.DelegationOutput) diff --git a/tools/docker-network/tests/api_core_test.go b/tools/docker-network/tests/api_core_test.go index c3290ad6e..797b80856 100644 --- a/tools/docker-network/tests/api_core_test.go +++ b/tools/docker-network/tests/api_core_test.go @@ -46,6 +46,11 @@ func Test_ValidatorsAPI(t *testing.T) { d.WaitUntilNetworkReady() hrp := d.defaultWallet.Client.CommittedAPI().ProtocolParameters().Bech32HRP() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(func() { + cancel() + }) + // Create registered validators var wg sync.WaitGroup clt := d.defaultWallet.Client @@ -58,8 +63,13 @@ func Test_ValidatorsAPI(t *testing.T) { go func() { defer wg.Done() - wallet, accountData := d.CreateAccount(WithStakingFeature(100, 1, currentEpoch)) + // create implicit accounts for every validator + wallet, implicitAccountOutputData := d.CreateImplicitAccount(ctx) + + // create account with staking feature for every validator + accountData := d.CreateAccountFromImplicitAccount(wallet, implicitAccountOutputData, wallet.GetNewBlockIssuanceResponse(), 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 d.AwaitCommitment(clt.CommittedAPI().TimeProvider().EpochEnd(currentEpoch)) blkID := d.IssueCandidacyPayloadFromAccount(wallet) diff --git a/tools/docker-network/tests/committeerotation_test.go b/tools/docker-network/tests/committeerotation_test.go index 7e689766e..75fa28149 100644 --- a/tools/docker-network/tests/committeerotation_test.go +++ b/tools/docker-network/tests/committeerotation_test.go @@ -3,6 +3,7 @@ package tests import ( + "context" "fmt" "testing" "time" @@ -205,9 +206,16 @@ func Test_Staking(t *testing.T) { d.WaitUntilNetworkReady() - _, account := d.CreateAccount(WithStakingFeature(100, 1, 0)) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) - d.AssertValidatorExists(account.Address) + // create implicit account for the validator + wallet, implicitAccountOutputData := d.CreateImplicitAccount(ctx) + + // create account with staking feature for the validator + accountData := d.CreateAccountFromImplicitAccount(wallet, implicitAccountOutputData, wallet.GetNewBlockIssuanceResponse(), WithStakingFeature(100, 1, 0)) + + d.AssertValidatorExists(accountData.Address) } // Test_Delegation tests if committee changed due to delegation. diff --git a/tools/docker-network/tests/dockerframework.go b/tools/docker-network/tests/dockerframework.go index 44a9fdd01..f5b7d2870 100644 --- a/tools/docker-network/tests/dockerframework.go +++ b/tools/docker-network/tests/dockerframework.go @@ -536,17 +536,6 @@ func (d *DockerTestFramework) CreateFoundryTransitionBlockFromInput(issuerID iot lo.PanicOnErr(d.defaultWallet.CreateAndSubmitBasicBlock(context.Background(), "foundry_transition", mock.WithPayload(signedTx))).ProtocolBlock() } -// CreateAccountBlock creates an new account from implicit one to full one. -func (d *DockerTestFramework) CreateAccountBlock(opts ...options.Option[builder.AccountOutputBuilder]) (*mock.AccountData, *mock.Wallet, *iotago.SignedTransaction, *iotago.Block) { - // create an implicit account by requesting faucet funds - ctx := context.TODO() - newWallet, implicitAccountOutputData := d.CreateImplicitAccount(ctx) - - accountData, tx, blk := d.CreateAccountBlockFromImplicit(newWallet, implicitAccountOutputData, d.defaultWallet.GetNewBlockIssuanceResponse(), opts...) - - return accountData, newWallet, tx, blk -} - // 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 @@ -602,9 +591,13 @@ func (d *DockerTestFramework) CreateImplicitAccount(ctx context.Context) (*mock. } // 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(opts ...options.Option[builder.AccountOutputBuilder]) (*mock.Wallet, *mock.AccountData) { +func (d *DockerTestFramework) CreateAccount() (*mock.Wallet, *mock.AccountData) { ctx := context.TODO() - accountData, newWallet, signedTx, block := d.CreateAccountBlock(opts...) + + 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) diff --git a/tools/docker-network/tests/eventapi_test.go b/tools/docker-network/tests/eventapi_test.go index 1ba3815fe..8465ed092 100644 --- a/tools/docker-network/tests/eventapi_test.go +++ b/tools/docker-network/tests/eventapi_test.go @@ -236,12 +236,19 @@ func test_DelegationTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramewo func test_AccountTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) { // get event API client ready ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + eventClt := e.ConnectEventAPIClient(ctx) defer eventClt.Close() // implicit account transition { - accountData, wallet, _, blk := e.dockerFramework.CreateAccountBlock() + // create an implicit account by requesting faucet funds + wallet, implicitAccountOutputData := e.dockerFramework.CreateImplicitAccount(ctx) + + // prepare account transition block + accountData, _, blk := e.dockerFramework.CreateAccountBlockFromImplicit(wallet, implicitAccountOutputData, wallet.GetNewBlockIssuanceResponse()) + expectedBlocks := map[string]*iotago.Block{ blk.MustID().ToHex(): blk, } From 6107882b1a3151f7074e3a26ad08635d5f48211d Mon Sep 17 00:00:00 2001 From: muXxer Date: Mon, 6 May 2024 11:59:08 +0200 Subject: [PATCH 12/16] Move `DockerTestFramework` to own package --- .../tests/accounttransition_test.go | 5 +- tools/docker-network/tests/api_core.go | 254 --------------- tools/docker-network/tests/api_core_test.go | 294 ++++++++++++++++-- .../tests/api_management_test.go | 17 +- .../tests/committeerotation_test.go | 29 +- .../framework.go} | 12 +- .../framework_eventapi.go} | 6 +- .../{ => dockertestframework}/options.go | 2 +- .../tests/{ => dockertestframework}/utils.go | 21 +- tools/docker-network/tests/eventapi_test.go | 65 ++-- .../tests/mempool_invalid_signatures_test.go | 5 +- tools/docker-network/tests/rewards_test.go | 27 +- .../tests/sync_snapshot_test.go | 5 +- 13 files changed, 372 insertions(+), 370 deletions(-) delete mode 100644 tools/docker-network/tests/api_core.go rename tools/docker-network/tests/{dockerframework.go => dockertestframework/framework.go} (99%) rename tools/docker-network/tests/{eventapiframework.go => dockertestframework/framework_eventapi.go} (99%) rename tools/docker-network/tests/{ => dockertestframework}/options.go (98%) rename tools/docker-network/tests/{ => dockertestframework}/utils.go (96%) diff --git a/tools/docker-network/tests/accounttransition_test.go b/tools/docker-network/tests/accounttransition_test.go index e6e002a3a..3dd4b6953 100644 --- a/tools/docker-network/tests/accounttransition_test.go +++ b/tools/docker-network/tests/accounttransition_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/iotaledger/iota-core/tools/docker-network/tests/dockertestframework" iotago "github.com/iotaledger/iota.go/v4" ) @@ -18,8 +19,8 @@ import ( // 3. account1 requests faucet funds then allots 1000 mana to account2. // 4. account2 requests faucet funds then creates native tokens. func Test_AccountTransitions(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), diff --git a/tools/docker-network/tests/api_core.go b/tools/docker-network/tests/api_core.go deleted file mode 100644 index f15cddb8b..000000000 --- a/tools/docker-network/tests/api_core.go +++ /dev/null @@ -1,254 +0,0 @@ -//go:build dockertests - -package tests - -import ( - "context" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/iotaledger/hive.go/ds/types" - "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" -) - -type coreAPIAssets map[iotago.SlotIndex]*coreAPISlotAssets - -func (a coreAPIAssets) setupAssetsForSlot(slot iotago.SlotIndex) { - _, ok := a[slot] - if !ok { - a[slot] = newAssetsPerSlot() - } -} - -func (a coreAPIAssets) assertCommitments(t *testing.T) { - for _, asset := range a { - asset.assertCommitments(t) - } -} - -func (a coreAPIAssets) assertBICs(t *testing.T) { - for _, asset := range a { - asset.assertBICs(t) - } -} - -func (a coreAPIAssets) forEachBlock(t *testing.T, f func(*testing.T, *iotago.Block)) { - for _, asset := range a { - for _, block := range asset.dataBlocks { - f(t, block) - } - for _, block := range asset.valueBlocks { - f(t, block) - } - } -} - -func (a coreAPIAssets) forEachTransaction(t *testing.T, f func(*testing.T, *iotago.SignedTransaction, iotago.BlockID)) { - for _, asset := range a { - for i, tx := range asset.transactions { - blockID := asset.valueBlocks[i].MustID() - f(t, tx, blockID) - } - } -} - -func (a coreAPIAssets) forEachReattachment(t *testing.T, f func(*testing.T, iotago.BlockID)) { - for _, asset := range a { - for _, reattachment := range asset.reattachments { - f(t, reattachment) - } - } -} - -func (a coreAPIAssets) forEachOutput(t *testing.T, f func(*testing.T, iotago.OutputID, iotago.Output)) { - for _, asset := range a { - for outID, out := range asset.basicOutputs { - f(t, outID, out) - } - for outID, out := range asset.faucetOutputs { - f(t, outID, out) - } - for outID, out := range asset.delegationOutputs { - f(t, outID, out) - } - } -} - -func (a coreAPIAssets) forEachSlot(t *testing.T, f func(*testing.T, iotago.SlotIndex, map[string]iotago.CommitmentID)) { - for slot, slotAssets := range a { - f(t, slot, slotAssets.commitmentPerNode) - } -} - -func (a coreAPIAssets) forEachCommitment(t *testing.T, f func(*testing.T, map[string]iotago.CommitmentID)) { - for _, asset := range a { - f(t, asset.commitmentPerNode) - } -} - -func (a coreAPIAssets) forEachAccountAddress(t *testing.T, f func(t *testing.T, accountAddress *iotago.AccountAddress, commitmentPerNode map[string]iotago.CommitmentID, bicPerNode map[string]iotago.BlockIssuanceCredits)) { - for _, asset := range a { - if asset.accountAddress == nil { - // no account created in this slot - continue - } - f(t, asset.accountAddress, asset.commitmentPerNode, asset.bicPerNode) - } -} - -func (a coreAPIAssets) assertUTXOOutputIDsInSlot(t *testing.T, slot iotago.SlotIndex, createdOutputs iotago.OutputIDs, spentOutputs iotago.OutputIDs) { - created := make(map[iotago.OutputID]types.Empty) - spent := make(map[iotago.OutputID]types.Empty) - for _, outputID := range createdOutputs { - created[outputID] = types.Void - } - - for _, outputID := range spentOutputs { - spent[outputID] = types.Void - } - - for outID := range a[slot].basicOutputs { - _, ok := created[outID] - require.True(t, ok, "Output ID not found in created outputs: %s, for slot %d", outID, slot) - } - - for outID := range a[slot].faucetOutputs { - _, ok := spent[outID] - require.True(t, ok, "Output ID not found in spent outputs: %s, for slot %d", outID, slot) - } -} - -func (a coreAPIAssets) assertUTXOOutputsInSlot(t *testing.T, slot iotago.SlotIndex, created []*api.OutputWithID, spent []*api.OutputWithID) { - createdMap := make(map[iotago.OutputID]iotago.Output) - spentMap := make(map[iotago.OutputID]iotago.Output) - for _, output := range created { - createdMap[output.OutputID] = output.Output - } - for _, output := range spent { - spentMap[output.OutputID] = output.Output - } - - for outID, out := range a[slot].basicOutputs { - _, ok := createdMap[outID] - require.True(t, ok, "Output ID not found in created outputs: %s, for slot %d", outID, slot) - require.Equal(t, out, createdMap[outID], "Output not equal for ID: %s, for slot %d", outID, slot) - } - - for outID, out := range a[slot].faucetOutputs { - _, ok := spentMap[outID] - require.True(t, ok, "Output ID not found in spent outputs: %s, for slot %d", outID, slot) - require.Equal(t, out, spentMap[outID], "Output not equal for ID: %s, for slot %d", outID, slot) - } -} - -type coreAPISlotAssets struct { - accountAddress *iotago.AccountAddress - dataBlocks []*iotago.Block - valueBlocks []*iotago.Block - transactions []*iotago.SignedTransaction - reattachments []iotago.BlockID - basicOutputs map[iotago.OutputID]iotago.Output - faucetOutputs map[iotago.OutputID]iotago.Output - delegationOutputs map[iotago.OutputID]iotago.Output - - commitmentPerNode map[string]iotago.CommitmentID - bicPerNode map[string]iotago.BlockIssuanceCredits -} - -func (a *coreAPISlotAssets) assertCommitments(t *testing.T) { - prevCommitment := a.commitmentPerNode["V1"] - for _, commitmentID := range a.commitmentPerNode { - if prevCommitment == iotago.EmptyCommitmentID { - require.Fail(t, "commitment is empty") - } - - require.Equal(t, commitmentID, prevCommitment) - prevCommitment = commitmentID - } -} - -func (a *coreAPISlotAssets) assertBICs(t *testing.T) { - prevBIC := a.bicPerNode["V1"] - for _, bic := range a.bicPerNode { - require.Equal(t, bic, prevBIC) - prevBIC = bic - } -} - -func newAssetsPerSlot() *coreAPISlotAssets { - return &coreAPISlotAssets{ - dataBlocks: make([]*iotago.Block, 0), - valueBlocks: make([]*iotago.Block, 0), - transactions: make([]*iotago.SignedTransaction, 0), - reattachments: make([]iotago.BlockID, 0), - basicOutputs: make(map[iotago.OutputID]iotago.Output), - faucetOutputs: make(map[iotago.OutputID]iotago.Output), - delegationOutputs: make(map[iotago.OutputID]iotago.Output), - commitmentPerNode: make(map[string]iotago.CommitmentID), - bicPerNode: make(map[string]iotago.BlockIssuanceCredits), - } -} - -func (d *DockerTestFramework) prepareAssets(totalAssetsNum int) (coreAPIAssets, iotago.SlotIndex) { - assets := make(coreAPIAssets) - ctx := context.Background() - - latestSlot := iotago.SlotIndex(0) - - for i := 0; i < totalAssetsNum; i++ { - // account - wallet, account := d.CreateAccount() - assets.setupAssetsForSlot(account.OutputID.Slot()) - assets[account.OutputID.Slot()].accountAddress = account.Address - - // data block - block := d.CreateTaggedDataBlock(wallet, []byte("tag")) - blockSlot := lo.PanicOnErr(block.ID()).Slot() - assets.setupAssetsForSlot(blockSlot) - assets[blockSlot].dataBlocks = append(assets[blockSlot].dataBlocks, block) - d.SubmitBlock(ctx, block) - - // transaction - valueBlock, signedTx, faucetOutput := d.CreateBasicOutputBlock(wallet) - valueBlockSlot := valueBlock.MustID().Slot() - assets.setupAssetsForSlot(valueBlockSlot) - // transaction and outputs are stored with the earliest included block - assets[valueBlockSlot].valueBlocks = append(assets[valueBlockSlot].valueBlocks, valueBlock) - assets[valueBlockSlot].transactions = append(assets[valueBlockSlot].transactions, signedTx) - basicOutputID := iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) - assets[valueBlockSlot].basicOutputs[basicOutputID] = signedTx.Transaction.Outputs[0] - assets[valueBlockSlot].faucetOutputs[faucetOutput.ID] = faucetOutput.Output - d.SubmitBlock(ctx, valueBlock) - d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) - - // issue reattachment after the first one is already included - secondAttachment, err := wallet.CreateAndSubmitBasicBlock(ctx, "second_attachment", mock.WithPayload(signedTx)) - require.NoError(d.Testing, err) - assets[valueBlockSlot].reattachments = append(assets[valueBlockSlot].reattachments, secondAttachment.ID()) - - // delegation - //nolint:forcetypeassert - delegationOutputData := d.DelegateToValidator(wallet, d.Node("V1").AccountAddress(d.Testing)) - assets.setupAssetsForSlot(delegationOutputData.ID.CreationSlot()) - assets[delegationOutputData.ID.CreationSlot()].delegationOutputs[delegationOutputData.ID] = delegationOutputData.Output.(*iotago.DelegationOutput) - - latestSlot = lo.Max[iotago.SlotIndex](latestSlot, blockSlot, valueBlockSlot, delegationOutputData.ID.CreationSlot(), secondAttachment.ID().Slot()) - - fmt.Printf("Assets for slot %d\n: dataBlock: %s block: %s\ntx: %s\nbasic output: %s, faucet output: %s\n delegation output: %s\n", - valueBlockSlot, block.MustID().String(), valueBlock.MustID().String(), signedTx.MustID().String(), - basicOutputID.String(), faucetOutput.ID.String(), delegationOutputData.ID.String()) - } - - return assets, latestSlot -} - -func (d *DockerTestFramework) requestFromClients(testFunc func(*testing.T, string)) { - for alias := range d.nodes { - testFunc(d.Testing, alias) - } -} diff --git a/tools/docker-network/tests/api_core_test.go b/tools/docker-network/tests/api_core_test.go index 797b80856..8b5e22ce4 100644 --- a/tools/docker-network/tests/api_core_test.go +++ b/tools/docker-network/tests/api_core_test.go @@ -12,12 +12,246 @@ import ( "github.com/stretchr/testify/require" + "github.com/iotaledger/hive.go/ds/types" + "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/iota-core/pkg/testsuite/mock" + "github.com/iotaledger/iota-core/tools/docker-network/tests/dockertestframework" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/api" "github.com/iotaledger/iota.go/v4/tpkg" ) +type coreAPIAssets map[iotago.SlotIndex]*coreAPISlotAssets + +func (a coreAPIAssets) setupAssetsForSlot(slot iotago.SlotIndex) { + _, ok := a[slot] + if !ok { + a[slot] = newAssetsPerSlot() + } +} + +func (a coreAPIAssets) assertCommitments(t *testing.T) { + for _, asset := range a { + asset.assertCommitments(t) + } +} + +func (a coreAPIAssets) assertBICs(t *testing.T) { + for _, asset := range a { + asset.assertBICs(t) + } +} + +func (a coreAPIAssets) forEachBlock(t *testing.T, f func(*testing.T, *iotago.Block)) { + for _, asset := range a { + for _, block := range asset.dataBlocks { + f(t, block) + } + for _, block := range asset.valueBlocks { + f(t, block) + } + } +} + +func (a coreAPIAssets) forEachTransaction(t *testing.T, f func(*testing.T, *iotago.SignedTransaction, iotago.BlockID)) { + for _, asset := range a { + for i, tx := range asset.transactions { + blockID := asset.valueBlocks[i].MustID() + f(t, tx, blockID) + } + } +} + +func (a coreAPIAssets) forEachReattachment(t *testing.T, f func(*testing.T, iotago.BlockID)) { + for _, asset := range a { + for _, reattachment := range asset.reattachments { + f(t, reattachment) + } + } +} + +func (a coreAPIAssets) forEachOutput(t *testing.T, f func(*testing.T, iotago.OutputID, iotago.Output)) { + for _, asset := range a { + for outID, out := range asset.basicOutputs { + f(t, outID, out) + } + for outID, out := range asset.faucetOutputs { + f(t, outID, out) + } + for outID, out := range asset.delegationOutputs { + f(t, outID, out) + } + } +} + +func (a coreAPIAssets) forEachSlot(t *testing.T, f func(*testing.T, iotago.SlotIndex, map[string]iotago.CommitmentID)) { + for slot, slotAssets := range a { + f(t, slot, slotAssets.commitmentPerNode) + } +} + +func (a coreAPIAssets) forEachCommitment(t *testing.T, f func(*testing.T, map[string]iotago.CommitmentID)) { + for _, asset := range a { + f(t, asset.commitmentPerNode) + } +} + +func (a coreAPIAssets) forEachAccountAddress(t *testing.T, f func(t *testing.T, accountAddress *iotago.AccountAddress, commitmentPerNode map[string]iotago.CommitmentID, bicPerNode map[string]iotago.BlockIssuanceCredits)) { + for _, asset := range a { + if asset.accountAddress == nil { + // no account created in this slot + continue + } + f(t, asset.accountAddress, asset.commitmentPerNode, asset.bicPerNode) + } +} + +func (a coreAPIAssets) assertUTXOOutputIDsInSlot(t *testing.T, slot iotago.SlotIndex, createdOutputs iotago.OutputIDs, spentOutputs iotago.OutputIDs) { + created := make(map[iotago.OutputID]types.Empty) + spent := make(map[iotago.OutputID]types.Empty) + for _, outputID := range createdOutputs { + created[outputID] = types.Void + } + + for _, outputID := range spentOutputs { + spent[outputID] = types.Void + } + + for outID := range a[slot].basicOutputs { + _, ok := created[outID] + require.True(t, ok, "Output ID not found in created outputs: %s, for slot %d", outID, slot) + } + + for outID := range a[slot].faucetOutputs { + _, ok := spent[outID] + require.True(t, ok, "Output ID not found in spent outputs: %s, for slot %d", outID, slot) + } +} + +func (a coreAPIAssets) assertUTXOOutputsInSlot(t *testing.T, slot iotago.SlotIndex, created []*api.OutputWithID, spent []*api.OutputWithID) { + createdMap := make(map[iotago.OutputID]iotago.Output) + spentMap := make(map[iotago.OutputID]iotago.Output) + for _, output := range created { + createdMap[output.OutputID] = output.Output + } + for _, output := range spent { + spentMap[output.OutputID] = output.Output + } + + for outID, out := range a[slot].basicOutputs { + _, ok := createdMap[outID] + require.True(t, ok, "Output ID not found in created outputs: %s, for slot %d", outID, slot) + require.Equal(t, out, createdMap[outID], "Output not equal for ID: %s, for slot %d", outID, slot) + } + + for outID, out := range a[slot].faucetOutputs { + _, ok := spentMap[outID] + require.True(t, ok, "Output ID not found in spent outputs: %s, for slot %d", outID, slot) + require.Equal(t, out, spentMap[outID], "Output not equal for ID: %s, for slot %d", outID, slot) + } +} + +type coreAPISlotAssets struct { + accountAddress *iotago.AccountAddress + dataBlocks []*iotago.Block + valueBlocks []*iotago.Block + transactions []*iotago.SignedTransaction + reattachments []iotago.BlockID + basicOutputs map[iotago.OutputID]iotago.Output + faucetOutputs map[iotago.OutputID]iotago.Output + delegationOutputs map[iotago.OutputID]iotago.Output + + commitmentPerNode map[string]iotago.CommitmentID + bicPerNode map[string]iotago.BlockIssuanceCredits +} + +func (a *coreAPISlotAssets) assertCommitments(t *testing.T) { + prevCommitment := a.commitmentPerNode["V1"] + for _, commitmentID := range a.commitmentPerNode { + if prevCommitment == iotago.EmptyCommitmentID { + require.Fail(t, "commitment is empty") + } + + require.Equal(t, commitmentID, prevCommitment) + prevCommitment = commitmentID + } +} + +func (a *coreAPISlotAssets) assertBICs(t *testing.T) { + prevBIC := a.bicPerNode["V1"] + for _, bic := range a.bicPerNode { + require.Equal(t, bic, prevBIC) + prevBIC = bic + } +} + +func newAssetsPerSlot() *coreAPISlotAssets { + return &coreAPISlotAssets{ + dataBlocks: make([]*iotago.Block, 0), + valueBlocks: make([]*iotago.Block, 0), + transactions: make([]*iotago.SignedTransaction, 0), + reattachments: make([]iotago.BlockID, 0), + basicOutputs: make(map[iotago.OutputID]iotago.Output), + faucetOutputs: make(map[iotago.OutputID]iotago.Output), + delegationOutputs: make(map[iotago.OutputID]iotago.Output), + commitmentPerNode: make(map[string]iotago.CommitmentID), + bicPerNode: make(map[string]iotago.BlockIssuanceCredits), + } +} + +func prepareAssets(d *dockertestframework.DockerTestFramework, totalAssetsNum int) (coreAPIAssets, iotago.SlotIndex) { + assets := make(coreAPIAssets) + ctx := context.Background() + + latestSlot := iotago.SlotIndex(0) + + for i := 0; i < totalAssetsNum; i++ { + // account + wallet, account := d.CreateAccount() + assets.setupAssetsForSlot(account.OutputID.Slot()) + assets[account.OutputID.Slot()].accountAddress = account.Address + + // data block + block := d.CreateTaggedDataBlock(wallet, []byte("tag")) + blockSlot := lo.PanicOnErr(block.ID()).Slot() + assets.setupAssetsForSlot(blockSlot) + assets[blockSlot].dataBlocks = append(assets[blockSlot].dataBlocks, block) + d.SubmitBlock(ctx, block) + + // transaction + valueBlock, signedTx, faucetOutput := d.CreateBasicOutputBlock(wallet) + valueBlockSlot := valueBlock.MustID().Slot() + assets.setupAssetsForSlot(valueBlockSlot) + // transaction and outputs are stored with the earliest included block + assets[valueBlockSlot].valueBlocks = append(assets[valueBlockSlot].valueBlocks, valueBlock) + assets[valueBlockSlot].transactions = append(assets[valueBlockSlot].transactions, signedTx) + basicOutputID := iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) + assets[valueBlockSlot].basicOutputs[basicOutputID] = signedTx.Transaction.Outputs[0] + assets[valueBlockSlot].faucetOutputs[faucetOutput.ID] = faucetOutput.Output + d.SubmitBlock(ctx, valueBlock) + d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) + + // issue reattachment after the first one is already included + secondAttachment, err := wallet.CreateAndSubmitBasicBlock(ctx, "second_attachment", mock.WithPayload(signedTx)) + require.NoError(d.Testing, err) + assets[valueBlockSlot].reattachments = append(assets[valueBlockSlot].reattachments, secondAttachment.ID()) + + // delegation + //nolint:forcetypeassert + delegationOutputData := d.DelegateToValidator(wallet, d.Node("V1").AccountAddress(d.Testing)) + assets.setupAssetsForSlot(delegationOutputData.ID.CreationSlot()) + assets[delegationOutputData.ID.CreationSlot()].delegationOutputs[delegationOutputData.ID] = delegationOutputData.Output.(*iotago.DelegationOutput) + + latestSlot = lo.Max[iotago.SlotIndex](latestSlot, blockSlot, valueBlockSlot, delegationOutputData.ID.CreationSlot(), secondAttachment.ID().Slot()) + + fmt.Printf("Assets for slot %d\n: dataBlock: %s block: %s\ntx: %s\nbasic output: %s, faucet output: %s\n delegation output: %s\n", + valueBlockSlot, block.MustID().String(), valueBlock.MustID().String(), signedTx.MustID().String(), + basicOutputID.String(), faucetOutput.ID.String(), delegationOutputData.ID.String()) + } + + return assets, latestSlot +} + // Test_ValidatorsAPI tests if the validators API returns the expected validators. // 1. Run docker network. // 2. Create 50 new accounts with staking feature. @@ -25,8 +259,8 @@ import ( // 4. Check if all 54 validators are returned from the validators API with pageSize 10, the pagination of api is also tested. // 5. Wait until next epoch then check again if the results remain. func Test_ValidatorsAPI(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), @@ -44,7 +278,7 @@ func Test_ValidatorsAPI(t *testing.T) { require.NoError(t, runErr) d.WaitUntilNetworkReady() - hrp := d.defaultWallet.Client.CommittedAPI().ProtocolParameters().Bech32HRP() + hrp := d.DefaultWallet().Client.CommittedAPI().ProtocolParameters().Bech32HRP() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(func() { @@ -53,7 +287,7 @@ func Test_ValidatorsAPI(t *testing.T) { // Create registered validators var wg sync.WaitGroup - clt := d.defaultWallet.Client + clt := d.DefaultWallet().Client status := d.NodeStatus("V1") currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(status.LatestAcceptedBlockSlot) expectedValidators := d.AccountsFromNodes(d.Nodes()...) @@ -67,7 +301,7 @@ 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(), 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 @@ -91,8 +325,8 @@ func Test_ValidatorsAPI(t *testing.T) { } func Test_CoreAPI(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), @@ -111,7 +345,7 @@ func Test_CoreAPI(t *testing.T) { d.WaitUntilNetworkReady() - assetsPerSlot, lastSlot := d.prepareAssets(5) + assetsPerSlot, lastSlot := prepareAssets(d, 5) fmt.Println("Await finalisation of slot", lastSlot) d.AwaitFinalization(lastSlot) @@ -426,7 +660,7 @@ func Test_CoreAPI(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - d.requestFromClients(test.testFunc) + d.RequestFromClients(test.testFunc) }) } @@ -436,8 +670,8 @@ func Test_CoreAPI(t *testing.T) { } func Test_CoreAPI_BadRequests(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), @@ -466,7 +700,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { blockID := tpkg.RandBlockID() respBlock, err := d.Client(nodeAlias).BlockByBlockID(context.Background(), blockID) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, respBlock) }, }, @@ -476,7 +710,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { blockID := tpkg.RandBlockID() resp, err := d.Client(nodeAlias).BlockMetadataByBlockID(context.Background(), blockID) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -486,7 +720,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { blockID := tpkg.RandBlockID() resp, err := d.Client(nodeAlias).BlockWithMetadataByBlockID(context.Background(), blockID) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -496,7 +730,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { slot := iotago.SlotIndex(1000_000_000) resp, err := d.Client(nodeAlias).CommitmentBySlot(context.Background(), slot) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -506,7 +740,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { committmentID := tpkg.RandCommitmentID() resp, err := d.Client(nodeAlias).CommitmentByID(context.Background(), committmentID) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -517,7 +751,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { resp, err := d.Client(nodeAlias).CommitmentUTXOChangesByID(context.Background(), committmentID) require.Error(t, err) // commitmentID is valid, but the UTXO changes does not exist in the storage - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -529,7 +763,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { resp, err := d.Client(nodeAlias).CommitmentUTXOChangesFullByID(context.Background(), committmentID) require.Error(t, err) // commitmentID is valid, but the UTXO changes does not exist in the storage - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -539,7 +773,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { slot := iotago.SlotIndex(1000_000_000) resp, err := d.Client(nodeAlias).CommitmentUTXOChangesBySlot(context.Background(), slot) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -550,7 +784,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { resp, err := d.Client(nodeAlias).CommitmentUTXOChangesFullBySlot(context.Background(), slot) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -560,7 +794,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { outputID := tpkg.RandOutputID(0) resp, err := d.Client(nodeAlias).OutputByID(context.Background(), outputID) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -571,7 +805,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { resp, err := d.Client(nodeAlias).OutputMetadataByID(context.Background(), outputID) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -584,7 +818,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { require.Error(t, err) require.Nil(t, out) require.Nil(t, outMetadata) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) }, }, { @@ -593,7 +827,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { txID := tpkg.RandTransactionID() resp, err := d.Client(nodeAlias).TransactionIncludedBlock(context.Background(), txID) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -604,7 +838,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { resp, err := d.Client(nodeAlias).TransactionIncludedBlockMetadata(context.Background(), txID) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -615,7 +849,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { resp, err := d.Client(nodeAlias).TransactionMetadata(context.Background(), txID) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -626,7 +860,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { commitmentID := tpkg.RandCommitmentID() resp, err := d.Client(nodeAlias).Congestion(context.Background(), accountAddress, 0, commitmentID) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -635,7 +869,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { testFunc: func(t *testing.T, nodeAlias string) { resp, err := d.Client(nodeAlias).Committee(context.Background(), 4) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusBadRequest)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusBadRequest)) require.Nil(t, resp) }, }, @@ -645,7 +879,7 @@ func Test_CoreAPI_BadRequests(t *testing.T) { outputID := tpkg.RandOutputID(0) resp, err := d.Client(nodeAlias).Rewards(context.Background(), outputID) require.Error(t, err) - require.True(t, isStatusCode(err, http.StatusNotFound)) + require.True(t, dockertestframework.IsStatusCode(err, http.StatusNotFound)) require.Nil(t, resp) }, }, @@ -653,7 +887,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.RequestFromClients(test.testFunc) }) } } diff --git a/tools/docker-network/tests/api_management_test.go b/tools/docker-network/tests/api_management_test.go index a89e2d0bc..6bf9023da 100644 --- a/tools/docker-network/tests/api_management_test.go +++ b/tools/docker-network/tests/api_management_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/iotaledger/iota-core/pkg/storage/database" + "github.com/iotaledger/iota-core/tools/docker-network/tests/dockertestframework" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/api" ) @@ -29,8 +30,8 @@ func getContextWithTimeout(duration time.Duration) context.Context { // 5. Re-Add the peer to node 1. // 6. List all peers of node 1 again and check if the peer was added. func Test_ManagementAPI_Peers(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), @@ -118,8 +119,8 @@ func Test_ManagementAPI_Peers(t *testing.T) { } func Test_ManagementAPI_Peers_BadRequests(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), @@ -182,8 +183,8 @@ func Test_ManagementAPI_Peers_BadRequests(t *testing.T) { } func Test_ManagementAPI_Pruning(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(0, time.Now().Unix(), 4, 4), iotago.WithLivenessOptions(3, 4, 2, 4, 5), iotago.WithCongestionControlOptions(1, 1, 1, 400_000, 250_000, 50_000_000, 1000, 100), @@ -263,8 +264,8 @@ func Test_ManagementAPI_Pruning(t *testing.T) { } func Test_ManagementAPI_Snapshots(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(0, time.Now().Unix(), 3, 4), iotago.WithLivenessOptions(3, 4, 2, 4, 8), iotago.WithCongestionControlOptions(1, 1, 1, 400_000, 250_000, 50_000_000, 1000, 100), diff --git a/tools/docker-network/tests/committeerotation_test.go b/tools/docker-network/tests/committeerotation_test.go index 75fa28149..f3ea39970 100644 --- a/tools/docker-network/tests/committeerotation_test.go +++ b/tools/docker-network/tests/committeerotation_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/iotaledger/hive.go/ierrors" + "github.com/iotaledger/iota-core/tools/docker-network/tests/dockertestframework" iotago "github.com/iotaledger/iota.go/v4" ) @@ -22,8 +23,8 @@ import ( // 4. Restart inx-validator of V2. // 5. Check that committee of size 4 is selected in next epoch. func Test_SmallerCommittee(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), @@ -44,7 +45,7 @@ func Test_SmallerCommittee(t *testing.T) { status := d.NodeStatus("V1") - clt := d.defaultWallet.Client + clt := d.DefaultWallet().Client currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(status.LatestAcceptedBlockSlot) // stop inx-validator plugin of validator 2 @@ -67,8 +68,8 @@ func Test_SmallerCommittee(t *testing.T) { // 4. Restart inx-validator of V2. // 5. Check that committee of size 3 (V1, V2, V4) is selected in next epoch and finalization occurs again from that epoch. func Test_ReuseDueToNoFinalization(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), @@ -91,7 +92,7 @@ func Test_ReuseDueToNoFinalization(t *testing.T) { err = d.StopContainer(d.Node("V2").ContainerName, d.Node("V3").ContainerName) require.NoError(t, err) - clt := d.defaultWallet.Client + clt := d.DefaultWallet().Client status := d.NodeStatus("V1") prevFinalizedSlot := status.LatestFinalizedSlot @@ -136,8 +137,8 @@ func Test_ReuseDueToNoFinalization(t *testing.T) { // 4. Start issuing candidacy payload on 3 validators only. // 5. Check finalization advances and the committee is changed to 3 committee members. func Test_NoCandidacyPayload(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), @@ -156,7 +157,7 @@ func Test_NoCandidacyPayload(t *testing.T) { d.WaitUntilNetworkReady() - clt := d.defaultWallet.Client + clt := d.DefaultWallet().Client status := d.NodeStatus("V1") prevFinalizedSlot := status.LatestFinalizedSlot fmt.Println("First finalized slot: ", prevFinalizedSlot) @@ -186,8 +187,8 @@ func Test_NoCandidacyPayload(t *testing.T) { // 2. Create an account with staking feature. // 3. Check if the account became a staker. func Test_Staking(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), @@ -213,7 +214,7 @@ 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(), WithStakingFeature(100, 1, 0)) + accountData := d.CreateAccountFromImplicitAccount(wallet, implicitAccountOutputData, wallet.GetNewBlockIssuanceResponse(), dockertestframework.WithStakingFeature(100, 1, 0)) d.AssertValidatorExists(accountData.Address) } @@ -225,8 +226,8 @@ func Test_Staking(t *testing.T) { // 3. Delegate requested faucet funds to V2, V2 should replace V3 as a committee member. (V2 > V4 > V1 > V3) // 4. Delegate requested faucet funds to V3, V3 should replace V1 as a committee member. (V3 > V2 > V4 > V1) func Test_Delegation(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), diff --git a/tools/docker-network/tests/dockerframework.go b/tools/docker-network/tests/dockertestframework/framework.go similarity index 99% rename from tools/docker-network/tests/dockerframework.go rename to tools/docker-network/tests/dockertestframework/framework.go index f5b7d2870..92c67b9f8 100644 --- a/tools/docker-network/tests/dockerframework.go +++ b/tools/docker-network/tests/dockertestframework/framework.go @@ -1,6 +1,6 @@ //go:build dockertests -package tests +package dockertestframework import ( "context" @@ -118,7 +118,7 @@ func NewDockerTestFramework(t *testing.T, opts ...options.Option[DockerTestFrame protocolParams := iotago.NewV3SnapshotProtocolParameters(d.optsProtocolParameterOptions...) testAPI := iotago.V3API(protocolParams) - d.logDirectoryPath = createLogDirectory(t.Name()) + d.logDirectoryPath = CreateLogDirectory(t.Name()) d.snapshotPath = snapshotFilePath d.optsSnapshotOptions = append(DefaultAccountOptions(protocolParams), []options.Option[snapshotcreator.Options]{ @@ -218,6 +218,10 @@ loop: return nil } +func (d *DockerTestFramework) DefaultWallet() *mock.Wallet { + return d.defaultWallet +} + func (d *DockerTestFramework) waitForNodesAndGetClients() error { nodes := d.Nodes() @@ -492,7 +496,7 @@ func (d *DockerTestFramework) CreateDelegationBlockFromInput(wallet *mock.Wallet "", input, mock.WithDelegatedValidatorAddress(accountAdddress), - mock.WithDelegationStartEpoch(getDelegationStartEpoch(clt.LatestAPI(), wallet.GetNewBlockIssuanceResponse().LatestCommitment.Slot)), + mock.WithDelegationStartEpoch(GetDelegationStartEpoch(clt.LatestAPI(), wallet.GetNewBlockIssuanceResponse().LatestCommitment.Slot)), ) outputID := iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) @@ -680,7 +684,7 @@ func (d *DockerTestFramework) DelegateToValidator(fromWallet *mock.Wallet, accou "delegation_tx", fundsOutputData, mock.WithDelegatedValidatorAddress(accountAddress), - mock.WithDelegationStartEpoch(getDelegationStartEpoch(fromWallet.Client.LatestAPI(), fromWallet.GetNewBlockIssuanceResponse().LatestCommitment.Slot)), + mock.WithDelegationStartEpoch(GetDelegationStartEpoch(fromWallet.Client.LatestAPI(), fromWallet.GetNewBlockIssuanceResponse().LatestCommitment.Slot)), ) fromWallet.CreateAndSubmitBasicBlock(ctx, "delegation", mock.WithPayload(signedTx)) diff --git a/tools/docker-network/tests/eventapiframework.go b/tools/docker-network/tests/dockertestframework/framework_eventapi.go similarity index 99% rename from tools/docker-network/tests/eventapiframework.go rename to tools/docker-network/tests/dockertestframework/framework_eventapi.go index c02f93d65..92526d577 100644 --- a/tools/docker-network/tests/eventapiframework.go +++ b/tools/docker-network/tests/dockertestframework/framework_eventapi.go @@ -1,6 +1,6 @@ //go:build dockertests -package tests +package dockertestframework import ( "context" @@ -43,6 +43,10 @@ func NewEventAPIDockerTestFramework(t *testing.T, dockerFramework *DockerTestFra } } +func (e EventAPIDockerTestFramework) DockerTestFramework() *DockerTestFramework { + return e.dockerFramework +} + func (e *EventAPIDockerTestFramework) ConnectEventAPIClient(ctx context.Context) *nodeclient.EventAPIClient { eventClt, err := e.DefaultClient.EventAPI(ctx) require.NoError(e.Testing, err) diff --git a/tools/docker-network/tests/options.go b/tools/docker-network/tests/dockertestframework/options.go similarity index 98% rename from tools/docker-network/tests/options.go rename to tools/docker-network/tests/dockertestframework/options.go index 7cb79d3d9..1df7097b9 100644 --- a/tools/docker-network/tests/options.go +++ b/tools/docker-network/tests/dockertestframework/options.go @@ -1,6 +1,6 @@ //go:build dockertests -package tests +package dockertestframework import ( "time" diff --git a/tools/docker-network/tests/utils.go b/tools/docker-network/tests/dockertestframework/utils.go similarity index 96% rename from tools/docker-network/tests/utils.go rename to tools/docker-network/tests/dockertestframework/utils.go index 268f3ef7e..39c390bb3 100644 --- a/tools/docker-network/tests/utils.go +++ b/tools/docker-network/tests/dockertestframework/utils.go @@ -1,6 +1,6 @@ //go:build dockertests -package tests +package dockertestframework import ( "bytes" @@ -13,6 +13,7 @@ import ( "regexp" "sort" "strconv" + "testing" "time" "github.com/stretchr/testify/assert" @@ -359,7 +360,13 @@ func (d *DockerTestFramework) SendFaucetRequest(ctx context.Context, wallet *moc require.Equal(d.Testing, http.StatusAccepted, res.StatusCode) } -func createLogDirectory(testName string) string { +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) if err != nil { @@ -381,7 +388,7 @@ func createLogDirectory(testName string) string { return dir } -func getDelegationStartEpoch(api iotago.API, commitmentSlot iotago.SlotIndex) iotago.EpochIndex { +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) @@ -394,7 +401,7 @@ func getDelegationStartEpoch(api iotago.API, commitmentSlot iotago.SlotIndex) io return pastBoundedEpoch + 2 } -func getDelegationEndEpoch(api iotago.API, slot, latestCommitmentSlot iotago.SlotIndex) iotago.EpochIndex { +func GetDelegationEndEpoch(api iotago.API, slot, latestCommitmentSlot iotago.SlotIndex) iotago.EpochIndex { futureBoundedSlot := latestCommitmentSlot + api.ProtocolParameters().MinCommittableAge() futureBoundedEpoch := api.TimeProvider().EpochFromSlot(futureBoundedSlot) @@ -407,11 +414,11 @@ func getDelegationEndEpoch(api iotago.API, slot, latestCommitmentSlot iotago.Slo return futureBoundedEpoch + 1 } -func isStatusCode(err error, status int) bool { +func IsStatusCode(err error, status int) bool { if err == nil { return false } - code, err := extractStatusCode(err.Error()) + code, err := ExtractStatusCode(err.Error()) if err != nil { return false } @@ -419,7 +426,7 @@ func isStatusCode(err error, status int) bool { return code == status } -func extractStatusCode(errorMessage string) (int, error) { +func ExtractStatusCode(errorMessage string) (int, error) { re := regexp.MustCompile(`code=(\d+)`) matches := re.FindStringSubmatch(errorMessage) if len(matches) != 2 { diff --git a/tools/docker-network/tests/eventapi_test.go b/tools/docker-network/tests/eventapi_test.go index 8465ed092..ccf107e5c 100644 --- a/tools/docker-network/tests/eventapi_test.go +++ b/tools/docker-network/tests/eventapi_test.go @@ -10,11 +10,12 @@ import ( "github.com/stretchr/testify/require" + "github.com/iotaledger/iota-core/tools/docker-network/tests/dockertestframework" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/api" ) -var eventAPITests = map[string]func(t *testing.T, e *EventAPIDockerTestFramework){ +var eventAPITests = map[string]func(t *testing.T, e *dockertestframework.EventAPIDockerTestFramework){ "Test_Commitments": test_Commitments, "Test_ValidationBlocks": test_ValidationBlocks, "Test_BasicTaggedDataBlocks": test_BasicTaggedDataBlocks, @@ -26,8 +27,8 @@ var eventAPITests = map[string]func(t *testing.T, e *EventAPIDockerTestFramework } func Test_MQTTTopics(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), )) @@ -44,7 +45,7 @@ func Test_MQTTTopics(t *testing.T) { d.WaitUntilNetworkReady() - e := NewEventAPIDockerTestFramework(t, d) + e := dockertestframework.NewEventAPIDockerTestFramework(t, d) for name, test := range eventAPITests { t.Run(name, func(t *testing.T) { @@ -53,7 +54,7 @@ func Test_MQTTTopics(t *testing.T) { } } -func test_Commitments(t *testing.T, e *EventAPIDockerTestFramework) { +func test_Commitments(t *testing.T, e *dockertestframework.EventAPIDockerTestFramework) { // get event API client ready ctx, cancel := context.WithCancel(context.Background()) @@ -89,7 +90,7 @@ func test_Commitments(t *testing.T, e *EventAPIDockerTestFramework) { require.NoError(t, err) } -func test_ValidationBlocks(t *testing.T, e *EventAPIDockerTestFramework) { +func test_ValidationBlocks(t *testing.T, e *dockertestframework.EventAPIDockerTestFramework) { // get event API client ready ctx, cancel := context.WithCancel(context.Background()) @@ -98,7 +99,7 @@ func test_ValidationBlocks(t *testing.T, e *EventAPIDockerTestFramework) { // prepare the expected commitments to be received validators := make(map[string]struct{}, 0) - nodes := e.dockerFramework.Nodes("V1", "V2", "V3", "V4") + nodes := e.DockerTestFramework().Nodes("V1", "V2", "V3", "V4") for _, node := range nodes { validators[node.AccountAddressBech32] = struct{}{} } @@ -119,19 +120,19 @@ func test_ValidationBlocks(t *testing.T, e *EventAPIDockerTestFramework) { require.NoError(t, err) } -func test_BasicTaggedDataBlocks(t *testing.T, e *EventAPIDockerTestFramework) { +func test_BasicTaggedDataBlocks(t *testing.T, e *dockertestframework.EventAPIDockerTestFramework) { // get event API client ready ctx, cancel := context.WithCancel(context.Background()) eventClt := e.ConnectEventAPIClient(ctx) defer eventClt.Close() // create an account to issue blocks - wallet, _ := e.dockerFramework.CreateAccount() + wallet, _ := e.DockerTestFramework().CreateAccount() // prepare data blocks to send expectedBlocks := make(map[string]*iotago.Block) for i := 0; i < 10; i++ { - blk := e.dockerFramework.CreateTaggedDataBlock(wallet, []byte("tag")) + blk := e.DockerTestFramework().CreateTaggedDataBlock(wallet, []byte("tag")) expectedBlocks[blk.MustID().ToHex()] = blk } @@ -162,7 +163,7 @@ func test_BasicTaggedDataBlocks(t *testing.T, e *EventAPIDockerTestFramework) { } fmt.Println("submitting a block: ", blk.MustID().ToHex()) - e.dockerFramework.SubmitBlock(context.Background(), blk) + e.DockerTestFramework().SubmitBlock(context.Background(), blk) } }() @@ -171,18 +172,18 @@ func test_BasicTaggedDataBlocks(t *testing.T, e *EventAPIDockerTestFramework) { require.NoError(t, err) } -func test_DelegationTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) { +func test_DelegationTransactionBlocks(t *testing.T, e *dockertestframework.EventAPIDockerTestFramework) { // get event API client ready ctx, cancel := context.WithCancel(context.Background()) eventClt := e.ConnectEventAPIClient(ctx) defer eventClt.Close() // create an account to issue blocks - wallet, _ := e.dockerFramework.CreateAccount() - fundsOutputData := e.dockerFramework.RequestFaucetFunds(ctx, wallet, iotago.AddressEd25519) + wallet, _ := e.DockerTestFramework().CreateAccount() + fundsOutputData := e.DockerTestFramework().RequestFaucetFunds(ctx, wallet, iotago.AddressEd25519) // prepare data blocks to send - delegationId, outputId, blk := e.dockerFramework.CreateDelegationBlockFromInput(wallet, e.dockerFramework.Node("V2").AccountAddress(t), fundsOutputData) + delegationId, outputId, blk := e.DockerTestFramework().CreateDelegationBlockFromInput(wallet, e.DockerTestFramework().Node("V2").AccountAddress(t), fundsOutputData) expectedBlocks := map[string]*iotago.Block{ blk.MustID().ToHex(): blk, } @@ -224,7 +225,7 @@ func test_DelegationTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramewo } fmt.Println("submitting a block: ", blk.MustID().ToHex()) - e.dockerFramework.SubmitBlock(context.Background(), blk) + e.DockerTestFramework().SubmitBlock(context.Background(), blk) } }() @@ -233,7 +234,7 @@ func test_DelegationTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramewo require.NoError(t, err) } -func test_AccountTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) { +func test_AccountTransactionBlocks(t *testing.T, e *dockertestframework.EventAPIDockerTestFramework) { // get event API client ready ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -244,10 +245,10 @@ func test_AccountTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) // implicit account transition { // create an implicit account by requesting faucet funds - wallet, implicitAccountOutputData := e.dockerFramework.CreateImplicitAccount(ctx) + wallet, implicitAccountOutputData := e.DockerTestFramework().CreateImplicitAccount(ctx) // prepare account transition block - accountData, _, blk := e.dockerFramework.CreateAccountBlockFromImplicit(wallet, implicitAccountOutputData, wallet.GetNewBlockIssuanceResponse()) + accountData, _, blk := e.DockerTestFramework().CreateAccountBlockFromImplicit(wallet, implicitAccountOutputData, wallet.GetNewBlockIssuanceResponse()) expectedBlocks := map[string]*iotago.Block{ blk.MustID().ToHex(): blk, @@ -292,7 +293,7 @@ func test_AccountTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) } fmt.Println("submitting a block: ", blk.MustID().ToHex()) - e.dockerFramework.SubmitBlock(context.Background(), blk) + e.DockerTestFramework().SubmitBlock(context.Background(), blk) } }() @@ -302,18 +303,18 @@ func test_AccountTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) } } -func test_FoundryTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) { +func test_FoundryTransactionBlocks(t *testing.T, e *dockertestframework.EventAPIDockerTestFramework) { // get event API client ready ctx, cancel := context.WithCancel(context.Background()) eventClt := e.ConnectEventAPIClient(ctx) defer eventClt.Close() { - wallet, account := e.dockerFramework.CreateAccount() - fundsOutputData := e.dockerFramework.RequestFaucetFunds(ctx, wallet, iotago.AddressEd25519) + wallet, account := e.DockerTestFramework().CreateAccount() + fundsOutputData := e.DockerTestFramework().RequestFaucetFunds(ctx, wallet, iotago.AddressEd25519) // prepare foundry output block - foundryId, outputId, blk := e.dockerFramework.CreateFoundryBlockFromInput(wallet, fundsOutputData.ID, 5_000_000, 10_000_000_000) + foundryId, outputId, blk := e.DockerTestFramework().CreateFoundryBlockFromInput(wallet, fundsOutputData.ID, 5_000_000, 10_000_000_000) expectedBlocks := map[string]*iotago.Block{ blk.MustID().ToHex(): blk, } @@ -357,7 +358,7 @@ func test_FoundryTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) } fmt.Println("submitting a block: ", blk.MustID().ToHex()) - e.dockerFramework.SubmitBlock(context.Background(), blk) + e.DockerTestFramework().SubmitBlock(context.Background(), blk) } }() @@ -367,18 +368,18 @@ func test_FoundryTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) } } -func test_NFTTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) { +func test_NFTTransactionBlocks(t *testing.T, e *dockertestframework.EventAPIDockerTestFramework) { // get event API client ready ctx, cancel := context.WithCancel(context.Background()) eventClt := e.ConnectEventAPIClient(ctx) defer eventClt.Close() { - wallet, _ := e.dockerFramework.CreateAccount() - fundsOutputData := e.dockerFramework.RequestFaucetFunds(ctx, wallet, iotago.AddressEd25519) + wallet, _ := e.DockerTestFramework().CreateAccount() + fundsOutputData := e.DockerTestFramework().RequestFaucetFunds(ctx, wallet, iotago.AddressEd25519) // prepare foundry output block - nftId, outputId, blk := e.dockerFramework.CreateNFTBlockFromInput(wallet, fundsOutputData) + nftId, outputId, blk := e.DockerTestFramework().CreateNFTBlockFromInput(wallet, fundsOutputData) expectedBlocks := map[string]*iotago.Block{ blk.MustID().ToHex(): blk, } @@ -420,7 +421,7 @@ func test_NFTTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) { } fmt.Println("submitting a block: ", blk.MustID().ToHex()) - e.dockerFramework.SubmitBlock(context.Background(), blk) + e.DockerTestFramework().SubmitBlock(context.Background(), blk) } }() @@ -430,14 +431,14 @@ func test_NFTTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) { } } -func test_BlockMetadataMatchedCoreAPI(t *testing.T, e *EventAPIDockerTestFramework) { +func test_BlockMetadataMatchedCoreAPI(t *testing.T, e *dockertestframework.EventAPIDockerTestFramework) { // get event API client ready ctx, cancel := context.WithCancel(context.Background()) eventClt := e.ConnectEventAPIClient(ctx) defer eventClt.Close() { - wallet, _ := e.dockerFramework.CreateAccount() + wallet, _ := e.DockerTestFramework().CreateAccount() 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 4a9fc18da..542051b35 100644 --- a/tools/docker-network/tests/mempool_invalid_signatures_test.go +++ b/tools/docker-network/tests/mempool_invalid_signatures_test.go @@ -11,13 +11,14 @@ import ( "github.com/stretchr/testify/require" "github.com/iotaledger/iota-core/pkg/testsuite/mock" + "github.com/iotaledger/iota-core/tools/docker-network/tests/dockertestframework" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/api" ) func Test_MempoolInvalidSignatures(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithRewardsOptions(8, 10, 2, 384), diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go index b5fee342f..c3e824cc6 100644 --- a/tools/docker-network/tests/rewards_test.go +++ b/tools/docker-network/tests/rewards_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/iotaledger/iota-core/pkg/testsuite/mock" + "github.com/iotaledger/iota-core/tools/docker-network/tests/dockertestframework" iotago "github.com/iotaledger/iota.go/v4" ) @@ -21,8 +22,8 @@ import ( // 3. One of the account issues 3 validation blocks per slot, the other account issues 1 validation block per slot until claiming slot is reached. // 4. Claim rewards and check if the mana increased as expected, the account that issued less validation blocks should have less mana. func Test_ValidatorRewards(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithStakingOptions(3, 10, 10), @@ -45,7 +46,7 @@ func Test_ValidatorRewards(t *testing.T) { // cancel the context when the test is done t.Cleanup(cancel) - clt := d.defaultWallet.Client + clt := d.DefaultWallet().Client slotsDuration := clt.CommittedAPI().ProtocolParameters().SlotDurationInSeconds() // create good account @@ -55,14 +56,14 @@ func Test_ValidatorRewards(t *testing.T) { require.NoError(t, err) latestCommitmentSlot := blockIssuance.LatestCommitment.Slot - stakingStartEpoch := d.defaultWallet.StakingStartEpochFromSlot(latestCommitmentSlot) + stakingStartEpoch := d.DefaultWallet().StakingStartEpochFromSlot(latestCommitmentSlot) currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(latestCommitmentSlot) // Set end epoch so the staking feature can be removed as soon as possible. endEpoch := stakingStartEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 // 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, WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) + goodAccountData := d.CreateAccountFromImplicitAccount(goodWallet, goodAccountOutputData, blockIssuance, dockertestframework.WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) initialMana := goodAccountData.Output.StoredMana() issueCandidacyPayloadInBackground(ctx, d, @@ -78,12 +79,12 @@ func Test_ValidatorRewards(t *testing.T) { require.NoError(t, err) latestCommitmentSlot = blockIssuance.LatestCommitment.Slot - stakingStartEpoch = d.defaultWallet.StakingStartEpochFromSlot(latestCommitmentSlot) + stakingStartEpoch = d.DefaultWallet().StakingStartEpochFromSlot(latestCommitmentSlot) currentEpoch = clt.CommittedAPI().TimeProvider().EpochFromSlot(latestCommitmentSlot) endEpoch = stakingStartEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 claimingSlot = clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) - lazyAccountData := d.CreateAccountFromImplicitAccount(lazyWallet, lazyAccountOutputData, blockIssuance, WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) + lazyAccountData := d.CreateAccountFromImplicitAccount(lazyWallet, lazyAccountOutputData, blockIssuance, dockertestframework.WithStakingFeature(100, 1, stakingStartEpoch, endEpoch)) lazyInitialMana := lazyAccountData.Output.StoredMana() issueCandidacyPayloadInBackground(ctx, @@ -131,8 +132,8 @@ func Test_ValidatorRewards(t *testing.T) { // 2. Wait long enough so there's rewards can be claimed. // 3. Claim rewards and check if the mana increased as expected. func Test_DelegatorRewards(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 3), iotago.WithLivenessOptions(10, 10, 2, 4, 5), iotago.WithStakingOptions(3, 10, 10), @@ -189,8 +190,8 @@ func Test_DelegatorRewards(t *testing.T) { // 2. Delay claiming rewards for the delegation and check if the delegated stake is removed from the validator. // 3. Claim rewards and check to destroy the delegation output. func Test_DelayedClaimingRewards(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), iotago.WithStakingOptions(3, 10, 10), @@ -226,7 +227,7 @@ func Test_DelayedClaimingRewards(t *testing.T) { currentSlot := delegatorWallet.CurrentSlot() apiForSlot := clt.APIForSlot(currentSlot) latestCommitmentSlot := delegatorWallet.GetNewBlockIssuanceResponse().LatestCommitment.Slot - delegationEndEpoch := getDelegationEndEpoch(apiForSlot, currentSlot, latestCommitmentSlot) + delegationEndEpoch := dockertestframework.GetDelegationEndEpoch(apiForSlot, currentSlot, latestCommitmentSlot) delegationOutputData = d.DelayedClaimingTransition(ctx, delegatorWallet, delegationOutputData) d.AwaitCommitment(delegationOutputData.ID.CreationSlot()) @@ -265,7 +266,7 @@ func Test_DelayedClaimingRewards(t *testing.T) { } } -func issueCandidacyPayloadInBackground(ctx context.Context, d *DockerTestFramework, wallet *mock.Wallet, startSlot, endSlot iotago.SlotIndex, slotDuration uint8) { +func issueCandidacyPayloadInBackground(ctx context.Context, d *dockertestframework.DockerTestFramework, wallet *mock.Wallet, startSlot, endSlot iotago.SlotIndex, slotDuration uint8) { go func() { fmt.Println("Issuing candidacy payloads for account", wallet.BlockIssuer.AccountData.ID, "in the background...") defer fmt.Println("Issuing candidacy payloads for account", wallet.BlockIssuer.AccountData.ID, "in the background......done") diff --git a/tools/docker-network/tests/sync_snapshot_test.go b/tools/docker-network/tests/sync_snapshot_test.go index 88f92cfcd..912cc29b5 100644 --- a/tools/docker-network/tests/sync_snapshot_test.go +++ b/tools/docker-network/tests/sync_snapshot_test.go @@ -8,12 +8,13 @@ import ( "github.com/stretchr/testify/require" + "github.com/iotaledger/iota-core/tools/docker-network/tests/dockertestframework" iotago "github.com/iotaledger/iota.go/v4" ) func Test_SyncFromSnapshot(t *testing.T) { - d := NewDockerTestFramework(t, - WithProtocolParametersOptions( + d := dockertestframework.NewDockerTestFramework(t, + dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(0, time.Now().Unix(), 4, 4), iotago.WithLivenessOptions(3, 4, 2, 4, 5), iotago.WithCongestionControlOptions(1, 1, 1, 400_000, 250_000, 50_000_000, 1000, 100), From ab19b4d345d536481b4182fae87ed962d8fd5564 Mon Sep 17 00:00:00 2001 From: muXxer Date: Mon, 6 May 2024 12:51:13 +0200 Subject: [PATCH 13/16] Split up `DockerTestFramework` into several files --- .../tests/accounttransition_test.go | 6 +- tools/docker-network/tests/api_core_test.go | 13 +- .../tests/committeerotation_test.go | 8 +- .../tests/dockertestframework/accounts.go | 177 +++++ .../tests/dockertestframework/asserts.go | 121 ++++ .../tests/dockertestframework/awaits.go | 146 ++++ .../tests/dockertestframework/blocks.go | 94 +++ .../tests/dockertestframework/clock.go | 20 + .../tests/dockertestframework/faucet.go | 133 ++++ .../tests/dockertestframework/framework.go | 664 +----------------- .../dockertestframework/framework_eventapi.go | 24 +- .../tests/dockertestframework/nodes.go | 198 ++++++ .../tests/dockertestframework/rewards.go | 112 +++ .../tests/dockertestframework/utils.go | 336 --------- .../tests/dockertestframework/validator.go | 45 ++ tools/docker-network/tests/eventapi_test.go | 14 +- .../tests/mempool_invalid_signatures_test.go | 2 +- tools/docker-network/tests/rewards_test.go | 17 +- 18 files changed, 1130 insertions(+), 1000 deletions(-) create mode 100644 tools/docker-network/tests/dockertestframework/accounts.go create mode 100644 tools/docker-network/tests/dockertestframework/asserts.go create mode 100644 tools/docker-network/tests/dockertestframework/awaits.go create mode 100644 tools/docker-network/tests/dockertestframework/blocks.go create mode 100644 tools/docker-network/tests/dockertestframework/clock.go create mode 100644 tools/docker-network/tests/dockertestframework/faucet.go create mode 100644 tools/docker-network/tests/dockertestframework/nodes.go create mode 100644 tools/docker-network/tests/dockertestframework/rewards.go create mode 100644 tools/docker-network/tests/dockertestframework/validator.go 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 { From 516cd6ac0f19add316ca2944879458ed527090db Mon Sep 17 00:00:00 2001 From: muXxer Date: Mon, 6 May 2024 13:20:32 +0200 Subject: [PATCH 14/16] Fix linter errors --- .golangci.yml | 5 ++--- .../engine/congestioncontrol/scheduler/drr/scheduler.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b17b71756..55bcc68f1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -58,7 +58,6 @@ linters: - errchkjson - errname - errorlint - - execinquery #- exhaustive #- exhaustruct - exportloopref @@ -77,7 +76,7 @@ linters: #- gocyclo - godot #- godox - - goerr113 + - err113 - gofmt #- gofumpt - goheader @@ -92,7 +91,7 @@ linters: - importas - inamedparam #- interfacebloat - #- intrange # TODO: re-enable after https://github.com/ckaznocha/intrange v0.1.2 release is merged in golangci-lint + - intrange #- ireturn #- lll - loggercheck diff --git a/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go b/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go index d9dcf8412..a8dd7c28c 100644 --- a/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go +++ b/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go @@ -549,7 +549,7 @@ func (s *Scheduler) removeIssuer(issuerID iotago.AccountID, err error) { return true }) - for i := 0; i < q.readyHeap.Len(); i++ { + for i := range q.readyHeap.Len() { block := q.readyHeap[i].Value block.SetDropped() s.events.BlockDropped.Trigger(block, err) From c6abb59b903fa474b7220aded514271b953e801c Mon Sep 17 00:00:00 2001 From: jkrvivian Date: Mon, 6 May 2024 21:10:45 +0800 Subject: [PATCH 15/16] Decrease StakingUnboundPeriod and endEpoch in Test_ValidatorRewards --- tools/docker-network/tests/rewards_test.go | 28 ++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go index aac4155c1..55950108b 100644 --- a/tools/docker-network/tests/rewards_test.go +++ b/tools/docker-network/tests/rewards_test.go @@ -26,7 +26,7 @@ func Test_ValidatorRewards(t *testing.T) { dockertestframework.WithProtocolParametersOptions( iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), iotago.WithLivenessOptions(10, 10, 2, 4, 8), - iotago.WithStakingOptions(3, 10, 10), + iotago.WithStakingOptions(2, 10, 10), )) defer d.Stop() @@ -57,11 +57,10 @@ func Test_ValidatorRewards(t *testing.T) { latestCommitmentSlot := blockIssuance.LatestCommitment.Slot stakingStartEpoch := d.DefaultWallet().StakingStartEpochFromSlot(latestCommitmentSlot) - currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(latestCommitmentSlot) // Set end epoch so the staking feature can be removed as soon as possible. - endEpoch := stakingStartEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 + endEpoch := stakingStartEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() // The earliest epoch in which we can remove the staking feature and claim rewards. - claimingSlot := clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) + goodClaimingSlot := clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) goodAccountData := d.CreateAccountFromImplicitAccount(goodWallet, goodAccountOutputData, @@ -74,7 +73,7 @@ func Test_ValidatorRewards(t *testing.T) { d, goodWallet, clt.CommittedAPI().TimeProvider().CurrentSlot(), - claimingSlot, + goodClaimingSlot, slotsDuration) // create lazy account @@ -85,9 +84,8 @@ func Test_ValidatorRewards(t *testing.T) { latestCommitmentSlot = blockIssuance.LatestCommitment.Slot stakingStartEpoch = d.DefaultWallet().StakingStartEpochFromSlot(latestCommitmentSlot) - currentEpoch = clt.CommittedAPI().TimeProvider().EpochFromSlot(latestCommitmentSlot) - endEpoch = stakingStartEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 - claimingSlot = clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) + endEpoch = stakingStartEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + lazyClaimingSlot := clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) lazyAccountData := d.CreateAccountFromImplicitAccount(lazyWallet, lazyAccountOutputData, @@ -100,28 +98,28 @@ func Test_ValidatorRewards(t *testing.T) { d, lazyWallet, clt.CommittedAPI().TimeProvider().CurrentSlot(), - claimingSlot, + lazyClaimingSlot, slotsDuration) // make sure the account is in the committee, so it can issue validation blocks goodAccountAddrBech32 := goodAccountData.Address.Bech32(clt.CommittedAPI().ProtocolParameters().Bech32HRP()) lazyAccountAddrBech32 := lazyAccountData.Address.Bech32(clt.CommittedAPI().ProtocolParameters().Bech32HRP()) - d.AssertCommittee(currentEpoch+2, append(d.AccountsFromNodes(d.Nodes("V1", "V3", "V2", "V4")...), goodAccountAddrBech32, lazyAccountAddrBech32)) + d.AssertCommittee(stakingStartEpoch+1, append(d.AccountsFromNodes(d.Nodes("V1", "V3", "V2", "V4")...), goodAccountAddrBech32, lazyAccountAddrBech32)) // issue validation blocks to have performance currentSlot := clt.CommittedAPI().TimeProvider().CurrentSlot() - slotToWait := claimingSlot - currentSlot + slotToWait := lazyClaimingSlot - currentSlot secToWait := time.Duration(slotToWait) * time.Duration(slotsDuration) * time.Second - fmt.Println("Wait for ", secToWait, "until expected slot: ", claimingSlot) + fmt.Println("Issue validation blocks, wait for ", secToWait, "until expected slot: ", lazyClaimingSlot) var wg sync.WaitGroup - issueValidationBlockInBackground(ctx, &wg, goodWallet, currentSlot, claimingSlot, 5) - issueValidationBlockInBackground(ctx, &wg, lazyWallet, currentSlot, claimingSlot, 1) + issueValidationBlockInBackground(ctx, &wg, goodWallet, currentSlot, goodClaimingSlot, 5) + issueValidationBlockInBackground(ctx, &wg, lazyWallet, currentSlot, lazyClaimingSlot, 1) wg.Wait() // claim rewards that put to the account output - d.AwaitCommitment(claimingSlot) + d.AwaitCommitment(lazyClaimingSlot) d.ClaimRewardsForValidator(ctx, goodWallet) d.ClaimRewardsForValidator(ctx, lazyWallet) From b1afe518ea18764df3996f980299730ae83ff36d Mon Sep 17 00:00:00 2001 From: muXxer Date: Tue, 7 May 2024 10:33:16 +0200 Subject: [PATCH 16/16] Fix BlockIssuerKeys size in `String()` --- pkg/model/account_diff.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/model/account_diff.go b/pkg/model/account_diff.go index 8cbba56ee..c8ccb779d 100644 --- a/pkg/model/account_diff.go +++ b/pkg/model/account_diff.go @@ -84,8 +84,8 @@ func (d *AccountDiff) String() string { builder.AddField(stringify.NewStructField("PreviousExpirySlot", uint32(d.PreviousExpirySlot))) builder.AddField(stringify.NewStructField("NewOutputID", d.NewOutputID)) builder.AddField(stringify.NewStructField("PreviousOutputID", d.PreviousOutputID)) - builder.AddField(stringify.NewStructField("BlockIssuerKeysAdded", func() string { return strconv.Itoa(d.BlockIssuerKeysAdded.Size()) }())) - builder.AddField(stringify.NewStructField("BlockIssuerKeysRemoved", func() string { return strconv.Itoa(d.BlockIssuerKeysRemoved.Size()) }())) + builder.AddField(stringify.NewStructField("BlockIssuerKeysAdded", func() string { return strconv.Itoa(len(d.BlockIssuerKeysAdded)) }())) + builder.AddField(stringify.NewStructField("BlockIssuerKeysRemoved", func() string { return strconv.Itoa(len(d.BlockIssuerKeysRemoved)) }())) builder.AddField(stringify.NewStructField("ValidatorStakeChange", d.ValidatorStakeChange)) builder.AddField(stringify.NewStructField("DelegationStakeChange", d.DelegationStakeChange)) builder.AddField(stringify.NewStructField("FixedCostChange", d.FixedCostChange))