|
|
package api
import ( "fmt" "math" "math/big" "testing" "time"
"github.com/hermeznetwork/hermez-node/apitypes" "github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/db" "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 testTxsResponse struct { Txs []testTx `json:"transactions"` Pagination *db.Pagination `json:"pagination"` }
func (t testTxsResponse) GetPagination() *db.Pagination { if t.Txs[0].ItemID < t.Txs[len(t.Txs)-1].ItemID { t.Pagination.FirstReturnedItem = t.Txs[0].ItemID t.Pagination.LastReturnedItem = t.Txs[len(t.Txs)-1].ItemID } else { t.Pagination.LastReturnedItem = t.Txs[0].ItemID t.Pagination.FirstReturnedItem = t.Txs[len(t.Txs)-1].ItemID } return t.Pagination }
func (t testTxsResponse) Len() int { return len(t.Txs) }
// TxSortFields represents the fields needed to sort L1 and L2 transactions
type txSortFields struct { BatchNum *common.BatchNum Position int }
// TxSortFielder is a interface that allows sorting L1 and L2 transactions in a combined way
type txSortFielder interface { SortFields() txSortFields L1() *common.L1Tx L2() *common.L2Tx }
// TxsSort array of TxSortFielder
type txsSort []txSortFielder
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].SortFields() jsf := t[j].SortFields() 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 wrappedL1 common.L1Tx
// SortFields implements TxSortFielder
func (tx *wrappedL1) SortFields() txSortFields { return txSortFields{ BatchNum: tx.BatchNum, Position: tx.Position, } }
// L1 implements TxSortFielder
func (tx *wrappedL1) L1() *common.L1Tx { l1tx := common.L1Tx(*tx) return &l1tx }
// L2 implements TxSortFielder
func (tx *wrappedL1) L2() *common.L2Tx { return nil }
type wrappedL2 common.L2Tx
// SortFields implements TxSortFielder
func (tx *wrappedL2) SortFields() txSortFields { return txSortFields{ BatchNum: &tx.BatchNum, Position: tx.Position, } }
// L1 implements TxSortFielder
func (tx *wrappedL2) L1() *common.L1Tx { return nil }
// L2 implements TxSortFielder
func (tx *wrappedL2) L2() *common.L2Tx { l2tx := common.L2Tx(*tx) return &l2tx }
func genTestTxs( genericTxs []txSortFielder, usrIdxs []string, accs []common.Account, tokens []historydb.TokenWithUSD, blocks []common.Block, ) (usrTxs []testTx, allTxs []testTx) { usrTxs = []testTx{} allTxs = []testTx{} isUsrTx := func(tx testTx) bool { for _, idx := range usrIdxs { if tx.FromIdx != nil && *tx.FromIdx == idx { return true } if tx.ToIdx == idx { return true } } return false } for _, genericTx := range genericTxs { l1 := genericTx.L1() l2 := genericTx.L2() if l1 != nil { // L1Tx to testTx
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, } // If FromIdx is not nil
if l1.FromIdx != 0 { 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)) tx.HistoricUSD = &usd laf := new(big.Float).SetInt(l1.LoadAmount) loadAmountFloat, _ := laf.Float64() loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals)) tx.L1Info.HistoricLoadAmountUSD = &loadUSD } allTxs = append(allTxs, tx) if isUsrTx(tx) { usrTxs = append(usrTxs, tx) } } else { // L2Tx to testTx
token := getTokenByIdx(l2.FromIdx, tokens, accs) // l1.FromIdx can't be nil
fromIdx := idxToHez(l2.FromIdx, token.Symbol) tx := testTx{ IsL1: "L2", TxID: l2.TxID, Type: l2.Type, Position: l2.Position, ToIdx: idxToHez(l2.ToIdx, token.Symbol), FromIdx: &fromIdx, Amount: l2.Amount.String(), BatchNum: &l2.BatchNum, Timestamp: getTimestamp(l2.EthBlockNum, blocks), L2Info: &testL2Info{ Nonce: l2.Nonce, Fee: l2.Fee, }, Token: token, } // If FromIdx is not nil
if l2.FromIdx != 0 { idxStr := idxToHez(l2.FromIdx, token.Symbol) tx.FromIdx = &idxStr } // Set FromEthAddr and FromBJJ (FromIdx it's always >255)
for _, acc := range accs { if l2.ToIdx == 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 l2.ToIdx >= common.UserThreshold { // find account
for _, acc := range accs { if l2.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(l2.Amount) amountFloat, _ := af.Float64() usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals)) tx.HistoricUSD = &usd feeUSD := usd * l2.Fee.Percentage() tx.HistoricUSD = &usd tx.L2Info.HistoricFeeUSD = &feeUSD } allTxs = append(allTxs, tx) if isUsrTx(tx) { usrTxs = append(usrTxs, tx) } } } return usrTxs, allTxs }
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.allTxs, fetchedTxs) // Uncomment once tx generation for tests is fixed
// // Get by ethAddr
// fetchedTxs = []testTx{}
// limit = 7
// path = fmt.Sprintf(
// "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
// endpoint, tc.usrAddr, limit,
// )
// err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
// assert.NoError(t, err)
// assertTxs(t, tc.usrTxs, fetchedTxs)
// // Get by bjj
// fetchedTxs = []testTx{}
// limit = 6
// path = fmt.Sprintf(
// "%s?BJJ=%s&limit=%d&fromItem=",
// endpoint, tc.usrBjj, limit,
// )
// err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
// assert.NoError(t, err)
// assertTxs(t, tc.usrTxs, fetchedTxs)
// Get by tokenID
fetchedTxs = []testTx{} limit = 5 tokenID := tc.allTxs[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.allTxs); i++ { if tc.allTxs[i].Token.TokenID == tokenID { tokenIDTxs = append(tokenIDTxs, tc.allTxs[i]) } } assertTxs(t, tokenIDTxs, fetchedTxs) // // idx
// fetchedTxs = []testTx{}
// limit = 4
idx := tc.allTxs[0].ToIdx // path = fmt.Sprintf(
// "%s?accountIndex=%s&limit=%d&fromItem=",
// endpoint, idx, limit,
// )
// err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
// assert.NoError(t, err)
// idxTxs := []testTx{}
// for i := 0; i < len(tc.allTxs); i++ {
// if (tc.allTxs[i].FromIdx != nil && (*tc.allTxs[i].FromIdx)[6:] == idx[6:]) ||
// tc.allTxs[i].ToIdx[6:] == idx[6:] {
// idxTxs = append(idxTxs, tc.allTxs[i])
// }
// }
// assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
// batchNum
fetchedTxs = []testTx{} limit = 3 batchNum := tc.allTxs[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.allTxs); i++ { if tc.allTxs[i].BatchNum != nil && *tc.allTxs[i].BatchNum == *batchNum { batchNumTxs = append(batchNumTxs, tc.allTxs[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,
// common.TxTypeTransferToEthAddr,
// common.TxTypeTransferToBJJ,
} 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.allTxs); i++ { if tc.allTxs[i].Type == txType { txTypeTxs = append(txTypeTxs, tc.allTxs[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.allTxs); i++ { if tc.allTxs[i].BatchNum != nil { if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID { mixedTxs = append(mixedTxs, tc.allTxs[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.allTxs); i++ { flipedTxs = append(flipedTxs, tc.allTxs[len(tc.allTxs)-1-i]) } assertTxs(t, flipedTxs, fetchedTxs) // 400
path = fmt.Sprintf( "%s?accountIndex=%s&hermezEthereumAddress=%s", endpoint, idx, tc.usrAddr, ) 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.allTxs { fetchedTx := testTx{} err := doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx) assert.NoError(t, err) fetchedTxs = append(fetchedTxs, fetchedTx) } assertTxs(t, tc.allTxs, 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
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) } 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]) } }
|