def _finalize_block(self, block): if self._scheduler: self._scheduler.finalize() self._scheduler.complete(block=True) # Read valid batches from self._scheduler pending_batches = self._pending_batches # this is a transaction cache to track the transactions committed # upto this batch. committed_txn_cache = TransactionCache(self._block_cache.block_store) self._pending_batches = [] self._committed_txn_cache = TransactionCache( self._block_cache.block_store) state_hash = None for batch in pending_batches: result = self._scheduler.get_batch_execution_result( batch.header_signature) # if a result is None, this means that the executor never # received the batch and it should be added to # the pending_batches if result is None: self._pending_batches.append(batch) self._committed_txn_cache.add_batch(batch) elif result.is_valid: # check if a dependent batch failed. This could be belt and # suspenders action here but it is logically possible that # a transaction has a dependency that fails it could # still succeed validation. In that case we do not want # to add it to the batch. if not self._check_batch_dependencies(batch, committed_txn_cache): LOGGER.debug( "Batch %s invalid, due to missing txn " "dependency.", batch.header_signature) LOGGER.debug( "Abandoning block %s:" + "root state hash has invalid txn applied", block) pending_batches.remove(batch) self._pending_batches = pending_batches self._committed_txn_cache = \ TransactionCache(self._block_cache.block_store) return False else: block.add_batch(batch) self._committed_txn_cache.add_batch(batch) state_hash = result.state_hash else: committed_txn_cache.uncommit_batch(batch) LOGGER.debug("Batch %s invalid, not added to block.", batch.header_signature) if state_hash is None: LOGGER.debug("Abandoning block %s no batches added", block) return False if not self._consensus.finalize_block(block.block_header): LOGGER.debug( "Abandoning block %s, consensus failed to finalize " "it", block) return False self._consensus = None block.set_state_hash(state_hash) self._sign_block(block) return True
def run(self): """ Main entry for Block Validation, Take a given candidate block and decide if it is valid then if it is valid determine if it should be the new head block. Returns the results to the ChainController so that the change over can be made if necessary. """ try: LOGGER.info("Starting block validation of : %s", self._new_block) cur_chain = self._result["cur_chain"] # ordered list of the # current chain blocks new_chain = self._result["new_chain"] # ordered list of the new # chain blocks # 1) Find the common ancestor block, the root of the fork. # walk back till both chains are the same height (new_blkw, cur_blkw) = self._find_common_height(new_chain, cur_chain) # 2) Walk back until we find the common ancestor self._find_common_ancestor(new_blkw, cur_blkw, new_chain, cur_chain) # 3) Determine the validity of the new fork # build the transaction cache to simulate the state of the # chain at the common root. committed_txn = TransactionCache(self._block_cache.block_store) for block in cur_chain: for batch in block.batches: committed_txn.uncommit_batch(batch) valid = True for block in reversed(new_chain): if valid: if not self.validate_block(block, committed_txn): LOGGER.info("Block validation failed: %s", block) valid = False else: LOGGER.info( "Block marked invalid(invalid predecessor): " + "%s", block) block.status = BlockStatus.Invalid if not valid: self._done_cb(False, self._result) return # 4) Evaluate the 2 chains to see if the new chain should be # committed commit_new_chain = self._test_commit_new_chain() # 5) Consensus to compute batch sets (only if we are switching). if commit_new_chain: (self._result["committed_batches"], self._result["uncommitted_batches"]) =\ self._compute_batch_change(new_chain, cur_chain) # 6) Tell the journal we are done. self._done_cb(commit_new_chain, self._result) LOGGER.info("Finished block validation of: %s", self._new_block) except BlockValidationAborted: return except Exception as exc: # pylint: disable=broad-except LOGGER.error("Block validation failed with unexpected error: %s", self._new_block) LOGGER.exception(exc) # callback to clean up the block out of the processing list. self._done_cb(False, self._result)
def run(self): """ Main entry for Block Validation, Take a given candidate block and decide if it is valid then if it is valid determine if it should be the new head block. Returns the results to the ChainController so that the change over can be made if necessary. """ try: LOGGER.info("Starting block validation of : %s", self._new_block) cur_chain = self._result["cur_chain"] # ordered list of the # current chain blocks new_chain = self._result["new_chain"] # ordered list of the new # chain blocks # 1) Find the common ancestor block, the root of the fork. # walk back till both chains are the same height (new_blkw, cur_blkw) = self._find_common_height(new_chain, cur_chain) # 2) Walk back until we find the common ancestor self._find_common_ancestor(new_blkw, cur_blkw, new_chain, cur_chain) # 3) Determine the validity of the new fork # build the transaction cache to simulate the state of the # chain at the common root. committed_txn = TransactionCache(self._block_cache.block_store) for block in cur_chain: for batch in block.batches: committed_txn.uncommit_batch(batch) valid = True for block in reversed(new_chain): if valid: if not self.validate_block(block, committed_txn): LOGGER.info("Block validation failed: %s", block) valid = False else: LOGGER.info("Block marked invalid(invalid predecessor): " + "%s", block) block.status = BlockStatus.Invalid if not valid: self._done_cb(False, self._result) return # 4) Evaluate the 2 chains to see if the new chain should be # committed commit_new_chain = self._test_commit_new_chain() # 5) Consensus to compute batch sets (only if we are switching). if commit_new_chain: (self._result["committed_batches"], self._result["uncommitted_batches"]) =\ self._compute_batch_change(new_chain, cur_chain) # 6) Tell the journal we are done. self._done_cb(commit_new_chain, self._result) LOGGER.info("Finished block validation of: %s", self._new_block) except BlockValidationAborted: return except Exception as exc: # pylint: disable=broad-except LOGGER.error("Block validation failed with unexpected error: %s", self._new_block) LOGGER.exception(exc) # callback to clean up the block out of the processing list. self._done_cb(False, self._result)