|
|
package api
import ( "fmt" "math" "math/big" "sort" "testing" "time"
"github.com/hermeznetwork/hermez-node/apitypes" "github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/db/historydb" "github.com/hermeznetwork/hermez-node/test" "github.com/mitchellh/copystructure" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" )
type testL1Info struct { ToForgeL1TxsNum *int64 `json:"toForgeL1TransactionsNum"` UserOrigin bool `json:"userOrigin"` LoadAmount string `json:"loadAmount"` HistoricLoadAmountUSD *float64 `json:"historicLoadAmountUSD"` EthBlockNum int64 `json:"ethereumBlockNum"` }
type testL2Info struct { Fee common.FeeSelector `json:"fee"` HistoricFeeUSD *float64 `json:"historicFeeUSD"` Nonce common.Nonce `json:"nonce"` }
type testTx struct { IsL1 string `json:"L1orL2"` TxID common.TxID `json:"id"` ItemID uint64 `json:"itemId"` Type common.TxType `json:"type"` Position int `json:"position"` FromIdx *string `json:"fromAccountIndex"` FromEthAddr *string `json:"fromHezEthereumAddress"` FromBJJ *string `json:"fromBJJ"` ToIdx string `json:"toAccountIndex"` ToEthAddr *string `json:"toHezEthereumAddress"` ToBJJ *string `json:"toBJJ"` Amount string `json:"amount"` BatchNum *common.BatchNum `json:"batchNum"` HistoricUSD *float64 `json:"historicUSD"` Timestamp time.Time `json:"timestamp"` L1Info *testL1Info `json:"L1Info"` L2Info *testL2Info `json:"L2Info"` Token historydb.TokenWithUSD `json:"token"` }
type txsSort []testTx
func (t txsSort) Len() int { return len(t) } func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] } func (t txsSort) Less(i, j int) bool { // i not forged yet
isf := t[i] jsf := t[j] if isf.BatchNum == nil { if jsf.BatchNum != nil { // j is already forged
return false } // Both aren't forged, is i in a smaller position?
return isf.Position < jsf.Position } // i is forged
if jsf.BatchNum == nil { return false // j is not forged
} // Both are forged
if *isf.BatchNum == *jsf.BatchNum { // At the same batch, is i in a smaller position?
return isf.Position < jsf.Position } // At different batches, is i in a smaller batch?
return *isf.BatchNum < *jsf.BatchNum }
type testTxsResponse struct { Txs []testTx `json:"transactions"` PendingItems uint64 `json:"pendingItems"` }
func (t testTxsResponse) GetPending() (pendingItems, lastItemID uint64) { pendingItems = t.PendingItems lastItemID = t.Txs[len(t.Txs)-1].ItemID return pendingItems, lastItemID }
func (t testTxsResponse) Len() int { return len(t.Txs) }
func (t testTxsResponse) New() Pendinger { return &testTxsResponse{} }
func genTestTxs( l1s []common.L1Tx, l2s []common.L2Tx, accs []common.Account, tokens []historydb.TokenWithUSD, blocks []common.Block, ) []testTx { txs := []testTx{} // common.L1Tx ==> testTx
for _, l1 := range l1s { token := getTokenByID(l1.TokenID, tokens) // l1.FromEthAddr and l1.FromBJJ can't be nil
fromEthAddr := string(apitypes.NewHezEthAddr(l1.FromEthAddr)) fromBJJ := string(apitypes.NewHezBJJ(l1.FromBJJ)) tx := testTx{ IsL1: "L1", TxID: l1.TxID, Type: l1.Type, Position: l1.Position, FromEthAddr: &fromEthAddr, FromBJJ: &fromBJJ, ToIdx: idxToHez(l1.ToIdx, token.Symbol), Amount: l1.Amount.String(), BatchNum: l1.BatchNum, Timestamp: getTimestamp(l1.EthBlockNum, blocks), L1Info: &testL1Info{ ToForgeL1TxsNum: l1.ToForgeL1TxsNum, UserOrigin: l1.UserOrigin, LoadAmount: l1.LoadAmount.String(), EthBlockNum: l1.EthBlockNum, }, Token: token, } // set BatchNum for user txs
if tx.L1Info.ToForgeL1TxsNum != nil { // WARNING: this is an asumption, and the test input data can brake it easily
bn := common.BatchNum(*tx.L1Info.ToForgeL1TxsNum + 2) tx.BatchNum = &bn } // If FromIdx is not nil
idxStr := idxToHez(l1.FromIdx, token.Symbol) tx.FromIdx = &idxStr // If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
if l1.ToIdx >= common.UserThreshold { // find account
for _, acc := range accs { if l1.ToIdx == acc.Idx { toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr)) tx.ToEthAddr = &toEthAddr toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey)) tx.ToBJJ = &toBJJ break } } } // If the token has USD value setted
if token.USD != nil { af := new(big.Float).SetInt(l1.Amount) amountFloat, _ := af.Float64() usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals)) if usd != 0 { tx.HistoricUSD = &usd } laf := new(big.Float).SetInt(l1.LoadAmount) loadAmountFloat, _ := laf.Float64() loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals)) if loadAmountFloat != 0 { tx.L1Info.HistoricLoadAmountUSD = &loadUSD } } txs = append(txs, tx) }
// common.L2Tx ==> testTx
for i := 0; i < len(l2s); i++ { token := getTokenByIdx(l2s[i].FromIdx, tokens, accs) // l1.FromIdx can't be nil
fromIdx := idxToHez(l2s[i].FromIdx, token.Symbol) tx := testTx{ IsL1: "L2", TxID: l2s[i].TxID, Type: l2s[i].Type, Position: l2s[i].Position, ToIdx: idxToHez(l2s[i].ToIdx, token.Symbol), FromIdx: &fromIdx, Amount: l2s[i].Amount.String(), BatchNum: &l2s[i].BatchNum, Timestamp: getTimestamp(l2s[i].EthBlockNum, blocks), L2Info: &testL2Info{ Nonce: l2s[i].Nonce, Fee: l2s[i].Fee, }, Token: token, } // If FromIdx is not nil
if l2s[i].FromIdx != 0 { idxStr := idxToHez(l2s[i].FromIdx, token.Symbol) tx.FromIdx = &idxStr } // Set FromEthAddr and FromBJJ (FromIdx it's always >255)
for _, acc := range accs { if l2s[i].FromIdx == acc.Idx { fromEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr)) tx.FromEthAddr = &fromEthAddr fromBJJ := string(apitypes.NewHezBJJ(acc.PublicKey)) tx.FromBJJ = &fromBJJ break } } // If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
if l2s[i].ToIdx >= common.UserThreshold { // find account
for _, acc := range accs { if l2s[i].ToIdx == acc.Idx { toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr)) tx.ToEthAddr = &toEthAddr toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey)) tx.ToBJJ = &toBJJ break } } } // If the token has USD value setted
if token.USD != nil { af := new(big.Float).SetInt(l2s[i].Amount) amountFloat, _ := af.Float64() usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals)) if usd != 0 { tx.HistoricUSD = &usd feeUSD := usd * l2s[i].Fee.Percentage() if feeUSD != 0 { tx.L2Info.HistoricFeeUSD = &feeUSD } } } txs = append(txs, tx) }
// Sort txs
sortedTxs := txsSort(txs) sort.Sort(sortedTxs)
return []testTx(sortedTxs) }
func TestGetHistoryTxs(t *testing.T) { endpoint := apiURL + "transactions-history" fetchedTxs := []testTx{} appendIter := func(intr interface{}) { for i := 0; i < len(intr.(*testTxsResponse).Txs); i++ { tmp, err := copystructure.Copy(intr.(*testTxsResponse).Txs[i]) if err != nil { panic(err) } fetchedTxs = append(fetchedTxs, tmp.(testTx)) } } // Get all (no filters)
limit := 8 path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit) err := doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) assert.NoError(t, err) assertTxs(t, tc.txs, fetchedTxs) // Get by ethAddr
account := tc.accounts[2] fetchedTxs = []testTx{} limit = 7 path = fmt.Sprintf( "%s?hermezEthereumAddress=%s&limit=%d&fromItem=", endpoint, account.EthAddr, limit, ) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) assert.NoError(t, err) accountTxs := []testTx{} for i := 0; i < len(tc.txs); i++ { tx := tc.txs[i] if (tx.FromIdx != nil && *tx.FromIdx == string(account.Idx)) || tx.ToIdx == string(account.Idx) || (tx.FromEthAddr != nil && *tx.FromEthAddr == string(account.EthAddr)) || (tx.ToEthAddr != nil && *tx.ToEthAddr == string(account.EthAddr)) || (tx.FromBJJ != nil && *tx.FromBJJ == string(account.PublicKey)) || (tx.ToBJJ != nil && *tx.ToBJJ == string(account.PublicKey)) { accountTxs = append(accountTxs, tx) } } assertTxs(t, accountTxs, fetchedTxs) // Get by bjj
fetchedTxs = []testTx{} limit = 6 path = fmt.Sprintf( "%s?BJJ=%s&limit=%d&fromItem=", endpoint, account.PublicKey, limit, ) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) assert.NoError(t, err) assertTxs(t, accountTxs, fetchedTxs) // Get by tokenID
fetchedTxs = []testTx{} limit = 5 tokenID := tc.txs[0].Token.TokenID path = fmt.Sprintf( "%s?tokenId=%d&limit=%d&fromItem=", endpoint, tokenID, limit, ) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) assert.NoError(t, err) tokenIDTxs := []testTx{} for i := 0; i < len(tc.txs); i++ { if tc.txs[i].Token.TokenID == tokenID { tokenIDTxs = append(tokenIDTxs, tc.txs[i]) } } assertTxs(t, tokenIDTxs, fetchedTxs) // idx
fetchedTxs = []testTx{} limit = 4 idxStr := tc.txs[0].ToIdx idx, err := stringToIdx(idxStr, "") assert.NoError(t, err) path = fmt.Sprintf( "%s?accountIndex=%s&limit=%d&fromItem=", endpoint, idxStr, limit, ) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) assert.NoError(t, err) idxTxs := []testTx{} for i := 0; i < len(tc.txs); i++ { var fromIdx *common.Idx if tc.txs[i].FromIdx != nil { fromIdx, err = stringToIdx(*tc.txs[i].FromIdx, "") assert.NoError(t, err) if *fromIdx == *idx { idxTxs = append(idxTxs, tc.txs[i]) continue } } toIdx, err := stringToIdx((tc.txs[i].ToIdx), "") assert.NoError(t, err) if *toIdx == *idx { idxTxs = append(idxTxs, tc.txs[i]) } } assertTxs(t, idxTxs, fetchedTxs) // batchNum
fetchedTxs = []testTx{} limit = 3 batchNum := tc.txs[0].BatchNum path = fmt.Sprintf( "%s?batchNum=%d&limit=%d&fromItem=", endpoint, *batchNum, limit, ) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) assert.NoError(t, err) batchNumTxs := []testTx{} for i := 0; i < len(tc.txs); i++ { if tc.txs[i].BatchNum != nil && *tc.txs[i].BatchNum == *batchNum { batchNumTxs = append(batchNumTxs, tc.txs[i]) } } assertTxs(t, batchNumTxs, fetchedTxs) // type
txTypes := []common.TxType{ // Uncomment once test gen is fixed
common.TxTypeExit, common.TxTypeTransfer, common.TxTypeDeposit, common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDepositTransfer, common.TxTypeDepositTransfer, common.TxTypeForceTransfer, common.TxTypeForceExit, } for _, txType := range txTypes { fetchedTxs = []testTx{} limit = 2 path = fmt.Sprintf( "%s?type=%s&limit=%d&fromItem=", endpoint, txType, limit, ) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) assert.NoError(t, err) txTypeTxs := []testTx{} for i := 0; i < len(tc.txs); i++ { if tc.txs[i].Type == txType { txTypeTxs = append(txTypeTxs, tc.txs[i]) } } assertTxs(t, txTypeTxs, fetchedTxs) } // Multiple filters
fetchedTxs = []testTx{} limit = 1 path = fmt.Sprintf( "%s?batchNum=%d&tokenId=%d&limit=%d&fromItem=", endpoint, *batchNum, tokenID, limit, ) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) assert.NoError(t, err) mixedTxs := []testTx{} for i := 0; i < len(tc.txs); i++ { if tc.txs[i].BatchNum != nil { if *tc.txs[i].BatchNum == *batchNum && tc.txs[i].Token.TokenID == tokenID { mixedTxs = append(mixedTxs, tc.txs[i]) } } } assertTxs(t, mixedTxs, fetchedTxs) // All, in reverse order
fetchedTxs = []testTx{} limit = 5 path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit) err = doGoodReqPaginated(path, historydb.OrderDesc, &testTxsResponse{}, appendIter) assert.NoError(t, err) flipedTxs := []testTx{} for i := 0; i < len(tc.txs); i++ { flipedTxs = append(flipedTxs, tc.txs[len(tc.txs)-1-i]) } assertTxs(t, flipedTxs, fetchedTxs) // 400
path = fmt.Sprintf( "%s?accountIndex=%s&hermezEthereumAddress=%s", endpoint, idx, account.EthAddr, ) err = doBadReq("GET", path, nil, 400) assert.NoError(t, err) path = fmt.Sprintf("%s?tokenId=X", endpoint) err = doBadReq("GET", path, nil, 400) assert.NoError(t, err) // 404
path = fmt.Sprintf("%s?batchNum=999999", endpoint) err = doBadReq("GET", path, nil, 404) assert.NoError(t, err) path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint) err = doBadReq("GET", path, nil, 404) assert.NoError(t, err) }
func TestGetHistoryTx(t *testing.T) { // Get all txs by their ID
endpoint := apiURL + "transactions-history/" fetchedTxs := []testTx{} for _, tx := range tc.txs { fetchedTx := testTx{} err := doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx) assert.NoError(t, err) fetchedTxs = append(fetchedTxs, fetchedTx) } assertTxs(t, tc.txs, fetchedTxs) // 400
err := doBadReq("GET", endpoint+"0x001", nil, 400) assert.NoError(t, err) // 404
err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404) assert.NoError(t, err) }
func assertTxs(t *testing.T, expected, actual []testTx) { require.Equal(t, len(expected), len(actual)) for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
assert.Equal(t, expected[i].BatchNum, actual[i].BatchNum) assert.Equal(t, expected[i].Position, actual[i].Position) actual[i].ItemID = 0 actual[i].Token.ItemID = 0 assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix()) expected[i].Timestamp = actual[i].Timestamp if expected[i].Token.USDUpdate == nil { assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate) expected[i].Token.USDUpdate = actual[i].Token.USDUpdate } else { assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix()) expected[i].Token.USDUpdate = actual[i].Token.USDUpdate } test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD) if expected[i].L2Info != nil { test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD) } else { test.AssertUSD(t, expected[i].L1Info.HistoricLoadAmountUSD, actual[i].L1Info.HistoricLoadAmountUSD) } assert.Equal(t, expected[i], actual[i]) } }
|