From 6da80cdb54edb909cd5624e7315102756c588474 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:26:14 +0200 Subject: [PATCH 1/3] Fix mana calculation overflow. --- go.mod | 2 +- go.sum | 4 +- pkg/protocol/engine/accounts/mana.go | 19 -- pkg/protocol/engine/accounts/mana/manager.go | 180 +++++++++------ .../engine/accounts/mana/manager_test.go | 205 ++++++++++++++++++ pkg/testsuite/testsuite.go | 9 +- tools/evil-spammer/go.mod | 2 +- tools/evil-spammer/go.sum | 4 +- tools/gendoc/go.mod | 2 +- tools/gendoc/go.sum | 4 +- tools/genesis-snapshot/go.mod | 2 +- tools/genesis-snapshot/go.sum | 4 +- 12 files changed, 338 insertions(+), 99 deletions(-) create mode 100644 pkg/protocol/engine/accounts/mana/manager_test.go diff --git a/go.mod b/go.mod index 06abbdcd7..5f1183822 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/iotaledger/hive.go/stringify v0.0.0-20231010133617-cdbd5387e2af github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231011161248-cf0bd6e08811 github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231011154428-257141868dad - github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1 + github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961 github.com/labstack/echo/v4 v4.11.2 github.com/labstack/gommon v0.4.0 github.com/libp2p/go-libp2p v0.30.0 diff --git a/go.sum b/go.sum index 864310299..4a7e50124 100644 --- a/go.sum +++ b/go.sum @@ -309,8 +309,8 @@ github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231011161248-cf0bd6e08811 h1:nR8uT github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231011161248-cf0bd6e08811/go.mod h1:rmclNpXw5sKJDHU0e51Ar/9zL00P7Uu9hkfaM7vAAiE= github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231011154428-257141868dad h1:TRM9EkAole9fYY1vHEVQ6zCEOGuvCWq/bczZ98Al5Ec= github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231011154428-257141868dad/go.mod h1:plZ0+8yLdDWHedj3SfHUwQtIETD+lcS6M1iEAxcjzJ4= -github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1 h1:mz5E00q1U/LDiUi/wVAbwdhGNKX0dNThaO99Fsyjkgs= -github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1/go.mod h1:XmgOVYZ7805zVEYPwhvqBDVa7XieXRgPrCEGZW35W8k= +github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961 h1:8IDGv5emJrMC3FXFCy9xUx7W2DBoth0Jg5S+YvCtwrw= +github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961/go.mod h1:XmgOVYZ7805zVEYPwhvqBDVa7XieXRgPrCEGZW35W8k= github.com/ipfs/boxo v0.10.0 h1:tdDAxq8jrsbRkYoF+5Rcqyeb91hgWe2hp7iLu7ORZLY= github.com/ipfs/boxo v0.10.0/go.mod h1:Fg+BnfxZ0RPzR0nOodzdIq3A7KgoWAOWsEIImrIQdBM= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= diff --git a/pkg/protocol/engine/accounts/mana.go b/pkg/protocol/engine/accounts/mana.go index 207e19a24..61933570f 100644 --- a/pkg/protocol/engine/accounts/mana.go +++ b/pkg/protocol/engine/accounts/mana.go @@ -22,25 +22,6 @@ func NewMana(value iotago.Mana, excessBaseTokens iotago.BaseToken, updateTime io } } -// Update is applied when the account output is transitioned, updating the total value and excess base tokens. -func (m *Mana) Update(value iotago.Mana, excessBaseTokens iotago.BaseToken, updateTime iotago.SlotIndex) { - m.mutex.Lock() - defer m.mutex.Unlock() - - m.value = value - m.excessBaseTokens = excessBaseTokens - m.updateTime = updateTime -} - -// UpdateValue is applied when the total decayed value is updated but the account output is not changed. -func (m *Mana) UpdateValue(value iotago.Mana, updateTime iotago.SlotIndex) { - m.mutex.Lock() - defer m.mutex.Unlock() - - m.value = value - m.updateTime = updateTime -} - func (m *Mana) Value() iotago.Mana { m.mutex.RLock() defer m.mutex.RUnlock() diff --git a/pkg/protocol/engine/accounts/mana/manager.go b/pkg/protocol/engine/accounts/mana/manager.go index 726cc6996..ad7dbd089 100644 --- a/pkg/protocol/engine/accounts/mana/manager.go +++ b/pkg/protocol/engine/accounts/mana/manager.go @@ -41,75 +41,131 @@ func NewManager(apiProvider iotago.APIProvider, accountOutputResolveFunc func(io } } -func (m *Manager) GetManaOnAccount(accountID iotago.AccountID, currentSlot iotago.SlotIndex) (iotago.Mana, error) { +func (m *Manager) GetManaOnAccount(accountID iotago.AccountID, slot iotago.SlotIndex) (iotago.Mana, error) { m.mutex.Lock() defer m.mutex.Unlock() mana, exists := m.manaVectorCache.Get(accountID) - if !exists || mana.UpdateTime() > currentSlot { - output, err := m.accountOutputResolveFunc(accountID, currentSlot) + // if entry does not exist or required slot is earlier than the one stored in the cache, + // then need to calculate it from scratch + if !exists || mana.UpdateTime() > slot { + output, err := m.accountOutputResolveFunc(accountID, slot) if err != nil { - return 0, ierrors.Errorf("failed to resolve AccountOutput for %s in slot %s: %w", accountID, currentSlot, err) - } - minDeposit, err := m.apiProvider.CurrentAPI().RentStructure().MinDeposit(output.Output()) - if err != nil { - return 0, ierrors.Errorf("failed to get min deposit for %s: %w", accountID, err) - } - excessBaseTokens, err := safemath.SafeSub(output.BaseTokenAmount(), minDeposit) - if err != nil { - excessBaseTokens = 0 + return 0, ierrors.Wrapf(err, "failed to resolve AccountOutput for %s in slot %s", accountID, slot) } - decayedBIC, err := m.getDecayedBIC(accountID, currentSlot) + mana, err = m.getMana(accountID, output, slot) if err != nil { - return 0, ierrors.Wrapf(err, "failed to get decayed BIC for %s", accountID) + return 0, ierrors.Wrapf(err, "failed to calculate mana for %s in slot %s", accountID, slot) } - totalMana, err := safemath.SafeAdd(output.StoredMana(), decayedBIC) - if err != nil { - return 0, ierrors.Wrapf(err, "overflow when adding stored mana and decayed BIC for account %s", accountID) - } - - mana = accounts.NewMana(totalMana, excessBaseTokens, output.SlotCreated()) + // If it did not exist in cache, then add an entry to cache. + // If it did exist and the requested slot is earlier, + // then don't add it as it could lead to inaccurate results if any later slot is requested afterward. if !exists { m.manaVectorCache.Put(accountID, mana) } } - if currentSlot == mana.UpdateTime() { + manaDecayProvider := m.apiProvider.CurrentAPI().ManaDecayProvider() + + // If the requested slot is the same as the update time the cached mana, then return how it is. + // Otherwise, need to calculate decay and potential mana generation before returning. + if slot == mana.UpdateTime() { return mana.Value(), nil } - manaDecayProvider := m.apiProvider.CurrentAPI().ManaDecayProvider() - // apply decay to stored, allotted and potential that was added on last update - manaStored, err := manaDecayProvider.ManaWithDecay(mana.Value(), mana.UpdateTime(), currentSlot) + // Apply decay to stored, potential and BIC mana that was calculated when adding the entry to cache + // so that it's correct for the requested slot. + manaWithDecay, err := manaDecayProvider.ManaWithDecay(mana.Value(), mana.UpdateTime(), slot) if err != nil { return 0, err } - updatedValue := manaStored - // get newly generated potential since last update and apply decay - manaPotential, err := manaDecayProvider.ManaGenerationWithDecay(mana.ExcessBaseTokens(), mana.UpdateTime(), currentSlot) + // Calculate newly generated potential mana since last update time. + manaPotential, err := manaDecayProvider.ManaGenerationWithDecay(mana.ExcessBaseTokens(), mana.UpdateTime(), slot) if err != nil { return 0, err } - updatedValue, err = safemath.SafeAdd(updatedValue, manaPotential) + + // Add potential mana to the decayed mana. + manaWithDecayAndGeneration, err := safemath.SafeAdd(manaWithDecay, manaPotential) if err != nil { return 0, ierrors.Wrapf(err, "overflow when adding stored and potential mana for account %s", accountID) } - decayedBIC, err := m.getDecayedBIC(accountID, currentSlot) + return manaWithDecayAndGeneration, nil +} + +func (m *Manager) getMana(accountID iotago.AccountID, output *utxoledger.Output, slot iotago.SlotIndex) (*accounts.Mana, error) { + bic, bicUpdateTime, err := m.getBIC(accountID, slot) if err != nil { - return 0, ierrors.Wrapf(err, "failed to get decayed BIC for %s", accountID) + return nil, ierrors.Wrap(err, "failed to retrieve BIC") } - updatedValue, err = safemath.SafeAdd(updatedValue, decayedBIC) + + manaDecayProvider := m.apiProvider.CurrentAPI().ManaDecayProvider() + minDeposit := lo.PanicOnErr(m.apiProvider.CurrentAPI().RentStructure().MinDeposit(output.Output())) + excessBaseTokens, err := safemath.SafeSub(output.BaseTokenAmount(), minDeposit) if err != nil { - return 0, ierrors.Wrapf(err, "overflow when adding stored, potential and decayed BIC for account %s", accountID) + // if subtraction underflows, then excess base tokens is 0 + excessBaseTokens = 0 + } + + if output.SlotCreated() > slot || bicUpdateTime > slot { + return nil, ierrors.Errorf("BIC update time (%d) or output creation slot (%d) earlier than requested slot (%d)", bicUpdateTime, output.SlotCreated(), slot) } - mana.UpdateValue(updatedValue, currentSlot) + // Decay and generate stored mana to match either the BIC update time or Output creation slot(bigger of the two), + // so that subsequent decay calculations don't need to consider the fact that the two values should be decayed for different periods, + // but instead can be added and decayed together. + + var manaUpdateTime iotago.SlotIndex + var totalMana iotago.Mana + if bicUpdateTime > output.SlotCreated() { + manaPotential, err := manaDecayProvider.ManaGenerationWithDecay(excessBaseTokens, output.SlotCreated(), bicUpdateTime) + if err != nil { + return nil, ierrors.Wrapf(err, "failed to calculate mana generation with decay (excessBaseTokens: %d; outputSlotCreated: %d; targetSlot: %d)", excessBaseTokens, output.SlotCreated(), bicUpdateTime) + } + + manaStored, err := manaDecayProvider.ManaWithDecay(output.StoredMana(), output.SlotCreated(), bicUpdateTime) + if err != nil { + return nil, ierrors.Wrapf(err, "failed to calculate mana with decay (storedMana: %d; outputSlotCreated: %d; targetSlot: %d)", output.StoredMana(), output.SlotCreated(), bicUpdateTime) + } + + manaStoredPotential, err := safemath.SafeAdd(manaStored, manaPotential) + if err != nil { + return nil, ierrors.Wrapf(err, "overflow when adding decayed stored and potential mana (storedMana: %d; potentialMana: %d)", manaStored, manaPotential) + } + + totalMana, err = safemath.SafeAdd(bic, manaStoredPotential) + if err != nil { + return nil, ierrors.Wrapf(err, "overflow when adding decayed stored, potential mana and BIC (storedPotentialMana: %d; BIC: %d)", manaStoredPotential, bic) + } + + manaUpdateTime = bicUpdateTime + } else if output.SlotCreated() > bicUpdateTime { + // Decay BIC to match the Output creation time. + bicWithDecay, err := manaDecayProvider.ManaWithDecay(bic, bicUpdateTime, output.SlotCreated()) + if err != nil { + return nil, err + } + + totalMana, err = safemath.SafeAdd(bicWithDecay, output.StoredMana()) + if err != nil { + return nil, ierrors.Wrapf(err, "overflow when adding stored mana and decayed BIC (storedMana: %d; decayed BIC: %d)", output.StoredMana(), bicWithDecay) + } + + manaUpdateTime = output.SlotCreated() + } else { + totalMana, err = safemath.SafeAdd(bic, output.StoredMana()) + if err != nil { + return nil, ierrors.Wrapf(err, "overflow when adding stored mana and BIC (storedMana: %d; BIC: %d)", output.StoredMana(), bic) + } - return mana.Value(), nil + manaUpdateTime = output.SlotCreated() + } + + return accounts.NewMana(totalMana, excessBaseTokens, manaUpdateTime), nil } func (m *Manager) ApplyDiff(slot iotago.SlotIndex, destroyedAccounts ds.Set[iotago.AccountID], accountOutputs map[iotago.AccountID]*utxoledger.Output, accountDiffs map[iotago.AccountID]*model.AccountDiff) error { @@ -120,51 +176,49 @@ func (m *Manager) ApplyDiff(slot iotago.SlotIndex, destroyedAccounts ds.Set[iota m.manaVectorCache.Remove(accountID) }) - for accountID := range accountDiffs { - mana, exists := m.manaVectorCache.Get(accountID) - if exists { - var excessBaseTokens iotago.BaseToken - var storedMana iotago.Mana - var err error + for accountID, accountDiff := range accountDiffs { + if _, exists := m.manaVectorCache.Get(accountID); exists { + // If the account output was spent and a new one was created, + // or if BIC amount changed, then mana entry in the cache needs to be updated. + + var accountOutput *utxoledger.Output + // If the account output was updated, then we can use the output directly from the diff; + // otherwise it needs to be retrieved from the UTXO ledger. if output, has := accountOutputs[accountID]; has { - minDeposit := lo.PanicOnErr(m.apiProvider.CurrentAPI().RentStructure().MinDeposit(output.Output())) - excessBaseTokens, err = safemath.SafeSub(output.BaseTokenAmount(), minDeposit) - if err != nil { - excessBaseTokens = 0 + accountOutput = output + } else if accountDiff.BICChange != 0 { + var err error + if accountOutput, err = m.accountOutputResolveFunc(accountID, slot); err != nil { + return ierrors.Errorf("failed to resolve AccountOutput for %s in slot %s: %w", accountID, slot, err) } - storedMana = output.StoredMana() - } - decayedBIC, err := m.getDecayedBIC(accountID, slot) - if err != nil { - return err - } - totalMana, err := safemath.SafeAdd(decayedBIC, storedMana) - if err != nil { - return ierrors.Wrapf(err, "overflow when adding stored mana and decayed BIC for account %s", accountID) } - mana.Update(totalMana, excessBaseTokens, slot) + // If account output was not retrieved, that means that the applied diff affected neither BIC nor AccountOutput. + if accountOutput != nil { + mana, err := m.getMana(accountID, accountOutput, slot) + if err != nil { + return ierrors.Wrapf(err, "failed to calculate mana on an account %s", accountID) + } + + m.manaVectorCache.Put(accountID, mana) + } } } return nil } -func (m *Manager) getDecayedBIC(accountID iotago.AccountID, slot iotago.SlotIndex) (iotago.Mana, error) { +func (m *Manager) getBIC(accountID iotago.AccountID, slot iotago.SlotIndex) (bic iotago.Mana, updateTime iotago.SlotIndex, err error) { accountBIC, exists, err := m.accountRetrieveFunc(accountID, slot) if err != nil { - return 0, ierrors.Wrapf(err, "failed to retrieve account data for %s in slot %s", accountID, slot) + return 0, 0, ierrors.Wrapf(err, "failed to retrieve account data for %s in slot %s", accountID, slot) } if !exists { - return 0, ierrors.Errorf("account data for %s in slot %s does not exist", accountID, slot) + return 0, 0, ierrors.Errorf("account data for %s in slot %s does not exist", accountID, slot) } if accountBIC.Credits.Value <= 0 { - return 0, nil - } - decayedBIC, err := m.apiProvider.CurrentAPI().ManaDecayProvider().ManaWithDecay(iotago.Mana(accountBIC.Credits.Value), accountBIC.Credits.UpdateTime, slot) - if err != nil { - return 0, ierrors.Wrapf(err, "failed to apply mana decay for account %s", accountID) + return 0, 0, nil } - return decayedBIC, nil + return iotago.Mana(accountBIC.Credits.Value), accountBIC.Credits.UpdateTime, nil } diff --git a/pkg/protocol/engine/accounts/mana/manager_test.go b/pkg/protocol/engine/accounts/mana/manager_test.go new file mode 100644 index 000000000..f4d58dd62 --- /dev/null +++ b/pkg/protocol/engine/accounts/mana/manager_test.go @@ -0,0 +1,205 @@ +package mana + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/iotaledger/hive.go/core/safemath" + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/iota-core/pkg/model" + "github.com/iotaledger/iota-core/pkg/protocol/engine/accounts" + "github.com/iotaledger/iota-core/pkg/protocol/engine/utxoledger" + iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/api" + "github.com/iotaledger/iota.go/v4/tpkg" +) + +func TestManager_GetManaOnAccountOverflow(t *testing.T) { + accountIDOverflow := tpkg.RandAccountID() + accountIDValid := tpkg.RandAccountID() + accountIDRecentBIC := tpkg.RandAccountID() + accountIDRecentOutput := tpkg.RandAccountID() + + outputRetriever := func(id iotago.AccountID, _ iotago.SlotIndex) (*utxoledger.Output, error) { + switch id { + case accountIDOverflow: + return utxoledger.CreateOutput( + api.SingleVersionProvider(tpkg.TestAPI), + iotago.OutputIDFromTransactionIDAndIndex(iotago.NewTransactionID(0, tpkg.Rand32ByteArray()), 0), + tpkg.RandBlockID(), + tpkg.RandSlot(), + &iotago.AccountOutput{ + Amount: iotago.MaxBaseToken / 2, + Mana: iotago.MaxMana/2 + iotago.MaxMana/4, + AccountID: accountIDOverflow, + }, + lo.PanicOnErr(iotago.NewOutputIDProof(tpkg.TestAPI, tpkg.Rand32ByteArray(), tpkg.RandSlot(), iotago.TxEssenceOutputs{tpkg.RandBasicOutput(iotago.AddressEd25519)}, 0)), + ), nil + case accountIDRecentOutput: + return utxoledger.CreateOutput( + api.SingleVersionProvider(tpkg.TestAPI), + iotago.OutputIDFromTransactionIDAndIndex(iotago.NewTransactionID(1, tpkg.Rand32ByteArray()), 0), + tpkg.RandBlockID(), + tpkg.RandSlot(), + &iotago.AccountOutput{ + Amount: iotago.MaxBaseToken / 2, + Mana: iotago.MaxMana / 2, + AccountID: id, + }, + lo.PanicOnErr(iotago.NewOutputIDProof(tpkg.TestAPI, tpkg.Rand32ByteArray(), tpkg.RandSlot(), iotago.TxEssenceOutputs{tpkg.RandBasicOutput(iotago.AddressEd25519)}, 0)), + ), nil + default: + return utxoledger.CreateOutput( + api.SingleVersionProvider(tpkg.TestAPI), + iotago.OutputIDFromTransactionIDAndIndex(iotago.NewTransactionID(0, tpkg.Rand32ByteArray()), 0), + tpkg.RandBlockID(), + tpkg.RandSlot(), + &iotago.AccountOutput{ + Amount: iotago.MaxBaseToken / 2, + Mana: iotago.MaxMana / 2, + AccountID: id, + }, + lo.PanicOnErr(iotago.NewOutputIDProof(tpkg.TestAPI, tpkg.Rand32ByteArray(), tpkg.RandSlot(), iotago.TxEssenceOutputs{tpkg.RandBasicOutput(iotago.AddressEd25519)}, 0)), + ), nil + } + } + + accountRetriever := func(id iotago.AccountID, index iotago.SlotIndex) (*accounts.AccountData, bool, error) { + switch id { + case accountIDRecentBIC: + return &accounts.AccountData{ + ID: id, + Credits: &accounts.BlockIssuanceCredits{ + Value: iotago.MaxBlockIssuanceCredits/2 + iotago.MaxBlockIssuanceCredits/4, + UpdateTime: 1, + }, + ExpirySlot: iotago.MaxSlotIndex, + OutputID: iotago.OutputID{}, + BlockIssuerKeys: nil, + ValidatorStake: 0, + DelegationStake: 0, + FixedCost: 0, + StakeEndEpoch: 0, + LatestSupportedProtocolVersionAndHash: model.VersionAndHash{}, + }, true, nil + default: + return &accounts.AccountData{ + ID: id, + Credits: &accounts.BlockIssuanceCredits{ + Value: iotago.MaxBlockIssuanceCredits/2 + iotago.MaxBlockIssuanceCredits/4, + }, + ExpirySlot: iotago.MaxSlotIndex, + OutputID: iotago.OutputID{}, + BlockIssuerKeys: nil, + ValidatorStake: 0, + DelegationStake: 0, + FixedCost: 0, + StakeEndEpoch: 0, + LatestSupportedProtocolVersionAndHash: model.VersionAndHash{}, + }, true, nil + } + } + + manager := NewManager(api.SingleVersionProvider(tpkg.TestAPI), outputRetriever, accountRetriever) + manaDecayProvider := manager.apiProvider.LatestAPI().ManaDecayProvider() + + // The value for this account will overflow because component values are too big. + { + _, err := manager.GetManaOnAccount(accountIDOverflow, 0) + require.ErrorIs(t, err, safemath.ErrIntegerOverflow) + } + + // The value for this account should be correctly calculated and create an entry in cache. + { + mana, err := manager.GetManaOnAccount(accountIDValid, 0) + require.NoError(t, err) + + require.EqualValues(t, iotago.MaxMana/2+iotago.Mana(iotago.MaxBlockIssuanceCredits/2+iotago.MaxBlockIssuanceCredits/4), mana) + + cachedMana, exists := manager.manaVectorCache.Get(accountIDValid) + require.True(t, exists) + require.EqualValues(t, mana, cachedMana.Value()) + require.EqualValues(t, 0, cachedMana.UpdateTime()) + + // Make sure that the entry retrieved from the cache is the same as the previous one. + mana, err = manager.GetManaOnAccount(accountIDValid, 0) + require.NoError(t, err) + + require.EqualValues(t, iotago.MaxMana/2+iotago.Mana(iotago.MaxBlockIssuanceCredits/2+iotago.MaxBlockIssuanceCredits/4), mana) + } + + // Make sure that mana is decayed correctly. + { + mana, err := manager.GetManaOnAccount(accountIDValid, 1) + require.NoError(t, err) + + decayedMana, err := manaDecayProvider.ManaWithDecay(iotago.MaxMana/2+iotago.Mana(iotago.MaxBlockIssuanceCredits/2+iotago.MaxBlockIssuanceCredits/4), 0, 1) + require.NoError(t, err) + + generatedMana, err := manaDecayProvider.ManaGenerationWithDecay(iotago.MaxBaseToken/2, 0, 1) + require.NoError(t, err) + + require.EqualValues(t, decayedMana+generatedMana, mana) + + // Make sure that it's possible to ask for mana value at an earlier slot than the cached value. + mana, err = manager.GetManaOnAccount(accountIDValid, 0) + require.NoError(t, err) + + require.EqualValues(t, iotago.MaxMana/2+iotago.Mana(iotago.MaxBlockIssuanceCredits/2+iotago.MaxBlockIssuanceCredits/4), mana) + } + + // Make sure that decaying BIC to match the output creation slot works. + { + mana, err := manager.GetManaOnAccount(accountIDRecentOutput, 2) + require.NoError(t, err) + + decayedStoredMana, err := manaDecayProvider.ManaWithDecay(iotago.MaxMana/2, 1, 2) + require.NoError(t, err) + decayedBIC2Slots, err := manaDecayProvider.ManaWithDecay(iotago.Mana(iotago.MaxBlockIssuanceCredits/2+iotago.MaxBlockIssuanceCredits/4), 0, 2) + require.NoError(t, err) + + generatedMana, err := manaDecayProvider.ManaGenerationWithDecay(iotago.MaxBaseToken/2, 1, 2) + require.NoError(t, err) + + require.EqualValues(t, decayedBIC2Slots+decayedStoredMana+generatedMana, mana) + + decayedBIC1Slot, err := manaDecayProvider.ManaWithDecay(iotago.Mana(iotago.MaxBlockIssuanceCredits/2+iotago.MaxBlockIssuanceCredits/4), 0, 2) + require.NoError(t, err) + + // Make sure that cache entry is for slot 1. + cachedMana, exists := manager.manaVectorCache.Get(accountIDRecentOutput) + require.True(t, exists) + require.EqualValues(t, iotago.MaxMana/2+decayedBIC1Slot, cachedMana.Value()) + require.EqualValues(t, 1, cachedMana.UpdateTime()) + } + + // Make sure that decaying StoredMana to match the BIC update time works. + { + mana, err := manager.GetManaOnAccount(accountIDRecentBIC, 2) + require.NoError(t, err) + + decayedStoredMana2Slots, err := manaDecayProvider.ManaWithDecay(iotago.MaxMana/2, 0, 0) + require.NoError(t, err) + + generatedMana1Slot, err := manaDecayProvider.ManaGenerationWithDecay(iotago.MaxBaseToken/2, 0, 1) + require.NoError(t, err) + + decayedBIC, err := manaDecayProvider.ManaWithDecay(iotago.Mana(iotago.MaxBlockIssuanceCredits/2+iotago.MaxBlockIssuanceCredits/4), 1, 1) + require.NoError(t, err) + + // generatedMana1Slot is multiplied to calculate generation for two slots. It cannot be done in a single step + // because the value does not match due to approximation errors inherent to the underlying calculation method. + require.EqualValues(t, decayedStoredMana2Slots+generatedMana1Slot*2+decayedBIC, mana) + + decayedStoredMana1Slot, err := manaDecayProvider.ManaWithDecay(iotago.MaxMana/2, 0, 1) + require.NoError(t, err) + + // Make sure that cache entry is for slot 1. + cachedMana, exists := manager.manaVectorCache.Get(accountIDRecentBIC) + require.True(t, exists) + require.EqualValues(t, 1, cachedMana.UpdateTime()) + require.EqualValues(t, iotago.Mana(iotago.MaxBlockIssuanceCredits/2+iotago.MaxBlockIssuanceCredits/4)+decayedStoredMana1Slot+generatedMana1Slot, cachedMana.Value()) + } + +} diff --git a/pkg/testsuite/testsuite.go b/pkg/testsuite/testsuite.go index febd28625..68e334e3e 100644 --- a/pkg/testsuite/testsuite.go +++ b/pkg/testsuite/testsuite.go @@ -332,11 +332,10 @@ func (t *TestSuite) addNodeToPartition(name string, partition string, validator IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(node.Validator.PublicKey)), ExpirySlot: iotago.MaxSlotIndex, BlockIssuanceCredits: iotago.MaxBlockIssuanceCredits / 2, - } - if validator { - accountDetails.StakedAmount = accountDetails.Amount - accountDetails.StakingEpochEnd = iotago.MaxEpochIndex - accountDetails.FixedCost = iotago.Mana(0) + StakedAmount: amount, + StakingEpochEnd: iotago.MaxEpochIndex, + FixedCost: iotago.Mana(0), + AccountID: node.Validator.AccountID, } t.optsAccounts = append(t.optsAccounts, accountDetails) diff --git a/tools/evil-spammer/go.mod b/tools/evil-spammer/go.mod index 2381fe759..f9f54fddb 100644 --- a/tools/evil-spammer/go.mod +++ b/tools/evil-spammer/go.mod @@ -17,7 +17,7 @@ require ( github.com/iotaledger/hive.go/runtime v0.0.0-20231010133617-cdbd5387e2af github.com/iotaledger/iota-core v0.0.0-00010101000000-000000000000 github.com/iotaledger/iota-core/tools/genesis-snapshot v0.0.0-00010101000000-000000000000 - github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1 + github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961 github.com/mr-tron/base58 v1.2.0 go.uber.org/atomic v1.11.0 ) diff --git a/tools/evil-spammer/go.sum b/tools/evil-spammer/go.sum index c5109f6b5..618420550 100644 --- a/tools/evil-spammer/go.sum +++ b/tools/evil-spammer/go.sum @@ -197,8 +197,8 @@ github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231010133617-cdbd538 github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231010133617-cdbd5387e2af/go.mod h1:IJgaaxbgKCsNat18jlJJEAxCY2oVYR3F30B+M4vJ89I= github.com/iotaledger/hive.go/stringify v0.0.0-20231010133617-cdbd5387e2af h1:2/8In9gw03NW1hL4qyXuFYFoWZScHmyZtYUG0kHPmo4= github.com/iotaledger/hive.go/stringify v0.0.0-20231010133617-cdbd5387e2af/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs= -github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1 h1:mz5E00q1U/LDiUi/wVAbwdhGNKX0dNThaO99Fsyjkgs= -github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1/go.mod h1:XmgOVYZ7805zVEYPwhvqBDVa7XieXRgPrCEGZW35W8k= +github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961 h1:8IDGv5emJrMC3FXFCy9xUx7W2DBoth0Jg5S+YvCtwrw= +github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961/go.mod h1:XmgOVYZ7805zVEYPwhvqBDVa7XieXRgPrCEGZW35W8k= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= diff --git a/tools/gendoc/go.mod b/tools/gendoc/go.mod index ff3ae99ba..7e3194951 100644 --- a/tools/gendoc/go.mod +++ b/tools/gendoc/go.mod @@ -73,7 +73,7 @@ require ( github.com/iotaledger/hive.go/stringify v0.0.0-20231010133617-cdbd5387e2af // indirect github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231011161248-cf0bd6e08811 // indirect github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231011154428-257141868dad // indirect - github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1 // indirect + github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961 // indirect github.com/ipfs/boxo v0.10.0 // indirect github.com/ipfs/go-cid v0.4.1 // indirect github.com/ipfs/go-datastore v0.6.0 // indirect diff --git a/tools/gendoc/go.sum b/tools/gendoc/go.sum index 24fe10d0b..84830938d 100644 --- a/tools/gendoc/go.sum +++ b/tools/gendoc/go.sum @@ -313,8 +313,8 @@ github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231011161248-cf0bd6e08811 h1:nR8uT github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231011161248-cf0bd6e08811/go.mod h1:rmclNpXw5sKJDHU0e51Ar/9zL00P7Uu9hkfaM7vAAiE= github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231011154428-257141868dad h1:TRM9EkAole9fYY1vHEVQ6zCEOGuvCWq/bczZ98Al5Ec= github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231011154428-257141868dad/go.mod h1:plZ0+8yLdDWHedj3SfHUwQtIETD+lcS6M1iEAxcjzJ4= -github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1 h1:mz5E00q1U/LDiUi/wVAbwdhGNKX0dNThaO99Fsyjkgs= -github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1/go.mod h1:XmgOVYZ7805zVEYPwhvqBDVa7XieXRgPrCEGZW35W8k= +github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961 h1:8IDGv5emJrMC3FXFCy9xUx7W2DBoth0Jg5S+YvCtwrw= +github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961/go.mod h1:XmgOVYZ7805zVEYPwhvqBDVa7XieXRgPrCEGZW35W8k= github.com/ipfs/boxo v0.10.0 h1:tdDAxq8jrsbRkYoF+5Rcqyeb91hgWe2hp7iLu7ORZLY= github.com/ipfs/boxo v0.10.0/go.mod h1:Fg+BnfxZ0RPzR0nOodzdIq3A7KgoWAOWsEIImrIQdBM= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= diff --git a/tools/genesis-snapshot/go.mod b/tools/genesis-snapshot/go.mod index 121169d39..8ca1adf7a 100644 --- a/tools/genesis-snapshot/go.mod +++ b/tools/genesis-snapshot/go.mod @@ -10,7 +10,7 @@ require ( github.com/iotaledger/hive.go/lo v0.0.0-20231010133617-cdbd5387e2af github.com/iotaledger/hive.go/runtime v0.0.0-20231010133617-cdbd5387e2af github.com/iotaledger/iota-core v0.0.0-00010101000000-000000000000 - github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1 + github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961 github.com/mr-tron/base58 v1.2.0 github.com/spf13/pflag v1.0.5 golang.org/x/crypto v0.14.0 diff --git a/tools/genesis-snapshot/go.sum b/tools/genesis-snapshot/go.sum index 33ae5c4a9..f17b990a7 100644 --- a/tools/genesis-snapshot/go.sum +++ b/tools/genesis-snapshot/go.sum @@ -52,8 +52,8 @@ github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231010133617-cdbd538 github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231010133617-cdbd5387e2af/go.mod h1:IJgaaxbgKCsNat18jlJJEAxCY2oVYR3F30B+M4vJ89I= github.com/iotaledger/hive.go/stringify v0.0.0-20231010133617-cdbd5387e2af h1:2/8In9gw03NW1hL4qyXuFYFoWZScHmyZtYUG0kHPmo4= github.com/iotaledger/hive.go/stringify v0.0.0-20231010133617-cdbd5387e2af/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs= -github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1 h1:mz5E00q1U/LDiUi/wVAbwdhGNKX0dNThaO99Fsyjkgs= -github.com/iotaledger/iota.go/v4 v4.0.0-20231011161154-7004432004e1/go.mod h1:XmgOVYZ7805zVEYPwhvqBDVa7XieXRgPrCEGZW35W8k= +github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961 h1:8IDGv5emJrMC3FXFCy9xUx7W2DBoth0Jg5S+YvCtwrw= +github.com/iotaledger/iota.go/v4 v4.0.0-20231017091339-469a6ca13961/go.mod h1:XmgOVYZ7805zVEYPwhvqBDVa7XieXRgPrCEGZW35W8k= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= From 8dde5a651ea975fced7503720f408c5734a3921e Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Wed, 18 Oct 2023 11:27:40 +0200 Subject: [PATCH 2/3] Update pkg/protocol/engine/accounts/mana/manager.go Co-authored-by: Andrew Cullen <45826600+cyberphysic4l@users.noreply.github.com> --- pkg/protocol/engine/accounts/mana/manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/protocol/engine/accounts/mana/manager.go b/pkg/protocol/engine/accounts/mana/manager.go index ad7dbd089..fae62fa2d 100644 --- a/pkg/protocol/engine/accounts/mana/manager.go +++ b/pkg/protocol/engine/accounts/mana/manager.go @@ -112,7 +112,7 @@ func (m *Manager) getMana(accountID iotago.AccountID, output *utxoledger.Output, } if output.SlotCreated() > slot || bicUpdateTime > slot { - return nil, ierrors.Errorf("BIC update time (%d) or output creation slot (%d) earlier than requested slot (%d)", bicUpdateTime, output.SlotCreated(), slot) + return nil, ierrors.Errorf("BIC update time (%d) or output creation slot (%d) later than requested slot (%d)", bicUpdateTime, output.SlotCreated(), slot) } // Decay and generate stored mana to match either the BIC update time or Output creation slot(bigger of the two), From 5a2edd887733be2d92510395950ebe77659cca96 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Thu, 19 Oct 2023 13:37:23 +0200 Subject: [PATCH 3/3] Post merge fixes --- pkg/protocol/engine/accounts/mana/manager.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/protocol/engine/accounts/mana/manager.go b/pkg/protocol/engine/accounts/mana/manager.go index c79bd7e24..b53432329 100644 --- a/pkg/protocol/engine/accounts/mana/manager.go +++ b/pkg/protocol/engine/accounts/mana/manager.go @@ -100,13 +100,13 @@ func (m *Manager) GetManaOnAccount(accountID iotago.AccountID, slot iotago.SlotI } func (m *Manager) getMana(accountID iotago.AccountID, output *utxoledger.Output, slot iotago.SlotIndex) (*accounts.Mana, error) { - bic, bicUpdateTime, err := m.getBIC(accountID, slot,) + bic, bicUpdateTime, err := m.getBIC(accountID, slot) if err != nil { return nil, ierrors.Wrap(err, "failed to retrieve BIC") } - manaDecayProvider := m.apiProvider.CurrentAPI().ManaDecayProvider() - minDeposit := lo.PanicOnErr(m.apiProvider.CurrentAPI().RentStructure().MinDeposit(output.Output())) + manaDecayProvider := m.apiProvider.APIForSlot(slot).ManaDecayProvider() + minDeposit := lo.PanicOnErr(m.apiProvider.APIForSlot(slot).StorageScoreStructure().MinDeposit(output.Output())) excessBaseTokens, err := safemath.SafeSub(output.BaseTokenAmount(), minDeposit) if err != nil { // if subtraction underflows, then excess base tokens is 0