Skip to content
Snippets Groups Projects
Select Git revision
  • 9d61542a211dcee6752079e6f2899b369a85dda5
  • without_tipselection default
  • develop protected
  • fix/grafana-local-dashboard
  • wasp
  • fix/dashboard-explorer-freeze
  • master
  • feat/timerqueue
  • test/sync_debug_and_650
  • feat/sync_revamp_inv
  • wip/sync
  • tool/db-recovery
  • portcheck/fix
  • fix/synchronization
  • feat/new-dashboard-analysis
  • feat/refactored-analysis-dashboard
  • feat/new-analysis-dashboard
  • test/demo-prometheus-fpc
  • prometheus_metrics
  • wip/analysis-server
  • merge/fpc-test-value-transfer
  • v0.2.2
  • v0.2.1
  • v0.2.0
  • v0.1.3
  • v0.1.2
  • v0.1.1
  • v0.1.0
28 results

testutil.go

Blame
  • testutil.go 12.75 KiB
    package tests
    
    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
    	id              string
    	data            []byte
    	issuerPublicKey string
    }
    
    type Shutdowner interface {
    	Shutdown() error
    }
    
    // SendDataMessagesOnRandomPeer sends data messages on a random peer and saves the sent message to a map.
    func SendDataMessagesOnRandomPeer(t *testing.T, peers []*framework.Peer, numMessages int, idsMap ...map[string]DataMessageSent) map[string]DataMessageSent {
    	var ids map[string]DataMessageSent
    	if len(idsMap) > 0 {
    		ids = idsMap[0]
    	} else {
    		ids = make(map[string]DataMessageSent, numMessages)
    	}
    
    	for i := 0; i < numMessages; i++ {
    		data := []byte(fmt.Sprintf("Test%d", i))
    
    		peer := peers[rand.Intn(len(peers))]
    		id, sent := SendDataMessage(t, peer, data, i)
    
    		ids[id] = sent
    	}
    
    	return ids
    }
    
    // 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())
    
    	sent := DataMessageSent{
    		number: number,
    		id:     id,
    		// save payload to be able to compare API response
    		data:            payload.NewData(data).Bytes(),
    		issuerPublicKey: peer.Identity.PublicKey().String(),
    	}
    	return id, sent
    }
    
    // CheckForMessageIds performs checks to make sure that all peers received all given messages defined in ids.
    func CheckForMessageIds(t *testing.T, peers []*framework.Peer, ids map[string]DataMessageSent, checkSynchronized bool) {
    	var idsSlice []string
    	for id := range ids {
    		idsSlice = append(idsSlice, id)
    	}
    
    	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)
    		}
    
    		resp, err := peer.FindMessageByID(idsSlice)
    		require.NoError(t, err)
    
    		// check that all messages are present in response
    		respIDs := make([]string, len(resp.Messages))
    		for i, msg := range resp.Messages {
    			respIDs[i] = msg.ID
    		}
    		assert.ElementsMatchf(t, idsSlice, respIDs, "messages do not match sent in %s", peer.String())
    
    		// check for general information
    		for _, msg := range resp.Messages {
    			msgSent := ids[msg.ID]
    
    			assert.Equalf(t, msgSent.issuerPublicKey, msg.IssuerPublicKey, "messageID=%s, issuer=%s not correct issuer in %s.", msgSent.id, msgSent.issuerPublicKey, peer.String())
    			assert.Equalf(t, msgSent.data, msg.Payload, "messageID=%s, issuer=%s data not equal in %s.", msgSent.id, msgSent.issuerPublicKey, peer.String())
    			assert.Truef(t, msg.Metadata.Solid, "messageID=%s, issuer=%s not solid in %s.", msgSent.id, msgSent.issuerPublicKey, peer.String())
    		}
    	}
    }
    
    // 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()
    	require.NoError(t, err)
    }