github.com/intfoundation/intchain@v0.0.0-20220727031208-4316ad31ca73/intprotocol/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 intchain filtering system for block,
    18  // transactions and log events.
    19  package filters
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/intfoundation/intchain"
    29  	"github.com/intfoundation/intchain/common"
    30  	"github.com/intfoundation/intchain/core"
    31  	"github.com/intfoundation/intchain/core/rawdb"
    32  	"github.com/intfoundation/intchain/core/types"
    33  	"github.com/intfoundation/intchain/event"
    34  	"github.com/intfoundation/intchain/rpc"
    35  )
    36  
    37  // Type determines the kind of filter and is used to put the filter in to
    38  // the correct bucket when added.
    39  type Type byte
    40  
    41  const (
    42  	// UnknownSubscription indicates an unknown subscription type
    43  	UnknownSubscription Type = iota
    44  	// LogsSubscription queries for new or removed (chain reorg) logs
    45  	LogsSubscription
    46  	// PendingLogsSubscription queries for logs in pending blocks
    47  	PendingLogsSubscription
    48  	// MinedAndPendingLogsSubscription queries for logs in mined and pending blocks.
    49  	MinedAndPendingLogsSubscription
    50  	// PendingTransactionsSubscription queries tx hashes for pending
    51  	// transactions entering the pending state
    52  	PendingTransactionsSubscription
    53  	// BlocksSubscription queries hashes for blocks that are imported
    54  	BlocksSubscription
    55  	// LastSubscription keeps track of the last index
    56  	LastIndexSubscription
    57  )
    58  
    59  const (
    60  
    61  	// txChanSize is the size of channel listening to TxPreEvent.
    62  	// The number is referenced from the size of tx pool.
    63  	txChanSize = 4096
    64  	// rmLogsChanSize is the size of channel listening to RemovedLogsEvent.
    65  	rmLogsChanSize = 10
    66  	// logsChanSize is the size of channel listening to LogsEvent.
    67  	logsChanSize = 10
    68  	// chainEvChanSize is the size of channel listening to ChainEvent.
    69  	chainEvChanSize = 10
    70  )
    71  
    72  var (
    73  	ErrInvalidSubscriptionID = errors.New("invalid id")
    74  )
    75  
    76  type subscription struct {
    77  	id        rpc.ID
    78  	typ       Type
    79  	created   time.Time
    80  	logsCrit  intchain.FilterQuery
    81  	logs      chan []*types.Log
    82  	hashes    chan common.Hash
    83  	headers   chan *types.Header
    84  	installed chan struct{} // closed when the filter is installed
    85  	err       chan error    // closed when the filter is uninstalled
    86  }
    87  
    88  // EventSystem creates subscriptions, processes events and broadcasts them to the
    89  // subscription which match the subscription criteria.
    90  type EventSystem struct {
    91  	mux       *event.TypeMux
    92  	backend   Backend
    93  	lightMode bool
    94  	lastHead  *types.Header
    95  	install   chan *subscription // install filter for event notification
    96  	uninstall chan *subscription // remove filter for event notification
    97  }
    98  
    99  // NewEventSystem creates a new manager that listens for event on the given mux,
   100  // parses and filters them. It uses the all map to retrieve filter changes. The
   101  // work loop holds its own index that is used to forward events to filters.
   102  //
   103  // The returned manager has a loop that needs to be stopped with the Stop function
   104  // or by stopping the given mux.
   105  func NewEventSystem(mux *event.TypeMux, backend Backend, lightMode bool) *EventSystem {
   106  	m := &EventSystem{
   107  		mux:       mux,
   108  		backend:   backend,
   109  		lightMode: lightMode,
   110  		install:   make(chan *subscription),
   111  		uninstall: make(chan *subscription),
   112  	}
   113  
   114  	go m.eventLoop()
   115  
   116  	return m
   117  }
   118  
   119  // Subscription is created when the client registers itself for a particular event.
   120  type Subscription struct {
   121  	ID        rpc.ID
   122  	f         *subscription
   123  	es        *EventSystem
   124  	unsubOnce sync.Once
   125  }
   126  
   127  // Err returns a channel that is closed when unsubscribed.
   128  func (sub *Subscription) Err() <-chan error {
   129  	return sub.f.err
   130  }
   131  
   132  // Unsubscribe uninstalls the subscription from the event broadcast loop.
   133  func (sub *Subscription) Unsubscribe() {
   134  	sub.unsubOnce.Do(func() {
   135  	uninstallLoop:
   136  		for {
   137  			// write uninstall request and consume logs/hashes. This prevents
   138  			// the eventLoop broadcast method to deadlock when writing to the
   139  			// filter event channel while the subscription loop is waiting for
   140  			// this method to return (and thus not reading these events).
   141  			select {
   142  			case sub.es.uninstall <- sub.f:
   143  				break uninstallLoop
   144  			case <-sub.f.logs:
   145  			case <-sub.f.hashes:
   146  			case <-sub.f.headers:
   147  			}
   148  		}
   149  
   150  		// wait for filter to be uninstalled in work loop before returning
   151  		// this ensures that the manager won't use the event channel which
   152  		// will probably be closed by the client asap after this method returns.
   153  		<-sub.Err()
   154  	})
   155  }
   156  
   157  // subscribe installs the subscription in the event broadcast loop.
   158  func (es *EventSystem) subscribe(sub *subscription) *Subscription {
   159  	es.install <- sub
   160  	<-sub.installed
   161  	return &Subscription{ID: sub.id, f: sub, es: es}
   162  }
   163  
   164  // SubscribeLogs creates a subscription that will write all logs matching the
   165  // given criteria to the given logs channel. Default value for the from and to
   166  // block is "latest". If the fromBlock > toBlock an error is returned.
   167  func (es *EventSystem) SubscribeLogs(crit intchain.FilterQuery, logs chan []*types.Log) (*Subscription, error) {
   168  	var from, to rpc.BlockNumber
   169  	if crit.FromBlock == nil {
   170  		from = rpc.LatestBlockNumber
   171  	} else {
   172  		from = rpc.BlockNumber(crit.FromBlock.Int64())
   173  	}
   174  	if crit.ToBlock == nil {
   175  		to = rpc.LatestBlockNumber
   176  	} else {
   177  		to = rpc.BlockNumber(crit.ToBlock.Int64())
   178  	}
   179  
   180  	// only interested in pending logs
   181  	if from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber {
   182  		return es.subscribePendingLogs(crit, logs), nil
   183  	}
   184  	// only interested in new mined logs
   185  	if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber {
   186  		return es.subscribeLogs(crit, logs), nil
   187  	}
   188  	// only interested in mined logs within a specific block range
   189  	if from >= 0 && to >= 0 && to >= from {
   190  		return es.subscribeLogs(crit, logs), nil
   191  	}
   192  	// interested in mined logs from a specific block number, new logs and pending logs
   193  	if from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber {
   194  		return es.subscribeMinedPendingLogs(crit, logs), nil
   195  	}
   196  	// interested in logs from a specific block number to new mined blocks
   197  	if from >= 0 && to == rpc.LatestBlockNumber {
   198  		return es.subscribeLogs(crit, logs), nil
   199  	}
   200  	return nil, fmt.Errorf("invalid from and to block combination: from > to")
   201  }
   202  
   203  // subscribeMinedPendingLogs creates a subscription that returned mined and
   204  // pending logs that match the given criteria.
   205  func (es *EventSystem) subscribeMinedPendingLogs(crit intchain.FilterQuery, logs chan []*types.Log) *Subscription {
   206  	sub := &subscription{
   207  		id:        rpc.NewID(),
   208  		typ:       MinedAndPendingLogsSubscription,
   209  		logsCrit:  crit,
   210  		created:   time.Now(),
   211  		logs:      logs,
   212  		hashes:    make(chan common.Hash),
   213  		headers:   make(chan *types.Header),
   214  		installed: make(chan struct{}),
   215  		err:       make(chan error),
   216  	}
   217  	return es.subscribe(sub)
   218  }
   219  
   220  // subscribeLogs creates a subscription that will write all logs matching the
   221  // given criteria to the given logs channel.
   222  func (es *EventSystem) subscribeLogs(crit intchain.FilterQuery, logs chan []*types.Log) *Subscription {
   223  	sub := &subscription{
   224  		id:        rpc.NewID(),
   225  		typ:       LogsSubscription,
   226  		logsCrit:  crit,
   227  		created:   time.Now(),
   228  		logs:      logs,
   229  		hashes:    make(chan common.Hash),
   230  		headers:   make(chan *types.Header),
   231  		installed: make(chan struct{}),
   232  		err:       make(chan error),
   233  	}
   234  	return es.subscribe(sub)
   235  }
   236  
   237  // subscribePendingLogs creates a subscription that writes transaction hashes for
   238  // transactions that enter the transaction pool.
   239  func (es *EventSystem) subscribePendingLogs(crit intchain.FilterQuery, logs chan []*types.Log) *Subscription {
   240  	sub := &subscription{
   241  		id:        rpc.NewID(),
   242  		typ:       PendingLogsSubscription,
   243  		logsCrit:  crit,
   244  		created:   time.Now(),
   245  		logs:      logs,
   246  		hashes:    make(chan common.Hash),
   247  		headers:   make(chan *types.Header),
   248  		installed: make(chan struct{}),
   249  		err:       make(chan error),
   250  	}
   251  	return es.subscribe(sub)
   252  }
   253  
   254  // SubscribeNewHeads creates a subscription that writes the header of a block that is
   255  // imported in the chain.
   256  func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscription {
   257  	sub := &subscription{
   258  		id:        rpc.NewID(),
   259  		typ:       BlocksSubscription,
   260  		created:   time.Now(),
   261  		logs:      make(chan []*types.Log),
   262  		hashes:    make(chan common.Hash),
   263  		headers:   headers,
   264  		installed: make(chan struct{}),
   265  		err:       make(chan error),
   266  	}
   267  	return es.subscribe(sub)
   268  }
   269  
   270  // SubscribePendingTxEvents creates a subscription that writes transaction hashes for
   271  // transactions that enter the transaction pool.
   272  func (es *EventSystem) SubscribePendingTxEvents(hashes chan common.Hash) *Subscription {
   273  	sub := &subscription{
   274  		id:        rpc.NewID(),
   275  		typ:       PendingTransactionsSubscription,
   276  		created:   time.Now(),
   277  		logs:      make(chan []*types.Log),
   278  		hashes:    hashes,
   279  		headers:   make(chan *types.Header),
   280  		installed: make(chan struct{}),
   281  		err:       make(chan error),
   282  	}
   283  	return es.subscribe(sub)
   284  }
   285  
   286  type filterIndex map[Type]map[rpc.ID]*subscription
   287  
   288  // broadcast event to filters that match criteria.
   289  func (es *EventSystem) broadcast(filters filterIndex, ev interface{}) {
   290  	if ev == nil {
   291  		return
   292  	}
   293  
   294  	switch e := ev.(type) {
   295  	case []*types.Log:
   296  		if len(e) > 0 {
   297  			for _, f := range filters[LogsSubscription] {
   298  				if matchedLogs := filterLogs(e, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 {
   299  					f.logs <- matchedLogs
   300  				}
   301  			}
   302  		}
   303  	case core.RemovedLogsEvent:
   304  		for _, f := range filters[LogsSubscription] {
   305  			if matchedLogs := filterLogs(e.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 {
   306  				f.logs <- matchedLogs
   307  			}
   308  		}
   309  	case *event.TypeMuxEvent:
   310  		switch muxe := e.Data.(type) {
   311  		case core.PendingLogsEvent:
   312  			for _, f := range filters[PendingLogsSubscription] {
   313  				if e.Time.After(f.created) {
   314  					if matchedLogs := filterLogs(muxe.Logs, nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 {
   315  						f.logs <- matchedLogs
   316  					}
   317  				}
   318  			}
   319  		}
   320  	case core.TxPreEvent:
   321  		for _, f := range filters[PendingTransactionsSubscription] {
   322  			f.hashes <- e.Tx.Hash()
   323  		}
   324  	case core.ChainEvent:
   325  		for _, f := range filters[BlocksSubscription] {
   326  			f.headers <- e.Block.Header()
   327  		}
   328  		if es.lightMode && len(filters[LogsSubscription]) > 0 {
   329  			es.lightFilterNewHead(e.Block.Header(), func(header *types.Header, remove bool) {
   330  				for _, f := range filters[LogsSubscription] {
   331  					if matchedLogs := es.lightFilterLogs(header, f.logsCrit.Addresses, f.logsCrit.Topics, remove); len(matchedLogs) > 0 {
   332  						f.logs <- matchedLogs
   333  					}
   334  				}
   335  			})
   336  		}
   337  	}
   338  }
   339  
   340  func (es *EventSystem) lightFilterNewHead(newHeader *types.Header, callBack func(*types.Header, bool)) {
   341  	oldh := es.lastHead
   342  	es.lastHead = newHeader
   343  	if oldh == nil {
   344  		return
   345  	}
   346  	newh := newHeader
   347  	// find common ancestor, create list of rolled back and new block hashes
   348  	var oldHeaders, newHeaders []*types.Header
   349  	for oldh.Hash() != newh.Hash() {
   350  		if oldh.Number.Uint64() >= newh.Number.Uint64() {
   351  			oldHeaders = append(oldHeaders, oldh)
   352  			oldh = rawdb.ReadHeader(es.backend.ChainDb(), oldh.ParentHash, oldh.Number.Uint64()-1)
   353  		}
   354  		if oldh.Number.Uint64() < newh.Number.Uint64() {
   355  			newHeaders = append(newHeaders, newh)
   356  			newh = rawdb.ReadHeader(es.backend.ChainDb(), newh.ParentHash, newh.Number.Uint64()-1)
   357  			if newh == nil {
   358  				// happens when CHT syncing, nothing to do
   359  				newh = oldh
   360  			}
   361  		}
   362  	}
   363  	// roll back old blocks
   364  	for _, h := range oldHeaders {
   365  		callBack(h, true)
   366  	}
   367  	// check new blocks (array is in reverse order)
   368  	for i := len(newHeaders) - 1; i >= 0; i-- {
   369  		callBack(newHeaders[i], false)
   370  	}
   371  }
   372  
   373  // filter logs of a single header in light client mode
   374  func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.Address, topics [][]common.Hash, remove bool) []*types.Log {
   375  	if bloomFilter(header.Bloom, addresses, topics) {
   376  		// Get the logs of the block
   377  		ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
   378  		defer cancel()
   379  		logsList, err := es.backend.GetLogs(ctx, header.Hash())
   380  		if err != nil {
   381  			return nil
   382  		}
   383  		var unfiltered []*types.Log
   384  		for _, logs := range logsList {
   385  			for _, log := range logs {
   386  				logcopy := *log
   387  				logcopy.Removed = remove
   388  				unfiltered = append(unfiltered, &logcopy)
   389  			}
   390  		}
   391  		logs := filterLogs(unfiltered, nil, nil, addresses, topics)
   392  		if len(logs) > 0 && logs[0].TxHash == (common.Hash{}) {
   393  			// We have matching but non-derived logs
   394  			receipts, err := es.backend.GetReceipts(ctx, header.Hash())
   395  			if err != nil {
   396  				return nil
   397  			}
   398  			unfiltered = unfiltered[:0]
   399  			for _, receipt := range receipts {
   400  				for _, log := range receipt.Logs {
   401  					logcopy := *log
   402  					logcopy.Removed = remove
   403  					unfiltered = append(unfiltered, &logcopy)
   404  				}
   405  			}
   406  			logs = filterLogs(unfiltered, nil, nil, addresses, topics)
   407  		}
   408  		return logs
   409  	}
   410  	return nil
   411  }
   412  
   413  // eventLoop (un)installs filters and processes mux events.
   414  func (es *EventSystem) eventLoop() {
   415  	var (
   416  		index = make(filterIndex)
   417  		sub   = es.mux.Subscribe(core.PendingLogsEvent{})
   418  		// Subscribe TxPreEvent form txpool
   419  		txCh  = make(chan core.TxPreEvent, txChanSize)
   420  		txSub = es.backend.SubscribeTxPreEvent(txCh)
   421  		// Subscribe RemovedLogsEvent
   422  		rmLogsCh  = make(chan core.RemovedLogsEvent, rmLogsChanSize)
   423  		rmLogsSub = es.backend.SubscribeRemovedLogsEvent(rmLogsCh)
   424  		// Subscribe []*types.Log
   425  		logsCh  = make(chan []*types.Log, logsChanSize)
   426  		logsSub = es.backend.SubscribeLogsEvent(logsCh)
   427  		// Subscribe ChainEvent
   428  		chainEvCh  = make(chan core.ChainEvent, chainEvChanSize)
   429  		chainEvSub = es.backend.SubscribeChainEvent(chainEvCh)
   430  	)
   431  
   432  	// Unsubscribe all events
   433  	defer sub.Unsubscribe()
   434  	defer txSub.Unsubscribe()
   435  	defer rmLogsSub.Unsubscribe()
   436  	defer logsSub.Unsubscribe()
   437  	defer chainEvSub.Unsubscribe()
   438  
   439  	for i := UnknownSubscription; i < LastIndexSubscription; i++ {
   440  		index[i] = make(map[rpc.ID]*subscription)
   441  	}
   442  
   443  	for {
   444  		select {
   445  		case ev, active := <-sub.Chan():
   446  			if !active { // system stopped
   447  				return
   448  			}
   449  			es.broadcast(index, ev)
   450  
   451  		// Handle subscribed events
   452  		case ev := <-txCh:
   453  			es.broadcast(index, ev)
   454  		case ev := <-rmLogsCh:
   455  			es.broadcast(index, ev)
   456  		case ev := <-logsCh:
   457  			es.broadcast(index, ev)
   458  		case ev := <-chainEvCh:
   459  			es.broadcast(index, ev)
   460  
   461  		case f := <-es.install:
   462  			if f.typ == MinedAndPendingLogsSubscription {
   463  				// the type are logs and pending logs subscriptions
   464  				index[LogsSubscription][f.id] = f
   465  				index[PendingLogsSubscription][f.id] = f
   466  			} else {
   467  				index[f.typ][f.id] = f
   468  			}
   469  			close(f.installed)
   470  		case f := <-es.uninstall:
   471  			if f.typ == MinedAndPendingLogsSubscription {
   472  				// the type are logs and pending logs subscriptions
   473  				delete(index[LogsSubscription], f.id)
   474  				delete(index[PendingLogsSubscription], f.id)
   475  			} else {
   476  				delete(index[f.typ], f.id)
   477  			}
   478  			close(f.err)
   479  
   480  		// System stopped
   481  		case <-txSub.Err():
   482  			return
   483  		case <-rmLogsSub.Err():
   484  			return
   485  		case <-logsSub.Err():
   486  			return
   487  		case <-chainEvSub.Err():
   488  			return
   489  		}
   490  	}
   491  }