mirror of
https://github.com/status-im/status-go.git
synced 2025-01-18 18:55:47 +00:00
81073b208e
mock it in tests. Made a configurable timeout interval for Commander interface. Added tests to verify loadBlocksAndTransfers command is stopped correctly on max errors limit reached
184 lines
6.9 KiB
Go
184 lines
6.9 KiB
Go
package transfer
|
|
|
|
import (
|
|
"database/sql"
|
|
"math/big"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/status-im/status-go/services/wallet/bigint"
|
|
)
|
|
|
|
type BlockRangeDAOer interface {
|
|
getBlockRange(chainID uint64, address common.Address) (blockRange *ethTokensBlockRanges, err error)
|
|
upsertRange(chainID uint64, account common.Address, newBlockRange *ethTokensBlockRanges) (err error)
|
|
updateTokenRange(chainID uint64, account common.Address, newBlockRange *BlockRange) (err error)
|
|
upsertEthRange(chainID uint64, account common.Address, newBlockRange *BlockRange) (err error)
|
|
}
|
|
|
|
type BlockRangeSequentialDAO struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
type BlockRange struct {
|
|
Start *big.Int // Block of first transfer
|
|
FirstKnown *big.Int // Oldest scanned block
|
|
LastKnown *big.Int // Last scanned block
|
|
}
|
|
|
|
func NewBlockRange() *BlockRange {
|
|
return &BlockRange{Start: &big.Int{}, FirstKnown: &big.Int{}, LastKnown: &big.Int{}}
|
|
}
|
|
|
|
type ethTokensBlockRanges struct {
|
|
eth *BlockRange
|
|
tokens *BlockRange
|
|
}
|
|
|
|
func newEthTokensBlockRanges() *ethTokensBlockRanges {
|
|
return ðTokensBlockRanges{eth: NewBlockRange(), tokens: NewBlockRange()}
|
|
}
|
|
|
|
func (b *BlockRangeSequentialDAO) getBlockRange(chainID uint64, address common.Address) (blockRange *ethTokensBlockRanges, err error) {
|
|
query := `SELECT blk_start, blk_first, blk_last, token_blk_start, token_blk_first, token_blk_last FROM blocks_ranges_sequential
|
|
WHERE address = ?
|
|
AND network_id = ?`
|
|
|
|
rows, err := b.db.Query(query, address, chainID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
blockRange = ðTokensBlockRanges{}
|
|
if rows.Next() {
|
|
blockRange = newEthTokensBlockRanges()
|
|
err = rows.Scan((*bigint.SQLBigInt)(blockRange.eth.Start), (*bigint.SQLBigInt)(blockRange.eth.FirstKnown), (*bigint.SQLBigInt)(blockRange.eth.LastKnown),
|
|
(*bigint.SQLBigInt)(blockRange.tokens.Start), (*bigint.SQLBigInt)(blockRange.tokens.FirstKnown), (*bigint.SQLBigInt)(blockRange.tokens.LastKnown))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return blockRange, nil
|
|
}
|
|
|
|
return blockRange, nil
|
|
}
|
|
|
|
func (b *BlockRangeSequentialDAO) deleteRange(account common.Address) error {
|
|
log.Debug("delete blocks range", "account", account)
|
|
delete, err := b.db.Prepare(`DELETE FROM blocks_ranges_sequential WHERE address = ?`)
|
|
if err != nil {
|
|
log.Error("Failed to prepare deletion of sequential block range", "error", err)
|
|
return err
|
|
}
|
|
|
|
_, err = delete.Exec(account)
|
|
return err
|
|
}
|
|
|
|
func (b *BlockRangeSequentialDAO) upsertRange(chainID uint64, account common.Address, newBlockRange *ethTokensBlockRanges) (err error) {
|
|
ethTokensBlockRange, err := b.getBlockRange(chainID, account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ethBlockRange := prepareUpdatedBlockRange(chainID, account, ethTokensBlockRange.eth, newBlockRange.eth)
|
|
tokensBlockRange := prepareUpdatedBlockRange(chainID, account, ethTokensBlockRange.tokens, newBlockRange.tokens)
|
|
|
|
log.Debug("update eth and tokens blocks range", "account", account, "chainID", chainID,
|
|
"eth.start", ethBlockRange.Start, "eth.first", ethBlockRange.FirstKnown, "eth.last", ethBlockRange.LastKnown,
|
|
"tokens.start", tokensBlockRange.Start, "tokens.first", ethBlockRange.FirstKnown, "eth.last", ethBlockRange.LastKnown)
|
|
|
|
upsert, err := b.db.Prepare(`REPLACE INTO blocks_ranges_sequential
|
|
(network_id, address, blk_start, blk_first, blk_last, token_blk_start, token_blk_first, token_blk_last) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = upsert.Exec(chainID, account, (*bigint.SQLBigInt)(ethBlockRange.Start), (*bigint.SQLBigInt)(ethBlockRange.FirstKnown), (*bigint.SQLBigInt)(ethBlockRange.LastKnown),
|
|
(*bigint.SQLBigInt)(tokensBlockRange.Start), (*bigint.SQLBigInt)(tokensBlockRange.FirstKnown), (*bigint.SQLBigInt)(tokensBlockRange.LastKnown))
|
|
|
|
return err
|
|
}
|
|
|
|
func (b *BlockRangeSequentialDAO) upsertEthRange(chainID uint64, account common.Address,
|
|
newBlockRange *BlockRange) (err error) {
|
|
|
|
ethTokensBlockRange, err := b.getBlockRange(chainID, account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
blockRange := prepareUpdatedBlockRange(chainID, account, ethTokensBlockRange.eth, newBlockRange)
|
|
|
|
log.Debug("update eth blocks range", "account", account, "chainID", chainID,
|
|
"start", blockRange.Start, "first", blockRange.FirstKnown, "last", blockRange.LastKnown)
|
|
|
|
upsert, err := b.db.Prepare(`REPLACE INTO blocks_ranges_sequential
|
|
(network_id, address, blk_start, blk_first, blk_last) VALUES (?, ?, ?, ?, ?)`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = upsert.Exec(chainID, account, (*bigint.SQLBigInt)(blockRange.Start), (*bigint.SQLBigInt)(blockRange.FirstKnown),
|
|
(*bigint.SQLBigInt)(blockRange.LastKnown))
|
|
|
|
return err
|
|
}
|
|
|
|
func (b *BlockRangeSequentialDAO) updateTokenRange(chainID uint64, account common.Address,
|
|
newBlockRange *BlockRange) (err error) {
|
|
|
|
ethTokensBlockRange, err := b.getBlockRange(chainID, account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
blockRange := prepareUpdatedBlockRange(chainID, account, ethTokensBlockRange.tokens, newBlockRange)
|
|
|
|
log.Debug("update tokens blocks range", "account", account, "chainID", chainID,
|
|
"start", blockRange.Start, "first", blockRange.FirstKnown, "last", blockRange.LastKnown)
|
|
|
|
update, err := b.db.Prepare(`UPDATE blocks_ranges_sequential SET token_blk_start = ?, token_blk_first = ?, token_blk_last = ? WHERE network_id = ? AND address = ?`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = update.Exec(chainID, account, (*bigint.SQLBigInt)(blockRange.Start), (*bigint.SQLBigInt)(blockRange.FirstKnown),
|
|
(*bigint.SQLBigInt)(blockRange.LastKnown))
|
|
|
|
return err
|
|
}
|
|
|
|
func prepareUpdatedBlockRange(chainID uint64, account common.Address, blockRange, newBlockRange *BlockRange) *BlockRange {
|
|
// Update existing range
|
|
if blockRange != nil {
|
|
if newBlockRange != nil {
|
|
// Ovewrite start block if there was not any or if new one is older, because it can be precised only
|
|
// to a greater value, because no history can be before some block that is considered
|
|
// as a start of history, but due to concurrent block range checks, a newer greater block
|
|
// can be found that matches criteria of a start block (nonce is zero, balances are equal)
|
|
if newBlockRange.Start != nil && (blockRange.Start == nil || blockRange.Start.Cmp(newBlockRange.Start) < 0) {
|
|
blockRange.Start = newBlockRange.Start
|
|
}
|
|
|
|
// Overwrite first known block if there was not any or if new one is older
|
|
if (blockRange.FirstKnown == nil && newBlockRange.FirstKnown != nil) ||
|
|
(blockRange.FirstKnown != nil && newBlockRange.FirstKnown != nil && blockRange.FirstKnown.Cmp(newBlockRange.FirstKnown) > 0) {
|
|
blockRange.FirstKnown = newBlockRange.FirstKnown
|
|
}
|
|
|
|
// Overwrite last known block if there was not any or if new one is newer
|
|
if (blockRange.LastKnown == nil && newBlockRange.LastKnown != nil) ||
|
|
(blockRange.LastKnown != nil && newBlockRange.LastKnown != nil && blockRange.LastKnown.Cmp(newBlockRange.LastKnown) < 0) {
|
|
blockRange.LastKnown = newBlockRange.LastKnown
|
|
}
|
|
}
|
|
} else {
|
|
blockRange = newBlockRange
|
|
}
|
|
|
|
return blockRange
|
|
}
|