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:
parent
c68856bc43
commit
7fd1de696c
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user