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

     1  package transfer
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"math/big"
     7  	"time"
     8  
     9  	"golang.org/x/exp/slices" // since 1.21, this is in the standard library
    10  
    11  	"github.com/ethereum/go-ethereum"
    12  	"github.com/ethereum/go-ethereum/common"
    13  	"github.com/ethereum/go-ethereum/core"
    14  	"github.com/ethereum/go-ethereum/core/types"
    15  	"github.com/ethereum/go-ethereum/log"
    16  
    17  	"github.com/status-im/status-go/rpc/chain"
    18  	w_common "github.com/status-im/status-go/services/wallet/common"
    19  )
    20  
    21  var (
    22  	zero = big.NewInt(0)
    23  	one  = big.NewInt(1)
    24  	two  = big.NewInt(2)
    25  )
    26  
    27  // Partial transaction info obtained by ERC20Downloader.
    28  // A PreloadedTransaction represents a Transaction which contains one
    29  // ERC20/ERC721/ERC1155 transfer event.
    30  // To be converted into one Transfer object post-indexing.
    31  type PreloadedTransaction struct {
    32  	Type    w_common.Type  `json:"type"`
    33  	ID      common.Hash    `json:"-"`
    34  	Address common.Address `json:"address"`
    35  	// Log that was used to generate preloaded transaction.
    36  	Log     *types.Log `json:"log"`
    37  	TokenID *big.Int   `json:"tokenId"`
    38  	Value   *big.Int   `json:"value"`
    39  }
    40  
    41  // Transfer stores information about transfer.
    42  // A Transfer represents a plain ETH transfer or some token activity inside a Transaction
    43  // Since ERC1155 transfers can contain multiple tokens, a single Transfer represents a single token transfer,
    44  // that means ERC1155 batch transfers will be represented by multiple Transfer objects.
    45  type Transfer struct {
    46  	Type        w_common.Type      `json:"type"`
    47  	ID          common.Hash        `json:"-"`
    48  	Address     common.Address     `json:"address"`
    49  	BlockNumber *big.Int           `json:"blockNumber"`
    50  	BlockHash   common.Hash        `json:"blockhash"`
    51  	Timestamp   uint64             `json:"timestamp"`
    52  	Transaction *types.Transaction `json:"transaction"`
    53  	Loaded      bool
    54  	NetworkID   uint64
    55  	// From is derived from tx signature in order to offload this computation from UI component.
    56  	From    common.Address `json:"from"`
    57  	Receipt *types.Receipt `json:"receipt"`
    58  	// Log that was used to generate erc20 transfer. Nil for eth transfer.
    59  	Log *types.Log `json:"log"`
    60  	// TokenID is the id of the transferred token. Nil for eth transfer.
    61  	TokenID *big.Int `json:"tokenId"`
    62  	// TokenValue is the value of the token transfer. Nil for eth transfer.
    63  	TokenValue  *big.Int `json:"tokenValue"`
    64  	BaseGasFees string
    65  	// Internal field that is used to track multi-transaction transfers.
    66  	MultiTransactionID w_common.MultiTransactionIDType `json:"multi_transaction_id"`
    67  }
    68  
    69  // ETHDownloader downloads regular eth transfers and tokens transfers.
    70  type ETHDownloader struct {
    71  	chainClient chain.ClientInterface
    72  	accounts    []common.Address
    73  	signer      types.Signer
    74  	db          *Database
    75  }
    76  
    77  var errLogsDownloaderStuck = errors.New("logs downloader stuck")
    78  
    79  func (d *ETHDownloader) GetTransfersByNumber(ctx context.Context, number *big.Int) ([]Transfer, error) {
    80  	blk, err := d.chainClient.BlockByNumber(ctx, number)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	rst, err := d.getTransfersInBlock(ctx, blk, d.accounts)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	return rst, err
    89  }
    90  
    91  // Only used by status-mobile
    92  func getTransferByHash(ctx context.Context, client chain.ClientInterface, signer types.Signer, address common.Address, hash common.Hash) (*Transfer, error) {
    93  	transaction, _, err := client.TransactionByHash(ctx, hash)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	receipt, err := client.TransactionReceipt(ctx, hash)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	eventType, transactionLog := w_common.GetFirstEvent(receipt.Logs)
   104  	transactionType := w_common.EventTypeToSubtransactionType(eventType)
   105  
   106  	from, err := types.Sender(signer, transaction)
   107  
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	baseGasFee, err := client.GetBaseFeeFromBlock(ctx, big.NewInt(int64(transactionLog.BlockNumber)))
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	transfer := &Transfer{
   118  		Type:        transactionType,
   119  		ID:          hash,
   120  		Address:     address,
   121  		BlockNumber: receipt.BlockNumber,
   122  		BlockHash:   receipt.BlockHash,
   123  		Timestamp:   uint64(time.Now().Unix()),
   124  		Transaction: transaction,
   125  		From:        from,
   126  		Receipt:     receipt,
   127  		Log:         transactionLog,
   128  		BaseGasFees: baseGasFee,
   129  	}
   130  
   131  	return transfer, nil
   132  }
   133  
   134  func (d *ETHDownloader) getTransfersInBlock(ctx context.Context, blk *types.Block, accounts []common.Address) ([]Transfer, error) {
   135  	startTs := time.Now()
   136  
   137  	rst := make([]Transfer, 0, len(blk.Transactions()))
   138  
   139  	receiptsByAddressAndTxHash := make(map[common.Address]map[common.Hash]*types.Receipt)
   140  	txsByAddressAndTxHash := make(map[common.Address]map[common.Hash]*types.Transaction)
   141  
   142  	addReceiptToCache := func(address common.Address, txHash common.Hash, receipt *types.Receipt) {
   143  		if receiptsByAddressAndTxHash[address] == nil {
   144  			receiptsByAddressAndTxHash[address] = make(map[common.Hash]*types.Receipt)
   145  		}
   146  		receiptsByAddressAndTxHash[address][txHash] = receipt
   147  	}
   148  
   149  	addTxToCache := func(address common.Address, txHash common.Hash, tx *types.Transaction) {
   150  		if txsByAddressAndTxHash[address] == nil {
   151  			txsByAddressAndTxHash[address] = make(map[common.Hash]*types.Transaction)
   152  		}
   153  		txsByAddressAndTxHash[address][txHash] = tx
   154  	}
   155  
   156  	getReceiptFromCache := func(address common.Address, txHash common.Hash) *types.Receipt {
   157  		if receiptsByAddressAndTxHash[address] == nil {
   158  			return nil
   159  		}
   160  		return receiptsByAddressAndTxHash[address][txHash]
   161  	}
   162  
   163  	getTxFromCache := func(address common.Address, txHash common.Hash) *types.Transaction {
   164  		if txsByAddressAndTxHash[address] == nil {
   165  			return nil
   166  		}
   167  		return txsByAddressAndTxHash[address][txHash]
   168  	}
   169  
   170  	getReceipt := func(address common.Address, txHash common.Hash) (receipt *types.Receipt, err error) {
   171  		receipt = getReceiptFromCache(address, txHash)
   172  		if receipt == nil {
   173  			receipt, err = d.fetchTransactionReceipt(ctx, txHash)
   174  			if err != nil {
   175  				return nil, err
   176  			}
   177  			addReceiptToCache(address, txHash, receipt)
   178  		}
   179  		return receipt, nil
   180  	}
   181  
   182  	getTx := func(address common.Address, txHash common.Hash) (tx *types.Transaction, err error) {
   183  		tx = getTxFromCache(address, txHash)
   184  		if tx == nil {
   185  			tx, err = d.fetchTransaction(ctx, txHash)
   186  			if err != nil {
   187  				return nil, err
   188  			}
   189  			addTxToCache(address, txHash, tx)
   190  		}
   191  		return tx, nil
   192  	}
   193  
   194  	for _, address := range accounts {
   195  		// During block discovery, we should have populated the DB with 1 item per transfer log containing
   196  		// erc20/erc721/erc1155 transfers.
   197  		// ID is a hash of the tx hash and the log index. log_index is unique per ERC20/721 tx, but not per ERC1155 tx.
   198  		transactionsToLoad, err := d.db.GetTransactionsToLoad(d.chainClient.NetworkID(), address, blk.Number())
   199  		if err != nil {
   200  			return nil, err
   201  		}
   202  
   203  		areSubTxsCheckedForTxHash := make(map[common.Hash]bool)
   204  
   205  		log.Debug("getTransfersInBlock", "block", blk.Number(), "transactionsToLoad", len(transactionsToLoad))
   206  
   207  		for _, t := range transactionsToLoad {
   208  			receipt, err := getReceipt(address, t.Log.TxHash)
   209  			if err != nil {
   210  				return nil, err
   211  			}
   212  
   213  			tx, err := getTx(address, t.Log.TxHash)
   214  			if err != nil {
   215  				return nil, err
   216  			}
   217  
   218  			subtransactions, err := d.subTransactionsFromPreloaded(t, tx, receipt, blk)
   219  			if err != nil {
   220  				log.Error("can't fetch subTxs for erc20/erc721/erc1155 transfer", "error", err)
   221  				return nil, err
   222  			}
   223  			rst = append(rst, subtransactions...)
   224  			areSubTxsCheckedForTxHash[t.Log.TxHash] = true
   225  		}
   226  
   227  		for _, tx := range blk.Transactions() {
   228  			// Skip dummy blob transactions, as they are not supported by us
   229  			if tx.Type() == types.BlobTxType {
   230  				continue
   231  			}
   232  			if tx.ChainId().Cmp(big.NewInt(0)) != 0 && tx.ChainId().Cmp(d.chainClient.ToBigInt()) != 0 {
   233  				log.Info("chain id mismatch", "tx hash", tx.Hash(), "tx chain id", tx.ChainId(), "expected chain id", d.chainClient.NetworkID())
   234  				continue
   235  			}
   236  			from, err := types.Sender(d.signer, tx)
   237  
   238  			if err != nil {
   239  				if err == core.ErrTxTypeNotSupported {
   240  					log.Error("Tx Type not supported", "tx chain id", tx.ChainId(), "type", tx.Type(), "error", err)
   241  					continue
   242  				}
   243  				return nil, err
   244  			}
   245  
   246  			isPlainTransfer := from == address || (tx.To() != nil && *tx.To() == address)
   247  			mustCheckSubTxs := false
   248  
   249  			if !isPlainTransfer {
   250  				// We might miss some subTransactions of interest for some transaction types. We need to check if we
   251  				// find the address in the transaction data.
   252  				switch tx.Type() {
   253  				case types.DynamicFeeTxType, types.OptimismDepositTxType, types.ArbitrumDepositTxType, types.ArbitrumRetryTxType:
   254  					mustCheckSubTxs = !areSubTxsCheckedForTxHash[tx.Hash()] && w_common.TxDataContainsAddress(tx.Type(), tx.Data(), address)
   255  				}
   256  			}
   257  
   258  			if isPlainTransfer || mustCheckSubTxs {
   259  				receipt, err := getReceipt(address, tx.Hash())
   260  				if err != nil {
   261  					return nil, err
   262  				}
   263  
   264  				// Since we've already got the receipt, check for subTxs of
   265  				// interest in case we haven't already.
   266  				if !areSubTxsCheckedForTxHash[tx.Hash()] {
   267  					subtransactions, err := d.subTransactionsFromTransactionData(address, from, tx, receipt, blk)
   268  					if err != nil {
   269  						log.Error("can't fetch subTxs for eth transfer", "error", err)
   270  						return nil, err
   271  					}
   272  					rst = append(rst, subtransactions...)
   273  					areSubTxsCheckedForTxHash[tx.Hash()] = true
   274  				}
   275  
   276  				// If it's a plain ETH transfer, add it to the list
   277  				if isPlainTransfer {
   278  					rst = append(rst, Transfer{
   279  						Type:               w_common.EthTransfer,
   280  						NetworkID:          tx.ChainId().Uint64(),
   281  						ID:                 tx.Hash(),
   282  						Address:            address,
   283  						BlockNumber:        blk.Number(),
   284  						BlockHash:          receipt.BlockHash,
   285  						Timestamp:          blk.Time(),
   286  						Transaction:        tx,
   287  						From:               from,
   288  						Receipt:            receipt,
   289  						Log:                nil,
   290  						BaseGasFees:        blk.BaseFee().String(),
   291  						MultiTransactionID: w_common.NoMultiTransactionID})
   292  				}
   293  			}
   294  		}
   295  	}
   296  	log.Debug("getTransfersInBlock found", "block", blk.Number(), "len", len(rst), "time", time.Since(startTs))
   297  	// TODO(dshulyak) test that balance difference was covered by transactions
   298  	return rst, nil
   299  }
   300  
   301  // NewERC20TransfersDownloader returns new instance.
   302  func NewERC20TransfersDownloader(client chain.ClientInterface, accounts []common.Address, signer types.Signer, incomingOnly bool) *ERC20TransfersDownloader {
   303  	signature := w_common.GetEventSignatureHash(w_common.Erc20_721TransferEventSignature)
   304  
   305  	return &ERC20TransfersDownloader{
   306  		client:                 client,
   307  		accounts:               accounts,
   308  		signature:              signature,
   309  		incomingOnly:           incomingOnly,
   310  		signatureErc1155Single: w_common.GetEventSignatureHash(w_common.Erc1155TransferSingleEventSignature),
   311  		signatureErc1155Batch:  w_common.GetEventSignatureHash(w_common.Erc1155TransferBatchEventSignature),
   312  		signer:                 signer,
   313  	}
   314  }
   315  
   316  // ERC20TransfersDownloader is a downloader for erc20 and erc721 tokens transfers.
   317  // Since both transaction types share the same signature, both will be assigned
   318  // type Erc20Transfer. Until the downloader gets refactored and a migration of the
   319  // database gets implemented, differentiation between erc20 and erc721 will handled
   320  // in the controller.
   321  type ERC20TransfersDownloader struct {
   322  	client       chain.ClientInterface
   323  	accounts     []common.Address
   324  	incomingOnly bool
   325  
   326  	// hash of the Transfer event signature
   327  	signature              common.Hash
   328  	signatureErc1155Single common.Hash
   329  	signatureErc1155Batch  common.Hash
   330  
   331  	// signer is used to derive tx sender from tx signature
   332  	signer types.Signer
   333  }
   334  
   335  func topicFromAddressSlice(addresses []common.Address) []common.Hash {
   336  	rst := make([]common.Hash, len(addresses))
   337  	for i, address := range addresses {
   338  		rst[i] = common.BytesToHash(address.Bytes())
   339  	}
   340  	return rst
   341  }
   342  
   343  func (d *ERC20TransfersDownloader) inboundTopics(addresses []common.Address) [][]common.Hash {
   344  	return [][]common.Hash{{d.signature}, {}, topicFromAddressSlice(addresses)}
   345  }
   346  
   347  func (d *ERC20TransfersDownloader) outboundTopics(addresses []common.Address) [][]common.Hash {
   348  	return [][]common.Hash{{d.signature}, topicFromAddressSlice(addresses), {}}
   349  }
   350  
   351  func (d *ERC20TransfersDownloader) inboundERC20OutboundERC1155Topics(addresses []common.Address) [][]common.Hash {
   352  	return [][]common.Hash{{d.signature, d.signatureErc1155Single, d.signatureErc1155Batch}, {}, topicFromAddressSlice(addresses)}
   353  }
   354  
   355  func (d *ERC20TransfersDownloader) inboundTopicsERC1155(addresses []common.Address) [][]common.Hash {
   356  	return [][]common.Hash{{d.signatureErc1155Single, d.signatureErc1155Batch}, {}, {}, topicFromAddressSlice(addresses)}
   357  }
   358  
   359  func (d *ETHDownloader) fetchTransactionReceipt(parent context.Context, txHash common.Hash) (*types.Receipt, error) {
   360  	ctx, cancel := context.WithTimeout(parent, 3*time.Second)
   361  	receipt, err := d.chainClient.TransactionReceipt(ctx, txHash)
   362  	cancel()
   363  	if err != nil {
   364  		return nil, err
   365  	}
   366  	return receipt, nil
   367  }
   368  
   369  func (d *ETHDownloader) fetchTransaction(parent context.Context, txHash common.Hash) (*types.Transaction, error) {
   370  	ctx, cancel := context.WithTimeout(parent, 3*time.Second)
   371  	tx, _, err := d.chainClient.TransactionByHash(ctx, txHash) // TODO Save on requests by checking in the DB first
   372  	cancel()
   373  	if err != nil {
   374  		return nil, err
   375  	}
   376  	return tx, nil
   377  }
   378  
   379  func (d *ETHDownloader) subTransactionsFromPreloaded(preloadedTx *PreloadedTransaction, tx *types.Transaction, receipt *types.Receipt, blk *types.Block) ([]Transfer, error) {
   380  	log.Debug("subTransactionsFromPreloaded start", "txHash", tx.Hash().Hex(), "address", preloadedTx.Address, "tokenID", preloadedTx.TokenID, "value", preloadedTx.Value)
   381  	address := preloadedTx.Address
   382  	txLog := preloadedTx.Log
   383  
   384  	rst := make([]Transfer, 0, 1)
   385  
   386  	from, err := types.Sender(d.signer, tx)
   387  	if err != nil {
   388  		if err == core.ErrTxTypeNotSupported {
   389  			return nil, nil
   390  		}
   391  		return nil, err
   392  	}
   393  
   394  	eventType := w_common.GetEventType(preloadedTx.Log)
   395  	// Only add ERC20/ERC721/ERC1155 transfers from/to the given account
   396  	// from/to matching is already handled by getLogs filter
   397  	switch eventType {
   398  	case w_common.Erc20TransferEventType,
   399  		w_common.Erc721TransferEventType,
   400  		w_common.Erc1155TransferSingleEventType, w_common.Erc1155TransferBatchEventType:
   401  		log.Debug("subTransactionsFromPreloaded transfer", "eventType", eventType, "logIdx", txLog.Index, "txHash", tx.Hash().Hex(), "address", address.Hex(), "tokenID", preloadedTx.TokenID, "value", preloadedTx.Value, "baseFee", blk.BaseFee().String())
   402  
   403  		transfer := Transfer{
   404  			Type:               w_common.EventTypeToSubtransactionType(eventType),
   405  			ID:                 preloadedTx.ID,
   406  			Address:            address,
   407  			BlockNumber:        new(big.Int).SetUint64(txLog.BlockNumber),
   408  			BlockHash:          txLog.BlockHash,
   409  			Loaded:             true,
   410  			NetworkID:          d.signer.ChainID().Uint64(),
   411  			From:               from,
   412  			Log:                txLog,
   413  			TokenID:            preloadedTx.TokenID,
   414  			TokenValue:         preloadedTx.Value,
   415  			BaseGasFees:        blk.BaseFee().String(),
   416  			Transaction:        tx,
   417  			Receipt:            receipt,
   418  			Timestamp:          blk.Time(),
   419  			MultiTransactionID: w_common.NoMultiTransactionID,
   420  		}
   421  
   422  		rst = append(rst, transfer)
   423  	}
   424  
   425  	log.Debug("subTransactionsFromPreloaded end", "txHash", tx.Hash().Hex(), "address", address.Hex(), "tokenID", preloadedTx.TokenID, "value", preloadedTx.Value)
   426  	return rst, nil
   427  }
   428  
   429  func (d *ETHDownloader) subTransactionsFromTransactionData(address, from common.Address, tx *types.Transaction, receipt *types.Receipt, blk *types.Block) ([]Transfer, error) {
   430  	log.Debug("subTransactionsFromTransactionData start", "txHash", tx.Hash().Hex(), "address", address)
   431  
   432  	rst := make([]Transfer, 0, 1)
   433  
   434  	for _, txLog := range receipt.Logs {
   435  		eventType := w_common.GetEventType(txLog)
   436  		switch eventType {
   437  		case w_common.UniswapV2SwapEventType, w_common.UniswapV3SwapEventType,
   438  			w_common.HopBridgeTransferSentToL2EventType, w_common.HopBridgeTransferFromL1CompletedEventType,
   439  			w_common.HopBridgeWithdrawalBondedEventType, w_common.HopBridgeTransferSentEventType:
   440  			transfer := Transfer{
   441  				Type:               w_common.EventTypeToSubtransactionType(eventType),
   442  				ID:                 w_common.GetLogSubTxID(*txLog),
   443  				Address:            address,
   444  				BlockNumber:        new(big.Int).SetUint64(txLog.BlockNumber),
   445  				BlockHash:          txLog.BlockHash,
   446  				Loaded:             true,
   447  				NetworkID:          d.signer.ChainID().Uint64(),
   448  				From:               from,
   449  				Log:                txLog,
   450  				BaseGasFees:        blk.BaseFee().String(),
   451  				Transaction:        tx,
   452  				Receipt:            receipt,
   453  				Timestamp:          blk.Time(),
   454  				MultiTransactionID: w_common.NoMultiTransactionID,
   455  			}
   456  
   457  			rst = append(rst, transfer)
   458  		}
   459  	}
   460  
   461  	log.Debug("subTransactionsFromTransactionData end", "txHash", tx.Hash().Hex(), "address", address.Hex())
   462  	return rst, nil
   463  }
   464  
   465  func (d *ERC20TransfersDownloader) blocksFromLogs(parent context.Context, logs []types.Log) ([]*DBHeader, error) {
   466  	concurrent := NewConcurrentDownloader(parent, NoThreadLimit)
   467  
   468  	for i := range logs {
   469  		l := logs[i]
   470  
   471  		if l.Removed {
   472  			continue
   473  		}
   474  
   475  		var address common.Address
   476  		from, to, txIDs, tokenIDs, values, err := w_common.ParseTransferLog(l)
   477  		if err != nil {
   478  			log.Error("failed to parse transfer log", "log", l, "address", d.accounts, "error", err)
   479  			continue
   480  		}
   481  
   482  		// Double check provider returned the correct log
   483  		if slices.Contains(d.accounts, from) {
   484  			address = from
   485  		} else if slices.Contains(d.accounts, to) {
   486  			address = to
   487  		} else {
   488  			log.Error("from/to address mismatch", "log", l, "addresses", d.accounts)
   489  			continue
   490  		}
   491  
   492  		eventType := w_common.GetEventType(&l)
   493  		logType := w_common.EventTypeToSubtransactionType(eventType)
   494  
   495  		for i, txID := range txIDs {
   496  			log.Debug("block from logs", "block", l.BlockNumber, "log", l, "logType", logType, "txID", txID)
   497  
   498  			// For ERC20 there is no tokenID, so we use nil
   499  			var tokenID *big.Int
   500  			if len(tokenIDs) > i {
   501  				tokenID = tokenIDs[i]
   502  			}
   503  
   504  			header := &DBHeader{
   505  				Number:  big.NewInt(int64(l.BlockNumber)),
   506  				Hash:    l.BlockHash,
   507  				Address: address,
   508  				PreloadedTransactions: []*PreloadedTransaction{{
   509  					ID:      txID,
   510  					Type:    logType,
   511  					Log:     &l,
   512  					TokenID: tokenID,
   513  					Value:   values[i],
   514  				}},
   515  				Loaded: false,
   516  			}
   517  
   518  			concurrent.Add(func(ctx context.Context) error {
   519  				concurrent.PushHeader(header)
   520  				return nil
   521  			})
   522  		}
   523  	}
   524  	select {
   525  	case <-concurrent.WaitAsync():
   526  	case <-parent.Done():
   527  		return nil, errLogsDownloaderStuck
   528  	}
   529  	return concurrent.GetHeaders(), concurrent.Error()
   530  }
   531  
   532  // GetHeadersInRange returns transfers between two blocks.
   533  // time to get logs for 100000 blocks = 1.144686979s. with 249 events in the result set.
   534  func (d *ERC20TransfersDownloader) GetHeadersInRange(parent context.Context, from, to *big.Int) ([]*DBHeader, error) {
   535  	start := time.Now()
   536  	log.Debug("get erc20 transfers in range start", "chainID", d.client.NetworkID(), "from", from, "to", to, "accounts", d.accounts)
   537  
   538  	// TODO #16062: Figure out real root cause of invalid range
   539  	if from != nil && to != nil && from.Cmp(to) > 0 {
   540  		log.Error("invalid range", "chainID", d.client.NetworkID(), "from", from, "to", to, "accounts", d.accounts)
   541  		return nil, errors.New("invalid range")
   542  	}
   543  
   544  	headers := []*DBHeader{}
   545  	ctx := context.Background()
   546  	var err error
   547  	outbound := []types.Log{}
   548  	var inboundOrMixed []types.Log // inbound ERC20 or outbound ERC1155 share the same signature for our purposes
   549  	if !d.incomingOnly {
   550  		outbound, err = d.client.FilterLogs(ctx, ethereum.FilterQuery{
   551  			FromBlock: from,
   552  			ToBlock:   to,
   553  			Topics:    d.outboundTopics(d.accounts),
   554  		})
   555  		if err != nil {
   556  			return nil, err
   557  		}
   558  		inboundOrMixed, err = d.client.FilterLogs(ctx, ethereum.FilterQuery{
   559  			FromBlock: from,
   560  			ToBlock:   to,
   561  			Topics:    d.inboundERC20OutboundERC1155Topics(d.accounts),
   562  		})
   563  		if err != nil {
   564  			return nil, err
   565  		}
   566  	} else {
   567  		inboundOrMixed, err = d.client.FilterLogs(ctx, ethereum.FilterQuery{
   568  			FromBlock: from,
   569  			ToBlock:   to,
   570  			Topics:    d.inboundTopics(d.accounts),
   571  		})
   572  		if err != nil {
   573  			return nil, err
   574  		}
   575  	}
   576  
   577  	inbound1155, err := d.client.FilterLogs(ctx, ethereum.FilterQuery{
   578  		FromBlock: from,
   579  		ToBlock:   to,
   580  		Topics:    d.inboundTopicsERC1155(d.accounts),
   581  	})
   582  	if err != nil {
   583  		return nil, err
   584  	}
   585  
   586  	logs := concatLogs(outbound, inboundOrMixed, inbound1155)
   587  
   588  	if len(logs) == 0 {
   589  		log.Debug("no logs found for account")
   590  		return nil, nil
   591  	}
   592  
   593  	rst, err := d.blocksFromLogs(parent, logs)
   594  	if err != nil {
   595  		return nil, err
   596  	}
   597  	if len(rst) == 0 {
   598  		log.Warn("no headers found in logs for account", "chainID", d.client.NetworkID(), "addresses", d.accounts, "from", from, "to", to)
   599  	} else {
   600  		headers = append(headers, rst...)
   601  		log.Debug("found erc20 transfers for account", "chainID", d.client.NetworkID(), "addresses", d.accounts,
   602  			"from", from, "to", to, "headers", len(headers))
   603  	}
   604  
   605  	log.Debug("get erc20 transfers in range end", "chainID", d.client.NetworkID(),
   606  		"from", from, "to", to, "headers", len(headers), "accounts", d.accounts, "took", time.Since(start))
   607  	return headers, nil
   608  }
   609  
   610  func concatLogs(slices ...[]types.Log) []types.Log {
   611  	var totalLen int
   612  	for _, s := range slices {
   613  		totalLen += len(s)
   614  	}
   615  	tmp := make([]types.Log, totalLen)
   616  	var i int
   617  	for _, s := range slices {
   618  		i += copy(tmp[i:], s)
   619  	}
   620  
   621  	return tmp
   622  }