use the staking and payment provider uris as default conditions providers before Ursula's construction.

pull/3185/head
Kieran Prasch 2023-08-22 10:45:37 +02:00
parent 8a551d34ab
commit 57ce72ade9
8 changed files with 110 additions and 25 deletions

View File

@ -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(

View File

@ -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):

View File

@ -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:

View File

@ -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):

View File

@ -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
)

View File

@ -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()

View File

@ -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()

View File

@ -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
)