diff --git a/nucypher/characters/lawful.py b/nucypher/characters/lawful.py index 1133cf5d1..bb2b10c39 100644 --- a/nucypher/characters/lawful.py +++ b/nucypher/characters/lawful.py @@ -205,6 +205,11 @@ class Alice(Character, PolicyAuthor): if handpicked_ursulas is None: handpicked_ursulas = set() + else: + # This might be the first time alice learns about the handpicked Ursulas. + for handpicked_ursula in handpicked_ursulas: + self.remember_node(node=handpicked_ursula) + policy = self.create_policy(bob, label, m, n, @@ -218,7 +223,7 @@ class Alice(Character, PolicyAuthor): # value and expiration combinations on a limited number of Ursulas; # Users may decide to inject some market strategies here. # - # TODO: 289 + # TODO: #289 # If we're federated only, we need to block to make sure we have enough nodes. if self.federated_only and len(self.known_nodes) < n: @@ -292,16 +297,13 @@ class Alice(Character, PolicyAuthor): I/O signatures match Bob's retrieve interface. """ - cleartexts = [] - cleartexts.append( - self.verify_from( - data_source, - message_kit, - signature=message_kit.signature, - decrypt=True, - label=label - ) - ) + cleartexts = [self.verify_from( + data_source, + message_kit, + signature=message_kit.signature, + decrypt=True, + label=label + )] return cleartexts def make_web_controller(drone_alice, crash_on_error: bool = False): diff --git a/nucypher/network/nodes.py b/nucypher/network/nodes.py index f67b7e18a..bf0985a14 100644 --- a/nucypher/network/nodes.py +++ b/nucypher/network/nodes.py @@ -17,30 +17,33 @@ along with nucypher. If not, see . import binascii import random +import time from collections import defaultdict, OrderedDict from collections import deque from collections import namedtuple from contextlib import suppress - from typing import Set, Tuple import maya import requests -import time +from bytestring_splitter import BytestringSplitter +from bytestring_splitter import VariableLengthBytestring, BytestringSplittingError +from constant_sorrow import constant_or_bytes +from constant_sorrow.constants import ( + NO_KNOWN_NODES, + NOT_SIGNED, + NEVER_SEEN, + NO_STORAGE_AVAILIBLE, + FLEET_STATES_MATCH, + CERTIFICATE_NOT_SAVED +) from cryptography.x509 import Certificate -from eth_keys.datatypes import Signature as EthSignature from requests.exceptions import SSLError from twisted.internet import reactor, defer from twisted.internet import task from twisted.internet.threads import deferToThread from twisted.logger import Logger -from bytestring_splitter import BytestringSplitter -from bytestring_splitter import VariableLengthBytestring, BytestringSplittingError -from constant_sorrow import constant_or_bytes -from constant_sorrow.constants import NO_KNOWN_NODES, NOT_SIGNED, NEVER_SEEN, NO_STORAGE_AVAILIBLE, FLEET_STATES_MATCH - -from nucypher.blockchain.eth.clients import Web3Client from nucypher.config.constants import SeednodeMetadata from nucypher.config.storages import ForgetfulNodeStorage from nucypher.crypto.api import keccak_digest, verify_eip_191 @@ -994,20 +997,28 @@ class Teacher: """ Three things happening here: - * Verify that the stamp matches the address (raises InvalidNode is it's not valid, or WrongMode if it's a federated mode and being verified as a decentralized node) + * Verify that the stamp matches the address (raises InvalidNode is it's not valid, + or WrongMode if it's a federated mode and being verified as a decentralized node) + * Verify the interface signature (raises InvalidNode if not valid) - * Connect to the node, make sure that it's up, and that the signature and address we checked are the same ones this node is using now. (raises InvalidNode if not valid; also emits a specific warning depending on which check failed). + + * Connect to the node, make sure that it's up, and that the signature and address we + checked are the same ones this node is using now. (raises InvalidNode if not valid; + also emits a specific warning depending on which check failed). + """ + + # Only perform this check once per object if not force: if self._verified_node: return True - # This is both the stamp signature and interface check. + # This is both the stamp's client signature and interface metadata check. self.validate_metadata(accept_federated_only) if not certificate_filepath: - if not self.certificate_filepath: + if self.certificate_filepath is CERTIFICATE_NOT_SAVED: raise TypeError("We haven't saved a certificate for this node yet.") else: certificate_filepath = self.certificate_filepath @@ -1020,7 +1031,7 @@ class Teacher: version, node_bytes = self.version_splitter(response_data, return_remainder=True) node_details = self.internal_splitter(node_bytes) - # TODO check timestamp here. 589 + # TODO: #589 - check timestamp here. verifying_keys_match = node_details['verifying_key'] == self.public_keys(SigningPower) encrypting_keys_match = node_details['encrypting_key'] == self.public_keys(DecryptingPower) @@ -1028,7 +1039,7 @@ class Teacher: evidence_matches = node_details['identity_evidence'] == self._evidence_of_decentralized_identity if not all((encrypting_keys_match, verifying_keys_match, addresses_match, evidence_matches)): - # TODO: Optional reporting. 355 + # TODO: #355 - Optional reporting. if not addresses_match: self.log.warn("Wallet address swapped out. It appears that someone is trying to defraud this node.") if not verifying_keys_match: @@ -1039,7 +1050,7 @@ class Teacher: def substantiate_stamp(self, password: str): blockchain_power = self._crypto_power.power_ups(BlockchainPower) - blockchain_power.unlock_account(password=password) # TODO: 349 + blockchain_power.unlock_account(password=password) # TODO: #349 signature = blockchain_power.sign_message(bytes(self.stamp)) self._evidence_of_decentralized_identity = signature diff --git a/tests/cli/ursula/test_blockchain_ursula.py b/tests/cli/ursula/test_blockchain_ursula.py index dd0d2557f..fbd6ffa15 100644 --- a/tests/cli/ursula/test_blockchain_ursula.py +++ b/tests/cli/ursula/test_blockchain_ursula.py @@ -229,7 +229,9 @@ def test_collect_rewards_integration(click_runner, expiration=expiration, handpicked_ursulas={staking_participant}) - # Bob joins the policy + # Bob learns about the new staker and joins the policy + blockchain_bob.start_learning_loop() + blockchain_bob.remember_node(node=staking_participant) blockchain_bob.join_policy(random_policy_label, bytes(blockchain_alice.stamp)) # Enrico Encrypts (of course) diff --git a/tests/fixtures.py b/tests/fixtures.py index 4b759a3c0..81d3d9e90 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -488,12 +488,13 @@ def funded_blockchain(testerchain, three_agents, token_economics): @pytest.fixture(scope='module') def staking_participant(funded_blockchain, blockchain_ursulas): + # Start up the local fleet for teacher in blockchain_ursulas: start_pytest_ursula_services(ursula=teacher) teachers = list(blockchain_ursulas) - staking_participant = teachers[-1] + staking_participant = teachers[-1] # TODO: # 1035 return staking_participant