Skip to content

Next major version#1408

Open
alexcos20 wants to merge 9 commits into
mainfrom
next-4
Open

Next major version#1408
alexcos20 wants to merge 9 commits into
mainfrom
next-4

Conversation

@alexcos20

@alexcos20 alexcos20 commented Jul 7, 2026

Copy link
Copy Markdown
Member

Fixes # .

Changes proposed in this PR:

Summary by CodeRabbit

  • New Features
    • Added “Service on Demand” API: service templates, start/stop/extend/restart, status, and streaming logs.
    • Introduced connection-level compute resource pools with environment-level lightweight resource references, including discrete vs fungible sharing behavior.
  • Bug Fixes
    • Corrected resource availability/allocation gating for global vs per-environment discrete accounting and fungible ceilings; improved service lifecycle robustness and cleanup.
  • Documentation
    • Updated compute/pricing/env/GPU/config docs for the new two-level resource model; added services documentation, API docs, and service template examples.
  • Chores
    • Updated CI/workflows and added a service integration test script; shipped service templates in the Docker image.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7340b52a-bd3f-45f8-81a0-29abcf81c6a5

📥 Commits

Reviewing files that changed from the base of the PR and between ea51b7c and 1733e6d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • package.json
  • src/components/Auth/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

📝 Walkthrough

Walkthrough

This PR introduces shared connection-level compute resources with environment references, adds Service-on-Demand APIs and Docker lifecycle handling, updates delegated authentication, and indexes escrow relock events. Documentation, configuration, CI, templates, persistence, routes, and tests are updated accordingly.

Changes

Compute resource model

Layer / File(s) Summary
Resource contracts and validation
src/@types/C2D/C2D.ts, src/utils/config/schemas.ts, config.json, .env.example, docs/env.md, docs/compute-pricing.md, docs/GPU.md
Defines shared connection resources, environment references, resource kinds, shareability, feature flags, and migration validation.
Resolution and accounting
src/components/c2d/compute_engine_base.ts, src/components/c2d/compute_engine_docker.ts, scripts/ocean-node-quickstart.sh
Resolves physical and configured pools, applies environment limits, tracks discrete resources globally, and updates GPU auto-detection.
Validation coverage
src/test/unit/compute.test.ts, src/test/unit/c2d/serviceResourceMatching.test.ts
Tests resource resolution, constraints, availability gates, schemas, GPU handling, and service image selection.

Service-On-Demand

Layer / File(s) Summary
Contracts, templates, and routes
src/@types/C2D/ServiceOnDemand.ts, src/@types/commands.ts, src/components/core/service/*, src/components/httpRoutes/compute.ts, src/components/core/handler/coreHandlersRegistry.ts
Adds service command contracts, template loading, lifecycle handlers, protocol registration, and HTTP endpoints.
Runtime and persistence
src/components/c2d/compute_engine_docker.ts, src/components/database/*, src/components/core/service/utils.ts, src/components/core/utils/escrow.ts
Persists service jobs and implements asynchronous escrow, image, network, container, monitoring, restart, stop, expiry, and log flows.
Documentation and tests
docs/API.md, docs/services.md, docs/serviceTemplates/*, docs/Ocean Node.postman_collection.json, src/test/unit/service/*, src/test/integration/services.test.ts
Documents service APIs and templates and tests handlers, utilities, persistence, template loading, and end-to-end lifecycle behavior.

Authentication and escrow indexing

Layer / File(s) Summary
Delegated token validation
src/components/Auth/index.ts, src/components/core/handler/authHandler.ts, src/components/core/utils/nonceHandler.ts, src/test/unit/auth/token.test.ts
Adds issuer-bound JWT claims, remote issuer validation, bounded verdict parsing, and validation command handling.
Ownership and relock indexing
src/components/core/handler/handler.ts, src/components/core/handler/persistentStorage.ts, src/components/Indexer/*, src/@types/Escrow.ts, src/components/database/*, src/test/integration/escrow.test.ts
Uses authenticated consumer addresses for storage ownership and indexes escrow relock amounts, expiry, and token data.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: bogdanfazakas, dnsi0, ndrpp, andreip136

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is generic and doesn't describe the actual changes in the pull request. Use a concise title that names the main change, such as adding service-on-demand and the new compute resource model.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch next-4

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
docs/compute-pricing.md (1)

44-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fenced code block missing language identifier.

The "Configuration Layout" ascii diagram block opens with a bare ```, triggering the markdownlint MD040 rule.

📝 Suggested fix
-```
+```text
 DOCKER_COMPUTE_ENVIRONMENTS
 └── [ Docker connection ]          ← socketPath, resources[], environments[]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/compute-pricing.md` around lines 44 - 59, The Configuration Layout
diagram in the compute pricing docs uses a fenced code block without a language
tag, which trips markdownlint MD040. Update the opening fence for this ascii
diagram to use a text language identifier so the block is consistently
recognized; keep the rest of the diagram unchanged.

Source: Linters/SAST tools

docs/env.md (1)

196-241: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Heading level skips from h2 to h4.

#### Connection-level fields, #### Environment-level fields, and #### Migration from old format all sit directly under ## Compute (h2) with no intervening h3, per the markdownlint hint.

📝 Suggested fix
-#### Connection-level fields
+### Connection-level fields
...
-#### Environment-level fields
+### Environment-level fields
...
-#### Migration from old format
+### Migration from old format
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/env.md` around lines 196 - 241, The Compute section has heading level
skips because the subsections under the top-level `## Compute` are written as
`####` instead of the expected `###`. Update the Markdown headings for
`Connection-level fields`, `Environment-level fields`, and `Migration from old
format` to the correct level so they sit one level below `## Compute` and
satisfy the markdownlint structure.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/c2d/compute_engine_docker.ts`:
- Around line 258-267: The custom resource handling in compute_engine_docker.ts
is leaving physicalLimits with an undefined total when res.total is omitted,
which makes checkGlobalResourceAvailability treat the resource as zero-capacity.
Update the custom resource branch in the resource registration logic around
ComputeResource creation so it either rejects configs without a concrete total
or assigns a valid default before calling this.physicalLimits.set for the custom
resource id.

In `@src/utils/config/schemas.ts`:
- Around line 301-357: The validation in the docker config schema only checks
`env.resources`, so `env.free.resources` can still contain legacy or invalid
resource refs. Update the `.superRefine` logic in `schemas.ts` to validate both
`resources` lists on `C2DEnvironmentConfig` and `C2DEnvironmentFreeConfig` when
checking for old-format hardware fields and when verifying pool ids against
`dockerConfig.resources`. Reuse the existing `dockerConfig.environments.forEach`
pass, but include `env.free.resources` alongside `env.resources` so both paths
are rejected consistently.

---

Nitpick comments:
In `@docs/compute-pricing.md`:
- Around line 44-59: The Configuration Layout diagram in the compute pricing
docs uses a fenced code block without a language tag, which trips markdownlint
MD040. Update the opening fence for this ascii diagram to use a text language
identifier so the block is consistently recognized; keep the rest of the diagram
unchanged.

In `@docs/env.md`:
- Around line 196-241: The Compute section has heading level skips because the
subsections under the top-level `## Compute` are written as `####` instead of
the expected `###`. Update the Markdown headings for `Connection-level fields`,
`Environment-level fields`, and `Migration from old format` to the correct level
so they sit one level below `## Compute` and satisfy the markdownlint structure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f771fb66-2692-4d15-b908-0ec6dcd79a26

📥 Commits

Reviewing files that changed from the base of the PR and between 762874e and 2e892d2.

📒 Files selected for processing (13)
  • .env.example
  • CLAUDE.md
  • README.md
  • config.json
  • docs/GPU.md
  • docs/compute-pricing.md
  • docs/env.md
  • scripts/ocean-node-quickstart.sh
  • src/@types/C2D/C2D.ts
  • src/components/c2d/compute_engine_base.ts
  • src/components/c2d/compute_engine_docker.ts
  • src/test/unit/compute.test.ts
  • src/utils/config/schemas.ts

Comment thread src/components/c2d/compute_engine_docker.ts
Comment on lines +301 to +357
.superRefine((dockerConfig, ctx) => {
// Reject old format: env-level resources with init/driverVersion/platform indicate full ComputeResource objects
// that should have been moved to connection-level resources.
dockerConfig.environments.forEach((env, envIdx) => {
;(env.resources || []).forEach((ref, i) => {
if (
(ref as any).init !== undefined ||
(ref as any).driverVersion !== undefined
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `environments[${envIdx}].resources[${i}]: hardware fields (init, driverVersion, platform, etc.) must be defined at connection level in "resources", not inside an environment. See migration guide.`,
path: ['environments', envIdx, 'resources', i]
})
}
})
})

// Validate env resource refs point to known pool ids.
// cpu, ram, disk are always valid (auto-detected from host).
const autoDetected = new Set(['cpu', 'ram', 'disk'])
const poolIds = new Set([
...autoDetected,
...(dockerConfig.resources ?? []).map((r) => r.id)
])
dockerConfig.environments.forEach((env, envIdx) => {
;(env.resources || []).forEach((ref, i) => {
if (!poolIds.has(ref.id)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `environments[${envIdx}].resources[${i}].id "${ref.id}" not found in connection-level resources`,
path: ['environments', envIdx, 'resources', i, 'id']
})
}
})
})

// Reject shareable:true on gpu/fpga type resources — these require exclusive access.
;(dockerConfig.resources ?? []).forEach((res, i) => {
if (res.shareable === true && (res.type === 'gpu' || res.type === 'fpga')) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Resource "${res.id}": shareable:true is not allowed for type "${res.type}" — GPUs and FPGAs require exclusive access per job`,
path: ['resources', i]
})
}
})

// Warn (not error) if shareable:true on a fungible resource — it has no effect.
;(dockerConfig.resources ?? []).forEach((res) => {
if (res.shareable === true && res.kind === 'fungible') {
CONFIG_LOGGER.warn(
`Resource "${res.id}": shareable:true has no effect on fungible resources`
)
}
})
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map relevant files and locate schemas/tests.
git ls-files 'src/utils/config/*.ts' 'test/**/*.ts' 'tests/**/*.ts' | sed -n '1,200p'

