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

Log Event Trigger Capability Development: Part 1 #14308

Merged
merged 45 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a93f9e9
Log Event Trigger Capability
kidambisrinivas Sep 2, 2024
072214d
Minor refactoring
kidambisrinivas Sep 2, 2024
833c0c1
Moved main script to plugins/cmd
kidambisrinivas Sep 2, 2024
73644e6
Added initial implementation for UnregisterTrigger
kidambisrinivas Sep 2, 2024
0ff783b
Merge branch 'develop' into CM-378-log-event-trigger
kidambisrinivas Sep 5, 2024
1b617a6
Create NewContractReader in RegisterTrigger flow of the trigger capab…
kidambisrinivas Sep 9, 2024
b09646b
Refactoring to integrate with ChainReader QueryKey API
kidambisrinivas Sep 10, 2024
d90d860
Merge branch 'develop' into CM-378-log-event-trigger
kidambisrinivas Sep 10, 2024
16a87f4
Merge branch 'develop' into CM-378-log-event-trigger
kidambisrinivas Sep 13, 2024
a455565
Merge branch 'develop' into CM-378-log-event-trigger
kidambisrinivas Sep 16, 2024
4e49c39
Integrate with ChainReader QueryKey interface
kidambisrinivas Sep 17, 2024
d89020e
Minor changes
kidambisrinivas Sep 17, 2024
2967430
Send cursor in QueryKey in subsequent calls
kidambisrinivas Sep 18, 2024
5e3dec0
Merge branch 'develop' into CM-378-log-event-trigger
kidambisrinivas Sep 25, 2024
75904af
Test utils for LOOP capability
kidambisrinivas Sep 25, 2024
c26d024
Happy path test for log event trigger capability
kidambisrinivas Sep 26, 2024
3880a5c
Float64 fix in values
kidambisrinivas Sep 26, 2024
60976f7
Happy path integration test for Log Event Trigger Capability
kidambisrinivas Sep 26, 2024
a4a7f0e
Fix code lint annotations
kidambisrinivas Sep 26, 2024
1cc9ce0
Addressed PR comments
kidambisrinivas Sep 27, 2024
d4c5efc
Merge branch 'develop' into CM-378-log-event-trigger
kidambisrinivas Sep 30, 2024
4a56bb5
Added changeset
kidambisrinivas Sep 30, 2024
344f6eb
Addressed Lint errors
kidambisrinivas Sep 30, 2024
3c629e4
Addressed PR comments
kidambisrinivas Sep 30, 2024
842da5a
Addressed more lint issues
kidambisrinivas Sep 30, 2024
17b6c1a
Simplified trigger ctx creation and cancel flows
kidambisrinivas Sep 30, 2024
2786cef
Added comment
kidambisrinivas Sep 30, 2024
31954ea
Addressed PR comments
kidambisrinivas Sep 30, 2024
c48b80d
Implemented Start/Close pattern in logEventTrigger and used stopChan …
kidambisrinivas Sep 30, 2024
33bed46
Addressed more PR comments
kidambisrinivas Sep 30, 2024
48c6728
Handled errors from Info and Close methods
kidambisrinivas Sep 30, 2024
3be3488
Fixed lint errors and pass ctx to Info
kidambisrinivas Sep 30, 2024
de4485c
Handle race conditions in log event trigger service
kidambisrinivas Sep 30, 2024
ecf40eb
Fixed lint errors
kidambisrinivas Sep 30, 2024
6e452f7
Merge branch 'develop' into CM-378-log-event-trigger
kidambisrinivas Sep 30, 2024
e23f0d1
Minor change
kidambisrinivas Sep 30, 2024
017c1f7
Test fix and lint fixes
kidambisrinivas Sep 30, 2024
d70aff4
Move EVM specific tests out of chain-agnostic capability
kidambisrinivas Sep 30, 2024
97e18b8
Set block time
kidambisrinivas Sep 30, 2024
7f316b0
Merge branch 'develop' into CM-378-log-event-trigger
kidambisrinivas Sep 30, 2024
723b872
Check existence of trigger in slow path
kidambisrinivas Sep 30, 2024
9b6d66b
Complete usage of services.Service with StartOnce and StopOnce with t…
kidambisrinivas Sep 30, 2024
ecd045e
Wait for all goroutines to exit in test
kidambisrinivas Sep 30, 2024
7562bc1
Merge branch 'develop' into CM-378-log-event-trigger
kidambisrinivas Sep 30, 2024
cd33c86
Cleanup logpoller and headtracker after test
kidambisrinivas Sep 30, 2024
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
5 changes: 5 additions & 0 deletions .changeset/chilly-crews-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

