Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/cw-enabled-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

Add the ConfidentialWorkflows.Enabled feature gate to the v2 workflow engine. Confidential workflow execution can now be toggled per workflow/owner/org/global via the settings registry; when disabled, ConfidentialModule.Execute rejects the request.
5 changes: 4 additions & 1 deletion core/services/workflows/syncer/v2/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,10 @@ func (h *eventHandler) engineFactoryFn(ctx context.Context, workflowID string, o
binaryHash := v2.ComputeBinaryHash(binary)
confLggr := logger.Named(h.lggr, "WorkflowEngine.ConfidentialModule")
confLggr = logger.With(confLggr, "workflowID", workflowID, "workflowName", name, "workflowOwner", owner)
confidential := v2.NewConfidentialModule(h.capRegistry, binaryURL, binaryHash, workflowID, owner, name.String(), tag, confLggr)
confidential, err := v2.NewConfidentialModule(h.capRegistry, binaryURL, binaryHash, workflowID, owner, name.String(), tag, h.engineLimiters.ConfidentialWorkflowsEnabled, confLggr)
if err != nil {
return nil, fmt.Errorf("failed to create confidential module: %w", err)
}
engineModule := h.createEngineModule(ctx, workflowID, binary, moduleConfig, module)
selectingModule := generichost.NewRequirementSelectingModule(
generichost.ModuleAndHandler{Module: engineModule},
Expand Down
14 changes: 12 additions & 2 deletions core/services/workflows/v2/confidential_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/capabilities"
"github.com/smartcontractkit/chainlink-common/pkg/contexts"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink-common/pkg/settings/limits"
"github.com/smartcontractkit/chainlink-common/pkg/types/core"
"github.com/smartcontractkit/chainlink-common/pkg/workflows/host"

Expand Down Expand Up @@ -64,11 +65,15 @@ type ConfidentialModule struct {
requirements sync.Map
infoOnce sync.Once
provider func(tee *sdkpb.Tee) bool
enabledGate limits.GateLimiter
}

var _ host.RequirementEnforcingModule = (*ConfidentialModule)(nil)

func NewConfidentialModule(capRegistry core.CapabilitiesRegistry, binaryURL string, binaryHash []byte, workflowID, workflowOwner, workflowName, workflowTag string, lggr logger.Logger) *ConfidentialModule {
func NewConfidentialModule(capRegistry core.CapabilitiesRegistry, binaryURL string, binaryHash []byte, workflowID, workflowOwner, workflowName, workflowTag string, enabledGate limits.GateLimiter, lggr logger.Logger) (*ConfidentialModule, error) {
if enabledGate == nil {
return nil, errors.New("enabledGate must not be nil")
}
return &ConfidentialModule{
capRegistry: capRegistry,
binaryURL: binaryURL,
Expand All @@ -77,8 +82,9 @@ func NewConfidentialModule(capRegistry core.CapabilitiesRegistry, binaryURL stri
workflowOwner: workflowOwner,
workflowName: workflowName,
workflowTag: workflowTag,
enabledGate: enabledGate,
lggr: lggr,
}
}, nil
}

func (m *ConfidentialModule) Start() {}
Expand All @@ -90,6 +96,10 @@ func (m *ConfidentialModule) Execute(
request *sdkpb.ExecuteRequest,
helper host.ExecutionHelper,
) (*sdkpb.ExecutionResult, error) {
if err := m.enabledGate.AllowErr(ctx); err != nil {
return nil, fmt.Errorf("confidential-workflows capability is disabled by settings: %w", err)
}

var requirements *sdkpb.Requirements
rawRequirements, loaded := m.requirements.LoadAndDelete(helper.GetWorkflowExecutionID())
if loaded {
Expand Down
46 changes: 31 additions & 15 deletions core/services/workflows/v2/confidential_module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/smartcontractkit/chainlink-common/pkg/capabilities"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink-common/pkg/settings/limits"
regmocks "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks"
"github.com/smartcontractkit/chainlink-common/pkg/workflows/host"

Expand Down Expand Up @@ -132,14 +133,15 @@ func TestConfidentialModule_Execute(t *testing.T) {
req.Payload != nil
})).Return(capabilities.CapabilityResponse{Payload: respPayload}, nil).Once()

mod := NewConfidentialModule(
mod := mustNewConfidentialModule(t,
capReg,
"https://example.com/binary.wasm",
[]byte("fakehash"),
"wf-123",
"owner-abc",
"my-workflow",
"v1",
limits.NewGateLimiter(true),
lggr,
)

Expand All @@ -157,7 +159,7 @@ func TestConfidentialModule_Execute(t *testing.T) {
capReg.EXPECT().GetExecutable(matches.AnyContext, confidentialWorkflowsCapabilityID).
Return(nil, errors.New("capability not found")).Once()

mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr)
mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr)

_, err := mod.Execute(ctx, execReq, &stubExecutionHelper{})
require.Error(t, err)
Expand All @@ -173,7 +175,7 @@ func TestConfidentialModule_Execute(t *testing.T) {
execCap.EXPECT().Execute(matches.AnyContext, mock.Anything).
Return(capabilities.CapabilityResponse{}, errors.New("enclave unavailable")).Once()

mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr)
mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr)

_, err := mod.Execute(ctx, execReq, &stubExecutionHelper{})
require.Error(t, err)
Expand All @@ -189,7 +191,7 @@ func TestConfidentialModule_Execute(t *testing.T) {
execCap.EXPECT().Execute(matches.AnyContext, mock.Anything).
Return(capabilities.CapabilityResponse{Payload: nil}, nil).Once()

mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr)
mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr)

_, err := mod.Execute(ctx, execReq, &stubExecutionHelper{})
require.Error(t, err)
Expand All @@ -211,7 +213,7 @@ func TestConfidentialModule_Execute(t *testing.T) {
Return(capabilities.CapabilityResponse{Payload: respPayload}, nil).Once()

binaryHash := ComputeBinaryHash([]byte("some-binary"))
mod := NewConfidentialModule(capReg, "https://example.com/wasm", binaryHash, "wf-abc", "0xowner", "my-workflow", "v2", lggr)
mod := mustNewConfidentialModule(t, capReg, "https://example.com/wasm", binaryHash, "wf-abc", "0xowner", "my-workflow", "v2", limits.NewGateLimiter(true), lggr)

_, err := mod.Execute(ctx, execReq, &stubExecutionHelper{executionID: "exec-xyz"})
require.NoError(t, err)
Expand Down Expand Up @@ -268,7 +270,7 @@ func TestConfidentialModule_Tee(t *testing.T) {
{Type: sdkpb.TeeType_TEE_TYPE_AWS_NITRO, Regions: []string{"us-east-1", "eu-west-1"}},
})}, nil).Once()

