Renames(mostly) NetworkyStuff to NetworkMiddleware.

pull/330/head
Kieran Prasch 2018-06-01 13:34:42 -07:00
parent 2e141507f2
commit 6e751f6775
13 changed files with 113 additions and 101 deletions

View File

@ -9,11 +9,11 @@ import sys
from examples.sandbox_resources import SandboxNetworkyStuff
from nucypher.characters import Alice, Bob, Ursula
from nucypher.data_sources import DataSource
from nucypher.network.node import NetworkyStuff
from nucypher.network.middleware import NetworkMiddleware
import maya
# This is already running in another process.
URSULA = Ursula.from_rest_url(NetworkyStuff(), address="localhost", port=3601)
URSULA = Ursula.from_rest_url(NetworkMiddleware(), ip_address="localhost", port=3601)
network_middleware = SandboxNetworkyStuff([URSULA])

View File

@ -1,10 +1,10 @@
import requests
from nucypher.characters import Ursula
from nucypher.network.node import NetworkyStuff
from nucypher.network.middleware import NetworkMiddleware
from nucypher.crypto.powers import SigningPower, EncryptingPower
class SandboxNetworkyStuff(NetworkyStuff):
class SandboxNetworkyStuff(NetworkMiddleware):
def __init__(self, ursulas):
self.ursulas = ursulas

View File

@ -50,6 +50,7 @@ class Miner(NucypherTokenActor):
def __init__(self, miner_agent: MinerAgent=None, *args, **kwargs):
if miner_agent is None:
miner_agent = MinerAgent(token_agent=NucypherTokenAgent())
super().__init__(token_agent=miner_agent.token_agent, *args, **kwargs)
self.miner_agent = miner_agent
@ -206,7 +207,6 @@ class Miner(NucypherTokenActor):
"""Retrieve all asosciated contract data for this miner."""
count_bytes = self.miner_agent.contract.functions.getMinerIdsLength(self.address).call()
count = self.blockchain.interface.w3.toInt(count_bytes)
miner_ids = list()
@ -232,19 +232,18 @@ class PolicyAuthor(NucypherTokenActor):
self._arrangements = OrderedDict() # Track authored policies by id
def revoke_arrangement(self, arrangement_id):
def revoke_arrangement(self, arrangement_id) -> str:
"""Get the arrangement from the cache and revoke it on the blockchain"""
try:
arrangement = self._arrangements[arrangement_id]
except KeyError:
raise self.ActorError('No such arrangement')
raise self.ActorError('Not tracking arrangement {}'.format(arrangement_id))
else:
txhash = arrangement.revoke()
return txhash
def recruit(self, quantity: int) -> List[str]:
def recruit(self, quantity: int, **options) -> List[str]:
"""Uses sampling logic to gather miner address from the blockchain"""
miner_addresses = self.policy_agent.miner_agent.sample(quantity=quantity)
miner_addresses = self.policy_agent.miner_agent.sample(quantity=quantity, **options)
return miner_addresses

View File

