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