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"
11 "math/big"
12 "net/http"
13 "os"
14 "sort"
15 "strconv"
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/db/statedb"
27 "github.com/hermeznetwork/hermez-node/log"
28 "github.com/hermeznetwork/hermez-node/test"
29 "github.com/iden3/go-iden3-crypto/babyjub"
30 "github.com/mitchellh/copystructure"
31 "github.com/stretchr/testify/assert"
32 "github.com/stretchr/testify/require"
33)
34
35const apiPort = ":4010"
36const apiURL = "http://localhost" + apiPort + "/"
37
38type testCommon struct {
39 blocks []common.Block
40 tokens []tokenAPI
41 batches []common.Batch
42 usrAddr string
43 usrBjj string
44 accs []common.Account
45 usrTxs []historyTxAPI
46 allTxs []historyTxAPI
47 exits []exitAPI
48 usrExits []exitAPI
49 router *swagger.Router
50}
51
52// TxSortFields represents the fields needed to sort L1 and L2 transactions
53type txSortFields struct {
54 BatchNum *common.BatchNum
55 Position int
56}
57
58// TxSortFielder is a interface that allows sorting L1 and L2 transactions in a combined way
59type txSortFielder interface {
60 SortFields() txSortFields
61 L1() *common.L1Tx
62 L2() *common.L2Tx
63}
64
65// TxsSort array of TxSortFielder
66type txsSort []txSortFielder
67
68func (t txsSort) Len() int { return len(t) }
69func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
70func (t txsSort) Less(i, j int) bool {
71 // i not forged yet
72 isf := t[i].SortFields()
73 jsf := t[j].SortFields()
74 if isf.BatchNum == nil {
75 if jsf.BatchNum != nil { // j is already forged
76 return false
77 }
78 // Both aren't forged, is i in a smaller position?
79 return isf.Position < jsf.Position
80 }
81 // i is forged
82 if jsf.BatchNum == nil {
83 return false // j is not forged
84 }
85 // Both are forged
86 if *isf.BatchNum == *jsf.BatchNum {
87 // At the same batch, is i in a smaller position?
88 return isf.Position < jsf.Position
89 }
90 // At different batches, is i in a smaller batch?
91 return *isf.BatchNum < *jsf.BatchNum
92}
93
94type wrappedL1 common.L1Tx
95
96// SortFields implements TxSortFielder
97func (tx *wrappedL1) SortFields() txSortFields {
98 return txSortFields{
99 BatchNum: tx.BatchNum,
100 Position: tx.Position,
101 }
102}
103
104// L1 implements TxSortFielder
105func (tx *wrappedL1) L1() *common.L1Tx {
106 l1tx := common.L1Tx(*tx)
107 return &l1tx
108}
109
110// L2 implements TxSortFielder
111func (tx *wrappedL1) L2() *common.L2Tx { return nil }
112
113type wrappedL2 common.L2Tx
114
115// SortFields implements TxSortFielder
116func (tx *wrappedL2) SortFields() txSortFields {
117 return txSortFields{
118 BatchNum: &tx.BatchNum,
119 Position: tx.Position,
120 }
121}
122
123// L1 implements TxSortFielder
124func (tx *wrappedL2) L1() *common.L1Tx { return nil }
125
126// L2 implements TxSortFielder
127func (tx *wrappedL2) L2() *common.L2Tx {
128 l2tx := common.L2Tx(*tx)
129 return &l2tx
130}
131
132var tc testCommon
133
134func TestMain(m *testing.M) {
135 // Init swagger
136 router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
137 // Init DBs
138 // HistoryDB
139 pass := os.Getenv("POSTGRES_PASS")
140 database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
141 if err != nil {
142 panic(err)
143 }
144 hdb := historydb.NewHistoryDB(database)
145 err = hdb.Reorg(-1)
146 if err != nil {
147 panic(err)
148 }
149 // StateDB
150 dir, err := ioutil.TempDir("", "tmpdb")
151 if err != nil {
152 panic(err)
153 }
154 defer func() {
155 if err := os.RemoveAll(dir); err != nil {
156 panic(err)
157 }
158 }()
159 sdb, err := statedb.NewStateDB(dir, statedb.TypeTxSelector, 0)
160 if err != nil {
161 panic(err)
162 }
163 // L2DB
164 l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
165 test.CleanL2DB(l2DB.DB())
166
167 // Init API
168 api := gin.Default()
169 if err := SetAPIEndpoints(
170 true,
171 true,
172 api,
173 hdb,
174 sdb,
175 l2DB,
176 ); err != nil {
177 panic(err)
178 }
179 // Start server
180 server := &http.Server{Addr: apiPort, Handler: api}
181 go func() {
182 if err := server.ListenAndServe(); err != nil &&
183 err != http.ErrServerClosed {
184 panic(err)
185 }
186 }()
187
188 // Populate DBs
189 // Clean DB
190 err = h.Reorg(0)
191 if err != nil {
192 panic(err)
193 }
194 // Gen blocks and add them to DB
195 const nBlocks = 5
196 blocks := test.GenBlocks(1, nBlocks+1)
197 err = h.AddBlocks(blocks)
198 if err != nil {
199 panic(err)
200 }
201 // Gen tokens and add them to DB
202 const nTokens = 10
203 tokens := test.GenTokens(nTokens, blocks)
204 err = h.AddTokens(tokens)
205 if err != nil {
206 panic(err)
207 }
208 // Set token value
209 tokensUSD := []tokenAPI{}
210 for i, tkn := range tokens {
211 token := tokenAPI{
212 TokenID: tkn.TokenID,
213 EthBlockNum: tkn.EthBlockNum,
214 EthAddr: tkn.EthAddr,
215 Name: tkn.Name,
216 Symbol: tkn.Symbol,
217 Decimals: tkn.Decimals,
218 }
219 // Set value of 50% of the tokens
220 if i%2 != 0 {
221 value := float64(i) * 1.234567
222 now := time.Now().UTC()
223 token.USD = &value
224 token.USDUpdate = &now
225 err = h.UpdateTokenValue(token.Symbol, value)
226 if err != nil {
227 panic(err)
228 }
229 }
230 tokensUSD = append(tokensUSD, token)
231 }
232 // Gen batches and add them to DB
233 const nBatches = 10
234 batches := test.GenBatches(nBatches, blocks)
235 err = h.AddBatches(batches)
236 if err != nil {
237 panic(err)
238 }
239 // Gen accounts and add them to DB
240 const totalAccounts = 40
241 const userAccounts = 4
242 usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
243 privK := babyjub.NewRandPrivKey()
244 usrBjj := privK.Public()
245 accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
246 err = h.AddAccounts(accs)
247 if err != nil {
248 panic(err)
249 }
250 // Gen exits and add them to DB
251 const totalExits = 40
252 exits := test.GenExitTree(totalExits, batches, accs)
253 err = h.AddExitTree(exits)
254 if err != nil {
255 panic(err)
256 }
257 // Gen L1Txs and add them to DB
258 const totalL1Txs = 40
259 const userL1Txs = 4
260 usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
261 // Gen L2Txs and add them to DB
262 const totalL2Txs = 20
263 const userL2Txs = 4
264 usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
265 // Order txs
266 sortedTxs := []txSortFielder{}
267 for i := 0; i < len(usrL1Txs); i++ {
268 wL1 := wrappedL1(usrL1Txs[i])
269 sortedTxs = append(sortedTxs, &wL1)
270 }
271 for i := 0; i < len(othrL1Txs); i++ {
272 wL1 := wrappedL1(othrL1Txs[i])
273 sortedTxs = append(sortedTxs, &wL1)
274 }
275 for i := 0; i < len(usrL2Txs); i++ {
276 wL2 := wrappedL2(usrL2Txs[i])
277 sortedTxs = append(sortedTxs, &wL2)
278 }
279 for i := 0; i < len(othrL2Txs); i++ {
280 wL2 := wrappedL2(othrL2Txs[i])
281 sortedTxs = append(sortedTxs, &wL2)
282 }
283 sort.Sort(txsSort(sortedTxs))
284 // Add txs to DB and prepare them for test commons
285 usrTxs := []historyTxAPI{}
286 allTxs := []historyTxAPI{}
287 getTimestamp := func(blockNum int64) time.Time {
288 for i := 0; i < len(blocks); i++ {
289 if blocks[i].EthBlockNum == blockNum {
290 return blocks[i].Timestamp
291 }
292 }
293 panic("timesamp not found")
294 }
295 getToken := func(id common.TokenID) tokenAPI {
296 for i := 0; i < len(tokensUSD); i++ {
297 if tokensUSD[i].TokenID == id {
298 return tokensUSD[i]
299 }
300 }
301 panic("token not found")
302 }
303 getTokenByIdx := func(idx common.Idx) tokenAPI {
304 for _, acc := range accs {
305 if idx == acc.Idx {
306 return getToken(acc.TokenID)
307 }
308 }
309 panic("token not found")
310 }
311 usrIdxs := []string{}
312 for _, acc := range accs {
313 if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
314 for _, token := range tokens {
315 if token.TokenID == acc.TokenID {
316 usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
317 }
318 }
319 }
320 }
321 isUsrTx := func(tx historyTxAPI) bool {
322 for _, idx := range usrIdxs {
323 if tx.FromIdx != nil && *tx.FromIdx == idx {
324 return true
325 }
326 if tx.ToIdx == idx {
327 return true
328 }
329 }
330 return false
331 }
332 for _, genericTx := range sortedTxs {
333 l1 := genericTx.L1()
334 l2 := genericTx.L2()
335 if l1 != nil {
336 // Add L1 tx to DB
337 err = h.AddL1Txs([]common.L1Tx{*l1})
338 if err != nil {
339 panic(err)
340 }
341 // L1Tx ==> historyTxAPI
342 token := getToken(l1.TokenID)
343 tx := historyTxAPI{
344 IsL1: "L1",
345 TxID: l1.TxID.String(),
346 Type: l1.Type,
347 Position: l1.Position,
348 ToIdx: idxToHez(l1.ToIdx, token.Symbol),
349 Amount: l1.Amount.String(),
350 BatchNum: l1.BatchNum,
351 Timestamp: getTimestamp(l1.EthBlockNum),
352 L1Info: &l1Info{
353 ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
354 UserOrigin: l1.UserOrigin,
355 FromEthAddr: ethAddrToHez(l1.FromEthAddr),
356 FromBJJ: bjjToString(l1.FromBJJ),
357 LoadAmount: l1.LoadAmount.String(),
358 EthBlockNum: l1.EthBlockNum,
359 },
360 Token: token,
361 }
362 if l1.FromIdx != 0 {
363 idxStr := idxToHez(l1.FromIdx, token.Symbol)
364 tx.FromIdx = &idxStr
365 }
366 if token.USD != nil {
367 af := new(big.Float).SetInt(l1.Amount)
368 amountFloat, _ := af.Float64()
369 usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
370 tx.HistoricUSD = &usd
371 laf := new(big.Float).SetInt(l1.LoadAmount)
372 loadAmountFloat, _ := laf.Float64()
373 loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals))
374 tx.L1Info.HistoricLoadAmountUSD = &loadUSD
375 }
376 allTxs = append(allTxs, tx)
377 if isUsrTx(tx) {
378 usrTxs = append(usrTxs, tx)
379 }
380 } else {
381 // Add L2 tx to DB
382 err = h.AddL2Txs([]common.L2Tx{*l2})
383 if err != nil {
384 panic(err)
385 }
386 // L2Tx ==> historyTxAPI
387 var tokenID common.TokenID
388 found := false
389 for _, acc := range accs {
390 if acc.Idx == l2.FromIdx {
391 found = true
392 tokenID = acc.TokenID
393 break
394 }
395 }
396 if !found {
397 panic("tokenID not found")
398 }
399 token := getToken(tokenID)
400 tx := historyTxAPI{
401 IsL1: "L2",
402 TxID: l2.TxID.String(),
403 Type: l2.Type,
404 Position: l2.Position,
405 ToIdx: idxToHez(l2.ToIdx, token.Symbol),
406 Amount: l2.Amount.String(),
407 BatchNum: &l2.BatchNum,
408 Timestamp: getTimestamp(l2.EthBlockNum),
409 L2Info: &l2Info{
410 Nonce: l2.Nonce,
411 Fee: l2.Fee,
412 },
413 Token: token,
414 }
415 if l2.FromIdx != 0 {
416 idxStr := idxToHez(l2.FromIdx, token.Symbol)
417 tx.FromIdx = &idxStr
418 }
419 if token.USD != nil {
420 af := new(big.Float).SetInt(l2.Amount)
421 amountFloat, _ := af.Float64()
422 usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
423 tx.HistoricUSD = &usd
424 feeUSD := usd * l2.Fee.Percentage()
425 tx.HistoricUSD = &usd
426 tx.L2Info.HistoricFeeUSD = &feeUSD
427 }
428 allTxs = append(allTxs, tx)
429 if isUsrTx(tx) {
430 usrTxs = append(usrTxs, tx)
431 }
432 }
433 }
434 // Transform exits to API
435 exitsToAPIExits := func(exits []common.ExitInfo, accs []common.Account, tokens []common.Token) []exitAPI {
436 historyExits := []historydb.HistoryExit{}
437 for _, exit := range exits {
438 token := getTokenByIdx(exit.AccountIdx)
439 historyExits = append(historyExits, historydb.HistoryExit{
440 BatchNum: exit.BatchNum,
441 AccountIdx: exit.AccountIdx,
442 MerkleProof: exit.MerkleProof,
443 Balance: exit.Balance,
444 InstantWithdrawn: exit.InstantWithdrawn,
445 DelayedWithdrawRequest: exit.DelayedWithdrawRequest,
446 DelayedWithdrawn: exit.DelayedWithdrawn,
447 TokenID: token.TokenID,
448 TokenEthBlockNum: token.EthBlockNum,
449 TokenEthAddr: token.EthAddr,
450 TokenName: token.Name,
451 TokenSymbol: token.Symbol,
452 TokenDecimals: token.Decimals,
453 TokenUSD: token.USD,
454 TokenUSDUpdate: token.USDUpdate,
455 })
456 }
457 return historyExitsToAPI(historyExits)
458 }
459 apiExits := exitsToAPIExits(exits, accs, tokens)
460 // sort.Sort(apiExits)
461 usrExits := []exitAPI{}
462 for _, exit := range apiExits {
463 for _, idx := range usrIdxs {
464 if idx == exit.AccountIdx {
465 usrExits = append(usrExits, exit)
466 }
467 }
468 }
469 tc = testCommon{
470 blocks: blocks,
471 tokens: tokensUSD,
472 batches: batches,
473 usrAddr: ethAddrToHez(usrAddr),
474 usrBjj: bjjToString(usrBjj),
475 accs: accs,
476 usrTxs: usrTxs,
477 allTxs: allTxs,
478 exits: apiExits,
479 usrExits: usrExits,
480 router: router,
481 }
482 // Run tests
483 result := m.Run()
484 // Stop server
485 if err := server.Shutdown(context.Background()); err != nil {
486 panic(err)
487 }
488 if err := database.Close(); err != nil {
489 panic(err)
490 }
491 os.Exit(result)
492}
493
494func TestGetHistoryTxs(t *testing.T) {
495 endpoint := apiURL + "transactions-history"
496 fetchedTxs := []historyTxAPI{}
497 appendIter := func(intr interface{}) {
498 for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
499 tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
500 if err != nil {
501 panic(err)
502 }
503 fetchedTxs = append(fetchedTxs, tmp.(historyTxAPI))
504 }
505 }
506 // Get all (no filters)
507 limit := 8
508 path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
509 err := doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
510 assert.NoError(t, err)
511 assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
512 // Uncomment once tx generation for tests is fixed
513 // // Get by ethAddr
514 // fetchedTxs = []historyTxAPI{}
515 // limit = 7
516 // path = fmt.Sprintf(
517 // "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
518 // endpoint, tc.usrAddr, limit,
519 // )
520 // err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
521 // assert.NoError(t, err)
522 // assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
523 // // Get by bjj
524 // fetchedTxs = []historyTxAPI{}
525 // limit = 6
526 // path = fmt.Sprintf(
527 // "%s?BJJ=%s&limit=%d&fromItem=",
528 // endpoint, tc.usrBjj, limit,
529 // )
530 // err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
531 // assert.NoError(t, err)
532 // assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
533 // Get by tokenID
534 fetchedTxs = []historyTxAPI{}
535 limit = 5
536 tokenID := tc.allTxs[0].Token.TokenID
537 path = fmt.Sprintf(
538 "%s?tokenId=%d&limit=%d&fromItem=",
539 endpoint, tokenID, limit,
540 )
541 err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
542 assert.NoError(t, err)
543 tokenIDTxs := []historyTxAPI{}
544 for i := 0; i < len(tc.allTxs); i++ {
545 if tc.allTxs[i].Token.TokenID == tokenID {
546 tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
547 }
548 }
549 assertHistoryTxAPIs(t, tokenIDTxs, fetchedTxs)
550 // idx
551 fetchedTxs = []historyTxAPI{}
552 limit = 4
553 idx := tc.allTxs[0].ToIdx
554 path = fmt.Sprintf(
555 "%s?accountIndex=%s&limit=%d&fromItem=",
556 endpoint, idx, limit,
557 )
558 err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
559 assert.NoError(t, err)
560 idxTxs := []historyTxAPI{}
561 for i := 0; i < len(tc.allTxs); i++ {
562 if (tc.allTxs[i].FromIdx != nil && (*tc.allTxs[i].FromIdx)[6:] == idx[6:]) ||
563 tc.allTxs[i].ToIdx[6:] == idx[6:] {
564 idxTxs = append(idxTxs, tc.allTxs[i])
565 }
566 }
567 assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
568 // batchNum
569 fetchedTxs = []historyTxAPI{}
570 limit = 3
571 batchNum := tc.allTxs[0].BatchNum
572 path = fmt.Sprintf(
573 "%s?batchNum=%d&limit=%d&fromItem=",
574 endpoint, *batchNum, limit,
575 )
576 err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
577 assert.NoError(t, err)
578 batchNumTxs := []historyTxAPI{}
579 for i := 0; i < len(tc.allTxs); i++ {
580 if tc.allTxs[i].BatchNum != nil &&
581 *tc.allTxs[i].BatchNum == *batchNum {
582 batchNumTxs = append(batchNumTxs, tc.allTxs[i])
583 }
584 }
585 assertHistoryTxAPIs(t, batchNumTxs, fetchedTxs)
586 // type
587 txTypes := []common.TxType{
588 // Uncomment once test gen is fixed
589 // common.TxTypeExit,
590 // common.TxTypeTransfer,
591 // common.TxTypeDeposit,
592 common.TxTypeCreateAccountDeposit,
593 // common.TxTypeCreateAccountDepositTransfer,
594 // common.TxTypeDepositTransfer,
595 common.TxTypeForceTransfer,
596 // common.TxTypeForceExit,
597 // common.TxTypeTransferToEthAddr,
598 // common.TxTypeTransferToBJJ,
599 }
600 for _, txType := range txTypes {
601 fetchedTxs = []historyTxAPI{}
602 limit = 2
603 path = fmt.Sprintf(
604 "%s?type=%s&limit=%d&fromItem=",
605 endpoint, txType, limit,
606 )
607 err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
608 assert.NoError(t, err)
609 txTypeTxs := []historyTxAPI{}
610 for i := 0; i < len(tc.allTxs); i++ {
611 if tc.allTxs[i].Type == txType {
612 txTypeTxs = append(txTypeTxs, tc.allTxs[i])
613 }
614 }
615 assertHistoryTxAPIs(t, txTypeTxs, fetchedTxs)
616 }
617 // Multiple filters
618 fetchedTxs = []historyTxAPI{}
619 limit = 1
620 path = fmt.Sprintf(
621 "%s?batchNum=%d&tokenId=%d&limit=%d&fromItem=",
622 endpoint, *batchNum, tokenID, limit,
623 )
624 err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
625 assert.NoError(t, err)
626 mixedTxs := []historyTxAPI{}
627 for i := 0; i < len(tc.allTxs); i++ {
628 if tc.allTxs[i].BatchNum != nil {
629 if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
630 mixedTxs = append(mixedTxs, tc.allTxs[i])
631 }
632 }
633 }
634 assertHistoryTxAPIs(t, mixedTxs, fetchedTxs)
635 // All, in reverse order
636 fetchedTxs = []historyTxAPI{}
637 limit = 5
638 path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
639 err = doGoodReqPaginated(path, historydb.OrderDesc, &historyTxsAPI{}, appendIter)
640 assert.NoError(t, err)
641 flipedTxs := []historyTxAPI{}
642 for i := 0; i < len(tc.allTxs); i++ {
643 flipedTxs = append(flipedTxs, tc.allTxs[len(tc.allTxs)-1-i])
644 }
645 assertHistoryTxAPIs(t, flipedTxs, fetchedTxs)
646 // 400
647 path = fmt.Sprintf(
648 "%s?accountIndex=%s&hermezEthereumAddress=%s",
649 endpoint, idx, tc.usrAddr,
650 )
651 err = doBadReq("GET", path, nil, 400)
652 assert.NoError(t, err)
653 path = fmt.Sprintf("%s?tokenId=X", endpoint)
654 err = doBadReq("GET", path, nil, 400)
655 assert.NoError(t, err)
656 // 404
657 path = fmt.Sprintf("%s?batchNum=999999", endpoint)
658 err = doBadReq("GET", path, nil, 404)
659 assert.NoError(t, err)
660 path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
661 err = doBadReq("GET", path, nil, 404)
662 assert.NoError(t, err)
663}
664
665func TestGetHistoryTx(t *testing.T) {
666 // Get all txs by their ID
667 endpoint := apiURL + "transactions-history/"
668 fetchedTxs := []historyTxAPI{}
669 for _, tx := range tc.allTxs {
670 fetchedTx := historyTxAPI{}
671 assert.NoError(t, doGoodReq("GET", endpoint+tx.TxID, nil, &fetchedTx))
672 fetchedTxs = append(fetchedTxs, fetchedTx)
673 }
674 assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
675 // 400
676 err := doBadReq("GET", endpoint+"0x001", nil, 400)
677 assert.NoError(t, err)
678 // 404
679 err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404)
680 assert.NoError(t, err)
681}
682
683func assertHistoryTxAPIs(t *testing.T, expected, actual []historyTxAPI) {
684 require.Equal(t, len(expected), len(actual))
685 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
686 actual[i].ItemID = 0
687 assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
688 expected[i].Timestamp = actual[i].Timestamp
689 if expected[i].Token.USDUpdate == nil {
690 assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
691 } else {
692 assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
693 expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
694 }
695 test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
696 if expected[i].L2Info != nil {
697 test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
698 } else {
699 test.AssertUSD(t, expected[i].L1Info.HistoricLoadAmountUSD, actual[i].L1Info.HistoricLoadAmountUSD)
700 }
701 assert.Equal(t, expected[i], actual[i])
702 }
703}
704
705func TestGetExits(t *testing.T) {
706 endpoint := apiURL + "exits"
707 fetchedExits := []exitAPI{}
708 appendIter := func(intr interface{}) {
709 for i := 0; i < len(intr.(*exitsAPI).Exits); i++ {
710 tmp, err := copystructure.Copy(intr.(*exitsAPI).Exits[i])
711 if err != nil {
712 panic(err)
713 }
714 fetchedExits = append(fetchedExits, tmp.(exitAPI))
715 }
716 }
717 // Get all (no filters)
718 limit := 8
719 path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
720 err := doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
721 assert.NoError(t, err)
722 assertExitAPIs(t, tc.exits, fetchedExits)
723
724 // Get by ethAddr
725 fetchedExits = []exitAPI{}
726 limit = 7
727 path = fmt.Sprintf(
728 "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
729 endpoint, tc.usrAddr, limit,
730 )
731 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
732 assert.NoError(t, err)
733 assertExitAPIs(t, tc.usrExits, fetchedExits)
734 // Get by bjj
735 fetchedExits = []exitAPI{}
736 limit = 6
737 path = fmt.Sprintf(
738 "%s?BJJ=%s&limit=%d&fromItem=",
739 endpoint, tc.usrBjj, limit,
740 )
741 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
742 assert.NoError(t, err)
743 assertExitAPIs(t, tc.usrExits, fetchedExits)
744 // Get by tokenID
745 fetchedExits = []exitAPI{}
746 limit = 5
747 tokenID := tc.exits[0].Token.TokenID
748 path = fmt.Sprintf(
749 "%s?tokenId=%d&limit=%d&fromItem=",
750 endpoint, tokenID, limit,
751 )
752 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
753 assert.NoError(t, err)
754 tokenIDExits := []exitAPI{}
755 for i := 0; i < len(tc.exits); i++ {
756 if tc.exits[i].Token.TokenID == tokenID {
757 tokenIDExits = append(tokenIDExits, tc.exits[i])
758 }
759 }
760 assertExitAPIs(t, tokenIDExits, fetchedExits)
761 // idx
762 fetchedExits = []exitAPI{}
763 limit = 4
764 idx := tc.exits[0].AccountIdx
765 path = fmt.Sprintf(
766 "%s?accountIndex=%s&limit=%d&fromItem=",
767 endpoint, idx, limit,
768 )
769 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
770 assert.NoError(t, err)
771 idxExits := []exitAPI{}
772 for i := 0; i < len(tc.exits); i++ {
773 if tc.exits[i].AccountIdx[6:] == idx[6:] {
774 idxExits = append(idxExits, tc.exits[i])
775 }
776 }
777 assertExitAPIs(t, idxExits, fetchedExits)
778 // batchNum
779 fetchedExits = []exitAPI{}
780 limit = 3
781 batchNum := tc.exits[0].BatchNum
782 path = fmt.Sprintf(
783 "%s?batchNum=%d&limit=%d&fromItem=",
784 endpoint, batchNum, limit,
785 )
786 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
787 assert.NoError(t, err)
788 batchNumExits := []exitAPI{}
789 for i := 0; i < len(tc.exits); i++ {
790 if tc.exits[i].BatchNum == batchNum {
791 batchNumExits = append(batchNumExits, tc.exits[i])
792 }
793 }
794 assertExitAPIs(t, batchNumExits, fetchedExits)
795 // Multiple filters
796 fetchedExits = []exitAPI{}
797 limit = 1
798 path = fmt.Sprintf(
799 "%s?batchNum=%d&tokeId=%d&limit=%d&fromItem=",
800 endpoint, batchNum, tokenID, limit,
801 )
802 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
803 assert.NoError(t, err)
804 mixedExits := []exitAPI{}
805 flipedExits := []exitAPI{}
806 for i := 0; i < len(tc.exits); i++ {
807 if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID {
808 mixedExits = append(mixedExits, tc.exits[i])
809 }
810 flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i])
811 }
812 assertExitAPIs(t, mixedExits, fetchedExits)
813 // All, in reverse order
814 fetchedExits = []exitAPI{}
815 limit = 5
816 path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
817 err = doGoodReqPaginated(path, historydb.OrderDesc, &exitsAPI{}, appendIter)
818 assert.NoError(t, err)
819 assertExitAPIs(t, flipedExits, fetchedExits)
820 // 400
821 path = fmt.Sprintf(
822 "%s?accountIndex=%s&hermezEthereumAddress=%s",
823 endpoint, idx, tc.usrAddr,
824 )
825 err = doBadReq("GET", path, nil, 400)
826 assert.NoError(t, err)
827 path = fmt.Sprintf("%s?tokenId=X", endpoint)
828 err = doBadReq("GET", path, nil, 400)
829 assert.NoError(t, err)
830 // 404
831 path = fmt.Sprintf("%s?batchNum=999999", endpoint)
832 err = doBadReq("GET", path, nil, 404)
833 assert.NoError(t, err)
834 path = fmt.Sprintf("%s?limit=1000&fromItem=1000", endpoint)
835 err = doBadReq("GET", path, nil, 404)
836 assert.NoError(t, err)
837}
838
839func TestGetExit(t *testing.T) {
840 // Get all txs by their ID
841 endpoint := apiURL + "exits/"
842 fetchedExits := []exitAPI{}
843 for _, exit := range tc.exits {
844 fetchedExit := exitAPI{}
845 assert.NoError(
846 t, doGoodReq(
847 "GET",
848 fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx),
849 nil, &fetchedExit,
850 ),
851 )
852 fetchedExits = append(fetchedExits, fetchedExit)
853 }
854 assertExitAPIs(t, tc.exits, fetchedExits)
855 // 400
856 err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400)
857 assert.NoError(t, err)
858 err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400)
859 assert.NoError(t, err)
860 // 404
861 err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404)
862 assert.NoError(t, err)
863}
864
865func assertExitAPIs(t *testing.T, expected, actual []exitAPI) {
866 require.Equal(t, len(expected), len(actual))
867 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
868 actual[i].ItemID = 0
869 if expected[i].Token.USDUpdate == nil {
870 assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
871 } else {
872 assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
873 expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
874 }
875 assert.Equal(t, expected[i], actual[i])
876 }
877}
878
879func TestGetToken(t *testing.T) {
880 // Get all txs by their ID
881 endpoint := apiURL + "tokens/"
882 fetchedTokens := []tokenAPI{}
883 for _, token := range tc.tokens {
884 fetchedToken := tokenAPI{}
885 assert.NoError(t, doGoodReq("GET", endpoint+strconv.Itoa(int(token.TokenID)), nil, &fetchedToken))
886 fetchedTokens = append(fetchedTokens, fetchedToken)
887 }
888 assertTokensAPIs(t, tc.tokens, fetchedTokens)
889}
890
891func TestGetTokens(t *testing.T) {
892 endpoint := apiURL + "tokens"
893 fetchedTokens := []tokenAPI{}
894 appendIter := func(intr interface{}) {
895 for i := 0; i < len(intr.(*tokensAPI).Tokens); i++ {
896 tmp, err := copystructure.Copy(intr.(*tokensAPI).Tokens[i])
897 if err != nil {
898 panic(err)
899 }
900 fetchedTokens = append(fetchedTokens, tmp.(tokenAPI))
901 }
902 }
903 // Get all (no filters)
904 limit := 8
905 path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
906 err := doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
907 assert.NoError(t, err)
908 assertTokensAPIs(t, tc.tokens, fetchedTokens)
909
910 // Get by tokenIds
911 fetchedTokens = []tokenAPI{}
912 limit = 7
913 stringIds := strconv.Itoa(int(tc.tokens[2].TokenID)) + "," + strconv.Itoa(int(tc.tokens[5].TokenID)) + "," + strconv.Itoa(int(tc.tokens[6].TokenID))
914 path = fmt.Sprintf(
915 "%s?ids=%s&limit=%d&fromItem=",
916 endpoint, stringIds, limit,
917 )
918 err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
919 assert.NoError(t, err)
920 var tokensFiltered []tokenAPI
921 tokensFiltered = append(tokensFiltered, tc.tokens[2])
922 tokensFiltered = append(tokensFiltered, tc.tokens[5])
923 tokensFiltered = append(tokensFiltered, tc.tokens[6])
924 assertTokensAPIs(t, tokensFiltered, fetchedTokens)
925
926 // Get by symbols
927 fetchedTokens = []tokenAPI{}
928 limit = 7
929 stringSymbols := tc.tokens[1].Symbol + "," + tc.tokens[3].Symbol
930 path = fmt.Sprintf(
931 "%s?symbols=%s&limit=%d&fromItem=",
932 endpoint, stringSymbols, limit,
933 )
934 err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
935 assert.NoError(t, err)
936 tokensFiltered = nil
937 tokensFiltered = append(tokensFiltered, tc.tokens[1])
938 tokensFiltered = append(tokensFiltered, tc.tokens[3])
939 assertTokensAPIs(t, tokensFiltered, fetchedTokens)
940
941 // Get by name
942 fetchedTokens = []tokenAPI{}
943 limit = 5
944 stringName := tc.tokens[8].Name[4:5]
945 path = fmt.Sprintf(
946 "%s?name=%s&limit=%d&fromItem=",
947 endpoint, stringName, limit,
948 )
949 err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
950 assert.NoError(t, err)
951 tokensFiltered = nil
952 tokensFiltered = append(tokensFiltered, tc.tokens[8])
953 assertTokensAPIs(t, tokensFiltered, fetchedTokens)
954
955 // Multiple filters
956 fetchedTokens = []tokenAPI{}
957 limit = 5
958 stringSymbols = tc.tokens[2].Symbol + "," + tc.tokens[6].Symbol
959 stringIds = strconv.Itoa(int(tc.tokens[2].TokenID)) + "," + strconv.Itoa(int(tc.tokens[5].TokenID)) + "," + strconv.Itoa(int(tc.tokens[6].TokenID))
960 path = fmt.Sprintf(
961 "%s?symbols=%s&ids=%s&limit=%d&fromItem=",
962 endpoint, stringSymbols, stringIds, limit,
963 )
964 err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
965 assert.NoError(t, err)
966
967 tokensFiltered = nil
968 tokensFiltered = append(tokensFiltered, tc.tokens[2])
969 tokensFiltered = append(tokensFiltered, tc.tokens[6])
970 assertTokensAPIs(t, tokensFiltered, fetchedTokens)
971
972 // All, in reverse order
973 fetchedTokens = []tokenAPI{}
974 limit = 5
975 path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
976 err = doGoodReqPaginated(path, historydb.OrderDesc, &tokensAPI{}, appendIter)
977 assert.NoError(t, err)
978 flipedTokens := []tokenAPI{}
979 for i := 0; i < len(tc.tokens); i++ {
980 flipedTokens = append(flipedTokens, tc.tokens[len(tc.tokens)-1-i])
981 }
982 assertTokensAPIs(t, flipedTokens, fetchedTokens)
983}
984
985func assertTokensAPIs(t *testing.T, expected, actual []tokenAPI) {
986 require.Equal(t, len(expected), len(actual))
987 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
988 actual[i].ItemID = 0
989 if expected[i].USDUpdate == nil {
990 assert.Equal(t, expected[i].USDUpdate, actual[i].USDUpdate)
991 } else {
992 assert.Equal(t, expected[i].USDUpdate.Unix(), actual[i].USDUpdate.Unix())
993 expected[i].USDUpdate = actual[i].USDUpdate
994 }
995 assert.Equal(t, expected[i], actual[i])
996 }
997}
998
999func doGoodReqPaginated(
1000 path, order string,
1001 iterStruct db.Paginationer,
1002 appendIter func(res interface{}),
1003) error {
1004 next := 0
1005 for {
1006 // Call API to get this iteration items
1007 iterPath := path
1008 if next == 0 && order == historydb.OrderDesc {
1009 // Fetch first item in reverse order
1010 iterPath += "99999"
1011 } else {
1012 // Fetch from next item or 0 if it's ascending order
1013 iterPath += strconv.Itoa(next)
1014 }
1015 if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
1016 return err
1017 }
1018 appendIter(iterStruct)
1019 // Keep iterating?
1020 pag := iterStruct.GetPagination()
1021 if order == historydb.OrderAsc {
1022 if pag.LastReturnedItem == pag.LastItem { // No
1023 break
1024 } else { // Yes
1025 next = pag.LastReturnedItem + 1
1026 }
1027 } else {
1028 if pag.FirstReturnedItem == pag.FirstItem { // No
1029 break
1030 } else { // Yes
1031 next = pag.FirstReturnedItem - 1
1032 }
1033 }
1034 }
1035 return nil
1036}
1037
1038func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
1039 ctx := context.Background()
1040 client := &http.Client{}
1041 httpReq, _ := http.NewRequest(method, path, reqBody)
1042 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
1043 if err != nil {
1044 return err
1045 }
1046 // Validate request against swagger spec
1047 requestValidationInput := &swagger.RequestValidationInput{
1048 Request: httpReq,
1049 PathParams: pathParams,
1050 Route: route,
1051 }
1052 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
1053 return err
1054 }
1055 // Do API call
1056 resp, err := client.Do(httpReq)
1057 if err != nil {
1058 return err
1059 }
1060 if resp.Body == nil {
1061 return errors.New("Nil body")
1062 }
1063 //nolint
1064 defer resp.Body.Close()
1065 body, err := ioutil.ReadAll(resp.Body)
1066 if err != nil {
1067 return err
1068 }
1069 if resp.StatusCode != 200 {
1070 return fmt.Errorf("%d response: %s", resp.StatusCode, string(body))
1071 }
1072 // Unmarshal body into return struct
1073 if err := json.Unmarshal(body, returnStruct); err != nil {
1074 return err
1075 }
1076 // Validate response against swagger spec
1077 responseValidationInput := &swagger.ResponseValidationInput{
1078 RequestValidationInput: requestValidationInput,
1079 Status: resp.StatusCode,
1080 Header: resp.Header,
1081 }
1082 responseValidationInput = responseValidationInput.SetBodyBytes(body)
1083 return swagger.ValidateResponse(ctx, responseValidationInput)
1084}
1085
1086func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
1087 ctx := context.Background()
1088 client := &http.Client{}
1089 httpReq, _ := http.NewRequest(method, path, reqBody)
1090 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
1091 if err != nil {
1092 return err
1093 }
1094 // Validate request against swagger spec
1095 requestValidationInput := &swagger.RequestValidationInput{
1096 Request: httpReq,
1097 PathParams: pathParams,
1098 Route: route,
1099 }
1100 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
1101 if expectedResponseCode != 400 {
1102 return err
1103 }
1104 log.Warn("The request does not match the API spec")
1105 }
1106 // Do API call
1107 resp, err := client.Do(httpReq)
1108 if err != nil {
1109 return err
1110 }
1111 if resp.Body == nil {
1112 return errors.New("Nil body")
1113 }
1114 //nolint
1115 defer resp.Body.Close()
1116 body, err := ioutil.ReadAll(resp.Body)
1117 if err != nil {
1118 return err
1119 }
1120 if resp.StatusCode != expectedResponseCode {
1121 return fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
1122 }
1123 // Validate response against swagger spec
1124 responseValidationInput := &swagger.ResponseValidationInput{
1125 RequestValidationInput: requestValidationInput,
1126 Status: resp.StatusCode,
1127 Header: resp.Header,
1128 }
1129 responseValidationInput = responseValidationInput.SetBodyBytes(body)
1130 return swagger.ValidateResponse(ctx, responseValidationInput)
1131}