diff --git a/docs/web3.eth.rst b/docs/web3.eth.rst index d9a6ae9761..dd62eac425 100644 --- a/docs/web3.eth.rst +++ b/docs/web3.eth.rst @@ -282,7 +282,7 @@ The following methods are available on the ``web3.eth`` namespace. 'gasLimit': '0x2fefd8', 'gasUsed': '0x0', 'hash': '0xc78c35720d930f9ef34b4e6fb9d02ffec936f9b02a8f0fa858456e4afd4d5614', - 'logsBloom':'0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, + 'logsBloom':'0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'miner': '0xbe4532e1b1db5c913cf553be76180c1777055403', 'mixHash': '0x041e14603f35a82f6023802fec96ef760433292434a39787514f140950597e5e', 'nonce': '0x5d2b7e3f1af09995', @@ -489,6 +489,27 @@ The following methods are available on the ``web3.eth`` namespace. '0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331' +.. py:method:: Eth.signTransaction() + + * Delegates to ``eth_signTransaction`` RPC Method. + + Returns a transaction that's been signed by the node's private key, but not yet submitted. + The signed tx can be submitted with `Eth.sendRawTransaction`` + + .. code-block:: python + + >>> signed_txn = w3.eth.signTransaction(dict( + nonce=w3.eth.getTransactionCount(w3.eth.coinbase), + gasPrice=w3.eth.gasPrice, + gas=100000, + to='0xd3cda913deb6f67967b99d67acdfa1712c293601', + value=1, + data=b'', + ) + ) + b"\xf8d\x80\x85\x040\xe24\x00\x82R\x08\x94\xdcTM\x1a\xa8\x8f\xf8\xbb\xd2\xf2\xae\xc7T\xb1\xf1\xe9\x9e\x18\x12\xfd\x01\x80\x1b\xa0\x11\r\x8f\xee\x1d\xe5=\xf0\x87\x0en\xb5\x99\xed;\xf6\x8f\xb3\xf1\xe6,\x82\xdf\xe5\x97lF|\x97%;\x15\xa04P\xb7=*\xef \t\xf0&\xbc\xbf\tz%z\xe7\xa3~\xb5\xd3\xb7=\xc0v\n\xef\xad+\x98\xe3'" # noqa: E501 + + .. py:method:: Eth.sendRawTransaction(raw_transaction) * Delegates to ``eth_sendRawTransaction`` RPC Method diff --git a/tests/integration/go_ethereum/common.py b/tests/integration/go_ethereum/common.py index c41a096e07..4aa0fc1163 100644 --- a/tests/integration/go_ethereum/common.py +++ b/tests/integration/go_ethereum/common.py @@ -8,6 +8,8 @@ Web3ModuleTest, ) +GETH_SIGNED_TX = b'\xf8j\x80\x85\x040\xe24\x00\x82R\x08\x94\xdcTM\x1a\xa8\x8f\xf8\xbb\xd2\xf2\xae\xc7T\xb1\xf1\xe9\x9e\x18\x12\xfd\x01\x80\x86\xee\xca\xc4f\xe1\x16\xa0\xbb7^\x1f\xf0\x03(P\x07|\x053Q\xd3M\xf1\x83\xe9\xdcp\xdc\x02\xb4\xe7`\x85\xcd\x84\xdb\xb4\xd0\xaa\xa07\x8cl\xd7\xa6R\x01\xfaW\x0e\x0f\xc1_$\xdf`\x8dO\x18\x1dC\xbc\x87\x8fud\xd2R*W\xfd4' # noqa: E501 + class GoEthereumTest(Web3ModuleTest): def _check_web3_clientVersion(self, client_version): @@ -59,6 +61,9 @@ def test_eth_estimateGas_with_block(self, web3, unlocked_account_dual_type ) + def test_eth_signTransaction(self, web3, unlocked_account): + super().test_eth_signTransaction(web3, unlocked_account, GETH_SIGNED_TX) + class GoEthereumVersionModuleTest(VersionModuleTest): pass diff --git a/tests/integration/test_ethereum_tester.py b/tests/integration/test_ethereum_tester.py index dfaa228749..b6a529b2d6 100644 --- a/tests/integration/test_ethereum_tester.py +++ b/tests/integration/test_ethereum_tester.py @@ -216,6 +216,7 @@ def func_wrapper(self, eth_tester, *args, **kwargs): class TestEthereumTesterEthModule(EthModuleTest): test_eth_sign = not_implemented(EthModuleTest.test_eth_sign, ValueError) + test_eth_signTransaction = not_implemented(EthModuleTest.test_eth_signTransaction, ValueError) @disable_auto_mine def test_eth_getTransactionReceipt_unmined(self, eth_tester, web3, unlocked_account): diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py index 91eeb8d9ff..336e4b2217 100644 --- a/web3/_utils/module_testing/eth_module.py +++ b/web3/_utils/module_testing/eth_module.py @@ -196,6 +196,28 @@ def test_eth_sign(self, web3, unlocked_account_dual_type): ) assert new_signature != signature + def test_eth_signTransaction(self, web3, unlocked_account, geth_signed_tx=None): + txn_params = { + 'from': unlocked_account, + 'to': unlocked_account, + 'value': 1, + 'gas': 21000, + 'gasPrice': web3.eth.gasPrice, + 'nonce': 0, + } + COINBASE_PK = '0x58d23b55bc9cdce1f18c2500f40ff4ab7245df9a89505e9b1fa4851f623d241d' + result = web3.eth.signTransaction(txn_params) + actual = web3.eth.account.signTransaction(txn_params, COINBASE_PK) + if geth_signed_tx: + assert result['raw'] == geth_signed_tx + else: + assert result['raw'] == actual.rawTransaction + assert result['tx']['to'] == txn_params['to'] + assert result['tx']['value'] == txn_params['value'] + assert result['tx']['gas'] == txn_params['gas'] + assert result['tx']['gasPrice'] == txn_params['gasPrice'] + assert result['tx']['nonce'] == txn_params['nonce'] + def test_eth_sendTransaction_addr_checksum_required(self, web3, unlocked_account): non_checksum_addr = unlocked_account.lower() txn_params = { diff --git a/web3/_utils/rpc_abi.py b/web3/_utils/rpc_abi.py index 40eccb0bff..17fc06c678 100644 --- a/web3/_utils/rpc_abi.py +++ b/web3/_utils/rpc_abi.py @@ -50,6 +50,7 @@ 'eth_newFilter': FILTER_PARAMS_ABIS, 'eth_sendRawTransaction': ['bytes'], 'eth_sendTransaction': TRANSACTION_PARAMS_ABIS, + 'eth_signTransaction': TRANSACTION_PARAMS_ABIS, 'eth_sign': ['address', 'bytes'], # personal 'personal_sendTransaction': TRANSACTION_PARAMS_ABIS, diff --git a/web3/eth.py b/web3/eth.py index 4475d7a1a4..e7047f163c 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -311,6 +311,11 @@ def sign(self, account, data=None, hexstr=None, text=None): "eth_sign", [account, message_hex], ) + def signTransaction(self, transaction): + return self.web3.manager.request_blocking( + "eth_signTransaction", [transaction], + ) + @apply_to_return_value(HexBytes) def call(self, transaction, block_identifier=None): # TODO: move to middleware diff --git a/web3/middleware/pythonic.py b/web3/middleware/pythonic.py index 726d206959..7296c0a2d0 100644 --- a/web3/middleware/pythonic.py +++ b/web3/middleware/pythonic.py @@ -109,6 +109,15 @@ def to_hexbytes(num_bytes, val, variable_length=False): transaction_formatter = apply_formatters_to_dict(TRANSACTION_FORMATTERS) +SIGNED_TX_FORMATTER = { + 'raw': HexBytes, + 'tx': transaction_formatter, +} + + +signed_tx_formatter = apply_formatters_to_dict(SIGNED_TX_FORMATTER) + + WHISPER_LOG_FORMATTERS = { 'sig': to_hexbytes(130), 'topic': to_hexbytes(8), @@ -344,6 +353,7 @@ def to_hexbytes(num_bytes, val, variable_length=False): ), 'eth_sendRawTransaction': to_hexbytes(32), 'eth_sendTransaction': to_hexbytes(32), + 'eth_signTransaction': apply_formatter_if(is_not_null, signed_tx_formatter), 'eth_sign': HexBytes, 'eth_syncing': apply_formatter_if(is_not_false, syncing_formatter), # personal diff --git a/web3/providers/eth_tester/defaults.py b/web3/providers/eth_tester/defaults.py index 92345ba4f9..bdf7397498 100644 --- a/web3/providers/eth_tester/defaults.py +++ b/web3/providers/eth_tester/defaults.py @@ -201,6 +201,7 @@ def personal_send_transaction(eth_tester, params): )), 'getCode': call_eth_tester('get_code'), 'sign': not_implemented, + 'signTransaction': not_implemented, 'sendTransaction': call_eth_tester('send_transaction'), 'sendRawTransaction': call_eth_tester('send_raw_transaction'), 'call': call_eth_tester('call'), # TODO: untested