#added log-event-trigger LOOPP capability, using ChainReader
118 changes: 118 additions & 0 deletions core/capabilities/testutils/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package testutils

import (
"context"
"encoding/json"
"math/big"
"testing"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/test-go/testify/require"

Check failure on line 15 in core/capabilities/testutils/backend.go

View workflow job for this annotation

GitHub Actions / lint

import 'github.com/test-go/testify/require' is not allowed from list 'main': Use github.com/stretchr/testify/require instead (depguard)

"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
ettec marked this conversation as resolved.
Show resolved Hide resolved
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller"
kidambisrinivas marked this conversation as resolved.
Show resolved Hide resolved
"github.com/smartcontractkit/chainlink/v2/core/internal/cltest"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey"
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm"
evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types"
)

// Test harness with EVM backend and chainlink core services like
// Log Poller and Head Tracker
type EVMBackendTH struct {
// Backend details
Lggr logger.Logger
ChainID *big.Int
Backend *backends.SimulatedBackend
EVMClient evmclient.Client

ContractsOwner *bind.TransactOpts
ContractsOwnerKey ethkey.KeyV2

HeadTracker logpoller.HeadTracker
LogPoller logpoller.LogPoller
}

// Test harness to create a simulated backend for testing a LOOPCapability
func NewEVMBackendTH(t *testing.T) *EVMBackendTH {
lggr := logger.TestLogger(t)

ownerKey := cltest.MustGenerateRandomKey(t)
contractsOwner, err := bind.NewKeyedTransactorWithChainID(ownerKey.ToEcdsaPrivKey(), testutils.SimulatedChainID)
require.NoError(t, err)

// Setup simulated go-ethereum EVM backend
genesisData := core.GenesisAlloc{
contractsOwner.From: {Balance: assets.Ether(100000).ToInt()},
}
chainID := testutils.SimulatedChainID
gasLimit := uint32(ethconfig.Defaults.Miner.GasCeil)

Check failure on line 59 in core/capabilities/testutils/backend.go

View workflow job for this annotation

GitHub Actions / lint

G115: integer overflow conversion uint64 -> uint32 (gosec)
backend := cltest.NewSimulatedBackend(t, genesisData, gasLimit)
blockTime := time.UnixMilli(int64(backend.Blockchain().CurrentHeader().Time))

Check failure on line 61 in core/capabilities/testutils/backend.go

View workflow job for this annotation

GitHub Actions / lint

G115: integer overflow conversion uint64 -> int64 (gosec)
err = backend.AdjustTime(time.Since(blockTime) - 24*time.Hour)
require.NoError(t, err)
backend.Commit()

// Setup backend client
client := evmclient.NewSimulatedBackendClient(t, backend, chainID)

th := &EVMBackendTH{
Lggr: lggr,
ChainID: chainID,
Backend: backend,
EVMClient: client,

ContractsOwner: contractsOwner,
ContractsOwnerKey: ownerKey,
}
th.HeadTracker, th.LogPoller = th.SetupCoreServices(t)

return th
}

