def test_create_before_wait_timer_expires(self):
        # Need to create signup information
        SignupInfo.create_signup_info(
            poet_enclave_module=self.poet_enclave_module,
            originator_public_key_hash=self._originator_public_key_hash,
            nonce=NULL_BLOCK_IDENTIFIER)

        # Create a wait certificate for the genesis block so that we can
        # create another wait certificate that has to play by the rules.
        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)
        wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                wait_timer=wt,
                block_hash="Reader's Digest")

        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=wc.identifier,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)

        with self.assertRaises(ValueError):
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                wait_timer=wt,
                block_hash="Reader's Digest")
    def test_create_with_reused_wait_timer(self):
        # Need to create signup information
        SignupInfo.create_signup_info(
            poet_enclave_module=self.poet_enclave_module,
            originator_public_key_hash=self._originator_public_key_hash,
            nonce=NULL_BLOCK_IDENTIFIER)

        # Create a wait certificate for the genesis block so that we can
        # create another wait certificate that has to play by the rules.
        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)
        wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                wait_timer=wt,
                block_hash="Reader's Digest")

        consumed_wt = wt

        # Verify that we cannot use the consumed wait timer to create a wait
        # certificate either before or after creating a new wait timer
        with self.assertRaises(ValueError):
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                wait_timer=consumed_wt,
                block_hash="Reader's Digest")
        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=wc.identifier,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)
        with self.assertRaises(ValueError):
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                wait_timer=consumed_wt,
                block_hash="Reader's Digest")

        # Verify that once the new timer expires, we can create a wait
        # certificate with it
        while not wt.has_expired(time.time()):
            time.sleep(1)

        WaitCertificate.create_wait_certificate(
            poet_enclave_module=self.poet_enclave_module,
            wait_timer=wt,
            block_hash="Reader's Digest")
    def test_create_before_create_wait_timer(self):
        # Need to create signup information
        SignupInfo.create_signup_info(
            poet_enclave_module=self.poet_enclave_module,
            originator_public_key_hash=self._originator_public_key_hash,
            nonce=NULL_BLOCK_IDENTIFIER)

        # Make sure that trying to create a wait certificate before creating
        # a wait timer causes an error
        with self.assertRaises(ValueError):
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                wait_timer=None,
                block_hash="Reader's Digest")
    def test_unsealing_data(self):
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=poet_enclave,
                originator_public_key_hash=self._originator_public_key_hash,
                nonce=NULL_BLOCK_IDENTIFIER)
        poet_public_key = \
            SignupInfo.unseal_signup_data(
                poet_enclave_module=poet_enclave,
                sealed_signup_data=signup_info.sealed_signup_data)

        self.assertEqual(
            signup_info.poet_public_key,
            poet_public_key,
            msg="PoET public key in signup info and sealed data don't match")
    def signup_attempt_timed_out(self,
                                 signup_nonce,
                                 poet_settings_view,
                                 block_cache):
        """Checks whether too many blocks have elapsed since
        since the registration attempt.

        Args:
            signup_nonce (string): nonce (~ block id) used in signup
            poet_settings_view (PoetSettingsView): The current Poet config view
            block_cache (BlockCache): The block store cache

        Returns:
            bool: True if too many blocks have elapsed; False if you just need
            to chill out a little while longer.
        """
        # It's tempting to set this timeout as the lesser of retry_delay
        # and signup_commit_maximum_delay, but then we can't individually
        # control this timeout behavior. Consider behavior as a new node joins
        # an old network.
        depth = poet_settings_view.registration_retry_delay

        i = 0
        for block in block_cache.block_store.get_block_iter(reverse=True):
            if i > depth:
                return True
            block_id = block.identifier
            if signup_nonce == SignupInfo.block_id_to_nonce(block_id):
                return False
            i += 1
        return False
    def test_create_with_wrong_signup_data(self):
        # Need to create signup information
        signup_info = SignupInfo.create_signup_info(
            poet_enclave_module=self.poet_enclave_module,
            originator_public_key_hash=self._originator_public_key_hash,
            nonce=NULL_BLOCK_IDENTIFIER)

        signup_info2 = SignupInfo.create_signup_info(
            poet_enclave_module=self.poet_enclave_module,
            originator_public_key_hash=self._originator_public_key_hash,
            nonce=NULL_BLOCK_IDENTIFIER)

        # Create two timers and try to create the wait certificate with the
        # first one, which should fail as it is not the current wait timer
        wt1 = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                sealed_signup_data=signup_info.sealed_signup_data,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)
        wt2 = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                sealed_signup_data=signup_info2.sealed_signup_data,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)

        # Verify that we cannot create a wait certificate using the first
        # wait timer with the second signup data
        with self.assertRaises(ValueError):
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                sealed_signup_data=signup_info2.sealed_signup_data,
                wait_timer=wt1,
                block_hash="Reader's Digest")

        WaitCertificate.create_wait_certificate(
            poet_enclave_module=self.poet_enclave_module,
            sealed_signup_data=signup_info2.sealed_signup_data,
            wait_timer=wt2,
            block_hash="Reader's Digest")
    def test_create(self):
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=poet_enclave,
                originator_public_key_hash=self._originator_public_key_hash,
                nonce=NULL_BLOCK_IDENTIFIER)

        self.assertIsNotNone(signup_info.poet_public_key)
        self.assertIsNotNone(signup_info.proof_data)
        self.assertIsNotNone(signup_info.anti_sybil_id)
        self.assertIsNotNone(signup_info.sealed_signup_data)
    def test_serialization(self):
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=poet_enclave,
                originator_public_key_hash=self._originator_public_key_hash,
                nonce=NULL_BLOCK_IDENTIFIER)
        serialized = signup_info.serialize()
        copy_signup_info = \
            SignupInfo.signup_info_from_serialized(
                poet_enclave_module=poet_enclave,
                serialized=serialized)

        self.assertEqual(
            signup_info.poet_public_key,
            copy_signup_info.poet_public_key)
        self.assertEqual(signup_info.proof_data, copy_signup_info.proof_data)
        self.assertEqual(
            signup_info.anti_sybil_id,
            copy_signup_info.anti_sybil_id)
        self.assertIsNone(copy_signup_info.sealed_signup_data)
    def test_create_with_wrong_wait_timer(self):
        # Need to create signup information
        SignupInfo.create_signup_info(
            poet_enclave_module=self.poet_enclave_module,
            originator_public_key_hash=self._originator_public_key_hash,
            nonce=NULL_BLOCK_IDENTIFIER)

        # Create two timers and try to create the wait certificate with the
        # first one, which should fail as it is not the current wait timer
        invalid_wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)
        valid_wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)

        # Verify that we cannot create a wait certificate with the old wait
        # timer, but we can with the new one
        with self.assertRaises(ValueError):
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                wait_timer=invalid_wt,
                block_hash="Reader's Digest")

        WaitCertificate.create_wait_certificate(
            poet_enclave_module=self.poet_enclave_module,
            wait_timer=valid_wt,
            block_hash="Reader's Digest")
    def test_create_after_wait_timer_timed_out(self):
        # Need to create signup information
        signup_info = SignupInfo.create_signup_info(
            poet_enclave_module=self.poet_enclave_module,
            originator_public_key_hash=self._originator_public_key_hash,
            nonce=NULL_BLOCK_IDENTIFIER)

        # Create a wait certificate for the genesis block so that we can
        # create another wait certificate that has to play by the rules.
        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                sealed_signup_data=signup_info.sealed_signup_data,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)
        wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                sealed_signup_data=signup_info.sealed_signup_data,
                wait_timer=wt,
                block_hash="Reader's Digest")

        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                sealed_signup_data=signup_info.sealed_signup_data,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=wc.identifier,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)

        while not wt.has_expired(time.time()):
            time.sleep(1)
        time.sleep(self.poet_enclave_module.TIMER_TIMEOUT_PERIOD + 1)

        with self.assertRaises(ValueError):
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                sealed_signup_data=signup_info.sealed_signup_data,
                wait_timer=wt,
                block_hash="Reader's Digest")
    def test_create(self):
        # Need to create signup information first
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=self.poet_enclave_module,
                originator_public_key_hash=self._originator_public_key_hash,
                nonce=NULL_BLOCK_IDENTIFIER)

        stake_in_the_sand = time.time()

        # create mock_poet_enclave_wait_timer
        mock_poet_enclave_wait_timer = \
            mock.Mock(validator_address='1060 W Addison Street',
                      duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      signature=None,
                      serialized_timer=None,
                      request_time=time.time())

        # create mock_poet_enclave_simulator
        mock_poet_enclave_simulator = mock.Mock()
        mock_poet_enclave_simulator.create_wait_timer.return_value = \
            mock_poet_enclave_wait_timer

        # An empty certificate list should result in a local mean that is
        # the target wait time
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)

        self.assertIsNotNone(wt)
        self.assertEqual(
            wt.local_mean,
            self.mock_poet_settings_view.target_wait_time)
        self.assertEqual(wt.previous_certificate_id, NULL_BLOCK_IDENTIFIER)
        self.assertGreaterEqual(wt.request_time, stake_in_the_sand)
        self.assertLessEqual(wt.request_time, time.time())
        self.assertGreaterEqual(
            wt.duration,
            self.mock_poet_settings_view.minimum_wait_time)
        self.assertEqual(wt.validator_address, '1060 W Addison Street')

        # Ensure that the enclave is set back to initial state
        self.poet_enclave_module = reload(poet_enclave)

        # Make sure that trying to create a wait timer before signup
        # information is provided causes an error
        with self.assertRaises(ValueError):
            wait_timer.WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1060 W Addison Street',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.consensus_state,
                poet_settings_view=self.mock_poet_settings_view)

        # Initialize the enclave with sealed signup data
        SignupInfo.unseal_signup_data(
            poet_enclave_module=self.poet_enclave_module,
            sealed_signup_data=signup_info.sealed_signup_data)

        stake_in_the_sand = time.time()

        mock_poet_enclave_wait_timer.request_time = time.time()

        # An empty certificate list should result in a local mean that is
        # the target wait time
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)

        self.assertIsNotNone(wt)
        self.assertEqual(
            wt.local_mean,
            self.mock_poet_settings_view.target_wait_time)
        self.assertEqual(wt.previous_certificate_id, NULL_BLOCK_IDENTIFIER)
        self.assertGreaterEqual(wt.request_time, stake_in_the_sand)
        self.assertLessEqual(wt.request_time, time.time())
        self.assertGreaterEqual(
            wt.duration,
            self.mock_poet_settings_view.minimum_wait_time)
        self.assertEqual(wt.validator_address, '1060 W Addison Street')
    def test_has_expired(self):
        # Need to create signup information first
        SignupInfo.create_signup_info(
            poet_enclave_module=self.poet_enclave_module,
            originator_public_key_hash=self._originator_public_key_hash,
            nonce=NULL_BLOCK_IDENTIFIER)

        # create mock_poet_enclave_wait_timer
        mock_poet_enclave_wait_timer = \
            mock.Mock(validator_address='1060 W Addison Street',
                      duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      signature=None,
                      serialized_timer=None,
                      request_time=time.time())

        # create mock_poet_enclave_simulator
        mock_poet_enclave_simulator = mock.Mock()
        mock_poet_enclave_simulator.create_wait_timer.return_value = \
            mock_poet_enclave_wait_timer

        # Verify that a timer doesn't expire before its creation time
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)
        self.assertFalse(wt.has_expired(wt.request_time - 1))

        # Create a timer and when it has expired, verify that the duration is
        # not greater than actual elapsed time.
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)

        self.assertTrue(wt.has_expired(wt.request_time + wt.duration + 1.0))

        # Tampering with the duration should not affect wait timer expiration
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)

        assigned_duration = wt.duration
        wt.duration = 0

        self.assertTrue(wt.has_expired
                        (wt.request_time + assigned_duration + 1.0))

        # Tampering with the request time should not affect wait timer
        # expiration
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)
        assigned_request_time = wt.request_time
        wt.request_time -= wt.duration

        self.assertTrue(wt.has_expired
                        (assigned_request_time + wt.duration + 1.0))
