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: ð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 // 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}