From 4877cebd8d7484744a2dd5df87c4552aa7993e74 Mon Sep 17 00:00:00 2001 From: jkrvivian <jkrvivian@gmail.com> Date: Thu, 11 Jun 2020 14:52:35 +0800 Subject: [PATCH] Add colored tokens test --- pluginmgr/core/plugins.go | 2 +- .../tester/framework/parameters.go | 2 - .../tester/tests/testutil.go | 258 +++++++++++++----- .../tester/tests/value/value_test.go | 88 +++++- 4 files changed, 269 insertions(+), 81 deletions(-) diff --git a/pluginmgr/core/plugins.go b/pluginmgr/core/plugins.go index 9bc1f4db..eb4ff3b6 100644 --- a/pluginmgr/core/plugins.go +++ b/pluginmgr/core/plugins.go @@ -17,8 +17,8 @@ import ( "github.com/iotaledger/goshimmer/plugins/metrics" "github.com/iotaledger/goshimmer/plugins/portcheck" "github.com/iotaledger/goshimmer/plugins/profiling" - "github.com/iotaledger/goshimmer/plugins/testsnapshots" "github.com/iotaledger/goshimmer/plugins/sync" + "github.com/iotaledger/goshimmer/plugins/testsnapshots" "github.com/iotaledger/hive.go/node" ) diff --git a/tools/integration-tests/tester/framework/parameters.go b/tools/integration-tests/tester/framework/parameters.go index 7041a77b..fb4e1435 100644 --- a/tools/integration-tests/tester/framework/parameters.go +++ b/tools/integration-tests/tester/framework/parameters.go @@ -25,8 +25,6 @@ const ( dkgMaxTries = 50 exitStatusSuccessful = 0 - - SnapShotFirstAddress = "JaMauTaTSVBNc13edCCvBK9fZxZ1KKW5fXegT1B7N9jY" ) var ( diff --git a/tools/integration-tests/tester/tests/testutil.go b/tools/integration-tests/tester/tests/testutil.go index 5a494501..d998da35 100644 --- a/tools/integration-tests/tester/tests/testutil.go +++ b/tools/integration-tests/tester/tests/testutil.go @@ -100,94 +100,143 @@ func CheckForMessageIds(t *testing.T, peers []*framework.Peer, ids map[string]Da } } -// SendValueMessage sends a value message on a given peer and returns the transaction ID. -func SendValueMessagesOnFaucet(t *testing.T, peers []*framework.Peer) (txId string, addrBalance map[string]int64) { +// SendValueMessagesOnFaucet sends funds to peers from the faucet and returns the transaction ID. +func SendValueMessagesOnFaucet(t *testing.T, peers []*framework.Peer) (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) + addrBalance[addr][balance.ColorIOTA] = 0 + } + faucetPeer := peers[0] - sigScheme := signaturescheme.ED25519(*faucetPeer.Seed().KeyPair(0)) - // send the same funds to the original address - remainAddr := faucetPeer.Seed().Address(0) - var sentValue int64 = 0 + faucetAddrStr := faucetPeer.Seed().Address(0).String() - // prepare inputs - unspentOutputs, err := faucetPeer.GetUnspentOutputs([]string{framework.SnapShotFirstAddress}) + // get faucet balances + unspentOutputs, err := faucetPeer.GetUnspentOutputs([]string{faucetAddrStr}) require.NoErrorf(t, err, "Could not get unspent outputs on %s", faucetPeer.String()) - value := unspentOutputs.UnspentOutputs[0].OutputIDs[0].Balances[0].Value + 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 := SendIotaValueMessages(t, faucetPeer, peers[i], addrBalance) + require.False(t, fail) + txIds = append(txIds, txId) + + // let the transaction propagate + time.Sleep(1 * time.Second) + } + return +} + +// SendValueMessagesOnRandomPeer sends IOTA tokens on random peer and saves the sent message token to a map. +func SendValueMessagesOnRandomPeer(t *testing.T, peers []*framework.Peer, addrBalance map[string]map[balance.Color]int64, numMessages int) (txIds []string) { + for i := 0; i < numMessages; i++ { + from := rand.Intn(len(peers)) + to := rand.Intn(len(peers)) + fail, txId := SendIotaValueMessages(t, peers[from], peers[to], addrBalance) + if fail { + i-- + continue + } + + // attach tx id + txIds = append(txIds, txId) - out, err := transaction.OutputIDFromBase58(unspentOutputs.UnspentOutputs[0].OutputIDs[0].ID) - require.NoErrorf(t, err, "Invalid unspent outputs ID on %s", faucetPeer.String()) + // let the transaction propagate + time.Sleep(1 * time.Second) + } + + return +} + +// SendIotaValueMessages sends IOTA token from and to a given peer and returns the transaction ID. +// The same addresses are used in each round +func SendIotaValueMessages(t *testing.T, from *framework.Peer, to *framework.Peer, addrBalance map[string]map[balance.Color]int64) (fail bool, txId string) { + var sentValue int64 = 100 + sigScheme := signaturescheme.ED25519(*from.Seed().KeyPair(0)) + inputAddr := from.Seed().Address(0) + outputAddr := to.Seed().Address(0) + + // abort if no unspent outputs + if addrBalance[inputAddr.String()][balance.ColorIOTA] < sentValue { + return true, "" + } + + // prepare inputs + resp, err := from.GetUnspentOutputs([]string{inputAddr.String()}) + require.NoErrorf(t, err, "Could not get unspent outputs on %s", from.String()) + availableValue := resp.UnspentOutputs[0].OutputIDs[0].Balances[0].Value + + 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{} - addrBalance = make(map[string]int64) - - // send funds to other peers, skip faucet node - for i := 1; i < len(peers); i++ { - addr := peers[i].Seed().Address(0) - - // set balances - balances := []*balance.Balance{balance.New(balance.ColorIOTA, 100)} - outmap[addr] = balances - sentValue += 100 - // set return map - addrBalance[addr.String()] = 100 + if inputAddr == outputAddr { + sentValue = availableValue } + + // set balances + outmap[outputAddr] = []*balance.Balance{balance.New(balance.ColorIOTA, sentValue)} outputs := transaction.NewOutputs(outmap) // handle remain address - outputs.Add(remainAddr, []*balance.Balance{balance.New(balance.ColorIOTA, value-sentValue)}) - addrBalance[remainAddr.String()] = value - sentValue + 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 = faucetPeer.SendTransaction(txn.Bytes()) - require.NoErrorf(t, err, "Could not send transaction on %s", faucetPeer.String()) + txId, err = from.SendTransaction(txn.Bytes()) + require.NoErrorf(t, err, "Could not send transaction on %s", from.String()) - return -} + addrBalance[inputAddr.String()][balance.ColorIOTA] -= sentValue + addrBalance[outputAddr.String()][balance.ColorIOTA] += sentValue -// SendDataMessagesOnRandomPeer sends data messages on a random peer and saves the sent message to a map. -func SendValueMessagesOnRandomPeer(t *testing.T, peers []*framework.Peer, addrBalance map[string]int64, numMessages int) { - addrMap := make(map[int]string) - for i, p := range peers { - addrMap[i] = p.Seed().Address(0).String() - } + return false, txId +} +// SendColoredValueMessagesOnRandomPeer sends colored token on a random peer and saves the sent token to a map. +func SendColoredValueMessagesOnRandomPeer(t *testing.T, peers []*framework.Peer, addrBalance map[string]map[balance.Color]int64, numMessages int) (txIds []string) { for i := 0; i < numMessages; i++ { from := rand.Intn(len(peers)) to := rand.Intn(len(peers)) - sentValue := SendValueMessages(t, peers[from], peers[to], addrBalance) - - // update balance - addrBalance[addrMap[from]] -= sentValue - addrBalance[addrMap[to]] += sentValue - - if sentValue == 0 { + fail, txId := SendColoredValueMessage(t, peers[from], peers[to], addrBalance) + if fail { i-- + continue } - time.Sleep(5 * time.Second) + + // attach tx id + txIds = append(txIds, txId) + + // let the transaction propagate + time.Sleep(1 * time.Second) } return } -// SendValueMessage sends a value message from and to a given peer and returns the transaction ID. -// for testing convenience, the same addresses are used in each round -func SendValueMessages(t *testing.T, from *framework.Peer, to *framework.Peer, addrBalance map[string]int64) (sent int64) { +// SendColoredValueMessage sends a colored tokens from and to a given peer and returns the transaction ID. +// The same addresses are used in each round +func SendColoredValueMessage(t *testing.T, from *framework.Peer, to *framework.Peer, addrBalance map[string]map[balance.Color]int64) (fail bool, txId string) { sigScheme := signaturescheme.ED25519(*from.Seed().KeyPair(0)) inputAddr := from.Seed().Address(0) outputAddr := to.Seed().Address(0) - // skip if from peer has no balance - if addrBalance[inputAddr.String()] == 0 { - return 0 - } // prepare inputs resp, err := from.GetUnspentOutputs([]string{inputAddr.String()}) require.NoErrorf(t, err, "Could not get unspent outputs on %s", from.String()) - value := resp.UnspentOutputs[0].OutputIDs[0].Balances[0].Value + + // abort if no unspent outputs + if len(resp.UnspentOutputs[0].OutputIDs) == 0 { + return true, "" + } out, err := transaction.OutputIDFromBase58(resp.UnspentOutputs[0].OutputIDs[0].ID) require.NoErrorf(t, err, "Invalid unspent outputs ID on %s", from.String()) @@ -195,24 +244,105 @@ func SendValueMessages(t *testing.T, from *framework.Peer, to *framework.Peer, a // prepare outputs outmap := map[address.Address][]*balance.Balance{} + bs := []*balance.Balance{} + var outputs *transaction.Outputs + var availableIOTA int64 + availableBalances := resp.UnspentOutputs[0].OutputIDs[0].Balances + newColor := false // set balances - balances := []*balance.Balance{balance.New(balance.ColorIOTA, value)} - outmap[outputAddr] = balances - outputs := transaction.NewOutputs(outmap) + if len(availableBalances) > 1 { + // the balances contain more than one color, send it all + for _, b := range availableBalances { + value := b.Value + color := getColorFromString(b.Color) + bs = append(bs, balance.New(color, value)) + + // update balance list + addrBalance[inputAddr.String()][color] -= value + if _, ok := addrBalance[outputAddr.String()][color]; ok { + addrBalance[outputAddr.String()][color] += value + } else { + addrBalance[outputAddr.String()][color] = value + } + } + } else { + // create new colored token if inputs only contain IOTA + // half of availableIota tokens remain IOTA, else get recolored + newColor = true + availableIOTA = availableBalances[0].Value + + bs = append(bs, balance.New(balance.ColorIOTA, availableIOTA/2)) + bs = append(bs, balance.New(balance.ColorNew, availableIOTA/2)) + + // update balance list + addrBalance[inputAddr.String()][balance.ColorIOTA] -= availableIOTA + addrBalance[outputAddr.String()][balance.ColorIOTA] += availableIOTA / 2 + } + outmap[outputAddr] = bs + + outputs = transaction.NewOutputs(outmap) // sign transaction txn := transaction.New(inputs, outputs).Sign(sigScheme) // send transaction - _, err = from.SendTransaction(txn.Bytes()) + txId, err = from.SendTransaction(txn.Bytes()) require.NoErrorf(t, err, "Could not send transaction on %s", from.String()) - return value + // FIXME: the new color should be txn ID + if newColor { + if _, ok := addrBalance[outputAddr.String()][balance.ColorNew]; ok { + addrBalance[outputAddr.String()][balance.ColorNew] += availableIOTA / 2 + } else { + addrBalance[outputAddr.String()][balance.ColorNew] = availableIOTA / 2 + } + //addrBalance[outputAddr.String()][getColorFromString(txId)] = availableIOTA / 2 + } + return false, txId +} + +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]int64, checkSynchronized bool) { +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 color coin + for _, unspents := range resp.UnspentOutputs[0].OutputIDs { + for _, b := range unspents.Balances { + color := getColorFromString(b.Color) + if _, ok := sum[color]; ok { + sum[color] += b.Value + } else { + sum[color] = b.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 @@ -221,18 +351,12 @@ func CheckBalances(t *testing.T, peers []*framework.Peer, addrBalance map[string require.True(t, info.Synced) } - for addr, b := range addrBalance { - var iotas int64 = 0 - resp, err := peer.GetUnspentOutputs([]string{addr}) + for _, txId := range transactionIDs { + resp, err := peer.GetTransactionByID(txId) require.NoError(t, err) - assert.Equal(t, addr, resp.UnspentOutputs[0].Address) - // calculate the total iotas of an address - for _, unspents := range resp.UnspentOutputs[0].OutputIDs { - iotas += unspents.Balances[0].Value - assert.Equal(t, balance.ColorIOTA.String(), unspents.Balances[0].Color) - } - assert.Equal(t, b, iotas) + // check inclusion state + assert.True(t, resp.InclusionState.Confirmed) } } } diff --git a/tools/integration-tests/tester/tests/value/value_test.go b/tools/integration-tests/tester/tests/value/value_test.go index 9696ac26..48777a8c 100644 --- a/tools/integration-tests/tester/tests/value/value_test.go +++ b/tools/integration-tests/tester/tests/value/value_test.go @@ -9,31 +9,38 @@ import ( ) // TestPersistence issues messages on random peers, restarts them and checks for persistence after restart. -func TestPersistence(t *testing.T) { - n, err := f.CreateNetwork("value_TestPersistence", 4, 2) +func TestValueIotaPersistence(t *testing.T) { + n, err := f.CreateNetwork("valueIota_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 issue transaction to all peers in the network - _, addrBalance := tests.SendValueMessagesOnFaucet(t, n.Peers()) + // master node sends funds to all peers in the network + txIds, addrBalance := tests.SendValueMessagesOnFaucet(t, n.Peers()) // wait for messages to be gossiped time.Sleep(10 * time.Second) - // 2. check whether all issued messages are available on all nodes - tests.CheckBalances(t, n.Peers(), addrBalance, true) + // 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 - tests.SendValueMessagesOnRandomPeer(t, n.Peers(), addrBalance, 10) + randomTxIds := tests.SendValueMessagesOnRandomPeer(t, n.Peers(), addrBalance, 10) + txIds = append(txIds, randomTxIds...) // wait for messages to be gossiped time.Sleep(10 * time.Second) - // check whether all issued messages are available on all nodes - tests.CheckBalances(t, n.Peers(), addrBalance, true) + // 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) // 3. stop all nodes for _, peer := range n.Peers() { @@ -50,6 +57,65 @@ func TestPersistence(t *testing.T) { // wait for peers to start time.Sleep(10 * time.Second) - // 5. check whether all issued messages are persistently available on all nodes - tests.CheckBalances(t, n.Peers(), addrBalance, true) + // 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) +} + +// TestColoredPersistence 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.SendValueMessagesOnFaucet(t, n.Peers()) + + // 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) + + // check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) + + // send funds around + randomTxIds := tests.SendColoredValueMessagesOnRandomPeer(t, n.Peers(), addrBalance, 10) + txIds = append(txIds, randomTxIds...) + + // 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) + + // 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(10 * 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