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

Test custom request; Edit .dockerignore #35

Merged
merged 8 commits into from
Feb 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,19 @@ jobs:
- run:
name: Test interaction with Starknet CLI (with authentication)
command: python3 -m test.test_cli_auth
- run:
name: Test endpoints directly
command: poetry run pytest test/test_endpoints.py
- run:
name: Test plugin - dockerized
command: python3 -m test.test_plugin
command: ./test/test_plugin.sh
no_output_timeout: 1m
environment:
HARDHAT_CONFIG_FILE: ../test/hardhat.config.dockerized.ts
TEST_FILE: test/quick-test.ts
- run:
name: Test plugin - venv (tests various cases in sample-test)
command: python3 -m test.test_plugin
command: ./test/test_plugin.sh
no_output_timeout: 1m
environment:
HARDHAT_CONFIG_FILE: ../test/hardhat.config.venv.ts
Expand Down
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ starknet-hardhat-example/
scripts/
Dockerfile
.dockerignore
test/
.pylintrc
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ python3 -m test.test_cli_auth

To see if Devnet can interact with the Hardhat plugin, run:
```text
python3 -m test.test_plugin
./test/test_plugin.sh
```

## Development - Build
Expand Down
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry.scripts]
starknet-devnet = "starknet_devnet.server:main"

[tool.pytest.ini_options]
markers = [
"deploy",
"invoke",
"call"
]
53 changes: 30 additions & 23 deletions starknet_devnet/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"""

import os
import json

from flask import Flask, request, jsonify, abort
from flask.wrappers import Response
Expand All @@ -16,7 +15,6 @@

from .constants import CAIRO_LANG_VERSION
from .starknet_wrapper import StarknetWrapper
from .origin import NullOrigin
from .util import custom_int, fixed_length_hex, parse_args

app = Flask(__name__)
Expand All @@ -33,16 +31,7 @@ def is_alive():
async def add_transaction():
"""Endpoint for accepting DEPLOY and INVOKE_FUNCTION transactions."""

request_dict = json.loads(request.data.decode("utf-8"))

if "signature" not in request_dict and request_dict["type"] == TransactionType.INVOKE_FUNCTION.name:
request_dict["signature"] = []

try:
transaction = Transaction.load(request_dict)
except (TypeError, ValidationError):
msg = f"Invalid tx. Be sure to use the correct compilation (json) artifact. Devnet-compatible cairo-lang version: {CAIRO_LANG_VERSION}"
abort(Response(msg, 400))
transaction = validate_transaction(request.data)

tx_type = transaction.tx_type.name

Expand All @@ -61,6 +50,17 @@ async def add_transaction():
**result_dict
})

def validate_transaction(data: bytes):
dribeiro-ShardLabs marked this conversation as resolved.
Show resolved Hide resolved
"""Ensure `data` is a valid Starknet transaction. Returns the parsed `Transaction`."""

try:
transaction = Transaction.loads(data)
except (TypeError, ValidationError) as err:
msg = f"Invalid tx: {err}\nBe sure to use the correct compilation (json) artifact. Devnet-compatible cairo-lang version: {CAIRO_LANG_VERSION}"
abort(Response(msg, 400))

return transaction

@app.route("/feeder_gateway/get_contract_addresses", methods=["GET"])
def get_contract_addresses():
"""Endpoint that returns an object containing the addresses of key system components."""
Expand All @@ -72,20 +72,26 @@ async def call_contract():
Endpoint for receiving calls (not invokes) of contract functions.
"""

request_dict = json.loads(request.data.decode("utf-8"))

if "signature" not in request_dict:
request_dict["signature"] = []
call_specifications = validate_call(request.data)

try:
call_specifications = InvokeFunction.load(request_dict)
result_dict = await starknet_wrapper.call(call_specifications)
except StarkException as err:
# code 400 would make more sense, but alpha returns 500
abort(Response(err.message, 500))

return jsonify(result_dict)

def validate_call(data: bytes):
"""Ensure `data` is valid Starknet function call. Returns an `InvokeFunction`."""

try:
call_specifications = InvokeFunction.loads(data)
except (TypeError, ValidationError) as err:
abort(Response(f"Invalid Starknet function call: {err}", 400))

return call_specifications

def _check_block_hash(request_args: MultiDict):
block_hash = request_args.get("blockHash", type=custom_int)
if block_hash is not None:
Expand Down Expand Up @@ -163,17 +169,18 @@ def get_transaction_receipt():
ret = starknet_wrapper.get_transaction_receipt(transaction_hash)
return jsonify(ret)

args = parse_args()
# Uncomment this once fork support is added
# origin = Origin(args.fork) if args.fork else NullOrigin()
origin = NullOrigin()
starknet_wrapper = StarknetWrapper(origin)
starknet_wrapper = StarknetWrapper()

def main():
"""Runs the server."""

# reduce startup logging
os.environ['WERKZEUG_RUN_MAIN'] = 'true'
os.environ["WERKZEUG_RUN_MAIN"] = "true"

args = parse_args()
# Uncomment this once fork support is added
# origin = Origin(args.fork) if args.fork else NullOrigin()
# starknet_wrapper.set_origin(origin)

app.run(host=args.host, port=args.port)

Expand Down
31 changes: 18 additions & 13 deletions starknet_devnet/starknet_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from starkware.starkware_utils.error_handling import StarkException
from starkware.starknet.services.api.feeder_gateway.block_hash import calculate_block_hash

