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