Example #13
0
def do_create(args):
    """Executes the `poet registration` subcommand.

    This command generates a validator registry transaction and saves it to a
    file, whose location is determined by the args.  The signup data, generated
    by the selected enclave, is also stored in a well-known location.
    """
    signer = _read_signer(args.key)
    public_key = signer.get_public_key().as_hex()

    public_key_hash = sha256(public_key.encode()).hexdigest()
    nonce = SignupInfo.block_id_to_nonce(args.block)
    with PoetEnclaveModuleWrapper(
            enclave_module=args.enclave_module,
            config_dir=config.get_config_dir(),
            data_dir=config.get_data_dir()) as poet_enclave_module:
        signup_info = SignupInfo.create_signup_info(
            poet_enclave_module=poet_enclave_module,
            originator_public_key_hash=public_key_hash,
            nonce=nonce)

    print(
        'Writing key state for PoET public key: {}...{}'.format(
            signup_info.poet_public_key[:8],
            signup_info.poet_public_key[-8:]))

    # Store the newly-created PoET key state, associating it with its
    # corresponding public key
    poet_key_state_store = \
        PoetKeyStateStore(
            data_dir=config.get_data_dir(),
            validator_id=public_key)
    poet_key_state_store[signup_info.poet_public_key] = \
        PoetKeyState(
            sealed_signup_data=signup_info.sealed_signup_data,
            has_been_refreshed=False,
            signup_nonce=nonce)

    # Create the validator registry payload
    payload = \
        vr_pb.ValidatorRegistryPayload(
            verb='register',
            name='validator-{}'.format(public_key[:8]),
            id=public_key,
            signup_info=vr_pb.SignUpInfo(
                poet_public_key=signup_info.poet_public_key,
                proof_data=signup_info.proof_data,
                anti_sybil_id=signup_info.anti_sybil_id,
                nonce=SignupInfo.block_id_to_nonce(args.block)))
    serialized = payload.SerializeToString()

    # Create the address that will be used to look up this validator
    # registry transaction.  Seems like a potential for refactoring..
    validator_entry_address = \
        VR_NAMESPACE + sha256(public_key.encode()).hexdigest()

    # Create a transaction header and transaction for the validator
    # registry update amd then hand it off to the batch publisher to
    # send out.
    output_addresses = [validator_entry_address, VALIDATOR_MAP_ADDRESS]
    input_addresses = \
        output_addresses + \
        [SettingsView.setting_address('sawtooth.poet.report_public_key_pem'),
         SettingsView.setting_address('sawtooth.poet.'
                                      'valid_enclave_measurements'),
         SettingsView.setting_address('sawtooth.poet.valid_enclave_basenames')]

    header = \
        txn_pb.TransactionHeader(
            signer_public_key=public_key,
            family_name='sawtooth_validator_registry',
            family_version='1.0',
            inputs=input_addresses,
            outputs=output_addresses,
            dependencies=[],
            payload_sha512=sha512(serialized).hexdigest(),
            batcher_public_key=public_key,
            nonce=time.time().hex().encode()).SerializeToString()
    signature = signer.sign(header)

    transaction = \
        txn_pb.Transaction(
            header=header,
            payload=serialized,
            header_signature=signature)

    batch = _create_batch(signer, [transaction])
    batch_list = batch_pb.BatchList(batches=[batch])
    try:
        print('Generating {}'.format(args.output))
        with open(args.output, 'wb') as batch_file:
            batch_file.write(batch_list.SerializeToString())
    except IOError as e:
        raise CliException('Unable to write to batch file: {}'.format(str(e)))
