github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/qct/filters/filter_system.go (about)

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