def __init__( self, domains: Set, certificate: Certificate, certificate_filepath: str, interface_signature=NOT_SIGNED.bool_value(False), timestamp=NOT_SIGNED, identity_evidence=NOT_SIGNED, substantiate_immediately=False, passphrase=None, ) -> None: self.serving_domains = domains self.certificate = certificate self.certificate_filepath = certificate_filepath self._interface_signature_object = interface_signature self._timestamp = timestamp self.last_seen = NEVER_SEEN("Haven't connected to this node yet.") self.fleet_state_checksum = None self.fleet_state_updated = None self._evidence_of_decentralized_identity = constant_or_bytes( identity_evidence) if substantiate_immediately: self.substantiate_stamp( password=passphrase) # TODO: Derive from keyring
def test_constant_can_be_used_as_hashable_object(): from constant_sorrow.constants import DAPPER_DAN pomades = {DAPPER_DAN, "fop"} assert DAPPER_DAN in pomades dapper_dan_bytes = bytes(DAPPER_DAN) IN_A_COUPLE_WEEKS = constant_or_bytes(dapper_dan_bytes) assert DAPPER_DAN is IN_A_COUPLE_WEEKS assert DAPPER_DAN in pomades assert IN_A_COUPLE_WEEKS in pomades
def from_bytes( cls, ursula_as_bytes: bytes, version: int = INCLUDED_IN_BYTESTRING, federated_only: bool = False, ) -> 'Ursula': if version is INCLUDED_IN_BYTESTRING: version, payload = cls.version_splitter(ursula_as_bytes, return_remainder=True) else: payload = ursula_as_bytes # Check version and raise IsFromTheFuture if this node is... you guessed it... if version > cls.LEARNER_VERSION: # TODO: Some auto-updater logic? try: canonical_address, _ = BytestringSplitter( PUBLIC_ADDRESS_LENGTH)(payload, return_remainder=True) checksum_address = to_checksum_address(canonical_address) nickname, _ = nickname_from_seed(checksum_address) display_name = "⇀{}↽ ({})".format(nickname, checksum_address) message = cls.unknown_version_message.format( display_name, version, cls.LEARNER_VERSION) except BytestringSplittingError: message = cls.really_unknown_version_message.format( version, cls.LEARNER_VERSION) raise cls.IsFromTheFuture(message) # Version stuff checked out. Moving on. node_info = cls.internal_splitter(payload) powers_and_material = { SigningPower: node_info.pop("verifying_key"), DecryptingPower: node_info.pop("encrypting_key") } interface_info = node_info.pop("rest_interface") node_info['rest_host'] = interface_info.host node_info['rest_port'] = interface_info.port node_info['timestamp'] = maya.MayaDT(node_info.pop("timestamp")) node_info['checksum_public_address'] = to_checksum_address( node_info.pop("public_address")) domains_vbytes = VariableLengthBytestring.dispense( node_info['domains']) node_info['domains'] = [constant_or_bytes(d) for d in domains_vbytes] ursula = cls.from_public_keys(powers_and_material, federated_only=federated_only, **node_info) return ursula
def __init__( self, domains: Set, certificate: Certificate, certificate_filepath: str, interface_signature=NOT_SIGNED.bool_value(False), timestamp=NOT_SIGNED, decentralized_identity_evidence=NOT_SIGNED, substantiate_immediately=False, password=None, ) -> None: # # Fleet # self.serving_domains = domains self.fleet_state_checksum = None self.fleet_state_updated = None self.last_seen = NEVER_SEEN("No Connection to Node") self.fleet_state_icon = UNKNOWN_FLEET_STATE self.fleet_state_nickname = UNKNOWN_FLEET_STATE self.fleet_state_nickname_metadata = UNKNOWN_FLEET_STATE # # Identity # self._timestamp = timestamp self.certificate = certificate self.certificate_filepath = certificate_filepath self.__interface_signature = interface_signature self.__decentralized_identity_evidence = constant_or_bytes( decentralized_identity_evidence) # Assume unverified self.verified_stamp = False self.verified_worker = False self.verified_interface = False self.verified_node = False if substantiate_immediately: self.substantiate_stamp(client_password=password)
def learn_from_teacher_node(self, eager=True): """ Sends a request to node_url to find out about known nodes. """ self._learning_round += 1 try: current_teacher = self.current_teacher_node() except self.NotEnoughTeachers as e: self.log.warn("Can't learn right now: {}".format(e.args[0])) return if Teacher in self.__class__.__bases__: announce_nodes = [self] else: announce_nodes = None unresponsive_nodes = set() try: # TODO: Streamline path generation certificate_filepath = self.node_storage.generate_certificate_filepath( checksum_address=current_teacher.checksum_public_address) response = self.network_middleware.get_nodes_via_rest( node=current_teacher, nodes_i_need=self._node_ids_to_learn_about_immediately, announce_nodes=announce_nodes, fleet_checksum=self.known_nodes.checksum) except NodeSeemsToBeDown as e: unresponsive_nodes.add(current_teacher) self.log.info("Bad Response from teacher: {}:{}.".format( current_teacher, e)) return finally: self.cycle_teacher_node() # # Before we parse the response, let's handle some edge cases. if response.status_code == 204: # In this case, this node knows about no other nodes. Hopefully we've taught it something. if response.content == b"": return NO_KNOWN_NODES # In the other case - where the status code is 204 but the repsonse isn't blank - we'll keep parsing. # It's possible that our fleet states match, and we'll check for that later. elif response.status_code != 200: self.log.info("Bad response from teacher {}: {} - {}".format( current_teacher, response, response.content)) return try: signature, node_payload = signature_splitter(response.content, return_remainder=True) except BytestringSplittingError as e: self.log.warn(e.args[0]) return try: self.verify_from(current_teacher, node_payload, signature=signature) except current_teacher.InvalidSignature: # TODO: What to do if the teacher improperly signed the node payload? raise # End edge case handling. # fleet_state_checksum_bytes, fleet_state_updated_bytes, node_payload = FleetStateTracker.snapshot_splitter( node_payload, return_remainder=True) current_teacher.last_seen = maya.now() # TODO: This is weird - let's get a stranger FleetState going. checksum = fleet_state_checksum_bytes.hex() # TODO: This doesn't make sense - a decentralized node can still learn about a federated-only node. from nucypher.characters.lawful import Ursula if constant_or_bytes(node_payload) is FLEET_STATES_MATCH: current_teacher.update_snapshot( checksum=checksum, updated=maya.MayaDT( int.from_bytes(fleet_state_updated_bytes, byteorder="big")), number_of_known_nodes=len(self.known_nodes)) return FLEET_STATES_MATCH node_list = Ursula.batch_from_bytes( node_payload, federated_only=self.federated_only) # TODO: 466 current_teacher.update_snapshot(checksum=checksum, updated=maya.MayaDT( int.from_bytes( fleet_state_updated_bytes, byteorder="big")), number_of_known_nodes=len(node_list)) new_nodes = [] for node in node_list: if GLOBAL_DOMAIN not in self.learning_domains: if not self.learning_domains.intersection( node.serving_domains): continue # This node is not serving any of our domains. # First, determine if this is an outdated representation of an already known node. with suppress(KeyError): already_known_node = self.known_nodes[ node.checksum_public_address] if not node.timestamp > already_known_node.timestamp: self.log.debug("Skipping already known node {}".format( already_known_node)) # This node is already known. We can safely continue to the next. continue certificate_filepath = self.node_storage.store_node_certificate( certificate=node.certificate) try: if eager: node.verify_node( self.network_middleware, accept_federated_only=self.federated_only, # TODO: 466 certificate_filepath=certificate_filepath) self.log.debug("Verified node: {}".format( node.checksum_public_address)) else: node.validate_metadata( accept_federated_only=self.federated_only) # TODO: 466 # This block is a mess of eagerness. This can all be done better lazily. except NodeSeemsToBeDown as e: self.log.info( f"Can't connect to {node} to verify it right now.") except node.InvalidNode: # TODO: Account for possibility that stamp, rather than interface, was bad. self.log.warn(node.invalid_metadata_message.format(node)) except node.SuspiciousActivity: message = "Suspicious Activity: Discovered node with bad signature: {}. " \ "Propagated by: {}".format(current_teacher.checksum_public_address, teacher_uri) self.log.warn(message) else: new = self.remember_node(node, record_fleet_state=False) if new: new_nodes.append(node) self._adjust_learning(new_nodes) learning_round_log_message = "Learning round {}. Teacher: {} knew about {} nodes, {} were new." self.log.info( learning_round_log_message.format(self._learning_round, current_teacher, len(node_list), len(new_nodes)), ) if new_nodes: self.known_nodes.record_fleet_state() for node in new_nodes: self.node_storage.store_node_certificate( certificate=node.certificate) return new_nodes