mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 13:16:42 +00:00
initia custom
This commit is contained in:
parent
36b2371c59
commit
c70cf58157
12 changed files with 1550 additions and 6 deletions
18
core/bloombits/docs.go
Normal file
18
core/bloombits/docs.go
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package bloombits implements bloom filtering on batches of data.
|
||||||
|
package bloombits
|
||||||
98
core/bloombits/generator.go
Normal file
98
core/bloombits/generator.go
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bloombits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// errSectionOutOfBounds is returned if the user tried to add more bloom filters
|
||||||
|
// to the batch than available space, or if tries to retrieve above the capacity.
|
||||||
|
errSectionOutOfBounds = errors.New("section out of bounds")
|
||||||
|
|
||||||
|
// errBloomBitOutOfBounds is returned if the user tried to retrieve specified
|
||||||
|
// bit bloom above the capacity.
|
||||||
|
errBloomBitOutOfBounds = errors.New("bloom bit out of bounds")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generator takes a number of bloom filters and generates the rotated bloom bits
|
||||||
|
// to be used for batched filtering.
|
||||||
|
type Generator struct {
|
||||||
|
blooms [types.BloomBitLength][]byte // Rotated blooms for per-bit matching
|
||||||
|
sections uint // Number of sections to batch together
|
||||||
|
nextSec uint // Next section to set when adding a bloom
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGenerator creates a rotated bloom generator that can iteratively fill a
|
||||||
|
// batched bloom filter's bits.
|
||||||
|
func NewGenerator(sections uint) (*Generator, error) {
|
||||||
|
if sections%8 != 0 {
|
||||||
|
return nil, errors.New("section count not multiple of 8")
|
||||||
|
}
|
||||||
|
b := &Generator{sections: sections}
|
||||||
|
for i := 0; i < types.BloomBitLength; i++ {
|
||||||
|
b.blooms[i] = make([]byte, sections/8)
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBloom takes a single bloom filter and sets the corresponding bit column
|
||||||
|
// in memory accordingly.
|
||||||
|
func (b *Generator) AddBloom(index uint, bloom types.Bloom) error {
|
||||||
|
// Make sure we're not adding more bloom filters than our capacity
|
||||||
|
if b.nextSec >= b.sections {
|
||||||
|
return errSectionOutOfBounds
|
||||||
|
}
|
||||||
|
if b.nextSec != index {
|
||||||
|
return errors.New("bloom filter with unexpected index")
|
||||||
|
}
|
||||||
|
// Rotate the bloom and insert into our collection
|
||||||
|
byteIndex := b.nextSec / 8
|
||||||
|
bitIndex := byte(7 - b.nextSec%8)
|
||||||
|
for byt := 0; byt < types.BloomByteLength; byt++ {
|
||||||
|
bloomByte := bloom[types.BloomByteLength-1-byt]
|
||||||
|
if bloomByte == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
base := 8 * byt
|
||||||
|
b.blooms[base+7][byteIndex] |= ((bloomByte >> 7) & 1) << bitIndex
|
||||||
|
b.blooms[base+6][byteIndex] |= ((bloomByte >> 6) & 1) << bitIndex
|
||||||
|
b.blooms[base+5][byteIndex] |= ((bloomByte >> 5) & 1) << bitIndex
|
||||||
|
b.blooms[base+4][byteIndex] |= ((bloomByte >> 4) & 1) << bitIndex
|
||||||
|
b.blooms[base+3][byteIndex] |= ((bloomByte >> 3) & 1) << bitIndex
|
||||||
|
b.blooms[base+2][byteIndex] |= ((bloomByte >> 2) & 1) << bitIndex
|
||||||
|
b.blooms[base+1][byteIndex] |= ((bloomByte >> 1) & 1) << bitIndex
|
||||||
|
b.blooms[base][byteIndex] |= (bloomByte & 1) << bitIndex
|
||||||
|
}
|
||||||
|
b.nextSec++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitset returns the bit vector belonging to the given bit index after all
|
||||||
|
// blooms have been added.
|
||||||
|
func (b *Generator) Bitset(idx uint) ([]byte, error) {
|
||||||
|
if b.nextSec != b.sections {
|
||||||
|
return nil, errors.New("bloom not fully generated yet")
|
||||||
|
}
|
||||||
|
if idx >= types.BloomBitLength {
|
||||||
|
return nil, errBloomBitOutOfBounds
|
||||||
|
}
|
||||||
|
return b.blooms[idx], nil
|
||||||
|
}
|
||||||
100
core/bloombits/generator_test.go
Normal file
100
core/bloombits/generator_test.go
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bloombits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
crand "crypto/rand"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that batched bloom bits are correctly rotated from the input bloom
|
||||||
|
// filters.
|
||||||
|
func TestGenerator(t *testing.T) {
|
||||||
|
// Generate the input and the rotated output
|
||||||
|
var input, output [types.BloomBitLength][types.BloomByteLength]byte
|
||||||
|
|
||||||
|
for i := 0; i < types.BloomBitLength; i++ {
|
||||||
|
for j := 0; j < types.BloomBitLength; j++ {
|
||||||
|
bit := byte(rand.Int() % 2)
|
||||||
|
|
||||||
|
input[i][j/8] |= bit << byte(7-j%8)
|
||||||
|
output[types.BloomBitLength-1-j][i/8] |= bit << byte(7-i%8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Crunch the input through the generator and verify the result
|
||||||
|
gen, err := NewGenerator(types.BloomBitLength)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create bloombit generator: %v", err)
|
||||||
|
}
|
||||||
|
for i, bloom := range input {
|
||||||
|
if err := gen.AddBloom(uint(i), bloom); err != nil {
|
||||||
|
t.Fatalf("bloom %d: failed to add: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, want := range output {
|
||||||
|
have, err := gen.Bitset(uint(i))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("output %d: failed to retrieve bits: %v", i, err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(have, want[:]) {
|
||||||
|
t.Errorf("output %d: bit vector mismatch have %x, want %x", i, have, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGenerator(b *testing.B) {
|
||||||
|
var input [types.BloomBitLength][types.BloomByteLength]byte
|
||||||
|
b.Run("empty", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// Crunch the input through the generator and verify the result
|
||||||
|
gen, err := NewGenerator(types.BloomBitLength)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("failed to create bloombit generator: %v", err)
|
||||||
|
}
|
||||||
|
for j, bloom := range &input {
|
||||||
|
if err := gen.AddBloom(uint(j), bloom); err != nil {
|
||||||
|
b.Fatalf("bloom %d: failed to add: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for i := 0; i < types.BloomBitLength; i++ {
|
||||||
|
crand.Read(input[i][:])
|
||||||
|
}
|
||||||
|
b.Run("random", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// Crunch the input through the generator and verify the result
|
||||||
|
gen, err := NewGenerator(types.BloomBitLength)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("failed to create bloombit generator: %v", err)
|
||||||
|
}
|
||||||
|
for j, bloom := range &input {
|
||||||
|
if err := gen.AddBloom(uint(j), bloom); err != nil {
|
||||||
|
b.Fatalf("bloom %d: failed to add: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
649
core/bloombits/matcher.go
Normal file
649
core/bloombits/matcher.go
Normal file
|
|
@ -0,0 +1,649 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bloombits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/bitutil"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// bloomIndexes represents the bit indexes inside the bloom filter that belong
|
||||||
|
// to some key.
|
||||||
|
type bloomIndexes [3]uint
|
||||||
|
|
||||||
|
// calcBloomIndexes returns the bloom filter bit indexes belonging to the given key.
|
||||||
|
func calcBloomIndexes(b []byte) bloomIndexes {
|
||||||
|
b = crypto.Keccak256(b)
|
||||||
|
|
||||||
|
var idxs bloomIndexes
|
||||||
|
for i := 0; i < len(idxs); i++ {
|
||||||
|
idxs[i] = (uint(b[2*i])<<8)&2047 + uint(b[2*i+1])
|
||||||
|
}
|
||||||
|
return idxs
|
||||||
|
}
|
||||||
|
|
||||||
|
// partialMatches with a non-nil vector represents a section in which some sub-
|
||||||
|
// matchers have already found potential matches. Subsequent sub-matchers will
|
||||||
|
// binary AND their matches with this vector. If vector is nil, it represents a
|
||||||
|
// section to be processed by the first sub-matcher.
|
||||||
|
type partialMatches struct {
|
||||||
|
section uint64
|
||||||
|
bitset []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieval represents a request for retrieval task assignments for a given
|
||||||
|
// bit with the given number of fetch elements, or a response for such a request.
|
||||||
|
// It can also have the actual results set to be used as a delivery data struct.
|
||||||
|
//
|
||||||
|
// The context and error fields are used by the light client to terminate matching
|
||||||
|
// early if an error is encountered on some path of the pipeline.
|
||||||
|
type Retrieval struct {
|
||||||
|
Bit uint
|
||||||
|
Sections []uint64
|
||||||
|
Bitsets [][]byte
|
||||||
|
|
||||||
|
Context context.Context
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matcher is a pipelined system of schedulers and logic matchers which perform
|
||||||
|
// binary AND/OR operations on the bit-streams, creating a stream of potential
|
||||||
|
// blocks to inspect for data content.
|
||||||
|
type Matcher struct {
|
||||||
|
sectionSize uint64 // Size of the data batches to filter on
|
||||||
|
|
||||||
|
filters [][]bloomIndexes // Filter the system is matching for
|
||||||
|
schedulers map[uint]*scheduler // Retrieval schedulers for loading bloom bits
|
||||||
|
|
||||||
|
retrievers chan chan uint // Retriever processes waiting for bit allocations
|
||||||
|
counters chan chan uint // Retriever processes waiting for task count reports
|
||||||
|
retrievals chan chan *Retrieval // Retriever processes waiting for task allocations
|
||||||
|
deliveries chan *Retrieval // Retriever processes waiting for task response deliveries
|
||||||
|
|
||||||
|
running atomic.Bool // Atomic flag whether a session is live or not
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMatcher creates a new pipeline for retrieving bloom bit streams and doing
|
||||||
|
// address and topic filtering on them. Setting a filter component to `nil` is
|
||||||
|
// allowed and will result in that filter rule being skipped (OR 0x11...1).
|
||||||
|
func NewMatcher(sectionSize uint64, filters [][][]byte) *Matcher {
|
||||||
|
// Create the matcher instance
|
||||||
|
m := &Matcher{
|
||||||
|
sectionSize: sectionSize,
|
||||||
|
schedulers: make(map[uint]*scheduler),
|
||||||
|
retrievers: make(chan chan uint),
|
||||||
|
counters: make(chan chan uint),
|
||||||
|
retrievals: make(chan chan *Retrieval),
|
||||||
|
deliveries: make(chan *Retrieval),
|
||||||
|
}
|
||||||
|
// Calculate the bloom bit indexes for the groups we're interested in
|
||||||
|
m.filters = nil
|
||||||
|
|
||||||
|
for _, filter := range filters {
|
||||||
|
// Gather the bit indexes of the filter rule, special casing the nil filter
|
||||||
|
if len(filter) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bloomBits := make([]bloomIndexes, len(filter))
|
||||||
|
for i, clause := range filter {
|
||||||
|
if clause == nil {
|
||||||
|
bloomBits = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
bloomBits[i] = calcBloomIndexes(clause)
|
||||||
|
}
|
||||||
|
// Accumulate the filter rules if no nil rule was within
|
||||||
|
if bloomBits != nil {
|
||||||
|
m.filters = append(m.filters, bloomBits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// For every bit, create a scheduler to load/download the bit vectors
|
||||||
|
for _, bloomIndexLists := range m.filters {
|
||||||
|
for _, bloomIndexList := range bloomIndexLists {
|
||||||
|
for _, bloomIndex := range bloomIndexList {
|
||||||
|
m.addScheduler(bloomIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// addScheduler adds a bit stream retrieval scheduler for the given bit index if
|
||||||
|
// it has not existed before. If the bit is already selected for filtering, the
|
||||||
|
// existing scheduler can be used.
|
||||||
|
func (m *Matcher) addScheduler(idx uint) {
|
||||||
|
if _, ok := m.schedulers[idx]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.schedulers[idx] = newScheduler(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the matching process and returns a stream of bloom matches in
|
||||||
|
// a given range of blocks. If there are no more matches in the range, the result
|
||||||
|
// channel is closed.
|
||||||
|
func (m *Matcher) Start(ctx context.Context, begin, end uint64, results chan uint64) (*MatcherSession, error) {
|
||||||
|
// Make sure we're not creating concurrent sessions
|
||||||
|
if m.running.Swap(true) {
|
||||||
|
return nil, errors.New("matcher already running")
|
||||||
|
}
|
||||||
|
defer m.running.Store(false)
|
||||||
|
|
||||||
|
// Initiate a new matching round
|
||||||
|
session := &MatcherSession{
|
||||||
|
matcher: m,
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
for _, scheduler := range m.schedulers {
|
||||||
|
scheduler.reset()
|
||||||
|
}
|
||||||
|
sink := m.run(begin, end, cap(results), session)
|
||||||
|
|
||||||
|
// Read the output from the result sink and deliver to the user
|
||||||
|
session.pend.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer session.pend.Done()
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-session.quit:
|
||||||
|
return
|
||||||
|
|
||||||
|
case res, ok := <-sink:
|
||||||
|
// New match result found
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Calculate the first and last blocks of the section
|
||||||
|
sectionStart := res.section * m.sectionSize
|
||||||
|
|
||||||
|
first := sectionStart
|
||||||
|
if begin > first {
|
||||||
|
first = begin
|
||||||
|
}
|
||||||
|
last := sectionStart + m.sectionSize - 1
|
||||||
|
if end < last {
|
||||||
|
last = end
|
||||||
|
}
|
||||||
|
// Iterate over all the blocks in the section and return the matching ones
|
||||||
|
for i := first; i <= last; i++ {
|
||||||
|
// Skip the entire byte if no matches are found inside (and we're processing an entire byte!)
|
||||||
|
next := res.bitset[(i-sectionStart)/8]
|
||||||
|
if next == 0 {
|
||||||
|
if i%8 == 0 {
|
||||||
|
i += 7
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Some bit it set, do the actual submatching
|
||||||
|
if bit := 7 - i%8; next&(1<<bit) != 0 {
|
||||||
|
select {
|
||||||
|
case <-session.quit:
|
||||||
|
return
|
||||||
|
case results <- i:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// run creates a daisy-chain of sub-matchers, one for the address set and one
|
||||||
|
// for each topic set, each sub-matcher receiving a section only if the previous
|
||||||
|
// ones have all found a potential match in one of the blocks of the section,
|
||||||
|
// then binary AND-ing its own matches and forwarding the result to the next one.
|
||||||
|
//
|
||||||
|
// The method starts feeding the section indexes into the first sub-matcher on a
|
||||||
|
// new goroutine and returns a sink channel receiving the results.
|
||||||
|
func (m *Matcher) run(begin, end uint64, buffer int, session *MatcherSession) chan *partialMatches {
|
||||||
|
// Create the source channel and feed section indexes into
|
||||||
|
source := make(chan *partialMatches, buffer)
|
||||||
|
|
||||||
|
session.pend.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer session.pend.Done()
|
||||||
|
defer close(source)
|
||||||
|
|
||||||
|
for i := begin / m.sectionSize; i <= end/m.sectionSize; i++ {
|
||||||
|
select {
|
||||||
|
case <-session.quit:
|
||||||
|
return
|
||||||
|
case source <- &partialMatches{i, bytes.Repeat([]byte{0xff}, int(m.sectionSize/8))}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Assemble the daisy-chained filtering pipeline
|
||||||
|
next := source
|
||||||
|
dist := make(chan *request, buffer)
|
||||||
|
|
||||||
|
for _, bloom := range m.filters {
|
||||||
|
next = m.subMatch(next, dist, bloom, session)
|
||||||
|
}
|
||||||
|
// Start the request distribution
|
||||||
|
session.pend.Add(1)
|
||||||
|
go m.distributor(dist, session)
|
||||||
|
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
// subMatch creates a sub-matcher that filters for a set of addresses or topics, binary OR-s those matches, then
|
||||||
|
// binary AND-s the result to the daisy-chain input (source) and forwards it to the daisy-chain output.
|
||||||
|
// The matches of each address/topic are calculated by fetching the given sections of the three bloom bit indexes belonging to
|
||||||
|
// that address/topic, and binary AND-ing those vectors together.
|
||||||
|
func (m *Matcher) subMatch(source chan *partialMatches, dist chan *request, bloom []bloomIndexes, session *MatcherSession) chan *partialMatches {
|
||||||
|
// Start the concurrent schedulers for each bit required by the bloom filter
|
||||||
|
sectionSources := make([][3]chan uint64, len(bloom))
|
||||||
|
sectionSinks := make([][3]chan []byte, len(bloom))
|
||||||
|
for i, bits := range bloom {
|
||||||
|
for j, bit := range bits {
|
||||||
|
sectionSources[i][j] = make(chan uint64, cap(source))
|
||||||
|
sectionSinks[i][j] = make(chan []byte, cap(source))
|
||||||
|
|
||||||
|
m.schedulers[bit].run(sectionSources[i][j], dist, sectionSinks[i][j], session.quit, &session.pend)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process := make(chan *partialMatches, cap(source)) // entries from source are forwarded here after fetches have been initiated
|
||||||
|
results := make(chan *partialMatches, cap(source))
|
||||||
|
|
||||||
|
session.pend.Add(2)
|
||||||
|
go func() {
|
||||||
|
// Tear down the goroutine and terminate all source channels
|
||||||
|
defer session.pend.Done()
|
||||||
|
defer close(process)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for _, bloomSources := range sectionSources {
|
||||||
|
for _, bitSource := range bloomSources {
|
||||||
|
close(bitSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Read sections from the source channel and multiplex into all bit-schedulers
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-session.quit:
|
||||||
|
return
|
||||||
|
|
||||||
|
case subres, ok := <-source:
|
||||||
|
// New subresult from previous link
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Multiplex the section index to all bit-schedulers
|
||||||
|
for _, bloomSources := range sectionSources {
|
||||||
|
for _, bitSource := range bloomSources {
|
||||||
|
select {
|
||||||
|
case <-session.quit:
|
||||||
|
return
|
||||||
|
case bitSource <- subres.section:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Notify the processor that this section will become available
|
||||||
|
select {
|
||||||
|
case <-session.quit:
|
||||||
|
return
|
||||||
|
case process <- subres:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// Tear down the goroutine and terminate the final sink channel
|
||||||
|
defer session.pend.Done()
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
|
// Read the source notifications and collect the delivered results
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-session.quit:
|
||||||
|
return
|
||||||
|
|
||||||
|
case subres, ok := <-process:
|
||||||
|
// Notified of a section being retrieved
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Gather all the sub-results and merge them together
|
||||||
|
var orVector []byte
|
||||||
|
for _, bloomSinks := range sectionSinks {
|
||||||
|
var andVector []byte
|
||||||
|
for _, bitSink := range bloomSinks {
|
||||||
|
var data []byte
|
||||||
|
select {
|
||||||
|
case <-session.quit:
|
||||||
|
return
|
||||||
|
case data = <-bitSink:
|
||||||
|
}
|
||||||
|
if andVector == nil {
|
||||||
|
andVector = make([]byte, int(m.sectionSize/8))
|
||||||
|
copy(andVector, data)
|
||||||
|
} else {
|
||||||
|
bitutil.ANDBytes(andVector, andVector, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if orVector == nil {
|
||||||
|
orVector = andVector
|
||||||
|
} else {
|
||||||
|
bitutil.ORBytes(orVector, orVector, andVector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if orVector == nil {
|
||||||
|
orVector = make([]byte, int(m.sectionSize/8))
|
||||||
|
}
|
||||||
|
if subres.bitset != nil {
|
||||||
|
bitutil.ANDBytes(orVector, orVector, subres.bitset)
|
||||||
|
}
|
||||||
|
if bitutil.TestBytes(orVector) {
|
||||||
|
select {
|
||||||
|
case <-session.quit:
|
||||||
|
return
|
||||||
|
case results <- &partialMatches{subres.section, orVector}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// distributor receives requests from the schedulers and queues them into a set
|
||||||
|
// of pending requests, which are assigned to retrievers wanting to fulfil them.
|
||||||
|
func (m *Matcher) distributor(dist chan *request, session *MatcherSession) {
|
||||||
|
defer session.pend.Done()
|
||||||
|
|
||||||
|
var (
|
||||||
|
requests = make(map[uint][]uint64) // Per-bit list of section requests, ordered by section number
|
||||||
|
unallocs = make(map[uint]struct{}) // Bits with pending requests but not allocated to any retriever
|
||||||
|
retrievers chan chan uint // Waiting retrievers (toggled to nil if unallocs is empty)
|
||||||
|
allocs int // Number of active allocations to handle graceful shutdown requests
|
||||||
|
shutdown = session.quit // Shutdown request channel, will gracefully wait for pending requests
|
||||||
|
)
|
||||||
|
|
||||||
|
// assign is a helper method to try to assign a pending bit an actively
|
||||||
|
// listening servicer, or schedule it up for later when one arrives.
|
||||||
|
assign := func(bit uint) {
|
||||||
|
select {
|
||||||
|
case fetcher := <-m.retrievers:
|
||||||
|
allocs++
|
||||||
|
fetcher <- bit
|
||||||
|
default:
|
||||||
|
// No retrievers active, start listening for new ones
|
||||||
|
retrievers = m.retrievers
|
||||||
|
unallocs[bit] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-shutdown:
|
||||||
|
// Shutdown requested. No more retrievers can be allocated,
|
||||||
|
// but we still need to wait until all pending requests have returned.
|
||||||
|
shutdown = nil
|
||||||
|
if allocs == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case req := <-dist:
|
||||||
|
// New retrieval request arrived to be distributed to some fetcher process
|
||||||
|
queue := requests[req.bit]
|
||||||
|
index := sort.Search(len(queue), func(i int) bool { return queue[i] >= req.section })
|
||||||
|
requests[req.bit] = append(queue[:index], append([]uint64{req.section}, queue[index:]...)...)
|
||||||
|
|
||||||
|
// If it's a new bit and we have waiting fetchers, allocate to them
|
||||||
|
if len(queue) == 0 {
|
||||||
|
assign(req.bit)
|
||||||
|
}
|
||||||
|
|
||||||
|
case fetcher := <-retrievers:
|
||||||
|
// New retriever arrived, find the lowest section-ed bit to assign
|
||||||
|
bit, best := uint(0), uint64(math.MaxUint64)
|
||||||
|
for idx := range unallocs {
|
||||||
|
if requests[idx][0] < best {
|
||||||
|
bit, best = idx, requests[idx][0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Stop tracking this bit (and alloc notifications if no more work is available)
|
||||||
|
delete(unallocs, bit)
|
||||||
|
if len(unallocs) == 0 {
|
||||||
|
retrievers = nil
|
||||||
|
}
|
||||||
|
allocs++
|
||||||
|
fetcher <- bit
|
||||||
|
|
||||||
|
case fetcher := <-m.counters:
|
||||||
|
// New task count request arrives, return number of items
|
||||||
|
fetcher <- uint(len(requests[<-fetcher]))
|
||||||
|
|
||||||
|
case fetcher := <-m.retrievals:
|
||||||
|
// New fetcher waiting for tasks to retrieve, assign
|
||||||
|
task := <-fetcher
|
||||||
|
if want := len(task.Sections); want >= len(requests[task.Bit]) {
|
||||||
|
task.Sections = requests[task.Bit]
|
||||||
|
delete(requests, task.Bit)
|
||||||
|
} else {
|
||||||
|
task.Sections = append(task.Sections[:0], requests[task.Bit][:want]...)
|
||||||
|
requests[task.Bit] = append(requests[task.Bit][:0], requests[task.Bit][want:]...)
|
||||||
|
}
|
||||||
|
fetcher <- task
|
||||||
|
|
||||||
|
// If anything was left unallocated, try to assign to someone else
|
||||||
|
if len(requests[task.Bit]) > 0 {
|
||||||
|
assign(task.Bit)
|
||||||
|
}
|
||||||
|
|
||||||
|
case result := <-m.deliveries:
|
||||||
|
// New retrieval task response from fetcher, split out missing sections and
|
||||||
|
// deliver complete ones
|
||||||
|
var (
|
||||||
|
sections = make([]uint64, 0, len(result.Sections))
|
||||||
|
bitsets = make([][]byte, 0, len(result.Bitsets))
|
||||||
|
missing = make([]uint64, 0, len(result.Sections))
|
||||||
|
)
|
||||||
|
for i, bitset := range result.Bitsets {
|
||||||
|
if len(bitset) == 0 {
|
||||||
|
missing = append(missing, result.Sections[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sections = append(sections, result.Sections[i])
|
||||||
|
bitsets = append(bitsets, bitset)
|
||||||
|
}
|
||||||
|
m.schedulers[result.Bit].deliver(sections, bitsets)
|
||||||
|
allocs--
|
||||||
|
|
||||||
|
// Reschedule missing sections and allocate bit if newly available
|
||||||
|
if len(missing) > 0 {
|
||||||
|
queue := requests[result.Bit]
|
||||||
|
for _, section := range missing {
|
||||||
|
index := sort.Search(len(queue), func(i int) bool { return queue[i] >= section })
|
||||||
|
queue = append(queue[:index], append([]uint64{section}, queue[index:]...)...)
|
||||||
|
}
|
||||||
|
requests[result.Bit] = queue
|
||||||
|
|
||||||
|
if len(queue) == len(missing) {
|
||||||
|
assign(result.Bit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// End the session when all pending deliveries have arrived.
|
||||||
|
if shutdown == nil && allocs == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherSession is returned by a started matcher to be used as a terminator
|
||||||
|
// for the actively running matching operation.
|
||||||
|
type MatcherSession struct {
|
||||||
|
matcher *Matcher
|
||||||
|
|
||||||
|
closer sync.Once // Sync object to ensure we only ever close once
|
||||||
|
quit chan struct{} // Quit channel to request pipeline termination
|
||||||
|
|
||||||
|
ctx context.Context // Context used by the light client to abort filtering
|
||||||
|
err error // Global error to track retrieval failures deep in the chain
|
||||||
|
errLock sync.Mutex
|
||||||
|
|
||||||
|
pend sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close stops the matching process and waits for all subprocesses to terminate
|
||||||
|
// before returning. The timeout may be used for graceful shutdown, allowing the
|
||||||
|
// currently running retrievals to complete before this time.
|
||||||
|
func (s *MatcherSession) Close() {
|
||||||
|
s.closer.Do(func() {
|
||||||
|
// Signal termination and wait for all goroutines to tear down
|
||||||
|
close(s.quit)
|
||||||
|
s.pend.Wait()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns any failure encountered during the matching session.
|
||||||
|
func (s *MatcherSession) Error() error {
|
||||||
|
s.errLock.Lock()
|
||||||
|
defer s.errLock.Unlock()
|
||||||
|
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocateRetrieval assigns a bloom bit index to a client process that can either
|
||||||
|
// immediately request and fetch the section contents assigned to this bit or wait
|
||||||
|
// a little while for more sections to be requested.
|
||||||
|
func (s *MatcherSession) allocateRetrieval() (uint, bool) {
|
||||||
|
fetcher := make(chan uint)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.quit:
|
||||||
|
return 0, false
|
||||||
|
case s.matcher.retrievers <- fetcher:
|
||||||
|
bit, ok := <-fetcher
|
||||||
|
return bit, ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pendingSections returns the number of pending section retrievals belonging to
|
||||||
|
// the given bloom bit index.
|
||||||
|
func (s *MatcherSession) pendingSections(bit uint) int {
|
||||||
|
fetcher := make(chan uint)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.quit:
|
||||||
|
return 0
|
||||||
|
case s.matcher.counters <- fetcher:
|
||||||
|
fetcher <- bit
|
||||||
|
return int(<-fetcher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocateSections assigns all or part of an already allocated bit-task queue
|
||||||
|
// to the requesting process.
|
||||||
|
func (s *MatcherSession) allocateSections(bit uint, count int) []uint64 {
|
||||||
|
fetcher := make(chan *Retrieval)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.quit:
|
||||||
|
return nil
|
||||||
|
case s.matcher.retrievals <- fetcher:
|
||||||
|
task := &Retrieval{
|
||||||
|
Bit: bit,
|
||||||
|
Sections: make([]uint64, count),
|
||||||
|
}
|
||||||
|
fetcher <- task
|
||||||
|
return (<-fetcher).Sections
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deliverSections delivers a batch of section bit-vectors for a specific bloom
|
||||||
|
// bit index to be injected into the processing pipeline.
|
||||||
|
func (s *MatcherSession) deliverSections(bit uint, sections []uint64, bitsets [][]byte) {
|
||||||
|
s.matcher.deliveries <- &Retrieval{Bit: bit, Sections: sections, Bitsets: bitsets}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiplex polls the matcher session for retrieval tasks and multiplexes it into
|
||||||
|
// the requested retrieval queue to be serviced together with other sessions.
|
||||||
|
//
|
||||||
|
// This method will block for the lifetime of the session. Even after termination
|
||||||
|
// of the session, any request in-flight need to be responded to! Empty responses
|
||||||
|
// are fine though in that case.
|
||||||
|
func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan *Retrieval) {
|
||||||
|
waitTimer := time.NewTimer(wait)
|
||||||
|
defer waitTimer.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Allocate a new bloom bit index to retrieve data for, stopping when done
|
||||||
|
bit, ok := s.allocateRetrieval()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Bit allocated, throttle a bit if we're below our batch limit
|
||||||
|
if s.pendingSections(bit) < batch {
|
||||||
|
waitTimer.Reset(wait)
|
||||||
|
select {
|
||||||
|
case <-s.quit:
|
||||||
|
// Session terminating, we can't meaningfully service, abort
|
||||||
|
s.allocateSections(bit, 0)
|
||||||
|
s.deliverSections(bit, []uint64{}, [][]byte{})
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-waitTimer.C:
|
||||||
|
// Throttling up, fetch whatever is available
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Allocate as much as we can handle and request servicing
|
||||||
|
sections := s.allocateSections(bit, batch)
|
||||||
|
request := make(chan *Retrieval)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.quit:
|
||||||
|
// Session terminating, we can't meaningfully service, abort
|
||||||
|
s.deliverSections(bit, sections, make([][]byte, len(sections)))
|
||||||
|
return
|
||||||
|
|
||||||
|
case mux <- request:
|
||||||
|
// Retrieval accepted, something must arrive before we're aborting
|
||||||
|
request <- &Retrieval{Bit: bit, Sections: sections, Context: s.ctx}
|
||||||
|
|
||||||
|
result := <-request
|
||||||
|
|
||||||
|
// Deliver a result before s.Close() to avoid a deadlock
|
||||||
|
s.deliverSections(result.Bit, result.Sections, result.Bitsets)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
s.errLock.Lock()
|
||||||
|
s.err = result.Error
|
||||||
|
s.errLock.Unlock()
|
||||||
|
s.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
292
core/bloombits/matcher_test.go
Normal file
292
core/bloombits/matcher_test.go
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bloombits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testSectionSize = 4096
|
||||||
|
|
||||||
|
// Tests that wildcard filter rules (nil) can be specified and are handled well.
|
||||||
|
func TestMatcherWildcards(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
matcher := NewMatcher(testSectionSize, [][][]byte{
|
||||||
|
{common.Address{}.Bytes(), common.Address{0x01}.Bytes()}, // Default address is not a wildcard
|
||||||
|
{common.Hash{}.Bytes(), common.Hash{0x01}.Bytes()}, // Default hash is not a wildcard
|
||||||
|
{common.Hash{0x01}.Bytes()}, // Plain rule, sanity check
|
||||||
|
{common.Hash{0x01}.Bytes(), nil}, // Wildcard suffix, drop rule
|
||||||
|
{nil, common.Hash{0x01}.Bytes()}, // Wildcard prefix, drop rule
|
||||||
|
{nil, nil}, // Wildcard combo, drop rule
|
||||||
|
{}, // Inited wildcard rule, drop rule
|
||||||
|
nil, // Proper wildcard rule, drop rule
|
||||||
|
})
|
||||||
|
if len(matcher.filters) != 3 {
|
||||||
|
t.Fatalf("filter system size mismatch: have %d, want %d", len(matcher.filters), 3)
|
||||||
|
}
|
||||||
|
if len(matcher.filters[0]) != 2 {
|
||||||
|
t.Fatalf("address clause size mismatch: have %d, want %d", len(matcher.filters[0]), 2)
|
||||||
|
}
|
||||||
|
if len(matcher.filters[1]) != 2 {
|
||||||
|
t.Fatalf("combo topic clause size mismatch: have %d, want %d", len(matcher.filters[1]), 2)
|
||||||
|
}
|
||||||
|
if len(matcher.filters[2]) != 1 {
|
||||||
|
t.Fatalf("singletone topic clause size mismatch: have %d, want %d", len(matcher.filters[2]), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the matcher pipeline on a single continuous workflow without interrupts.
|
||||||
|
func TestMatcherContinuous(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, false, 75)
|
||||||
|
testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, false, 81)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the matcher pipeline on a constantly interrupted and resumed work pattern
|
||||||
|
// with the aim of ensuring data items are requested only once.
|
||||||
|
func TestMatcherIntermittent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, true, 75)
|
||||||
|
testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, true, 81)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the matcher pipeline on random input to hopefully catch anomalies.
|
||||||
|
func TestMatcherRandom(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
testMatcherBothModes(t, makeRandomIndexes([]int{1}, 50), 0, 10000, 0)
|
||||||
|
testMatcherBothModes(t, makeRandomIndexes([]int{3}, 50), 0, 10000, 0)
|
||||||
|
testMatcherBothModes(t, makeRandomIndexes([]int{2, 2, 2}, 20), 0, 10000, 0)
|
||||||
|
testMatcherBothModes(t, makeRandomIndexes([]int{5, 5, 5}, 50), 0, 10000, 0)
|
||||||
|
testMatcherBothModes(t, makeRandomIndexes([]int{4, 4, 4}, 20), 0, 10000, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that the matcher can properly find matches if the starting block is
|
||||||
|
// shifted from a multiple of 8. This is needed to cover an optimisation with
|
||||||
|
// bitset matching https://github.com/ethereum/go-ethereum/issues/15309.
|
||||||
|
func TestMatcherShifted(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
// Block 0 always matches in the tests, skip ahead of first 8 blocks with the
|
||||||
|
// start to get a potential zero byte in the matcher bitset.
|
||||||
|
|
||||||
|
// To keep the second bitset byte zero, the filter must only match for the first
|
||||||
|
// time in block 16, so doing an all-16 bit filter should suffice.
|
||||||
|
|
||||||
|
// To keep the starting block non divisible by 8, block number 9 is the first
|
||||||
|
// that would introduce a shift and not match block 0.
|
||||||
|
testMatcherBothModes(t, [][]bloomIndexes{{{16, 16, 16}}}, 9, 64, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that matching on everything doesn't crash (special case internally).
|
||||||
|
func TestWildcardMatcher(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testMatcherBothModes(t, nil, 0, 10000, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeRandomIndexes generates a random filter system, composed of multiple filter
|
||||||
|
// criteria, each having one bloom list component for the address and arbitrarily
|
||||||
|
// many topic bloom list components.
|
||||||
|
func makeRandomIndexes(lengths []int, max int) [][]bloomIndexes {
|
||||||
|
res := make([][]bloomIndexes, len(lengths))
|
||||||
|
for i, topics := range lengths {
|
||||||
|
res[i] = make([]bloomIndexes, topics)
|
||||||
|
for j := 0; j < topics; j++ {
|
||||||
|
for k := 0; k < len(res[i][j]); k++ {
|
||||||
|
res[i][j][k] = uint(rand.Intn(max-1) + 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// testMatcherDiffBatches runs the given matches test in single-delivery and also
|
||||||
|
// in batches delivery mode, verifying that all kinds of deliveries are handled
|
||||||
|
// correctly within.
|
||||||
|
func testMatcherDiffBatches(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, intermittent bool, retrievals uint32) {
|
||||||
|
singleton := testMatcher(t, filter, start, blocks, intermittent, retrievals, 1)
|
||||||
|
batched := testMatcher(t, filter, start, blocks, intermittent, retrievals, 16)
|
||||||
|
|
||||||
|
if singleton != batched {
|
||||||
|
t.Errorf("filter = %v blocks = %v intermittent = %v: request count mismatch, %v in singleton vs. %v in batched mode", filter, blocks, intermittent, singleton, batched)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testMatcherBothModes runs the given matcher test in both continuous as well as
|
||||||
|
// in intermittent mode, verifying that the request counts match each other.
|
||||||
|
func testMatcherBothModes(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, retrievals uint32) {
|
||||||
|
continuous := testMatcher(t, filter, start, blocks, false, retrievals, 16)
|
||||||
|
intermittent := testMatcher(t, filter, start, blocks, true, retrievals, 16)
|
||||||
|
|
||||||
|
if continuous != intermittent {
|
||||||
|
t.Errorf("filter = %v blocks = %v: request count mismatch, %v in continuous vs. %v in intermittent mode", filter, blocks, continuous, intermittent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testMatcher is a generic tester to run the given matcher test and return the
|
||||||
|
// number of requests made for cross validation between different modes.
|
||||||
|
func testMatcher(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, intermittent bool, retrievals uint32, maxReqCount int) uint32 {
|
||||||
|
// Create a new matcher an simulate our explicit random bitsets
|
||||||
|
matcher := NewMatcher(testSectionSize, nil)
|
||||||
|
matcher.filters = filter
|
||||||
|
|
||||||
|
for _, rule := range filter {
|
||||||
|
for _, topic := range rule {
|
||||||
|
for _, bit := range topic {
|
||||||
|
matcher.addScheduler(bit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Track the number of retrieval requests made
|
||||||
|
var requested atomic.Uint32
|
||||||
|
|
||||||
|
// Start the matching session for the filter and the retriever goroutines
|
||||||
|
quit := make(chan struct{})
|
||||||
|
matches := make(chan uint64, 16)
|
||||||
|
|
||||||
|
session, err := matcher.Start(context.Background(), start, blocks-1, matches)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to stat matcher session: %v", err)
|
||||||
|
}
|
||||||
|
startRetrievers(session, quit, &requested, maxReqCount)
|
||||||
|
|
||||||
|
// Iterate over all the blocks and verify that the pipeline produces the correct matches
|
||||||
|
for i := start; i < blocks; i++ {
|
||||||
|
if expMatch3(filter, i) {
|
||||||
|
match, ok := <-matches
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("filter = %v blocks = %v intermittent = %v: expected #%v, results channel closed", filter, blocks, intermittent, i)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if match != i {
|
||||||
|
t.Errorf("filter = %v blocks = %v intermittent = %v: expected #%v, got #%v", filter, blocks, intermittent, i, match)
|
||||||
|
}
|
||||||
|
// If we're testing intermittent mode, abort and restart the pipeline
|
||||||
|
if intermittent {
|
||||||
|
session.Close()
|
||||||
|
close(quit)
|
||||||
|
|
||||||
|
quit = make(chan struct{})
|
||||||
|
matches = make(chan uint64, 16)
|
||||||
|
|
||||||
|
session, err = matcher.Start(context.Background(), i+1, blocks-1, matches)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to stat matcher session: %v", err)
|
||||||
|
}
|
||||||
|
startRetrievers(session, quit, &requested, maxReqCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ensure the result channel is torn down after the last block
|
||||||
|
match, ok := <-matches
|
||||||
|
if ok {
|
||||||
|
t.Errorf("filter = %v blocks = %v intermittent = %v: expected closed channel, got #%v", filter, blocks, intermittent, match)
|
||||||
|
}
|
||||||
|
// Clean up the session and ensure we match the expected retrieval count
|
||||||
|
session.Close()
|
||||||
|
close(quit)
|
||||||
|
|
||||||
|
if retrievals != 0 && requested.Load() != retrievals {
|
||||||
|
t.Errorf("filter = %v blocks = %v intermittent = %v: request count mismatch, have #%v, want #%v", filter, blocks, intermittent, requested.Load(), retrievals)
|
||||||
|
}
|
||||||
|
return requested.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// startRetrievers starts a batch of goroutines listening for section requests
|
||||||
|
// and serving them.
|
||||||
|
func startRetrievers(session *MatcherSession, quit chan struct{}, retrievals *atomic.Uint32, batch int) {
|
||||||
|
requests := make(chan chan *Retrieval)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
// Start a multiplexer to test multiple threaded execution
|
||||||
|
go session.Multiplex(batch, 100*time.Microsecond, requests)
|
||||||
|
|
||||||
|
// Start a services to match the above multiplexer
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
// Wait for a service request or a shutdown
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
|
||||||
|
case request := <-requests:
|
||||||
|
task := <-request
|
||||||
|
|
||||||
|
task.Bitsets = make([][]byte, len(task.Sections))
|
||||||
|
for i, section := range task.Sections {
|
||||||
|
if rand.Int()%4 != 0 { // Handle occasional missing deliveries
|
||||||
|
task.Bitsets[i] = generateBitset(task.Bit, section)
|
||||||
|
retrievals.Add(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request <- task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateBitset generates the rotated bitset for the given bloom bit and section
|
||||||
|
// numbers.
|
||||||
|
func generateBitset(bit uint, section uint64) []byte {
|
||||||
|
bitset := make([]byte, testSectionSize/8)
|
||||||
|
for i := 0; i < len(bitset); i++ {
|
||||||
|
for b := 0; b < 8; b++ {
|
||||||
|
blockIdx := section*testSectionSize + uint64(i*8+b)
|
||||||
|
bitset[i] += bitset[i]
|
||||||
|
if (blockIdx % uint64(bit)) == 0 {
|
||||||
|
bitset[i]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bitset
|
||||||
|
}
|
||||||
|
|
||||||
|
func expMatch1(filter bloomIndexes, i uint64) bool {
|
||||||
|
for _, ii := range filter {
|
||||||
|
if (i % uint64(ii)) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func expMatch2(filter []bloomIndexes, i uint64) bool {
|
||||||
|
for _, ii := range filter {
|
||||||
|
if expMatch1(ii, i) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func expMatch3(filter [][]bloomIndexes, i uint64) bool {
|
||||||
|
for _, ii := range filter {
|
||||||
|
if !expMatch2(ii, i) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
181
core/bloombits/scheduler.go
Normal file
181
core/bloombits/scheduler.go
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bloombits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// request represents a bloom retrieval task to prioritize and pull from the local
|
||||||
|
// database or remotely from the network.
|
||||||
|
type request struct {
|
||||||
|
section uint64 // Section index to retrieve the bit-vector from
|
||||||
|
bit uint // Bit index within the section to retrieve the vector of
|
||||||
|
}
|
||||||
|
|
||||||
|
// response represents the state of a requested bit-vector through a scheduler.
|
||||||
|
type response struct {
|
||||||
|
cached []byte // Cached bits to dedup multiple requests
|
||||||
|
done chan struct{} // Channel to allow waiting for completion
|
||||||
|
}
|
||||||
|
|
||||||
|
// scheduler handles the scheduling of bloom-filter retrieval operations for
|
||||||
|
// entire section-batches belonging to a single bloom bit. Beside scheduling the
|
||||||
|
// retrieval operations, this struct also deduplicates the requests and caches
|
||||||
|
// the results to minimize network/database overhead even in complex filtering
|
||||||
|
// scenarios.
|
||||||
|
type scheduler struct {
|
||||||
|
bit uint // Index of the bit in the bloom filter this scheduler is responsible for
|
||||||
|
responses map[uint64]*response // Currently pending retrieval requests or already cached responses
|
||||||
|
lock sync.Mutex // Lock protecting the responses from concurrent access
|
||||||
|
}
|
||||||
|
|
||||||
|
// newScheduler creates a new bloom-filter retrieval scheduler for a specific
|
||||||
|
// bit index.
|
||||||
|
func newScheduler(idx uint) *scheduler {
|
||||||
|
return &scheduler{
|
||||||
|
bit: idx,
|
||||||
|
responses: make(map[uint64]*response),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run creates a retrieval pipeline, receiving section indexes from sections and
|
||||||
|
// returning the results in the same order through the done channel. Concurrent
|
||||||
|
// runs of the same scheduler are allowed, leading to retrieval task deduplication.
|
||||||
|
func (s *scheduler) run(sections chan uint64, dist chan *request, done chan []byte, quit chan struct{}, wg *sync.WaitGroup) {
|
||||||
|
// Create a forwarder channel between requests and responses of the same size as
|
||||||
|
// the distribution channel (since that will block the pipeline anyway).
|
||||||
|
pend := make(chan uint64, cap(dist))
|
||||||
|
|
||||||
|
// Start the pipeline schedulers to forward between user -> distributor -> user
|
||||||
|
wg.Add(2)
|
||||||
|
go s.scheduleRequests(sections, dist, pend, quit, wg)
|
||||||
|
go s.scheduleDeliveries(pend, done, quit, wg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset cleans up any leftovers from previous runs. This is required before a
|
||||||
|
// restart to ensure the no previously requested but never delivered state will
|
||||||
|
// cause a lockup.
|
||||||
|
func (s *scheduler) reset() {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
for section, res := range s.responses {
|
||||||
|
if res.cached == nil {
|
||||||
|
delete(s.responses, section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scheduleRequests reads section retrieval requests from the input channel,
|
||||||
|
// deduplicates the stream and pushes unique retrieval tasks into the distribution
|
||||||
|
// channel for a database or network layer to honour.
|
||||||
|
func (s *scheduler) scheduleRequests(reqs chan uint64, dist chan *request, pend chan uint64, quit chan struct{}, wg *sync.WaitGroup) {
|
||||||
|
// Clean up the goroutine and pipeline when done
|
||||||
|
defer wg.Done()
|
||||||
|
defer close(pend)
|
||||||
|
|
||||||
|
// Keep reading and scheduling section requests
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
|
||||||
|
case section, ok := <-reqs:
|
||||||
|
// New section retrieval requested
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Deduplicate retrieval requests
|
||||||
|
unique := false
|
||||||
|
|
||||||
|
s.lock.Lock()
|
||||||
|
if s.responses[section] == nil {
|
||||||
|
s.responses[section] = &response{
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
unique = true
|
||||||
|
}
|
||||||
|
s.lock.Unlock()
|
||||||
|
|
||||||
|
// Schedule the section for retrieval and notify the deliverer to expect this section
|
||||||
|
if unique {
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
case dist <- &request{bit: s.bit, section: section}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
case pend <- section:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scheduleDeliveries reads section acceptance notifications and waits for them
|
||||||
|
// to be delivered, pushing them into the output data buffer.
|
||||||
|
func (s *scheduler) scheduleDeliveries(pend chan uint64, done chan []byte, quit chan struct{}, wg *sync.WaitGroup) {
|
||||||
|
// Clean up the goroutine and pipeline when done
|
||||||
|
defer wg.Done()
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
// Keep reading notifications and scheduling deliveries
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
|
||||||
|
case idx, ok := <-pend:
|
||||||
|
// New section retrieval pending
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Wait until the request is honoured
|
||||||
|
s.lock.Lock()
|
||||||
|
res := s.responses[idx]
|
||||||
|
s.lock.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
case <-res.done:
|
||||||
|
}
|
||||||
|
// Deliver the result
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
case done <- res.cached:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deliver is called by the request distributor when a reply to a request arrives.
|
||||||
|
func (s *scheduler) deliver(sections []uint64, data [][]byte) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
for i, section := range sections {
|
||||||
|
if res := s.responses[section]; res != nil && res.cached == nil { // Avoid non-requests and double deliveries
|
||||||
|
res.cached = data[i]
|
||||||
|
close(res.done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
core/bloombits/scheduler_test.go
Normal file
103
core/bloombits/scheduler_test.go
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bloombits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that the scheduler can deduplicate and forward retrieval requests to
|
||||||
|
// underlying fetchers and serve responses back, irrelevant of the concurrency
|
||||||
|
// of the requesting clients or serving data fetchers.
|
||||||
|
func TestSchedulerSingleClientSingleFetcher(t *testing.T) { testScheduler(t, 1, 1, 5000) }
|
||||||
|
func TestSchedulerSingleClientMultiFetcher(t *testing.T) { testScheduler(t, 1, 10, 5000) }
|
||||||
|
func TestSchedulerMultiClientSingleFetcher(t *testing.T) { testScheduler(t, 10, 1, 5000) }
|
||||||
|
func TestSchedulerMultiClientMultiFetcher(t *testing.T) { testScheduler(t, 10, 10, 5000) }
|
||||||
|
|
||||||
|
func testScheduler(t *testing.T, clients int, fetchers int, requests int) {
|
||||||
|
t.Parallel()
|
||||||
|
f := newScheduler(0)
|
||||||
|
|
||||||
|
// Create a batch of handler goroutines that respond to bloom bit requests and
|
||||||
|
// deliver them to the scheduler.
|
||||||
|
var fetchPend sync.WaitGroup
|
||||||
|
fetchPend.Add(fetchers)
|
||||||
|
defer fetchPend.Wait()
|
||||||
|
|
||||||
|
fetch := make(chan *request, 16)
|
||||||
|
defer close(fetch)
|
||||||
|
|
||||||
|
var delivered atomic.Uint32
|
||||||
|
for i := 0; i < fetchers; i++ {
|
||||||
|
go func() {
|
||||||
|
defer fetchPend.Done()
|
||||||
|
|
||||||
|
for req := range fetch {
|
||||||
|
delivered.Add(1)
|
||||||
|
|
||||||
|
f.deliver([]uint64{
|
||||||
|
req.section + uint64(requests), // Non-requested data (ensure it doesn't go out of bounds)
|
||||||
|
req.section, // Requested data
|
||||||
|
req.section, // Duplicated data (ensure it doesn't double close anything)
|
||||||
|
}, [][]byte{
|
||||||
|
{},
|
||||||
|
new(big.Int).SetUint64(req.section).Bytes(),
|
||||||
|
new(big.Int).SetUint64(req.section).Bytes(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
// Start a batch of goroutines to concurrently run scheduling tasks
|
||||||
|
quit := make(chan struct{})
|
||||||
|
|
||||||
|
var pend sync.WaitGroup
|
||||||
|
pend.Add(clients)
|
||||||
|
|
||||||
|
for i := 0; i < clients; i++ {
|
||||||
|
go func() {
|
||||||
|
defer pend.Done()
|
||||||
|
|
||||||
|
in := make(chan uint64, 16)
|
||||||
|
out := make(chan []byte, 16)
|
||||||
|
|
||||||
|
f.run(in, fetch, out, quit, &pend)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for j := 0; j < requests; j++ {
|
||||||
|
in <- uint64(j)
|
||||||
|
}
|
||||||
|
close(in)
|
||||||
|
}()
|
||||||
|
b := new(big.Int)
|
||||||
|
for j := 0; j < requests; j++ {
|
||||||
|
bits := <-out
|
||||||
|
if want := b.SetUint64(uint64(j)).Bytes(); !bytes.Equal(bits, want) {
|
||||||
|
t.Errorf("vector %d: delivered content mismatch: have %x, want %x", j, bits, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
pend.Wait()
|
||||||
|
|
||||||
|
if have := delivered.Load(); int(have) != requests {
|
||||||
|
t.Errorf("request count mismatch: have %v, want %v", have, requests)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -161,3 +161,17 @@ func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byt
|
||||||
func BloomLookup(bin Bloom, topic bytesBacked) bool {
|
func BloomLookup(bin Bloom, topic bytesBacked) bool {
|
||||||
return bin.Test(topic.Bytes())
|
return bin.Test(topic.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// INITIA CUSTOM
|
||||||
|
// LogsBloom returns the bloom bytes for the given logs
|
||||||
|
func LogsBloom(logs []*Log) []byte {
|
||||||
|
buf := make([]byte, 6)
|
||||||
|
var bin Bloom
|
||||||
|
for _, log := range logs {
|
||||||
|
bin.add(log.Address.Bytes(), buf)
|
||||||
|
for _, b := range log.Topics {
|
||||||
|
bin.add(b[:], buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bin[:]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,17 @@ type PrecompiledContract interface {
|
||||||
Run(input []byte) ([]byte, error) // Run runs the precompiled contract
|
Run(input []byte) ([]byte, error) // Run runs the precompiled contract
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// INITIA CUSTOM
|
||||||
|
// ExtendedPrecompiledContract is the initia custom specific interface for native Go contracts.
|
||||||
|
type ExtendedPrecompiledContract interface {
|
||||||
|
ExtendedRun(
|
||||||
|
caller common.Address,
|
||||||
|
input []byte,
|
||||||
|
suppliedGas uint64,
|
||||||
|
readOnly bool,
|
||||||
|
) ([]byte, uint64 /* gas cost */, error)
|
||||||
|
}
|
||||||
|
|
||||||
// PrecompiledContracts contains the precompiled contracts supported at the given fork.
|
// PrecompiledContracts contains the precompiled contracts supported at the given fork.
|
||||||
type PrecompiledContracts map[common.Address]PrecompiledContract
|
type PrecompiledContracts map[common.Address]PrecompiledContract
|
||||||
|
|
||||||
|
|
@ -231,6 +242,39 @@ func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uin
|
||||||
return output, suppliedGas, err
|
return output, suppliedGas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtendedRunPrecompiledContract runs and evaluates the output of a precompiled contract.
|
||||||
|
// It returns
|
||||||
|
// - the returned bytes,
|
||||||
|
// - the _remaining_ gas,
|
||||||
|
// - any error that occurred
|
||||||
|
func ExtendedRunPrecompiledContract(
|
||||||
|
p PrecompiledContract,
|
||||||
|
caller common.Address,
|
||||||
|
input []byte,
|
||||||
|
suppliedGas uint64,
|
||||||
|
readOnly bool,
|
||||||
|
logger *tracing.Hooks,
|
||||||
|
) (
|
||||||
|
ret []byte,
|
||||||
|
remainingGas uint64,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
if p, ok := p.(ExtendedPrecompiledContract); ok {
|
||||||
|
output, gasCost, err := p.ExtendedRun(caller, input, suppliedGas, readOnly)
|
||||||
|
if err == ErrOutOfGas || suppliedGas < gasCost {
|
||||||
|
return nil, 0, ErrOutOfGas
|
||||||
|
}
|
||||||
|
if logger != nil && logger.OnGasChange != nil {
|
||||||
|
logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract)
|
||||||
|
}
|
||||||
|
|
||||||
|
suppliedGas -= gasCost
|
||||||
|
return output, suppliedGas, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return RunPrecompiledContract(p, input, suppliedGas, logger)
|
||||||
|
}
|
||||||
|
|
||||||
// ecrecover implemented as a native contract.
|
// ecrecover implemented as a native contract.
|
||||||
type ecrecover struct{}
|
type ecrecover struct{}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,34 @@ type EVM struct {
|
||||||
// jumpDests is the aggregated result of JUMPDEST analysis made through
|
// jumpDests is the aggregated result of JUMPDEST analysis made through
|
||||||
// the life cycle of EVM.
|
// the life cycle of EVM.
|
||||||
jumpDests map[common.Hash]bitvec
|
jumpDests map[common.Hash]bitvec
|
||||||
|
|
||||||
|
// INITIA CUSTOM
|
||||||
|
// disallowCosmosDispatch is a flag that is used to determine if the EVM should disallow Cosmos dispatch
|
||||||
|
disallowCosmosDispatch bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// INITIA CUSTOM
|
||||||
|
// SetDisallowCosmosDispatch sets the disallowCosmosDispatch flag
|
||||||
|
func (evm *EVM) SetDisallowCosmosDispatch(value bool) {
|
||||||
|
evm.disallowCosmosDispatch = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// INITIA CUSTOM
|
||||||
|
// GetDisallowCosmosDispatch returns the disallowCosmosDispatch flag
|
||||||
|
func (evm *EVM) GetDisallowCosmosDispatch() bool {
|
||||||
|
return evm.disallowCosmosDispatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// INITIA CUSTOM
|
||||||
|
// IncreaseDepth increases the depth of the EVM
|
||||||
|
func (evm *EVM) IncreaseDepth() {
|
||||||
|
evm.depth++
|
||||||
|
}
|
||||||
|
|
||||||
|
// INITIA CUSTOM
|
||||||
|
// DecreaseDepth decreases the depth of the EVM
|
||||||
|
func (evm *EVM) DecreaseDepth() {
|
||||||
|
evm.depth--
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEVM constructs an EVM instance with the supplied block context, state
|
// NewEVM constructs an EVM instance with the supplied block context, state
|
||||||
|
|
@ -224,7 +252,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
||||||
evm.Context.Transfer(evm.StateDB, caller, addr, value)
|
evm.Context.Transfer(evm.StateDB, caller, addr, value)
|
||||||
|
|
||||||
if isPrecompile {
|
if isPrecompile {
|
||||||
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
ret, gas, err = ExtendedRunPrecompiledContract(p, caller, input, gas, false, evm.Config.Tracer)
|
||||||
} else {
|
} else {
|
||||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||||
code := evm.resolveCode(addr)
|
code := evm.resolveCode(addr)
|
||||||
|
|
@ -288,7 +316,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
||||||
|
|
||||||
// It is allowed to call precompiles, even via delegatecall
|
// It is allowed to call precompiles, even via delegatecall
|
||||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||||
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
ret, gas, err = ExtendedRunPrecompiledContract(p, caller, input, gas, false, evm.Config.Tracer)
|
||||||
} else {
|
} else {
|
||||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||||
// The contract is a scoped environment for this execution context only.
|
// The contract is a scoped environment for this execution context only.
|
||||||
|
|
@ -331,7 +359,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
|
||||||
|
|
||||||
// It is allowed to call precompiles, even via delegatecall
|
// It is allowed to call precompiles, even via delegatecall
|
||||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||||
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
ret, gas, err = ExtendedRunPrecompiledContract(p, caller, input, gas, true, evm.Config.Tracer)
|
||||||
} else {
|
} else {
|
||||||
// Initialise a new contract and make initialise the delegate values
|
// Initialise a new contract and make initialise the delegate values
|
||||||
//
|
//
|
||||||
|
|
@ -383,7 +411,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
||||||
evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount)
|
evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount)
|
||||||
|
|
||||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||||
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
ret, gas, err = ExtendedRunPrecompiledContract(p, caller, input, gas, true, evm.Config.Tracer)
|
||||||
} else {
|
} else {
|
||||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||||
// The contract is a scoped environment for this execution context only.
|
// The contract is a scoped environment for this execution context only.
|
||||||
|
|
|
||||||
|
|
@ -436,12 +436,20 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// INITIA CUSTOM
|
||||||
|
// number of blocks to retain block hashes is parameterized by the chain config.
|
||||||
|
// - `0` means all block hashes are retained
|
||||||
|
numOfBlocks := uint64(256)
|
||||||
|
if interpreter.evm.Config.NumRetainBlockHashes != nil {
|
||||||
|
numOfBlocks = *interpreter.evm.Config.NumRetainBlockHashes
|
||||||
|
}
|
||||||
|
|
||||||
var upper, lower uint64
|
var upper, lower uint64
|
||||||
upper = interpreter.evm.Context.BlockNumber.Uint64()
|
upper = interpreter.evm.Context.BlockNumber.Uint64()
|
||||||
if upper < 257 {
|
if numOfBlocks == 0 || upper < numOfBlocks+1 {
|
||||||
lower = 0
|
lower = 0
|
||||||
} else {
|
} else {
|
||||||
lower = upper - 256
|
lower = upper - numOfBlocks
|
||||||
}
|
}
|
||||||
if num64 >= lower && num64 < upper {
|
if num64 >= lower && num64 < upper {
|
||||||
res := interpreter.evm.Context.GetHash(num64)
|
res := interpreter.evm.Context.GetHash(num64)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ type Config struct {
|
||||||
ExtraEips []int // Additional EIPS that are to be enabled
|
ExtraEips []int // Additional EIPS that are to be enabled
|
||||||
|
|
||||||
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
|
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
|
||||||
|
|
||||||
|
// INITIA CUSTOM
|
||||||
|
NumRetainBlockHashes *uint64 // Number of block hashes to retain for the BLOCKHASH opcode
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScopeContext contains the things that are per-call, such as stack and memory,
|
// ScopeContext contains the things that are per-call, such as stack and memory,
|
||||||
|
|
@ -164,6 +167,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||||
in.evm.depth++
|
in.evm.depth++
|
||||||
defer func() { in.evm.depth-- }()
|
defer func() { in.evm.depth-- }()
|
||||||
|
|
||||||
|
// INITIA CUSTOM
|
||||||
|
originDisableCosmosDispatch := in.evm.GetDisallowCosmosDispatch()
|
||||||
|
defer func() {
|
||||||
|
in.evm.SetDisallowCosmosDispatch(originDisableCosmosDispatch)
|
||||||
|
}()
|
||||||
|
|
||||||
// Make sure the readOnly is only set if we aren't in readOnly yet.
|
// Make sure the readOnly is only set if we aren't in readOnly yet.
|
||||||
// This also makes sure that the readOnly flag isn't removed for child calls.
|
// This also makes sure that the readOnly flag isn't removed for child calls.
|
||||||
if readOnly && !in.readOnly {
|
if readOnly && !in.readOnly {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue