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
9 changes: 2 additions & 7 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,11 +581,6 @@ func (b *BlockChainAPI) GetLogs(
return nil, err
}

filter := logs.FilterCriteria{
Addresses: criteria.Addresses,
Topics: criteria.Topics,
}

// if filter provided specific block ID
if criteria.BlockHash != nil {
// Check if the block exists, and return an error if not.
Expand All @@ -598,7 +593,7 @@ func (b *BlockChainAPI) GetLogs(
return []*types.Log{}, nil
}

f, err := logs.NewIDFilter(*criteria.BlockHash, filter, b.blocks, b.receipts)
f, err := logs.NewIDFilter(criteria, b.blocks, b.receipts)
if err != nil {
return handleError[[]*types.Log](err, l, b.collector)
}
Expand Down Expand Up @@ -641,7 +636,7 @@ func (b *BlockChainAPI) GetLogs(
to = latest
}

f, err := logs.NewRangeFilter(from.Uint64(), to.Uint64(), filter, b.receipts)
f, err := logs.NewRangeFilter(from.Uint64(), to.Uint64(), criteria, b.receipts)
if err != nil {
return handleError[[]*types.Log](err, l, b.collector)
}
Expand Down
12 changes: 4 additions & 8 deletions api/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ func newTransactionsFilter(expiry time.Duration, latestHeight uint64, fullTx boo
// Criteria parameter filters the logs according to the criteria values.
type logsFilter struct {
*baseFilter
criteria *filters.FilterCriteria
criteria filters.FilterCriteria
}

func newLogsFilter(
expiry time.Duration,
latestHeight uint64,
criteria *filters.FilterCriteria,
criteria filters.FilterCriteria,
) *logsFilter {
return &logsFilter{
newBaseFilter(expiry, latestHeight),
Expand Down Expand Up @@ -272,7 +272,7 @@ func (api *PullAPI) NewFilter(ctx context.Context, criteria filters.FilterCriter
// todo we should check for max range of from-to heights
}

f := newLogsFilter(api.config.FilterExpiry, latest, &criteria)
f := newLogsFilter(api.config.FilterExpiry, latest, criteria)

api.logger.Debug().
Str("id", string(f.id())).
Expand Down Expand Up @@ -483,10 +483,6 @@ func (api *PullAPI) getTransactions(latestHeight uint64, filter *transactionsFil

func (api *PullAPI) getLogs(latestHeight uint64, filter *logsFilter) (any, error) {
nextHeight := filter.next()
criteria := logs.FilterCriteria{
Addresses: filter.criteria.Addresses,
Topics: filter.criteria.Topics,
}

to := filter.criteria.ToBlock
// we use latest as default for end range
Expand All @@ -506,7 +502,7 @@ func (api *PullAPI) getLogs(latestHeight uint64, filter *logsFilter) (any, error
return []*gethTypes.Log{}, nil
}

f, err := logs.NewRangeFilter(start, end, criteria, api.receipts)
f, err := logs.NewRangeFilter(start, end, filter.criteria, api.receipts)
if err != nil {
return nil, fmt.Errorf("could not create range filter from %d to %d: %w", start, end, err)
}
Expand Down
7 changes: 1 addition & 6 deletions api/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,6 @@ func (s *StreamAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) (*

// Logs creates a subscription that fires for all new log that match the given filter criteria.
func (s *StreamAPI) Logs(ctx context.Context, criteria filters.FilterCriteria) (*rpc.Subscription, error) {
logCriteria, err := logs.NewFilterCriteria(criteria.Addresses, criteria.Topics)
if err != nil {
return nil, fmt.Errorf("failed to create log subscription filter: %w", err)
}

return newSubscription(
ctx,
s.logger,
Expand All @@ -106,7 +101,7 @@ func (s *StreamAPI) Logs(ctx context.Context, criteria filters.FilterCriteria) (
for _, log := range allLogs {
// todo we could optimize this matching for cases where we have multiple subscriptions
// using the same filter criteria, we could only filter once and stream to all subscribers
if !logs.ExactMatch(log, logCriteria) {
if !logs.ExactMatch(log, criteria) {
continue
}

Expand Down
61 changes: 12 additions & 49 deletions services/logs/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,22 @@ import (
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/go-ethereum/common"
gethTypes "github.com/onflow/go-ethereum/core/types"
"github.com/onflow/go-ethereum/eth/filters"
)

// The maximum number of topic criteria allowed
const maxTopics = 4

// The maximum number of addresses allowed
const maxAddresses = 6

// FilterCriteria for log filtering.
// Address of the contract emitting the log.
// Topics that match the log topics, following the format:
// [] “anything”
// [A] “A in first position (and anything after)”
// [null, B] “anything in first position AND B in second position (and anything after)”
// [A, B] “A in first position AND B in second position (and anything after)”
// [[A, B], [A, B]] “(A OR B) in first position AND (A OR B) in second position (and anything after)”
type FilterCriteria struct {
Addresses []common.Address
Topics [][]common.Hash
}

func NewFilterCriteria(addresses []common.Address, topics [][]common.Hash) (*FilterCriteria, error) {
if len(topics) > maxTopics {
return nil, fmt.Errorf("max topics exceeded, only %d allowed, got %d", maxTopics, len(topics))
}
if len(addresses) > maxAddresses {
return nil, fmt.Errorf("max addresses exceeded, only %d allowed, got %d", maxAddresses, len(addresses))
}
Comment on lines -33 to -38

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these limits enforced somewhere else?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly, that's the main idea behind this PR.

The FilterCriteria type from Geth, enforces the validation logic inside the UnmarshalJSON method.

More specifically:

Whenever a JSON-RPC call, accepts a parameter of FilterCriteria, for example:

// GetLogs returns logs matching the given argument that are stored within the state.
func (b *BlockChainAPI) GetLogs(
	ctx context.Context,
	criteria filters.FilterCriteria,
) ([]*types.Log, error) {

Under the hood, Geth will call UnmarshalJSON on FilterCriteria, to parse the JSON object, to the appropriate FilterCriteria object, and runs any validation logic. If the validation fails, it will return the appropriate error.


return &FilterCriteria{
Addresses: addresses,
Topics: topics,
}, nil
}

// RangeFilter matches all the indexed logs within the range defined as
// start and end block height. The start must be strictly smaller or equal than end value.
type RangeFilter struct {
start, end uint64
criteria *FilterCriteria
criteria filters.FilterCriteria
receipts storage.ReceiptIndexer
}

func NewRangeFilter(
start, end uint64,
criteria FilterCriteria,
criteria filters.FilterCriteria,
receipts storage.ReceiptIndexer,
) (*RangeFilter, error) {
if len(criteria.Topics) > maxTopics {
return nil, fmt.Errorf("max topics exceeded, only %d allowed, got %d", maxTopics, len(criteria.Topics))
}

// make sure that beginning number is not bigger than end
if start > end {
return nil, fmt.Errorf(
Expand All @@ -73,7 +37,7 @@ func NewRangeFilter(
return &RangeFilter{
start: start,
end: end,
criteria: &criteria,
criteria: criteria,
receipts: receipts,
}, nil
}
Expand Down Expand Up @@ -126,24 +90,23 @@ func (r *RangeFilter) Match() ([]*gethTypes.Log, error) {
// by the provided block ID.
type IDFilter struct {
id common.Hash
criteria *FilterCriteria
criteria filters.FilterCriteria
blocks storage.BlockIndexer
receipts storage.ReceiptIndexer
}

func NewIDFilter(
id common.Hash,
criteria FilterCriteria,
criteria filters.FilterCriteria,
blocks storage.BlockIndexer,
receipts storage.ReceiptIndexer,
) (*IDFilter, error) {
if len(criteria.Topics) > maxTopics {
return nil, fmt.Errorf("max topics exceeded, only %d allowed, got %d", maxTopics, len(criteria.Topics))
if criteria.BlockHash == nil {
return nil, fmt.Errorf("filter criteria should have a non-nil block hash")
}

return &IDFilter{
id: id,
criteria: &criteria,
id: *criteria.BlockHash,
criteria: criteria,
blocks: blocks,
receipts: receipts,
}, nil
Expand Down Expand Up @@ -173,7 +136,7 @@ func (i *IDFilter) Match() ([]*gethTypes.Log, error) {
}

// ExactMatch checks the topic and address values of the log match the filter exactly.
func ExactMatch(log *gethTypes.Log, criteria *FilterCriteria) bool {
func ExactMatch(log *gethTypes.Log, criteria filters.FilterCriteria) bool {
// check criteria doesn't have more topics than the log, but it can have less due to wildcards
if len(criteria.Topics) > len(log.Topics) {
return false
Expand Down Expand Up @@ -203,7 +166,7 @@ func ExactMatch(log *gethTypes.Log, criteria *FilterCriteria) bool {
// If true is returned we should further check against the exactMatch to really make sure the log is matched.
//
// source: https://github.com/ethereum/go-ethereum/blob/8d1db1601d3a9e4fd067558a49db6f0b879c9b48/eth/filters/filter.go#L395
func bloomMatch(bloom gethTypes.Bloom, criteria *FilterCriteria) bool {
func bloomMatch(bloom gethTypes.Bloom, criteria filters.FilterCriteria) bool {
if len(criteria.Addresses) > 0 {
var included bool
for _, addr := range criteria.Addresses {
Expand Down
Loading