github.com/intfoundation/intchain@v0.0.0-20220727031208-4316ad31ca73/core/bloombits/matcher.go (about)

     1  // Copyright 2017 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 bloombits
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"errors"
    23  	"math"
    24  	"sort"
    25  	"sync"
    26  	"sync/atomic"
    27  	"time"
    28  
    29  	"github.com/intfoundation/intchain/common/bitutil"
    30  	"github.com/intfoundation/intchain/crypto"
    31  )
    32  
    33  // bloomIndexes represents the bit indexes inside the bloom filter that belong
    34  // to some key.
    35  type bloomIndexes [3]uint
    36  
    37  // calcBloomIndexes returns the bloom filter bit indexes belonging to the given key.
    38  func calcBloomIndexes(b []byte) bloomIndexes {
    39  	b = crypto.Keccak256(b)
    40  
    41  	var idxs bloomIndexes
    42  	for i := 0; i < len(idxs); i++ {
    43  		idxs[i] = (uint(b[2*i])<<8)&2047 + uint(b[2*i+1])
    44  	}
    45  	return idxs
    46  }
    47  
    48  // partialMatches with a non-nil vector represents a section in which some sub-
    49  // matchers have already found potential matches. Subsequent sub-matchers will
    50  // binary AND their matches with this vector. If vector is nil, it represents a
    51  // section to be processed by the first sub-matcher.
    52  type partialMatches struct {
    53  	section uint64
    54  	bitset  []byte
    55  }
    56  
    57  // Retrieval represents a request for retrieval task assignments for a given
    58  // bit with the given number of fetch elements, or a response for such a request.
    59  // It can also have the actual results set to be used as a delivery data struct.
    60  //
    61  // The contest and error fields are used by the light client to terminate matching
    62  // early if an error is enountered on some path of the pipeline.
    63  type Retrieval struct {
    64  	Bit      uint
    65  	Sections []uint64
    66  	Bitsets  [][]byte
    67  
    68  	Context context.Context
    69  	Error   error
    70  }
    71  
    72  // Matcher is a pipelined system of schedulers and logic matchers which perform
    73  // binary AND/OR operations on the bit-streams, creating a stream of potential
    74  // blocks to inspect for data content.
    75  type Matcher struct {
    76  	sectionSize uint64 // Size of the data batches to filter on
    77  
    78  	filters    [][]bloomIndexes    // Filter the system is matching for
    79  	schedulers map[uint]*scheduler // Retrieval schedulers for loading bloom bits
    80  
    81  	retrievers chan chan uint       // Retriever processes waiting for bit allocations
    82  	counters   chan chan uint       // Retriever processes waiting for task count reports
    83  	retrievals chan chan *Retrieval // Retriever processes waiting for task allocations
    84  	deliveries chan *Retrieval      // Retriever processes waiting for task response deliveries
    85  
    86  	running uint32 // Atomic flag whether a session is live or not
    87  }
    88  
    89  // NewMatcher creates a new pipeline for retrieving bloom bit streams and doing
    90  // address and topic filtering on them. Setting a filter component to `nil` is
    91  // allowed and will result in that filter rule being skipped (OR 0x11...1).
    92  func NewMatcher(sectionSize uint64, filters [][][]byte) *Matcher {
    93  	// Create the matcher instance
    94  	m := &Matcher{
    95  		sectionSize: sectionSize,
    96  		schedulers:  make(map[uint]*scheduler),
    97  		retrievers:  make(chan chan uint),
    98  		counters:    make(chan chan uint),
    99  		retrievals:  make(chan chan *Retrieval),
   100  		deliveries:  make(chan *Retrieval),
   101  	}
   102  	// Calculate the bloom bit indexes for the groups we're interested in
   103  	m.filters = nil
   104  
   105  	for _, filter := range filters {
   106  		// Gather the bit indexes of the filter rule, special casing the nil filter
   107  		if len(filter) == 0 {
   108  			continue
   109  		}
   110  		bloomBits := make([]bloomIndexes, len(filter))
   111  		for i, clause := range filter {
   112  			if clause == nil {
   113  				bloomBits = nil
   114  				break
   115  			}
   116  			bloomBits[i] = calcBloomIndexes(clause)
   117  		}
   118  		// Accumulate the filter rules if no nil rule was within
   119  		if bloomBits != nil {
   120  			m.filters = append(m.filters, bloomBits)
   121  		}
   122  	}
   123  	// For every bit, create a scheduler to load/download the bit vectors
   124  	for _, bloomIndexLists := range m.filters {
   125  		for _, bloomIndexList := range bloomIndexLists {
   126  			for _, bloomIndex := range bloomIndexList {
   127  				m.addScheduler(bloomIndex)
   128  			}
   129  		}
   130  	}
   131  	return m
   132  }
   133  
   134  // addScheduler adds a bit stream retrieval scheduler for the given bit index if
   135  // it has not existed before. If the bit is already selected for filtering, the
   136  // existing scheduler can be used.
   137  func (m *Matcher) addScheduler(idx uint) {
   138  	if _, ok := m.schedulers[idx]; ok {
   139  		return
   140  	}
   141  	m.schedulers[idx] = newScheduler(idx)
   142  }
   143  
   144  // Start starts the matching process and returns a stream of bloom matches in
   145  // a given range of blocks. If there are no more matches in the range, the result
   146  // channel is closed.
   147  func (m *Matcher) Start(ctx context.Context, begin, end uint64, results chan uint64) (*MatcherSession, error) {
   148  	// Make sure we're not creating concurrent sessions
   149  	if atomic.SwapUint32(&m.running, 1) == 1 {
   150  		return nil, errors.New("matcher already running")
   151  	}
   152  	defer atomic.StoreUint32(&m.running, 0)
   153  
   154  	// Initiate a new matching round
   155  	session := &MatcherSession{
   156  		matcher: m,
   157  		quit:    make(chan struct{}),
   158  		kill:    make(chan struct{}),
   159  		ctx:     ctx,
   160  	}
   161  	for _, scheduler := range m.schedulers {
   162  		scheduler.reset()
   163  	}
   164  	sink := m.run(begin, end, cap(results), session)
   165  
   166  	// Read the output from the result sink and deliver to the user
   167  	session.pend.Add(1)
   168  	go func() {
   169  		defer session.pend.Done()
   170  		defer close(results)
   171  
   172  		for {
   173  			select {
   174  			case <-session.quit:
   175  				return
   176  
   177  			case res, ok := <-sink:
   178  				// New match result found
   179  				if !ok {
   180  					return
   181  				}
   182  				// Calculate the first and last blocks of the section
   183  				sectionStart := res.section * m.sectionSize
   184  
   185  				first := sectionStart
   186  				if begin > first {
   187  					first = begin
   188  				}
   189  				last := sectionStart + m.sectionSize - 1
   190  				if end < last {
   191  					last = end
   192  				}
   193  				// Iterate over all the blocks in the section and return the matching ones
   194  				for i := first; i <= last; i++ {
   195  					// Skip the entire byte if no matches are found inside (and we're processing an entire byte!)
   196  					next := res.bitset[(i-sectionStart)/8]
   197  					if next == 0 {
   198  						if i%8 == 0 {
   199  							i += 7
   200  						}
   201  						continue
   202  					}
   203  					// Some bit it set, do the actual submatching
   204  					if bit := 7 - i%8; next&(1<<bit) != 0 {
   205  						select {
   206  						case <-session.quit:
   207  							return
   208  						case results <- i:
   209  						}
   210  					}
   211  				}
   212  			}
   213  		}
   214  	}()
   215  	return session, nil
   216  }
   217  
   218  // run creates a daisy-chain of sub-matchers, one for the address set and one
   219  // for each topic set, each sub-matcher receiving a section only if the previous
   220  // ones have all found a potential match in one of the blocks of the section,
   221  // then binary AND-ing its own matches and forwaring the result to the next one.
   222  //
   223  // The method starts feeding the section indexes into the first sub-matcher on a
   224  // new goroutine and returns a sink channel receiving the results.
   225  func (m *Matcher) run(begin, end uint64, buffer int, session *MatcherSession) chan *partialMatches {
   226  	// Create the source channel and feed section indexes into
   227  	source := make(chan *partialMatches, buffer)
   228  
   229  	session.pend.Add(1)
   230  	go func() {
   231  		defer session.pend.Done()
   232  		defer close(source)
   233  
   234  		for i := begin / m.sectionSize; i <= end/m.sectionSize; i++ {
   235  			select {
   236  			case <-session.quit:
   237  				return
   238  			case source <- &partialMatches{i, bytes.Repeat([]byte{0xff}, int(m.sectionSize/8))}:
   239  			}
   240  		}
   241  	}()
   242  	// Assemble the daisy-chained filtering pipeline
   243  	next := source
   244  	dist := make(chan *request, buffer)
   245  
   246  	for _, bloom := range m.filters {
   247  		next = m.subMatch(next, dist, bloom, session)
   248  	}
   249  	// Start the request distribution
   250  	session.pend.Add(1)
   251  	go m.distributor(dist, session)
   252  
   253  	return next
   254  }
   255  
   256  // subMatch creates a sub-matcher that filters for a set of addresses or topics, binary OR-s those matches, then
   257  // binary AND-s the result to the daisy-chain input (source) and forwards it to the daisy-chain output.
   258  // The matches of each address/topic are calculated by fetching the given sections of the three bloom bit indexes belonging to
   259  // that address/topic, and binary AND-ing those vectors together.
   260  func (m *Matcher) subMatch(source chan *partialMatches, dist chan *request, bloom []bloomIndexes, session *MatcherSession) chan *partialMatches {
   261  	// Start the concurrent schedulers for each bit required by the bloom filter
   262  	sectionSources := make([][3]chan uint64, len(bloom))
   263  	sectionSinks := make([][3]chan []byte, len(bloom))
   264  	for i, bits := range bloom {
   265  		for j, bit := range bits {
   266  			sectionSources[i][j] = make(chan uint64, cap(source))
   267  			sectionSinks[i][j] = make(chan []byte, cap(source))
   268  
   269  			m.schedulers[bit].run(sectionSources[i][j], dist, sectionSinks[i][j], session.quit, &session.pend)
   270  		}
   271  	}
   272  
   273  	process := make(chan *partialMatches, cap(source)) // entries from source are forwarded here after fetches have been initiated
   274  	results := make(chan *partialMatches, cap(source))
   275  
   276  	session.pend.Add(2)
   277  	go func() {
   278  		// Tear down the goroutine and terminate all source channels
   279  		defer session.pend.Done()
   280  		defer close(process)
   281  
   282  		defer func() {
   283  			for _, bloomSources := range sectionSources {
   284  				for _, bitSource := range bloomSources {
   285  					close(bitSource)
   286  				}
   287  			}
   288  		}()
   289  		// Read sections from the source channel and multiplex into all bit-schedulers
   290  		for {
   291  			select {
   292  			case <-session.quit:
   293  				return
   294  
   295  			case subres, ok := <-source:
   296  				// New subresult from previous link
   297  				if !ok {
   298  					return
   299  				}
   300  				// Multiplex the section index to all bit-schedulers
   301  				for _, bloomSources := range sectionSources {
   302  					for _, bitSource := range bloomSources {
   303  						select {
   304  						case <-session.quit:
   305  							return
   306  						case bitSource <- subres.section:
   307  						}
   308  					}
   309  				}
   310  				// Notify the processor that this section will become available
   311  				select {
   312  				case <-session.quit:
   313  					return
   314  				case process <- subres:
   315  				}
   316  			}
   317  		}
   318  	}()
   319  
   320  	go func() {
   321  		// Tear down the goroutine and terminate the final sink channel
   322  		defer session.pend.Done()
   323  		defer close(results)
   324  
   325  		// Read the source notifications and collect the delivered results
   326  		for {
   327  			select {
   328  			case <-session.quit:
   329  				return
   330  
   331  			case subres, ok := <-process:
   332  				// Notified of a section being retrieved
   333  				if !ok {
   334  					return
   335  				}
   336  				// Gather all the sub-results and merge them together
   337  				var orVector []byte
   338  				for _, bloomSinks := range sectionSinks {
   339  					var andVector []byte
   340  					for _, bitSink := range bloomSinks {
   341  						var data []byte
   342  						select {
   343  						case <-session.quit:
   344  							return
   345  						case data = <-bitSink:
   346  						}
   347  						if andVector == nil {
   348  							andVector = make([]byte, int(m.sectionSize/8))
   349  							copy(andVector, data)
   350  						} else {
   351  							bitutil.ANDBytes(andVector, andVector, data)
   352  						}
   353  					}
   354  					if orVector == nil {
   355  						orVector = andVector
   356  					} else {
   357  						bitutil.ORBytes(orVector, orVector, andVector)
   358  					}
   359  				}
   360  
   361  				if orVector == nil {
   362  					orVector = make([]byte, int(m.sectionSize/8))
   363  				}
   364  				if subres.bitset != nil {
   365  					bitutil.ANDBytes(orVector, orVector, subres.bitset)
   366  				}
   367  				if bitutil.TestBytes(orVector) {
   368  					select {
   369  					case <-session.quit:
   370  						return
   371  					case results <- &partialMatches{subres.section, orVector}:
   372  					}
   373  				}
   374  			}
   375  		}
   376  	}()
   377  	return results
   378  }
   379  
   380  // distributor receives requests from the schedulers and queues them into a set
   381  // of pending requests, which are assigned to retrievers wanting to fulfil them.
   382  func (m *Matcher) distributor(dist chan *request, session *MatcherSession) {
   383  	defer session.pend.Done()
   384  
   385  	var (
   386  		requests   = make(map[uint][]uint64) // Per-bit list of section requests, ordered by section number
   387  		unallocs   = make(map[uint]struct{}) // Bits with pending requests but not allocated to any retriever
   388  		retrievers chan chan uint            // Waiting retrievers (toggled to nil if unallocs is empty)
   389  	)
   390  	var (
   391  		allocs   int            // Number of active allocations to handle graceful shutdown requests
   392  		shutdown = session.quit // Shutdown request channel, will gracefully wait for pending requests
   393  	)
   394  
   395  	// assign is a helper method fo try to assign a pending bit an an actively
   396  	// listening servicer, or schedule it up for later when one arrives.
   397  	assign := func(bit uint) {
   398  		select {
   399  		case fetcher := <-m.retrievers:
   400  			allocs++
   401  			fetcher <- bit
   402  		default:
   403  			// No retrievers active, start listening for new ones
   404  			retrievers = m.retrievers
   405  			unallocs[bit] = struct{}{}
   406  		}
   407  	}
   408  
   409  	for {
   410  		select {
   411  		case <-shutdown:
   412  			// Graceful shutdown requested, wait until all pending requests are honoured
   413  			if allocs == 0 {
   414  				return
   415  			}
   416  			shutdown = nil
   417  
   418  		case <-session.kill:
   419  			// Pending requests not honoured in time, hard terminate
   420  			return
   421  
   422  		case req := <-dist:
   423  			// New retrieval request arrived to be distributed to some fetcher process
   424  			queue := requests[req.bit]
   425  			index := sort.Search(len(queue), func(i int) bool { return queue[i] >= req.section })
   426  			requests[req.bit] = append(queue[:index], append([]uint64{req.section}, queue[index:]...)...)
   427  
   428  			// If it's a new bit and we have waiting fetchers, allocate to them
   429  			if len(queue) == 0 {
   430  				assign(req.bit)
   431  			}
   432  
   433  		case fetcher := <-retrievers:
   434  			// New retriever arrived, find the lowest section-ed bit to assign
   435  			bit, best := uint(0), uint64(math.MaxUint64)
   436  			for idx := range unallocs {
   437  				if requests[idx][0] < best {
   438  					bit, best = idx, requests[idx][0]
   439  				}
   440  			}
   441  			// Stop tracking this bit (and alloc notifications if no more work is available)
   442  			delete(unallocs, bit)
   443  			if len(unallocs) == 0 {
   444  				retrievers = nil
   445  			}
   446  			allocs++
   447  			fetcher <- bit
   448  
   449  		case fetcher := <-m.counters:
   450  			// New task count request arrives, return number of items
   451  			fetcher <- uint(len(requests[<-fetcher]))
   452  
   453  		case fetcher := <-m.retrievals:
   454  			// New fetcher waiting for tasks to retrieve, assign
   455  			task := <-fetcher
   456  			if want := len(task.Sections); want >= len(requests[task.Bit]) {
   457  				task.Sections = requests[task.Bit]
   458  				delete(requests, task.Bit)
   459  			} else {
   460  				task.Sections = append(task.Sections[:0], requests[task.Bit][:want]...)
   461  				requests[task.Bit] = append(requests[task.Bit][:0], requests[task.Bit][want:]...)
   462  			}
   463  			fetcher <- task
   464  
   465  			// If anything was left unallocated, try to assign to someone else
   466  			if len(requests[task.Bit]) > 0 {
   467  				assign(task.Bit)
   468  			}
   469  
   470  		case result := <-m.deliveries:
   471  			// New retrieval task response from fetcher, split out missing sections and
   472  			// deliver complete ones
   473  			var (
   474  				sections = make([]uint64, 0, len(result.Sections))
   475  				bitsets  = make([][]byte, 0, len(result.Bitsets))
   476  				missing  = make([]uint64, 0, len(result.Sections))
   477  			)
   478  			for i, bitset := range result.Bitsets {
   479  				if len(bitset) == 0 {
   480  					missing = append(missing, result.Sections[i])
   481  					continue
   482  				}
   483  				sections = append(sections, result.Sections[i])
   484  				bitsets = append(bitsets, bitset)
   485  			}
   486  			m.schedulers[result.Bit].deliver(sections, bitsets)
   487  			allocs--
   488  
   489  			// Reschedule missing sections and allocate bit if newly available
   490  			if len(missing) > 0 {
   491  				queue := requests[result.Bit]
   492  				for _, section := range missing {
   493  					index := sort.Search(len(queue), func(i int) bool { return queue[i] >= section })
   494  					queue = append(queue[:index], append([]uint64{section}, queue[index:]...)...)
   495  				}
   496  				requests[result.Bit] = queue
   497  
   498  				if len(queue) == len(missing) {
   499  					assign(result.Bit)
   500  				}
   501  			}
   502  			// If we're in the process of shutting down, terminate
   503  			if allocs == 0 && shutdown == nil {
   504  				return
   505  			}
   506  		}
   507  	}
   508  }
   509  
   510  // MatcherSession is returned by a started matcher to be used as a terminator
   511  // for the actively running matching operation.
   512  type MatcherSession struct {
   513  	matcher *Matcher
   514  
   515  	closer sync.Once     // Sync object to ensure we only ever close once
   516  	quit   chan struct{} // Quit channel to request pipeline termination
   517  	kill   chan struct{} // Term channel to signal non-graceful forced shutdown
   518  
   519  	ctx context.Context // Context used by the light client to abort filtering
   520  	err atomic.Value    // Global error to track retrieval failures deep in the chain
   521  
   522  	pend sync.WaitGroup
   523  }
   524  
   525  // Close stops the matching process and waits for all subprocesses to terminate
   526  // before returning. The timeout may be used for graceful shutdown, allowing the
   527  // currently running retrievals to complete before this time.
   528  func (s *MatcherSession) Close() {
   529  	s.closer.Do(func() {
   530  		// Signal termination and wait for all goroutines to tear down
   531  		close(s.quit)
   532  		time.AfterFunc(time.Second, func() { close(s.kill) })
   533  		s.pend.Wait()
   534  	})
   535  }
   536  
   537  // Error returns any failure encountered during the matching session.
   538  func (s *MatcherSession) Error() error {
   539  	if err := s.err.Load(); err != nil {
   540  		return err.(error)
   541  	}
   542  	return nil
   543  }
   544  
   545  // AllocateRetrieval assigns a bloom bit index to a client process that can either
   546  // immediately reuest and fetch the section contents assigned to this bit or wait
   547  // a little while for more sections to be requested.
   548  func (s *MatcherSession) AllocateRetrieval() (uint, bool) {
   549  	fetcher := make(chan uint)
   550  
   551  	select {
   552  	case <-s.quit:
   553  		return 0, false
   554  	case s.matcher.retrievers <- fetcher:
   555  		bit, ok := <-fetcher
   556  		return bit, ok
   557  	}
   558  }
   559  
   560  // PendingSections returns the number of pending section retrievals belonging to
   561  // the given bloom bit index.
   562  func (s *MatcherSession) PendingSections(bit uint) int {
   563  	fetcher := make(chan uint)
   564  
   565  	select {
   566  	case <-s.quit:
   567  		return 0
   568  	case s.matcher.counters <- fetcher:
   569  		fetcher <- bit
   570  		return int(<-fetcher)
   571  	}
   572  }
   573  
   574  // AllocateSections assigns all or part of an already allocated bit-task queue
   575  // to the requesting process.
   576  func (s *MatcherSession) AllocateSections(bit uint, count int) []uint64 {
   577  	fetcher := make(chan *Retrieval)
   578  
   579  	select {
   580  	case <-s.quit:
   581  		return nil
   582  	case s.matcher.retrievals <- fetcher:
   583  		task := &Retrieval{
   584  			Bit:      bit,
   585  			Sections: make([]uint64, count),
   586  		}
   587  		fetcher <- task
   588  		return (<-fetcher).Sections
   589  	}
   590  }
   591  
   592  // DeliverSections delivers a batch of section bit-vectors for a specific bloom
   593  // bit index to be injected into the processing pipeline.
   594  func (s *MatcherSession) DeliverSections(bit uint, sections []uint64, bitsets [][]byte) {
   595  	select {
   596  	case <-s.kill:
   597  		return
   598  	case s.matcher.deliveries <- &Retrieval{Bit: bit, Sections: sections, Bitsets: bitsets}:
   599  	}
   600  }
   601  
   602  // Multiplex polls the matcher session for rerieval tasks and multiplexes it into
   603  // the reuested retrieval queue to be serviced together with other sessions.
   604  //
   605  // This method will block for the lifetime of the session. Even after termination
   606  // of the session, any request in-flight need to be responded to! Empty responses
   607  // are fine though in that case.
   608  func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan *Retrieval) {
   609  	for {
   610  		// Allocate a new bloom bit index to retrieve data for, stopping when done
   611  		bit, ok := s.AllocateRetrieval()
   612  		if !ok {
   613  			return
   614  		}
   615  		// Bit allocated, throttle a bit if we're below our batch limit
   616  		if s.PendingSections(bit) < batch {
   617  			select {
   618  			case <-s.quit:
   619  				// Session terminating, we can't meaningfully service, abort
   620  				s.AllocateSections(bit, 0)
   621  				s.DeliverSections(bit, []uint64{}, [][]byte{})
   622  				return
   623  
   624  			case <-time.After(wait):
   625  				// Throttling up, fetch whatever's available
   626  			}
   627  		}
   628  		// Allocate as much as we can handle and request servicing
   629  		sections := s.AllocateSections(bit, batch)
   630  		request := make(chan *Retrieval)
   631  
   632  		select {
   633  		case <-s.quit:
   634  			// Session terminating, we can't meaningfully service, abort
   635  			s.DeliverSections(bit, sections, make([][]byte, len(sections)))
   636  			return
   637  
   638  		case mux <- request:
   639  			// Retrieval accepted, something must arrive before we're aborting
   640  			request <- &Retrieval{Bit: bit, Sections: sections, Context: s.ctx}
   641  
   642  			result := <-request
   643  			if result.Error != nil {
   644  				s.err.Store(result.Error)
   645  				s.Close()
   646  			}
   647  			s.DeliverSections(result.Bit, result.Sections, result.Bitsets)
   648  		}
   649  	}
   650  }