From 7d082d5005061bb8473c69fa13e04b3594562570 Mon Sep 17 00:00:00 2001 From: Bolek Kulbabinski <1416262+bolekk@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:03:40 -0700 Subject: [PATCH 1/2] Temp hasher logs --- core/capabilities/launcher.go | 2 +- core/capabilities/remote/executable/hasher.go | 12 ++++++++++++ .../regression/cre/cre_regression_suite_test.go | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/core/capabilities/launcher.go b/core/capabilities/launcher.go index 71513be27d6..4c21ba47a43 100644 --- a/core/capabilities/launcher.go +++ b/core/capabilities/launcher.go @@ -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) diff --git a/core/capabilities/remote/executable/hasher.go b/core/capabilities/remote/executable/hasher.go index 2e99d3998f9..0842523aed5 100644 --- a/core/capabilities/remote/executable/hasher.go +++ b/core/capabilities/remote/executable/hasher.go @@ -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" ) @@ -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 @@ -261,9 +264,18 @@ func baseMetadataFields(md capabilities.RequestMetadata) capabilities.RequestMet func applyMetadataFields(ctx context.Context, md capabilities.RequestMetadata, cfg OptInHasherConfig) capabilities.RequestMetadata { result := baseMetadataFields(md) ts := config.Timestamp(md.ExecutionTimestamp.Unix()) + 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 removing tag") + } result.WorkflowTag = md.WorkflowTag } } diff --git a/system-tests/tests/regression/cre/cre_regression_suite_test.go b/system-tests/tests/regression/cre/cre_regression_suite_test.go index f41a465de38..321bba7f8d1 100644 --- a/system-tests/tests/regression/cre/cre_regression_suite_test.go +++ b/system-tests/tests/regression/cre/cre_regression_suite_test.go @@ -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() }) } } From 58f1dfed942d8f6f81d299825bf3284d6c80a97b Mon Sep 17 00:00:00 2001 From: Bolek Kulbabinski <1416262+bolekk@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:06:25 -0700 Subject: [PATCH 2/2] fix --- core/capabilities/remote/executable/hasher.go | 13 ++++++- .../remote/executable/hasher_test.go | 34 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/core/capabilities/remote/executable/hasher.go b/core/capabilities/remote/executable/hasher.go index 0842523aed5..596b14e51ad 100644 --- a/core/capabilities/remote/executable/hasher.go +++ b/core/capabilities/remote/executable/hasher.go @@ -261,9 +261,18 @@ 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") } @@ -274,9 +283,11 @@ func applyMetadataFields(ctx context.Context, md capabilities.RequestMetadata, c } if err := cfg.IncludeWorkflowTag.Check(ctx, ts); err == nil { if cfg.Logger != nil { - cfg.Logger.Info("applyMetadataFields removing tag") + 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) } } diff --git a/core/capabilities/remote/executable/hasher_test.go b/core/capabilities/remote/executable/hasher_test.go index 134c83e977c..564d15d612a 100644 --- a/core/capabilities/remote/executable/hasher_test.go +++ b/core/capabilities/remote/executable/hasher_test.go @@ -1,6 +1,7 @@ package executable import ( + "context" "testing" "time" @@ -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()