Merge pull request #444 from KPrasch/tidy-types

A bucket of type hints and bug fixes
pull/455/head
K Prasch 2018-09-22 15:50:09 -07:00 committed by GitHub
commit 53e1624ced
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 246 additions and 158 deletions

View File

@ -6,3 +6,7 @@ disallow_untyped_defs=False
check_untyped_defs=False check_untyped_defs=False
disallow_untyped_calls=False disallow_untyped_calls=False
ignore_missing_imports=True ignore_missing_imports=True
warn_return_any = False
no_implicit_optional = False
strict_optional = False
warn_no_return = False

View File

@ -1,6 +1,6 @@
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime from datetime import datetime
from typing import Tuple from typing import Tuple, List
import maya import maya
@ -37,18 +37,18 @@ class NucypherTokenActor:
""" """
try: try:
parent_address = self.checksum_public_address parent_address = self.checksum_public_address # type: str
if checksum_address is not None: if checksum_address is not None:
if parent_address != checksum_address: if parent_address != checksum_address:
raise ValueError("Can't have two different addresses.") raise ValueError("Can't have two different addresses.")
except AttributeError: except AttributeError:
self.checksum_public_address = checksum_address self.checksum_public_address = checksum_address # type: str
if registry_filepath is not None: if registry_filepath is not None:
EthereumContractRegistry(registry_filepath=registry_filepath) EthereumContractRegistry(registry_filepath=registry_filepath)
self.token_agent = token_agent if token_agent is not None else NucypherTokenAgent() self.token_agent = token_agent if token_agent is not None else NucypherTokenAgent()
self._transaction_cache = list() # track transactions transmitted self._transaction_cache = list() # type: list # track transactions transmitted
def __repr__(self): def __repr__(self):
class_name = self.__class__.__name__ class_name = self.__class__.__name__
@ -206,7 +206,7 @@ class Miner(NucypherTokenActor):
if entire_balance is True: if entire_balance is True:
amount = self.token_balance amount = self.token_balance
staking_transactions = OrderedDict() # Time series of txhases staking_transactions = OrderedDict() # type: OrderedDict # Time series of txhases
# Validate # Validate
assert self.__validate_stake(amount=amount, lock_periods=lock_periods) assert self.__validate_stake(amount=amount, lock_periods=lock_periods)
@ -281,7 +281,7 @@ class PolicyAuthor(NucypherTokenActor):
checksum_address=checksum_address, checksum_address=checksum_address,
) )
def recruit(self, quantity: int, **options) -> None: def recruit(self, quantity: int, **options) -> List[str]:
""" """
Uses sampling logic to gather miners from the blockchain and Uses sampling logic to gather miners from the blockchain and
caches the resulting node ethereum addresses. caches the resulting node ethereum addresses.

View File

@ -1,12 +1,12 @@
import random import random
from abc import ABC from abc import ABC
from typing import Generator, List, Tuple, Union
from constant_sorrow import constants from constant_sorrow import constants
from typing import Generator, List, Tuple, Union
from web3.contract import Contract
from nucypher.blockchain.eth import constants from nucypher.blockchain.eth import constants
from nucypher.blockchain.eth.chains import Blockchain from nucypher.blockchain.eth.chains import Blockchain
from nucypher.blockchain.eth.interfaces import EthereumContractRegistry
class EthereumContractAgent(ABC): class EthereumContractAgent(ABC):
@ -31,20 +31,21 @@ class EthereumContractAgent(ABC):
def __init__(self, def __init__(self,
blockchain: Blockchain = None, blockchain: Blockchain = None,
registry_filepath: str = None, registry_filepath: str = None,
*args, **kwargs) -> None: contract: Contract = None
) -> None:
if blockchain is None: if blockchain is None:
blockchain = Blockchain.connect() blockchain = Blockchain.connect()
self.blockchain = blockchain self.blockchain = blockchain
if registry_filepath is not None: if registry_filepath is not None:
# TODO: Warn on override? # TODO: Warn on override/ do this elsewhere?
self.blockchain.interface._registry._swap_registry(filepath=registry_filepath) self.blockchain.interface._registry._swap_registry(filepath=registry_filepath)
# Fetch the contract by reading address and abi from the registry and blockchain if contract is None:
contract = self.blockchain.interface.get_contract_by_name(name=self.principal_contract_name, # Fetch the contract by reading address and abi from the registry and blockchain
upgradeable=self._upgradeable) contract = self.blockchain.interface.get_contract_by_name(name=self.principal_contract_name,
upgradeable=self._upgradeable)
self.__contract = contract self.__contract = contract
super().__init__() super().__init__()
@ -98,14 +99,23 @@ class MinerAgent(EthereumContractAgent):
principal_contract_name = "MinersEscrow" principal_contract_name = "MinersEscrow"
_upgradeable = True _upgradeable = True
__instance = None __instance = None # TODO: constants.NO_CONTRACT_AVAILABLE
class NotEnoughMiners(Exception): class NotEnoughMiners(Exception):
pass pass
def __init__(self, token_agent: NucypherTokenAgent=None, registry_filepath=None, *args, **kwargs) -> None: def __init__(self,
token_agent: NucypherTokenAgent = None,
registry_filepath: str = None,
*args, **kwargs
) -> None:
token_agent = token_agent if token_agent is not None else NucypherTokenAgent(registry_filepath=registry_filepath) token_agent = token_agent if token_agent is not None else NucypherTokenAgent(registry_filepath=registry_filepath)
super().__init__(blockchain=token_agent.blockchain, registry_filepath=registry_filepath, *args, **kwargs)
super().__init__(blockchain=token_agent.blockchain,
registry_filepath=registry_filepath,
*args, **kwargs)
self.token_agent = token_agent self.token_agent = token_agent
# #

View File

