mirror of https://github.com/nucypher/nucypher.git
Integrate FleetState into NodeConfiguration; checksum_address -> checksum_public_address
parent
9a83488281
commit
1c86a07a13
|
@ -70,7 +70,7 @@ class Character(Learner):
|
|||
is_me: bool = True,
|
||||
federated_only: bool = False,
|
||||
blockchain: Blockchain = None,
|
||||
checksum_address: bytes = NO_BLOCKCHAIN_CONNECTION.bool_value(False),
|
||||
checksum_public_address: bytes = NO_BLOCKCHAIN_CONNECTION.bool_value(False),
|
||||
network_middleware: RestMiddleware = None,
|
||||
keyring_dir: str = None,
|
||||
crypto_power: CryptoPower = None,
|
||||
|
@ -118,7 +118,7 @@ class Character(Learner):
|
|||
else:
|
||||
self._crypto_power = CryptoPower(power_ups=self._default_crypto_powerups)
|
||||
|
||||
self._checksum_address = checksum_address
|
||||
self._checksum_address = checksum_public_address
|
||||
#
|
||||
# Self-Character
|
||||
#
|
||||
|
@ -161,10 +161,10 @@ class Character(Learner):
|
|||
# Decentralized
|
||||
#
|
||||
if not federated_only:
|
||||
if not checksum_address:
|
||||
raise ValueError("No checksum_address provided while running in a non-federated mode.")
|
||||
if not checksum_public_address:
|
||||
raise ValueError("No checksum_public_address provided while running in a non-federated mode.")
|
||||
else:
|
||||
self._checksum_address = checksum_address # TODO: Check that this matches BlockchainPower
|
||||
self._checksum_address = checksum_public_address # TODO: Check that this matches BlockchainPower
|
||||
#
|
||||
# Federated
|
||||
#
|
||||
|
@ -173,11 +173,11 @@ class Character(Learner):
|
|||
self._set_checksum_address() # type: str
|
||||
except NoSigningPower:
|
||||
self._checksum_address = NO_BLOCKCHAIN_CONNECTION
|
||||
if checksum_address:
|
||||
if checksum_public_address:
|
||||
# We'll take a checksum address, as long as it matches their singing key
|
||||
if not checksum_address == self.checksum_public_address:
|
||||
if not checksum_public_address == self.checksum_public_address:
|
||||
error = "Federated-only Characters derive their address from their Signing key; got {} instead."
|
||||
raise self.SuspiciousActivity(error.format(checksum_address))
|
||||
raise self.SuspiciousActivity(error.format(checksum_public_address))
|
||||
|
||||
#
|
||||
# Nicknames
|
||||
|
@ -254,7 +254,7 @@ class Character(Learner):
|
|||
with the public_material_bytes, and the resulting CryptoPowerUp instance
|
||||
consumed by the Character.
|
||||
|
||||
# TODO: Need to be federated only until we figure out the best way to get the checksum_address in here.
|
||||
# TODO: Need to be federated only until we figure out the best way to get the checksum_public_address in here.
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
@ -20,10 +20,12 @@ from collections import OrderedDict
|
|||
|
||||
import maya
|
||||
import requests
|
||||
import socket
|
||||
import time
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
from cryptography.x509 import load_pem_x509_certificate, Certificate
|
||||
from cryptography.x509 import load_pem_x509_certificate, Certificate, NameOID
|
||||
from eth_utils import to_checksum_address
|
||||
from functools import partial
|
||||
from twisted.internet import threads
|
||||
|
@ -33,17 +35,20 @@ from typing import List
|
|||
|
||||
from bytestring_splitter import BytestringSplitter, VariableLengthBytestring
|
||||
from constant_sorrow import constants
|
||||
from constant_sorrow.constants import PUBLIC_ONLY
|
||||
from nucypher.blockchain.eth.actors import PolicyAuthor, Miner
|
||||
from nucypher.blockchain.eth.agents import MinerAgent
|
||||
from nucypher.characters.base import Character, Learner
|
||||
from nucypher.config.storages import NodeStorage
|
||||
from nucypher.config.storages import NodeStorage, ForgetfulNodeStorage
|
||||
from nucypher.crypto.api import keccak_digest
|
||||
from nucypher.crypto.constants import PUBLIC_ADDRESS_LENGTH, PUBLIC_KEY_LENGTH
|
||||
from nucypher.crypto.powers import SigningPower, EncryptingPower, DelegatingPower, BlockchainPower
|
||||
from nucypher.keystore.keypairs import HostingKeypair
|
||||
from nucypher.network.middleware import RestMiddleware
|
||||
from nucypher.network.nodes import Teacher
|
||||
from nucypher.network.protocols import InterfaceInfo
|
||||
from nucypher.network.protocols import InterfaceInfo, parse_node_uri
|
||||
from nucypher.network.server import ProxyRESTServer, TLSHostingPower, ProxyRESTRoutes
|
||||
from nucypher.utilities.decorators import validate_checksum_address
|
||||
from umbral.keys import UmbralPublicKey
|
||||
from umbral.signing import Signature
|
||||
|
||||
|
@ -54,11 +59,11 @@ class Alice(Character, PolicyAuthor):
|
|||
def __init__(self, is_me=True, federated_only=False, network_middleware=None, *args, **kwargs) -> None:
|
||||
|
||||
policy_agent = kwargs.pop("policy_agent", None)
|
||||
checksum_address = kwargs.pop("checksum_address", None)
|
||||
checksum_address = kwargs.pop("checksum_public_address", None)
|
||||
Character.__init__(self,
|
||||
is_me=is_me,
|
||||
federated_only=federated_only,
|
||||
checksum_address=checksum_address,
|
||||
checksum_public_address=checksum_address,
|
||||
network_middleware=network_middleware,
|
||||
*args, **kwargs)
|
||||
|
||||
|
@ -455,7 +460,7 @@ class Ursula(Teacher, Character, Miner):
|
|||
timestamp=None,
|
||||
|
||||
# Blockchain
|
||||
checksum_address: str = None,
|
||||
checksum_public_address: str = None,
|
||||
|
||||
# Character
|
||||
password: str = None,
|
||||
|
@ -475,7 +480,7 @@ class Ursula(Teacher, Character, Miner):
|
|||
self._work_orders = list()
|
||||
Character.__init__(self,
|
||||
is_me=is_me,
|
||||
checksum_address=checksum_address,
|
||||
checksum_public_address=checksum_public_address,
|
||||
start_learning_now=start_learning_now,
|
||||
federated_only=federated_only,
|
||||
crypto_power=crypto_power,
|
||||
|
@ -493,7 +498,7 @@ class Ursula(Teacher, Character, Miner):
|
|||
# Staking Ursula
|
||||
#
|
||||
if not federated_only:
|
||||
Miner.__init__(self, is_me=is_me, checksum_address=checksum_address)
|
||||
Miner.__init__(self, is_me=is_me, checksum_address=checksum_public_address)
|
||||
|
||||
# Access staking node via node's transacting keys TODO: Better handle ephemeral staking self ursula
|
||||
blockchain_power = BlockchainPower(blockchain=self.blockchain, account=self.checksum_public_address)
|
||||
|
@ -602,7 +607,7 @@ class Ursula(Teacher, Character, Miner):
|
|||
return deployer
|
||||
|
||||
def rest_server_certificate(self): # TODO: relocate and use reference on TLS hosting power
|
||||
return self.get_deployer().cert.to_cryptography()
|
||||
return self.certificate
|
||||
|
||||
def __bytes__(self):
|
||||
|
||||
|
@ -627,6 +632,124 @@ class Ursula(Teacher, Character, Miner):
|
|||
# Alternate Constructors
|
||||
#
|
||||
|
||||
@classmethod
|
||||
def from_rest_url(cls,
|
||||
network_middleware: RestMiddleware,
|
||||
host: str,
|
||||
port: int,
|
||||
certificate_filepath,
|
||||
federated_only: bool = False,
|
||||
*args, **kwargs
|
||||
):
|
||||
|
||||
response = network_middleware.node_information(host, port, certificate_filepath=certificate_filepath)
|
||||
if not response.status_code == 200:
|
||||
raise RuntimeError("Got a bad response: {}".format(response))
|
||||
|
||||
stranger_ursula_from_public_keys = cls.from_bytes(response.content, federated_only=federated_only, *args, **kwargs)
|
||||
return stranger_ursula_from_public_keys
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_seednode_metadata(cls,
|
||||
seednode_metadata,
|
||||
*args,
|
||||
**kwargs):
|
||||
"""
|
||||
Essentially another deserialization method, but this one doesn't reconstruct a complete
|
||||
node from bytes; instead it's just enough to connect to and verify a node.
|
||||
"""
|
||||
|
||||
return cls.from_seed_and_stake_info(checksum_public_address=seednode_metadata.checksum_public_address,
|
||||
host=seednode_metadata.rest_host,
|
||||
port=seednode_metadata.rest_port,
|
||||
*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_teacher_uri(cls,
|
||||
federated_only: bool,
|
||||
teacher_uri: str,
|
||||
min_stake: int,
|
||||
) -> 'Ursula':
|
||||
|
||||
hostname, port, checksum_address = parse_node_uri(uri=teacher_uri)
|
||||
try:
|
||||
teacher = cls.from_seed_and_stake_info(host=hostname,
|
||||
port=port,
|
||||
federated_only=federated_only,
|
||||
checksum_public_address=checksum_address,
|
||||
minimum_stake=min_stake)
|
||||
|
||||
except (socket.gaierror, requests.exceptions.ConnectionError, ConnectionRefusedError):
|
||||
# self.log.warn("Can't connect to seed node. Will retry.")
|
||||
time.sleep(5) # TODO: Move this 5
|
||||
|
||||
else:
|
||||
return teacher
|
||||
|
||||
@classmethod
|
||||
@validate_checksum_address
|
||||
def from_seed_and_stake_info(cls,
|
||||
host: str,
|
||||
port: int,
|
||||
federated_only: bool,
|
||||
minimum_stake: int = 0,
|
||||
checksum_public_address: str = None,
|
||||
network_middleware: RestMiddleware = None,
|
||||
*args,
|
||||
**kwargs
|
||||
) -> 'Ursula':
|
||||
|
||||
#
|
||||
# WARNING: xxx Poison xxx
|
||||
# Let's learn what we can about the ... "seednode".
|
||||
#
|
||||
|
||||
if network_middleware is None:
|
||||
network_middleware = RestMiddleware()
|
||||
|
||||
# Fetch the hosts TLS certificate and read the common name
|
||||
certificate = network_middleware.get_certificate(host=host, port=port)
|
||||
real_host = certificate.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
|
||||
temp_node_storage = ForgetfulNodeStorage(federated_only=federated_only)
|
||||
certificate_filepath = temp_node_storage.store_host_certificate(host=real_host,
|
||||
certificate=certificate)
|
||||
# Load the host as a potential seed node
|
||||
potential_seed_node = cls.from_rest_url(
|
||||
host=real_host,
|
||||
port=port,
|
||||
network_middleware=network_middleware,
|
||||
certificate_filepath=certificate_filepath,
|
||||
federated_only=True,
|
||||
*args,
|
||||
**kwargs) # TODO: 466
|
||||
|
||||
if checksum_public_address:
|
||||
# Ensure this is the specific node we expected
|
||||
if not checksum_public_address == potential_seed_node.checksum_public_address:
|
||||
template = "This seed node has a different wallet address: {} (expected {}). Are you sure this is a seednode?"
|
||||
raise potential_seed_node.SuspiciousActivity(template.format(potential_seed_node.checksum_public_address,
|
||||
checksum_public_address))
|
||||
|
||||
# Check the node's stake (optional)
|
||||
if minimum_stake > 0:
|
||||
# TODO: check the blockchain to verify that address has more then minimum_stake. #511
|
||||
raise NotImplementedError("Stake checking is not implemented yet.")
|
||||
|
||||
# Verify the node's TLS certificate
|
||||
try:
|
||||
potential_seed_node.verify_node(
|
||||
network_middleware=network_middleware,
|
||||
accept_federated_only=federated_only,
|
||||
certificate_filepath=certificate_filepath)
|
||||
|
||||
except potential_seed_node.InvalidNode:
|
||||
raise # TODO: What if our seed node fails verification?
|
||||
|
||||
# OK - everyone get out
|
||||
temp_node_storage.forget()
|
||||
return potential_seed_node
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls,
|
||||
ursula_as_bytes: bytes,
|
||||
|
@ -684,7 +807,7 @@ class Ursula(Teacher, Character, Miner):
|
|||
},
|
||||
interface_signature=signature,
|
||||
timestamp=timestamp,
|
||||
checksum_address=to_checksum_address(public_address),
|
||||
checksum_public_address=to_checksum_address(public_address),
|
||||
certificate=certificate,
|
||||
rest_host=rest_info.host,
|
||||
rest_port=rest_info.port,
|
||||
|
@ -699,8 +822,8 @@ class Ursula(Teacher, Character, Miner):
|
|||
node_storage: NodeStorage,
|
||||
checksum_adress: str,
|
||||
federated_only: bool = False) -> 'Ursula':
|
||||
return node_storage.get(checksum_address=checksum_adress,
|
||||
federated_only=federated_only)
|
||||
|
||||
return node_storage.get(checksum_address=checksum_adress, federated_only=federated_only)
|
||||
|
||||
#
|
||||
# Properties
|
||||
|
@ -722,7 +845,6 @@ class Ursula(Teacher, Character, Miner):
|
|||
@property
|
||||
def rest_app(self):
|
||||
rest_app_on_server = self.rest_server.rest_app
|
||||
|
||||
if not rest_app_on_server:
|
||||
m = "This Ursula doesn't have a REST app attached. If you want one, init with is_me and attach_server."
|
||||
raise AttributeError(m)
|
||||
|
|
|
@ -58,7 +58,7 @@ class Vladimir(Ursula):
|
|||
rest_port=target_ursula.rest_information()[0].port,
|
||||
certificate=target_ursula.rest_server_certificate(),
|
||||
network_middleware=cls.network_middleware,
|
||||
checksum_address = cls.fraud_address,
|
||||
checksum_public_address = cls.fraud_address,
|
||||
######### Asshole.
|
||||
timestamp=target_ursula._timestamp,
|
||||
interface_signature=target_ursula._interface_signature_object,
|
||||
|
|
|
@ -26,7 +26,8 @@ from twisted.logger import globalLogPublisher
|
|||
|
||||
from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION, NO_PASSWORD
|
||||
from nucypher.blockchain.eth.constants import MIN_LOCKED_PERIODS, MAX_MINTING_PERIODS
|
||||
from nucypher.cli.painting import BANNER, paint_configuration
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.cli.painting import BANNER, paint_configuration, paint_known_nodes, paint_contract_status
|
||||
from nucypher.cli.protocol import UrsulaCommandProtocol
|
||||
from nucypher.cli.types import (
|
||||
EIP55_CHECKSUM_ADDRESS,
|
||||
|
@ -37,8 +38,6 @@ from nucypher.cli.types import (
|
|||
STAKE_DURATION
|
||||
)
|
||||
from nucypher.config.characters import UrsulaConfiguration
|
||||
from nucypher.config.constants import SEEDNODES
|
||||
from nucypher.network.nodes import Teacher
|
||||
from nucypher.utilities.logging import (
|
||||
logToSentry,
|
||||
getTextFileObserver,
|
||||
|
@ -127,80 +126,11 @@ def status(click_config, config_file):
|
|||
ursula_config.connect_to_blockchain(provider_uri=ursula_config.provider_uri)
|
||||
ursula_config.connect_to_contracts()
|
||||
|
||||
contract_payload = """
|
||||
# Contracts
|
||||
paint_contract_status(ursula_config=ursula_config, click_config=click_config)
|
||||
|
||||
| NuCypher ETH Contracts |
|
||||
|
||||
Provider URI ............. {provider_uri}
|
||||
Registry Path ............ {registry_filepath}
|
||||
|
||||
NucypherToken ............ {token}
|
||||
MinerEscrow .............. {escrow}
|
||||
PolicyManager ............ {manager}
|
||||
|
||||
""".format(provider_uri=ursula_config.blockchain.interface.provider_uri,
|
||||
registry_filepath=ursula_config.blockchain.interface.registry.filepath,
|
||||
token=ursula_config.token_agent.contract_address,
|
||||
escrow=ursula_config.miner_agent.contract_address,
|
||||
manager=ursula_config.policy_agent.contract_address,
|
||||
period=ursula_config.miner_agent.get_current_period())
|
||||
click.secho(contract_payload)
|
||||
|
||||
network_payload = """
|
||||
| Blockchain Network |
|
||||
|
||||
Current Period ........... {period}
|
||||
Gas Price ................ {gas_price}
|
||||
Active Staking Ursulas ... {ursulas}
|
||||
|
||||
""".format(period=click_config.miner_agent.get_current_period(),
|
||||
gas_price=click_config.blockchain.interface.w3.eth.gasPrice,
|
||||
ursulas=click_config.miner_agent.get_miner_population())
|
||||
click.secho(network_payload)
|
||||
|
||||
#
|
||||
# Known Nodes
|
||||
#
|
||||
|
||||
# Gather Data
|
||||
known_nodes = ursula_config.read_known_nodes()
|
||||
known_certificates = ursula_config.node_storage.all(certificates_only=True, federated_only=ursula_config.federated_only)
|
||||
number_of_known_nodes = len(known_nodes)
|
||||
seen_nodes = len(known_certificates)
|
||||
|
||||
# Operating Mode
|
||||
federated_only = ursula_config.federated_only
|
||||
if federated_only:
|
||||
click.secho("Configured in Federated Only mode", fg='green')
|
||||
|
||||
# Heading
|
||||
label = "Known Nodes (connected {} / seen {})".format(number_of_known_nodes, seen_nodes)
|
||||
heading = '\n' + label + " " * (45 - len(label)) + "Last Seen "
|
||||
click.secho(heading, bold=True, nl=False)
|
||||
|
||||
# Legend
|
||||
color_index = {
|
||||
'self': 'yellow',
|
||||
'known': 'white',
|
||||
'seednode': 'blue'
|
||||
}
|
||||
for node_type, color in color_index.items():
|
||||
click.secho('{0:<6} | '.format(node_type), fg=color, nl=False)
|
||||
click.echo('\n')
|
||||
|
||||
seednode_addresses = list(bn.checksum_address for bn in SEEDNODES)
|
||||
for node in known_nodes:
|
||||
row_template = "{} | {} | {}"
|
||||
node_type = 'known'
|
||||
if node.checksum_public_address == ursula_config.checksum_address:
|
||||
node_type = 'self'
|
||||
row_template += ' ({})'.format(node_type)
|
||||
if node.checksum_public_address in seednode_addresses:
|
||||
node_type = 'seednode'
|
||||
row_template += ' ({})'.format(node_type)
|
||||
click.secho(row_template.format(node.checksum_public_address,
|
||||
node.rest_url(),
|
||||
node.timestamp), fg=color_index[node_type])
|
||||
paint_known_nodes(ursula=ursula_config)
|
||||
|
||||
|
||||
@nucypher_cli.command()
|
||||
|
@ -301,7 +231,7 @@ def ursula(click_config,
|
|||
rest_port=rest_port,
|
||||
db_filepath=db_filepath,
|
||||
federated_only=federated_only,
|
||||
checksum_address=checksum_address,
|
||||
checksum_public_address=checksum_address,
|
||||
no_registry=federated_only or no_registry,
|
||||
registry_filepath=registry_filepath,
|
||||
provider_uri=provider_uri)
|
||||
|
@ -324,7 +254,7 @@ def ursula(click_config,
|
|||
poa=poa,
|
||||
registry_filepath=registry_filepath,
|
||||
provider_uri=provider_uri,
|
||||
checksum_address=checksum_address,
|
||||
checksum_public_address=checksum_address,
|
||||
federated_only=federated_only,
|
||||
rest_host=rest_host,
|
||||
rest_port=rest_port,
|
||||
|
@ -338,7 +268,7 @@ def ursula(click_config,
|
|||
# poa = poa,
|
||||
# registry_filepath = registry_filepath,
|
||||
# provider_uri = provider_uri,
|
||||
# checksum_address = checksum_address,
|
||||
# checksum_public_address = checksum_public_address,
|
||||
# federated_only = federated_only,
|
||||
# rest_host = rest_host,
|
||||
# rest_port = rest_port,
|
||||
|
@ -365,14 +295,13 @@ def ursula(click_config,
|
|||
#
|
||||
teacher_nodes = list()
|
||||
if teacher_uri:
|
||||
node = Teacher.from_teacher_uri(teacher_uri=teacher_uri,
|
||||
min_stake=min_stake,
|
||||
federated_only=federated_only)
|
||||
node = Ursula.from_teacher_uri(teacher_uri=teacher_uri, min_stake=min_stake, federated_only=federated_only)
|
||||
teacher_nodes.append(node)
|
||||
|
||||
#
|
||||
# Produce - Step 2
|
||||
#
|
||||
ursula = ursula_config.produce()
|
||||
ursula = ursula_config.produce(known_nodes=teacher_nodes)
|
||||
ursula_config.log.debug("Initialized Ursula {}".format(ursula), fg='green')
|
||||
|
||||
# GO!
|
||||
|
@ -488,7 +417,7 @@ def stake(click_config,
|
|||
|
||||
if not checksum_address:
|
||||
|
||||
if config.accounts == NO_BLOCKCHAIN_CONNECTION:
|
||||
if click_config.accounts == NO_BLOCKCHAIN_CONNECTION:
|
||||
click.echo('No account found.')
|
||||
raise click.Abort()
|
||||
|
||||
|
|
|
@ -47,6 +47,23 @@ BANNER = """
|
|||
# Paint
|
||||
#
|
||||
|
||||
def build_fleet_state_status(ursula) -> str:
|
||||
# Build FleetState status line
|
||||
if ursula.known_nodes.checksum is not NO_KNOWN_NODES:
|
||||
fleet_state_checksum = ursula.known_nodes.checksum[:7]
|
||||
fleet_state_nickname = ursula.known_nodes.nickname
|
||||
fleet_state_icon = ursula.known_nodes.icon
|
||||
fleet_state = '{checksum} ⇀{nickname}↽ {icon}'.format(icon=fleet_state_icon,
|
||||
nickname=fleet_state_nickname,
|
||||
checksum=fleet_state_checksum)
|
||||
elif ursula.known_nodes.checksum is not NO_KNOWN_NODES:
|
||||
fleet_state = 'No Known Nodes'
|
||||
else:
|
||||
fleet_state = 'Unknown'
|
||||
|
||||
return fleet_state
|
||||
|
||||
|
||||
def paint_configuration(config_filepath: str) -> None:
|
||||
json_config = UrsulaConfiguration._read_configuration_file(filepath=config_filepath)
|
||||
click.secho("\n======== Ursula Configuration ======== \n", bold=True)
|
||||
|
@ -68,15 +85,7 @@ def paint_node_status(ursula, start_time):
|
|||
teacher = 'Current Teacher ..... {}'.format(ursula._current_teacher_node)
|
||||
|
||||
# Build FleetState status line
|
||||
if ursula.known_nodes.checksum is not NO_KNOWN_NODES:
|
||||
fleet_state_checksum = ursula.known_nodes.checksum[:7]
|
||||
fleet_state_nickname = ursula.known_nodes.nickname
|
||||
fleet_state_icon = ursula.known_nodes.icon
|
||||
fleet_state = '{2} {1} ({0})'.format(fleet_state_checksum, fleet_state_nickname, fleet_state_icon)
|
||||
elif ursula.known_nodes.checksum is not NO_KNOWN_NODES:
|
||||
fleet_state = 'No Known Nodes'
|
||||
else:
|
||||
fleet_state = 'Unknown'
|
||||
fleet_state = build_fleet_state_status(ursula=ursula)
|
||||
|
||||
stats = ['⇀URSULA {}↽'.format(ursula.nickname_icon),
|
||||
'{}'.format(ursula),
|
||||
|
@ -108,8 +117,13 @@ def paint_known_nodes(ursula) -> None:
|
|||
|
||||
# Heading
|
||||
label = "Known Nodes (connected {} / seen {})".format(number_of_known_nodes, seen_nodes)
|
||||
heading = '\n' + label + " " * (45 - len(label)) + "Last Seen "
|
||||
click.secho(heading, bold=True, nl=False)
|
||||
heading = '\n' + label + " " * (45 - len(label))
|
||||
click.secho(heading, bold=True, nl=True)
|
||||
|
||||
# Build FleetState status line
|
||||
fleet_state = build_fleet_state_status(ursula=ursula)
|
||||
fleet_status_line = 'Fleet State {}'.format(fleet_state)
|
||||
click.secho(fleet_status_line, fg='blue', bold=True, nl=True)
|
||||
|
||||
# Legend
|
||||
color_index = {
|
||||
|
@ -117,14 +131,16 @@ def paint_known_nodes(ursula) -> None:
|
|||
'known': 'white',
|
||||
'seednode': 'blue'
|
||||
}
|
||||
for node_type, color in color_index.items():
|
||||
click.secho('{0:<6} | '.format(node_type), fg=color, nl=False)
|
||||
click.echo('\n')
|
||||
|
||||
# Ledgend
|
||||
# for node_type, color in color_index.items():
|
||||
# click.secho('{0:<6} | '.format(node_type), fg=color, nl=False)
|
||||
# click.echo('\n')
|
||||
|
||||
seednode_addresses = list(bn.checksum_address for bn in SEEDNODES)
|
||||
|
||||
for node in known_nodes:
|
||||
row_template = "{} | {} | {} | {} | {}"
|
||||
row_template = "{} | {}"
|
||||
node_type = 'known'
|
||||
if node.checksum_public_address == ursula.checksum_public_address:
|
||||
node_type = 'self'
|
||||
|
@ -132,9 +148,37 @@ def paint_known_nodes(ursula) -> None:
|
|||
elif node.checksum_public_address in seednode_addresses:
|
||||
node_type = 'seednode'
|
||||
row_template += ' ({})'.format(node_type)
|
||||
click.secho(row_template.format(node.checksum_public_address,
|
||||
node.rest_url().ljust(20),
|
||||
node.nickname.ljust(50),
|
||||
node.timestamp,
|
||||
node.last_seen,
|
||||
), fg=color_index[node_type])
|
||||
click.secho(row_template.format(node.rest_url().ljust(20), node), fg=color_index[node_type])
|
||||
|
||||
|
||||
def paint_contract_status(ursula_config, click_config):
|
||||
contract_payload = """
|
||||
|
||||
| NuCypher ETH Contracts |
|
||||
|
||||
Provider URI ............. {provider_uri}
|
||||
Registry Path ............ {registry_filepath}
|
||||
|
||||
NucypherToken ............ {token}
|
||||
MinerEscrow .............. {escrow}
|
||||
PolicyManager ............ {manager}
|
||||
|
||||
""".format(provider_uri=ursula_config.blockchain.interface.provider_uri,
|
||||
registry_filepath=ursula_config.blockchain.interface.registry.filepath,
|
||||
token=ursula_config.token_agent.contract_address,
|
||||
escrow=ursula_config.miner_agent.contract_address,
|
||||
manager=ursula_config.policy_agent.contract_address,
|
||||
period=ursula_config.miner_agent.get_current_period())
|
||||
click.secho(contract_payload)
|
||||
|
||||
network_payload = """
|
||||
| Blockchain Network |
|
||||
|
||||
Current Period ........... {period}
|
||||
Gas Price ................ {gas_price}
|
||||
Active Staking Ursulas ... {ursulas}
|
||||
|
||||
""".format(period=click_config.miner_agent.get_current_period(),
|
||||
gas_price=click_config.blockchain.interface.w3.eth.gasPrice,
|
||||
ursulas=click_config.miner_agent.get_miner_population())
|
||||
click.secho(network_payload)
|
||||
|
|
|
@ -24,6 +24,8 @@ import maya
|
|||
from twisted.internet import reactor
|
||||
from twisted.protocols.basic import LineReceiver
|
||||
|
||||
from nucypher.cli.painting import build_fleet_state_status
|
||||
|
||||
|
||||
class UrsulaCommandProtocol(LineReceiver):
|
||||
|
||||
|
@ -39,12 +41,20 @@ class UrsulaCommandProtocol(LineReceiver):
|
|||
|
||||
# Expose Ursula functional entry points
|
||||
self.__commands = {
|
||||
'stop': reactor.stop,
|
||||
'known_nodes': self.paintKnownNodes,
|
||||
|
||||
# Status
|
||||
'status': self.paintStatus,
|
||||
'known_nodes': self.paintKnownNodes,
|
||||
'fleet_state': self.paintFleetState,
|
||||
|
||||
# Learning Control
|
||||
'cycle_teacher': self.ursula.cycle_teacher_node,
|
||||
'start_learning': self.ursula.start_learning_loop,
|
||||
'stop_learning': self.ursula.stop_learning_loop
|
||||
'stop_learning': self.ursula.stop_learning_loop,
|
||||
|
||||
# Process Control
|
||||
'stop': reactor.stop,
|
||||
|
||||
}
|
||||
|
||||
super().__init__()
|
||||
|
@ -61,6 +71,10 @@ class UrsulaCommandProtocol(LineReceiver):
|
|||
from nucypher.cli.painting import paint_node_status
|
||||
paint_node_status(ursula=self.ursula, start_time=self.start_time)
|
||||
|
||||
def paintFleetState(self):
|
||||
line = '{}'.format(build_fleet_state_status(ursula=self.ursula))
|
||||
click.secho(line)
|
||||
|
||||
def connectionMade(self):
|
||||
|
||||
message = 'Attached {}@{}'.format(
|
||||
|
|
|
@ -25,7 +25,7 @@ from nucypher.blockchain.eth.constants import MIN_ALLOWED_LOCKED, MAX_MINTING_PE
|
|||
|
||||
|
||||
class ChecksumAddress(click.ParamType):
|
||||
name = 'checksum_address'
|
||||
name = 'checksum_public_address'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if is_checksum_address(value):
|
||||
|
|
|
@ -34,5 +34,5 @@ DEFAULT_CONFIG_ROOT = APP_DIR.user_data_dir
|
|||
USER_LOG_DIR = APP_DIR.user_log_dir
|
||||
|
||||
# Static Seednodes
|
||||
SeednodeMetadata = namedtuple('seednode', ['checksum_address', 'rest_host', 'rest_port'])
|
||||
SeednodeMetadata = namedtuple('seednode', ['checksum_public_address', 'rest_host', 'rest_port'])
|
||||
SEEDNODES = tuple()
|
||||
|
|
|
@ -47,6 +47,7 @@ from nucypher.config.keyring import NucypherKeyring
|
|||
from nucypher.config.storages import NodeStorage, ForgetfulNodeStorage, LocalFileBasedNodeStorage
|
||||
from nucypher.crypto.powers import CryptoPowerUp, CryptoPower
|
||||
from nucypher.network.middleware import RestMiddleware
|
||||
from nucypher.network.nodes import FleetStateTracker
|
||||
from umbral.signing import Signature
|
||||
|
||||
|
||||
|
@ -99,7 +100,7 @@ class NodeConfiguration(ABC):
|
|||
|
||||
# Identity
|
||||
is_me: bool = True,
|
||||
checksum_address: str = None,
|
||||
checksum_public_address: str = None,
|
||||
crypto_power: CryptoPower = None,
|
||||
|
||||
# Keyring
|
||||
|
@ -126,7 +127,7 @@ class NodeConfiguration(ABC):
|
|||
# Node Storage
|
||||
known_nodes: set = None,
|
||||
node_storage: NodeStorage = None,
|
||||
load_metadata: bool = True,
|
||||
reload_metadata: bool = True,
|
||||
save_metadata: bool = True,
|
||||
|
||||
# Blockchain
|
||||
|
@ -195,11 +196,11 @@ class NodeConfiguration(ABC):
|
|||
# Identity
|
||||
#
|
||||
self.is_me = is_me
|
||||
self.checksum_address = checksum_address
|
||||
self.checksum_public_address = checksum_public_address
|
||||
|
||||
if self.is_me is True or dev_mode is True:
|
||||
# Self
|
||||
if self.checksum_address and dev_mode is False:
|
||||
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:
|
||||
|
@ -214,12 +215,17 @@ class NodeConfiguration(ABC):
|
|||
#
|
||||
# Learner
|
||||
#
|
||||
self.known_nodes = known_nodes or set()
|
||||
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.load_metadata = load_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()
|
||||
|
||||
#
|
||||
# Blockchain
|
||||
|
@ -240,7 +246,7 @@ class NodeConfiguration(ABC):
|
|||
|
||||
# Ephemeral dev settings
|
||||
self.save_metadata = False
|
||||
self.load_metadata = False
|
||||
self.reload_metadata = False
|
||||
|
||||
# Generate one-time alphanumeric development password
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
|
@ -258,6 +264,10 @@ class NodeConfiguration(ABC):
|
|||
def dev_mode(self):
|
||||
return self.__dev_mode
|
||||
|
||||
@property
|
||||
def known_nodes(self):
|
||||
return self.__fleet_state
|
||||
|
||||
def connect_to_blockchain(self, provider_uri: str, poa: bool = False, compile_contracts: bool = False):
|
||||
if self.federated_only:
|
||||
raise NodeConfiguration.ConfigurationError("Cannot connect to blockchain in federated mode")
|
||||
|
@ -278,7 +288,10 @@ class NodeConfiguration(ABC):
|
|||
self.log.debug("Established connection to nucypher contracts")
|
||||
|
||||
def read_known_nodes(self):
|
||||
self.known_nodes.update(self.node_storage.all(federated_only=self.federated_only))
|
||||
known_nodes = self.node_storage.all(federated_only=self.federated_only)
|
||||
known_nodes = {node.checksum_public_address: node for node in known_nodes}
|
||||
self.known_nodes._nodes.update(known_nodes)
|
||||
self.known_nodes.record_fleet_state()
|
||||
return self.known_nodes
|
||||
|
||||
def forget_nodes(self) -> None:
|
||||
|
@ -389,7 +402,7 @@ class NodeConfiguration(ABC):
|
|||
# Identity
|
||||
is_me=self.is_me,
|
||||
federated_only=self.federated_only, # TODO: 466
|
||||
checksum_address=self.checksum_address,
|
||||
checksum_public_address=self.checksum_public_address,
|
||||
keyring_dir=self.keyring_dir,
|
||||
|
||||
# Behavior
|
||||
|
@ -403,8 +416,12 @@ class NodeConfiguration(ABC):
|
|||
@property
|
||||
def dynamic_payload(self, **overrides) -> dict:
|
||||
"""Exported dynamic configuration values for initializing Ursula"""
|
||||
if self.load_metadata:
|
||||
self.known_nodes.update(self.node_storage.all(federated_only=self.federated_only))
|
||||
if self.reload_metadata:
|
||||
known_nodes = self.node_storage.all(federated_only=self.federated_only)
|
||||
known_nodes = {node.checksum_public_address: node for node in known_nodes}
|
||||
self.known_nodes._nodes.update(known_nodes)
|
||||
self.known_nodes.record_fleet_state()
|
||||
|
||||
payload = dict(network_middleware=self.network_middleware or self.__DEFAULT_NETWORK_MIDDLEWARE_CLASS(),
|
||||
known_nodes=self.known_nodes,
|
||||
node_storage=self.node_storage,
|
||||
|
@ -503,15 +520,15 @@ class NodeConfiguration(ABC):
|
|||
|
||||
def attach_keyring(self, checksum_address: str = None, *args, **kwargs) -> None:
|
||||
if self.keyring is not NO_KEYRING_ATTACHED:
|
||||
if self.keyring.checksum_address != (checksum_address or self.checksum_address):
|
||||
if self.keyring.checksum_address != (checksum_address or self.checksum_public_address):
|
||||
raise self.ConfigurationError("There is already a keyring attached to this configuration.")
|
||||
return
|
||||
|
||||
if (checksum_address or self.checksum_address) is None:
|
||||
if (checksum_address or self.checksum_public_address) is None:
|
||||
raise self.ConfigurationError("No account specified to unlock keyring")
|
||||
|
||||
self.keyring = NucypherKeyring(keyring_root=self.keyring_dir, # type: str
|
||||
account=checksum_address or self.checksum_address, # type: str
|
||||
account=checksum_address or self.checksum_public_address, # type: str
|
||||
*args, **kwargs)
|
||||
|
||||
def write_keyring(self,
|
||||
|
@ -533,9 +550,9 @@ class NodeConfiguration(ABC):
|
|||
|
||||
# TODO: Operating mode switch #466
|
||||
if self.federated_only or not wallet:
|
||||
self.checksum_address = self.keyring.federated_address
|
||||
self.checksum_public_address = self.keyring.federated_address
|
||||
else:
|
||||
self.checksum_address = self.keyring.checksum_address
|
||||
self.checksum_public_address = self.keyring.checksum_address
|
||||
|
||||
return self.keyring
|
||||
|
||||
|
|
|
@ -250,8 +250,7 @@ class Learner:
|
|||
self.unresponsive_startup_nodes = list() # TODO: Attempt to use these again later
|
||||
for node in known_nodes:
|
||||
try:
|
||||
self.remember_node(
|
||||
node) # TODO: Need to test this better - do we ever init an Ursula-Learner with Node Storage?
|
||||
self.remember_node(node) # TODO: Need to test this better - do we ever init an Ursula-Learner with Node Storage?
|
||||
except self.UnresponsiveTeacher:
|
||||
self.unresponsive_startup_nodes.append(node)
|
||||
|
||||
|
@ -285,7 +284,7 @@ class Learner:
|
|||
def __attempt_seednode_learning(seednode_metadata, current_attempt=1):
|
||||
from nucypher.characters.lawful import Ursula
|
||||
self.log.debug(
|
||||
"Seeding from: {}|{}:{}".format(seednode_metadata.checksum_address,
|
||||
"Seeding from: {}|{}:{}".format(seednode_metadata.checksum_public_address,
|
||||
seednode_metadata.rest_host,
|
||||
seednode_metadata.rest_port))
|
||||
|
||||
|
@ -725,128 +724,6 @@ class Teacher:
|
|||
Raise when a Character tries to use another Character as decentralized when the latter is federated_only.
|
||||
"""
|
||||
|
||||
#
|
||||
# Alternate Constructors
|
||||
#
|
||||
|
||||
@classmethod
|
||||
def from_seednode_metadata(cls,
|
||||
seednode_metadata,
|
||||
*args,
|
||||
**kwargs):
|
||||
"""
|
||||
Essentially another deserialization method, but this one doesn't reconstruct a complete
|
||||
node from bytes; instead it's just enough to connect to and verify a node.
|
||||
"""
|
||||
|
||||
return cls.from_seed_and_stake_info(checksum_address=seednode_metadata.checksum_address,
|
||||
host=seednode_metadata.rest_host,
|
||||
port=seednode_metadata.rest_port,
|
||||
*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_teacher_uri(cls,
|
||||
federated_only: bool,
|
||||
teacher_uri: str,
|
||||
min_stake: int,
|
||||
) -> 'Ursula':
|
||||
|
||||
hostname, port, checksum_address = parse_node_uri(uri=teacher_uri)
|
||||
try:
|
||||
teacher = cls.from_seed_and_stake_info(host=hostname,
|
||||
port=port,
|
||||
federated_only=federated_only,
|
||||
checksum_address=checksum_address,
|
||||
minimum_stake=min_stake)
|
||||
|
||||
except (socket.gaierror, requests.exceptions.ConnectionError, ConnectionRefusedError):
|
||||
# self.log.warn("Can't connect to seed node. Will retry.")
|
||||
time.sleep(5) # TODO: Move this 5
|
||||
|
||||
else:
|
||||
return teacher
|
||||
|
||||
@classmethod
|
||||
@validate_checksum_address
|
||||
def from_seed_and_stake_info(cls,
|
||||
host: str,
|
||||
port: int,
|
||||
federated_only: bool,
|
||||
minimum_stake: int = 0,
|
||||
checksum_address: str = None,
|
||||
network_middleware: RestMiddleware = None,
|
||||
*args,
|
||||
**kwargs
|
||||
) -> 'Teacher':
|
||||
|
||||
#
|
||||
# WARNING: xxx Poison xxx
|
||||
# Let's learn what we can about the ... "seednode".
|
||||
#
|
||||
|
||||
if network_middleware is None:
|
||||
network_middleware = RestMiddleware()
|
||||
|
||||
# Fetch the hosts TLS certificate and read the common name
|
||||
certificate = network_middleware.get_certificate(host=host, port=port)
|
||||
real_host = certificate.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
|
||||
temp_node_storage = ForgetfulNodeStorage(federated_only=federated_only)
|
||||
certificate_filepath = temp_node_storage.store_host_certificate(host=real_host,
|
||||
certificate=certificate)
|
||||
# Load the host as a potential seed node
|
||||
potential_seed_node = cls.from_rest_url(
|
||||
host=real_host,
|
||||
port=port,
|
||||
network_middleware=network_middleware,
|
||||
certificate_filepath=certificate_filepath,
|
||||
federated_only=True,
|
||||
*args,
|
||||
**kwargs) # TODO: 466
|
||||
|
||||
if checksum_address:
|
||||
# Ensure this is the specific node we expected
|
||||
if not checksum_address == potential_seed_node.checksum_public_address:
|
||||
template = "This seed node has a different wallet address: {} (expected {}). Are you sure this is a seednode?"
|
||||
raise potential_seed_node.SuspiciousActivity(template.format(potential_seed_node.checksum_public_address,
|
||||
checksum_address))
|
||||
|
||||
# Check the node's stake (optional)
|
||||
if minimum_stake > 0:
|
||||
# TODO: check the blockchain to verify that address has more then minimum_stake. #511
|
||||
raise NotImplementedError("Stake checking is not implemented yet.")
|
||||
|
||||
# Verify the node's TLS certificate
|
||||
try:
|
||||
potential_seed_node.verify_node(
|
||||
network_middleware=network_middleware,
|
||||
accept_federated_only=federated_only,
|
||||
certificate_filepath=certificate_filepath)
|
||||
|
||||
except potential_seed_node.InvalidNode:
|
||||
raise # TODO: What if our seed node fails verification?
|
||||
|
||||
# OK - everyone get out
|
||||
temp_node_storage.forget()
|
||||
return potential_seed_node
|
||||
|
||||
@classmethod
|
||||
def from_rest_url(cls,
|
||||
network_middleware: RestMiddleware,
|
||||
host: str,
|
||||
port: int,
|
||||
certificate_filepath,
|
||||
federated_only: bool = False,
|
||||
*args, **kwargs
|
||||
):
|
||||
|
||||
response = network_middleware.node_information(host, port, certificate_filepath=certificate_filepath)
|
||||
if not response.status_code == 200:
|
||||
raise RuntimeError("Got a bad response: {}".format(response))
|
||||
|
||||
stranger_ursula_from_public_keys = cls.from_bytes(response.content, federated_only=federated_only, *args,
|
||||
**kwargs)
|
||||
return stranger_ursula_from_public_keys
|
||||
|
||||
@classmethod
|
||||
def from_tls_hosting_power(cls, tls_hosting_power: TLSHostingPower, *args, **kwargs) -> 'Teacher':
|
||||
certificate_filepath = tls_hosting_power.keypair.certificate_filepath
|
||||
|
|
|
@ -83,7 +83,7 @@ def make_decentralized_ursulas(ursula_config: UrsulaConfiguration,
|
|||
ursulas = set()
|
||||
for port, checksum_address in enumerate(ether_addresses, start=starting_port):
|
||||
|
||||
ursula = ursula_config.produce(checksum_address=checksum_address,
|
||||
ursula = ursula_config.produce(checksum_public_address=checksum_address,
|
||||
db_filepath=MOCK_URSULA_DB_FILEPATH,
|
||||
rest_port=port + 100,
|
||||
**ursula_overrides)
|
||||
|
|
|
@ -140,7 +140,7 @@ def test_alices_powers_are_persistent(federated_ursulas, tmpdir):
|
|||
start_learning_now=False,
|
||||
federated_only=True,
|
||||
save_metadata=False,
|
||||
load_metadata=False)
|
||||
reload_metadata=False)
|
||||
|
||||
# Generate keys and write them the disk
|
||||
alice_config.initialize(password=INSECURE_DEVELOPMENT_PASSWORD)
|
||||
|
|
|
@ -99,7 +99,7 @@ def test_character_blockchain_power(testerchain):
|
|||
sig_privkey = testerchain.interface.providers[0].ethereum_tester.backend._key_lookup[eth_utils.to_canonical_address(eth_address)]
|
||||
sig_pubkey = sig_privkey.public_key
|
||||
|
||||
signer = Character(is_me=True, checksum_address=eth_address)
|
||||
signer = Character(is_me=True, checksum_public_address=eth_address)
|
||||
signer._crypto_power.consume_power_up(BlockchainPower(testerchain, eth_address))
|
||||
|
||||
# Due to testing backend, the account is already unlocked.
|
||||
|
|
|
@ -59,6 +59,6 @@ def test_empty_federated_status(click_runner, custom_filepath):
|
|||
assert result.exit_code == 0
|
||||
|
||||
assert 'Federated Only' in result.output
|
||||
heading = 'Known Nodes (connected 0 / seen 0) Last Seen self | known | seednode |'
|
||||
heading = 'Known Nodes (connected 0 / seen 0)'
|
||||
assert heading in result.output
|
||||
assert 'password' not in result.output
|
||||
|
|
|
@ -128,7 +128,7 @@ def ursula_federated_test_config():
|
|||
federated_only=True,
|
||||
network_middleware=MockRestMiddleware(),
|
||||
save_metadata=False,
|
||||
load_metadata=False)
|
||||
reload_metadata=False)
|
||||
yield ursula_config
|
||||
ursula_config.cleanup()
|
||||
|
||||
|
@ -145,7 +145,7 @@ def ursula_decentralized_test_config(three_agents):
|
|||
network_middleware=MockRestMiddleware(),
|
||||
import_seed_registry=False,
|
||||
save_metadata=False,
|
||||
load_metadata=False)
|
||||
reload_metadata=False)
|
||||
yield ursula_config
|
||||
ursula_config.cleanup()
|
||||
|
||||
|
@ -159,7 +159,7 @@ def alice_federated_test_config(federated_ursulas):
|
|||
federated_only=True,
|
||||
abort_on_learning_error=True,
|
||||
save_metadata=False,
|
||||
load_metadata=False)
|
||||
reload_metadata=False)
|
||||
yield config
|
||||
config.cleanup()
|
||||
|
||||
|
@ -171,13 +171,13 @@ def alice_blockchain_test_config(blockchain_ursulas, three_agents):
|
|||
|
||||
config = AliceConfiguration(dev_mode=True,
|
||||
is_me=True,
|
||||
checksum_address=alice_address,
|
||||
checksum_public_address=alice_address,
|
||||
network_middleware=MockRestMiddleware(),
|
||||
known_nodes=blockchain_ursulas,
|
||||
abort_on_learning_error=True,
|
||||
import_seed_registry=False,
|
||||
save_metadata=False,
|
||||
load_metadata=False)
|
||||
reload_metadata=False)
|
||||
yield config
|
||||
config.cleanup()
|
||||
|
||||
|
@ -190,7 +190,7 @@ def bob_federated_test_config():
|
|||
abort_on_learning_error=True,
|
||||
federated_only=True,
|
||||
save_metadata=False,
|
||||
load_metadata=False)
|
||||
reload_metadata=False)
|
||||
yield config
|
||||
config.cleanup()
|
||||
|
||||
|
@ -201,7 +201,7 @@ def bob_blockchain_test_config(blockchain_ursulas, three_agents):
|
|||
etherbase, alice_address, bob_address, *everyone_else = token_agent.blockchain.interface.w3.eth.accounts
|
||||
|
||||
config = BobConfiguration(dev_mode=True,
|
||||
checksum_address=bob_address,
|
||||
checksum_public_address=bob_address,
|
||||
network_middleware=MockRestMiddleware(),
|
||||
known_nodes=blockchain_ursulas,
|
||||
start_learning_now=False,
|
||||
|
@ -209,7 +209,7 @@ def bob_blockchain_test_config(blockchain_ursulas, three_agents):
|
|||
federated_only=False,
|
||||
import_seed_registry=False,
|
||||
save_metadata=False,
|
||||
load_metadata=False)
|
||||
reload_metadata=False)
|
||||
yield config
|
||||
config.cleanup()
|
||||
|
||||
|
|
Loading…
Reference in New Issue