github.com/status-im/status-go@v1.1.0/services/wallet/transfer/database.go (about)

     1  package transfer
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"database/sql/driver"
     7  	"encoding/json"
     8  	"errors"
     9  	"math/big"
    10  	"reflect"
    11  
    12  	"github.com/ethereum/go-ethereum/common"
    13  	"github.com/ethereum/go-ethereum/core/types"
    14  	"github.com/ethereum/go-ethereum/log"
    15  
    16  	"github.com/status-im/status-go/services/wallet/bigint"
    17  	w_common "github.com/status-im/status-go/services/wallet/common"
    18  	"github.com/status-im/status-go/services/wallet/thirdparty"
    19  	"github.com/status-im/status-go/sqlite"
    20  )
    21  
    22  // DBHeader fields from header that are stored in database.
    23  type DBHeader struct {
    24  	Number                *big.Int
    25  	Hash                  common.Hash
    26  	Timestamp             uint64
    27  	PreloadedTransactions []*PreloadedTransaction
    28  	Network               uint64
    29  	Address               common.Address
    30  	// Head is true if the block was a head at the time it was pulled from chain.
    31  	Head bool
    32  	// Loaded is true if transfers from this block have been already fetched
    33  	Loaded bool
    34  }
    35  
    36  func toDBHeader(header *types.Header, blockHash common.Hash, account common.Address) *DBHeader {
    37  	return &DBHeader{
    38  		Hash:      blockHash,
    39  		Number:    header.Number,
    40  		Timestamp: header.Time,
    41  		Loaded:    false,
    42  		Address:   account,
    43  	}
    44  }
    45  
    46  // SyncOption is used to specify that application processed transfers for that block.
    47  type SyncOption uint
    48  
    49  // JSONBlob type for marshaling/unmarshaling inner type to json.
    50  type JSONBlob struct {
    51  	data interface{}
    52  }
    53  
    54  // Scan implements interface.
    55  func (blob *JSONBlob) Scan(value interface{}) error {
    56  	if value == nil || reflect.ValueOf(blob.data).IsNil() {
    57  		return nil
    58  	}
    59  	bytes, ok := value.([]byte)
    60  	if !ok {
    61  		return errors.New("not a byte slice")
    62  	}
    63  	if len(bytes) == 0 {
    64  		return nil
    65  	}
    66  	err := json.Unmarshal(bytes, blob.data)
    67  	return err
    68  }
    69  
    70  // Value implements interface.
    71  func (blob *JSONBlob) Value() (driver.Value, error) {
    72  	if blob.data == nil || reflect.ValueOf(blob.data).IsNil() {
    73  		return nil, nil
    74  	}
    75  	return json.Marshal(blob.data)
    76  }
    77  
    78  func NewDB(client *sql.DB) *Database {
    79  	return &Database{client: client}
    80  }
    81  
    82  // Database sql wrapper for operations with wallet objects.
    83  type Database struct {
    84  	client *sql.DB
    85  }
    86  
    87  // Close closes database.
    88  func (db *Database) Close() error {
    89  	return db.client.Close()
    90  }
    91  
    92  func (db *Database) SaveBlocks(chainID uint64, headers []*DBHeader) (err error) {
    93  	var (
    94  		tx *sql.Tx
    95  	)
    96  	tx, err = db.client.Begin()
    97  	if err != nil {
    98  		return err
    99  	}
   100  	defer func() {
   101  		if err == nil {
   102  			err = tx.Commit()
   103  			return
   104  		}
   105  		_ = tx.Rollback()
   106  	}()
   107  
   108  	err = insertBlocksWithTransactions(chainID, tx, headers)
   109  	if err != nil {
   110  		return
   111  	}
   112  
   113  	return
   114  }
   115  
   116  func saveTransfersMarkBlocksLoaded(creator statementCreator, chainID uint64, address common.Address, transfers []Transfer, blocks []*big.Int) (err error) {
   117  	err = updateOrInsertTransfers(chainID, creator, transfers)
   118  	if err != nil {
   119  		return
   120  	}
   121  
   122  	err = markBlocksAsLoaded(chainID, creator, address, blocks)
   123  	if err != nil {
   124  		return
   125  	}
   126  
   127  	return
   128  }
   129  
   130  // GetTransfersInRange loads transfers for a given address between two blocks.
   131  func (db *Database) GetTransfersInRange(chainID uint64, address common.Address, start, end *big.Int) (rst []Transfer, err error) {
   132  	query := newTransfersQuery().FilterNetwork(chainID).FilterAddress(address).FilterStart(start).FilterEnd(end).FilterLoaded(1)
   133  	rows, err := db.client.Query(query.String(), query.Args()...)
   134  	if err != nil {
   135  		return
   136  	}
   137  	defer rows.Close()
   138  	return query.TransferScan(rows)
   139  }
   140  
   141  // GetTransfersByAddress loads transfers for a given address between two blocks.
   142  func (db *Database) GetTransfersByAddress(chainID uint64, address common.Address, toBlock *big.Int, limit int64) (rst []Transfer, err error) {
   143  	query := newTransfersQuery().
   144  		FilterNetwork(chainID).
   145  		FilterAddress(address).
   146  		FilterEnd(toBlock).
   147  		FilterLoaded(1).
   148  		SortByBlockNumberAndHash().
   149  		Limit(limit)
   150  
   151  	rows, err := db.client.Query(query.String(), query.Args()...)
   152  	if err != nil {
   153  		return
   154  	}
   155  	defer rows.Close()
   156  	return query.TransferScan(rows)
   157  }
   158  
   159  // GetTransfersByAddressAndBlock loads transfers for a given address and block.
   160  func (db *Database) GetTransfersByAddressAndBlock(chainID uint64, address common.Address, block *big.Int, limit int64) (rst []Transfer, err error) {
   161  	query := newTransfersQuery().
   162  		FilterNetwork(chainID).
   163  		FilterAddress(address).
   164  		FilterBlockNumber(block).
   165  		FilterLoaded(1).
   166  		SortByBlockNumberAndHash().
   167  		Limit(limit)
   168  
   169  	rows, err := db.client.Query(query.String(), query.Args()...)
   170  	if err != nil {
   171  		return
   172  	}
   173  	defer rows.Close()
   174  	return query.TransferScan(rows)
   175  }
   176  
   177  // GetTransfers load transfers transfer between two blocks.
   178  func (db *Database) GetTransfers(chainID uint64, start, end *big.Int) (rst []Transfer, err error) {
   179  	query := newTransfersQuery().FilterNetwork(chainID).FilterStart(start).FilterEnd(end).FilterLoaded(1)
   180  	rows, err := db.client.Query(query.String(), query.Args()...)
   181  	if err != nil {
   182  		return
   183  	}
   184  	defer rows.Close()
   185  	return query.TransferScan(rows)
   186  }
   187  
   188  func (db *Database) GetTransfersForIdentities(ctx context.Context, identities []TransactionIdentity) (rst []Transfer, err error) {
   189  	query := newTransfersQuery()
   190  	for _, identity := range identities {
   191  		subQuery := newSubQuery()
   192  		subQuery = subQuery.FilterNetwork(uint64(identity.ChainID)).FilterTransactionID(identity.Hash).FilterAddress(identity.Address)
   193  		query.addSubQuery(subQuery, OrSeparator)
   194  	}
   195  	rows, err := db.client.QueryContext(ctx, query.String(), query.Args()...)
   196  	if err != nil {
   197  		return
   198  	}
   199  	defer rows.Close()
   200  	return query.TransferScan(rows)
   201  }
   202  
   203  func (db *Database) GetTransactionsToLoad(chainID uint64, address common.Address, blockNumber *big.Int) (rst []*PreloadedTransaction, err error) {
   204  	query := newTransfersQueryForPreloadedTransactions().
   205  		FilterNetwork(chainID).
   206  		FilterLoaded(0)
   207  
   208  	if address != (common.Address{}) {
   209  		query.FilterAddress(address)
   210  	}
   211  
   212  	if blockNumber != nil {
   213  		query.FilterBlockNumber(blockNumber)
   214  	}
   215  
   216  	rows, err := db.client.Query(query.String(), query.Args()...)
   217  	if err != nil {
   218  		return
   219  	}
   220  	defer rows.Close()
   221  	return query.PreloadedTransactionScan(rows)
   222  }
   223  
   224  // statementCreator allows to pass transaction or database to use in consumer.
   225  type statementCreator interface {
   226  	Prepare(query string) (*sql.Stmt, error)
   227  }
   228  
   229  // Only used by status-mobile
   230  func (db *Database) InsertBlock(chainID uint64, account common.Address, blockNumber *big.Int, blockHash common.Hash) error {
   231  	var (
   232  		tx *sql.Tx
   233  	)
   234  	tx, err := db.client.Begin()
   235  	if err != nil {
   236  		return err
   237  	}
   238  	defer func() {
   239  		if err == nil {
   240  			err = tx.Commit()
   241  			return
   242  		}
   243  		_ = tx.Rollback()
   244  	}()
   245  
   246  	blockDB := blockDBFields{
   247  		chainID:     chainID,
   248  		account:     account,
   249  		blockNumber: blockNumber,
   250  		blockHash:   blockHash,
   251  	}
   252  	return insertBlockDBFields(tx, blockDB)
   253  }
   254  
   255  type blockDBFields struct {
   256  	chainID     uint64
   257  	account     common.Address
   258  	blockNumber *big.Int
   259  	blockHash   common.Hash
   260  }
   261  
   262  func insertBlockDBFields(creator statementCreator, block blockDBFields) error {
   263  	insert, err := creator.Prepare("INSERT OR IGNORE INTO blocks(network_id, address, blk_number, blk_hash, loaded) VALUES (?, ?, ?, ?, ?)")
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	_, err = insert.Exec(block.chainID, block.account, (*bigint.SQLBigInt)(block.blockNumber), block.blockHash, true)
   269  	return err
   270  }
   271  
   272  func insertBlocksWithTransactions(chainID uint64, creator statementCreator, headers []*DBHeader) error {
   273  	insert, err := creator.Prepare("INSERT OR IGNORE INTO blocks(network_id, address, blk_number, blk_hash, loaded) VALUES (?, ?, ?, ?, ?)")
   274  	if err != nil {
   275  		return err
   276  	}
   277  	updateTx, err := creator.Prepare(`UPDATE transfers
   278  	SET log = ?, log_index = ?
   279  	WHERE network_id = ? AND address = ? AND hash = ?`)
   280  	if err != nil {
   281  		return err
   282  	}
   283  
   284  	insertTx, err := creator.Prepare(`INSERT OR IGNORE
   285  	INTO transfers (network_id, address, sender, hash, blk_number, blk_hash, type, timestamp, log, loaded, log_index, token_id, amount_padded128hex)
   286  	VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, 0, ?, ?, ?)`)
   287  	if err != nil {
   288  		return err
   289  	}
   290  
   291  	for _, header := range headers {
   292  		_, err = insert.Exec(chainID, header.Address, (*bigint.SQLBigInt)(header.Number), header.Hash, header.Loaded)
   293  		if err != nil {
   294  			return err
   295  		}
   296  		for _, transaction := range header.PreloadedTransactions {
   297  			var logIndex *uint
   298  			if transaction.Log != nil {
   299  				logIndex = new(uint)
   300  				*logIndex = transaction.Log.Index
   301  			}
   302  			res, err := updateTx.Exec(&JSONBlob{transaction.Log}, logIndex, chainID, header.Address, transaction.ID)
   303  			if err != nil {
   304  				return err
   305  			}
   306  			affected, err := res.RowsAffected()
   307  			if err != nil {
   308  				return err
   309  			}
   310  			if affected > 0 {
   311  				continue
   312  			}
   313  
   314  			tokenID := (*bigint.SQLBigIntBytes)(transaction.TokenID)
   315  			txValue := sqlite.BigIntToPadded128BitsStr(transaction.Value)
   316  			// Is that correct to set sender as account address?
   317  			_, err = insertTx.Exec(chainID, header.Address, header.Address, transaction.ID, (*bigint.SQLBigInt)(header.Number), header.Hash, transaction.Type, &JSONBlob{transaction.Log}, logIndex, tokenID, txValue)
   318  			if err != nil {
   319  				log.Error("error saving token transfer", "err", err)
   320  				return err
   321  			}
   322  		}
   323  	}
   324  	return nil
   325  }
   326  
   327  func updateOrInsertTransfers(chainID uint64, creator statementCreator, transfers []Transfer) error {
   328  	txsDBFields := make([]transferDBFields, 0, len(transfers))
   329  	for _, localTransfer := range transfers {
   330  		// to satisfy gosec: C601 checks
   331  		t := localTransfer
   332  		var receiptType *uint8
   333  		var txHash, blockHash *common.Hash
   334  		var receiptStatus, cumulativeGasUsed, gasUsed *uint64
   335  		var contractAddress *common.Address
   336  		var transactionIndex, logIndex *uint
   337  
   338  		if t.Receipt != nil {
   339  			receiptType = &t.Receipt.Type
   340  			receiptStatus = &t.Receipt.Status
   341  			txHash = &t.Receipt.TxHash
   342  			if t.Log != nil {
   343  				logIndex = new(uint)
   344  				*logIndex = t.Log.Index
   345  			}
   346  			blockHash = &t.Receipt.BlockHash
   347  			cumulativeGasUsed = &t.Receipt.CumulativeGasUsed
   348  			contractAddress = &t.Receipt.ContractAddress
   349  			gasUsed = &t.Receipt.GasUsed
   350  			transactionIndex = &t.Receipt.TransactionIndex
   351  		}
   352  
   353  		var txProtected *bool
   354  		var txGas, txNonce, txSize *uint64
   355  		var txGasPrice, txGasTipCap, txGasFeeCap *big.Int
   356  		var txType *uint8
   357  		var txValue *big.Int
   358  		var tokenAddress *common.Address
   359  		var tokenID *big.Int
   360  		var txFrom *common.Address
   361  		var txTo *common.Address
   362  		if t.Transaction != nil {
   363  			if t.Log != nil {
   364  				_, tokenAddress, txFrom, txTo = w_common.ExtractTokenTransferData(t.Type, t.Log, t.Transaction)
   365  				tokenID = t.TokenID
   366  				// Zero tokenID can be used for ERC721 and ERC1155 transfers but when serialzed/deserialized it becomes nil
   367  				// as 0 value of big.Int bytes is nil.
   368  				if tokenID == nil && (t.Type == w_common.Erc721Transfer || t.Type == w_common.Erc1155Transfer) {
   369  					tokenID = big.NewInt(0)
   370  				}
   371  				txValue = t.TokenValue
   372  			} else {
   373  				txValue = new(big.Int).Set(t.Transaction.Value())
   374  				txFrom = &t.From
   375  				txTo = t.Transaction.To()
   376  			}
   377  
   378  			txType = new(uint8)
   379  			*txType = t.Transaction.Type()
   380  			txProtected = new(bool)
   381  			*txProtected = t.Transaction.Protected()
   382  			txGas = new(uint64)
   383  			*txGas = t.Transaction.Gas()
   384  			txGasPrice = t.Transaction.GasPrice()
   385  			txGasTipCap = t.Transaction.GasTipCap()
   386  			txGasFeeCap = t.Transaction.GasFeeCap()
   387  			txNonce = new(uint64)
   388  			*txNonce = t.Transaction.Nonce()
   389  			txSize = new(uint64)
   390  			*txSize = t.Transaction.Size()
   391  		}
   392  
   393  		dbFields := transferDBFields{
   394  			chainID:            chainID,
   395  			id:                 t.ID,
   396  			blockHash:          t.BlockHash,
   397  			blockNumber:        t.BlockNumber,
   398  			timestamp:          t.Timestamp,
   399  			address:            t.Address,
   400  			transaction:        t.Transaction,
   401  			sender:             t.From,
   402  			receipt:            t.Receipt,
   403  			log:                t.Log,
   404  			transferType:       t.Type,
   405  			baseGasFees:        t.BaseGasFees,
   406  			multiTransactionID: t.MultiTransactionID,
   407  			receiptStatus:      receiptStatus,
   408  			receiptType:        receiptType,
   409  			txHash:             txHash,
   410  			logIndex:           logIndex,
   411  			receiptBlockHash:   blockHash,
   412  			cumulativeGasUsed:  cumulativeGasUsed,
   413  			contractAddress:    contractAddress,
   414  			gasUsed:            gasUsed,
   415  			transactionIndex:   transactionIndex,
   416  			txType:             txType,
   417  			txProtected:        txProtected,
   418  			txGas:              txGas,
   419  			txGasPrice:         txGasPrice,
   420  			txGasTipCap:        txGasTipCap,
   421  			txGasFeeCap:        txGasFeeCap,
   422  			txValue:            txValue,
   423  			txNonce:            txNonce,
   424  			txSize:             txSize,
   425  			tokenAddress:       tokenAddress,
   426  			tokenID:            tokenID,
   427  			txFrom:             txFrom,
   428  			txTo:               txTo,
   429  		}
   430  		txsDBFields = append(txsDBFields, dbFields)
   431  	}
   432  
   433  	return updateOrInsertTransfersDBFields(creator, txsDBFields)
   434  }
   435  
   436  type transferDBFields struct {
   437  	chainID            uint64
   438  	id                 common.Hash
   439  	blockHash          common.Hash
   440  	blockNumber        *big.Int
   441  	timestamp          uint64
   442  	address            common.Address
   443  	transaction        *types.Transaction
   444  	sender             common.Address
   445  	receipt            *types.Receipt
   446  	log                *types.Log
   447  	transferType       w_common.Type
   448  	baseGasFees        string
   449  	multiTransactionID w_common.MultiTransactionIDType
   450  	receiptStatus      *uint64
   451  	receiptType        *uint8
   452  	txHash             *common.Hash
   453  	logIndex           *uint
   454  	receiptBlockHash   *common.Hash
   455  	cumulativeGasUsed  *uint64
   456  	contractAddress    *common.Address
   457  	gasUsed            *uint64
   458  	transactionIndex   *uint
   459  	txType             *uint8
   460  	txProtected        *bool
   461  	txGas              *uint64
   462  	txGasPrice         *big.Int
   463  	txGasTipCap        *big.Int
   464  	txGasFeeCap        *big.Int
   465  	txValue            *big.Int
   466  	txNonce            *uint64
   467  	txSize             *uint64
   468  	tokenAddress       *common.Address
   469  	tokenID            *big.Int
   470  	txFrom             *common.Address
   471  	txTo               *common.Address
   472  }
   473  
   474  func updateOrInsertTransfersDBFields(creator statementCreator, transfers []transferDBFields) error {
   475  	insert, err := creator.Prepare(`INSERT OR REPLACE INTO transfers
   476          (network_id, hash, blk_hash, blk_number, timestamp, address, tx, sender, receipt, log, type, loaded, base_gas_fee, multi_transaction_id,
   477  		status, receipt_type, tx_hash, log_index, block_hash, cumulative_gas_used, contract_address, gas_used, tx_index,
   478  		tx_type, protected, gas_limit, gas_price_clamped64, gas_tip_cap_clamped64, gas_fee_cap_clamped64, amount_padded128hex, account_nonce, size, token_address, token_id, tx_from_address, tx_to_address)
   479  	VALUES
   480          (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
   481  	if err != nil {
   482  		return err
   483  	}
   484  	for _, t := range transfers {
   485  		txGasPrice := sqlite.BigIntToClampedInt64(t.txGasPrice)
   486  		txGasTipCap := sqlite.BigIntToClampedInt64(t.txGasTipCap)
   487  		txGasFeeCap := sqlite.BigIntToClampedInt64(t.txGasFeeCap)
   488  		txValue := sqlite.BigIntToPadded128BitsStr(t.txValue)
   489  
   490  		_, err = insert.Exec(t.chainID, t.id, t.blockHash, (*bigint.SQLBigInt)(t.blockNumber), t.timestamp, t.address, &JSONBlob{t.transaction}, t.sender, &JSONBlob{t.receipt}, &JSONBlob{t.log}, t.transferType, t.baseGasFees, t.multiTransactionID,
   491  			t.receiptStatus, t.receiptType, t.txHash, t.logIndex, t.receiptBlockHash, t.cumulativeGasUsed, t.contractAddress, t.gasUsed, t.transactionIndex,
   492  			t.txType, t.txProtected, t.txGas, txGasPrice, txGasTipCap, txGasFeeCap, txValue, t.txNonce, t.txSize, t.tokenAddress, (*bigint.SQLBigIntBytes)(t.tokenID), t.txFrom, t.txTo)
   493  		if err != nil {
   494  			log.Error("can't save transfer", "b-hash", t.blockHash, "b-n", t.blockNumber, "a", t.address, "h", t.id)
   495  			return err
   496  		}
   497  	}
   498  
   499  	for _, t := range transfers {
   500  		err = removeGasOnlyEthTransfer(creator, t)
   501  		if err != nil {
   502  			log.Error("can't remove gas only eth transfer", "b-hash", t.blockHash, "b-n", t.blockNumber, "a", t.address, "h", t.id, "err", err)
   503  			// no return err, since it's not critical
   504  		}
   505  	}
   506  	return nil
   507  }
   508  
   509  func removeGasOnlyEthTransfer(creator statementCreator, t transferDBFields) error {
   510  	if t.transferType == w_common.EthTransfer {
   511  		countQuery, err := creator.Prepare(`SELECT COUNT(*) FROM transfers WHERE tx_hash = ?`)
   512  		if err != nil {
   513  			return err
   514  		}
   515  		defer countQuery.Close()
   516  
   517  		var count int
   518  		err = countQuery.QueryRow(t.txHash).Scan(&count)
   519  		if err != nil {
   520  			return err
   521  		}
   522  
   523  		// If there's only one (or none), return without deleting
   524  		if count <= 1 {
   525  			log.Debug("Only one or no transfer found with the same tx_hash, skipping deletion.")
   526  			return nil
   527  		}
   528  	}
   529  	query, err := creator.Prepare(`DELETE FROM transfers WHERE tx_hash = ? AND address = ? AND network_id = ? AND account_nonce = ? AND type = 'eth' AND amount_padded128hex = '00000000000000000000000000000000'`)
   530  	if err != nil {
   531  		return err
   532  	}
   533  	defer query.Close()
   534  
   535  	res, err := query.Exec(t.txHash, t.address, t.chainID, t.txNonce)
   536  	if err != nil {
   537  		return err
   538  	}
   539  	count, err := res.RowsAffected()
   540  	if err != nil {
   541  		return err
   542  	}
   543  	log.Debug("removeGasOnlyEthTransfer rows deleted", "count", count)
   544  	return nil
   545  }
   546  
   547  // markBlocksAsLoaded(chainID, tx, address, blockNumbers)
   548  // In case block contains both ETH and token transfers, it will be marked as loaded on ETH transfer processing.
   549  // This is not a problem since for token transfers we have preloaded transactions and blocks 'loaded' flag is needed
   550  // for ETH transfers only.
   551  func markBlocksAsLoaded(chainID uint64, creator statementCreator, address common.Address, blocks []*big.Int) error {
   552  	update, err := creator.Prepare("UPDATE blocks SET loaded=? WHERE address=? AND blk_number=? AND network_id=?")
   553  	if err != nil {
   554  		return err
   555  	}
   556  
   557  	for _, block := range blocks {
   558  		_, err := update.Exec(true, address, (*bigint.SQLBigInt)(block), chainID)
   559  		if err != nil {
   560  			return err
   561  		}
   562  	}
   563  	return nil
   564  }
   565  
   566  // GetOwnedMultiTransactionID returns sql.ErrNoRows if no transaction is found for the given identity
   567  func GetOwnedMultiTransactionID(tx *sql.Tx, chainID w_common.ChainID, hash common.Hash, address common.Address) (mTID int64, err error) {
   568  	row := tx.QueryRow(`SELECT COALESCE(multi_transaction_id, 0) FROM transfers WHERE network_id = ? AND tx_hash = ? AND address = ?`, chainID, hash, address)
   569  	err = row.Scan(&mTID)
   570  	if err != nil {
   571  		return 0, err
   572  	}
   573  	return mTID, nil
   574  }
   575  
   576  func (db *Database) GetLatestCollectibleTransfer(address common.Address, id thirdparty.CollectibleUniqueID) (*Transfer, error) {
   577  	query := newTransfersQuery().
   578  		FilterAddress(address).
   579  		FilterNetwork(uint64(id.ContractID.ChainID)).
   580  		FilterTokenAddress(id.ContractID.Address).
   581  		FilterTokenID(id.TokenID.Int).
   582  		FilterLoaded(1).
   583  		SortByTimestamp(false).
   584  		Limit(1)
   585  	rows, err := db.client.Query(query.String(), query.Args()...)
   586  	if err != nil {
   587  		return nil, err
   588  	}
   589  	defer rows.Close()
   590  
   591  	transfers, err := query.TransferScan(rows)
   592  	if err == sql.ErrNoRows || len(transfers) == 0 {
   593  		return nil, nil
   594  	} else if err != nil {
   595  		return nil, err
   596  	}
   597  
   598  	return &transfers[0], nil
   599  }
   600  
   601  // Delete blocks for address and chainID
   602  // Transfers will be deleted by cascade
   603  func deleteBlocks(creator statementCreator, address common.Address) error {
   604  	delete, err := creator.Prepare("DELETE FROM blocks WHERE address = ?")
   605  	if err != nil {
   606  		return err
   607  	}
   608  
   609  	_, err = delete.Exec(address)
   610  	return err
   611  }
   612  
   613  func getAddresses(creator statementCreator) (rst []common.Address, err error) {
   614  	stmt, err := creator.Prepare(`SELECT address FROM transfers UNION SELECT address FROM blocks UNION
   615  		SELECT address FROM blocks_ranges_sequential UNION SELECT address FROM blocks_ranges`)
   616  	if err != nil {
   617  		return
   618  	}
   619  	rows, err := stmt.Query()
   620  	if err != nil {
   621  		return nil, err
   622  	}
   623  	defer rows.Close()
   624  
   625  	address := common.Address{}
   626  	for rows.Next() {
   627  		err = rows.Scan(&address)
   628  		if err != nil {
   629  			return nil, err
   630  		}
   631  		rst = append(rst, address)
   632  	}
   633  
   634  	return rst, nil
   635  }