Worker validation pursuant to consensus on #1003. Closes #1033.

pull/1029/head
David Núñez 2019-06-11 02:00:10 +02:00
parent edb95407c3
commit 6cd4d2fde3
2 changed files with 52 additions and 21 deletions

View File

@ -817,11 +817,16 @@ class Learner:
except node.NotStaking:
self.log.warn(f'Verification Failed - '
f'{node} has no active stakes in the current period ({self.staking_agent.get_current_period()}')
f'{node} has no active stakes in the current period '
f'({self.staking_agent.get_current_period()}')
except node.InvalidWalletSignature:
except node.InvalidWorkerSignature:
self.log.warn(f'Verification Failed - '
f'{node} has an invalid wallet signature for {node.checksum_public_address}')
f'{node} has an invalid wallet signature for {node.decentralized_identity_evidence}')
except node.DetachedWorker:
self.log.warn(f'Verification Failed - '
f'{node} is not bonded to a Staker.')
except node.InvalidNode:
self.log.warn(node.invalid_metadata_message.format(node))
@ -916,12 +921,15 @@ class Teacher:
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 InvalidWorkerSignature(InvalidStamp):
"""Raised when a stamp fails signature verification or recovers an unexpected worker address"""
class NotStaking(InvalidStamp):
"""Raised when a node fails verification because it is not currently staking"""
class DetachedWorker(InvalidNode):
"""Raised when a node fails verification because it is not bonded to a Staker"""
class WrongMode(TypeError):
"""Raised when a Character tries to use another Character as decentralized when the latter is federated_only."""
@ -973,26 +981,33 @@ class Teacher:
# Stamp
#
def _stamp_has_valid_wallet_signature(self) -> bool:
"""Off-chain Signature Verification of ethereum client signature of stamp"""
def _stamp_has_valid_signature_by_worker(self) -> bool:
"""Off-chain Signature Verification of stamp signature by Worker's ETH account"""
if self.__decentralized_identity_evidence is NOT_SIGNED:
return False
signature_is_valid = verify_eip_191(message=bytes(self.stamp),
signature=self.__decentralized_identity_evidence,
address=self.checksum_address)
address=self.worker_address)
return signature_is_valid
def _is_valid_worker(self) -> bool:
def _worker_is_bonded_to_staker(self) -> bool:
"""
This method assumes the stamp's signature is valid and accurate.
As a follow-up, validate the Staker and Worker on-chain.
As a follow-up, this checks that the worker is linked to a staker, but it may be
the case that the "staker" isn't "staking" (e.g., all her tokens have been slashed).
"""
staker_address = self.staking_agent.get_staker_from_worker(worker_address=self.worker_address)
return staker_address == self.checksum_address
TODO: #1033 - Verify Staker <-> Worker relationship on-chain
def _staker_is_really_staking(self) -> bool:
"""
This method assumes the stamp's signature is valid and accurate.
As a follow-up, this checks that the staker is, indeed, staking.
"""
locked_tokens = self.staking_agent.get_locked_tokens(staker_address=self.checksum_address)
return locked_tokens > 0
def validate_stamp(self, verify_staking: bool = True) -> None:
def validate_worker(self, verify_staking: bool = True) -> None:
# Federated
if self.federated_only:
@ -1008,12 +1023,15 @@ class Teacher:
raise self.StampNotSigned
# Off-chain signature verification
if not self._stamp_has_valid_wallet_signature():
raise self.InvalidWalletSignature
if not self._stamp_has_valid_signature_by_worker():
raise self.InvalidWorkerSignature
# On-chain staking check
if verify_staking:
if self._is_valid_worker(): # <-- Blockchain CALL
if not self._worker_is_bonded_to_staker(): # <-- Blockchain CALL
raise self.DetachedWorker
if self._staker_is_really_staking(): # <-- Blockchain CALL
self.verified_worker = True
else:
raise self.NotStaking
@ -1034,7 +1052,7 @@ class Teacher:
# Offline check of valid stamp signature by worker
try:
self.validate_stamp(verify_staking=verify_staking)
self.validate_worker(verify_staking=verify_staking)
except self.WrongMode:
if not accept_federated_only:
raise

View File

@ -26,7 +26,7 @@ from nucypher.crypto.api import verify_eip_191
from nucypher.crypto.powers import SigningPower, CryptoPower
from nucypher.utilities.sandbox.constants import INSECURE_DEVELOPMENT_PASSWORD
from nucypher.utilities.sandbox.middleware import MockRestMiddleware
from nucypher.utilities.sandbox.ursula import make_federated_ursulas
from nucypher.utilities.sandbox.ursula import make_federated_ursulas, make_decentralized_ursulas
def test_new_federated_ursula_announces_herself(ursula_federated_test_config):
@ -51,16 +51,29 @@ def test_new_federated_ursula_announces_herself(ursula_federated_test_config):
assert ursula_in_a_house in ursula_with_a_mouse.known_nodes
def test_stakers_bond_to_ursulas(testerchain, stakers, ursula_decentralized_test_config):
ursulas = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config,
stakers_addresses=testerchain.stakers_accounts,
workers_addresses=testerchain.ursulas_accounts,
confirm_activity=False)
assert len(ursulas) == len(stakers)
for ursula in ursulas:
ursula.validate_worker(verify_staking=True)
assert ursula.verified_worker
def test_blockchain_ursula_substantiates_stamp(blockchain_ursulas):
first_ursula = list(blockchain_ursulas)[0]
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_address,
assert verify_eip_191(address=first_ursula.worker_address,
message=bytes(first_ursula.stamp),
signature=signature_as_bytes)
# This method is a shortcut for the above.
assert first_ursula._stamp_has_valid_wallet_signature
assert first_ursula._stamp_has_valid_signature_by_worker()
def test_blockchain_ursula_verifies_stamp(blockchain_ursulas):
@ -68,7 +81,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.validate_stamp()
first_ursula.validate_worker()
# ...but now it's verified.
assert first_ursula.verified_stamp
@ -89,7 +102,7 @@ def test_vladimir_cannot_verify_interface_with_ursulas_signing_key(blockchain_ur
# Vladimir can substantiate the stamp using his own ether address...
vladimir.substantiate_stamp(client_password=INSECURE_DEVELOPMENT_PASSWORD)
vladimir._is_valid_worker = lambda: True
vladimir.validate_stamp()
vladimir.validate_worker()
# Now, even though his public signing key matches Ursulas...
assert vladimir.stamp == his_target.stamp