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