@ -80,7 +80,7 @@ class MinerAgent(EthereumContractAgent, NucypherMinerConstants):
_principal_contract_name = "MinersEscrow"
class NotEnoughUrsulas(Exception):
class NotEnoughMiners(Exception):
pass
class MinerInfo(Enum):
@ -112,7 +112,7 @@ class MinerAgent(EthereumContractAgent, NucypherMinerConstants):
count = self.contract.functions.getMinersLength().call()
for index in range(count):
addr = self.contract.functions.miners(index).call()
yield self.blockchain.interface.w3.toChecksumAddress(addr)
yield self.blockchain.interface.w3.toChecksumAddress(addr) # string address of next node
def sample(self, quantity: int=10, additional_ursulas: float=1.7, attempts: int=5, duration: int=10) -> List[str]:
"""
@ -142,7 +142,7 @@ class MinerAgent(EthereumContractAgent, NucypherMinerConstants):
n_tokens = self.contract.functions.getAllLockedTokens().call()
if not n_tokens > 0:
raise self.NotEnoughUrsulas('There are no locked tokens.')
raise self.NotEnoughMiners('There are no locked tokens.')
for _ in range(attempts):
points = [0] + sorted(system_random.randrange(n_tokens) for _ in range(n_select))
@ -156,7 +156,7 @@ class MinerAgent(EthereumContractAgent, NucypherMinerConstants):
if len(addrs) >= quantity:
return system_random.sample(addrs, quantity)
raise self.NotEnoughUrsulas('Selection failed after {} attempts'.format(attempts))
raise self.NotEnoughMiners('Selection failed after {} attempts'.format(attempts))
class PolicyAgent(EthereumContractAgent):

View File

@ -1,5 +1,7 @@
from typing import List, Union
from constant_sorrow.constants import NO_BLOCKCHAIN_AVAILIBLE
from nucypher.blockchain.eth.constants import NucypherMinerConstants
from nucypher.blockchain.eth.interfaces import ContractInterface, DeployerInterface
@ -7,7 +9,7 @@ from nucypher.blockchain.eth.interfaces import ContractInterface, DeployerInterf
class Blockchain:
"""A view of a blockchain through a provided interface"""
_instance = None
_instance = NO_BLOCKCHAIN_AVAILIBLE
__default_interface_class = ContractInterface
test_chains = ('tester', )
@ -16,7 +18,7 @@ class Blockchain:
def __init__(self, interface: Union[ContractInterface, DeployerInterface]=None):
if interface is None:
if interface is NO_BLOCKCHAIN_AVAILIBLE:
interface = self.__default_interface_class(blockchain_config=interface.config)
self.__interface = interface

View File

@ -1,4 +1,4 @@
from nucypher.blockchain.eth.actors import Miner
from nucypher.blockchain.eth.actors import Miner, PolicyAuthor
class BlockchainArrangement:
@ -6,7 +6,7 @@ class BlockchainArrangement:
A relationship between Alice and a single Ursula as part of Blockchain Policy
"""
def __init__(self, author: str, miner: str, value: int, lock_periods: int, arrangement_id: bytes=None):
def __init__(self, author, miner, value: int, lock_periods: int, arrangement_id: bytes=None):
self.id = arrangement_id
@ -24,6 +24,10 @@ class BlockchainArrangement:
self.lock_periods = lock_periods # TODO: <datetime> -> lock_periods
self.is_published = False
self.publish_transaction = None
self.is_revoked = False
self.revoke_transaction = None
def __repr__(self):
class_name = self.__class__.__name__
@ -31,18 +35,12 @@ class BlockchainArrangement:
r = r.format(class_name, self.author, self.miner)
return r
def publish(self, gas_price: int) -> str:
def publish(self) -> str:
payload = {'from': self.author.address,
'value': self.value,
'gas_price': gas_price}
payload = {'from': self.author.address, 'value': self.value}
txhash = self.policy_agent.transact(payload).createPolicy(self.id,
self.miner.address,
self.lock_periods)
self.policy_agent._blockchain._chain.wait.for_receipt(txhash)
txhash = self.policy_agent.contract.functions.createPolicy(self.id, self.miner.address, self.lock_periods).transact(payload)
self.policy_agent.blockchain.wait.for_receipt(txhash)
self.publish_transaction = txhash
self.is_published = True
@ -50,44 +48,34 @@ class BlockchainArrangement:
def revoke(self, gas_price: int) -> str:
"""Revoke this arrangement and return the transaction hash as hex."""
txhash = self.policy_agent.revoke_arrangement(self.id, author=self.author, gas_price=gas_price)
self.revoke_transaction = txhash
self.is_revoked = True
return txhash
class BlockchainPolicy:
"""TODO: A collection of n BlockchainArrangements representing a single Policy"""
"""
A collection of n BlockchainArrangements representing a single Policy
"""
class NoSuchPolicy(Exception):
pass
def __init__(self):
self._arrangements = list()
def publish_arrangement(self, miner, lock_periods: int, rate: int, arrangement_id: bytes=None) -> 'BlockchainArrangement':
"""
Create a new arrangement to carry out a blockchain policy for the specified rate and time.
"""
value = rate * lock_periods
arrangement = BlockchainArrangement(author=self,
miner=miner,
value=value,
lock_periods=lock_periods)
self._arrangements[arrangement.id] = {arrangement_id: arrangement}
return arrangement
def __init__(self, author: PolicyAuthor):
self.author = author
def get_arrangement(self, arrangement_id: bytes) -> BlockchainArrangement:
"""Fetch a published arrangement from the blockchain"""
"""Fetch published arrangements from the blockchain"""
blockchain_record = self.policy_agent.read().policies(arrangement_id)
blockchain_record = self.author.policy_agent.read().policies(arrangement_id)
author_address, miner_address, rate, start_block, end_block, downtime_index = blockchain_record
duration = end_block - start_block
miner = Miner(address=miner_address, miner_agent=self.policy_agent.miner_agent)
arrangement = BlockchainArrangement(author=self, miner=miner, lock_periods=duration)
miner = Miner(address=miner_address, miner_agent=self.author.policy_agent.miner_agent)
arrangement = BlockchainArrangement(author=self.author, miner=miner, lock_periods=duration)
arrangement.is_published = True
return arrangement

