diff --git a/READMEs/datatokens-flow.md b/READMEs/datatokens-flow.md index 60776aff6..8a8577769 100644 --- a/READMEs/datatokens-flow.md +++ b/READMEs/datatokens-flow.md @@ -35,7 +35,7 @@ Create a file called `config.ini` and fill it as follows. ```text [eth-network] -network = http://127.0.0.1:8545 +network = ganache address.file = ~/.ocean/ocean-contracts/artifacts/address.json ``` diff --git a/READMEs/marketplace-flow.md b/READMEs/marketplace-flow.md index 4d5dea8aa..22ab2d4f6 100644 --- a/READMEs/marketplace-flow.md +++ b/READMEs/marketplace-flow.md @@ -82,7 +82,7 @@ Create a file called `test3/config.ini` and fill it as follows. ```text [eth-network] -network = http://127.0.0.1:8545 +network = ganache address.file = ~/.ocean/ocean-contracts/artifacts/address.json [resources] diff --git a/config.ini b/config.ini index c616b3545..20946b6cb 100644 --- a/config.ini +++ b/config.ini @@ -1,6 +1,6 @@ [eth-network] -network = http://127.0.0.1:8545 +network = ganache address.file = ~/.ocean/ocean-contracts/artifacts/address.json diff --git a/ocean_lib/config.py b/ocean_lib/config.py index c8a095572..0bd8f8007 100644 --- a/ocean_lib/config.py +++ b/ocean_lib/config.py @@ -107,7 +107,7 @@ def __init__( ``` [eth-network] ; ethereum network url - network = https://rinkeby.infura.io/v3/ + network = rinkeby [resources] metadata_cache_uri = http://localhost:5000 diff --git a/ocean_lib/example_config.py b/ocean_lib/example_config.py index 2a3f0d77d..7d5386b70 100644 --- a/ocean_lib/example_config.py +++ b/ocean_lib/example_config.py @@ -4,10 +4,12 @@ # import logging +import os from typing import Dict, Optional from enforce_typing import enforce_types from ocean_lib.config import Config +from ocean_lib.ocean.util import get_infura_id, get_infura_url logging.basicConfig(level=logging.INFO) @@ -15,7 +17,14 @@ @enforce_types class ExampleConfig: @staticmethod - def _get_config() -> Dict[str, Dict[str, str]]: + def get_config_net() -> str: + """ + :return: value of environment variable `TEST_NET` or default `ganache` + """ + return os.environ.get("TEST_NET", "ganache") + + @staticmethod + def get_base_config() -> Dict[str, Dict[str, str]]: """ :return: dict """ @@ -29,6 +38,27 @@ def _get_config() -> Dict[str, Dict[str, str]]: }, } + @staticmethod + def get_network_config(network_name: str) -> Dict[str, Dict[str, str]]: + """ + :return: dict + """ + config = ExampleConfig.get_base_config() + config["eth-network"]["network"] = get_infura_url(get_infura_id(), network_name) + return config + + @staticmethod + def _get_config( + local_node: bool = True, net_name: Optional[str] = None + ) -> Dict[str, Dict[str, str]]: + """ + :return: dict + """ + if local_node: + return ExampleConfig.get_base_config() + + return ExampleConfig.get_network_config(net_name) + @staticmethod def get_config_dict( network_name: Optional[str] = None, @@ -36,15 +66,17 @@ def get_config_dict( """ :return: dict """ - config_dict = ExampleConfig._get_config() + test_net = network_name or ExampleConfig.get_config_net() + local_node = not test_net or test_net in {"local", "ganache"} + config_dict = ExampleConfig._get_config(local_node, test_net) logging.debug( f"Configuration loaded for environment `{network_name}`: {config_dict}" ) return config_dict @staticmethod - def get_config(network_url: Optional[str] = None) -> Config: + def get_config(network_name: Optional[str] = None) -> Config: """ :return: `Config` instance """ - return Config(options_dict=ExampleConfig.get_config_dict(network_url)) + return Config(options_dict=ExampleConfig.get_config_dict(network_name)) diff --git a/ocean_lib/ocean/env_constants.py b/ocean_lib/ocean/env_constants.py index 6e486ae62..29d8f3b44 100644 --- a/ocean_lib/ocean/env_constants.py +++ b/ocean_lib/ocean/env_constants.py @@ -6,7 +6,11 @@ Defines constants: - `ENV_CONFIG_FILE` - `ENV_PROVIDER_API_VERSION` +- `ENV_INFURA_CONNECTION_TYPE` +- `ENV_INFURA_PROJECT_ID` """ ENV_CONFIG_FILE = "OCEAN_CONFIG_FILE" ENV_PROVIDER_API_VERSION = "PROVIDER_API_VERSION" +ENV_INFURA_CONNECTION_TYPE = "INFURA_CONNECTION_TYPE" +ENV_INFURA_PROJECT_ID = "INFURA_PROJECT_ID" diff --git a/ocean_lib/ocean/test/test_util.py b/ocean_lib/ocean/test/test_util.py index eac27d1a6..acaa129db 100644 --- a/ocean_lib/ocean/test/test_util.py +++ b/ocean_lib/ocean/test/test_util.py @@ -3,8 +3,14 @@ # SPDX-License-Identifier: Apache-2.0 # +import os + import pytest from ocean_lib.ocean import util +from ocean_lib.ocean.env_constants import ( + ENV_INFURA_CONNECTION_TYPE, + ENV_INFURA_PROJECT_ID, +) from ocean_lib.ocean.util import ( from_base, from_base_18, @@ -16,7 +22,57 @@ ) +def test_get_infura_connection_type(monkeypatch): + # no envvar + if ENV_INFURA_CONNECTION_TYPE in os.environ: + monkeypatch.delenv(ENV_INFURA_CONNECTION_TYPE) + assert ( + util.get_infura_connection_type() == "http" + ), "The default connection type for infura is not http." + + # envvar is "http" + monkeypatch.setenv(ENV_INFURA_CONNECTION_TYPE, "http") + assert util.get_infura_connection_type() == "http" + + # envvar is "websocket" + monkeypatch.setenv(ENV_INFURA_CONNECTION_TYPE, "websocket") + assert util.get_infura_connection_type() == "websocket" + + # envvar is other val + monkeypatch.setenv(ENV_INFURA_CONNECTION_TYPE, "foo_type") + assert util.get_infura_connection_type() == "http" + + +def test_get_infura_id(monkeypatch): + # no envvar + if ENV_INFURA_PROJECT_ID in os.environ: + monkeypatch.delenv(ENV_INFURA_PROJECT_ID) + assert util.get_infura_id() == util.WEB3_INFURA_PROJECT_ID + + # envvar is other val + monkeypatch.setenv(ENV_INFURA_PROJECT_ID, "foo_id") + assert util.get_infura_id() == "foo_id" + + +def test_get_infura_url(monkeypatch): + # envvar is "http" + monkeypatch.setenv(ENV_INFURA_CONNECTION_TYPE, "http") + assert util.get_infura_url("id1", "net1") == "https://net1.infura.io/v3/id1" + + # envvar is "websocket" + monkeypatch.setenv(ENV_INFURA_CONNECTION_TYPE, "websocket") + assert util.get_infura_url("id2", "net2") == "wss://net2.infura.io/ws/v3/id2" + + # envvar is other val - it will resort to "http" + monkeypatch.setenv(ENV_INFURA_CONNECTION_TYPE, "foo_type") + assert util.get_infura_url("id3", "net3") == "https://net3.infura.io/v3/id3" + + def test_get_web3_connection_provider(monkeypatch): + # "ganache" + provider = util.get_web3_connection_provider("ganache") + assert provider.endpoint_uri == util.GANACHE_URL # e.g. http://127.0.0.1:8545 + # GANACHE_URL provider = util.get_web3_connection_provider(util.GANACHE_URL) assert provider.endpoint_uri == util.GANACHE_URL @@ -29,8 +85,39 @@ def test_get_web3_connection_provider(monkeypatch): provider = util.get_web3_connection_provider("https://bar.com") assert provider.endpoint_uri == "https://bar.com" + # "rinkeby" + assert "rinkeby" in util.SUPPORTED_NETWORK_NAMES + monkeypatch.setenv(ENV_INFURA_PROJECT_ID, "id1") + provider = util.get_web3_connection_provider("rinkeby") + assert provider.endpoint_uri == "https://rinkeby.infura.io/v3/id1" + + # polygon network name + assert ( + "polygon" in util.SUPPORTED_NETWORK_NAMES + ), "polygon is missing from SUPPORTED_NETWORK_NAMES" + assert util.POLYGON_URL == "https://rpc.polygon.oceanprotocol.com" + provider = util.get_web3_connection_provider("polygon") + assert provider.endpoint_uri == "https://rpc.polygon.oceanprotocol.com" + + # bsc network name + assert ( + "bsc" in util.SUPPORTED_NETWORK_NAMES + ), "bsc is missing from SUPPORTED_NETWORK_NAMES" + assert util.BSC_URL == "https://bsc-dataseed.binance.org" + provider = util.get_web3_connection_provider("bsc") + assert provider.endpoint_uri == "https://bsc-dataseed.binance.org" + + # all infura-supported network names + for network in util.SUPPORTED_NETWORK_NAMES: + if network == "ganache" or "polygon": + continue # tested above + monkeypatch.setenv(ENV_INFURA_PROJECT_ID, f"id_{network}") + provider = util.get_web3_connection_provider(network) + assert provider.endpoint_uri == f"https://{network}.infura.io/v3/id_{network}" + # non-supported name - with pytest.raises(AssertionError): + monkeypatch.setenv(ENV_INFURA_PROJECT_ID, "idx") + with pytest.raises(Exception): util.get_web3_connection_provider("not_network_name") # typical websockets uri "wss://foo.com" diff --git a/ocean_lib/ocean/util.py b/ocean_lib/ocean/util.py index a203f2e88..00b9b3828 100644 --- a/ocean_lib/ocean/util.py +++ b/ocean_lib/ocean/util.py @@ -2,10 +2,16 @@ # Copyright 2021 Ocean Protocol Foundation # SPDX-License-Identifier: Apache-2.0 # +import os from typing import Dict, Optional, Union + from enforce_typing import enforce_types from ocean_lib.models.bfactory import BFactory from ocean_lib.models.dtfactory import DTFactory +from ocean_lib.ocean.env_constants import ( + ENV_INFURA_CONNECTION_TYPE, + ENV_INFURA_PROJECT_ID, +) from ocean_lib.web3_internal.contract_utils import ( get_contracts_addresses as get_contracts_addresses_web3, ) @@ -14,7 +20,39 @@ from web3 import WebsocketProvider from web3.main import Web3 +WEB3_INFURA_PROJECT_ID = "357f2fe737db4304bd2f7285c5602d0d" GANACHE_URL = "http://127.0.0.1:8545" +POLYGON_URL = "https://rpc.polygon.oceanprotocol.com" +BSC_URL = "https://bsc-dataseed.binance.org" + +# shortcut names for networks that *Infura* supports, plus ganache and polygon +SUPPORTED_NETWORK_NAMES = {"rinkeby", "ganache", "mainnet", "ropsten", "polygon", "bsc"} + + +@enforce_types +def get_infura_connection_type() -> str: + _type = os.getenv(ENV_INFURA_CONNECTION_TYPE, "http") + if _type not in ("http", "websocket"): + _type = "http" + + return _type + + +@enforce_types +def get_infura_id() -> str: + return os.getenv(ENV_INFURA_PROJECT_ID, WEB3_INFURA_PROJECT_ID) + + +@enforce_types +def get_infura_url(infura_id: str, network: str) -> str: + conn_type = get_infura_connection_type() + if conn_type == "http": + return f"https://{network}.infura.io/v3/{infura_id}" + + if conn_type == "websocket": + return f"wss://{network}.infura.io/ws/v3/{infura_id}" + + raise AssertionError(f"Unknown connection type {conn_type}") @enforce_types @@ -23,7 +61,8 @@ def get_web3_connection_provider( ) -> Union[CustomHTTPProvider, WebsocketProvider]: """Return the suitable web3 provider based on the network_url. - Requires going through some gateway such as `infura`. + When connecting to a public ethereum network (mainnet or a test net) without + running a local node requires going through some gateway such as `infura`. Using infura has some issues if your code is relying on evm events. To use events with an infura connection you have to use the websocket interface. @@ -41,15 +80,28 @@ def get_web3_connection_provider( :return: provider : HTTPProvider """ if network_url.startswith("http"): - return CustomHTTPProvider(network_url) + provider = CustomHTTPProvider(network_url) elif network_url.startswith("ws"): - return WebsocketProvider(network_url) + provider = WebsocketProvider(network_url) + elif network_url == "ganache": + provider = CustomHTTPProvider(GANACHE_URL) + elif network_url == "polygon": + provider = CustomHTTPProvider(POLYGON_URL) + elif network_url == "bsc": + provider = CustomHTTPProvider(BSC_URL) else: - msg = ( + assert network_url in SUPPORTED_NETWORK_NAMES, ( f"The given network_url *{network_url}* does not start with either " - f"`http` or `wss`. A correct network url is required." + f"`http` or `wss`, in this case a network name is expected and must " + f"be one of the supported networks {SUPPORTED_NETWORK_NAMES}." ) - raise AssertionError(msg) + network_url = get_infura_url(get_infura_id(), network_url) + if network_url.startswith("http"): + provider = CustomHTTPProvider(network_url) + else: + provider = WebsocketProvider(network_url) + + return provider def get_contracts_addresses(address_file: str, network: str) -> Dict[str, str]: diff --git a/ocean_lib/test/test_config.py b/ocean_lib/test/test_config.py index 63fde4dac..e26c11b3b 100644 --- a/ocean_lib/test/test_config.py +++ b/ocean_lib/test/test_config.py @@ -13,16 +13,16 @@ deprecated_environ_names, environ_names_and_sections, ) +from ocean_lib.example_config import ExampleConfig from ocean_lib.ocean.env_constants import ENV_CONFIG_FILE from ocean_lib.ocean.ocean import Ocean -from ocean_lib.ocean.util import GANACHE_URL from tests.resources.ddo_helpers import get_resource_path def test_metadataStoreUri_config_key(): """Tests that the metadata_cache_uri config property can be set using the `metadataStoreUri` config dict key when created via the Ocean __init__""" - config_dict = {"metadataStoreUri": "http://ItWorked.com", "network": GANACHE_URL} + config_dict = {"metadataStoreUri": "http://ItWorked.com", "network": "ganache"} ocean_instance = Ocean(config=config_dict) assert "http://ItWorked.com" == ocean_instance.config.metadata_cache_uri @@ -30,7 +30,7 @@ def test_metadataStoreUri_config_key(): def test_metadataCacheUri_config_key(): """Tests that the metadata_cache_uri config property can be set using the `metadataCacheUri` config dict key when created via the Ocean __init__""" - config_dict = {"metadataCacheUri": "http://ItWorked.com", "network": GANACHE_URL} + config_dict = {"metadataCacheUri": "http://ItWorked.com", "network": "ganache"} ocean_instance = Ocean(config=config_dict) assert "http://ItWorked.com" == ocean_instance.config.metadata_cache_uri @@ -153,6 +153,13 @@ def test_config_from_text_malformed_content(): Config(text=config_text) +def test_network_config(): + assert ( + "ganache.infura.io" + in ExampleConfig.get_network_config("ganache")["eth-network"]["network"] + ) + + def test_metadata_cache_uri_set_via_config_options(caplog): """Tests the metadata_cache_uri property fallback logic when set via a config dict""" config_dict = {"resources": {"metadata_cache_uri": "https://custom-aqua.uri"}} diff --git a/tox.ini b/tox.ini index 068bb849b..712e4c995 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,7 @@ python = [testenv] passenv = CODACY_PROJECT_TOKEN setenv = + TEST_NET=ganache PYTHONPATH = {toxinidir} OCEAN_CONFIG_FILE = {toxinidir}/config.ini TEST_PRIVATE_KEY1=0xbbfbee4961061d506ffbb11dfea64eba16355cbf1d9c29613126ba7fec0aed5d