Skip to content

Commit

Permalink
Inital commit for log poller filters in config
Browse files Browse the repository at this point in the history
  • Loading branch information
ilija42 committed May 17, 2024
1 parent c82399e commit aec73ec
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 43 deletions.
18 changes: 12 additions & 6 deletions core/services/relay/evm/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ import (
"fmt"

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

// key is contract name
type contractBindings map[string]readBindings

// key is read name
type readBindings map[string]readBinding
type readBindings struct {
// contractFilter is used to filter over all events or any subset of events with same filtering parameters.
// if an event is present in the contract filter, it can't define its own filter in the event binding.
contractFilter logpoller.Filter
// key is read name
bindings map[string]readBinding
}

func (b contractBindings) GetReadBinding(contractName, readName string) (readBinding, error) {
rb, rbExists := b[contractName]
if !rbExists {
return nil, fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidType, contractName)
}

reader, readerExists := rb[readName]
reader, readerExists := rb.bindings[readName]
if !readerExists {
return nil, fmt.Errorf("%w: no readName named %s in contract %s", commontypes.ErrInvalidType, readName, contractName)
}
Expand All @@ -32,7 +38,7 @@ func (b contractBindings) AddReadBinding(contractName, readName string, reader r
rbs = readBindings{}
b[contractName] = rbs
}
rbs[readName] = reader
rbs.bindings[readName] = reader
}

