-
Notifications
You must be signed in to change notification settings - Fork 2k
Add ConfidentialModule for confidential workflow execution #21298
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
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "chainlink": patch | ||
| --- | ||
|
|
||
| Add ConfidentialModule and attributes plumbing for confidential CRE workflows #added #db_update #wip |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,184 @@ | ||||||||||||||
| package v2 | ||||||||||||||
|
|
||||||||||||||
| import ( | ||||||||||||||
| "context" | ||||||||||||||
| "crypto/sha256" | ||||||||||||||
| "encoding/json" | ||||||||||||||
| "fmt" | ||||||||||||||
|
|
||||||||||||||
| "google.golang.org/protobuf/proto" | ||||||||||||||
| "google.golang.org/protobuf/types/known/anypb" | ||||||||||||||
|
|
||||||||||||||
| "github.com/smartcontractkit/chainlink-common/pkg/capabilities" | ||||||||||||||
| "github.com/smartcontractkit/chainlink-common/pkg/logger" | ||||||||||||||
| "github.com/smartcontractkit/chainlink-common/pkg/types/core" | ||||||||||||||
| "github.com/smartcontractkit/chainlink-common/pkg/workflows/wasm/host" | ||||||||||||||
|
|
||||||||||||||
| confworkflowtypes "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/actions/confidentialworkflow" | ||||||||||||||
| sdkpb "github.com/smartcontractkit/chainlink-protos/cre/go/sdk" | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| const confidentialWorkflowsCapabilityID = "confidential-workflows@1.0.0-alpha" | ||||||||||||||
|
|
||||||||||||||
| // WorkflowAttributes is the JSON structure stored in WorkflowSpec.Attributes. | ||||||||||||||
| type WorkflowAttributes struct { | ||||||||||||||
| Confidential bool `json:"confidential"` | ||||||||||||||
| VaultDonSecrets []SecretIdentifier `json:"vault_don_secrets"` | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // SecretIdentifier identifies a secret in VaultDON. | ||||||||||||||
| type SecretIdentifier struct { | ||||||||||||||
| Key string `json:"key"` | ||||||||||||||
| Namespace string `json:"namespace,omitempty"` | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // ParseWorkflowAttributes parses the Attributes JSON from a WorkflowSpec. | ||||||||||||||
| // Returns a zero-value struct if data is nil or empty. | ||||||||||||||
| func ParseWorkflowAttributes(data []byte) (WorkflowAttributes, error) { | ||||||||||||||
| var attrs WorkflowAttributes | ||||||||||||||
| if len(data) == 0 { | ||||||||||||||
| return attrs, nil | ||||||||||||||
| } | ||||||||||||||
| if err := json.Unmarshal(data, &attrs); err != nil { | ||||||||||||||
| return attrs, fmt.Errorf("failed to parse workflow attributes: %w", err) | ||||||||||||||
| } | ||||||||||||||
| return attrs, nil | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // IsConfidential returns true if the Attributes JSON has "confidential": true. | ||||||||||||||
| // Returns an error if the attributes contain malformed JSON, so callers can | ||||||||||||||
| // fail loudly rather than silently falling through to non-confidential execution. | ||||||||||||||
| func IsConfidential(data []byte) (bool, error) { | ||||||||||||||
| attrs, err := ParseWorkflowAttributes(data) | ||||||||||||||
| if err != nil { | ||||||||||||||
| return false, err | ||||||||||||||
| } | ||||||||||||||
| return attrs.Confidential, nil | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
48
to
57
|
||||||||||||||
|
|
||||||||||||||
| // ConfidentialModule implements host.ModuleV2 for confidential workflows. | ||||||||||||||
| // Instead of running WASM locally, it delegates execution to the | ||||||||||||||
| // confidential-workflows capability via the CapabilitiesRegistry. | ||||||||||||||
| type ConfidentialModule struct { | ||||||||||||||
| capRegistry core.CapabilitiesRegistry | ||||||||||||||
| binaryURL string | ||||||||||||||
| binaryHash []byte | ||||||||||||||
| workflowID string | ||||||||||||||
| workflowOwner string | ||||||||||||||
| workflowName string | ||||||||||||||
| workflowTag string | ||||||||||||||
| vaultDonSecrets []SecretIdentifier | ||||||||||||||
| lggr logger.Logger | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| var _ host.ModuleV2 = (*ConfidentialModule)(nil) | ||||||||||||||
|
|
||||||||||||||
| func NewConfidentialModule( | ||||||||||||||
| capRegistry core.CapabilitiesRegistry, | ||||||||||||||
| binaryURL string, | ||||||||||||||
| binaryHash []byte, | ||||||||||||||
| workflowID string, | ||||||||||||||
| workflowOwner string, | ||||||||||||||
| workflowName string, | ||||||||||||||
| workflowTag string, | ||||||||||||||
| vaultDonSecrets []SecretIdentifier, | ||||||||||||||
| lggr logger.Logger, | ||||||||||||||
| ) *ConfidentialModule { | ||||||||||||||
| return &ConfidentialModule{ | ||||||||||||||
| capRegistry: capRegistry, | ||||||||||||||
| binaryURL: binaryURL, | ||||||||||||||
| binaryHash: binaryHash, | ||||||||||||||
| workflowID: workflowID, | ||||||||||||||
| workflowOwner: workflowOwner, | ||||||||||||||
| workflowName: workflowName, | ||||||||||||||
| workflowTag: workflowTag, | ||||||||||||||
| vaultDonSecrets: vaultDonSecrets, | ||||||||||||||
| lggr: lggr, | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func (m *ConfidentialModule) Start() {} | ||||||||||||||
| func (m *ConfidentialModule) Close() {} | ||||||||||||||
| func (m *ConfidentialModule) IsLegacyDAG() bool { return false } | ||||||||||||||
|
|
||||||||||||||
| func (m *ConfidentialModule) Execute( | ||||||||||||||
| ctx context.Context, | ||||||||||||||
| request *sdkpb.ExecuteRequest, | ||||||||||||||
| _ host.ExecutionHelper, | ||||||||||||||
| ) (*sdkpb.ExecutionResult, error) { | ||||||||||||||
| execReqBytes, err := proto.Marshal(request) | ||||||||||||||
| if err != nil { | ||||||||||||||
| return nil, fmt.Errorf("failed to marshal ExecuteRequest: %w", err) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| protoSecrets := make([]*confworkflowtypes.SecretIdentifier, len(m.vaultDonSecrets)) | ||||||||||||||
| for i, s := range m.vaultDonSecrets { | ||||||||||||||
| ns := s.Namespace | ||||||||||||||
|
||||||||||||||
| ns := s.Namespace | |
| ns := s.Namespace | |
| // Default to the "main" namespace when none is provided. VaultDON and the | |
| // confidential workflows capability treat "main" as the canonical default | |
| // namespace for secrets, so leaving this empty would not match the expected | |
| // behavior of downstream components. |
Copilot
AI
Feb 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new ConfidentialModule and tryConfidentialEngineCreate function lack test coverage. The codebase shows comprehensive testing for other modules and engine creation flows. Consider adding tests that verify: (1) confidential module creation and configuration, (2) routing logic for workflows with confidential attributes, (3) error handling when the capability is not available, and (4) proper passing of vault secrets and binary hash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tryConfidentialEngineCreate function lacks test coverage. The codebase shows comprehensive testing for tryEngineCreate and other handler flows. Consider adding tests that verify: (1) confidential engine creation when IsConfidential returns true, (2) proper initialization and lifecycle hooks, (3) error handling during engine creation and startup, and (4) integration with the engine registry.