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 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 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. block = \ block_cache.block_store.get_block_by_transaction_id( validator_info.transaction_id) 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.error( '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.error( '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 _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_pubkey.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_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, 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_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. 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_pubkey=block_header.signer_pubkey, family_name='sawtooth_validator_registry', family_version='1.0', inputs=input_addresses, outputs=output_addresses, dependencies=[], 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) 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) self._poet_key_state_store.active_key = signup_info.poet_public_key
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() 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=SignupInfo.block_id_to_nonce(args.block)) 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) # 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 _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 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 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