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