Skip to content

Commit 4c60216

Browse files
authored
Add missing RPC methods (#1094)
* Add missing methods * Add tests * Fix lint * Adjust tests * Adjust tests * Update migration guide * Add test_get_syncing_status * Fix lint * Fix format
1 parent 174f0b7 commit 4c60216

File tree

7 files changed

+162
-23
lines changed

7 files changed

+162
-23
lines changed

docs/migration_guide.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ Migration guide
1111

1212
.. currentmodule:: starknet_py.contract
1313

14+
Also, four methods were added to its interface:
15+
16+
- :meth:`FullNodeClient.get_block_number`
17+
- :meth:`FullNodeClient.get_block_hash_and_number`
18+
- :meth:`FullNodeClient.get_chain_id`
19+
- :meth:`FullNodeClient.get_syncing_status`
20+
1421

1522
:class:`Contract` now *initially* supports contracts written in **Cairo1**.
1623

starknet_py/net/client_models.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,22 @@ class StarknetBlockWithTxHashes(StarknetBlockCommon):
283283
transactions: List[int]
284284

285285

286+
@dataclass
287+
class BlockHashAndNumber:
288+
block_hash: int
289+
block_number: int
290+
291+
292+
@dataclass
293+
class SyncStatus:
294+
starting_block_hash: int
295+
starting_block_num: int
296+
current_block_hash: int
297+
current_block_num: int
298+
highest_block_hash: int
299+
highest_block_num: int
300+
301+
286302
@dataclass
287303
class BlockSingleTransactionTrace:
288304
"""

starknet_py/net/full_node_client.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from starknet_py.net.client import Client
99
from starknet_py.net.client_errors import ClientError
1010
from starknet_py.net.client_models import (
11+
BlockHashAndNumber,
1112
BlockStateUpdate,
1213
BlockTransactionTraces,
1314
Call,
@@ -22,6 +23,7 @@
2223
SierraContractClass,
2324
StarknetBlock,
2425
StarknetBlockWithTxHashes,
26+
SyncStatus,
2527
Tag,
2628
Transaction,
2729
TransactionReceipt,
@@ -39,6 +41,7 @@
3941
)
4042
from starknet_py.net.networks import Network
4143
from starknet_py.net.schemas.rpc import (
44+
BlockHashAndNumberSchema,
4245
BlockStateUpdateSchema,
4346
ContractClassSchema,
4447
DeclareTransactionResponseSchema,
@@ -51,6 +54,7 @@
5154
SierraContractClassSchema,
5255
StarknetBlockSchema,
5356
StarknetBlockWithTxHashesSchema,
57+
SyncStatusSchema,
5458
TransactionReceiptSchema,
5559
TypesOfTransactionsSchema,
5660
)
@@ -342,6 +346,26 @@ async def estimate_fee(
342346
),
343347
)
344348

349+
async def get_block_number(self) -> int:
350+
"""Get the most recent accepted block number"""
351+
return await self._client.call(method_name="blockNumber", params={})
352+
353+
async def get_block_hash_and_number(self) -> BlockHashAndNumber:
354+
"""Get the most recent accepted block hash and number"""
355+
res = await self._client.call(method_name="blockHashAndNumber", params={})
356+
return cast(BlockHashAndNumber, BlockHashAndNumberSchema().load(res))
357+
358+
async def get_chain_id(self) -> int:
359+
"""Return the currently configured Starknet chain id"""
360+
return await self._client.call(method_name="chainId", params={})
361+
362+
async def get_syncing_status(self) -> Union[bool, SyncStatus]:
363+
"""Returns an object about the sync status, or false if the node is not syncing"""
364+
sync_status = await self._client.call(method_name="syncing", params={})
365+
if isinstance(sync_status, bool):
366+
return sync_status
367+
return cast(SyncStatus, SyncStatusSchema().load(sync_status))
368+
345369
async def call_contract(
346370
self,
347371
call: Call,

starknet_py/net/http_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ async def handle_request_error(self, request: ClientResponse):
8585

8686

8787
class RpcHttpClient(HttpClient):
88-
async def call(self, method_name: str, params: dict) -> dict:
88+
async def call(self, method_name: str, params: dict):
8989
payload = {
9090
"jsonrpc": "2.0",
9191
"method": f"starknet_{method_name}",

starknet_py/net/schemas/rpc.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from starknet_py.abi.schemas import ContractAbiEntrySchema
55
from starknet_py.net.client_models import (
6+
BlockHashAndNumber,
67
BlockStateUpdate,
78
ContractClass,
89
ContractsNonce,
@@ -32,6 +33,7 @@
3233
StarknetBlockWithTxHashes,
3334
StateDiff,
3435
StorageDiffItem,
36+
SyncStatus,
3537
TransactionReceipt,
3638
)
3739
from starknet_py.net.schemas.common import (
@@ -231,6 +233,28 @@ def make_dataclass(self, data, **kwargs) -> StarknetBlock:
231233
return StarknetBlock(**data)
232234

233235

236+
class BlockHashAndNumberSchema(Schema):
237+
block_hash = Felt(data_key="block_hash", required=True)
238+
block_number = fields.Integer(data_key="block_number", required=True)
239+
240+
@post_load
241+
def make_dataclass(self, data, **kwargs) -> BlockHashAndNumber:
242+
return BlockHashAndNumber(**data)
243+
244+
245+
class SyncStatusSchema(Schema):
246+
starting_block_hash = Felt(data_key="starting_block_hash", required=True)
247+
starting_block_num = Felt(data_key="starting_block_num", required=True)
248+
current_block_hash = Felt(data_key="current_block_hash", required=True)
249+
current_block_num = Felt(data_key="current_block_num", required=True)
250+
highest_block_hash = Felt(data_key="highest_block_hash", required=True)
251+
highest_block_num = Felt(data_key="highest_block_num", required=True)
252+
253+
@post_load
254+
def make_dataclass(self, data, **kwargs) -> SyncStatus:
255+
return SyncStatus(**data)
256+
257+
234258
class StarknetBlockWithTxHashesSchema(Schema):
235259
block_hash = Felt(data_key="block_hash", required=True)
236260
parent_block_hash = Felt(data_key="parent_hash", required=True)

starknet_py/tests/e2e/client/full_node_test.py

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import asyncio
2-
from unittest.mock import MagicMock, patch
1+
from unittest.mock import AsyncMock, patch
32

43
import pytest
54

@@ -9,13 +8,16 @@
98
from starknet_py.net.account.account import Account
109
from starknet_py.net.client_errors import ClientError
1110
from starknet_py.net.client_models import (
11+
BlockHashAndNumber,
1212
ContractClass,
1313
DeclareTransaction,
1414
SierraContractClass,
15+
SyncStatus,
1516
TransactionType,
1617
)
1718
from starknet_py.net.full_node_client import _to_rpc_felt
1819
from starknet_py.net.models import StarknetChainId
20+
from starknet_py.tests.e2e.utils import create_empty_block
1921

2022

2123
def _parse_event_name(event: str) -> str:
@@ -100,30 +102,26 @@ async def test_method_raises_on_both_block_hash_and_number(full_node_client):
100102
@pytest.mark.asyncio
101103
async def test_pending_transactions(full_node_client):
102104
with patch(
103-
"starknet_py.net.http_client.RpcHttpClient.call", MagicMock()
105+
"starknet_py.net.http_client.RpcHttpClient.call", AsyncMock()
104106
) as mocked_http_call:
105-
result = asyncio.Future()
106-
result.set_result(
107-
[
108-
{
109-
"transaction_hash": "0x01",
110-
"class_hash": "0x05",
111-
"version": "0x0",
112-
"type": "DEPLOY",
113-
"contract_address": "0x02",
114-
"contract_address_salt": "0x0",
115-
"constructor_calldata": [],
116-
}
117-
]
118-
)
119-
mocked_http_call.return_value = result
107+
mocked_http_call.return_value = [
108+
{
109+
"transaction_hash": "0x01",
110+
"class_hash": "0x05",
111+
"version": "0x0",
112+
"type": "DEPLOY",
113+
"contract_address": "0x02",
114+
"contract_address_salt": "0x0",
115+
"constructor_calldata": [],
116+
}
117+
]
120118

121119
pending_transactions = await full_node_client.get_pending_transactions()
122120

123-
assert len(pending_transactions) == 1
124-
assert pending_transactions[0].hash == 0x1
125-
assert pending_transactions[0].signature == []
126-
assert pending_transactions[0].max_fee == 0
121+
assert len(pending_transactions) == 1
122+
assert pending_transactions[0].hash == 0x1
123+
assert pending_transactions[0].signature == []
124+
assert pending_transactions[0].max_fee == 0
127125

128126

129127
@pytest.mark.asyncio
@@ -350,3 +348,64 @@ async def test_get_events_nonexistent_starting_block(
350348
follow_continuation_token=False,
351349
chunk_size=1,
352350
)
351+
352+
353+
@pytest.mark.asyncio
354+
async def test_get_block_number(full_node_client):
355+
block_number = await full_node_client.get_block_number()
356+
357+
# pylint: disable=protected-access
358+
await create_empty_block(full_node_client._client)
359+
360+
new_block_number = await full_node_client.get_block_number()
361+
assert new_block_number == block_number + 1
362+
363+
364+
@pytest.mark.asyncio
365+
async def test_get_block_hash_and_number(full_node_client):
366+
block_hash_and_number = await full_node_client.get_block_hash_and_number()
367+
368+
assert isinstance(block_hash_and_number, BlockHashAndNumber)
369+
370+
# pylint: disable=protected-access
371+
await create_empty_block(full_node_client._client)
372+
373+
new_block_hash_and_number = await full_node_client.get_block_hash_and_number()
374+
375+
assert (
376+
new_block_hash_and_number.block_number == block_hash_and_number.block_number + 1
377+
)
378+
assert new_block_hash_and_number.block_hash > 0
379+
380+
381+
@pytest.mark.asyncio
382+
async def test_get_chain_id(full_node_client):
383+
chain_id = await full_node_client.get_chain_id()
384+
385+
assert chain_id == hex(StarknetChainId.TESTNET.value)
386+
387+
388+
@pytest.mark.asyncio
389+
async def test_get_syncing_status_false(full_node_client):
390+
sync_status = await full_node_client.get_syncing_status()
391+
392+
assert sync_status is False
393+
394+
395+
@pytest.mark.asyncio
396+
async def test_get_syncing_status(full_node_client):
397+
with patch(
398+
"starknet_py.net.http_client.RpcHttpClient.call", AsyncMock()
399+
) as mocked_status:
400+
mocked_status.return_value = {
401+
"starting_block_num": "0xc8023",
402+
"current_block_num": "0xc9773",
403+
"highest_block_num": "0xc9773",
404+
"starting_block_hash": "0x60be3a55621597c15a53a1f83e977ca5e52e775ab2ebf572d2ebd6a8168fc88",
405+
"current_block_hash": "0x79abcb48e71524ad2e123624b0ee3d5f69f99759a23441f6f363794d0687a66",
406+
"highest_block_hash": "0x79abcb48e71524ad2e123624b0ee3d5f69f99759a23441f6f363794d0687a66",
407+
}
408+
409+
sync_status = await full_node_client.get_syncing_status()
410+
411+
assert isinstance(sync_status, SyncStatus)

starknet_py/tests/e2e/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from starknet_py.net.account.account import Account
88
from starknet_py.net.client import Client
99
from starknet_py.net.gateway_client import GatewayClient
10+
from starknet_py.net.http_client import HttpClient, HttpMethod
1011
from starknet_py.net.models import StarknetChainId
1112
from starknet_py.net.models.transaction import DeployAccount
1213
from starknet_py.net.networks import Network
@@ -85,3 +86,11 @@ def _get_random_private_key_unsafe() -> int:
8586
This is not a safe way of generating private keys and should be used only in tests.
8687
"""
8788
return random.randint(1, EC_ORDER - 1)
89+
90+
91+
async def create_empty_block(http_client: HttpClient) -> None:
92+
url = http_client.url[:-4] if http_client.url.endswith("/rpc") else http_client.url
93+
await http_client.request(
94+
address=f"{url}/create_block",
95+
http_method=HttpMethod.POST,
96+
)

0 commit comments

Comments
 (0)