mod := NewConfidentialModule(capReg, "https://example.com/binary.wasm", []byte("fakehash"), "wf-123", "owner-abc", "my-workflow", "v1", lggr)
mod := mustNewConfidentialModule(t, capReg, "https://example.com/binary.wasm", []byte("fakehash"), "wf-123", "owner-abc", "my-workflow", "v1", limits.NewGateLimiter(true), lggr)

assert.True(t, mod.Tee(ctx, anyRegionsTee("us-east-1")))
})
Expand All @@ -284,7 +286,7 @@ func TestConfidentialModule_Tee(t *testing.T) {
{Type: sdkpb.TeeType_TEE_TYPE_AWS_NITRO, Regions: []string{"us-east-1"}},
})}, nil).Once()

mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr)
mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr)
assert.False(t, mod.Tee(ctx, anyRegionsTee("ap-southeast-1")))
})

Expand All @@ -297,7 +299,7 @@ func TestConfidentialModule_Tee(t *testing.T) {
execCap.EXPECT().Execute(matches.AnyContext, mock.Anything).
Return(capabilities.CapabilityResponse{Payload: buildRespPayload(t, nil)}, nil).Once()

mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr)
mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr)
assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1")))
})

Expand All @@ -306,7 +308,7 @@ func TestConfidentialModule_Tee(t *testing.T) {
capReg.EXPECT().GetExecutable(matches.AnyContext, confidentialWorkflowsCapabilityID).
Return(nil, errors.New("capability not found")).Once()

mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr)
mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr)
assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1")))
})

Expand All @@ -319,7 +321,7 @@ func TestConfidentialModule_Tee(t *testing.T) {
execCap.EXPECT().Execute(matches.AnyContext, mock.Anything).
Return(capabilities.CapabilityResponse{}, errors.New("enclave unavailable")).Once()

mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr)
mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr)
assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1")))
})

Expand All @@ -332,7 +334,7 @@ func TestConfidentialModule_Tee(t *testing.T) {
execCap.EXPECT().Execute(matches.AnyContext, mock.Anything).
Return(capabilities.CapabilityResponse{Payload: nil}, nil).Once()

mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr)
mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr)
assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1")))
})

Expand All @@ -350,7 +352,7 @@ func TestConfidentialModule_Tee(t *testing.T) {
}).
Return(capabilities.CapabilityResponse{Payload: buildRespPayload(t, nil)}, nil).Once()

