mirror of
https://github.com/arnaucube/hermez-node.git
synced 2026-02-07 03:16:45 +01:00
Impls exit endpoints and change pagination to cursor
This commit is contained in:
@@ -18,6 +18,13 @@ import (
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
const (
|
||||
// OrderAsc indicates ascending order when using pagination
|
||||
OrderAsc = "ASC"
|
||||
// OrderDesc indicates descending order when using pagination
|
||||
OrderDesc = "DESC"
|
||||
)
|
||||
|
||||
// TODO(Edu): Document here how HistoryDB is kept consistent
|
||||
|
||||
// HistoryDB persist the historic of the rollup
|
||||
@@ -445,25 +452,47 @@ func (hdb *HistoryDB) addTxs(d meddler.DB, txs []txWrite) error {
|
||||
// return db.SlicePtrsToSlice(txs).([]common.Tx), err
|
||||
// }
|
||||
|
||||
// GetHistoryTx returns a tx from the DB given a TxID
|
||||
func (hdb *HistoryDB) GetHistoryTx(txID common.TxID) (*HistoryTx, error) {
|
||||
tx := &HistoryTx{}
|
||||
err := meddler.QueryRow(
|
||||
hdb.db, tx, `SELECT tx.item_id, tx.is_l1, tx.id, tx.type, tx.position,
|
||||
tx.from_idx, tx.to_idx, tx.amount, tx.token_id, tx.amount_usd,
|
||||
tx.batch_num, tx.eth_block_num, tx.to_forge_l1_txs_num, tx.user_origin,
|
||||
tx.from_eth_addr, tx.from_bjj, tx.load_amount,
|
||||
tx.load_amount_usd, tx.fee, tx.fee_usd, tx.nonce,
|
||||
token.token_id, token.eth_block_num AS token_block,
|
||||
token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
|
||||
token.usd_update, block.timestamp
|
||||
FROM tx INNER JOIN token ON tx.token_id = token.token_id
|
||||
INNER JOIN block ON tx.eth_block_num = block.eth_block_num
|
||||
WHERE tx.id = $1;`, txID,
|
||||
)
|
||||
return tx, err
|
||||
}
|
||||
|
||||
// GetHistoryTxs returns a list of txs from the DB using the HistoryTx struct
|
||||
// and pagination info
|
||||
func (hdb *HistoryDB) GetHistoryTxs(
|
||||
ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
|
||||
tokenID, idx, batchNum *uint, txType *common.TxType,
|
||||
offset, limit *uint, last bool,
|
||||
) ([]HistoryTx, int, error) {
|
||||
tokenID *common.TokenID, idx *common.Idx, batchNum *uint, txType *common.TxType,
|
||||
fromItem, limit *uint, order string,
|
||||
) ([]HistoryTx, *db.Pagination, error) {
|
||||
if ethAddr != nil && bjj != nil {
|
||||
return nil, 0, errors.New("ethAddr and bjj are incompatible")
|
||||
return nil, nil, errors.New("ethAddr and bjj are incompatible")
|
||||
}
|
||||
var query string
|
||||
var args []interface{}
|
||||
queryStr := `SELECT tx.is_l1, tx.id, tx.type, tx.position, tx.from_idx, tx.to_idx,
|
||||
tx.amount, tx.token_id, tx.batch_num, tx.eth_block_num, tx.to_forge_l1_txs_num,
|
||||
tx.user_origin, tx.from_eth_addr, tx.from_bjj, tx.load_amount, tx.fee, tx.nonce,
|
||||
queryStr := `SELECT tx.item_id, tx.is_l1, tx.id, tx.type, tx.position,
|
||||
tx.from_idx, tx.to_idx, tx.amount, tx.token_id, tx.amount_usd,
|
||||
tx.batch_num, tx.eth_block_num, tx.to_forge_l1_txs_num, tx.user_origin,
|
||||
tx.from_eth_addr, tx.from_bjj, tx.load_amount,
|
||||
tx.load_amount_usd, tx.fee, tx.fee_usd, tx.nonce,
|
||||
token.token_id, token.eth_block_num AS token_block,
|
||||
token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
|
||||
token.usd_update, block.timestamp, count(*) OVER() AS total_items
|
||||
FROM tx
|
||||
INNER JOIN token ON tx.token_id = token.token_id
|
||||
token.usd_update, block.timestamp, count(*) OVER() AS total_items,
|
||||
MIN(tx.item_id) OVER() AS first_item, MAX(tx.item_id) OVER() AS last_item
|
||||
FROM tx INNER JOIN token ON tx.token_id = token.token_id
|
||||
INNER JOIN block ON tx.eth_block_num = block.eth_block_num `
|
||||
// Apply filters
|
||||
nextIsAnd := false
|
||||
@@ -523,33 +552,164 @@ func (hdb *HistoryDB) GetHistoryTxs(
|
||||
}
|
||||
queryStr += "tx.type = ? "
|
||||
args = append(args, txType)
|
||||
nextIsAnd = true
|
||||
}
|
||||
if fromItem != nil {
|
||||
if nextIsAnd {
|
||||
queryStr += "AND "
|
||||
} else {
|
||||
queryStr += "WHERE "
|
||||
}
|
||||
if order == OrderAsc {
|
||||
queryStr += "tx.item_id >= ? "
|
||||
} else {
|
||||
queryStr += "tx.item_id <= ? "
|
||||
}
|
||||
args = append(args, fromItem)
|
||||
nextIsAnd = true
|
||||
}
|
||||
if nextIsAnd {
|
||||
queryStr += "AND "
|
||||
} else {
|
||||
queryStr += "WHERE "
|
||||
}
|
||||
queryStr += "tx.batch_num IS NOT NULL "
|
||||
|
||||
// pagination
|
||||
queryStr += "ORDER BY tx.item_id "
|
||||
if order == OrderAsc {
|
||||
queryStr += " ASC "
|
||||
} else {
|
||||
queryStr += " DESC "
|
||||
}
|
||||
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
|
||||
query = hdb.db.Rebind(queryStr)
|
||||
log.Debug(query)
|
||||
txsPtrs := []*HistoryTx{}
|
||||
if err := meddler.QueryAll(hdb.db, &txsPtrs, query, args...); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
txs := db.SlicePtrsToSlice(txsPtrs).([]HistoryTx)
|
||||
if len(txs) == 0 {
|
||||
return nil, nil, sql.ErrNoRows
|
||||
}
|
||||
return txs, &db.Pagination{
|
||||
TotalItems: txs[0].TotalItems,
|
||||
FirstItem: txs[0].FirstItem,
|
||||
LastItem: txs[0].LastItem,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetExit returns a exit from the DB
|
||||
func (hdb *HistoryDB) GetExit(batchNum *uint, idx *common.Idx) (*HistoryExit, error) {
|
||||
exit := &HistoryExit{}
|
||||
err := meddler.QueryRow(
|
||||
hdb.db, exit, `SELECT exit_tree.*, token.token_id, token.eth_block_num AS token_block,
|
||||
token.eth_addr, token.name, token.symbol, token.decimals, token.usd, token.usd_update
|
||||
FROM exit_tree INNER JOIN account ON exit_tree.account_idx = account.idx
|
||||
INNER JOIN token ON account.token_id = token.token_id
|
||||
WHERE exit_tree.batch_num = $1 AND exit_tree.account_idx = $2;`, batchNum, idx,
|
||||
)
|
||||
return exit, err
|
||||
}
|
||||
|
||||
// GetExits returns a list of exits from the DB and pagination info
|
||||
func (hdb *HistoryDB) GetExits(
|
||||
ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
|
||||
tokenID *common.TokenID, idx *common.Idx, batchNum *uint,
|
||||
fromItem, limit *uint, order string,
|
||||
) ([]HistoryExit, *db.Pagination, error) {
|
||||
if ethAddr != nil && bjj != nil {
|
||||
return nil, nil, errors.New("ethAddr and bjj are incompatible")
|
||||
}
|
||||
var query string
|
||||
var args []interface{}
|
||||
queryStr := `SELECT exit_tree.*, token.token_id, token.eth_block_num AS token_block,
|
||||
token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
|
||||
token.usd_update, COUNT(*) OVER() AS total_items, MIN(exit_tree.item_id) OVER() AS first_item, MAX(exit_tree.item_id) OVER() AS last_item
|
||||
FROM exit_tree INNER JOIN account ON exit_tree.account_idx = account.idx
|
||||
INNER JOIN token ON account.token_id = token.token_id `
|
||||
// Apply filters
|
||||
nextIsAnd := false
|
||||
// ethAddr filter
|
||||
if ethAddr != nil {
|
||||
queryStr += "WHERE account.eth_addr = ? "
|
||||
nextIsAnd = true
|
||||
args = append(args, ethAddr)
|
||||
} else if bjj != nil { // bjj filter
|
||||
queryStr += "WHERE account.bjj = ? "
|
||||
nextIsAnd = true
|
||||
args = append(args, bjj)
|
||||
}
|
||||
// tokenID filter
|
||||
if tokenID != nil {
|
||||
if nextIsAnd {
|
||||
queryStr += "AND "
|
||||
} else {
|
||||
queryStr += "WHERE "
|
||||
}
|
||||
queryStr += "account.token_id = ? "
|
||||
args = append(args, tokenID)
|
||||
nextIsAnd = true
|
||||
}
|
||||
// idx filter
|
||||
if idx != nil {
|
||||
if nextIsAnd {
|
||||
queryStr += "AND "
|
||||
} else {
|
||||
queryStr += "WHERE "
|
||||
}
|
||||
queryStr += "exit_tree.account_idx = ? "
|
||||
args = append(args, idx)
|
||||
nextIsAnd = true
|
||||
}
|
||||
// batchNum filter
|
||||
if batchNum != nil {
|
||||
if nextIsAnd {
|
||||
queryStr += "AND "
|
||||
} else {
|
||||
queryStr += "WHERE "
|
||||
}
|
||||
queryStr += "exit_tree.batch_num = ? "
|
||||
args = append(args, batchNum)
|
||||
nextIsAnd = true
|
||||
}
|
||||
if fromItem != nil {
|
||||
if nextIsAnd {
|
||||
queryStr += "AND "
|
||||
} else {
|
||||
queryStr += "WHERE "
|
||||
}
|
||||
if order == OrderAsc {
|
||||
queryStr += "exit_tree.item_id >= ? "
|
||||
} else {
|
||||
queryStr += "exit_tree.item_id <= ? "
|
||||
}
|
||||
args = append(args, fromItem)
|
||||
// nextIsAnd = true
|
||||
}
|
||||
// pagination
|
||||
if last {
|
||||
queryStr += "ORDER BY (batch_num, position) DESC NULLS FIRST "
|
||||
queryStr += "ORDER BY exit_tree.item_id "
|
||||
if order == OrderAsc {
|
||||
queryStr += " ASC "
|
||||
} else {
|
||||
queryStr += "ORDER BY (batch_num, position) ASC NULLS LAST "
|
||||
queryStr += fmt.Sprintf("OFFSET %d ", *offset)
|
||||
queryStr += " DESC "
|
||||
}
|
||||
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
|
||||
query = hdb.db.Rebind(queryStr)
|
||||
// log.Debug(query)
|
||||
txsPtrs := []*HistoryTx{}
|
||||
if err := meddler.QueryAll(hdb.db, &txsPtrs, query, args...); err != nil {
|
||||
return nil, 0, err
|
||||
exits := []*HistoryExit{}
|
||||
if err := meddler.QueryAll(hdb.db, &exits, query, args...); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
txs := db.SlicePtrsToSlice(txsPtrs).([]HistoryTx)
|
||||
if len(txs) == 0 {
|
||||
return nil, 0, sql.ErrNoRows
|
||||
} else if last {
|
||||
tmp := []HistoryTx{}
|
||||
for i := len(txs) - 1; i >= 0; i-- {
|
||||
tmp = append(tmp, txs[i])
|
||||
}
|
||||
txs = tmp
|
||||
if len(exits) == 0 {
|
||||
return nil, nil, sql.ErrNoRows
|
||||
}
|
||||
return txs, txs[0].TotalItems, nil
|
||||
return db.SlicePtrsToSlice(exits).([]HistoryExit), &db.Pagination{
|
||||
TotalItems: exits[0].TotalItems,
|
||||
FirstItem: exits[0].FirstItem,
|
||||
LastItem: exits[0].LastItem,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// // GetTx returns a tx from the DB
|
||||
|
||||
@@ -213,6 +213,7 @@ func TestTxs(t *testing.T) {
|
||||
|
||||
/*
|
||||
Uncomment once the transaction generation is fixed
|
||||
!! test that batches that forge user L1s !!
|
||||
!! Missing tests to check that historic USD is not set if USDUpdate is too old (24h) !!
|
||||
|
||||
// Generate fake L1 txs
|
||||
@@ -333,9 +334,14 @@ func TestExitTree(t *testing.T) {
|
||||
blocks := setTestBlocks(0, 10)
|
||||
batches := test.GenBatches(nBatches, blocks)
|
||||
err := historyDB.AddBatches(batches)
|
||||
const nTokens = 50
|
||||
tokens := test.GenTokens(nTokens, blocks)
|
||||
assert.NoError(t, historyDB.AddTokens(tokens))
|
||||
assert.NoError(t, err)
|
||||
|
||||
exitTree := test.GenExitTree(nBatches)
|
||||
const nAccounts = 3
|
||||
accs := test.GenAccounts(nAccounts, 0, tokens, nil, nil, batches)
|
||||
assert.NoError(t, historyDB.AddAccounts(accs))
|
||||
exitTree := test.GenExitTree(nBatches, batches, accs)
|
||||
err = historyDB.AddExitTree(exitTree)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/hermeznetwork/hermez-node/common"
|
||||
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||
"github.com/iden3/go-merkletree"
|
||||
)
|
||||
|
||||
// HistoryTx is a representation of a generic Tx with additional information
|
||||
@@ -15,6 +16,7 @@ type HistoryTx struct {
|
||||
// Generic
|
||||
IsL1 bool `meddler:"is_l1"`
|
||||
TxID common.TxID `meddler:"id"`
|
||||
ItemID int `meddler:"item_id"`
|
||||
Type common.TxType `meddler:"type"`
|
||||
Position int `meddler:"position"`
|
||||
FromIdx *common.Idx `meddler:"from_idx"`
|
||||
@@ -38,6 +40,8 @@ type HistoryTx struct {
|
||||
// API extras
|
||||
Timestamp time.Time `meddler:"timestamp,utctime"`
|
||||
TotalItems int `meddler:"total_items"`
|
||||
FirstItem int `meddler:"first_item"`
|
||||
LastItem int `meddler:"last_item"`
|
||||
TokenID common.TokenID `meddler:"token_id"`
|
||||
TokenEthBlockNum int64 `meddler:"token_block"`
|
||||
TokenEthAddr ethCommon.Address `meddler:"eth_addr"`
|
||||
@@ -86,3 +90,27 @@ type TokenRead struct {
|
||||
USD *float64 `json:"USD" meddler:"usd"`
|
||||
USDUpdate *time.Time `json:"fiatUpdate" meddler:"usd_update,utctime"`
|
||||
}
|
||||
|
||||
// HistoryExit is a representation of a exit with additional information
|
||||
// required by the API, and extracted by joining token table
|
||||
type HistoryExit struct {
|
||||
ItemID int `meddler:"item_id"`
|
||||
BatchNum common.BatchNum `meddler:"batch_num"`
|
||||
AccountIdx common.Idx `meddler:"account_idx"`
|
||||
MerkleProof *merkletree.CircomVerifierProof `meddler:"merkle_proof,json"`
|
||||
Balance *big.Int `meddler:"balance,bigint"`
|
||||
InstantWithdrawn *int64 `meddler:"instant_withdrawn"`
|
||||
DelayedWithdrawRequest *int64 `meddler:"delayed_withdraw_request"`
|
||||
DelayedWithdrawn *int64 `meddler:"delayed_withdrawn"`
|
||||
TotalItems int `meddler:"total_items"`
|
||||
FirstItem int `meddler:"first_item"`
|
||||
LastItem int `meddler:"last_item"`
|
||||
TokenID common.TokenID `meddler:"token_id"`
|
||||
TokenEthBlockNum int64 `meddler:"token_block"`
|
||||
TokenEthAddr ethCommon.Address `meddler:"eth_addr"`
|
||||
TokenName string `meddler:"name"`
|
||||
TokenSymbol string `meddler:"symbol"`
|
||||
TokenDecimals uint64 `meddler:"decimals"`
|
||||
TokenUSD *float64 `meddler:"usd"`
|
||||
TokenUSDUpdate *time.Time `meddler:"usd_update"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user