github.com/ethereum/go-ethereum@v1.16.1/eth/filters/filter_system.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package filters implements an ethereum filtering system for block,
    18  // transactions and log events.
    19  package filters
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"sync"
    25  	"sync/atomic"
    26  	"time"
    27  
    28  	"github.com/ethereum/go-ethereum"
    29  	"github.com/ethereum/go-ethereum/common"
    30  	"github.com/ethereum/go-ethereum/common/lru"
    31  	"github.com/ethereum/go-ethereum/core"
    32  	"github.com/ethereum/go-ethereum/core/filtermaps"
    33  	"github.com/ethereum/go-ethereum/core/history"
    34  	"github.com/ethereum/go-ethereum/core/types"
    35  	"github.com/ethereum/go-ethereum/ethdb"
    36  	"github.com/ethereum/go-ethereum/event"
    37  	"github.com/ethereum/go-ethereum/log"
    38  	"github.com/ethereum/go-ethereum/params"
    39  	"github.com/ethereum/go-ethereum/rpc"
    40  )
    41  
    42  // Config represents the configuration of the filter system.
    43  type Config struct {
    44  	LogCacheSize int           // maximum number of cached blocks (default: 32)
    45  	Timeout      time.Duration // how long filters stay active (default: 5min)
    46  }
    47  
    48  func (cfg Config) withDefaults() Config {
    49  	if cfg.Timeout == 0 {
    50  		cfg.Timeout = 5 * time.Minute
    51  	}
    52  	if cfg.LogCacheSize == 0 {
    53  		cfg.LogCacheSize = 32
    54  	}
    55  	return cfg
    56  }
    57  
    58  type Backend interface {
    59  	ChainDb() ethdb.Database
    60  	HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
    61  	HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error)
    62  	GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error)
    63  	GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
    64  	GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error)
    65  
    66  	CurrentHeader() *types.Header
    67  	ChainConfig() *params.ChainConfig
    68  	HistoryPruningCutoff() uint64
    69  	SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
    70  	SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
    71  	SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
    72  	SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
    73  
    74  	CurrentView() *filtermaps.ChainView
    75  	NewMatcherBackend() filtermaps.MatcherBackend
    76  }
    77  
    78  // FilterSystem holds resources shared by all filters.
    79  type FilterSystem struct {
    80  	backend   Backend
    81  	logsCache *lru.Cache[common.Hash, *logCacheElem]
    82  	cfg       *Config
    83  }
    84  
    85  // NewFilterSystem creates a filter system.
    86  func NewFilterSystem(backend Backend, config Config) *FilterSystem {
    87  	config = config.withDefaults()
    88  	return &FilterSystem{
    89  		backend:   backend,
    90  		logsCache: lru.NewCache[common.Hash, *logCacheElem](config.LogCacheSize),
    91  		cfg:       &config,
    92  	}
    93  }
    94  
    95  type logCacheElem struct {
    96  	logs []*types.Log
    97  	body atomic.Pointer[types.Body]
    98  }
    99  
   100  // cachedLogElem loads block logs from the backend and caches the result.
   101  func (sys *FilterSystem) cachedLogElem(ctx context.Context, blockHash common.Hash, number, time uint64) (*logCacheElem, error) {
   102  	cached, ok := sys.logsCache.Get(blockHash)
   103  	if ok {
   104  		return cached, nil
   105  	}
   106  
   107  	logs, err := sys.backend.GetLogs(ctx, blockHash, number)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	if logs == nil {
   112  		return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, blockHash.TerminalString())
   113  	}
   114  	// Database logs are un-derived.
   115  	// Fill in whatever we can (txHash is inaccessible at this point).
   116  	flattened := make([]*types.Log, 0)
   117  	var logIdx uint
   118  	for i, txLogs := range logs {
   119  		for _, log := range txLogs {
   120  			log.BlockHash = blockHash
   121  			log.BlockNumber = number
   122  			log.BlockTimestamp = time
   123  			log.TxIndex = uint(i)
   124  			log.Index = logIdx
   125  			logIdx++
   126  			flattened = append(flattened, log)
   127  		}
   128  	}
   129  	elem := &logCacheElem{logs: flattened}
   130  	sys.logsCache.Add(blockHash, elem)
   131  	return elem, nil
   132  }
   133  
   134  func (sys *FilterSystem) cachedGetBody(ctx context.Context, elem *logCacheElem, hash common.Hash, number uint64) (*types.Body, error) {
   135  	if body := elem.body.Load(); body != nil {
   136  		return body, nil
   137  	}
   138  	body, err := sys.backend.GetBody(ctx, hash, rpc.BlockNumber(number))
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	elem.body.Store(body)
   143  	return body, nil
   144  }
   145  
   146  // Type determines the kind of filter and is used to put the filter in to
   147  // the correct bucket when added.
   148  type Type byte
   149  
   150  const (
   151  	// UnknownSubscription indicates an unknown subscription type
   152  	UnknownSubscription Type = iota
   153  	// LogsSubscription queries for new or removed (chain reorg) logs
   154  	LogsSubscription
   155  	// PendingTransactionsSubscription queries for pending transactions entering
   156  	// the pending state
   157  	PendingTransactionsSubscription
   158  	// BlocksSubscription queries hashes for blocks that are imported
   159  	BlocksSubscription
   160  	// LastIndexSubscription keeps track of the last index
   161  	LastIndexSubscription
   162  )
   163  
   164  const (
   165  	// txChanSize is the size of channel listening to NewTxsEvent.
   166  	// The number is referenced from the size of tx pool.
   167  	txChanSize = 4096
   168  	// rmLogsChanSize is the size of channel listening to RemovedLogsEvent.
   169  	rmLogsChanSize = 10
   170  	// logsChanSize is the size of channel listening to LogsEvent.
   171  	logsChanSize = 10
   172  	// chainEvChanSize is the size of channel listening to ChainEvent.
   173  	chainEvChanSize = 10
   174  )
   175  
   176  type subscription struct {
   177  	id        rpc.ID
   178  	typ       Type
   179  	created   time.Time
   180  	logsCrit  ethereum.FilterQuery
   181  	logs      chan []*types.Log
   182  	txs       chan []*types.Transaction
   183  	headers   chan *types.Header
   184  	installed chan struct{} // closed when the filter is installed
   185  	err       chan error    // closed when the filter is uninstalled
   186  }
   187  
   188  // EventSystem creates subscriptions, processes events and broadcasts them to the
   189  // subscription which match the subscription criteria.
   190  type EventSystem struct {
   191  	backend Backend
   192  	sys     *FilterSystem
   193  
   194  	// Subscriptions
   195  	txsSub    event.Subscription // Subscription for new transaction event
   196  	logsSub   event.Subscription // Subscription for new log event
   197  	rmLogsSub event.Subscription // Subscription for removed log event
   198  	chainSub  event.Subscription // Subscription for new chain event
   199  
   200  	// Channels
   201  	install   chan *subscription         // install filter for event notification
   202  	uninstall chan *subscription         // remove filter for event notification
   203  	txsCh     chan core.NewTxsEvent      // Channel to receive new transactions event
   204  	logsCh    chan []*types.Log          // Channel to receive new log event
   205  	rmLogsCh  chan core.RemovedLogsEvent // Channel to receive removed log event
   206  	chainCh   chan core.ChainEvent       // Channel to receive new chain event
   207  }
   208  
   209  // NewEventSystem creates a new manager that listens for event on the given mux,
   210  // parses and filters them. It uses the all map to retrieve filter changes. The
   211  // work loop holds its own index that is used to forward events to filters.
   212  //
   213  // The returned manager has a loop that needs to be stopped with the Stop function
   214  // or by stopping the given mux.
   215  func NewEventSystem(sys *FilterSystem) *EventSystem {
   216  	m := &EventSystem{
   217  		sys:       sys,
   218  		backend:   sys.backend,
   219  		install:   make(chan *subscription),
   220  		uninstall: make(chan *subscription),
   221  		txsCh:     make(chan core.NewTxsEvent, txChanSize),
   222  		logsCh:    make(chan []*types.Log, logsChanSize),
   223  		rmLogsCh:  make(chan core.RemovedLogsEvent, rmLogsChanSize),
   224  		chainCh:   make(chan core.ChainEvent, chainEvChanSize),
   225  	}
   226  
   227  	// Subscribe events
   228  	m.txsSub = m.backend.SubscribeNewTxsEvent(m.txsCh)
   229  	m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh)
   230  	m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh)
   231  	m.chainSub = m.backend.SubscribeChainEvent(m.chainCh)
   232  
   233  	// Make sure none of the subscriptions are empty
   234  	if m.txsSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil {
   235  		log.Crit("Subscribe for event system failed")
   236  	}
   237  
   238  	go m.eventLoop()
   239  	return m
   240  }
   241  
   242  // Subscription is created when the client registers itself for a particular event.
   243  type Subscription struct {
   244  	ID        rpc.ID
   245  	f         *subscription
   246  	es        *EventSystem
   247  	unsubOnce sync.Once
   248  }
   249  
   250  // Err returns a channel that is closed when unsubscribed.
   251  func (sub *Subscription) Err() <-chan error {
   252  	return sub.f.err
   253  }
   254  
   255  // Unsubscribe uninstalls the subscription from the event broadcast loop.
   256  func (sub *Subscription) Unsubscribe() {
   257  	sub.unsubOnce.Do(func() {
   258  	uninstallLoop:
   259  		for {
   260  			// write uninstall request and consume logs/hashes. This prevents
   261  			// the eventLoop broadcast method to deadlock when writing to the
   262  			// filter event channel while the subscription loop is waiting for
   263  			// this method to return (and thus not reading these events).
   264  			select {
   265  			case sub.es.uninstall <- sub.f:
   266  				break uninstallLoop
   267  			case <-sub.f.logs:
   268  			case <-sub.f.txs:
   269  			case <-sub.f.headers:
   270  			}
   271  		}
   272  
   273  		// wait for filter to be uninstalled in work loop before returning
   274  		// this ensures that the manager won't use the event channel which
   275  		// will probably be closed by the client asap after this method returns.
   276  		<-sub.Err()
   277  	})
   278  }
   279  
   280  // subscribe installs the subscription in the event broadcast loop.
   281  func (es *EventSystem) subscribe(sub *subscription) *Subscription {
   282  	es.install <- sub
   283  	<-sub.installed
   284  	return &Subscription{ID: sub.id, f: sub, es: es}
   285  }
   286  
   287  // SubscribeLogs creates a subscription that will write all logs matching the
   288  // given criteria to the given logs channel. Default value for the from and to
   289  // block is "latest". If the fromBlock > toBlock an error is returned.
   290  func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) (*Subscription, error) {
   291  	if len(crit.Topics) > maxTopics {
   292  		return nil, errExceedMaxTopics
   293  	}
   294  	if len(crit.Addresses) > maxAddresses {
   295  		return nil, errExceedMaxAddresses
   296  	}
   297  	var from, to rpc.BlockNumber
   298  	if crit.FromBlock == nil {
   299  		from = rpc.LatestBlockNumber
   300  	} else {
   301  		from = rpc.BlockNumber(crit.FromBlock.Int64())
   302  	}
   303  	if crit.ToBlock == nil {
   304  		to = rpc.LatestBlockNumber
   305  	} else {
   306  		to = rpc.BlockNumber(crit.ToBlock.Int64())
   307  	}
   308  
   309  	// Pending logs are not supported anymore.
   310  	if from == rpc.PendingBlockNumber || to == rpc.PendingBlockNumber {
   311  		return nil, errPendingLogsUnsupported
   312  	}
   313  
   314  	if from == rpc.EarliestBlockNumber {
   315  		from = rpc.BlockNumber(es.backend.HistoryPruningCutoff())
   316  	}
   317  	// Queries beyond the pruning cutoff are not supported.
   318  	if uint64(from) < es.backend.HistoryPruningCutoff() {
   319  		return nil, &history.PrunedHistoryError{}
   320  	}
   321  
   322  	// only interested in new mined logs
   323  	if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber {
   324  		return es.subscribeLogs(crit, logs), nil
   325  	}
   326  	// only interested in mined logs within a specific block range
   327  	if from >= 0 && to >= 0 && to >= from {
   328  		return es.subscribeLogs(crit, logs), nil
   329  	}
   330  	// interested in logs from a specific block number to new mined blocks
   331  	if from >= 0 && to == rpc.LatestBlockNumber {
   332  		return es.subscribeLogs(crit, logs), nil
   333  	}
   334  	return nil, errInvalidBlockRange
   335  }
   336  
   337  // subscribeLogs creates a subscription that will write all logs matching the
   338  // given criteria to the given logs channel.
   339  func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription {
   340  	sub := &subscription{
   341  		id:        rpc.NewID(),
   342  		typ:       LogsSubscription,
   343  		logsCrit:  crit,
   344  		created:   time.Now(),
   345  		logs:      logs,
   346  		txs:       make(chan []*types.Transaction),
   347  		headers:   make(chan *types.Header),
   348  		installed: make(chan struct{}),
   349  		err:       make(chan error),
   350  	}
   351  	return es.subscribe(sub)
   352  }
   353  
   354  // SubscribeNewHeads creates a subscription that writes the header of a block that is
   355  // imported in the chain.
   356  func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscription {
   357  	sub := &subscription{
   358  		id:        rpc.NewID(),
   359  		typ:       BlocksSubscription,
   360  		created:   time.Now(),
   361  		logs:      make(chan []*types.Log),
   362  		txs:       make(chan []*types.Transaction),
   363  		headers:   headers,
   364  		installed: make(chan struct{}),
   365  		err:       make(chan error),
   366  	}
   367  	return es.subscribe(sub)
   368  }
   369  
   370  // SubscribePendingTxs creates a subscription that writes transactions for
   371  // transactions that enter the transaction pool.
   372  func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subscription {
   373  	sub := &subscription{
   374  		id:        rpc.NewID(),
   375  		typ:       PendingTransactionsSubscription,
   376  		created:   time.Now(),
   377  		logs:      make(chan []*types.Log),
   378  		txs:       txs,
   379  		headers:   make(chan *types.Header),
   380  		installed: make(chan struct{}),
   381  		err:       make(chan error),
   382  	}
   383  	return es.subscribe(sub)
   384  }
   385  
   386  type filterIndex map[Type]map[rpc.ID]*subscription
   387  
   388  func (es *EventSystem) handleLogs(filters filterIndex, ev []*types.Log) {
   389  	if len(ev) == 0 {
   390  		return
   391  	}
   392  	for _, f := range filters[LogsSubscription] {
   393  		matchedLogs := filterLogs(ev, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics)
   394  		if len(matchedLogs) > 0 {
   395  			f.logs <- matchedLogs
   396  		}
   397  	}
   398  }
   399  
   400  func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent) {
   401  	for _, f := range filters[PendingTransactionsSubscription] {
   402  		f.txs <- ev.Txs
   403  	}
   404  }
   405  
   406  func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) {
   407  	for _, f := range filters[BlocksSubscription] {
   408  		f.headers <- ev.Header
   409  	}
   410  }
   411  
   412  // eventLoop (un)installs filters and processes mux events.
   413  func (es *EventSystem) eventLoop() {
   414  	// Ensure all subscriptions get cleaned up
   415  	defer func() {
   416  		es.txsSub.Unsubscribe()
   417  		es.logsSub.Unsubscribe()
   418  		es.rmLogsSub.Unsubscribe()
   419  		es.chainSub.Unsubscribe()
   420  	}()
   421  
   422  	index := make(filterIndex)
   423  	for i := UnknownSubscription; i < LastIndexSubscription; i++ {
   424  		index[i] = make(map[rpc.ID]*subscription)
   425  	}
   426  
   427  	for {
   428  		select {
   429  		case ev := <-es.txsCh:
   430  			es.handleTxsEvent(index, ev)
   431  		case ev := <-es.logsCh:
   432  			es.handleLogs(index, ev)
   433  		case ev := <-es.rmLogsCh:
   434  			es.handleLogs(index, ev.Logs)
   435  		case ev := <-es.chainCh:
   436  			es.handleChainEvent(index, ev)
   437  
   438  		case f := <-es.install:
   439  			index[f.typ][f.id] = f
   440  			close(f.installed)
   441  
   442  		case f := <-es.uninstall:
   443  			delete(index[f.typ], f.id)
   444  			close(f.err)
   445  
   446  		// System stopped
   447  		case <-es.txsSub.Err():
   448  			return
   449  		case <-es.logsSub.Err():
   450  			return
   451  		case <-es.rmLogsSub.Err():
   452  			return
   453  		case <-es.chainSub.Err():
   454  			return
   455  		}
   456  	}
   457  }