Skip to content
This repository has been archived by the owner on Dec 15, 2023. It is now read-only.

Commit

Permalink
Support JSON-RPC write API (#186)
Browse files Browse the repository at this point in the history
  • Loading branch information
drknzz authored Jul 22, 2022
1 parent 7cbe273 commit d535050
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ If you don't specify the `HOST` part, the server will indeed be available on all

## JSON-RPC API

Devnet also partially supports JSON-RPC API (v0.15.0: [specifications](https://github.com/starkware-libs/starknet-specs/blob/606c21e06be92ea1543fd0134b7f98df622c2fbf/api/starknet_api_openrpc.json)). It can be reached under `/rpc`. For an example:
Devnet also partially supports JSON-RPC API (v0.15.0: [specifications](https://github.com/starkware-libs/starknet-specs/blob/606c21e06be92ea1543fd0134b7f98df622c2fbf/api/starknet_api_openrpc.json)) and WRITE API (v0.3.0: [specifications](https://github.com/starkware-libs/starknet-specs/blob/4c31d6f9f842028ca8cfd073ec8d0d5089b087c4/api/starknet_write_api.json)). It can be reached under `/rpc`. For an example:

```
POST /rpc
Expand Down
46 changes: 33 additions & 13 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ meinheld = "~1.0.2"
Werkzeug = "~2.0.3"
cloudpickle = "~2.1.0"
crypto-cpp-py = "~1.0.4"
marshmallow = "~3.17.0"
typing-extensions = "~4.3.0"

[tool.poetry.dev-dependencies]
pylint = "~2.12.2"
Expand Down
127 changes: 117 additions & 10 deletions starknet_devnet/blueprints/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@
RPC routes
rpc version: 0.15.0
"""
from __future__ import annotations
# pylint: disable=too-many-lines

from __future__ import annotations
import dataclasses
import json
from typing import Callable, Union, List, Tuple, Optional, Any

from typing import Callable, Union, List, Tuple, Optional, Any
from typing_extensions import TypedDict

from flask import Blueprint, request
from marshmallow.exceptions import MarshmallowError

from starkware.starknet.services.api.contract_class import ContractClass
from starkware.starkware_utils.error_handling import StarkException
from starkware.starknet.services.api.gateway.transaction import InvokeFunction
from starkware.starknet.services.api.gateway.transaction_utils import compress_program
from starkware.starknet.definitions import constants
from starkware.starknet.services.api.gateway.transaction import (
DECLARE_SENDER_ADDRESS,
Declare,
Deploy,
InvokeFunction,
)
from starkware.starknet.services.api.gateway.transaction_utils import compress_program, decompress_program
from starkware.starknet.services.api.feeder_gateway.response_objects import (
StarknetBlock,
InvokeSpecificInfo,
Expand All @@ -34,6 +42,7 @@

PROTOCOL_VERSION = "0.15.0"


@rpc.route("", methods=["POST"])
async def base_route():
"""
Expand Down Expand Up @@ -330,6 +339,79 @@ async def get_events():
raise NotImplementedError()


async def add_invoke_transaction(function_invocation: dict, signature: List[str], max_fee: str, version: str) -> dict:
"""
Submit a new transaction to be added to the chain
"""
invoke_function = InvokeFunction(
contract_address=int(function_invocation["contract_address"], 16),
entry_point_selector=int(function_invocation["entry_point_selector"], 16),
calldata=[int(data, 16) for data in function_invocation["calldata"]],
max_fee=int(max_fee, 16),
version=int(version, 16),
signature=[int(data, 16) for data in signature],
)

_, transaction_hash, _ = await state.starknet_wrapper.invoke(invoke_function=invoke_function)
return RpcInvokeTransactionResult(
transaction_hash=rpc_felt(transaction_hash),
)


async def add_declare_transaction(contract_class: RpcContractClass, version: str) -> dict:
"""
Submit a new class declaration transaction
"""
try:
decompressed_program = decompress_program({"contract_class": contract_class}, False)["contract_class"]
contract_definition = ContractClass.load(decompressed_program)

# Replace None with [] in abi key to avoid Missing Abi exception
contract_definition = dataclasses.replace(contract_definition, abi=[])
except (StarkException, TypeError, MarshmallowError) as ex:
raise RpcError(code=50, message="Invalid contract class") from ex

declare_transaction = Declare(
contract_class=contract_definition,
version=version,
sender_address=DECLARE_SENDER_ADDRESS,
max_fee=0,
signature=[],
nonce=0,
)

class_hash, transaction_hash = await state.starknet_wrapper.declare(declare_transaction=declare_transaction)
return RpcDeclareTransactionResult(
transaction_hash=rpc_felt(transaction_hash),
class_hash=rpc_felt(class_hash),
)


async def add_deploy_transaction(contract_address_salt: str, constructor_calldata: List[str], contract_definition: RpcContractClass) -> dict:
"""
Submit a new deploy contract transaction
"""
try:
decompressed_program = decompress_program({"contract_definition": contract_definition}, False)["contract_definition"]
contract_class = ContractClass.load(decompressed_program)
contract_class = dataclasses.replace(contract_class, abi=[])
except (StarkException, TypeError, MarshmallowError) as ex:
raise RpcError(code=50, message="Invalid contract class") from ex

deploy_transaction = Deploy(
contract_address_salt=contract_address_salt,
constructor_calldata=constructor_calldata,
contract_definition=contract_class,
version=constants.TRANSACTION_VERSION,
)

contract_address, transaction_hash = await state.starknet_wrapper.deploy(deploy_transaction=deploy_transaction)
return RpcDeployTransactionResult(
transaction_hash=rpc_felt(transaction_hash),
contract_address=rpc_felt(contract_address),
)


def make_invoke_function(request_body: dict) -> InvokeFunction:
"""
Convert RPC request to internal InvokeFunction
Expand Down Expand Up @@ -586,7 +668,7 @@ class RpcInvokeTransaction(TypedDict):

class RpcDeclareTransaction(TypedDict):
"""
TypedDcit for rpc declare transaction
TypedDict for rpc declare transaction
"""
contract_class: RpcContractClass
sender_address: str
Expand All @@ -597,6 +679,29 @@ class RpcDeclareTransaction(TypedDict):
signature: List[str]


class RpcInvokeTransactionResult(TypedDict):
"""
TypedDict for rpc invoke transaction result
"""
transaction_hash: str


class RpcDeclareTransactionResult(TypedDict):
"""
TypedDict for rpc declare transaction result
"""
transaction_hash: str
class_hash: str


class RpcDeployTransactionResult(TypedDict):
"""
TypedDict for rpc deploy transaction result
"""
transaction_hash: str
contract_address: str


def rpc_invoke_transaction(transaction: InvokeSpecificInfo) -> RpcInvokeTransaction:
"""
Convert gateway invoke transaction to rpc format
Expand Down Expand Up @@ -772,7 +877,7 @@ def events() -> List[Event]:

def rpc_declare_receipt(txr) -> RpcDeclareReceipt:
"""
Conver rpc declare transaction receipt to rpc format
Convert rpc declare transaction receipt to rpc format
"""
base_receipt = rpc_base_transaction_receipt(txr)
receipt: RpcDeclareReceipt = {
Expand All @@ -786,7 +891,7 @@ def rpc_declare_receipt(txr) -> RpcDeclareReceipt:

def rpc_deploy_receipt(txr) -> RpcBaseTransactionReceipt:
"""
Conver rpc deploy transaction receipt to rpc format
Convert rpc deploy transaction receipt to rpc format
"""
return rpc_base_transaction_receipt(txr)

Expand Down Expand Up @@ -905,9 +1010,11 @@ def parse_body(body: dict) -> Tuple[Callable, Union[List, dict], int]:
"getClassHashAt": get_class_hash_at,
"getClassAt": get_class_at,
"estimateFee": estimate_fee,
"addInvokeTransaction": add_invoke_transaction,
"addDeclareTransaction": add_declare_transaction,
"addDeployTransaction": add_deploy_transaction,
}

method_name = body["method"].lstrip("starknet_")
method_name = body["method"].replace("starknet_", "")
args: Union[List, dict] = body["params"]
message_id = body["id"]

Expand Down
24 changes: 24 additions & 0 deletions test/rpc/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,27 @@ def fixture_declare_info() -> dict:
resp = send_transaction(declare_tx)
declare_info = json.loads(resp.data.decode("utf-8"))
return {**declare_info, **declare_tx}


@pytest.fixture(name="invoke_content", scope="module")
def fixture_invoke_content() -> dict:
"""
Invoke content JSON object
"""
return json.loads(INVOKE_CONTENT)


@pytest.fixture(name="deploy_content", scope="module")
def fixture_deploy_content() -> dict:
"""
Deploy content JSON object
"""
return json.loads(DEPLOY_CONTENT)


@pytest.fixture(name="declare_content", scope="module")
def fixture_declare_content() -> dict:
"""
Declare content JSON object
"""
return json.loads(DECLARE_CONTENT)
Loading

0 comments on commit d535050

Please sign in to comment.