arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/api/api_test.go
api/api_test.goRaw
   1package api
   2
   3import (
   4	"bytes"
   5	"context"
   6	"encoding/json"
   7	"errors"
   8	"fmt"
   9	"io"
  10	"io/ioutil"
  11	"math"
  12	"math/big"
  13	"net/http"
  14	"os"
  15	"sort"
  16	"strconv"
  17	"testing"
  18	"time"
  19
  20	ethCommon "github.com/ethereum/go-ethereum/common"
  21	swagger "github.com/getkin/kin-openapi/openapi3filter"
  22	"github.com/gin-gonic/gin"
  23	"github.com/hermeznetwork/hermez-node/common"
  24	"github.com/hermeznetwork/hermez-node/db"
  25	"github.com/hermeznetwork/hermez-node/db/historydb"
  26	"github.com/hermeznetwork/hermez-node/db/l2db"
  27	"github.com/hermeznetwork/hermez-node/db/statedb"
  28	"github.com/hermeznetwork/hermez-node/eth"
  29	"github.com/hermeznetwork/hermez-node/log"
  30	"github.com/hermeznetwork/hermez-node/test"
  31	"github.com/iden3/go-iden3-crypto/babyjub"
  32	"github.com/mitchellh/copystructure"
  33	"github.com/stretchr/testify/assert"
  34	"github.com/stretchr/testify/require"
  35)
  36
  37const apiPort = ":4010"
  38const apiURL = "http://localhost" + apiPort + "/"
  39
  40type testCommon struct {
  41	blocks           []common.Block
  42	tokens           []tokenAPI
  43	batches          []common.Batch
  44	usrAddr          string
  45	usrBjj           string
  46	accs             []common.Account
  47	usrTxs           []historyTxAPI
  48	allTxs           []historyTxAPI
  49	exits            []exitAPI
  50	usrExits         []exitAPI
  51	poolTxsToSend    []receivedPoolTx
  52	poolTxsToReceive []sendPoolTx
  53	router           *swagger.Router
  54}
  55
  56// TxSortFields represents the fields needed to sort L1 and L2 transactions
  57type txSortFields struct {
  58	BatchNum *common.BatchNum
  59	Position int
  60}
  61
  62// TxSortFielder is a interface that allows sorting L1 and L2 transactions in a combined way
  63type txSortFielder interface {
  64	SortFields() txSortFields
  65	L1() *common.L1Tx
  66	L2() *common.L2Tx
  67}
  68
  69// TxsSort array of TxSortFielder
  70type txsSort []txSortFielder
  71
  72func (t txsSort) Len() int      { return len(t) }
  73func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
  74func (t txsSort) Less(i, j int) bool {
  75	// i not forged yet
  76	isf := t[i].SortFields()
  77	jsf := t[j].SortFields()
  78	if isf.BatchNum == nil {
  79		if jsf.BatchNum != nil { // j is already forged
  80			return false
  81		}
  82		// Both aren't forged, is i in a smaller position?
  83		return isf.Position < jsf.Position
  84	}
  85	// i is forged
  86	if jsf.BatchNum == nil {
  87		return false // j is not forged
  88	}
  89	// Both are forged
  90	if *isf.BatchNum == *jsf.BatchNum {
  91		// At the same batch,  is i in a smaller position?
  92		return isf.Position < jsf.Position
  93	}
  94	// At different batches, is i in a smaller batch?
  95	return *isf.BatchNum < *jsf.BatchNum
  96}
  97
  98type wrappedL1 common.L1Tx
  99
 100// SortFields implements TxSortFielder
 101func (tx *wrappedL1) SortFields() txSortFields {
 102	return txSortFields{
 103		BatchNum: tx.BatchNum,
 104		Position: tx.Position,
 105	}
 106}
 107
 108// L1 implements TxSortFielder
 109func (tx *wrappedL1) L1() *common.L1Tx {
 110	l1tx := common.L1Tx(*tx)
 111	return &l1tx
 112}
 113
 114// L2 implements TxSortFielder
 115func (tx *wrappedL1) L2() *common.L2Tx { return nil }
 116
 117type wrappedL2 common.L2Tx
 118
 119// SortFields implements TxSortFielder
 120func (tx *wrappedL2) SortFields() txSortFields {
 121	return txSortFields{
 122		BatchNum: &tx.BatchNum,
 123		Position: tx.Position,
 124	}
 125}
 126
 127// L1 implements TxSortFielder
 128func (tx *wrappedL2) L1() *common.L1Tx { return nil }
 129
 130// L2 implements TxSortFielder
 131func (tx *wrappedL2) L2() *common.L2Tx {
 132	l2tx := common.L2Tx(*tx)
 133	return &l2tx
 134}
 135
 136var tc testCommon
 137var config configAPI
 138
 139func TestMain(m *testing.M) {
 140	// Init swagger
 141	router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
 142	// Init DBs
 143	// HistoryDB
 144	pass := os.Getenv("POSTGRES_PASS")
 145	database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
 146	if err != nil {
 147		panic(err)
 148	}
 149	hdb := historydb.NewHistoryDB(database)
 150	err = hdb.Reorg(-1)
 151	if err != nil {
 152		panic(err)
 153	}
 154	// StateDB
 155	dir, err := ioutil.TempDir("", "tmpdb")
 156	if err != nil {
 157		panic(err)
 158	}
 159	defer func() {
 160		if err := os.RemoveAll(dir); err != nil {
 161			panic(err)
 162		}
 163	}()
 164	sdb, err := statedb.NewStateDB(dir, statedb.TypeTxSelector, 0)
 165	if err != nil {
 166		panic(err)
 167	}
 168	// L2DB
 169	l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
 170	test.CleanL2DB(l2DB.DB())
 171
 172	config.RollupConstants.ExchangeMultiplier = eth.RollupConstExchangeMultiplier
 173	config.RollupConstants.ExitIdx = eth.RollupConstExitIDx
 174	config.RollupConstants.ReservedIdx = eth.RollupConstReservedIDx
 175	config.RollupConstants.LimitLoadAmount, _ = new(big.Int).SetString("340282366920938463463374607431768211456", 10)
 176	config.RollupConstants.LimitL2TransferAmount, _ = new(big.Int).SetString("6277101735386680763835789423207666416102355444464034512896", 10)
 177	config.RollupConstants.LimitTokens = eth.RollupConstLimitTokens
 178	config.RollupConstants.L1CoordinatorTotalBytes = eth.RollupConstL1CoordinatorTotalBytes
 179	config.RollupConstants.L1UserTotalBytes = eth.RollupConstL1UserTotalBytes
 180	config.RollupConstants.MaxL1UserTx = eth.RollupConstMaxL1UserTx
 181	config.RollupConstants.MaxL1Tx = eth.RollupConstMaxL1Tx
 182	config.RollupConstants.InputSHAConstantBytes = eth.RollupConstInputSHAConstantBytes
 183	config.RollupConstants.NumBuckets = eth.RollupConstNumBuckets
 184	config.RollupConstants.MaxWithdrawalDelay = eth.RollupConstMaxWithdrawalDelay
 185	var rollupPublicConstants eth.RollupPublicConstants
 186	rollupPublicConstants.AbsoluteMaxL1L2BatchTimeout = 240
 187	rollupPublicConstants.HermezAuctionContract = ethCommon.HexToAddress("0x500D1d6A4c7D8Ae28240b47c8FCde034D827fD5e")
 188	rollupPublicConstants.HermezGovernanceDAOAddress = ethCommon.HexToAddress("0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4")
 189	rollupPublicConstants.SafetyAddress = ethCommon.HexToAddress("0xE5904695748fe4A84b40b3fc79De2277660BD1D3")
 190	rollupPublicConstants.TokenHEZ = ethCommon.HexToAddress("0xf784709d2317D872237C4bC22f867d1BAe2913AB")
 191	rollupPublicConstants.WithdrawDelayerContract = ethCommon.HexToAddress("0xD6C850aeBFDC46D7F4c207e445cC0d6B0919BDBe")
 192	var verifier eth.RollupVerifierStruct
 193	verifier.MaxTx = 512
 194	verifier.NLevels = 32
 195	rollupPublicConstants.Verifiers = append(rollupPublicConstants.Verifiers, verifier)
 196
 197	var auctionConstants eth.AuctionConstants
 198	auctionConstants.BlocksPerSlot = 40
 199	auctionConstants.GenesisBlockNum = 100
 200	auctionConstants.GovernanceAddress = ethCommon.HexToAddress("0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4")
 201	auctionConstants.InitialMinimalBidding, _ = new(big.Int).SetString("10000000000000000000", 10)
 202	auctionConstants.HermezRollup = ethCommon.HexToAddress("0xEa960515F8b4C237730F028cBAcF0a28E7F45dE0")
 203	auctionConstants.TokenHEZ = ethCommon.HexToAddress("0xf784709d2317D872237C4bC22f867d1BAe2913AB")
 204
 205	var wdelayerConstants eth.WDelayerConstants
 206	wdelayerConstants.HermezRollup = ethCommon.HexToAddress("0xEa960515F8b4C237730F028cBAcF0a28E7F45dE0")
 207	wdelayerConstants.MaxEmergencyModeTime = uint64(1000000)
 208	wdelayerConstants.MaxWithdrawalDelay = uint64(10000000)
 209
 210	config.RollupConstants.PublicConstants = rollupPublicConstants
 211	config.AuctionConstants = auctionConstants
 212	config.WDelayerConstants = wdelayerConstants
 213
 214	// Init API
 215	api := gin.Default()
 216	if err := SetAPIEndpoints(
 217		true,
 218		true,
 219		api,
 220		hdb,
 221		sdb,
 222		l2DB,
 223		&config,
 224	); err != nil {
 225		panic(err)
 226	}
 227	// Start server
 228	server := &http.Server{Addr: apiPort, Handler: api}
 229	go func() {
 230		if err := server.ListenAndServe(); err != nil &&
 231			err != http.ErrServerClosed {
 232			panic(err)
 233		}
 234	}()
 235
 236	// Populate DBs
 237	// Clean DB
 238	err = h.Reorg(0)
 239	if err != nil {
 240		panic(err)
 241	}
 242	// Gen blocks and add them to DB
 243	const nBlocks = 5
 244	blocks := test.GenBlocks(1, nBlocks+1)
 245	err = h.AddBlocks(blocks)
 246	if err != nil {
 247		panic(err)
 248	}
 249	// Gen tokens and add them to DB
 250	const nTokens = 10
 251	tokens := test.GenTokens(nTokens, blocks)
 252	err = h.AddTokens(tokens)
 253	if err != nil {
 254		panic(err)
 255	}
 256	// Set token value
 257	tokensUSD := []tokenAPI{}
 258	for i, tkn := range tokens {
 259		token := tokenAPI{
 260			TokenID:     tkn.TokenID,
 261			EthBlockNum: tkn.EthBlockNum,
 262			EthAddr:     tkn.EthAddr,
 263			Name:        tkn.Name,
 264			Symbol:      tkn.Symbol,
 265			Decimals:    tkn.Decimals,
 266		}
 267		// Set value of 50% of the tokens
 268		if i%2 != 0 {
 269			value := float64(i) * 1.234567
 270			now := time.Now().UTC()
 271			token.USD = &value
 272			token.USDUpdate = &now
 273			err = h.UpdateTokenValue(token.Symbol, value)
 274			if err != nil {
 275				panic(err)
 276			}
 277		}
 278		tokensUSD = append(tokensUSD, token)
 279	}
 280	// Gen batches and add them to DB
 281	const nBatches = 10
 282	batches := test.GenBatches(nBatches, blocks)
 283	err = h.AddBatches(batches)
 284	if err != nil {
 285		panic(err)
 286	}
 287	// Gen accounts and add them to HistoryDB and StateDB
 288	const totalAccounts = 40
 289	const userAccounts = 4
 290	usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
 291	privK := babyjub.NewRandPrivKey()
 292	usrBjj := privK.Public()
 293	accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
 294	err = h.AddAccounts(accs)
 295	if err != nil {
 296		panic(err)
 297	}
 298	for i := 0; i < len(accs); i++ {
 299		if _, err := s.CreateAccount(accs[i].Idx, &accs[i]); err != nil {
 300			panic(err)
 301		}
 302	}
 303	// Gen exits and add them to DB
 304	const totalExits = 40
 305	exits := test.GenExitTree(totalExits, batches, accs)
 306	err = h.AddExitTree(exits)
 307	if err != nil {
 308		panic(err)
 309	}
 310	// Gen L1Txs and add them to DB
 311	const totalL1Txs = 40
 312	const userL1Txs = 4
 313	usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
 314	// Gen L2Txs and add them to DB
 315	const totalL2Txs = 20
 316	const userL2Txs = 4
 317	usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
 318	// Order txs
 319	sortedTxs := []txSortFielder{}
 320	for i := 0; i < len(usrL1Txs); i++ {
 321		wL1 := wrappedL1(usrL1Txs[i])
 322		sortedTxs = append(sortedTxs, &wL1)
 323	}
 324	for i := 0; i < len(othrL1Txs); i++ {
 325		wL1 := wrappedL1(othrL1Txs[i])
 326		sortedTxs = append(sortedTxs, &wL1)
 327	}
 328	for i := 0; i < len(usrL2Txs); i++ {
 329		wL2 := wrappedL2(usrL2Txs[i])
 330		sortedTxs = append(sortedTxs, &wL2)
 331	}
 332	for i := 0; i < len(othrL2Txs); i++ {
 333		wL2 := wrappedL2(othrL2Txs[i])
 334		sortedTxs = append(sortedTxs, &wL2)
 335	}
 336	sort.Sort(txsSort(sortedTxs))
 337	// Add txs to DB and prepare them for test commons
 338	usrTxs := []historyTxAPI{}
 339	allTxs := []historyTxAPI{}
 340	getTimestamp := func(blockNum int64) time.Time {
 341		for i := 0; i < len(blocks); i++ {
 342			if blocks[i].EthBlockNum == blockNum {
 343				return blocks[i].Timestamp
 344			}
 345		}
 346		panic("timesamp not found")
 347	}
 348	getToken := func(id common.TokenID) tokenAPI {
 349		for i := 0; i < len(tokensUSD); i++ {
 350			if tokensUSD[i].TokenID == id {
 351				return tokensUSD[i]
 352			}
 353		}
 354		panic("token not found")
 355	}
 356	getTokenByIdx := func(idx common.Idx) tokenAPI {
 357		for _, acc := range accs {
 358			if idx == acc.Idx {
 359				return getToken(acc.TokenID)
 360			}
 361		}
 362		panic("token not found")
 363	}
 364	usrIdxs := []string{}
 365	for _, acc := range accs {
 366		if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
 367			for _, token := range tokens {
 368				if token.TokenID == acc.TokenID {
 369					usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
 370				}
 371			}
 372		}
 373	}
 374	isUsrTx := func(tx historyTxAPI) bool {
 375		for _, idx := range usrIdxs {
 376			if tx.FromIdx != nil && *tx.FromIdx == idx {
 377				return true
 378			}
 379			if tx.ToIdx == idx {
 380				return true
 381			}
 382		}
 383		return false
 384	}
 385	for _, genericTx := range sortedTxs {
 386		l1 := genericTx.L1()
 387		l2 := genericTx.L2()
 388		if l1 != nil {
 389			// Add L1 tx to DB
 390			err = h.AddL1Txs([]common.L1Tx{*l1})
 391			if err != nil {
 392				panic(err)
 393			}
 394			// L1Tx ==> historyTxAPI
 395			token := getToken(l1.TokenID)
 396			tx := historyTxAPI{
 397				IsL1:      "L1",
 398				TxID:      l1.TxID,
 399				Type:      l1.Type,
 400				Position:  l1.Position,
 401				ToIdx:     idxToHez(l1.ToIdx, token.Symbol),
 402				Amount:    l1.Amount.String(),
 403				BatchNum:  l1.BatchNum,
 404				Timestamp: getTimestamp(l1.EthBlockNum),
 405				L1Info: &l1Info{
 406					ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
 407					UserOrigin:      l1.UserOrigin,
 408					FromEthAddr:     ethAddrToHez(l1.FromEthAddr),
 409					FromBJJ:         bjjToString(l1.FromBJJ),
 410					LoadAmount:      l1.LoadAmount.String(),
 411					EthBlockNum:     l1.EthBlockNum,
 412				},
 413				Token: token,
 414			}
 415			if l1.FromIdx != 0 {
 416				idxStr := idxToHez(l1.FromIdx, token.Symbol)
 417				tx.FromIdx = &idxStr
 418			}
 419			if token.USD != nil {
 420				af := new(big.Float).SetInt(l1.Amount)
 421				amountFloat, _ := af.Float64()
 422				usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
 423				tx.HistoricUSD = &usd
 424				laf := new(big.Float).SetInt(l1.LoadAmount)
 425				loadAmountFloat, _ := laf.Float64()
 426				loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals))
 427				tx.L1Info.HistoricLoadAmountUSD = &loadUSD
 428			}
 429			allTxs = append(allTxs, tx)
 430			if isUsrTx(tx) {
 431				usrTxs = append(usrTxs, tx)
 432			}
 433		} else {
 434			// Add L2 tx to DB
 435			err = h.AddL2Txs([]common.L2Tx{*l2})
 436			if err != nil {
 437				panic(err)
 438			}
 439			// L2Tx ==> historyTxAPI
 440			var tokenID common.TokenID
 441			found := false
 442			for _, acc := range accs {
 443				if acc.Idx == l2.FromIdx {
 444					found = true
 445					tokenID = acc.TokenID
 446					break
 447				}
 448			}
 449			if !found {
 450				panic("tokenID not found")
 451			}
 452			token := getToken(tokenID)
 453			tx := historyTxAPI{
 454				IsL1:      "L2",
 455				TxID:      l2.TxID,
 456				Type:      l2.Type,
 457				Position:  l2.Position,
 458				ToIdx:     idxToHez(l2.ToIdx, token.Symbol),
 459				Amount:    l2.Amount.String(),
 460				BatchNum:  &l2.BatchNum,
 461				Timestamp: getTimestamp(l2.EthBlockNum),
 462				L2Info: &l2Info{
 463					Nonce: l2.Nonce,
 464					Fee:   l2.Fee,
 465				},
 466				Token: token,
 467			}
 468			if l2.FromIdx != 0 {
 469				idxStr := idxToHez(l2.FromIdx, token.Symbol)
 470				tx.FromIdx = &idxStr
 471			}
 472			if token.USD != nil {
 473				af := new(big.Float).SetInt(l2.Amount)
 474				amountFloat, _ := af.Float64()
 475				usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
 476				tx.HistoricUSD = &usd
 477				feeUSD := usd * l2.Fee.Percentage()
 478				tx.HistoricUSD = &usd
 479				tx.L2Info.HistoricFeeUSD = &feeUSD
 480			}
 481			allTxs = append(allTxs, tx)
 482			if isUsrTx(tx) {
 483				usrTxs = append(usrTxs, tx)
 484			}
 485		}
 486	}
 487	// Transform exits to API
 488	exitsToAPIExits := func(exits []common.ExitInfo, accs []common.Account, tokens []common.Token) []exitAPI {
 489		historyExits := []historydb.HistoryExit{}
 490		for _, exit := range exits {
 491			token := getTokenByIdx(exit.AccountIdx)
 492			historyExits = append(historyExits, historydb.HistoryExit{
 493				BatchNum:               exit.BatchNum,
 494				AccountIdx:             exit.AccountIdx,
 495				MerkleProof:            exit.MerkleProof,
 496				Balance:                exit.Balance,
 497				InstantWithdrawn:       exit.InstantWithdrawn,
 498				DelayedWithdrawRequest: exit.DelayedWithdrawRequest,
 499				DelayedWithdrawn:       exit.DelayedWithdrawn,
 500				TokenID:                token.TokenID,
 501				TokenEthBlockNum:       token.EthBlockNum,
 502				TokenEthAddr:           token.EthAddr,
 503				TokenName:              token.Name,
 504				TokenSymbol:            token.Symbol,
 505				TokenDecimals:          token.Decimals,
 506				TokenUSD:               token.USD,
 507				TokenUSDUpdate:         token.USDUpdate,
 508			})
 509		}
 510		return historyExitsToAPI(historyExits)
 511	}
 512	apiExits := exitsToAPIExits(exits, accs, tokens)
 513	// sort.Sort(apiExits)
 514	usrExits := []exitAPI{}
 515	for _, exit := range apiExits {
 516		for _, idx := range usrIdxs {
 517			if idx == exit.AccountIdx {
 518				usrExits = append(usrExits, exit)
 519			}
 520		}
 521	}
 522	// Prepare pool Txs
 523	// Generate common.PoolL2Tx
 524	// WARNING: this should be replaced once transakcio is ready
 525	poolTxs := []common.PoolL2Tx{}
 526	amount := new(big.Int)
 527	amount, ok := amount.SetString("100000000000000", 10)
 528	if !ok {
 529		panic("bad amount")
 530	}
 531	poolTx := common.PoolL2Tx{
 532		FromIdx: accs[0].Idx,
 533		ToIdx:   accs[1].Idx,
 534		Amount:  amount,
 535		TokenID: accs[0].TokenID,
 536		Nonce:   6,
 537	}
 538	if _, err := common.NewPoolL2Tx(&poolTx); err != nil {
 539		panic(err)
 540	}
 541	h, err := poolTx.HashToSign()
 542	if err != nil {
 543		panic(err)
 544	}
 545	poolTx.Signature = privK.SignPoseidon(h).Compress()
 546	poolTxs = append(poolTxs, poolTx)
 547	// Transform to API formats
 548	poolTxsToSend := []receivedPoolTx{}
 549	poolTxsToReceive := []sendPoolTx{}
 550	for _, poolTx := range poolTxs {
 551		// common.PoolL2Tx ==> receivedPoolTx
 552		token := getToken(poolTx.TokenID)
 553		genSendTx := receivedPoolTx{
 554			TxID:      poolTx.TxID,
 555			Type:      poolTx.Type,
 556			TokenID:   poolTx.TokenID,
 557			FromIdx:   idxToHez(poolTx.FromIdx, token.Symbol),
 558			Amount:    poolTx.Amount.String(),
 559			Fee:       poolTx.Fee,
 560			Nonce:     poolTx.Nonce,
 561			Signature: poolTx.Signature,
 562			RqFee:     &poolTx.RqFee,
 563			RqNonce:   &poolTx.RqNonce,
 564		}
 565		// common.PoolL2Tx ==> receivedPoolTx
 566		genReceiveTx := sendPoolTx{
 567			TxID:      poolTx.TxID,
 568			Type:      poolTx.Type,
 569			FromIdx:   idxToHez(poolTx.FromIdx, token.Symbol),
 570			Amount:    poolTx.Amount.String(),
 571			Fee:       poolTx.Fee,
 572			Nonce:     poolTx.Nonce,
 573			State:     poolTx.State,
 574			Signature: poolTx.Signature,
 575			Timestamp: poolTx.Timestamp,
 576			// BatchNum:    poolTx.BatchNum,
 577			RqFee:   &poolTx.RqFee,
 578			RqNonce: &poolTx.RqNonce,
 579			Token:   token,
 580		}
 581		if poolTx.ToIdx != 0 {
 582			toIdx := idxToHez(poolTx.ToIdx, token.Symbol)
 583			genSendTx.ToIdx = &toIdx
 584			genReceiveTx.ToIdx = &toIdx
 585		}
 586		if poolTx.ToEthAddr != common.EmptyAddr {
 587			toEth := ethAddrToHez(poolTx.ToEthAddr)
 588			genSendTx.ToEthAddr = &toEth
 589			genReceiveTx.ToEthAddr = &toEth
 590		}
 591		if poolTx.ToBJJ != nil {
 592			toBJJ := bjjToString(poolTx.ToBJJ)
 593			genSendTx.ToBJJ = &toBJJ
 594			genReceiveTx.ToBJJ = &toBJJ
 595		}
 596		if poolTx.RqFromIdx != 0 {
 597			rqFromIdx := idxToHez(poolTx.RqFromIdx, token.Symbol)
 598			genSendTx.RqFromIdx = &rqFromIdx
 599			genReceiveTx.RqFromIdx = &rqFromIdx
 600			genSendTx.RqTokenID = &token.TokenID
 601			genReceiveTx.RqTokenID = &token.TokenID
 602			rqAmount := poolTx.RqAmount.String()
 603			genSendTx.RqAmount = &rqAmount
 604			genReceiveTx.RqAmount = &rqAmount
 605
 606			if poolTx.RqToIdx != 0 {
 607				rqToIdx := idxToHez(poolTx.RqToIdx, token.Symbol)
 608				genSendTx.RqToIdx = &rqToIdx
 609				genReceiveTx.RqToIdx = &rqToIdx
 610			}
 611			if poolTx.RqToEthAddr != common.EmptyAddr {
 612				rqToEth := ethAddrToHez(poolTx.RqToEthAddr)
 613				genSendTx.RqToEthAddr = &rqToEth
 614				genReceiveTx.RqToEthAddr = &rqToEth
 615			}
 616			if poolTx.RqToBJJ != nil {
 617				rqToBJJ := bjjToString(poolTx.RqToBJJ)
 618				genSendTx.RqToBJJ = &rqToBJJ
 619				genReceiveTx.RqToBJJ = &rqToBJJ
 620			}
 621		}
 622		poolTxsToSend = append(poolTxsToSend, genSendTx)
 623		poolTxsToReceive = append(poolTxsToReceive, genReceiveTx)
 624	}
 625	// Set testCommon
 626	tc = testCommon{
 627		blocks:           blocks,
 628		tokens:           tokensUSD,
 629		batches:          batches,
 630		usrAddr:          ethAddrToHez(usrAddr),
 631		usrBjj:           bjjToString(usrBjj),
 632		accs:             accs,
 633		usrTxs:           usrTxs,
 634		allTxs:           allTxs,
 635		exits:            apiExits,
 636		usrExits:         usrExits,
 637		poolTxsToSend:    poolTxsToSend,
 638		poolTxsToReceive: poolTxsToReceive,
 639		router:           router,
 640	}
 641	// Run tests
 642	result := m.Run()
 643	// Stop server
 644	if err := server.Shutdown(context.Background()); err != nil {
 645		panic(err)
 646	}
 647	if err := database.Close(); err != nil {
 648		panic(err)
 649	}
 650	if err := os.RemoveAll(dir); err != nil {
 651		panic(err)
 652	}
 653	os.Exit(result)
 654}
 655
 656func TestGetHistoryTxs(t *testing.T) {
 657	endpoint := apiURL + "transactions-history"
 658	fetchedTxs := []historyTxAPI{}
 659	appendIter := func(intr interface{}) {
 660		for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
 661			tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
 662			if err != nil {
 663				panic(err)
 664			}
 665			fetchedTxs = append(fetchedTxs, tmp.(historyTxAPI))
 666		}
 667	}
 668	// Get all (no filters)
 669	limit := 8
 670	path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
 671	err := doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 672	assert.NoError(t, err)
 673	assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
 674	// Uncomment once tx generation for tests is fixed
 675	// // Get by ethAddr
 676	// fetchedTxs = []historyTxAPI{}
 677	// limit = 7
 678	// path = fmt.Sprintf(
 679	// 	"%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
 680	// 	endpoint, tc.usrAddr, limit,
 681	// )
 682	// err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 683	// assert.NoError(t, err)
 684	// assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
 685	// // Get by bjj
 686	// fetchedTxs = []historyTxAPI{}
 687	// limit = 6
 688	// path = fmt.Sprintf(
 689	// 	"%s?BJJ=%s&limit=%d&fromItem=",
 690	// 	endpoint, tc.usrBjj, limit,
 691	// )
 692	// err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 693	// assert.NoError(t, err)
 694	// assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
 695	// Get by tokenID
 696	fetchedTxs = []historyTxAPI{}
 697	limit = 5
 698	tokenID := tc.allTxs[0].Token.TokenID
 699	path = fmt.Sprintf(
 700		"%s?tokenId=%d&limit=%d&fromItem=",
 701		endpoint, tokenID, limit,
 702	)
 703	err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 704	assert.NoError(t, err)
 705	tokenIDTxs := []historyTxAPI{}
 706	for i := 0; i < len(tc.allTxs); i++ {
 707		if tc.allTxs[i].Token.TokenID == tokenID {
 708			tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
 709		}
 710	}
 711	assertHistoryTxAPIs(t, tokenIDTxs, fetchedTxs)
 712	// idx
 713	fetchedTxs = []historyTxAPI{}
 714	limit = 4
 715	idx := tc.allTxs[0].ToIdx
 716	path = fmt.Sprintf(
 717		"%s?accountIndex=%s&limit=%d&fromItem=",
 718		endpoint, idx, limit,
 719	)
 720	err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 721	assert.NoError(t, err)
 722	idxTxs := []historyTxAPI{}
 723	for i := 0; i < len(tc.allTxs); i++ {
 724		if (tc.allTxs[i].FromIdx != nil && (*tc.allTxs[i].FromIdx)[6:] == idx[6:]) ||
 725			tc.allTxs[i].ToIdx[6:] == idx[6:] {
 726			idxTxs = append(idxTxs, tc.allTxs[i])
 727		}
 728	}
 729	assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
 730	// batchNum
 731	fetchedTxs = []historyTxAPI{}
 732	limit = 3
 733	batchNum := tc.allTxs[0].BatchNum
 734	path = fmt.Sprintf(
 735		"%s?batchNum=%d&limit=%d&fromItem=",
 736		endpoint, *batchNum, limit,
 737	)
 738	err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 739	assert.NoError(t, err)
 740	batchNumTxs := []historyTxAPI{}
 741	for i := 0; i < len(tc.allTxs); i++ {
 742		if tc.allTxs[i].BatchNum != nil &&
 743			*tc.allTxs[i].BatchNum == *batchNum {
 744			batchNumTxs = append(batchNumTxs, tc.allTxs[i])
 745		}
 746	}
 747	assertHistoryTxAPIs(t, batchNumTxs, fetchedTxs)
 748	// type
 749	txTypes := []common.TxType{
 750		// Uncomment once test gen is fixed
 751		// common.TxTypeExit,
 752		// common.TxTypeTransfer,
 753		// common.TxTypeDeposit,
 754		common.TxTypeCreateAccountDeposit,
 755		// common.TxTypeCreateAccountDepositTransfer,
 756		// common.TxTypeDepositTransfer,
 757		common.TxTypeForceTransfer,
 758		// common.TxTypeForceExit,
 759		// common.TxTypeTransferToEthAddr,
 760		// common.TxTypeTransferToBJJ,
 761	}
 762	for _, txType := range txTypes {
 763		fetchedTxs = []historyTxAPI{}
 764		limit = 2
 765		path = fmt.Sprintf(
 766			"%s?type=%s&limit=%d&fromItem=",
 767			endpoint, txType, limit,
 768		)
 769		err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 770		assert.NoError(t, err)
 771		txTypeTxs := []historyTxAPI{}
 772		for i := 0; i < len(tc.allTxs); i++ {
 773			if tc.allTxs[i].Type == txType {
 774				txTypeTxs = append(txTypeTxs, tc.allTxs[i])
 775			}
 776		}
 777		assertHistoryTxAPIs(t, txTypeTxs, fetchedTxs)
 778	}
 779	// Multiple filters
 780	fetchedTxs = []historyTxAPI{}
 781	limit = 1
 782	path = fmt.Sprintf(
 783		"%s?batchNum=%d&tokenId=%d&limit=%d&fromItem=",
 784		endpoint, *batchNum, tokenID, limit,
 785	)
 786	err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 787	assert.NoError(t, err)
 788	mixedTxs := []historyTxAPI{}
 789	for i := 0; i < len(tc.allTxs); i++ {
 790		if tc.allTxs[i].BatchNum != nil {
 791			if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
 792				mixedTxs = append(mixedTxs, tc.allTxs[i])
 793			}
 794		}
 795	}
 796	assertHistoryTxAPIs(t, mixedTxs, fetchedTxs)
 797	// All, in reverse order
 798	fetchedTxs = []historyTxAPI{}
 799	limit = 5
 800	path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
 801	err = doGoodReqPaginated(path, historydb.OrderDesc, &historyTxsAPI{}, appendIter)
 802	assert.NoError(t, err)
 803	flipedTxs := []historyTxAPI{}
 804	for i := 0; i < len(tc.allTxs); i++ {
 805		flipedTxs = append(flipedTxs, tc.allTxs[len(tc.allTxs)-1-i])
 806	}
 807	assertHistoryTxAPIs(t, flipedTxs, fetchedTxs)
 808	// 400
 809	path = fmt.Sprintf(
 810		"%s?accountIndex=%s&hermezEthereumAddress=%s",
 811		endpoint, idx, tc.usrAddr,
 812	)
 813	err = doBadReq("GET", path, nil, 400)
 814	assert.NoError(t, err)
 815	path = fmt.Sprintf("%s?tokenId=X", endpoint)
 816	err = doBadReq("GET", path, nil, 400)
 817	assert.NoError(t, err)
 818	// 404
 819	path = fmt.Sprintf("%s?batchNum=999999", endpoint)
 820	err = doBadReq("GET", path, nil, 404)
 821	assert.NoError(t, err)
 822	path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
 823	err = doBadReq("GET", path, nil, 404)
 824	assert.NoError(t, err)
 825}
 826
 827func TestGetHistoryTx(t *testing.T) {
 828	// Get all txs by their ID
 829	endpoint := apiURL + "transactions-history/"
 830	fetchedTxs := []historyTxAPI{}
 831	for _, tx := range tc.allTxs {
 832		fetchedTx := historyTxAPI{}
 833		assert.NoError(t, doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx))
 834		fetchedTxs = append(fetchedTxs, fetchedTx)
 835	}
 836	assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
 837	// 400
 838	err := doBadReq("GET", endpoint+"0x001", nil, 400)
 839	assert.NoError(t, err)
 840	// 404
 841	err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404)
 842	assert.NoError(t, err)
 843}
 844
 845func assertHistoryTxAPIs(t *testing.T, expected, actual []historyTxAPI) {
 846	require.Equal(t, len(expected), len(actual))
 847	for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
 848		actual[i].ItemID = 0
 849		assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
 850		expected[i].Timestamp = actual[i].Timestamp
 851		if expected[i].Token.USDUpdate == nil {
 852			assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
 853		} else {
 854			assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
 855			expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
 856		}
 857		test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
 858		if expected[i].L2Info != nil {
 859			test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
 860		} else {
 861			test.AssertUSD(t, expected[i].L1Info.HistoricLoadAmountUSD, actual[i].L1Info.HistoricLoadAmountUSD)
 862		}
 863		assert.Equal(t, expected[i], actual[i])
 864	}
 865}
 866
 867func TestGetExits(t *testing.T) {
 868	endpoint := apiURL + "exits"
 869	fetchedExits := []exitAPI{}
 870	appendIter := func(intr interface{}) {
 871		for i := 0; i < len(intr.(*exitsAPI).Exits); i++ {
 872			tmp, err := copystructure.Copy(intr.(*exitsAPI).Exits[i])
 873			if err != nil {
 874				panic(err)
 875			}
 876			fetchedExits = append(fetchedExits, tmp.(exitAPI))
 877		}
 878	}
 879	// Get all (no filters)
 880	limit := 8
 881	path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
 882	err := doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 883	assert.NoError(t, err)
 884	assertExitAPIs(t, tc.exits, fetchedExits)
 885
 886	// Get by ethAddr
 887	fetchedExits = []exitAPI{}
 888	limit = 7
 889	path = fmt.Sprintf(
 890		"%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
 891		endpoint, tc.usrAddr, limit,
 892	)
 893	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 894	assert.NoError(t, err)
 895	assertExitAPIs(t, tc.usrExits, fetchedExits)
 896	// Get by bjj
 897	fetchedExits = []exitAPI{}
 898	limit = 6
 899	path = fmt.Sprintf(
 900		"%s?BJJ=%s&limit=%d&fromItem=",
 901		endpoint, tc.usrBjj, limit,
 902	)
 903	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 904	assert.NoError(t, err)
 905	assertExitAPIs(t, tc.usrExits, fetchedExits)
 906	// Get by tokenID
 907	fetchedExits = []exitAPI{}
 908	limit = 5
 909	tokenID := tc.exits[0].Token.TokenID
 910	path = fmt.Sprintf(
 911		"%s?tokenId=%d&limit=%d&fromItem=",
 912		endpoint, tokenID, limit,
 913	)
 914	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 915	assert.NoError(t, err)
 916	tokenIDExits := []exitAPI{}
 917	for i := 0; i < len(tc.exits); i++ {
 918		if tc.exits[i].Token.TokenID == tokenID {
 919			tokenIDExits = append(tokenIDExits, tc.exits[i])
 920		}
 921	}
 922	assertExitAPIs(t, tokenIDExits, fetchedExits)
 923	// idx
 924	fetchedExits = []exitAPI{}
 925	limit = 4
 926	idx := tc.exits[0].AccountIdx
 927	path = fmt.Sprintf(
 928		"%s?accountIndex=%s&limit=%d&fromItem=",
 929		endpoint, idx, limit,
 930	)
 931	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 932	assert.NoError(t, err)
 933	idxExits := []exitAPI{}
 934	for i := 0; i < len(tc.exits); i++ {
 935		if tc.exits[i].AccountIdx[6:] == idx[6:] {
 936			idxExits = append(idxExits, tc.exits[i])
 937		}
 938	}
 939	assertExitAPIs(t, idxExits, fetchedExits)
 940	// batchNum
 941	fetchedExits = []exitAPI{}
 942	limit = 3
 943	batchNum := tc.exits[0].BatchNum
 944	path = fmt.Sprintf(
 945		"%s?batchNum=%d&limit=%d&fromItem=",
 946		endpoint, batchNum, limit,
 947	)
 948	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 949	assert.NoError(t, err)
 950	batchNumExits := []exitAPI{}
 951	for i := 0; i < len(tc.exits); i++ {
 952		if tc.exits[i].BatchNum == batchNum {
 953			batchNumExits = append(batchNumExits, tc.exits[i])
 954		}
 955	}
 956	assertExitAPIs(t, batchNumExits, fetchedExits)
 957	// Multiple filters
 958	fetchedExits = []exitAPI{}
 959	limit = 1
 960	path = fmt.Sprintf(
 961		"%s?batchNum=%d&tokeId=%d&limit=%d&fromItem=",
 962		endpoint, batchNum, tokenID, limit,
 963	)
 964	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 965	assert.NoError(t, err)
 966	mixedExits := []exitAPI{}
 967	flipedExits := []exitAPI{}
 968	for i := 0; i < len(tc.exits); i++ {
 969		if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID {
 970			mixedExits = append(mixedExits, tc.exits[i])
 971		}
 972		flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i])
 973	}
 974	assertExitAPIs(t, mixedExits, fetchedExits)
 975	// All, in reverse order
 976	fetchedExits = []exitAPI{}
 977	limit = 5
 978	path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
 979	err = doGoodReqPaginated(path, historydb.OrderDesc, &exitsAPI{}, appendIter)
 980	assert.NoError(t, err)
 981	assertExitAPIs(t, flipedExits, fetchedExits)
 982	// 400
 983	path = fmt.Sprintf(
 984		"%s?accountIndex=%s&hermezEthereumAddress=%s",
 985		endpoint, idx, tc.usrAddr,
 986	)
 987	err = doBadReq("GET", path, nil, 400)
 988	assert.NoError(t, err)
 989	path = fmt.Sprintf("%s?tokenId=X", endpoint)
 990	err = doBadReq("GET", path, nil, 400)
 991	assert.NoError(t, err)
 992	// 404
 993	path = fmt.Sprintf("%s?batchNum=999999", endpoint)
 994	err = doBadReq("GET", path, nil, 404)
 995	assert.NoError(t, err)
 996	path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
 997	err = doBadReq("GET", path, nil, 404)
 998	assert.NoError(t, err)
 999}
