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