Skip to content

fix(deployment): skip initial funding terminally when the escrow account is closed - #3531

Merged
baktun14 merged 2 commits into
mainfrom
fix/deployment-skip-initial-funding-closed-escrow
Jul 28, 2026
Merged

fix(deployment): skip initial funding terminally when the escrow account is closed#3531
baktun14 merged 2 commits into
mainfrom
fix/deployment-skip-initial-funding-closed-escrow

Conversation

@baktun14

@baktun14 baktun14 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Why

Related to CON-735

The new initial-funding flow (#3527) produced this production error in tx-signer during gas simulation of the initial deposit:

Query failed with (6): rpc error: code = Unknown desc = failed to execute message; message index: 0: account closed

The chain's escrow module rejects the deposit because the deployment's escrow account is already closed by the time the async FundDeploymentCommand job runs (the user closed the deployment right after creation, the provider closed it, or the small initial deposit fully drained). The pre-check in fundOnLeaseStarted can't reliably catch this: it reads a lagging REST snapshot (lease.closed_on), and its escrow-balance check counts already-transferred funds.

The actual defect is error classification: "account closed" was unmapped in both chain-error services, so a permanently terminal condition surfaced as a 500 and pg-boss retried it 5 times (exp backoff) against the still-closed account. The hourly top-up cron never has this problem because it tolerates per-deployment deposit failures instead of throwing.

What

  • apps/tx-signer ChainErrorService: map account closed → 400 (next to the existing deployment closed pattern).
  • apps/api ChainErrorService: add the account closed clue (→ 400 "Deployment closed") and a public isDeploymentClosedError helper, following the isMasterWalletInsufficientFundsError precedent used by the top-up cron.
  • apps/api InitialDeploymentFundingService: a deposit that fails because the deployment is closed is now a terminal skip (INITIAL_FUNDING_SKIPPED / DEPLOYMENT_CLOSED) on both failure paths — the thrown simulation error and a landed tx with non-zero code. All other errors keep throwing so pg-boss retry still covers transient failures (indexing lag, RPC 5xx).

Side benefit: user-facing deposits into an already-closed deployment now return 400 "Deployment closed" instead of 500.

Deliberately out of scope: a pre-check on escrow_account.state.state from chain REST — it reads the same lagging snapshot so it can only shrink the race window, and plumbing it through RpcDeploymentInfo/DrainingDeploymentOutput touches types shared with the top-up cron. Possible follow-up.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of closed deployment/escrow accounts during initial funding by treating them as terminal, non-retryable outcomes.
    • Added robust detection for “account closed” / “deployment closed” conditions and consistent mapping to a “Deployment closed” error.
    • Ensured jobs skip unnecessary follow-up actions (such as wallet reload scheduling) when funding is skipped due to a closed deployment.
    • Updated funding command payloads to omit the user identifier, and aligned related tests accordingly.

…unt is closed

The chain rejects the initial deposit with "account closed" when the
deployment escrow account gets closed between lease start and the
FundDeploymentCommand execution. The error was unmapped in both
chain-error services, surfaced as a 500, and pg-boss retried the
permanently terminal condition five times.

Classify "account closed" as a 400 alongside "deployment closed" and
make InitialDeploymentFundingService treat closed-deployment deposit
failures as a terminal skip on both the thrown-simulation and
landed-tx-with-non-zero-code paths, mirroring how the hourly top-up
cron tolerates them. All other errors keep retrying.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8f6d169f-b8da-4bf1-99e2-cb5e44a24a5e

📥 Commits

Reviewing files that changed from the base of the PR and between 08a0f3d and 0abc96f.

📒 Files selected for processing (5)
  • apps/api/src/app/services/fund-deployment/fund-deployment.handler.spec.ts
  • apps/api/src/billing/commands/fund-deployment.command.ts
  • apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts
  • apps/api/src/billing/services/managed-signer/managed-signer.service.ts
  • apps/api/src/deployment/services/initial-deployment-funding/initial-deployment-funding.service.ts
💤 Files with no reviewable changes (4)
  • apps/api/src/billing/commands/fund-deployment.command.ts
  • apps/api/src/app/services/fund-deployment/fund-deployment.handler.spec.ts
  • apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts
  • apps/api/src/billing/services/managed-signer/managed-signer.service.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/src/deployment/services/initial-deployment-funding/initial-deployment-funding.service.ts

📝 Walkthrough

Walkthrough

Initial funding now classifies closed-account failures as terminal, logs a skip, and avoids wallet reload scheduling. Chain-error mappings and tests cover the new classification. Fund deployment commands now use walletId instead of requiring userId.

Changes

Closed deployment funding

Layer / File(s) Summary
Closed account error classification
apps/api/src/billing/services/chain-error/*, apps/tx-signer/src/services/chain-error/*
Adds account-closed mappings, deployment-closed detection, and coverage for API conversion and HTTP 400 status handling.
Terminal initial funding path
apps/api/src/deployment/services/initial-deployment-funding/*
Classifies thrown and non-OK deposit results, logs INITIAL_FUNDING_SKIPPED for closed deployments, and preserves ordinary failures as errors. Tests cover wiring and terminal and non-terminal paths.

Fund deployment command payload

Layer / File(s) Summary
Wallet-based funding command payload
apps/api/src/billing/commands/fund-deployment.command.ts, apps/api/src/billing/services/managed-signer/*
Removes userId from the command contract and published payload while retaining walletId, address, and dseq.
Funding handler payload alignment
apps/api/src/app/services/fund-deployment/fund-deployment.handler.spec.ts
Updates the handler test payload to match the revised command shape.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: ygrishajev

✨ 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 fix/deployment-skip-initial-funding-closed-escrow

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

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.54%. Comparing base (c5f6a63) to head (0abc96f).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3531      +/-   ##
==========================================
- Coverage   74.30%   73.54%   -0.76%     
==========================================
  Files        1148     1084      -64     
  Lines       29815    27900    -1915     
  Branches     7465     7068     -397     
==========================================
- Hits        22154    20520    -1634     
+ Misses       6768     6515     -253     
+ Partials      893      865      -28     
Flag Coverage Δ *Carryforward flag
api 87.53% <100.00%> (-0.02%) ⬇️
deploy-web 63.79% <ø> (ø) Carriedforward from 08a0f3d
log-collector ?
notifications 93.84% <ø> (ø) Carriedforward from 08a0f3d
provider-console 81.38% <ø> (ø) Carriedforward from 08a0f3d
provider-inventory ?
provider-proxy 88.17% <ø> (ø) Carriedforward from 08a0f3d
tx-signer 86.72% <ø> (ø)

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...pi/src/billing/commands/fund-deployment.command.ts 100.00% <ø> (ø)
...illing/services/chain-error/chain-error.service.ts 100.00% <100.00%> (ø)
.../services/managed-signer/managed-signer.service.ts 100.00% <ø> (ø)
...ment-funding/initial-deployment-funding.service.ts 100.00% <100.00%> (ø)
...er/src/services/chain-error/chain-error.service.ts 100.00% <ø> (ø)

... and 65 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@claude claude 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.

LGTM — small, well-tested fix that correctly classifies the "account closed" chain error as a terminal 400 instead of retrying against a permanently closed escrow account.

Extended reasoning...

Overview

The PR touches three services: apps/tx-signer/ChainErrorService and apps/api/ChainErrorService (both gain an account closed → 400 mapping), and apps/api/InitialDeploymentFundingService (now treats a deposit rejected for a closed deployment as a terminal skip on both the thrown-simulation-error path and the landed-non-zero-code path, instead of throwing and letting pg-boss retry against a dead escrow account).

Security risks

None. This is error classification/control-flow only — no new external input handling, no auth/permission changes, and no new data exposure. The regex /account closed|deployment closed/i only inspects an already-thrown error message string.

Level of scrutiny

Low-to-moderate is appropriate: the change is additive (new map entries, one new public helper, one new constructor dependency resolved via tsyringe DI) and mirrors an existing pattern (isMasterWalletInsufficientFundsError) already used by the top-up cron. InitialDeploymentFundingService is only ever instantiated through the DI container, so the new ChainErrorService constructor parameter is safe. I checked the ordering in the non-zero-code path — isDeploymentClosedError is checked before the INITIAL_FUNDING_TX_FAILED error log, so closed-account failures correctly skip that noisy error log and go to the terminal skip branch instead.

Other factors

Tests were added for both new chain-error branches and both funding-service skip paths (thrown error and landed tx with bad code), plus a regression test confirming unrelated errors (e.g. 503) still rethrow so pg-boss retry is preserved. The PR explicitly scopes out a REST pre-check as a documented follow-up rather than over-engineering this fix, which is reasonable given it would only shrink (not close) the race window.

…height fetch

Remove the never-consumed userId field from FundDeploymentCommand and move
getCurrentHeight below the early-return guards so it is not fetched on the
not-indexed-yet or closed-deployment paths.

@claude claude 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.

LGTM — targeted, well-tested fix for a real production error.

What was reviewed: the new isDeploymentClosedError classification in both ChainErrorServices (api and tx-signer), the terminal-skip branches added to InitialDeploymentFundingService.fundOnLeaseStarted for both the thrown-error and landed-tx-with-nonzero-code paths, and the unrelated userId field removal from FundDeploymentCommand/handler (confirmed unused elsewhere). Verified unrelated errors (e.g. RPC 503) still rethrow so pg-boss retry is preserved for transient failures.

Extended reasoning...

Overview

This PR fixes error classification so that an "account closed" chain error (escrow account already closed by the time the async initial-funding job runs) is treated as a terminal, non-retryable 400 instead of an unmapped 500 that pg-boss retried 5 times against an account that can never accept the deposit. Changes touch ChainErrorService in both apps/api and apps/tx-signer (adding an account closed clue/pattern), a new public isDeploymentClosedError helper in the api's ChainErrorService, and two new terminal-skip branches in InitialDeploymentFundingService.fundOnLeaseStarted (one for the thrown simulation error, one for a landed tx with a non-zero code). It also carries an unrelated but small cleanup: dropping the now-unused userId field from FundDeploymentCommand/FundDeploymentHandler, and deferring the getCurrentHeight() RPC call until after the cheap closed-deployment check.

Security risks

None identified. This only changes error classification (500 → 400) and adds an early-return terminal-skip path; it does not touch auth, permissions, or any user-supplied input parsing beyond string matching on chain error messages.

Level of scrutiny

Moderate-light. This is a backend bug fix in billing/deployment funding code (not a config tweak), so it merits a real look, but the change is small, isolated to error-classification and one service method, follows an existing precedent in the codebase (isMasterWalletInsufficientFundsError), and is fully covered by new unit tests for every new branch (thrown error, landed-tx raw log, and the negative case of unrelated errors still rethrowing for retry).

Other factors

Coverage is 100% on all three touched service files per Codecov. The regex-based isDeploymentClosedError (/account closed|deployment closed/i) is applied consistently whether the error was pre-transformed by toAppError (message becomes "Deployment closed") or is a raw chain/rawLog string (contains "account closed"), which I verified by tracing both call sites. No CODEOWNERS or security-sensitive paths are touched, and CodeRabbit's automated review found no actionable comments.

@baktun14
baktun14 added this pull request to the merge queue Jul 28, 2026
Merged via the queue into main with commit 89cf8cc Jul 28, 2026
58 checks passed
@baktun14
baktun14 deleted the fix/deployment-skip-initial-funding-closed-escrow branch July 28, 2026 13:49
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