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