github.com/diadata-org/diadata@v1.4.593/pkg/utils/ethtxfilter.go (about)

     1  package utils
     2  
     3  import (
     4  	"context"
     5  	"math/big"
     6  	"sort"
     7  
     8  	"github.com/ethereum/go-ethereum"
     9  	"github.com/ethereum/go-ethereum/accounts/abi"
    10  	"github.com/ethereum/go-ethereum/common"
    11  	"github.com/ethereum/go-ethereum/core/types"
    12  	"github.com/ethereum/go-ethereum/ethclient"
    13  )
    14  
    15  // EthTxFilterCriteria used for filtering transaction records
    16  type EthTxFilterCriteria struct {
    17  	StartBlockNum      uint64           // inclusive. filter transactions from the specific block
    18  	StartTxIndex       uint             // inclusive. filter transactions from specific index in the given StartBlockNum
    19  	LimitBlocks        int              // filter transactions in the specific number of blocks, zero means up to highest possible block
    20  	BehindHighestBlock int              // stay behind the highest synched block if StartBlockNum+LimitBlocks in excess of the head
    21  	EvAddrs            []common.Address // list of addresses from which transaction events should originate
    22  	Events             []common.Hash    // list of events from which transactions should contain
    23  }
    24  
    25  // EthTxFilterResult describes filter results and holds found transactions
    26  type EthTxFilterResult struct {
    27  	Synced       bool             // it means the most recent inspected block matches the highest known block (see BehindHighestBlock)
    28  	NumBlocks    int              // number of blocks found
    29  	NumTXs       int              // number of transactions found
    30  	NumLogs      int              // number of log records found
    31  	TXs          []*EthFilteredTx // list of found transactions
    32  	LastBlockNum uint64           // block number of most recent transaction inspected
    33  }
    34  
    35  // EthFilteredTx holds limited info for found transactions
    36  type EthFilteredTx struct {
    37  	BlockNum  uint64
    38  	BlockHash common.Hash
    39  	TXIndex   uint
    40  	TXHash    common.Hash
    41  	Logs      []types.Log // list of matched log records if Events or EvAddrs were used, otherwise all logs of the transaction
    42  }
    43  
    44  // EthFilterTXs returns transactions filtered by log records
    45  func EthFilterTXs(ctx context.Context, ethClient *ethclient.Client, filter EthTxFilterCriteria) (*EthTxFilterResult, error) {
    46  	startBlockNum, endBlockNum, synced, err := ethFilterTXsCalcEndBlockNum(ctx, ethClient, filter.StartBlockNum, uint64(filter.BehindHighestBlock), uint64(filter.LimitBlocks))
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	query := make([][]interface{}, 1)
    52  	query[0] = make([]interface{}, len(filter.Events))
    53  
    54  	for i, ev := range filter.Events {
    55  		query[0][i] = ev
    56  	}
    57  
    58  	topics, err := abi.MakeTopics(query...)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	logs, err := ethClient.FilterLogs(ctx, ethereum.FilterQuery{
    64  		Addresses: filter.EvAddrs,
    65  		Topics:    topics,
    66  		FromBlock: new(big.Int).SetUint64(startBlockNum),
    67  		ToBlock:   new(big.Int).SetUint64(endBlockNum),
    68  	})
    69  
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	txMap := make(map[common.Hash]*EthFilteredTx)
    75  
    76  	for _, log := range logs {
    77  		// skip if the log removed due to chain re-organization
    78  		if log.Removed {
    79  			continue
    80  		}
    81  
    82  		// skip if the event has already been passed
    83  		if log.BlockNumber == filter.StartBlockNum && log.TxIndex < filter.StartTxIndex {
    84  			continue
    85  		}
    86  
    87  		tx, ok := txMap[log.TxHash]
    88  		if !ok {
    89  			tx = &EthFilteredTx{
    90  				BlockNum:  log.BlockNumber,
    91  				BlockHash: log.BlockHash,
    92  				TXIndex:   log.TxIndex,
    93  				TXHash:    log.TxHash,
    94  				Logs:      make([]types.Log, 0, 10),
    95  			}
    96  
    97  			txMap[log.TxHash] = tx
    98  		}
    99  
   100  		tx.Logs = append(tx.Logs, log)
   101  	}
   102  
   103  	result := &EthTxFilterResult{
   104  		Synced:       synced,
   105  		TXs:          make([]*EthFilteredTx, 0, len(txMap)),
   106  		LastBlockNum: endBlockNum,
   107  	}
   108  
   109  	lastBlockNum := uint64(0)
   110  	for _, tx := range txMap {
   111  		if lastBlockNum != tx.BlockNum {
   112  			lastBlockNum = tx.BlockNum
   113  			result.NumBlocks++
   114  		}
   115  
   116  		sort.Slice(tx.Logs, func(i, j int) bool { return tx.Logs[i].Index < tx.Logs[j].Index })
   117  
   118  		result.NumTXs++
   119  		result.NumLogs += len(tx.Logs)
   120  
   121  		result.TXs = append(result.TXs, tx)
   122  	}
   123  
   124  	sort.Slice(result.TXs, func(i, j int) bool {
   125  		if result.TXs[i].BlockNum < result.TXs[j].BlockNum {
   126  			return true
   127  		}
   128  
   129  		if result.TXs[i].BlockNum > result.TXs[j].BlockNum {
   130  			return false
   131  		}
   132  
   133  		return result.TXs[i].TXIndex < result.TXs[j].TXIndex
   134  	})
   135  
   136  	return result, nil
   137  }
   138  
   139  func ethFilterTXsCalcEndBlockNum(ctx context.Context, ethClient *ethclient.Client, start, stayBehind, limit uint64) (uint64, uint64, bool, error) {
   140  	end, err := ethClient.BlockNumber(ctx)
   141  	if err != nil {
   142  		return 0, 0, false, err
   143  	}
   144  
   145  	syncProgress, err := ethClient.SyncProgress(ctx)
   146  	if err != nil {
   147  		return 0, 0, false, err
   148  	}
   149  
   150  	if syncProgress == nil { // means the connected node is synced
   151  		end -= stayBehind
   152  	} else {
   153  		max := syncProgress.HighestBlock - stayBehind
   154  		if syncProgress.CurrentBlock < max {
   155  			end = syncProgress.CurrentBlock
   156  		}
   157  	}
   158  
   159  	synced := true
   160  
   161  	if to := start + limit; limit != 0 && to < end {
   162  		synced = false
   163  		end = to
   164  	}
   165  
   166  	if start > end {
   167  		start = end
   168  	}
   169  	log.Infof("resulting start -- end: %v --- %v", start, end)
   170  
   171  	return start, end, synced, nil
   172  }