Skip to content
Closed
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
2 changes: 1 addition & 1 deletion core/capabilities/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ func (w *launcher) serveCapabilityV2(ctx context.Context, capID string, methodCo
}

var requestHasher remotetypes.MessageHasher
optInCfg := executable.OptInHasherConfig{IncludeWorkflowTag: w.workflowTagHashFlag}
optInCfg := executable.OptInHasherConfig{IncludeWorkflowTag: w.workflowTagHashFlag, Logger: w.lggr}
switch config.RemoteExecutableConfig.RequestHasherType {
case capabilities.RequestHasherType_Simple:
requestHasher = executable.NewSimpleHasher(optInCfg)
Expand Down
23 changes: 23 additions & 0 deletions core/capabilities/remote/executable/hasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
solcappb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/chain-capabilities/solana"
stellarcappb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/chain-capabilities/stellar"
"github.com/smartcontractkit/chainlink-common/pkg/config"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink-common/pkg/settings/limits"
"github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types"
)
Expand Down Expand Up @@ -233,6 +234,8 @@ type OptInHasherConfig struct {
// zero time.Time{}), so WorkflowTag is included in the hash matching current
// prod behavior. After rollout, set to far-future window to exclude it.
IncludeWorkflowTag limits.RangeLimiter[config.Timestamp]

Logger logger.Logger
}

// baseMetadataFields returns a copy of the metadata containing only the
Expand All @@ -258,13 +261,33 @@ func baseMetadataFields(md capabilities.RequestMetadata) capabilities.RequestMet
// applyMetadataFields returns a copy of the metadata containing the base
// allowlisted fields plus any optional fields whose per-field feature flag is
// active for the given ExecutionTimestamp.
//
// The incoming ctx may not carry the CRE workflow/owner/org values (e.g. it
// originates from the don2don dispatcher's bare stop-channel context). Scoped
// limiters (PerWorkflow.*) resolve their tenant from contexts.CRE, so without
// it Check fails with "missing tenant" and the optional field would be
// silently excluded from the hash on every node. Derive the CRE values from
// the request metadata itself, which is authoritative for the request being
// hashed.
func applyMetadataFields(ctx context.Context, md capabilities.RequestMetadata, cfg OptInHasherConfig) capabilities.RequestMetadata {
result := baseMetadataFields(md)
ts := config.Timestamp(md.ExecutionTimestamp.Unix())
ctx = md.ContextWithCRE(ctx)
if cfg.Logger != nil {
cfg.Logger.Info("applyMetadataFields")
}

if cfg.IncludeWorkflowTag != nil {
if cfg.Logger != nil {
cfg.Logger.Info("applyMetadataFields tag not nil")
}
if err := cfg.IncludeWorkflowTag.Check(ctx, ts); err == nil {
if cfg.Logger != nil {
cfg.Logger.Info("applyMetadataFields including workflow tag")
}
result.WorkflowTag = md.WorkflowTag
} else if cfg.Logger != nil {
cfg.Logger.Infow("applyMetadataFields excluding workflow tag", "err", err)
}
}

Expand Down
34 changes: 34 additions & 0 deletions core/capabilities/remote/executable/hasher_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package executable

import (
"context"
"testing"
"time"

Expand Down Expand Up @@ -398,6 +399,39 @@ func TestSimpleHasher_IncludesWorkflowTag_WithZeroTimestamp(t *testing.T) {
require.NotEqual(t, hash1, hash2) // WorkflowTag included even with zero timestamp
}

// TestSimpleHasher_IncludesWorkflowTag_WithScopedLimiterAndBareCtx reproduces
// the production wiring: launcher.NewLauncher builds the flag via
// limits.Factory.MakeRangeLimiter with the PerWorkflow (workflow-scoped)
// setting, and server.Receive passes the don2don dispatcher's bare context
// which carries no contexts.CRE values. Without deriving the CRE values from
// the request metadata, the scoped limiter fails with "missing tenant" and
// WorkflowTag is silently excluded from the hash on every node.
func TestSimpleHasher_IncludesWorkflowTag_WithScopedLimiterAndBareCtx(t *testing.T) {
t.Parallel()

// ON-by-default window, scoped like cresettings.Default.PerWorkflow.FeatureRequestHashIncludeWorkflowTagActivePeriod
flagSpec := settings.TimeRange(
time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(2100, 1, 1, 0, 0, 0, 0, time.UTC))
flagSpec.Scope = settings.ScopeWorkflow
flag, err := limits.MakeRangeLimiter[commonconfig.Timestamp](limits.Factory{}, flagSpec)
require.NoError(t, err)
defer func() { require.NoError(t, flag.Close()) }()

req1 := getRequestWithWorkflowTag(t, []byte("testdata"), "tag-v1")
req2 := getRequestWithWorkflowTag(t, []byte("testdata"), "tag-v2")

hasher := NewSimpleHasher(OptInHasherConfig{IncludeWorkflowTag: flag})

// Bare ctx without CRE values, as delivered by the don2don dispatcher.
hash1, err := hasher.Hash(context.Background(), req1)
require.NoError(t, err)
hash2, err := hasher.Hash(context.Background(), req2)
require.NoError(t, err)

require.NotEqual(t, hash1, hash2) // WorkflowTag must still be included in the hash
}

func TestSimpleHasher_ExcludesWorkflowTag_WhenFlagInactive(t *testing.T) {
t.Parallel()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func runEVMNegativeTestSuite(t *testing.T, testCases []evmNegativeTest) {
framework.L.Info().Msg("Running EVM Read Regression test")
EVMReadFailsTest(t, testEnv, tCase)
}
t.Fail()
})
}
}
Expand Down
Loading