// Setup core services like log poller and head tracker for the simulated backend
func (th *EVMBackendTH) SetupCoreServices(t *testing.T) (logpoller.HeadTracker, logpoller.LogPoller) {
db := pgtest.NewSqlxDB(t)
const finalityDepth = 2
ht := headtracker.NewSimulatedHeadTracker(th.EVMClient, false, finalityDepth)
lp := logpoller.NewLogPoller(
logpoller.NewORM(testutils.SimulatedChainID, db, th.Lggr),
th.EVMClient,
th.Lggr,
ht,
logpoller.Opts{
PollPeriod: 100 * time.Millisecond,
FinalityDepth: finalityDepth,
BackfillBatchSize: 3,
RpcBatchSize: 2,
KeepFinalizedBlocksDepth: 1000,
},
)
require.NoError(t, ht.Start(testutils.Context(t)))
require.NoError(t, lp.Start(testutils.Context(t)))
return ht, lp
}

func (th *EVMBackendTH) NewContractReader(t *testing.T, ctx context.Context, cfg []byte) (types.ContractReader, error) {

Check failure on line 106 in core/capabilities/testutils/backend.go

View workflow job for this annotation

GitHub Actions / lint

context-as-argument: context.Context should be the first parameter of a function (revive)
crCfg := &evmrelaytypes.ChainReaderConfig{}
if err := json.Unmarshal(cfg, crCfg); err != nil {
return nil, err
}

svc, err := evm.NewChainReaderService(ctx, th.Lggr, th.LogPoller, th.HeadTracker, th.EVMClient, *crCfg)
if err != nil {
return nil, err
}

return svc, svc.Start(ctx)
}
178 changes: 178 additions & 0 deletions core/capabilities/testutils/chain_reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package testutils

import (
"encoding/json"
"fmt"
"math/big"
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
commoncaps "github.com/smartcontractkit/chainlink-common/pkg/capabilities"
commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
commonvalues "github.com/smartcontractkit/chainlink-common/pkg/values"
"github.com/test-go/testify/require"

"github.com/smartcontractkit/chainlink/v2/core/capabilities/triggers/logevent"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_emitter"
coretestutils "github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/logger"
evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types"
)

// Test harness with EVM backend and chainlink core services like
// Log Poller and Head Tracker
type ContractReaderTH struct {
BackendTH *EVMBackendTH

LogEmitterAddress *common.Address
LogEmitterContract *log_emitter.LogEmitter
LogEmitterContractReader commontypes.ContractReader
LogEmitterRegRequest commoncaps.TriggerRegistrationRequest
LogEmitterContractReaderCfg []byte
}

// Creates a new test harness for Contract Reader tests
func NewContractReaderTH(t *testing.T) *ContractReaderTH {
backendTH := NewEVMBackendTH(t)

// Deploy a test contract LogEmitter for testing ContractReader
logEmitterAddress, _, _, err :=
log_emitter.DeployLogEmitter(backendTH.ContractsOwner, backendTH.Backend)
require.NoError(t, err)
logEmitter, err := log_emitter.NewLogEmitter(logEmitterAddress, backendTH.Backend)
require.NoError(t, err)

// Create new contract reader
reqConfig := logevent.RequestConfig{
ContractName: "LogEmitter",
ContractAddress: logEmitterAddress.Hex(),
ContractEventName: "Log1",
}
contractReaderCfg := evmtypes.ChainReaderConfig{
Contracts: map[string]evmtypes.ChainContractReader{
reqConfig.ContractName: {
ContractPollingFilter: evmtypes.ContractPollingFilter{
GenericEventNames: []string{reqConfig.ContractEventName},
},
ContractABI: log_emitter.LogEmitterABI,
Configs: map[string]*evmtypes.ChainReaderDefinition{
reqConfig.ContractEventName: {
ChainSpecificName: reqConfig.ContractEventName,
ReadType: evmtypes.Event,
},
},
},
},
}

// Encode contractReaderConfig as JSON and decode it into a map[string]any for
// the capability request config. Log Event Trigger capability takes in a
// []byte as ContractReaderConfig to not depend on evm ChainReaderConfig type
// and be chain agnostic
contractReaderCfgBytes, err := json.Marshal(contractReaderCfg)
require.NoError(t, err)
contractReaderCfgMap := make(map[string]any)
err = json.Unmarshal(contractReaderCfgBytes, &contractReaderCfgMap)
require.NoError(t, err)
// Encode the config map as JSON to specify in the expected call in mocked object
// The LogEventTrigger Capability receives a config map, encodes it and
// calls NewContractReader with it
contractReaderCfgBytes, _ = json.Marshal(contractReaderCfgMap)

reqConfig.ContractReaderConfig = contractReaderCfgMap

config, err := commonvalues.WrapMap(reqConfig)
require.NoError(t, err)
req := commoncaps.TriggerRegistrationRequest{
TriggerID: "logeventtrigger_log1",
Config: config,
Metadata: commoncaps.RequestMetadata{
ReferenceID: "logeventtrigger",
},
}

// Create a new contract reader to return from mock relayer
ctx := coretestutils.Context(t)
contractReader, err := backendTH.NewContractReader(t, ctx, contractReaderCfgBytes)
require.NoError(t, err)

return &ContractReaderTH{
BackendTH: backendTH,

LogEmitterAddress: &logEmitterAddress,
LogEmitterContract: logEmitter,
LogEmitterContractReader: contractReader,
LogEmitterRegRequest: req,
LogEmitterContractReaderCfg: contractReaderCfgBytes,
}
}

