Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions core/bloom_indexer.libevm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2026 the libevm authors.
//
// The libevm additions to go-ethereum are free software: you can redistribute
// them and/or modify them 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 libevm additions are distributed in the hope that they 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 core

import (
"github.com/ava-labs/libevm/core/types"
"github.com/ava-labs/libevm/ethdb"
)

// BloomThrottling is the time to wait between processing two consecutive index sections.
const BloomThrottling = bloomThrottling

// NewBloomIndexerBackend creates a [BloomIndexer] instance for the given database and section size,
// allowing users to provide custom functionality to the bloom indexer.
func NewBloomIndexerBackend(db ethdb.Database, size uint64) *BloomIndexer {
return &BloomIndexer{
db: db,
size: size,
}
}

// ProcessWithBloomOverride is the same as [BloomIndexer.Process], but takes the header and bloom separately.
// This must obey the same invariates as [BloomIndexer.Process], including calling [BloomIndexer.Reset]
// to start a new section prior to this call, otherwise this function will panic.
func (b *BloomIndexer) ProcessWithBloomOverride(header *types.Header, bloom types.Bloom) error {
index := uint(header.Number.Uint64() - b.section*b.size)
if err := b.gen.AddBloom(index, bloom); err != nil {
return err
}
b.head = header.Hash()
return nil
}
67 changes: 67 additions & 0 deletions eth/bloombits.libevm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2026 the libevm authors.
//
// The libevm additions to go-ethereum are free software: you can redistribute
// them and/or modify them 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 libevm additions are distributed in the hope that they 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 eth

import (
"github.com/ava-labs/libevm/core/bloombits"
"github.com/ava-labs/libevm/ethdb"
)

const (
// BloomFilterThreads is the number of goroutines used locally per filter to
// multiplex requests onto the global servicing goroutines.
BloomFilterThreads = bloomFilterThreads

// BloomRetrievalBatch is the maximum number of bloom bit retrievals to
// service in a single batch.
BloomRetrievalBatch = bloomRetrievalBatch

// BloomRetrievalWait is the maximum time to wait for enough bloom bit
// requests to accumulate request an entire batch (avoiding hysteresis).
BloomRetrievalWait = bloomRetrievalWait
)

// StartBloomHandlers starts a batch of goroutines to serve data for
// [bloombits.Retrieval] requests from any number of filters. This is identical
// to [Ethereum.startBloomHandlers], but exposed for independent use.
func StartBloomHandlers(db ethdb.Database, sectionSize uint64) *BloomHandlers {
bh := &BloomHandlers{
Requests: make(chan chan *bloombits.Retrieval),
quit: make(chan struct{}),
}
eth := &Ethereum{
bloomRequests: bh.Requests,
closeBloomHandler: bh.quit,
chainDb: db,
}
eth.startBloomHandlers(sectionSize)
return bh
}

// BloomHandlers serve data for [bloombits.Retrieval] requests from any number
// of filters. [BloomHandlers.Close] MUST be called to release goroutines, after
// which a send on the requests channel will block indefinitely.
type BloomHandlers struct {
Requests chan chan *bloombits.Retrieval
quit chan struct{}
}

// Close releases resources in use by the [BloomHandlers]; repeated calls will
// panic.
func (bh *BloomHandlers) Close() {
close(bh.quit)
}
30 changes: 30 additions & 0 deletions eth/bloombits.libevm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2026 the libevm authors.
//
// The libevm additions to go-ethereum are free software: you can redistribute
// them and/or modify them 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 libevm additions are distributed in the hope that they 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 eth

import (
"testing"

"go.uber.org/goleak"

"github.com/ava-labs/libevm/core/rawdb"
)

