diff --git a/backend/database_handler/accounts_manager.py b/backend/database_handler/accounts_manager.py index 4b2838eac..9a08ea97a 100644 --- a/backend/database_handler/accounts_manager.py +++ b/backend/database_handler/accounts_manager.py @@ -1,7 +1,7 @@ # consensus/services/transactions_db_service.py from eth_account import Account -from eth_account._utils.validation import is_valid_address +from eth_utils import is_address from .models import CurrentState from backend.database_handler.errors import AccountNotFoundError @@ -38,7 +38,7 @@ def create_new_account_with_address(self, address: str): self.session.commit() def is_valid_address(self, address: str) -> bool: - return is_valid_address(address) + return is_address(address) def get_account(self, account_address: str) -> CurrentState | None: """Private method to retrieve an account from the data base""" diff --git a/backend/database_handler/transactions_processor.py b/backend/database_handler/transactions_processor.py index 163c6f3d3..2bd6b58aa 100644 --- a/backend/database_handler/transactions_processor.py +++ b/backend/database_handler/transactions_processor.py @@ -4,7 +4,7 @@ from .models import Transactions from sqlalchemy.orm import Session -from sqlalchemy import or_, and_ +from sqlalchemy import or_, and_, desc from .models import TransactionStatus from eth_utils import to_bytes, keccak, is_address @@ -366,3 +366,51 @@ def set_transaction_appeal_failed(self, transaction_hash: str, appeal_failed: in self.session.query(Transactions).filter_by(hash=transaction_hash).one() ) transaction.appeal_failed = appeal_failed + + def get_highest_timestamp(self) -> int: + transaction = ( + self.session.query(Transactions) + .order_by(desc(Transactions.timestamp_accepted)) + .first() + ) + if transaction is None: + return 0 + return transaction.timestamp_accepted + + def get_transactions_for_block( + self, block_number: int, include_full_tx: bool + ) -> dict: + transactions = ( + self.session.query(Transactions) + .filter(Transactions.timestamp_accepted == block_number) + .all() + ) + + if not transactions: + return None + + block_hash = transactions[0].hash + parent_hash = "0x" + "0" * 64 # Placeholder for parent block hash + timestamp = transactions[0].timestamp_accepted or int(time.time()) + + if include_full_tx: + transaction_data = [self._parse_transaction_data(tx) for tx in transactions] + else: + transaction_data = [tx.hash for tx in transactions] + + block_details = { + "number": hex(block_number), + "hash": block_hash, + "parentHash": parent_hash, + "nonce": "0x" + "0" * 16, + "transactions": transaction_data, + "timestamp": hex(int(timestamp)), + "miner": "0x" + "0" * 40, + "difficulty": "0x1", + "gasUsed": "0x0", + "gasLimit": "0x0", + "size": "0x0", + "extraData": "0x", + } + + return block_details diff --git a/backend/protocol_rpc/endpoints.py b/backend/protocol_rpc/endpoints.py index d1500e2e9..0f8818a1a 100644 --- a/backend/protocol_rpc/endpoints.py +++ b/backend/protocol_rpc/endpoints.py @@ -44,7 +44,7 @@ TransactionAddressFilter, TransactionsProcessor, ) -from backend.node.base import Node +from backend.node.base import Node, SIMULATOR_CHAIN_ID from backend.node.types import ExecutionMode, ExecutionResultStatus from backend.consensus.base import ConsensusAlgorithm @@ -368,7 +368,7 @@ def get_balance( def get_transaction_count( - transactions_processor: TransactionsProcessor, address: str + transactions_processor: TransactionsProcessor, address: str, block: str ) -> int: return transactions_processor.get_transaction_count(address) @@ -390,6 +390,11 @@ async def call( from_address = params["from"] if "from" in params else None data = params["data"] + if from_address is None: + return base64.b64encode(b"\x00' * 31 + b'\x01").decode( + "ascii" + ) # Return '1' as a uint256 + if from_address and not accounts_manager.is_valid_address(from_address): raise InvalidAddressError(from_address) @@ -531,6 +536,113 @@ def set_finality_window_time(consensus: ConsensusAlgorithm, time: int) -> None: consensus.set_finality_window_time(time) +def get_chain_id() -> str: + return hex(SIMULATOR_CHAIN_ID) + + +def get_net_version() -> str: + return str(SIMULATOR_CHAIN_ID) + + +def get_block_number(transactions_processor: TransactionsProcessor) -> str: + transaction_count = transactions_processor.get_highest_timestamp() + return hex(transaction_count) + + +def get_block_by_number( + transactions_processor: TransactionsProcessor, block_number: str, full_tx: bool +) -> dict | None: + try: + block_number_int = int(block_number, 16) + except ValueError: + raise JSONRPCError(f"Invalid block number format: {block_number}") + + block_details = transactions_processor.get_transactions_for_block( + block_number_int, include_full_tx=full_tx + ) + + if not block_details: + raise JSONRPCError(f"Block not found for number: {block_number}") + + return block_details + + +def get_gas_price() -> str: + gas_price_in_wei = 20 * 10**9 + return hex(gas_price_in_wei) + + +def get_transaction_receipt( + transactions_processor: TransactionsProcessor, + transaction_hash: str, +) -> dict | None: + + transaction = transactions_processor.get_transaction_by_hash(transaction_hash) + + if not transaction: + return None + + receipt = { + "transactionHash": transaction_hash, + "transactionIndex": hex(0), + "blockHash": transaction_hash, + "blockNumber": hex(transaction.get("block_number", 0)), + "from": transaction.get("from_address"), + "to": transaction.get("to_address") if transaction.get("to_address") else None, + "cumulativeGasUsed": hex(transaction.get("gas_used", 21000)), + "gasUsed": hex(transaction.get("gas_used", 21000)), + "contractAddress": ( + transaction.get("contract_address") + if transaction.get("contract_address") + else None + ), + "logs": transaction.get("logs", []), + "logsBloom": "0x" + "00" * 256, + "status": hex(1 if transaction.get("status", True) else 0), + } + + return receipt + + +def get_block_by_hash( + transactions_processor: TransactionsProcessor, + transaction_hash: str, + full_tx: bool = False, +) -> dict | None: + + transaction = transactions_processor.get_transaction_by_hash(transaction_hash) + + if not transaction: + return None + + block_details = { + "hash": transaction_hash, + "parentHash": "0x" + "00" * 32, + "number": hex(transaction.get("block_number", 0)), + "timestamp": hex(transaction.get("timestamp", 0)), + "nonce": "0x" + "00" * 8, + "transactionsRoot": "0x" + "00" * 32, + "stateRoot": "0x" + "00" * 32, + "receiptsRoot": "0x" + "00" * 32, + "miner": "0x" + "00" * 20, + "difficulty": "0x1", + "totalDifficulty": "0x1", + "size": "0x0", + "extraData": "0x", + "gasLimit": hex(transaction.get("gas_limit", 8000000)), + "gasUsed": hex(transaction.get("gas_used", 21000)), + "logsBloom": "0x" + "00" * 256, + "transactions": [], + } + + if full_tx: + block_details["transactions"].append(transaction) + else: + block_details["transactions"].append(transaction_hash) + + return block_details + + def get_contract(consensus_service: ConsensusService, contract_name: str) -> dict: """ Get contract instance by name @@ -689,3 +801,22 @@ def register_all_rpc_endpoints( partial(get_contract, consensus_service), method_name="sim_getConsensusContract", ) + register_rpc_endpoint(get_chain_id, method_name="eth_chainId") + register_rpc_endpoint(get_net_version, method_name="net_version") + register_rpc_endpoint( + partial(get_block_number, transactions_processor), + method_name="eth_blockNumber", + ) + register_rpc_endpoint( + partial(get_block_by_number, transactions_processor), + method_name="eth_getBlockByNumber", + ) + register_rpc_endpoint(get_gas_price, method_name="eth_gasPrice") + register_rpc_endpoint( + partial(get_transaction_receipt, transactions_processor), + method_name="eth_getTransactionReceipt", + ) + register_rpc_endpoint( + partial(get_block_by_hash, transactions_processor), + method_name="eth_getBlockByHash", + ) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fa1081198..937e49b44 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,7 +23,7 @@ "defu": "^6.1.4", "dexie": "^4.0.4", "floating-vue": "^5.2.2", - "genlayer-js": "^0.4.6", + "genlayer-js": "^0.5.0", "hash-sum": "^2.0.0", "jump.js": "^1.0.2", "lodash-es": "^4.17.21", @@ -5547,9 +5547,9 @@ } }, "node_modules/genlayer-js": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/genlayer-js/-/genlayer-js-0.4.7.tgz", - "integrity": "sha512-vp+7spuVaX7vflZd2q7qmaYgi5Cf7S/h4lAoVhAkFdyAsDStvhtwCdJGcZDt+U77AWxo3I1mUMXz0sk9ER3JXQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/genlayer-js/-/genlayer-js-0.5.0.tgz", + "integrity": "sha512-sW3seeKEG3TxnWbU7FzFBWMwm3oOjoktbtYg8I95uQTP74PInn7rxjYt9wea3M3VxnZnkEFu4f1bOMEIFWwAqg==", "dependencies": { "eslint-plugin-import": "^2.30.0", "typescript-parsec": "^0.3.4", diff --git a/frontend/package.json b/frontend/package.json index d453f6c3a..60f2a4a03 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,7 +35,7 @@ "defu": "^6.1.4", "dexie": "^4.0.4", "floating-vue": "^5.2.2", - "genlayer-js": "^0.4.6", + "genlayer-js": "^0.5.0", "hash-sum": "^2.0.0", "jump.js": "^1.0.2", "lodash-es": "^4.17.21", diff --git a/frontend/src/components/Simulator/AccountItem.vue b/frontend/src/components/Simulator/AccountItem.vue index 1f35cf569..bf371a925 100644 --- a/frontend/src/components/Simulator/AccountItem.vue +++ b/frontend/src/components/Simulator/AccountItem.vue @@ -1,15 +1,14 @@