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)
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