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