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