1000
1001func TestGetExit(t *testing.T) {
1002	// Get all txs by their ID
1003	endpoint := apiURL + "exits/"
1004	fetchedExits := []exitAPI{}
1005	for _, exit := range tc.exits {
1006		fetchedExit := exitAPI{}
1007		assert.NoError(
1008			t, doGoodReq(
1009				"GET",
1010				fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx),
1011				nil, &fetchedExit,
1012			),
1013		)
1014		fetchedExits = append(fetchedExits, fetchedExit)
1015	}
1016	assertExitAPIs(t, tc.exits, fetchedExits)
1017	// 400
1018	err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400)
1019	assert.NoError(t, err)
1020	err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400)
1021	assert.NoError(t, err)
1022	// 404
1023	err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404)
1024	assert.NoError(t, err)
1025}
1026
1027func assertExitAPIs(t *testing.T, expected, actual []exitAPI) {
1028	require.Equal(t, len(expected), len(actual))
1029	for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
1030		actual[i].ItemID = 0
1031		if expected[i].Token.USDUpdate == nil {
1032			assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
1033		} else {
1034			assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
1035			expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
1036		}
1037		assert.Equal(t, expected[i], actual[i])
1038	}
1039}
1040
1041func TestGetToken(t *testing.T) {
1042	// Get all txs by their ID
1043	endpoint := apiURL + "tokens/"
1044	fetchedTokens := []tokenAPI{}
1045	for _, token := range tc.tokens {
1046		fetchedToken := tokenAPI{}
1047		assert.NoError(t, doGoodReq("GET", endpoint+strconv.Itoa(int(token.TokenID)), nil, &fetchedToken))
1048		fetchedTokens = append(fetchedTokens, fetchedToken)
1049	}
1050	assertTokensAPIs(t, tc.tokens, fetchedTokens)
1051}
1052
1053func TestGetTokens(t *testing.T) {
1054	endpoint := apiURL + "tokens"
1055	fetchedTokens := []tokenAPI{}
1056	appendIter := func(intr interface{}) {
1057		for i := 0; i < len(intr.(*tokensAPI).Tokens); i++ {
1058			tmp, err := copystructure.Copy(intr.(*tokensAPI).Tokens[i])
1059			if err != nil {
1060				panic(err)
1061			}
1062			fetchedTokens = append(fetchedTokens, tmp.(tokenAPI))
1063		}
1064	}
1065	// Get all (no filters)
1066	limit := 8
1067	path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
1068	err := doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
1069	assert.NoError(t, err)
1070	assertTokensAPIs(t, tc.tokens, fetchedTokens)
1071
1072	// Get by tokenIds
1073	fetchedTokens = []tokenAPI{}
1074	limit = 7
1075	stringIds := strconv.Itoa(int(tc.tokens[2].TokenID)) + "," + strconv.Itoa(int(tc.tokens[5].TokenID)) + "," + strconv.Itoa(int(tc.tokens[6].TokenID))
1076	path = fmt.Sprintf(
1077		"%s?ids=%s&limit=%d&fromItem=",
1078		endpoint, stringIds, limit,
1079	)
1080	err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
1081	assert.NoError(t, err)
1082	var tokensFiltered []tokenAPI
1083	tokensFiltered = append(tokensFiltered, tc.tokens[2])
1084	tokensFiltered = append(tokensFiltered, tc.tokens[5])
1085	tokensFiltered = append(tokensFiltered, tc.tokens[6])
1086	assertTokensAPIs(t, tokensFiltered, fetchedTokens)
1087
1088	// Get by symbols
1089	fetchedTokens = []tokenAPI{}
1090	limit = 7
1091	stringSymbols := tc.tokens[1].Symbol + "," + tc.tokens[3].Symbol
1092	path = fmt.Sprintf(
1093		"%s?symbols=%s&limit=%d&fromItem=",
1094		endpoint, stringSymbols, limit,
1095	)
1096	err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
1097	assert.NoError(t, err)
1098	tokensFiltered = nil
1099	tokensFiltered = append(tokensFiltered, tc.tokens[1])
1100	tokensFiltered = append(tokensFiltered, tc.tokens[3])
1101	assertTokensAPIs(t, tokensFiltered, fetchedTokens)
1102
1103	// Get by name
1104	fetchedTokens = []tokenAPI{}
1105	limit = 5
1106	stringName := tc.tokens[8].Name[4:5]
1107	path = fmt.Sprintf(
1108		"%s?name=%s&limit=%d&fromItem=",
1109		endpoint, stringName, limit,
1110	)
1111	err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
1112	assert.NoError(t, err)
1113	tokensFiltered = nil
1114	tokensFiltered = append(tokensFiltered, tc.tokens[8])
1115	assertTokensAPIs(t, tokensFiltered, fetchedTokens)
1116
1117	// Multiple filters
1118	fetchedTokens = []tokenAPI{}
1119	limit = 5
1120	stringSymbols = tc.tokens[2].Symbol + "," + tc.tokens[6].Symbol
1121	stringIds = strconv.Itoa(int(tc.tokens[2].TokenID)) + "," + strconv.Itoa(int(tc.tokens[5].TokenID)) + "," + strconv.Itoa(int(tc.tokens[6].TokenID))
1122	path = fmt.Sprintf(
1123		"%s?symbols=%s&ids=%s&limit=%d&fromItem=",
1124		endpoint, stringSymbols, stringIds, limit,
1125	)
1126	err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
1127	assert.NoError(t, err)
1128
1129	tokensFiltered = nil
1130	tokensFiltered = append(tokensFiltered, tc.tokens[2])
1131	tokensFiltered = append(tokensFiltered, tc.tokens[6])
1132	assertTokensAPIs(t, tokensFiltered, fetchedTokens)
1133
1134	// All, in reverse order
1135	fetchedTokens = []tokenAPI{}
1136	limit = 5
1137	path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
1138	err = doGoodReqPaginated(path, historydb.OrderDesc, &tokensAPI{}, appendIter)
1139	assert.NoError(t, err)
1140	flipedTokens := []tokenAPI{}
1141	for i := 0; i < len(tc.tokens); i++ {
1142		flipedTokens = append(flipedTokens, tc.tokens[len(tc.tokens)-1-i])
1143	}
1144	assertTokensAPIs(t, flipedTokens, fetchedTokens)
1145}
1146
1147func assertTokensAPIs(t *testing.T, expected, actual []tokenAPI) {
1148	require.Equal(t, len(expected), len(actual))
1149	for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
1150		actual[i].ItemID = 0
1151		if expected[i].USDUpdate == nil {
1152			assert.Equal(t, expected[i].USDUpdate, actual[i].USDUpdate)
1153		} else {
1154			assert.Equal(t, expected[i].USDUpdate.Unix(), actual[i].USDUpdate.Unix())
1155			expected[i].USDUpdate = actual[i].USDUpdate
1156		}
1157		assert.Equal(t, expected[i], actual[i])
1158	}
1159}
1160
1161func TestGetConfig(t *testing.T) {
1162	endpoint := apiURL + "config"
1163	var configTest configAPI
1164	assert.NoError(t, doGoodReq("GET", endpoint, nil, &configTest))
1165	assert.Equal(t, config, configTest)
1166	assert.Equal(t, cg, &configTest)
1167}
1168
1169func TestPoolTxs(t *testing.T) {
1170	// POST
1171	endpoint := apiURL + "transactions-pool"
1172	fetchedTxID := common.TxID{}
1173	for _, tx := range tc.poolTxsToSend {
1174		jsonTxBytes, err := json.Marshal(tx)
1175		assert.NoError(t, err)
1176		jsonTxReader := bytes.NewReader(jsonTxBytes)
1177		fmt.Println(string(jsonTxBytes))
1178		assert.NoError(
1179			t, doGoodReq(
1180				"POST",
1181				endpoint,
1182				jsonTxReader, &fetchedTxID,
1183			),
1184		)
1185		assert.Equal(t, tx.TxID, fetchedTxID)
1186	}
1187	// 400
1188	// Wrong signature
1189	badTx := tc.poolTxsToSend[0]
1190	badTx.FromIdx = "hez:foo:1000"
1191	jsonTxBytes, err := json.Marshal(badTx)
1192	assert.NoError(t, err)
1193	jsonTxReader := bytes.NewReader(jsonTxBytes)
1194	err = doBadReq("POST", endpoint, jsonTxReader, 400)
1195	assert.NoError(t, err)
1196	// Wrong to
1197	badTx = tc.poolTxsToSend[0]
1198	ethAddr := "hez:0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
1199	badTx.ToEthAddr = &ethAddr
1200	badTx.ToIdx = nil
1201	jsonTxBytes, err = json.Marshal(badTx)
1202	assert.NoError(t, err)
1203	jsonTxReader = bytes.NewReader(jsonTxBytes)
1204	err = doBadReq("POST", endpoint, jsonTxReader, 400)
1205	assert.NoError(t, err)
1206	// Wrong rq
1207	badTx = tc.poolTxsToSend[0]
1208	rqFromIdx := "hez:foo:30"
1209	badTx.RqFromIdx = &rqFromIdx
1210	jsonTxBytes, err = json.Marshal(badTx)
1211	assert.NoError(t, err)
1212	jsonTxReader = bytes.NewReader(jsonTxBytes)
1213	err = doBadReq("POST", endpoint, jsonTxReader, 400)
1214	assert.NoError(t, err)
1215	// GET
1216	endpoint += "/"
1217	for _, tx := range tc.poolTxsToReceive {
1218		fetchedTx := sendPoolTx{}
1219		assert.NoError(
1220			t, doGoodReq(
1221				"GET",
1222				endpoint+tx.TxID.String(),
1223				nil, &fetchedTx,
1224			),
1225		)
1226		assertPoolTx(t, tx, fetchedTx)
1227	}
1228	// 400
1229	err = doBadReq("GET", endpoint+"0xG20000000156660000000090", nil, 400)
1230	assert.NoError(t, err)
1231	// 404
1232	err = doBadReq("GET", endpoint+"0x020000000156660000000090", nil, 404)
1233	assert.NoError(t, err)
1234}
1235
1236func assertPoolTx(t *testing.T, expected, actual sendPoolTx) {
1237	// state should be pending
1238	assert.Equal(t, common.PoolL2TxStatePending, actual.State)
1239	expected.State = actual.State
1240	// timestamp should be very close to now
1241	assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
1242	expected.Timestamp = actual.Timestamp
1243	// token timestamp
1244	if expected.Token.USDUpdate == nil {
1245		assert.Equal(t, expected.Token.USDUpdate, actual.Token.USDUpdate)
1246	} else {
1247		assert.Equal(t, expected.Token.USDUpdate.Unix(), actual.Token.USDUpdate.Unix())
1248		expected.Token.USDUpdate = actual.Token.USDUpdate
1249	}
1250	assert.Equal(t, expected, actual)
1251}
1252
1253func doGoodReqPaginated(
1254	path, order string,
1255	iterStruct db.Paginationer,
1256	appendIter func(res interface{}),
1257) error {
1258	next := 0
1259	for {
1260		// Call API to get this iteration items
1261		iterPath := path
1262		if next == 0 && order == historydb.OrderDesc {
1263			// Fetch first item in reverse order
1264			iterPath += "99999"
1265		} else {
1266			// Fetch from next item or 0 if it's ascending order
1267			iterPath += strconv.Itoa(next)
1268		}
1269		if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
1270			return err
1271		}
1272		appendIter(iterStruct)
1273		// Keep iterating?
1274		pag := iterStruct.GetPagination()
1275		if order == historydb.OrderAsc {
1276			if pag.LastReturnedItem == pag.LastItem { // No
1277				break
1278			} else { // Yes
1279				next = pag.LastReturnedItem + 1
1280			}
1281		} else {
1282			if pag.FirstReturnedItem == pag.FirstItem { // No
1283				break
1284			} else { // Yes
1285				next = pag.FirstReturnedItem - 1
1286			}
1287		}
1288	}
1289	return nil
1290}
1291
1292func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
1293	ctx := context.Background()
1294	client := &http.Client{}
1295	httpReq, err := http.NewRequest(method, path, reqBody)
1296	if err != nil {
1297		return err
1298	}
1299	if reqBody != nil {
1300		httpReq.Header.Add("Content-Type", "application/json")
1301	}
1302	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
1303	if err != nil {
1304		return err
1305	}
1306	// Validate request against swagger spec
1307	requestValidationInput := &swagger.RequestValidationInput{
1308		Request:    httpReq,
1309		PathParams: pathParams,
1310		Route:      route,
1311	}
1312	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
1313		return err
1314	}
1315	// Do API call
1316	resp, err := client.Do(httpReq)
1317	if err != nil {
1318		return err
1319	}
1320	if resp.Body == nil {
1321		return errors.New("Nil body")
1322	}
1323	//nolint
1324	defer resp.Body.Close()
1325	body, err := ioutil.ReadAll(resp.Body)
1326	if err != nil {
1327		return err
1328	}
1329	if resp.StatusCode != 200 {
1330		return fmt.Errorf("%d response: %s", resp.StatusCode, string(body))
1331	}
1332	// Unmarshal body into return struct
1333	if err := json.Unmarshal(body, returnStruct); err != nil {
1334		return err
1335	}
1336	// Validate response against swagger spec
1337	responseValidationInput := &swagger.ResponseValidationInput{
1338		RequestValidationInput: requestValidationInput,
1339		Status:                 resp.StatusCode,
1340		Header:                 resp.Header,
1341	}
1342	responseValidationInput = responseValidationInput.SetBodyBytes(body)
1343	return swagger.ValidateResponse(ctx, responseValidationInput)
1344}
1345
1346func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
1347	ctx := context.Background()
1348	client := &http.Client{}
1349	httpReq, _ := http.NewRequest(method, path, reqBody)
1350	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
1351	if err != nil {
1352		return err
1353	}
1354	// Validate request against swagger spec
1355	requestValidationInput := &swagger.RequestValidationInput{
1356		Request:    httpReq,
1357		PathParams: pathParams,
1358		Route:      route,
1359	}
1360	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
1361		if expectedResponseCode != 400 {
1362			return err
1363		}
1364		log.Warn("The request does not match the API spec")
1365	}
1366	// Do API call
1367	resp, err := client.Do(httpReq)
1368	if err != nil {
1369		return err
1370	}
1371	if resp.Body == nil {
1372		return errors.New("Nil body")
1373	}
1374	//nolint
1375	defer resp.Body.Close()
1376	body, err := ioutil.ReadAll(resp.Body)
1377	if err != nil {
1378		return err
1379	}
1380	if resp.StatusCode != expectedResponseCode {
1381		return fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body))
1382	}
1383	// Validate response against swagger spec
1384	responseValidationInput := &swagger.ResponseValidationInput{
1385		RequestValidationInput: requestValidationInput,
1386		Status:                 resp.StatusCode,
1387		Header:                 resp.Header,
1388	}
1389	responseValidationInput = responseValidationInput.SetBodyBytes(body)
1390	return swagger.ValidateResponse(ctx, responseValidationInput)
1391}