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: 11111111100000000000
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: 11111100000000000 (2)
93 Transfer(0) B-C: 22222200000000000 (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: 33333300000000000 (111)
102 Transfer(0) C-A: 44444400000000000 (222)
103 Transfer(1) B-C: 55555500000000000 (123)
104 Exit(0) A: 66666600000000000 (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: 99999900000000000 (77)
114 Transfer(0) B-D: 12312300000000000 (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(dir, 128, statedb.TypeTxSelector, 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: ðUSD,
301 USDUpdate: ðNow,
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
359 // Generate Coordinators and add them to HistoryDB
360 const nCoords = 10
361 commonCoords := test.GenCoordinators(nCoords, commonBlocks)
362 // Update one coordinator to test behaviour when bidder address is repeated
363 updatedCoordBlock := commonCoords[len(commonCoords)-1].EthBlockNum
364 commonCoords = append(commonCoords, common.Coordinator{
365 Bidder: commonCoords[0].Bidder,
366 Forger: commonCoords[0].Forger,
367 EthBlockNum: updatedCoordBlock,
368 URL: commonCoords[0].URL + ".new",
369 })
370 if err := api.h.AddCoordinators(commonCoords); err != nil {
371 panic(err)
372 }
373
374 // Test next forgers
375 // Set auction vars
376 // Slots 3 and 6 will have bids that will be invalidated because of minBid update
377 // Slots 4 and 7 will have valid bids, the rest will be cordinator slots
378 var slot3MinBid int64 = 3
379 var slot4MinBid int64 = 4
380 var slot6MinBid int64 = 6
381 var slot7MinBid int64 = 7
382 // First update will indicate how things behave from slot 0
383 var defaultSlotSetBid [6]*big.Int = [6]*big.Int{
384 big.NewInt(10), // Slot 0 min bid
385 big.NewInt(10), // Slot 1 min bid
386 big.NewInt(10), // Slot 2 min bid
387 big.NewInt(slot3MinBid), // Slot 3 min bid
388 big.NewInt(slot4MinBid), // Slot 4 min bid
389 big.NewInt(10), // Slot 5 min bid
390 }
391 auctionVars := common.AuctionVariables{
392 EthBlockNum: int64(2),
393 DonationAddress: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
394 DefaultSlotSetBid: defaultSlotSetBid,
395 DefaultSlotSetBidSlotNum: 0,
396 Outbidding: uint16(1),
397 SlotDeadline: uint8(20),
398 BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
399 BootCoordinatorURL: "https://boot.coordinator.io",
400 ClosedAuctionSlots: uint16(10),
401 OpenAuctionSlots: uint16(20),
402 }
403 if err := api.h.AddAuctionVars(&auctionVars); err != nil {
404 panic(err)
405 }
406 // Last update in auction vars will indicate how things will behave from slot 5
407 defaultSlotSetBid = [6]*big.Int{
408 big.NewInt(10), // Slot 5 min bid
409 big.NewInt(slot6MinBid), // Slot 6 min bid
410 big.NewInt(slot7MinBid), // Slot 7 min bid
411 big.NewInt(10), // Slot 8 min bid
412 big.NewInt(10), // Slot 9 min bid
413 big.NewInt(10), // Slot 10 min bid
414 }
415 auctionVars = common.AuctionVariables{
416 EthBlockNum: int64(3),
417 DonationAddress: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
418 DefaultSlotSetBid: defaultSlotSetBid,
419 DefaultSlotSetBidSlotNum: 5,
420 Outbidding: uint16(1),
421 SlotDeadline: uint8(20),
422 BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
423 BootCoordinatorURL: "https://boot.coordinator.io",
424 ClosedAuctionSlots: uint16(10),
425 OpenAuctionSlots: uint16(20),
426 }
427 if err := api.h.AddAuctionVars(&auctionVars); err != nil {
428 panic(err)
429 }
430
431 // Generate Bids and add them to HistoryDB
432 bids := []common.Bid{}
433 // Slot 1 and 2, no bids, wins boot coordinator
434 // Slot 3, below what's going to be the minimum (wins boot coordinator)
435 bids = append(bids, common.Bid{
436 SlotNum: 3,
437 BidValue: big.NewInt(slot3MinBid - 1),
438 EthBlockNum: commonBlocks[0].Num,
439 Bidder: commonCoords[0].Bidder,
440 })
441 // Slot 4, valid bid (wins bidder)
442 bids = append(bids, common.Bid{
443 SlotNum: 4,
444 BidValue: big.NewInt(slot4MinBid),
445 EthBlockNum: commonBlocks[0].Num,
446 Bidder: commonCoords[0].Bidder,
447 })
448 // Slot 5 no bids, wins boot coordinator
449 // Slot 6, below what's going to be the minimum (wins boot coordinator)
450 bids = append(bids, common.Bid{
451 SlotNum: 6,
452 BidValue: big.NewInt(slot6MinBid - 1),
453 EthBlockNum: commonBlocks[0].Num,
454 Bidder: commonCoords[0].Bidder,
455 })
456 // Slot 7, valid bid (wins bidder)
457 bids = append(bids, common.Bid{
458 SlotNum: 7,
459 BidValue: big.NewInt(slot7MinBid),
460 EthBlockNum: commonBlocks[0].Num,
461 Bidder: commonCoords[0].Bidder,
462 })
463 if err = api.h.AddBids(bids); err != nil {
464 panic(err)
465 }
466 bootForger := NextForger{
467 Coordinator: historydb.CoordinatorAPI{
468 Bidder: ethCommon.HexToAddress("0x0111111111111111111111111111111111111111"),
469 Forger: ethCommon.HexToAddress("0x0111111111111111111111111111111111111111"),
470 URL: "https://bootCoordinator",
471 },
472 }
473 // Set next forgers: set all as boot coordinator then replace the non boot coordinators
474 nextForgers := []NextForger{}
475 var initBlock int64 = 140
476 var deltaBlocks int64 = 40
477 for i := 1; i < int(auctionVars.ClosedAuctionSlots)+2; i++ {
478 fromBlock := initBlock + deltaBlocks*int64(i-1)
479 bootForger.Period = Period{
480 SlotNum: int64(i),
481 FromBlock: fromBlock,
482 ToBlock: fromBlock + deltaBlocks - 1,
483 }
484 nextForgers = append(nextForgers, bootForger)
485 }
486 // Set next forgers that aren't the boot coordinator
487 nonBootForger := historydb.CoordinatorAPI{
488 Bidder: commonCoords[0].Bidder,
489 Forger: commonCoords[0].Forger,
490 URL: commonCoords[0].URL + ".new",
491 }
492 // Slot 4
493 nextForgers[3].Coordinator = nonBootForger
494 // Slot 7
495 nextForgers[6].Coordinator = nonBootForger
496
497 var buckets [common.RollupConstNumBuckets]common.BucketParams
498 for i := range buckets {
499 buckets[i].CeilUSD = big.NewInt(int64(i) * 10)
500 buckets[i].Withdrawals = big.NewInt(int64(i) * 100)
501 buckets[i].BlockWithdrawalRate = big.NewInt(int64(i) * 1000)
502 buckets[i].MaxWithdrawals = big.NewInt(int64(i) * 10000)
503 }
504
505 // Generate SC vars and add them to HistoryDB (if needed)
506 rollupVars := common.RollupVariables{
507 EthBlockNum: int64(3),
508 FeeAddToken: big.NewInt(100),
509 ForgeL1L2BatchTimeout: int64(44),
510 WithdrawalDelay: uint64(3000),
511 Buckets: buckets,
512 SafeMode: false,
513 }
514
515 wdelayerVars := common.WDelayerVariables{
516 WithdrawalDelay: uint64(3000),
517 }
518
519 // Generate test data, as expected to be received/sended from/to the API
520 testCoords := genTestCoordinators(commonCoords)
521 testBids := genTestBids(commonBlocks, testCoords, bids)
522 testExits := genTestExits(commonExitTree, testTokens, commonAccounts)
523 testTxs := genTestTxs(commonL1Txs, commonL2Txs, commonAccounts, testTokens, commonBlocks)
524 testBatches, testFullBatches := genTestBatches(commonBlocks, commonBatches, testTxs)
525 poolTxsToSend, poolTxsToReceive := genTestPoolTxs(commonPoolTxs, testTokens, commonAccounts)
526 tc = testCommon{
527 blocks: commonBlocks,
528 tokens: testTokens,
529 batches: testBatches,
530 fullBatches: testFullBatches,
531 coordinators: testCoords,
532 accounts: genTestAccounts(commonAccounts, testTokens),
533 txs: testTxs,
534 exits: testExits,
535 poolTxsToSend: poolTxsToSend,
536 poolTxsToReceive: poolTxsToReceive,
537 auths: genTestAuths(test.GenAuths(5, _config.ChainID, _config.HermezAddress)),
538 router: router,
539 bids: testBids,
540 slots: api.genTestSlots(
541 20,
542 commonBlocks[len(commonBlocks)-1].Num,
543 testBids,
544 auctionVars,
545 ),
546 auctionVars: auctionVars,
547 rollupVars: rollupVars,
548 wdelayerVars: wdelayerVars,
549 nextForgers: nextForgers,
550 }
551
552 // Run tests
553 result := m.Run()
554 // Fake server
555 if os.Getenv("FAKE_SERVER") == "yes" {
556 for {
557 log.Info("Running fake server at " + apiURL + " until ^C is received")
558 time.Sleep(30 * time.Second)
559 }
560 }
561 // Stop server
562 if err := server.Shutdown(context.Background()); err != nil {
563 panic(err)
564 }
565 if err := database.Close(); err != nil {
566 panic(err)
567 }
568 if err := os.RemoveAll(dir); err != nil {
569 panic(err)
570 }
571 os.Exit(result)
572}
573
574func doGoodReqPaginated(
575 path, order string,
576 iterStruct Pendinger,
577 appendIter func(res interface{}),
578) error {
579 var next uint64
580 firstIte := true
581 expectedTotal := 0
582 totalReceived := 0
583 for {
584 // Calculate fromItem
585 iterPath := path
586 if !firstIte {
587 iterPath += "&fromItem=" + strconv.Itoa(int(next))
588 }
589 // Call API to get this iteration items
590 iterStruct = iterStruct.New()
591 if err := doGoodReq(
592 "GET", iterPath+"&order="+order, nil,
593 iterStruct,
594 ); err != nil {
595 return tracerr.Wrap(err)
596 }
597 appendIter(iterStruct)
598 // Keep iterating?
599 remaining, lastID := iterStruct.GetPending()
600 if remaining == 0 {
601 break
602 }
603 if order == historydb.OrderDesc {
604 next = lastID - 1
605 } else {
606 next = lastID + 1
607 }
608 // Check that the expected amount of items is consistent across iterations
609 totalReceived += iterStruct.Len()
610 if firstIte {
611 firstIte = false
612 expectedTotal = totalReceived + int(remaining)
613 }
614 if expectedTotal != totalReceived+int(remaining) {
615 panic(fmt.Sprintf(
616 "pagination error, totalReceived + remaining should be %d, but is %d",
617 expectedTotal, totalReceived+int(remaining),
618 ))
619 }
620 }
621 return nil
622}
623
624func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
625 ctx := context.Background()
626 client := &http.Client{}
627 httpReq, err := http.NewRequest(method, path, reqBody)
628 if err != nil {
629 return tracerr.Wrap(err)
630 }
631 if reqBody != nil {
632 httpReq.Header.Add("Content-Type", "application/json")
633 }
634 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
635 if err != nil {
636 return tracerr.Wrap(err)
637 }
638 // Validate request against swagger spec
639 requestValidationInput := &swagger.RequestValidationInput{
640 Request: httpReq,
641 PathParams: pathParams,
642 Route: route,
643 }
644 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
645 return tracerr.Wrap(err)
646 }
647 // Do API call
648 resp, err := client.Do(httpReq)
649 if err != nil {
650 return tracerr.Wrap(err)
651 }
652 if resp.Body == nil && returnStruct != nil {
653 return tracerr.Wrap(errors.New("Nil body"))
654 }
655 //nolint
656 defer resp.Body.Close()
657 body, err := ioutil.ReadAll(resp.Body)
658 if err != nil {
659 return tracerr.Wrap(err)
660 }
661 if resp.StatusCode != 200 {
662 return tracerr.Wrap(fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body)))
663 }
664 if returnStruct == nil {
665 return nil
666 }
667 // Unmarshal body into return struct
668 if err := json.Unmarshal(body, returnStruct); err != nil {
669 log.Error("invalid json: " + string(body))
670 log.Error(err)
671 return tracerr.Wrap(err)
672 }
673 // log.Info(string(body))
674 // Validate response against swagger spec
675 responseValidationInput := &swagger.ResponseValidationInput{
676 RequestValidationInput: requestValidationInput,
677 Status: resp.StatusCode,
678 Header: resp.Header,
679 }
680 responseValidationInput = responseValidationInput.SetBodyBytes(body)
681 return swagger.ValidateResponse(ctx, responseValidationInput)
682}
683
684func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
685 ctx := context.Background()
686 client := &http.Client{}
687 httpReq, _ := http.NewRequest(method, path, reqBody)
688 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
689 if err != nil {
690 return tracerr.Wrap(err)
691 }
692 // Validate request against swagger spec
693 requestValidationInput := &swagger.RequestValidationInput{
694 Request: httpReq,
695 PathParams: pathParams,
696 Route: route,
697 }
698 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
699 if expectedResponseCode != 400 {
700 return tracerr.Wrap(err)
701 }
702 log.Warn("The request does not match the API spec")
703 }
704 // Do API call
705 resp, err := client.Do(httpReq)
706 if err != nil {
707 return tracerr.Wrap(err)
708 }
709 if resp.Body == nil {
710 return tracerr.Wrap(errors.New("Nil body"))
711 }
712 //nolint
713 defer resp.Body.Close()
714 body, err := ioutil.ReadAll(resp.Body)
715 if err != nil {
716 return tracerr.Wrap(err)
717 }
718 if resp.StatusCode != expectedResponseCode {
719 return tracerr.Wrap(fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body)))
720 }
721 // Validate response against swagger spec
722 responseValidationInput := &swagger.ResponseValidationInput{
723 RequestValidationInput: requestValidationInput,
724 Status: resp.StatusCode,
725 Header: resp.Header,
726 }
727 responseValidationInput = responseValidationInput.SetBodyBytes(body)
728 return swagger.ValidateResponse(ctx, responseValidationInput)
729}
730
731// test helpers
732
733func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
734 for i := 0; i < len(blocks); i++ {
735 if blocks[i].Num == blockNum {
736 return blocks[i].Timestamp
737 }
738 }
739 panic("timesamp not found")
740}
741
742func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
743 for i := 0; i < len(tokens); i++ {
744 if tokens[i].TokenID == id {
745 return tokens[i]
746 }
747 }
748 panic("token not found")
749}
750
751func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
752 for _, acc := range accs {
753 if idx == acc.Idx {
754 return getTokenByID(acc.TokenID, tokens)
755 }
756 }
757 panic("token not found")
758}
759
760func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
761 for _, acc := range accs {
762 if acc.Idx == idx {
763 return &acc
764 }
765 }
766 panic("account not found")
767}
768
769func getBlockByNum(ethBlockNum int64, blocks []common.Block) common.Block {
770 for _, b := range blocks {
771 if b.Num == ethBlockNum {
772 return b
773 }
774 }
775 panic("block not found")
776}
777
778func getCoordinatorByBidder(bidder ethCommon.Address, coordinators []historydb.CoordinatorAPI) historydb.CoordinatorAPI {
779 var coordLastUpdate historydb.CoordinatorAPI
780 found := false
781 for _, c := range coordinators {
782 if c.Bidder == bidder {
783 coordLastUpdate = c
784 found = true
785 }
786 }
787 if !found {
788 panic("coordinator not found")
789 }
790 return coordLastUpdate
791}