def test_get_gas_strategy():

    # Testing Web3's bundled time-based gas strategies
    bundled_gas_strategies = {
        'glacial': time_based.glacial_gas_price_strategy,  # 24h
        'slow': time_based.slow_gas_price_strategy,  # 1h
        'medium': time_based.medium_gas_price_strategy,  # 5m
        'fast': time_based.fast_gas_price_strategy  # 60s
    }
    for gas_strategy_name, expected_gas_strategy in bundled_gas_strategies.items(
    ):
        gas_strategy = BlockchainInterface.get_gas_strategy(gas_strategy_name)
        assert expected_gas_strategy == gas_strategy
        assert callable(gas_strategy)

    # Passing a callable gas strategy
    callable_gas_strategy = time_based.glacial_gas_price_strategy
    assert callable_gas_strategy == BlockchainInterface.get_gas_strategy(
        callable_gas_strategy)

    # Passing None should retrieve the default gas strategy
    assert BlockchainInterface.DEFAULT_GAS_STRATEGY == 'fast'
    default = bundled_gas_strategies[BlockchainInterface.DEFAULT_GAS_STRATEGY]
    gas_strategy = BlockchainInterface.get_gas_strategy()
    assert default == gas_strategy
def test_geth_create_new_account(instant_geth_dev_node):
    blockchain = BlockchainInterface(provider_process=instant_geth_dev_node,
                                     poa=True)
    blockchain.connect()
    new_account = blockchain.client.new_account(
        password=INSECURE_DEVELOPMENT_PASSWORD)
    assert is_checksum_address(new_account)
Beispiel #3
0
def status(click_config, provider_uri, sync, geth, poa):
    """
    Echo a snapshot of live network metadata.
    """

    emitter = click_config.emitter
    click.clear()
    emitter.banner(NU_BANNER)
    emitter.echo(message="Reading Latest Chaindata...")

    try:
        ETH_NODE = None
        if geth:
            ETH_NODE = get_provider_process()
        blockchain = BlockchainInterface(provider_uri=provider_uri,
                                         provider_process=ETH_NODE,
                                         poa=poa)
        blockchain.connect(sync_now=sync, fetch_registry=True)
        paint_contract_status(blockchain=blockchain, emitter=emitter)
        return  # Exit

    except Exception as e:
        if click_config.debug:
            raise
        click.secho(str(e), bold=True, fg='red')
        return  # Exit
Beispiel #4
0
def test_geth_create_new_account(instant_geth_dev_node):

    # TODO: Move to decorator
    if 'CIRCLECI' in os.environ:
        pytest.skip("Do not run Geth nodes in CI")

    blockchain = BlockchainInterface(provider_process=instant_geth_dev_node)
    blockchain.connect(fetch_registry=False, sync_now=False)
    new_account = blockchain.client.new_account(
        password=INSECURE_DEVELOPMENT_PASSWORD)
    assert is_checksum_address(new_account)
Beispiel #5
0
    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 test_geth_EIP_191_client_signature_integration(instant_geth_dev_node):

    # Start a geth process
    blockchain = BlockchainInterface(provider_process=instant_geth_dev_node,
                                     poa=True)
    blockchain.connect()

    # Sign a message (RPC) and verify it.
    etherbase = blockchain.client.accounts[0]
    stamp = b'STAMP-' + os.urandom(64)
    signature = blockchain.client.sign_message(account=etherbase,
                                               message=stamp)
    is_valid = verify_eip_191(address=etherbase,
                              signature=signature,
                              message=stamp)
    assert is_valid
Beispiel #7
0
    def __init__(self,
                 blockchain: BlockchainInterface,
                 deployer_address: str = None,
                 client_password: str = None,
                 bare: bool = True) -> None:

        self.blockchain = blockchain
        self.__deployer_address = NO_DEPLOYER_ADDRESS
        self.deployer_address = deployer_address
        self.checksum_address = self.deployer_address

        if not bare:
            self.token_agent = NucypherTokenAgent(blockchain=blockchain)
            self.staking_agent = StakingEscrowAgent(blockchain=blockchain)
            self.policy_agent = PolicyAgent(blockchain=blockchain)
            self.adjudicator_agent = AdjudicatorAgent(blockchain=blockchain)

        self.user_escrow_deployers = dict()
        self.deployers = {d.contract_name: d for d in self.deployer_classes}

        blockchain.transacting_power = TransactingPower(
            blockchain=blockchain,
            account=deployer_address,
            password=client_password)
        blockchain.transacting_power.activate()
        self.log = Logger("Deployment-Actor")
Beispiel #8
0
def test_get_gas_strategy():

    # Testing Web3's bundled time-based gas strategies
    for gas_strategy_name, expected_gas_strategy in WEB3_GAS_STRATEGIES.items():
        gas_strategy = BlockchainInterface.get_gas_strategy(gas_strategy_name)
        assert expected_gas_strategy == gas_strategy
        assert callable(gas_strategy)

    # Passing a callable gas strategy
    callable_gas_strategy = time_based.glacial_gas_price_strategy
    assert callable_gas_strategy == BlockchainInterface.get_gas_strategy(callable_gas_strategy)

    # Passing None should retrieve the default gas strategy
    assert BlockchainInterface.DEFAULT_GAS_STRATEGY == 'fast'
    default = WEB3_GAS_STRATEGIES[BlockchainInterface.DEFAULT_GAS_STRATEGY]
    gas_strategy = BlockchainInterface.get_gas_strategy()
    assert default == gas_strategy