Example #14
0
    def test_create(self):
        # Need to create signup information and wait timer first
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                originator_public_key_hash=self._originator_public_key_hash,
                nonce=NULL_BLOCK_IDENTIFIER)

        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.consensus_state,
                poet_config_view=self.mock_poet_config_view)
        while not wt.has_expired(time.time()):
            time.sleep(1)

        # Now we can create a wait certificate and verify that it correlates
        # to the wait timer we just created
        wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                wait_timer=wt,
                block_hash="Reader's Digest")

        self.assertIsNotNone(wc)

        self.assertEqual(wc.previous_certificate_id,
                         wt.previous_certificate_id)
        self.assertAlmostEqual(wc.local_mean, wt.local_mean)
        self.assertAlmostEqual(wc.request_time, wt.request_time)
        self.assertAlmostEqual(wc.duration, wt.duration)
        self.assertEqual(wc.validator_address, wt.validator_address)
        self.assertEqual(wc.block_hash, "Reader's Digest")
        self.assertIsNotNone(wc.signature)
        self.assertIsNotNone(wc.identifier)

        # A newly-created wait certificate should be valid
        wc.check_valid(poet_enclave_module=self.poet_enclave_module,
                       previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                       poet_public_key=signup_info.poet_public_key,
                       consensus_state=self.consensus_state,
                       poet_config_view=self.mock_poet_config_view)

        validator_info = \
            ValidatorInfo(
                id='validator_001',
                signup_info=SignUpInfo(
                    poet_public_key='key_001'))
        self.consensus_state.validator_did_claim_block(
            validator_info=validator_info,
            wait_certificate=wc,
            poet_config_view=self.mock_poet_config_view)

        # Create another wait certificate and verify it is valid also
        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=wc.identifier,
                consensus_state=self.consensus_state,
                poet_config_view=self.mock_poet_config_view)
        while not wt.has_expired(time.time()):
            time.sleep(1)

        # Now we can create a wait certificate and verify that it correlates
        # to the wait timer we just created
        another_wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                wait_timer=wt,
                block_hash="Pepto Bismol")

        another_wc.check_valid(poet_enclave_module=self.poet_enclave_module,
                               previous_certificate_id=wc.identifier,
                               poet_public_key=signup_info.poet_public_key,
                               consensus_state=self.consensus_state,
                               poet_config_view=self.mock_poet_config_view)
    def test_create(self):
        # Need to create signup information and wait timer first
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=self.poet_enclave_module,
                originator_public_key_hash=self._originator_public_key_hash,
                nonce=NULL_BLOCK_IDENTIFIER)

        # create mock_poet_enclave_wait_timer
        mock_poet_enclave_wait_timer = \
            mock.Mock(sealed_signup_data=signup_info.sealed_signup_data,
                      validator_address='1060 W Addison Street',
                      duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      signature='00112233445566778899aabbccddeeff',
                      serialized_timer=None,
                      request_time=time.time())

        # create mock_poet_enclave_wait_certificate
        mock_poet_enclave_wait_certificate = \
            mock.Mock(sealed_signup_data=signup_info.sealed_signup_data,
                      duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      request_time=time.time(),
                      validator_address='1060 W Addison Street',
                      nonce=NULL_BLOCK_IDENTIFIER,
                      block_hash="Reader's Digest",
                      signature='00112233445566778899aabbccddeeff',
                      serialized_certificate='001122334455667'
                                             '78899aabbccddeeff')

        # create mock_poet_enclave_module
        mock_poet_enclave_module = mock.Mock()
        mock_poet_enclave_module.create_wait_timer.return_value = \
            mock_poet_enclave_wait_timer

        # set the mock enclave wait certificate and wait timer to
        # have the same request_time
        mock_poet_enclave_wait_certificate.request_time = \
            mock_poet_enclave_wait_timer.request_time

        # set the mock enclave wait certificate and wait timer to
        # have the same previous_certificate_id
        mock_poet_enclave_wait_certificate.previous_certificate_id = \
            mock_poet_enclave_wait_timer.previous_certificate_id

        # set the identifier for mock_poet_enclave_wait_certificate
        mock_poet_enclave_wait_certificate.identifier.return_value = \
            mock_poet_enclave_wait_certificate.previous_certificate_id[:16]

        mock_poet_enclave_module.create_wait_certificate.return_value = \
            mock_poet_enclave_wait_certificate

        mock_poet_enclave_module.deserialize_wait_certificate.return_value = \
            mock_poet_enclave_wait_certificate

        # create wait timer
        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=mock_poet_enclave_module,
                sealed_signup_data=signup_info.sealed_signup_data,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)

        # Now we can create a wait certificate and verify that it correlates
        # to the wait timer we just created
        wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=mock_poet_enclave_module,
                sealed_signup_data=signup_info.sealed_signup_data,
                wait_timer=wt,
                block_hash="Reader's Digest")

        self.assertIsNotNone(wc)
        self.assertEqual(
            wc.previous_certificate_id,
            wt.previous_certificate_id)
        self.assertAlmostEqual(wc.local_mean, wt.local_mean)
        self.assertAlmostEqual(wc.request_time, wt.request_time)
        self.assertAlmostEqual(wc.duration, wt.duration)
        self.assertEqual(wc.validator_address, wt.validator_address)
        self.assertEqual(wc.block_hash, "Reader's Digest")
        self.assertIsNotNone(wc.signature)
        self.assertIsNotNone(wc.identifier)

        # A newly-created wait certificate should be valid
        wc.check_valid(
            poet_enclave_module=mock_poet_enclave_module,
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            poet_public_key=signup_info.poet_public_key,
            consensus_state=self.mock_consensus_state,
            poet_settings_view=self.mock_poet_settings_view)

        validator_info = \
            ValidatorInfo(
                id='validator_001',
                signup_info=SignUpInfo(
                    poet_public_key='key_001'))
        self.mock_consensus_state.validator_did_claim_block(
            validator_info=validator_info,
            wait_certificate=wc,
            poet_settings_view=self.mock_poet_settings_view)

        # Create another wait certificate and verify it is valid also
        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=mock_poet_enclave_module,
                sealed_signup_data=signup_info.sealed_signup_data,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=wc.identifier,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)

        # Now we can create a wait certificate and verify that it correlates
        # to the wait timer we just created
        another_wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=mock_poet_enclave_module,
                sealed_signup_data=signup_info.sealed_signup_data,
                wait_timer=wt,
                block_hash="Pepto Bismol")

        another_wc.check_valid(
            poet_enclave_module=mock_poet_enclave_module,
            previous_certificate_id=wc.identifier,
            poet_public_key=signup_info.poet_public_key,
            consensus_state=self.mock_consensus_state,
            poet_settings_view=self.mock_poet_settings_view)
    def test_create(self):
        # Need to create signup information and wait timer first
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=self.poet_enclave_module,
                originator_public_key_hash=self._originator_public_key_hash,
                nonce=NULL_BLOCK_IDENTIFIER)

        # create mock_poet_enclave_wait_timer
        mock_poet_enclave_wait_timer = \
            mock.Mock(validator_address='1060 W Addison Street',
                      duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      signature='00112233445566778899aabbccddeeff',
                      serialized_timer=None,
                      request_time=time.time())

        # create mock_poet_enclave_wait_certificate
        mock_poet_enclave_wait_certificate = \
            mock.Mock(duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      request_time=time.time(),
                      validator_address='1060 W Addison Street',
                      nonce=NULL_BLOCK_IDENTIFIER,
                      block_hash="Reader's Digest",
                      signature='00112233445566778899aabbccddeeff',
                      serialized_certificate='001122334455667'
                                             '78899aabbccddeeff')

        # create mock_poet_enclave_module
        mock_poet_enclave_module = mock.Mock()
        mock_poet_enclave_module.create_wait_timer.return_value = \
            mock_poet_enclave_wait_timer

        # set the mock enclave wait certificate and wait timer to
        # have the same request_time
        mock_poet_enclave_wait_certificate.request_time = \
            mock_poet_enclave_wait_timer.request_time

        # set the mock enclave wait certificate and wait timer to
        # have the same previous_certificate_id
        mock_poet_enclave_wait_certificate.previous_certificate_id = \
            mock_poet_enclave_wait_timer.previous_certificate_id

        # set the identifier for mock_poet_enclave_wait_certificate
        mock_poet_enclave_wait_certificate.identifier.return_value = \
            mock_poet_enclave_wait_certificate.previous_certificate_id[:16]

        mock_poet_enclave_module.create_wait_certificate.return_value = \
            mock_poet_enclave_wait_certificate

        mock_poet_enclave_module.deserialize_wait_certificate.return_value = \
            mock_poet_enclave_wait_certificate

        # create wait timer
        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=mock_poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)

        # Now we can create a wait certificate and verify that it correlates
        # to the wait timer we just created
        wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=mock_poet_enclave_module,
                wait_timer=wt,
                block_hash="Reader's Digest")

        self.assertIsNotNone(wc)
        self.assertEqual(
            wc.previous_certificate_id,
            wt.previous_certificate_id)
        self.assertAlmostEqual(wc.local_mean, wt.local_mean)
        self.assertAlmostEqual(wc.request_time, wt.request_time)
        self.assertAlmostEqual(wc.duration, wt.duration)
        self.assertEqual(wc.validator_address, wt.validator_address)
        self.assertEqual(wc.block_hash, "Reader's Digest")
        self.assertIsNotNone(wc.signature)
        self.assertIsNotNone(wc.identifier)

        # A newly-created wait certificate should be valid
        wc.check_valid(
            poet_enclave_module=mock_poet_enclave_module,
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            poet_public_key=signup_info.poet_public_key,
            consensus_state=self.mock_consensus_state,
            poet_settings_view=self.mock_poet_settings_view)

        validator_info = \
            ValidatorInfo(
                id='validator_001',
                signup_info=SignUpInfo(
                    poet_public_key='key_001'))
        self.mock_consensus_state.validator_did_claim_block(
            validator_info=validator_info,
            wait_certificate=wc,
            poet_settings_view=self.mock_poet_settings_view)

        # Create another wait certificate and verify it is valid also
        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=mock_poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=wc.identifier,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)

        # Now we can create a wait certificate and verify that it correlates
        # to the wait timer we just created
        another_wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=mock_poet_enclave_module,
                wait_timer=wt,
                block_hash="Pepto Bismol")

        another_wc.check_valid(
            poet_enclave_module=mock_poet_enclave_module,
            previous_certificate_id=wc.identifier,
            poet_public_key=signup_info.poet_public_key,
            consensus_state=self.mock_consensus_state,
            poet_settings_view=self.mock_poet_settings_view)
