1
0
mirror of https://github.com/bigchaindb/bigchaindb.git synced 2024-06-28 00:27:45 +02:00

move voting logic out of block_election_status

This commit is contained in:
Scott Sadler 2017-02-23 18:31:40 +01:00
parent c68856bc43
commit 7fd1de696c
3 changed files with 15 additions and 133 deletions

View File

@ -1,11 +1,4 @@
import logging from bigchaindb.voting import Voting
from bigchaindb.utils import verify_vote_signature
from bigchaindb.common.schema import (SchemaValidationError,
validate_vote_schema)
logger = logging.getLogger(__name__)
class BaseConsensusRules(): class BaseConsensusRules():
@ -16,34 +9,15 @@ class BaseConsensusRules():
All methods listed below must be implemented. All methods listed below must be implemented.
""" """
voting = Voting
@staticmethod @staticmethod
def validate_transaction(bigchain, transaction): def validate_transaction(bigchain, transaction):
"""See :meth:`bigchaindb.models.Transaction.validate` """See :meth:`bigchaindb.models.Transaction.validate`
for documentation. for documentation."""
"""
return transaction.validate(bigchain) return transaction.validate(bigchain)
@staticmethod @staticmethod
def validate_block(bigchain, block): def validate_block(bigchain, block):
"""See :meth:`bigchaindb.models.Block.validate` for documentation.""" """See :meth:`bigchaindb.models.Block.validate` for documentation."""
return block.validate(bigchain) return block.validate(bigchain)
@staticmethod
def verify_vote(voters, signed_vote):
"""Verify the signature of a vote.
Refer to the documentation of
:func:`bigchaindb.utils.verify_signature`.
"""
if verify_vote_signature(voters, signed_vote):
try:
validate_vote_schema(signed_vote)
return True
except SchemaValidationError as exc:
logger.warning(exc)
else:
logger.warning('Vote failed signature verification: '
'%s with voters: %s', signed_vote, voters)
return False

View File

