github.com/status-im/status-go@v1.1.0/services/wallet/transfer/block_ranges_sequential_dao.go (about) 1 package transfer 2 3 import ( 4 "database/sql" 5 "math/big" 6 7 "github.com/ethereum/go-ethereum/common" 8 "github.com/ethereum/go-ethereum/log" 9 "github.com/status-im/status-go/services/wallet/bigint" 10 ) 11 12 type BlockRangeDAOer interface { 13 getBlockRange(chainID uint64, address common.Address) (blockRange *ethTokensBlockRanges, exists bool, err error) 14 getBlockRanges(chainID uint64, addresses []common.Address) (blockRanges map[common.Address]*ethTokensBlockRanges, err error) 15 upsertRange(chainID uint64, account common.Address, newBlockRange *ethTokensBlockRanges) (err error) 16 updateTokenRange(chainID uint64, account common.Address, newBlockRange *BlockRange) (err error) 17 upsertEthRange(chainID uint64, account common.Address, newBlockRange *BlockRange) (err error) 18 } 19 20 type BlockRangeSequentialDAO struct { 21 db *sql.DB 22 } 23 24 type BlockRange struct { 25 Start *big.Int // Block of first transfer 26 FirstKnown *big.Int // Oldest scanned block 27 LastKnown *big.Int // Last scanned block 28 } 29 30 func NewBlockRange() *BlockRange { 31 return &BlockRange{Start: nil, FirstKnown: nil, LastKnown: nil} 32 } 33 34 type ethTokensBlockRanges struct { 35 eth *BlockRange 36 tokens *BlockRange 37 balanceCheckHash string 38 } 39 40 func newEthTokensBlockRanges() *ethTokensBlockRanges { 41 return ðTokensBlockRanges{eth: NewBlockRange(), tokens: NewBlockRange()} 42 } 43 44 func scanRanges(rows *sql.Rows) (map[common.Address]*ethTokensBlockRanges, error) { 45 blockRanges := make(map[common.Address]*ethTokensBlockRanges) 46 for rows.Next() { 47 efk := &bigint.NilableSQLBigInt{} 48 elk := &bigint.NilableSQLBigInt{} 49 es := &bigint.NilableSQLBigInt{} 50 tfk := &bigint.NilableSQLBigInt{} 51 tlk := &bigint.NilableSQLBigInt{} 52 ts := &bigint.NilableSQLBigInt{} 53 addressB := []byte{} 54 blockRange := newEthTokensBlockRanges() 55 err := rows.Scan(&addressB, es, efk, elk, ts, tfk, tlk, &blockRange.balanceCheckHash) 56 if err != nil { 57 return nil, err 58 } 59 address := common.BytesToAddress(addressB) 60 blockRanges[address] = blockRange 61 62 if !es.IsNil() { 63 blockRanges[address].eth.Start = big.NewInt(es.Int64()) 64 } 65 if !efk.IsNil() { 66 blockRanges[address].eth.FirstKnown = big.NewInt(efk.Int64()) 67 } 68 if !elk.IsNil() { 69 blockRanges[address].eth.LastKnown = big.NewInt(elk.Int64()) 70 } 71 if !ts.IsNil() { 72 blockRanges[address].tokens.Start = big.NewInt(ts.Int64()) 73 } 74 if !tfk.IsNil() { 75 blockRanges[address].tokens.FirstKnown = big.NewInt(tfk.Int64()) 76 } 77 if !tlk.IsNil() { 78 blockRanges[address].tokens.LastKnown = big.NewInt(tlk.Int64()) 79 } 80 } 81 return blockRanges, nil 82 } 83 84 func (b *BlockRangeSequentialDAO) getBlockRange(chainID uint64, address common.Address) (blockRange *ethTokensBlockRanges, exists bool, err error) { 85 query := `SELECT address, blk_start, blk_first, blk_last, token_blk_start, token_blk_first, token_blk_last, balance_check_hash FROM blocks_ranges_sequential 86 WHERE address = ? 87 AND network_id = ?` 88 89 rows, err := b.db.Query(query, address, chainID) 90 if err != nil { 91 return 92 } 93 defer rows.Close() 94 95 ranges, err := scanRanges(rows) 96 if err != nil { 97 return nil, false, err 98 } 99 100 blockRange, exists = ranges[address] 101 if !exists { 102 blockRange = newEthTokensBlockRanges() 103 } 104 105 return blockRange, exists, nil 106 } 107 108 func (b *BlockRangeSequentialDAO) getBlockRanges(chainID uint64, addresses []common.Address) (blockRanges map[common.Address]*ethTokensBlockRanges, err error) { 109 blockRanges = make(map[common.Address]*ethTokensBlockRanges) 110 addressesPlaceholder := "" 111 for i := 0; i < len(addresses); i++ { 112 addressesPlaceholder += "?" 113 if i < len(addresses)-1 { 114 addressesPlaceholder += "," 115 } 116 } 117 118 query := "SELECT address, blk_start, blk_first, blk_last, token_blk_start, token_blk_first, token_blk_last, balance_check_hash FROM blocks_ranges_sequential WHERE address IN (" + //nolint: gosec 119 addressesPlaceholder + ") AND network_id = ?" 120 121 params := []interface{}{} 122 for _, address := range addresses { 123 params = append(params, address) 124 } 125 params = append(params, chainID) 126 127 rows, err := b.db.Query(query, params...) 128 if err != nil { 129 return 130 } 131 defer rows.Close() 132 133 return scanRanges(rows) 134 } 135 136 func (b *BlockRangeSequentialDAO) deleteRange(account common.Address) error { 137 log.Debug("delete blocks range", "account", account) 138 delete, err := b.db.Prepare(`DELETE FROM blocks_ranges_sequential WHERE address = ?`) 139 if err != nil { 140 log.Error("Failed to prepare deletion of sequential block range", "error", err) 141 return err 142 } 143 144 _, err = delete.Exec(account) 145 return err 146 } 147 148 func (b *BlockRangeSequentialDAO) upsertRange(chainID uint64, account common.Address, newBlockRange *ethTokensBlockRanges) (err error) { 149 ethTokensBlockRange, exists, err := b.getBlockRange(chainID, account) 150 if err != nil { 151 return err 152 } 153 154 ethBlockRange := prepareUpdatedBlockRange(ethTokensBlockRange.eth, newBlockRange.eth) 155 tokensBlockRange := prepareUpdatedBlockRange(ethTokensBlockRange.tokens, newBlockRange.tokens) 156 157 log.Debug("upsert eth and tokens blocks range", 158 "account", account, "chainID", chainID, 159 "eth.start", ethBlockRange.Start, 160 "eth.first", ethBlockRange.FirstKnown, 161 "eth.last", ethBlockRange.LastKnown, 162 "tokens.first", tokensBlockRange.FirstKnown, 163 "tokens.last", tokensBlockRange.LastKnown, 164 "hash", newBlockRange.balanceCheckHash) 165 166 var query *sql.Stmt 167 168 if exists { 169 query, err = b.db.Prepare(`UPDATE blocks_ranges_sequential SET 170 blk_start = ?, 171 blk_first = ?, 172 blk_last = ?, 173 token_blk_start = ?, 174 token_blk_first = ?, 175 token_blk_last = ?, 176 balance_check_hash = ? 177 WHERE network_id = ? AND address = ?`) 178 179 } else { 180 query, err = b.db.Prepare(`INSERT INTO blocks_ranges_sequential 181 (blk_start, blk_first, blk_last, token_blk_start, token_blk_first, token_blk_last, balance_check_hash, network_id, address) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`) 182 } 183 184 if err != nil { 185 return err 186 } 187 _, err = query.Exec((*bigint.SQLBigInt)(ethBlockRange.Start), (*bigint.SQLBigInt)(ethBlockRange.FirstKnown), (*bigint.SQLBigInt)(ethBlockRange.LastKnown), 188 (*bigint.SQLBigInt)(tokensBlockRange.Start), (*bigint.SQLBigInt)(tokensBlockRange.FirstKnown), (*bigint.SQLBigInt)(tokensBlockRange.LastKnown), newBlockRange.balanceCheckHash, chainID, account) 189 190 return err 191 } 192 193 func (b *BlockRangeSequentialDAO) upsertEthRange(chainID uint64, account common.Address, 194 newBlockRange *BlockRange) (err error) { 195 196 ethTokensBlockRange, exists, err := b.getBlockRange(chainID, account) 197 if err != nil { 198 return err 199 } 200 201 blockRange := prepareUpdatedBlockRange(ethTokensBlockRange.eth, newBlockRange) 202 203 log.Debug("upsert eth blocks range", "account", account, "chainID", chainID, 204 "start", blockRange.Start, 205 "first", blockRange.FirstKnown, 206 "last", blockRange.LastKnown, 207 "old hash", ethTokensBlockRange.balanceCheckHash) 208 209 var query *sql.Stmt 210 211 if exists { 212 query, err = b.db.Prepare(`UPDATE blocks_ranges_sequential SET 213 blk_start = ?, 214 blk_first = ?, 215 blk_last = ? 216 WHERE network_id = ? AND address = ?`) 217 } else { 218 query, err = b.db.Prepare(`INSERT INTO blocks_ranges_sequential 219 (blk_start, blk_first, blk_last, network_id, address) VALUES (?, ?, ?, ?, ?)`) 220 } 221 222 if err != nil { 223 return err 224 } 225 _, err = query.Exec((*bigint.SQLBigInt)(blockRange.Start), (*bigint.SQLBigInt)(blockRange.FirstKnown), (*bigint.SQLBigInt)(blockRange.LastKnown), chainID, account) 226 227 return err 228 } 229 230 func (b *BlockRangeSequentialDAO) updateTokenRange(chainID uint64, account common.Address, 231 newBlockRange *BlockRange) (err error) { 232 233 ethTokensBlockRange, _, err := b.getBlockRange(chainID, account) 234 if err != nil { 235 return err 236 } 237 238 blockRange := prepareUpdatedBlockRange(ethTokensBlockRange.tokens, newBlockRange) 239 240 log.Debug("update tokens blocks range", 241 "first", blockRange.FirstKnown, 242 "last", blockRange.LastKnown) 243 244 update, err := b.db.Prepare(`UPDATE blocks_ranges_sequential SET token_blk_start = ?, token_blk_first = ?, token_blk_last = ? WHERE network_id = ? AND address = ?`) 245 if err != nil { 246 return err 247 } 248 249 _, err = update.Exec((*bigint.SQLBigInt)(blockRange.Start), (*bigint.SQLBigInt)(blockRange.FirstKnown), 250 (*bigint.SQLBigInt)(blockRange.LastKnown), chainID, account) 251 252 return err 253 } 254 255 func prepareUpdatedBlockRange(blockRange, newBlockRange *BlockRange) *BlockRange { 256 if newBlockRange != nil { 257 // Ovewrite start block if there was not any or if new one is older, because it can be precised only 258 // to a greater value, because no history can be before some block that is considered 259 // as a start of history, but due to concurrent block range checks, a newer greater block 260 // can be found that matches criteria of a start block (nonce is zero, balances are equal) 261 if newBlockRange.Start != nil && (blockRange.Start == nil || blockRange.Start.Cmp(newBlockRange.Start) < 0) { 262 blockRange.Start = newBlockRange.Start 263 } 264 265 // Overwrite first known block if there was not any or if new one is older 266 if (blockRange.FirstKnown == nil && newBlockRange.FirstKnown != nil) || 267 (blockRange.FirstKnown != nil && newBlockRange.FirstKnown != nil && blockRange.FirstKnown.Cmp(newBlockRange.FirstKnown) > 0) { 268 blockRange.FirstKnown = newBlockRange.FirstKnown 269 } 270 271 // Overwrite last known block if there was not any or if new one is newer 272 if (blockRange.LastKnown == nil && newBlockRange.LastKnown != nil) || 273 (blockRange.LastKnown != nil && newBlockRange.LastKnown != nil && blockRange.LastKnown.Cmp(newBlockRange.LastKnown) < 0) { 274 blockRange.LastKnown = newBlockRange.LastKnown 275 } 276 } 277 278 return blockRange 279 }