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

     1  package transfer
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/big"
     7  	"sort"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/golang/mock/gomock"
    14  	"github.com/pkg/errors"
    15  	"github.com/stretchr/testify/mock"
    16  	"golang.org/x/exp/slices" // since 1.21, this is in the standard library
    17  
    18  	"github.com/stretchr/testify/require"
    19  
    20  	"github.com/ethereum/go-ethereum"
    21  	"github.com/ethereum/go-ethereum/accounts/abi"
    22  	"github.com/ethereum/go-ethereum/common"
    23  	"github.com/ethereum/go-ethereum/core/types"
    24  	"github.com/ethereum/go-ethereum/event"
    25  	"github.com/ethereum/go-ethereum/rpc"
    26  	"github.com/status-im/status-go/appdatabase"
    27  	"github.com/status-im/status-go/contracts"
    28  	"github.com/status-im/status-go/contracts/balancechecker"
    29  	"github.com/status-im/status-go/contracts/ethscan"
    30  	"github.com/status-im/status-go/contracts/ierc20"
    31  	ethtypes "github.com/status-im/status-go/eth-node/types"
    32  	"github.com/status-im/status-go/rpc/chain"
    33  	mock_client "github.com/status-im/status-go/rpc/chain/mock/client"
    34  	mock_rpcclient "github.com/status-im/status-go/rpc/mock/client"
    35  	"github.com/status-im/status-go/server"
    36  	"github.com/status-im/status-go/services/wallet/async"
    37  	"github.com/status-im/status-go/services/wallet/balance"
    38  	"github.com/status-im/status-go/services/wallet/blockchainstate"
    39  	"github.com/status-im/status-go/services/wallet/community"
    40  	"github.com/status-im/status-go/t/helpers"
    41  	"github.com/status-im/status-go/t/utils"
    42  
    43  	"github.com/status-im/status-go/multiaccounts/accounts"
    44  	multicommon "github.com/status-im/status-go/multiaccounts/common"
    45  	"github.com/status-im/status-go/params"
    46  	statusRpc "github.com/status-im/status-go/rpc"
    47  	"github.com/status-im/status-go/rpc/network"
    48  	walletcommon "github.com/status-im/status-go/services/wallet/common"
    49  	"github.com/status-im/status-go/services/wallet/token"
    50  	"github.com/status-im/status-go/transactions"
    51  	"github.com/status-im/status-go/walletdatabase"
    52  )
    53  
    54  type TestClient struct {
    55  	t *testing.T
    56  	// [][block, newBalance, nonceDiff]
    57  	balances                       map[common.Address][][]int
    58  	outgoingERC20Transfers         map[common.Address][]testERC20Transfer
    59  	incomingERC20Transfers         map[common.Address][]testERC20Transfer
    60  	outgoingERC1155SingleTransfers map[common.Address][]testERC20Transfer
    61  	incomingERC1155SingleTransfers map[common.Address][]testERC20Transfer
    62  	balanceHistory                 map[common.Address]map[uint64]*big.Int
    63  	tokenBalanceHistory            map[common.Address]map[common.Address]map[uint64]*big.Int
    64  	nonceHistory                   map[common.Address]map[uint64]uint64
    65  	traceAPICalls                  bool
    66  	printPreparedData              bool
    67  	rw                             sync.RWMutex
    68  	callsCounter                   map[string]int
    69  	currentBlock                   uint64
    70  	limiter                        chain.RequestLimiter
    71  	tag                            string
    72  	groupTag                       string
    73  }
    74  
    75  var countAndlog = func(tc *TestClient, method string, params ...interface{}) error {
    76  	tc.incCounter(method)
    77  	if tc.traceAPICalls {
    78  		if len(params) > 0 {
    79  			tc.t.Log(method, params)
    80  		} else {
    81  			tc.t.Log(method)
    82  		}
    83  	}
    84  
    85  	return nil
    86  }
    87  
    88  func (tc *TestClient) countAndlog(method string, params ...interface{}) error {
    89  	return countAndlog(tc, method, params...)
    90  }
    91  
    92  func (tc *TestClient) incCounter(method string) {
    93  	tc.rw.Lock()
    94  	defer tc.rw.Unlock()
    95  	tc.callsCounter[method] = tc.callsCounter[method] + 1
    96  }
    97  
    98  func (tc *TestClient) getCounter() int {
    99  	tc.rw.RLock()
   100  	defer tc.rw.RUnlock()
   101  	cnt := 0
   102  	for _, v := range tc.callsCounter {
   103  		cnt += v
   104  	}
   105  	return cnt
   106  }
   107  
   108  func (tc *TestClient) printCounter() {
   109  	total := tc.getCounter()
   110  
   111  	tc.rw.RLock()
   112  	defer tc.rw.RUnlock()
   113  
   114  	tc.t.Log("========================================= Total calls", total)
   115  	for k, v := range tc.callsCounter {
   116  		tc.t.Log(k, v)
   117  	}
   118  	tc.t.Log("=========================================")
   119  }
   120  
   121  func (tc *TestClient) resetCounter() {
   122  	tc.rw.Lock()
   123  	defer tc.rw.Unlock()
   124  	tc.callsCounter = map[string]int{}
   125  }
   126  
   127  func (tc *TestClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
   128  	if tc.traceAPICalls {
   129  		tc.t.Log("BatchCallContext")
   130  	}
   131  	return nil
   132  }
   133  
   134  func (tc *TestClient) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
   135  	err := tc.countAndlog("HeaderByHash")
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	return nil, nil
   140  }
   141  
   142  func (tc *TestClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
   143  	err := tc.countAndlog("BlockByHash")
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	return nil, nil
   148  }
   149  
   150  func (tc *TestClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
   151  	err := tc.countAndlog("BlockByNumber")
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	return nil, nil
   156  }
   157  
   158  func (tc *TestClient) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
   159  	nonce := tc.nonceHistory[account][blockNumber.Uint64()]
   160  	err := tc.countAndlog("NonceAt", fmt.Sprintf("result: %d", nonce))
   161  	if err != nil {
   162  		return nonce, err
   163  	}
   164  	return nonce, nil
   165  }
   166  
   167  func (tc *TestClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
   168  	err := tc.countAndlog("FilterLogs")
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	// We do not verify addresses for now
   174  	allTransfers := []testERC20Transfer{}
   175  	signatures := q.Topics[0]
   176  	erc20TransferSignature := walletcommon.GetEventSignatureHash(walletcommon.Erc20_721TransferEventSignature)
   177  	erc1155TransferSingleSignature := walletcommon.GetEventSignatureHash(walletcommon.Erc1155TransferSingleEventSignature)
   178  
   179  	var address common.Hash
   180  	for i := 1; i < len(q.Topics); i++ {
   181  		if len(q.Topics[i]) > 0 {
   182  			address = q.Topics[i][0]
   183  			break
   184  		}
   185  	}
   186  
   187  	if slices.Contains(signatures, erc1155TransferSingleSignature) {
   188  		from := q.Topics[2]
   189  		var to []common.Hash
   190  		if len(q.Topics) > 3 {
   191  			to = q.Topics[3]
   192  		}
   193  
   194  		if len(to) > 0 {
   195  			for _, addressHash := range to {
   196  				address := &common.Address{}
   197  				address.SetBytes(addressHash.Bytes())
   198  				allTransfers = append(allTransfers, tc.incomingERC1155SingleTransfers[*address]...)
   199  			}
   200  		}
   201  		if len(from) > 0 {
   202  			for _, addressHash := range from {
   203  				address := &common.Address{}
   204  				address.SetBytes(addressHash.Bytes())
   205  				allTransfers = append(allTransfers, tc.outgoingERC1155SingleTransfers[*address]...)
   206  			}
   207  		}
   208  	}
   209  
   210  	if slices.Contains(signatures, erc20TransferSignature) {
   211  		from := q.Topics[1]
   212  		to := q.Topics[2]
   213  
   214  		if len(to) > 0 {
   215  			for _, addressHash := range to {
   216  				address := &common.Address{}
   217  				address.SetBytes(addressHash.Bytes())
   218  				allTransfers = append(allTransfers, tc.incomingERC20Transfers[*address]...)
   219  			}
   220  		}
   221  
   222  		if len(from) > 0 {
   223  			for _, addressHash := range from {
   224  				address := &common.Address{}
   225  				address.SetBytes(addressHash.Bytes())
   226  				allTransfers = append(allTransfers, tc.outgoingERC20Transfers[*address]...)
   227  			}
   228  		}
   229  	}
   230  
   231  	logs := []types.Log{}
   232  	for _, transfer := range allTransfers {
   233  		if transfer.block.Cmp(q.FromBlock) >= 0 && transfer.block.Cmp(q.ToBlock) <= 0 {
   234  			log := types.Log{
   235  				BlockNumber: transfer.block.Uint64(),
   236  				BlockHash:   common.BigToHash(transfer.block),
   237  			}
   238  
   239  			// Use the address at least in one any(from/to) topic to trick the implementation
   240  			switch transfer.eventType {
   241  			case walletcommon.Erc20TransferEventType, walletcommon.Erc721TransferEventType:
   242  				// To detect properly ERC721, we need a different number of topics. For now we use only ERC20 for testing
   243  				log.Topics = []common.Hash{walletcommon.GetEventSignatureHash(walletcommon.Erc20_721TransferEventSignature), address, address}
   244  			case walletcommon.Erc1155TransferSingleEventType:
   245  				log.Topics = []common.Hash{walletcommon.GetEventSignatureHash(walletcommon.Erc1155TransferSingleEventSignature), address, address, address}
   246  				log.Data = make([]byte, 2*common.HashLength)
   247  			case walletcommon.Erc1155TransferBatchEventType:
   248  				log.Topics = []common.Hash{walletcommon.GetEventSignatureHash(walletcommon.Erc1155TransferBatchEventSignature), address, address, address}
   249  			}
   250  
   251  			logs = append(logs, log)
   252  		}
   253  	}
   254  
   255  	return logs, nil
   256  }
   257  
   258  func (tc *TestClient) getBalance(address common.Address, blockNumber *big.Int) *big.Int {
   259  	balance := tc.balanceHistory[address][blockNumber.Uint64()]
   260  	if balance == nil {
   261  		balance = big.NewInt(0)
   262  	}
   263  
   264  	return balance
   265  }
   266  
   267  func (tc *TestClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
   268  	balance := tc.getBalance(account, blockNumber)
   269  	err := tc.countAndlog("BalanceAt", fmt.Sprintf("account: %s, result: %d", account, balance))
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  
   274  	return balance, nil
   275  }
   276  
   277  func (tc *TestClient) tokenBalanceAt(account common.Address, token common.Address, blockNumber *big.Int) *big.Int {
   278  	balance := tc.tokenBalanceHistory[account][token][blockNumber.Uint64()]
   279  	if balance == nil {
   280  		balance = big.NewInt(0)
   281  	}
   282  
   283  	if tc.traceAPICalls {
   284  		tc.t.Log("tokenBalanceAt", token, blockNumber, "account:", account, "result:", balance)
   285  	}
   286  	return balance
   287  }
   288  
   289  func (tc *TestClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
   290  	if number == nil {
   291  		number = big.NewInt(int64(tc.currentBlock))
   292  	}
   293  
   294  	err := tc.countAndlog("HeaderByNumber", fmt.Sprintf("number: %d", number))
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  
   299  	header := &types.Header{
   300  		Number: number,
   301  		Time:   0,
   302  	}
   303  
   304  	return header, nil
   305  }
   306  
   307  func (tc *TestClient) CallBlockHashByTransaction(ctx context.Context, blockNumber *big.Int, index uint) (common.Hash, error) {
   308  	err := tc.countAndlog("CallBlockHashByTransaction")
   309  	if err != nil {
   310  		return common.Hash{}, err
   311  	}
   312  	return common.BigToHash(blockNumber), nil
   313  }
   314  
   315  func (tc *TestClient) GetBaseFeeFromBlock(ctx context.Context, blockNumber *big.Int) (string, error) {
   316  	err := tc.countAndlog("GetBaseFeeFromBlock")
   317  	if err != nil {
   318  		return "", err
   319  	}
   320  	return "", nil
   321  }
   322  
   323  func (tc *TestClient) NetworkID() uint64 {
   324  	return 777333
   325  }
   326  
   327  func (tc *TestClient) ToBigInt() *big.Int {
   328  	if tc.traceAPICalls {
   329  		tc.t.Log("ToBigInt")
   330  	}
   331  	return nil
   332  }
   333  
   334  var ethscanAddress = common.HexToAddress("0x0000000000000000000000000000000000777333")
   335  var balanceCheckAddress = common.HexToAddress("0x0000000000000000000000000000000010777333")
   336  
   337  func (tc *TestClient) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
   338  	err := tc.countAndlog("CodeAt", fmt.Sprintf("contract: %s, blockNumber: %d", contract, blockNumber))
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  
   343  	if ethscanAddress == contract || balanceCheckAddress == contract {
   344  		return []byte{1}, nil
   345  	}
   346  
   347  	return nil, nil
   348  }
   349  
   350  func (tc *TestClient) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
   351  	err := tc.countAndlog("CallContract", fmt.Sprintf("call: %v, blockNumber: %d, to: %s", call, blockNumber, call.To))
   352  	if err != nil {
   353  		return nil, err
   354  	}
   355  
   356  	if *call.To == ethscanAddress {
   357  		parsed, err := abi.JSON(strings.NewReader(ethscan.BalanceScannerABI))
   358  		if err != nil {
   359  			return nil, err
   360  		}
   361  		method := parsed.Methods["tokensBalance"]
   362  		params := call.Data[len(method.ID):]
   363  		args, err := method.Inputs.Unpack(params)
   364  
   365  		if err != nil {
   366  			tc.t.Log("ERROR on unpacking", err)
   367  			return nil, err
   368  		}
   369  
   370  		account := args[0].(common.Address)
   371  		tokens := args[1].([]common.Address)
   372  		balances := []*big.Int{}
   373  		for _, token := range tokens {
   374  			balances = append(balances, tc.tokenBalanceAt(account, token, blockNumber))
   375  		}
   376  		results := []ethscan.BalanceScannerResult{}
   377  		for _, balance := range balances {
   378  			results = append(results, ethscan.BalanceScannerResult{
   379  				Success: true,
   380  				Data:    balance.Bytes(),
   381  			})
   382  		}
   383  
   384  		output, err := method.Outputs.Pack(results)
   385  		if err != nil {
   386  			tc.t.Log("ERROR on packing", err)
   387  			return nil, err
   388  		}
   389  
   390  		return output, nil
   391  	}
   392  
   393  	if *call.To == tokenTXXAddress || *call.To == tokenTXYAddress {
   394  		parsed, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
   395  		if err != nil {
   396  			return nil, err
   397  		}
   398  
   399  		method := parsed.Methods["balanceOf"]
   400  		params := call.Data[len(method.ID):]
   401  		args, err := method.Inputs.Unpack(params)
   402  
   403  		if err != nil {
   404  			tc.t.Log("ERROR on unpacking", err)
   405  			return nil, err
   406  		}
   407  
   408  		account := args[0].(common.Address)
   409  
   410  		balance := tc.tokenBalanceAt(account, *call.To, blockNumber)
   411  
   412  		output, err := method.Outputs.Pack(balance)
   413  		if err != nil {
   414  			tc.t.Log("ERROR on packing ERC20 balance", err)
   415  			return nil, err
   416  		}
   417  
   418  		return output, nil
   419  	}
   420  
   421  	if *call.To == balanceCheckAddress {
   422  		parsed, err := abi.JSON(strings.NewReader(balancechecker.BalanceCheckerABI))
   423  		if err != nil {
   424  			return nil, err
   425  		}
   426  
   427  		method := parsed.Methods["balancesHash"]
   428  		params := call.Data[len(method.ID):]
   429  		args, err := method.Inputs.Unpack(params)
   430  
   431  		if err != nil {
   432  			tc.t.Log("ERROR on unpacking", err)
   433  			return nil, err
   434  		}
   435  
   436  		addresses := args[0].([]common.Address)
   437  		tokens := args[1].([]common.Address)
   438  		bn := big.NewInt(int64(tc.currentBlock))
   439  		hashes := [][32]byte{}
   440  
   441  		for _, address := range addresses {
   442  			balance := tc.getBalance(address, big.NewInt(int64(tc.currentBlock)))
   443  			balanceBytes := balance.Bytes()
   444  			for _, token := range tokens {
   445  				balance := tc.tokenBalanceAt(address, token, bn)
   446  				balanceBytes = append(balanceBytes, balance.Bytes()...)
   447  			}
   448  
   449  			hash := [32]byte{}
   450  			for i, b := range ethtypes.BytesToHash(balanceBytes).Bytes() {
   451  				hash[i] = b
   452  			}
   453  
   454  			hashes = append(hashes, hash)
   455  		}
   456  
   457  		output, err := method.Outputs.Pack(bn, hashes)
   458  		if err != nil {
   459  			tc.t.Log("ERROR on packing", err)
   460  			return nil, err
   461  		}
   462  
   463  		return output, nil
   464  	}
   465  
   466  	return nil, nil
   467  }
   468  
   469  func (tc *TestClient) prepareBalanceHistory(toBlock int) {
   470  	tc.balanceHistory = map[common.Address]map[uint64]*big.Int{}
   471  	tc.nonceHistory = map[common.Address]map[uint64]uint64{}
   472  
   473  	for address, balances := range tc.balances {
   474  		var currentBlock, currentBalance, currentNonce int
   475  
   476  		tc.balanceHistory[address] = map[uint64]*big.Int{}
   477  		tc.nonceHistory[address] = map[uint64]uint64{}
   478  
   479  		if len(balances) == 0 {
   480  			balances = append(balances, []int{toBlock + 1, 0, 0})
   481  		} else {
   482  			lastBlock := balances[len(balances)-1]
   483  			balances = append(balances, []int{toBlock + 1, lastBlock[1], 0})
   484  		}
   485  		for _, change := range balances {
   486  			for blockN := currentBlock; blockN < change[0]; blockN++ {
   487  				tc.balanceHistory[address][uint64(blockN)] = big.NewInt(int64(currentBalance))
   488  				tc.nonceHistory[address][uint64(blockN)] = uint64(currentNonce)
   489  			}
   490  			currentBlock = change[0]
   491  			currentBalance = change[1]
   492  			currentNonce += change[2]
   493  		}
   494  	}
   495  
   496  	if tc.printPreparedData {
   497  		tc.t.Log("========================================= ETH BALANCES")
   498  		tc.t.Log(tc.balanceHistory)
   499  		tc.t.Log(tc.nonceHistory)
   500  		tc.t.Log(tc.tokenBalanceHistory)
   501  		tc.t.Log("=========================================")
   502  	}
   503  }
   504  
   505  func (tc *TestClient) prepareTokenBalanceHistory(toBlock int) {
   506  	transfersPerAddress := map[common.Address]map[common.Address][]testERC20Transfer{}
   507  	for account, transfers := range tc.outgoingERC20Transfers {
   508  		if _, ok := transfersPerAddress[account]; !ok {
   509  			transfersPerAddress[account] = map[common.Address][]testERC20Transfer{}
   510  		}
   511  		for _, transfer := range transfers {
   512  			transfer.amount = new(big.Int).Neg(transfer.amount)
   513  			transfer.eventType = walletcommon.Erc20TransferEventType
   514  			transfersPerAddress[account][transfer.address] = append(transfersPerAddress[account][transfer.address], transfer)
   515  		}
   516  	}
   517  
   518  	for account, transfers := range tc.incomingERC20Transfers {
   519  		if _, ok := transfersPerAddress[account]; !ok {
   520  			transfersPerAddress[account] = map[common.Address][]testERC20Transfer{}
   521  		}
   522  		for _, transfer := range transfers {
   523  			transfer.amount = new(big.Int).Neg(transfer.amount)
   524  			transfer.eventType = walletcommon.Erc20TransferEventType
   525  			transfersPerAddress[account][transfer.address] = append(transfersPerAddress[account][transfer.address], transfer)
   526  		}
   527  	}
   528  
   529  	for account, transfers := range tc.outgoingERC1155SingleTransfers {
   530  		if _, ok := transfersPerAddress[account]; !ok {
   531  			transfersPerAddress[account] = map[common.Address][]testERC20Transfer{}
   532  		}
   533  		for _, transfer := range transfers {
   534  			transfer.amount = new(big.Int).Neg(transfer.amount)
   535  			transfer.eventType = walletcommon.Erc1155TransferSingleEventType
   536  			transfersPerAddress[account][transfer.address] = append(transfersPerAddress[account][transfer.address], transfer)
   537  		}
   538  	}
   539  
   540  	for account, transfers := range tc.incomingERC1155SingleTransfers {
   541  		if _, ok := transfersPerAddress[account]; !ok {
   542  			transfersPerAddress[account] = map[common.Address][]testERC20Transfer{}
   543  		}
   544  		for _, transfer := range transfers {
   545  			transfer.amount = new(big.Int).Neg(transfer.amount)
   546  			transfer.eventType = walletcommon.Erc1155TransferSingleEventType
   547  			transfersPerAddress[account][transfer.address] = append(transfersPerAddress[account][transfer.address], transfer)
   548  		}
   549  	}
   550  
   551  	tc.tokenBalanceHistory = map[common.Address]map[common.Address]map[uint64]*big.Int{}
   552  
   553  	for account, transfersPerToken := range transfersPerAddress {
   554  		tc.tokenBalanceHistory[account] = map[common.Address]map[uint64]*big.Int{}
   555  		for token, transfers := range transfersPerToken {
   556  			sort.Slice(transfers, func(i, j int) bool {
   557  				return transfers[i].block.Cmp(transfers[j].block) < 0
   558  			})
   559  
   560  			currentBlock := uint64(0)
   561  			currentBalance := big.NewInt(0)
   562  
   563  			tc.tokenBalanceHistory[token] = map[common.Address]map[uint64]*big.Int{}
   564  			transfers = append(transfers, testERC20Transfer{big.NewInt(int64(toBlock + 1)), token, big.NewInt(0), walletcommon.Erc20TransferEventType})
   565  
   566  			tc.tokenBalanceHistory[account][token] = map[uint64]*big.Int{}
   567  			for _, transfer := range transfers {
   568  				for blockN := currentBlock; blockN < transfer.block.Uint64(); blockN++ {
   569  					tc.tokenBalanceHistory[account][token][blockN] = new(big.Int).Set(currentBalance)
   570  				}
   571  				currentBlock = transfer.block.Uint64()
   572  				currentBalance = new(big.Int).Add(currentBalance, transfer.amount)
   573  			}
   574  		}
   575  	}
   576  	if tc.printPreparedData {
   577  		tc.t.Log("========================================= ERC20 BALANCES")
   578  		tc.t.Log(tc.tokenBalanceHistory)
   579  		tc.t.Log("=========================================")
   580  	}
   581  }
   582  
   583  func (tc *TestClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
   584  	err := tc.countAndlog("CallContext")
   585  	if err != nil {
   586  		return err
   587  	}
   588  	return nil
   589  }
   590  
   591  func (tc *TestClient) GetWalletNotifier() func(chainId uint64, message string) {
   592  	if tc.traceAPICalls {
   593  		tc.t.Log("GetWalletNotifier")
   594  	}
   595  	return nil
   596  }
   597  
   598  func (tc *TestClient) SetWalletNotifier(notifier func(chainId uint64, message string)) {
   599  	if tc.traceAPICalls {
   600  		tc.t.Log("SetWalletNotifier")
   601  	}
   602  }
   603  
   604  func (tc *TestClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
   605  	err = tc.countAndlog("EstimateGas")
   606  	if err != nil {
   607  		return 0, err
   608  	}
   609  	return 0, nil
   610  }
   611  
   612  func (tc *TestClient) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
   613  	err := tc.countAndlog("PendingCodeAt")
   614  	if err != nil {
   615  		return nil, err
   616  	}
   617  	return nil, nil
   618  }
   619  
   620  func (tc *TestClient) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
   621  	err := tc.countAndlog("PendingCallContract")
   622  	if err != nil {
   623  		return nil, err
   624  	}
   625  	return nil, nil
   626  }
   627  
   628  func (tc *TestClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
   629  	err := tc.countAndlog("PendingNonceAt")
   630  	if err != nil {
   631  		return 0, err
   632  	}
   633  	return 0, nil
   634  }
   635  
   636  func (tc *TestClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
   637  	err := tc.countAndlog("SuggestGasPrice")
   638  	if err != nil {
   639  		return nil, err
   640  	}
   641  	return nil, nil
   642  }
   643  
   644  func (tc *TestClient) SendTransaction(ctx context.Context, tx *types.Transaction) error {
   645  	err := tc.countAndlog("SendTransaction")
   646  	if err != nil {
   647  		return err
   648  	}
   649  	return nil
   650  }
   651  
   652  func (tc *TestClient) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
   653  	err := tc.countAndlog("SuggestGasTipCap")
   654  	if err != nil {
   655  		return nil, err
   656  	}
   657  	return nil, nil
   658  }
   659  
   660  func (tc *TestClient) BatchCallContextIgnoringLocalHandlers(ctx context.Context, b []rpc.BatchElem) error {
   661  	err := tc.countAndlog("BatchCallContextIgnoringLocalHandlers")
   662  	if err != nil {
   663  		return err
   664  	}
   665  	return nil
   666  }
   667  
   668  func (tc *TestClient) CallContextIgnoringLocalHandlers(ctx context.Context, result interface{}, method string, args ...interface{}) error {
   669  	err := tc.countAndlog("CallContextIgnoringLocalHandlers")
   670  	if err != nil {
   671  		return err
   672  	}
   673  	return nil
   674  }
   675  
   676  func (tc *TestClient) CallRaw(data string) string {
   677  	_ = tc.countAndlog("CallRaw")
   678  	return ""
   679  }
   680  
   681  func (tc *TestClient) GetChainID() *big.Int {
   682  	return big.NewInt(1)
   683  }
   684  
   685  func (tc *TestClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
   686  	err := tc.countAndlog("SubscribeFilterLogs")
   687  	if err != nil {
   688  		return nil, err
   689  	}
   690  	return nil, nil
   691  }
   692  
   693  func (tc *TestClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
   694  	err := tc.countAndlog("TransactionReceipt")
   695  	if err != nil {
   696  		return nil, err
   697  	}
   698  	return nil, nil
   699  }
   700  
   701  func (tc *TestClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
   702  	err := tc.countAndlog("TransactionByHash")
   703  	if err != nil {
   704  		return nil, false, err
   705  	}
   706  	return nil, false, nil
   707  }
   708  
   709  func (tc *TestClient) BlockNumber(ctx context.Context) (uint64, error) {
   710  	err := tc.countAndlog("BlockNumber")
   711  	if err != nil {
   712  		return 0, err
   713  	}
   714  	return 0, nil
   715  }
   716  func (tc *TestClient) SetIsConnected(value bool) {
   717  	if tc.traceAPICalls {
   718  		tc.t.Log("SetIsConnected")
   719  	}
   720  }
   721  
   722  func (tc *TestClient) IsConnected() bool {
   723  	if tc.traceAPICalls {
   724  		tc.t.Log("GetIsConnected")
   725  	}
   726  
   727  	return true
   728  }
   729  
   730  func (tc *TestClient) GetLimiter() chain.RequestLimiter {
   731  	return tc.limiter
   732  }
   733  
   734  func (tc *TestClient) SetLimiter(limiter chain.RequestLimiter) {
   735  	tc.limiter = limiter
   736  }
   737  
   738  type testERC20Transfer struct {
   739  	block     *big.Int
   740  	address   common.Address
   741  	amount    *big.Int
   742  	eventType walletcommon.EventType
   743  }
   744  
   745  type findBlockCase struct {
   746  	balanceChanges                 [][]int
   747  	ERC20BalanceChanges            [][]int
   748  	fromBlock                      int64
   749  	toBlock                        int64
   750  	rangeSize                      int
   751  	expectedBlocksFound            int
   752  	outgoingERC20Transfers         []testERC20Transfer
   753  	incomingERC20Transfers         []testERC20Transfer
   754  	outgoingERC1155SingleTransfers []testERC20Transfer
   755  	incomingERC1155SingleTransfers []testERC20Transfer
   756  	label                          string
   757  	expectedCalls                  map[string]int
   758  }
   759  
   760  func transferInEachBlock() [][]int {
   761  	res := [][]int{}
   762  
   763  	for i := 1; i < 101; i++ {
   764  		res = append(res, []int{i, i, i})
   765  	}
   766  
   767  	return res
   768  }
   769  
   770  func getCases() []findBlockCase {
   771  	cases := []findBlockCase{}
   772  	case1 := findBlockCase{
   773  		balanceChanges: [][]int{
   774  			{5, 1, 0},
   775  			{20, 2, 0},
   776  			{45, 1, 1},
   777  			{46, 50, 0},
   778  			{75, 0, 1},
   779  		},
   780  		outgoingERC20Transfers: []testERC20Transfer{
   781  			{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType},
   782  		},
   783  		toBlock:             100,
   784  		expectedBlocksFound: 6,
   785  		expectedCalls: map[string]int{
   786  			"FilterLogs":     15,
   787  			"HeaderByNumber": 5,
   788  		},
   789  	}
   790  
   791  	case100transfers := findBlockCase{
   792  		balanceChanges:      transferInEachBlock(),
   793  		toBlock:             100,
   794  		expectedBlocksFound: 100,
   795  		expectedCalls: map[string]int{
   796  			"BalanceAt":      101,
   797  			"NonceAt":        0,
   798  			"FilterLogs":     15,
   799  			"HeaderByNumber": 100,
   800  		},
   801  	}
   802  
   803  	case3 := findBlockCase{
   804  		balanceChanges: [][]int{
   805  			{1, 1, 1},
   806  			{2, 2, 2},
   807  			{45, 1, 1},
   808  			{46, 50, 0},
   809  			{75, 0, 1},
   810  		},
   811  		toBlock:             100,
   812  		expectedBlocksFound: 5,
   813  	}
   814  	case4 := findBlockCase{
   815  		balanceChanges: [][]int{
   816  			{20, 1, 0},
   817  		},
   818  		toBlock:             100,
   819  		fromBlock:           10,
   820  		expectedBlocksFound: 1,
   821  		label:               "single block",
   822  	}
   823  
   824  	case5 := findBlockCase{
   825  		balanceChanges:      [][]int{},
   826  		toBlock:             100,
   827  		fromBlock:           20,
   828  		expectedBlocksFound: 0,
   829  	}
   830  
   831  	case6 := findBlockCase{
   832  		balanceChanges: [][]int{
   833  			{20, 1, 0},
   834  			{45, 1, 1},
   835  		},
   836  		toBlock:             100,
   837  		fromBlock:           30,
   838  		expectedBlocksFound: 1,
   839  		rangeSize:           20,
   840  		label:               "single block in range",
   841  	}
   842  
   843  	case7emptyHistoryWithOneERC20Transfer := findBlockCase{
   844  		balanceChanges:      [][]int{},
   845  		toBlock:             100,
   846  		rangeSize:           20,
   847  		expectedBlocksFound: 1,
   848  		incomingERC20Transfers: []testERC20Transfer{
   849  			{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType},
   850  		},
   851  	}
   852  
   853  	case8emptyHistoryWithERC20Transfers := findBlockCase{
   854  		balanceChanges:      [][]int{},
   855  		toBlock:             100,
   856  		rangeSize:           20,
   857  		expectedBlocksFound: 2,
   858  		incomingERC20Transfers: []testERC20Transfer{
   859  			// edge case when a regular scan will find transfer at 80,
   860  			// but erc20 tail scan should only find transfer at block 6
   861  			{big.NewInt(80), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType},
   862  			{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType},
   863  		},
   864  		expectedCalls: map[string]int{
   865  			"FilterLogs":   5,
   866  			"CallContract": 3,
   867  		},
   868  	}
   869  
   870  	case9emptyHistoryWithERC20Transfers := findBlockCase{
   871  		balanceChanges: [][]int{},
   872  		toBlock:        100,
   873  		rangeSize:      20,
   874  		// we expect only a single eth_getLogs to be executed here for both erc20 transfers,
   875  		// thus only 2 blocks found
   876  		expectedBlocksFound: 2,
   877  		incomingERC20Transfers: []testERC20Transfer{
   878  			{big.NewInt(7), tokenTXYAddress, big.NewInt(1), walletcommon.Erc20TransferEventType},
   879  			{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType},
   880  		},
   881  		expectedCalls: map[string]int{
   882  			"FilterLogs": 5,
   883  		},
   884  	}
   885  
   886  	case10 := findBlockCase{
   887  		balanceChanges:      [][]int{},
   888  		toBlock:             100,
   889  		fromBlock:           99,
   890  		expectedBlocksFound: 0,
   891  		label:               "single block range, no transactions",
   892  		expectedCalls: map[string]int{
   893  			// only two requests to check the range for incoming ERC20
   894  			"FilterLogs": 3,
   895  			// no contract calls as ERC20 is not checked
   896  			"CallContract": 0,
   897  		},
   898  	}
   899  
   900  	case11IncomingERC1155SingleTransfers := findBlockCase{
   901  		balanceChanges: [][]int{},
   902  		toBlock:        100,
   903  		rangeSize:      20,
   904  		// we expect only a single eth_getLogs to be executed here for both erc20 transfers,
   905  		// thus only 2 blocks found
   906  		expectedBlocksFound: 2,
   907  		incomingERC1155SingleTransfers: []testERC20Transfer{
   908  			{big.NewInt(7), tokenTXYAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType},
   909  			{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType},
   910  		},
   911  		expectedCalls: map[string]int{
   912  			"FilterLogs":   5,
   913  			"CallContract": 5,
   914  		},
   915  	}
   916  
   917  	case12OutgoingERC1155SingleTransfers := findBlockCase{
   918  		balanceChanges: [][]int{
   919  			{6, 1, 0},
   920  		},
   921  		toBlock:             100,
   922  		rangeSize:           20,
   923  		expectedBlocksFound: 3,
   924  		outgoingERC1155SingleTransfers: []testERC20Transfer{
   925  			{big.NewInt(80), tokenTXYAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType},
   926  			{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType},
   927  		},
   928  		expectedCalls: map[string]int{
   929  			"FilterLogs": 15, // 3 for each range
   930  		},
   931  	}
   932  
   933  	case13outgoingERC20ERC1155SingleTransfers := findBlockCase{
   934  		balanceChanges: [][]int{
   935  			{63, 1, 0},
   936  		},
   937  		toBlock:             100,
   938  		rangeSize:           20,
   939  		expectedBlocksFound: 3,
   940  		outgoingERC1155SingleTransfers: []testERC20Transfer{
   941  			{big.NewInt(80), tokenTXYAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType},
   942  		},
   943  		outgoingERC20Transfers: []testERC20Transfer{
   944  			{big.NewInt(63), tokenTXYAddress, big.NewInt(1), walletcommon.Erc20TransferEventType},
   945  		},
   946  		expectedCalls: map[string]int{
   947  			"FilterLogs": 6, // 3 for each range, 0 for tail check becauseERC20ScanByBalance  returns no ranges
   948  		},
   949  	}
   950  
   951  	case14outgoingERC20ERC1155SingleTransfersMoreFilterLogs := findBlockCase{
   952  		balanceChanges: [][]int{
   953  			{61, 1, 0},
   954  		},
   955  		toBlock:             100,
   956  		rangeSize:           20,
   957  		expectedBlocksFound: 3,
   958  		outgoingERC1155SingleTransfers: []testERC20Transfer{
   959  			{big.NewInt(80), tokenTXYAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType},
   960  		},
   961  		outgoingERC20Transfers: []testERC20Transfer{
   962  			{big.NewInt(61), tokenTXYAddress, big.NewInt(1), walletcommon.Erc20TransferEventType},
   963  		},
   964  		expectedCalls: map[string]int{
   965  			"FilterLogs": 9, // 3 for each range of [40-100], 0 for tail check because ERC20ScanByBalance returns no ranges
   966  		},
   967  		label: "outgoing ERC20 and ERC1155 transfers but more FilterLogs calls because startFromBlock is not detected at range [60-80] as it is in the first subrange",
   968  	}
   969  
   970  	case15incomingERC20outgoingERC1155SingleTransfers := findBlockCase{
   971  		balanceChanges: [][]int{
   972  			{85, 1, 0},
   973  		},
   974  		toBlock:             100,
   975  		rangeSize:           20,
   976  		expectedBlocksFound: 2,
   977  		outgoingERC1155SingleTransfers: []testERC20Transfer{
   978  			{big.NewInt(85), tokenTXYAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType},
   979  		},
   980  		incomingERC20Transfers: []testERC20Transfer{
   981  			{big.NewInt(88), tokenTXYAddress, big.NewInt(1), walletcommon.Erc20TransferEventType},
   982  		},
   983  		expectedCalls: map[string]int{
   984  			"FilterLogs": 3, // 3 for each range of [40-100], 0 for tail check because ERC20ScanByBalance returns no ranges
   985  		},
   986  		label: "incoming ERC20 and outgoing ERC1155 transfers are fetched with same topic",
   987  	}
   988  
   989  	case16 := findBlockCase{
   990  		balanceChanges: [][]int{
   991  			{75, 0, 1},
   992  		},
   993  		outgoingERC20Transfers: []testERC20Transfer{
   994  			{big.NewInt(80), tokenTXXAddress, big.NewInt(4), walletcommon.Erc20TransferEventType},
   995  		},
   996  		toBlock:             100,
   997  		rangeSize:           20,
   998  		expectedBlocksFound: 3, // ideally we should find 2 blocks, but we will find 3 and this test shows that we are ok with that
   999  		label: `duplicate blocks detected but we wont fix it because we want to save requests on the edges of the ranges,
  1000  		 taking balance and nonce from cache while ETH and tokens ranges searching are tightly coupled`,
  1001  	}
  1002  
  1003  	cases = append(cases, case1)
  1004  	cases = append(cases, case100transfers)
  1005  	cases = append(cases, case3)
  1006  	cases = append(cases, case4)
  1007  	cases = append(cases, case5)
  1008  
  1009  	cases = append(cases, case6)
  1010  	cases = append(cases, case7emptyHistoryWithOneERC20Transfer)
  1011  	cases = append(cases, case8emptyHistoryWithERC20Transfers)
  1012  	cases = append(cases, case9emptyHistoryWithERC20Transfers)
  1013  	cases = append(cases, case10)
  1014  	cases = append(cases, case11IncomingERC1155SingleTransfers)
  1015  	cases = append(cases, case12OutgoingERC1155SingleTransfers)
  1016  	cases = append(cases, case13outgoingERC20ERC1155SingleTransfers)
  1017  	cases = append(cases, case14outgoingERC20ERC1155SingleTransfersMoreFilterLogs)
  1018  	cases = append(cases, case15incomingERC20outgoingERC1155SingleTransfers)
  1019  	cases = append(cases, case16)
  1020  
  1021  	//cases = append([]findBlockCase{}, case10)
  1022  
  1023  	return cases
  1024  }
  1025  
  1026  var tokenTXXAddress = common.HexToAddress("0x53211")
  1027  var tokenTXYAddress = common.HexToAddress("0x73211")
  1028  
  1029  func setupFindBlocksCommand(t *testing.T, accountAddress common.Address, fromBlock, toBlock *big.Int, rangeSize int, balances map[common.Address][][]int, outgoingERC20Transfers, incomingERC20Transfers, outgoingERC1155SingleTransfers, incomingERC1155SingleTransfers map[common.Address][]testERC20Transfer) (*findBlocksCommand, *TestClient, chan []*DBHeader, *BlockRangeSequentialDAO) {
  1030  	appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
  1031  	require.NoError(t, err)
  1032  
  1033  	db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
  1034  	require.NoError(t, err)
  1035  
  1036  	mediaServer, err := server.NewMediaServer(appdb, nil, nil, db)
  1037  	require.NoError(t, err)
  1038  
  1039  	wdb := NewDB(db)
  1040  	tc := &TestClient{
  1041  		t:                              t,
  1042  		balances:                       balances,
  1043  		outgoingERC20Transfers:         outgoingERC20Transfers,
  1044  		incomingERC20Transfers:         incomingERC20Transfers,
  1045  		outgoingERC1155SingleTransfers: outgoingERC1155SingleTransfers,
  1046  		incomingERC1155SingleTransfers: incomingERC1155SingleTransfers,
  1047  		callsCounter:                   map[string]int{},
  1048  	}
  1049  	// tc.traceAPICalls = true
  1050  	// tc.printPreparedData = true
  1051  	tc.prepareBalanceHistory(100)
  1052  	tc.prepareTokenBalanceHistory(100)
  1053  	blockChannel := make(chan []*DBHeader, 100)
  1054  
  1055  	// Reimplement the common function that is called from every method to check for the limit
  1056  	countAndlog = func(tc *TestClient, method string, params ...interface{}) error {
  1057  		if tc.GetLimiter() != nil {
  1058  			if allow, _ := tc.GetLimiter().Allow(tc.tag); !allow {
  1059  				t.Log("ERROR: requests over limit")
  1060  				return chain.ErrRequestsOverLimit
  1061  			}
  1062  			if allow, _ := tc.GetLimiter().Allow(tc.groupTag); !allow {
  1063  				t.Log("ERROR: requests over limit for group tag")
  1064  				return chain.ErrRequestsOverLimit
  1065  			}
  1066  		}
  1067  
  1068  		tc.incCounter(method)
  1069  		if tc.traceAPICalls {
  1070  			if len(params) > 0 {
  1071  				tc.t.Log(method, params)
  1072  			} else {
  1073  				tc.t.Log(method)
  1074  			}
  1075  		}
  1076  
  1077  		return nil
  1078  	}
  1079  	client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil)
  1080  	client.SetClient(tc.NetworkID(), tc)
  1081  	tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db))
  1082  	tokenManager.SetTokens([]*token.Token{
  1083  		{
  1084  			Address:  tokenTXXAddress,
  1085  			Symbol:   "TXX",
  1086  			Decimals: 18,
  1087  			ChainID:  tc.NetworkID(),
  1088  			Name:     "Test Token 1",
  1089  			Verified: true,
  1090  		},
  1091  		{
  1092  			Address:  tokenTXYAddress,
  1093  			Symbol:   "TXY",
  1094  			Decimals: 18,
  1095  			ChainID:  tc.NetworkID(),
  1096  			Name:     "Test Token 2",
  1097  			Verified: true,
  1098  		},
  1099  	})
  1100  	accDB, err := accounts.NewDB(appdb)
  1101  	require.NoError(t, err)
  1102  	blockRangeDAO := &BlockRangeSequentialDAO{wdb.client}
  1103  	fbc := &findBlocksCommand{
  1104  		accounts:                  []common.Address{accountAddress},
  1105  		db:                        wdb,
  1106  		blockRangeDAO:             blockRangeDAO,
  1107  		accountsDB:                accDB,
  1108  		chainClient:               tc,
  1109  		balanceCacher:             balance.NewCacherWithTTL(5 * time.Minute),
  1110  		feed:                      &event.Feed{},
  1111  		noLimit:                   false,
  1112  		fromBlockNumber:           fromBlock,
  1113  		toBlockNumber:             toBlock,
  1114  		blocksLoadedCh:            blockChannel,
  1115  		defaultNodeBlockChunkSize: rangeSize,
  1116  		tokenManager:              tokenManager,
  1117  	}
  1118  	return fbc, tc, blockChannel, blockRangeDAO
  1119  }
  1120  
  1121  func TestFindBlocksCommand(t *testing.T) {
  1122  	for idx, testCase := range getCases() {
  1123  		t.Log("case #", idx+1)
  1124  
  1125  		accountAddress := common.HexToAddress("0x1234")
  1126  		rangeSize := 20
  1127  		if testCase.rangeSize != 0 {
  1128  			rangeSize = testCase.rangeSize
  1129  		}
  1130  
  1131  		balances := map[common.Address][][]int{accountAddress: testCase.balanceChanges}
  1132  		outgoingERC20Transfers := map[common.Address][]testERC20Transfer{accountAddress: testCase.outgoingERC20Transfers}
  1133  		incomingERC20Transfers := map[common.Address][]testERC20Transfer{accountAddress: testCase.incomingERC20Transfers}
  1134  		outgoingERC1155SingleTransfers := map[common.Address][]testERC20Transfer{accountAddress: testCase.outgoingERC1155SingleTransfers}
  1135  		incomingERC1155SingleTransfers := map[common.Address][]testERC20Transfer{accountAddress: testCase.incomingERC1155SingleTransfers}
  1136  
  1137  		fbc, tc, blockChannel, blockRangeDAO := setupFindBlocksCommand(t, accountAddress, big.NewInt(testCase.fromBlock), big.NewInt(testCase.toBlock), rangeSize, balances, outgoingERC20Transfers, incomingERC20Transfers, outgoingERC1155SingleTransfers, incomingERC1155SingleTransfers)
  1138  		ctx := context.Background()
  1139  		group := async.NewGroup(ctx)
  1140  		group.Add(fbc.Command())
  1141  
  1142  		foundBlocks := []*DBHeader{}
  1143  		select {
  1144  		case <-ctx.Done():
  1145  			t.Log("ERROR")
  1146  		case <-group.WaitAsync():
  1147  			close(blockChannel)
  1148  			for {
  1149  				bloks, ok := <-blockChannel
  1150  				if !ok {
  1151  					break
  1152  				}
  1153  				foundBlocks = append(foundBlocks, bloks...)
  1154  			}
  1155  
  1156  			numbers := []int64{}
  1157  			for _, block := range foundBlocks {
  1158  				numbers = append(numbers, block.Number.Int64())
  1159  			}
  1160  
  1161  			if tc.traceAPICalls {
  1162  				tc.printCounter()
  1163  			}
  1164  
  1165  			for name, cnt := range testCase.expectedCalls {
  1166  				require.Equal(t, cnt, tc.callsCounter[name], "calls to "+name)
  1167  			}
  1168  
  1169  			sort.Slice(numbers, func(i, j int) bool { return numbers[i] < numbers[j] })
  1170  			require.Equal(t, testCase.expectedBlocksFound, len(foundBlocks), testCase.label, "found blocks", numbers)
  1171  
  1172  			blRange, _, err := blockRangeDAO.getBlockRange(tc.NetworkID(), accountAddress)
  1173  			require.NoError(t, err)
  1174  			require.NotNil(t, blRange.eth.FirstKnown)
  1175  			require.NotNil(t, blRange.tokens.FirstKnown)
  1176  			if testCase.fromBlock == 0 {
  1177  				require.Equal(t, 0, blRange.tokens.FirstKnown.Cmp(zero))
  1178  			}
  1179  		}
  1180  	}
  1181  }
  1182  
  1183  func TestFindBlocksCommandWithLimiter(t *testing.T) {
  1184  	maxRequests := 1
  1185  	rangeSize := 20
  1186  	accountAddress := common.HexToAddress("0x1234")
  1187  	balances := map[common.Address][][]int{accountAddress: {{5, 1, 0}, {20, 2, 0}, {45, 1, 1}, {46, 50, 0}, {75, 0, 1}}}
  1188  	fbc, tc, blockChannel, _ := setupFindBlocksCommand(t, accountAddress, big.NewInt(0), big.NewInt(20), rangeSize, balances, nil, nil, nil, nil)
  1189  
  1190  	limiter := chain.NewRequestLimiter(chain.NewInMemRequestsMapStorage())
  1191  	err := limiter.SetLimit(transferHistoryTag, maxRequests, time.Hour)
  1192  	require.NoError(t, err)
  1193  	tc.SetLimiter(limiter)
  1194  	tc.tag = transferHistoryTag
  1195  
  1196  	ctx := context.Background()
  1197  	group := async.NewAtomicGroup(ctx)
  1198  	group.Add(fbc.Command(1 * time.Millisecond))
  1199  
  1200  	select {
  1201  	case <-ctx.Done():
  1202  		t.Log("ERROR")
  1203  	case <-group.WaitAsync():
  1204  		close(blockChannel)
  1205  		require.Error(t, chain.ErrRequestsOverLimit, group.Error())
  1206  		require.Equal(t, maxRequests, tc.getCounter())
  1207  	}
  1208  }
  1209  
  1210  func TestFindBlocksCommandWithLimiterTagDifferentThanTransfers(t *testing.T) {
  1211  	rangeSize := 20
  1212  	maxRequests := 1
  1213  	accountAddress := common.HexToAddress("0x1234")
  1214  	balances := map[common.Address][][]int{accountAddress: {{5, 1, 0}, {20, 2, 0}, {45, 1, 1}, {46, 50, 0}, {75, 0, 1}}}
  1215  	outgoingERC20Transfers := map[common.Address][]testERC20Transfer{accountAddress: {{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}}}
  1216  	incomingERC20Transfers := map[common.Address][]testERC20Transfer{accountAddress: {{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}}}
  1217  
  1218  	fbc, tc, blockChannel, _ := setupFindBlocksCommand(t, accountAddress, big.NewInt(0), big.NewInt(20), rangeSize, balances, outgoingERC20Transfers, incomingERC20Transfers, nil, nil)
  1219  	limiter := chain.NewRequestLimiter(chain.NewInMemRequestsMapStorage())
  1220  	err := limiter.SetLimit("some-other-tag-than-transfer-history", maxRequests, time.Hour)
  1221  	require.NoError(t, err)
  1222  	tc.SetLimiter(limiter)
  1223  
  1224  	ctx := context.Background()
  1225  	group := async.NewAtomicGroup(ctx)
  1226  	group.Add(fbc.Command(1 * time.Millisecond))
  1227  
  1228  	select {
  1229  	case <-ctx.Done():
  1230  		t.Log("ERROR")
  1231  	case <-group.WaitAsync():
  1232  		close(blockChannel)
  1233  		require.NoError(t, group.Error())
  1234  		require.Greater(t, tc.getCounter(), maxRequests)
  1235  	}
  1236  }
  1237  
  1238  func TestFindBlocksCommandWithLimiterForMultipleAccountsSameGroup(t *testing.T) {
  1239  	rangeSize := 20
  1240  	maxRequestsTotal := 5
  1241  	limit1 := 3
  1242  	limit2 := 3
  1243  	account1 := common.HexToAddress("0x1234")
  1244  	account2 := common.HexToAddress("0x5678")
  1245  	balances := map[common.Address][][]int{account1: {{5, 1, 0}, {20, 2, 0}, {45, 1, 1}, {46, 50, 0}, {75, 0, 1}}, account2: {{5, 1, 0}, {20, 2, 0}, {45, 1, 1}, {46, 50, 0}, {75, 0, 1}}}
  1246  	outgoingERC20Transfers := map[common.Address][]testERC20Transfer{account1: {{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}}}
  1247  	incomingERC20Transfers := map[common.Address][]testERC20Transfer{account2: {{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}}}
  1248  
  1249  	// Limiters share the same storage
  1250  	storage := chain.NewInMemRequestsMapStorage()
  1251  
  1252  	// Set up the first account
  1253  	fbc, tc, blockChannel, _ := setupFindBlocksCommand(t, account1, big.NewInt(0), big.NewInt(20), rangeSize, balances, outgoingERC20Transfers, nil, nil, nil)
  1254  	tc.tag = transferHistoryTag + account1.String()
  1255  	tc.groupTag = transferHistoryTag
  1256  
  1257  	limiter1 := chain.NewRequestLimiter(storage)
  1258  	err := limiter1.SetLimit(transferHistoryTag, maxRequestsTotal, time.Hour)
  1259  	require.NoError(t, err)
  1260  	err = limiter1.SetLimit(transferHistoryTag+account1.String(), limit1, time.Hour)
  1261  	require.NoError(t, err)
  1262  	tc.SetLimiter(limiter1)
  1263  
  1264  	// Set up the second account
  1265  	fbc2, tc2, _, _ := setupFindBlocksCommand(t, account2, big.NewInt(0), big.NewInt(20), rangeSize, balances, nil, incomingERC20Transfers, nil, nil)
  1266  	tc2.tag = transferHistoryTag + account2.String()
  1267  	tc2.groupTag = transferHistoryTag
  1268  	limiter2 := chain.NewRequestLimiter(storage)
  1269  	err = limiter2.SetLimit(transferHistoryTag, maxRequestsTotal, time.Hour)
  1270  	require.NoError(t, err)
  1271  	err = limiter2.SetLimit(transferHistoryTag+account2.String(), limit2, time.Hour)
  1272  	require.NoError(t, err)
  1273  	tc2.SetLimiter(limiter2)
  1274  	fbc2.blocksLoadedCh = blockChannel
  1275  
  1276  	ctx := context.Background()
  1277  	group := async.NewGroup(ctx)
  1278  	group.Add(fbc.Command(1 * time.Millisecond))
  1279  	group.Add(fbc2.Command(1 * time.Millisecond))
  1280  
  1281  	select {
  1282  	case <-ctx.Done():
  1283  		t.Log("ERROR")
  1284  	case <-group.WaitAsync():
  1285  		close(blockChannel)
  1286  		require.LessOrEqual(t, tc.getCounter(), maxRequestsTotal)
  1287  	}
  1288  }
  1289  
  1290  type MockETHClient struct {
  1291  	mock.Mock
  1292  }
  1293  
  1294  func (m *MockETHClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
  1295  	args := m.Called(ctx, b)
  1296  	return args.Error(0)
  1297  }
  1298  
  1299  type MockChainClient struct {
  1300  	mock.Mock
  1301  	mock_client.MockClientInterface
  1302  
  1303  	clients map[walletcommon.ChainID]*MockETHClient
  1304  }
  1305  
  1306  func newMockChainClient() *MockChainClient {
  1307  	return &MockChainClient{
  1308  		clients: make(map[walletcommon.ChainID]*MockETHClient),
  1309  	}
  1310  }
  1311  
  1312  func (m *MockChainClient) AbstractEthClient(chainID walletcommon.ChainID) (chain.BatchCallClient, error) {
  1313  	if _, ok := m.clients[chainID]; !ok {
  1314  		panic(fmt.Sprintf("no mock client for chainID %d", chainID))
  1315  	}
  1316  	return m.clients[chainID], nil
  1317  }
  1318  
  1319  func TestFetchTransfersForLoadedBlocks(t *testing.T) {
  1320  	appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
  1321  	require.NoError(t, err)
  1322  
  1323  	db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
  1324  	require.NoError(t, err)
  1325  	tm := &TransactionManager{NewMultiTransactionDB(db), nil, nil, nil, nil, nil, nil, nil, nil, nil}
  1326  
  1327  	mediaServer, err := server.NewMediaServer(appdb, nil, nil, db)
  1328  	require.NoError(t, err)
  1329  
  1330  	wdb := NewDB(db)
  1331  	blockChannel := make(chan []*DBHeader, 100)
  1332  
  1333  	tc := &TestClient{
  1334  		t:                      t,
  1335  		balances:               map[common.Address][][]int{},
  1336  		outgoingERC20Transfers: map[common.Address][]testERC20Transfer{},
  1337  		incomingERC20Transfers: map[common.Address][]testERC20Transfer{},
  1338  		callsCounter:           map[string]int{},
  1339  		currentBlock:           100,
  1340  	}
  1341  
  1342  	client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil)
  1343  	client.SetClient(tc.NetworkID(), tc)
  1344  	tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db))
  1345  
  1346  	tokenManager.SetTokens([]*token.Token{
  1347  		{
  1348  			Address:  tokenTXXAddress,
  1349  			Symbol:   "TXX",
  1350  			Decimals: 18,
  1351  			ChainID:  tc.NetworkID(),
  1352  			Name:     "Test Token 1",
  1353  			Verified: true,
  1354  		},
  1355  		{
  1356  			Address:  tokenTXYAddress,
  1357  			Symbol:   "TXY",
  1358  			Decimals: 18,
  1359  			ChainID:  tc.NetworkID(),
  1360  			Name:     "Test Token 2",
  1361  			Verified: true,
  1362  		},
  1363  	})
  1364  
  1365  	address := common.HexToAddress("0x1234")
  1366  	chainClient := newMockChainClient()
  1367  	ctrl := gomock.NewController(t)
  1368  	defer ctrl.Finish()
  1369  	rpcClient := mock_rpcclient.NewMockClientInterface(ctrl)
  1370  	rpcClient.EXPECT().AbstractEthClient(tc.NetworkID()).Return(chainClient, nil).AnyTimes()
  1371  	tracker := transactions.NewPendingTxTracker(db, rpcClient, nil, &event.Feed{}, transactions.PendingCheckInterval)
  1372  	accDB, err := accounts.NewDB(appdb)
  1373  	require.NoError(t, err)
  1374  
  1375  	cmd := &loadBlocksAndTransfersCommand{
  1376  		accounts:           []common.Address{address},
  1377  		db:                 wdb,
  1378  		blockRangeDAO:      &BlockRangeSequentialDAO{wdb.client},
  1379  		blockDAO:           &BlockDAO{db},
  1380  		accountsDB:         accDB,
  1381  		chainClient:        tc,
  1382  		feed:               &event.Feed{},
  1383  		balanceCacher:      balance.NewCacherWithTTL(5 * time.Minute),
  1384  		transactionManager: tm,
  1385  		pendingTxManager:   tracker,
  1386  		tokenManager:       tokenManager,
  1387  		blocksLoadedCh:     blockChannel,
  1388  		omitHistory:        true,
  1389  		contractMaker:      tokenManager.ContractMaker,
  1390  	}
  1391  
  1392  	tc.prepareBalanceHistory(int(tc.currentBlock))
  1393  	tc.prepareTokenBalanceHistory(int(tc.currentBlock))
  1394  	// tc.traceAPICalls = true
  1395  
  1396  	ctx := context.Background()
  1397  	group := async.NewAtomicGroup(ctx)
  1398  
  1399  	fromNum := big.NewInt(0)
  1400  	toNum, err := getHeadBlockNumber(ctx, cmd.chainClient)
  1401  	require.NoError(t, err)
  1402  	err = cmd.fetchHistoryBlocksForAccount(group, address, fromNum, toNum, blockChannel)
  1403  	require.NoError(t, err)
  1404  
  1405  	select {
  1406  	case <-ctx.Done():
  1407  		t.Log("ERROR")
  1408  	case <-group.WaitAsync():
  1409  		require.Equal(t, 1, tc.getCounter())
  1410  	}
  1411  }
  1412  
  1413  func getNewBlocksCases() []findBlockCase {
  1414  	cases := []findBlockCase{
  1415  		findBlockCase{
  1416  			balanceChanges: [][]int{
  1417  				{20, 1, 0},
  1418  			},
  1419  			fromBlock:           0,
  1420  			toBlock:             10,
  1421  			expectedBlocksFound: 0,
  1422  			label:               "single block, but not in range",
  1423  		},
  1424  		findBlockCase{
  1425  			balanceChanges: [][]int{
  1426  				{20, 1, 0},
  1427  			},
  1428  			fromBlock:           10,
  1429  			toBlock:             20,
  1430  			expectedBlocksFound: 1,
  1431  			label:               "single block in range",
  1432  		},
  1433  	}
  1434  
  1435  	return cases
  1436  }
  1437  
  1438  func TestFetchNewBlocksCommand_findBlocksWithEthTransfers(t *testing.T) {
  1439  	appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
  1440  	require.NoError(t, err)
  1441  
  1442  	db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
  1443  	require.NoError(t, err)
  1444  
  1445  	mediaServer, err := server.NewMediaServer(appdb, nil, nil, db)
  1446  	require.NoError(t, err)
  1447  
  1448  	wdb := NewDB(db)
  1449  	blockChannel := make(chan []*DBHeader, 10)
  1450  
  1451  	address := common.HexToAddress("0x1234")
  1452  	accDB, err := accounts.NewDB(appdb)
  1453  	require.NoError(t, err)
  1454  
  1455  	for idx, testCase := range getNewBlocksCases() {
  1456  		t.Log("case #", idx+1)
  1457  		tc := &TestClient{
  1458  			t:                      t,
  1459  			balances:               map[common.Address][][]int{address: testCase.balanceChanges},
  1460  			outgoingERC20Transfers: map[common.Address][]testERC20Transfer{},
  1461  			incomingERC20Transfers: map[common.Address][]testERC20Transfer{},
  1462  			callsCounter:           map[string]int{},
  1463  			currentBlock:           100,
  1464  		}
  1465  
  1466  		client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil)
  1467  		client.SetClient(tc.NetworkID(), tc)
  1468  		tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db))
  1469  
  1470  		tokenManager.SetTokens([]*token.Token{
  1471  			{
  1472  				Address:  tokenTXXAddress,
  1473  				Symbol:   "TXX",
  1474  				Decimals: 18,
  1475  				ChainID:  tc.NetworkID(),
  1476  				Name:     "Test Token 1",
  1477  				Verified: true,
  1478  			},
  1479  			{
  1480  				Address:  tokenTXYAddress,
  1481  				Symbol:   "TXY",
  1482  				Decimals: 18,
  1483  				ChainID:  tc.NetworkID(),
  1484  				Name:     "Test Token 2",
  1485  				Verified: true,
  1486  			},
  1487  		})
  1488  
  1489  		cmd := &findNewBlocksCommand{
  1490  			findBlocksCommand: &findBlocksCommand{
  1491  				accounts:                  []common.Address{address},
  1492  				db:                        wdb,
  1493  				accountsDB:                accDB,
  1494  				blockRangeDAO:             &BlockRangeSequentialDAO{wdb.client},
  1495  				chainClient:               tc,
  1496  				balanceCacher:             balance.NewCacherWithTTL(5 * time.Minute),
  1497  				feed:                      &event.Feed{},
  1498  				noLimit:                   false,
  1499  				tokenManager:              tokenManager,
  1500  				blocksLoadedCh:            blockChannel,
  1501  				defaultNodeBlockChunkSize: DefaultNodeBlockChunkSize,
  1502  			},
  1503  			nonceCheckIntervalIterations: nonceCheckIntervalIterations,
  1504  			logsCheckIntervalIterations:  logsCheckIntervalIterations,
  1505  		}
  1506  		tc.prepareBalanceHistory(int(tc.currentBlock))
  1507  		tc.prepareTokenBalanceHistory(int(tc.currentBlock))
  1508  
  1509  		ctx := context.Background()
  1510  		blocks, _, err := cmd.findBlocksWithEthTransfers(ctx, address, big.NewInt(testCase.fromBlock), big.NewInt(testCase.toBlock))
  1511  		require.NoError(t, err)
  1512  		require.Equal(t, testCase.expectedBlocksFound, len(blocks), fmt.Sprintf("case %d: %s, blocks from %d to %d", idx+1, testCase.label, testCase.fromBlock, testCase.toBlock))
  1513  	}
  1514  }
  1515  
  1516  func TestFetchNewBlocksCommand_nonceDetection(t *testing.T) {
  1517  	balanceChanges := [][]int{
  1518  		{5, 1, 0},
  1519  		{6, 0, 1},
  1520  	}
  1521  
  1522  	scanRange := 5
  1523  	address := common.HexToAddress("0x1234")
  1524  
  1525  	tc := &TestClient{
  1526  		t:                      t,
  1527  		balances:               map[common.Address][][]int{address: balanceChanges},
  1528  		outgoingERC20Transfers: map[common.Address][]testERC20Transfer{},
  1529  		incomingERC20Transfers: map[common.Address][]testERC20Transfer{},
  1530  		callsCounter:           map[string]int{},
  1531  		currentBlock:           0,
  1532  	}
  1533  
  1534  	//tc.printPreparedData = true
  1535  	tc.prepareBalanceHistory(20)
  1536  
  1537  	appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
  1538  	require.NoError(t, err)
  1539  
  1540  	db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
  1541  	require.NoError(t, err)
  1542  
  1543  	mediaServer, err := server.NewMediaServer(appdb, nil, nil, db)
  1544  	require.NoError(t, err)
  1545  
  1546  	client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil)
  1547  	client.SetClient(tc.NetworkID(), tc)
  1548  	tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db))
  1549  
  1550  	wdb := NewDB(db)
  1551  	blockChannel := make(chan []*DBHeader, 10)
  1552  
  1553  	accDB, err := accounts.NewDB(appdb)
  1554  	require.NoError(t, err)
  1555  
  1556  	maker, _ := contracts.NewContractMaker(client)
  1557  
  1558  	cmd := &findNewBlocksCommand{
  1559  		findBlocksCommand: &findBlocksCommand{
  1560  			accounts:                  []common.Address{address},
  1561  			db:                        wdb,
  1562  			accountsDB:                accDB,
  1563  			blockRangeDAO:             &BlockRangeSequentialDAO{wdb.client},
  1564  			chainClient:               tc,
  1565  			balanceCacher:             balance.NewCacherWithTTL(5 * time.Minute),
  1566  			feed:                      &event.Feed{},
  1567  			noLimit:                   false,
  1568  			tokenManager:              tokenManager,
  1569  			blocksLoadedCh:            blockChannel,
  1570  			defaultNodeBlockChunkSize: scanRange,
  1571  			fromBlockNumber:           big.NewInt(0),
  1572  		},
  1573  		blockChainState:              blockchainstate.NewBlockChainState(),
  1574  		contractMaker:                maker,
  1575  		nonceCheckIntervalIterations: 2,
  1576  		logsCheckIntervalIterations:  2,
  1577  	}
  1578  
  1579  	acc := &accounts.Account{
  1580  		Address: ethtypes.BytesToAddress(address.Bytes()),
  1581  		Type:    accounts.AccountTypeWatch,
  1582  		Name:    address.String(),
  1583  		ColorID: multicommon.CustomizationColorPrimary,
  1584  		Emoji:   "emoji",
  1585  	}
  1586  	err = accDB.SaveOrUpdateAccounts([]*accounts.Account{acc}, false)
  1587  	require.NoError(t, err)
  1588  
  1589  	ctx := context.Background()
  1590  	tc.currentBlock = 3
  1591  	for i := 0; i < 3; i++ {
  1592  		err := cmd.Run(ctx)
  1593  		require.NoError(t, err)
  1594  		close(blockChannel)
  1595  
  1596  		foundBlocks := []*DBHeader{}
  1597  		for {
  1598  			bloks, ok := <-blockChannel
  1599  			if !ok {
  1600  				break
  1601  			}
  1602  			foundBlocks = append(foundBlocks, bloks...)
  1603  		}
  1604  
  1605  		numbers := []int64{}
  1606  		for _, block := range foundBlocks {
  1607  			numbers = append(numbers, block.Number.Int64())
  1608  		}
  1609  		if i == 2 {
  1610  			require.Equal(t, 2, len(foundBlocks), "blocks", numbers)
  1611  		} else {
  1612  			require.Equal(t, 0, len(foundBlocks), "no blocks expected to be found")
  1613  		}
  1614  		blockChannel = make(chan []*DBHeader, 10)
  1615  		cmd.blocksLoadedCh = blockChannel
  1616  		tc.currentBlock += uint64(scanRange)
  1617  	}
  1618  }
  1619  
  1620  func TestFetchNewBlocksCommand(t *testing.T) {
  1621  	appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
  1622  	require.NoError(t, err)
  1623  
  1624  	db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
  1625  	require.NoError(t, err)
  1626  
  1627  	mediaServer, err := server.NewMediaServer(appdb, nil, nil, db)
  1628  	require.NoError(t, err)
  1629  
  1630  	wdb := NewDB(db)
  1631  	blockChannel := make(chan []*DBHeader, 10)
  1632  
  1633  	address1 := common.HexToAddress("0x1234")
  1634  	address2 := common.HexToAddress("0x5678")
  1635  	accDB, err := accounts.NewDB(appdb)
  1636  	require.NoError(t, err)
  1637  
  1638  	for _, address := range []*common.Address{&address1, &address2} {
  1639  		acc := &accounts.Account{
  1640  			Address: ethtypes.BytesToAddress(address.Bytes()),
  1641  			Type:    accounts.AccountTypeWatch,
  1642  			Name:    address.String(),
  1643  			ColorID: multicommon.CustomizationColorPrimary,
  1644  			Emoji:   "emoji",
  1645  		}
  1646  		err = accDB.SaveOrUpdateAccounts([]*accounts.Account{acc}, false)
  1647  		require.NoError(t, err)
  1648  	}
  1649  
  1650  	tc := &TestClient{
  1651  		t:                      t,
  1652  		balances:               map[common.Address][][]int{},
  1653  		outgoingERC20Transfers: map[common.Address][]testERC20Transfer{},
  1654  		incomingERC20Transfers: map[common.Address][]testERC20Transfer{},
  1655  		callsCounter:           map[string]int{},
  1656  		currentBlock:           1,
  1657  	}
  1658  	//tc.printPreparedData = true
  1659  
  1660  	client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil)
  1661  	client.SetClient(tc.NetworkID(), tc)
  1662  
  1663  	tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db))
  1664  
  1665  	tokenManager.SetTokens([]*token.Token{
  1666  		{
  1667  			Address:  tokenTXXAddress,
  1668  			Symbol:   "TXX",
  1669  			Decimals: 18,
  1670  			ChainID:  tc.NetworkID(),
  1671  			Name:     "Test Token 1",
  1672  			Verified: true,
  1673  		},
  1674  		{
  1675  			Address:  tokenTXYAddress,
  1676  			Symbol:   "TXY",
  1677  			Decimals: 18,
  1678  			ChainID:  tc.NetworkID(),
  1679  			Name:     "Test Token 2",
  1680  			Verified: true,
  1681  		},
  1682  	})
  1683  
  1684  	cmd := &findNewBlocksCommand{
  1685  		findBlocksCommand: &findBlocksCommand{
  1686  			accounts:                  []common.Address{address1, address2},
  1687  			db:                        wdb,
  1688  			accountsDB:                accDB,
  1689  			blockRangeDAO:             &BlockRangeSequentialDAO{wdb.client},
  1690  			chainClient:               tc,
  1691  			balanceCacher:             balance.NewCacherWithTTL(5 * time.Minute),
  1692  			feed:                      &event.Feed{},
  1693  			noLimit:                   false,
  1694  			fromBlockNumber:           big.NewInt(int64(tc.currentBlock)),
  1695  			tokenManager:              tokenManager,
  1696  			blocksLoadedCh:            blockChannel,
  1697  			defaultNodeBlockChunkSize: DefaultNodeBlockChunkSize,
  1698  		},
  1699  		contractMaker:                tokenManager.ContractMaker,
  1700  		blockChainState:              blockchainstate.NewBlockChainState(),
  1701  		nonceCheckIntervalIterations: nonceCheckIntervalIterations,
  1702  		logsCheckIntervalIterations:  logsCheckIntervalIterations,
  1703  	}
  1704  
  1705  	ctx := context.Background()
  1706  
  1707  	// I don't prepare lots of data and a loop, as I just need to verify a few cases
  1708  
  1709  	// Verify that cmd.fromBlockNumber stays the same
  1710  	tc.prepareBalanceHistory(int(tc.currentBlock))
  1711  	tc.prepareTokenBalanceHistory(int(tc.currentBlock))
  1712  	err = cmd.Run(ctx)
  1713  	require.NoError(t, err)
  1714  	require.Equal(t, uint64(1), cmd.fromBlockNumber.Uint64())
  1715  
  1716  	// Verify that cmd.fromBlockNumber is incremented, equal to the head block number
  1717  	tc.currentBlock = 2 // this is the head block number that will be returned by the mock client
  1718  	tc.prepareBalanceHistory(int(tc.currentBlock))
  1719  	tc.prepareTokenBalanceHistory(int(tc.currentBlock))
  1720  	err = cmd.Run(ctx)
  1721  	require.NoError(t, err)
  1722  	require.Equal(t, tc.currentBlock, cmd.fromBlockNumber.Uint64())
  1723  
  1724  	// Verify that blocks are found and cmd.fromBlockNumber is incremented
  1725  	tc.resetCounter()
  1726  	tc.currentBlock = 3
  1727  	tc.balances = map[common.Address][][]int{
  1728  		address1: {{3, 1, 0}},
  1729  		address2: {{3, 1, 0}},
  1730  	}
  1731  	tc.incomingERC20Transfers = map[common.Address][]testERC20Transfer{
  1732  		address1: {{big.NewInt(3), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}},
  1733  		address2: {{big.NewInt(3), tokenTXYAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}},
  1734  	}
  1735  	tc.prepareBalanceHistory(int(tc.currentBlock))
  1736  	tc.prepareTokenBalanceHistory(int(tc.currentBlock))
  1737  
  1738  	group := async.NewGroup(ctx)
  1739  	group.Add(cmd.Command()) // This is an infinite command, I can't use WaitAsync() here to wait for it to finish
  1740  
  1741  	expectedBlocksNumber := 3 // ETH block is found twice for each account as we don't handle addresses in MockClient. A block with ERC20 transfer is found once
  1742  	blocksFound := 0
  1743  	stop := false
  1744  	for stop == false {
  1745  		select {
  1746  		case <-ctx.Done():
  1747  			require.Fail(t, "context done")
  1748  			stop = true
  1749  		case <-blockChannel:
  1750  			blocksFound++
  1751  		case <-time.After(100 * time.Millisecond):
  1752  			stop = true
  1753  		}
  1754  	}
  1755  	group.Stop()
  1756  	group.Wait()
  1757  	require.Equal(t, expectedBlocksNumber, blocksFound)
  1758  	require.Equal(t, tc.currentBlock, cmd.fromBlockNumber.Uint64())
  1759  	// We must check all the logs for all accounts with a single iteration of eth_getLogs call
  1760  	require.Equal(t, 3, tc.callsCounter["FilterLogs"], "calls to FilterLogs")
  1761  }
  1762  
  1763  type TestClientWithError struct {
  1764  	*TestClient
  1765  }
  1766  
  1767  func (tc *TestClientWithError) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
  1768  	tc.incCounter("BlockByNumber")
  1769  	if tc.traceAPICalls {
  1770  		tc.t.Log("BlockByNumber", number)
  1771  	}
  1772  
  1773  	return nil, errors.New("Network error")
  1774  }
  1775  
  1776  type BlockRangeSequentialDAOMockError struct {
  1777  	*BlockRangeSequentialDAO
  1778  }
  1779  
  1780  func (b *BlockRangeSequentialDAOMockError) getBlockRange(chainID uint64, address common.Address) (blockRange *ethTokensBlockRanges, exists bool, err error) {
  1781  	return nil, true, errors.New("DB error")
  1782  }
  1783  
  1784  type BlockRangeSequentialDAOMockSuccess struct {
  1785  	*BlockRangeSequentialDAO
  1786  }
  1787  
  1788  func (b *BlockRangeSequentialDAOMockSuccess) getBlockRange(chainID uint64, address common.Address) (blockRange *ethTokensBlockRanges, exists bool, err error) {
  1789  	return newEthTokensBlockRanges(), true, nil
  1790  }
  1791  
  1792  func TestLoadBlocksAndTransfersCommand_FiniteFinishedInfiniteRunning(t *testing.T) {
  1793  	appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
  1794  	require.NoError(t, err)
  1795  
  1796  	db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
  1797  	require.NoError(t, err)
  1798  
  1799  	client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil)
  1800  	maker, _ := contracts.NewContractMaker(client)
  1801  
  1802  	wdb := NewDB(db)
  1803  	tc := &TestClient{
  1804  		t:            t,
  1805  		callsCounter: map[string]int{},
  1806  	}
  1807  	accDB, err := accounts.NewDB(appdb)
  1808  	require.NoError(t, err)
  1809  
  1810  	cmd := &loadBlocksAndTransfersCommand{
  1811  		accounts:    []common.Address{common.HexToAddress("0x1234")},
  1812  		chainClient: tc,
  1813  		blockDAO:    &BlockDAO{db},
  1814  		blockRangeDAO: &BlockRangeSequentialDAOMockSuccess{
  1815  			&BlockRangeSequentialDAO{
  1816  				wdb.client,
  1817  			},
  1818  		},
  1819  		accountsDB:    accDB,
  1820  		db:            wdb,
  1821  		contractMaker: maker,
  1822  	}
  1823  
  1824  	ctx, cancel := context.WithCancel(context.Background())
  1825  	group := async.NewGroup(ctx)
  1826  
  1827  	group.Add(cmd.Command(1 * time.Millisecond))
  1828  
  1829  	select {
  1830  	case <-ctx.Done():
  1831  		cancel() // linter is not happy if cancel is not called on all code paths
  1832  		t.Log("Done")
  1833  	case <-group.WaitAsync():
  1834  		require.True(t, cmd.isStarted())
  1835  
  1836  		// Test that it stops if canceled
  1837  		cancel()
  1838  		require.NoError(t, utils.Eventually(func() error {
  1839  			if !cmd.isStarted() {
  1840  				return nil
  1841  			}
  1842  			return errors.New("command is still running")
  1843  		}, 100*time.Millisecond, 10*time.Millisecond))
  1844  	}
  1845  }
  1846  
  1847  func TestTransfersCommand_RetryAndQuitOnMaxError(t *testing.T) {
  1848  	tc := &TestClientWithError{
  1849  		&TestClient{
  1850  			t:            t,
  1851  			callsCounter: map[string]int{},
  1852  		},
  1853  	}
  1854  
  1855  	address := common.HexToAddress("0x1234")
  1856  	cmd := &transfersCommand{
  1857  		chainClient: tc,
  1858  		address:     address,
  1859  		eth: &ETHDownloader{
  1860  			chainClient: tc,
  1861  			accounts:    []common.Address{address},
  1862  		},
  1863  		blockNums: []*big.Int{big.NewInt(1)},
  1864  	}
  1865  
  1866  	ctx := context.Background()
  1867  	group := async.NewGroup(ctx)
  1868  
  1869  	runner := cmd.Runner(1 * time.Millisecond)
  1870  	group.Add(runner.Run)
  1871  
  1872  	select {
  1873  	case <-ctx.Done():
  1874  		t.Log("Done")
  1875  	case <-group.WaitAsync():
  1876  		errorCounter := runner.(async.FiniteCommandWithErrorCounter).ErrorCounter
  1877  		require.Equal(t, errorCounter.MaxErrors(), tc.callsCounter["BlockByNumber"])
  1878  
  1879  		_, expectedErr := tc.BlockByNumber(context.TODO(), nil)
  1880  		require.Error(t, expectedErr, errorCounter.Error())
  1881  	}
  1882  }