def test_geth_EIP_191_client_signature_integration(geth_dev_node):
    if 'CIRCLECI' in os.environ:
        pytest.skip("Do not run Geth nodes in CI")

    # Start a geth process
    blockchain = BlockchainInterface(provider_process=geth_dev_node)
    blockchain.connect(fetch_registry=False, sync_now=False)

    # Sign a message (RPC) and verify it.
    etherbase = blockchain.client.accounts[0]
    stamp = b'STAMP-' + os.urandom(64)
    signature = blockchain.client.sign_message(account=etherbase,
                                               message=stamp)
    is_valid = verify_eip_191(address=etherbase,
                              signature=signature,
                              message=stamp)
    assert is_valid
Beispiel #10
0
 def from_blockchain(cls,
                     provider_uri: str,
                     registry=None,
                     *args,
                     **kwargs):
     blockchain = BlockchainInterface.connect(provider_uri=provider_uri,
                                              registry=registry)
     instance = cls(blockchain=blockchain, *args, **kwargs)
     return instance
Beispiel #11
0
    def get_blockchain_interface(self) -> None:
        if self.federated_only:
            raise CharacterConfiguration.ConfigurationError(
                "Cannot connect to blockchain in federated mode")

        self.blockchain = BlockchainInterface(
            provider_uri=self.provider_uri,
            poa=self.poa,
            provider_process=self.provider_process)
Beispiel #12
0
def test_stake_init(click_runner,
                    stakeholder_configuration_file_location,
                    stake_value,
                    mock_registry_filepath,
                    token_economics,
                    testerchain,
                    agency,
                    manual_staker):

    # Simulate "Reconnection"
    cached_blockchain = BlockchainInterface.reconnect()
    registry = cached_blockchain.registry
    assert registry.filepath == mock_registry_filepath

    def from_dict(*args, **kwargs):
        return testerchain
    BlockchainInterface.from_dict = from_dict

    # Staker address has not stakes
    staking_agent = Agency.get_agent(StakingEscrowAgent)
    stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
    assert not stakes

    stake_args = ('stake', 'init',
                  '--config-file', stakeholder_configuration_file_location,
                  '--registry-filepath', mock_registry_filepath,
                  '--staking-address', manual_staker,
                  '--value', stake_value.to_tokens(),
                  '--duration', token_economics.minimum_locked_periods,
                  '--force')

    # TODO: This test it writing to the default system directory and ignoring updates to the passes filepath
    user_input = f'0\n' + f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + f'Y\n'
    result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
    assert result.exit_code == 0

    # Test integration with BaseConfiguration
    with open(stakeholder_configuration_file_location, 'r') as config_file:
        _config_data = json.loads(config_file.read())

    # Verify the stake is on-chain
    # Test integration with Agency
    stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
    assert len(stakes) == 1

    # Test integration with NU
    start_period, end_period, value = stakes[0]
    assert NU(int(value), 'NuNit') == stake_value
    assert (end_period - start_period) == token_economics.minimum_locked_periods - 1

    # Test integration with Stake
    stake = Stake.from_stake_info(index=0,
                                  checksum_address=manual_staker,
                                  stake_info=stakes[0])
    assert stake.value == stake_value
    assert stake.duration == token_economics.minimum_locked_periods
Beispiel #13
0
def test_geth_EIP_191_client_signature_integration(instant_geth_dev_node):

    # TODO: #1909 Move to decorator
    if 'CIRCLECI' in os.environ:
        pytest.skip("Do not run Geth nodes in CI")

    # Start a geth process
    blockchain = BlockchainInterface(provider_process=instant_geth_dev_node,
                                     poa=True)
    blockchain.connect()

    # Sign a message (RPC) and verify it.
    etherbase = blockchain.client.accounts[0]
    stamp = b'STAMP-' + os.urandom(64)
    signature = blockchain.client.sign_message(account=etherbase,
                                               message=stamp)
    is_valid = verify_eip_191(address=etherbase,
                              signature=signature,
                              message=stamp)
    assert is_valid
Beispiel #14
0
def make_testerchain(provider_uri, solidity_compiler):

    # Destroy existing blockchain
    BlockchainInterface.disconnect()
    TesterBlockchain.sever_connection()

    registry = EthereumContractRegistry(registry_filepath=MOCK_REGISTRY_FILEPATH)
    deployer_interface = BlockchainDeployerInterface(compiler=solidity_compiler,
                                                     registry=registry,
                                                     provider_uri=provider_uri)

    # Create new blockchain
    testerchain = TesterBlockchain(interface=deployer_interface,
                                   eth_airdrop=True,
                                   free_transactions=False,
                                   poa=True)

    # Set the deployer address from a freshly created test account
    deployer_interface.deployer_address = testerchain.etherbase_account
    return testerchain
Beispiel #15
0
 def connect(cls, provider_uri: str = None) -> 'Blockchain':
     if cls._instance is NO_BLOCKCHAIN_AVAILABLE:
         cls._instance = cls(interface=BlockchainInterface(
             provider_uri=provider_uri))
     else:
         if provider_uri is not None:
             existing_uri = cls._instance.interface.provider_uri
             if existing_uri != provider_uri:
                 raise ValueError(
                     "There is an existing blockchain connection to {}. "
                     "Use Interface.add_provider to connect additional providers"
                     .format(existing_uri))
     return cls._instance
Beispiel #16
0
    def connect_to_blockchain(self, sync_now: bool = False) -> None:
        if self.federated_only:
            raise CharacterConfiguration.ConfigurationError(
                "Cannot connect to blockchain in federated mode")

        self.blockchain = BlockchainInterface(
            provider_uri=self.provider_uri,
            poa=self.poa,
            fetch_registry=True,
            provider_process=self.provider_process,
            sync_now=sync_now)

        # Read Ethereum Node Keyring
        self.accounts = self.blockchain.client.accounts
