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