diff --git a/VERSION b/VERSION index cf1590537..10d7da8f1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.91.9 \ No newline at end of file +0.91.10 diff --git a/services/wallet/api.go b/services/wallet/api.go index 03570395b..60c680c30 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -39,10 +39,16 @@ func (api *API) CheckRecentHistoryForChainIDs(ctx context.Context, chainIDs []ui // GetTransfersByAddress returns transfers for a single address func (api *API) GetTransfersByAddress(ctx context.Context, address common.Address, toBlock, limit *hexutil.Big, fetchMore bool) ([]transfer.View, error) { - log.Debug("[WalletAPI:: GetTransfersByAddress] get transfers for an address", "address") + log.Debug("[WalletAPI:: GetTransfersByAddress] get transfers for an address", "address", address) return api.s.transferController.GetTransfersByAddress(ctx, api.s.rpcClient.UpstreamChainID, address, toBlock, limit, fetchMore) } +// LoadTransferByHash loads transfer to the database +func (api *API) LoadTransferByHash(ctx context.Context, address common.Address, hash common.Hash) error { + log.Debug("[WalletAPI:: LoadTransferByHash] get transfer by hash", "address", address, "hash", hash) + return api.s.transferController.LoadTransferByHash(ctx, api.s.rpcClient, address, hash) +} + func (api *API) GetTransfersByAddressAndChainID(ctx context.Context, chainID uint64, address common.Address, toBlock, limit *hexutil.Big, fetchMore bool) ([]transfer.View, error) { log.Debug("[WalletAPI:: GetTransfersByAddressAndChainID] get transfers for an address", "address", address) return api.s.transferController.GetTransfersByAddress(ctx, chainID, address, toBlock, limit, fetchMore) diff --git a/services/wallet/transfer/commands.go b/services/wallet/transfer/commands.go index aa2845935..7391a2030 100644 --- a/services/wallet/transfer/commands.go +++ b/services/wallet/transfer/commands.go @@ -14,7 +14,16 @@ import ( "github.com/status-im/status-go/services/wallet/chain" ) -var numberOfBlocksCheckedPerIteration = 40 +var ( + // This will work only for binance testnet as mainnet doesn't support + // archival request. + binanceChainMaxInitialRange = big.NewInt(500000) + binanceChainErc20BatchSize = big.NewInt(5000) + erc20BatchSize = big.NewInt(100000) + binancChainID = uint64(56) + binanceTestChainID = uint64(97) + numberOfBlocksCheckedPerIteration = 40 +) type ethHistoricalCommand struct { db *Database @@ -87,12 +96,20 @@ func (c *erc20HistoricalCommand) Command() async.Command { }.Run } +func getErc20BatchSize(chainID uint64) *big.Int { + if isBinanceChain(chainID) { + return binanceChainErc20BatchSize + } + + return erc20BatchSize +} + func (c *erc20HistoricalCommand) Run(ctx context.Context) (err error) { start := time.Now() if c.iterator == nil { c.iterator, err = SetupIterativeDownloader( c.db, c.chainClient, c.address, - c.erc20, erc20BatchSize, c.to, c.from) + c.erc20, getErc20BatchSize(c.chainClient.ChainID), c.to, c.from, !isBinanceChain(c.chainClient.ChainID)) if err != nil { log.Error("failed to setup historical downloader for erc20") return err @@ -585,8 +602,21 @@ func loadTransfers(ctx context.Context, accounts []common.Address, block *Block, } } -func findFirstRange(c context.Context, account common.Address, initialTo *big.Int, client *chain.Client) (*big.Int, error) { +func isBinanceChain(chainID uint64) bool { + return chainID == binancChainID || chainID == binanceTestChainID +} + +func getLowestFrom(chainID uint64, to *big.Int) *big.Int { from := big.NewInt(0) + if isBinanceChain(chainID) && big.NewInt(0).Sub(to, from).Cmp(binanceChainMaxInitialRange) == 1 { + from = big.NewInt(0).Sub(to, binanceChainMaxInitialRange) + } + + return from +} + +func findFirstRange(c context.Context, account common.Address, initialTo *big.Int, client *chain.Client) (*big.Int, error) { + from := getLowestFrom(client.ChainID, initialTo) to := initialTo goal := uint64(20) @@ -602,7 +632,7 @@ func findFirstRange(c context.Context, account common.Address, initialTo *big.In } if firstNonce <= goal { - return zero, nil + return from, nil } nonceDiff := firstNonce diff --git a/services/wallet/transfer/controller.go b/services/wallet/transfer/controller.go index 1c0a18360..274ac3e94 100644 --- a/services/wallet/transfer/controller.go +++ b/services/wallet/transfer/controller.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/status-im/status-go/multiaccounts/accounts" @@ -169,6 +170,35 @@ func mapToList(m map[common.Address]struct{}) []common.Address { return rst } +func (c *Controller) LoadTransferByHash(ctx context.Context, rpcClient *rpc.Client, address common.Address, hash common.Hash) error { + chainClient, err := chain.NewClient(rpcClient, rpcClient.UpstreamChainID) + if err != nil { + return err + } + + signer := types.NewLondonSigner(chainClient.ToBigInt()) + + transfer, err := getTransferByHash(ctx, chainClient, signer, address, hash) + if err != nil { + return err + } + + transfers := []Transfer{*transfer} + + err = c.db.InsertBlock(rpcClient.UpstreamChainID, address, transfer.BlockNumber, transfer.BlockHash) + if err != nil { + return err + } + + blocks := []*big.Int{transfer.BlockNumber} + err = c.db.SaveTranfers(rpcClient.UpstreamChainID, address, transfers, blocks) + if err != nil { + return err + } + + return nil +} + func (c *Controller) GetTransfersByAddress(ctx context.Context, chainID uint64, address common.Address, toBlock, limit *hexutil.Big, fetchMore bool) ([]View, error) { log.Debug("[WalletAPI:: GetTransfersByAddress] get transfers for an address", "address", address) var toBlockBN *big.Int diff --git a/services/wallet/transfer/database.go b/services/wallet/transfer/database.go index f91e19afb..65f799b8c 100644 --- a/services/wallet/transfer/database.go +++ b/services/wallet/transfer/database.go @@ -351,6 +351,31 @@ func deleteHeaders(creator statementCreator, headers []*DBHeader) error { return nil } +func (db *Database) InsertBlock(chainID uint64, account common.Address, blockNumber *big.Int, blockHash common.Hash) error { + var ( + tx *sql.Tx + ) + tx, err := db.client.Begin() + if err != nil { + return err + } + defer func() { + if err == nil { + err = tx.Commit() + return + } + _ = tx.Rollback() + }() + + insert, err := tx.Prepare("INSERT OR IGNORE INTO blocks(network_id, address, blk_number, blk_hash, loaded) VALUES (?, ?, ?, ?, ?)") + if err != nil { + return err + } + + _, err = insert.Exec(chainID, account, (*bigint.SQLBigInt)(blockNumber), blockHash, true) + return err +} + func insertBlocksWithTransactions(chainID uint64, creator statementCreator, account common.Address, headers []*DBHeader) error { insert, err := creator.Prepare("INSERT OR IGNORE INTO blocks(network_id, address, blk_number, blk_hash, loaded) VALUES (?, ?, ?, ?, ?)") if err != nil { @@ -363,7 +388,7 @@ func insertBlocksWithTransactions(chainID uint64, creator statementCreator, acco return err } - insertTx, err := creator.Prepare(`INSERT OR IGNORE + insertTx, err := creator.Prepare(`INSERT OR IGNORE INTO transfers (network_id, address, sender, hash, blk_number, blk_hash, type, timestamp, log, loaded) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0)`) if err != nil { diff --git a/services/wallet/transfer/downloader.go b/services/wallet/transfer/downloader.go index 3202ca02d..b46232435 100644 --- a/services/wallet/transfer/downloader.go +++ b/services/wallet/transfer/downloader.go @@ -90,6 +90,44 @@ func (d *ETHDownloader) GetTransfersByNumber(ctx context.Context, number *big.In return rst, err } +func getTransferByHash(ctx context.Context, client *chain.Client, signer types.Signer, address common.Address, hash common.Hash) (*Transfer, error) { + transaction, _, err := client.TransactionByHash(ctx, hash) + if err != nil { + return nil, err + } + + receipt, err := client.TransactionReceipt(ctx, hash) + if err != nil { + return nil, err + } + + transactionLog := getTokenLog(receipt.Logs) + + transferType := ethTransfer + if transactionLog != nil { + transferType = erc20Transfer + } + + from, err := types.Sender(signer, transaction) + + if err != nil { + return nil, err + } + + transfer := &Transfer{Type: transferType, + ID: hash, + Address: address, + BlockNumber: receipt.BlockNumber, + BlockHash: receipt.BlockHash, + Timestamp: uint64(time.Now().Unix()), + Transaction: transaction, + From: from, + Receipt: receipt, + Log: transactionLog} + + return transfer, nil +} + func (d *ETHDownloader) getTransfersInBlock(ctx context.Context, blk *types.Block, accounts []common.Address) (rst []Transfer, err error) { for _, address := range accounts { preloadedTransfers, err := d.db.GetPreloadedTransactions(d.chainClient.ChainID, address, blk.Hash()) diff --git a/services/wallet/transfer/iterative.go b/services/wallet/transfer/iterative.go index 768684a10..4d160383d 100644 --- a/services/wallet/transfer/iterative.go +++ b/services/wallet/transfer/iterative.go @@ -12,14 +12,14 @@ import ( // SetupIterativeDownloader configures IterativeDownloader with last known synced block. func SetupIterativeDownloader( db *Database, client HeaderReader, address common.Address, - downloader BatchDownloader, size *big.Int, to *big.Int, from *big.Int) (*IterativeDownloader, error) { + downloader BatchDownloader, size *big.Int, to *big.Int, from *big.Int, isBatchSizeAdjustable bool) (*IterativeDownloader, error) { if to == nil || from == nil { return nil, errors.New("to or from cannot be nil") } adjustedSize := big.NewInt(0).Div(big.NewInt(0).Sub(to, from), big.NewInt(10)) - if adjustedSize.Cmp(size) == 1 { + if isBatchSizeAdjustable && adjustedSize.Cmp(size) == 1 { size = adjustedSize } log.Info("iterative downloader", "address", address, "from", from, "to", to, "size", size) diff --git a/services/wallet/transfer/reactor.go b/services/wallet/transfer/reactor.go index 76a8ab196..6bd4e489f 100644 --- a/services/wallet/transfer/reactor.go +++ b/services/wallet/transfer/reactor.go @@ -13,10 +13,7 @@ import ( "github.com/status-im/status-go/services/wallet/chain" ) -var ( - erc20BatchSize = big.NewInt(100000) - errAlreadyRunning = errors.New("already running") -) +var errAlreadyRunning = errors.New("already running") // HeaderReader interface for reading headers using block number or hash. type HeaderReader interface {