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