Skip to content

feat(backend): add ttl_warning field to service list/get responses#193

Open
leocagli wants to merge 1 commit into
Stellar-Ecosystem:mainfrom
leocagli:feat/service-ttl-warning
Open

feat(backend): add ttl_warning field to service list/get responses#193
leocagli wants to merge 1 commit into
Stellar-Ecosystem:mainfrom
leocagli:feat/service-ttl-warning

Conversation

@leocagli

@leocagli leocagli commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Closes #90

Summary

  • Added getCurrentLedgerSequence() to stellar.js via server.getLatestLedger()
  • Added MAX_TTL_LEDGERS = 3_110_400 and TTL_WARNING_THRESHOLD = 10% constants to contract.js (mirrors Rust contract constants)
  • Added hasTtlWarning(registeredAt, currentLedger) helper
  • Both listServices() and getService() now fetch the current ledger in parallel with the simulation call (zero added latency), then append ttl_warning: boolean to every ServiceEntry
  • Fallback: if ledger fetch fails, treats as 0 (no false positives)

Test plan

  • GET /api/services — each entry includes ttl_warning: false for freshly-registered services
  • GET /api/services/:id — same field present
  • Simulate near-expiry: set registered_at = current_ledger - MAX_TTL + 100, confirm ttl_warning: true
  • Ledger RPC failure: mock getLatestLedger() to throw; confirm endpoint still returns 200 with ttl_warning: false

Summary by CodeRabbit

  • New Features
    • Services now include a TTL warning indicator to alert when a service registration is nearing expiration, enabling better lifecycle management.

…esponses

Closes Stellar-Ecosystem#90

Soroban persistent entries with TTL near zero are archived and become
unreachable, but list_services returned no indication of remaining TTL —
providers had no way to know their listings were about to expire without
monitoring the ledger directly.

Changes:
- stellar.js: exported getCurrentLedgerSequence() using server.getLatestLedger()
- contract.js: added MAX_TTL_LEDGERS = 3_110_400 (mirrors Rust constant) and
  TTL_WARNING_THRESHOLD = 10% of MAX_TTL (~18 days); hasTtlWarning() helper
- listServices(): fetches current ledger in parallel with the RPC simulation
  call (no added latency) and appends ttl_warning: bool to each ServiceEntry
- getService(): same — parallel ledger fetch, ttl_warning in return object

If the ledger fetch fails, falls back to 0 (no false warnings). The field is
additive and backwards-compatible — clients that ignore it see no change.
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A new getCurrentLedgerSequence helper is added to stellar.js. In contract.js, TTL constants (MAX_TTL_LEDGERS, TTL_WARNING_THRESHOLD) and a hasTtlWarning predicate are introduced. Both listServices and getService now fetch the current ledger in parallel with their contract reads and include a ttl_warning boolean in each returned service object.

Changes

Soroban TTL expiry warning on service read APIs

Layer / File(s) Summary
Ledger sequence helper, TTL constants, and warning predicate
backend/src/lib/stellar.js, backend/src/lib/contract.js
Adds getCurrentLedgerSequence() to stellar.js via getLatestLedger(). Imports it in contract.js alongside MAX_TTL_LEDGERS and TTL_WARNING_THRESHOLD constants, and defines hasTtlWarning(registeredAt, currentLedger) to check if a service is within the warning window.
listServices and getService updated with ttl_warning
backend/src/lib/contract.js
Both read functions fetch the current ledger in parallel with their contract calls, fall back to 0 on ledger lookup failure, normalize registered_at to a number, and attach ttl_warning to every returned service object.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hop, hop — the ledger ticks away,
Each block a step toward archival day.
Now ttl_warning blinks a gentle light,
"Renew your entry before the night!"
The rabbit checks the sequence with care —
No service lost to the void out there. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a ttl_warning field to service responses.
Linked Issues check ✅ Passed The PR implementation addresses all coding requirements from issue #90: adds ttl_warning field to ServiceEntry responses, implements ledger sequence comparison logic for TTL expiration detection, and modifies listServices/getService to include the new field.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing ttl_warning functionality: new getCurrentLedgerSequence helper, TTL constants, warning logic, and response field addition align with issue #90 requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 1

🤖 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 `@backend/src/lib/contract.js`:
- Around line 139-142: The getCurrentLedgerSequence() call wrapped with
.catch(() => 0) silently suppresses RPC failures without logging, and provides
no protection against hanging calls that never reject, allowing Promise.all to
stall indefinitely. Add logging to the catch handler to record the error when it
occurs, and wrap the getCurrentLedgerSequence() call with a timeout mechanism to
protect against non-rejecting promises that hang. Apply this fix to both
occurrences of the pattern (around line 141 and line 176) to ensure both
endpoints are protected from stalling.
🪄 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: 18b9c26e-2636-474f-853d-7d11433fba17

📥 Commits

Reviewing files that changed from the base of the PR and between 6fb6da9 and e0132eb.

📒 Files selected for processing (2)
  • backend/src/lib/contract.js
  • backend/src/lib/stellar.js

Comment on lines +139 to +142
const [retval, currentLedger] = await Promise.all([
simulateRead(callOp),
getCurrentLedgerSequence().catch(() => 0),
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ledger fallback is silent and won’t trigger on hangs.

At Line 141 and Line 176, .catch(() => 0) suppresses RPC failures with no log, and it does not protect against a hanging ledger call (no rejection), so Promise.all can still stall both endpoints.

Suggested fix
 const TIMEOUT = 30;
+const LEDGER_FETCH_TIMEOUT_MS = TIMEOUT * 1000;
+
+async function getCurrentLedgerSequenceSafe(context) {
+  try {
+    return await Promise.race([
+      getCurrentLedgerSequence(),
+      new Promise((_, reject) =>
+        setTimeout(() => reject(new Error('getCurrentLedgerSequence timeout')), LEDGER_FETCH_TIMEOUT_MS),
+      ),
+    ]);
+  } catch (err) {
+    logger.warn({ err, context }, 'Ledger fetch failed; ttl_warning disabled for this response');
+    return 0;
+  }
+}
 ...
-    const [retval, currentLedger] = await Promise.all([
+    const [retval, currentLedger] = await Promise.all([
       simulateRead(callOp),
-      getCurrentLedgerSequence().catch(() => 0),
+      getCurrentLedgerSequenceSafe('listServices'),
     ]);
 ...
-    const [retval, currentLedger] = await Promise.all([
+    const [retval, currentLedger] = await Promise.all([
       simulateRead(op),
-      getCurrentLedgerSequence().catch(() => 0),
+      getCurrentLedgerSequenceSafe('getService'),
     ]);

Also applies to: 174-177

🤖 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 `@backend/src/lib/contract.js` around lines 139 - 142, The
getCurrentLedgerSequence() call wrapped with .catch(() => 0) silently suppresses
RPC failures without logging, and provides no protection against hanging calls
that never reject, allowing Promise.all to stall indefinitely. Add logging to
the catch handler to record the error when it occurs, and wrap the
getCurrentLedgerSequence() call with a timeout mechanism to protect against
non-rejecting promises that hang. Apply this fix to both occurrences of the
pattern (around line 141 and line 176) to ensure both endpoints are protected
from stalling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add MAX_TTL expiry warning to list_services for services approaching archival

1 participant