Esempio n. 1
0
class Vote:
    """This class encapsulates the logic to vote on blocks.

    Note:
        Methods of this class will be executed in different processes.
    """
    def __init__(self):
        """Initialize the Block voter."""

        # Since cannot share a connection to RethinkDB using multiprocessing,
        # we need to create a temporary instance of BigchainDB that we use
        # only to query RethinkDB

        # This is the Bigchain instance that will be "shared" (aka: copied)
        # by all the subprocesses

        self.bigchain = Bigchain()
        self.last_voted_id = Bigchain().get_last_voted_block().id

        self.counters = Counter()
        self.blocks_validity_status = {}

        dummy_tx = Transaction.create([self.bigchain.me],
                                      [([self.bigchain.me], 1)]).to_dict()
        self.invalid_dummy_tx = dummy_tx

    def validate_block(self, block_dict):
        if not self.bigchain.has_previous_vote(block_dict['id']):
            try:
                block = Block.from_db(
                    self.bigchain,
                    block_dict,
                    from_dict_kwargs={'tx_construct': FastTransaction})
            except (exceptions.InvalidHash):
                # XXX: if a block is invalid we should skip the `validate_tx`
                # step, but since we are in a pipeline we cannot just jump to
                # another function. Hackish solution: generate an invalid
                # transaction and propagate it to the next steps of the
                # pipeline.
                return block_dict['id'], [self.invalid_dummy_tx]
            try:
                block._validate_block(self.bigchain)
            except exceptions.ValidationError:
                # XXX: if a block is invalid we should skip the `validate_tx`
                # step, but since we are in a pipeline we cannot just jump to
                # another function. Hackish solution: generate an invalid
                # transaction and propagate it to the next steps of the
                # pipeline.
                return block.id, [self.invalid_dummy_tx]
            return block.id, block_dict['block']['transactions']

    def ungroup(self, block_id, transactions):
        """Given a block, ungroup the transactions in it.

        Args:
            block_id (str): the id of the block in progress.
            transactions (list(dict)): transactions of the block in
                progress.

        Returns:
            ``None`` if the block has been already voted, an iterator that
            yields a transaction, block id, and the total number of
            transactions contained in the block otherwise.
        """

        num_tx = len(transactions)
        for tx in transactions:
            yield tx, block_id, num_tx

    def validate_tx(self, tx_dict, block_id, num_tx):
        """Validate a transaction. Transaction must also not be in any VALID
           block.

        Args:
            tx_dict (dict): the transaction to validate
            block_id (str): the id of block containing the transaction
            num_tx (int): the total number of transactions to process

        Returns:
            Three values are returned, the validity of the transaction,
            ``block_id``, ``num_tx``.
        """

        try:
            tx = Transaction.from_dict(tx_dict)
            new = self.bigchain.is_new_transaction(tx.id,
                                                   exclude_block_id=block_id)
            if not new:
                raise exceptions.ValidationError('Tx already exists, %s',
                                                 tx.id)
            tx.validate(self.bigchain)
            valid = True
        except exceptions.ValidationError as e:
            valid = False
            logger.warning('Invalid tx: %s', e)

        return valid, block_id, num_tx

    def vote(self, tx_validity, block_id, num_tx):
        """Collect the validity of transactions and cast a vote when ready.

        Args:
            tx_validity (bool): the validity of the transaction
            block_id (str): the id of block containing the transaction
            num_tx (int): the total number of transactions to process

        Returns:
            None, or a vote if a decision has been reached.
        """

        self.counters[block_id] += 1
        self.blocks_validity_status[
            block_id] = tx_validity and self.blocks_validity_status.get(
                block_id, True)

        if self.counters[block_id] == num_tx:
            vote = self.bigchain.vote(block_id, self.last_voted_id,
                                      self.blocks_validity_status[block_id])
            self.last_voted_id = block_id
            del self.counters[block_id]
            del self.blocks_validity_status[block_id]
            return vote, num_tx

    def write_vote(self, vote, num_tx):
        """Write vote to the database.

        Args:
            vote: the vote to write.
        """
        validity = 'valid' if vote['vote']['is_block_valid'] else 'invalid'
        logger.info("Voting '%s' for block %s", validity,
                    vote['vote']['voting_for_block'])
        self.bigchain.write_vote(vote)
        self.bigchain.statsd.incr('pipelines.vote.throughput', num_tx)
        return vote
