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 &ethTokensBlockRanges{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  }