func TestStartBloomHandlersNoLeaks(t *testing.T) {
defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
StartBloomHandlers(rawdb.NewMemoryDatabase(), 42).Close()
}
4 changes: 2 additions & 2 deletions eth/filters/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func (f *Filter) unindexedLogs(ctx context.Context, end uint64, logChan chan *ty

// blockLogs returns the logs matching the filter criteria within a single block.
func (f *Filter) blockLogs(ctx context.Context, header *types.Header) ([]*types.Log, error) {
if bloomFilter(header.Bloom, f.addresses, f.topics) {
if bloomFilter(maybeOverrideBloom(header, f.sys.backend), f.addresses, f.topics) {
return f.checkMatches(ctx, header)
}
return nil, nil
Expand Down Expand Up @@ -337,7 +337,7 @@ func (f *Filter) pendingLogs() []*types.Log {
if block == nil || receipts == nil {
return nil
}
if bloomFilter(block.Bloom(), f.addresses, f.topics) {
if bloomFilter(maybeOverrideBloom(block.Header(), f.sys.backend), f.addresses, f.topics) {
var unfiltered []*types.Log
for _, r := range receipts {
unfiltered = append(unfiltered, r.Logs...)
Expand Down
2 changes: 1 addition & 1 deletion eth/filters/filter_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ func (es *EventSystem) lightFilterNewHead(newHeader *types.Header, callBack func

// filter logs of a single header in light client mode
func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.Address, topics [][]common.Hash, remove bool) []*types.Log {
if !bloomFilter(header.Bloom, addresses, topics) {
if !bloomFilter(maybeOverrideBloom(header, es.backend), addresses, topics) {
return nil
}
// Get the logs of the block
Expand Down
35 changes: 35 additions & 0 deletions eth/filters/filter_system.libevm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2026 the libevm authors.
//
// The libevm additions to go-ethereum are free software: you can redistribute
// them and/or modify them 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 libevm additions are distributed in the hope that they 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 filters

import (
"github.com/ava-labs/libevm/core/types"
)

// BloomOverrider is an optional extension to [Backend], allowing arbitrary
// bloom filters to be returned for a header. If not implemented,
// [types.Header.Bloom] is used instead.
type BloomOverrider interface {
OverrideHeaderBloom(*types.Header) types.Bloom
}

func maybeOverrideBloom(header *types.Header, backend Backend) types.Bloom {
if bo, ok := backend.(BloomOverrider); ok {
return bo.OverrideHeaderBloom(header)
}
return header.Bloom
}
91 changes: 91 additions & 0 deletions eth/filters/filter_system.libevm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2026 the libevm authors.
//
// The libevm additions to go-ethereum are free software: you can redistribute
// them and/or modify them 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 libevm additions are distributed in the hope that they 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 filters

import (
"math/big"
"testing"

"github.com/stretchr/testify/require"

"github.com/ava-labs/libevm/core"
"github.com/ava-labs/libevm/core/rawdb"
"github.com/ava-labs/libevm/core/types"
"github.com/ava-labs/libevm/rpc"
)

type bloomOverriderBackend struct {
*testBackend
overridden chan struct{}
}

var _ BloomOverrider = (*bloomOverriderBackend)(nil)

func (b *bloomOverriderBackend) OverrideHeaderBloom(header *types.Header) types.Bloom {
b.overridden <- struct{}{}
return header.Bloom
}

func TestBloomOverride(t *testing.T) {
db := rawdb.NewMemoryDatabase()
backend, sys := newTestFilterSystem(t, db, Config{})
sut := &bloomOverriderBackend{
testBackend: backend,
overridden: make(chan struct{}),
}
sys.backend = sut

t.Run("lightFilterLogs", func(t *testing.T) {
api := NewFilterAPI(sys, true /*lightMode*/)
defer CloseAPI(api)

id, err := api.NewFilter(FilterCriteria{})
require.NoErrorf(t, err, "%T.NewFilter()", api)
defer api.UninstallFilter(id)

// If there is no historical header then the filter system returns early.
for i := range int64(2) {
sut.chainFeed.Send(core.ChainEvent{
Block: types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(i),
}),
})
}
<-sut.overridden
})

t.Run("blockLogs", func(t *testing.T) {
hdr := &types.Header{Number: big.NewInt(0)}
h := hdr.Hash()
rawdb.WriteHeader(db, hdr)
rawdb.WriteCanonicalHash(db, h, 0)
rawdb.WriteHeaderNumber(db, h, 0)

go sys.NewBlockFilter(h, nil, nil).Logs(t.Context()) //nolint:errcheck // Known but irrelevant error
<-sut.overridden
})

t.Run("pendingLogs", func(t *testing.T) {
hdr := &types.Header{Number: big.NewInt(1)}
sut.pendingBlock = types.NewBlockWithHeader(hdr)
sut.pendingReceipts = types.Receipts{}

n := rpc.PendingBlockNumber.Int64()
go sys.NewRangeFilter(n, n, nil, nil).Logs(t.Context()) //nolint:errcheck // Known but irrelevant error
<-sut.overridden
})
}