hermez-node/api/txshistory_test.go
api/txshistory_test.goRaw
1package api
2
3import (
4 "fmt"
5 "math"
6 "math/big"
7 "sort"
8 "testing"
9 "time"
10
11 "github.com/hermeznetwork/hermez-node/apitypes"
12 "github.com/hermeznetwork/hermez-node/common"
13 "github.com/hermeznetwork/hermez-node/db/historydb"
14 "github.com/hermeznetwork/hermez-node/test"
15 "github.com/mitchellh/copystructure"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18)
19
20type testL1Info struct {
21 ToForgeL1TxsNum *int64 `json:"toForgeL1TransactionsNum"`
22 UserOrigin bool `json:"userOrigin"`
23 DepositAmount string `json:"depositAmount"`
24 AmountSuccess bool `json:"amountSuccess"`
25 DepositAmountSuccess bool `json:"depositAmountSuccess"`
26 HistoricDepositAmountUSD *float64 `json:"historicDepositAmountUSD"`
27 EthBlockNum int64 `json:"ethereumBlockNum"`
28}
29
30type testL2Info struct {
31 Fee common.FeeSelector `json:"fee"`
32 HistoricFeeUSD *float64 `json:"historicFeeUSD"`
33 Nonce common.Nonce `json:"nonce"`
34}
35
36type testTx struct {
37 IsL1 string `json:"L1orL2"`
38 TxID common.TxID `json:"id"`
39 ItemID uint64 `json:"itemId"`
40 Type common.TxType `json:"type"`
41 Position int `json:"position"`
42 FromIdx *string `json:"fromAccountIndex"`
43 FromEthAddr *string `json:"fromHezEthereumAddress"`
44 FromBJJ *string `json:"fromBJJ"`
45 ToIdx string `json:"toAccountIndex"`
46 ToEthAddr *string `json:"toHezEthereumAddress"`
47 ToBJJ *string `json:"toBJJ"`
48 Amount string `json:"amount"`
49 BatchNum *common.BatchNum `json:"batchNum"`
50 HistoricUSD *float64 `json:"historicUSD"`
51 Timestamp time.Time `json:"timestamp"`
52 L1Info *testL1Info `json:"L1Info"`
53 L2Info *testL2Info `json:"L2Info"`
54 Token historydb.TokenWithUSD `json:"token"`
55}
56
57type txsSort []testTx
58
59func (t txsSort) Len() int { return len(t) }
60func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
61func (t txsSort) Less(i, j int) bool {
62 // i not forged yet
63 isf := t[i]
64 jsf := t[j]
65 if isf.BatchNum == nil {
66 if jsf.BatchNum != nil { // j is already forged
67 return false
68 }
69 // Both aren't forged, is i in a smaller position?
70 return isf.Position < jsf.Position
71 }
72 // i is forged
73 if jsf.BatchNum == nil {
74 return false // j is not forged
75 }
76 // Both are forged
77 if *isf.BatchNum == *jsf.BatchNum {
78 // At the same batch, is i in a smaller position?
79 return isf.Position < jsf.Position
80 }
81 // At different batches, is i in a smaller batch?
82 return *isf.BatchNum < *jsf.BatchNum
83}
84
85type testTxsResponse struct {
86 Txs []testTx `json:"transactions"`
87 PendingItems uint64 `json:"pendingItems"`
88}
89
90func (t testTxsResponse) GetPending() (pendingItems, lastItemID uint64) {
91 pendingItems = t.PendingItems
92 lastItemID = t.Txs[len(t.Txs)-1].ItemID
93 return pendingItems, lastItemID
94}
95
96func (t testTxsResponse) Len() int {
97 return len(t.Txs)
98}
99
100func (t testTxsResponse) New() Pendinger { return &testTxsResponse{} }
101
102func genTestTxs(
103 l1s []common.L1Tx,
104 l2s []common.L2Tx,
105 accs []common.Account,
106 tokens []historydb.TokenWithUSD,
107 blocks []common.Block,
108) []testTx {
109 txs := []testTx{}
110 // common.L1Tx ==> testTx
111 for _, l1 := range l1s {
112 token := getTokenByID(l1.TokenID, tokens)
113 // l1.FromEthAddr and l1.FromBJJ can't be nil
114 fromEthAddr := string(apitypes.NewHezEthAddr(l1.FromEthAddr))
115 fromBJJ := string(apitypes.NewHezBJJ(l1.FromBJJ))
116 tx := testTx{
117 IsL1: "L1",
118 TxID: l1.TxID,
119 Type: l1.Type,
120 Position: l1.Position,
121 FromEthAddr: &fromEthAddr,
122 FromBJJ: &fromBJJ,
123 ToIdx: idxToHez(l1.ToIdx, token.Symbol),
124 Amount: l1.Amount.String(),
125 BatchNum: l1.BatchNum,
126 Timestamp: getTimestamp(l1.EthBlockNum, blocks),
127 L1Info: &testL1Info{
128 ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
129 UserOrigin: l1.UserOrigin,
130 DepositAmount: l1.DepositAmount.String(),
131 AmountSuccess: true,
132 DepositAmountSuccess: true,
133 EthBlockNum: l1.EthBlockNum,
134 },
135 Token: token,
136 }
137 // set BatchNum for user txs
138 if tx.L1Info.ToForgeL1TxsNum != nil {
139 // WARNING: this is an asumption, and the test input data can brake it easily
140 bn := common.BatchNum(*tx.L1Info.ToForgeL1TxsNum + 2)
141 tx.BatchNum = &bn
142 }
143 // If FromIdx is not nil
144 idxStr := idxToHez(l1.FromIdx, token.Symbol)
145 tx.FromIdx = &idxStr
146 // If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
147 if l1.ToIdx >= common.UserThreshold {
148 // find account
149 for _, acc := range accs {
150 if l1.ToIdx == acc.Idx {
151 toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
152 tx.ToEthAddr = &toEthAddr
153 toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
154 tx.ToBJJ = &toBJJ
155 break
156 }
157 }
158 }
159 // If the token has USD value setted
160 if token.USD != nil {
161 af := new(big.Float).SetInt(l1.Amount)
162 amountFloat, _ := af.Float64()
163 usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
164 if usd != 0 {
165 tx.HistoricUSD = &usd
166 }
167 laf := new(big.Float).SetInt(l1.DepositAmount)
168 depositAmountFloat, _ := laf.Float64()
169 loadUSD := *token.USD * depositAmountFloat / math.Pow(10, float64(token.Decimals))
170 if depositAmountFloat != 0 {
171 tx.L1Info.HistoricDepositAmountUSD = &loadUSD
172 }
173 }
174 txs = append(txs, tx)
175 }
176
177 // common.L2Tx ==> testTx
178 for i := 0; i < len(l2s); i++ {
179 token := getTokenByIdx(l2s[i].FromIdx, tokens, accs)
180 // l1.FromIdx can't be nil
181 fromIdx := idxToHez(l2s[i].FromIdx, token.Symbol)
182 tx := testTx{
183 IsL1: "L2",
184 TxID: l2s[i].TxID,
185 Type: l2s[i].Type,
186 Position: l2s[i].Position,
187 ToIdx: idxToHez(l2s[i].ToIdx, token.Symbol),
188 FromIdx: &fromIdx,
189 Amount: l2s[i].Amount.String(),
190 BatchNum: &l2s[i].BatchNum,
191 Timestamp: getTimestamp(l2s[i].EthBlockNum, blocks),
192 L2Info: &testL2Info{
193 Nonce: l2s[i].Nonce,
194 Fee: l2s[i].Fee,
195 },
196 Token: token,
197 }
198 // If FromIdx is not nil
199 if l2s[i].FromIdx != 0 {
200 idxStr := idxToHez(l2s[i].FromIdx, token.Symbol)
201 tx.FromIdx = &idxStr
202 }
203 // Set FromEthAddr and FromBJJ (FromIdx it's always >255)
204 for _, acc := range accs {
205 if l2s[i].FromIdx == acc.Idx {
206 fromEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
207 tx.FromEthAddr = &fromEthAddr
208 fromBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
209 tx.FromBJJ = &fromBJJ
210 break
211 }
212 }
213 // If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
214 if l2s[i].ToIdx >= common.UserThreshold {
215 // find account
216 for _, acc := range accs {
217 if l2s[i].ToIdx == acc.Idx {
218 toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
219 tx.ToEthAddr = &toEthAddr
220 toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
221 tx.ToBJJ = &toBJJ
222 break
223 }
224 }
225 }
226 // If the token has USD value setted
227 if token.USD != nil {
228 af := new(big.Float).SetInt(l2s[i].Amount)
229 amountFloat, _ := af.Float64()
230 usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
231 if usd != 0 {
232 tx.HistoricUSD = &usd
233 feeUSD := usd * l2s[i].Fee.Percentage()
234 if feeUSD != 0 {
235 tx.L2Info.HistoricFeeUSD = &feeUSD
236 }
237 }
238 }
239 txs = append(txs, tx)
240 }
241
242 // Sort txs
243 sortedTxs := txsSort(txs)
244 sort.Sort(sortedTxs)
245
246 return []testTx(sortedTxs)
247}
248
249func TestGetHistoryTxs(t *testing.T) {
250 endpoint := apiURL + "transactions-history"
251 fetchedTxs := []testTx{}
252 appendIter := func(intr interface{}) {
253 for i := 0; i < len(intr.(*testTxsResponse).Txs); i++ {
254 tmp, err := copystructure.Copy(intr.(*testTxsResponse).Txs[i])
255 if err != nil {
256 panic(err)
257 }
258 fetchedTxs = append(fetchedTxs, tmp.(testTx))
259 }
260 }
261 // Get all (no filters)
262 limit := 20
263 path := fmt.Sprintf("%s?limit=%d", endpoint, limit)
264 err := doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
265 assert.NoError(t, err)
266 assertTxs(t, tc.txs, fetchedTxs)
267 // Get by ethAddr
268 account := tc.accounts[2]
269 fetchedTxs = []testTx{}
270 limit = 7
271 path = fmt.Sprintf(
272 "%s?hermezEthereumAddress=%s&limit=%d",
273 endpoint, account.EthAddr, limit,
274 )
275 err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
276 assert.NoError(t, err)
277 accountTxs := []testTx{}
278 for i := 0; i < len(tc.txs); i++ {
279 tx := tc.txs[i]
280 if (tx.FromIdx != nil && *tx.FromIdx == string(account.Idx)) ||
281 tx.ToIdx == string(account.Idx) ||
282 (tx.FromEthAddr != nil && *tx.FromEthAddr == string(account.EthAddr)) ||
283 (tx.ToEthAddr != nil && *tx.ToEthAddr == string(account.EthAddr)) ||
284 (tx.FromBJJ != nil && *tx.FromBJJ == string(account.PublicKey)) ||
285 (tx.ToBJJ != nil && *tx.ToBJJ == string(account.PublicKey)) {
286 accountTxs = append(accountTxs, tx)
287 }
288 }
289 assertTxs(t, accountTxs, fetchedTxs)
290 // Get by bjj
291 fetchedTxs = []testTx{}
292 limit = 6
293 path = fmt.Sprintf(
294 "%s?BJJ=%s&limit=%d",
295 endpoint, account.PublicKey, limit,
296 )
297 err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
298 assert.NoError(t, err)
299 assertTxs(t, accountTxs, fetchedTxs)
300 // Get by tokenID
301 fetchedTxs = []testTx{}
302 limit = 5
303 tokenID := tc.txs[0].Token.TokenID
304 path = fmt.Sprintf(
305 "%s?tokenId=%d&limit=%d",
306 endpoint, tokenID, limit,
307 )
308 err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
309 assert.NoError(t, err)
310 tokenIDTxs := []testTx{}
311 for i := 0; i < len(tc.txs); i++ {
312 if tc.txs[i].Token.TokenID == tokenID {
313 tokenIDTxs = append(tokenIDTxs, tc.txs[i])
314 }
315 }
316 assertTxs(t, tokenIDTxs, fetchedTxs)
317 // idx
318 fetchedTxs = []testTx{}
319 limit = 4
320 idxStr := tc.txs[0].ToIdx
321 idx, err := stringToIdx(idxStr, "")
322 assert.NoError(t, err)
323 path = fmt.Sprintf(
324 "%s?accountIndex=%s&limit=%d",
325 endpoint, idxStr, limit,
326 )
327 err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
328 assert.NoError(t, err)
329 idxTxs := []testTx{}
330 for i := 0; i < len(tc.txs); i++ {
331 var fromIdx *common.Idx
332 if tc.txs[i].FromIdx != nil {
333 fromIdx, err = stringToIdx(*tc.txs[i].FromIdx, "")
334 assert.NoError(t, err)
335 if *fromIdx == *idx {
336 idxTxs = append(idxTxs, tc.txs[i])
337 continue
338 }
339 }
340 toIdx, err := stringToIdx((tc.txs[i].ToIdx), "")
341 assert.NoError(t, err)
342 if *toIdx == *idx {
343 idxTxs = append(idxTxs, tc.txs[i])
344 }
345 }
346 assertTxs(t, idxTxs, fetchedTxs)
347 // batchNum
348 fetchedTxs = []testTx{}
349 limit = 3
350 batchNum := tc.txs[0].BatchNum
351 path = fmt.Sprintf(
352 "%s?batchNum=%d&limit=%d",
353 endpoint, *batchNum, limit,
354 )
355 err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
356 assert.NoError(t, err)
357 batchNumTxs := []testTx{}
358 for i := 0; i < len(tc.txs); i++ {
359 if tc.txs[i].BatchNum != nil &&
360 *tc.txs[i].BatchNum == *batchNum {
361 batchNumTxs = append(batchNumTxs, tc.txs[i])
362 }
363 }
364 assertTxs(t, batchNumTxs, fetchedTxs)
365 // type
366 txTypes := []common.TxType{
367 // Uncomment once test gen is fixed
368 common.TxTypeExit,
369 common.TxTypeTransfer,
370 common.TxTypeDeposit,
371 common.TxTypeCreateAccountDeposit,
372 common.TxTypeCreateAccountDepositTransfer,
373 common.TxTypeDepositTransfer,
374 common.TxTypeForceTransfer,
375 common.TxTypeForceExit,
376 }
377 for _, txType := range txTypes {
378 fetchedTxs = []testTx{}
379 limit = 2
380 path = fmt.Sprintf(
381 "%s?type=%s&limit=%d",
382 endpoint, txType, limit,
383 )
384 err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
385 assert.NoError(t, err)
386 txTypeTxs := []testTx{}
387 for i := 0; i < len(tc.txs); i++ {
388 if tc.txs[i].Type == txType {
389 txTypeTxs = append(txTypeTxs, tc.txs[i])
390 }
391 }
392 assertTxs(t, txTypeTxs, fetchedTxs)
393 }
394 // Multiple filters
395 fetchedTxs = []testTx{}
396 limit = 1
397 path = fmt.Sprintf(
398 "%s?batchNum=%d&tokenId=%d&limit=%d",
399 endpoint, *batchNum, tokenID, limit,
400 )
401 err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
402 assert.NoError(t, err)
403 mixedTxs := []testTx{}
404 for i := 0; i < len(tc.txs); i++ {
405 if tc.txs[i].BatchNum != nil {
406 if *tc.txs[i].BatchNum == *batchNum && tc.txs[i].Token.TokenID == tokenID {
407 mixedTxs = append(mixedTxs, tc.txs[i])
408 }
409 }
410 }
411 assertTxs(t, mixedTxs, fetchedTxs)
412 // All, in reverse order
413 fetchedTxs = []testTx{}
414 limit = 5
415 path = fmt.Sprintf("%s?limit=%d", endpoint, limit)
416 err = doGoodReqPaginated(path, historydb.OrderDesc, &testTxsResponse{}, appendIter)
417 assert.NoError(t, err)
418 flipedTxs := []testTx{}
419 for i := 0; i < len(tc.txs); i++ {
420 flipedTxs = append(flipedTxs, tc.txs[len(tc.txs)-1-i])
421 }
422 assertTxs(t, flipedTxs, fetchedTxs)
423 // 400
424 path = fmt.Sprintf(
425 "%s?accountIndex=%s&hermezEthereumAddress=%s",
426 endpoint, idx, account.EthAddr,
427 )
428 err = doBadReq("GET", path, nil, 400)
429 assert.NoError(t, err)
430 path = fmt.Sprintf("%s?tokenId=X", endpoint)
431 err = doBadReq("GET", path, nil, 400)
432 assert.NoError(t, err)
433 // 404
434 path = fmt.Sprintf("%s?batchNum=999999", endpoint)
435 err = doBadReq("GET", path, nil, 404)
436 assert.NoError(t, err)
437 path = fmt.Sprintf("%s?fromItem=1000999999", endpoint)
438 err = doBadReq("GET", path, nil, 404)
439 assert.NoError(t, err)
440}
441
442func TestGetHistoryTx(t *testing.T) {
443 // Get all txs by their ID
444 endpoint := apiURL + "transactions-history/"
445 fetchedTxs := []testTx{}
446 for _, tx := range tc.txs {
447 fetchedTx := testTx{}
448 err := doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx)
449 assert.NoError(t, err)
450 fetchedTxs = append(fetchedTxs, fetchedTx)
451 }
452 assertTxs(t, tc.txs, fetchedTxs)
453 // 400
454 err := doBadReq("GET", endpoint+"0x001", nil, 400)
455 assert.NoError(t, err)
456 // 404
457 err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404)
458 assert.NoError(t, err)
459}
460
461func assertTxs(t *testing.T, expected, actual []testTx) {
462 require.Equal(t, len(expected), len(actual))
463 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
464 assert.Equal(t, expected[i].BatchNum, actual[i].BatchNum)
465 assert.Equal(t, expected[i].Position, actual[i].Position)
466 actual[i].ItemID = 0
467 actual[i].Token.ItemID = 0
468 assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
469 expected[i].Timestamp = actual[i].Timestamp
470 if expected[i].Token.USDUpdate == nil {
471 assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
472 expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
473 } else {
474 assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
475 expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
476 }
477 test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
478 if expected[i].L2Info != nil {
479 test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
480 } else {
481 test.AssertUSD(t, expected[i].L1Info.HistoricDepositAmountUSD, actual[i].L1Info.HistoricDepositAmountUSD)
482 }
483 assert.Equal(t, expected[i], actual[i])
484 }
485}