Skip to content

Commit

Permalink
emv implementation for contract reader query keys api
Browse files Browse the repository at this point in the history
  • Loading branch information
ettec committed Nov 26, 2024
1 parent 4e6d7ad commit 24a7c6d
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 0 deletions.
35 changes: 35 additions & 0 deletions core/services/relay/evm/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,41 @@ func (cr *chainReader) QueryKey(
return sequenceOfValues, nil
}

func (cr *chainReader) QueryKeys(ctx context.Context, filters []commontypes.ContractKeyFilter,
limitAndSort query.LimitAndSort) ([]commontypes.SequenceWithKey, error) {

var eventQueries []read.EventQuery
for _, filter := range filters {
binding, address, err := cr.bindings.GetReader(filter.Contract.ReadIdentifier(filter.KeyFilter.Key))
if err != nil {
return nil, err
}

sequenceDataType := filter.SequenceDataType
_, isValuePtr := filter.SequenceDataType.(*values.Value)
if isValuePtr {
sequenceDataType, err = cr.CreateContractType(filter.Contract.ReadIdentifier(filter.Key), false)
if err != nil {
return nil, err
}
}

if eventBinding, ok := binding.(*read.EventBinding); ok {
eventQueries = append(eventQueries, read.EventQuery{
Filter: filter.KeyFilter,
SequenceDataType: sequenceDataType,
IsValuePtr: isValuePtr,
EventBinding: eventBinding,
Address: common.HexToAddress(address),
})
} else {
return nil, fmt.Errorf("query key %s is not an event", filter.KeyFilter.Key)
}
}

return read.MultiEventTypeQuery(ctx, cr.lp, eventQueries, limitAndSort)
}

