diff --git a/pkg/tests/accounts_test.go b/pkg/tests/accounts_test.go index 1195eba30..99886583d 100644 --- a/pkg/tests/accounts_test.go +++ b/pkg/tests/accounts_test.go @@ -3,40 +3,34 @@ package tests import ( "testing" - "github.com/iotaledger/hive.go/lo" - "github.com/iotaledger/hive.go/runtime/options" "github.com/iotaledger/iota-core/pkg/model" - "github.com/iotaledger/iota-core/pkg/protocol" "github.com/iotaledger/iota-core/pkg/protocol/engine/accounts" - "github.com/iotaledger/iota-core/pkg/protocol/snapshotcreator" "github.com/iotaledger/iota-core/pkg/testsuite" "github.com/iotaledger/iota-core/pkg/testsuite/mock" + "github.com/iotaledger/iota-core/pkg/testsuite/snapshotcreator" "github.com/iotaledger/iota-core/pkg/utils" iotago "github.com/iotaledger/iota.go/v4" ) -// TODO: implement tests for staking and delegation transitions that cover edge cases - part of hardening phase. -func Test_TransitionAccount(t *testing.T) { +func Test_TransitionAndDestroyAccount(t *testing.T) { oldGenesisOutputKey := utils.RandBlockIssuerKey() + ts := testsuite.NewTestSuite(t, testsuite.WithAccounts(snapshotcreator.AccountDetails{ // Nil address will be replaced with the address generated from genesis seed. - // A single key may unlock multiple accounts; that's why it can't be used as a source for AccountID derivation. Address: nil, - // Set an amount enough to cover the rent and to cover an additional key that is added in the test. - // If it's too little, then the test will fail. - Amount: testsuite.MinIssuerAccountAmount * 10, + // Set an amount enough to cover storage deposit and more issuer keys. + Amount: mock.MinIssuerAccountAmount * 10, Mana: 0, // AccountID is derived from this field, so this must be set uniquely for each account. IssuerKey: oldGenesisOutputKey, // Expiry Slot is the slot index at which the account expires. - ExpirySlot: 1, + ExpirySlot: iotago.MaxSlotIndex, // BlockIssuanceCredits on this account is custom because it never needs to issue. - // On Validator nodes it's unlimited (MaxInt64). BlockIssuanceCredits: iotago.BlockIssuanceCredits(123), }), testsuite.WithProtocolParametersOptions( iotago.WithTimeProviderOptions( - testsuite.GenesisTimeWithOffsetBySlots(100, testsuite.DefaultSlotDurationInSeconds), + testsuite.GenesisTimeWithOffsetBySlots(200, testsuite.DefaultSlotDurationInSeconds), testsuite.DefaultSlotDurationInSeconds, 8, ), @@ -51,68 +45,73 @@ func Test_TransitionAccount(t *testing.T) { ) defer ts.Shutdown() + // add a validator node to the network. This will add a validator account to the snapshot. node1 := ts.AddValidatorNode("node1") + // add a non-validator node to the network. This will not add any accounts to the snapshot. _ = ts.AddNode("node2") - blockIssuer := ts.AddBasicBlockIssuer("default", iotago.MaxBlockIssuanceCredits/2) + // add a default block issuer to the network. This will add another block issuer account to the snapshot. + wallet := ts.AddGenesisWallet("default", node1, iotago.MaxBlockIssuanceCredits/2) - ts.Run(true, map[string][]options.Option[protocol.Protocol]{}) + ts.Run(true) + // check that the accounts added in the genesis snapshot were added to account manager correctly. + // genesis account. genesisAccount := ts.AccountOutput("Genesis:1") genesisAccountOutput := genesisAccount.Output().(*iotago.AccountOutput) - ts.AssertAccountData(&accounts.AccountData{ ID: genesisAccountOutput.AccountID, Credits: accounts.NewBlockIssuanceCredits(iotago.BlockIssuanceCredits(123), 0), OutputID: genesisAccount.OutputID(), - ExpirySlot: 1, + ExpirySlot: iotago.MaxSlotIndex, BlockIssuerKeys: iotago.NewBlockIssuerKeys(oldGenesisOutputKey), }, ts.Nodes()...) + // validator node account. + validatorAccountOutput := ts.AccountOutput("Genesis:2") + ts.AssertAccountData(&accounts.AccountData{ + ID: node1.Validator.AccountID, + Credits: accounts.NewBlockIssuanceCredits(iotago.MaxBlockIssuanceCredits/2, 0), + OutputID: validatorAccountOutput.OutputID(), + ExpirySlot: iotago.MaxSlotIndex, + BlockIssuerKeys: node1.Validator.BlockIssuerKeys(), + StakeEndEpoch: iotago.MaxEpochIndex, + ValidatorStake: mock.MinValidatorAccountAmount, + }, ts.Nodes()...) + // default wallet block issuer account. + blockIssuerAccountOutput := ts.AccountOutput("Genesis:3") + ts.AssertAccountData(&accounts.AccountData{ + ID: wallet.BlockIssuer.AccountID, + Credits: accounts.NewBlockIssuanceCredits(iotago.MaxBlockIssuanceCredits/2, 0), + OutputID: blockIssuerAccountOutput.OutputID(), + ExpirySlot: iotago.MaxSlotIndex, + BlockIssuerKeys: wallet.BlockIssuer.BlockIssuerKeys(), + }, ts.Nodes()...) - // MODIFY EXISTING GENESIS ACCOUNT AND PREPARE SOME BASIC OUTPUTS - + // MODIFY EXISTING GENESIS ACCOUNT newGenesisOutputKey := utils.RandBlockIssuerKey() - newAccountBlockIssuerKey := utils.RandBlockIssuerKey() - latestCommitmentID := node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment().MustID() + var block1Slot iotago.SlotIndex = 1 + // set the expiry of the genesis account to be the block slot + max committable age. + newExpirySlot := block1Slot + ts.API.ProtocolParameters().MaxCommittableAge() - accountInput, accountOutputs, accountWallets := ts.TransactionFramework.TransitionAccount( + tx1 := ts.DefaultWallet().TransitionAccount( + "TX1", "Genesis:1", - testsuite.WithAddBlockIssuerKey(newGenesisOutputKey), - testsuite.WithBlockIssuerExpirySlot(1), + mock.WithAddBlockIssuerKey(newGenesisOutputKey), + mock.WithBlockIssuerExpirySlot(newExpirySlot), ) - consumedInputs, equalOutputs, equalWallets := ts.TransactionFramework.CreateBasicOutputsEqually(4, "Genesis:0") - - tx1 := lo.PanicOnErr(ts.TransactionFramework.CreateSignedTransactionWithOptions("TX1", append(accountWallets, equalWallets...), - testsuite.WithAccountInput(accountInput, true), - testsuite.WithInputs(consumedInputs), - testsuite.WithContextInputs(iotago.TxEssenceContextInputs{ - &iotago.BlockIssuanceCreditInput{ - AccountID: genesisAccountOutput.AccountID, - }, - &iotago.CommitmentInput{ - CommitmentID: latestCommitmentID, - }, - }), - testsuite.WithOutputs(append(accountOutputs, equalOutputs...)), - testsuite.WithAllotments(iotago.Allotments{&iotago.Allotment{ - AccountID: genesisAccountOutput.AccountID, - Mana: 0, - }}), - )) - var block1Slot iotago.SlotIndex = 1 + // default block issuer issues a block containing the transaction in slot 1. genesisCommitment := iotago.NewEmptyCommitment(ts.API.ProtocolParameters().Version()) genesisCommitment.ReferenceManaCost = ts.API.ProtocolParameters().CongestionControlParameters().MinReferenceManaCost - - block1 := ts.IssueBasicBlockAtSlotWithOptions("block1", block1Slot, genesisCommitment, blockIssuer, node1, tx1) - + block1 := ts.IssueBasicBlockAtSlotWithOptions("block1", block1Slot, ts.DefaultWallet(), tx1, mock.WithSlotCommitment(genesisCommitment)) latestParent := ts.CommitUntilSlot(ts.BlockID("block1").Slot(), block1) + // assert diff of the genesis account, it should have a new output ID, new expiry slot and a new block issuer key. ts.AssertAccountDiff(genesisAccountOutput.AccountID, block1Slot, &model.AccountDiff{ BICChange: 0, PreviousUpdatedTime: 0, - PreviousExpirySlot: 1, - NewExpirySlot: 1, - NewOutputID: iotago.OutputIDFromTransactionIDAndIndex(ts.TransactionFramework.TransactionID("TX1"), 0), + PreviousExpirySlot: iotago.MaxSlotIndex, + NewExpirySlot: newExpirySlot, + NewOutputID: ts.DefaultWallet().Output("TX1:0").OutputID(), PreviousOutputID: genesisAccount.OutputID(), BlockIssuerKeysRemoved: iotago.NewBlockIssuerKeys(), BlockIssuerKeysAdded: iotago.NewBlockIssuerKeys(newGenesisOutputKey), @@ -121,57 +120,30 @@ func Test_TransitionAccount(t *testing.T) { ts.AssertAccountData(&accounts.AccountData{ ID: genesisAccountOutput.AccountID, Credits: accounts.NewBlockIssuanceCredits(iotago.BlockIssuanceCredits(123), 0), - OutputID: iotago.OutputIDFromTransactionIDAndIndex(ts.TransactionFramework.TransactionID("TX1"), 0), + OutputID: ts.DefaultWallet().Output("TX1:0").OutputID(), BlockIssuerKeys: iotago.NewBlockIssuerKeys(oldGenesisOutputKey, newGenesisOutputKey), - ExpirySlot: 1, + ExpirySlot: newExpirySlot, }, ts.Nodes()...) - // DESTROY GENESIS ACCOUNT, CREATE NEW ACCOUNT WITH BLOCK ISSUER AND STAKING FEATURES FROM BASIC UTXO - - // commit until the expiry slot of the transitioned genesis account plus one - latestParent = ts.CommitUntilSlot(accountOutputs[0].FeatureSet().BlockIssuer().ExpirySlot+1, latestParent) - // set the expiry slof of the transitioned genesis account to the latest committed + MaxCommittableAge - newAccountExpirySlot := node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Slot() + ts.API.ProtocolParameters().MaxCommittableAge() - inputForNewAccount, newAccountOutputs, newAccountWallets := ts.TransactionFramework.CreateAccountFromInput("TX1:1", - testsuite.WithAccountConditions(iotago.AccountOutputUnlockConditions{ - &iotago.StateControllerAddressUnlockCondition{Address: ts.TransactionFramework.DefaultAddress()}, - &iotago.GovernorAddressUnlockCondition{Address: ts.TransactionFramework.DefaultAddress()}, - }), - testsuite.WithBlockIssuerFeature(iotago.BlockIssuerKeys{newAccountBlockIssuerKey}, newAccountExpirySlot), - testsuite.WithStakingFeature(10000, 421, 0, 10), - ) - - destroyedAccountInput, destructionOutputs, destroyWallets := ts.TransactionFramework.DestroyAccount("TX1:0") + // DESTROY GENESIS ACCOUNT + // commit until the expiry slot of the transitioned genesis account plus one. + latestParent = ts.CommitUntilSlot(newExpirySlot+1, latestParent) + // issue the block containing the transaction in the same slot as the latest parent block. block2Slot := latestParent.ID().Slot() - - tx2 := lo.PanicOnErr(ts.TransactionFramework.CreateSignedTransactionWithOptions("TX2", append(newAccountWallets, destroyWallets...), - testsuite.WithContextInputs(iotago.TxEssenceContextInputs{ - &iotago.BlockIssuanceCreditInput{ - AccountID: genesisAccountOutput.AccountID, - }, - &iotago.CommitmentInput{ - CommitmentID: node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment().MustID(), - }, - }), - testsuite.WithInputs(inputForNewAccount), - testsuite.WithAccountInput(destroyedAccountInput, true), - testsuite.WithOutputs(append(newAccountOutputs, destructionOutputs...)), - testsuite.WithSlotCreated(block2Slot), - )) - - block2 := ts.IssueBasicBlockAtSlotWithOptions("block2", block2Slot, node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), blockIssuer, node1, tx2, mock.WithStrongParents(latestParent.ID())) - + // create a transaction which destroys the genesis account. + tx2 := ts.DefaultWallet().DestroyAccount("TX2", "TX1:0", block2Slot) + block2 := ts.IssueBasicBlockAtSlotWithOptions("block2", block2Slot, ts.DefaultWallet(), tx2, mock.WithStrongParents(latestParent.ID())) latestParent = ts.CommitUntilSlot(block2Slot, block2) - // assert diff of a destroyed account, to make sure we can correctly restore it + // assert diff of the destroyed account. ts.AssertAccountDiff(genesisAccountOutput.AccountID, block2Slot, &model.AccountDiff{ BICChange: -iotago.BlockIssuanceCredits(123), PreviousUpdatedTime: 0, NewExpirySlot: 0, - PreviousExpirySlot: 1, + PreviousExpirySlot: newExpirySlot, NewOutputID: iotago.EmptyOutputID, - PreviousOutputID: iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(ts.TransactionFramework.Transaction("TX1").ID()), 0), + PreviousOutputID: ts.DefaultWallet().Output("TX1:0").OutputID(), BlockIssuerKeysAdded: iotago.NewBlockIssuerKeys(), BlockIssuerKeysRemoved: iotago.NewBlockIssuerKeys(oldGenesisOutputKey, newGenesisOutputKey), ValidatorStakeChange: 0, @@ -179,11 +151,83 @@ func Test_TransitionAccount(t *testing.T) { FixedCostChange: 0, DelegationStakeChange: 0, }, true, ts.Nodes()...) +} + +func Test_StakeDelegateAndDelayedClaim(t *testing.T) { + ts := testsuite.NewTestSuite(t, + testsuite.WithProtocolParametersOptions( + iotago.WithTimeProviderOptions( + testsuite.GenesisTimeWithOffsetBySlots(100, testsuite.DefaultSlotDurationInSeconds), + testsuite.DefaultSlotDurationInSeconds, + 8, + ), + iotago.WithLivenessOptions( + testsuite.DefaultLivenessThresholdLowerBoundInSeconds, + testsuite.DefaultLivenessThresholdUpperBoundInSeconds, + testsuite.DefaultMinCommittableAge, + 100, + testsuite.DefaultEpochNearingThreshold, + ), + ), + ) + defer ts.Shutdown() + + // add a validator node to the network. This will add a validator account to the snapshot. + node1 := ts.AddValidatorNode("node1") + // add a non-validator node to the network. This will not add any accounts to the snapshot. + _ = ts.AddNode("node2") + // add a default block issuer to the network. This will add another block issuer account to the snapshot. + wallet := ts.AddGenesisWallet("default", node1, iotago.MaxBlockIssuanceCredits/2) + + ts.Run(true) + + // assert validator and block issuer accounts in genesis snapshot. + // validator node account. + validatorAccountOutput := ts.AccountOutput("Genesis:1") + ts.AssertAccountData(&accounts.AccountData{ + ID: node1.Validator.AccountID, + Credits: accounts.NewBlockIssuanceCredits(iotago.MaxBlockIssuanceCredits/2, 0), + OutputID: validatorAccountOutput.OutputID(), + ExpirySlot: iotago.MaxSlotIndex, + BlockIssuerKeys: node1.Validator.BlockIssuerKeys(), + StakeEndEpoch: iotago.MaxEpochIndex, + ValidatorStake: mock.MinValidatorAccountAmount, + }, ts.Nodes()...) + // default wallet block issuer account. + blockIssuerAccountOutput := ts.AccountOutput("Genesis:2") + ts.AssertAccountData(&accounts.AccountData{ + ID: wallet.BlockIssuer.AccountID, + Credits: accounts.NewBlockIssuanceCredits(iotago.MaxBlockIssuanceCredits/2, 0), + OutputID: blockIssuerAccountOutput.OutputID(), + ExpirySlot: iotago.MaxSlotIndex, + BlockIssuerKeys: wallet.BlockIssuer.BlockIssuerKeys(), + }, ts.Nodes()...) + + //CREATE NEW ACCOUNT WITH BLOCK ISSUER AND STAKING FEATURES FROM BASIC UTXO + newAccountBlockIssuerKey := utils.RandBlockIssuerKey() + // set the expiry slot of the transitioned genesis account to the latest committed + MaxCommittableAge + newAccountExpirySlot := node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Slot() + ts.API.ProtocolParameters().MaxCommittableAge() + + var block1Slot iotago.SlotIndex = 1 + tx1 := ts.DefaultWallet().CreateAccountFromInput( + "TX1", + "Genesis:0", + ts.DefaultWallet(), + block1Slot, + mock.WithBlockIssuerFeature(iotago.BlockIssuerKeys{newAccountBlockIssuerKey}, newAccountExpirySlot), + mock.WithStakingFeature(10000, 421, 0, 10), + mock.WithAccountAmount(mock.MinIssuerAccountAmount), + ) + + genesisCommitment := iotago.NewEmptyCommitment(ts.API.ProtocolParameters().Version()) + genesisCommitment.ReferenceManaCost = ts.API.ProtocolParameters().CongestionControlParameters().MinReferenceManaCost + block1 := ts.IssueBasicBlockAtSlotWithOptions("block1", block1Slot, ts.DefaultWallet(), tx1) + latestParent := ts.CommitUntilSlot(block1Slot, block1) - newAccount := ts.AccountOutput("TX2:0") + newAccount := ts.DefaultWallet().AccountOutput("TX1:0") newAccountOutput := newAccount.Output().(*iotago.AccountOutput) - ts.AssertAccountDiff(newAccountOutput.AccountID, block2Slot, &model.AccountDiff{ + ts.AssertAccountDiff(newAccountOutput.AccountID, block1Slot, &model.AccountDiff{ BICChange: 0, PreviousUpdatedTime: 0, NewExpirySlot: newAccountExpirySlot, @@ -200,7 +244,7 @@ func Test_TransitionAccount(t *testing.T) { ts.AssertAccountData(&accounts.AccountData{ ID: newAccountOutput.AccountID, - Credits: accounts.NewBlockIssuanceCredits(0, block2Slot), + Credits: accounts.NewBlockIssuanceCredits(0, block1Slot), ExpirySlot: newAccountExpirySlot, OutputID: newAccount.OutputID(), BlockIssuerKeys: iotago.NewBlockIssuerKeys(newAccountBlockIssuerKey), @@ -210,32 +254,22 @@ func Test_TransitionAccount(t *testing.T) { ValidatorStake: 10000, }, ts.Nodes()...) + // CREATE DELEGATION TO NEW ACCOUNT FROM BASIC UTXO accountAddress := iotago.AccountAddress(newAccountOutput.AccountID) - // create a delegation output delegating to the newly created account - inputForNewDelegation, newDelegationOutputs, newDelegationWallets := ts.TransactionFramework.CreateDelegationFromInput("TX1:2", - testsuite.WithDelegatedValidatorAddress(&accountAddress), - testsuite.WithDelegationStartEpoch(1), + block2Slot := latestParent.ID().Slot() + tx2 := ts.DefaultWallet().CreateDelegationFromInput( + "TX2", + "TX1:1", + block2Slot, + mock.WithDelegatedValidatorAddress(&accountAddress), + mock.WithDelegationStartEpoch(1), ) + block2 := ts.IssueBasicBlockAtSlotWithOptions("block2", block2Slot, ts.DefaultWallet(), tx2, mock.WithStrongParents(latestParent.ID())) - block3Slot := latestParent.ID().Slot() - - tx3 := lo.PanicOnErr(ts.TransactionFramework.CreateSignedTransactionWithOptions("TX3", newDelegationWallets, - testsuite.WithContextInputs(iotago.TxEssenceContextInputs{ - &iotago.CommitmentInput{ - CommitmentID: node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment().MustID(), - }, - }), - testsuite.WithInputs(inputForNewDelegation), - testsuite.WithOutputs(newDelegationOutputs), - testsuite.WithSlotCreated(block3Slot), - )) - - block3 := ts.IssueBasicBlockAtSlotWithOptions("block3", block3Slot, node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), blockIssuer, node1, tx3, mock.WithStrongParents(latestParent.ID())) - - latestParent = ts.CommitUntilSlot(block3Slot, block3) - delegatedAmount := inputForNewDelegation[0].BaseTokenAmount() + latestParent = ts.CommitUntilSlot(block2Slot, block2) + delegatedAmount := ts.DefaultWallet().Output("TX1:1").BaseTokenAmount() - ts.AssertAccountDiff(newAccountOutput.AccountID, block3Slot, &model.AccountDiff{ + ts.AssertAccountDiff(newAccountOutput.AccountID, block2Slot, &model.AccountDiff{ BICChange: 0, PreviousUpdatedTime: 0, NewOutputID: iotago.EmptyOutputID, @@ -250,7 +284,7 @@ func Test_TransitionAccount(t *testing.T) { ts.AssertAccountData(&accounts.AccountData{ ID: newAccountOutput.AccountID, - Credits: accounts.NewBlockIssuanceCredits(0, block2Slot), + Credits: accounts.NewBlockIssuanceCredits(0, block1Slot), ExpirySlot: newAccountExpirySlot, OutputID: newAccount.OutputID(), BlockIssuerKeys: iotago.NewBlockIssuerKeys(newAccountBlockIssuerKey), @@ -261,27 +295,14 @@ func Test_TransitionAccount(t *testing.T) { }, ts.Nodes()...) // transition a delegation output to a delayed claiming state - inputForDelegationTransition, delegationTransitionOutputs, delegationTransitionWallets := ts.TransactionFramework.DelayedClaimingTransition("TX3:0", 0) - - tx4 := lo.PanicOnErr(ts.TransactionFramework.CreateSignedTransactionWithOptions("TX4", delegationTransitionWallets, - testsuite.WithContextInputs(iotago.TxEssenceContextInputs{ - &iotago.CommitmentInput{ - CommitmentID: node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment().MustID(), - }, - }), - testsuite.WithInputs(inputForDelegationTransition), - testsuite.WithOutputs(delegationTransitionOutputs), - testsuite.WithSlotCreated(block3Slot), - )) - - block4Slot := latestParent.ID().Slot() - - block4 := ts.IssueBasicBlockAtSlotWithOptions("block4", block4Slot, node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), blockIssuer, node1, tx4, mock.WithStrongParents(latestParent.ID())) + block3Slot := latestParent.ID().Slot() + tx3 := ts.DefaultWallet().DelayedClaimingTransition("TX3", "TX2:0", block3Slot, 0) + block3 := ts.IssueBasicBlockAtSlotWithOptions("block3", block3Slot, ts.DefaultWallet(), tx3, mock.WithStrongParents(latestParent.ID())) - latestParent = ts.CommitUntilSlot(block4Slot, block4) + latestParent = ts.CommitUntilSlot(block3Slot, block3) // Transitioning to delayed claiming effectively removes the delegation, so we expect a negative delegation stake change. - ts.AssertAccountDiff(newAccountOutput.AccountID, block4Slot, &model.AccountDiff{ + ts.AssertAccountDiff(newAccountOutput.AccountID, block3Slot, &model.AccountDiff{ BICChange: 0, PreviousUpdatedTime: 0, NewOutputID: iotago.EmptyOutputID, @@ -296,7 +317,7 @@ func Test_TransitionAccount(t *testing.T) { ts.AssertAccountData(&accounts.AccountData{ ID: newAccountOutput.AccountID, - Credits: accounts.NewBlockIssuanceCredits(0, block2Slot), + Credits: accounts.NewBlockIssuanceCredits(0, block1Slot), ExpirySlot: newAccountExpirySlot, OutputID: newAccount.OutputID(), BlockIssuerKeys: iotago.NewBlockIssuerKeys(newAccountBlockIssuerKey), @@ -305,72 +326,110 @@ func Test_TransitionAccount(t *testing.T) { DelegationStake: iotago.BaseToken(0), ValidatorStake: 10000, }, ts.Nodes()...) +} - // CREATE IMPLICIT ACCOUNT FROM BASIC UTXO - inputForImplicitAccount, outputsForImplicitAccount, implicitAccountAddress, implicitWallet := ts.TransactionFramework.CreateImplicitAccountFromInput("TX1:3") - - tx5 := lo.PanicOnErr(ts.TransactionFramework.CreateSignedTransactionWithOptions("TX5", implicitWallet, - testsuite.WithInputs(inputForImplicitAccount), - testsuite.WithOutputs(outputsForImplicitAccount), - )) - - implicitAccountOutput := ts.TransactionFramework.Output("TX5:0") - implicitAccountOutputID := implicitAccountOutput.OutputID() - implicitAccountID := iotago.AccountIDFromOutputID(implicitAccountOutputID) +func Test_ImplicitAccounts(t *testing.T) { + ts := testsuite.NewTestSuite(t, + testsuite.WithProtocolParametersOptions( + iotago.WithTimeProviderOptions( + testsuite.GenesisTimeWithOffsetBySlots(100, testsuite.DefaultSlotDurationInSeconds), + testsuite.DefaultSlotDurationInSeconds, + 8, + ), + iotago.WithLivenessOptions( + testsuite.DefaultLivenessThresholdLowerBoundInSeconds, + testsuite.DefaultLivenessThresholdUpperBoundInSeconds, + testsuite.DefaultMinCommittableAge, + 100, + testsuite.DefaultEpochNearingThreshold, + ), + ), + ) + defer ts.Shutdown() - slotIndexBlock5 := latestParent.ID().Index() + // add a validator node to the network. This will add a validator account to the snapshot. + node1 := ts.AddValidatorNode("node1") + // add a non-validator node to the network. This will not add any accounts to the snapshot. + _ = ts.AddNode("node2") + // add a default block issuer to the network. This will add another block issuer account to the snapshot. + wallet := ts.AddGenesisWallet("default", node1, iotago.MaxBlockIssuanceCredits/2) - block5 := ts.IssueBasicBlockAtSlotWithOptions("block5", slotIndexBlock5, node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), blockIssuer, node1, tx5, mock.WithStrongParents(latestParent.ID())) + ts.Run(true) - latestParent = ts.CommitUntilSlot(slotIndexBlock5, block5) + // assert validator and block issuer accounts in genesis snapshot. + // validator node account. + validatorAccountOutput := ts.AccountOutput("Genesis:1") + ts.AssertAccountData(&accounts.AccountData{ + ID: node1.Validator.AccountID, + Credits: accounts.NewBlockIssuanceCredits(iotago.MaxBlockIssuanceCredits/2, 0), + OutputID: validatorAccountOutput.OutputID(), + ExpirySlot: iotago.MaxSlotIndex, + BlockIssuerKeys: node1.Validator.BlockIssuerKeys(), + StakeEndEpoch: iotago.MaxEpochIndex, + ValidatorStake: mock.MinValidatorAccountAmount, + }, ts.Nodes()...) + // default wallet block issuer account. + blockIssuerAccountOutput := ts.AccountOutput("Genesis:2") + ts.AssertAccountData(&accounts.AccountData{ + ID: wallet.BlockIssuer.AccountID, + Credits: accounts.NewBlockIssuanceCredits(iotago.MaxBlockIssuanceCredits/2, 0), + OutputID: blockIssuerAccountOutput.OutputID(), + ExpirySlot: iotago.MaxSlotIndex, + BlockIssuerKeys: wallet.BlockIssuer.BlockIssuerKeys(), + }, ts.Nodes()...) - var implicitBlockIssuerKey iotago.BlockIssuerKey = iotago.Ed25519PublicKeyHashBlockIssuerKeyFromImplicitAccountCreationAddress(implicitAccountAddress) + // CREATE IMPLICIT ACCOUNT FROM GENESIS BASIC UTXO, SENT TO A NEW USER WALLET. + // this wallet is not registered in the ledger yet. + newUserWallet := mock.NewWallet(ts.Testing, "newUser", node1) + // a default wallet, already registered in the ledger, will issue the transaction and block. + tx1 := ts.DefaultWallet().CreateImplicitAccountFromInput( + "TX1", + "Genesis:0", + newUserWallet, + ) + var block1Slot iotago.SlotIndex = 1 + block1 := ts.IssueBasicBlockAtSlotWithOptions("block1", block1Slot, ts.DefaultWallet(), tx1) + latestParent := ts.CommitUntilSlot(block1Slot, block1) + implicitAccountOutput := newUserWallet.Output("TX1:0") + implicitAccountOutputID := implicitAccountOutput.OutputID() + implicitAccountID := iotago.AccountIDFromOutputID(implicitAccountOutputID) + var implicitBlockIssuerKey iotago.BlockIssuerKey = iotago.Ed25519PublicKeyHashBlockIssuerKeyFromImplicitAccountCreationAddress(newUserWallet.ImplicitAccountCreationAddress()) + // the new implicit account should now be registered in the accounts ledger. ts.AssertAccountData(&accounts.AccountData{ ID: implicitAccountID, - Credits: accounts.NewBlockIssuanceCredits(0, slotIndexBlock5), + Credits: accounts.NewBlockIssuanceCredits(0, block1Slot), ExpirySlot: iotago.MaxSlotIndex, OutputID: implicitAccountOutputID, BlockIssuerKeys: iotago.NewBlockIssuerKeys(implicitBlockIssuerKey), }, ts.Nodes()...) - // TRANSITION IMPLICIT ACCOUNT TO ACCOUNT OUTPUT - + // TRANSITION IMPLICIT ACCOUNT TO ACCOUNT OUTPUT. + // USE IMPLICIT ACCOUNT AS BLOCK ISSUER. fullAccountBlockIssuerKey := utils.RandBlockIssuerKey() - inputForImplicitAccountTransition, outputsForImplicitAccountTransition, fullAccountWallet := ts.TransactionFramework.TransitionImplicitAccountToAccountOutput( - "TX5:0", - testsuite.WithBlockIssuerFeature( + block2Slot := latestParent.ID().Index() + tx2 := newUserWallet.TransitionImplicitAccountToAccountOutput( + "TX2", + "TX1:0", + block2Slot, + mock.WithBlockIssuerFeature( iotago.BlockIssuerKeys{fullAccountBlockIssuerKey}, iotago.MaxSlotIndex, ), + mock.WithAccountAmount(mock.MinIssuerAccountAmount), ) + block2Commitment := node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment() + block2 := ts.IssueBasicBlockAtSlotWithOptions("block2", block2Slot, newUserWallet, tx2, mock.WithStrongParents(latestParent.ID())) + latestParent = ts.CommitUntilSlot(block2Slot, block2) - tx6 := lo.PanicOnErr(ts.TransactionFramework.CreateSignedTransactionWithOptions("TX6", fullAccountWallet, - testsuite.WithContextInputs(iotago.TxEssenceContextInputs{ - &iotago.BlockIssuanceCreditInput{ - AccountID: implicitAccountID, - }, - &iotago.CommitmentInput{ - CommitmentID: node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment().MustID(), - }, - }), - testsuite.WithInputs(inputForImplicitAccountTransition), - testsuite.WithOutputs(outputsForImplicitAccountTransition), - testsuite.WithSlotCreated(slotIndexBlock5), - )) - - slotIndexBlock6 := latestParent.ID().Index() - - block6 := ts.IssueBasicBlockAtSlotWithOptions("block6", slotIndexBlock6, node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), blockIssuer, node1, tx6, mock.WithStrongParents(latestParent.ID())) - - latestParent = ts.CommitUntilSlot(slotIndexBlock6, block6) - - fullAccountOutputID := ts.TransactionFramework.Output("TX6:0").OutputID() - - ts.AssertAccountDiff(implicitAccountID, slotIndexBlock6, &model.AccountDiff{ - BICChange: 0, - PreviousUpdatedTime: 0, + fullAccountOutputID := newUserWallet.Output("TX2:0").OutputID() + allotted := iotago.BlockIssuanceCredits(tx2.Transaction.Allotments.Get(implicitAccountID)) + burned := iotago.BlockIssuanceCredits(block2.WorkScore()) * iotago.BlockIssuanceCredits(block2Commitment.ReferenceManaCost) + // the implicit account should now have been transitioned to a full account in the accounts ledger. + ts.AssertAccountDiff(implicitAccountID, block2Slot, &model.AccountDiff{ + BICChange: allotted - burned, + PreviousUpdatedTime: block1Slot, NewOutputID: fullAccountOutputID, PreviousOutputID: implicitAccountOutputID, PreviousExpirySlot: iotago.MaxSlotIndex, @@ -382,10 +441,9 @@ func Test_TransitionAccount(t *testing.T) { FixedCostChange: 0, DelegationStakeChange: 0, }, false, ts.Nodes()...) - ts.AssertAccountData(&accounts.AccountData{ ID: implicitAccountID, - Credits: accounts.NewBlockIssuanceCredits(0, slotIndexBlock5), + Credits: accounts.NewBlockIssuanceCredits(allotted-burned, block2Slot), ExpirySlot: iotago.MaxSlotIndex, OutputID: fullAccountOutputID, BlockIssuerKeys: iotago.NewBlockIssuerKeys(fullAccountBlockIssuerKey), diff --git a/pkg/tests/booker_test.go b/pkg/tests/booker_test.go index ea4234537..8a9e2e576 100644 --- a/pkg/tests/booker_test.go +++ b/pkg/tests/booker_test.go @@ -18,33 +18,33 @@ func Test_IssuingTransactionsOutOfOrder(t *testing.T) { defer ts.Shutdown() node1 := ts.AddValidatorNode("node1") - blockIssuer := ts.AddBasicBlockIssuer("default") + wallet := ts.AddGenesisWallet("default", node1) ts.Run(true, map[string][]options.Option[protocol.Protocol]{}) - tx1 := lo.PanicOnErr(ts.TransactionFramework.CreateSimpleTransaction("tx1", 1, "Genesis:0")) + tx1 := wallet.CreateBasicOutputsEquallyFromInputs("tx1", 1, "Genesis:0") - tx2 := lo.PanicOnErr(ts.TransactionFramework.CreateSimpleTransaction("tx2", 1, "tx1:0")) + tx2 := wallet.CreateBasicOutputsEquallyFromInputs("tx2", 1, "tx1:0") - ts.IssuePayloadWithOptions("block1", blockIssuer, node1, tx2) + ts.IssuePayloadWithOptions("block1", wallet, tx2) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx2"), true, node1) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1"), false, node1) + ts.AssertTransactionsExist(wallet.Transactions("tx2"), true, node1) + ts.AssertTransactionsExist(wallet.Transactions("tx1"), false, node1) - ts.AssertTransactionsInCacheBooked(ts.TransactionFramework.Transactions("tx2"), false, node1) + ts.AssertTransactionsInCacheBooked(wallet.Transactions("tx2"), false, node1) // make sure that the block is not booked - ts.IssuePayloadWithOptions("block2", blockIssuer, node1, tx1) + ts.IssuePayloadWithOptions("block2", wallet, tx1) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1) - ts.AssertTransactionsInCacheBooked(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1) + ts.AssertTransactionsExist(wallet.Transactions("tx1", "tx2"), true, node1) + ts.AssertTransactionsInCacheBooked(wallet.Transactions("tx1", "tx2"), true, node1) ts.AssertBlocksInCacheConflicts(map[*blocks.Block][]string{ ts.Block("block1"): {"tx2"}, ts.Block("block2"): {"tx1"}, }, node1) ts.AssertTransactionInCacheConflicts(map[*iotago.Transaction][]string{ - ts.TransactionFramework.Transaction("tx2"): {"tx2"}, - ts.TransactionFramework.Transaction("tx1"): {"tx1"}, + wallet.Transaction("tx2"): {"tx2"}, + wallet.Transaction("tx1"): {"tx1"}, }, node1) } @@ -54,7 +54,7 @@ func Test_DoubleSpend(t *testing.T) { node1 := ts.AddValidatorNode("node1") node2 := ts.AddValidatorNode("node2") - blockIssuer := ts.AddBasicBlockIssuer("default") + wallet := ts.AddGenesisWallet("default", node1) ts.Run(true, map[string][]options.Option[protocol.Protocol]{}) @@ -65,23 +65,23 @@ func Test_DoubleSpend(t *testing.T) { // Create and issue double spends { - tx1 := lo.PanicOnErr(ts.TransactionFramework.CreateSimpleTransaction("tx1", 1, "Genesis:0")) - tx2 := lo.PanicOnErr(ts.TransactionFramework.CreateSimpleTransaction("tx2", 1, "Genesis:0")) + tx1 := wallet.CreateBasicOutputsEquallyFromInputs("tx1", 1, "Genesis:0") + tx2 := wallet.CreateBasicOutputsEquallyFromInputs("tx2", 1, "Genesis:0") - ts.IssuePayloadWithOptions("block1", blockIssuer, node1, tx1, mock.WithStrongParents(ts.BlockID("Genesis"))) - ts.IssuePayloadWithOptions("block2", blockIssuer, node1, tx2, mock.WithStrongParents(ts.BlockID("Genesis"))) + ts.IssuePayloadWithOptions("block1", wallet, tx1, mock.WithStrongParents(ts.BlockID("Genesis"))) + ts.IssuePayloadWithOptions("block2", wallet, tx2, mock.WithStrongParents(ts.BlockID("Genesis"))) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) - ts.AssertTransactionsInCacheBooked(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) - ts.AssertTransactionsInCachePending(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsExist(wallet.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCacheBooked(wallet.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCachePending(wallet.Transactions("tx1", "tx2"), true, node1, node2) ts.AssertBlocksInCacheConflicts(map[*blocks.Block][]string{ ts.Block("block1"): {"tx1"}, ts.Block("block2"): {"tx2"}, }, node1, node2) ts.AssertTransactionInCacheConflicts(map[*iotago.Transaction][]string{ - ts.TransactionFramework.Transaction("tx2"): {"tx2"}, - ts.TransactionFramework.Transaction("tx1"): {"tx1"}, + wallet.Transaction("tx2"): {"tx2"}, + wallet.Transaction("tx1"): {"tx1"}, }, node1, node2) } @@ -94,14 +94,14 @@ func Test_DoubleSpend(t *testing.T) { ts.Block("block3"): {"tx1"}, ts.Block("block4"): {"tx2"}, }, node1, node2) - ts.AssertTransactionsInCachePending(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCachePending(wallet.Transactions("tx1", "tx2"), true, node1, node2) } // Issue an invalid block and assert that its vote is not cast. { ts.IssueValidationBlock("block5", node2, mock.WithStrongParents(ts.BlockIDs("block3", "block4")...)) - ts.AssertTransactionsInCachePending(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCachePending(wallet.Transactions("tx1", "tx2"), true, node1, node2) } // Issue valid blocks that resolve the conflict. @@ -112,8 +112,8 @@ func Test_DoubleSpend(t *testing.T) { ts.AssertBlocksInCacheConflicts(map[*blocks.Block][]string{ ts.Block("block6"): {"tx2"}, }, node1, node2) - ts.AssertTransactionsInCacheAccepted(ts.TransactionFramework.Transactions("tx2"), true, node1, node2) - ts.AssertTransactionsInCacheRejected(ts.TransactionFramework.Transactions("tx1"), true, node1, node2) + ts.AssertTransactionsInCacheAccepted(wallet.Transactions("tx2"), true, node1, node2) + ts.AssertTransactionsInCacheRejected(wallet.Transactions("tx1"), true, node1, node2) } } @@ -124,8 +124,7 @@ func Test_MultipleAttachments(t *testing.T) { nodeA := ts.AddValidatorNode("nodeA") nodeB := ts.AddValidatorNode("nodeB") - blockIssuerA := ts.AddBasicBlockIssuer("blockIssuerA") - blockIssuerB := ts.AddBasicBlockIssuer("blockIssuerA") + wallet := ts.AddGenesisWallet("default", nodeA) ts.Run(true, map[string][]options.Option[protocol.Protocol]{}) @@ -133,11 +132,12 @@ func Test_MultipleAttachments(t *testing.T) { // Create a transaction and issue it from both nodes, so that the conflict is accepted, but no attachment is included yet. { - tx1 := lo.PanicOnErr(ts.TransactionFramework.CreateSimpleTransaction("tx1", 2, "Genesis:0")) + tx1 := wallet.CreateBasicOutputsEquallyFromInputs("tx1", 2, "Genesis:0") - ts.IssuePayloadWithOptions("A.1", blockIssuerA, nodeA, tx1, mock.WithStrongParents(ts.BlockID("Genesis"))) + ts.IssuePayloadWithOptions("A.1", wallet, tx1, mock.WithStrongParents(ts.BlockID("Genesis"))) ts.IssueValidationBlock("A.1.1", nodeA, mock.WithStrongParents(ts.BlockID("A.1"))) - ts.IssuePayloadWithOptions("B.1", blockIssuerB, nodeB, tx1, mock.WithStrongParents(ts.BlockID("Genesis"))) + wallet.SetDefaultNode(nodeB) + ts.IssuePayloadWithOptions("B.1", wallet, tx1, mock.WithStrongParents(ts.BlockID("Genesis"))) ts.IssueValidationBlock("B.1.1", nodeB, mock.WithStrongParents(ts.BlockID("B.1"))) ts.IssueValidationBlock("A.2", nodeA, mock.WithStrongParents(ts.BlockID("B.1.1"))) @@ -153,17 +153,19 @@ func Test_MultipleAttachments(t *testing.T) { ts.Block("B.2"): {"tx1"}, }), ts.Nodes()...) ts.AssertTransactionInCacheConflicts(map[*iotago.Transaction][]string{ - ts.TransactionFramework.Transaction("tx1"): {"tx1"}, + wallet.Transaction("tx1"): {"tx1"}, }, ts.Nodes()...) ts.AssertConflictsInCacheAcceptanceState([]string{"tx1"}, acceptance.Accepted, ts.Nodes()...) } // Create a transaction that is included and whose conflict is accepted, but whose inputs are not accepted. { - tx2 := lo.PanicOnErr(ts.TransactionFramework.CreateSimpleTransaction("tx2", 1, "tx1:1")) + tx2 := wallet.CreateBasicOutputsEquallyFromInputs("tx2", 1, "tx1:1") - ts.IssuePayloadWithOptions("A.3", nodeA.Validator, nodeA, tx2, mock.WithStrongParents(ts.BlockID("Genesis"))) - ts.IssueValidationBlock("B.3", nodeB, mock.WithStrongParents(ts.BlockID("A.3"))) + wallet.SetDefaultNode(nodeA) + ts.IssuePayloadWithOptions("A.3", wallet, tx2, mock.WithStrongParents(ts.BlockID("Genesis"))) + ts.IssueValidationBlock("A.3.1", nodeA, mock.WithStrongParents(ts.BlockID("A.3"))) + ts.IssueValidationBlock("B.3", nodeB, mock.WithStrongParents(ts.BlockID("A.3.1"))) ts.IssueValidationBlock("A.4", nodeA, mock.WithStrongParents(ts.BlockID("B.3"))) ts.AssertBlocksInCachePreAccepted(ts.Blocks("A.3"), true, ts.Nodes()...) @@ -175,8 +177,8 @@ func Test_MultipleAttachments(t *testing.T) { ts.AssertBlocksInCachePreAccepted(ts.Blocks("B.4", "A.5"), false, ts.Nodes()...) ts.AssertBlocksInCacheAccepted(ts.Blocks("A.3"), true, ts.Nodes()...) - ts.AssertTransactionsInCacheBooked(ts.TransactionFramework.Transactions("tx1", "tx2"), true, ts.Nodes()...) - ts.AssertTransactionsInCachePending(ts.TransactionFramework.Transactions("tx1", "tx2"), true, ts.Nodes()...) + ts.AssertTransactionsInCacheBooked(wallet.Transactions("tx1", "tx2"), true, ts.Nodes()...) + ts.AssertTransactionsInCachePending(wallet.Transactions("tx1", "tx2"), true, ts.Nodes()...) ts.AssertBlocksInCacheConflicts(lo.MergeMaps(blocksConflicts, map[*blocks.Block][]string{ ts.Block("A.3"): {"tx2"}, @@ -186,8 +188,8 @@ func Test_MultipleAttachments(t *testing.T) { ts.Block("B.4"): {}, }), ts.Nodes()...) ts.AssertTransactionInCacheConflicts(map[*iotago.Transaction][]string{ - ts.TransactionFramework.Transaction("tx1"): {"tx1"}, - ts.TransactionFramework.Transaction("tx2"): {"tx2"}, + wallet.Transaction("tx1"): {"tx1"}, + wallet.Transaction("tx2"): {"tx2"}, }, nodeA, nodeB) ts.AssertConflictsInCacheAcceptanceState([]string{"tx1", "tx2"}, acceptance.Accepted, ts.Nodes()...) } @@ -204,9 +206,9 @@ func Test_MultipleAttachments(t *testing.T) { ts.AssertBlocksInCacheAccepted(ts.Blocks("A.1", "B.1"), true, ts.Nodes()...) ts.AssertBlocksInCachePreAccepted(ts.Blocks("A.7", "B.6"), false, ts.Nodes()...) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1", "tx2"), true, ts.Nodes()...) - ts.AssertTransactionsInCacheBooked(ts.TransactionFramework.Transactions("tx1", "tx2"), true, ts.Nodes()...) - ts.AssertTransactionsInCacheAccepted(ts.TransactionFramework.Transactions("tx1", "tx2"), true, ts.Nodes()...) + ts.AssertTransactionsExist(wallet.Transactions("tx1", "tx2"), true, ts.Nodes()...) + ts.AssertTransactionsInCacheBooked(wallet.Transactions("tx1", "tx2"), true, ts.Nodes()...) + ts.AssertTransactionsInCacheAccepted(wallet.Transactions("tx1", "tx2"), true, ts.Nodes()...) ts.AssertBlocksInCacheConflicts(lo.MergeMaps(blocksConflicts, map[*blocks.Block][]string{ ts.Block("A.6"): {}, @@ -216,8 +218,8 @@ func Test_MultipleAttachments(t *testing.T) { }), ts.Nodes()...) ts.AssertTransactionInCacheConflicts(map[*iotago.Transaction][]string{ - ts.TransactionFramework.Transaction("tx1"): {"tx1"}, - ts.TransactionFramework.Transaction("tx2"): {"tx2"}, + wallet.Transaction("tx1"): {"tx1"}, + wallet.Transaction("tx2"): {"tx2"}, }, nodeA, nodeB) ts.AssertConflictsInCacheAcceptanceState([]string{"tx1", "tx2"}, acceptance.Accepted, nodeA, nodeB) } @@ -244,7 +246,7 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { node1 := ts.AddValidatorNode("node1") node2 := ts.AddValidatorNode("node2") - ts.AddBasicBlockIssuer("default") + wallet := ts.AddGenesisWallet("default", node1) ts.Run(true, map[string][]options.Option[protocol.Protocol]{}) @@ -257,16 +259,17 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { // Create and issue double spends { - tx1 := lo.PanicOnErr(ts.TransactionFramework.CreateSimpleTransaction("tx1", 1, "Genesis:0")) - tx2 := lo.PanicOnErr(ts.TransactionFramework.CreateSimpleTransaction("tx2", 1, "Genesis:0")) + tx1 := wallet.CreateBasicOutputsEquallyFromInputs("tx1", 1, "Genesis:0") + tx2 := wallet.CreateBasicOutputsEquallyFromInputs("tx2", 1, "Genesis:0") - ts.IssueBasicBlockAtSlotWithOptions("block1.1", 1, genesisCommitment, ts.DefaultBasicBlockIssuer(), node1, tx1) - ts.IssueBasicBlockAtSlotWithOptions("block1.2", 1, genesisCommitment, ts.DefaultBasicBlockIssuer(), node1, tx2) + wallet.SetDefaultNode(node1) + ts.IssueBasicBlockAtSlotWithOptions("block1.1", 1, wallet, tx1, mock.WithSlotCommitment(genesisCommitment)) + ts.IssueBasicBlockAtSlotWithOptions("block1.2", 1, wallet, tx2, mock.WithSlotCommitment(genesisCommitment)) ts.IssueValidationBlockAtSlot("block2.tx1", 2, genesisCommitment, node1, ts.BlockIDs("block1.1")...) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) - ts.AssertTransactionsInCacheBooked(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) - ts.AssertTransactionsInCachePending(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsExist(wallet.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCacheBooked(wallet.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCachePending(wallet.Transactions("tx1", "tx2"), true, node1, node2) ts.AssertBlocksInCacheConflicts(map[*blocks.Block][]string{ ts.Block("block1.1"): {"tx1"}, ts.Block("block1.2"): {"tx2"}, @@ -274,8 +277,8 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { }, node1, node2) ts.AssertTransactionInCacheConflicts(map[*iotago.Transaction][]string{ - ts.TransactionFramework.Transaction("tx2"): {"tx2"}, - ts.TransactionFramework.Transaction("tx1"): {"tx1"}, + wallet.Transaction("tx2"): {"tx2"}, + wallet.Transaction("tx1"): {"tx1"}, }, node1, node2) } @@ -289,7 +292,7 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { ts.Block("block2.2"): {"tx2"}, ts.Block("block2.tx1"): {"tx1"}, }, node1, node2) - ts.AssertTransactionsInCachePending(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCachePending(wallet.Transactions("tx1", "tx2"), true, node1, node2) } // Issue valid blocks that resolve the conflict. @@ -301,8 +304,8 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { ts.Block("block2.3"): {"tx2"}, ts.Block("block2.tx1"): {"tx1"}, }, node1, node2) - ts.AssertTransactionsInCacheAccepted(ts.TransactionFramework.Transactions("tx2"), true, node1, node2) - ts.AssertTransactionsInCacheRejected(ts.TransactionFramework.Transactions("tx1"), true, node1, node2) + ts.AssertTransactionsInCacheAccepted(wallet.Transactions("tx2"), true, node1, node2) + ts.AssertTransactionsInCacheRejected(wallet.Transactions("tx1"), true, node1, node2) } // Advance both nodes at the edge of slot 1 committability @@ -361,20 +364,21 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { commitment1 := lo.PanicOnErr(node2.Protocol.MainEngineInstance().Storage.Commitments().Load(1)).Commitment() // This should be booked on the rejected tx1 conflict - tx4 := lo.PanicOnErr(ts.TransactionFramework.CreateSimpleTransaction("tx4", 1, "tx1:0")) + tx4 := wallet.CreateBasicOutputsEquallyFromInputs("tx4", 1, "tx1:0") // Issue TX3 on top of rejected TX1 and 1 commitment on node2 (committed to slot 1) { - ts.IssueBasicBlockAtSlotWithOptions("n2-commit1", 5, commitment1, ts.DefaultBasicBlockIssuer(), node2, tx4) + wallet.SetDefaultNode(node2) + ts.IssueBasicBlockAtSlotWithOptions("n2-commit1", 5, wallet, tx4, mock.WithSlotCommitment(commitment1)) ts.AssertBlocksInCacheConflicts(map[*blocks.Block][]string{ ts.Block("n2-commit1"): {}, // no conflits inherited as the block is invalid and doesn't get booked. ts.Block("block2.tx1"): {"tx1"}, }, node2) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1"), true, node2) - ts.AssertTransactionsInCacheRejected(ts.TransactionFramework.Transactions("tx4"), true, node2) - ts.AssertTransactionsInCacheBooked(ts.TransactionFramework.Transactions("tx4"), true, node2) + ts.AssertTransactionsExist(wallet.Transactions("tx1"), true, node2) + ts.AssertTransactionsInCacheRejected(wallet.Transactions("tx4"), true, node2) + ts.AssertTransactionsInCacheBooked(wallet.Transactions("tx4"), true, node2) // As the block commits to 1 but spending something orphaned in 1 it should be invalid ts.AssertBlocksInCacheBooked(ts.Blocks("n2-commit1"), false, node2) @@ -388,7 +392,7 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { ts.AssertBlocksInCacheBooked(ts.Blocks("n1-rejected-genesis"), true, node1) ts.AssertBlocksInCacheInvalid(ts.Blocks("n1-rejected-genesis"), false, node1) - ts.AssertTransactionsInCacheRejected(ts.TransactionFramework.Transactions("tx1"), true, node2) + ts.AssertTransactionsInCacheRejected(wallet.Transactions("tx1"), true, node2) ts.AssertBlocksInCacheConflicts(map[*blocks.Block][]string{ ts.Block("block2.tx1"): {"tx1"}, @@ -398,7 +402,8 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { // Issue TX4 on top of rejected TX1 but Genesis commitment on node2 (committed to slot 1) { - ts.IssueBasicBlockAtSlotWithOptions("n2-genesis", 5, genesisCommitment, ts.DefaultBasicBlockIssuer(), node2, tx4, mock.WithStrongParents(ts.BlockID("Genesis"))) + wallet.SetDefaultNode(node2) + ts.IssueBasicBlockAtSlotWithOptions("n2-genesis", 5, wallet, tx4, mock.WithStrongParents(ts.BlockID("Genesis")), mock.WithSlotCommitment(genesisCommitment)) ts.AssertBlocksInCacheConflicts(map[*blocks.Block][]string{ ts.Block("n2-genesis"): {"tx4"}, // on rejected conflict @@ -410,11 +415,12 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { // Issue TX4 on top of rejected TX1 but Genesis commitment on node1 (committed to slot 0) { - ts.IssueBasicBlockAtSlotWithOptions("n1-genesis", 5, genesisCommitment, ts.DefaultBasicBlockIssuer(), node1, tx4, mock.WithStrongParents(ts.BlockID("Genesis"))) + wallet.SetDefaultNode(node1) + ts.IssueBasicBlockAtSlotWithOptions("n1-genesis", 5, wallet, tx4, mock.WithStrongParents(ts.BlockID("Genesis")), mock.WithSlotCommitment(genesisCommitment)) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1"), true, node2) - ts.AssertTransactionsInCacheRejected(ts.TransactionFramework.Transactions("tx4"), true, node2) - ts.AssertTransactionsInCacheBooked(ts.TransactionFramework.Transactions("tx4"), true, node2) + ts.AssertTransactionsExist(wallet.Transactions("tx1"), true, node2) + ts.AssertTransactionsInCacheRejected(wallet.Transactions("tx4"), true, node2) + ts.AssertTransactionsInCacheBooked(wallet.Transactions("tx4"), true, node2) ts.AssertBlocksInCacheConflicts(map[*blocks.Block][]string{ ts.Block("n1-genesis"): {"tx4"}, // on rejected conflict @@ -439,14 +445,16 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { ) // Exchange each-other blocks, ignoring invalidity - ts.IssueExistingBlock("n2-genesis", ts.DefaultBasicBlockIssuer(), node1) - ts.IssueExistingBlock("n2-commit1", ts.DefaultBasicBlockIssuer(), node1) - ts.IssueExistingBlock("n1-genesis", ts.DefaultBasicBlockIssuer(), node2) - ts.IssueExistingBlock("n1-rejected-genesis", ts.DefaultBasicBlockIssuer(), node2) + wallet.SetDefaultNode(node1) + ts.IssueExistingBlock("n2-genesis", wallet) + ts.IssueExistingBlock("n2-commit1", wallet) + wallet.SetDefaultNode(node2) + ts.IssueExistingBlock("n1-genesis", wallet) + ts.IssueExistingBlock("n1-rejected-genesis", wallet) ts.IssueValidationBlockAtSlot("n1-rejected-commit1", 5, commitment1, node1, ts.BlockIDs("n1-rejected-genesis")...) // Needs reissuing on node2 because it is invalid - ts.IssueExistingBlock("n1-rejected-commit1", ts.DefaultBasicBlockIssuer(), node2) + ts.IssueExistingBlock("n1-rejected-commit1", wallet) // The nodes agree on the results of the invalid blocks ts.AssertBlocksInCacheBooked(ts.Blocks("n2-genesis", "n1-genesis", "n1-rejected-genesis"), true, node1, node2) @@ -471,7 +479,7 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { // Commit further and test eviction of transactions { - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1", "tx2", "tx4"), true, node1, node2) + ts.AssertTransactionsExist(wallet.Transactions("tx1", "tx2", "tx4"), true, node1, node2) ts.IssueBlocksAtSlots("", []iotago.SlotIndex{6, 7, 8, 9, 10}, 5, "5.1", ts.Nodes("node1", "node2"), false, nil) @@ -482,7 +490,7 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { testsuite.WithEvictedSlot(8), ) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1", "tx2", "tx4"), false, node1, node2) + ts.AssertTransactionsExist(wallet.Transactions("tx1", "tx2", "tx4"), false, node1, node2) } } @@ -507,7 +515,7 @@ func Test_SpendPendingCommittedRace(t *testing.T) { node1 := ts.AddValidatorNode("node1") node2 := ts.AddValidatorNode("node2") - ts.AddBasicBlockIssuer("default") + wallet := ts.AddGenesisWallet("default", node1) ts.Run(true, map[string][]options.Option[protocol.Protocol]{}) @@ -520,23 +528,24 @@ func Test_SpendPendingCommittedRace(t *testing.T) { // Create and issue double spends { - tx1 := lo.PanicOnErr(ts.TransactionFramework.CreateSimpleTransaction("tx1", 1, "Genesis:0")) - tx2 := lo.PanicOnErr(ts.TransactionFramework.CreateSimpleTransaction("tx2", 1, "Genesis:0")) + tx1 := wallet.CreateBasicOutputsEquallyFromInputs("tx1", 1, "Genesis:0") + tx2 := wallet.CreateBasicOutputsEquallyFromInputs("tx2", 1, "Genesis:0") - ts.IssueBasicBlockAtSlotWithOptions("block1.1", 1, genesisCommitment, ts.DefaultBasicBlockIssuer(), node2, tx1) - ts.IssueBasicBlockAtSlotWithOptions("block1.2", 1, genesisCommitment, ts.DefaultBasicBlockIssuer(), node2, tx2) + wallet.SetDefaultNode(node2) + ts.IssueBasicBlockAtSlotWithOptions("block1.1", 1, wallet, tx1) + ts.IssueBasicBlockAtSlotWithOptions("block1.2", 1, wallet, tx2) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) - ts.AssertTransactionsInCacheBooked(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) - ts.AssertTransactionsInCachePending(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsExist(wallet.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCacheBooked(wallet.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCachePending(wallet.Transactions("tx1", "tx2"), true, node1, node2) ts.AssertBlocksInCacheConflicts(map[*blocks.Block][]string{ ts.Block("block1.1"): {"tx1"}, ts.Block("block1.2"): {"tx2"}, }, node1, node2) ts.AssertTransactionInCacheConflicts(map[*iotago.Transaction][]string{ - ts.TransactionFramework.Transaction("tx2"): {"tx2"}, - ts.TransactionFramework.Transaction("tx1"): {"tx1"}, + wallet.Transaction("tx2"): {"tx2"}, + wallet.Transaction("tx1"): {"tx1"}, }, node1, node2) } @@ -549,7 +558,7 @@ func Test_SpendPendingCommittedRace(t *testing.T) { ts.Block("block2.1"): {"tx1"}, ts.Block("block2.2"): {"tx2"}, }, node1, node2) - ts.AssertTransactionsInCachePending(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCachePending(wallet.Transactions("tx1", "tx2"), true, node1, node2) } // Advance both nodes at the edge of slot 1 committability @@ -604,8 +613,8 @@ func Test_SpendPendingCommittedRace(t *testing.T) { ts.IssueValidationBlockAtSlot("n2-pending-genesis", 5, genesisCommitment, node2, ts.BlockIDs("block2.1")...) ts.IssueValidationBlockAtSlot("n2-pending-commit1", 5, commitment1, node2, ts.BlockIDs("block2.1")...) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1"), true, node2) - ts.AssertTransactionsInCachePending(ts.TransactionFramework.Transactions("tx1"), true, node2) + ts.AssertTransactionsExist(wallet.Transactions("tx1"), true, node2) + ts.AssertTransactionsInCachePending(wallet.Transactions("tx1"), true, node2) ts.AssertBlocksInCacheBooked(ts.Blocks("n2-pending-genesis", "n2-pending-commit1"), true, node2) ts.AssertBlocksInCacheInvalid(ts.Blocks("n2-pending-genesis", "n2-pending-commit1"), false, node2) @@ -632,8 +641,9 @@ func Test_SpendPendingCommittedRace(t *testing.T) { ) // Exchange each-other blocks, ignoring invalidity - ts.IssueExistingBlock("n2-pending-genesis", ts.DefaultBasicBlockIssuer(), node1) - ts.IssueExistingBlock("n2-pending-commit1", ts.DefaultBasicBlockIssuer(), node1) + wallet.SetDefaultNode(node1) + ts.IssueExistingBlock("n2-pending-genesis", wallet) + ts.IssueExistingBlock("n2-pending-commit1", wallet) // The nodes agree on the results of the invalid blocks ts.AssertBlocksInCacheBooked(ts.Blocks("n2-pending-genesis", "n2-pending-commit1"), true, node1, node2) @@ -645,13 +655,13 @@ func Test_SpendPendingCommittedRace(t *testing.T) { ts.Block("n2-pending-commit1"): {}, // no conflits inherited as the block merges orphaned conflicts. }, node1, node2) - ts.AssertTransactionsInCachePending(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCachePending(wallet.Transactions("tx1", "tx2"), true, node1, node2) } // Commit further and test eviction of transactions { - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) - ts.AssertTransactionsInCachePending(ts.TransactionFramework.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsExist(wallet.Transactions("tx1", "tx2"), true, node1, node2) + ts.AssertTransactionsInCachePending(wallet.Transactions("tx1", "tx2"), true, node1, node2) ts.IssueBlocksAtSlots("", []iotago.SlotIndex{6, 7, 8, 9, 10}, 5, "5.1", ts.Nodes("node1", "node2"), false, nil) @@ -662,6 +672,6 @@ func Test_SpendPendingCommittedRace(t *testing.T) { testsuite.WithEvictedSlot(8), ) - ts.AssertTransactionsExist(ts.TransactionFramework.Transactions("tx1", "tx2"), false, node1, node2) + ts.AssertTransactionsExist(wallet.Transactions("tx1", "tx2"), false, node1, node2) } } diff --git a/pkg/tests/committee_rotation_test.go b/pkg/tests/committee_rotation_test.go index 56b4f397d..375a3c98f 100644 --- a/pkg/tests/committee_rotation_test.go +++ b/pkg/tests/committee_rotation_test.go @@ -5,10 +5,10 @@ import ( "github.com/iotaledger/hive.go/runtime/options" "github.com/iotaledger/iota-core/pkg/protocol" - "github.com/iotaledger/iota-core/pkg/protocol/snapshotcreator" "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager/topstakers" "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/sybilprotectionv1" "github.com/iotaledger/iota-core/pkg/testsuite" + "github.com/iotaledger/iota-core/pkg/testsuite/snapshotcreator" iotago "github.com/iotaledger/iota.go/v4" ) @@ -38,12 +38,13 @@ func Test_TopStakersRotation(t *testing.T) { ) defer ts.Shutdown() - ts.AddValidatorNode("node1", 1_000_006) + node1 := ts.AddValidatorNode("node1", 1_000_006) ts.AddValidatorNode("node2", 1_000_005) ts.AddValidatorNode("node3", 1_000_004) ts.AddValidatorNode("node4", 1_000_003) ts.AddValidatorNode("node5", 1_000_002) ts.AddValidatorNode("node6", 1_000_001) + ts.AddGenesisWallet("default", node1) nodeOptions := make(map[string][]options.Option[protocol.Protocol]) @@ -77,24 +78,24 @@ func Test_TopStakersRotation(t *testing.T) { ts.IssueBlocksAtSlots("wave-1:", []iotago.SlotIndex{1, 2, 3, 4}, 4, "Genesis", ts.Nodes(), true, nil) - ts.IssueCandidacyAnnouncementInSlot("node1-candidacy:1", 4, "wave-1:4.3", ts.Node("node1")) - ts.IssueCandidacyAnnouncementInSlot("node4-candidacy:1", 5, "node1-candidacy:1", ts.Node("node4")) + ts.IssueCandidacyAnnouncementInSlot("node1-candidacy:1", 4, "wave-1:4.3", ts.Wallet("node1")) + ts.IssueCandidacyAnnouncementInSlot("node4-candidacy:1", 5, "node1-candidacy:1", ts.Wallet("node4")) ts.IssueBlocksAtSlots("wave-2:", []iotago.SlotIndex{5, 6, 7, 8, 9}, 4, "node4-candidacy:1", ts.Nodes(), true, nil) - ts.IssueCandidacyAnnouncementInSlot("node4-candidacy:2", 9, "wave-2:9.3", ts.Node("node4")) - ts.IssueCandidacyAnnouncementInSlot("node5-candidacy:1", 9, "node4-candidacy:2", ts.Node("node5")) + ts.IssueCandidacyAnnouncementInSlot("node4-candidacy:2", 9, "wave-2:9.3", ts.Wallet("node4")) + ts.IssueCandidacyAnnouncementInSlot("node5-candidacy:1", 9, "node4-candidacy:2", ts.Wallet("node5")) // This candidacy should be considered as it's announced at the last possible slot. - ts.IssueCandidacyAnnouncementInSlot("node6-candidacy:1", 10, "node5-candidacy:1", ts.Node("node6")) + ts.IssueCandidacyAnnouncementInSlot("node6-candidacy:1", 10, "node5-candidacy:1", ts.Wallet("node6")) ts.IssueBlocksAtSlots("wave-3:", []iotago.SlotIndex{10}, 4, "node6-candidacy:1", ts.Nodes(), true, nil) // Those candidacies should not be considered as they're issued after EpochNearingThreshold (slot 10). - ts.IssueCandidacyAnnouncementInSlot("node2-candidacy:1", 11, "wave-3:10.3", ts.Node("node2")) - ts.IssueCandidacyAnnouncementInSlot("node3-candidacy:1", 11, "node2-candidacy:1", ts.Node("node3")) - ts.IssueCandidacyAnnouncementInSlot("node4-candidacy:3", 11, "node3-candidacy:1", ts.Node("node3")) - ts.IssueCandidacyAnnouncementInSlot("node5-candidacy:2", 11, "node4-candidacy:3", ts.Node("node3")) + ts.IssueCandidacyAnnouncementInSlot("node2-candidacy:1", 11, "wave-3:10.3", ts.Wallet("node2")) + ts.IssueCandidacyAnnouncementInSlot("node3-candidacy:1", 11, "node2-candidacy:1", ts.Wallet("node3")) + ts.IssueCandidacyAnnouncementInSlot("node4-candidacy:3", 11, "node3-candidacy:1", ts.Wallet("node3")) + ts.IssueCandidacyAnnouncementInSlot("node5-candidacy:2", 11, "node4-candidacy:3", ts.Wallet("node3")) // Assert that only candidates that issued before slot 11 are considered. ts.AssertSybilProtectionCandidates(1, []iotago.AccountID{ diff --git a/pkg/tests/loss_of_acceptance_test.go b/pkg/tests/loss_of_acceptance_test.go index ed26be402..a8bb02537 100644 --- a/pkg/tests/loss_of_acceptance_test.go +++ b/pkg/tests/loss_of_acceptance_test.go @@ -32,8 +32,8 @@ func TestLossOfAcceptanceFromGenesis(t *testing.T) { ) defer ts.Shutdown() - ts.AddBasicBlockIssuer("default") node0 := ts.AddValidatorNode("node0") + ts.AddGenesisWallet("default", node0) ts.AddValidatorNode("node1") ts.AddNode("node2") @@ -117,8 +117,8 @@ func TestLossOfAcceptanceFromSnapshot(t *testing.T) { ) defer ts.Shutdown() - ts.AddBasicBlockIssuer("default") node0 := ts.AddValidatorNode("node0") + ts.AddGenesisWallet("default", node0) ts.AddValidatorNode("node1") node2 := ts.AddNode("node2") @@ -211,8 +211,8 @@ func TestLossOfAcceptanceWithRestartFromDisk(t *testing.T) { ) defer ts.Shutdown() - ts.AddBasicBlockIssuer("default") node0 := ts.AddValidatorNode("node0") + ts.AddGenesisWallet("default", node0) ts.AddValidatorNode("node1") node2 := ts.AddNode("node2") diff --git a/pkg/tests/protocol_engine_switching_test.go b/pkg/tests/protocol_engine_switching_test.go index b0c262f19..aa6bf7834 100644 --- a/pkg/tests/protocol_engine_switching_test.go +++ b/pkg/tests/protocol_engine_switching_test.go @@ -56,7 +56,7 @@ func TestProtocol_EngineSwitching(t *testing.T) { node6 := ts.AddValidatorNode("node6") node7 := ts.AddValidatorNode("node7") node8 := ts.AddNode("node8") - ts.AddBasicBlockIssuer("default", iotago.MaxBlockIssuanceCredits/2) + ts.AddGenesisWallet("default", node0, iotago.MaxBlockIssuanceCredits/2) const expectedCommittedSlotAfterPartitionMerge = 19 nodesP1 := []*mock.Node{node0, node1, node2, node3, node4, node5} diff --git a/pkg/tests/protocol_startup_test.go b/pkg/tests/protocol_startup_test.go index 20e957be5..11b6672d1 100644 --- a/pkg/tests/protocol_startup_test.go +++ b/pkg/tests/protocol_startup_test.go @@ -141,7 +141,7 @@ func Test_StartNodeFromSnapshotAndDisk(t *testing.T) { nodeA := ts.AddValidatorNode("nodeA") nodeB := ts.AddValidatorNode("nodeB") ts.AddNode("nodeC") - ts.AddBasicBlockIssuer("default", iotago.MaxBlockIssuanceCredits/2) + ts.AddGenesisWallet("default", nodeA, iotago.MaxBlockIssuanceCredits/2) nodeOptions := []options.Option[protocol.Protocol]{ protocol.WithStorageOptions( diff --git a/pkg/tests/upgrade_signaling_test.go b/pkg/tests/upgrade_signaling_test.go index b5ce6336b..8f5ed29f3 100644 --- a/pkg/tests/upgrade_signaling_test.go +++ b/pkg/tests/upgrade_signaling_test.go @@ -22,6 +22,7 @@ import ( "github.com/iotaledger/iota-core/pkg/storage" "github.com/iotaledger/iota-core/pkg/storage/permanent" "github.com/iotaledger/iota-core/pkg/testsuite" + "github.com/iotaledger/iota-core/pkg/testsuite/mock" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/api" ) @@ -107,13 +108,13 @@ func Test_Upgrade_Signaling(t *testing.T) { ), ) - ts.AddValidatorNode("nodeA") + nodeA := ts.AddValidatorNode("nodeA") ts.AddValidatorNode("nodeB") ts.AddValidatorNode("nodeC") ts.AddValidatorNode("nodeD") ts.AddNode("nodeE") ts.AddNode("nodeF") - ts.AddBasicBlockIssuer("default", iotago.MaxBlockIssuanceCredits/2) + wallet := ts.AddGenesisWallet("default", nodeA, iotago.MaxBlockIssuanceCredits/2) ts.Run(true, map[string][]options.Option[protocol.Protocol]{ "nodeA": nodeOptionsWithoutV5, @@ -135,7 +136,7 @@ func Test_Upgrade_Signaling(t *testing.T) { ExpirySlot: iotago.MaxSlotIndex, OutputID: ts.AccountOutput("Genesis:1").OutputID(), BlockIssuerKeys: iotago.NewBlockIssuerKeys(iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(ts.Node("nodeA").Validator.PublicKey))), - ValidatorStake: testsuite.MinValidatorAccountAmount, + ValidatorStake: mock.MinValidatorAccountAmount, DelegationStake: 0, FixedCost: 0, StakeEndEpoch: iotago.MaxEpochIndex, @@ -143,11 +144,11 @@ func Test_Upgrade_Signaling(t *testing.T) { }, ts.Nodes()...) ts.AssertAccountData(&accounts.AccountData{ - ID: ts.DefaultBasicBlockIssuer().AccountID, + ID: wallet.BlockIssuer.AccountID, Credits: &accounts.BlockIssuanceCredits{Value: iotago.MaxBlockIssuanceCredits / 2, UpdateTime: 0}, ExpirySlot: iotago.MaxSlotIndex, OutputID: ts.AccountOutput("Genesis:5").OutputID(), - BlockIssuerKeys: iotago.NewBlockIssuerKeys(iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(ts.DefaultBasicBlockIssuer().PublicKey))), + BlockIssuerKeys: iotago.NewBlockIssuerKeys(iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(wallet.BlockIssuer.PublicKey))), ValidatorStake: 0, DelegationStake: 0, FixedCost: 0, @@ -169,7 +170,7 @@ func Test_Upgrade_Signaling(t *testing.T) { ExpirySlot: iotago.MaxSlotIndex, OutputID: ts.AccountOutput("Genesis:1").OutputID(), BlockIssuerKeys: iotago.NewBlockIssuerKeys(iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(ts.Node("nodeA").Validator.PublicKey))), - ValidatorStake: testsuite.MinValidatorAccountAmount, + ValidatorStake: mock.MinValidatorAccountAmount, DelegationStake: 0, FixedCost: 0, StakeEndEpoch: iotago.MaxEpochIndex, @@ -182,7 +183,7 @@ func Test_Upgrade_Signaling(t *testing.T) { ExpirySlot: iotago.MaxSlotIndex, OutputID: ts.AccountOutput("Genesis:4").OutputID(), BlockIssuerKeys: iotago.NewBlockIssuerKeys(iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(ts.Node("nodeD").Validator.PublicKey))), - ValidatorStake: testsuite.MinValidatorAccountAmount, + ValidatorStake: mock.MinValidatorAccountAmount, DelegationStake: 0, FixedCost: 0, StakeEndEpoch: iotago.MaxEpochIndex, @@ -203,7 +204,7 @@ func Test_Upgrade_Signaling(t *testing.T) { ExpirySlot: iotago.MaxSlotIndex, OutputID: ts.AccountOutput("Genesis:1").OutputID(), BlockIssuerKeys: iotago.NewBlockIssuerKeys(iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(ts.Node("nodeA").Validator.PublicKey))), - ValidatorStake: testsuite.MinValidatorAccountAmount, + ValidatorStake: mock.MinValidatorAccountAmount, DelegationStake: 0, FixedCost: 0, StakeEndEpoch: iotago.MaxEpochIndex, @@ -368,7 +369,7 @@ func Test_Upgrade_Signaling(t *testing.T) { ExpirySlot: iotago.MaxSlotIndex, OutputID: ts.AccountOutput("Genesis:1").OutputID(), BlockIssuerKeys: iotago.NewBlockIssuerKeys(iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(ts.Node("nodeA").Validator.PublicKey))), - ValidatorStake: testsuite.MinValidatorAccountAmount, + ValidatorStake: mock.MinValidatorAccountAmount, DelegationStake: 0, FixedCost: 0, StakeEndEpoch: iotago.MaxEpochIndex, @@ -381,7 +382,7 @@ func Test_Upgrade_Signaling(t *testing.T) { ExpirySlot: iotago.MaxSlotIndex, OutputID: ts.AccountOutput("Genesis:4").OutputID(), BlockIssuerKeys: iotago.NewBlockIssuerKeys(iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(ts.Node("nodeD").Validator.PublicKey))), - ValidatorStake: testsuite.MinValidatorAccountAmount, + ValidatorStake: mock.MinValidatorAccountAmount, DelegationStake: 0, FixedCost: 0, StakeEndEpoch: iotago.MaxEpochIndex, diff --git a/pkg/testsuite/blocks.go b/pkg/testsuite/blocks.go index cf6c89cc5..90e5c70ac 100644 --- a/pkg/testsuite/blocks.go +++ b/pkg/testsuite/blocks.go @@ -130,7 +130,7 @@ func (t *TestSuite) AssertBlocksInCacheConflicts(blockConflicts map[*blocks.Bloc return ierrors.Errorf("AssertBlocksInCacheConflicts: %s: block %s is root block", node.Name, blockFromCache.ID()) } - expectedConflictIDs := ds.NewSet(lo.Map(conflictAliases, t.TransactionFramework.TransactionID)...) + expectedConflictIDs := ds.NewSet(lo.Map(conflictAliases, t.DefaultWallet().TransactionID)...) actualConflictIDs := blockFromCache.ConflictIDs() if expectedConflictIDs.Size() != actualConflictIDs.Size() { diff --git a/pkg/testsuite/conflicts.go b/pkg/testsuite/conflicts.go index 7c7047da0..b1fa92989 100644 --- a/pkg/testsuite/conflicts.go +++ b/pkg/testsuite/conflicts.go @@ -13,7 +13,7 @@ func (t *TestSuite) AssertConflictsInCacheAcceptanceState(expectedConflictAliase for _, node := range nodes { for _, conflictAlias := range expectedConflictAliases { t.Eventually(func() error { - acceptanceState := node.Protocol.MainEngineInstance().Ledger.ConflictDAG().AcceptanceState(ds.NewSet(t.TransactionFramework.TransactionID(conflictAlias))) + acceptanceState := node.Protocol.MainEngineInstance().Ledger.ConflictDAG().AcceptanceState(ds.NewSet(t.DefaultWallet().TransactionID(conflictAlias))) if acceptanceState != expectedState { return ierrors.Errorf("assertTransactionsInCacheWithFunc: %s: conflict %s is %s, but expected %s", node.Name, conflictAlias, acceptanceState, expectedState) diff --git a/pkg/testsuite/mock/blockissuer.go b/pkg/testsuite/mock/blockissuer.go index 527bcff19..b29b015ba 100644 --- a/pkg/testsuite/mock/blockissuer.go +++ b/pkg/testsuite/mock/blockissuer.go @@ -12,6 +12,7 @@ import ( "golang.org/x/crypto/blake2b" "github.com/iotaledger/hive.go/core/safemath" + hiveEd25519 "github.com/iotaledger/hive.go/crypto/ed25519" "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/runtime/event" @@ -45,14 +46,15 @@ type BlockIssuer struct { Name string Validator bool + AccountID iotago.AccountID + OutputID iotago.OutputID + PublicKey ed25519.PublicKey + privateKey ed25519.PrivateKey + events *Events workerPool *workerpool.WorkerPool - privateKey ed25519.PrivateKey - PublicKey ed25519.PublicKey - AccountID iotago.AccountID - optsTipSelectionTimeout time.Duration optsTipSelectionRetryInterval time.Duration // optsIncompleteBlockAccepted defines whether the node allows filling in incomplete block and issuing it for user. @@ -60,13 +62,12 @@ type BlockIssuer struct { optsRateSetterEnabled bool } -func NewBlockIssuer(t *testing.T, name string, validator bool, opts ...options.Option[BlockIssuer]) *BlockIssuer { - pub, priv, err := ed25519.GenerateKey(nil) - if err != nil { - panic(err) - } +func NewBlockIssuer(t *testing.T, name string, keyManager *KeyManager, accountID iotago.AccountID, validator bool, opts ...options.Option[BlockIssuer]) *BlockIssuer { + priv, pub := keyManager.KeyPair() - accountID := iotago.AccountID(blake2b.Sum256(pub)) + if accountID == iotago.EmptyAccountID { + accountID = iotago.AccountID(blake2b.Sum256(pub)) + } accountID.RegisterAlias(name) return options.Apply(&BlockIssuer{ @@ -85,6 +86,10 @@ func NewBlockIssuer(t *testing.T, name string, validator bool, opts ...options.O }, opts) } +func (i *BlockIssuer) BlockIssuerKeys() iotago.BlockIssuerKeys { + return iotago.NewBlockIssuerKeys(iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(hiveEd25519.PublicKey(i.PublicKey))) +} + // Shutdown shuts down the block issuer. func (i *BlockIssuer) Shutdown() { i.workerPool.Shutdown() diff --git a/pkg/testsuite/mock/hdwallet.go b/pkg/testsuite/mock/hdwallet.go deleted file mode 100644 index a4e1ceb4e..000000000 --- a/pkg/testsuite/mock/hdwallet.go +++ /dev/null @@ -1,138 +0,0 @@ -package mock - -import ( - "crypto/ed25519" - "fmt" - - "github.com/wollac/iota-crypto-demo/pkg/bip32path" - "github.com/wollac/iota-crypto-demo/pkg/slip10" - "github.com/wollac/iota-crypto-demo/pkg/slip10/eddsa" - - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/iota-core/pkg/protocol/engine/utxoledger" - iotago "github.com/iotaledger/iota.go/v4" -) - -const ( - pathString = "44'/4218'/0'/%d'" -) - -type HDWallet struct { - name string - seed []byte - index uint64 - utxo []*utxoledger.Output -} - -func NewHDWallet(name string, seed []byte, index uint64) *HDWallet { - return &HDWallet{ - name: name, - seed: seed, - index: index, - utxo: make([]*utxoledger.Output, 0), - } -} - -func (hd *HDWallet) BookSpents(spentOutputs []*utxoledger.Output) { - for _, spent := range spentOutputs { - hd.BookSpent(spent) - } -} - -func (hd *HDWallet) BookSpent(spentOutput *utxoledger.Output) { - newUtxo := make([]*utxoledger.Output, 0) - for _, u := range hd.utxo { - if u.OutputID() == spentOutput.OutputID() { - fmt.Printf("%s spent %s\n", hd.name, u.OutputID().ToHex()) - - continue - } - newUtxo = append(newUtxo, u) - } - hd.utxo = newUtxo -} - -func (hd *HDWallet) Name() string { - return hd.name -} - -func (hd *HDWallet) Balance() iotago.BaseToken { - var balance iotago.BaseToken - for _, u := range hd.utxo { - balance += u.BaseTokenAmount() - } - - return balance -} - -func (hd *HDWallet) BookOutput(output *utxoledger.Output) { - if output != nil { - fmt.Printf("%s book %s\n", hd.name, output.OutputID().ToHex()) - hd.utxo = append(hd.utxo, output) - } -} - -// KeyPair calculates an ed25519 key pair by using slip10. -func (hd *HDWallet) KeyPair() (ed25519.PrivateKey, ed25519.PublicKey) { - path, err := bip32path.ParsePath(fmt.Sprintf(pathString, hd.index)) - if err != nil { - panic(err) - } - - curve := eddsa.Ed25519() - key, err := slip10.DeriveKeyFromPath(hd.seed, curve, path) - if err != nil { - panic(err) - } - - pubKey, privKey := key.Key.(eddsa.Seed).Ed25519Key() - - return ed25519.PrivateKey(privKey), ed25519.PublicKey(pubKey) -} - -func (hd *HDWallet) AddressSigner() iotago.AddressSigner { - privKey, pubKey := hd.KeyPair() - address := iotago.Ed25519AddressFromPubKey(pubKey) - - return iotago.NewInMemoryAddressSigner(iotago.NewAddressKeysForEd25519Address(address, privKey)) -} - -func (hd *HDWallet) Outputs() []*utxoledger.Output { - return hd.utxo -} - -// Address calculates an ed25519 address by using slip10. -func (hd *HDWallet) Address(addressType ...iotago.AddressType) iotago.DirectUnlockableAddress { - _, pubKey := hd.KeyPair() - - addrType := iotago.AddressEd25519 - if len(addressType) > 0 { - addrType = addressType[0] - } - - switch addrType { - case iotago.AddressEd25519: - return iotago.Ed25519AddressFromPubKey(pubKey) - case iotago.AddressImplicitAccountCreation: - return iotago.ImplicitAccountCreationAddressFromPubKey(pubKey) - default: - panic(ierrors.Wrapf(iotago.ErrUnknownAddrType, "type %d", addressType)) - } -} - -func (hd *HDWallet) PrintStatus() { - var status string - status += fmt.Sprintf("Name: %s\n", hd.name) - status += fmt.Sprintf("Address: %s\n", hd.Address().Bech32(iotago.PrefixTestnet)) - status += fmt.Sprintf("Balance: %d\n", hd.Balance()) - status += "Outputs: \n" - for _, u := range hd.utxo { - nativeTokenDescription := "" - nativeTokenFeature := u.Output().FeatureSet().NativeToken() - if nativeTokenFeature != nil { - nativeTokenDescription += fmt.Sprintf("%s: %s, ", nativeTokenFeature.ID.ToHex(), nativeTokenFeature.Amount) - } - status += fmt.Sprintf("\t%s [%s] = %d %v\n", u.OutputID().ToHex(), u.OutputType(), u.BaseTokenAmount(), nativeTokenDescription) - } - fmt.Printf("%s\n", status) -} diff --git a/pkg/testsuite/mock/keymanager.go b/pkg/testsuite/mock/keymanager.go new file mode 100644 index 000000000..236a50551 --- /dev/null +++ b/pkg/testsuite/mock/keymanager.go @@ -0,0 +1,75 @@ +package mock + +import ( + "crypto/ed25519" + "fmt" + + "github.com/wollac/iota-crypto-demo/pkg/bip32path" + "github.com/wollac/iota-crypto-demo/pkg/slip10" + "github.com/wollac/iota-crypto-demo/pkg/slip10/eddsa" + + "github.com/iotaledger/hive.go/ierrors" + iotago "github.com/iotaledger/iota.go/v4" +) + +const ( + pathString = "44'/4218'/0'/%d'" +) + +// KeyManager is a hierarchical deterministic key manager. +type KeyManager struct { + seed []byte + index uint64 +} + +func NewKeyManager(seed []byte, index uint64) *KeyManager { + return &KeyManager{ + seed: seed, + index: index, + } +} + +// KeyPair calculates an ed25519 key pair by using slip10. +func (k *KeyManager) KeyPair() (ed25519.PrivateKey, ed25519.PublicKey) { + path, err := bip32path.ParsePath(fmt.Sprintf(pathString, k.index)) + if err != nil { + panic(err) + } + + curve := eddsa.Ed25519() + key, err := slip10.DeriveKeyFromPath(k.seed, curve, path) + if err != nil { + panic(err) + } + + pubKey, privKey := key.Key.(eddsa.Seed).Ed25519Key() + + return ed25519.PrivateKey(privKey), ed25519.PublicKey(pubKey) +} + +// AddressSigner returns an address signer. +func (k *KeyManager) AddressSigner() iotago.AddressSigner { + privKey, pubKey := k.KeyPair() + + // add both address types for simplicity in tests + ed25519Address := iotago.Ed25519AddressFromPubKey(pubKey) + ed25519AddressKey := iotago.NewAddressKeysForEd25519Address(ed25519Address, privKey) + implicitAccountCreationAddress := iotago.ImplicitAccountCreationAddressFromPubKey(pubKey) + implicitAccountCreationAddressKey := iotago.NewAddressKeysForImplicitAccountCreationAddress(implicitAccountCreationAddress, privKey) + + return iotago.NewInMemoryAddressSigner(ed25519AddressKey, implicitAccountCreationAddressKey) +} + +// Address calculates an address of the specified type. +func (k *KeyManager) Address(addressType iotago.AddressType) iotago.DirectUnlockableAddress { + _, pubKey := k.KeyPair() + + switch addressType { + case iotago.AddressEd25519: + return iotago.Ed25519AddressFromPubKey(pubKey) + case iotago.AddressImplicitAccountCreation: + return iotago.ImplicitAccountCreationAddressFromPubKey(pubKey) + default: + panic(ierrors.Wrapf(iotago.ErrUnknownAddrType, "type %d", addressType)) + } +} diff --git a/pkg/testsuite/mock/node.go b/pkg/testsuite/mock/node.go index afca4da93..061730ff9 100644 --- a/pkg/testsuite/mock/node.go +++ b/pkg/testsuite/mock/node.go @@ -2,7 +2,6 @@ package mock import ( "context" - "crypto/ed25519" "fmt" "sync/atomic" "testing" @@ -29,6 +28,7 @@ import ( "github.com/iotaledger/iota-core/pkg/protocol/engine/notarization" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/merklehasher" + "github.com/iotaledger/iota.go/v4/tpkg" ) // idAliases contains a list of aliases registered for a set of IDs. @@ -48,8 +48,9 @@ func UnregisterIDAliases() { type Node struct { Testing *testing.T - Name string - Validator *BlockIssuer + Name string + Validator *BlockIssuer + KeyManager *KeyManager ctx context.Context ctxCancel context.CancelFunc @@ -73,10 +74,9 @@ type Node struct { } func NewNode(t *testing.T, net *Network, partition string, name string, validator bool) *Node { - pub, priv, err := ed25519.GenerateKey(nil) - if err != nil { - panic(err) - } + seed := tpkg.RandEd25519Seed() + keyManager := NewKeyManager(seed[:], 0) + priv, pub := keyManager.KeyPair() accountID := iotago.AccountID(blake2b.Sum256(pub)) accountID.RegisterAlias(name) @@ -86,7 +86,7 @@ func NewNode(t *testing.T, net *Network, partition string, name string, validato var validatorBlockIssuer *BlockIssuer if validator { - validatorBlockIssuer = NewBlockIssuer(t, name, validator) + validatorBlockIssuer = NewBlockIssuer(t, name, keyManager, accountID, validator) } else { validatorBlockIssuer = nil } @@ -96,7 +96,8 @@ func NewNode(t *testing.T, net *Network, partition string, name string, validato Name: name, - Validator: validatorBlockIssuer, + Validator: validatorBlockIssuer, + KeyManager: keyManager, PeerID: peerID, @@ -515,3 +516,11 @@ func (n *Node) AttachedBlocks() []*blocks.Block { return n.attachedBlocks } + +func (n *Node) IssueValidationBlock(ctx context.Context, alias string, opts ...options.Option[ValidatorBlockParams]) *blocks.Block { + if n.Validator == nil { + panic("node is not a validator") + } + + return n.Validator.IssueValidationBlock(ctx, alias, n, opts...) +} diff --git a/pkg/testsuite/mock/utils.go b/pkg/testsuite/mock/utils.go new file mode 100644 index 000000000..f7cc8adcd --- /dev/null +++ b/pkg/testsuite/mock/utils.go @@ -0,0 +1,221 @@ +package mock + +import ( + "github.com/iotaledger/hive.go/runtime/options" + "github.com/iotaledger/iota-core/pkg/protocol/engine/utxoledger" + iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/builder" +) + +const MinIssuerAccountAmount = iotago.BaseToken(372900) +const MinValidatorAccountAmount = iotago.BaseToken(722800) +const AccountConversionManaCost = iotago.Mana(1000000) + +// TransactionBuilder options + +func WithInputs(inputs utxoledger.Outputs) options.Option[builder.TransactionBuilder] { + return func(txBuilder *builder.TransactionBuilder) { + for _, input := range inputs { + switch input.OutputType() { + case iotago.OutputFoundry: + // For foundries we need to unlock the account output + txBuilder.AddInput(&builder.TxInput{ + UnlockTarget: input.Output().UnlockConditionSet().ImmutableAccount().Address, + InputID: input.OutputID(), + Input: input.Output(), + }) + case iotago.OutputAccount: + // For alias we need to unlock the state controller + txBuilder.AddInput(&builder.TxInput{ + UnlockTarget: input.Output().UnlockConditionSet().StateControllerAddress().Address, + InputID: input.OutputID(), + Input: input.Output(), + }) + default: + txBuilder.AddInput(&builder.TxInput{ + UnlockTarget: input.Output().UnlockConditionSet().Address().Address, + InputID: input.OutputID(), + Input: input.Output(), + }) + } + } + } +} + +func WithAccountInput(input *utxoledger.Output, governorTransition bool) options.Option[builder.TransactionBuilder] { + return func(txBuilder *builder.TransactionBuilder) { + switch input.OutputType() { + case iotago.OutputAccount: + address := input.Output().UnlockConditionSet().StateControllerAddress().Address + if governorTransition { + address = input.Output().UnlockConditionSet().GovernorAddress().Address + } + txBuilder.AddInput(&builder.TxInput{ + UnlockTarget: address, + InputID: input.OutputID(), + Input: input.Output(), + }) + default: + panic("only OutputAccount can be added as account input") + } + } +} + +func WithAllotments(allotments iotago.Allotments) options.Option[builder.TransactionBuilder] { + return func(txBuilder *builder.TransactionBuilder) { + for _, allotment := range allotments { + txBuilder.IncreaseAllotment(allotment.AccountID, allotment.Mana) + } + } +} + +func WithSlotCreated(creationSlot iotago.SlotIndex) options.Option[builder.TransactionBuilder] { + return func(txBuilder *builder.TransactionBuilder) { + txBuilder.SetCreationSlot(creationSlot) + } +} + +func WithContextInputs(contextInputs iotago.TxEssenceContextInputs) options.Option[builder.TransactionBuilder] { + return func(txBuilder *builder.TransactionBuilder) { + for _, input := range contextInputs { + txBuilder.AddContextInput(input) + } + } +} + +func WithOutputs(outputs iotago.Outputs[iotago.Output]) options.Option[builder.TransactionBuilder] { + return func(txBuilder *builder.TransactionBuilder) { + for _, output := range outputs { + txBuilder.AddOutput(output) + } + } +} + +func WithTaggedDataPayload(payload *iotago.TaggedData) options.Option[builder.TransactionBuilder] { + return func(txBuilder *builder.TransactionBuilder) { + txBuilder.AddTaggedDataPayload(payload) + } +} + +// DelegationOutput options + +func WithDelegatedAmount(delegatedAmount iotago.BaseToken) options.Option[builder.DelegationOutputBuilder] { + return func(delegationBuilder *builder.DelegationOutputBuilder) { + delegationBuilder.DelegatedAmount(delegatedAmount) + } +} + +func WithDelegatedValidatorAddress(validatorAddress *iotago.AccountAddress) options.Option[builder.DelegationOutputBuilder] { + return func(delegationBuilder *builder.DelegationOutputBuilder) { + delegationBuilder.ValidatorAddress(validatorAddress) + } +} + +func WithDelegationStartEpoch(startEpoch iotago.EpochIndex) options.Option[builder.DelegationOutputBuilder] { + return func(delegationBuilder *builder.DelegationOutputBuilder) { + delegationBuilder.StartEpoch(startEpoch) + } +} + +func WithDelegationEndEpoch(endEpoch iotago.EpochIndex) options.Option[builder.DelegationOutputBuilder] { + return func(delegationBuilder *builder.DelegationOutputBuilder) { + delegationBuilder.EndEpoch(endEpoch) + } +} + +func WithDelegationConditions(delegationConditions iotago.DelegationOutputUnlockConditions) options.Option[builder.DelegationOutputBuilder] { + return func(delegationBuilder *builder.DelegationOutputBuilder) { + delegationBuilder.Address(delegationConditions.MustSet().Address().Address) + } +} + +func WithDelegationAmount(amount iotago.BaseToken) options.Option[builder.DelegationOutputBuilder] { + return func(delegationBuilder *builder.DelegationOutputBuilder) { + delegationBuilder.Amount(amount) + } +} + +// BlockIssuer options + +func WithBlockIssuerFeature(keys iotago.BlockIssuerKeys, expirySlot iotago.SlotIndex) options.Option[builder.AccountOutputBuilder] { + return func(accountBuilder *builder.AccountOutputBuilder) { + accountBuilder.BlockIssuer(keys, expirySlot) + } +} + +func WithAddBlockIssuerKey(key iotago.BlockIssuerKey) options.Option[builder.AccountOutputBuilder] { + return func(accountBuilder *builder.AccountOutputBuilder) { + transition := accountBuilder.GovernanceTransition() + transition.BlockIssuerTransition().AddKeys(key) + } +} + +func WithBlockIssuerKeys(keys iotago.BlockIssuerKeys) options.Option[builder.AccountOutputBuilder] { + return func(accountBuilder *builder.AccountOutputBuilder) { + transition := accountBuilder.GovernanceTransition() + transition.BlockIssuerTransition().Keys(keys) + } +} + +func WithBlockIssuerExpirySlot(expirySlot iotago.SlotIndex) options.Option[builder.AccountOutputBuilder] { + return func(accountBuilder *builder.AccountOutputBuilder) { + transition := accountBuilder.GovernanceTransition() + transition.BlockIssuerTransition().ExpirySlot(expirySlot) + } +} + +func WithStakingFeature(amount iotago.BaseToken, fixedCost iotago.Mana, startEpoch iotago.EpochIndex, optEndEpoch ...iotago.EpochIndex) options.Option[builder.AccountOutputBuilder] { + return func(accountBuilder *builder.AccountOutputBuilder) { + accountBuilder.Staking(amount, fixedCost, startEpoch, optEndEpoch...) + } +} + +// Account options + +func WithAccountMana(mana iotago.Mana) options.Option[builder.AccountOutputBuilder] { + return func(accountBuilder *builder.AccountOutputBuilder) { + accountBuilder.Mana(mana) + } +} + +func WithAccountAmount(amount iotago.BaseToken) options.Option[builder.AccountOutputBuilder] { + return func(accountBuilder *builder.AccountOutputBuilder) { + accountBuilder.Amount(amount) + } +} + +func WithAccountIncreasedFoundryCounter(diff uint32) options.Option[builder.AccountOutputBuilder] { + return func(accountBuilder *builder.AccountOutputBuilder) { + accountBuilder.FoundriesToGenerate(diff) + } +} + +func WithAccountImmutableFeatures(features iotago.AccountOutputImmFeatures) options.Option[builder.AccountOutputBuilder] { + return func(accountBuilder *builder.AccountOutputBuilder) { + for _, feature := range features.MustSet() { + switch feature.Type() { + case iotago.FeatureMetadata: + //nolint:forcetypeassert + accountBuilder.ImmutableMetadata(feature.(*iotago.MetadataFeature).Data) + case iotago.FeatureSender: + //nolint:forcetypeassert + accountBuilder.ImmutableSender(feature.(*iotago.SenderFeature).Address) + } + } + } +} + +func WithAccountConditions(conditions iotago.AccountOutputUnlockConditions) options.Option[builder.AccountOutputBuilder] { + return func(accountBuilder *builder.AccountOutputBuilder) { + for _, condition := range conditions.MustSet() { + switch condition.Type() { + case iotago.UnlockConditionStateControllerAddress: + //nolint:forcetypeassert + accountBuilder.StateController(condition.(*iotago.StateControllerAddressUnlockCondition).Address) + case iotago.UnlockConditionGovernorAddress: + //nolint:forcetypeassert + accountBuilder.Governor(condition.(*iotago.GovernorAddressUnlockCondition).Address) + } + } + } +} diff --git a/pkg/testsuite/mock/wallet.go b/pkg/testsuite/mock/wallet.go new file mode 100644 index 000000000..b161ed1af --- /dev/null +++ b/pkg/testsuite/mock/wallet.go @@ -0,0 +1,131 @@ +package mock + +import ( + "crypto/ed25519" + "testing" + + "github.com/iotaledger/hive.go/ierrors" + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/iota-core/pkg/protocol/engine/utxoledger" + iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/tpkg" +) + +// Wallet is an object representing a wallet (similar to a FireFly wallet) capable of the following: +// - hierarchical deterministic key management +// - signing transactions +// - signing blocks +// - keeping track of unspent outputs. +type Wallet struct { + Testing *testing.T + + Name string + + Node *Node + + keyManager *KeyManager + + BlockIssuer *BlockIssuer + + outputs map[string]*utxoledger.Output + transactions map[string]*iotago.Transaction +} + +func NewWallet(t *testing.T, name string, node *Node, keyManager ...*KeyManager) *Wallet { + var km *KeyManager + if len(keyManager) == 0 { + randomSeed := tpkg.RandEd25519Seed() + km = NewKeyManager(randomSeed[:], 0) + } else { + km = keyManager[0] + } + + return &Wallet{ + Testing: t, + Name: name, + Node: node, + outputs: make(map[string]*utxoledger.Output), + transactions: make(map[string]*iotago.Transaction), + keyManager: km, + } +} + +func (w *Wallet) SetBlockIssuer(accountID iotago.AccountID) { + w.BlockIssuer = NewBlockIssuer(w.Testing, w.Name, w.keyManager, accountID, false) +} + +func (w *Wallet) SetDefaultNode(node *Node) { + w.Node = node +} + +func (w *Wallet) AddOutput(outputName string, output *utxoledger.Output) { + w.outputs[outputName] = output +} + +func (w *Wallet) Balance() iotago.BaseToken { + var balance iotago.BaseToken + for _, output := range w.outputs { + balance += output.BaseTokenAmount() + } + + return balance +} + +func (w *Wallet) Output(outputName string) *utxoledger.Output { + output, exists := w.outputs[outputName] + if !exists { + panic(ierrors.Errorf("output %s not registered in wallet %s", outputName, w.Name)) + } + + return output +} + +func (w *Wallet) AccountOutput(outputName string) *utxoledger.Output { + output := w.Output(outputName) + if _, ok := output.Output().(*iotago.AccountOutput); !ok { + panic(ierrors.Errorf("output %s is not an account output", outputName)) + } + + return output +} + +func (w *Wallet) Transaction(alias string) *iotago.Transaction { + transaction, exists := w.transactions[alias] + if !exists { + panic(ierrors.Errorf("transaction with given alias does not exist %s", alias)) + } + + return transaction +} + +func (w *Wallet) Transactions(transactionNames ...string) []*iotago.Transaction { + return lo.Map(transactionNames, w.Transaction) +} + +func (w *Wallet) TransactionID(alias string) iotago.TransactionID { + return lo.PanicOnErr(w.Transaction(alias).ID()) +} + +func (w *Wallet) Address() iotago.DirectUnlockableAddress { + address := w.keyManager.Address(iotago.AddressEd25519) + //nolint:forcetypeassert + return address.(*iotago.Ed25519Address) +} + +func (w *Wallet) ImplicitAccountCreationAddress() *iotago.ImplicitAccountCreationAddress { + address := w.keyManager.Address(iotago.AddressImplicitAccountCreation) + //nolint:forcetypeassert + return address.(*iotago.ImplicitAccountCreationAddress) +} + +func (w *Wallet) HasAddress(address iotago.Address) bool { + return address.Equal(w.Address()) || address.Equal(w.ImplicitAccountCreationAddress()) +} + +func (w *Wallet) KeyPair() (ed25519.PrivateKey, ed25519.PublicKey) { + return w.keyManager.KeyPair() +} + +func (w *Wallet) AddressSigner() iotago.AddressSigner { + return w.keyManager.AddressSigner() +} diff --git a/pkg/testsuite/mock/wallet_blocks.go b/pkg/testsuite/mock/wallet_blocks.go new file mode 100644 index 000000000..22fe8bea4 --- /dev/null +++ b/pkg/testsuite/mock/wallet_blocks.go @@ -0,0 +1,12 @@ +package mock + +import ( + "context" + + "github.com/iotaledger/hive.go/runtime/options" + "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" +) + +func (w *Wallet) IssueBasicBlock(ctx context.Context, blockName string, opts ...options.Option[BasicBlockParams]) *blocks.Block { + return w.BlockIssuer.IssueBasicBlock(ctx, blockName, w.Node, opts...) +} diff --git a/pkg/testsuite/mock/wallet_transactions.go b/pkg/testsuite/mock/wallet_transactions.go new file mode 100644 index 000000000..6b3481046 --- /dev/null +++ b/pkg/testsuite/mock/wallet_transactions.go @@ -0,0 +1,362 @@ +package mock + +import ( + "fmt" + "time" + + "github.com/iotaledger/hive.go/ierrors" + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/hive.go/runtime/options" + "github.com/iotaledger/iota-core/pkg/protocol/engine/utxoledger" + iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/builder" + "github.com/iotaledger/iota.go/v4/tpkg" +) + +// Functionality for creating transactions in the mock wallet. + +func (w *Wallet) CreateAccountFromInput(transactionName string, inputName string, recipientWallet *Wallet, creationSlot iotago.SlotIndex, opts ...options.Option[builder.AccountOutputBuilder]) *iotago.SignedTransaction { + input := w.Output(inputName) + + accountOutput := options.Apply(builder.NewAccountOutputBuilder(recipientWallet.Address(), recipientWallet.Address(), input.BaseTokenAmount()). + Mana(input.StoredMana()), + opts).MustBuild() + + outputStates := iotago.Outputs[iotago.Output]{accountOutput} + + // if amount was set by options, a remainder output needs to be created + if accountOutput.Amount != input.BaseTokenAmount() { + remainderOutput := &iotago.BasicOutput{ + Amount: input.BaseTokenAmount() - accountOutput.Amount, + Mana: input.StoredMana() - accountOutput.Mana, + Conditions: iotago.BasicOutputUnlockConditions{ + &iotago.AddressUnlockCondition{Address: recipientWallet.Address()}, + }, + Features: iotago.BasicOutputFeatures{}, + } + outputStates = append(outputStates, remainderOutput) + } + + signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions( + transactionName, + WithContextInputs(iotago.TxEssenceContextInputs{ + &iotago.CommitmentInput{ + CommitmentID: w.Node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment().MustID(), + }, + }), + WithInputs(utxoledger.Outputs{input}), + WithOutputs(outputStates), + WithSlotCreated(creationSlot), + )) + + // register the outputs in the recipient wallet (so wallet doesn't have to scan for outputs on its addresses) + recipientWallet.registerOutputs(transactionName, signedTransaction.Transaction) + + return signedTransaction +} + +// CreateDelegationFromInput creates a new DelegationOutput with given options from an input. If the remainder Output +// is not created, then StoredMana from the input is not passed and can potentially be burned. +// In order not to burn it, it needs to be assigned manually in another output in the transaction. +func (w *Wallet) CreateDelegationFromInput(transactionName string, inputName string, creationSlot iotago.SlotIndex, opts ...options.Option[builder.DelegationOutputBuilder]) *iotago.SignedTransaction { + input := w.Output(inputName) + + delegationOutput := options.Apply(builder.NewDelegationOutputBuilder(&iotago.AccountAddress{}, w.Address(), input.BaseTokenAmount()). + DelegatedAmount(input.BaseTokenAmount()), + opts).MustBuild() + + if delegationOutput.ValidatorAddress.AccountID() == iotago.EmptyAccountID || + delegationOutput.DelegatedAmount == 0 || + delegationOutput.StartEpoch == 0 { + panic(fmt.Sprintf("delegation output created incorrectly %+v", delegationOutput)) + } + + outputStates := iotago.Outputs[iotago.Output]{delegationOutput} + + // if options set an Amount, a remainder output needs to be created + if delegationOutput.Amount != input.BaseTokenAmount() { + outputStates = append(outputStates, &iotago.BasicOutput{ + Amount: input.BaseTokenAmount() - delegationOutput.Amount, + Mana: input.StoredMana(), + Conditions: iotago.BasicOutputUnlockConditions{ + &iotago.AddressUnlockCondition{Address: w.Address()}, + }, + Features: iotago.BasicOutputFeatures{}, + }) + } + + // create the signed transaction + signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions( + transactionName, + WithContextInputs(iotago.TxEssenceContextInputs{ + &iotago.CommitmentInput{ + CommitmentID: w.Node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment().MustID(), + }, + }), + WithInputs(utxoledger.Outputs{input}), + WithOutputs(outputStates), + WithSlotCreated(creationSlot), + )) + + return signedTransaction +} + +// DelayedClaimingTransition transitions DelegationOutput into delayed claiming state by setting DelegationID and EndEpoch. +func (w *Wallet) DelayedClaimingTransition(transactionName string, inputName string, creationSlot iotago.SlotIndex, delegationEndEpoch iotago.EpochIndex) *iotago.SignedTransaction { + input := w.Output(inputName) + if input.OutputType() != iotago.OutputDelegation { + panic(ierrors.Errorf("%s is not a delegation output, cannot transition to delayed claiming state", inputName)) + } + + prevOutput, ok := input.Output().Clone().(*iotago.DelegationOutput) + if !ok { + panic(ierrors.Errorf("cloned output %s is not a delegation output, cannot transition to delayed claiming state", inputName)) + } + + delegationBuilder := builder.NewDelegationOutputBuilderFromPrevious(prevOutput).EndEpoch(delegationEndEpoch) + if prevOutput.DelegationID == iotago.EmptyDelegationID() { + delegationBuilder.DelegationID(iotago.DelegationIDFromOutputID(input.OutputID())) + } + + delegationOutput := delegationBuilder.MustBuild() + + signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions( + transactionName, + WithContextInputs(iotago.TxEssenceContextInputs{ + &iotago.CommitmentInput{ + CommitmentID: w.Node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment().MustID(), + }, + }), + WithInputs(utxoledger.Outputs{input}), + WithOutputs(iotago.Outputs[iotago.Output]{delegationOutput}), + WithSlotCreated(creationSlot), + )) + + return signedTransaction +} + +func (w *Wallet) TransitionAccount(transactionName string, inputName string, opts ...options.Option[builder.AccountOutputBuilder]) *iotago.SignedTransaction { + input, exists := w.outputs[inputName] + if !exists { + panic(fmt.Sprintf("account with alias %s does not exist", inputName)) + } + + accountOutput, ok := input.Output().Clone().(*iotago.AccountOutput) + if !ok { + panic(fmt.Sprintf("output with alias %s is not *iotago.AccountOutput", inputName)) + } + + accountBuilder := builder.NewAccountOutputBuilderFromPrevious(accountOutput) + accountOutput = options.Apply(accountBuilder, opts).MustBuild() + + signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions( + transactionName, + WithAccountInput(input, true), + WithContextInputs(iotago.TxEssenceContextInputs{ + &iotago.BlockIssuanceCreditInput{ + AccountID: accountOutput.AccountID, + }, + &iotago.CommitmentInput{ + CommitmentID: w.Node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment().MustID(), + }, + }), + WithOutputs(iotago.Outputs[iotago.Output]{accountOutput}), + )) + + return signedTransaction +} + +func (w *Wallet) DestroyAccount(transactionName string, inputName string, creationSlot iotago.SlotIndex) *iotago.SignedTransaction { + input := w.Output(inputName) + inputAccount, ok := input.Output().(*iotago.AccountOutput) + if !ok { + panic(fmt.Sprintf("output with alias %s is not *iotago.AccountOutput", inputName)) + } + + destructionOutputs := iotago.Outputs[iotago.Output]{&iotago.BasicOutput{ + Amount: input.BaseTokenAmount(), + Mana: input.StoredMana(), + Conditions: iotago.BasicOutputUnlockConditions{ + &iotago.AddressUnlockCondition{Address: w.Address()}, + }, + Features: iotago.BasicOutputFeatures{}, + }} + + signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions( + transactionName, + WithContextInputs(iotago.TxEssenceContextInputs{ + &iotago.BlockIssuanceCreditInput{ + AccountID: inputAccount.AccountID, + }, + &iotago.CommitmentInput{ + CommitmentID: w.Node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment().MustID(), + }, + }), + WithAccountInput(input, true), + WithOutputs(destructionOutputs), + WithSlotCreated(creationSlot), + )) + + return signedTransaction +} + +// CreateImplicitAccountFromInput creates an implicit account output. +func (w *Wallet) CreateImplicitAccountFromInput(transactionName string, inputName string, recipientWallet *Wallet) *iotago.SignedTransaction { + input := w.Output(inputName) + + implicitAccountOutput := &iotago.BasicOutput{ + Amount: MinIssuerAccountAmount, + Mana: AccountConversionManaCost, + Conditions: iotago.BasicOutputUnlockConditions{ + &iotago.AddressUnlockCondition{Address: recipientWallet.ImplicitAccountCreationAddress()}, + }, + Features: iotago.BasicOutputFeatures{}, + } + + remainderBasicOutput := &iotago.BasicOutput{ + Amount: input.BaseTokenAmount() - MinIssuerAccountAmount, + Mana: input.StoredMana() - AccountConversionManaCost, + Conditions: iotago.BasicOutputUnlockConditions{ + &iotago.AddressUnlockCondition{Address: input.Output().UnlockConditionSet().Address().Address}, + }, + Features: iotago.BasicOutputFeatures{}, + } + + signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions( + transactionName, + WithInputs(utxoledger.Outputs{input}), + WithOutputs(iotago.Outputs[iotago.Output]{implicitAccountOutput, remainderBasicOutput}), + )) + + // register the outputs in the recipient wallet (so wallet doesn't have to scan for outputs on its addresses) + recipientWallet.registerOutputs(transactionName, signedTransaction.Transaction) + + // register the implicit account as a block issuer in the wallet + implicitAccountID := iotago.AccountIDFromOutputID(recipientWallet.Output(fmt.Sprintf("%s:0", transactionName)).OutputID()) + recipientWallet.SetBlockIssuer(implicitAccountID) + + return signedTransaction +} + +func (w *Wallet) TransitionImplicitAccountToAccountOutput(transactionName string, inputName string, creationSlot iotago.SlotIndex, opts ...options.Option[builder.AccountOutputBuilder]) *iotago.SignedTransaction { + input := w.Output(inputName) + implicitAccountID := iotago.AccountIDFromOutputID(input.OutputID()) + + basicOutput, isBasic := input.Output().(*iotago.BasicOutput) + if !isBasic { + panic(fmt.Sprintf("output with alias %s is not *iotago.BasicOutput", inputName)) + } + if basicOutput.UnlockConditionSet().Address().Address.Type() != iotago.AddressImplicitAccountCreation { + panic(fmt.Sprintf("output with alias %s is not an implicit account", inputName)) + } + + accountOutput := options.Apply(builder.NewAccountOutputBuilder(w.Address(), w.Address(), MinIssuerAccountAmount). + AccountID(iotago.AccountIDFromOutputID(input.OutputID())), + opts).MustBuild() + + signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions( + transactionName, + WithContextInputs(iotago.TxEssenceContextInputs{ + &iotago.BlockIssuanceCreditInput{ + AccountID: implicitAccountID, + }, + &iotago.CommitmentInput{ + CommitmentID: w.Node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment().MustID(), + }, + }), + WithInputs(utxoledger.Outputs{input}), + WithOutputs(iotago.Outputs[iotago.Output]{accountOutput}), + WithSlotCreated(creationSlot), + func(txBuilder *builder.TransactionBuilder) { + txBuilder.AllotAllMana(creationSlot, implicitAccountID) + }, + )) + + return signedTransaction +} + +func (w *Wallet) CreateBasicOutputsEquallyFromInputs(transactionName string, outputCount int, inputNames ...string) *iotago.SignedTransaction { + inputStates := make([]*utxoledger.Output, 0, len(inputNames)) + totalInputAmounts := iotago.BaseToken(0) + totalInputStoredMana := iotago.Mana(0) + + for _, inputName := range inputNames { + output := w.Output(inputName) + inputStates = append(inputStates, output) + totalInputAmounts += output.BaseTokenAmount() + totalInputStoredMana += output.StoredMana() + } + + manaAmount := totalInputStoredMana / iotago.Mana(outputCount) + remainderMana := totalInputStoredMana + + tokenAmount := totalInputAmounts / iotago.BaseToken(outputCount) + remainderFunds := totalInputAmounts + + outputStates := make(iotago.Outputs[iotago.Output], 0, outputCount) + for i := 0; i < outputCount; i++ { + if i+1 == outputCount { + tokenAmount = remainderFunds + manaAmount = remainderMana + } + remainderFunds -= tokenAmount + remainderMana -= manaAmount + + outputStates = append(outputStates, &iotago.BasicOutput{ + Amount: tokenAmount, + Mana: manaAmount, + Conditions: iotago.BasicOutputUnlockConditions{ + &iotago.AddressUnlockCondition{Address: w.Address()}, + }, + Features: iotago.BasicOutputFeatures{}, + }) + } + + signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions( + transactionName, + WithInputs(inputStates), + WithOutputs(outputStates), + )) + + return signedTransaction +} + +func (w *Wallet) createSignedTransactionWithOptions(transactionName string, opts ...options.Option[builder.TransactionBuilder]) (*iotago.SignedTransaction, error) { + currentAPI := w.Node.Protocol.CommittedAPI() + + txBuilder := builder.NewTransactionBuilder(currentAPI) + txBuilder.WithTransactionCapabilities(iotago.TransactionCapabilitiesBitMaskWithCapabilities(iotago.WithTransactionCanDoAnything())) + // Always add a random payload to randomize transaction ID. + randomPayload := tpkg.Rand12ByteArray() + txBuilder.AddTaggedDataPayload(&iotago.TaggedData{Tag: randomPayload[:], Data: randomPayload[:]}) + + addrSigner := w.AddressSigner() + signedTransaction, err := options.Apply(txBuilder, opts).Build(addrSigner) + + // register the outputs in the wallet + w.registerOutputs(transactionName, signedTransaction.Transaction) + + return signedTransaction, err +} + +func (w *Wallet) registerOutputs(transactionName string, transaction *iotago.Transaction) { + currentAPI := w.Node.Protocol.CommittedAPI() + (lo.PanicOnErr(transaction.ID())).RegisterAlias(transactionName) + w.transactions[transactionName] = transaction + + for outputID, output := range lo.PanicOnErr(transaction.OutputsSet()) { + // register the output if it belongs to this wallet + addressUC := output.UnlockConditionSet().Address() + stateControllerUC := output.UnlockConditionSet().StateControllerAddress() + if addressUC != nil && w.HasAddress(addressUC.Address) || stateControllerUC != nil && w.HasAddress(stateControllerUC.Address) { + clonedOutput := output.Clone() + actualOutputID := iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(transaction.ID()), outputID.Index()) + if clonedOutput.Type() == iotago.OutputAccount { + if accountOutput, ok := clonedOutput.(*iotago.AccountOutput); ok && accountOutput.AccountID == iotago.EmptyAccountID { + accountOutput.AccountID = iotago.AccountIDFromOutputID(actualOutputID) + } + } + w.outputs[fmt.Sprintf("%s:%d", transactionName, outputID.Index())] = utxoledger.CreateOutput(w.Node.Protocol, actualOutputID, iotago.EmptyBlockID, currentAPI.TimeProvider().SlotFromTime(time.Now()), clonedOutput, lo.PanicOnErr(iotago.OutputIDProofFromTransaction(transaction, outputID.Index()))) + } + } +} diff --git a/pkg/protocol/snapshotcreator/options.go b/pkg/testsuite/snapshotcreator/options.go similarity index 90% rename from pkg/protocol/snapshotcreator/options.go rename to pkg/testsuite/snapshotcreator/options.go index 74d986fe4..85aa1f439 100644 --- a/pkg/protocol/snapshotcreator/options.go +++ b/pkg/testsuite/snapshotcreator/options.go @@ -8,6 +8,7 @@ import ( ledger1 "github.com/iotaledger/iota-core/pkg/protocol/engine/ledger/ledger" "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager" "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager/poa" + "github.com/iotaledger/iota-core/pkg/testsuite/mock" iotago "github.com/iotaledger/iota.go/v4" ) @@ -22,8 +23,8 @@ type Options struct { // RootBlocks define the initial blocks to which new blocks can attach to. RootBlocks map[iotago.BlockID]iotago.CommitmentID - // GenesisSeed defines the seed used to generate keypair that can spend Genesis outputs. - GenesisSeed []byte + // GenesisKeyManager defines the key manager used to generate keypair that can spend Genesis outputs. + GenesisKeyManager *mock.KeyManager // Accounts defines the accounts that are created in the ledger as part of the Genesis. Accounts []AccountDetails @@ -83,10 +84,10 @@ func WithRootBlocks(rootBlocks map[iotago.BlockID]iotago.CommitmentID) options.O } } -// WithGenesisSeed defines the seed used to generate keypair that can spend Genesis outputs. -func WithGenesisSeed(genesisSeed []byte) options.Option[Options] { +// WithGenesisKeyManager defines the seed used to generate keypair that can spend Genesis outputs. +func WithGenesisKeyManager(keyManager *mock.KeyManager) options.Option[Options] { return func(m *Options) { - m.GenesisSeed = genesisSeed + m.GenesisKeyManager = keyManager } } diff --git a/pkg/protocol/snapshotcreator/snapshotcreator.go b/pkg/testsuite/snapshotcreator/snapshotcreator.go similarity index 98% rename from pkg/protocol/snapshotcreator/snapshotcreator.go rename to pkg/testsuite/snapshotcreator/snapshotcreator.go index 5223e0083..f0d3ee8a0 100644 --- a/pkg/protocol/snapshotcreator/snapshotcreator.go +++ b/pkg/testsuite/snapshotcreator/snapshotcreator.go @@ -133,7 +133,7 @@ func CreateSnapshot(opts ...options.Option[Options]) error { }, iotago.BaseToken(0)) var genesisTransactionOutputs iotago.TxEssenceOutputs - genesisOutput, err := createGenesisOutput(api, opt.ProtocolParameters.TokenSupply()-totalAccountAmount-totalBasicOutputAmount, opt.GenesisSeed) + genesisOutput, err := createGenesisOutput(api, opt.ProtocolParameters.TokenSupply()-totalAccountAmount-totalBasicOutputAmount, opt.GenesisKeyManager) if err != nil { return ierrors.Wrap(err, "failed to create genesis outputs") } @@ -187,10 +187,9 @@ func CreateSnapshot(opts ...options.Option[Options]) error { return engineInstance.WriteSnapshot(opt.FilePath) } -func createGenesisOutput(api iotago.API, genesisTokenAmount iotago.BaseToken, genesisSeed []byte) (iotago.Output, error) { +func createGenesisOutput(api iotago.API, genesisTokenAmount iotago.BaseToken, genesisKeyManager *mock.KeyManager) (iotago.Output, error) { if genesisTokenAmount > 0 { - genesisWallet := mock.NewHDWallet("genesis", genesisSeed, 0) - output := createOutput(genesisWallet.Address(), genesisTokenAmount, 0) + output := createOutput(genesisKeyManager.Address(iotago.AddressEd25519), genesisTokenAmount, iotago.Mana(genesisTokenAmount)) if _, err := api.StorageScoreStructure().CoversMinDeposit(output, genesisTokenAmount); err != nil { return nil, ierrors.Wrap(err, "min rent not covered by Genesis output with index 0") diff --git a/pkg/testsuite/testsuite.go b/pkg/testsuite/testsuite.go index 52d99db5c..794702d79 100644 --- a/pkg/testsuite/testsuite.go +++ b/pkg/testsuite/testsuite.go @@ -19,26 +19,23 @@ import ( "github.com/iotaledger/iota-core/pkg/protocol" "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" "github.com/iotaledger/iota-core/pkg/protocol/engine/utxoledger" - "github.com/iotaledger/iota-core/pkg/protocol/snapshotcreator" "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/sybilprotectionv1" "github.com/iotaledger/iota-core/pkg/storage/utils" "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/tpkg" ) -const MinIssuerAccountAmount = iotago.BaseToken(372900) -const MinValidatorAccountAmount = iotago.BaseToken(722800) - type TestSuite struct { Testing *testing.T fakeTesting *testing.T network *mock.Network - Directory *utils.Directory - nodes *orderedmap.OrderedMap[string, *mock.Node] - blockIssuers *orderedmap.OrderedMap[string, *mock.BlockIssuer] - running bool + Directory *utils.Directory + nodes *orderedmap.OrderedMap[string, *mock.Node] + wallets *orderedmap.OrderedMap[string, *mock.Wallet] + running bool snapshotPath string blocks *shrinkingmap.ShrinkingMap[string, *blocks.Block] @@ -54,19 +51,19 @@ type TestSuite struct { uniqueBlockTimeCounter atomic.Int64 automaticTransactionIssuingCounters shrinkingmap.ShrinkingMap[string, int] mutex syncutils.RWMutex - TransactionFramework *TransactionFramework - genesisSeed [32]byte + genesisKeyManager *mock.KeyManager } func NewTestSuite(testingT *testing.T, opts ...options.Option[TestSuite]) *TestSuite { + genesisSeed := tpkg.RandEd25519Seed() return options.Apply(&TestSuite{ Testing: testingT, fakeTesting: &testing.T{}, - genesisSeed: tpkg.RandEd25519Seed(), + genesisKeyManager: mock.NewKeyManager(genesisSeed[:], 0), network: mock.NewNetwork(), Directory: utils.NewDirectory(testingT.TempDir()), nodes: orderedmap.New[string, *mock.Node](), - blockIssuers: orderedmap.New[string, *mock.BlockIssuer](), + wallets: orderedmap.New[string, *mock.Wallet](), blocks: shrinkingmap.New[string, *blocks.Block](), automaticTransactionIssuingCounters: *shrinkingmap.New[string, int](), @@ -153,10 +150,7 @@ func (t *TestSuite) AccountOutput(alias string) *utxoledger.Output { t.mutex.RLock() defer t.mutex.RUnlock() - output, exist := t.TransactionFramework.states[alias] - if !exist { - panic(fmt.Sprintf("account %s not registered", alias)) - } + output := t.DefaultWallet().Output(alias) if _, ok := output.Output().(*iotago.AccountOutput); !ok { panic(fmt.Sprintf("output %s is not an account", alias)) @@ -255,6 +249,18 @@ func (t *TestSuite) Node(name string) *mock.Node { return node } +func (t *TestSuite) Wallet(name string) *mock.Wallet { + t.mutex.RLock() + defer t.mutex.RUnlock() + + wallet, exist := t.wallets.Get(name) + if !exist { + panic(fmt.Sprintf("wallet %s does not exist", name)) + } + + return wallet +} + func (t *TestSuite) Nodes(names ...string) []*mock.Node { if len(names) == 0 { t.mutex.RLock() @@ -320,7 +326,7 @@ func (t *TestSuite) addNodeToPartition(name string, partition string, validator node := mock.NewNode(t.Testing, t.network, partition, name, validator) t.nodes.Set(name, node) - amount := MinValidatorAccountAmount + amount := mock.MinValidatorAccountAmount if len(optAmount) > 0 { amount = optAmount[0] } @@ -349,7 +355,11 @@ func (t *TestSuite) AddValidatorNodeToPartition(name string, partition string, o } func (t *TestSuite) AddValidatorNode(name string, optAmount ...iotago.BaseToken) *mock.Node { - return t.addNodeToPartition(name, mock.NetworkMainPartition, true, optAmount...) + node := t.addNodeToPartition(name, mock.NetworkMainPartition, true, optAmount...) + // create a wallet for each validator node which uses the validator account as a block issuer + t.addWallet(name, node, node.Validator.AccountID, node.KeyManager) + + return node } func (t *TestSuite) AddNodeToPartition(name string, partition string, optAmount ...iotago.BaseToken) *mock.Node { @@ -364,9 +374,10 @@ func (t *TestSuite) RemoveNode(name string) { t.nodes.Delete(name) } -func (t *TestSuite) AddBasicBlockIssuer(name string, blockIssuanceCredits ...iotago.BlockIssuanceCredits) *mock.BlockIssuer { - newBlockIssuer := mock.NewBlockIssuer(t.Testing, name, false) - t.blockIssuers.Set(name, newBlockIssuer) +// AddGenesisWallet adds a wallet to the test suite with a block issuer in the genesis snapshot and access to the genesis seed. +// If no block issuance credits are provided, the wallet will be assigned half of the maximum block issuance credits. +func (t *TestSuite) AddGenesisWallet(name string, node *mock.Node, blockIssuanceCredits ...iotago.BlockIssuanceCredits) *mock.Wallet { + newWallet := t.addWallet(name, node, iotago.EmptyAccountID, t.genesisKeyManager) var bic iotago.BlockIssuanceCredits if len(blockIssuanceCredits) == 0 { bic = iotago.MaxBlockIssuanceCredits / 2 @@ -375,24 +386,34 @@ func (t *TestSuite) AddBasicBlockIssuer(name string, blockIssuanceCredits ...iot } accountDetails := snapshotcreator.AccountDetails{ - Address: iotago.Ed25519AddressFromPubKey(newBlockIssuer.PublicKey), - Amount: MinIssuerAccountAmount, - Mana: iotago.Mana(MinIssuerAccountAmount), - IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(newBlockIssuer.PublicKey)), + Address: iotago.Ed25519AddressFromPubKey(newWallet.BlockIssuer.PublicKey), + Amount: mock.MinIssuerAccountAmount, + Mana: iotago.Mana(mock.MinIssuerAccountAmount), + IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(newWallet.BlockIssuer.PublicKey)), ExpirySlot: iotago.MaxSlotIndex, BlockIssuanceCredits: bic, } t.optsAccounts = append(t.optsAccounts, accountDetails) - return newBlockIssuer + return newWallet } -func (t *TestSuite) DefaultBasicBlockIssuer() *mock.BlockIssuer { - defaultBasicBlockIssuer, exists := t.blockIssuers.Get("default") - require.True(t.Testing, exists, "default block issuer not found") +func (t *TestSuite) addWallet(name string, node *mock.Node, accountID iotago.AccountID, keyManager *mock.KeyManager) *mock.Wallet { + newWallet := mock.NewWallet(t.Testing, name, node, keyManager) + newWallet.SetBlockIssuer(accountID) + t.wallets.Set(name, newWallet) - return defaultBasicBlockIssuer + return newWallet +} + +func (t *TestSuite) DefaultWallet() *mock.Wallet { + defaultWallet, exists := t.wallets.Get("default") + if !exists { + return nil + } + + return defaultWallet } func (t *TestSuite) Run(failOnBlockFiltered bool, nodesOptions ...map[string][]options.Option[protocol.Protocol]) { @@ -401,11 +422,10 @@ func (t *TestSuite) Run(failOnBlockFiltered bool, nodesOptions ...map[string][]o // Create accounts for any block issuer nodes added before starting the network. if t.optsAccounts != nil { - wallet := mock.NewHDWallet("genesis", t.genesisSeed[:], 0) t.optsSnapshotOptions = append(t.optsSnapshotOptions, snapshotcreator.WithAccounts(lo.Map(t.optsAccounts, func(accountDetails snapshotcreator.AccountDetails) snapshotcreator.AccountDetails { - // if no custom address is assigned to the account, assign an address generated from GenesisSeed + // if no custom address is assigned to the account, assign an address generated from GenesisKeyManager if accountDetails.Address == nil { - accountDetails.Address = wallet.Address() + accountDetails.Address = t.genesisKeyManager.Address(iotago.AddressEd25519) } if accountDetails.AccountID.Empty() { @@ -422,7 +442,7 @@ func (t *TestSuite) Run(failOnBlockFiltered bool, nodesOptions ...map[string][]o })...)) } - err := snapshotcreator.CreateSnapshot(append([]options.Option[snapshotcreator.Options]{snapshotcreator.WithGenesisSeed(t.genesisSeed[:])}, t.optsSnapshotOptions...)...) + err := snapshotcreator.CreateSnapshot(append([]options.Option[snapshotcreator.Options]{snapshotcreator.WithGenesisKeyManager(t.genesisKeyManager)}, t.optsSnapshotOptions...)...) if err != nil { panic(fmt.Sprintf("failed to create snapshot: %s", err)) } @@ -443,8 +463,13 @@ func (t *TestSuite) Run(failOnBlockFiltered bool, nodesOptions ...map[string][]o node.Initialize(failOnBlockFiltered, baseOpts...) - if t.TransactionFramework == nil { - t.TransactionFramework = NewTransactionFramework(node.Protocol, t.genesisSeed[:]) + if defaultWallet := t.DefaultWallet(); defaultWallet != nil { + if err := node.Protocol.MainEngineInstance().Ledger.ForEachUnspentOutput(func(output *utxoledger.Output) bool { + defaultWallet.AddOutput(fmt.Sprintf("Genesis:%d", output.OutputID().Index()), output) + return true + }); err != nil { + panic(err) + } } return true @@ -473,7 +498,7 @@ func (t *TestSuite) BlockIssuersForNodes(nodes []*mock.Node) []*mock.BlockIssuer if node.IsValidator() { blockIssuers = append(blockIssuers, node.Validator) } else { - blockIssuers = append(blockIssuers, t.DefaultBasicBlockIssuer()) + blockIssuers = append(blockIssuers, t.DefaultWallet().BlockIssuer) } } diff --git a/pkg/testsuite/testsuite_issue_blocks.go b/pkg/testsuite/testsuite_issue_blocks.go index 8b07356ac..982f4a714 100644 --- a/pkg/testsuite/testsuite_issue_blocks.go +++ b/pkg/testsuite/testsuite_issue_blocks.go @@ -49,29 +49,19 @@ func (t *TestSuite) limitParentsCountInBlockOptions(blockOpts []options.Option[m return blockOpts } -func (t *TestSuite) RegisterBlock(alias string, block *blocks.Block) { +func (t *TestSuite) RegisterBlock(blockName string, block *blocks.Block) { t.mutex.Lock() defer t.mutex.Unlock() - t.registerBlock(alias, block) + t.registerBlock(blockName, block) } -func (t *TestSuite) registerBlock(alias string, block *blocks.Block) { - t.blocks.Set(alias, block) - block.ID().RegisterAlias(alias) +func (t *TestSuite) registerBlock(blockName string, block *blocks.Block) { + t.blocks.Set(blockName, block) + block.ID().RegisterAlias(blockName) } -func (t *TestSuite) CreateBasicBlock(alias string, blockIssuer *mock.BlockIssuer, node *mock.Node, blockOpts ...options.Option[mock.BasicBlockParams]) { - t.mutex.Lock() - defer t.mutex.Unlock() - - block, err := blockIssuer.CreateBasicBlock(context.Background(), alias, node, blockOpts...) - require.NoError(t.Testing, err) - - t.registerBlock(alias, block) -} - -func (t *TestSuite) IssueValidationBlockAtSlot(alias string, slot iotago.SlotIndex, slotCommitment *iotago.Commitment, node *mock.Node, parents ...iotago.BlockID) *blocks.Block { +func (t *TestSuite) IssueValidationBlockAtSlot(blockName string, slot iotago.SlotIndex, slotCommitment *iotago.Commitment, node *mock.Node, parents ...iotago.BlockID) *blocks.Block { t.AssertBlocksExist(t.Blocks(lo.Map(parents, func(id iotago.BlockID) string { return id.Alias() })...), true, node) t.mutex.Lock() @@ -83,48 +73,48 @@ func (t *TestSuite) IssueValidationBlockAtSlot(alias string, slot iotago.SlotInd require.Truef(t.Testing, issuingTime.Before(time.Now()), "node: %s: issued block (%s, slot: %d) is in the current (%s, slot: %d) or future slot", node.Name, issuingTime, slot, time.Now(), timeProvider.SlotFromTime(time.Now())) require.True(t.Testing, node.IsValidator(), "node: %s: is not a validator node", node.Name) - block := node.Validator.IssueValidationBlock(context.Background(), alias, node, mock.WithValidationBlockHeaderOptions(mock.WithIssuingTime(issuingTime), mock.WithSlotCommitment(slotCommitment), mock.WithStrongParents(parents...))) + block := node.Validator.IssueValidationBlock(context.Background(), blockName, node, mock.WithValidationBlockHeaderOptions(mock.WithIssuingTime(issuingTime), mock.WithSlotCommitment(slotCommitment), mock.WithStrongParents(parents...))) - t.registerBlock(alias, block) + t.registerBlock(blockName, block) return block } -func (t *TestSuite) IssueExistingBlock(alias string, blockIssuer *mock.BlockIssuer, node *mock.Node) { +func (t *TestSuite) IssueExistingBlock(blockName string, wallet *mock.Wallet) { t.mutex.Lock() defer t.mutex.Unlock() - block, exists := t.blocks.Get(alias) + block, exists := t.blocks.Get(blockName) require.True(t.Testing, exists) require.NotNil(t.Testing, block) - require.NoError(t.Testing, blockIssuer.IssueBlock(block.ModelBlock(), node)) + require.NoError(t.Testing, wallet.BlockIssuer.IssueBlock(block.ModelBlock(), wallet.Node)) } -func (t *TestSuite) IssueValidationBlockWithOptions(alias string, blockIssuer *mock.BlockIssuer, node *mock.Node, blockOpts ...options.Option[mock.ValidatorBlockParams]) *blocks.Block { +func (t *TestSuite) IssueValidationBlockWithOptions(blockName string, node *mock.Node, blockOpts ...options.Option[mock.ValidatorBlockParams]) *blocks.Block { t.mutex.Lock() defer t.mutex.Unlock() - block := blockIssuer.IssueValidationBlock(context.Background(), alias, node, blockOpts...) + block := node.IssueValidationBlock(context.Background(), blockName, blockOpts...) - t.registerBlock(alias, block) + t.registerBlock(blockName, block) return block } -func (t *TestSuite) IssueBasicBlockWithOptions(alias string, blockIssuer *mock.BlockIssuer, node *mock.Node, blockOpts ...options.Option[mock.BasicBlockParams]) *blocks.Block { +func (t *TestSuite) IssueBasicBlockWithOptions(blockName string, wallet *mock.Wallet, blockOpts ...options.Option[mock.BasicBlockParams]) *blocks.Block { t.mutex.Lock() defer t.mutex.Unlock() - block := blockIssuer.IssueBasicBlock(context.Background(), alias, node, blockOpts...) + block := wallet.IssueBasicBlock(context.Background(), blockName, blockOpts...) - t.registerBlock(alias, block) + t.registerBlock(blockName, block) return block } -func (t *TestSuite) IssueBasicBlockAtSlotWithOptions(alias string, slot iotago.SlotIndex, slotCommitment *iotago.Commitment, blockIssuer *mock.BlockIssuer, node *mock.Node, payload iotago.Payload, blockOpts ...options.Option[mock.BlockHeaderParams]) *blocks.Block { - t.assertParentsExistFromBlockOptions(blockOpts, node) +func (t *TestSuite) IssueBasicBlockAtSlotWithOptions(blockName string, slot iotago.SlotIndex, wallet *mock.Wallet, payload iotago.Payload, blockOpts ...options.Option[mock.BlockHeaderParams]) *blocks.Block { + t.assertParentsExistFromBlockOptions(blockOpts, wallet.Node) t.mutex.Lock() defer t.mutex.Unlock() @@ -132,29 +122,29 @@ func (t *TestSuite) IssueBasicBlockAtSlotWithOptions(alias string, slot iotago.S timeProvider := t.API.TimeProvider() issuingTime := timeProvider.SlotStartTime(slot).Add(time.Duration(t.uniqueBlockTimeCounter.Add(1))) - require.Truef(t.Testing, issuingTime.Before(time.Now()), "node: %s: issued block (%s, slot: %d) is in the current (%s, slot: %d) or future slot", node.Name, issuingTime, slot, time.Now(), timeProvider.SlotFromTime(time.Now())) + require.Truef(t.Testing, issuingTime.Before(time.Now()), "wallet: %s: issued block (%s, slot: %d) is in the current (%s, slot: %d) or future slot", wallet.Name, issuingTime, slot, time.Now(), timeProvider.SlotFromTime(time.Now())) - block := blockIssuer.IssueBasicBlock(context.Background(), alias, node, mock.WithBasicBlockHeader(append(blockOpts, mock.WithIssuingTime(issuingTime), mock.WithSlotCommitment(slotCommitment))...), mock.WithPayload(payload)) + block := wallet.IssueBasicBlock(context.Background(), blockName, mock.WithBasicBlockHeader(append(blockOpts, mock.WithIssuingTime(issuingTime))...), mock.WithPayload(payload)) - t.registerBlock(alias, block) + t.registerBlock(blockName, block) return block } -func (t *TestSuite) IssuePayloadWithOptions(alias string, blockIssuer *mock.BlockIssuer, node *mock.Node, payload iotago.Payload, blockHeaderOpts ...options.Option[mock.BlockHeaderParams]) *blocks.Block { - t.assertParentsExistFromBlockOptions(blockHeaderOpts, node) +func (t *TestSuite) IssuePayloadWithOptions(blockName string, wallet *mock.Wallet, payload iotago.Payload, blockHeaderOpts ...options.Option[mock.BlockHeaderParams]) *blocks.Block { + t.assertParentsExistFromBlockOptions(blockHeaderOpts, wallet.Node) t.mutex.Lock() defer t.mutex.Unlock() - block := blockIssuer.IssueBasicBlock(context.Background(), alias, node, mock.WithPayload(payload), mock.WithBasicBlockHeader(blockHeaderOpts...)) + block := wallet.IssueBasicBlock(context.Background(), blockName, mock.WithPayload(payload), mock.WithBasicBlockHeader(blockHeaderOpts...)) - t.registerBlock(alias, block) + t.registerBlock(blockName, block) return block } -func (t *TestSuite) IssueValidationBlock(alias string, node *mock.Node, blockHeaderOpts ...options.Option[mock.BlockHeaderParams]) *blocks.Block { +func (t *TestSuite) IssueValidationBlock(blockName string, node *mock.Node, blockHeaderOpts ...options.Option[mock.BlockHeaderParams]) *blocks.Block { t.assertParentsExistFromBlockOptions(blockHeaderOpts, node) require.Truef(t.Testing, node.IsValidator(), "node: %s: is not a validator node", node.Name) @@ -162,21 +152,20 @@ func (t *TestSuite) IssueValidationBlock(alias string, node *mock.Node, blockHea t.mutex.Lock() defer t.mutex.Unlock() - block := node.Validator.IssueValidationBlock(context.Background(), alias, node, mock.WithValidationBlockHeaderOptions(blockHeaderOpts...)) + block := node.Validator.IssueValidationBlock(context.Background(), blockName, node, mock.WithValidationBlockHeaderOptions(blockHeaderOpts...)) - t.registerBlock(alias, block) + t.registerBlock(blockName, block) return block } -func (t *TestSuite) IssueCandidacyAnnouncementInSlot(alias string, slot iotago.SlotIndex, parentsPrefixAlias string, node *mock.Node, issuingOptions ...options.Option[mock.BlockHeaderParams]) *blocks.Block { +func (t *TestSuite) IssueCandidacyAnnouncementInSlot(alias string, slot iotago.SlotIndex, parentsPrefixAlias string, wallet *mock.Wallet, issuingOptions ...options.Option[mock.BlockHeaderParams]) *blocks.Block { timeProvider := t.API.TimeProvider() issuingTime := timeProvider.SlotStartTime(slot).Add(time.Duration(t.uniqueBlockTimeCounter.Add(1))) - require.Truef(t.Testing, issuingTime.Before(time.Now()), "node: %s: issued block (%s, slot: %d) is in the current (%s, slot: %d) or future slot", node.Name, issuingTime, slot, time.Now(), timeProvider.SlotFromTime(time.Now())) + require.Truef(t.Testing, issuingTime.Before(time.Now()), "wallet: %s: issued block (%s, slot: %d) is in the current (%s, slot: %d) or future slot", wallet.Name, issuingTime, slot, time.Now(), timeProvider.SlotFromTime(time.Now())) return t.IssuePayloadWithOptions( alias, - node.Validator, - node, + wallet, &iotago.CandidacyAnnouncement{}, append(issuingOptions, mock.WithStrongParents(t.BlockIDsWithPrefix(parentsPrefixAlias)...), @@ -185,15 +174,14 @@ func (t *TestSuite) IssueCandidacyAnnouncementInSlot(alias string, slot iotago.S ) } -func (t *TestSuite) IssueBlockRowInSlot(prefix string, slot iotago.SlotIndex, row int, parentsPrefixAlias string, nodes []*mock.Node, issuingOptions map[string][]options.Option[mock.BlockHeaderParams]) []*blocks.Block { - blockIssuers := t.BlockIssuersForNodes(nodes) +func (t *TestSuite) IssueBlockRowInSlot(prefix string, slot iotago.SlotIndex, row int, parentsPrefix string, nodes []*mock.Node, issuingOptions map[string][]options.Option[mock.BlockHeaderParams]) []*blocks.Block { blocksIssued := make([]*blocks.Block, 0, len(nodes)) - strongParents := t.BlockIDsWithPrefix(parentsPrefixAlias) + strongParents := t.BlockIDsWithPrefix(parentsPrefix) issuingOptionsCopy := lo.MergeMaps(make(map[string][]options.Option[mock.BlockHeaderParams]), issuingOptions) - for index, node := range nodes { - blockAlias := fmt.Sprintf("%s%d.%d-%s", prefix, slot, row, node.Name) + for _, node := range nodes { + blockName := fmt.Sprintf("%s%d.%d-%s", prefix, slot, row, node.Name) issuingOptionsCopy[node.Name] = append(issuingOptionsCopy[node.Name], mock.WithStrongParents(strongParents...)) timeProvider := t.API.TimeProvider() @@ -202,23 +190,22 @@ func (t *TestSuite) IssueBlockRowInSlot(prefix string, slot iotago.SlotIndex, ro var b *blocks.Block // Only issue validator blocks if account has staking feature and is part of committee. - if blockIssuers[index].Validator && lo.Return1(node.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(slot)).HasAccount(node.Validator.AccountID) { + if node.Validator != nil && lo.Return1(node.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(slot)).HasAccount(node.Validator.AccountID) { blockHeaderOptions := append(issuingOptionsCopy[node.Name], mock.WithIssuingTime(issuingTime)) t.assertParentsCommitmentExistFromBlockOptions(blockHeaderOptions, node) t.assertParentsExistFromBlockOptions(blockHeaderOptions, node) - b = t.IssueValidationBlockWithOptions(blockAlias, blockIssuers[index], node, mock.WithValidationBlockHeaderOptions(blockHeaderOptions...), mock.WithHighestSupportedVersion(node.HighestSupportedVersion()), mock.WithProtocolParametersHash(node.ProtocolParametersHash())) + b = t.IssueValidationBlockWithOptions(blockName, node, mock.WithValidationBlockHeaderOptions(blockHeaderOptions...), mock.WithHighestSupportedVersion(node.HighestSupportedVersion()), mock.WithProtocolParametersHash(node.ProtocolParametersHash())) } else { txCount := t.automaticTransactionIssuingCounters.Compute(node.Partition, func(currentValue int, exists bool) int { return currentValue + 1 }) - inputAlias := fmt.Sprintf("automaticSpent-%d:0", txCount-1) - txAlias := fmt.Sprintf("automaticSpent-%d", txCount) + inputName := fmt.Sprintf("automaticSpent-%d:0", txCount-1) + txName := fmt.Sprintf("automaticSpent-%d", txCount) if txCount == 1 { - inputAlias = "Genesis:0" + inputName = "Genesis:0" } - tx, err := t.TransactionFramework.CreateSimpleTransaction(txAlias, 1, inputAlias) - require.NoError(t.Testing, err) + tx := t.DefaultWallet().CreateBasicOutputsEquallyFromInputs(txName, 1, inputName) issuingOptionsCopy[node.Name] = t.limitParentsCountInBlockOptions(issuingOptionsCopy[node.Name], iotago.BlockMaxParents) @@ -226,7 +213,8 @@ func (t *TestSuite) IssueBlockRowInSlot(prefix string, slot iotago.SlotIndex, ro t.assertParentsCommitmentExistFromBlockOptions(blockHeaderOptions, node) t.assertParentsExistFromBlockOptions(blockHeaderOptions, node) - b = t.IssueBasicBlockWithOptions(blockAlias, blockIssuers[index], node, mock.WithPayload(tx), mock.WithBasicBlockHeader(blockHeaderOptions...)) + t.DefaultWallet().SetDefaultNode(node) + b = t.IssueBasicBlockWithOptions(blockName, t.DefaultWallet(), mock.WithPayload(tx), mock.WithBasicBlockHeader(blockHeaderOptions...)) } blocksIssued = append(blocksIssued, b) } @@ -234,32 +222,32 @@ func (t *TestSuite) IssueBlockRowInSlot(prefix string, slot iotago.SlotIndex, ro return blocksIssued } -func (t *TestSuite) IssueBlockRowsInSlot(prefix string, slot iotago.SlotIndex, rows int, initialParentsPrefixAlias string, nodes []*mock.Node, issuingOptions map[string][]options.Option[mock.BlockHeaderParams]) (allBlocksIssued []*blocks.Block, lastBlockRow []*blocks.Block) { +func (t *TestSuite) IssueBlockRowsInSlot(prefix string, slot iotago.SlotIndex, rows int, initialParentsPrefix string, nodes []*mock.Node, issuingOptions map[string][]options.Option[mock.BlockHeaderParams]) (allBlocksIssued []*blocks.Block, lastBlockRow []*blocks.Block) { var blocksIssued, lastBlockRowIssued []*blocks.Block - parentsPrefixAlias := initialParentsPrefixAlias + parentsPrefix := initialParentsPrefix for row := 0; row < rows; row++ { if row > 0 { - parentsPrefixAlias = fmt.Sprintf("%s%d.%d", prefix, slot, row-1) + parentsPrefix = fmt.Sprintf("%s%d.%d", prefix, slot, row-1) } - lastBlockRowIssued = t.IssueBlockRowInSlot(prefix, slot, row, parentsPrefixAlias, nodes, issuingOptions) + lastBlockRowIssued = t.IssueBlockRowInSlot(prefix, slot, row, parentsPrefix, nodes, issuingOptions) blocksIssued = append(blocksIssued, lastBlockRowIssued...) } return blocksIssued, lastBlockRowIssued } -func (t *TestSuite) IssueBlocksAtSlots(prefix string, slots []iotago.SlotIndex, rowsPerSlot int, initialParentsPrefixAlias string, nodes []*mock.Node, waitForSlotsCommitted bool, issuingOptions map[string][]options.Option[mock.BlockHeaderParams]) (allBlocksIssued []*blocks.Block, lastBlockRow []*blocks.Block) { +func (t *TestSuite) IssueBlocksAtSlots(prefix string, slots []iotago.SlotIndex, rowsPerSlot int, initialParentsPrefix string, nodes []*mock.Node, waitForSlotsCommitted bool, issuingOptions map[string][]options.Option[mock.BlockHeaderParams]) (allBlocksIssued []*blocks.Block, lastBlockRow []*blocks.Block) { var blocksIssued, lastBlockRowIssued []*blocks.Block - parentsPrefixAlias := initialParentsPrefixAlias + parentsPrefix := initialParentsPrefix for i, slot := range slots { if i > 0 { - parentsPrefixAlias = fmt.Sprintf("%s%d.%d", prefix, slots[i-1], rowsPerSlot-1) + parentsPrefix = fmt.Sprintf("%s%d.%d", prefix, slots[i-1], rowsPerSlot-1) } - blocksInSlot, lastRowInSlot := t.IssueBlockRowsInSlot(prefix, slot, rowsPerSlot, parentsPrefixAlias, nodes, issuingOptions) + blocksInSlot, lastRowInSlot := t.IssueBlockRowsInSlot(prefix, slot, rowsPerSlot, parentsPrefix, nodes, issuingOptions) blocksIssued = append(blocksIssued, blocksInSlot...) lastBlockRowIssued = lastRowInSlot @@ -275,8 +263,8 @@ func (t *TestSuite) IssueBlocksAtSlots(prefix string, slots []iotago.SlotIndex, return blocksIssued, lastBlockRowIssued } -func (t *TestSuite) IssueBlocksAtEpoch(prefix string, epoch iotago.EpochIndex, rowsPerSlot int, initialParentsPrefixAlias string, nodes []*mock.Node, waitForSlotsCommitted bool, issuingOptions map[string][]options.Option[mock.BlockHeaderParams]) (allBlocksIssued []*blocks.Block, lastBlockRow []*blocks.Block) { - return t.IssueBlocksAtSlots(prefix, t.SlotsForEpoch(epoch), rowsPerSlot, initialParentsPrefixAlias, nodes, waitForSlotsCommitted, issuingOptions) +func (t *TestSuite) IssueBlocksAtEpoch(prefix string, epoch iotago.EpochIndex, rowsPerSlot int, initialParentsPrefix string, nodes []*mock.Node, waitForSlotsCommitted bool, issuingOptions map[string][]options.Option[mock.BlockHeaderParams]) (allBlocksIssued []*blocks.Block, lastBlockRow []*blocks.Block) { + return t.IssueBlocksAtSlots(prefix, t.SlotsForEpoch(epoch), rowsPerSlot, initialParentsPrefix, nodes, waitForSlotsCommitted, issuingOptions) } func (t *TestSuite) SlotsForEpoch(epoch iotago.EpochIndex) []iotago.SlotIndex { @@ -315,8 +303,8 @@ func (t *TestSuite) CommitUntilSlot(slot iotago.SlotIndex, parent *blocks.Block) committeeAtBlockSlot, exists := node.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(nextBlockSlot) require.True(t.Testing, exists, "node: %s: does not have committee selected for slot %d", node.Name, nextBlockSlot) if committeeAtBlockSlot.HasAccount(node.Validator.AccountID) { - blockAlias := fmt.Sprintf("chain-%s-%d-%s", parent.ID().Alias(), chainIndex, node.Name) - tip = t.IssueValidationBlockAtSlot(blockAlias, nextBlockSlot, node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), node, tip.ID()) + blockName := fmt.Sprintf("chain-%s-%d-%s", parent.ID().Alias(), chainIndex, node.Name) + tip = t.IssueValidationBlockAtSlot(blockName, nextBlockSlot, node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), node, tip.ID()) } } // acceptance of nextBlockSlot @@ -324,8 +312,8 @@ func (t *TestSuite) CommitUntilSlot(slot iotago.SlotIndex, parent *blocks.Block) committeeAtBlockSlot, exists := node.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(nextBlockSlot) require.True(t.Testing, exists, "node: %s: does not have committee selected for slot %d", node.Name, nextBlockSlot) if committeeAtBlockSlot.HasAccount(node.Validator.AccountID) { - blockAlias := fmt.Sprintf("chain-%s-%d-%s", parent.ID().Alias(), chainIndex+1, node.Name) - tip = t.IssueValidationBlockAtSlot(blockAlias, nextBlockSlot, node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), node, tip.ID()) + blockName := fmt.Sprintf("chain-%s-%d-%s", parent.ID().Alias(), chainIndex+1, node.Name) + tip = t.IssueValidationBlockAtSlot(blockName, nextBlockSlot, node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), node, tip.ID()) } } diff --git a/pkg/testsuite/testsuite_options.go b/pkg/testsuite/testsuite_options.go index eec758168..6f467834e 100644 --- a/pkg/testsuite/testsuite_options.go +++ b/pkg/testsuite/testsuite_options.go @@ -5,7 +5,7 @@ import ( "time" "github.com/iotaledger/hive.go/runtime/options" - "github.com/iotaledger/iota-core/pkg/protocol/snapshotcreator" + "github.com/iotaledger/iota-core/pkg/testsuite/snapshotcreator" iotago "github.com/iotaledger/iota.go/v4" ) diff --git a/pkg/testsuite/transactions.go b/pkg/testsuite/transactions.go index f87e92e3a..23d2eb885 100644 --- a/pkg/testsuite/transactions.go +++ b/pkg/testsuite/transactions.go @@ -141,7 +141,7 @@ func (t *TestSuite) AssertTransactionInCacheConflicts(transactionConflicts map[* return ierrors.Errorf("AssertTransactionInCacheConflicts: %s: block %s does not exist", node.Name, transactionID) } - expectedConflictIDs := ds.NewSet(lo.Map(conflictAliases, t.TransactionFramework.TransactionID)...) + expectedConflictIDs := ds.NewSet(lo.Map(conflictAliases, t.DefaultWallet().TransactionID)...) actualConflictIDs := transactionFromCache.ConflictIDs() if expectedConflictIDs.Size() != actualConflictIDs.Size() { diff --git a/pkg/testsuite/transactions_framework.go b/pkg/testsuite/transactions_framework.go deleted file mode 100644 index 0419fda55..000000000 --- a/pkg/testsuite/transactions_framework.go +++ /dev/null @@ -1,603 +0,0 @@ -package testsuite - -import ( - "fmt" - "time" - - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/lo" - "github.com/iotaledger/hive.go/runtime/options" - "github.com/iotaledger/iota-core/pkg/protocol" - "github.com/iotaledger/iota-core/pkg/protocol/engine/utxoledger" - "github.com/iotaledger/iota-core/pkg/testsuite/mock" - iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/builder" - "github.com/iotaledger/iota.go/v4/tpkg" -) - -type TransactionFramework struct { - apiProvider iotago.APIProvider - - wallet *mock.HDWallet - states map[string]*utxoledger.Output - signedTransactions map[string]*iotago.SignedTransaction - transactions map[string]*iotago.Transaction -} - -func NewTransactionFramework(protocol *protocol.Protocol, genesisSeed []byte) *TransactionFramework { - tf := &TransactionFramework{ - apiProvider: protocol, - states: make(map[string]*utxoledger.Output), - signedTransactions: make(map[string]*iotago.SignedTransaction), - transactions: make(map[string]*iotago.Transaction), - - wallet: mock.NewHDWallet("genesis", genesisSeed, 0), - } - - if err := protocol.MainEngineInstance().Ledger.ForEachUnspentOutput(func(output *utxoledger.Output) bool { - tf.states[fmt.Sprintf("Genesis:%d", output.OutputID().Index())] = output - return true - }); err != nil { - panic(err) - } - - if len(tf.states) == 0 { - panic("no genesis outputs found") - } - - return tf -} - -func (t *TransactionFramework) RegisterTransaction(alias string, transaction *iotago.Transaction) { - currentAPI := t.apiProvider.CommittedAPI() - (lo.PanicOnErr(transaction.ID())).RegisterAlias(alias) - - t.transactions[alias] = transaction - - for outputID, output := range lo.PanicOnErr(transaction.OutputsSet()) { - clonedOutput := output.Clone() - actualOutputID := iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(transaction.ID()), outputID.Index()) - if clonedOutput.Type() == iotago.OutputAccount { - if accountOutput, ok := clonedOutput.(*iotago.AccountOutput); ok && accountOutput.AccountID == iotago.EmptyAccountID { - accountOutput.AccountID = iotago.AccountIDFromOutputID(actualOutputID) - } - } - - t.states[fmt.Sprintf("%s:%d", alias, outputID.Index())] = utxoledger.CreateOutput(t.apiProvider, actualOutputID, iotago.EmptyBlockID, currentAPI.TimeProvider().SlotFromTime(time.Now()), clonedOutput, lo.PanicOnErr(iotago.OutputIDProofFromTransaction(transaction, outputID.Index()))) - } -} - -func (t *TransactionFramework) RegisterSignedTransaction(alias string, signedTransaction *iotago.SignedTransaction) { - (lo.PanicOnErr(signedTransaction.ID())).RegisterAlias(alias) - - t.signedTransactions[alias] = signedTransaction -} - -func (t *TransactionFramework) CreateSignedTransactionWithOptions(alias string, signingWallets []*mock.HDWallet, opts ...options.Option[builder.TransactionBuilder]) (*iotago.SignedTransaction, error) { - currentAPI := t.apiProvider.CommittedAPI() - - walletKeys := make([]iotago.AddressKeys, 0, len(signingWallets)*2) - for _, wallet := range signingWallets { - inputPrivateKey, _ := wallet.KeyPair() - // add address keys for both types of directly unlockable addresses to simplify the TransactionFramework - //nolint:forcetypeassert - walletKeys = append(walletKeys, iotago.NewAddressKeysForEd25519Address(wallet.Address(iotago.AddressEd25519).(*iotago.Ed25519Address), inputPrivateKey)) - //nolint:forcetypeassert - walletKeys = append(walletKeys, iotago.NewAddressKeysForImplicitAccountCreationAddress(wallet.Address(iotago.AddressImplicitAccountCreation).(*iotago.ImplicitAccountCreationAddress), inputPrivateKey)) - } - - txBuilder := builder.NewTransactionBuilder(currentAPI) - txBuilder.WithTransactionCapabilities(iotago.TransactionCapabilitiesBitMaskWithCapabilities(iotago.WithTransactionCanDoAnything())) - // Always add a random payload to randomize transaction ID. - randomPayload := tpkg.Rand12ByteArray() - txBuilder.AddTaggedDataPayload(&iotago.TaggedData{Tag: randomPayload[:], Data: randomPayload[:]}) - - signedTransaction, err := options.Apply(txBuilder, opts).Build(iotago.NewInMemoryAddressSigner(walletKeys...)) - if err == nil { - t.RegisterSignedTransaction(alias, signedTransaction) - t.RegisterTransaction(alias, signedTransaction.Transaction) - } - - return signedTransaction, err -} - -func (t *TransactionFramework) CreateSimpleTransaction(alias string, outputCount int, inputAliases ...string) (*iotago.SignedTransaction, error) { - inputStates, outputStates, signingWallets := t.CreateBasicOutputsEqually(outputCount, inputAliases...) - - return t.CreateSignedTransactionWithOptions(alias, signingWallets, WithInputs(inputStates), WithOutputs(outputStates)) -} - -func (t *TransactionFramework) CreateBasicOutputsEqually(outputCount int, inputAliases ...string) (consumedInputs utxoledger.Outputs, outputs iotago.Outputs[iotago.Output], signingWallets []*mock.HDWallet) { - inputStates := make([]*utxoledger.Output, 0, len(inputAliases)) - totalInputAmounts := iotago.BaseToken(0) - totalInputStoredMana := iotago.Mana(0) - - for _, inputAlias := range inputAliases { - output := t.Output(inputAlias) - inputStates = append(inputStates, output) - totalInputAmounts += output.BaseTokenAmount() - totalInputStoredMana += output.StoredMana() - } - - manaAmount := totalInputStoredMana / iotago.Mana(outputCount) - remainderMana := totalInputStoredMana - - tokenAmount := totalInputAmounts / iotago.BaseToken(outputCount) - remainderFunds := totalInputAmounts - - outputStates := make(iotago.Outputs[iotago.Output], 0, outputCount) - for i := 0; i < outputCount; i++ { - if i+1 == outputCount { - tokenAmount = remainderFunds - manaAmount = remainderMana - } - remainderFunds -= tokenAmount - remainderMana -= manaAmount - - outputStates = append(outputStates, &iotago.BasicOutput{ - Amount: tokenAmount, - Mana: manaAmount, - Conditions: iotago.BasicOutputUnlockConditions{ - &iotago.AddressUnlockCondition{Address: t.DefaultAddress()}, - }, - Features: iotago.BasicOutputFeatures{}, - }) - } - - return inputStates, outputStates, []*mock.HDWallet{t.wallet} -} - -func (t *TransactionFramework) CreateBasicOutputs(amountDistribution []iotago.BaseToken, manaDistribution []iotago.Mana, inputAliases ...string) (consumedInputs utxoledger.Outputs, outputs iotago.Outputs[iotago.Output], signingWallets []*mock.HDWallet) { - if len(amountDistribution) != len(manaDistribution) { - panic("amount and mana distributions should have the same length") - } - - inputStates := make([]*utxoledger.Output, 0, len(inputAliases)) - totalInputAmounts := iotago.BaseToken(0) - totalInputStoredMana := iotago.Mana(0) - - for _, inputAlias := range inputAliases { - output := t.Output(inputAlias) - inputStates = append(inputStates, output) - totalInputAmounts += output.BaseTokenAmount() - totalInputStoredMana += output.StoredMana() - } - - if lo.Sum(amountDistribution...) != totalInputAmounts { - panic("amount on input and output side must be equal") - } - - outputStates := make(iotago.Outputs[iotago.Output], 0, len(amountDistribution)) - for idx, outputAmount := range amountDistribution { - outputStates = append(outputStates, &iotago.BasicOutput{ - Amount: outputAmount, - Mana: manaDistribution[idx], - Conditions: iotago.BasicOutputUnlockConditions{ - &iotago.AddressUnlockCondition{Address: t.DefaultAddress()}, - }, - Features: iotago.BasicOutputFeatures{}, - }) - } - - return inputStates, outputStates, []*mock.HDWallet{t.wallet} -} - -func (t *TransactionFramework) CreateAccountFromInput(inputAlias string, opts ...options.Option[builder.AccountOutputBuilder]) (utxoledger.Outputs, iotago.Outputs[iotago.Output], []*mock.HDWallet) { - input := t.Output(inputAlias) - - accountOutput := options.Apply(builder.NewAccountOutputBuilder(t.DefaultAddress(), t.DefaultAddress(), input.BaseTokenAmount()). - Mana(input.StoredMana()). - StateController(t.DefaultAddress()). - Governor(t.DefaultAddress()), - opts).MustBuild() - - outputStates := iotago.Outputs[iotago.Output]{accountOutput} - - // if amount was set by options, a remainder output needs to be created - if accountOutput.Amount != input.BaseTokenAmount() { - outputStates = append(outputStates, &iotago.BasicOutput{ - Amount: input.BaseTokenAmount() - accountOutput.Amount, - Mana: input.StoredMana() - accountOutput.Mana, - Conditions: iotago.BasicOutputUnlockConditions{ - &iotago.AddressUnlockCondition{Address: t.DefaultAddress()}, - }, - Features: iotago.BasicOutputFeatures{}, - }) - } - - return utxoledger.Outputs{input}, outputStates, []*mock.HDWallet{t.wallet} -} - -// CreateImplicitAccountFromInput creates an implicit account output. -func (t *TransactionFramework) CreateImplicitAccountFromInput(inputAlias string) (utxoledger.Outputs, iotago.Outputs[iotago.Output], *iotago.ImplicitAccountCreationAddress, []*mock.HDWallet) { - input := t.Output(inputAlias) - - //nolint:forcetypeassert - implicitAccountAddress := t.DefaultAddress(iotago.AddressImplicitAccountCreation).(*iotago.ImplicitAccountCreationAddress) - - basicOutput := &iotago.BasicOutput{ - Amount: input.BaseTokenAmount(), - Mana: input.StoredMana(), - Conditions: iotago.BasicOutputUnlockConditions{ - &iotago.AddressUnlockCondition{Address: implicitAccountAddress}, - }, - Features: iotago.BasicOutputFeatures{}, - } - - return utxoledger.Outputs{input}, iotago.Outputs[iotago.Output]{basicOutput}, implicitAccountAddress, []*mock.HDWallet{t.wallet} -} - -// CreateDelegationFromInput creates a new DelegationOutput with given options from an input. If the remainder Output -// is not created, then StoredMana from the input is not passed and can potentially be burned. -// In order not to burn it, it needs to be assigned manually in another output in the transaction. -func (t *TransactionFramework) CreateDelegationFromInput(inputAlias string, opts ...options.Option[builder.DelegationOutputBuilder]) (utxoledger.Outputs, iotago.Outputs[iotago.Output], []*mock.HDWallet) { - input := t.Output(inputAlias) - - delegationOutput := options.Apply(builder.NewDelegationOutputBuilder(&iotago.AccountAddress{}, t.DefaultAddress(), input.BaseTokenAmount()). - DelegatedAmount(input.BaseTokenAmount()), - opts).MustBuild() - - if delegationOutput.ValidatorAddress.AccountID() == iotago.EmptyAccountID || - delegationOutput.DelegatedAmount == 0 || - delegationOutput.StartEpoch == 0 { - panic(fmt.Sprintf("delegation output created incorrectly %+v", delegationOutput)) - } - - outputStates := iotago.Outputs[iotago.Output]{delegationOutput} - - // if options set an Amount, a remainder output needs to be created - if delegationOutput.Amount != input.BaseTokenAmount() { - outputStates = append(outputStates, &iotago.BasicOutput{ - Amount: input.BaseTokenAmount() - delegationOutput.Amount, - Mana: input.StoredMana(), - Conditions: iotago.BasicOutputUnlockConditions{ - &iotago.AddressUnlockCondition{Address: t.DefaultAddress()}, - }, - Features: iotago.BasicOutputFeatures{}, - }) - } - - return utxoledger.Outputs{input}, outputStates, []*mock.HDWallet{t.wallet} -} - -// DelayedClaimingTransition transitions DelegationOutput into delayed claiming state by setting DelegationID and EndEpoch. -func (t *TransactionFramework) DelayedClaimingTransition(inputAlias string, delegationEndEpoch iotago.EpochIndex) (utxoledger.Outputs, iotago.Outputs[iotago.Output], []*mock.HDWallet) { - input := t.Output(inputAlias) - if input.OutputType() != iotago.OutputDelegation { - panic(ierrors.Errorf("%s is not a delegation output, cannot transition to delayed claiming state", inputAlias)) - } - - prevOutput, ok := input.Output().Clone().(*iotago.DelegationOutput) - if !ok { - panic(ierrors.Errorf("cloned output %s is not a delegation output, cannot transition to delayed claiming state", inputAlias)) - } - - delegationBuilder := builder.NewDelegationOutputBuilderFromPrevious(prevOutput).EndEpoch(delegationEndEpoch) - if prevOutput.DelegationID == iotago.EmptyDelegationID() { - delegationBuilder.DelegationID(iotago.DelegationIDFromOutputID(input.OutputID())) - } - - return utxoledger.Outputs{input}, iotago.Outputs[iotago.Output]{delegationBuilder.MustBuild()}, []*mock.HDWallet{t.wallet} -} - -func (t *TransactionFramework) DestroyAccount(alias string) (consumedInputs *utxoledger.Output, outputs iotago.Outputs[iotago.Output], signingWallets []*mock.HDWallet) { - output := t.Output(alias) - - outputStates := iotago.Outputs[iotago.Output]{&iotago.BasicOutput{ - Amount: output.BaseTokenAmount(), - Mana: output.StoredMana(), - Conditions: iotago.BasicOutputUnlockConditions{ - &iotago.AddressUnlockCondition{Address: t.DefaultAddress()}, - }, - Features: iotago.BasicOutputFeatures{}, - }} - - return output, outputStates, []*mock.HDWallet{t.wallet} -} - -func (t *TransactionFramework) TransitionAccount(alias string, opts ...options.Option[builder.AccountOutputBuilder]) (consumedInput *utxoledger.Output, outputs iotago.Outputs[iotago.Output], signingWallets []*mock.HDWallet) { - output, exists := t.states[alias] - if !exists { - panic(fmt.Sprintf("account with alias %s does not exist", alias)) - } - - accountOutput, ok := output.Output().Clone().(*iotago.AccountOutput) - if !ok { - panic(fmt.Sprintf("output with alias %s is not *iotago.AccountOutput", alias)) - } - - accountBuilder := builder.NewAccountOutputBuilderFromPrevious(accountOutput) - accountOutput = options.Apply(accountBuilder, opts).MustBuild() - - return output, iotago.Outputs[iotago.Output]{accountOutput}, []*mock.HDWallet{t.wallet} -} - -func (t *TransactionFramework) TransitionImplicitAccountToAccountOutput(alias string, opts ...options.Option[builder.AccountOutputBuilder]) (consumedInput utxoledger.Outputs, outputs iotago.Outputs[iotago.Output], signingWallets []*mock.HDWallet) { - input, exists := t.states[alias] - if !exists { - panic(fmt.Sprintf("output with alias %s does not exist", alias)) - } - - basicOutput, isBasic := input.Output().(*iotago.BasicOutput) - if !isBasic { - panic(fmt.Sprintf("output with alias %s is not *iotago.BasicOutput", alias)) - } - if basicOutput.UnlockConditionSet().Address().Address.Type() != iotago.AddressImplicitAccountCreation { - panic(fmt.Sprintf("output with alias %s is not an implicit account", alias)) - } - - accountOutput := options.Apply(builder.NewAccountOutputBuilder(t.DefaultAddress(), t.DefaultAddress(), input.BaseTokenAmount()). - Mana(input.StoredMana()). - AccountID(iotago.AccountIDFromOutputID(input.OutputID())), - opts).MustBuild() - - return utxoledger.Outputs{input}, iotago.Outputs[iotago.Output]{accountOutput}, []*mock.HDWallet{t.wallet} -} - -func (t *TransactionFramework) Output(alias string) *utxoledger.Output { - output, exists := t.states[alias] - if !exists { - panic(ierrors.Errorf("output with given alias does not exist %s", alias)) - } - - return output -} - -func (t *TransactionFramework) OutputID(alias string) iotago.OutputID { - return t.Output(alias).OutputID() -} - -func (t *TransactionFramework) SignedTransaction(alias string) *iotago.SignedTransaction { - transaction, exists := t.signedTransactions[alias] - if !exists { - panic(ierrors.Errorf("transaction with given alias does not exist %s", alias)) - } - - return transaction -} - -func (t *TransactionFramework) SignedTransactionID(alias string) iotago.SignedTransactionID { - return lo.PanicOnErr(t.SignedTransaction(alias).ID()) -} - -func (t *TransactionFramework) SignedTransactions(aliases ...string) []*iotago.SignedTransaction { - return lo.Map(aliases, t.SignedTransaction) -} - -func (t *TransactionFramework) SignedTransactionIDs(aliases ...string) []iotago.SignedTransactionID { - return lo.Map(aliases, t.SignedTransactionID) -} - -func (t *TransactionFramework) Transaction(alias string) *iotago.Transaction { - transaction, exists := t.transactions[alias] - if !exists { - panic(ierrors.Errorf("transaction with given alias does not exist %s", alias)) - } - - return transaction -} - -func (t *TransactionFramework) TransactionID(alias string) iotago.TransactionID { - return lo.PanicOnErr(t.Transaction(alias).ID()) -} - -func (t *TransactionFramework) Transactions(aliases ...string) []*iotago.Transaction { - return lo.Map(aliases, t.Transaction) -} - -func (t *TransactionFramework) TransactionIDs(aliases ...string) []iotago.TransactionID { - return lo.Map(aliases, t.TransactionID) -} - -func (t *TransactionFramework) DefaultAddress(addressType ...iotago.AddressType) iotago.Address { - return t.wallet.Address(addressType...) -} - -// DelegationOutput options - -func WithDelegatedAmount(delegatedAmount iotago.BaseToken) options.Option[builder.DelegationOutputBuilder] { - return func(delegationBuilder *builder.DelegationOutputBuilder) { - delegationBuilder.DelegatedAmount(delegatedAmount) - } -} - -func WithDelegatedValidatorAddress(validatorAddress *iotago.AccountAddress) options.Option[builder.DelegationOutputBuilder] { - return func(delegationBuilder *builder.DelegationOutputBuilder) { - delegationBuilder.ValidatorAddress(validatorAddress) - } -} - -func WithDelegationStartEpoch(startEpoch iotago.EpochIndex) options.Option[builder.DelegationOutputBuilder] { - return func(delegationBuilder *builder.DelegationOutputBuilder) { - delegationBuilder.StartEpoch(startEpoch) - } -} - -func WithDelegationEndEpoch(endEpoch iotago.EpochIndex) options.Option[builder.DelegationOutputBuilder] { - return func(delegationBuilder *builder.DelegationOutputBuilder) { - delegationBuilder.EndEpoch(endEpoch) - } -} - -func WithDelegationConditions(delegationConditions iotago.DelegationOutputUnlockConditions) options.Option[builder.DelegationOutputBuilder] { - return func(delegationBuilder *builder.DelegationOutputBuilder) { - delegationBuilder.Address(delegationConditions.MustSet().Address().Address) - } -} - -func WithDelegationAmount(amount iotago.BaseToken) options.Option[builder.DelegationOutputBuilder] { - return func(delegationBuilder *builder.DelegationOutputBuilder) { - delegationBuilder.Amount(amount) - } -} - -// BlockIssuer options - -func WithBlockIssuerFeature(keys iotago.BlockIssuerKeys, expirySlot iotago.SlotIndex) options.Option[builder.AccountOutputBuilder] { - return func(accountBuilder *builder.AccountOutputBuilder) { - accountBuilder.BlockIssuer(keys, expirySlot) - } -} - -func WithAddBlockIssuerKey(key iotago.BlockIssuerKey) options.Option[builder.AccountOutputBuilder] { - return func(accountBuilder *builder.AccountOutputBuilder) { - transition := accountBuilder.GovernanceTransition() - transition.BlockIssuerTransition().AddKeys(key) - } -} - -func WithBlockIssuerKeys(keys iotago.BlockIssuerKeys) options.Option[builder.AccountOutputBuilder] { - return func(accountBuilder *builder.AccountOutputBuilder) { - transition := accountBuilder.GovernanceTransition() - transition.BlockIssuerTransition().Keys(keys) - } -} - -func WithBlockIssuerExpirySlot(expirySlot iotago.SlotIndex) options.Option[builder.AccountOutputBuilder] { - return func(accountBuilder *builder.AccountOutputBuilder) { - transition := accountBuilder.GovernanceTransition() - transition.BlockIssuerTransition().ExpirySlot(expirySlot) - } -} - -func WithStakingFeature(amount iotago.BaseToken, fixedCost iotago.Mana, startEpoch iotago.EpochIndex, optEndEpoch ...iotago.EpochIndex) options.Option[builder.AccountOutputBuilder] { - return func(accountBuilder *builder.AccountOutputBuilder) { - accountBuilder.Staking(amount, fixedCost, startEpoch, optEndEpoch...) - } -} - -// Account options - -func WithAccountMana(mana iotago.Mana) options.Option[builder.AccountOutputBuilder] { - return func(accountBuilder *builder.AccountOutputBuilder) { - accountBuilder.Mana(mana) - } -} - -func WithAccountAmount(amount iotago.BaseToken) options.Option[builder.AccountOutputBuilder] { - return func(accountBuilder *builder.AccountOutputBuilder) { - accountBuilder.Amount(amount) - } -} - -func WithAccountIncreasedFoundryCounter(diff uint32) options.Option[builder.AccountOutputBuilder] { - return func(accountBuilder *builder.AccountOutputBuilder) { - accountBuilder.FoundriesToGenerate(diff) - } -} - -func WithAccountImmutableFeatures(features iotago.AccountOutputImmFeatures) options.Option[builder.AccountOutputBuilder] { - return func(accountBuilder *builder.AccountOutputBuilder) { - for _, feature := range features.MustSet() { - switch feature.Type() { - case iotago.FeatureMetadata: - //nolint:forcetypeassert - accountBuilder.ImmutableMetadata(feature.(*iotago.MetadataFeature).Data) - case iotago.FeatureSender: - //nolint:forcetypeassert - accountBuilder.ImmutableSender(feature.(*iotago.SenderFeature).Address) - } - } - } -} - -func WithAccountConditions(conditions iotago.AccountOutputUnlockConditions) options.Option[builder.AccountOutputBuilder] { - return func(accountBuilder *builder.AccountOutputBuilder) { - for _, condition := range conditions.MustSet() { - switch condition.Type() { - case iotago.UnlockConditionStateControllerAddress: - //nolint:forcetypeassert - accountBuilder.StateController(condition.(*iotago.StateControllerAddressUnlockCondition).Address) - case iotago.UnlockConditionGovernorAddress: - //nolint:forcetypeassert - accountBuilder.Governor(condition.(*iotago.GovernorAddressUnlockCondition).Address) - } - } - } -} - -// TransactionBuilder options - -func WithInputs(inputs utxoledger.Outputs) options.Option[builder.TransactionBuilder] { - return func(txBuilder *builder.TransactionBuilder) { - for _, input := range inputs { - switch input.OutputType() { - case iotago.OutputFoundry: - // For foundries we need to unlock the alias - txBuilder.AddInput(&builder.TxInput{ - UnlockTarget: input.Output().UnlockConditionSet().ImmutableAccount().Address, - InputID: input.OutputID(), - Input: input.Output(), - }) - case iotago.OutputAccount: - // For alias we need to unlock the state controller - txBuilder.AddInput(&builder.TxInput{ - UnlockTarget: input.Output().UnlockConditionSet().StateControllerAddress().Address, - InputID: input.OutputID(), - Input: input.Output(), - }) - default: - txBuilder.AddInput(&builder.TxInput{ - UnlockTarget: input.Output().UnlockConditionSet().Address().Address, - InputID: input.OutputID(), - Input: input.Output(), - }) - } - } - } -} - -func WithAccountInput(input *utxoledger.Output, governorTransition bool) options.Option[builder.TransactionBuilder] { - return func(txBuilder *builder.TransactionBuilder) { - switch input.OutputType() { - case iotago.OutputAccount: - address := input.Output().UnlockConditionSet().StateControllerAddress().Address - if governorTransition { - address = input.Output().UnlockConditionSet().GovernorAddress().Address - } - txBuilder.AddInput(&builder.TxInput{ - UnlockTarget: address, - InputID: input.OutputID(), - Input: input.Output(), - }) - default: - panic("only OutputAccount can be added as account input") - } - } -} - -func WithAllotments(allotments iotago.Allotments) options.Option[builder.TransactionBuilder] { - return func(txBuilder *builder.TransactionBuilder) { - for _, allotment := range allotments { - txBuilder.IncreaseAllotment(allotment.AccountID, allotment.Mana) - } - } -} - -func WithSlotCreated(creationSlot iotago.SlotIndex) options.Option[builder.TransactionBuilder] { - return func(txBuilder *builder.TransactionBuilder) { - txBuilder.SetCreationSlot(creationSlot) - } -} - -func WithContextInputs(contextInputs iotago.TxEssenceContextInputs) options.Option[builder.TransactionBuilder] { - return func(txBuilder *builder.TransactionBuilder) { - for _, input := range contextInputs { - txBuilder.AddContextInput(input) - } - } -} - -func WithOutputs(outputs iotago.Outputs[iotago.Output]) options.Option[builder.TransactionBuilder] { - return func(txBuilder *builder.TransactionBuilder) { - for _, output := range outputs { - txBuilder.AddOutput(output) - } - } -} - -func WithTaggedDataPayload(payload *iotago.TaggedData) options.Option[builder.TransactionBuilder] { - return func(txBuilder *builder.TransactionBuilder) { - txBuilder.AddTaggedDataPayload(payload) - } -} diff --git a/tools/genesis-snapshot/main.go b/tools/genesis-snapshot/main.go index a8d292a87..63d303a83 100644 --- a/tools/genesis-snapshot/main.go +++ b/tools/genesis-snapshot/main.go @@ -8,7 +8,8 @@ import ( "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/runtime/options" - "github.com/iotaledger/iota-core/pkg/protocol/snapshotcreator" + "github.com/iotaledger/iota-core/pkg/testsuite/mock" + "github.com/iotaledger/iota-core/pkg/testsuite/snapshotcreator" "github.com/iotaledger/iota-core/tools/genesis-snapshot/presets" ) @@ -49,7 +50,8 @@ func parseFlags() (opt []options.Option[snapshotcreator.Options], conf string) { if err != nil { log.Fatal(ierrors.Errorf("failed to decode base58 seed, using the default one: %w", err)) } - opt = append(opt, snapshotcreator.WithGenesisSeed(genesisSeed)) + keyManager := mock.NewKeyManager(genesisSeed[:], 0) + opt = append(opt, snapshotcreator.WithGenesisKeyManager(keyManager)) } return opt, *config diff --git a/tools/genesis-snapshot/presets/presets.go b/tools/genesis-snapshot/presets/presets.go index d04865543..8157c81e2 100644 --- a/tools/genesis-snapshot/presets/presets.go +++ b/tools/genesis-snapshot/presets/presets.go @@ -9,8 +9,8 @@ import ( "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/runtime/options" "github.com/iotaledger/iota-core/pkg/protocol" - "github.com/iotaledger/iota-core/pkg/protocol/snapshotcreator" - "github.com/iotaledger/iota-core/pkg/testsuite" + "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/hexutil" ) @@ -40,38 +40,38 @@ var Docker = []options.Option[snapshotcreator.Options]{ snapshotcreator.AccountDetails{ // node-1-validator AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x293dc170d9a59474e6d81cfba7f7d924c09b25d7166bcfba606e53114d0a758b"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x293dc170d9a59474e6d81cfba7f7d924c09b25d7166bcfba606e53114d0a758b"))), - Amount: testsuite.MinValidatorAccountAmount, + Amount: mock.MinValidatorAccountAmount, IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(lo.PanicOnErr(hexutil.DecodeHex("0x293dc170d9a59474e6d81cfba7f7d924c09b25d7166bcfba606e53114d0a758b")))), ExpirySlot: iotago.MaxSlotIndex, BlockIssuanceCredits: iotago.MaxBlockIssuanceCredits / 4, StakingEpochEnd: iotago.MaxEpochIndex, FixedCost: 1, - StakedAmount: testsuite.MinValidatorAccountAmount, - Mana: iotago.Mana(testsuite.MinValidatorAccountAmount), + StakedAmount: mock.MinValidatorAccountAmount, + Mana: iotago.Mana(mock.MinValidatorAccountAmount), }, snapshotcreator.AccountDetails{ // node-2-validator AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x05c1de274451db8de8182d64c6ee0dca3ae0c9077e0b4330c976976171d79064"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x05c1de274451db8de8182d64c6ee0dca3ae0c9077e0b4330c976976171d79064"))), - Amount: testsuite.MinValidatorAccountAmount, + Amount: mock.MinValidatorAccountAmount, IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(lo.PanicOnErr(hexutil.DecodeHex("0x05c1de274451db8de8182d64c6ee0dca3ae0c9077e0b4330c976976171d79064")))), ExpirySlot: iotago.MaxSlotIndex, BlockIssuanceCredits: iotago.MaxBlockIssuanceCredits / 4, StakingEpochEnd: iotago.MaxEpochIndex, FixedCost: 1, - StakedAmount: testsuite.MinValidatorAccountAmount, - Mana: iotago.Mana(testsuite.MinValidatorAccountAmount), + StakedAmount: mock.MinValidatorAccountAmount, + Mana: iotago.Mana(mock.MinValidatorAccountAmount), }, snapshotcreator.AccountDetails{ // node-3-validator AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x1e4b21eb51dcddf65c20db1065e1f1514658b23a3ddbf48d30c0efc926a9a648"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x1e4b21eb51dcddf65c20db1065e1f1514658b23a3ddbf48d30c0efc926a9a648"))), - Amount: testsuite.MinValidatorAccountAmount, + Amount: mock.MinValidatorAccountAmount, IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(lo.PanicOnErr(hexutil.DecodeHex("0x1e4b21eb51dcddf65c20db1065e1f1514658b23a3ddbf48d30c0efc926a9a648")))), ExpirySlot: iotago.MaxSlotIndex, BlockIssuanceCredits: iotago.MaxBlockIssuanceCredits / 4, StakingEpochEnd: iotago.MaxEpochIndex, FixedCost: 1, - StakedAmount: testsuite.MinValidatorAccountAmount, - Mana: iotago.Mana(testsuite.MinValidatorAccountAmount), + StakedAmount: mock.MinValidatorAccountAmount, + Mana: iotago.Mana(mock.MinValidatorAccountAmount), }, snapshotcreator.AccountDetails{ /* @@ -84,11 +84,11 @@ var Docker = []options.Option[snapshotcreator.Options]{ */ AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x997be92a22b1933f36e26fba5f721756f95811d6b4ae21564197c2bfa4f28270"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x997be92a22b1933f36e26fba5f721756f95811d6b4ae21564197c2bfa4f28270"))), - Amount: testsuite.MinIssuerAccountAmount, + Amount: mock.MinIssuerAccountAmount, IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(lo.PanicOnErr(hexutil.DecodeHex("0x997be92a22b1933f36e26fba5f721756f95811d6b4ae21564197c2bfa4f28270")))), ExpirySlot: iotago.MaxSlotIndex, BlockIssuanceCredits: iotago.MaxBlockIssuanceCredits / 4, - Mana: iotago.Mana(testsuite.MinIssuerAccountAmount), + Mana: iotago.Mana(mock.MinIssuerAccountAmount), }, ), snapshotcreator.WithBasicOutputs( @@ -128,38 +128,38 @@ var Feature = []options.Option[snapshotcreator.Options]{ snapshotcreator.AccountDetails{ // node-01 AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x01fb6b9db5d96240aef00bc950d1c67a6494513f6d7cf784e57b4972b96ab2fe"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x01fb6b9db5d96240aef00bc950d1c67a6494513f6d7cf784e57b4972b96ab2fe"))), - Amount: testsuite.MinValidatorAccountAmount, + Amount: mock.MinValidatorAccountAmount, IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(lo.PanicOnErr(hexutil.DecodeHex("0x01fb6b9db5d96240aef00bc950d1c67a6494513f6d7cf784e57b4972b96ab2fe")))), ExpirySlot: iotago.MaxSlotIndex, BlockIssuanceCredits: iotago.MaxBlockIssuanceCredits / 4, StakingEpochEnd: iotago.MaxEpochIndex, FixedCost: 1, - StakedAmount: testsuite.MinValidatorAccountAmount, - Mana: iotago.Mana(testsuite.MinValidatorAccountAmount), + StakedAmount: mock.MinValidatorAccountAmount, + Mana: iotago.Mana(mock.MinValidatorAccountAmount), }, snapshotcreator.AccountDetails{ // node-02 AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x83e7f71a440afd48981a8b4684ddae24434b7182ce5c47cfb56ac528525fd4b6"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x83e7f71a440afd48981a8b4684ddae24434b7182ce5c47cfb56ac528525fd4b6"))), - Amount: testsuite.MinValidatorAccountAmount, + Amount: mock.MinValidatorAccountAmount, IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(lo.PanicOnErr(hexutil.DecodeHex("0x83e7f71a440afd48981a8b4684ddae24434b7182ce5c47cfb56ac528525fd4b6")))), ExpirySlot: iotago.MaxSlotIndex, BlockIssuanceCredits: iotago.MaxBlockIssuanceCredits / 4, StakingEpochEnd: iotago.MaxEpochIndex, FixedCost: 1, - StakedAmount: testsuite.MinValidatorAccountAmount, - Mana: iotago.Mana(testsuite.MinValidatorAccountAmount), + StakedAmount: mock.MinValidatorAccountAmount, + Mana: iotago.Mana(mock.MinValidatorAccountAmount), }, snapshotcreator.AccountDetails{ // node-03 AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0xac628986b2ef52a1679f2289fcd7b4198476976dea4c30ae34ff04ae52e14805"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0xac628986b2ef52a1679f2289fcd7b4198476976dea4c30ae34ff04ae52e14805"))), - Amount: testsuite.MinValidatorAccountAmount, + Amount: mock.MinValidatorAccountAmount, IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(lo.PanicOnErr(hexutil.DecodeHex("0xac628986b2ef52a1679f2289fcd7b4198476976dea4c30ae34ff04ae52e14805")))), ExpirySlot: iotago.MaxSlotIndex, BlockIssuanceCredits: iotago.MaxBlockIssuanceCredits / 4, StakingEpochEnd: iotago.MaxEpochIndex, FixedCost: 1, - StakedAmount: testsuite.MinValidatorAccountAmount, - Mana: iotago.Mana(testsuite.MinValidatorAccountAmount), + StakedAmount: mock.MinValidatorAccountAmount, + Mana: iotago.Mana(mock.MinValidatorAccountAmount), }, snapshotcreator.AccountDetails{ /* @@ -171,11 +171,11 @@ var Feature = []options.Option[snapshotcreator.Options]{ */ AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x670a1a20ddb02a6cec53ec3196bc7d5bd26df2f5a6ca90b5fffd71364f104b25"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x670a1a20ddb02a6cec53ec3196bc7d5bd26df2f5a6ca90b5fffd71364f104b25"))), - Amount: testsuite.MinIssuerAccountAmount, + Amount: mock.MinIssuerAccountAmount, IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(lo.PanicOnErr(hexutil.DecodeHex("0x670a1a20ddb02a6cec53ec3196bc7d5bd26df2f5a6ca90b5fffd71364f104b25")))), ExpirySlot: iotago.MaxSlotIndex, BlockIssuanceCredits: iotago.MaxBlockIssuanceCredits / 4, - Mana: iotago.Mana(testsuite.MinIssuerAccountAmount), + Mana: iotago.Mana(mock.MinIssuerAccountAmount), }, ), snapshotcreator.WithBasicOutputs(