mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Problem: voting code is not used anymore (#2357)
* Problem: voting code is not used anymore Solution: remove all voting related code * Problem: Some voting functionality is still present. Solution: Remove it. Update some of the related tests. * Problem: some skipped tests are now running Solution: remove pytest mark to not run them * Problem: fastquery is not related to votes Solution: remove it in another PR
This commit is contained in:
parent
67c4ce964a
commit
7449b026fa
@ -6,8 +6,6 @@ Attributes:
|
|||||||
* ``backlog`` for incoming transactions awaiting to be put into
|
* ``backlog`` for incoming transactions awaiting to be put into
|
||||||
a block.
|
a block.
|
||||||
* ``bigchain`` for blocks.
|
* ``bigchain`` for blocks.
|
||||||
* ``votes`` to store votes for each block by each federation
|
|
||||||
node.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -21,7 +19,7 @@ from bigchaindb.common.utils import validate_all_values_for_key
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
TABLES = ('bigchain', 'backlog', 'votes', 'assets', 'metadata')
|
TABLES = ('bigchain', 'backlog', 'assets', 'metadata')
|
||||||
VALID_LANGUAGES = ('danish', 'dutch', 'english', 'finnish', 'french', 'german',
|
VALID_LANGUAGES = ('danish', 'dutch', 'english', 'finnish', 'french', 'german',
|
||||||
'hungarian', 'italian', 'norwegian', 'portuguese', 'romanian',
|
'hungarian', 'italian', 'norwegian', 'portuguese', 'romanian',
|
||||||
'russian', 'spanish', 'swedish', 'turkish', 'none',
|
'russian', 'spanish', 'swedish', 'turkish', 'none',
|
||||||
|
@ -66,14 +66,6 @@ class InvalidSignature(ValidationError):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ImproperVoteError(ValidationError):
|
|
||||||
"""Raised if a vote is not constructed correctly, or signed incorrectly"""
|
|
||||||
|
|
||||||
|
|
||||||
class MultipleVotesError(ValidationError):
|
|
||||||
"""Raised if a voter has voted more than once"""
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionNotInValidBlock(ValidationError):
|
class TransactionNotInValidBlock(ValidationError):
|
||||||
"""Raised when a transfer transaction is attempting to fulfill the
|
"""Raised when a transfer transaction is attempting to fulfill the
|
||||||
outputs of a transaction that is in an invalid or undecided block
|
outputs of a transaction that is in an invalid or undecided block
|
||||||
@ -96,10 +88,6 @@ class TransactionOwnerError(ValidationError):
|
|||||||
"""Raised if a user tries to transfer a transaction they don't own"""
|
"""Raised if a user tries to transfer a transaction they don't own"""
|
||||||
|
|
||||||
|
|
||||||
class SybilError(ValidationError):
|
|
||||||
"""If a block or vote comes from an unidentifiable node"""
|
|
||||||
|
|
||||||
|
|
||||||
class DuplicateTransaction(ValidationError):
|
class DuplicateTransaction(ValidationError):
|
||||||
"""Raised if a duplicated transaction is found"""
|
"""Raised if a duplicated transaction is found"""
|
||||||
|
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
---
|
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#"
|
|
||||||
id: "http://www.bigchaindb.com/schema/vote.json"
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
title: Vote Schema
|
|
||||||
required:
|
|
||||||
- node_pubkey
|
|
||||||
- signature
|
|
||||||
- vote
|
|
||||||
properties:
|
|
||||||
node_pubkey:
|
|
||||||
type: "string"
|
|
||||||
pattern: "[1-9a-zA-Z^OIl]{43,44}"
|
|
||||||
signature:
|
|
||||||
type: "string"
|
|
||||||
pattern: "[1-9a-zA-Z^OIl]{86,88}"
|
|
||||||
vote:
|
|
||||||
type: "object"
|
|
||||||
additionalProperties: false
|
|
||||||
required:
|
|
||||||
- invalid_reason
|
|
||||||
- is_block_valid
|
|
||||||
- previous_block
|
|
||||||
- voting_for_block
|
|
||||||
- timestamp
|
|
||||||
properties:
|
|
||||||
previous_block:
|
|
||||||
"$ref": "#/definitions/sha3_hexdigest"
|
|
||||||
voting_for_block:
|
|
||||||
"$ref": "#/definitions/sha3_hexdigest"
|
|
||||||
is_block_valid:
|
|
||||||
type: "boolean"
|
|
||||||
invalid_reason:
|
|
||||||
anyOf:
|
|
||||||
- type: "string"
|
|
||||||
- type: "null"
|
|
||||||
timestamp:
|
|
||||||
type: "string"
|
|
||||||
pattern: "[0-9]{10}"
|
|
||||||
definitions:
|
|
||||||
sha3_hexdigest:
|
|
||||||
pattern: "[0-9a-f]{64}"
|
|
||||||
type: string
|
|
@ -1,4 +1,3 @@
|
|||||||
from bigchaindb.voting import Voting
|
|
||||||
|
|
||||||
|
|
||||||
class BaseConsensusRules():
|
class BaseConsensusRules():
|
||||||
@ -10,8 +9,6 @@ class BaseConsensusRules():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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`
|
||||||
|
@ -14,7 +14,7 @@ class Bigchain(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
BLOCK_INVALID = 'invalid'
|
BLOCK_INVALID = 'invalid'
|
||||||
"""return if a block has been voted invalid"""
|
"""return if a block is invalid"""
|
||||||
|
|
||||||
BLOCK_VALID = TX_VALID = 'valid'
|
BLOCK_VALID = TX_VALID = 'valid'
|
||||||
"""return if a block is valid, or tx is in valid block"""
|
"""return if a block is valid, or tx is in valid block"""
|
||||||
@ -372,11 +372,7 @@ class Bigchain(object):
|
|||||||
self.write_metadata(metadatas)
|
self.write_metadata(metadatas)
|
||||||
|
|
||||||
# write the block
|
# write the block
|
||||||
return backend.query.write_block(self.connection, block_dict)
|
return backend.query.store_block(self.connection, block_dict)
|
||||||
|
|
||||||
def write_vote(self, vote):
|
|
||||||
"""Write the vote to the database."""
|
|
||||||
return backend.query.write_vote(self.connection, vote)
|
|
||||||
|
|
||||||
def get_assets(self, asset_ids):
|
def get_assets(self, asset_ids):
|
||||||
"""Return a list of assets that match the asset_ids
|
"""Return a list of assets that match the asset_ids
|
||||||
@ -409,7 +405,7 @@ class Bigchain(object):
|
|||||||
assets (:obj:`list` of :obj:`dict`): A list of assets to write to
|
assets (:obj:`list` of :obj:`dict`): A list of assets to write to
|
||||||
the database.
|
the database.
|
||||||
"""
|
"""
|
||||||
return backend.query.write_assets(self.connection, assets)
|
return backend.query.store_assets(self.connection, assets)
|
||||||
|
|
||||||
def write_metadata(self, metadata):
|
def write_metadata(self, metadata):
|
||||||
"""Writes a list of metadata into the database.
|
"""Writes a list of metadata into the database.
|
||||||
|
@ -8,7 +8,3 @@ class CriticalDoubleSpend(BigchainDBError):
|
|||||||
|
|
||||||
class CriticalDoubleInclusion(BigchainDBError):
|
class CriticalDoubleInclusion(BigchainDBError):
|
||||||
"""Data integrity error that requires attention"""
|
"""Data integrity error that requires attention"""
|
||||||
|
|
||||||
|
|
||||||
class CriticalDuplicateVote(BigchainDBError):
|
|
||||||
"""Data integrity error that requires attention"""
|
|
||||||
|
@ -158,7 +158,7 @@ class Transaction(Transaction):
|
|||||||
|
|
||||||
# TODO: Remove node_pubkey as part of cleanup II
|
# TODO: Remove node_pubkey as part of cleanup II
|
||||||
class Block(object):
|
class Block(object):
|
||||||
"""Bundle a list of Transactions in a Block. Nodes vote on its validity.
|
"""Bundle a list of Transactions in a Block.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
transaction (:obj:`list` of :class:`~.Transaction`):
|
transaction (:obj:`list` of :class:`~.Transaction`):
|
||||||
@ -326,7 +326,7 @@ class Block(object):
|
|||||||
signature = block_body.get('signature')
|
signature = block_body.get('signature')
|
||||||
|
|
||||||
return cls(transactions, block['node_pubkey'],
|
return cls(transactions, block['node_pubkey'],
|
||||||
block['timestamp'], [], signature)
|
block['timestamp'], signature)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self):
|
def id(self):
|
||||||
|
@ -1,128 +0,0 @@
|
|||||||
import collections
|
|
||||||
|
|
||||||
from bigchaindb.exceptions import CriticalDuplicateVote
|
|
||||||
from bigchaindb.common.utils import serialize
|
|
||||||
from bigchaindb.common.crypto import PublicKey
|
|
||||||
|
|
||||||
|
|
||||||
VALID = 'valid'
|
|
||||||
INVALID = 'invalid'
|
|
||||||
UNDECIDED = 'undecided'
|
|
||||||
|
|
||||||
|
|
||||||
class Voting:
|
|
||||||
"""Everything to do with verifying and counting votes for block election.
|
|
||||||
|
|
||||||
All functions in this class should be referentially transparent, that is,
|
|
||||||
they always give the same output for a given input. This makes it easier
|
|
||||||
to test. This also means no logging!
|
|
||||||
|
|
||||||
Assumptions regarding data:
|
|
||||||
* Vote is a dictionary, but no assumptions are made on it's properties.
|
|
||||||
* Everything else is assumed to be structurally correct, otherwise errors
|
|
||||||
may be thrown.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def block_election(cls, block, votes):
|
|
||||||
"""Calculate the election status of a block."""
|
|
||||||
eligible_voters = set(block['block']['voters'])
|
|
||||||
n_voters = len(eligible_voters)
|
|
||||||
eligible_votes, ineligible_votes = \
|
|
||||||
cls.partition_eligible_votes(votes, eligible_voters)
|
|
||||||
by_voter = cls.dedupe_by_voter(eligible_votes)
|
|
||||||
results = cls.count_votes(by_voter)
|
|
||||||
results['block_id'] = block['id']
|
|
||||||
results['status'] = cls.decide_votes(n_voters, **results['counts'])
|
|
||||||
results['ineligible'] = ineligible_votes
|
|
||||||
return results
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def partition_eligible_votes(cls, votes, eligible_voters):
|
|
||||||
"""Filter votes from unknown nodes or nodes that are not listed on
|
|
||||||
block. This is the primary Sybill protection.
|
|
||||||
"""
|
|
||||||
eligible, ineligible = ([], [])
|
|
||||||
|
|
||||||
for vote in votes:
|
|
||||||
voter_eligible = vote.get('node_pubkey') in eligible_voters
|
|
||||||
if voter_eligible:
|
|
||||||
try:
|
|
||||||
if cls.verify_vote_signature(vote):
|
|
||||||
eligible.append(vote)
|
|
||||||
continue
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
ineligible.append(vote)
|
|
||||||
return eligible, ineligible
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def dedupe_by_voter(cls, eligible_votes):
|
|
||||||
"""Throw a critical error if there is a duplicate vote
|
|
||||||
"""
|
|
||||||
by_voter = {}
|
|
||||||
for vote in eligible_votes:
|
|
||||||
pubkey = vote['node_pubkey']
|
|
||||||
if pubkey in by_voter:
|
|
||||||
raise CriticalDuplicateVote(pubkey)
|
|
||||||
by_voter[pubkey] = vote
|
|
||||||
return by_voter
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def count_votes(cls, by_voter):
|
|
||||||
"""Given a list of eligible votes, (votes from known nodes that are listed
|
|
||||||
as voters), produce the number that say valid and the number that say
|
|
||||||
invalid. Votes must agree on previous block, otherwise they become invalid.
|
|
||||||
"""
|
|
||||||
prev_blocks = collections.Counter()
|
|
||||||
malformed = []
|
|
||||||
|
|
||||||
for vote in by_voter.values():
|
|
||||||
if vote['vote']['is_block_valid'] is True:
|
|
||||||
prev_blocks[vote['vote']['previous_block']] += 1
|
|
||||||
|
|
||||||
n_valid = 0
|
|
||||||
prev_block = None
|
|
||||||
# Valid votes must agree on previous block
|
|
||||||
if prev_blocks:
|
|
||||||
prev_block, n_valid = prev_blocks.most_common()[0]
|
|
||||||
del prev_blocks[prev_block]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'counts': {
|
|
||||||
'n_valid': n_valid,
|
|
||||||
'n_invalid': len(by_voter) - n_valid,
|
|
||||||
},
|
|
||||||
'malformed': malformed,
|
|
||||||
'previous_block': prev_block,
|
|
||||||
'other_previous_block': dict(prev_blocks),
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def decide_votes(cls, n_voters, n_valid, n_invalid):
|
|
||||||
"""Decide on votes.
|
|
||||||
|
|
||||||
To return VALID there must be a clear majority that say VALID
|
|
||||||
and also agree on the previous block.
|
|
||||||
|
|
||||||
A tie on an even number of votes counts as INVALID.
|
|
||||||
"""
|
|
||||||
if n_invalid * 2 >= n_voters:
|
|
||||||
return INVALID
|
|
||||||
if n_valid * 2 > n_voters:
|
|
||||||
return VALID
|
|
||||||
return UNDECIDED
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def verify_vote_signature(cls, vote):
|
|
||||||
"""Verify the signature of a vote
|
|
||||||
"""
|
|
||||||
signature = vote.get('signature')
|
|
||||||
pk_base58 = vote.get('node_pubkey')
|
|
||||||
|
|
||||||
if not (type(signature) == str and type(pk_base58) == str):
|
|
||||||
raise ValueError('Malformed vote: %s' % vote)
|
|
||||||
|
|
||||||
public_key = PublicKey(pk_base58)
|
|
||||||
body = serialize(vote['vote']).encode()
|
|
||||||
return public_key.verify(body, signature)
|
|
@ -7,7 +7,6 @@ from bigchaindb.web.views import (
|
|||||||
info,
|
info,
|
||||||
transactions as tx,
|
transactions as tx,
|
||||||
outputs,
|
outputs,
|
||||||
votes,
|
|
||||||
validators,
|
validators,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,7 +33,6 @@ ROUTES_API_V1 = [
|
|||||||
r('transactions/<string:tx_id>', tx.TransactionApi),
|
r('transactions/<string:tx_id>', tx.TransactionApi),
|
||||||
r('transactions', tx.TransactionListApi),
|
r('transactions', tx.TransactionListApi),
|
||||||
r('outputs/', outputs.OutputListApi),
|
r('outputs/', outputs.OutputListApi),
|
||||||
r('votes/', votes.VotesApi),
|
|
||||||
r('validators/', validators.ValidatorsApi),
|
r('validators/', validators.ValidatorsApi),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
"""This module provides the blueprint for the votes API endpoints.
|
|
||||||
|
|
||||||
For more information please refer to the documentation: http://bigchaindb.com/http-api
|
|
||||||
|
|
||||||
We might bring back a votes API endpoint in the future, see:
|
|
||||||
https://github.com/bigchaindb/bigchaindb/issues/2037
|
|
||||||
"""
|
|
||||||
|
|
||||||
from flask import jsonify
|
|
||||||
from flask_restful import Resource
|
|
||||||
# from flask import current_app
|
|
||||||
# from flask_restful import Resource, reqparse
|
|
||||||
|
|
||||||
# from bigchaindb import backend
|
|
||||||
|
|
||||||
|
|
||||||
class VotesApi(Resource):
|
|
||||||
def get(self):
|
|
||||||
"""API endpoint to get details about votes.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
404 Not Found
|
|
||||||
"""
|
|
||||||
# parser = reqparse.RequestParser()
|
|
||||||
# parser.add_argument('block_id', type=str, required=True)
|
|
||||||
|
|
||||||
# args = parser.parse_args(strict=True)
|
|
||||||
|
|
||||||
# pool = current_app.config['bigchain_pool']
|
|
||||||
# with pool() as bigchain:
|
|
||||||
# votes = list(backend.query.get_votes_by_block_id(bigchain.connection, args['block_id']))
|
|
||||||
|
|
||||||
# return votes
|
|
||||||
|
|
||||||
# Return an HTTP status code 404 Not Found, which means:
|
|
||||||
# The requested resource could not be found but may be available in the future.
|
|
||||||
|
|
||||||
gone = 'The votes endpoint is gone now, but it might return in the future.'
|
|
||||||
response = jsonify({'message': gone})
|
|
||||||
response.status_code = 404
|
|
||||||
|
|
||||||
return response
|
|
@ -312,21 +312,6 @@ def double_spend_tx(signed_create_tx, carol_pubkey, user_sk):
|
|||||||
return tx.sign([user_sk])
|
return tx.sign([user_sk])
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def structurally_valid_vote():
|
|
||||||
return {
|
|
||||||
'node_pubkey': 'c' * 44,
|
|
||||||
'signature': 'd' * 86,
|
|
||||||
'vote': {
|
|
||||||
'voting_for_block': 'a' * 64,
|
|
||||||
'previous_block': 'b' * 64,
|
|
||||||
'is_block_valid': False,
|
|
||||||
'invalid_reason': None,
|
|
||||||
'timestamp': '1111111111'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _get_height(b):
|
def _get_height(b):
|
||||||
maybe_block = b.get_latest_block()
|
maybe_block = b.get_latest_block()
|
||||||
return 0 if maybe_block is None else maybe_block['height']
|
return 0 if maybe_block is None else maybe_block['height']
|
||||||
|
@ -10,16 +10,12 @@ class TestBlockModel(object):
|
|||||||
|
|
||||||
block = Block()
|
block = Block()
|
||||||
assert block.transactions == []
|
assert block.transactions == []
|
||||||
assert block.voters == []
|
|
||||||
assert block.timestamp == '1'
|
assert block.timestamp == '1'
|
||||||
assert block.node_pubkey is None
|
assert block.node_pubkey is None
|
||||||
assert block.signature is None
|
assert block.signature is None
|
||||||
|
|
||||||
with raises(TypeError):
|
with raises(TypeError):
|
||||||
Block('not a list or None')
|
Block('not a list or None')
|
||||||
with raises(TypeError):
|
|
||||||
Block(None, 'valid node_pubkey', 'valid timestamp',
|
|
||||||
'not a list or None')
|
|
||||||
|
|
||||||
def test_block_serialization(self, b, alice):
|
def test_block_serialization(self, b, alice):
|
||||||
from bigchaindb.common.crypto import hash_data
|
from bigchaindb.common.crypto import hash_data
|
||||||
@ -28,12 +24,10 @@ class TestBlockModel(object):
|
|||||||
|
|
||||||
transactions = [Transaction.create([alice.public_key], [([alice.public_key], 1)])]
|
transactions = [Transaction.create([alice.public_key], [([alice.public_key], 1)])]
|
||||||
timestamp = gen_timestamp()
|
timestamp = gen_timestamp()
|
||||||
voters = ['Qaaa', 'Qbbb']
|
|
||||||
expected_block = {
|
expected_block = {
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'transactions': [tx.to_dict() for tx in transactions],
|
'transactions': [tx.to_dict() for tx in transactions],
|
||||||
'node_pubkey': alice.public_key,
|
'node_pubkey': alice.public_key,
|
||||||
'voters': voters,
|
|
||||||
}
|
}
|
||||||
expected = {
|
expected = {
|
||||||
'id': hash_data(serialize(expected_block)),
|
'id': hash_data(serialize(expected_block)),
|
||||||
@ -41,7 +35,7 @@ class TestBlockModel(object):
|
|||||||
'signature': None,
|
'signature': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
block = Block(transactions, alice.public_key, timestamp, voters)
|
block = Block(transactions, alice.public_key, timestamp)
|
||||||
|
|
||||||
assert block.to_dict() == expected
|
assert block.to_dict() == expected
|
||||||
|
|
||||||
@ -60,14 +54,12 @@ class TestBlockModel(object):
|
|||||||
transaction = Transaction.create([alice.public_key], [([alice.public_key], 1)])
|
transaction = Transaction.create([alice.public_key], [([alice.public_key], 1)])
|
||||||
transaction.sign([alice.private_key])
|
transaction.sign([alice.private_key])
|
||||||
timestamp = gen_timestamp()
|
timestamp = gen_timestamp()
|
||||||
voters = ['Qaaa', 'Qbbb']
|
expected = Block([transaction], alice.public_key, timestamp)
|
||||||
expected = Block([transaction], alice.public_key, timestamp, voters)
|
|
||||||
|
|
||||||
block = {
|
block = {
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'transactions': [transaction.to_dict()],
|
'transactions': [transaction.to_dict()],
|
||||||
'node_pubkey': alice.public_key,
|
'node_pubkey': alice.public_key,
|
||||||
'voters': voters,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block_body = {
|
block_body = {
|
||||||
@ -106,7 +98,6 @@ class TestBlockModel(object):
|
|||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'transactions': [transaction.to_dict()],
|
'transactions': [transaction.to_dict()],
|
||||||
'node_pubkey': alice.public_key,
|
'node_pubkey': alice.public_key,
|
||||||
'voters': list(b.federation),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block_body = {
|
block_body = {
|
||||||
@ -133,16 +124,14 @@ class TestBlockModel(object):
|
|||||||
|
|
||||||
transactions = [Transaction.create([alice.public_key], [([alice.public_key], 1)])]
|
transactions = [Transaction.create([alice.public_key], [([alice.public_key], 1)])]
|
||||||
timestamp = gen_timestamp()
|
timestamp = gen_timestamp()
|
||||||
voters = ['Qaaa', 'Qbbb']
|
|
||||||
expected_block = {
|
expected_block = {
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'transactions': [tx.to_dict() for tx in transactions],
|
'transactions': [tx.to_dict() for tx in transactions],
|
||||||
'node_pubkey': alice.public_key,
|
'node_pubkey': alice.public_key,
|
||||||
'voters': voters,
|
|
||||||
}
|
}
|
||||||
expected_block_serialized = serialize(expected_block).encode()
|
expected_block_serialized = serialize(expected_block).encode()
|
||||||
expected = PrivateKey(alice.private_key).sign(expected_block_serialized)
|
expected = PrivateKey(alice.private_key).sign(expected_block_serialized)
|
||||||
block = Block(transactions, alice.public_key, timestamp, voters)
|
block = Block(transactions, alice.public_key, timestamp)
|
||||||
block = block.sign(alice.private_key)
|
block = block.sign(alice.private_key)
|
||||||
assert block.signature == expected.decode()
|
assert block.signature == expected.decode()
|
||||||
|
|
||||||
@ -150,12 +139,15 @@ class TestBlockModel(object):
|
|||||||
assert public_key.verify(expected_block_serialized, block.signature)
|
assert public_key.verify(expected_block_serialized, block.signature)
|
||||||
|
|
||||||
def test_block_dupe_tx(self, b, alice):
|
def test_block_dupe_tx(self, b, alice):
|
||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Block, Transaction
|
||||||
from bigchaindb.common.exceptions import DuplicateTransaction
|
from bigchaindb.common.exceptions import DuplicateTransaction
|
||||||
|
|
||||||
tx = Transaction.create([alice.public_key], [([alice.public_key], 1)])
|
tx = Transaction.create([alice.public_key], [([alice.public_key], 1)])
|
||||||
block = b.create_block([tx, tx])
|
block = Block([tx, tx], alice.public_key)
|
||||||
|
block.sign(alice.private_key)
|
||||||
|
b.store_block(block.to_dict())
|
||||||
with raises(DuplicateTransaction):
|
with raises(DuplicateTransaction):
|
||||||
block._validate_block(b)
|
block.validate(b)
|
||||||
|
|
||||||
def test_decouple_assets(self, b, alice):
|
def test_decouple_assets(self, b, alice):
|
||||||
from bigchaindb.models import Block, Transaction
|
from bigchaindb.models import Block, Transaction
|
||||||
|
@ -1,137 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from bigchaindb.core import Bigchain
|
|
||||||
from bigchaindb.voting import Voting, INVALID, VALID, UNDECIDED
|
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Tests for checking vote eligibility
|
|
||||||
|
|
||||||
|
|
||||||
def test_partition_eligible_votes():
|
|
||||||
class TestVoting(Voting):
|
|
||||||
@classmethod
|
|
||||||
def verify_vote_signature(cls, vote):
|
|
||||||
if vote['node_pubkey'] == 'invalid sig':
|
|
||||||
return False
|
|
||||||
if vote['node_pubkey'] == 'value error':
|
|
||||||
raise ValueError()
|
|
||||||
return True
|
|
||||||
|
|
||||||
voters = ['valid', 'invalid sig', 'value error', 'not in set']
|
|
||||||
votes = [{'node_pubkey': k} for k in voters]
|
|
||||||
|
|
||||||
el, inel = TestVoting.partition_eligible_votes(votes, voters[:-1])
|
|
||||||
assert el == [votes[0]]
|
|
||||||
assert inel == votes[1:]
|
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Test vote counting
|
|
||||||
|
|
||||||
|
|
||||||
def test_count_votes():
|
|
||||||
class TestVoting(Voting):
|
|
||||||
@classmethod
|
|
||||||
def verify_vote_schema(cls, vote):
|
|
||||||
return vote['node_pubkey'] != 'malformed'
|
|
||||||
|
|
||||||
voters = (['says invalid', 'malformed'] +
|
|
||||||
['kosher' + str(i) for i in range(10)])
|
|
||||||
|
|
||||||
votes = [Bigchain(v).vote('block', 'a', True) for v in voters]
|
|
||||||
votes[0]['vote']['is_block_valid'] = False
|
|
||||||
# Incorrect previous block subtracts from n_valid and adds to n_invalid
|
|
||||||
votes[-1]['vote']['previous_block'] = 'z'
|
|
||||||
|
|
||||||
by_voter = dict(enumerate(votes))
|
|
||||||
|
|
||||||
assert TestVoting.count_votes(by_voter) == {
|
|
||||||
'counts': {
|
|
||||||
'n_valid': 9, # 9 kosher votes
|
|
||||||
'n_invalid': 3, # 1 invalid, 1 malformed, 1 rogue prev block
|
|
||||||
},
|
|
||||||
'malformed': [votes[1]],
|
|
||||||
'previous_block': 'a',
|
|
||||||
'other_previous_block': {'z': 1},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_must_agree_prev_block():
|
|
||||||
class TestVoting(Voting):
|
|
||||||
@classmethod
|
|
||||||
def verify_vote_schema(cls, vote):
|
|
||||||
return True
|
|
||||||
|
|
||||||
voters = 'abcd'
|
|
||||||
votes = [Bigchain(v).vote('block', 'a', True) for v in voters]
|
|
||||||
votes[0]['vote']['previous_block'] = 'b'
|
|
||||||
votes[1]['vote']['previous_block'] = 'c'
|
|
||||||
by_voter = dict(enumerate(votes))
|
|
||||||
assert TestVoting.count_votes(by_voter) == {
|
|
||||||
'counts': {
|
|
||||||
'n_valid': 2,
|
|
||||||
'n_invalid': 2,
|
|
||||||
},
|
|
||||||
'previous_block': 'a',
|
|
||||||
'other_previous_block': {'b': 1, 'c': 1},
|
|
||||||
'malformed': [],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Tests for vote decision making
|
|
||||||
|
|
||||||
|
|
||||||
DECISION_TESTS = [
|
|
||||||
{'n_voters': 1, 'n_valid': 1, 'n_invalid': 1},
|
|
||||||
{'n_voters': 2, 'n_valid': 2, 'n_invalid': 1},
|
|
||||||
{'n_voters': 3, 'n_valid': 2, 'n_invalid': 2},
|
|
||||||
{'n_voters': 4, 'n_valid': 3, 'n_invalid': 2},
|
|
||||||
{'n_voters': 5, 'n_valid': 3, 'n_invalid': 3},
|
|
||||||
{'n_voters': 6, 'n_valid': 4, 'n_invalid': 3},
|
|
||||||
{'n_voters': 7, 'n_valid': 4, 'n_invalid': 4},
|
|
||||||
{'n_voters': 8, 'n_valid': 5, 'n_invalid': 4}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('kwargs', DECISION_TESTS)
|
|
||||||
def test_decide_votes_valid(kwargs):
|
|
||||||
kwargs = kwargs.copy()
|
|
||||||
kwargs['n_invalid'] = 0
|
|
||||||
assert Voting.decide_votes(**kwargs) == VALID
|
|
||||||
kwargs['n_valid'] -= 1
|
|
||||||
assert Voting.decide_votes(**kwargs) == UNDECIDED
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('kwargs', DECISION_TESTS)
|
|
||||||
def test_decide_votes_invalid(kwargs):
|
|
||||||
kwargs = kwargs.copy()
|
|
||||||
kwargs['n_valid'] = 0
|
|
||||||
assert Voting.decide_votes(**kwargs) == INVALID
|
|
||||||
kwargs['n_invalid'] -= 1
|
|
||||||
assert Voting.decide_votes(**kwargs) == UNDECIDED
|
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Tests for vote signature
|
|
||||||
|
|
||||||
|
|
||||||
def test_verify_vote_signature_passes(b):
|
|
||||||
vote = b.vote('block', 'a', True)
|
|
||||||
assert Voting.verify_vote_signature(vote)
|
|
||||||
vote['signature'] = ''
|
|
||||||
assert not Voting.verify_vote_signature(vote)
|
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Tests for vote schema
|
|
||||||
|
|
||||||
|
|
||||||
def test_verify_vote_schema(b):
|
|
||||||
vote = b.vote('b' * 64, 'a' * 64, True)
|
|
||||||
assert Voting.verify_vote_schema(vote)
|
|
||||||
vote = b.vote('b' * 64, 'a', True)
|
|
||||||
assert not Voting.verify_vote_schema(vote)
|
|
||||||
vote = b.vote('b', 'a' * 64, True)
|
|
||||||
assert not Voting.verify_vote_schema(vote)
|
|
@ -86,10 +86,6 @@ def test_get_divisble_transactions_returns_500(b, client):
|
|||||||
block = b.create_block(tx_list)
|
block = b.create_block(tx_list)
|
||||||
b.write_block(block)
|
b.write_block(block)
|
||||||
|
|
||||||
# vote the block valid
|
|
||||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
|
||||||
b.write_vote(vote)
|
|
||||||
|
|
||||||
alice_priv, alice_pub = crypto.generate_key_pair()
|
alice_priv, alice_pub = crypto.generate_key_pair()
|
||||||
bob_priv, bob_pub = crypto.generate_key_pair()
|
bob_priv, bob_pub = crypto.generate_key_pair()
|
||||||
carly_priv, carly_pub = crypto.generate_key_pair()
|
carly_priv, carly_pub = crypto.generate_key_pair()
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
# from bigchaindb.models import Transaction
|
|
||||||
|
|
||||||
VOTES_ENDPOINT = '/api/v1/votes'
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.tendermint
|
|
||||||
def test_get_votes_endpoint(client):
|
|
||||||
gone = 'The votes endpoint is gone now, but it might return in the future.'
|
|
||||||
response = {'message': gone}
|
|
||||||
|
|
||||||
res = client.get(VOTES_ENDPOINT)
|
|
||||||
assert response == res.json
|
|
||||||
assert res.status_code == 404
|
|
||||||
|
|
||||||
res = client.get(VOTES_ENDPOINT + '?block_id=')
|
|
||||||
assert response == res.json
|
|
||||||
assert res.status_code == 404
|
|
||||||
|
|
||||||
res = client.get(VOTES_ENDPOINT + '?block_id=123')
|
|
||||||
assert response == res.json
|
|
||||||
assert res.status_code == 404
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Old tests are below. We're keeping their code in a long comment block for now,
|
|
||||||
# because we might bring back a votes endpoint in the future.
|
|
||||||
# https://github.com/bigchaindb/bigchaindb/issues/2037
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
|
||||||
@pytest.mark.usefixtures('inputs')
|
|
||||||
def test_get_votes_endpoint(b, client, alice):
|
|
||||||
tx = Transaction.create([alice.public_key], [([alice.public_key], 1)])
|
|
||||||
tx = tx.sign([alice.private_key])
|
|
||||||
|
|
||||||
block = b.create_block([tx])
|
|
||||||
b.write_block(block)
|
|
||||||
|
|
||||||
# vote the block valid
|
|
||||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
|
||||||
b.write_vote(vote)
|
|
||||||
|
|
||||||
res = client.get(VOTES_ENDPOINT + '?block_id=' + block.id)
|
|
||||||
assert vote == res.json[0]
|
|
||||||
assert len(res.json) == 1
|
|
||||||
assert res.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
|
||||||
@pytest.mark.usefixtures('inputs')
|
|
||||||
def test_get_votes_endpoint_multiple_votes(b, client):
|
|
||||||
from bigchaindb.common.crypto import generate_key_pair
|
|
||||||
|
|
||||||
tx = Transaction.create([alice.public_key], [([alice.public_key], 1)])
|
|
||||||
tx = tx.sign([alice.private_key])
|
|
||||||
|
|
||||||
block = b.create_block([tx])
|
|
||||||
b.write_block(block)
|
|
||||||
last_block = b.get_last_voted_block().id
|
|
||||||
# vote the block valid
|
|
||||||
vote_valid = b.vote(block.id, last_block, True)
|
|
||||||
b.write_vote(vote_valid)
|
|
||||||
|
|
||||||
# vote the block invalid
|
|
||||||
# a note can only vote once so we need a new node_pubkey for the second
|
|
||||||
# vote
|
|
||||||
_, pk = generate_key_pair()
|
|
||||||
vote_invalid = b.vote(block.id, last_block, False)
|
|
||||||
vote_invalid['node_pubkey'] = pk
|
|
||||||
b.write_vote(vote_invalid)
|
|
||||||
|
|
||||||
res = client.get(VOTES_ENDPOINT + '?block_id=' + block.id)
|
|
||||||
assert len(res.json) == 2
|
|
||||||
assert res.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
|
||||||
def test_get_votes_endpoint_returns_empty_list_not_found(client):
|
|
||||||
res = client.get(VOTES_ENDPOINT + '?block_id=')
|
|
||||||
assert [] == res.json
|
|
||||||
assert res.status_code == 200
|
|
||||||
|
|
||||||
res = client.get(VOTES_ENDPOINT + '?block_id=123')
|
|
||||||
assert [] == res.json
|
|
||||||
assert res.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
|
||||||
def test_get_votes_endpoint_returns_400_bad_query_params(client):
|
|
||||||
res = client.get(VOTES_ENDPOINT)
|
|
||||||
assert res.status_code == 400
|
|
||||||
|
|
||||||
res = client.get(VOTES_ENDPOINT + '?ts_id=123')
|
|
||||||
assert res.status_code == 400
|
|
||||||
|
|
||||||
res = client.get(VOTES_ENDPOINT + '?tx_id=123&block_id=123')
|
|
||||||
assert res.status_code == 400
|
|
||||||
"""
|
|
Loading…
x
Reference in New Issue
Block a user