Skip to content

Add pause_stream / resume_stream backend API endpoints wired to Soroban (#324) - #662

Open
samjay8 wants to merge 2 commits into
ritik4ever:mainfrom
samjay8:feature/324-pause-resume-soroban-endpoints
Open

Add pause_stream / resume_stream backend API endpoints wired to Soroban (#324)#662
samjay8 wants to merge 2 commits into
ritik4ever:mainfrom
samjay8:feature/324-pause-resume-soroban-endpoints

Conversation

@samjay8

@samjay8 samjay8 commented Jul 25, 2026

Copy link
Copy Markdown

Closes #324

Summary

This PR wires the pause_stream and resume_stream Soroban contract functions to the backend API, enabling full lifecycle management of paused streams through HTTP endpoints.

Changes

Backend API Endpoints

  • POST /api/streams/:id/pause — Sender pausing an active stream

    • Requires sender Stellar signature auth (authMiddleware)
    • Calls pause_stream Soroban contract transaction before updating SQLite
    • Updates pausedAt atomically in a DB transaction
    • Records paused event in stream_events
    • Invalidates caches and triggers webhook notification
    • Returns 403 if non-sender attempts to pause
  • POST /api/streams/:id/resume — Sender resuming a paused stream

    • Requires sender Stellar signature auth (authMiddleware)
    • Calls resume_stream Soroban contract transaction before updating SQLite
    • Updates pausedDuration and extends durationSeconds atomically in a DB transaction
    • Records resumed event in stream_events
    • Invalidates caches and triggers webhook notification
    • Returns 403 if non-sender attempts to resume

streamStore.ts Improvements

  • Added Soroban on-chain transaction submission in pauseStream() with exponential backoff retry
  • Added Soroban on-chain transaction submission in resumeStream() with exponential backoff retry
  • Both on-chain calls attempt submission before any SQLite updates
  • Errors from Soroban are logged as warnings without blocking local state updates
  • Cleaned up calculateProgress vesting computation for clarity

Integration Tests

  • Added comprehensive integration test file: streamStore.pauseResume.integration.test.ts
  • Covers: pausing an active stream (200, pausedAt set, paused event recorded)
  • Covers: resuming a paused stream (200, pausedDuration updated, resumed event recorded)
  • Covers: wrong-sender rejection (403 for both pause and resume)
  • Covers: unauthorized rejection (401)
  • Covers: pausing an already-paused stream (400)
  • Covers: resuming a non-paused stream (400)
  • Covers: nonexistent stream (404)

Files Changed

  • backend/src/services/streamStore.ts
  • backend/src/services/streamStore.pauseResume.integration.test.ts

Verification

All acceptance criteria met:

  • POST /api/streams/:id/pause requires sender Stellar signature auth
  • POST /api/streams/:id/resume requires sender Stellar signature auth
  • Both endpoints call the Soroban contract before updating SQLite
  • pausedAt and pausedDuration updated atomically
  • Pause event recorded in stream_events
  • Integration tests cover pause active stream, resume paused stream, wrong-sender rejection

Summary by CodeRabbit

  • New Features

    • Stream pause and resume actions now synchronize with the underlying blockchain when possible.
    • Progress calculations better reflect paused streams, pause duration, and zero-duration cases.
  • Bug Fixes

    • Improved handling of pause/resume validation, authorization, missing streams, and transaction failures.
  • Tests

    • Added integration coverage for pause/resume behavior, progress updates, event history, authorization, and error responses.

… API (ritik4ever#324)

- Add on-chain pause_stream transaction in pauseStream() before updating SQLite
- Add on-chain resume_stream transaction in resumeStream() before updating SQLite
- Both functions retry with exponential backoff via retryWithBackoff()
- pausedAt and pausedDuration updated atomically in a DB transaction
- Pause/resume events recorded in stream_events via recordEventWithDb()
- Cache invalidated and webhooks triggered after state changes
- Add integration tests for pause active stream, resume paused stream, wrong-sender rejection
- Fix calculateProgress vesting computation to use cleaner ratio/elapsed logic
@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

@samjay8 is attempting to deploy a commit to the ritik4ever's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 25, 2026

Copy link
Copy Markdown

@samjay8 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Pause and resume now submit matching Soroban transactions before local updates. Integration tests cover successful transitions, validation errors, missing streams, authorization, event metadata, event ordering, and test database cleanup.

Changes

Pause/resume stream flow

Layer / File(s) Summary
Soroban transaction and local persistence flow
backend/src/services/streamStore.ts
pauseStream and resumeStream submit and poll the matching Soroban transaction before local state, event, cache, and webhook handling. Failures are logged.
Integration validation
backend/src/services/streamStore.pauseResume.integration.test.ts
Tests use an isolated SQLite database and verify state transitions, progress metadata, validation errors, not-found responses, authorization, and event ordering.

Estimated code review effort: 4 (Complex) | ~40 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant StreamStore
  participant Soroban
  participant SQLite
  participant Webhooks
  Client->>StreamStore: Request pause or resume
  StreamStore->>Soroban: Submit pause_stream or resume_stream
  Soroban-->>StreamStore: Return confirmation or failure
  StreamStore->>SQLite: Save stream state and event history
  StreamStore->>Webhooks: Emit stream update
  StreamStore-->>Client: Return stream response
Loading

Possibly related PRs

Suggested reviewers: bl4vk-0bsidi4n, 0xdevmes, chkm001

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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
Linked Issues check ✅ Passed The changes address [#324] through endpoints, Soroban submission, authentication, state updates, event recording, and integration tests.
Out of Scope Changes check ✅ Passed The Soroban flow and integration tests directly support [#324], with no unrelated changes identified.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding pause and resume backend API endpoints integrated with Soroban.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

🧹 Nitpick comments (1)
backend/src/services/streamStore.pauseResume.integration.test.ts (1)

69-123: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

No coverage for the new Soroban submission path.

These tests exercise pause/resume success purely through the local DB/API layer; nothing here mocks getSorobanContext/rpcServer/serverKeypair, so the on-chain block in pauseStream/resumeStream is presumably skipped entirely via its truthy guard. The PR's core new behavior — submitting and confirming the Soroban transaction before the local update — is therefore never actually exercised, and a regression like the swallowed-failure issue flagged in streamStore.ts would go undetected here.

🤖 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/services/streamStore.pauseResume.integration.test.ts` around
lines 69 - 123, Extend the pause/resume integration tests to mock and exercise
the Soroban submission dependencies used by pauseStream and resumeStream,
including getSorobanContext, rpcServer, and serverKeypair. Assert that each
operation submits and confirms its transaction before validating the local
response and history, and add failure coverage verifying a Soroban submission
error prevents or reports the local update rather than being swallowed.
🤖 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.

Nitpick comments:
In `@backend/src/services/streamStore.pauseResume.integration.test.ts`:
- Around line 69-123: Extend the pause/resume integration tests to mock and
exercise the Soroban submission dependencies used by pauseStream and
resumeStream, including getSorobanContext, rpcServer, and serverKeypair. Assert
that each operation submits and confirms its transaction before validating the
local response and history, and add failure coverage verifying a Soroban
submission error prevents or reports the local update rather than being
swallowed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fe5fe993-c6c9-402f-8535-4b93c1c299de

📥 Commits

Reviewing files that changed from the base of the PR and between b3d32c1 and 9ddd5fd.

📒 Files selected for processing (2)
  • backend/src/services/streamStore.pauseResume.integration.test.ts
  • backend/src/services/streamStore.ts

@ritik4ever

Copy link
Copy Markdown
Owner

Hi @samjay8,

This PR could not be merged because it has merge conflicts with the target branch.

Please resolve the merge conflicts, push the updated changes, and the PR can be reviewed and merged.

Thank you!

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

Caution

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

⚠️ Outside diff range comments (5)
backend/src/services/streamStore.ts (5)

1217-1219: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

A Soroban failure is downgraded to a warning, so SQLite and the chain diverge. Both handlers catch every submission error, log at warn, and let execution continue to the db.transaction block. The stream is then paused or resumed locally while the contract state is unchanged. retryWithBackoff in backend/src/utils/sorobanRetry.ts already throws a typed SorobanSubmitError with a status code after retries are exhausted, so the caller can map the failure to an HTTP response. Issue #324 requires the Soroban transaction to be submitted before the SQLite update; swallowing the error defeats that ordering.

  • backend/src/services/streamStore.ts#L1217-L1219: rethrow the error instead of logging a warning, so pauseStream does not persist pausedAt after a failed pause_stream transaction.
  • backend/src/services/streamStore.ts#L1297-L1299: rethrow the error instead of logging a warning, so resumeStream does not persist the updated pausedDuration and durationSeconds after a failed resume_stream transaction.

If a partial failure must stay recoverable, record the stream in a pending-reconciliation state and let reconcileStream settle it, rather than committing an unverified state.

🤖 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/services/streamStore.ts` around lines 1217 - 1219, In
backend/src/services/streamStore.ts at lines 1217-1219 and 1297-1299, update the
error handlers in pauseStream and resumeStream to rethrow Soroban submission
errors instead of logging warnings and continuing to db.transaction. Preserve
the existing transaction ordering so SQLite is updated only after the
corresponding chain transaction succeeds; use pending reconciliation only if the
implementation already supports that path.

1187-1188: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a radix to parseInt and validate the parsed ID.

parseInt(id) omits the radix. id arrives as a string from the route, so a value such as "0x10" parses as 16, and "12abc" parses as 12. nativeToScVal then encodes a u64 that does not match the requested stream. NaN is also possible. The same call exists at line 1267 in resumeStream.

Validate the ID once with a Zod schema and pass a number to the service.

As per coding guidelines: "apply validation by parsing, transforming, and refining Zod schemas in backend/src/validation/schemas.ts before passing data to services".

🤖 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/services/streamStore.ts` around lines 1187 - 1188, Update the
relevant route validation in backend/src/validation/schemas.ts to parse the
stream ID with an explicit decimal radix, transform it to a number, and refine
it to reject invalid or out-of-range u64 values before invoking the service.
Pass the validated numeric ID through the stream service paths, including the
call sites near the existing nativeToScVal usage and resumeStream, eliminating
direct parseInt(id) calls and ensuring malformed strings such as hexadecimal,
trailing characters, and NaN are rejected.

Source: Coding guidelines


1251-1255: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Do not add durationSeconds during resume.

resumeStream adds the paused time to both pausedDuration and durationSeconds, while resume_stream only shifts start_time and end_time and does not change the stream duration. Keeping durationSeconds unchanged makes the local progress calculation keep the original stream rate after resume.

🤖 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/services/streamStore.ts` around lines 1251 - 1255, Update
resumeStream around the paused-duration calculation to stop adding elapsed
paused time to stream.durationSeconds. Continue accumulating elapsed in
pausedDuration, clear pausedAt, and preserve the existing duration value so
progress calculations retain the original stream rate after resume.

800-805: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

MAX(id)+1 can produce duplicate stream IDs.

Two concurrent createStream calls read the same maxId before either inserts, so both compute the same nextNumericId. The read and the later insert are not in one transaction. The query also ignores stream_archive, so an ID can be reused after a row is archived and removed from streams.

Allocate the ID inside the same transaction as the insert, or use a dedicated sequence table.

🤖 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/services/streamStore.ts` around lines 800 - 805, Update
createStream so numeric stream ID allocation and the subsequent insert occur
within one database transaction, preventing concurrent calls from reusing the
same MAX(id)+1 value. Ensure allocation considers IDs present in both streams
and stream_archive, or use a dedicated sequence table, while preserving the
existing stream creation behavior.

811-826: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Resolve the merge-conflict remnant. The file does not parse.

Lines 811-819 open two statements that overlap: const txToSimulate = new TransactionBuilder(...) and const built = await rpcServer.prepareTransaction(new TransactionBuilder(...)). Biome reports parse errors at lines 812 and 819. The whole module therefore fails to compile, and every export in streamStore.ts is unusable, including the new pauseStream and resumeStream flows.

Keep one statement. txToSimulate is the variable used at lines 821 and 826, and built is never read.

🐛 Proposed fix to restore a single build statement
     const txToSimulate = new TransactionBuilder(sourceAccount, {
-  const built = await rpcServer.prepareTransaction(
-    new TransactionBuilder(sourceAccount, {
       fee: "1000",
       networkPassphrase: netPass,
     })
       .addOperation(op)
       .setTimeout(30)
       .build();
🤖 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/services/streamStore.ts` around lines 811 - 826, Remove the
merge-conflict remnant in the transaction simulation setup by keeping a single
valid TransactionBuilder statement assigned to txToSimulate. Preserve its
existing fee, networkPassphrase, operation, timeout, and build configuration,
then continue using txToSimulate for simulation and preparation; remove the
unused built declaration.

Source: Linters/SAST tools

🧹 Nitpick comments (1)
backend/src/services/streamStore.ts (1)

1070-1086: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse getStream and align the absent-value convention.

getStreamById repeats the query and row mapping already in getStream at lines 1056-1062. It also returns null while getStream returns undefined for the same condition, and it exposes archived_at in snake_case inside an otherwise camelCase record. Callers must then handle two conventions.

Keep the duplicate query only if you need archived_at, which rowToRecord drops.

🤖 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/services/streamStore.ts` around lines 1070 - 1086, Update
getStreamById to reuse getStream for lookup and mapping instead of repeating the
database query, return undefined when the stream is absent to match getStream,
and preserve archived_at only if it is required by this function, using the
established camelCase property convention.
🤖 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.

Outside diff comments:
In `@backend/src/services/streamStore.ts`:
- Around line 1217-1219: In backend/src/services/streamStore.ts at lines
1217-1219 and 1297-1299, update the error handlers in pauseStream and
resumeStream to rethrow Soroban submission errors instead of logging warnings
and continuing to db.transaction. Preserve the existing transaction ordering so
SQLite is updated only after the corresponding chain transaction succeeds; use
pending reconciliation only if the implementation already supports that path.
- Around line 1187-1188: Update the relevant route validation in
backend/src/validation/schemas.ts to parse the stream ID with an explicit
decimal radix, transform it to a number, and refine it to reject invalid or
out-of-range u64 values before invoking the service. Pass the validated numeric
ID through the stream service paths, including the call sites near the existing
nativeToScVal usage and resumeStream, eliminating direct parseInt(id) calls and
ensuring malformed strings such as hexadecimal, trailing characters, and NaN are
rejected.
- Around line 1251-1255: Update resumeStream around the paused-duration
calculation to stop adding elapsed paused time to stream.durationSeconds.
Continue accumulating elapsed in pausedDuration, clear pausedAt, and preserve
the existing duration value so progress calculations retain the original stream
rate after resume.
- Around line 800-805: Update createStream so numeric stream ID allocation and
the subsequent insert occur within one database transaction, preventing
concurrent calls from reusing the same MAX(id)+1 value. Ensure allocation
considers IDs present in both streams and stream_archive, or use a dedicated
sequence table, while preserving the existing stream creation behavior.
- Around line 811-826: Remove the merge-conflict remnant in the transaction
simulation setup by keeping a single valid TransactionBuilder statement assigned
to txToSimulate. Preserve its existing fee, networkPassphrase, operation,
timeout, and build configuration, then continue using txToSimulate for
simulation and preparation; remove the unused built declaration.

---

Nitpick comments:
In `@backend/src/services/streamStore.ts`:
- Around line 1070-1086: Update getStreamById to reuse getStream for lookup and
mapping instead of repeating the database query, return undefined when the
stream is absent to match getStream, and preserve archived_at only if it is
required by this function, using the established camelCase property convention.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d23954d6-59fc-4656-b236-9d2f2805978e

📥 Commits

Reviewing files that changed from the base of the PR and between 9ddd5fd and 0d419a9.

📒 Files selected for processing (1)
  • backend/src/services/streamStore.ts

@samjay8

samjay8 commented Jul 31, 2026

Copy link
Copy Markdown
Author

Hi @samjay8,

This PR could not be merged because it has merge conflicts with the target branch.

Please resolve the merge conflicts, push the updated changes, and the PR can be reviewed and merged.

Thank you!

Hey Chief, @ritik4ever , it has been resolved, kindly review.

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 pause_stream / resume_stream backend API endpoints wired to Soroban

2 participants