Merge pull request #1048 from KPrasch/verify

Decentralized Identity Verification Fixes and Naming
pull/951/head
David Núñez 2019-06-05 17:24:51 +02:00 committed by GitHub
commit 6f1fa31aa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 204 additions and 142 deletions

View File

@ -717,7 +717,7 @@ class Ursula(Teacher, Character, Miner):
timestamp=None,
# Blockchain
identity_evidence: bytes = constants.NOT_SIGNED,
decentralized_identity_evidence: bytes = constants.NOT_SIGNED,
checksum_public_address: str = None,
# Character
@ -765,12 +765,12 @@ class Ursula(Teacher, Character, Miner):
if not federated_only:
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
# Access staking node via node's transacting keys TODO: Better handling of ephemeral staking self ursula?
blockchain_power = BlockchainPower(blockchain=self.blockchain, account=self.checksum_public_address)
self._crypto_power.consume_power_up(blockchain_power)
# Use blockchain power to substantiate stamp, instead of signing key
self.substantiate_stamp(password=password) # TODO: Derive from keyring
self.substantiate_stamp(client_password=password) # TODO: Derive from keyring
#
# ProxyRESTServer and TLSHostingPower # TODO: Maybe we want _power_ups to be public after all?
@ -841,7 +841,7 @@ class Ursula(Teacher, Character, Miner):
certificate_filepath=certificate_filepath,
interface_signature=interface_signature,
timestamp=timestamp,
identity_evidence=identity_evidence,
decentralized_identity_evidence=decentralized_identity_evidence,
substantiate_immediately=is_me and not federated_only,
# FIXME: When is_me and not federated_only, the stamp is substantiated twice
# See line 728 above.
@ -880,7 +880,7 @@ class Ursula(Teacher, Character, Miner):
version = self.TEACHER_VERSION.to_bytes(2, "big")
interface_info = VariableLengthBytestring(bytes(self.rest_information()[0]))
identity_evidence = VariableLengthBytestring(self._identity_evidence)
decentralized_identity_evidence = VariableLengthBytestring(self.decentralized_identity_evidence)
certificate = self.rest_server_certificate()
cert_vbytes = VariableLengthBytestring(certificate.public_bytes(Encoding.PEM))
@ -891,7 +891,7 @@ class Ursula(Teacher, Character, Miner):
bytes(VariableLengthBytestring.bundle(domains)),
self.timestamp_bytes(),
bytes(self._interface_signature),
bytes(identity_evidence),
bytes(decentralized_identity_evidence),
bytes(self.public_keys(SigningPower)),
bytes(self.public_keys(DecryptingPower)),
bytes(cert_vbytes),
@ -1041,7 +1041,7 @@ class Ursula(Teacher, Character, Miner):
domains=VariableLengthBytestring,
timestamp=(int, 4, {'byteorder': 'big'}),
interface_signature=Signature,
identity_evidence=VariableLengthBytestring,
decentralized_identity_evidence=VariableLengthBytestring,
verifying_key=(UmbralPublicKey, PUBLIC_KEY_LENGTH),
encrypting_key=(UmbralPublicKey, PUBLIC_KEY_LENGTH),
certificate=(load_pem_x509_certificate, VariableLengthBytestring, {"backend": default_backend()}),

View File

@ -58,10 +58,10 @@ class Vladimir(Ursula):
rest_port=target_ursula.rest_information()[0].port,
certificate=target_ursula.rest_server_certificate(),
network_middleware=cls.network_middleware,
checksum_public_address = cls.fraud_address,
checksum_public_address=cls.fraud_address,
######### Asshole.
timestamp=target_ursula._timestamp,
interface_signature=target_ursula._interface_signature_object,
interface_signature=target_ursula._interface_signature,
#########
)

View File