Example #17
0
def do_create(args):
    """Executes the `poet registration` subcommand.

    This command generates a validator registry transaction and saves it to a
    file, whose location is determined by the args.  The signup data, generated
    by the selected enclave, is also stored in a well-known location.
    """
    signer = _read_signer(args.key)
    public_key = signer.get_public_key().as_hex()

    public_key_hash = sha256(public_key.encode()).hexdigest()
    nonce = SignupInfo.block_id_to_nonce(args.block)
    with PoetEnclaveModuleWrapper(
            enclave_module=args.enclave_module,
            config_dir=config.get_config_dir(),
            data_dir=config.get_data_dir()) as poet_enclave_module:
        signup_info = SignupInfo.create_signup_info(
            poet_enclave_module=poet_enclave_module,
            originator_public_key_hash=public_key_hash,
            nonce=nonce)

    print('Writing key state for PoET public key: {}...{}'.format(
        signup_info.poet_public_key[:8], signup_info.poet_public_key[-8:]))

    # Store the newly-created PoET key state, associating it with its
    # corresponding public key
    poet_key_state_store = \
        PoetKeyStateStore(
            data_dir=config.get_data_dir(),
            validator_id=public_key)
    poet_key_state_store[signup_info.poet_public_key] = \
        PoetKeyState(
            sealed_signup_data=signup_info.sealed_signup_data,
            has_been_refreshed=False,
            signup_nonce=nonce)

    # Create the validator registry payload
    payload = \
        vr_pb.ValidatorRegistryPayload(
            verb='register',
            name='validator-{}'.format(public_key[:8]),
            id=public_key,
            signup_info=vr_pb.SignUpInfo(
                poet_public_key=signup_info.poet_public_key,
                proof_data=signup_info.proof_data,
                anti_sybil_id=signup_info.anti_sybil_id,
                nonce=SignupInfo.block_id_to_nonce(args.block)))
    serialized = payload.SerializeToString()

    # Create the address that will be used to look up this validator
    # registry transaction.  Seems like a potential for refactoring..
    validator_entry_address = \
        VR_NAMESPACE + sha256(public_key.encode()).hexdigest()

    # Create a transaction header and transaction for the validator
    # registry update amd then hand it off to the batch publisher to
    # send out.
    output_addresses = [validator_entry_address, VALIDATOR_MAP_ADDRESS]
    input_addresses = \
        output_addresses + \
        [SettingsView.setting_address('sawtooth.poet.report_public_key_pem'),
         SettingsView.setting_address('sawtooth.poet.'
                                      'valid_enclave_measurements'),
         SettingsView.setting_address('sawtooth.poet.valid_enclave_basenames')]

    header = \
        txn_pb.TransactionHeader(
            signer_public_key=public_key,
            family_name='sawtooth_validator_registry',
            family_version='1.0',
            inputs=input_addresses,
            outputs=output_addresses,
            dependencies=[],
            payload_sha512=sha512(serialized).hexdigest(),
            batcher_public_key=public_key,
            nonce=time.time().hex().encode()).SerializeToString()
    signature = signer.sign(header)

    transaction = \
        txn_pb.Transaction(
            header=header,
            payload=serialized,
            header_signature=signature)

    batch = _create_batch(signer, [transaction])
    batch_list = batch_pb.BatchList(batches=[batch])
    try:
        print('Generating {}'.format(args.output))
        with open(args.output, 'wb') as batch_file:
            batch_file.write(batch_list.SerializeToString())
    except IOError as e:
        raise CliException('Unable to write to batch file: {}'.format(
            str(e))) from e
    def initialize_block(self, block_header):
        """Do initialization necessary for the consensus to claim a block,
        this may include initiating voting activities, starting proof of work
        hash generation, or create a PoET wait timer.

        Args:
            block_header (BlockHeader): The BlockHeader to initialize.
        Returns:
            Boolean: True if the candidate block should be built. False if
            no candidate should be built.
        """
        # If the previous block ID matches our cached one, that means that we
        # have already determined that even if we initialize the requested
        # block we would not be able to claim it.  So, instead of wasting time
        # doing all of the checking again, simply short-circuit the failure so
        # that the validator can go do something more useful.
        if block_header.previous_block_id == \
                PoetBlockPublisher._previous_block_id:
            return False
        PoetBlockPublisher._previous_block_id = block_header.previous_block_id

        # Using the current chain head, we need to create a state view so we
        # can create a PoET enclave.
        state_view = \
            BlockWrapper.state_view_for_block(
                block_wrapper=self._block_cache.block_store.chain_head,
                state_view_factory=self._state_view_factory)

        poet_enclave_module = \
            factory.PoetEnclaveFactory.get_poet_enclave_module(
                state_view=state_view,
                config_dir=self._config_dir,
                data_dir=self._data_dir)

        # Get our validator registry entry to see what PoET public key
        # other validators think we are using.
        validator_registry_view = ValidatorRegistryView(state_view)
        validator_info = None

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

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

            return False

        # Otherwise, we have a current validator registry entry.  In that
        # case, we need to make sure that we are using the same PPK that the
        # other validators think we are using.  If not, then we need to switch
        # the PoET enclave to using the correct keys.
        elif validator_info.signup_info.poet_public_key != \
                active_poet_public_key:
            # Retrieve the key state corresponding to the PoET public key and
            # use it to re-establish the key used by the enclave.  Also update
            # the active PoET public key.
            poet_key_state = \
                self._poet_key_state_store[
                    validator_info.signup_info.poet_public_key]

            active_poet_public_key = \
                SignupInfo.unseal_signup_data(
                    poet_enclave_module=poet_enclave_module,
                    sealed_signup_data=poet_key_state.sealed_signup_data)
            self._poet_key_state_store.active_key = active_poet_public_key

            assert active_poet_public_key == \
                validator_info.signup_info.poet_public_key

            LOGGER.debug(
                'Switched to public key: %s...%s',
                active_poet_public_key[:8],
                active_poet_public_key[-8:])
            LOGGER.debug(
                'Unseal signup data: %s...%s',
                poet_key_state.sealed_signup_data[:8],
                poet_key_state.sealed_signup_data[-8:])

        consensus_state = \
            ConsensusState.consensus_state_for_block_id(
                block_id=block_header.previous_block_id,
                block_cache=self._block_cache,
                state_view_factory=self._state_view_factory,
                consensus_state_store=self._consensus_state_store,
                poet_enclave_module=poet_enclave_module)
        poet_settings_view = PoetSettingsView(state_view)

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

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

                sealed_signup_data = poet_key_state.sealed_signup_data
                self._poet_key_state_store[active_poet_public_key] = \
                    PoetKeyState(
                        sealed_signup_data=sealed_signup_data,
                        has_been_refreshed=True)

                self._register_signup_information(
                    block_header=block_header,
                    poet_enclave_module=poet_enclave_module)

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

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

        # We need to create a wait timer for the block...this is what we
        # will check when we are asked if it is time to publish the block
        previous_certificate_id = \
            utils.get_previous_certificate_id(
                block_header=block_header,
                block_cache=self._block_cache,
                poet_enclave_module=poet_enclave_module)
        wait_timer = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=poet_enclave_module,
                validator_address=block_header.signer_pubkey,
                previous_certificate_id=previous_certificate_id,
                consensus_state=consensus_state,
                poet_settings_view=poet_settings_view)

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

        # Check to see if by chance we were to be able to claim this block
        # if it would result in us winning more frequently than statistically
        # expected.  If so, then refuse to initialize the block because other
        # validators will not accept anyway.
        if consensus_state.validator_is_claiming_too_frequently(
                validator_info=validator_info,
                previous_block_id=block_header.previous_block_id,
                poet_settings_view=poet_settings_view,
                population_estimate=wait_timer.population_estimate(
                    poet_settings_view=poet_settings_view),
                block_cache=self._block_cache,
                poet_enclave_module=poet_enclave_module):
            LOGGER.info(
                'Reject building on block %s: Validator is claiming blocks '
                'too frequently.',
                block_header.previous_block_id[:8])
            return False

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

        self._wait_timer = wait_timer
        PoetBlockPublisher._previous_block_id = None

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

        return True
Example #19
0
    def test_create(self):
        # Need to create signup information first
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=self.poet_enclave_module,
                originator_public_key_hash=self._originator_public_key_hash,
                nonce=NULL_BLOCK_IDENTIFIER)

        stake_in_the_sand = time.time()

        # create mock_poet_enclave_wait_timer
        mock_poet_enclave_wait_timer = \
            mock.Mock(validator_address='1060 W Addison Street',
                      duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      signature=None,
                      serialized_timer=None,
                      request_time=time.time())

        # create mock_poet_enclave_simulator
        mock_poet_enclave_simulator = mock.Mock()
        mock_poet_enclave_simulator.create_wait_timer.return_value = \
            mock_poet_enclave_wait_timer

        # An empty certificate list should result in a local mean that is
        # the target wait time
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            sealed_signup_data=signup_info.sealed_signup_data,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)

        self.assertIsNotNone(wt)
        self.assertEqual(wt.local_mean,
                         self.mock_poet_settings_view.target_wait_time)
        self.assertEqual(wt.previous_certificate_id, NULL_BLOCK_IDENTIFIER)
        self.assertGreaterEqual(wt.request_time, stake_in_the_sand)
        self.assertLessEqual(wt.request_time, time.time())
        self.assertGreaterEqual(wt.duration, TestWaitTimer.MINIMUM_WAIT_TIME)
        self.assertEqual(wt.validator_address, '1060 W Addison Street')

        # Ensure that the enclave is set back to initial state
        self.poet_enclave_module = reload(poet_enclave)

        stake_in_the_sand = time.time()

        mock_poet_enclave_wait_timer.request_time = time.time()

        # An empty certificate list should result in a local mean that is
        # the target wait time
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            sealed_signup_data=signup_info.sealed_signup_data,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)

        self.assertIsNotNone(wt)
        self.assertEqual(wt.local_mean,
                         self.mock_poet_settings_view.target_wait_time)
        self.assertEqual(wt.previous_certificate_id, NULL_BLOCK_IDENTIFIER)
        self.assertGreaterEqual(wt.request_time, stake_in_the_sand)
        self.assertLessEqual(wt.request_time, time.time())
        self.assertGreaterEqual(wt.duration, TestWaitTimer.MINIMUM_WAIT_TIME)
        self.assertEqual(wt.validator_address, '1060 W Addison Street')
