Skip to content

Commit

Permalink
Implement test that locks and unlocks an account due to negative BIC.
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrm50 committed Nov 7, 2023
1 parent c641e82 commit fb5f2c0
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 14 deletions.
224 changes: 221 additions & 3 deletions pkg/tests/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ package tests
import (
"testing"

"github.com/stretchr/testify/require"

"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/blocks"
"github.com/iotaledger/iota-core/pkg/protocol/engine/utxoledger"
"github.com/iotaledger/iota-core/pkg/testsuite"
"github.com/iotaledger/iota-core/pkg/testsuite/mock"
"github.com/iotaledger/iota-core/pkg/testsuite/snapshotcreator"
Expand Down Expand Up @@ -45,11 +50,11 @@ func Test_TransitionAndDestroyAccount(t *testing.T) {
)
defer ts.Shutdown()

// add a validator node to the network. This will add a validator account to the snapshot.
// Add a validator node to the network. This will add a validator account to the snapshot.
node1 := ts.AddValidatorNode("node1")
// add a non-validator node to the network. This will not add any accounts to the snapshot.
// Add a non-validator node to the network. This will not add any accounts to the snapshot.
_ = ts.AddNode("node2")
// add a default block issuer to the network. This will add another block issuer account to the snapshot.
// Add a default block issuer to the network. This will add another block issuer account to the snapshot.
wallet := ts.AddGenesisWallet("default", node1, iotago.MaxBlockIssuanceCredits/2)

ts.Run(true)
Expand Down Expand Up @@ -452,6 +457,219 @@ func Test_ImplicitAccounts(t *testing.T) {
ts.Wait(ts.Nodes()...)
}

func Test_NegativeBIC(t *testing.T) {
ts := testsuite.NewTestSuite(t,
testsuite.WithProtocolParametersOptions(
iotago.WithTimeProviderOptions(
testsuite.GenesisTimeWithOffsetBySlots(200, testsuite.DefaultSlotDurationInSeconds),
testsuite.DefaultSlotDurationInSeconds,
8,
),
iotago.WithLivenessOptions(
testsuite.DefaultLivenessThresholdLowerBoundInSeconds,
testsuite.DefaultLivenessThresholdUpperBoundInSeconds,
testsuite.DefaultMinCommittableAge,
100,
testsuite.DefaultEpochNearingThreshold,
),
),
)
defer ts.Shutdown()

// Add a validator node to the network. This will add a validator account to the snapshot.
node1 := ts.AddValidatorNode("node1")
node2 := ts.AddNode("node2")

validatorBIC := iotago.MaxBlockIssuanceCredits / 2
// Add a default block issuer to the network. This will add another block issuer account to the snapshot.
walletBIC := iotago.BlockIssuanceCredits(499)
wallet := ts.AddGenesisWallet("default", node2, walletBIC)

ts.Run(false)

// check that the accounts added in the genesis snapshot were added to the account manager correctly.
// validator node account.
validatorAccountOutput := ts.AccountOutput("Genesis:1")
ts.AssertAccountData(&accounts.AccountData{
ID: node1.Validator.AccountID,
Credits: accounts.NewBlockIssuanceCredits(validatorBIC, 0),
OutputID: validatorAccountOutput.OutputID(),
ExpirySlot: iotago.MaxSlotIndex,
BlockIssuerKeys: node1.Validator.BlockIssuerKeys(),
StakeEndEpoch: iotago.MaxEpochIndex,
ValidatorStake: mock.MinValidatorAccountAmount,
}, ts.Nodes()...)
// default wallet block issuer account.
blockIssuerAccountOutput := ts.AccountOutput("Genesis:2")
ts.AssertAccountData(&accounts.AccountData{
ID: wallet.BlockIssuer.AccountID,
Credits: accounts.NewBlockIssuanceCredits(walletBIC, 0),
OutputID: blockIssuerAccountOutput.OutputID(),
ExpirySlot: iotago.MaxSlotIndex,
BlockIssuerKeys: wallet.BlockIssuer.BlockIssuerKeys(),
}, ts.Nodes()...)

// MODIFY EXISTING GENESIS ACCOUNT
var block1Slot iotago.SlotIndex = 1
var latestParent *blocks.Block
// Issue one block from each of the two block-issuers - one will go negative and the other has enough BICs.
{
block1Commitment := iotago.NewEmptyCommitment(ts.API.ProtocolParameters().Version())
block1Commitment.ReferenceManaCost = ts.API.ProtocolParameters().CongestionControlParameters().MinReferenceManaCost
block11 := ts.IssueBasicBlockAtSlotWithOptions("block1.1", block1Slot, ts.Wallet("node1"), &iotago.TaggedData{}, mock.WithSlotCommitment(block1Commitment))
block12 := ts.IssueBasicBlockAtSlotWithOptions("block1.2", block1Slot, wallet, &iotago.TaggedData{}, mock.WithStrongParents(block11.ID()), mock.WithSlotCommitment(block1Commitment))

// Commit BIC burns and check account states.
latestParent = ts.CommitUntilSlot(ts.BlockID("block1.2").Slot(), block12)

burned := iotago.BlockIssuanceCredits(block11.WorkScore()) * iotago.BlockIssuanceCredits(ts.API.ProtocolParameters().CongestionControlParameters().MinReferenceManaCost)

validatorBIC -= burned
walletBIC -= burned

ts.AssertAccountData(&accounts.AccountData{
ID: node1.Validator.AccountID,
Credits: accounts.NewBlockIssuanceCredits(validatorBIC, block1Slot),
OutputID: validatorAccountOutput.OutputID(),
ExpirySlot: iotago.MaxSlotIndex,
BlockIssuerKeys: node1.Validator.BlockIssuerKeys(),
StakeEndEpoch: iotago.MaxEpochIndex,
ValidatorStake: mock.MinValidatorAccountAmount,
}, ts.Nodes()...)
ts.AssertAccountData(&accounts.AccountData{
ID: wallet.BlockIssuer.AccountID,
Credits: accounts.NewBlockIssuanceCredits(walletBIC, block1Slot),
ExpirySlot: iotago.MaxSlotIndex,
OutputID: blockIssuerAccountOutput.OutputID(),
BlockIssuerKeys: wallet.BlockIssuer.BlockIssuerKeys(),
}, ts.Nodes()...)
}

block2Slot := latestParent.ID().Slot()

// Try to issue more blocks from each of the issuers - one succeeds in issuing a block,
// the other has the block rejected in the CommitmentFilter as his account has negative BIC value.
{

block21 := ts.IssueBasicBlockAtSlotWithOptions("block2.1", block2Slot, ts.Wallet("node1"), &iotago.TaggedData{})

block22 := ts.IssueBasicBlockAtSlotWithOptions("block2.2", block2Slot, wallet, &iotago.TaggedData{}, mock.WithStrongParents(ts.BlockID("block2.1")))

ts.AssertBlockFiltered([]*blocks.Block{block22}, iotago.ErrNegativeBIC, wallet.Node)

latestParent = ts.CommitUntilSlot(ts.BlockID("block2.1").Slot(), block21)

burned := iotago.BlockIssuanceCredits(block21.WorkScore()) * iotago.BlockIssuanceCredits(ts.API.ProtocolParameters().CongestionControlParameters().MinReferenceManaCost)

validatorBIC -= burned

ts.AssertAccountData(&accounts.AccountData{
ID: node1.Validator.AccountID,
Credits: accounts.NewBlockIssuanceCredits(validatorBIC, block2Slot),
OutputID: validatorAccountOutput.OutputID(),
ExpirySlot: iotago.MaxSlotIndex,
BlockIssuerKeys: node1.Validator.BlockIssuerKeys(),
StakeEndEpoch: iotago.MaxEpochIndex,
ValidatorStake: mock.MinValidatorAccountAmount,
LatestSupportedProtocolVersionAndHash: model.VersionAndHash{
Version: ts.API.ProtocolParameters().Version(),
Hash: lo.PanicOnErr(ts.API.ProtocolParameters().Hash()),
},
}, ts.Nodes()...)
ts.AssertAccountData(&accounts.AccountData{
ID: wallet.BlockIssuer.AccountID,
Credits: accounts.NewBlockIssuanceCredits(walletBIC, block1Slot),
ExpirySlot: iotago.MaxSlotIndex,
OutputID: blockIssuerAccountOutput.OutputID(),
BlockIssuerKeys: wallet.BlockIssuer.BlockIssuerKeys(),
}, ts.Nodes()...)
}

block3Slot := latestParent.ID().Slot()

// Allot some mana to the locked account to unlock it.
{
allottedBIC := iotago.BlockIssuanceCredits(500)
tx1, err := wallet.CreateSignedTransactionWithOptions(
"TX1",
mock.WithInputs(utxoledger.Outputs{wallet.Output("Genesis:0")}),
mock.WithOutputs(iotago.Outputs[iotago.Output]{wallet.Output("Genesis:0").Output()}),
mock.WithSlotCreated(block3Slot),
mock.WithAllotments(iotago.Allotments{&iotago.Allotment{
AccountID: wallet.BlockIssuer.AccountID,
Mana: iotago.Mana(allottedBIC),
}}),
)
require.NoError(t, err)

block3Commitment := node1.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment()
block31 := ts.IssueBasicBlockAtSlotWithOptions("block3.1", block3Slot, ts.Wallet("node1"), tx1, mock.WithStrongParents(latestParent.ID()), mock.WithSlotCommitment(block3Commitment))

latestParent = ts.CommitUntilSlot(ts.BlockID("block3.1").Slot(), block31)

burned := iotago.BlockIssuanceCredits(block31.WorkScore()) * iotago.BlockIssuanceCredits(ts.API.ProtocolParameters().CongestionControlParameters().MinReferenceManaCost)

validatorBIC -= burned
walletBIC += allottedBIC

ts.AssertAccountData(&accounts.AccountData{
ID: node1.Validator.AccountID,
Credits: accounts.NewBlockIssuanceCredits(validatorBIC, block3Slot),
OutputID: validatorAccountOutput.OutputID(),
ExpirySlot: iotago.MaxSlotIndex,
BlockIssuerKeys: node1.Validator.BlockIssuerKeys(),
StakeEndEpoch: iotago.MaxEpochIndex,
ValidatorStake: mock.MinValidatorAccountAmount,
LatestSupportedProtocolVersionAndHash: model.VersionAndHash{
Version: ts.API.ProtocolParameters().Version(),
Hash: lo.PanicOnErr(ts.API.ProtocolParameters().Hash()),
},
}, ts.Nodes()...)
ts.AssertAccountData(&accounts.AccountData{
ID: wallet.BlockIssuer.AccountID,
Credits: accounts.NewBlockIssuanceCredits(walletBIC, block3Slot),
ExpirySlot: iotago.MaxSlotIndex,
OutputID: blockIssuerAccountOutput.OutputID(),
BlockIssuerKeys: wallet.BlockIssuer.BlockIssuerKeys(),
}, ts.Nodes()...)
}

block4Slot := latestParent.ID().Slot()

// Issue block from the unlocked account to make sure that it's actually unlocked.
{

block4 := ts.IssueBasicBlockAtSlotWithOptions("block4", block4Slot, wallet, &iotago.TaggedData{}, mock.WithStrongParents(latestParent.ID()))

latestParent = ts.CommitUntilSlot(ts.BlockID("block4").Slot(), block4)

burned := iotago.BlockIssuanceCredits(block4.WorkScore()) * iotago.BlockIssuanceCredits(ts.API.ProtocolParameters().CongestionControlParameters().MinReferenceManaCost)

walletBIC -= burned

ts.AssertAccountData(&accounts.AccountData{
ID: node1.Validator.AccountID,
Credits: accounts.NewBlockIssuanceCredits(validatorBIC, block3Slot),
OutputID: validatorAccountOutput.OutputID(),
ExpirySlot: iotago.MaxSlotIndex,
BlockIssuerKeys: node1.Validator.BlockIssuerKeys(),
StakeEndEpoch: iotago.MaxEpochIndex,
ValidatorStake: mock.MinValidatorAccountAmount,
LatestSupportedProtocolVersionAndHash: model.VersionAndHash{
Version: ts.API.ProtocolParameters().Version(),
Hash: lo.PanicOnErr(ts.API.ProtocolParameters().Hash()),
},
}, ts.Nodes()...)
ts.AssertAccountData(&accounts.AccountData{
ID: wallet.BlockIssuer.AccountID,
Credits: accounts.NewBlockIssuanceCredits(walletBIC, block4Slot),
ExpirySlot: iotago.MaxSlotIndex,
OutputID: blockIssuerAccountOutput.OutputID(),
BlockIssuerKeys: wallet.BlockIssuer.BlockIssuerKeys(),
}, ts.Nodes()...)
}
}

/*
For Mana allotment and stored:
1. Collect potential and stored on the input side.
Expand Down
16 changes: 16 additions & 0 deletions pkg/testsuite/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ func (t *TestSuite) AssertBlocksExist(blocks []*blocks.Block, expectedExist bool
}
}

func (t *TestSuite) AssertBlockFiltered(blocks []*blocks.Block, reason error, node *mock.Node) {
for _, block := range blocks {
t.Eventually(func() error {
for _, filteredBlockEvent := range node.FilteredBlocks() {
if filteredBlockEvent.Block.ID() == block.ID() {
if ierrors.Is(filteredBlockEvent.Reason, reason) {
return nil
}
}
}

return ierrors.Errorf("AssertBlockFiltered: %s: block %s was not filtered by the CommitmentFilter", node.Name, block.ID())
})
}
}

func (t *TestSuite) assertBlocksInCacheWithFunc(expectedBlocks []*blocks.Block, propertyName string, expectedPropertyState bool, propertyFunc func(*blocks.Block) bool, nodes ...*mock.Node) {
mustNodes(nodes)

Expand Down
13 changes: 11 additions & 2 deletions pkg/testsuite/mock/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ type Node struct {
candidateEngineActivatedCount atomic.Uint32
mainEngineSwitchedCount atomic.Uint32

mutex syncutils.RWMutex
attachedBlocks []*blocks.Block
mutex syncutils.RWMutex
attachedBlocks []*blocks.Block
filteredBlockEvents []*commitmentfilter.BlockFilteredEvent
}

func NewNode(t *testing.T, net *Network, partition string, name string, validator bool) *Node {
Expand Down Expand Up @@ -149,6 +150,10 @@ func (n *Node) hookEvents() {
events.CandidateEngineActivated.Hook(func(e *engine.Engine) { n.candidateEngineActivatedCount.Add(1) })

events.MainEngineSwitched.Hook(func(e *engine.Engine) { n.mainEngineSwitchedCount.Add(1) })

events.Engine.CommitmentFilter.BlockFiltered.Hook(func(event *commitmentfilter.BlockFilteredEvent) {
n.filteredBlockEvents = append(n.filteredBlockEvents, event)
})
}

func (n *Node) hookLogging(failOnBlockFiltered bool) {
Expand Down Expand Up @@ -506,6 +511,10 @@ func (n *Node) CandidateEngineActivatedCount() int {
return int(n.candidateEngineActivatedCount.Load())
}

func (n *Node) FilteredBlocks() []*commitmentfilter.BlockFilteredEvent {
return n.filteredBlockEvents
}

func (n *Node) MainEngineSwitchedCount() int {
return int(n.mainEngineSwitchedCount.Load())
}
Expand Down
18 changes: 9 additions & 9 deletions pkg/testsuite/mock/wallet_transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (w *Wallet) CreateAccountFromInput(transactionName string, inputName string
outputStates = append(outputStates, remainderOutput)
}

signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions(
signedTransaction := lo.PanicOnErr(w.CreateSignedTransactionWithOptions(
transactionName,
WithContextInputs(iotago.TxEssenceContextInputs{
&iotago.CommitmentInput{
Expand Down Expand Up @@ -86,7 +86,7 @@ func (w *Wallet) CreateDelegationFromInput(transactionName string, inputName str
}

// create the signed transaction
signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions(
signedTransaction := lo.PanicOnErr(w.CreateSignedTransactionWithOptions(
transactionName,
WithContextInputs(iotago.TxEssenceContextInputs{
&iotago.CommitmentInput{
Expand Down Expand Up @@ -120,7 +120,7 @@ func (w *Wallet) DelayedClaimingTransition(transactionName string, inputName str

delegationOutput := delegationBuilder.MustBuild()

signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions(
signedTransaction := lo.PanicOnErr(w.CreateSignedTransactionWithOptions(
transactionName,
WithContextInputs(iotago.TxEssenceContextInputs{
&iotago.CommitmentInput{
Expand Down Expand Up @@ -149,7 +149,7 @@ func (w *Wallet) TransitionAccount(transactionName string, inputName string, opt
accountBuilder := builder.NewAccountOutputBuilderFromPrevious(accountOutput)
accountOutput = options.Apply(accountBuilder, opts).MustBuild()

signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions(
signedTransaction := lo.PanicOnErr(w.CreateSignedTransactionWithOptions(
transactionName,
WithAccountInput(input),
WithContextInputs(iotago.TxEssenceContextInputs{
Expand Down Expand Up @@ -182,7 +182,7 @@ func (w *Wallet) DestroyAccount(transactionName string, inputName string, creati
Features: iotago.BasicOutputFeatures{},
}}

signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions(
signedTransaction := lo.PanicOnErr(w.CreateSignedTransactionWithOptions(
transactionName,
WithContextInputs(iotago.TxEssenceContextInputs{
&iotago.BlockIssuanceCreditInput{
Expand Down Expand Up @@ -222,7 +222,7 @@ func (w *Wallet) CreateImplicitAccountFromInput(transactionName string, inputNam
Features: iotago.BasicOutputFeatures{},
}

signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions(
signedTransaction := lo.PanicOnErr(w.CreateSignedTransactionWithOptions(
transactionName,
WithInputs(utxoledger.Outputs{input}),
WithOutputs(iotago.Outputs[iotago.Output]{implicitAccountOutput, remainderBasicOutput}),
Expand Down Expand Up @@ -254,7 +254,7 @@ func (w *Wallet) TransitionImplicitAccountToAccountOutput(transactionName string
AccountID(iotago.AccountIDFromOutputID(input.OutputID())),
opts).MustBuild()

signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions(
signedTransaction := lo.PanicOnErr(w.CreateSignedTransactionWithOptions(
transactionName,
WithContextInputs(iotago.TxEssenceContextInputs{
&iotago.BlockIssuanceCreditInput{
Expand Down Expand Up @@ -312,7 +312,7 @@ func (w *Wallet) CreateBasicOutputsEquallyFromInputs(transactionName string, out
})
}

signedTransaction := lo.PanicOnErr(w.createSignedTransactionWithOptions(
signedTransaction := lo.PanicOnErr(w.CreateSignedTransactionWithOptions(
transactionName,
WithInputs(inputStates),
WithOutputs(outputStates),
Expand All @@ -321,7 +321,7 @@ func (w *Wallet) CreateBasicOutputsEquallyFromInputs(transactionName string, out
return signedTransaction
}

func (w *Wallet) createSignedTransactionWithOptions(transactionName string, opts ...options.Option[builder.TransactionBuilder]) (*iotago.SignedTransaction, error) {
func (w *Wallet) CreateSignedTransactionWithOptions(transactionName string, opts ...options.Option[builder.TransactionBuilder]) (*iotago.SignedTransaction, error) {
currentAPI := w.Node.Protocol.CommittedAPI()

txBuilder := builder.NewTransactionBuilder(currentAPI)
Expand Down

0 comments on commit fb5f2c0

Please sign in to comment.