def test_create_before_wait_timer_expires(self): # Need to create signup information SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # Create a wait certificate for the genesis block so that we can # create another wait certificate that has to play by the rules. wt = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=wt, block_hash="Reader's Digest") wt = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=wc.identifier, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) with self.assertRaises(ValueError): WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=wt, block_hash="Reader's Digest")
def test_create_with_reused_wait_timer(self): # Need to create signup information SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # Create a wait certificate for the genesis block so that we can # create another wait certificate that has to play by the rules. wt = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=wt, block_hash="Reader's Digest") consumed_wt = wt # Verify that we cannot use the consumed wait timer to create a wait # certificate either before or after creating a new wait timer with self.assertRaises(ValueError): WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=consumed_wt, block_hash="Reader's Digest") wt = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=wc.identifier, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) with self.assertRaises(ValueError): WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=consumed_wt, block_hash="Reader's Digest") # Verify that once the new timer expires, we can create a wait # certificate with it while not wt.has_expired(time.time()): time.sleep(1) WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=wt, block_hash="Reader's Digest")
def test_create_before_create_wait_timer(self): # Need to create signup information SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # Make sure that trying to create a wait certificate before creating # a wait timer causes an error with self.assertRaises(ValueError): WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=None, block_hash="Reader's Digest")
def test_unsealing_data(self): signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=poet_enclave, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) poet_public_key = \ SignupInfo.unseal_signup_data( poet_enclave_module=poet_enclave, sealed_signup_data=signup_info.sealed_signup_data) self.assertEqual( signup_info.poet_public_key, poet_public_key, msg="PoET public key in signup info and sealed data don't match")
def signup_attempt_timed_out(self, signup_nonce, poet_settings_view, block_cache): """Checks whether too many blocks have elapsed since since the registration attempt. Args: signup_nonce (string): nonce (~ block id) used in signup poet_settings_view (PoetSettingsView): The current Poet config view block_cache (BlockCache): The block store cache Returns: bool: True if too many blocks have elapsed; False if you just need to chill out a little while longer. """ # It's tempting to set this timeout as the lesser of retry_delay # and signup_commit_maximum_delay, but then we can't individually # control this timeout behavior. Consider behavior as a new node joins # an old network. depth = poet_settings_view.registration_retry_delay i = 0 for block in block_cache.block_store.get_block_iter(reverse=True): if i > depth: return True block_id = block.identifier if signup_nonce == SignupInfo.block_id_to_nonce(block_id): return False i += 1 return False
def test_create_with_wrong_signup_data(self): # Need to create signup information signup_info = SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) signup_info2 = SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # Create two timers and try to create the wait certificate with the # first one, which should fail as it is not the current wait timer wt1 = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) wt2 = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, sealed_signup_data=signup_info2.sealed_signup_data, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) # Verify that we cannot create a wait certificate using the first # wait timer with the second signup data with self.assertRaises(ValueError): WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, sealed_signup_data=signup_info2.sealed_signup_data, wait_timer=wt1, block_hash="Reader's Digest") WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, sealed_signup_data=signup_info2.sealed_signup_data, wait_timer=wt2, block_hash="Reader's Digest")
def test_create(self): signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=poet_enclave, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) self.assertIsNotNone(signup_info.poet_public_key) self.assertIsNotNone(signup_info.proof_data) self.assertIsNotNone(signup_info.anti_sybil_id) self.assertIsNotNone(signup_info.sealed_signup_data)
def test_serialization(self): signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=poet_enclave, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) serialized = signup_info.serialize() copy_signup_info = \ SignupInfo.signup_info_from_serialized( poet_enclave_module=poet_enclave, serialized=serialized) self.assertEqual( signup_info.poet_public_key, copy_signup_info.poet_public_key) self.assertEqual(signup_info.proof_data, copy_signup_info.proof_data) self.assertEqual( signup_info.anti_sybil_id, copy_signup_info.anti_sybil_id) self.assertIsNone(copy_signup_info.sealed_signup_data)
def test_create_with_wrong_wait_timer(self): # Need to create signup information SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # Create two timers and try to create the wait certificate with the # first one, which should fail as it is not the current wait timer invalid_wt = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) valid_wt = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) # Verify that we cannot create a wait certificate with the old wait # timer, but we can with the new one with self.assertRaises(ValueError): WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=invalid_wt, block_hash="Reader's Digest") WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=valid_wt, block_hash="Reader's Digest")
def test_create_after_wait_timer_timed_out(self): # Need to create signup information signup_info = SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # Create a wait certificate for the genesis block so that we can # create another wait certificate that has to play by the rules. wt = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data, wait_timer=wt, block_hash="Reader's Digest") wt = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=wc.identifier, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) while not wt.has_expired(time.time()): time.sleep(1) time.sleep(self.poet_enclave_module.TIMER_TIMEOUT_PERIOD + 1) with self.assertRaises(ValueError): WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data, wait_timer=wt, block_hash="Reader's Digest")
def test_create(self): # Need to create signup information first signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) stake_in_the_sand = time.time() # create mock_poet_enclave_wait_timer mock_poet_enclave_wait_timer = \ mock.Mock(validator_address='1060 W Addison Street', duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, signature=None, serialized_timer=None, request_time=time.time()) # create mock_poet_enclave_simulator mock_poet_enclave_simulator = mock.Mock() mock_poet_enclave_simulator.create_wait_timer.return_value = \ mock_poet_enclave_wait_timer # An empty certificate list should result in a local mean that is # the target wait time wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) self.assertIsNotNone(wt) self.assertEqual( wt.local_mean, self.mock_poet_settings_view.target_wait_time) self.assertEqual(wt.previous_certificate_id, NULL_BLOCK_IDENTIFIER) self.assertGreaterEqual(wt.request_time, stake_in_the_sand) self.assertLessEqual(wt.request_time, time.time()) self.assertGreaterEqual( wt.duration, self.mock_poet_settings_view.minimum_wait_time) self.assertEqual(wt.validator_address, '1060 W Addison Street') # Ensure that the enclave is set back to initial state self.poet_enclave_module = reload(poet_enclave) # Make sure that trying to create a wait timer before signup # information is provided causes an error with self.assertRaises(ValueError): wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) # Initialize the enclave with sealed signup data SignupInfo.unseal_signup_data( poet_enclave_module=self.poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data) stake_in_the_sand = time.time() mock_poet_enclave_wait_timer.request_time = time.time() # An empty certificate list should result in a local mean that is # the target wait time wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) self.assertIsNotNone(wt) self.assertEqual( wt.local_mean, self.mock_poet_settings_view.target_wait_time) self.assertEqual(wt.previous_certificate_id, NULL_BLOCK_IDENTIFIER) self.assertGreaterEqual(wt.request_time, stake_in_the_sand) self.assertLessEqual(wt.request_time, time.time()) self.assertGreaterEqual( wt.duration, self.mock_poet_settings_view.minimum_wait_time) self.assertEqual(wt.validator_address, '1060 W Addison Street')
def test_has_expired(self): # Need to create signup information first SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # create mock_poet_enclave_wait_timer mock_poet_enclave_wait_timer = \ mock.Mock(validator_address='1060 W Addison Street', duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, signature=None, serialized_timer=None, request_time=time.time()) # create mock_poet_enclave_simulator mock_poet_enclave_simulator = mock.Mock() mock_poet_enclave_simulator.create_wait_timer.return_value = \ mock_poet_enclave_wait_timer # Verify that a timer doesn't expire before its creation time wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) self.assertFalse(wt.has_expired(wt.request_time - 1)) # Create a timer and when it has expired, verify that the duration is # not greater than actual elapsed time. wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) self.assertTrue(wt.has_expired(wt.request_time + wt.duration + 1.0)) # Tampering with the duration should not affect wait timer expiration wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) assigned_duration = wt.duration wt.duration = 0 self.assertTrue(wt.has_expired (wt.request_time + assigned_duration + 1.0)) # Tampering with the request time should not affect wait timer # expiration wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) assigned_request_time = wt.request_time wt.request_time -= wt.duration self.assertTrue(wt.has_expired (assigned_request_time + wt.duration + 1.0))
def do_create(args): """Executes the `poet registration` subcommand. This command generates a validator registry transaction and saves it to a file, whose location is determined by the args. The signup data, generated by the selected enclave, is also stored in a well-known location. """ signer = _read_signer(args.key) public_key = signer.get_public_key().as_hex() public_key_hash = sha256(public_key.encode()).hexdigest() nonce = SignupInfo.block_id_to_nonce(args.block) with PoetEnclaveModuleWrapper( enclave_module=args.enclave_module, config_dir=config.get_config_dir(), data_dir=config.get_data_dir()) as poet_enclave_module: signup_info = SignupInfo.create_signup_info( poet_enclave_module=poet_enclave_module, originator_public_key_hash=public_key_hash, nonce=nonce) print( 'Writing key state for PoET public key: {}...{}'.format( signup_info.poet_public_key[:8], signup_info.poet_public_key[-8:])) # Store the newly-created PoET key state, associating it with its # corresponding public key poet_key_state_store = \ PoetKeyStateStore( data_dir=config.get_data_dir(), validator_id=public_key) poet_key_state_store[signup_info.poet_public_key] = \ PoetKeyState( sealed_signup_data=signup_info.sealed_signup_data, has_been_refreshed=False, signup_nonce=nonce) # Create the validator registry payload payload = \ vr_pb.ValidatorRegistryPayload( verb='register', name='validator-{}'.format(public_key[:8]), id=public_key, signup_info=vr_pb.SignUpInfo( poet_public_key=signup_info.poet_public_key, proof_data=signup_info.proof_data, anti_sybil_id=signup_info.anti_sybil_id, nonce=SignupInfo.block_id_to_nonce(args.block))) serialized = payload.SerializeToString() # Create the address that will be used to look up this validator # registry transaction. Seems like a potential for refactoring.. validator_entry_address = \ VR_NAMESPACE + sha256(public_key.encode()).hexdigest() # Create a transaction header and transaction for the validator # registry update amd then hand it off to the batch publisher to # send out. output_addresses = [validator_entry_address, VALIDATOR_MAP_ADDRESS] input_addresses = \ output_addresses + \ [SettingsView.setting_address('sawtooth.poet.report_public_key_pem'), SettingsView.setting_address('sawtooth.poet.' 'valid_enclave_measurements'), SettingsView.setting_address('sawtooth.poet.valid_enclave_basenames')] header = \ txn_pb.TransactionHeader( signer_public_key=public_key, family_name='sawtooth_validator_registry', family_version='1.0', inputs=input_addresses, outputs=output_addresses, dependencies=[], payload_sha512=sha512(serialized).hexdigest(), batcher_public_key=public_key, nonce=time.time().hex().encode()).SerializeToString() signature = signer.sign(header) transaction = \ txn_pb.Transaction( header=header, payload=serialized, header_signature=signature) batch = _create_batch(signer, [transaction]) batch_list = batch_pb.BatchList(batches=[batch]) try: print('Generating {}'.format(args.output)) with open(args.output, 'wb') as batch_file: batch_file.write(batch_list.SerializeToString()) except IOError as e: raise CliException('Unable to write to batch file: {}'.format(str(e)))
def test_create(self): # Need to create signup information and wait timer first signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) wt = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_config_view=self.mock_poet_config_view) while not wt.has_expired(time.time()): time.sleep(1) # Now we can create a wait certificate and verify that it correlates # to the wait timer we just created wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=wt, block_hash="Reader's Digest") self.assertIsNotNone(wc) self.assertEqual(wc.previous_certificate_id, wt.previous_certificate_id) self.assertAlmostEqual(wc.local_mean, wt.local_mean) self.assertAlmostEqual(wc.request_time, wt.request_time) self.assertAlmostEqual(wc.duration, wt.duration) self.assertEqual(wc.validator_address, wt.validator_address) self.assertEqual(wc.block_hash, "Reader's Digest") self.assertIsNotNone(wc.signature) self.assertIsNotNone(wc.identifier) # A newly-created wait certificate should be valid wc.check_valid(poet_enclave_module=self.poet_enclave_module, previous_certificate_id=NULL_BLOCK_IDENTIFIER, poet_public_key=signup_info.poet_public_key, consensus_state=self.consensus_state, poet_config_view=self.mock_poet_config_view) validator_info = \ ValidatorInfo( id='validator_001', signup_info=SignUpInfo( poet_public_key='key_001')) self.consensus_state.validator_did_claim_block( validator_info=validator_info, wait_certificate=wc, poet_config_view=self.mock_poet_config_view) # Create another wait certificate and verify it is valid also wt = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=wc.identifier, consensus_state=self.consensus_state, poet_config_view=self.mock_poet_config_view) while not wt.has_expired(time.time()): time.sleep(1) # Now we can create a wait certificate and verify that it correlates # to the wait timer we just created another_wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=wt, block_hash="Pepto Bismol") another_wc.check_valid(poet_enclave_module=self.poet_enclave_module, previous_certificate_id=wc.identifier, poet_public_key=signup_info.poet_public_key, consensus_state=self.consensus_state, poet_config_view=self.mock_poet_config_view)
def test_create(self): # Need to create signup information and wait timer first signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # create mock_poet_enclave_wait_timer mock_poet_enclave_wait_timer = \ mock.Mock(sealed_signup_data=signup_info.sealed_signup_data, validator_address='1060 W Addison Street', duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, signature='00112233445566778899aabbccddeeff', serialized_timer=None, request_time=time.time()) # create mock_poet_enclave_wait_certificate mock_poet_enclave_wait_certificate = \ mock.Mock(sealed_signup_data=signup_info.sealed_signup_data, duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, request_time=time.time(), validator_address='1060 W Addison Street', nonce=NULL_BLOCK_IDENTIFIER, block_hash="Reader's Digest", signature='00112233445566778899aabbccddeeff', serialized_certificate='001122334455667' '78899aabbccddeeff') # create mock_poet_enclave_module mock_poet_enclave_module = mock.Mock() mock_poet_enclave_module.create_wait_timer.return_value = \ mock_poet_enclave_wait_timer # set the mock enclave wait certificate and wait timer to # have the same request_time mock_poet_enclave_wait_certificate.request_time = \ mock_poet_enclave_wait_timer.request_time # set the mock enclave wait certificate and wait timer to # have the same previous_certificate_id mock_poet_enclave_wait_certificate.previous_certificate_id = \ mock_poet_enclave_wait_timer.previous_certificate_id # set the identifier for mock_poet_enclave_wait_certificate mock_poet_enclave_wait_certificate.identifier.return_value = \ mock_poet_enclave_wait_certificate.previous_certificate_id[:16] mock_poet_enclave_module.create_wait_certificate.return_value = \ mock_poet_enclave_wait_certificate mock_poet_enclave_module.deserialize_wait_certificate.return_value = \ mock_poet_enclave_wait_certificate # create wait timer wt = \ WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) # Now we can create a wait certificate and verify that it correlates # to the wait timer we just created wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=mock_poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data, wait_timer=wt, block_hash="Reader's Digest") self.assertIsNotNone(wc) self.assertEqual( wc.previous_certificate_id, wt.previous_certificate_id) self.assertAlmostEqual(wc.local_mean, wt.local_mean) self.assertAlmostEqual(wc.request_time, wt.request_time) self.assertAlmostEqual(wc.duration, wt.duration) self.assertEqual(wc.validator_address, wt.validator_address) self.assertEqual(wc.block_hash, "Reader's Digest") self.assertIsNotNone(wc.signature) self.assertIsNotNone(wc.identifier) # A newly-created wait certificate should be valid wc.check_valid( poet_enclave_module=mock_poet_enclave_module, previous_certificate_id=NULL_BLOCK_IDENTIFIER, poet_public_key=signup_info.poet_public_key, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) validator_info = \ ValidatorInfo( id='validator_001', signup_info=SignUpInfo( poet_public_key='key_001')) self.mock_consensus_state.validator_did_claim_block( validator_info=validator_info, wait_certificate=wc, poet_settings_view=self.mock_poet_settings_view) # Create another wait certificate and verify it is valid also wt = \ WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=wc.identifier, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) # Now we can create a wait certificate and verify that it correlates # to the wait timer we just created another_wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=mock_poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data, wait_timer=wt, block_hash="Pepto Bismol") another_wc.check_valid( poet_enclave_module=mock_poet_enclave_module, previous_certificate_id=wc.identifier, poet_public_key=signup_info.poet_public_key, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view)
def test_create(self): # Need to create signup information and wait timer first signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # create mock_poet_enclave_wait_timer mock_poet_enclave_wait_timer = \ mock.Mock(validator_address='1060 W Addison Street', duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, signature='00112233445566778899aabbccddeeff', serialized_timer=None, request_time=time.time()) # create mock_poet_enclave_wait_certificate mock_poet_enclave_wait_certificate = \ mock.Mock(duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, request_time=time.time(), validator_address='1060 W Addison Street', nonce=NULL_BLOCK_IDENTIFIER, block_hash="Reader's Digest", signature='00112233445566778899aabbccddeeff', serialized_certificate='001122334455667' '78899aabbccddeeff') # create mock_poet_enclave_module mock_poet_enclave_module = mock.Mock() mock_poet_enclave_module.create_wait_timer.return_value = \ mock_poet_enclave_wait_timer # set the mock enclave wait certificate and wait timer to # have the same request_time mock_poet_enclave_wait_certificate.request_time = \ mock_poet_enclave_wait_timer.request_time # set the mock enclave wait certificate and wait timer to # have the same previous_certificate_id mock_poet_enclave_wait_certificate.previous_certificate_id = \ mock_poet_enclave_wait_timer.previous_certificate_id # set the identifier for mock_poet_enclave_wait_certificate mock_poet_enclave_wait_certificate.identifier.return_value = \ mock_poet_enclave_wait_certificate.previous_certificate_id[:16] mock_poet_enclave_module.create_wait_certificate.return_value = \ mock_poet_enclave_wait_certificate mock_poet_enclave_module.deserialize_wait_certificate.return_value = \ mock_poet_enclave_wait_certificate # create wait timer wt = \ WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) # Now we can create a wait certificate and verify that it correlates # to the wait timer we just created wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=mock_poet_enclave_module, wait_timer=wt, block_hash="Reader's Digest") self.assertIsNotNone(wc) self.assertEqual( wc.previous_certificate_id, wt.previous_certificate_id) self.assertAlmostEqual(wc.local_mean, wt.local_mean) self.assertAlmostEqual(wc.request_time, wt.request_time) self.assertAlmostEqual(wc.duration, wt.duration) self.assertEqual(wc.validator_address, wt.validator_address) self.assertEqual(wc.block_hash, "Reader's Digest") self.assertIsNotNone(wc.signature) self.assertIsNotNone(wc.identifier) # A newly-created wait certificate should be valid wc.check_valid( poet_enclave_module=mock_poet_enclave_module, previous_certificate_id=NULL_BLOCK_IDENTIFIER, poet_public_key=signup_info.poet_public_key, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) validator_info = \ ValidatorInfo( id='validator_001', signup_info=SignUpInfo( poet_public_key='key_001')) self.mock_consensus_state.validator_did_claim_block( validator_info=validator_info, wait_certificate=wc, poet_settings_view=self.mock_poet_settings_view) # Create another wait certificate and verify it is valid also wt = \ WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=wc.identifier, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) # Now we can create a wait certificate and verify that it correlates # to the wait timer we just created another_wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=mock_poet_enclave_module, wait_timer=wt, block_hash="Pepto Bismol") another_wc.check_valid( poet_enclave_module=mock_poet_enclave_module, previous_certificate_id=wc.identifier, poet_public_key=signup_info.poet_public_key, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view)
def do_create(args): """Executes the `poet registration` subcommand. This command generates a validator registry transaction and saves it to a file, whose location is determined by the args. The signup data, generated by the selected enclave, is also stored in a well-known location. """ signer = _read_signer(args.key) public_key = signer.get_public_key().as_hex() public_key_hash = sha256(public_key.encode()).hexdigest() nonce = SignupInfo.block_id_to_nonce(args.block) with PoetEnclaveModuleWrapper( enclave_module=args.enclave_module, config_dir=config.get_config_dir(), data_dir=config.get_data_dir()) as poet_enclave_module: signup_info = SignupInfo.create_signup_info( poet_enclave_module=poet_enclave_module, originator_public_key_hash=public_key_hash, nonce=nonce) print('Writing key state for PoET public key: {}...{}'.format( signup_info.poet_public_key[:8], signup_info.poet_public_key[-8:])) # Store the newly-created PoET key state, associating it with its # corresponding public key poet_key_state_store = \ PoetKeyStateStore( data_dir=config.get_data_dir(), validator_id=public_key) poet_key_state_store[signup_info.poet_public_key] = \ PoetKeyState( sealed_signup_data=signup_info.sealed_signup_data, has_been_refreshed=False, signup_nonce=nonce) # Create the validator registry payload payload = \ vr_pb.ValidatorRegistryPayload( verb='register', name='validator-{}'.format(public_key[:8]), id=public_key, signup_info=vr_pb.SignUpInfo( poet_public_key=signup_info.poet_public_key, proof_data=signup_info.proof_data, anti_sybil_id=signup_info.anti_sybil_id, nonce=SignupInfo.block_id_to_nonce(args.block))) serialized = payload.SerializeToString() # Create the address that will be used to look up this validator # registry transaction. Seems like a potential for refactoring.. validator_entry_address = \ VR_NAMESPACE + sha256(public_key.encode()).hexdigest() # Create a transaction header and transaction for the validator # registry update amd then hand it off to the batch publisher to # send out. output_addresses = [validator_entry_address, VALIDATOR_MAP_ADDRESS] input_addresses = \ output_addresses + \ [SettingsView.setting_address('sawtooth.poet.report_public_key_pem'), SettingsView.setting_address('sawtooth.poet.' 'valid_enclave_measurements'), SettingsView.setting_address('sawtooth.poet.valid_enclave_basenames')] header = \ txn_pb.TransactionHeader( signer_public_key=public_key, family_name='sawtooth_validator_registry', family_version='1.0', inputs=input_addresses, outputs=output_addresses, dependencies=[], payload_sha512=sha512(serialized).hexdigest(), batcher_public_key=public_key, nonce=time.time().hex().encode()).SerializeToString() signature = signer.sign(header) transaction = \ txn_pb.Transaction( header=header, payload=serialized, header_signature=signature) batch = _create_batch(signer, [transaction]) batch_list = batch_pb.BatchList(batches=[batch]) try: print('Generating {}'.format(args.output)) with open(args.output, 'wb') as batch_file: batch_file.write(batch_list.SerializeToString()) except IOError as e: raise CliException('Unable to write to batch file: {}'.format( str(e))) from e
def initialize_block(self, block_header): """Do initialization necessary for the consensus to claim a block, this may include initiating voting activities, starting proof of work hash generation, or create a PoET wait timer. Args: block_header (BlockHeader): The BlockHeader to initialize. Returns: Boolean: True if the candidate block should be built. False if no candidate should be built. """ # If the previous block ID matches our cached one, that means that we # have already determined that even if we initialize the requested # block we would not be able to claim it. So, instead of wasting time # doing all of the checking again, simply short-circuit the failure so # that the validator can go do something more useful. if block_header.previous_block_id == \ PoetBlockPublisher._previous_block_id: return False PoetBlockPublisher._previous_block_id = block_header.previous_block_id # Using the current chain head, we need to create a state view so we # can create a PoET enclave. state_view = \ BlockWrapper.state_view_for_block( block_wrapper=self._block_cache.block_store.chain_head, state_view_factory=self._state_view_factory) poet_enclave_module = \ factory.PoetEnclaveFactory.get_poet_enclave_module( state_view=state_view, config_dir=self._config_dir, data_dir=self._data_dir) # Get our validator registry entry to see what PoET public key # other validators think we are using. validator_registry_view = ValidatorRegistryView(state_view) validator_info = None try: validator_id = block_header.signer_pubkey validator_info = \ validator_registry_view.get_validator_info( validator_id=validator_id) except KeyError: pass # If we don't have a validator registry entry, then check the active # key. If we don't have one, then we need to sign up. # If we do have one, then our validator registry entry has not # percolated through the system, so nothing to to but wait. active_poet_public_key = self._poet_key_state_store.active_key if validator_info is None: if active_poet_public_key is None: LOGGER.debug( 'No public key found, so going to register new signup ' 'information') self._register_signup_information( block_header=block_header, poet_enclave_module=poet_enclave_module) return False # Otherwise, we have a current validator registry entry. In that # case, we need to make sure that we are using the same PPK that the # other validators think we are using. If not, then we need to switch # the PoET enclave to using the correct keys. elif validator_info.signup_info.poet_public_key != \ active_poet_public_key: # Retrieve the key state corresponding to the PoET public key and # use it to re-establish the key used by the enclave. Also update # the active PoET public key. poet_key_state = \ self._poet_key_state_store[ validator_info.signup_info.poet_public_key] active_poet_public_key = \ SignupInfo.unseal_signup_data( poet_enclave_module=poet_enclave_module, sealed_signup_data=poet_key_state.sealed_signup_data) self._poet_key_state_store.active_key = active_poet_public_key assert active_poet_public_key == \ validator_info.signup_info.poet_public_key LOGGER.debug( 'Switched to public key: %s...%s', active_poet_public_key[:8], active_poet_public_key[-8:]) LOGGER.debug( 'Unseal signup data: %s...%s', poet_key_state.sealed_signup_data[:8], poet_key_state.sealed_signup_data[-8:]) consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=block_header.previous_block_id, block_cache=self._block_cache, state_view_factory=self._state_view_factory, consensus_state_store=self._consensus_state_store, poet_enclave_module=poet_enclave_module) poet_settings_view = PoetSettingsView(state_view) # If our signup information does not pass the freshness test, then we # know that other validators will reject any blocks we try to claim so # we need to try to sign up again. if consensus_state.validator_signup_was_committed_too_late( validator_info=validator_info, poet_settings_view=poet_settings_view, block_cache=self._block_cache): LOGGER.info( 'Reject building on block %s: Validator signup information ' 'not committed in a timely manner.', block_header.previous_block_id[:8]) self._register_signup_information( block_header=block_header, poet_enclave_module=poet_enclave_module) return False # Using the consensus state for the block upon which we want to # build, check to see how many blocks we have claimed on this chain # with this PoET key. If we have hit the key block claim limit, then # we need to check if the key has been refreshed. if consensus_state.validator_has_claimed_block_limit( validator_info=validator_info, poet_settings_view=poet_settings_view): # Because we have hit the limit, check to see if we have already # submitted a validator registry transaction with new signup # information, and therefore a new PoET public key. If not, then # mark this PoET public key in the store as having been refreshed # and register new signup information. Regardless, since we have # hit the key block claim limit, we won't even bother initializing # a block on this chain as it will be rejected by other # validators. poet_key_state = self._poet_key_state_store[active_poet_public_key] if not poet_key_state.has_been_refreshed: LOGGER.info( 'Reached block claim limit for key: %s...%s', active_poet_public_key[:8], active_poet_public_key[-8:]) sealed_signup_data = poet_key_state.sealed_signup_data self._poet_key_state_store[active_poet_public_key] = \ PoetKeyState( sealed_signup_data=sealed_signup_data, has_been_refreshed=True) self._register_signup_information( block_header=block_header, poet_enclave_module=poet_enclave_module) LOGGER.info( 'Reject building on block %s: Validator has reached maximum ' 'number of blocks with key pair.', block_header.previous_block_id[:8]) return False # Verify that we are abiding by the block claim delay (i.e., waiting a # certain number of blocks since our validator registry was added/ # updated). if consensus_state.validator_is_claiming_too_early( validator_info=validator_info, block_number=block_header.block_num, validator_registry_view=validator_registry_view, poet_settings_view=poet_settings_view, block_store=self._block_cache.block_store): LOGGER.info( 'Reject building on block %s: Validator has not waited long ' 'enough since registering validator information.', block_header.previous_block_id[:8]) return False # We need to create a wait timer for the block...this is what we # will check when we are asked if it is time to publish the block previous_certificate_id = \ utils.get_previous_certificate_id( block_header=block_header, block_cache=self._block_cache, poet_enclave_module=poet_enclave_module) wait_timer = \ WaitTimer.create_wait_timer( poet_enclave_module=poet_enclave_module, validator_address=block_header.signer_pubkey, previous_certificate_id=previous_certificate_id, consensus_state=consensus_state, poet_settings_view=poet_settings_view) # NOTE - we do the zTest after we create the wait timer because we # need its population estimate to see if this block would be accepted # by other validators based upon the zTest. # Check to see if by chance we were to be able to claim this block # if it would result in us winning more frequently than statistically # expected. If so, then refuse to initialize the block because other # validators will not accept anyway. if consensus_state.validator_is_claiming_too_frequently( validator_info=validator_info, previous_block_id=block_header.previous_block_id, poet_settings_view=poet_settings_view, population_estimate=wait_timer.population_estimate( poet_settings_view=poet_settings_view), block_cache=self._block_cache, poet_enclave_module=poet_enclave_module): LOGGER.info( 'Reject building on block %s: Validator is claiming blocks ' 'too frequently.', block_header.previous_block_id[:8]) return False # At this point, we know that if we are able to claim the block we are # initializing, we will not be prevented from doing so because of PoET # policies. self._wait_timer = wait_timer PoetBlockPublisher._previous_block_id = None LOGGER.debug('Created wait timer: %s', self._wait_timer) return True
def test_create(self): # Need to create signup information first signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) stake_in_the_sand = time.time() # create mock_poet_enclave_wait_timer mock_poet_enclave_wait_timer = \ mock.Mock(validator_address='1060 W Addison Street', duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, signature=None, serialized_timer=None, request_time=time.time()) # create mock_poet_enclave_simulator mock_poet_enclave_simulator = mock.Mock() mock_poet_enclave_simulator.create_wait_timer.return_value = \ mock_poet_enclave_wait_timer # An empty certificate list should result in a local mean that is # the target wait time wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) self.assertIsNotNone(wt) self.assertEqual(wt.local_mean, self.mock_poet_settings_view.target_wait_time) self.assertEqual(wt.previous_certificate_id, NULL_BLOCK_IDENTIFIER) self.assertGreaterEqual(wt.request_time, stake_in_the_sand) self.assertLessEqual(wt.request_time, time.time()) self.assertGreaterEqual(wt.duration, TestWaitTimer.MINIMUM_WAIT_TIME) self.assertEqual(wt.validator_address, '1060 W Addison Street') # Ensure that the enclave is set back to initial state self.poet_enclave_module = reload(poet_enclave) stake_in_the_sand = time.time() mock_poet_enclave_wait_timer.request_time = time.time() # An empty certificate list should result in a local mean that is # the target wait time wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) self.assertIsNotNone(wt) self.assertEqual(wt.local_mean, self.mock_poet_settings_view.target_wait_time) self.assertEqual(wt.previous_certificate_id, NULL_BLOCK_IDENTIFIER) self.assertGreaterEqual(wt.request_time, stake_in_the_sand) self.assertLessEqual(wt.request_time, time.time()) self.assertGreaterEqual(wt.duration, TestWaitTimer.MINIMUM_WAIT_TIME) self.assertEqual(wt.validator_address, '1060 W Addison Street')
def test_has_expired(self): # Need to create signup information first signup_info = SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # create mock_poet_enclave_wait_timer mock_poet_enclave_wait_timer = \ mock.Mock(validator_address='1060 W Addison Street', duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, signature=None, serialized_timer=None, request_time=time.time()) # create mock_poet_enclave_simulator mock_poet_enclave_simulator = mock.Mock() mock_poet_enclave_simulator.create_wait_timer.return_value = \ mock_poet_enclave_wait_timer # Verify that a timer doesn't expire before its creation time wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) self.assertFalse(wt.has_expired(wt.request_time - 1)) # Create a timer and when it has expired, verify that the duration is # not greater than actual elapsed time. wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) self.assertTrue(wt.has_expired(wt.request_time + wt.duration + 1.0)) # Tampering with the duration should not affect wait timer expiration wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) assigned_duration = wt.duration wt.duration = 0 self.assertTrue( wt.has_expired(wt.request_time + assigned_duration + 1.0)) # Tampering with the request time should not affect wait timer # expiration wt = wait_timer.WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_simulator, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1060 W Addison Street', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_settings_view=self.mock_poet_settings_view) assigned_request_time = wt.request_time wt.request_time -= wt.duration self.assertTrue( wt.has_expired(assigned_request_time + wt.duration + 1.0))
def validator_signup_was_committed_too_late(self, validator_info, poet_settings_view, block_cache): """Determines if a validator's registry transaction committing it current PoET keys, etc., was committed too late - i.e., the number of blocks between when the transaction was submitted and when it was committed is greater than signup commit maximum delay. Args: validator_info (ValidatorInfo): The current validator information poet_settings_view (PoetSettingsView): The current Poet config view block_cache (BlockCache): The block store cache Returns: bool: True if the validator's registry transaction was committed beyond the signup commit maximum delay, False otherwise """ # Figure out the block in which the current validator information # was committed. try: block = block_cache.block_store.get_block_by_transaction_id( validator_info.transaction_id) except ValueError: LOGGER.warning( 'Validator %s (ID=%s...%s): Signup txn %s not found in block.', validator_info.name, validator_info.id[:8], validator_info.id[-8:], validator_info.transaction_id[:8]) return False commit_block_id = block.identifier # Starting with that block's immediate predecessor, walk back until # either we match the block ID with the nonce field in the signup # info, we have checked the maximum number of blocks, or we somehow # reached the beginning of the blockchain. The first case is # success (i.e., the validator signup info passed the freshness # test) while the other two cases are failure. for _ in range(poet_settings_view.signup_commit_maximum_delay + 1): if SignupInfo.block_id_to_nonce(block.previous_block_id) == \ validator_info.signup_info.nonce: LOGGER.debug( 'Validator %s (ID=%s...%s): Signup committed block %s, ' 'chain head was block %s', validator_info.name, validator_info.id[:8], validator_info.id[-8:], commit_block_id[:8], block.previous_block_id[:8]) return False if utils.block_id_is_genesis(block.previous_block_id): LOGGER.info( 'Validator %s (ID=%s...%s): Signup committed block %s, ' 'hit start of blockchain looking for block %s', validator_info.name, validator_info.id[:8], validator_info.id[-8:], commit_block_id[:8], validator_info.signup_info.nonce[:8]) return True block = block_cache[block.previous_block_id] LOGGER.info( 'Validator %s (ID=%s...%s): Signup committed block %s, failed to ' 'find block with ID ending in %s in %d previous block(s)', validator_info.name, validator_info.id[:8], validator_info.id[-8:], commit_block_id[:8], validator_info.signup_info.nonce, poet_settings_view.signup_commit_maximum_delay + 1) return True
def test_serialization(self): # Need to create signup information and wait timer first signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) wt = \ WaitTimer.create_wait_timer( poet_enclave_module=self.poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.consensus_state, poet_config_view=self.mock_poet_config_view) while not wt.has_expired(time.time()): time.sleep(1) # Now we can create a wait certificate and serialize wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=self.poet_enclave_module, wait_timer=wt, block_hash="Reader's Digest") dumped = wc.dump() self.assertIsNotNone(dumped.get('SerializedCertificate')) self.assertIsNotNone(dumped.get('Signature')) # Deserialize and verify that wait certificates are the same # and that deserialized one is valid wc_copy = \ WaitCertificate.wait_certificate_from_serialized( poet_enclave_module=self.poet_enclave_module, serialized=dumped.get('SerializedCertificate'), signature=dumped.get('Signature')) self.assertEqual(wc.previous_certificate_id, wc_copy.previous_certificate_id) self.assertAlmostEqual(wc.local_mean, wc_copy.local_mean) self.assertAlmostEqual(wc.request_time, wc_copy.request_time) self.assertAlmostEqual(wc.duration, wc_copy.duration) self.assertEqual(wc.validator_address, wc_copy.validator_address) self.assertEqual(wc.block_hash, wc_copy.block_hash) self.assertEqual(wc.signature, wc_copy.signature) self.assertEqual(wc.identifier, wc_copy.identifier) # Serialize the copy and verify that its serialization and # signature are the same dumped_copy = wc_copy.dump() self.assertTrue(dumped.get('SerializedCertificate'), dumped_copy.get('SerializedCertificate')) self.assertTrue(dumped.get('Signature'), dumped_copy.get('Signature')) wc_copy.check_valid(poet_enclave_module=self.poet_enclave_module, previous_certificate_id=NULL_BLOCK_IDENTIFIER, poet_public_key=signup_info.poet_public_key, consensus_state=self.consensus_state, poet_config_view=self.mock_poet_config_view)
def _register_signup_information(self, block_header, poet_enclave_module): # Create signup information for this validator, putting the block ID # of the block previous to the block referenced by block_header in the # nonce. Block ID is better than wait certificate ID for testing # freshness as we need to account for non-PoET blocks. public_key_hash = \ hashlib.sha256( block_header.signer_public_key.encode()).hexdigest() nonce = SignupInfo.block_id_to_nonce(block_header.previous_block_id) signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=poet_enclave_module, originator_public_key_hash=public_key_hash, nonce=nonce) # Create the validator registry payload payload = \ vr_pb.ValidatorRegistryPayload( verb='register', name='validator-{}'.format(block_header.signer_public_key[:8]), id=block_header.signer_public_key, signup_info=vr_pb.SignUpInfo( poet_public_key=signup_info.poet_public_key, proof_data=signup_info.proof_data, anti_sybil_id=signup_info.anti_sybil_id, nonce=nonce), ) serialized = payload.SerializeToString() # Create the address that will be used to look up this validator # registry transaction. Seems like a potential for refactoring.. validator_entry_address = \ PoetBlockPublisher._validator_registry_namespace + \ hashlib.sha256(block_header.signer_public_key.encode()).hexdigest() # Create a transaction header and transaction for the validator # registry update amd then hand it off to the batch publisher to # send out. output_addresses = \ [validator_entry_address, PoetBlockPublisher._validator_map_address] input_addresses = \ output_addresses + \ [SettingsView.setting_address( 'sawtooth.poet.report_public_key_pem'), SettingsView.setting_address( 'sawtooth.poet.valid_enclave_measurements'), SettingsView.setting_address( 'sawtooth.poet.valid_enclave_basenames')] header = \ txn_pb.TransactionHeader( signer_public_key=block_header.signer_public_key, family_name='sawtooth_validator_registry', family_version='1.0', inputs=input_addresses, outputs=output_addresses, dependencies=[], payload_sha512=hashlib.sha512(serialized).hexdigest(), batcher_public_key=block_header.signer_public_key, nonce=time.time().hex().encode()).SerializeToString() signature = self._batch_publisher.identity_signer.sign(header) transaction = \ txn_pb.Transaction( header=header, payload=serialized, header_signature=signature) LOGGER.info( 'Register Validator Name=%s, ID=%s...%s, PoET public key=%s...%s, ' 'Nonce=%s', payload.name, payload.id[:8], payload.id[-8:], payload.signup_info.poet_public_key[:8], payload.signup_info.poet_public_key[-8:], nonce) self._batch_publisher.send([transaction]) # Store the key state so that we can look it up later if need be and # set the new key as our active key LOGGER.info( 'Save key state PPK=%s...%s => SSD=%s...%s', signup_info.poet_public_key[:8], signup_info.poet_public_key[-8:], signup_info.sealed_signup_data[:8], signup_info.sealed_signup_data[-8:]) self._poet_key_state_store[signup_info.poet_public_key] = \ PoetKeyState( sealed_signup_data=signup_info.sealed_signup_data, has_been_refreshed=False, signup_nonce=nonce) self._poet_key_state_store.active_key = signup_info.poet_public_key
def initialize_block(self, block_header): """Do initialization necessary for the consensus to claim a block, this may include initiating voting activities, starting proof of work hash generation, or create a PoET wait timer. Args: block_header (BlockHeader): The BlockHeader to initialize. Returns: Boolean: True if the candidate block should be built. False if no candidate should be built. """ # If the previous block ID matches our cached one, that means that we # have already determined that even if we initialize the requested # block we would not be able to claim it. So, instead of wasting time # doing all of the checking again, simply short-circuit the failure so # that the validator can go do something more useful. if block_header.previous_block_id == \ PoetBlockPublisher._previous_block_id: return False PoetBlockPublisher._previous_block_id = block_header.previous_block_id # Using the current chain head, we need to create a state view so we # can create a PoET enclave. state_view = \ BlockWrapper.state_view_for_block( block_wrapper=self._block_cache.block_store.chain_head, state_view_factory=self._state_view_factory) poet_enclave_module = \ factory.PoetEnclaveFactory.get_poet_enclave_module( state_view=state_view, config_dir=self._config_dir, data_dir=self._data_dir) # Get our validator registry entry to see what PoET public key # other validators think we are using. validator_registry_view = ValidatorRegistryView(state_view) validator_info = None try: validator_id = block_header.signer_public_key validator_info = \ validator_registry_view.get_validator_info( validator_id=validator_id) except KeyError: pass # If we don't have a validator registry entry, then check the active # key. If we don't have one, then we need to sign up. If we do have # one, then our validator registry entry has not percolated through the # system, so nothing to to but wait. active_poet_public_key = self._poet_key_state_store.active_key if validator_info is None: if active_poet_public_key is None: LOGGER.debug( 'No public key found, so going to register new signup ' 'information') self._register_signup_information( block_header=block_header, poet_enclave_module=poet_enclave_module) else: # Check if we need to give up on this registration attempt try: nonce = self._poet_key_state_store[ active_poet_public_key].signup_nonce except (ValueError, AttributeError): self._poet_key_state_store.active_key = None LOGGER.warning('Poet Key State Store had inaccessible or ' 'corrupt active key [%s] clearing ' 'key.', active_poet_public_key) return False self._handle_registration_timeout( block_header=block_header, poet_enclave_module=poet_enclave_module, state_view=state_view, signup_nonce=nonce, poet_public_key=active_poet_public_key ) return False # Retrieve the key state corresponding to the PoET public key in our # validator registry entry. poet_key_state = None try: poet_key_state = \ self._poet_key_state_store[ validator_info.signup_info.poet_public_key] except (ValueError, KeyError): pass # If there is no key state associated with the PoET public key that # other validators think we should be using, then we need to create # new signup information as we have no way whatsoever to publish # blocks that other validators will accept. if poet_key_state is None: LOGGER.debug( 'PoET public key %s...%s in validator registry not found in ' 'key state store. Sign up again', validator_info.signup_info.poet_public_key[:8], validator_info.signup_info.poet_public_key[-8:]) self._register_signup_information( block_header=block_header, poet_enclave_module=poet_enclave_module) # We need to put fake information in the key state store for the # PoET public key the other validators think we are using so that # we don't try to keep signing up. However, we are going to mark # that key state store entry as being refreshed so that we will # never actually try to use it. dummy_data = b64encode(b'No sealed signup data').decode('utf-8') self._poet_key_state_store[ validator_info.signup_info.poet_public_key] = \ PoetKeyState( sealed_signup_data=dummy_data, has_been_refreshed=True, signup_nonce='unknown') return False # Check the key state. If it is marked as being refreshed, then we are # waiting until our PoET public key is updated in the validator # registry and therefore we cannot publish any blocks. if poet_key_state.has_been_refreshed: LOGGER.debug( 'PoET public key %s...%s has been refreshed. Wait for new ' 'key to show up in validator registry.', validator_info.signup_info.poet_public_key[:8], validator_info.signup_info.poet_public_key[-8:]) # Check if we need to give up on this registration attempt self._handle_registration_timeout( block_header=block_header, poet_enclave_module=poet_enclave_module, state_view=state_view, signup_nonce=poet_key_state.signup_nonce, poet_public_key=active_poet_public_key ) return False # If the PoET public key in the validator registry is not the active # one, then we need to switch the active key in the key state store. if validator_info.signup_info.poet_public_key != \ active_poet_public_key: active_poet_public_key = validator_info.signup_info.poet_public_key self._poet_key_state_store.active_key = active_poet_public_key # Ensure that the enclave is using the appropriate keys try: unsealed_poet_public_key = \ SignupInfo.unseal_signup_data( poet_enclave_module=poet_enclave_module, sealed_signup_data=poet_key_state.sealed_signup_data) except SystemError: # Signup data is unuseable LOGGER.error( 'Could not unseal signup data associated with PPK: %s..%s', active_poet_public_key[:8], active_poet_public_key[-8:]) self._poet_key_state_store.active_key = None return False assert active_poet_public_key == unsealed_poet_public_key LOGGER.debug( 'Using PoET public key: %s...%s', active_poet_public_key[:8], active_poet_public_key[-8:]) LOGGER.debug( 'Unseal signup data: %s...%s', poet_key_state.sealed_signup_data[:8], poet_key_state.sealed_signup_data[-8:]) consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=block_header.previous_block_id, block_cache=self._block_cache, state_view_factory=self._state_view_factory, consensus_state_store=self._consensus_state_store, poet_enclave_module=poet_enclave_module) poet_settings_view = PoetSettingsView(state_view) # If our signup information does not pass the freshness test, then we # know that other validators will reject any blocks we try to claim so # we need to try to sign up again. if consensus_state.validator_signup_was_committed_too_late( validator_info=validator_info, poet_settings_view=poet_settings_view, block_cache=self._block_cache): LOGGER.info( 'Reject building on block %s: Validator signup information ' 'not committed in a timely manner.', block_header.previous_block_id[:8]) self._register_signup_information( block_header=block_header, poet_enclave_module=poet_enclave_module) return False # Using the consensus state for the block upon which we want to # build, check to see how many blocks we have claimed on this chain # with this PoET key. If we have hit the key block claim limit, then # we need to check if the key has been refreshed. if consensus_state.validator_has_claimed_block_limit( validator_info=validator_info, poet_settings_view=poet_settings_view): # Because we have hit the limit, check to see if we have already # submitted a validator registry transaction with new signup # information, and therefore a new PoET public key. If not, then # mark this PoET public key in the store as having been refreshed # and register new signup information. Regardless, since we have # hit the key block claim limit, we won't even bother initializing # a block on this chain as it will be rejected by other # validators. poet_key_state = self._poet_key_state_store[active_poet_public_key] if not poet_key_state.has_been_refreshed: LOGGER.info( 'Reached block claim limit for key: %s...%s', active_poet_public_key[:8], active_poet_public_key[-8:]) sealed_signup_data = poet_key_state.sealed_signup_data signup_nonce = poet_key_state.signup_nonce self._poet_key_state_store[active_poet_public_key] = \ PoetKeyState( sealed_signup_data=sealed_signup_data, has_been_refreshed=True, signup_nonce=signup_nonce) # Release enclave resources for this identity # This signup will be invalid on all forks that use it, # even if there is a rollback to a point it should be valid. # A more sophisticated policy would be to release signups # only at a block depth where finality probability # is high. SignupInfo.release_signup_data( poet_enclave_module=poet_enclave_module, sealed_signup_data=sealed_signup_data) self._register_signup_information( block_header=block_header, poet_enclave_module=poet_enclave_module) LOGGER.info( 'Reject building on block %s: Validator has reached maximum ' 'number of blocks with key pair.', block_header.previous_block_id[:8]) return False # Verify that we are abiding by the block claim delay (i.e., waiting a # certain number of blocks since our validator registry was added/ # updated). if consensus_state.validator_is_claiming_too_early( validator_info=validator_info, block_number=block_header.block_num, validator_registry_view=validator_registry_view, poet_settings_view=poet_settings_view, block_store=self._block_cache.block_store): LOGGER.info( 'Reject building on block %s: Validator has not waited long ' 'enough since registering validator information.', block_header.previous_block_id[:8]) return False # We need to create a wait timer for the block...this is what we # will check when we are asked if it is time to publish the block poet_key_state = self._poet_key_state_store[active_poet_public_key] sealed_signup_data = poet_key_state.sealed_signup_data previous_certificate_id = \ utils.get_previous_certificate_id( block_header=block_header, block_cache=self._block_cache, poet_enclave_module=poet_enclave_module) wait_timer = \ WaitTimer.create_wait_timer( poet_enclave_module=poet_enclave_module, sealed_signup_data=sealed_signup_data, validator_address=block_header.signer_public_key, previous_certificate_id=previous_certificate_id, consensus_state=consensus_state, poet_settings_view=poet_settings_view) # NOTE - we do the zTest after we create the wait timer because we # need its population estimate to see if this block would be accepted # by other validators based upon the zTest. # Check to see if by chance we were to be able to claim this block # if it would result in us winning more frequently than statistically # expected. If so, then refuse to initialize the block because other # validators will not accept anyway. if consensus_state.validator_is_claiming_too_frequently( validator_info=validator_info, previous_block_id=block_header.previous_block_id, poet_settings_view=poet_settings_view, population_estimate=wait_timer.population_estimate( poet_settings_view=poet_settings_view), block_cache=self._block_cache, poet_enclave_module=poet_enclave_module): LOGGER.info( 'Reject building on block %s: ' 'Validator (signing public key: %s) is claiming blocks ' 'too frequently.', block_header.previous_block_id[:8], block_header.signer_public_key) return False # At this point, we know that if we are able to claim the block we are # initializing, we will not be prevented from doing so because of PoET # policies. self._wait_timer = wait_timer PoetBlockPublisher._previous_block_id = None LOGGER.debug('Created wait timer: %s', self._wait_timer) return True
def _register_signup_information(self, block_header, poet_enclave_module): # Find the most-recent block in the block cache, if such a block # exists, and get its wait certificate ID wait_certificate_id = NULL_BLOCK_IDENTIFIER most_recent_block = self._block_cache.block_store.chain_head if most_recent_block is not None: wait_certificate = \ utils.deserialize_wait_certificate( block=most_recent_block, poet_enclave_module=poet_enclave_module) if wait_certificate is not None: wait_certificate_id = wait_certificate.identifier # Create signup information for this validator public_key_hash = \ hashlib.sha256( block_header.signer_pubkey.encode()).hexdigest() signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=poet_enclave_module, validator_address=block_header.signer_pubkey, originator_public_key_hash=public_key_hash, most_recent_wait_certificate_id=wait_certificate_id) # Create the validator registry payload payload = \ vr_pb.ValidatorRegistryPayload( verb='register', name='validator-{}'.format(block_header.signer_pubkey[-8:]), id=block_header.signer_pubkey, signup_info=vr_pb.SignUpInfo( poet_public_key=signup_info.poet_public_key, proof_data=signup_info.proof_data, anti_sybil_id=signup_info.anti_sybil_id), ) serialized = payload.SerializeToString() # Create the address that will be used to look up this validator # registry transaction. Seems like a potential for refactoring.. validator_entry_address = \ PoetBlockPublisher._validator_registry_namespace + \ hashlib.sha256(block_header.signer_pubkey.encode()).hexdigest() # Create a transaction header and transaction for the validator # registry update amd then hand it off to the batch publisher to # send out. addresses = \ [validator_entry_address, PoetBlockPublisher._validator_map_address] header = \ txn_pb.TransactionHeader( signer_pubkey=block_header.signer_pubkey, family_name='sawtooth_validator_registry', family_version='1.0', inputs=addresses, outputs=addresses, dependencies=[], payload_encoding="application/protobuf", payload_sha512=hashlib.sha512(serialized).hexdigest(), batcher_pubkey=block_header.signer_pubkey, nonce=time.time().hex().encode()).SerializeToString() signature = \ signing.sign(header, self._batch_publisher.identity_signing_key) transaction = \ txn_pb.Transaction( header=header, payload=serialized, header_signature=signature) self._batch_publisher.send([transaction]) LOGGER.info( 'Register Validator Name=%s, ID=%s...%s, PoET public key=%s...%s', payload.name, payload.id[:8], payload.id[-8:], payload.signup_info.poet_public_key[:8], payload.signup_info.poet_public_key[-8:]) # HACER: Once we have the consensus state implemented, we can # store this information in there. For now, we will store in the # class so it persists for the lifetime of the validator. PoetBlockPublisher._sealed_signup_data = \ signup_info.sealed_signup_data PoetBlockPublisher._poet_public_key = signup_info.poet_public_key
def test_serialization(self): # Need to create signup information and wait timer first signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # create mock_poet_enclave_wait_timer mock_poet_enclave_wait_timer = \ mock.Mock(validator_address='1060 W Addison Street', duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, signature='00112233445566778899aabbccddeeff', serialized_timer=None, request_time=time.time()) # create mock_poet_enclave_wait_certificate mock_poet_enclave_wait_certificate = \ mock.Mock(duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, request_time=time.time(), validator_address='1060 W Addison Street', nonce=NULL_BLOCK_IDENTIFIER, block_hash="Reader's Digest", signature='00112233445566778899aabbccddeeff', serialized_certificate=None) # create mock_poet_enclave_module mock_poet_enclave_module = mock.Mock() mock_poet_enclave_module.create_wait_timer.return_value = \ mock_poet_enclave_wait_timer # set the mock enclave wait certificate and wait timer to # have the same previous_certificate_id mock_poet_enclave_wait_certificate.previous_certificate_id = \ mock_poet_enclave_wait_timer.previous_certificate_id mock_poet_enclave_module.create_wait_certificate.return_value = \ mock_poet_enclave_wait_certificate mock_poet_enclave_module.deserialize_wait_certificate.return_value = \ mock_poet_enclave_wait_certificate wt = \ WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) # Now we can create a wait certificate and serialize wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=mock_poet_enclave_module, sealed_signup_data=signup_info.sealed_signup_data, wait_timer=wt, block_hash="Reader's Digest") dumped = wc.dump() self.assertIsNotNone(dumped.get('SerializedCertificate')) self.assertIsNotNone(dumped.get('Signature')) # Deserialize and verify that wait certificates are the same # and that deserialized one is valid wc_copy = \ WaitCertificate.wait_certificate_from_serialized( poet_enclave_module=mock_poet_enclave_module, serialized=dumped.get('SerializedCertificate'), signature=dumped.get('Signature')) self.assertEqual( wc.previous_certificate_id, wc_copy.previous_certificate_id) self.assertEqual(wc.local_mean, wc_copy.local_mean) self.assertAlmostEqual(wc.request_time, wc_copy.request_time) self.assertAlmostEqual(wc.duration, wc_copy.duration) self.assertEqual(wc.validator_address, wc_copy.validator_address) self.assertEqual(wc.block_hash, wc_copy.block_hash) self.assertEqual(wc.signature, wc_copy.signature) self.assertEqual(wc.identifier, wc_copy.identifier) # Serialize the copy and verify that its serialization and # signature are the same dumped_copy = wc_copy.dump() self.assertTrue( dumped.get('SerializedCertificate'), dumped_copy.get('SerializedCertificate')) self.assertTrue( dumped.get('Signature'), dumped_copy.get('Signature')) wc_copy.check_valid( poet_enclave_module=mock_poet_enclave_module, previous_certificate_id=NULL_BLOCK_IDENTIFIER, poet_public_key=signup_info.poet_public_key, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view)
def initialize_block(self, block_header): """Do initialization necessary for the consensus to claim a block, this may include initiating voting activities, starting proof of work hash generation, or create a PoET wait timer. Args: block_header (BlockHeader): The BlockHeader to initialize. Returns: Boolean: True if the candidate block should be built. False if no candidate should be built. """ # Using the current chain head, we need to create a state view so we # can create a PoET enclave. We are going to special case this until # the genesis consensus is available. We know that the genesis block # is special cased to have a state view constructed for it. state_root_hash = \ self._block_cache.block_store.chain_head.state_root_hash \ if self._block_cache.block_store.chain_head is not None \ else block_header.state_root_hash state_view = self._state_view_factory.create_view(state_root_hash) poet_enclave_module = \ factory.PoetEnclaveFactory.get_poet_enclave_module(state_view) # HACER: Once we have PoET consensus state, we should be looking in # there for the sealed signup data. For the time being, signup # information will be tied to the lifetime of the validator. # HACER: Once the genesis utility is creating the validator registry # transaction, we no longer need to do this. But for now, if we are # creating the genesis block, we need to re-establish the state of # the enclave using pre-canned sealed signup data so that we can # create the wait timer and certificate. Note that this is only # used for the genesis block. Going forward after the genesis block, # all validators will use generated signup information. if utils.block_id_is_genesis(block_header.previous_block_id): LOGGER.debug( 'Creating genesis block, so will use sealed signup data') SignupInfo.unseal_signup_data( poet_enclave_module=poet_enclave_module, validator_address=block_header.signer_pubkey, sealed_signup_data=PoetBlockPublisher._sealed_signup_data) # HACER: Otherwise, if it is not the first block and we don't already # have a public key, we need to create signup information and create a # transaction to add it to the validator registry. elif PoetBlockPublisher._poet_public_key is None: self._register_signup_information( block_header=block_header, poet_enclave_module=poet_enclave_module) # Create a list of certificates for the wait timer. This seems to have # a little too much knowledge of the WaitTimer implementation, but # there is no use getting more than # WaitTimer.certificate_sample_length wait certificates. certificates = \ utils.build_certificate_list( block_header=block_header, block_cache=self._block_cache, poet_enclave_module=poet_enclave_module, maximum_number=WaitTimer.certificate_sample_length) # We need to create a wait timer for the block...this is what we # will check when we are asked if it is time to publish the block self._wait_timer = \ WaitTimer.create_wait_timer( poet_enclave_module=poet_enclave_module, validator_address=block_header.signer_pubkey, certificates=list(certificates)) LOGGER.debug('Created wait timer: %s', self._wait_timer) return True
def test_serialization(self): # Need to create signup information and wait timer first signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=self.poet_enclave_module, originator_public_key_hash=self._originator_public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) # create mock_poet_enclave_wait_timer mock_poet_enclave_wait_timer = \ mock.Mock(validator_address='1060 W Addison Street', duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, signature='00112233445566778899aabbccddeeff', serialized_timer=None, request_time=time.time()) # create mock_poet_enclave_wait_certificate mock_poet_enclave_wait_certificate = \ mock.Mock(duration=1.0, previous_certificate_id=NULL_BLOCK_IDENTIFIER, local_mean=5.0, request_time=time.time(), validator_address='1060 W Addison Street', nonce=NULL_BLOCK_IDENTIFIER, block_hash="Reader's Digest", signature='00112233445566778899aabbccddeeff', serialized_certificate=None) # create mock_poet_enclave_module mock_poet_enclave_module = mock.Mock() mock_poet_enclave_module.create_wait_timer.return_value = \ mock_poet_enclave_wait_timer # set the mock enclave wait certificate and wait timer to # have the same previous_certificate_id mock_poet_enclave_wait_certificate.previous_certificate_id = \ mock_poet_enclave_wait_timer.previous_certificate_id mock_poet_enclave_module.create_wait_certificate.return_value = \ mock_poet_enclave_wait_certificate mock_poet_enclave_module.deserialize_wait_certificate.return_value = \ mock_poet_enclave_wait_certificate wt = \ WaitTimer.create_wait_timer( poet_enclave_module=mock_poet_enclave_module, validator_address='1660 Pennsylvania Avenue NW', previous_certificate_id=NULL_BLOCK_IDENTIFIER, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view) # Now we can create a wait certificate and serialize wc = \ WaitCertificate.create_wait_certificate( poet_enclave_module=mock_poet_enclave_module, wait_timer=wt, block_hash="Reader's Digest") dumped = wc.dump() self.assertIsNotNone(dumped.get('SerializedCertificate')) self.assertIsNotNone(dumped.get('Signature')) # Deserialize and verify that wait certificates are the same # and that deserialized one is valid wc_copy = \ WaitCertificate.wait_certificate_from_serialized( poet_enclave_module=mock_poet_enclave_module, serialized=dumped.get('SerializedCertificate'), signature=dumped.get('Signature')) self.assertEqual( wc.previous_certificate_id, wc_copy.previous_certificate_id) self.assertEqual(wc.local_mean, wc_copy.local_mean) self.assertAlmostEqual(wc.request_time, wc_copy.request_time) self.assertAlmostEqual(wc.duration, wc_copy.duration) self.assertEqual(wc.validator_address, wc_copy.validator_address) self.assertEqual(wc.block_hash, wc_copy.block_hash) self.assertEqual(wc.signature, wc_copy.signature) self.assertEqual(wc.identifier, wc_copy.identifier) # Serialize the copy and verify that its serialization and # signature are the same dumped_copy = wc_copy.dump() self.assertTrue( dumped.get('SerializedCertificate'), dumped_copy.get('SerializedCertificate')) self.assertTrue( dumped.get('Signature'), dumped_copy.get('Signature')) wc_copy.check_valid( poet_enclave_module=mock_poet_enclave_module, previous_certificate_id=NULL_BLOCK_IDENTIFIER, poet_public_key=signup_info.poet_public_key, consensus_state=self.mock_consensus_state, poet_settings_view=self.mock_poet_settings_view)
def _register_signup_information(self, block_header, poet_enclave_module): # Create signup information for this validator, putting the block ID # of the block previous to the block referenced by block_header in the # nonce. Block ID is better than wait certificate ID for testing # freshness as we need to account for non-PoET blocks. public_key_hash = \ hashlib.sha256( block_header.signer_public_key.encode()).hexdigest() nonce = SignupInfo.block_id_to_nonce(block_header.previous_block_id) signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=poet_enclave_module, originator_public_key_hash=public_key_hash, nonce=nonce) # Create the validator registry payload payload = \ vr_pb.ValidatorRegistryPayload( verb='register', name='validator-{}'.format(block_header.signer_public_key[:8]), id=block_header.signer_public_key, signup_info=vr_pb.SignUpInfo( poet_public_key=signup_info.poet_public_key, proof_data=signup_info.proof_data, anti_sybil_id=signup_info.anti_sybil_id, nonce=nonce), ) serialized = payload.SerializeToString() # Create the address that will be used to look up this validator # registry transaction. Seems like a potential for refactoring.. validator_entry_address = \ PoetBlockPublisher._validator_registry_namespace + \ hashlib.sha256(block_header.signer_public_key.encode()).hexdigest() # Create a transaction header and transaction for the validator # registry update amd then hand it off to the batch publisher to # send out. output_addresses = \ [validator_entry_address, PoetBlockPublisher._validator_map_address] input_addresses = \ output_addresses + \ [SettingsView.setting_address( 'sawtooth.poet.report_public_key_pem'), SettingsView.setting_address( 'sawtooth.poet.valid_enclave_measurements'), SettingsView.setting_address( 'sawtooth.poet.valid_enclave_basenames')] header = \ txn_pb.TransactionHeader( signer_public_key=block_header.signer_public_key, family_name='sawtooth_validator_registry', family_version='1.0', inputs=input_addresses, outputs=output_addresses, dependencies=[], payload_sha512=hashlib.sha512(serialized).hexdigest(), batcher_public_key=block_header.signer_public_key, nonce=time.time().hex().encode()).SerializeToString() signature = self._batch_publisher.identity_signer.sign(header) transaction = \ txn_pb.Transaction( header=header, payload=serialized, header_signature=signature) LOGGER.info( 'Register Validator Name=%s, ID=%s...%s, PoET public key=%s...%s, ' 'Nonce=%s', payload.name, payload.id[:8], payload.id[-8:], payload.signup_info.poet_public_key[:8], payload.signup_info.poet_public_key[-8:], nonce) self._batch_publisher.send([transaction]) # Store the key state so that we can look it up later if need be and # set the new key as our active key LOGGER.info('Save key state PPK=%s...%s => SSD=%s...%s', signup_info.poet_public_key[:8], signup_info.poet_public_key[-8:], signup_info.sealed_signup_data[:8], signup_info.sealed_signup_data[-8:]) self._poet_key_state_store[signup_info.poet_public_key] = \ PoetKeyState( sealed_signup_data=signup_info.sealed_signup_data, has_been_refreshed=False, signup_nonce=nonce) self._poet_key_state_store.active_key = signup_info.poet_public_key