def _build_population_estimate_list(self, block_id, poet_settings_view, block_cache, poet_enclave_module): """Starting at the block provided, walk back the blocks and collect the population estimates. Args: block_id (str): The ID of the block to start with poet_settings_view (PoetSettingsView): The current PoET settings view block_cache (BlockCache): The block store cache poet_enclave_module (module): The PoET enclave module Returns: deque: The list, in order of most-recent block to least-recent block, of _PopulationEstimate objects. """ population_estimate_list = collections.deque() # Until we get to the first fixed-duration block (i.e., a block for # which the local mean is simply a ratio of the target and initial wait # times), first look in our population estimate cache for the # population estimate information and if not there fetch the block. # Then add the value to the population estimate list. # # Note that since we know the total block claim count from the # consensus state object, we don't have to worry about non-PoET blocks. # Using that value and the fixed duration block count from the PoET # settings view, we know now many blocks to get. number_of_blocks = \ self.total_block_claim_count - \ poet_settings_view.population_estimate_sample_size with ConsensusState._population_estimate_cache_lock: for _ in range(number_of_blocks): population_cache_entry = \ ConsensusState._population_estimate_cache.get(block_id) if population_cache_entry is None: block = block_cache[block_id] wait_certificate = \ utils.deserialize_wait_certificate( block=block, poet_enclave_module=poet_enclave_module) population_estimate = \ wait_certificate.population_estimate( poet_settings_view=poet_settings_view) population_cache_entry = \ ConsensusState._EstimateInfo( population_estimate=population_estimate, previous_block_id=block.previous_block_id, validator_id=block.header.signer_public_key) ConsensusState._population_estimate_cache[block_id] = \ population_cache_entry population_estimate_list.append(population_cache_entry) block_id = population_cache_entry.previous_block_id return population_estimate_list
def consensus_state_for_block_id(block_id, block_cache, state_view_factory, consensus_state_store, poet_enclave_module): """Returns the consensus state for the block referenced by block ID, creating it from the consensus state history if necessary. Args: block_id (str): The ID of the block for which consensus state will be returned. block_cache (BlockCache): The block store cache state_view_factory (StateViewFactory): A factory that can be used to create state view object corresponding to blocks consensus_state_store (ConsensusStateStore): The consensus state store that is used to store interim consensus state created up to resulting consensus state poet_enclave_module (module): The PoET enclave module Returns: ConsensusState object representing the consensus state for the block referenced by block_id """ consensus_state = None previous_wait_certificate = None blocks = collections.OrderedDict() # Starting at the chain head, walk the block store backwards until we # either get to the root or we get a block for which we have already # created consensus state current_id = block_id while True: block = \ ConsensusState._block_for_id( block_id=current_id, block_cache=block_cache) if block is None: break # Try to fetch the consensus state. If that succeeds, we can # stop walking back as we can now build on that consensus # state. consensus_state = consensus_state_store.get(block_id=current_id) if consensus_state is not None: break wait_certificate = \ utils.deserialize_wait_certificate( block=block, poet_enclave_module=poet_enclave_module) # If this is a PoET block (i.e., it has a wait certificate), get # the validator info for the validator that signed this block and # add the block information we will need to set validator state in # the block's consensus state. if wait_certificate is not None: state_view = \ state_view_factory.create_view( state_root_hash=block.state_root_hash) validator_registry_view = \ ValidatorRegistryView(state_view=state_view) validator_info = \ validator_registry_view.get_validator_info( validator_id=block.header.signer_pubkey) LOGGER.debug( 'We need to build consensus state for block: %s...%s', current_id[:8], current_id[-8:]) blocks[current_id] = \ ConsensusState._BlockInfo( wait_certificate=wait_certificate, validator_info=validator_info, poet_settings_view=PoetSettingsView(state_view)) # Otherwise, this is a non-PoET block. If we don't have any blocks # yet or the last block we processed was a PoET block, put a # placeholder in the list so that when we get to it we know that we # need to reset the statistics. elif not blocks or previous_wait_certificate is not None: blocks[current_id] = \ ConsensusState._BlockInfo( wait_certificate=None, validator_info=None, poet_settings_view=None) previous_wait_certificate = wait_certificate # Move to the previous block current_id = block.previous_block_id # At this point, if we have not found any consensus state, we need to # create default state from which we can build upon if consensus_state is None: consensus_state = ConsensusState() # Now, walk through the blocks for which we were supposed to create # consensus state, from oldest to newest (i.e., in the reverse order in # which they were added), and store state for PoET blocks so that the # next time we don't have to walk so far back through the block chain. for current_id, block_info in reversed(blocks.items()): # If the block was not a PoET block (i.e., didn't have a wait # certificate), reset the consensus state statistics. We are not # going to store this in the consensus state store, but we will use # it as the starting for the next PoET block. if block_info.wait_certificate is None: consensus_state = ConsensusState() # Otherwise, let the consensus state update itself appropriately # based upon the validator claiming a block, and then associate the # consensus state with the new block in the store. # validator state for the validator which claimed the block, create # updated validator state for the validator, set/update the # validator state in the consensus state object, and then associate # the consensus state with the corresponding block in the consensus # state store. else: consensus_state.validator_did_claim_block( validator_info=block_info.validator_info, wait_certificate=block_info.wait_certificate, poet_settings_view=block_info.poet_settings_view) consensus_state_store[current_id] = consensus_state LOGGER.debug('Create consensus state: BID=%s, ALM=%f, TBCC=%d', current_id[:8], consensus_state.aggregate_local_mean, consensus_state.total_block_claim_count) return consensus_state
def compare_forks(self, cur_fork_head, new_fork_head): """Given the head of two forks, return which should be the fork that the validator chooses. When this is called both forks consist of only valid blocks. Args: cur_fork_head (Block): The current head of the block chain. new_fork_head (Block): The head of the fork that is being evaluated. Returns: Boolean: True if the new chain should replace the current chain. False if the new chain should be discarded. """ chosen_fork_head = None state_view = \ BlockWrapper.state_view_for_block( block_wrapper=cur_fork_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) current_fork_wait_certificate = \ utils.deserialize_wait_certificate( block=cur_fork_head, poet_enclave_module=poet_enclave_module) new_fork_wait_certificate = \ utils.deserialize_wait_certificate( block=new_fork_head, poet_enclave_module=poet_enclave_module) # If we ever get a new fork head that is not a PoET block, then bail # out. This should never happen, but defensively protect against it. if new_fork_wait_certificate is None: raise \ TypeError( 'New fork head {} is not a PoET block'.format( new_fork_head.identifier[:8])) # Criterion #1: If the current fork head is not PoET, then check to see # if the new fork head is building on top of it. That would be okay. # However if not, then we don't have a good deterministic way of # choosing a winner. Again, the latter should never happen, but # defensively protect against it. if current_fork_wait_certificate is None: if new_fork_head.previous_block_id == cur_fork_head.identifier: LOGGER.info( 'Choose new fork %s over current fork %s: ' 'New fork head switches consensus to PoET', new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8]) chosen_fork_head = new_fork_head else: raise \ TypeError( 'Trying to compare a PoET block {} to a non-PoET ' 'block {} that is not the direct predecessor'.format( new_fork_head.identifier[:8], cur_fork_head.identifier[:8])) # Criterion #2: If they share the same immediate previous block, # then the one with the smaller wait duration is chosen elif cur_fork_head.previous_block_id == \ new_fork_head.previous_block_id: if current_fork_wait_certificate.duration < \ new_fork_wait_certificate.duration: LOGGER.info( 'Choose current fork %s over new fork %s: ' 'Current fork wait duration (%f) less than new fork wait ' 'duration (%f)', cur_fork_head.header_signature[:8], new_fork_head.header_signature[:8], current_fork_wait_certificate.duration, new_fork_wait_certificate.duration) chosen_fork_head = cur_fork_head elif new_fork_wait_certificate.duration < \ current_fork_wait_certificate.duration: LOGGER.info( 'Choose new fork %s over current fork %s: ' 'New fork wait duration (%f) less than current fork wait ' 'duration (%f)', new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8], new_fork_wait_certificate.duration, current_fork_wait_certificate.duration) chosen_fork_head = new_fork_head # Criterion #3: If they don't share the same immediate previous # block, then the one with the higher aggregate local mean wins else: # Get the consensus state for the current fork head and the # block immediately before the new fork head (as we haven't # committed to the block yet). So that the new fork doesn't # have to fight with one hand tied behind its back, add the # new fork head's wait certificate's local mean to the # aggregate local mean for the predecessor block's consensus # state for the comparison. current_fork_consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=cur_fork_head.identifier, 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) new_fork_consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=new_fork_head.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) new_fork_aggregate_local_mean = \ new_fork_consensus_state.aggregate_local_mean + \ new_fork_wait_certificate.local_mean if current_fork_consensus_state.aggregate_local_mean > \ new_fork_aggregate_local_mean: LOGGER.info( 'Choose current fork %s over new fork %s: ' 'Current fork aggregate local mean (%f) greater than new ' 'fork aggregate local mean (%f)', cur_fork_head.header_signature[:8], new_fork_head.header_signature[:8], current_fork_consensus_state.aggregate_local_mean, new_fork_aggregate_local_mean) chosen_fork_head = cur_fork_head elif new_fork_aggregate_local_mean > \ current_fork_consensus_state.aggregate_local_mean: LOGGER.info( 'Choose new fork %s over current fork %s: ' 'New fork aggregate local mean (%f) greater than current ' 'fork aggregate local mean (%f)', new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8], new_fork_aggregate_local_mean, current_fork_consensus_state.aggregate_local_mean) chosen_fork_head = new_fork_head # Criterion #4: If we have gotten to this point and we have not chosen # yet, we are going to fall back on using the block identifiers # (header signatures) . The lexicographically larger one will be the # chosen one. The chance that they are equal are infinitesimally # small. if chosen_fork_head is None: if cur_fork_head.header_signature > \ new_fork_head.header_signature: LOGGER.info( 'Choose current fork %s over new fork %s: ' 'Current fork header signature (%s) greater than new fork ' 'header signature (%s)', cur_fork_head.header_signature[:8], new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8], new_fork_head.header_signature[:8]) chosen_fork_head = cur_fork_head else: LOGGER.info( 'Choose new fork %s over current fork %s: ' 'New fork header signature (%s) greater than current fork ' 'header signature (%s)', new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8], new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8]) chosen_fork_head = new_fork_head # Now that we have chosen a fork for the chain head, if we chose the # new fork and it is a PoET block (i.e., it has a wait certificate), # we need to create consensus state store information for the new # fork's chain head. if chosen_fork_head == new_fork_head: # Get the state view for the previous block in the chain so we can # create a PoET enclave previous_block = None try: previous_block = \ self._block_cache[new_fork_head.previous_block_id] except KeyError: pass state_view = \ BlockWrapper.state_view_for_block( block_wrapper=previous_block, state_view_factory=self._state_view_factory) validator_registry_view = ValidatorRegistryView(state_view) try: # Get the validator info for the validator that claimed the # fork head validator_info = \ validator_registry_view.get_validator_info( new_fork_head.header.signer_public_key) # Get the consensus state for the new fork head's previous # block, let the consensus state update itself appropriately # based upon the validator claiming a block, and then # associate the consensus state with the new block in the # store. consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=new_fork_head.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) consensus_state.validator_did_claim_block( validator_info=validator_info, wait_certificate=new_fork_wait_certificate, poet_settings_view=PoetSettingsView(state_view)) self._consensus_state_store[new_fork_head.identifier] = \ consensus_state LOGGER.debug('Create consensus state: BID=%s, ALM=%f, TBCC=%d', new_fork_head.identifier[:8], consensus_state.aggregate_local_mean, consensus_state.total_block_claim_count) except KeyError: # This _should_ never happen. The new potential fork head # has to have been a PoET block and for it to be verified # by the PoET block verifier, it must have been signed by # validator in the validator registry. If not found, we # are going to just stick with the current fork head. LOGGER.error( 'New fork head claimed by validator not in validator ' 'registry: %s...%s', new_fork_head.header.signer_public_key[:8], new_fork_head.header.signer_public_key[-8:]) chosen_fork_head = cur_fork_head return chosen_fork_head == new_fork_head
def verify_block(self, block_wrapper): """Check that the block received conforms to the consensus rules. Args: block_wrapper (BlockWrapper): The block to validate. Returns: Boolean: True if the Block is valid, False if the block is invalid. """ # Get the state view for the previous block in the chain so we can # create a PoET enclave and validator registry view previous_block = None try: previous_block = \ self._block_cache[block_wrapper.previous_block_id] except KeyError: pass state_view = \ BlockWrapper.state_view_for_block( block_wrapper=previous_block, 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) validator_registry_view = ValidatorRegistryView(state_view) # Grab the validator info based upon the block signer's public # key try: validator_info = \ validator_registry_view.get_validator_info( block_wrapper.header.signer_pubkey) except KeyError: LOGGER.error( 'Block %s rejected: Received block from an unregistered ' 'validator %s...%s', block_wrapper.identifier[:8], block_wrapper.header.signer_pubkey[:8], block_wrapper.header.signer_pubkey[-8:]) return False LOGGER.debug( 'Block Signer Name=%s, ID=%s...%s, PoET public key=' '%s...%s', validator_info.name, validator_info.id[:8], validator_info.id[-8:], validator_info.signup_info.poet_public_key[:8], validator_info.signup_info.poet_public_key[-8:]) # For the candidate block, reconstitute the wait certificate # and verify that it is valid wait_certificate = \ utils.deserialize_wait_certificate( block=block_wrapper, poet_enclave_module=poet_enclave_module) if wait_certificate is None: LOGGER.error( 'Block %s rejected: Block from validator %s (ID=%s...%s) was ' 'not created by PoET consensus module', block_wrapper.identifier[:8], validator_info.name, validator_info.id[:8], validator_info.id[-8:]) return False # Get the consensus state and PoET configuration view for the block # that is being built upon consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=block_wrapper.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_config_view = PoetConfigView(state_view=state_view) previous_certificate_id = \ utils.get_previous_certificate_id( block_header=block_wrapper.header, block_cache=self._block_cache, poet_enclave_module=poet_enclave_module) try: wait_certificate.check_valid( poet_enclave_module=poet_enclave_module, previous_certificate_id=previous_certificate_id, poet_public_key=validator_info.signup_info.poet_public_key, consensus_state=consensus_state, poet_config_view=poet_config_view) except ValueError as error: LOGGER.error( 'Block %s rejected: Wait certificate check failed - %s', block_wrapper.identifier[:8], error) return False # Reject the block if the validator signup information fails the # freshness check. if consensus_state.validator_signup_was_committed_too_late( validator_info=validator_info, poet_config_view=poet_config_view, block_cache=self._block_cache): LOGGER.error( 'Block %s rejected: Validator signup information not ' 'committed in a timely manner.', block_wrapper.identifier[:8]) return False # Reject the block if the validator has already claimed the key bock # limit for its current PoET key pair. if consensus_state.validator_has_claimed_block_limit( validator_info=validator_info, poet_config_view=poet_config_view): LOGGER.error( 'Block %s rejected: Validator has reached maximum number of ' 'blocks with key pair.', block_wrapper.identifier[:8]) return False # Reject the block if the validator has not waited the required number # of blocks between when the block containing its validator registry # transaction was committed to the chain and trying to claim this # block if consensus_state.validator_is_claiming_too_early( validator_info=validator_info, block_number=block_wrapper.block_num, validator_registry_view=validator_registry_view, poet_config_view=poet_config_view, block_store=self._block_cache.block_store): LOGGER.error( 'Block %s rejected: Validator has not waited long enough ' 'since registering validator information.', block_wrapper.identifier[:8]) return False # Reject the block if the validator is claiming blocks at a rate that # is more frequent than is statistically allowed (i.e., zTest) if consensus_state.validator_is_claiming_too_frequently( validator_info=validator_info, previous_block_id=block_wrapper.previous_block_id, poet_config_view=poet_config_view, population_estimate=wait_certificate.population_estimate( poet_config_view=poet_config_view), block_cache=self._block_cache, poet_enclave_module=poet_enclave_module): LOGGER.error( 'Block %s rejected: Validator is claiming blocks too ' 'frequently.', block_wrapper.identifier[:8]) return False 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) 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:]) self._batch_publisher.send([transaction]) # Store the key state so that we can look it up later if need be 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) # Cache the PoET public key in a class to indicate that this is the # current public key for the PoET enclave PoetBlockPublisher._poet_public_key = signup_info.poet_public_key
def verify_block(self, block_wrapper): """Check that the block received conforms to the consensus rules. Args: block_wrapper (BlockWrapper): The block to validate. Returns: Boolean: True if the Block is valid, False if the block is invalid. """ # HACER: If we don't have any validators registered yet, we are going # to immediately approve the block. We cannot test the block's wait # certificate until we can retrieve the block signer's corresponding # public key. We cannot do that until the genesis block is populated # with the "initial" validator's signup information validator_registry_view = ValidatorRegistryView(self._state_view) if len(validator_registry_view.get_validators()) > 0: try: # Grab the validator info based upon the block signer's public # key validator_info = \ validator_registry_view.get_validator_info( block_wrapper.header.signer_pubkey) LOGGER.debug( 'Block Signer Name=%s, ID=%s...%s, PoET public key=' '%s...%s', validator_info.name, validator_info.id[:8], validator_info.id[-8:], validator_info.signup_info.poet_public_key[:8], validator_info.signup_info.poet_public_key[-8:]) # Create a list of certificates leading up to this block. # 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_wrapper.header, block_cache=self._block_cache, poet_enclave_module=self._poet_enclave_module, maximum_number=WaitTimer.certificate_sample_length) # For the candidate block, reconstitute the wait certificate # and verify that it is valid wait_certificate = \ utils.deserialize_wait_certificate( block=block_wrapper, poet_enclave_module=self._poet_enclave_module) wait_certificate.check_valid( poet_enclave_module=self._poet_enclave_module, certificates=certificates, poet_public_key=validator_info.signup_info.poet_public_key) except KeyError: LOGGER.error( 'Attempted to verify block from validator with no ' 'validator registry entry') return False except ValueError as ve: LOGGER.error('Wait certificate is not valid: %s', str(ve)) LOGGER.warning('We will accept for now') else: LOGGER.warning( 'Block accepted by default because no validators registered') return True
def verify_block(self, block_wrapper): """Check that the block received conforms to the consensus rules. Args: block_wrapper (BlockWrapper): The block to validate. Returns: Boolean: True if the Block is valid, False if the block is invalid. """ # Get the state view for the previous block in the chain so we can # create a PoET enclave and validator registry view previous_block = None try: previous_block = \ self._block_cache[block_wrapper.previous_block_id] except KeyError: pass state_view = \ BlockWrapper.state_view_for_block( block_wrapper=previous_block, 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) validator_registry_view = ValidatorRegistryView(state_view) # Grab the validator info based upon the block signer's public # key try: validator_info = \ validator_registry_view.get_validator_info( block_wrapper.header.signer_public_key) except KeyError: LOGGER.error( 'Block %s rejected: Received block from an unregistered ' 'validator %s...%s', block_wrapper.identifier[:8], block_wrapper.header.signer_public_key[:8], block_wrapper.header.signer_public_key[-8:]) return False LOGGER.debug( 'Block Signer Name=%s, ID=%s...%s, PoET public key=' '%s...%s', validator_info.name, validator_info.id[:8], validator_info.id[-8:], validator_info.signup_info.poet_public_key[:8], validator_info.signup_info.poet_public_key[-8:]) # For the candidate block, reconstitute the wait certificate # and verify that it is valid wait_certificate = \ utils.deserialize_wait_certificate( block=block_wrapper, poet_enclave_module=poet_enclave_module) if wait_certificate is None: LOGGER.error( 'Block %s rejected: Block from validator %s (ID=%s...%s) was ' 'not created by PoET consensus module', block_wrapper.identifier[:8], validator_info.name, validator_info.id[:8], validator_info.id[-8:]) return False # Get the consensus state and PoET configuration view for the block # that is being built upon consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=block_wrapper.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=state_view) previous_certificate_id = \ utils.get_previous_certificate_id( block_header=block_wrapper.header, block_cache=self._block_cache, poet_enclave_module=poet_enclave_module) try: wait_certificate.check_valid( poet_enclave_module=poet_enclave_module, previous_certificate_id=previous_certificate_id, poet_public_key=validator_info.signup_info.poet_public_key, consensus_state=consensus_state, poet_settings_view=poet_settings_view) except ValueError as error: LOGGER.error( 'Block %s rejected: Wait certificate check failed - %s', block_wrapper.identifier[:8], error) return False # Reject the block if the validator signup information fails the # freshness check. 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.error( 'Block %s rejected: Validator signup information not ' 'committed in a timely manner.', block_wrapper.identifier[:8]) return False # Reject the block if the validator has already claimed the key block # limit for its current PoET key pair. if consensus_state.validator_has_claimed_block_limit( validator_info=validator_info, poet_settings_view=poet_settings_view): LOGGER.error( 'Block %s rejected: Validator has reached maximum number of ' 'blocks with key pair.', block_wrapper.identifier[:8]) return False # Reject the block if the validator has not waited the required number # of blocks between when the block containing its validator registry # transaction was committed to the chain and trying to claim this # block if consensus_state.validator_is_claiming_too_early( validator_info=validator_info, block_number=block_wrapper.block_num, validator_registry_view=validator_registry_view, poet_settings_view=poet_settings_view, block_store=self._block_cache.block_store): LOGGER.error( 'Block %s rejected: Validator has not waited long enough ' 'since registering validator information.', block_wrapper.identifier[:8]) return False # Reject the block if the validator is claiming blocks at a rate that # is more frequent than is statistically allowed (i.e., zTest) if consensus_state.validator_is_claiming_too_frequently( validator_info=validator_info, previous_block_id=block_wrapper.previous_block_id, poet_settings_view=poet_settings_view, population_estimate=wait_certificate.population_estimate( poet_settings_view=poet_settings_view), block_cache=self._block_cache, poet_enclave_module=poet_enclave_module): LOGGER.error( 'Block %s rejected: Validator is claiming blocks too ' 'frequently.', block_wrapper.identifier[:8]) return False return True
def verify_block(self, block_wrapper): """Check that the block received conforms to the consensus rules. Args: block_wrapper (BlockWrapper): The block to validate. Returns: Boolean: True if the Block is valid, False if the block is invalid. """ # Get the state view for the previous block in the chain so we can # create a PoET enclave and validator registry view previous_block = None try: previous_block = \ self._block_cache[block_wrapper.previous_block_id] except KeyError: pass state_view = \ BlockWrapper.state_view_for_block( block_wrapper=previous_block, state_view_factory=self._state_view_factory) poet_enclave_module = \ factory.PoetEnclaveFactory.get_poet_enclave_module(state_view) validator_registry_view = ValidatorRegistryView(state_view) try: # Grab the validator info based upon the block signer's public # key try: validator_info = \ validator_registry_view.get_validator_info( block_wrapper.header.signer_pubkey) except KeyError: raise \ ValueError( 'Received block from an unregistered validator ' '{}...{}'.format( block_wrapper.header.signer_pubkey[:8], block_wrapper.header.signer_pubkey[-8:])) LOGGER.debug( 'Block Signer Name=%s, ID=%s...%s, PoET public key=' '%s...%s', validator_info.name, validator_info.id[:8], validator_info.id[-8:], validator_info.signup_info.poet_public_key[:8], validator_info.signup_info.poet_public_key[-8:]) # Create a list of certificates leading up to this block. # 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_wrapper.header, block_cache=self._block_cache, poet_enclave_module=poet_enclave_module, maximum_number=WaitTimer.certificate_sample_length) # For the candidate block, reconstitute the wait certificate # and verify that it is valid wait_certificate = \ utils.deserialize_wait_certificate( block=block_wrapper, poet_enclave_module=poet_enclave_module) if wait_certificate is None: raise \ ValueError( 'Being asked to verify a block that was not ' 'created by PoET consensus module') poet_public_key = \ validator_info.signup_info.poet_public_key wait_certificate.check_valid( poet_enclave_module=poet_enclave_module, certificates=certificates, poet_public_key=poet_public_key) except ValueError as error: LOGGER.error('Failed to verify block: %s', error) return False return True
def consensus_state_for_block_id(block_id, block_cache, state_view_factory, consensus_state_store, poet_enclave_module): """Returns the consensus state for the block referenced by block ID, creating it from the consensus state history if necessary. Args: block_id (str): The ID of the block for which consensus state will be returned. block_cache (BlockCache): The block store cache state_view_factory (StateViewFactory): A factory that can be used to create state view object corresponding to blocks consensus_state_store (ConsensusStateStore): The consensus state store that is used to store interim consensus state created up to resulting consensus state poet_enclave_module (module): The PoET enclave module Returns: ConsensusState object representing the consensus state for the block referenced by block_id """ consensus_state = None previous_wait_certificate = None blocks = collections.OrderedDict() # Starting at the chain head, walk the block store backwards until we # either get to the root or we get a block for which we have already # created consensus state current_id = block_id while True: block = \ ConsensusState._block_for_id( block_id=current_id, block_cache=block_cache) if block is None: break # Try to fetch the consensus state. If that succeeds, we can # stop walking back as we can now build on that consensus # state. consensus_state = consensus_state_store.get(block_id=current_id) if consensus_state is not None: break wait_certificate = \ utils.deserialize_wait_certificate( block=block, poet_enclave_module=poet_enclave_module) # If this is a PoET block (i.e., it has a wait certificate), get # the validator info for the validator that signed this block and # add the block information we will need to set validator state in # the block's consensus state. if wait_certificate is not None: state_view = \ state_view_factory.create_view( state_root_hash=block.state_root_hash) validator_registry_view = \ ValidatorRegistryView(state_view=state_view) validator_info = \ validator_registry_view.get_validator_info( validator_id=block.header.signer_public_key) LOGGER.debug( 'We need to build consensus state for block: %s...%s', current_id[:8], current_id[-8:]) blocks[current_id] = \ ConsensusState._BlockInfo( wait_certificate=wait_certificate, validator_info=validator_info, poet_settings_view=PoetSettingsView(state_view)) # Otherwise, this is a non-PoET block. If we don't have any blocks # yet or the last block we processed was a PoET block, put a # placeholder in the list so that when we get to it we know that we # need to reset the statistics. elif not blocks or previous_wait_certificate is not None: blocks[current_id] = \ ConsensusState._BlockInfo( wait_certificate=None, validator_info=None, poet_settings_view=None) previous_wait_certificate = wait_certificate # Move to the previous block current_id = block.previous_block_id # At this point, if we have not found any consensus state, we need to # create default state from which we can build upon if consensus_state is None: consensus_state = ConsensusState() # Now, walk through the blocks for which we were supposed to create # consensus state, from oldest to newest (i.e., in the reverse order in # which they were added), and store state for PoET blocks so that the # next time we don't have to walk so far back through the block chain. for current_id, block_info in reversed(blocks.items()): # If the block was not a PoET block (i.e., didn't have a wait # certificate), reset the consensus state statistics. We are not # going to store this in the consensus state store, but we will use # it as the starting for the next PoET block. if block_info.wait_certificate is None: consensus_state = ConsensusState() # Otherwise, let the consensus state update itself appropriately # based upon the validator claiming a block, and then associate the # consensus state with the new block in the store. # validator state for the validator which claimed the block, create # updated validator state for the validator, set/update the # validator state in the consensus state object, and then associate # the consensus state with the corresponding block in the consensus # state store. else: consensus_state.validator_did_claim_block( validator_info=block_info.validator_info, wait_certificate=block_info.wait_certificate, poet_settings_view=block_info.poet_settings_view) consensus_state_store[current_id] = consensus_state LOGGER.debug( 'Create consensus state: BID=%s, ALM=%f, TBCC=%d', current_id[:8], consensus_state.aggregate_local_mean, consensus_state.total_block_claim_count) return consensus_state
def verify_block(self, block_wrapper): """Check that the block received conforms to the consensus rules. Args: block_wrapper (BlockWrapper): The block to validate. Returns: Boolean: True if the Block is valid, False if the block is invalid. """ # Get the state view for the previous block in the chain so we can # create a PoET enclave and validator registry view previous_block = None try: previous_block = \ self._block_cache[block_wrapper.previous_block_id] except KeyError: pass state_view = \ BlockWrapper.state_view_for_block( block_wrapper=previous_block, state_view_factory=self._state_view_factory) poet_enclave_module = \ factory.PoetEnclaveFactory.get_poet_enclave_module(state_view) validator_registry_view = ValidatorRegistryView(state_view) try: # Grab the validator info based upon the block signer's public # key try: validator_info = \ validator_registry_view.get_validator_info( block_wrapper.header.signer_pubkey) except KeyError: raise \ ValueError( 'Received block from an unregistered validator ' '{}...{}'.format( block_wrapper.header.signer_pubkey[:8], block_wrapper.header.signer_pubkey[-8:])) LOGGER.debug( 'Block Signer Name=%s, ID=%s...%s, PoET public key=' '%s...%s', validator_info.name, validator_info.id[:8], validator_info.id[-8:], validator_info.signup_info.poet_public_key[:8], validator_info.signup_info.poet_public_key[-8:]) # Create a list of certificates leading up to this block. # 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_wrapper.header, block_cache=self._block_cache, poet_enclave_module=poet_enclave_module, maximum_number=WaitTimer.certificate_sample_length) # For the candidate block, reconstitute the wait certificate # and verify that it is valid wait_certificate = \ utils.deserialize_wait_certificate( block=block_wrapper, poet_enclave_module=poet_enclave_module) if wait_certificate is None: raise \ ValueError( 'Being asked to verify a block that was not ' 'created by PoET consensus module') poet_public_key = \ validator_info.signup_info.poet_public_key wait_certificate.check_valid( poet_enclave_module=poet_enclave_module, certificates=certificates, poet_public_key=poet_public_key) # Get the consensus state for the block that is being built # upon, fetch the validator state for this validator, and then # see if that validator has already claimed the key bock limit # for its current PoET key pair. If so, then we reject the # block. consensus_state = \ utils.get_consensus_state_for_block_id( block_id=block_wrapper.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) validator_state = \ utils.get_current_validator_state( validator_info=validator_info, consensus_state=consensus_state, block_cache=self._block_cache) poet_config_view = PoetConfigView(state_view=state_view) if validator_state.poet_public_key == poet_public_key and \ validator_state.key_block_claim_count >= \ poet_config_view.key_block_claim_limit: raise \ ValueError( 'Validator {} has already reached claim block limit ' 'for current PoET key pair: {} >= {}'.format( validator_info.name, validator_state.key_block_claim_count, poet_config_view.key_block_claim_limit)) # While having a block claim delay is nice, it turns out that in # practice the claim delay should not be more than one less than # the number of validators. It helps to imagine the scenario # where each validator hits their block claim limit in sequential # blocks and their new validator registry information is updated # in the following block by another validator, assuming that there # were no forks. If there are N validators, once all N validators # have updated their validator registry information, there will # have been N-1 block commits and the Nth validator will only be # able to get its updated validator registry information updated # if the first validator that kicked this off is now able to claim # a block. If the block claim delay was greater than or equal to # the number of validators, at this point no validators would be # able to claim a block. number_of_validators = \ len(validator_registry_view.get_validators()) block_claim_delay = \ min( poet_config_view.block_claim_delay, number_of_validators - 1) # While a validator network is starting up, we need to be careful # about applying the block claim delay because if we are too # aggressive we will get ourselves into a situation where the # block claim delay will prevent any validators from claiming # blocks. So, until we get at least block_claim_delay blocks # we are going to choose not to enforce the delay. if consensus_state.total_block_claim_count <= block_claim_delay: LOGGER.debug( 'Skipping block claim delay check. Only %d block(s) in ' 'the chain. Claim delay is %d block(s). %d validator(s) ' 'registered.', consensus_state.total_block_claim_count, block_claim_delay, number_of_validators) return True blocks_since_registration = \ block_wrapper.block_num - \ validator_state.commit_block_number - 1 if block_claim_delay > blocks_since_registration: raise \ ValueError( 'Validator {} claiming too early. Block: {}, ' 'registered in: {}, wait until after: {}.'.format( validator_info.name, block_wrapper.block_num, validator_state.commit_block_number, validator_state.commit_block_number + block_claim_delay)) LOGGER.debug( '%d block(s) claimed since %s was registered and block ' 'claim delay is %d block(s). Check passed.', blocks_since_registration, validator_info.name, block_claim_delay) except ValueError as error: LOGGER.error('Failed to verify block: %s', error) return False return True
def compare_forks(self, cur_fork_head, new_fork_head): """Given the head of two forks, return which should be the fork that the validator chooses. When this is called both forks consist of only valid blocks. Args: cur_fork_head (Block): The current head of the block chain. new_fork_head (Block): The head of the fork that is being evaluated. Returns: Boolean: True if the new chain should replace the current chain. False if the new chain should be discarded. """ chosen_fork_head = None state_view = \ BlockWrapper.state_view_for_block( block_wrapper=cur_fork_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) current_fork_wait_certificate = \ utils.deserialize_wait_certificate( block=cur_fork_head, poet_enclave_module=poet_enclave_module) new_fork_wait_certificate = \ utils.deserialize_wait_certificate( block=new_fork_head, poet_enclave_module=poet_enclave_module) # If we ever get a new fork head that is not a PoET block, then bail # out. This should never happen, but defensively protect against it. if new_fork_wait_certificate is None: raise \ TypeError( 'New fork head {} is not a PoET block'.format( new_fork_head.identifier[:8])) # Criterion #1: If the current fork head is not PoET, then check to see # if the new fork head is building on top of it. That would be okay. # However if not, then we don't have a good deterministic way of # choosing a winner. Again, the latter should never happen, but # defensively protect against it. if current_fork_wait_certificate is None: if new_fork_head.previous_block_id == cur_fork_head.identifier: LOGGER.info( 'Choose new fork %s over current fork %s: ' 'New fork head switches consensus to PoET', new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8]) chosen_fork_head = new_fork_head else: raise \ TypeError( 'Trying to compare a PoET block {} to a non-PoET ' 'block {} that is not the direct predecessor'.format( new_fork_head.identifier[:8], cur_fork_head.identifier[:8])) # Criterion #2: If they share the same immediate previous block, # then the one with the smaller wait duration is chosen elif cur_fork_head.previous_block_id == \ new_fork_head.previous_block_id: if current_fork_wait_certificate.duration < \ new_fork_wait_certificate.duration: LOGGER.info( 'Choose current fork %s over new fork %s: ' 'Current fork wait duration (%f) less than new fork wait ' 'duration (%f)', cur_fork_head.header_signature[:8], new_fork_head.header_signature[:8], current_fork_wait_certificate.duration, new_fork_wait_certificate.duration) chosen_fork_head = cur_fork_head elif new_fork_wait_certificate.duration < \ current_fork_wait_certificate.duration: LOGGER.info( 'Choose new fork %s over current fork %s: ' 'New fork wait duration (%f) less than current fork wait ' 'duration (%f)', new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8], new_fork_wait_certificate.duration, current_fork_wait_certificate.duration) chosen_fork_head = new_fork_head # Criterion #3: If they don't share the same immediate previous # block, then the one with the higher aggregate local mean wins else: # Get the consensus state for the current fork head and the # block immediately before the new fork head (as we haven't # committed to the block yet). So that the new fork doesn't # have to fight with one hand tied behind its back, add the # new fork head's wait certificate's local mean to the # aggregate local mean for the predecessor block's consensus # state for the comparison. current_fork_consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=cur_fork_head.identifier, 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) new_fork_consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=new_fork_head.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) new_fork_aggregate_local_mean = \ new_fork_consensus_state.aggregate_local_mean + \ new_fork_wait_certificate.local_mean if current_fork_consensus_state.aggregate_local_mean > \ new_fork_aggregate_local_mean: LOGGER.info( 'Choose current fork %s over new fork %s: ' 'Current fork aggregate local mean (%f) greater than new ' 'fork aggregate local mean (%f)', cur_fork_head.header_signature[:8], new_fork_head.header_signature[:8], current_fork_consensus_state.aggregate_local_mean, new_fork_aggregate_local_mean) chosen_fork_head = cur_fork_head elif new_fork_aggregate_local_mean > \ current_fork_consensus_state.aggregate_local_mean: LOGGER.info( 'Choose new fork %s over current fork %s: ' 'New fork aggregate local mean (%f) greater than current ' 'fork aggregate local mean (%f)', new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8], new_fork_aggregate_local_mean, current_fork_consensus_state.aggregate_local_mean) chosen_fork_head = new_fork_head # Criterion #4: If we have gotten to this point and we have not chosen # yet, we are going to fall back on using the block identifiers # (header signatures) . The lexicographically larger one will be the # chosen one. The chance that they are equal are infinitesimally # small. if chosen_fork_head is None: if cur_fork_head.header_signature > \ new_fork_head.header_signature: LOGGER.info( 'Choose current fork %s over new fork %s: ' 'Current fork header signature (%s) greater than new fork ' 'header signature (%s)', cur_fork_head.header_signature[:8], new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8], new_fork_head.header_signature[:8]) chosen_fork_head = cur_fork_head else: LOGGER.info( 'Choose new fork %s over current fork %s: ' 'New fork header signature (%s) greater than current fork ' 'header signature (%s)', new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8], new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8]) chosen_fork_head = new_fork_head # Now that we have chosen a fork for the chain head, if we chose the # new fork and it is a PoET block (i.e., it has a wait certificate), # we need to create consensus state store information for the new # fork's chain head. if chosen_fork_head == new_fork_head: # Get the state view for the previous block in the chain so we can # create a PoET enclave previous_block = None try: previous_block = \ self._block_cache[new_fork_head.previous_block_id] except KeyError: pass state_view = \ BlockWrapper.state_view_for_block( block_wrapper=previous_block, state_view_factory=self._state_view_factory) validator_registry_view = ValidatorRegistryView(state_view) try: # Get the validator info for the validator that claimed the # fork head validator_info = \ validator_registry_view.get_validator_info( new_fork_head.header.signer_public_key) # Get the consensus state for the new fork head's previous # block, let the consensus state update itself appropriately # based upon the validator claiming a block, and then # associate the consensus state with the new block in the # store. consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=new_fork_head.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) consensus_state.validator_did_claim_block( validator_info=validator_info, wait_certificate=new_fork_wait_certificate, poet_settings_view=PoetSettingsView(state_view)) self._consensus_state_store[new_fork_head.identifier] = \ consensus_state LOGGER.debug( 'Create consensus state: BID=%s, ALM=%f, TBCC=%d', new_fork_head.identifier[:8], consensus_state.aggregate_local_mean, consensus_state.total_block_claim_count) except KeyError: # This _should_ never happen. The new potential fork head # has to have been a PoET block and for it to be verified # by the PoET block verifier, it must have been signed by # validator in the validator registry. If not found, we # are going to just stick with the current fork head. LOGGER.error( 'New fork head claimed by validator not in validator ' 'registry: %s...%s', new_fork_head.header.signer_public_key[:8], new_fork_head.header.signer_public_key[-8:]) chosen_fork_head = cur_fork_head return chosen_fork_head == new_fork_head
def verify_block(self, block_wrapper): """Check that the block received conforms to the consensus rules. Args: block_wrapper (BlockWrapper): The block to validate. Returns: Boolean: True if the Block is valid, False if the block is invalid. """ # Get the state view for the previous block in the chain so we can # create a PoET enclave and validator registry view previous_block = None try: previous_block = \ self._block_cache[block_wrapper.previous_block_id] except KeyError: pass state_view = \ BlockWrapper.state_view_for_block( block_wrapper=previous_block, state_view_factory=self._state_view_factory) poet_enclave_module = \ factory.PoetEnclaveFactory.get_poet_enclave_module(state_view) validator_registry_view = ValidatorRegistryView(state_view) # Grab the validator info based upon the block signer's public # key try: validator_info = \ validator_registry_view.get_validator_info( block_wrapper.header.signer_pubkey) except KeyError: LOGGER.error( 'Block %s rejected: Received block from an unregistered ' 'validator %s...%s', block_wrapper.identifier[:8], block_wrapper.header.signer_pubkey[:8], block_wrapper.header.signer_pubkey[-8:]) return False LOGGER.debug( 'Block Signer Name=%s, ID=%s...%s, PoET public key=' '%s...%s', validator_info.name, validator_info.id[:8], validator_info.id[-8:], validator_info.signup_info.poet_public_key[:8], validator_info.signup_info.poet_public_key[-8:]) # Create a list of certificates leading up to this block. # 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_wrapper.header, block_cache=self._block_cache, poet_enclave_module=poet_enclave_module, maximum_number=WaitTimer.certificate_sample_length) # For the candidate block, reconstitute the wait certificate # and verify that it is valid wait_certificate = \ utils.deserialize_wait_certificate( block=block_wrapper, poet_enclave_module=poet_enclave_module) if wait_certificate is None: LOGGER.error( 'Block %s rejected: Block from validator %s (ID=%s...%s) was ' 'not created by PoET consensus module', block_wrapper.identifier[:8], validator_info.name, validator_info.id[:8], validator_info.id[-8:]) return False wait_certificate.check_valid( poet_enclave_module=poet_enclave_module, certificates=certificates, poet_public_key=validator_info.signup_info.poet_public_key) # Get the consensus state for the block that is being built upon and # then fetch the validator state for this validator consensus_state = \ utils.get_consensus_state_for_block_id( block_id=block_wrapper.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) validator_state = \ utils.get_current_validator_state( validator_info=validator_info, consensus_state=consensus_state, block_cache=self._block_cache) poet_config_view = PoetConfigView(state_view=state_view) # Reject the block if the validator has already claimed the key bock # limit for its current PoET key pair. key_block_claim_limit = poet_config_view.key_block_claim_limit if utils.validator_has_claimed_maximum_number_of_blocks( validator_info=validator_info, validator_state=validator_state, key_block_claim_limit=key_block_claim_limit): LOGGER.error( 'Block %s rejected: Validator has reached maximum number of ' 'blocks with key pair.', block_wrapper.identifier[:8]) return False # Reject the block if the validator has not waited the required number # of blocks between when the block containing its validator registry # transaction was committed to the chain and trying to claim this # block if utils.validator_has_claimed_too_early( validator_info=validator_info, consensus_state=consensus_state, block_number=block_wrapper.block_num, validator_registry_view=validator_registry_view, poet_config_view=poet_config_view, block_store=self._block_cache.block_store): LOGGER.error( 'Block %s rejected: Validator has not waited long enough ' 'since registering validator information.', block_wrapper.identifier[:8]) return False # Reject the block if the validator is claiming blocks at a rate that # is more frequent than is statistically allowed (i.e., zTest) if utils.validator_has_claimed_too_frequently( validator_info=validator_info, previous_block_id=block_wrapper.previous_block_id, consensus_state=consensus_state, poet_config_view=poet_config_view, population_estimate=wait_certificate.population_estimate, block_cache=self._block_cache, poet_enclave_module=poet_enclave_module): LOGGER.error( 'Block %s rejected: Validator is claiming blocks too ' 'frequently.', block_wrapper.identifier[:8]) return False return True