Beispiel #17
0
    def from_configuration_file(cls,
                                filepath: str = None,
                                sync_now: bool = True,
                                **overrides) -> 'StakeHolder':
        filepath = filepath or cls.default_filepath()
        payload = cls._read_configuration_file(filepath=filepath)

        # Sub config
        blockchain_payload = payload.pop('blockchain')
        blockchain = BlockchainInterface.from_dict(payload=blockchain_payload)
        blockchain.connect(sync_now=sync_now)  # TODO: Leave this here?

        payload.update(dict(blockchain=blockchain))

        payload.update(overrides)
        instance = cls(filepath=filepath, **payload)
        return instance
Beispiel #18
0
    def from_config(cls, filepath=None) -> 'Blockchain':

        filepath = filepath if filepath is not None else DEFAULT_INI_FILEPATH
        payload = parse_blockchain_config(filepath=filepath)

        interface = BlockchainInterface.from_config(filepath=filepath)

        if cls._instance is not None:
            return cls.connect()

        if payload['tester']:
            blockchain = TesterBlockchain(
                interface=interface,
                poa=payload['poa'],
                test_accounts=payload['test_accounts'],
                airdrop=False)
        else:
            blockchain = Blockchain(interface=interface)

        return blockchain
Beispiel #19
0
def test_stake_list(click_runner,
                    stakeholder_configuration_file_location,
                    stake_value,
                    mock_registry_filepath,
                    testerchain):

    # Simulate "Reconnection"
    cached_blockchain = BlockchainInterface.reconnect()
    registry = cached_blockchain.registry
    assert registry.filepath == mock_registry_filepath

    def from_dict(*args, **kwargs):
        return testerchain
    BlockchainInterface.from_dict = from_dict

    stake_args = ('stake', 'list', '--config-file', stakeholder_configuration_file_location)

    user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}'
    result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
    assert result.exit_code == 0
    assert str(stake_value) in result.output
Beispiel #20
0
def alice(
    click_config,
    action,

    # Mode
    dev,
    force,
    dry_run,

    # Network
    teacher_uri,
    min_stake,
    federated_only,
    network,
    discovery_port,
    controller_port,

    # Filesystem
    config_root,
    config_file,

    # Blockchain
    pay_with,
    provider_uri,
    geth,
    sync,
    poa,
    registry_filepath,
    hw_wallet,

    # Alice
    bob_encrypting_key,
    bob_verifying_key,
    label,
    m,
    n,
    value,
    rate,
    duration,
    expiration,
    message_kit,
):
    """
    "Alice the Policy Authority" management commands.
    """

    #
    # Validate
    #

    if federated_only and geth:
        raise click.BadOptionUsage(
            option_name="--geth",
            message="Federated only cannot be used with the --geth flag")

    # Banner
    emitter = click_config.emitter
    emitter.clear()
    emitter.banner(ALICE_BANNER)

    #
    # Managed Ethereum Client
    #

    ETH_NODE = NO_BLOCKCHAIN_CONNECTION
    if geth:
        ETH_NODE = actions.get_provider_process()
        provider_uri = ETH_NODE.provider_uri(scheme='file')

    #
    # Eager Actions (No Authentication Required)
    #

    if action == 'init':
        """Create a brand-new persistent Alice"""

        if dev:
            raise click.BadArgumentUsage(
                "Cannot create a persistent development character")

        if not provider_uri and not federated_only:
            raise click.BadOptionUsage(
                option_name='--provider',
                message=
                "--provider is required to create a new decentralized alice.")

        if not config_root:  # Flag
            config_root = click_config.config_file  # Envvar

        if not pay_with and not federated_only:
            registry = None
            if registry_filepath:
                registry = EthereumContractRegistry(
                    registry_filepath=registry_filepath)
            blockchain = BlockchainInterface(provider_uri=provider_uri,
                                             registry=registry,
                                             poa=poa)
            blockchain.connect(sync_now=sync, fetch_registry=False)
            pay_with = select_client_account(emitter=emitter,
                                             blockchain=blockchain)

        new_alice_config = AliceConfiguration.generate(
            password=get_nucypher_password(confirm=True),
            config_root=config_root,
            checksum_address=pay_with,
            domains={network} if network else None,
            federated_only=federated_only,
            download_registry=click_config.no_registry,
            registry_filepath=registry_filepath,
            provider_process=ETH_NODE,
            poa=poa,
            provider_uri=provider_uri,
            m=m,
            n=n,
            duration=duration,
            rate=rate)

        painting.paint_new_installation_help(
            emitter, new_configuration=new_alice_config)
        return  # Exit

    elif action == "view":
        """Paint an existing configuration to the console"""
        configuration_file_location = config_file or AliceConfiguration.default_filepath(
        )
        response = AliceConfiguration._read_configuration_file(
            filepath=configuration_file_location)
        return emitter.ipc(
            response=response, request_id=0,
            duration=0)  # FIXME: what are request_id and duration here?

    #
    # Get Alice Configuration
    #

    if dev:
        alice_config = AliceConfiguration(
            dev_mode=True,
            network_middleware=click_config.middleware,
            domains={network},
            provider_process=ETH_NODE,
            provider_uri=provider_uri,
            federated_only=True)

    else:
        try:
            alice_config = AliceConfiguration.from_configuration_file(
                dev_mode=False,
                filepath=config_file,
                domains={network} if network else None,
                network_middleware=click_config.middleware,
                rest_port=discovery_port,
                checksum_address=pay_with,
                provider_process=ETH_NODE,
                provider_uri=provider_uri)
        except FileNotFoundError:
            return actions.handle_missing_configuration_file(
                character_config_class=AliceConfiguration,
                config_file=config_file)

    if action == "destroy":
        """Delete all configuration files from the disk"""
        if dev:
            message = "'nucypher alice destroy' cannot be used in --dev mode"
            raise click.BadOptionUsage(option_name='--dev', message=message)
        return actions.destroy_configuration(emitter,
                                             character_config=alice_config,
                                             force=force)

    #
    # Produce Alice
    #

    # TODO: OH MY.
    client_password = None
    if not alice_config.federated_only:
        if (not hw_wallet or not dev) and not click_config.json_ipc:
            client_password = get_client_password(
                checksum_address=alice_config.checksum_address)
    ALICE = actions.make_cli_character(character_config=alice_config,
                                       click_config=click_config,
                                       dev=dev,
                                       teacher_uri=teacher_uri,
                                       min_stake=min_stake,
                                       client_password=client_password)

    #
    # Admin Actions
    #

    if action == "run":
        """Start Alice Controller"""

        try:

            # RPC
            if click_config.json_ipc:
                rpc_controller = ALICE.make_rpc_controller()
                _transport = rpc_controller.make_control_transport()
                rpc_controller.start()
                return

            # HTTP
            else:
                emitter.message(
                    f"Alice Verifying Key {bytes(ALICE.stamp).hex()}",
                    color="green",
                    bold=True)
                controller = ALICE.make_web_controller(
                    crash_on_error=click_config.debug)
                ALICE.log.info('Starting HTTP Character Web Controller')
                emitter.message(
                    f'Running HTTP Alice Controller at http://localhost:{controller_port}'
                )
                return controller.start(http_port=controller_port,
                                        dry_run=dry_run)

        # Handle Crash
        except Exception as e:
            alice_config.log.critical(str(e))
            emitter.message(f"{e.__class__.__name__} {e}",
                            color='red',
                            bold=True)
            if click_config.debug:
                raise  # Crash :-(
            return

    #
    # Alice API
    #

    elif action == "public-keys":
        response = ALICE.controller.public_keys()
        return response

    elif action == "derive-policy-pubkey":

        # Validate
        if not label:
            raise click.BadOptionUsage(
                option_name='label',
                message=
                "--label is required for deriving a policy encrypting key.")

        # Request
        return ALICE.controller.derive_policy_encrypting_key(label=label)

    elif action == "grant":

        # Validate
        if not all((bob_verifying_key, bob_encrypting_key, label)):
            raise click.BadArgumentUsage(
                message=
                "--bob-verifying-key, --bob-encrypting-key, and --label are "
                "required options to grant (optionally --m, --n, and --expiration)."
            )

        # Request
        grant_request = {
            'bob_encrypting_key': bob_encrypting_key,
            'bob_verifying_key': bob_verifying_key,
            'label': label,
            'm': m,
            'n': n,
            'expiration': expiration,
        }

        if not ALICE.federated_only:
            grant_request.update({'value': value})
        return ALICE.controller.grant(request=grant_request)

    elif action == "revoke":

        # Validate
        if not label and bob_verifying_key:
            raise click.BadArgumentUsage(
                message=
                f"--label and --bob-verifying-key are required options for revoke."
            )

        # Request
        revoke_request = {
            'label': label,
            'bob_verifying_key': bob_verifying_key
        }
        return ALICE.controller.revoke(request=revoke_request)

    elif action == "decrypt":

        # Validate
        if not all((label, message_kit)):
            input_specification, output_specification = ALICE.controller.get_specifications(
                interface_name=action)
            required_fields = ', '.join(input_specification)
            raise click.BadArgumentUsage(
                f'{required_fields} are required flags to decrypt')

        # Request
        request_data = {'label': label, 'message_kit': message_kit}
        response = ALICE.controller.decrypt(request=request_data)
        return response

    else:
        raise click.BadArgumentUsage(f"No such argument {action}")
