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/big"
 11	"net/http"
 12	"os"
 13	"sort"
 14	"strconv"
 15	"testing"
 16	"time"
 17
 18	ethCommon "github.com/ethereum/go-ethereum/common"
 19	swagger "github.com/getkin/kin-openapi/openapi3filter"
 20	"github.com/gin-gonic/gin"
 21	"github.com/hermeznetwork/hermez-node/common"
 22	"github.com/hermeznetwork/hermez-node/db"
 23	"github.com/hermeznetwork/hermez-node/db/historydb"
 24	"github.com/hermeznetwork/hermez-node/db/l2db"
 25	"github.com/hermeznetwork/hermez-node/db/statedb"
 26	"github.com/hermeznetwork/hermez-node/log"
 27	"github.com/hermeznetwork/hermez-node/test"
 28	"github.com/hermeznetwork/hermez-node/test/til"
 29	"github.com/iden3/go-iden3-crypto/babyjub"
 30)
 31
 32// Pendinger is an interface that allows getting last returned item ID and PendingItems to be used for building fromItem
 33// when testing paginated endpoints.
 34type Pendinger interface {
 35	GetPending() (pendingItems, lastItemID uint64)
 36	Len() int
 37}
 38
 39const apiPort = ":4010"
 40const apiURL = "http://localhost" + apiPort + "/"
 41
 42var SetBlockchain = `
 43	Type: Blockchain
 44
 45	AddToken(1)
 46	AddToken(2)
 47	AddToken(3)
 48	AddToken(4)
 49
 50	// Coordinator accounts, Idxs: 256, 257
 51	CreateAccountCoordinator(0) Coord
 52	CreateAccountCoordinator(1) Coord
 53
 54	// close Block:0, Batch:0
 55	> batch
 56
 57	CreateAccountDeposit(0) A: 500
 58	CreateAccountDeposit(1) C: 0
 59	CreateAccountCoordinator(0) C
 60
 61	// close Block:0, Batch:1
 62	> batchL1
 63	// Expected balances:
 64	//     Coord(0): 0, Coord(1): 0
 65	//     C(0): 0
 66
 67	CreateAccountDeposit(1) A: 500
 68
 69	// close Block:0, Batch:2
 70	> batchL1
 71
 72	// close Block:0, Batch:3
 73	> batchL1
 74
 75	CreateAccountDepositTransfer(0) B-A: 500, 100
 76
 77	// close Block:0, Batch:4
 78	> batchL1
 79	CreateAccountDeposit(0) D: 800
 80
 81	// close Block:0, Batch:5
 82	> batchL1
 83
 84	CreateAccountCoordinator(1) B
 85
 86	Transfer(1) A-B: 200 (126)
 87	Transfer(0) B-C: 100 (126)
 88
 89	// close Block:0, Batch:6
 90	> batchL1 // forge L1User{1}, forge L1Coord{2}, forge L2{2}
 91
 92	Deposit(0) C: 500
 93	DepositTransfer(0) C-D: 400, 100
 94
 95	Transfer(0) A-B: 100 (126)
 96	Transfer(0) C-A: 50 (126)
 97	Transfer(1) B-C: 100 (126)
 98	Exit(0) A: 100 (126)
 99
100	ForceTransfer(0) D-B: 200
101	ForceExit(0) B: 100
102
103	// close Block:0, Batch:7
104	> batchL1
105	> block
106
107	AddToken(5)
108	AddToken(6)
109	AddToken(7)
110	AddToken(8)
111
112	Transfer(0) D-A: 300 (126)
113	Transfer(0) B-D: 100 (126)
114
115	// close Block:1, Batch:0
116	> batchL1
117
118	CreateAccountCoordinator(0) F
119
120	CreateAccountCoordinator(0) G
121	CreateAccountCoordinator(0) H
122	CreateAccountCoordinator(0) I
123	CreateAccountCoordinator(0) J
124	CreateAccountCoordinator(0) K
125	CreateAccountCoordinator(0) L
126	CreateAccountCoordinator(0) M
127	CreateAccountCoordinator(0) N
128	CreateAccountCoordinator(0) O
129	CreateAccountCoordinator(0) P
130
131	CreateAccountCoordinator(5) G
132	CreateAccountCoordinator(5) H
133	CreateAccountCoordinator(5) I
134	CreateAccountCoordinator(5) J
135	CreateAccountCoordinator(5) K
136	CreateAccountCoordinator(5) L
137	CreateAccountCoordinator(5) M
138	CreateAccountCoordinator(5) N
139	CreateAccountCoordinator(5) O
140	CreateAccountCoordinator(5) P
141
142	CreateAccountCoordinator(2) G
143	CreateAccountCoordinator(2) H
144	CreateAccountCoordinator(2) I
145	CreateAccountCoordinator(2) J
146	CreateAccountCoordinator(2) K
147	CreateAccountCoordinator(2) L
148	CreateAccountCoordinator(2) M
149	CreateAccountCoordinator(2) N
150	CreateAccountCoordinator(2) O
151	CreateAccountCoordinator(2) P
152
153
154	> batch
155	> block
156	> batch
157	> block
158	> batch
159	> block
160`
161
162type testCommon struct {
163	blocks           []common.Block
164	tokens           []historydb.TokenWithUSD
165	batches          []testBatch
166	fullBatches      []testFullBatch
167	coordinators     []historydb.CoordinatorAPI
168	accounts         []testAccount
169	usrAddr          string
170	usrBjj           string
171	accs             []common.Account
172	usrTxs           []testTx
173	allTxs           []testTx
174	exits            []testExit
175	usrExits         []testExit
176	poolTxsToSend    []testPoolTxSend
177	poolTxsToReceive []testPoolTxReceive
178	auths            []testAuth
179	router           *swagger.Router
180	bids             []testBid
181	slots            []testSlot
182	auctionVars      common.AuctionVariables
183	rollupVars       common.RollupVariables
184	wdelayerVars     common.WDelayerVariables
185}
186
187var tc testCommon
188var config configAPI
189var api *API
190
191// TestMain initializes the API server, and fill HistoryDB and StateDB with fake data,
192// emulating the task of the synchronizer in order to have data to be returned
193// by the API endpoints that will be tested
194func TestMain(m *testing.M) {
195	/*
196		til update considerations:
197		1. Two instructions sets should be enough (one for L2 another for historydb)
198		2. FillBlocksExtra function must be used, there is a coment on top of the function that explains which data is setted
199		3. Some data will not be generated by til nor FillBlocksExtra, test.GenXXX will still be required to cover this cases
200		4. Most of the historydb inserts should be replaced with nBlocks calls to AddBlockSCData
201		5. When defining til instructions, there is no need to have 100s of entries for each table, but it's interesting to
202		   cover all different cases (for instance all tx types)
203	*/
204
205	// Initializations
206	// Swagger
207	router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
208	// HistoryDB
209	pass := os.Getenv("POSTGRES_PASS")
210
211	database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
212	if err != nil {
213		panic(err)
214	}
215	hdb := historydb.NewHistoryDB(database)
216	if err != nil {
217		panic(err)
218	}
219	// StateDB
220	dir, err := ioutil.TempDir("", "tmpdb")
221	if err != nil {
222		panic(err)
223	}
224	defer func() {
225		if err := os.RemoveAll(dir); err != nil {
226			panic(err)
227		}
228	}()
229	sdb, err := statedb.NewStateDB(dir, statedb.TypeTxSelector, 0)
230	if err != nil {
231		panic(err)
232	}
233	// L2DB
234	l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
235	test.WipeDB(l2DB.DB()) // this will clean HistoryDB and L2DB
236
237	// Config (smart contract constants)
238	config = getConfigTest()
239
240	// API
241	apiGin := gin.Default()
242	api, err = NewAPI(
243		true,
244		true,
245		apiGin,
246		hdb,
247		sdb,
248		l2DB,
249		&config,
250	)
251	if err != nil {
252		panic(err)
253	}
254	// Start server
255	server := &http.Server{Addr: apiPort, Handler: apiGin}
256	go func() {
257		if err := server.ListenAndServe(); err != nil &&
258			err != http.ErrServerClosed {
259			panic(err)
260		}
261	}()
262
263	// Fill HistoryDB and StateDB with fake data
264	// Gen blocks and add them to DB
265	const nBlocks = 5
266	// TODO: UPDATE with til
267	blocks := test.GenBlocks(1, nBlocks+1)
268	err = api.h.AddBlocks(blocks)
269	if err != nil {
270		panic(err)
271	}
272	lastBlockNum := blocks[nBlocks-1].EthBlockNum
273
274	// Gen tokens and add them to DB
275	const nTokens = 10
276	// TODO: UPDATE with til
277	tokens, ethToken := test.GenTokens(nTokens, blocks)
278	err = api.h.AddTokens(tokens)
279	if err != nil {
280		panic(err)
281	}
282	tokens = append([]common.Token{ethToken}, tokens...)
283	// Set token value
284	tokensUSD := []historydb.TokenWithUSD{}
285	for i, tkn := range tokens {
286		token := historydb.TokenWithUSD{
287			TokenID:     tkn.TokenID,
288			EthBlockNum: tkn.EthBlockNum,
289			EthAddr:     tkn.EthAddr,
290			Name:        tkn.Name,
291			Symbol:      tkn.Symbol,
292			Decimals:    tkn.Decimals,
293		}
294		// Set value of 50% of the tokens
295		if i%2 != 0 {
296			value := float64(i) * 1.234567
297			now := time.Now().UTC()
298			token.USD = &value
299			token.USDUpdate = &now
300			err = api.h.UpdateTokenValue(token.Symbol, value)
301			if err != nil {
302				panic(err)
303			}
304		}
305		tokensUSD = append(tokensUSD, token)
306	}
307	// Gen batches and add them to DB
308	const nBatches = 10
309	// TODO: UPDATE with til
310	batches := test.GenBatches(nBatches, blocks)
311	err = api.h.AddBatches(batches)
312	if err != nil {
313		panic(err)
314	}
315	// Gen accounts and add them to HistoryDB and StateDB
316	const totalAccounts = 40
317	const userAccounts = 4
318	usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
319	privK := babyjub.NewRandPrivKey()
320	usrBjj := privK.Public()
321	// TODO: UPDATE with til
322	accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
323	err = api.h.AddAccounts(accs)
324	if err != nil {
325		panic(err)
326	}
327
328	// api.s.CreateAccount called in new part with til
329	/* for i := 0; i < len(accs); i++ {
330		if _, err := api.s.CreateAccount(accs[i].Idx, &accs[i]); err != nil {
331			panic(err)
332		}
333	} */
334
335	// helper to vinculate user related resources
336	usrIdxs := []string{}
337	for _, acc := range accs {
338		if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
339			for _, token := range tokens {
340				if token.TokenID == acc.TokenID {
341					usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
342				}
343			}
344		}
345	}
346	// Gen exits and add them to DB
347	const totalExits = 40
348	// TODO: UPDATE with til
349	exits := test.GenExitTree(totalExits, batches, accs, blocks)
350	err = api.h.AddExitTree(exits)
351	if err != nil {
352		panic(err)
353	}
354
355	// L1 and L2 txs need to be sorted in a combined way
356	// Gen L1Txs
357	const totalL1Txs = 40
358	const userL1Txs = 4
359	// TODO: UPDATE with til
360	usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
361	// Gen L2Txs
362	const totalL2Txs = 20
363	const userL2Txs = 4
364	// TODO: UPDATE with til
365	usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
366	// Sort txs
367	sortedTxs := []txSortFielder{}
368	for i := 0; i < len(usrL1Txs); i++ {
369		wL1 := wrappedL1(usrL1Txs[i])
370		sortedTxs = append(sortedTxs, &wL1)
371	}
372	for i := 0; i < len(othrL1Txs); i++ {
373		wL1 := wrappedL1(othrL1Txs[i])
374		sortedTxs = append(sortedTxs, &wL1)
375	}
376	for i := 0; i < len(usrL2Txs); i++ {
377		wL2 := wrappedL2(usrL2Txs[i])
378		sortedTxs = append(sortedTxs, &wL2)
379	}
380	for i := 0; i < len(othrL2Txs); i++ {
381		wL2 := wrappedL2(othrL2Txs[i])
382		sortedTxs = append(sortedTxs, &wL2)
383	}
384	sort.Sort(txsSort(sortedTxs))
385	// Store txs to DB
386	for _, genericTx := range sortedTxs {
387		l1 := genericTx.L1()
388		l2 := genericTx.L2()
389		if l1 != nil {
390			err = api.h.AddL1Txs([]common.L1Tx{*l1})
391			if err != nil {
392				panic(err)
393			}
394		} else if l2 != nil {
395			err = api.h.AddL2Txs([]common.L2Tx{*l2})
396			if err != nil {
397				panic(err)
398			}
399		} else {
400			panic("should be l1 or l2")
401		}
402	}
403
404	// Coordinators
405	const nCoords = 10
406	coords := test.GenCoordinators(nCoords, blocks)
407	err = api.h.AddCoordinators(coords)
408	if err != nil {
409		panic(err)
410	}
411	fromItem := uint(0)
412	limit := uint(99999)
413	coordinators, _, err := api.h.GetCoordinatorsAPI(&fromItem, &limit, historydb.OrderAsc)
414	if err != nil {
415		panic(err)
416	}
417
418	// Bids
419	const nBids = 20
420	bids := test.GenBids(nBids, blocks, coords)
421	err = api.h.AddBids(bids)
422	if err != nil {
423		panic(err)
424	}
425	testBids := genTestBids(blocks, coordinators, bids)
426
427	// Vars
428	var defaultSlotSetBid [6]*big.Int = [6]*big.Int{big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10)}
429	auctionVars := common.AuctionVariables{
430		EthBlockNum:        int64(2),
431		DonationAddress:    ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
432		DefaultSlotSetBid:  defaultSlotSetBid,
433		Outbidding:         uint16(1),
434		SlotDeadline:       uint8(20),
435		BootCoordinator:    ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
436		ClosedAuctionSlots: uint16(2),
437		OpenAuctionSlots:   uint16(5),
438	}
439
440	rollupVars := common.RollupVariables{
441		EthBlockNum:           int64(3),
442		FeeAddToken:           big.NewInt(100),
443		ForgeL1L2BatchTimeout: int64(44),
444		WithdrawalDelay:       uint64(3000),
445	}
446
447	wdelayerVars := common.WDelayerVariables{
448		WithdrawalDelay: uint64(3000),
449	}
450
451	err = api.h.AddAuctionVars(&auctionVars)
452	if err != nil {
453		panic(err)
454	}
455
456	const nSlots = 20
457
458	// Set testCommon
459	usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
460	poolTxsToSend, poolTxsToReceive := genTestPoolTx(accs, []babyjub.PrivateKey{privK}, tokensUSD) // NOTE: pool txs are not inserted to the DB here. In the test they will be posted and getted.
461	testBatches, fullBatches := genTestBatches(blocks, batches, allTxs)
462	/* usrExits, allExits*/ _, _ = genTestExits(exits, tokensUSD, accs, usrIdxs)
463
464	// NEW WITH TIL
465	// Reset DB
466	test.WipeDB(api.h.DB())
467
468	tcc := til.NewContext(common.RollupConstMaxL1UserTx)
469	tilCfgExtra := til.ConfigExtra{
470		BootCoordAddr: ethCommon.HexToAddress("0xE39fEc6224708f0772D2A74fd3f9055A90E0A9f2"),
471		CoordUser:     "A",
472	}
473	blocksData, err := tcc.GenerateBlocks(SetBlockchain)
474	if err != nil {
475		panic(err)
476	}
477	err = tcc.FillBlocksExtra(blocksData, &tilCfgExtra)
478	if err != nil {
479		panic(err)
480	}
481	// poolL2Txs, err := tcc.GeneratePoolL2Txs(til.SetPoolL2MinimumFlow0)
482
483	var blocksTc []common.Block
484	var batchesTc []common.Batch
485	var tokensTc []common.Token
486	var accountsTc []common.Account
487	var exitTreeTc []common.ExitInfo
488	var allL1TxsTc []common.L1Tx
489	var allL2TxsTc []common.L2Tx
490
491	AddAditionalInformation(blocksData)
492
493	for _, block := range blocksData {
494		// Insert block
495		err := api.h.AddBlockSCData(&block)
496		if err != nil {
497			panic(err)
498		}
499		blocksTc = append(blocksTc, block.Block)
500		tokensTc = append(tokensTc, block.Rollup.AddedTokens...)
501		allL1TxsTc = append(allL1TxsTc, block.Rollup.L1UserTxs...)
502		for _, batch := range block.Rollup.Batches {
503			allL2TxsTc = append(allL2TxsTc, batch.L2Txs...)
504			accountsTc = append(accountsTc, batch.CreatedAccounts...)
505			batchesTc = append(batchesTc, batch.Batch)
506			exitTreeTc = append(exitTreeTc, batch.ExitTree...)
507			allL1TxsTc = append(allL1TxsTc, batch.L1CoordinatorTxs...)
508		}
509	}
510	// lastBlockNum2 := blocksData[len(blocksData)-1].Block.EthBlockNum
511
512	tokensTc = append([]common.Token{ethToken}, tokensTc...)
513	tokensUSDTc := []historydb.TokenWithUSD{}
514	for i, tkn := range tokensTc {
515		token := historydb.TokenWithUSD{
516			TokenID:     tkn.TokenID,
517			EthBlockNum: tkn.EthBlockNum,
518			EthAddr:     tkn.EthAddr,
519			Name:        tkn.Name,
520			Symbol:      tkn.Symbol,
521			Decimals:    tkn.Decimals,
522		}
523		// Set value of 50% of the tokens
524		if i%2 != 0 {
525			value := float64(i) * 1.234567
526			now := time.Now().UTC()
527			token.USD = &value
528			token.USDUpdate = &now
529			err = api.h.UpdateTokenValue(token.Symbol, value)
530			if err != nil {
531				panic(err)
532			}
533		}
534		tokensUSDTc = append(tokensUSDTc, token)
535	}
536
537	for i := 0; i < len(accountsTc); i++ {
538		if _, err := api.s.CreateAccount(accountsTc[i].Idx, &accountsTc[i]); err != nil {
539			panic(err)
540		}
541	}
542
543	usrIdxsTc := []string{}
544	for _, acc := range accountsTc {
545		for _, token := range tokensTc {
546			if token.TokenID == acc.TokenID {
547				usrIdxsTc = append(usrIdxsTc, idxToHez(acc.Idx, token.Symbol))
548			}
549		}
550	}
551
552	// Sort txs
553	sortedTxsTc := []txSortFielder{}
554	for i := 0; i < len(allL1TxsTc); i++ {
555		wL1 := wrappedL1(allL1TxsTc[i])
556		sortedTxsTc = append(sortedTxsTc, &wL1)
557	}
558	for i := 0; i < len(allL2TxsTc); i++ {
559		wL2 := wrappedL2(allL2TxsTc[i])
560		sortedTxsTc = append(sortedTxsTc, &wL2)
561	}
562	sort.Sort(txsSort(sortedTxsTc))
563
564	// Coordinators
565	const nCoordsTc = 10
566	coordsTc := test.GenCoordinators(nCoordsTc, blocksTc)
567	err = api.h.AddCoordinators(coordsTc)
568	if err != nil {
569		panic(err)
570	}
571	coordinatorsTc, _, err := api.h.GetCoordinatorsAPI(&fromItem, &limit, historydb.OrderAsc)
572	if err != nil {
573		panic(err)
574	}
575
576	// Bids
577	const nBidsTc = 20
578	bidsTc := test.GenBids(nBidsTc, blocksTc, coordsTc)
579	err = api.h.AddBids(bidsTc)
580	if err != nil {
581		panic(err)
582	}
583	testBidsTc := genTestBids(blocksTc, coordinatorsTc, bidsTc)
584	usrExitsTc, allExitsTc := genTestExits(exitTreeTc, tokensUSDTc, accountsTc, usrIdxsTc)
585	_, allTxsTc := genTestTxs(sortedTxsTc, usrIdxsTc, accountsTc, tokensUSDTc, blocksTc)
586	fmt.Println(allTxsTc)
587	// allTxsTc == allTxs
588	// testBatchesTc, fullBatchesTc := genTestBatches(blocksTc, batchesTc, allTxsTc)
589
590	tc = testCommon{
591		blocks:           blocksTc,
592		tokens:           tokensUSDTc,
593		batches:          testBatches,
594		fullBatches:      fullBatches,
595		coordinators:     coordinatorsTc,
596		accounts:         genTestAccounts(accountsTc, tokensUSDTc),
597		usrAddr:          ethAddrToHez(usrAddr),
598		usrBjj:           bjjToString(usrBjj),
599		accs:             accountsTc,
600		usrTxs:           usrTxs,
601		allTxs:           allTxs,
602		exits:            allExitsTc,
603		usrExits:         usrExitsTc,
604		poolTxsToSend:    poolTxsToSend,
605		poolTxsToReceive: poolTxsToReceive,
606		auths:            genTestAuths(test.GenAuths(5)),
607		router:           router,
608		bids:             testBidsTc,
609		slots:            api.genTestSlots(nSlots, lastBlockNum, testBids, auctionVars),
610		auctionVars:      auctionVars,
611		rollupVars:       rollupVars,
612		wdelayerVars:     wdelayerVars,
613	}
614
615	/* tc = testCommon{
616		blocks:           blocks,
617		tokens:           tokensUSD,
618		batches:          testBatches,
619		fullBatches:      fullBatches,
620		coordinators:     coordinators,
621		accounts:         genTestAccounts(accs, tokensUSD),
622		usrAddr:          ethAddrToHez(usrAddr),
623		usrBjj:           bjjToString(usrBjj),
624		accs:             accs,
625		usrTxs:           usrTxs,
626		allTxs:           allTxs,
627		exits:            allExits,
628		usrExits:         usrExits,
629		poolTxsToSend:    poolTxsToSend,
630		poolTxsToReceive: poolTxsToReceive,
631		auths:            genTestAuths(test.GenAuths(5)),
632		router:           router,
633		bids:             testBids,
634		slots:            api.genTestSlots(nSlots, lastBlockNum, testBids, auctionVars),
635		auctionVars:      auctionVars,
636		rollupVars:       rollupVars,
637		wdelayerVars:     wdelayerVars,
638	} */
639
640	// Fake server
641	if os.Getenv("FAKE_SERVER") == "yes" {
642		for {
643			log.Info("Running fake server at " + apiURL + " until ^C is received")
644			time.Sleep(30 * time.Second)
645		}
646	}
647	// Run tests
648	result := m.Run()
649	// Stop server
650	if err := server.Shutdown(context.Background()); err != nil {
651		panic(err)
652	}
653	if err := database.Close(); err != nil {
654		panic(err)
655	}
656	if err := os.RemoveAll(dir); err != nil {
657		panic(err)
658	}
659	os.Exit(result)
660}
661
662func doGoodReqPaginated(
663	path, order string,
664	iterStruct Pendinger,
665	appendIter func(res interface{}),
666) error {
667	var next uint64
668	firstIte := true
669	expectedTotal := 0
670	totalReceived := 0
671	for {
672		// Calculate fromItem
673		iterPath := path
674		if firstIte {
675			if order == historydb.OrderDesc {
676				// Fetch first item in reverse order
677				iterPath += "99999" // Asumption that for testing there won't be any itemID > 99999
678			} else {
679				iterPath += "0"
680			}
681		} else {
682			iterPath += strconv.Itoa(int(next))
683		}
684		// Call API to get this iteration items
685		if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
686			return err
687		}
688		appendIter(iterStruct)
689		// Keep iterating?
690		remaining, lastID := iterStruct.GetPending()
691		if remaining == 0 {
692			break
693		}
694		if order == historydb.OrderDesc {
695			next = lastID - 1
696		} else {
697			next = lastID + 1
698		}
699		// Check that the expected amount of items is consistent across iterations
700		totalReceived += iterStruct.Len()
701		if firstIte {
702			firstIte = false
703			expectedTotal = totalReceived + int(remaining)
704		}
705		if expectedTotal != totalReceived+int(remaining) {
706			panic(fmt.Sprintf(
707				"pagination error, totalReceived + remaining should be %d, but is %d",
708				expectedTotal, totalReceived+int(remaining),
709			))
710		}
711	}
712	return nil
713}
714
715func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
716	ctx := context.Background()
717	client := &http.Client{}
718	httpReq, err := http.NewRequest(method, path, reqBody)
719	if err != nil {
720		return err
721	}
722	if reqBody != nil {
723		httpReq.Header.Add("Content-Type", "application/json")
724	}
725	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
726	if err != nil {
727		return err
728	}
729	// Validate request against swagger spec
730	requestValidationInput := &swagger.RequestValidationInput{
731		Request:    httpReq,
732		PathParams: pathParams,
733		Route:      route,
734	}
735	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
736		return err
737	}
738	// Do API call
739	resp, err := client.Do(httpReq)
740	if err != nil {
741		return err
742	}
743	if resp.Body == nil && returnStruct != nil {
744		return errors.New("Nil body")
745	}
746	//nolint
747	defer resp.Body.Close()
748	body, err := ioutil.ReadAll(resp.Body)
749	if err != nil {
750		return err
751	}
752	if resp.StatusCode != 200 {
753		return fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body))
754	}
755	if returnStruct == nil {
756		return nil
757	}
758	// Unmarshal body into return struct
759	if err := json.Unmarshal(body, returnStruct); err != nil {
760		log.Error("invalid json: " + string(body))
761		return err
762	}
763	// Validate response against swagger spec
764	responseValidationInput := &swagger.ResponseValidationInput{
765		RequestValidationInput: requestValidationInput,
766		Status:                 resp.StatusCode,
767		Header:                 resp.Header,
768	}
769	responseValidationInput = responseValidationInput.SetBodyBytes(body)
770	return swagger.ValidateResponse(ctx, responseValidationInput)
771}
772
773func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
774	ctx := context.Background()
775	client := &http.Client{}
776	httpReq, _ := http.NewRequest(method, path, reqBody)
777	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
778	if err != nil {
779		return err
780	}
781	// Validate request against swagger spec
782	requestValidationInput := &swagger.RequestValidationInput{
783		Request:    httpReq,
784		PathParams: pathParams,
785		Route:      route,
786	}
787	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
788		if expectedResponseCode != 400 {
789			return err
790		}
791		log.Warn("The request does not match the API spec")
792	}
793	// Do API call
794	resp, err := client.Do(httpReq)
795	if err != nil {
796		return err
797	}
798	if resp.Body == nil {
799		return errors.New("Nil body")
800	}
801	//nolint
802	defer resp.Body.Close()
803	body, err := ioutil.ReadAll(resp.Body)
804	if err != nil {
805		return err
806	}
807	if resp.StatusCode != expectedResponseCode {
808		return fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body))
809	}
810	// Validate response against swagger spec
811	responseValidationInput := &swagger.ResponseValidationInput{
812		RequestValidationInput: requestValidationInput,
813		Status:                 resp.StatusCode,
814		Header:                 resp.Header,
815	}
816	responseValidationInput = responseValidationInput.SetBodyBytes(body)
817	return swagger.ValidateResponse(ctx, responseValidationInput)
818}
819
820// test helpers
821
822func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
823	for i := 0; i < len(blocks); i++ {
824		if blocks[i].EthBlockNum == blockNum {
825			return blocks[i].Timestamp
826		}
827	}
828	panic("timesamp not found")
829}
830
831func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
832	for i := 0; i < len(tokens); i++ {
833		if tokens[i].TokenID == id {
834			return tokens[i]
835		}
836	}
837	panic("token not found")
838}
839
840func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
841	for _, acc := range accs {
842		if idx == acc.Idx {
843			return getTokenByID(acc.TokenID, tokens)
844		}
845	}
846	panic("token not found")
847}
848
849func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
850	for _, acc := range accs {
851		if acc.Idx == idx {
852			return &acc
853		}
854	}
855	panic("account not found")
856}
857
858func getBlockByNum(ethBlockNum int64, blocks []common.Block) common.Block {
859	for _, b := range blocks {
860		if b.EthBlockNum == ethBlockNum {
861			return b
862		}
863	}
864	panic("block not found")
865}
866
867func getCoordinatorByBidder(bidder ethCommon.Address, coordinators []historydb.CoordinatorAPI) historydb.CoordinatorAPI {
868	for _, c := range coordinators {
869		if c.Bidder == bidder {
870			return c
871		}
872	}
873	panic("coordinator not found")
874}