Skip to content
This repository was archived by the owner on Jun 9, 2026. It is now read-only.

Commit 464c95c

Browse files
alarso16ARR4NJonathanOppenheimerStephenButtolph
authored
feat: eth_getLogs (#120)
Adds the two functions required in the backend to implement `eth_getLogs`, and adds some basic tests. --------- Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> Co-authored-by: Arran Schlosberg <me@arranschlosberg.com> Co-authored-by: Jonathan Oppenheimer <jonathan.oppenheimer@avalabs.org> Co-authored-by: Jonathan Oppenheimer <147infiniti@gmail.com> Co-authored-by: Stephen Buttolph <stephen@avalabs.org>
1 parent 58fd556 commit 464c95c

8 files changed

Lines changed: 379 additions & 28 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ tool github.com/go-task/task/v3/cmd/task
66

77
require (
88
github.com/ava-labs/avalanchego v1.14.2-0.20260123184805-18c4dbe2714e
9-
github.com/ava-labs/libevm v1.13.15-0.20260128160829-e673c7097059
9+
github.com/ava-labs/libevm v1.13.15-0.20260212133604-62502c671227
1010
github.com/google/go-cmp v0.7.0
1111
github.com/holiman/uint256 v1.2.4
1212
github.com/stretchr/testify v1.11.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
4040
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
4141
github.com/ava-labs/avalanchego v1.14.2-0.20260123184805-18c4dbe2714e h1:vmO2RL0wG6QuMxZqSzY13BtR77XsSCcCmiwPKCJkAXI=
4242
github.com/ava-labs/avalanchego v1.14.2-0.20260123184805-18c4dbe2714e/go.mod h1:aE2RZUWfJwiK+tyVu+fnE5DWOZ0W1TEg5BL3n1rkq7s=
43-
github.com/ava-labs/libevm v1.13.15-0.20260128160829-e673c7097059 h1:3l75HBUnvVeSjs1ikoA8LUu8eo/A7NTgacuy1qUvQuk=
44-
github.com/ava-labs/libevm v1.13.15-0.20260128160829-e673c7097059/go.mod h1:oyJdZfpQTc9fVzAbDry+QRYeiCbw8s/kGaDUsEMpb4I=
43+
github.com/ava-labs/libevm v1.13.15-0.20260212133604-62502c671227 h1:G/fQiyPj+0dXaLRUu1ntbfcIww233nqbofis8deBar0=
44+
github.com/ava-labs/libevm v1.13.15-0.20260212133604-62502c671227/go.mod h1:oyJdZfpQTc9fVzAbDry+QRYeiCbw8s/kGaDUsEMpb4I=
4545
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
4646
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
4747
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=

sae/bloom.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (C) 2026, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package sae
5+
6+
import (
7+
"context"
8+
"math"
9+
10+
"github.com/ava-labs/libevm/core"
11+
"github.com/ava-labs/libevm/core/bloombits"
12+
"github.com/ava-labs/libevm/core/rawdb"
13+
"github.com/ava-labs/libevm/core/types"
14+
"github.com/ava-labs/libevm/eth"
15+
"github.com/ava-labs/libevm/eth/filters"
16+
"github.com/ava-labs/libevm/ethdb"
17+
"github.com/ava-labs/libevm/params"
18+
)
19+
20+
// bloomIndexer provides the [bloomIndexer.BloomStatus] and
21+
// [bloomIndexer.ServiceFilter] methods of an [ethapi.Backend] implementation.
22+
type bloomIndexer struct {
23+
indexer *core.ChainIndexer
24+
size uint64
25+
handlers *eth.BloomHandlers
26+
}
27+
28+
// newBloomIndexer creates a [bloomIndexer] and starts the indexer to run with
29+
// events from `chain`.
30+
//
31+
// The consumer must call [bloomIndexer.Close] to release allocated resources.
32+
func newBloomIndexer(db ethdb.Database, chain core.ChainIndexerChain, override filters.BloomOverrider, size uint64) *bloomIndexer {
33+
if size == 0 || size > math.MaxInt32 {
34+
size = params.BloomBitsBlocks
35+
}
36+
37+
backend := &bloomBackend{
38+
BloomIndexer: core.NewBloomIndexerBackend(db, size),
39+
BloomOverrider: override,
40+
}
41+
table := rawdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix))
42+
b := &bloomIndexer{
43+
indexer: core.NewChainIndexer(db, table, backend, size, 0, core.BloomThrottling, "bloombits"),
44+
size: size,
45+
handlers: eth.StartBloomHandlers(db, size),
46+
}
47+
b.indexer.Start(chain)
48+
return b
49+
}
50+
51+
func (b *bloomIndexer) BloomStatus() (size uint64, sections uint64) {
52+
sections, _, _ = b.indexer.Sections()
53+
return b.size, sections
54+
}
55+
56+
func (b *bloomIndexer) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
57+
for range eth.BloomFilterThreads {
58+
go session.Multiplex(eth.BloomRetrievalBatch, eth.BloomRetrievalWait, b.handlers.Requests)
59+
}
60+
}
61+
62+
func (b *bloomIndexer) Close() error {
63+
b.handlers.Close()
64+
return b.indexer.Close()
65+
}
66+
67+
var _ core.ChainIndexerBackend = (*bloomBackend)(nil)
68+
69+
// bloomBackend is a wrapper around a [core.BloomIndexer] that overrides
70+
// Process() to allow for custom bloom-filter generation.
71+
type bloomBackend struct {
72+
*core.BloomIndexer
73+
filters.BloomOverrider
74+
}
75+
76+
func (b *bloomBackend) Process(ctx context.Context, hdr *types.Header) error {
77+
return b.ProcessWithBloomOverride(hdr, b.OverrideHeaderBloom(hdr))
78+
}