Beispiel #21
0
def ursula(
    click_config,
    action,
    dev,
    dry_run,
    force,
    lonely,
    network,
    teacher_uri,
    min_stake,
    rest_host,
    rest_port,
    db_filepath,
    staker_address,
    worker_address,
    federated_only,
    poa,
    config_root,
    config_file,
    provider_uri,
    geth,
    registry_filepath,
    interactive,
    sync,
) -> None:
    """
    "Ursula the Untrusted" PRE Re-encryption node management commands.

    \b
    Actions
    -------------------------------------------------
    \b
    init              Create a new Ursula node configuration.
    view              View the Ursula node's configuration.
    run               Run an "Ursula" node.
    save-metadata     Manually write node metadata to disk without running
    forget            Forget all known nodes.
    destroy           Delete Ursula node configuration.
    confirm-activity  Manually confirm-activity for the current period.

    """

    emitter = click_config.emitter

    #
    # Validate
    #

    if federated_only:
        if geth:
            raise click.BadOptionUsage(
                option_name="--geth",
                message="Federated only cannot be used with the --geth flag")

        if staker_address:
            raise click.BadOptionUsage(
                option_name='--federated-only',
                message="Staking address cannot be used in federated mode.")

    # Banner
    emitter.banner(URSULA_BANNER.format(worker_address or ''))

    #
    # Pre-Launch Warnings
    #

    if dev:
        emitter.echo("WARNING: Running in Development mode",
                     color='yellow',
                     verbosity=1)
    if force:
        emitter.echo("WARNING: Force is enabled", color='yellow', verbosity=1)

    #
    # Internal Ethereum Client
    #

    ETH_NODE = NO_BLOCKCHAIN_CONNECTION
    if geth:
        ETH_NODE = actions.get_provider_process()
        provider_uri = ETH_NODE.provider_uri(scheme='file')

    #
    # Eager Actions
    #

    if action == "init":
        """Create a brand-new persistent Ursula"""

        if dev:
            raise click.BadArgumentUsage(
                "Cannot create a persistent development character")

        if (not staker_address or not worker_address) and not federated_only:

            # Connect to Blockchain
            fetch_registry = registry_filepath is None and not click_config.no_registry
            registry = None
            if registry_filepath:
                registry = EthereumContractRegistry(
                    registry_filepath=registry_filepath)
            blockchain = BlockchainInterface(provider_uri=provider_uri,
                                             registry=registry,
                                             poa=poa)
            blockchain.connect(fetch_registry=fetch_registry,
                               sync_now=sync,
                               emitter=emitter)

            if not staker_address:
                prompt = "Select staker account"
                staker_address = select_client_account(emitter=emitter,
                                                       blockchain=blockchain,
                                                       prompt=prompt)

            if not worker_address:
                prompt = "Select worker account"
                worker_address = select_client_account(emitter=emitter,
                                                       blockchain=blockchain,
                                                       prompt=prompt)

        if not config_root:  # Flag
            config_root = click_config.config_file  # Envvar

        if not rest_host:
            rest_host = actions.determine_external_ip_address(emitter,
                                                              force=force)

        download_registry = not federated_only and not click_config.no_registry
        ursula_config = UrsulaConfiguration.generate(
            password=get_nucypher_password(confirm=True),
            config_root=config_root,
            rest_host=rest_host,
            rest_port=rest_port,
            db_filepath=db_filepath,
            domains={network} if network else None,
            federated_only=federated_only,
            checksum_address=staker_address,
            worker_address=worker_address,
            download_registry=download_registry,
            registry_filepath=registry_filepath,
            provider_process=ETH_NODE,
            provider_uri=provider_uri,
            poa=poa)

        painting.paint_new_installation_help(emitter,
                                             new_configuration=ursula_config)
        return

    #
    # Make Ursula
    #

    if dev:
        ursula_config = UrsulaConfiguration(
            dev_mode=True,
            domains={TEMPORARY_DOMAIN},
            poa=poa,
            download_registry=False,
            registry_filepath=registry_filepath,
            provider_process=ETH_NODE,
            provider_uri=provider_uri,
            checksum_address=staker_address,
            worker_address=worker_address,
            federated_only=federated_only,
            rest_host=rest_host,
            rest_port=rest_port,
            db_filepath=db_filepath)
    else:
        try:
            ursula_config = UrsulaConfiguration.from_configuration_file(
                filepath=config_file,
                domains={network} if network else None,
                registry_filepath=registry_filepath,
                provider_process=ETH_NODE,
                provider_uri=provider_uri,
                rest_host=rest_host,
                rest_port=rest_port,
                db_filepath=db_filepath,
                poa=poa,
                federated_only=federated_only)
        except FileNotFoundError:
            return actions.handle_missing_configuration_file(
                character_config_class=UrsulaConfiguration,
                config_file=config_file)
        except NucypherKeyring.AuthenticationFailed as e:
            emitter.echo(str(e), color='red', bold=True)
            click.get_current_context().exit(1)
            # TODO: Exit codes (not only for this, but for other exceptions)

    #
    # Configured Pre-Authentication Actions
    #

    # Handle destruction and forget *before* network bootstrap and character initialization below
    if action == "destroy":
        """Delete all configuration files from the disk"""
        if dev:
            message = "'nucypher ursula destroy' cannot be used in --dev mode - There is nothing to destroy."
            raise click.BadOptionUsage(option_name='--dev', message=message)
        actions.destroy_configuration(emitter,
                                      character_config=ursula_config,
                                      force=force)
        return

    elif action == "forget":
        actions.forget(emitter, configuration=ursula_config)
        return

    #
    # Make Ursula
    #
    client_password = None
    if not ursula_config.federated_only:
        if not dev and not click_config.json_ipc:
            client_password = get_client_password(
                checksum_address=ursula_config.worker_address,
                envvar="NUCYPHER_WORKER_ETH_PASSWORD")

    try:
        URSULA = actions.make_cli_character(character_config=ursula_config,
                                            click_config=click_config,
                                            min_stake=min_stake,
                                            teacher_uri=teacher_uri,
                                            dev=dev,
                                            lonely=lonely,
                                            client_password=client_password)
    except NucypherKeyring.AuthenticationFailed as e:
        emitter.echo(str(e), color='red', bold=True)
        click.get_current_context().exit(1)
        # TODO: Exit codes (not only for this, but for other exceptions)

    #
    # Authenticated Action Switch
    #

    if action == 'run':
        """Seed, Produce, Run!"""

        # GO!
        try:

            # Ursula Deploy Warnings
            emitter.message(f"Starting Ursula on {URSULA.rest_interface}",
                            color='green',
                            bold=True)

            emitter.message(f"Connecting to {','.join(ursula_config.domains)}",
                            color='green',
                            bold=True)

            emitter.message("Working ~ Keep Ursula Online!",
                            color='blue',
                            bold=True)

            if interactive:
                stdio.StandardIO(
                    UrsulaCommandProtocol(ursula=URSULA, emitter=emitter))

            if dry_run:
                return  # <-- ABORT - (Last Chance)

            # Run - Step 3
            node_deployer = URSULA.get_deployer()
            node_deployer.addServices()
            node_deployer.catalogServers(node_deployer.hendrix)
            node_deployer.run()  # <--- Blocking Call (Reactor)

        # Handle Crash
        except Exception as e:
            ursula_config.log.critical(str(e))
            emitter.message(f"{e.__class__.__name__} {e}",
                            color='red',
                            bold=True)
            raise  # Crash :-(

        # Graceful Exit
        finally:
            emitter.message("Stopping Ursula", color='green')
            ursula_config.cleanup()
            emitter.message("Ursula Stopped", color='red')
        return

    elif action == "save-metadata":
        """Manually save a node self-metadata file"""
        metadata_path = ursula.write_node_metadata(node=URSULA)
        emitter.message(
            f"Successfully saved node metadata to {metadata_path}.",
            color='green')
        return

    elif action == "view":
        """Paint an existing configuration to the console"""

        if not URSULA.federated_only:
            emitter.echo("BLOCKCHAIN ----------\n")
            painting.paint_contract_status(emitter=emitter,
                                           blockchain=URSULA.blockchain)
            current_block = URSULA.blockchain.w3.eth.blockNumber
            emitter.echo(f'Block # {current_block}')
            # TODO: 1231
            emitter.echo(f'NU Balance (staker): {URSULA.token_balance}')
            emitter.echo(
                f'ETH Balance (worker): {URSULA.blockchain.client.get_balance(URSULA.worker_address)}'
            )
            emitter.echo(
                f'Current Gas Price {URSULA.blockchain.client.gas_price}')

        emitter.echo("CONFIGURATION --------")
        response = UrsulaConfiguration._read_configuration_file(
            filepath=config_file or ursula_config.config_file_location)
        return emitter.ipc(
            response=response, request_id=0, duration=0
        )  # FIXME: #1216 - what are request_id and duration here?

    elif action == 'confirm-activity':
        receipt = URSULA.confirm_activity()

        confirmed_period = URSULA.staking_agent.get_current_period() + 1
        date = datetime_at_period(period=confirmed_period)

        # TODO: Double-check dates here
        emitter.echo(
            f'\nActivity confirmed for period #{confirmed_period} '
            f'(starting at {date})',
            bold=True,
            color='blue')
        painting.paint_receipt_summary(
            emitter=emitter,
            receipt=receipt,
            chain_name=URSULA.blockchain.client.chain_name)

        # TODO: Check ActivityConfirmation event (see #1193)
        return

    else:
        raise click.BadArgumentUsage("No such argument {}".format(action))