// Wait for a specific log to be emitted to a response channel by ChainReader
func WaitForLog(lggr logger.Logger, logCh <-chan commoncaps.TriggerResponse, timeout time.Duration) (
*commoncaps.TriggerResponse, map[string]any, error) {
select {
case <-time.After(timeout):
return nil, nil, fmt.Errorf("timeout waiting for Log1 event from ContractReader")
case log := <-logCh:
lggr.Infow("Received log from ContractReader", "event", log.Event.ID)
if log.Err != nil {
return nil, nil, fmt.Errorf("error listening for Log1 event from ContractReader: %v", log.Err)
}
v := make(map[string]any)
err := log.Event.Outputs.UnwrapTo(&v)
if err != nil {
return nil, nil, fmt.Errorf("error unwrapping log to map: (log %v) %v", log.Event.Outputs, log.Err)
}
return &log, v, nil
}
}

// Get the string value of a key from a generic map[string]any
func GetStrVal(m map[string]any, k string) (string, error) {
v, ok := m[k]
if !ok {
return "", fmt.Errorf("key %s not found", k)
}
vstr, ok := v.(string)
if !ok {
return "", fmt.Errorf("key %s not a string (%T)", k, v)
}
return vstr, nil
}

// Get int value of a key from a generic map[string]any
func GetBigIntVal(m map[string]any, k string) (*big.Int, error) {
v, ok := m[k]
if !ok {
return nil, fmt.Errorf("key %s not found", k)
}
val, ok := v.(*big.Int)
if !ok {
return nil, fmt.Errorf("key %s not a *big.Int (%T)", k, v)
}
return val, nil
}

// Get the int value from a map[string]map[string]any
func GetBigIntValL2(m map[string]any, level1Key string, level2Key string) (*big.Int, error) {
v, ok := m[level1Key]
if !ok {
return nil, fmt.Errorf("key %s not found", level1Key)
}
level2Map, ok := v.(map[string]any)
if !ok {
return nil, fmt.Errorf("key %s not a map[string]any (%T)", level1Key, v)
}
return GetBigIntVal(level2Map, level2Key)
}

// Pretty print a map as a JSON string
func PrintMap(m map[string]any, prefix string, lggr logger.Logger) error {
json, err := json.MarshalIndent(m, "", " ")
if err != nil {
return err
}
lggr.Infow(prefix, "map", string(json))
kidambisrinivas marked this conversation as resolved.
Show resolved Hide resolved
return nil
}
Loading
Loading