Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
[nodesets.chain_capabilities]
# we want to write only to the home chain, so that we can test whether remote write-evm-2337 capability of the 'capabilities DON' is used
write-evm = ["1337"]
evm = ["1337"] # TODO: move to capabilities DON when supported
read-contract = ["1337"]
evm = ["1337"] # TODO: move to capabilities DON when fix for WriteReport is done
Copy link
Contributor

Choose a reason for hiding this comment

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

assuming this is transmission strategy?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no, to use the new codebase for the transmission strategy the capability has to be deployed in the capabilities don which I cannot do right now due to broken tests I'm planning to fix them now.

One of them is directly impacted by this https://smartcontract-it.atlassian.net/browse/PLEX-1730, and 2 others I didn't debug them properly.

Unless you were asking for the exact code regarding the transmission strategy, which is done in several PRs, among them #19102 (there are other fixes scattered while I was testing this)


# See ./examples/workflow-don-overrides.toml to learn how to override capability configs

Expand Down
100 changes: 86 additions & 14 deletions system-tests/lib/cre/capabilities/evm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const (
configTemplate = `'{"chainId":{{.ChainID}},"network":"{{.NetworkFamily}}","logTriggerPollInterval":{{.LogTriggerPollInterval}}, "creForwarderAddress":"{{.CreForwarderAddress}}","receiverGasMinimum":{{.ReceiverGasMinimum}},"nodeAddress":"{{.NodeAddress}}"}'`
registrationRefresh = 20 * time.Second
registrationExpiry = 60 * time.Second
deltaStage = 500*time.Millisecond + 1*time.Second // block time + 1 second delta
requestTimeout = 30 * time.Second
)

func New(registryChainID uint64) (*capabilities.Capability, error) {
Expand Down Expand Up @@ -71,32 +73,102 @@ func registerWithV1(_ []string, nodeSetInput *cre.CapabilitiesAwareNodeSet) ([]k
return nil, errors.Wrapf(selectorErr, "failed to get selector from chainID: %d", chainID)
}

faultyNodes, faultyErr := nodeSetInput.MaxFaultyNodes()
if faultyErr != nil {
return nil, errors.Wrap(faultyErr, "failed to get faulty nodes")
evmMethodConfigs, err := getEvmMethodConfigs(nodeSetInput)
if err != nil {
return nil, errors.Wrap(err, "there was an error getting EVM method configs")
}

capabilities = append(capabilities, keystone_changeset.DONCapabilityWithConfig{
Capability: kcr.CapabilitiesRegistryCapability{
LabelledName: "evm" + ":ChainSelector:" + strconv.FormatUint(selector, 10),
Version: "1.0.0",
CapabilityType: 0, // TRIGGER
LabelledName: "evm" + ":ChainSelector:" + strconv.FormatUint(selector, 10),
Version: "1.0.0",
},
Config: &capabilitiespb.CapabilityConfig{
RemoteConfig: &capabilitiespb.CapabilityConfig_RemoteTriggerConfig{
RemoteTriggerConfig: &capabilitiespb.RemoteTriggerConfig{
// needed for message_cache.go#Ready(), without these events from the capability will never be accepted
RegistrationRefresh: durationpb.New(registrationRefresh),
RegistrationExpiry: durationpb.New(registrationExpiry),
MinResponsesToAggregate: faultyNodes + 1,
},
},
MethodConfigs: evmMethodConfigs,
},
})
}

return capabilities, nil
}