Beispiel #22
0
def bob(click_config, action, teacher_uri, min_stake, controller_port,
        discovery_port, federated_only, network, config_root, config_file,
        checksum_address, provider_uri, registry_filepath, dev, force, poa,
        dry_run, label, policy_encrypting_key, alice_verifying_key,
        message_kit, sync):
    """
    "Bob" management commands.

    \b
    Actions
    -------------------------------------------------
    \b
    init         Create a brand new persistent Bob
    view         View existing Bob's configuration.
    run          Start Bob's controller.
    destroy      Delete existing Bob's configuration.
    public-keys  Obtain Bob's public verification and encryption keys.
    retrieve     Obtain plaintext from encrypted data, if access was granted.

    """

    #
    # Validate
    #

    # Banner
    emitter = click_config.emitter
    emitter.clear()
    emitter.banner(BOB_BANNER)

    #
    # Eager Actions
    #

    if action == 'init':
        """Create a brand-new persistent Bob"""

        if dev:
            raise click.BadArgumentUsage(
                "Cannot create a persistent development character")

        if not config_root:  # Flag
            config_root = click_config.config_file  # Envvar

        if not checksum_address and not federated_only:
            registry = None
            if registry_filepath:
                registry = EthereumContractRegistry(
                    registry_filepath=registry_filepath)
            blockchain = BlockchainInterface(provider_uri=provider_uri,
                                             registry=registry,
                                             poa=poa)
            blockchain.connect(sync_now=sync, emitter=emitter)
            checksum_address = select_client_account(emitter=emitter,
                                                     blockchain=blockchain)

        download_registry = not federated_only and not click_config.no_registry
        new_bob_config = BobConfiguration.generate(
            password=get_nucypher_password(confirm=True),
            config_root=config_root or DEFAULT_CONFIG_ROOT,
            checksum_address=checksum_address,
            domains={network} if network else None,
            federated_only=federated_only,
            download_registry=download_registry,
            registry_filepath=registry_filepath,
            provider_uri=provider_uri)

        return painting.paint_new_installation_help(
            emitter, new_configuration=new_bob_config)

    #
    # Make Bob
    #

    if dev:
        bob_config = BobConfiguration(
            dev_mode=True,
            domains={network},
            provider_uri=provider_uri,
            federated_only=True,
            checksum_address=checksum_address,
            network_middleware=click_config.middleware)
    else:

        try:
            bob_config = BobConfiguration.from_configuration_file(
                filepath=config_file,
                domains={network} if network else None,
                checksum_address=checksum_address,
                rest_port=discovery_port,
                provider_uri=provider_uri,
                network_middleware=click_config.middleware)
        except FileNotFoundError:
            return actions.handle_missing_configuration_file(
                character_config_class=BobConfiguration,
                config_file=config_file)

    BOB = actions.make_cli_character(character_config=bob_config,
                                     click_config=click_config,
                                     dev=dev,
                                     teacher_uri=teacher_uri,
                                     min_stake=min_stake)

    #
    # Admin Action
    #

    if action == "run":

        # RPC
        if click_config.json_ipc:
            rpc_controller = BOB.make_rpc_controller()
            _transport = rpc_controller.make_control_transport()
            rpc_controller.start()
            return

        # Echo Public Keys
        emitter.message(f"Bob Verifying Key {bytes(BOB.stamp).hex()}",
                        color='green',
                        bold=True)
        bob_encrypting_key = bytes(BOB.public_keys(DecryptingPower)).hex()
        emitter.message(f"Bob Encrypting Key {bob_encrypting_key}",
                        color="blue",
                        bold=True)

        # Start Controller
        controller = BOB.make_web_controller(crash_on_error=click_config.debug)
        BOB.log.info('Starting HTTP Character Web Controller')
        return controller.start(http_port=controller_port, dry_run=dry_run)

    elif action == "view":
        """Paint an existing configuration to the console"""
        response = BobConfiguration._read_configuration_file(
            filepath=config_file or bob_config.config_file_location)
        return BOB.controller.emitter.ipc(
            response, request_id=0, duration=0
        )  # FIXME: #1216 - what are request_id and duration here?

    elif action == "destroy":
        """Delete Bob's character configuration files from the disk"""

        # Validate
        if dev:
            message = "'nucypher bob destroy' cannot be used in --dev mode"
            raise click.BadOptionUsage(option_name='--dev', message=message)

        # Request
        return actions.destroy_configuration(emitter,
                                             character_config=bob_config)

    #
    # Bob API Actions
    #

    elif action == "public-keys":
        response = BOB.controller.public_keys()
        return response

    elif action == "retrieve":

        # Validate
        if not all(
            (label, policy_encrypting_key, alice_verifying_key, message_kit)):
            input_specification, output_specification = BOB.control.get_specifications(
                interface_name='retrieve')
            required_fields = ', '.join(input_specification)
            raise click.BadArgumentUsage(
                f'{required_fields} are required flags to retrieve')

        # Request
        bob_request_data = {
            'label': label,
            'policy_encrypting_key': policy_encrypting_key,
            'alice_verifying_key': alice_verifying_key,
            'message_kit': message_kit,
        }

        response = BOB.controller.retrieve(request=bob_request_data)
        return response

    else:
        raise click.BadArgumentUsage(f"No such argument {action}")
