diff --git a/core/bloombits/docs.go b/core/bloombits/docs.go
new file mode 100644
index 0000000000..3d159e74f7
--- /dev/null
+++ b/core/bloombits/docs.go
@@ -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 .
+
+// Package bloombits implements bloom filtering on batches of data.
+package bloombits
diff --git a/core/bloombits/generator.go b/core/bloombits/generator.go
new file mode 100644
index 0000000000..646151db0b
--- /dev/null
+++ b/core/bloombits/generator.go
@@ -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 .
+
+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
+}
diff --git a/core/bloombits/generator_test.go b/core/bloombits/generator_test.go
new file mode 100644
index 0000000000..ac1aee0b25
--- /dev/null
+++ b/core/bloombits/generator_test.go
@@ -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 .
+
+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)
+ }
+ }
+ }
+ })
+}
diff --git a/core/bloombits/matcher.go b/core/bloombits/matcher.go
new file mode 100644
index 0000000000..486581fe23
--- /dev/null
+++ b/core/bloombits/matcher.go
@@ -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 .
+
+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<= 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()
+ }
+ }
+ }
+}
diff --git a/core/bloombits/matcher_test.go b/core/bloombits/matcher_test.go
new file mode 100644
index 0000000000..7f3d5f279c
--- /dev/null
+++ b/core/bloombits/matcher_test.go
@@ -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 .
+
+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
+}
diff --git a/core/bloombits/scheduler.go b/core/bloombits/scheduler.go
new file mode 100644
index 0000000000..a523bc55ab
--- /dev/null
+++ b/core/bloombits/scheduler.go
@@ -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 .
+
+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)
+ }
+ }
+}
diff --git a/core/bloombits/scheduler_test.go b/core/bloombits/scheduler_test.go
new file mode 100644
index 0000000000..dcaaa91525
--- /dev/null
+++ b/core/bloombits/scheduler_test.go
@@ -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 .
+
+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)
+ }
+}
diff --git a/core/types/bloom9.go b/core/types/bloom9.go
index 962ba46d47..b5cd0a625c 100644
--- a/core/types/bloom9.go
+++ b/core/types/bloom9.go
@@ -161,3 +161,17 @@ func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byt
func BloomLookup(bin Bloom, topic bytesBacked) bool {
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[:]
+}
diff --git a/core/vm/contracts.go b/core/vm/contracts.go
index 06849e65ad..f3cdcc8907 100644
--- a/core/vm/contracts.go
+++ b/core/vm/contracts.go
@@ -47,6 +47,17 @@ type PrecompiledContract interface {
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.
type PrecompiledContracts map[common.Address]PrecompiledContract
@@ -231,6 +242,39 @@ func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uin
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.
type ecrecover struct{}
diff --git a/core/vm/evm.go b/core/vm/evm.go
index c28dcb2554..c00bf2f611 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -125,6 +125,34 @@ type EVM struct {
// jumpDests is the aggregated result of JUMPDEST analysis made through
// the life cycle of EVM.
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
@@ -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)
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 {
// Initialise a new contract and set the code that is to be used by the EVM.
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
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 {
// 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.
@@ -331,7 +359,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
// It is allowed to call precompiles, even via delegatecall
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 {
// 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)
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 {
// 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.
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 0b3b1d1569..6090151bfa 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -436,12 +436,20 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
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
upper = interpreter.evm.Context.BlockNumber.Uint64()
- if upper < 257 {
+ if numOfBlocks == 0 || upper < numOfBlocks+1 {
lower = 0
} else {
- lower = upper - 256
+ lower = upper - numOfBlocks
}
if num64 >= lower && num64 < upper {
res := interpreter.evm.Context.GetHash(num64)
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index a0038d1aa8..e16cd23989 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -35,6 +35,9 @@ type Config struct {
ExtraEips []int // Additional EIPS that are to be enabled
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,
@@ -164,6 +167,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
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.
// This also makes sure that the readOnly flag isn't removed for child calls.
if readOnly && !in.readOnly {