Scanning of ERC20 tail of transfers history

This commit is contained in:
Roman Volosovskyi 2023-09-20 10:41:23 +02:00
parent 7d03ae8272
commit 382fcde74e
No known key found for this signature in database
GPG Key ID: 0238A4B5ECEE70DE
17 changed files with 927 additions and 231 deletions

View File

@ -165,29 +165,36 @@ func (c *ContractMaker) NewDirectory(chainID uint64) (*directory.Directory, erro
) )
} }
func (c *ContractMaker) NewEthScan(chainID uint64) (*ethscan.BalanceScanner, error) { func (c *ContractMaker) NewEthScan(chainID uint64) (*ethscan.BalanceScanner, uint, error) {
contractAddr, err := ethscan.ContractAddress(chainID) contractAddr, err := ethscan.ContractAddress(chainID)
if err != nil { if err != nil {
return nil, err return nil, 0, err
}
contractCreatedAt, err := ethscan.ContractCreatedAt(chainID)
if err != nil {
return nil, 0, err
} }
backend, err := c.RPCClient.EthClient(chainID) backend, err := c.RPCClient.EthClient(chainID)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
bytecode, err := backend.CodeAt(context.Background(), contractAddr, nil) bytecode, err := backend.CodeAt(context.Background(), contractAddr, nil)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
if len(bytecode) == 0 { if len(bytecode) == 0 {
return nil, errors.New("is not a contract") return nil, 0, errors.New("is not a contract")
} }
return ethscan.NewBalanceScanner( scanner, err := ethscan.NewBalanceScanner(
contractAddr, contractAddr,
backend, backend,
) )
return scanner, contractCreatedAt, err
} }
func (c *ContractMaker) NewHopL2SaddlSwap(chainID uint64, symbol string) (*hopSwap.HopSwap, error) { func (c *ContractMaker) NewHopL2SaddlSwap(chainID uint64, symbol string) (*hopSwap.HopSwap, error) {

View File

@ -8,20 +8,34 @@ import (
var errorNotAvailableOnChainID = errors.New("not available for chainID") var errorNotAvailableOnChainID = errors.New("not available for chainID")
var contractAddressByChainID = map[uint64]common.Address{ type ContractData struct {
1: common.HexToAddress("0x08A8fDBddc160A7d5b957256b903dCAb1aE512C5"), // mainnet Address common.Address
5: common.HexToAddress("0x08A8fDBddc160A7d5b957256b903dCAb1aE512C5"), // goerli CreatedAtBlock uint
10: common.HexToAddress("0x9e5076df494fc949abc4461f4e57592b81517d81"), // optimism }
420: common.HexToAddress("0xf532c75239fa61b66d31e73f44300c46da41aadd"), // goerli optimism
42161: common.HexToAddress("0xbb85398092b83a016935a17fc857507b7851a071"), // arbitrum var contractDataByChainID = map[uint64]ContractData{
421613: common.HexToAddress("0xec21ebe1918e8975fc0cd0c7747d318c00c0acd5"), // goerli arbitrum 1: {common.HexToAddress("0x08A8fDBddc160A7d5b957256b903dCAb1aE512C5"), 12_194_222}, // mainnet
11155111: common.HexToAddress("0xec21ebe1918e8975fc0cd0c7747d318c00c0acd5"), // sepolia 5: {common.HexToAddress("0x08A8fDBddc160A7d5b957256b903dCAb1aE512C5"), 4_578_854}, // goerli
10: {common.HexToAddress("0x9e5076df494fc949abc4461f4e57592b81517d81"), 34_421_097}, // optimism
420: {common.HexToAddress("0xf532c75239fa61b66d31e73f44300c46da41aadd"), 2_236_534}, // goerli optimism
42161: {common.HexToAddress("0xbb85398092b83a016935a17fc857507b7851a071"), 70_031_945}, // arbitrum
421613: {common.HexToAddress("0xec21ebe1918e8975fc0cd0c7747d318c00c0acd5"), 818_155}, // goerli arbitrum
777333: {common.HexToAddress("0x0000000000000000000000000000000000777333"), 50}, // unit tests
11155111: {common.HexToAddress("0xec21ebe1918e8975fc0cd0c7747d318c00c0acd5"), 4_366_506}, // sepolia
} }
func ContractAddress(chainID uint64) (common.Address, error) { func ContractAddress(chainID uint64) (common.Address, error) {
addr, exists := contractAddressByChainID[chainID] contract, exists := contractDataByChainID[chainID]
if !exists { if !exists {
return *new(common.Address), errorNotAvailableOnChainID return *new(common.Address), errorNotAvailableOnChainID
} }
return addr, nil return contract.Address, nil
}
func ContractCreatedAt(chainID uint64) (uint, error) {
contract, exists := contractDataByChainID[chainID]
if !exists {
return 0, errorNotAvailableOnChainID
}
return contract.CreatedAtBlock, nil
} }

View File

@ -42,6 +42,19 @@ type ClientInterface interface {
GetBaseFeeFromBlock(blockNumber *big.Int) (string, error) GetBaseFeeFromBlock(blockNumber *big.Int) (string, error)
NetworkID() uint64 NetworkID() uint64
ToBigInt() *big.Int ToBigInt() *big.Int
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
GetWalletNotifier() func(chainId uint64, message string)
SetWalletNotifier(notifier func(chainId uint64, message string))
TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error)
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
BlockNumber(ctx context.Context) (uint64, error)
SetIsConnected(value bool)
GetIsConnected() bool
bind.ContractCaller
bind.ContractTransactor
bind.ContractFilterer
} }
type ClientWithFallback struct { type ClientWithFallback struct {
@ -169,6 +182,12 @@ func (c *ClientWithFallback) SetIsConnected(value bool) {
} }
} }
func (c *ClientWithFallback) GetIsConnected() bool {
c.IsConnectedLock.RLock()
defer c.IsConnectedLock.RUnlock()
return c.IsConnected
}
func (c *ClientWithFallback) makeCallNoReturn(main func() error, fallback func() error) error { func (c *ClientWithFallback) makeCallNoReturn(main func() error, fallback func() error) error {
resultChan := make(chan CommandResult, 1) resultChan := make(chan CommandResult, 1)
c.LastCheckedAt = time.Now().Unix() c.LastCheckedAt = time.Now().Unix()
@ -877,3 +896,11 @@ func (c *ClientWithFallback) FullTransactionByBlockNumberAndIndex(ctx context.Co
return tx.(*FullTransaction), nil return tx.(*FullTransaction), nil
} }
func (c *ClientWithFallback) GetWalletNotifier() func(chainId uint64, message string) {
return c.WalletNotifier
}
func (c *ClientWithFallback) SetWalletNotifier(notifier func(chainId uint64, message string)) {
c.WalletNotifier = notifier
}

View File

@ -48,9 +48,9 @@ type Client struct {
UpstreamChainID uint64 UpstreamChainID uint64
local *gethrpc.Client local *gethrpc.Client
upstream *chain.ClientWithFallback upstream chain.ClientInterface
rpcClientsMutex sync.RWMutex rpcClientsMutex sync.RWMutex
rpcClients map[uint64]*chain.ClientWithFallback rpcClients map[uint64]chain.ClientInterface
router *router router *router
NetworkManager *network.Manager NetworkManager *network.Manager
@ -84,7 +84,7 @@ func NewClient(client *gethrpc.Client, upstreamChainID uint64, upstream params.U
local: client, local: client,
NetworkManager: networkManager, NetworkManager: networkManager,
handlers: make(map[string]Handler), handlers: make(map[string]Handler),
rpcClients: make(map[uint64]*chain.ClientWithFallback), rpcClients: make(map[uint64]chain.ClientInterface),
log: log, log: log,
} }
@ -112,12 +112,12 @@ func (c *Client) SetWalletNotifier(notifier func(chainID uint64, message string)
c.walletNotifier = notifier c.walletNotifier = notifier
} }
func (c *Client) getClientUsingCache(chainID uint64) (*chain.ClientWithFallback, error) { func (c *Client) getClientUsingCache(chainID uint64) (chain.ClientInterface, error) {
c.rpcClientsMutex.Lock() c.rpcClientsMutex.Lock()
defer c.rpcClientsMutex.Unlock() defer c.rpcClientsMutex.Unlock()
if rpcClient, ok := c.rpcClients[chainID]; ok { if rpcClient, ok := c.rpcClients[chainID]; ok {
if rpcClient.WalletNotifier == nil { if rpcClient.GetWalletNotifier() == nil {
rpcClient.WalletNotifier = c.walletNotifier rpcClient.SetWalletNotifier(c.walletNotifier)
} }
return rpcClient, nil return rpcClient, nil
} }
@ -150,7 +150,7 @@ func (c *Client) getClientUsingCache(chainID uint64) (*chain.ClientWithFallback,
} }
// Ethclient returns ethclient.Client per chain // Ethclient returns ethclient.Client per chain
func (c *Client) EthClient(chainID uint64) (*chain.ClientWithFallback, error) { func (c *Client) EthClient(chainID uint64) (chain.ClientInterface, error) {
client, err := c.getClientUsingCache(chainID) client, err := c.getClientUsingCache(chainID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -169,8 +169,8 @@ func (c *Client) AbstractEthClient(chainID common.ChainID) (chain.BatchCallClien
return client, nil return client, nil
} }
func (c *Client) EthClients(chainIDs []uint64) (map[uint64]*chain.ClientWithFallback, error) { func (c *Client) EthClients(chainIDs []uint64) (map[uint64]chain.ClientInterface, error) {
clients := make(map[uint64]*chain.ClientWithFallback, 0) clients := make(map[uint64]chain.ClientInterface, 0)
for _, chainID := range chainIDs { for _, chainID := range chainIDs {
client, err := c.getClientUsingCache(chainID) client, err := c.getClientUsingCache(chainID)
if err != nil { if err != nil {
@ -182,6 +182,13 @@ func (c *Client) EthClients(chainIDs []uint64) (map[uint64]*chain.ClientWithFall
return clients, nil return clients, nil
} }
// SetClient strictly for testing purposes
func (c *Client) SetClient(chainID uint64, client chain.ClientInterface) {
c.rpcClientsMutex.Lock()
defer c.rpcClientsMutex.Unlock()
c.rpcClients[chainID] = client
}
// UpdateUpstreamURL changes the upstream RPC client URL, if the upstream is enabled. // UpdateUpstreamURL changes the upstream RPC client URL, if the upstream is enabled.
func (c *Client) UpdateUpstreamURL(url string) error { func (c *Client) UpdateUpstreamURL(url string) error {
if c.upstream == nil { if c.upstream == nil {

View File

@ -150,7 +150,7 @@ func (s *Service) isTokenVisible(tokenSymbol string) bool {
// Native token implementation of DataSource interface // Native token implementation of DataSource interface
type chainClientSource struct { type chainClientSource struct {
chainClient *chain.ClientWithFallback chainClient chain.ClientInterface
currency string currency string
} }
@ -163,7 +163,7 @@ func (src *chainClientSource) BalanceAt(ctx context.Context, account common.Addr
} }
func (src *chainClientSource) ChainID() uint64 { func (src *chainClientSource) ChainID() uint64 {
return src.chainClient.ChainID return src.chainClient.NetworkID()
} }
func (src *chainClientSource) Currency() string { func (src *chainClientSource) Currency() string {
@ -184,7 +184,7 @@ type tokenChainClientSource struct {
} }
func (src *tokenChainClientSource) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { func (src *tokenChainClientSource) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
network := src.NetworkManager.Find(src.chainClient.ChainID) network := src.NetworkManager.Find(src.chainClient.NetworkID())
if network == nil { if network == nil {
return nil, errors.New("network not found") return nil, errors.New("network not found")
} }

View File

@ -237,7 +237,7 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address)
} }
hasError := false hasError := false
if client, ok := clients[token.ChainID]; ok { if client, ok := clients[token.ChainID]; ok {
hasError = err != nil || !client.IsConnected hasError = err != nil || !client.GetIsConnected()
} }
if !anyPositiveBalance { if !anyPositiveBalance {
anyPositiveBalance = balance.Cmp(big.NewFloat(0.0)) > 0 anyPositiveBalance = balance.Cmp(big.NewFloat(0.0)) > 0

View File

@ -14,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/contracts" "github.com/status-im/status-go/contracts"
"github.com/status-im/status-go/contracts/ethscan"
"github.com/status-im/status-go/contracts/ierc20" "github.com/status-im/status-go/contracts/ierc20"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc"
@ -153,7 +154,7 @@ func (tm *Manager) getAddressTokenMap(chainID uint64) (addressTokenMap, bool) {
return tokenMap, chainPresent return tokenMap, chainPresent
} }
func (tm *Manager) setTokens(tokens []*Token) { func (tm *Manager) SetTokens(tokens []*Token) {
tm.tokenLock.Lock() tm.tokenLock.Lock()
defer tm.tokenLock.Unlock() defer tm.tokenLock.Unlock()
@ -187,7 +188,7 @@ func (tm *Manager) fetchTokens() {
tokenList = mergeTokenLists([][]*Token{tokenList, validTokens}) tokenList = mergeTokenLists([][]*Token{tokenList, validTokens})
} }
tm.setTokens(tokenList) tm.SetTokens(tokenList)
} }
func (tm *Manager) getFullTokenList(chainID uint64) []*Token { func (tm *Manager) getFullTokenList(chainID uint64) []*Token {
@ -577,7 +578,7 @@ func (tm *Manager) DeleteCustom(chainID uint64, address common.Address) error {
return err return err
} }
func (tm *Manager) GetTokenBalance(ctx context.Context, client *chain.ClientWithFallback, account common.Address, token common.Address) (*big.Int, error) { func (tm *Manager) GetTokenBalance(ctx context.Context, client chain.ClientInterface, account common.Address, token common.Address) (*big.Int, error) {
caller, err := ierc20.NewIERC20Caller(token, client) caller, err := ierc20.NewIERC20Caller(token, client)
if err != nil { if err != nil {
return nil, err return nil, err
@ -588,23 +589,32 @@ func (tm *Manager) GetTokenBalance(ctx context.Context, client *chain.ClientWith
}, account) }, account)
} }
func (tm *Manager) GetTokenBalanceAt(ctx context.Context, client *chain.ClientWithFallback, account common.Address, token common.Address, blockNumber *big.Int) (*big.Int, error) { func (tm *Manager) GetTokenBalanceAt(ctx context.Context, client chain.ClientInterface, account common.Address, token common.Address, blockNumber *big.Int) (*big.Int, error) {
caller, err := ierc20.NewIERC20Caller(token, client) caller, err := ierc20.NewIERC20Caller(token, client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return caller.BalanceOf(&bind.CallOpts{ balance, err := caller.BalanceOf(&bind.CallOpts{
Context: ctx, Context: ctx,
BlockNumber: blockNumber, BlockNumber: blockNumber,
}, account) }, account)
if err != nil {
if err != bind.ErrNoCode {
return nil, err
}
balance = big.NewInt(0)
}
return balance, nil
} }
func (tm *Manager) GetChainBalance(ctx context.Context, client *chain.ClientWithFallback, account common.Address) (*big.Int, error) { func (tm *Manager) GetChainBalance(ctx context.Context, client chain.ClientInterface, account common.Address) (*big.Int, error) {
return client.BalanceAt(ctx, account, nil) return client.BalanceAt(ctx, account, nil)
} }
func (tm *Manager) GetBalance(ctx context.Context, client *chain.ClientWithFallback, account common.Address, token common.Address) (*big.Int, error) { func (tm *Manager) GetBalance(ctx context.Context, client chain.ClientInterface, account common.Address, token common.Address) (*big.Int, error) {
if token == nativeChainAddress { if token == nativeChainAddress {
return tm.GetChainBalance(ctx, client, account) return tm.GetChainBalance(ctx, client, account)
} }
@ -612,7 +622,7 @@ func (tm *Manager) GetBalance(ctx context.Context, client *chain.ClientWithFallb
return tm.GetTokenBalance(ctx, client, account, token) return tm.GetTokenBalance(ctx, client, account, token)
} }
func (tm *Manager) GetBalances(parent context.Context, clients map[uint64]*chain.ClientWithFallback, accounts, tokens []common.Address) (map[common.Address]map[common.Address]*hexutil.Big, error) { func (tm *Manager) GetBalances(parent context.Context, clients map[uint64]chain.ClientInterface, accounts, tokens []common.Address) (map[common.Address]map[common.Address]*hexutil.Big, error) {
var ( var (
group = async.NewAtomicGroup(parent) group = async.NewAtomicGroup(parent)
mu sync.Mutex mu sync.Mutex
@ -638,7 +648,7 @@ func (tm *Manager) GetBalances(parent context.Context, clients map[uint64]*chain
for clientIdx := range clients { for clientIdx := range clients {
client := clients[clientIdx] client := clients[clientIdx]
ethScanContract, err := tm.contractMaker.NewEthScan(client.ChainID) ethScanContract, _, err := tm.contractMaker.NewEthScan(client.NetworkID())
if err == nil { if err == nil {
fetchChainBalance := false fetchChainBalance := false
@ -666,7 +676,7 @@ func (tm *Manager) GetBalances(parent context.Context, clients map[uint64]*chain
Context: ctx, Context: ctx,
}, accounts) }, accounts)
if err != nil { if err != nil {
log.Error("can't fetch chain balance", err) log.Error("can't fetch chain balance 2", err)
return nil return nil
} }
for idx, account := range accounts { for idx, account := range accounts {
@ -690,7 +700,7 @@ func (tm *Manager) GetBalances(parent context.Context, clients map[uint64]*chain
Context: ctx, Context: ctx,
}, account, chunk) }, account, chunk)
if err != nil { if err != nil {
log.Error("can't fetch erc20 token balance", "account", account, "error", err) log.Error("can't fetch erc20 token balance 3", "account", account, "error", err)
return nil return nil
} }
@ -713,7 +723,7 @@ func (tm *Manager) GetBalances(parent context.Context, clients map[uint64]*chain
account := accounts[accountIdx] account := accounts[accountIdx]
token := tokens[tokenIdx] token := tokens[tokenIdx]
client := clients[clientIdx] client := clients[clientIdx]
if !tm.inStore(token, client.ChainID) { if !tm.inStore(token, client.NetworkID()) {
continue continue
} }
group.Add(func(parent context.Context) error { group.Add(func(parent context.Context) error {
@ -722,7 +732,7 @@ func (tm *Manager) GetBalances(parent context.Context, clients map[uint64]*chain
balance, err := tm.GetBalance(ctx, client, account, token) balance, err := tm.GetBalance(ctx, client, account, token)
if err != nil { if err != nil {
log.Error("can't fetch erc20 token balance", "account", account, "token", token, "error", err) log.Error("can't fetch erc20 token balance 4", "account", account, "token", token, "error", err)
return nil return nil
} }
@ -742,7 +752,11 @@ func (tm *Manager) GetBalances(parent context.Context, clients map[uint64]*chain
return response, group.Error() return response, group.Error()
} }
func (tm *Manager) GetBalancesByChain(parent context.Context, clients map[uint64]*chain.ClientWithFallback, accounts, tokens []common.Address) (map[uint64]map[common.Address]map[common.Address]*hexutil.Big, error) { func (tm *Manager) GetBalancesByChain(parent context.Context, clients map[uint64]chain.ClientInterface, accounts, tokens []common.Address) (map[uint64]map[common.Address]map[common.Address]*hexutil.Big, error) {
return tm.GetBalancesAtByChain(parent, clients, accounts, tokens, nil)
}
func (tm *Manager) GetBalancesAtByChain(parent context.Context, clients map[uint64]chain.ClientInterface, accounts, tokens []common.Address, atBlocks map[uint64]*big.Int) (map[uint64]map[common.Address]map[common.Address]*hexutil.Big, error) {
var ( var (
group = async.NewAtomicGroup(parent) group = async.NewAtomicGroup(parent)
mu sync.Mutex mu sync.Mutex
@ -769,14 +783,15 @@ func (tm *Manager) GetBalancesByChain(parent context.Context, clients map[uint64
mu.Unlock() mu.Unlock()
} }
for clientIdx := range clients { for _, client := range clients {
client := clients[clientIdx] ethScanContract, availableAtBlock, err := tm.contractMaker.NewEthScan(client.NetworkID())
ethScanContract, err := tm.contractMaker.NewEthScan(client.ChainID)
if err != nil { if err != nil {
log.Error("error scanning contract", "err", err) log.Error("error scanning contract", "err", err)
return nil, err return nil, err
} }
atBlock := atBlocks[client.NetworkID()]
fetchChainBalance := false fetchChainBalance := false
var tokenChunks [][]common.Address var tokenChunks [][]common.Address
chunkSize := 500 chunkSize := 500
@ -799,16 +814,17 @@ func (tm *Manager) GetBalancesByChain(parent context.Context, clients map[uint64
ctx, cancel := context.WithTimeout(parent, requestTimeout) ctx, cancel := context.WithTimeout(parent, requestTimeout)
defer cancel() defer cancel()
res, err := ethScanContract.EtherBalances(&bind.CallOpts{ res, err := ethScanContract.EtherBalances(&bind.CallOpts{
Context: ctx, Context: ctx,
BlockNumber: atBlock,
}, accounts) }, accounts)
if err != nil { if err != nil {
log.Error("can't fetch chain balance", err) log.Error("can't fetch chain balance 5", err)
return nil return nil
} }
for idx, account := range accounts { for idx, account := range accounts {
balance := new(big.Int) balance := new(big.Int)
balance.SetBytes(res[idx].Data) balance.SetBytes(res[idx].Data)
updateBalance(client.ChainID, account, common.HexToAddress("0x"), balance) updateBalance(client.NetworkID(), account, common.HexToAddress("0x"), balance)
} }
return nil return nil
@ -822,28 +838,46 @@ func (tm *Manager) GetBalancesByChain(parent context.Context, clients map[uint64
group.Add(func(parent context.Context) error { group.Add(func(parent context.Context) error {
ctx, cancel := context.WithTimeout(parent, requestTimeout) ctx, cancel := context.WithTimeout(parent, requestTimeout)
defer cancel() defer cancel()
res, err := ethScanContract.TokensBalance(&bind.CallOpts{ var res []ethscan.BalanceScannerResult
Context: ctx, if atBlock == nil || big.NewInt(int64(availableAtBlock)).Cmp(atBlock) < 0 {
}, account, chunk) res, err = ethScanContract.TokensBalance(&bind.CallOpts{
if err != nil { Context: ctx,
log.Error("can't fetch erc20 token balance", "account", account, "error", err) BlockNumber: atBlock,
return nil }, account, chunk)
} if err != nil {
log.Error("can't fetch erc20 token balance 6", "account", account, "error", err)
if len(res) != len(chunk) { return nil
log.Error("can't fetch erc20 token balance", "account", account, "error response not complete")
return nil
}
for idx, token := range chunk {
if !res[idx].Success {
continue
} }
balance := new(big.Int)
balance.SetBytes(res[idx].Data) if len(res) != len(chunk) {
updateBalance(client.ChainID, account, token, balance) log.Error("can't fetch erc20 token balance 7", "account", account, "error response not complete")
return nil
}
for idx, token := range chunk {
if !res[idx].Success {
continue
}
balance := new(big.Int)
balance.SetBytes(res[idx].Data)
updateBalance(client.NetworkID(), account, token, balance)
}
return nil
} }
for _, token := range chunk {
balance, err := tm.GetTokenBalanceAt(ctx, client, account, token, atBlock)
if err != nil {
if err != bind.ErrNoCode {
log.Error("can't fetch erc20 token balance 8", "account", account, "token", token, "error on fetching token balance")
return nil
}
}
updateBalance(client.NetworkID(), account, token, balance)
}
return nil return nil
}) })
} }

View File

@ -131,7 +131,7 @@ func upsertHopBridgeDestinationTx(ctx context.Context, transactionManager *Trans
return multiTx, nil return multiTx, nil
} }
func buildHopBridgeMultitransaction(ctx context.Context, client *chain.ClientWithFallback, transactionManager *TransactionManager, tokenManager *token.Manager, subTx *Transfer) (*MultiTransaction, error) { func buildHopBridgeMultitransaction(ctx context.Context, client chain.ClientInterface, transactionManager *TransactionManager, tokenManager *token.Manager, subTx *Transfer) (*MultiTransaction, error) {
// Identify if it's from/to transaction // Identify if it's from/to transaction
switch w_common.GetEventType(subTx.Log) { switch w_common.GetEventType(subTx.Log) {
case w_common.HopBridgeTransferSentToL2EventType: case w_common.HopBridgeTransferSentToL2EventType:

View File

@ -77,7 +77,7 @@ func (c *ethHistoricalCommand) Command() async.Command {
} }
func (c *ethHistoricalCommand) Run(ctx context.Context) (err error) { func (c *ethHistoricalCommand) Run(ctx context.Context) (err error) {
log.Info("eth historical downloader start", "chainID", c.chainClient.NetworkID(), "address", c.address, log.Debug("eth historical downloader start", "chainID", c.chainClient.NetworkID(), "address", c.address,
"from", c.from.Number, "to", c.to, "noLimit", c.noLimit) "from", c.from.Number, "to", c.to, "noLimit", c.noLimit)
start := time.Now() start := time.Now()
@ -101,7 +101,7 @@ func (c *ethHistoricalCommand) Run(ctx context.Context) (err error) {
c.resultingFrom = from c.resultingFrom = from
c.startBlock = startBlock c.startBlock = startBlock
log.Info("eth historical downloader finished successfully", "chain", c.chainClient.NetworkID(), log.Debug("eth historical downloader finished successfully", "chain", c.chainClient.NetworkID(),
"address", c.address, "from", from, "to", c.to, "total blocks", len(headers), "time", time.Since(start)) "address", c.address, "from", from, "to", c.to, "total blocks", len(headers), "time", time.Since(start))
return nil return nil
@ -147,7 +147,7 @@ func getErc20BatchSize(chainID uint64) *big.Int {
} }
func (c *erc20HistoricalCommand) Run(ctx context.Context) (err error) { func (c *erc20HistoricalCommand) Run(ctx context.Context) (err error) {
log.Info("wallet historical downloader for erc20 transfers start", "chainID", c.chainClient.NetworkID(), "address", c.address, log.Debug("wallet historical downloader for erc20 transfers start", "chainID", c.chainClient.NetworkID(), "address", c.address,
"from", c.from, "to", c.to) "from", c.from, "to", c.to)
start := time.Now() start := time.Now()
@ -168,7 +168,7 @@ func (c *erc20HistoricalCommand) Run(ctx context.Context) (err error) {
} }
c.foundHeaders = append(c.foundHeaders, headers...) c.foundHeaders = append(c.foundHeaders, headers...)
} }
log.Info("wallet historical downloader for erc20 transfers finished", "chainID", c.chainClient.NetworkID(), "address", c.address, log.Debug("wallet historical downloader for erc20 transfers finished", "chainID", c.chainClient.NetworkID(), "address", c.address,
"from", c.from, "to", c.to, "time", time.Since(start), "headers", len(c.foundHeaders)) "from", c.from, "to", c.to, "time", time.Since(start), "headers", len(c.foundHeaders))
return nil return nil
} }
@ -183,7 +183,7 @@ type controlCommand struct {
blockDAO *BlockDAO blockDAO *BlockDAO
eth *ETHDownloader eth *ETHDownloader
erc20 *ERC20TransfersDownloader erc20 *ERC20TransfersDownloader
chainClient *chain.ClientWithFallback chainClient chain.ClientInterface
feed *event.Feed feed *event.Feed
errorsCount int errorsCount int
nonArchivalRPCNode bool nonArchivalRPCNode bool
@ -365,7 +365,7 @@ type transfersCommand struct {
eth *ETHDownloader eth *ETHDownloader
blockNums []*big.Int blockNums []*big.Int
address common.Address address common.Address
chainClient *chain.ClientWithFallback chainClient chain.ClientInterface
blocksLimit int blocksLimit int
transactionManager *TransactionManager transactionManager *TransactionManager
pendingTxManager *transactions.PendingTxTracker pendingTxManager *transactions.PendingTxTracker
@ -625,7 +625,7 @@ type loadTransfersCommand struct {
accounts []common.Address accounts []common.Address
db *Database db *Database
blockDAO *BlockDAO blockDAO *BlockDAO
chainClient *chain.ClientWithFallback chainClient chain.ClientInterface
blocksByAddress map[common.Address][]*big.Int blocksByAddress map[common.Address][]*big.Int
transactionManager *TransactionManager transactionManager *TransactionManager
pendingTxManager *transactions.PendingTxTracker pendingTxManager *transactions.PendingTxTracker
@ -655,7 +655,7 @@ type findAndCheckBlockRangeCommand struct {
accounts []common.Address accounts []common.Address
db *Database db *Database
blockDAO *BlockDAO blockDAO *BlockDAO
chainClient *chain.ClientWithFallback chainClient chain.ClientInterface
balanceCacher balance.Cacher balanceCacher balance.Cacher
feed *event.Feed feed *event.Feed
fromByAddress map[common.Address]*Block fromByAddress map[common.Address]*Block
@ -818,11 +818,11 @@ func (c *findAndCheckBlockRangeCommand) fastIndexErc20(ctx context.Context, from
} }
func loadTransfers(ctx context.Context, accounts []common.Address, blockDAO *BlockDAO, db *Database, func loadTransfers(ctx context.Context, accounts []common.Address, blockDAO *BlockDAO, db *Database,
chainClient *chain.ClientWithFallback, blocksLimitPerAccount int, blocksByAddress map[common.Address][]*big.Int, chainClient chain.ClientInterface, blocksLimitPerAccount int, blocksByAddress map[common.Address][]*big.Int,
transactionManager *TransactionManager, pendingTxManager *transactions.PendingTxTracker, transactionManager *TransactionManager, pendingTxManager *transactions.PendingTxTracker,
tokenManager *token.Manager, feed *event.Feed) error { tokenManager *token.Manager, feed *event.Feed) error {
log.Info("loadTransfers start", "accounts", accounts, "chain", chainClient.ChainID, "limit", blocksLimitPerAccount) log.Info("loadTransfers start", "accounts", accounts, "chain", chainClient.NetworkID(), "limit", blocksLimitPerAccount)
start := time.Now() start := time.Now()
group := async.NewGroup(ctx) group := async.NewGroup(ctx)
@ -852,7 +852,7 @@ func loadTransfers(ctx context.Context, accounts []common.Address, blockDAO *Blo
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
case <-group.WaitAsync(): case <-group.WaitAsync():
log.Info("loadTransfers finished for account", "in", time.Since(start), "chain", chainClient.ChainID) log.Info("loadTransfers finished for account", "in", time.Since(start), "chain", chainClient.NetworkID())
return nil return nil
} }
} }
@ -871,10 +871,10 @@ func getLowestFrom(chainID uint64, to *big.Int) *big.Int {
} }
// Finds the latest range up to initialTo where the number of transactions is between 20 and 25 // Finds the latest range up to initialTo where the number of transactions is between 20 and 25
func findFirstRange(c context.Context, account common.Address, initialTo *big.Int, client *chain.ClientWithFallback) (*big.Int, error) { func findFirstRange(c context.Context, account common.Address, initialTo *big.Int, client chain.ClientInterface) (*big.Int, error) {
log.Info("findFirstRange", "account", account, "initialTo", initialTo, "client", client) log.Info("findFirstRange", "account", account, "initialTo", initialTo, "client", client)
from := getLowestFrom(client.ChainID, initialTo) from := getLowestFrom(client.NetworkID(), initialTo)
to := initialTo to := initialTo
goal := uint64(20) goal := uint64(20)
@ -930,7 +930,7 @@ func findFirstRange(c context.Context, account common.Address, initialTo *big.In
} }
// Finds the latest ranges up to initialTo where the number of transactions is between 20 and 25 // Finds the latest ranges up to initialTo where the number of transactions is between 20 and 25
func findFirstRanges(c context.Context, accounts []common.Address, initialTo *big.Int, client *chain.ClientWithFallback) (map[common.Address]*big.Int, error) { func findFirstRanges(c context.Context, accounts []common.Address, initialTo *big.Int, client chain.ClientInterface) (map[common.Address]*big.Int, error) {
res := map[common.Address]*big.Int{} res := map[common.Address]*big.Int{}
for _, address := range accounts { for _, address := range accounts {

View File

@ -66,22 +66,25 @@ func (c *findNewBlocksCommand) Run(parent context.Context) (err error) {
// TODO NewFindBlocksCommand // TODO NewFindBlocksCommand
type findBlocksCommand struct { type findBlocksCommand struct {
account common.Address account common.Address
db *Database db *Database
blockRangeDAO *BlockRangeSequentialDAO blockRangeDAO *BlockRangeSequentialDAO
chainClient chain.ClientInterface chainClient chain.ClientInterface
balanceCacher balance.Cacher balanceCacher balance.Cacher
feed *event.Feed feed *event.Feed
noLimit bool noLimit bool
transactionManager *TransactionManager transactionManager *TransactionManager
fromBlockNumber *big.Int tokenManager *token.Manager
toBlockNumber *big.Int fromBlockNumber *big.Int
blocksLoadedCh chan<- []*DBHeader toBlockNumber *big.Int
blocksLoadedCh chan<- []*DBHeader
defaultNodeBlockChunkSize int
// Not to be set by the caller // Not to be set by the caller
resFromBlock *Block resFromBlock *Block
startBlockNumber *big.Int startBlockNumber *big.Int
error error reachedETHHistoryStart bool
error error
} }
func (c *findBlocksCommand) Command() async.Command { func (c *findBlocksCommand) Command() async.Command {
@ -91,10 +94,114 @@ func (c *findBlocksCommand) Command() async.Command {
}.Run }.Run
} }
func (c *findBlocksCommand) Run(parent context.Context) (err error) { func (c *findBlocksCommand) ERC20ScanByBalance(parent context.Context, fromBlock, toBlock *big.Int, token common.Address) ([]*DBHeader, error) {
log.Debug("start findBlocksCommand", "account", c.account, "chain", c.chainClient.NetworkID(), "noLimit", c.noLimit) var err error
batchSize := getErc20BatchSize(c.chainClient.NetworkID())
ranges := [][]*big.Int{{fromBlock, toBlock}}
foundHeaders := []*DBHeader{}
cache := map[int64]*big.Int{}
for {
nextRanges := [][]*big.Int{}
for _, blockRange := range ranges {
from, to := blockRange[0], blockRange[1]
fromBalance, ok := cache[from.Int64()]
if !ok {
fromBalance, err = c.tokenManager.GetTokenBalanceAt(parent, c.chainClient, c.account, token, from)
if err != nil {
return nil, err
}
rangeSize := big.NewInt(DefaultNodeBlockChunkSize) if fromBalance == nil {
fromBalance = big.NewInt(0)
}
cache[from.Int64()] = fromBalance
}
toBalance, ok := cache[to.Int64()]
if !ok {
toBalance, err = c.tokenManager.GetTokenBalanceAt(parent, c.chainClient, c.account, token, to)
if err != nil {
return nil, err
}
if toBalance == nil {
toBalance = big.NewInt(0)
}
cache[to.Int64()] = toBalance
}
if fromBalance.Cmp(toBalance) != 0 {
diff := new(big.Int).Sub(to, from)
if diff.Cmp(batchSize) <= 0 {
headers, err := c.fastIndexErc20(parent, from, to)
if err != nil {
return nil, err
}
foundHeaders = append(foundHeaders, headers...)
continue
}
halfOfDiff := new(big.Int).Div(diff, big.NewInt(2))
mid := new(big.Int).Add(from, halfOfDiff)
nextRanges = append(nextRanges, []*big.Int{from, mid})
nextRanges = append(nextRanges, []*big.Int{mid, to})
}
}
if len(nextRanges) == 0 {
break
}
ranges = nextRanges
}
return foundHeaders, nil
}
func (c *findBlocksCommand) checkERC20Tail(parent context.Context) ([]*DBHeader, error) {
log.Debug("checkERC20Tail", "account", c.account, "to block", c.startBlockNumber, "from", c.resFromBlock.Number)
tokens, err := c.tokenManager.GetTokens(c.chainClient.NetworkID())
if err != nil {
return nil, err
}
addresses := make([]common.Address, len(tokens))
for i, token := range tokens {
addresses[i] = token.Address
}
from := new(big.Int).Sub(c.resFromBlock.Number, big.NewInt(1))
clients := make(map[uint64]chain.ClientInterface, 1)
clients[c.chainClient.NetworkID()] = c.chainClient
atBlocks := make(map[uint64]*big.Int, 1)
atBlocks[c.chainClient.NetworkID()] = from
balances, err := c.tokenManager.GetBalancesAtByChain(parent, clients, []common.Address{c.account}, addresses, atBlocks)
if err != nil {
return nil, err
}
headers := []*DBHeader{}
for token, balance := range balances[c.chainClient.NetworkID()][c.account] {
bigintBalance := big.NewInt(balance.ToInt().Int64())
if bigintBalance.Cmp(big.NewInt(0)) <= 0 {
continue
}
result, err := c.ERC20ScanByBalance(parent, big.NewInt(0), from, token)
if err != nil {
return nil, err
}
headers = append(headers, result...)
}
return headers, nil
}
func (c *findBlocksCommand) Run(parent context.Context) (err error) {
log.Debug("start findBlocksCommand", "account", c.account, "chain", c.chainClient.NetworkID(), "noLimit", c.noLimit, "from", c.fromBlockNumber, "to", c.toBlockNumber)
rangeSize := big.NewInt(int64(c.defaultNodeBlockChunkSize))
from, to := new(big.Int).Set(c.fromBlockNumber), new(big.Int).Set(c.toBlockNumber) from, to := new(big.Int).Set(c.fromBlockNumber), new(big.Int).Set(c.toBlockNumber)
@ -104,7 +211,18 @@ func (c *findBlocksCommand) Run(parent context.Context) (err error) {
} }
for { for {
headers, _ := c.checkRange(parent, from, to) var headers []*DBHeader
if c.reachedETHHistoryStart {
if c.fromBlockNumber.Cmp(zero) == 0 && c.startBlockNumber != nil && c.startBlockNumber.Cmp(zero) == 1 {
headers, err = c.checkERC20Tail(parent)
if err != nil {
c.error = err
}
}
} else {
headers, _ = c.checkRange(parent, from, to)
}
if c.error != nil { if c.error != nil {
log.Error("findBlocksCommand checkRange", "error", c.error, "account", c.account, log.Error("findBlocksCommand checkRange", "error", c.error, "account", c.account,
"chain", c.chainClient.NetworkID(), "from", from, "to", to) "chain", c.chainClient.NetworkID(), "from", from, "to", to)
@ -126,17 +244,32 @@ func (c *findBlocksCommand) Run(parent context.Context) (err error) {
c.blocksFound(headers) c.blocksFound(headers)
} }
if c.reachedETHHistoryStart {
break
}
err = c.upsertBlockRange(&BlockRange{c.startBlockNumber, c.resFromBlock.Number, to}) err = c.upsertBlockRange(&BlockRange{c.startBlockNumber, c.resFromBlock.Number, to})
if err != nil { if err != nil {
break break
} }
from, to = nextRange(c.resFromBlock.Number, c.fromBlockNumber) if from.Cmp(to) == 0 {
break
}
nextFrom, nextTo := nextRange(c.defaultNodeBlockChunkSize, c.resFromBlock.Number, c.fromBlockNumber)
if nextFrom.Cmp(from) == 0 && nextTo.Cmp(to) == 0 {
break
}
from = nextFrom
to = nextTo
if to.Cmp(c.fromBlockNumber) <= 0 || (c.startBlockNumber != nil && if to.Cmp(c.fromBlockNumber) <= 0 || (c.startBlockNumber != nil &&
c.startBlockNumber.Cmp(big.NewInt(0)) > 0 && to.Cmp(c.startBlockNumber) <= 0) { c.startBlockNumber.Cmp(big.NewInt(0)) > 0 && to.Cmp(c.startBlockNumber) <= 0) {
log.Debug("Checked all ranges, stop execution", "startBlock", c.startBlockNumber, "from", from, "to", to) log.Debug("Checked all ranges, stop execution", "startBlock", c.startBlockNumber, "from", from, "to", to)
break c.reachedETHHistoryStart = true
} }
} }
@ -319,18 +452,18 @@ func (c *findBlocksCommand) fastIndexErc20(ctx context.Context, fromBlockNumber
} }
func loadTransfersLoop(ctx context.Context, account common.Address, blockDAO *BlockDAO, db *Database, func loadTransfersLoop(ctx context.Context, account common.Address, blockDAO *BlockDAO, db *Database,
chainClient *chain.ClientWithFallback, transactionManager *TransactionManager, pendingTxManager *transactions.PendingTxTracker, chainClient chain.ClientInterface, transactionManager *TransactionManager, pendingTxManager *transactions.PendingTxTracker,
tokenManager *token.Manager, feed *event.Feed, blocksLoadedCh <-chan []*DBHeader) { tokenManager *token.Manager, feed *event.Feed, blocksLoadedCh <-chan []*DBHeader) {
log.Debug("loadTransfersLoop start", "chain", chainClient.ChainID, "account", account) log.Debug("loadTransfersLoop start", "chain", chainClient.NetworkID(), "account", account)
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
log.Info("loadTransfersLoop error", "chain", chainClient.ChainID, "account", account, "error", ctx.Err()) log.Info("loadTransfersLoop error", "chain", chainClient.NetworkID(), "account", account, "error", ctx.Err())
return return
case dbHeaders := <-blocksLoadedCh: case dbHeaders := <-blocksLoadedCh:
log.Debug("loadTransfersOnDemand transfers received", "chain", chainClient.ChainID, "account", account, "headers", len(dbHeaders)) log.Debug("loadTransfersOnDemand transfers received", "chain", chainClient.NetworkID(), "account", account, "headers", len(dbHeaders))
blockNums := make([]*big.Int, len(dbHeaders)) blockNums := make([]*big.Int, len(dbHeaders))
for i, dbHeader := range dbHeaders { for i, dbHeader := range dbHeaders {
@ -347,7 +480,7 @@ func loadTransfersLoop(ctx context.Context, account common.Address, blockDAO *Bl
} }
func newLoadBlocksAndTransfersCommand(account common.Address, db *Database, func newLoadBlocksAndTransfersCommand(account common.Address, db *Database,
blockDAO *BlockDAO, chainClient *chain.ClientWithFallback, feed *event.Feed, blockDAO *BlockDAO, chainClient chain.ClientInterface, feed *event.Feed,
transactionManager *TransactionManager, pendingTxManager *transactions.PendingTxTracker, transactionManager *TransactionManager, pendingTxManager *transactions.PendingTxTracker,
tokenManager *token.Manager, balanceCacher balance.Cacher) *loadBlocksAndTransfersCommand { tokenManager *token.Manager, balanceCacher balance.Cacher) *loadBlocksAndTransfersCommand {
@ -372,7 +505,7 @@ type loadBlocksAndTransfersCommand struct {
db *Database db *Database
blockRangeDAO *BlockRangeSequentialDAO blockRangeDAO *BlockRangeSequentialDAO
blockDAO *BlockDAO blockDAO *BlockDAO
chainClient *chain.ClientWithFallback chainClient chain.ClientInterface
feed *event.Feed feed *event.Feed
balanceCacher balance.Cacher balanceCacher balance.Cacher
errorsCount int errorsCount int
@ -455,17 +588,19 @@ func (c *loadBlocksAndTransfersCommand) fetchHistoryBlocks(ctx context.Context,
if !allHistoryLoaded { if !allHistoryLoaded {
fbc := &findBlocksCommand{ fbc := &findBlocksCommand{
account: c.account, account: c.account,
db: c.db, db: c.db,
blockRangeDAO: c.blockRangeDAO, blockRangeDAO: c.blockRangeDAO,
chainClient: c.chainClient, chainClient: c.chainClient,
balanceCacher: c.balanceCacher, balanceCacher: c.balanceCacher,
feed: c.feed, feed: c.feed,
noLimit: false, noLimit: false,
fromBlockNumber: big.NewInt(0), fromBlockNumber: big.NewInt(0),
toBlockNumber: to, toBlockNumber: to,
transactionManager: c.transactionManager, transactionManager: c.transactionManager,
blocksLoadedCh: blocksLoadedCh, tokenManager: c.tokenManager,
blocksLoadedCh: blocksLoadedCh,
defaultNodeBlockChunkSize: DefaultNodeBlockChunkSize,
} }
group.Add(fbc.Command()) group.Add(fbc.Command())
} else { } else {
@ -501,6 +636,7 @@ func (c *loadBlocksAndTransfersCommand) startFetchingNewBlocks(group *async.Grou
feed: c.feed, feed: c.feed,
noLimit: false, noLimit: false,
transactionManager: c.transactionManager, transactionManager: c.transactionManager,
tokenManager: c.tokenManager,
blocksLoadedCh: blocksLoadedCh, blocksLoadedCh: blocksLoadedCh,
}, },
} }
@ -583,15 +719,14 @@ func getHeadBlockNumber(parent context.Context, chainClient chain.ClientInterfac
return head.Number, err return head.Number, err
} }
func nextRange(from *big.Int, zeroBlockNumber *big.Int) (*big.Int, *big.Int) { func nextRange(maxRangeSize int, prevFrom, zeroBlockNumber *big.Int) (*big.Int, *big.Int) {
log.Debug("next range start", "from", from, "zeroBlockNumber", zeroBlockNumber) log.Debug("next range start", "from", prevFrom, "zeroBlockNumber", zeroBlockNumber)
rangeSize := big.NewInt(DefaultNodeBlockChunkSize) rangeSize := big.NewInt(int64(maxRangeSize))
to := new(big.Int).Sub(from, big.NewInt(1)) // it won't hit the cache, but we wont load the transfers twice to := big.NewInt(0).Set(prevFrom)
if to.Cmp(rangeSize) > 0 { from := big.NewInt(0).Sub(to, rangeSize)
from.Sub(to, rangeSize) if from.Cmp(zeroBlockNumber) < 0 {
} else {
from = new(big.Int).Set(zeroBlockNumber) from = new(big.Int).Set(zeroBlockNumber)
} }

View File

@ -3,60 +3,243 @@ package transfer
import ( import (
"context" "context"
"math/big" "math/big"
"sort"
"strings"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/contracts/ethscan"
"github.com/status-im/status-go/contracts/ierc20"
"github.com/status-im/status-go/rpc/chain" "github.com/status-im/status-go/rpc/chain"
"github.com/status-im/status-go/services/wallet/async" "github.com/status-im/status-go/services/wallet/async"
"github.com/status-im/status-go/services/wallet/balance" "github.com/status-im/status-go/services/wallet/balance"
"github.com/status-im/status-go/t/helpers" "github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/params"
statusRpc "github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/rpc/network"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/walletdatabase" "github.com/status-im/status-go/walletdatabase"
) )
type TestClient struct { type TestClient struct {
t *testing.T t *testing.T
// [][block, newBalance, nonceDiff] // [][block, newBalance, nonceDiff]
balances [][]int balances [][]int
balanceHistory map[uint64]*big.Int outgoingERC20Transfers []testERC20Transfer
nonceHistory map[uint64]uint64 incomingERC20Transfers []testERC20Transfer
balanceHistory map[uint64]*big.Int
tokenBalanceHistory map[common.Address]map[uint64]*big.Int
nonceHistory map[uint64]uint64
traceAPICalls bool
printPreparedData bool
} }
func (tc TestClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { func (tc TestClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
tc.t.Log("BatchCallContext") if tc.traceAPICalls {
tc.t.Log("BatchCallContext")
}
return nil return nil
} }
func (tc TestClient) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { func (tc TestClient) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
tc.t.Log("HeaderByHash") if tc.traceAPICalls {
tc.t.Log("HeaderByHash")
}
return nil, nil return nil, nil
} }
func (tc TestClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { func (tc TestClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
tc.t.Log("BlockByHash") if tc.traceAPICalls {
tc.t.Log("BlockByHash")
}
return nil, nil return nil, nil
} }
func (tc TestClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { func (tc TestClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
tc.t.Log("BlockByNumber") if tc.traceAPICalls {
tc.t.Log("BlockByNumber")
}
return nil, nil return nil, nil
} }
func (tc TestClient) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { func (tc TestClient) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
nonce := tc.nonceHistory[blockNumber.Uint64()] nonce := tc.nonceHistory[blockNumber.Uint64()]
if tc.traceAPICalls {
tc.t.Log("NonceAt", blockNumber, "result:", nonce) tc.t.Log("NonceAt", blockNumber, "result:", nonce)
}
return nonce, nil return nonce, nil
} }
func (tc TestClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { func (tc TestClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
tc.t.Log("FilterLogs") if tc.traceAPICalls {
tc.t.Log("FilterLogs")
}
//checking only ERC20 for now
incomingAddress := q.Topics[len(q.Topics)-1]
allTransfers := tc.incomingERC20Transfers
if len(incomingAddress) == 0 {
allTransfers = tc.outgoingERC20Transfers
}
logs := []types.Log{}
for _, transfer := range allTransfers {
if transfer.block.Cmp(q.FromBlock) >= 0 && transfer.block.Cmp(q.ToBlock) <= 0 {
logs = append(logs, types.Log{
BlockNumber: transfer.block.Uint64(),
BlockHash: common.BigToHash(transfer.block),
})
}
}
return logs, nil
}
func (tc TestClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
balance := tc.balanceHistory[blockNumber.Uint64()]
if tc.traceAPICalls {
tc.t.Log("BalanceAt", blockNumber, "result:", balance)
}
return balance, nil
}
func (tc TestClient) tokenBalanceAt(token common.Address, blockNumber *big.Int) *big.Int {
balance := tc.tokenBalanceHistory[token][blockNumber.Uint64()]
if tc.traceAPICalls {
tc.t.Log("tokenBalanceAt", token, blockNumber, "result:", balance)
}
return balance
}
func (tc *TestClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
if tc.traceAPICalls {
tc.t.Log("HeaderByNumber", number)
}
header := &types.Header{
Number: number,
Time: 0,
}
return header, nil
}
func (tc TestClient) FullTransactionByBlockNumberAndIndex(ctx context.Context, blockNumber *big.Int, index uint) (*chain.FullTransaction, error) {
if tc.traceAPICalls {
tc.t.Log("FullTransactionByBlockNumberAndIndex")
}
blockHash := common.BigToHash(blockNumber)
tx := &chain.FullTransaction{
Tx: &types.Transaction{},
TxExtraInfo: chain.TxExtraInfo{
BlockNumber: (*hexutil.Big)(big.NewInt(0)),
BlockHash: &blockHash,
},
}
return tx, nil
}
func (tc TestClient) GetBaseFeeFromBlock(blockNumber *big.Int) (string, error) {
if tc.traceAPICalls {
tc.t.Log("GetBaseFeeFromBloc")
}
return "", nil
}
func (tc TestClient) NetworkID() uint64 {
return 777333
}
func (tc TestClient) ToBigInt() *big.Int {
if tc.traceAPICalls {
tc.t.Log("ToBigInt")
}
return nil
}
var ethscanAddress = common.HexToAddress("0x0000000000000000000000000000000000777333")
func (tc TestClient) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
if tc.traceAPICalls {
tc.t.Log("CodeAt", contract, blockNumber)
}
if ethscanAddress == contract {
return []byte{1}, nil
}
return nil, nil
}
func (tc TestClient) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
if tc.traceAPICalls {
tc.t.Log("CallContract", call, blockNumber, call.To)
}
if *call.To == ethscanAddress {
parsed, err := abi.JSON(strings.NewReader(ethscan.BalanceScannerABI))
if err != nil {
return nil, err
}
method := parsed.Methods["tokensBalance"]
params := call.Data[len(method.ID):]
args, err := method.Inputs.Unpack(params)
if err != nil {
tc.t.Log("ERROR on unpacking", err)
return nil, err
}
tokens := args[1].([]common.Address)
balances := []*big.Int{}
for _, token := range tokens {
balances = append(balances, tc.tokenBalanceAt(token, blockNumber))
}
results := []ethscan.BalanceScannerResult{}
for _, balance := range balances {
results = append(results, ethscan.BalanceScannerResult{
Success: true,
Data: balance.Bytes(),
})
}
output, err := method.Outputs.Pack(results)
if err != nil {
tc.t.Log("ERROR on packing", err)
return nil, err
}
return output, nil
}
if *call.To == tokenTXXAddress {
balance := tc.tokenBalanceAt(tokenTXXAddress, blockNumber)
parsed, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
if err != nil {
return nil, err
}
method := parsed.Methods["balanceOf"]
output, err := method.Outputs.Pack(balance)
if err != nil {
tc.t.Log("ERROR on packing ERC20 balance", err)
return nil, err
}
return output, nil
}
return nil, nil return nil, nil
} }
@ -82,66 +265,233 @@ func (tc *TestClient) prepareBalanceHistory(toBlock int) {
currentNonce += change[2] currentNonce += change[2]
} }
tc.t.Log("=========================================") if tc.printPreparedData {
tc.t.Log(tc.balanceHistory) tc.t.Log("========================================= ETH BALANCES")
tc.t.Log(tc.nonceHistory) tc.t.Log(tc.balanceHistory)
tc.t.Log("=========================================") tc.t.Log(tc.nonceHistory)
tc.t.Log(tc.tokenBalanceHistory)
tc.t.Log("=========================================")
}
} }
func (tc TestClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { func (tc *TestClient) prepareTokenBalanceHistory(toBlock int) {
balance := tc.balanceHistory[blockNumber.Uint64()] transfersPerToken := map[common.Address][]testERC20Transfer{}
for _, transfer := range tc.outgoingERC20Transfers {
tc.t.Log("BalanceAt", blockNumber, "result:", balance) transfer.amount = new(big.Int).Neg(transfer.amount)
return balance, nil transfersPerToken[transfer.address] = append(transfersPerToken[transfer.address], transfer)
}
func (tc *TestClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
tc.t.Log("HeaderByNumber", number)
header := &types.Header{
Number: number,
Time: 0,
} }
return header, nil for _, transfer := range tc.incomingERC20Transfers {
} transfersPerToken[transfer.address] = append(transfersPerToken[transfer.address], transfer)
func (tc TestClient) FullTransactionByBlockNumberAndIndex(ctx context.Context, blockNumber *big.Int, index uint) (*chain.FullTransaction, error) {
tc.t.Log("FullTransactionByBlockNumberAndIndex")
blockHash := common.BigToHash(blockNumber)
tx := &chain.FullTransaction{
Tx: &types.Transaction{},
TxExtraInfo: chain.TxExtraInfo{
BlockNumber: (*hexutil.Big)(big.NewInt(0)),
BlockHash: &blockHash,
},
} }
return tx, nil tc.tokenBalanceHistory = map[common.Address]map[uint64]*big.Int{}
for token, transfers := range transfersPerToken {
sort.Slice(transfers, func(i, j int) bool {
return transfers[i].block.Cmp(transfers[j].block) < 0
})
currentBlock := uint64(0)
currentBalance := big.NewInt(0)
tc.tokenBalanceHistory[token] = map[uint64]*big.Int{}
transfers = append(transfers, testERC20Transfer{big.NewInt(int64(toBlock + 1)), token, big.NewInt(0)})
for _, transfer := range transfers {
for blockN := currentBlock; blockN < transfer.block.Uint64(); blockN++ {
tc.tokenBalanceHistory[token][blockN] = new(big.Int).Set(currentBalance)
}
currentBlock = transfer.block.Uint64()
currentBalance = new(big.Int).Add(currentBalance, transfer.amount)
}
}
if tc.printPreparedData {
tc.t.Log("========================================= ERC20 BALANCES")
tc.t.Log(tc.tokenBalanceHistory)
tc.t.Log("=========================================")
}
} }
func (tc TestClient) GetBaseFeeFromBlock(blockNumber *big.Int) (string, error) { func (tc TestClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
tc.t.Log("GetBaseFeeFromBloc") if tc.traceAPICalls {
return "", nil tc.t.Log("CallContext")
} }
func (tc TestClient) NetworkID() uint64 {
return 1
}
func (tc TestClient) ToBigInt() *big.Int {
tc.t.Log("ToBigInt")
return nil return nil
} }
type findBlockCase struct { func (tc TestClient) GetWalletNotifier() func(chainId uint64, message string) {
balanceChanges [][]int if tc.traceAPICalls {
fromBlock int64 tc.t.Log("GetWalletNotifier")
toBlock int64 }
expectedBlocksFound int return nil
} }
var findBlocksCommandCases = []findBlockCase{ func (tc TestClient) SetWalletNotifier(notifier func(chainId uint64, message string)) {
{ if tc.traceAPICalls {
tc.t.Log("SetWalletNotifier")
}
}
func (tc TestClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
if tc.traceAPICalls {
tc.t.Log("EstimateGas")
}
return 0, nil
}
func (tc TestClient) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
if tc.traceAPICalls {
tc.t.Log("PendingCodeAt")
}
return nil, nil
}
func (tc TestClient) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
if tc.traceAPICalls {
tc.t.Log("PendingCallContract")
}
return nil, nil
}
func (tc TestClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
if tc.traceAPICalls {
tc.t.Log("PendingNonceAt")
}
return 0, nil
}
func (tc TestClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
if tc.traceAPICalls {
tc.t.Log("SuggestGasPrice")
}
return nil, nil
}
func (tc TestClient) SendTransaction(ctx context.Context, tx *types.Transaction) error {
if tc.traceAPICalls {
tc.t.Log("SendTransaction")
}
return nil
}
func (tc TestClient) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
if tc.traceAPICalls {
tc.t.Log("SuggestGasTipCap")
}
return nil, nil
}
func (tc TestClient) BatchCallContextIgnoringLocalHandlers(ctx context.Context, b []rpc.BatchElem) error {
if tc.traceAPICalls {
tc.t.Log("BatchCallContextIgnoringLocalHandlers")
}
return nil
}
func (tc TestClient) CallContextIgnoringLocalHandlers(ctx context.Context, result interface{}, method string, args ...interface{}) error {
if tc.traceAPICalls {
tc.t.Log("CallContextIgnoringLocalHandlers")
}
return nil
}
func (tc TestClient) CallRaw(data string) string {
if tc.traceAPICalls {
tc.t.Log("CallRaw")
}
return ""
}
func (tc TestClient) GetChainID() *big.Int {
return big.NewInt(1)
}
func (tc TestClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
if tc.traceAPICalls {
tc.t.Log("SubscribeFilterLogs")
}
return nil, nil
}
func (tc TestClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
if tc.traceAPICalls {
tc.t.Log("TransactionReceipt")
}
return nil, nil
}
func (tc TestClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
if tc.traceAPICalls {
tc.t.Log("TransactionByHash")
}
return nil, false, nil
}
func (tc TestClient) BlockNumber(ctx context.Context) (uint64, error) {
if tc.traceAPICalls {
tc.t.Log("BlockNumber")
}
return 0, nil
}
func (tc TestClient) SetIsConnected(value bool) {
if tc.traceAPICalls {
tc.t.Log("SetIsConnected")
}
}
func (tc TestClient) GetIsConnected() bool {
if tc.traceAPICalls {
tc.t.Log("GetIsConnected")
}
return true
}
type testERC20Transfer struct {
block *big.Int
address common.Address
amount *big.Int
}
type findBlockCase struct {
balanceChanges [][]int
ERC20BalanceChanges [][]int
fromBlock int64
toBlock int64
rangeSize int
expectedBlocksFound int
outgoingERC20Transfers []testERC20Transfer
incomingERC20Transfers []testERC20Transfer
label string
}
func transferInEachBlock() [][]int {
res := [][]int{}
for i := 1; i < 101; i++ {
res = append(res, []int{i, i, i})
}
return res
}
func getCases() []findBlockCase {
cases := []findBlockCase{}
case1 := findBlockCase{
balanceChanges: [][]int{ balanceChanges: [][]int{
{5, 1, 0}, {5, 1, 0},
{20, 2, 0}, {20, 2, 0},
@ -149,19 +499,102 @@ var findBlocksCommandCases = []findBlockCase{
{46, 50, 0}, {46, 50, 0},
{75, 0, 1}, {75, 0, 1},
}, },
outgoingERC20Transfers: []testERC20Transfer{
{big.NewInt(6), tokenTXXAddress, big.NewInt(1)},
},
toBlock: 100,
expectedBlocksFound: 6,
}
case100transfers := findBlockCase{
balanceChanges: transferInEachBlock(),
toBlock: 100,
expectedBlocksFound: 100,
}
case3 := findBlockCase{
balanceChanges: [][]int{
{1, 1, 1},
{2, 2, 2},
{45, 1, 1},
{46, 50, 0},
{75, 0, 1},
},
toBlock: 100, toBlock: 100,
expectedBlocksFound: 5, expectedBlocksFound: 5,
}, }
{ case4 := findBlockCase{
balanceChanges: [][]int{
{20, 1, 0},
},
toBlock: 100,
fromBlock: 10,
expectedBlocksFound: 1,
label: "single block",
}
case5 := findBlockCase{
balanceChanges: [][]int{}, balanceChanges: [][]int{},
toBlock: 100, toBlock: 100,
fromBlock: 20,
expectedBlocksFound: 0, expectedBlocksFound: 0,
}, }
case6 := findBlockCase{
balanceChanges: [][]int{
{20, 1, 0},
{45, 1, 1},
},
toBlock: 100,
fromBlock: 30,
expectedBlocksFound: 1,
rangeSize: 20,
label: "single block in range",
}
case7emptyHistoryWithOneERC20Transfer := findBlockCase{
balanceChanges: [][]int{},
toBlock: 100,
rangeSize: 20,
expectedBlocksFound: 1,
incomingERC20Transfers: []testERC20Transfer{
{big.NewInt(6), tokenTXXAddress, big.NewInt(1)},
},
}
case8emptyHistoryWithERC20Transfers := findBlockCase{
balanceChanges: [][]int{},
toBlock: 100,
rangeSize: 20,
expectedBlocksFound: 2,
incomingERC20Transfers: []testERC20Transfer{
// edge case when a regular scan will find transfer at 80,
// but erc20 tail scan should only find transfer at block 6
{big.NewInt(80), tokenTXXAddress, big.NewInt(1)},
{big.NewInt(6), tokenTXXAddress, big.NewInt(1)},
},
}
cases = append(cases, case1)
cases = append(cases, case100transfers)
cases = append(cases, case3)
cases = append(cases, case4)
cases = append(cases, case5)
cases = append(cases, case6)
cases = append(cases, case7emptyHistoryWithOneERC20Transfer)
cases = append(cases, case8emptyHistoryWithERC20Transfers)
//cases = append([]findBlockCase{}, case8emptyHistoryWithERC20Transfers)
return cases
} }
func TestFindBlocksCommand(t *testing.T) { var tokenTXXAddress = common.HexToAddress("0x53211")
for _, testCase := range findBlocksCommandCases {
func TestFindBlocksCommand(t *testing.T) {
for idx, testCase := range getCases() {
t.Log("case #", idx)
ctx := context.Background() ctx := context.Background()
group := async.NewGroup(ctx) group := async.NewGroup(ctx)
@ -171,32 +604,71 @@ func TestFindBlocksCommand(t *testing.T) {
wdb := NewDB(db) wdb := NewDB(db)
tc := &TestClient{ tc := &TestClient{
t: t, t: t,
balances: testCase.balanceChanges, balances: testCase.balanceChanges,
outgoingERC20Transfers: testCase.outgoingERC20Transfers,
incomingERC20Transfers: testCase.incomingERC20Transfers,
} }
//tc.traceAPICalls = true
//tc.printPreparedData = true
tc.prepareBalanceHistory(100) tc.prepareBalanceHistory(100)
tc.prepareTokenBalanceHistory(100)
blockChannel := make(chan []*DBHeader, 100) blockChannel := make(chan []*DBHeader, 100)
rangeSize := 20
if testCase.rangeSize != 0 {
rangeSize = testCase.rangeSize
}
client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db)
client.SetClient(tc.NetworkID(), tc)
tokenManager := token.NewTokenManager(db, client, network.NewManager(db))
tokenManager.SetTokens([]*token.Token{
{
Address: tokenTXXAddress,
Symbol: "TXX",
Decimals: 18,
ChainID: tc.NetworkID(),
Name: "Test Token 1",
Verified: true,
},
})
fbc := &findBlocksCommand{ fbc := &findBlocksCommand{
account: common.HexToAddress("0x1234"), account: common.HexToAddress("0x12345"),
db: wdb, db: wdb,
blockRangeDAO: &BlockRangeSequentialDAO{wdb.client}, blockRangeDAO: &BlockRangeSequentialDAO{wdb.client},
chainClient: tc, chainClient: tc,
balanceCacher: balance.NewCache(), balanceCacher: balance.NewCache(),
feed: &event.Feed{}, feed: &event.Feed{},
noLimit: false, noLimit: false,
fromBlockNumber: big.NewInt(testCase.fromBlock), fromBlockNumber: big.NewInt(testCase.fromBlock),
toBlockNumber: big.NewInt(testCase.toBlock), toBlockNumber: big.NewInt(testCase.toBlock),
transactionManager: tm, transactionManager: tm,
blocksLoadedCh: blockChannel, blocksLoadedCh: blockChannel,
defaultNodeBlockChunkSize: rangeSize,
tokenManager: tokenManager,
} }
group.Add(fbc.Command()) group.Add(fbc.Command())
foundBlocks := []*DBHeader{}
select { select {
case <-ctx.Done(): case <-ctx.Done():
t.Log("ERROR") t.Log("ERROR")
case <-group.WaitAsync(): case <-group.WaitAsync():
close(blockChannel) close(blockChannel)
require.Equal(t, testCase.expectedBlocksFound, len(<-blockChannel)) for {
bloks, ok := <-blockChannel
if !ok {
break
}
foundBlocks = append(foundBlocks, bloks...)
}
numbers := []int64{}
for _, block := range foundBlocks {
numbers = append(numbers, block.Number.Int64())
}
sort.Slice(numbers, func(i, j int) bool { return numbers[i] < numbers[j] })
require.Equal(t, testCase.expectedBlocksFound, len(foundBlocks), testCase.label, "found blocks", numbers)
} }
} }
} }

View File

@ -114,7 +114,7 @@ func (c *Controller) CheckRecentHistory(chainIDs []uint64, accounts []common.Add
// watchAccountsChanges subscribes to a feed and watches for changes in accounts list. If there are new or removed accounts // watchAccountsChanges subscribes to a feed and watches for changes in accounts list. If there are new or removed accounts
// reactor will be restarted. // reactor will be restarted.
func watchAccountsChanges(ctx context.Context, accountFeed *event.Feed, reactor *Reactor, func watchAccountsChanges(ctx context.Context, accountFeed *event.Feed, reactor *Reactor,
chainClients map[uint64]*chain.ClientWithFallback, initial []common.Address, loadAllTransfers bool) error { chainClients map[uint64]chain.ClientInterface, initial []common.Address, loadAllTransfers bool) error {
ch := make(chan accountsevent.Event, 1) // it may block if the rate of updates will be significantly higher ch := make(chan accountsevent.Event, 1) // it may block if the rate of updates will be significantly higher
sub := accountFeed.Subscribe(ch) sub := accountFeed.Subscribe(ch)

View File

@ -74,7 +74,7 @@ type Transfer struct {
// ETHDownloader downloads regular eth transfers. // ETHDownloader downloads regular eth transfers.
type ETHDownloader struct { type ETHDownloader struct {
chainClient *chain.ClientWithFallback chainClient chain.ClientInterface
accounts []common.Address accounts []common.Address
signer types.Signer signer types.Signer
db *Database db *Database
@ -95,7 +95,7 @@ func (d *ETHDownloader) GetTransfersByNumber(ctx context.Context, number *big.In
} }
// Only used by status-mobile // Only used by status-mobile
func getTransferByHash(ctx context.Context, client *chain.ClientWithFallback, signer types.Signer, address common.Address, hash common.Hash) (*Transfer, error) { func getTransferByHash(ctx context.Context, client chain.ClientInterface, signer types.Signer, address common.Address, hash common.Hash) (*Transfer, error) {
transaction, _, err := client.TransactionByHash(ctx, hash) transaction, _, err := client.TransactionByHash(ctx, hash)
if err != nil { if err != nil {
return nil, err return nil, err
@ -143,7 +143,7 @@ func (d *ETHDownloader) getTransfersInBlock(ctx context.Context, blk *types.Bloc
for _, address := range accounts { for _, address := range accounts {
// During block discovery, we should have populated the DB with 1 item per Transaction containing // During block discovery, we should have populated the DB with 1 item per Transaction containing
// erc20/erc721 transfers // erc20/erc721 transfers
transactionsToLoad, err := d.db.GetTransactionsToLoad(d.chainClient.ChainID, address, blk.Number()) transactionsToLoad, err := d.db.GetTransactionsToLoad(d.chainClient.NetworkID(), address, blk.Number())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -162,7 +162,7 @@ func (d *ETHDownloader) getTransfersInBlock(ctx context.Context, blk *types.Bloc
for _, tx := range blk.Transactions() { for _, tx := range blk.Transactions() {
if tx.ChainId().Cmp(big.NewInt(0)) != 0 && tx.ChainId().Cmp(d.chainClient.ToBigInt()) != 0 { if tx.ChainId().Cmp(big.NewInt(0)) != 0 && tx.ChainId().Cmp(d.chainClient.ToBigInt()) != 0 {
log.Info("chain id mismatch", "tx hash", tx.Hash(), "tx chain id", tx.ChainId(), "expected chain id", d.chainClient.ChainID) log.Info("chain id mismatch", "tx hash", tx.Hash(), "tx chain id", tx.ChainId(), "expected chain id", d.chainClient.NetworkID())
continue continue
} }
from, err := types.Sender(d.signer, tx) from, err := types.Sender(d.signer, tx)

View File

@ -18,7 +18,7 @@ func SetupIterativeDownloader(
return nil, errors.New("to or from cannot be nil") return nil, errors.New("to or from cannot be nil")
} }
log.Info("iterative downloader", "address", address, "from", from, "to", to, "size", size) log.Debug("iterative downloader", "address", address, "from", from, "to", to, "size", size)
d := &IterativeDownloader{ d := &IterativeDownloader{
client: client, client: client,
batchSize: size, batchSize: size,
@ -65,7 +65,7 @@ func (d *IterativeDownloader) Next(parent context.Context) ([]*DBHeader, *big.In
from = d.from from = d.from
} }
headers, err := d.downloader.GetHeadersInRange(parent, from, to) headers, err := d.downloader.GetHeadersInRange(parent, from, to)
log.Info("load erc20 transfers in range", "from", from, "to", to, "batchSize", d.batchSize) log.Debug("load erc20 transfers in range", "from", from, "to", to, "batchSize", d.batchSize)
if err != nil { if err != nil {
log.Error("failed to get transfer in between two blocks", "from", from, "to", to, "error", err) log.Error("failed to get transfer in between two blocks", "from", from, "to", to, "error", err)
return nil, nil, nil, err return nil, nil, nil, err

View File

@ -57,7 +57,7 @@ func NewOnDemandFetchStrategy(
transactionManager *TransactionManager, transactionManager *TransactionManager,
pendingTxManager *transactions.PendingTxTracker, pendingTxManager *transactions.PendingTxTracker,
tokenManager *token.Manager, tokenManager *token.Manager,
chainClients map[uint64]*chain.ClientWithFallback, chainClients map[uint64]chain.ClientInterface,
accounts []common.Address, accounts []common.Address,
balanceCacher balance.Cacher, balanceCacher balance.Cacher,
) *OnDemandFetchStrategy { ) *OnDemandFetchStrategy {
@ -86,11 +86,11 @@ type OnDemandFetchStrategy struct {
transactionManager *TransactionManager transactionManager *TransactionManager
pendingTxManager *transactions.PendingTxTracker pendingTxManager *transactions.PendingTxTracker
tokenManager *token.Manager tokenManager *token.Manager
chainClients map[uint64]*chain.ClientWithFallback chainClients map[uint64]chain.ClientInterface
accounts []common.Address accounts []common.Address
} }
func (s *OnDemandFetchStrategy) newControlCommand(chainClient *chain.ClientWithFallback, accounts []common.Address) *controlCommand { func (s *OnDemandFetchStrategy) newControlCommand(chainClient chain.ClientInterface, accounts []common.Address) *controlCommand {
signer := types.LatestSignerForChainID(chainClient.ToBigInt()) signer := types.LatestSignerForChainID(chainClient.ToBigInt())
ctl := &controlCommand{ ctl := &controlCommand{
db: s.db, db: s.db,
@ -277,7 +277,7 @@ func NewReactor(db *Database, blockDAO *BlockDAO, feed *event.Feed, tm *Transact
} }
// Start runs reactor loop in background. // Start runs reactor loop in background.
func (r *Reactor) start(chainClients map[uint64]*chain.ClientWithFallback, accounts []common.Address, func (r *Reactor) start(chainClients map[uint64]chain.ClientInterface, accounts []common.Address,
loadAllTransfers bool) error { loadAllTransfers bool) error {
r.strategy = r.createFetchStrategy(chainClients, accounts, loadAllTransfers) r.strategy = r.createFetchStrategy(chainClients, accounts, loadAllTransfers)
@ -291,14 +291,14 @@ func (r *Reactor) stop() {
} }
} }
func (r *Reactor) restart(chainClients map[uint64]*chain.ClientWithFallback, accounts []common.Address, func (r *Reactor) restart(chainClients map[uint64]chain.ClientInterface, accounts []common.Address,
loadAllTransfers bool) error { loadAllTransfers bool) error {
r.stop() r.stop()
return r.start(chainClients, accounts, loadAllTransfers) return r.start(chainClients, accounts, loadAllTransfers)
} }
func (r *Reactor) createFetchStrategy(chainClients map[uint64]*chain.ClientWithFallback, func (r *Reactor) createFetchStrategy(chainClients map[uint64]chain.ClientInterface,
accounts []common.Address, loadAllTransfers bool) HistoryFetcher { accounts []common.Address, loadAllTransfers bool) HistoryFetcher {
if loadAllTransfers { if loadAllTransfers {
@ -328,9 +328,9 @@ func (r *Reactor) getTransfersByAddress(ctx context.Context, chainID uint64, add
return nil, errors.New(ReactorNotStarted) return nil, errors.New(ReactorNotStarted)
} }
func getChainClientByID(clients map[uint64]*chain.ClientWithFallback, id uint64) (*chain.ClientWithFallback, error) { func getChainClientByID(clients map[uint64]chain.ClientInterface, id uint64) (chain.ClientInterface, error) {
for _, client := range clients { for _, client := range clients {
if client.ChainID == id { if client.NetworkID() == id {
return client, nil return client, nil
} }
} }

View File

@ -19,7 +19,7 @@ import (
func NewSequentialFetchStrategy(db *Database, blockDAO *BlockDAO, feed *event.Feed, func NewSequentialFetchStrategy(db *Database, blockDAO *BlockDAO, feed *event.Feed,
transactionManager *TransactionManager, pendingTxManager *transactions.PendingTxTracker, transactionManager *TransactionManager, pendingTxManager *transactions.PendingTxTracker,
tokenManager *token.Manager, tokenManager *token.Manager,
chainClients map[uint64]*chain.ClientWithFallback, chainClients map[uint64]chain.ClientInterface,
accounts []common.Address, accounts []common.Address,
balanceCacher balance.Cacher, balanceCacher balance.Cacher,
) *SequentialFetchStrategy { ) *SequentialFetchStrategy {
@ -46,12 +46,12 @@ type SequentialFetchStrategy struct {
transactionManager *TransactionManager transactionManager *TransactionManager
pendingTxManager *transactions.PendingTxTracker pendingTxManager *transactions.PendingTxTracker
tokenManager *token.Manager tokenManager *token.Manager
chainClients map[uint64]*chain.ClientWithFallback chainClients map[uint64]chain.ClientInterface
accounts []common.Address accounts []common.Address
balanceCacher balance.Cacher balanceCacher balance.Cacher
} }
func (s *SequentialFetchStrategy) newCommand(chainClient *chain.ClientWithFallback, func (s *SequentialFetchStrategy) newCommand(chainClient chain.ClientInterface,
account common.Address) async.Commander { account common.Address) async.Commander {
return newLoadBlocksAndTransfersCommand(account, s.db, s.blockDAO, chainClient, s.feed, return newLoadBlocksAndTransfersCommand(account, s.db, s.blockDAO, chainClient, s.feed,

View File

@ -20,7 +20,7 @@ import (
const ETHSymbol string = "ETH" const ETHSymbol string = "ETH"
const WETHSymbol string = "WETH" const WETHSymbol string = "WETH"
func fetchUniswapV2PairInfo(ctx context.Context, client *chain.ClientWithFallback, pairAddress common.Address) (*common.Address, *common.Address, error) { func fetchUniswapV2PairInfo(ctx context.Context, client chain.ClientInterface, pairAddress common.Address) (*common.Address, *common.Address, error) {
caller, err := uniswapv2.NewUniswapv2Caller(pairAddress, client) caller, err := uniswapv2.NewUniswapv2Caller(pairAddress, client)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -43,7 +43,7 @@ func fetchUniswapV2PairInfo(ctx context.Context, client *chain.ClientWithFallbac
return &token0Address, &token1Address, nil return &token0Address, &token1Address, nil
} }
func fetchUniswapV3PoolInfo(ctx context.Context, client *chain.ClientWithFallback, poolAddress common.Address) (*common.Address, *common.Address, error) { func fetchUniswapV3PoolInfo(ctx context.Context, client chain.ClientInterface, poolAddress common.Address) (*common.Address, *common.Address, error) {
caller, err := uniswapv3.NewUniswapv3Caller(poolAddress, client) caller, err := uniswapv3.NewUniswapv3Caller(poolAddress, client)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -90,7 +90,7 @@ func identifyUniswapV2Asset(tokenManager *token.Manager, chainID uint64, amount0
return return
} }
func fetchUniswapV2Info(ctx context.Context, client *chain.ClientWithFallback, tokenManager *token.Manager, log *types.Log) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) { func fetchUniswapV2Info(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, log *types.Log) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) {
pairAddress, _, _, amount0In, amount1In, amount0Out, amount1Out, err := w_common.ParseUniswapV2Log(log) pairAddress, _, _, amount0In, amount1In, amount0Out, amount1Out, err := w_common.ParseUniswapV2Log(log)
if err != nil { if err != nil {
return return
@ -101,7 +101,7 @@ func fetchUniswapV2Info(ctx context.Context, client *chain.ClientWithFallback, t
return return
} }
fromToken, fromAmountInt, err := identifyUniswapV2Asset(tokenManager, client.ChainID, amount0In, *token0ContractAddress, amount1In, *token1ContractAddress) fromToken, fromAmountInt, err := identifyUniswapV2Asset(tokenManager, client.NetworkID(), amount0In, *token0ContractAddress, amount1In, *token1ContractAddress)
if err != nil { if err != nil {
// "Soft" error, allow to continue with unknown asset // "Soft" error, allow to continue with unknown asset
fromAsset = "" fromAsset = ""
@ -111,7 +111,7 @@ func fetchUniswapV2Info(ctx context.Context, client *chain.ClientWithFallback, t
fromAmount = (*hexutil.Big)(fromAmountInt) fromAmount = (*hexutil.Big)(fromAmountInt)
} }
toToken, toAmountInt, err := identifyUniswapV2Asset(tokenManager, client.ChainID, amount0Out, *token0ContractAddress, amount1Out, *token1ContractAddress) toToken, toAmountInt, err := identifyUniswapV2Asset(tokenManager, client.NetworkID(), amount0Out, *token0ContractAddress, amount1Out, *token1ContractAddress)
if err != nil { if err != nil {
// "Soft" error, allow to continue with unknown asset // "Soft" error, allow to continue with unknown asset
toAsset = "" toAsset = ""
@ -159,7 +159,7 @@ func identifyUniswapV3Assets(tokenManager *token.Manager, chainID uint64, amount
return return
} }
func fetchUniswapV3Info(ctx context.Context, client *chain.ClientWithFallback, tokenManager *token.Manager, log *types.Log) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) { func fetchUniswapV3Info(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, log *types.Log) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) {
poolAddress, _, _, amount0, amount1, err := w_common.ParseUniswapV3Log(log) poolAddress, _, _, amount0, amount1, err := w_common.ParseUniswapV3Log(log)
if err != nil { if err != nil {
return return
@ -170,7 +170,7 @@ func fetchUniswapV3Info(ctx context.Context, client *chain.ClientWithFallback, t
return return
} }
fromToken, fromAmountInt, toToken, toAmountInt, err := identifyUniswapV3Assets(tokenManager, client.ChainID, amount0, *token0ContractAddress, amount1, *token1ContractAddress) fromToken, fromAmountInt, toToken, toAmountInt, err := identifyUniswapV3Assets(tokenManager, client.NetworkID(), amount0, *token0ContractAddress, amount1, *token1ContractAddress)
if err != nil { if err != nil {
// "Soft" error, allow to continue with unknown asset // "Soft" error, allow to continue with unknown asset
err = nil err = nil
@ -188,7 +188,7 @@ func fetchUniswapV3Info(ctx context.Context, client *chain.ClientWithFallback, t
return return
} }
func fetchUniswapInfo(ctx context.Context, client *chain.ClientWithFallback, tokenManager *token.Manager, log *types.Log, logType w_common.EventType) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) { func fetchUniswapInfo(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, log *types.Log, logType w_common.EventType) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) {
switch logType { switch logType {
case w_common.UniswapV2SwapEventType: case w_common.UniswapV2SwapEventType:
return fetchUniswapV2Info(ctx, client, tokenManager, log) return fetchUniswapV2Info(ctx, client, tokenManager, log)
@ -201,7 +201,7 @@ func fetchUniswapInfo(ctx context.Context, client *chain.ClientWithFallback, tok
// Build a Swap multitransaction from a list containing one or several uniswapV2/uniswapV3 subTxs // Build a Swap multitransaction from a list containing one or several uniswapV2/uniswapV3 subTxs
// We only care about the first and last swap to identify the input/output token and amounts // We only care about the first and last swap to identify the input/output token and amounts
func buildUniswapSwapMultitransaction(ctx context.Context, client *chain.ClientWithFallback, tokenManager *token.Manager, transfer *Transfer) (*MultiTransaction, error) { func buildUniswapSwapMultitransaction(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, transfer *Transfer) (*MultiTransaction, error) {
multiTransaction := MultiTransaction{ multiTransaction := MultiTransaction{
Type: MultiTransactionSwap, Type: MultiTransactionSwap,
FromNetworkID: transfer.NetworkID, FromNetworkID: transfer.NetworkID,