def __getitem__(self, block_id):
        """Return the consensus state corresponding to the block ID

        Args:
            block_id (str): The ID of the block for which consensus state
                is being requested

        Returns:
            ConsensusState object

        Raises:
            KeyError if the block ID is not in the store
        """
        #LOGGER.debug('getitem id=%s', type(block_id))
        serialized_consensus_state = self._store_db[block_id]
        if serialized_consensus_state is None:
            raise KeyError('Block ID {} not found'.format(block_id))

        try:
            consensus_state = ConsensusState()
            consensus_state.parse_from_bytes(buffer=serialized_consensus_state)
            return consensus_state
        except ValueError as error:
            raise \
                KeyError(
                    'Cannot return block with ID {}: {}'.format(
                        block_id,
                        error))
    def __str__(self):
        out = []
        for block_id in self._store_db.keys():
            try:
                serialized_consensus_state = self._store_db[block_id]
                consensus_state = ConsensusState()
                consensus_state.parse_from_bytes(
                    buffer=serialized_consensus_state)
                out.append('{}...{}: {{{}}}'.format(block_id[:8],
                                                    block_id[-8:],
                                                    consensus_state))
            except ValueError:
                pass

        return ', '.join(out)
Exemplo n.º 3
0
 def get_consensus_state_for_block_id(self, block):
     block_id = block.block_id.hex()
     LOGGER.debug("PbftOracle: get_consensus_state for block_id='%s'",
                  block_id)
     consensus_state = ConsensusState.consensus_state_for_block_id(
         block_id=block_id,  #block_header.previous_block_id,
         block_cache=self._block_cache,
         state_view_factory=self._state_view_factory,
         consensus_state_store=self._consensus_state_store)
     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 PBFT 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)
        if _VREG_:
            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)
                LOGGER.debug('Block Signer Name=%s, ID=%s...%s PBFT',
                             validator_info.name, validator_info.id[:8],
                             validator_info.id[-8:])
            except KeyError:
                LOGGER.error(
                    'Block %s rejected: Received block from an unregistered validator %s...%s num_transactions=%s',
                    block_wrapper.identifier[:8],
                    block_wrapper.header.signer_public_key[:8],
                    block_wrapper.header.signer_public_key[-8:],
                    block_wrapper.num_transactions)
                #return False

        # Get the consensus state and PBFT 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,
        )
        LOGGER.debug(
            'PbftBlockVerifier:: consensus_state=%s block_wrapper=(%s)',
            consensus_state, block_wrapper)
        pbft_settings_view = PbftSettingsView(state_view=state_view)
        LOGGER.debug('PbftBlockVerifier:: pbft_settings_view=%s',
                     pbft_settings_view)
        return block_wrapper.header.consensus == b"pbft"
