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 []historydb.TokenRead
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 := []historydb.TokenRead{}
210 for i, tkn := range tokens {
211 token := historydb.TokenRead{
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) historydb.TokenRead {
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) historydb.TokenRead {
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 := []historydb.TokenRead{}
883 for _, token := range tc.tokens {
884 fetchedToken := historydb.TokenRead{}
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 assertTokensAPIs(t *testing.T, expected, actual []historydb.TokenRead) {
892 require.Equal(t, len(expected), len(actual))
893 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
894 if expected[i].USDUpdate == nil {
895 assert.Equal(t, expected[i].USDUpdate, actual[i].USDUpdate)
896 } else {
897 assert.Equal(t, expected[i].USDUpdate.Unix(), actual[i].USDUpdate.Unix())
898 expected[i].USDUpdate = actual[i].USDUpdate
899 }
900 assert.Equal(t, expected[i], actual[i])
901 }
902}
903
904func doGoodReqPaginated(
905 path, order string,
906 iterStruct db.Paginationer,
907 appendIter func(res interface{}),
908) error {
909 next := 0
910 for {
911 // Call API to get this iteration items
912 iterPath := path
913 if next == 0 && order == historydb.OrderDesc {
914 // Fetch first item in reverse order
915 iterPath += "99999"
916 } else {
917 // Fetch from next item or 0 if it's ascending order
918 iterPath += strconv.Itoa(next)
919 }
920 if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
921 return err
922 }
923 appendIter(iterStruct)
924 // Keep iterating?
925 pag := iterStruct.GetPagination()
926 if order == historydb.OrderAsc {
927 if pag.LastReturnedItem == pag.LastItem { // No
928 break
929 } else { // Yes
930 next = pag.LastReturnedItem + 1
931 }
932 } else {
933 if pag.FirstReturnedItem == pag.FirstItem { // No
934 break
935 } else { // Yes
936 next = pag.FirstReturnedItem - 1
937 }
938 }
939 }
940 return nil
941}
942
943func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
944 ctx := context.Background()
945 client := &http.Client{}
946 httpReq, _ := http.NewRequest(method, path, reqBody)
947 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
948 if err != nil {
949 return err
950 }
951 // Validate request against swagger spec
952 requestValidationInput := &swagger.RequestValidationInput{
953 Request: httpReq,
954 PathParams: pathParams,
955 Route: route,
956 }
957 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
958 return err
959 }
960 // Do API call
961 resp, err := client.Do(httpReq)
962 if err != nil {
963 return err
964 }
965 if resp.Body == nil {
966 return errors.New("Nil body")
967 }
968 //nolint
969 defer resp.Body.Close()
970 body, err := ioutil.ReadAll(resp.Body)
971 if err != nil {
972 return err
973 }
974 if resp.StatusCode != 200 {
975 return fmt.Errorf("%d response: %s", resp.StatusCode, string(body))
976 }
977 // Unmarshal body into return struct
978 if err := json.Unmarshal(body, returnStruct); err != nil {
979 return err
980 }
981 // Validate response against swagger spec
982 responseValidationInput := &swagger.ResponseValidationInput{
983 RequestValidationInput: requestValidationInput,
984 Status: resp.StatusCode,
985 Header: resp.Header,
986 }
987 responseValidationInput = responseValidationInput.SetBodyBytes(body)
988 return swagger.ValidateResponse(ctx, responseValidationInput)
989}
990
991func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
992 ctx := context.Background()
993 client := &http.Client{}
994 httpReq, _ := http.NewRequest(method, path, reqBody)
995 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
996 if err != nil {
997 return err
998 }
999 // Validate request against swagger spec
1000 requestValidationInput := &swagger.RequestValidationInput{
1001 Request: httpReq,
1002 PathParams: pathParams,
1003 Route: route,
1004 }
1005 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
1006 if expectedResponseCode != 400 {
1007 return err
1008 }
1009 log.Warn("The request does not match the API spec")
1010 }
1011 // Do API call
1012 resp, err := client.Do(httpReq)
1013 if err != nil {
1014 return err
1015 }
1016 if resp.Body == nil {
1017 return errors.New("Nil body")
1018 }
1019 //nolint
1020 defer resp.Body.Close()
1021 body, err := ioutil.ReadAll(resp.Body)
1022 if err != nil {
1023 return err
1024 }
1025 if resp.StatusCode != expectedResponseCode {
1026 return fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
1027 }
1028 // Validate response against swagger spec
1029 responseValidationInput := &swagger.ResponseValidationInput{
1030 RequestValidationInput: requestValidationInput,
1031 Status: resp.StatusCode,
1032 Header: resp.Header,
1033 }
1034 responseValidationInput = responseValidationInput.SetBodyBytes(body)
1035 return swagger.ValidateResponse(ctx, responseValidationInput)
1036}