From 93ffe5fd708c289276f79aba58b272e220129bc4 Mon Sep 17 00:00:00 2001 From: DB Date: Sat, 8 Jan 2022 21:01:27 -0500 Subject: [PATCH] Add Async Geth Personal module --- docs/providers.rst | 12 +- newsfragments/2299.feature.rst | 1 + .../go_ethereum/test_goethereum_http.py | 15 +- web3/_utils/module_testing/__init__.py | 2 +- ...dule.py => go_ethereum_personal_module.py} | 81 +++++++++ web3/geth.py | 166 ++++++++++++++++-- 6 files changed, 253 insertions(+), 24 deletions(-) create mode 100644 newsfragments/2299.feature.rst rename web3/_utils/module_testing/{personal_module.py => go_ethereum_personal_module.py} (76%) diff --git a/docs/providers.rst b/docs/providers.rst index fe0a5be23f..736e748f0d 100644 --- a/docs/providers.rst +++ b/docs/providers.rst @@ -392,7 +392,8 @@ AsyncHTTPProvider ... modules={'eth': (AsyncEth,), ... 'net': (AsyncNet,), ... 'geth': (Geth, - ... {'txpool': (AsyncGethTxPool,)}) + ... {'txpool': (AsyncGethTxPool,), + ... 'personal': (AsyncGethPersonal,)}) ... }, ... middlewares=[]) # See supported middleware section below for middleware options @@ -438,6 +439,15 @@ Net Geth **** +- :meth:`web3.geth.personal.ec_recover()` +- :meth:`web3.geth.personal.import_raw_key() ` +- :meth:`web3.geth.personal.list_accounts() ` +- :meth:`web3.geth.personal.list_wallets() ` +- :meth:`web3.geth.personal.lock_account() ` +- :meth:`web3.geth.personal.new_account() ` +- :meth:`web3.geth.personal.send_transaction() ` +- :meth:`web3.geth.personal.sign()` +- :meth:`web3.geth.personal.unlock_account() ` - :meth:`web3.geth.txpool.inspect() ` - :meth:`web3.geth.txpool.content() ` - :meth:`web3.geth.txpool.status() ` diff --git a/newsfragments/2299.feature.rst b/newsfragments/2299.feature.rst new file mode 100644 index 0000000000..025051f606 --- /dev/null +++ b/newsfragments/2299.feature.rst @@ -0,0 +1 @@ +Added Async functions for Geth Personal module diff --git a/tests/integration/go_ethereum/test_goethereum_http.py b/tests/integration/go_ethereum/test_goethereum_http.py index 6c14cf5787..90e0b7313e 100644 --- a/tests/integration/go_ethereum/test_goethereum_http.py +++ b/tests/integration/go_ethereum/test_goethereum_http.py @@ -4,10 +4,14 @@ get_open_port, ) from web3 import Web3 +from web3._utils.module_testing.go_ethereum_personal_module import ( + GoEthereumAsyncPersonalModuleTest, +) from web3.eth import ( AsyncEth, ) from web3.geth import ( + AsyncGethPersonal, AsyncGethTxPool, Geth, ) @@ -94,10 +98,11 @@ async def async_w3(geth_process, endpoint_uri): async_gas_price_strategy_middleware, async_buffered_gas_estimate_middleware ], - modules={'eth': (AsyncEth,), - 'async_net': (AsyncNet,), + modules={'eth': AsyncEth, + 'async_net': AsyncNet, 'geth': (Geth, - {'txpool': (AsyncGethTxPool,)} + {'txpool': (AsyncGethTxPool,), + 'personal': (AsyncGethPersonal,)} ) } ) @@ -144,6 +149,10 @@ class TestGoEthereumPersonalModuleTest(GoEthereumPersonalModuleTest): pass +class TestGoEthereumAsyncPersonalModuleTest(GoEthereumAsyncPersonalModuleTest): + pass + + class TestGoEthereumAsyncEthModuleTest(GoEthereumAsyncEthModuleTest): pass diff --git a/web3/_utils/module_testing/__init__.py b/web3/_utils/module_testing/__init__.py index 57b52501a1..6d5cdfc5a4 100644 --- a/web3/_utils/module_testing/__init__.py +++ b/web3/_utils/module_testing/__init__.py @@ -13,7 +13,7 @@ AsyncNetModuleTest, NetModuleTest, ) -from .personal_module import ( # noqa: F401 +from .go_ethereum_personal_module import ( # noqa: F401 GoEthereumPersonalModuleTest, ) from .version_module import ( # noqa: F401 diff --git a/web3/_utils/module_testing/personal_module.py b/web3/_utils/module_testing/go_ethereum_personal_module.py similarity index 76% rename from web3/_utils/module_testing/personal_module.py rename to web3/_utils/module_testing/go_ethereum_personal_module.py index b21b5a839f..e39a1b177c 100644 --- a/web3/_utils/module_testing/personal_module.py +++ b/web3/_utils/module_testing/go_ethereum_personal_module.py @@ -21,6 +21,9 @@ from web3 import ( constants, ) +from web3.datastructures import ( + AttributeDict, +) from web3.types import ( # noqa: F401 TxParams, Wei, @@ -31,6 +34,7 @@ PRIVATE_KEY_HEX = '0x56ebb41875ceedd42e395f730e03b5c44989393c9f0484ee6bc05f933673458f' SECOND_PRIVATE_KEY_HEX = '0x56ebb41875ceedd42e395f730e03b5c44989393c9f0484ee6bc05f9336712345' +THIRD_PRIVATE_KEY_HEX = '0x56ebb41875ceedd42e395f730e03b5c44989393c9f0484ee6bc05f9336754321' PASSWORD = 'web3-testing' ADDRESS = '0x844B417c0C58B02c2224306047B9fb0D3264fE8c' SECOND_ADDRESS = '0xB96b6B21053e67BA59907E252D990C71742c41B8' @@ -343,3 +347,80 @@ def test_personal_sign_typed_data_deprecated( ) assert signature == expected_signature assert len(signature) == 32 + 32 + 1 + + +class GoEthereumAsyncPersonalModuleTest: + + @pytest.mark.asyncio + async def test_async_sign_and_ec_recover(self, + async_w3: "Web3", + unlockable_account_dual_type: ChecksumAddress, + unlockable_account_pw: str) -> None: + message = "This is a test" + signature = await async_w3.geth.personal.sign(message, # type: ignore + unlockable_account_dual_type, + unlockable_account_pw) + address = await async_w3.geth.personal.ec_recover(message, signature) # type: ignore + assert is_same_address(unlockable_account_dual_type, address) + + @pytest.mark.asyncio + async def test_async_import_key(self, async_w3: "Web3") -> None: + address = await async_w3.geth.personal.import_raw_key(THIRD_PRIVATE_KEY_HEX, # type: ignore + "Testing") + assert address is not None + + @pytest.mark.asyncio + async def test_async_list_accounts(self, async_w3: "Web3") -> None: + accounts = await async_w3.geth.personal.list_accounts() # type: ignore + assert len(accounts) > 0 + + @pytest.mark.asyncio + async def test_async_list_wallets(self, async_w3: "Web3") -> None: + wallets = await async_w3.geth.personal.list_wallets() # type: ignore + assert isinstance(wallets[0], AttributeDict) + + @pytest.mark.asyncio + async def test_async_new_account(self, async_w3: "Web3") -> None: + passphrase = "Create New Account" + account = await async_w3.geth.personal.new_account(passphrase) # type: ignore + assert is_checksum_address(account) + + @pytest.mark.asyncio + async def test_async_unlock_lock_account(self, + async_w3: "Web3", + unlockable_account_dual_type: ChecksumAddress, + unlockable_account_pw: str) -> None: + unlocked = await async_w3.geth.personal.unlock_account( # type: ignore + unlockable_account_dual_type, + unlockable_account_pw) + assert unlocked is True + locked = await async_w3.geth.personal.lock_account( # type: ignore + unlockable_account_dual_type) + assert locked is True + + @pytest.mark.asyncio + async def test_async_send_transaction(self, + async_w3: "Web3", + unlockable_account_dual_type: ChecksumAddress, + unlockable_account_pw: str) -> None: + tx_params = TxParams() + tx_params["to"] = unlockable_account_dual_type + tx_params["from"] = unlockable_account_dual_type + tx_params["value"] = Wei(123) + response = await async_w3.geth.personal.send_transaction( # type: ignore + tx_params, + unlockable_account_pw) + assert response is not None + + @pytest.mark.xfail(reason="personal_signTypedData JSON RPC call has not been released in geth") + @pytest.mark.asyncio + async def test_async_sign_typed_data(self, + async_w3: "Web3", + unlockable_account_dual_type: ChecksumAddress, + unlockable_account_pw: str) -> None: + message = {"message": "This is a test"} + signature = await async_w3.geth.personal.sign_typed_data(message, # type: ignore + unlockable_account_dual_type, + unlockable_account_pw) + address = await async_w3.geth.personal.ec_recover(message, signature) # type: ignore + assert is_same_address(unlockable_account_dual_type, address) diff --git a/web3/geth.py b/web3/geth.py index 478e0a141b..2320607078 100644 --- a/web3/geth.py +++ b/web3/geth.py @@ -1,6 +1,19 @@ from typing import ( Any, Awaitable, + Dict, + List, + Optional, +) + +from eth_typing.encoding import ( + HexStr, +) +from eth_typing.evm import ( + ChecksumAddress, +) +from hexbytes.main import ( + HexBytes, ) from web3._utils.admin import ( @@ -64,35 +77,150 @@ Module, ) from web3.types import ( + GethWallet, + TxParams, TxPoolContent, TxPoolInspect, TxPoolStatus, ) -class GethPersonal(Module): +class BaseGethPersonal(Module): """ https://github.com/ethereum/go-ethereum/wiki/management-apis#personal """ - ec_recover = ec_recover - import_raw_key = import_raw_key - list_accounts = list_accounts - list_wallets = list_wallets - lock_account = lock_account - new_account = new_account - send_transaction = send_transaction - sign = sign - sign_typed_data = sign_typed_data - unlock_account = unlock_account + _ec_recover = ec_recover + _import_raw_key = import_raw_key + _list_accounts = list_accounts + _list_wallets = list_wallets + _lock_account = lock_account + _new_account = new_account + _send_transaction = send_transaction + _sign = sign + _sign_typed_data = sign_typed_data + _unlock_account = unlock_account # deprecated - ecRecover = ecRecover - importRawKey = importRawKey - listAccounts = listAccounts - lockAccount = lockAccount - newAccount = newAccount - sendTransaction = sendTransaction - signTypedData = signTypedData - unlockAccount = unlockAccount + _ecRecover = ecRecover + _importRawKey = importRawKey + _listAccounts = listAccounts + _lockAccount = lockAccount + _newAccount = newAccount + _sendTransaction = sendTransaction + _signTypedData = signTypedData + _unlockAccount = unlockAccount + + +class GethPersonal(BaseGethPersonal): + is_async = False + + def ec_recover(self, message: str, signature: HexStr) -> ChecksumAddress: + return self._ec_recover(message, signature) + + def import_raw_key(self, private_key: str, passphrase: str) -> ChecksumAddress: + return self._import_raw_key(private_key, passphrase) + + def list_accounts(self) -> List[ChecksumAddress]: + return self._list_accounts() + + def list_wallets(self) -> List[GethWallet]: + return self._list_wallets() + + def lock_account(self, account: ChecksumAddress) -> bool: + return self._lock_account(account) + + def new_account(self, passphrase: str) -> ChecksumAddress: + return self._new_account(passphrase) + + def send_transaction(self, transaction: TxParams, passphrase: str) -> HexBytes: + return self._send_transaction(transaction, passphrase) + + def sign(self, message: str, account: ChecksumAddress, password: Optional[str]) -> HexStr: + return self._sign(message, account, password) + + def sign_typed_data(self, + message: Dict[str, Any], + account: ChecksumAddress, + password: Optional[str]) -> HexStr: + return self._sign_typed_data(message, account, password) + + def unlock_account(self, + account: ChecksumAddress, + passphrase: str, + duration: Optional[int] = None) -> bool: + return self._unlock_account(account, passphrase, duration) + + def ecRecover(self, message: str, signature: HexStr) -> ChecksumAddress: + return self._ecRecover(message, signature) + + def importRawKey(self, private_key: str, passphrase: str) -> ChecksumAddress: + return self._importRawKey(private_key, passphrase) + + def listAccounts(self) -> List[ChecksumAddress]: + return self._listAccounts() + + def lockAccount(self, account: ChecksumAddress) -> bool: + return self._lockAccount(account) + + def newAccount(self, passphrase: str) -> ChecksumAddress: + return self._newAccount(passphrase) + + def sendTransaction(self, transaction: TxParams, passphrase: str) -> HexBytes: + return self._sendTransaction(transaction, passphrase) + + def signTypedData(self, + message: Dict[str, Any], + account: ChecksumAddress, + password: Optional[str] = None) -> HexStr: + return self._signTypedData(message, account, password) + + def unlockAccount(self, + account: ChecksumAddress, + passphrase: str, + duration: Optional[int] = None) -> bool: + return self._unlockAccount(account, passphrase, duration) + + +class AsyncGethPersonal(BaseGethPersonal): + is_async = True + + async def ec_recover(self, message: str, signature: HexStr) -> Awaitable[ChecksumAddress]: + return await self._ec_recover(message, signature) # type: ignore + + async def import_raw_key(self, private_key: str, passphrase: str) -> Awaitable[ChecksumAddress]: + return await self._import_raw_key(private_key, passphrase) # type: ignore + + async def list_accounts(self) -> Awaitable[List[ChecksumAddress]]: + return await self._list_accounts() # type: ignore + + async def list_wallets(self) -> Awaitable[List[GethWallet]]: + return await self._list_wallets() # type: ignore + + async def lock_account(self, account: ChecksumAddress) -> Awaitable[bool]: + return await self._lock_account(account) # type: ignore + + async def new_account(self, passphrase: str) -> Awaitable[ChecksumAddress]: + return await self._new_account(passphrase) # type: ignore + + async def send_transaction(self, transaction: TxParams, passphrase: str) -> Awaitable[HexBytes]: + return await self._send_transaction(transaction, passphrase) # type: ignore + + async def sign(self, + message: str, + account: ChecksumAddress, + password: Optional[str]) -> Awaitable[HexStr]: + return await self._sign(message, account, password) # type: ignore + + async def sign_typed_data(self, + message: Dict[str, Any], + account: ChecksumAddress, + password: Optional[str]) -> Awaitable[HexStr]: + return await self._sign_typed_data(message, account, password) # type: ignore + + async def unlock_account(self, + account: ChecksumAddress, + passphrase: str, + duration: Optional[int] = None) -> Awaitable[bool]: + return await self._unlock_account(account, passphrase, duration) # type: ignore class BaseTxPool(Module):