Esempio n. 1
0
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)
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
 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)
Esempio n. 5
0
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
Esempio n. 6
0
 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)
Esempio n. 7
0
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
Esempio n. 8
0
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)
Esempio n. 9
0
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"
Esempio n. 10
0
    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)
Esempio n. 11
0
    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)
Esempio n. 12
0
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