Esempio n. 2
0
class BlockPipeline:
    """This class encapsulates the logic to create blocks.

    Note:
        Methods of this class will be executed in different processes.
    """
    def __init__(self):
        """Initialize the BlockPipeline creator"""
        self.bigchain = Bigchain()
        self.txs = tx_collector()

    def filter_tx(self, tx):
        """Filter a transaction.

        Args:
            tx (dict): the transaction to process.

        Returns:
            dict: The transaction if assigned to the current node,
            ``None`` otherwise.
        """
        if tx['assignee'] == self.bigchain.me:
            tx.pop('assignee')
            tx.pop('assignment_timestamp')
            return tx

    def validate_tx(self, tx):
        """Validate a transaction.

        Also checks if the transaction already exists in the blockchain. If it
        does, or it's invalid, it's deleted from the backlog immediately.

        Args:
            tx (dict): the transaction to validate.

        Returns:
            :class:`~bigchaindb.models.Transaction`: The transaction if valid,
            ``None`` otherwise.
        """
        try:
            tx = Transaction.from_dict(tx)
        except ValidationError:
            return None

        # If transaction is in any VALID or UNDECIDED block we
        # should not include it again
        if not self.bigchain.is_new_transaction(tx.id):
            self.bigchain.delete_transaction(tx.id)
            return None

        # If transaction is not valid it should not be included
        try:
            # Do not allow an externally submitted GENESIS transaction.
            # A simple check is enough as a pipeline is started only after the
            # creation of GENESIS block, or after the verification of a GENESIS
            # block. Voting will fail at a later stage if the GENESIS block is
            # absent.
            if tx.operation == Transaction.GENESIS:
                raise GenesisBlockAlreadyExistsError(
                    'Duplicate GENESIS transaction')

            tx.validate(self.bigchain)
            return tx
        except ValidationError as e:
            logger.warning('Invalid tx: %s', e)
            self.bigchain.delete_transaction(tx.id)
            return None

    def create(self, tx, timeout=False):
        """Create a block.

        This method accumulates transactions to put in a block and outputs
        a block when one of the following conditions is true:
        - the size limit of the block has been reached, or
        - a timeout happened.

        Args:
            tx (:class:`~bigchaindb.models.Transaction`): the transaction
                to validate, might be None if a timeout happens.
            timeout (bool): ``True`` if a timeout happened
                (Default: ``False``).

        Returns:
            :class:`~bigchaindb.models.Block`: The block,
            if a block is ready, or ``None``.
        """
        txs = self.txs.send(tx)
        if len(txs) == 1000 or (timeout and txs):
            block = self.bigchain.create_block(txs)
            self.txs = tx_collector()
            return block

    def write(self, block):
        """Write the block to the Database.

        Args:
            block (:class:`~bigchaindb.models.Block`): the block of
                transactions to write to the database.

        Returns:
            :class:`~bigchaindb.models.Block`: The Block.
        """
        logger.info('Write new block %s with %s transactions', block.id,
                    len(block.transactions))
        self.bigchain.write_block(block)
        self.bigchain.statsd.incr('pipelines.block.throughput',
                                  len(block.transactions))
        return block

    def delete_tx(self, block):
        """Delete transactions.

        Args:
            block (:class:`~bigchaindb.models.Block`): the block
                containg the transactions to delete.

        Returns:
            :class:`~bigchaindb.models.Block`: The block.
        """
        self.bigchain.delete_transaction(*[tx.id for tx in block.transactions])
        return block