@ -1,9 +1,6 @@
import random import random
import math
import collections
from time import time from time import time
from itertools import compress
from bigchaindb.common import crypto, exceptions from bigchaindb.common import crypto, exceptions
from bigchaindb.common.utils import gen_timestamp, serialize from bigchaindb.common.utils import gen_timestamp, serialize
from bigchaindb.common.transaction import TransactionLink from bigchaindb.common.transaction import TransactionLink
@ -203,8 +200,7 @@ class Bigchain(object):
if include_status: if include_status:
if block: if block:
status = self.block_election_status(block_id, status = self.block_election_status(block)
block['block']['voters'])
return block, status return block, status
else: else:
return block return block
@ -305,12 +301,8 @@ class Bigchain(object):
blocks = backend.query.get_blocks_status_from_transaction(self.connection, txid) blocks = backend.query.get_blocks_status_from_transaction(self.connection, txid)
if blocks: if blocks:
# Determine the election status of each block # Determine the election status of each block
validity = { validity = {block['id']: self.block_election_status(block)
block['id']: self.block_election_status( for block in blocks}
block['id'],
block['block']['voters']
) for block in blocks
}
# NOTE: If there are multiple valid blocks with this transaction, # NOTE: If there are multiple valid blocks with this transaction,
# something has gone wrong # something has gone wrong
@ -626,69 +618,12 @@ class Bigchain(object):
# XXX: should this return instaces of Block? # XXX: should this return instaces of Block?
return backend.query.get_unvoted_blocks(self.connection, self.me) return backend.query.get_unvoted_blocks(self.connection, self.me)
def block_election_status(self, block_id, voters): def block_election_status(self, block):
"""Tally the votes on a block, and return the status: valid, invalid, or undecided.""" """Tally the votes on a block, and return the status:
valid, invalid, or undecided."""
votes = list(backend.query.get_votes_by_block_id(self.connection, block_id)) votes = list(backend.query.get_votes_by_block_id(self.connection,
n_voters = len(voters) block.id))
keyring = self.nodes_except_me + [self.me]
voter_counts = collections.Counter([vote['node_pubkey'] for vote in votes]) result = self.consensus.voting.block_election(block, votes, keyring)
for node in voter_counts: # TODO: logging
if voter_counts[node] > 1: return result['status']
raise exceptions.MultipleVotesError(
'Block {block_id} has multiple votes ({n_votes}) from voting node {node_id}'
.format(block_id=block_id, n_votes=str(voter_counts[node]), node_id=node))
if len(votes) > n_voters:
raise exceptions.MultipleVotesError('Block {block_id} has {n_votes} votes cast, but only {n_voters} voters'
.format(block_id=block_id, n_votes=str(len(votes)),
n_voters=str(n_voters)))
# vote_cast is the list of votes e.g. [True, True, False]
vote_cast = [vote['vote']['is_block_valid'] for vote in votes]
# prev_block are the ids of the nominal prev blocks e.g.
# ['block1_id', 'block1_id', 'block2_id']
prev_block = [vote['vote']['previous_block'] for vote in votes]
# vote_validity checks whether a vote is valid
# or invalid, e.g. [False, True, True]
vote_validity = [self.consensus.verify_vote(voters, vote) for vote in votes]
# element-wise product of stated vote and validity of vote
# vote_cast = [True, True, False] and
# vote_validity = [False, True, True] gives
# [True, False]
# Only the correctly signed votes are tallied.
vote_list = list(compress(vote_cast, vote_validity))
# Total the votes. Here, valid and invalid refer
# to the vote cast, not whether the vote itself
# is valid or invalid.
n_valid_votes = sum(vote_list)
n_invalid_votes = len(vote_cast) - n_valid_votes
# The use of ceiling and floor is to account for the case of an
# even number of voters where half the voters have voted 'invalid'
# and half 'valid'. In this case, the block should be marked invalid
# to avoid a tie. In the case of an odd number of voters this is not
# relevant, since one side must be a majority.
if n_invalid_votes >= math.ceil(n_voters / 2):
return Bigchain.BLOCK_INVALID
elif n_valid_votes > math.floor(n_voters / 2):
# The block could be valid, but we still need to check if votes
# agree on the previous block.
#
# First, only consider blocks with legitimate votes
prev_block_list = list(compress(prev_block, vote_validity))
# Next, only consider the blocks with 'yes' votes
prev_block_valid_list = list(compress(prev_block_list, vote_list))
counts = collections.Counter(prev_block_valid_list)
# Make sure the majority vote agrees on previous node.
# The majority vote must be the most common, by definition.
# If it's not, there is no majority agreement on the previous
# block.
if counts.most_common()[0][1] > math.floor(n_voters / 2):
return Bigchain.BLOCK_VALID
else:
return Bigchain.BLOCK_INVALID
else:
return Bigchain.BLOCK_UNDECIDED

View File

@ -3,9 +3,6 @@ import threading
import queue import queue
import multiprocessing as mp import multiprocessing as mp
from bigchaindb.common import crypto
from bigchaindb.common.utils import serialize
class ProcessGroup(object): class ProcessGroup(object):
@ -116,30 +113,6 @@ def condition_details_has_owner(condition_details, owner):
return False return False
def verify_vote_signature(voters, signed_vote):
"""Verify the signature of a vote
A valid vote should have been signed by a voter's private key.
Args:
voters (list): voters of the block that is under election
signed_vote (dict): a vote with the `signature` included.
Returns:
bool: True if the signature is correct, False otherwise.
"""
signature = signed_vote['signature']
pk_base58 = signed_vote['node_pubkey']
# immediately return False if the voter is not in the block voter list
if pk_base58 not in voters:
return False
public_key = crypto.PublicKey(pk_base58)
return public_key.verify(serialize(signed_vote['vote']).encode(), signature)
def is_genesis_block(block): def is_genesis_block(block):
"""Check if the block is the genesis block. """Check if the block is the genesis block.