Example #20
0
    def test_has_expired(self):
        # Need to create signup information first
        signup_info = SignupInfo.create_signup_info(
            poet_enclave_module=self.poet_enclave_module,
            originator_public_key_hash=self._originator_public_key_hash,
            nonce=NULL_BLOCK_IDENTIFIER)

        # create mock_poet_enclave_wait_timer
        mock_poet_enclave_wait_timer = \
            mock.Mock(validator_address='1060 W Addison Street',
                      duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      signature=None,
                      serialized_timer=None,
                      request_time=time.time())

        # create mock_poet_enclave_simulator
        mock_poet_enclave_simulator = mock.Mock()
        mock_poet_enclave_simulator.create_wait_timer.return_value = \
            mock_poet_enclave_wait_timer

        # Verify that a timer doesn't expire before its creation time
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            sealed_signup_data=signup_info.sealed_signup_data,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)
        self.assertFalse(wt.has_expired(wt.request_time - 1))

        # Create a timer and when it has expired, verify that the duration is
        # not greater than actual elapsed time.
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            sealed_signup_data=signup_info.sealed_signup_data,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)

        self.assertTrue(wt.has_expired(wt.request_time + wt.duration + 1.0))

        # Tampering with the duration should not affect wait timer expiration
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            sealed_signup_data=signup_info.sealed_signup_data,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)

        assigned_duration = wt.duration
        wt.duration = 0

        self.assertTrue(
            wt.has_expired(wt.request_time + assigned_duration + 1.0))

        # Tampering with the request time should not affect wait timer
        # expiration
        wt = wait_timer.WaitTimer.create_wait_timer(
            poet_enclave_module=mock_poet_enclave_simulator,
            sealed_signup_data=signup_info.sealed_signup_data,
            validator_address='1060 W Addison Street',
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            consensus_state=self.consensus_state,
            poet_settings_view=self.mock_poet_settings_view)
        assigned_request_time = wt.request_time
        wt.request_time -= wt.duration

        self.assertTrue(
            wt.has_expired(assigned_request_time + wt.duration + 1.0))
    def validator_signup_was_committed_too_late(self, validator_info,
                                                poet_settings_view,
                                                block_cache):
        """Determines if a validator's registry transaction committing it
        current PoET keys, etc., was committed too late - i.e., the number of
        blocks between when the transaction was submitted and when it was
        committed is greater than signup commit maximum delay.

        Args:
            validator_info (ValidatorInfo): The current validator information
            poet_settings_view (PoetSettingsView): The current Poet config view
            block_cache (BlockCache): The block store cache

        Returns:
            bool: True if the validator's registry transaction was committed
                beyond the signup commit maximum delay, False otherwise
        """
        # Figure out the block in which the current validator information
        # was committed.
        try:
            block = block_cache.block_store.get_block_by_transaction_id(
                validator_info.transaction_id)
        except ValueError:
            LOGGER.warning(
                'Validator %s (ID=%s...%s): Signup txn %s not found in block.',
                validator_info.name, validator_info.id[:8],
                validator_info.id[-8:], validator_info.transaction_id[:8])
            return False

        commit_block_id = block.identifier

        # Starting with that block's immediate predecessor, walk back until
        # either we match the block ID with the nonce field in the signup
        # info, we have checked the maximum number of blocks, or we somehow
        # reached the beginning of the blockchain.  The first case is
        # success (i.e., the validator signup info passed the freshness
        # test) while the other two cases are failure.
        for _ in range(poet_settings_view.signup_commit_maximum_delay + 1):
            if SignupInfo.block_id_to_nonce(block.previous_block_id) == \
                    validator_info.signup_info.nonce:
                LOGGER.debug(
                    'Validator %s (ID=%s...%s): Signup committed block %s, '
                    'chain head was block %s', validator_info.name,
                    validator_info.id[:8], validator_info.id[-8:],
                    commit_block_id[:8], block.previous_block_id[:8])
                return False

            if utils.block_id_is_genesis(block.previous_block_id):
                LOGGER.info(
                    'Validator %s (ID=%s...%s): Signup committed block %s, '
                    'hit start of blockchain looking for block %s',
                    validator_info.name, validator_info.id[:8],
                    validator_info.id[-8:], commit_block_id[:8],
                    validator_info.signup_info.nonce[:8])
                return True

            block = block_cache[block.previous_block_id]

        LOGGER.info(
            'Validator %s (ID=%s...%s): Signup committed block %s, failed to '
            'find block with ID ending in %s in %d previous block(s)',
            validator_info.name, validator_info.id[:8], validator_info.id[-8:],
            commit_block_id[:8], validator_info.signup_info.nonce,
            poet_settings_view.signup_commit_maximum_delay + 1)
        return True
Example #22
0
    def test_serialization(self):
        # Need to create signup information and wait timer first
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                originator_public_key_hash=self._originator_public_key_hash,
                nonce=NULL_BLOCK_IDENTIFIER)

        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=self.poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.consensus_state,
                poet_config_view=self.mock_poet_config_view)
        while not wt.has_expired(time.time()):
            time.sleep(1)

        # Now we can create a wait certificate and serialize
        wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=self.poet_enclave_module,
                wait_timer=wt,
                block_hash="Reader's Digest")

        dumped = wc.dump()

        self.assertIsNotNone(dumped.get('SerializedCertificate'))
        self.assertIsNotNone(dumped.get('Signature'))

        # Deserialize and verify that wait certificates are the same
        # and that deserialized one is valid
        wc_copy = \
            WaitCertificate.wait_certificate_from_serialized(
                poet_enclave_module=self.poet_enclave_module,
                serialized=dumped.get('SerializedCertificate'),
                signature=dumped.get('Signature'))

        self.assertEqual(wc.previous_certificate_id,
                         wc_copy.previous_certificate_id)
        self.assertAlmostEqual(wc.local_mean, wc_copy.local_mean)
        self.assertAlmostEqual(wc.request_time, wc_copy.request_time)
        self.assertAlmostEqual(wc.duration, wc_copy.duration)
        self.assertEqual(wc.validator_address, wc_copy.validator_address)
        self.assertEqual(wc.block_hash, wc_copy.block_hash)
        self.assertEqual(wc.signature, wc_copy.signature)
        self.assertEqual(wc.identifier, wc_copy.identifier)

        # Serialize the copy and verify that its serialization and
        # signature are the same
        dumped_copy = wc_copy.dump()

        self.assertTrue(dumped.get('SerializedCertificate'),
                        dumped_copy.get('SerializedCertificate'))
        self.assertTrue(dumped.get('Signature'), dumped_copy.get('Signature'))

        wc_copy.check_valid(poet_enclave_module=self.poet_enclave_module,
                            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                            poet_public_key=signup_info.poet_public_key,
                            consensus_state=self.consensus_state,
                            poet_config_view=self.mock_poet_config_view)
    def _register_signup_information(self, block_header, poet_enclave_module):
        # Create signup information for this validator, putting the block ID
        # of the block previous to the block referenced by block_header in the
        # nonce.  Block ID is better than wait certificate ID for testing
        # freshness as we need to account for non-PoET blocks.
        public_key_hash = \
            hashlib.sha256(
                block_header.signer_public_key.encode()).hexdigest()
        nonce = SignupInfo.block_id_to_nonce(block_header.previous_block_id)
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=poet_enclave_module,
                originator_public_key_hash=public_key_hash,
                nonce=nonce)

        # Create the validator registry payload
        payload = \
            vr_pb.ValidatorRegistryPayload(
                verb='register',
                name='validator-{}'.format(block_header.signer_public_key[:8]),
                id=block_header.signer_public_key,
                signup_info=vr_pb.SignUpInfo(
                    poet_public_key=signup_info.poet_public_key,
                    proof_data=signup_info.proof_data,
                    anti_sybil_id=signup_info.anti_sybil_id,
                    nonce=nonce),
            )
        serialized = payload.SerializeToString()

        # Create the address that will be used to look up this validator
        # registry transaction.  Seems like a potential for refactoring..
        validator_entry_address = \
            PoetBlockPublisher._validator_registry_namespace + \
            hashlib.sha256(block_header.signer_public_key.encode()).hexdigest()

        # Create a transaction header and transaction for the validator
        # registry update amd then hand it off to the batch publisher to
        # send out.
        output_addresses = \
            [validator_entry_address,
             PoetBlockPublisher._validator_map_address]
        input_addresses = \
            output_addresses + \
            [SettingsView.setting_address(
                'sawtooth.poet.report_public_key_pem'),
             SettingsView.setting_address(
                 'sawtooth.poet.valid_enclave_measurements'),
             SettingsView.setting_address(
                 'sawtooth.poet.valid_enclave_basenames')]

        header = \
            txn_pb.TransactionHeader(
                signer_public_key=block_header.signer_public_key,
                family_name='sawtooth_validator_registry',
                family_version='1.0',
                inputs=input_addresses,
                outputs=output_addresses,
                dependencies=[],
                payload_sha512=hashlib.sha512(serialized).hexdigest(),
                batcher_public_key=block_header.signer_public_key,
                nonce=time.time().hex().encode()).SerializeToString()

        signature = self._batch_publisher.identity_signer.sign(header)

        transaction = \
            txn_pb.Transaction(
                header=header,
                payload=serialized,
                header_signature=signature)

        LOGGER.info(
            'Register Validator Name=%s, ID=%s...%s, PoET public key=%s...%s, '
            'Nonce=%s',
            payload.name,
            payload.id[:8],
            payload.id[-8:],
            payload.signup_info.poet_public_key[:8],
            payload.signup_info.poet_public_key[-8:],
            nonce)

        self._batch_publisher.send([transaction])

        # Store the key state so that we can look it up later if need be and
        # set the new key as our active key
        LOGGER.info(
            'Save key state PPK=%s...%s => SSD=%s...%s',
            signup_info.poet_public_key[:8],
            signup_info.poet_public_key[-8:],
            signup_info.sealed_signup_data[:8],
            signup_info.sealed_signup_data[-8:])
        self._poet_key_state_store[signup_info.poet_public_key] = \
            PoetKeyState(
                sealed_signup_data=signup_info.sealed_signup_data,
                has_been_refreshed=False,
                signup_nonce=nonce)
        self._poet_key_state_store.active_key = signup_info.poet_public_key
    def initialize_block(self, block_header):
        """Do initialization necessary for the consensus to claim a block,
        this may include initiating voting activities, starting proof of work
        hash generation, or create a PoET wait timer.

        Args:
            block_header (BlockHeader): The BlockHeader to initialize.
        Returns:
            Boolean: True if the candidate block should be built. False if
            no candidate should be built.
        """
        # If the previous block ID matches our cached one, that means that we
        # have already determined that even if we initialize the requested
        # block we would not be able to claim it.  So, instead of wasting time
        # doing all of the checking again, simply short-circuit the failure so
        # that the validator can go do something more useful.
        if block_header.previous_block_id == \
                PoetBlockPublisher._previous_block_id:
            return False
        PoetBlockPublisher._previous_block_id = block_header.previous_block_id

        # Using the current chain head, we need to create a state view so we
        # can create a PoET enclave.
        state_view = \
            BlockWrapper.state_view_for_block(
                block_wrapper=self._block_cache.block_store.chain_head,
                state_view_factory=self._state_view_factory)

        poet_enclave_module = \
            factory.PoetEnclaveFactory.get_poet_enclave_module(
                state_view=state_view,
                config_dir=self._config_dir,
                data_dir=self._data_dir)

        # Get our validator registry entry to see what PoET public key
        # other validators think we are using.
        validator_registry_view = ValidatorRegistryView(state_view)
        validator_info = None

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

        # If we don't have a validator registry entry, then check the active
        # key.  If we don't have one, then we need to sign up.  If we do have
        # one, then our validator registry entry has not percolated through the
        # system, so nothing to to but wait.
        active_poet_public_key = self._poet_key_state_store.active_key
        if validator_info is None:
            if active_poet_public_key is None:
                LOGGER.debug(
                    'No public key found, so going to register new signup '
                    'information')
                self._register_signup_information(
                    block_header=block_header,
                    poet_enclave_module=poet_enclave_module)
            else:  # Check if we need to give up on this registration attempt
                try:
                    nonce = self._poet_key_state_store[
                        active_poet_public_key].signup_nonce
                except (ValueError, AttributeError):
                    self._poet_key_state_store.active_key = None
                    LOGGER.warning('Poet Key State Store had inaccessible or '
                                   'corrupt active key [%s] clearing '
                                   'key.', active_poet_public_key)
                    return False

                self._handle_registration_timeout(
                    block_header=block_header,
                    poet_enclave_module=poet_enclave_module,
                    state_view=state_view,
                    signup_nonce=nonce,
                    poet_public_key=active_poet_public_key
                )
            return False

        # Retrieve the key state corresponding to the PoET public key in our
        # validator registry entry.
        poet_key_state = None
        try:
            poet_key_state = \
                self._poet_key_state_store[
                    validator_info.signup_info.poet_public_key]
        except (ValueError, KeyError):
            pass

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

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

            return False

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

            # Check if we need to give up on this registration attempt
            self._handle_registration_timeout(
                block_header=block_header,
                poet_enclave_module=poet_enclave_module,
                state_view=state_view,
                signup_nonce=poet_key_state.signup_nonce,
                poet_public_key=active_poet_public_key
            )
            return False

        # If the PoET public key in the validator registry is not the active
        # one, then we need to switch the active key in the key state store.
        if validator_info.signup_info.poet_public_key != \
                active_poet_public_key:
            active_poet_public_key = validator_info.signup_info.poet_public_key
            self._poet_key_state_store.active_key = active_poet_public_key

        # Ensure that the enclave is using the appropriate keys
        try:
            unsealed_poet_public_key = \
                SignupInfo.unseal_signup_data(
                    poet_enclave_module=poet_enclave_module,
                    sealed_signup_data=poet_key_state.sealed_signup_data)
        except SystemError:
            # Signup data is unuseable
            LOGGER.error(
                'Could not unseal signup data associated with PPK: %s..%s',
                active_poet_public_key[:8],
                active_poet_public_key[-8:])
            self._poet_key_state_store.active_key = None
            return False

        assert active_poet_public_key == unsealed_poet_public_key

        LOGGER.debug(
            'Using PoET public key: %s...%s',
            active_poet_public_key[:8],
            active_poet_public_key[-8:])
        LOGGER.debug(
            'Unseal signup data: %s...%s',
            poet_key_state.sealed_signup_data[:8],
            poet_key_state.sealed_signup_data[-8:])

        consensus_state = \
            ConsensusState.consensus_state_for_block_id(
                block_id=block_header.previous_block_id,
                block_cache=self._block_cache,
                state_view_factory=self._state_view_factory,
                consensus_state_store=self._consensus_state_store,
                poet_enclave_module=poet_enclave_module)
        poet_settings_view = PoetSettingsView(state_view)

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

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

                sealed_signup_data = poet_key_state.sealed_signup_data
                signup_nonce = poet_key_state.signup_nonce
                self._poet_key_state_store[active_poet_public_key] = \
                    PoetKeyState(
                        sealed_signup_data=sealed_signup_data,
                        has_been_refreshed=True,
                        signup_nonce=signup_nonce)

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

                self._register_signup_information(
                    block_header=block_header,
                    poet_enclave_module=poet_enclave_module)

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

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

        # We need to create a wait timer for the block...this is what we
        # will check when we are asked if it is time to publish the block
        poet_key_state = self._poet_key_state_store[active_poet_public_key]
        sealed_signup_data = poet_key_state.sealed_signup_data
        previous_certificate_id = \
            utils.get_previous_certificate_id(
                block_header=block_header,
                block_cache=self._block_cache,
                poet_enclave_module=poet_enclave_module)
        wait_timer = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=poet_enclave_module,
                sealed_signup_data=sealed_signup_data,
                validator_address=block_header.signer_public_key,
                previous_certificate_id=previous_certificate_id,
                consensus_state=consensus_state,
                poet_settings_view=poet_settings_view)

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

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

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

        self._wait_timer = wait_timer
        PoetBlockPublisher._previous_block_id = None

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

        return True
