github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/core/bloombits/matcher_test.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 bloombits
    19  
    20  import (
    21  	"context"
    22  	"math/rand"
    23  	"sync/atomic"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/AigarNetwork/aigar/common"
    28  )
    29  
    30  const testSectionSize = 4096
    31  
    32  // Tests that wildcard filter rules (nil) can be specified and are handled well.
    33  func TestMatcherWildcards(t *testing.T) {
    34  	matcher := NewMatcher(testSectionSize, [][][]byte{
    35  		{common.Address{}.Bytes(), common.Address{0x01}.Bytes()}, // Default address is not a wildcard
    36  		{common.Hash{}.Bytes(), common.Hash{0x01}.Bytes()},       // Default hash is not a wildcard
    37  		{common.Hash{0x01}.Bytes()},                              // Plain rule, sanity check
    38  		{common.Hash{0x01}.Bytes(), nil},                         // Wildcard suffix, drop rule
    39  		{nil, common.Hash{0x01}.Bytes()},                         // Wildcard prefix, drop rule
    40  		{nil, nil},                                               // Wildcard combo, drop rule
    41  		{},                                                       // Inited wildcard rule, drop rule
    42  		nil,                                                      // Proper wildcard rule, drop rule
    43  	})
    44  	if len(matcher.filters) != 3 {
    45  		t.Fatalf("filter system size mismatch: have %d, want %d", len(matcher.filters), 3)
    46  	}
    47  	if len(matcher.filters[0]) != 2 {
    48  		t.Fatalf("address clause size mismatch: have %d, want %d", len(matcher.filters[0]), 2)
    49  	}
    50  	if len(matcher.filters[1]) != 2 {
    51  		t.Fatalf("combo topic clause size mismatch: have %d, want %d", len(matcher.filters[1]), 2)
    52  	}
    53  	if len(matcher.filters[2]) != 1 {
    54  		t.Fatalf("singletone topic clause size mismatch: have %d, want %d", len(matcher.filters[2]), 1)
    55  	}
    56  }
    57  
    58  // Tests the matcher pipeline on a single continuous workflow without interrupts.
    59  func TestMatcherContinuous(t *testing.T) {
    60  	testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, false, 75)
    61  	testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, false, 81)
    62  	testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 0, 10000, false, 36)
    63  }
    64  
    65  // Tests the matcher pipeline on a constantly interrupted and resumed work pattern
    66  // with the aim of ensuring data items are requested only once.
    67  func TestMatcherIntermittent(t *testing.T) {
    68  	testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, true, 75)
    69  	testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, true, 81)
    70  	testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 0, 10000, true, 36)
    71  }
    72  
    73  // Tests the matcher pipeline on random input to hopefully catch anomalies.
    74  func TestMatcherRandom(t *testing.T) {
    75  	for i := 0; i < 10; i++ {
    76  		testMatcherBothModes(t, makeRandomIndexes([]int{1}, 50), 0, 10000, 0)
    77  		testMatcherBothModes(t, makeRandomIndexes([]int{3}, 50), 0, 10000, 0)
    78  		testMatcherBothModes(t, makeRandomIndexes([]int{2, 2, 2}, 20), 0, 10000, 0)
    79  		testMatcherBothModes(t, makeRandomIndexes([]int{5, 5, 5}, 50), 0, 10000, 0)
    80  		testMatcherBothModes(t, makeRandomIndexes([]int{4, 4, 4}, 20), 0, 10000, 0)
    81  	}
    82  }
    83  
    84  // Tests that the matcher can properly find matches if the starting block is
    85  // shifter from a multiple of 8. This is needed to cover an optimisation with
    86  // bitset matching https://github.com/AigarNetwork/aigar/issues/15309.
    87  func TestMatcherShifted(t *testing.T) {
    88  	// Block 0 always matches in the tests, skip ahead of first 8 blocks with the
    89  	// start to get a potential zero byte in the matcher bitset.
    90  
    91  	// To keep the second bitset byte zero, the filter must only match for the first
    92  	// time in block 16, so doing an all-16 bit filter should suffice.
    93  
    94  	// To keep the starting block non divisible by 8, block number 9 is the first
    95  	// that would introduce a shift and not match block 0.
    96  	testMatcherBothModes(t, [][]bloomIndexes{{{16, 16, 16}}}, 9, 64, 0)
    97  }
    98  
    99  // Tests that matching on everything doesn't crash (special case internally).
   100  func TestWildcardMatcher(t *testing.T) {
   101  	testMatcherBothModes(t, nil, 0, 10000, 0)
   102  }
   103  
   104  // makeRandomIndexes generates a random filter system, composed on multiple filter
   105  // criteria, each having one bloom list component for the address and arbitrarily
   106  // many topic bloom list components.
   107  func makeRandomIndexes(lengths []int, max int) [][]bloomIndexes {
   108  	res := make([][]bloomIndexes, len(lengths))
   109  	for i, topics := range lengths {
   110  		res[i] = make([]bloomIndexes, topics)
   111  		for j := 0; j < topics; j++ {
   112  			for k := 0; k < len(res[i][j]); k++ {
   113  				res[i][j][k] = uint(rand.Intn(max-1) + 2)
   114  			}
   115  		}
   116  	}
   117  	return res
   118  }
   119  
   120  // testMatcherDiffBatches runs the given matches test in single-delivery and also
   121  // in batches delivery mode, verifying that all kinds of deliveries are handled
   122  // correctly withn.
   123  func testMatcherDiffBatches(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, intermittent bool, retrievals uint32) {
   124  	singleton := testMatcher(t, filter, start, blocks, intermittent, retrievals, 1)
   125  	batched := testMatcher(t, filter, start, blocks, intermittent, retrievals, 16)
   126  
   127  	if singleton != batched {
   128  		t.Errorf("filter = %v blocks = %v intermittent = %v: request count mismatch, %v in signleton vs. %v in batched mode", filter, blocks, intermittent, singleton, batched)
   129  	}
   130  }
   131  
   132  // testMatcherBothModes runs the given matcher test in both continuous as well as
   133  // in intermittent mode, verifying that the request counts match each other.
   134  func testMatcherBothModes(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, retrievals uint32) {
   135  	continuous := testMatcher(t, filter, start, blocks, false, retrievals, 16)
   136  	intermittent := testMatcher(t, filter, start, blocks, true, retrievals, 16)
   137  
   138  	if continuous != intermittent {
   139  		t.Errorf("filter = %v blocks = %v: request count mismatch, %v in continuous vs. %v in intermittent mode", filter, blocks, continuous, intermittent)
   140  	}
   141  }
   142  
   143  // testMatcher is a generic tester to run the given matcher test and return the
   144  // number of requests made for cross validation between different modes.
   145  func testMatcher(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, intermittent bool, retrievals uint32, maxReqCount int) uint32 {
   146  	// Create a new matcher an simulate our explicit random bitsets
   147  	matcher := NewMatcher(testSectionSize, nil)
   148  	matcher.filters = filter
   149  
   150  	for _, rule := range filter {
   151  		for _, topic := range rule {
   152  			for _, bit := range topic {
   153  				matcher.addScheduler(bit)
   154  			}
   155  		}
   156  	}
   157  	// Track the number of retrieval requests made
   158  	var requested uint32
   159  
   160  	// Start the matching session for the filter and the retriever goroutines
   161  	quit := make(chan struct{})
   162  	matches := make(chan uint64, 16)
   163  
   164  	session, err := matcher.Start(context.Background(), start, blocks-1, matches)
   165  	if err != nil {
   166  		t.Fatalf("failed to stat matcher session: %v", err)
   167  	}
   168  	startRetrievers(session, quit, &requested, maxReqCount)
   169  
   170  	// Iterate over all the blocks and verify that the pipeline produces the correct matches
   171  	for i := start; i < blocks; i++ {
   172  		if expMatch3(filter, i) {
   173  			match, ok := <-matches
   174  			if !ok {
   175  				t.Errorf("filter = %v  blocks = %v  intermittent = %v: expected #%v, results channel closed", filter, blocks, intermittent, i)
   176  				return 0
   177  			}
   178  			if match != i {
   179  				t.Errorf("filter = %v  blocks = %v  intermittent = %v: expected #%v, got #%v", filter, blocks, intermittent, i, match)
   180  			}
   181  			// If we're testing intermittent mode, abort and restart the pipeline
   182  			if intermittent {
   183  				session.Close()
   184  				close(quit)
   185  
   186  				quit = make(chan struct{})
   187  				matches = make(chan uint64, 16)
   188  
   189  				session, err = matcher.Start(context.Background(), i+1, blocks-1, matches)
   190  				if err != nil {
   191  					t.Fatalf("failed to stat matcher session: %v", err)
   192  				}
   193  				startRetrievers(session, quit, &requested, maxReqCount)
   194  			}
   195  		}
   196  	}
   197  	// Ensure the result channel is torn down after the last block
   198  	match, ok := <-matches
   199  	if ok {
   200  		t.Errorf("filter = %v  blocks = %v  intermittent = %v: expected closed channel, got #%v", filter, blocks, intermittent, match)
   201  	}
   202  	// Clean up the session and ensure we match the expected retrieval count
   203  	session.Close()
   204  	close(quit)
   205  
   206  	if retrievals != 0 && requested != retrievals {
   207  		t.Errorf("filter = %v  blocks = %v  intermittent = %v: request count mismatch, have #%v, want #%v", filter, blocks, intermittent, requested, retrievals)
   208  	}
   209  	return requested
   210  }
   211  
   212  // startRetrievers starts a batch of goroutines listening for section requests
   213  // and serving them.
   214  func startRetrievers(session *MatcherSession, quit chan struct{}, retrievals *uint32, batch int) {
   215  	requests := make(chan chan *Retrieval)
   216  
   217  	for i := 0; i < 10; i++ {
   218  		// Start a multiplexer to test multiple threaded execution
   219  		go session.Multiplex(batch, 100*time.Microsecond, requests)
   220  
   221  		// Start a services to match the above multiplexer
   222  		go func() {
   223  			for {
   224  				// Wait for a service request or a shutdown
   225  				select {
   226  				case <-quit:
   227  					return
   228  
   229  				case request := <-requests:
   230  					task := <-request
   231  
   232  					task.Bitsets = make([][]byte, len(task.Sections))
   233  					for i, section := range task.Sections {
   234  						if rand.Int()%4 != 0 { // Handle occasional missing deliveries
   235  							task.Bitsets[i] = generateBitset(task.Bit, section)
   236  							atomic.AddUint32(retrievals, 1)
   237  						}
   238  					}
   239  					request <- task
   240  				}
   241  			}
   242  		}()
   243  	}
   244  }
   245  
   246  // generateBitset generates the rotated bitset for the given bloom bit and section
   247  // numbers.
   248  func generateBitset(bit uint, section uint64) []byte {
   249  	bitset := make([]byte, testSectionSize/8)
   250  	for i := 0; i < len(bitset); i++ {
   251  		for b := 0; b < 8; b++ {
   252  			blockIdx := section*testSectionSize + uint64(i*8+b)
   253  			bitset[i] += bitset[i]
   254  			if (blockIdx % uint64(bit)) == 0 {
   255  				bitset[i]++
   256  			}
   257  		}
   258  	}
   259  	return bitset
   260  }
   261  
   262  func expMatch1(filter bloomIndexes, i uint64) bool {
   263  	for _, ii := range filter {
   264  		if (i % uint64(ii)) != 0 {
   265  			return false
   266  		}
   267  	}
   268  	return true
   269  }
   270  
   271  func expMatch2(filter []bloomIndexes, i uint64) bool {
   272  	for _, ii := range filter {
   273  		if expMatch1(ii, i) {
   274  			return true
   275  		}
   276  	}
   277  	return false
   278  }
   279  
   280  func expMatch3(filter [][]bloomIndexes, i uint64) bool {
   281  	for _, ii := range filter {
   282  		if !expMatch2(ii, i) {
   283  			return false
   284  		}
   285  	}
   286  	return true
   287  }