示例#1
0
    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
示例#2
0
    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)
示例#3
0
    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)