Skip to content

feat(rpc): Starknet WebSocket subscription API#569

Merged
kariy merged 9 commits into
mainfrom
feat/ws-subscription
May 13, 2026
Merged

feat(rpc): Starknet WebSocket subscription API#569
kariy merged 9 commits into
mainfrom
feat/ws-subscription

Conversation

@kariy
Copy link
Copy Markdown
Member

@kariy kariy commented May 13, 2026

Implements the Starknet WebSocket subscription API (v0.9.0):

  • starknet_subscribeNewHeads
  • starknet_subscribeEvents
  • starknet_subscribeTransactionStatus
  • starknet_subscribeNewTransactionReceipts
  • starknet_subscribeNewTransactions

Block notifications are fanned out from BlockProductionTask via a tokio::sync::broadcast channel. Backfill is bounded to 1024 blocks and pre-validated with structured errors (BLOCK_NOT_FOUND, TOO_MANY_BLOCKS_BACK). HTTP health check on / and WS upgrades on / coexist via a new WsPassthroughProxyLayer.

StarknetRpcClient<C = HttpClient> is generalized over the jsonrpsee transport — new_ws(url) returns a StarknetRpcClient<WsClient> that exposes both the regular API and the typed subscription methods, returning a SubscriptionStream<T> wrapper.

🤖 Generated with Claude Code

kariy and others added 5 commits May 13, 2026 10:25
Adds the v0.9.0 WebSocket subscription API for feature parity with the
Starknet spec: subscribeNewHeads, subscribeEvents, subscribeTransactionStatus,
subscribeNewTransactionReceipts, and subscribeNewTransactions.

Block notifications are propagated from BlockProductionTask via a
tokio::sync::broadcast channel, fanned out to subscribers through
StarknetApi::subscribe_blocks(). Pre-accept validation rejects invalid
backfill parameters (TOO_MANY_BLOCKS_BACK, BLOCK_NOT_FOUND); post-accept
errors are propagated to the client as structured subscription error
notifications via StarknetApiError::into_subscription_error.

The health-check ProxyGetRequestLayer on / is wrapped in a new
WsPassthroughProxyLayer that forwards WebSocket upgrades directly to
jsonrpsee's server, so HTTP GET / (health) and WS on / coexist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generalize `StarknetRpcClient<C = HttpClient>` over the underlying jsonrpsee
client so a single client type can drive both HTTP and WebSocket transports
without redeclaring the regular RPC methods. WebSocket users opt in via
`new_ws(url)` and get the subscription methods (`subscribe_new_heads`,
`subscribe_events`, `subscribe_transaction_status`,
`subscribe_new_transaction_receipts`, `subscribe_new_transactions`) on top
of the existing API. Subscription methods return a `SubscriptionStream<T>`
wrapper that hides `jsonrpsee::Subscription` from the public surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The subscription API server impl was added in 899d277 but never merged
into `rpc_modules`, so `starknet_subscribe*` calls returned MethodNotFound
on both the sequencer and full-node setups. Merge it alongside
`StarknetApiServer` under the same `RpcModuleKind::Starknet` gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the raw `WsClient::subscribe::<T, _>(method_name, params, unsub)`
calls with the typed `StarknetRpcClient<WsClient>` methods. Tests now
exercise the same client surface that downstream users will use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.30.

Benchmark suite Current: 4f04af4 Previous: c8d76b6 Ratio
CompiledClass(fixture)/compress 2731149 ns/iter (± 42014) 2070455 ns/iter (± 15393) 1.32
CompiledClass(fixture)/decompress 2959493 ns/iter (± 30785) 2272571 ns/iter (± 7035) 1.30
GenericContractInfo/compress 143 ns/iter (± 12) 109 ns/iter (± 8) 1.31
BlockNumber/decompress 27 ns/iter (± 1) 20 ns/iter (± 0) 1.35
TxNumber/decompress 27 ns/iter (± 1) 20 ns/iter (± 0) 1.35
FinalityStatus/compress 1 ns/iter (± 0) 0 ns/iter (± 0) +∞
TypedTransactionExecutionInfo/compress 16115 ns/iter (± 85) 10669 ns/iter (± 37) 1.51
VersionedContractClass/compress 385 ns/iter (± 14) 284 ns/iter (± 3) 1.36
MigratedCompiledClassHash/compress 164 ns/iter (± 12) 124 ns/iter (± 9) 1.32
ContractInfoChangeList/compress 1700 ns/iter (± 47) 1161 ns/iter (± 25) 1.46
BlockChangeList/compress 739 ns/iter (± 42) 509 ns/iter (± 101) 1.45
ReceiptEnvelope/compress 31198 ns/iter (± 3209) 20236 ns/iter (± 1207) 1.54
TrieDatabaseValue/compress 166 ns/iter (± 1) 123 ns/iter (± 3) 1.35
TrieHistoryEntry/compress 296 ns/iter (± 4) 222 ns/iter (± 1) 1.33

This comment was automatically generated by workflow using github-action-benchmark.

CC: @kariy

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 13, 2026

Codec benchmark diff vs main