View File

@ -1,19 +1,18 @@
import asyncio
from collections import OrderedDict
from contextlib import suppress
from logging import getLogger
import msgpack
from collections import OrderedDict
from kademlia.network import Server
from kademlia.utils import digest
from typing import Dict, ClassVar
from typing import Union, List
import msgpack
from bytestring_splitter import BytestringSplitter
from nucypher.blockchain.eth.agents import PolicyAgent
from nucypher.network.node import NetworkyStuff
from umbral.keys import UmbralPublicKey
from constant_sorrow import constants, default_constant_splitter
from kademlia.network import Server
from kademlia.utils import digest
from nucypher.crypto.signature import Signature, signature_splitter, StrangerStamp
from nucypher.network.middleware import NetworkMiddleware
from umbral.keys import UmbralPublicKey
from nucypher.blockchain.eth.actors import PolicyAuthor, Miner
from nucypher.config.configs import NucypherConfig
@ -21,7 +20,7 @@ from nucypher.crypto.api import secure_random, keccak_digest, encrypt_and_sign
from nucypher.crypto.constants import PUBLIC_KEY_LENGTH
from nucypher.crypto.kits import UmbralMessageKit
from nucypher.crypto.powers import CryptoPower, SigningPower, EncryptingPower, DelegatingPower, NoSigningPower
from nucypher.crypto.signing import Signature, signature_splitter, SignatureStamp, StrangerStamp
from nucypher.crypto.signing import signature_splitter, StrangerStamp
from nucypher.network import blockchain_client
from nucypher.network.protocols import dht_value_splitter, dht_with_hrac_splitter
from nucypher.network.server import NucypherDHTServer, NucypherSeedOnlyDHTServer, ProxyRESTServer
@ -79,8 +78,12 @@ class Character(object):
self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
else:
self._crypto_power = CryptoPower(self._default_crypto_powerups)
#
# Identity and Network
#
if is_me:
self.network_middleware = network_middleware or NetworkyStuff()
self.network_middleware = network_middleware or NetworkMiddleware()
try:
signing_power = self._crypto_power.power_ups(SigningPower)
self._stamp = signing_power.get_signature_stamp()
@ -138,6 +141,7 @@ class Character(object):
storage=None, *args, **kwargs) -> None:
if self._server:
raise RuntimeError("Attaching the server twice is almost certainly a bad idea.")
self._server = self._server_class(
ksize, alpha, id, storage, *args, **kwargs)
@ -381,13 +385,14 @@ class Alice(Character, PolicyAuthor):
policy = self.create_policy(bob, uri, m, n)
# We'll find n Ursulas by default. It's possible to "play the field"
# by trying differet
# deposits and expirations on a limited number of Ursulas.
#
# We'll find n Ursulas by default. It's possible to "play the field" by trying different
# deposit and expiration combinations on a limited number of Ursulas;
# Users may decide to inject some market strategies here.
found_ursulas = policy.find_ursulas(self.network_middleware, deposit,
expiration, num_ursulas=n)
#
found_ursulas = policy.find_ursulas(self.network_middleware, deposit, expiration, num_ursulas=n)
policy.match_kfrags_to_found_ursulas(found_ursulas)
# REST call happens here, as does population of TreasureMap.
policy.enact(self.network_middleware)
policy.publish_treasure_map(self.network_middleware)
@ -501,7 +506,6 @@ class Bob(Character):
Return the first one who has it.
TODO: What if a node gives a bunk TreasureMap?
"""
from nucypher.network.protocols import dht_value_splitter
for node in self.known_nodes.values():
response = networky_stuff.get_treasure_map_from_node(node, map_id)
@ -620,13 +624,17 @@ class Ursula(Character, ProxyRESTServer, Miner):
return self._rest_app
@classmethod
def as_discovered_on_network(cls, dht_port=None, ip_address=None,
rest_port=None, powers_and_keys=()):
def as_discovered_on_network(cls, dht_port=None, ip_address=None, rest_port=None, powers_and_keys=None):
if powers_and_keys is None: # behold the beauty
powers_and_keys = tuple()
# TODO: We also need the encrypting public key here.
ursula = cls.from_public_keys(powers_and_keys)
ursula.dht_port = dht_port
ursula.ip_address = ip_address
ursula.rest_port = rest_port
return ursula
@classmethod

View File

@ -10,7 +10,7 @@ from constant_sorrow import constants
from umbral.config import default_params
from umbral.pre import Capsule
from nucypher.blockchain.eth.policies import BlockchainArrangement
from nucypher.blockchain.eth.policies import BlockchainArrangement, BlockchainPolicy
from nucypher.characters import Alice
from nucypher.characters import Bob, Ursula
from nucypher.crypto.api import keccak_digest
@ -80,9 +80,8 @@ class Arrangement(BlockchainArrangement):
self.negotiation_result = negotiation_result
# Publish arrangement to blockchain
# TODO Determine actual gas price here
super().publish() # TODO Determine actual gas price here
# TODO Negotiate the receipt of a KFrag per Ursula
# super().publish(gas_price=0)
def encrypt_payload_for_ursula(self):
"""
@ -100,7 +99,7 @@ class ArrangementResponse(object):
pass
class Policy(object):
class Policy(BlockchainPolicy):
"""
An edict by Alice, arranged with n Ursulas, to perform re-encryption for a specific Bob
for a specific path.
@ -130,6 +129,9 @@ class Policy(object):
self._accepted_arrangements = OrderedDict()
self.alices_signature = alices_signature
self.arrangements = list()
super().__init__(author=self.alice)
class MoreArrangementsThanKFrags(TypeError):
"""
@ -232,23 +234,31 @@ class Policy(object):
self.treasure_map.add_ursula(arrangement.ursula)
def make_arrangement(self, deposit, expiration):
return Arrangement(self.alice, self.hrac(), expiration=expiration, deposit=deposit)
def find_ursulas(self, networky_stuff, deposit, expiration, num_ursulas=None):
"""
:param networky_stuff: A compliant interface (maybe a Client instance) to be used to engage the DHT swarm.
"""
if num_ursulas is None:
num_ursulas = self.n
arrangement = Arrangement(self.alice, self.hrac(), expiration=expiration, deposit=deposit)
# self.arrangements[arrangement.id] = {arrangement.id: arrangement}
return arrangement
def find_ursulas(self, network_middleware, deposit, expiration, num_ursulas=None):
"""
:param network_middleware: A compliant interface (maybe a Client instance) to be used to engage the DHT swarm.
"""
num_ursulas = num_ursulas or self.n
found_ursulas = self.alice.recruit(quantity=num_ursulas)
for address in found_ursulas:
pass # TODO
found_ursulas = []
while len(found_ursulas) < num_ursulas:
arrangement = self.make_arrangement(deposit, expiration)
try:
ursula, result = networky_stuff.find_ursula(arrangement)
ursula, result = network_middleware.find_ursula(arrangement)
found_ursulas.append((ursula, arrangement, result))
except networky_stuff.NotEnoughQualifiedUrsulas:
except network_middleware.NotEnoughQualifiedUrsulas:
pass # TODO: Tell Alice to either wait or lower the value of num_ursulas.
raise
return found_ursulas
def assign_kfrag_to_arrangement(self, arrangement):