Beispiel #23
0
def stake(
    click_config,
    action,
    config_root,
    config_file,

    # Mode
    force,
    offline,
    hw_wallet,

    # Blockchain
    poa,
    registry_filepath,
    provider_uri,
    sync,

    # Stake
    staking_address,
    worker_address,
    withdraw_address,
    value,
    duration,
    index,
    policy_reward,
    staking_reward,
) -> None:
    """
    Manage stakes and other staker-related operations.

    \b
    Actions
    -------------------------------------------------
    new-stakeholder  Create a new stakeholder configuration
    list             List active stakes for current stakeholder
    accounts         Show ETH and NU balances for stakeholder's accounts
    sync             Synchronize stake data with on-chain information
    set-worker       Bound a worker to a staker
    detach-worker    Detach worker currently bound to a staker
    init             Create a new stake
    divide           Create a new stake from part of an existing one
    collect-reward   Withdraw staking reward

    """

    # Banner
    emitter = click_config.emitter
    emitter.clear()
    emitter.banner(NU_BANNER)

    if action == 'new-stakeholder':

        if not provider_uri:
            raise click.BadOptionUsage(
                option_name='--provider',
                message="--provider is required to create a new stakeholder.")

        registry = None
        fetch_registry = True
        if registry_filepath:
            registry = EthereumContractRegistry(
                registry_filepath=registry_filepath)
            fetch_registry = False
        blockchain = BlockchainInterface(provider_uri=provider_uri,
                                         registry=registry,
                                         poa=poa)
        blockchain.connect(sync_now=sync, fetch_registry=fetch_registry)

        new_stakeholder = StakeHolder(config_root=config_root,
                                      offline_mode=offline,
                                      blockchain=blockchain)

        filepath = new_stakeholder.to_configuration_file(override=force)
        emitter.echo(f"Wrote new stakeholder configuration to {filepath}",
                     color='green')
        return  # Exit

    #
    # Make Staker
    #

    STAKEHOLDER = StakeHolder.from_configuration_file(
        filepath=config_file,
        provider_uri=provider_uri,
        registry_filepath=registry_filepath,
        offline=offline,
        sync_now=sync)
    #
    # Eager Actions
    #

    if action == 'list':
        if not STAKEHOLDER.stakes:
            emitter.echo(f"There are no active stakes")
        else:
            painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes)
        return

    elif action == 'accounts':
        for address, balances in STAKEHOLDER.account_balances.items():
            emitter.echo(
                f"{address} | {Web3.fromWei(balances['ETH'], 'ether')} ETH | {NU.from_nunits(balances['NU'])}"
            )
        return  # Exit

    elif action == 'sync':
        emitter.echo("Reading on-chain stake data...")
        STAKEHOLDER.read_onchain_stakes()
        STAKEHOLDER.to_configuration_file(override=True)
        emitter.echo("OK!", color='green')
        return  # Exit

    elif action in ('set-worker', 'detach-worker'):

        if not staking_address:
            staking_address = select_stake(stakeholder=STAKEHOLDER,
                                           emitter=emitter).owner_address

        if action == 'set-worker':
            if not worker_address:
                worker_address = click.prompt("Enter worker address",
                                              type=EIP55_CHECKSUM_ADDRESS)
        elif action == 'detach-worker':
            if worker_address:
                raise click.BadOptionUsage(
                    message=
                    "detach-worker cannot be used together with --worker-address option"
                )
            worker_address = BlockchainInterface.NULL_ADDRESS

        password = None
        if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)
        receipt = STAKEHOLDER.set_worker(staker_address=staking_address,
                                         password=password,
                                         worker_address=worker_address)

        emitter.echo(f"OK | Receipt: {receipt['transactionHash'].hex()}",
                     color='green')
        return  # Exit

    elif action == 'init':
        """Initialize a new stake"""

        #
        # Get Staking Account
        #

        password = None
        if not staking_address:
            staking_address = select_client_account(
                blockchain=STAKEHOLDER.blockchain,
                prompt="Select staking account",
                emitter=emitter)

        if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local:
            password = click.prompt(
                f"Enter password to unlock {staking_address}",
                hide_input=True,
                confirmation_prompt=False)
        #
        # Stage Stake
        #

        if not value:
            min_locked = STAKEHOLDER.economics.minimum_allowed_locked
            value = click.prompt(
                f"Enter stake value in NU",
                type=STAKE_VALUE,
                default=NU.from_nunits(min_locked).to_tokens())
        value = NU.from_tokens(value)

        if not duration:
            prompt = f"Enter stake duration ({STAKEHOLDER.economics.minimum_locked_periods} periods minimum)"
            duration = click.prompt(prompt, type=STAKE_DURATION)

        start_period = STAKEHOLDER.staking_agent.get_current_period()
        end_period = start_period + duration

        #
        # Review
        #

        if not force:
            painting.paint_staged_stake(emitter=emitter,
                                        stakeholder=STAKEHOLDER,
                                        staking_address=staking_address,
                                        stake_value=value,
                                        duration=duration,
                                        start_period=start_period,
                                        end_period=end_period)

            confirm_staged_stake(staker_address=staking_address,
                                 value=value,
                                 duration=duration)

        # Last chance to bail
        click.confirm("Publish staged stake to the blockchain?", abort=True)

        # Execute
        new_stake = STAKEHOLDER.initialize_stake(
            amount=value,
            duration=duration,
            checksum_address=staking_address,
            password=password)

        painting.paint_staking_confirmation(
            emitter=emitter,
            ursula=STAKEHOLDER,
            transactions=new_stake.transactions)
        return  # Exit

    elif action == 'divide':
        """Divide an existing stake by specifying the new target value and end period"""

        if staking_address and index is not None:
            staker = STAKEHOLDER.get_active_staker(address=staking_address)
            current_stake = staker.stakes[index]
        else:
            current_stake = select_stake(stakeholder=STAKEHOLDER,
                                         emitter=emitter)

        #
        # Stage Stake
        #

        # Value
        if not value:
            value = click.prompt(
                f"Enter target value (must be less than or equal to {str(current_stake.value)})",
                type=STAKE_VALUE)
        value = NU(value, 'NU')

        # Duration
        if not duration:
            extension = click.prompt("Enter number of periods to extend",
                                     type=STAKE_EXTENSION)
        else:
            extension = duration

        if not force:
            painting.paint_staged_stake_division(emitter=emitter,
                                                 stakeholder=STAKEHOLDER,
                                                 original_stake=current_stake,
                                                 target_value=value,
                                                 extension=extension)
            click.confirm("Is this correct?", abort=True)

        # Execute
        password = None
        if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local:
            password = get_client_password(
                checksum_address=current_stake.owner_address)
        modified_stake, new_stake = STAKEHOLDER.divide_stake(
            address=current_stake.owner_address,
            index=current_stake.index,
            value=value,
            duration=extension,
            password=password)
        emitter.echo('Successfully divided stake', color='green', verbosity=1)
        emitter.echo(f'Receipt ........... {new_stake.receipt}', verbosity=1)

        # Show the resulting stake list
        painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes)
        return  # Exit

    elif action == 'collect-reward':
        """Withdraw staking reward to the specified wallet address"""
        password = None
        if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)
        STAKEHOLDER.collect_rewards(staker_address=staking_address,
                                    withdraw_address=withdraw_address,
                                    password=password,
                                    staking=staking_reward,
                                    policy=policy_reward)

    else:
        ctx = click.get_current_context()
        click.UsageError(message=f"Unknown action '{action}'.", ctx=ctx).show()
    return  # Exit