printf '\n--- outline schemas.ts ---\n'
ast-grep outline src/utils/config/schemas.ts --view expanded || true

printf '\n--- search for C2DEnvironmentFreeConfigSchema / EnvironmentResourceRefSchema / resolveEnvironmentResources ---\n'
rg -n "C2DEnvironmentFreeConfigSchema|EnvironmentResourceRefSchema|resolveEnvironmentResources|free\.resources|resources" src test tests -g '!**/dist/**' -g '!**/build/**' || true

Repository: oceanprotocol/ocean-node

Length of output: 28307


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant slices of the schema file once we know line ranges.
file=src/utils/config/schemas.ts
wc -l "$file"
printf '\n--- lines 240-380 ---\n'
sed -n '240,380p' "$file" | cat -n

printf '\n--- any tests mentioning free.resources or environment resource validation ---\n'
rg -n "free\.resources|hardware fields|shareable:true|migration guide|environment resource" src test tests -g '!**/dist/**' -g '!**/build/**' || true

Repository: oceanprotocol/ocean-node

Length of output: 9551


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect related runtime validation to see whether free resources are handled elsewhere.
rg -n "resolveEnvironmentResources|env\.free|free\.resources|resources" src/utils src -g '!**/dist/**' -g '!**/build/**' | sed -n '1,240p'

Repository: oceanprotocol/ocean-node

Length of output: 26578


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focus specifically on the schema definitions around environment and free resources.
file=src/utils/config/schemas.ts
printf '\n--- top-level outlines for relevant symbols ---\n'
ast-grep outline "$file" --match 'C2DEnvironmentSchema' --view expanded || true
ast-grep outline "$file" --match 'C2DEnvironmentFreeConfigSchema' --view expanded || true
ast-grep outline "$file" --match 'EnvironmentResourceRefSchema' --view expanded || true

printf '\n--- relevant lines around environment schema definitions ---\n'
sed -n '1,260p' "$file" | cat -n

Repository: oceanprotocol/ocean-node

Length of output: 9836


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if tests exist for the specific validation behavior.
rg -n "compute\.test|schemas\.test|free\.resources|resolveEnvironmentResources" test tests src -g '!**/dist/**' -g '!**/build/**' || true

Repository: oceanprotocol/ocean-node

Length of output: 3060


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Comprehensive read-only inspection for the schema and any tests.
file=src/utils/config/schemas.ts

printf 'FILE=%s\n' "$file"
wc -l "$file"

printf '\n--- relevant symbol outline ---\n'
ast-grep outline "$file" --view expanded | sed -n '1,260p' || true

printf '\n--- lines 1-420 ---\n'
sed -n '1,420p' "$file" | cat -n

printf '\n--- repository search for free resources and validation tests ---\n'
rg -n "free\.resources|C2DEnvironmentFreeConfigSchema|EnvironmentResourceRefSchema|resolveEnvironmentResources|shareable:true|migration guide" src test tests -g '!**/dist/**' -g '!**/build/**' || true

Repository: oceanprotocol/ocean-node

Length of output: 22559


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the resolver that handles free.resources at runtime.
file=src/components/c2d/compute_engine_docker.ts

printf '\n--- resolver around resolveEnvironmentResources ---\n'
sed -n '260,460p' "$file" | cat -n

printf '\n--- any direct validation or error handling for unknown ids in free.resources ---\n'
rg -n "not found|resolveEnvironmentResources|free\.resources|envDef\.free\.resources|envResources" "$file" src/test/unit/compute.test.ts src/test/integration/compute.test.ts -n

Repository: oceanprotocol/ocean-node

Length of output: 13789


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect type definitions for environment configs to confirm what free.resources can contain.
file=src/@types/C2D/C2D.ts

printf '\n--- relevant C2D types ---\n'
sed -n '110,210p' "$file" | cat -n

Repository: oceanprotocol/ocean-node

Length of output: 4103


Include env.free.resources in the same validation pass. C2DEnvironmentFreeConfigSchema.resources is also a list of resource refs, but the migration check and pool-id check only iterate env.resources. That lets invalid or legacy refs in free.resources pass schema validation and get skipped later at runtime.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/config/schemas.ts` around lines 301 - 357, The validation in the
docker config schema only checks `env.resources`, so `env.free.resources` can
still contain legacy or invalid resource refs. Update the `.superRefine` logic
in `schemas.ts` to validate both `resources` lists on `C2DEnvironmentConfig` and
`C2DEnvironmentFreeConfig` when checking for old-format hardware fields and when
verifying pool ids against `dockerConfig.resources`. Reuse the existing
`dockerConfig.environments.forEach` pass, but include `env.free.resources`
alongside `env.resources` so both paths are rejected consistently.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci.yml (1)

116-121: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Add persist-credentials: false for the third-party Barge checkout.

Static analysis flags this checkout for credential persistence. start_ocean.sh runs code from the third-party barge repo right after; leaving the default token persisted in git config gives it access to a token it doesn't need.

🔒 Proposed fix
       - name: Checkout Barge
         uses: actions/checkout@v4
         with:
           repository: 'oceanprotocol/barge'
           path: 'barge'
           ref: '43cfdfd21154a2bae00770b779e7c39390ff5043'
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 116 - 121, The third-party checkout in
the workflow currently leaves Git credentials persisted, which `start_ocean.sh`
can inherit when running code from the `barge` repo. Update the `Checkout Barge`
step in the CI workflow to disable credential persistence by adding
`persist-credentials: false` to the `actions/checkout@v4` configuration for that
checkout.

Source: Linters/SAST tools

🧹 Nitpick comments (7)
src/components/c2d/compute_engine_docker.ts (1)

1778-1796: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Expired-service teardown runs sequentially inside the shared InternalLoop tick.

Each expired service is await-ed one at a time (c.stop({t:10}) allows up to 10s), and isInternalLoopRunning/setNewTimer serialize ticks — so a batch of simultaneously-expiring services can stall processing of unrelated running compute jobs and other services on the same engine for many seconds. Consider running these concurrently (e.g. Promise.all) similar to how processJob batches compute jobs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 1778 - 1796, The
expired-service cleanup in compute_engine_docker’s internal loop is running one
service at a time and can block the whole tick. Update the expired-services
handling in the InternalLoop path to process the `expiredServices` batch
concurrently instead of awaiting each `stopService` sequentially, using the
existing `stopService`, `getServiceJob`, and `updateServiceJob` flow per
service. Preserve the per-service error logging and status update, but structure
the loop so simultaneous expirations do not stall unrelated compute jobs or
services.
docs/serviceTemplates/vllm-qwen-0_5b.json (1)

5-6: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Pin the image tag instead of using latest.

Using "tag": "latest" makes the template non-reproducible — a future upstream vllm/vllm-openai image update could break the service or change behavior. Consider pinning to a specific version tag (e.g., v0.8.3).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/serviceTemplates/vllm-qwen-0_5b.json` around lines 5 - 6, The service
template is using a mutable container tag, so update the vllm/vllm-openai image
definition to a fixed version instead of latest. Replace the tag value in the
Qwen template JSON with a specific, reproducible release (for example a v0.x.y
tag) so deployments of this template stay stable over time.
docs/serviceTemplates/vllm-dual-lite-gpu.json (1)

