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	"strconv"
 14	"sync"
 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/log"
 26	"github.com/hermeznetwork/hermez-node/test"
 27	"github.com/hermeznetwork/hermez-node/test/til"
 28	"github.com/hermeznetwork/hermez-node/test/txsets"
 29	"github.com/hermeznetwork/tracerr"
 30	"github.com/stretchr/testify/require"
 31)
 32
 33// Pendinger is an interface that allows getting last returned item ID and PendingItems to be used for building fromItem
 34// when testing paginated endpoints.
 35type Pendinger interface {
 36	GetPending() (pendingItems, lastItemID uint64)
 37	Len() int
 38	New() Pendinger
 39}
 40
 41const apiPort = ":4010"
 42const apiURL = "http://localhost" + apiPort + "/"
 43
 44var SetBlockchain = `
 45	Type: Blockchain
 46
 47	AddToken(1)
 48	AddToken(2)
 49	AddToken(3)
 50	AddToken(4)
 51	AddToken(5)
 52	AddToken(6)
 53	AddToken(7)
 54	AddToken(8)
 55	> block
 56
 57	// Coordinator accounts, Idxs: 256, 257
 58	CreateAccountCoordinator(0) Coord
 59	CreateAccountCoordinator(1) Coord
 60
 61	// close Block:0, Batch:1
 62	> batch
 63
 64	CreateAccountDeposit(0) A: 11100000000000000
 65	CreateAccountDeposit(1) C: 22222222200000000000
 66	CreateAccountCoordinator(0) C
 67
 68	// close Block:0, Batch:2
 69	> batchL1
 70	// Expected balances:
 71	//     Coord(0): 0, Coord(1): 0
 72	//     C(0): 0
 73
 74	CreateAccountDeposit(1) A: 33333333300000000000
 75
 76	// close Block:0, Batch:3
 77	> batchL1
 78
 79	// close Block:0, Batch:4
 80	> batchL1
 81
 82	CreateAccountDepositTransfer(0) B-A: 44444444400000000000, 123444444400000000000
 83
 84	// close Block:0, Batch:5
 85	> batchL1
 86	CreateAccountDeposit(0) D: 55555555500000000000
 87
 88	// close Block:0, Batch:6
 89	> batchL1
 90
 91	CreateAccountCoordinator(1) B
 92
 93	Transfer(1) A-B: 11100000000000000 (2)
 94	Transfer(0) B-C: 22200000000000000 (3)
 95
 96	// close Block:0, Batch:7
 97	> batchL1 // forge L1User{1}, forge L1Coord{2}, forge L2{2}
 98
 99	Deposit(0) C: 66666666600000000000
100	DepositTransfer(0) C-D: 77777777700000000000, 12377777700000000000
101
102	Transfer(0) A-B: 33350000000000000 (111)
103	Transfer(0) C-A: 44450000000000000 (222)
104	Transfer(1) B-C: 55550000000000000 (123)
105	Exit(0) A: 66650000000000000 (44)
106
107	ForceTransfer(0) D-B: 77777700000000000
108	ForceExit(0) B: 88888800000000000
109
110	// close Block:0, Batch:8
111	> batchL1
112	> block
113
114	Transfer(0) D-A: 99950000000000000 (77)
115	Transfer(0) B-D: 12300000000000000 (55)
116
117	// close Block:1, Batch:1
118	> batchL1
119
120	CreateAccountCoordinator(0) F
121
122	CreateAccountCoordinator(0) G
123	CreateAccountCoordinator(0) H
124	CreateAccountCoordinator(0) I
125	CreateAccountCoordinator(0) J
126	CreateAccountCoordinator(0) K
127	CreateAccountCoordinator(0) L
128	CreateAccountCoordinator(0) M
129	CreateAccountCoordinator(0) N
130	CreateAccountCoordinator(0) O
131	CreateAccountCoordinator(0) P
132
133	CreateAccountCoordinator(5) G
134	CreateAccountCoordinator(5) H
135	CreateAccountCoordinator(5) I
136	CreateAccountCoordinator(5) J
137	CreateAccountCoordinator(5) K
138	CreateAccountCoordinator(5) L
139	CreateAccountCoordinator(5) M
140	CreateAccountCoordinator(5) N
141	CreateAccountCoordinator(5) O
142	CreateAccountCoordinator(5) P
143
144	CreateAccountCoordinator(2) G
145	CreateAccountCoordinator(2) H
146	CreateAccountCoordinator(2) I
147	CreateAccountCoordinator(2) J
148	CreateAccountCoordinator(2) K
149	CreateAccountCoordinator(2) L
150	CreateAccountCoordinator(2) M
151	CreateAccountCoordinator(2) N
152	CreateAccountCoordinator(2) O
153	CreateAccountCoordinator(2) P
154
155
156	> batch
157	> block
158	> batch
159	> block
160	> batch
161	> block
162`
163
164type testCommon struct {
165	blocks           []common.Block
166	tokens           []historydb.TokenWithUSD
167	batches          []testBatch
168	fullBatches      []testFullBatch
169	coordinators     []historydb.CoordinatorAPI
170	accounts         []testAccount
171	txs              []testTx
172	exits            []testExit
173	poolTxsToSend    []testPoolTxSend
174	poolTxsToReceive []testPoolTxReceive
175	auths            []testAuth
176	router           *swagger.Router
177	bids             []testBid
178	slots            []testSlot
179	auctionVars      common.AuctionVariables
180	rollupVars       common.RollupVariables
181	wdelayerVars     common.WDelayerVariables
182	nextForgers      []NextForger
183}
184
185var tc testCommon
186var config configAPI
187var api *API
188
189// TestMain initializes the API server, and fill HistoryDB and StateDB with fake data,
190// emulating the task of the synchronizer in order to have data to be returned
191// by the API endpoints that will be tested
192func TestMain(m *testing.M) {
193	// Initializations
194	// Swagger
195	router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
196	// HistoryDB
197	pass := os.Getenv("POSTGRES_PASS")
198
199	database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
200	if err != nil {
201		panic(err)
202	}
203	apiConnCon := db.NewAPICnnectionController(1, time.Second)
204	hdb := historydb.NewHistoryDB(database, database, apiConnCon)
205	if err != nil {
206		panic(err)
207	}
208	// StateDB
209	dir, err := ioutil.TempDir("", "tmpdb")
210	if err != nil {
211		panic(err)
212	}
213	defer func() {
214		if err := os.RemoveAll(dir); err != nil {
215			panic(err)
216		}
217	}()
218	// L2DB
219	l2DB := l2db.NewL2DB(database, database, 10, 1000, 0.0, 24*time.Hour, apiConnCon)
220	test.WipeDB(l2DB.DB()) // this will clean HistoryDB and L2DB
221	// Config (smart contract constants)
222	chainID := uint16(0)
223	_config := getConfigTest(chainID)
224	config = configAPI{
225		RollupConstants:   *newRollupConstants(_config.RollupConstants),
226		AuctionConstants:  _config.AuctionConstants,
227		WDelayerConstants: _config.WDelayerConstants,
228	}
229
230	// API
231	apiGin := gin.Default()
232	api, err = NewAPI(
233		true,
234		true,
235		apiGin,
236		hdb,
237		l2DB,
238		&_config,
239	)
240	if err != nil {
241		panic(err)
242	}
243	// Start server
244	server := &http.Server{Addr: apiPort, Handler: apiGin}
245	go func() {
246		if err := server.ListenAndServe(); err != nil && tracerr.Unwrap(err) != http.ErrServerClosed {
247			panic(err)
248		}
249	}()
250
251	// Reset DB
252	test.WipeDB(api.h.DB())
253
254	// Genratre blockchain data with til
255	tcc := til.NewContext(chainID, common.RollupConstMaxL1UserTx)
256	tilCfgExtra := til.ConfigExtra{
257		BootCoordAddr: ethCommon.HexToAddress("0xE39fEc6224708f0772D2A74fd3f9055A90E0A9f2"),
258		CoordUser:     "Coord",
259	}
260	blocksData, err := tcc.GenerateBlocks(SetBlockchain)
261	if err != nil {
262		panic(err)
263	}
264	err = tcc.FillBlocksExtra(blocksData, &tilCfgExtra)
265	if err != nil {
266		panic(err)
267	}
268	err = tcc.FillBlocksForgedL1UserTxs(blocksData)
269	if err != nil {
270		panic(err)
271	}
272	AddAditionalInformation(blocksData)
273	// Generate L2 Txs with til
274	commonPoolTxs, err := tcc.GeneratePoolL2Txs(txsets.SetPoolL2MinimumFlow0)
275	if err != nil {
276		panic(err)
277	}
278
279	// Extract til generated data, and add it to HistoryDB
280	var commonBlocks []common.Block
281	var commonBatches []common.Batch
282	var commonAccounts []common.Account
283	var commonExitTree []common.ExitInfo
284	var commonL1Txs []common.L1Tx
285	var commonL2Txs []common.L2Tx
286	// Add ETH token at the beginning of the array
287	testTokens := []historydb.TokenWithUSD{}
288	ethUSD := float64(500)
289	ethNow := time.Now()
290	testTokens = append(testTokens, historydb.TokenWithUSD{
291		TokenID:     test.EthToken.TokenID,
292		EthBlockNum: test.EthToken.EthBlockNum,
293		EthAddr:     test.EthToken.EthAddr,
294		Name:        test.EthToken.Name,
295		Symbol:      test.EthToken.Symbol,
296		Decimals:    test.EthToken.Decimals,
297		USD:         &ethUSD,
298		USDUpdate:   &ethNow,
299	})
300	err = api.h.UpdateTokenValue(test.EthToken.Symbol, ethUSD)
301	if err != nil {
302		panic(err)
303	}
304	for _, block := range blocksData {
305		// Insert block into HistoryDB
306		// nolint reason: block is used as read only in the function
307		if err := api.h.AddBlockSCData(&block); err != nil { //nolint:gosec
308			log.Error(err)
309			panic(err)
310		}
311		// Extract data
312		commonBlocks = append(commonBlocks, block.Block)
313		for i, tkn := range block.Rollup.AddedTokens {
314			token := historydb.TokenWithUSD{
315				TokenID:     tkn.TokenID,
316				EthBlockNum: tkn.EthBlockNum,
317				EthAddr:     tkn.EthAddr,
318				Name:        tkn.Name,
319				Symbol:      tkn.Symbol,
320				Decimals:    tkn.Decimals,
321			}
322			value := float64(i + 423)
323			now := time.Now().UTC()
324			token.USD = &value
325			token.USDUpdate = &now
326			// Set value in DB
327			err = api.h.UpdateTokenValue(token.Symbol, value)
328			if err != nil {
329				panic(err)
330			}
331			testTokens = append(testTokens, token)
332		}
333		// Set USD value for tokens in DB
334		for _, batch := range block.Rollup.Batches {
335			commonL2Txs = append(commonL2Txs, batch.L2Txs...)
336			for i := range batch.CreatedAccounts {
337				batch.CreatedAccounts[i].Nonce = common.Nonce(i)
338				commonAccounts = append(commonAccounts, batch.CreatedAccounts[i])
339			}
340			commonBatches = append(commonBatches, batch.Batch)
341			commonExitTree = append(commonExitTree, batch.ExitTree...)
342			commonL1Txs = append(commonL1Txs, batch.L1UserTxs...)
343			commonL1Txs = append(commonL1Txs, batch.L1CoordinatorTxs...)
344		}
345	}
346
347	// Generate Coordinators and add them to HistoryDB
348	const nCoords = 10
349	commonCoords := test.GenCoordinators(nCoords, commonBlocks)
350	// Update one coordinator to test behaviour when bidder address is repeated
351	updatedCoordBlock := commonCoords[len(commonCoords)-1].EthBlockNum
352	commonCoords = append(commonCoords, common.Coordinator{
353		Bidder:      commonCoords[0].Bidder,
354		Forger:      commonCoords[0].Forger,
355		EthBlockNum: updatedCoordBlock,
356		URL:         commonCoords[0].URL + ".new",
357	})
358	if err := api.h.AddCoordinators(commonCoords); err != nil {
359		panic(err)
360	}
361
362	// Test next forgers
363	// Set auction vars
364	// Slots 3 and 6 will have bids that will be invalidated because of minBid update
365	// Slots 4 and 7 will have valid bids, the rest will be cordinator slots
366	var slot3MinBid int64 = 3
367	var slot4MinBid int64 = 4
368	var slot6MinBid int64 = 6
369	var slot7MinBid int64 = 7
370	// First update will indicate how things behave from slot 0
371	var defaultSlotSetBid [6]*big.Int = [6]*big.Int{
372		big.NewInt(10),          // Slot 0 min bid
373		big.NewInt(10),          // Slot 1 min bid
374		big.NewInt(10),          // Slot 2 min bid
375		big.NewInt(slot3MinBid), // Slot 3 min bid
376		big.NewInt(slot4MinBid), // Slot 4 min bid
377		big.NewInt(10),          // Slot 5 min bid
378	}
379	auctionVars := common.AuctionVariables{
380		EthBlockNum:              int64(2),
381		DonationAddress:          ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
382		DefaultSlotSetBid:        defaultSlotSetBid,
383		DefaultSlotSetBidSlotNum: 0,
384		Outbidding:               uint16(1),
385		SlotDeadline:             uint8(20),
386		BootCoordinator:          ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
387		BootCoordinatorURL:       "https://boot.coordinator.io",
388		ClosedAuctionSlots:       uint16(10),
389		OpenAuctionSlots:         uint16(20),
390	}
391	if err := api.h.AddAuctionVars(&auctionVars); err != nil {
392		panic(err)
393	}
394	// Last update in auction vars will indicate how things will behave from slot 5
395	defaultSlotSetBid = [6]*big.Int{
396		big.NewInt(10),          // Slot 5 min bid
397		big.NewInt(slot6MinBid), // Slot 6 min bid
398		big.NewInt(slot7MinBid), // Slot 7 min bid
399		big.NewInt(10),          // Slot 8 min bid
400		big.NewInt(10),          // Slot 9 min bid
401		big.NewInt(10),          // Slot 10 min bid
402	}
403	auctionVars = common.AuctionVariables{
404		EthBlockNum:              int64(3),
405		DonationAddress:          ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
406		DefaultSlotSetBid:        defaultSlotSetBid,
407		DefaultSlotSetBidSlotNum: 5,
408		Outbidding:               uint16(1),
409		SlotDeadline:             uint8(20),
410		BootCoordinator:          ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
411		BootCoordinatorURL:       "https://boot.coordinator.io",
412		ClosedAuctionSlots:       uint16(10),
413		OpenAuctionSlots:         uint16(20),
414	}
415	if err := api.h.AddAuctionVars(&auctionVars); err != nil {
416		panic(err)
417	}
418
419	// Generate Bids and add them to HistoryDB
420	bids := []common.Bid{}
421	// Slot 1 and 2, no bids, wins boot coordinator
422	// Slot 3, below what's going to be the minimum (wins boot coordinator)
423	bids = append(bids, common.Bid{
424		SlotNum:     3,
425		BidValue:    big.NewInt(slot3MinBid - 1),
426		EthBlockNum: commonBlocks[0].Num,
427		Bidder:      commonCoords[0].Bidder,
428	})
429	// Slot 4, valid bid (wins bidder)
430	bids = append(bids, common.Bid{
431		SlotNum:     4,
432		BidValue:    big.NewInt(slot4MinBid),
433		EthBlockNum: commonBlocks[0].Num,
434		Bidder:      commonCoords[0].Bidder,
435	})
436	// Slot 5 no bids, wins boot coordinator
437	// Slot 6, below what's going to be the minimum (wins boot coordinator)
438	bids = append(bids, common.Bid{
439		SlotNum:     6,
440		BidValue:    big.NewInt(slot6MinBid - 1),
441		EthBlockNum: commonBlocks[0].Num,
442		Bidder:      commonCoords[0].Bidder,
443	})
444	// Slot 7, valid bid (wins bidder)
445	bids = append(bids, common.Bid{
446		SlotNum:     7,
447		BidValue:    big.NewInt(slot7MinBid),
448		EthBlockNum: commonBlocks[0].Num,
449		Bidder:      commonCoords[0].Bidder,
450	})
451	if err = api.h.AddBids(bids); err != nil {
452		panic(err)
453	}
454	bootForger := NextForger{
455		Coordinator: historydb.CoordinatorAPI{
456			Forger: auctionVars.BootCoordinator,
457			URL:    auctionVars.BootCoordinatorURL,
458		},
459	}
460	// Set next forgers: set all as boot coordinator then replace the non boot coordinators
461	nextForgers := []NextForger{}
462	var initBlock int64 = 140
463	var deltaBlocks int64 = 40
464	for i := 1; i < int(auctionVars.ClosedAuctionSlots)+2; i++ {
465		fromBlock := initBlock + deltaBlocks*int64(i-1)
466		bootForger.Period = Period{
467			SlotNum:   int64(i),
468			FromBlock: fromBlock,
469			ToBlock:   fromBlock + deltaBlocks - 1,
470		}
471		nextForgers = append(nextForgers, bootForger)
472	}
473	// Set next forgers that aren't the boot coordinator
474	nonBootForger := historydb.CoordinatorAPI{
475		Bidder: commonCoords[0].Bidder,
476		Forger: commonCoords[0].Forger,
477		URL:    commonCoords[0].URL + ".new",
478	}
479	// Slot 4
480	nextForgers[3].Coordinator = nonBootForger
481	// Slot 7
482	nextForgers[6].Coordinator = nonBootForger
483
484	var buckets [common.RollupConstNumBuckets]common.BucketParams
485	for i := range buckets {
486		buckets[i].CeilUSD = big.NewInt(int64(i) * 10)
487		buckets[i].Withdrawals = big.NewInt(int64(i) * 100)
488		buckets[i].BlockWithdrawalRate = big.NewInt(int64(i) * 1000)
489		buckets[i].MaxWithdrawals = big.NewInt(int64(i) * 10000)
490	}
491
492	// Generate SC vars and add them to HistoryDB (if needed)
493	rollupVars := common.RollupVariables{
494		EthBlockNum:           int64(3),
495		FeeAddToken:           big.NewInt(100),
496		ForgeL1L2BatchTimeout: int64(44),
497		WithdrawalDelay:       uint64(3000),
498		Buckets:               buckets,
499		SafeMode:              false,
500	}
501
502	wdelayerVars := common.WDelayerVariables{
503		WithdrawalDelay: uint64(3000),
504	}
505
506	// Generate test data, as expected to be received/sended from/to the API
507	testCoords := genTestCoordinators(commonCoords)
508	testBids := genTestBids(commonBlocks, testCoords, bids)
509	testExits := genTestExits(commonExitTree, testTokens, commonAccounts)
510	testTxs := genTestTxs(commonL1Txs, commonL2Txs, commonAccounts, testTokens, commonBlocks)
511	testBatches, testFullBatches := genTestBatches(commonBlocks, commonBatches, testTxs)
512	poolTxsToSend, poolTxsToReceive := genTestPoolTxs(commonPoolTxs, testTokens, commonAccounts)
513	// Add balance and nonce to historyDB
514	accounts := genTestAccounts(commonAccounts, testTokens)
515	accUpdates := []common.AccountUpdate{}
516	for i := 0; i < len(accounts); i++ {
517		balance := new(big.Int)
518		balance.SetString(string(*accounts[i].Balance), 10)
519		idx, err := stringToIdx(string(accounts[i].Idx), "foo")
520		if err != nil {
521			panic(err)
522		}
523		accUpdates = append(accUpdates, common.AccountUpdate{
524			EthBlockNum: 0,
525			BatchNum:    1,
526			Idx:         *idx,
527			Nonce:       0,
528			Balance:     balance,
529		})
530		accUpdates = append(accUpdates, common.AccountUpdate{
531			EthBlockNum: 0,
532			BatchNum:    1,
533			Idx:         *idx,
534			Nonce:       accounts[i].Nonce,
535			Balance:     balance,
536		})
537	}
538	if err := api.h.AddAccountUpdates(accUpdates); err != nil {
539		panic(err)
540	}
541	tc = testCommon{
542		blocks:           commonBlocks,
543		tokens:           testTokens,
544		batches:          testBatches,
545		fullBatches:      testFullBatches,
546		coordinators:     testCoords,
547		accounts:         accounts,
548		txs:              testTxs,
549		exits:            testExits,
550		poolTxsToSend:    poolTxsToSend,
551		poolTxsToReceive: poolTxsToReceive,
552		auths:            genTestAuths(test.GenAuths(5, _config.ChainID, _config.HermezAddress)),
553		router:           router,
554		bids:             testBids,
555		slots: api.genTestSlots(
556			20,
557			commonBlocks[len(commonBlocks)-1].Num,
558			testBids,
559			auctionVars,
560		),
561		auctionVars:  auctionVars,
562		rollupVars:   rollupVars,
563		wdelayerVars: wdelayerVars,
564		nextForgers:  nextForgers,
565	}
566
567	// Run tests
568	result := m.Run()
569	// Fake server
570	if os.Getenv("FAKE_SERVER") == "yes" {
571		for {
572			log.Info("Running fake server at " + apiURL + " until ^C is received")
573			time.Sleep(30 * time.Second)
574		}
575	}
576	// Stop server
577	if err := server.Shutdown(context.Background()); err != nil {
578		panic(err)
579	}
580	if err := database.Close(); err != nil {
581		panic(err)
582	}
583	if err := os.RemoveAll(dir); err != nil {
584		panic(err)
585	}
586	os.Exit(result)
587}
588
589func TestTimeout(t *testing.T) {
590	pass := os.Getenv("POSTGRES_PASS")
591	databaseTO, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
592	require.NoError(t, err)
593	apiConnConTO := db.NewAPICnnectionController(1, 100*time.Millisecond)
594	hdbTO := historydb.NewHistoryDB(databaseTO, databaseTO, apiConnConTO)
595	require.NoError(t, err)
596	// L2DB
597	l2DBTO := l2db.NewL2DB(databaseTO, databaseTO, 10, 1000, 1.0, 24*time.Hour, apiConnConTO)
598
599	// API
600	apiGinTO := gin.Default()
601	finishWait := make(chan interface{})
602	startWait := make(chan interface{})
603	apiGinTO.GET("/wait", func(c *gin.Context) {
604		cancel, err := apiConnConTO.Acquire()
605		defer cancel()
606		require.NoError(t, err)
607		defer apiConnConTO.Release()
608		startWait <- nil
609		<-finishWait
610	})
611	// Start server
612	serverTO := &http.Server{Addr: ":4444", Handler: apiGinTO}
613	go func() {
614		if err := serverTO.ListenAndServe(); err != nil && tracerr.Unwrap(err) != http.ErrServerClosed {
615			require.NoError(t, err)
616		}
617	}()
618	_config := getConfigTest(0)
619	_, err = NewAPI(
620		true,
621		true,
622		apiGinTO,
623		hdbTO,
624		l2DBTO,
625		&_config,
626	)
627	require.NoError(t, err)
628
629	client := &http.Client{}
630	httpReq, err := http.NewRequest("GET", "http://localhost:4444/tokens", nil)
631	require.NoError(t, err)
632	httpReqWait, err := http.NewRequest("GET", "http://localhost:4444/wait", nil)
633	require.NoError(t, err)
634	// Request that will get timed out
635	var wg sync.WaitGroup
636	wg.Add(1)
637	go func() {
638		// Request that will make the API busy
639		_, err = client.Do(httpReqWait)
640		require.NoError(t, err)
641		wg.Done()
642	}()
643	<-startWait
644	resp, err := client.Do(httpReq)
645	require.NoError(t, err)
646	require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
647	defer resp.Body.Close() //nolint
648	body, err := ioutil.ReadAll(resp.Body)
649	require.NoError(t, err)
650	// Unmarshal body into return struct
651	msg := &errorMsg{}
652	err = json.Unmarshal(body, msg)
653	require.NoError(t, err)
654	// Check that the error was the expected down
655	require.Equal(t, errSQLTimeout, msg.Message)
656	finishWait <- nil
657
658	// Stop server
659	wg.Wait()
660	require.NoError(t, serverTO.Shutdown(context.Background()))
661	require.NoError(t, databaseTO.Close())
662}
663
664func doGoodReqPaginated(
665	path, order string,
666	iterStruct Pendinger,
667	appendIter func(res interface{}),
668) error {
669	var next uint64
670	firstIte := true
671	expectedTotal := 0
672	totalReceived := 0
673	for {
674		// Calculate fromItem
675		iterPath := path
676		if !firstIte {
677			iterPath += "&fromItem=" + strconv.Itoa(int(next))
678		}
679		// Call API to get this iteration items
680		iterStruct = iterStruct.New()
681		if err := doGoodReq(
682			"GET", iterPath+"&order="+order, nil,
683			iterStruct,
684		); err != nil {
685			return tracerr.Wrap(err)
686		}
687		appendIter(iterStruct)
688		// Keep iterating?
689		remaining, lastID := iterStruct.GetPending()
690		if remaining == 0 {
691			break
692		}
693		if order == historydb.OrderDesc {
694			next = lastID - 1
695		} else {
696			next = lastID + 1
697		}
698		// Check that the expected amount of items is consistent across iterations
699		totalReceived += iterStruct.Len()
700		if firstIte {
701			firstIte = false
702			expectedTotal = totalReceived + int(remaining)
703		}
704		if expectedTotal != totalReceived+int(remaining) {
705			panic(fmt.Sprintf(
706				"pagination error, totalReceived + remaining should be %d, but is %d",
707				expectedTotal, totalReceived+int(remaining),
708			))
709		}
710	}
711	return nil
712}
713
714func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
715	ctx := context.Background()
716	client := &http.Client{}
717	httpReq, err := http.NewRequest(method, path, reqBody)
718	if err != nil {
719		return tracerr.Wrap(err)
720	}
721	if reqBody != nil {
722		httpReq.Header.Add("Content-Type", "application/json")
723	}
724	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
725	if err != nil {
726		return tracerr.Wrap(err)
727	}
728	// Validate request against swagger spec
729	requestValidationInput := &swagger.RequestValidationInput{
730		Request:    httpReq,
731		PathParams: pathParams,
732		Route:      route,
733	}
734	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
735		return tracerr.Wrap(err)
736	}
737	// Do API call
738	resp, err := client.Do(httpReq)
739	if err != nil {
740		return tracerr.Wrap(err)
741	}
742	if resp.Body == nil && returnStruct != nil {
743		return tracerr.Wrap(errors.New("Nil body"))
744	}
745	//nolint
746	defer resp.Body.Close()
747	body, err := ioutil.ReadAll(resp.Body)
748	if err != nil {
749		return tracerr.Wrap(err)
750	}
751	if resp.StatusCode != 200 {
752		return tracerr.Wrap(fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body)))
753	}
754	if returnStruct == nil {
755		return nil
756	}
757	// Unmarshal body into return struct
758	if err := json.Unmarshal(body, returnStruct); err != nil {
759		log.Error("invalid json: " + string(body))
760		log.Error(err)
761		return tracerr.Wrap(err)
762	}
763	// log.Info(string(body))
764	// Validate response against swagger spec
765	responseValidationInput := &swagger.ResponseValidationInput{
766		RequestValidationInput: requestValidationInput,
767		Status:                 resp.StatusCode,
768		Header:                 resp.Header,
769	}
770	responseValidationInput = responseValidationInput.SetBodyBytes(body)
771	return swagger.ValidateResponse(ctx, responseValidationInput)
772}
773
774func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
775	ctx := context.Background()
776	client := &http.Client{}
777	httpReq, _ := http.NewRequest(method, path, reqBody)
778	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
779	if err != nil {
780		return tracerr.Wrap(err)
781	}
782	// Validate request against swagger spec
783	requestValidationInput := &swagger.RequestValidationInput{
784		Request:    httpReq,
785		PathParams: pathParams,
786		Route:      route,
787	}
788	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
789		if expectedResponseCode != 400 {
790			return tracerr.Wrap(err)
791		}
792		log.Warn("The request does not match the API spec")
793	}
794	// Do API call
795	resp, err := client.Do(httpReq)
796	if err != nil {
797		return tracerr.Wrap(err)
798	}
799	if resp.Body == nil {
800		return tracerr.Wrap(errors.New("Nil body"))
801	}
802	//nolint
803	defer resp.Body.Close()
804	body, err := ioutil.ReadAll(resp.Body)
805	if err != nil {
806		return tracerr.Wrap(err)
807	}
808	if resp.StatusCode != expectedResponseCode {
809		return tracerr.Wrap(fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body)))
810	}
811	// Validate response against swagger spec
812	responseValidationInput := &swagger.ResponseValidationInput{
813		RequestValidationInput: requestValidationInput,
814		Status:                 resp.StatusCode,
815		Header:                 resp.Header,
816	}
817	responseValidationInput = responseValidationInput.SetBodyBytes(body)
818	return swagger.ValidateResponse(ctx, responseValidationInput)
819}
820
821// test helpers
822
823func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
824	for i := 0; i < len(blocks); i++ {
825		if blocks[i].Num == blockNum {
826			return blocks[i].Timestamp
827		}
828	}
829	panic("timesamp not found")
830}
831
832func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
833	for i := 0; i < len(tokens); i++ {
834		if tokens[i].TokenID == id {
835			return tokens[i]
836		}
837	}
838	panic("token not found")
839}
840
841func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
842	for _, acc := range accs {
843		if idx == acc.Idx {
844			return getTokenByID(acc.TokenID, tokens)
845		}
846	}
847	panic("token not found")
848}
849
850func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
851	for _, acc := range accs {
852		if acc.Idx == idx {
853			return &acc
854		}
855	}
856	panic("account not found")
857}
858
859func getBlockByNum(ethBlockNum int64, blocks []common.Block) common.Block {
860	for _, b := range blocks {
861		if b.Num == ethBlockNum {
862			return b
863		}
864	}
865	panic("block not found")
866}
867
868func getCoordinatorByBidder(bidder ethCommon.Address, coordinators []historydb.CoordinatorAPI) historydb.CoordinatorAPI {
869	var coordLastUpdate historydb.CoordinatorAPI
870	found := false
871	for _, c := range coordinators {
872		if c.Bidder == bidder {
873			coordLastUpdate = c
874			found = true
875		}
876	}
877	if !found {
878		panic("coordinator not found")
879	}
880	return coordLastUpdate
881}