Skip to content
This repository has been archived by the owner on Jan 24, 2025. It is now read-only.

Commit

Permalink
Merge pull request #770 from iotaledger/fix/mana-manager
Browse files Browse the repository at this point in the history
Recreate mempool breaking with invalid signature
  • Loading branch information
cyberphysic4l authored Feb 28, 2024
2 parents bd68a54 + 270dbbd commit edc441c
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 27 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ require (
github.com/iotaledger/inx-app v1.0.0-rc.3.0.20240216141618-d7dfe94bdc1e
github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240216141023-6d5f4ef12ac5
github.com/iotaledger/iota-crypto-demo v0.0.0-20240216103559-27ca8dffd1e7
github.com/iotaledger/iota.go/v4 v4.0.0-20240216140514-c867d6524642
github.com/iotaledger/iota.go/v4 v4.0.0-20240220155644-70d23c10cabe
github.com/labstack/echo/v4 v4.11.4
github.com/labstack/gommon v0.4.2
github.com/libp2p/go-libp2p v0.32.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240216141023-6d5f4ef12ac5 h1:ebh2IK
github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240216141023-6d5f4ef12ac5/go.mod h1:Go1Gp6s+RCwNyaTjSw/TCk1Li5xd3+926aCu61kL+ks=
github.com/iotaledger/iota-crypto-demo v0.0.0-20240216103559-27ca8dffd1e7 h1:t6k4MqiUov0FrBb2o2JhKlOVSdlPbIQWM8ivYHL0G0g=
github.com/iotaledger/iota-crypto-demo v0.0.0-20240216103559-27ca8dffd1e7/go.mod h1:do+N3LpeDEi9qselEC4XcjqGoRc7cWGiqBtIeBOKEMs=
github.com/iotaledger/iota.go/v4 v4.0.0-20240216140514-c867d6524642 h1:s3nISWsyLwNA4+fh19yp1CQip3pHpIgsbYIRITpl8aA=
github.com/iotaledger/iota.go/v4 v4.0.0-20240216140514-c867d6524642/go.mod h1:IZNS0qmVdoRjIbFe4VTPG7k3bzSJQBvAHL2eC/2kFT0=
github.com/iotaledger/iota.go/v4 v4.0.0-20240220155644-70d23c10cabe h1:c6Sa60ISQHsEsKEF+fnAKjsH/pQQwz/l66r/Y2QWHtg=
github.com/iotaledger/iota.go/v4 v4.0.0-20240220155644-70d23c10cabe/go.mod h1:IZNS0qmVdoRjIbFe4VTPG7k3bzSJQBvAHL2eC/2kFT0=
github.com/ipfs/boxo v0.17.0 h1:fVXAb12dNbraCX1Cdid5BB6Kl62gVLNVA+e0EYMqAU0=
github.com/ipfs/boxo v0.17.0/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80=
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
Expand Down
13 changes: 13 additions & 0 deletions pkg/protocol/engine/accounts/accountsledger/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,8 +605,21 @@ func (m *Manager) updateSlotDiffWithBurns(slot iotago.SlotIndex, accountDiffs ma
}
for id, burn := range burns {
accountDiff, exists := accountDiffs[id]
// if diff doesn't already exist, that means the account output was not updated, so we need to preserve the previous state in the diff.
if !exists {
accountDiff = model.NewAccountDiff()
accountData, exists, err := m.account(id, slot-1)
if err != nil {
panic(ierrors.Errorf("error loading account %s in slot %d: %w", id, slot-1, err))
}
if !exists {
panic(ierrors.Errorf("trying to burn Mana from account %s which is not present in slot %d", id, slot-1))
}
accountDiff.PreviousUpdatedSlot = accountData.Credits.UpdateSlot
accountDiff.NewExpirySlot = accountData.ExpirySlot
accountDiff.PreviousExpirySlot = accountData.ExpirySlot
accountDiff.NewOutputID = accountData.OutputID
accountDiff.PreviousOutputID = accountData.OutputID
}

accountDiff.BICChange -= iotago.BlockIssuanceCredits(burn)
Expand Down
2 changes: 1 addition & 1 deletion pkg/protocol/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func New(
e.ReactiveModule = e.initReactiveModule(logger)

e.errorHandler = func(err error) {
e.LogTrace("engine error", "err", err)
e.LogError("engine error", "err", err)
}

// Import the settings from the snapshot file if needed.
Expand Down
1 change: 1 addition & 0 deletions pkg/protocol/engine/mempool/v1/mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ func (m *MemPool[VoteRank]) setupSignedTransaction(signedTransactionMetadata *Si
executionContext, err := m.vm.ValidateSignatures(signedTransactionMetadata.SignedTransaction(), lo.Map(signedTransactionMetadata.transactionMetadata.inputs, (*StateMetadata).State))
if err != nil {
_ = signedTransactionMetadata.signaturesInvalid.Set(err)

return
}

Expand Down
7 changes: 2 additions & 5 deletions pkg/protocol/engine/mempool/v1/transaction_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func (t *TransactionMetadata) OnConflicting(callback func()) {
}

func (t *TransactionMetadata) IsConflictAccepted() bool {
return !t.IsConflicting() || t.conflictAccepted.WasTriggered()
return t.conflictAccepted.WasTriggered()
}

func (t *TransactionMetadata) OnConflictAccepted(callback func()) {
Expand Down Expand Up @@ -301,10 +301,7 @@ func (t *TransactionMetadata) setup() (self *TransactionMetadata) {
})

t.OnEarliestIncludedAttachmentUpdated(func(previousBlockID iotago.BlockID, newBlockID iotago.BlockID) {
if previousBlockID.Empty() && !newBlockID.Empty() {
if t.invalid.Get() != nil || !t.IsConflictAccepted() || !t.AllInputsAccepted() {
return
}
if previousBlockID.Empty() && !newBlockID.Empty() && t.invalid.Get() == nil && t.IsConflictAccepted() && t.AllInputsAccepted() {
t.accepted.Set(true)
}
})
Expand Down
2 changes: 1 addition & 1 deletion pkg/tests/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ func Test_ImplicitAccounts(t *testing.T) {
block2Slot := ts.CurrentSlot()
tx2 := newUserWallet.TransitionImplicitAccountToAccountOutput(
"TX2",
"TX1:0",
[]string{"TX1:0"},
mock.WithBlockIssuerFeature(
iotago.BlockIssuerKeys{fullAccountBlockIssuerKey},
iotago.MaxSlotIndex,
Expand Down
5 changes: 2 additions & 3 deletions pkg/tests/combined_account_transition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func createImplicitToFullAccount(ts *testsuite.TestSuite) iotago.AccountID {

// CREATE IMPLICIT ACCOUNT FROM GENESIS BASIC UTXO, SENT TO A NEW USER WALLET.
// a default wallet, already registered in the ledger, will issue the transaction and block.
tx3 := ts.DefaultWallet().CreateImplicitAccountFromInput(
tx3 := ts.DefaultWallet().CreateImplicitAccountAndBasicOutputFromInput(
"TX3",
"TX1:1",
newUserWallet,
Expand All @@ -153,12 +153,11 @@ func createImplicitToFullAccount(ts *testsuite.TestSuite) iotago.AccountID {
block3Slot := ts.CurrentSlot()
tx4 := newUserWallet.TransitionImplicitAccountToAccountOutput(
"TX4",
"TX3:0",
[]string{"TX3:0"},
mock.WithBlockIssuerFeature(
iotago.BlockIssuerKeys{implicitBlockIssuerKey},
iotago.MaxSlotIndex,
),
mock.WithAccountAmount(mock.MinIssuerAccountAmount(ts.API.ProtocolParameters())),
)
block2Commitment := node1.Protocol.Engines.Main.Get().Storage.Settings().LatestCommitment().Commitment()
block3 := ts.IssueBasicBlockWithOptions("block3", newUserWallet, tx4, mock.WithStrongParents(latestParents...))
Expand Down
130 changes: 130 additions & 0 deletions pkg/tests/mempool_invalid_signatures_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package tests

import (
"testing"

"github.com/iotaledger/iota-core/pkg/model"
"github.com/iotaledger/iota-core/pkg/protocol/engine/accounts"
"github.com/iotaledger/iota-core/pkg/testsuite"
"github.com/iotaledger/iota-core/pkg/testsuite/mock"
iotago "github.com/iotaledger/iota.go/v4"
)

// Test_MempoolInvalidSignatures attempts to recreate the bug in https://github.com/iotaledger/iota-core/issues/765
func Test_MempoolInvalidSignatures(t *testing.T) {

ts := testsuite.NewTestSuite(t,
testsuite.WithProtocolParametersOptions(
iotago.WithTimeProviderOptions(
0,
testsuite.GenesisTimeWithOffsetBySlots(200, testsuite.DefaultSlotDurationInSeconds),
testsuite.DefaultSlotDurationInSeconds,
testsuite.DefaultSlotsPerEpochExponent,
),
),
)
defer ts.Shutdown()

node1 := ts.AddValidatorNode("node1")
node2 := ts.AddValidatorNode("node2")
wallet := ts.AddDefaultWallet(node1)

ts.Run(true)

// split genesis output into 4 outputs for the further usage.
tx1 := wallet.CreateBasicOutputsEquallyFromInput("TX1", 4, "Genesis:0")
ts.IssueBasicBlockWithOptions("block0", wallet, tx1)

// Issue some more blocks to make transaction accepted
{
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.AssertTransactionsInCacheAccepted(wallet.Transactions("TX1"), true, node1, node2)
}

// create the account2, from implicit to full account from TX1:1 with wallet "second"
// generated (block2, TX3), (block3, TX4)
ts.AddWallet("second", node1, iotago.EmptyAccountID)
transitionAccountWithInvalidSignature(ts)

}

func transitionAccountWithInvalidSignature(ts *testsuite.TestSuite) iotago.AccountID {
node1 := ts.Node("node1")
newUserWallet := ts.Wallet("second")

// CREATE IMPLICIT ACCOUNT FROM GENESIS BASIC UTXO, SENT TO A NEW USER WALLET.
// a default wallet, already registered in the ledger, will issue the transaction and block.
tx3 := ts.DefaultWallet().CreateImplicitAccountAndBasicOutputFromInput(
"TX3",
"TX1:0",
newUserWallet,
)
block2 := ts.IssueBasicBlockWithOptions("block2", ts.DefaultWallet(), tx3)
block2Slot := block2.ID().Slot()
latestParents := ts.CommitUntilSlot(block2Slot, block2.ID())

implicitAccountOutput := newUserWallet.Output("TX3:0")
implicitAccountOutputID := implicitAccountOutput.OutputID()
implicitAccountID := iotago.AccountIDFromOutputID(implicitAccountOutputID)
var implicitBlockIssuerKey iotago.BlockIssuerKey = iotago.Ed25519PublicKeyHashBlockIssuerKeyFromImplicitAccountCreationAddress(newUserWallet.ImplicitAccountCreationAddress())

// the new implicit account should now be registered in the accounts ledger.
ts.AssertAccountData(&accounts.AccountData{
ID: implicitAccountID,
Credits: accounts.NewBlockIssuanceCredits(0, block2Slot),
ExpirySlot: iotago.MaxSlotIndex,
OutputID: implicitAccountOutputID,
BlockIssuerKeys: iotago.NewBlockIssuerKeys(implicitBlockIssuerKey),
}, ts.Nodes()...)

// TRANSITION IMPLICIT ACCOUNT TO ACCOUNT OUTPUT.
block3Slot := ts.CurrentSlot()
tx4 := newUserWallet.TransitionImplicitAccountToAccountOutput(
"TX4",
[]string{"TX3:0", "TX3:1"},
mock.WithBlockIssuerFeature(
iotago.BlockIssuerKeys{implicitBlockIssuerKey},
iotago.MaxSlotIndex,
),
)
// replace the first unlock with an empty signature unlock
_, is := tx4.Unlocks[0].(*iotago.SignatureUnlock)
if !is {
panic("expected signature unlock as first unlock")
}
tx4.Unlocks[0] = &iotago.SignatureUnlock{
Signature: &iotago.Ed25519Signature{},
}

block2Commitment := node1.Protocol.Engines.Main.Get().Storage.Settings().LatestCommitment().Commitment()
block3 := ts.IssueBasicBlockWithOptions("block3", newUserWallet, tx4, mock.WithStrongParents(latestParents...))
latestParents = ts.CommitUntilSlot(block3Slot, block3.ID())

burned := iotago.BlockIssuanceCredits(block3.WorkScore()) * iotago.BlockIssuanceCredits(block2Commitment.ReferenceManaCost)
// the implicit account transition should fail, so the burned amount should be deducted from BIC, but no allotment made.
ts.AssertAccountDiff(implicitAccountID, block3Slot, &model.AccountDiff{
BICChange: -burned,
PreviousUpdatedSlot: block2Slot,
NewOutputID: implicitAccountOutputID,
PreviousOutputID: implicitAccountOutputID,
PreviousExpirySlot: iotago.MaxSlotIndex,
NewExpirySlot: iotago.MaxSlotIndex,
ValidatorStakeChange: 0,
StakeEndEpochChange: 0,
FixedCostChange: 0,
DelegationStakeChange: 0,
}, false, ts.Nodes()...)

ts.AssertAccountData(&accounts.AccountData{
ID: implicitAccountID,
Credits: accounts.NewBlockIssuanceCredits(-burned, block3Slot),
ExpirySlot: iotago.MaxSlotIndex,
OutputID: implicitAccountOutputID,
BlockIssuerKeys: iotago.NewBlockIssuerKeys(implicitBlockIssuerKey),
}, ts.Nodes()...)

return implicitAccountID
}
2 changes: 1 addition & 1 deletion pkg/testsuite/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (t *TestSuite) AssertAccountDiff(accountID iotago.AccountID, index iotago.S
}

if accountDiff.BICChange != actualAccountDiff.BICChange {
return ierrors.Errorf("AssertAccountDiff: %s: expected change %d but actual %d for account %s at slot %d", node.Name, accountDiff.BICChange, actualAccountDiff.BICChange, accountID, index)
return ierrors.Errorf("AssertAccountDiff: %s: BIC expected change %d but actual %d for account %s at slot %d", node.Name, accountDiff.BICChange, actualAccountDiff.BICChange, accountID, index)
}

if accountDiff.PreviousUpdatedSlot != actualAccountDiff.PreviousUpdatedSlot {
Expand Down
71 changes: 61 additions & 10 deletions pkg/testsuite/mock/wallet_transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,20 +347,71 @@ func (w *Wallet) CreateImplicitAccountFromInput(transactionName string, inputNam
return signedTransaction
}

func (w *Wallet) TransitionImplicitAccountToAccountOutput(transactionName string, inputName string, opts ...options.Option[builder.AccountOutputBuilder]) *iotago.SignedTransaction {
// CreateImplicitAccountAndBasicOutputFromInput creates an implicit account output and a remainder basic output from a basic output.
func (w *Wallet) CreateImplicitAccountAndBasicOutputFromInput(transactionName string, inputName string, recipientWallet *Wallet) *iotago.SignedTransaction {
input := w.Output(inputName)
implicitAccountID := iotago.AccountIDFromOutputID(input.OutputID())

basicOutput, isBasic := input.Output().(*iotago.BasicOutput)
if !isBasic {
panic(fmt.Sprintf("output with alias %s is not *iotago.BasicOutput", inputName))
implicitAccountOutput := &iotago.BasicOutput{
Amount: MinIssuerAccountAmount(w.Node.Protocol.CommittedAPI().ProtocolParameters()),
Mana: AccountConversionManaCost(w.Node.Protocol.CommittedAPI().ProtocolParameters()),
UnlockConditions: iotago.BasicOutputUnlockConditions{
&iotago.AddressUnlockCondition{Address: recipientWallet.ImplicitAccountCreationAddress()},
},
Features: iotago.BasicOutputFeatures{},
}
if basicOutput.UnlockConditionSet().Address().Address.Type() != iotago.AddressImplicitAccountCreation {
panic(fmt.Sprintf("output with alias %s is not an implicit account", inputName))

remainderBasicOutput := &iotago.BasicOutput{
Amount: input.BaseTokenAmount() - implicitAccountOutput.Amount,
Mana: input.StoredMana() - implicitAccountOutput.Mana,
UnlockConditions: iotago.BasicOutputUnlockConditions{
&iotago.AddressUnlockCondition{Address: recipientWallet.Address()},
},
Features: iotago.BasicOutputFeatures{},
}

accountOutput := options.Apply(builder.NewAccountOutputBuilder(w.Address(), MinIssuerAccountAmount(w.Node.Protocol.CommittedAPI().ProtocolParameters())).
AccountID(iotago.AccountIDFromOutputID(input.OutputID())),
signedTransaction := w.createSignedTransactionWithOptions(
transactionName,
[]uint32{0},
WithInputs(input),
WithOutputs(implicitAccountOutput, remainderBasicOutput),
)

// register the outputs in the recipient wallet (so wallet doesn't have to scan for outputs on its addresses)
recipientWallet.registerOutputs(transactionName, signedTransaction.Transaction)

// register the implicit account as a block issuer in the wallet
implicitAccountID := iotago.AccountIDFromOutputID(recipientWallet.Output(fmt.Sprintf("%s:0", transactionName)).OutputID())
recipientWallet.SetBlockIssuer(implicitAccountID)

return signedTransaction
}

func (w *Wallet) TransitionImplicitAccountToAccountOutput(transactionName string, inputNames []string, opts ...options.Option[builder.AccountOutputBuilder]) *iotago.SignedTransaction {
var implicitAccountOutput *utxoledger.Output
var baseTokenAmount iotago.BaseToken
inputs := make(utxoledger.Outputs, 0, len(inputNames))
for _, inputName := range inputNames {
input := w.Output(inputName)
basicOutput, isBasic := input.Output().(*iotago.BasicOutput)
if !isBasic {
panic(fmt.Sprintf("output with alias %s is not *iotago.BasicOutput", inputName))
}
if basicOutput.UnlockConditionSet().Address().Address.Type() == iotago.AddressImplicitAccountCreation {
if implicitAccountOutput != nil {
panic("multiple implicit account outputs found")
}
implicitAccountOutput = input
}
inputs = append(inputs, input)
baseTokenAmount += input.BaseTokenAmount()
}
if implicitAccountOutput == nil {
panic("no implicit account output found")
}
implicitAccountID := iotago.AccountIDFromOutputID(implicitAccountOutput.OutputID())

accountOutput := options.Apply(builder.NewAccountOutputBuilder(w.Address(), baseTokenAmount).
AccountID(implicitAccountID),
opts).MustBuild()

signedTransaction := w.createSignedTransactionWithOptions(
Expand All @@ -372,7 +423,7 @@ func (w *Wallet) TransitionImplicitAccountToAccountOutput(transactionName string
WithCommitmentInput(&iotago.CommitmentInput{
CommitmentID: w.Node.Protocol.Engines.Main.Get().SyncManager.LatestCommitment().Commitment().MustID(),
}),
WithInputs(input),
WithInputs(inputs...),
WithOutputs(accountOutput),
WithAllotAllManaToAccount(w.currentSlot, implicitAccountID),
)
Expand Down
2 changes: 1 addition & 1 deletion tools/gendoc/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ require (
github.com/iotaledger/inx-app v1.0.0-rc.3.0.20240216141618-d7dfe94bdc1e // indirect
github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240216141023-6d5f4ef12ac5 // indirect
github.com/iotaledger/iota-crypto-demo v0.0.0-20240216103559-27ca8dffd1e7 // indirect
github.com/iotaledger/iota.go/v4 v4.0.0-20240216140514-c867d6524642 // indirect
github.com/iotaledger/iota.go/v4 v4.0.0-20240223110058-f304abf3efa0 // indirect
github.com/ipfs/boxo v0.17.0 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/ipfs/go-datastore v0.6.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions tools/gendoc/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,8 @@ github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240216141023-6d5f4ef12ac5 h1:ebh2IK
github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240216141023-6d5f4ef12ac5/go.mod h1:Go1Gp6s+RCwNyaTjSw/TCk1Li5xd3+926aCu61kL+ks=
github.com/iotaledger/iota-crypto-demo v0.0.0-20240216103559-27ca8dffd1e7 h1:t6k4MqiUov0FrBb2o2JhKlOVSdlPbIQWM8ivYHL0G0g=
github.com/iotaledger/iota-crypto-demo v0.0.0-20240216103559-27ca8dffd1e7/go.mod h1:do+N3LpeDEi9qselEC4XcjqGoRc7cWGiqBtIeBOKEMs=
github.com/iotaledger/iota.go/v4 v4.0.0-20240216140514-c867d6524642 h1:s3nISWsyLwNA4+fh19yp1CQip3pHpIgsbYIRITpl8aA=
github.com/iotaledger/iota.go/v4 v4.0.0-20240216140514-c867d6524642/go.mod h1:IZNS0qmVdoRjIbFe4VTPG7k3bzSJQBvAHL2eC/2kFT0=
github.com/iotaledger/iota.go/v4 v4.0.0-20240223110058-f304abf3efa0 h1:gQjVmVSa8ezyzsE90oIz7HESKtLpGN8mSVkNX4mRysQ=
github.com/iotaledger/iota.go/v4 v4.0.0-20240223110058-f304abf3efa0/go.mod h1:IZNS0qmVdoRjIbFe4VTPG7k3bzSJQBvAHL2eC/2kFT0=
github.com/ipfs/boxo v0.17.0 h1:fVXAb12dNbraCX1Cdid5BB6Kl62gVLNVA+e0EYMqAU0=
github.com/ipfs/boxo v0.17.0/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80=
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
Expand Down

0 comments on commit edc441c

Please sign in to comment.