5-6: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Pin the image tag instead of using latest.

Same reproducibility concern as the other template — "tag": "latest" can break the service on upstream image changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/serviceTemplates/vllm-dual-lite-gpu.json` around lines 5 - 6, The
service template is using a floating container tag, which can make deployments
non-reproducible when the upstream image changes. Update the image configuration
in the vllm dual lite GPU template to use a pinned, explicit tag instead of the
current latest value, and keep the change localized to the template’s image/tag
fields so future runs always pull the same image version.
src/test/unit/service/serviceJobsDatabase.test.ts (1)

270-362: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Shared resource accounting tests have implicit interdependencies.

The tests in this describe block build on each other: the first creates a service in env-shared (cpu:3), the third creates one in env-gpu (gpu0:1), and the fourth relies on that GPU being held globally. If an earlier test fails, later tests may fail for misleading reasons. Consider giving each test its own setup or using unique resource IDs per test to reduce coupling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/service/serviceJobsDatabase.test.ts` around lines 270 - 362,
The shared resource accounting tests in the describe block are coupled because
later cases depend on jobs created in earlier cases. Update the tests around
SharedAccountingEngine/getUsedResources/checkIfResourcesAreAvailable so each
test sets up its own service jobs or uses unique resource IDs/environment names,
rather than relying on the cpu/gpu state left by prior tests. This will make the
assertions independent and prevent misleading failures when one case breaks.
.github/workflows/ci.yml (1)

121-121: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Barge checkout is pinned in test_integration but not in test_system.

Line 121 pins barge to a fixed commit for stability, but the test_system job's identical "Checkout Barge" step (214-218) still floats on the default branch, leaving it exposed to the same upstream-drift flakiness this pin is meant to avoid.

Also applies to: 214-218

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 121, `test_system` has the same unpinned
Barge checkout as `test_integration`, so update the "Checkout Barge" step in
that job to use the same fixed commit ref already used elsewhere in the
workflow. Keep the change aligned with the existing checkout step so both jobs
reference the same stable Barge revision and avoid upstream drift; use the
matching checkout configuration in the workflow rather than leaving the default
branch floating.
src/components/database/sqliteCompute.ts (1)

244-355: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated db.all → Promise → mapServiceRows boilerplate.

getServiceJob, getRunningServiceJobs, getExpiredServiceJobs, and getPendingServiceStarts all repeat the same execute/resolve/reject/map pattern, differing only by SQL string and params.

♻️ Proposed refactor
+  private runServiceQuery(selectSQL: string, params: Array<string | number>): Promise<ServiceJob[]> {
+    return new Promise<ServiceJob[]>((resolve, reject) => {
+      this.db.all(selectSQL, params, (err, rows: any[] | undefined) => {
+        if (err) {
+          DATABASE_LOGGER.error(err.message)
+          reject(err)
+        } else {
+          resolve(this.mapServiceRows(rows))
+        }
+      })
+    })
+  }
+
   getServiceJob(serviceId?: string, owner?: string): Promise<ServiceJob[]> {
     const params: any[] = []
     let selectSQL = `SELECT * FROM service_jobs WHERE 1=1`
     if (serviceId) {
       selectSQL += ` AND serviceId = ?`
       params.push(serviceId)
     }
     if (owner) {
       selectSQL += ` AND owner = ?`
       params.push(owner)
     }
-    return new Promise<ServiceJob[]>((resolve, reject) => {
-      this.db.all(selectSQL, params, (err, rows: any[] | undefined) => {
-        if (err) {
-          DATABASE_LOGGER.error(err.message)
-          reject(err)
-        } else {
-          resolve(this.mapServiceRows(rows))
-        }
-      })
-    })
+    return this.runServiceQuery(selectSQL, params)
   }

Apply the same substitution to getRunningServiceJobs, getExpiredServiceJobs, and getPendingServiceStarts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/database/sqliteCompute.ts` around lines 244 - 355, The methods
getServiceJob, getRunningServiceJobs, getExpiredServiceJobs, and
getPendingServiceStarts all duplicate the same this.db.all Promise wrapper and
mapServiceRows resolution logic. Extract that repeated database execution
pattern into a single reusable helper in sqliteCompute.ts, then have each of
those methods build its SQL/params and delegate to the helper so only the
query-specific parts remain.
src/test/integration/services.test.ts (1)

223-248: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the unnecessary async + eslint-disable on readLogsWithTimeout.

The function body already returns new Promise(...) directly — no await occurs, so the async keyword (and the require-await suppression it needs) can be removed rather than worked around. As per coding guidelines, Use async only for functions that actually await something (require-await is an error).

♻️ Proposed fix
-  // eslint-disable-next-line require-await
-  async function readLogsWithTimeout(
+  function readLogsWithTimeout(
     stream: Readable,
     timeoutMs = 5000
   ): Promise<string> {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/integration/services.test.ts` around lines 223 - 248, The
