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