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