View File

@ -42,7 +42,7 @@ def test_sample_miners(chain, mock_miner_agent, mock_token_agent):
chain.time_travel(periods=1)
with pytest.raises(MinerAgent.NotEnoughUrsulas):
with pytest.raises(MinerAgent.NotEnoughMiners):
mock_miner_agent.sample(quantity=100) # Waay more than we have deployed
miners = mock_miner_agent.sample(quantity=3)

View File

@ -1,5 +1,6 @@
import datetime
import pytest
from apistar.test import TestClient
from nucypher.characters import Ursula
@ -12,7 +13,16 @@ from umbral.keys import UmbralPublicKey
import maya
def test_grant(alice, bob, nucypher_test_config):
@pytest.mark.usefixtures('nucypher_test_config')
def test_grant(alice, bob, mock_miner_agent, mock_token_agent, chain):
mock_token_agent.token_airdrop(amount=100000 * mock_token_agent.M)
_origin, ursula, *everybody_else = mock_miner_agent.blockchain.interface.w3.eth.accounts
mock_miner_agent.spawn_random_miners(addresses=everybody_else)
chain.time_travel(periods=1)
policy_end_datetime = maya.now() + datetime.timedelta(days=5)
n = 5
uri = b"this_is_the_path_to_which_access_is_being_granted"