Beispiel #24
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
Beispiel #25
0
def test_nucypher_deploy_allocation_contracts(click_runner,
                                              testerchain,
                                              deploy_user_input,
                                              mock_primary_registry_filepath,
                                              mock_allocation_infile,
                                              token_economics):

    TesterBlockchain.sever_connection()
    Agency.clear()

    if os.path.isfile(MOCK_ALLOCATION_REGISTRY_FILEPATH):
        os.remove(MOCK_ALLOCATION_REGISTRY_FILEPATH)
    assert not os.path.isfile(MOCK_ALLOCATION_REGISTRY_FILEPATH)

    # We start with a blockchain node, and nothing else...
    if os.path.isfile(mock_primary_registry_filepath):
        os.remove(mock_primary_registry_filepath)
    assert not os.path.isfile(mock_primary_registry_filepath)

    command = ['contracts',
               '--registry-outfile', mock_primary_registry_filepath,
               '--provider-uri', TEST_PROVIDER_URI,
               '--poa',
               '--no-sync']

    user_input = deploy_user_input
    result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
    assert result.exit_code == 0

    #
    # Main
    #

    deploy_command = ('allocations',
                      '--registry-infile', MOCK_REGISTRY_FILEPATH,
                      '--allocation-infile', mock_allocation_infile.filepath,
                      '--allocation-outfile', MOCK_ALLOCATION_REGISTRY_FILEPATH,
                      '--provider-uri', 'tester://pyevm',
                      '--poa')

    account_index = '0\n'
    yes = 'Y\n'
    node_password = f'{INSECURE_DEVELOPMENT_PASSWORD}\n'
    user_input = account_index + yes + node_password + yes

    result = click_runner.invoke(deploy,
                                 deploy_command,
                                 input=user_input,
                                 catch_exceptions=False)
    assert result.exit_code == 0

    # ensure that a pre-allocation recipient has the allocated token quantity.
    beneficiary = testerchain.interface.w3.eth.accounts[-1]
    allocation_registry = AllocationRegistry(registry_filepath=MOCK_ALLOCATION_REGISTRY_FILEPATH)
    user_escrow_agent = UserEscrowAgent(beneficiary=beneficiary, allocation_registry=allocation_registry)
    assert user_escrow_agent.unvested_tokens == token_economics.minimum_allowed_locked

    #
    # Tear Down
    #

    # Destroy existing blockchain
    BlockchainInterface.disconnect()