From 682c20459e13a9832f45fb03bbcc47e54ea0799c Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Tue, 14 Sep 2021 16:21:09 +0200 Subject: [PATCH 1/7] add eth_sendRawTransaction support --- etheno/jsonrpc.py | 66 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/etheno/jsonrpc.py b/etheno/jsonrpc.py index 05f36b3..5e5d2fa 100644 --- a/etheno/jsonrpc.py +++ b/etheno/jsonrpc.py @@ -4,6 +4,63 @@ from .etheno import EthenoPlugin from .utils import format_hex_address +from dataclasses import asdict, dataclass +from pprint import pprint +from typing import Optional + + +# source: https://ethereum.stackexchange.com/a/83855 +import rlp +from eth_typing import HexStr +from eth_utils import keccak, to_bytes +from rlp.sedes import Binary, big_endian_int, binary +from web3 import Web3 +from web3.auto import w3 + + +class Transaction(rlp.Serializable): + fields = [ + ("nonce", big_endian_int), + ("gas_price", big_endian_int), + ("gas", big_endian_int), + ("to", Binary.fixed_length(20, allow_empty=True)), + ("value", big_endian_int), + ("data", binary), + ("v", big_endian_int), + ("r", big_endian_int), + ("s", big_endian_int), + ] + + +def hex_to_bytes(data: str) -> bytes: + return to_bytes(hexstr=HexStr(data)) + + +def decode_raw_tx(raw_tx: str): + tx = rlp.decode(hex_to_bytes(raw_tx), Transaction) + hash_tx = Web3.toHex(keccak(hex_to_bytes(raw_tx))) + from_ = w3.eth.account.recover_transaction(raw_tx) + to = w3.toChecksumAddress(tx.to) if tx.to else None + data = w3.toHex(tx.data) + r = hex(tx.r) + s = hex(tx.s) + chain_id = (tx.v - 35) // 2 if tx.v % 2 else (tx.v - 36) // 2 + return { + 'txHash': hash_tx, + 'from': from_, + 'to': to, + 'nonce': tx.nonce, + 'gas': tx.gas, + 'gasPrice': tx.gas_price, + 'value': tx.value, + 'data': data, + 'chainId': chain_id, + 'r': r, + 's': s, + 'v': tx.v + } + + class JSONExporter: def __init__(self, out_stream: Union[str, TextIO]): self._was_path = isinstance(out_stream, str) @@ -65,12 +122,15 @@ def after_post(self, post_data, result): result = result[0] if 'method' not in post_data: return - elif post_data['method'] == 'eth_sendTransaction' and 'result' in result: + elif (post_data['method'] == 'eth_sendTransaction' or post_data['method'] == 'eth_sendRawTransaction') and 'result' in result: try: transaction_hash = int(result['result'], 16) except ValueError: return - self._transactions[transaction_hash] = post_data + if post_data['method'] == 'eth_sendRawTransaction': + self._transactions[transaction_hash] = decode_raw_tx(post_data['params'][0]) + else: + self._transactions[transaction_hash] = post_data['params'][0] elif post_data['method'] == 'evm_mine': self.handle_increase_block_number() elif post_data['method'] == 'evm_increaseTime': @@ -80,7 +140,7 @@ def after_post(self, post_data, result): if transaction_hash not in self._transactions: self.logger.error(f'Received transaction receipt {result} for unknown transaction hash {post_data["params"][0]}') return - original_transaction = self._transactions[transaction_hash]['params'][0] + original_transaction = self._transactions[transaction_hash] if 'value' not in original_transaction or original_transaction['value'] is None: value = '0x0' else: From aa6bbecdeba0503891fcfa63ade7baace6ea8b54 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Wed, 15 Sep 2021 11:55:22 +0200 Subject: [PATCH 2/7] wip support eth_getTransactionByHash --- etheno/__main__.py | 5 +++-- etheno/jsonrpc.py | 12 +++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/etheno/__main__.py b/etheno/__main__.py index 747c822..2a115f9 100644 --- a/etheno/__main__.py +++ b/etheno/__main__.py @@ -179,8 +179,9 @@ def main(argv = None): ganache_accounts = ["--account=%s,0x%x" % (acct.private_key, acct.balance) for acct in accounts] - ganache_args = ganache_accounts + ['-g', str(args.gas_price), '-i', str(args.network_id)] - + # ganache_args = ganache_accounts + ['-g', str(args.gas_price), '-i', str(args.network_id)] + ganache_args = ['-g', str(args.gas_price), '-i', str(args.network_id)] + if args.ganache_args is not None: ganache_args += shlex.split(args.ganache_args) diff --git a/etheno/jsonrpc.py b/etheno/jsonrpc.py index 5e5d2fa..0830457 100644 --- a/etheno/jsonrpc.py +++ b/etheno/jsonrpc.py @@ -122,6 +122,8 @@ def after_post(self, post_data, result): result = result[0] if 'method' not in post_data: return + # print('POST DATA', post_data) + # print('RESULT', result) elif (post_data['method'] == 'eth_sendTransaction' or post_data['method'] == 'eth_sendRawTransaction') and 'result' in result: try: transaction_hash = int(result['result'], 16) @@ -135,12 +137,20 @@ def after_post(self, post_data, result): self.handle_increase_block_number() elif post_data['method'] == 'evm_increaseTime': self.handle_increase_block_timestamp(post_data['params'][0]) - elif post_data['method'] == 'eth_getTransactionReceipt': + elif post_data['method'] == 'eth_getTransactionReceipt' or post_data['method'] == 'eth_getTransactionByHash': transaction_hash = int(post_data['params'][0], 16) if transaction_hash not in self._transactions: self.logger.error(f'Received transaction receipt {result} for unknown transaction hash {post_data["params"][0]}') return original_transaction = self._transactions[transaction_hash] + if post_data['method'] == 'eth_getTransactionByHash': + print('REQ eth_getTransactionByHash', original_transaction) + print('RES eth_getTransactionByHash', result) + # we need to fetch the contractAddress if to == None, + # however, for that we need the transaction receipt!! + # now i would like to ugly hack call self.client.post() here + # but that ofcouse wont work, as there is no self.client + return # to not break down below because result['result']['contractAddress'] does not exist if 'value' not in original_transaction or original_transaction['value'] is None: value = '0x0' else: From d7473c35c350373e3c637bf1f04e1299197d43fe Mon Sep 17 00:00:00 2001 From: Evan Sultanik Date: Thu, 16 Sep 2021 17:12:31 -0400 Subject: [PATCH 3/7] get a transaction receipt for `eth_getTransactionByHash` --- etheno/jsonrpc.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/etheno/jsonrpc.py b/etheno/jsonrpc.py index 0830457..216adbd 100644 --- a/etheno/jsonrpc.py +++ b/etheno/jsonrpc.py @@ -4,10 +4,6 @@ from .etheno import EthenoPlugin from .utils import format_hex_address -from dataclasses import asdict, dataclass -from pprint import pprint -from typing import Optional - # source: https://ethereum.stackexchange.com/a/83855 import rlp @@ -144,13 +140,17 @@ def after_post(self, post_data, result): return original_transaction = self._transactions[transaction_hash] if post_data['method'] == 'eth_getTransactionByHash': - print('REQ eth_getTransactionByHash', original_transaction) - print('RES eth_getTransactionByHash', result) + self.logger.debug('REQ eth_getTransactionByHash', original_transaction) + self.logger.debug('RES eth_getTransactionByHash', result) # we need to fetch the contractAddress if to == None, - # however, for that we need the transaction receipt!! - # now i would like to ugly hack call self.client.post() here - # but that ofcouse wont work, as there is no self.client - return # to not break down below because result['result']['contractAddress'] does not exist + # however, for that we need the transaction receipt! + if self.etheno.master_client is None: + return # there is no way to figure out the contract address + receipt = self.etheno.master_client.wait_for_transaction(original_transaction) + + # TODO @rmi7: I think the transaction receipt is what you need here, + # but I am not sure what you need to do with it. + if 'value' not in original_transaction or original_transaction['value'] is None: value = '0x0' else: From 57dcab59792435d041e76cf7293e337e8f31f092 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 20 Sep 2021 14:24:18 +0200 Subject: [PATCH 4/7] remove eth_getTransactionByHash --- etheno/jsonrpc.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/etheno/jsonrpc.py b/etheno/jsonrpc.py index 216adbd..922f591 100644 --- a/etheno/jsonrpc.py +++ b/etheno/jsonrpc.py @@ -118,8 +118,6 @@ def after_post(self, post_data, result): result = result[0] if 'method' not in post_data: return - # print('POST DATA', post_data) - # print('RESULT', result) elif (post_data['method'] == 'eth_sendTransaction' or post_data['method'] == 'eth_sendRawTransaction') and 'result' in result: try: transaction_hash = int(result['result'], 16) @@ -133,24 +131,12 @@ def after_post(self, post_data, result): self.handle_increase_block_number() elif post_data['method'] == 'evm_increaseTime': self.handle_increase_block_timestamp(post_data['params'][0]) - elif post_data['method'] == 'eth_getTransactionReceipt' or post_data['method'] == 'eth_getTransactionByHash': + elif post_data['method'] == 'eth_getTransactionReceipt': transaction_hash = int(post_data['params'][0], 16) if transaction_hash not in self._transactions: self.logger.error(f'Received transaction receipt {result} for unknown transaction hash {post_data["params"][0]}') return original_transaction = self._transactions[transaction_hash] - if post_data['method'] == 'eth_getTransactionByHash': - self.logger.debug('REQ eth_getTransactionByHash', original_transaction) - self.logger.debug('RES eth_getTransactionByHash', result) - # we need to fetch the contractAddress if to == None, - # however, for that we need the transaction receipt! - if self.etheno.master_client is None: - return # there is no way to figure out the contract address - receipt = self.etheno.master_client.wait_for_transaction(original_transaction) - - # TODO @rmi7: I think the transaction receipt is what you need here, - # but I am not sure what you need to do with it. - if 'value' not in original_transaction or original_transaction['value'] is None: value = '0x0' else: From 00314ef9ad3bc57af3fb148cd1c41ec0aa5c4c7c Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 20 Sep 2021 14:31:37 +0200 Subject: [PATCH 5/7] undo ganache_args change --- etheno/__main__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/etheno/__main__.py b/etheno/__main__.py index e30de4c..2f0e24b 100644 --- a/etheno/__main__.py +++ b/etheno/__main__.py @@ -236,8 +236,7 @@ def main(argv=None): ganache_accounts = ["--account=%s,0x%x" % (acct.private_key, acct.balance) for acct in accounts] - # ganache_args = ganache_accounts + ['-g', str(args.gas_price), '-i', str(args.network_id)] - ganache_args = ['-g', str(args.gas_price), '-i', str(args.network_id)] + ganache_args = ganache_accounts + ['-g', str(args.gas_price), '-i', str(args.network_id)] if args.ganache_args is not None: ganache_args += shlex.split(args.ganache_args) From e6bec534c431fcd8441094a93bd4df681dfa9b27 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 20 Sep 2021 15:51:15 +0200 Subject: [PATCH 6/7] remove whitespace --- etheno/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etheno/__main__.py b/etheno/__main__.py index 2f0e24b..06d2030 100644 --- a/etheno/__main__.py +++ b/etheno/__main__.py @@ -237,7 +237,7 @@ def main(argv=None): ganache_accounts = ["--account=%s,0x%x" % (acct.private_key, acct.balance) for acct in accounts] ganache_args = ganache_accounts + ['-g', str(args.gas_price), '-i', str(args.network_id)] - + if args.ganache_args is not None: ganache_args += shlex.split(args.ganache_args) From 232f2d18e215f01f02bab3eb92194e3c08ca78fa Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Tue, 21 Sep 2021 10:12:59 +0200 Subject: [PATCH 7/7] remove duplicate call --- etheno/jsonrpc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/etheno/jsonrpc.py b/etheno/jsonrpc.py index 922f591..c315353 100644 --- a/etheno/jsonrpc.py +++ b/etheno/jsonrpc.py @@ -33,8 +33,9 @@ def hex_to_bytes(data: str) -> bytes: def decode_raw_tx(raw_tx: str): - tx = rlp.decode(hex_to_bytes(raw_tx), Transaction) - hash_tx = Web3.toHex(keccak(hex_to_bytes(raw_tx))) + tx_bytes = hex_to_bytes(raw_tx) + tx = rlp.decode(tx_bytes, Transaction) + hash_tx = Web3.toHex(keccak(tx_bytes)) from_ = w3.eth.account.recover_transaction(raw_tx) to = w3.toChecksumAddress(tx.to) if tx.to else None data = w3.toHex(tx.data)