arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
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&fromItem=", 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&fromItem=",
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&fromItem=",
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&fromItem=",
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&fromItem=",
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&fromItem=",
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&fromItem=",
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&fromItem=",
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&fromItem=", 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?limit=1000&fromItem=999999", 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}