func (b contractBindings) Bind(ctx context.Context, boundContracts []commontypes.BoundContract) error {
Expand All @@ -41,7 +47,7 @@ func (b contractBindings) Bind(ctx context.Context, boundContracts []commontypes
if !rbsExist {
return fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidConfig, bc.Name)
}
for _, r := range rbs {
for _, r := range rbs.bindings {
if err := r.Bind(ctx, bc); err != nil {
return err
}
Expand All @@ -52,7 +58,7 @@ func (b contractBindings) Bind(ctx context.Context, boundContracts []commontypes

func (b contractBindings) ForEach(ctx context.Context, fn func(readBinding, context.Context) error) error {
for _, rbs := range b {
for _, rb := range rbs {
for _, rb := range rbs.bindings {
if err := fn(rb, ctx); err != nil {
return err
}
Expand Down
30 changes: 21 additions & 9 deletions core/services/relay/evm/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller"
evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/services"
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types"
Expand Down Expand Up @@ -162,9 +163,9 @@ func (cr *chainReader) addMethod(
return fmt.Errorf("%w: method %s doesn't exist", commontypes.ErrInvalidConfig, chainReaderDefinition.ChainSpecificName)
}

if len(chainReaderDefinition.EventInputFields) != 0 {
if chainReaderDefinition.EventDefinitions != nil {
return fmt.Errorf(
"%w: method %s has event topic fields defined, but is not an event",
"%w: method %s has event definition, but is not an event",
commontypes.ErrInvalidConfig,
chainReaderDefinition.ChainSpecificName)
}
Expand Down Expand Up @@ -207,24 +208,35 @@ func (cr *chainReader) addEvent(contractName, eventName string, a abi.ABI, chain
return err
}

eventDefinitions := chainReaderDefinition.EventDefinitions
pollingFilter := eventDefinitions.PollingFilter
eb := &eventBinding{
contractName: contractName,
eventName: eventName,
lp: cr.lp,
contractName: contractName,
eventName: eventName,
lp: cr.lp,
logPollerFilter: logpoller.Filter{
EventSigs: evmtypes.HashArray{event.ID},
Topic2: pollingFilter.Topic2,
Topic3: pollingFilter.Topic3,
Topic4: pollingFilter.Topic4,
Retention: pollingFilter.Retention.Duration(),
MaxLogsKept: pollingFilter.MaxLogsKept,
LogsPerBlock: pollingFilter.LogsPerBlock,
},
hash: event.ID,
inputInfo: inputInfo,
inputModifier: inputModifier,
codecTopicInfo: codecTopicInfo,
topics: make(map[string]topicDetail),
eventDataWords: chainReaderDefinition.GenericDataWordNames,
eventDataWords: eventDefinitions.GenericDataWordNames,
id: wrapItemType(contractName, eventName, false) + uuid.NewString(),
}

cr.contractBindings.AddReadBinding(contractName, eventName, eb)

// set topic mappings for QueryKeys
for topicIndex, topic := range event.Inputs {
genericTopicName, ok := chainReaderDefinition.GenericTopicNames[topic.Name]
genericTopicName, ok := eventDefinitions.GenericTopicNames[topic.Name]
if ok {
eb.topics[genericTopicName] = topicDetail{
Argument: topic,
Expand Down Expand Up @@ -262,7 +274,7 @@ func (cr *chainReader) getEventInput(def types.ChainReaderDefinition, contractNa
}

func verifyEventInputsUsed(chainReaderDefinition types.ChainReaderDefinition, indexArgNames map[string]bool) error {
for _, value := range chainReaderDefinition.EventInputFields {
for _, value := range chainReaderDefinition.EventDefinitions.InputFields {
if !indexArgNames[abi.ToCamelCase(value)] {
return fmt.Errorf("%w: %s is not an indexed argument of event %s", commontypes.ErrInvalidConfig, value, chainReaderDefinition.ChainSpecificName)
}
Expand Down Expand Up @@ -298,7 +310,7 @@ func (cr *chainReader) addDecoderDef(contractName, itemType string, outputs abi.

func setupEventInput(event abi.Event, def types.ChainReaderDefinition) ([]abi.Argument, types.CodecEntry, map[string]bool) {
topicFieldDefs := map[string]bool{}
for _, value := range def.EventInputFields {
for _, value := range def.EventDefinitions.InputFields {
capFirstValue := abi.ToCamelCase(value)
topicFieldDefs[capFirstValue] = true
}
Expand Down
36 changes: 17 additions & 19 deletions core/services/relay/evm/event_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,20 @@ import (
)

type eventBinding struct {
address common.Address
contractName string
eventName string
lp logpoller.LogPoller
hash common.Hash
codec commontypes.RemoteCodec
pending bool
bound bool
registerCalled bool
lock sync.Mutex
inputInfo types.CodecEntry
inputModifier codec.Modifier
codecTopicInfo types.CodecEntry
address common.Address
contractName string
eventName string
logPollerFilter logpoller.Filter
lp logpoller.LogPoller
hash common.Hash
codec commontypes.RemoteCodec
pending bool
bound bool
registerCalled bool
lock sync.Mutex
inputInfo types.CodecEntry
inputModifier codec.Modifier
codecTopicInfo types.CodecEntry
// topics maps a generic topic name (key) to topic data
topics map[string]topicDetail
// eventDataWords maps a generic name to a word index
Expand Down Expand Up @@ -63,11 +64,7 @@ func (e *eventBinding) Register(ctx context.Context) error {
return nil
}

if err := e.lp.RegisterFilter(ctx, logpoller.Filter{
Name: e.id,
EventSigs: evmtypes.HashArray{e.hash},
Addresses: evmtypes.AddressArray{e.address},
}); err != nil {
if err := e.lp.RegisterFilter(ctx, e.logPollerFilter); err != nil {
return fmt.Errorf("%w: %w", commontypes.ErrInternal, err)
}
return nil
Expand Down Expand Up @@ -140,7 +137,8 @@ func (e *eventBinding) Bind(ctx context.Context, binding commontypes.BoundContra
}

e.address = common.HexToAddress(binding.Address)
e.pending = binding.Pending
e.logPollerFilter.Name = logpoller.FilterName(e.id, e.address)
e.logPollerFilter.Addresses = evmtypes.AddressArray{e.address}
e.bound = true

if e.registerCalled {
Expand Down
41 changes: 32 additions & 9 deletions core/services/relay/evm/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/codec"
"github.com/smartcontractkit/chainlink-common/pkg/services"
"github.com/smartcontractkit/chainlink-common/pkg/types"
evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types"
"github.com/smartcontractkit/chainlink/v2/core/store/models"

"github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big"
)
Expand All @@ -37,12 +39,26 @@ type ChainCodecConfig struct {

type ChainContractReader struct {
ContractABI string `json:"contractABI" toml:"contractABI"`

ContractPollingFilter ContractPollingFilter `json:"contractPollingFilter"`
// key is genericName from config
Configs map[string]*ChainReaderDefinition `json:"configs" toml:"configs"`
}

type ChainReaderDefinition chainReaderDefinitionFields

type EventDefinitions struct {
PollingFilter PollingFilter `json:"pollingFilter"`
// GenericTopicNames helps QueryingKeys not rely on EVM specific topic names. Key is chain specific name, value is generic name.
// This helps us translate chain agnostic querying key "transfer-value" to EVM specific "evmTransferEvent-weiAmountTopic".
GenericTopicNames map[string]string `json:"genericTopicNames,omitempty"`
// key is a predefined generic name for evm log event data word
// for eg. first evm data word(32bytes) of USDC log event is value so the key can be called value
GenericDataWordNames map[string]uint8 `json:"genericDataWordNames,omitempty"`
// InputFields allows you to choose which indexed fields are expected from the input
InputFields []string `json:"inputFields,omitempty"`
}

// chainReaderDefinitionFields has the fields for ChainReaderDefinition but no methods.
// This is necessary because package json recognizes the text encoding methods used for TOML,
// and would infinitely recurse on itself.
Expand All @@ -53,15 +69,7 @@ type chainReaderDefinitionFields struct {
ReadType ReadType `json:"readType,omitempty"`
InputModifications codec.ModifiersConfig `json:"inputModifications,omitempty"`
OutputModifications codec.ModifiersConfig `json:"outputModifications,omitempty"`

// EventInputFields allows you to choose which indexed fields are expected from the input
EventInputFields []string `json:"eventInputFields,omitempty"`
// GenericTopicNames helps QueryingKeys not rely on EVM specific topic names. Key is chain specific name, value is generic name.
// This helps us translate chain agnostic querying key "transfer-value" to EVM specific "evmTransferEvent-weiAmountTopic".
GenericTopicNames map[string]string `json:"genericTopicNames,omitempty"`
// key is a predefined generic name for evm log event data word
// for eg. first evm data word(32bytes) of USDC log event is value so the key can be called value
GenericDataWordNames map[string]uint8 `json:"genericDataWordNames,omitempty"`
EventDefinitions *EventDefinitions `json:"eventDefinitions,omitempty"`
}

func (d *ChainReaderDefinition) MarshalText() ([]byte, error) {
Expand Down Expand Up @@ -111,6 +119,21 @@ func (r *ReadType) UnmarshalText(text []byte) error {
return fmt.Errorf("unrecognized ReadType: %s", string(text))
}

type PollingFilter struct {
Topic2 evmtypes.HashArray `json:"topic2"` // list of possible values for topic2
Topic3 evmtypes.HashArray `json:"topic3"` // list of possible values for topic3
Topic4 evmtypes.HashArray `json:"topic4"` // list of possible values for topic4
Retention models.Interval `json:"retention"` // maximum amount of time to retain logs
MaxLogsKept uint64 `json:"maxLogsKept"` // maximum number of logs to retain ( 0 = unlimited )
LogsPerBlock uint64 `json:"logsPerBlock"` // rate limit ( maximum # of logs per block, 0 = unlimited )
}

type ContractPollingFilter struct {
EventKeys []string `json:"eventKeys"` // list of possible values for eventsig (aka topic1)
// contract wide polling filter
PollingFilter PollingFilter
}

type RelayConfig struct {
ChainID *big.Big `json:"chainID"`
FromBlock uint64 `json:"fromBlock"`
Expand Down

0 comments on commit aec73ec

Please sign in to comment.