diff --git a/dapps/valuetransfers/packages/tangle/tangle.go b/dapps/valuetransfers/packages/tangle/tangle.go index 71c3dbb6db8992fbad9fd3370d9f0c9c0a0d46e5..104194ff524c394dfd7bf17933fb5b06414d2eba 100644 --- a/dapps/valuetransfers/packages/tangle/tangle.go +++ b/dapps/valuetransfers/packages/tangle/tangle.go @@ -167,6 +167,7 @@ func (tangle *Tangle) BranchManager() *branchmanager.BranchManager { // LoadSnapshot creates a set of outputs in the value tangle, that are forming the genesis for future transactions. func (tangle *Tangle) LoadSnapshot(snapshot Snapshot) { + // TODO: snapshot should also reflect the consumers of transactions for transactionID, addressBalances := range snapshot { for outputAddress, balances := range addressBalances { input := NewOutput(outputAddress, transactionID, branchmanager.MasterBranchID, balances) diff --git a/plugins/webapi/value/gettransactionbyid/handler.go b/plugins/webapi/value/gettransactionbyid/handler.go index ba4d9248bf24eac9cdca043f1c6da2bd2aa3d8c4..3bd214df64635497b423d0ee0319f303e5987ef3 100644 --- a/plugins/webapi/value/gettransactionbyid/handler.go +++ b/plugins/webapi/value/gettransactionbyid/handler.go @@ -19,20 +19,28 @@ func Handler(c echo.Context) error { } // get txn by txn id - txnObj := valuetransfers.Tangle.Transaction(txnID) - defer txnObj.Release() - if !txnObj.Exists() { + cachedTxnMetaObj := valuetransfers.Tangle.TransactionMetadata(txnID) + defer cachedTxnMetaObj.Release() + if !cachedTxnMetaObj.Exists() { return c.JSON(http.StatusNotFound, Response{Error: "Transaction not found"}) } - txn := utils.ParseTransaction(txnObj.Unwrap()) + cachedTxnObj := valuetransfers.Tangle.Transaction(txnID) + defer cachedTxnObj.Release() + if !cachedTxnObj.Exists() { + return c.JSON(http.StatusNotFound, Response{Error: "Transaction not found"}) + } + txn := utils.ParseTransaction(cachedTxnObj.Unwrap()) - // TODO: get inclusion state + txnMeta := cachedTxnMetaObj.Unwrap() return c.JSON(http.StatusOK, Response{ Transaction: txn, InclusionState: utils.InclusionState{ - Confirmed: true, - Conflict: false, - Liked: true, + Confirmed: txnMeta.Confirmed(), + Conflict: txnMeta.Conflicting(), + Liked: txnMeta.Liked(), + Solid: txnMeta.Solid(), + Rejected: txnMeta.Rejected(), + Finalized: txnMeta.Finalized(), }, }) } diff --git a/plugins/webapi/value/unspentoutputs/handler.go b/plugins/webapi/value/unspentoutputs/handler.go index acb5f5b83ff7f39b2cc176b8d76d792834bfde31..7b77c21544d9521d07317ccc090f99016215429c 100644 --- a/plugins/webapi/value/unspentoutputs/handler.go +++ b/plugins/webapi/value/unspentoutputs/handler.go @@ -28,9 +28,13 @@ func Handler(c echo.Context) error { outputids := make([]OutputID, 0) // get outputids by address - for id, outputObj := range valuetransfers.Tangle.OutputsOnAddress(address) { - defer outputObj.Release() - output := outputObj.Unwrap() + for id, cachedOutput := range valuetransfers.Tangle.OutputsOnAddress(address) { + // TODO: don't do this in a for + defer cachedOutput.Release() + output := cachedOutput.Unwrap() + cachedTxMeta := valuetransfers.Tangle.TransactionMetadata(output.TransactionID()) + // TODO: don't do this in a for + defer cachedTxMeta.Release() // TODO: get inclusion state if output.ConsumerCount() == 0 { @@ -43,14 +47,20 @@ func Handler(c echo.Context) error { }) } + inclusionState := utils.InclusionState{} + if cachedTxMeta.Exists() { + txMeta := cachedTxMeta.Unwrap() + inclusionState.Confirmed = txMeta.Confirmed() + inclusionState.Liked = txMeta.Liked() + inclusionState.Rejected = txMeta.Rejected() + inclusionState.Finalized = txMeta.Finalized() + inclusionState.Conflict = txMeta.Conflicting() + inclusionState.Confirmed = txMeta.Confirmed() + } outputids = append(outputids, OutputID{ - ID: id.String(), - Balances: b, - InclusionState: utils.InclusionState{ - Confirmed: true, - Conflict: false, - Liked: true, - }, + ID: id.String(), + Balances: b, + InclusionState: inclusionState, }) } } diff --git a/plugins/webapi/value/utils/transaction_handler.go b/plugins/webapi/value/utils/transaction_handler.go index 15a587ead8c24e2233a4e6b78249f56146c94fb8..26acd8d9e25001a6428128348d3ae4cd84ec3b12 100644 --- a/plugins/webapi/value/utils/transaction_handler.go +++ b/plugins/webapi/value/utils/transaction_handler.go @@ -67,4 +67,7 @@ type InclusionState struct { Confirmed bool `json:"confirmed,omitempty"` Conflict bool `json:"conflict,omitempty"` Liked bool `json:"liked,omitempty"` + Solid bool `json:"solid,omitempty"` + Rejected bool `json:"rejected,omitempty"` + Finalized bool `json:"finalized,omitempty"` } diff --git a/tools/integration-tests/assets/entrypoint.sh b/tools/integration-tests/assets/entrypoint.sh index a5ec0407b81c57c162ead53b0494c224f3c76ae7..7e5f060f00c41ae1b7f58e452fddc87a7909221d 100644 --- a/tools/integration-tests/assets/entrypoint.sh +++ b/tools/integration-tests/assets/entrypoint.sh @@ -6,4 +6,4 @@ chmod 777 /assets/* echo "assets:" ls /assets echo "running tests..." -go test ./tests/"${TEST_NAME}" -v -timeout 30m +go test ./tests/"${TEST_NAME}" -run TestConsensusConflicts -v -timeout 30m diff --git a/tools/integration-tests/tester/framework/framework.go b/tools/integration-tests/tester/framework/framework.go index d4456d5740694f9ffb31fbe9f01d2f73715cb871..4c19cc4ac861bf931af96574c19e1b94b2ee70dc 100644 --- a/tools/integration-tests/tester/framework/framework.go +++ b/tools/integration-tests/tester/framework/framework.go @@ -80,7 +80,7 @@ func (f *Framework) CreateNetwork(name string, peers int, minimumNeighbors int, // create peers/GoShimmer nodes for i := 0; i < peers; i++ { config := GoShimmerConfig{ - Bootstrap: i == 0, + Bootstrap: true, BootstrapInitialIssuanceTimePeriodSec: bootstrapInitialIssuanceTimePeriodSec, Faucet: i == 0, } @@ -128,9 +128,7 @@ func (f *Framework) CreateNetworkWithPartitions(name string, peers, partitions, // create peers/GoShimmer nodes for i := 0; i < peers; i++ { - config := GoShimmerConfig{ - Bootstrap: i == 0, - } + config := GoShimmerConfig{Bootstrap: true} if _, err = network.CreatePeer(config); err != nil { return nil, err } diff --git a/tools/integration-tests/tester/tests/consensus/consensus_conflicts_test.go b/tools/integration-tests/tester/tests/consensus/consensus_conflicts_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1c22d2e5b46859943efd4645f57c2fb5ceebae94 --- /dev/null +++ b/tools/integration-tests/tester/tests/consensus/consensus_conflicts_test.go @@ -0,0 +1,103 @@ +package consensus + +import ( + "log" + "testing" + "time" + + "github.com/iotaledger/goshimmer/dapps/valuetransfers" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address/signaturescheme" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet" + "github.com/iotaledger/goshimmer/plugins/webapi/value/utils" + "github.com/iotaledger/goshimmer/tools/integration-tests/tester/tests" + "github.com/mr-tron/base58/base58" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestConsensusConflicts issues valid conflicting value objects and makes sure that +// the conflicts are resolved via FPC. +func TestConsensusConflicts(t *testing.T) { + n, err := f.CreateNetworkWithPartitions("consensus_TestConsensusConflicts", 8, 2, 2) + require.NoError(t, err) + defer tests.ShutdownNetwork(t, n) + + time.Sleep(10 * time.Second) + + // split the network + //assert.NoError(t, n.Split(n.Peers()[:len(n.Peers())/2], n.Peers()[len(n.Peers())/2:])) + + // genesis wallet + genesisSeedBytes, err := base58.Decode("7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih") + require.NoError(t, err, "couldn't decode genesis seed from base58 seed") + + const genesisBalance = 1000000000 + genesisWallet := wallet.New(genesisSeedBytes) + genesisAddr := genesisWallet.Seed().Address(0) + genesisOutputID := transaction.NewOutputID(genesisAddr, transaction.GenesisID) + + // issue transactions which spend the same genesis output in all partitions + conflictingTxs := make([]*transaction.Transaction, len(n.Partitions())) + conflictingTxIDs := make([]string, len(n.Partitions())) + receiverWallets := make([]*wallet.Wallet, len(n.Partitions())) + for i, partition := range n.Partitions() { + partitionReceiverWallet := wallet.New() + destAddr := partitionReceiverWallet.Seed().Address(0) + receiverWallets[i] = partitionReceiverWallet + tx := transaction.New( + transaction.NewInputs(genesisOutputID), + transaction.NewOutputs(map[address.Address][]*balance.Balance{ + destAddr: { + {Value: genesisBalance / 2, Color: balance.ColorIOTA}, + }, + })) + tx = tx.Sign(signaturescheme.ED25519(*genesisWallet.Seed().KeyPair(0))) + conflictingTxs[i] = tx + conflictingTxIDs[i] = tx.ID().String() + log.Println("issuing conflict transaction on partition", i, tx.ID().String()) + _, err := partition.Peers()[0].SendTransaction(tx.Bytes()) + require.NoError(t, err) + } + + // sleep the avg. network delay so both partitions prefer their own first seen transaction + time.Sleep(valuetransfers.AverageNetworkDelay) + + // merge back the partitions + log.Println("merging partitions...") + assert.NoError(t, n.DeletePartitions(), "merging the network partitions should work") + log.Println("waiting for resolved partitions to autopeer to each other") + err = n.WaitForAutopeering(5) + require.NoError(t, err) + + // ensure message flow so that both partitions will get the conflicting tx + for _, p := range n.Peers() { + tests.SendDataMessage(t, p, []byte("DATA"), 10) + } + + log.Println("waiting for transactions to be available on all peers...") + err = tests.AwaitTransactionAvailability(n.Peers(), conflictingTxIDs, 30*time.Second) + assert.NoError(t, err, "transactions should have been available") + + expectations := map[string]*tests.ExpectedTransaction{} + for _, conflictingTx := range conflictingTxs { + utilsTx := utils.ParseTransaction(conflictingTx) + expectations[conflictingTx.ID().String()] = &tests.ExpectedTransaction{ + Inputs: &utilsTx.Inputs, + Outputs: &utilsTx.Outputs, + Signature: &utilsTx.Signature, + } + } + + tests.CheckTransactions(t, n.Peers(), expectations, true, tests.ExpectedInclusionState{ + Confirmed: tests.False(), + Finalized: tests.False(), + // should be part of a conflict set + Conflict: tests.True(), + Solid: tests.True(), + Rejected: tests.False(), + Liked: tests.False(), + }) +} diff --git a/tools/integration-tests/tester/tests/consensus/consensus_noconflicts_test.go b/tools/integration-tests/tester/tests/consensus/consensus_noconflicts_test.go new file mode 100644 index 0000000000000000000000000000000000000000..720334b2c3c2b83ea4dac783622047cacb542ad7 --- /dev/null +++ b/tools/integration-tests/tester/tests/consensus/consensus_noconflicts_test.go @@ -0,0 +1,118 @@ +package consensus + +import ( + "math/rand" + "testing" + "time" + + "github.com/iotaledger/goshimmer/dapps/valuetransfers" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address/signaturescheme" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet" + "github.com/iotaledger/goshimmer/plugins/webapi/value/utils" + "github.com/iotaledger/goshimmer/tools/integration-tests/tester/tests" + "github.com/mr-tron/base58/base58" + "github.com/stretchr/testify/require" +) + +// TestConsensusNoConflicts issues valid non-conflicting value objects and then checks +// whether the ledger of every peer reflects the same correct state. +func TestConsensusNoConflicts(t *testing.T) { + n, err := f.CreateNetwork("consensus_TestConsensusNoConflicts", 4, 2) + require.NoError(t, err) + defer tests.ShutdownNetwork(t, n) + + time.Sleep(5 * time.Second) + + // genesis wallet + genesisSeedBytes, err := base58.Decode("7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih") + require.NoError(t, err, "couldn't decode genesis seed from base58 seed") + + const genesisBalance = 1000000000 + genesisWallet := wallet.New(genesisSeedBytes) + genesisAddr := genesisWallet.Seed().Address(0) + genesisOutputID := transaction.NewOutputID(genesisAddr, transaction.GenesisID) + + firstReceiver := wallet.New() + const depositCount = 10 + const deposit = genesisBalance / depositCount + firstReceiverAddresses := make([]string, depositCount) + firstReceiverDepositAddrs := make([]address.Address, depositCount) + firstReceiverDepositOutputs := map[address.Address][]*balance.Balance{} + firstReceiverExpectedBalances := map[string]map[balance.Color]int64{} + for i := 0; i < depositCount; i++ { + addr := firstReceiver.Seed().Address(uint64(i)) + firstReceiverDepositAddrs[i] = addr + firstReceiverAddresses[i] = addr.String() + firstReceiverDepositOutputs[addr] = []*balance.Balance{{Value: deposit, Color: balance.ColorIOTA}} + firstReceiverExpectedBalances[addr.String()] = map[balance.Color]int64{balance.ColorIOTA: deposit} + } + + // issue transaction spending from the genesis output + tx := transaction.New(transaction.NewInputs(genesisOutputID), transaction.NewOutputs(firstReceiverDepositOutputs)) + tx = tx.Sign(signaturescheme.ED25519(*genesisWallet.Seed().KeyPair(0))) + utilsTx := utils.ParseTransaction(tx) + + txID, err := n.Peers()[0].SendTransaction(tx.Bytes()) + require.NoError(t, err) + + // wait for the transaction to be propagated through the network + // and it becoming preferred, finalized and confirmed + time.Sleep(valuetransfers.AverageNetworkDelay*2 + valuetransfers.AverageNetworkDelay/2) + + // since we just issued a transaction spending the genesis output, there + // shouldn't be any UTXOs on the genesis address anymore + tests.CheckAddressOutputsFullyConsumed(t, n.Peers(), []string{genesisAddr.String()}) + + // since we waited 2.5 avg. network delays and there were no conflicting transactions, + // the transaction we just issued must be preferred, liked, finalized and confirmed + tests.CheckTransactions(t, n.Peers(), map[string]*tests.ExpectedTransaction{ + txID: {Inputs: &utilsTx.Inputs, Outputs: &utilsTx.Outputs, Signature: &utilsTx.Signature}, + }, true, tests.ExpectedInclusionState{ + Confirmed: tests.True(), Finalized: tests.True(), + Conflict: tests.False(), Solid: tests.True(), + Rejected: tests.False(), Liked: tests.True(), + }) + + // check balances on peers + tests.CheckBalances(t, n.Peers(), firstReceiverExpectedBalances) + + // issue transactions spending all the outputs which were just created from a random peer + secondReceiverWallet := wallet.New() + secondReceiverAddresses := make([]string, depositCount) + secondReceiverExpectedBalances := map[string]map[balance.Color]int64{} + secondReceiverExpectedTransactions := map[string]*tests.ExpectedTransaction{} + for i := 0; i < depositCount; i++ { + addr := secondReceiverWallet.Seed().Address(uint64(i)) + tx := transaction.New( + transaction.NewInputs(transaction.NewOutputID(firstReceiver.Seed().Address(uint64(i)), tx.ID())), + transaction.NewOutputs(map[address.Address][]*balance.Balance{ + addr: {{Value: deposit, Color: balance.ColorIOTA}}, + }), + ) + secondReceiverAddresses[i] = addr.String() + tx = tx.Sign(signaturescheme.ED25519(*secondReceiverWallet.Seed().KeyPair(uint64(i)))) + txID, err := n.Peers()[rand.Intn(len(n.Peers()))].SendTransaction(tx.Bytes()) + require.NoError(t, err) + + utilsTx := utils.ParseTransaction(tx) + secondReceiverExpectedBalances[addr.String()] = map[balance.Color]int64{balance.ColorIOTA: deposit} + secondReceiverExpectedTransactions[txID] = &tests.ExpectedTransaction{ + Inputs: &utilsTx.Inputs, Outputs: &utilsTx.Outputs, Signature: &utilsTx.Signature, + } + } + + // wait again some network delays for the transactions to materialize + time.Sleep(valuetransfers.AverageNetworkDelay*2 + valuetransfers.AverageNetworkDelay/2) + tests.CheckAddressOutputsFullyConsumed(t, n.Peers(), firstReceiverAddresses) + tests.CheckTransactions(t, n.Peers(), secondReceiverExpectedTransactions, true, + tests.ExpectedInclusionState{ + Confirmed: tests.True(), Finalized: tests.True(), + Conflict: tests.False(), Solid: tests.True(), + Rejected: tests.False(), Liked: tests.True(), + }, + ) + tests.CheckBalances(t, n.Peers(), secondReceiverExpectedBalances) +} diff --git a/tools/integration-tests/tester/tests/consensus/consensus_test.go b/tools/integration-tests/tester/tests/consensus/consensus_test.go deleted file mode 100644 index 3dca33caebc9d0b831dd54ad0aa4999862c92574..0000000000000000000000000000000000000000 --- a/tools/integration-tests/tester/tests/consensus/consensus_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package autopeering - -import ( - "testing" - "time" - - "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address" - "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address/signaturescheme" - "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance" - "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction" - "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet" - "github.com/iotaledger/goshimmer/plugins/webapi/value/utils" - "github.com/iotaledger/goshimmer/tools/integration-tests/tester/tests" - "github.com/mr-tron/base58/base58" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestConsensusNoConflicts issues valid non-conflicting value objects and then checks -// whether the ledger of every peer reflects the same correct state. -func TestConsensusNoConflicts(t *testing.T) { - n, err := f.CreateNetwork("consensus_TestConsensusNoConflicts", 4, 2) - require.NoError(t, err) - defer tests.ShutdownNetwork(t, n) - - time.Sleep(5 * time.Second) - - // genesis wallet - genesisSeedBytes, err := base58.Decode("7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih") - require.NoError(t, err, "couldn't decode genesis seed from base58 seed") - - const genesisBalance = 1000000000 - genesisWallet := wallet.New(genesisSeedBytes) - genesisAddr := genesisWallet.Seed().Address(0) - genesisOutputID := transaction.NewOutputID(genesisAddr, transaction.GenesisID) - - w := wallet.New() - addr1 := w.Seed().Address(0) - addr2 := w.Seed().Address(1) - const deposit = genesisBalance / 2 - - // issue transaction spending from the genesis output - tx := transaction.New( - transaction.NewInputs(genesisOutputID), - transaction.NewOutputs(map[address.Address][]*balance.Balance{ - addr1: {{Value: deposit, Color: balance.ColorIOTA}}, - addr2: {{Value: deposit, Color: balance.ColorIOTA}}, - }), - ) - tx = tx.Sign(signaturescheme.ED25519(*genesisWallet.Seed().KeyPair(0))) - utilsTx := utils.ParseTransaction(tx) - - txID, err := n.Peers()[0].SendTransaction(tx.Bytes()) - require.NoError(t, err) - - // wait for the transaction to be propagated through the network - time.Sleep(20 * time.Second) - - // check that each node has the same perception - for _, p := range n.Peers() { - // check existence of the transaction we just created - res, err := p.GetTransactionByID(txID) - assert.NoError(t, err) - assert.Len(t, res.Error, 0, "there shouldn't be any error from submitting the valid transaction") - assert.EqualValues(t, utilsTx.Inputs, res.Transaction.Inputs) - assert.EqualValues(t, utilsTx.Outputs, res.Transaction.Outputs) - assert.EqualValues(t, utilsTx.Signature, res.Transaction.Signature) - - // check that genesis UTXO is spent - utxos, err := p.GetUnspentOutputs([]string{genesisAddr.String()}) - assert.NoError(t, err) - assert.Len(t, utxos.Error, 0, "there shouldn't be any error from querying UTXOs") - assert.Len(t, utxos.UnspentOutputs, 0, "genesis address should not have any UTXOs") - - // check UTXOs - utxos, err = p.GetUnspentOutputs([]string{addr1.String(), addr2.String()}) - assert.Len(t, utxos.UnspentOutputs, 2, "addresses should have UTXOs") - assert.Equal(t, addr1.String(), utxos.UnspentOutputs[0].Address) - assert.EqualValues(t, deposit, utxos.UnspentOutputs[0].OutputIDs[0].Balances[0].Value) - assert.Equal(t, addr2.String(), utxos.UnspentOutputs[1].Address) - assert.EqualValues(t, deposit, utxos.UnspentOutputs[1].OutputIDs[0].Balances[0].Value) - } -} diff --git a/tools/integration-tests/tester/tests/consensus/main_test.go b/tools/integration-tests/tester/tests/consensus/main_test.go index bcc09c2e53590df061cbff5a2159071d567440f1..422928f9627868376b0610691f0e3ac2f6108636 100644 --- a/tools/integration-tests/tester/tests/consensus/main_test.go +++ b/tools/integration-tests/tester/tests/consensus/main_test.go @@ -1,4 +1,4 @@ -package autopeering +package consensus import ( "os" diff --git a/tools/integration-tests/tester/tests/testutil.go b/tools/integration-tests/tester/tests/testutil.go index a18532a0327b0422d4032e5ebabe5a3ade934cc0..38cee7a2c99d2bd8c043132d57ba4176a3f85dfe 100644 --- a/tools/integration-tests/tester/tests/testutil.go +++ b/tools/integration-tests/tester/tests/testutil.go @@ -1,8 +1,11 @@ package tests import ( + "errors" "fmt" "math/rand" + "sync" + "sync/atomic" "testing" "time" @@ -11,11 +14,16 @@ import ( "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction" "github.com/iotaledger/goshimmer/packages/binary/messagelayer/payload" + "github.com/iotaledger/goshimmer/plugins/webapi/value/utils" "github.com/iotaledger/goshimmer/tools/integration-tests/tester/framework" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +var ( + ErrTransactionNotAvailableInTime = errors.New("transaction was not available in time") +) + // DataMessageSent defines a struct to identify from which issuer a data message was sent. type DataMessageSent struct { number int @@ -346,8 +354,63 @@ func CheckBalances(t *testing.T, peers []*framework.Peer, addrBalance map[string } } -// CheckTransactions performs checks to make sure that all peers have received all transactions . -func CheckTransactions(t *testing.T, peers []*framework.Peer, transactionIDs []string, checkSynchronized bool) { +// CheckAddressOutputsFullyConsumed performs checks to make sure that on all given peers, +// the given addresses have no UTXOs. +func CheckAddressOutputsFullyConsumed(t *testing.T, peers []*framework.Peer, addrs []string) { + for _, peer := range peers { + resp, err := peer.GetUnspentOutputs(addrs) + assert.NoError(t, err) + assert.Len(t, resp.Error, 0) + for i, utxos := range resp.UnspentOutputs { + assert.Len(t, utxos.OutputIDs, 0, "address %s should not have any UTXOs", addrs[i]) + } + } +} + +// ExpectedInclusionState is an expected inclusion state. +// All fields are optional. +type ExpectedInclusionState struct { + // The optional confirmed state to check against. + Confirmed *bool + // The optional finalized state to check against. + Finalized *bool + // The optional conflict state to check against. + Conflict *bool + // The optional solid state to check against. + Solid *bool + // The optional rejected state to check against. + Rejected *bool + // The optional liked state to check against. + Liked *bool +} + +// True returns a pointer to a true bool. +func True() *bool { + x := true + return &x +} + +// False returns a pointer to a false bool. +func False() *bool { + x := false + return &x +} + +// ExpectedTransaction defines the expected data of a transaction. +// All fields are optional. +type ExpectedTransaction struct { + // The optional input IDs to check against. + Inputs *[]string + // The optional outputs to check against. + Outputs *[]utils.Output + // The optional signature to check against. + Signature *[]byte +} + +// CheckTransactions performs checks to make sure that all peers have received all transactions. +// Optionally takes an expected inclusion state for all supplied transaction IDs and expected transaction +// data per transaction ID. +func CheckTransactions(t *testing.T, peers []*framework.Peer, transactionIDs map[string]*ExpectedTransaction, checkSynchronized bool, expectedInclusionState ExpectedInclusionState) { for _, peer := range peers { if checkSynchronized { // check that the peer sees itself as synchronized @@ -356,14 +419,68 @@ func CheckTransactions(t *testing.T, peers []*framework.Peer, transactionIDs []s require.True(t, info.Synced) } - for _, txId := range transactionIDs { + for txId, expectedTransaction := range transactionIDs { resp, err := peer.GetTransactionByID(txId) require.NoError(t, err) // check inclusion state - assert.True(t, resp.InclusionState.Confirmed) + if expectedInclusionState.Confirmed != nil { + assert.Equal(t, *expectedInclusionState.Confirmed, resp.InclusionState.Confirmed, "confirmed state doesn't match") + } + if expectedInclusionState.Conflict != nil { + assert.Equal(t, *expectedInclusionState.Conflict, resp.InclusionState.Conflict, "conflict state doesn't match") + } + if expectedInclusionState.Solid != nil { + assert.Equal(t, *expectedInclusionState.Solid, resp.InclusionState.Solid, "solid state doesn't match") + } + if expectedInclusionState.Rejected != nil { + assert.Equal(t, *expectedInclusionState.Rejected, resp.InclusionState.Rejected, "rejected state doesn't match") + } + if expectedInclusionState.Liked != nil { + assert.Equal(t, *expectedInclusionState.Liked, resp.InclusionState.Liked, "liked state doesn't match") + } + + if expectedTransaction != nil { + if expectedTransaction.Inputs != nil { + assert.Equal(t, *expectedTransaction.Inputs, resp.Transaction.Inputs, "inputs do not match") + } + if expectedTransaction.Outputs != nil { + assert.Equal(t, *expectedTransaction.Outputs, resp.Transaction.Outputs, "outputs do not match") + } + if expectedTransaction.Signature != nil { + assert.Equal(t, *expectedTransaction.Signature, resp.Transaction.Signature, "signatures do not match") + } + } + } + } +} + +// AwaitTransactionAvailability awaits until the given transaction IDs become available on all given peers or +// the max duration is reached. +func AwaitTransactionAvailability(peers []*framework.Peer, transactionIDs []string, maxAwait time.Duration) error { + s := time.Now() + for ; time.Since(s) < maxAwait; { + var wg sync.WaitGroup + wg.Add(len(peers)) + counter := int32(len(peers) * len(transactionIDs)) + for _, p := range peers { + go func(p *framework.Peer) { + defer wg.Done() + for _, txID := range transactionIDs { + _, err := p.GetTransactionByID(txID) + if err == nil { + atomic.AddInt32(&counter, -1) + } + } + }(p) + } + wg.Wait() + if counter == 0 { + // everything available + return nil } } + return ErrTransactionNotAvailableInTime } // ShutdownNetwork shuts down the network and reports errors. diff --git a/tools/integration-tests/tester/tests/value/value_test.go b/tools/integration-tests/tester/tests/value/value_test.go index 4280c3354eaac9d4e813930c861a775ff8909e40..f0cb8109be17c0bea4ffaa1959042cd902070f43 100644 --- a/tools/integration-tests/tester/tests/value/value_test.go +++ b/tools/integration-tests/tester/tests/value/value_test.go @@ -18,26 +18,36 @@ func TestValueIotaPersistence(t *testing.T) { time.Sleep(5 * time.Second) // master node sends funds to all peers in the network - txIds, addrBalance := tests.SendValueMessagesOnFaucet(t, n.Peers()) + txIdsSlice, addrBalance := tests.SendValueMessagesOnFaucet(t, n.Peers()) + txIds := make(map[string]*tests.ExpectedTransaction) + for _, txID := range txIdsSlice { + txIds[txID] = nil + } // wait for messages to be gossiped time.Sleep(10 * time.Second) // check whether the first issued transaction is available on all nodes, and confirmed - tests.CheckTransactions(t, n.Peers(), txIds, true) + tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{ + Confirmed: tests.True(), + }) // check ledger state tests.CheckBalances(t, n.Peers(), addrBalance) // send value message randomly randomTxIds := tests.SendValueMessagesOnRandomPeer(t, n.Peers(), addrBalance, 10) - txIds = append(txIds, randomTxIds...) + for _, randomTxId := range randomTxIds { + txIds[randomTxId] = nil + } // wait for messages to be gossiped time.Sleep(10 * time.Second) // check whether all issued transactions are persistently available on all nodes, and confirmed - tests.CheckTransactions(t, n.Peers(), txIds, true) + tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{ + Confirmed: tests.True(), + }) // check ledger state tests.CheckBalances(t, n.Peers(), addrBalance) @@ -58,7 +68,9 @@ func TestValueIotaPersistence(t *testing.T) { time.Sleep(10 * time.Second) // check whether all issued transactions are persistently available on all nodes, and confirmed - tests.CheckTransactions(t, n.Peers(), txIds, true) + tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{ + Confirmed: tests.True(), + }) // 5. check ledger state tests.CheckBalances(t, n.Peers(), addrBalance) @@ -74,26 +86,36 @@ func TestValueColoredPersistence(t *testing.T) { time.Sleep(5 * time.Second) // master node sends funds to all peers in the network - txIds, addrBalance := tests.SendValueMessagesOnFaucet(t, n.Peers()) + txIdsSlice, addrBalance := tests.SendValueMessagesOnFaucet(t, n.Peers()) + txIds := make(map[string]*tests.ExpectedTransaction) + for _, txID := range txIdsSlice { + txIds[txID] = nil + } // wait for messages to be gossiped time.Sleep(10 * time.Second) // check whether the transactions are available on all nodes, and confirmed - tests.CheckTransactions(t, n.Peers(), txIds, true) + tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{ + Confirmed: tests.True(), + }) // check ledger state tests.CheckBalances(t, n.Peers(), addrBalance) // send funds around randomTxIds := tests.SendColoredValueMessagesOnRandomPeer(t, n.Peers(), addrBalance, 10) - txIds = append(txIds, randomTxIds...) + for _, randomTxId := range randomTxIds { + txIds[randomTxId] = nil + } // wait for value messages to be gossiped time.Sleep(10 * time.Second) // check whether all issued transactions are persistently available on all nodes, and confirmed - tests.CheckTransactions(t, n.Peers(), txIds, true) + tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{ + Confirmed: tests.True(), + }) // check ledger state tests.CheckBalances(t, n.Peers(), addrBalance) @@ -114,7 +136,9 @@ func TestValueColoredPersistence(t *testing.T) { time.Sleep(10 * time.Second) // check whether all issued transactions are persistently available on all nodes, and confirmed - tests.CheckTransactions(t, n.Peers(), txIds, true) + tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{ + Confirmed: tests.True(), + }) // 5. check ledger state tests.CheckBalances(t, n.Peers(), addrBalance)