mirror of https://github.com/nucypher/nucypher.git
commit
53e1624ced
6
mypy.ini
6
mypy.ini
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
#
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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())]
|
||||
|
|
Loading…
Reference in New Issue