Example #1
0
    def get_last_voted_block(self):
        """Returns the last block that this node voted on."""

        try:
            # get the latest value for the vote timestamp (over all votes)
            max_timestamp = self.connection.run(
                r.table('votes', read_mode=self.read_mode).filter(
                    r.row['node_pubkey'] == self.me).max(
                        r.row['vote']['timestamp']))['vote']['timestamp']

            last_voted = list(
                self.connection.run(
                    r.table('votes', read_mode=self.read_mode).filter(
                        r.row['vote']['timestamp'] == max_timestamp).filter(
                            r.row['node_pubkey'] == self.me)))

        except r.ReqlNonExistenceError:
            # return last vote if last vote exists else return Genesis block
            res = self.connection.run(
                r.table('bigchain', read_mode=self.read_mode).filter(
                    util.is_genesis_block))
            block = list(res)[0]
            return Block.from_dict(block)

        # Now the fun starts. Since the resolution of timestamp is a second,
        # we might have more than one vote per timestamp. If this is the case
        # then we need to rebuild the chain for the blocks that have been retrieved
        # to get the last one.

        # Given a block_id, mapping returns the id of the block pointing at it.
        mapping = {
            v['vote']['previous_block']: v['vote']['voting_for_block']
            for v in last_voted
        }

        # Since we follow the chain backwards, we can start from a random
        # point of the chain and "move up" from it.
        last_block_id = list(mapping.values())[0]

        # We must be sure to break the infinite loop. This happens when:
        # - the block we are currenty iterating is the one we are looking for.
        #   This will trigger a KeyError, breaking the loop
        # - we are visiting again a node we already explored, hence there is
        #   a loop. This might happen if a vote points both `previous_block`
        #   and `voting_for_block` to the same `block_id`
        explored = set()

        while True:
            try:
                if last_block_id in explored:
                    raise exceptions.CyclicBlockchainError()
                explored.add(last_block_id)
                last_block_id = mapping[last_block_id]
            except KeyError:
                break

        res = self.connection.run(
            r.table('bigchain', read_mode=self.read_mode).get(last_block_id))

        return Block.from_dict(res)
Example #2
0
    def vote(self, block_id, previous_block_id, decision, invalid_reason=None):
        """Create a signed vote for a block given the
		:attr:`previous_block_id` and the :attr:`decision` (valid/invalid).

        Args:
            block_id (str): The id of the block to vote on.
            previous_block_id (str): The id of the previous block.
            decision (bool): Whether the block is valid or invalid.
            invalid_reason (Optional[str]): Reason the block is invalid
        """

        if block_id == previous_block_id:
            raise exceptions.CyclicBlockchainError()

        vote = {
            'voting_for_block': block_id,
            'previous_block': previous_block_id,
            'is_block_valid': decision,
            'invalid_reason': invalid_reason,
            'timestamp': gen_timestamp()
        }

        vote_data = serialize(vote)
        signature = crypto.SigningKey(self.me_private).sign(vote_data.encode())

        vote_signed = {
            'node_pubkey': self.me,
            'signature': signature,
            'vote': vote
        }

        return vote_signed