Skip to content

Commit

Permalink
feat: 764 add new rpc endpoints metamask (#765)
Browse files Browse the repository at this point in the history
* feat: new metamask rpc endpoints

* feat: adding wallet to frontend and improving backend

* fix: removing ts-ignore

* test: window ethereum coverage

* fix: precommit lint transactions processor

* fix: precommit lint endpoints

* fix: lint on vue files

* fix: fixing spaces (lint)

* fix: fixing spaces (lint)

* fix: fixing spaces (lint)

* chore: updated genlayer-js dependency

* refactor: unified account management for local and metamas + fixed account selection

* fix: changing block number from nonce to timestamp

* fix: adding constant over hardcoded chainid

* fix: docker compose duplicated items

* fix: accounts store unified structure

* fix: method signature

* fix: local accounts working

* fix transaction endpoint

Signed-off-by: Agustín Ramiro Díaz <[email protected]>

* chore: added type for GenLayerJS hook

* fix: remove metamask account on disconnect

* test: commented failing tests to allow build

* fis: account store tests

* fix: account tests

* fix: transactions tests

* fix: show sorten address

* fix: button spacing

* fix: handling error connecting metamask

* fix: disable nonce check as a workaraund for metamask not sending the nonce

* fix: set nonce manually on inserting transaction

---------

Signed-off-by: Agustín Ramiro Díaz <[email protected]>
Co-authored-by: Cristiam Da Silva <[email protected]>
Co-authored-by: Agustín Díaz <[email protected]>
Co-authored-by: Agustín Ramiro Díaz <[email protected]>
Co-authored-by: Den <[email protected]>
  • Loading branch information
5 people authored Jan 21, 2025
1 parent aabe5ea commit e7baf02
Show file tree
Hide file tree
Showing 22 changed files with 618 additions and 143 deletions.
4 changes: 2 additions & 2 deletions backend/database_handler/accounts_manager.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"""
Expand Down
63 changes: 57 additions & 6 deletions backend/database_handler/transactions_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -133,13 +133,15 @@ def insert_transaction(
) -> str:
current_nonce = self.get_transaction_count(from_address)

if nonce != current_nonce:
raise Exception(
f"Unexpected nonce. Provided: {nonce}, expected: {current_nonce}"
)
# Follow up: https://github.com/MetaMask/metamask-extension/issues/29787
# to uncomment this check
# if nonce != current_nonce:
# raise Exception(
# f"Unexpected nonce. Provided: {nonce}, expected: {current_nonce}"
# )

transaction_hash = self._generate_transaction_hash(
from_address, to_address, data, value, type, nonce
from_address, to_address, data, value, type, current_nonce
)
ghost_contract_address = None

Expand Down Expand Up @@ -369,3 +371,52 @@ 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)
.filter(Transactions.timestamp_accepted.isnot(None))
.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
133 changes: 132 additions & 1 deletion backend/protocol_rpc/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,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

Expand Down Expand Up @@ -420,6 +420,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)

Expand Down Expand Up @@ -562,6 +567,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:
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
Expand Down Expand Up @@ -720,3 +832,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",
)
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ services:
- ./hardhat/test:/app/test
- ./hardhat/hardhat.config.js:/app/hardhat.config.js
- ./hardhat/deploy:/app/deploy
- ./hardhat/scripts:/app/scripts
- hardhat_artifacts:/app/artifacts
- hardhat_cache:/app/cache
- hardhat_deployments:/app/deployments
Expand Down
18 changes: 11 additions & 7 deletions frontend/src/components/Simulator/AccountItem.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
<script setup lang="ts">
import { useAccountsStore } from '@/stores';
import { useAccountsStore, type AccountInfo } from '@/stores';
import { notify } from '@kyvg/vue3-notification';
import { PowerCircle } from 'lucide-vue-next';
import { PowerCircle, Wallet } from 'lucide-vue-next';
import { ref } from 'vue';
import CopyTextButton from '../global/CopyTextButton.vue';
import { TrashIcon, CheckCircleIcon } from '@heroicons/vue/16/solid';
import type { Account } from 'genlayer-js/types';
const store = useAccountsStore();
const setCurentAddress = () => {
store.setCurrentAccount(props.privateKey);
store.setCurrentAccount(props.account as AccountInfo);
notify({
title: 'Active account changed',
type: 'success',
Expand All @@ -18,7 +17,7 @@ const setCurentAddress = () => {
const deleteAddress = () => {
try {
store.removeAccount(props.privateKey);
store.removeAccount(props.account as AccountInfo);
notify({
title: 'Account deleted',
type: 'success',
Expand All @@ -35,8 +34,7 @@ const deleteAddress = () => {
const props = defineProps<{
active?: Boolean;
account: Account;
privateKey: `0x${string}`;
account: AccountInfo;
canDelete?: Boolean;
}>();
Expand Down Expand Up @@ -65,6 +63,12 @@ const showConfirmDelete = ref(false);
{{ account.address }}
</span>

<Wallet
v-if="account.type === 'metamask'"
class="h-4 w-4 text-orange-500"
v-tooltip="'MetaMask Account'"
/>

<div
class="flex flex-row items-center gap-1 opacity-0 group-hover:opacity-100"
>
Expand Down
Loading

0 comments on commit e7baf02

Please sign in to comment.