Skip to content

Expand websocket API to support JSON-RPC 0.9.0#830

Merged
FabijanC merged 25 commits intomainfrom
rpc-0.9.0
Aug 21, 2025
Merged

Expand websocket API to support JSON-RPC 0.9.0#830
FabijanC merged 25 commits intomainfrom
rpc-0.9.0

Conversation

@FabijanC
Copy link
Copy Markdown
Contributor

@FabijanC FabijanC commented Aug 19, 2025

Usage related changes

  • Expand ws API to support JSON-RPC 0.9.0
    • Remove subscription to pending txs
    • Add subscription to new txs
    • Add subscription to new tx receipts
  • Reorg notification sent to all subscribers, including new tx and new tx receipt
  • CANDIDATE and RECEIVED tx statuses still not used.

Development related changes

  • Reduce lockedness of api.starknet

Checklist:

  • Checked out the contribution guidelines
  • Applied formatting - ./scripts/format.sh
  • No linter errors - ./scripts/clippy_check.sh
  • No unused dependencies - ./scripts/check_unused_deps.sh
  • No spelling errors - ./scripts/check_spelling.sh
  • Performed code self-review
  • Rebased to the latest commit of the target branch (or merged it into my branch)
    • Once you make the PR reviewable, please avoid force-pushing
  • Updated the docs if needed - ./website/README.md
  • Linked the issues resolvable by this PR - linking info
  • Updated the tests if needed; all passing - execution info

Summary by CodeRabbit

  • New Features
    • Finality-aware event filtering and notifications; events now include finality_status.
    • New WebSocket subscriptions: subscribe to NewTransactions and NewTransactionReceipts with address and finality filters.
  • Refactor
    • Reworked WebSocket flow and internal locking patterns; replaced pending-transaction pathways.
  • Documentation
    • Updated finality semantics; clarified PRE_CONFIRMED behavior and L1 acceptance.
  • Tests
    • Added comprehensive suites for new transaction, receipt, and event subscriptions; removed legacy pending-tx tests; expanded reorg coverage.
  • Chores
    • Bumped advertised JSON-RPC spec version to 0.9.0.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 19, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds finality-awareness across events and subscriptions. Core: get_events gains finality_status_filter and applies runtime filtering by TransactionFinalityStatus. Starknet API propagates this parameter. WebSocket JSON-RPC replaces PendingTransactions with NewTransactions and NewTransactionReceipts, introduces new subscription input types, and wraps emitted events in SubscriptionEmittedEvent carrying finality_status. Broadcasting paths and handlers updated to emit finality-tagged notifications for pre-confirmed and latest blocks. Types: TransactionFinalityStatus extended and made orderable; helpers added for sender address and receipt finality. RPC spec version bumped to 0.9.0. Postman endpoint lock handling adjusted. Extensive integration tests updated/added; pending-txs tests removed.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120–180 minutes

Possibly related PRs

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rpc-0.9.0

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@FabijanC FabijanC marked this pull request as ready for review August 20, 2025 14:32
@FabijanC
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 20, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
crates/starknet-devnet-server/src/api/json_rpc/mod.rs (1)

259-321: Avoid holding the Starknet lock across awaits; also handle N>1 new pre-confirmed txs.

Two issues:

  • The starknet mutex guard remains held until after notify_subscribers(...).await. Holding locks across .await hurts concurrency and can cause priority inversions.
  • If multiple txs enter the pre-confirmed block in one mutation, only the last is broadcast.