Example #25
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)

        self._batch_publisher.send([transaction])

        LOGGER.info(
            'Register Validator Name=%s, ID=%s...%s, PoET public key=%s...%s',
            payload.name, payload.id[:8], payload.id[-8:],
            payload.signup_info.poet_public_key[:8],
            payload.signup_info.poet_public_key[-8:])

        # HACER: Once we have the consensus state implemented, we can
        # store this information in there.  For now, we will store in the
        # class so it persists for the lifetime of the validator.
        PoetBlockPublisher._sealed_signup_data = \
            signup_info.sealed_signup_data
        PoetBlockPublisher._poet_public_key = signup_info.poet_public_key
    def test_serialization(self):
        # Need to create signup information and wait timer first
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=self.poet_enclave_module,
                originator_public_key_hash=self._originator_public_key_hash,
                nonce=NULL_BLOCK_IDENTIFIER)

        # create mock_poet_enclave_wait_timer
        mock_poet_enclave_wait_timer = \
            mock.Mock(validator_address='1060 W Addison Street',
                      duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      signature='00112233445566778899aabbccddeeff',
                      serialized_timer=None,
                      request_time=time.time())

        # create mock_poet_enclave_wait_certificate
        mock_poet_enclave_wait_certificate = \
            mock.Mock(duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      request_time=time.time(),
                      validator_address='1060 W Addison Street',
                      nonce=NULL_BLOCK_IDENTIFIER,
                      block_hash="Reader's Digest",
                      signature='00112233445566778899aabbccddeeff',
                      serialized_certificate=None)

        # create mock_poet_enclave_module
        mock_poet_enclave_module = mock.Mock()
        mock_poet_enclave_module.create_wait_timer.return_value = \
            mock_poet_enclave_wait_timer

        # set the mock enclave wait certificate and wait timer to
        # have the same previous_certificate_id
        mock_poet_enclave_wait_certificate.previous_certificate_id = \
            mock_poet_enclave_wait_timer.previous_certificate_id

        mock_poet_enclave_module.create_wait_certificate.return_value = \
            mock_poet_enclave_wait_certificate

        mock_poet_enclave_module.deserialize_wait_certificate.return_value = \
            mock_poet_enclave_wait_certificate

        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=mock_poet_enclave_module,
                sealed_signup_data=signup_info.sealed_signup_data,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)

        # Now we can create a wait certificate and serialize
        wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=mock_poet_enclave_module,
                sealed_signup_data=signup_info.sealed_signup_data,
                wait_timer=wt,
                block_hash="Reader's Digest")

        dumped = wc.dump()

        self.assertIsNotNone(dumped.get('SerializedCertificate'))
        self.assertIsNotNone(dumped.get('Signature'))

        # Deserialize and verify that wait certificates are the same
        # and that deserialized one is valid
        wc_copy = \
            WaitCertificate.wait_certificate_from_serialized(
                poet_enclave_module=mock_poet_enclave_module,
                serialized=dumped.get('SerializedCertificate'),
                signature=dumped.get('Signature'))

        self.assertEqual(
            wc.previous_certificate_id,
            wc_copy.previous_certificate_id)
        self.assertEqual(wc.local_mean, wc_copy.local_mean)
        self.assertAlmostEqual(wc.request_time, wc_copy.request_time)
        self.assertAlmostEqual(wc.duration, wc_copy.duration)
        self.assertEqual(wc.validator_address, wc_copy.validator_address)
        self.assertEqual(wc.block_hash, wc_copy.block_hash)
        self.assertEqual(wc.signature, wc_copy.signature)
        self.assertEqual(wc.identifier, wc_copy.identifier)

        # Serialize the copy and verify that its serialization and
        # signature are the same
        dumped_copy = wc_copy.dump()

        self.assertTrue(
            dumped.get('SerializedCertificate'),
            dumped_copy.get('SerializedCertificate'))
        self.assertTrue(
            dumped.get('Signature'),
            dumped_copy.get('Signature'))

        wc_copy.check_valid(
            poet_enclave_module=mock_poet_enclave_module,
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            poet_public_key=signup_info.poet_public_key,
            consensus_state=self.mock_consensus_state,
            poet_settings_view=self.mock_poet_settings_view)
