From 9d61542a211dcee6752079e6f2899b369a85dda5 Mon Sep 17 00:00:00 2001 From: "Ching-Hua (Vivian) Lin" <jkrvivian@gmail.com> Date: Thu, 18 Jun 2020 23:09:07 +0800 Subject: [PATCH] Add value tangle integration tests (#464) * feat: Add first value transfer integration test * fix: fix wrong plugin name * Add colored tokens test * Add value tangle test to github workflow * fix: Fix wrong function name in comments * refactor: Make testSnapshots disabled in default and minor tweaks * refactor: refactor balance list update method * feat: Add inclusion states to weapis * refactor: Rename Conflict to Rejected * refactor: Remove unused parameter * refactor: Simplify colored token test and increase waiting time * fix: fix updateBalanceList * fix: enable testsnapshots plugin for each peer * refactor: Rename package name of each tests * refactor: Rename functions and add more comments * refactor: Add maxRetry to limit to avoid infinte loop and reduce else statement * refactor: Refactor functions and remove redundant codes * refactor: Rename shadowing variables * test: Add value test in runTest.sh * fixes some func comments * increase startup time to make up for slow CI Co-authored-by: Luca Moser <moser.luca@gmail.com> --- .github/workflows/integration-tests.yml | 35 +++ pluginmgr/core/plugins.go | 2 + plugins/testsnapshots/plugin.go | 41 +++ tools/integration-tests/runTests.sh | 2 +- .../tester/framework/docker.go | 11 +- .../tester/framework/drngnetwork.go | 2 +- .../tester/framework/framework.go | 1 + .../tester/framework/network.go | 11 +- .../tester/framework/parameters.go | 9 +- .../tester/framework/peer.go | 7 +- .../tester/tests/common/common_test.go | 2 +- .../tester/tests/common/main_test.go | 2 +- .../tester/tests/drng/drng_test.go | 2 +- .../tester/tests/drng/main_test.go | 2 +- .../tester/tests/message/main_test.go | 2 +- .../tester/tests/message/message_test.go | 2 +- .../tester/tests/testutil.go | 283 +++++++++++++++++- .../tester/tests/value/main_test.go | 23 ++ .../tester/tests/value/value_test.go | 122 ++++++++ 19 files changed, 547 insertions(+), 14 deletions(-) create mode 100644 plugins/testsnapshots/plugin.go create mode 100644 tools/integration-tests/tester/tests/value/main_test.go create mode 100644 tools/integration-tests/tester/tests/value/value_test.go diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index f1160f1c..cd08c1e7 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -143,3 +143,38 @@ jobs: with: name: ${{ env.TEST_NAME }} path: tools/integration-tests/logs + + + value: + name: value + env: + TEST_NAME: value + runs-on: ubuntu-latest + steps: + + - name: Check out code + uses: actions/checkout@v2 + + - name: Build GoShimmer image + run: docker build -t iotaledger/goshimmer . + + - name: Pull additional Docker images + run: | + docker pull angelocapossele/drand:latest + docker pull gaiaadm/pumba:latest + docker pull gaiadocker/iproute2:latest + + - name: Run integration tests + run: docker-compose -f tools/integration-tests/tester/docker-compose.yml up --abort-on-container-exit --exit-code-from tester --build + + - name: Create logs from tester + if: always() + run: | + docker logs tester &> tools/integration-tests/logs/tester.log + + - name: Save logs as artifacts + if: always() + uses: actions/upload-artifact@v1 + with: + name: ${{ env.TEST_NAME }} + path: tools/integration-tests/logs diff --git a/pluginmgr/core/plugins.go b/pluginmgr/core/plugins.go index 1eba3f11..eb4ff3b6 100644 --- a/pluginmgr/core/plugins.go +++ b/pluginmgr/core/plugins.go @@ -18,6 +18,7 @@ import ( "github.com/iotaledger/goshimmer/plugins/portcheck" "github.com/iotaledger/goshimmer/plugins/profiling" "github.com/iotaledger/goshimmer/plugins/sync" + "github.com/iotaledger/goshimmer/plugins/testsnapshots" "github.com/iotaledger/hive.go/node" ) @@ -40,4 +41,5 @@ var PLUGINS = node.Plugins( metrics.Plugin, drng.Plugin, valuetransfers.App, + testsnapshots.Plugin, ) diff --git a/plugins/testsnapshots/plugin.go b/plugins/testsnapshots/plugin.go new file mode 100644 index 00000000..294e5337 --- /dev/null +++ b/plugins/testsnapshots/plugin.go @@ -0,0 +1,41 @@ +package testsnapshots + +import ( + "github.com/iotaledger/goshimmer/dapps/valuetransfers" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction" + "github.com/iotaledger/hive.go/logger" + "github.com/iotaledger/hive.go/node" +) + +// FIXME: This plugin can be removed after snapshots is implemented +const ( + // PluginName is the plugin name of the TestSnapshots plugin. + PluginName = "TestSnapshots" +) + +var ( + // Plugin is the plugin instance of the TestSnapshots plugin. + Plugin = node.NewPlugin(PluginName, node.Disabled, configure, run) + log *logger.Logger + + // addresses for snapshots + address0, _ = address.FromBase58("JaMauTaTSVBNc13edCCvBK9fZxZ1KKW5fXegT1B7N9jY") +) + +func configure(_ *node.Plugin) { + log = logger.NewLogger(PluginName) + + valuetransfers.Tangle.LoadSnapshot(map[transaction.ID]map[address.Address][]*balance.Balance{ + transaction.GenesisID: { + address0: []*balance.Balance{ + balance.New(balance.ColorIOTA, 10000000), + }, + }, + }) + + log.Infof("load snapshots to tangle") +} + +func run(_ *node.Plugin) {} diff --git a/tools/integration-tests/runTests.sh b/tools/integration-tests/runTests.sh index 71a65da5..28cb55b9 100755 --- a/tools/integration-tests/runTests.sh +++ b/tools/integration-tests/runTests.sh @@ -1,6 +1,6 @@ #!/bin/bash -TEST_NAMES='autopeering common drng message' +TEST_NAMES='autopeering common drng message value' echo "Build GoShimmer image" docker build -t iotaledger/goshimmer ../../. diff --git a/tools/integration-tests/tester/framework/docker.go b/tools/integration-tests/tester/framework/docker.go index a5c2cb24..67a65d58 100644 --- a/tools/integration-tests/tester/framework/docker.go +++ b/tools/integration-tests/tester/framework/docker.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "strings" "time" "github.com/docker/docker/api/types" @@ -83,10 +84,16 @@ func (d *DockerContainer) CreateGoShimmerPeer(config GoShimmerConfig) error { "--logger.level=debug", fmt.Sprintf("--node.disablePlugins=%s", config.DisabledPlugins), fmt.Sprintf("--node.enablePlugins=%s", func() string { + var plugins []string + //TODO: remove this when snapshots is implemented + plugins = append(plugins, "testSnapshots") if config.Bootstrap { - return "Bootstrap" + plugins = append(plugins, "Bootstrap") } - return "" + if config.Faucet { + plugins = append(plugins, "faucet") + } + return strings.Join(plugins[:], ",") }()), fmt.Sprintf("--bootstrap.initialIssuance.timePeriodSec=%d", config.BootstrapInitialIssuanceTimePeriodSec), "--webapi.bindAddress=0.0.0.0:8080", diff --git a/tools/integration-tests/tester/framework/drngnetwork.go b/tools/integration-tests/tester/framework/drngnetwork.go index 9df42cdb..2f3031ce 100644 --- a/tools/integration-tests/tester/framework/drngnetwork.go +++ b/tools/integration-tests/tester/framework/drngnetwork.go @@ -55,7 +55,7 @@ func (n *DRNGNetwork) CreatePeer(c GoShimmerConfig, publicKey ed25519.PublicKey) return nil, err } - peer, err := newPeer(name, identity.New(publicKey), container, n.network) + peer, err := newPeer(name, identity.New(publicKey), container, nil, n.network) if err != nil { return nil, err } diff --git a/tools/integration-tests/tester/framework/framework.go b/tools/integration-tests/tester/framework/framework.go index 449c2dc6..d4456d57 100644 --- a/tools/integration-tests/tester/framework/framework.go +++ b/tools/integration-tests/tester/framework/framework.go @@ -82,6 +82,7 @@ func (f *Framework) CreateNetwork(name string, peers int, minimumNeighbors int, config := GoShimmerConfig{ Bootstrap: i == 0, BootstrapInitialIssuanceTimePeriodSec: bootstrapInitialIssuanceTimePeriodSec, + Faucet: i == 0, } if _, err = network.CreatePeer(config); err != nil { return nil, err diff --git a/tools/integration-tests/tester/framework/network.go b/tools/integration-tests/tester/framework/network.go index c115fcb5..4580588f 100644 --- a/tools/integration-tests/tester/framework/network.go +++ b/tools/integration-tests/tester/framework/network.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/client" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet" "github.com/iotaledger/hive.go/crypto/ed25519" "github.com/iotaledger/hive.go/identity" ) @@ -100,6 +101,14 @@ func (n *Network) CreatePeer(c GoShimmerConfig) (*Peer, error) { config.EntryNodePublicKey = n.entryNodePublicKey() config.DisabledPlugins = disabledPluginsPeer + // create wallet + var nodeWallet *wallet.Wallet + if c.Faucet == true { + nodeWallet = wallet.New(faucetSeed) + } else { + nodeWallet = wallet.New() + } + // create Docker container container := NewDockerContainer(n.dockerClient) err = container.CreateGoShimmerPeer(config) @@ -115,7 +124,7 @@ func (n *Network) CreatePeer(c GoShimmerConfig) (*Peer, error) { return nil, err } - peer, err := newPeer(name, identity.New(publicKey), container, n) + peer, err := newPeer(name, identity.New(publicKey), container, nodeWallet, n) if err != nil { return nil, err } diff --git a/tools/integration-tests/tester/framework/parameters.go b/tools/integration-tests/tester/framework/parameters.go index 6e2ae8c4..ddbb7046 100644 --- a/tools/integration-tests/tester/framework/parameters.go +++ b/tools/integration-tests/tester/framework/parameters.go @@ -13,7 +13,7 @@ const ( logsDir = "/tmp/logs/" - disabledPluginsEntryNode = "portcheck,dashboard,analysis-client,profiling,gossip,drng,issuer,sync,metrics,valuetransfers,messagelayer,webapi,webapibroadcastdataendpoint,webapifindtransactionhashesendpoint,webapigetneighborsendpoint,webapigettransactionobjectsbyhashendpoint,webapigettransactiontrytesbyhashendpoint" + disabledPluginsEntryNode = "portcheck,dashboard,analysis-client,profiling,gossip,drng,issuer,sync,metrics,valuetransfers,testsnapshots,messagelayer,webapi,webapibroadcastdataendpoint,webapifindtransactionhashesendpoint,webapigetneighborsendpoint,webapigettransactionobjectsbyhashendpoint,webapigettransactiontrytesbyhashendpoint" disabledPluginsPeer = "portcheck,dashboard,analysis-client,profiling" dockerLogsPrefixLen = 8 @@ -23,6 +23,11 @@ const ( exitStatusSuccessful = 0 ) +var ( + faucetSeed = []byte{251, 163, 190, 98, 92, 82, 164, 79, 74, 48, 203, 162, 247, 119, 140, 76, 33, 100, 148, 204, 244, 248, 232, 18, + 132, 217, 85, 31, 246, 83, 193, 193} +) + // GoShimmerConfig defines the config of a GoShimmer node. type GoShimmerConfig struct { Seed string @@ -38,6 +43,8 @@ type GoShimmerConfig struct { DRNGDistKey string DRNGInstance int DRNGThreshold int + + Faucet bool } // NetworkConfig defines the config of a GoShimmer Docker network. diff --git a/tools/integration-tests/tester/framework/peer.go b/tools/integration-tests/tester/framework/peer.go index 18c059e8..8da31c20 100644 --- a/tools/integration-tests/tester/framework/peer.go +++ b/tools/integration-tests/tester/framework/peer.go @@ -6,6 +6,7 @@ import ( "time" "github.com/iotaledger/goshimmer/client" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet" "github.com/iotaledger/goshimmer/plugins/webapi/autopeering" "github.com/iotaledger/hive.go/identity" ) @@ -24,13 +25,16 @@ type Peer struct { // the DockerContainer that this peer is running in *DockerContainer + // Wallet + *wallet.Wallet + chosen []autopeering.Neighbor accepted []autopeering.Neighbor } // newPeer creates a new instance of Peer with the given information. // dockerContainer needs to be started in order to determine the container's (and therefore peer's) IP correctly. -func newPeer(name string, identity *identity.Identity, dockerContainer *DockerContainer, network *Network) (*Peer, error) { +func newPeer(name string, identity *identity.Identity, dockerContainer *DockerContainer, wallet *wallet.Wallet, network *Network) (*Peer, error) { // after container is started we can get its IP ip, err := dockerContainer.IP(network.name) if err != nil { @@ -43,6 +47,7 @@ func newPeer(name string, identity *identity.Identity, dockerContainer *DockerCo Identity: identity, GoShimmerAPI: client.NewGoShimmerAPI(getWebAPIBaseURL(name), http.Client{Timeout: 30 * time.Second}), DockerContainer: dockerContainer, + Wallet: wallet, }, nil } diff --git a/tools/integration-tests/tester/tests/common/common_test.go b/tools/integration-tests/tester/tests/common/common_test.go index 7cdceef8..32f4fea3 100644 --- a/tools/integration-tests/tester/tests/common/common_test.go +++ b/tools/integration-tests/tester/tests/common/common_test.go @@ -1,4 +1,4 @@ -package autopeering +package common import ( "testing" diff --git a/tools/integration-tests/tester/tests/common/main_test.go b/tools/integration-tests/tester/tests/common/main_test.go index bcc09c2e..cdffe476 100644 --- a/tools/integration-tests/tester/tests/common/main_test.go +++ b/tools/integration-tests/tester/tests/common/main_test.go @@ -1,4 +1,4 @@ -package autopeering +package common import ( "os" diff --git a/tools/integration-tests/tester/tests/drng/drng_test.go b/tools/integration-tests/tester/tests/drng/drng_test.go index a9b59a9f..4db59119 100644 --- a/tools/integration-tests/tester/tests/drng/drng_test.go +++ b/tools/integration-tests/tester/tests/drng/drng_test.go @@ -1,4 +1,4 @@ -package autopeering +package drng import ( "encoding/json" diff --git a/tools/integration-tests/tester/tests/drng/main_test.go b/tools/integration-tests/tester/tests/drng/main_test.go index bcc09c2e..27877125 100644 --- a/tools/integration-tests/tester/tests/drng/main_test.go +++ b/tools/integration-tests/tester/tests/drng/main_test.go @@ -1,4 +1,4 @@ -package autopeering +package drng import ( "os" diff --git a/tools/integration-tests/tester/tests/message/main_test.go b/tools/integration-tests/tester/tests/message/main_test.go index bcc09c2e..4d3e5451 100644 --- a/tools/integration-tests/tester/tests/message/main_test.go +++ b/tools/integration-tests/tester/tests/message/main_test.go @@ -1,4 +1,4 @@ -package autopeering +package message import ( "os" diff --git a/tools/integration-tests/tester/tests/message/message_test.go b/tools/integration-tests/tester/tests/message/message_test.go index 37460d75..11315ebb 100644 --- a/tools/integration-tests/tester/tests/message/message_test.go +++ b/tools/integration-tests/tester/tests/message/message_test.go @@ -1,4 +1,4 @@ -package autopeering +package message import ( "testing" diff --git a/tools/integration-tests/tester/tests/testutil.go b/tools/integration-tests/tester/tests/testutil.go index 9e11ee44..b47c5c12 100644 --- a/tools/integration-tests/tester/tests/testutil.go +++ b/tools/integration-tests/tester/tests/testutil.go @@ -4,13 +4,20 @@ import ( "fmt" "math/rand" "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/packages/binary/messagelayer/payload" "github.com/iotaledger/goshimmer/tools/integration-tests/tester/framework" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +const maxRetry = 50 + // DataMessageSent defines a struct to identify from which issuer a data message was sent. type DataMessageSent struct { number int @@ -47,7 +54,7 @@ func SendDataMessagesOnRandomPeer(t *testing.T, peers []*framework.Peer, numMess // SendDataMessage sends a data message on a given peer and returns the id and a DataMessageSent struct. func SendDataMessage(t *testing.T, peer *framework.Peer, data []byte, number int) (string, DataMessageSent) { id, err := peer.Data(data) - require.NoErrorf(t, err, "Could not send message on %s", peer.String()) + require.NoErrorf(t, err, "could not send message on %s", peer.String()) sent := DataMessageSent{ number: number, @@ -95,6 +102,280 @@ func CheckForMessageIds(t *testing.T, peers []*framework.Peer, ids map[string]Da } } +// SendTransactionFromFaucet sends funds to peers from the faucet, sends back the remainder to faucet, and returns the transaction ID. +func SendTransactionFromFaucet(t *testing.T, peers []*framework.Peer, sentValue int64) (txIds []string, addrBalance map[string]map[balance.Color]int64) { + // initiate addrBalance map + addrBalance = make(map[string]map[balance.Color]int64) + for _, p := range peers { + addr := p.Seed().Address(0).String() + addrBalance[addr] = make(map[balance.Color]int64) + } + + faucetPeer := peers[0] + faucetAddrStr := faucetPeer.Seed().Address(0).String() + + // get faucet balances + unspentOutputs, err := faucetPeer.GetUnspentOutputs([]string{faucetAddrStr}) + require.NoErrorf(t, err, "could not get unspent outputs on %s", faucetPeer.String()) + addrBalance[faucetAddrStr][balance.ColorIOTA] = unspentOutputs.UnspentOutputs[0].OutputIDs[0].Balances[0].Value + + // send funds to other peers + for i := 1; i < len(peers); i++ { + fail, txId := SendIotaTransaction(t, faucetPeer, peers[i], addrBalance, sentValue) + require.False(t, fail) + txIds = append(txIds, txId) + + // let the transaction propagate + time.Sleep(3 * time.Second) + } + + return +} + +// SendTransactionOnRandomPeer sends sentValue amount of IOTA tokens from/to a random peer, mutates the given balance map and returns the transaction IDs. +func SendTransactionOnRandomPeer(t *testing.T, peers []*framework.Peer, addrBalance map[string]map[balance.Color]int64, numMessages int, sentValue int64) (txIds []string) { + counter := 0 + for i := 0; i < numMessages; i++ { + from := rand.Intn(len(peers)) + to := rand.Intn(len(peers)) + fail, txId := SendIotaTransaction(t, peers[from], peers[to], addrBalance, sentValue) + if fail { + i-- + counter++ + if counter >= maxRetry { + return + } + continue + } + + // attach tx id + txIds = append(txIds, txId) + + // let the transaction propagate + time.Sleep(3 * time.Second) + } + + return +} + +// SendIotaTransaction sends sentValue amount of IOTA tokens and remainders from and to a given peer and returns the fail flag and the transaction ID. +// Every peer sends and receives the transaction on the address of index 0. +func SendIotaTransaction(t *testing.T, from *framework.Peer, to *framework.Peer, addrBalance map[string]map[balance.Color]int64, sentValue int64) (fail bool, txId string) { + sigScheme := signaturescheme.ED25519(*from.Seed().KeyPair(0)) + inputAddr := from.Seed().Address(0) + outputAddr := to.Seed().Address(0) + + // prepare inputs + resp, err := from.GetUnspentOutputs([]string{inputAddr.String()}) + require.NoErrorf(t, err, "could not get unspent outputs on %s", from.String()) + + // abort if no unspent outputs + if len(resp.UnspentOutputs[0].OutputIDs) == 0 { + return true, "" + } + availableValue := resp.UnspentOutputs[0].OutputIDs[0].Balances[0].Value + + // abort if the balance is not enough + if availableValue < sentValue { + return true, "" + } + + out, err := transaction.OutputIDFromBase58(resp.UnspentOutputs[0].OutputIDs[0].ID) + require.NoErrorf(t, err, "invalid unspent outputs ID on %s", from.String()) + inputs := transaction.NewInputs([]transaction.OutputID{out}...) + + // prepare outputs + outmap := map[address.Address][]*balance.Balance{} + if inputAddr == outputAddr { + sentValue = availableValue + } + + // set balances + outmap[outputAddr] = []*balance.Balance{balance.New(balance.ColorIOTA, sentValue)} + outputs := transaction.NewOutputs(outmap) + + // handle remainder address + if availableValue > sentValue { + outputs.Add(inputAddr, []*balance.Balance{balance.New(balance.ColorIOTA, availableValue-sentValue)}) + } + + // sign transaction + txn := transaction.New(inputs, outputs).Sign(sigScheme) + + // send transaction + txId, err = from.SendTransaction(txn.Bytes()) + require.NoErrorf(t, err, "could not send transaction on %s", from.String()) + + addrBalance[inputAddr.String()][balance.ColorIOTA] -= sentValue + addrBalance[outputAddr.String()][balance.ColorIOTA] += sentValue + + return false, txId +} + +// SendColoredTransactionOnRandomPeer sends colored tokens on a random peer, saves the sent token amount to a map, and returns transaction IDs. +func SendColoredTransactionOnRandomPeer(t *testing.T, peers []*framework.Peer, addrBalance map[string]map[balance.Color]int64, numMessages int) (txIds []string) { + counter := 0 + for i := 0; i < numMessages; i++ { + from := rand.Intn(len(peers)) + to := rand.Intn(len(peers)) + fail, txId := SendColoredTransaction(t, peers[from], peers[to], addrBalance) + if fail { + i-- + counter++ + if counter >= maxRetry { + return + } + continue + } + + // attach tx id + txIds = append(txIds, txId) + + // let the transaction propagate + time.Sleep(3 * time.Second) + } + + return +} + +// SendColoredTransaction sends IOTA and colored tokens from and to a given peer and returns the fail flag and the transaction ID. +// 1. Get the first unspent outputs of `from` +// 2. Accumulate the token amount of the first unspent output +// 3. Send 50 IOTA tokens + [accumalate token amount - 50] new minted tokens to `to` +func SendColoredTransaction(t *testing.T, from *framework.Peer, to *framework.Peer, addrBalance map[string]map[balance.Color]int64) (fail bool, txId string) { + var sentValue int64 = 50 + var balanceList []*balance.Balance + sigScheme := signaturescheme.ED25519(*from.Seed().KeyPair(0)) + inputAddr := from.Seed().Address(0) + outputAddr := to.Seed().Address(0) + + // prepare inputs + resp, err := from.GetUnspentOutputs([]string{inputAddr.String()}) + require.NoErrorf(t, err, "could not get unspent outputs on %s", from.String()) + + // abort if no unspent outputs + if len(resp.UnspentOutputs[0].OutputIDs) == 0 { + return true, "" + } + + // calculate available token in the unspent output + var availableValue int64 = 0 + for _, b := range resp.UnspentOutputs[0].OutputIDs[0].Balances { + availableValue += b.Value + balanceList = append(balanceList, balance.New(getColorFromString(b.Color), (-1)*b.Value)) + } + + // abort if not enough tokens + if availableValue < sentValue { + return true, "" + } + + out, err := transaction.OutputIDFromBase58(resp.UnspentOutputs[0].OutputIDs[0].ID) + require.NoErrorf(t, err, "invalid unspent outputs ID on %s", from.String()) + inputs := transaction.NewInputs([]transaction.OutputID{out}...) + + // prepare outputs + outmap := map[address.Address][]*balance.Balance{} + + // set balances + outmap[outputAddr] = []*balance.Balance{balance.New(balance.ColorIOTA, sentValue)} + if availableValue > sentValue { + outmap[outputAddr] = append(outmap[outputAddr], balance.New(balance.ColorNew, availableValue-sentValue)) + } + outputs := transaction.NewOutputs(outmap) + + // sign transaction + txn := transaction.New(inputs, outputs).Sign(sigScheme) + + // send transaction + txId, err = from.SendTransaction(txn.Bytes()) + require.NoErrorf(t, err, "could not send transaction on %s", from.String()) + + // update balance list + balanceList = append(balanceList, outmap[outputAddr]...) + updateBalanceList(addrBalance, balanceList, inputAddr.String(), outputAddr.String(), txId) + + return false, txId +} + +// updateBalanceList updates the token amount map with given peers and balances. +// If the value of balance is negative, it is the balance to be deducted from peer from, else it is deposited to peer to. +// If the color is balance.ColorNew, it should be recolored with txId. +func updateBalanceList(addrBalance map[string]map[balance.Color]int64, balances []*balance.Balance, from, to, txId string) { + for _, b := range balances { + color := b.Color() + value := b.Value() + if value < 0 { + // deduct + addrBalance[from][color] += value + continue + } + // deposit + if color == balance.ColorNew { + addrBalance[to][getColorFromString(txId)] = value + continue + } + addrBalance[to][color] += value + } + return +} + +func getColorFromString(colorStr string) (color balance.Color) { + if colorStr == "IOTA" { + color = balance.ColorIOTA + } else { + t, _ := transaction.IDFromBase58(colorStr) + color, _, _ = balance.ColorFromBytes(t.Bytes()) + } + return +} + +// CheckBalances performs checks to make sure that all peers have the same ledger state. +func CheckBalances(t *testing.T, peers []*framework.Peer, addrBalance map[string]map[balance.Color]int64) { + for _, peer := range peers { + for addr, b := range addrBalance { + sum := make(map[balance.Color]int64) + resp, err := peer.GetUnspentOutputs([]string{addr}) + require.NoError(t, err) + assert.Equal(t, addr, resp.UnspentOutputs[0].Address) + + // calculate the balances of each colored coin + for _, unspents := range resp.UnspentOutputs[0].OutputIDs { + for _, respBalance := range unspents.Balances { + color := getColorFromString(respBalance.Color) + sum[color] += respBalance.Value + } + } + + // check balances + for color, value := range sum { + assert.Equal(t, b[color], value) + } + } + } +} + +// 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) { + for _, peer := range peers { + if checkSynchronized { + // check that the peer sees itself as synchronized + info, err := peer.Info() + require.NoError(t, err) + require.True(t, info.Synced) + } + + for _, txId := range transactionIDs { + resp, err := peer.GetTransactionByID(txId) + require.NoError(t, err) + + // check inclusion state + assert.True(t, resp.InclusionState.Confirmed) + assert.False(t, resp.InclusionState.Rejected) + } + } +} + // ShutdownNetwork shuts down the network and reports errors. func ShutdownNetwork(t *testing.T, n Shutdowner) { err := n.Shutdown() diff --git a/tools/integration-tests/tester/tests/value/main_test.go b/tools/integration-tests/tester/tests/value/main_test.go new file mode 100644 index 00000000..f5d00e21 --- /dev/null +++ b/tools/integration-tests/tester/tests/value/main_test.go @@ -0,0 +1,23 @@ +package value + +import ( + "os" + "testing" + + "github.com/iotaledger/goshimmer/tools/integration-tests/tester/framework" +) + +var f *framework.Framework + +// TestMain gets called by the test utility and is executed before any other test in this package. +// It is therefore used to initialize the integration testing framework. +func TestMain(m *testing.M) { + var err error + f, err = framework.Instance() + if err != nil { + panic(err) + } + + // call the tests + os.Exit(m.Run()) +} diff --git a/tools/integration-tests/tester/tests/value/value_test.go b/tools/integration-tests/tester/tests/value/value_test.go new file mode 100644 index 00000000..a5fd1ea0 --- /dev/null +++ b/tools/integration-tests/tester/tests/value/value_test.go @@ -0,0 +1,122 @@ +package value + +import ( + "testing" + "time" + + "github.com/iotaledger/goshimmer/dapps/valuetransfers" + "github.com/iotaledger/goshimmer/tools/integration-tests/tester/tests" + "github.com/stretchr/testify/require" +) + +// TestTransactionPersistence issues messages on random peers, restarts them and checks for persistence after restart. +func TestTransactionPersistence(t *testing.T) { + n, err := f.CreateNetwork("transaction_TestPersistence", 4, 2) + require.NoError(t, err) + defer tests.ShutdownNetwork(t, n) + + // wait for peers to change their state to synchronized + time.Sleep(5 * time.Second) + + // faucet node sends 100 IOTA tokens to all peers in the network + txIds, addrBalance := tests.SendTransactionFromFaucet(t, n.Peers(), 100) + + // wait for messages to be gossiped + time.Sleep(2 * valuetransfers.AverageNetworkDelay) + + // check whether the first issued transaction is available on all nodes, and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) + + // send value message randomly + randomTxIds := tests.SendTransactionOnRandomPeer(t, n.Peers(), addrBalance, 10, 100) + txIds = append(txIds, randomTxIds...) + + // wait for messages to be gossiped + time.Sleep(2 * valuetransfers.AverageNetworkDelay) + + // check whether all issued transactions are available on all nodes and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) + + // 3. stop all nodes + for _, peer := range n.Peers() { + err = peer.Stop() + require.NoError(t, err) + } + + // 4. start all nodes + for _, peer := range n.Peers() { + err = peer.Start() + require.NoError(t, err) + } + + // wait for peers to start + time.Sleep(20 * time.Second) + + // check whether all issued transactions are available on all nodes and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // 5. check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) +} + +// TestValueColoredPersistence issues colored tokens on random peers, restarts them and checks for persistence after restart. +func TestValueColoredPersistence(t *testing.T) { + n, err := f.CreateNetwork("valueColor_TestPersistence", 4, 2) + require.NoError(t, err) + defer tests.ShutdownNetwork(t, n) + + // wait for peers to change their state to synchronized + time.Sleep(5 * time.Second) + + // master node sends funds to all peers in the network + txIds, addrBalance := tests.SendTransactionFromFaucet(t, n.Peers(), 100) + + // wait for messages to be gossiped + time.Sleep(2 * valuetransfers.AverageNetworkDelay) + + // check whether the transactions are available on all nodes, and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) + + // send funds around + randomTxIds := tests.SendColoredTransactionOnRandomPeer(t, n.Peers(), addrBalance, 10) + txIds = append(txIds, randomTxIds...) + + // wait for value messages to be gossiped + time.Sleep(2 * valuetransfers.AverageNetworkDelay) + + // check whether all issued transactions are persistently available on all nodes, and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) + + // stop all nodes + for _, peer := range n.Peers() { + err = peer.Stop() + require.NoError(t, err) + } + + // start all nodes + for _, peer := range n.Peers() { + err = peer.Start() + require.NoError(t, err) + } + + // wait for peers to start + time.Sleep(20 * time.Second) + + // check whether all issued transactions are persistently available on all nodes, and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // 5. check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) +} -- GitLab