Exemplo n.º 5
0
 def _handle_registration_timeout(self, block_header, pbft_enclave_module,
                                  state_view, signup_nonce,
                                  pbft_public_key):
     # See if a registration attempt has timed out. Assumes the caller has
     # checked for a committed registration and did not find it.
     # If it has timed out then this method will re-register.
     LOGGER.debug("PbftBlockPublisher::_handle_registration_timeout:ADD CONSENSUS_STATE for block_id=%s",block_header.previous_block_id)
     consensus_state = ConsensusState.consensus_state_for_block_id(
             block_id=block_header.previous_block_id,
             block_cache=self._block_cache,
             state_view_factory=self._state_view_factory,
             consensus_state_store=self._consensus_state_store,
             pbft_enclave_module=None #pbft_enclave_module
         )
     """
Exemplo n.º 6
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 PBFT 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)
        """
        bgt_enclave_module = \
            factory.BgtEnclaveFactory.get_bgt_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, PBFT public key='
            '%s...%s', validator_info.name, validator_info.id[:8],
            validator_info.id[-8:],
            validator_info.signup_info.bgt_public_key[:8],
            validator_info.signup_info.bgt_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, bgt_enclave_module=bgt_enclave_module)
        if wait_certificate is None:
            LOGGER.error(
                'Block %s rejected: Block from validator %s (ID=%s...%s) was '
                'not created by PBFT consensus module',
                block_wrapper.identifier[:8], validator_info.name,
                validator_info.id[:8], validator_info.id[-8:])
            return False

        # Get the consensus state and PBFT 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,
            #bgt_enclave_module=bgt_enclave_module
        )
        pbft_settings_view = PbftSettingsView(state_view=state_view)

        previous_certificate_id = utils.get_previous_certificate_id(
            block_header=block_wrapper.header,
            block_cache=self._block_cache,
            bgt_enclave_module=bgt_enclave_module)
        try:
            wait_certificate.check_valid(
                bgt_enclave_module=bgt_enclave_module,
                previous_certificate_id=previous_certificate_id,
                bgt_public_key=validator_info.signup_info.bgt_public_key,
                consensus_state=consensus_state,
                bgt_settings_view=bgt_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,
                bgt_settings_view=bgt_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 PBFT key pair.
        if consensus_state.validator_has_claimed_block_limit(
                validator_info=validator_info,
                bgt_settings_view=bgt_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,
                bgt_settings_view=bgt_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,
                bgt_settings_view=bgt_settings_view,
                population_estimate=wait_certificate.population_estimate(
                    bgt_settings_view=bgt_settings_view),
                block_cache=self._block_cache,
                bgt_enclave_module=bgt_enclave_module):
            LOGGER.error(
                'Block %s rejected: Validator is claiming blocks too '
                'frequently.', block_wrapper.identifier[:8])
            return False

        return True
Exemplo n.º 7
0
    def initialize_block(self, block_header):
        """Do initialization necessary for the consensus to claim a block,
        this may include initiating voting activities, starting proof of work
        hash generation, or create a PBFT wait timer.

        Args:
            block_header (BlockHeader): The BlockHeader to initialize.
        Returns:
            Boolean: True if the candidate block should be built. False if
            no candidate should be built.
        """
        # If the previous block ID matches our cached one, that means that we
        # have already determined that even if we initialize the requested
        # block we would not be able to claim it.  So, instead of wasting time
        # doing all of the checking again, simply short-circuit the failure so
        # that the validator can go do something more useful.
        if block_header.previous_block_id == PbftBlockPublisher._previous_block_id:
            LOGGER.debug(
                "block_header.previous_block_id == PbftBlockPublisher._previous_block_id TRUE"
            )
            return False
        PbftBlockPublisher._previous_block_id = block_header.previous_block_id
        # Using the current chain head, we need to create a state view so we
        # can create a PBFT enclave.
        state_view = BlockWrapper.state_view_for_block(
            block_wrapper=self._block_cache.block_store.chain_head,
            state_view_factory=self._state_view_factory)

        pbft_enclave_module = factory.PbftEnclaveFactory.get_pbft_enclave_module(
            state_view=state_view,
            config_dir=self._config_dir,
            data_dir=self._data_dir)
        LOGGER.debug("pbft_enclave_module=%s previous_block_id=%s",
                     pbft_enclave_module, type(block_header.previous_block_id))
        # Get our validator registry entry to see what PBFT public key
        # other validators think we are using.
        validator_registry_view = ValidatorRegistryView(state_view)
        validator_info = None

        try:
            validator_id = block_header.signer_public_key
            validator_info = validator_registry_view.get_validator_info(
                validator_id=validator_id)
        except KeyError:
            pass

        # If we don't have a validator registry entry, then check the active
        # key.  If we don't have one, then we need to sign up.  If we do have
        # one, then our validator registry entry has not percolated through the
        # system, so nothing to to but wait.
        active_pbft_public_key = self._pbft_key_state_store.active_key
        if validator_info is None:
            if active_pbft_public_key is None:
                LOGGER.debug(
                    'No public key found, so going to register new signup information'
                )
                self._register_signup_information(block_header=block_header)

            else:  # Check if we need to give up on this registration attempt
                try:
                    nonce = self._pbft_key_state_store[
                        active_pbft_public_key].signup_nonce
                except (ValueError, AttributeError):
                    self._pbft_key_state_store.active_key = None
                    LOGGER.warning(
                        'Pbft Key State Store had inaccessible or '
                        'corrupt active key [%s] clearing '
                        'key.', active_pbft_public_key)
                    return False
                LOGGER.debug(
                    'Check if we need to give up on this registration attempt')
                self._handle_registration_timeout(
                    block_header=block_header,
                    pbft_enclave_module=pbft_enclave_module,
                    state_view=state_view,
                    signup_nonce=nonce,
                    pbft_public_key=active_pbft_public_key)
            LOGGER.debug("validator_info NONE")
            return True  #False

        # Retrieve the key state corresponding to the PBFT public key in our
        # validator registry entry.
        pbft_key_state = None
        try:
            pbft_key_state = self._pbft_key_state_store[
                validator_info.signup_info.pbft_public_key]
        except (ValueError, KeyError):
            pass

        # If there is no key state associated with the PBFT public key that
        # other validators think we should be using, then we need to create
        # new signup information as we have no way whatsoever to publish
        # blocks that other validators will accept.
        LOGGER.debug("check pbft_key_state=%s", pbft_key_state)
        if pbft_key_state is None:
            LOGGER.debug(
                'PBFT public key %s...%s in validator registry not found in key state store.  Sign up again',
                validator_info.signup_info.pbft_public_key[:8],
                validator_info.signup_info.pbft_public_key[-8:])
            self._register_signup_information(block_header=block_header)

            # We need to put fake information in the key state store for the
            # PBFT public key the other validators think we are using so that
            # we don't try to keep signing up.  However, we are going to mark
            # that key state store entry as being refreshed so that we will
            # never actually try to use it.
            dummy_data = b64encode(b'No sealed signup data').decode('utf-8')
            self._pbft_key_state_store[
                validator_info.signup_info.pbft_public_key] = PbftKeyState(
                    sealed_signup_data=dummy_data,
                    has_been_refreshed=True,
                    signup_nonce='unknown')

            return False

        # Check the key state.  If it is marked as being refreshed, then we are
        # waiting until our PBFT public key is updated in the validator
        # registry and therefore we cannot publish any blocks.
        if pbft_key_state.has_been_refreshed:
            LOGGER.debug(
                'PBFT public key %s...%s has been refreshed.  Wait for new '
                'key to show up in validator registry.',
                validator_info.signup_info.pbft_public_key[:8],
                validator_info.signup_info.pbft_public_key[-8:])

            # Check if we need to give up on this registration attempt
            self._handle_registration_timeout(
                block_header=block_header,
                pbft_enclave_module=pbft_enclave_module,
                state_view=state_view,
                signup_nonce=pbft_key_state.signup_nonce,
                pbft_public_key=active_pbft_public_key)
            return False

        # If the PBFT public key in the validator registry is not the active
        # one, then we need to switch the active key in the key state store.
        if validator_info.signup_info.pbft_public_key != active_pbft_public_key:
            active_pbft_public_key = validator_info.signup_info.pbft_public_key
            self._pbft_key_state_store.active_key = active_pbft_public_key

        # Ensure that the enclave is using the appropriate keys
        try:
            unsealed_pbft_public_key = SignupInfo.unseal_signup_data(
                pbft_enclave_module=pbft_enclave_module,
                sealed_signup_data=pbft_key_state.sealed_signup_data)
        except SystemError:
            # Signup data is unuseable
            LOGGER.error(
                'Could not unseal signup data associated with PPK: %s..%s',
                active_pbft_public_key[:8], active_pbft_public_key[-8:])
            self._pbft_key_state_store.active_key = None
            return False
        LOGGER.debug("unsealed_pbft_public_key=%s ~ %s",
                     unsealed_pbft_public_key, active_pbft_public_key)
        assert active_pbft_public_key == unsealed_pbft_public_key

        LOGGER.debug('Using PBFT public key: %s...%s',
                     active_pbft_public_key[:8], active_pbft_public_key[-8:])
        LOGGER.debug('Unseal signup data: %s...%s',
                     pbft_key_state.sealed_signup_data[:8],
                     pbft_key_state.sealed_signup_data[-8:])
        LOGGER.debug("initialize_block:ADD CONSENSUS_STATE for block_id=%s",
                     block_header.previous_block_id)
        consensus_state = ConsensusState.consensus_state_for_block_id(
            block_id=block_header.previous_block_id,
            block_cache=self._block_cache,
            state_view_factory=self._state_view_factory,
            consensus_state_store=self._consensus_state_store,
            pbft_enclave_module=None,  #pbft_enclave_module=pbft_enclave_module
        )
        pbft_settings_view = PbftSettingsView(state_view)

        # If our signup information does not pass the freshness test, then we
        # know that other validators will reject any blocks we try to claim so
        # we need to try to sign up again.
        if consensus_state.validator_signup_was_committed_too_late(
                validator_info=validator_info,
                pbft_settings_view=pbft_settings_view,
                block_cache=self._block_cache):
            LOGGER.info(
                'Reject building on block %s: Validator signup information '
                'not committed in a timely manner.',
                block_header.previous_block_id[:8])
            self._register_signup_information(block_header=block_header)
            return False

        # Using the consensus state for the block upon which we want to
        # build, check to see how many blocks we have claimed on this chain
        # with this PBFT key.  If we have hit the key block claim limit, then
        # we need to check if the key has been refreshed.
        if consensus_state.validator_has_claimed_block_limit(
                validator_info=validator_info,
                pbft_settings_view=pbft_settings_view):
            # Because we have hit the limit, check to see if we have already
            # submitted a validator registry transaction with new signup
            # information, and therefore a new PBFT public key.  If not, then
            # mark this PBFT public key in the store as having been refreshed
            # and register new signup information.  Regardless, since we have
            # hit the key block claim limit, we won't even bother initializing
            # a block on this chain as it will be rejected by other
            # validators.

            LOGGER.debug(
                "initialize_block:ADD validator_has_claimed_block_limit ")
            pbft_key_state = self._pbft_key_state_store[active_pbft_public_key]
            if not pbft_key_state.has_been_refreshed:
                LOGGER.info('Reached block claim limit for key: %s...%s',
                            active_pbft_public_key[:8],
                            active_pbft_public_key[-8:])

                sealed_signup_data = pbft_key_state.sealed_signup_data
                signup_nonce = pbft_key_state.signup_nonce
                self._pbft_key_state_store[
                    active_pbft_public_key] = PbftKeyState(
                        sealed_signup_data=sealed_signup_data,
                        has_been_refreshed=True,
                        signup_nonce=signup_nonce)

                # Release enclave resources for this identity
                # This signup will be invalid on all forks that use it,
                # even if there is a rollback to a point it should be valid.
                # A more sophisticated policy would be to release signups
                # only at a block depth where finality probability
                # is high.
                SignupInfo.release_signup_data(
                    pbft_enclave_module=
                    None,  #pbft_enclave_module=pbft_enclave_module,
                    sealed_signup_data=sealed_signup_data)

                self._register_signup_information(block_header=block_header)

            LOGGER.info(
                'Reject building on block %s: Validator has reached maximum '
                'number of blocks with key pair.',
                block_header.previous_block_id[:8])
            return False

        # Verify that we are abiding by the block claim delay (i.e., waiting a
        # certain number of blocks since our validator registry was added/
        # updated).
        if consensus_state.validator_is_claiming_too_early(
                validator_info=validator_info,
                block_number=block_header.block_num,
                validator_registry_view=validator_registry_view,
                pbft_settings_view=pbft_settings_view,
                block_store=self._block_cache.block_store):
            LOGGER.info(
                'Reject building on block %s: Validator has not waited long '
                'enough since registering validator information.',
                block_header.previous_block_id[:8])
            return False

        # We need to create a wait timer for the block...this is what we
        # will check when we are asked if it is time to publish the block
        pbft_key_state = self._pbft_key_state_store[active_pbft_public_key]
        sealed_signup_data = pbft_key_state.sealed_signup_data
        previous_certificate_id = \
            utils.get_previous_certificate_id(
                block_header=block_header,
                block_cache=self._block_cache,
                pbft_enclave_module=pbft_enclave_module
                )
        """
        wait_timer = \
            WaitTimer.create_wait_timer(
                pbft_enclave_module=pbft_enclave_module,
                sealed_signup_data=sealed_signup_data,
                validator_address=block_header.signer_public_key,
                previous_certificate_id=previous_certificate_id,
                consensus_state=consensus_state,
                pbft_settings_view=pbft_settings_view)
        """
        wait_timer = None

        # NOTE - we do the zTest after we create the wait timer because we
        # need its population estimate to see if this block would be accepted
        # by other validators based upon the zTest.

        # Check to see if by chance we were to be able to claim this block
        # if it would result in us winning more frequently than statistically
        # expected.  If so, then refuse to initialize the block because other
        # validators will not accept anyway.
        """
        if consensus_state.validator_is_claiming_too_frequently(
                validator_info=validator_info,
                previous_block_id=block_header.previous_block_id,
                pbft_settings_view=pbft_settings_view,
                population_estimate=wait_timer.population_estimate(pbft_settings_view=pbft_settings_view),
                block_cache=self._block_cache,
                pbft_enclave_module=pbft_enclave_module):
            LOGGER.info(
                'Reject building on block %s: '
                'Validator (signing public key: %s) is claiming blocks '
                'too frequently.',
                block_header.previous_block_id[:8],
                block_header.signer_public_key)
            return False
        """
        # At this point, we know that if we are able to claim the block we are
        # initializing, we will not be prevented from doing so because of PBFT
        # policies.

        self._wait_timer = wait_timer
        PbftBlockPublisher._previous_block_id = None

        LOGGER.debug('Created wait timer: %s', self._wait_timer)

        return True
Exemplo n.º 8
0
    def initialize_block(self, block_header):
        """Do initialization necessary for the consensus to claim a block,
        this may include initiating voting activities, starting proof of work
        hash generation, or create a PBFT wait timer.

        Args:
            block_header (BlockHeader): The BlockHeader to initialize.
        Returns:
            Boolean: True if the candidate block should be built. False if
            no candidate should be built.
        """
        LOGGER.debug('PbftBlockPublisher::initialize_block previous_block_id=%s (%s)',_short_id(block_header.previous_block_id),block_header)
        # If the previous block ID matches our cached one, that means that we
        # have already determined that even if we initialize the requested
        # block we would not be able to claim it.  So, instead of wasting time
        # doing all of the checking again, simply short-circuit the failure so
        # that the validator can go do something more useful.
        if block_header.previous_block_id == PbftBlockPublisher._previous_block_id:
            LOGGER.debug("PbftBlockPublisher::initialize_block block_header.previous_block_id == PbftBlockPublisher._previous_block_id TRUE")
            return False
        PbftBlockPublisher._previous_block_id = block_header.previous_block_id
        # Using the current chain head, we need to create a state view so we
        # can create a PBFT enclave.
        if False:
            state_view = BlockWrapper.state_view_for_block(
                    block_wrapper=self._block_cache.block_store.chain_head,
                    state_view_factory=self._state_view_factory)

            pbft_settings_view = PbftSettingsView(state_view)
            LOGGER.debug("PbftBlockPublisher::pbft_settings_view node=%s",pbft_settings_view.pbft_node)
        #self._node = pbft_settings_view.pbft_node
        
        consensus_state = ConsensusState.consensus_state_for_block_id(
                block_id=block_header.previous_block_id,
                block_cache=self._block_cache,
                state_view_factory=self._state_view_factory,
                consensus_state_store=self._consensus_state_store,
                node=self._node
                )
        # shift into PrePrepare state
        consensus_state.next_step()
        #consensus_state.mark_as_own()
        consensus_state.set_consensus_state_for_block_id(block_header.previous_block_id,self._consensus_state_store)
        self._block_id = block_header.previous_block_id
        #consensus_state.set_node(self._node)
        LOGGER.debug("PbftBlockPublisher::initialize_block GET CONSENSUS_STATE=%s for block_id=%s ",consensus_state,_short_id(block_header.previous_block_id))
        # start 
        # Get our validator registry entry to see what PBFT public key
        # other validators think we are using.

        if _VREG_:
            validator_registry_view = ValidatorRegistryView(state_view)
            validator_info = None

            try:
                validator_id = block_header.signer_public_key
                validator_info = validator_registry_view.get_validator_info(validator_id=validator_id)
            except KeyError:
                pass

            # If we don't have a validator registry entry, then check the active
            # key.  If we don't have one, then we need to sign up.  If we do have
            # one, then our validator registry entry has not percolated through the
            # system, so nothing to to but wait.
            active_pbft_public_key = self._pbft_key_state_store.active_key
            if validator_info is None:
                if active_pbft_public_key is None:
                    LOGGER.debug('PbftBlockPublisher::initialize_block No public key found, so going to register new signup information')
                    self._register_signup_information(block_header=block_header)

                else:  # Check if we need to give up on this registration attempt
                    try:
                        nonce = self._pbft_key_state_store[active_pbft_public_key].signup_nonce
                    except (ValueError, AttributeError):
                        self._pbft_key_state_store.active_key = None
                        LOGGER.warning('PbftBlockPublisher::initialize_block Pbft Key State Store had inaccessible or '
                                       'corrupt active key [%s] clearing '
                                       'key.', active_pbft_public_key)
                        return False
                    LOGGER.debug('PbftBlockPublisher::initialize_block Check if we need to give up on this registration attempt')
                    self._handle_registration_timeout(
                        block_header=block_header,
                        pbft_enclave_module=None,#pbft_enclave_module,
                            state_view=state_view,
                        signup_nonce=nonce,
                        pbft_public_key=active_pbft_public_key
                    )
                LOGGER.debug("PbftBlockPublisher::initialize_block validator_info NONE")
                return True #False

                # Retrieve the key state corresponding to the PBFT public key in our
                # validator registry entry.
                pbft_key_state = None
                try:
                    pbft_key_state = self._pbft_key_state_store[validator_info.signup_info.pbft_public_key]
                except (ValueError, KeyError):
                    pass

                # If there is no key state associated with the PBFT public key that
                # other validators think we should be using, then we need to create
                # new signup information as we have no way whatsoever to publish
                # blocks that other validators will accept.
                LOGGER.debug("PbftBlockPublisher::check pbft_key_state=%s",pbft_key_state)
                if pbft_key_state is None:
                    LOGGER.debug('PbftBlockPublisher::initialize_block PBFT public key %s...%s in validator registry not found in key state store.  Sign up again',
                        validator_info.signup_info.pbft_public_key[:8],
                        validator_info.signup_info.pbft_public_key[-8:])
                    self._register_signup_information(block_header=block_header)

                    # We need to put fake information in the key state store for the
                    # PBFT public key the other validators think we are using so that
                    # we don't try to keep signing up.  However, we are going to mark
                    # that key state store entry as being refreshed so that we will
                    # never actually try to use it.
                    dummy_data = b64encode(b'No sealed signup data').decode('utf-8')
                    self._pbft_key_state_store[validator_info.signup_info.pbft_public_key] = PbftKeyState(
                            sealed_signup_data=dummy_data,
                            has_been_refreshed=True,
                            signup_nonce='unknown')

                    return False

        # Check the key state.  If it is marked as being refreshed, then we are
        # waiting until our PBFT public key is updated in the validator
        # registry and therefore we cannot publish any blocks.
        if _VREG_ and pbft_key_state.has_been_refreshed:
            LOGGER.debug(
                'PBFT public key %s...%s has been refreshed.  Wait for new '
                'key to show up in validator registry.',
                validator_info.signup_info.pbft_public_key[:8],
                validator_info.signup_info.pbft_public_key[-8:])

            # Check if we need to give up on this registration attempt
            self._handle_registration_timeout(
                block_header=block_header,
                pbft_enclave_module=pbft_enclave_module,
                state_view=state_view,
                signup_nonce=pbft_key_state.signup_nonce,
                pbft_public_key=active_pbft_public_key
            )
            return False

        # If the PBFT public key in the validator registry is not the active
        # one, then we need to switch the active key in the key state store.
        if _VREG_:
            if validator_info.signup_info.pbft_public_key != active_pbft_public_key:
                active_pbft_public_key = validator_info.signup_info.pbft_public_key
                self._pbft_key_state_store.active_key = active_pbft_public_key

            # Ensure that the enclave is using the appropriate keys
            try:
                    signup_data = json2dict(base64.b64decode(pbft_key_state.sealed_signup_data.encode()).decode())
                    unsealed_pbft_public_key = signup_data.get('pbft_public_key')
            except SystemError:
                # Signup data is unuseable
                LOGGER.error(
                    'Could not unseal signup data associated with PPK: %s..%s',
                    active_pbft_public_key[:8],
                    active_pbft_public_key[-8:])
                self._pbft_key_state_store.active_key = None
                return False
            LOGGER.debug("PbftBlockPublisher::unsealed_pbft_public_key=%s ~ %s signup_data=%s",unsealed_pbft_public_key,active_pbft_public_key,signup_data)
            assert active_pbft_public_key == unsealed_pbft_public_key

            LOGGER.debug('Using PBFT public key: %s...%s',active_pbft_public_key[:8],active_pbft_public_key[-8:])
            LOGGER.debug('Unseal signup data: %s...%s',pbft_key_state.sealed_signup_data[:8],pbft_key_state.sealed_signup_data[-8:])
            """
            LOGGER.debug("PbftBlockPublisher::initialize_block  ADD CONSENSUS_STATE for block_id=%s",block_header.previous_block_id)
            consensus_state = ConsensusState.consensus_state_for_block_id(
                    block_id=block_header.previous_block_id,
                    block_cache=self._block_cache,
                    state_view_factory=self._state_view_factory,
                    consensus_state_store=self._consensus_state_store,
                    pbft_enclave_module=None,
                    )
            """
            #pbft_settings_view = PbftSettingsView(state_view)
            #LOGGER.debug("PbftBlockPublisher::pbft_settings_view node=%s",pbft_settings_view.pbft_node)

            # If our signup information does not pass the freshness test, then we
            # know that other validators will reject any blocks we try to claim so
            # we need to try to sign up again.

                # Using the consensus state for the block upon which we want to
            # build, check to see how many blocks we have claimed on this chain
            # with this PBFT key.  If we have hit the key block claim limit, then
            # we need to check if the key has been refreshed.
                # We need to create a wait timer for the block...this is what we
            # will check when we are asked if it is time to publish the block
            pbft_key_state = self._pbft_key_state_store[active_pbft_public_key]
            sealed_signup_data = pbft_key_state.sealed_signup_data

            # At this point, we know that if we are able to claim the block we are
            # initializing, we will not be prevented from doing so because of PBFT
            # policies.

            self._wait_timer = 20
        self._wait_timer = 20
        PbftBlockPublisher._previous_block_id = None
        block_header.consensus = b"pbft"
        LOGGER.debug('PbftBlockPublisher::initialize_block DONE _wait_timer=%s',self._wait_timer)
        self._block_header = block_header
        return True
Exemplo n.º 9
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)
        bgt_enclave_module = \
            factory.BgtEnclaveFactory.get_bgt_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,
                bgt_enclave_module=bgt_enclave_module)
        new_fork_wait_certificate = \
            utils.deserialize_wait_certificate(
                block=new_fork_head,
                bgt_enclave_module=bgt_enclave_module)

        # If we ever get a new fork head that is not a PBFT 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 PBFT block'.format(
                        new_fork_head.identifier[:8]))

        # Criterion #1: If the current fork head is not PBFT, 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 PBFT',
                    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 PBFT block {} to a non-PBFT '
                        '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,
                    bgt_enclave_module=bgt_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,
                    bgt_enclave_module=bgt_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 PBFT 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 PBFT 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,
                    bgt_enclave_module=bgt_enclave_module)
                consensus_state.validator_did_claim_block(
                    validator_info=validator_info,
                    wait_certificate=new_fork_wait_certificate,
                    bgt_settings_view=BgtSettingsView(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 PBFT block and for it to be verified
                # by the PBFT 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