Esempio n. 3
0
class BlockPipeline:
    """This class encapsulates the logic to create blocks.

    Note:
        Methods of this class will be executed in different processes.
    """

    def __init__(self):
        """Initialize the BlockPipeline creator"""
        self.bigchain = Bigchain()
        self.txs = tx_collector()

    def filter_tx(self, tx):
        """Filter a transaction.

        Args:
            tx (dict): the transaction to process.

        Returns:
            dict: The transaction if assigned to the current node,
            ``None`` otherwise.
        """
        if tx['assignee'] == self.bigchain.me:
            tx.pop('assignee')
            tx.pop('assignment_timestamp')
            return tx

    def validate_tx(self, tx):
        """Validate a transaction.

        Also checks if the transaction already exists in the blockchain. If it
        does, or it's invalid, it's deleted from the backlog immediately.

        Args:
            tx (dict): the transaction to validate.

        Returns:
            :class:`~bigchaindb.models.Transaction`: The transaction if valid,
            ``None`` otherwise.
        """
        try:
            tx = Transaction.from_dict(tx)
        except ValidationError:
            return None

        # If transaction is in any VALID or UNDECIDED block we
        # should not include it again
        if not self.bigchain.is_new_transaction(tx.id):
            self.bigchain.delete_transaction(tx.id)
            return None

        # If transaction is not valid it should not be included
        try:
            # Do not allow an externally submitted GENESIS transaction.
            # A simple check is enough as a pipeline is started only after the
            # creation of GENESIS block, or after the verification of a GENESIS
            # block. Voting will fail at a later stage if the GENESIS block is
            # absent.
            if tx.operation == Transaction.GENESIS:
                raise GenesisBlockAlreadyExistsError('Duplicate GENESIS transaction')

            tx.validate(self.bigchain)
            return tx
        except ValidationError as e:
            logger.warning('Invalid tx: %s', e)
            self.bigchain.delete_transaction(tx.id)
            return None

    def create(self, tx, timeout=False):
        """Create a block.

        This method accumulates transactions to put in a block and outputs
        a block when one of the following conditions is true:
        - the size limit of the block has been reached, or
        - a timeout happened.

        Args:
            tx (:class:`~bigchaindb.models.Transaction`): the transaction
                to validate, might be None if a timeout happens.
            timeout (bool): ``True`` if a timeout happened
                (Default: ``False``).

        Returns:
            :class:`~bigchaindb.models.Block`: The block,
            if a block is ready, or ``None``.
        """
        txs = self.txs.send(tx)
        if len(txs) == 1000 or (timeout and txs):
            block = self.bigchain.create_block(txs)
            self.txs = tx_collector()
            return block

    def write(self, block):
        """Write the block to the Database.

        Args:
            block (:class:`~bigchaindb.models.Block`): the block of
                transactions to write to the database.

        Returns:
            :class:`~bigchaindb.models.Block`: The Block.
        """
        logger.info('Write new block %s with %s transactions',
                    block.id, len(block.transactions))
        self.bigchain.write_block(block)
        self.bigchain.statsd.incr('pipelines.block.throughput',
                                  len(block.transactions))
        return block

    def delete_tx(self, block):
        """Delete transactions.

        Args:
            block (:class:`~bigchaindb.models.Block`): the block
                containg the transactions to delete.

        Returns:
            :class:`~bigchaindb.models.Block`: The block.
        """
        self.bigchain.delete_transaction(*[tx.id for tx in block.transactions])
        return block
Esempio n. 4
0
class Vote:
    """This class encapsulates the logic to vote on blocks.

    Note:
        Methods of this class will be executed in different processes.
    """

    def __init__(self):
        """Initialize the Block voter."""

        # Since cannot share a connection to RethinkDB using multiprocessing,
        # we need to create a temporary instance of BigchainDB that we use
        # only to query RethinkDB

        # This is the Bigchain instance that will be "shared" (aka: copied)
        # by all the subprocesses

        self.bigchain = Bigchain()
        self.last_voted_id = Bigchain().get_last_voted_block().id

        self.counters = Counter()
        self.validity = {}

        self.invalid_dummy_tx = Transaction.create([self.bigchain.me],
                                                   [([self.bigchain.me], 1)])

    def validate_block(self, block):
        if not self.bigchain.has_previous_vote(block['id']):
            try:
                block = Block.from_dict(block)
            except (exceptions.InvalidHash):
                # XXX: if a block is invalid we should skip the `validate_tx`
                # step, but since we are in a pipeline we cannot just jump to
                # another function. Hackish solution: generate an invalid
                # transaction and propagate it to the next steps of the
                # pipeline.
                return block['id'], [self.invalid_dummy_tx]
            try:
                block._validate_block(self.bigchain)
            except exceptions.ValidationError:
                # XXX: if a block is invalid we should skip the `validate_tx`
                # step, but since we are in a pipeline we cannot just jump to
                # another function. Hackish solution: generate an invalid
                # transaction and propagate it to the next steps of the
                # pipeline.
                return block.id, [self.invalid_dummy_tx]
            return block.id, block.transactions

    def ungroup(self, block_id, transactions):
        """Given a block, ungroup the transactions in it.

        Args:
            block_id (str): the id of the block in progress.
            transactions (list(Transaction)): transactions of the block in
                progress.

        Returns:
            ``None`` if the block has been already voted, an iterator that
            yields a transaction, block id, and the total number of
            transactions contained in the block otherwise.
        """

        num_tx = len(transactions)
        for tx in transactions:
            yield tx, block_id, num_tx

    def validate_tx(self, tx, block_id, num_tx):
        """Validate a transaction. Transaction must also not be in any VALID
           block.

        Args:
            tx (dict): the transaction to validate
            block_id (str): the id of block containing the transaction
            num_tx (int): the total number of transactions to process

        Returns:
            Three values are returned, the validity of the transaction,
            ``block_id``, ``num_tx``.
        """
        new = self.bigchain.is_new_transaction(tx.id, exclude_block_id=block_id)
        if not new:
            return False, block_id, num_tx

        try:
            tx.validate(self.bigchain)
            valid = True
        except exceptions.ValidationError as e:
            logger.warning('Invalid tx: %s', e)
            valid = False

        return valid, block_id, num_tx

    def vote(self, tx_validity, block_id, num_tx):
        """Collect the validity of transactions and cast a vote when ready.

        Args:
            tx_validity (bool): the validity of the transaction
            block_id (str): the id of block containing the transaction
            num_tx (int): the total number of transactions to process

        Returns:
            None, or a vote if a decision has been reached.
        """

        self.counters[block_id] += 1
        self.validity[block_id] = tx_validity and self.validity.get(block_id,
                                                                    True)

        if self.counters[block_id] == num_tx:
            vote = self.bigchain.vote(block_id,
                                      self.last_voted_id,
                                      self.validity[block_id])
            self.last_voted_id = block_id
            del self.counters[block_id]
            del self.validity[block_id]
            return vote

    def write_vote(self, vote):
        """Write vote to the database.

        Args:
            vote: the vote to write.
        """
        validity = 'valid' if vote['vote']['is_block_valid'] else 'invalid'
        logger.info("Voting '%s' for block %s", validity,
                    vote['vote']['voting_for_block'])
        self.bigchain.write_vote(vote)
        return vote