`readLogsWithTimeout` helper in `services.test.ts` is marked `async` even though
it only returns a `Promise` and never uses `await`, so remove the unnecessary
`async` and the `eslint-disable-next-line require-await` suppression. Keep the
existing promise-based implementation and its behavior intact, just simplify the
function signature so it matches the `require-await` rule. Reference the
`readLogsWithTimeout` function when updating it.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/c2d/compute_engine_base.ts`:
- Around line 445-470: Make running-job accounting fail consistently in
getUsedResources: the new service-job fetch already propagates DB errors, but
getRunningJobs still catches and falls back to an empty list, which can
under-count compute usage and let checkIfResourcesAreAvailable overcommit.
Update getRunningJobs and the surrounding getUsedResources flow in
compute_engine_base so both running-work queries share the same failure
behavior, and ensure any DB fetch error prevents allocation rather than
continuing with partial data.

In `@src/components/c2d/compute_engine_docker.ts`:
- Around line 3313-3329: `compute_engine_docker.ts` in the service start flow
leaves `job.payment.lockTx` unset until after
`this.escrow.waitForTransaction(chainId, lockTx)`, so a timeout/rejection
bypasses the refund path in the outer `catch`. Move the `job.payment.lockTx =
lockTx` assignment (and persist via `this.db.updateServiceJob(job)`) immediately
after `this.escrow.createLock` succeeds, before waiting for confirmation, so
`safeCancelLock` can still run if `waitForTransaction` fails. Use the `lockTx`
handling in the LOCKING section of the job setup flow as the fix point.
- Around line 3183-3208: buildServiceResourceConstraints is resolving GPU
DeviceRequests from the connection-wide resource pool instead of the selected
environment, so multi-environment services can attach the wrong devices. Update
the method to resolve the environment-specific resource list first (matching the
compute-job flow) and pass that resolved list into getDockerDeviceRequest, while
keeping allocateCpus scoped by serviceId/environment as it is now. Use the
existing buildServiceResourceConstraints and getDockerDeviceRequest symbols to
wire the environment-local resources through the device request path.
- Around line 3183-3208: The CPU cpuset path in buildServiceResourceConstraints
is using ComputeResourceRequest.amount directly, so fractional values like 0.5
or 2.5 can make allocateCpus() fall through and return all free cores. Update
buildServiceResourceConstraints and/or allocateCpus so cpuset assignment only
happens for integer CPU counts, or explicitly round/validate the cpu value
before calling allocateCpus(). Keep NanoCpus based on the original CPU amount,
but prevent non-integer requests from being converted into a cpuset string.
- Around line 1778-1796: The expired-service cleanup in compute_engine_docker’s
expired-services loop always rewrites the job to Expired, even if stopService
returned an error-state job, so first inspect the result of stopService for each
serviceId/owner instead of relying on .catch(). If stopService reports failure
(for example by returning a job with Error status or similar), preserve that
status and do not overwrite it to Expired; only mark the record Expired after a
successful teardown, using the existing db.getServiceJob and db.updateServiceJob
flow.

In `@src/components/c2d/serviceResourceMatching.ts`:
- Around line 3-13: The exported resolveServiceImage utility uses a non-null
assertion on the optional serviceId parameter, which makes the signature
misleading and unsafe. Update the function so the dockerfile branch no longer
force-unwraps serviceId; either require serviceId in the signature or
guard/fallback before calling toLowerCase. Keep the fix localized to
resolveServiceImage and ensure any callers remain compatible with the chosen
contract.

In `@src/components/core/service/extendService.ts`:
- Around line 108-116: Normalize task.additionalDuration to a number in
extendService before any arithmetic or downstream use. In the extendService
flow, coerce the raw value once near the maxDuration/remainingSeconds
calculation, then reuse that numeric local for newTotalDuration, pricing, lock
time, and the expiresAt/duration updates so string input cannot concatenate or
leak through. Keep the validation and response logic in place, but base it on
the normalized value rather than task.additionalDuration.

In `@src/test/unit/compute.test.ts`:
- Around line 1291-1292: The test file has a duplicate block-scoped declaration
for network, which will break TypeScript compilation. In the compute.test.ts
setup near the engine and network declarations, remove the extra let network
line so network is declared only once and the existing sinon.SinonStub typing is
preserved.

---

Outside diff comments:
In @.github/workflows/ci.yml:
- Around line 116-121: The third-party checkout in the workflow currently leaves
Git credentials persisted, which `start_ocean.sh` can inherit when running code
from the `barge` repo. Update the `Checkout Barge` step in the CI workflow to
disable credential persistence by adding `persist-credentials: false` to the
`actions/checkout@v4` configuration for that checkout.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Line 121: `test_system` has the same unpinned Barge checkout as
`test_integration`, so update the "Checkout Barge" step in that job to use the
same fixed commit ref already used elsewhere in the workflow. Keep the change
aligned with the existing checkout step so both jobs reference the same stable
Barge revision and avoid upstream drift; use the matching checkout configuration
in the workflow rather than leaving the default branch floating.

In `@docs/serviceTemplates/vllm-dual-lite-gpu.json`:
- Around line 5-6: The service template is using a floating container tag, which
can make deployments non-reproducible when the upstream image changes. Update
the image configuration in the vllm dual lite GPU template to use a pinned,
explicit tag instead of the current latest value, and keep the change localized
to the template’s image/tag fields so future runs always pull the same image
version.

In `@docs/serviceTemplates/vllm-qwen-0_5b.json`:
- Around line 5-6: The service template is using a mutable container tag, so
update the vllm/vllm-openai image definition to a fixed version instead of
latest. Replace the tag value in the Qwen template JSON with a specific,
reproducible release (for example a v0.x.y tag) so deployments of this template
stay stable over time.

In `@src/components/c2d/compute_engine_docker.ts`:
- Around line 1778-1796: The expired-service cleanup in compute_engine_docker’s
internal loop is running one service at a time and can block the whole tick.
Update the expired-services handling in the InternalLoop path to process the
`expiredServices` batch concurrently instead of awaiting each `stopService`
sequentially, using the existing `stopService`, `getServiceJob`, and
`updateServiceJob` flow per service. Preserve the per-service error logging and
status update, but structure the loop so simultaneous expirations do not stall
unrelated compute jobs or services.

In `@src/components/database/sqliteCompute.ts`:
- Around line 244-355: The methods getServiceJob, getRunningServiceJobs,
getExpiredServiceJobs, and getPendingServiceStarts all duplicate the same
this.db.all Promise wrapper and mapServiceRows resolution logic. Extract that
repeated database execution pattern into a single reusable helper in
sqliteCompute.ts, then have each of those methods build its SQL/params and
delegate to the helper so only the query-specific parts remain.

In `@src/test/integration/services.test.ts`:
- Around line 223-248: The `readLogsWithTimeout` helper in `services.test.ts` is
marked `async` even though it only returns a `Promise` and never uses `await`,
so remove the unnecessary `async` and the `eslint-disable-next-line
require-await` suppression. Keep the existing promise-based implementation and
its behavior intact, just simplify the function signature so it matches the
`require-await` rule. Reference the `readLogsWithTimeout` function when updating
it.

In `@src/test/unit/service/serviceJobsDatabase.test.ts`:
- Around line 270-362: The shared resource accounting tests in the describe
block are coupled because later cases depend on jobs created in earlier cases.
Update the tests around
SharedAccountingEngine/getUsedResources/checkIfResourcesAreAvailable so each
test sets up its own service jobs or uses unique resource IDs/environment names,
rather than relying on the cpu/gpu state left by prior tests. This will make the
assertions independent and prevent misleading failures when one case breaks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 821c7e66-de0c-40cb-8366-4e51e8dc5130

📥 Commits

Reviewing files that changed from the base of the PR and between 2e892d2 and 0e007d8.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (61)
  • .dockerignore
  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
  • CLAUDE.md
  • Dockerfile
  • README.md
  • config.json
  • docs/API.md
  • docs/Ocean Node.postman_collection.json
  • docs/env.md
  • docs/serviceTemplates/README.md
  • docs/serviceTemplates/llamacpp-phi4-cpu.json
  • docs/serviceTemplates/vllm-dual-lite-cpu.json
  • docs/serviceTemplates/vllm-dual-lite-gpu.json
  • docs/serviceTemplates/vllm-hf-model.json
  • docs/serviceTemplates/vllm-nomic-embed.json
  • docs/serviceTemplates/vllm-qwen-0_5b.json
  • docs/services.md
  • package.json
  • src/@types/C2D/C2D.ts
  • src/@types/C2D/ServiceOnDemand.ts
  • src/@types/Escrow.ts
  • src/@types/OceanNode.ts
  • src/@types/commands.ts
  • src/components/Indexer/processor.ts
  • src/components/Indexer/processors/EscrowEventProcessor.ts
  • src/components/c2d/compute_engine_base.ts
  • src/components/c2d/compute_engine_docker.ts
  • src/components/c2d/compute_engines.ts
  • src/components/c2d/serviceResourceMatching.ts
  • src/components/core/compute/startCompute.ts
  • src/components/core/handler/coreHandlersRegistry.ts
  • src/components/core/service/extendService.ts
  • src/components/core/service/getStatus.ts
  • src/components/core/service/getStreamableLogs.ts
  • src/components/core/service/getTemplates.ts
  • src/components/core/service/index.ts
  • src/components/core/service/restartService.ts
  • src/components/core/service/startService.ts
  • src/components/core/service/stopService.ts
  • src/components/core/service/templateLoader.ts
  • src/components/core/service/utils.ts
  • src/components/core/utils/escrow.ts
  • src/components/database/C2DDatabase.ts
  • src/components/database/ElasticSchemas.ts
  • src/components/database/TypesenseSchemas.ts
  • src/components/database/sqliteCompute.ts
  • src/components/httpRoutes/compute.ts
  • src/test/integration/algorithmsAccess.test.ts
  • src/test/integration/escrow.test.ts
  • src/test/integration/services.test.ts
  • src/test/unit/c2d/serviceResourceMatching.test.ts
  • src/test/unit/compute.test.ts
  • src/test/unit/service/serviceHandlers.test.ts
  • src/test/unit/service/serviceJobsDatabase.test.ts
  • src/test/unit/service/serviceSchemas.test.ts
  • src/test/unit/service/serviceUtils.test.ts
  • src/test/unit/service/templateLoader.test.ts
  • src/utils/config/constants.ts
  • src/utils/config/schemas.ts
  • src/utils/constants.ts
✅ Files skipped from review due to trivial changes (4)
  • README.md
  • src/components/core/service/index.ts
  • docs/services.md
  • CLAUDE.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/env.md
  • config.json
  • src/@types/C2D/C2D.ts

Comment on lines +445 to +470

// Fold in on-demand services: they share the same physical resource pool as
// compute jobs, so a running service must occupy resources too. Services are
// always paid (no free tier) and always "running" while in the DB's running set,
// so we only tally their resources — job-slot/queue metrics stay compute-only.
// Do NOT swallow this failure: getUsedResources feeds the strict resource-availability
// gate (checkIfResourcesAreAvailable). Under-counting running services would let the
// engine overcommit shared GPU/CPU/RAM. Let it propagate so the allocation path defers
// the job (the caller already wraps getComputeEnvironments in try/catch) rather than
// proceeding with missing service data.
const serviceJobs: ServiceJob[] = await this.db.getRunningServiceJobs(
this.getC2DConfig().hash
)
for (const svc of serviceJobs) {
const isThisEnv = svc.environment === env.id
for (const resource of svc.resources) {
const envRes = envResourceMap.get(resource.id)
if (!envRes) continue
// discrete resources (GPUs, FPGAs, NICs) tracked globally across all envs;
// fungible resources (cpu, ram, disk) are per-env exclusive.
const isGloballyTracked = envRes.kind === 'discrete'
if (!isGloballyTracked && !isThisEnv) continue
if (!(resource.id in usedResources)) usedResources[resource.id] = 0
usedResources[resource.id] += resource.amount
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Callers of getUsedResources and whether they are guarded
rg -nP -C4 '\bgetUsedResources\s*\(' src --type=ts
# Confirm ComputeResource carries kind/shareable used at 433/465/536/538
rg -nP '(kind|shareable)\s*[?:]' src/@types --type=ts

Repository: oceanprotocol/ocean-node

Length of output: 5362


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the full getUsedResources implementation around both fetches
sed -n '381,470p' src/components/c2d/compute_engine_base.ts

# Inspect the caller that uses getUsedResources / getComputeEnvironments
sed -n '1040,1105p' src/components/c2d/compute_engine_docker.ts

# Inspect any other direct callers that may need try/catch
rg -n -C3 '\bgetComputeEnvironments\s*\(|\bgetUsedResources\s*\(' src --type=ts

Repository: oceanprotocol/ocean-node

Length of output: 21179


Make running-job accounting fail consistently

src/components/c2d/compute_engine_base.ts:385-470
The new service-job path correctly propagates DB failures, but getRunningJobs() still swallows its error and continues with an empty list. That leaves the same under-count/overcommit gap for compute jobs, so the availability gate can still admit work on a stale view. Either propagate both fetches or have getUsedResources() stop allocation whenever either running-work query fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_base.ts` around lines 445 - 470, Make
running-job accounting fail consistently in getUsedResources: the new
service-job fetch already propagates DB errors, but getRunningJobs still catches
and falls back to an empty list, which can under-count compute usage and let
checkIfResourcesAreAvailable overcommit. Update getRunningJobs and the
surrounding getUsedResources flow in compute_engine_base so both running-work
queries share the same failure behavior, and ensure any DB fetch error prevents
allocation rather than continuing with partial data.

