github.com/status-im/status-go@v1.1.0/services/wallet/transfer/block_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 BlocksRange struct { 13 from *big.Int 14 to *big.Int 15 } 16 17 type Block struct { 18 Number *big.Int 19 Balance *big.Int 20 Nonce *int64 21 } 22 23 type BlockView struct { 24 Address common.Address `json:"address"` 25 Number *big.Int `json:"blockNumber"` 26 Balance bigint.BigInt `json:"balance"` 27 Nonce *int64 `json:"nonce"` 28 } 29 30 func blocksToViews(blocks map[common.Address]*Block) []BlockView { 31 blocksViews := []BlockView{} 32 for address, block := range blocks { 33 view := BlockView{ 34 Address: address, 35 Number: block.Number, 36 Balance: bigint.BigInt{Int: block.Balance}, 37 Nonce: block.Nonce, 38 } 39 blocksViews = append(blocksViews, view) 40 } 41 42 return blocksViews 43 } 44 45 type BlockDAO struct { 46 db *sql.DB 47 } 48 49 // MergeBlocksRanges merge old blocks ranges if possible 50 func (b *BlockDAO) mergeBlocksRanges(chainIDs []uint64, accounts []common.Address) error { 51 for _, chainID := range chainIDs { 52 for _, account := range accounts { 53 err := b.mergeRanges(chainID, account) 54 if err != nil { 55 return err 56 } 57 } 58 } 59 return nil 60 } 61 62 func (b *BlockDAO) mergeRanges(chainID uint64, account common.Address) (err error) { 63 var ( 64 tx *sql.Tx 65 ) 66 67 ranges, err := b.getOldRanges(chainID, account) 68 if err != nil { 69 return err 70 } 71 72 log.Info("merge old ranges", "account", account, "network", chainID, "ranges", len(ranges)) 73 74 if len(ranges) <= 1 { 75 return nil 76 } 77 78 tx, err = b.db.Begin() 79 if err != nil { 80 return err 81 } 82 83 defer func() { 84 if err == nil { 85 err = tx.Commit() 86 return 87 } 88 _ = tx.Rollback() 89 }() 90 91 newRanges, deletedRanges := getNewRanges(ranges) 92 93 for _, rangeToDelete := range deletedRanges { 94 err = deleteRange(chainID, tx, account, rangeToDelete.from, rangeToDelete.to) 95 if err != nil { 96 return err 97 } 98 } 99 100 for _, newRange := range newRanges { 101 err = insertRange(chainID, tx, account, newRange.from, newRange.to) 102 if err != nil { 103 return err 104 } 105 } 106 107 return nil 108 } 109 110 func (b *BlockDAO) insertRange(chainID uint64, account common.Address, from, to, balance *big.Int, nonce uint64) error { 111 log.Debug("insert blocks range", "account", account, "network id", chainID, "from", from, "to", to, "balance", balance, "nonce", nonce) 112 insert, err := b.db.Prepare("INSERT INTO blocks_ranges (network_id, address, blk_from, blk_to, balance, nonce) VALUES (?, ?, ?, ?, ?, ?)") 113 if err != nil { 114 return err 115 } 116 _, err = insert.Exec(chainID, account, (*bigint.SQLBigInt)(from), (*bigint.SQLBigInt)(to), (*bigint.SQLBigIntBytes)(balance), &nonce) 117 return err 118 } 119 120 func (b *BlockDAO) getOldRanges(chainID uint64, account common.Address) ([]*BlocksRange, error) { 121 query := `select blk_from, blk_to from blocks_ranges 122 where address = ? 123 and network_id = ? 124 order by blk_from` 125 126 rows, err := b.db.Query(query, account, chainID) 127 if err != nil { 128 return nil, err 129 } 130 defer rows.Close() 131 ranges := []*BlocksRange{} 132 for rows.Next() { 133 from := &big.Int{} 134 to := &big.Int{} 135 err = rows.Scan((*bigint.SQLBigInt)(from), (*bigint.SQLBigInt)(to)) 136 if err != nil { 137 return nil, err 138 } 139 140 ranges = append(ranges, &BlocksRange{ 141 from: from, 142 to: to, 143 }) 144 } 145 146 return ranges, nil 147 } 148 149 // GetBlocksToLoadByAddress gets unloaded blocks for a given address. 150 func (b *BlockDAO) GetBlocksToLoadByAddress(chainID uint64, address common.Address, limit int) (rst []*big.Int, err error) { 151 query := `SELECT blk_number FROM blocks 152 WHERE address = ? AND network_id = ? AND loaded = 0 153 ORDER BY blk_number DESC 154 LIMIT ?` 155 rows, err := b.db.Query(query, address, chainID, limit) 156 if err != nil { 157 return 158 } 159 defer rows.Close() 160 for rows.Next() { 161 block := &big.Int{} 162 err = rows.Scan((*bigint.SQLBigInt)(block)) 163 if err != nil { 164 return nil, err 165 } 166 rst = append(rst, block) 167 } 168 return rst, nil 169 } 170 171 func (b *BlockDAO) GetLastBlockByAddress(chainID uint64, address common.Address, limit int) (rst *big.Int, err error) { 172 query := `SELECT * FROM 173 (SELECT blk_number FROM blocks WHERE address = ? AND network_id = ? ORDER BY blk_number DESC LIMIT ?) 174 ORDER BY blk_number LIMIT 1` 175 rows, err := b.db.Query(query, address, chainID, limit) 176 if err != nil { 177 return 178 } 179 defer rows.Close() 180 181 if rows.Next() { 182 block := &big.Int{} 183 err = rows.Scan((*bigint.SQLBigInt)(block)) 184 if err != nil { 185 return nil, err 186 } 187 188 return block, nil 189 } 190 191 return nil, nil 192 } 193 194 func (b *BlockDAO) GetFirstSavedBlock(chainID uint64, address common.Address) (rst *DBHeader, err error) { 195 query := `SELECT blk_number, blk_hash, loaded 196 FROM blocks 197 WHERE network_id = ? AND address = ? 198 ORDER BY blk_number LIMIT 1` 199 rows, err := b.db.Query(query, chainID, address) 200 if err != nil { 201 return 202 } 203 defer rows.Close() 204 205 if rows.Next() { 206 header := &DBHeader{Hash: common.Hash{}, Number: new(big.Int)} 207 err = rows.Scan((*bigint.SQLBigInt)(header.Number), &header.Hash, &header.Loaded) 208 if err != nil { 209 return nil, err 210 } 211 212 return header, nil 213 } 214 215 return nil, nil 216 } 217 218 func (b *BlockDAO) GetFirstKnownBlock(chainID uint64, address common.Address) (rst *big.Int, err error) { 219 query := `SELECT blk_from FROM blocks_ranges 220 WHERE address = ? 221 AND network_id = ? 222 ORDER BY blk_from 223 LIMIT 1` 224 225 rows, err := b.db.Query(query, address, chainID) 226 if err != nil { 227 return 228 } 229 defer rows.Close() 230 231 if rows.Next() { 232 block := &big.Int{} 233 err = rows.Scan((*bigint.SQLBigInt)(block)) 234 if err != nil { 235 return nil, err 236 } 237 238 return block, nil 239 } 240 241 return nil, nil 242 } 243 244 func (b *BlockDAO) GetLastKnownBlockByAddress(chainID uint64, address common.Address) (block *Block, err error) { 245 query := `SELECT blk_to, balance, nonce FROM blocks_ranges 246 WHERE address = ? 247 AND network_id = ? 248 ORDER BY blk_to DESC 249 LIMIT 1` 250 251 rows, err := b.db.Query(query, address, chainID) 252 if err != nil { 253 return 254 } 255 defer rows.Close() 256 257 if rows.Next() { 258 var nonce sql.NullInt64 259 block = &Block{Number: &big.Int{}, Balance: &big.Int{}} 260 err = rows.Scan((*bigint.SQLBigInt)(block.Number), (*bigint.SQLBigIntBytes)(block.Balance), &nonce) 261 if err != nil { 262 return nil, err 263 } 264 265 if nonce.Valid { 266 block.Nonce = &nonce.Int64 267 } 268 return block, nil 269 } 270 271 return nil, nil 272 } 273 274 func (b *BlockDAO) getLastKnownBlocks(chainID uint64, addresses []common.Address) (map[common.Address]*Block, error) { 275 result := map[common.Address]*Block{} 276 for _, address := range addresses { 277 block, error := b.GetLastKnownBlockByAddress(chainID, address) 278 if error != nil { 279 return nil, error 280 } 281 282 if block != nil { 283 result[address] = block 284 } 285 } 286 287 return result, nil 288 } 289 290 // TODO Remove the method below, it is used in one place and duplicates getLastKnownBlocks method with slight unneeded change 291 func (b *BlockDAO) GetLastKnownBlockByAddresses(chainID uint64, addresses []common.Address) (map[common.Address]*Block, []common.Address, error) { 292 res := map[common.Address]*Block{} 293 accountsWithoutHistory := []common.Address{} 294 for _, address := range addresses { 295 block, err := b.GetLastKnownBlockByAddress(chainID, address) 296 if err != nil { 297 log.Info("Can't get last block", "error", err) 298 return nil, nil, err 299 } 300 301 if block != nil { 302 res[address] = block 303 } else { 304 accountsWithoutHistory = append(accountsWithoutHistory, address) 305 } 306 } 307 308 return res, accountsWithoutHistory, nil 309 } 310 311 func getNewRanges(ranges []*BlocksRange) ([]*BlocksRange, []*BlocksRange) { 312 initValue := big.NewInt(-1) 313 prevFrom := big.NewInt(-1) 314 prevTo := big.NewInt(-1) 315 hasMergedRanges := false 316 var newRanges []*BlocksRange 317 var deletedRanges []*BlocksRange 318 for idx, blocksRange := range ranges { 319 if prevTo.Cmp(initValue) == 0 { 320 prevTo = blocksRange.to 321 prevFrom = blocksRange.from 322 } else if prevTo.Cmp(blocksRange.from) >= 0 { 323 hasMergedRanges = true 324 deletedRanges = append(deletedRanges, ranges[idx-1]) 325 if prevTo.Cmp(blocksRange.to) <= 0 { 326 prevTo = blocksRange.to 327 } 328 } else { 329 if hasMergedRanges { 330 deletedRanges = append(deletedRanges, ranges[idx-1]) 331 newRanges = append(newRanges, &BlocksRange{ 332 from: prevFrom, 333 to: prevTo, 334 }) 335 } 336 log.Info("blocks ranges gap detected", "from", prevTo, "to", blocksRange.from) 337 hasMergedRanges = false 338 339 prevFrom = blocksRange.from 340 prevTo = blocksRange.to 341 } 342 } 343 344 if hasMergedRanges { 345 deletedRanges = append(deletedRanges, ranges[len(ranges)-1]) 346 newRanges = append(newRanges, &BlocksRange{ 347 from: prevFrom, 348 to: prevTo, 349 }) 350 } 351 352 return newRanges, deletedRanges 353 } 354 355 func deleteRange(chainID uint64, creator statementCreator, account common.Address, from *big.Int, to *big.Int) error { 356 log.Info("delete blocks range", "account", account, "network", chainID, "from", from, "to", to) 357 delete, err := creator.Prepare(`DELETE FROM blocks_ranges 358 WHERE address = ? 359 AND network_id = ? 360 AND blk_from = ? 361 AND blk_to = ?`) 362 if err != nil { 363 log.Info("some error", "error", err) 364 return err 365 } 366 367 _, err = delete.Exec(account, chainID, (*bigint.SQLBigInt)(from), (*bigint.SQLBigInt)(to)) 368 return err 369 } 370 371 func deleteAllRanges(creator statementCreator, account common.Address) error { 372 delete, err := creator.Prepare(`DELETE FROM blocks_ranges WHERE address = ?`) 373 if err != nil { 374 return err 375 } 376 377 _, err = delete.Exec(account) 378 return err 379 } 380 381 func insertRange(chainID uint64, creator statementCreator, account common.Address, from *big.Int, to *big.Int) error { 382 log.Info("insert blocks range", "account", account, "network", chainID, "from", from, "to", to) 383 insert, err := creator.Prepare("INSERT INTO blocks_ranges (network_id, address, blk_from, blk_to) VALUES (?, ?, ?, ?)") 384 if err != nil { 385 return err 386 } 387 388 _, err = insert.Exec(chainID, account, (*bigint.SQLBigInt)(from), (*bigint.SQLBigInt)(to)) 389 return err 390 }