Skip to content

Commit 2ab2a9f

Browse files
karalabefjl
authored andcommitted
core/bloombits, eth/filters: handle null topics (#15195)
When implementing the new bloombits based filter, I've accidentally broke null topics by removing the special casing of common.Hash{} filter rules, which acted as the wildcard topic until now. This PR fixes the regression, but instead of using the magic hash common.Hash{} as the null wildcard, the PR reworks the code to handle nil topics during parsing, converting a JSON null into nil []common.Hash topic.
1 parent 860e697 commit 2ab2a9f

File tree

7 files changed

+68
-48
lines changed

7 files changed

+68
-48
lines changed

core/bloombits/matcher.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ type Matcher struct {
8080
}
8181

8282
// NewMatcher creates a new pipeline for retrieving bloom bit streams and doing
83-
// address and topic filtering on them.
83+
// address and topic filtering on them. Setting a filter component to `nil` is
84+
// allowed and will result in that filter rule being skipped (OR 0x11...1).
8485
func NewMatcher(sectionSize uint64, filters [][][]byte) *Matcher {
8586
// Create the matcher instance
8687
m := &Matcher{
@@ -95,11 +96,22 @@ func NewMatcher(sectionSize uint64, filters [][][]byte) *Matcher {
9596
m.filters = nil
9697

9798
for _, filter := range filters {
99+
// Gather the bit indexes of the filter rule, special casing the nil filter
100+
if len(filter) == 0 {
101+
continue
102+
}
98103
bloomBits := make([]bloomIndexes, len(filter))
99104
for i, clause := range filter {
105+
if clause == nil {
106+
bloomBits = nil
107+
break
108+
}
100109
bloomBits[i] = calcBloomIndexes(clause)
101110
}
102-
m.filters = append(m.filters, bloomBits)
111+
// Accumulate the filter rules if no nil rule was within
112+
if bloomBits != nil {
113+
m.filters = append(m.filters, bloomBits)
114+
}
103115
}
104116
// For every bit, create a scheduler to load/download the bit vectors
105117
for _, bloomIndexLists := range m.filters {

core/bloombits/matcher_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,38 @@ import (
2121
"sync/atomic"
2222
"testing"
2323
"time"
24+
25+
"github.com/ethereum/go-ethereum/common"
2426
)
2527

2628
const testSectionSize = 4096
2729

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+
2856
// Tests the matcher pipeline on a single continuous workflow without interrupts.
2957
func TestMatcherContinuous(t *testing.T) {
3058
testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 100000, false, 75)

eth/filters/api.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,6 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
498498
switch topic := t.(type) {
499499
case nil:
500500
// ignore topic when matching logs
501-
args.Topics[i] = []common.Hash{{}}
502501

503502
case string:
504503
// match specific topic
@@ -507,12 +506,16 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
507506
return err
508507
}
509508
args.Topics[i] = []common.Hash{top}
509+
510510
case []interface{}:
511511
// or case e.g. [null, "topic0", "topic1"]
512512
for _, rawTopic := range topic {
513513
if rawTopic == nil {
514-
args.Topics[i] = append(args.Topics[i], common.Hash{})
515-
} else if topic, ok := rawTopic.(string); ok {
514+
// null component, match all
515+
args.Topics[i] = nil
516+
break
517+
}
518+
if topic, ok := rawTopic.(string); ok {
516519
parsed, err := decodeTopic(topic)
517520
if err != nil {
518521
return err

eth/filters/api_test.go

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) {
3434
topic0 = common.HexToHash("3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1ca")
3535
topic1 = common.HexToHash("9084a792d2f8b16a62b882fd56f7860c07bf5fa91dd8a2ae7e809e5180fef0b3")
3636
topic2 = common.HexToHash("6ccae1c4af4152f460ff510e573399795dfab5dcf1fa60d1f33ac8fdc1e480ce")
37-
nullTopic = common.Hash{}
3837
)
3938

4039
// default values
@@ -150,11 +149,8 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) {
150149
if test6.Topics[0][0] != topic0 {
151150
t.Fatalf("got %x, expected %x", test6.Topics[0][0], topic0)
152151
}
153-
if len(test6.Topics[1]) != 1 {
154-
t.Fatalf("expected 1 topic, got %d", len(test6.Topics[1]))
155-
}
156-
if test6.Topics[1][0] != nullTopic {
157-
t.Fatalf("got %x, expected empty hash", test6.Topics[1][0])
152+
if len(test6.Topics[1]) != 0 {
153+
t.Fatalf("expected 0 topic, got %d", len(test6.Topics[1]))
158154
}
159155
if len(test6.Topics[2]) != 1 {
160156
t.Fatalf("expected 1 topic, got %d", len(test6.Topics[2]))
@@ -180,18 +176,10 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) {
180176
topic0, topic1, test7.Topics[0][0], test7.Topics[0][1],
181177
)
182178
}
183-
if len(test7.Topics[1]) != 1 {
184-
t.Fatalf("expected 1 topic, got %d topics", len(test7.Topics[1]))
185-
}
186-
if test7.Topics[1][0] != nullTopic {
187-
t.Fatalf("expected empty hash, got %x", test7.Topics[1][0])
188-
}
189-
if len(test7.Topics[2]) != 2 {
190-
t.Fatalf("expected 2 topics, got %d topics", len(test7.Topics[2]))
179+
if len(test7.Topics[1]) != 0 {
180+
t.Fatalf("expected 0 topic, got %d topics", len(test7.Topics[1]))
191181
}
192-
if test7.Topics[2][0] != topic2 || test7.Topics[2][1] != nullTopic {
193-
t.Fatalf("invalid topics expected [%x,%x], got [%x,%x]",
194-
topic2, nullTopic, test7.Topics[2][0], test7.Topics[2][1],
195-
)
182+
if len(test7.Topics[2]) != 0 {
183+
t.Fatalf("expected 0 topics, got %d topics", len(test7.Topics[2]))
196184
}
197185
}

eth/filters/filter.go

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ type Filter struct {
6060
// New creates a new filter which uses a bloom filter on blocks to figure out whether
6161
// a particular block is interesting or not.
6262
func New(backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter {
63-
// Flatten the address and topic filter clauses into a single filter system
63+
// Flatten the address and topic filter clauses into a single bloombits filter
64+
// system. Since the bloombits are not positional, nil topics are permitted,
65+
// which get flattened into a nil byte slice.
6466
var filters [][][]byte
6567
if len(addresses) > 0 {
6668
filter := make([][]byte, len(addresses))
@@ -235,32 +237,24 @@ Logs:
235237
if len(addresses) > 0 && !includes(addresses, log.Address) {
236238
continue
237239
}
238-
239-
logTopics := make([]common.Hash, len(topics))
240-
copy(logTopics, log.Topics)
241-
242240
// If the to filtered topics is greater than the amount of topics in logs, skip.
243241
if len(topics) > len(log.Topics) {
244242
continue Logs
245243
}
246-
247244
for i, topics := range topics {
248-
var match bool
245+
match := len(topics) == 0 // empty rule set == wildcard
249246
for _, topic := range topics {
250-
// common.Hash{} is a match all (wildcard)
251-
if (topic == common.Hash{}) || log.Topics[i] == topic {
247+
if log.Topics[i] == topic {
252248
match = true
253249
break
254250
}
255251
}
256-
257252
if !match {
258253
continue Logs
259254
}
260255
}
261256
ret = append(ret, log)
262257
}
263-
264258
return ret
265259
}
266260

@@ -273,16 +267,15 @@ func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]commo
273267
break
274268
}
275269
}
276-
277270
if !included {
278271
return false
279272
}
280273
}
281274

282275
for _, sub := range topics {
283-
var included bool
276+
included := len(sub) == 0 // empty rule set == wildcard
284277
for _, topic := range sub {
285-
if (topic == common.Hash{}) || types.BloomLookup(bloom, topic) {
278+
if types.BloomLookup(bloom, topic) {
286279
included = true
287280
break
288281
}
@@ -291,6 +284,5 @@ func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]commo
291284
return false
292285
}
293286
}
294-
295287
return true
296288
}

eth/filters/filter_system.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ func (es *EventSystem) subscribeMinedPendingLogs(crit FilterCriteria, logs chan
212212
installed: make(chan struct{}),
213213
err: make(chan error),
214214
}
215-
216215
return es.subscribe(sub)
217216
}
218217

@@ -230,7 +229,6 @@ func (es *EventSystem) subscribeLogs(crit FilterCriteria, logs chan []*types.Log
230229
installed: make(chan struct{}),
231230
err: make(chan error),
232231
}
233-
234232
return es.subscribe(sub)
235233
}
236234

@@ -248,7 +246,6 @@ func (es *EventSystem) subscribePendingLogs(crit FilterCriteria, logs chan []*ty
248246
installed: make(chan struct{}),
249247
err: make(chan error),
250248
}
251-
252249
return es.subscribe(sub)
253250
}
254251

@@ -265,7 +262,6 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti
265262
installed: make(chan struct{}),
266263
err: make(chan error),
267264
}
268-
269265
return es.subscribe(sub)
270266
}
271267

@@ -282,7 +278,6 @@ func (es *EventSystem) SubscribePendingTxEvents(hashes chan common.Hash) *Subscr
282278
installed: make(chan struct{}),
283279
err: make(chan error),
284280
}
285-
286281
return es.subscribe(sub)
287282
}
288283