Esempio n. 5
0
class BlockPipeline:
    """This class encapsulates the logic to create blocks.

    Note:
        Methods of this class will be executed in different processes.
    """
    def __init__(self):
        """Initialize the BlockPipeline creator"""
        self.bigchain = Bigchain()
        self.txs = []

    def filter_tx(self, tx):
        """Filter a transaction.

        Args:
            tx (dict): the transaction to process.

        Returns:
            dict: The transaction if assigned to the current node,
            ``None`` otherwise.
        """
        if tx['assignee'] == self.bigchain.me:
            tx.pop('assignee')
            tx.pop('assignment_timestamp')
            return tx

    def validate_tx(self, tx):
        """Validate a transaction.

        Also checks if the transaction already exists in the blockchain. If it
        does, or it's invalid, it's deleted from the backlog immediately.

        Args:
            tx (dict): the transaction to validate.

        Returns:
            :class:`~bigchaindb.models.Transaction`: The transaction if valid,
            ``None`` otherwise.
        """
        try:
            tx = Transaction.from_dict(tx)
        except (SchemaValidationError, InvalidHash, InvalidSignature,
                AmountError):
            return None

        # If transaction is in any VALID or UNDECIDED block we
        # should not include it again
        if not self.bigchain.is_new_transaction(tx.id):
            self.bigchain.delete_transaction(tx.id)
            return None

        # If transaction is not valid it should not be included
        if not self.bigchain.is_valid_transaction(tx):
            self.bigchain.delete_transaction(tx.id)
            return None

        return tx

    def create(self, tx, timeout=False):
        """Create a block.

        This method accumulates transactions to put in a block and outputs
        a block when one of the following conditions is true:
        - the size limit of the block has been reached, or
        - a timeout happened.

        Args:
            tx (:class:`~bigchaindb.models.Transaction`): the transaction
                to validate, might be None if a timeout happens.
            timeout (bool): ``True`` if a timeout happened
                (Default: ``False``).

        Returns:
            :class:`~bigchaindb.models.Block`: The block,
            if a block is ready, or ``None``.
        """
        if tx:
            self.txs.append(tx)
        if len(self.txs) == 1000 or (timeout and self.txs):
            block = self.bigchain.create_block(self.txs)
            self.txs = []
            return block

    def write(self, block):
        """Write the block to the Database.

        Args:
            block (:class:`~bigchaindb.models.Block`): the block of
                transactions to write to the database.

        Returns:
            :class:`~bigchaindb.models.Block`: The Block.
        """
        logger.info('Write new block %s with %s transactions', block.id,
                    len(block.transactions))
        self.bigchain.write_block(block)
        return block

    def delete_tx(self, block):
        """Delete transactions.

        Args:
            block (:class:`~bigchaindb.models.Block`): the block
                containg the transactions to delete.

        Returns:
            :class:`~bigchaindb.models.Block`: The block.
        """
        self.bigchain.delete_transaction(*[tx.id for tx in block.transactions])
        return block