github.com/status-im/status-go@v1.1.0/services/wallet/history/balance_db.go (about) 1 package history 2 3 import ( 4 "database/sql" 5 "encoding/hex" 6 "fmt" 7 "math/big" 8 9 "github.com/ethereum/go-ethereum/common" 10 "github.com/ethereum/go-ethereum/log" 11 "github.com/status-im/status-go/services/wallet/bigint" 12 ) 13 14 type BalanceDB struct { 15 db *sql.DB 16 } 17 18 func NewBalanceDB(sqlDb *sql.DB) *BalanceDB { 19 return &BalanceDB{ 20 db: sqlDb, 21 } 22 } 23 24 // entry represents a single row in the balance_history table 25 type entry struct { 26 chainID uint64 27 address common.Address 28 tokenSymbol string 29 tokenAddress common.Address 30 block *big.Int 31 timestamp int64 32 balance *big.Int 33 } 34 35 type assetIdentity struct { 36 ChainID uint64 37 Addresses []common.Address 38 TokenSymbol string 39 } 40 41 func (a *assetIdentity) addressesToString() string { 42 var addressesStr string 43 for i, address := range a.Addresses { 44 addressStr := hex.EncodeToString(address[:]) 45 if i == 0 { 46 addressesStr = "X'" + addressStr + "'" 47 } else { 48 addressesStr += ", X'" + addressStr + "'" 49 } 50 } 51 return addressesStr 52 } 53 54 func (e *entry) String() string { 55 return fmt.Sprintf("chainID: %v, address: %v, tokenSymbol: %v, tokenAddress: %v, block: %v, timestamp: %v, balance: %v", 56 e.chainID, e.address, e.tokenSymbol, e.tokenAddress, e.block, e.timestamp, e.balance) 57 } 58 59 func (b *BalanceDB) add(entry *entry) error { 60 log.Debug("Adding entry to balance_history", "entry", entry) 61 62 _, 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)) 63 return err 64 } 65 66 func (b *BalanceDB) getEntriesWithoutBalances(chainID uint64, address common.Address) (entries []*entry, err error) { 67 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", 68 chainID, address) 69 if err == sql.ErrNoRows { 70 return nil, nil 71 } 72 73 if err != nil { 74 return nil, err 75 } 76 defer rows.Close() 77 78 entries = make([]*entry, 0) 79 for rows.Next() { 80 entry := &entry{ 81 chainID: chainID, 82 address: address, 83 block: new(big.Int), 84 } 85 86 // tokenAddress can be NULL and can not unmarshal to common.Address 87 tokenHexAddress := make([]byte, common.AddressLength) 88 err := rows.Scan((*bigint.SQLBigInt)(entry.block), &entry.timestamp, &tokenHexAddress) 89 if err != nil { 90 return nil, err 91 } 92 93 tokenAddress := common.BytesToAddress(tokenHexAddress) 94 if tokenAddress != (common.Address{}) { 95 entry.tokenAddress = tokenAddress 96 } 97 entries = append(entries, entry) 98 } 99 return entries, nil 100 } 101 102 func (b *BalanceDB) getNewerThan(identity *assetIdentity, timestamp uint64) (entries []*entry, err error) { 103 // DISTINCT removes duplicates that can happen when a block has multiple transfers of same token 104 rawQueryStr := "SELECT DISTINCT block, timestamp, balance, address FROM balance_history WHERE chain_id = ? AND address IN (%s) AND currency = ? AND timestamp > ? ORDER BY timestamp" 105 queryString := fmt.Sprintf(rawQueryStr, identity.addressesToString()) 106 rows, err := b.db.Query(queryString, identity.ChainID, identity.TokenSymbol, timestamp) 107 if err == sql.ErrNoRows { 108 return nil, nil 109 } else if err != nil { 110 return nil, err 111 } 112 113 defer rows.Close() 114 115 result := make([]*entry, 0) 116 for rows.Next() { 117 entry := &entry{ 118 chainID: identity.ChainID, 119 tokenSymbol: identity.TokenSymbol, 120 block: new(big.Int), 121 balance: new(big.Int), 122 } 123 err := rows.Scan((*bigint.SQLBigInt)(entry.block), &entry.timestamp, (*bigint.SQLBigIntBytes)(entry.balance), &entry.address) 124 if err != nil { 125 return nil, err 126 } 127 result = append(result, entry) 128 } 129 return result, nil 130 } 131 132 func (b *BalanceDB) getEntryPreviousTo(item *entry) (res *entry, err error) { 133 res = &entry{ 134 chainID: item.chainID, 135 address: item.address, 136 block: new(big.Int), 137 balance: new(big.Int), 138 tokenSymbol: item.tokenSymbol, 139 } 140 141 queryStr := "SELECT block, timestamp, balance FROM balance_history WHERE chain_id = ? AND address = ? AND currency = ? AND timestamp < ? ORDER BY timestamp DESC LIMIT 1" 142 row := b.db.QueryRow(queryStr, item.chainID, item.address, item.tokenSymbol, item.timestamp) 143 144 err = row.Scan((*bigint.SQLBigInt)(res.block), &res.timestamp, (*bigint.SQLBigIntBytes)(res.balance)) 145 if err == sql.ErrNoRows { 146 return nil, nil 147 } else if err != nil { 148 return nil, err 149 } 150 151 return res, nil 152 } 153 154 func (b *BalanceDB) removeBalanceHistory(address common.Address) error { 155 _, err := b.db.Exec("DELETE FROM balance_history WHERE address = ?", address) 156 return err 157 }