Skip to content

Commit 2780d31

Browse files
PLEX-1706 Adding EVM cap methods for remote don (#19307)
* PLEX-1706 Adding EVM cap methods for remote don * lint * leaving EVM cap only in remote DON * fixing unmarshalling problem for interface types * lint * revert remote evm capability to workflow don so tests pass * comment
1 parent fe4712e commit 2780d31

File tree

14 files changed

+467
-68
lines changed

14 files changed

+467
-68
lines changed

core/scripts/cre/environment/configs/workflow-gateway-capabilities-don.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@
5151
[nodesets.chain_capabilities]
5252
# 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
5353
write-evm = ["1337"]
54-
evm = ["1337"] # TODO: move to capabilities DON when supported
5554
read-contract = ["1337"]
55+
evm = ["1337"] # TODO: move to capabilities DON when fix for WriteReport is done
5656

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

system-tests/lib/cre/capabilities/evm/evm.go

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const (
3434
configTemplate = `'{"chainId":{{.ChainID}},"network":"{{.NetworkFamily}}","logTriggerPollInterval":{{.LogTriggerPollInterval}}, "creForwarderAddress":"{{.CreForwarderAddress}}","receiverGasMinimum":{{.ReceiverGasMinimum}},"nodeAddress":"{{.NodeAddress}}"}'`
3535
registrationRefresh = 20 * time.Second
3636
registrationExpiry = 60 * time.Second
37+
deltaStage = 500*time.Millisecond + 1*time.Second // block time + 1 second delta
38+
requestTimeout = 30 * time.Second
3739
)
3840

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

74-
faultyNodes, faultyErr := nodeSetInput.MaxFaultyNodes()
75-
if faultyErr != nil {
76-
return nil, errors.Wrap(faultyErr, "failed to get faulty nodes")
76+
evmMethodConfigs, err := getEvmMethodConfigs(nodeSetInput)
77+
if err != nil {
78+
return nil, errors.Wrap(err, "there was an error getting EVM method configs")
7779
}
80+
7881
capabilities = append(capabilities, keystone_changeset.DONCapabilityWithConfig{
7982
Capability: kcr.CapabilitiesRegistryCapability{
80-
LabelledName: "evm" + ":ChainSelector:" + strconv.FormatUint(selector, 10),
81-
Version: "1.0.0",
82-
CapabilityType: 0, // TRIGGER
83+
LabelledName: "evm" + ":ChainSelector:" + strconv.FormatUint(selector, 10),
84+
Version: "1.0.0",
8385
},
8486
Config: &capabilitiespb.CapabilityConfig{
85-
RemoteConfig: &capabilitiespb.CapabilityConfig_RemoteTriggerConfig{
86-
RemoteTriggerConfig: &capabilitiespb.RemoteTriggerConfig{
87-
// needed for message_cache.go#Ready(), without these events from the capability will never be accepted
88-
RegistrationRefresh: durationpb.New(registrationRefresh),
89-
RegistrationExpiry: durationpb.New(registrationExpiry),
90-
MinResponsesToAggregate: faultyNodes + 1,
91-
},
92-
},
87+
MethodConfigs: evmMethodConfigs,
9388
},
9489
})
9590
}
9691

9792
return capabilities, nil
9893
}
9994

