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