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