From b6da0b935e3a47682f9765166d0443f5e3a6c576 Mon Sep 17 00:00:00 2001 From: jkrvivian Date: Wed, 27 Mar 2024 16:02:36 +0800 Subject: [PATCH 01/13] Add Validator rewards claiming docker test --- tools/docker-network/tests/dockerframework.go | 59 ++++++++++++ tools/docker-network/tests/rewards_test.go | 89 +++++++++++++++++++ tools/docker-network/tests/wallet.go | 56 ++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 tools/docker-network/tests/rewards_test.go diff --git a/tools/docker-network/tests/dockerframework.go b/tools/docker-network/tests/dockerframework.go index e2a0d3c76..e9701cff8 100644 --- a/tools/docker-network/tests/dockerframework.go +++ b/tools/docker-network/tests/dockerframework.go @@ -20,6 +20,8 @@ import ( "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/runtime/options" "github.com/iotaledger/hive.go/runtime/syncutils" + "github.com/iotaledger/hive.go/serializer/v2/serix" + "github.com/iotaledger/iota-core/pkg/model" "github.com/iotaledger/iota-core/pkg/protocol" "github.com/iotaledger/iota-core/pkg/testsuite/mock" "github.com/iotaledger/iota-core/pkg/testsuite/snapshotcreator" @@ -392,6 +394,42 @@ func (d *DockerTestFramework) CreateTaggedDataBlock(issuerID iotago.AccountID, t }, issuerID, congestionResp, issuerResp) } +func (d *DockerTestFramework) SubmitValidationBlock(issuerID iotago.AccountID) *iotago.Block { + issuer := d.wallet.Account(issuerID) + ctx := context.TODO() + issuingTime := time.Now() + clt := d.wallet.DefaultClient() + currentSlot := d.wallet.DefaultClient().LatestAPI().TimeProvider().SlotFromTime(issuingTime) + apiForSlot := d.wallet.DefaultClient().APIForSlot(currentSlot) + + issuerResp, _ := d.PrepareBlockIssuance(ctx, clt, issuer.Address) + + protocolParametersHash, err := apiForSlot.ProtocolParameters().Hash() + require.NoError(d.Testing, err) + + blockBuilder := builder.NewValidationBlockBuilder(apiForSlot). + IssuingTime(issuingTime). + SlotCommitmentID(issuerResp.LatestCommitment.MustID()). + LatestFinalizedSlot(issuerResp.LatestFinalizedSlot). + StrongParents(issuerResp.StrongParents). + WeakParents(issuerResp.WeakParents). + ShallowLikeParents(issuerResp.ShallowLikeParents). + HighestSupportedVersion(clt.LatestAPI().Version()). + ProtocolParametersHash(protocolParametersHash). + Sign(issuerID, lo.Return1(d.wallet.KeyPair(issuer.AddressIndex))) + + block, err := blockBuilder.Build() + require.NoError(d.Testing, err) + + // Make sure we only create syntactically valid blocks. + _, err = model.BlockFromBlock(block, serix.WithValidation()) + require.NoError(d.Testing, err) + + d.SubmitBlock(ctx, block) + + return block +} + func (d *DockerTestFramework) CreateBasicOutputBlock(issuerAccountID iotago.AccountID) (*iotago.Block, *iotago.SignedTransaction, *mock.OutputData) { clt := d.wallet.DefaultClient() ctx := context.Background() @@ -542,6 +580,27 @@ func (d *DockerTestFramework) CreateAccount(opts ...options.Option[builder.Accou return fullAccount } +func (d *DockerTestFramework) ClaimRewardsForValidator(ctx context.Context, validator *mock.AccountData) *mock.AccountData { + clt := d.wallet.DefaultClient() + issuerResp, congestionResp := d.PrepareBlockIssuance(ctx, clt, validator.Address) + signedTx := d.wallet.ClaimValidatorRewards(validator.ID, issuerResp) + + d.SubmitPayload(ctx, signedTx, validator.ID, congestionResp, issuerResp) + d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) + + // update account data of validator + accountInfo := &mock.AccountData{ + ID: validator.ID, + Address: validator.Address, + AddressIndex: validator.AddressIndex, + OutputID: iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), + Output: signedTx.Transaction.Outputs[0].(*iotago.AccountOutput), + } + d.wallet.AddAccount(validator.ID, accountInfo) + + return accountInfo +} + // DelegateToValidator requests faucet funds and delegate the UTXO output to the validator. func (d *DockerTestFramework) DelegateToValidator(fromID iotago.AccountID, accountAddress *iotago.AccountAddress) (iotago.OutputID, *iotago.DelegationOutput) { from := d.wallet.Account(fromID) diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go new file mode 100644 index 000000000..a477640a4 --- /dev/null +++ b/tools/docker-network/tests/rewards_test.go @@ -0,0 +1,89 @@ +//go:build dockertests + +package tests + +import ( + "context" + "fmt" + "testing" + "time" + + iotago "github.com/iotaledger/iota.go/v4" + "github.com/stretchr/testify/require" +) + +// Test_ValidatorRewards tests the rewards for a validator. +// 1. Create an account with staking feature. +// 2. Issue candidacy payloads for the account and wait until the account is in the committee. +// 3. Issue validation blocks until claiming slot is reached. +// 4. Claim rewards and check if the mana increased as expected. +func Test_ValidatorRewards(t *testing.T) { + d := NewDockerTestFramework(t, + WithProtocolParametersOptions( + iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), + iotago.WithLivenessOptions(10, 10, 2, 4, 8), + iotago.WithStakingOptions(3, 10, 10), + )) + defer d.Stop() + + d.AddValidatorNode("V1", "docker-network-inx-validator-1-1", "http://localhost:8050", "rms1pzg8cqhfxqhq7pt37y8cs4v5u4kcc48lquy2k73ehsdhf5ukhya3y5rx2w6") + d.AddValidatorNode("V2", "docker-network-inx-validator-2-1", "http://localhost:8060", "rms1pqm4xk8e9ny5w5rxjkvtp249tfhlwvcshyr3pc0665jvp7g3hc875k538hl") + d.AddValidatorNode("V3", "docker-network-inx-validator-3-1", "http://localhost:8070", "rms1pp4wuuz0y42caz48vv876qfpmffswsvg40zz8v79sy8cp0jfxm4kunflcgt") + d.AddValidatorNode("V4", "docker-network-inx-validator-4-1", "http://localhost:8040", "rms1pr8cxs3dzu9xh4cduff4dd4cxdthpjkpwmz2244f75m0urslrsvtsshrrjw") + d.AddNode("node5", "docker-network-node-5-1", "http://localhost:8090") + + err := d.Run() + require.NoError(t, err) + + d.WaitUntilNetworkReady() + + ctx := context.Background() + clt := d.wallet.DefaultClient() + status := d.NodeStatus("V1") + currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(status.LatestAcceptedBlockSlot) + + // Set end epoch so the staking feature can be removed as soon as possible. + endEpoch := currentEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + // The earliest epoch in which we can remove the staking feature and claim rewards. + claimingSlot := clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) + + account := d.CreateAccount(WithStakingFeature(100, 1, currentEpoch, endEpoch)) + initialMana := account.Output.StoredMana() + + // continue issuing candidacy payload for account in the background + go func() { + fmt.Println("Issuing candidacy payloads for account in the background...") + defer fmt.Println("Issuing candidacy payloads for account in the background......done") + + currentSlot := clt.CommittedAPI().TimeProvider().CurrentSlot() + + for i := currentSlot; i < claimingSlot; i++ { + d.IssueCandidacyPayloadFromAccount(account.ID) + time.Sleep(10 * time.Second) + } + }() + + // make sure the account is in the committee, so it can issue validation blocks + accountAddrBech32 := account.Address.Bech32(clt.CommittedAPI().ProtocolParameters().Bech32HRP()) + d.AssertCommittee(currentEpoch+2, append(d.AccountsFromNodes(d.Nodes("V1", "V3", "V2", "V4")...), accountAddrBech32)) + + // issua validation blocks to have performance + currentSlot := clt.CommittedAPI().TimeProvider().CurrentSlot() + slotToWait := claimingSlot - currentSlot + secToWait := time.Duration(slotToWait) * time.Duration(clt.CommittedAPI().TimeProvider().SlotDurationSeconds()) * time.Second + fmt.Println("Wait for ", secToWait, "until expected slot: ", claimingSlot) + + for i := currentSlot; i < claimingSlot; i++ { + d.SubmitValidationBlock(account.ID) + time.Sleep(10 * time.Second) + } + + // claim rewards that put to the account output + account = d.ClaimRewardsForValidator(ctx, account) + + // check if the mana increased as expected + outputFromAPI, err := clt.OutputByID(ctx, account.OutputID) + require.NoError(t, err) + require.Greater(t, outputFromAPI.StoredMana(), initialMana) + require.Equal(t, account.Output.StoredMana(), outputFromAPI.StoredMana()) +} diff --git a/tools/docker-network/tests/wallet.go b/tools/docker-network/tests/wallet.go index a1a70372e..7cb98eacf 100644 --- a/tools/docker-network/tests/wallet.go +++ b/tools/docker-network/tests/wallet.go @@ -3,6 +3,7 @@ package tests import ( + "context" "crypto/ed25519" "math/big" "sync" @@ -509,3 +510,58 @@ func (w *DockerWallet) CreateBasicOutputFromInput(input *mock.OutputData, issuer return signedTx } + +func (w *DockerWallet) ClaimValidatorRewards(issuerAccountID iotago.AccountID, issuerResp *api.IssuanceBlockHeaderResponse) *iotago.SignedTransaction { + acc := w.Account(issuerAccountID) + clt := w.DefaultClient() + + currentSlot := clt.LatestAPI().TimeProvider().SlotFromTime(time.Now()) + apiForSlot := clt.APIForSlot(currentSlot) + + rewardResp, err := clt.Rewards(context.Background(), acc.OutputID) + require.NoError(w.Testing, err) + potentialMana := w.PotentialMana(apiForSlot, acc.Output, acc.OutputID, currentSlot) + storedMana := w.StoredMana(apiForSlot, acc.Output, acc.OutputID, currentSlot) + + accountOutput := builder.NewAccountOutputBuilderFromPrevious(acc.Output). + RemoveFeature(iotago.FeatureStaking). + Mana(potentialMana + storedMana + rewardResp.Rewards). + MustBuild() + + signedTx, err := builder.NewTransactionBuilder(apiForSlot, w.AddressSigner(acc.AddressIndex)). + AddInput(&builder.TxInput{ + UnlockTarget: acc.Output.UnlockConditionSet().Address().Address, + InputID: acc.OutputID, + Input: acc.Output, + }). + AddRewardInput(&iotago.RewardInput{Index: 0}, rewardResp.Rewards). + AddBlockIssuanceCreditInput(&iotago.BlockIssuanceCreditInput{ + AccountID: accountOutput.AccountID, + }). + AddCommitmentInput(&iotago.CommitmentInput{CommitmentID: lo.Return1(issuerResp.LatestCommitment.ID())}). + AddOutput(accountOutput). + SetCreationSlot(currentSlot). + AllotAllMana(currentSlot, issuerAccountID, 0). + AddTaggedDataPayload(&iotago.TaggedData{Tag: []byte("basic")}). + Build() + require.NoError(w.Testing, err) + + w.AddOutput(iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), &mock.OutputData{ + ID: iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), + Output: accountOutput, + Address: acc.Address, + AddressIndex: acc.AddressIndex, + }) + + return signedTx +} + +// Computes the Potential Mana that the output generates until the current slot. +func (w *DockerWallet) PotentialMana(api iotago.API, input iotago.Output, inputID iotago.OutputID, currentSlot iotago.SlotIndex) iotago.Mana { + return lo.PanicOnErr(iotago.PotentialMana(api.ManaDecayProvider(), api.StorageScoreStructure(), input, inputID.CreationSlot(), currentSlot)) +} + +// Computes the decay on stored mana that the output holds until the current slot. +func (w *DockerWallet) StoredMana(api iotago.API, input iotago.Output, inputID iotago.OutputID, currentSlot iotago.SlotIndex) iotago.Mana { + return lo.PanicOnErr(api.ManaDecayProvider().DecayManaBySlots(input.StoredMana(), inputID.CreationSlot(), currentSlot)) +} From 615177f079f77990e84da0e5950eb0181fb1ac38 Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Wed, 27 Mar 2024 12:59:08 +0100 Subject: [PATCH 02/13] Feat: new branch for spenddag preacceptance --- pkg/core/acceptance/state.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/core/acceptance/state.go b/pkg/core/acceptance/state.go index 6212fac73..30aecf19a 100644 --- a/pkg/core/acceptance/state.go +++ b/pkg/core/acceptance/state.go @@ -8,6 +8,9 @@ const ( // Pending is the state of pending spenders. Pending State = iota + // PreAccepted is the state of pre-accepted spenders. + PreAccepted + // Accepted is the state of accepted spenders. Accepted From 8045ab719726f50acb2dab76435005dfe8195792 Mon Sep 17 00:00:00 2001 From: jkrvivian Date: Wed, 27 Mar 2024 21:10:40 +0800 Subject: [PATCH 03/13] Add delegator rewards claiming docker test --- tools/docker-network/tests/dockerframework.go | 11 ++++ tools/docker-network/tests/rewards_test.go | 56 +++++++++++++++++++ tools/docker-network/tests/wallet.go | 44 +++++++++++++++ 3 files changed, 111 insertions(+) diff --git a/tools/docker-network/tests/dockerframework.go b/tools/docker-network/tests/dockerframework.go index e9701cff8..874a1c04c 100644 --- a/tools/docker-network/tests/dockerframework.go +++ b/tools/docker-network/tests/dockerframework.go @@ -601,6 +601,17 @@ func (d *DockerTestFramework) ClaimRewardsForValidator(ctx context.Context, vali return accountInfo } +func (d *DockerTestFramework) ClaimRewardsForDelegator(ctx context.Context, account *mock.AccountData, delegationOutputID iotago.OutputID) iotago.OutputID { + clt := d.wallet.DefaultClient() + issuerResp, congestionResp := d.PrepareBlockIssuance(ctx, clt, account.Address) + signedTx := d.wallet.ClaimDelegatorRewards(delegationOutputID, account.ID, issuerResp) + + d.SubmitPayload(ctx, signedTx, account.ID, congestionResp, issuerResp) + d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) + + return iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) +} + // DelegateToValidator requests faucet funds and delegate the UTXO output to the validator. func (d *DockerTestFramework) DelegateToValidator(fromID iotago.AccountID, accountAddress *iotago.AccountAddress) (iotago.OutputID, *iotago.DelegationOutput) { from := d.wallet.Account(fromID) diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go index a477640a4..01a5efa74 100644 --- a/tools/docker-network/tests/rewards_test.go +++ b/tools/docker-network/tests/rewards_test.go @@ -87,3 +87,59 @@ func Test_ValidatorRewards(t *testing.T) { require.Greater(t, outputFromAPI.StoredMana(), initialMana) require.Equal(t, account.Output.StoredMana(), outputFromAPI.StoredMana()) } + +// Test_DelegatorRewards tests the rewards for a delegator. +// 1. Create an account and delegate funds to a validator. +// 2. Wait long enough so there's rewards can be claimed. +// 3. Claim rewards and check if the mana increased as expected. +func Test_DelegatorRewards(t *testing.T) { + d := NewDockerTestFramework(t, + WithProtocolParametersOptions( + iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 3), + iotago.WithLivenessOptions(10, 10, 2, 4, 5), + iotago.WithStakingOptions(3, 10, 10), + )) + defer d.Stop() + + d.AddValidatorNode("V1", "docker-network-inx-validator-1-1", "http://localhost:8050", "rms1pzg8cqhfxqhq7pt37y8cs4v5u4kcc48lquy2k73ehsdhf5ukhya3y5rx2w6") + d.AddValidatorNode("V2", "docker-network-inx-validator-2-1", "http://localhost:8060", "rms1pqm4xk8e9ny5w5rxjkvtp249tfhlwvcshyr3pc0665jvp7g3hc875k538hl") + d.AddValidatorNode("V3", "docker-network-inx-validator-3-1", "http://localhost:8070", "rms1pp4wuuz0y42caz48vv876qfpmffswsvg40zz8v79sy8cp0jfxm4kunflcgt") + d.AddValidatorNode("V4", "docker-network-inx-validator-4-1", "http://localhost:8040", "rms1pr8cxs3dzu9xh4cduff4dd4cxdthpjkpwmz2244f75m0urslrsvtsshrrjw") + d.AddNode("node5", "docker-network-node-5-1", "http://localhost:8090") + + err := d.Run() + require.NoError(t, err) + + d.WaitUntilNetworkReady() + + ctx := context.Background() + clt := d.wallet.DefaultClient() + + account := d.CreateAccount() + + // delegate funds to V2 + delegationOutputID, delegationOutput := d.DelegateToValidator(account.ID, d.Node("V2").AccountAddress(t)) + d.AwaitCommitment(delegationOutputID.CreationSlot()) + + // check if V2 received the delegator stake + v2Resp, err := clt.Validator(ctx, d.Node("V2").AccountAddress(t)) + require.NoError(t, err) + require.Greater(t, v2Resp.PoolStake, v2Resp.ValidatorStake) + + // wait until next epoch so the rewards can be claimed + expectedSlot := clt.CommittedAPI().TimeProvider().EpochStart(delegationOutput.StartEpoch + 2) + slotToWait := expectedSlot - clt.CommittedAPI().TimeProvider().CurrentSlot() + secToWait := time.Duration(slotToWait) * time.Duration(clt.CommittedAPI().ProtocolParameters().SlotDurationInSeconds()) * time.Second + fmt.Println("Wait for ", secToWait, "until expected slot: ", expectedSlot) + time.Sleep(secToWait) + + // claim rewards that put to an basic output + rewardsOutputID := d.ClaimRewardsForDelegator(ctx, account, delegationOutputID) + + // check if the mana increased as expected + outputFromAPI, err := clt.OutputByID(ctx, rewardsOutputID) + require.NoError(t, err) + + rewardsOutput := d.wallet.Output(rewardsOutputID) + require.Equal(t, rewardsOutput.Output.StoredMana(), outputFromAPI.StoredMana()) +} diff --git a/tools/docker-network/tests/wallet.go b/tools/docker-network/tests/wallet.go index 7cb98eacf..b65a6e4d0 100644 --- a/tools/docker-network/tests/wallet.go +++ b/tools/docker-network/tests/wallet.go @@ -556,6 +556,50 @@ func (w *DockerWallet) ClaimValidatorRewards(issuerAccountID iotago.AccountID, i return signedTx } +func (w *DockerWallet) ClaimDelegatorRewards(delegationOutputID iotago.OutputID, issuerAccountID iotago.AccountID, issuerResp *api.IssuanceBlockHeaderResponse) *iotago.SignedTransaction { + delegationOutput := w.Output(delegationOutputID) + acc := w.Account(issuerAccountID) + clt := w.DefaultClient() + + currentSlot := clt.LatestAPI().TimeProvider().SlotFromTime(time.Now()) + apiForSlot := clt.APIForSlot(currentSlot) + potentialMana := w.PotentialMana(apiForSlot, delegationOutput.Output, delegationOutputID, currentSlot) + + rewardsResp, err := clt.Rewards(context.Background(), delegationOutputID) + require.NoError(w.Testing, err) + + // Create Basic Output where the reward will be put. + basicOutput := builder.NewBasicOutputBuilder(delegationOutput.Output.UnlockConditionSet().Address().Address, delegationOutput.Output.BaseTokenAmount()). + Mana(rewardsResp.Rewards + potentialMana). + MustBuild() + + signedTx, err := builder.NewTransactionBuilder(apiForSlot, w.AddressSigner(delegationOutput.AddressIndex)). + AddInput(&builder.TxInput{ + UnlockTarget: delegationOutput.Output.UnlockConditionSet().Address().Address, + InputID: delegationOutputID, + Input: delegationOutput.Output, + }). + AddRewardInput(&iotago.RewardInput{Index: 0}, rewardsResp.Rewards). + AddBlockIssuanceCreditInput(&iotago.BlockIssuanceCreditInput{ + AccountID: acc.ID, + }). + AddCommitmentInput(&iotago.CommitmentInput{CommitmentID: lo.Return1(issuerResp.LatestCommitment.ID())}). + AddOutput(basicOutput). + SetCreationSlot(currentSlot). + AllotAllMana(currentSlot, issuerAccountID, 0). + Build() + require.NoError(w.Testing, err) + + w.AddOutput(iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), &mock.OutputData{ + ID: iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), + Output: basicOutput, + Address: delegationOutput.Output.UnlockConditionSet().Address().Address, + AddressIndex: delegationOutput.AddressIndex, + }) + + return signedTx +} + // Computes the Potential Mana that the output generates until the current slot. func (w *DockerWallet) PotentialMana(api iotago.API, input iotago.Output, inputID iotago.OutputID, currentSlot iotago.SlotIndex) iotago.Mana { return lo.PanicOnErr(iotago.PotentialMana(api.ManaDecayProvider(), api.StorageScoreStructure(), input, inputID.CreationSlot(), currentSlot)) From 7aa74c61c0e94524f6c9866a9aed63e7c65eae4f Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Thu, 28 Mar 2024 00:17:33 +0100 Subject: [PATCH 04/13] Feat: trigger acceptance only after the winning state has been attested --- pkg/core/acceptance/state.go | 3 - pkg/core/weight/value.go | 15 +++++ pkg/core/weight/weight.go | 57 ++++++++++++++++++- .../mempool/spenddag/spenddagv1/spender.go | 13 ++++- 4 files changed, 82 insertions(+), 6 deletions(-) diff --git a/pkg/core/acceptance/state.go b/pkg/core/acceptance/state.go index 30aecf19a..6212fac73 100644 --- a/pkg/core/acceptance/state.go +++ b/pkg/core/acceptance/state.go @@ -8,9 +8,6 @@ const ( // Pending is the state of pending spenders. Pending State = iota - // PreAccepted is the state of pre-accepted spenders. - PreAccepted - // Accepted is the state of accepted spenders. Accepted diff --git a/pkg/core/weight/value.go b/pkg/core/weight/value.go index 2bcb0706d..a251fa581 100644 --- a/pkg/core/weight/value.go +++ b/pkg/core/weight/value.go @@ -13,6 +13,9 @@ type Value struct { // validatorsWeight is the second tier which tracks weight in a non-cumulative manner (BFT style). validatorsWeight int64 + // attestorsWeight is the third tier which tracks weight in a non-cumulative manner (BFT style). + attestorsWeight int64 + // acceptanceState is the final tier which determines the decision of the spender. acceptanceState acceptance.State } @@ -55,6 +58,18 @@ func (v Value) SetValidatorsWeight(weight int64) Value { return v } +// AttestorsWeight returns the weight of the validators. +func (v Value) AttestorsWeight() int64 { + return v.attestorsWeight +} + +// SetAttestorsWeight sets the weight of the attestors and returns the new Value. +func (v Value) SetAttestorsWeight(weight int64) Value { + v.attestorsWeight = weight + + return v +} + // AcceptanceState returns the acceptance state of the Value. func (v Value) AcceptanceState() acceptance.State { return v.acceptanceState diff --git a/pkg/core/weight/weight.go b/pkg/core/weight/weight.go index 0fb43fd9f..d0f21f3f5 100644 --- a/pkg/core/weight/weight.go +++ b/pkg/core/weight/weight.go @@ -17,6 +17,9 @@ type Weight struct { // Voters is the set of voters contributing to the weight Voters ds.Set[account.SeatIndex] + // Attestors is the set of attestors contributing to the weight. + Attestors ds.Set[account.SeatIndex] + // value is the current weight Value. value Value @@ -27,8 +30,9 @@ type Weight struct { // New creates a new Weight instance. func New() *Weight { w := &Weight{ - Voters: ds.NewSet[account.SeatIndex](), - OnUpdate: event.New1[Value](), + Voters: ds.NewSet[account.SeatIndex](), + Attestors: ds.NewSet[account.SeatIndex](), + OnUpdate: event.New1[Value](), } return w @@ -103,6 +107,41 @@ func (w *Weight) DeleteVoter(seat account.SeatIndex) *Weight { return w } +// AddAttestor adds the given voter to the list of Attestors, updates the weight and returns the Weight (for chaining). +func (w *Weight) AddAttestor(seat account.SeatIndex) *Weight { + if added := w.Attestors.Add(seat); added { + if newValue, valueUpdated := w.updateAttestorsWeight(); valueUpdated { + w.OnUpdate.Trigger(newValue) + } + } + + return w +} + +// DeleteAttestor removes the given voter from the list of Attestors, updates the weight and returns the Weight (for chaining). +func (w *Weight) DeleteAttestor(seat account.SeatIndex) *Weight { + if deleted := w.Attestors.Delete(seat); deleted { + if newValue, valueUpdated := w.updateAttestorsWeight(); valueUpdated { + w.OnUpdate.Trigger(newValue) + } + } + + return w +} + +// ResetAttestors removes all voters from the list of Attestors, updates the weight and returns the Weight (for chaining). +func (w *Weight) ResetAttestors() *Weight { + if w.Attestors.Size() >= 1 { + w.Attestors.Clear() + + if newValue, valueUpdated := w.updateAttestorsWeight(); valueUpdated { + w.OnUpdate.Trigger(newValue) + } + } + + return w +} + func (w *Weight) updateValidatorsWeight() (Value, bool) { w.mutex.Lock() defer w.mutex.Unlock() @@ -117,6 +156,20 @@ func (w *Weight) updateValidatorsWeight() (Value, bool) { return w.value, false } +func (w *Weight) updateAttestorsWeight() (Value, bool) { + w.mutex.Lock() + defer w.mutex.Unlock() + + newAttestorsWeight := int64(w.Attestors.Size()) + if w.value.AttestorsWeight() != newAttestorsWeight { + w.value = w.value.SetAttestorsWeight(newAttestorsWeight) + + return w.value, true + } + + return w.value, false +} + // AcceptanceState returns the acceptance state of the weight. func (w *Weight) AcceptanceState() acceptance.State { w.mutex.RLock() diff --git a/pkg/protocol/engine/mempool/spenddag/spenddagv1/spender.go b/pkg/protocol/engine/mempool/spenddag/spenddagv1/spender.go index fc432b021..cd8935e51 100644 --- a/pkg/protocol/engine/mempool/spenddag/spenddagv1/spender.go +++ b/pkg/protocol/engine/mempool/spenddag/spenddagv1/spender.go @@ -113,7 +113,7 @@ func NewSpender[SpenderID, ResourceID spenddag.IDType, VoteRank spenddag.VoteRan c.preferredInstead = c c.unhookAcceptanceMonitoring = c.Weight.OnUpdate.Hook(func(value weight.Value) { - if value.AcceptanceState().IsPending() && value.ValidatorsWeight() >= c.acceptanceThreshold() { + if value.AcceptanceState().IsPending() && value.ValidatorsWeight() >= c.acceptanceThreshold() && value.AttestorsWeight() >= c.acceptanceThreshold() { c.setAcceptanceState(acceptance.Accepted) } }).Unhook @@ -219,6 +219,17 @@ func (c *Spender[SpenderID, ResourceID, VoteRank]) ApplyVote(vote *vote.Vote[Vot // update the latest vote c.LatestVotes.Set(vote.Voter, vote) + // track attestors when we reach the acceptance threshold + if c.Weight.Value().ValidatorsWeight() > c.acceptanceThreshold() { + if vote.IsLiked() { + c.Weight.AddAttestor(vote.Voter) + } else { + c.Weight.DeleteAttestor(vote.Voter) + } + } else { + c.Weight.ResetAttestors() + } + // abort if the vote does not change the opinion of the validator if exists && latestVote.IsLiked() == vote.IsLiked() { return From 805168b89dd8c114dd8f70a1c7c8d5300652dab5 Mon Sep 17 00:00:00 2001 From: jkrvivian Date: Thu, 28 Mar 2024 15:05:37 +0800 Subject: [PATCH 05/13] Fix wrong slot index comparing in DelegationEndFromSlot --- pkg/testsuite/mock/wallet_transactions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/testsuite/mock/wallet_transactions.go b/pkg/testsuite/mock/wallet_transactions.go index 62d83cfcd..91e805ba2 100644 --- a/pkg/testsuite/mock/wallet_transactions.go +++ b/pkg/testsuite/mock/wallet_transactions.go @@ -160,7 +160,7 @@ func (w *Wallet) DelegationEndFromSlot(slot, latestCommitmentSlot iotago.SlotInd registrationSlot := w.registrationSlot(slot) - if futureBoundedEpochIndex <= iotago.EpochIndex(registrationSlot) { + if futureBoundedSlotIndex <= registrationSlot { return futureBoundedEpochIndex } From c6ff649a5a5526a1c889cbbdb1d12cafbb61b3a3 Mon Sep 17 00:00:00 2001 From: jkrvivian Date: Thu, 28 Mar 2024 15:09:30 +0800 Subject: [PATCH 06/13] Add Test_DelayedClaimingRewards --- tools/docker-network/tests/dockerframework.go | 12 +++ tools/docker-network/tests/rewards_test.go | 75 +++++++++++++++++++ tools/docker-network/tests/utils.go | 13 ++++ tools/docker-network/tests/wallet.go | 45 +++++++++++ 4 files changed, 145 insertions(+) diff --git a/tools/docker-network/tests/dockerframework.go b/tools/docker-network/tests/dockerframework.go index 874a1c04c..b5a80352b 100644 --- a/tools/docker-network/tests/dockerframework.go +++ b/tools/docker-network/tests/dockerframework.go @@ -612,6 +612,18 @@ func (d *DockerTestFramework) ClaimRewardsForDelegator(ctx context.Context, acco return iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0) } +func (d *DockerTestFramework) DelayedClaimingTransition(ctx context.Context, account *mock.AccountData, delegationOutputID iotago.OutputID) (iotago.OutputID, iotago.EpochIndex) { + clt := d.wallet.DefaultClient() + issuerResp, congestionResp := d.PrepareBlockIssuance(ctx, clt, account.Address) + signedTx := d.wallet.DelayedClaimingTransition(delegationOutputID, account.ID, issuerResp) + + d.SubmitPayload(ctx, signedTx, account.ID, congestionResp, issuerResp) + d.AwaitTransactionPayloadAccepted(ctx, signedTx.Transaction.MustID()) + + return iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), + signedTx.Transaction.Outputs[0].(*iotago.DelegationOutput).EndEpoch +} + // DelegateToValidator requests faucet funds and delegate the UTXO output to the validator. func (d *DockerTestFramework) DelegateToValidator(fromID iotago.AccountID, accountAddress *iotago.AccountAddress) (iotago.OutputID, *iotago.DelegationOutput) { from := d.wallet.Account(fromID) diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go index 01a5efa74..88fcbc5f4 100644 --- a/tools/docker-network/tests/rewards_test.go +++ b/tools/docker-network/tests/rewards_test.go @@ -143,3 +143,78 @@ func Test_DelegatorRewards(t *testing.T) { rewardsOutput := d.wallet.Output(rewardsOutputID) require.Equal(t, rewardsOutput.Output.StoredMana(), outputFromAPI.StoredMana()) } + +// Test_DelayedClaimingRewards tests the delayed claiming rewards for a delegator. +// 1. Create an account and delegate funds to a validator. +// 2. Delay claiming rewards for the delegation and check if the delegated stake is removed from the validator. +// 4. Claim rewards and check to destroy the delegation output. +func Test_DelayedClaimingRewards(t *testing.T) { + d := NewDockerTestFramework(t, + WithProtocolParametersOptions( + iotago.WithTimeProviderOptions(5, time.Now().Unix(), 10, 4), + iotago.WithLivenessOptions(10, 10, 2, 4, 8), + iotago.WithStakingOptions(3, 10, 10), + )) + defer d.Stop() + + d.AddValidatorNode("V1", "docker-network-inx-validator-1-1", "http://localhost:8050", "rms1pzg8cqhfxqhq7pt37y8cs4v5u4kcc48lquy2k73ehsdhf5ukhya3y5rx2w6") + d.AddValidatorNode("V2", "docker-network-inx-validator-2-1", "http://localhost:8060", "rms1pqm4xk8e9ny5w5rxjkvtp249tfhlwvcshyr3pc0665jvp7g3hc875k538hl") + d.AddValidatorNode("V3", "docker-network-inx-validator-3-1", "http://localhost:8070", "rms1pp4wuuz0y42caz48vv876qfpmffswsvg40zz8v79sy8cp0jfxm4kunflcgt") + d.AddValidatorNode("V4", "docker-network-inx-validator-4-1", "http://localhost:8040", "rms1pr8cxs3dzu9xh4cduff4dd4cxdthpjkpwmz2244f75m0urslrsvtsshrrjw") + d.AddNode("node5", "docker-network-node-5-1", "http://localhost:8090") + + err := d.Run() + require.NoError(t, err) + + d.WaitUntilNetworkReady() + + ctx := context.Background() + clt := d.wallet.DefaultClient() + + account := d.CreateAccount() + + { + // delegate funds to V2 + delegationOutputID, _ := d.DelegateToValidator(account.ID, d.Node("V2").AccountAddress(t)) + d.AwaitCommitment(delegationOutputID.CreationSlot()) + + // check if V2 received the delegator stake + v2Resp, err := clt.Validator(ctx, d.Node("V2").AccountAddress(t)) + require.NoError(t, err) + require.Greater(t, v2Resp.PoolStake, v2Resp.ValidatorStake) + + // delay claiming rewards + delegationOutputID1, delegationEndEpoch := d.DelayedClaimingTransition(ctx, account, delegationOutputID) + d.AwaitCommitment(delegationOutputID1.CreationSlot()) + + // the delegated stake should be removed from the validator, so the pool stake should equal to the validator stake + v2Resp, err = clt.Validator(ctx, d.Node("V2").AccountAddress(t)) + require.NoError(t, err) + require.Equal(t, v2Resp.PoolStake, v2Resp.ValidatorStake) + + // wait until next epoch to destroy the delegation + expectedSlot := clt.CommittedAPI().TimeProvider().EpochStart(delegationEndEpoch) + slotToWait := expectedSlot - clt.CommittedAPI().TimeProvider().CurrentSlot() + secToWait := time.Duration(slotToWait) * time.Duration(clt.CommittedAPI().ProtocolParameters().SlotDurationInSeconds()) * time.Second + fmt.Println("Wait for ", secToWait, "until expected slot: ", expectedSlot) + time.Sleep(secToWait) + d.ClaimRewardsForDelegator(ctx, account, delegationOutputID1) + } + + { + // delegate funds to V2 + delegationOutputID, _ := d.DelegateToValidator(account.ID, d.Node("V2").AccountAddress(t)) + + // delay claiming rewards in the same slot of delegation + delegationOutputID1, _ := d.DelayedClaimingTransition(ctx, account, delegationOutputID) + d.AwaitCommitment(delegationOutputID1.CreationSlot()) + + // the delegated stake should be 0, thus poolStake should be equal to validatorStake + v2Resp, err := clt.Validator(ctx, d.Node("V2").AccountAddress(t)) + require.NoError(t, err) + require.Equal(t, v2Resp.PoolStake, v2Resp.ValidatorStake) + + // wait until next epoch to destroy the delegation + d.ClaimRewardsForDelegator(ctx, account, delegationOutputID1) + } +} diff --git a/tools/docker-network/tests/utils.go b/tools/docker-network/tests/utils.go index 244ceeb50..6e3cca765 100644 --- a/tools/docker-network/tests/utils.go +++ b/tools/docker-network/tests/utils.go @@ -365,6 +365,19 @@ func getDelegationStartEpoch(api iotago.API, commitmentSlot iotago.SlotIndex) io return pastBoundedEpoch + 2 } +func getDelegationEndEpoch(api iotago.API, slot, latestCommitmentSlot iotago.SlotIndex) iotago.EpochIndex { + futureBoundedSlotIndex := latestCommitmentSlot + api.ProtocolParameters().MinCommittableAge() + futureBoundedEpochIndex := api.TimeProvider().EpochFromSlot(futureBoundedSlotIndex) + + registrationSlot := api.TimeProvider().EpochEnd(api.TimeProvider().EpochFromSlot(slot)) - api.ProtocolParameters().EpochNearingThreshold() + + if futureBoundedSlotIndex <= registrationSlot { + return futureBoundedEpochIndex + } + + return futureBoundedEpochIndex + 1 +} + func isStatusCode(err error, status int) bool { if err == nil { return false diff --git a/tools/docker-network/tests/wallet.go b/tools/docker-network/tests/wallet.go index b65a6e4d0..7e93341ec 100644 --- a/tools/docker-network/tests/wallet.go +++ b/tools/docker-network/tests/wallet.go @@ -600,6 +600,51 @@ func (w *DockerWallet) ClaimDelegatorRewards(delegationOutputID iotago.OutputID, return signedTx } +// DelayedClaimingTransition transitions DelegationOutput into delayed claiming state by setting DelegationID and EndEpoch. +func (w *DockerWallet) DelayedClaimingTransition(delegationOutputID iotago.OutputID, issuerAccountID iotago.AccountID, issuerResp *api.IssuanceBlockHeaderResponse) *iotago.SignedTransaction { + input := w.Output(delegationOutputID) + require.Equal(w.Testing, iotago.OutputDelegation, input.Output.Type()) + + acc := w.Account(issuerAccountID) + clt := w.DefaultClient() + currentSlot := clt.LatestAPI().TimeProvider().SlotFromTime(time.Now()) + apiForSlot := clt.APIForSlot(currentSlot) + + prevOutput, ok := input.Output.Clone().(*iotago.DelegationOutput) + require.True(w.Testing, ok) + + delegationBuilder := builder.NewDelegationOutputBuilderFromPrevious(prevOutput).EndEpoch(getDelegationEndEpoch(apiForSlot, currentSlot, issuerResp.LatestCommitment.Slot)) + if prevOutput.DelegationID == iotago.EmptyDelegationID() { + delegationBuilder.DelegationID(iotago.DelegationIDFromOutputID(delegationOutputID)) + } + output := delegationBuilder.MustBuild() + + signedTx, err := builder.NewTransactionBuilder(apiForSlot, w.AddressSigner(input.AddressIndex)). + AddInput(&builder.TxInput{ + UnlockTarget: input.Output.UnlockConditionSet().Address().Address, + InputID: delegationOutputID, + Input: input.Output, + }). + AddBlockIssuanceCreditInput(&iotago.BlockIssuanceCreditInput{ + AccountID: acc.ID, + }). + AddCommitmentInput(&iotago.CommitmentInput{CommitmentID: lo.Return1(issuerResp.LatestCommitment.ID())}). + AddOutput(output). + SetCreationSlot(currentSlot). + AllotAllMana(currentSlot, issuerAccountID, 0). + Build() + require.NoError(w.Testing, err) + + w.AddOutput(iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), &mock.OutputData{ + ID: iotago.OutputIDFromTransactionIDAndIndex(signedTx.Transaction.MustID(), 0), + Output: output, + Address: input.Output.UnlockConditionSet().Address().Address, + AddressIndex: input.AddressIndex, + }) + + return signedTx +} + // Computes the Potential Mana that the output generates until the current slot. func (w *DockerWallet) PotentialMana(api iotago.API, input iotago.Output, inputID iotago.OutputID, currentSlot iotago.SlotIndex) iotago.Mana { return lo.PanicOnErr(iotago.PotentialMana(api.ManaDecayProvider(), api.StorageScoreStructure(), input, inputID.CreationSlot(), currentSlot)) From d095c0b6f811896a36f905c985662f60199b583c Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:18:47 +0100 Subject: [PATCH 07/13] Fix unit tests. --- .../mempool/spenddag/spenddagv1/spender.go | 6 +-- .../engine/mempool/spenddag/tests/tests.go | 20 ++++++- pkg/protocol/engine/mempool/v1/mempool.go | 8 ++- pkg/tests/booker_test.go | 52 +++++++++++++------ pkg/tests/combined_account_transition_test.go | 32 +++++++----- pkg/tests/loss_of_acceptance_test.go | 13 ++--- pkg/tests/mempool_invalid_signatures_test.go | 2 +- 7 files changed, 93 insertions(+), 40 deletions(-) diff --git a/pkg/protocol/engine/mempool/spenddag/spenddagv1/spender.go b/pkg/protocol/engine/mempool/spenddag/spenddagv1/spender.go index cd8935e51..ea91565fa 100644 --- a/pkg/protocol/engine/mempool/spenddag/spenddagv1/spender.go +++ b/pkg/protocol/engine/mempool/spenddag/spenddagv1/spender.go @@ -113,13 +113,13 @@ func NewSpender[SpenderID, ResourceID spenddag.IDType, VoteRank spenddag.VoteRan c.preferredInstead = c c.unhookAcceptanceMonitoring = c.Weight.OnUpdate.Hook(func(value weight.Value) { - if value.AcceptanceState().IsPending() && value.ValidatorsWeight() >= c.acceptanceThreshold() && value.AttestorsWeight() >= c.acceptanceThreshold() { + if threshold := c.acceptanceThreshold(); value.AcceptanceState().IsPending() && value.ValidatorsWeight() >= threshold && value.AttestorsWeight() >= threshold { c.setAcceptanceState(acceptance.Accepted) } }).Unhook // in case the initial weight is enough to accept the spend, accept it immediately - if threshold := c.acceptanceThreshold(); initialWeight.Value().ValidatorsWeight() >= threshold { + if threshold := c.acceptanceThreshold(); initialWeight.AcceptanceState().IsPending() && initialWeight.Value().ValidatorsWeight() >= threshold && initialWeight.Value().AttestorsWeight() >= threshold { c.setAcceptanceState(acceptance.Accepted) } @@ -220,7 +220,7 @@ func (c *Spender[SpenderID, ResourceID, VoteRank]) ApplyVote(vote *vote.Vote[Vot c.LatestVotes.Set(vote.Voter, vote) // track attestors when we reach the acceptance threshold - if c.Weight.Value().ValidatorsWeight() > c.acceptanceThreshold() { + if c.Weight.Value().ValidatorsWeight() >= c.acceptanceThreshold() { if vote.IsLiked() { c.Weight.AddAttestor(vote.Voter) } else { diff --git a/pkg/protocol/engine/mempool/spenddag/tests/tests.go b/pkg/protocol/engine/mempool/spenddag/tests/tests.go index cfe6ce0b7..db1d56997 100644 --- a/pkg/protocol/engine/mempool/spenddag/tests/tests.go +++ b/pkg/protocol/engine/mempool/spenddag/tests/tests.go @@ -117,7 +117,9 @@ func CreateSpendWithoutMembers(t *testing.T, tf *Framework) { require.NoError(t, tf.CastVotes("nodeID1", 1, "spender1")) require.NoError(t, tf.CastVotes("nodeID2", 1, "spender1")) require.NoError(t, tf.CastVotes("nodeID3", 1, "spender1")) - + require.NoError(t, tf.CastVotes("nodeID1", 2, "spender1")) + require.NoError(t, tf.CastVotes("nodeID2", 2, "spender1")) + require.NoError(t, tf.CastVotes("nodeID3", 2, "spender1")) tf.Assert.LikedInstead([]string{"spender1"}) tf.Assert.Accepted("spender1") } @@ -188,6 +190,9 @@ func SpendAcceptance(t *testing.T, tf *Framework) { require.NoError(t, tf.CastVotes("nodeID2", 1, "spender4")) require.NoError(t, tf.CastVotes("nodeID3", 1, "spender4")) + require.NoError(t, tf.CastVotes("nodeID1", 2, "spender4")) + require.NoError(t, tf.CastVotes("nodeID2", 2, "spender4")) + require.NoError(t, tf.CastVotes("nodeID3", 2, "spender4")) tf.Assert.LikedInstead([]string{"spender1"}) tf.Assert.LikedInstead([]string{"spender2"}, "spender1") tf.Assert.LikedInstead([]string{"spender3"}, "spender4") @@ -224,6 +229,10 @@ func CastVotes(t *testing.T, tf *Framework) { require.NoError(t, tf.CastVotes("nodeID1", 1, "spender2")) require.NoError(t, tf.CastVotes("nodeID2", 1, "spender2")) require.NoError(t, tf.CastVotes("nodeID3", 1, "spender2")) + require.NoError(t, tf.CastVotes("nodeID4", 2, "spender2")) + require.NoError(t, tf.CastVotes("nodeID2", 2, "spender2")) + require.NoError(t, tf.CastVotes("nodeID3", 2, "spender2")) + tf.Assert.LikedInstead([]string{"spender1"}, "spender2") tf.Assert.Accepted("spender2") @@ -317,6 +326,9 @@ func CastVotesAcceptance(t *testing.T, tf *Framework) { require.NoError(t, tf.CastVotes("nodeID1", 1, "spender3")) require.NoError(t, tf.CastVotes("nodeID2", 1, "spender3")) require.NoError(t, tf.CastVotes("nodeID3", 1, "spender3")) + require.NoError(t, tf.CastVotes("nodeID4", 2, "spender3")) + require.NoError(t, tf.CastVotes("nodeID2", 2, "spender3")) + require.NoError(t, tf.CastVotes("nodeID3", 2, "spender3")) tf.Assert.LikedInstead([]string{"spender1"}) tf.Assert.Accepted("spender1") tf.Assert.Rejected("spender2") @@ -410,6 +422,9 @@ func EvictAcceptedSpender(t *testing.T, tf *Framework) { require.NoError(t, tf.CastVotes("nodeID1", 1, "spender2")) require.NoError(t, tf.CastVotes("nodeID2", 1, "spender2")) require.NoError(t, tf.CastVotes("nodeID3", 1, "spender2")) + require.NoError(t, tf.CastVotes("nodeID1", 2, "spender2")) + require.NoError(t, tf.CastVotes("nodeID2", 2, "spender2")) + require.NoError(t, tf.CastVotes("nodeID3", 2, "spender2")) tf.Assert.LikedInstead([]string{"spender1"}, "spender2") tf.Assert.Accepted("spender2") @@ -478,6 +493,9 @@ func EvictRejectedSpender(t *testing.T, tf *Framework) { require.NoError(t, tf.CastVotes("nodeID1", 1, "spender2")) require.NoError(t, tf.CastVotes("nodeID2", 1, "spender2")) require.NoError(t, tf.CastVotes("nodeID3", 1, "spender2")) + require.NoError(t, tf.CastVotes("nodeID1", 2, "spender2")) + require.NoError(t, tf.CastVotes("nodeID2", 2, "spender2")) + require.NoError(t, tf.CastVotes("nodeID3", 2, "spender2")) tf.Assert.LikedInstead([]string{"spender1"}, "spender2") tf.Assert.Rejected("spender1") diff --git a/pkg/protocol/engine/mempool/v1/mempool.go b/pkg/protocol/engine/mempool/v1/mempool.go index dc0a4458f..b7c23cb5e 100644 --- a/pkg/protocol/engine/mempool/v1/mempool.go +++ b/pkg/protocol/engine/mempool/v1/mempool.go @@ -2,6 +2,7 @@ package mempoolv1 import ( "context" + "fmt" "github.com/iotaledger/hive.go/core/memstorage" "github.com/iotaledger/hive.go/ds" @@ -121,7 +122,7 @@ func (m *MemPool[VoteRank]) AttachSignedTransaction(signedTransaction mempool.Si if isNewTransaction { m.transactionAttached.Trigger(storedSignedTransaction.transactionMetadata) - + fmt.Println("solidify inputs of", storedSignedTransaction.transactionMetadata.ID()) m.solidifyInputs(storedSignedTransaction.transactionMetadata) } } @@ -376,6 +377,7 @@ func (m *MemPool[VoteRank]) storeTransaction(signedTransaction mempool.SignedTra func (m *MemPool[VoteRank]) solidifyInputs(transaction *TransactionMetadata) { for index, inputReference := range transaction.inputReferences { + fmt.Println("solidify", inputReference.ReferencedStateID(), "<-", transaction.ID()) request, created := m.cachedStateRequests.GetOrCreate(inputReference.ReferencedStateID(), func() *promise.Promise[*StateMetadata] { return m.requestState(inputReference, true) }) @@ -388,7 +390,10 @@ func (m *MemPool[VoteRank]) solidifyInputs(transaction *TransactionMetadata) { } if transaction.markInputSolid() { + fmt.Println("inputs solid of", transaction.ID()) transaction.executionContext.OnUpdate(func(_ context.Context, executionContext context.Context) { + fmt.Println("executing", transaction.ID()) + m.executeTransaction(executionContext, transaction) }) } @@ -406,6 +411,7 @@ func (m *MemPool[VoteRank]) executeTransaction(executionContext context.Context, transaction.setInvalid(err) } else { transaction.setExecuted(outputStates) + fmt.Println("booking", transaction.ID()) m.bookTransaction(transaction) } diff --git a/pkg/tests/booker_test.go b/pkg/tests/booker_test.go index 333fb3e4c..57f396580 100644 --- a/pkg/tests/booker_test.go +++ b/pkg/tests/booker_test.go @@ -201,6 +201,8 @@ func Test_DoubleSpend(t *testing.T) { { ts.IssueValidationBlockWithHeaderOptions("block6", node2, mock.WithStrongParents(ts.BlockIDs("block3", "block4")...), mock.WithShallowLikeParents(ts.BlockID("block2"))) ts.IssueValidationBlockWithHeaderOptions("block7", node1, mock.WithStrongParents(ts.BlockIDs("block6")...)) + ts.IssueValidationBlockWithHeaderOptions("block8", node2, mock.WithStrongParents(ts.BlockIDs("block7")...)) + ts.IssueValidationBlockWithHeaderOptions("block9", node1, mock.WithStrongParents(ts.BlockIDs("block8")...)) ts.AssertBlocksInCacheConflicts(map[*blocks.Block][]string{ ts.Block("block6"): {"tx2"}, @@ -223,7 +225,7 @@ func Test_MultipleAttachments(t *testing.T) { blocksConflicts := make(map[*blocks.Block][]string) - // Create a transaction and issue it from both nodes, so that the conflict is accepted, but no attachment is included yet. + // Create a transaction and issue it from both nodes, so that the spend is accepted, but no attachment is included yet. { tx1 := wallet.CreateBasicOutputsEquallyFromInput("tx1", 2, "Genesis:0") @@ -234,17 +236,23 @@ func Test_MultipleAttachments(t *testing.T) { ts.IssueValidationBlockWithHeaderOptions("B.1.1", nodeB, mock.WithStrongParents(ts.BlockID("B.1"))) nodeA.Wait() - ts.IssueValidationBlockWithHeaderOptions("A.2", nodeA, mock.WithStrongParents(ts.BlockID("B.1.1"))) - ts.IssueValidationBlockWithHeaderOptions("B.2", nodeB, mock.WithStrongParents(ts.BlockID("A.1.1"))) + ts.IssueValidationBlockWithHeaderOptions("A.2.1", nodeA, mock.WithStrongParents(ts.BlockID("B.1.1"))) + ts.IssueValidationBlockWithHeaderOptions("B.2.1", nodeB, mock.WithStrongParents(ts.BlockID("A.1.1"))) + + // Cast more votes to accept the transaction. + ts.IssueValidationBlockWithHeaderOptions("A.2.2", nodeA, mock.WithStrongParents(ts.BlockID("A.1"))) + ts.IssueValidationBlockWithHeaderOptions("B.2.2", nodeB, mock.WithStrongParents(ts.BlockID("B.1"))) + ts.IssueValidationBlockWithHeaderOptions("A.3.2", nodeA, mock.WithStrongParents(ts.BlockID("B.2.2"))) + ts.IssueValidationBlockWithHeaderOptions("B.3.2", nodeB, mock.WithStrongParents(ts.BlockID("A.2.2"))) ts.AssertBlocksInCachePreAccepted(ts.Blocks("A.1", "B.1"), true, ts.Nodes()...) ts.AssertBlocksInCacheAccepted(ts.Blocks("A.1", "B.1"), false, ts.Nodes()...) ts.AssertBlocksInCacheConflicts(lo.MergeMaps(blocksConflicts, map[*blocks.Block][]string{ - ts.Block("A.1"): {"tx1"}, - ts.Block("B.1"): {"tx1"}, - ts.Block("A.2"): {"tx1"}, - ts.Block("B.2"): {"tx1"}, + ts.Block("A.1"): {"tx1"}, + ts.Block("B.1"): {"tx1"}, + ts.Block("A.2.1"): {"tx1"}, + ts.Block("B.2.1"): {"tx1"}, }), ts.Nodes()...) ts.AssertTransactionInCacheConflicts(map[*iotago.Transaction][]string{ wallet.Transaction("tx1"): {"tx1"}, @@ -262,13 +270,19 @@ func Test_MultipleAttachments(t *testing.T) { ts.IssueValidationBlockWithHeaderOptions("B.3", nodeB, mock.WithStrongParents(ts.BlockID("A.3.1"))) ts.IssueValidationBlockWithHeaderOptions("A.4", nodeA, mock.WithStrongParents(ts.BlockID("B.3"))) + // Issue attestor votes. + ts.IssueValidationBlockWithHeaderOptions("B.4", nodeB, mock.WithStrongParents(ts.BlockID("A.3"))) + ts.IssueValidationBlockWithHeaderOptions("A.5", nodeA, mock.WithStrongParents(ts.BlockID("B.4"))) + ts.AssertBlocksInCachePreAccepted(ts.Blocks("A.3"), true, ts.Nodes()...) - ts.IssueValidationBlockWithHeaderOptions("B.4", nodeB, mock.WithStrongParents(ts.BlockIDs("B.3", "A.4")...)) - ts.IssueValidationBlockWithHeaderOptions("A.5", nodeA, mock.WithStrongParents(ts.BlockIDs("B.3", "A.4")...)) + ts.IssueValidationBlockWithHeaderOptions("B.5", nodeB, mock.WithStrongParents(ts.BlockIDs("B.3", "A.4")...)) + ts.IssueValidationBlockWithHeaderOptions("A.6", nodeA, mock.WithStrongParents(ts.BlockIDs("B.3", "A.4")...)) + ts.IssueValidationBlockWithHeaderOptions("B.6", nodeB, mock.WithStrongParents(ts.BlockIDs("B.3", "A.4")...)) + ts.IssueValidationBlockWithHeaderOptions("A.7", nodeA, mock.WithStrongParents(ts.BlockIDs("B.3", "A.4")...)) - ts.AssertBlocksInCachePreAccepted(ts.Blocks("B.3", "A.4"), true, ts.Nodes()...) - ts.AssertBlocksInCachePreAccepted(ts.Blocks("B.4", "A.5"), false, ts.Nodes()...) + ts.AssertBlocksInCachePreAccepted(ts.Blocks("B.3", "A.4", "B.4"), true, ts.Nodes()...) + ts.AssertBlocksInCachePreAccepted(ts.Blocks("A.5", "B.5", "A.6", "B.6", "A.7"), false, ts.Nodes()...) ts.AssertBlocksInCacheAccepted(ts.Blocks("A.3"), true, ts.Nodes()...) ts.AssertTransactionsInCacheBooked(wallet.Transactions("tx1", "tx2"), true, ts.Nodes()...) @@ -278,8 +292,12 @@ func Test_MultipleAttachments(t *testing.T) { ts.Block("A.3"): {"tx2"}, ts.Block("B.3"): {"tx2"}, ts.Block("A.4"): {"tx2"}, - ts.Block("A.5"): {}, - ts.Block("B.4"): {}, + ts.Block("A.5"): {"tx2"}, + ts.Block("A.6"): {}, + ts.Block("B.4"): {"tx2"}, + ts.Block("B.5"): {"tx2"}, + ts.Block("A.7"): {}, + ts.Block("B.6"): {}, }), ts.Nodes()...) ts.AssertTransactionInCacheConflicts(map[*iotago.Transaction][]string{ wallet.Transaction("tx1"): {"tx1"}, @@ -290,13 +308,13 @@ func Test_MultipleAttachments(t *testing.T) { // Issue a block that includes tx1, and make sure that tx2 is accepted as well as a consequence. { - ts.IssueValidationBlockWithHeaderOptions("A.6", nodeA, mock.WithStrongParents(ts.BlockIDs("A.2", "B.2")...)) - ts.IssueValidationBlockWithHeaderOptions("B.5", nodeB, mock.WithStrongParents(ts.BlockIDs("A.2", "B.2")...)) + ts.IssueValidationBlockWithHeaderOptions("A.6", nodeA, mock.WithStrongParents(ts.BlockIDs("A.2.1", "B.2.1")...)) + ts.IssueValidationBlockWithHeaderOptions("B.5", nodeB, mock.WithStrongParents(ts.BlockIDs("A.2.1", "B.2.1")...)) ts.IssueValidationBlockWithHeaderOptions("A.7", nodeA, mock.WithStrongParents(ts.BlockIDs("A.6", "B.5")...)) ts.IssueValidationBlockWithHeaderOptions("B.6", nodeB, mock.WithStrongParents(ts.BlockIDs("A.6", "B.5")...)) - ts.AssertBlocksInCachePreAccepted(ts.Blocks("A.2", "B.2", "A.6", "B.5"), true, ts.Nodes()...) + ts.AssertBlocksInCachePreAccepted(ts.Blocks("A.2.1", "B.2.1", "A.6", "B.5"), true, ts.Nodes()...) ts.AssertBlocksInCacheAccepted(ts.Blocks("A.1", "B.1"), true, ts.Nodes()...) ts.AssertBlocksInCachePreAccepted(ts.Blocks("A.7", "B.6"), false, ts.Nodes()...) @@ -396,6 +414,8 @@ func Test_SpendRejectedCommittedRace(t *testing.T) { { ts.IssueValidationBlockWithHeaderOptions("block2.3", node2, mock.WithSlotCommitment(genesisCommitment), mock.WithStrongParents(ts.BlockIDs("block2.2")...)) ts.IssueValidationBlockWithHeaderOptions("block2.4", node1, mock.WithSlotCommitment(genesisCommitment), mock.WithStrongParents(ts.BlockIDs("block2.3")...)) + ts.IssueValidationBlockWithHeaderOptions("block2.5", node2, mock.WithSlotCommitment(genesisCommitment), mock.WithStrongParents(ts.BlockIDs("block2.4")...)) + ts.IssueValidationBlockWithHeaderOptions("block2.6", node1, mock.WithSlotCommitment(genesisCommitment), mock.WithStrongParents(ts.BlockIDs("block2.5")...)) ts.AssertBlocksInCacheConflicts(map[*blocks.Block][]string{ ts.Block("block2.3"): {"tx2"}, diff --git a/pkg/tests/combined_account_transition_test.go b/pkg/tests/combined_account_transition_test.go index 5717b6f37..b5d99192c 100644 --- a/pkg/tests/combined_account_transition_test.go +++ b/pkg/tests/combined_account_transition_test.go @@ -42,6 +42,8 @@ func Test_AccountStateTransition(t *testing.T) { ts.IssueValidationBlockWithHeaderOptions("vblock0", node2, mock.WithStrongParents(ts.BlockID("block0"))) ts.IssueValidationBlockWithHeaderOptions("vblock1", node1, mock.WithStrongParents(ts.BlockID("vblock0"))) ts.IssueValidationBlockWithHeaderOptions("vblock2", node2, mock.WithStrongParents(ts.BlockID("vblock1"))) + ts.IssueValidationBlockWithHeaderOptions("vblock3", node1, mock.WithStrongParents(ts.BlockID("vblock2"))) + ts.IssueValidationBlockWithHeaderOptions("vblock4", node2, mock.WithStrongParents(ts.BlockID("vblock3"))) ts.AssertTransactionsInCacheAccepted(wallet.Transactions("TX1"), true, node1, node2) } @@ -200,16 +202,18 @@ func sendFunds(ts *testsuite.TestSuite) { // send funds from defaultWallet to secondWallet tx := wallet.SendFundsToWallet("TX5", secondWallet, "TX1:2") - ts.IssueBasicBlockWithOptions("block4", wallet, tx) + ts.IssueBasicBlockWithOptions("block5", wallet, tx) ts.AssertTransactionsExist(wallet.Transactions("TX5"), true, node1) ts.AssertTransactionsInCacheBooked(wallet.Transactions("TX5"), true, node1) // Issue some more blocks to make transaction accepted { - ts.IssueValidationBlockWithHeaderOptions("vblock9", node2, mock.WithStrongParents(ts.BlockID("block4"))) - ts.IssueValidationBlockWithHeaderOptions("vblock10", node1, mock.WithStrongParents(ts.BlockID("vblock9"))) - ts.IssueValidationBlockWithHeaderOptions("vblock11", node2, mock.WithStrongParents(ts.BlockID("vblock10"))) + ts.IssueValidationBlockWithHeaderOptions("vblock12", node2, mock.WithStrongParents(ts.BlockID("block5"))) + ts.IssueValidationBlockWithHeaderOptions("vblock13", node1, mock.WithStrongParents(ts.BlockID("vblock12"))) + ts.IssueValidationBlockWithHeaderOptions("vblock14", node2, mock.WithStrongParents(ts.BlockID("vblock13"))) + ts.IssueValidationBlockWithHeaderOptions("vblock15", node1, mock.WithStrongParents(ts.BlockID("vblock14"))) + ts.IssueValidationBlockWithHeaderOptions("vblock16", node2, mock.WithStrongParents(ts.BlockID("vblock15"))) ts.AssertTransactionsInCacheAccepted(wallet.Transactions("TX5"), true, node1, node2) } @@ -226,13 +230,15 @@ func allotManaTo(ts *testsuite.TestSuite, to iotago.AccountID) { Mana: iotago.Mana(1000), }}, "TX1:3") commitment := node1.Protocol.Engines.Main.Get().Storage.Settings().LatestCommitment().Commitment() - ts.IssueBasicBlockWithOptions("block5", wallet, tx6, mock.WithSlotCommitment(commitment)) + ts.IssueBasicBlockWithOptions("block6", wallet, tx6, mock.WithSlotCommitment(commitment)) // Issue some more blocks to make transaction accepted { - ts.IssueValidationBlockWithHeaderOptions("vblock6", node2, mock.WithStrongParents(ts.BlockID("block5"))) - ts.IssueValidationBlockWithHeaderOptions("vblock7", node1, mock.WithStrongParents(ts.BlockID("vblock6"))) - ts.IssueValidationBlockWithHeaderOptions("vblock8", node2, mock.WithStrongParents(ts.BlockID("vblock7"))) + ts.IssueValidationBlockWithHeaderOptions("vblock7", node2, mock.WithStrongParents(ts.BlockID("block6"))) + ts.IssueValidationBlockWithHeaderOptions("vblock8", node1, mock.WithStrongParents(ts.BlockID("vblock7"))) + ts.IssueValidationBlockWithHeaderOptions("vblock9", node2, mock.WithStrongParents(ts.BlockID("vblock8"))) + ts.IssueValidationBlockWithHeaderOptions("vblock10", node1, mock.WithStrongParents(ts.BlockID("vblock9"))) + ts.IssueValidationBlockWithHeaderOptions("vblock11", node2, mock.WithStrongParents(ts.BlockID("vblock10"))) ts.AssertTransactionsInCacheAccepted(wallet.Transactions("TX6"), true, node1, node2) } @@ -245,16 +251,18 @@ func createNativetoken(ts *testsuite.TestSuite) { node2 := ts.Node("node2") tx := wallet.CreateNativeTokenFromInput("TX7", "TX5:0", "TX4:0", 5_000_000, 10_000_000_000) - ts.IssueBasicBlockWithOptions("block6", wallet, tx) + ts.IssueBasicBlockWithOptions("block17", wallet, tx) ts.AssertTransactionsExist(wallet.Transactions("TX7"), true, node1) ts.AssertTransactionsInCacheBooked(wallet.Transactions("TX7"), true, node1) // Issue some more blocks to make transaction accepted { - ts.IssueValidationBlockWithHeaderOptions("vblock12", node2, mock.WithStrongParents(ts.BlockID("block6"))) - ts.IssueValidationBlockWithHeaderOptions("vblock13", node1, mock.WithStrongParents(ts.BlockID("vblock12"))) - ts.IssueValidationBlockWithHeaderOptions("vblock14", node2, mock.WithStrongParents(ts.BlockID("vblock13"))) + ts.IssueValidationBlockWithHeaderOptions("vblock18", node2, mock.WithStrongParents(ts.BlockID("block17"))) + ts.IssueValidationBlockWithHeaderOptions("vblock19", node1, mock.WithStrongParents(ts.BlockID("vblock18"))) + ts.IssueValidationBlockWithHeaderOptions("vblock20", node2, mock.WithStrongParents(ts.BlockID("vblock19"))) + ts.IssueValidationBlockWithHeaderOptions("vblock21", node1, mock.WithStrongParents(ts.BlockID("vblock20"))) + ts.IssueValidationBlockWithHeaderOptions("vblock22", node2, mock.WithStrongParents(ts.BlockID("vblock21"))) ts.AssertTransactionsInCacheAccepted(wallet.Transactions("TX7"), true, node1, node2) } diff --git a/pkg/tests/loss_of_acceptance_test.go b/pkg/tests/loss_of_acceptance_test.go index d6515045b..fcd178860 100644 --- a/pkg/tests/loss_of_acceptance_test.go +++ b/pkg/tests/loss_of_acceptance_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/hive.go/log" "github.com/iotaledger/iota-core/pkg/protocol" "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" "github.com/iotaledger/iota-core/pkg/testsuite" @@ -314,10 +315,10 @@ func TestLossOfAcceptanceWithoutRestart(t *testing.T) { node2 := ts.AddNode("node2") ts.Run(true, nil) - + node2.Protocol.SetLogLevel(log.LevelTrace) // Issue up to slot 10, committing slot 8. { - ts.IssueBlocksAtSlots("", []iotago.SlotIndex{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 3, "Genesis", ts.Nodes(), true, true) + ts.IssueBlocksAtSlots("", []iotago.SlotIndex{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 4, "Genesis", ts.Nodes(), true, true) ts.AssertBlocksInCacheAccepted(ts.BlocksWithPrefix("10.0"), true, ts.Nodes()...) ts.AssertEqualStoredCommitmentAtIndex(8, ts.Nodes()...) @@ -340,7 +341,7 @@ func TestLossOfAcceptanceWithoutRestart(t *testing.T) { // Need to issue to slot 22 so that all other nodes can warp sync up to slot 19 and then commit slot 20 themselves. { - ts.IssueBlocksAtSlots("", []iotago.SlotIndex{21, 22}, 2, "block0", mock.Nodes(node0), true, false) + ts.IssueBlocksAtSlots("", []iotago.SlotIndex{21, 22}, 4, "block0", mock.Nodes(node0), true, false) ts.AssertEqualStoredCommitmentAtIndex(20, ts.Nodes()...) ts.AssertLatestCommitmentSlotIndex(20, ts.Nodes()...) @@ -350,9 +351,9 @@ func TestLossOfAcceptanceWithoutRestart(t *testing.T) { { // Since already issued, but not accepted blocks in slot 9 and 10 are be orphaned, we need to make sure that // the already issued transactions in the testsuite are not used again. - ts.SetAutomaticTransactionIssuingCounters(node2.Partition, 28) + ts.SetAutomaticTransactionIssuingCounters(node2.Partition, 37) - ts.IssueBlocksAtSlots("", []iotago.SlotIndex{23, 24, 25}, 3, "22.1", ts.Nodes(), true, false) + ts.IssueBlocksAtSlots("", []iotago.SlotIndex{23, 24, 25}, 4, "22.3", ts.Nodes(), true, false) ts.AssertBlocksInCacheAccepted(ts.BlocksWithPrefix("25.0"), true, ts.Nodes()...) ts.AssertEqualStoredCommitmentAtIndex(23, ts.Nodes()...) @@ -366,7 +367,7 @@ func TestLossOfAcceptanceWithoutRestart(t *testing.T) { ts.AssertStorageCommitmentTransactions(9, expectedTransactions(ts.BlocksWithPrefix("9")), ts.Nodes()...) ts.AssertStorageCommitmentBlocks(10, map[iotago.CommitmentID]iotago.BlockIDs{ - lo.PanicOnErr(node1.Protocol.Engines.Main.Get().Storage.Commitments().Load(7)).ID(): ts.BlockIDsWithPrefix("10.0"), // only the first blocks row in slot 10 was accepted + lo.PanicOnErr(node1.Protocol.Engines.Main.Get().Storage.Commitments().Load(7)).ID(): append(ts.BlockIDsWithPrefix("10.0"), ts.BlockIDsWithPrefix("10.1")...), // only the first blocks row in slot 10 was accepted }, ts.Nodes()...) ts.AssertStorageCommitmentTransactions(10, expectedTransactions(ts.BlocksWithPrefix("10.0")), ts.Nodes()...) diff --git a/pkg/tests/mempool_invalid_signatures_test.go b/pkg/tests/mempool_invalid_signatures_test.go index f01d5117c..e372150c5 100644 --- a/pkg/tests/mempool_invalid_signatures_test.go +++ b/pkg/tests/mempool_invalid_signatures_test.go @@ -62,7 +62,7 @@ func Test_MempoolInvalidSignatures(t *testing.T) { require.NoError(t, err) // Accept block2 and block3. - ts.IssueBlocksAtSlots("accept-block3", []iotago.SlotIndex{block3.ID().Slot()}, 2, "block3", ts.Nodes(), false, true) + ts.IssueBlocksAtSlots("accept-block3", []iotago.SlotIndex{block3.ID().Slot()}, 4, "block3", ts.Nodes(), false, true) // Ensure that the valid attachment exists and got accepted, // while the invalid attachment did not override the previous valid attachment. From 42289aae80de417df74c7a6c84b9e7106fe1d1fa Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Fri, 29 Mar 2024 09:32:20 +0100 Subject: [PATCH 08/13] Remove unnecessary prints --- pkg/protocol/engine/mempool/v1/mempool.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pkg/protocol/engine/mempool/v1/mempool.go b/pkg/protocol/engine/mempool/v1/mempool.go index b7c23cb5e..c87aee4c3 100644 --- a/pkg/protocol/engine/mempool/v1/mempool.go +++ b/pkg/protocol/engine/mempool/v1/mempool.go @@ -2,7 +2,6 @@ package mempoolv1 import ( "context" - "fmt" "github.com/iotaledger/hive.go/core/memstorage" "github.com/iotaledger/hive.go/ds" @@ -122,7 +121,6 @@ func (m *MemPool[VoteRank]) AttachSignedTransaction(signedTransaction mempool.Si if isNewTransaction { m.transactionAttached.Trigger(storedSignedTransaction.transactionMetadata) - fmt.Println("solidify inputs of", storedSignedTransaction.transactionMetadata.ID()) m.solidifyInputs(storedSignedTransaction.transactionMetadata) } } @@ -377,7 +375,6 @@ func (m *MemPool[VoteRank]) storeTransaction(signedTransaction mempool.SignedTra func (m *MemPool[VoteRank]) solidifyInputs(transaction *TransactionMetadata) { for index, inputReference := range transaction.inputReferences { - fmt.Println("solidify", inputReference.ReferencedStateID(), "<-", transaction.ID()) request, created := m.cachedStateRequests.GetOrCreate(inputReference.ReferencedStateID(), func() *promise.Promise[*StateMetadata] { return m.requestState(inputReference, true) }) @@ -390,10 +387,7 @@ func (m *MemPool[VoteRank]) solidifyInputs(transaction *TransactionMetadata) { } if transaction.markInputSolid() { - fmt.Println("inputs solid of", transaction.ID()) transaction.executionContext.OnUpdate(func(_ context.Context, executionContext context.Context) { - fmt.Println("executing", transaction.ID()) - m.executeTransaction(executionContext, transaction) }) } @@ -411,7 +405,6 @@ func (m *MemPool[VoteRank]) executeTransaction(executionContext context.Context, transaction.setInvalid(err) } else { transaction.setExecuted(outputStates) - fmt.Println("booking", transaction.ID()) m.bookTransaction(transaction) } From 4041311e38b654e8e634e8fac4e2a75a84c0d86d Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:47:28 +0100 Subject: [PATCH 09/13] Improve log levels --- pkg/protocol/chains.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/protocol/chains.go b/pkg/protocol/chains.go index d0cecca08..def67869d 100644 --- a/pkg/protocol/chains.go +++ b/pkg/protocol/chains.go @@ -213,15 +213,15 @@ func attachEngineLogs(instance *engine.Engine) func() { }).Unhook, events.SeatManager.OnlineCommitteeSeatAdded.Hook(func(seat account.SeatIndex, accountID iotago.AccountID) { - instance.LogTrace("SybilProtection.OnlineCommitteeSeatAdded", "seat", seat, "accountID", accountID) + instance.LogInfo("SybilProtection.OnlineCommitteeSeatAdded", "seat", seat, "accountID", accountID) }).Unhook, events.SeatManager.OnlineCommitteeSeatRemoved.Hook(func(seat account.SeatIndex) { - instance.LogTrace("SybilProtection.OnlineCommitteeSeatRemoved", "seat", seat) + instance.LogInfo("SybilProtection.OnlineCommitteeSeatRemoved", "seat", seat) }).Unhook, events.SybilProtection.CommitteeSelected.Hook(func(committee *account.SeatedAccounts, epoch iotago.EpochIndex) { - instance.LogTrace("SybilProtection.CommitteeSelected", "epoch", epoch, "committee", committee.IDs()) + instance.LogInfo("SybilProtection.CommitteeSelected", "epoch", epoch, "committee", committee.IDs()) }).Unhook, events.SpendDAG.SpenderCreated.Hook(func(conflictID iotago.TransactionID) { From 2074bed0cc7ba83b97d7a457bff4337dc60b6c91 Mon Sep 17 00:00:00 2001 From: jkrvivian Date: Fri, 29 Mar 2024 19:47:32 +0800 Subject: [PATCH 10/13] Resolve comments --- tools/docker-network/tests/rewards_test.go | 100 +++++++++++++++------ tools/docker-network/tests/wallet.go | 17 +--- 2 files changed, 77 insertions(+), 40 deletions(-) diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go index 88fcbc5f4..7b281070e 100644 --- a/tools/docker-network/tests/rewards_test.go +++ b/tools/docker-network/tests/rewards_test.go @@ -5,6 +5,7 @@ package tests import ( "context" "fmt" + "sync" "testing" "time" @@ -13,10 +14,10 @@ import ( ) // Test_ValidatorRewards tests the rewards for a validator. -// 1. Create an account with staking feature. -// 2. Issue candidacy payloads for the account and wait until the account is in the committee. -// 3. Issue validation blocks until claiming slot is reached. -// 4. Claim rewards and check if the mana increased as expected. +// 1. Create 2 accounts with staking feature. +// 2. Issue candidacy payloads for the accounts and wait until the accounts is in the committee. +// 3. One of the account issues 3 validation blocks per slot, the other account issues 1 validation block per slot until claiming slot is reached. +// 4. Claim rewards and check if the mana increased as expected, the account that issued less validation blocks should have less mana. func Test_ValidatorRewards(t *testing.T) { d := NewDockerTestFramework(t, WithProtocolParametersOptions( @@ -41,51 +42,59 @@ func Test_ValidatorRewards(t *testing.T) { clt := d.wallet.DefaultClient() status := d.NodeStatus("V1") currentEpoch := clt.CommittedAPI().TimeProvider().EpochFromSlot(status.LatestAcceptedBlockSlot) + slotsDuration := clt.CommittedAPI().ProtocolParameters().SlotDurationInSeconds() // Set end epoch so the staking feature can be removed as soon as possible. - endEpoch := currentEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + endEpoch := currentEpoch + clt.CommittedAPI().ProtocolParameters().StakingUnbondingPeriod() + 1 // The earliest epoch in which we can remove the staking feature and claim rewards. claimingSlot := clt.CommittedAPI().TimeProvider().EpochStart(endEpoch + 1) + // create accounts and continue issuing candidacy payload for account in the background account := d.CreateAccount(WithStakingFeature(100, 1, currentEpoch, endEpoch)) initialMana := account.Output.StoredMana() + issueCandidacyPayload(d, account.ID, clt.CommittedAPI().TimeProvider().CurrentSlot(), claimingSlot, + slotsDuration) - // continue issuing candidacy payload for account in the background - go func() { - fmt.Println("Issuing candidacy payloads for account in the background...") - defer fmt.Println("Issuing candidacy payloads for account in the background......done") - - currentSlot := clt.CommittedAPI().TimeProvider().CurrentSlot() - - for i := currentSlot; i < claimingSlot; i++ { - d.IssueCandidacyPayloadFromAccount(account.ID) - time.Sleep(10 * time.Second) - } - }() + lazyAccount := d.CreateAccount(WithStakingFeature(100, 1, currentEpoch, endEpoch)) + lazyInitialMana := lazyAccount.Output.StoredMana() + issueCandidacyPayload(d, lazyAccount.ID, clt.CommittedAPI().TimeProvider().CurrentSlot(), claimingSlot, + slotsDuration) // make sure the account is in the committee, so it can issue validation blocks accountAddrBech32 := account.Address.Bech32(clt.CommittedAPI().ProtocolParameters().Bech32HRP()) - d.AssertCommittee(currentEpoch+2, append(d.AccountsFromNodes(d.Nodes("V1", "V3", "V2", "V4")...), accountAddrBech32)) + lazyAccountAddrBech32 := lazyAccount.Address.Bech32(clt.CommittedAPI().ProtocolParameters().Bech32HRP()) + d.AssertCommittee(currentEpoch+2, append(d.AccountsFromNodes(d.Nodes("V1", "V3", "V2", "V4")...), accountAddrBech32, lazyAccountAddrBech32)) - // issua validation blocks to have performance + // issue validation blocks to have performance currentSlot := clt.CommittedAPI().TimeProvider().CurrentSlot() slotToWait := claimingSlot - currentSlot - secToWait := time.Duration(slotToWait) * time.Duration(clt.CommittedAPI().TimeProvider().SlotDurationSeconds()) * time.Second + secToWait := time.Duration(slotToWait) * time.Duration(slotsDuration) * time.Second fmt.Println("Wait for ", secToWait, "until expected slot: ", claimingSlot) - for i := currentSlot; i < claimingSlot; i++ { - d.SubmitValidationBlock(account.ID) - time.Sleep(10 * time.Second) - } + var wg sync.WaitGroup + issueValidationBlockInBackground(&wg, d, account.ID, currentSlot, claimingSlot, 3, slotsDuration) + issueValidationBlockInBackground(&wg, d, lazyAccount.ID, currentSlot, claimingSlot, 1, slotsDuration) + + wg.Wait() // claim rewards that put to the account output + d.AwaitCommitment(claimingSlot) account = d.ClaimRewardsForValidator(ctx, account) + lazyAccount = d.ClaimRewardsForValidator(ctx, lazyAccount) // check if the mana increased as expected outputFromAPI, err := clt.OutputByID(ctx, account.OutputID) require.NoError(t, err) require.Greater(t, outputFromAPI.StoredMana(), initialMana) require.Equal(t, account.Output.StoredMana(), outputFromAPI.StoredMana()) + + lazyOutputFromAPI, err := clt.OutputByID(ctx, lazyAccount.OutputID) + require.NoError(t, err) + require.Greater(t, lazyOutputFromAPI.StoredMana(), lazyInitialMana) + require.Equal(t, lazyAccount.Output.StoredMana(), lazyOutputFromAPI.StoredMana()) + + // account that issued more validation blocks should have more mana + require.Greater(t, account.Output.StoredMana(), lazyAccount.Output.StoredMana()) } // Test_DelegatorRewards tests the rewards for a delegator. @@ -147,7 +156,7 @@ func Test_DelegatorRewards(t *testing.T) { // Test_DelayedClaimingRewards tests the delayed claiming rewards for a delegator. // 1. Create an account and delegate funds to a validator. // 2. Delay claiming rewards for the delegation and check if the delegated stake is removed from the validator. -// 4. Claim rewards and check to destroy the delegation output. +// 3. Claim rewards and check to destroy the delegation output. func Test_DelayedClaimingRewards(t *testing.T) { d := NewDockerTestFramework(t, WithProtocolParametersOptions( @@ -218,3 +227,44 @@ func Test_DelayedClaimingRewards(t *testing.T) { d.ClaimRewardsForDelegator(ctx, account, delegationOutputID1) } } + +func issueCandidacyPayload(d *DockerTestFramework, accountID iotago.AccountID, startSlot, endSlot iotago.SlotIndex, slotDuration uint8) { + go func() { + fmt.Println("Issuing candidacy payloads for account", accountID, "in the background...") + defer fmt.Println("Issuing candidacy payloads for account", accountID, "in the background......done") + + for i := startSlot; i < endSlot; i++ { + d.IssueCandidacyPayloadFromAccount(accountID) + time.Sleep(time.Duration(slotDuration) * time.Second) + } + }() +} + +func issueValidationBlock(d *DockerTestFramework, accountID iotago.AccountID, startSlot, endSlot iotago.SlotIndex, blocksPerSlot int, slotDuration uint8) { + fmt.Println("Issuing validation block for account", accountID, "in the background...") + defer fmt.Println("Issuing validation block for account", accountID, "in the background......done") + + for i := startSlot; i < endSlot; i++ { + for range blocksPerSlot { + d.SubmitValidationBlock(accountID) + } + time.Sleep(time.Duration(slotDuration) * time.Second) + } +} + +func issueValidationBlockInBackground(wg *sync.WaitGroup, d *DockerTestFramework, accountID iotago.AccountID, startSlot, endSlot iotago.SlotIndex, blocksPerSlot int, slotDuration uint8) { + wg.Add(1) + + go func() { + defer wg.Done() + fmt.Println("Issuing validation block for account", accountID, "in the background...") + defer fmt.Println("Issuing validation block for account", accountID, "in the background......done") + + for i := startSlot; i < endSlot; i++ { + for range blocksPerSlot { + d.SubmitValidationBlock(accountID) + } + time.Sleep(time.Duration(slotDuration) * time.Second) + } + }() +} diff --git a/tools/docker-network/tests/wallet.go b/tools/docker-network/tests/wallet.go index 7e93341ec..ebb4c3006 100644 --- a/tools/docker-network/tests/wallet.go +++ b/tools/docker-network/tests/wallet.go @@ -520,12 +520,10 @@ func (w *DockerWallet) ClaimValidatorRewards(issuerAccountID iotago.AccountID, i rewardResp, err := clt.Rewards(context.Background(), acc.OutputID) require.NoError(w.Testing, err) - potentialMana := w.PotentialMana(apiForSlot, acc.Output, acc.OutputID, currentSlot) - storedMana := w.StoredMana(apiForSlot, acc.Output, acc.OutputID, currentSlot) accountOutput := builder.NewAccountOutputBuilderFromPrevious(acc.Output). RemoveFeature(iotago.FeatureStaking). - Mana(potentialMana + storedMana + rewardResp.Rewards). + Mana(rewardResp.Rewards). MustBuild() signedTx, err := builder.NewTransactionBuilder(apiForSlot, w.AddressSigner(acc.AddressIndex)). @@ -563,14 +561,13 @@ func (w *DockerWallet) ClaimDelegatorRewards(delegationOutputID iotago.OutputID, currentSlot := clt.LatestAPI().TimeProvider().SlotFromTime(time.Now()) apiForSlot := clt.APIForSlot(currentSlot) - potentialMana := w.PotentialMana(apiForSlot, delegationOutput.Output, delegationOutputID, currentSlot) rewardsResp, err := clt.Rewards(context.Background(), delegationOutputID) require.NoError(w.Testing, err) // Create Basic Output where the reward will be put. basicOutput := builder.NewBasicOutputBuilder(delegationOutput.Output.UnlockConditionSet().Address().Address, delegationOutput.Output.BaseTokenAmount()). - Mana(rewardsResp.Rewards + potentialMana). + Mana(rewardsResp.Rewards). MustBuild() signedTx, err := builder.NewTransactionBuilder(apiForSlot, w.AddressSigner(delegationOutput.AddressIndex)). @@ -644,13 +641,3 @@ func (w *DockerWallet) DelayedClaimingTransition(delegationOutputID iotago.Outpu return signedTx } - -// Computes the Potential Mana that the output generates until the current slot. -func (w *DockerWallet) PotentialMana(api iotago.API, input iotago.Output, inputID iotago.OutputID, currentSlot iotago.SlotIndex) iotago.Mana { - return lo.PanicOnErr(iotago.PotentialMana(api.ManaDecayProvider(), api.StorageScoreStructure(), input, inputID.CreationSlot(), currentSlot)) -} - -// Computes the decay on stored mana that the output holds until the current slot. -func (w *DockerWallet) StoredMana(api iotago.API, input iotago.Output, inputID iotago.OutputID, currentSlot iotago.SlotIndex) iotago.Mana { - return lo.PanicOnErr(api.ManaDecayProvider().DecayManaBySlots(input.StoredMana(), inputID.CreationSlot(), currentSlot)) -} From a70875bce60012679b7d45840f3635714a6716e1 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Fri, 29 Mar 2024 18:16:49 +0100 Subject: [PATCH 11/13] Update pkg/tests/loss_of_acceptance_test.go --- pkg/tests/loss_of_acceptance_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/tests/loss_of_acceptance_test.go b/pkg/tests/loss_of_acceptance_test.go index fcd178860..3394cdb32 100644 --- a/pkg/tests/loss_of_acceptance_test.go +++ b/pkg/tests/loss_of_acceptance_test.go @@ -315,7 +315,6 @@ func TestLossOfAcceptanceWithoutRestart(t *testing.T) { node2 := ts.AddNode("node2") ts.Run(true, nil) - node2.Protocol.SetLogLevel(log.LevelTrace) // Issue up to slot 10, committing slot 8. { ts.IssueBlocksAtSlots("", []iotago.SlotIndex{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 4, "Genesis", ts.Nodes(), true, true) From a1ae89b36acaf716e0a2ffe7aa3a14d3dd778ca2 Mon Sep 17 00:00:00 2001 From: muXxer Date: Sat, 30 Mar 2024 20:04:59 +0100 Subject: [PATCH 12/13] Fix imports --- pkg/tests/loss_of_acceptance_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/tests/loss_of_acceptance_test.go b/pkg/tests/loss_of_acceptance_test.go index 3394cdb32..aa91469f0 100644 --- a/pkg/tests/loss_of_acceptance_test.go +++ b/pkg/tests/loss_of_acceptance_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/require" "github.com/iotaledger/hive.go/lo" - "github.com/iotaledger/hive.go/log" "github.com/iotaledger/iota-core/pkg/protocol" "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" "github.com/iotaledger/iota-core/pkg/testsuite" From b96fe2645ab78ef7e64db06f09299211f8b52d27 Mon Sep 17 00:00:00 2001 From: muXxer Date: Sat, 30 Mar 2024 20:09:58 +0100 Subject: [PATCH 13/13] Cleanup --- tools/docker-network/tests/rewards_test.go | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/tools/docker-network/tests/rewards_test.go b/tools/docker-network/tests/rewards_test.go index 7b281070e..103c9293c 100644 --- a/tools/docker-network/tests/rewards_test.go +++ b/tools/docker-network/tests/rewards_test.go @@ -9,8 +9,9 @@ import ( "testing" "time" - iotago "github.com/iotaledger/iota.go/v4" "github.com/stretchr/testify/require" + + iotago "github.com/iotaledger/iota.go/v4" ) // Test_ValidatorRewards tests the rewards for a validator. @@ -52,12 +53,12 @@ func Test_ValidatorRewards(t *testing.T) { // create accounts and continue issuing candidacy payload for account in the background account := d.CreateAccount(WithStakingFeature(100, 1, currentEpoch, endEpoch)) initialMana := account.Output.StoredMana() - issueCandidacyPayload(d, account.ID, clt.CommittedAPI().TimeProvider().CurrentSlot(), claimingSlot, + issueCandidacyPayloadInBackground(d, account.ID, clt.CommittedAPI().TimeProvider().CurrentSlot(), claimingSlot, slotsDuration) lazyAccount := d.CreateAccount(WithStakingFeature(100, 1, currentEpoch, endEpoch)) lazyInitialMana := lazyAccount.Output.StoredMana() - issueCandidacyPayload(d, lazyAccount.ID, clt.CommittedAPI().TimeProvider().CurrentSlot(), claimingSlot, + issueCandidacyPayloadInBackground(d, lazyAccount.ID, clt.CommittedAPI().TimeProvider().CurrentSlot(), claimingSlot, slotsDuration) // make sure the account is in the committee, so it can issue validation blocks @@ -228,7 +229,7 @@ func Test_DelayedClaimingRewards(t *testing.T) { } } -func issueCandidacyPayload(d *DockerTestFramework, accountID iotago.AccountID, startSlot, endSlot iotago.SlotIndex, slotDuration uint8) { +func issueCandidacyPayloadInBackground(d *DockerTestFramework, accountID iotago.AccountID, startSlot, endSlot iotago.SlotIndex, slotDuration uint8) { go func() { fmt.Println("Issuing candidacy payloads for account", accountID, "in the background...") defer fmt.Println("Issuing candidacy payloads for account", accountID, "in the background......done") @@ -240,18 +241,6 @@ func issueCandidacyPayload(d *DockerTestFramework, accountID iotago.AccountID, s }() } -func issueValidationBlock(d *DockerTestFramework, accountID iotago.AccountID, startSlot, endSlot iotago.SlotIndex, blocksPerSlot int, slotDuration uint8) { - fmt.Println("Issuing validation block for account", accountID, "in the background...") - defer fmt.Println("Issuing validation block for account", accountID, "in the background......done") - - for i := startSlot; i < endSlot; i++ { - for range blocksPerSlot { - d.SubmitValidationBlock(accountID) - } - time.Sleep(time.Duration(slotDuration) * time.Second) - } -} - func issueValidationBlockInBackground(wg *sync.WaitGroup, d *DockerTestFramework, accountID iotago.AccountID, startSlot, endSlot iotago.SlotIndex, blocksPerSlot int, slotDuration uint8) { wg.Add(1)