Stefan c020222f1b feat(Wallet): add activity filter api prototype
Add the possibility of retrieving the metadata of wallet activity based
on the given filter criteria.

Current implementation relies that after fetching the metadata, user
will follow up with more requests for details. However, after some
experimenting I'm considering extracting all required information
for the summary viewing while filtering. This way there will be no
need for another batch requests for transfers, multi-transactions and
pending transactions to show the summary. Only when user wants to see
the details for one will specifically request it.

For this first prototype, the filter criteria is limited to:
- time
- type
- addresses

Major changes:
- Add the filter definition to be used in propagating the filter
  information
- Add GetActivityEntries API to return the list of activity entries
  for the given addresses/chainIDs by a view in the complete list
- GetTransfersForIdentities to batch retrieve further details of the
  transfers
- GetPendingTransactionsForIdentities to batch retrieve further details
  of the pending transactions
- Added a new package testutils for tests.
- Added tests

Updates status-desktop #10366
Closes status-desktop #10633
2023-05-14 23:05:28 +02:00

167 lines
4.3 KiB
Go

package transfer
import (
"bytes"
"database/sql"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/status-im/status-go/services/wallet/bigint"
)
const baseTransfersQuery = "SELECT hash, type, blk_hash, blk_number, timestamp, address, tx, sender, receipt, log, network_id, base_gas_fee, COALESCE(multi_transaction_id, 0) FROM transfers"
type transfersQuery struct {
buf *bytes.Buffer
args []interface{}
whereAdded bool
subQuery bool
}
func newTransfersQuery() *transfersQuery {
newQuery := newEmptyQuery()
newQuery.buf.WriteString(baseTransfersQuery)
return newQuery
}
func newSubQuery() *transfersQuery {
newQuery := newEmptyQuery()
newQuery.subQuery = true
return newQuery
}
func newEmptyQuery() *transfersQuery {
buf := bytes.NewBuffer(nil)
return &transfersQuery{buf: buf}
}
func (q *transfersQuery) addWhereSeparator(separator SeparatorType) {
if !q.whereAdded {
if !q.subQuery {
q.buf.WriteString(" WHERE")
}
q.whereAdded = true
} else if separator == OrSeparator {
q.buf.WriteString(" OR")
} else if separator == AndSeparator {
q.buf.WriteString(" AND")
} else if separator != NoSeparator {
panic("Unknown separator. Need to handle current SeparatorType value")
}
}
type SeparatorType int
const (
NoSeparator SeparatorType = iota + 1
OrSeparator
AndSeparator
)
// addSubQuery adds where clause formed as: WHERE/<separator> (<subQuery>)
func (q *transfersQuery) addSubQuery(subQuery *transfersQuery, separator SeparatorType) *transfersQuery {
q.addWhereSeparator(separator)
q.buf.WriteString(" (")
q.buf.Write(subQuery.buf.Bytes())
q.buf.WriteString(")")
q.args = append(q.args, subQuery.args...)
return q
}
func (q *transfersQuery) FilterStart(start *big.Int) *transfersQuery {
if start != nil {
q.addWhereSeparator(AndSeparator)
q.buf.WriteString(" blk_number >= ?")
q.args = append(q.args, (*bigint.SQLBigInt)(start))
}
return q
}
func (q *transfersQuery) FilterEnd(end *big.Int) *transfersQuery {
if end != nil {
q.addWhereSeparator(AndSeparator)
q.buf.WriteString(" blk_number <= ?")
q.args = append(q.args, (*bigint.SQLBigInt)(end))
}
return q
}
func (q *transfersQuery) FilterLoaded(loaded int) *transfersQuery {
q.addWhereSeparator(AndSeparator)
q.buf.WriteString(" loaded = ? ")
q.args = append(q.args, loaded)
return q
}
func (q *transfersQuery) FilterNetwork(network uint64) *transfersQuery {
q.addWhereSeparator(AndSeparator)
q.buf.WriteString(" network_id = ?")
q.args = append(q.args, network)
return q
}
func (q *transfersQuery) FilterAddress(address common.Address) *transfersQuery {
q.addWhereSeparator(AndSeparator)
q.buf.WriteString(" address = ?")
q.args = append(q.args, address)
return q
}
func (q *transfersQuery) FilterTransactionHash(hash common.Hash) *transfersQuery {
q.addWhereSeparator(AndSeparator)
q.buf.WriteString(" hash = ?")
q.args = append(q.args, hash)
return q
}
func (q *transfersQuery) FilterBlockHash(blockHash common.Hash) *transfersQuery {
q.addWhereSeparator(AndSeparator)
q.buf.WriteString(" blk_hash = ?")
q.args = append(q.args, blockHash)
return q
}
func (q *transfersQuery) FilterBlockNumber(blockNumber *big.Int) *transfersQuery {
q.addWhereSeparator(AndSeparator)
q.buf.WriteString(" blk_number = ?")
q.args = append(q.args, (*bigint.SQLBigInt)(blockNumber))
return q
}
func (q *transfersQuery) Limit(pageSize int64) *transfersQuery {
q.buf.WriteString(" ORDER BY blk_number DESC, hash ASC ")
q.buf.WriteString(" LIMIT ?")
q.args = append(q.args, pageSize)
return q
}
func (q *transfersQuery) String() string {
return q.buf.String()
}
func (q *transfersQuery) Args() []interface{} {
return q.args
}
func (q *transfersQuery) Scan(rows *sql.Rows) (rst []Transfer, err error) {
for rows.Next() {
transfer := Transfer{
BlockNumber: &big.Int{},
Transaction: &types.Transaction{},
Receipt: &types.Receipt{},
Log: &types.Log{},
}
err = rows.Scan(
&transfer.ID, &transfer.Type, &transfer.BlockHash,
(*bigint.SQLBigInt)(transfer.BlockNumber), &transfer.Timestamp, &transfer.Address,
&JSONBlob{transfer.Transaction}, &transfer.From, &JSONBlob{transfer.Receipt}, &JSONBlob{transfer.Log}, &transfer.NetworkID, &transfer.BaseGasFees, &transfer.MultiTransactionID)
if err != nil {
return nil, err
}
rst = append(rst, transfer)
}
return rst, nil
}