From a0bfb22c0538e1fbeb2ccbc02ae60d52905c4dc7 Mon Sep 17 00:00:00 2001 From: Kieran Prasch Date: Tue, 19 Mar 2019 20:41:26 -0700 Subject: [PATCH] Introduce MiningAdjudicator deployer & agent; Deployment entry points and integration testing. --- nucypher/blockchain/eth/actors.py | 27 ++++++++- nucypher/blockchain/eth/agents.py | 19 ++++++ nucypher/blockchain/eth/deployers.py | 58 +++++++++++++++++++ nucypher/cli/config.py | 24 +++++--- nucypher/cli/deploy.py | 3 +- .../test_interdeployer_integration.py | 5 +- tests/cli/test_deploy.py | 45 ++++++++++++-- 7 files changed, 162 insertions(+), 19 deletions(-) diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py index c319eab9e..bccb8925f 100644 --- a/nucypher/blockchain/eth/actors.py +++ b/nucypher/blockchain/eth/actors.py @@ -100,6 +100,8 @@ class NucypherTokenActor: class Deployer(NucypherTokenActor): + contract_names = tuple(a.registry_contract_name for a in EthereumContractAgent.__subclasses__()) + __interface_class = BlockchainDeployerInterface def __init__(self, @@ -117,6 +119,7 @@ class Deployer(NucypherTokenActor): self.token_agent = NucypherTokenAgent(blockchain=blockchain) self.miner_agent = MinerAgent(blockchain=blockchain) self.policy_agent = PolicyAgent(blockchain=blockchain) + self.adjudicator_agent = MiningAdjudicatorAgent(blockchain=blockchain) self.user_escrow_deployers = dict() @@ -125,6 +128,7 @@ class Deployer(NucypherTokenActor): MinerEscrowDeployer.contract_name: self.deploy_miner_contract, PolicyManagerDeployer.contract_name: self.deploy_policy_contract, UserEscrowProxyDeployer.contract_name: self.deploy_escrow_proxy, + MiningAdjudicatorDeployer.contract_name: self.deploy_mining_adjudicator_contract, } def __repr__(self): @@ -182,6 +186,16 @@ class Deployer(NucypherTokenActor): self.policy_agent = policy_manager_deployer.make_agent() return txhashes + def deploy_mining_adjudicator_contract(self, secret: bytes): + secret = self.blockchain.interface.w3.keccak(secret) + mining_adjudicator_deployer = MiningAdjudicatorDeployer(blockchain=self.blockchain, + deployer_address=self.deployer_address, + secret_hash=secret) + + txhashes = mining_adjudicator_deployer.deploy() + self.adjudicator_agent = mining_adjudicator_deployer.make_agent() + return txhashes + def deploy_escrow_proxy(self, secret: bytes): secret = self.blockchain.interface.w3.keccak(secret) escrow_proxy_deployer = UserEscrowProxyDeployer(blockchain=self.blockchain, @@ -201,24 +215,31 @@ class Deployer(NucypherTokenActor): self.user_escrow_deployers[principal_address] = user_escrow_deployer return user_escrow_deployer - def deploy_network_contracts(self, miner_secret: bytes, policy_secret: bytes) -> Tuple[dict, dict]: + def deploy_network_contracts(self, + miner_secret: bytes, + policy_secret: bytes, + adjudicator_secret: bytes + ) -> Tuple[dict, dict]: """ Musketeers, if you will; Deploy the "big three" contracts to the blockchain. """ token_txhashes = self.deploy_token_contract() miner_txhashes = self.deploy_miner_contract(secret=miner_secret) policy_txhashes = self.deploy_policy_contract(secret=policy_secret) + adjudicator_txhashes = self.deploy_mining_adjudicator_contract(secret=adjudicator_secret) txhashes = { NucypherTokenDeployer.contract_name: token_txhashes, MinerEscrowDeployer.contract_name: miner_txhashes, - PolicyManagerDeployer.contract_name: policy_txhashes + PolicyManagerDeployer.contract_name: policy_txhashes, + MiningAdjudicatorDeployer.contract_name: adjudicator_txhashes } agents = { NucypherTokenDeployer.contract_name: self.token_agent, MinerEscrowDeployer.contract_name: self.miner_agent, - PolicyManagerDeployer.contract_name: self.policy_agent + PolicyManagerDeployer.contract_name: self.policy_agent, + MiningAdjudicatorDeployer.contract_name: self.adjudicator_agent } return txhashes, agents diff --git a/nucypher/blockchain/eth/agents.py b/nucypher/blockchain/eth/agents.py index de156fc1c..bd96cf854 100644 --- a/nucypher/blockchain/eth/agents.py +++ b/nucypher/blockchain/eth/agents.py @@ -451,3 +451,22 @@ class UserEscrowAgent(EthereumContractAgent): txhash = self.__proxy_contract.functions.setMinRewardRate(rate).transact({'from': self.__beneficiary}) self.blockchain.wait_for_receipt(txhash) return txhash + + +class MiningAdjudicatorAgent(EthereumContractAgent): + """TODO""" + + registry_contract_name = "MiningAdjudicator" + _proxy_name = "Dispatcher" + + def evaluate_cfrag(self, + capsule: bytes, + capsule_signature_by_requester: bytes, + capsule_signature_by_requester_and_miner: bytes, + cfrag: bytes, + cfrag_signature_by_miner: bytes, + requester_public_key: bytes, + miner_public_key: bytes, + miner_piblc_key_signature: bytes, + precomputed_data: bytes): + pass diff --git a/nucypher/blockchain/eth/deployers.py b/nucypher/blockchain/eth/deployers.py index bf94446e8..5a8c2d973 100644 --- a/nucypher/blockchain/eth/deployers.py +++ b/nucypher/blockchain/eth/deployers.py @@ -53,6 +53,7 @@ class ContractDeployer: self.blockchain = blockchain or Blockchain.connect() + self.deployment_transactions = CONTRACT_NOT_DEPLOYED self.deployment_receipt = CONTRACT_NOT_DEPLOYED self._contract = CONTRACT_NOT_DEPLOYED self.__proxy_contract = NotImplemented @@ -509,3 +510,60 @@ class UserEscrowDeployer(ContractDeployer): self._contract = user_escrow_contract return deployment_transactions + + +class MiningAdjudicatorDeployer(ContractDeployer): + + agency = MiningAdjudicatorAgent + contract_name = agency.registry_contract_name + __upgradeable = True + __proxy_deployer = DispatcherDeployer + + def __init__(self, + secret_hash, + economics: SlashingEconomics = None, + *args, **kwargs): + + super().__init__(*args, **kwargs) + self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) + self.miner_agent = MinerAgent(blockchain=self.blockchain) + self.secret_hash = secret_hash + if not economics: + economics = SlashingEconomics() + self.__economics = economics + + def deploy(self) -> Dict[str, str]: + self.check_deployment_readiness() + + mining_adjudicator_contract, deploy_txhash = self.blockchain.interface \ + .deploy_contract(self.contract_name, + self.miner_agent.contract_address, + *self.__economics.deployment_parameters) + + proxy_deployer = self.__proxy_deployer(blockchain=self.blockchain, + target_contract=mining_adjudicator_contract, + deployer_address=self.deployer_address, + secret_hash=self.secret_hash) + + proxy_deploy_txhashes = proxy_deployer.deploy() + + # Cache the dispatcher contract + proxy_contract = proxy_deployer.contract + self.__proxy_contract = proxy_contract + + # Wrap the escrow contract + wrapped_mining_adjudicator_contract = self.blockchain.interface._wrap_contract(proxy_contract, target_contract=mining_adjudicator_contract) + + # Switch the contract for the wrapped one + mining_adjudicator_contract = wrapped_mining_adjudicator_contract + + # Gather the transaction hashes + deployment_transactions = {'deployment': deploy_txhash, + 'dispatcher_deployment': proxy_deploy_txhashes['txhash']} + + self.deployment_transactions = deployment_transactions + self._contract = mining_adjudicator_contract + + return deployment_transactions + + diff --git a/nucypher/cli/config.py b/nucypher/cli/config.py index be226c31a..31c1ff3ff 100644 --- a/nucypher/cli/config.py +++ b/nucypher/cli/config.py @@ -33,13 +33,10 @@ from nucypher.utilities.logging import ( logToSentry, getTextFileObserver, initialize_sentry, - getJsonFileObserver) + getJsonFileObserver +) -# -# Click CLI Config -# - class NucypherClickConfig: # Output Sinks @@ -118,7 +115,14 @@ class NucypherClickConfig: class NucypherDeployerClickConfig(NucypherClickConfig): - Secrets = collections.namedtuple('Secrets', ('miner_secret', 'policy_secret', 'escrow_proxy_secret')) + # Deploy Environment Variables + miner_escrow_deployment_secret = os.environ.get("NUCYPHER_MINER_ESCROW_SECRET", None) + policy_manager_deployment_secret = os.environ.get("NUCYPHER_POLICY_MANAGER_SECRET", None) + user_escrow_proxy_deployment_secret = os.environ.get("NUCYPHER_USER_ESCROW_PROXY_SECRET", None) + mining_adjudicator_deployment_secret = os.environ.get("NUCYPHER_MINING_ADJUDICATOR_SECRET", None) + + __secrets = ('miner_secret', 'policy_secret', 'escrow_proxy_secret', 'mining_adjudicator_secret') + Secrets = collections.namedtuple('Secrets', __secrets) def collect_deployment_secrets(self) -> Secrets: @@ -137,9 +141,15 @@ class NucypherDeployerClickConfig(NucypherClickConfig): escrow_proxy_secret = click.prompt('Enter UserEscrowProxy Deployment Secret', hide_input=True, confirmation_prompt=True) + mining_adjudicator_secret = self.user_escrow_proxy_deployment_secret + if not mining_adjudicator_secret: + mining_adjudicator_secret = click.prompt('Enter MiningAdjudicator Deployment Secret', hide_input=True, + confirmation_prompt=True) + secrets = self.Secrets(miner_secret=miner_secret, # type: str policy_secret=policy_secret, # type: str - escrow_proxy_secret=escrow_proxy_secret # type: str + escrow_proxy_secret=escrow_proxy_secret, # type: str + mining_adjudicator_secret=mining_adjudicator_secret ) return secrets diff --git a/nucypher/cli/deploy.py b/nucypher/cli/deploy.py index 075a3f966..79cb519e5 100644 --- a/nucypher/cli/deploy.py +++ b/nucypher/cli/deploy.py @@ -101,7 +101,8 @@ def deploy(click_config, try: txhashes, agents = deployer.deploy_network_contracts(miner_secret=bytes(secrets.miner_secret, encoding='utf-8'), - policy_secret=bytes(secrets.policy_secret, encoding='utf-8')) + policy_secret=bytes(secrets.policy_secret, encoding='utf-8'), + adjudicator_secret=bytes(secrets.mining_adjudicator_secret, encoding='utf-8')) except BlockchainInterface.InterfaceError: raise # TODO: Handle registry management here (contract may already exist) else: diff --git a/tests/blockchain/eth/entities/deployers/test_interdeployer_integration.py b/tests/blockchain/eth/entities/deployers/test_interdeployer_integration.py index c33b25dd2..3e04574ae 100644 --- a/tests/blockchain/eth/entities/deployers/test_interdeployer_integration.py +++ b/tests/blockchain/eth/entities/deployers/test_interdeployer_integration.py @@ -20,11 +20,10 @@ import pytest from constant_sorrow import constants from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent -from nucypher.blockchain.eth.constants import DISPATCHER_SECRET_LENGTH from nucypher.blockchain.eth.deployers import (NucypherTokenDeployer, MinerEscrowDeployer, PolicyManagerDeployer, - ContractDeployer) + ContractDeployer, DispatcherDeployer) @pytest.mark.slow() @@ -57,7 +56,7 @@ def test_deploy_ethereum_contracts(testerchain): # # Miner Escrow # - miners_escrow_secret = os.urandom(DISPATCHER_SECRET_LENGTH) + miners_escrow_secret = os.urandom(DispatcherDeployer.DISPATCHER_SECRET_LENGTH) miner_escrow_deployer = MinerEscrowDeployer( blockchain=testerchain, deployer_address=origin, diff --git a/tests/cli/test_deploy.py b/tests/cli/test_deploy.py index cf0c8085f..4de9ebb29 100644 --- a/tests/cli/test_deploy.py +++ b/tests/cli/test_deploy.py @@ -1,7 +1,14 @@ +import json import os -from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent, UserEscrowAgent -from nucypher.blockchain.eth.constants import MAX_ALLOWED_LOCKED +from nucypher.blockchain.eth.actors import Deployer +from nucypher.blockchain.eth.agents import ( + NucypherTokenAgent, + MinerAgent, + UserEscrowAgent, + PolicyAgent, + MiningAdjudicatorAgent +) from nucypher.blockchain.eth.registry import AllocationRegistry from nucypher.cli.deploy import deploy from nucypher.config.constants import DEFAULT_CONFIG_ROOT @@ -31,22 +38,50 @@ def test_nucypher_deploy_contracts(testerchain, click_runner, mock_primary_regis '--provider-uri', TEST_PROVIDER_URI, '--poa') - user_input = 'Y\n'+f'{INSECURE_DEVELOPMENT_PASSWORD}\n'*6 + user_input = 'Y\n'+f'{INSECURE_DEVELOPMENT_PASSWORD}\n'*8 result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 + # Ensure there is a report on each contract + for registry_name in Deployer.contract_names: + assert registry_name in result.output + # Check that the primary contract registry was written + # and peek at some of the registered entries assert os.path.isfile(mock_primary_registry_filepath) + with open(mock_primary_registry_filepath, 'r') as file: + + # Ensure every contract's name was written to the file, somehow + raw_registry_data = file.read() + for registry_name in Deployer.contract_names: + assert registry_name in raw_registry_data + + # Ensure the Registry is JSON deserializable + registry_data = json.loads(raw_registry_data) + + # and that is has the correct number of entries + assert len(registry_data) == 9 + + # Read several records + token_record, escrow_record, dispatcher_record, *other_records = registry_data + registered_name, registered_address, registered_abi = token_record + token_agent = NucypherTokenAgent() + assert token_agent.contract_name == registered_name + assert token_agent.registry_contract_name == registered_name + assert token_agent.contract_address == registered_address # Now show that we can use contract Agency and read from the blockchain - token_agent = NucypherTokenAgent() assert token_agent.get_balance() == 0 miner_agent = MinerAgent() assert miner_agent.get_current_period() + + # and at least the others can be instantiated + assert PolicyAgent() + assert MiningAdjudicatorAgent() testerchain.sever_connection() -def test_nucypher_deploy_allocations(testerchain, click_runner, mock_allocation_infile): +def test_nucypher_deploy_allocations(testerchain, click_runner, mock_allocation_infile, token_economics): deploy_command = ('allocations', '--registry-infile', MOCK_REGISTRY_FILEPATH,