// getEvmMethodConfigs returns the method configs for all EVM methods we want to support, if any method is missing it
// will not be reached by the node when running evm capability in remote don
func getEvmMethodConfigs(nodeSetInput *cre.CapabilitiesAwareNodeSet) (map[string]*capabilitiespb.CapabilityMethodConfig, error) {
evmMethodConfigs := map[string]*capabilitiespb.CapabilityMethodConfig{}

// the read actions should be all defined in the proto that are neither a LogTrigger type, not a WriteReport type
// see the RPC methods to map here: https://github.com/smartcontractkit/chainlink-protos/blob/main/cre/capabilities/blockchain/evm/v1alpha/client.proto
readActions := []string{
"CallContract",
"FilterLogs",
"BalanceAt",
"EstimateGas",
"GetTransactionByHash",
"GetTransactionReceipt",
"HeaderByNumber",
}
for _, action := range readActions {
evmMethodConfigs[action] = readActionConfig()
}

triggerConfig, err := logTriggerConfig(nodeSetInput)
if err != nil {
return nil, errors.Wrap(err, "failed get config for LogTrigger")
}

evmMethodConfigs["LogTrigger"] = triggerConfig
evmMethodConfigs["WriteReport"] = writeReportActionConfig()
return evmMethodConfigs, nil
}

func logTriggerConfig(nodeSetInput *cre.CapabilitiesAwareNodeSet) (*capabilitiespb.CapabilityMethodConfig, error) {
faultyNodes, faultyErr := nodeSetInput.MaxFaultyNodes()
if faultyErr != nil {
return nil, errors.Wrap(faultyErr, "failed to get faulty nodes")
}

return &capabilitiespb.CapabilityMethodConfig{
RemoteConfig: &capabilitiespb.CapabilityMethodConfig_RemoteTriggerConfig{
RemoteTriggerConfig: &capabilitiespb.RemoteTriggerConfig{
RegistrationRefresh: durationpb.New(registrationRefresh),
RegistrationExpiry: durationpb.New(registrationExpiry),
MinResponsesToAggregate: faultyNodes + 1,
MessageExpiry: durationpb.New(2 * registrationExpiry),
MaxBatchSize: 25,
BatchCollectionPeriod: durationpb.New(200 * time.Millisecond),
},
},
}, nil
}

func writeReportActionConfig() *capabilitiespb.CapabilityMethodConfig {
return &capabilitiespb.CapabilityMethodConfig{
RemoteConfig: &capabilitiespb.CapabilityMethodConfig_RemoteExecutableConfig{
RemoteExecutableConfig: &capabilitiespb.RemoteExecutableConfig{
TransmissionSchedule: capabilitiespb.TransmissionSchedule_OneAtATime,
DeltaStage: durationpb.New(deltaStage),
RequestTimeout: durationpb.New(requestTimeout),
ServerMaxParallelRequests: 10,
RequestHasherType: capabilitiespb.RequestHasherType_WriteReportExcludeSignatures,
},
},
}
}

func readActionConfig() *capabilitiespb.CapabilityMethodConfig {
return &capabilitiespb.CapabilityMethodConfig{
RemoteConfig: &capabilitiespb.CapabilityMethodConfig_RemoteExecutableConfig{
RemoteExecutableConfig: &capabilitiespb.RemoteExecutableConfig{
TransmissionSchedule: capabilitiespb.TransmissionSchedule_AllAtOnce,
RequestTimeout: durationpb.New(requestTimeout),
ServerMaxParallelRequests: 10,
RequestHasherType: capabilitiespb.RequestHasherType_Simple,
},
},
}
}

