2019-11-13 19:54:50 +00:00
"""
This file is part of nucypher .
nucypher is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
nucypher is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with nucypher . If not , see < https : / / www . gnu . org / licenses / > .
"""
2019-11-13 19:55:17 +00:00
from unittest . mock import patch
import time
from umbral . config import default_params
from umbral . signing import Signature
from nucypher . characters . lawful import Ursula
from nucypher . config . characters import AliceConfiguration
from nucypher . utilities . logging import GlobalLoggerSettings
from nucypher . utilities . sandbox . middleware import MockRestMiddlewareForLargeFleetTests
from nucypher . utilities . sandbox . ursula import make_federated_ursulas
2019-11-13 19:54:50 +00:00
"""
Node Discovery happens in phases . The first step is for a network actor to learn about the mere existence of a Node .
This is a straightforward step which we currently do with our own logic , but which may someday be replaced by something
like libp2p , depending on the course of development of those sorts of tools .
After this , our " Learning Loop " does four other things in sequence which are not part of the offering of node discovery tooling alone :
* Instantiation of an actual Node object ( currently , an Ursula object ) from node metadata .
* Validation of the node ' s metadata (non-interactive; shows that the Node ' s public material is indeed signed by the wallet holder of its Staker ) .
* Verification of the Node itself ( interactive ; shows that the REST server operating at the Node ' s interface matches the node ' s metadata ) .
* Verification of the Stake ( reads the blockchain ; shows that the Node is sponsored by a Staker with sufficient Stake to support a Policy ) .
These tests show that each phase of this process is done correctly , and in some cases , with attention to specific
performance bottlenecks .
"""
2019-11-13 19:55:17 +00:00
2019-11-13 19:54:50 +00:00
def test_alice_can_learn_about_a_whole_bunch_of_ursulas ( ursula_federated_test_config ) :
# First, we need to do some optimizing of this test in order
# to be able to create a whole bunch of Ursulas without it freezing.
# BEGIN CRAZY MONKEY PATCHING BLOCK
class NotAPublicKey :
_serial = 10000
@classmethod
def tick ( cls ) :
cls . _serial + = 1
def __init__ ( self , serial = None ) :
if serial is None :
self . tick ( )
self . serial = str ( self . _serial ) . encode ( )
else :
self . serial = serial
def __bytes__ ( self ) :
return b " not a compressed public key: " + self . serial
@classmethod
def from_bytes ( cls , some_bytes ) :
return cls ( serial = some_bytes [ - 5 : ] )
def to_bytes ( self , * args , * * kwargs ) :
return b " this is not a public key... but it is 64 bytes.. so, ya know " + self . serial
class NotAPrivateKey :
params = default_params ( )
fake_signature = Signature . from_bytes (
2019-11-13 19:55:17 +00:00
b ' @ \xbf S& \x97 \xb3 \x9e \x9e \xd3 \\ j \x9f \x0e \x8f Y \x0c \xbe S \x08 d \x0b %s \xf6 \x17 \xe2 \xb6 \xcd \x95 u \xaa pON \xd9 E \xb3 \x10 M \xe1 \xf4 u \x0b L \x99 q \xd6 \r \x8e _ \xe5 I \x1e \xe5 \xa2 \xcf \xe5 \x8b e_ \x07 7Gz '
)
2019-11-13 19:54:50 +00:00
def public_key ( self ) :
return NotAPublicKey ( )
def get_pubkey ( self , * args , * * kwargs ) :
return self . public_key ( )
def to_cryptography_privkey ( self , * args , * * kwargs ) :
return self
def sign ( self , * args , * * kwargs ) :
return b ' 0D \x02 @ \xbf S& \x97 \xb3 \x9e \x9e \xd3 \\ j \x9f \x0e \x8f Y \x0c \xbe S \x08 d \x0b %s \xf6 \x17 \xe2 \xb6 \xcd \x95 u \xaa p \x02 ON \xd9 E \xb3 \x10 M \xe1 \xf4 u \x0b L \x99 q \xd6 \r \x8e _ \xe5 I \x1e \xe5 \xa2 \xcf \xe5 \x8b e_ \x07 7Gz '
@classmethod
def stamp ( cls , * args , * * kwargs ) :
return cls . fake_signature
@classmethod
def signature_bytes ( cls , * args , * * kwargs ) :
return b ' @ \xbf S& \x97 \xb3 \x9e \x9e \xd3 \\ j \x9f \x0e \x8f Y \x0c \xbe S \x08 d \x0b %s \xf6 \x17 \xe2 \xb6 \xcd \x95 u \xaa pON \xd9 E \xb3 \x10 M \xe1 \xf4 u \x0b L \x99 q \xd6 \r \x8e _ \xe5 I \x1e \xe5 \xa2 \xcf \xe5 \x8b e_ \x07 7Gz '
class NotACert :
class Subject :
def get_attributes_for_oid ( self , * args , * * kwargs ) :
class Pseudonym :
value = " 0x51347fF6eb8F1D39B83B5e9c244Dc2E1E9EB14B4 "
2019-11-13 19:55:17 +00:00
2019-11-13 19:54:50 +00:00
return Pseudonym ( ) , " Or whatever? "
2019-11-13 19:55:17 +00:00
2019-11-13 19:54:50 +00:00
subject = Subject ( )
def public_bytes ( self , does_not_matter ) :
return b " this is not a cert. "
def public_key ( self ) :
return NotAPublicKey ( )
def do_not_create_cert ( * args , * * kwargs ) :
return NotACert ( ) , NotAPrivateKey ( )
def simple_remember ( ursula , node , * args , * * kwargs ) :
address = node . checksum_address
ursula . known_nodes [ address ] = node
class NotARestApp :
testing = True
2019-11-13 23:31:36 +00:00
mock_cert_storage = patch ( " nucypher.config.storages.ForgetfulNodeStorage.store_node_certificate " ,
new = lambda * args , * * kwargs : " do not store cert. " )
mock_cert_loading = patch ( " nucypher.characters.lawful.load_pem_x509_certificate " ,
new = lambda * args , * * kwargs : NotACert ( ) )
mock_cert_generation = patch ( " nucypher.keystore.keypairs.generate_self_signed_certificate " , new = do_not_create_cert )
mock_rest_app_creation = patch ( " nucypher.characters.lawful.make_rest_app " ,
new = lambda * args , * * kwargs : ( NotARestApp ( ) , " this is not a datastore " ) )
mock_secret_source = patch ( " nucypher.keystore.keypairs.Keypair._private_key_source " ,
new = lambda * args , * * kwargs : NotAPrivateKey ( ) )
mock_remember_node = patch ( " nucypher.characters.lawful.Ursula.remember_node " , new = simple_remember )
2019-11-13 19:54:50 +00:00
with GlobalLoggerSettings . pause_all_logging_while ( ) :
2019-11-13 23:31:36 +00:00
with mock_cert_storage , mock_cert_loading , mock_rest_app_creation , mock_cert_generation , mock_secret_source , mock_remember_node :
_ursulas = make_federated_ursulas ( ursula_config = ursula_federated_test_config ,
quantity = 5000 , know_each_other = False )
# END FIRST CRAZY MONKEY PATCHING BLOCK
all_ursulas = { u . checksum_address : u for u in _ursulas }
for ursula in _ursulas :
ursula . known_nodes . _nodes = all_ursulas
ursula . known_nodes . checksum = b " This is a fleet state checksum.. " . hex ( )
2019-11-13 19:54:50 +00:00
config = AliceConfiguration ( dev_mode = True ,
network_middleware = MockRestMiddlewareForLargeFleetTests ( ) ,
known_nodes = _ursulas ,
federated_only = True ,
abort_on_learning_error = True ,
save_metadata = False ,
reload_metadata = False )
class VerificationTracker :
node_verifications = 0
metadata_verifications = 0
@classmethod
def fake_verify_node ( cls , * args , * * kwargs ) :
cls . node_verifications + = 1
@classmethod
def fake_verify_metadata ( cls , * args , * * kwargs ) :
cls . metadata_verifications + = 1
2019-11-13 23:31:36 +00:00
with mock_cert_storage :
2019-11-13 19:54:50 +00:00
with patch ( " nucypher.characters.lawful.Ursula.verify_node " , new = VerificationTracker . fake_verify_node ) :
with patch ( " nucypher.network.nodes.FleetStateTracker.record_fleet_state " , new = lambda * args , * * kwargs : None ) :
alice = config . produce ( known_nodes = list ( _ursulas ) [ : 1 ] ,
2019-11-13 19:55:17 +00:00
)
2019-11-13 19:54:50 +00:00
# We started with one known_node and verified it.
# TODO: Consider changing this - #1449
assert VerificationTracker . node_verifications == 1
with patch ( " nucypher.config.storages.ForgetfulNodeStorage.store_node_certificate " ,
new = lambda * args , * * kwargs : " do not store cert. " ) :
with patch ( " nucypher.characters.lawful.Ursula.verify_node " , new = VerificationTracker . fake_verify_node ) :
2019-11-13 19:55:17 +00:00
with patch ( " nucypher.network.nodes.Teacher.validate_metadata " ,
new = VerificationTracker . fake_verify_metadata ) :
2019-11-13 19:54:50 +00:00
with patch ( ' nucypher.characters.lawful.Alice.verify_from ' , new = lambda * args , * * kwargs : None ) :
with patch ( ' umbral.keys.UmbralPublicKey.from_bytes ' , NotAPublicKey . from_bytes ) :
2019-11-13 19:55:17 +00:00
with patch ( ' nucypher.characters.lawful.load_pem_x509_certificate ' ,
new = lambda * args , * * kwargs : NotACert ( ) ) :
2019-11-13 19:54:50 +00:00
with patch ( ' nucypher.crypto.signing.SignatureStamp.__call__ ' , new = NotAPrivateKey . stamp ) :
with patch ( ' umbral.signing.Signature.__bytes__ ' , new = NotAPrivateKey . signature_bytes ) :
started = time . time ( )
alice . block_until_number_of_known_nodes_is ( 8 , learn_on_this_thread = True , timeout = 60 )
ended = time . time ( )
elapsed = ended - started
2019-11-13 19:55:17 +00:00
assert VerificationTracker . node_verifications == 1 # We have only verified the first Ursula.
2019-11-13 19:54:50 +00:00
assert sum ( isinstance ( u , Ursula ) for u in alice . known_nodes ) < 20 # We haven't instantiated many Ursulas.
assert elapsed < 8 # 8 seconds is still a little long to discover 8 out of 5000 nodes, but before starting the optimization that went with this test, this operation took about 18 minutes on jMyles' laptop.