@ -1,9 +1,13 @@
from constant_sorrow import constants
from typing import Tuple, Dict from typing import Tuple, Dict
from constant_sorrow import constants from nucypher.blockchain.eth.agents import (
EthereumContractAgent,
from nucypher.blockchain.eth.agents import EthereumContractAgent, MinerAgent, NucypherTokenAgent, PolicyAgent, \ MinerAgent,
NucypherTokenAgent,
PolicyAgent,
UserEscrowAgent UserEscrowAgent
)
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface
from .chains import Blockchain from .chains import Blockchain
@ -18,10 +22,14 @@ class ContractDeployer:
class ContractDeploymentError(Exception): class ContractDeploymentError(Exception):
pass pass
def __init__(self, blockchain: Blockchain, deployer_address: str) -> None: def __init__(self,
blockchain: Blockchain,
deployer_address: str
) -> None:
self.__armed = False self.__armed = False
self._contract = None self._contract = constants.CONTRACT_NOT_DEPLOYED
self.deployment_receipt = None self.deployment_receipt = constants.CONTRACT_NOT_DEPLOYED
self.__dispatcher = NotImplemented self.__dispatcher = NotImplemented
# Sanity check # Sanity check
@ -33,13 +41,11 @@ class ContractDeployer:
@property @property
def contract_address(self) -> str: def contract_address(self) -> str:
try: if self._contract is constants.CONTRACT_NOT_DEPLOYED:
address = self._contract.address
except AttributeError:
cls = self.__class__ cls = self.__class__
raise cls.ContractDeploymentError('Contract not deployed') raise ContractDeployer.ContractDeploymentError('Contract not deployed')
else: address = self._contract.address # type: str
return address return address
@property @property
def deployer_address(self): def deployer_address(self):
@ -55,7 +61,7 @@ class ContractDeployer:
@property @property
def is_deployed(self) -> bool: def is_deployed(self) -> bool:
return bool(self._contract is not None) return bool(self._contract is not constants.CONTRACT_NOT_DEPLOYED)
@property @property
def is_armed(self) -> bool: def is_armed(self) -> bool:
@ -95,14 +101,14 @@ class ContractDeployer:
def _ensure_contract_deployment(self) -> bool: def _ensure_contract_deployment(self) -> bool:
"""Raises ContractDeploymentError if the contract has not been armed and deployed.""" """Raises ContractDeploymentError if the contract has not been armed and deployed."""
if self._contract is None: if self._contract is constants.CONTRACT_NOT_DEPLOYED:
class_name = self.__class__.__name__ class_name = self.__class__.__name__
message = '{} contract is not deployed. Arm, then deploy.'.format(class_name) message = '{} contract is not deployed. Arm, then deploy.'.format(class_name)
raise self.ContractDeploymentError(message) raise self.ContractDeploymentError(message)
return True return True
def arm(self, fail_on_abort=True) -> None: def arm(self, fail_on_abort: bool = True) -> None:
""" """
Safety mechanism for ethereum contract deployment Safety mechanism for ethereum contract deployment
@ -140,7 +146,7 @@ class ContractDeployer:
arm = True # If this is a private chain, just arm the deployer without interaction. arm = True # If this is a private chain, just arm the deployer without interaction.
self.__armed = arm # Set the arming status self.__armed = arm # Set the arming status
def deploy(self) -> str: def deploy(self) -> dict:
""" """
Used after arming the deployer; Used after arming the deployer;
Provides for the setup, deployment, and initialization of ethereum smart contracts. Provides for the setup, deployment, and initialization of ethereum smart contracts.
@ -158,7 +164,11 @@ class NucypherTokenDeployer(ContractDeployer):
agency = NucypherTokenAgent agency = NucypherTokenAgent
_contract_name = agency.principal_contract_name # TODO _contract_name = agency.principal_contract_name # TODO
def __init__(self, blockchain, deployer_address) -> None: def __init__(self,
blockchain,
deployer_address: str
) -> None:
if not type(blockchain.interface) is self._interface_class: if not type(blockchain.interface) is self._interface_class:
raise ValueError("{} must be used to create a {}".format(self._interface_class.__name__, raise ValueError("{} must be used to create a {}".format(self._interface_class.__name__,
self.__class__.__name__)) self.__class__.__name__))
@ -166,7 +176,7 @@ class NucypherTokenDeployer(ContractDeployer):
super().__init__(blockchain=blockchain, deployer_address=deployer_address) super().__init__(blockchain=blockchain, deployer_address=deployer_address)
self._creator = deployer_address self._creator = deployer_address
def deploy(self) -> str: def deploy(self) -> dict:
""" """
Deploy and publish the NuCypher Token contract Deploy and publish the NuCypher Token contract
to the blockchain network specified in self.blockchain.network. to the blockchain network specified in self.blockchain.network.
@ -183,7 +193,7 @@ class NucypherTokenDeployer(ContractDeployer):
int(constants.TOKEN_SATURATION)) int(constants.TOKEN_SATURATION))
self._contract = _contract self._contract = _contract
return self.deployment_receipt return {'deployment_receipt': self.deployment_receipt}
class DispatcherDeployer(ContractDeployer): class DispatcherDeployer(ContractDeployer):
@ -199,14 +209,14 @@ class DispatcherDeployer(ContractDeployer):
self.secret_hash = secret_hash self.secret_hash = secret_hash
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def deploy(self) -> str: def deploy(self) -> dict:
dispatcher_contract, txhash = self.blockchain.interface.deploy_contract('Dispatcher', dispatcher_contract, txhash = self.blockchain.interface.deploy_contract('Dispatcher',
self.target_contract.address, self.target_contract.address,
self.secret_hash) self.secret_hash)
self._contract = dispatcher_contract self._contract = dispatcher_contract
return txhash return {'txhash': txhash}
class MinerEscrowDeployer(ContractDeployer): class MinerEscrowDeployer(ContractDeployer):
@ -227,7 +237,7 @@ class MinerEscrowDeployer(ContractDeployer):
if result is constants.NULL_ADDRESS: if result is constants.NULL_ADDRESS:
raise RuntimeError("PolicyManager contract is not initialized.") raise RuntimeError("PolicyManager contract is not initialized.")
def deploy(self) -> Dict[str, str]: def deploy(self) -> dict:
""" """
Deploy and publish the NuCypher Token contract Deploy and publish the NuCypher Token contract
to the blockchain network specified in self.blockchain.network. to the blockchain network specified in self.blockchain.network.
@ -383,7 +393,7 @@ class UserEscrowDeployer(ContractDeployer):
self.token_deployer = miner_escrow_deployer.token_deployer self.token_deployer = miner_escrow_deployer.token_deployer
super().__init__(blockchain=miner_escrow_deployer.blockchain, *args, **kwargs) super().__init__(blockchain=miner_escrow_deployer.blockchain, *args, **kwargs)
def deploy(self): def deploy(self) -> dict:
is_ready, _disqualifications = self.check_ready_to_deploy(fail=True) is_ready, _disqualifications = self.check_ready_to_deploy(fail=True)
assert is_ready assert is_ready
@ -391,11 +401,11 @@ class UserEscrowDeployer(ContractDeployer):
self.miner_deployer.contract_address, self.miner_deployer.contract_address,
self.policy_deployer.contract_address] self.policy_deployer.contract_address]
deploy_transaction = {'from': self.token_deployer.contract_address} deploy_transaction = {'from': self.token_deployer.contract_address} # TODO:.. eh?
the_user_escrow_contract, deploy_txhash = self.blockchain.interface.deploy_contract( the_user_escrow_contract, deploy_txhash = self.blockchain.interface.deploy_contract(
self._contract_name, self._contract_name,
*deployment_args) *deployment_args)
self._contract = the_user_escrow_contract self._contract = the_user_escrow_contract
return deploy_txhash return {'deploy_txhash': deploy_txhash}

View File

@ -155,7 +155,7 @@ class BlockchainInterface:
@classmethod @classmethod
def from_config(cls, config: NodeConfiguration) -> 'BlockchainInterface': def from_config(cls, config: NodeConfiguration) -> 'BlockchainInterface':
# Parse # Parse
payload = parse_blockchain_config(filepath=config.ini_filepath) payload = parse_blockchain_config(filepath=config.config_file_location)
# Init deps # Init deps
compiler = SolidityCompiler() if payload['compile'] else None compiler = SolidityCompiler() if payload['compile'] else None
@ -171,7 +171,7 @@ class BlockchainInterface:
return circumflex return circumflex
@property @property
def providers(self) -> Tuple[Union[IPCProvider, WebsocketProvider, HTTPProvider]]: def providers(self) -> Tuple[Union[IPCProvider, WebsocketProvider, HTTPProvider], ...]:
return tuple(self.__providers) return tuple(self.__providers)
@property @property

View File

@ -1,5 +1,5 @@
from collections import deque from collections import deque
from typing import List from typing import List, Tuple, Iterable
from typing import Set from typing import Set
import math import math
@ -9,7 +9,7 @@ from eth_utils import to_canonical_address
from nucypher.blockchain.eth.actors import Miner from nucypher.blockchain.eth.actors import Miner
from nucypher.blockchain.eth.actors import PolicyAuthor from nucypher.blockchain.eth.actors import PolicyAuthor
from nucypher.blockchain.eth.agents import MinerAgent from nucypher.blockchain.eth.agents import MinerAgent, PolicyAgent
from nucypher.blockchain.eth.constants import calculate_period_duration from nucypher.blockchain.eth.constants import calculate_period_duration
from nucypher.characters.lawful import Ursula from nucypher.characters.lawful import Ursula
from nucypher.network.middleware import RestMiddleware from nucypher.network.middleware import RestMiddleware
@ -28,29 +28,30 @@ class BlockchainArrangement(Arrangement):
lock_periods: int, lock_periods: int,
expiration: maya.MayaDT, expiration: maya.MayaDT,
*args, **kwargs) -> None: *args, **kwargs) -> None:
super().__init__(alice=author, ursula=miner, *args, **kwargs) super().__init__(alice=author, ursula=miner, *args, **kwargs)
delta = expiration - maya.now() delta = expiration - maya.now()
hours = (delta.total_seconds() / 60) / 60 hours = (delta.total_seconds() / 60) / 60 # type: int
periods = int(math.ceil(hours / int(constants.HOURS_PER_PERIOD))) periods = int(math.ceil(hours / int(constants.HOURS_PER_PERIOD))) # type: int
# The relationship exists between two addresses # The relationship exists between two addresses
self.author = author self.author = author # type: PolicyAuthor
self.policy_agent = author.policy_agent self.policy_agent = author.policy_agent # type: PolicyAgent
self.miner = miner self.miner = miner # type: Miner
# Arrangement value, rate, and duration # Arrangement value, rate, and duration
rate = value // lock_periods rate = value // lock_periods # type: int
self._rate = rate self._rate = rate # type: int
self.value = value self.value = value # type: int
self.lock_periods = lock_periods # TODO: <datetime> -> lock_periods self.lock_periods = lock_periods # type: int # TODO: <datetime> -> lock_periods
self.is_published = False self.is_published = False # type: bool
self.publish_transaction = None self.publish_transaction = None
self.is_revoked = False self.is_revoked = False # type: bool
self.revoke_transaction = None self.revoke_transaction = None
def __repr__(self): def __repr__(self):
@ -60,9 +61,11 @@ class BlockchainArrangement(Arrangement):
return r return r
def publish(self) -> str: def publish(self) -> str:
payload = {'from': self.author._ether_address, 'value': self.value} payload = {'from': self.author.checksum_public_address,
'value': self.value}
txhash = self.policy_agent.contract.functions.createPolicy(self.id, self.miner._ether_address, txhash = self.policy_agent.contract.functions.createPolicy(self.id,
self.miner.checksum_public_address,
self.lock_periods).transact(payload) self.lock_periods).transact(payload)
self.policy_agent.blockchain.wait_for_receipt(txhash) self.policy_agent.blockchain.wait_for_receipt(txhash)
@ -91,7 +94,11 @@ class BlockchainPolicy(Policy):
class NotEnoughBlockchainUrsulas(Exception): class NotEnoughBlockchainUrsulas(Exception):
pass pass
def __init__(self, author: PolicyAuthor, *args, **kwargs) -> None: def __init__(self,
author: PolicyAuthor,
*args, **kwargs
) -> None:
self.author = author self.author = author
super().__init__(alice=author, *args, **kwargs) super().__init__(alice=author, *args, **kwargs)
@ -104,14 +111,18 @@ class BlockchainPolicy(Policy):
duration = end_block - start_block duration = end_block - start_block
miner = Miner(address=miner_address, miner_agent=self.author.policy_agent.miner_agent) miner = Miner(address=miner_address, miner_agent=self.author.policy_agent.miner_agent)
arrangement = BlockchainArrangement(author=self.author, miner=miner, lock_periods=duration) arrangement = BlockchainArrangement(author=self.author,
miner=miner,
value=rate*duration, # TODO Check the math/types here
lock_periods=duration,
expiration=end_block) # TODO: fix missing argument here
arrangement.is_published = True arrangement.is_published = True
return arrangement return arrangement
def __find_ursulas(self, ether_addresses: List[str], target_quantity: int, timeout: int = 120): def __find_ursulas(self, ether_addresses: List[str], target_quantity: int, timeout: int = 120):
start_time = maya.now() # Marker for timeout calculation start_time = maya.now() # Marker for timeout calculation
found_ursulas, unknown_addresses = set(), deque() found_ursulas, unknown_addresses = set(), deque() # type: set, deque
while len(found_ursulas) < target_quantity: while len(found_ursulas) < target_quantity:
# Check for a timeout # Check for a timeout
@ -146,24 +157,27 @@ class BlockchainPolicy(Policy):
return found_ursulas return found_ursulas
def make_arrangements(self, network_middleware: RestMiddleware, def make_arrangements(self,
deposit: int, expiration: maya.MayaDT, network_middleware: RestMiddleware,
handpicked_ursulas: Set[Ursula] = set()) -> None: deposit: int,
expiration: maya.MayaDT,
handpicked_ursulas: Set[Ursula] = None
) -> None:
""" """
Create and consider n Arrangements from sampled miners, a list of Ursulas, or a combination of both. Create and consider n Arrangements from sampled miners, a list of Ursulas, or a combination of both.
""" """
ADDITIONAL_URSULAS = 1.5 # TODO: Make constant ADDITIONAL_URSULAS = 1.5 # TODO: Make constant
handpicked_ursulas = handpicked_ursulas or set() # type: set
target_sample_quantity = self.n - len(handpicked_ursulas) target_sample_quantity = self.n - len(handpicked_ursulas)
selected_addresses = set() selected_addresses = set() # type: set
try: # Sample by reading from the Blockchain try: # Sample by reading from the Blockchain
actual_sample_quantity = math.ceil(target_sample_quantity * ADDITIONAL_URSULAS) actual_sample_quantity = math.ceil(target_sample_quantity * ADDITIONAL_URSULAS)
duration = int(calculate_period_duration(expiration)) duration = int(calculate_period_duration(expiration))
sampled_addresses = self.alice.recruit(quantity=actual_sample_quantity, sampled_addresses = self.alice.recruit(quantity=actual_sample_quantity,
duration=duration, duration=duration)
)
except MinerAgent.NotEnoughMiners: except MinerAgent.NotEnoughMiners:
error = "Cannot create policy with {} arrangements." error = "Cannot create policy with {} arrangements."
raise self.NotEnoughBlockchainUrsulas(error.format(self.n)) raise self.NotEnoughBlockchainUrsulas(error.format(self.n))

View File

@ -1,12 +1,13 @@
import json import json
import os import os
from typing import Union
import shutil import shutil
import tempfile import tempfile
from constant_sorrow import constants from constant_sorrow import constants
# from nucypher.config.config import DEFAULT_CONFIG_ROOT, NodeConfiguration from nucypher.config.constants import DEFAULT_CONFIG_ROOT
# from nucypher.config.parsers import parse_blockchain_config
class EthereumContractRegistry: class EthereumContractRegistry:
@ -17,7 +18,8 @@ class EthereumContractRegistry:
WARNING: Unless you are developing NuCypher, you most likely won't ever need WARNING: Unless you are developing NuCypher, you most likely won't ever need
to use this. to use this.
""" """
# __default_registry_path = os.path.join(DEFAULT_CONFIG_ROOT, 'registry.json') # TODO: Integrate with config classes
__default_registry_path = os.path.join(DEFAULT_CONFIG_ROOT, 'contract_registry.json')
class RegistryError(Exception): class RegistryError(Exception):
pass pass
@ -32,18 +34,17 @@ class EthereumContractRegistry:
self.__registry_filepath = registry_filepath or self.__default_registry_path self.__registry_filepath = registry_filepath or self.__default_registry_path
@classmethod @classmethod
def from_config(cls, config) -> 'EthereumContractRegistry': def from_config(cls, config) -> Union['EthereumContractRegistry', 'TemporaryEthereumContractRegistry']:
if config.temp_registry is True: # In memory only if config.temp_registry is True: # In memory only
registry = TemporaryEthereumContractRegistry() return TemporaryEthereumContractRegistry()
else: else:
registry = EthereumContractRegistry() return EthereumContractRegistry()
return registry
@property @property
def registry_filepath(self): def registry_filepath(self):
return self.__registry_filepath return self.__registry_filepath
def _swap_registry(self, filepath: str) -> True: def _swap_registry(self, filepath: str) -> bool:
self.__registry_filepath = filepath self.__registry_filepath = filepath
return True return True

View File

@ -8,19 +8,27 @@ from solc.exceptions import SolcError
class SolidityCompiler: class SolidityCompiler:
# TODO: Integrate with config classes
__default_version = 'v0.4.24' __default_version = 'v0.4.24'
__default_configuration_path = os.path.join(dirname(abspath(__file__)), './compiler.json') __default_configuration_path = os.path.join(dirname(abspath(__file__)), './compiler.json')
__default_sol_binary_path = shutil.which('solc') __default_sol_binary_path = shutil.which('solc')
if __default_sol_binary_path is None: if __default_sol_binary_path is None:
__bin_path = os.path.dirname(shutil.which('python')) __bin_path = os.path.dirname(shutil.which('python')) # type: str
__default_sol_binary_path = os.path.join(__bin_path, 'solc') __default_sol_binary_path = os.path.join(__bin_path, 'solc') # type: str
__default_contract_dir = os.path.join(dirname(abspath(__file__)), 'source', 'contracts') __default_contract_dir = os.path.join(dirname(abspath(__file__)), 'source', 'contracts')
__default_chain_name = 'tester' __default_chain_name = 'tester'
def __init__(self, solc_binary_path=None, configuration_path=None, def __init__(self,
chain_name=None, contract_dir=None, test_contract_dir=None) -> None: solc_binary_path: str = None,
configuration_path: str = None,
chain_name: str = None,
contract_dir: str = None,
test_contract_dir: str= None
) -> None:
# Compiler binary and root solidity source code directory # Compiler binary and root solidity source code directory
self.__sol_binary_path = solc_binary_path if solc_binary_path is not None else self.__default_sol_binary_path self.__sol_binary_path = solc_binary_path if solc_binary_path is not None else self.__default_sol_binary_path
@ -34,7 +42,7 @@ class SolidityCompiler:
# Set the local env's solidity compiler binary # Set the local env's solidity compiler binary
os.environ['SOLC_BINARY'] = self.__sol_binary_path os.environ['SOLC_BINARY'] = self.__sol_binary_path
def install_compiler(self, version=None): def install_compiler(self, version: str=None):
""" """
Installs the specified solidity compiler version. Installs the specified solidity compiler version.
https://github.com/ethereum/py-solc#installing-the-solc-binary https://github.com/ethereum/py-solc#installing-the-solc-binary

View File

@ -13,8 +13,9 @@ from nacl.exceptions import CryptoError
from nacl.secret import SecretBox from nacl.secret import SecretBox
from umbral.keys import UmbralPrivateKey from umbral.keys import UmbralPrivateKey
from nucypher.config.constants import DEFAULT_KEYRING_ROOT from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.config.utils import validate_passphrase, NucypherConfigurationError from nucypher.config.node import NodeConfiguration
from nucypher.config.utils import validate_passphrase
from nucypher.crypto.powers import SigningPower, EncryptingPower, CryptoPower from nucypher.crypto.powers import SigningPower, EncryptingPower, CryptoPower
@ -25,7 +26,7 @@ def _parse_keyfile(keypath: str):
try: try:
key_metadata = json.loads(keyfile) key_metadata = json.loads(keyfile)
except json.JSONDecodeError: except json.JSONDecodeError:
raise NucypherConfigurationError("Invalid data in keyfile {}".format(keypath)) raise NodeConfiguration.ConfigurationError("Invalid data in keyfile {}".format(keypath))
else: else:
return key_metadata return key_metadata
@ -53,7 +54,7 @@ def _save_private_keyfile(keypath: str, key_data: dict) -> str:
mode = stat.S_IRUSR | stat.S_IWUSR # 0o600 mode = stat.S_IRUSR | stat.S_IWUSR # 0o600
try: try:
keyfile_descriptor = os.open(path=keypath, flags=flags, mode=mode) keyfile_descriptor = os.open(file=keypath, flags=flags, mode=mode)
finally: finally:
os.umask(0) # Set the umask to 0 after opening os.umask(0) # Set the umask to 0 after opening
@ -87,10 +88,10 @@ def _save_public_keyfile(keypath: str, key_data: bytes) -> str:
""" """
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL # Write, Create, Non-Existing flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL # Write, Create, Non-Existing
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH # 0o644 mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH # 0o644
try: try:
keyfile_descriptor = os.open(path=keypath, flags=flags, mode=mode) keyfile_descriptor = os.open(file=keypath, flags=flags, mode=mode)
finally: finally:
os.umask(0) # Set the umask to 0 after opening os.umask(0) # Set the umask to 0 after opening
@ -154,7 +155,10 @@ def _encrypt_umbral_key(wrapping_key: bytes, umbral_key: UmbralPrivateKey) -> di
return crypto_data return crypto_data
def _decrypt_umbral_key(wrapping_key: bytes, nonce: bytes, enc_key_material: bytes) -> UmbralPrivateKey: def _decrypt_umbral_key(wrapping_key: bytes,
nonce: bytes,
enc_key_material: bytes
) -> UmbralPrivateKey:
""" """
Decrypts an encrypted key with nacl's XSalsa20-Poly1305 algorithm (SecretBox). Decrypts an encrypted key with nacl's XSalsa20-Poly1305 algorithm (SecretBox).
Returns a decrypted key as an UmbralPrivateKey. Returns a decrypted key as an UmbralPrivateKey.
@ -209,7 +213,8 @@ class NucypherKeyring:
""" """
__default_keyring_root = DEFAULT_KEYRING_ROOT # TODO: Make lazy for better integration with config classes
__default_keyring_root = os.path.join(DEFAULT_CONFIG_ROOT, "keyring")
__default_public_key_dir = os.path.join(__default_keyring_root, 'public') __default_public_key_dir = os.path.join(__default_keyring_root, 'public')
__default_private_key_dir = os.path.join(__default_keyring_root, 'private') __default_private_key_dir = os.path.join(__default_keyring_root, 'private')
@ -228,7 +233,8 @@ class NucypherKeyring:
class KeyringLocked(KeyringError): class KeyringLocked(KeyringError):
pass pass
def __init__(self, root_key_path: str=None, def __init__(self,
root_key_path: str=None,
pub_root_key_path: str=None, pub_root_key_path: str=None,
signing_key_path: str=None, signing_key_path: str=None,
pub_signing_key_path: str=None, pub_signing_key_path: str=None,
@ -280,6 +286,7 @@ class NucypherKeyring:
if self.__derived_key_material is not None: if self.__derived_key_material is not None:
raise Exception('Keyring already unlocked') raise Exception('Keyring already unlocked')
# TODO: missing salt parameter below
derived_key = _derive_key_material_from_passphrase(passphrase=passphrase) derived_key = _derive_key_material_from_passphrase(passphrase=passphrase)
self.__derived_key_material = derived_key self.__derived_key_material = derived_key
@ -313,7 +320,12 @@ class NucypherKeyring:
return new_cryptopower return new_cryptopower
@classmethod @classmethod
def generate(cls, passphrase: str, encryption: bool=True, transacting: bool=True, output_path: str=None) -> 'NucypherKeyring': def generate(cls,
passphrase: str,
encryption: bool = True,
transacting: bool = True,
output_path: str = None
) -> 'NucypherKeyring':
""" """
Generates new encryption, signing, and transacting keys encrypted with the passphrase, Generates new encryption, signing, and transacting keys encrypted with the passphrase,
respectively saving keyfiles on the local filesystem from *default* paths, respectively saving keyfiles on the local filesystem from *default* paths,
@ -337,10 +349,10 @@ class NucypherKeyring:
os.mkdir(_private_key_dir, mode=0o700) # private os.mkdir(_private_key_dir, mode=0o700) # private
# Generate keys # Generate keys
keyring_args = dict() keyring_args = dict() # type: dict
if encryption is True: if encryption is True:
enc_privkey, enc_pubkey = _generate_encryption_keys() enc_privkey, enc_pubkey = _generate_encryption_keys()
sig_privkey, enc_pubkey = _generate_signing_keys() sig_privkey, sig_pubkey = _generate_signing_keys()
passphrase_salt = os.urandom(32) passphrase_salt = os.urandom(32)
enc_salt = os.urandom(32) enc_salt = os.urandom(32)
@ -350,21 +362,21 @@ class NucypherKeyring:
enc_wrap_key = _derive_wrapping_key_from_key_material(enc_salt, der_key_material) enc_wrap_key = _derive_wrapping_key_from_key_material(enc_salt, der_key_material)
sig_wrap_key = _derive_wrapping_key_from_key_material(sig_salt, der_key_material) sig_wrap_key = _derive_wrapping_key_from_key_material(sig_salt, der_key_material)
enc_json = _encrypt_umbral_key(der_wrap_key, enc_key) enc_json = _encrypt_umbral_key(der_key_material, enc_wrap_key)
sig_json = _encrypt_umbral_key(der_wrap_key, sig_key) sig_json = _encrypt_umbral_key(der_key_material, sig_wrap_key)
enc_json['master_salt'] = urlsafe_b64encode(salt).decode() enc_json['master_salt'] = urlsafe_b64encode(enc_salt).decode()
sig_json['master_salt'] = urlsafe_b64encode(salt).decode() sig_json['master_salt'] = urlsafe_b64encode(sig_salt).decode()
enc_json['wrap_salt'] = urlsafe_b64encode(salt).decode() enc_json['wrap_salt'] = urlsafe_b64encode(enc_salt).decode()
sig_json['wrap_salt'] = urlsafe_b64encode(salt).decode() sig_json['wrap_salt'] = urlsafe_b64encode(sig_salt).decode()
# Write private keys to files # Write private keys to files
rootkey_path = _save_private_keyfile(cls.__default_key_filepaths['root'], enc_json) rootkey_path = _save_private_keyfile(cls.__default_key_filepaths['root'], enc_json)
sigkey_path = _save_private_keyfile(cls.__default_key_filepaths['signing'], sig_json) sigkey_path = _save_private_keyfile(cls.__default_key_filepaths['signing'], sig_json)
bytes_enc_pubkey = enc_pubkey.to_bytes(encoder=urlsafe_b64encoder) bytes_enc_pubkey = enc_pubkey.to_bytes(encoder=urlsafe_b64encode)
bytes_sig_pubkey = sig_pubkey.to_bytes(encoder=urlsafe_b64encoder) bytes_sig_pubkey = sig_pubkey.to_bytes(encoder=urlsafe_b64encode)
# Write public keys to files # Write public keys to files
rootkey_pub_path = _save_public_keyfile( rootkey_pub_path = _save_public_keyfile(

View File

@ -100,7 +100,7 @@ def parse_alice_config(config=None, filepath=DEFAULT_CONFIG_FILE_LOCATION) -> di
character_payload = parse_character_config(config=config) character_payload = parse_character_config(config=config)
alice_payload = dict() # Alice specific alice_payload = dict() # type: dict # Alice specific
character_payload.update(alice_payload) character_payload.update(alice_payload)

View File

@ -28,10 +28,10 @@ class NoBlockchainPower(PowerUpError):
class CryptoPower(object): class CryptoPower(object):
def __init__(self, power_ups=None) -> None: def __init__(self, power_ups: dict = None) -> None:
self._power_ups = {} self._power_ups = {} # type: dict
# TODO: The keys here will actually be IDs for looking up in a KeyStore. # TODO: The keys here will actually be IDs for looking up in a KeyStore.
self.public_keys = {} self.public_keys = {} # type: dict
if power_ups is not None: if power_ups is not None:
for power_up in power_ups: for power_up in power_ups:

View File

@ -15,11 +15,16 @@ class SuspiciousActivity(RuntimeError):
class NucypherHashProtocol(KademliaProtocol): class NucypherHashProtocol(KademliaProtocol):
def __init__(self, sourceNode, storage, ksize, *args, **kwargs) -> None: def __init__(self,
sourceNode,
storage,
ksize,
*args, **kwargs) -> None:
super().__init__(sourceNode, storage, ksize, *args, **kwargs) super().__init__(sourceNode, storage, ksize, *args, **kwargs)
self.router = NucypherRoutingTable(self, ksize, sourceNode) self.router = NucypherRoutingTable(self, ksize, sourceNode)
self.illegal_keys_seen = [] # TODO: 340 self.illegal_keys_seen = [] # type: list # TODO: 340
@property @property
def ursulas(self): def ursulas(self):

View File

@ -2,7 +2,7 @@ import binascii
import uuid import uuid
from abc import abstractmethod from abc import abstractmethod
from collections import OrderedDict from collections import OrderedDict
from typing import Generator, List, Set from typing import Generator, List, Set, Iterable
import maya import maya
import msgpack import msgpack
@ -10,6 +10,7 @@ from bytestring_splitter import BytestringSplitter, VariableLengthBytestring
from constant_sorrow import constants from constant_sorrow import constants
from eth_utils import to_canonical_address, to_checksum_address from eth_utils import to_canonical_address, to_checksum_address
from umbral.config import default_params from umbral.config import default_params
from umbral.fragments import KFrag
from umbral.pre import Capsule from umbral.pre import Capsule
from nucypher.characters.lawful import Alice from nucypher.characters.lawful import Alice
@ -20,6 +21,7 @@ from nucypher.crypto.kits import UmbralMessageKit
from nucypher.crypto.powers import SigningPower, EncryptingPower from nucypher.crypto.powers import SigningPower, EncryptingPower
from nucypher.crypto.signing import Signature from nucypher.crypto.signing import Signature
from nucypher.crypto.splitters import key_splitter from nucypher.crypto.splitters import key_splitter
from nucypher.network.middleware import RestMiddleware
class Arrangement: class Arrangement:
@ -104,26 +106,32 @@ class Policy:
and generates a TreasureMap for the Policy, recording which Ursulas got a KFrag. and generates a TreasureMap for the Policy, recording which Ursulas got a KFrag.
""" """
def __init__(self, alice, label, bob=None, kfrags=(constants.UNKNOWN_KFRAG,), def __init__(self,
public_key=None, m=None, alices_signature=constants.NOT_SIGNED) -> None: alice,
label,
bob=None,
kfrags=(constants.UNKNOWN_KFRAG,),
public_key=None,
m: int = None,
alices_signature=constants.NOT_SIGNED) -> None:
""" """
:param kfrags: A list of KFrags to distribute per this Policy. :param kfrags: A list of KFrags to distribute per this Policy.
:param label: The identity of the resource to which Bob is granted access. :param label: The identity of the resource to which Bob is granted access.
""" """
self.alice = alice self.alice = alice # type: Alice
self.label = label self.label = label # type: bytes
self.bob = bob self.bob = bob # type: Bob
self.kfrags = kfrags self.kfrags = kfrags # type: List[KFrag]
self.public_key = public_key self.public_key = public_key
self.treasure_map = TreasureMap(m=m) self.treasure_map = TreasureMap(m=m)
# Keep track of this stuff # Keep track of this stuff
self._accepted_arrangements = set() self._accepted_arrangements = set() # type: set
self._rejected_arrangements = set() self._rejected_arrangements = set() # type: set
self._enacted_arrangements = OrderedDict() self._enacted_arrangements = OrderedDict() # type: OrderedDict
self._published_arrangements = OrderedDict() self._published_arrangements = OrderedDict() # type: OrderedDict
self.alices_signature = alices_signature self.alices_signature = alices_signature
@ -134,10 +142,10 @@ class Policy:
""" """
@property @property
def n(self): def n(self) -> int:
return len(self.kfrags) return len(self.kfrags)
def hrac(self): def hrac(self) -> bytes:
""" """
This function is hanging on for dear life. After 180 is closed, it can be completely deprecated. This function is hanging on for dear life. After 180 is closed, it can be completely deprecated.
@ -153,7 +161,7 @@ class Policy:
""" """
return keccak_digest(bytes(self.alice.stamp) + bytes(self.bob.stamp) + self.label) return keccak_digest(bytes(self.alice.stamp) + bytes(self.bob.stamp) + self.label)
def publish_treasure_map(self, network_middleare): def publish_treasure_map(self, network_middleware: RestMiddleware) -> dict:
self.treasure_map.prepare_for_publication(self.bob.public_keys(EncryptingPower), self.treasure_map.prepare_for_publication(self.bob.public_keys(EncryptingPower),
self.bob.public_keys(SigningPower), self.bob.public_keys(SigningPower),
self.alice.stamp, self.alice.stamp,
@ -163,14 +171,13 @@ class Policy:
# TODO: Optionally block. # TODO: Optionally block.
raise RuntimeError("Alice hasn't learned of any nodes. Thus, she can't push the TreasureMap.") raise RuntimeError("Alice hasn't learned of any nodes. Thus, she can't push the TreasureMap.")
responses = {} responses = dict()
for node in self.alice.known_nodes.values(): for node in self.alice.known_nodes.values():
# TODO: It's way overkill to push this to every node we know about. Come up with a system. 342 # TODO: It's way overkill to push this to every node we know about. Come up with a system. 342
response = network_middleare.put_treasure_map_on_node(node, response = network_middleware.put_treasure_map_on_node(node,
self.treasure_map.public_id(), self.treasure_map.public_id(),
bytes(self.treasure_map) bytes(self.treasure_map)
) ) # TODO: Certificate filepath needs to be looked up and passed here
if response.status_code == 202: if response.status_code == 202:
responses[node] = response responses[node] = response
# TODO: Handle response wherein node already had a copy of this TreasureMap. 341 # TODO: Handle response wherein node already had a copy of this TreasureMap. 341
@ -180,9 +187,9 @@ class Policy:
return responses return responses
def publish(self, network_middleware) -> None: def publish(self, network_middleware: RestMiddleware) -> dict:
"""Spread word of this Policy far and wide.""" """Spread word of this Policy far and wide."""
return self.publish_treasure_map(network_middleare=network_middleware) return self.publish_treasure_map(network_middleware=network_middleware)
def __assign_kfrags(self) -> Generator[Arrangement, None, None]: def __assign_kfrags(self) -> Generator[Arrangement, None, None]:
@ -203,8 +210,9 @@ class Policy:
# This is ideally an impossible situation, because we don't typically # This is ideally an impossible situation, because we don't typically
# enter this method unless we've already had n or more Arrangements accepted. # enter this method unless we've already had n or more Arrangements accepted.
raise self.MoreKFragsThanArrangements("Not enough accepted arrangements to assign all KFrags.") raise self.MoreKFragsThanArrangements("Not enough accepted arrangements to assign all KFrags.")
return
def enact(self, network_middleware, publish=True) -> None: def enact(self, network_middleware, publish=True) -> dict:
""" """
Assign kfrags to ursulas_on_network, and distribute them via REST, Assign kfrags to ursulas_on_network, and distribute them via REST,
populating enacted_arrangements populating enacted_arrangements
@ -247,17 +255,21 @@ class Policy:
return negotiation_result return negotiation_result
@abstractmethod @abstractmethod
def make_arrangements(self, network_middleware, def make_arrangements(self,
network_middleware: RestMiddleware,
deposit: int, deposit: int,
expiration: maya.MayaDT, expiration: maya.MayaDT,
ursulas: List[Ursula] = None) -> None: ursulas: Set[Ursula] = None) -> None:
""" """
Create and consider n Arangement objects. Create and consider n Arangement objects.
""" """
raise NotImplementedError raise NotImplementedError
def _consider_arrangements(self, network_middleware, candidate_ursulas: Set[Ursula], def _consider_arrangements(self,
deposit: int, expiration: maya.MayaDT) -> tuple: network_middleware: RestMiddleware,
candidate_ursulas: Set[Ursula],
deposit: int,
expiration: maya.MayaDT) -> tuple:
for selected_ursula in candidate_ursulas: for selected_ursula in candidate_ursulas:
arrangement = self._arrangement_class(alice=self.alice, arrangement = self._arrangement_class(alice=self.alice,
@ -278,22 +290,24 @@ class FederatedPolicy(Policy):
self.ursulas = ursulas self.ursulas = ursulas
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def make_arrangements(self, network_middleware, def make_arrangements(self,
network_middleware: RestMiddleware,
deposit: int, deposit: int,
expiration: maya.MayaDT, expiration: maya.MayaDT,
handpicked_ursulas: Set[Ursula] = None) -> None: handpicked_ursulas: Set[Ursula] = None) -> None:
if handpicked_ursulas is None: if handpicked_ursulas is None:
ursulas = set() ursulas = set() # type: set
else: else:
ursulas = handpicked_ursulas ursulas = handpicked_ursulas
ursulas.update(self.ursulas) ursulas.update(self.ursulas)
if len(ursulas) < self.n: if len(ursulas) < self.n:
raise ValueError( raise ValueError(
"To make a Policy in federated mode, you need to designate *all*\ "To make a Policy in federated mode, you need to designate *all* ' \
the Ursulas you need (in this case, {}); there's no other way to\ the Ursulas you need (in this case, {}); there's no other way to ' \
know which nodes to use. Either pass them here or when you make\ know which nodes to use. Either pass them here or when you make ' \
the Policy.".format(self.n)) the Policy.".format(self.n))
# TODO: One of these layers needs to add concurrency. # TODO: One of these layers needs to add concurrency.
@ -317,17 +331,17 @@ class TreasureMap:
"""Raised when the public signature (typically intended for Ursula) is not valid.""" """Raised when the public signature (typically intended for Ursula) is not valid."""
def __init__(self, def __init__(self,
m=None, m: int = None,
destinations=None, destinations=None,
message_kit=None, message_kit: UmbralMessageKit= None,
public_signature=None, public_signature: Signature = None,
hrac=None) -> None: hrac=None) -> None:
if m is not None: if m is not None:
if m > 255: if m > 255:
raise ValueError( raise ValueError("Largest allowed value for m is 255.")
"Largest allowed value for m is 255. Why the heck are you trying to make it larger than that anyway? That's too big.")
self.m = m self.m = m
self.destinations = destinations or {} self.destinations = destinations or {}
else: else:
self.m = constants.NO_DECRYPTION_PERFORMED self.m = constants.NO_DECRYPTION_PERFORMED
@ -339,7 +353,12 @@ class TreasureMap:
self._hrac = hrac self._hrac = hrac
self._payload = None self._payload = None
def prepare_for_publication(self, bob_encrypting_key, bob_verifying_key, alice_stamp, label): def prepare_for_publication(self,
bob_encrypting_key,
bob_verifying_key,
alice_stamp,
label):
plaintext = self.m.to_bytes(1, "big") + self.nodes_as_bytes() plaintext = self.m.to_bytes(1, "big") + self.nodes_as_bytes()
self.message_kit, _signature_for_bob = encrypt_and_sign(bob_encrypting_key, self.message_kit, _signature_for_bob = encrypt_and_sign(bob_encrypting_key,
@ -502,7 +521,7 @@ class WorkOrder(object):
class WorkOrderHistory: class WorkOrderHistory:
def __init__(self) -> None: def __init__(self) -> None:
self.by_ursula = {} self.by_ursula = {} # type: dict
def __contains__(self, item): def __contains__(self, item):
assert False assert False
@ -521,7 +540,7 @@ class WorkOrderHistory:
return self.by_ursula.keys() return self.by_ursula.keys()
def by_capsule(self, capsule): def by_capsule(self, capsule):
ursulas_by_capsules = {} ursulas_by_capsules = {} # type: dict
for ursula, capsules in self.by_ursula.items(): for ursula, capsules in self.by_ursula.items():
for saved_capsule, work_order in capsules.items(): for saved_capsule, work_order in capsules.items():
if saved_capsule == capsule: if saved_capsule == capsule:

View File

@ -1,9 +1,10 @@
from collections import OrderedDict from collections import OrderedDict
from typing import List from typing import List, Set
import maya import maya
from nucypher.characters.lawful import Ursula from nucypher.characters.lawful import Ursula
from nucypher.network.middleware import RestMiddleware
from nucypher.policy.models import Arrangement, Policy from nucypher.policy.models import Arrangement, Policy
@ -18,20 +19,24 @@ class MockArrangement(Arrangement):
class MockPolicy(Policy): class MockPolicy(Policy):
def make_arrangements(self, network_middleware, def make_arrangements(self,
network_middleware: RestMiddleware,
deposit: int, deposit: int,
expiration: maya.MayaDT, expiration: maya.MayaDT,
ursulas: List[Ursula] = None) -> None: ursulas: Set[Ursula] = None
) -> None:
""" """
Create and consider n Arangement objects from all known nodes. Create and consider n Arangement objects from all known nodes.
""" """
for ursula in self.alice._known_nodes: for ursula in self.alice.known_nodes:
arrangement = MockArrangement(alice=self.alice, ursula=ursula, arrangement = MockArrangement(alice=self.alice, ursula=ursula,
hrac=self.hrac(), hrac=self.hrac(),
expiration=expiration) expiration=expiration)
self.consider_arrangement(network_middleware=network_middleware, arrangement=arrangement) self.consider_arrangement(network_middleware=network_middleware,
ursula=ursula,
arrangement=arrangement)
class MockPolicyCreation: class MockPolicyCreation:

View File

@ -50,7 +50,7 @@ def test_alice_sets_treasure_map(enacted_federated_policy, federated_ursulas):
Having enacted all the policies of a PolicyGroup, Alice creates a TreasureMap and ...... TODO Having enacted all the policies of a PolicyGroup, Alice creates a TreasureMap and ...... TODO
""" """
enacted_federated_policy.publish_treasure_map(network_middleare=MockRestMiddleware()) enacted_federated_policy.publish_treasure_map(network_middleware=MockRestMiddleware())
treasure_map_as_set_on_network = list(federated_ursulas)[0].treasure_maps[ treasure_map_as_set_on_network = list(federated_ursulas)[0].treasure_maps[
digest(enacted_federated_policy.treasure_map.public_id())] digest(enacted_federated_policy.treasure_map.public_id())]