Benchmark Baseline (ns) Current (ns) Δ
CompiledClass(fixture)/compress 2070455 2731149 +31.91%
CompiledClass(fixture)/decompress 2272571 2959493 +30.23%
ExecutionCheckpoint/compress 27 35 +29.63%
ExecutionCheckpoint/decompress 20 26 +30.00%
PruningCheckpoint/compress 27 35 +29.63%
PruningCheckpoint/decompress 20 25 +25.00%
VersionedHeader/compress 523 658 +25.81%
VersionedHeader/decompress 704 831 +18.04%
StoredBlockBodyIndices/compress 63 77 +22.22%
StoredBlockBodyIndices/decompress 31 36 +16.13%
StorageEntry/compress 124 146 +17.74%
StorageEntry/decompress 123 139 +13.01%
ContractNonceChange/compress 124 157 +26.61%
ContractNonceChange/decompress 204 232 +13.73%
ContractClassChange/compress 175 210 +20.00%
ContractClassChange/decompress 215 252 +17.21%
ContractStorageEntry/compress 135 156 +15.56%
ContractStorageEntry/decompress 271 306 +12.92%
GenericContractInfo/compress 109 143 +31.19%
GenericContractInfo/decompress 91 101 +10.99%
Felt/compress 71 81 +14.08%
Felt/decompress 49 59 +20.41%
BlockHash/compress 70 82 +17.14%
BlockHash/decompress 49 58 +18.37%
TxHash/compress 70 82 +17.14%
TxHash/decompress 49 58 +18.37%
ClassHash/compress 69 82 +18.84%
ClassHash/decompress 50 58 +16.00%
CompiledClassHash/compress 69 82 +18.84%
CompiledClassHash/decompress 50 58 +16.00%
BlockNumber/compress 39 47 +20.51%
BlockNumber/decompress 20 27 +35.00%
TxNumber/compress 39 47 +20.51%
TxNumber/decompress 20 27 +35.00%
FinalityStatus/compress 0 1 +Infinity%
FinalityStatus/decompress 10 12 +20.00%
TypedTransactionExecutionInfo/compress 10669 16115 +51.05%
TypedTransactionExecutionInfo/decompress 2872 3526 +22.77%
VersionedContractClass/compress 284 385 +35.56%
VersionedContractClass/decompress 677 799 +18.02%
MigratedCompiledClassHash/compress 124 164 +32.26%
MigratedCompiledClassHash/decompress 123 138 +12.20%
ContractInfoChangeList/compress 1161 1700 +46.43%
ContractInfoChangeList/decompress 1787 2241 +25.41%
BlockChangeList/compress 509 739 +45.19%
BlockChangeList/decompress 731 911 +24.62%
ReceiptEnvelope/compress 20236 31198 +54.17%
ReceiptEnvelope/decompress 5084 6147 +20.91%
TrieDatabaseValue/compress 123 166 +34.96%
TrieDatabaseValue/decompress 201 227 +12.94%
TrieHistoryEntry/compress 222 296 +33.33%
TrieHistoryEntry/decompress 226 259 +14.60%

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 13, 2026

Runner: AMD EPYC 7763 64-Core Processor (4 cores) · 15Gi RAM

@codecov
Copy link
Copy Markdown

codecov Bot commented May 13, 2026

Codecov Report

❌ Patch coverage is 79.61165% with 105 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.42%. Comparing base (9bde0ae) to head (4f04af4).
⚠️ Report is 413 commits behind head on main.

Files with missing lines Patch % Lines
crates/rpc/rpc-server/src/starknet/subscription.rs 72.56% 90 Missing ⚠️
crates/rpc/rpc-api/src/error/starknet.rs 0.00% 6 Missing ⚠️
crates/node/full/src/lib.rs 0.00% 3 Missing ⚠️
crates/rpc/rpc-server/src/starknet/mod.rs 62.50% 3 Missing ⚠️
crates/starknet/src/rpc.rs 95.89% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #569      +/-   ##
==========================================
- Coverage   73.32%   68.42%   -4.91%     
==========================================
  Files         209      322     +113     
  Lines       23132    45252   +22120     
==========================================
+ Hits        16961    30962   +14001     
- Misses       6171    14290    +8119     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

kariy and others added 4 commits May 13, 2026 13:57
`evaluate_receipt_from_params` previously short-circuited to `Ok(receipt)` in
the `ReceiptBlockInfo::Block` branch whenever `expected_finality_status` was
`None`, even if the receipt's execution result was `Reverted`. Callers using
the default `must_succeed = true` therefore received `Ok` for reverted txs,
opposite to the documented contract.

Hoist the `must_succeed` check out of the finality-status gate so it applies
in both branches, and cover the previously-uncovered cases (reverted receipt
with no expected finality status, with and without `must_succeed`) in unit
tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Split the existing one-shot test into three to exercise each branch of the
server handler:

- already-finalized + succeeded: subscribe after the tx lands in storage,
  assert a single SUCCEEDED notification.
- already-finalized + reverted: submit a tx that calls a non-existent
  selector (with explicit resource bounds so fee estimation is bypassed),
  poll the receipt directly, then subscribe and assert a REVERTED
  notification with a non-empty failure_reason.
- in-pool then mined: use block_time mode so the tx sits in the pool when
  we subscribe; assert the first notification has no execution_status and
  the second carries SUCCEEDED once the block is mined.

Also adds a happy-path and a sender-filter test for
subscribe_new_transaction_receipts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kariy kariy merged commit a1cb8c9 into main May 13, 2026
20 of 21 checks passed
@kariy kariy deleted the feat/ws-subscription branch May 13, 2026 21:11
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