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/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/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)) 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) 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..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" @@ -136,34 +137,43 @@ 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) - 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 +195,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] @@ -394,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 { @@ -426,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), @@ -437,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/.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 diff --git a/tools/docker-network/tests/accounttransition_test.go b/tools/docker-network/tests/accounttransition_test.go index e6e002a3a..54f72324b 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), @@ -40,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.go b/tools/docker-network/tests/api_core.go deleted file mode 100644 index 7aa93897e..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 c3290ad6e..838f5d5b4 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.CreateAccountFromFaucet() + 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,11 +278,16 @@ 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() { + cancel() + }) // 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()...) @@ -58,8 +297,18 @@ 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(), + 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 d.AwaitCommitment(clt.CommittedAPI().TimeProvider().EpochEnd(currentEpoch)) blkID := d.IssueCandidacyPayloadFromAccount(wallet) @@ -81,8 +330,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), @@ -101,7 +350,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) @@ -416,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) }) } @@ -426,8 +675,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), @@ -456,7 +705,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) }, }, @@ -466,7 +715,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) }, }, @@ -476,7 +725,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) }, }, @@ -486,7 +735,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) }, }, @@ -496,7 +745,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) }, }, @@ -507,7 +756,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) }, }, @@ -519,7 +768,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) }, }, @@ -529,7 +778,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) }, }, @@ -540,7 +789,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) }, }, @@ -550,7 +799,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) }, }, @@ -561,7 +810,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) }, }, @@ -574,7 +823,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)) }, }, { @@ -583,7 +832,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) }, }, @@ -594,7 +843,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) }, }, @@ -605,7 +854,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) }, }, @@ -616,7 +865,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) }, }, @@ -625,7 +874,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) }, }, @@ -635,7 +884,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) }, }, @@ -643,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/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 7e689766e..6e117dcc4 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" @@ -10,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" ) @@ -21,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), @@ -43,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 @@ -66,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), @@ -90,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 @@ -135,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), @@ -155,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) @@ -185,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), @@ -205,9 +207,20 @@ 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(), + dockertestframework.WithStakingFeature(100, 1, 0), + ) + + d.AssertValidatorExists(accountData.Address) } // Test_Delegation tests if committee changed due to delegation. @@ -217,8 +230,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), @@ -242,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/dockerframework.go b/tools/docker-network/tests/dockerframework.go deleted file mode 100644 index 7b9543f94..000000000 --- a/tools/docker-network/tests/dockerframework.go +++ /dev/null @@ -1,879 +0,0 @@ -//go:build dockertests - -package tests - -import ( - "context" - "fmt" - "log" - "math/rand" - "net/http" - "os" - "os/exec" - "strings" - "testing" - "time" - - "github.com/mr-tron/base58" - "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 { - genesisSeed, err := base58.Decode("7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih") - if err != nil { - log.Fatal(ierrors.Wrap(err, "failed to decode base58 seed")) - } - keyManager, err := wallet.NewKeyManager(genesisSeed[:], wallet.DefaultIOTAPath) - if err != nil { - log.Fatal(ierrors.Wrap(err, "failed to create KeyManager from seed")) - } - - return keyManager - } -) - -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 - fakeTesting *testing.T - - nodes map[string]*Node - clients map[string]mock.Client - nodesLock syncutils.RWMutex - - snapshotPath string - logDirectoryPath string - - defaultWallet *mock.Wallet - - optsProtocolParameterOptions []options.Option[iotago.V3ProtocolParameters] - optsSnapshotOptions []options.Option[snapshotcreator.Options] - optsWaitForSync time.Duration - optsWaitFor time.Duration - optsTick time.Duration - optsFaucetURL string -} - -func NewDockerTestFramework(t *testing.T, opts ...options.Option[DockerTestFramework]) *DockerTestFramework { - return options.Apply(&DockerTestFramework{ - Testing: t, - fakeTesting: &testing.T{}, - nodes: make(map[string]*Node), - clients: make(map[string]mock.Client), - optsWaitForSync: 5 * time.Minute, - optsWaitFor: 2 * time.Minute, - optsTick: 5 * time.Second, - optsFaucetURL: "http://localhost:8088", - }, opts, func(d *DockerTestFramework) { - d.optsProtocolParameterOptions = append(DefaultProtocolParametersOptions, d.optsProtocolParameterOptions...) - protocolParams := iotago.NewV3SnapshotProtocolParameters(d.optsProtocolParameterOptions...) - testAPI := iotago.V3API(protocolParams) - - d.logDirectoryPath = createLogDirectory(t.Name()) - d.snapshotPath = snapshotFilePath - d.optsSnapshotOptions = append(DefaultAccountOptions(protocolParams), - []options.Option[snapshotcreator.Options]{ - snapshotcreator.WithDatabaseVersion(protocol.DatabaseVersion), - snapshotcreator.WithFilePath(d.snapshotPath), - snapshotcreator.WithProtocolParameters(testAPI.ProtocolParameters()), - snapshotcreator.WithRootBlocks(map[iotago.BlockID]iotago.CommitmentID{ - testAPI.ProtocolParameters().GenesisBlockID(): iotago.NewEmptyCommitment(testAPI).MustID(), - }), - snapshotcreator.WithGenesisKeyManager(keyManager()), - }...) - - err := snapshotcreator.CreateSnapshot(d.optsSnapshotOptions...) - if err != nil { - panic(fmt.Sprintf("failed to create snapshot: %s", err)) - } - }) -} - -func (d *DockerTestFramework) DockerComposeUp(detach ...bool) error { - cmd := exec.Command("docker", "compose", "up") - - if len(detach) > 0 && detach[0] { - cmd = exec.Command("docker", "compose", "up", "-d") - } - - cmd.Env = os.Environ() - for _, node := range d.Nodes() { - cmd.Env = append(cmd.Env, fmt.Sprintf("ISSUE_CANDIDACY_PAYLOAD_%s=%t", node.Name, node.IssueCandidacyPayload)) - if node.DatabasePath != "" { - fmt.Println("Setting Database Path for", node.Name, " to", node.DatabasePath) - cmd.Env = append(cmd.Env, fmt.Sprintf("DB_PATH_%s=%s", node.Name, node.DatabasePath)) - } - if node.SnapshotPath != "" { - fmt.Println("Setting snapshot path for", node.Name, " to", node.SnapshotPath) - cmd.Env = append(cmd.Env, fmt.Sprintf("SNAPSHOT_PATH_%s=%s", node.Name, node.SnapshotPath)) - } - } - - var out strings.Builder - cmd.Stderr = &out - err := cmd.Run() - if err != nil { - fmt.Println("Docker compose up failed with error:", err, ":", out.String()) - } - - return err -} - -func (d *DockerTestFramework) Run() error { - ch := make(chan error) - stopCh := make(chan struct{}) - defer close(ch) - defer close(stopCh) - - go func() { - err := d.DockerComposeUp() - - // make sure that the channel is not already closed - select { - case <-stopCh: - return - default: - } - - ch <- err - }() - - timer := time.NewTimer(d.optsWaitForSync) - defer timer.Stop() - - ticker := time.NewTicker(d.optsTick) - defer ticker.Stop() - -loop: - for { - select { - case <-timer.C: - require.FailNow(d.Testing, "Docker network did not start in time") - case err := <-ch: - if err != nil { - require.FailNow(d.Testing, "failed to start Docker network", err) - } - case <-ticker.C: - fmt.Println("Waiting for nodes to become available...") - if d.waitForNodesAndGetClients() == nil { - break loop - } - } - } - - d.GetContainersConfigs() - - // make sure all nodes are up then we can start dumping logs - d.DumpContainerLogsToFiles() - - return nil -} - -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.WaitUntilSync() - - // 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) WaitUntilSync() { - fmt.Println("Wait until the nodes are synced...") - defer fmt.Println("Wait until the nodes are synced......done") - - d.Eventually(func() error { - for _, node := range d.Nodes() { - for { - synced, err := d.Client(node.Name).Health(context.TODO()) - if err != nil { - return err - } - - if synced { - 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.WaitUntilSync() -} - -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() -} - -// CreateAccountBlockFromInput consumes the given output, which should be either an basic output with implicit address, then build block with the given account output options. -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()) - opts = append(opts, mock.WithBlockIssuerFeature( - iotago.NewBlockIssuerKeys(implicitBlockIssuerKey), - iotago.MaxSlotIndex, - )) - signedTx := newWallet.TransitionImplicitAccountToAccountOutput("", []*mock.OutputData{implicitAccountOutputData}, opts...) - - // The account transition block should be issued by the implicit account block issuer key. - block, err := newWallet.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, newWallet, 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(opts ...options.Option[builder.AccountOutputBuilder]) (*mock.Wallet, *mock.AccountData) { - ctx := context.TODO() - accountData, newWallet, signedTx, block := d.CreateAccountBlock(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 - 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) -} - -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") - - _ = exec.Command("docker", "compose", "down").Run() - _ = exec.Command("rm", d.snapshotPath).Run() //nolint:gosec -} - -func (d *DockerTestFramework) StopContainer(containerName ...string) error { - fmt.Println("Stop validator", containerName, "......") - - args := append([]string{"stop"}, containerName...) - - return exec.Command("docker", args...).Run() -} - -func (d *DockerTestFramework) RestartContainer(containerName ...string) error { - fmt.Println("Restart validator", containerName, "......") - - args := append([]string{"restart"}, containerName...) - - return exec.Command("docker", args...).Run() -} - -func (d *DockerTestFramework) DumpContainerLogsToFiles() { - // get container names - cmd := "docker compose ps | awk '{print $1}' | tail -n +2" - containerNamesBytes, err := exec.Command("bash", "-c", cmd).Output() - require.NoError(d.Testing, err) - - // dump logs to files - fmt.Println("Dump container logs to files...") - containerNames := strings.Split(string(containerNamesBytes), "\n") - - for _, name := range containerNames { - if name == "" { - continue - } - - d.DumpContainerLog(name) - } -} - -func (d *DockerTestFramework) DumpContainerLog(name string, optLogNameExtension ...string) { - var filePath string - if len(optLogNameExtension) > 0 { - filePath = fmt.Sprintf("%s/%s-%s.log", d.logDirectoryPath, name, optLogNameExtension[0]) - } else { - filePath = fmt.Sprintf("%s/%s.log", d.logDirectoryPath, name) - } - - // dump logs to file if the file does not exist, which means the container is just started. - // logs should exist for the already running containers. - _, err := os.Stat(filePath) - if os.IsNotExist(err) { - logCmd := fmt.Sprintf("docker logs -f %s > %s 2>&1 &", name, filePath) - err := exec.Command("bash", "-c", logCmd).Run() - require.NoError(d.Testing, err) - } -} - -func (d *DockerTestFramework) GetContainersConfigs() { - // get container configs - nodes := d.Nodes() - - 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() - require.NoError(d.Testing, err) - - configs := string(containerConfigsBytes) - // remove "[" and "]" - configs = configs[1 : len(configs)-2] - - // get validator private key - cmd = fmt.Sprintf("docker inspect --format='{{.Config.Env}}' %s", node.ContainerName) - envBytes, err := exec.Command("bash", "-c", cmd).Output() - require.NoError(d.Testing, err) - - envs := string(envBytes) - envs = strings.Split(envs[1:len(envs)-2], " ")[0] - - node.ContainerConfigs = configs - node.PrivateKey = envs - d.nodes[node.Name] = node - } -} - -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/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 new file mode 100644 index 000000000..a6b203b77 --- /dev/null +++ b/tools/docker-network/tests/dockertestframework/framework.go @@ -0,0 +1,304 @@ +//go:build dockertests + +package dockertestframework + +import ( + "fmt" + "log" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/mr-tron/base58" + "github.com/stretchr/testify/require" + + "github.com/iotaledger/hive.go/ierrors" + "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/wallet" +) + +var ( + // need to build snapshotfile in tools/docker-network. + snapshotFilePath = "../docker-network-snapshots/snapshot.bin" + + keyManager = func() *wallet.KeyManager { + genesisSeed, err := base58.Decode("7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih") + if err != nil { + log.Fatal(ierrors.Wrap(err, "failed to decode base58 seed")) + } + keyManager, err := wallet.NewKeyManager(genesisSeed[:], wallet.DefaultIOTAPath) + if err != nil { + log.Fatal(ierrors.Wrap(err, "failed to create KeyManager from seed")) + } + + return keyManager + } +) + +type DockerTestFramework struct { + Testing *testing.T + // we use the fake testing so that actual tests don't fail if an assertion fails + fakeTesting *testing.T + + nodes map[string]*Node + clients map[string]mock.Client + nodesLock syncutils.RWMutex + + snapshotPath string + logDirectoryPath string + + defaultWallet *mock.Wallet + + optsProtocolParameterOptions []options.Option[iotago.V3ProtocolParameters] + optsSnapshotOptions []options.Option[snapshotcreator.Options] + optsWaitForSync time.Duration + optsWaitFor time.Duration + optsTick time.Duration + optsFaucetURL string +} + +func NewDockerTestFramework(t *testing.T, opts ...options.Option[DockerTestFramework]) *DockerTestFramework { + return options.Apply(&DockerTestFramework{ + Testing: t, + fakeTesting: &testing.T{}, + nodes: make(map[string]*Node), + clients: make(map[string]mock.Client), + optsWaitForSync: 5 * time.Minute, + optsWaitFor: 2 * time.Minute, + optsTick: 5 * time.Second, + optsFaucetURL: "http://localhost:8088", + }, opts, func(d *DockerTestFramework) { + d.optsProtocolParameterOptions = append(DefaultProtocolParametersOptions, d.optsProtocolParameterOptions...) + protocolParams := iotago.NewV3SnapshotProtocolParameters(d.optsProtocolParameterOptions...) + testAPI := iotago.V3API(protocolParams) + + d.logDirectoryPath = CreateLogDirectory(t.Name()) + d.snapshotPath = snapshotFilePath + d.optsSnapshotOptions = append(DefaultAccountOptions(protocolParams), + []options.Option[snapshotcreator.Options]{ + snapshotcreator.WithDatabaseVersion(protocol.DatabaseVersion), + snapshotcreator.WithFilePath(d.snapshotPath), + snapshotcreator.WithProtocolParameters(testAPI.ProtocolParameters()), + snapshotcreator.WithRootBlocks(map[iotago.BlockID]iotago.CommitmentID{ + testAPI.ProtocolParameters().GenesisBlockID(): iotago.NewEmptyCommitment(testAPI).MustID(), + }), + snapshotcreator.WithGenesisKeyManager(keyManager()), + }...) + + err := snapshotcreator.CreateSnapshot(d.optsSnapshotOptions...) + if err != nil { + panic(fmt.Sprintf("failed to create snapshot: %s", err)) + } + }) +} + +func (d *DockerTestFramework) DockerComposeUp(detach ...bool) error { + cmd := exec.Command("docker", "compose", "up") + + if len(detach) > 0 && detach[0] { + cmd = exec.Command("docker", "compose", "up", "-d") + } + + cmd.Env = os.Environ() + for _, node := range d.Nodes() { + cmd.Env = append(cmd.Env, fmt.Sprintf("ISSUE_CANDIDACY_PAYLOAD_%s=%t", node.Name, node.IssueCandidacyPayload)) + if node.DatabasePath != "" { + fmt.Println("Setting Database Path for", node.Name, " to", node.DatabasePath) + cmd.Env = append(cmd.Env, fmt.Sprintf("DB_PATH_%s=%s", node.Name, node.DatabasePath)) + } + if node.SnapshotPath != "" { + fmt.Println("Setting snapshot path for", node.Name, " to", node.SnapshotPath) + cmd.Env = append(cmd.Env, fmt.Sprintf("SNAPSHOT_PATH_%s=%s", node.Name, node.SnapshotPath)) + } + } + + var out strings.Builder + cmd.Stderr = &out + err := cmd.Run() + if err != nil { + fmt.Println("Docker compose up failed with error:", err, ":", out.String()) + } + + return err +} + +func (d *DockerTestFramework) Run() error { + ch := make(chan error) + stopCh := make(chan struct{}) + defer close(ch) + defer close(stopCh) + + go func() { + err := d.DockerComposeUp() + + // make sure that the channel is not already closed + select { + case <-stopCh: + return + default: + } + + ch <- err + }() + + timer := time.NewTimer(d.optsWaitForSync) + defer timer.Stop() + + ticker := time.NewTicker(d.optsTick) + defer ticker.Stop() + +loop: + for { + select { + case <-timer.C: + require.FailNow(d.Testing, "Docker network did not start in time") + case err := <-ch: + if err != nil { + require.FailNow(d.Testing, "failed to start Docker network", err) + } + case <-ticker.C: + fmt.Println("Waiting for nodes to become available...") + if d.waitForNodesAndGetClients() == nil { + break loop + } + } + } + + d.GetContainersConfigs() + + // make sure all nodes are up then we can start dumping logs + d.DumpContainerLogsToFiles() + + return nil +} + +func (d *DockerTestFramework) Stop() { + fmt.Println("Stop the network...") + defer fmt.Println("Stop the network.....done") + + _ = exec.Command("docker", "compose", "down").Run() + _ = exec.Command("rm", d.snapshotPath).Run() //nolint:gosec +} + +func (d *DockerTestFramework) StopContainer(containerName ...string) error { + fmt.Println("Stop validator", containerName, "......") + + args := append([]string{"stop"}, containerName...) + + return exec.Command("docker", args...).Run() +} + +func (d *DockerTestFramework) RestartContainer(containerName ...string) error { + fmt.Println("Restart validator", containerName, "......") + + args := append([]string{"restart"}, containerName...) + + return exec.Command("docker", args...).Run() +} + +func (d *DockerTestFramework) DumpContainerLogsToFiles() { + // get container names + cmd := "docker compose ps | awk '{print $1}' | tail -n +2" + containerNamesBytes, err := exec.Command("bash", "-c", cmd).Output() + require.NoError(d.Testing, err) + + // dump logs to files + fmt.Println("Dump container logs to files...") + containerNames := strings.Split(string(containerNamesBytes), "\n") + + for _, name := range containerNames { + if name == "" { + continue + } + + d.DumpContainerLog(name) + } +} + +func (d *DockerTestFramework) DumpContainerLog(name string, optLogNameExtension ...string) { + var filePath string + if len(optLogNameExtension) > 0 { + filePath = fmt.Sprintf("%s/%s-%s.log", d.logDirectoryPath, name, optLogNameExtension[0]) + } else { + filePath = fmt.Sprintf("%s/%s.log", d.logDirectoryPath, name) + } + + // dump logs to file if the file does not exist, which means the container is just started. + // logs should exist for the already running containers. + _, err := os.Stat(filePath) + if os.IsNotExist(err) { + logCmd := fmt.Sprintf("docker logs -f %s > %s 2>&1 &", name, filePath) + err := exec.Command("bash", "-c", logCmd).Run() + require.NoError(d.Testing, err) + } +} + +func (d *DockerTestFramework) GetContainersConfigs() { + // get container configs + nodes := d.Nodes() + + 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() + require.NoError(d.Testing, err) + + configs := string(containerConfigsBytes) + // remove "[" and "]" + configs = configs[1 : len(configs)-2] + + // get validator private key + cmd = fmt.Sprintf("docker inspect --format='{{.Config.Env}}' %s", node.ContainerName) + envBytes, err := exec.Command("bash", "-c", cmd).Output() + require.NoError(d.Testing, err) + + envs := string(envBytes) + envs = strings.Split(envs[1:len(envs)-2], " ")[0] + + node.ContainerConfigs = configs + node.PrivateKey = envs + d.nodes[node.Name] = node + } +} + +func (d *DockerTestFramework) DefaultWallet() *mock.Wallet { + return d.defaultWallet +} + +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/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..2652f84c3 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" @@ -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 @@ -43,6 +55,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) @@ -614,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/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/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 new file mode 100644 index 000000000..f96154048 --- /dev/null +++ b/tools/docker-network/tests/dockertestframework/utils.go @@ -0,0 +1,106 @@ +//go:build dockertests + +package dockertestframework + +import ( + "fmt" + "os" + "regexp" + "strconv" + "time" + + "github.com/stretchr/testify/require" + + "github.com/iotaledger/hive.go/ierrors" +) + +// 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) + + timer := time.NewTimer(deadline) + defer timer.Stop() + + ticker := time.NewTicker(tick) + defer ticker.Stop() + + var lastErr error + for tick := ticker.C; ; { + select { + case <-timer.C: + require.FailNow(d.Testing, "condition never satisfied", lastErr) + case <-tick: + tick = nil + go func() { ch <- condition() }() + case lastErr = <-ch: + // The condition is satisfied, we can exit. + if lastErr == nil { + return + } + tick = ticker.C + } + } +} + +// 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 CreateLogDirectory(testName string) string { + // make sure logs/ exists + err := os.Mkdir("logs", 0755) + if err != nil { + if !os.IsExist(err) { + panic(err) + } + } + + // create directory for this run + timestamp := time.Now().Format("20060102_150405") + dir := fmt.Sprintf("logs/%s-%s", timestamp, testName) + err = os.Mkdir(dir, 0755) + if err != nil { + if !os.IsExist(err) { + panic(err) + } + } + + return dir +} + +func IsStatusCode(err error, status int) bool { + if err == nil { + return false + } + code, err := ExtractStatusCode(err.Error()) + if err != nil { + return false + } + + return code == status +} + +func ExtractStatusCode(errorMessage string) (int, error) { + re := regexp.MustCompile(`code=(\d+)`) + matches := re.FindStringSubmatch(errorMessage) + if len(matches) != 2 { + return 0, ierrors.Errorf("unable to extract status code from error message") + } + + statusCode, err := strconv.Atoi(matches[1]) + if err != nil { + return 0, err + } + + return statusCode, nil +} 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 1a337943a..7e2eb378c 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().CreateAccountFromFaucet() // 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 } @@ -156,8 +157,13 @@ func test_BasicTaggedDataBlocks(t *testing.T, e *EventAPIDockerTestFramework) { // issue blocks go func() { for _, blk := range expectedBlocks { - fmt.Println("submitting a block") - e.dockerFramework.SubmitBlock(context.Background(), blk) + if ctx.Err() != nil { + // context is canceled + return + } + + fmt.Println("submitting a block: ", blk.MustID().ToHex()) + e.DockerTestFramework().SubmitBlock(context.Background(), blk) } }() @@ -166,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().CreateAccountFromFaucet() + 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, } @@ -213,8 +219,13 @@ 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) + e.DockerTestFramework().SubmitBlock(context.Background(), blk) } }() @@ -223,15 +234,22 @@ 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) + 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.DockerTestFramework().CreateImplicitAccount(ctx) + + // prepare account transition block + accountData, _, blk := e.DockerTestFramework().TransitionImplicitAccountToAccountOutputBlock(wallet, implicitAccountOutputData, wallet.GetNewBlockIssuanceResponse()) + expectedBlocks := map[string]*iotago.Block{ blk.MustID().ToHex(): blk, } @@ -269,8 +287,13 @@ 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) + e.DockerTestFramework().SubmitBlock(context.Background(), blk) } }() @@ -280,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().CreateAccountFromFaucet() + 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, } @@ -329,8 +352,13 @@ 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) + e.DockerTestFramework().SubmitBlock(context.Background(), blk) } }() @@ -340,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().CreateAccountFromFaucet() + fundsOutputData := e.DockerTestFramework().RequestFaucetFunds(ctx, wallet, iotago.AddressEd25519) - // prepare foundry output block - nftId, outputId, blk := e.dockerFramework.CreateNFTBlockFromInput(wallet, fundsOutputData) + // prepare NFT output block + nftId, outputId, blk := e.DockerTestFramework().CreateNFTBlockFromInput(wallet, fundsOutputData) expectedBlocks := map[string]*iotago.Block{ blk.MustID().ToHex(): blk, } @@ -387,7 +415,13 @@ func test_NFTTransactionBlocks(t *testing.T, e *EventAPIDockerTestFramework) { // issue blocks go func() { for _, blk := range expectedBlocks { - e.dockerFramework.SubmitBlock(context.Background(), blk) + if ctx.Err() != nil { + // context is canceled + return + } + + fmt.Println("submitting a block: ", blk.MustID().ToHex()) + e.DockerTestFramework().SubmitBlock(context.Background(), blk) } }() @@ -397,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().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 4a9fc18da..feee13e79 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), @@ -36,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 35ab84c60..55950108b 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,11 +22,11 @@ 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), + iotago.WithStakingOptions(2, 10, 10), )) defer d.Stop() @@ -40,48 +41,85 @@ func Test_ValidatorRewards(t *testing.T) { d.WaitUntilNetworkReady() - ctx := context.Background() - clt := d.defaultWallet.Client - status := d.NodeStatus("V1") - currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(status.LatestAcceptedBlockSlot) + ctx, cancel := context.WithCancel(context.Background()) + + // cancel the context when the test is done + 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) // Set end epoch so the staking feature can be removed as soon as possible. - endEpoch := currentEpoch + 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, + blockIssuance, + dockertestframework.WithStakingFeature(100, 1, stakingStartEpoch, endEpoch), + ) - // 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(), + goodClaimingSlot, slotsDuration) - lazyWallet, lazyAccountData := d.CreateAccount(WithStakingFeature(100, 1, currentEpoch, 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) + endEpoch = stakingStartEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + lazyClaimingSlot := clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) + + lazyAccountData := d.CreateAccountFromImplicitAccount(lazyWallet, + lazyAccountOutputData, + blockIssuance, + dockertestframework.WithStakingFeature(100, 1, stakingStartEpoch, endEpoch), + ) + lazyInitialMana := lazyAccountData.Output.StoredMana() - issueCandidacyPayloadInBackground(d, lazyWallet, clt.CommittedAPI().TimeProvider().CurrentSlot(), claimingSlot, + issueCandidacyPayloadInBackground(ctx, + d, + lazyWallet, + clt.CommittedAPI().TimeProvider().CurrentSlot(), + 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 - 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 := lazyClaimingSlot - currentSlot + secToWait := time.Duration(slotToWait) * time.Duration(slotsDuration) * time.Second + fmt.Println("Issue validation blocks, wait for ", secToWait, "until expected slot: ", lazyClaimingSlot) - var wg sync.WaitGroup - issueValidationBlockInBackground(&wg, goodWallet, currentSlot, claimingSlot, 5) - issueValidationBlockInBackground(&wg, lazyWallet, currentSlot, claimingSlot, 1) + var wg sync.WaitGroup + issueValidationBlockInBackground(ctx, &wg, goodWallet, currentSlot, goodClaimingSlot, 5) + issueValidationBlockInBackground(ctx, &wg, lazyWallet, currentSlot, lazyClaimingSlot, 1) - wg.Wait() - } + wg.Wait() // claim rewards that put to the account output - d.AwaitCommitment(claimingSlot) + d.AwaitCommitment(lazyClaimingSlot) d.ClaimRewardsForValidator(ctx, goodWallet) d.ClaimRewardsForValidator(ctx, lazyWallet) @@ -101,8 +139,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), @@ -121,7 +159,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 @@ -159,8 +197,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), @@ -179,7 +217,7 @@ func Test_DelayedClaimingRewards(t *testing.T) { d.WaitUntilNetworkReady() ctx := context.Background() - delegatorWallet, _ := d.CreateAccount() + delegatorWallet, _ := d.CreateAccountFromFaucet() clt := delegatorWallet.Client { @@ -196,7 +234,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()) @@ -235,19 +273,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.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 +301,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 +313,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) } 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), diff --git a/tools/docker-network/tests/utils.go b/tools/docker-network/tests/utils.go deleted file mode 100644 index ab4bd38eb..000000000 --- a/tools/docker-network/tests/utils.go +++ /dev/null @@ -1,418 +0,0 @@ -//go:build dockertests - -package tests - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "regexp" - "sort" - "strconv" - "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) - } -} - -// 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) { - 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) - defer ticker.Stop() - - var lastErr error - for tick := ticker.C; ; { - select { - case <-timer.C: - require.FailNow(d.Testing, "condition never satisfied", lastErr) - case <-tick: - tick = nil - go func() { ch <- condition() }() - case lastErr = <-ch: - // The condition is satisfied, we can exit. - if lastErr == nil { - return - } - tick = ticker.C - } - } -} - -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: %d", txID.ToHex(), resp.TransactionState.String(), resp.TransactionFailureReason) - }) -} - -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 { - 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 %T, got %s instead, failure details: %s", txID, expectedReason, resp.TransactionState, resp.TransactionFailureDetails) - } - }) -} - -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() - - if targetSlot <= latestCommittedSlot { - return - } - - time.Sleep(10 * 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 createLogDirectory(testName string) string { - // make sure logs/ exists - err := os.Mkdir("logs", 0755) - if err != nil { - if !os.IsExist(err) { - panic(err) - } - } - - // create directory for this run - timestamp := time.Now().Format("20060102_150405") - dir := fmt.Sprintf("logs/%s-%s", timestamp, testName) - err = os.Mkdir(dir, 0755) - if err != nil { - if !os.IsExist(err) { - panic(err) - } - } - - 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 { - futureBoundedSlotIndex := latestCommitmentSlot + api.ProtocolParameters().MinCommittableAge() - futureBoundedEpochIndex := api.TimeProvider().EpochFromSlot(futureBoundedSlotIndex) - - registrationSlot := api.TimeProvider().EpochEnd(api.TimeProvider().EpochFromSlot(slot)) - api.ProtocolParameters().EpochNearingThreshold() - - if futureBoundedSlotIndex <= registrationSlot { - return futureBoundedEpochIndex - } - - return futureBoundedEpochIndex + 1 -} - -func isStatusCode(err error, status int) bool { - if err == nil { - return false - } - code, err := extractStatusCode(err.Error()) - if err != nil { - return false - } - - return code == status -} - -func extractStatusCode(errorMessage string) (int, error) { - re := regexp.MustCompile(`code=(\d+)`) - matches := re.FindStringSubmatch(errorMessage) - if len(matches) != 2 { - return 0, ierrors.Errorf("unable to extract status code from error message") - } - - statusCode, err := strconv.Atoi(matches[1]) - if err != nil { - return 0, err - } - - return statusCode, nil -} 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=