mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Create abstract election class (#2498).
* Problem: `ValidatorElection` and `MigrationElection` need to inherit from a common `Election` class. Solution: Factored the common logic out of `ValidatorElection` and moved it to `Election` parent class. * Problem: No need to store different types of elections in their own tables Solution: Remove `DB_TABLE` property from `Election` class. Solution: Created the `elections` table with secondary_index `election_id`. * Problem: `UpsertValidatorVote` can be generalized to just be `Vote` Solution: Renamed, refactored and moved the `Vote` class to tie in with the more general `Election` base class. * Problem: `election_id` is not unique if two elections have the same properties. Solution: Added a random `uuid4` seed to enforce uniqueness.
This commit is contained in:
parent
c79848d66a
commit
0fe749d830
@ -94,9 +94,9 @@ _config = copy.deepcopy(config)
|
|||||||
from bigchaindb.common.transaction import Transaction # noqa
|
from bigchaindb.common.transaction import Transaction # noqa
|
||||||
from bigchaindb import models # noqa
|
from bigchaindb import models # noqa
|
||||||
from bigchaindb.upsert_validator import ValidatorElection # noqa
|
from bigchaindb.upsert_validator import ValidatorElection # noqa
|
||||||
from bigchaindb.upsert_validator import ValidatorElectionVote # noqa
|
from bigchaindb.elections.vote import Vote # noqa
|
||||||
|
|
||||||
Transaction.register_type(Transaction.CREATE, models.Transaction)
|
Transaction.register_type(Transaction.CREATE, models.Transaction)
|
||||||
Transaction.register_type(Transaction.TRANSFER, models.Transaction)
|
Transaction.register_type(Transaction.TRANSFER, models.Transaction)
|
||||||
Transaction.register_type(ValidatorElection.VALIDATOR_ELECTION, ValidatorElection)
|
Transaction.register_type(ValidatorElection.OPERATION, ValidatorElection)
|
||||||
Transaction.register_type(ValidatorElectionVote.VALIDATOR_ELECTION_VOTE, ValidatorElectionVote)
|
Transaction.register_type(Vote.OPERATION, Vote)
|
||||||
|
@ -281,6 +281,18 @@ def store_validator_set(conn, validators_update):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_election_results(conn, election):
|
||||||
|
height = election['height']
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('elections').replace_one(
|
||||||
|
{'height': height},
|
||||||
|
election,
|
||||||
|
upsert=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_validator_set(conn, height=None):
|
def get_validator_set(conn, height=None):
|
||||||
query = {}
|
query = {}
|
||||||
@ -298,11 +310,11 @@ def get_validator_set(conn, height=None):
|
|||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_validator_set_by_election_id(conn, election_id):
|
def get_election(conn, election_id):
|
||||||
query = {'election_id': election_id}
|
query = {'election_id': election_id}
|
||||||
|
|
||||||
cursor = conn.run(
|
cursor = conn.run(
|
||||||
conn.collection('validators')
|
conn.collection('elections')
|
||||||
.find(query, projection={'_id': False})
|
.find(query, projection={'_id': False})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ def create_indexes(conn, dbname):
|
|||||||
create_pre_commit_secondary_index(conn, dbname)
|
create_pre_commit_secondary_index(conn, dbname)
|
||||||
create_validators_secondary_index(conn, dbname)
|
create_validators_secondary_index(conn, dbname)
|
||||||
create_abci_chains_indexes(conn, dbname)
|
create_abci_chains_indexes(conn, dbname)
|
||||||
|
create_elections_secondary_index(conn, dbname)
|
||||||
|
|
||||||
|
|
||||||
@register_schema(LocalMongoDBConnection)
|
@register_schema(LocalMongoDBConnection)
|
||||||
@ -144,6 +145,15 @@ def create_abci_chains_indexes(conn, dbname):
|
|||||||
unique=True,)
|
unique=True,)
|
||||||
|
|
||||||
logger.info('Create `abci_chains.chain_id` secondary index.')
|
logger.info('Create `abci_chains.chain_id` secondary index.')
|
||||||
|
|
||||||
conn.conn[dbname]['abci_chains'].create_index('chain_id',
|
conn.conn[dbname]['abci_chains'].create_index('chain_id',
|
||||||
name='chain_id',
|
name='chain_id',
|
||||||
unique=True)
|
unique=True)
|
||||||
|
|
||||||
|
|
||||||
|
def create_elections_secondary_index(conn, dbname):
|
||||||
|
logger.info('Create `elections` secondary index.')
|
||||||
|
|
||||||
|
conn.conn[dbname]['elections'].create_index('election_id',
|
||||||
|
name='election_id',
|
||||||
|
unique=True,)
|
||||||
|
@ -351,6 +351,13 @@ def store_validator_set(conn, validator_update):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@singledispatch
|
||||||
|
def store_election_results(conn, election):
|
||||||
|
"""Store election results"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
def get_validator_set(conn, height):
|
def get_validator_set(conn, height):
|
||||||
"""Get validator set for a given `height`, if `height` is not specified
|
"""Get validator set for a given `height`, if `height` is not specified
|
||||||
@ -361,7 +368,7 @@ def get_validator_set(conn, height):
|
|||||||
|
|
||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
def get_validator_set_by_election_id(conn, election_id):
|
def get_election(conn, election_id):
|
||||||
"""Return a validator set change with the specified election_id
|
"""Return a validator set change with the specified election_id
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -369,13 +376,11 @@ def get_validator_set_by_election_id(conn, election_id):
|
|||||||
|
|
||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
def get_asset_tokens_for_public_key(connection, asset_id,
|
def get_asset_tokens_for_public_key(connection, asset_id, public_key):
|
||||||
public_key, operation):
|
|
||||||
"""Retrieve a list of tokens of type `asset_id` that are owned by the `public_key`.
|
"""Retrieve a list of tokens of type `asset_id` that are owned by the `public_key`.
|
||||||
Args:
|
Args:
|
||||||
asset_id (str): Id of the token.
|
asset_id (str): Id of the token.
|
||||||
public_key (str): base58 encoded public key
|
public_key (str): base58 encoded public key
|
||||||
operation: filter transaction based on `operation`
|
|
||||||
Returns:
|
Returns:
|
||||||
Iterator of transaction that list given owner in conditions.
|
Iterator of transaction that list given owner in conditions.
|
||||||
"""
|
"""
|
||||||
|
@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# Tables/collections that every backend database must create
|
# Tables/collections that every backend database must create
|
||||||
TABLES = ('transactions', 'blocks', 'assets', 'metadata',
|
TABLES = ('transactions', 'blocks', 'assets', 'metadata',
|
||||||
'validators', 'pre_commit', 'utxos', 'abci_chains')
|
'validators', 'elections', 'pre_commit', 'utxos', 'abci_chains')
|
||||||
|
|
||||||
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',
|
||||||
|
@ -17,9 +17,10 @@ from bigchaindb.utils import load_node_key
|
|||||||
from bigchaindb.common.exceptions import (DatabaseAlreadyExists,
|
from bigchaindb.common.exceptions import (DatabaseAlreadyExists,
|
||||||
DatabaseDoesNotExist,
|
DatabaseDoesNotExist,
|
||||||
ValidationError)
|
ValidationError)
|
||||||
|
from bigchaindb.elections.vote import Vote
|
||||||
import bigchaindb
|
import bigchaindb
|
||||||
from bigchaindb import (backend, ValidatorElection,
|
from bigchaindb import (backend, ValidatorElection,
|
||||||
BigchainDB, ValidatorElectionVote)
|
BigchainDB)
|
||||||
from bigchaindb.backend import schema
|
from bigchaindb.backend import schema
|
||||||
from bigchaindb.backend import query
|
from bigchaindb.backend import query
|
||||||
from bigchaindb.backend.query import PRE_COMMIT_ID
|
from bigchaindb.backend.query import PRE_COMMIT_ID
|
||||||
@ -176,9 +177,9 @@ def run_upsert_validator_approve(args, bigchain):
|
|||||||
|
|
||||||
inputs = [i for i in tx.to_inputs() if key.public_key in i.owners_before]
|
inputs = [i for i in tx.to_inputs() if key.public_key in i.owners_before]
|
||||||
election_pub_key = ValidatorElection.to_public_key(tx.id)
|
election_pub_key = ValidatorElection.to_public_key(tx.id)
|
||||||
approval = ValidatorElectionVote.generate(inputs,
|
approval = Vote.generate(inputs,
|
||||||
[([election_pub_key], voting_power)],
|
[([election_pub_key], voting_power)],
|
||||||
tx.id).sign([key.private_key])
|
tx.id).sign([key.private_key])
|
||||||
approval.validate(bigchain)
|
approval.validate(bigchain)
|
||||||
|
|
||||||
resp = bigchain.write_transaction(approval, 'broadcast_tx_commit')
|
resp = bigchain.write_transaction(approval, 'broadcast_tx_commit')
|
||||||
|
@ -37,8 +37,7 @@ _, TX_SCHEMA_TRANSFER = _load_schema('transaction_transfer_' +
|
|||||||
_, TX_SCHEMA_VALIDATOR_ELECTION = _load_schema('transaction_validator_election_' +
|
_, TX_SCHEMA_VALIDATOR_ELECTION = _load_schema('transaction_validator_election_' +
|
||||||
TX_SCHEMA_VERSION)
|
TX_SCHEMA_VERSION)
|
||||||
|
|
||||||
_, TX_SCHEMA_VALIDATOR_ELECTION_VOTE = _load_schema('transaction_validator_election_vote_' +
|
_, TX_SCHEMA_VOTE = _load_schema('transaction_vote_' + TX_SCHEMA_VERSION)
|
||||||
TX_SCHEMA_VERSION)
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_schema(schema, body):
|
def _validate_schema(schema, body):
|
||||||
|
@ -63,7 +63,7 @@ definitions:
|
|||||||
- CREATE
|
- CREATE
|
||||||
- TRANSFER
|
- TRANSFER
|
||||||
- VALIDATOR_ELECTION
|
- VALIDATOR_ELECTION
|
||||||
- VALIDATOR_ELECTION_VOTE
|
- VOTE
|
||||||
asset:
|
asset:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
@ -22,6 +22,8 @@ properties:
|
|||||||
properties:
|
properties:
|
||||||
node_id:
|
node_id:
|
||||||
type: string
|
type: string
|
||||||
|
seed:
|
||||||
|
type: string
|
||||||
public_key:
|
public_key:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
@ -5,14 +5,14 @@
|
|||||||
---
|
---
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#"
|
"$schema": "http://json-schema.org/draft-04/schema#"
|
||||||
type: object
|
type: object
|
||||||
title: Validator Election Vote Schema - Vote on a validator set change
|
title: Vote Schema - Vote on an election
|
||||||
required:
|
required:
|
||||||
- operation
|
- operation
|
||||||
- outputs
|
- outputs
|
||||||
properties:
|
properties:
|
||||||
operation:
|
operation:
|
||||||
type: string
|
type: string
|
||||||
value: "VALIDATOR_ELECTION_VOTE"
|
value: "VOTE"
|
||||||
outputs:
|
outputs:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
@ -100,7 +100,7 @@ class App(BaseApplication):
|
|||||||
|
|
||||||
block = Block(app_hash=app_hash, height=height, transactions=[])
|
block = Block(app_hash=app_hash, height=height, transactions=[])
|
||||||
self.bigchaindb.store_block(block._asdict())
|
self.bigchaindb.store_block(block._asdict())
|
||||||
self.bigchaindb.store_validator_set(height + 1, validator_set, None)
|
self.bigchaindb.store_validator_set(height + 1, validator_set)
|
||||||
abci_chain_height = 0 if known_chain is None else known_chain['height']
|
abci_chain_height = 0 if known_chain is None else known_chain['height']
|
||||||
self.bigchaindb.store_abci_chain(abci_chain_height,
|
self.bigchaindb.store_abci_chain(abci_chain_height,
|
||||||
genesis.chain_id, True)
|
genesis.chain_id, True)
|
||||||
@ -209,9 +209,10 @@ class App(BaseApplication):
|
|||||||
|
|
||||||
# Check if the current block concluded any validator elections and
|
# Check if the current block concluded any validator elections and
|
||||||
# update the locally tracked validator set
|
# update the locally tracked validator set
|
||||||
validator_updates = ValidatorElection.get_validator_update(self.bigchaindb,
|
validator_update = ValidatorElection.approved_update(self.bigchaindb,
|
||||||
self.new_height,
|
self.new_height,
|
||||||
self.block_transactions)
|
self.block_transactions)
|
||||||
|
update = [validator_update] if validator_update else []
|
||||||
|
|
||||||
# Store pre-commit state to recover in case there is a crash
|
# Store pre-commit state to recover in case there is a crash
|
||||||
# during `commit`
|
# during `commit`
|
||||||
@ -220,7 +221,7 @@ class App(BaseApplication):
|
|||||||
transactions=self.block_txn_ids)
|
transactions=self.block_txn_ids)
|
||||||
logger.debug('Updating PreCommitState: %s', self.new_height)
|
logger.debug('Updating PreCommitState: %s', self.new_height)
|
||||||
self.bigchaindb.store_pre_commit_state(pre_commit_state._asdict())
|
self.bigchaindb.store_pre_commit_state(pre_commit_state._asdict())
|
||||||
return ResponseEndBlock(validator_updates=validator_updates)
|
return ResponseEndBlock(validator_updates=update)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
"""Store the new height and along with block hash."""
|
"""Store the new height and along with block hash."""
|
||||||
|
0
bigchaindb/elections/__init__.py
Normal file
0
bigchaindb/elections/__init__.py
Normal file
254
bigchaindb/elections/election.py
Normal file
254
bigchaindb/elections/election.py
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
# Copyright BigchainDB GmbH and BigchainDB contributors
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
|
import base58
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from bigchaindb import backend
|
||||||
|
from bigchaindb.elections.vote import Vote
|
||||||
|
from bigchaindb.common.exceptions import (InvalidSignature,
|
||||||
|
MultipleInputsError,
|
||||||
|
InvalidProposer,
|
||||||
|
UnequalValidatorSet,
|
||||||
|
DuplicateTransaction)
|
||||||
|
from bigchaindb.tendermint_utils import key_from_base64
|
||||||
|
from bigchaindb.common.crypto import (public_key_from_ed25519_key)
|
||||||
|
from bigchaindb.common.transaction import Transaction
|
||||||
|
from bigchaindb.common.schema import (_validate_schema,
|
||||||
|
TX_SCHEMA_COMMON,
|
||||||
|
TX_SCHEMA_CREATE)
|
||||||
|
|
||||||
|
|
||||||
|
class Election(Transaction):
|
||||||
|
|
||||||
|
# NOTE: this transaction class extends create so the operation inheritance is achieved
|
||||||
|
# by setting an ELECTION_TYPE and renaming CREATE = ELECTION_TYPE and ALLOWED_OPERATIONS = (ELECTION_TYPE,)
|
||||||
|
OPERATION = None
|
||||||
|
# Custom validation schema
|
||||||
|
TX_SCHEMA_CUSTOM = None
|
||||||
|
# Election Statuses:
|
||||||
|
ONGOING = 'ongoing'
|
||||||
|
CONCLUDED = 'concluded'
|
||||||
|
INCONCLUSIVE = 'inconclusive'
|
||||||
|
# Vote ratio to approve an election
|
||||||
|
ELECTION_THRESHOLD = 2 / 3
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_validator_change(cls, bigchain, height=None):
|
||||||
|
"""Return the latest change to the validator set
|
||||||
|
|
||||||
|
:return: {
|
||||||
|
'height': <block_height>,
|
||||||
|
'validators': <validator_set>,
|
||||||
|
'election_id': <election_id_that_approved_the_change>
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
return bigchain.get_validator_change(height)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_validators(cls, bigchain, height=None):
|
||||||
|
"""Return a dictionary of validators with key as `public_key` and
|
||||||
|
value as the `voting_power`
|
||||||
|
"""
|
||||||
|
validators = {}
|
||||||
|
for validator in bigchain.get_validators(height):
|
||||||
|
# NOTE: we assume that Tendermint encodes public key in base64
|
||||||
|
public_key = public_key_from_ed25519_key(key_from_base64(validator['public_key']['value']))
|
||||||
|
validators[public_key] = validator['voting_power']
|
||||||
|
|
||||||
|
return validators
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def recipients(cls, bigchain):
|
||||||
|
"""Convert validator dictionary to a recipient list for `Transaction`"""
|
||||||
|
|
||||||
|
recipients = []
|
||||||
|
for public_key, voting_power in cls.get_validators(bigchain).items():
|
||||||
|
recipients.append(([public_key], voting_power))
|
||||||
|
|
||||||
|
return recipients
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_same_topology(cls, current_topology, election_topology):
|
||||||
|
voters = {}
|
||||||
|
for voter in election_topology:
|
||||||
|
if len(voter.public_keys) > 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
[public_key] = voter.public_keys
|
||||||
|
voting_power = voter.amount
|
||||||
|
voters[public_key] = voting_power
|
||||||
|
|
||||||
|
# Check whether the voters and their votes is same to that of the
|
||||||
|
# validators and their voting power in the network
|
||||||
|
return current_topology == voters
|
||||||
|
|
||||||
|
def validate(self, bigchain, current_transactions=[]):
|
||||||
|
"""Validate election transaction
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
* A valid election is initiated by an existing validator.
|
||||||
|
|
||||||
|
* A valid election is one where voters are validators and votes are
|
||||||
|
allocated according to the voting power of each validator node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
:param bigchain: (BigchainDB) an instantiated bigchaindb.lib.BigchainDB object.
|
||||||
|
:param current_transactions: (list) A list of transactions to be validated along with the election
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Election: a Election object or an object of the derived Election subclass.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValidationError: If the election is invalid
|
||||||
|
"""
|
||||||
|
input_conditions = []
|
||||||
|
|
||||||
|
duplicates = any(txn for txn in current_transactions if txn.id == self.id)
|
||||||
|
if bigchain.is_committed(self.id) or duplicates:
|
||||||
|
raise DuplicateTransaction('transaction `{}` already exists'
|
||||||
|
.format(self.id))
|
||||||
|
|
||||||
|
if not self.inputs_valid(input_conditions):
|
||||||
|
raise InvalidSignature('Transaction signature is invalid.')
|
||||||
|
|
||||||
|
current_validators = self.get_validators(bigchain)
|
||||||
|
|
||||||
|
# NOTE: Proposer should be a single node
|
||||||
|
if len(self.inputs) != 1 or len(self.inputs[0].owners_before) != 1:
|
||||||
|
raise MultipleInputsError('`tx_signers` must be a list instance of length one')
|
||||||
|
|
||||||
|
# NOTE: Check if the proposer is a validator.
|
||||||
|
[election_initiator_node_pub_key] = self.inputs[0].owners_before
|
||||||
|
if election_initiator_node_pub_key not in current_validators.keys():
|
||||||
|
raise InvalidProposer('Public key is not a part of the validator set')
|
||||||
|
|
||||||
|
# NOTE: Check if all validators have been assigned votes equal to their voting power
|
||||||
|
if not self.is_same_topology(current_validators, self.outputs):
|
||||||
|
raise UnequalValidatorSet('Validator set much be exactly same to the outputs of election')
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate(cls, initiator, voters, election_data, metadata=None):
|
||||||
|
# Break symmetry in case we need to call an election with the same properties twice
|
||||||
|
uuid = uuid4()
|
||||||
|
election_data['seed'] = str(uuid)
|
||||||
|
|
||||||
|
(inputs, outputs) = cls.validate_create(initiator, voters, election_data, metadata)
|
||||||
|
election = cls(cls.OPERATION, {'data': election_data}, inputs, outputs, metadata)
|
||||||
|
cls.validate_schema(election.to_dict())
|
||||||
|
return election
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate_schema(cls, tx):
|
||||||
|
"""Validate the election transaction. Since `ELECTION` extends `CREATE` transaction, all the validations for
|
||||||
|
`CREATE` transaction should be inherited
|
||||||
|
"""
|
||||||
|
_validate_schema(TX_SCHEMA_COMMON, tx)
|
||||||
|
_validate_schema(TX_SCHEMA_CREATE, tx)
|
||||||
|
if cls.TX_SCHEMA_CUSTOM:
|
||||||
|
_validate_schema(cls.TX_SCHEMA_CUSTOM, tx)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, tx_signers, recipients, metadata=None, asset=None):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def transfer(cls, tx_signers, recipients, metadata=None, asset=None):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_public_key(cls, election_id):
|
||||||
|
return base58.b58encode(bytes.fromhex(election_id)).decode()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def count_votes(cls, election_pk, transactions, getter=getattr):
|
||||||
|
votes = 0
|
||||||
|
for txn in transactions:
|
||||||
|
if getter(txn, 'operation') == Vote.OPERATION:
|
||||||
|
for output in getter(txn, 'outputs'):
|
||||||
|
# NOTE: We enforce that a valid vote to election id will have only
|
||||||
|
# election_pk in the output public keys, including any other public key
|
||||||
|
# along with election_pk will lead to vote being not considered valid.
|
||||||
|
if len(getter(output, 'public_keys')) == 1 and [election_pk] == getter(output, 'public_keys'):
|
||||||
|
votes = votes + int(getter(output, 'amount'))
|
||||||
|
return votes
|
||||||
|
|
||||||
|
def get_commited_votes(self, bigchain, election_pk=None):
|
||||||
|
if election_pk is None:
|
||||||
|
election_pk = self.to_public_key(self.id)
|
||||||
|
txns = list(backend.query.get_asset_tokens_for_public_key(bigchain.connection,
|
||||||
|
self.id,
|
||||||
|
election_pk))
|
||||||
|
return self.count_votes(election_pk, txns, dict.get)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def has_concluded(cls, bigchain, election_id, current_votes=[], height=None):
|
||||||
|
"""Check if the given `election_id` can be concluded or not
|
||||||
|
NOTE:
|
||||||
|
* Election is concluded iff the current validator set is exactly equal
|
||||||
|
to the validator set encoded in election outputs
|
||||||
|
* Election can concluded only if the current votes achieves a supermajority
|
||||||
|
"""
|
||||||
|
election = bigchain.get_transaction(election_id)
|
||||||
|
|
||||||
|
if election:
|
||||||
|
election_pk = election.to_public_key(election.id)
|
||||||
|
votes_committed = election.get_commited_votes(bigchain, election_pk)
|
||||||
|
votes_current = election.count_votes(election_pk, current_votes)
|
||||||
|
current_validators = election.get_validators(bigchain, height)
|
||||||
|
|
||||||
|
if election.is_same_topology(current_validators, election.outputs):
|
||||||
|
total_votes = sum(current_validators.values())
|
||||||
|
if (votes_committed < (2/3)*total_votes) and \
|
||||||
|
(votes_committed + votes_current >= (2/3)*total_votes):
|
||||||
|
return election
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_status(self, bigchain):
|
||||||
|
concluded = self.get_election(self.id, bigchain)
|
||||||
|
if concluded:
|
||||||
|
return self.CONCLUDED
|
||||||
|
|
||||||
|
latest_change = self.get_validator_change(bigchain)
|
||||||
|
latest_change_height = latest_change['height']
|
||||||
|
election_height = bigchain.get_block_containing_tx(self.id)[0]
|
||||||
|
|
||||||
|
if latest_change_height >= election_height:
|
||||||
|
return self.INCONCLUSIVE
|
||||||
|
else:
|
||||||
|
return self.ONGOING
|
||||||
|
|
||||||
|
def get_election(self, election_id, bigchain):
|
||||||
|
result = bigchain.get_election(election_id)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def store_election_results(cls, bigchain, election, height):
|
||||||
|
bigchain.store_election_results(height, election)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def approved_update(cls, bigchain, new_height, txns):
|
||||||
|
votes = {}
|
||||||
|
for txn in txns:
|
||||||
|
if not isinstance(txn, Vote):
|
||||||
|
continue
|
||||||
|
|
||||||
|
election_id = txn.asset['id']
|
||||||
|
election_votes = votes.get(election_id, [])
|
||||||
|
election_votes.append(txn)
|
||||||
|
votes[election_id] = election_votes
|
||||||
|
|
||||||
|
election = cls.has_concluded(bigchain, election_id, election_votes, new_height)
|
||||||
|
# Once an election concludes any other conclusion for the same
|
||||||
|
# or any other election is invalidated
|
||||||
|
if election:
|
||||||
|
cls.store_election_results(bigchain, election, new_height)
|
||||||
|
return cls.on_approval(bigchain, election, new_height)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def on_approval(cls, bigchain, election, new_height):
|
||||||
|
raise NotImplementedError
|
@ -6,16 +6,18 @@ from bigchaindb.common.transaction import Transaction
|
|||||||
from bigchaindb.common.schema import (_validate_schema,
|
from bigchaindb.common.schema import (_validate_schema,
|
||||||
TX_SCHEMA_COMMON,
|
TX_SCHEMA_COMMON,
|
||||||
TX_SCHEMA_TRANSFER,
|
TX_SCHEMA_TRANSFER,
|
||||||
TX_SCHEMA_VALIDATOR_ELECTION_VOTE)
|
TX_SCHEMA_VOTE)
|
||||||
|
|
||||||
|
|
||||||
class ValidatorElectionVote(Transaction):
|
class Vote(Transaction):
|
||||||
|
|
||||||
VALIDATOR_ELECTION_VOTE = 'VALIDATOR_ELECTION_VOTE'
|
OPERATION = 'VOTE'
|
||||||
# NOTE: This class inherits TRANSFER txn type. The `TRANSFER` property is
|
# NOTE: This class inherits TRANSFER txn type. The `TRANSFER` property is
|
||||||
# overriden to re-use methods from parent class
|
# overriden to re-use methods from parent class
|
||||||
TRANSFER = VALIDATOR_ELECTION_VOTE
|
TRANSFER = OPERATION
|
||||||
ALLOWED_OPERATIONS = (VALIDATOR_ELECTION_VOTE,)
|
ALLOWED_OPERATIONS = (OPERATION,)
|
||||||
|
# Custom validation schema
|
||||||
|
TX_SCHEMA_CUSTOM = TX_SCHEMA_VOTE
|
||||||
|
|
||||||
def validate(self, bigchain, current_transactions=[]):
|
def validate(self, bigchain, current_transactions=[]):
|
||||||
"""Validate election vote transaction
|
"""Validate election vote transaction
|
||||||
@ -28,7 +30,7 @@ class ValidatorElectionVote(Transaction):
|
|||||||
bigchain (BigchainDB): an instantiated bigchaindb.lib.BigchainDB object.
|
bigchain (BigchainDB): an instantiated bigchaindb.lib.BigchainDB object.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ValidatorElectionVote object
|
Vote: a Vote object
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValidationError: If the election vote is invalid
|
ValidationError: If the election vote is invalid
|
||||||
@ -39,20 +41,20 @@ class ValidatorElectionVote(Transaction):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def generate(cls, inputs, recipients, election_id, metadata=None):
|
def generate(cls, inputs, recipients, election_id, metadata=None):
|
||||||
(inputs, outputs) = cls.validate_transfer(inputs, recipients, election_id, metadata)
|
(inputs, outputs) = cls.validate_transfer(inputs, recipients, election_id, metadata)
|
||||||
election_vote = cls(cls.VALIDATOR_ELECTION_VOTE, {'id': election_id}, inputs, outputs, metadata)
|
election_vote = cls(cls.OPERATION, {'id': election_id}, inputs, outputs, metadata)
|
||||||
cls.validate_schema(election_vote.to_dict(), skip_id=True)
|
cls.validate_schema(election_vote.to_dict(), skip_id=True)
|
||||||
return election_vote
|
return election_vote
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_schema(cls, tx, skip_id=False):
|
def validate_schema(cls, tx, skip_id=False):
|
||||||
"""Validate the validator election vote transaction. Since `VALIDATOR_ELECTION_VOTE` extends `TRANFER`
|
"""Validate the validator election vote transaction. Since `VOTE` extends `TRANSFER`
|
||||||
transaction, all the validations for `CREATE` transaction should be inherited
|
transaction, all the validations for `CREATE` transaction should be inherited
|
||||||
"""
|
"""
|
||||||
if not skip_id:
|
if not skip_id:
|
||||||
cls.validate_id(tx)
|
cls.validate_id(tx)
|
||||||
_validate_schema(TX_SCHEMA_COMMON, tx)
|
_validate_schema(TX_SCHEMA_COMMON, tx)
|
||||||
_validate_schema(TX_SCHEMA_TRANSFER, tx)
|
_validate_schema(TX_SCHEMA_TRANSFER, tx)
|
||||||
_validate_schema(TX_SCHEMA_VALIDATOR_ELECTION_VOTE, tx)
|
_validate_schema(cls.TX_SCHEMA_CUSTOM, tx)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, tx_signers, recipients, metadata=None, asset=None):
|
def create(cls, tx_signers, recipients, metadata=None, asset=None):
|
@ -428,24 +428,20 @@ class BigchainDB(object):
|
|||||||
result = self.get_validator_change(height)
|
result = self.get_validator_change(height)
|
||||||
return [] if result is None else result['validators']
|
return [] if result is None else result['validators']
|
||||||
|
|
||||||
def get_validators_by_election_id(self, election_id):
|
def get_election(self, election_id):
|
||||||
result = backend.query.get_validator_set_by_election_id(self.connection, election_id)
|
result = backend.query.get_election(self.connection, election_id)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def delete_validator_update(self):
|
|
||||||
return backend.query.delete_validator_update(self.connection)
|
|
||||||
|
|
||||||
def store_pre_commit_state(self, state):
|
def store_pre_commit_state(self, state):
|
||||||
return backend.query.store_pre_commit_state(self.connection, state)
|
return backend.query.store_pre_commit_state(self.connection, state)
|
||||||
|
|
||||||
def store_validator_set(self, height, validators, election_id):
|
def store_validator_set(self, height, validators):
|
||||||
"""Store validator set at a given `height`.
|
"""Store validator set at a given `height`.
|
||||||
NOTE: If the validator set already exists at that `height` then an
|
NOTE: If the validator set already exists at that `height` then an
|
||||||
exception will be raised.
|
exception will be raised.
|
||||||
"""
|
"""
|
||||||
return backend.query.store_validator_set(self.connection, {'height': height,
|
return backend.query.store_validator_set(self.connection, {'height': height,
|
||||||
'validators': validators,
|
'validators': validators})
|
||||||
'election_id': election_id})
|
|
||||||
|
|
||||||
def store_abci_chain(self, height, chain_id, is_synced=True):
|
def store_abci_chain(self, height, chain_id, is_synced=True):
|
||||||
return backend.query.store_abci_chain(self.connection, height,
|
return backend.query.store_abci_chain(self.connection, height,
|
||||||
@ -478,6 +474,14 @@ class BigchainDB(object):
|
|||||||
|
|
||||||
self.store_abci_chain(block['height'] + 1, new_chain_id, False)
|
self.store_abci_chain(block['height'] + 1, new_chain_id, False)
|
||||||
|
|
||||||
|
def store_election_results(self, height, election):
|
||||||
|
"""Store election results
|
||||||
|
:param height: the block height at which the election concluded
|
||||||
|
:param election: a concluded election
|
||||||
|
"""
|
||||||
|
return backend.query.store_election_results(self.connection, {'height': height,
|
||||||
|
'election_id': election.id})
|
||||||
|
|
||||||
|
|
||||||
Block = namedtuple('Block', ('app_hash', 'height', 'transactions'))
|
Block = namedtuple('Block', ('app_hash', 'height', 'transactions'))
|
||||||
|
|
||||||
|
@ -3,5 +3,4 @@
|
|||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
|
|
||||||
from bigchaindb.upsert_validator.validator_election_vote import ValidatorElectionVote # noqa
|
|
||||||
from bigchaindb.upsert_validator.validator_election import ValidatorElection # noqa
|
from bigchaindb.upsert_validator.validator_election import ValidatorElection # noqa
|
||||||
|
@ -2,264 +2,48 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
import base58
|
from bigchaindb.common.exceptions import InvalidPowerChange
|
||||||
|
from bigchaindb.elections.election import Election
|
||||||
from bigchaindb import backend
|
from bigchaindb.common.schema import (TX_SCHEMA_VALIDATOR_ELECTION)
|
||||||
from bigchaindb.common.exceptions import (InvalidSignature,
|
from .validator_utils import (new_validator_set, encode_validator, validate_asset_public_key)
|
||||||
MultipleInputsError,
|
|
||||||
InvalidProposer,
|
|
||||||
UnequalValidatorSet,
|
|
||||||
InvalidPowerChange,
|
|
||||||
DuplicateTransaction)
|
|
||||||
from bigchaindb.tendermint_utils import key_from_base64
|
|
||||||
from bigchaindb.common.crypto import (public_key_from_ed25519_key)
|
|
||||||
from bigchaindb.common.transaction import Transaction
|
|
||||||
from bigchaindb.common.schema import (_validate_schema,
|
|
||||||
TX_SCHEMA_VALIDATOR_ELECTION,
|
|
||||||
TX_SCHEMA_COMMON,
|
|
||||||
TX_SCHEMA_CREATE)
|
|
||||||
from . import ValidatorElectionVote
|
|
||||||
from .validator_utils import (new_validator_set,
|
|
||||||
encode_validator,
|
|
||||||
encode_pk_to_base16,
|
|
||||||
validate_asset_public_key)
|
|
||||||
|
|
||||||
|
|
||||||
class ValidatorElection(Transaction):
|
class ValidatorElection(Election):
|
||||||
|
|
||||||
VALIDATOR_ELECTION = 'VALIDATOR_ELECTION'
|
OPERATION = 'VALIDATOR_ELECTION'
|
||||||
# NOTE: this transaction class extends create so the operation inheritence is achieved
|
# NOTE: this transaction class extends create so the operation inheritence is achieved
|
||||||
# by renaming CREATE to VALIDATOR_ELECTION
|
# by renaming CREATE to VALIDATOR_ELECTION
|
||||||
CREATE = VALIDATOR_ELECTION
|
CREATE = OPERATION
|
||||||
ALLOWED_OPERATIONS = (VALIDATOR_ELECTION,)
|
ALLOWED_OPERATIONS = (OPERATION,)
|
||||||
# Election Statuses:
|
TX_SCHEMA_CUSTOM = TX_SCHEMA_VALIDATOR_ELECTION
|
||||||
ONGOING = 'ongoing'
|
|
||||||
CONCLUDED = 'concluded'
|
|
||||||
INCONCLUSIVE = 'inconclusive'
|
|
||||||
ELECTION_THRESHOLD = 2 / 3
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_validator_change(cls, bigchain, height=None):
|
|
||||||
"""Return the latest change to the validator set
|
|
||||||
|
|
||||||
:return: {
|
|
||||||
'height': <block_height>,
|
|
||||||
'asset': {
|
|
||||||
'height': <block_height>,
|
|
||||||
'validators': <validator_set>,
|
|
||||||
'election_id': <election_id_that_approved_the_change>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
return bigchain.get_validator_change(height)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_validators(cls, bigchain, height=None):
|
|
||||||
"""Return a dictionary of validators with key as `public_key` and
|
|
||||||
value as the `voting_power`
|
|
||||||
"""
|
|
||||||
validators = {}
|
|
||||||
for validator in bigchain.get_validators(height):
|
|
||||||
# NOTE: we assume that Tendermint encodes public key in base64
|
|
||||||
public_key = public_key_from_ed25519_key(key_from_base64(validator['public_key']['value']))
|
|
||||||
validators[public_key] = validator['voting_power']
|
|
||||||
|
|
||||||
return validators
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def recipients(cls, bigchain):
|
|
||||||
"""Convert validator dictionary to a recipient list for `Transaction`"""
|
|
||||||
|
|
||||||
recipients = []
|
|
||||||
for public_key, voting_power in cls.get_validators(bigchain).items():
|
|
||||||
recipients.append(([public_key], voting_power))
|
|
||||||
|
|
||||||
return recipients
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_same_topology(cls, current_topology, election_topology):
|
|
||||||
voters = {}
|
|
||||||
for voter in election_topology:
|
|
||||||
if len(voter.public_keys) > 1:
|
|
||||||
return False
|
|
||||||
|
|
||||||
[public_key] = voter.public_keys
|
|
||||||
voting_power = voter.amount
|
|
||||||
voters[public_key] = voting_power
|
|
||||||
|
|
||||||
# Check whether the voters and their votes is same to that of the
|
|
||||||
# validators and their voting power in the network
|
|
||||||
return (current_topology == voters)
|
|
||||||
|
|
||||||
def validate(self, bigchain, current_transactions=[]):
|
def validate(self, bigchain, current_transactions=[]):
|
||||||
"""Validate election transaction
|
"""For more details refer BEP-21: https://github.com/bigchaindb/BEPs/tree/master/21
|
||||||
For more details refer BEP-21: https://github.com/bigchaindb/BEPs/tree/master/21
|
|
||||||
|
|
||||||
NOTE:
|
|
||||||
* A valid election is initiated by an existing validator.
|
|
||||||
|
|
||||||
* A valid election is one where voters are validators and votes are
|
|
||||||
alloacted according to the voting power of each validator node.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
bigchain (BigchainDB): an instantiated bigchaindb.lib.BigchainDB object.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ValidatorElection object
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValidationError: If the election is invalid
|
|
||||||
"""
|
"""
|
||||||
input_conditions = []
|
|
||||||
|
|
||||||
duplicates = any(txn for txn in current_transactions if txn.id == self.id)
|
|
||||||
if bigchain.get_transaction(self.id) or duplicates:
|
|
||||||
raise DuplicateTransaction('transaction `{}` already exists'
|
|
||||||
.format(self.id))
|
|
||||||
|
|
||||||
if not self.inputs_valid(input_conditions):
|
|
||||||
raise InvalidSignature('Transaction signature is invalid.')
|
|
||||||
|
|
||||||
current_validators = self.get_validators(bigchain)
|
current_validators = self.get_validators(bigchain)
|
||||||
|
|
||||||
# NOTE: Proposer should be a single node
|
super(ValidatorElection, self).validate(bigchain, current_transactions=current_transactions)
|
||||||
if len(self.inputs) != 1 or len(self.inputs[0].owners_before) != 1:
|
|
||||||
raise MultipleInputsError('`tx_signers` must be a list instance of length one')
|
|
||||||
|
|
||||||
# NOTE: change more than 1/3 of the current power is not allowed
|
# NOTE: change more than 1/3 of the current power is not allowed
|
||||||
if self.asset['data']['power'] >= (1/3)*sum(current_validators.values()):
|
if self.asset['data']['power'] >= (1/3)*sum(current_validators.values()):
|
||||||
raise InvalidPowerChange('`power` change must be less than 1/3 of total power')
|
raise InvalidPowerChange('`power` change must be less than 1/3 of total power')
|
||||||
|
|
||||||
# NOTE: Check if the proposer is a validator.
|
|
||||||
[election_initiator_node_pub_key] = self.inputs[0].owners_before
|
|
||||||
if election_initiator_node_pub_key not in current_validators.keys():
|
|
||||||
raise InvalidProposer('Public key is not a part of the validator set')
|
|
||||||
|
|
||||||
# NOTE: Check if all validators have been assigned votes equal to their voting power
|
|
||||||
if not self.is_same_topology(current_validators, self.outputs):
|
|
||||||
raise UnequalValidatorSet('Validator set much be exactly same to the outputs of election')
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate(cls, initiator, voters, election_data, metadata=None):
|
def validate_schema(cls, tx):
|
||||||
(inputs, outputs) = cls.validate_create(initiator, voters, election_data, metadata)
|
super(ValidatorElection, cls).validate_schema(tx)
|
||||||
election = cls(cls.VALIDATOR_ELECTION, {'data': election_data}, inputs, outputs, metadata)
|
|
||||||
cls.validate_schema(election.to_dict(), skip_id=True)
|
|
||||||
return election
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate_schema(cls, tx, skip_id=False):
|
|
||||||
"""Validate the validator election transaction. Since `VALIDATOR_ELECTION` extends `CREATE`
|
|
||||||
transaction, all the validations for `CREATE` transaction should be inherited
|
|
||||||
"""
|
|
||||||
if not skip_id:
|
|
||||||
cls.validate_id(tx)
|
|
||||||
_validate_schema(TX_SCHEMA_COMMON, tx)
|
|
||||||
_validate_schema(TX_SCHEMA_CREATE, tx)
|
|
||||||
_validate_schema(TX_SCHEMA_VALIDATOR_ELECTION, tx)
|
|
||||||
validate_asset_public_key(tx['asset']['data']['public_key'])
|
validate_asset_public_key(tx['asset']['data']['public_key'])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, tx_signers, recipients, metadata=None, asset=None):
|
def on_approval(cls, bigchain, election, new_height):
|
||||||
raise NotImplementedError
|
# The new validator set comes into effect from height = new_height+1
|
||||||
|
validator_updates = [election.asset['data']]
|
||||||
|
curr_validator_set = bigchain.get_validators(new_height)
|
||||||
|
updated_validator_set = new_validator_set(curr_validator_set,
|
||||||
|
validator_updates)
|
||||||
|
|
||||||
@classmethod
|
updated_validator_set = [v for v in updated_validator_set if v['voting_power'] > 0]
|
||||||
def transfer(cls, tx_signers, recipients, metadata=None, asset=None):
|
bigchain.store_validator_set(new_height+1, updated_validator_set)
|
||||||
raise NotImplementedError
|
return encode_validator(election.asset['data'])
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def to_public_key(cls, election_id):
|
|
||||||
return base58.b58encode(bytes.fromhex(election_id)).decode()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def count_votes(cls, election_pk, transactions, getter=getattr):
|
|
||||||
votes = 0
|
|
||||||
for txn in transactions:
|
|
||||||
if getter(txn, 'operation') == 'VALIDATOR_ELECTION_VOTE':
|
|
||||||
for output in getter(txn, 'outputs'):
|
|
||||||
# NOTE: We enforce that a valid vote to election id will have only
|
|
||||||
# election_pk in the output public keys, including any other public key
|
|
||||||
# along with election_pk will lead to vote being not considered valid.
|
|
||||||
if len(getter(output, 'public_keys')) == 1 and [election_pk] == getter(output, 'public_keys'):
|
|
||||||
votes = votes + int(getter(output, 'amount'))
|
|
||||||
return votes
|
|
||||||
|
|
||||||
def get_commited_votes(self, bigchain, election_pk=None):
|
|
||||||
if election_pk is None:
|
|
||||||
election_pk = self.to_public_key(self.id)
|
|
||||||
txns = list(backend.query.get_asset_tokens_for_public_key(bigchain.connection,
|
|
||||||
self.id,
|
|
||||||
election_pk))
|
|
||||||
return self.count_votes(election_pk, txns, dict.get)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def has_concluded(cls, bigchain, election_id, current_votes=[], height=None):
|
|
||||||
"""Check if the given `election_id` can be concluded or not
|
|
||||||
NOTE:
|
|
||||||
* Election is concluded iff the current validator set is exactly equal
|
|
||||||
to the validator set encoded in election outputs
|
|
||||||
* Election can concluded only if the current votes achieves a supermajority
|
|
||||||
"""
|
|
||||||
election = bigchain.get_transaction(election_id)
|
|
||||||
|
|
||||||
if election:
|
|
||||||
election_pk = election.to_public_key(election.id)
|
|
||||||
votes_commited = election.get_commited_votes(bigchain, election_pk)
|
|
||||||
votes_current = election.count_votes(election_pk, current_votes)
|
|
||||||
current_validators = election.get_validators(bigchain, height)
|
|
||||||
|
|
||||||
if election.is_same_topology(current_validators, election.outputs):
|
|
||||||
total_votes = sum(current_validators.values())
|
|
||||||
if (votes_commited < (2/3)*total_votes) and \
|
|
||||||
(votes_commited + votes_current >= (2/3)*total_votes):
|
|
||||||
return election
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_validator_update(cls, bigchain, new_height, txns):
|
|
||||||
votes = {}
|
|
||||||
for txn in txns:
|
|
||||||
if not isinstance(txn, ValidatorElectionVote):
|
|
||||||
continue
|
|
||||||
|
|
||||||
election_id = txn.asset['id']
|
|
||||||
election_votes = votes.get(election_id, [])
|
|
||||||
election_votes.append(txn)
|
|
||||||
votes[election_id] = election_votes
|
|
||||||
|
|
||||||
election = cls.has_concluded(bigchain, election_id, election_votes, new_height)
|
|
||||||
# Once an election concludes any other conclusion for the same
|
|
||||||
# or any other election is invalidated
|
|
||||||
if election:
|
|
||||||
# The new validator set comes into effect from height = new_height+1
|
|
||||||
validator_updates = [election.asset['data']]
|
|
||||||
curr_validator_set = bigchain.get_validators(new_height)
|
|
||||||
updated_validator_set = new_validator_set(curr_validator_set,
|
|
||||||
validator_updates)
|
|
||||||
|
|
||||||
updated_validator_set = [v for v in updated_validator_set if v['voting_power'] > 0]
|
|
||||||
bigchain.store_validator_set(new_height+1, updated_validator_set, election.id)
|
|
||||||
|
|
||||||
validator16 = encode_pk_to_base16(election.asset['data'])
|
|
||||||
return [encode_validator(validator16)]
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
def get_validator_update_by_election_id(self, election_id, bigchain):
|
|
||||||
result = bigchain.get_validators_by_election_id(election_id)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_status(self, bigchain):
|
|
||||||
concluded = self.get_validator_update_by_election_id(self.id, bigchain)
|
|
||||||
if concluded:
|
|
||||||
return self.CONCLUDED
|
|
||||||
|
|
||||||
latest_change = self.get_validator_change(bigchain)
|
|
||||||
latest_change_height = latest_change['height']
|
|
||||||
election_height = bigchain.get_block_containing_tx(self.id)[0]
|
|
||||||
|
|
||||||
if latest_change_height >= election_height:
|
|
||||||
return self.INCONCLUSIVE
|
|
||||||
else:
|
|
||||||
return self.ONGOING
|
|
||||||
|
@ -21,7 +21,7 @@ def test_init_creates_db_tables_and_indexes():
|
|||||||
collection_names = conn.conn[dbname].collection_names()
|
collection_names = conn.conn[dbname].collection_names()
|
||||||
assert set(collection_names) == {
|
assert set(collection_names) == {
|
||||||
'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'pre_commit',
|
'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'pre_commit',
|
||||||
'validators', 'abci_chains',
|
'validators', 'elections', 'abci_chains',
|
||||||
}
|
}
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['assets'].index_information().keys()
|
indexes = conn.conn[dbname]['assets'].index_information().keys()
|
||||||
@ -46,6 +46,9 @@ def test_init_creates_db_tables_and_indexes():
|
|||||||
indexes = conn.conn[dbname]['abci_chains'].index_information().keys()
|
indexes = conn.conn[dbname]['abci_chains'].index_information().keys()
|
||||||
assert set(indexes) == {'_id_', 'height', 'chain_id'}
|
assert set(indexes) == {'_id_', 'height', 'chain_id'}
|
||||||
|
|
||||||
|
indexes = conn.conn[dbname]['elections'].index_information().keys()
|
||||||
|
assert set(indexes) == {'_id_', 'election_id'}
|
||||||
|
|
||||||
|
|
||||||
def test_init_database_fails_if_db_exists():
|
def test_init_database_fails_if_db_exists():
|
||||||
import bigchaindb
|
import bigchaindb
|
||||||
@ -78,7 +81,7 @@ def test_create_tables():
|
|||||||
|
|
||||||
collection_names = conn.conn[dbname].collection_names()
|
collection_names = conn.conn[dbname].collection_names()
|
||||||
assert set(collection_names) == {
|
assert set(collection_names) == {
|
||||||
'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'validators',
|
'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'validators', 'elections',
|
||||||
'pre_commit', 'abci_chains',
|
'pre_commit', 'abci_chains',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +120,10 @@ def test_create_secondary_indexes():
|
|||||||
assert index_info['utxo']['key'] == [('transaction_id', 1),
|
assert index_info['utxo']['key'] == [('transaction_id', 1),
|
||||||
('output_index', 1)]
|
('output_index', 1)]
|
||||||
|
|
||||||
|
indexes = conn.conn[dbname]['elections'].index_information()
|
||||||
|
assert set(indexes.keys()) == {'_id_', 'election_id'}
|
||||||
|
assert indexes['election_id']['unique']
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['pre_commit'].index_information()
|
indexes = conn.conn[dbname]['pre_commit'].index_information()
|
||||||
assert set(indexes.keys()) == {'_id_', 'pre_commit_id'}
|
assert set(indexes.keys()) == {'_id_', 'pre_commit_id'}
|
||||||
assert indexes['pre_commit_id']['unique']
|
assert indexes['pre_commit_id']['unique']
|
||||||
|
@ -366,8 +366,8 @@ def test_end_block_return_validator_updates(b, init_chain_request):
|
|||||||
resp = app.end_block(RequestEndBlock(height=99))
|
resp = app.end_block(RequestEndBlock(height=99))
|
||||||
assert resp.validator_updates[0] == encode_validator(validator)
|
assert resp.validator_updates[0] == encode_validator(validator)
|
||||||
|
|
||||||
updates = b.get_validator_update()
|
updates = b.approved_update()
|
||||||
assert updates == []
|
assert not updates
|
||||||
|
|
||||||
|
|
||||||
def test_store_pre_commit_state_in_end_block(b, alice, init_chain_request):
|
def test_store_pre_commit_state_in_end_block(b, alice, init_chain_request):
|
||||||
@ -427,7 +427,7 @@ def test_new_validator_set(b):
|
|||||||
|
|
||||||
validators = [node1]
|
validators = [node1]
|
||||||
updates = [node1_new_power, node2]
|
updates = [node1_new_power, node2]
|
||||||
b.store_validator_set(1, validators, 'election_id')
|
b.store_validator_set(1, validators)
|
||||||
updated_validator_set = new_validator_set(b.get_validators(1), updates)
|
updated_validator_set = new_validator_set(b.get_validators(1), updates)
|
||||||
|
|
||||||
updated_validators = []
|
updated_validators = []
|
||||||
|
@ -161,11 +161,11 @@ def test_validator_updates(b, validator_pub_key):
|
|||||||
'update_id': VALIDATOR_UPDATE_ID}
|
'update_id': VALIDATOR_UPDATE_ID}
|
||||||
query.store_validator_update(b.connection, validator_update)
|
query.store_validator_update(b.connection, validator_update)
|
||||||
|
|
||||||
updates = b.get_validator_update()
|
updates = b.approved_updates()
|
||||||
assert updates == [validator_update['validator']]
|
assert updates == validator_update['validator']
|
||||||
|
|
||||||
b.delete_validator_update()
|
b.delete_validator_update()
|
||||||
assert b.get_validator_update() == []
|
assert not b.approved_updates()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
@pytest.mark.bdb
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
# Copyright BigchainDB GmbH and BigchainDB contributors
|
# Copyright BigchainDB GmbH and BigchainDB contributors
|
||||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from bigchaindb import ValidatorElectionVote
|
from bigchaindb import Vote
|
||||||
from bigchaindb.backend.localmongodb import query
|
from bigchaindb.backend.localmongodb import query
|
||||||
from bigchaindb.lib import Block
|
from bigchaindb.lib import Block
|
||||||
from bigchaindb.upsert_validator import ValidatorElection
|
from bigchaindb.upsert_validator import ValidatorElection
|
||||||
@ -46,6 +47,15 @@ def valid_election_b(b, node_key, new_validator):
|
|||||||
new_validator, None).sign([node_key.private_key])
|
new_validator, None).sign([node_key.private_key])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
@patch('bigchaindb.elections.election.uuid4', lambda: 'mock_uuid4')
|
||||||
|
def fixed_seed_election(b_mock, node_key, new_validator):
|
||||||
|
voters = ValidatorElection.recipients(b_mock)
|
||||||
|
return ValidatorElection.generate([node_key.public_key],
|
||||||
|
voters,
|
||||||
|
new_validator, None).sign([node_key.private_key])
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def ongoing_election(b, valid_election, ed25519_node_keys):
|
def ongoing_election(b, valid_election, ed25519_node_keys):
|
||||||
validators = b.get_validators(height=1)
|
validators = b.get_validators(height=1)
|
||||||
@ -62,12 +72,10 @@ def ongoing_election(b, valid_election, ed25519_node_keys):
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def concluded_election(b, ongoing_election, ed25519_node_keys):
|
def concluded_election(b, ongoing_election, ed25519_node_keys):
|
||||||
validators = b.get_validators(height=1)
|
election_result = {'height': 2,
|
||||||
validator_update = {'validators': validators,
|
'election_id': ongoing_election.id}
|
||||||
'height': 2,
|
|
||||||
'election_id': ongoing_election.id}
|
|
||||||
|
|
||||||
query.store_validator_set(b.connection, validator_update)
|
query.store_election_results(b.connection, election_result)
|
||||||
return ongoing_election
|
return ongoing_election
|
||||||
|
|
||||||
|
|
||||||
@ -91,9 +99,9 @@ def vote(election, voter, keys, b):
|
|||||||
|
|
||||||
election_pub_key = ValidatorElection.to_public_key(election.id)
|
election_pub_key = ValidatorElection.to_public_key(election.id)
|
||||||
|
|
||||||
v = ValidatorElectionVote.generate([election_input],
|
v = Vote.generate([election_input],
|
||||||
[([election_pub_key], votes)],
|
[([election_pub_key], votes)],
|
||||||
election_id=election.id)\
|
election_id=election.id)\
|
||||||
.sign([key.private_key])
|
.sign([key.private_key])
|
||||||
b.store_bulk_transactions([v])
|
b.store_bulk_transactions([v])
|
||||||
return v
|
return v
|
||||||
|
@ -6,10 +6,11 @@ import pytest
|
|||||||
import codecs
|
import codecs
|
||||||
|
|
||||||
from bigchaindb.tendermint_utils import public_key_to_base64
|
from bigchaindb.tendermint_utils import public_key_to_base64
|
||||||
from bigchaindb.upsert_validator import ValidatorElection, ValidatorElectionVote
|
from bigchaindb.upsert_validator import ValidatorElection
|
||||||
from bigchaindb.common.exceptions import AmountError
|
from bigchaindb.common.exceptions import AmountError
|
||||||
from bigchaindb.common.crypto import generate_key_pair
|
from bigchaindb.common.crypto import generate_key_pair
|
||||||
from bigchaindb.common.exceptions import ValidationError
|
from bigchaindb.common.exceptions import ValidationError
|
||||||
|
from bigchaindb.elections.vote import Vote
|
||||||
from tests.utils import generate_block
|
from tests.utils import generate_block
|
||||||
|
|
||||||
pytestmark = [pytest.mark.execute]
|
pytestmark = [pytest.mark.execute]
|
||||||
@ -26,10 +27,10 @@ def test_upsert_validator_valid_election_vote(b_mock, valid_election, ed25519_no
|
|||||||
|
|
||||||
election_pub_key = ValidatorElection.to_public_key(valid_election.id)
|
election_pub_key = ValidatorElection.to_public_key(valid_election.id)
|
||||||
|
|
||||||
vote = ValidatorElectionVote.generate([input0],
|
vote = Vote.generate([input0],
|
||||||
[([election_pub_key], votes)],
|
[([election_pub_key], votes)],
|
||||||
election_id=valid_election.id)\
|
election_id=valid_election.id)\
|
||||||
.sign([key0.private_key])
|
.sign([key0.private_key])
|
||||||
assert vote.validate(b_mock)
|
assert vote.validate(b_mock)
|
||||||
|
|
||||||
|
|
||||||
@ -46,10 +47,10 @@ def test_upsert_validator_valid_non_election_vote(b_mock, valid_election, ed2551
|
|||||||
|
|
||||||
# Ensure that threshold conditions are now allowed
|
# Ensure that threshold conditions are now allowed
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
ValidatorElectionVote.generate([input0],
|
Vote.generate([input0],
|
||||||
[([election_pub_key, key0.public_key], votes)],
|
[([election_pub_key, key0.public_key], votes)],
|
||||||
election_id=valid_election.id)\
|
election_id=valid_election.id)\
|
||||||
.sign([key0.private_key])
|
.sign([key0.private_key])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
@pytest.mark.bdb
|
||||||
@ -63,10 +64,10 @@ def test_upsert_validator_delegate_election_vote(b_mock, valid_election, ed25519
|
|||||||
public_key0 = input0.owners_before[0]
|
public_key0 = input0.owners_before[0]
|
||||||
key0 = ed25519_node_keys[public_key0]
|
key0 = ed25519_node_keys[public_key0]
|
||||||
|
|
||||||
delegate_vote = ValidatorElectionVote.generate([input0],
|
delegate_vote = Vote.generate([input0],
|
||||||
[([alice.public_key], 3), ([key0.public_key], votes-3)],
|
[([alice.public_key], 3), ([key0.public_key], votes-3)],
|
||||||
election_id=valid_election.id)\
|
election_id=valid_election.id)\
|
||||||
.sign([key0.private_key])
|
.sign([key0.private_key])
|
||||||
|
|
||||||
assert delegate_vote.validate(b_mock)
|
assert delegate_vote.validate(b_mock)
|
||||||
|
|
||||||
@ -74,17 +75,17 @@ def test_upsert_validator_delegate_election_vote(b_mock, valid_election, ed25519
|
|||||||
election_pub_key = ValidatorElection.to_public_key(valid_election.id)
|
election_pub_key = ValidatorElection.to_public_key(valid_election.id)
|
||||||
|
|
||||||
alice_votes = delegate_vote.to_inputs()[0]
|
alice_votes = delegate_vote.to_inputs()[0]
|
||||||
alice_casted_vote = ValidatorElectionVote.generate([alice_votes],
|
alice_casted_vote = Vote.generate([alice_votes],
|
||||||
[([election_pub_key], 3)],
|
[([election_pub_key], 3)],
|
||||||
election_id=valid_election.id)\
|
election_id=valid_election.id)\
|
||||||
.sign([alice.private_key])
|
.sign([alice.private_key])
|
||||||
assert alice_casted_vote.validate(b_mock)
|
assert alice_casted_vote.validate(b_mock)
|
||||||
|
|
||||||
key0_votes = delegate_vote.to_inputs()[1]
|
key0_votes = delegate_vote.to_inputs()[1]
|
||||||
key0_casted_vote = ValidatorElectionVote.generate([key0_votes],
|
key0_casted_vote = Vote.generate([key0_votes],
|
||||||
[([election_pub_key], votes-3)],
|
[([election_pub_key], votes-3)],
|
||||||
election_id=valid_election.id)\
|
election_id=valid_election.id)\
|
||||||
.sign([key0.private_key])
|
.sign([key0.private_key])
|
||||||
assert key0_casted_vote.validate(b_mock)
|
assert key0_casted_vote.validate(b_mock)
|
||||||
|
|
||||||
|
|
||||||
@ -99,10 +100,10 @@ def test_upsert_validator_invalid_election_vote(b_mock, valid_election, ed25519_
|
|||||||
|
|
||||||
election_pub_key = ValidatorElection.to_public_key(valid_election.id)
|
election_pub_key = ValidatorElection.to_public_key(valid_election.id)
|
||||||
|
|
||||||
vote = ValidatorElectionVote.generate([input0],
|
vote = Vote.generate([input0],
|
||||||
[([election_pub_key], votes+1)],
|
[([election_pub_key], votes+1)],
|
||||||
election_id=valid_election.id)\
|
election_id=valid_election.id)\
|
||||||
.sign([key0.private_key])
|
.sign([key0.private_key])
|
||||||
|
|
||||||
with pytest.raises(AmountError):
|
with pytest.raises(AmountError):
|
||||||
assert vote.validate(b_mock)
|
assert vote.validate(b_mock)
|
||||||
@ -120,10 +121,10 @@ def test_valid_election_votes_received(b_mock, valid_election, ed25519_node_keys
|
|||||||
key0 = ed25519_node_keys[public_key0]
|
key0 = ed25519_node_keys[public_key0]
|
||||||
|
|
||||||
# delegate some votes to alice
|
# delegate some votes to alice
|
||||||
delegate_vote = ValidatorElectionVote.generate([input0],
|
delegate_vote = Vote.generate([input0],
|
||||||
[([alice.public_key], 4), ([key0.public_key], votes-4)],
|
[([alice.public_key], 4), ([key0.public_key], votes-4)],
|
||||||
election_id=valid_election.id)\
|
election_id=valid_election.id)\
|
||||||
.sign([key0.private_key])
|
.sign([key0.private_key])
|
||||||
b_mock.store_bulk_transactions([delegate_vote])
|
b_mock.store_bulk_transactions([delegate_vote])
|
||||||
assert valid_election.get_commited_votes(b_mock) == 0
|
assert valid_election.get_commited_votes(b_mock) == 0
|
||||||
|
|
||||||
@ -131,10 +132,10 @@ def test_valid_election_votes_received(b_mock, valid_election, ed25519_node_keys
|
|||||||
alice_votes = delegate_vote.to_inputs()[0]
|
alice_votes = delegate_vote.to_inputs()[0]
|
||||||
key0_votes = delegate_vote.to_inputs()[1]
|
key0_votes = delegate_vote.to_inputs()[1]
|
||||||
|
|
||||||
alice_casted_vote = ValidatorElectionVote.generate([alice_votes],
|
alice_casted_vote = Vote.generate([alice_votes],
|
||||||
[([election_public_key], 2), ([alice.public_key], 2)],
|
[([election_public_key], 2), ([alice.public_key], 2)],
|
||||||
election_id=valid_election.id)\
|
election_id=valid_election.id)\
|
||||||
.sign([alice.private_key])
|
.sign([alice.private_key])
|
||||||
|
|
||||||
assert alice_casted_vote.validate(b_mock)
|
assert alice_casted_vote.validate(b_mock)
|
||||||
b_mock.store_bulk_transactions([alice_casted_vote])
|
b_mock.store_bulk_transactions([alice_casted_vote])
|
||||||
@ -142,10 +143,10 @@ def test_valid_election_votes_received(b_mock, valid_election, ed25519_node_keys
|
|||||||
# Check if the delegated vote is count as valid vote
|
# Check if the delegated vote is count as valid vote
|
||||||
assert valid_election.get_commited_votes(b_mock) == 2
|
assert valid_election.get_commited_votes(b_mock) == 2
|
||||||
|
|
||||||
key0_casted_vote = ValidatorElectionVote.generate([key0_votes],
|
key0_casted_vote = Vote.generate([key0_votes],
|
||||||
[([election_public_key], votes-4)],
|
[([election_public_key], votes-4)],
|
||||||
election_id=valid_election.id)\
|
election_id=valid_election.id)\
|
||||||
.sign([key0.private_key])
|
.sign([key0.private_key])
|
||||||
|
|
||||||
assert key0_casted_vote.validate(b_mock)
|
assert key0_casted_vote.validate(b_mock)
|
||||||
b_mock.store_bulk_transactions([key0_casted_vote])
|
b_mock.store_bulk_transactions([key0_casted_vote])
|
||||||
@ -227,7 +228,7 @@ def test_upsert_validator(b, node_key, node_keys, ed25519_node_keys):
|
|||||||
|
|
||||||
latest_block = b.get_latest_block()
|
latest_block = b.get_latest_block()
|
||||||
# reset the validator set
|
# reset the validator set
|
||||||
b.store_validator_set(latest_block['height'], validators, 'previous_election_id')
|
b.store_validator_set(latest_block['height'], validators)
|
||||||
|
|
||||||
power = 1
|
power = 1
|
||||||
public_key = '9B3119650DF82B9A5D8A12E38953EA47475C09F0C48A4E6A0ECE182944B24403'
|
public_key = '9B3119650DF82B9A5D8A12E38953EA47475C09F0C48A4E6A0ECE182944B24403'
|
||||||
@ -291,19 +292,19 @@ def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys):
|
|||||||
assert not ValidatorElection.has_concluded(b, election.id, [tx_vote0, tx_vote1])
|
assert not ValidatorElection.has_concluded(b, election.id, [tx_vote0, tx_vote1])
|
||||||
assert ValidatorElection.has_concluded(b, election.id, [tx_vote0, tx_vote1, tx_vote2])
|
assert ValidatorElection.has_concluded(b, election.id, [tx_vote0, tx_vote1, tx_vote2])
|
||||||
|
|
||||||
assert ValidatorElection.get_validator_update(b, 4, [tx_vote0]) == []
|
assert not ValidatorElection.approved_update(b, 4, [tx_vote0])
|
||||||
assert ValidatorElection.get_validator_update(b, 4, [tx_vote0, tx_vote1]) == []
|
assert not ValidatorElection.approved_update(b, 4, [tx_vote0, tx_vote1])
|
||||||
|
|
||||||
update = ValidatorElection.get_validator_update(b, 4, [tx_vote0, tx_vote1, tx_vote2])
|
update = ValidatorElection.approved_update(b, 4, [tx_vote0, tx_vote1, tx_vote2])
|
||||||
update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n')
|
assert update
|
||||||
assert len(update) == 1
|
update_public_key = codecs.encode(update.pub_key.data, 'base64').decode().rstrip('\n')
|
||||||
assert update_public_key == public_key64
|
assert update_public_key == public_key64
|
||||||
|
|
||||||
b.store_bulk_transactions([tx_vote0, tx_vote1])
|
b.store_bulk_transactions([tx_vote0, tx_vote1])
|
||||||
|
|
||||||
update = ValidatorElection.get_validator_update(b, 4, [tx_vote2])
|
update = ValidatorElection.approved_update(b, 4, [tx_vote2])
|
||||||
update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n')
|
assert update
|
||||||
assert len(update) == 1
|
update_public_key = codecs.encode(update.pub_key.data, 'base64').decode().rstrip('\n')
|
||||||
assert update_public_key == public_key64
|
assert update_public_key == public_key64
|
||||||
|
|
||||||
# remove validator
|
# remove validator
|
||||||
@ -324,9 +325,10 @@ def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys):
|
|||||||
|
|
||||||
b.store_bulk_transactions([tx_vote0, tx_vote1])
|
b.store_bulk_transactions([tx_vote0, tx_vote1])
|
||||||
|
|
||||||
update = ValidatorElection.get_validator_update(b, 9, [tx_vote2])
|
update = ValidatorElection.approved_update(b, 9, [tx_vote2])
|
||||||
update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n')
|
if update:
|
||||||
assert len(update) == 1
|
update_public_key = codecs.encode(update.pub_key.data, 'base64').decode().rstrip('\n')
|
||||||
|
assert update
|
||||||
assert update_public_key == public_key64
|
assert update_public_key == public_key64
|
||||||
|
|
||||||
# assert that the public key is not a part of the current validator set
|
# assert that the public key is not a part of the current validator set
|
||||||
@ -348,10 +350,10 @@ def to_inputs(election, i, ed25519_node_keys):
|
|||||||
def gen_vote(election, i, ed25519_node_keys):
|
def gen_vote(election, i, ed25519_node_keys):
|
||||||
(input_i, votes_i, key_i) = to_inputs(election, i, ed25519_node_keys)
|
(input_i, votes_i, key_i) = to_inputs(election, i, ed25519_node_keys)
|
||||||
election_pub_key = ValidatorElection.to_public_key(election.id)
|
election_pub_key = ValidatorElection.to_public_key(election.id)
|
||||||
return ValidatorElectionVote.generate([input_i],
|
return Vote.generate([input_i],
|
||||||
[([election_pub_key], votes_i)],
|
[([election_pub_key], votes_i)],
|
||||||
election_id=election.id)\
|
election_id=election.id)\
|
||||||
.sign([key_i.private_key])
|
.sign([key_i.private_key])
|
||||||
|
|
||||||
|
|
||||||
def reset_validator_set(b, node_keys, height):
|
def reset_validator_set(b, node_keys, height):
|
||||||
@ -360,4 +362,4 @@ def reset_validator_set(b, node_keys, height):
|
|||||||
validators.append({'public_key': {'type': 'ed25519-base64',
|
validators.append({'public_key': {'type': 'ed25519-base64',
|
||||||
'value': node_pub},
|
'value': node_pub},
|
||||||
'voting_power': 10})
|
'voting_power': 10})
|
||||||
b.store_validator_set(height, validators, 'election_id')
|
b.store_validator_set(height, validators)
|
@ -2,6 +2,7 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -72,16 +73,17 @@ def test_upsert_validator_invalid_inputs_election(b_mock, new_validator, node_ke
|
|||||||
election.validate(b_mock)
|
election.validate(b_mock)
|
||||||
|
|
||||||
|
|
||||||
def test_upsert_validator_invalid_election(b_mock, new_validator, node_key, valid_election):
|
@patch('bigchaindb.elections.election.uuid4', lambda: 'mock_uuid4')
|
||||||
|
def test_upsert_validator_invalid_election(b_mock, new_validator, node_key, fixed_seed_election):
|
||||||
voters = ValidatorElection.recipients(b_mock)
|
voters = ValidatorElection.recipients(b_mock)
|
||||||
duplicate_election = ValidatorElection.generate([node_key.public_key],
|
duplicate_election = ValidatorElection.generate([node_key.public_key],
|
||||||
voters,
|
voters,
|
||||||
new_validator, None).sign([node_key.private_key])
|
new_validator, None).sign([node_key.private_key])
|
||||||
|
|
||||||
with pytest.raises(DuplicateTransaction):
|
with pytest.raises(DuplicateTransaction):
|
||||||
valid_election.validate(b_mock, [duplicate_election])
|
fixed_seed_election.validate(b_mock, [duplicate_election])
|
||||||
|
|
||||||
b_mock.store_bulk_transactions([valid_election])
|
b_mock.store_bulk_transactions([fixed_seed_election])
|
||||||
|
|
||||||
with pytest.raises(DuplicateTransaction):
|
with pytest.raises(DuplicateTransaction):
|
||||||
duplicate_election.validate(b_mock)
|
duplicate_election.validate(b_mock)
|
||||||
|
@ -10,7 +10,7 @@ def test_get_validators_endpoint(b, client):
|
|||||||
'pub_key': {'data': '4E2685D9016126864733225BE00F005515200727FBAB1312FC78C8B76831255A',
|
'pub_key': {'data': '4E2685D9016126864733225BE00F005515200727FBAB1312FC78C8B76831255A',
|
||||||
'type': 'ed25519'},
|
'type': 'ed25519'},
|
||||||
'voting_power': 10}]
|
'voting_power': 10}]
|
||||||
b.store_validator_set(23, validator_set, 'election_id')
|
b.store_validator_set(23, validator_set)
|
||||||
|
|
||||||
res = client.get(VALIDATORS_ENDPOINT)
|
res = client.get(VALIDATORS_ENDPOINT)
|
||||||
assert is_validator(res.json[0])
|
assert is_validator(res.json[0])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user