def test_storage_store_state_metadata(): node_storage = CrawlerNodeStorage(storage_filepath=IN_MEMORY_FILEPATH) state = create_specific_mock_state() # Store state data node_storage.store_state_metadata( state=FleetStateTracker.abridged_state_details(state)) result = node_storage.db_conn.execute( f"SELECT * FROM {CrawlerNodeStorage.STATE_DB_NAME}").fetchall() assert len(result) == 1 for row in result: verify_mock_state_matches_row(state, row) # update state new_now = state.updated.add(minutes=5) new_color = 'red' new_color_hex = '4F3D21' symbol = '%' updated_state = create_specific_mock_state(updated=new_now, color=new_color, color_hex=new_color_hex, symbol=symbol) node_storage.store_state_metadata( state=FleetStateTracker.abridged_state_details(updated_state)) # ensure same item gets updated result = node_storage.db_conn.execute( f"SELECT * FROM {CrawlerNodeStorage.STATE_DB_NAME}").fetchall() assert len(result) == 1 # state data is updated not added for row in result: verify_mock_state_matches_row(updated_state, row)
def test_blockchain_ursula_is_not_valid_with_unsigned_identity_evidence(blockchain_ursulas, caplog): lonely_blockchain_learner, blockchain_teacher, unsigned = list(blockchain_ursulas)[0:3] unsigned._evidence_of_decentralized_identity = NOT_SIGNED # Wipe known nodes. lonely_blockchain_learner._Learner__known_nodes = FleetStateTracker() lonely_blockchain_learner._current_teacher_node = blockchain_teacher lonely_blockchain_learner.remember_node(blockchain_teacher) warnings = [] def warning_trapper(event): if event['log_level'] == LogLevel.warn: warnings.append(event) globalLogPublisher.addObserver(warning_trapper) lonely_blockchain_learner.learn_from_teacher_node() globalLogPublisher.removeObserver(warning_trapper) # We received one warning during learning, and it was about this very matter. assert len(warnings) == 1 assert warnings[0]['log_format'] == unsigned.invalid_metadata_message.format(unsigned) # minus 2 for self and, of course, unsigned. assert len(lonely_blockchain_learner.known_nodes) == len(blockchain_ursulas) - 2 assert blockchain_teacher in lonely_blockchain_learner.known_nodes assert unsigned not in lonely_blockchain_learner.known_nodes
def test_blockchain_ursula_stamp_verification_tolerance( blockchain_ursulas, caplog): # # Setup # # TODO: #1035 lonely_blockchain_learner, blockchain_teacher, unsigned, *the_others, non_staking_ursula = list( blockchain_ursulas) warnings = [] def warning_trapper(event): if event['log_level'] == LogLevel.warn: warnings.append(event) # # Attempt to verify unsigned stamp # unsigned._Teacher__decentralized_identity_evidence = NOT_SIGNED # Wipe known nodes! lonely_blockchain_learner._Learner__known_nodes = FleetStateTracker() lonely_blockchain_learner._current_teacher_node = blockchain_teacher lonely_blockchain_learner.remember_node(blockchain_teacher) globalLogPublisher.addObserver(warning_trapper) lonely_blockchain_learner.learn_from_teacher_node() globalLogPublisher.removeObserver(warning_trapper) # We received one warning during learning, and it was about this very matter. assert len(warnings) == 1 warning = warnings[0]['log_format'] assert str(unsigned) in warning assert "stamp is unsigned" in warning # TODO: Cleanup logging templates assert unsigned not in lonely_blockchain_learner.known_nodes # TODO: #1035 # minus 3: self, a non-staking ursula, and the unsigned ursula. assert len( lonely_blockchain_learner.known_nodes) == len(blockchain_ursulas) - 3 assert blockchain_teacher in lonely_blockchain_learner.known_nodes # # Attempt to verify non-staking Ursula # lonely_blockchain_learner._current_teacher_node = non_staking_ursula globalLogPublisher.addObserver(warning_trapper) lonely_blockchain_learner.learn_from_teacher_node() globalLogPublisher.removeObserver(warning_trapper) assert len(warnings) == 2 warning = warnings[1]['log_format'] assert str(non_staking_ursula) in warning assert "no active stakes" in warning # TODO: Cleanup logging templates assert non_staking_ursula not in lonely_blockchain_learner.known_nodes
def __write_state_metadata(self, state): from nucypher.network.nodes import FleetStateTracker state_dict = FleetStateTracker.abridged_state_details(state) # convert updated timestamp format for supported sqlite3 sorting state_dict['updated'] = state.updated.rfc3339() db_row = (state_dict['nickname'], state_dict['symbol'], state_dict['color_hex'], state_dict['color_name'], state_dict['updated']) with self.db_conn: self.db_conn.execute(f'REPLACE INTO {self.STATE_DB_NAME} VALUES(?,?,?,?,?)', db_row)
def test_blockchain_alice_finds_ursula_via_rest(blockchain_alice, blockchain_ursulas): # Imagine alice knows of nobody. blockchain_alice._Learner__known_nodes = FleetStateTracker() blockchain_alice.remember_node(blockchain_ursulas[0]) blockchain_alice.learn_from_teacher_node() assert len(blockchain_alice.known_nodes) == len(blockchain_ursulas) for ursula in blockchain_ursulas: assert ursula in blockchain_alice.known_nodes
def __write_node_metadata(self, node): from nucypher.network.nodes import FleetStateTracker node_dict = FleetStateTracker.abridged_node_details(node) db_row = (node_dict['staker_address'], node_dict['rest_url'], node_dict['nickname'], node_dict['timestamp'], node_dict['last_seen'], node_dict['fleet_state_icon']) with self.db_conn: self.db_conn.execute( f'REPLACE INTO {self.NODE_DB_NAME} VALUES(?,?,?,?,?,?)', db_row)
def test_blockchain_ursula_stamp_verification_tolerance( blockchain_ursulas, mocker): # # Setup # lonely_blockchain_learner, blockchain_teacher, unsigned, *the_others = list( blockchain_ursulas) warnings = [] def warning_trapper(event): if event['log_level'] == LogLevel.warn: warnings.append(event) # # Attempt to verify unsigned stamp # unsigned._Teacher__decentralized_identity_evidence = NOT_SIGNED # Wipe known nodes! lonely_blockchain_learner._Learner__known_nodes = FleetStateTracker() lonely_blockchain_learner._current_teacher_node = blockchain_teacher lonely_blockchain_learner.remember_node(blockchain_teacher) globalLogPublisher.addObserver(warning_trapper) lonely_blockchain_learner.learn_from_teacher_node(eager=True) globalLogPublisher.removeObserver(warning_trapper) # We received one warning during learning, and it was about this very matter. assert len(warnings) == 1 warning = warnings[0]['log_format'] assert str(unsigned) in warning assert "stamp is unsigned" in warning # TODO: Cleanup logging templates # TODO: Buckets! #567 # assert unsigned not in lonely_blockchain_learner.known_nodes # minus 2: self and the unsigned ursula. # assert len(lonely_blockchain_learner.known_nodes) == len(blockchain_ursulas) - 2 assert blockchain_teacher in lonely_blockchain_learner.known_nodes # Learn about a node with a badly signed payload mocker.patch.object(lonely_blockchain_learner, 'verify_from', side_effect=Learner.InvalidSignature) lonely_blockchain_learner.learn_from_teacher_node(eager=True) assert len(lonely_blockchain_learner. suspicious_activities_witnessed['vladimirs']) == 1
def test_storage_db_clear_not_metadata(): node_storage = CrawlerNodeStorage(storage_filepath=IN_MEMORY_FILEPATH) # store some data node = create_random_mock_node() node_storage.store_node_metadata(node=node) state = create_specific_mock_state() node_storage.store_state_metadata( state=FleetStateTracker.abridged_state_details(state)) teacher_checksum = '0x123456789' node_storage.store_current_teacher(teacher_checksum) verify_all_db_tables(node_storage.db_conn, expect_empty=False) # only clear certificates data node_storage.clear(metadata=False, certificates=True) # db tables should not have been cleared verify_all_db_tables(node_storage.db_conn, expect_empty=False)
def test_node_client_get_state_metadata(tempfile_path): # Add some node data node_storage = CrawlerNodeStorage(storage_filepath=tempfile_path) state_1 = create_random_mock_state() state_2 = create_random_mock_state() state_3 = create_random_mock_state() state_list = [state_1, state_2, state_3] for state in state_list: state_dict = FleetStateTracker.abridged_state_details(state) node_storage.store_state_metadata(state=state_dict) node_db_client = CrawlerStorageClient(db_filepath=tempfile_path) result = node_db_client.get_previous_states_metadata(limit=len(state_list)) state_list.sort(key=lambda x: x.updated.epoch, reverse=True) # sorted by timestamp in descending order assert len(result) == len(state_list) # verify result # "result" of form of a list of state_info dictionaries for idx, value in enumerate(result): expected_row = convert_state_to_display_values(state_list[idx]) for info_idx, column in enumerate(CrawlerNodeStorage.STATE_DB_SCHEMA): assert value[column[0]] == expected_row[info_idx], f"{column[0]} matches"
def __init__( self, # Base config_root: str = None, config_file_location: str = None, # Mode dev_mode: bool = False, federated_only: bool = False, # Identity is_me: bool = True, checksum_public_address: str = None, crypto_power: CryptoPower = None, # Keyring keyring: NucypherKeyring = None, keyring_dir: str = None, # Learner learn_on_same_thread: bool = False, abort_on_learning_error: bool = False, start_learning_now: bool = True, # REST rest_host: str = None, rest_port: int = None, # TLS tls_curve: EllipticCurve = None, certificate: Certificate = None, # Network domains: Set[str] = None, interface_signature: Signature = None, network_middleware: RestMiddleware = None, # Node Storage known_nodes: set = None, node_storage: NodeStorage = None, reload_metadata: bool = True, save_metadata: bool = True, # Blockchain poa: bool = False, provider_uri: str = None, # Registry registry_source: str = None, registry_filepath: str = None, import_seed_registry: bool = False # TODO: needs cleanup ) -> None: # Logs self.log = Logger(self.__class__.__name__) # # REST + TLS (Ursula) # self.rest_host = rest_host or self.DEFAULT_REST_HOST default_port = (self.DEFAULT_DEVELOPMENT_REST_PORT if dev_mode else self.DEFAULT_REST_PORT) self.rest_port = rest_port or default_port self.tls_curve = tls_curve or self.__DEFAULT_TLS_CURVE self.certificate = certificate self.interface_signature = interface_signature self.crypto_power = crypto_power # # Keyring # self.keyring = keyring or NO_KEYRING_ATTACHED self.keyring_dir = keyring_dir or UNINITIALIZED_CONFIGURATION # Contract Registry if import_seed_registry is True: registry_source = self.REGISTRY_SOURCE if not os.path.isfile(registry_source): message = "Seed contract registry does not exist at path {}.".format( registry_filepath) self.log.debug(message) raise RuntimeError(message) self.__registry_source = registry_source or self.REGISTRY_SOURCE self.registry_filepath = registry_filepath or UNINITIALIZED_CONFIGURATION # # Configuration # self.config_file_location = config_file_location or UNINITIALIZED_CONFIGURATION self.config_root = UNINITIALIZED_CONFIGURATION # # Mode # self.federated_only = federated_only self.__dev_mode = dev_mode if self.__dev_mode: self.__temp_dir = UNINITIALIZED_CONFIGURATION self.node_storage = ForgetfulNodeStorage( federated_only=federated_only, character_class=self.__class__) else: self.__temp_dir = LIVE_CONFIGURATION self.config_root = config_root or DEFAULT_CONFIG_ROOT self._cache_runtime_filepaths() self.node_storage = node_storage or LocalFileBasedNodeStorage( federated_only=federated_only, config_root=self.config_root) # Domains self.domains = domains or {self.DEFAULT_DOMAIN} # # Identity # self.is_me = is_me self.checksum_public_address = checksum_public_address if self.is_me is True or dev_mode is True: # Self if self.checksum_public_address and dev_mode is False: self.attach_keyring() self.network_middleware = network_middleware or self.__DEFAULT_NETWORK_MIDDLEWARE_CLASS( ) else: # Stranger self.node_storage = STRANGER_CONFIGURATION self.keyring_dir = STRANGER_CONFIGURATION self.keyring = STRANGER_CONFIGURATION self.network_middleware = STRANGER_CONFIGURATION if network_middleware: raise self.ConfigurationError( "Cannot configure a stranger to use network middleware.") # # Learner # self.learn_on_same_thread = learn_on_same_thread self.abort_on_learning_error = abort_on_learning_error self.start_learning_now = start_learning_now self.save_metadata = save_metadata self.reload_metadata = reload_metadata self.__fleet_state = FleetStateTracker() known_nodes = known_nodes or set() if known_nodes: self.known_nodes._nodes.update( {node.checksum_public_address: node for node in known_nodes}) self.known_nodes.record_fleet_state( ) # TODO: Does this call need to be here? # # Blockchain # self.poa = poa self.provider_uri = provider_uri or self.DEFAULT_PROVIDER_URI self.blockchain = NO_BLOCKCHAIN_CONNECTION self.accounts = NO_BLOCKCHAIN_CONNECTION self.token_agent = NO_BLOCKCHAIN_CONNECTION self.miner_agent = NO_BLOCKCHAIN_CONNECTION self.policy_agent = NO_BLOCKCHAIN_CONNECTION # # Development Mode # if dev_mode: # Ephemeral dev settings self.abort_on_learning_error = True self.save_metadata = False self.reload_metadata = False # Generate one-time alphanumeric development password alphabet = string.ascii_letters + string.digits password = ''.join(secrets.choice(alphabet) for _ in range(32)) # Auto-initialize self.initialize(password=password, import_registry=import_seed_registry)
def __init__( self, # Base config_root: str = None, filepath: str = None, # Mode dev_mode: bool = False, federated_only: bool = False, # Identity checksum_address: str = None, crypto_power: CryptoPower = None, # Keyring keyring: NucypherKeyring = None, keyring_root: str = None, # Learner learn_on_same_thread: bool = False, abort_on_learning_error: bool = False, start_learning_now: bool = True, # Network controller_port: int = None, domains: Set[str] = None, interface_signature: Signature = None, network_middleware: RestMiddleware = None, # Node Storage known_nodes: set = None, node_storage: NodeStorage = None, reload_metadata: bool = True, save_metadata: bool = True, # Blockchain poa: bool = False, provider_uri: str = None, provider_process=None, # Registry registry_filepath: str = None, download_registry: bool = True) -> None: self.log = Logger(self.__class__.__name__) # Identity # NOTE: NodeConfigurations can only be used with Self-Characters self.is_me = True self.checksum_address = checksum_address # Network self.controller_port = controller_port or self.DEFAULT_CONTROLLER_PORT self.network_middleware = network_middleware or self.DEFAULT_NETWORK_MIDDLEWARE( ) self.interface_signature = interface_signature # Keyring self.crypto_power = crypto_power self.keyring = keyring or NO_KEYRING_ATTACHED self.keyring_root = keyring_root or UNINITIALIZED_CONFIGURATION # Contract Registry self.download_registry = download_registry self.registry_filepath = registry_filepath or UNINITIALIZED_CONFIGURATION # Blockchain self.poa = poa self.provider_uri = provider_uri or self.DEFAULT_PROVIDER_URI self.provider_process = provider_process or NO_BLOCKCHAIN_CONNECTION self.blockchain = NO_BLOCKCHAIN_CONNECTION.bool_value(False) self.token_agent = NO_BLOCKCHAIN_CONNECTION self.staking_agent = NO_BLOCKCHAIN_CONNECTION self.policy_agent = NO_BLOCKCHAIN_CONNECTION # Learner self.federated_only = federated_only self.domains = domains or {self.DEFAULT_DOMAIN} self.learn_on_same_thread = learn_on_same_thread self.abort_on_learning_error = abort_on_learning_error self.start_learning_now = start_learning_now self.save_metadata = save_metadata self.reload_metadata = reload_metadata self.__known_nodes = known_nodes or set() # handpicked self.__fleet_state = FleetStateTracker() # Configuration self.__dev_mode = dev_mode self.config_file_location = filepath or UNINITIALIZED_CONFIGURATION self.config_root = UNINITIALIZED_CONFIGURATION if dev_mode: self.__temp_dir = UNINITIALIZED_CONFIGURATION self.__setup_node_storage() self.initialize(password=DEVELOPMENT_CONFIGURATION) else: self.__temp_dir = LIVE_CONFIGURATION self.config_root = config_root or self.DEFAULT_CONFIG_ROOT self._cache_runtime_filepaths() self.__setup_node_storage(node_storage=node_storage) super().__init__(filepath=self.config_file_location, config_root=self.config_root)
class CharacterConfiguration(BaseConfiguration): """ 'Sideways Engagement' of Character classes; a reflection of input parameters. """ CHARACTER_CLASS = NotImplemented DEFAULT_CONTROLLER_PORT = NotImplemented DEFAULT_PROVIDER_URI = 'http://localhost:8545' DEFAULT_DOMAIN = 'goerli' DEFAULT_NETWORK_MIDDLEWARE = RestMiddleware TEMP_CONFIGURATION_DIR_PREFIX = 'tmp-nucypher' def __init__( self, # Base config_root: str = None, filepath: str = None, # Mode dev_mode: bool = False, federated_only: bool = False, # Identity checksum_address: str = None, crypto_power: CryptoPower = None, # Keyring keyring: NucypherKeyring = None, keyring_root: str = None, # Learner learn_on_same_thread: bool = False, abort_on_learning_error: bool = False, start_learning_now: bool = True, # Network controller_port: int = None, domains: Set[str] = None, interface_signature: Signature = None, network_middleware: RestMiddleware = None, # Node Storage known_nodes: set = None, node_storage: NodeStorage = None, reload_metadata: bool = True, save_metadata: bool = True, # Blockchain poa: bool = False, provider_uri: str = None, provider_process=None, # Registry registry_filepath: str = None, download_registry: bool = True) -> None: self.log = Logger(self.__class__.__name__) # Identity # NOTE: NodeConfigurations can only be used with Self-Characters self.is_me = True self.checksum_address = checksum_address # Network self.controller_port = controller_port or self.DEFAULT_CONTROLLER_PORT self.network_middleware = network_middleware or self.DEFAULT_NETWORK_MIDDLEWARE( ) self.interface_signature = interface_signature # Keyring self.crypto_power = crypto_power self.keyring = keyring or NO_KEYRING_ATTACHED self.keyring_root = keyring_root or UNINITIALIZED_CONFIGURATION # Contract Registry self.download_registry = download_registry self.registry_filepath = registry_filepath or UNINITIALIZED_CONFIGURATION # Blockchain self.poa = poa self.provider_uri = provider_uri or self.DEFAULT_PROVIDER_URI self.provider_process = provider_process or NO_BLOCKCHAIN_CONNECTION self.blockchain = NO_BLOCKCHAIN_CONNECTION.bool_value(False) self.token_agent = NO_BLOCKCHAIN_CONNECTION self.staking_agent = NO_BLOCKCHAIN_CONNECTION self.policy_agent = NO_BLOCKCHAIN_CONNECTION # Learner self.federated_only = federated_only self.domains = domains or {self.DEFAULT_DOMAIN} self.learn_on_same_thread = learn_on_same_thread self.abort_on_learning_error = abort_on_learning_error self.start_learning_now = start_learning_now self.save_metadata = save_metadata self.reload_metadata = reload_metadata self.__known_nodes = known_nodes or set() # handpicked self.__fleet_state = FleetStateTracker() # Configuration self.__dev_mode = dev_mode self.config_file_location = filepath or UNINITIALIZED_CONFIGURATION self.config_root = UNINITIALIZED_CONFIGURATION if dev_mode: self.__temp_dir = UNINITIALIZED_CONFIGURATION self.__setup_node_storage() self.initialize(password=DEVELOPMENT_CONFIGURATION) else: self.__temp_dir = LIVE_CONFIGURATION self.config_root = config_root or self.DEFAULT_CONFIG_ROOT self._cache_runtime_filepaths() self.__setup_node_storage(node_storage=node_storage) super().__init__(filepath=self.config_file_location, config_root=self.config_root) def __call__(self, **character_kwargs): return self.produce(**character_kwargs) @classmethod def generate(cls, password: str, *args, **kwargs): """Shortcut: Hook-up a new initial installation and write configuration file to the disk""" node_config = cls(dev_mode=False, *args, **kwargs) node_config.initialize(password=password) node_config.to_configuration_file() return node_config def cleanup(self) -> None: if self.__dev_mode: self.__temp_dir.cleanup() @property def dev_mode(self) -> bool: return self.__dev_mode def get_blockchain_interface(self) -> None: if self.federated_only: raise CharacterConfiguration.ConfigurationError( "Cannot connect to blockchain in federated mode") registry = None if self.registry_filepath: registry = EthereumContractRegistry( registry_filepath=self.registry_filepath) self.blockchain = BlockchainInterface( provider_uri=self.provider_uri, poa=self.poa, registry=registry, provider_process=self.provider_process) def acquire_agency(self) -> None: self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) self.staking_agent = StakingEscrowAgent(blockchain=self.blockchain) self.policy_agent = PolicyManagerAgent(blockchain=self.blockchain) self.log.debug("Established connection to nucypher contracts") @property def known_nodes(self) -> FleetStateTracker: return self.__fleet_state def __setup_node_storage(self, node_storage=None) -> None: if self.dev_mode: node_storage = ForgetfulNodeStorage( federated_only=self.federated_only) elif not node_storage: node_storage = LocalFileBasedNodeStorage( federated_only=self.federated_only, config_root=self.config_root) self.node_storage = node_storage def read_known_nodes(self, additional_nodes=None) -> None: known_nodes = self.node_storage.all(federated_only=self.federated_only) known_nodes = {node.checksum_address: node for node in known_nodes} if additional_nodes: known_nodes.update( {node.checksum_address: node for node in additional_nodes}) if self.__known_nodes: known_nodes.update( {node.checksum_address: node for node in self.__known_nodes}) self.__fleet_state._nodes.update(known_nodes) self.__fleet_state.record_fleet_state( additional_nodes_to_track=self.__known_nodes) def forget_nodes(self) -> None: self.node_storage.clear() message = "Removed all stored node node metadata and certificates" self.log.debug(message) def destroy(self) -> None: """Parse a node configuration and remove all associated files from the filesystem""" self.attach_keyring() self.keyring.destroy() os.remove(self.config_file_location) def generate_parameters(self, **overrides) -> dict: merged_parameters = { **self.static_payload(), **self.dynamic_payload, **overrides } non_init_params = ('config_root', 'poa', 'provider_uri') character_init_params = filter(lambda t: t[0] not in non_init_params, merged_parameters.items()) return dict(character_init_params) def produce(self, **overrides) -> CHARACTER_CLASS: """Initialize a new character instance and return it.""" merged_parameters = self.generate_parameters(**overrides) character = self.CHARACTER_CLASS(**merged_parameters) return character @classmethod def assemble(cls, filepath: str = None, **overrides) -> dict: payload = cls._read_configuration_file(filepath=filepath) node_storage = cls.load_node_storage( storage_payload=payload['node_storage'], federated_only=payload['federated_only']) domains = set(payload['domains']) # Assemble payload.update(dict(node_storage=node_storage, domains=domains)) # Filter out None values from **overrides to detect, well, overrides... # Acts as a shim for optional CLI flags. overrides = {k: v for k, v in overrides.items() if v is not None} payload = {**payload, **overrides} return payload @classmethod def from_configuration_file(cls, filepath: str = None, provider_process=None, **overrides) -> 'CharacterConfiguration': """Initialize a CharacterConfiguration from a JSON file.""" filepath = filepath or cls.default_filepath() assembled_params = cls.assemble(filepath=filepath, **overrides) node_configuration = cls(filepath=filepath, provider_process=provider_process, **assembled_params) return node_configuration def validate(self, no_registry: bool = False) -> bool: # Top-level if not os.path.exists(self.config_root): raise self.ConfigurationError( f'No configuration directory found at {self.config_root}.') # Sub-paths filepaths = self.runtime_filepaths if no_registry: del filepaths['registry_filepath'] for field, path in filepaths.items(): if not os.path.exists(path): message = 'Missing configuration file or directory: {}.' if 'registry' in path: message += ' Did you mean to pass --federated-only?' raise CharacterConfiguration.InvalidConfiguration( message.format(path)) return True def static_payload(self) -> dict: """Exported static configuration values for initializing Ursula""" payload = dict( # Identity federated_only=self.federated_only, checksum_address=self.checksum_address, keyring_root=self.keyring_root, # Behavior domains=list(self.domains), # From Set provider_uri=self.provider_uri, learn_on_same_thread=self.learn_on_same_thread, abort_on_learning_error=self.abort_on_learning_error, start_learning_now=self.start_learning_now, save_metadata=self.save_metadata, node_storage=self.node_storage.payload(), ) # Optional values (mode) if not self.federated_only: payload.update(dict(provider_uri=self.provider_uri, poa=self.poa)) # Merge with base payload base_payload = super().static_payload() base_payload.update(payload) return payload @property def dynamic_payload(self) -> dict: """Exported dynamic configuration values for initializing Ursula""" self.read_known_nodes() payload = dict(network_middleware=self.network_middleware or self.DEFAULT_NETWORK_MIDDLEWARE(), known_nodes=self.known_nodes, node_storage=self.node_storage, crypto_power_ups=self.derive_node_power_ups()) if not self.federated_only: self.get_blockchain_interface() self.blockchain.connect( ) # TODO: This makes blockchain connection more eager than transacting power acivation payload.update(blockchain=self.blockchain) return payload def generate_filepath(self, filepath: str = None, modifier: str = None, override: bool = False) -> str: modifier = modifier or self.checksum_address filepath = super().generate_filepath(filepath=filepath, modifier=modifier, override=override) return filepath @property def runtime_filepaths(self) -> dict: filepaths = dict(config_root=self.config_root, keyring_root=self.keyring_root, registry_filepath=self.registry_filepath) return filepaths @classmethod def generate_runtime_filepaths(cls, config_root: str) -> dict: """Dynamically generate paths based on configuration root directory""" filepaths = dict(config_root=config_root, config_file_location=os.path.join( config_root, cls.generate_filename()), keyring_root=os.path.join(config_root, 'keyring'), registry_filepath=os.path.join( config_root, EthereumContractRegistry.REGISTRY_NAME)) return filepaths def _cache_runtime_filepaths(self) -> None: """Generate runtime filepaths and cache them on the config object""" filepaths = self.generate_runtime_filepaths( config_root=self.config_root) for field, filepath in filepaths.items(): if getattr(self, field) is UNINITIALIZED_CONFIGURATION: setattr(self, field, filepath) def attach_keyring(self, checksum_address: str = None, *args, **kwargs) -> None: account = checksum_address or self.checksum_address if not account: raise self.ConfigurationError( "No account specified to unlock keyring") if self.keyring is not NO_KEYRING_ATTACHED: if self.keyring.checksum_address != account: raise self.ConfigurationError( "There is already a keyring attached to this configuration." ) return self.keyring = NucypherKeyring(keyring_root=self.keyring_root, account=account, *args, **kwargs) def derive_node_power_ups(self) -> List[CryptoPowerUp]: power_ups = list() if self.is_me and not self.dev_mode: for power_class in self.CHARACTER_CLASS._default_crypto_powerups: power_up = self.keyring.derive_crypto_power(power_class) power_ups.append(power_up) return power_ups def initialize(self, password: str) -> str: """Initialize a new configuration and write installation files to disk.""" # Development if self.dev_mode: self.__temp_dir = TemporaryDirectory( prefix=self.TEMP_CONFIGURATION_DIR_PREFIX) self.config_root = self.__temp_dir.name # Persistent else: self._ensure_config_root_exists() self.write_keyring(password=password) self._cache_runtime_filepaths() self.node_storage.initialize() if self.download_registry: self.registry_filepath = EthereumContractRegistry.download_latest_publication( ) # Validate if not self.__dev_mode: self.validate(no_registry=( not self.download_registry) or self.federated_only) # Success message = "Created nucypher installation files at {}".format( self.config_root) self.log.debug(message) return self.config_root def write_keyring(self, password: str, checksum_address: str = None, **generation_kwargs) -> NucypherKeyring: if self.federated_only: checksum_address = FEDERATED_ADDRESS elif not checksum_address: # Note: It is assumed the blockchain interface is not yet connected. if self.provider_process: # Generate Geth's "datadir" if not os.path.exists(self.provider_process.data_dir): os.mkdir(self.provider_process.data_dir) # Get or create wallet address if not self.checksum_address: self.checksum_address = self.provider_process.ensure_account_exists( password=password) elif self.checksum_address not in self.provider_process.accounts( ): raise self.ConfigurationError( f'Unknown Account {self.checksum_address}') elif not self.checksum_address: raise self.ConfigurationError( f'No checksum address provided for decentralized configuration.' ) checksum_address = self.checksum_address self.keyring = NucypherKeyring.generate( password=password, keyring_root=self.keyring_root, checksum_address=checksum_address, **generation_kwargs) if self.federated_only: self.checksum_address = self.keyring.checksum_address return self.keyring @classmethod def load_node_storage(cls, storage_payload: dict, federated_only: bool): from nucypher.config.storages import NodeStorage node_storage_subclasses = { storage._name: storage for storage in NodeStorage.__subclasses__() } storage_type = storage_payload[NodeStorage._TYPE_LABEL] storage_class = node_storage_subclasses[storage_type] node_storage = storage_class.from_payload( payload=storage_payload, federated_only=federated_only) return node_storage