diff --git a/Pipfile b/Pipfile index b19346c..c1b6378 100644 --- a/Pipfile +++ b/Pipfile @@ -7,13 +7,13 @@ name = "pypi" python_version = "3" [packages] -nucypher = {git = "https://github.com/nucypher/nucypher.git", ref = "v7.0.0-rc.8"} +nucypher = {git = "https://github.com/nucypher/nucypher.git", ref = "v7.0.0-rc.10"} nucypher-core = "==0.13.0" # must be the same as nucypher flask-cors = "*" prometheus-flask-exporter = "*" [dev-packages] -nucypher = {git = "https://github.com/nucypher/nucypher.git", editable = true, ref = "v7.0.0-rc.8", extras = ["dev"]} # needed for testerchain, and must be editable +nucypher = {git = "https://github.com/nucypher/nucypher.git", editable = true, ref = "v7.0.0-rc.10", extras = ["dev"]} # needed for testerchain, and must be editable pytest = "<7" # match with nucypher/nucypher pytest-cov = "*" pytest-mock = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 25c0e71..c563ed2 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "46a5138d44f1bae27372c812ba7b68f4bddd9dcd22f75a3ebf590224fcb47134" + "sha256": "cb97453b370ef434cc8ac515b2e63564cf07c9b8b997e24f155f3ed25f5bbfb2" }, "pipfile-spec": 6, "requires": { @@ -1125,7 +1125,7 @@ }, "nucypher": { "git": "https://github.com/nucypher/nucypher.git", - "ref": "598ad313762fa2a5c8653f5996e888e75d9370ed" + "ref": "c0d12e14e4526f3fb3549a1176d0b2a79923c5ca" }, "nucypher-core": { "hashes": [ @@ -3639,7 +3639,7 @@ }, "nucypher": { "git": "https://github.com/nucypher/nucypher.git", - "ref": "598ad313762fa2a5c8653f5996e888e75d9370ed" + "ref": "c0d12e14e4526f3fb3549a1176d0b2a79923c5ca" }, "nucypher-core": { "hashes": [ @@ -4533,10 +4533,10 @@ }, "sentry-sdk": { "hashes": [ - "sha256:1cce906dc86afda1ecd22c4716b0c846639151a3c3b59e23826711c6525c5642", - "sha256:816aeb900a54bba2d9346bad8ffac2d258c4fa09271b95a6533a714e9000f074" + "sha256:04e392db9a0d59bd49a51b9e3a92410ac5867556820465057c2ef89a38e953e9", + "sha256:a7865952701e46d38b41315c16c075367675c48d049b90a4cc2e41991ebc7efa" ], - "version": "==1.33.1" + "version": "==1.35.0" }, "service-identity": { "hashes": [ diff --git a/dev-requirements.txt b/dev-requirements.txt index 1f6def9..8a7eb14 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -83,7 +83,7 @@ msgspec==0.18.4 ; python_version >= '3.8' multidict==5.2.0 ; python_version >= '3.6' mypy-extensions==1.0.0 ; python_version >= '3.5' nodeenv==1.8.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6' -git+https://github.com/nucypher/nucypher.git@598ad313762fa2a5c8653f5996e888e75d9370ed#egg=nucypher +git+https://github.com/nucypher/nucypher.git@c0d12e14e4526f3fb3549a1176d0b2a79923c5ca#egg=nucypher nucypher-core==0.13.0 numpy==1.26.1 ; python_version < '3.13' and python_version >= '3.9' packaging==23.2 ; python_version >= '3.7' @@ -140,7 +140,7 @@ rlp==3.0.0 rpds-py==0.10.6 ; python_version >= '3.8' safe-pysha3==1.0.4 semantic-version==2.10.0 ; python_version >= '2.7' -sentry-sdk==1.33.1 +sentry-sdk==1.35.0 service-identity==23.1.0 ; python_version >= '3.8' setuptools==68.2.2 ; python_version >= '3.8' six==1.16.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' diff --git a/porter/cli/main.py b/porter/cli/main.py index d33208f..e3fb436 100644 --- a/porter/cli/main.py +++ b/porter/cli/main.py @@ -30,8 +30,15 @@ def porter_cli(): @porter_cli.command() @group_general_config -@option_domain(default=str(domains.DEFAULT_DOMAIN), validate=True, required=False) -@option_eth_endpoint(required=False) +@option_domain(default=str(domains.DEFAULT_DOMAIN), validate=True, required=True) +@option_eth_endpoint(required=True) +@click.option( + "--polygon-endpoint", + "polygon_endpoint", + help="Connection URL for Polygon chain", + type=click.STRING, + required=True, +) @option_teacher_uri @option_registry_filepath @option_min_stake @@ -62,6 +69,7 @@ def run( general_config, domain, eth_endpoint, + polygon_endpoint, teacher_uri, registry_filepath, min_stake, @@ -73,23 +81,6 @@ def run( """Start Porter's Web controller.""" emitter = setup_emitter(general_config, banner=BANNER) - # HTTP/HTTPS - if not eth_endpoint: - raise click.BadOptionUsage( - option_name="--eth-endpoint", - message=click.style( - "--eth-endpoint is required for decentralized porter.", fg="red" - ), - ) - if not domain: - # should never happen - domain defaults to 'mainnet' if not specified - raise click.BadOptionUsage( - option_name="--domain", - message=click.style( - "--domain is required for decentralized porter.", "red" - ), - ) - domain = domains.get_domain(domain) registry = get_registry(domain=domain, registry_filepath=registry_filepath) teacher = None @@ -107,10 +98,12 @@ def run( registry=registry, start_learning_now=eager, eth_endpoint=eth_endpoint, + polygon_endpoint=polygon_endpoint, ) emitter.message(f"TACo Domain: {str(PORTER.domain).capitalize()}", color="green") emitter.message(f"ETH Endpoint URI: {eth_endpoint}", color="green") + emitter.message(f"Polygon Endpoint URI: {polygon_endpoint}", color="green") # firm up falsy status (i.e. change specified empty string to None) allow_origins = allow_origins if allow_origins else None diff --git a/porter/main.py b/porter/main.py index a08e656..f2095a8 100644 --- a/porter/main.py +++ b/porter/main.py @@ -5,8 +5,11 @@ from eth_typing import ChecksumAddress from eth_utils import to_checksum_address from flask import Response, request -from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent -from nucypher.blockchain.eth.domains import TACoDomain +from nucypher.blockchain.eth.agents import ( + ContractAgency, + TACoChildApplicationAgent, +) +from nucypher.blockchain.eth.domains import DEFAULT_DOMAIN, TACoDomain from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory from nucypher.blockchain.eth.registry import ContractRegistry from nucypher.characters.lawful import Ursula @@ -84,31 +87,33 @@ class DecryptOutcome(NamedTuple): def __init__( self, - domain: TACoDomain = None, + eth_endpoint: str, + polygon_endpoint: str, + domain: TACoDomain = DEFAULT_DOMAIN, registry: ContractRegistry = None, controller: bool = True, node_class: object = Ursula, - eth_endpoint: str = None, execution_timeout: int = DEFAULT_EXECUTION_TIMEOUT, *args, **kwargs, ): + if not domain: + raise ValueError("TACo Domain must be provided.") if not eth_endpoint: - raise ValueError('ETH Provider URI is required for decentralized Porter.') + raise ValueError("ETH Provider URI must be provided.") + if not polygon_endpoint: + raise ValueError("Polygon Provider URI must be provided.") - if not BlockchainInterfaceFactory.is_interface_initialized( - endpoint=eth_endpoint - ): - BlockchainInterfaceFactory.initialize_interface(endpoint=eth_endpoint) + self._initialize_endpoints(eth_endpoint, polygon_endpoint) + self.eth_endpoint, self.polygon_endpoint = eth_endpoint, polygon_endpoint - self.eth_endpoint = eth_endpoint self.registry = registry or ContractRegistry.from_latest_publication( domain=domain ) - self.application_agent = ContractAgency.get_agent( - TACoApplicationAgent, + self.taco_child_application_agent = ContractAgency.get_agent( + TACoChildApplicationAgent, registry=self.registry, - blockchain_endpoint=self.eth_endpoint, + blockchain_endpoint=self.polygon_endpoint, ) super().__init__(save_metadata=True, domain=domain, node_class=node_class, *args, **kwargs) @@ -125,6 +130,18 @@ def __init__( self.log.info(BANNER) + @staticmethod + def _initialize_endpoints(eth_endpoint: str, polygon_endpoint: str): + if not BlockchainInterfaceFactory.is_interface_initialized( + endpoint=eth_endpoint + ): + BlockchainInterfaceFactory.initialize_interface(endpoint=eth_endpoint) + + if not BlockchainInterfaceFactory.is_interface_initialized( + endpoint=polygon_endpoint + ): + BlockchainInterfaceFactory.initialize_interface(endpoint=polygon_endpoint) + def get_ursulas(self, quantity: int, exclude_ursulas: Optional[Sequence[ChecksumAddress]] = None, @@ -224,7 +241,7 @@ def _make_reservoir( include_ursulas: Optional[Sequence[ChecksumAddress]] = None, ): return make_staking_provider_reservoir( - application_agent=self.application_agent, + application_agent=self.taco_child_application_agent, exclude_addresses=exclude_ursulas, include_addresses=include_ursulas, ) diff --git a/requirements.txt b/requirements.txt index 128f540..b41e52e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -53,7 +53,7 @@ msgpack==1.0.7 ; python_version >= '3.8' msgpack-python==0.5.6 multidict==5.2.0 ; python_version >= '3.6' mypy-extensions==1.0.0 ; python_version >= '3.5' -git+https://github.com/nucypher/nucypher.git@598ad313762fa2a5c8653f5996e888e75d9370ed#egg=nucypher +git+https://github.com/nucypher/nucypher.git@c0d12e14e4526f3fb3549a1176d0b2a79923c5ca#egg=nucypher nucypher-core==0.13.0 packaging==23.2 ; python_version >= '3.7' parsimonious==0.9.0 diff --git a/tests/conftest.py b/tests/conftest.py index 8826cbd..7b5293b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,7 @@ ContractAgency, CoordinatorAgent, StakingProvidersReservoir, - TACoApplicationAgent, + TACoChildApplicationAgent, ) from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory from nucypher.blockchain.eth.registry import ContractRegistry @@ -182,7 +182,7 @@ def mock_reservoir( } return StakingProvidersReservoir(addresses) - mock_agent = mock_contract_agency.get_agent(TACoApplicationAgent) + mock_agent = mock_contract_agency.get_agent(TACoChildApplicationAgent) mock_agent.get_staking_provider_reservoir = mock_reservoir @@ -208,6 +208,7 @@ def porter(ursulas, mock_rest_middleware, test_registry): porter = Porter( domain=TEMPORARY_DOMAIN, eth_endpoint=MOCK_ETH_PROVIDER_URI, + polygon_endpoint=MOCK_ETH_PROVIDER_URI, registry=test_registry, abort_on_learning_error=True, start_learning_now=True, @@ -340,7 +341,7 @@ def dkg_setup( # Configure CoordinatorAgent coordinator_agent.get_ritual.return_value = ritual coordinator_agent.get_ritual_status.return_value = ( - CoordinatorAgent.Ritual.Status.FINALIZED + CoordinatorAgent.Ritual.Status.ACTIVE ) coordinator_agent.is_encryption_authorized.return_value = True diff --git a/tests/pre/conftest.py b/tests/pre/conftest.py new file mode 100644 index 0000000..6d68a71 --- /dev/null +++ b/tests/pre/conftest.py @@ -0,0 +1,26 @@ +from typing import Iterable, Optional + +import pytest +from eth_typing import ChecksumAddress +from nucypher.blockchain.eth.agents import ( + StakingProvidersReservoir, + TACoApplicationAgent, +) + + +@pytest.fixture(scope="module", autouse=True) +def mock_sample_reservoir(testerchain, mock_contract_agency): + def mock_reservoir( + without: Optional[Iterable[ChecksumAddress]] = None, *args, **kwargs + ): + addresses = { + address: 1 + for address in testerchain.stake_providers_accounts + if address not in without + } + return StakingProvidersReservoir(addresses) + + # TODO - this is needed for PRE Policy.enact(...) sample functionality which + # uses TACoApplication - should we change this (in `nucypher`)? + mock_agent = mock_contract_agency.get_agent(TACoApplicationAgent) + mock_agent.get_staking_provider_reservoir = mock_reservoir diff --git a/tests/test_porter_cli.py b/tests/test_porter_cli.py index 87c586f..c398e78 100644 --- a/tests/test_porter_cli.py +++ b/tests/test_porter_cli.py @@ -24,6 +24,8 @@ def test_porter_cli_run_simple(click_runner, teacher_uri): TEMPORARY_DOMAIN_NAME, "--eth-endpoint", TEST_ETH_PROVIDER_URI, + "--polygon-endpoint", + TEST_ETH_PROVIDER_URI, "--teacher", teacher_uri, ) @@ -42,6 +44,8 @@ def test_porter_cli_run_simple(click_runner, teacher_uri): TEMPORARY_DOMAIN_NAME, "--eth-endpoint", TEST_ETH_PROVIDER_URI, + "--polygon-endpoint", + TEST_ETH_PROVIDER_URI, "--http-port", non_default_port, "--teacher", @@ -60,12 +64,30 @@ def test_porter_cli_run_eth_provider_must_be_provided(click_runner, teacher_uri) "--dry-run", "--domain", TEMPORARY_DOMAIN_NAME, + "--polygon-endpoint", + TEST_ETH_PROVIDER_URI, "--teacher", teacher_uri, ) result = click_runner.invoke(porter_cli, porter_run_command, catch_exceptions=False) assert result.exit_code != 0, result.output - assert f"--eth-endpoint is required" in result.output + assert "Missing option '--eth-endpoint'" in result.output + + +def test_porter_cli_run_polygon_provider_must_be_provided(click_runner, teacher_uri): + porter_run_command = ( + "run", + "--dry-run", + "--domain", + TEMPORARY_DOMAIN_NAME, + "--eth-endpoint", + TEST_ETH_PROVIDER_URI, + "--teacher", + teacher_uri, + ) + result = click_runner.invoke(porter_cli, porter_run_command, catch_exceptions=False) + assert result.exit_code != 0, result.output + assert "Missing option '--polygon-endpoint'" in result.output def test_cli_run_with_cors_origin(click_runner, teacher_uri): @@ -78,6 +100,8 @@ def test_cli_run_with_cors_origin(click_runner, teacher_uri): TEMPORARY_DOMAIN_NAME, "--eth-endpoint", TEST_ETH_PROVIDER_URI, + "--polygon-endpoint", + TEST_ETH_PROVIDER_URI, "--teacher", teacher_uri, "--allow-origins", @@ -99,6 +123,8 @@ def test_cli_run_with_empty_string_cors_origin(click_runner, teacher_uri): TEMPORARY_DOMAIN_NAME, "--eth-endpoint", TEST_ETH_PROVIDER_URI, + "--polygon-endpoint", + TEST_ETH_PROVIDER_URI, "--teacher", teacher_uri, "--allow-origins",