Skip to content

Commit e4b7bab

Browse files
Websockets docs (#1575) [skip ci]
* Add `ws_client` and `full_node_ws_client` fixtures * Move `devnet_ws` to separate file * Refactor `FullNodeWSClient._handle_received_message()`; Fix typechecks * Fix formatting and linting * Fix linting * Update `resource_bounds` params description * Apply other code review suggestions * Fix description in migration guide * Fix example snippets in readme * Add mocked values in tests * Add setup devnet workflow * Change `devnet.yml` to be composite action * Rename `l1_transaction_hash` to `transaction_hash` in `starknet_getMessagesStatus` * Move `devnet.yml` to `composite-actions` dir * Format * Rename devnet composite action * Update devnet action path * Add missing `shell` property * Refactor devnet installation action; Add temporary workflow which will create devnet cache * Add inputs to steps using devnet action * Add missing checkout step * Add missing `devnet_sha` input to devnet setup workflow * Fix devnet installation action - use `devnet_sha` input * Trgigger CI * Remove `install_devnet.yml` workflow * Add todo in `checks.yml` * Remove temporary step for showing dirs * Move `parsed_abi_v2` into `test_event_serialization_v2` * Fix formatting * Add echo for displaying contracts v2 directory contents * Fix displaying contents of contracts v2 directory * Temporary change for displaying directory contents * Temporarily list contract v2 directory * Temporarily display owd * Revert "Temporarily display owd" This reverts commit 2ad7fe7. * Include Python 3.9 in `Setup Tests` job * Remove CI changes apart from modified devnet installation * Temporarily display directory with compiled contracts * Fix ls * Fix ls * Unconditionally compile contracts v1 and v2 * Use asdf action * Implement `test_get_transaction_status_with_failure_reason`; Code cleanup * Fix linting; Tests cleanup * Update regex in `test_rejection_reason_in_transaction_receipt` * Skip `test_compute_deploy_account_v3_transaction_hash` * Format * Skip `test_block_with_receipts_latest` * Fix failing docs tests * Fix linting * Remove contracts conditional compilation on CI * Add contracts compilation steps in docs tests * Skip `test_get_transaction_by_block_id`; Add todo * Skip `test_get_transaction_by_block_id`; Add todo * Skip `test_using_full_node_client`; Add todo * Skip `test_get_transaction_by_block_id`; Add todo * Add todos * Add todos in workflow * Update resource bounds params * Minor refactor of resource bounds params usage * Apply code review suggestion * Add `storage_root` field to `ContractLeafData` * Rename `block` to `block_id` param in ws methods * Add `is_reverted` field to `FunctionInvocation` * Add todo * Remove `block_id` param from `starknet_subscribeTransactionStatus` endpoint * Remove `contracts_storage_proof` field from `ContractsProof` * Fix `storage_root` data key * Implement `test_get_messages_status` * Fix `test_latest_resource_bounds_take_precedence` * Fix `test_deploy_prefunded_account` * Remove mocks from full node client * Add `l1_data_gas` to `ExecutionResources` * Update `CommonTransactionV3Fields.compute_resource_bounds_for_fee` * Add `l1_data_gas` to ExecutionResourcesSchema` * Fix `FullNodeClient.estimate_fee` * Fix contract tests * Temporarily skip some tests; Fix other tests * Update devnet sha * Remove code, tests and docs for old txs * Fix lint and typecheck * Fix formatting * Fix lint and typecheck * Remove old txs usages, adjust tests and docs * Fix tests * Remove todos * Remove `test_account_get_balance_eth` * Fix other tests * Skip test * Adjust devnet * Fix skip mark * Remove skip marks; Fix tests * Update ledger app sha * Refactor assertion in test * Restore original `test_ci_v2` * Remove `ResourceBounds.init_with_l1_gas_only` * Add todos * Formatting * Adjust todos and fixmes * Use `argent_account_class_hash` in `test_deploy_account_and_transfer` * Fix `test_deploy_account_and_transfer` * Revert "Fix `test_deploy_account_and_transfer`" This reverts commit d4c2eed. * Revert "Use `argent_account_class_hash` in `test_deploy_account_and_transfer`" This reverts commit 240c8ad. * Skip and add todo for `test_deploy_account_and_transfer` * Update skip message for `test_get_transaction_by_block_id` * Update todo * Remove `--initial-balance` flag for devnet start * Revert "Remove `--initial-balance` flag for devnet start" This reverts commit d9a2091. * Fix models and schemas for `get_storage_proof`; Add tests * Add storage proof response json * Restore values in storage proof response json * Use shorter example response for `get_storage_proof` test * Adjust storage proof tests * Remove helper function * Update migration guide * Update migration guide * Fix linting * Fix formatting * Remove unused imports * Add file with `ContractsStorageKeysSchema` * Fix `test_get_storage_proof` * Remove multipliers from `EstimatedFee.to_resource_bounds` * update todo * Fix and update network tests * Fix devnet client tests * Fix CI * Use asdf action * Restore `Download contracts` step in CI * Temporarily list contracts dir * Update listing dirs * Update listing dirs * Temporary CI change * Display compiled contracts path * Compile contract before running tests in CI * Partially implement `test_get_compiled_casm` * Fix params in `FullNodeClient.get_compiled_casm` * Update dependencies * Restore read api for txs other than v3 * Refactor broadcasted txn schemas * Add todo as skip reason * Add `test_sign_invoke_v3_auto_estimate` * Run `poetry lock` * Restore test values in `test_get_transaction_by_block_id_and_index` * Fix docs * Docstrings formatting * Add todos * Remove old transactions (#1557) * Fix `test_transaction_not_received_max_fee_too_big` * Fix and update network tests (#1563) * Add rust toolchain installation * Add `HintSchema`; Rename fields * Update field names in models * Add tests for `get_compiled_casm` * Move models and schema from executables api to separate files * Add docs * Fix migration guide * Rename file * Fix `test_get_compiled_casm` for devnet * Remove unused import * Add `STARKNET_PY_MARSHMALLOW_UNKNOWN_EXCLUDE` to CI * Change `index_delta_minus_1` to `index_delta_minus1` * Add fixes in executables schemas and models * Use schema with excluding unknown fields for `CasmClassSchema` * Update todos * Support `starknet_getCompiledCasm` (#1514) * Add todos * Add missing websocket API methods; Add tests * Fix formatting * Remove composite action * Format and lint * Add more tests * Further improvements of websocket client * Remove api docs (will be added in subsequent PR) * Run `poetry lock` * Update docstrings * Add `WebsocketClientError` * Fix `pytest_plugins` * Remove unused schemas; Refactor passing block number and block hash params * Remove unused `WSClient` * Add `devnet_ws` fixture * Add `test_new_heads_subscription_block_not_found` * Remove prints * Fix passing block id * Update websocket schemas * Add devnet websocket fixture * Remove `test_new_heads_subscription_block_not_found` * Restructure files; Add new tests * Remove unused model * Add missing docstrings * Update subscription id type to `str` (RPC 0.8.1) * Fix formatting * Remove unused schema * Add `is_connected` property; Add connection test * Little comments cleanup * Add websockets docs; Add code examples * Add api and guide docs files * Remove unneeded file * Rename `reorg_notification_handler` to `on_chain_reorg` * Fix formatting * Trigger CI * Format docstrings of `subscribe_pending_transactions` * Fix docs * Update `transaction_details` param description * Increase sleep time in `test_subscribe_transaction_status` * Fix serialize method in `PendingTransactionsNotificationResultField` * Apply code review suggestions * Apply code review suggestions * Change subscription id type to int * Change type of subscription id to str
1 parent f0e2ffb commit e4b7bab

File tree

7 files changed

+430
-219
lines changed

7 files changed

+430
-219
lines changed

docs/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ API
2222
api/serializers
2323
api/proxy_resolvers
2424
api/typed_data
25+
api/websocket_client

docs/api/websocket_client.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
WebsocketClient
2+
===============
3+
4+
.. py:module:: starknet_py.net.websockets.websocket_client
5+
6+
.. autoclass-with-examples:: WebsocketClient
7+
:members:
8+
:member-order: groupwise

docs/guide.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ Guide
1010
guide/serialization
1111
guide/signing
1212
guide/generating_key_pair
13+
guide/websockets

docs/guide/websockets.rst

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
Websockets
2+
==========
3+
4+
Introduction
5+
------------
6+
Apart from interacting with Starknet by request-response model, you can also rely on real-time notifications.
7+
Here comes :class:`~starknet_py.net.websockets.websocket_client.WebsocketClient` which allows to establish a connection with Starknet node and listen for events.
8+
9+
Connecting
10+
----------
11+
12+
To begin interacting with Starknet via websockets, create a new instance of :class:`~starknet_py.net.websockets.websocket_client.WebsocketClient` and connect to the node.
13+
14+
.. codesnippet:: ../../starknet_py/tests/e2e/docs/code_examples/test_websocket_client.py
15+
:language: python
16+
:dedent: 4
17+
:start-after: docs-start: connect
18+
:end-before: docs-end: connect
19+
20+
Different subscription methods
21+
------------------------------
22+
23+
New block headers
24+
#################
25+
26+
To subscribe to new block headers, use :meth:`~starknet_py.net.websockets.websocket_client.WebsocketClient.subscribe_new_heads`.
27+
Every time a new block is created, the event will be fired.
28+
29+
.. codesnippet:: ../../starknet_py/tests/e2e/docs/code_examples/test_websocket_client.py
30+
:language: python
31+
:dedent: 4
32+
:start-after: docs-start: subscribe_new_heads
33+
:end-before: docs-end: subscribe_new_heads
34+
35+
New events
36+
##########
37+
38+
To subscribe to new events, use :meth:`~starknet_py.net.websockets.websocket_client.WebsocketClient.subscribe_events`.
39+
Every time a new event is emitted, the event will be fired.
40+
41+
It's possible to filter events by contract addresses, keys and block id. See all options in the method documentation.
42+
43+
.. codesnippet:: ../../starknet_py/tests/e2e/docs/code_examples/test_websocket_client.py
44+
:language: python
45+
:dedent: 4
46+
:start-after: docs-start: subscribe_events
47+
:end-before: docs-end: subscribe_events
48+
49+
50+
Transaction status
51+
##################
52+
53+
To subscribe to transaction status changes, use :meth:`~starknet_py.net.websockets.websocket_client.WebsocketClient.subscribe_transaction_status`.
54+
Every time a transaction status changes, the event will be fired.
55+
56+
.. codesnippet:: ../../starknet_py/tests/e2e/docs/code_examples/test_websocket_client.py
57+
:language: python
58+
:dedent: 4
59+
:start-after: docs-start: subscribe_transaction_status
60+
:end-before: docs-end: subscribe_transaction_status
61+
62+
63+
Pending transactions
64+
####################
65+
66+
To subscribe to pending transactions, use :meth:`~starknet_py.net.websockets.websocket_client.WebsocketClient.subscribe_pending_transactions`.
67+
Every time a new pending transaction is added, the event will be fired.
68+
69+
It's possible to filter pending transactions by sender address.
70+
71+
.. codesnippet:: ../../starknet_py/tests/e2e/docs/code_examples/test_websocket_client.py
72+
:language: python
73+
:dedent: 4
74+
:start-after: docs-start: subscribe_pending_transactions
75+
:end-before: docs-end: subscribe_pending_transactions
76+
77+
Handling chain reorganization notifications
78+
###########################################
79+
80+
When subscribing to new block headers, events, or transaction statuses, you also receive notifications of chain reorganizations. For example, if two blocks are produced nearly simultaneously and one replaces the other as the canonical block, you'll get an update indicating that the chain has restructured.
81+
To handle them, you need to set the ``on_chain_reorg`` to your custom function.
82+
83+
.. codesnippet:: ../../starknet_py/tests/e2e/docs/code_examples/test_websocket_client.py
84+
:language: python
85+
:dedent: 4
86+
:start-after: docs-start: on_chain_reorg
87+
:end-before: docs-end: on_chain_reorg
88+
89+
Disconnecting
90+
-------------
91+
92+
To disconnect from the node, use :meth:`~starknet_py.net.websockets.websocket_client.WebsocketClient.disconnect`.
93+
94+
.. codesnippet:: ../../starknet_py/tests/e2e/docs/code_examples/test_websocket_client.py
95+
:language: python
96+
:dedent: 4
97+
:start-after: docs-start: disconnect
98+
:end-before: docs-end: disconnect

starknet_py/net/websockets/websocket_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,9 @@ async def subscribe_pending_transactions(
187187
While there is no mempool, this notifies of transactions in the pending block.
188188
189189
:param handler: The function to call when a new pending transaction is received.
190-
:param transaction_details: Whether to include transaction details in the notification.
191-
If false, only hash is returned.
190+
:param transaction_details: If false, only hash is returned, otherwise full transaction details.
192191
:param sender_address: The sender address to filter transactions by.
192+
193193
:return: The subscription ID.
194194
"""
195195
params = {}
@@ -220,7 +220,7 @@ def on_chain_reorg(
220220
@on_chain_reorg.setter
221221
def on_chain_reorg(self, handler: Callable[[ReorgNotification], Any]):
222222
"""
223-
Sets the handler for reorg notifications.
223+
Sets the handler for chain reorg notifications.
224224
225225
:param handler: The handler for chain reorg notifications.
226226
"""

starknet_py/tests/e2e/client/websocket_client_test.py

Lines changed: 3 additions & 216 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,13 @@
1-
import asyncio
2-
from typing import List, Optional, Union
1+
from typing import Optional
32

43
import pytest
54

65
from starknet_py.devnet_utils.devnet_client import DevnetClient
7-
from starknet_py.hash.selector import get_selector_from_name
8-
from starknet_py.net.account.base_account import BaseAccount
9-
from starknet_py.net.client_models import (
10-
BlockHeader,
11-
Call,
12-
EmittedEvent,
13-
StarknetBlock,
14-
TransactionExecutionStatus,
15-
TransactionStatus,
16-
)
6+
from starknet_py.net.client_models import BlockHeader, StarknetBlock
177
from starknet_py.net.full_node_client import FullNodeClient
18-
from starknet_py.net.models import StarknetChainId
198
from starknet_py.net.websockets.errors import WebsocketClientError
20-
from starknet_py.net.websockets.models import (
21-
NewEventsNotification,
22-
NewHeadsNotification,
23-
NewTransactionStatus,
24-
PendingTransactionsNotification,
25-
ReorgData,
26-
ReorgNotification,
27-
Transaction,
28-
TransactionStatusNotification,
29-
)
9+
from starknet_py.net.websockets.models import NewHeadsNotification
3010
from starknet_py.net.websockets.websocket_client import WebsocketClient
31-
from starknet_py.tests.e2e.fixtures.constants import MAX_RESOURCE_BOUNDS
32-
33-
34-
@pytest.mark.asyncio
35-
async def test_connect_and_disconnect(devnet_ws: str):
36-
websocket_client = WebsocketClient(devnet_ws)
37-
assert not await websocket_client.is_connected
38-
39-
await websocket_client.connect()
40-
assert await websocket_client.is_connected
41-
42-
await websocket_client.disconnect()
43-
assert not await websocket_client.is_connected
44-
45-
46-
@pytest.mark.asyncio
47-
async def test_subscribe_new_heads(
48-
websocket_client: WebsocketClient,
49-
devnet_client: DevnetClient,
50-
):
51-
received_block: Optional[BlockHeader] = None
52-
53-
def handler(new_heads_notification: NewHeadsNotification):
54-
nonlocal received_block
55-
received_block = new_heads_notification.result
56-
57-
subscription_id = await websocket_client.subscribe_new_heads(handler=handler)
58-
59-
new_block_hash = await devnet_client.create_block()
60-
61-
assert received_block is not None
62-
assert int(new_block_hash, 16) == received_block.block_hash
63-
64-
unsubscribe_result = await websocket_client.unsubscribe(subscription_id)
65-
assert unsubscribe_result is True
6611

6712

6813
@pytest.mark.asyncio
@@ -156,164 +101,6 @@ async def test_subscribe_new_heads_too_many_blocks_back(
156101
)
157102

158103

159-
@pytest.mark.asyncio
160-
async def test_subscribe_events(
161-
websocket_client: WebsocketClient,
162-
deployed_balance_contract,
163-
argent_account_v040: BaseAccount,
164-
):
165-
emitted_events: List[EmittedEvent] = []
166-
167-
def handler(new_events_notification: NewEventsNotification):
168-
nonlocal emitted_events
169-
emitted_events.append(new_events_notification.result)
170-
171-
subscription_id = await websocket_client.subscribe_events(
172-
handler=handler, from_address=argent_account_v040.address
173-
)
174-
175-
increase_balance_call = Call(
176-
to_addr=deployed_balance_contract.address,
177-
selector=get_selector_from_name("increase_balance"),
178-
calldata=[100],
179-
)
180-
execute = await argent_account_v040.execute_v3(
181-
calls=increase_balance_call, resource_bounds=MAX_RESOURCE_BOUNDS
182-
)
183-
await argent_account_v040.client.wait_for_tx(tx_hash=execute.transaction_hash)
184-
await argent_account_v040.client.get_transaction_receipt(
185-
tx_hash=execute.transaction_hash
186-
)
187-
188-
assert len(emitted_events) > 0
189-
for emitted_event in emitted_events:
190-
assert emitted_event.from_address == argent_account_v040.address
191-
192-
unsubscribe_result = await websocket_client.unsubscribe(subscription_id)
193-
assert unsubscribe_result is True
194-
195-
196-
@pytest.mark.asyncio
197-
async def test_subscribe_transaction_status(
198-
websocket_client: WebsocketClient,
199-
deployed_balance_contract,
200-
argent_account_v040: BaseAccount,
201-
):
202-
new_transaction_status: Optional[NewTransactionStatus] = None
203-
204-
def handler(transaction_status_notification: TransactionStatusNotification):
205-
nonlocal new_transaction_status
206-
new_transaction_status = transaction_status_notification.result
207-
208-
increase_balance_call = Call(
209-
to_addr=deployed_balance_contract.address,
210-
selector=get_selector_from_name("increase_balance"),
211-
calldata=[100],
212-
)
213-
execute = await argent_account_v040.execute_v3(
214-
calls=increase_balance_call, resource_bounds=MAX_RESOURCE_BOUNDS
215-
)
216-
217-
subscription_id = await websocket_client.subscribe_transaction_status(
218-
handler=handler, transaction_hash=execute.transaction_hash
219-
)
220-
221-
await argent_account_v040.client.wait_for_tx(tx_hash=execute.transaction_hash)
222-
await argent_account_v040.client.get_transaction_receipt(
223-
tx_hash=execute.transaction_hash
224-
)
225-
226-
await asyncio.sleep(5)
227-
228-
assert new_transaction_status is not None
229-
assert new_transaction_status.transaction_hash == execute.transaction_hash
230-
assert (
231-
new_transaction_status.status.finality_status
232-
== TransactionStatus.ACCEPTED_ON_L2
233-
)
234-
assert (
235-
new_transaction_status.status.execution_status
236-
== TransactionExecutionStatus.SUCCEEDED
237-
)
238-
assert new_transaction_status.status.failure_reason is None
239-
240-
unsubscribe_result = await websocket_client.unsubscribe(subscription_id)
241-
assert unsubscribe_result is True
242-
243-
244-
@pytest.mark.asyncio
245-
async def test_subscribe_pending_transactions(
246-
websocket_client: WebsocketClient,
247-
deployed_balance_contract,
248-
argent_account_v040: BaseAccount,
249-
):
250-
pending_transactions: List[Union[int, Transaction]] = []
251-
252-
def handler(pending_transaction_notification: PendingTransactionsNotification):
253-
nonlocal pending_transactions
254-
pending_transactions.append(pending_transaction_notification.result)
255-
256-
subscription_id = await websocket_client.subscribe_pending_transactions(
257-
handler=handler,
258-
sender_address=[argent_account_v040.address],
259-
)
260-
261-
increase_balance_call = Call(
262-
to_addr=deployed_balance_contract.address,
263-
selector=get_selector_from_name("increase_balance"),
264-
calldata=[100],
265-
)
266-
execute = await argent_account_v040.execute_v3(
267-
calls=increase_balance_call, resource_bounds=MAX_RESOURCE_BOUNDS
268-
)
269-
270-
await argent_account_v040.client.wait_for_tx(tx_hash=execute.transaction_hash)
271-
await argent_account_v040.client.get_transaction_receipt(
272-
tx_hash=execute.transaction_hash
273-
)
274-
275-
assert len(pending_transactions) == 1
276-
pending_transaction = pending_transactions[0]
277-
278-
transaction_hash = (
279-
execute.transaction_hash
280-
if isinstance(pending_transaction, int)
281-
else pending_transaction.calculate_hash(StarknetChainId.SEPOLIA)
282-
)
283-
assert execute.transaction_hash == transaction_hash
284-
285-
unsubscribe_result = await websocket_client.unsubscribe(subscription_id)
286-
assert unsubscribe_result is True
287-
288-
289-
@pytest.mark.asyncio
290-
async def test_receive_reorg_notification(
291-
websocket_client: WebsocketClient,
292-
devnet_client: DevnetClient,
293-
):
294-
reorg_data: Optional[ReorgData] = None
295-
296-
def handler_reorg(reorg_notification: ReorgNotification):
297-
nonlocal reorg_data
298-
reorg_data = reorg_notification.result
299-
300-
subscription_id = await websocket_client.subscribe_new_heads(handler=lambda _: _)
301-
302-
websocket_client.on_chain_reorg = handler_reorg
303-
new_block_hash = await devnet_client.create_block()
304-
305-
await devnet_client.abort_block(block_hash=new_block_hash)
306-
307-
await asyncio.sleep(5)
308-
309-
assert reorg_data is not None
310-
assert reorg_data.starting_block_hash == int(new_block_hash, 16)
311-
assert reorg_data.ending_block_hash == int(new_block_hash, 16)
312-
313-
unsubscribe_result = await websocket_client.unsubscribe(subscription_id)
314-
assert unsubscribe_result is True
315-
316-
317104
@pytest.mark.asyncio
318105
async def test_unsubscribe_with_non_existing_id(
319106
websocket_client: WebsocketClient,

0 commit comments

Comments
 (0)