Comment on lines +1778 to +1796
// Service-on-Demand expiry: stop services whose paid window has elapsed.
const expiredServices = await this.db.getExpiredServiceJobs(
this.getC2DConfig().hash
)
for (const svc of expiredServices) {
CORE_LOGGER.info(`Service ${svc.serviceId} expired — stopping`)
await this.stopService(svc.serviceId, svc.owner).catch((e) => {
CORE_LOGGER.error(
`Failed to stop expired service ${svc.serviceId}: ${e.message}`
)
})
// mark the (now stopped) record as Expired so it is not picked up again
const [stoppedJob] = await this.db.getServiceJob(svc.serviceId, svc.owner)
if (stoppedJob) {
stoppedJob.status = ServiceStatusNumber.Expired
stoppedJob.statusText = ServiceStatusText[ServiceStatusNumber.Expired]
await this.db.updateServiceJob(stoppedJob)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Expired-service cleanup always marks the record Expired, even when teardown fails.

stopService never throws — on a genuine cleanup failure (non-benign Docker error) it internally sets job.status = Error and simply returns the job, so .catch() here never fires. This loop then unconditionally re-fetches the record and overwrites its status to Expired regardless of what stopService actually returned. If the container/network teardown failed, releaseHostPort was never called inside stopService (it's skipped once the try-block throws), so the host ports stay reserved in memory while the record is now marked Expired and will no longer be picked up by getExpiredServiceJobs on the next tick — the Docker container/network can keep running indefinitely with no retry, and after a node restart those same ports won't be re-seeded via seedAllocatedPorts (which only seeds "running" jobs), risking a port collision when a new service later gets allocated the same host port.

🐛 Proposed fix
-        await this.stopService(svc.serviceId, svc.owner).catch((e) => {
-          CORE_LOGGER.error(
-            `Failed to stop expired service ${svc.serviceId}: ${e.message}`
-          )
-        })
-        // mark the (now stopped) record as Expired so it is not picked up again
-        const [stoppedJob] = await this.db.getServiceJob(svc.serviceId, svc.owner)
-        if (stoppedJob) {
-          stoppedJob.status = ServiceStatusNumber.Expired
-          stoppedJob.statusText = ServiceStatusText[ServiceStatusNumber.Expired]
-          await this.db.updateServiceJob(stoppedJob)
-        }
+        const stopped = await this.stopService(svc.serviceId, svc.owner).catch((e) => {
+          CORE_LOGGER.error(
+            `Failed to stop expired service ${svc.serviceId}: ${e.message}`
+          )
+          return null
+        })
+        // Only flip to Expired once teardown actually succeeded; otherwise leave the
+        // Error status so it keeps getting retried instead of leaking the container.
+        if (stopped && stopped.status === ServiceStatusNumber.Stopped) {
+          stopped.status = ServiceStatusNumber.Expired
+          stopped.statusText = ServiceStatusText[ServiceStatusNumber.Expired]
+          await this.db.updateServiceJob(stopped)
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Service-on-Demand expiry: stop services whose paid window has elapsed.
const expiredServices = await this.db.getExpiredServiceJobs(
this.getC2DConfig().hash
)
for (const svc of expiredServices) {
CORE_LOGGER.info(`Service ${svc.serviceId} expired — stopping`)
await this.stopService(svc.serviceId, svc.owner).catch((e) => {
CORE_LOGGER.error(
`Failed to stop expired service ${svc.serviceId}: ${e.message}`
)
})
// mark the (now stopped) record as Expired so it is not picked up again
const [stoppedJob] = await this.db.getServiceJob(svc.serviceId, svc.owner)
if (stoppedJob) {
stoppedJob.status = ServiceStatusNumber.Expired
stoppedJob.statusText = ServiceStatusText[ServiceStatusNumber.Expired]
await this.db.updateServiceJob(stoppedJob)
}
}
// Service-on-Demand expiry: stop services whose paid window has elapsed.
const expiredServices = await this.db.getExpiredServiceJobs(
this.getC2DConfig().hash
)
for (const svc of expiredServices) {
CORE_LOGGER.info(`Service ${svc.serviceId} expired — stopping`)
const stopped = await this.stopService(svc.serviceId, svc.owner).catch((e) => {
CORE_LOGGER.error(
`Failed to stop expired service ${svc.serviceId}: ${e.message}`
)
return null
})
// Only flip to Expired once teardown actually succeeded; otherwise leave the
// Error status so it keeps getting retried instead of leaking the container.
if (stopped && stopped.status === ServiceStatusNumber.Stopped) {
stopped.status = ServiceStatusNumber.Expired
stopped.statusText = ServiceStatusText[ServiceStatusNumber.Expired]
await this.db.updateServiceJob(stopped)
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 1778 - 1796, The
expired-service cleanup in compute_engine_docker’s expired-services loop always
rewrites the job to Expired, even if stopService returned an error-state job, so
first inspect the result of stopService for each serviceId/owner instead of
relying on .catch(). If stopService reports failure (for example by returning a
job with Error status or similar), preserve that status and do not overwrite it
to Expired; only mark the record Expired after a successful teardown, using the
existing db.getServiceJob and db.updateServiceJob flow.

Comment on lines +3183 to +3208
// Builds Docker HostConfig resource constraints (memory, cpu, GPU device requests)
// from a service resource request, resolved against the connection-level resource pool.
private buildServiceResourceConstraints(
resources: ComputeResourceRequest[],
serviceId: string,
environment: string
): {
Memory?: number
NanoCpus?: number
DeviceRequests?: any[]
CpusetCpus?: string
} {
const connResources: ComputeResource[] =
this.getC2DConfig().connection?.resources ?? []
const ram = resources.find((r) => r.id === 'ram')?.amount
const cpu = resources.find((r) => r.id === 'cpu')?.amount
const deviceRequests = this.getDockerDeviceRequest(resources, connResources) ?? []
const cpusetStr =
cpu && cpu > 0 ? this.allocateCpus(serviceId, cpu, environment) : null
return {
Memory: ram ? ram * 1024 ** 3 : undefined,
NanoCpus: cpu ? cpu * 1e9 : undefined,
DeviceRequests: deviceRequests.length ? deviceRequests : undefined,
CpusetCpus: cpusetStr ?? undefined
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Device requests resolved against the wrong resource pool for multi-environment setups.

buildServiceResourceConstraints takes an environment parameter but only uses it for allocateCpus; GPU device requests are built from this.getC2DConfig().connection?.resources (the raw, connection-wide config) instead of the specific environment's resolved resource list. For compute jobs, the equivalent code correctly scopes to the environment (const envResource = env?.resources || [] before calling getDockerDeviceRequest). With multiple environments each owning distinct GPUs (the "shared hardware resource pool" this PR targets), this can select/attach the wrong device(s) to a service container.

🐛 Proposed fix
   private buildServiceResourceConstraints(
     resources: ComputeResourceRequest[],
     serviceId: string,
     environment: string
   ): {
     Memory?: number
     NanoCpus?: number
     DeviceRequests?: any[]
     CpusetCpus?: string
   } {
-    const connResources: ComputeResource[] =
-      this.getC2DConfig().connection?.resources ?? []
+    const envResources: ComputeResource[] =
+      this.envs.find((e) => e.id === environment)?.resources ?? []
     const ram = resources.find((r) => r.id === 'ram')?.amount
     const cpu = resources.find((r) => r.id === 'cpu')?.amount
-    const deviceRequests = this.getDockerDeviceRequest(resources, connResources) ?? []
+    const deviceRequests = this.getDockerDeviceRequest(resources, envResources) ?? []
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Builds Docker HostConfig resource constraints (memory, cpu, GPU device requests)
// from a service resource request, resolved against the connection-level resource pool.
private buildServiceResourceConstraints(
resources: ComputeResourceRequest[],
serviceId: string,
environment: string
): {
Memory?: number
NanoCpus?: number
DeviceRequests?: any[]
CpusetCpus?: string
} {
const connResources: ComputeResource[] =
this.getC2DConfig().connection?.resources ?? []
const ram = resources.find((r) => r.id === 'ram')?.amount
const cpu = resources.find((r) => r.id === 'cpu')?.amount
const deviceRequests = this.getDockerDeviceRequest(resources, connResources) ?? []
const cpusetStr =
cpu && cpu > 0 ? this.allocateCpus(serviceId, cpu, environment) : null
return {
Memory: ram ? ram * 1024 ** 3 : undefined,
NanoCpus: cpu ? cpu * 1e9 : undefined,
DeviceRequests: deviceRequests.length ? deviceRequests : undefined,
CpusetCpus: cpusetStr ?? undefined
}
}
// Builds Docker HostConfig resource constraints (memory, cpu, GPU device requests)
// from a service resource request, resolved against the connection-level resource pool.
private buildServiceResourceConstraints(
resources: ComputeResourceRequest[],
serviceId: string,
environment: string
): {
Memory?: number
NanoCpus?: number
DeviceRequests?: any[]
CpusetCpus?: string
} {
const envResources: ComputeResource[] =
this.envs.find((e) => e.id === environment)?.resources ?? []
const ram = resources.find((r) => r.id === 'ram')?.amount
const cpu = resources.find((r) => r.id === 'cpu')?.amount
const deviceRequests = this.getDockerDeviceRequest(resources, envResources) ?? []
const cpusetStr =
cpu && cpu > 0 ? this.allocateCpus(serviceId, cpu, environment) : null
return {
Memory: ram ? ram * 1024 ** 3 : undefined,
NanoCpus: cpu ? cpu * 1e9 : undefined,
DeviceRequests: deviceRequests.length ? deviceRequests : undefined,
CpusetCpus: cpusetStr ?? undefined
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 3183 - 3208,
buildServiceResourceConstraints is resolving GPU DeviceRequests from the
connection-wide resource pool instead of the selected environment, so
multi-environment services can attach the wrong devices. Update the method to
resolve the environment-specific resource list first (matching the compute-job
flow) and pass that resolved list into getDockerDeviceRequest, while keeping
allocateCpus scoped by serviceId/environment as it is now. Use the existing
buildServiceResourceConstraints and getDockerDeviceRequest symbols to wire the
environment-local resources through the device request path.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether ComputeResourceRequest/ServiceResourceRequest cpu "amount" can be fractional
# and whether allocateCpus() is exercised with non-integer counts elsewhere.
rg -n "amount" src/@types/C2D/*.ts src/utils/config/schemas.ts
rg -n "freeCores.length === count" -A3 -B3 src/components/c2d/compute_engine_docker.ts

Repository: oceanprotocol/ocean-node

Length of output: 650


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the CPU allocation logic and the resource request definitions.
sed -n '2440,2525p' src/components/c2d/compute_engine_docker.ts
printf '\n---\n'
sed -n '1,220p' src/@types/C2D/ServiceOnDemand.ts
printf '\n---\n'
sed -n '1,220p' src/@types/C2D/C2D.ts
printf '\n---\n'
sed -n '1,140p' src/utils/config/schemas.ts

Repository: oceanprotocol/ocean-node

Length of output: 20465


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any normalization or integer-only validation on CPU amounts.
rg -n "cpu.*amount|amount.*cpu|floor\\(|round\\(|parseInt\\(|integer|Number.isInteger|isInteger|NanoCpus|CpusetCpus" src/components/c2d src/utils src/@types

Repository: oceanprotocol/ocean-node

Length of output: 3941


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Probe the exact effect of the equality check for fractional counts with a tiny standalone simulation.
python3 - <<'PY'
def allocate(env_cores, count):
    free = []
    for core in env_cores:
        free.append(core)
        if len(free) == count:
            break
    return free

for count in [0.5, 1, 1.5, 2, 2.5]:
    print(count, allocate(list(range(6)), count))
PY

Repository: oceanprotocol/ocean-node

Length of output: 246


Clamp or reject fractional CPU counts before cpuset allocation. ComputeResourceRequest.amount is a plain number, and allocateCpus() stops only on freeCores.length === count. For 0.5/2.5, that never matches, so the scan returns every free core and pins the service/job to the whole environment. Skip cpuset assignment for non-integer CPU requests, or round the count before scanning.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 3183 - 3208, The
CPU cpuset path in buildServiceResourceConstraints is using
ComputeResourceRequest.amount directly, so fractional values like 0.5 or 2.5 can
make allocateCpus() fall through and return all free cores. Update
buildServiceResourceConstraints and/or allocateCpus so cpuset assignment only
happens for integer CPU counts, or explicitly round/validate the cpu value
before calling allocateCpus(). Keep NanoCpus based on the original CPU amount,
but prevent non-integer requests from being converted into a cpuset string.

Comment on lines +3313 to +3329
try {
// 1. LOCKING — lock the consumer's funds in escrow (refundable until claimed).
job.status = ServiceStatusNumber.Locking
job.statusText = ServiceStatusText[ServiceStatusNumber.Locking]
await this.db.updateServiceJob(job)
const lockTx = await this.escrow.createLock(
chainId,
serviceId,
token,
job.owner,
job.payment.cost,
this.escrow.getMinLockTime(job.duration)
)
if (!lockTx) throw new Error('Escrow lock failed')
await this.escrow.waitForTransaction(chainId, lockTx)
job.payment.lockTx = lockTx
await this.db.updateServiceJob(job)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

lockTx is only persisted after waitForTransaction succeeds, so a timeout loses refund tracking.

If this.escrow.waitForTransaction(chainId, lockTx) throws (e.g. slow confirmation past the default 60s timeout) or otherwise rejects, execution jumps straight to the outer catch, and job.payment.lockTx was never assigned. The catch's refund guard (if (job.payment.lockTx && !job.payment.claimTx && !job.payment.cancelTx)) then evaluates false, so safeCancelLock is never attempted even though createLock actually succeeded on-chain and the consumer's funds are locked. The lock now silently sits until it naturally expires (job.duration + claimDurationTimeout) and is only swept much later by the periodic cleanUpUnknownLocks fallback — the consumer's funds are effectively frozen for a failed service start.

🐛 Proposed fix
       const lockTx = await this.escrow.createLock(
         chainId,
         serviceId,
         token,
         job.owner,
         job.payment.cost,
         this.escrow.getMinLockTime(job.duration)
       )
       if (!lockTx) throw new Error('Escrow lock failed')
-      await this.escrow.waitForTransaction(chainId, lockTx)
       job.payment.lockTx = lockTx
       await this.db.updateServiceJob(job)
+      await this.escrow.waitForTransaction(chainId, lockTx)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
// 1. LOCKING — lock the consumer's funds in escrow (refundable until claimed).
job.status = ServiceStatusNumber.Locking
job.statusText = ServiceStatusText[ServiceStatusNumber.Locking]
await this.db.updateServiceJob(job)
const lockTx = await this.escrow.createLock(
chainId,
serviceId,
token,
job.owner,
job.payment.cost,
this.escrow.getMinLockTime(job.duration)
)
if (!lockTx) throw new Error('Escrow lock failed')
await this.escrow.waitForTransaction(chainId, lockTx)
job.payment.lockTx = lockTx
await this.db.updateServiceJob(job)
try {
// 1. LOCKING — lock the consumer's funds in escrow (refundable until claimed).
job.status = ServiceStatusNumber.Locking
job.statusText = ServiceStatusText[ServiceStatusNumber.Locking]
await this.db.updateServiceJob(job)
const lockTx = await this.escrow.createLock(
chainId,
serviceId,
token,
job.owner,
job.payment.cost,
this.escrow.getMinLockTime(job.duration)
)
if (!lockTx) throw new Error('Escrow lock failed')
job.payment.lockTx = lockTx
await this.db.updateServiceJob(job)
await this.escrow.waitForTransaction(chainId, lockTx)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_docker.ts` around lines 3313 - 3329,
`compute_engine_docker.ts` in the service start flow leaves `job.payment.lockTx`
unset until after `this.escrow.waitForTransaction(chainId, lockTx)`, so a
timeout/rejection bypasses the refund path in the outer `catch`. Move the
`job.payment.lockTx = lockTx` assignment (and persist via
`this.db.updateServiceJob(job)`) immediately after `this.escrow.createLock`
succeeds, before waiting for confirmation, so `safeCancelLock` can still run if
`waitForTransaction` fails. Use the `lockTx` handling in the LOCKING section of
the job setup flow as the fix point.

Comment on lines +3 to +13
export function resolveServiceImage(
image: string,
tag?: string,
checksum?: string,
dockerfile?: string,
serviceId?: string
): string {
if (dockerfile) return `${serviceId!.toLowerCase()}-svc-image:latest`
if (checksum) return `${image}@${checksum}`
return `${image}:${tag ?? 'latest'}`
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Non-null assertion on an optional parameter.

serviceId!.toLowerCase() will throw if dockerfile is set but serviceId is omitted — the parameter is typed optional (serviceId?: string) yet force-unwrapped. Since the only caller always supplies it, this doesn't crash today, but the signature is misleading for a small, reusable, exported utility.

🐛 Proposed fix
 export function resolveServiceImage(
   image: string,
   tag?: string,
   checksum?: string,
   dockerfile?: string,
   serviceId?: string
 ): string {
-  if (dockerfile) return `${serviceId!.toLowerCase()}-svc-image:latest`
+  if (dockerfile) {
+    if (!serviceId) throw new Error('serviceId is required when dockerfile is provided')
+    return `${serviceId.toLowerCase()}-svc-image:latest`
+  }
   if (checksum) return `${image}@${checksum}`
   return `${image}:${tag ?? 'latest'}`
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function resolveServiceImage(
image: string,
tag?: string,
checksum?: string,
dockerfile?: string,
serviceId?: string
): string {
if (dockerfile) return `${serviceId!.toLowerCase()}-svc-image:latest`
if (checksum) return `${image}@${checksum}`
return `${image}:${tag ?? 'latest'}`
}
export function resolveServiceImage(
image: string,
tag?: string,
checksum?: string,
dockerfile?: string,
serviceId?: string
): string {
if (dockerfile) {
if (!serviceId) throw new Error('serviceId is required when dockerfile is provided')
return `${serviceId.toLowerCase()}-svc-image:latest`
}
if (checksum) return `${image}@${checksum}`
return `${image}:${tag ?? 'latest'}`
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/serviceResourceMatching.ts` around lines 3 - 13, The
exported resolveServiceImage utility uses a non-null assertion on the optional
serviceId parameter, which makes the signature misleading and unsafe. Update the
function so the dockerfile branch no longer force-unwraps serviceId; either
require serviceId in the signature or guard/fallback before calling toLowerCase.
Keep the fix localized to resolveServiceImage and ensure any callers remain
compatible with the chosen contract.

Comment on lines +108 to +116
const maxDuration = sod?.maxDurationSeconds ?? 86400
const remainingSeconds = Math.max(0, Math.floor((job.expiresAt - Date.now()) / 1000))
const newTotalDuration = remainingSeconds + task.additionalDuration
if (newTotalDuration > maxDuration)
return buildInvalidParametersResponse(
buildInvalidRequestMessage(
`Extension would result in ${newTotalDuration}s remaining, exceeding maximum ${maxDuration}s`
)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether additionalDuration is ever passed as a string at runtime and
# whether the arithmetic paths rely on numeric coercion.
rg -nP 'additionalDuration' src/components/core/service/extendService.ts src/components/httpRoutes/compute.ts

Repository: oceanprotocol/ocean-node

Length of output: 1233


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant service code and the request mapping that feeds it.
sed -n '1,260p' src/components/core/service/extendService.ts
printf '\n--- compute.ts excerpt ---\n'
sed -n '400,470p' src/components/httpRoutes/compute.ts

Repository: oceanprotocol/ocean-node

Length of output: 11580


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the command type and validation helper to see what runtime shapes are allowed.
sed -n '1,220p' src/@types/commands.ts
printf '\n--- validateCommands.ts excerpt ---\n'
sed -n '1,220p' src/components/httpRoutes/validateCommands.ts

Repository: oceanprotocol/ocean-node

Length of output: 9660


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the ServiceExtendCommand definition and all handler call sites.
rg -n "interface ServiceExtendCommand|class ServiceExtendHandler|new ServiceExtendHandler|runServiceCommand\\(ServiceExtendHandler|SERVICE_EXTEND" src/@types src/components -g '*.ts'

Repository: oceanprotocol/ocean-node

Length of output: 851


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '450,490p' src/@types/commands.ts

Repository: oceanprotocol/ocean-node

Length of output: 1034


Normalize additionalDuration before using it in arithmetic.

additionalDuration is only cast at the route boundary, so a string value can still reach this handler. remainingSeconds + task.additionalDuration will concatenate for string input, and the same raw value is reused for pricing, lock time, and the expiresAt / duration updates. Coerce once to a number and reuse that local everywhere.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/service/extendService.ts` around lines 108 - 116,
Normalize task.additionalDuration to a number in extendService before any
arithmetic or downstream use. In the extendService flow, coerce the raw value
once near the maxDuration/remainingSeconds calculation, then reuse that numeric
local for newTotalDuration, pricing, lock time, and the expiresAt/duration
updates so string input cannot concatenate or leak through. Keep the validation
and response logic in place, but base it on the normalized value rather than
task.additionalDuration.

Comment on lines +1291 to +1292
let engine: any
let network: { id: string; remove: sinon.SinonStub }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Duplicate let network declaration will cause a TypeScript compilation error.

The variable network is declared twice with identical content on consecutive lines. TypeScript will reject this with "Cannot redeclare block-scoped variable 'network'", preventing the entire test file from compiling.

🐛 Proposed fix: remove the duplicate line
 describe('service start/restart Docker cleanup on failure', function () {
   let engine: any
   let network: { id: string; remove: sinon.SinonStub }
-  let network: { id: string; remove: sinon.SinonStub }
 
   function makeContainer(startRejects: boolean) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let engine: any
let network: { id: string; remove: sinon.SinonStub }
let engine: any
let network: { id: string; remove: sinon.SinonStub }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/compute.test.ts` around lines 1291 - 1292, The test file has a
duplicate block-scoped declaration for network, which will break TypeScript
compilation. In the compute.test.ts setup near the engine and network
declarations, remove the extra let network line so network is declared only once
and the existing sinon.SinonStub typing is preserved.

* cross-node auth tokens

* new validate auth token method

* stateless

* fix comment reviews

* revert to ask remote node

* finite number expiry + oom protection

* fix test

* remove meax ttl

* add peerid for signature check

* issuerpeerid on create auth token only

---------

Co-authored-by: alexcos20 <alex.coseru@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/test/utils/signature.ts (1)

21-24: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Require callers to choose the peer binding explicitly.

The helper documents node-bound signatures, but issuerPeerId = '' silently keeps unbound signatures valid by omission. Make the parameter required, or use a separate explicitly named helper for legacy/unbound signatures.

Proposed change
-  issuerPeerId: string = ''
+  issuerPeerId: string
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/utils/signature.ts` around lines 21 - 24, The signature helper
currently defaults issuerPeerId to an empty string, which allows unbound
signatures to be created implicitly. Update the sign helper in signature.ts so
caller intent is explicit: either make issuerPeerId a required parameter in the
existing function, or split the logic into clearly named helpers for bound and
legacy/unbound signatures. Ensure any call sites are updated to pass the peer
binding explicitly and that the helper name and signature reflect the binding
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Auth/index.ts`:
- Around line 160-166: `validateToken()` currently lets a rejected `p2p.sendTo`
throw, instead of treating an unreachable issuer as a failed validation. Update
the `validateToken` flow in `Auth/index.ts` around the `sendTo` call so failures
from `p2p.sendTo` are caught and converted to a `null` return, while preserving
the existing `response?.status?.httpStatus !== 200 || !response.stream` failure
path. Keep the handling localized to `validateToken` and use the existing
`issuerPeerId`, `token`, and `PROTOCOL_COMMANDS.VALIDATE_AUTH_TOKEN` logic.
- Around line 167-183: The delegated-token acceptance path in the Auth flow
currently allows `{ valid: true }` verdicts without a usable expiry and can
construct invalid dates from malformed values. Update the verdict handling in
the token verification logic so `validUntil` is parsed and validated as a finite
timestamp in the future before returning `isValid: true`; otherwise return null.
Use the `verdict` parsing block and the returned object construction in
`Auth/index.ts` to locate the fix, and ensure `validUntil` never becomes `null`
or `Invalid Date` for accepted remote verdicts.

In `@src/components/core/handler/handler.ts`:
- Around line 206-209: The success response in `handler.ts` is missing the
`error` field, making this `P2PCommandResponse` shape inconsistent with the
other status returns in the `Handler` class. Update the success object returned
from the `isAuthRequestValid.address` path to include `error: null` alongside
`stream` and `status`, matching the standard response format used elsewhere in
`Handler`.

In `@src/components/core/utils/nonceHandler.ts`:
- Around line 230-232: The auth-token signature message in
CreateAuthTokenHandler/nonceHandler currently omits chainId, so the same
nonce/signature can be reused across requested chains. Update the message
construction in both the local token creation path and the remote validation
check to include chainId ?? '' alongside consumer, nonce, command, and
issuerPeerId, and make sure the same concatenation is used wherever the
signature is computed or verified.

In `@src/test/integration/auth.test.ts`:
- Around line 198-202: The INVALIDATE_AUTH_TOKEN signature is not bound to the
issuer peer, so it can be replayed across nodes. Update createHashForSignature
usage for the invalidate path in auth.test.ts and the matching validation logic
in validateNonceAndSignature so the hash includes
oceanNode.getKeyManager().getPeerIdString() alongside address, nonce, and
PROTOCOL_COMMANDS.INVALIDATE_AUTH_TOKEN. Make sure the invalidate command’s
signature generation and verification both use the same peer-bound inputs.

---

Nitpick comments:
In `@src/test/utils/signature.ts`:
- Around line 21-24: The signature helper currently defaults issuerPeerId to an
empty string, which allows unbound signatures to be created implicitly. Update
the sign helper in signature.ts so caller intent is explicit: either make
issuerPeerId a required parameter in the existing function, or split the logic
into clearly named helpers for bound and legacy/unbound signatures. Ensure any
call sites are updated to pass the peer binding explicitly and that the helper
name and signature reflect the binding behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 20ccf51f-e7ad-47be-8998-06d06e7f329d

📥 Commits

Reviewing files that changed from the base of the PR and between 0e007d8 and ea51b7c.

📒 Files selected for processing (10)
  • src/components/Auth/index.ts
  • src/components/core/handler/authHandler.ts
  • src/components/core/handler/coreHandlersRegistry.ts
  • src/components/core/handler/handler.ts
  • src/components/core/handler/persistentStorage.ts
  • src/components/core/utils/nonceHandler.ts
  • src/test/integration/auth.test.ts
  • src/test/unit/auth/token.test.ts
  • src/test/utils/signature.ts
  • src/utils/constants.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/constants.ts

Comment thread src/components/Auth/index.ts Outdated
Comment thread src/components/Auth/index.ts
Comment on lines 206 to +209
return {
stream: null,
status: { httpStatus: 200 }
status: { httpStatus: 200 },
consumerAddress: isAuthRequestValid.address

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the success status shape consistent.

This success response drops error: null, unlike the other P2PCommandResponse statuses in this class. Add it to avoid downstream checks seeing an undefined error field.

Proposed change
     return {
       stream: null,
-      status: { httpStatus: 200 },
+      status: { httpStatus: 200, error: null },
       consumerAddress: isAuthRequestValid.address
     }

As per coding guidelines, “All handler responses must use P2PCommandResponse format with httpStatus, body, stream, and error properties.” <coding_guidelines>

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return {
stream: null,
status: { httpStatus: 200 }
status: { httpStatus: 200 },
consumerAddress: isAuthRequestValid.address
return {
stream: null,
status: { httpStatus: 200, error: null },
consumerAddress: isAuthRequestValid.address
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/handler/handler.ts` around lines 206 - 209, The success
response in `handler.ts` is missing the `error` field, making this
`P2PCommandResponse` shape inconsistent with the other status returns in the
`Handler` class. Update the success object returned from the
`isAuthRequestValid.address` path to include `error: null` alongside `stream`
and `status`, matching the standard response format used elsewhere in `Handler`.

Source: Coding guidelines

Comment on lines +230 to +232
const message = String(
String(consumer) + String(nonce) + String(command) + String(issuerPeerId)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Bind chainId into the signed auth-token message.

CreateAuthTokenHandler now stores task.chainId in the JWT, but the signature message omits it. That lets the same unused nonce/signature mint a token for a different requested chain ID; include chainId ?? '' in both local creation and remote validation signature checks if this claim affects authorization.

Proposed fix
   const message = String(
-    String(consumer) + String(nonce) + String(command) + String(issuerPeerId)
+    String(consumer) +
+      String(nonce) +
+      String(command) +
+      String(issuerPeerId) +
+      String(chainId ?? '')
   )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const message = String(
String(consumer) + String(nonce) + String(command) + String(issuerPeerId)
)
const message = String(
String(consumer) +
String(nonce) +
String(command) +
String(issuerPeerId) +
String(chainId ?? '')
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/utils/nonceHandler.ts` around lines 230 - 232, The
auth-token signature message in CreateAuthTokenHandler/nonceHandler currently
omits chainId, so the same nonce/signature can be reused across requested
chains. Update the message construction in both the local token creation path
and the remote validation check to include chainId ?? '' alongside consumer,
nonce, command, and issuerPeerId, and make sure the same concatenation is used
wherever the signature is computed or verified.

Comment on lines +198 to +202
const invalidateHash = createHashForSignature(
consumerAddress,
newNonce,
PROTOCOL_COMMANDS.INVALIDATE_AUTH_TOKEN
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== auth.test.ts excerpt ==\n'
sed -n '160,240p' src/test/integration/auth.test.ts

printf '\n== search createHashForSignature ==\n'
rg -n "createHashForSignature|INVALIDATE_AUTH_TOKEN|getPeerIdString|invalidate auth token|invalidateAuthToken|invalidate.*token" src --glob '!**/dist/**' --glob '!**/build/**'

printf '\n== locate signature helper definitions ==\n'
rg -n "function createHashForSignature|const createHashForSignature|export .*createHashForSignature" src --glob '!**/dist/**' --glob '!**/build/**'

Repository: oceanprotocol/ocean-node

Length of output: 19063


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== signature helper ==\n'
sed -n '1,120p' src/test/utils/signature.ts

printf '\n== auth handler ==\n'
sed -n '1,220p' src/components/core/handler/authHandler.ts

printf '\n== invalidate auth token handler and routes ==\n'
sed -n '1,220p' src/components/core/handler/invalidateAuthTokenHandler.ts 2>/dev/null || true
sed -n '180,240p' src/components/core/handler/coreHandlersRegistry.ts

printf '\n== unit auth token tests ==\n'
sed -n '1,260p' src/test/unit/auth/token.test.ts

Repository: oceanprotocol/ocean-node

Length of output: 18429


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== nonce handler ==\n'
sed -n '1,320p' src/components/core/utils/nonceHandler.ts

printf '\n== auth token implementation ==\n'
sed -n '1,260p' src/components/Auth/index.ts 2>/dev/null || true
sed -n '1,260p' src/components/Auth/*.ts 2>/dev/null || true

printf '\n== auth test helper usages around create/invalidate ==\n'
sed -n '110,220p' src/test/integration/auth.test.ts

Repository: oceanprotocol/ocean-node

Length of output: 26044


Bind INVALIDATE_AUTH_TOKEN to the issuer peer. checkNonce still hashes only address + nonce + command for this path, so the same invalidation signature can be replayed on other nodes. Add oceanNode.getKeyManager().getPeerIdString() to the hash here and mirror it in validateNonceAndSignature for the invalidate command.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/integration/auth.test.ts` around lines 198 - 202, The
INVALIDATE_AUTH_TOKEN signature is not bound to the issuer peer, so it can be
replayed across nodes. Update createHashForSignature usage for the invalidate
path in auth.test.ts and the matching validation logic in
validateNonceAndSignature so the hash includes
oceanNode.getKeyManager().getPeerIdString() alongside address, nonce, and
PROTOCOL_COMMANDS.INVALIDATE_AUTH_TOKEN. Make sure the invalidate command’s
signature generation and verification both use the same peer-bound inputs.

alexcos20 and others added 3 commits July 9, 2026 21:14
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants