From e69560ddc63eafcc9dbc6fb1d3962765338ce268 Mon Sep 17 00:00:00 2001 From: fselmo Date: Thu, 25 Apr 2024 10:54:38 -0600 Subject: [PATCH] Deprecate ``geth.personal`` namespace methods --- docs/web3.geth.rst | 4 + newsfragments/3364.deprecation.rst | 1 + web3/_utils/decorators.py | 29 +++-- web3/_utils/miner.py | 20 ++-- .../go_ethereum_personal_module.py | 111 +++++++++++------- web3/contract/base_contract.py | 4 +- web3/geth.py | 58 +++++++-- web3/method.py | 14 ++- 8 files changed, 163 insertions(+), 78 deletions(-) create mode 100644 newsfragments/3364.deprecation.rst diff --git a/docs/web3.geth.rst b/docs/web3.geth.rst index c54df849bd..f3d8ce71ce 100644 --- a/docs/web3.geth.rst +++ b/docs/web3.geth.rst @@ -173,6 +173,10 @@ GethPersonal API The following methods are available on the ``web3.geth.personal`` namespace. +.. warning:: Deprecated: Geth has deprecated the ``personal`` namespace and + has transitioned to using ``clef`` for account management. The ``personal`` + namespace will be removed in *web3.py* ``v7``. + .. py:method:: ec_recover(message, signature) * Delegates to ``personal_ecRecover`` RPC Method diff --git a/newsfragments/3364.deprecation.rst b/newsfragments/3364.deprecation.rst new file mode 100644 index 0000000000..e64dafbc1c --- /dev/null +++ b/newsfragments/3364.deprecation.rst @@ -0,0 +1 @@ +Deprecate the ``geth.personal`` namespace methods. These will be removed in *web3.py* ``v7``. diff --git a/web3/_utils/decorators.py b/web3/_utils/decorators.py index 369b5378bc..4241261b34 100644 --- a/web3/_utils/decorators.py +++ b/web3/_utils/decorators.py @@ -35,22 +35,37 @@ def wrapped(*args: Any) -> Any: return wrapped -def deprecated_for(replace_message: str) -> Callable[..., Any]: +def deprecate_method( + replacement_method: str = None, deprecation_msg: str = None +) -> Callable[..., Any]: """ - Decorate a deprecated function, with info about what to use instead, like: + Decorate a deprecated function with info on its replacement method OR a clarifying + reason for the deprecation. - @deprecated_for("to_bytes()") - def toAscii(arg): + @deprecate_method("to_bytes()") + def to_ascii(arg): + ... + + @deprecate_method(deprecation_msg=( + "This method is no longer supported and will be removed in the next release." + )) + def some_method(arg): ... """ + if replacement_method is None and deprecation_msg is None: + raise ValueError( + "Must provide either `replacement_method` or `deprecation_msg`" + ) def decorator(to_wrap: TFunc) -> TFunc: @functools.wraps(to_wrap) def wrapper(*args: Any, **kwargs: Any) -> Callable[..., Any]: - warnings.warn( - f"{to_wrap.__name__} is deprecated in favor of {replace_message}", - category=DeprecationWarning, + msg = ( + f"{to_wrap.__name__} is deprecated in favor of {replacement_method}" + if replacement_method is not None + else deprecation_msg ) + warnings.warn(msg, category=DeprecationWarning) return to_wrap(*args, **kwargs) return cast(TFunc, wrapper) diff --git a/web3/_utils/miner.py b/web3/_utils/miner.py index 388d0a879c..1925607a8a 100644 --- a/web3/_utils/miner.py +++ b/web3/_utils/miner.py @@ -28,18 +28,14 @@ mungers=[default_root_munger], ) -make_dag = DeprecatedMethod( - _make_dag, "make_dag", msg="All mining methods have been deprecated" -) +make_dag = DeprecatedMethod(_make_dag, msg="All mining methods have been deprecated") _set_extra: Method[Callable[[str], bool]] = Method( RPC.miner_setExtra, mungers=[default_root_munger], ) -set_extra = DeprecatedMethod( - _set_extra, "set_extra", msg="All mining methods have been deprecated" -) +set_extra = DeprecatedMethod(_set_extra, msg="All mining methods have been deprecated") _set_etherbase: Method[Callable[[ChecksumAddress], bool]] = Method( RPC.miner_setEtherbase, @@ -47,7 +43,7 @@ ) set_etherbase = DeprecatedMethod( - _set_etherbase, "set_etherbase", msg="All mining methods have been deprecated" + _set_etherbase, msg="All mining methods have been deprecated" ) _set_gas_price: Method[Callable[[Wei], bool]] = Method( @@ -56,7 +52,7 @@ ) set_gas_price = DeprecatedMethod( - _set_gas_price, "set_gas_price", msg="All mining methods have been deprecated" + _set_gas_price, msg="All mining methods have been deprecated" ) _start: Method[Callable[[int], bool]] = Method( @@ -64,14 +60,14 @@ mungers=[default_root_munger], ) -start = DeprecatedMethod(_start, "start", msg="All mining methods have been deprecated") +start = DeprecatedMethod(_start, msg="All mining methods have been deprecated") _stop: Method[Callable[[], bool]] = Method( RPC.miner_stop, is_property=True, ) -stop = DeprecatedMethod(_stop, "stop", msg="All mining methods have been deprecated") +stop = DeprecatedMethod(_stop, msg="All mining methods have been deprecated") _start_auto_dag: Method[Callable[[], bool]] = Method( RPC.miner_startAutoDag, @@ -79,7 +75,7 @@ ) start_auto_dag = DeprecatedMethod( - _start_auto_dag, "start_auto_dag", msg="All mining methods have been deprecated" + _start_auto_dag, msg="All mining methods have been deprecated" ) _stop_auto_dag: Method[Callable[[], bool]] = Method( @@ -88,5 +84,5 @@ ) stop_auto_dag = DeprecatedMethod( - _stop_auto_dag, "stop_auto_dag", msg="All mining methods have been deprecated" + _stop_auto_dag, msg="All mining methods have been deprecated" ) diff --git a/web3/_utils/module_testing/go_ethereum_personal_module.py b/web3/_utils/module_testing/go_ethereum_personal_module.py index 53a78485ed..a95c03756d 100644 --- a/web3/_utils/module_testing/go_ethereum_personal_module.py +++ b/web3/_utils/module_testing/go_ethereum_personal_module.py @@ -55,17 +55,20 @@ class GoEthereumPersonalModuleTest: def test_personal_import_raw_key(self, w3: "Web3") -> None: - actual = w3.geth.personal.import_raw_key(PRIVATE_KEY_HEX, PASSWORD) + with pytest.warns(DeprecationWarning): + actual = w3.geth.personal.import_raw_key(PRIVATE_KEY_HEX, PASSWORD) assert actual == ADDRESS def test_personal_list_accounts(self, w3: "Web3") -> None: - accounts = w3.geth.personal.list_accounts() + with pytest.warns(DeprecationWarning): + accounts = w3.geth.personal.list_accounts() assert is_list_like(accounts) assert len(accounts) > 0 assert all((is_checksum_address(item) for item in accounts)) def test_personal_list_wallets(self, w3: "Web3") -> None: - wallets = w3.geth.personal.list_wallets() + with pytest.warns(DeprecationWarning): + wallets = w3.geth.personal.list_wallets() assert is_list_like(wallets) assert len(wallets) > 0 assert is_checksum_address(wallets[0]["accounts"][0]["address"]) @@ -76,8 +79,8 @@ def test_personal_list_wallets(self, w3: "Web3") -> None: def test_personal_lock_account( self, w3: "Web3", unlockable_account_dual_type: ChecksumAddress ) -> None: - # TODO: how do we test this better? - w3.geth.personal.lock_account(unlockable_account_dual_type) + with pytest.warns(DeprecationWarning): + w3.geth.personal.lock_account(unlockable_account_dual_type) def test_personal_unlock_account_success( self, @@ -85,9 +88,10 @@ def test_personal_unlock_account_success( unlockable_account_dual_type: ChecksumAddress, unlockable_account_pw: str, ) -> None: - result = w3.geth.personal.unlock_account( - unlockable_account_dual_type, unlockable_account_pw - ) + with pytest.warns(DeprecationWarning): + result = w3.geth.personal.unlock_account( + unlockable_account_dual_type, unlockable_account_pw + ) assert result is True def test_personal_unlock_account_failure( @@ -99,7 +103,8 @@ def test_personal_unlock_account_failure( ) def test_personal_new_account(self, w3: "Web3") -> None: - new_account = w3.geth.personal.new_account(PASSWORD) + with pytest.warns(DeprecationWarning): + new_account = w3.geth.personal.new_account(PASSWORD) assert is_checksum_address(new_account) def test_personal_send_transaction( @@ -118,7 +123,10 @@ def test_personal_send_transaction( "value": Wei(1), "gasPrice": w3.to_wei(1, "gwei"), } - txn_hash = w3.geth.personal.send_transaction(txn_params, unlockable_account_pw) + with pytest.warns(DeprecationWarning): + txn_hash = w3.geth.personal.send_transaction( + txn_params, unlockable_account_pw + ) assert txn_hash transaction = w3.eth.get_transaction(txn_hash) @@ -139,10 +147,13 @@ def test_personal_sign_and_ecrecover( unlockable_account_pw: str, ) -> None: message = "test-web3-geth-personal-sign" - signature = w3.geth.personal.sign( - message, unlockable_account_dual_type, unlockable_account_pw - ) - signer = w3.geth.personal.ec_recover(message, signature) + with pytest.warns(DeprecationWarning): + signature = w3.geth.personal.sign( + message, unlockable_account_dual_type, unlockable_account_pw + ) + + with pytest.warns(DeprecationWarning): + signer = w3.geth.personal.ec_recover(message, signature) assert is_same_address(signer, unlockable_account_dual_type) @pytest.mark.xfail( @@ -193,13 +204,14 @@ def test_personal_sign_typed_data( } } """ - signature = HexBytes( - w3.geth.personal.sign_typed_data( - json.loads(typed_message), - unlockable_account_dual_type, - unlockable_account_pw, + with pytest.warns(DeprecationWarning): + signature = HexBytes( + w3.geth.personal.sign_typed_data( + json.loads(typed_message), + unlockable_account_dual_type, + unlockable_account_pw, + ) ) - ) expected_signature = HexBytes( "0xc8b56aaeefd10ab4005c2455daf28d9082af661ac347cd" @@ -219,33 +231,38 @@ async def test_async_sign_and_ec_recover( unlockable_account_pw: str, ) -> None: message = "This is a test" - signature = await async_w3.geth.personal.sign( - message, async_unlockable_account_dual_type, unlockable_account_pw - ) + with pytest.warns(DeprecationWarning): + signature = await async_w3.geth.personal.sign( + message, async_unlockable_account_dual_type, unlockable_account_pw + ) address = await async_w3.geth.personal.ec_recover(message, signature) assert is_same_address(async_unlockable_account_dual_type, address) @pytest.mark.asyncio async def test_async_import_key(self, async_w3: "AsyncWeb3") -> None: - address = await async_w3.geth.personal.import_raw_key( - THIRD_PRIVATE_KEY_HEX, "Testing" - ) + with pytest.warns(DeprecationWarning): + address = await async_w3.geth.personal.import_raw_key( + THIRD_PRIVATE_KEY_HEX, "Testing" + ) assert address is not None @pytest.mark.asyncio async def test_async_list_accounts(self, async_w3: "AsyncWeb3") -> None: - accounts = await async_w3.geth.personal.list_accounts() + with pytest.warns(DeprecationWarning): + accounts = await async_w3.geth.personal.list_accounts() assert len(accounts) > 0 @pytest.mark.asyncio async def test_async_list_wallets(self, async_w3: "AsyncWeb3") -> None: - wallets = await async_w3.geth.personal.list_wallets() + with pytest.warns(DeprecationWarning): + wallets = await async_w3.geth.personal.list_wallets() assert isinstance(wallets[0], AttributeDict) @pytest.mark.asyncio async def test_async_new_account(self, async_w3: "AsyncWeb3") -> None: passphrase = "Create New Account" - account = await async_w3.geth.personal.new_account(passphrase) + with pytest.warns(DeprecationWarning): + account = await async_w3.geth.personal.new_account(passphrase) assert is_checksum_address(account) @pytest.mark.asyncio @@ -255,13 +272,16 @@ async def test_async_unlock_lock_account( async_unlockable_account_dual_type: ChecksumAddress, unlockable_account_pw: str, ) -> None: - unlocked = await async_w3.geth.personal.unlock_account( - async_unlockable_account_dual_type, unlockable_account_pw - ) + with pytest.warns(DeprecationWarning): + unlocked = await async_w3.geth.personal.unlock_account( + async_unlockable_account_dual_type, unlockable_account_pw + ) assert unlocked is True - locked = await async_w3.geth.personal.lock_account( - async_unlockable_account_dual_type - ) + + with pytest.warns(DeprecationWarning): + locked = await async_w3.geth.personal.lock_account( + async_unlockable_account_dual_type + ) assert locked is True @pytest.mark.asyncio @@ -275,9 +295,10 @@ async def test_async_send_transaction( tx_params["to"] = async_unlockable_account_dual_type tx_params["from"] = async_unlockable_account_dual_type tx_params["value"] = Wei(123) - response = await async_w3.geth.personal.send_transaction( - tx_params, unlockable_account_pw - ) + with pytest.warns(DeprecationWarning): + response = await async_w3.geth.personal.send_transaction( + tx_params, unlockable_account_pw + ) assert response is not None @pytest.mark.xfail( @@ -291,10 +312,12 @@ async def test_async_sign_typed_data( unlockable_account_pw: str, ) -> None: message = {"message": "This is a test"} - signature = await async_w3.geth.personal.sign_typed_data( - message, async_unlockable_account_dual_type, unlockable_account_pw - ) - address = await async_w3.geth.personal.ec_recover( - json.dumps(message), signature - ) + with pytest.warns(DeprecationWarning): + signature = await async_w3.geth.personal.sign_typed_data( + message, async_unlockable_account_dual_type, unlockable_account_pw + ) + with pytest.warns(DeprecationWarning): + address = await async_w3.geth.personal.ec_recover( + json.dumps(message), signature + ) assert is_same_address(async_unlockable_account_dual_type, address) diff --git a/web3/contract/base_contract.py b/web3/contract/base_contract.py index 00c2ee7a8c..cf5b91e344 100644 --- a/web3/contract/base_contract.py +++ b/web3/contract/base_contract.py @@ -57,7 +57,7 @@ PropertyCheckingFactory, ) from web3._utils.decorators import ( - deprecated_for, + deprecate_method, ) from web3._utils.empty import ( empty, @@ -727,7 +727,7 @@ class BaseContract: # Public API # @combomethod - @deprecated_for("encode_abi()") + @deprecate_method("encode_abi()") def encodeABI( cls, fn_name: str, diff --git a/web3/geth.py b/web3/geth.py index 5600aa2703..f89886619e 100644 --- a/web3/geth.py +++ b/web3/geth.py @@ -21,6 +21,9 @@ from web3._utils.compat import ( Protocol, ) +from web3._utils.decorators import ( + deprecate_method, +) from web3._utils.miner import ( make_dag, set_etherbase, @@ -35,6 +38,7 @@ RPC, ) from web3.method import ( + DeprecatedMethod, Method, default_root_munger, ) @@ -63,6 +67,12 @@ def __call__( pass +GETH_PERSONAL_DEPRECATION_MSG = ( + "Geth now uses `clef` for account and key management. This method will be removed " + "in web3.py `v7`." +) + + class GethPersonal(Module): """ https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-personal @@ -70,57 +80,75 @@ class GethPersonal(Module): is_async = False - ec_recover: Method[Callable[[str, HexStr], ChecksumAddress]] = Method( + _ec_recover: Method[Callable[[str, HexStr], ChecksumAddress]] = Method( RPC.personal_ecRecover, mungers=[default_root_munger], ) + ec_recover = DeprecatedMethod(_ec_recover, msg=GETH_PERSONAL_DEPRECATION_MSG) - import_raw_key: Method[Callable[[str, str], ChecksumAddress]] = Method( + _import_raw_key: Method[Callable[[str, str], ChecksumAddress]] = Method( RPC.personal_importRawKey, mungers=[default_root_munger], ) + import_raw_key = DeprecatedMethod( + _import_raw_key, msg=GETH_PERSONAL_DEPRECATION_MSG + ) - list_accounts: Method[Callable[[], List[ChecksumAddress]]] = Method( + _list_accounts: Method[Callable[[], List[ChecksumAddress]]] = Method( RPC.personal_listAccounts, is_property=True, ) + list_accounts = DeprecatedMethod(_list_accounts, msg=GETH_PERSONAL_DEPRECATION_MSG) - list_wallets: Method[Callable[[], List[GethWallet]]] = Method( + _list_wallets: Method[Callable[[], List[GethWallet]]] = Method( RPC.personal_listWallets, is_property=True, ) + list_wallets = DeprecatedMethod(_list_wallets, msg=GETH_PERSONAL_DEPRECATION_MSG) - send_transaction: Method[Callable[[TxParams, str], HexBytes]] = Method( + _send_transaction: Method[Callable[[TxParams, str], HexBytes]] = Method( RPC.personal_sendTransaction, mungers=[default_root_munger], ) + send_transaction = DeprecatedMethod( + _send_transaction, msg=GETH_PERSONAL_DEPRECATION_MSG + ) - sign: Method[Callable[[str, ChecksumAddress, Optional[str]], HexStr]] = Method( + _sign: Method[Callable[[str, ChecksumAddress, Optional[str]], HexStr]] = Method( RPC.personal_sign, mungers=[default_root_munger], ) + sign = DeprecatedMethod(_sign, msg=GETH_PERSONAL_DEPRECATION_MSG) - sign_typed_data: Method[ + _sign_typed_data: Method[ Callable[[Dict[str, Any], ChecksumAddress, str], HexStr] ] = Method( RPC.personal_signTypedData, mungers=[default_root_munger], ) + sign_typed_data = DeprecatedMethod( + _sign_typed_data, msg=GETH_PERSONAL_DEPRECATION_MSG + ) - new_account: Method[Callable[[str], ChecksumAddress]] = Method( + _new_account: Method[Callable[[str], ChecksumAddress]] = Method( RPC.personal_newAccount, mungers=[default_root_munger], ) + new_account = DeprecatedMethod(_new_account, msg=GETH_PERSONAL_DEPRECATION_MSG) - lock_account: Method[Callable[[ChecksumAddress], bool]] = Method( + _lock_account: Method[Callable[[ChecksumAddress], bool]] = Method( RPC.personal_lockAccount, mungers=[default_root_munger], ) + lock_account = DeprecatedMethod(_lock_account, msg=GETH_PERSONAL_DEPRECATION_MSG) - unlock_account: Method[UnlockAccountWrapper] = Method( + _unlock_account: Method[UnlockAccountWrapper] = Method( RPC.personal_unlockAccount, mungers=[default_root_munger], ) + unlock_account = DeprecatedMethod( + _unlock_account, msg=GETH_PERSONAL_DEPRECATION_MSG + ) class GethTxPool(Module): @@ -373,6 +401,7 @@ class AsyncGethPersonal(Module): mungers=[default_root_munger], ) + @deprecate_method(deprecation_msg=GETH_PERSONAL_DEPRECATION_MSG) async def ec_recover(self, message: str, signature: HexStr) -> ChecksumAddress: return await self._ec_recover(message, signature) @@ -383,6 +412,7 @@ async def ec_recover(self, message: str, signature: HexStr) -> ChecksumAddress: mungers=[default_root_munger], ) + @deprecate_method(deprecation_msg=GETH_PERSONAL_DEPRECATION_MSG) async def import_raw_key( self, private_key: str, passphrase: str ) -> ChecksumAddress: @@ -400,9 +430,11 @@ async def import_raw_key( is_property=True, ) + @deprecate_method(deprecation_msg=GETH_PERSONAL_DEPRECATION_MSG) async def list_accounts(self) -> List[ChecksumAddress]: return await self._list_accounts() + @deprecate_method(deprecation_msg=GETH_PERSONAL_DEPRECATION_MSG) async def list_wallets(self) -> List[GethWallet]: return await self._list_wallets() @@ -413,6 +445,7 @@ async def list_wallets(self) -> List[GethWallet]: mungers=[default_root_munger], ) + @deprecate_method(deprecation_msg=GETH_PERSONAL_DEPRECATION_MSG) async def send_transaction( self, transaction: TxParams, passphrase: str ) -> HexBytes: @@ -434,11 +467,13 @@ async def send_transaction( mungers=[default_root_munger], ) + @deprecate_method(deprecation_msg=GETH_PERSONAL_DEPRECATION_MSG) async def sign( self, message: str, account: ChecksumAddress, passphrase: str ) -> HexStr: return await self._sign(message, account, passphrase) + @deprecate_method(deprecation_msg=GETH_PERSONAL_DEPRECATION_MSG) async def sign_typed_data( self, message: Dict[str, Any], account: ChecksumAddress, passphrase: str ) -> HexStr: @@ -463,12 +498,15 @@ async def sign_typed_data( mungers=[default_root_munger], ) + @deprecate_method(deprecation_msg=GETH_PERSONAL_DEPRECATION_MSG) async def new_account(self, passphrase: str) -> ChecksumAddress: return await self._new_account(passphrase) + @deprecate_method(deprecation_msg=GETH_PERSONAL_DEPRECATION_MSG) async def lock_account(self, account: ChecksumAddress) -> bool: return await self._lock_account(account) + @deprecate_method(deprecation_msg=GETH_PERSONAL_DEPRECATION_MSG) async def unlock_account( self, account: ChecksumAddress, passphrase: str, duration: Optional[int] = None ) -> bool: diff --git a/web3/method.py b/web3/method.py index c3fb36c80e..40af81bc26 100644 --- a/web3/method.py +++ b/web3/method.py @@ -220,7 +220,7 @@ class DeprecatedMethod: def __init__( self, method: Method[Callable[..., Any]], - old_name: str, + old_name: Optional[str] = None, new_name: Optional[str] = None, msg: Optional[str] = None, ) -> None: @@ -232,9 +232,17 @@ def __init__( def __get__( self, obj: Optional["Module"] = None, obj_type: Optional[Type["Module"]] = None ) -> Any: - message = f"{self.old_name} is deprecated in favor of {self.new_name}" - if self.msg is not None: + if self.old_name is not None and self.new_name is not None: + if self.msg is not None: + raise ValueError( + "Cannot specify `old_name` and `new_name` along with `msg`" + ) + + message = f"{self.old_name} is deprecated in favor of {self.new_name}" + elif self.msg is not None: message = self.msg + else: + raise ValueError("Must provide either `old_name` and `new_name` or `msg`") warnings.warn( message,