From bc82b9de473874ee9b267825a0acda2c96bb6335 Mon Sep 17 00:00:00 2001 From: skudasov Date: Fri, 24 Jul 2026 12:52:57 +0200 Subject: [PATCH 1/7] wip --- core/services/chainlink/application.go | 17 ++ core/services/cljobinfo/cljobinfo.go | 207 +++++++++++++++++++++++++ core/services/cljobinfo/emit_test.go | 116 ++++++++++++++ 3 files changed, 340 insertions(+) create mode 100644 core/services/cljobinfo/cljobinfo.go create mode 100644 core/services/cljobinfo/emit_test.go diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 468b0c6529b..8e19b3cbe53 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -62,6 +62,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/blockheaderfeeder" "github.com/smartcontractkit/chainlink/v2/core/services/ccv/ccvcommitteeverifier" "github.com/smartcontractkit/chainlink/v2/core/services/ccv/ccvexecutor" + "github.com/smartcontractkit/chainlink/v2/core/services/cljobinfo" "github.com/smartcontractkit/chainlink/v2/core/services/cre" "github.com/smartcontractkit/chainlink/v2/core/services/cresettings" "github.com/smartcontractkit/chainlink/v2/core/services/cron" @@ -589,6 +590,9 @@ func NewApplication(ctx context.Context, opts ApplicationOpts) (Application, err ) srvcs = append(srvcs, workflowORM) + // Superseded by cljobinfo.Reporter (wired below), which emits a full, + // type-agnostic job definition; retained until consumers migrate off the + // flat submitter-address projection. nodePlatformJobInfo := NewNodePlatformJobInfoService(NewNodePlatformJobInfoConfig(opts, jobORM, relayChainInterops)) srvcs = append(srvcs, &nodePlatformJobInfo) @@ -862,6 +866,8 @@ func NewApplication(ctx context.Context, opts ApplicationOpts) (Application, err } hostname, _ := os.Hostname() + // Superseded by cljobinfo.Reporter (wired below); this OCR2-only reporter is + // retained until its consumers migrate to the generic CLJobInfo schema. jobSpecReporter := jobspec.NewJobSpecReporter( cfg.JobSpecReporter(), jobSpawner, @@ -874,6 +880,17 @@ func NewApplication(ctx context.Context, opts ApplicationOpts) (Application, err ) srvcs = append(srvcs, jobSpecReporter) + // CLJobInfo: single, type-agnostic reporter that emits a full job + // definition (as TOML) on create/delete/heartbeat for every job type. + clJobInfoReporter := cljobinfo.NewReporter( + jobSpawner, + beholder.GetEmitter(), + cljobinfo.NodeIdentity{CSAPublicKey: csaPubKeyHex, NodeVersion: static.Version, Hostname: hostname}, + cljobinfo.DefaultPollInterval, + globalLogger, + ) + srvcs = append(srvcs, clJobInfoReporter) + for _, s := range srvcs { if s == nil { panic("service unexpectedly nil") diff --git a/core/services/cljobinfo/cljobinfo.go b/core/services/cljobinfo/cljobinfo.go new file mode 100644 index 00000000000..0e251fb651c --- /dev/null +++ b/core/services/cljobinfo/cljobinfo.go @@ -0,0 +1,207 @@ +// Package cljobinfo provides a single, generic way for any part of the core +// node to emit a full job definition as telemetry. +// +// It supersedes the two prior, divergent approaches: +// +// - NodePlatformJobInfoService (core/services/chainlink/node_platform.go) +// emits a flat, denormalized projection (chain_id/job_type/field_path -> +// addresses) aggregated across all jobs. Generic schema, but only carries +// submitter addresses and has hardcoded per-spec extractors. +// - JobSpecReporter (core/services/nodestatusreporter/jobspec) emits a +// rich, per-job, create/delete/heartbeat event, but models each spec as a +// dedicated proto message and only supports OCR2. +// +// cljobinfo keeps the better half of each: the event-driven, per-job lifecycle +// and node identity of JobSpecReporter, and a schema-agnostic payload like +// NodePlatformJobInfo. The full, type-specific job definition is carried as a +// raw TOML string, so any job type is supported with no per-type code here and +// none for future job types. +package cljobinfo + +import ( + "context" + "fmt" + "time" + + "github.com/pelletier/go-toml" + "google.golang.org/protobuf/proto" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-common/pkg/services" + commonv1 "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1" + + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/job" +) + +const ( + // Domain, Entity and DataSchema identify CLJobInfo telemetry on Beholder. + Domain = "node-platform" + Entity = "common.v1.CLJobInfo" + DataSchema = "/node-platform/common/v1" + + ServiceName = "CLJobInfoReporter" + + // DefaultPollInterval is the heartbeat cadence when a caller does not + // specify one, matching the node-platform build/job info beat. + DefaultPollInterval = 3 * time.Minute +) + +// NodeIdentity is the node-level context attached to every emitted CLJobInfo. +type NodeIdentity struct { + CSAPublicKey string + NodeVersion string + Hostname string +} + +// Build converts any job.Job into its generic CLJobInfo representation. +// +// The complete, type-specific job definition is captured as TOML, so no +// per-job-type code lives here and none is needed for future job types. If the +// job cannot be TOML-encoded, Build still returns a fully populated identity +// payload (with an empty SpecToml) alongside the encoding error, so callers can +// choose to emit the envelope and log the failure rather than drop the event. +func Build(jb job.Job, trigger commonv1.CLJobInfoTrigger, id NodeIdentity, now time.Time) (*commonv1.CLJobInfo, error) { + info := &commonv1.CLJobInfo{ + CsaPublicKey: id.CSAPublicKey, + NodeVersion: id.NodeVersion, + Hostname: id.Hostname, + ExternalJobId: jb.ExternalJobID.String(), + JobId: jb.ID, + Name: jb.Name.ValueOrZero(), + JobType: string(jb.Type), + SchemaVersion: jb.SchemaVersion, + ForwardingAllowed: jb.ForwardingAllowed, + CreatedAt: formatTime(jb.CreatedAt), + Trigger: trigger, + Timestamp: now.UTC().Format(time.RFC3339Nano), + } + if jb.GasLimit.Valid { + info.GasLimit = new(jb.GasLimit.Uint32) + } + if jb.StreamID != nil { + info.StreamId = new(*jb.StreamID) + } + + specTOML, err := jobTOML(jb) + if err != nil { + return info, fmt.Errorf("encoding job %s (%d) spec to TOML: %w", jb.ExternalJobID, jb.ID, err) + } + info.SpecToml = specTOML + + return info, nil +} + +// Emit marshals a CLJobInfo and publishes it to Beholder. +func Emit(ctx context.Context, emitter beholder.Emitter, info *commonv1.CLJobInfo) error { + payload, err := proto.Marshal(info) + if err != nil { + return fmt.Errorf("marshaling CLJobInfo: %w", err) + } + + err = emitter.Emit(ctx, payload, + beholder.AttrKeyDomain, Domain, + beholder.AttrKeyEntity, Entity, + beholder.AttrKeyDataSchema, DataSchema, + ) + if err != nil { + return fmt.Errorf("emitting CLJobInfo: %w", err) + } + return nil +} + +// jobTOML serializes the entire job definition to TOML. Marshaling the whole +// job.Job captures both the common top-level fields and the single active +// type-specific spec, so all fields for any job type are included without +// enumerating them. +func jobTOML(jb job.Job) (string, error) { + out, err := toml.Marshal(jb) + if err != nil { + return "", err + } + return string(out), nil +} + +func formatTime(t time.Time) string { + if t.IsZero() { + return "" + } + return t.UTC().Format(time.RFC3339Nano) +} + +var _ job.Listener = (*Reporter)(nil) + +// Reporter emits a CLJobInfo for every job on create, delete, and on a +// recurring heartbeat. It is job-type agnostic: any job the node runs is +// reported through the single generic schema. +type Reporter struct { + services.Service + eng *services.Engine + + spawner job.Spawner + emitter beholder.Emitter + identity NodeIdentity + pollInterval time.Duration +} + +// NewReporter builds a Reporter that reports every job the node runs. +func NewReporter( + spawner job.Spawner, + emitter beholder.Emitter, + identity NodeIdentity, + pollInterval time.Duration, + lggr logger.Logger, +) *Reporter { + r := &Reporter{ + spawner: spawner, + emitter: emitter, + identity: identity, + pollInterval: pollInterval, + } + r.Service, r.eng = services.Config{ + Name: ServiceName, + Start: r.start, + }.NewServiceEngine(lggr) + return r +} + +func (r *Reporter) start(_ context.Context) error { + r.spawner.RegisterListener(r) + r.eng.GoTick(services.NewTicker(r.pollInterval), r.pollAllJobs) + return nil +} + +func (r *Reporter) HealthReport() map[string]error { + return map[string]error{ServiceName: r.Ready()} +} + +// AfterJobStarted emits a create event when a job starts. +func (r *Reporter) AfterJobStarted(ctx context.Context, jb job.Job) { + r.emitForJob(ctx, jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE) +} + +// AfterJobStopped emits a delete event when a job is removed. +func (r *Reporter) AfterJobStopped(ctx context.Context, jb job.Job) { + r.emitForJob(ctx, jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_DELETE) +} + +// pollAllJobs emits a heartbeat event for every active job. +func (r *Reporter) pollAllJobs(ctx context.Context) { + for _, jb := range r.spawner.ActiveJobs() { + r.emitForJob(ctx, jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT) + } +} + +func (r *Reporter) emitForJob(ctx context.Context, jb job.Job, trigger commonv1.CLJobInfoTrigger) { + info, err := Build(jb, trigger, r.identity, time.Now()) + if err != nil { + // Spec encoding failed; still emit the identity envelope so the job is + // accounted for, but flag the gap. + r.eng.Warnw("Failed to encode job spec for CLJobInfo; emitting without spec_toml", + "jobID", jb.ID, "externalJobID", jb.ExternalJobID, "error", err) + } + + if err := Emit(ctx, r.emitter, info); err != nil { + r.eng.Warnw("Failed to emit CLJobInfo", "jobID", jb.ID, "trigger", trigger, "error", err) + } +} diff --git a/core/services/cljobinfo/emit_test.go b/core/services/cljobinfo/emit_test.go new file mode 100644 index 00000000000..c58f327bad2 --- /dev/null +++ b/core/services/cljobinfo/emit_test.go @@ -0,0 +1,116 @@ +package cljobinfo_test + +import ( + "testing" + "time" + + "github.com/pelletier/go-toml" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "gopkg.in/guregu/null.v4" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-common/pkg/beholder/beholdertest" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" + commonv1 "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1" + + "github.com/smartcontractkit/chainlink/v2/core/services/cljobinfo" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" +) + +func sampleJob() job.Job { + streamID := uint32(42) + return job.Job{ + ID: 7, + Name: null.StringFrom("my-ocr2-job"), + Type: job.OffchainReporting2, + SchemaVersion: 1, + ForwardingAllowed: true, + StreamID: &streamID, + CreatedAt: time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC), + OCR2OracleSpec: &job.OCR2OracleSpec{ + Relay: "evm", + ChainID: "1", + PluginType: commontypes.Median, + ContractID: "0xcccccccccccccccccccccccccccccccccccccccc", + TransmitterID: null.StringFrom("0x1111111111111111111111111111111111111111"), + RelayConfig: job.JSONConfig{ + "chainID": "1", + "sendingKeys": []any{"0x1111111111111111111111111111111111111111"}, + }, + }, + VRFSpec: nil, + Pipeline: pipeline.Pipeline{Tasks: []pipeline.Task{ + &pipeline.ETHTxTask{From: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, + }}, + } +} + +// TestBuild_EncodesFullSpecAsTOML is the load-bearing check: an arbitrary job +// must round-trip to TOML with no per-type code. +func TestBuild_EncodesFullSpecAsTOML(t *testing.T) { + jb := sampleJob() + id := cljobinfo.NodeIdentity{CSAPublicKey: "csa", NodeVersion: "1.2.3", Hostname: "host-1"} + + info, err := cljobinfo.Build(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, id, time.Date(2026, 7, 24, 12, 0, 0, 0, time.UTC)) + require.NoError(t, err) + + require.Equal(t, "csa", info.CsaPublicKey) + require.Equal(t, "1.2.3", info.NodeVersion) + require.Equal(t, "host-1", info.Hostname) + require.Equal(t, int32(7), info.JobId) + require.Equal(t, "my-ocr2-job", info.Name) + require.Equal(t, "offchainreporting2", info.JobType) + require.Equal(t, uint32(1), info.SchemaVersion) + require.True(t, info.ForwardingAllowed) + require.NotNil(t, info.StreamId) + require.Equal(t, uint32(42), *info.StreamId) + require.Equal(t, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, info.Trigger) + require.NotEmpty(t, info.Timestamp) + + // spec_toml must be valid TOML and contain type-specific spec data. + require.NotEmpty(t, info.SpecToml) + var decoded map[string]any + require.NoError(t, toml.Unmarshal([]byte(info.SpecToml), &decoded)) + require.Contains(t, info.SpecToml, "median") + require.Contains(t, info.SpecToml, "0xcccccccccccccccccccccccccccccccccccccccc") +} + +func TestBuild_HandlesMultipleJobTypesGenerically(t *testing.T) { + jobs := []job.Job{ + {Type: job.VRF, VRFSpec: &job.VRFSpec{ + EVMChainID: sqlutil.NewI(4), + FromAddresses: []evmtypes.EIP55Address{evmtypes.MustEIP55Address("0x6666666666666666666666666666666666666666")}, + }}, + {Type: job.BlockhashStore, BlockhashStoreSpec: &job.BlockhashStoreSpec{EVMChainID: sqlutil.NewI(5)}}, + } + for _, jb := range jobs { + info, err := cljobinfo.Build(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT, cljobinfo.NodeIdentity{}, time.Now()) + require.NoErrorf(t, err, "job type %s should encode without per-type code", jb.Type) + require.NotEmpty(t, info.SpecToml) + } +} + +func TestEmit_PublishesToBeholder(t *testing.T) { + obs := beholdertest.NewObserver(t) + + info, err := cljobinfo.Build(sampleJob(), commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, cljobinfo.NodeIdentity{CSAPublicKey: "csa"}, time.Now()) + require.NoError(t, err) + require.NoError(t, cljobinfo.Emit(t.Context(), beholder.GetEmitter(), info)) + + msgs := obs.Messages(t, beholder.AttrKeyEntity, cljobinfo.Entity) + require.NotEmpty(t, msgs) + + msg := msgs[0] + require.Equal(t, cljobinfo.Domain, msg.Attrs[beholder.AttrKeyDomain]) + require.Equal(t, cljobinfo.DataSchema, msg.Attrs[beholder.AttrKeyDataSchema]) + + var payload commonv1.CLJobInfo + require.NoError(t, proto.Unmarshal(msg.Body, &payload)) + require.Equal(t, "csa", payload.CsaPublicKey) + require.Equal(t, "offchainreporting2", payload.JobType) + require.NotEmpty(t, payload.SpecToml) +} From a66cc6d89ea0c84be7f26f607398a84b402e1efd Mon Sep 17 00:00:00 2001 From: gheorghestrimtu Date: Fri, 4 Sep 2026 15:00:10 +0300 Subject: [PATCH 2/7] refactor(nodestatusreporter): report every job type from the existing reporter Fold core/services/cljobinfo into the existing nodestatusreporter/jobspec service instead of adding a parallel reporter. CLJobInfo is now emitted for every job regardless of type, alongside the unchanged OCR2-only JobSpecEvent track. application.go and the [JobSpecReporter] config are unchanged. Requires: https://github.com/smartcontractkit/chainlink-protos/pull/432 RANE-4655 --- core/services/chainlink/application.go | 17 -- core/services/cljobinfo/cljobinfo.go | 207 ------------------ .../nodestatusreporter/jobspec/cl_job_info.go | 134 ++++++++++++ .../jobspec/cl_job_info_test.go} | 76 +++++-- .../jobspec/job_spec_reporter.go | 100 +++++++-- 5 files changed, 277 insertions(+), 257 deletions(-) delete mode 100644 core/services/cljobinfo/cljobinfo.go create mode 100644 core/services/nodestatusreporter/jobspec/cl_job_info.go rename core/services/{cljobinfo/emit_test.go => nodestatusreporter/jobspec/cl_job_info_test.go} (51%) diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 8e19b3cbe53..468b0c6529b 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -62,7 +62,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/blockheaderfeeder" "github.com/smartcontractkit/chainlink/v2/core/services/ccv/ccvcommitteeverifier" "github.com/smartcontractkit/chainlink/v2/core/services/ccv/ccvexecutor" - "github.com/smartcontractkit/chainlink/v2/core/services/cljobinfo" "github.com/smartcontractkit/chainlink/v2/core/services/cre" "github.com/smartcontractkit/chainlink/v2/core/services/cresettings" "github.com/smartcontractkit/chainlink/v2/core/services/cron" @@ -590,9 +589,6 @@ func NewApplication(ctx context.Context, opts ApplicationOpts) (Application, err ) srvcs = append(srvcs, workflowORM) - // Superseded by cljobinfo.Reporter (wired below), which emits a full, - // type-agnostic job definition; retained until consumers migrate off the - // flat submitter-address projection. nodePlatformJobInfo := NewNodePlatformJobInfoService(NewNodePlatformJobInfoConfig(opts, jobORM, relayChainInterops)) srvcs = append(srvcs, &nodePlatformJobInfo) @@ -866,8 +862,6 @@ func NewApplication(ctx context.Context, opts ApplicationOpts) (Application, err } hostname, _ := os.Hostname() - // Superseded by cljobinfo.Reporter (wired below); this OCR2-only reporter is - // retained until its consumers migrate to the generic CLJobInfo schema. jobSpecReporter := jobspec.NewJobSpecReporter( cfg.JobSpecReporter(), jobSpawner, @@ -880,17 +874,6 @@ func NewApplication(ctx context.Context, opts ApplicationOpts) (Application, err ) srvcs = append(srvcs, jobSpecReporter) - // CLJobInfo: single, type-agnostic reporter that emits a full job - // definition (as TOML) on create/delete/heartbeat for every job type. - clJobInfoReporter := cljobinfo.NewReporter( - jobSpawner, - beholder.GetEmitter(), - cljobinfo.NodeIdentity{CSAPublicKey: csaPubKeyHex, NodeVersion: static.Version, Hostname: hostname}, - cljobinfo.DefaultPollInterval, - globalLogger, - ) - srvcs = append(srvcs, clJobInfoReporter) - for _, s := range srvcs { if s == nil { panic("service unexpectedly nil") diff --git a/core/services/cljobinfo/cljobinfo.go b/core/services/cljobinfo/cljobinfo.go deleted file mode 100644 index 0e251fb651c..00000000000 --- a/core/services/cljobinfo/cljobinfo.go +++ /dev/null @@ -1,207 +0,0 @@ -// Package cljobinfo provides a single, generic way for any part of the core -// node to emit a full job definition as telemetry. -// -// It supersedes the two prior, divergent approaches: -// -// - NodePlatformJobInfoService (core/services/chainlink/node_platform.go) -// emits a flat, denormalized projection (chain_id/job_type/field_path -> -// addresses) aggregated across all jobs. Generic schema, but only carries -// submitter addresses and has hardcoded per-spec extractors. -// - JobSpecReporter (core/services/nodestatusreporter/jobspec) emits a -// rich, per-job, create/delete/heartbeat event, but models each spec as a -// dedicated proto message and only supports OCR2. -// -// cljobinfo keeps the better half of each: the event-driven, per-job lifecycle -// and node identity of JobSpecReporter, and a schema-agnostic payload like -// NodePlatformJobInfo. The full, type-specific job definition is carried as a -// raw TOML string, so any job type is supported with no per-type code here and -// none for future job types. -package cljobinfo - -import ( - "context" - "fmt" - "time" - - "github.com/pelletier/go-toml" - "google.golang.org/protobuf/proto" - - "github.com/smartcontractkit/chainlink-common/pkg/beholder" - "github.com/smartcontractkit/chainlink-common/pkg/services" - commonv1 "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1" - - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/job" -) - -const ( - // Domain, Entity and DataSchema identify CLJobInfo telemetry on Beholder. - Domain = "node-platform" - Entity = "common.v1.CLJobInfo" - DataSchema = "/node-platform/common/v1" - - ServiceName = "CLJobInfoReporter" - - // DefaultPollInterval is the heartbeat cadence when a caller does not - // specify one, matching the node-platform build/job info beat. - DefaultPollInterval = 3 * time.Minute -) - -// NodeIdentity is the node-level context attached to every emitted CLJobInfo. -type NodeIdentity struct { - CSAPublicKey string - NodeVersion string - Hostname string -} - -// Build converts any job.Job into its generic CLJobInfo representation. -// -// The complete, type-specific job definition is captured as TOML, so no -// per-job-type code lives here and none is needed for future job types. If the -// job cannot be TOML-encoded, Build still returns a fully populated identity -// payload (with an empty SpecToml) alongside the encoding error, so callers can -// choose to emit the envelope and log the failure rather than drop the event. -func Build(jb job.Job, trigger commonv1.CLJobInfoTrigger, id NodeIdentity, now time.Time) (*commonv1.CLJobInfo, error) { - info := &commonv1.CLJobInfo{ - CsaPublicKey: id.CSAPublicKey, - NodeVersion: id.NodeVersion, - Hostname: id.Hostname, - ExternalJobId: jb.ExternalJobID.String(), - JobId: jb.ID, - Name: jb.Name.ValueOrZero(), - JobType: string(jb.Type), - SchemaVersion: jb.SchemaVersion, - ForwardingAllowed: jb.ForwardingAllowed, - CreatedAt: formatTime(jb.CreatedAt), - Trigger: trigger, - Timestamp: now.UTC().Format(time.RFC3339Nano), - } - if jb.GasLimit.Valid { - info.GasLimit = new(jb.GasLimit.Uint32) - } - if jb.StreamID != nil { - info.StreamId = new(*jb.StreamID) - } - - specTOML, err := jobTOML(jb) - if err != nil { - return info, fmt.Errorf("encoding job %s (%d) spec to TOML: %w", jb.ExternalJobID, jb.ID, err) - } - info.SpecToml = specTOML - - return info, nil -} - -// Emit marshals a CLJobInfo and publishes it to Beholder. -func Emit(ctx context.Context, emitter beholder.Emitter, info *commonv1.CLJobInfo) error { - payload, err := proto.Marshal(info) - if err != nil { - return fmt.Errorf("marshaling CLJobInfo: %w", err) - } - - err = emitter.Emit(ctx, payload, - beholder.AttrKeyDomain, Domain, - beholder.AttrKeyEntity, Entity, - beholder.AttrKeyDataSchema, DataSchema, - ) - if err != nil { - return fmt.Errorf("emitting CLJobInfo: %w", err) - } - return nil -} - -// jobTOML serializes the entire job definition to TOML. Marshaling the whole -// job.Job captures both the common top-level fields and the single active -// type-specific spec, so all fields for any job type are included without -// enumerating them. -func jobTOML(jb job.Job) (string, error) { - out, err := toml.Marshal(jb) - if err != nil { - return "", err - } - return string(out), nil -} - -func formatTime(t time.Time) string { - if t.IsZero() { - return "" - } - return t.UTC().Format(time.RFC3339Nano) -} - -var _ job.Listener = (*Reporter)(nil) - -// Reporter emits a CLJobInfo for every job on create, delete, and on a -// recurring heartbeat. It is job-type agnostic: any job the node runs is -// reported through the single generic schema. -type Reporter struct { - services.Service - eng *services.Engine - - spawner job.Spawner - emitter beholder.Emitter - identity NodeIdentity - pollInterval time.Duration -} - -// NewReporter builds a Reporter that reports every job the node runs. -func NewReporter( - spawner job.Spawner, - emitter beholder.Emitter, - identity NodeIdentity, - pollInterval time.Duration, - lggr logger.Logger, -) *Reporter { - r := &Reporter{ - spawner: spawner, - emitter: emitter, - identity: identity, - pollInterval: pollInterval, - } - r.Service, r.eng = services.Config{ - Name: ServiceName, - Start: r.start, - }.NewServiceEngine(lggr) - return r -} - -func (r *Reporter) start(_ context.Context) error { - r.spawner.RegisterListener(r) - r.eng.GoTick(services.NewTicker(r.pollInterval), r.pollAllJobs) - return nil -} - -func (r *Reporter) HealthReport() map[string]error { - return map[string]error{ServiceName: r.Ready()} -} - -// AfterJobStarted emits a create event when a job starts. -func (r *Reporter) AfterJobStarted(ctx context.Context, jb job.Job) { - r.emitForJob(ctx, jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE) -} - -// AfterJobStopped emits a delete event when a job is removed. -func (r *Reporter) AfterJobStopped(ctx context.Context, jb job.Job) { - r.emitForJob(ctx, jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_DELETE) -} - -// pollAllJobs emits a heartbeat event for every active job. -func (r *Reporter) pollAllJobs(ctx context.Context) { - for _, jb := range r.spawner.ActiveJobs() { - r.emitForJob(ctx, jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT) - } -} - -func (r *Reporter) emitForJob(ctx context.Context, jb job.Job, trigger commonv1.CLJobInfoTrigger) { - info, err := Build(jb, trigger, r.identity, time.Now()) - if err != nil { - // Spec encoding failed; still emit the identity envelope so the job is - // accounted for, but flag the gap. - r.eng.Warnw("Failed to encode job spec for CLJobInfo; emitting without spec_toml", - "jobID", jb.ID, "externalJobID", jb.ExternalJobID, "error", err) - } - - if err := Emit(ctx, r.emitter, info); err != nil { - r.eng.Warnw("Failed to emit CLJobInfo", "jobID", jb.ID, "trigger", trigger, "error", err) - } -} diff --git a/core/services/nodestatusreporter/jobspec/cl_job_info.go b/core/services/nodestatusreporter/jobspec/cl_job_info.go new file mode 100644 index 00000000000..f5852cc36a0 --- /dev/null +++ b/core/services/nodestatusreporter/jobspec/cl_job_info.go @@ -0,0 +1,134 @@ +package jobspec + +import ( + "context" + "fmt" + "time" + + "github.com/pelletier/go-toml" + "google.golang.org/protobuf/proto" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + commonv1 "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1" + + "github.com/smartcontractkit/chainlink/v2/core/services/job" +) + +// CLJobInfo is the generic, job-type-agnostic half of this reporter. Where +// JobSpecEvent models one job type (OCR2) field by field, CLJobInfo carries the +// job's common identity plus the complete definition as a raw TOML string, so +// every job the node runs is reported through a single schema with no +// per-type code here and none needed for future job types. +// +// It is emitted on the same triggers and from the same service as JobSpecEvent +// rather than from a parallel one, so there is exactly one place in the node +// that reports what jobs it runs. Once consumers have migrated, the OCR2-only +// half can be deleted from here without touching the wiring. +const ( + // Domain, Entity and DataSchema identify CLJobInfo telemetry on Beholder. + Domain = "node-platform" + Entity = "common.v1.CLJobInfo" + DataSchema = "/node-platform/common/v1" +) + +// NodeIdentity is the node-level context attached to every emitted CLJobInfo. +type NodeIdentity struct { + CSAPublicKey string + NodeVersion string + Hostname string +} + +// JobProposal is the Job Distributor provenance for a job that arrived as an +// approved job proposal. Jobs created directly (CLI, UI, TOML on disk) have no +// proposal, and the zero value leaves the corresponding CLJobInfo fields unset +// — which is how a consumer tells a managed job from an unmanaged one. +type JobProposal struct { + FeedsManagerID int64 + RemoteUUID string + SpecVersion int32 + ProposedAt time.Time + ApprovedAt time.Time +} + +// BuildCLJobInfo converts any job.Job into its generic CLJobInfo representation. +// +// prop is optional: pass nil for a job with no Job Distributor proposal. +// +// If the job cannot be TOML-encoded, BuildCLJobInfo still returns a fully +// populated identity payload (with an empty SpecToml) alongside the encoding +// error, so callers can choose to emit the envelope and log the failure rather +// than drop the event. +func BuildCLJobInfo(jb job.Job, trigger commonv1.CLJobInfoTrigger, id NodeIdentity, prop *JobProposal, now time.Time) (*commonv1.CLJobInfo, error) { + info := &commonv1.CLJobInfo{ + CsaPublicKey: id.CSAPublicKey, + NodeVersion: id.NodeVersion, + Hostname: id.Hostname, + ExternalJobId: jb.ExternalJobID.String(), + JobId: jb.ID, + Name: jb.Name.ValueOrZero(), + JobType: string(jb.Type), + SchemaVersion: jb.SchemaVersion, + ForwardingAllowed: jb.ForwardingAllowed, + CreatedAt: formatCLJobInfoTime(jb.CreatedAt), + Trigger: trigger, + Timestamp: now.UTC().Format(time.RFC3339Nano), + } + if jb.GasLimit.Valid { + info.GasLimit = new(jb.GasLimit.Uint32) + } + if jb.StreamID != nil { + info.StreamId = new(*jb.StreamID) + } + if prop != nil { + info.FeedsManagerId = &prop.FeedsManagerID + info.RemoteUuid = &prop.RemoteUUID + info.SpecVersion = &prop.SpecVersion + info.ProposedAt = new(formatCLJobInfoTime(prop.ProposedAt)) + info.ApprovedAt = new(formatCLJobInfoTime(prop.ApprovedAt)) + } + + specTOML, err := jobTOML(jb) + if err != nil { + return info, fmt.Errorf("encoding job %s (%d) spec to TOML: %w", jb.ExternalJobID, jb.ID, err) + } + info.SpecToml = specTOML + + return info, nil +} + +// EmitCLJobInfo marshals a CLJobInfo and publishes it to Beholder. +func EmitCLJobInfo(ctx context.Context, emitter beholder.Emitter, info *commonv1.CLJobInfo) error { + payload, err := proto.Marshal(info) + if err != nil { + return fmt.Errorf("marshaling CLJobInfo: %w", err) + } + + err = emitter.Emit(ctx, payload, + beholder.AttrKeyDomain, Domain, + beholder.AttrKeyEntity, Entity, + beholder.AttrKeyDataSchema, DataSchema, + ) + if err != nil { + return fmt.Errorf("emitting CLJobInfo: %w", err) + } + return nil +} + +// jobTOML serializes the entire job definition to TOML. Marshaling the whole +// job.Job captures both the common top-level fields and the single active +// type-specific spec, so all fields for any job type are included without +// enumerating them. +func jobTOML(jb job.Job) (string, error) { + out, err := toml.Marshal(jb) + if err != nil { + return "", err + } + return string(out), nil +} + +func formatCLJobInfoTime(t time.Time) string { + if t.IsZero() { + return "" + } + return t.UTC().Format(time.RFC3339Nano) +} diff --git a/core/services/cljobinfo/emit_test.go b/core/services/nodestatusreporter/jobspec/cl_job_info_test.go similarity index 51% rename from core/services/cljobinfo/emit_test.go rename to core/services/nodestatusreporter/jobspec/cl_job_info_test.go index c58f327bad2..0b326c7e1c1 100644 --- a/core/services/cljobinfo/emit_test.go +++ b/core/services/nodestatusreporter/jobspec/cl_job_info_test.go @@ -1,4 +1,4 @@ -package cljobinfo_test +package jobspec_test import ( "testing" @@ -16,12 +16,12 @@ import ( evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" commonv1 "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1" - "github.com/smartcontractkit/chainlink/v2/core/services/cljobinfo" "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/nodestatusreporter/jobspec" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" ) -func sampleJob() job.Job { +func clJobInfoSampleJob() job.Job { streamID := uint32(42) return job.Job{ ID: 7, @@ -42,20 +42,19 @@ func sampleJob() job.Job { "sendingKeys": []any{"0x1111111111111111111111111111111111111111"}, }, }, - VRFSpec: nil, Pipeline: pipeline.Pipeline{Tasks: []pipeline.Task{ &pipeline.ETHTxTask{From: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, }}, } } -// TestBuild_EncodesFullSpecAsTOML is the load-bearing check: an arbitrary job -// must round-trip to TOML with no per-type code. -func TestBuild_EncodesFullSpecAsTOML(t *testing.T) { - jb := sampleJob() - id := cljobinfo.NodeIdentity{CSAPublicKey: "csa", NodeVersion: "1.2.3", Hostname: "host-1"} +// TestBuildCLJobInfo_EncodesFullSpecAsTOML is the load-bearing check: an +// arbitrary job must round-trip to TOML with no per-type code. +func TestBuildCLJobInfo_EncodesFullSpecAsTOML(t *testing.T) { + jb := clJobInfoSampleJob() + id := jobspec.NodeIdentity{CSAPublicKey: "csa", NodeVersion: "1.2.3", Hostname: "host-1"} - info, err := cljobinfo.Build(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, id, time.Date(2026, 7, 24, 12, 0, 0, 0, time.UTC)) + info, err := jobspec.BuildCLJobInfo(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, id, nil, time.Date(2026, 7, 24, 12, 0, 0, 0, time.UTC)) require.NoError(t, err) require.Equal(t, "csa", info.CsaPublicKey) @@ -79,7 +78,7 @@ func TestBuild_EncodesFullSpecAsTOML(t *testing.T) { require.Contains(t, info.SpecToml, "0xcccccccccccccccccccccccccccccccccccccccc") } -func TestBuild_HandlesMultipleJobTypesGenerically(t *testing.T) { +func TestBuildCLJobInfo_HandlesMultipleJobTypesGenerically(t *testing.T) { jobs := []job.Job{ {Type: job.VRF, VRFSpec: &job.VRFSpec{ EVMChainID: sqlutil.NewI(4), @@ -88,25 +87,66 @@ func TestBuild_HandlesMultipleJobTypesGenerically(t *testing.T) { {Type: job.BlockhashStore, BlockhashStoreSpec: &job.BlockhashStoreSpec{EVMChainID: sqlutil.NewI(5)}}, } for _, jb := range jobs { - info, err := cljobinfo.Build(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT, cljobinfo.NodeIdentity{}, time.Now()) + info, err := jobspec.BuildCLJobInfo(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT, jobspec.NodeIdentity{}, nil, time.Now()) require.NoErrorf(t, err, "job type %s should encode without per-type code", jb.Type) require.NotEmpty(t, info.SpecToml) } } -func TestEmit_PublishesToBeholder(t *testing.T) { +// TestBuildCLJobInfo_CarriesJobDistributorProvenance covers the JD join key: +// remote_uuid is what links this event back to api.job.v1.Job.uuid. +func TestBuildCLJobInfo_CarriesJobDistributorProvenance(t *testing.T) { + proposedAt := time.Date(2026, 7, 20, 9, 0, 0, 0, time.UTC) + approvedAt := time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC) + prop := &jobspec.JobProposal{ + FeedsManagerID: 3, + RemoteUUID: "6d7d9d1a-0d0f-4b3f-9a2f-2e4a1c0b8d55", + SpecVersion: 2, + ProposedAt: proposedAt, + ApprovedAt: approvedAt, + } + + info, err := jobspec.BuildCLJobInfo(clJobInfoSampleJob(), commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, jobspec.NodeIdentity{}, prop, time.Now()) + require.NoError(t, err) + + require.NotNil(t, info.FeedsManagerId) + require.Equal(t, int64(3), *info.FeedsManagerId) + require.NotNil(t, info.RemoteUuid) + require.Equal(t, "6d7d9d1a-0d0f-4b3f-9a2f-2e4a1c0b8d55", *info.RemoteUuid) + require.NotNil(t, info.SpecVersion) + require.Equal(t, int32(2), *info.SpecVersion) + require.NotNil(t, info.ProposedAt) + require.Equal(t, proposedAt.Format(time.RFC3339Nano), *info.ProposedAt) + require.NotNil(t, info.ApprovedAt) + require.Equal(t, approvedAt.Format(time.RFC3339Nano), *info.ApprovedAt) +} + +// TestBuildCLJobInfo_UnmanagedJobHasNoProvenance: an unset feeds_manager_id is +// how a consumer tells a directly-created job from a JD-managed one. +func TestBuildCLJobInfo_UnmanagedJobHasNoProvenance(t *testing.T) { + info, err := jobspec.BuildCLJobInfo(clJobInfoSampleJob(), commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, jobspec.NodeIdentity{}, nil, time.Now()) + require.NoError(t, err) + + require.Nil(t, info.FeedsManagerId) + require.Nil(t, info.RemoteUuid) + require.Nil(t, info.SpecVersion) + require.Nil(t, info.ProposedAt) + require.Nil(t, info.ApprovedAt) +} + +func TestEmitCLJobInfo_PublishesToBeholder(t *testing.T) { obs := beholdertest.NewObserver(t) - info, err := cljobinfo.Build(sampleJob(), commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, cljobinfo.NodeIdentity{CSAPublicKey: "csa"}, time.Now()) + info, err := jobspec.BuildCLJobInfo(clJobInfoSampleJob(), commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, jobspec.NodeIdentity{CSAPublicKey: "csa"}, nil, time.Now()) require.NoError(t, err) - require.NoError(t, cljobinfo.Emit(t.Context(), beholder.GetEmitter(), info)) + require.NoError(t, jobspec.EmitCLJobInfo(t.Context(), beholder.GetEmitter(), info)) - msgs := obs.Messages(t, beholder.AttrKeyEntity, cljobinfo.Entity) + msgs := obs.Messages(t, beholder.AttrKeyEntity, jobspec.Entity) require.NotEmpty(t, msgs) msg := msgs[0] - require.Equal(t, cljobinfo.Domain, msg.Attrs[beholder.AttrKeyDomain]) - require.Equal(t, cljobinfo.DataSchema, msg.Attrs[beholder.AttrKeyDataSchema]) + require.Equal(t, jobspec.Domain, msg.Attrs[beholder.AttrKeyDomain]) + require.Equal(t, jobspec.DataSchema, msg.Attrs[beholder.AttrKeyDataSchema]) var payload commonv1.CLJobInfo require.NoError(t, proto.Unmarshal(msg.Body, &payload)) diff --git a/core/services/nodestatusreporter/jobspec/job_spec_reporter.go b/core/services/nodestatusreporter/jobspec/job_spec_reporter.go index 9ee6b91b8de..ea3ed5969a3 100644 --- a/core/services/nodestatusreporter/jobspec/job_spec_reporter.go +++ b/core/services/nodestatusreporter/jobspec/job_spec_reporter.go @@ -14,6 +14,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/beholder" "github.com/smartcontractkit/chainlink-common/pkg/services" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + commonv1 "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1" coreconfig "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -29,6 +30,17 @@ var _ job.Listener = (*Service)(nil) // Service polls active jobs and pushes their specs to Beholder, and also emits // on job create/delete via the job.Listener interface. +// +// It emits two payloads on every trigger: +// +// - CLJobInfo, for every job the node runs regardless of type, carrying the +// complete definition as TOML (see cl_job_info.go). +// - JobSpecEvent, the original OCR2-only projection, for jobs passing the +// EnabledOCR2PluginTypes gate. +// +// The second is superseded by the first and is retained only until its +// consumers migrate; once they have, ShouldEmit, EmitForJob and the events +// package can be deleted from here without touching the service wiring. type Service struct { services.Service eng *services.Engine @@ -88,34 +100,92 @@ func (s *Service) HealthReport() map[string]error { // AfterJobStarted emits a create event when a job starts. func (s *Service) AfterJobStarted(ctx context.Context, jb job.Job) { - if !s.ShouldEmit(&jb) { - return - } - if err := s.EmitForJob(ctx, jb, events.EmissionTrigger_EMISSION_TRIGGER_CREATE); err != nil { - s.eng.Warnw("Failed to emit job spec telemetry on create", "jobID", jb.ID, "error", err) - } + s.emit(ctx, jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, events.EmissionTrigger_EMISSION_TRIGGER_CREATE) } // AfterJobStopped emits a delete event when a job is removed. func (s *Service) AfterJobStopped(ctx context.Context, jb job.Job) { + s.emit(ctx, jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_DELETE, events.EmissionTrigger_EMISSION_TRIGGER_DELETE) +} + +// pollAllJobs emits heartbeat telemetry for every active job. +func (s *Service) pollAllJobs(ctx context.Context) { + for _, jb := range s.spawner.ActiveJobs() { + s.emit(ctx, jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT, events.EmissionTrigger_EMISSION_TRIGGER_HEARTBEAT) + } +} + +// emit reports jb on both tracks: CLJobInfo unconditionally, and the legacy +// OCR2 JobSpecEvent only for jobs passing the plugin-type gate. A failure on +// one track never suppresses the other. +func (s *Service) emit(ctx context.Context, jb job.Job, clTrigger commonv1.CLJobInfoTrigger, trigger events.EmissionTrigger) { + if err := s.EmitCLJobInfoForJob(ctx, jb, clTrigger); err != nil { + s.eng.Warnw("Failed to emit CLJobInfo", "jobID", jb.ID, "trigger", clTrigger, "error", err) + } + if !s.ShouldEmit(&jb) { return } - if err := s.EmitForJob(ctx, jb, events.EmissionTrigger_EMISSION_TRIGGER_DELETE); err != nil { - s.eng.Warnw("Failed to emit job spec telemetry on delete", "jobID", jb.ID, "error", err) + if err := s.EmitForJob(ctx, jb, trigger); err != nil { + s.eng.Warnw("Failed to emit job spec telemetry", "jobID", jb.ID, "trigger", trigger, "error", err) } } -// pollAllJobs emits heartbeat telemetry for every active job that passes the emit gate. -func (s *Service) pollAllJobs(ctx context.Context) { - for _, jb := range s.spawner.ActiveJobs() { - if !s.ShouldEmit(&jb) { - continue +// EmitCLJobInfoForJob builds and emits the generic CLJobInfo for any job type. +// +// A job whose spec cannot be TOML-encoded is still reported: the identity +// envelope is emitted without spec_toml so the job is accounted for, and the +// encoding failure is returned for logging rather than dropping the event. +func (s *Service) EmitCLJobInfoForJob(ctx context.Context, jb job.Job, trigger commonv1.CLJobInfoTrigger) error { + prop, err := s.jobProposal(ctx, jb) + if err != nil { + // Provenance is an enrichment, not a precondition: a job with no + // proposal is a valid, unmanaged job. + s.eng.Warnw("Failed to resolve job proposal provenance for CLJobInfo", + "jobID", jb.ID, "externalJobID", jb.ExternalJobID, "error", err) + } + + identity := NodeIdentity{CSAPublicKey: s.csaPublicKey, NodeVersion: s.nodeVersion, Hostname: s.hostname} + info, buildErr := BuildCLJobInfo(jb, trigger, identity, prop, time.Now()) + + if emitErr := EmitCLJobInfo(ctx, s.emitter, info); emitErr != nil { + return emitErr + } + return buildErr +} + +// jobProposal resolves the Job Distributor provenance for jb, or nil if the job +// did not arrive as an approved job proposal. +func (s *Service) jobProposal(ctx context.Context, jb job.Job) (*JobProposal, error) { + if s.feedsORM == nil || jb.ExternalJobID == uuid.Nil { + return nil, nil + } + + prop, err := s.feedsORM.GetJobProposalByExternalJobID(ctx, jb.ExternalJobID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil } - if err := s.EmitForJob(ctx, jb, events.EmissionTrigger_EMISSION_TRIGGER_HEARTBEAT); err != nil { - s.eng.Warnw("Failed to emit job spec telemetry", "jobID", jb.ID, "error", err) + return nil, fmt.Errorf("fetching job proposal: %w", err) + } + + spec, err := s.feedsORM.GetApprovedSpec(ctx, prop.ID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + // The proposal exists but has no approved spec, e.g. we are + // reporting a job that is mid-cancellation. + return &JobProposal{FeedsManagerID: prop.FeedsManagerID, RemoteUUID: prop.RemoteUUID.String()}, nil } + return nil, fmt.Errorf("fetching approved spec: %w", err) } + + return &JobProposal{ + FeedsManagerID: prop.FeedsManagerID, + RemoteUUID: prop.RemoteUUID.String(), + SpecVersion: spec.Version, + ProposedAt: spec.CreatedAt, + ApprovedAt: spec.StatusUpdatedAt, + }, nil } // ShouldEmit reports whether the job passes the config-driven emit gate. From 4e0d6fd33cc82c009fcc72d7fd93fba9a2043abb Mon Sep 17 00:00:00 2001 From: gheorghestrimtu Date: Fri, 4 Sep 2026 17:49:13 +0300 Subject: [PATCH 3/7] refactor(nodestatusreporter): emit CLJobInfo times as protobuf Timestamps --- .../nodestatusreporter/jobspec/cl_job_info.go | 21 ++++++--- .../jobspec/cl_job_info_test.go | 46 +++++++++++++++++-- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/core/services/nodestatusreporter/jobspec/cl_job_info.go b/core/services/nodestatusreporter/jobspec/cl_job_info.go index f5852cc36a0..d6d158aafc8 100644 --- a/core/services/nodestatusreporter/jobspec/cl_job_info.go +++ b/core/services/nodestatusreporter/jobspec/cl_job_info.go @@ -7,6 +7,7 @@ import ( "github.com/pelletier/go-toml" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/smartcontractkit/chainlink-common/pkg/beholder" commonv1 "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1" @@ -69,9 +70,9 @@ func BuildCLJobInfo(jb job.Job, trigger commonv1.CLJobInfoTrigger, id NodeIdenti JobType: string(jb.Type), SchemaVersion: jb.SchemaVersion, ForwardingAllowed: jb.ForwardingAllowed, - CreatedAt: formatCLJobInfoTime(jb.CreatedAt), + CreatedAt: timestampOrNil(jb.CreatedAt), Trigger: trigger, - Timestamp: now.UTC().Format(time.RFC3339Nano), + Timestamp: timestamppb.New(now), } if jb.GasLimit.Valid { info.GasLimit = new(jb.GasLimit.Uint32) @@ -83,8 +84,8 @@ func BuildCLJobInfo(jb job.Job, trigger commonv1.CLJobInfoTrigger, id NodeIdenti info.FeedsManagerId = &prop.FeedsManagerID info.RemoteUuid = &prop.RemoteUUID info.SpecVersion = &prop.SpecVersion - info.ProposedAt = new(formatCLJobInfoTime(prop.ProposedAt)) - info.ApprovedAt = new(formatCLJobInfoTime(prop.ApprovedAt)) + info.ProposedAt = timestampOrNil(prop.ProposedAt) + info.ApprovedAt = timestampOrNil(prop.ApprovedAt) } specTOML, err := jobTOML(jb) @@ -126,9 +127,15 @@ func jobTOML(jb job.Job) (string, error) { return string(out), nil } -func formatCLJobInfoTime(t time.Time) string { +// timestampOrNil converts t to a protobuf Timestamp, leaving an unset time as +// nil rather than mapping it onto the epoch. google.protobuf.Timestamp is used +// throughout the Job Distributor protos and, unlike an RFC3339Nano string, +// orders correctly for consumers: Go trims trailing zeros from the fractional +// seconds, so those strings are variable-width and do not sort lexicographically +// in chronological order. +func timestampOrNil(t time.Time) *timestamppb.Timestamp { if t.IsZero() { - return "" + return nil } - return t.UTC().Format(time.RFC3339Nano) + return timestamppb.New(t) } diff --git a/core/services/nodestatusreporter/jobspec/cl_job_info_test.go b/core/services/nodestatusreporter/jobspec/cl_job_info_test.go index 0b326c7e1c1..79fe68592e8 100644 --- a/core/services/nodestatusreporter/jobspec/cl_job_info_test.go +++ b/core/services/nodestatusreporter/jobspec/cl_job_info_test.go @@ -68,7 +68,10 @@ func TestBuildCLJobInfo_EncodesFullSpecAsTOML(t *testing.T) { require.NotNil(t, info.StreamId) require.Equal(t, uint32(42), *info.StreamId) require.Equal(t, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, info.Trigger) - require.NotEmpty(t, info.Timestamp) + require.NotNil(t, info.Timestamp) + require.Equal(t, time.Date(2026, 7, 24, 12, 0, 0, 0, time.UTC), info.Timestamp.AsTime()) + require.NotNil(t, info.CreatedAt) + require.Equal(t, time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC), info.CreatedAt.AsTime()) // spec_toml must be valid TOML and contain type-specific spec data. require.NotEmpty(t, info.SpecToml) @@ -116,9 +119,9 @@ func TestBuildCLJobInfo_CarriesJobDistributorProvenance(t *testing.T) { require.NotNil(t, info.SpecVersion) require.Equal(t, int32(2), *info.SpecVersion) require.NotNil(t, info.ProposedAt) - require.Equal(t, proposedAt.Format(time.RFC3339Nano), *info.ProposedAt) + require.Equal(t, proposedAt, info.ProposedAt.AsTime()) require.NotNil(t, info.ApprovedAt) - require.Equal(t, approvedAt.Format(time.RFC3339Nano), *info.ApprovedAt) + require.Equal(t, approvedAt, info.ApprovedAt.AsTime()) } // TestBuildCLJobInfo_UnmanagedJobHasNoProvenance: an unset feeds_manager_id is @@ -154,3 +157,40 @@ func TestEmitCLJobInfo_PublishesToBeholder(t *testing.T) { require.Equal(t, "offchainreporting2", payload.JobType) require.NotEmpty(t, payload.SpecToml) } + +// TestBuildCLJobInfo_TimestampsRoundTripExactly guards the reason these fields +// are google.protobuf.Timestamp rather than RFC3339Nano strings: Go trims +// trailing zeros from the fractional seconds, so string-encoded times are +// variable-width and do not sort lexicographically in chronological order — a +// whole-second time sorts after every sub-second one in the same second. +func TestBuildCLJobInfo_TimestampsRoundTripExactly(t *testing.T) { + for _, tc := range []struct { + name string + at time.Time + }{ + {"whole second", time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC)}, + {"tenth of a second", time.Date(2026, 7, 24, 10, 0, 0, 100000000, time.UTC)}, + {"sub-millisecond", time.Date(2026, 7, 24, 10, 0, 0, 123400000, time.UTC)}, + {"nanosecond", time.Date(2026, 7, 24, 10, 0, 0, 123456789, time.UTC)}, + } { + t.Run(tc.name, func(t *testing.T) { + jb := clJobInfoSampleJob() + jb.CreatedAt = tc.at + + info, err := jobspec.BuildCLJobInfo(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT, jobspec.NodeIdentity{}, nil, time.Now()) + require.NoError(t, err) + require.NotNil(t, info.CreatedAt) + require.Equal(t, tc.at, info.CreatedAt.AsTime()) + }) + } +} + +// TestBuildCLJobInfo_ZeroTimeIsUnset: an absent time must be nil, not the epoch. +func TestBuildCLJobInfo_ZeroTimeIsUnset(t *testing.T) { + jb := clJobInfoSampleJob() + jb.CreatedAt = time.Time{} + + info, err := jobspec.BuildCLJobInfo(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT, jobspec.NodeIdentity{}, nil, time.Now()) + require.NoError(t, err) + require.Nil(t, info.CreatedAt) +} From 061228b5d4a7bef84e506e2ada3e5f13b3234e84 Mon Sep 17 00:00:00 2001 From: gheorghestrimtu Date: Mon, 7 Sep 2026 12:42:12 +0300 Subject: [PATCH 4/7] import in-development chainlink-protos commit --- core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 ++-- deployment/go.mod | 2 +- deployment/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 ++-- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 ++-- system-tests/lib/go.mod | 2 +- system-tests/lib/go.sum | 4 ++-- system-tests/tests/go.mod | 2 +- system-tests/tests/go.sum | 4 ++-- 14 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index c9122a8d7c0..002d541eabf 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -512,7 +512,7 @@ require ( github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260331131315-f08a616d8dcd // indirect github.com/smartcontractkit/chainlink-protos/storage-service v0.3.0 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index ca212835687..4027709f119 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1628,8 +1628,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe h1:MDnY5wQbWTpFdDnMRicEnoMfSP5nM/KncARr4skP1ug= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 h1:/vkKPJoweLkRd56V4YHGRAtTG4+/JAlgklGEfvH6l4c= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/deployment/go.mod b/deployment/go.mod index 679197f5455..7ab6a854117 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -452,7 +452,7 @@ require ( github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260331131315-f08a616d8dcd // indirect github.com/smartcontractkit/chainlink-protos/storage-service v0.3.0 // indirect diff --git a/deployment/go.sum b/deployment/go.sum index f0ea1e726fd..68c2b2a6c69 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1462,8 +1462,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe h1:MDnY5wQbWTpFdDnMRicEnoMfSP5nM/KncARr4skP1ug= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 h1:/vkKPJoweLkRd56V4YHGRAtTG4+/JAlgklGEfvH6l4c= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/go.mod b/go.mod index 313b52c774b..87b63222a68 100644 --- a/go.mod +++ b/go.mod @@ -100,7 +100,7 @@ require ( github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260714170805-29c5577b5f55 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-20260709145319-7782fb89eb16 + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260331131315-f08a616d8dcd github.com/smartcontractkit/chainlink-protos/storage-service v0.3.0 diff --git a/go.sum b/go.sum index cfce1f5bf90..8e7ce0d79af 100644 --- a/go.sum +++ b/go.sum @@ -1203,8 +1203,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe h1:MDnY5wQbWTpFdDnMRicEnoMfSP5nM/KncARr4skP1ug= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 h1:/vkKPJoweLkRd56V4YHGRAtTG4+/JAlgklGEfvH6l4c= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260331131315-f08a616d8dcd h1:7DURXB3+Qf9REr3XA+q0FNyZO3CSAeSgJvNaek/GiZI= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 965589c07a9..1e47f22307f 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -438,7 +438,7 @@ require ( github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260714170805-29c5577b5f55 // indirect github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260331131315-f08a616d8dcd // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 2c36c5b6cac..2ae8e8f9830 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1449,8 +1449,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe h1:MDnY5wQbWTpFdDnMRicEnoMfSP5nM/KncARr4skP1ug= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 h1:/vkKPJoweLkRd56V4YHGRAtTG4+/JAlgklGEfvH6l4c= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 528d318704f..da544a6f9ab 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -504,7 +504,7 @@ require ( github.com/smartcontractkit/chainlink-protos/job-distributor v0.20.1-0.20260701185448-696c075849ea // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260331131315-f08a616d8dcd // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 0fe434917e7..43aa0cdc086 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1685,8 +1685,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe h1:MDnY5wQbWTpFdDnMRicEnoMfSP5nM/KncARr4skP1ug= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 h1:/vkKPJoweLkRd56V4YHGRAtTG4+/JAlgklGEfvH6l4c= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/system-tests/lib/go.mod b/system-tests/lib/go.mod index 15e844bdc90..765d06d1c30 100644 --- a/system-tests/lib/go.mod +++ b/system-tests/lib/go.mod @@ -475,7 +475,7 @@ require ( github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d // indirect github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260331131315-f08a616d8dcd // indirect github.com/smartcontractkit/chainlink-protos/storage-service v0.3.0 // indirect diff --git a/system-tests/lib/go.sum b/system-tests/lib/go.sum index 6320097c1ea..d22fa613487 100644 --- a/system-tests/lib/go.sum +++ b/system-tests/lib/go.sum @@ -1599,8 +1599,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe h1:MDnY5wQbWTpFdDnMRicEnoMfSP5nM/KncARr4skP1ug= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 h1:/vkKPJoweLkRd56V4YHGRAtTG4+/JAlgklGEfvH6l4c= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/system-tests/tests/go.mod b/system-tests/tests/go.mod index 074571c1f35..3cc2251a97a 100644 --- a/system-tests/tests/go.mod +++ b/system-tests/tests/go.mod @@ -248,7 +248,7 @@ require ( github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.20.1-0.20260701185448-696c075849ea // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect github.com/smartcontractkit/chainlink-solana/contracts v0.0.0-20260513123719-d347eaf314e1 // indirect github.com/smartcontractkit/chainlink-sui/codec v0.0.0-20260720132736-e99278bfdc96 // indirect github.com/smartcontractkit/chainlink-testing-framework/lib v1.54.9 // indirect diff --git a/system-tests/tests/go.sum b/system-tests/tests/go.sum index 2c145cb9525..4b1c3fcbdfe 100644 --- a/system-tests/tests/go.sum +++ b/system-tests/tests/go.sum @@ -1804,8 +1804,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe h1:MDnY5wQbWTpFdDnMRicEnoMfSP5nM/KncARr4skP1ug= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 h1:/vkKPJoweLkRd56V4YHGRAtTG4+/JAlgklGEfvH6l4c= -github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= From f7b8e17a475968427c1ffdfe092e8fa66c29e600 Mon Sep 17 00:00:00 2001 From: gheorghestrimtu Date: Mon, 7 Sep 2026 21:27:00 +0300 Subject: [PATCH 5/7] new chainlink-protos version --- core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 +- .../nodestatusreporter/jobspec/cl_job_info.go | 25 ++++--- .../jobspec/cl_job_info_test.go | 72 ++++++++++++++----- deployment/go.mod | 2 +- deployment/go.sum | 4 +- go.mod | 2 +- go.sum | 4 +- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 +- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 +- system-tests/lib/go.mod | 2 +- system-tests/lib/go.sum | 4 +- system-tests/tests/go.mod | 2 +- system-tests/tests/go.sum | 4 +- 16 files changed, 88 insertions(+), 51 deletions(-) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 54b3d092d77..10b04720cc4 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -538,7 +538,7 @@ require ( github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c // indirect github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 85211303a79..9811198a984 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1741,8 +1741,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/core/services/nodestatusreporter/jobspec/cl_job_info.go b/core/services/nodestatusreporter/jobspec/cl_job_info.go index d6d158aafc8..62c2ab6f820 100644 --- a/core/services/nodestatusreporter/jobspec/cl_job_info.go +++ b/core/services/nodestatusreporter/jobspec/cl_job_info.go @@ -7,7 +7,6 @@ import ( "github.com/pelletier/go-toml" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" "github.com/smartcontractkit/chainlink-common/pkg/beholder" commonv1 "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1" @@ -70,9 +69,9 @@ func BuildCLJobInfo(jb job.Job, trigger commonv1.CLJobInfoTrigger, id NodeIdenti JobType: string(jb.Type), SchemaVersion: jb.SchemaVersion, ForwardingAllowed: jb.ForwardingAllowed, - CreatedAt: timestampOrNil(jb.CreatedAt), + CreatedAtMs: unixMillisOrNil(jb.CreatedAt), Trigger: trigger, - Timestamp: timestamppb.New(now), + TimestampMs: now.UnixMilli(), } if jb.GasLimit.Valid { info.GasLimit = new(jb.GasLimit.Uint32) @@ -84,8 +83,8 @@ func BuildCLJobInfo(jb job.Job, trigger commonv1.CLJobInfoTrigger, id NodeIdenti info.FeedsManagerId = &prop.FeedsManagerID info.RemoteUuid = &prop.RemoteUUID info.SpecVersion = &prop.SpecVersion - info.ProposedAt = timestampOrNil(prop.ProposedAt) - info.ApprovedAt = timestampOrNil(prop.ApprovedAt) + info.ProposedAtMs = unixMillisOrNil(prop.ProposedAt) + info.ApprovedAtMs = unixMillisOrNil(prop.ApprovedAt) } specTOML, err := jobTOML(jb) @@ -127,15 +126,15 @@ func jobTOML(jb job.Job) (string, error) { return string(out), nil } -// timestampOrNil converts t to a protobuf Timestamp, leaving an unset time as -// nil rather than mapping it onto the epoch. google.protobuf.Timestamp is used -// throughout the Job Distributor protos and, unlike an RFC3339Nano string, -// orders correctly for consumers: Go trims trailing zeros from the fractional -// seconds, so those strings are variable-width and do not sort lexicographically -// in chronological order. -func timestampOrNil(t time.Time) *timestamppb.Timestamp { +// unixMillisOrNil converts t to Unix epoch milliseconds, leaving an unset time +// as nil rather than mapping it onto the epoch. Milliseconds rather than an +// RFC3339Nano string because Go trims trailing zeros from the fractional +// seconds, so those strings are variable-width and do not sort +// lexicographically in chronological order — a whole-second value sorts after +// every sub-second value in the same second. +func unixMillisOrNil(t time.Time) *int64 { if t.IsZero() { return nil } - return timestamppb.New(t) + return new(t.UnixMilli()) } diff --git a/core/services/nodestatusreporter/jobspec/cl_job_info_test.go b/core/services/nodestatusreporter/jobspec/cl_job_info_test.go index 79fe68592e8..ed490544591 100644 --- a/core/services/nodestatusreporter/jobspec/cl_job_info_test.go +++ b/core/services/nodestatusreporter/jobspec/cl_job_info_test.go @@ -68,10 +68,9 @@ func TestBuildCLJobInfo_EncodesFullSpecAsTOML(t *testing.T) { require.NotNil(t, info.StreamId) require.Equal(t, uint32(42), *info.StreamId) require.Equal(t, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, info.Trigger) - require.NotNil(t, info.Timestamp) - require.Equal(t, time.Date(2026, 7, 24, 12, 0, 0, 0, time.UTC), info.Timestamp.AsTime()) - require.NotNil(t, info.CreatedAt) - require.Equal(t, time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC), info.CreatedAt.AsTime()) + require.Equal(t, time.Date(2026, 7, 24, 12, 0, 0, 0, time.UTC).UnixMilli(), info.TimestampMs) + require.NotNil(t, info.CreatedAtMs) + require.Equal(t, time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC).UnixMilli(), *info.CreatedAtMs) // spec_toml must be valid TOML and contain type-specific spec data. require.NotEmpty(t, info.SpecToml) @@ -118,10 +117,10 @@ func TestBuildCLJobInfo_CarriesJobDistributorProvenance(t *testing.T) { require.Equal(t, "6d7d9d1a-0d0f-4b3f-9a2f-2e4a1c0b8d55", *info.RemoteUuid) require.NotNil(t, info.SpecVersion) require.Equal(t, int32(2), *info.SpecVersion) - require.NotNil(t, info.ProposedAt) - require.Equal(t, proposedAt, info.ProposedAt.AsTime()) - require.NotNil(t, info.ApprovedAt) - require.Equal(t, approvedAt, info.ApprovedAt.AsTime()) + require.NotNil(t, info.ProposedAtMs) + require.Equal(t, proposedAt.UnixMilli(), *info.ProposedAtMs) + require.NotNil(t, info.ApprovedAtMs) + require.Equal(t, approvedAt.UnixMilli(), *info.ApprovedAtMs) } // TestBuildCLJobInfo_UnmanagedJobHasNoProvenance: an unset feeds_manager_id is @@ -133,8 +132,8 @@ func TestBuildCLJobInfo_UnmanagedJobHasNoProvenance(t *testing.T) { require.Nil(t, info.FeedsManagerId) require.Nil(t, info.RemoteUuid) require.Nil(t, info.SpecVersion) - require.Nil(t, info.ProposedAt) - require.Nil(t, info.ApprovedAt) + require.Nil(t, info.ProposedAtMs) + require.Nil(t, info.ApprovedAtMs) } func TestEmitCLJobInfo_PublishesToBeholder(t *testing.T) { @@ -158,12 +157,14 @@ func TestEmitCLJobInfo_PublishesToBeholder(t *testing.T) { require.NotEmpty(t, payload.SpecToml) } -// TestBuildCLJobInfo_TimestampsRoundTripExactly guards the reason these fields -// are google.protobuf.Timestamp rather than RFC3339Nano strings: Go trims +// TestBuildCLJobInfo_TimestampsAreOrderedUnixMillis guards the reason these +// fields are int64 epoch millis rather than RFC3339Nano strings: Go trims // trailing zeros from the fractional seconds, so string-encoded times are // variable-width and do not sort lexicographically in chronological order — a // whole-second time sorts after every sub-second one in the same second. -func TestBuildCLJobInfo_TimestampsRoundTripExactly(t *testing.T) { +// Millis truncate sub-millisecond precision, which is acceptable here and is +// asserted explicitly below. +func TestBuildCLJobInfo_TimestampsAreOrderedUnixMillis(t *testing.T) { for _, tc := range []struct { name string at time.Time @@ -171,7 +172,7 @@ func TestBuildCLJobInfo_TimestampsRoundTripExactly(t *testing.T) { {"whole second", time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC)}, {"tenth of a second", time.Date(2026, 7, 24, 10, 0, 0, 100000000, time.UTC)}, {"sub-millisecond", time.Date(2026, 7, 24, 10, 0, 0, 123400000, time.UTC)}, - {"nanosecond", time.Date(2026, 7, 24, 10, 0, 0, 123456789, time.UTC)}, + {"millisecond", time.Date(2026, 7, 24, 10, 0, 0, 123000000, time.UTC)}, } { t.Run(tc.name, func(t *testing.T) { jb := clJobInfoSampleJob() @@ -179,8 +180,8 @@ func TestBuildCLJobInfo_TimestampsRoundTripExactly(t *testing.T) { info, err := jobspec.BuildCLJobInfo(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT, jobspec.NodeIdentity{}, nil, time.Now()) require.NoError(t, err) - require.NotNil(t, info.CreatedAt) - require.Equal(t, tc.at, info.CreatedAt.AsTime()) + require.NotNil(t, info.CreatedAtMs) + require.Equal(t, tc.at.UnixMilli(), *info.CreatedAtMs) }) } } @@ -192,5 +193,42 @@ func TestBuildCLJobInfo_ZeroTimeIsUnset(t *testing.T) { info, err := jobspec.BuildCLJobInfo(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT, jobspec.NodeIdentity{}, nil, time.Now()) require.NoError(t, err) - require.Nil(t, info.CreatedAt) + require.Nil(t, info.CreatedAtMs) +} + +// TestBuildCLJobInfo_SubMillisecondIsTruncated documents the one thing epoch +// millis give up versus nanosecond encodings, so nobody is surprised by it. +func TestBuildCLJobInfo_SubMillisecondIsTruncated(t *testing.T) { + jb := clJobInfoSampleJob() + jb.CreatedAt = time.Date(2026, 7, 24, 10, 0, 0, 123456789, time.UTC) + + info, err := jobspec.BuildCLJobInfo(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT, jobspec.NodeIdentity{}, nil, time.Now()) + require.NoError(t, err) + require.NotNil(t, info.CreatedAtMs) + require.Equal(t, time.Date(2026, 7, 24, 10, 0, 0, 123000000, time.UTC).UnixMilli(), *info.CreatedAtMs) +} + +// TestBuildCLJobInfo_TimestampsSortChronologically is the property the old +// RFC3339Nano encoding violated: a whole-second value sorted after every +// sub-second value in the same second. +func TestBuildCLJobInfo_TimestampsSortChronologically(t *testing.T) { + times := []time.Time{ + time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC), + time.Date(2026, 7, 24, 10, 0, 0, 100000000, time.UTC), + time.Date(2026, 7, 24, 10, 0, 0, 123000000, time.UTC), + time.Date(2026, 7, 24, 10, 0, 0, 900000000, time.UTC), + } + var prev int64 + for i, at := range times { + jb := clJobInfoSampleJob() + jb.CreatedAt = at + + info, err := jobspec.BuildCLJobInfo(jb, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT, jobspec.NodeIdentity{}, nil, time.Now()) + require.NoError(t, err) + require.NotNil(t, info.CreatedAtMs) + if i > 0 { + require.Greater(t, *info.CreatedAtMs, prev, "encoded times must increase with chronological order") + } + prev = *info.CreatedAtMs + } } diff --git a/deployment/go.mod b/deployment/go.mod index 6af2433011a..6cfe50390f4 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -452,7 +452,7 @@ require ( github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c // indirect github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6 // indirect diff --git a/deployment/go.sum b/deployment/go.sum index 4f6c42191a4..afd2fa7e7dd 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1458,8 +1458,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/go.mod b/go.mod index 67f5077bb15..b0b74acce7e 100644 --- a/go.mod +++ b/go.mod @@ -98,7 +98,7 @@ require ( 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/metering/go v0.0.0-20260729184203-90b4cdd48536 - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c github.com/smartcontractkit/chainlink-protos/storage-service v0.3.0 diff --git a/go.sum b/go.sum index 3bdf1cd06d3..4168a91c0e8 100644 --- a/go.sum +++ b/go.sum @@ -1162,8 +1162,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c h1:Eb2ogeKJhKKdzAU4EUDIiNqdtW6jPlrBzKci3BysduA= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 16394baca2b..9a45fefb1ea 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -437,7 +437,7 @@ require ( github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804200254-c1accce563a8 // indirect github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index d4ca87480c4..c510703e786 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1447,8 +1447,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 52d69eccbb8..a16a9eef04a 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -503,7 +503,7 @@ require ( github.com/smartcontractkit/chainlink-protos/job-distributor v0.20.1-0.20260701185448-696c075849ea // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 1acdbf14c99..9164b9da44e 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1685,8 +1685,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/system-tests/lib/go.mod b/system-tests/lib/go.mod index 6abdfacad00..6cfcc99146d 100644 --- a/system-tests/lib/go.mod +++ b/system-tests/lib/go.mod @@ -515,7 +515,7 @@ require ( github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d // indirect github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c // indirect github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6 // indirect diff --git a/system-tests/lib/go.sum b/system-tests/lib/go.sum index 1d3382714de..6195b050be9 100644 --- a/system-tests/lib/go.sum +++ b/system-tests/lib/go.sum @@ -1712,8 +1712,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/system-tests/tests/go.mod b/system-tests/tests/go.mod index 99586f0f3fb..5de3c0c7569 100644 --- a/system-tests/tests/go.mod +++ b/system-tests/tests/go.mod @@ -281,7 +281,7 @@ require ( github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.20.1-0.20260701185448-696c075849ea // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6 // indirect github.com/smartcontractkit/chainlink-solana/contracts v0.0.0-20260513123719-d347eaf314e1 // indirect github.com/smartcontractkit/chainlink-sui/codec v0.0.0-20260720132736-e99278bfdc96 // indirect diff --git a/system-tests/tests/go.sum b/system-tests/tests/go.sum index 988bf57e873..b8f8b0c3534 100644 --- a/system-tests/tests/go.sum +++ b/system-tests/tests/go.sum @@ -1893,8 +1893,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be h1:bypEEtGdqOqPy2ABgvuCdq7LGH3bR57zh8X61qRda04= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260904145454-76d3813c33be/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= From 44f32c7464b717c8fcdfccde09ce884e23762b71 Mon Sep 17 00:00:00 2001 From: gheorghestrimtu Date: Tue, 8 Sep 2026 11:57:44 +0300 Subject: [PATCH 6/7] update chainlink-protos --- core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 +- .../nodestatusreporter/jobspec/cl_job_info.go | 43 +++++---------- .../jobspec/cl_job_info_test.go | 27 +++------- .../jobspec/job_spec_reporter.go | 53 +++++++------------ .../jobspec/job_spec_reporter_test.go | 25 +++++++++ deployment/go.mod | 2 +- deployment/go.sum | 4 +- go.mod | 2 +- go.sum | 4 +- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 +- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 +- system-tests/lib/go.mod | 2 +- system-tests/lib/go.sum | 4 +- system-tests/tests/go.mod | 2 +- system-tests/tests/go.sum | 4 +- 18 files changed, 85 insertions(+), 105 deletions(-) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 10b04720cc4..1be0b54834a 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -538,7 +538,7 @@ require ( github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c // indirect github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 9811198a984..a5a7a8bc943 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1741,8 +1741,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e h1:dgw4Hi5YBqOrqkaPV5O5GSlQnUokQZjo7udfpvT3JeE= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/core/services/nodestatusreporter/jobspec/cl_job_info.go b/core/services/nodestatusreporter/jobspec/cl_job_info.go index 62c2ab6f820..b1e9dc8ee65 100644 --- a/core/services/nodestatusreporter/jobspec/cl_job_info.go +++ b/core/services/nodestatusreporter/jobspec/cl_job_info.go @@ -14,16 +14,9 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/job" ) -// CLJobInfo is the generic, job-type-agnostic half of this reporter. Where -// JobSpecEvent models one job type (OCR2) field by field, CLJobInfo carries the -// job's common identity plus the complete definition as a raw TOML string, so -// every job the node runs is reported through a single schema with no -// per-type code here and none needed for future job types. -// -// It is emitted on the same triggers and from the same service as JobSpecEvent -// rather than from a parallel one, so there is exactly one place in the node -// that reports what jobs it runs. Once consumers have migrated, the OCR2-only -// half can be deleted from here without touching the wiring. +// CLJobInfo is the job-type-agnostic half of this reporter: common identity +// plus the whole definition as TOML, so every job type is covered with no +// per-type code. JobSpecEvent (OCR2-only) is emitted from the same service. const ( // Domain, Entity and DataSchema identify CLJobInfo telemetry on Beholder. Domain = "node-platform" @@ -38,10 +31,8 @@ type NodeIdentity struct { Hostname string } -// JobProposal is the Job Distributor provenance for a job that arrived as an -// approved job proposal. Jobs created directly (CLI, UI, TOML on disk) have no -// proposal, and the zero value leaves the corresponding CLJobInfo fields unset -// — which is how a consumer tells a managed job from an unmanaged one. +// JobProposal is the JD provenance for a job that arrived as an approved job +// proposal. Nil for jobs created directly (CLI, UI, TOML on disk). type JobProposal struct { FeedsManagerID int64 RemoteUUID string @@ -50,13 +41,10 @@ type JobProposal struct { ApprovedAt time.Time } -// BuildCLJobInfo converts any job.Job into its generic CLJobInfo representation. -// -// prop is optional: pass nil for a job with no Job Distributor proposal. +// BuildCLJobInfo converts any job.Job into a CLJobInfo. prop may be nil. // -// If the job cannot be TOML-encoded, BuildCLJobInfo still returns a fully -// populated identity payload (with an empty SpecToml) alongside the encoding -// error, so callers can choose to emit the envelope and log the failure rather +// On TOML encoding failure it still returns a populated identity payload with +// an empty SpecToml alongside the error, so callers can emit and log rather // than drop the event. func BuildCLJobInfo(jb job.Job, trigger commonv1.CLJobInfoTrigger, id NodeIdentity, prop *JobProposal, now time.Time) (*commonv1.CLJobInfo, error) { info := &commonv1.CLJobInfo{ @@ -114,10 +102,8 @@ func EmitCLJobInfo(ctx context.Context, emitter beholder.Emitter, info *commonv1 return nil } -// jobTOML serializes the entire job definition to TOML. Marshaling the whole -// job.Job captures both the common top-level fields and the single active -// type-specific spec, so all fields for any job type are included without -// enumerating them. +// jobTOML serializes the whole job.Job, which captures both the common fields +// and the single active type-specific spec. func jobTOML(jb job.Job) (string, error) { out, err := toml.Marshal(jb) if err != nil { @@ -126,12 +112,9 @@ func jobTOML(jb job.Job) (string, error) { return string(out), nil } -// unixMillisOrNil converts t to Unix epoch milliseconds, leaving an unset time -// as nil rather than mapping it onto the epoch. Milliseconds rather than an -// RFC3339Nano string because Go trims trailing zeros from the fractional -// seconds, so those strings are variable-width and do not sort -// lexicographically in chronological order — a whole-second value sorts after -// every sub-second value in the same second. +// unixMillisOrNil maps an unset time to nil rather than the epoch. Millis not +// RFC3339Nano: Go trims trailing zeros, so those strings are variable-width and +// don't sort chronologically. func unixMillisOrNil(t time.Time) *int64 { if t.IsZero() { return nil diff --git a/core/services/nodestatusreporter/jobspec/cl_job_info_test.go b/core/services/nodestatusreporter/jobspec/cl_job_info_test.go index ed490544591..47a0bc73288 100644 --- a/core/services/nodestatusreporter/jobspec/cl_job_info_test.go +++ b/core/services/nodestatusreporter/jobspec/cl_job_info_test.go @@ -48,8 +48,7 @@ func clJobInfoSampleJob() job.Job { } } -// TestBuildCLJobInfo_EncodesFullSpecAsTOML is the load-bearing check: an -// arbitrary job must round-trip to TOML with no per-type code. +// Load-bearing: an arbitrary job must round-trip to TOML with no per-type code. func TestBuildCLJobInfo_EncodesFullSpecAsTOML(t *testing.T) { jb := clJobInfoSampleJob() id := jobspec.NodeIdentity{CSAPublicKey: "csa", NodeVersion: "1.2.3", Hostname: "host-1"} @@ -72,7 +71,7 @@ func TestBuildCLJobInfo_EncodesFullSpecAsTOML(t *testing.T) { require.NotNil(t, info.CreatedAtMs) require.Equal(t, time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC).UnixMilli(), *info.CreatedAtMs) - // spec_toml must be valid TOML and contain type-specific spec data. + // Must be valid TOML and contain type-specific spec data. require.NotEmpty(t, info.SpecToml) var decoded map[string]any require.NoError(t, toml.Unmarshal([]byte(info.SpecToml), &decoded)) @@ -95,8 +94,7 @@ func TestBuildCLJobInfo_HandlesMultipleJobTypesGenerically(t *testing.T) { } } -// TestBuildCLJobInfo_CarriesJobDistributorProvenance covers the JD join key: -// remote_uuid is what links this event back to api.job.v1.Job.uuid. +// remote_uuid is the join key back to api.job.v1.Job.uuid. func TestBuildCLJobInfo_CarriesJobDistributorProvenance(t *testing.T) { proposedAt := time.Date(2026, 7, 20, 9, 0, 0, 0, time.UTC) approvedAt := time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC) @@ -123,8 +121,7 @@ func TestBuildCLJobInfo_CarriesJobDistributorProvenance(t *testing.T) { require.Equal(t, approvedAt.UnixMilli(), *info.ApprovedAtMs) } -// TestBuildCLJobInfo_UnmanagedJobHasNoProvenance: an unset feeds_manager_id is -// how a consumer tells a directly-created job from a JD-managed one. +// An unset feeds_manager_id marks a directly-created job. func TestBuildCLJobInfo_UnmanagedJobHasNoProvenance(t *testing.T) { info, err := jobspec.BuildCLJobInfo(clJobInfoSampleJob(), commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, jobspec.NodeIdentity{}, nil, time.Now()) require.NoError(t, err) @@ -157,13 +154,8 @@ func TestEmitCLJobInfo_PublishesToBeholder(t *testing.T) { require.NotEmpty(t, payload.SpecToml) } -// TestBuildCLJobInfo_TimestampsAreOrderedUnixMillis guards the reason these -// fields are int64 epoch millis rather than RFC3339Nano strings: Go trims -// trailing zeros from the fractional seconds, so string-encoded times are -// variable-width and do not sort lexicographically in chronological order — a -// whole-second time sorts after every sub-second one in the same second. -// Millis truncate sub-millisecond precision, which is acceptable here and is -// asserted explicitly below. +// Why these are millis and not RFC3339Nano: Go trims trailing zeros, so those +// strings are variable-width and don't sort chronologically. func TestBuildCLJobInfo_TimestampsAreOrderedUnixMillis(t *testing.T) { for _, tc := range []struct { name string @@ -196,8 +188,7 @@ func TestBuildCLJobInfo_ZeroTimeIsUnset(t *testing.T) { require.Nil(t, info.CreatedAtMs) } -// TestBuildCLJobInfo_SubMillisecondIsTruncated documents the one thing epoch -// millis give up versus nanosecond encodings, so nobody is surprised by it. +// The one thing millis give up versus a nanosecond encoding. func TestBuildCLJobInfo_SubMillisecondIsTruncated(t *testing.T) { jb := clJobInfoSampleJob() jb.CreatedAt = time.Date(2026, 7, 24, 10, 0, 0, 123456789, time.UTC) @@ -208,9 +199,7 @@ func TestBuildCLJobInfo_SubMillisecondIsTruncated(t *testing.T) { require.Equal(t, time.Date(2026, 7, 24, 10, 0, 0, 123000000, time.UTC).UnixMilli(), *info.CreatedAtMs) } -// TestBuildCLJobInfo_TimestampsSortChronologically is the property the old -// RFC3339Nano encoding violated: a whole-second value sorted after every -// sub-second value in the same second. +// The property the old RFC3339Nano encoding violated. func TestBuildCLJobInfo_TimestampsSortChronologically(t *testing.T) { times := []time.Time{ time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC), diff --git a/core/services/nodestatusreporter/jobspec/job_spec_reporter.go b/core/services/nodestatusreporter/jobspec/job_spec_reporter.go index 86c4b11604b..43df99c280c 100644 --- a/core/services/nodestatusreporter/jobspec/job_spec_reporter.go +++ b/core/services/nodestatusreporter/jobspec/job_spec_reporter.go @@ -27,19 +27,10 @@ const ServiceName = "JobSpecReporter" var _ job.Listener = (*Service)(nil) -// Service polls active jobs and pushes their specs to Beholder, and also emits -// on job create/delete via the job.Listener interface. -// -// It emits two payloads on every trigger: -// -// - CLJobInfo, for every job the node runs regardless of type, carrying the -// complete definition as TOML (see cl_job_info.go). -// - JobSpecEvent, the original OCR2-only projection, for jobs passing the -// EnabledOCR2PluginTypes gate. -// -// The second is superseded by the first and is retained only until its -// consumers migrate; once they have, ShouldEmit, EmitForJob and the events -// package can be deleted from here without touching the service wiring. +// Service polls active jobs and pushes their specs to Beholder, and emits on +// job create/delete via job.Listener. Two payloads per trigger: CLJobInfo for +// every job type (see cl_job_info.go), and the OCR2-only JobSpecEvent it +// supersedes, kept until its consumers migrate. type Service struct { services.Service eng *services.Engine @@ -79,13 +70,12 @@ func NewJobSpecReporter( return s } +// start always runs so CLJobInfo needs no per-node opt-in; the legacy track +// stays behind JobSpecReporter.Enabled (see ShouldEmit). Still a no-op where +// Beholder is disabled, which is the default. func (s *Service) start(ctx context.Context) error { - if !s.config.Enabled() { - s.eng.Info("Job Spec Reporter Service is disabled") - return nil - } - - s.eng.Info("Starting Job Spec Reporter Service") + s.eng.Infow("Starting Job Spec Reporter Service", + "clJobInfo", true, "legacyJobSpecEvent", s.config.Enabled()) s.spawner.RegisterListener(s) ticker := services.NewTicker(s.config.PollingInterval()) s.eng.GoTick(ticker, s.pollAllJobs) @@ -114,9 +104,7 @@ func (s *Service) pollAllJobs(ctx context.Context) { } } -// emit reports jb on both tracks: CLJobInfo unconditionally, and the legacy -// OCR2 JobSpecEvent only for jobs passing the plugin-type gate. A failure on -// one track never suppresses the other. +// emit reports jb on both tracks; a failure on one never suppresses the other. func (s *Service) emit(ctx context.Context, jb job.Job, clTrigger commonv1.CLJobInfoTrigger, trigger events.EmissionTrigger) { if err := s.EmitCLJobInfoForJob(ctx, jb, clTrigger); err != nil { s.eng.Warnw("Failed to emit CLJobInfo", "jobID", jb.ID, "trigger", clTrigger, "error", err) @@ -130,16 +118,13 @@ func (s *Service) emit(ctx context.Context, jb job.Job, clTrigger commonv1.CLJob } } -// EmitCLJobInfoForJob builds and emits the generic CLJobInfo for any job type. -// -// A job whose spec cannot be TOML-encoded is still reported: the identity -// envelope is emitted without spec_toml so the job is accounted for, and the -// encoding failure is returned for logging rather than dropping the event. +// EmitCLJobInfoForJob emits the generic CLJobInfo for any job type. A job whose +// spec won't TOML-encode is still reported without spec_toml, and the encoding +// error returned for logging. func (s *Service) EmitCLJobInfoForJob(ctx context.Context, jb job.Job, trigger commonv1.CLJobInfoTrigger) error { prop, err := s.jobProposal(ctx, jb) if err != nil { - // Provenance is an enrichment, not a precondition: a job with no - // proposal is a valid, unmanaged job. + // Provenance is an enrichment, not a precondition. s.eng.Warnw("Failed to resolve job proposal provenance for CLJobInfo", "jobID", jb.ID, "externalJobID", jb.ExternalJobID, "error", err) } @@ -153,8 +138,7 @@ func (s *Service) EmitCLJobInfoForJob(ctx context.Context, jb job.Job, trigger c return buildErr } -// jobProposal resolves the Job Distributor provenance for jb, or nil if the job -// did not arrive as an approved job proposal. +// jobProposal resolves JD provenance for jb, or nil if it wasn't proposed. func (s *Service) jobProposal(ctx context.Context, jb job.Job) (*JobProposal, error) { if s.feedsORM == nil || jb.ExternalJobID == uuid.Nil { return nil, nil @@ -171,8 +155,7 @@ func (s *Service) jobProposal(ctx context.Context, jb job.Job) (*JobProposal, er spec, err := s.feedsORM.GetApprovedSpec(ctx, prop.ID) if err != nil { if errors.Is(err, sql.ErrNoRows) { - // The proposal exists but has no approved spec, e.g. we are - // reporting a job that is mid-cancellation. + // Proposal exists but has no approved spec, e.g. mid-cancellation. return &JobProposal{FeedsManagerID: prop.FeedsManagerID, RemoteUUID: prop.RemoteUUID.String()}, nil } return nil, fmt.Errorf("fetching approved spec: %w", err) @@ -187,9 +170,9 @@ func (s *Service) jobProposal(ctx context.Context, jb job.Job) (*JobProposal, er }, nil } -// ShouldEmit reports whether the job passes the config-driven emit gate. +// ShouldEmit gates the legacy OCR2 track only; CLJobInfo ignores it. func (s *Service) ShouldEmit(j *job.Job) bool { - if j == nil { + if j == nil || !s.config.Enabled() { return false } if j.Type != job.OffchainReporting2 || j.OCR2OracleSpec == nil { diff --git a/core/services/nodestatusreporter/jobspec/job_spec_reporter_test.go b/core/services/nodestatusreporter/jobspec/job_spec_reporter_test.go index a1d32805b35..d2b9aa42123 100644 --- a/core/services/nodestatusreporter/jobspec/job_spec_reporter_test.go +++ b/core/services/nodestatusreporter/jobspec/job_spec_reporter_test.go @@ -16,6 +16,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/beholder" "github.com/smartcontractkit/chainlink-common/pkg/beholder/beholdertest" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + commonv1 "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/feeds" feedsmocks "github.com/smartcontractkit/chainlink/v2/core/services/feeds/mocks" @@ -389,3 +390,27 @@ func TestBuildEvent_ProposalLifecycle(t *testing.T) { assert.Equal(t, approvedAt.Format(time.RFC3339Nano), ev.ApprovedAt) assert.InDelta(t, approvedAt.Sub(proposedAt).Seconds(), ev.AcceptLatencySeconds, 1.0) } + +// Pins the split: CLJobInfo needs no per-node opt-in, the legacy OCR2 track +// stays behind JobSpecReporter.Enabled. +func TestAfterJobStarted_CLJobInfoIgnoresEnabledGate(t *testing.T) { + observer := beholdertest.NewObserver(t) + + cfg := defaultConfig() + cfg.enabled = false // legacy track off + + jb := makeMedianJob() + reporter := newTestReporter(t, cfg, newFeedsORMWithoutProposal(t, jb)) + reporter.AfterJobStarted(t.Context(), jb) + + clMsgs := observer.Messages(t, beholder.AttrKeyEntity, jobspec.Entity) + require.Len(t, clMsgs, 1, "CLJobInfo must be emitted even with JobSpecReporter disabled") + + var payload commonv1.CLJobInfo + require.NoError(t, proto.Unmarshal(clMsgs[0].Body, &payload)) + require.Equal(t, commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, payload.Trigger) + require.NotEmpty(t, payload.SpecToml) + + legacy := observer.Messages(t, "beholder_entity", events.ProtoPkg+"."+events.JobSpecEventEntity) + require.Empty(t, legacy, "legacy JobSpecEvent must stay gated by Enabled") +} diff --git a/deployment/go.mod b/deployment/go.mod index 6cfe50390f4..3199486a15e 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -452,7 +452,7 @@ require ( github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e // indirect github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c // indirect github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6 // indirect diff --git a/deployment/go.sum b/deployment/go.sum index afd2fa7e7dd..1310125fef3 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1458,8 +1458,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e h1:dgw4Hi5YBqOrqkaPV5O5GSlQnUokQZjo7udfpvT3JeE= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/go.mod b/go.mod index b0b74acce7e..0fa7a4ea584 100644 --- a/go.mod +++ b/go.mod @@ -98,7 +98,7 @@ require ( 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/metering/go v0.0.0-20260729184203-90b4cdd48536 - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c github.com/smartcontractkit/chainlink-protos/storage-service v0.3.0 diff --git a/go.sum b/go.sum index 4168a91c0e8..4c1e165ce8c 100644 --- a/go.sum +++ b/go.sum @@ -1162,8 +1162,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e h1:dgw4Hi5YBqOrqkaPV5O5GSlQnUokQZjo7udfpvT3JeE= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c h1:Eb2ogeKJhKKdzAU4EUDIiNqdtW6jPlrBzKci3BysduA= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 9a45fefb1ea..5bfc976065d 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -437,7 +437,7 @@ require ( github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804200254-c1accce563a8 // indirect github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e // indirect github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index c510703e786..8422f388bfc 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1447,8 +1447,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e h1:dgw4Hi5YBqOrqkaPV5O5GSlQnUokQZjo7udfpvT3JeE= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index a16a9eef04a..02b4e4cf08a 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -503,7 +503,7 @@ require ( github.com/smartcontractkit/chainlink-protos/job-distributor v0.20.1-0.20260701185448-696c075849ea // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e // indirect github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 9164b9da44e..5e668f2bef6 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1685,8 +1685,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e h1:dgw4Hi5YBqOrqkaPV5O5GSlQnUokQZjo7udfpvT3JeE= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/system-tests/lib/go.mod b/system-tests/lib/go.mod index 6cfcc99146d..df40ea22a2e 100644 --- a/system-tests/lib/go.mod +++ b/system-tests/lib/go.mod @@ -515,7 +515,7 @@ require ( github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d // indirect github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 // indirect github.com/smartcontractkit/chainlink-protos/ring/go v0.0.0-20260821021345-a75f67fe965c // indirect github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6 // indirect diff --git a/system-tests/lib/go.sum b/system-tests/lib/go.sum index 6195b050be9..1ba219bdef7 100644 --- a/system-tests/lib/go.sum +++ b/system-tests/lib/go.sum @@ -1712,8 +1712,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e h1:dgw4Hi5YBqOrqkaPV5O5GSlQnUokQZjo7udfpvT3JeE= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= diff --git a/system-tests/tests/go.mod b/system-tests/tests/go.mod index 5de3c0c7569..e08284e818c 100644 --- a/system-tests/tests/go.mod +++ b/system-tests/tests/go.mod @@ -281,7 +281,7 @@ require ( github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.20.1-0.20260701185448-696c075849ea // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 // indirect - github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c // indirect + github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e // indirect github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6 // indirect github.com/smartcontractkit/chainlink-solana/contracts v0.0.0-20260513123719-d347eaf314e1 // indirect github.com/smartcontractkit/chainlink-sui/codec v0.0.0-20260720132736-e99278bfdc96 // indirect diff --git a/system-tests/tests/go.sum b/system-tests/tests/go.sum index b8f8b0c3534..3825914068d 100644 --- a/system-tests/tests/go.sum +++ b/system-tests/tests/go.sum @@ -1893,8 +1893,8 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202605122 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536 h1:ecQYtdRA+NQLXf0aKYUMfcn1TRhcQ4RZCZzzadFsbSs= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260729184203-90b4cdd48536/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c h1:poxOgwzVlrNDXcVTBtcOp50H8oJp8P0EMj0GUNNJB9w= -github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260907182240-581aff049b7c/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e h1:dgw4Hi5YBqOrqkaPV5O5GSlQnUokQZjo7udfpvT3JeE= +github.com/smartcontractkit/chainlink-protos/node-platform v0.2.1-0.20260908085225-914a0fc9200e/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-protos/orchestrator v0.11.0 h1:NXKTdIESAiCkVnPS6dyZP+NXVek3GzXa6P4uFAs0o8Y= From c50e924ba64a5705dd47c1b8bdac4bf6f37e9de7 Mon Sep 17 00:00:00 2001 From: gheorghestrimtu Date: Tue, 8 Sep 2026 12:41:26 +0300 Subject: [PATCH 7/7] lint --- .../nodestatusreporter/jobspec/cl_job_info.go | 1 - .../nodestatusreporter/jobspec/cl_job_info_test.go | 11 ++++++++++- .../jobspec/job_spec_reporter_test.go | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/services/nodestatusreporter/jobspec/cl_job_info.go b/core/services/nodestatusreporter/jobspec/cl_job_info.go index b1e9dc8ee65..9780af9c712 100644 --- a/core/services/nodestatusreporter/jobspec/cl_job_info.go +++ b/core/services/nodestatusreporter/jobspec/cl_job_info.go @@ -10,7 +10,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/beholder" commonv1 "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1" - "github.com/smartcontractkit/chainlink/v2/core/services/job" ) diff --git a/core/services/nodestatusreporter/jobspec/cl_job_info_test.go b/core/services/nodestatusreporter/jobspec/cl_job_info_test.go index 47a0bc73288..a6d8e9f6a20 100644 --- a/core/services/nodestatusreporter/jobspec/cl_job_info_test.go +++ b/core/services/nodestatusreporter/jobspec/cl_job_info_test.go @@ -15,7 +15,6 @@ import ( commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types" commonv1 "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1" - "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/nodestatusreporter/jobspec" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" @@ -50,6 +49,7 @@ func clJobInfoSampleJob() job.Job { // Load-bearing: an arbitrary job must round-trip to TOML with no per-type code. func TestBuildCLJobInfo_EncodesFullSpecAsTOML(t *testing.T) { + t.Parallel() jb := clJobInfoSampleJob() id := jobspec.NodeIdentity{CSAPublicKey: "csa", NodeVersion: "1.2.3", Hostname: "host-1"} @@ -80,6 +80,7 @@ func TestBuildCLJobInfo_EncodesFullSpecAsTOML(t *testing.T) { } func TestBuildCLJobInfo_HandlesMultipleJobTypesGenerically(t *testing.T) { + t.Parallel() jobs := []job.Job{ {Type: job.VRF, VRFSpec: &job.VRFSpec{ EVMChainID: sqlutil.NewI(4), @@ -96,6 +97,7 @@ func TestBuildCLJobInfo_HandlesMultipleJobTypesGenerically(t *testing.T) { // remote_uuid is the join key back to api.job.v1.Job.uuid. func TestBuildCLJobInfo_CarriesJobDistributorProvenance(t *testing.T) { + t.Parallel() proposedAt := time.Date(2026, 7, 20, 9, 0, 0, 0, time.UTC) approvedAt := time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC) prop := &jobspec.JobProposal{ @@ -123,6 +125,7 @@ func TestBuildCLJobInfo_CarriesJobDistributorProvenance(t *testing.T) { // An unset feeds_manager_id marks a directly-created job. func TestBuildCLJobInfo_UnmanagedJobHasNoProvenance(t *testing.T) { + t.Parallel() info, err := jobspec.BuildCLJobInfo(clJobInfoSampleJob(), commonv1.CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE, jobspec.NodeIdentity{}, nil, time.Now()) require.NoError(t, err) @@ -133,6 +136,7 @@ func TestBuildCLJobInfo_UnmanagedJobHasNoProvenance(t *testing.T) { require.Nil(t, info.ApprovedAtMs) } +//nolint:paralleltest // installs a process-global beholder emitter func TestEmitCLJobInfo_PublishesToBeholder(t *testing.T) { obs := beholdertest.NewObserver(t) @@ -157,6 +161,7 @@ func TestEmitCLJobInfo_PublishesToBeholder(t *testing.T) { // Why these are millis and not RFC3339Nano: Go trims trailing zeros, so those // strings are variable-width and don't sort chronologically. func TestBuildCLJobInfo_TimestampsAreOrderedUnixMillis(t *testing.T) { + t.Parallel() for _, tc := range []struct { name string at time.Time @@ -167,6 +172,7 @@ func TestBuildCLJobInfo_TimestampsAreOrderedUnixMillis(t *testing.T) { {"millisecond", time.Date(2026, 7, 24, 10, 0, 0, 123000000, time.UTC)}, } { t.Run(tc.name, func(t *testing.T) { + t.Parallel() jb := clJobInfoSampleJob() jb.CreatedAt = tc.at @@ -180,6 +186,7 @@ func TestBuildCLJobInfo_TimestampsAreOrderedUnixMillis(t *testing.T) { // TestBuildCLJobInfo_ZeroTimeIsUnset: an absent time must be nil, not the epoch. func TestBuildCLJobInfo_ZeroTimeIsUnset(t *testing.T) { + t.Parallel() jb := clJobInfoSampleJob() jb.CreatedAt = time.Time{} @@ -190,6 +197,7 @@ func TestBuildCLJobInfo_ZeroTimeIsUnset(t *testing.T) { // The one thing millis give up versus a nanosecond encoding. func TestBuildCLJobInfo_SubMillisecondIsTruncated(t *testing.T) { + t.Parallel() jb := clJobInfoSampleJob() jb.CreatedAt = time.Date(2026, 7, 24, 10, 0, 0, 123456789, time.UTC) @@ -201,6 +209,7 @@ func TestBuildCLJobInfo_SubMillisecondIsTruncated(t *testing.T) { // The property the old RFC3339Nano encoding violated. func TestBuildCLJobInfo_TimestampsSortChronologically(t *testing.T) { + t.Parallel() times := []time.Time{ time.Date(2026, 7, 24, 10, 0, 0, 0, time.UTC), time.Date(2026, 7, 24, 10, 0, 0, 100000000, time.UTC), diff --git a/core/services/nodestatusreporter/jobspec/job_spec_reporter_test.go b/core/services/nodestatusreporter/jobspec/job_spec_reporter_test.go index d2b9aa42123..02cc19e080c 100644 --- a/core/services/nodestatusreporter/jobspec/job_spec_reporter_test.go +++ b/core/services/nodestatusreporter/jobspec/job_spec_reporter_test.go @@ -393,6 +393,8 @@ func TestBuildEvent_ProposalLifecycle(t *testing.T) { // Pins the split: CLJobInfo needs no per-node opt-in, the legacy OCR2 track // stays behind JobSpecReporter.Enabled. +// +//nolint:paralleltest // installs a process-global beholder emitter func TestAfterJobStarted_CLJobInfoIgnoresEnabledGate(t *testing.T) { observer := beholdertest.NewObserver(t)