View File

@ -16,15 +16,13 @@ from nucypher.data_sources import DataSource
from nucypher.keystore import keystore
from nucypher.keystore.db import Base
from nucypher.keystore.keypairs import SigningKeypair
from nucypher.network import blockchain_client
from tests.utilities import NUMBER_OF_URSULAS_IN_NETWORK, MockNetworkyStuff, make_ursulas, \
URSULA_PORT, EVENT_LOOP
from tests.utilities import NUMBER_OF_URSULAS_IN_NETWORK, MockNetworkMiddleware, make_ursulas, URSULA_PORT, EVENT_LOOP
@pytest.fixture(scope="module")
def nucypher_test_config(blockchain_config):
config = NucypherConfig(keyring="this is the most secure password in the world.",
config = NucypherConfig(keyring="this is a faked keyring object",
blockchain_config=blockchain_config)
yield config
NucypherConfig.reset()
@ -55,7 +53,7 @@ def enacted_policy(idle_policy, ursulas):
deposit = constants.NON_PAYMENT(b"0000000")
contract_end_datetime = maya.now() + datetime.timedelta(days=5)
networky_stuff = MockNetworkyStuff(ursulas)
networky_stuff = MockNetworkMiddleware(ursulas)
found_ursulas = idle_policy.find_ursulas(networky_stuff, deposit, expiration=contract_end_datetime)
idle_policy.match_kfrags_to_found_ursulas(found_ursulas)
idle_policy.enact(networky_stuff) # REST call happens here, as does population of TreasureMap.
@ -75,8 +73,8 @@ def alice(ursulas, mock_policy_agent, nucypher_test_config):
@pytest.fixture(scope="module")
def bob(ursulas):
BOB = Bob(network_middleware=MockNetworkyStuff(ursulas))
return BOB
_bob = Bob(network_middleware=MockNetworkMiddleware(ursulas))
return _bob
@pytest.fixture(scope="module")
@ -92,7 +90,7 @@ def ursulas(nucypher_test_config):
@pytest.fixture(scope="module")
def treasure_map_is_set_on_dht(enacted_policy, ursulas):
networky_stuff = MockNetworkyStuff(ursulas)
networky_stuff = MockNetworkMiddleware(ursulas)
enacted_policy.publish_treasure_map(networky_stuff, use_dht=True)

View File

@ -9,7 +9,7 @@ from nucypher.crypto.api import keccak_digest
from nucypher.crypto.kits import UmbralMessageKit
from nucypher.network import blockchain_client
from nucypher.network.protocols import dht_value_splitter, dht_with_hrac_splitter
from tests.utilities import MockNetworkyStuff, EVENT_LOOP, URSULA_PORT, NUMBER_OF_URSULAS_IN_NETWORK
from tests.utilities import MockNetworkMiddleware, EVENT_LOOP, URSULA_PORT, NUMBER_OF_URSULAS_IN_NETWORK
def test_all_ursulas_know_about_all_other_ursulas(ursulas):
@ -65,7 +65,7 @@ def test_alice_finds_ursula_via_dht(alice, ursulas):
def test_alice_finds_ursula_via_rest(alice, ursulas):
networky_stuff = MockNetworkyStuff(ursulas)
networky_stuff = MockNetworkMiddleware(ursulas)
# Imagine alice knows of nobody.
alice.known_nodes = {}
@ -92,7 +92,7 @@ def test_alice_sets_treasure_map_on_network(enacted_policy, ursulas):
"""
Having enacted all the policies of a PolicyGroup, Alice creates a TreasureMap and sends it to Ursula via the DHT.
"""
networky_stuff = MockNetworkyStuff(ursulas)
networky_stuff = MockNetworkMiddleware(ursulas)
_, packed_encrypted_treasure_map, _, _ = enacted_policy.publish_treasure_map(networky_stuff=networky_stuff, use_dht=True)
treasure_map_as_set_on_network = ursulas[0].server.storage[
@ -148,7 +148,7 @@ def test_bob_can_retreive_the_treasure_map_and_decrypt_it(enacted_policy, ursula
that Bob can retrieve it with only the information about which he is privy pursuant to the PolicyGroup.
"""
bob = enacted_policy.bob
networky_stuff = MockNetworkyStuff(ursulas)
networky_stuff = MockNetworkMiddleware(ursulas)
# Of course, in the real world, Bob has sufficient information to reconstitute a PolicyGroup, gleaned, we presume,
# through a side-channel with Alice.

View File

@ -4,7 +4,7 @@ from apistar.test import TestClient
from nucypher.characters import Ursula
from nucypher.config.configs import NucypherConfig
from nucypher.network.node import NetworkyStuff
from nucypher.network.middleware import NetworkMiddleware
from nucypher.policy.models import ArrangementResponse
NUMBER_OF_URSULAS_IN_NETWORK = 6
@ -53,15 +53,12 @@ class MockArrangementResponse(ArrangementResponse):
return b"This is a arrangement response; we have no idea what the bytes repr will be."
class MockNetworkyStuff(NetworkyStuff):
class MockNetworkMiddleware(NetworkMiddleware):
def __init__(self, ursulas):
self._ursulas = {bytes(u.stamp): u for u in ursulas}
self.ursulas = iter(ursulas)
def go_live_with_policy(self, ursula, policy_offer):
return
def find_ursula(self, arrangement=None):
try:
ursula = next(self.ursulas)