diff --git a/backend/config/__init__.py b/backend/config/__init__.py new file mode 100644 index 000000000..585beface --- /dev/null +++ b/backend/config/__init__.py @@ -0,0 +1 @@ +"""Configuration package for backend services.""" diff --git a/backend/config/hardhat_config.py b/backend/config/hardhat_config.py new file mode 100644 index 000000000..02bc77341 --- /dev/null +++ b/backend/config/hardhat_config.py @@ -0,0 +1,31 @@ +"""Centralized configuration for Hardhat settings.""" + +import os +from web3 import Web3 + + +class HardhatConfig: + """Configuration class for Hardhat network settings.""" + + @staticmethod + def get_port() -> str: + """Get the Hardhat port from environment variable.""" + return os.environ.get("HARDHAT_PORT", "8545") + + @staticmethod + def get_base_url() -> str: + """Get the Hardhat base URL from environment variable.""" + return os.environ.get("HARDHAT_URL", "http://localhost") + + @staticmethod + def get_full_url() -> str: + """Get the complete Hardhat URL with port.""" + port = HardhatConfig.get_port() + url = HardhatConfig.get_base_url() + return f"{url}:{port}" + + @staticmethod + def get_web3_instance() -> Web3: + """Get a Web3 instance connected to Hardhat network.""" + hardhat_url = HardhatConfig.get_full_url() + return Web3(Web3.HTTPProvider(hardhat_url)) diff --git a/backend/database_handler/transactions_processor.py b/backend/database_handler/transactions_processor.py index d0c3bcce4..0a1f2de89 100644 --- a/backend/database_handler/transactions_processor.py +++ b/backend/database_handler/transactions_processor.py @@ -15,7 +15,7 @@ import time from backend.domain.types import TransactionType from web3 import Web3 -import os +from backend.config.hardhat_config import HardhatConfig class TransactionAddressFilter(Enum): @@ -105,10 +105,7 @@ def __init__( self.session = session # Connect to Hardhat Network - port = os.environ.get("HARDHAT_PORT") - url = os.environ.get("HARDHAT_URL") - hardhat_url = f"{url}:{port}" - self.web3 = Web3(Web3.HTTPProvider(hardhat_url)) + self.web3 = HardhatConfig.get_web3_instance() @staticmethod def _parse_transaction_data(transaction_data: Transactions) -> dict: diff --git a/backend/protocol_rpc/endpoint_generator.py b/backend/protocol_rpc/endpoint_generator.py index 14c21db10..58d1a404a 100644 --- a/backend/protocol_rpc/endpoint_generator.py +++ b/backend/protocol_rpc/endpoint_generator.py @@ -11,10 +11,10 @@ from flask_jsonrpc.exceptions import JSONRPCError from functools import partial, wraps import requests -import os import traceback from backend.protocol_rpc.aio import run_in_main_server_loop from backend.protocol_rpc.message_handler.base import MessageHandler +from backend.config.hardhat_config import HardhatConfig def get_json_rpc_method_name(function: Callable, method_name: str | None = None): @@ -67,9 +67,7 @@ def unfold(x: typing.Any): def setup_eth_method_handler(jsonrpc: JSONRPC): """Forwards eth_ methods to Hardhat if no own implementation is available""" app = jsonrpc.app - port = os.environ.get("HARDHAT_PORT") - url = os.environ.get("HARDHAT_URL") - HARDHAT_URL = f"{url}:{port}" + hardhat_url = HardhatConfig.get_full_url() @app.before_request def handle_eth_methods(): @@ -89,7 +87,7 @@ def handle_eth_methods(): try: with requests.Session() as http: result = http.post( - HARDHAT_URL, + hardhat_url, json=request_json, headers={"Content-Type": "application/json"}, ).json() diff --git a/backend/protocol_rpc/endpoints.py b/backend/protocol_rpc/endpoints.py index 33c71a8a3..1cf5c4b6b 100644 --- a/backend/protocol_rpc/endpoints.py +++ b/backend/protocol_rpc/endpoints.py @@ -9,6 +9,7 @@ from sqlalchemy import Table from sqlalchemy.orm import Session import backend.validators as validators +from backend.config.hardhat_config import HardhatConfig from backend.database_handler.contract_snapshot import ContractSnapshot from backend.database_handler.llm_providers import LLMProviderRegistry @@ -879,8 +880,39 @@ def get_gas_price() -> str: def get_gas_estimate(data: Any) -> str: - gas_price_in_wei = 30 * 10**6 - return hex(gas_price_in_wei) + """ + Estimate gas for a transaction using Hardhat service. + Falls back to a default value if the estimation fails. + """ + fallback_gas_estimate = 30 * 10**6 + + try: + web3 = HardhatConfig.get_web3_instance() + tx_params = {} + if isinstance(data, dict): + if "from" in data: + tx_params["from"] = data["from"] + if "to" in data: + tx_params["to"] = data["to"] + # Handle both 'data' and 'input' fields (some clients use 'input' instead of 'data') + if "data" in data: + tx_params["data"] = data["data"] + elif "input" in data: + tx_params["data"] = data["input"] + if "value" in data: + tx_params["value"] = ( + int(data["value"], 16) + if isinstance(data["value"], str) + else data["value"] + ) + if tx_params: + gas_estimate = web3.eth.estimate_gas(tx_params) + return hex(gas_estimate) + else: + return hex(fallback_gas_estimate) + + except Exception: + return hex(fallback_gas_estimate) def get_transaction_receipt( diff --git a/backend/rollup/consensus_service.py b/backend/rollup/consensus_service.py index 89033e4ce..e6adaacad 100644 --- a/backend/rollup/consensus_service.py +++ b/backend/rollup/consensus_service.py @@ -1,6 +1,4 @@ import json -import os -from web3 import Web3 from typing import Optional, Dict, Any from pathlib import Path from hexbytes import HexBytes @@ -9,6 +7,7 @@ from backend.rollup.default_contracts.consensus_main import ( get_default_consensus_main_contract, ) +from backend.config.hardhat_config import HardhatConfig class ConsensusService: @@ -17,10 +16,7 @@ def __init__(self): Initialize the ConsensusService class """ # Connect to Hardhat Network - port = os.environ.get("HARDHAT_PORT") - url = os.environ.get("HARDHAT_URL") - hardhat_url = f"{url}:{port}" - self.web3 = Web3(Web3.HTTPProvider(hardhat_url)) + self.web3 = HardhatConfig.get_web3_instance() self.web3_connected = self.web3.is_connected() diff --git a/tests/db-sqlalchemy/conftest.py b/tests/db-sqlalchemy/conftest.py index 247c27ecc..41e9c4df9 100644 --- a/tests/db-sqlalchemy/conftest.py +++ b/tests/db-sqlalchemy/conftest.py @@ -1,9 +1,12 @@ import os from typing import Iterable +from unittest.mock import patch, MagicMock import pytest from sqlalchemy import Engine, create_engine from sqlalchemy.orm import Session, sessionmaker +from web3 import Web3 +from web3.providers import BaseProvider from backend.database_handler.models import Base from backend.database_handler.transactions_processor import TransactionsProcessor @@ -41,4 +44,15 @@ def session(engine: Engine) -> Iterable[Session]: @pytest.fixture def transactions_processor(session: Session) -> Iterable[TransactionsProcessor]: - yield TransactionsProcessor(session) + # Create a mock Web3 instance with proper provider + mock_provider = MagicMock(spec=BaseProvider) + web3_instance = Web3(mock_provider) + web3_instance.eth = MagicMock() + web3_instance.eth.accounts = ["0x0000000000000000000000000000000000000000"] + + # Patch HardhatConfig.get_web3_instance to return our mock + with patch( + "backend.database_handler.transactions_processor.HardhatConfig.get_web3_instance", + return_value=web3_instance, + ): + yield TransactionsProcessor(session) diff --git a/tests/db-sqlalchemy/transactions_processor_test.py b/tests/db-sqlalchemy/transactions_processor_test.py index a6906fe0b..1c2f4494a 100644 --- a/tests/db-sqlalchemy/transactions_processor_test.py +++ b/tests/db-sqlalchemy/transactions_processor_test.py @@ -1,20 +1,15 @@ -from sqlalchemy.orm import Session import pytest -from unittest.mock import patch, MagicMock +from unittest.mock import patch import os import math from datetime import datetime -from web3 import Web3 -from web3.providers import BaseProvider -from backend.database_handler.chain_snapshot import ChainSnapshot -from backend.database_handler.models import Transactions from backend.database_handler.transactions_processor import TransactionStatus from backend.database_handler.transactions_processor import TransactionsProcessor @pytest.fixture(autouse=True) -def mock_env_and_web3(): +def mock_env(): with patch.dict( os.environ, { @@ -22,15 +17,8 @@ def mock_env_and_web3(): "HARDHAT_URL": "http://localhost", "HARDHAT_PRIVATE_KEY": "0x0123456789", }, - ), patch("web3.Web3.HTTPProvider"): - web3_instance = Web3(MagicMock(spec=BaseProvider)) - web3_instance.eth = MagicMock() - web3_instance.eth.accounts = ["0x0000000000000000000000000000000000000000"] - with patch( - "backend.database_handler.transactions_processor.Web3", - return_value=web3_instance, - ): - yield + ): + yield def test_transactions_processor(transactions_processor: TransactionsProcessor): diff --git a/tests/hardhat/docker-compose.yml b/tests/hardhat/docker-compose.yml index 4d253b435..96fa95381 100644 --- a/tests/hardhat/docker-compose.yml +++ b/tests/hardhat/docker-compose.yml @@ -30,4 +30,5 @@ services: hardhat: condition: service_healthy environment: - - HARDHAT_URL=http://hardhat:8545 + - HARDHAT_URL=http://hardhat + - HARDHAT_PORT=8545 diff --git a/tests/hardhat/test_hardhat.py b/tests/hardhat/test_hardhat.py index 093f7bbe3..3879d2648 100644 --- a/tests/hardhat/test_hardhat.py +++ b/tests/hardhat/test_hardhat.py @@ -1,7 +1,6 @@ -from web3 import Web3 import json from eth_account import Account -import os +from backend.config.hardhat_config import HardhatConfig def test_eth_account(): @@ -41,8 +40,7 @@ def connect_to_hardhat(): Raises: Exception: If the connection to the Hardhat network fails. """ - hardhat_url = os.environ.get("HARDHAT_URL") - web3 = Web3(Web3.HTTPProvider(hardhat_url)) + web3 = HardhatConfig.get_web3_instance() # Check connection if not web3.is_connected():