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