diff --git a/starknet_devnet/blueprints/base.py b/starknet_devnet/blueprints/base.py index e2e3b8549..d3b86458c 100644 --- a/starknet_devnet/blueprints/base.py +++ b/starknet_devnet/blueprints/base.py @@ -5,7 +5,7 @@ from starknet_devnet.fee_token import FeeToken from starknet_devnet.state import state -from starknet_devnet.util import StarknetDevnetException +from starknet_devnet.util import StarknetDevnetException, check_valid_dump_path base = Blueprint("base", __name__) @@ -61,6 +61,11 @@ def dump(): if not dump_path: raise StarknetDevnetException(message="No path provided.", status_code=400) + try: + check_valid_dump_path(dump_path) + except ValueError as error: + raise StarknetDevnetException(status_code=400, message=str(error)) from error + state.dumper.dump(dump_path) return Response(status=200) diff --git a/starknet_devnet/server.py b/starknet_devnet/server.py index ad7978314..d05da0dda 100644 --- a/starknet_devnet/server.py +++ b/starknet_devnet/server.py @@ -2,22 +2,22 @@ A server exposing Starknet functionalities as API endpoints. """ -import sys from pickle import UnpicklingError +import sys from flask import Flask, jsonify from flask_cors import CORS import meinheld - from starkware.starkware_utils.error_handling import StarkException + from .blueprints.base import base from .blueprints.gateway import gateway from .blueprints.feeder_gateway import feeder_gateway from .blueprints.postman import postman from .blueprints.rpc import rpc -from .util import DumpOn, parse_args -from .state import state +from .util import DumpOn, check_valid_dump_path, parse_args from .starknet_wrapper import DevnetConfig +from .state import state app = Flask(__name__) CORS(app) @@ -44,6 +44,12 @@ def generate_accounts(args): def set_dump_options(args): """Assign dumping options from args to state.""" + if args.dump_path: + try: + check_valid_dump_path(args.dump_path) + except ValueError as error: + sys.exit(str(error)) + state.dumper.dump_path = args.dump_path state.dumper.dump_on = args.dump_on @@ -89,8 +95,8 @@ def main(): # starknet_wrapper.origin = origin load_dumped(args) - generate_accounts(args) set_dump_options(args) + generate_accounts(args) enable_lite_mode(args) set_start_time(args) set_gas_price(args) diff --git a/starknet_devnet/util.py b/starknet_devnet/util.py index eec14254a..b181c4340 100644 --- a/starknet_devnet/util.py +++ b/starknet_devnet/util.py @@ -2,9 +2,10 @@ Utility functions used across the project. """ +import argparse from dataclasses import dataclass from enum import Enum, auto -import argparse +import os import sys from typing import List, Dict, Union @@ -302,3 +303,15 @@ def to_bytes(value: Union[int, bytes]) -> bytes: If bytes, return the received value """ return value if isinstance(value, bytes) else value.to_bytes(32, "big") + +def check_valid_dump_path(dump_path: str): + """Checks if dump path is a directory. Raises ValueError if not.""" + + dump_path_dir = os.path.dirname(dump_path) + + if not dump_path_dir: + # dump_path is just a file, with no parent dir + return + + if not os.path.isdir(dump_path_dir): + raise ValueError(f"Invalid dump path: directory '{dump_path_dir}' not found.") diff --git a/test/test_dump.py b/test/test_dump.py index de9f8c051..cc5c53c4e 100644 --- a/test/test_dump.py +++ b/test/test_dump.py @@ -107,7 +107,7 @@ def test_load_if_no_file(): """Test loading if dump file not present.""" assert_no_dump_present(DUMP_PATH) devnet_proc = ACTIVE_DEVNET.start("--load-path", DUMP_PATH, stderr=subprocess.PIPE) - assert devnet_proc.returncode != 0 + assert devnet_proc.returncode == 1 expected_msg = f"Error: Cannot load from {DUMP_PATH}. Make sure the file exists and contains a Devnet dump.\n" assert expected_msg == devnet_proc.stderr.read().decode("utf-8") @@ -117,6 +117,26 @@ def test_dumping_if_path_not_provided(): resp = send_dump_request() assert resp.status_code == 400 +NONEXISTENT_DIR = "nonexistent-dir" + +def test_dumping_if_nonexistent_dir_via_cli(): + """Assert failure if dumping attempted via cli with a path containing a nonexistent dir""" + invalid_path = os.path.join(NONEXISTENT_DIR, DUMP_PATH) + devnet_proc = ACTIVE_DEVNET.start("--dump-path", invalid_path, stderr=subprocess.PIPE) + assert devnet_proc.returncode == 1 + + expected_msg = f"Invalid dump path: directory '{NONEXISTENT_DIR}' not found.\n" + assert expected_msg == devnet_proc.stderr.read().decode("utf-8") + +@devnet_in_background() +def test_dumping_if_nonexistent_dir_via_http(): + """Assert failure if dumping attempted via http with a path containing a nonexistent dir""" + invalid_path = os.path.join(NONEXISTENT_DIR, DUMP_PATH) + + resp = send_dump_request(dump_path=invalid_path) + assert resp.json()["message"] == f"Invalid dump path: directory '{NONEXISTENT_DIR}' not found." + assert resp.status_code == 400 + @devnet_in_background("--dump-path", DUMP_PATH) def test_dumping_if_path_provided_as_cli_option(): """Test dumping if path provided as CLI option""" @@ -207,7 +227,7 @@ def test_invalid_dump_on_option(): stderr=subprocess.PIPE ) - assert devnet_proc.returncode != 0 + assert devnet_proc.returncode == 1 expected_msg = b"Error: Invalid --dump-on option: obviously-invalid. Valid options: exit, transaction\n" assert devnet_proc.stderr.read() == expected_msg @@ -215,7 +235,7 @@ def test_dump_path_not_present_with_dump_on_present(): """Test behavior when dump-path is not present and dump-on is.""" devnet_proc = ACTIVE_DEVNET.start("--dump-on", "exit", stderr=subprocess.PIPE) - assert devnet_proc.returncode != 0 + assert devnet_proc.returncode == 1 expected_msg = b"Error: --dump-path required if --dump-on present\n" assert devnet_proc.stderr.read() == expected_msg