from .origin import Origin
from .origin import NullOrigin, Origin
from .util import Choice, StarknetDevnetException, TxStatus, fixed_length_hex, DummyExecutionInfo
from .contract_wrapper import ContractWrapper
from .transaction_wrapper import TransactionWrapper, DeployTransactionWrapper, InvokeTransactionWrapper
Expand All @@ -27,8 +27,9 @@ class StarknetWrapper:
Wraps a Starknet instance and stores data to be returned by the server:
contract states, transactions, blocks, storages.
"""
def __init__(self, origin):
self.origin: Origin = origin

def __init__(self):
self.__origin: Origin = NullOrigin()
"""Origin chain that this devnet was forked from."""

self.__address2contract_wrapper: Dict[int, ContractWrapper] = {}
Expand All @@ -37,7 +38,7 @@ def __init__(self, origin):
self.__transaction_wrappers: Dict[int, TransactionWrapper] = {}
"""Maps transaction hash to transaction wrapper."""

self.__hash2block = {}
self.__hash2block: Dict[int, dict] = {}
"""Maps block hash to block."""

self.__num2block: Dict[int, Dict] = {}
Expand Down Expand Up @@ -94,6 +95,10 @@ def __get_contract_wrapper(self, address: int) -> ContractWrapper:

return self.__address2contract_wrapper[address]

def set_origin(self, origin: Origin):
"""Set the origin chain."""
self.__origin = origin

async def deploy(self, transaction: Transaction):
"""
Deploys the contract specified with `transaction`.
Expand Down Expand Up @@ -152,7 +157,7 @@ async def invoke(self, transaction: InvokeFunction):
error_message = err.message
status = TxStatus.REJECTED
execution_info = DummyExecutionInfo()
adapted_result = {}
adapted_result = []

await self.__store_transaction(
internal_tx=invoke_transaction,
Expand Down Expand Up @@ -202,7 +207,7 @@ def get_transaction_status(self, transaction_hash: str):

return ret

return self.origin.get_transaction_status(transaction_hash)
return self.__origin.get_transaction_status(transaction_hash)

def get_transaction(self, transaction_hash: str):
"""Returns the transaction identified by `transaction_hash`."""
Expand All @@ -211,7 +216,7 @@ def get_transaction(self, transaction_hash: str):
if tx_hash_int in self.__transaction_wrappers:
return self.__transaction_wrappers[tx_hash_int].transaction

return self.origin.get_transaction(transaction_hash)
return self.__origin.get_transaction(transaction_hash)

def get_transaction_receipt(self, transaction_hash: str):
"""Returns the transaction receipt of the transaction identified by `transaction_hash`."""
Expand All @@ -228,7 +233,7 @@ def get_transaction_receipt(self, transaction_hash: str):

def get_number_of_blocks(self) -> int:
"""Returns the number of blocks stored so far."""
return len(self.__num2block) + self.origin.get_number_of_blocks()
return len(self.__num2block) + self.__origin.get_number_of_blocks()

async def __generate_block(self, tx_wrapper: TransactionWrapper):
"""
Expand Down Expand Up @@ -285,14 +290,14 @@ def get_block_by_hash(self, block_hash: str):
block_hash_int = int(block_hash, 16)
if block_hash_int in self.__hash2block:
return self.__hash2block[block_hash_int]
return self.origin.get_block_by_hash(block_hash=block_hash)
return self.__origin.get_block_by_hash(block_hash=block_hash)

def get_block_by_number(self, block_number: int):
"""Returns the block whose block_number is provided"""
if block_number is None:
if self.__num2block:
return self.__get_last_block()
return self.origin.get_block_by_number(block_number)
return self.__origin.get_block_by_number(block_number)

if block_number < 0:
message = f"Block number must be a non-negative integer; got: {block_number}."
Expand All @@ -305,7 +310,7 @@ def get_block_by_number(self, block_number: int):
if block_number in self.__num2block:
return self.__num2block[block_number]

return self.origin.get_block_by_number(block_number)
return self.__origin.get_block_by_number(block_number)

async def __store_transaction(self, internal_tx: InternalTransaction, status: TxStatus,
execution_info: StarknetTransactionExecutionInfo, error_message: str=None
Expand Down Expand Up @@ -333,7 +338,7 @@ def get_code(self, contract_address: int) -> dict:
if self.__is_contract_deployed(contract_address):
contract_wrapper = self.__get_contract_wrapper(contract_address)
return contract_wrapper.code
return self.origin.get_code(contract_address)
return self.__origin.get_code(contract_address)

async def get_storage_at(self, contract_address: int, key: int) -> str:
"""
Expand All @@ -346,4 +351,4 @@ async def get_storage_at(self, contract_address: int, key: int) -> str:
state = contract_states[contract_address]
if key in state.storage_updates:
return hex(state.storage_updates[key].value)
return self.origin.get_storage_at(self, contract_address, key)
return self.__origin.get_storage_at(self, contract_address, key)
3 changes: 1 addition & 2 deletions starknet_devnet/transaction_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ def __init__(
self.transaction_hash = tx_details.transaction_hash

events = []

if hasattr(execution_info, 'raw_events'):
if hasattr(execution_info, "raw_events"):
events = execution_info.raw_events

self.transaction = {
Expand Down
6 changes: 6 additions & 0 deletions test/call.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"calldata": [],
"contract_address": "0x256e835bb26d9acae43b747cb0e04124907608311137185aef65559ee5f61b9",
"signature": [],
"entry_point_selector": "0x39e11d48192e4333233c7eb19d10ad67c362bb28580c604d67884c85da39695"
}
Loading