sae/rpc.go

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,24 @@ import (
3030
"github.com/ava-labs/libevm/params"
3131
"github.com/ava-labs/libevm/rpc"
3232

33+
"github.com/ava-labs/strevm/saexec"
3334
"github.com/ava-labs/strevm/txgossip"
3435
)
3536

36-
// APIBackend returns an API backend backed by the VM.
37-
func (vm *VM) APIBackend() ethapi.Backend {
37+
// APIBackend is the union of all interfaces required to implement the SAE APIs.
38+
type APIBackend interface {
39+
ethapi.Backend
40+
filters.BloomOverrider
41+
}
42+
43+
// APIBackend returns an API backend backed by the [VM].
44+
func (vm *VM) APIBackend() APIBackend {
3845
return vm.apiBackend
3946
}
4047

4148
func (vm *VM) ethRPCServer() (*rpc.Server, error) {
4249
b := vm.APIBackend()
43-
s := rpc.NewServer()
4450

45-
// Even if this function errors, we should close API to prevent a goroutine
46-
// from leaking.
4751
filterSystem := filters.NewFilterSystem(b, filters.Config{})
4852
filterAPI := filters.NewFilterAPI(filterSystem, false /*isLightClient*/)
4953
vm.toClose = append(vm.toClose, func() error {
@@ -108,6 +112,9 @@ func (vm *VM) ethRPCServer() (*rpc.Server, error) {
108112
// - eth_getRawTransactionByHash
109113
// - eth_pendingTransactions
110114
{"eth", ethapi.NewTransactionAPI(b, new(ethapi.AddrLocker))},
115+
// Standard Ethereum node APIS:
116+
// - eth_getLogs
117+
//
111118
// Geth-specific APIs:
112119
// - eth_subscribe
113120
// - newHeads
@@ -143,6 +150,7 @@ func (vm *VM) ethRPCServer() (*rpc.Server, error) {
143150
})
144151
}
145152

153+
s := rpc.NewServer()
146154
for _, api := range apis {
147155
if err := s.RegisterName(api.namespace, api.api); err != nil {
148156
return nil, fmt.Errorf("%T.RegisterName(%q, %T): %v", s, api.namespace, api.api, err)
@@ -200,12 +208,51 @@ func (s *netAPI) Version() string {
200208
return s.chainID
201209
}
202210

211+
// chainIndexer implements the subset of [ethapi.Backend] required to back a
212+
// [core.ChainIndexer].
213+
type chainIndexer struct {
214+
exec *saexec.Executor
215+
}
216+
217+
var _ core.ChainIndexerChain = chainIndexer{}
218+
219+
func (c chainIndexer) CurrentHeader() *types.Header {
220+
return types.CopyHeader(c.exec.LastExecuted().Header())
221+
}
222+
223+
func (c chainIndexer) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
224+
return c.exec.SubscribeChainHeadEvent(ch)
225+
}
226+
227+
// A bloomOverrider constructs Bloom filters from persisted receipts instead of
228+
// relying on the [types.Header] field.
229+
type bloomOverrider struct {
230+
db ethdb.Database
231+
}
232+
233+
var _ filters.BloomOverrider = bloomOverrider{}
234+
235+
// OverrideHeaderBloom returns the Bloom filter of the receipts generated when
236+
// executing the respective block, whereas the [types.Header] carries those
237+
// settled by the block.
238+
func (b bloomOverrider) OverrideHeaderBloom(header *types.Header) types.Bloom {
239+
return types.CreateBloom(rawdb.ReadRawReceipts(
240+
b.db,
241+
header.Hash(),
242+
header.Number.Uint64(),
243+
))
244+
}
245+
203246
type ethAPIBackend struct {
204-
*txgossip.Set
205247
vm *VM
206248
accountManager *accounts.Manager
207249

208-
ethapi.Backend // TODO(arr4n) remove in favour of `var _ ethapi.Backend = (*ethAPIBackend)(nil)`
250+
*txgossip.Set
251+
chainIndexer
252+
bloomOverrider
253+
*bloomIndexer
254+
255+
ethapi.Backend // TODO(arr4n) remove once all methods are implemented
209256
}
210257

211258
func (b *ethAPIBackend) ChainConfig() *params.ChainConfig {
@@ -224,10 +271,6 @@ func (b *ethAPIBackend) AccountManager() *accounts.Manager {
224271
return b.accountManager
225272
}
226273

227-
func (b *ethAPIBackend) CurrentHeader() *types.Header {
228-
return types.CopyHeader(b.vm.exec.LastExecuted().Header())
229-
}
230-
231274
func (b *ethAPIBackend) CurrentBlock() *types.Header {
232275
return b.CurrentHeader()
233276
}
@@ -275,6 +318,29 @@ func (b *ethAPIBackend) GetPoolTransaction(txHash common.Hash) *types.Transactio
275318
return b.Set.Pool.Get(txHash)
276319
}
277320

321+
func (b *ethAPIBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
322+
if hash == (common.Hash{}) {
323+
return nil, errors.New("empty block hash")
324+
}
325+
n, err := b.resolveBlockNumber(number)
326+
if err != nil {
327+
return nil, err
328+
}
329+
330+
if block, ok := b.vm.blocks.Load(hash); ok {
331+
if block.NumberU64() != n {
332+
return nil, fmt.Errorf("found block number %d for hash %#x, expected %d", block.NumberU64(), hash, number)
333+
}
334+
return block.EthBlock().Body(), nil
335+
}
336+
337+
return rawdb.ReadBody(b.vm.db, hash, n), nil
338+
}
339+
340+
func (b *ethAPIBackend) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) {
341+
return rawdb.ReadLogs(b.vm.db, blockHash, number), nil
342+
}
343+
278344
func (b *ethAPIBackend) GetPoolTransactions() (types.Transactions, error) {
279345
pending := b.Pool.Pending(txpool.PendingFilter{})
280346

@@ -355,10 +421,6 @@ func (b *ethAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Sub
355421
return b.vm.exec.SubscribeChainEvent(ch)
356422
}
357423

358-
func (b *ethAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
359-
return b.vm.exec.SubscribeChainHeadEvent(ch)
360-
}
361-
362424
func (b *ethAPIBackend) SubscribeChainSideEvent(chan<- core.ChainSideEvent) event.Subscription {
363425
// SAE never reorgs, so there are no side events.
364426
return newNoopSubscription()

0 commit comments

Comments
 (0)