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.NewAPIConnectionController(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		&NodeConfig{
241			ForgeDelay: 180,
242		},
243	)
244	if err != nil {
245		panic(err)
246	}
247	// Start server
248	listener, err := net.Listen("tcp", apiAddr) //nolint:gosec
249	if err != nil {
250		panic(err)
251	}
252	server := &http.Server{Handler: apiGin}
253	go func() {
254		if err := server.Serve(listener); err != nil &&
255			tracerr.Unwrap(err) != http.ErrServerClosed {
256			panic(err)
257		}
258	}()
259
260	// Reset DB
261	test.WipeDB(api.h.DB())
262
263	// Generate blockchain data with til
264	tcc := til.NewContext(chainID, common.RollupConstMaxL1UserTx)
265	tilCfgExtra := til.ConfigExtra{
266		BootCoordAddr: ethCommon.HexToAddress("0xE39fEc6224708f0772D2A74fd3f9055A90E0A9f2"),
267		CoordUser:     "Coord",
268	}
269	blocksData, err := tcc.GenerateBlocks(SetBlockchain)
270	if err != nil {
271		panic(err)
272	}
273	err = tcc.FillBlocksExtra(blocksData, &tilCfgExtra)
274	if err != nil {
275		panic(err)
276	}
277	err = tcc.FillBlocksForgedL1UserTxs(blocksData)
278	if err != nil {
279		panic(err)
280	}
281	AddAditionalInformation(blocksData)
282	// Generate L2 Txs with til
283	commonPoolTxs, err := tcc.GeneratePoolL2Txs(txsets.SetPoolL2MinimumFlow0)
284	if err != nil {
285		panic(err)
286	}
287
288	// Extract til generated data, and add it to HistoryDB
289	var commonBlocks []common.Block
290	var commonBatches []common.Batch
291	var commonAccounts []common.Account
292	var commonExitTree []common.ExitInfo
293	var commonL1Txs []common.L1Tx
294	var commonL2Txs []common.L2Tx
295	// Add ETH token at the beginning of the array
296	testTokens := []historydb.TokenWithUSD{}
297	ethUSD := float64(500)
298	ethNow := time.Now()
299	testTokens = append(testTokens, historydb.TokenWithUSD{
300		TokenID:     test.EthToken.TokenID,
301		EthBlockNum: test.EthToken.EthBlockNum,
302		EthAddr:     test.EthToken.EthAddr,
303		Name:        test.EthToken.Name,
304		Symbol:      test.EthToken.Symbol,
305		Decimals:    test.EthToken.Decimals,
306		USD:         &ethUSD,
307		USDUpdate:   &ethNow,
308	})
309	err = api.h.UpdateTokenValue(common.EmptyAddr, ethUSD)
310	if err != nil {
311		panic(err)
312	}
313	for _, block := range blocksData {
314		// Insert block into HistoryDB
315		// nolint reason: block is used as read only in the function
316		if err := api.h.AddBlockSCData(&block); err != nil { //nolint:gosec
317			log.Error(err)
318			panic(err)
319		}
320		// Extract data
321		commonBlocks = append(commonBlocks, block.Block)
322		for i, tkn := range block.Rollup.AddedTokens {
323			token := historydb.TokenWithUSD{
324				TokenID:     tkn.TokenID,
325				EthBlockNum: tkn.EthBlockNum,
326				EthAddr:     tkn.EthAddr,
327				Name:        tkn.Name,
328				Symbol:      tkn.Symbol,
329				Decimals:    tkn.Decimals,
330			}
331			value := float64(i + 423)
332			now := time.Now().UTC()
333			token.USD = &value
334			token.USDUpdate = &now
335			// Set value in DB
336			err = api.h.UpdateTokenValue(token.EthAddr, value)
337			if err != nil {
338				panic(err)
339			}
340			testTokens = append(testTokens, token)
341		}
342		// Set USD value for tokens in DB
343		for _, batch := range block.Rollup.Batches {
344			commonL2Txs = append(commonL2Txs, batch.L2Txs...)
345			for i := range batch.CreatedAccounts {
346				batch.CreatedAccounts[i].Nonce = common.Nonce(i)
347				commonAccounts = append(commonAccounts, batch.CreatedAccounts[i])
348			}
349			commonBatches = append(commonBatches, batch.Batch)
350			commonExitTree = append(commonExitTree, batch.ExitTree...)
351			commonL1Txs = append(commonL1Txs, batch.L1UserTxs...)
352			commonL1Txs = append(commonL1Txs, batch.L1CoordinatorTxs...)
353		}
354	}
355
356	// Generate Coordinators and add them to HistoryDB
357	const nCoords = 10
358	commonCoords := test.GenCoordinators(nCoords, commonBlocks)
359	// Update one coordinator to test behaviour when bidder address is repeated
360	updatedCoordBlock := commonCoords[len(commonCoords)-1].EthBlockNum
361	commonCoords = append(commonCoords, common.Coordinator{
362		Bidder:      commonCoords[0].Bidder,
363		Forger:      commonCoords[0].Forger,
364		EthBlockNum: updatedCoordBlock,
365		URL:         commonCoords[0].URL + ".new",
366	})
367	if err := api.h.AddCoordinators(commonCoords); err != nil {
368		panic(err)
369	}
370
371	// Test next forgers
372	// Set auction vars
373	// Slots 3 and 6 will have bids that will be invalidated because of minBid update
374	// Slots 4 and 7 will have valid bids, the rest will be cordinator slots
375	var slot3MinBid int64 = 3
376	var slot4MinBid int64 = 4
377	var slot6MinBid int64 = 6
378	var slot7MinBid int64 = 7
379	// First update will indicate how things behave from slot 0
380	var defaultSlotSetBid [6]*big.Int = [6]*big.Int{
381		big.NewInt(10),          // Slot 0 min bid
382		big.NewInt(10),          // Slot 1 min bid
383		big.NewInt(10),          // Slot 2 min bid
384		big.NewInt(slot3MinBid), // Slot 3 min bid
385		big.NewInt(slot4MinBid), // Slot 4 min bid
386		big.NewInt(10),          // Slot 5 min bid
387	}
388	auctionVars := common.AuctionVariables{
389		EthBlockNum:              int64(2),
390		DonationAddress:          ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
391		DefaultSlotSetBid:        defaultSlotSetBid,
392		DefaultSlotSetBidSlotNum: 0,
393		Outbidding:               uint16(1),
394		SlotDeadline:             uint8(20),
395		BootCoordinator:          ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
396		BootCoordinatorURL:       "https://boot.coordinator.io",
397		ClosedAuctionSlots:       uint16(10),
398		OpenAuctionSlots:         uint16(20),
399	}
400	if err := api.h.AddAuctionVars(&auctionVars); err != nil {
401		panic(err)
402	}
403	// Last update in auction vars will indicate how things will behave from slot 5
404	defaultSlotSetBid = [6]*big.Int{
405		big.NewInt(10),          // Slot 5 min bid
406		big.NewInt(slot6MinBid), // Slot 6 min bid
407		big.NewInt(slot7MinBid), // Slot 7 min bid
408		big.NewInt(10),          // Slot 8 min bid
409		big.NewInt(10),          // Slot 9 min bid
410		big.NewInt(10),          // Slot 10 min bid
411	}
412	auctionVars = common.AuctionVariables{
413		EthBlockNum:              int64(3),
414		DonationAddress:          ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
415		DefaultSlotSetBid:        defaultSlotSetBid,
416		DefaultSlotSetBidSlotNum: 5,
417		Outbidding:               uint16(1),
418		SlotDeadline:             uint8(20),
419		BootCoordinator:          ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
420		BootCoordinatorURL:       "https://boot.coordinator.io",
421		ClosedAuctionSlots:       uint16(10),
422		OpenAuctionSlots:         uint16(20),
423	}
424	if err := api.h.AddAuctionVars(&auctionVars); err != nil {
425		panic(err)
426	}
427
428	// Generate Bids and add them to HistoryDB
429	bids := []common.Bid{}
430	// Slot 1 and 2, no bids, wins boot coordinator
431	// Slot 3, below what's going to be the minimum (wins boot coordinator)
432	bids = append(bids, common.Bid{
433		SlotNum:     3,
434		BidValue:    big.NewInt(slot3MinBid - 1),
435		EthBlockNum: commonBlocks[0].Num,
436		Bidder:      commonCoords[0].Bidder,
437	})
438	// Slot 4, valid bid (wins bidder)
439	bids = append(bids, common.Bid{
440		SlotNum:     4,
441		BidValue:    big.NewInt(slot4MinBid),
442		EthBlockNum: commonBlocks[0].Num,
443		Bidder:      commonCoords[0].Bidder,
444	})
445	// Slot 5 no bids, wins boot coordinator
446	// Slot 6, below what's going to be the minimum (wins boot coordinator)
447	bids = append(bids, common.Bid{
448		SlotNum:     6,
449		BidValue:    big.NewInt(slot6MinBid - 1),
450		EthBlockNum: commonBlocks[0].Num,
451		Bidder:      commonCoords[0].Bidder,
452	})
453	// Slot 7, valid bid (wins bidder)
454	bids = append(bids, common.Bid{
455		SlotNum:     7,
456		BidValue:    big.NewInt(slot7MinBid),
457		EthBlockNum: commonBlocks[0].Num,
458		Bidder:      commonCoords[0].Bidder,
459	})
460	if err = api.h.AddBids(bids); err != nil {
461		panic(err)
462	}
463	bootForger := NextForger{
464		Coordinator: historydb.CoordinatorAPI{
465			Forger: auctionVars.BootCoordinator,
466			URL:    auctionVars.BootCoordinatorURL,
467		},
468	}
469	// Set next forgers: set all as boot coordinator then replace the non boot coordinators
470	nextForgers := []NextForger{}
471	var initBlock int64 = 140
472	var deltaBlocks int64 = 40
473	for i := 1; i < int(auctionVars.ClosedAuctionSlots)+2; i++ {
474		fromBlock := initBlock + deltaBlocks*int64(i-1)
475		bootForger.Period = Period{
476			SlotNum:   int64(i),
477			FromBlock: fromBlock,
478			ToBlock:   fromBlock + deltaBlocks - 1,
479		}
480		nextForgers = append(nextForgers, bootForger)
481	}
482	// Set next forgers that aren't the boot coordinator
483	nonBootForger := historydb.CoordinatorAPI{
484		Bidder: commonCoords[0].Bidder,
485		Forger: commonCoords[0].Forger,
486		URL:    commonCoords[0].URL + ".new",
487	}
488	// Slot 4
489	nextForgers[3].Coordinator = nonBootForger
490	// Slot 7
491	nextForgers[6].Coordinator = nonBootForger
492
493	var buckets [common.RollupConstNumBuckets]common.BucketParams
494	for i := range buckets {
495		buckets[i].CeilUSD = big.NewInt(int64(i) * 10)
496		buckets[i].Withdrawals = big.NewInt(int64(i) * 100)
497		buckets[i].BlockWithdrawalRate = big.NewInt(int64(i) * 1000)
498		buckets[i].MaxWithdrawals = big.NewInt(int64(i) * 10000)
499	}
500
501	// Generate SC vars and add them to HistoryDB (if needed)
502	rollupVars := common.RollupVariables{
503		EthBlockNum:           int64(3),
504		FeeAddToken:           big.NewInt(100),
505		ForgeL1L2BatchTimeout: int64(44),
506		WithdrawalDelay:       uint64(3000),
507		Buckets:               buckets,
508		SafeMode:              false,
509	}
510
511	wdelayerVars := common.WDelayerVariables{
512		WithdrawalDelay: uint64(3000),
513	}
514
515	// Generate test data, as expected to be received/sent from/to the API
516	testCoords := genTestCoordinators(commonCoords)
517	testBids := genTestBids(commonBlocks, testCoords, bids)
518	testExits := genTestExits(commonExitTree, testTokens, commonAccounts)
519	testTxs := genTestTxs(commonL1Txs, commonL2Txs, commonAccounts, testTokens, commonBlocks)
520	testBatches, testFullBatches := genTestBatches(commonBlocks, commonBatches, testTxs)
521	poolTxsToSend, poolTxsToReceive := genTestPoolTxs(commonPoolTxs, testTokens, commonAccounts)
522	// Add balance and nonce to historyDB
523	accounts := genTestAccounts(commonAccounts, testTokens)
524	accUpdates := []common.AccountUpdate{}
525	for i := 0; i < len(accounts); i++ {
526		balance := new(big.Int)
527		balance.SetString(string(*accounts[i].Balance), 10)
528		idx, err := stringToIdx(string(accounts[i].Idx), "foo")
529		if err != nil {
530			panic(err)
531		}
532		accUpdates = append(accUpdates, common.AccountUpdate{
533			EthBlockNum: 0,
534			BatchNum:    1,
535			Idx:         *idx,
536			Nonce:       0,
537			Balance:     balance,
538		})
539		accUpdates = append(accUpdates, common.AccountUpdate{
540			EthBlockNum: 0,
541			BatchNum:    1,
542			Idx:         *idx,
543			Nonce:       accounts[i].Nonce,
544			Balance:     balance,
545		})
546	}
547	if err := api.h.AddAccountUpdates(accUpdates); err != nil {
548		panic(err)
549	}
550	tc = testCommon{
551		blocks:           commonBlocks,
552		tokens:           testTokens,
553		batches:          testBatches,
554		fullBatches:      testFullBatches,
555		coordinators:     testCoords,
556		accounts:         accounts,
557		txs:              testTxs,
558		exits:            testExits,
559		poolTxsToSend:    poolTxsToSend,
560		poolTxsToReceive: poolTxsToReceive,
561		auths:            genTestAuths(test.GenAuths(5, _config.ChainID, _config.HermezAddress)),
562		router:           router,
563		bids:             testBids,
564		slots: api.genTestSlots(
565			20,
566			commonBlocks[len(commonBlocks)-1].Num,
567			testBids,
568			auctionVars,
569		),
570		auctionVars:  auctionVars,
571		rollupVars:   rollupVars,
572		wdelayerVars: wdelayerVars,
573		nextForgers:  nextForgers,
574	}
575
576	// Run tests
577	result := m.Run()
578	// Fake server
579	if os.Getenv("FAKE_SERVER") == "yes" {
580		for {
581			log.Info("Running fake server at " + apiURL + " until ^C is received")
582			time.Sleep(30 * time.Second)
583		}
584	}
585	// Stop server
586	if err := server.Shutdown(context.Background()); err != nil {
587		panic(err)
588	}
589	if err := database.Close(); err != nil {
590		panic(err)
591	}
592	if err := os.RemoveAll(dir); err != nil {
593		panic(err)
594	}
595	os.Exit(result)
596}
597
598func TestTimeout(t *testing.T) {
599	pass := os.Getenv("POSTGRES_PASS")
600	databaseTO, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
601	require.NoError(t, err)
602	apiConnConTO := db.NewAPIConnectionController(1, 100*time.Millisecond)
603	hdbTO := historydb.NewHistoryDB(databaseTO, databaseTO, apiConnConTO)
604	require.NoError(t, err)
605	// L2DB
606	l2DBTO := l2db.NewL2DB(databaseTO, databaseTO, 10, 1000, 1.0, 24*time.Hour, apiConnConTO)
607
608	// API
609	apiGinTO := gin.Default()
610	finishWait := make(chan interface{})
611	startWait := make(chan interface{})
612	apiGinTO.GET("/wait", func(c *gin.Context) {
613		cancel, err := apiConnConTO.Acquire()
614		defer cancel()
615		require.NoError(t, err)
616		defer apiConnConTO.Release()
617		startWait <- nil
618		<-finishWait
619	})
620	// Start server
621	serverTO := &http.Server{Handler: apiGinTO}
622	listener, err := net.Listen("tcp", ":4444") //nolint:gosec
623	require.NoError(t, err)
624	go func() {
625		if err := serverTO.Serve(listener); err != nil &&
626			tracerr.Unwrap(err) != http.ErrServerClosed {
627			require.NoError(t, err)
628		}
629	}()
630	_config := getConfigTest(0)
631	_, err = NewAPI(
632		true,
633		true,
634		apiGinTO,
635		hdbTO,
636		l2DBTO,
637		&_config,
638		&NodeConfig{
639			ForgeDelay: 180,
640		},
641	)
642	require.NoError(t, err)
643
644	client := &http.Client{}
645	httpReq, err := http.NewRequest("GET", "http://localhost:4444/tokens", nil)
646	require.NoError(t, err)
647	httpReqWait, err := http.NewRequest("GET", "http://localhost:4444/wait", nil)
648	require.NoError(t, err)
649	// Request that will get timed out
650	var wg sync.WaitGroup
651	wg.Add(1)
652	go func() {
653		// Request that will make the API busy
654		_, err = client.Do(httpReqWait)
655		require.NoError(t, err)
656		wg.Done()
657	}()
658	<-startWait
659	resp, err := client.Do(httpReq)
660	require.NoError(t, err)
661	require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
662	defer resp.Body.Close() //nolint
663	body, err := ioutil.ReadAll(resp.Body)
664	require.NoError(t, err)
665	// Unmarshal body into return struct
666	msg := &errorMsg{}
667	err = json.Unmarshal(body, msg)
668	require.NoError(t, err)
669	// Check that the error was the expected down
670	require.Equal(t, errSQLTimeout, msg.Message)
671	finishWait <- nil
672
673	// Stop server
674	wg.Wait()
675	require.NoError(t, serverTO.Shutdown(context.Background()))
676	require.NoError(t, databaseTO.Close())
677}
678
679func doGoodReqPaginated(
680	path, order string,
681	iterStruct Pendinger,
682	appendIter func(res interface{}),
683) error {
684	var next uint64
685	firstIte := true
686	expectedTotal := 0
687	totalReceived := 0
688	for {
689		// Calculate fromItem
690		iterPath := path
691		if !firstIte {
692			iterPath += "&fromItem=" + strconv.Itoa(int(next))
693		}
694		// Call API to get this iteration items
695		iterStruct = iterStruct.New()
696		if err := doGoodReq(
697			"GET", iterPath+"&order="+order, nil,
698			iterStruct,
699		); err != nil {
700			return tracerr.Wrap(err)
701		}
702		appendIter(iterStruct)
703		// Keep iterating?
704		remaining, lastID := iterStruct.GetPending()
705		if remaining == 0 {
706			break
707		}
708		if order == historydb.OrderDesc {
709			next = lastID - 1
710		} else {
711			next = lastID + 1
712		}
713		// Check that the expected amount of items is consistent across iterations
714		totalReceived += iterStruct.Len()
715		if firstIte {
716			firstIte = false
717			expectedTotal = totalReceived + int(remaining)
718		}
719		if expectedTotal != totalReceived+int(remaining) {
720			panic(fmt.Sprintf(
721				"pagination error, totalReceived + remaining should be %d, but is %d",
722				expectedTotal, totalReceived+int(remaining),
723			))
724		}
725	}
726	return nil
727}
728
729func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
730	ctx := context.Background()
731	client := &http.Client{}
732	httpReq, err := http.NewRequest(method, path, reqBody)
733	if err != nil {
734		return tracerr.Wrap(err)
735	}
736	if reqBody != nil {
737		httpReq.Header.Add("Content-Type", "application/json")
738	}
739	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
740	if err != nil {
741		return tracerr.Wrap(err)
742	}
743	// Validate request against swagger spec
744	requestValidationInput := &swagger.RequestValidationInput{
745		Request:    httpReq,
746		PathParams: pathParams,
747		Route:      route,
748	}
749	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
750		return tracerr.Wrap(err)
751	}
752	// Do API call
753	resp, err := client.Do(httpReq)
754	if err != nil {
755		return tracerr.Wrap(err)
756	}
757	if resp.Body == nil && returnStruct != nil {
758		return tracerr.Wrap(errors.New("Nil body"))
759	}
760	//nolint
761	defer resp.Body.Close()
762	body, err := ioutil.ReadAll(resp.Body)
763	if err != nil {
764		return tracerr.Wrap(err)
765	}
766	if resp.StatusCode != 200 {
767		return tracerr.Wrap(fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body)))
768	}
769	if returnStruct == nil {
770		return nil
771	}
772	// Unmarshal body into return struct
773	if err := json.Unmarshal(body, returnStruct); err != nil {
774		log.Error("invalid json: " + string(body))
775		log.Error(err)
776		return tracerr.Wrap(err)
777	}
778	// log.Info(string(body))
779	// Validate response against swagger spec
780	responseValidationInput := &swagger.ResponseValidationInput{
781		RequestValidationInput: requestValidationInput,
782		Status:                 resp.StatusCode,
783		Header:                 resp.Header,
784	}
785	responseValidationInput = responseValidationInput.SetBodyBytes(body)
786	return swagger.ValidateResponse(ctx, responseValidationInput)
787}
788
789func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
790	ctx := context.Background()
791	client := &http.Client{}
792	httpReq, _ := http.NewRequest(method, path, reqBody)
793	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
794	if err != nil {
795		return tracerr.Wrap(err)
796	}
797	// Validate request against swagger spec
798	requestValidationInput := &swagger.RequestValidationInput{
799		Request:    httpReq,
800		PathParams: pathParams,
801		Route:      route,
802	}
803	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
804		if expectedResponseCode != 400 {
805			return tracerr.Wrap(err)
806		}
807		log.Warn("The request does not match the API spec")
808	}
809	// Do API call
810	resp, err := client.Do(httpReq)
811	if err != nil {
812		return tracerr.Wrap(err)
813	}
814	if resp.Body == nil {
815		return tracerr.Wrap(errors.New("Nil body"))
816	}
817	//nolint
818	defer resp.Body.Close()
819	body, err := ioutil.ReadAll(resp.Body)
820	if err != nil {
821		return tracerr.Wrap(err)
822	}
823	if resp.StatusCode != expectedResponseCode {
824		return tracerr.Wrap(fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body)))
825	}
826	// Validate response against swagger spec
827	responseValidationInput := &swagger.ResponseValidationInput{
828		RequestValidationInput: requestValidationInput,
829		Status:                 resp.StatusCode,
830		Header:                 resp.Header,
831	}
832	responseValidationInput = responseValidationInput.SetBodyBytes(body)
833	return swagger.ValidateResponse(ctx, responseValidationInput)
834}
835
836// test helpers
837
838func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
839	for i := 0; i < len(blocks); i++ {
840		if blocks[i].Num == blockNum {
841			return blocks[i].Timestamp
842		}
843	}
844	panic("timesamp not found")
845}
846
847func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
848	for i := 0; i < len(tokens); i++ {
849		if tokens[i].TokenID == id {
850			return tokens[i]
851		}
852	}
853	panic("token not found")
854}
855
856func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
857	for _, acc := range accs {
858		if idx == acc.Idx {
859			return getTokenByID(acc.TokenID, tokens)
860		}
861	}
862	panic("token not found")
863}
864
865func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
866	for _, acc := range accs {
867		if acc.Idx == idx {
868			return &acc
869		}
870	}
871	panic("account not found")
872}
873
874func getBlockByNum(ethBlockNum int64, blocks []common.Block) common.Block {
875	for _, b := range blocks {
876		if b.Num == ethBlockNum {
877			return b
878		}
879	}
880	panic("block not found")
881}
882
883func getCoordinatorByBidder(bidder ethCommon.Address, coordinators []historydb.CoordinatorAPI) historydb.CoordinatorAPI {
884	var coordLastUpdate historydb.CoordinatorAPI
885	found := false
886	for _, c := range coordinators {
887		if c.Bidder == bidder {
888			coordLastUpdate = c
889			found = true
890		}
891	}
892	if !found {
893		panic("coordinator not found")
894	}
895	return coordLastUpdate
896}