mirror of
https://github.com/status-im/status-go.git
synced 2025-01-10 14:47:06 +00:00
b6ade53603
Refactored transfers loading to reduce blockchain RPC requests (getBaseFee, getTransaction, getTransactionReceipt) by reusing preloaded transaction and block fee. Split extraction of subtransaction from logs and from ETH transfer into different methods. Refactored log_parser to extract sender and receiver addresses uniformly for different transfer types. Replaced info logs with debug where needed. closes #4221
235 lines
6.0 KiB
Go
235 lines
6.0 KiB
Go
package transfer
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql"
|
|
"fmt"
|
|
"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) %s FROM transfers"
|
|
const preloadedTransfersQuery = "SELECT hash, type, address, log, token_id, amount_padded128hex FROM transfers"
|
|
|
|
type transfersQuery struct {
|
|
buf *bytes.Buffer
|
|
args []interface{}
|
|
whereAdded bool
|
|
subQuery bool
|
|
}
|
|
|
|
func newTransfersQuery() *transfersQuery {
|
|
newQuery := newEmptyQuery()
|
|
transfersQueryString := fmt.Sprintf(baseTransfersQuery, "")
|
|
newQuery.buf.WriteString(transfersQueryString)
|
|
return newQuery
|
|
}
|
|
|
|
func newTransfersQueryForPreloadedTransactions() *transfersQuery {
|
|
newQuery := newEmptyQuery()
|
|
newQuery.buf.WriteString(preloadedTransfersQuery)
|
|
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
|
|
|
|
// Beware: please update addWhereSeparator if changing this enum
|
|
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) FilterTransactionID(hash common.Hash) *transfersQuery {
|
|
q.addWhereSeparator(AndSeparator)
|
|
q.buf.WriteString(" hash = ?")
|
|
q.args = append(q.args, hash)
|
|
return q
|
|
}
|
|
|
|
func (q *transfersQuery) FilterTransactionHash(hash common.Hash) *transfersQuery {
|
|
q.addWhereSeparator(AndSeparator)
|
|
q.buf.WriteString(" tx_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) TransferScan(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
|
|
}
|
|
|
|
func (q *transfersQuery) PreloadedTransactionScan(rows *sql.Rows) (rst []*PreloadedTransaction, err error) {
|
|
transfers := make([]Transfer, 0)
|
|
for rows.Next() {
|
|
transfer := Transfer{
|
|
Log: &types.Log{},
|
|
}
|
|
tokenValue := sql.NullString{}
|
|
tokenID := sql.RawBytes{}
|
|
err = rows.Scan(
|
|
&transfer.ID, &transfer.Type,
|
|
&transfer.Address,
|
|
&JSONBlob{transfer.Log},
|
|
&tokenID, &tokenValue)
|
|
|
|
if len(tokenID) > 0 {
|
|
transfer.TokenID = new(big.Int).SetBytes(tokenID)
|
|
}
|
|
|
|
if tokenValue.Valid {
|
|
var ok bool
|
|
transfer.TokenValue, ok = new(big.Int).SetString(tokenValue.String, 16)
|
|
if !ok {
|
|
panic("failed to parse token value")
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
transfers = append(transfers, transfer)
|
|
}
|
|
|
|
rst = make([]*PreloadedTransaction, 0, len(transfers))
|
|
|
|
for _, transfer := range transfers {
|
|
preloadedTransaction := &PreloadedTransaction{
|
|
ID: transfer.ID,
|
|
Type: transfer.Type,
|
|
Address: transfer.Address,
|
|
Log: transfer.Log,
|
|
TokenID: transfer.TokenID,
|
|
Value: transfer.TokenValue,
|
|
}
|
|
|
|
rst = append(rst, preloadedTransaction)
|
|
}
|
|
|
|
return rst, nil
|
|
}
|