Unverified Commit db9a178a authored by Delweng's avatar Delweng Committed by GitHub

eth/filters: retrieve logs in async (#27135)

This change implements async log retrievals via feeding logs in channels, instead of returning slices. This is a first step to implement #15063.  

---------
Signed-off-by: 's avatarjsvisa <delweng@gmail.com>
Co-authored-by: 's avatarSina Mahmoodi <itz.s1na@gmail.com>
Co-authored-by: 's avatarMartin Holst Swende <martin@swende.se>
Co-authored-by: 's avatarSina Mahmoodi <1591639+s1na@users.noreply.github.com>
parent 9358b62f
...@@ -106,32 +106,32 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { ...@@ -106,32 +106,32 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
} }
return f.blockLogs(ctx, header) return f.blockLogs(ctx, header)
} }
// Short-cut if all we care about is pending logs
if f.begin == rpc.PendingBlockNumber.Int64() { var (
if f.end != rpc.PendingBlockNumber.Int64() { beginPending = f.begin == rpc.PendingBlockNumber.Int64()
endPending = f.end == rpc.PendingBlockNumber.Int64()
)
// special case for pending logs
if beginPending && !endPending {
return nil, errors.New("invalid block range") return nil, errors.New("invalid block range")
} }
return f.pendingLogs()
} // Short-cut if all we care about is pending logs
// Figure out the limits of the filter range if beginPending && endPending {
header, _ := f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) return f.pendingLogs(), nil
if header == nil {
return nil, nil
} }
var (
err error
head = header.Number.Int64()
pending = f.end == rpc.PendingBlockNumber.Int64()
)
resolveSpecial := func(number int64) (int64, error) { resolveSpecial := func(number int64) (int64, error) {
var hdr *types.Header var hdr *types.Header
switch number { switch number {
case rpc.LatestBlockNumber.Int64(): case rpc.LatestBlockNumber.Int64(), rpc.PendingBlockNumber.Int64():
return head, nil
case rpc.PendingBlockNumber.Int64():
// we should return head here since we've already captured // we should return head here since we've already captured
// that we need to get the pending logs in the pending boolean above // that we need to get the pending logs in the pending boolean above
return head, nil hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
if hdr == nil {
return 0, errors.New("latest header not found")
}
case rpc.FinalizedBlockNumber.Int64(): case rpc.FinalizedBlockNumber.Int64():
hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber)
if hdr == nil { if hdr == nil {
...@@ -147,57 +147,92 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { ...@@ -147,57 +147,92 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
} }
return hdr.Number.Int64(), nil return hdr.Number.Int64(), nil
} }
var err error
// range query need to resolve the special begin/end block number
if f.begin, err = resolveSpecial(f.begin); err != nil { if f.begin, err = resolveSpecial(f.begin); err != nil {
return nil, err return nil, err
} }
if f.end, err = resolveSpecial(f.end); err != nil { if f.end, err = resolveSpecial(f.end); err != nil {
return nil, err return nil, err
} }
logChan, errChan := f.rangeLogsAsync(ctx)
var logs []*types.Log
for {
select {
case log := <-logChan:
logs = append(logs, log)
case err := <-errChan:
if err != nil {
// if an error occurs during extraction, we do return the extracted data
return logs, err
}
// Append the pending ones
if endPending {
pendingLogs := f.pendingLogs()
logs = append(logs, pendingLogs...)
}
return logs, nil
}
}
}
// rangeLogsAsync retrieves block-range logs that match the filter criteria asynchronously,
// it creates and returns two channels: one for delivering log data, and one for reporting errors.
func (f *Filter) rangeLogsAsync(ctx context.Context) (chan *types.Log, chan error) {
var (
logChan = make(chan *types.Log)
errChan = make(chan error)
)
go func() {
defer func() {
close(errChan)
close(logChan)
}()
// Gather all indexed logs, and finish with non indexed ones // Gather all indexed logs, and finish with non indexed ones
var ( var (
logs []*types.Log
end = uint64(f.end) end = uint64(f.end)
size, sections = f.sys.backend.BloomStatus() size, sections = f.sys.backend.BloomStatus()
err error
) )
if indexed := sections * size; indexed > uint64(f.begin) { if indexed := sections * size; indexed > uint64(f.begin) {
if indexed > end { if indexed > end {
logs, err = f.indexedLogs(ctx, end) indexed = end + 1
} else {
logs, err = f.indexedLogs(ctx, indexed-1)
} }
if err != nil { if err = f.indexedLogs(ctx, indexed-1, logChan); err != nil {
return logs, err errChan <- err
return
} }
} }
rest, err := f.unindexedLogs(ctx, end)
logs = append(logs, rest...) if err := f.unindexedLogs(ctx, end, logChan); err != nil {
if pending { errChan <- err
pendingLogs, err := f.pendingLogs() return
if err != nil {
return nil, err
}
logs = append(logs, pendingLogs...)
} }
return logs, err
errChan <- nil
}()
return logChan, errChan
} }
// indexedLogs returns the logs matching the filter criteria based on the bloom // indexedLogs returns the logs matching the filter criteria based on the bloom
// bits indexed available locally or via the network. // bits indexed available locally or via the network.
func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { func (f *Filter) indexedLogs(ctx context.Context, end uint64, logChan chan *types.Log) error {
// Create a matcher session and request servicing from the backend // Create a matcher session and request servicing from the backend
matches := make(chan uint64, 64) matches := make(chan uint64, 64)
session, err := f.matcher.Start(ctx, uint64(f.begin), end, matches) session, err := f.matcher.Start(ctx, uint64(f.begin), end, matches)
if err != nil { if err != nil {
return nil, err return err
} }
defer session.Close() defer session.Close()
f.sys.backend.ServiceFilter(ctx, session) f.sys.backend.ServiceFilter(ctx, session)
// Iterate over the matches until exhausted or context closed
var logs []*types.Log
for { for {
select { select {
case number, ok := <-matches: case number, ok := <-matches:
...@@ -207,47 +242,50 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err ...@@ -207,47 +242,50 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err
if err == nil { if err == nil {
f.begin = int64(end) + 1 f.begin = int64(end) + 1
} }
return logs, err return err
} }
f.begin = int64(number) + 1 f.begin = int64(number) + 1
// Retrieve the suggested block and pull any truly matching logs // Retrieve the suggested block and pull any truly matching logs
header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(number)) header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(number))
if header == nil || err != nil { if header == nil || err != nil {
return logs, err return err
} }
found, err := f.checkMatches(ctx, header) found, err := f.checkMatches(ctx, header)
if err != nil { if err != nil {
return logs, err return err
}
for _, log := range found {
logChan <- log
} }
logs = append(logs, found...)
case <-ctx.Done(): case <-ctx.Done():
return logs, ctx.Err() return ctx.Err()
} }
} }
} }
// unindexedLogs returns the logs matching the filter criteria based on raw block // unindexedLogs returns the logs matching the filter criteria based on raw block
// iteration and bloom matching. // iteration and bloom matching.
func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { func (f *Filter) unindexedLogs(ctx context.Context, end uint64, logChan chan *types.Log) error {
var logs []*types.Log
for ; f.begin <= int64(end); f.begin++ { for ; f.begin <= int64(end); f.begin++ {
if f.begin%10 == 0 && ctx.Err() != nil {
return logs, ctx.Err()
}
header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin)) header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin))
if header == nil || err != nil { if header == nil || err != nil {
return logs, err return err
} }
found, err := f.blockLogs(ctx, header) found, err := f.blockLogs(ctx, header)
if err != nil { if err != nil {
return logs, err return err
}
for _, log := range found {
select {
case logChan <- log:
case <-ctx.Done():
return ctx.Err()
} }
logs = append(logs, found...)
} }
return logs, nil }
return nil
} }
// blockLogs returns the logs matching the filter criteria within a single block. // blockLogs returns the logs matching the filter criteria within a single block.
...@@ -294,19 +332,19 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*typ ...@@ -294,19 +332,19 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*typ
} }
// pendingLogs returns the logs matching the filter criteria within the pending block. // pendingLogs returns the logs matching the filter criteria within the pending block.
func (f *Filter) pendingLogs() ([]*types.Log, error) { func (f *Filter) pendingLogs() []*types.Log {
block, receipts := f.sys.backend.PendingBlockAndReceipts() block, receipts := f.sys.backend.PendingBlockAndReceipts()
if block == nil { if block == nil {
return nil, errors.New("pending state not available") return nil
} }
if bloomFilter(block.Bloom(), f.addresses, f.topics) { if bloomFilter(block.Bloom(), f.addresses, f.topics) {
var unfiltered []*types.Log var unfiltered []*types.Log
for _, r := range receipts { for _, r := range receipts {
unfiltered = append(unfiltered, r.Logs...) unfiltered = append(unfiltered, r.Logs...)
} }
return filterLogs(unfiltered, nil, nil, f.addresses, f.topics), nil return filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
} }
return nil, nil return nil
} }
func includes(addresses []common.Address, a common.Address) bool { func includes(addresses []common.Address, a common.Address) bool {
......
...@@ -50,6 +50,8 @@ type testBackend struct { ...@@ -50,6 +50,8 @@ type testBackend struct {
rmLogsFeed event.Feed rmLogsFeed event.Feed
pendingLogsFeed event.Feed pendingLogsFeed event.Feed
chainFeed event.Feed chainFeed event.Feed
pendingBlock *types.Block
pendingReceipts types.Receipts
} }
func (b *testBackend) ChainConfig() *params.ChainConfig { func (b *testBackend) ChainConfig() *params.ChainConfig {
...@@ -124,7 +126,7 @@ func (b *testBackend) GetLogs(ctx context.Context, hash common.Hash, number uint ...@@ -124,7 +126,7 @@ func (b *testBackend) GetLogs(ctx context.Context, hash common.Hash, number uint
} }
func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
return nil, nil return b.pendingBlock, b.pendingReceipts
} }
func (b *testBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { func (b *testBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment