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

@ -5,4 +5,8 @@ verbosity=0
disallow_untyped_defs=False
check_untyped_defs=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 datetime import datetime
from typing import Tuple
from typing import Tuple, List
import maya
@ -37,18 +37,18 @@ class NucypherTokenActor:
"""
try:
parent_address = self.checksum_public_address
parent_address = self.checksum_public_address # type: str
if checksum_address is not None:
if parent_address != checksum_address:
raise ValueError("Can't have two different addresses.")
except AttributeError:
self.checksum_public_address = checksum_address
self.checksum_public_address = checksum_address # type: str
if registry_filepath is not None:
EthereumContractRegistry(registry_filepath=registry_filepath)
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):
class_name = self.__class__.__name__
@ -206,7 +206,7 @@ class Miner(NucypherTokenActor):
if entire_balance is True:
amount = self.token_balance
staking_transactions = OrderedDict() # Time series of txhases
staking_transactions = OrderedDict() # type: OrderedDict # Time series of txhases
# Validate
assert self.__validate_stake(amount=amount, lock_periods=lock_periods)
@ -281,7 +281,7 @@ class PolicyAuthor(NucypherTokenActor):
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
caches the resulting node ethereum addresses.

View File

@ -1,12 +1,12 @@
import random
from abc import ABC
from typing import Generator, List, Tuple, Union
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.chains import Blockchain
from nucypher.blockchain.eth.interfaces import EthereumContractRegistry
class EthereumContractAgent(ABC):
@ -31,20 +31,21 @@ class EthereumContractAgent(ABC):
def __init__(self,
blockchain: Blockchain = None,
registry_filepath: str = None,
*args, **kwargs) -> None:
contract: Contract = None
) -> None:
if blockchain is None:
blockchain = Blockchain.connect()
self.blockchain = blockchain
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)
# Fetch the contract by reading address and abi from the registry and blockchain
contract = self.blockchain.interface.get_contract_by_name(name=self.principal_contract_name,
upgradeable=self._upgradeable)
if contract is None:
# Fetch the contract by reading address and abi from the registry and blockchain
contract = self.blockchain.interface.get_contract_by_name(name=self.principal_contract_name,
upgradeable=self._upgradeable)
self.__contract = contract
super().__init__()
@ -98,14 +99,23 @@ class MinerAgent(EthereumContractAgent):
principal_contract_name = "MinersEscrow"
_upgradeable = True
__instance = None
__instance = None # TODO: constants.NO_CONTRACT_AVAILABLE
class NotEnoughMiners(Exception):
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)
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
#

View File

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

View File

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

View File

@ -1,5 +1,5 @@
from collections import deque
from typing import List
from typing import List, Tuple, Iterable
from typing import Set
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 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.characters.lawful import Ursula
from nucypher.network.middleware import RestMiddleware
@ -28,29 +28,30 @@ class BlockchainArrangement(Arrangement):
lock_periods: int,
expiration: maya.MayaDT,
*args, **kwargs) -> None:
super().__init__(alice=author, ursula=miner, *args, **kwargs)
delta = expiration - maya.now()
hours = (delta.total_seconds() / 60) / 60
periods = int(math.ceil(hours / int(constants.HOURS_PER_PERIOD)))
hours = (delta.total_seconds() / 60) / 60 # type: int
periods = int(math.ceil(hours / int(constants.HOURS_PER_PERIOD))) # type: int
# The relationship exists between two addresses
self.author = author
self.policy_agent = author.policy_agent
self.author = author # type: PolicyAuthor
self.policy_agent = author.policy_agent # type: PolicyAgent
self.miner = miner
self.miner = miner # type: Miner
# Arrangement value, rate, and duration
rate = value // lock_periods
self._rate = rate
rate = value // lock_periods # type: int
self._rate = rate # type: int
self.value = value
self.lock_periods = lock_periods # TODO: <datetime> -> lock_periods
self.value = value # type: int
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.is_revoked = False
self.is_revoked = False # type: bool
self.revoke_transaction = None
def __repr__(self):
@ -60,9 +61,11 @@ class BlockchainArrangement(Arrangement):
return r
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.policy_agent.blockchain.wait_for_receipt(txhash)
@ -91,7 +94,11 @@ class BlockchainPolicy(Policy):
class NotEnoughBlockchainUrsulas(Exception):
pass
def __init__(self, author: PolicyAuthor, *args, **kwargs) -> None:
def __init__(self,
author: PolicyAuthor,
*args, **kwargs
) -> None:
self.author = author
super().__init__(alice=author, *args, **kwargs)
@ -104,14 +111,18 @@ class BlockchainPolicy(Policy):
duration = end_block - start_block
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
return arrangement
def __find_ursulas(self, ether_addresses: List[str], target_quantity: int, timeout: int = 120):
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:
# Check for a timeout
@ -146,24 +157,27 @@ class BlockchainPolicy(Policy):
return found_ursulas
def make_arrangements(self, network_middleware: RestMiddleware,
deposit: int, expiration: maya.MayaDT,
handpicked_ursulas: Set[Ursula] = set()) -> None:
def make_arrangements(self,
network_middleware: RestMiddleware,
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.
"""
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)
selected_addresses = set()
try: # Sample by reading from the Blockchain
selected_addresses = set() # type: set
try: # Sample by reading from the Blockchain
actual_sample_quantity = math.ceil(target_sample_quantity * ADDITIONAL_URSULAS)
duration = int(calculate_period_duration(expiration))
sampled_addresses = self.alice.recruit(quantity=actual_sample_quantity,
duration=duration,
)
duration=duration)
except MinerAgent.NotEnoughMiners:
error = "Cannot create policy with {} arrangements."
raise self.NotEnoughBlockchainUrsulas(error.format(self.n))

View File

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

View File

@ -8,19 +8,27 @@ from solc.exceptions import SolcError
class SolidityCompiler:
# TODO: Integrate with config classes
__default_version = 'v0.4.24'
__default_configuration_path = os.path.join(dirname(abspath(__file__)), './compiler.json')
__default_sol_binary_path = shutil.which('solc')
if __default_sol_binary_path is None:
__bin_path = os.path.dirname(shutil.which('python'))
__default_sol_binary_path = os.path.join(__bin_path, 'solc')
__bin_path = os.path.dirname(shutil.which('python')) # type: str
__default_sol_binary_path = os.path.join(__bin_path, 'solc') # type: str
__default_contract_dir = os.path.join(dirname(abspath(__file__)), 'source', 'contracts')
__default_chain_name = 'tester'
def __init__(self, solc_binary_path=None, configuration_path=None,
chain_name=None, contract_dir=None, test_contract_dir=None) -> None:
def __init__(self,
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
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
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.
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 umbral.keys import UmbralPrivateKey
from nucypher.config.constants import DEFAULT_KEYRING_ROOT
from nucypher.config.utils import validate_passphrase, NucypherConfigurationError
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.config.node import NodeConfiguration
from nucypher.config.utils import validate_passphrase
from nucypher.crypto.powers import SigningPower, EncryptingPower, CryptoPower
@ -25,7 +26,7 @@ def _parse_keyfile(keypath: str):
try:
key_metadata = json.loads(keyfile)
except json.JSONDecodeError:
raise NucypherConfigurationError("Invalid data in keyfile {}".format(keypath))
raise NodeConfiguration.ConfigurationError("Invalid data in keyfile {}".format(keypath))
else:
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
try:
keyfile_descriptor = os.open(path=keypath, flags=flags, mode=mode)
keyfile_descriptor = os.open(file=keypath, flags=flags, mode=mode)
finally:
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
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:
keyfile_descriptor = os.open(path=keypath, flags=flags, mode=mode)
keyfile_descriptor = os.open(file=keypath, flags=flags, mode=mode)
finally:
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
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).
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_private_key_dir = os.path.join(__default_keyring_root, 'private')
@ -228,7 +233,8 @@ class NucypherKeyring:
class KeyringLocked(KeyringError):
pass
def __init__(self, root_key_path: str=None,
def __init__(self,
root_key_path: str=None,
pub_root_key_path: str=None,
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:
raise Exception('Keyring already unlocked')
# TODO: missing salt parameter below
derived_key = _derive_key_material_from_passphrase(passphrase=passphrase)
self.__derived_key_material = derived_key
@ -313,7 +320,12 @@ class NucypherKeyring:
return new_cryptopower
@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,
respectively saving keyfiles on the local filesystem from *default* paths,
@ -337,10 +349,10 @@ class NucypherKeyring:
os.mkdir(_private_key_dir, mode=0o700) # private
# Generate keys
keyring_args = dict()
keyring_args = dict() # type: dict
if encryption is True:
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)
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)
sig_wrap_key = _derive_wrapping_key_from_key_material(sig_salt, der_key_material)
enc_json = _encrypt_umbral_key(der_wrap_key, enc_key)
sig_json = _encrypt_umbral_key(der_wrap_key, sig_key)
enc_json = _encrypt_umbral_key(der_key_material, enc_wrap_key)
sig_json = _encrypt_umbral_key(der_key_material, sig_wrap_key)
enc_json['master_salt'] = urlsafe_b64encode(salt).decode()
sig_json['master_salt'] = urlsafe_b64encode(salt).decode()
enc_json['master_salt'] = urlsafe_b64encode(enc_salt).decode()
sig_json['master_salt'] = urlsafe_b64encode(sig_salt).decode()
enc_json['wrap_salt'] = urlsafe_b64encode(salt).decode()
sig_json['wrap_salt'] = urlsafe_b64encode(salt).decode()
enc_json['wrap_salt'] = urlsafe_b64encode(enc_salt).decode()
sig_json['wrap_salt'] = urlsafe_b64encode(sig_salt).decode()
# Write private keys to files
rootkey_path = _save_private_keyfile(cls.__default_key_filepaths['root'], enc_json)
sigkey_path = _save_private_keyfile(cls.__default_key_filepaths['signing'], sig_json)
bytes_enc_pubkey = enc_pubkey.to_bytes(encoder=urlsafe_b64encoder)
bytes_sig_pubkey = sig_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_b64encode)
# Write public keys to files
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)
alice_payload = dict() # Alice specific
alice_payload = dict() # type: dict # Alice specific
character_payload.update(alice_payload)

View File

@ -28,10 +28,10 @@ class NoBlockchainPower(PowerUpError):
class CryptoPower(object):
def __init__(self, power_ups=None) -> None:
self._power_ups = {}
def __init__(self, power_ups: dict = None) -> None:
self._power_ups = {} # type: dict
# 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:
for power_up in power_ups:

View File

@ -15,11 +15,16 @@ class SuspiciousActivity(RuntimeError):
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)
self.router = NucypherRoutingTable(self, ksize, sourceNode)
self.illegal_keys_seen = [] # TODO: 340
self.illegal_keys_seen = [] # type: list # TODO: 340
@property
def ursulas(self):

View File

@ -2,7 +2,7 @@ import binascii
import uuid
from abc import abstractmethod
from collections import OrderedDict
from typing import Generator, List, Set
from typing import Generator, List, Set, Iterable
import maya
import msgpack
@ -10,6 +10,7 @@ from bytestring_splitter import BytestringSplitter, VariableLengthBytestring
from constant_sorrow import constants
from eth_utils import to_canonical_address, to_checksum_address
from umbral.config import default_params
from umbral.fragments import KFrag
from umbral.pre import Capsule
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.signing import Signature
from nucypher.crypto.splitters import key_splitter
from nucypher.network.middleware import RestMiddleware
class Arrangement:
@ -104,26 +106,32 @@ class Policy:
and generates a TreasureMap for the Policy, recording which Ursulas got a KFrag.
"""
def __init__(self, alice, label, bob=None, kfrags=(constants.UNKNOWN_KFRAG,),
public_key=None, m=None, alices_signature=constants.NOT_SIGNED) -> None:
def __init__(self,
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 label: The identity of the resource to which Bob is granted access.
"""
self.alice = alice
self.label = label
self.bob = bob
self.kfrags = kfrags
self.alice = alice # type: Alice
self.label = label # type: bytes
self.bob = bob # type: Bob
self.kfrags = kfrags # type: List[KFrag]
self.public_key = public_key
self.treasure_map = TreasureMap(m=m)
# Keep track of this stuff
self._accepted_arrangements = set()
self._rejected_arrangements = set()
self._accepted_arrangements = set() # type: set
self._rejected_arrangements = set() # type: set
self._enacted_arrangements = OrderedDict()
self._published_arrangements = OrderedDict()
self._enacted_arrangements = OrderedDict() # type: OrderedDict
self._published_arrangements = OrderedDict() # type: OrderedDict
self.alices_signature = alices_signature
@ -134,10 +142,10 @@ class Policy:
"""
@property
def n(self):
def n(self) -> int:
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.
@ -153,7 +161,7 @@ class Policy:
"""
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.bob.public_keys(SigningPower),
self.alice.stamp,
@ -163,14 +171,13 @@ class Policy:
# TODO: Optionally block.
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():
# 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,
self.treasure_map.public_id(),
bytes(self.treasure_map)
)
response = network_middleware.put_treasure_map_on_node(node,
self.treasure_map.public_id(),
bytes(self.treasure_map)
) # TODO: Certificate filepath needs to be looked up and passed here
if response.status_code == 202:
responses[node] = response
# TODO: Handle response wherein node already had a copy of this TreasureMap. 341
@ -180,9 +187,9 @@ class Policy:
return responses
def publish(self, network_middleware) -> None:
def publish(self, network_middleware: RestMiddleware) -> dict:
"""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]:
@ -203,8 +210,9 @@ class Policy:
# This is ideally an impossible situation, because we don't typically
# enter this method unless we've already had n or more Arrangements accepted.
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,
populating enacted_arrangements
@ -247,17 +255,21 @@ class Policy:
return negotiation_result
@abstractmethod
def make_arrangements(self, network_middleware,
def make_arrangements(self,
network_middleware: RestMiddleware,
deposit: int,
expiration: maya.MayaDT,
ursulas: List[Ursula] = None) -> None:
ursulas: Set[Ursula] = None) -> None:
"""
Create and consider n Arangement objects.
"""
raise NotImplementedError
def _consider_arrangements(self, network_middleware, candidate_ursulas: Set[Ursula],
deposit: int, expiration: maya.MayaDT) -> tuple:
def _consider_arrangements(self,
network_middleware: RestMiddleware,
candidate_ursulas: Set[Ursula],
deposit: int,
expiration: maya.MayaDT) -> tuple:
for selected_ursula in candidate_ursulas:
arrangement = self._arrangement_class(alice=self.alice,
@ -278,22 +290,24 @@ class FederatedPolicy(Policy):
self.ursulas = ursulas
super().__init__(*args, **kwargs)
def make_arrangements(self, network_middleware,
def make_arrangements(self,
network_middleware: RestMiddleware,
deposit: int,
expiration: maya.MayaDT,
handpicked_ursulas: Set[Ursula] = None) -> None:
if handpicked_ursulas is None:
ursulas = set()
ursulas = set() # type: set
else:
ursulas = handpicked_ursulas
ursulas.update(self.ursulas)
if len(ursulas) < self.n:
raise ValueError(
"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\
know which nodes to use. Either pass them here or when you make\
the Policy.".format(self.n))
"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 ' \
know which nodes to use. Either pass them here or when you make ' \
the Policy.".format(self.n))
# 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."""
def __init__(self,
m=None,
m: int = None,
destinations=None,
message_kit=None,
public_signature=None,
message_kit: UmbralMessageKit= None,
public_signature: Signature = None,
hrac=None) -> None:
if m is not None:
if m > 255:
raise ValueError(
"Largest allowed value for m is 255. Why the heck are you trying to make it larger than that anyway? That's too big.")
raise ValueError("Largest allowed value for m is 255.")
self.m = m
self.destinations = destinations or {}
else:
self.m = constants.NO_DECRYPTION_PERFORMED
@ -339,7 +353,12 @@ class TreasureMap:
self._hrac = hrac
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()
self.message_kit, _signature_for_bob = encrypt_and_sign(bob_encrypting_key,
@ -502,7 +521,7 @@ class WorkOrder(object):
class WorkOrderHistory:
def __init__(self) -> None:
self.by_ursula = {}
self.by_ursula = {} # type: dict
def __contains__(self, item):
assert False
@ -521,7 +540,7 @@ class WorkOrderHistory:
return self.by_ursula.keys()
def by_capsule(self, capsule):
ursulas_by_capsules = {}
ursulas_by_capsules = {} # type: dict
for ursula, capsules in self.by_ursula.items():
for saved_capsule, work_order in capsules.items():
if saved_capsule == capsule:

View File

@ -1,9 +1,10 @@
from collections import OrderedDict
from typing import List
from typing import List, Set
import maya
from nucypher.characters.lawful import Ursula
from nucypher.network.middleware import RestMiddleware
from nucypher.policy.models import Arrangement, Policy
@ -18,20 +19,24 @@ class MockArrangement(Arrangement):
class MockPolicy(Policy):
def make_arrangements(self, network_middleware,
def make_arrangements(self,
network_middleware: RestMiddleware,
deposit: int,
expiration: maya.MayaDT,
ursulas: List[Ursula] = None) -> None:
ursulas: Set[Ursula] = None
) -> None:
"""
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,
hrac=self.hrac(),
expiration=expiration)
self.consider_arrangement(network_middleware=network_middleware, arrangement=arrangement)
self.consider_arrangement(network_middleware=network_middleware,
ursula=ursula,
arrangement=arrangement)
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
"""
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[
digest(enacted_federated_policy.treasure_map.public_id())]