Merge pull request #3213 from KPrasch/alpha-12

Coordinator Alpha 12 Support
pull/3226/head
Derek Pierre 2023-09-14 16:23:58 -04:00 committed by GitHub
commit 84d8344236
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1142 additions and 455 deletions

View File

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

View File

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

View File

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

View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.'

View File

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

View File

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

View File

@ -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}",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: <TToken.address>
threshold_staking: <ThresholdStakingForTACoApplicationMock.address>
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: <StakeInfo.address>
ritual_timeout: 3600
max_dkg_size: 8
admin: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0
currency: <RitualToken.address>
fee: 1
- contract_type: GlobalAllowList
address: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0
coordinator: <Coordinator.address>
admin: '0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C' # deployer account at index 0
test:
mnemonic: test test test test test test test test test test test junk

View File

@ -1,5 +0,0 @@
dependencies:
- name: nucypher-contracts
github: nucypher/nucypher-contracts
ref: main
contracts_folder: src

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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