Example #27
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 PoET wait timer.

        Args:
            block_header (BlockHeader): The BlockHeader to initialize.
        Returns:
            Boolean: True if the candidate block should be built. False if
            no candidate should be built.
        """

        # Using the current chain head, we need to create a state view so we
        # can create a PoET enclave.  We are going to special case this until
        # the genesis consensus is available.  We know that the genesis block
        # is special cased to have a state view constructed for it.
        state_root_hash = \
            self._block_cache.block_store.chain_head.state_root_hash \
            if self._block_cache.block_store.chain_head is not None \
            else block_header.state_root_hash
        state_view = self._state_view_factory.create_view(state_root_hash)

        poet_enclave_module = \
            factory.PoetEnclaveFactory.get_poet_enclave_module(state_view)

        # HACER: Once we have PoET consensus state, we should be looking in
        # there for the sealed signup data.  For the time being, signup
        # information will be tied to the lifetime of the validator.

        # HACER: Once the genesis utility is creating the validator registry
        # transaction, we no longer need to do this.  But for now, if we are
        # creating the genesis block, we need to re-establish the state of
        # the enclave using pre-canned sealed signup data so that we can
        # create the wait timer and certificate.  Note that this is only
        # used for the genesis block.  Going forward after the genesis block,
        # all validators will use generated signup information.
        if utils.block_id_is_genesis(block_header.previous_block_id):
            LOGGER.debug(
                'Creating genesis block, so will use sealed signup data')
            SignupInfo.unseal_signup_data(
                poet_enclave_module=poet_enclave_module,
                validator_address=block_header.signer_pubkey,
                sealed_signup_data=PoetBlockPublisher._sealed_signup_data)

        # HACER: Otherwise, if it is not the first block and we don't already
        # have a public key, we need to create signup information and create a
        # transaction to add it to the validator registry.
        elif PoetBlockPublisher._poet_public_key is None:
            self._register_signup_information(
                block_header=block_header,
                poet_enclave_module=poet_enclave_module)

        # Create a list of certificates for the wait timer.  This seems to have
        # a little too much knowledge of the WaitTimer implementation, but
        # there is no use getting more than
        # WaitTimer.certificate_sample_length wait certificates.
        certificates = \
            utils.build_certificate_list(
                block_header=block_header,
                block_cache=self._block_cache,
                poet_enclave_module=poet_enclave_module,
                maximum_number=WaitTimer.certificate_sample_length)

        # We need to create a wait timer for the block...this is what we
        # will check when we are asked if it is time to publish the block
        self._wait_timer = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=poet_enclave_module,
                validator_address=block_header.signer_pubkey,
                certificates=list(certificates))

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

        return True
    def test_serialization(self):
        # Need to create signup information and wait timer first
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=self.poet_enclave_module,
                originator_public_key_hash=self._originator_public_key_hash,
                nonce=NULL_BLOCK_IDENTIFIER)

        # create mock_poet_enclave_wait_timer
        mock_poet_enclave_wait_timer = \
            mock.Mock(validator_address='1060 W Addison Street',
                      duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      signature='00112233445566778899aabbccddeeff',
                      serialized_timer=None,
                      request_time=time.time())

        # create mock_poet_enclave_wait_certificate
        mock_poet_enclave_wait_certificate = \
            mock.Mock(duration=1.0,
                      previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                      local_mean=5.0,
                      request_time=time.time(),
                      validator_address='1060 W Addison Street',
                      nonce=NULL_BLOCK_IDENTIFIER,
                      block_hash="Reader's Digest",
                      signature='00112233445566778899aabbccddeeff',
                      serialized_certificate=None)

        # create mock_poet_enclave_module
        mock_poet_enclave_module = mock.Mock()
        mock_poet_enclave_module.create_wait_timer.return_value = \
            mock_poet_enclave_wait_timer

        # set the mock enclave wait certificate and wait timer to
        # have the same previous_certificate_id
        mock_poet_enclave_wait_certificate.previous_certificate_id = \
            mock_poet_enclave_wait_timer.previous_certificate_id

        mock_poet_enclave_module.create_wait_certificate.return_value = \
            mock_poet_enclave_wait_certificate

        mock_poet_enclave_module.deserialize_wait_certificate.return_value = \
            mock_poet_enclave_wait_certificate

        wt = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=mock_poet_enclave_module,
                validator_address='1660 Pennsylvania Avenue NW',
                previous_certificate_id=NULL_BLOCK_IDENTIFIER,
                consensus_state=self.mock_consensus_state,
                poet_settings_view=self.mock_poet_settings_view)

        # Now we can create a wait certificate and serialize
        wc = \
            WaitCertificate.create_wait_certificate(
                poet_enclave_module=mock_poet_enclave_module,
                wait_timer=wt,
                block_hash="Reader's Digest")

        dumped = wc.dump()

        self.assertIsNotNone(dumped.get('SerializedCertificate'))
        self.assertIsNotNone(dumped.get('Signature'))

        # Deserialize and verify that wait certificates are the same
        # and that deserialized one is valid
        wc_copy = \
            WaitCertificate.wait_certificate_from_serialized(
                poet_enclave_module=mock_poet_enclave_module,
                serialized=dumped.get('SerializedCertificate'),
                signature=dumped.get('Signature'))

        self.assertEqual(
            wc.previous_certificate_id,
            wc_copy.previous_certificate_id)
        self.assertEqual(wc.local_mean, wc_copy.local_mean)
        self.assertAlmostEqual(wc.request_time, wc_copy.request_time)
        self.assertAlmostEqual(wc.duration, wc_copy.duration)
        self.assertEqual(wc.validator_address, wc_copy.validator_address)
        self.assertEqual(wc.block_hash, wc_copy.block_hash)
        self.assertEqual(wc.signature, wc_copy.signature)
        self.assertEqual(wc.identifier, wc_copy.identifier)

        # Serialize the copy and verify that its serialization and
        # signature are the same
        dumped_copy = wc_copy.dump()

        self.assertTrue(
            dumped.get('SerializedCertificate'),
            dumped_copy.get('SerializedCertificate'))
        self.assertTrue(
            dumped.get('Signature'),
            dumped_copy.get('Signature'))

        wc_copy.check_valid(
            poet_enclave_module=mock_poet_enclave_module,
            previous_certificate_id=NULL_BLOCK_IDENTIFIER,
            poet_public_key=signup_info.poet_public_key,
            consensus_state=self.mock_consensus_state,
            poet_settings_view=self.mock_poet_settings_view)
    def validator_signup_was_committed_too_late(self,
                                                validator_info,
                                                poet_settings_view,
                                                block_cache):
        """Determines if a validator's registry transaction committing it
        current PoET keys, etc., was committed too late - i.e., the number of
        blocks between when the transaction was submitted and when it was
        committed is greater than signup commit maximum delay.

        Args:
            validator_info (ValidatorInfo): The current validator information
            poet_settings_view (PoetSettingsView): The current Poet config view
            block_cache (BlockCache): The block store cache

        Returns:
            bool: True if the validator's registry transaction was committed
                beyond the signup commit maximum delay, False otherwise
        """
        # Figure out the block in which the current validator information
        # was committed.
        try:
            block = block_cache.block_store.get_block_by_transaction_id(
                validator_info.transaction_id)
        except ValueError:
            LOGGER.warning(
                'Validator %s (ID=%s...%s): Signup txn %s not found in block.',
                validator_info.name,
                validator_info.id[:8],
                validator_info.id[-8:],
                validator_info.transaction_id[:8])
            return False

        commit_block_id = block.identifier

        # Starting with that block's immediate predecessor, walk back until
        # either we match the block ID with the nonce field in the signup
        # info, we have checked the maximum number of blocks, or we somehow
        # reached the beginning of the blockchain.  The first case is
        # success (i.e., the validator signup info passed the freshness
        # test) while the other two cases are failure.
        for _ in range(poet_settings_view.signup_commit_maximum_delay + 1):
            if SignupInfo.block_id_to_nonce(block.previous_block_id) == \
                    validator_info.signup_info.nonce:
                LOGGER.debug(
                    'Validator %s (ID=%s...%s): Signup committed block %s, '
                    'chain head was block %s',
                    validator_info.name,
                    validator_info.id[:8],
                    validator_info.id[-8:],
                    commit_block_id[:8],
                    block.previous_block_id[:8])
                return False

            if utils.block_id_is_genesis(block.previous_block_id):
                LOGGER.info(
                    'Validator %s (ID=%s...%s): Signup committed block %s, '
                    'hit start of blockchain looking for block %s',
                    validator_info.name,
                    validator_info.id[:8],
                    validator_info.id[-8:],
                    commit_block_id[:8],
                    validator_info.signup_info.nonce[:8])
                return True

            block = block_cache[block.previous_block_id]

        LOGGER.info(
            'Validator %s (ID=%s...%s): Signup committed block %s, failed to '
            'find block with ID ending in %s in %d previous block(s)',
            validator_info.name,
            validator_info.id[:8],
            validator_info.id[-8:],
            commit_block_id[:8],
            validator_info.signup_info.nonce,
            poet_settings_view.signup_commit_maximum_delay + 1)
        return True
Example #30
0
    def _register_signup_information(self, block_header, poet_enclave_module):
        # Create signup information for this validator, putting the block ID
        # of the block previous to the block referenced by block_header in the
        # nonce.  Block ID is better than wait certificate ID for testing
        # freshness as we need to account for non-PoET blocks.
        public_key_hash = \
            hashlib.sha256(
                block_header.signer_public_key.encode()).hexdigest()
        nonce = SignupInfo.block_id_to_nonce(block_header.previous_block_id)
        signup_info = \
            SignupInfo.create_signup_info(
                poet_enclave_module=poet_enclave_module,
                originator_public_key_hash=public_key_hash,
                nonce=nonce)

        # Create the validator registry payload
        payload = \
            vr_pb.ValidatorRegistryPayload(
                verb='register',
                name='validator-{}'.format(block_header.signer_public_key[:8]),
                id=block_header.signer_public_key,
                signup_info=vr_pb.SignUpInfo(
                    poet_public_key=signup_info.poet_public_key,
                    proof_data=signup_info.proof_data,
                    anti_sybil_id=signup_info.anti_sybil_id,
                    nonce=nonce),
            )
        serialized = payload.SerializeToString()

        # Create the address that will be used to look up this validator
        # registry transaction.  Seems like a potential for refactoring..
        validator_entry_address = \
            PoetBlockPublisher._validator_registry_namespace + \
            hashlib.sha256(block_header.signer_public_key.encode()).hexdigest()

        # Create a transaction header and transaction for the validator
        # registry update amd then hand it off to the batch publisher to
        # send out.
        output_addresses = \
            [validator_entry_address,
             PoetBlockPublisher._validator_map_address]
        input_addresses = \
            output_addresses + \
            [SettingsView.setting_address(
                'sawtooth.poet.report_public_key_pem'),
             SettingsView.setting_address(
                 'sawtooth.poet.valid_enclave_measurements'),
             SettingsView.setting_address(
                 'sawtooth.poet.valid_enclave_basenames')]

        header = \
            txn_pb.TransactionHeader(
                signer_public_key=block_header.signer_public_key,
                family_name='sawtooth_validator_registry',
                family_version='1.0',
                inputs=input_addresses,
                outputs=output_addresses,
                dependencies=[],
                payload_sha512=hashlib.sha512(serialized).hexdigest(),
                batcher_public_key=block_header.signer_public_key,
                nonce=time.time().hex().encode()).SerializeToString()

        signature = self._batch_publisher.identity_signer.sign(header)

        transaction = \
            txn_pb.Transaction(
                header=header,
                payload=serialized,
                header_signature=signature)

        LOGGER.info(
            'Register Validator Name=%s, ID=%s...%s, PoET public key=%s...%s, '
            'Nonce=%s', payload.name, payload.id[:8], payload.id[-8:],
            payload.signup_info.poet_public_key[:8],
            payload.signup_info.poet_public_key[-8:], nonce)

        self._batch_publisher.send([transaction])

        # Store the key state so that we can look it up later if need be and
        # set the new key as our active key
        LOGGER.info('Save key state PPK=%s...%s => SSD=%s...%s',
                    signup_info.poet_public_key[:8],
                    signup_info.poet_public_key[-8:],
                    signup_info.sealed_signup_data[:8],
                    signup_info.sealed_signup_data[-8:])
        self._poet_key_state_store[signup_info.poet_public_key] = \
            PoetKeyState(
                sealed_signup_data=signup_info.sealed_signup_data,
                has_been_refreshed=False,
                signup_nonce=nonce)
        self._poet_key_state_store.active_key = signup_info.poet_public_key
Example #31
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 PoET wait timer.

        Args:
            block_header (BlockHeader): The BlockHeader to initialize.
        Returns:
            Boolean: True if the candidate block should be built. False if
            no candidate should be built.
        """
        # If the previous block ID matches our cached one, that means that we
        # have already determined that even if we initialize the requested
        # block we would not be able to claim it.  So, instead of wasting time
        # doing all of the checking again, simply short-circuit the failure so
        # that the validator can go do something more useful.
        if block_header.previous_block_id == \
                PoetBlockPublisher._previous_block_id:
            return False
        PoetBlockPublisher._previous_block_id = block_header.previous_block_id

        # Using the current chain head, we need to create a state view so we
        # can create a PoET enclave.
        state_view = \
            BlockWrapper.state_view_for_block(
                block_wrapper=self._block_cache.block_store.chain_head,
                state_view_factory=self._state_view_factory)

        poet_enclave_module = \
            factory.PoetEnclaveFactory.get_poet_enclave_module(
                state_view=state_view,
                config_dir=self._config_dir,
                data_dir=self._data_dir)

        # Get our validator registry entry to see what PoET public key
        # other validators think we are using.
        validator_registry_view = ValidatorRegistryView(state_view)
        validator_info = None

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

        # If we don't have a validator registry entry, then check the active
        # key.  If we don't have one, then we need to sign up.  If we do have
        # one, then our validator registry entry has not percolated through the
        # system, so nothing to to but wait.
        active_poet_public_key = self._poet_key_state_store.active_key
        if validator_info is None:
            if active_poet_public_key is None:
                LOGGER.debug(
                    'No public key found, so going to register new signup '
                    'information')
                self._register_signup_information(
                    block_header=block_header,
                    poet_enclave_module=poet_enclave_module)
            else:  # Check if we need to give up on this registration attempt
                try:
                    nonce = self._poet_key_state_store[
                        active_poet_public_key].signup_nonce
                except (ValueError, AttributeError):
                    self._poet_key_state_store.active_key = None
                    LOGGER.warning('Poet Key State Store had inaccessible or '
                                   'corrupt active key [%s] clearing '
                                   'key.', active_poet_public_key)
                    return False

                self._handle_registration_timeout(
                    block_header=block_header,
                    poet_enclave_module=poet_enclave_module,
                    state_view=state_view,
                    signup_nonce=nonce,
                    poet_public_key=active_poet_public_key
                )
            return False

        # Retrieve the key state corresponding to the PoET public key in our
        # validator registry entry.
        poet_key_state = None
        try:
            poet_key_state = \
                self._poet_key_state_store[
                    validator_info.signup_info.poet_public_key]
        except (ValueError, KeyError):
            pass

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

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

            return False

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

            # Check if we need to give up on this registration attempt
            self._handle_registration_timeout(
                block_header=block_header,
                poet_enclave_module=poet_enclave_module,
                state_view=state_view,
                signup_nonce=poet_key_state.signup_nonce,
                poet_public_key=active_poet_public_key
            )
            return False

        # If the PoET public key in the validator registry is not the active
        # one, then we need to switch the active key in the key state store.
        if validator_info.signup_info.poet_public_key != \
                active_poet_public_key:
            active_poet_public_key = validator_info.signup_info.poet_public_key
            self._poet_key_state_store.active_key = active_poet_public_key

        # Ensure that the enclave is using the appropriate keys
        try:
            unsealed_poet_public_key = \
                SignupInfo.unseal_signup_data(
                    poet_enclave_module=poet_enclave_module,
                    sealed_signup_data=poet_key_state.sealed_signup_data)
        except SystemError:
            # Signup data is unuseable
            LOGGER.error(
                'Could not unseal signup data associated with PPK: %s..%s',
                active_poet_public_key[:8],
                active_poet_public_key[-8:])
            self._poet_key_state_store.active_key = None
            return False

        assert active_poet_public_key == unsealed_poet_public_key

        LOGGER.debug(
            'Using PoET public key: %s...%s',
            active_poet_public_key[:8],
            active_poet_public_key[-8:])
        LOGGER.debug(
            'Unseal signup data: %s...%s',
            poet_key_state.sealed_signup_data[:8],
            poet_key_state.sealed_signup_data[-8:])

        consensus_state = \
            ConsensusState.consensus_state_for_block_id(
                block_id=block_header.previous_block_id,
                block_cache=self._block_cache,
                state_view_factory=self._state_view_factory,
                consensus_state_store=self._consensus_state_store,
                poet_enclave_module=poet_enclave_module)
        poet_settings_view = PoetSettingsView(state_view)

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

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

                sealed_signup_data = poet_key_state.sealed_signup_data
                signup_nonce = poet_key_state.signup_nonce
                self._poet_key_state_store[active_poet_public_key] = \
                    PoetKeyState(
                        sealed_signup_data=sealed_signup_data,
                        has_been_refreshed=True,
                        signup_nonce=signup_nonce)

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

                self._register_signup_information(
                    block_header=block_header,
                    poet_enclave_module=poet_enclave_module)

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

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

        # We need to create a wait timer for the block...this is what we
        # will check when we are asked if it is time to publish the block
        poet_key_state = self._poet_key_state_store[active_poet_public_key]
        sealed_signup_data = poet_key_state.sealed_signup_data
        previous_certificate_id = \
            utils.get_previous_certificate_id(
                block_header=block_header,
                block_cache=self._block_cache,
                poet_enclave_module=poet_enclave_module)
        wait_timer = \
            WaitTimer.create_wait_timer(
                poet_enclave_module=poet_enclave_module,
                sealed_signup_data=sealed_signup_data,
                validator_address=block_header.signer_public_key,
                previous_certificate_id=previous_certificate_id,
                consensus_state=consensus_state,
                poet_settings_view=poet_settings_view)

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

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

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

        self._wait_timer = wait_timer
        PoetBlockPublisher._previous_block_id = None

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

        return True