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