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	if len(t.Txs) == 0 {
 92		return 0, 0
 93	}
 94	pendingItems = t.PendingItems
 95	lastItemID = t.Txs[len(t.Txs)-1].ItemID
 96	return pendingItems, lastItemID
 97}
 98
 99func (t testTxsResponse) Len() int {
100	return len(t.Txs)
101}
102
103func (t testTxsResponse) New() Pendinger { return &testTxsResponse{} }
104
105func genTestTxs(
106	l1s []common.L1Tx,
107	l2s []common.L2Tx,
108	accs []common.Account,
109	tokens []historydb.TokenWithUSD,
110	blocks []common.Block,
111) []testTx {
112	txs := []testTx{}
113	// common.L1Tx ==> testTx
114	for _, l1 := range l1s {
115		token := getTokenByID(l1.TokenID, tokens)
116		// l1.FromEthAddr and l1.FromBJJ can't be nil
117		fromEthAddr := string(apitypes.NewHezEthAddr(l1.FromEthAddr))
118		fromBJJ := string(apitypes.NewHezBJJ(l1.FromBJJ))
119		tx := testTx{
120			IsL1:        "L1",
121			TxID:        l1.TxID,
122			Type:        l1.Type,
123			Position:    l1.Position,
124			FromEthAddr: &fromEthAddr,
125			FromBJJ:     &fromBJJ,
126			ToIdx:       idxToHez(l1.ToIdx, token.Symbol),
127			Amount:      l1.Amount.String(),
128			BatchNum:    l1.BatchNum,
129			Timestamp:   getTimestamp(l1.EthBlockNum, blocks),
130			L1Info: &testL1Info{
131				ToForgeL1TxsNum:      l1.ToForgeL1TxsNum,
132				UserOrigin:           l1.UserOrigin,
133				DepositAmount:        l1.DepositAmount.String(),
134				AmountSuccess:        true,
135				DepositAmountSuccess: true,
136				EthBlockNum:          l1.EthBlockNum,
137			},
138			Token: token,
139		}
140		// set BatchNum for user txs
141		if tx.L1Info.ToForgeL1TxsNum != nil {
142			// WARNING: this is an asumption, and the test input data can brake it easily
143			bn := common.BatchNum(*tx.L1Info.ToForgeL1TxsNum + 2)
144			tx.BatchNum = &bn
145		}
146		// TODO: User L1 txs that create txs will have fromAccountIndex equal to the idx of the
147		// created account. Once this is done this test will be broken and will need to be updated here.
148		// At the moment they are null
149		if l1.Type != common.TxTypeCreateAccountDeposit &&
150			l1.Type != common.TxTypeCreateAccountDepositTransfer {
151			idxStr := idxToHez(l1.FromIdx, token.Symbol)
152			tx.FromIdx = &idxStr
153		}
154		// If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
155		if l1.ToIdx >= common.UserThreshold {
156			// find account
157			for _, acc := range accs {
158				if l1.ToIdx == acc.Idx {
159					toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
160					tx.ToEthAddr = &toEthAddr
161					toBJJ := string(apitypes.NewHezBJJ(acc.BJJ))
162					tx.ToBJJ = &toBJJ
163					break
164				}
165			}
166		}
167		// If the token has USD value setted
168		if token.USD != nil {
169			af := new(big.Float).SetInt(l1.Amount)
170			amountFloat, _ := af.Float64()
171			usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
172			if usd != 0 {
173				tx.HistoricUSD = &usd
174			}
175			laf := new(big.Float).SetInt(l1.DepositAmount)
176			depositAmountFloat, _ := laf.Float64()
177			depositUSD := *token.USD * depositAmountFloat / math.Pow(10, float64(token.Decimals))
178			if depositAmountFloat != 0 {
179				tx.L1Info.HistoricDepositAmountUSD = &depositUSD
180			}
181		}
182		txs = append(txs, tx)
183	}
184
185	// common.L2Tx ==> testTx
186	for i := 0; i < len(l2s); i++ {
187		token := getTokenByIdx(l2s[i].FromIdx, tokens, accs)
188		// l1.FromIdx can't be nil
189		fromIdx := idxToHez(l2s[i].FromIdx, token.Symbol)
190		tx := testTx{
191			IsL1:      "L2",
192			TxID:      l2s[i].TxID,
193			Type:      l2s[i].Type,
194			Position:  l2s[i].Position,
195			ToIdx:     idxToHez(l2s[i].ToIdx, token.Symbol),
196			FromIdx:   &fromIdx,
197			Amount:    l2s[i].Amount.String(),
198			BatchNum:  &l2s[i].BatchNum,
199			Timestamp: getTimestamp(l2s[i].EthBlockNum, blocks),
200			L2Info: &testL2Info{
201				Nonce: l2s[i].Nonce,
202				Fee:   l2s[i].Fee,
203			},
204			Token: token,
205		}
206		// If FromIdx is not nil
207		if l2s[i].FromIdx != 0 {
208			idxStr := idxToHez(l2s[i].FromIdx, token.Symbol)
209			tx.FromIdx = &idxStr
210		}
211		// Set FromEthAddr and FromBJJ (FromIdx it's always >255)
212		for _, acc := range accs {
213			if l2s[i].FromIdx == acc.Idx {
214				fromEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
215				tx.FromEthAddr = &fromEthAddr
216				fromBJJ := string(apitypes.NewHezBJJ(acc.BJJ))
217				tx.FromBJJ = &fromBJJ
218				break
219			}
220		}
221		// If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
222		if l2s[i].ToIdx >= common.UserThreshold {
223			// find account
224			for _, acc := range accs {
225				if l2s[i].ToIdx == acc.Idx {
226					toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
227					tx.ToEthAddr = &toEthAddr
228					toBJJ := string(apitypes.NewHezBJJ(acc.BJJ))
229					tx.ToBJJ = &toBJJ
230					break
231				}
232			}
233		}
234		// If the token has USD value setted
235		if token.USD != nil {
236			af := new(big.Float).SetInt(l2s[i].Amount)
237			amountFloat, _ := af.Float64()
238			usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
239			if usd != 0 {
240				tx.HistoricUSD = &usd
241				feeUSD := usd * l2s[i].Fee.Percentage()
242				if feeUSD != 0 {
243					tx.L2Info.HistoricFeeUSD = &feeUSD
244				}
245			}
246		}
247		txs = append(txs, tx)
248	}
249
250	// Sort txs
251	sortedTxs := txsSort(txs)
252	sort.Sort(sortedTxs)
253
254	return []testTx(sortedTxs)
255}
256
257func TestGetHistoryTxs(t *testing.T) {
258	endpoint := apiURL + "transactions-history"
259	fetchedTxs := []testTx{}
260	appendIter := func(intr interface{}) {
261		for i := 0; i < len(intr.(*testTxsResponse).Txs); i++ {
262			tmp, err := copystructure.Copy(intr.(*testTxsResponse).Txs[i])
263			if err != nil {
264				panic(err)
265			}
266			fetchedTxs = append(fetchedTxs, tmp.(testTx))
267		}
268	}
269	// Get all (no filters)
270	limit := 20
271	path := fmt.Sprintf("%s?limit=%d", endpoint, limit)
272	err := doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
273	assert.NoError(t, err)
274	assertTxs(t, tc.txs, fetchedTxs)
275	// Get by ethAddr
276	account := tc.accounts[2]
277	fetchedTxs = []testTx{}
278	limit = 7
279	path = fmt.Sprintf(
280		"%s?hezEthereumAddress=%s&limit=%d",
281		endpoint, account.EthAddr, limit,
282	)
283	err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
284	assert.NoError(t, err)
285	accountTxs := []testTx{}
286	for i := 0; i < len(tc.txs); i++ {
287		tx := tc.txs[i]
288		if (tx.FromIdx != nil && *tx.FromIdx == string(account.Idx)) ||
289			tx.ToIdx == string(account.Idx) ||
290			(tx.FromEthAddr != nil && *tx.FromEthAddr == string(account.EthAddr)) ||
291			(tx.ToEthAddr != nil && *tx.ToEthAddr == string(account.EthAddr)) ||
292			(tx.FromBJJ != nil && *tx.FromBJJ == string(account.PublicKey)) ||
293			(tx.ToBJJ != nil && *tx.ToBJJ == string(account.PublicKey)) {
294			accountTxs = append(accountTxs, tx)
295		}
296	}
297	assertTxs(t, accountTxs, fetchedTxs)
298	// Get by bjj
299	fetchedTxs = []testTx{}
300	limit = 6
301	path = fmt.Sprintf(
302		"%s?BJJ=%s&limit=%d",
303		endpoint, account.PublicKey, limit,
304	)
305	err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
306	assert.NoError(t, err)
307	assertTxs(t, accountTxs, fetchedTxs)
308	// Get by tokenID
309	fetchedTxs = []testTx{}
310	limit = 5
311	tokenID := tc.txs[0].Token.TokenID
312	path = fmt.Sprintf(
313		"%s?tokenId=%d&limit=%d",
314		endpoint, tokenID, limit,
315	)
316	err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
317	assert.NoError(t, err)
318	tokenIDTxs := []testTx{}
319	for i := 0; i < len(tc.txs); i++ {
320		if tc.txs[i].Token.TokenID == tokenID {
321			tokenIDTxs = append(tokenIDTxs, tc.txs[i])
322		}
323	}
324	assertTxs(t, tokenIDTxs, fetchedTxs)
325	// idx
326	fetchedTxs = []testTx{}
327	limit = 4
328	idxStr := tc.txs[0].ToIdx
329	idx, err := stringToIdx(idxStr, "")
330	assert.NoError(t, err)
331	path = fmt.Sprintf(
332		"%s?accountIndex=%s&limit=%d",
333		endpoint, idxStr, limit,
334	)
335	err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
336	assert.NoError(t, err)
337	idxTxs := []testTx{}
338	for i := 0; i < len(tc.txs); i++ {
339		var fromIdx *common.Idx
340		if tc.txs[i].FromIdx != nil {
341			fromIdx, err = stringToIdx(*tc.txs[i].FromIdx, "")
342			assert.NoError(t, err)
343			if *fromIdx == *idx {
344				idxTxs = append(idxTxs, tc.txs[i])
345				continue
346			}
347		}
348		toIdx, err := stringToIdx((tc.txs[i].ToIdx), "")
349		assert.NoError(t, err)
350		if *toIdx == *idx {
351			idxTxs = append(idxTxs, tc.txs[i])
352		}
353	}
354	assertTxs(t, idxTxs, fetchedTxs)
355	// batchNum
356	fetchedTxs = []testTx{}
357	limit = 3
358	batchNum := tc.txs[0].BatchNum
359	path = fmt.Sprintf(
360		"%s?batchNum=%d&limit=%d",
361		endpoint, *batchNum, limit,
362	)
363	err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
364	assert.NoError(t, err)
365	batchNumTxs := []testTx{}
366	for i := 0; i < len(tc.txs); i++ {
367		if tc.txs[i].BatchNum != nil &&
368			*tc.txs[i].BatchNum == *batchNum {
369			batchNumTxs = append(batchNumTxs, tc.txs[i])
370		}
371	}
372	assertTxs(t, batchNumTxs, fetchedTxs)
373	// type
374	txTypes := []common.TxType{
375		// Uncomment once test gen is fixed
376		common.TxTypeExit,
377		common.TxTypeTransfer,
378		common.TxTypeDeposit,
379		common.TxTypeCreateAccountDeposit,
380		common.TxTypeCreateAccountDepositTransfer,
381		common.TxTypeDepositTransfer,
382		common.TxTypeForceTransfer,
383		common.TxTypeForceExit,
384	}
385	for _, txType := range txTypes {
386		fetchedTxs = []testTx{}
387		limit = 2
388		path = fmt.Sprintf(
389			"%s?type=%s&limit=%d",
390			endpoint, txType, limit,
391		)
392		err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
393		assert.NoError(t, err)
394		txTypeTxs := []testTx{}
395		for i := 0; i < len(tc.txs); i++ {
396			if tc.txs[i].Type == txType {
397				txTypeTxs = append(txTypeTxs, tc.txs[i])
398			}
399		}
400		assertTxs(t, txTypeTxs, fetchedTxs)
401	}
402	// Multiple filters
403	fetchedTxs = []testTx{}
404	limit = 1
405	path = fmt.Sprintf(
406		"%s?batchNum=%d&tokenId=%d&limit=%d",
407		endpoint, *batchNum, tokenID, limit,
408	)
409	err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
410	assert.NoError(t, err)
411	mixedTxs := []testTx{}
412	for i := 0; i < len(tc.txs); i++ {
413		if tc.txs[i].BatchNum != nil {
414			if *tc.txs[i].BatchNum == *batchNum && tc.txs[i].Token.TokenID == tokenID {
415				mixedTxs = append(mixedTxs, tc.txs[i])
416			}
417		}
418	}
419	assertTxs(t, mixedTxs, fetchedTxs)
420	// All, in reverse order
421	fetchedTxs = []testTx{}
422	limit = 5
423	path = fmt.Sprintf("%s?limit=%d", endpoint, limit)
424	err = doGoodReqPaginated(path, historydb.OrderDesc, &testTxsResponse{}, appendIter)
425	assert.NoError(t, err)
426	flipedTxs := []testTx{}
427	for i := 0; i < len(tc.txs); i++ {
428		flipedTxs = append(flipedTxs, tc.txs[len(tc.txs)-1-i])
429	}
430	assertTxs(t, flipedTxs, fetchedTxs)
431	// Empty array
432	fetchedTxs = []testTx{}
433	path = fmt.Sprintf("%s?batchNum=999999", endpoint)
434	err = doGoodReqPaginated(path, historydb.OrderDesc, &testTxsResponse{}, appendIter)
435	assert.NoError(t, err)
436	assertTxs(t, []testTx{}, fetchedTxs)
437	// 400
438	path = fmt.Sprintf(
439		"%s?accountIndex=%s&hezEthereumAddress=%s",
440		endpoint, idx, account.EthAddr,
441	)
442	err = doBadReq("GET", path, nil, 400)
443	assert.NoError(t, err)
444	path = fmt.Sprintf("%s?tokenId=X", endpoint)
445	err = doBadReq("GET", path, nil, 400)
446	assert.NoError(t, err)
447}
448
449func TestGetHistoryTx(t *testing.T) {
450	// Get all txs by their ID
451	endpoint := apiURL + "transactions-history/"
452	fetchedTxs := []testTx{}
453	for _, tx := range tc.txs {
454		fetchedTx := testTx{}
455		err := doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx)
456		assert.NoError(t, err)
457		fetchedTxs = append(fetchedTxs, fetchedTx)
458	}
459	assertTxs(t, tc.txs, fetchedTxs)
460	// 400
461	err := doBadReq("GET", endpoint+"0x001", nil, 400)
462	assert.NoError(t, err)
463	// 404
464	err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404)
465	assert.NoError(t, err)
466}
467
468func assertTxs(t *testing.T, expected, actual []testTx) {
469	require.Equal(t, len(expected), len(actual))
470	for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
471		assert.Equal(t, expected[i].BatchNum, actual[i].BatchNum)
472		assert.Equal(t, expected[i].Position, actual[i].Position)
473		actual[i].ItemID = 0
474		actual[i].Token.ItemID = 0
475		assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
476		expected[i].Timestamp = actual[i].Timestamp
477		if expected[i].Token.USDUpdate == nil {
478			assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
479			expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
480		} else {
481			assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
482			expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
483		}
484		test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
485		if expected[i].L2Info != nil {
486			test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
487		} else {
488			test.AssertUSD(t, expected[i].L1Info.HistoricDepositAmountUSD, actual[i].L1Info.HistoricDepositAmountUSD)
489		}
490		assert.Equal(t, expected[i], actual[i])
491	}
492}