Skip to content

Commit

Permalink
error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ettec committed Dec 6, 2024
1 parent f6f2457 commit f5cd1f9
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 0 deletions.
36 changes: 36 additions & 0 deletions core/services/relay/evm/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"iter"
"maps"
"slices"
"strings"
Expand Down Expand Up @@ -298,6 +299,41 @@ func (cr *chainReader) QueryKey(
return sequenceOfValues, nil
}

func (cr *chainReader) QueryKeys(ctx context.Context, filters []commontypes.ContractKeyFilter,

Check failure on line 302 in core/services/relay/evm/chain_reader.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_fuzz)

undefined: commontypes.ContractKeyFilter

Check failure on line 302 in core/services/relay/evm/chain_reader.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_race_tests)

undefined: commontypes.ContractKeyFilter

Check failure on line 302 in core/services/relay/evm/chain_reader.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests)

undefined: commontypes.ContractKeyFilter

Check failure on line 302 in core/services/relay/evm/chain_reader.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_ccip_deployment_tests)

undefined: commontypes.ContractKeyFilter

Check failure on line 302 in core/services/relay/evm/chain_reader.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests_integration)

undefined: commontypes.ContractKeyFilter

Check failure on line 302 in core/services/relay/evm/chain_reader.go

View workflow job for this annotation

GitHub Actions / split-arm64

undefined: commontypes.ContractKeyFilter

Check failure on line 302 in core/services/relay/evm/chain_reader.go

View workflow job for this annotation

GitHub Actions / lint

undefined: commontypes.ContractKeyFilter
limitAndSort query.LimitAndSort) (iter.Seq2[string, commontypes.Sequence], error) {
eventQueries := make([]read.EventQuery, 0, len(filters))
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
}
}

eventBinding, ok := binding.(*read.EventBinding)
if !ok {
return nil, fmt.Errorf("query key %s is not an event", filter.KeyFilter.Key)
}

eventQueries = append(eventQueries, read.EventQuery{
Filter: filter.KeyFilter,
SequenceDataType: sequenceDataType,
IsValuePtr: isValuePtr,
EventBinding: eventBinding,
Address: common.HexToAddress(address),
})
}

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.ContractReaderQueryKeysReturnsDataTwoEventTypes, 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
52 changes: 52 additions & 0 deletions core/services/relay/evm/read/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,58 @@ func (e Error) Unwrap() error {
return e.Err
}

type MultiCallError struct {
Err error
Type readType
Detail *callsReadDetail
Result *string
}

type callsReadDetail struct {
Calls []Call
Block string
}

func newErrorFromCalls(err error, calls []Call, block string, tp readType) MultiCallError {
return MultiCallError{
Err: err,
Type: tp,
Detail: &callsReadDetail{
Calls: calls,
Block: block,
},
}
}

func (e MultiCallError) Error() string {
var builder strings.Builder

builder.WriteString("[read error]")
builder.WriteString(fmt.Sprintf(" err: %s;", e.Err.Error()))
builder.WriteString(fmt.Sprintf(" type: %s;", e.Type))

if e.Detail != nil {
builder.WriteString(fmt.Sprintf(" block: %s;", e.Detail.Block))
for _, call := range e.Detail.Calls {
builder.WriteString(fmt.Sprintf(" address: %s;", call.ContractAddress.Hex()))
builder.WriteString(fmt.Sprintf(" contract-name: %s;", call.ContractName))
builder.WriteString(fmt.Sprintf(" read-name: %s;", call.ReadName))
builder.WriteString(fmt.Sprintf(" params: %+v;", call.Params))
builder.WriteString(fmt.Sprintf(" expected return type: %s;", reflect.TypeOf(call.ReturnVal)))
}

if e.Result != nil {
builder.WriteString(fmt.Sprintf("encoded result: %s;", *e.Result))
}
}

return builder.String()
}

func (e MultiCallError) Unwrap() error {
return e.Err
}

type ConfigError struct {
Msg string
}
Expand Down
156 changes: 156 additions & 0 deletions core/services/relay/evm/read/multieventtype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package read

import (
"context"
"fmt"
"iter"
"reflect"
"strconv"
"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 iter.Seq2[string, commontypes.Sequence], err error) {
defer func() {
if err != nil {
if len(eventQueries) > 0 {
var calls []Call
for _, eq := range eventQueries {
calls = append(calls, Call{
ContractAddress: eq.Address,
ContractName: eq.EventBinding.contractName,
ReadName: eq.EventBinding.eventName,
ReturnVal: eq.SequenceDataType,
})
}

err = newErrorFromCalls(err, calls, "", eventReadType)
} else {
err = fmt.Errorf("no event queries provided: %w", err)
}
}
}()

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

allFilterExpressions := make([]query.Expression, 0, len(eventQueries))
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, remapErr := eq.EventBinding.remap(eq.Filter)
if remapErr != nil {
return nil, fmt.Errorf("error remapping filter: %w", 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 + "-"
}
queryName = strings.TrimSuffix(queryName, "-")

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

return decodeMultiEventTypeLogsIntoSequences(ctx, logs, eventQueries)
}

func decodeMultiEventTypeLogsIntoSequences(ctx context.Context, logs []logpoller.Log, eventQueries []EventQuery) (iter.Seq2[string, commontypes.Sequence], error) {
type sequenceWithKey struct {
Key string
Sequence commontypes.Sequence
}
sequenceWithKeys := make([]sequenceWithKey, 0, len(logs))
eventSigToEventQuery := map[common.Hash]EventQuery{}
for _, eq := range eventQueries {
eventSigToEventQuery[eq.EventBinding.hash] = eq
}

for _, logEntry := range logs {
eventSignatureHash := logEntry.EventSig

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

seqWithKey := sequenceWithKey{
Key: eq.Filter.Key,
Sequence: commontypes.Sequence{
Cursor: logpoller.FormatContractReaderCursor(logEntry),
Head: commontypes.Head{
Height: strconv.FormatInt(logEntry.BlockNumber, 10),
Hash: logEntry.BlockHash.Bytes(),
Timestamp: uint64(logEntry.BlockTimestamp.Unix()), //nolint:gosec // G115 false positive
},
},
}

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
seqWithKey.Sequence.Data = typeVal.Interface()

if err := eq.EventBinding.decodeLog(ctx, &logEntry, seqWithKey.Sequence.Data); err != nil {
return nil, err
}

if eq.IsValuePtr {
wrappedValue, err := values.Wrap(seqWithKey.Sequence.Data)
if err != nil {
return nil, err
}
seqWithKey.Sequence.Data = &wrappedValue
}

sequenceWithKeys = append(sequenceWithKeys, seqWithKey)
}

return func(yield func(string, commontypes.Sequence) bool) {
for _, s := range sequenceWithKeys {
if !yield(s.Key, s.Sequence) {
return
}
}
}, nil
}

0 comments on commit f5cd1f9

Please sign in to comment.