コード例 #1
0
    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
コード例 #2
0
    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
コード例 #3
0
    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
コード例 #4
0
    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
コード例 #5
0
    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
コード例 #6
0
    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
コード例 #7
0
    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
コード例 #8
0
    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
コード例 #9
0
    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
コード例 #10
0
    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
コード例 #11
0
    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
コード例 #12
0
    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
コード例 #13
0
    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