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