// buildRuntimeValues creates runtime-generated values for any keys not specified in TOML
func buildRuntimeValues(chainID uint64, networkFamily, creForwarderAddress, nodeAddress string) map[string]any {
return map[string]any{
Expand Down
111 changes: 78 additions & 33 deletions system-tests/lib/cre/environment/env_artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ func (c *DONCapabilityConfig) UnmarshalJSON(data []byte) error {

// use a map to hold any nested shape: RemoteTriggerConfig/RemoteTargetConfig/RemoteExecutableConfig
RemoteConfig map[string]json.RawMessage `json:"RemoteConfig,omitempty"`
// use a map to hold any methods, if present, to iterate later
MethodConfigs map[string]json.RawMessage `json:"method_configs,omitempty"`
}

aux.Alias = (*Alias)(c)
Expand All @@ -92,42 +94,85 @@ func (c *DONCapabilityConfig) UnmarshalJSON(data []byte) error {
return err
}

if aux.RemoteConfig == nil {
// nothing else to do, no remote config to parse
return nil
if aux.RemoteConfig != nil {
// parse the remote config based on the key
switch {
case aux.RemoteConfig["RemoteTriggerConfig"] != nil:
var rt capabilitiespb.RemoteTriggerConfig
if err := json.Unmarshal(aux.RemoteConfig["RemoteTriggerConfig"], &rt); err != nil {
return err
}
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTriggerConfig{
RemoteTriggerConfig: &rt,
}
case aux.RemoteConfig["RemoteTargetConfig"] != nil:
var tgt capabilitiespb.RemoteTargetConfig
if err := json.Unmarshal(aux.RemoteConfig["RemoteTargetConfig"], &tgt); err != nil {
return err
}
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTargetConfig{
RemoteTargetConfig: &tgt,
}
case aux.RemoteConfig["RemoteExecutableConfig"] != nil:
var ex capabilitiespb.RemoteExecutableConfig
if err := json.Unmarshal(aux.RemoteConfig["RemoteExecutableConfig"], &ex); err != nil {
return err
}
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteExecutableConfig{
RemoteExecutableConfig: &ex,
}
default:
keys := make([]string, 0, len(aux.RemoteConfig))
for k := range aux.RemoteConfig {
keys = append(keys, k)
}
return fmt.Errorf("unknown remote config type in capability config, keys: %v", keys)
}
}

switch {
case aux.RemoteConfig["RemoteTriggerConfig"] != nil:
var rt capabilitiespb.RemoteTriggerConfig
if err := json.Unmarshal(aux.RemoteConfig["RemoteTriggerConfig"], &rt); err != nil {
return err
}
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTriggerConfig{
RemoteTriggerConfig: &rt,
}
case aux.RemoteConfig["RemoteTargetConfig"] != nil:
var tgt capabilitiespb.RemoteTargetConfig
if err := json.Unmarshal(aux.RemoteConfig["RemoteTargetConfig"], &tgt); err != nil {
return err
}
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTargetConfig{
RemoteTargetConfig: &tgt,
}
case aux.RemoteConfig["RemoteExecutableConfig"] != nil:
var ex capabilitiespb.RemoteExecutableConfig
if err := json.Unmarshal(aux.RemoteConfig["RemoteExecutableConfig"], &ex); err != nil {
return err
}
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteExecutableConfig{
RemoteExecutableConfig: &ex,
}
default:
keys := make([]string, 0, len(aux.RemoteConfig))
for k := range aux.RemoteConfig {
keys = append(keys, k)
if aux.MethodConfigs != nil {
methodConfigs := make(map[string]*capabilitiespb.CapabilityMethodConfig, len(aux.MethodConfigs))
for methodName, methodConfig := range aux.MethodConfigs {
var methodRemoteConfig map[string]json.RawMessage
if err := json.Unmarshal(methodConfig, &methodRemoteConfig); err != nil {
return err
}

var innerRemoteConfig map[string]json.RawMessage
if err := json.Unmarshal(methodRemoteConfig["RemoteConfig"], &innerRemoteConfig); err != nil {
return err
}
switch {
case innerRemoteConfig["RemoteTriggerConfig"] != nil:
var rt capabilitiespb.RemoteTriggerConfig
if err := json.Unmarshal(innerRemoteConfig["RemoteTriggerConfig"], &rt); err != nil {
return err
}
methodConfigs[methodName] = &capabilitiespb.CapabilityMethodConfig{
RemoteConfig: &capabilitiespb.CapabilityMethodConfig_RemoteTriggerConfig{
RemoteTriggerConfig: &rt,
},
}
case innerRemoteConfig["RemoteExecutableConfig"] != nil:
var ex capabilitiespb.RemoteExecutableConfig
if err := json.Unmarshal(innerRemoteConfig["RemoteExecutableConfig"], &ex); err != nil {
return err
}
methodConfigs[methodName] = &capabilitiespb.CapabilityMethodConfig{
RemoteConfig: &capabilitiespb.CapabilityMethodConfig_RemoteExecutableConfig{
RemoteExecutableConfig: &ex,
},
}
default:
keys := make([]string, 0, len(innerRemoteConfig))
for k := range innerRemoteConfig {
keys = append(keys, k)
}
return fmt.Errorf("unknown method config type for method %s, unknown config value keys: %s", methodName, strings.Join(keys, ","))
}
}
return fmt.Errorf("unknown remote config type in capability config, keys: %v", keys)

c.MethodConfigs = methodConfigs
}

return nil
Expand Down
58 changes: 46 additions & 12 deletions system-tests/lib/cre/environment/env_artifact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ func TestDONCapabilityConfigRoundTrip(t *testing.T) {
{dir: "002-remote-trigger"},
{dir: "003-remote-target"},
{dir: "004-remote-executable"},
{dir: "005-method-configs-trigger"},
{dir: "006-method-configs-executable-all-at-once"}, // transmission_schedule=0, no deltaStage
{dir: "007-method-configs-executable-one-at-a-time"}, // transmission_schedule=1, with deltaStage
{dir: "008-method-configs-all-options"},
}
for _, tc := range cases {
t.Run(tc.dir, func(t *testing.T) {
Expand Down Expand Up @@ -56,22 +60,52 @@ func TestDONCapabilityConfigRoundTrip(t *testing.T) {
}
}
func TestDONCapabilityConfigUnknownRemoteConfigType(t *testing.T) {
input := []byte(`{
"config": {
"RemoteConfig": {
"UnknownConfigType": {
"foo": "bar"
tests := []struct {
name string
input []byte
errContains string
}{
{
name: "unknown remote config type",
input: []byte(`{
"config": {
"RemoteConfig": {
"UnknownConfigType": {
"foo": "bar"
}
}
}
}
}`)

var obj struct {
Config DONCapabilityConfig `json:"config"`
}`),
errContains: "unknown remote config type in capability config, keys: [UnknownConfigType]",
},
{
name: "unknown method_configs config type",
input: []byte(`{
"config": {
"method_configs": {
"SomeRandomMethod": {
"RemoteConfig": {
"UnknownConfigType2": {
"foo": "bar"
}
}
}
}
}
}`),
errContains: "unknown method config type for method SomeRandomMethod, unknown config value keys: UnknownConfigType2",
},
}

err := json.Unmarshal(input, &obj)
assert.ErrorContains(t, err, "unknown remote config type in capability config, keys: [UnknownConfigType]")
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var obj struct {
Config DONCapabilityConfig `json:"config"`
}
err := json.Unmarshal(tc.input, &obj)
assert.ErrorContains(t, err, tc.errContains)
})
}
}

func readFile(t *testing.T, file string) []byte {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
"config": {
"RemoteConfig": {
"RemoteExecutableConfig": {
"registrationExpiry": {
"seconds": 60
},
"registrationRefresh": {
"seconds": 20
"delta_stage": {
"nanos": 500000000,
"seconds": 1
},
"requestHashExcludedAttributes": [
"data1"
]
],
"request_hasher_type": 1,
"request_timeout": {
"seconds": 30
},
"server_max_parallel_requests": 10,
"transmission_schedule": 1
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@
"RemoteConfig": {
"RemoteExecutableConfig": {
"requestHashExcludedAttributes": ["data1"],
"registrationRefresh": { "seconds": 20 },
"registrationExpiry": { "seconds": 60 }
"transmission_schedule": 1,
"delta_stage": {
"seconds": 1,
"nanos": 500000000
},
"request_timeout": {
"seconds": 30
},
"server_max_parallel_requests": 10,
"request_hasher_type": 1
}
}
}
Expand Down
Loading
Loading