func (cr *chainReader) CreateContractType(readIdentifier string, forEncoding bool) (any, error) {
return cr.codec.CreateType(cr.bindings.ReadTypeIdentifier(readIdentifier, forEncoding), forEncoding)
}
Expand Down
7 changes: 7 additions & 0 deletions core/services/relay/evm/evmtesting/bindings_test_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ func WrapContractReaderTesterWithBindings(t *testing.T, wrapped *EVMChainCompone
interfacetests.ContractReaderBatchGetLatestValueSetsErrorsProperly, interfacetests.ContractReaderBatchGetLatestValueNoArgumentsWithSliceReturn, interfacetests.ContractReaderBatchGetLatestValueWithModifiersOwnMapstructureOverride,
interfacetests.ContractReaderQueryKeyNotFound, interfacetests.ContractReaderQueryKeyReturnsData, interfacetests.ContractReaderQueryKeyReturnsDataAsValuesDotValue, interfacetests.ContractReaderQueryKeyReturnsDataAsValuesDotValue,
interfacetests.ContractReaderQueryKeyCanFilterWithValueComparator, interfacetests.ContractReaderQueryKeyCanLimitResultsWithCursor,
interfacetests.ContractReaderQueryKeysNotFound, interfacetests.ContractReaderQueryKeysReturnsData, interfacetests.ContractReaderQueryKeyReturnsDataAsValuesDotValue, interfacetests.ContractReaderQueryKeysReturnsDataAsValuesDotValue,
interfacetests.ContractReaderQueryKeysCanFilterWithValueComparator, interfacetests.ContractReaderQueryKeysCanLimitResultsWithCursor,
ContractReaderQueryKeyFilterOnDataWordsWithValueComparator, ContractReaderQueryKeyOnDataWordsWithValueComparatorOnNestedField,
ContractReaderQueryKeyFilterOnDataWordsWithValueComparatorOnDynamicField, ContractReaderQueryKeyFilteringOnDataWordsUsingValueComparatorsOnFieldsWithManualIndex,
// TODO BCFR-1073 - Fix flaky tests
Expand Down Expand Up @@ -71,6 +73,7 @@ func newBindingsMapping() bindingsMapping {
interfacetests.MethodSettingStruct: "AddTestStruct",
interfacetests.MethodSettingUint64: "SetAlterablePrimitiveValue",
interfacetests.MethodTriggeringEvent: "TriggerEvent",
interfacetests.MethodTriggeringEventWithDynamicTopic: "TriggerEventWithDynamicTopic",
}
methodNameMappingByContract[interfacetests.AnySecondContractName] = map[string]string{
interfacetests.MethodReturningUint64: "GetDifferentPrimitiveValue",
Expand Down Expand Up @@ -249,6 +252,10 @@ func (b bindingChainWriterProxy) SubmitTransaction(ctx context.Context, contract
bindingsInput := bindings.TriggerEventInput{}
_ = convertStruct(args, &bindingsInput)
return chainReaderTesters.TriggerEvent(ctx, bindingsInput, transactionID, toAddress, meta)
case interfacetests.MethodTriggeringEventWithDynamicTopic:
bindingsInput := bindings.TriggerEventWithDynamicTopicInput{}
_ = convertStruct(args, &bindingsInput)
return chainReaderTesters.TriggerEventWithDynamicTopic(ctx, bindingsInput, transactionID, toAddress, meta)
default:
return errors.New("No logic implemented for method: " + method)
}
Expand Down
155 changes: 155 additions & 0 deletions core/services/relay/evm/read/multieventtype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package read

import (
"context"
"fmt"
"reflect"
"strings"

"github.com/ethereum/go-ethereum/common"

commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/smartcontractkit/chainlink-common/pkg/types/query"
"github.com/smartcontractkit/chainlink-common/pkg/values"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller"
)

type EventQuery struct {
Filter query.KeyFilter
EventBinding *EventBinding
SequenceDataType any
IsValuePtr bool
Address common.Address
}

func MultiEventTypeQuery(ctx context.Context, lp logpoller.LogPoller, eventQueries []EventQuery, limitAndSort query.LimitAndSort) (sequences []commontypes.SequenceWithKey, err error) {

Check failure on line 25 in core/services/relay/evm/read/multieventtype.go

View workflow job for this annotation

GitHub Actions / split-amd64

undefined: commontypes.SequenceWithKey

Check failure on line 25 in core/services/relay/evm/read/multieventtype.go

View workflow job for this annotation

GitHub Actions / split-arm64

undefined: commontypes.SequenceWithKey

defer func() {
if err != nil {
if len(eventQueries) > 0 {
// TODO need to figure out what would be a sensible way to capture the error for all the queries
eq := eventQueries[0]

err = newErrorFromCall(err, Call{
ContractAddress: eq.Address,
ContractName: eq.EventBinding.contractName,
ReadName: eq.EventBinding.eventName,
ReturnVal: eq.SequenceDataType,
}, "", eventReadType)
} else {
err = fmt.Errorf("no event queries provided: %v", err)
}
}
}()

for _, eq := range eventQueries {
if err = eq.EventBinding.validateBound(eq.Address); err != nil {
return nil, err
}
}

var allFilterExpressions []query.Expression
for _, eq := range eventQueries {
var expressions []query.Expression

defaultExpressions := []query.Expression{
logpoller.NewAddressFilter(eq.Address),
logpoller.NewEventSigFilter(eq.EventBinding.hash),
}
expressions = append(expressions, defaultExpressions...)

remapped, err := eq.EventBinding.remap(eq.Filter)
if err != nil {
return nil, fmt.Errorf("error remapping filter: %v", err)
}
expressions = append(expressions, remapped.Expressions...)

filterExpression := query.And(expressions...)

allFilterExpressions = append(allFilterExpressions, filterExpression)
}

eventQuery := query.Or(allFilterExpressions...)

queryName := ""
for _, eq := range eventQueries {
queryName += eq.EventBinding.contractName + "-" + eq.Address.String() + "-" + eq.EventBinding.eventName + "-"
}
strings.TrimSuffix(queryName, "-")

logs, err := lp.FilteredLogs(ctx, []query.Expression{eventQuery}, limitAndSort, queryName)
if err != nil {
return nil, wrapInternalErr(err)
}

// no need to return an error. an empty list is fine
if len(logs) == 0 {
return []commontypes.SequenceWithKey{}, nil

Check failure on line 87 in core/services/relay/evm/read/multieventtype.go

View workflow job for this annotation

GitHub Actions / split-amd64

undefined: commontypes.SequenceWithKey

Check failure on line 87 in core/services/relay/evm/read/multieventtype.go

View workflow job for this annotation

GitHub Actions / split-arm64

undefined: commontypes.SequenceWithKey
}

sequences, err = decodeMultiEventTypeLogsIntoSequences(ctx, logs, eventQueries)
if err != nil {
return nil, err
}

return sequences, nil
}

func decodeMultiEventTypeLogsIntoSequences(ctx context.Context, logs []logpoller.Log, eventQueries []EventQuery) ([]commontypes.SequenceWithKey, error) {

Check failure on line 98 in core/services/relay/evm/read/multieventtype.go

View workflow job for this annotation

GitHub Actions / split-amd64

undefined: commontypes.SequenceWithKey

Check failure on line 98 in core/services/relay/evm/read/multieventtype.go

View workflow job for this annotation

GitHub Actions / split-arm64

undefined: commontypes.SequenceWithKey
sequences := make([]commontypes.SequenceWithKey, len(logs))

Check failure on line 99 in core/services/relay/evm/read/multieventtype.go

View workflow job for this annotation

GitHub Actions / split-amd64

undefined: commontypes.SequenceWithKey

Check failure on line 99 in core/services/relay/evm/read/multieventtype.go

View workflow job for this annotation

GitHub Actions / split-arm64

undefined: commontypes.SequenceWithKey

eventSigToEventQuery := map[common.Hash]EventQuery{}
for _, eq := range eventQueries {
eventSigToEventQuery[eq.EventBinding.hash] = eq
}

for idx := range logs {

logEntry := logs[idx]
eventSignatureHash := logEntry.EventSig

eq, exists := eventSigToEventQuery[eventSignatureHash]
if !exists {
return nil, fmt.Errorf("no event query found for log with event signature %s", eventSignatureHash)
}

sequences[idx] = commontypes.SequenceWithKey{

Check failure on line 116 in core/services/relay/evm/read/multieventtype.go

View workflow job for this annotation

GitHub Actions / split-amd64

undefined: commontypes.SequenceWithKey

Check failure on line 116 in core/services/relay/evm/read/multieventtype.go

View workflow job for this annotation

GitHub Actions / split-arm64

undefined: commontypes.SequenceWithKey
Key: eq.Filter.Key,
Sequence: commontypes.Sequence{
Cursor: logpoller.FormatContractReaderCursor(logEntry),
Head: commontypes.Head{
Height: fmt.Sprint(logEntry.BlockNumber),
Hash: logEntry.BlockHash.Bytes(),
Timestamp: uint64(logEntry.BlockTimestamp.Unix()),
},
},
}

var typeVal reflect.Value

typeInto := reflect.TypeOf(eq.SequenceDataType)
if typeInto.Kind() == reflect.Pointer {
typeVal = reflect.New(typeInto.Elem())
} else {
typeVal = reflect.Indirect(reflect.New(typeInto))
}

// create a new value of the same type as 'into' for the data to be extracted to
sequences[idx].Data = typeVal.Interface()

if err := eq.EventBinding.decodeLog(ctx, &logs[idx], sequences[idx].Data); err != nil {
return nil, err
}

if eq.IsValuePtr {
wrappedValue, err := values.Wrap(sequences[idx].Data)
if err != nil {
return nil, err
}
sequences[idx].Data = &wrappedValue
}

}

return sequences, nil
}

0 comments on commit 24a7c6d

Please sign in to comment.