From 022b8aff22c6ad4f784a54b385548c20269f9e7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 08:06:06 +0200 Subject: [PATCH 1/3] Bump cryptography from 41.0.4 to 41.0.5 (#1489) Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.4 to 41.0.5. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.4...41.0.5) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index db0a3471b..e31c04eb8 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ "pytz", # used minimally and unlikely to change, common dependency "enforce-typing==1.0.0.post1", "eciespy==0.4.0", - "cryptography==41.0.4", + "cryptography==41.0.5", "web3==6.11.1" # web3.py requires eth-abi, requests, and more, # so those will be installed too. From 678f17f8aeb4bf97e8d88d737eeb3868247c63b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 08:06:20 +0200 Subject: [PATCH 2/3] Bump pytest from 7.4.2 to 7.4.3 (#1490) Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.2 to 7.4.3. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.4.2...7.4.3) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e31c04eb8..28bd62308 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ "codacy-coverage==1.3.11", "coverage==7.3.2", "mccabe==0.7.0", - "pytest==7.4.2", + "pytest==7.4.3", "pytest-watch==4.2.0", "pytest-env", # common dependency "matplotlib", # just used in a readme test and unlikely to change, common dependency From 56fa373002008cec305e85d9d8335795ae486be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C4=83lina=20Cenan?= Date: Mon, 30 Oct 2023 16:49:25 +0200 Subject: [PATCH 3/3] Fix #1461: Reinstate Clef accounts (PR #1491) --- READMEs/using-clef.md | 70 ++++++++++++++++++++++++ ocean_lib/data_provider/base.py | 8 +-- ocean_lib/web3_internal/clef.py | 41 ++++++++++++++ ocean_lib/web3_internal/contract_base.py | 21 ++++++- ocean_lib/web3_internal/utils.py | 26 +++++---- setup-local.sh | 11 ++++ tests/readmes/test_readmes.py | 2 +- 7 files changed, 160 insertions(+), 19 deletions(-) create mode 100644 READMEs/using-clef.md create mode 100644 ocean_lib/web3_internal/clef.py create mode 100644 setup-local.sh diff --git a/READMEs/using-clef.md b/READMEs/using-clef.md new file mode 100644 index 000000000..eb26c9e09 --- /dev/null +++ b/READMEs/using-clef.md @@ -0,0 +1,70 @@ + + +# Using hardware wallets with ocean.py + +This README describes how to setup ocean.py with hardware wallets. + +We assume you've already (a) [installed Ocean](install.md), configured any environment variables necessary and created the Ocean object as described in (b) done [local setup](setup-local.md) or [remote setup](setup-remote.md). +These instructions are applicable to both local and remote setup. If you intend to use hardware wallets ONLY, then you can skip the wallet creation parts in the setup instructions. + +## 1. Setting up and running Clef +ocean.py allows the use of hardware wallets via [Clef](https://geth.ethereum.org/docs/clef/tutorial), an account management tool included within [Geth](https://geth.ethereum.org/) + +To use a hardware wallet with ocean.py, start by [installing Geth](https://geth.ethereum.org/docs/install-and-build/installing-geth). +Once finished, type the following command in a bash console and follow the on-screen prompts to set of Clef: + +```console +clef init +``` + +If you need to create a new account, you can use the command `clef newaccount`. For other usefull commands, please consult the [Clef documentation](https://geth.ethereum.org/docs/tools/clef/introduction). + +Once Clef is configured, run it in a bash console as needed, i.e. + +```console +# you can use a different chain if needed +clef --chainid 8996 +``` + +You can also customise your run, e.g. `clef --chainid 8996 --advanced`. + +Keep the clef console open, you will be required to approve transactions and input your password when so requested. + +## 2. Connect ocean.py to Clef via Brownie + +In your Python console where you have setup the Ocean object: + +```python +from ocean_lib.web3_internal.clef import get_clef_accounts +clef_accounts = get_clef_accounts() +``` + +Approve the connection from the Clef console. This will add your Clef account to the `accounts` array. +You can now use the Clef account instead of any wallet argument, e.g. when publishing or consuming DDOs. + + +```python +# pick up the account for convenience +clef_account = clef_accounts[index] + +# make sure account is funded. Let's transfer some ether and OCEAN from alice +from ocean_lib.ocean.util import send_ether +send_ether(config, alice, clef_account.address, to_wei(4)) +OCEAN.transfer(clef_account, to_wei(4), {"from": alice}) + +# publish and download an asset +name = "Branin dataset" +url = "https://raw.githubusercontent.com/trentmc/branin/main/branin.arff" + +(data_nft, datatoken, ddo) = ocean.assets.create_url_asset(name, url, {"from": clef_account}) +datatoken.mint(clef_account, to_wei(1), {"from": clef_account}) +order_tx_id = ocean.assets.pay_for_access_service(ddo, {"from": clef_account}) +ocean.assets.download_asset(ddo, clef_account, './', order_tx_id) + +``` + +Please note that you need to consult your clef console periodically to approve transactions and input your password if needed. +You can use the ClefAccount object seamlessly, in any transaction, just like regular Accounts. Simply send your transaction with `{"from": clef_account}` where needed. diff --git a/ocean_lib/data_provider/base.py b/ocean_lib/data_provider/base.py index cda0ffce8..68f3e029c 100644 --- a/ocean_lib/data_provider/base.py +++ b/ocean_lib/data_provider/base.py @@ -21,7 +21,8 @@ from ocean_lib.exceptions import DataProviderException from ocean_lib.http_requests.requests_session import get_requests_session -from ocean_lib.web3_internal.utils import sign_with_key +from ocean_lib.web3_internal.clef import ClefAccount +from ocean_lib.web3_internal.utils import sign_with_clef, sign_with_key logger = logging.getLogger(__name__) @@ -69,9 +70,8 @@ def sign_message( print(f"signing message with nonce {nonce}: {msg}, account={wallet.address}") - # reinstate as part of #1461 - # if isinstance(wallet, ClefAccount): - # return nonce, str(sign_with_clef(f"{msg}{nonce}", wallet)) + if isinstance(wallet, ClefAccount): + return nonce, str(sign_with_clef(f"{msg}{nonce}", wallet)) return nonce, str(sign_with_key(f"{msg}{nonce}", wallet._private_key.hex())) diff --git a/ocean_lib/web3_internal/clef.py b/ocean_lib/web3_internal/clef.py new file mode 100644 index 000000000..260294b5b --- /dev/null +++ b/ocean_lib/web3_internal/clef.py @@ -0,0 +1,41 @@ +# +# Copyright 2023 Ocean Protocol Foundation +# SPDX-License-Identifier: Apache-2.0 +# +import sys +from pathlib import Path + +from web3 import HTTPProvider, IPCProvider +from web3.main import Web3 + + +def get_clef_accounts(uri: str = None, timeout: int = 120) -> None: + provider = None + if uri is None: + if sys.platform == "win32": + uri = "http://localhost:8550/" + else: + uri = Path.home().joinpath(".clef/clef.ipc").as_posix() + try: + if Path(uri).exists(): + provider = IPCProvider(uri, timeout=timeout) + except OSError: + if uri is not None and uri.startswith("http"): + provider = HTTPProvider(uri, {"timeout": timeout}) + if provider is None: + raise ValueError( + "Unknown URI, must be IPC socket path or URL starting with 'http'" + ) + + response = provider.make_request("account_list", []) + if "error" in response: + raise ValueError(response["error"]["message"]) + + clef_accounts = [ClefAccount(address, provider) for address in response["result"]] + return clef_accounts + + +class ClefAccount: + def __init__(self, address: str, provider: [HTTPProvider, IPCProvider]) -> None: + self.address = Web3.to_checksum_address(address) + self.provider = provider diff --git a/ocean_lib/web3_internal/contract_base.py b/ocean_lib/web3_internal/contract_base.py index 21b84900e..8389909ca 100644 --- a/ocean_lib/web3_internal/contract_base.py +++ b/ocean_lib/web3_internal/contract_base.py @@ -9,10 +9,12 @@ from enforce_typing import enforce_types from eth_typing import ChecksumAddress +from web3._utils.abi import abi_to_signature from web3.exceptions import MismatchedABI from web3.logs import DISCARD from web3.main import Web3 +from ocean_lib.web3_internal.clef import ClefAccount from ocean_lib.web3_internal.contract_utils import load_contract logger = logging.getLogger(__name__) @@ -46,6 +48,7 @@ def wrap(*args, **kwargs): func = getattr(contract_functions, func_name) result = func(*args2, **kwargs) + func_signature = abi_to_signature(result.abi) # view/pure functions don't need "from" key in tx_dict if not tx_dict and result.abi["stateMutability"] not in ["view", "pure"]: @@ -64,8 +67,22 @@ def wrap(*args, **kwargs): result = result.build_transaction(tx_dict2) # sign with wallet private key and send transaction - signed_tx = web3.eth.account.sign_transaction(result, wallet._private_key) - receipt = web3.eth.send_raw_transaction(signed_tx.rawTransaction) + if isinstance(wallet, ClefAccount): + for k, v in result.items(): + result[k] = Web3.to_hex(v) if not isinstance(v, str) else v + + raw_signed_tx = wallet.provider.make_request( + "account_signTransaction", [result, func_signature] + ) + + raw_signed_tx = raw_signed_tx["result"]["raw"] + else: + signed_tx = web3.eth.account.sign_transaction( + result, wallet._private_key + ) + raw_signed_tx = signed_tx.rawTransaction + + receipt = web3.eth.send_raw_transaction(raw_signed_tx) return web3.eth.wait_for_transaction_receipt(receipt) diff --git a/ocean_lib/web3_internal/utils.py b/ocean_lib/web3_internal/utils.py index 9a25af08d..154fcf372 100644 --- a/ocean_lib/web3_internal/utils.py +++ b/ocean_lib/web3_internal/utils.py @@ -13,6 +13,8 @@ from hexbytes.main import HexBytes from web3.main import Web3 +from ocean_lib.web3_internal.clef import ClefAccount + Signature = namedtuple("Signature", ("v", "r", "s")) logger = logging.getLogger(__name__) @@ -29,18 +31,18 @@ def to_32byte_hex(val: int) -> str: return Web3.to_hex(Web3.to_bytes(val).rjust(32, b"\0")) -# reinstate as part of #1461 -# @enforce_types -# def sign_with_clef(message_hash: str, wallet: ClefAccount) -> str: -# message_hash = Web3.solidity_keccak( -# ["bytes"], -# [Web3.to_bytes(text=message_hash)], -# ) -# -# orig_sig = wallet._provider.make_request( -# "account_signData", ["data/plain", wallet.address, message_hash.hex()] -# )["result"] -# return orig_sig +@enforce_types +def sign_with_clef(message_hash: str, wallet: ClefAccount) -> str: + message_hash = Web3.solidity_keccak( + ["bytes"], + [Web3.to_bytes(text=message_hash)], + ) + + orig_sig = wallet.provider.make_request( + "account_signData", ["data/plain", wallet.address, message_hash.hex()] + )["result"] + + return orig_sig @enforce_types diff --git a/setup-local.sh b/setup-local.sh new file mode 100644 index 000000000..f2008556e --- /dev/null +++ b/setup-local.sh @@ -0,0 +1,11 @@ +## +## Copyright 2023 Ocean Protocol Foundation +## SPDX-License-Identifier: Apache-2.0 +## +export REMOTE_TEST_PRIVATE_KEY1=0x8c14238c0fef15881c5638c6e542b2891a9f1cc02d38c69d23724513a5369180 + +# address: ADDRESS2=0xD6764a9F3387B14692CC783BcF5604a23EBb779f +export REMOTE_TEST_PRIVATE_KEY2=0xc217184f76bdcd66d0070bda50905082a96b714c872a5636982f2b676667f7dd + +export WEB3_INFURA_PROJECT_ID="9aa3d95b3bc440fa88ea12eaa4456161" +export MUMBAI_RPC_URL="https://polygon-mumbai.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161" diff --git a/tests/readmes/test_readmes.py b/tests/readmes/test_readmes.py index 7f4d6d8e2..98215d187 100644 --- a/tests/readmes/test_readmes.py +++ b/tests/readmes/test_readmes.py @@ -48,7 +48,7 @@ def test_script_execution(self, script_name): "publish-flow-credentials", "publish-flow-restapi", # TODO: fix and restore "gas-strategy-remote", - # "using-clef", # TODO: removed original clef readme, to reinstate in #1461 + "using-clef", # no way to approve transactions through automatic readme ] if script_name.replace("test_", "").replace(".py", "") in skippable: