def test_block_cache(self): block_store = {} cache = BlockCache(block_store=block_store, keep_time=1, purge_frequency=1) header1 = BlockHeader(previous_block_id="000") block1 = BlockWrapper(Block(header=header1.SerializeToString(), header_signature="ABC")) header2 = BlockHeader(previous_block_id="ABC") block2 = BlockWrapper(Block(header=header2.SerializeToString(), header_signature="DEF")) header3 = BlockHeader(previous_block_id="BCA") block3 = BlockWrapper(Block(header=header3.SerializeToString(), header_signature="FED")) cache[block1.header_signature] = block1 cache[block2.header_signature] = block2 # Check that blocks are in the BlockCache self.assertIn("ABC", cache) self.assertIn("DEF", cache) # Wait for purge time to expire time.sleep(1) # Add "FED" cache[block3.header_signature] = block3 # Check that "ABC" is still in the cache even though the keep time has # expired because it has a referecne count of 1 but "DEF" has been # removed self.assertIn("ABC", cache) self.assertNotIn("DEF", cache) self.assertIn("FED", cache)
def _generate_genesis_block(): genesis_header = BlockHeader( previous_block_id="0000000000000000", block_num=0) block = BlockWrapper(genesis_header) block.set_signature("genesis") return block
def _generate_genesis_block(self, header_sig="genesis"): genesis_header = BlockHeader(previous_block_id="0000000000000000", block_num=0) # Small hack here not asking consensus if it is happy. block = BlockWrapper(genesis_header) block.set_signature(header_sig) return block
def generate_genesis_block(self): genesis_header = BlockHeader(previous_block_id=NULLIDENTIFIER, block_num=0) # Small hack here not asking consensus if it is happy. block = BlockWrapper(genesis_header) block.set_signature("genesis") self._candidate_block = \ self._finalize_block(block) return self._candidate_block
def do_load_from_block_store(): bs = {} block1 = Block( header=BlockHeader(previous_block_id="000").SerializeToString(), header_signature="test") bs["test"] = BlockWrapper(block1) block2 = Block( header=BlockHeader(previous_block_id="000").SerializeToString(), header_signature="test2") blkw2 = BlockWrapper(block2) bs["test2"] = blkw2 bc = BlockCache(bs) return bc, blkw2
def generate_block(self, previous_block=None, add_to_store=False, add_to_cache=False, batch_count=0, status=BlockStatus.Unknown, invalid_consensus=False, invalid_batch=False, invalid_signature=False, weight=0): previous = self._get_block(previous_block) if previous is None: previous = self.chain_head self.block_publisher.on_chain_updated(previous) while self.block_sender.new_block is None: self.block_publisher.on_batch_received( self._generate_batch_from_payload('')) self.block_publisher.on_check_publish_block(True) block_from_sender = self.block_sender.new_block self.block_sender.new_block = None block_wrapper = BlockWrapper(block_from_sender) if invalid_signature: block_wrapper.block.header_signature = "BAD" if invalid_consensus: block_wrapper.header.consensus = b'BAD' if invalid_batch: block_wrapper.block.batches.extend( [self._generate_batch_from_payload('BAD')]) block_wrapper.weight = weight block_wrapper.status = status if add_to_cache: self.block_cache[block_wrapper.identifier] = block_wrapper if add_to_store: self.block_store[block_wrapper.identifier] = block_wrapper LOGGER.debug("Generated %s", dumps_block(block_wrapper)) return block_wrapper
def on_block_received(self, block): with self._lock: if block.header_signature in self._block_store: # do we already have this block return header = BlockHeader() header.ParseFromString(block.header) block = BlockWrapper(header, block) block_state = BlockState(block_wrapper=block, weight=0, status=BlockStatus.Unknown) self._block_store[block.header_signature] = block_state self._blocks_pending[block.header_signature] = [] if block.header_signature in self._blocks_requested: # is it a requested block # route block to the validator that requested validator = self._blocks_requested.pop(block.header_signature) if validator.chain_head.block.header_signature != \ self._chain_head.block.header_signature: # the head of the chain has changed start over self._verify_block(validator.new_block) else: self._executor.submit(validator.run) elif block.previous_block_id in self._blocks_processing: # if the previous block is being processed...put it in a wait # queue pending_blocks = \ self._blocks_pending.get(block.previous_block_id, []) pending_blocks.append(block_state) self._blocks_pending[block.previous_block_id] = \ pending_blocks else: # schedule this block for validation. self._verify_block(block_state)
def _submit_blocks_for_verification(self, blocks): for blkw in blocks: state_view = BlockWrapper.state_view_for_block( self.chain_head, self._state_view_factory) consensus_module = \ ConsensusFactory.get_configured_consensus_module( self.chain_head.header_signature, state_view) validator = BlockValidator( consensus_module=consensus_module, new_block=blkw, block_cache=self._block_cache, state_view_factory=self._state_view_factory, done_cb=self.on_block_validated, executor=self._transaction_executor, squash_handler=self._squash_handler, identity_signer=self._identity_signer, data_dir=self._data_dir, config_dir=self._config_dir, permission_verifier=self._permission_verifier, metrics_registry=self._metrics_registry) self._blocks_processing[blkw.block.header_signature] = validator self._thread_pool.submit(validator.run)
def initialize_block(self, block_header): """Do initialization necessary for the consensus to claim a block, this may include initiating voting activates, starting proof of work hash generation, or create a PoET wait timer. Args: block_header (BlockHeader): the BlockHeader to initialize. Returns: True """ # Using the current chain head, we need to create a state view so we # can get our config values. state_view = \ BlockWrapper.state_view_for_block( self._block_cache.block_store.chain_head, self._state_view_factory) settings_view = SettingsView(state_view) self._difficulty = settings_view.get_setting( "sawtooth.truss.difficulty", self._difficulty, int) block_header.consensus = b"Truss" self._nonce = random.uniform( self._min_wait_time, self._max_wait_time) return True
def gen_block(self, block_id, prev_id, num, batches): return BlockWrapper( Block(header_signature=block_id, batches=batches, header=BlockHeader( block_num=num, previous_block_id=prev_id).SerializeToString()))
def test_max_block_size(self): ''' Test block publisher obeys the block size limits ''' # Create a publisher that has a state view # with a batch limit addr, value = CreateSetting( 'sawtooth.publisher.max_batches_per_block', 1) print('test', addr) self.state_view_factory = MockStateViewFactory( {addr: value}) self.publisher = BlockPublisher( transaction_executor=MockTransactionExecutor(), block_cache=self.block_tree_manager.block_cache, state_view_factory=self.state_view_factory, block_sender=self.block_sender, batch_sender=self.batch_sender, squash_handler=None, chain_head=self.block_tree_manager.chain_head, identity_signing_key=self.block_tree_manager.identity_signing_key, data_dir=None, config_dir=None) self.assert_no_block_published() # receive batches, then try again (succeeding) self.receive_batches() # try to publish with no pending queue (failing) for i in range(self.batch_count): self.publish_block() self.assert_block_published() self.update_chain_head(BlockWrapper(self.result_block)) self.verify_block([self.batches[i]])
def _create_block(self): return BlockWrapper.wrap( Block(header_signature='some_block_id', batches=[], header=BlockHeader(block_num=0, previous_block_id=NULL_BLOCK_IDENTIFIER). SerializeToString()))
def __getitem__(self, key): stored_block = self._block_store[key] if stored_block is not None: block = Block() block.ParseFromString(stored_block) return BlockWrapper(status=BlockStatus.Valid, block=block) raise KeyError("Key {} not found.".format(key))
def _transaction_index_keys(block): blkw = BlockWrapper.wrap(block) keys = [] for batch in blkw.batches: for txn in batch.transactions: keys.append(txn.header_signature.encode()) return keys
def forks(self, head): (vec_ptr, vec_len, vec_cap) = ffi.prepare_vec_result(pointer_type=_BlockPayload) head = ctypes.c_char_p(head.encode()) _libexec('chain_controller_forks', self.pointer, head, ctypes.byref(vec_ptr), ctypes.byref(vec_len), ctypes.byref(vec_cap)) # Check if NULL if not vec_ptr: return None blocks = [] for i in range(vec_len.value): block_payload = vec_ptr[i] payload = ffi.from_rust_vec( block_payload.block_ptr, ctypes.c_size_t(block_payload.block_len), ctypes.c_size_t(block_payload.block_cap), ) block = Block() block.ParseFromString(payload) blocks.append(BlockWrapper(block)) LIBRARY.call("chain_controller_reclaim_block_payload_vec", vec_ptr, vec_len, vec_cap) return blocks
def initialize_block(self, block_header): """Do initialization necessary for the consensus to claim a block, this may include initiating voting activates, starting proof of work hash generation, or create a PoET wait timer. Args: block_header (BlockHeader): the BlockHeader to initialize. Returns: True """ # Using the current chain head, we need to create a state view so we # can get our config values. state_view = \ BlockWrapper.state_view_for_block( self._block_cache.block_store.chain_head, self._state_view_factory) config_view = ConfigView(state_view) self._min_wait_time = config_view.get_setting( "sawtooth.consensus.min_wait_time", self._min_wait_time, int) self._max_wait_time = config_view.get_setting( "sawtooth.consensus.max_wait_time", self._max_wait_time, int) self._valid_block_publishers = config_view.get_setting( "sawtooth.consensus.valid_block_publishers", self._valid_block_publishers, list) block_header.consensus = b"Devmode" self._start_time = time.time() self._wait_time = random.uniform(self._min_wait_time, self._max_wait_time) return True
def initialize_block(self, block_header): """Do initialization necessary for the consensus to claim a block, this may include initiating voting activates, starting proof of work hash generation, or create a PoET wait timer. Args: block_header (BlockHeader): the BlockHeader to initialize. Returns: True """ # Using the current chain head, we need to create a state view so we # can get our config values. state_view = \ BlockWrapper.state_view_for_block( self._block_cache.block_store.chain_head, self._state_view_factory) settings_view = SettingsView(state_view) self._min_wait_time = settings_view.get_setting( "sawtooth.consensus.min_wait_time", self._min_wait_time, int) self._max_wait_time = settings_view.get_setting( "sawtooth.consensus.max_wait_time", self._max_wait_time, int) self._valid_block_publishers = settings_view.get_setting( "sawtooth.consensus.valid_block_publishers", self._valid_block_publishers, list) block_header.consensus = b"Devmode" self._start_time = time.time() self._wait_time = random.uniform( self._min_wait_time, self._max_wait_time) return True
def create_block(): block_header = block_pb2.BlockHeader(block_num=85, state_root_hash="0987654321fedcba", previous_block_id="0000000000000000") block = BlockWrapper( block_pb2.Block(header_signature="abcdef1234567890", header=block_header.SerializeToString())) return block
def add_block(self, block): with self.lock: blkw = BlockWrapper(block) block = self._complete_block(blkw) if block is not None: self.block_cache[block.header_signature] = blkw self._on_block_received(blkw) self._process_incomplete_blocks(block.header_signature)
def _load_consensus(self, block): """Load the consensus module using the state as of the given block.""" if block is not None: return ConsensusFactory.get_configured_consensus_module( block.header_signature, BlockWrapper.state_view_for_block(block, self._state_view_factory)) return ConsensusFactory.get_consensus_module('genesis')
def _create_block(self): return BlockWrapper.wrap( Block( header_signature='some_block_id', batches=[], header=BlockHeader( block_num=0, previous_block_id=NULL_BLOCK_IDENTIFIER) .SerializeToString()))
def __init__(self, block_store, block_manager, transaction_executor, state_view_factory, block_sender, batch_sender, identity_signer, data_dir, config_dir, permission_verifier, batch_observers, batch_injector_factory=None): """ Initialize the BlockPublisher object Args: block_store (:obj: `BlockStore`): A BlockStore instance block_manager (:obj:`BlockManager`): A BlockManager instance transaction_executor (:obj:`TransactionExecutor`): A TransactionExecutor instance. state_view_factory (:obj:`NativeStateViewFactory`): NativeStateViewFactory for read-only state views. block_sender (:obj:`BlockSender`): The BlockSender instance. batch_sender (:obj:`BatchSender`): The BatchSender instance. chain_head_lock (:obj:`RLock`): The chain head lock. identity_signer (:obj:`Signer`): Cryptographic signer for signing blocks data_dir (str): path to location where persistent data for the consensus module can be stored. config_dir (str): path to location where configuration can be found. batch_injector_factory (:obj:`BatchInjectorFatctory`): A factory for creating BatchInjectors. """ super(BlockPublisher, self).__init__('block_publisher_drop') if block_store.chain_head is not None: chain_head = BlockWrapper.wrap(block_store.chain_head) chain_head_block = chain_head.block else: chain_head_block = None self._to_exception( PY_LIBRARY.call('block_publisher_new', block_store.pointer, block_manager.pointer, ctypes.py_object(transaction_executor), state_view_factory.pointer, ctypes.py_object(block_sender), ctypes.py_object(batch_sender), ctypes.py_object(chain_head_block), ctypes.py_object(identity_signer), ctypes.py_object(data_dir), ctypes.py_object(config_dir), ctypes.py_object(permission_verifier), ctypes.py_object(batch_observers), ctypes.py_object(batch_injector_factory), ctypes.byref(self.pointer)))
def generate_block(self, previous_block=None, add_to_store=False, add_to_cache=False, batch_count=0, status=BlockStatus.Unknown, invalid_consensus=False, invalid_batch=False, invalid_signature=False, weight=None): previous = self._get_block(previous_block) if previous is None: previous = self.chain_head self.block_publisher.on_chain_updated(previous) while self.block_sender.new_block is None: self.block_publisher.on_batch_received(Batch()) self.block_publisher.on_check_publish_block(True) block = self.block_sender.new_block self.block_sender.new_block = None block = BlockWrapper(block) if invalid_signature: block.block.header_signature = "BAD" block.weight = weight block.status = status if add_to_cache: self.block_cache[block.identifier] = block if add_to_store: if block.weight is None: state_view = self.state_view_factory.create_view(None) tmv = mock_consensus.\ BlockVerifier(block_cache=self.block_cache, state_view=state_view) block.weight = tmv.compute_block_weight(block) self.block_store[block.identifier] = block return block
def _load_consensus(self, block): """Load the consensus module using the state as of the given block.""" if block is not None: return ConsensusFactory.get_configured_consensus_module( block.header_signature, BlockWrapper.state_view_for_block( block, self._state_view_factory)) return ConsensusFactory.get_consensus_module('genesis')
def add_block(self, block): with self.lock: blkw = BlockWrapper(block) block = self._complete_block(blkw) if block is not None: self._send_block(block.block) self._process_incomplete_blocks(block.header_signature) self._incomplete_blocks_length.set_value( len(self._incomplete_blocks))
def on_check_publish_block(self, force=False): """Ask the consensus module if it is time to claim the candidate block if it is then, claim it and tell the world about it. :return: None """ try: with self._lock: if (self._chain_head is not None and self._candidate_block is None and self._pending_batches): self._build_candidate_block(self._chain_head) if (self._candidate_block and ( force or self._candidate_block.has_pending_batches()) and self._candidate_block.check_publish_block()): pending_batches = [] # will receive the list of batches # that were not added to the block injected_batch_ids = \ self._candidate_block.injected_batch_ids last_batch = self._candidate_block.last_batch block = self._candidate_block.finalize_block( self._identity_signer, pending_batches) self._candidate_block = None # Update the _pending_batches to reflect what we learned. last_batch_index = self._pending_batches.index(last_batch) unsent_batches = \ self._pending_batches[last_batch_index + 1:] self._pending_batches = pending_batches + unsent_batches self._pending_batch_gauge.set_value( len(self._pending_batches)) if block: blkw = BlockWrapper(block) LOGGER.info("Claimed Block: %s", blkw) self._block_sender.send( blkw.block, keep_batches=injected_batch_ids) self._blocks_published_count.inc() # We built our candidate, disable processing until # the chain head is updated. Only set this if # we succeeded. Otherwise try again, this # can happen in cases where txn dependencies # did not validate when building the block. self.on_chain_updated(None) # pylint: disable=broad-except except Exception as exc: LOGGER.critical("on_check_publish_block exception.") LOGGER.exception(exc)
def generate_block(self, previous_block=None, add_to_store=False, add_to_cache=False, batch_count=0, status=BlockStatus.Unknown, invalid_consensus=False, invalid_batch=False, invalid_signature=False, weight=1): previous = self._get_block(previous_block) if previous is None: previous = self._generate_genesis_block(_generate_id()) previous.set_signature(_generate_id()) previous_block = BlockWrapper( block=previous.build_block(), weight=0, status=BlockStatus.Valid) self.block_cache[previous_block.identifier] = previous_block self.block_publisher.on_chain_updated(previous_block) while self.block_sender.new_block is None: self.block_publisher.on_batch_received(Batch()) self.block_publisher.on_check_publish_block(True) block = self.block_sender.new_block self.block_sender.new_block = None block = BlockWrapper(block) if invalid_signature: block.block.header_signature = "BAD" block.weight = 0 block.status = status if add_to_cache: self.block_cache[block.identifier] = block if add_to_store: self.block_store[block.identifier] = block return block
def _generate_genesis_block(): """ Returns a blocker wrapper with the basics of the block header in place """ genesis_header = block_pb2.BlockHeader( previous_block_id=NULLIDENTIFIER, block_num=0) block = BlockWrapper(genesis_header) return block
def _build_candidate_block(self, chain_head): """ Build a candidate block and construct the consensus object to validate it. :param chain_head: The block to build on top of. :return: (BlockBuilder) - The candidate block in a BlockBuilder wrapper. """ state_view = BlockWrapper.state_view_for_block( chain_head, self._state_view_factory) consensus_module = ConsensusFactory.get_configured_consensus_module( chain_head.header_signature, state_view) settings_view = SettingsView(state_view) max_batches = settings_view.get_setting( 'sawtooth.publisher.max_batches_per_block', default_value=0, value_type=int) consensus = consensus_module.\ BlockPublisher(block_cache=self._block_cache, state_view_factory=self._state_view_factory, batch_publisher=self._batch_publisher, data_dir=self._data_dir, config_dir=self._config_dir, validator_id=self._identity_public_key) block_header = BlockHeader( block_num=chain_head.block_num + 1, previous_block_id=chain_head.header_signature, signer_pubkey=self._identity_public_key) block_builder = BlockBuilder(block_header) if not consensus.initialize_block(block_builder.block_header): LOGGER.debug("Consensus not ready to build candidate block.") return None # create a new scheduler scheduler = self._transaction_executor.create_scheduler( self._squash_handler, chain_head.state_root_hash) # build the TransactionCache committed_txn_cache = TransactionCache(self._block_cache.block_store) self._transaction_executor.execute(scheduler) self._candidate_block = _CandidateBlock(self._block_cache.block_store, consensus, scheduler, committed_txn_cache, block_builder, max_batches) for batch in self._pending_batches: if self._candidate_block.can_add_batch: self._candidate_block.add_batch(batch) else: break
def chain_update(self, block, receipts): """Removes batches from the pending cache if found in the block store, and notifies any observers. """ block = BlockWrapper.wrap(block) with self._lock: for batch_id in block.header.batch_ids: if batch_id in self._pending: self._pending.remove(batch_id) self._update_observers(batch_id, ClientBatchStatus.COMMITTED)
def _generate_genesis_block(self): genesis_header = BlockHeader(previous_block_id=NULL_BLOCK_IDENTIFIER, signer_pubkey=self.public_key, block_num=0) block = BlockBuilder(genesis_header) block.add_batches([self._generate_batch("Genesis")]) header_bytes = block.block_header.SerializeToString() signature = signing.sign(header_bytes, self.identity_signing_key) block.set_signature(signature) return BlockWrapper(block.build_block())
def initialize_block(self, previous_block): """Begin building a new candidate block. Args: previous_block (BlockWrapper): The block to base the new block on. Raises: ConsensusNotReady Consensus is not ready to build a block """ # using previous_block so so we can use the setting_cache max_batches = int( self._settings_cache.get_setting( 'sawtooth.publisher.max_batches_per_block', previous_block.state_root_hash, default_value=0)) state_view = BlockWrapper.state_view_for_block( previous_block, self._state_view_factory) public_key = self._identity_signer.get_public_key().as_hex() consensus = self._load_consensus(previous_block, state_view, public_key) batch_injectors = self._load_injectors(previous_block) block_header = BlockHeader( block_num=previous_block.block_num + 1, previous_block_id=previous_block.header_signature, signer_public_key=public_key) block_builder = BlockBuilder(block_header) if not consensus.initialize_block(block_builder.block_header): raise ConsensusNotReady() # create a new scheduler scheduler = self._transaction_executor.create_scheduler( previous_block.state_root_hash) # build the TransactionCommitCache committed_txn_cache = TransactionCommitCache( self._block_cache.block_store) self._transaction_executor.execute(scheduler) self._candidate_block = _CandidateBlock( self._block_cache.block_store, consensus, scheduler, committed_txn_cache, block_builder, max_batches, batch_injectors, SettingsView(state_view), self._identity_signer) for batch in self._pending_batches: if self._candidate_block.can_add_batch(): self._candidate_block.add_batch(batch) else: break
def _build_candidate_block(self, chain_head): """ Build a candidate block and construct the consensus object to validate it. :param chain_head: The block to build on top of. :return: (BlockBuilder) - The candidate block in a BlockBuilder wrapper. """ state_view = BlockWrapper.state_view_for_block( chain_head, self._state_view_factory) consensus_module = ConsensusFactory.get_configured_consensus_module( chain_head.header_signature, state_view) config_view = ConfigView(state_view) max_batches = config_view.get_setting( 'sawtooth.publisher.max_batches_per_block', default_value=0, value_type=int) consensus = consensus_module.\ BlockPublisher(block_cache=self._block_cache, state_view_factory=self._state_view_factory, batch_publisher=self._batch_publisher, data_dir=self._data_dir, config_dir=self._config_dir, validator_id=self._identity_public_key) block_header = BlockHeader( block_num=chain_head.block_num + 1, previous_block_id=chain_head.header_signature, signer_pubkey=self._identity_public_key) block_builder = BlockBuilder(block_header) if not consensus.initialize_block(block_builder.block_header): LOGGER.debug("Consensus not ready to build candidate block.") return None # create a new scheduler scheduler = self._transaction_executor.create_scheduler( self._squash_handler, chain_head.state_root_hash) # build the TransactionCache committed_txn_cache = TransactionCache(self._block_cache.block_store) self._transaction_executor.execute(scheduler) self._candidate_block = _CandidateBlock(self._block_cache.block_store, consensus, scheduler, committed_txn_cache, block_builder, max_batches) for batch in self._pending_batches: if self._candidate_block.can_add_batch: self._candidate_block.add_batch(batch) else: break
def publish_block(self, block, injected_batches): blkw = BlockWrapper(block) LOGGER.info("Claimed Block: %s", blkw) self._block_sender.send(blkw.block, keep_batches=injected_batches) self._blocks_published_count.inc() # We built our candidate, disable processing until the chain head is # updated. Only set this if we succeeded. Otherwise try again, this can # happen in cases where txn dependencies did not validate when building # the block. self.on_chain_updated(None)
def _get_block(self, block): if block is None: return None elif isinstance(block, Block): return BlockWrapper(block) elif isinstance(block, BlockWrapper): return block elif isinstance(block, str): return self.block_cache[block] else: # WTF try something crazy return self.block_cache[str(block)]
def finalize_block(self, block_header): """Finalize a block to be claimed. Provide any signatures and data updates that need to be applied to the block before it is signed and broadcast to the network. Args: block_header (BlockHeader): The block header for the candidate block that needs to be finalized. Returns: Boolean: True if the candidate block good and should be generated. False if the block should be abandoned. """ # To compute the block hash, we are going to perform a hash using the # previous block ID and the batch IDs contained in the block hasher = hashlib.sha256(block_header.previous_block_id.encode()) for batch_id in block_header.batch_ids: hasher.update(batch_id.encode()) block_hash = hasher.hexdigest() # 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) # We need to create a wait certificate for the block and then serialize # that into the block header consensus field. active_key = self._poet_key_state_store.active_key poet_key_state = self._poet_key_state_store[active_key] sealed_signup_data = poet_key_state.sealed_signup_data try: wait_certificate = \ WaitCertificate.create_wait_certificate( poet_enclave_module=poet_enclave_module, sealed_signup_data=sealed_signup_data, wait_timer=self._wait_timer, block_hash=block_hash) block_header.consensus = \ json.dumps(wait_certificate.dump()).encode() except ValueError as ve: LOGGER.error('Failed to create wait certificate: %s', ve) return False LOGGER.debug('Created wait certificate: %s', wait_certificate) return True
def _submit_blocks_for_verification(self, blocks): for blkw in blocks: state_view = BlockWrapper.state_view_for_block( self.chain_head, self._state_view_factory) consensus_module = \ ConsensusFactory.get_configured_consensus_module( self.chain_head.header_signature, state_view) validator = BlockValidator( consensus_module=consensus_module, new_block=blkw, chain_head=self._chain_head, block_cache=self._block_cache, state_view_factory=self._state_view_factory, done_cb=self.on_block_validated, executor=self._transaction_executor, squash_handler=self._squash_handler, identity_signing_key=self._identity_signing_key, data_dir=self._data_dir, config_dir=self._config_dir) self._blocks_processing[blkw.block.header_signature] = validator self._executor.submit(validator.run)
def __init__(self, block_manager, transaction_executor, batch_committed, transaction_committed, state_view_factory, block_sender, batch_sender, chain_head, identity_signer, data_dir, config_dir, permission_verifier, batch_observers, batch_injector_factory=None): """ Initialize the BlockPublisher object Args: block_manager (:obj:`BlockManager`): A BlockManager instance transaction_executor (:obj:`TransactionExecutor`): A TransactionExecutor instance. batch_committed (fn(batch_id) -> bool): A function for checking if a batch is committed. transaction_committed (fn(transaction_id) -> bool): A function for checking if a transaction is committed. state_view_factory (:obj:`NativeStateViewFactory`): NativeStateViewFactory for read-only state views. block_sender (:obj:`BlockSender`): The BlockSender instance. batch_sender (:obj:`BatchSender`): The BatchSender instance. chain_head (:obj:`BlockWrapper`): The initial chain head. chain_head_lock (:obj:`RLock`): The chain head lock. identity_signer (:obj:`Signer`): Cryptographic signer for signing blocks data_dir (str): path to location where persistent data for the consensus module can be stored. config_dir (str): path to location where configuration can be found. batch_injector_factory (:obj:`BatchInjectorFatctory`): A factory for creating BatchInjectors. """ super(BlockPublisher, self).__init__('block_publisher_drop') if chain_head is not None: chain_head = BlockWrapper.wrap(chain_head) chain_head_block = chain_head.block else: chain_head_block = None self._to_exception(PY_LIBRARY.call( 'block_publisher_new', block_manager.pointer, ctypes.py_object(transaction_executor), ctypes.py_object(batch_committed), ctypes.py_object(transaction_committed), state_view_factory.pointer, ctypes.py_object(block_sender), ctypes.py_object(batch_sender), ctypes.py_object(chain_head_block), ctypes.py_object(identity_signer), ctypes.py_object(data_dir), ctypes.py_object(config_dir), ctypes.py_object(permission_verifier), ctypes.py_object(batch_observers), ctypes.py_object(batch_injector_factory), ctypes.byref(self.pointer)))
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
def verify_block(self, block_wrapper): """Check that the block received conforms to the consensus rules. Args: block_wrapper (BlockWrapper): The block to validate. Returns: Boolean: True if the Block is valid, False if the block is invalid. """ # Get the state view for the previous block in the chain so we can # create a PoET enclave and validator registry view previous_block = None try: previous_block = \ self._block_cache[block_wrapper.previous_block_id] except KeyError: pass state_view = \ BlockWrapper.state_view_for_block( block_wrapper=previous_block, state_view_factory=self._state_view_factory) poet_enclave_module = \ factory.PoetEnclaveFactory.get_poet_enclave_module( state_view=state_view, config_dir=self._config_dir, data_dir=self._data_dir) validator_registry_view = ValidatorRegistryView(state_view) # Grab the validator info based upon the block signer's public # key try: validator_info = \ validator_registry_view.get_validator_info( block_wrapper.header.signer_pubkey) except KeyError: LOGGER.error( 'Block %s rejected: Received block from an unregistered ' 'validator %s...%s', block_wrapper.identifier[:8], block_wrapper.header.signer_pubkey[:8], block_wrapper.header.signer_pubkey[-8:]) return False LOGGER.debug( 'Block Signer Name=%s, ID=%s...%s, PoET public key=' '%s...%s', validator_info.name, validator_info.id[:8], validator_info.id[-8:], validator_info.signup_info.poet_public_key[:8], validator_info.signup_info.poet_public_key[-8:]) # For the candidate block, reconstitute the wait certificate # and verify that it is valid wait_certificate = \ utils.deserialize_wait_certificate( block=block_wrapper, poet_enclave_module=poet_enclave_module) if wait_certificate is None: LOGGER.error( 'Block %s rejected: Block from validator %s (ID=%s...%s) was ' 'not created by PoET consensus module', block_wrapper.identifier[:8], validator_info.name, validator_info.id[:8], validator_info.id[-8:]) return False # Get the consensus state and PoET configuration view for the block # that is being built upon consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=block_wrapper.previous_block_id, block_cache=self._block_cache, state_view_factory=self._state_view_factory, consensus_state_store=self._consensus_state_store, poet_enclave_module=poet_enclave_module) poet_settings_view = PoetSettingsView(state_view=state_view) previous_certificate_id = \ utils.get_previous_certificate_id( block_header=block_wrapper.header, block_cache=self._block_cache, poet_enclave_module=poet_enclave_module) try: wait_certificate.check_valid( poet_enclave_module=poet_enclave_module, previous_certificate_id=previous_certificate_id, poet_public_key=validator_info.signup_info.poet_public_key, consensus_state=consensus_state, poet_settings_view=poet_settings_view) except ValueError as error: LOGGER.error( 'Block %s rejected: Wait certificate check failed - %s', block_wrapper.identifier[:8], error) return False # Reject the block if the validator signup information fails the # freshness check. if consensus_state.validator_signup_was_committed_too_late( validator_info=validator_info, poet_settings_view=poet_settings_view, block_cache=self._block_cache): LOGGER.error( 'Block %s rejected: Validator signup information not ' 'committed in a timely manner.', block_wrapper.identifier[:8]) return False # Reject the block if the validator has already claimed the key block # limit for its current PoET key pair. if consensus_state.validator_has_claimed_block_limit( validator_info=validator_info, poet_settings_view=poet_settings_view): LOGGER.error( 'Block %s rejected: Validator has reached maximum number of ' 'blocks with key pair.', block_wrapper.identifier[:8]) return False # Reject the block if the validator has not waited the required number # of blocks between when the block containing its validator registry # transaction was committed to the chain and trying to claim this # block if consensus_state.validator_is_claiming_too_early( validator_info=validator_info, block_number=block_wrapper.block_num, validator_registry_view=validator_registry_view, poet_settings_view=poet_settings_view, block_store=self._block_cache.block_store): LOGGER.error( 'Block %s rejected: Validator has not waited long enough ' 'since registering validator information.', block_wrapper.identifier[:8]) return False # Reject the block if the validator is claiming blocks at a rate that # is more frequent than is statistically allowed (i.e., zTest) if consensus_state.validator_is_claiming_too_frequently( validator_info=validator_info, previous_block_id=block_wrapper.previous_block_id, poet_settings_view=poet_settings_view, population_estimate=wait_certificate.population_estimate( poet_settings_view=poet_settings_view), block_cache=self._block_cache, poet_enclave_module=poet_enclave_module): LOGGER.error( 'Block %s rejected: Validator is claiming blocks too ' 'frequently.', block_wrapper.identifier[:8]) return False return True
def _build_candidate_block(self, chain_head): """ Build a candidate block and construct the consensus object to validate it. :param chain_head: The block to build on top of. :return: (BlockBuilder) - The candidate block in a BlockBuilder wrapper. """ state_view = BlockWrapper.state_view_for_block( chain_head, self._state_view_factory) consensus_module = ConsensusFactory.get_configured_consensus_module( chain_head.header_signature, state_view) # using chain_head so so we can use the setting_cache max_batches = int(self._settings_cache.get_setting( 'sawtooth.publisher.max_batches_per_block', chain_head.state_root_hash, default_value=0)) public_key = self._identity_signer.get_public_key().as_hex() consensus = consensus_module.\ BlockPublisher(block_cache=self._block_cache, state_view_factory=self._state_view_factory, batch_publisher=self._batch_publisher, data_dir=self._data_dir, config_dir=self._config_dir, validator_id=public_key) batch_injectors = [] if self._batch_injector_factory is not None: batch_injectors = self._batch_injector_factory.create_injectors( chain_head.identifier) if batch_injectors: LOGGER.debug("Loaded batch injectors: %s", batch_injectors) block_header = BlockHeader( block_num=chain_head.block_num + 1, previous_block_id=chain_head.header_signature, signer_public_key=public_key) block_builder = BlockBuilder(block_header) if not consensus.initialize_block(block_builder.block_header): if not self._logging_states.consensus_not_ready: self._logging_states.consensus_not_ready = True LOGGER.debug("Consensus not ready to build candidate block.") return None if self._logging_states.consensus_not_ready: self._logging_states.consensus_not_ready = False LOGGER.debug("Consensus is ready to build candidate block.") # create a new scheduler scheduler = self._transaction_executor.create_scheduler( self._squash_handler, chain_head.state_root_hash) # build the TransactionCommitCache committed_txn_cache = TransactionCommitCache( self._block_cache.block_store) self._transaction_executor.execute(scheduler) self._candidate_block = _CandidateBlock( self._block_cache.block_store, consensus, scheduler, committed_txn_cache, block_builder, max_batches, batch_injectors, SettingsView(state_view), public_key) for batch in self._pending_batches: if self._candidate_block.can_add_batch: self._candidate_block.add_batch(batch) else: break
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
def generate_block(self, previous_block=None, add_to_store=False, add_to_cache=False, batch_count=1, batches=None, status=BlockStatus.Unknown, invalid_consensus=False, invalid_batch=False, invalid_signature=False, weight=0): previous = self._get_block(previous_block) if previous is None: previous = self.chain_head header = BlockHeader( previous_block_id=previous.identifier, signer_public_key=self.identity_signer.get_public_key().as_hex(), block_num=previous.block_num + 1) block_builder = BlockBuilder(header) if batches: block_builder.add_batches(batches) if batch_count != 0: block_builder.add_batches( [self._generate_batch() for _ in range(batch_count)]) if invalid_batch: block_builder.add_batches( [self._generate_batch_from_payload('BAD')]) block_builder.set_state_hash('0' * 70) consensus = mock_consensus.BlockPublisher() consensus.finalize_block(block_builder.block_header, weight=weight) header_bytes = block_builder.block_header.SerializeToString() signature = self.identity_signer.sign(header_bytes) block_builder.set_signature(signature) block_wrapper = BlockWrapper(block_builder.build_block()) if batches: block_wrapper.block.batches.extend(batches) if batch_count: block_wrapper.block.batches.extend( [self.generate_batch() for _ in range(batch_count)]) if invalid_signature: block_wrapper.block.header_signature = "BAD" if invalid_consensus: block_wrapper.header.consensus = b'BAD' block_wrapper.status = status if add_to_cache: self.block_cache[block_wrapper.identifier] = block_wrapper if add_to_store: self.block_store[block_wrapper.identifier] = block_wrapper LOGGER.debug("Generated %s", dumps_block(block_wrapper)) return block_wrapper
def compare_forks(self, cur_fork_head, new_fork_head): """Given the head of two forks, return which should be the fork that the validator chooses. When this is called both forks consist of only valid blocks. Args: cur_fork_head (Block): The current head of the block chain. new_fork_head (Block): The head of the fork that is being evaluated. Returns: Boolean: True if the new chain should replace the current chain. False if the new chain should be discarded. """ chosen_fork_head = None state_view = \ BlockWrapper.state_view_for_block( block_wrapper=cur_fork_head, state_view_factory=self._state_view_factory) poet_enclave_module = \ factory.PoetEnclaveFactory.get_poet_enclave_module( state_view=state_view, config_dir=self._config_dir, data_dir=self._data_dir) current_fork_wait_certificate = \ utils.deserialize_wait_certificate( block=cur_fork_head, poet_enclave_module=poet_enclave_module) new_fork_wait_certificate = \ utils.deserialize_wait_certificate( block=new_fork_head, poet_enclave_module=poet_enclave_module) # If we ever get a new fork head that is not a PoET block, then bail # out. This should never happen, but defensively protect against it. if new_fork_wait_certificate is None: raise \ TypeError( 'New fork head {} is not a PoET block'.format( new_fork_head.identifier[:8])) # Criterion #1: If the current fork head is not PoET, then check to see # if the new fork head is building on top of it. That would be okay. # However if not, then we don't have a good deterministic way of # choosing a winner. Again, the latter should never happen, but # defensively protect against it. if current_fork_wait_certificate is None: if new_fork_head.previous_block_id == cur_fork_head.identifier: LOGGER.info( 'Choose new fork %s: New fork head switches consensus to ' 'PoET', new_fork_head.identifier[:8]) chosen_fork_head = new_fork_head else: raise \ TypeError( 'Trying to compare a PoET block {} to a non-PoET ' 'block {} that is not the direct predecessor'.format( new_fork_head.identifier[:8], cur_fork_head.identifier[:8])) # Criterion #2: If they share the same immediate previous block, # then the one with the smaller wait duration is chosen elif cur_fork_head.previous_block_id == \ new_fork_head.previous_block_id: if current_fork_wait_certificate.duration < \ new_fork_wait_certificate.duration: LOGGER.info( 'Choose current fork %s: Current fork wait duration ' '(%f) less than new fork wait duration (%f)', cur_fork_head.header_signature[:8], current_fork_wait_certificate.duration, new_fork_wait_certificate.duration) chosen_fork_head = cur_fork_head elif new_fork_wait_certificate.duration < \ current_fork_wait_certificate.duration: LOGGER.info( 'Choose new fork %s: New fork wait duration (%f) ' 'less than current fork wait duration (%f)', new_fork_head.header_signature[:8], new_fork_wait_certificate.duration, current_fork_wait_certificate.duration) chosen_fork_head = new_fork_head # Criterion #3: If they don't share the same immediate previous # block, then the one with the higher aggregate local mean wins else: # Get the consensus state for the current fork head and the # block immediately before the new fork head (as we haven't # committed to the block yet). So that the new fork doesn't # have to fight with one hand tied behind its back, add the # new fork head's wait certificate's local mean to the # aggregate local mean for the predecessor block's consensus # state for the comparison. current_fork_consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=cur_fork_head.identifier, block_cache=self._block_cache, state_view_factory=self._state_view_factory, consensus_state_store=self._consensus_state_store, poet_enclave_module=poet_enclave_module) new_fork_consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=new_fork_head.previous_block_id, block_cache=self._block_cache, state_view_factory=self._state_view_factory, consensus_state_store=self._consensus_state_store, poet_enclave_module=poet_enclave_module) new_fork_aggregate_local_mean = \ new_fork_consensus_state.aggregate_local_mean + \ new_fork_wait_certificate.local_mean if current_fork_consensus_state.aggregate_local_mean > \ new_fork_aggregate_local_mean: LOGGER.info( 'Choose current fork %s: Current fork aggregate ' 'local mean (%f) greater than new fork aggregate ' 'local mean (%f)', cur_fork_head.header_signature[:8], current_fork_consensus_state.aggregate_local_mean, new_fork_aggregate_local_mean) chosen_fork_head = cur_fork_head elif new_fork_aggregate_local_mean > \ current_fork_consensus_state.aggregate_local_mean: LOGGER.info( 'Choose new fork %s: New fork aggregate local mean ' '(%f) greater than current fork aggregate local mean ' '(%f)', new_fork_head.header_signature[:8], new_fork_aggregate_local_mean, current_fork_consensus_state.aggregate_local_mean) chosen_fork_head = new_fork_head # Criterion #4: If we have gotten to this point and we have not chosen # yet, we are going to fall back on using the block identifiers # (header signatures) . The lexicographically larger one will be the # chosen one. The chance that they are equal are infinitesimally # small. if chosen_fork_head is None: if cur_fork_head.header_signature > \ new_fork_head.header_signature: LOGGER.info( 'Choose current fork %s: Current fork header signature' '(%s) greater than new fork header signature (%s)', cur_fork_head.header_signature[:8], cur_fork_head.header_signature[:8], new_fork_head.header_signature[:8]) chosen_fork_head = cur_fork_head else: LOGGER.info( 'Choose new fork %s: New fork header signature (%s) ' 'greater than current fork header signature (%s)', new_fork_head.header_signature[:8], new_fork_head.header_signature[:8], cur_fork_head.header_signature[:8]) chosen_fork_head = new_fork_head # Now that we have chosen a fork for the chain head, if we chose the # new fork and it is a PoET block (i.e., it has a wait certificate), # we need to create consensus state store information for the new # fork's chain head. if chosen_fork_head == new_fork_head: # Get the state view for the previous block in the chain so we can # create a PoET enclave previous_block = None try: previous_block = \ self._block_cache[new_fork_head.previous_block_id] except KeyError: pass state_view = \ BlockWrapper.state_view_for_block( block_wrapper=previous_block, state_view_factory=self._state_view_factory) validator_registry_view = ValidatorRegistryView(state_view) try: # Get the validator info for the validator that claimed the # fork head validator_info = \ validator_registry_view.get_validator_info( new_fork_head.header.signer_pubkey) # Get the consensus state for the new fork head's previous # block, let the consensus state update itself appropriately # based upon the validator claiming a block, and then # associate the consensus state with the new block in the # store. consensus_state = \ ConsensusState.consensus_state_for_block_id( block_id=new_fork_head.previous_block_id, block_cache=self._block_cache, state_view_factory=self._state_view_factory, consensus_state_store=self._consensus_state_store, poet_enclave_module=poet_enclave_module) consensus_state.validator_did_claim_block( validator_info=validator_info, wait_certificate=new_fork_wait_certificate, poet_settings_view=PoetSettingsView(state_view)) self._consensus_state_store[new_fork_head.identifier] = \ consensus_state LOGGER.debug( 'Create consensus state: BID=%s, ALM=%f, TBCC=%d', new_fork_head.identifier[:8], consensus_state.aggregate_local_mean, consensus_state.total_block_claim_count) except KeyError: # This _should_ never happen. The new potential fork head # has to have been a PoET block and for it to be verified # by the PoET block verifier, it must have been signed by # validator in the validator registry. If not found, we # are going to just stick with the current fork head. LOGGER.error( 'New fork head claimed by validator not in validator ' 'registry: %s...%s', new_fork_head.header.signer_pubkey[:8], new_fork_head.header.signer_pubkey[-8:]) chosen_fork_head = cur_fork_head return chosen_fork_head == new_fork_head
def _batch_index_keys(block): blkw = BlockWrapper.wrap(block) return [batch.header_signature.encode() for batch in blkw.batches]
def _get_block(self, key): value = self._block_store.get(key) if value is None: raise KeyError('Block "{}" not found in store'.format(key)) return BlockWrapper.wrap(value)
def _block_num_index_keys(block): blkw = BlockWrapper.wrap(block) # Format the number to a 64bit hex value, for natural ordering return [BlockStore.block_num_to_hex(blkw.block_num).encode()]