Address both by iterating the delta slice and dropping the lock before notifying:

 async fn broadcast_pre_confirmed_tx_changes(
     &self,
     old_pre_confirmed_block: StarknetBlock,
 ) -> Result<(), error::ApiError> {
     let new_pre_confirmed_block = self.get_block_by_tag(BlockTag::PreConfirmed).await;
     let old_pre_confirmed_txs = old_pre_confirmed_block.get_transactions();
     let new_pre_confirmed_txs = new_pre_confirmed_block.get_transactions();

-    if new_pre_confirmed_txs.len() > old_pre_confirmed_txs.len() {
-        #[allow(clippy::expect_used)]
-        let new_tx_hash = new_pre_confirmed_txs.last().expect("has at least one element");
-
-        let mut notifications = vec![];
-        let starknet = self.api.starknet.lock().await;
+    if new_pre_confirmed_txs.len() > old_pre_confirmed_txs.len() {
+        let mut notifications = vec![];
+        let old_len = old_pre_confirmed_txs.len();
+        let starknet = self.api.starknet.lock().await;

-        let status = starknet
-            .get_transaction_execution_and_finality_status(*new_tx_hash)
-            .map_err(error::ApiError::StarknetDevnetError)?;
-        notifications.push(NotificationData::TransactionStatus(NewTransactionStatus {
-            transaction_hash: *new_tx_hash,
-            status,
-        }));
-
-        let tx = starknet
-            .get_transaction_by_hash(*new_tx_hash)
-            .map_err(error::ApiError::StarknetDevnetError)?;
-        notifications.push(NotificationData::NewTransaction(NewTransactionNotification {
-            tx: tx.clone(),
-            finality_status: TransactionFinalityStatus::PreConfirmed,
-        }));
-
-        let receipt = starknet
-            .get_transaction_receipt_by_hash(new_tx_hash)
-            .map_err(error::ApiError::StarknetDevnetError)?;
-        notifications.push(NotificationData::NewTransactionReceipt(
-            NewTransactionReceiptNotification {
-                tx_receipt: receipt,
-                sender_address: tx.get_sender_address(),
-            },
-        ));
-
-        let events = starknet.get_unlimited_events(
-            Some(BlockId::Tag(BlockTag::PreConfirmed)),
-            Some(BlockId::Tag(BlockTag::PreConfirmed)),
-            None,
-            None,
-            None, // pre-confirmed block only has pre-confirmed txs
-        )?;
-        for emitted_event in events.into_iter().filter(|e| &e.transaction_hash == new_tx_hash) {
-            let subscription_event = SubscriptionEmittedEvent {
-                emitted_event,
-                // pre-confirmed block only has pre-confirmed txs
-                finality_status: TransactionFinalityStatus::PreConfirmed,
-            };
-            notifications.push(NotificationData::Event(subscription_event));
-        }
-
-        self.api.sockets.lock().await.notify_subscribers(&notifications).await;
+        for new_tx_hash in &new_pre_confirmed_txs[old_len..] {
+            let status = starknet
+                .get_transaction_execution_and_finality_status(*new_tx_hash)
+                .map_err(error::ApiError::StarknetDevnetError)?;
+            notifications.push(NotificationData::TransactionStatus(NewTransactionStatus {
+                transaction_hash: *new_tx_hash,
+                status,
+            }));
+
+            let tx = starknet
+                .get_transaction_by_hash(*new_tx_hash)
+                .map_err(error::ApiError::StarknetDevnetError)?;
+            notifications.push(NotificationData::NewTransaction(NewTransactionNotification {
+                tx: tx.clone(),
+                finality_status: TransactionFinalityStatus::PreConfirmed,
+            }));
+
+            let receipt = starknet
+                .get_transaction_receipt_by_hash(new_tx_hash)
+                .map_err(error::ApiError::StarknetDevnetError)?;
+            notifications.push(NotificationData::NewTransactionReceipt(
+                NewTransactionReceiptNotification {
+                    tx_receipt: receipt,
+                    sender_address: tx.get_sender_address(),
+                },
+            ));
+
+            let events = starknet.get_unlimited_events(
+                Some(BlockId::Tag(BlockTag::PreConfirmed)),
+                Some(BlockId::Tag(BlockTag::PreConfirmed)),
+                None,
+                None,
+                None, // pre-confirmed block only has pre-confirmed txs
+            )?;
+            for emitted_event in events.into_iter().filter(|e| &e.transaction_hash == new_tx_hash)
+            {
+                notifications.push(NotificationData::Event(SubscriptionEmittedEvent {
+                    emitted_event,
+                    finality_status: TransactionFinalityStatus::PreConfirmed,
+                }));
+            }
+        }
+        drop(starknet); // release before awaiting I/O
+        self.api.sockets.lock().await.notify_subscribers(&notifications).await;
     }

     Ok(())
 }
🧹 Nitpick comments (21)
crates/starknet-devnet-server/src/api/http/endpoints/postman.rs (2)

75-81: Make lock release explicit; clarify the comment to reference the lock guard.

The chained call holds the Starknet lock until fetch_messages_to_l2().await completes (which is fine), and then drops it before executing RPCs. To make this intent unambiguous and resilient to future edits, consider scoping the guard explicitly and rewording the comment to reference the lock guard rather than api.starknet.

Apply this diff to scope the lock and clarify the comment:

-        // It is important that api.starknet is dropped immediately to allow rpc execution
-        messages_to_l2 = api.starknet.lock().await.fetch_messages_to_l2().await.map_err(|e| {
-            ApiError::RpcError(RpcError::internal_error_with(format!(
-                "Error in fetching messages to L2: {e}"
-            )))
-        })?;
+        // Important: acquire a short-lived Starknet lock only to fetch messages;
+        // drop it before executing JSON-RPC calls to avoid deadlocks.
+        {
+            let mut starknet = api.starknet.lock().await;
+            messages_to_l2 = starknet.fetch_messages_to_l2().await.map_err(|e| {
+                ApiError::RpcError(RpcError::internal_error_with(format!(
+                    "Error in fetching messages to L2: {e}"
+                )))
+            })?;
+        } // lock dropped here

95-111: Tighten Starknet lock around L1 calls & confirm dry-run side effects

  • collect_messages_to_l1 mutates internal state (it extends l2_to_l1_messages_to_flush and advances last_local_block). In dry-run mode this will enqueue messages and bump the block pointer—please confirm this behavior is intended.
  • Reduce the scope of api.starknet.lock().await to only each async operation (collect, send, get_ethereum_url) to minimize mutex contention around potentially slow network calls.

Suggested refactor:

-    let mut starknet = api.starknet.lock().await;
-    let messages_to_l1 = starknet.collect_messages_to_l1().await.map_err(|e| {
-        ApiError::RpcError(RpcError::internal_error_with(format!(
-            "Error in collecting messages to L1: {e}"
-        )))
-    })?;
-
-    let l1_provider = if is_dry_run {
-        "dry run".to_string()
-    } else {
-        starknet.send_messages_to_l1().await.map_err(|e| {
-            ApiError::RpcError(RpcError::internal_error_with(format!(
-                "Error in sending messages to L1: {e}"
-            )))
-        })?;
-        starknet.get_ethereum_url().unwrap_or("Not set".to_string())
-    };
+    // Lock only for collecting messages
+    let messages_to_l1 = {
+        let mut starknet = api.starknet.lock().await;
+        starknet.collect_messages_to_l1().await.map_err(|e| {
+            ApiError::RpcError(RpcError::internal_error_with(format!(
+                "Error in collecting messages to L1: {e}"
+            )))
+        })?
+    };
+
+    let l1_provider = if is_dry_run {
+        "dry run".to_string()
+    } else {
+        // Lock only for sending
+        {
+            let mut starknet = api.starknet.lock().await;
+            starknet.send_messages_to_l1().await.map_err(|e| {
+                ApiError::RpcError(RpcError::internal_error_with(format!(
+                    "Error in sending messages to L1: {e}"
+                )))
+            })?;
+        }
+        // Short lock just to read the URL
+        api.starknet
+            .lock()
+            .await
+            .get_ethereum_url()
+            .unwrap_or_else(|| "Not set".to_string())
+    };
website/docs/intro.md (2)

18-25: Tighten phrasing and punctuation in the new finality semantics bullets.

Minor grammar/punctuation issues. Suggest:

  • End the last bullet with a period, not a comma.

Apply this minimal fix:

-  - Transactions are never automatically `ACCEPTED_ON_L1`, unless the user performs an action,
+  - Transactions are never automatically `ACCEPTED_ON_L1` unless the user performs an action.

14-17: Typos: “transactions” singular and “free” → “fee”.

Two small nits above the changed block for clarity:

- - By default, a new block is mined for each new transactions.
+ - By default, a new block is mined for each new transaction.
   - This can be modified by directing all new transactions into a pre-confirmed block, and at some point triggering block creation.
-  - Transactions in a pre-confirmed block cannot be replaced by sending a transaction with a higher free from the same account.
+  - Transactions in a pre-confirmed block cannot be replaced by sending a transaction with a higher fee from the same account.
crates/starknet-devnet-types/src/rpc/transaction_receipt.rs (1)

39-49: Consider returning the finality status by value to avoid tying callers to a borrow

If TransactionFinalityStatus is Clone (likely) or Copy, returning by value improves ergonomics and avoids exposing an internal borrow.

Apply this diff if acceptable:

-impl TransactionReceipt {
-    pub fn finality_status(&self) -> &TransactionFinalityStatus {
+impl TransactionReceipt {
+    pub fn finality_status(&self) -> TransactionFinalityStatus {
         let common = match self {
             TransactionReceipt::Deploy(receipt) => &receipt.common,
             TransactionReceipt::L1Handler(receipt) => &receipt.common,
             TransactionReceipt::Common(common) => common,
         };
 
-        &common.finality_status
+        common.finality_status.clone()
     }
 }
crates/starknet-devnet-core/src/starknet/mod.rs (1)

1201-1222: get_events now supports finality filtering — consider an options struct to reduce arg count

Functionally correct. To avoid growing argument lists and the need for allow(too_many_arguments), consider introducing a GetEventsOptions struct to carry address/keys/finality/skip/limit in a single parameter.

crates/starknet-devnet-types/src/rpc/transactions.rs (2)

105-114: Clarify semantics of get_sender_address across variants (especially Deploy/DeployAccount).

The mapping looks correct w.r.t. how we surface “sender”:

  • Declare/Invoke: actual sender_address
  • DeployAccount: derived contract address
  • L1Handler: contract handling the L1 message
  • Deploy: None

To avoid ambiguity for downstream users, please document this contract explicitly at the API point.

Apply this inline doc to make the intent unambiguous:

 impl Transaction {
+    /// Returns the logical initiator address for the transaction:
+    /// - DECLARE / INVOKE: the `sender_address`
+    /// - DEPLOY_ACCOUNT: the derived `contract_address` of the deployed account
+    /// - L1_HANDLER: the `contract_address` handling the message
+    /// - DEPLOY: `None` (no L2 sender)
     pub fn gas_vector_computation_mode(&self) -> GasVectorComputationMode {

1024-1032: Be explicit about ordering semantics for TransactionFinalityStatus and update affected documentation

• In crates/starknet-devnet-types/src/rpc/transactions.rs, add a doc comment above the enum to spell out the ascending progression and guard against variant reordering.
• (Optional) Add a unit test that asserts the expected ordering, e.g.:

 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Copy)]
 #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+/// Finality progresses in ascending order as declared below; do not reorder
+/// without adjusting comparisons relying on `PartialOrd`:
+/// RECEIVED < CANDIDATE < PRE_CONFIRMED < ACCEPTED_ON_L2 < ACCEPTED_ON_L1
 pub enum TransactionFinalityStatus {
     Received,
     Candidate,
     PreConfirmed,
     AcceptedOnL2,
     AcceptedOnL1,
 }

• Update the docs in website/docs/intro.md (around line 21) to remove the legacy “PENDING” reference and simply describe the new status:

-  - `PRE_CONFIRMED` behaves like the old `PENDING` status - it is the status assigned to the transactions in a pre-confirmed block, which is a precursor to a stable latest block.
+  - `PRE_CONFIRMED` is the status assigned to transactions in a pre-confirmed block, which is a precursor to a stable latest block.

• You can re-run the provided script to ensure no other “Pending…” references remain:

rg -n -C2 -g '!**/target/**' -P '\bPending(Transaction|Transactions)\b|PENDING(_ON_\w+)?'
tests/integration/test_subscription_to_new_txs.rs (1)

140-175: Sender-address filtering coverage looks solid; consider also covering Deploy (None) sender.

You cover Declare and Invoke via a predeployed account, which is great. Since Transaction::get_sender_address() returns None for plain Deploy, consider adding a negative test asserting that a sender_address filter does not match a Deploy tx, to lock in semantics.

I can add a small test that deploys via UDC (or any Deploy path) and asserts no notifications when filtering by a concrete sender address. Want me to draft it?

crates/starknet-devnet-core/src/starknet/events.rs (2)

11-22: Update function docs to include finality_status_filter.

The signature gained finality_status_filter, but the docstring doesn’t mention it. Please include it and clarify semantics (“returns events whose transaction finality >= filter”).

 /// * `keys_filter` - Optional. The keys to filter the events by.
-/// * `skip` - The number of elements to skip.
-/// * `limit` - Optional. The maximum number of elements to return.
+/// * `finality_status_filter` - Optional. Minimum transaction finality required for events to be included.
+///   Events from txs with finality strictly lower than the filter are skipped.
+/// * `skip` - The number of elements to skip (applied after filtering).
+/// * `limit` - Optional. The maximum number of elements to return.
 #[allow(clippy::too_many_arguments)]
 pub(crate) fn get_events(

44-49: Finality filter logic aligns with derived ordering.

if transaction.finality_status < finality_status_filter { continue } correctly implements “minimum finality” semantics when combined with the new enum ordering.

Two nits:

  • Minor shadowing: consider renaming the inner finality_status_filter binding to avoid shadowing the arg.
  • Add a unit test covering this filter (e.g., PreConfirmed filter excludes events from a Received tx but includes AcceptedOnL2/L1).

I can add a focused unit test around get_events to assert the filter behavior using a small in-memory setup. Want me to include it?

tests/integration/test_subscription_to_events.rs (2)

206-249: Pre-confirmed-only subscription behavior is pinned well.

Asserting no re-notify after pre_confirmed -> latest confirms dedup semantics for the same event across finalities. Good coverage.

Two small suggestions:

  • Consider one extra assertion that block_hash/block_number are absent in the pre-confirmed notification (you already implicitly do by not including them in the expected JSON).
  • Add a comment clarifying that finality_status in payload reflects the tx’s current finality at emission time.

251-327: Avoid brittle assumption on block_number; use runtime value.

Hardcoding "block_number": 1 may become flaky if the devnet boots with a different genesis/state in future. Capture the latest block after create_block() and assert that number instead.

Apply this refactor:

-    let created_block_hash = devnet.create_block().await.unwrap();
+    let created_block_hash = devnet.create_block().await.unwrap();
+    let created_block = devnet.get_latest_block_with_tx_hashes().await.unwrap();
...
-            json!({
-                "block_hash": created_block_hash,
-                "block_number": 1, // the only created block
+            json!({
+                "block_hash": created_block_hash,
+                "block_number": created_block.block_number,
                 "transaction_hash": invocation.transaction_hash,
                 "from_address": contract_address,
                 "keys": [static_event_key()],
                 "data": [],
                 "finality_status": finality_status,
             })
tests/integration/test_subscription_to_new_tx_receipts.rs (1)

167-189: Sender filter on receipts: solid; consider adding explicit Received/Candidate subscription case.

Given the PR note that subscribing with CANDIDATE or RECEIVED maps to PRE_CONFIRMED, consider a minimal extra test where finality_status: [RECEIVED] still yields a PreConfirmed receipt notification. That will lock in the “promotion” logic.

If helpful, I can draft a small test mirroring this one but with finality_status: [TransactionFinalityStatus::Received] and assert the received receipt has finality_status: PreConfirmed.

tests/integration/test_subscription_to_tx_status.rs (1)

197-211: Minor ergonomics nit: avoid moving sockets if you plan to reuse them.

Using for (mut ws, subscription_id) in [(ws_before_tx, id_before), (ws_after_tx, id_after)] moves both sockets. It’s fine here since you don’t reuse them later in this test, but in similar patterns consider iterating over mutable references to avoid accidental moves if future assertions are added.

Apply this localized refactor for future-proofing:

-    for (mut ws, subscription_id) in
-        [(ws_before_tx, subscription_id_before), (ws_after_tx, subscription_id_after)]
-    {
-        let notification = receive_rpc_via_ws(&mut ws).await.unwrap();
+    for (ws, subscription_id) in
+        [&mut ws_before_tx, &mut ws_after_tx].into_iter().zip([subscription_id_before, subscription_id_after])
+    {
+        let notification = receive_rpc_via_ws(ws).await.unwrap();
-        assert_no_notifications(&mut ws).await;
+        assert_no_notifications(ws).await;
     }
crates/starknet-devnet-server/src/api/json_rpc/models.rs (2)

14-15: Coupling to subscribe.rs types is acceptable, but keep layering in mind.

Importing TransactionFinalityStatusWithoutL1 and TransactionStatusWithoutL1 from crate::subscribe keeps enums single-sourced but ties models to the subscription layer. If these inputs are reused outside WS, consider moving the enums to a neutral module (e.g., types or models) to avoid circular concerns later.


251-256: EventsSubscriptionInput: consider documenting finality semantics.

You accept a single finality_status for events. Downstream code uses it to both filter and determine the historical scan window. A short doc comment here would prevent misuse and clarify that “no value” defaults to L2-accepted.

crates/starknet-devnet-server/src/api/json_rpc/mod.rs (1)

411-420: Prefer stable error message without “should be defined.”

The internal error string is fine for now, but consider a more actionable message (e.g., “No aborted block recorded in state”). Not critical.

crates/starknet-devnet-server/src/api/json_rpc/endpoints_ws.rs (2)

176-226: Transactions subscription: inputs OK; consider semantics for RECEIVED/CANDIDATE.

You accept TransactionStatusWithoutL1 (including RECEIVED/CANDIDATE), but the server only emits PRE_CONFIRMED and ACCEPTED_ON_L2 notifications for transactions. As-is, filters containing only RECEIVED/CANDIDATE will never match. If that’s by design, document it; otherwise, map RECEIVED/CANDIDATE to PRE_CONFIRMED similarly to receipts.

Would you like a small patch that normalizes RECEIVED|CANDIDATE to PRE_CONFIRMED for NewTransactions subscriptions?


229-281: Receipts subscription: good filter construction; same lock-holding caveat.

The filter/scan logic is sound. However, as in other subs, you hold the sockets mutex while sending the initial backlog. Consider reducing lock scope to the subscribe step and perform notifications without holding the collection lock.

crates/starknet-devnet-server/src/subscribe.rs (1)

395-401: Notification payload for receipts drops sender_address.

You include sender_address in NewTransactionReceiptNotification for filtering, but the serialized notification sends only the TransactionReceipt. If the spec later requires including sender for receipts, you’ll need to adjust SubscriptionNotification::NewTransactionReceipt accordingly. For now, this is fine and intentional.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4798b6d and 225e22a.

⛔ Files ignored due to path filters (6)
  • crates/starknet-devnet-server/test_data/spec/0.9.0/edit_spec_instructions.yaml is excluded by !crates/starknet-devnet-server/test_data/spec/**
  • crates/starknet-devnet-server/test_data/spec/0.9.0/starknet_api_openrpc.json is excluded by !crates/starknet-devnet-server/test_data/spec/**
  • crates/starknet-devnet-server/test_data/spec/0.9.0/starknet_executables.json is excluded by !crates/starknet-devnet-server/test_data/spec/**
  • crates/starknet-devnet-server/test_data/spec/0.9.0/starknet_trace_api_openrpc.json is excluded by !crates/starknet-devnet-server/test_data/spec/**
  • crates/starknet-devnet-server/test_data/spec/0.9.0/starknet_write_api.json is excluded by !crates/starknet-devnet-server/test_data/spec/**
  • crates/starknet-devnet-server/test_data/spec/0.9.0/starknet_ws_api.json is excluded by !crates/starknet-devnet-server/test_data/spec/**
📒 Files selected for processing (22)
  • crates/starknet-devnet-core/src/starknet/events.rs (8 hunks)
  • crates/starknet-devnet-core/src/starknet/mod.rs (1 hunks)
  • crates/starknet-devnet-server/src/api/http/endpoints/postman.rs (2 hunks)
  • crates/starknet-devnet-server/src/api/json_rpc/endpoints.rs (5 hunks)
  • crates/starknet-devnet-server/src/api/json_rpc/endpoints_ws.rs (5 hunks)
  • crates/starknet-devnet-server/src/api/json_rpc/mod.rs (9 hunks)
  • crates/starknet-devnet-server/src/api/json_rpc/models.rs (2 hunks)
  • crates/starknet-devnet-server/src/subscribe.rs (6 hunks)
  • crates/starknet-devnet-types/src/rpc/block.rs (2 hunks)
  • crates/starknet-devnet-types/src/rpc/emitted_event.rs (2 hunks)
  • crates/starknet-devnet-types/src/rpc/transaction_receipt.rs (1 hunks)
  • crates/starknet-devnet-types/src/rpc/transactions.rs (3 hunks)
  • tests/integration/general_rpc_tests.rs (1 hunks)
  • tests/integration/lib.rs (1 hunks)
  • tests/integration/test_subscription_to_events.rs (12 hunks)
  • tests/integration/test_subscription_to_new_tx_receipts.rs (1 hunks)
  • tests/integration/test_subscription_to_new_txs.rs (1 hunks)
  • tests/integration/test_subscription_to_pending_txs.rs (0 hunks)
  • tests/integration/test_subscription_to_reorg.rs (2 hunks)
  • tests/integration/test_subscription_to_tx_status.rs (2 hunks)
  • tests/integration/test_subscription_with_invalid_block_id.rs (3 hunks)
  • website/docs/intro.md (1 hunks)
💤 Files with no reviewable changes (1)
  • tests/integration/test_subscription_to_pending_txs.rs
🧰 Additional context used
🪛 LanguageTool
website/docs/intro.md

[grammar] ~21-~21: There might be a mistake here.
Context: ...is a precursor to a stable latest block. - Transactions are never automatically `AC...

(QB_NEW_EN)

🔇 Additional comments (47)
crates/starknet-devnet-server/src/api/http/endpoints/postman.rs (1)

70-73: Good ordering: handle L1→L2 before L2→L1.

Processing L1→L2 first avoids missing any L2→L1 messages produced by those transactions. The rationale is sound.

tests/integration/test_subscription_to_reorg.rs (2)

13-13: Renamed test accurately reflects new semantics (all subs notified on reorg).

Test intent is clear and aligned with PR behavior change. Good rename.


26-27: Good: reorg coverage extended; no stale PendingTransactions subscriptions remain
Verified across code, tests, and docs with:

rg -nC2 -g '!**/target/**' -e 'starknet_subscribePendingTransactions|PendingTransactions'

No matches found. Ready to approve.

crates/starknet-devnet-types/src/rpc/block.rs (2)

162-172: Deserialize: Explicitly reject 'pre_confirmed' and 'l1_accepted' for SubscriptionBlockId.

This matches the API intent and integration tests: only 'latest', hash, and number are permitted for subscriptions. Error messages are precise and map nicely to -32602 at the RPC layer.


256-259: Tests: Consolidated rejection of non-latest tags is clean and future-proof.

Looping over ["pending", "pre_confirmed", "l1_accepted"] keeps intent clear without over-specifying error messages for the "pending" case (which is rejected earlier in BlockId).

tests/integration/test_subscription_with_invalid_block_id.rs (3)

16-19: Helper for 'l1_accepted' error mirrors the pre_confirmed case.

Keeps assertions DRY and consistent. Good addition.


65-85: Renamed test to pre_confirmed appropriately; assertions look correct.

Covers both relevant subscription methods and asserts the exact error payload.


86-102: New test: 'l1_accepted' not allowed for block/events subscriptions.

Accurately validates the new restriction and expected error shape. Nice inclusion of the assertion message with the method name for easier debugging.

tests/integration/general_rpc_tests.rs (1)

30-30: No stale “-rc” references detected – changes approved
Spec version assertion updated to 0.9.0, fully aligned with the server constant.

tests/integration/lib.rs (2)

37-38: WS tests migrated from pending-txs to new tx/receipt topics — LGTM

Modules line up with the new WS API surface (NewTransactions and NewTransactionReceipts).


37-38: Pending-txs tests cleanup confirmed
test_subscription_to_pending_txs.rs has been removed and no remaining ‘pending_tx’ or ‘pending-tx’ references were found in the tests directory.

crates/starknet-devnet-types/src/rpc/transaction_receipt.rs (1)

39-49: finality_status accessor is correct and minimal

Cleanly extracts the inner CommonTransactionReceipt and returns a reference to the finality status.

crates/starknet-devnet-server/src/api/json_rpc/endpoints.rs (5)

63-69: Lock chaining in get_block_with_txs is fine; error mapping preserved

No semantic changes; happy with the concise style.


96-101: Pre-confirmed state update handling added — good

Returning PreConfirmedStateUpdate via the correct StarknetResponse variant aligns with the new finality-aware flow.


291-309: call: minor lock style change — LGTM

Lock scope and error mapping remain correct, including NoStateAtBlock and execution errors.


318-330: estimate_fee: concise locking pattern — LGTM

Mapping covers simulation and execution errors as before.


535-549: Intentional omission of finality filter in getEvents RPC
The JSON-RPC EventFilter (crates/starknet-devnet-types/src/rpc/transactions.rs) doesn’t include a finality_status field, so passing None into the core get_events call is correct and aligns with the current 0.9.0 spec. If a future spec revision adds a finality filter for getEvents, we can thread it through the RPC layer then.

crates/starknet-devnet-core/src/starknet/mod.rs (1)

1186-1199: get_unlimited_events: finality filter propagation — LGTM

The extra parameter cleanly forwards to events::get_events. Return shape unchanged.

crates/starknet-devnet-types/src/rpc/emitted_event.rs (3)

5-5: New import for TransactionFinalityStatus — OK

This aligns with finality-tagged subscription payloads.


73-77: From for Event via existing ref conversion — LGTM

Keeps the single cloning implementation centralized.


79-85: SubscriptionEmittedEvent fully wired through WS subscriptions and covered by tests

Verified with:

  • rg – found SubscriptionEmittedEvent usages in subscribe.rs, endpoints_ws.rs, and api/json_rpc/mod.rs.
  • rg in tests/ – confirmed integration tests exercise finality_status in:
    • Transaction status subscriptions
    • New‐tx subscriptions
    • New‐tx‐receipt subscriptions
    • Event subscriptions
    • Messaging and receipt lookups

No further changes needed.

crates/starknet-devnet-types/src/rpc/transactions.rs (1)

173-175: LGTM: Delegation to inner transaction is correct.

The delegation preserves the Option semantics and keeps the single source of truth in Transaction::get_sender_address.

tests/integration/test_subscription_to_new_txs.rs (3)

19-35: Helpers are clean and consistent with the new WS surface.

send_dummy_mint_tx, subscribe_new_txs, and receive_new_tx keep tests readable and DRY. Good abstraction.


55-65: Good pattern: assert and then parse; ensures finality field is validated.

Removing finality_status before deserializing into Transaction avoids unknown-field issues and keeps the test robust if payloads evolve.


196-215: Empty sender_address array semantics: verify and document.

You expect that "sender_address": [] behaves as “no sender filter” (i.e., wildcard). That’s reasonable, but easy to misinterpret server-side (some APIs treat empty arrays as “match nothing”). Please confirm this is the intended contract and document it in the WS subscription schema.

Do we guarantee "sender_address": [] == no filter? If yes, consider adding a comment in the API model (TransactionSubscriptionInput) and a server-side unit test to pin the behavior.

crates/starknet-devnet-core/src/starknet/events.rs (1)

354-377: Callsite updates look correct (new param None).

The tests were updated to pass None for the new parameter, preserving previous behavior. Good.

tests/integration/test_subscription_to_events.rs (1)

1-20: Event payload assertions include finality_status everywhere; nice consistency.

Good job updating assertions to include finality_status in all relevant tests (both filtered and unfiltered). This matches the new SubscriptionEmittedEvent payload design.

tests/integration/test_subscription_to_new_tx_receipts.rs (3)

20-41: Helper trio is tidy and mirrors the NewTransactions tests.

The helpers keep the test suite maintainable and aligned with the WS surface; naming is consistent.


61-74: Receipt decoding and finality assertion are correct for PreConfirmed.

Deserializing into InvokeTransactionReceipt and asserting both transaction_hash and finality_status pins the contract well.


212-231: Empty sender_address filter semantics: please confirm.

As with the transactions test, this treats "sender_address": [] as no filter. Please ensure the server treats empty array as wildcard rather than “match nothing” and document it in the input schema.

If this is intended, add a note in TransactionReceiptSubscriptionInput docs and a unit test to pin behavior.

tests/integration/test_subscription_to_tx_status.rs (3)

140-144: Good refactor: pass WS connections from the caller.

Threading externally created sockets makes the helper reusable and removes hidden side effects. Returning (tx_hash, sub_id_before, sub_id_after) is also convenient for downstream assertions.


147-162: Solid sequencing and assertions around pre/post-subscription.

The flow validates both “subscribed-before” and “subscribed-after” paths and ensures no extra notifications linger. The explicit .clone() on subscription IDs is fine given test ergonomics.


241-257: Clear coverage for “transaction mode” (no post-block extra notifications).

This is a good complement to the on-demand flow and prevents regressions around duplicate status transitions.

crates/starknet-devnet-server/src/api/json_rpc/models.rs (2)

260-264: Transactions subscription inputs look correct and spec-aligned.

Accepting multiple finality_status (without L1) and a list of sender addresses gives flexibility and maps neatly to the server-side filters.


267-270: Receipts input intentionally limits finality statuses.

Constraining to TransactionFinalityStatusWithoutL1 (no RECEIVED/CANDIDATE) matches the PR description. Good to see the API preventing unsupported combinations at the type level.

crates/starknet-devnet-server/src/api/json_rpc/mod.rs (3)

10-10: Spec version bump to 0.9.0 is straightforward.

Ensure all client-facing docs and tests assert “0.9.0” now (you already cover spec deserialization tests in this crate).


380-405: broadcast_changes flow looks correct.

Pre-confirmed diffs are processed before latest-block comparison, and reorgs produce a dedicated notification. This aligns with the finality-aware design.


940-956: New WS subscription variants are consistent with the spec surface.

The rename strings match the method names used in tests and endpoints. Looks good.

crates/starknet-devnet-server/src/api/json_rpc/endpoints_ws.rs (3)

138-157: Type-safe accessors for txs-with-receipts are solid.

Returning typed errors when encountering unexpected variants (Hashes/Full) is good defensive programming.


329-336: Finality-aware events: clarify initial scan window.

You default finality_status to AcceptedOnL2, which is good. The initial scan window is determined downstream; see next comment for a correctness fix.


355-360: Event payload wrapping is correct.

Wrapping emitted events with finality in SubscriptionEmittedEvent aligns with the new spec.

crates/starknet-devnet-server/src/subscribe.rs (6)

79-92: StatusFilter is simple and effective.

Empty container means “allow all,” which is a sensible default for absent filters.


97-113: Subscription enum evolution is clean.

Replacing legacy pending-tx variants with finality-aware NewTransactions/NewTransactionReceipts plus an Events status filter reads well and matches the new surface.


136-167: Matcher logic is precise; nice use of sender_address for filtering.

  • Transactions: pass through when both address and finality filters match; ignore when sender unavailable (Some networks may not populate it).
  • Receipts: finality from receipt plus optional sender match.
  • Events: filter via check_if_filter_applies_for_event and finality status.

222-257: WithoutL1 enums and conversions are well-scoped.

Encoding “no L1” at the type level prevents unsupported statuses from leaking into the API. Conversions to TransactionFinalityStatus are straightforward.


261-266: Flattening tx fields in NewTransactionNotification is a good UX.

Clients get a flat object with finality_status alongside tx fields; clean and spec-friendly.


414-420: All subscriptions receive reorg notifications.

This unified behavior is helpful for clients to reconcile state.

@FabijanC FabijanC merged commit 188ad87 into main Aug 21, 2025
2 checks passed
@FabijanC FabijanC deleted the rpc-0.9.0 branch August 21, 2025 09:22
@coderabbitai coderabbitai bot mentioned this pull request Sep 16, 2025
10 tasks
@coderabbitai coderabbitai bot mentioned this pull request Oct 1, 2025
10 tasks
@coderabbitai coderabbitai bot mentioned this pull request Dec 2, 2025
10 tasks
@coderabbitai coderabbitai bot mentioned this pull request Mar 13, 2026
10 tasks
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.

1 participant