@ -84,29 +84,29 @@ def keccak_digest(*messages: bytes) -> bytes:
:rtype: bytes
:return: bytestring of digested data
"""
hash = sha3.keccak_256()
_hash = sha3.keccak_256()
for message in messages:
hash.update(message)
return hash.digest()
_hash.update(message)
return _hash.digest()
def ecdsa_sign(message: bytes,
privkey: UmbralPrivateKey
private_key: UmbralPrivateKey
) -> bytes:
"""
Accepts a hashed message and signs it with the private key given.
:param message: Message to hash and sign
:param privkey: Private key to sign with
:param private_key: Private key to sign with
:return: signature
"""
signing_key = privkey.to_cryptography_privkey()
signing_key = private_key.to_cryptography_privkey()
signature_der_bytes = signing_key.sign(message, ec.ECDSA(SHA256))
return signature_der_bytes
def verify_eip_191(address: object, message: object, signature: object) -> object:
def verify_eip_191(address: str, message: bytes, signature: bytes) -> bool:
"""
EIP-191 Compatible signature verification for usage with w3.eth.sign.
"""
@ -115,15 +115,12 @@ def verify_eip_191(address: object, message: object, signature: object) -> objec
recovered_address = to_checksum_address(recovery)
signature_is_valid = recovered_address == to_checksum_address(address)
if signature_is_valid:
return True
else:
raise InvalidSignature
return signature_is_valid
def ecdsa_verify(message: bytes,
def verify_ecdsa(message: bytes,
signature: bytes,
pubkey: UmbralPublicKey
public_key: UmbralPublicKey
) -> bool:
"""
Accepts a message and signature and verifies it with the
@ -131,11 +128,11 @@ def ecdsa_verify(message: bytes,
:param message: Message to verify
:param signature: Signature to verify
:param pubkey: UmbralPublicKey to verify signature with
:param public_key: UmbralPublicKey to verify signature with
:return: True if valid, False if invalid.
"""
cryptography_pub_key = pubkey.to_cryptography_pubkey()
cryptography_pub_key = public_key.to_cryptography_pubkey()
try:
cryptography_pub_key.verify(
@ -170,7 +167,6 @@ def generate_self_signed_certificate(host: str,
cert = cert.serial_number(x509.random_serial_number())
cert = cert.not_valid_before(now)
cert = cert.not_valid_after(now + datetime.timedelta(days=days_valid))
# TODO: What are we going to do about domain name here? 179
cert = cert.add_extension(x509.SubjectAlternativeName([x509.IPAddress(IPv4Address(host))]), critical=False)
cert = cert.sign(private_key, hashes.SHA512(), default_backend())

View File

@ -35,7 +35,8 @@ from constant_sorrow.constants import (
NEVER_SEEN,
NO_STORAGE_AVAILIBLE,
FLEET_STATES_MATCH,
CERTIFICATE_NOT_SAVED
CERTIFICATE_NOT_SAVED,
UNKNOWN_FLEET_STATE
)
from cryptography.x509 import Certificate
from requests.exceptions import SSLError
@ -46,7 +47,7 @@ from twisted.logger import Logger
from nucypher.config.constants import SeednodeMetadata
from nucypher.config.storages import ForgetfulNodeStorage
from nucypher.crypto.api import keccak_digest, verify_eip_191
from nucypher.crypto.api import keccak_digest, verify_eip_191, verify_ecdsa
from nucypher.crypto.powers import BlockchainPower, SigningPower, DecryptingPower, NoSigningPower
from nucypher.crypto.signing import signature_splitter
from nucypher.network import LEARNING_LOOP_VERSION
@ -710,9 +711,6 @@ class Learner:
#
try:
# TODO: Streamline path generation
# FIXME: unused name "certificate filepath"
certificate_filepath = self.node_storage.generate_certificate_filepath(checksum_address=current_teacher.checksum_public_address)
response = self.network_middleware.get_nodes_via_rest(node=current_teacher,
nodes_i_need=self._node_ids_to_learn_about_immediately,
@ -796,7 +794,6 @@ class Learner:
#
certificate_filepath = self.node_storage.store_node_certificate(certificate=node.certificate)
try:
if eager:
node.verify_node(self.network_middleware,
@ -807,11 +804,27 @@ class Learner:
else:
node.validate_metadata(accept_federated_only=self.federated_only) # TODO: 466
#
# Report Failure
#
except NodeSeemsToBeDown as e:
self.log.info(f"Can't connect to {node} to verify it right now.")
self.log.info(f"Verification Failed - "
f"Cannot establish connection to {node}.")
except node.StampNotSigned:
self.log.warn(f'Verification Failed - '
f'{node} stamp is unsigned.')
except node.NotStaking:
self.log.warn(f'Verification Failed - '
f'{node} has no active stakes in the current period ({self.miner_agent.get_current_period()}')
except node.InvalidWalletSignature:
self.log.warn(f'Verification Failed - '
f'{node} has an invalid wallet signature for {node.checksum_public_address}')
except node.InvalidNode:
# TODO: Account for possibility that stamp, rather than interface, was bad.
self.log.warn(node.invalid_metadata_message.format(node))
except node.SuspiciousActivity:
@ -819,19 +832,25 @@ class Learner:
f"Propagated by: {current_teacher}"
self.log.warn(message)
#
# Success
#
else:
# Success
new = self.remember_node(node, record_fleet_state=False)
if new:
new_nodes.append(node)
self._adjust_learning(new_nodes)
#
# Continue
#
self._adjust_learning(new_nodes)
learning_round_log_message = "Learning round {}. Teacher: {} knew about {} nodes, {} were new."
self.log.info(learning_round_log_message.format(self._learning_round,
current_teacher,
len(node_list),
len(new_nodes)), )
len(new_nodes)))
if new_nodes:
self.known_nodes.record_fleet_state()
for node in new_nodes:
@ -840,10 +859,8 @@ class Learner:
class Teacher:
TEACHER_VERSION = LEARNING_LOOP_VERSION
verified_stamp = False
verified_interface = False
_verified_node = False
_interface_info_splitter = (int, 4, {'byteorder': 'big'})
log = Logger("teacher")
__DEFAULT_MIN_SEED_STAKE = 0
@ -854,38 +871,63 @@ class Teacher:
certificate_filepath: str,
interface_signature=NOT_SIGNED.bool_value(False),
timestamp=NOT_SIGNED,
identity_evidence=NOT_SIGNED,
decentralized_identity_evidence=NOT_SIGNED,
substantiate_immediately=False,
password=None,
) -> None:
#
# Fleet
#
self.serving_domains = domains
self.certificate = certificate
self.certificate_filepath = certificate_filepath
self._interface_signature_object = interface_signature
self._timestamp = timestamp
self.last_seen = NEVER_SEEN("No Connection to Node")
self.fleet_state_checksum = None
self.fleet_state_updated = None
self._identity_evidence = constant_or_bytes(identity_evidence)
self.last_seen = NEVER_SEEN("No Connection to Node")
self.fleet_state_icon = UNKNOWN_FLEET_STATE
self.fleet_state_nickname = UNKNOWN_FLEET_STATE
self.fleet_state_nickname_metadata = UNKNOWN_FLEET_STATE
#
# Identity
#
self._timestamp = timestamp
self.certificate = certificate
self.certificate_filepath = certificate_filepath
self.__interface_signature = interface_signature
self.__decentralized_identity_evidence = constant_or_bytes(decentralized_identity_evidence)
# Assume unverified
self.verified_stamp = False
self.verified_worker = False
self.verified_interface = False
self.verified_node = False
if substantiate_immediately:
self.substantiate_stamp(password=password) # TODO: Derive from keyring
self.substantiate_stamp(client_password=password)
class InvalidNode(SuspiciousActivity):
"""
Raised when a node has an invalid characteristic - stamp, interface, or address.
"""
"""Raised when a node has an invalid characteristic - stamp, interface, or address."""
class InvalidStamp(InvalidNode):
"""Base exception class for invalid character stamps"""
class StampNotSigned(InvalidStamp):
"""Raised when a node does not have a stamp signature when one is required for verification"""
class InvalidWalletSignature(InvalidStamp):
"""Raised when a stamp fails signature verification or recovers an unexpected wallet address"""
class NotStaking(InvalidStamp):
"""Raised when a node fails verification because it is not currently staking"""
class WrongMode(TypeError):
"""
Raised when a Character tries to use another Character as decentralized when the latter is federated_only.
"""
"""Raised when a Character tries to use another Character as decentralized when the latter is federated_only."""
class IsFromTheFuture(TypeError):
"""
Raised when deserializing a Character from a future version.
"""
"""Raised when deserializing a Character from a future version."""
@classmethod
def from_tls_hosting_power(cls, tls_hosting_power: TLSHostingPower, *args, **kwargs) -> 'Teacher':
@ -910,8 +952,17 @@ class Teacher:
return sorted(nodes_to_consider, key=lambda n: n.checksum_public_address)
def update_snapshot(self, checksum, updated, number_of_known_nodes):
# We update the simple snapshot here, but of course if we're dealing with an instance that is also a Learner, it has
# its own notion of its FleetState, so we probably need a reckoning of sorts here to manage that. In time.
"""
TODO: We update the simple snapshot here, but of course if we're dealing
with an instance that is also a Learner, it has
its own notion of its FleetState, so we probably
need a reckoning of sorts here to manage that. In time.
:param checksum:
:param updated:
:param number_of_known_nodes:
:return:
"""
self.fleet_state_nickname, self.fleet_state_nickname_metadata = nickname_from_seed(checksum, number_of_pairs=1)
self.fleet_state_checksum = checksum
self.fleet_state_updated = updated
@ -923,17 +974,16 @@ class Teacher:
# Stamp
#
def _stamp_has_valid_wallet_signature(self):
"""Offline Signature Verification"""
signature_bytes = self._identity_evidence
if signature_bytes is NOT_SIGNED:
def _stamp_has_valid_wallet_signature(self) -> bool:
"""Off-chain Signature Verification of ethereum client signature of stamp"""
if self.__decentralized_identity_evidence is NOT_SIGNED:
return False
signature_is_valid = verify_eip_191(message=bytes(self.stamp),
signature=signature_bytes,
signature=self.__decentralized_identity_evidence,
address=self.checksum_public_address)
return signature_is_valid
def _is_valid_worker(self):
def _is_valid_worker(self) -> bool:
"""
This method assumes the stamp's signature is valid and accurate.
As a follow-up, validate the Staker and Worker on-chain.
@ -941,45 +991,35 @@ class Teacher:
TODO: #1033 - Verify Staker <-> Worker relationship on-chain
"""
locked_tokens = self.miner_agent.get_locked_tokens(miner_address=self.checksum_public_address)
if not locked_tokens:
raise self.InvalidNode(f"{self.checksum_public_address} has no active stakes.")
return locked_tokens > 0
def stamp_is_valid(self, verify_staking: bool = True) -> bool:
"""Offline-only check of valid stamp signature"""
def validate_stamp(self, verify_staking: bool = True) -> None:
#
# Federated
#
if self.federated_only:
if self._identity_evidence is not NOT_SIGNED:
message = "This node cannot be verified in this manner, " \
"but is OK to use in federated mode if you " \
"have reason to believe it is trustworthy."
raise self.WrongMode(message)
message = "This node cannot be verified in this manner, " \
"but is OK to use in federated mode if you " \
"have reason to believe it is trustworthy."
raise self.WrongMode(message)
#
# Decentralized
#
else:
if self._identity_evidence is NOT_SIGNED:
raise self.InvalidNode
if self.__decentralized_identity_evidence is NOT_SIGNED:
raise self.StampNotSigned
# Off-chain signature verification
if self._stamp_has_valid_wallet_signature():
self.verified_stamp = True
return True
if not self._stamp_has_valid_wallet_signature():
raise self.InvalidWalletSignature
# On-chain staking check
if verify_staking:
self._is_valid_worker()
if self._is_valid_worker(): # <-- Blockchain CALL
self.verified_worker = True
else:
raise self.NotStaking
def verify_id(self, ursula_id, digest_factory=bytes):
self.verify()
if not ursula_id == digest_factory(self.canonical_public_address):
raise self.InvalidNode
self.verified_stamp = True
def validate_metadata(self,
accept_federated_only: bool = False,
@ -987,7 +1027,7 @@ class Teacher:
# Verify the interface signature
if not self.verified_interface:
self.interface_is_valid()
self.validate_interface()
# Verify the identity evidence
if self.verified_stamp:
@ -995,7 +1035,7 @@ class Teacher:
# Offline check of valid stamp signature by worker
try:
self.stamp_is_valid(verify_staking=verify_staking)
self.validate_stamp(verify_staking=verify_staking)
except self.WrongMode:
if not accept_federated_only:
raise
@ -1021,56 +1061,61 @@ class Teacher:
"""
# Only perform this check once per object
if not force:
if self._verified_node:
return True
if not force and self.verified_node:
return True
# This is both the stamp's client signature and interface metadata check.
# This is both the stamp's client signature and interface metadata check; May raise InvalidNode
self.validate_metadata(accept_federated_only=accept_federated_only)
# The node's metadata is valid; let's be sure the interface is in order.
if not certificate_filepath:
if self.certificate_filepath is CERTIFICATE_NOT_SAVED:
raise TypeError("We haven't saved a certificate for this node yet.")
else:
certificate_filepath = self.certificate_filepath
# The node's metadata is valid; let's be sure the interface is in order.
response_data = network_middleware.node_information(host=self.rest_information()[0].host,
port=self.rest_information()[0].port,
certificate_filepath=certificate_filepath)
version, node_bytes = self.version_splitter(response_data, return_remainder=True)
node_details = self.internal_splitter(node_bytes)
# TODO: #589 - check timestamp here.
verifying_keys_match = node_details['verifying_key'] == self.public_keys(SigningPower)
encrypting_keys_match = node_details['encrypting_key'] == self.public_keys(DecryptingPower)
addresses_match = node_details['public_address'] == self.canonical_public_address
evidence_matches = node_details['identity_evidence'] == self._identity_evidence
evidence_matches = node_details['decentralized_identity_evidence'] == self.__decentralized_identity_evidence
if not all((encrypting_keys_match, verifying_keys_match, addresses_match, evidence_matches)):
# TODO: #355 - Optional reporting.
# Failure
if not addresses_match:
self.log.warn("Wallet address swapped out. It appears that someone is trying to defraud this node.")
if not verifying_keys_match:
self.log.warn("Verifying key swapped out. It appears that someone is impersonating this node.")
raise self.InvalidNode("Wrong cryptographic material for this node - something fishy going on.")
else:
self._verified_node = True
def substantiate_stamp(self, password: str):
# TODO: #355 - Optional reporting.
raise self.InvalidNode("Wrong cryptographic material for this node - something fishy going on.")
else:
# Success
self.verified_node = True
@property
def decentralized_identity_evidence(self):
return self.__decentralized_identity_evidence
def substantiate_stamp(self, client_password: str):
blockchain_power = self._crypto_power.power_ups(BlockchainPower)
blockchain_power.unlock_account(password=password) # TODO: #349
blockchain_power.unlock_account(password=client_password) # TODO: #349
signature = blockchain_power.sign_message(bytes(self.stamp))
self._identity_evidence = signature
self.__decentralized_identity_evidence = signature
#
# Interface
#
def interface_is_valid(self) -> bool:
def validate_interface(self) -> bool:
"""
Checks that the interface info is valid for this node's canonical address.
"""
@ -1090,16 +1135,16 @@ class Teacher:
def _sign_and_date_interface_info(self):
message = self._signable_interface_info_message()
self._timestamp = maya.now()
self._interface_signature_object = self.stamp(self.timestamp_bytes() + message)
self.__interface_signature = self.stamp(self.timestamp_bytes() + message)
@property
def _interface_signature(self):
if not self._interface_signature_object:
if not self.__interface_signature:
try:
self._sign_and_date_interface_info()
except NoSigningPower:
raise NoSigningPower("This Ursula is a stranger and cannot be used to verify.")
return self._interface_signature_object
return self.__interface_signature
@property
def timestamp(self):

View File

@ -71,7 +71,7 @@ def test_actor_with_signing_power_can_sign():
# ...or to get the signer's public key for verification purposes.
# (note: we use the private _der_encoded_bytes here to test directly against the API, instead of Character)
verification = api.ecdsa_verify(message, signature._der_encoded_bytes(),
verification = api.verify_ecdsa(message, signature._der_encoded_bytes(),
stamp_of_the_signer.as_umbral_pubkey())
assert verification is True
@ -138,10 +138,10 @@ def test_character_blockchain_power(testerchain, three_agents):
assert is_verified is True
# Test a bad address/pubkey pair
with pytest.raises(InvalidSignature):
verify_eip_191(address=testerchain.interface.w3.eth.accounts[1],
message=data_to_sign,
signature=sig)
is_verified = verify_eip_191(address=testerchain.interface.w3.eth.accounts[1],
message=data_to_sign,
signature=sig)
assert is_verified is False
# Test a signature without unlocking the account
power.is_unlocked = False

View File

@ -53,7 +53,7 @@ def test_new_federated_ursula_announces_herself(ursula_federated_test_config):
def test_blockchain_ursula_substantiates_stamp(blockchain_ursulas):
first_ursula = list(blockchain_ursulas)[0]
signature_as_bytes = first_ursula._identity_evidence
signature_as_bytes = first_ursula.decentralized_identity_evidence
signature_as_bytes = to_standard_signature_bytes(signature_as_bytes)
assert verify_eip_191(address=first_ursula.checksum_public_address,
message=bytes(first_ursula.stamp),
@ -68,7 +68,7 @@ def test_blockchain_ursula_verifies_stamp(blockchain_ursulas):
# This Ursula does not yet have a verified stamp
first_ursula.verified_stamp = False
first_ursula.stamp_is_valid()
first_ursula.validate_stamp()
# ...but now it's verified.
assert first_ursula.verified_stamp
@ -81,14 +81,15 @@ def test_vladimir_cannot_verify_interface_with_ursulas_signing_key(blockchain_ur
# so that Alice (or whomever) pays him instead of Ursula, even though Ursula is providing the service.
# He finds a target and verifies that its interface is valid.
assert his_target.interface_is_valid()
assert his_target.validate_interface()
# Now Vladimir imitates Ursula - copying her public keys and interface info, but inserting his ether address.
vladimir = Vladimir.from_target_ursula(his_target, claim_signing_key=True)
# Vladimir can substantiate the stamp using his own ether address...
vladimir.substantiate_stamp(password=INSECURE_DEVELOPMENT_PASSWORD)
vladimir.stamp_is_valid()
vladimir.substantiate_stamp(client_password=INSECURE_DEVELOPMENT_PASSWORD)
vladimir._is_valid_worker = lambda: True
vladimir.validate_stamp()
# Now, even though his public signing key matches Ursulas...
assert vladimir.stamp == his_target.stamp
@ -96,7 +97,7 @@ def test_vladimir_cannot_verify_interface_with_ursulas_signing_key(blockchain_ur
# ...he is unable to pretend that his interface is valid
# because the interface validity check contains the canonical public address as part of its message.
with pytest.raises(vladimir.InvalidNode):
vladimir.interface_is_valid()
vladimir.validate_interface()
# Consequently, the metadata as a whole is also invalid.
with pytest.raises(vladimir.InvalidNode):
@ -109,19 +110,15 @@ def test_vladimir_uses_his_own_signing_key(blockchain_alice, blockchain_ursulas)
using his own signing key, which he claims is Ursula's.
"""
his_target = list(blockchain_ursulas)[4]
fraudulent_keys = CryptoPower(power_ups=Ursula._default_crypto_powerups) # TODO: Why is this unused?
vladimir = Vladimir.from_target_ursula(target_ursula=his_target)
message = vladimir._signable_interface_info_message()
signature = vladimir._crypto_power.power_ups(SigningPower).sign(vladimir.timestamp_bytes() + message)
vladimir._interface_signature_object = signature
vladimir.substantiate_stamp(password=INSECURE_DEVELOPMENT_PASSWORD)
vladimir._Teacher__interface_signature = signature
vladimir.substantiate_stamp(client_password=INSECURE_DEVELOPMENT_PASSWORD)
# With this slightly more sophisticated attack, his metadata does appear valid.
vladimir._is_valid_worker = lambda: True # mock staking verification TODO: Split into two tests
vladimir._is_valid_worker = lambda: True # bypass staking verification TODO: Split into two tests
vladimir.validate_metadata()
# However, the actual handshake proves him wrong.

View File

@ -10,39 +10,63 @@ from nucypher.utilities.sandbox.middleware import MockRestMiddleware
from nucypher.utilities.sandbox.ursula import make_federated_ursulas
def test_blockchain_ursula_is_not_valid_with_unsigned_identity_evidence(blockchain_ursulas, caplog):
lonely_blockchain_learner, blockchain_teacher, unsigned = list(blockchain_ursulas)[0:3]
def test_blockchain_ursula_stamp_verification_tolerance(blockchain_ursulas, caplog):
unsigned._identity_evidence = NOT_SIGNED
#
# Setup
#
# Wipe known nodes .
lonely_blockchain_learner._Learner__known_nodes = FleetStateTracker()
lonely_blockchain_learner._current_teacher_node = blockchain_teacher
# TODO: #1035
lonely_blockchain_learner, blockchain_teacher, unsigned, *the_others, non_staking_ursula = list(blockchain_ursulas)
lonely_blockchain_learner.remember_node(blockchain_teacher)
warnings = []
def warning_trapper(event):
if event['log_level'] == LogLevel.warn:
warnings.append(event)
#
# Attempt to verify unsigned stamp
#
unsigned._Teacher__decentralized_identity_evidence = NOT_SIGNED
# Wipe known nodes!
lonely_blockchain_learner._Learner__known_nodes = FleetStateTracker()
lonely_blockchain_learner._current_teacher_node = blockchain_teacher
lonely_blockchain_learner.remember_node(blockchain_teacher)
globalLogPublisher.addObserver(warning_trapper)
lonely_blockchain_learner.learn_from_teacher_node()
globalLogPublisher.removeObserver(warning_trapper)
# We received one warning during learning, and it was about this very matter.
assert len(warnings) == 1
assert warnings[0]['log_format'] == unsigned.invalid_metadata_message.format(unsigned)
warning = warnings[0]['log_format']
assert str(unsigned) in warning
assert "stamp is unsigned" in warning # TODO: Cleanup logging templates
assert unsigned not in lonely_blockchain_learner.known_nodes
# TODO: #1035
# minus 3 for self, a non-staking Ursula, and, of course, the unsigned ursula.
# minus 3: self, a non-staking ursula, and the unsigned ursula.
assert len(lonely_blockchain_learner.known_nodes) == len(blockchain_ursulas) - 3
assert blockchain_teacher in lonely_blockchain_learner.known_nodes
#
# Attempt to verify non-staking Ursula
#
lonely_blockchain_learner._current_teacher_node = non_staking_ursula
globalLogPublisher.addObserver(warning_trapper)
lonely_blockchain_learner.learn_from_teacher_node()
globalLogPublisher.removeObserver(warning_trapper)
assert len(warnings) == 2
warning = warnings[1]['log_format']
assert str(non_staking_ursula) in warning
assert "no active stakes" in warning # TODO: Cleanup logging templates
assert non_staking_ursula not in lonely_blockchain_learner.known_nodes
def test_emit_warning_upon_new_version(ursula_federated_test_config, caplog):
lonely_ursula_maker = partial(make_federated_ursulas,

View File

@ -191,8 +191,8 @@ def test_alice_refuses_to_make_arrangement_unless_ursula_is_valid(blockchain_ali
message = vladimir._signable_interface_info_message()
signature = vladimir._crypto_power.power_ups(SigningPower).sign(message)
vladimir.substantiate_stamp(password=INSECURE_DEVELOPMENT_PASSWORD)
vladimir._interface_signature_object = signature
vladimir.substantiate_stamp(client_password=INSECURE_DEVELOPMENT_PASSWORD)
vladimir._Teacher__interface_signature = signature
class FakeArrangement:
federated = False