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