95+
// getEvmMethodConfigs returns the method configs for all EVM methods we want to support, if any method is missing it
96+
// will not be reached by the node when running evm capability in remote don
97+
func getEvmMethodConfigs(nodeSetInput *cre.CapabilitiesAwareNodeSet) (map[string]*capabilitiespb.CapabilityMethodConfig, error) {
98+
evmMethodConfigs := map[string]*capabilitiespb.CapabilityMethodConfig{}
99+
100+
// the read actions should be all defined in the proto that are neither a LogTrigger type, not a WriteReport type
101+
// see the RPC methods to map here: https://github.com/smartcontractkit/chainlink-protos/blob/main/cre/capabilities/blockchain/evm/v1alpha/client.proto
102+
readActions := []string{
103+
"CallContract",
104+
"FilterLogs",
105+
"BalanceAt",
106+
"EstimateGas",
107+
"GetTransactionByHash",
108+
"GetTransactionReceipt",
109+
"HeaderByNumber",
110+
}
111+
for _, action := range readActions {
112+
evmMethodConfigs[action] = readActionConfig()
113+
}
114+
115+
triggerConfig, err := logTriggerConfig(nodeSetInput)
116+
if err != nil {
117+
return nil, errors.Wrap(err, "failed get config for LogTrigger")
118+
}
119+
120+
evmMethodConfigs["LogTrigger"] = triggerConfig
121+
evmMethodConfigs["WriteReport"] = writeReportActionConfig()
122+
return evmMethodConfigs, nil
123+
}
124+
125+
func logTriggerConfig(nodeSetInput *cre.CapabilitiesAwareNodeSet) (*capabilitiespb.CapabilityMethodConfig, error) {
126+
faultyNodes, faultyErr := nodeSetInput.MaxFaultyNodes()
127+
if faultyErr != nil {
128+
return nil, errors.Wrap(faultyErr, "failed to get faulty nodes")
129+
}
130+
131+
return &capabilitiespb.CapabilityMethodConfig{
132+
RemoteConfig: &capabilitiespb.CapabilityMethodConfig_RemoteTriggerConfig{
133+
RemoteTriggerConfig: &capabilitiespb.RemoteTriggerConfig{
134+
RegistrationRefresh: durationpb.New(registrationRefresh),
135+
RegistrationExpiry: durationpb.New(registrationExpiry),
136+
MinResponsesToAggregate: faultyNodes + 1,
137+
MessageExpiry: durationpb.New(2 * registrationExpiry),
138+
MaxBatchSize: 25,
139+
BatchCollectionPeriod: durationpb.New(200 * time.Millisecond),
140+
},
141+
},
142+
}, nil
143+
}
144+
145+
func writeReportActionConfig() *capabilitiespb.CapabilityMethodConfig {
146+
return &capabilitiespb.CapabilityMethodConfig{
147+
RemoteConfig: &capabilitiespb.CapabilityMethodConfig_RemoteExecutableConfig{
148+
RemoteExecutableConfig: &capabilitiespb.RemoteExecutableConfig{
149+
TransmissionSchedule: capabilitiespb.TransmissionSchedule_OneAtATime,
150+
DeltaStage: durationpb.New(deltaStage),
151+
RequestTimeout: durationpb.New(requestTimeout),
152+
ServerMaxParallelRequests: 10,
153+
RequestHasherType: capabilitiespb.RequestHasherType_WriteReportExcludeSignatures,
154+
},
155+
},
156+
}
157+
}
158+
159+
func readActionConfig() *capabilitiespb.CapabilityMethodConfig {
160+
return &capabilitiespb.CapabilityMethodConfig{
161+
RemoteConfig: &capabilitiespb.CapabilityMethodConfig_RemoteExecutableConfig{
162+
RemoteExecutableConfig: &capabilitiespb.RemoteExecutableConfig{
163+
TransmissionSchedule: capabilitiespb.TransmissionSchedule_AllAtOnce,
164+
RequestTimeout: durationpb.New(requestTimeout),
165+
ServerMaxParallelRequests: 10,
166+
RequestHasherType: capabilitiespb.RequestHasherType_Simple,
167+
},
168+
},
169+
}
170+
}
171+
100172
// buildRuntimeValues creates runtime-generated values for any keys not specified in TOML
101173
func buildRuntimeValues(chainID uint64, networkFamily, creForwarderAddress, nodeAddress string) map[string]any {
102174
return map[string]any{

system-tests/lib/cre/environment/env_artifact.go

Lines changed: 78 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ func (c *DONCapabilityConfig) UnmarshalJSON(data []byte) error {
8484

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

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

95-
if aux.RemoteConfig == nil {
96-
// nothing else to do, no remote config to parse
97-
return nil
97+
if aux.RemoteConfig != nil {
98+
// parse the remote config based on the key
99+
switch {
100+
case aux.RemoteConfig["RemoteTriggerConfig"] != nil:
101+
var rt capabilitiespb.RemoteTriggerConfig
102+
if err := json.Unmarshal(aux.RemoteConfig["RemoteTriggerConfig"], &rt); err != nil {
103+
return err
104+
}
105+
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTriggerConfig{
106+
RemoteTriggerConfig: &rt,
107+
}
108+
case aux.RemoteConfig["RemoteTargetConfig"] != nil:
109+
var tgt capabilitiespb.RemoteTargetConfig
110+
if err := json.Unmarshal(aux.RemoteConfig["RemoteTargetConfig"], &tgt); err != nil {
111+
return err
112+
}
113+
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTargetConfig{
114+
RemoteTargetConfig: &tgt,
115+
}
116+
case aux.RemoteConfig["RemoteExecutableConfig"] != nil:
117+
var ex capabilitiespb.RemoteExecutableConfig
118+
if err := json.Unmarshal(aux.RemoteConfig["RemoteExecutableConfig"], &ex); err != nil {
119+
return err
120+
}
121+
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteExecutableConfig{
122+
RemoteExecutableConfig: &ex,
123+
}
124+
default:
125+
keys := make([]string, 0, len(aux.RemoteConfig))
126+
for k := range aux.RemoteConfig {
127+
keys = append(keys, k)
128+
}
129+
return fmt.Errorf("unknown remote config type in capability config, keys: %v", keys)
130+
}
98131
}
99132

100-
switch {
101-
case aux.RemoteConfig["RemoteTriggerConfig"] != nil:
102-
var rt capabilitiespb.RemoteTriggerConfig
103-
if err := json.Unmarshal(aux.RemoteConfig["RemoteTriggerConfig"], &rt); err != nil {
104-
return err
105-
}
106-
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTriggerConfig{
107-
RemoteTriggerConfig: &rt,
108-
}
109-
case aux.RemoteConfig["RemoteTargetConfig"] != nil:
110-
var tgt capabilitiespb.RemoteTargetConfig
111-
if err := json.Unmarshal(aux.RemoteConfig["RemoteTargetConfig"], &tgt); err != nil {
112-
return err
113-
}
114-
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTargetConfig{
115-
RemoteTargetConfig: &tgt,
116-
}
117-
case aux.RemoteConfig["RemoteExecutableConfig"] != nil:
118-
var ex capabilitiespb.RemoteExecutableConfig
119-
if err := json.Unmarshal(aux.RemoteConfig["RemoteExecutableConfig"], &ex); err != nil {
120-
return err
121-
}
122-
c.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteExecutableConfig{
123-
RemoteExecutableConfig: &ex,
124-
}
125-
default:
126-
keys := make([]string, 0, len(aux.RemoteConfig))
127-
for k := range aux.RemoteConfig {
128-
keys = append(keys, k)
133+
if aux.MethodConfigs != nil {
134+
methodConfigs := make(map[string]*capabilitiespb.CapabilityMethodConfig, len(aux.MethodConfigs))
135+
for methodName, methodConfig := range aux.MethodConfigs {
136+
var methodRemoteConfig map[string]json.RawMessage
137+
if err := json.Unmarshal(methodConfig, &methodRemoteConfig); err != nil {
138+
return err
139+
}
140+
141+
var innerRemoteConfig map[string]json.RawMessage
142+
if err := json.Unmarshal(methodRemoteConfig["RemoteConfig"], &innerRemoteConfig); err != nil {
143+
return err
144+
}
145+
switch {
146+
case innerRemoteConfig["RemoteTriggerConfig"] != nil:
147+
var rt capabilitiespb.RemoteTriggerConfig
148+
if err := json.Unmarshal(innerRemoteConfig["RemoteTriggerConfig"], &rt); err != nil {
149+
return err
150+
}
151+
methodConfigs[methodName] = &capabilitiespb.CapabilityMethodConfig{
152+
RemoteConfig: &capabilitiespb.CapabilityMethodConfig_RemoteTriggerConfig{
153+
RemoteTriggerConfig: &rt,
154+
},
155+
}
156+
case innerRemoteConfig["RemoteExecutableConfig"] != nil:
157+
var ex capabilitiespb.RemoteExecutableConfig
158+
if err := json.Unmarshal(innerRemoteConfig["RemoteExecutableConfig"], &ex); err != nil {
159+
return err
160+
}
161+
methodConfigs[methodName] = &capabilitiespb.CapabilityMethodConfig{
162+
RemoteConfig: &capabilitiespb.CapabilityMethodConfig_RemoteExecutableConfig{
163+
RemoteExecutableConfig: &ex,
164+
},
165+
}
166+
default:
167+
keys := make([]string, 0, len(innerRemoteConfig))
168+
for k := range innerRemoteConfig {
169+
keys = append(keys, k)
170+
}
171+
return fmt.Errorf("unknown method config type for method %s, unknown config value keys: %s", methodName, strings.Join(keys, ","))
172+
}
129173
}
130-
return fmt.Errorf("unknown remote config type in capability config, keys: %v", keys)
174+
175+
c.MethodConfigs = methodConfigs
131176
}
132177

133178
return nil

system-tests/lib/cre/environment/env_artifact_test.go

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ func TestDONCapabilityConfigRoundTrip(t *testing.T) {
2222
{dir: "002-remote-trigger"},
2323
{dir: "003-remote-target"},
2424
{dir: "004-remote-executable"},
25+
{dir: "005-method-configs-trigger"},
26+
{dir: "006-method-configs-executable-all-at-once"}, // transmission_schedule=0, no deltaStage
27+
{dir: "007-method-configs-executable-one-at-a-time"}, // transmission_schedule=1, with deltaStage
28+
{dir: "008-method-configs-all-options"},
2529
}
2630
for _, tc := range cases {
2731
t.Run(tc.dir, func(t *testing.T) {
@@ -56,22 +60,52 @@ func TestDONCapabilityConfigRoundTrip(t *testing.T) {
5660
}
5761
}
5862
func TestDONCapabilityConfigUnknownRemoteConfigType(t *testing.T) {
59-
input := []byte(`{
60-
"config": {
61-
"RemoteConfig": {
62-
"UnknownConfigType": {
63-
"foo": "bar"
63+
tests := []struct {
64+
name string
65+
input []byte
66+
errContains string
67+
}{
68+
{
69+
name: "unknown remote config type",
70+
input: []byte(`{
71+
"config": {
72+
"RemoteConfig": {
73+
"UnknownConfigType": {
74+
"foo": "bar"
75+
}
6476
}
6577
}
66-
}
67-
}`)
68-
69-
var obj struct {
70-
Config DONCapabilityConfig `json:"config"`
78+
}`),
79+
errContains: "unknown remote config type in capability config, keys: [UnknownConfigType]",
80+
},
81+
{
82+
name: "unknown method_configs config type",
83+
input: []byte(`{
84+
"config": {
85+
"method_configs": {
86+
"SomeRandomMethod": {
87+
"RemoteConfig": {
88+
"UnknownConfigType2": {
89+
"foo": "bar"
90+
}
91+
}
92+
}
93+
}
94+
}
95+
}`),
96+
errContains: "unknown method config type for method SomeRandomMethod, unknown config value keys: UnknownConfigType2",
97+
},
7198
}
7299

73-
err := json.Unmarshal(input, &obj)
74-
assert.ErrorContains(t, err, "unknown remote config type in capability config, keys: [UnknownConfigType]")
100+
for _, tc := range tests {
101+
t.Run(tc.name, func(t *testing.T) {
102+
var obj struct {
103+
Config DONCapabilityConfig `json:"config"`
104+
}
105+
err := json.Unmarshal(tc.input, &obj)
106+
assert.ErrorContains(t, err, tc.errContains)
107+
})
108+
}
75109
}
76110

77111
func readFile(t *testing.T, file string) []byte {

system-tests/lib/cre/environment/testdata/roundtrip/004-remote-executable/expected.json

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22
"config": {
33
"RemoteConfig": {
44
"RemoteExecutableConfig": {
5-
"registrationExpiry": {
6-
"seconds": 60
7-
},
8-
"registrationRefresh": {
9-
"seconds": 20
5+
"delta_stage": {
6+
"nanos": 500000000,
7+
"seconds": 1
108
},
119
"requestHashExcludedAttributes": [
1210
"data1"
13-
]
11+
],
12+
"request_hasher_type": 1,
13+
"request_timeout": {
14+
"seconds": 30
15+
},
16+
"server_max_parallel_requests": 10,
17+
"transmission_schedule": 1
1418
}
1519
}
1620
}

system-tests/lib/cre/environment/testdata/roundtrip/004-remote-executable/input.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@
33
"RemoteConfig": {
44
"RemoteExecutableConfig": {
55
"requestHashExcludedAttributes": ["data1"],
6-
"registrationRefresh": { "seconds": 20 },
7-
"registrationExpiry": { "seconds": 60 }
6+
"transmission_schedule": 1,
7+
"delta_stage": {
8+
"seconds": 1,
9+
"nanos": 500000000
10+
},
11+
"request_timeout": {
12+
"seconds": 30
13+
},
14+
"server_max_parallel_requests": 10,
15+
"request_hasher_type": 1
816
}
917
}
1018
}

0 commit comments

Comments
 (0)