diff --git a/examples/tdec/compound_multichain_tdec.py b/examples/tdec/compound_multichain_tdec.py index 43febf9d3..95f9aac90 100644 --- a/examples/tdec/compound_multichain_tdec.py +++ b/examples/tdec/compound_multichain_tdec.py @@ -84,9 +84,9 @@ conditions = { } message = "hello world".encode() -ciphertext = enrico.encrypt_for_dkg(plaintext=message, conditions=conditions) +threshold_message_kit = enrico.encrypt_for_dkg(plaintext=message, conditions=conditions) -print(f"Encrypted message: {bytes(ciphertext).hex()}") +print(f"Encrypted message: {bytes(threshold_message_kit).hex()}") ############### # Bob @@ -103,10 +103,6 @@ bob = Bob( bob.start_learning_loop(now=True) -cleartext = bob.threshold_decrypt( - ritual_id=ritual_id, - ciphertext=ciphertext, - conditions=conditions, -) +cleartext = bob.threshold_decrypt(threshold_message_kit=threshold_message_kit) print(bytes(cleartext).decode()) diff --git a/examples/tdec/simple_tdec_local.py b/examples/tdec/simple_tdec_local.py index f50d44fc9..83bdffa12 100644 --- a/examples/tdec/simple_tdec_local.py +++ b/examples/tdec/simple_tdec_local.py @@ -26,7 +26,6 @@ threshold_message_kit = enrico.encrypt_for_dkg( ) cleartext_from_ciphertext = bob.threshold_decrypt( - ritual_id=ANYTHING_CAN_BE_PASSED_AS_RITUAL_ID, threshold_message_kit=threshold_message_kit, ) diff --git a/examples/tdec/simple_tdec_using_testnet.py b/examples/tdec/simple_tdec_using_testnet.py index 6158dfd5b..86ecb6cce 100644 --- a/examples/tdec/simple_tdec_using_testnet.py +++ b/examples/tdec/simple_tdec_using_testnet.py @@ -78,7 +78,6 @@ bob = Bob( bob.start_learning_loop(now=True) cleartext = bob.threshold_decrypt( - ritual_id=ritual_id, threshold_message_kit=threshold_message_kit, ) diff --git a/newsfragments/3213.feature.rst b/newsfragments/3213.feature.rst new file mode 100644 index 000000000..e69de29bb diff --git a/nucypher/blockchain/economics.py b/nucypher/blockchain/economics.py index cdfc182fd..9ea173afe 100644 --- a/nucypher/blockchain/economics.py +++ b/nucypher/blockchain/economics.py @@ -3,7 +3,7 @@ from typing import Optional, Tuple from web3 import Web3 from web3.types import Wei -from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent +from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent from nucypher.blockchain.eth.registry import BaseContractRegistry from nucypher.blockchain.eth.token import TToken @@ -61,7 +61,7 @@ class Economics: self.fee_rate = fee_rate @property - def pre_application_deployment_parameters(self) -> Tuple[int, ...]: + def taco_application_deployment_parameters(self) -> Tuple[int, ...]: """Cast coefficient attributes to uint256 compatible type for solidity+EVM""" deploy_parameters = ( # note: order-sensitive self.min_authorization, @@ -103,10 +103,10 @@ class EconomicsFactory: # Agents application_agent = ContractAgency.get_agent( - PREApplicationAgent, registry=registry, provider_uri=eth_provider_uri + TACoApplicationAgent, registry=registry, provider_uri=eth_provider_uri ) - # PRE Application + # TACo Application min_authorization = application_agent.get_min_authorization() min_operator_seconds = application_agent.get_min_operator_seconds() diff --git a/nucypher/blockchain/eth/__init__.py b/nucypher/blockchain/eth/__init__.py index ae68ffb22..7630effc6 100644 --- a/nucypher/blockchain/eth/__init__.py +++ b/nucypher/blockchain/eth/__init__.py @@ -1,5 +1,3 @@ - - from pathlib import Path from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION @@ -8,4 +6,5 @@ from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION BASE_DIRECTORY = Path(__file__).parent CONTRACT_REGISTRY_BASE = BASE_DIRECTORY / "contract_registry" + NO_BLOCKCHAIN_CONNECTION.bool_value(False) diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py index 63149c60b..23d5df2b8 100644 --- a/nucypher/blockchain/eth/actors.py +++ b/nucypher/blockchain/eth/actors.py @@ -33,7 +33,7 @@ from nucypher.blockchain.eth.agents import ( ContractAgency, CoordinatorAgent, NucypherTokenAgent, - PREApplicationAgent, + TACoApplicationAgent, ) from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.blockchain.eth.decorators import save_receipt, validate_checksum_address @@ -204,7 +204,7 @@ class Operator(BaseActor): self.__staking_provider_address = None # set by block_until_ready if is_me: self.application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, provider_uri=eth_provider_uri, registry=self.registry, ) @@ -324,6 +324,18 @@ class Ritualist(BaseActor): condition_provider_uris ) + self.set_provider_public_key() + + def set_provider_public_key(self): + # Here we're assuming there is one global key per node. + is_provider_key_set = self.coordinator_agent.is_provider_public_key_set( + self.staking_provider_address, + ) + if not is_provider_key_set: + self.coordinator_agent.set_provider_public_key( + self.ritual_power.public_key(), transacting_power=self.transacting_power + ) + @staticmethod def _is_permitted_condition_chain(chain_id: int) -> bool: return int(chain_id) in [int(cid) for cid in _CONDITION_CHAINS.keys()] @@ -458,7 +470,7 @@ class Ritualist(BaseActor): def perform_round_1( self, ritual_id: int, - initiator: ChecksumAddress, + authority: ChecksumAddress, participants: List[ChecksumAddress], timestamp: int, ) -> Optional[HexBytes]: @@ -501,7 +513,9 @@ class Ritualist(BaseActor): ) return None - self.log.debug(f"performing round 1 of DKG ritual #{ritual_id} from blocktime {timestamp}") + self.log.debug( + f"performing round 1 of DKG ritual #{ritual_id} from blocktime {timestamp} with authority {authority}." + ) # gather the cohort ritual = self.coordinator_agent.get_ritual(ritual_id, with_participants=True) @@ -516,7 +530,7 @@ class Ritualist(BaseActor): try: transcript = self.ritual_power.generate_transcript( nodes=nodes, - threshold=(ritual.shares // 2) + 1, # TODO: #3095 This is a constant or needs to be stored somewhere else + threshold=ritual.threshold, shares=ritual.shares, checksum_address=self.checksum_address, ritual_id=ritual_id @@ -538,7 +552,7 @@ class Ritualist(BaseActor): arrival = ritual.total_transcripts + 1 self.log.debug( f"{self.transacting_power.account[:8]} submitted a transcript for " - f"DKG ritual #{ritual_id} ({arrival}/{len(ritual.providers)}) initiated by {initiator}" + f"DKG ritual #{ritual_id} ({arrival}/{len(ritual.providers)}) with authority {authority}." ) return tx_hash @@ -589,7 +603,7 @@ class Ritualist(BaseActor): # Aggregate the transcripts try: result = self.ritual_power.aggregate_transcripts( - threshold=(ritual.shares // 2) + 1, # TODO: #3095 This is a constant or needs to be stored somewhere else + threshold=ritual.threshold, shares=ritual.shares, checksum_address=self.checksum_address, ritual_id=ritual_id, @@ -644,13 +658,12 @@ class Ritualist(BaseActor): f"ritual #{ritual_id} is missing transcripts" ) - threshold = (ritual.shares // 2) + 1 # TODO: consider the usage of local DKG artifact storage here #3052 # aggregated_transcript_bytes = self.dkg_storage.get_aggregated_transcript(ritual_id) aggregated_transcript = AggregatedTranscript.from_bytes(bytes(ritual.aggregated_transcript)) decryption_share = self.ritual_power.derive_decryption_share( nodes=nodes, - threshold=threshold, + threshold=ritual.threshold, shares=ritual.shares, checksum_address=self.checksum_address, ritual_id=ritual_id, @@ -686,7 +699,7 @@ class PolicyAuthor(NucypherTokenActor): def __init__(self, eth_provider_uri: str, *args, **kwargs): super().__init__(*args, **kwargs) self.application_agent = ContractAgency.get_agent( - PREApplicationAgent, registry=self.registry, provider_uri=eth_provider_uri + TACoApplicationAgent, registry=self.registry, provider_uri=eth_provider_uri ) def create_policy(self, *args, **kwargs): diff --git a/nucypher/blockchain/eth/agents.py b/nucypher/blockchain/eth/agents.py index 6c7026db0..7f741b60c 100644 --- a/nucypher/blockchain/eth/agents.py +++ b/nucypher/blockchain/eth/agents.py @@ -26,7 +26,12 @@ from eth_typing.evm import ChecksumAddress from eth_utils.address import to_checksum_address from hexbytes import HexBytes from nucypher_core import SessionStaticKey -from nucypher_core.ferveo import AggregatedTranscript, DkgPublicKey, Transcript +from nucypher_core.ferveo import ( + AggregatedTranscript, + DkgPublicKey, + FerveoPublicKey, + Transcript, +) from web3.contract.contract import Contract, ContractFunction from web3.types import Timestamp, TxParams, TxReceipt, Wei @@ -38,12 +43,14 @@ from nucypher.blockchain.eth.constants import ( ETH_ADDRESS_BYTE_LENGTH, NUCYPHER_TOKEN_CONTRACT_NAME, NULL_ADDRESS, - PRE_APPLICATION_CONTRACT_NAME, SUBSCRIPTION_MANAGER_CONTRACT_NAME, + TACO_APPLICATION_CONTRACT_NAME, ) from nucypher.blockchain.eth.decorators import contract_api from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory -from nucypher.blockchain.eth.registry import BaseContractRegistry +from nucypher.blockchain.eth.registry import ( + BaseContractRegistry, +) from nucypher.config.constants import ( NUCYPHER_ENVVAR_STAKING_PROVIDERS_PAGINATION_SIZE, NUCYPHER_ENVVAR_STAKING_PROVIDERS_PAGINATION_SIZE_LIGHT_NODE, @@ -382,9 +389,9 @@ class AdjudicatorAgent(EthereumContractAgent): return staking_parameters -class PREApplicationAgent(EthereumContractAgent): +class TACoApplicationAgent(EthereumContractAgent): - contract_name: str = PRE_APPLICATION_CONTRACT_NAME + contract_name: str = TACO_APPLICATION_CONTRACT_NAME DEFAULT_PROVIDERS_PAGINATION_SIZE_LIGHT_NODE = int(os.environ.get(NUCYPHER_ENVVAR_STAKING_PROVIDERS_PAGINATION_SIZE_LIGHT_NODE, default=30)) DEFAULT_PROVIDERS_PAGINATION_SIZE = int(os.environ.get(NUCYPHER_ENVVAR_STAKING_PROVIDERS_PAGINATION_SIZE, default=1000)) @@ -399,7 +406,7 @@ class PREApplicationAgent(EthereumContractAgent): @contract_api(CONTRACT_CALL) def get_min_authorization(self) -> int: - result = self.contract.functions.minAuthorization().call() + result = self.contract.functions.minimumAuthorization().call() return result @contract_api(CONTRACT_CALL) @@ -574,6 +581,38 @@ class CoordinatorAgent(EthereumContractAgent): contract_name: str = "Coordinator" _proxy_name = None + class G2Point(NamedTuple): + """ + Coordinator contract representation of Ferveo Participant public key. + """ + + # TODO validation of these if used directly + word0: bytes # 32 bytes + word1: bytes # 32 bytes + word2: bytes # 32 bytes + + @classmethod + def from_public_key(cls, public_key: FerveoPublicKey): + return cls.from_bytes(bytes(public_key)) + + @classmethod + def from_bytes(cls, data: bytes): + if len(data) != FerveoPublicKey.serialized_size(): + raise ValueError( + f"Invalid byte length; expected {FerveoPublicKey.serialized_size()} bytes but got {len(data)} bytes for G2Point" + ) + return cls(word0=data[:32], word1=data[32:64], word2=data[64:96]) + + def to_public_key(self) -> FerveoPublicKey: + data = bytes(self) + if not data: + return None + + return FerveoPublicKey.from_bytes(data) + + def __bytes__(self): + return self.word0 + self.word1 + self.word2 + @dataclass class Ritual: @@ -623,8 +662,13 @@ class CoordinatorAgent(EthereumContractAgent): return self.word0 + self.word1 initiator: ChecksumAddress + authority: ChecksumAddress + access_controller: ChecksumAddress dkg_size: int init_timestamp: int + end_timestamp: int + threshold: int + total_transcripts: int = 0 total_aggregations: int = 0 public_key: G1Point = None @@ -666,17 +710,21 @@ class CoordinatorAgent(EthereumContractAgent): result = self.contract.functions.rituals(int(ritual_id)).call() ritual = self.Ritual( initiator=ChecksumAddress(result[0]), - dkg_size=result[1], - init_timestamp=result[2], + init_timestamp=result[1], + end_timestamp=result[2], total_transcripts=result[3], total_aggregations=result[4], - aggregation_mismatch=result[6], - aggregated_transcript=bytes(result[7]), + authority=ChecksumAddress(result[5]), + dkg_size=result[6], + threshold=result[7], + aggregation_mismatch=result[8], + access_controller=ChecksumAddress(result[9]), + aggregated_transcript=bytes(result[11]), participants=[], # solidity does not return sub-structs ) # public key - ritual.public_key = self.Ritual.G1Point(result[5][0], result[5][1]) + ritual.public_key = self.Ritual.G1Point(result[10][0], result[10][1]) # participants if with_participants: @@ -703,6 +751,16 @@ class CoordinatorAgent(EthereumContractAgent): participants.append(participant) return participants + @contract_api(CONTRACT_CALL) + def get_provider_public_key( + self, provider: ChecksumAddress, ritual_id: int + ) -> FerveoPublicKey: + result = self.contract.functions.getProviderPublicKey( + provider, ritual_id + ).call() + g2_point = self.G2Point(result[0], result[1], result[2]) + return g2_point.to_public_key() + @contract_api(CONTRACT_CALL) def number_of_rituals(self) -> int: result = self.contract.functions.numberOfRituals().call() @@ -723,12 +781,47 @@ class CoordinatorAgent(EthereumContractAgent): ) return participant + @contract_api(CONTRACT_CALL) + def is_encryption_authorized( + self, ritual_id: int, evidence: bytes, ciphertext_header: bytes + ) -> bool: + """ + This contract read is relayed through coordinator to the access controller + contract associated with a given ritual. + """ + result = self.contract.functions.isEncryptionAuthorized( + ritual_id, evidence, ciphertext_header + ).call() + return result + + @contract_api(CONTRACT_CALL) + def is_provider_public_key_set(self, staking_provider: ChecksumAddress) -> bool: + result = self.contract.functions.isProviderPublicKeySet(staking_provider).call() + return result + + @contract_api(TRANSACTION) + def set_provider_public_key( + self, public_key: FerveoPublicKey, transacting_power: TransactingPower + ) -> TxReceipt: + contract_function = self.contract.functions.setProviderPublicKey( + self.G2Point.from_public_key(public_key) + ) + receipt = self.blockchain.send_transaction( + contract_function=contract_function, transacting_power=transacting_power + ) + return receipt + @contract_api(TRANSACTION) def initiate_ritual( - self, providers: List[ChecksumAddress], transacting_power: TransactingPower + self, + providers: List[ChecksumAddress], + authority: ChecksumAddress, + duration: int, + access_controller: ChecksumAddress, + transacting_power: TransactingPower, ) -> TxReceipt: contract_function: ContractFunction = self.contract.functions.initiateRitual( - providers + providers, authority, duration, access_controller ) receipt = self.blockchain.send_transaction( contract_function=contract_function, transacting_power=transacting_power @@ -766,7 +859,7 @@ class CoordinatorAgent(EthereumContractAgent): contract_function: ContractFunction = self.contract.functions.postAggregation( ritualId=ritual_id, aggregatedTranscript=bytes(aggregated_transcript), - publicKey=self.Ritual.G1Point.from_dkg_public_key(public_key), + dkgPublicKey=self.Ritual.G1Point.from_dkg_public_key(public_key), decryptionRequestStaticKey=bytes(participant_public_key), ) receipt = self.blockchain.send_transaction( @@ -776,6 +869,21 @@ class CoordinatorAgent(EthereumContractAgent): ) return receipt + @contract_api(TRANSACTION) + def get_ritual_initiation_cost( + self, providers: List[ChecksumAddress], duration: int + ) -> Wei: + result = self.contract.functions.getRitualInitiationCost( + providers, duration + ).call() + return Wei(result) + + @contract_api(TRANSACTION) + def get_ritual_id_from_public_key(self, public_key: DkgPublicKey) -> int: + g1_point = self.Ritual.G1Point.from_dkg_public_key(public_key) + result = self.contract.functions.getRitualIdFromPublicKey(g1_point).call() + return result + def get_ritual_public_key(self, ritual_id: int) -> DkgPublicKey: if self.get_ritual_status(ritual_id=ritual_id) != self.Ritual.Status.FINALIZED: # TODO should we raise here instead? @@ -787,13 +895,6 @@ class CoordinatorAgent(EthereumContractAgent): return ritual.public_key.to_dkg_public_key() - def is_encryption_authorized( - self, ritual_id: int, evidence: bytes, digest: bytes - ) -> bool: - # TODO: actually call contract. - # get ritual -> get access controller -> call isAuthorized(ritualId, evidence, digest) - return True - class ContractAgency: """Where agents live and die.""" @@ -843,8 +944,6 @@ class ContractAgency: if name == NUCYPHER_TOKEN_CONTRACT_NAME: # TODO: Perhaps rename NucypherTokenAgent name = "NucypherToken" - if name == PRE_APPLICATION_CONTRACT_NAME: - name = "PREApplication" # TODO not needed once full PRE Application is used agent_name = f"{name}Agent" return agent_name @@ -930,7 +1029,9 @@ class StakingProvidersReservoir: def draw(self, quantity): if quantity > len(self): - raise PREApplicationAgent.NotEnoughStakingProviders(f'Cannot sample {quantity} out of {len(self)} total staking providers') + raise TACoApplicationAgent.NotEnoughStakingProviders( + f"Cannot sample {quantity} out of {len(self)} total staking providers" + ) return self._sampler.sample_no_replacement(self._rng, quantity) diff --git a/nucypher/blockchain/eth/constants.py b/nucypher/blockchain/eth/constants.py index c8f245f3c..4513b9a06 100644 --- a/nucypher/blockchain/eth/constants.py +++ b/nucypher/blockchain/eth/constants.py @@ -4,20 +4,20 @@ # Contract Names # -DISPATCHER_CONTRACT_NAME = 'Dispatcher' -NUCYPHER_TOKEN_CONTRACT_NAME = 'NuCypherToken' -STAKING_ESCROW_CONTRACT_NAME = 'StakingEscrow' -STAKING_ESCROW_STUB_CONTRACT_NAME = 'StakingEscrowStub' -ADJUDICATOR_CONTRACT_NAME = 'Adjudicator' -PRE_APPLICATION_CONTRACT_NAME = 'SimplePREApplication' # TODO: Use the real PREApplication -SUBSCRIPTION_MANAGER_CONTRACT_NAME = 'SubscriptionManager' +DISPATCHER_CONTRACT_NAME = "Dispatcher" +NUCYPHER_TOKEN_CONTRACT_NAME = "NuCypherToken" +STAKING_ESCROW_CONTRACT_NAME = "StakingEscrow" +STAKING_ESCROW_STUB_CONTRACT_NAME = "StakingEscrowStub" +ADJUDICATOR_CONTRACT_NAME = "Adjudicator" +TACO_APPLICATION_CONTRACT_NAME = "TACoApplication" +SUBSCRIPTION_MANAGER_CONTRACT_NAME = "SubscriptionManager" NUCYPHER_CONTRACT_NAMES = ( NUCYPHER_TOKEN_CONTRACT_NAME, STAKING_ESCROW_CONTRACT_NAME, ADJUDICATOR_CONTRACT_NAME, DISPATCHER_CONTRACT_NAME, - PRE_APPLICATION_CONTRACT_NAME, + TACO_APPLICATION_CONTRACT_NAME, SUBSCRIPTION_MANAGER_CONTRACT_NAME ) diff --git a/nucypher/characters/chaotic.py b/nucypher/characters/chaotic.py index 7c8906d12..c03fe669c 100644 --- a/nucypher/characters/chaotic.py +++ b/nucypher/characters/chaotic.py @@ -259,6 +259,9 @@ class _UpAndDownInTheWater(Bob, DKGOmniscient): def __init__(self, session_seed=None, *args, **kwargs): super().__init__(*args, **kwargs) + def get_ritual_id_from_public_key(self, public_key) -> int: + return 55 # any ritual id can be returned here + def get_ritual_from_id(self, ritual_id): return self._dkg_insight.fake_ritual diff --git a/nucypher/characters/lawful.py b/nucypher/characters/lawful.py index a829b8669..682734465 100644 --- a/nucypher/characters/lawful.py +++ b/nucypher/characters/lawful.py @@ -74,13 +74,14 @@ from nucypher.blockchain.eth import actors from nucypher.blockchain.eth.agents import ( ContractAgency, CoordinatorAgent, - PREApplicationAgent, + TACoApplicationAgent, ) from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory from nucypher.blockchain.eth.registry import ( BaseContractRegistry, InMemoryContractRegistry, ) +from nucypher.blockchain.eth.signers import Signer from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.characters.banners import ( ALICE_BANNER, @@ -677,7 +678,9 @@ class Bob(Character): ) if len(successes) < threshold: - raise Ursula.NotEnoughUrsulas(f"Not enough Ursulas to decrypt: {failures}") + raise Ursula.NotEnoughUrsulas( + f"Threshold of Ursulas unable to decrypt: {failures}" + ) self.log.debug("Got enough shares to decrypt.") if decryption_request.variant == FerveoVariant.Precomputed: @@ -701,25 +704,37 @@ class Bob(Character): gathered_shares[provider_address] = decryption_share return gathered_shares - def get_ritual_from_id(self, ritual_id): - # blockchain reads: get the DKG parameters and the cohort. + def _get_coordinator_agent(self) -> CoordinatorAgent: if not self.coordinator_agent: raise ValueError( "No coordinator provider URI provided in Bob's constructor." ) - ritual = self.coordinator_agent.get_ritual(ritual_id, with_participants=True) + return self.coordinator_agent + + def get_ritual_id_from_public_key(self, public_key: DkgPublicKey) -> int: + ritual_id = self._get_coordinator_agent().get_ritual_id_from_public_key( + public_key + ) + return ritual_id + + def get_ritual_from_id(self, ritual_id) -> CoordinatorAgent.Ritual: + ritual = self._get_coordinator_agent().get_ritual( + ritual_id, with_participants=True + ) return ritual def threshold_decrypt( self, - ritual_id: int, threshold_message_kit: ThresholdMessageKit, context: Optional[dict] = None, ursulas: Optional[List["Ursula"]] = None, peering_timeout: int = 60, ) -> bytes: - ritual = self.get_ritual_from_id(ritual_id) + ritual_id = self.get_ritual_id_from_public_key( + public_key=threshold_message_kit.acp.public_key + ) + ritual = self.get_ritual_from_id(ritual_id=ritual_id) if not ursulas: # P2P: if the Ursulas are not provided, we need to resolve them from published records. @@ -735,11 +750,6 @@ class Bob(Character): self.remember_node(ursula) variant = self._default_dkg_variant - threshold = ( - (ritual.shares // 2) + 1 - if variant == FerveoVariant.Simple - else ritual.shares - ) # TODO: #3095 get this from the ritual / put it on-chain? decryption_request = self.__make_decryption_request( ritual_id=ritual_id, @@ -752,7 +762,7 @@ class Bob(Character): decryption_request=decryption_request, participant_public_keys=participant_public_keys, cohort=ursulas, - threshold=threshold, + threshold=ritual.threshold, ) return self.__decrypt( @@ -1305,7 +1315,7 @@ class Ursula(Teacher, Character, actors.Operator, actors.Ritualist): # Check the node's stake (optional) if minimum_stake > 0 and staking_provider_address: application_agent = ContractAgency.get_agent( - PREApplicationAgent, provider_uri=provider_uri, registry=registry + TACoApplicationAgent, provider_uri=provider_uri, registry=registry ) seednode_stake = application_agent.get_authorized_stake( staking_provider=staking_provider_address @@ -1450,9 +1460,13 @@ class Enrico: banner = ENRICO_BANNER - def __init__(self, encrypting_key: Union[PublicKey, DkgPublicKey]): - self.signing_power = SigningPower() - self._policy_pubkey = encrypting_key + def __init__( + self, + encrypting_key: Union[PublicKey, DkgPublicKey], + signer: Optional[Signer] = None, + ): + self.signer = signer + self._encrypting_key = encrypting_key self.log = Logger(f"{self.__class__.__name__}-{encrypting_key}") self.log.info(self.banner.format(encrypting_key)) @@ -1470,20 +1484,29 @@ class Enrico: return message_kit def encrypt_for_dkg( - self, plaintext: bytes, conditions: Lingo + self, + plaintext: bytes, + conditions: Lingo, ) -> ThresholdMessageKit: + if not self.signer: + raise TypeError("This Enrico doesn't have a signer.") + validate_condition_lingo(conditions) conditions_json = json.dumps(conditions) access_conditions = Conditions(conditions_json) + # encrypt for DKG ciphertext, auth_data = encrypt_for_dkg( plaintext, self.policy_pubkey, access_conditions ) - # authentication for AllowLogic - # TODO Replace with `Signer` to be passed as parameter + # authentication message for TACo header_hash = keccak_digest(bytes(ciphertext.header)) - authorization = self.signing_power.keypair.sign(header_hash).to_be_bytes() + authorization = bytes( + self.signer.sign_message( + message=header_hash, account=self.signer.accounts[0] + ) + ) return ThresholdMessageKit( ciphertext=ciphertext, @@ -1502,8 +1525,8 @@ class Enrico: @property def policy_pubkey(self): - if not self._policy_pubkey: + if not self._encrypting_key: raise TypeError( "This Enrico doesn't know which policy encrypting key he used. Oh well." ) - return self._policy_pubkey + return self._encrypting_key diff --git a/nucypher/characters/unlawful.py b/nucypher/characters/unlawful.py index 8ae24f5b1..42c255b66 100644 --- a/nucypher/characters/unlawful.py +++ b/nucypher/characters/unlawful.py @@ -11,7 +11,6 @@ from nucypher.config.constants import TEMPORARY_DOMAIN from nucypher.crypto.powers import CryptoPower from nucypher.exceptions import DevelopmentInstallationRequired from nucypher.policy.payment import FreeReencryptions -from tests.constants import TEST_ETH_PROVIDER_URI, TESTERCHAIN_CHAIN_ID class Vladimir(Ursula): @@ -40,12 +39,16 @@ class Vladimir(Ursula): try: from tests.utils.middleware import EvilMiddleWare except ImportError: - raise DevelopmentInstallationRequired(importable_name='tests.utils.middleware.EvilMiddleWare') - cls.network_middleware = EvilMiddleWare(eth_provider_uri=TEST_ETH_PROVIDER_URI) + raise DevelopmentInstallationRequired( + importable_name="tests.utils.middleware.EvilMiddleWare" + ) + blockchain = target_ursula.application_agent.blockchain + cls.network_middleware = EvilMiddleWare( + eth_provider_uri=blockchain.eth_provider_uri + ) crypto_power = CryptoPower(power_ups=target_ursula._default_crypto_powerups) - blockchain = target_ursula.application_agent.blockchain cls.attach_transacting_key(blockchain=blockchain) # Vladimir does not care about payment. @@ -53,10 +56,12 @@ class Vladimir(Ursula): bogus_payment_method.provider = Mock() bogus_payment_method.agent = Mock() bogus_payment_method.network = TEMPORARY_DOMAIN - bogus_payment_method.agent.blockchain.client.chain_id = TESTERCHAIN_CHAIN_ID + bogus_payment_method.agent.blockchain.client.chain_id = ( + blockchain.client.chain_id + ) mock.patch( "mock.interfaces.MockBlockchain.client.chain_id", - new_callable=mock.PropertyMock(return_value=TESTERCHAIN_CHAIN_ID), + new_callable=mock.PropertyMock(return_value=blockchain.client.chain_id), ) vladimir = cls(is_me=True, diff --git a/nucypher/cli/commands/status.py b/nucypher/cli/commands/status.py index d684c174a..5507cdd4d 100644 --- a/nucypher/cli/commands/status.py +++ b/nucypher/cli/commands/status.py @@ -10,7 +10,7 @@ from nucypher.blockchain.eth.agents import ( ContractAgency, EthereumContractAgent, NucypherTokenAgent, - PREApplicationAgent, + TACoApplicationAgent, ) from nucypher.blockchain.eth.constants import AVERAGE_BLOCK_TIME_IN_SECONDS from nucypher.blockchain.eth.networks import NetworksInventory @@ -41,7 +41,7 @@ STAKING_ESCROW = 'StakingEscrow' POLICY_MANAGER = 'PolicyManager' CONTRACT_NAMES = [ - PREApplicationAgent.contract_name, + TACoApplicationAgent.contract_name, NucypherTokenAgent.contract_name, STAKING_ESCROW, POLICY_MANAGER @@ -120,15 +120,23 @@ def network(general_config, registry_options): paint_contract_status(registry, emitter=emitter) -@status.command('pre') +@status.command("taco") @group_registry_options @option_staking_provider @group_general_config def staking_providers(general_config, registry_options, staking_provider_address): """Show relevant information about staking providers.""" - emitter, registry, blockchain = registry_options.setup(general_config=general_config) - application_agent = ContractAgency.get_agent(PREApplicationAgent, registry=registry) - staking_providers_list = [staking_provider_address] if staking_provider_address else application_agent.get_staking_providers() + emitter, registry, blockchain = registry_options.setup( + general_config=general_config + ) + application_agent = ContractAgency.get_agent( + TACoApplicationAgent, registry=registry + ) + staking_providers_list = ( + [staking_provider_address] + if staking_provider_address + else application_agent.get_staking_providers() + ) emitter.echo(staking_providers_list) # TODO: staking provider painter # paint_stakers(emitter=emitter, stakers=staking_providers_list, registry=registry) @@ -164,8 +172,8 @@ def events(general_config, registry_options, contract_name, from_block, to_block if event_name: raise click.BadOptionUsage(option_name='--event-name', message=click.style('--event-name requires --contract-name', fg="red")) # FIXME should we force a contract name to be specified? - # default to PREApplication contract - contract_names = [PREApplicationAgent.contract_name] + # default to TACoApplication contract + contract_names = [TACoApplicationAgent.contract_name] else: contract_names = [contract_name] diff --git a/nucypher/cli/literature.py b/nucypher/cli/literature.py index 5d928b20e..cd84ca113 100644 --- a/nucypher/cli/literature.py +++ b/nucypher/cli/literature.py @@ -177,7 +177,6 @@ WARNING: --etherscan is disabled. If you want to see deployed contracts and TXs """ - # # Ursula # @@ -186,7 +185,7 @@ SUCCESSFUL_MANUALLY_SAVE_METADATA = "Successfully saved node metadata to {metada # -# PREApplication +# TACoApplication # STAKING_PROVIDER_UNAUTHORIZED = '{provider} is not authorized.' diff --git a/nucypher/cli/painting/status.py b/nucypher/cli/painting/status.py index 1656df6af..aa3dbb3f4 100644 --- a/nucypher/cli/painting/status.py +++ b/nucypher/cli/painting/status.py @@ -4,14 +4,16 @@ from web3.main import Web3 from nucypher.blockchain.eth.agents import ( ContractAgency, - PREApplicationAgent, + TACoApplicationAgent, ) from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory def paint_contract_status(registry, emitter): blockchain = BlockchainInterfaceFactory.get_interface() - application_agent = ContractAgency.get_agent(PREApplicationAgent, registry=registry) + application_agent = ContractAgency.get_agent( + TACoApplicationAgent, registry=registry + ) contracts = f""" | Contract Deployments | {application_agent.contract_name} .............. {application_agent.contract_address} @@ -25,7 +27,7 @@ Registry ................. {registry.filepath} """ staking = f""" -| PREApplication | +| TACoApplication | Staking Provider Population ....... {application_agent.get_staking_providers_population()} """ diff --git a/nucypher/network/nodes.py b/nucypher/network/nodes.py index 45be30c73..c3abbca99 100644 --- a/nucypher/network/nodes.py +++ b/nucypher/network/nodes.py @@ -26,7 +26,7 @@ from twisted.internet.defer import Deferred from nucypher import characters from nucypher.acumen.nicknames import Nickname from nucypher.acumen.perception import FleetSensor -from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent +from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.blockchain.eth.networks import NetworksInventory from nucypher.blockchain.eth.registry import BaseContractRegistry @@ -1048,8 +1048,8 @@ class Teacher: the case that the "staking provider" isn't "staking" (e.g., all her tokens have been slashed). """ application_agent = ContractAgency.get_agent( - PREApplicationAgent, provider_uri=provider_uri, registry=registry - ) # type: PREApplicationAgent + TACoApplicationAgent, provider_uri=provider_uri, registry=registry + ) # type: TACoApplicationAgent staking_provider_address = application_agent.get_staking_provider_from_operator(operator_address=self.operator_address) if staking_provider_address == NULL_ADDRESS: raise self.UnbondedOperator(f"Operator {self.operator_address} is not bonded") @@ -1061,8 +1061,8 @@ class Teacher: As a follow-up, this checks that the staking provider is, indeed, staking. """ application_agent = ContractAgency.get_agent( - PREApplicationAgent, registry=registry, provider_uri=eth_provider_uri - ) # type: PREApplicationAgent + TACoApplicationAgent, registry=registry, provider_uri=eth_provider_uri + ) # type: TACoApplicationAgent is_staking = application_agent.is_authorized(staking_provider=self.checksum_address) # checksum address here is staking provider return is_staking diff --git a/nucypher/network/server.py b/nucypher/network/server.py index 8f937d631..ce5b9888b 100644 --- a/nucypher/network/server.py +++ b/nucypher/network/server.py @@ -20,7 +20,6 @@ from nucypher_core import ( from nucypher.config.constants import MAX_UPLOAD_CONTENT_LENGTH from nucypher.crypto.keypairs import DecryptingKeypair from nucypher.crypto.signing import InvalidSignature -from nucypher.crypto.utils import keccak_digest from nucypher.network.exceptions import NodeSeemsToBeDown from nucypher.network.nodes import NodeSprout from nucypher.network.protocols import InterfaceInfo @@ -166,13 +165,12 @@ def _make_rest_app(this_node, log: Logger) -> Flask: ciphertext_header = decryption_request.ciphertext_header - # check whether enrico is authorized - AllowLogic + # check whether enrico is authorized authorization = decryption_request.acp.authorization - ciphertext_header_hash = keccak_digest(bytes(ciphertext_header)) if not this_node.coordinator_agent.is_encryption_authorized( ritual_id=decryption_request.ritual_id, evidence=authorization, - digest=ciphertext_header_hash, + ciphertext_header=bytes(ciphertext_header), ): return Response( f"Encrypted data not authorized for ritual {decryption_request.ritual_id}", diff --git a/nucypher/network/trackers.py b/nucypher/network/trackers.py index 0e492d56a..4e3eb063d 100644 --- a/nucypher/network/trackers.py +++ b/nucypher/network/trackers.py @@ -9,7 +9,7 @@ from twisted.internet.task import LoopingCall from twisted.python.failure import Failure from nucypher import characters -from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent +from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.network.exceptions import NodeSeemsToBeDown from nucypher.network.middleware import RestMiddleware @@ -31,7 +31,7 @@ class OperatorBondedTracker(SimpleTask): def run(self) -> None: application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=self._ursula.registry, provider_uri=self._ursula.eth_provider_uri, ) diff --git a/nucypher/policy/reservoir.py b/nucypher/policy/reservoir.py index 95fb8834c..b3cd753fb 100644 --- a/nucypher/policy/reservoir.py +++ b/nucypher/policy/reservoir.py @@ -3,13 +3,13 @@ from typing import Iterable, List, Optional from eth_typing import ChecksumAddress from nucypher.blockchain.eth.agents import ( - PREApplicationAgent, StakingProvidersReservoir, + TACoApplicationAgent, ) def make_staking_provider_reservoir( - application_agent: PREApplicationAgent, + application_agent: TACoApplicationAgent, exclude_addresses: Optional[Iterable[ChecksumAddress]] = None, include_addresses: Optional[Iterable[ChecksumAddress]] = None, pagination_size: Optional[int] = None, @@ -22,7 +22,7 @@ def make_staking_provider_reservoir( without_set = set(include_addresses) | set(exclude_addresses or ()) try: reservoir = application_agent.get_staking_provider_reservoir(without=without_set, pagination_size=pagination_size) - except PREApplicationAgent.NotEnoughStakingProviders: + except TACoApplicationAgent.NotEnoughStakingProviders: # TODO: do that in `get_staking_provider_reservoir()`? reservoir = StakingProvidersReservoir({}) diff --git a/nucypher/utilities/prometheus/collector.py b/nucypher/utilities/prometheus/collector.py index b7e569342..7b4e43abc 100644 --- a/nucypher/utilities/prometheus/collector.py +++ b/nucypher/utilities/prometheus/collector.py @@ -19,7 +19,7 @@ from nucypher.blockchain.eth import actors from nucypher.blockchain.eth.agents import ( ContractAgency, EthereumContractAgent, - PREApplicationAgent, + TACoApplicationAgent, ) from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory from nucypher.blockchain.eth.registry import BaseContractRegistry @@ -178,7 +178,7 @@ class StakingProviderMetricsCollector(BaseMetricsCollector): def _collect_internal(self) -> None: application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=self.contract_registry, provider_uri=self.eth_provider_uri, ) diff --git a/scripts/hooks/nucypher_agents.py b/scripts/hooks/nucypher_agents.py index 2b5509940..9fc233671 100644 --- a/scripts/hooks/nucypher_agents.py +++ b/scripts/hooks/nucypher_agents.py @@ -23,10 +23,11 @@ import sys from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION -from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent from nucypher.blockchain.eth.agents import ( - PREApplicationAgent, - SubscriptionManagerAgent + ContractAgency, + NucypherTokenAgent, + SubscriptionManagerAgent, + TACoApplicationAgent, ) from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory from nucypher.blockchain.eth.registry import InMemoryContractRegistry @@ -62,9 +63,15 @@ blockchain.connect() registry = InMemoryContractRegistry.from_latest_publication(network=network) emitter.echo(f"NOTICE: Connecting to {network} network", color='yellow') -token_agent = ContractAgency.get_agent(agent_class=NucypherTokenAgent, registry=registry) # type: NucypherTokenAgent -application_agent = ContractAgency.get_agent(agent_class=PREApplicationAgent, registry=registry) # type: PREApplicationAgent -subscription_agent = ContractAgency.get_agent(agent_class=SubscriptionManagerAgent, registry=registry) # type: SubscriptionManagerAgent +token_agent = ContractAgency.get_agent( + agent_class=NucypherTokenAgent, registry=registry +) # type: NucypherTokenAgent +application_agent = ContractAgency.get_agent( + agent_class=TACoApplicationAgent, registry=registry +) # type: TACoApplicationAgent +subscription_agent = ContractAgency.get_agent( + agent_class=SubscriptionManagerAgent, registry=registry +) # type: SubscriptionManagerAgent -message = f"NuCypher agents pre-loaded in variables 'token_agent', 'subscription_agent' and 'application_agent'" +message = "NuCypher agents pre-loaded in variables 'token_agent', 'subscription_agent' and 'application_agent'" emitter.echo(message=message, color='green') diff --git a/scripts/hooks/nucypher_dkg.py b/scripts/hooks/nucypher_dkg.py index a81f1b7a4..44ff31e67 100644 --- a/scripts/hooks/nucypher_dkg.py +++ b/scripts/hooks/nucypher_dkg.py @@ -9,7 +9,7 @@ from web3 import Web3 from nucypher.blockchain.eth.agents import ( ContractAgency, CoordinatorAgent, - PREApplicationAgent, + TACoApplicationAgent, ) from nucypher.blockchain.eth.registry import InMemoryContractRegistry from nucypher.blockchain.eth.signers import Signer @@ -145,10 +145,10 @@ def nucypher_dkg( network=eth_staking_network ) application_agent = ContractAgency.get_agent( - agent_class=PREApplicationAgent, + agent_class=TACoApplicationAgent, registry=staking_network_registry, provider_uri=eth_provider_uri, - ) # type: PREApplicationAgent + ) # type: TACoApplicationAgent # # Initial Ritual @@ -313,7 +313,6 @@ def nucypher_dkg( bob.start_learning_loop(now=True) cleartext = bob.threshold_decrypt( - ritual_id=ritual_id, threshold_message_kit=threshold_message_kit, ) diff --git a/tests/acceptance/actors/test_dkg_ritual.py b/tests/acceptance/actors/test_dkg_ritual.py index 7d1ff20a8..bee7008c5 100644 --- a/tests/acceptance/actors/test_dkg_ritual.py +++ b/tests/acceptance/actors/test_dkg_ritual.py @@ -2,12 +2,11 @@ import pytest import pytest_twisted from twisted.internet.threads import deferToThread -from nucypher.blockchain.eth.agents import ContractAgency, CoordinatorAgent +from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.blockchain.eth.trackers.dkg import EventScannerTask -from nucypher.characters.lawful import Enrico +from nucypher.characters.lawful import Enrico, Ursula from nucypher.policy.conditions.lingo import ConditionLingo -from tests.acceptance.constants import APE_TEST_CHAIN_ID -from tests.constants import TEST_ETH_PROVIDER_URI +from tests.constants import APE_TEST_CHAIN_ID # constants DKG_SIZE = 4 @@ -29,6 +28,8 @@ CONDITIONS = { }, } +DURATION = 48 * 60 * 60 + @pytest.fixture(scope='module') def cohort(ursulas): @@ -38,26 +39,40 @@ def cohort(ursulas): return nodes -@pytest.fixture(scope='module') -def coordinator_agent(testerchain, test_registry): - """Creates a coordinator agent""" - return ContractAgency.get_agent( - CoordinatorAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI - ) - - @pytest_twisted.inlineCallbacks() -def test_ursula_ritualist(testerchain, coordinator_agent, cohort, alice, bob): +def test_ursula_ritualist( + testerchain, + coordinator_agent, + global_allow_list, + cohort, + initiator, + bob, + ritual_token, +): """Tests the DKG and the encryption/decryption of a message""" + signer = Web3Signer(client=testerchain.client) # Round 0 - Initiate the ritual def initialize(): """Initiates the ritual""" print("==================== INITIALIZING ====================") cohort_staking_provider_addresses = list(u.checksum_address for u in cohort) + + # Approve the ritual token for the coordinator agent to spend + amount = coordinator_agent.get_ritual_initiation_cost( + providers=cohort_staking_provider_addresses, duration=DURATION + ) + tx = ritual_token.functions.approve( + coordinator_agent.contract_address, amount + ).transact({"from": initiator.transacting_power.account}) + testerchain.wait_for_receipt(tx) + receipt = coordinator_agent.initiate_ritual( providers=cohort_staking_provider_addresses, - transacting_power=alice.transacting_power + authority=initiator.transacting_power.account, + duration=DURATION, + access_controller=global_allow_list.address, + transacting_power=initiator.transacting_power, ) return receipt @@ -121,21 +136,42 @@ def test_ursula_ritualist(testerchain, coordinator_agent, cohort, alice, bob): # prepare message and conditions plaintext = PLAINTEXT.encode() + # create Enrico + enrico = Enrico(encrypting_key=encrypting_key, signer=signer) + # encrypt - # print(f'encrypting for DKG with key {bytes(encrypting_key.to_bytes()).hex()}') - enrico = Enrico(encrypting_key=encrypting_key) + print(f"encrypting for DKG with key {bytes(encrypting_key).hex()}") threshold_message_kit = enrico.encrypt_for_dkg( plaintext=plaintext, conditions=CONDITIONS ) + + return threshold_message_kit + + def test_unauthorized_decrypt(threshold_message_kit): + """Attempts to decrypt a message before Enrico is authorized to use the ritual""" + print("======== DKG DECRYPTION UNAUTHORIZED ENCRYPTION ========") + # ritual_id, ciphertext, conditions are obtained from the side channel + bob.start_learning_loop(now=True) + with pytest.raises(Ursula.NotEnoughUrsulas): + _ = bob.threshold_decrypt( + threshold_message_kit=threshold_message_kit, + peering_timeout=0, + ) + return threshold_message_kit def test_decrypt(threshold_message_kit): """Decrypts a message and checks that it matches the original plaintext""" + # authorize Enrico to encrypt for ritual + tx = global_allow_list.functions.authorize( + RITUAL_ID, [signer.accounts[0]] + ).transact({"from": initiator.transacting_power.account}) + testerchain.wait_for_receipt(tx) + print("==================== DKG DECRYPTION ====================") # ritual_id, ciphertext, conditions are obtained from the side channel bob.start_learning_loop(now=True) cleartext = bob.threshold_decrypt( - ritual_id=RITUAL_ID, threshold_message_kit=threshold_message_kit, peering_timeout=0, ) @@ -154,6 +190,7 @@ def test_ursula_ritualist(testerchain, coordinator_agent, cohort, alice, bob): block_until_dkg_finalized, test_finality, test_encrypt, + test_unauthorized_decrypt, test_decrypt, ] diff --git a/tests/acceptance/actors/test_ritual_tracker.py b/tests/acceptance/actors/test_ritual_tracker.py index e6c3e1c6a..e1582e990 100644 --- a/tests/acceptance/actors/test_ritual_tracker.py +++ b/tests/acceptance/actors/test_ritual_tracker.py @@ -167,7 +167,7 @@ def test_get_participation_state_start_ritual(cohort, get_random_checksum_addres start_ritual_event = agent.contract.events.StartRitual() # create args data - args_dict["initiator"] = get_random_checksum_address() + args_dict["authority"] = get_random_checksum_address() args_dict["participants"] = [ get_random_checksum_address(), get_random_checksum_address(), @@ -359,7 +359,6 @@ def test_get_participation_state_end_ritual_participation_not_already_tracked( end_ritual_event = agent.contract.events.EndRitual() # create args data - args_dict["initiator"] = get_random_checksum_address() args_dict["successful"] = True # ensure that test matches latest event information @@ -492,7 +491,6 @@ def test_get_participation_state_end_ritual_participation_already_tracked( end_ritual_event = agent.contract.events.EndRitual() # create args data - args_dict["initiator"] = get_random_checksum_address() args_dict["successful"] = True # ensure that test matches latest event information @@ -607,7 +605,7 @@ def test_get_participation_state_purge_expired_cache_entries( start_ritual_event = agent.contract.events.StartRitual() args_dict = { "ritualId": ritual_id_1, - "initiator": get_random_checksum_address(), + "authority": get_random_checksum_address(), "participants": [ get_random_checksum_address(), get_random_checksum_address(), diff --git a/tests/acceptance/actors/test_ursula_operator.py b/tests/acceptance/actors/test_ursula_operator.py index 40e7a4622..4c29d55f3 100644 --- a/tests/acceptance/actors/test_ursula_operator.py +++ b/tests/acceptance/actors/test_ursula_operator.py @@ -11,7 +11,7 @@ from web3.middleware.simulate_unmined_transaction import ( ) from nucypher.blockchain.eth.actors import Operator -from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent +from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.blockchain.eth.trackers.pre import WorkTracker, WorkTrackerBase @@ -36,7 +36,7 @@ def test_ursula_operator_confirmation( test_registry, ): application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) @@ -48,8 +48,8 @@ def test_ursula_operator_confirmation( # make an staking_providers and some stakes tx = threshold_staking.functions.setRoles(staking_provider).transact() testerchain.wait_for_receipt(tx) - tx = threshold_staking.functions.setStakes( - staking_provider, min_authorization, 0, 0 + tx = threshold_staking.functions.authorizationIncreased( + staking_provider, 0, min_authorization ).transact() testerchain.wait_for_receipt(tx) @@ -96,7 +96,7 @@ def test_ursula_operator_confirmation_autopilot( test_registry, ): application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) @@ -112,8 +112,8 @@ def test_ursula_operator_confirmation_autopilot( # make an staking_providers and some stakes tx = threshold_staking.functions.setRoles(staking_provider2).transact() testerchain.wait_for_receipt(tx) - tx = threshold_staking.functions.setStakes( - staking_provider2, min_authorization, 0, 0 + tx = threshold_staking.functions.authorizationIncreased( + staking_provider2, 0, min_authorization ).transact() testerchain.wait_for_receipt(tx) @@ -170,7 +170,7 @@ def test_work_tracker( test_registry, ): application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) @@ -212,8 +212,8 @@ def test_work_tracker( # make an staking_providers and some stakes tx = threshold_staking.functions.setRoles(staking_provider3).transact() testerchain.wait_for_receipt(tx) - tx = threshold_staking.functions.setStakes( - staking_provider3, min_authorization, 0, 0 + tx = threshold_staking.functions.authorizationIncreased( + staking_provider3, 0, min_authorization ).transact() testerchain.wait_for_receipt(tx) diff --git a/tests/acceptance/agents/test_contract_agency.py b/tests/acceptance/agents/test_contract_agency.py index f50a5d625..5592a6705 100644 --- a/tests/acceptance/agents/test_contract_agency.py +++ b/tests/acceptance/agents/test_contract_agency.py @@ -1,18 +1,18 @@ -from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent +from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent from tests.constants import TEST_ETH_PROVIDER_URI def test_get_agent_with_different_registries(application_economics, test_registry, agency_local_registry): # Get agents using same registry instance application_agent_1 = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) application_agent_2 = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) @@ -21,7 +21,7 @@ def test_get_agent_with_different_registries(application_economics, test_registr # Same content but different classes of registries application_agent_2 = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=agency_local_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) diff --git a/tests/acceptance/agents/test_coordinator_agent.py b/tests/acceptance/agents/test_coordinator_agent.py index fba682b4c..ac7cb45e3 100644 --- a/tests/acceptance/agents/test_coordinator_agent.py +++ b/tests/acceptance/agents/test_coordinator_agent.py @@ -7,7 +7,7 @@ from nucypher_core import SessionStaticSecret from nucypher.blockchain.eth.agents import ( ContractAgency, CoordinatorAgent, - PREApplicationAgent, + TACoApplicationAgent, ) from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.crypto.powers import TransactingPower @@ -27,8 +27,10 @@ def transcripts(): return [os.urandom(32), os.urandom(32)] +@pytest.mark.usefixtures("ursulas") @pytest.fixture(scope="module") def cohort(testerchain, staking_providers): + # "ursulas" fixture is needed to set provider public key deployer, cohort_provider_1, cohort_provider_2, *everybody_else = staking_providers cohort_providers = [cohort_provider_1, cohort_provider_2] cohort_providers.sort() # providers must be sorted @@ -36,10 +38,10 @@ def cohort(testerchain, staking_providers): @pytest.fixture(scope='module') -def ursulas(cohort, test_registry): +def cohort_ursulas(cohort, test_registry): ursulas_for_cohort = [] application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) @@ -51,10 +53,10 @@ def ursulas(cohort, test_registry): @pytest.fixture(scope='module') -def transacting_powers(testerchain, ursulas): +def transacting_powers(testerchain, cohort_ursulas): return [ TransactingPower(account=ursula, signer=Web3Signer(testerchain.client)) - for ursula in ursulas + for ursula in cohort_ursulas ] @@ -65,12 +67,36 @@ def test_coordinator_properties(agent): assert not agent._proxy_name # not upgradeable -def test_initiate_ritual(agent, cohort, transacting_powers): +@pytest.mark.usefixtures("ursulas") +def test_initiate_ritual( + agent, + cohort, + get_random_checksum_address, + global_allow_list, + transacting_powers, + ritual_token, + testerchain, + initiator, +): number_of_rituals = agent.number_of_rituals() assert number_of_rituals == 0 + duration = 60 * 60 * 24 + amount = agent.get_ritual_initiation_cost(cohort, duration) + + # Approve the ritual token for the coordinator agent to spend + tx = ritual_token.functions.approve(agent.contract_address, amount).transact( + {"from": initiator.transacting_power.account} + ) + testerchain.wait_for_receipt(tx) + + authority = get_random_checksum_address() receipt = agent.initiate_ritual( - providers=cohort, transacting_power=transacting_powers[0] + providers=cohort, + authority=authority, + duration=duration, + access_controller=global_allow_list.address, + transacting_power=initiator.transacting_power, ) assert receipt['status'] == 1 start_ritual_event = agent.contract.events.StartRitual().process_receipt(receipt) @@ -81,7 +107,7 @@ def test_initiate_ritual(agent, cohort, transacting_powers): ritual_id = number_of_rituals - 1 ritual = agent.get_ritual(ritual_id) - assert ritual.initiator == transacting_powers[0].account + assert ritual.authority == authority participants = agent.get_participants(ritual_id) assert [p.provider for p in participants] == cohort diff --git a/tests/acceptance/agents/test_sampling_distribution.py b/tests/acceptance/agents/test_sampling_distribution.py index 89ddf1dd4..400af11bc 100644 --- a/tests/acceptance/agents/test_sampling_distribution.py +++ b/tests/acceptance/agents/test_sampling_distribution.py @@ -1,16 +1,20 @@ -from collections import Counter - -import pytest import random +from collections import Counter from itertools import permutations from unittest.mock import Mock +import pytest + from nucypher.blockchain.eth.actors import Operator -from nucypher.blockchain.eth.agents import WeightedSampler, ContractAgency, PREApplicationAgent +from nucypher.blockchain.eth.agents import ( + ContractAgency, + TACoApplicationAgent, + WeightedSampler, +) from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.config.constants import TEMPORARY_DOMAIN -from nucypher.crypto.powers import TransactingPower, CryptoPower +from nucypher.crypto.powers import CryptoPower, TransactingPower from tests.constants import TEST_ETH_PROVIDER_URI @@ -18,7 +22,7 @@ def test_sampling_distribution(testerchain, test_registry, threshold_staking, ap # setup application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) @@ -33,7 +37,9 @@ def test_sampling_distribution(testerchain, test_registry, threshold_staking, ap # initialize threshold stake tx = threshold_staking.functions.setRoles(provider_address).transact() testerchain.wait_for_receipt(tx) - tx = threshold_staking.functions.setStakes(provider_address, amount, 0, 0).transact() + tx = threshold_staking.functions.authorizationIncreased( + provider_address, 0, amount + ).transact() testerchain.wait_for_receipt(tx) power = TransactingPower(account=provider_address, signer=Web3Signer(testerchain.client)) diff --git a/tests/acceptance/agents/test_pre_application_agent.py b/tests/acceptance/agents/test_taco_application_agent.py similarity index 71% rename from tests/acceptance/agents/test_pre_application_agent.py rename to tests/acceptance/agents/test_taco_application_agent.py index 4d4c39b48..10223c131 100644 --- a/tests/acceptance/agents/test_pre_application_agent.py +++ b/tests/acceptance/agents/test_taco_application_agent.py @@ -1,8 +1,9 @@ import os -import pytest -from eth_utils import to_checksum_address, is_address -from nucypher.blockchain.eth.agents import PREApplicationAgent, ContractAgency +import pytest +from eth_utils import is_address, to_checksum_address + +from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.crypto.powers import TransactingPower @@ -15,7 +16,7 @@ MIN_SECONDS = 1 def test_get_min_authorization(test_registry, application_economics): application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) @@ -25,7 +26,7 @@ def test_get_min_authorization(test_registry, application_economics): def test_get_min_seconds(test_registry, application_economics): application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) @@ -37,12 +38,14 @@ def test_authorized_tokens( testerchain, application_economics, test_registry, staking_providers ): application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) provider_account = staking_providers[0] - authorized_amount = application_agent.get_authorized_stake(staking_provider=provider_account) + authorized_amount = application_agent.get_authorized_stake( + staking_provider=provider_account + ) assert authorized_amount >= application_economics.min_authorization @@ -50,7 +53,7 @@ def test_staking_providers_and_operators_relationships( testerchain, test_registry, threshold_staking, application_economics ): application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) @@ -58,41 +61,63 @@ def test_staking_providers_and_operators_relationships( staking_provider_account, operator_account, *other = testerchain.unassigned_accounts tx = threshold_staking.functions.setRoles(staking_provider_account).transact() testerchain.wait_for_receipt(tx) - tx = threshold_staking.functions.setStakes(staking_provider_account, application_economics.min_authorization, 0, 0).transact() + tx = threshold_staking.functions.authorizationIncreased( + staking_provider_account, 0, application_economics.min_authorization + ).transact() testerchain.wait_for_receipt(tx) # The staking provider hasn't bond an operator yet - assert NULL_ADDRESS == application_agent.get_operator_from_staking_provider(staking_provider=staking_provider_account) + assert NULL_ADDRESS == application_agent.get_operator_from_staking_provider( + staking_provider=staking_provider_account + ) - tpower = TransactingPower(account=staking_provider_account, signer=Web3Signer(testerchain.client)) - _txhash = application_agent.bond_operator(transacting_power=tpower, - staking_provider=staking_provider_account, - operator=operator_account) + tpower = TransactingPower( + account=staking_provider_account, signer=Web3Signer(testerchain.client) + ) + _txhash = application_agent.bond_operator( + transacting_power=tpower, + staking_provider=staking_provider_account, + operator=operator_account, + ) # We can check the staker-worker relation from both sides - assert operator_account == application_agent.get_operator_from_staking_provider(staking_provider=staking_provider_account) - assert staking_provider_account == application_agent.get_staking_provider_from_operator(operator_address=operator_account) + assert operator_account == application_agent.get_operator_from_staking_provider( + staking_provider=staking_provider_account + ) + assert ( + staking_provider_account + == application_agent.get_staking_provider_from_operator( + operator_address=operator_account + ) + ) # No staker-worker relationship random_address = to_checksum_address(os.urandom(20)) - assert NULL_ADDRESS == application_agent.get_operator_from_staking_provider(staking_provider=random_address) - assert NULL_ADDRESS == application_agent.get_staking_provider_from_operator(operator_address=random_address) + assert NULL_ADDRESS == application_agent.get_operator_from_staking_provider( + staking_provider=random_address + ) + assert NULL_ADDRESS == application_agent.get_staking_provider_from_operator( + operator_address=random_address + ) def test_get_staker_population(staking_providers, test_registry): application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) # Apart from all the providers in the fixture, we also added a new provider above - assert application_agent.get_staking_providers_population() == len(staking_providers) + 1 + assert ( + application_agent.get_staking_providers_population() + == len(staking_providers) + 1 + ) def test_get_swarm(staking_providers, test_registry): application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) @@ -110,22 +135,26 @@ def test_get_swarm(staking_providers, test_registry): @pytest.mark.usefixtures("staking_providers") def test_sample_staking_providers(test_registry): application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) providers_population = application_agent.get_staking_providers_population() - with pytest.raises(PREApplicationAgent.NotEnoughStakingProviders): - application_agent.get_staking_provider_reservoir().draw(providers_population + 1) # One more than we have deployed + with pytest.raises(TACoApplicationAgent.NotEnoughStakingProviders): + application_agent.get_staking_provider_reservoir().draw( + providers_population + 1 + ) # One more than we have deployed providers = application_agent.get_staking_provider_reservoir().draw(3) - assert len(providers) == 3 # Three... + assert len(providers) == 3 # Three... assert len(set(providers)) == 3 # ...unique addresses # Same but with pagination - providers = application_agent.get_staking_provider_reservoir(pagination_size=1).draw(3) + providers = application_agent.get_staking_provider_reservoir( + pagination_size=1 + ).draw(3) assert len(providers) == 3 assert len(set(providers)) == 3 light = application_agent.blockchain.is_light @@ -138,12 +167,14 @@ def test_sample_staking_providers(test_registry): def test_get_staking_provider_info(testerchain, test_registry): application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) staking_provider_account, operator_account, *other = testerchain.unassigned_accounts - info: StakingProviderInfo = application_agent.get_staking_provider_info(staking_provider=staking_provider_account) + info: StakingProviderInfo = application_agent.get_staking_provider_info( + staking_provider=staking_provider_account + ) assert info.operator_start_timestamp > 0 assert info.operator == operator_account assert not info.operator_confirmed diff --git a/tests/acceptance/ape-config.yaml b/tests/acceptance/ape-config.yaml index 0110d679f..803ed8a55 100644 --- a/tests/acceptance/ape-config.yaml +++ b/tests/acceptance/ape-config.yaml @@ -5,8 +5,8 @@ plugins: dependencies: - name: nucypher-contracts - github: nucypher/nucypher-contracts - ref: main + github: derekpierre/nucypher-contracts + ref: alpha-tweaks - name: openzeppelin github: OpenZeppelin/openzeppelin-contracts version: 4.8.1 @@ -20,19 +20,39 @@ solidity: deployments: ethereum: local: + - contract_type: RitualToken + address: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0 + total_supply: 1000000000000000000000000000 + - contract_type: TToken + address: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0 + total_supply: 1000000000000000000000000000 - contract_type: NuCypherToken - address: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # test account at index 0 + address: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0 nu_token_supply: 1_000_000_000 - - contract_type: SimplePREApplication - address: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # test account at index 0 - threshold_staking: '::ThresholdStakingForPREApplicationMock.address::' + - contract_type: StakeInfo + address: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0 + updaters: + - '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0 + - contract_type: TACoApplication + address: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0 + t_token: + threshold_staking: pre_min_authorization: 40000000000000000000000 pre_min_operator_seconds: 86400 # one day in seconds + reward_duration: 604800 + deauthorization_duration: 5184000 - contract_type: Coordinator - address: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # test account at index 0 - app: '::SimplePREApplication.address::' + address: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0 + stake_info: ritual_timeout: 3600 max_dkg_size: 8 + admin: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0 + currency: + fee: 1 + - contract_type: GlobalAllowList + address: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0 + coordinator: + admin: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0 test: mnemonic: test test test test test test test test test test test junk diff --git a/tests/acceptance/blockchain/ape-config.yml b/tests/acceptance/blockchain/ape-config.yml deleted file mode 100644 index 52037a002..000000000 --- a/tests/acceptance/blockchain/ape-config.yml +++ /dev/null @@ -1,5 +0,0 @@ -dependencies: - - name: nucypher-contracts - github: nucypher/nucypher-contracts - ref: main - contracts_folder: src diff --git a/tests/acceptance/characters/test_fault_tolerance.py b/tests/acceptance/characters/test_fault_tolerance.py index 5f8fa36f4..321188dfd 100644 --- a/tests/acceptance/characters/test_fault_tolerance.py +++ b/tests/acceptance/characters/test_fault_tolerance.py @@ -4,7 +4,7 @@ from nucypher_core import MetadataResponse, MetadataResponsePayload from twisted.logger import LogLevel, globalLogPublisher from nucypher.acumen.perception import FleetSensor -from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent +from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.config.constants import TEMPORARY_DOMAIN @@ -91,7 +91,7 @@ def test_invalid_operators_tolerance( # Setup # application_agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) @@ -112,8 +112,8 @@ def test_invalid_operators_tolerance( min_authorization = application_economics.min_authorization tx = threshold_staking.functions.setRoles(_staking_provider).transact() testerchain.wait_for_receipt(tx) - tx = threshold_staking.functions.setStakes( - _staking_provider, min_authorization, 0, 0 + tx = threshold_staking.functions.authorizationIncreased( + _staking_provider, 0, min_authorization ).transact() testerchain.wait_for_receipt(tx) diff --git a/tests/acceptance/characters/test_operator.py b/tests/acceptance/characters/test_operator.py index 4541a7313..4cb85f3c3 100644 --- a/tests/acceptance/characters/test_operator.py +++ b/tests/acceptance/characters/test_operator.py @@ -3,6 +3,7 @@ import datetime import maya import pytest from eth_account._utils.signing import to_standard_signature_bytes +from web3 import Web3 from nucypher.characters.lawful import Enrico, Ursula from nucypher.characters.unlawful import Vladimir @@ -12,6 +13,18 @@ from tests.constants import MOCK_ETH_PROVIDER_URI, TEST_ETH_PROVIDER_URI from tests.utils.middleware import NodeIsDownMiddleware +@pytest.fixture(scope="module") +def vladimir_needs_eth(testerchain): + # transfer some ETH to Vladimir who needs it to set provider public key + tx = { + "to": Vladimir.fraud_address, + "from": testerchain.etherbase_account, + "value": Web3.to_wei("1", "ether"), + } + txhash = testerchain.w3.eth.send_transaction(tx) + _ = testerchain.wait_for_receipt(txhash) + + def test_stakers_bond_to_ursulas(ursulas, test_registry, staking_providers): assert len(ursulas) == len(staking_providers) for ursula in ursulas: @@ -48,7 +61,10 @@ def remote_vladimir(**kwds): return remote_vladimir -def test_vladimir_cannot_verify_interface_with_ursulas_signing_key(ursulas, test_registry_source_manager): +@pytest.mark.usefixtures("vladimir_needs_eth") +def test_vladimir_cannot_verify_interface_with_ursulas_signing_key( + testerchain, ursulas, test_registry_source_manager +): his_target = list(ursulas)[4] # Vladimir has his own ether address; he hopes to publish it along with Ursula's details @@ -73,6 +89,7 @@ def test_vladimir_cannot_verify_interface_with_ursulas_signing_key(ursulas, test vladimir.validate_metadata() +@pytest.mark.usefixtures("vladimir_needs_eth") def test_vladimir_uses_his_own_signing_key(alice, ursulas, test_registry): """ Similar to the attack above, but this time Vladimir makes his own interface signature @@ -108,6 +125,7 @@ def test_vladimir_uses_his_own_signing_key(alice, ursulas, test_registry): ) +@pytest.mark.usefixtures("vladimir_needs_eth") def test_vladimir_invalidity_without_stake(testerchain, ursulas, alice): his_target = list(ursulas)[4] diff --git a/tests/acceptance/characters/test_transacting_power.py b/tests/acceptance/characters/test_transacting_power.py index bd7ffdaab..890b48f4f 100644 --- a/tests/acceptance/characters/test_transacting_power.py +++ b/tests/acceptance/characters/test_transacting_power.py @@ -1,6 +1,17 @@ +import pytest +from eth_account._utils.legacy_transactions import Transaction +from eth_utils import to_checksum_address + +from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent +from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.characters.lawful import Character from nucypher.config.constants import TEMPORARY_DOMAIN -from tests.constants import TEST_ETH_PROVIDER_URI +from nucypher.crypto.powers import TransactingPower +from nucypher.crypto.utils import verify_eip_191 +from tests.conftest import LOCK_FUNCTION +from tests.constants import INSECURE_DEVELOPMENT_PASSWORD, TEST_ETH_PROVIDER_URI + +TransactingPower.lock_account = LOCK_FUNCTION def test_character_transacting_power_signing(testerchain, test_registry): @@ -49,19 +60,6 @@ def test_character_transacting_power_signing(testerchain, test_registry): restored_dict = restored_transaction.as_dict() assert to_checksum_address(restored_dict['to']) == transaction_dict['to'] -import pytest -from eth_account._utils.legacy_transactions import Transaction -from eth_utils import to_checksum_address - -from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent -from nucypher.blockchain.eth.signers.software import Web3Signer -from nucypher.crypto.powers import TransactingPower -from nucypher.crypto.utils import verify_eip_191 -from tests.conftest import LOCK_FUNCTION -from tests.constants import INSECURE_DEVELOPMENT_PASSWORD - -TransactingPower.lock_account = LOCK_FUNCTION - def test_transacting_power_sign_message(testerchain): @@ -123,7 +121,7 @@ def test_transacting_power_sign_transaction(testerchain): def test_transacting_power_sign_agent_transaction(testerchain, test_registry): agent = ContractAgency.get_agent( - PREApplicationAgent, + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) diff --git a/tests/acceptance/cli/test_ursula_init.py b/tests/acceptance/cli/test_ursula_init.py index 39683abf0..675938e30 100644 --- a/tests/acceptance/cli/test_ursula_init.py +++ b/tests/acceptance/cli/test_ursula_init.py @@ -6,7 +6,7 @@ import pytest from eth_account import Account from web3 import Web3 -from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent +from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent from nucypher.blockchain.eth.signers import KeystoreSigner from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.cli.main import nucypher_cli @@ -59,7 +59,7 @@ def mock_funded_account_password_keystore( tx = threshold_staking.functions.setRoles(provider_address).transact() testerchain.wait_for_receipt(tx) tx = threshold_staking.functions.setStakes( - provider_address, application_economics.min_authorization, 0, 0 + provider_address, 0, application_economics.min_authorization ).transact() testerchain.wait_for_receipt(tx) @@ -68,12 +68,12 @@ def mock_funded_account_password_keystore( ) provider_power.unlock(password=INSECURE_DEVELOPMENT_PASSWORD) - pre_application_agent = ContractAgency.get_agent( - PREApplicationAgent, + taco_application_agent = ContractAgency.get_agent( + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) - pre_application_agent.bond_operator( + taco_application_agent.bond_operator( staking_provider=provider_address, operator=account.address, transacting_power=provider_power, diff --git a/tests/acceptance/conditions/conftest.py b/tests/acceptance/conditions/conftest.py index 4595b4512..931b03007 100644 --- a/tests/acceptance/conditions/conftest.py +++ b/tests/acceptance/conditions/conftest.py @@ -13,8 +13,7 @@ from nucypher.policy.conditions.lingo import ( OrCompoundCondition, ReturnValueTest, ) -from tests.acceptance.constants import APE_TEST_CHAIN_ID -from tests.constants import TEST_ETH_PROVIDER_URI +from tests.constants import APE_TEST_CHAIN_ID, TEST_ETH_PROVIDER_URI @pytest.fixture() @@ -67,11 +66,11 @@ def erc20_evm_condition_balanceof(testerchain, test_registry): @pytest.fixture -def erc721_contract(accounts, project, test_registry): +def erc721_contract(accounts, project): account = accounts[0] # deploy contract - deployed_contract = account.deploy(project.ConditionNFT) + deployed_contract = project.ConditionNFT.deploy(sender=account) # mint nft with token id = 1 deployed_contract.mint(account.address, 1, sender=account) diff --git a/tests/acceptance/conditions/test_conditions.py b/tests/acceptance/conditions/test_conditions.py index 5a5dfa9fc..297e1708b 100644 --- a/tests/acceptance/conditions/test_conditions.py +++ b/tests/acceptance/conditions/test_conditions.py @@ -25,8 +25,8 @@ from nucypher.policy.conditions.exceptions import ( RPCExecutionFailed, ) from nucypher.policy.conditions.lingo import ConditionLingo, ReturnValueTest -from tests.acceptance.constants import APE_TEST_CHAIN_ID from tests.constants import ( + APE_TEST_CHAIN_ID, TEST_ETH_PROVIDER_URI, TEST_POLYGON_PROVIDER_URI, ) diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py index e19eb066e..9e6acb2a4 100644 --- a/tests/acceptance/conftest.py +++ b/tests/acceptance/conftest.py @@ -5,7 +5,11 @@ import pytest from web3 import Web3 from nucypher.blockchain.eth.actors import Operator, Ritualist -from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent +from nucypher.blockchain.eth.agents import ( + ContractAgency, + CoordinatorAgent, + TACoApplicationAgent, +) from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory from nucypher.blockchain.eth.networks import NetworksInventory from nucypher.blockchain.eth.signers.software import Web3Signer @@ -17,12 +21,14 @@ from nucypher.policy.conditions.lingo import ConditionLingo, ReturnValueTest from nucypher.policy.conditions.time import TimeCondition from nucypher.policy.payment import SubscriptionManagerPayment from nucypher.utilities.logging import Logger -from tests.acceptance.constants import APE_TEST_CHAIN_ID from tests.constants import ( + APE_TEST_CHAIN_ID, BONUS_TOKENS_FOR_TESTS, + GLOBAL_ALLOW_LIST, INSECURE_DEVELOPMENT_PASSWORD, - MIN_STAKE_FOR_TESTS, MOCK_STAKING_CONTRACT_NAME, + RITUAL_TOKEN, + STAKE_INFO, TEST_ETH_PROVIDER_URI, ) from tests.utils.ape import ( @@ -65,6 +71,11 @@ def mock_multichain_configuration(module_mocker, testerchain): @pytest.fixture(scope='session', autouse=True) +def test_contracts(project): + return project.contracts + + +@pytest.fixture(scope="session", autouse=True) def nucypher_contracts(project): nucypher_contracts_dependency_api = project.dependencies["nucypher-contracts"] # simply use first entry - could be from github ('main') or local ('local') @@ -74,13 +85,32 @@ def nucypher_contracts(project): @pytest.fixture(scope='module', autouse=True) -def deploy_contracts(nucypher_contracts, accounts): +def deploy_contracts(nucypher_contracts, test_contracts, accounts): deployments = ape_deploy_contracts( - nucypher_contracts=nucypher_contracts, accounts=accounts + nucypher_contracts=nucypher_contracts, + test_contracts=test_contracts, + accounts=accounts, ) return deployments +@pytest.fixture(scope="module") +def deployer_account(accounts): + return accounts[0] + + +@pytest.fixture(scope="module") +def initiator(testerchain, alice, ritual_token, deployer_account): + """Returns the Initiator, funded with RitualToken""" + # transfer ritual token to alice (initiator) + tx = ritual_token.functions.transfer( + alice.transacting_power.account, + Web3.to_wei(1, "ether"), + ).transact({"from": deployer_account.address}) + testerchain.wait_for_receipt(tx) + return alice + + @pytest.fixture(scope='module', autouse=True) def test_registry(nucypher_contracts, deploy_contracts): registry = registry_from_ape_deployments(nucypher_contracts, deployments=deploy_contracts) @@ -97,23 +127,68 @@ def testerchain(project, test_registry) -> TesterBlockchain: @pytest.fixture(scope='module') -def threshold_staking(testerchain, test_registry): - result = test_registry.search(contract_name=MOCK_STAKING_CONTRACT_NAME)[0] - threshold_staking = testerchain.w3.eth.contract( - address=result[2], - abi=result[3] - ) - return threshold_staking +def stake_info(testerchain, test_registry): + result = test_registry.search(contract_name=STAKE_INFO)[0] + _stake_info = testerchain.w3.eth.contract(address=result[2], abi=result[3]) + return _stake_info @pytest.fixture(scope="module") -def staking_providers(testerchain, test_registry, threshold_staking): - pre_application_agent = ContractAgency.get_agent( - PREApplicationAgent, +def ritual_token(testerchain, test_registry): + result = test_registry.search(contract_name=RITUAL_TOKEN)[0] + _ritual_token = testerchain.w3.eth.contract(address=result[2], abi=result[3]) + return _ritual_token + + +@pytest.fixture(scope="module") +def threshold_staking(testerchain, test_registry): + result = test_registry.search(contract_name=MOCK_STAKING_CONTRACT_NAME)[0] + _threshold_staking = testerchain.w3.eth.contract(address=result[2], abi=result[3]) + + # TODO: Relocate this to pre application setup + taco_application_agent = ContractAgency.get_agent( + TACoApplicationAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI, ) - blockchain = pre_application_agent.blockchain + + tx = _threshold_staking.functions.setApplication( + taco_application_agent.contract_address + ).transact() + testerchain.wait_for_receipt(tx) + + return _threshold_staking + + +@pytest.fixture(scope="module", autouse=True) +def coordinator_agent(testerchain, test_registry): + """Creates a coordinator agent""" + coordinator = ContractAgency.get_agent( + CoordinatorAgent, registry=test_registry, provider_uri=TEST_ETH_PROVIDER_URI + ) + tx = coordinator.contract.functions.makeInitiationPublic().transact() + testerchain.wait_for_receipt(tx) + return coordinator + + +@pytest.fixture(scope="module") +def global_allow_list(testerchain, test_registry): + result = test_registry.search(contract_name=GLOBAL_ALLOW_LIST)[0] + _global_allow_list = testerchain.w3.eth.contract(address=result[2], abi=result[3]) + return _global_allow_list + + +@pytest.fixture(scope="module") +def staking_providers(testerchain, test_registry, threshold_staking, stake_info): + taco_application_agent = ContractAgency.get_agent( + TACoApplicationAgent, + registry=test_registry, + provider_uri=TEST_ETH_PROVIDER_URI, + ) + blockchain = taco_application_agent.blockchain + minimum_stake = ( + taco_application_agent.contract.functions.minimumAuthorization().call() + ) staking_providers = list() for provider_address, operator_address in zip(blockchain.stake_providers_accounts, blockchain.ursulas_accounts): @@ -121,22 +196,28 @@ def staking_providers(testerchain, test_registry, threshold_staking): provider_power.unlock(password=INSECURE_DEVELOPMENT_PASSWORD) # for a random amount - amount = MIN_STAKE_FOR_TESTS + random.randrange(BONUS_TOKENS_FOR_TESTS) + amount = minimum_stake + random.randrange(BONUS_TOKENS_FOR_TESTS) - # initialize threshold stake + # initialize threshold stake via threshold staking (permission-less mock) tx = threshold_staking.functions.setRoles(provider_address).transact() testerchain.wait_for_receipt(tx) - tx = threshold_staking.functions.setStakes(provider_address, amount, 0, 0).transact() + + # TODO: extract this to a fixture + tx = threshold_staking.functions.authorizationIncreased( + provider_address, 0, amount + ).transact() testerchain.wait_for_receipt(tx) - # We assume that the staking provider knows in advance the account of her operator - pre_application_agent.bond_operator(staking_provider=provider_address, - operator=operator_address, - transacting_power=provider_power) + taco_application_agent.bond_operator( + staking_provider=provider_address, + operator=operator_address, + transacting_power=provider_power, + ) operator_power = TransactingPower( account=operator_address, signer=Web3Signer(testerchain.client) ) + operator = Operator( is_me=True, operator_address=operator_address, @@ -154,6 +235,20 @@ def staking_providers(testerchain, test_registry, threshold_staking): ) operator.confirm_address() # assume we always need a "pre-confirmed" operator for now. + # TODO clean this up, perhaps with a fixture + # update StakeInfo + tx = stake_info.functions.updateOperator( + provider_address, + operator_address, + ).transact() + testerchain.wait_for_receipt(tx) + + tx = stake_info.functions.updateAmount( + provider_address, + amount, + ).transact() + testerchain.wait_for_receipt(tx) + # track staking_providers.append(provider_address) diff --git a/tests/acceptance/constants.py b/tests/acceptance/constants.py deleted file mode 100644 index 18adf6e05..000000000 --- a/tests/acceptance/constants.py +++ /dev/null @@ -1,3 +0,0 @@ -from ape.utils import DEFAULT_TEST_CHAIN_ID - -APE_TEST_CHAIN_ID = DEFAULT_TEST_CHAIN_ID # APE uses this chain id diff --git a/tests/acceptance/contracts/RitualToken.sol b/tests/acceptance/contracts/RitualToken.sol new file mode 100644 index 000000000..624f350a6 --- /dev/null +++ b/tests/acceptance/contracts/RitualToken.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/** + * @notice Contract for testing the application contract + */ +contract RitualToken is ERC20("RitualToken", "RT") { + constructor(uint256 _totalSupplyOfTokens) { + _mint(msg.sender, _totalSupplyOfTokens); + } +} diff --git a/tests/constants.py b/tests/constants.py index 35674c352..c28ad7789 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -4,6 +4,7 @@ from datetime import datetime from pathlib import Path from random import SystemRandom +from ape.utils import DEFAULT_TEST_CHAIN_ID from web3 import Web3 from nucypher.blockchain.eth.token import NU @@ -17,7 +18,19 @@ from nucypher.config.constants import ( # -MOCK_STAKING_CONTRACT_NAME = 'ThresholdStakingForPREApplicationMock' +MOCK_STAKING_CONTRACT_NAME = "ThresholdStakingForTACoApplicationMock" +RITUAL_TOKEN = "RitualToken" +T_TOKEN = "TToken" +STAKE_INFO = "StakeInfo" +CONDITION_NFT = "ConditionNFT" +GLOBAL_ALLOW_LIST = "GlobalAllowList" + + +# +# Ape +# + +APE_TEST_CHAIN_ID = DEFAULT_TEST_CHAIN_ID # ape uses this chain id # # Ursula @@ -62,6 +75,8 @@ DEVELOPMENT_ETH_AIRDROP_AMOUNT = int(Web3().to_wei(100, 'ether')) NUMBER_OF_ALLOCATIONS_IN_TESTS = 50 # TODO: Move to constants +TESTERCHAIN_CHAIN_ID = 131277322940537 + # # Insecure Secrets @@ -136,7 +151,6 @@ CLI_TEST_ENV = {NUCYPHER_ENVVAR_KEYSTORE_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD CLI_ENV = {NUCYPHER_ENVVAR_KEYSTORE_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD, NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD} -TESTERCHAIN_CHAIN_ID = 131277322940537 # diff --git a/tests/fixtures.py b/tests/fixtures.py index ab38f1989..4b191ce80 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -19,6 +19,7 @@ from web3 import Web3 import tests from nucypher.blockchain.economics import Economics +from nucypher.blockchain.eth.actors import Operator from nucypher.blockchain.eth.clients import EthereumClient from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory from nucypher.blockchain.eth.registry import LocalContractRegistry @@ -341,6 +342,11 @@ def light_ursula(temp_dir_path, test_registry_source_manager, random_account, mo payment_method = SubscriptionManagerPayment( eth_provider=MOCK_ETH_PROVIDER_URI, network=TEMPORARY_DOMAIN ) + + mocker.patch.object( + Operator, "get_staking_provider_address", return_value=random_account.address + ) + ursula = Ursula( rest_host=LOOPBACK_ADDRESS, rest_port=select_test_port(), diff --git a/tests/integration/blockchain/test_dkg_ritual.py b/tests/integration/blockchain/test_dkg_ritual.py index 0d7881a00..dd4145661 100644 --- a/tests/integration/blockchain/test_dkg_ritual.py +++ b/tests/integration/blockchain/test_dkg_ritual.py @@ -5,13 +5,13 @@ from unittest.mock import PropertyMock, patch import pytest import pytest_twisted from eth_typing import ChecksumAddress +from nucypher_core.ferveo import FerveoVariant from twisted.internet.threads import deferToThread from web3.datastructures import AttributeDict -from nucypher_core.ferveo import FerveoVariant from nucypher.blockchain.eth.agents import CoordinatorAgent +from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.characters.lawful import Enrico, Ursula -from nucypher.crypto.ferveo.dkg import FerveoVariant from nucypher.policy.conditions.lingo import ConditionLingo from tests.constants import TESTERCHAIN_CHAIN_ID from tests.mock.coordinator import MockCoordinatorAgent @@ -74,7 +74,7 @@ def cohort(ursulas, mock_coordinator_agent): return ursulas -def execute_round_1(ritual_id: int, initiator: ChecksumAddress, cohort: List[Ursula]): +def execute_round_1(ritual_id: int, authority: ChecksumAddress, cohort: List[Ursula]): # check that the ritual is being tracked locally upon initialization for each node for ursula in cohort: @@ -88,7 +88,7 @@ def execute_round_1(ritual_id: int, initiator: ChecksumAddress, cohort: List[Urs args=AttributeDict( { "ritualId": ritual_id, - "initiator": initiator, + "authority": authority, "participants": [u.checksum_address for u in cohort], } ), @@ -117,6 +117,7 @@ def execute_round_2(ritual_id: int, cohort: List[Ursula]): ursula.ritual_tracker._handle_ritual_event(event, get_block_when=lambda x: event) +@pytest.mark.usefixtures("mock_sign_message") @pytest.mark.parametrize('dkg_size, ritual_id, variant', PARAMS) @pytest_twisted.inlineCallbacks() def test_ursula_ritualist( @@ -129,96 +130,119 @@ def test_ursula_ritualist( ritual_id, variant, test_registry_source_manager, + get_random_checksum_address, ): """Tests the DKG and the encryption/decryption of a message""" cohort = cohort[:dkg_size] - def initialize(): - """Initiates the ritual""" - print("==================== INITIALIZING ====================") - cohort_staking_provider_addresses = list(u.checksum_address for u in cohort) - mock_coordinator_agent.initiate_ritual( - providers=cohort_staking_provider_addresses, - transacting_power=alice.transacting_power - ) - assert mock_coordinator_agent.number_of_rituals() == ritual_id + 1 + # adjust threshold since we are testing with pre-computed (simple is the default) + threshold = mock_coordinator_agent.get_threshold_for_ritual_size( + dkg_size + ) # default is simple + if variant == FerveoVariant.Precomputed: + threshold = dkg_size - def round_1(_): - """Checks the initialization of the ritual""" - print("==================== CHECKING INITIALIZATION ====================") - # verify that the ritual is in the correct state - assert mock_coordinator_agent.get_ritual_status(ritual_id=ritual_id) == \ - mock_coordinator_agent.Ritual.Status.AWAITING_TRANSCRIPTS + with patch.object( + mock_coordinator_agent, "get_threshold_for_ritual_size", return_value=threshold + ): - ritual = mock_coordinator_agent.get_ritual(ritual_id) - execute_round_1(ritual_id, ritual.initiator, cohort) - - def round_2(_): - """simulates the passage of time and the execution of the event scanner""" - print("==================== BLOCKING UNTIL DKG FINALIZED ====================") - execute_round_2(ritual_id, cohort) - - def finality(_): - """Checks the finality of the DKG""" - print("==================== CHECKING DKG FINALITY ====================") - - status = mock_coordinator_agent.get_ritual_status(ritual_id) - assert status == mock_coordinator_agent.Ritual.Status.FINALIZED - for ursula in cohort: - assert ursula.dkg_storage.get_transcript(ritual_id) is not None - - def encrypt(_): - """Encrypts a message and returns the ciphertext and conditions""" - print("==================== DKG ENCRYPTION ====================") - encrypting_key = mock_coordinator_agent.get_ritual_public_key( - ritual_id=ritual_id - ) - - # prepare message and conditions - plaintext = PLAINTEXT.encode() - - # encrypt - # print(f'encrypting for DKG with key {bytes(encrypting_key.to_bytes()).hex()}') - enrico = Enrico(encrypting_key=encrypting_key) - threshold_message_kit = enrico.encrypt_for_dkg( - plaintext=plaintext, conditions=CONDITIONS - ) - return threshold_message_kit - - def decrypt(threshold_message_kit): - """Decrypts a message and checks that it matches the original plaintext""" - print("==================== DKG DECRYPTION ====================") - bob.start_learning_loop(now=True) - - # mock the use of non-default variants since it can no longer be specified - with patch.object( - bob, "_default_dkg_variant", new_callable=PropertyMock(return_value=variant) - ): - cleartext = bob.threshold_decrypt( - ritual_id=ritual_id, - threshold_message_kit=threshold_message_kit, - peering_timeout=0, + def initialize(): + """Initiates the ritual""" + print("==================== INITIALIZING ====================") + cohort_staking_provider_addresses = list(u.checksum_address for u in cohort) + mock_coordinator_agent.initiate_ritual( + providers=cohort_staking_provider_addresses, + authority=alice.transacting_power.account, + duration=1, + access_controller=get_random_checksum_address(), + transacting_power=alice.transacting_power, ) - assert bytes(cleartext) == PLAINTEXT.encode() + assert mock_coordinator_agent.number_of_rituals() == ritual_id + 1 - print("==================== DECRYPTION SUCCESSFUL ====================") + def round_1(_): + """Checks the initialization of the ritual""" + print("==================== CHECKING INITIALIZATION ====================") + # verify that the ritual is in the correct state + assert ( + mock_coordinator_agent.get_ritual_status(ritual_id=ritual_id) + == mock_coordinator_agent.Ritual.Status.AWAITING_TRANSCRIPTS + ) - def error_handler(e): - """Prints the error and raises it""" - print("==================== ERROR ====================") - print(e.getTraceback()) - raise e + ritual = mock_coordinator_agent.get_ritual(ritual_id) + execute_round_1(ritual_id, ritual.authority, cohort) - # order matters - d = deferToThread(initialize) - callbacks = [ - round_1, - round_2, - finality, - encrypt, - decrypt, - ] - for callback in callbacks: - d.addCallback(callback) - d.addErrback(error_handler) - yield d + def round_2(_): + """simulates the passage of time and the execution of the event scanner""" + print( + "==================== BLOCKING UNTIL DKG FINALIZED ====================" + ) + execute_round_2(ritual_id, cohort) + + def finality(_): + """Checks the finality of the DKG""" + print("==================== CHECKING DKG FINALITY ====================") + + status = mock_coordinator_agent.get_ritual_status(ritual_id) + assert status == mock_coordinator_agent.Ritual.Status.FINALIZED + for ursula in cohort: + assert ursula.dkg_storage.get_transcript(ritual_id) is not None + + def encrypt(_): + """Encrypts a message and returns the ciphertext and conditions""" + print("==================== DKG ENCRYPTION ====================") + encrypting_key = mock_coordinator_agent.get_ritual_public_key( + ritual_id=ritual_id + ) + + # prepare message and conditions + plaintext = PLAINTEXT.encode() + + # create Enrico + signer = Web3Signer(client=testerchain.client) + enrico = Enrico(encrypting_key=encrypting_key, signer=signer) + + # encrypt + print(f"encrypting for DKG with key {bytes(encrypting_key).hex()}") + threshold_message_kit = enrico.encrypt_for_dkg( + plaintext=plaintext, conditions=CONDITIONS + ) + return threshold_message_kit + + def decrypt(threshold_message_kit): + """Decrypts a message and checks that it matches the original plaintext""" + print("==================== DKG DECRYPTION ====================") + bob.start_learning_loop(now=True) + + # mock the use of non-default variants since it can no longer be specified + with patch.object( + bob, + "_default_dkg_variant", + new_callable=PropertyMock(return_value=variant), + ): + cleartext = bob.threshold_decrypt( + threshold_message_kit=threshold_message_kit, + peering_timeout=0, + ) + assert bytes(cleartext) == PLAINTEXT.encode() + + print("==================== DECRYPTION SUCCESSFUL ====================") + + def error_handler(e): + """Prints the error and raises it""" + print("==================== ERROR ====================") + print(e.getTraceback()) + raise e + + # order matters + d = deferToThread(initialize) + callbacks = [ + round_1, + round_2, + finality, + encrypt, + decrypt, + ] + for callback in callbacks: + d.addCallback(callback) + d.addErrback(error_handler) + yield d diff --git a/tests/integration/blockchain/test_economics.py b/tests/integration/blockchain/test_economics.py index 9d962049d..c7170eaeb 100644 --- a/tests/integration/blockchain/test_economics.py +++ b/tests/integration/blockchain/test_economics.py @@ -6,4 +6,7 @@ from nucypher.blockchain.economics import EconomicsFactory @pytest.mark.usefixtures('testerchain') def test_retrieving_from_blockchain(application_economics, test_registry): economics = EconomicsFactory.get_economics(registry=test_registry) - assert economics.pre_application_deployment_parameters == application_economics.pre_application_deployment_parameters + assert ( + economics.taco_application_deployment_parameters + == application_economics.taco_application_deployment_parameters + ) diff --git a/tests/integration/characters/test_dkg_and_testnet_bypass.py b/tests/integration/characters/test_dkg_and_testnet_bypass.py index f791e2552..d42845d3d 100644 --- a/tests/integration/characters/test_dkg_and_testnet_bypass.py +++ b/tests/integration/characters/test_dkg_and_testnet_bypass.py @@ -1,5 +1,6 @@ import pytest +from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.characters.chaotic import ( NiceGuyEddie, ThisBobAlwaysDecrypts, @@ -14,18 +15,17 @@ from tests.constants import ( ) -def _attempt_decryption(BobClass, plaintext): +def _attempt_decryption(BobClass, plaintext, testerchain): trinket = 80 # Doens't matter. - enrico = NiceGuyEddie(encrypting_key=trinket) + signer = Web3Signer(client=testerchain.client) + enrico = NiceGuyEddie(encrypting_key=trinket, signer=signer) bob = BobClass( registry=MOCK_REGISTRY_FILEPATH, domain="lynx", eth_provider_uri=MOCK_ETH_PROVIDER_URI, ) - ANYTHING_CAN_BE_PASSED_AS_RITUAL_DATA = 55 - definitely_false_condition = { "version": ConditionLingo.VERSION, "condition": { @@ -42,20 +42,21 @@ def _attempt_decryption(BobClass, plaintext): ) decrypted_cleartext = bob.threshold_decrypt( - ritual_id=ANYTHING_CAN_BE_PASSED_AS_RITUAL_DATA, threshold_message_kit=threshold_message_kit, ) return decrypted_cleartext -def test_user_controls_success(): +@pytest.mark.usefixtures("mock_sign_message") +def test_user_controls_success(testerchain): plaintext = b"ever thus to deadbeats" - result = _attempt_decryption(ThisBobAlwaysDecrypts, plaintext) + result = _attempt_decryption(ThisBobAlwaysDecrypts, plaintext, testerchain) assert bytes(result) == bytes(plaintext) -def test_user_controls_failure(): +@pytest.mark.usefixtures("mock_sign_message") +def test_user_controls_failure(testerchain): plaintext = b"ever thus to deadbeats" with pytest.raises(Ursula.NotEnoughUrsulas): - _ = _attempt_decryption(ThisBobAlwaysFails, plaintext) + _ = _attempt_decryption(ThisBobAlwaysFails, plaintext, testerchain) diff --git a/tests/integration/cli/test_ursula_local_keystore_cli_functionality.py b/tests/integration/cli/test_ursula_local_keystore_cli_functionality.py index b4fa95bcd..230e9a52f 100644 --- a/tests/integration/cli/test_ursula_local_keystore_cli_functionality.py +++ b/tests/integration/cli/test_ursula_local_keystore_cli_functionality.py @@ -1,5 +1,3 @@ - - import json import secrets from pathlib import Path @@ -30,7 +28,9 @@ def mock_account_password_keystore(tmp_path_factory): return account, password, keystore -@pytest.mark.usefixtures("test_registry_source_manager") +@pytest.mark.usefixtures( + "test_registry_source_manager", "monkeypatch_get_staking_provider_from_operator" +) def test_ursula_init_with_local_keystore_signer( click_runner, temp_dir_path, mocker, testerchain, mock_account_password_keystore ): diff --git a/tests/integration/config/test_character_configuration.py b/tests/integration/config/test_character_configuration.py index 1c22a001f..b6bd9c69d 100644 --- a/tests/integration/config/test_character_configuration.py +++ b/tests/integration/config/test_character_configuration.py @@ -37,6 +37,7 @@ all_configurations = tuple( ) +@pytest.mark.usefixtures("monkeypatch_get_staking_provider_from_operator") @pytest.mark.parametrize("character,configuration", characters_and_configurations) def test_development_character_configurations( character, configuration, test_registry_source_manager, mocker, testerchain diff --git a/tests/integration/config/test_keystore_integration.py b/tests/integration/config/test_keystore_integration.py index 053360afc..7c993466a 100644 --- a/tests/integration/config/test_keystore_integration.py +++ b/tests/integration/config/test_keystore_integration.py @@ -151,6 +151,7 @@ def test_tls_hosting_certificate_remains_the_same(temp_dir_path, mocker): recreated_ursula.disenchant() +@pytest.mark.usefixtures("mock_sign_message") def test_ritualist(temp_dir_path, testerchain, dkg_public_key): keystore = Keystore.generate( password=INSECURE_DEVELOPMENT_PASSWORD, keystore_dir=temp_dir_path @@ -186,11 +187,15 @@ def test_ritualist(temp_dir_path, testerchain, dkg_public_key): }, } + # create enrico + signer = Web3Signer(client=testerchain.client) + enrico = Enrico(encrypting_key=dkg_public_key, signer=signer) + # encrypt - enrico = Enrico(encrypting_key=dkg_public_key) threshold_message_kit = enrico.encrypt_for_dkg( plaintext=plaintext, conditions=CONDITIONS ) + decryption_request = ThresholdDecryptionRequest( ritual_id=ritual_id, variant=FerveoVariant.Simple, diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 4667bd892..a4e4c7b04 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,3 +1,4 @@ +import os from pathlib import Path from typing import Iterable, Optional @@ -10,8 +11,8 @@ from nucypher.blockchain.eth.agents import ( AdjudicatorAgent, ContractAgency, CoordinatorAgent, - PREApplicationAgent, StakingProvidersReservoir, + TACoApplicationAgent, ) from nucypher.blockchain.eth.interfaces import ( BlockchainInterface, @@ -20,6 +21,7 @@ from nucypher.blockchain.eth.interfaces import ( from nucypher.blockchain.eth.networks import NetworksInventory from nucypher.blockchain.eth.registry import InMemoryContractRegistry from nucypher.blockchain.eth.signers import KeystoreSigner +from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.characters.lawful import Ursula from nucypher.cli.types import ChecksumAddress from nucypher.config.characters import UrsulaConfiguration @@ -55,15 +57,23 @@ def mock_sample_reservoir(testerchain, mock_contract_agency): } return StakingProvidersReservoir(addresses) - mock_agent = mock_contract_agency.get_agent(PREApplicationAgent) + mock_agent = mock_contract_agency.get_agent(TACoApplicationAgent) mock_agent.get_staking_provider_reservoir = mock_reservoir +@pytest.fixture(scope="function") +def mock_sign_message(mocker): + mocked_sign_message = mocker.patch.object( + Web3Signer, "sign_message", return_value=os.urandom(32) + ) + return mocked_sign_message + + @pytest.fixture(scope="function", autouse=True) def mock_application_agent( testerchain, application_economics, mock_contract_agency, mocker ): - mock_agent = mock_contract_agency.get_agent(PREApplicationAgent) + mock_agent = mock_contract_agency.get_agent(TACoApplicationAgent) # Handle the special case of confirming operator address, which returns a txhash due to the fire_and_forget option mock_agent.confirm_operator_address = mocker.Mock( return_value=testerchain.FAKE_TX_HASH @@ -236,7 +246,6 @@ def mock_transacting_power(module_mocker, monkeymodule): @pytest.fixture(scope="module", autouse=True) def staking_providers(testerchain, test_registry, monkeymodule): - def faked(self, *args, **kwargs): return testerchain.stake_providers_accounts[testerchain.ursulas_accounts.index(self.transacting_power.account)] @@ -244,6 +253,15 @@ def staking_providers(testerchain, test_registry, monkeymodule): return testerchain.stake_providers_accounts +@pytest.fixture(scope="module") +def monkeypatch_get_staking_provider_from_operator(monkeymodule): + monkeymodule.setattr( + Operator, + "get_staking_provider_address", + lambda self: self.transacting_power.account, + ) + + @pytest.fixture(scope="session", autouse=True) def mock_condition_blockchains(session_mocker): """adds testerchain's chain ID to permitted conditional chains""" diff --git a/tests/integration/learning/test_discovery_phases.py b/tests/integration/learning/test_discovery_phases.py index c5b29d40d..cb83d9efd 100644 --- a/tests/integration/learning/test_discovery_phases.py +++ b/tests/integration/learning/test_discovery_phases.py @@ -1,9 +1,8 @@ import contextlib - -import pytest import time import maya +import pytest from nucypher_core.umbral import SecretKey, Signer from nucypher.characters.lawful import Ursula @@ -15,7 +14,7 @@ from tests.mock.performance_mocks import ( mock_message_verification, mock_metadata_validation, mock_secret_source, - mock_verify_node + mock_verify_node, ) from tests.utils.ursula import MOCK_KNOWN_URSULAS_CACHE @@ -38,6 +37,7 @@ performance bottlenecks. """ +@pytest.mark.usefixtures("monkeypatch_get_staking_provider_from_operator") def test_alice_can_learn_about_a_whole_bunch_of_ursulas(highperf_mocked_alice, test_registry_source_manager): # During the fixture execution, Alice verified one node. # TODO: Consider changing this - #1449 diff --git a/tests/integration/network/test_network_actors.py b/tests/integration/network/test_network_actors.py index 1f009c6d0..a1aa7dc8a 100644 --- a/tests/integration/network/test_network_actors.py +++ b/tests/integration/network/test_network_actors.py @@ -4,10 +4,8 @@ from twisted.logger import LogLevel, globalLogPublisher from nucypher.acumen.nicknames import Nickname from nucypher.acumen.perception import FleetSensor -from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent from nucypher.characters.unlawful import Vladimir from nucypher.config.constants import TEMPORARY_DOMAIN -from nucypher.types import StakingProviderInfo from tests.constants import MOCK_ETH_PROVIDER_URI from tests.utils.middleware import MockRestMiddleware @@ -43,6 +41,7 @@ def test_alice_finds_ursula_via_rest(alice, ursulas): assert ursula in alice.known_nodes +@pytest.mark.usefixtures("monkeypatch_get_staking_provider_from_operator") def test_vladimir_illegal_interface_key_does_not_propagate(ursulas, test_registry_source_manager): """ Although Ursulas propagate each other's interface information, as demonstrated above, @@ -86,7 +85,7 @@ def test_vladimir_illegal_interface_key_does_not_propagate(ursulas, test_registr other_ursula._current_teacher_node = vladimir_as_learned globalLogPublisher.addObserver(warning_trapper) - result = other_ursula.learn_from_teacher_node() + other_ursula.learn_from_teacher_node() globalLogPublisher.removeObserver(warning_trapper) # Indeed, Ursula noticed that something was up. diff --git a/tests/integration/utilities/test_prometheus_collectors.py b/tests/integration/utilities/test_prometheus_collectors.py index 11394f101..b0205f86a 100644 --- a/tests/integration/utilities/test_prometheus_collectors.py +++ b/tests/integration/utilities/test_prometheus_collectors.py @@ -4,7 +4,7 @@ from typing import List import pytest -from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent +from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent from nucypher.types import StakingProviderInfo from tests.constants import MOCK_ETH_PROVIDER_URI @@ -107,8 +107,8 @@ def test_staking_provider_metrics_collector(test_registry, staking_providers, mo collector.initialize(metrics_prefix=prefix, registry=collector_registry) collector.collect() - pre_application_agent = ContractAgency.get_agent( - PREApplicationAgent, registry=test_registry + taco_application_agent = ContractAgency.get_agent( + TACoApplicationAgent, registry=test_registry ) active_stake = collector_registry.get_sample_value( @@ -117,13 +117,13 @@ def test_staking_provider_metrics_collector(test_registry, staking_providers, mo # only floats can be stored assert active_stake == float( int( - pre_application_agent.get_authorized_stake( + taco_application_agent.get_authorized_stake( staking_provider=staking_provider_address ) ) ) - staking_provider_info = pre_application_agent.get_staking_provider_info( + staking_provider_info = taco_application_agent.get_staking_provider_info( staking_provider=staking_provider_address ) diff --git a/tests/mock/coordinator.py b/tests/mock/coordinator.py index 4b7a3bcd6..1cc661a21 100644 --- a/tests/mock/coordinator.py +++ b/tests/mock/coordinator.py @@ -1,11 +1,16 @@ import time from enum import Enum -from typing import Dict, List +from typing import Dict, List, NamedTuple from eth_typing import ChecksumAddress from eth_utils import keccak from nucypher_core import SessionStaticKey -from nucypher_core.ferveo import AggregatedTranscript, DkgPublicKey, Transcript +from nucypher_core.ferveo import ( + AggregatedTranscript, + DkgPublicKey, + FerveoPublicKey, + Transcript, +) from web3.types import TxReceipt from nucypher.blockchain.eth.agents import CoordinatorAgent @@ -20,6 +25,11 @@ class MockCoordinatorAgent(MockContractAgent): Ritual = CoordinatorAgent.Ritual RitualStatus = CoordinatorAgent.Ritual.Status G1Point = CoordinatorAgent.Ritual.G1Point + G2Point = CoordinatorAgent.G2Point + + class ParticipantKey(NamedTuple): + lastRitualId: int + publicKey: CoordinatorAgent.G2Point EVENTS = {} rituals = [] @@ -35,6 +45,7 @@ class MockCoordinatorAgent(MockContractAgent): # Note that the call to super() is not necessary here self._operator_to_staking_provider = {} + self._participant_keys_history = {} def _add_operator_to_staking_provider_mapping( self, mapping: Dict[ChecksumAddress, ChecksumAddress] @@ -47,6 +58,11 @@ class MockCoordinatorAgent(MockContractAgent): except KeyError: return None + @classmethod + def get_threshold_for_ritual_size(cls, dkg_size: int): + # default is simple (same as Coordinator contract) + return dkg_size // 2 + 1 + def emit_event(self, ritual_id: int, signal: Events, **kwargs) -> None: self.EVENTS[(int(time.time_ns()), ritual_id)] = (signal, {**kwargs, 'ritual_id': ritual_id}) @@ -60,22 +76,33 @@ class MockCoordinatorAgent(MockContractAgent): # def initiate_ritual( - self, providers: List[ChecksumAddress], transacting_power: TransactingPower + self, + providers: List[ChecksumAddress], + authority: ChecksumAddress, + duration: int, + access_controller: ChecksumAddress, + transacting_power: TransactingPower, ) -> TxReceipt: ritual_id = len(self.rituals) + init_timestamp = int(time.time_ns()) + end_timestamp = init_timestamp + duration ritual = self.Ritual( - init_timestamp=int(time.time_ns()), + initiator=transacting_power.account, + authority=authority, + access_controller=access_controller, + init_timestamp=init_timestamp, + end_timestamp=end_timestamp, participants=[ self.Participant(provider=provider) for provider in providers ], dkg_size=len(providers), - initiator=transacting_power.account, + threshold=self.get_threshold_for_ritual_size(len(providers)), ) self.rituals.append(ritual) self.emit_event( signal=self.Events.START_RITUAL, ritual_id=ritual_id, - initiator=transacting_power.account, + authority=authority, participants=providers, ) return self.blockchain.FAKE_RECEIPT @@ -128,7 +155,7 @@ class MockCoordinatorAgent(MockContractAgent): participant.aggregated = True participant.decryption_request_static_key = bytes(participant_public_key) - g1_point = self.Ritual.G1Point.from_dkg_public_key(public_key) + g1_point = self.G1Point.from_dkg_public_key(public_key) if len(ritual.aggregated_transcript) == 0: ritual.aggregated_transcript = bytes(aggregated_transcript) ritual.public_key = g1_point @@ -143,6 +170,33 @@ class MockCoordinatorAgent(MockContractAgent): ritual.total_aggregations += 1 return self.blockchain.FAKE_RECEIPT + @staticmethod + def is_provider_public_key_set(staking_provider: ChecksumAddress) -> bool: + return False + + def set_provider_public_key( + self, public_key: FerveoPublicKey, transacting_power: TransactingPower + ) -> TxReceipt: + operator_address = transacting_power.account + # either mapping is populated or just assume provider same as operator for testing + provider_address = ( + self._get_staking_provider_from_operator(operator=operator_address) + or transacting_power.account + ) + + participant_keys = self._participant_keys_history.get(provider_address) + if not participant_keys: + participant_keys = [] + + participant_keys.append( + self.ParticipantKey( + lastRitualId=len(self.rituals), + publicKey=self.G2Point.from_public_key(public_key), + ) + ) + + return self.blockchain.FAKE_RECEIPT + # # Calls # @@ -158,12 +212,12 @@ class MockCoordinatorAgent(MockContractAgent): ) -> CoordinatorAgent.Ritual: return self.rituals[ritual_id] - def get_participants(self, ritual_id: int) -> List[Ritual.Participant]: + def get_participants(self, ritual_id: int) -> List[Participant]: return self.rituals[ritual_id].participants def get_participant_from_provider( self, ritual_id: int, provider: ChecksumAddress - ) -> Ritual.Participant: + ) -> Participant: for p in self.rituals[ritual_id].participants: if p.provider == provider: return p @@ -189,6 +243,15 @@ class MockCoordinatorAgent(MockContractAgent): else: raise RuntimeError(f"Ritual {ritual_id} is in an unknown state") # :-( + def get_ritual_id_from_public_key(self, public_key: DkgPublicKey) -> int: + for i, ritual in enumerate(self.rituals): + if bytes(ritual.public_key) == bytes(public_key): + return i + + raise ValueError( + f"No ritual id found for public key 0x{bytes(public_key).hex()}" + ) + def get_ritual_public_key(self, ritual_id: int) -> DkgPublicKey: if self.get_ritual_status(ritual_id=ritual_id) != self.RitualStatus.FINALIZED: # TODO should we raise here instead? @@ -200,8 +263,21 @@ class MockCoordinatorAgent(MockContractAgent): return ritual.public_key.to_dkg_public_key() + def get_provider_public_key( + self, provider: ChecksumAddress, ritual_id: int + ) -> FerveoPublicKey: + participant_keys = self._participant_keys_history.get(provider) + for participant_key in reversed(participant_keys): + if participant_key.lastRitualId <= ritual_id: + g2Point = participant_key.publicKey + return g2Point.to_public_key() + + raise ValueError( + f"Public key not found for provider {provider} for ritual #{ritual_id}" + ) + def is_encryption_authorized( - self, ritual_id: int, evidence: bytes, digest: bytes + self, ritual_id: int, evidence: bytes, ciphertext_header: bytes ) -> bool: - # get ritual -> get access controller -> call isAuthorized(ritualId, evidence, digest) + # always allow return True diff --git a/tests/mock/interfaces.py b/tests/mock/interfaces.py index a6add7ab0..c82599dc2 100644 --- a/tests/mock/interfaces.py +++ b/tests/mock/interfaces.py @@ -7,7 +7,7 @@ from typing import Union from hexbytes import HexBytes -from nucypher.blockchain.eth.clients import EthereumClient +from nucypher.blockchain.eth.clients import EthereumTesterClient from nucypher.blockchain.eth.networks import NetworksInventory from nucypher.blockchain.eth.registry import ( BaseContractRegistry, @@ -82,7 +82,7 @@ class MockBlockchain(TesterBlockchain): return self.FAKE_RECEIPT -class MockEthereumClient(EthereumClient): +class MockEthereumClient(EthereumTesterClient): def __init__(self, w3): super().__init__(w3=w3, node_technology=None, version=None, platform=None, backend=None) diff --git a/tests/unit/test_coordinator.py b/tests/unit/test_coordinator.py index eef0baa0a..5991c9083 100644 --- a/tests/unit/test_coordinator.py +++ b/tests/unit/test_coordinator.py @@ -31,12 +31,21 @@ def test_mock_coordinator_creation(coordinator): assert len(coordinator.rituals) == 0 -def test_mock_coordinator_initiation(mocker, nodes_transacting_powers, coordinator, random_address): +def test_mock_coordinator_initiation( + mocker, + nodes_transacting_powers, + coordinator, + random_address, + get_random_checksum_address, +): assert len(coordinator.rituals) == 0 mock_transacting_power = mocker.Mock() mock_transacting_power.account = random_address coordinator.initiate_ritual( providers=list(nodes_transacting_powers.keys()), + authority=mock_transacting_power.account, + duration=1, + access_controller=get_random_checksum_address(), transacting_power=mock_transacting_power, ) assert len(coordinator.rituals) == 1 @@ -54,7 +63,7 @@ def test_mock_coordinator_initiation(mocker, nodes_transacting_powers, coordinat signal_type, signal_data = signal assert signal_type == MockCoordinatorAgent.Events.START_RITUAL assert signal_data["ritual_id"] == 0 - assert signal_data["initiator"] == mock_transacting_power.account + assert signal_data["authority"] == mock_transacting_power.account assert set(signal_data["participants"]) == nodes_transacting_powers.keys() diff --git a/tests/unit/test_ritualist.py b/tests/unit/test_ritualist.py index e6eeb607a..51b74e46e 100644 --- a/tests/unit/test_ritualist.py +++ b/tests/unit/test_ritualist.py @@ -32,9 +32,19 @@ def transacting_power(testerchain, alice): ) -def test_initiate_ritual(agent: CoordinatorAgent, cohort, transacting_power): +def test_initiate_ritual( + agent: CoordinatorAgent, cohort, transacting_power, get_random_checksum_address +): + # any value will do + global_allow_list = get_random_checksum_address() + + duration = 100 receipt = agent.initiate_ritual( - providers=cohort, transacting_power=transacting_power + authority=transacting_power.account, + access_controller=global_allow_list, + providers=cohort, + duration=duration, + transacting_power=transacting_power, ) participants = [ @@ -44,10 +54,16 @@ def test_initiate_ritual(agent: CoordinatorAgent, cohort, transacting_power): for c in cohort ] + init_timestamp = 123456 + end_timestamp = init_timestamp + duration ritual = CoordinatorAgent.Ritual( initiator=transacting_power.account, + authority=transacting_power.account, + access_controller=global_allow_list, dkg_size=4, + threshold=MockCoordinatorAgent.get_threshold_for_ritual_size(dkg_size=4), init_timestamp=123456, + end_timestamp=end_timestamp, participants=participants, ) agent.get_ritual = lambda *args, **kwargs: ritual @@ -59,17 +75,30 @@ def test_initiate_ritual(agent: CoordinatorAgent, cohort, transacting_power): return ritual_id -def test_perform_round_1(ursula, random_address, cohort, agent, random_transcript): +def test_perform_round_1( + ursula, + random_address, + cohort, + agent, + random_transcript, + get_random_checksum_address, +): participants = dict() for checksum_address in cohort: participants[checksum_address] = CoordinatorAgent.Ritual.Participant( provider=checksum_address, ) + init_timestamp = 123456 + end_timestamp = init_timestamp + 100 ritual = CoordinatorAgent.Ritual( initiator=random_address, + authority=random_address, + access_controller=get_random_checksum_address(), dkg_size=4, - init_timestamp=123456, + threshold=MockCoordinatorAgent.get_threshold_for_ritual_size(dkg_size=4), + init_timestamp=init_timestamp, + end_timestamp=end_timestamp, total_transcripts=4, participants=list(participants.values()), ) @@ -90,7 +119,7 @@ def test_perform_round_1(ursula, random_address, cohort, agent, random_transcrip for state in non_application_states: agent.get_ritual_status = lambda *args, **kwargs: state tx_hash = ursula.perform_round_1( - ritual_id=0, initiator=random_address, participants=cohort, timestamp=0 + ritual_id=0, authority=random_address, participants=cohort, timestamp=0 ) assert tx_hash is None # no execution performed @@ -100,7 +129,7 @@ def test_perform_round_1(ursula, random_address, cohort, agent, random_transcrip ) tx_hash = ursula.perform_round_1( - ritual_id=0, initiator=random_address, participants=cohort, timestamp=0 + ritual_id=0, authority=random_address, participants=cohort, timestamp=0 ) assert tx_hash is not None @@ -109,7 +138,7 @@ def test_perform_round_1(ursula, random_address, cohort, agent, random_transcrip # try again tx_hash = ursula.perform_round_1( - ritual_id=0, initiator=random_address, participants=cohort, timestamp=0 + ritual_id=0, authority=random_address, participants=cohort, timestamp=0 ) assert tx_hash is None # no execution since pending tx already present @@ -124,20 +153,26 @@ def test_perform_round_1(ursula, random_address, cohort, agent, random_transcrip # try submitting again tx_hash = ursula.perform_round_1( - ritual_id=0, initiator=random_address, participants=cohort, timestamp=0 + ritual_id=0, authority=random_address, participants=cohort, timestamp=0 ) assert tx_hash is None # no execution performed # participant no longer already posted aggregated transcript participant.transcript = bytes() tx_hash = ursula.perform_round_1( - ritual_id=0, initiator=random_address, participants=cohort, timestamp=0 + ritual_id=0, authority=random_address, participants=cohort, timestamp=0 ) assert tx_hash is not None # execution occurs def test_perform_round_2( - ursula, cohort, transacting_power, agent, mocker, random_transcript + ursula, + cohort, + transacting_power, + agent, + mocker, + random_transcript, + get_random_checksum_address, ): participants = dict() for checksum_address in cohort: @@ -148,10 +183,19 @@ def test_perform_round_2( participants[checksum_address] = participant + init_timestamp = 123456 + end_timestamp = init_timestamp + 100 + ritual = CoordinatorAgent.Ritual( initiator=transacting_power.account, + authority=transacting_power.account, + access_controller=get_random_checksum_address(), dkg_size=len(cohort), - init_timestamp=123456, + threshold=MockCoordinatorAgent.get_threshold_for_ritual_size( + dkg_size=len(cohort) + ), + init_timestamp=init_timestamp, + end_timestamp=end_timestamp, total_transcripts=len(cohort), participants=list(participants.values()), ) diff --git a/tests/utils/ape.py b/tests/utils/ape.py index 40e6bce46..7f1b93d19 100644 --- a/tests/utils/ape.py +++ b/tests/utils/ape.py @@ -1,59 +1,140 @@ import json from copy import deepcopy from pathlib import Path -from typing import Any, Dict, Tuple +from typing import Dict, List, Tuple, Union from ape import config as ape_config +from ape import project from ape.api import AccountAPI, DependencyAPI +from ape.contracts.base import ContractInstance +from ape_test.accounts import TestAccount +from eth_typing import ChecksumAddress from eth_utils import to_checksum_address from nucypher.blockchain.eth.agents import ( CoordinatorAgent, NucypherTokenAgent, - PREApplicationAgent, SubscriptionManagerAgent, + TACoApplicationAgent, ) from nucypher.blockchain.eth.registry import InMemoryContractRegistry -from tests.constants import MOCK_STAKING_CONTRACT_NAME +from tests.constants import ( + CONDITION_NFT, + GLOBAL_ALLOW_LIST, + MOCK_STAKING_CONTRACT_NAME, + RITUAL_TOKEN, + STAKE_INFO, + T_TOKEN, +) # order sensitive _CONTRACTS_TO_DEPLOY_ON_TESTERCHAIN = ( + RITUAL_TOKEN, + T_TOKEN, NucypherTokenAgent.contract_name, + STAKE_INFO, MOCK_STAKING_CONTRACT_NAME, - PREApplicationAgent.contract_name, + TACoApplicationAgent.contract_name, SubscriptionManagerAgent.contract_name, CoordinatorAgent.contract_name, + GLOBAL_ALLOW_LIST, + CONDITION_NFT, ) +VARIABLE_PREFIX_SYMBOL = "<" +VARIABLE_SUFFIX_SYMBOL = ">" + def get_ape_project_build_path(project) -> Path: build_path = Path(project.path) / '.build' return build_path +def _is_variable(param: Union[str, int, List[Union[str, int]]]) -> bool: + """Check if param is a ape-config variable""" + return isinstance(param, str) and ( + param.startswith(VARIABLE_PREFIX_SYMBOL) + and param.endswith(VARIABLE_SUFFIX_SYMBOL) + ) + + +def _resolve_variable( + param: str, + contract_name: str, + deployments: Dict[str, ContractInstance], + accounts: List[TestAccount], +) -> Union[ChecksumAddress, str, int]: + """Resolve a ape-config variable to a literal""" + dependency_expression = param.strip(VARIABLE_PREFIX_SYMBOL).strip( + VARIABLE_SUFFIX_SYMBOL + ) + dependency_name, attribute_name = dependency_expression.split(".") + if dependency_name == "address": + try: + account = accounts[int(attribute_name)] + except ValueError: + raise ValueError( + f"Ape account must be accessed by an index; got '{attribute_name}'." + ) + address = ChecksumAddress(account.address) + return address + try: + param = getattr(deployments[dependency_name], attribute_name) + except KeyError: + raise ValueError(f"Contract {contract_name} not found in deployments") + except AttributeError: + raise ValueError(f"Attribute {attribute_name} not found in {dependency_name}") + return param + + def process_deployment_params( - contract_name, params, deployments, symbol: str = "::" -) -> Dict[str, Any]: + contract_name: str, + params: Dict[str, Union[str, int, list]], + deployments: Dict[str, ContractInstance], + accounts: List[TestAccount], +) -> Dict[str, Union[ChecksumAddress, str, int]]: """Process deployment params for a contract.""" processed_params = dict() - for k, v in params.items(): - if isinstance(v, str) and (v.startswith(symbol) and v.endswith(symbol)): - dependency_expression = v.strip(symbol) - dependency_name, attribute_name = dependency_expression.split(".") - try: - v = getattr(deployments[dependency_name], attribute_name) - except KeyError: - raise ValueError(f"Contract {contract_name} not found in deployments") - except AttributeError: - raise ValueError( - f"Attribute {attribute_name} not found in {dependency_name}" - ) - processed_params[k] = v + for param_name, param_value in params.items(): + if _is_variable(param_value): + param_value = _resolve_variable( + param=param_value, + contract_name=contract_name, + deployments=deployments, + accounts=accounts, + ) + processed_params[param_name] = param_value + continue + + elif isinstance(param_value, list): + value_list = list() + for param in param_value: + if _is_variable(param): + param = _resolve_variable( + param=param, + contract_name=contract_name, + deployments=deployments, + accounts=accounts, + ) + + value_list.append(param) + + processed_params[param_name] = value_list + continue + + else: + # this parameter is a literal + processed_params[param_name] = param_value + continue + return processed_params def get_deployment_params( - contract_name, config, accounts, deployments + contract_name: str, + config: Dict[str, Union[str, list]], + accounts: List[TestAccount], + deployments: Dict[str, ContractInstance], ) -> Tuple[Dict, AccountAPI]: """ Get deployment params for a contract. @@ -64,14 +145,23 @@ def get_deployment_params( deployer_address = accounts[params.pop("address")] name = params.pop("contract_type") if name == contract_name: - params = process_deployment_params(contract_name, params, deployments) + params = process_deployment_params( + contract_name=contract_name, + params=params, + deployments=deployments, + accounts=accounts, + ) return params, deployer_address else: # there are no deployment params for this contract; default to account at index 0 return dict(), accounts[0] -def deploy_contracts(nucypher_contracts: DependencyAPI, accounts): +def deploy_contracts( + nucypher_contracts: DependencyAPI, + test_contracts: DependencyAPI, + accounts: List[TestAccount], +): """Deploy contracts o via ape's API for testing.""" config = ape_config.get_config("deployments")["ethereum"]["local"] deployments = dict() @@ -79,22 +169,42 @@ def deploy_contracts(nucypher_contracts: DependencyAPI, accounts): params, deployer_account = get_deployment_params( name, deployments=deployments, config=config, accounts=accounts ) - dependency_contract = getattr(nucypher_contracts, name) - deployed_contract = deployer_account.deploy(dependency_contract, *params.values()) + try: + # this contract is a dependency + contract = getattr(nucypher_contracts, name) + except AttributeError: + # this contract is local to this project + try: + contract = getattr(project, name) + except AttributeError: + raise ValueError( + f"Contract {name} not found in project or in dependencies." + ) + deployed_contract = deployer_account.deploy(contract, *params.values()) deployments[name] = deployed_contract return deployments -def registry_from_ape_deployments(nucypher_contracts: DependencyAPI, deployments: Dict) -> InMemoryContractRegistry: +def registry_from_ape_deployments( + nucypher_contracts: DependencyAPI, deployments: Dict[str, ContractInstance] +) -> InMemoryContractRegistry: """Creates a registry from ape deployments.""" - # Get the raw abi from the cached manifest - manifest = json.loads(nucypher_contracts.cached_manifest.json()) - contract_data = manifest['contractTypes'] + local_contracts = project.contracts + + # Get the raw abi from the cached dependency manifest + dependency_manifest = json.loads(nucypher_contracts.cached_manifest.json()) + combined_contract_data = dependency_manifest["contractTypes"] + + # Add the local contract ABIs to the data + for contract_name, local_contract_data in local_contracts.items(): + contract_manifest = json.loads(local_contract_data.json()) + contract_abi = contract_manifest["abi"] + combined_contract_data[contract_name] = {"abi": contract_abi} data = list() for contract_name, deployment in deployments.items(): - abi = contract_data[contract_name]['abi'] + abi = combined_contract_data[contract_name]["abi"] entry = [ contract_name, 'v0.0.0', # TODO: get version from contract diff --git a/tests/utils/blockchain.py b/tests/utils/blockchain.py index d79ccc518..bb922f06a 100644 --- a/tests/utils/blockchain.py +++ b/tests/utils/blockchain.py @@ -1,25 +1,17 @@ import os -from typing import List, Optional, Tuple, Union +from typing import List, Union import maya -from ape_test import LocalProvider from eth_tester.exceptions import TransactionFailed -from eth_utils import to_canonical_address from hexbytes import HexBytes from web3 import Web3 from nucypher.blockchain.economics import Economics from nucypher.blockchain.eth.interfaces import ( BlockchainInterface, - BlockchainInterfaceFactory, -) -from nucypher.blockchain.eth.registry import ( - BaseContractRegistry, - InMemoryContractRegistry, ) from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.blockchain.eth.token import NU -from nucypher.config.constants import TEMPORARY_DOMAIN from nucypher.crypto.powers import TransactingPower from nucypher.utilities.gas_strategies import EXPECTED_CONFIRMATION_TIME_IN_SECONDS from nucypher.utilities.logging import Logger