Skip to content
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

[BCI-3863] - Use filtered logs in eventBinding GetLatestValue instead of manual filtering #14096

Merged
merged 9 commits into from
Aug 14, 2024
5 changes: 5 additions & 0 deletions .changeset/early-glasses-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

use FilteredLogs in EventBinding GetLatestValue instead of manual filtering. #internal
80 changes: 41 additions & 39 deletions core/services/relay/evm/event_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,32 +227,56 @@ func (e *eventBinding) getLatestValueWithFilters(
return err
}

fai := filtersAndIndices[0]
remainingFilters := filtersAndIndices[1:]

logs, err := e.lp.IndexedLogs(ctx, e.hash, e.address, 1, []common.Hash{fai}, confs)
// Create limiter and filter for the query.
limiter := query.NewLimitAndSort(query.CountLimit(1), query.NewSortBySequence(query.Desc))
ilija42 marked this conversation as resolved.
Show resolved Hide resolved
filter, err := query.Where(
e.address.String(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Passing the address here isn't necessary and can be left empty. Since you're within the evm relay package here, you're able to use the logpoller filters directly and no key -> filter mappings are necessary. Putting an actual value here might just cause confusion later on.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

logpoller.NewAddressFilter(e.address),
logpoller.NewEventSigFilter(e.hash),
logpoller.NewConfirmationsFilter(confs),
createTopicFilters(filtersAndIndices),
)
if err != nil {
return wrapInternalErr(err)
}

// TODO Use filtered logs here BCF-3316
// TODO: there should be a better way to ask log poller to filter these
// First, you should be able to ask for as many topics to match
// Second, you should be able to get the latest only
var logToUse *logpoller.Log
for _, log := range logs {
tmp := log
if compareLogs(&tmp, logToUse) > 0 && matchesRemainingFilters(&tmp, remainingFilters) {
// copy so that it's not pointing to the changing variable
logToUse = &tmp
}
// Gets the latest log that matches the filter and limiter.
logs, err := e.lp.FilteredLogs(ctx, filter, limiter, e.contractName+"-"+e.eventName)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

should I add address to the queryName too?

Copy link
Contributor

Choose a reason for hiding this comment

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

don't see why not , CC @EasterTheBunny

Copy link
Contributor

Choose a reason for hiding this comment

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

Using queryName here might be helpful. The idea is that the string passed will show in slow query logs and help identify problems in the log file.

if err != nil {
return wrapInternalErr(err)
}

if logToUse == nil {
if len(logs) == 0 {
return fmt.Errorf("%w: no events found", commontypes.ErrNotFound)
}

return e.decodeLog(ctx, logToUse, into)
return e.decodeLog(ctx, &logs[0], into)
}

func createTopicFilters(filtersAndIndices []common.Hash) query.Expression {
// If there's only one filter, return a simple expression without a boolean combination.
if len(filtersAndIndices) == 1 {
value := filtersAndIndices[0]
return logpoller.NewEventByTopicFilter(1, []primitives.ValueComparator{
{Value: value.Hex(), Operator: primitives.Eq},
})
}

// For multiple filters, create a boolean expression.
topicFilters := query.BoolExpression{
Expressions: make([]query.Expression, len(filtersAndIndices)),
BoolOperator: query.AND,
ilija42 marked this conversation as resolved.
Show resolved Hide resolved
}

// Every index represents a topic, and the underlying value represents what we want to match.
for idx, value := range filtersAndIndices {
// The topic index is 1-based, so we add 1.
topicFilters.Expressions[idx] = logpoller.NewEventByTopicFilter(uint64(idx)+1, []primitives.ValueComparator{
{Value: value.Hex(), Operator: primitives.Eq},
ilija42 marked this conversation as resolved.
Show resolved Hide resolved
})
}

return query.Expression{BoolExpression: topicFilters}
ilija42 marked this conversation as resolved.
Show resolved Hide resolved
}

// convertToOffChainType creates a struct based on contract abi with applied codec modifiers.
Expand All @@ -270,28 +294,6 @@ func (e *eventBinding) convertToOffChainType(params any) (any, error) {
return offChain, nil
}

func compareLogs(log, use *logpoller.Log) int64 {
if use == nil {
return 1
}

if log.BlockNumber != use.BlockNumber {
return log.BlockNumber - use.BlockNumber
}

return log.LogIndex - use.LogIndex
}

func matchesRemainingFilters(log *logpoller.Log, filters []common.Hash) bool {
for i, rfai := range filters {
if !reflect.DeepEqual(rfai[:], log.Topics[i+2]) {
return false
}
}

return true
}

// encodeParams accepts nativeParams and encodes them to match onchain topics.
func (e *eventBinding) encodeParams(nativeParams reflect.Value) ([]common.Hash, error) {
for nativeParams.Kind() == reflect.Pointer {
Expand Down
Loading