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  }