From 57ce72ade9ae3a74f07ebb8787600dbab1821534 Mon Sep 17 00:00:00 2001 From: Kieran Prasch Date: Tue, 22 Aug 2023 10:45:37 +0200 Subject: [PATCH] use the staking and payment provider uris as default conditions providers before Ursula's construction. --- nucypher/blockchain/eth/actors.py | 32 +++++++++---------- nucypher/blockchain/eth/networks.py | 13 ++++++-- nucypher/config/characters.py | 24 ++++++++++++-- tests/acceptance/conftest.py | 18 ++++++++++- tests/conftest.py | 17 ++++++++++ .../integration/cli/test_ursula_config_cli.py | 7 +++- .../config/test_character_configuration.py | 3 +- tests/integration/conftest.py | 21 +++++++++++- 8 files changed, 110 insertions(+), 25 deletions(-) diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py index 0b6a3dfeb..f637481ec 100644 --- a/nucypher/blockchain/eth/actors.py +++ b/nucypher/blockchain/eth/actors.py @@ -325,31 +325,30 @@ class Ritualist(BaseActor): condition_provider_uris ) + @staticmethod + def _is_permitted_condition_chain(chain_id: int) -> bool: + return int(chain_id) in [int(cid) for cid in _CONDITION_CHAINS.keys()] + + @staticmethod + def _make_condition_provider(uri: str) -> HTTPProvider: + provider = HTTPProvider(endpoint_uri=uri) + return provider + def connect_condition_providers( self, condition_provider_uris: Optional[Dict[int, List[str]]] = None ) -> DefaultDict[int, Set[HTTPProvider]]: """Multi-provider support""" - # If condition_provider_uris is `None` the node operator + # If condition_provider_uris is None the node operator # did not configure any additional condition providers. condition_provider_uris = condition_provider_uris or dict() # These are the chains that the Ritualist will connect to for conditions evaluation (read-only). condition_providers = defaultdict(set) - # Borrow default RPC endpoints from the Operator. - eth_chain = self.application_agent.blockchain - - # Presumably, they've already configured their polygon engpoint (or we'd not be this far), - # so we can use that for the polygon condition provider. - polygon_chain = self.payment_method.agent.blockchain - condition_providers[eth_chain.client.chain_id].add(eth_chain.provider) - condition_providers[polygon_chain.client.chain_id].add(polygon_chain.provider) - # Done borrowing. - # Now, add any additional providers that were passed in. for chain_id, condition_provider_uris in condition_provider_uris.items(): - if chain_id not in _CONDITION_CHAINS: + if not self._is_permitted_condition_chain(chain_id): # this is a safety check to prevent the Ritualist from connecting to # chains that are not supported by ursulas on the network; # Prevents the Ursula/Ritualist from starting up if this happens. @@ -357,11 +356,12 @@ class Ritualist(BaseActor): f"Chain ID {chain_id} is not supported for condition evaluation by the Ritualist." ) - providers = list() + providers = set() for uri in condition_provider_uris: - provider = HTTPProvider(endpoint_uri=uri) - providers.append(provider) - condition_providers[chain_id].update(providers) + provider = self._make_condition_provider(uri) + providers.add(provider) + + condition_providers[int(chain_id)] = providers # Log the chains that the Ritualist is connected to. humanized_chain_ids = ", ".join( diff --git a/nucypher/blockchain/eth/networks.py b/nucypher/blockchain/eth/networks.py index ee2bcd226..41cd9a0ba 100644 --- a/nucypher/blockchain/eth/networks.py +++ b/nucypher/blockchain/eth/networks.py @@ -34,11 +34,18 @@ class NetworksInventory: # TODO: See #1564 pass @classmethod - def get_ethereum_chain_id(cls, network): # TODO: Use this (where?) to make sure we're in the right chain + def get_ethereum_chain_id(cls, network): try: - return cls.__to_ethereum_chain_id[network] + return cls.__to_chain_id_eth[network] except KeyError: - return 1337 # TODO: what about chain id when testing? + raise cls.UnrecognizedNetwork(network) + + @classmethod + def get_polygon_chain_id(cls, network): + try: + return cls.__to_chain_id_polygon[network] + except KeyError: + raise cls.UnrecognizedNetwork(network) @classmethod def validate_network_name(cls, network_name: str): diff --git a/nucypher/config/characters.py b/nucypher/config/characters.py index 769bac348..44418cbc1 100644 --- a/nucypher/config/characters.py +++ b/nucypher/config/characters.py @@ -1,10 +1,12 @@ import json +from collections import defaultdict from pathlib import Path from typing import Dict, List, Optional from cryptography.x509 import Certificate from eth_utils import is_checksum_address +from nucypher.blockchain.eth.networks import NetworksInventory from nucypher.config.base import CharacterConfiguration from nucypher.config.constants import ( NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD, @@ -56,9 +58,27 @@ class UrsulaConfiguration(CharacterConfiguration): self.rest_host = rest_host self.certificate = certificate self.operator_address = operator_address - self.availability_check = availability_check if availability_check is not None else self.DEFAULT_AVAILABILITY_CHECKS + self.availability_check = ( + availability_check + if availability_check is not None + else self.DEFAULT_AVAILABILITY_CHECKS + ) + super().__init__( + dev_mode=dev_mode, keystore_path=keystore_path, *args, **kwargs + ) self.condition_provider_uris = condition_provider_uris or dict() - super().__init__(dev_mode=dev_mode, keystore_path=keystore_path, *args, **kwargs) + self.configure_condition_provider_uris() + + def configure_condition_provider_uris(self) -> None: + """Configure default condition provider URIs for mainnet and polygon network.""" + + # Polygon + polygon_chain_id = NetworksInventory.get_polygon_chain_id(self.payment_network) + self.condition_provider_uris[polygon_chain_id] = [self.payment_provider] + + # Ethereum + staking_chain_id = NetworksInventory.get_ethereum_chain_id(self.domain) + self.condition_provider_uris[staking_chain_id] = [self.eth_provider_uri] @classmethod def address_from_filepath(cls, filepath: Path) -> str: diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py index a52beb294..78a080e29 100644 --- a/tests/acceptance/conftest.py +++ b/tests/acceptance/conftest.py @@ -4,9 +4,10 @@ import random import pytest from web3 import Web3 -from nucypher.blockchain.eth.actors import Operator +from nucypher.blockchain.eth.actors import Operator, Ritualist from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory +from nucypher.blockchain.eth.networks import NetworksInventory from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.config.constants import TEMPORARY_DOMAIN from nucypher.crypto.powers import CryptoPower, TransactingPower @@ -35,6 +36,21 @@ def mock_condition_blockchains(session_mocker): {TESTERCHAIN_CHAIN_ID: "eth-tester/pyevm"}, ) + session_mocker.patch.object( + NetworksInventory, "get_polygon_chain_id", return_value=TESTERCHAIN_CHAIN_ID + ) + + session_mocker.patch.object( + NetworksInventory, "get_ethereum_chain_id", return_value=TESTERCHAIN_CHAIN_ID + ) + + +@pytest.fixture(scope="module", autouse=True) +def mock_multichain_configuration(module_mocker, testerchain): + module_mocker.patch.object( + Ritualist, "_make_condition_provider", return_value=testerchain.provider + ) + @pytest.fixture(scope='session', autouse=True) def nucypher_contracts(project): diff --git a/tests/conftest.py b/tests/conftest.py index 4500d7202..e6879311f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,8 @@ from collections import defaultdict import pytest from eth_utils.crypto import keccak +from nucypher.blockchain.eth.actors import Ritualist +from nucypher.blockchain.eth.networks import NetworksInventory from nucypher.crypto.powers import TransactingPower from nucypher.network.nodes import Learner from nucypher.network.trackers import AvailabilityTracker @@ -144,3 +146,18 @@ def mock_condition_blockchains(session_mocker): "nucypher.policy.conditions.evm._CONDITION_CHAINS", {TESTERCHAIN_CHAIN_ID: "eth-tester/pyevm"}, ) + + session_mocker.patch.object( + NetworksInventory, "get_polygon_chain_id", return_value=TESTERCHAIN_CHAIN_ID + ) + + session_mocker.patch.object( + NetworksInventory, "get_ethereum_chain_id", return_value=TESTERCHAIN_CHAIN_ID + ) + + +@pytest.fixture(scope="module", autouse=True) +def mock_multichain_configuration(module_mocker, testerchain): + module_mocker.patch.object( + Ritualist, "_make_condition_provider", return_value=testerchain.provider + ) diff --git a/tests/integration/cli/test_ursula_config_cli.py b/tests/integration/cli/test_ursula_config_cli.py index 0966f499f..28623a9f8 100644 --- a/tests/integration/cli/test_ursula_config_cli.py +++ b/tests/integration/cli/test_ursula_config_cli.py @@ -5,6 +5,8 @@ from unittest.mock import PropertyMock import pytest +import nucypher +from nucypher.blockchain.eth.actors import Ritualist from nucypher.blockchain.eth.trackers.dkg import ActiveRitualTracker from nucypher.cli.literature import ( COLLECT_NUCYPHER_PASSWORD, @@ -31,6 +33,7 @@ from tests.constants import ( MOCK_ETH_PROVIDER_URI, MOCK_IP_ADDRESS, YES_ENTER, + TESTERCHAIN_CHAIN_ID, ) from tests.utils.ursula import select_test_port @@ -180,7 +183,9 @@ def test_ursula_view_configuration(custom_filepath: Path, click_runner, nominal_ assert custom_config_filepath.is_file(), 'Configuration file does not exist' -def test_run_ursula_from_config_file(custom_filepath: Path, click_runner, mock_funding_and_bonding): +def test_run_ursula_from_config_file( + custom_filepath: Path, click_runner, mock_funding_and_bonding, mocker +): # Ensure the configuration file still exists custom_config_filepath = custom_filepath / UrsulaConfiguration.generate_filename() diff --git a/tests/integration/config/test_character_configuration.py b/tests/integration/config/test_character_configuration.py index 0d0dee17c..e172a4f73 100644 --- a/tests/integration/config/test_character_configuration.py +++ b/tests/integration/config/test_character_configuration.py @@ -1,3 +1,4 @@ +import json from pathlib import Path import pytest @@ -148,7 +149,7 @@ def test_default_character_configuration_preservation( # Restore from JSON file restored_configuration = configuration_class.from_configuration_file() - assert character_config.serialize() == restored_configuration.serialize() + assert json.loads(character_config.serialize()) == json.loads(restored_configuration.serialize()) # File still exists after reading assert written_filepath.exists() diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 4162e261b..737730ad8 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -4,7 +4,7 @@ from pathlib import Path from typing import Iterable, Optional from nucypher.blockchain.economics import EconomicsFactory -from nucypher.blockchain.eth.actors import Operator +from nucypher.blockchain.eth.actors import Operator, Ritualist from nucypher.blockchain.eth.agents import ( AdjudicatorAgent, ContractAgency, @@ -15,6 +15,7 @@ from nucypher.blockchain.eth.interfaces import ( BlockchainInterface, BlockchainInterfaceFactory, ) +from nucypher.blockchain.eth.networks import NetworksInventory from nucypher.blockchain.eth.registry import InMemoryContractRegistry from nucypher.blockchain.eth.signers import KeystoreSigner from nucypher.characters.lawful import Ursula @@ -26,6 +27,7 @@ from tests.constants import ( KEYFILE_NAME_TEMPLATE, MOCK_KEYSTORE_PATH, NUMBER_OF_MOCK_KEYSTORE_ACCOUNTS, + TESTERCHAIN_CHAIN_ID, ) from tests.mock.interfaces import MockBlockchain, mock_registry_source_manager from tests.mock.io import MockStdinWrapper @@ -245,3 +247,20 @@ def staking_providers(testerchain, test_registry, monkeymodule): Operator.get_staking_provider_address = faked return testerchain.stake_providers_accounts + + +@pytest.fixture(scope="session", autouse=True) +def mock_condition_blockchains(session_mocker): + """adds testerchain's chain ID to permitted conditional chains""" + session_mocker.patch.dict( + "nucypher.policy.conditions.evm._CONDITION_CHAINS", + {TESTERCHAIN_CHAIN_ID: "eth-tester/pyevm"}, + ) + + session_mocker.patch.object( + NetworksInventory, "get_polygon_chain_id", return_value=TESTERCHAIN_CHAIN_ID + ) + + session_mocker.patch.object( + NetworksInventory, "get_ethereum_chain_id", return_value=TESTERCHAIN_CHAIN_ID + )