github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/eth/filters/filter_system.go (about)

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