Refactor/api txsfeature/sql-semaphore1
@ -0,0 +1,507 @@ |
|||||
|
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 int `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) |
||||
|
assertHistoryTxAPIs(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)
|
||||
|
// assertHistoryTxAPIs(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)
|
||||
|
// assertHistoryTxAPIs(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]) |
||||
|
} |
||||
|
} |
||||
|
assertHistoryTxAPIs(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]) |
||||
|
} |
||||
|
} |
||||
|
assertHistoryTxAPIs(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]) |
||||
|
} |
||||
|
} |
||||
|
assertHistoryTxAPIs(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]) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
assertHistoryTxAPIs(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]) |
||||
|
} |
||||
|
assertHistoryTxAPIs(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) |
||||
|
} |
||||
|
assertHistoryTxAPIs(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 assertHistoryTxAPIs(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]) |
||||
|
} |
||||
|
} |
@ -0,0 +1,171 @@ |
|||||
|
package api |
||||
|
|
||||
|
import ( |
||||
|
"errors" |
||||
|
"math/big" |
||||
|
"net/http" |
||||
|
|
||||
|
ethCommon "github.com/ethereum/go-ethereum/common" |
||||
|
"github.com/gin-gonic/gin" |
||||
|
"github.com/hermeznetwork/hermez-node/apitypes" |
||||
|
"github.com/hermeznetwork/hermez-node/common" |
||||
|
"github.com/hermeznetwork/hermez-node/db/l2db" |
||||
|
"github.com/iden3/go-iden3-crypto/babyjub" |
||||
|
) |
||||
|
|
||||
|
func postPoolTx(c *gin.Context) { |
||||
|
// Parse body
|
||||
|
var receivedTx receivedPoolTx |
||||
|
if err := c.ShouldBindJSON(&receivedTx); err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
// Transform from received to insert format and validate
|
||||
|
writeTx := receivedTx.toPoolL2TxWrite() |
||||
|
if err := verifyPoolL2TxWrite(writeTx); err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
// Insert to DB
|
||||
|
if err := l2.AddTx(writeTx); err != nil { |
||||
|
retSQLErr(err, c) |
||||
|
return |
||||
|
} |
||||
|
// Return TxID
|
||||
|
c.JSON(http.StatusOK, writeTx.TxID.String()) |
||||
|
} |
||||
|
|
||||
|
func getPoolTx(c *gin.Context) { |
||||
|
// Get TxID
|
||||
|
txID, err := parseParamTxID(c) |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
// Fetch tx from l2DB
|
||||
|
tx, err := l2.GetTxAPI(txID) |
||||
|
if err != nil { |
||||
|
retSQLErr(err, c) |
||||
|
return |
||||
|
} |
||||
|
// Build succesfull response
|
||||
|
c.JSON(http.StatusOK, tx) |
||||
|
} |
||||
|
|
||||
|
type receivedPoolTx struct { |
||||
|
TxID common.TxID `json:"id" binding:"required"` |
||||
|
Type common.TxType `json:"type" binding:"required"` |
||||
|
TokenID common.TokenID `json:"tokenId"` |
||||
|
FromIdx apitypes.StrHezIdx `json:"fromAccountIndex" binding:"required"` |
||||
|
ToIdx *apitypes.StrHezIdx `json:"toAccountIndex"` |
||||
|
ToEthAddr *apitypes.StrHezEthAddr `json:"toHezEthereumAddress"` |
||||
|
ToBJJ *apitypes.StrHezBJJ `json:"toBjj"` |
||||
|
Amount apitypes.StrBigInt `json:"amount" binding:"required"` |
||||
|
Fee common.FeeSelector `json:"fee"` |
||||
|
Nonce common.Nonce `json:"nonce"` |
||||
|
Signature babyjub.SignatureComp `json:"signature" binding:"required"` |
||||
|
RqFromIdx *apitypes.StrHezIdx `json:"requestFromAccountIndex"` |
||||
|
RqToIdx *apitypes.StrHezIdx `json:"requestToAccountIndex"` |
||||
|
RqToEthAddr *apitypes.StrHezEthAddr `json:"requestToHezEthereumAddress"` |
||||
|
RqToBJJ *apitypes.StrHezBJJ `json:"requestToBjj"` |
||||
|
RqTokenID *common.TokenID `json:"requestTokenId"` |
||||
|
RqAmount *apitypes.StrBigInt `json:"requestAmount"` |
||||
|
RqFee *common.FeeSelector `json:"requestFee"` |
||||
|
RqNonce *common.Nonce `json:"requestNonce"` |
||||
|
} |
||||
|
|
||||
|
func (tx *receivedPoolTx) toPoolL2TxWrite() *l2db.PoolL2TxWrite { |
||||
|
f := new(big.Float).SetInt((*big.Int)(&tx.Amount)) |
||||
|
amountF, _ := f.Float64() |
||||
|
return &l2db.PoolL2TxWrite{ |
||||
|
TxID: tx.TxID, |
||||
|
FromIdx: common.Idx(tx.FromIdx), |
||||
|
ToIdx: (*common.Idx)(tx.ToIdx), |
||||
|
ToEthAddr: (*ethCommon.Address)(tx.ToEthAddr), |
||||
|
ToBJJ: (*babyjub.PublicKey)(tx.ToBJJ), |
||||
|
TokenID: tx.TokenID, |
||||
|
Amount: (*big.Int)(&tx.Amount), |
||||
|
AmountFloat: amountF, |
||||
|
Fee: tx.Fee, |
||||
|
Nonce: tx.Nonce, |
||||
|
State: common.PoolL2TxStatePending, |
||||
|
Signature: tx.Signature, |
||||
|
RqFromIdx: (*common.Idx)(tx.RqFromIdx), |
||||
|
RqToIdx: (*common.Idx)(tx.RqToIdx), |
||||
|
RqToEthAddr: (*ethCommon.Address)(tx.RqToEthAddr), |
||||
|
RqToBJJ: (*babyjub.PublicKey)(tx.RqToBJJ), |
||||
|
RqTokenID: tx.RqTokenID, |
||||
|
RqAmount: (*big.Int)(tx.RqAmount), |
||||
|
RqFee: tx.RqFee, |
||||
|
RqNonce: tx.RqNonce, |
||||
|
Type: tx.Type, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func verifyPoolL2TxWrite(txw *l2db.PoolL2TxWrite) error { |
||||
|
poolTx := common.PoolL2Tx{ |
||||
|
TxID: txw.TxID, |
||||
|
FromIdx: txw.FromIdx, |
||||
|
ToBJJ: txw.ToBJJ, |
||||
|
TokenID: txw.TokenID, |
||||
|
Amount: txw.Amount, |
||||
|
Fee: txw.Fee, |
||||
|
Nonce: txw.Nonce, |
||||
|
// State: txw.State,
|
||||
|
Signature: txw.Signature, |
||||
|
RqToBJJ: txw.RqToBJJ, |
||||
|
RqAmount: txw.RqAmount, |
||||
|
Type: txw.Type, |
||||
|
} |
||||
|
// ToIdx
|
||||
|
if txw.ToIdx != nil { |
||||
|
poolTx.ToIdx = *txw.ToIdx |
||||
|
} |
||||
|
// ToEthAddr
|
||||
|
if txw.ToEthAddr == nil { |
||||
|
poolTx.ToEthAddr = common.EmptyAddr |
||||
|
} else { |
||||
|
poolTx.ToEthAddr = *txw.ToEthAddr |
||||
|
} |
||||
|
// RqFromIdx
|
||||
|
if txw.RqFromIdx != nil { |
||||
|
poolTx.RqFromIdx = *txw.RqFromIdx |
||||
|
} |
||||
|
// RqToIdx
|
||||
|
if txw.RqToIdx != nil { |
||||
|
poolTx.RqToIdx = *txw.RqToIdx |
||||
|
} |
||||
|
// RqToEthAddr
|
||||
|
if txw.RqToEthAddr == nil { |
||||
|
poolTx.RqToEthAddr = common.EmptyAddr |
||||
|
} else { |
||||
|
poolTx.RqToEthAddr = *txw.RqToEthAddr |
||||
|
} |
||||
|
// RqTokenID
|
||||
|
if txw.RqTokenID != nil { |
||||
|
poolTx.RqTokenID = *txw.RqTokenID |
||||
|
} |
||||
|
// RqFee
|
||||
|
if txw.RqFee != nil { |
||||
|
poolTx.RqFee = *txw.RqFee |
||||
|
} |
||||
|
// RqNonce
|
||||
|
if txw.RqNonce != nil { |
||||
|
poolTx.RqNonce = *txw.RqNonce |
||||
|
} |
||||
|
// Check type and id
|
||||
|
_, err := common.NewPoolL2Tx(&poolTx) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
// Get public key
|
||||
|
account, err := s.GetAccount(poolTx.FromIdx) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
// Check signature
|
||||
|
if !poolTx.VerifySignature(account.PublicKey) { |
||||
|
return errors.New("wrong signature") |
||||
|
} |
||||
|
return nil |
||||
|
} |
@ -0,0 +1,272 @@ |
|||||
|
package api |
||||
|
|
||||
|
import ( |
||||
|
"bytes" |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"math/big" |
||||
|
"testing" |
||||
|
"time" |
||||
|
|
||||
|
"github.com/hermeznetwork/hermez-node/common" |
||||
|
"github.com/hermeznetwork/hermez-node/db/historydb" |
||||
|
"github.com/iden3/go-iden3-crypto/babyjub" |
||||
|
"github.com/stretchr/testify/assert" |
||||
|
) |
||||
|
|
||||
|
// testPoolTxReceive is a struct to be used to assert the response
|
||||
|
// of GET /transactions-pool/:id
|
||||
|
type testPoolTxReceive struct { |
||||
|
TxID common.TxID `json:"id"` |
||||
|
Type common.TxType `json:"type"` |
||||
|
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"` |
||||
|
Fee common.FeeSelector `json:"fee"` |
||||
|
Nonce common.Nonce `json:"nonce"` |
||||
|
State common.PoolL2TxState `json:"state"` |
||||
|
Signature babyjub.SignatureComp `json:"signature"` |
||||
|
RqFromIdx *string `json:"requestFromAccountIndex"` |
||||
|
RqToIdx *string `json:"requestToAccountIndex"` |
||||
|
RqToEthAddr *string `json:"requestToHezEthereumAddress"` |
||||
|
RqToBJJ *string `json:"requestToBJJ"` |
||||
|
RqTokenID *common.TokenID `json:"requestTokenId"` |
||||
|
RqAmount *string `json:"requestAmount"` |
||||
|
RqFee *common.FeeSelector `json:"requestFee"` |
||||
|
RqNonce *common.Nonce `json:"requestNonce"` |
||||
|
BatchNum *common.BatchNum `json:"batchNum"` |
||||
|
Timestamp time.Time `json:"timestamp"` |
||||
|
Token historydb.TokenWithUSD `json:"token"` |
||||
|
} |
||||
|
|
||||
|
// testPoolTxSend is a struct to be used as a JSON body
|
||||
|
// when testing POST /transactions-pool
|
||||
|
type testPoolTxSend struct { |
||||
|
TxID common.TxID `json:"id" binding:"required"` |
||||
|
Type common.TxType `json:"type" binding:"required"` |
||||
|
TokenID common.TokenID `json:"tokenId"` |
||||
|
FromIdx string `json:"fromAccountIndex" binding:"required"` |
||||
|
ToIdx *string `json:"toAccountIndex"` |
||||
|
ToEthAddr *string `json:"toHezEthereumAddress"` |
||||
|
ToBJJ *string `json:"toBjj"` |
||||
|
Amount string `json:"amount" binding:"required"` |
||||
|
Fee common.FeeSelector `json:"fee"` |
||||
|
Nonce common.Nonce `json:"nonce"` |
||||
|
Signature babyjub.SignatureComp `json:"signature" binding:"required"` |
||||
|
RqFromIdx *string `json:"requestFromAccountIndex"` |
||||
|
RqToIdx *string `json:"requestToAccountIndex"` |
||||
|
RqToEthAddr *string `json:"requestToHezEthereumAddress"` |
||||
|
RqToBJJ *string `json:"requestToBjj"` |
||||
|
RqTokenID *common.TokenID `json:"requestTokenId"` |
||||
|
RqAmount *string `json:"requestAmount"` |
||||
|
RqFee *common.FeeSelector `json:"requestFee"` |
||||
|
RqNonce *common.Nonce `json:"requestNonce"` |
||||
|
} |
||||
|
|
||||
|
func genTestPoolTx(accs []common.Account, privKs []babyjub.PrivateKey, tokens []historydb.TokenWithUSD) (poolTxsToSend []testPoolTxSend, poolTxsToReceive []testPoolTxReceive) { |
||||
|
// Generate common.PoolL2Tx
|
||||
|
// WARNING: this should be replaced once til is ready
|
||||
|
poolTxs := []common.PoolL2Tx{} |
||||
|
amount := new(big.Int) |
||||
|
amount, ok := amount.SetString("100000000000000", 10) |
||||
|
if !ok { |
||||
|
panic("bad amount") |
||||
|
} |
||||
|
poolTx := common.PoolL2Tx{ |
||||
|
FromIdx: accs[0].Idx, |
||||
|
ToIdx: accs[1].Idx, |
||||
|
Amount: amount, |
||||
|
TokenID: accs[0].TokenID, |
||||
|
Nonce: 6, |
||||
|
} |
||||
|
if _, err := common.NewPoolL2Tx(&poolTx); err != nil { |
||||
|
panic(err) |
||||
|
} |
||||
|
h, err := poolTx.HashToSign() |
||||
|
if err != nil { |
||||
|
panic(err) |
||||
|
} |
||||
|
poolTx.Signature = privKs[0].SignPoseidon(h).Compress() |
||||
|
poolTxs = append(poolTxs, poolTx) |
||||
|
// Transform to API formats
|
||||
|
poolTxsToSend = []testPoolTxSend{} |
||||
|
poolTxsToReceive = []testPoolTxReceive{} |
||||
|
for _, poolTx := range poolTxs { |
||||
|
fmt.Println(poolTx) |
||||
|
// common.PoolL2Tx ==> testPoolTxSend
|
||||
|
token := getTokenByID(poolTx.TokenID, tokens) |
||||
|
genSendTx := testPoolTxSend{ |
||||
|
TxID: poolTx.TxID, |
||||
|
Type: poolTx.Type, |
||||
|
TokenID: poolTx.TokenID, |
||||
|
FromIdx: idxToHez(poolTx.FromIdx, token.Symbol), |
||||
|
Amount: poolTx.Amount.String(), |
||||
|
Fee: poolTx.Fee, |
||||
|
Nonce: poolTx.Nonce, |
||||
|
Signature: poolTx.Signature, |
||||
|
RqFee: &poolTx.RqFee, |
||||
|
RqNonce: &poolTx.RqNonce, |
||||
|
} |
||||
|
// common.PoolL2Tx ==> testPoolTxReceive
|
||||
|
genReceiveTx := testPoolTxReceive{ |
||||
|
TxID: poolTx.TxID, |
||||
|
Type: poolTx.Type, |
||||
|
FromIdx: idxToHez(poolTx.FromIdx, token.Symbol), |
||||
|
Amount: poolTx.Amount.String(), |
||||
|
Fee: poolTx.Fee, |
||||
|
Nonce: poolTx.Nonce, |
||||
|
State: poolTx.State, |
||||
|
Signature: poolTx.Signature, |
||||
|
Timestamp: poolTx.Timestamp, |
||||
|
// BatchNum: poolTx.BatchNum,
|
||||
|
RqFee: &poolTx.RqFee, |
||||
|
RqNonce: &poolTx.RqNonce, |
||||
|
Token: token, |
||||
|
} |
||||
|
fromAcc := getAccountByIdx(poolTx.ToIdx, accs) |
||||
|
fromAddr := ethAddrToHez(fromAcc.EthAddr) |
||||
|
genReceiveTx.FromEthAddr = &fromAddr |
||||
|
fromBjj := bjjToString(fromAcc.PublicKey) |
||||
|
genReceiveTx.FromBJJ = &fromBjj |
||||
|
if poolTx.ToIdx != 0 { |
||||
|
toIdx := idxToHez(poolTx.ToIdx, token.Symbol) |
||||
|
genSendTx.ToIdx = &toIdx |
||||
|
genReceiveTx.ToIdx = &toIdx |
||||
|
} |
||||
|
if poolTx.ToEthAddr != common.EmptyAddr { |
||||
|
toEth := ethAddrToHez(poolTx.ToEthAddr) |
||||
|
genSendTx.ToEthAddr = &toEth |
||||
|
genReceiveTx.ToEthAddr = &toEth |
||||
|
} else if poolTx.ToIdx > 255 { |
||||
|
acc := getAccountByIdx(poolTx.ToIdx, accs) |
||||
|
addr := ethAddrToHez(acc.EthAddr) |
||||
|
genReceiveTx.ToEthAddr = &addr |
||||
|
} |
||||
|
if poolTx.ToBJJ != nil { |
||||
|
toBJJ := bjjToString(poolTx.ToBJJ) |
||||
|
genSendTx.ToBJJ = &toBJJ |
||||
|
genReceiveTx.ToBJJ = &toBJJ |
||||
|
} else if poolTx.ToIdx > 255 { |
||||
|
acc := getAccountByIdx(poolTx.ToIdx, accs) |
||||
|
bjj := bjjToString(acc.PublicKey) |
||||
|
genReceiveTx.ToBJJ = &bjj |
||||
|
} |
||||
|
if poolTx.RqFromIdx != 0 { |
||||
|
rqFromIdx := idxToHez(poolTx.RqFromIdx, token.Symbol) |
||||
|
genSendTx.RqFromIdx = &rqFromIdx |
||||
|
genReceiveTx.RqFromIdx = &rqFromIdx |
||||
|
genSendTx.RqTokenID = &token.TokenID |
||||
|
genReceiveTx.RqTokenID = &token.TokenID |
||||
|
rqAmount := poolTx.RqAmount.String() |
||||
|
genSendTx.RqAmount = &rqAmount |
||||
|
genReceiveTx.RqAmount = &rqAmount |
||||
|
|
||||
|
if poolTx.RqToIdx != 0 { |
||||
|
rqToIdx := idxToHez(poolTx.RqToIdx, token.Symbol) |
||||
|
genSendTx.RqToIdx = &rqToIdx |
||||
|
genReceiveTx.RqToIdx = &rqToIdx |
||||
|
} |
||||
|
if poolTx.RqToEthAddr != common.EmptyAddr { |
||||
|
rqToEth := ethAddrToHez(poolTx.RqToEthAddr) |
||||
|
genSendTx.RqToEthAddr = &rqToEth |
||||
|
genReceiveTx.RqToEthAddr = &rqToEth |
||||
|
} |
||||
|
if poolTx.RqToBJJ != nil { |
||||
|
rqToBJJ := bjjToString(poolTx.RqToBJJ) |
||||
|
genSendTx.RqToBJJ = &rqToBJJ |
||||
|
genReceiveTx.RqToBJJ = &rqToBJJ |
||||
|
} |
||||
|
} |
||||
|
poolTxsToSend = append(poolTxsToSend, genSendTx) |
||||
|
poolTxsToReceive = append(poolTxsToReceive, genReceiveTx) |
||||
|
} |
||||
|
return poolTxsToSend, poolTxsToReceive |
||||
|
} |
||||
|
|
||||
|
func TestPoolTxs(t *testing.T) { |
||||
|
// POST
|
||||
|
endpoint := apiURL + "transactions-pool" |
||||
|
fetchedTxID := common.TxID{} |
||||
|
for _, tx := range tc.poolTxsToSend { |
||||
|
jsonTxBytes, err := json.Marshal(tx) |
||||
|
assert.NoError(t, err) |
||||
|
jsonTxReader := bytes.NewReader(jsonTxBytes) |
||||
|
assert.NoError( |
||||
|
t, doGoodReq( |
||||
|
"POST", |
||||
|
endpoint, |
||||
|
jsonTxReader, &fetchedTxID, |
||||
|
), |
||||
|
) |
||||
|
assert.Equal(t, tx.TxID, fetchedTxID) |
||||
|
} |
||||
|
// 400
|
||||
|
// Wrong signature
|
||||
|
badTx := tc.poolTxsToSend[0] |
||||
|
badTx.FromIdx = "hez:foo:1000" |
||||
|
jsonTxBytes, err := json.Marshal(badTx) |
||||
|
assert.NoError(t, err) |
||||
|
jsonTxReader := bytes.NewReader(jsonTxBytes) |
||||
|
err = doBadReq("POST", endpoint, jsonTxReader, 400) |
||||
|
assert.NoError(t, err) |
||||
|
// Wrong to
|
||||
|
badTx = tc.poolTxsToSend[0] |
||||
|
ethAddr := "hez:0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" |
||||
|
badTx.ToEthAddr = ðAddr |
||||
|
badTx.ToIdx = nil |
||||
|
jsonTxBytes, err = json.Marshal(badTx) |
||||
|
assert.NoError(t, err) |
||||
|
jsonTxReader = bytes.NewReader(jsonTxBytes) |
||||
|
err = doBadReq("POST", endpoint, jsonTxReader, 400) |
||||
|
assert.NoError(t, err) |
||||
|
// Wrong rq
|
||||
|
badTx = tc.poolTxsToSend[0] |
||||
|
rqFromIdx := "hez:foo:30" |
||||
|
badTx.RqFromIdx = &rqFromIdx |
||||
|
jsonTxBytes, err = json.Marshal(badTx) |
||||
|
assert.NoError(t, err) |
||||
|
jsonTxReader = bytes.NewReader(jsonTxBytes) |
||||
|
err = doBadReq("POST", endpoint, jsonTxReader, 400) |
||||
|
assert.NoError(t, err) |
||||
|
// GET
|
||||
|
endpoint += "/" |
||||
|
for _, tx := range tc.poolTxsToReceive { |
||||
|
fetchedTx := testPoolTxReceive{} |
||||
|
assert.NoError( |
||||
|
t, doGoodReq( |
||||
|
"GET", |
||||
|
endpoint+tx.TxID.String(), |
||||
|
nil, &fetchedTx, |
||||
|
), |
||||
|
) |
||||
|
assertPoolTx(t, tx, fetchedTx) |
||||
|
} |
||||
|
// 400
|
||||
|
err = doBadReq("GET", endpoint+"0xG20000000156660000000090", nil, 400) |
||||
|
assert.NoError(t, err) |
||||
|
// 404
|
||||
|
err = doBadReq("GET", endpoint+"0x020000000156660000000090", nil, 404) |
||||
|
assert.NoError(t, err) |
||||
|
} |
||||
|
|
||||
|
func assertPoolTx(t *testing.T, expected, actual testPoolTxReceive) { |
||||
|
// state should be pending
|
||||
|
assert.Equal(t, common.PoolL2TxStatePending, actual.State) |
||||
|
expected.State = actual.State |
||||
|
actual.Token.ItemID = 0 |
||||
|
// timestamp should be very close to now
|
||||
|
assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix()) |
||||
|
expected.Timestamp = actual.Timestamp |
||||
|
// token timestamp
|
||||
|
if expected.Token.USDUpdate == nil { |
||||
|
assert.Equal(t, expected.Token.USDUpdate, actual.Token.USDUpdate) |
||||
|
} else { |
||||
|
assert.Equal(t, expected.Token.USDUpdate.Unix(), actual.Token.USDUpdate.Unix()) |
||||
|
expected.Token.USDUpdate = actual.Token.USDUpdate |
||||
|
} |
||||
|
assert.Equal(t, expected, actual) |
||||
|
} |