eth/filters/filter_system_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ func TestLogFilter(t *testing.T) {
363363
// match all
364364
0: {FilterCriteria{}, allLogs, ""},
365365
// match none due to no matching addresses
366-
1: {FilterCriteria{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{allLogs[0].Topics}}, []*types.Log{}, ""},
366+
1: {FilterCriteria{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{nil}}, []*types.Log{}, ""},
367367
// match logs based on addresses, ignore topics
368368
2: {FilterCriteria{Addresses: []common.Address{firstAddr}}, allLogs[:2], ""},
369369
// match none due to no matching topics (match with address)
@@ -384,6 +384,8 @@ func TestLogFilter(t *testing.T) {
384384
10: {FilterCriteria{FromBlock: big.NewInt(1), ToBlock: big.NewInt(2), Topics: [][]common.Hash{{secondTopic}}}, allLogs[3:4], ""},
385385
// all "mined" and pending logs with topic firstTopic
386386
11: {FilterCriteria{FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), Topics: [][]common.Hash{{firstTopic}}}, expectedCase11, ""},
387+
// match all logs due to wildcard topic
388+
12: {FilterCriteria{Topics: [][]common.Hash{nil}}, allLogs[1:], ""},
387389
}
388390
)
389391

@@ -459,7 +461,7 @@ func TestPendingLogsSubscription(t *testing.T) {
459461
firstTopic = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
460462
secondTopic = common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222")
461463
thirdTopic = common.HexToHash("0x3333333333333333333333333333333333333333333333333333333333333333")
462-
forthTopic = common.HexToHash("0x4444444444444444444444444444444444444444444444444444444444444444")
464+
fourthTopic = common.HexToHash("0x4444444444444444444444444444444444444444444444444444444444444444")
463465
notUsedTopic = common.HexToHash("0x9999999999999999999999999999999999999999999999999999999999999999")
464466

465467
allLogs = []core.PendingLogsEvent{
@@ -471,7 +473,7 @@ func TestPendingLogsSubscription(t *testing.T) {
471473
{Logs: []*types.Log{
472474
{Address: thirdAddress, Topics: []common.Hash{firstTopic}, BlockNumber: 5},
473475
{Address: thirdAddress, Topics: []common.Hash{thirdTopic}, BlockNumber: 5},
474-
{Address: thirdAddress, Topics: []common.Hash{forthTopic}, BlockNumber: 5},
476+
{Address: thirdAddress, Topics: []common.Hash{fourthTopic}, BlockNumber: 5},
475477
{Address: firstAddr, Topics: []common.Hash{firstTopic}, BlockNumber: 5},
476478
}},
477479
}
@@ -493,7 +495,7 @@ func TestPendingLogsSubscription(t *testing.T) {
493495
// match all
494496
{FilterCriteria{}, convertLogs(allLogs), nil, nil},
495497
// match none due to no matching addresses
496-
{FilterCriteria{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{{}}}, []*types.Log{}, nil, nil},
498+
{FilterCriteria{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{nil}}, []*types.Log{}, nil, nil},
497499
// match logs based on addresses, ignore topics
498500
{FilterCriteria{Addresses: []common.Address{firstAddr}}, append(convertLogs(allLogs[:2]), allLogs[5].Logs[3]), nil, nil},
499501
// match none due to no matching topics (match with address)
@@ -505,7 +507,7 @@ func TestPendingLogsSubscription(t *testing.T) {
505507
// block numbers are ignored for filters created with New***Filter, these return all logs that match the given criteria when the state changes
506508
{FilterCriteria{Addresses: []common.Address{firstAddr}, FromBlock: big.NewInt(2), ToBlock: big.NewInt(3)}, append(convertLogs(allLogs[:2]), allLogs[5].Logs[3]), nil, nil},
507509
// multiple pending logs, should match only 2 topics from the logs in block 5
508-
{FilterCriteria{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, forthTopic}}}, []*types.Log{allLogs[5].Logs[0], allLogs[5].Logs[2]}, nil, nil},
510+
{FilterCriteria{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, fourthTopic}}}, []*types.Log{allLogs[5].Logs[0], allLogs[5].Logs[2]}, nil, nil},
509511
}
510512
)
511513

0 commit comments

Comments
 (0)