2022-11-15 12:14:41 +00:00
package history
import (
"database/sql"
2023-11-29 11:20:18 +00:00
"encoding/hex"
2022-11-15 12:14:41 +00:00
"fmt"
"math/big"
2024-10-28 20:54:17 +00:00
"go.uber.org/zap"
2022-11-15 12:14:41 +00:00
"github.com/ethereum/go-ethereum/common"
2024-10-28 20:54:17 +00:00
"github.com/status-im/status-go/logutils"
2022-11-15 12:14:41 +00:00
"github.com/status-im/status-go/services/wallet/bigint"
)
type BalanceDB struct {
db * sql . DB
}
func NewBalanceDB ( sqlDb * sql . DB ) * BalanceDB {
return & BalanceDB {
db : sqlDb ,
}
}
// entry represents a single row in the balance_history table
type entry struct {
2023-10-04 12:00:12 +00:00
chainID uint64
address common . Address
tokenSymbol string
tokenAddress common . Address
block * big . Int
timestamp int64
balance * big . Int
2022-11-15 12:14:41 +00:00
}
2023-10-04 12:00:12 +00:00
type assetIdentity struct {
ChainID uint64
2023-11-29 11:20:18 +00:00
Addresses [ ] common . Address
2023-10-04 12:00:12 +00:00
TokenSymbol string
}
2022-11-15 12:14:41 +00:00
2023-11-29 11:20:18 +00:00
func ( a * assetIdentity ) addressesToString ( ) string {
var addressesStr string
for i , address := range a . Addresses {
addressStr := hex . EncodeToString ( address [ : ] )
if i == 0 {
addressesStr = "X'" + addressStr + "'"
} else {
addressesStr += ", X'" + addressStr + "'"
}
}
return addressesStr
}
2023-10-04 12:00:12 +00:00
func ( e * entry ) String ( ) string {
return fmt . Sprintf ( "chainID: %v, address: %v, tokenSymbol: %v, tokenAddress: %v, block: %v, timestamp: %v, balance: %v" ,
e . chainID , e . address , e . tokenSymbol , e . tokenAddress , e . block , e . timestamp , e . balance )
2022-11-15 12:14:41 +00:00
}
2023-10-04 12:00:12 +00:00
func ( b * BalanceDB ) add ( entry * entry ) error {
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Debug ( "Adding entry to balance_history" , zap . Stringer ( "entry" , entry ) )
2023-10-04 12:00:12 +00:00
_ , err := b . db . Exec ( "INSERT OR IGNORE INTO balance_history (chain_id, address, currency, block, timestamp, balance) VALUES (?, ?, ?, ?, ?, ?)" , entry . chainID , entry . address , entry . tokenSymbol , ( * bigint . SQLBigInt ) ( entry . block ) , entry . timestamp , ( * bigint . SQLBigIntBytes ) ( entry . balance ) )
2022-11-15 12:14:41 +00:00
return err
}
2023-10-04 12:00:12 +00:00
func ( b * BalanceDB ) getEntriesWithoutBalances ( chainID uint64 , address common . Address ) ( entries [ ] * entry , err error ) {
2024-07-16 14:02:52 +00:00
rows , err := b . db . Query ( "SELECT blk_number, tr.timestamp, token_address from transfers tr LEFT JOIN balance_history bh ON bh.block = tr.blk_number WHERE tr.network_id = ? AND tr.address = ? AND tr.type != 'erc721' AND tr.type !='erc1155' AND bh.block IS NULL" ,
2023-10-04 12:00:12 +00:00
chainID , address )
if err == sql . ErrNoRows {
return nil , nil
}
2022-11-15 12:14:41 +00:00
2023-10-04 12:00:12 +00:00
if err != nil {
return nil , err
}
defer rows . Close ( )
2022-11-15 12:14:41 +00:00
2023-10-04 12:00:12 +00:00
entries = make ( [ ] * entry , 0 )
for rows . Next ( ) {
entry := & entry {
chainID : chainID ,
address : address ,
block : new ( big . Int ) ,
}
2022-11-15 12:14:41 +00:00
2023-10-04 12:00:12 +00:00
// tokenAddress can be NULL and can not unmarshal to common.Address
tokenHexAddress := make ( [ ] byte , common . AddressLength )
err := rows . Scan ( ( * bigint . SQLBigInt ) ( entry . block ) , & entry . timestamp , & tokenHexAddress )
if err != nil {
return nil , err
}
2022-11-15 12:14:41 +00:00
2023-10-04 12:00:12 +00:00
tokenAddress := common . BytesToAddress ( tokenHexAddress )
if tokenAddress != ( common . Address { } ) {
entry . tokenAddress = tokenAddress
}
entries = append ( entries , entry )
2022-11-15 12:14:41 +00:00
}
2023-10-04 12:00:12 +00:00
return entries , nil
}
func ( b * BalanceDB ) getNewerThan ( identity * assetIdentity , timestamp uint64 ) ( entries [ ] * entry , err error ) {
// DISTINCT removes duplicates that can happen when a block has multiple transfers of same token
2023-11-29 11:20:18 +00:00
rawQueryStr := "SELECT DISTINCT block, timestamp, balance, address FROM balance_history WHERE chain_id = ? AND address IN (%s) AND currency = ? AND timestamp > ? ORDER BY timestamp"
queryString := fmt . Sprintf ( rawQueryStr , identity . addressesToString ( ) )
rows , err := b . db . Query ( queryString , identity . ChainID , identity . TokenSymbol , timestamp )
2023-10-04 12:00:12 +00:00
if err == sql . ErrNoRows {
return nil , nil
} else if err != nil {
return nil , err
2022-11-15 12:14:41 +00:00
}
2023-10-04 12:00:12 +00:00
2022-11-15 12:14:41 +00:00
defer rows . Close ( )
result := make ( [ ] * entry , 0 )
for rows . Next ( ) {
entry := & entry {
2023-10-04 12:00:12 +00:00
chainID : identity . ChainID ,
2022-11-15 12:14:41 +00:00
tokenSymbol : identity . TokenSymbol ,
block : new ( big . Int ) ,
balance : new ( big . Int ) ,
}
2023-11-29 11:20:18 +00:00
err := rows . Scan ( ( * bigint . SQLBigInt ) ( entry . block ) , & entry . timestamp , ( * bigint . SQLBigIntBytes ) ( entry . balance ) , & entry . address )
2022-11-15 12:14:41 +00:00
if err != nil {
2023-10-04 12:00:12 +00:00
return nil , err
2022-11-15 12:14:41 +00:00
}
result = append ( result , entry )
}
2023-10-04 12:00:12 +00:00
return result , nil
2022-11-15 12:14:41 +00:00
}
2023-10-04 12:00:12 +00:00
func ( b * BalanceDB ) getEntryPreviousTo ( item * entry ) ( res * entry , err error ) {
2022-11-15 12:14:41 +00:00
res = & entry {
2023-10-04 12:00:12 +00:00
chainID : item . chainID ,
address : item . address ,
block : new ( big . Int ) ,
balance : new ( big . Int ) ,
tokenSymbol : item . tokenSymbol ,
2022-11-15 12:14:41 +00:00
}
2023-10-04 12:00:12 +00:00
queryStr := "SELECT block, timestamp, balance FROM balance_history WHERE chain_id = ? AND address = ? AND currency = ? AND timestamp < ? ORDER BY timestamp DESC LIMIT 1"
row := b . db . QueryRow ( queryStr , item . chainID , item . address , item . tokenSymbol , item . timestamp )
2022-11-15 12:14:41 +00:00
2023-10-04 12:00:12 +00:00
err = row . Scan ( ( * bigint . SQLBigInt ) ( res . block ) , & res . timestamp , ( * bigint . SQLBigIntBytes ) ( res . balance ) )
2022-11-15 12:14:41 +00:00
if err == sql . ErrNoRows {
2023-10-04 12:00:12 +00:00
return nil , nil
2022-11-15 12:14:41 +00:00
} else if err != nil {
2023-10-04 12:00:12 +00:00
return nil , err
2022-11-15 12:14:41 +00:00
}
2023-10-04 12:00:12 +00:00
return res , nil
2022-11-15 12:14:41 +00:00
}
2024-03-21 13:00:34 +00:00
func ( b * BalanceDB ) removeBalanceHistory ( address common . Address ) error {
_ , err := b . db . Exec ( "DELETE FROM balance_history WHERE address = ?" , address )
return err
}