mod := NewConfidentialModule(capReg, "https://example.com/wasm", []byte("hash"), "wf-xyz", "0xowner", "my-workflow", "v3", lggr)
mod := mustNewConfidentialModule(t, capReg, "https://example.com/wasm", []byte("hash"), "wf-xyz", "0xowner", "my-workflow", "v3", limits.NewGateLimiter(true), lggr)
_ = mod.Tee(ctx, anyRegionsTee("us-east-1"))

assert.Equal(t, "ProvidedTees", capturedReq.Method)
Expand All @@ -376,7 +378,7 @@ func TestConfidentialModule_Tee(t *testing.T) {
{Type: sdkpb.TeeType_TEE_TYPE_AWS_NITRO, Regions: []string{"us-east-1"}},
})}, nil).Once()

mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr)
mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr)

assert.True(t, mod.Tee(ctx, anyRegionsTee("us-east-1")))
assert.True(t, mod.Tee(ctx, anyRegionsTee("us-east-1")))
Expand Down Expand Up @@ -416,7 +418,7 @@ func TestConfidentialModule_SetRequirements(t *testing.T) {
}).
Return(capabilities.CapabilityResponse{Payload: respPayload}, nil).Once()

mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr)
mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr)

requirements := &sdkpb.Requirements{
Tee: &sdkpb.Tee{
Expand Down Expand Up @@ -453,7 +455,7 @@ func TestConfidentialModule_SetRequirements(t *testing.T) {
}).
Return(capabilities.CapabilityResponse{Payload: respPayload}, nil).Once()

mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr)
mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr)
mod.SetRequirements("exec-789", &sdkpb.Requirements{})

_, err := mod.Execute(ctx, execReq, &stubExecutionHelper{executionID: "exec-789"})
Expand All @@ -476,3 +478,17 @@ func TestConfidentialModule_InterfaceMethods(t *testing.T) {
mod.Close()
assert.False(t, mod.IsLegacyDAG())
}

func mustNewConfidentialModule(t *testing.T, capRegistry *regmocks.CapabilitiesRegistry, binaryURL string, binaryHash []byte, workflowID, workflowOwner, workflowName, workflowTag string, enabledGate limits.GateLimiter, lggr logger.Logger) *ConfidentialModule {
t.Helper()
m, err := NewConfidentialModule(capRegistry, binaryURL, binaryHash, workflowID, workflowOwner, workflowName, workflowTag, enabledGate, lggr)
require.NoError(t, err)
return m
}

func TestNewConfidentialModule_NilGate(t *testing.T) {
t.Parallel()
capReg := regmocks.NewCapabilitiesRegistry(t)
_, err := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", nil, logger.Test(t))
require.Error(t, err)
}
9 changes: 8 additions & 1 deletion core/services/workflows/v2/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ type EngineLimiters struct {
UserMetricLabelsPerMetric limits.BoundLimiter[int]
UserMetricLabelValueLength limits.BoundLimiter[int]

ExecutionTimestampsEnabled limits.GateLimiter
ExecutionTimestampsEnabled limits.GateLimiter
ConfidentialWorkflowsEnabled limits.GateLimiter
}

// NewLimiters returns a new set of EngineLimiters based on the default configuration, and optionally modified by cfgFn.
Expand Down Expand Up @@ -264,6 +265,10 @@ func (l *EngineLimiters) init(lf limits.Factory, cfgFn func(*cresettings.Workflo
if err != nil {
return
}
l.ConfidentialWorkflowsEnabled, err = limits.MakeGateLimiter(lf, cfg.ConfidentialWorkflows.Enabled)
if err != nil {
return
}
return
}

Expand Down Expand Up @@ -302,6 +307,7 @@ func (l *EngineLimiters) EvictWorkflow(workflowID string) error {
l.ConfidentialHTTPCalls,
l.SecretsCalls,
l.ExecutionTimestampsEnabled,
l.ConfidentialWorkflowsEnabled,
}
var errs error
for _, e := range evictables {
Expand Down Expand Up @@ -343,6 +349,7 @@ func (l *EngineLimiters) Close() error {
l.ConfidentialHTTPCalls,
l.SecretsCalls,
l.ExecutionTimestampsEnabled,
l.ConfidentialWorkflowsEnabled,
)
}

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ require (
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc
github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc
github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260611143717-66b6ab499fd0
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260611160104-87b1e75d2c33
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260617161620-6c25e6db90be
github.com/smartcontractkit/chainlink-common/keystore v1.2.0
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0
github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a
Expand All @@ -97,7 +97,7 @@ require (
github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260423135514-5b1a7565a99c
github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805-26d78d5e1243
github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260611123141-db97012a6c32
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260616114954-8b52f3f386f2
github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36
github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305
github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260512230622-65f10f4cd305
Expand Down
8 changes: 4 additions & 4 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading