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