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, 100, 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: ðUSD,
296 USDUpdate: ðNow,
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}