-
Notifications
You must be signed in to change notification settings - Fork 20.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
eth/filters: avoid block body retrieval when no matching logs #25199
Changes from all commits
68c6bb3
be575b0
77f8e7e
4951c2a
78f0acb
aa65ec7
b4dc023
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ import ( | |
"context" | ||
"fmt" | ||
"sync" | ||
"sync/atomic" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum" | ||
|
@@ -58,6 +59,7 @@ type Backend interface { | |
ChainDb() ethdb.Database | ||
HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) | ||
HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) | ||
GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) | ||
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) | ||
GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) | ||
PendingBlockAndReceipts() (*types.Block, types.Receipts) | ||
|
@@ -77,7 +79,7 @@ type Backend interface { | |
// FilterSystem holds resources shared by all filters. | ||
type FilterSystem struct { | ||
backend Backend | ||
logsCache *lru.Cache[common.Hash, [][]*types.Log] | ||
logsCache *lru.Cache[common.Hash, *logCacheElem] | ||
cfg *Config | ||
} | ||
|
||
|
@@ -86,13 +88,18 @@ func NewFilterSystem(backend Backend, config Config) *FilterSystem { | |
config = config.withDefaults() | ||
return &FilterSystem{ | ||
backend: backend, | ||
logsCache: lru.NewCache[common.Hash, [][]*types.Log](config.LogCacheSize), | ||
logsCache: lru.NewCache[common.Hash, *logCacheElem](config.LogCacheSize), | ||
cfg: &config, | ||
} | ||
} | ||
|
||
// cachedGetLogs loads block logs from the backend and caches the result. | ||
func (sys *FilterSystem) cachedGetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { | ||
type logCacheElem struct { | ||
logs []*types.Log | ||
body atomic.Pointer[types.Body] | ||
} | ||
|
||
// cachedLogElem loads block logs from the backend and caches the result. | ||
func (sys *FilterSystem) cachedLogElem(ctx context.Context, blockHash common.Hash, number uint64) (*logCacheElem, error) { | ||
cached, ok := sys.logsCache.Get(blockHash) | ||
if ok { | ||
return cached, nil | ||
|
@@ -105,8 +112,35 @@ func (sys *FilterSystem) cachedGetLogs(ctx context.Context, blockHash common.Has | |
if logs == nil { | ||
return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, blockHash.TerminalString()) | ||
} | ||
sys.logsCache.Add(blockHash, logs) | ||
return logs, nil | ||
// Database logs are un-derived. | ||
// Fill in whatever we can (txHash is inaccessible at this point). | ||
flattened := make([]*types.Log, 0) | ||
var logIdx uint | ||
for i, txLogs := range logs { | ||
for _, log := range txLogs { | ||
log.BlockHash = blockHash | ||
log.BlockNumber = number | ||
log.TxIndex = uint(i) | ||
log.Index = logIdx | ||
logIdx++ | ||
flattened = append(flattened, log) | ||
} | ||
} | ||
elem := &logCacheElem{logs: flattened} | ||
sys.logsCache.Add(blockHash, elem) | ||
return elem, nil | ||
} | ||
|
||
func (sys *FilterSystem) cachedGetBody(ctx context.Context, elem *logCacheElem, hash common.Hash, number uint64) (*types.Body, error) { | ||
if body := elem.body.Load(); body != nil { | ||
return body, nil | ||
} | ||
body, err := sys.backend.GetBody(ctx, hash, rpc.BlockNumber(number)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
elem.body.Store(body) | ||
return body, nil | ||
} | ||
|
||
// Type determines the kind of filter and is used to put the filter in to | ||
|
@@ -431,6 +465,12 @@ func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) | |
if es.lightMode && len(filters[LogsSubscription]) > 0 { | ||
es.lightFilterNewHead(ev.Block.Header(), func(header *types.Header, remove bool) { | ||
for _, f := range filters[LogsSubscription] { | ||
if f.logsCrit.FromBlock != nil && header.Number.Cmp(f.logsCrit.FromBlock) < 0 { | ||
continue | ||
} | ||
if f.logsCrit.ToBlock != nil && header.Number.Cmp(f.logsCrit.ToBlock) > 0 { | ||
continue | ||
} | ||
if matchedLogs := es.lightFilterLogs(header, f.logsCrit.Addresses, f.logsCrit.Topics, remove); len(matchedLogs) > 0 { | ||
f.logs <- matchedLogs | ||
} | ||
|
@@ -474,42 +514,39 @@ 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) { | ||
// Get the logs of the block | ||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) | ||
defer cancel() | ||
logsList, err := es.sys.cachedGetLogs(ctx, header.Hash(), header.Number.Uint64()) | ||
if err != nil { | ||
return nil | ||
} | ||
var unfiltered []*types.Log | ||
for _, logs := range logsList { | ||
for _, log := range logs { | ||
logcopy := *log | ||
logcopy.Removed = remove | ||
unfiltered = append(unfiltered, &logcopy) | ||
} | ||
} | ||
logs := filterLogs(unfiltered, nil, nil, addresses, topics) | ||
if len(logs) > 0 && logs[0].TxHash == (common.Hash{}) { | ||
// We have matching but non-derived logs | ||
receipts, err := es.backend.GetReceipts(ctx, header.Hash()) | ||
if err != nil { | ||
return nil | ||
} | ||
unfiltered = unfiltered[:0] | ||
for _, receipt := range receipts { | ||
for _, log := range receipt.Logs { | ||
logcopy := *log | ||
logcopy.Removed = remove | ||
unfiltered = append(unfiltered, &logcopy) | ||
} | ||
} | ||
logs = filterLogs(unfiltered, nil, nil, addresses, topics) | ||
} | ||
if !bloomFilter(header.Bloom, addresses, topics) { | ||
return nil | ||
} | ||
// Get the logs of the block | ||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) | ||
defer cancel() | ||
cached, err := es.sys.cachedLogElem(ctx, header.Hash(), header.Number.Uint64()) | ||
if err != nil { | ||
return nil | ||
} | ||
unfiltered := append([]*types.Log{}, cached.logs...) | ||
for i, log := range unfiltered { | ||
// Don't modify in-cache elements | ||
logcopy := *log | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This copy is wasteful because we might have to copy the logs slice again in It would be good to clearly define/document that |
||
logcopy.Removed = remove | ||
// Swap copy in-place | ||
unfiltered[i] = &logcopy | ||
} | ||
logs := filterLogs(unfiltered, nil, nil, addresses, topics) | ||
// Txhash is already resolved | ||
if len(logs) > 0 && logs[0].TxHash != (common.Hash{}) { | ||
return logs | ||
} | ||
return nil | ||
// Resolve txhash | ||
body, err := es.sys.cachedGetBody(ctx, cached, header.Hash(), header.Number.Uint64()) | ||
if err != nil { | ||
return nil | ||
} | ||
for _, log := range logs { | ||
// logs are already copied, safe to modify | ||
log.TxHash = body.Transactions[log.TxIndex].Hash() | ||
} | ||
return logs | ||
} | ||
|
||
// eventLoop (un)installs filters and processes mux events. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review note: need to check for other uses of
ReadLogs
, which could be affected by not deriving the block context fields anymore.