diff --git a/bigchaindb/__init__.py b/bigchaindb/__init__.py index e1762f5d..223d7a0e 100644 --- a/bigchaindb/__init__.py +++ b/bigchaindb/__init__.py @@ -7,6 +7,7 @@ import logging from bigchaindb.log import DEFAULT_LOGGING_CONFIG as log_config from bigchaindb.lib import BigchainDB # noqa +from bigchaindb.migrations.chain_migration_election import ChainMigrationElection from bigchaindb.version import __version__ # noqa from bigchaindb.core import App # noqa @@ -99,4 +100,5 @@ from bigchaindb.elections.vote import Vote # noqa Transaction.register_type(Transaction.CREATE, models.Transaction) Transaction.register_type(Transaction.TRANSFER, models.Transaction) Transaction.register_type(ValidatorElection.OPERATION, ValidatorElection) +Transaction.register_type(ChainMigrationElection.OPERATION, ChainMigrationElection) Transaction.register_type(Vote.OPERATION, Vote) diff --git a/bigchaindb/commands/bigchaindb.py b/bigchaindb/commands/bigchaindb.py index 96d555bb..f9588dcd 100644 --- a/bigchaindb/commands/bigchaindb.py +++ b/bigchaindb/commands/bigchaindb.py @@ -13,6 +13,7 @@ import copy import json import sys +from bigchaindb.migrations.chain_migration_election import ChainMigrationElection from bigchaindb.utils import load_node_key from bigchaindb.common.exceptions import (DatabaseDoesNotExist, ValidationError) @@ -115,6 +116,31 @@ def run_election_new(args, bigchain): globals()[f'run_election_new_{args.election_type}'](args, bigchain) +def create_new_election(sk, bigchain, election_class, data): + + try: + key = load_node_key(sk) + voters = election_class.recipients(bigchain) + election = election_class.generate([key.public_key], + voters, + data, None).sign([key.private_key]) + election.validate(bigchain) + except ValidationError as e: + logger.error(e) + return False + except FileNotFoundError as fd_404: + logger.error(fd_404) + return False + + resp = bigchain.write_transaction(election, 'broadcast_tx_commit') + if resp == (202, ''): + logger.info('[SUCCESS] Submitted proposal with id: {}'.format(election.id)) + return election.id + else: + logger.error('Failed to commit election proposal') + return False + + def run_election_new_upsert_validator(args, bigchain): """Initiates an election to add/update/remove a validator to an existing BigchainDB network @@ -136,27 +162,21 @@ def run_election_new_upsert_validator(args, bigchain): 'node_id': args.node_id } - try: - key = load_node_key(args.sk) - voters = ValidatorElection.recipients(bigchain) - election = ValidatorElection.generate([key.public_key], - voters, - new_validator, None).sign([key.private_key]) - election.validate(bigchain) - except ValidationError as e: - logger.error(e) - return False - except FileNotFoundError as fd_404: - logger.error(fd_404) - return False + return create_new_election(args.sk, bigchain, ValidatorElection, new_validator) - resp = bigchain.write_transaction(election, 'broadcast_tx_commit') - if resp == (202, ''): - logger.info('[SUCCESS] Submitted proposal with id: {}'.format(election.id)) - return election.id - else: - logger.error('Failed to commit election proposal') - return False + +def run_election_new_chain_migration(args, bigchain): + """Initiates an election to halt block production + + :param args: dict + args = { + 'sk': the path to the private key of the node calling the election (str) + } + :param bigchain: an instance of BigchainDB + :return: election_id or `False` in case of failure + """ + + return create_new_election(args.sk, bigchain, ChainMigrationElection, {}) def run_election_approve(args, bigchain): diff --git a/bigchaindb/commands/election_types.py b/bigchaindb/commands/election_types.py index b57522f6..4745b85d 100644 --- a/bigchaindb/commands/election_types.py +++ b/bigchaindb/commands/election_types.py @@ -16,5 +16,14 @@ elections = { 'help': 'Path to the private key of the election initiator.' } } + }, + 'chain-migration': { + 'help': 'Call for a halt to block production to allow for a version change across breaking changes.', + 'args': { + '--private-key': { + 'dest': 'sk', + 'help': 'Path to the private key of the election initiator.' + } + } } } diff --git a/bigchaindb/common/schema/__init__.py b/bigchaindb/common/schema/__init__.py index 25943675..6280b7b9 100644 --- a/bigchaindb/common/schema/__init__.py +++ b/bigchaindb/common/schema/__init__.py @@ -37,6 +37,9 @@ _, TX_SCHEMA_TRANSFER = _load_schema('transaction_transfer_' + _, TX_SCHEMA_VALIDATOR_ELECTION = _load_schema('transaction_validator_election_' + TX_SCHEMA_VERSION) +_, TX_SCHEMA_CHAIN_MIGRATION_ELECTION = _load_schema('transaction_chain_migration_election_' + + TX_SCHEMA_VERSION) + _, TX_SCHEMA_VOTE = _load_schema('transaction_vote_' + TX_SCHEMA_VERSION) diff --git a/bigchaindb/common/schema/transaction_chain_migration_election_v2.0.yaml b/bigchaindb/common/schema/transaction_chain_migration_election_v2.0.yaml new file mode 100644 index 00000000..40fa1a7b --- /dev/null +++ b/bigchaindb/common/schema/transaction_chain_migration_election_v2.0.yaml @@ -0,0 +1,44 @@ +# 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 + +--- +"$schema": "http://json-schema.org/draft-04/schema#" +type: object +title: Chain Migration Election Schema - Propose a halt in block production to allow for a version change +required: +- operation +- asset +- outputs +properties: + operation: + type: string + value: "CHAIN_MIGRATION_ELECTION" + asset: + additionalProperties: false + properties: + data: + additionalProperties: false + properties: + seed: + type: string + required: + - data + outputs: + type: array + items: + "$ref": "#/definitions/output" +definitions: + output: + type: object + properties: + condition: + type: object + required: + - uri + properties: + uri: + type: string + pattern: "^ni:///sha-256;([a-zA-Z0-9_-]{0,86})[?]\ + (fpt=ed25519-sha-256(&)?|cost=[0-9]+(&)?|\ + subtypes=ed25519-sha-256(&)?){2,3}$" diff --git a/bigchaindb/common/schema/transaction_v2.0.yaml b/bigchaindb/common/schema/transaction_v2.0.yaml index 562c0d86..6c056f7f 100644 --- a/bigchaindb/common/schema/transaction_v2.0.yaml +++ b/bigchaindb/common/schema/transaction_v2.0.yaml @@ -63,6 +63,7 @@ definitions: - CREATE - TRANSFER - VALIDATOR_ELECTION + - CHAIN_MIGRATION_ELECTION - VOTE asset: type: object diff --git a/bigchaindb/core.py b/bigchaindb/core.py index f936bd91..8be3cdaf 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -20,13 +20,13 @@ from abci.types_pb2 import ( ) from bigchaindb import BigchainDB +from bigchaindb.elections.election import Election from bigchaindb.version import __tm_supported_versions__ from bigchaindb.utils import tendermint_version_is_compatible from bigchaindb.tendermint_utils import (decode_transaction, calculate_hash) from bigchaindb.lib import Block, PreCommitState from bigchaindb.backend.query import PRE_COMMIT_ID -from bigchaindb.upsert_validator import ValidatorElection import bigchaindb.upsert_validator.validator_utils as vutils from bigchaindb.events import EventTypes, Event @@ -219,15 +219,12 @@ class App(BaseApplication): else: self.block_txn_hash = block['app_hash'] - # Check if the current block concluded any validator elections and - # update the locally tracked validator set - validator_update = ValidatorElection.approved_update(self.bigchaindb, - self.new_height, - self.block_transactions) - update = [validator_update] if validator_update else [] + # Process all concluded elections in the current block and get any update to the validator set + update = Election.approved_elections(self.bigchaindb, + self.new_height, + self.block_transactions) - # Store pre-commit state to recover in case there is a crash - # during `commit` + # Store pre-commit state to recover in case there is a crash during `commit` pre_commit_state = PreCommitState(commit_id=PRE_COMMIT_ID, height=self.new_height, transactions=self.block_txn_ids) diff --git a/bigchaindb/elections/election.py b/bigchaindb/elections/election.py index b78e1456..ca0a4be3 100644 --- a/bigchaindb/elections/election.py +++ b/bigchaindb/elections/election.py @@ -1,6 +1,7 @@ # 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 +from collections import defaultdict import base58 from uuid import uuid4 @@ -33,17 +34,18 @@ class Election(Transaction): INCONCLUSIVE = 'inconclusive' # Vote ratio to approve an election ELECTION_THRESHOLD = 2 / 3 + CHANGES_VALIDATOR_SET = True @classmethod - def get_validator_change(cls, bigchain, height=None): - """Return the latest change to the validator set + def get_validator_change(cls, bigchain): + """Return the validator set from the most recent approved block :return: { 'height': , - 'validators': , - 'election_id': + 'validators': } """ + height = bigchain.get_latest_block()['height'] return bigchain.get_validator_change(height) @classmethod @@ -242,24 +244,41 @@ class Election(Transaction): return response @classmethod - def approved_update(cls, bigchain, new_height, txns): - votes = {} - for txn in txns: - if not isinstance(txn, Vote): + def approved_elections(cls, bigchain, new_height, txns): + elections = defaultdict(list) + for tx in txns: + if not isinstance(tx, Vote): + continue + election_id = tx.asset['id'] + elections[election_id].append(tx) + + validator_set_updated = False + validator_set_change = [] + for election_id, votes in elections.items(): + election = Election.has_concluded(bigchain, election_id, votes, new_height) + + if not election: continue - election_id = txn.asset['id'] - election_votes = votes.get(election_id, []) - election_votes.append(txn) - votes[election_id] = election_votes + if election.makes_validator_set_change(): + if validator_set_updated: + continue + validator_set_change.append(election.get_validator_set_change(bigchain, new_height)) + validator_set_updated = True - 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 + election.on_approval(bigchain, election, new_height) + + return validator_set_change + + def makes_validator_set_change(self): + return self.CHANGES_VALIDATOR_SET + + def get_validator_set_change(self, bigchain, new_height): + if self.makes_validator_set_change(): + return self.change_validator_set(bigchain, new_height) + + def change_validator_set(self, bigchain, new_height): + raise NotImplementedError @classmethod def on_approval(cls, bigchain, election, new_height): diff --git a/bigchaindb/migrations/chain_migration_election.py b/bigchaindb/migrations/chain_migration_election.py new file mode 100644 index 00000000..e1e783d0 --- /dev/null +++ b/bigchaindb/migrations/chain_migration_election.py @@ -0,0 +1,15 @@ +from bigchaindb.common.schema import TX_SCHEMA_CHAIN_MIGRATION_ELECTION +from bigchaindb.elections.election import Election + + +class ChainMigrationElection(Election): + + OPERATION = 'CHAIN_MIGRATION_ELECTION' + CREATE = OPERATION + ALLOWED_OPERATIONS = (OPERATION,) + TX_SCHEMA_CUSTOM = TX_SCHEMA_CHAIN_MIGRATION_ELECTION + CHANGES_VALIDATOR_SET = False + + @classmethod + def on_approval(cls, bigchain, election, new_height): + bigchain.migrate_abci_chain() diff --git a/bigchaindb/upsert_validator/validator_election.py b/bigchaindb/upsert_validator/validator_election.py index 3daf22eb..856b31a1 100644 --- a/bigchaindb/upsert_validator/validator_election.py +++ b/bigchaindb/upsert_validator/validator_election.py @@ -4,7 +4,7 @@ from bigchaindb.common.exceptions import InvalidPowerChange from bigchaindb.elections.election import Election -from bigchaindb.common.schema import (TX_SCHEMA_VALIDATOR_ELECTION) +from bigchaindb.common.schema import TX_SCHEMA_VALIDATOR_ELECTION from .validator_utils import (new_validator_set, encode_validator, validate_asset_public_key) @@ -36,14 +36,18 @@ class ValidatorElection(Election): super(ValidatorElection, cls).validate_schema(tx) validate_asset_public_key(tx['asset']['data']['public_key']) - @classmethod - def on_approval(cls, bigchain, election, new_height): + def change_validator_set(self, bigchain, new_height): # The new validator set comes into effect from height = new_height+1 - validator_updates = [election.asset['data']] + # (upcoming changes to Tendermint will change this to height = new_height+2) + validator_updates = [self.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) - return encode_validator(election.asset['data']) + return encode_validator(self.asset['data']) + + @classmethod + def on_approval(cls, bigchain, election, new_height): + pass diff --git a/docs/server/source/server-reference/bigchaindb-cli.md b/docs/server/source/server-reference/bigchaindb-cli.md index 44fa2687..82870445 100644 --- a/docs/server/source/server-reference/bigchaindb-cli.md +++ b/docs/server/source/server-reference/bigchaindb-cli.md @@ -91,6 +91,10 @@ Election management is broken into several subcommands. Below is the command lin Create a new election which proposes a change to your BigChainDB network. +If the command succeeds, it will create an election and return an `election_id`. + +**NOTE**: The election proposal consists of vote tokens allocated to each current validator as per their voting power. Validators then cast their votes to approve the election by spending their vote tokens, (see the documentation on `election approve`). + There are multiple types of election, which each take different parameters. Below is a short description of each type of election, as well as their command line syntax and the return value. ###### election new upsert-validator @@ -106,7 +110,9 @@ $ bigchaindb election new upsert-validator E_PUBKEY E_POWER E_NODE_ID --private- - `E_PUBKEY`: Public key of the node to be added/updated/removed. - `E_POWER`: The new power for the `E_PUBKEY`. NOTE, if power is set to `0` then `E_PUBKEY` will be removed from the validator set when the election concludes. - `E_NODE_ID`: Node id of `E_PUBKEY`. The node operator of `E_PUBKEY` can generate the node id via `tendermint show_node_id`. -- `--private-key`: The path to Tendermint's private key which can be generally found at `/home/user/.tendermint/config/priv_validator.json`. For example, to add a new validator, provide the public key and node id for some node not already in the validator set, along with whatever voting power you'd like them to have. To remove an existing validator, provide their public key and node id, and set `E_POWER` to `0`. Please note that the private key provided here is of the node which is generating this election i.e. +- `--private-key`: The path to Tendermint's private key which can be generally found at `/home/user/.tendermint/config/priv_validator.json`. + +For example, to add a new validator, provide the public key and node id for some node not already in the validator set, along with whatever voting power you'd like them to have. To remove an existing validator, provide their public key and node id, and set `E_POWER` to `0`. Please note that the private key provided here is of the node which is generating this election i.e. NOTE: A change to the validator set can only be proposed by one of the exisitng validators. @@ -118,10 +124,29 @@ $ bigchaindb election new upsert-validator HHG0IQRybpT6nJMIWWFWhMczCLHt6xcm7eP52 [SUCCESS] Submitted proposal with id: 04a067582cf03eba2b53b82e4adb5ece424474cbd4f7183780855a93ac5e3caa ``` -If the command succeeds, it will create an election and return an `election_id`. A successful execution of the above command **doesn't** imply that the validator set will be immediately updated but rather it means the proposal has been succcessfully accepted by the network. Once the `election_id` has been generated the node operator should share this `election_id` with other validators in the network and urge them to approve the proposal. Note that the node operator should themsleves also approve the proposal. +A successful execution of the above command **doesn't** imply that the validator set will be immediately updated but rather it means the proposal has been succcessfully accepted by the network. Once the `election_id` has been generated the node operator should share this `election_id` with other validators in the network and urge them to approve the proposal. Note that the node operator should themsleves also approve the proposal. -**NOTE**: The election proposal consists of vote tokens allocated to each current validator as per their voting power. Validators then cast their votes to approve the change to the validator set by spending their vote tokens. +###### election new migration + +Create an election to halt block production, to allow for a version change across breaking changes. + + +```bash +$ bigchaindb election new migration --private-key PATH_TO_YOUR_PRIVATE_KEY +[SUCCESS] Submitted proposal with id: +``` + +- `--private-key`: The path to Tendermint's private key which can be generally found at `/home/user/.tendermint/config/priv_validator.json`. + +Example usage, + +```bash +$ bigchaindb election new migration --private-key /home/user/.tendermint/config/priv_validator.json +[SUCCESS] Submitted proposal with id: 04a067582cf03eba2b53b82e4adb5ece424474cbd4f7183780855a93ac5e3caa +``` + +**NOTE** `migration` elections will halt block production at whichever blockheight they are approved. Once the election is concluded, the validators will need to restart their systems with a new `chain_id` to resume normal operations. #### election approve diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 38e251d8..bbeea227 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -26,6 +26,8 @@ def test_make_sure_we_dont_remove_any_command(): assert parser.parse_args(['start']).command assert parser.parse_args(['election', 'new', 'upsert-validator', 'TEMP_PUB_KEYPAIR', '10', 'TEMP_NODE_ID', '--private-key', 'TEMP_PATH_TO_PRIVATE_KEY']).command + assert parser.parse_args(['election', 'new', 'chain-migration', + '--private-key', 'TEMP_PATH_TO_PRIVATE_KEY']).command assert parser.parse_args(['election', 'approve', 'ELECTION_ID', '--private-key', 'TEMP_PATH_TO_PRIVATE_KEY']).command assert parser.parse_args(['election', 'show', 'ELECTION_ID']).command @@ -341,6 +343,42 @@ def test_election_new_upsert_validator_without_tendermint(caplog, b, priv_valida assert b.get_transaction(election_id) +@pytest.mark.abci +def test_election_new_chain_migration_with_tendermint(b, priv_validator_path, user_sk, validators): + from bigchaindb.commands.bigchaindb import run_election_new_chain_migration + + new_args = Namespace(action='new', + election_type='migration', + sk=priv_validator_path, + config={}) + + election_id = run_election_new_chain_migration(new_args, b) + + assert b.get_transaction(election_id) + + +@pytest.mark.bdb +def test_election_new_chain_migration_without_tendermint(caplog, b, priv_validator_path, user_sk): + from bigchaindb.commands.bigchaindb import run_election_new_chain_migration + + def mock_write(tx, mode): + b.store_bulk_transactions([tx]) + return (202, '') + + b.get_validators = mock_get_validators + b.write_transaction = mock_write + + args = Namespace(action='new', + election_type='migration', + sk=priv_validator_path, + config={}) + + with caplog.at_level(logging.INFO): + election_id = run_election_new_chain_migration(args, b) + assert caplog.records[0].msg == '[SUCCESS] Submitted proposal with id: ' + election_id + assert b.get_transaction(election_id) + + @pytest.mark.bdb def test_election_new_upsert_validator_invalid_election(caplog, b, priv_validator_path, user_sk): from bigchaindb.commands.bigchaindb import run_election_new_upsert_validator @@ -415,7 +453,7 @@ def test_election_approve_without_tendermint(caplog, b, priv_validator_path, new b, election_id = call_election(b, new_validator, node_key) - # call run_upsert_validator_approve with args that point to the election + # call run_election_approve with args that point to the election args = Namespace(action='approve', election_id=election_id, sk=priv_validator_path, diff --git a/tests/conftest.py b/tests/conftest.py index 15cfcf01..4c271aad 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,15 +20,17 @@ from logging.config import dictConfig import pytest from pymongo import MongoClient +from bigchaindb import ValidatorElection from bigchaindb.common import crypto from bigchaindb.log import setup_logging +from bigchaindb.migrations.chain_migration_election import ChainMigrationElection from bigchaindb.tendermint_utils import key_from_base64 -from bigchaindb.backend import schema +from bigchaindb.backend import schema, query from bigchaindb.common.crypto import (key_pair_from_ed25519_key, public_key_from_ed25519_key) from bigchaindb.common.exceptions import DatabaseDoesNotExist from bigchaindb.lib import Block - +from tests.utils import gen_vote TEST_DB_NAME = 'bigchain_test' @@ -242,6 +244,26 @@ def b(): return BigchainDB() +@pytest.fixture +def b_mock(b, network_validators): + b.get_validators = mock_get_validators(network_validators) + + return b + + +def mock_get_validators(network_validators): + def validator_set(height): + validators = [] + for public_key, power in network_validators.items(): + validators.append({ + 'public_key': {'type': 'ed25519-base64', 'value': public_key}, + 'voting_power': power + }) + return validators + + return validator_set + + @pytest.fixture def create_tx(alice, user_pk): from bigchaindb.models import Transaction @@ -674,3 +696,117 @@ def new_validator(): 'type': 'ed25519-base16'}, 'power': power, 'node_id': node_id} + + +@pytest.fixture +def valid_upsert_validator_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 +def valid_upsert_validator_election_2(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 +def valid_chain_migration_election(b_mock, node_key): + voters = ChainMigrationElection.recipients(b_mock) + return ChainMigrationElection.generate([node_key.public_key], + voters, + {}, None).sign([node_key.private_key]) + + +@pytest.fixture +def valid_chain_migration_election_2(b_mock, node_key): + voters = ChainMigrationElection.recipients(b_mock) + return ChainMigrationElection.generate([node_key.public_key], + voters, + {}, None).sign([node_key.private_key]) + + +@pytest.fixture +def ongoing_validator_election(b, valid_upsert_validator_election, ed25519_node_keys): + validators = b.get_validators(height=1) + genesis_validators = {'validators': validators, + 'height': 0, + 'election_id': None} + query.store_validator_set(b.connection, genesis_validators) + + b.store_bulk_transactions([valid_upsert_validator_election]) + block_1 = Block(app_hash='hash_1', height=1, transactions=[valid_upsert_validator_election.id]) + b.store_block(block_1._asdict()) + return valid_upsert_validator_election + + +@pytest.fixture +def ongoing_validator_election_2(b, valid_upsert_validator_election_2, ed25519_node_keys): + validators = b.get_validators(height=1) + genesis_validators = {'validators': validators, + 'height': 0, + 'election_id': None} + query.store_validator_set(b.connection, genesis_validators) + + b.store_bulk_transactions([valid_upsert_validator_election_2]) + block_1 = Block(app_hash='hash_2', height=1, transactions=[valid_upsert_validator_election_2.id]) + b.store_block(block_1._asdict()) + return valid_upsert_validator_election_2 + + +@pytest.fixture +def ongoing_chain_migration_election(b, valid_chain_migration_election, ed25519_node_keys): + + b.store_bulk_transactions([valid_chain_migration_election]) + block_1 = Block(app_hash='hash_1', height=1, transactions=[valid_chain_migration_election.id]) + b.store_block(block_1._asdict()) + return valid_chain_migration_election + + +@pytest.fixture +def ongoing_chain_migration_election_2(b, valid_chain_migration_election_2, ed25519_node_keys): + + b.store_bulk_transactions([valid_chain_migration_election_2]) + block_1 = Block(app_hash='hash_2', height=1, transactions=[valid_chain_migration_election_2.id]) + b.store_block(block_1._asdict()) + return valid_chain_migration_election_2 + + +@pytest.fixture +def validator_election_votes(b_mock, ongoing_validator_election, ed25519_node_keys): + voters = ValidatorElection.recipients(b_mock) + votes = generate_votes(ongoing_validator_election, voters, ed25519_node_keys) + return votes + + +@pytest.fixture +def validator_election_votes_2(b_mock, ongoing_validator_election_2, ed25519_node_keys): + voters = ValidatorElection.recipients(b_mock) + votes = generate_votes(ongoing_validator_election_2, voters, ed25519_node_keys) + return votes + + +@pytest.fixture +def chain_migration_election_votes(b_mock, ongoing_chain_migration_election, ed25519_node_keys): + voters = ChainMigrationElection.recipients(b_mock) + votes = generate_votes(ongoing_chain_migration_election, voters, ed25519_node_keys) + return votes + + +@pytest.fixture +def chain_migration_election_votes_2(b_mock, ongoing_chain_migration_election_2, ed25519_node_keys): + voters = ChainMigrationElection.recipients(b_mock) + votes = generate_votes(ongoing_chain_migration_election_2, voters, ed25519_node_keys) + return votes + + +def generate_votes(election, voters, keys): + votes = [] + for voter in range(len(voters)): + v = gen_vote(election, voter, keys) + votes.append(v) + return votes diff --git a/tests/elections/__init__.py b/tests/elections/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/elections/test_election.py b/tests/elections/test_election.py new file mode 100644 index 00000000..0e9da188 --- /dev/null +++ b/tests/elections/test_election.py @@ -0,0 +1,64 @@ +from unittest.mock import MagicMock + +import pytest + +from bigchaindb.elections.election import Election + + +@pytest.mark.bdb +def test_approved_elections_one_migration_one_upsert( + b, + ongoing_validator_election, validator_election_votes, + ongoing_chain_migration_election, chain_migration_election_votes +): + txns = validator_election_votes + \ + chain_migration_election_votes + mock_chain_migration, mock_store_validator = run_approved_elections(b, txns) + mock_chain_migration.assert_called_once() + mock_store_validator.assert_called_once() + + +@pytest.mark.bdb +def test_approved_elections_one_migration_two_upsert( + b, + ongoing_validator_election, validator_election_votes, + ongoing_validator_election_2, validator_election_votes_2, + ongoing_chain_migration_election, chain_migration_election_votes +): + txns = validator_election_votes + \ + validator_election_votes_2 + \ + chain_migration_election_votes + mock_chain_migration, mock_store_validator = run_approved_elections(b, txns) + mock_chain_migration.assert_called_once() + mock_store_validator.assert_called_once() + + +@pytest.mark.bdb +def test_approved_elections_two_migrations_one_upsert( + b, + ongoing_validator_election, validator_election_votes, + ongoing_chain_migration_election, chain_migration_election_votes, + ongoing_chain_migration_election_2, chain_migration_election_votes_2 +): + txns = validator_election_votes + \ + chain_migration_election_votes + \ + chain_migration_election_votes_2 + mock_chain_migration, mock_store_validator = run_approved_elections(b, txns) + assert mock_chain_migration.call_count == 2 + mock_store_validator.assert_called_once() + + +def test_approved_elections_no_elections(b): + txns = [] + mock_chain_migration, mock_store_validator = run_approved_elections(b, txns) + mock_chain_migration.assert_not_called() + mock_store_validator.assert_not_called() + + +def run_approved_elections(bigchain, txns): + mock_chain_migration = MagicMock() + mock_store_validator = MagicMock() + bigchain.migrate_abci_chain = mock_chain_migration + bigchain.store_validator_set = mock_store_validator + Election.approved_elections(bigchain, 1, txns) + return mock_chain_migration, mock_store_validator diff --git a/tests/migrations/test_migration_election.py b/tests/migrations/test_migration_election.py new file mode 100644 index 00000000..b811903b --- /dev/null +++ b/tests/migrations/test_migration_election.py @@ -0,0 +1,9 @@ +from bigchaindb.migrations.chain_migration_election import ChainMigrationElection + + +def test_valid_migration_election(b_mock, node_key): + voters = ChainMigrationElection.recipients(b_mock) + election = ChainMigrationElection.generate([node_key.public_key], + voters, + {}, None).sign([node_key.private_key]) + assert election.validate(b_mock) diff --git a/tests/upsert_validator/conftest.py b/tests/upsert_validator/conftest.py index 9ab2dad8..58b5e6dd 100644 --- a/tests/upsert_validator/conftest.py +++ b/tests/upsert_validator/conftest.py @@ -5,42 +5,12 @@ from unittest.mock import patch import pytest -from bigchaindb import Vote from bigchaindb.backend.localmongodb import query -from bigchaindb.lib import Block from bigchaindb.upsert_validator import ValidatorElection @pytest.fixture -def b_mock(b, network_validators): - b.get_validators = mock_get_validators(network_validators) - - return b - - -def mock_get_validators(network_validators): - def validator_set(height): - validators = [] - for public_key, power in network_validators.items(): - validators.append({ - 'public_key': {'type': 'ed25519-base64', 'value': public_key}, - 'voting_power': power - }) - return validators - - return validator_set - - -@pytest.fixture -def valid_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 -def valid_election_b(b, node_key, new_validator): +def valid_upsert_validator_election_b(b, node_key, new_validator): voters = ValidatorElection.recipients(b) return ValidatorElection.generate([node_key.public_key], voters, @@ -57,30 +27,16 @@ def fixed_seed_election(b_mock, node_key, new_validator): @pytest.fixture -def ongoing_election(b, valid_election, ed25519_node_keys): - validators = b.get_validators(height=1) - genesis_validators = {'validators': validators, - 'height': 0, - 'election_id': None} - query.store_validator_set(b.connection, genesis_validators) - - b.store_bulk_transactions([valid_election]) - block_1 = Block(app_hash='hash_1', height=1, transactions=[valid_election.id]) - b.store_block(block_1._asdict()) - return valid_election - - -@pytest.fixture -def concluded_election(b, ongoing_election, ed25519_node_keys): +def concluded_election(b, ongoing_validator_election, ed25519_node_keys): election_result = {'height': 2, - 'election_id': ongoing_election.id} + 'election_id': ongoing_validator_election.id} query.store_election_results(b.connection, election_result) - return ongoing_election + return ongoing_validator_election @pytest.fixture -def inconclusive_election(b, ongoing_election, new_validator): +def inconclusive_election(b, ongoing_validator_election, new_validator): validators = b.get_validators(height=1) validators[0]['voting_power'] = 15 validator_update = {'validators': validators, @@ -88,20 +44,4 @@ def inconclusive_election(b, ongoing_election, new_validator): 'election_id': 'some_other_election'} query.store_validator_set(b.connection, validator_update) - return ongoing_election - - -def vote(election, voter, keys, b): - election_input = election.to_inputs()[voter] - votes = election.outputs[voter].amount - public_key = election_input.owners_before[0] - key = keys[public_key] - - election_pub_key = ValidatorElection.to_public_key(election.id) - - v = Vote.generate([election_input], - [([election_pub_key], votes)], - election_id=election.id)\ - .sign([key.private_key]) - b.store_bulk_transactions([v]) - return v + return ongoing_validator_election diff --git a/tests/upsert_validator/test_upsert_validator_vote.py b/tests/upsert_validator/test_upsert_validator_vote.py index b2e23af7..3c3512d0 100644 --- a/tests/upsert_validator/test_upsert_validator_vote.py +++ b/tests/upsert_validator/test_upsert_validator_vote.py @@ -5,104 +5,105 @@ import pytest import codecs +from bigchaindb.elections.election import Election from bigchaindb.tendermint_utils import public_key_to_base64 from bigchaindb.upsert_validator import ValidatorElection from bigchaindb.common.exceptions import AmountError from bigchaindb.common.crypto import generate_key_pair from bigchaindb.common.exceptions import ValidationError from bigchaindb.elections.vote import Vote -from tests.utils import generate_block +from tests.utils import generate_block, gen_vote pytestmark = [pytest.mark.execute] @pytest.mark.bdb -def test_upsert_validator_valid_election_vote(b_mock, valid_election, ed25519_node_keys): - b_mock.store_bulk_transactions([valid_election]) +def test_upsert_validator_valid_election_vote(b_mock, valid_upsert_validator_election, ed25519_node_keys): + b_mock.store_bulk_transactions([valid_upsert_validator_election]) - input0 = valid_election.to_inputs()[0] - votes = valid_election.outputs[0].amount + input0 = valid_upsert_validator_election.to_inputs()[0] + votes = valid_upsert_validator_election.outputs[0].amount public_key0 = input0.owners_before[0] key0 = ed25519_node_keys[public_key0] - election_pub_key = ValidatorElection.to_public_key(valid_election.id) + election_pub_key = ValidatorElection.to_public_key(valid_upsert_validator_election.id) vote = Vote.generate([input0], [([election_pub_key], votes)], - election_id=valid_election.id)\ + election_id=valid_upsert_validator_election.id)\ .sign([key0.private_key]) assert vote.validate(b_mock) @pytest.mark.bdb -def test_upsert_validator_valid_non_election_vote(b_mock, valid_election, ed25519_node_keys): - b_mock.store_bulk_transactions([valid_election]) +def test_upsert_validator_valid_non_election_vote(b_mock, valid_upsert_validator_election, ed25519_node_keys): + b_mock.store_bulk_transactions([valid_upsert_validator_election]) - input0 = valid_election.to_inputs()[0] - votes = valid_election.outputs[0].amount + input0 = valid_upsert_validator_election.to_inputs()[0] + votes = valid_upsert_validator_election.outputs[0].amount public_key0 = input0.owners_before[0] key0 = ed25519_node_keys[public_key0] - election_pub_key = ValidatorElection.to_public_key(valid_election.id) + election_pub_key = ValidatorElection.to_public_key(valid_upsert_validator_election.id) # Ensure that threshold conditions are now allowed with pytest.raises(ValidationError): Vote.generate([input0], [([election_pub_key, key0.public_key], votes)], - election_id=valid_election.id)\ + election_id=valid_upsert_validator_election.id)\ .sign([key0.private_key]) @pytest.mark.bdb -def test_upsert_validator_delegate_election_vote(b_mock, valid_election, ed25519_node_keys): +def test_upsert_validator_delegate_election_vote(b_mock, valid_upsert_validator_election, ed25519_node_keys): alice = generate_key_pair() - b_mock.store_bulk_transactions([valid_election]) + b_mock.store_bulk_transactions([valid_upsert_validator_election]) - input0 = valid_election.to_inputs()[0] - votes = valid_election.outputs[0].amount + input0 = valid_upsert_validator_election.to_inputs()[0] + votes = valid_upsert_validator_election.outputs[0].amount public_key0 = input0.owners_before[0] key0 = ed25519_node_keys[public_key0] delegate_vote = Vote.generate([input0], [([alice.public_key], 3), ([key0.public_key], votes-3)], - election_id=valid_election.id)\ + election_id=valid_upsert_validator_election.id)\ .sign([key0.private_key]) assert delegate_vote.validate(b_mock) b_mock.store_bulk_transactions([delegate_vote]) - election_pub_key = ValidatorElection.to_public_key(valid_election.id) + election_pub_key = ValidatorElection.to_public_key(valid_upsert_validator_election.id) alice_votes = delegate_vote.to_inputs()[0] alice_casted_vote = Vote.generate([alice_votes], [([election_pub_key], 3)], - election_id=valid_election.id)\ + election_id=valid_upsert_validator_election.id)\ .sign([alice.private_key]) assert alice_casted_vote.validate(b_mock) key0_votes = delegate_vote.to_inputs()[1] key0_casted_vote = Vote.generate([key0_votes], [([election_pub_key], votes-3)], - election_id=valid_election.id)\ + election_id=valid_upsert_validator_election.id)\ .sign([key0.private_key]) assert key0_casted_vote.validate(b_mock) @pytest.mark.bdb -def test_upsert_validator_invalid_election_vote(b_mock, valid_election, ed25519_node_keys): - b_mock.store_bulk_transactions([valid_election]) +def test_upsert_validator_invalid_election_vote(b_mock, valid_upsert_validator_election, ed25519_node_keys): + b_mock.store_bulk_transactions([valid_upsert_validator_election]) - input0 = valid_election.to_inputs()[0] - votes = valid_election.outputs[0].amount + input0 = valid_upsert_validator_election.to_inputs()[0] + votes = valid_upsert_validator_election.outputs[0].amount public_key0 = input0.owners_before[0] key0 = ed25519_node_keys[public_key0] - election_pub_key = ValidatorElection.to_public_key(valid_election.id) + election_pub_key = ValidatorElection.to_public_key(valid_upsert_validator_election.id) vote = Vote.generate([input0], [([election_pub_key], votes+1)], - election_id=valid_election.id)\ + election_id=valid_upsert_validator_election.id)\ .sign([key0.private_key]) with pytest.raises(AmountError): @@ -110,113 +111,111 @@ def test_upsert_validator_invalid_election_vote(b_mock, valid_election, ed25519_ @pytest.mark.bdb -def test_valid_election_votes_received(b_mock, valid_election, ed25519_node_keys): +def test_valid_election_votes_received(b_mock, valid_upsert_validator_election, ed25519_node_keys): alice = generate_key_pair() - b_mock.store_bulk_transactions([valid_election]) - assert valid_election.get_commited_votes(b_mock) == 0 + b_mock.store_bulk_transactions([valid_upsert_validator_election]) + assert valid_upsert_validator_election.get_commited_votes(b_mock) == 0 - input0 = valid_election.to_inputs()[0] - votes = valid_election.outputs[0].amount + input0 = valid_upsert_validator_election.to_inputs()[0] + votes = valid_upsert_validator_election.outputs[0].amount public_key0 = input0.owners_before[0] key0 = ed25519_node_keys[public_key0] # delegate some votes to alice delegate_vote = Vote.generate([input0], [([alice.public_key], 4), ([key0.public_key], votes-4)], - election_id=valid_election.id)\ + election_id=valid_upsert_validator_election.id)\ .sign([key0.private_key]) b_mock.store_bulk_transactions([delegate_vote]) - assert valid_election.get_commited_votes(b_mock) == 0 + assert valid_upsert_validator_election.get_commited_votes(b_mock) == 0 - election_public_key = ValidatorElection.to_public_key(valid_election.id) + election_public_key = ValidatorElection.to_public_key(valid_upsert_validator_election.id) alice_votes = delegate_vote.to_inputs()[0] key0_votes = delegate_vote.to_inputs()[1] alice_casted_vote = Vote.generate([alice_votes], [([election_public_key], 2), ([alice.public_key], 2)], - election_id=valid_election.id)\ + election_id=valid_upsert_validator_election.id)\ .sign([alice.private_key]) assert alice_casted_vote.validate(b_mock) b_mock.store_bulk_transactions([alice_casted_vote]) # Check if the delegated vote is count as valid vote - assert valid_election.get_commited_votes(b_mock) == 2 + assert valid_upsert_validator_election.get_commited_votes(b_mock) == 2 key0_casted_vote = Vote.generate([key0_votes], [([election_public_key], votes-4)], - election_id=valid_election.id)\ + election_id=valid_upsert_validator_election.id)\ .sign([key0.private_key]) assert key0_casted_vote.validate(b_mock) b_mock.store_bulk_transactions([key0_casted_vote]) - assert valid_election.get_commited_votes(b_mock) == votes-2 + assert valid_upsert_validator_election.get_commited_votes(b_mock) == votes - 2 @pytest.mark.bdb -def test_valid_election_conclude(b_mock, valid_election, ed25519_node_keys): +def test_valid_election_conclude(b_mock, valid_upsert_validator_election, ed25519_node_keys): # Node 0: cast vote - tx_vote0 = gen_vote(valid_election, 0, ed25519_node_keys) + tx_vote0 = gen_vote(valid_upsert_validator_election, 0, ed25519_node_keys) # check if the vote is valid even before the election doesn't exist with pytest.raises(ValidationError): assert tx_vote0.validate(b_mock) # store election - b_mock.store_bulk_transactions([valid_election]) + b_mock.store_bulk_transactions([valid_upsert_validator_election]) # cannot conclude election as not votes exist - assert not ValidatorElection.has_concluded(b_mock, valid_election.id) + assert not ValidatorElection.has_concluded(b_mock, valid_upsert_validator_election.id) # validate vote assert tx_vote0.validate(b_mock) - assert not ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote0]) + assert not ValidatorElection.has_concluded(b_mock, valid_upsert_validator_election.id, [tx_vote0]) b_mock.store_bulk_transactions([tx_vote0]) - assert not ValidatorElection.has_concluded(b_mock, valid_election.id) + assert not ValidatorElection.has_concluded(b_mock, valid_upsert_validator_election.id) # Node 1: cast vote - tx_vote1 = gen_vote(valid_election, 1, ed25519_node_keys) + tx_vote1 = gen_vote(valid_upsert_validator_election, 1, ed25519_node_keys) # Node 2: cast vote - tx_vote2 = gen_vote(valid_election, 2, ed25519_node_keys) + tx_vote2 = gen_vote(valid_upsert_validator_election, 2, ed25519_node_keys) # Node 3: cast vote - tx_vote3 = gen_vote(valid_election, 3, ed25519_node_keys) + tx_vote3 = gen_vote(valid_upsert_validator_election, 3, ed25519_node_keys) assert tx_vote1.validate(b_mock) - assert not ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote1]) + assert not ValidatorElection.has_concluded(b_mock, valid_upsert_validator_election.id, [tx_vote1]) # 2/3 is achieved in the same block so the election can be.has_concludedd - assert ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote1, tx_vote2]) + assert ValidatorElection.has_concluded(b_mock, valid_upsert_validator_election.id, [tx_vote1, tx_vote2]) b_mock.store_bulk_transactions([tx_vote1]) - assert not ValidatorElection.has_concluded(b_mock, valid_election.id) + assert not ValidatorElection.has_concluded(b_mock, valid_upsert_validator_election.id) assert tx_vote2.validate(b_mock) assert tx_vote3.validate(b_mock) # conclusion can be triggered my different votes in the same block - assert ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote2]) - assert ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote2, tx_vote3]) + assert ValidatorElection.has_concluded(b_mock, valid_upsert_validator_election.id, [tx_vote2]) + assert ValidatorElection.has_concluded(b_mock, valid_upsert_validator_election.id, [tx_vote2, tx_vote3]) b_mock.store_bulk_transactions([tx_vote2]) # Once the blockchain records >2/3 of the votes the election is assumed to be.has_concludedd # so any invocation of `.has_concluded` for that election should return False - assert not ValidatorElection.has_concluded(b_mock, valid_election.id) + assert not ValidatorElection.has_concluded(b_mock, valid_upsert_validator_election.id) # Vote is still valid but the election cannot be.has_concludedd as it it assmed that it has # been.has_concludedd before assert tx_vote3.validate(b_mock) - assert not ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote3]) + assert not ValidatorElection.has_concluded(b_mock, valid_upsert_validator_election.id, [tx_vote3]) @pytest.mark.abci def test_upsert_validator(b, node_key, node_keys, ed25519_node_keys): - import time - import requests if b.get_latest_block()['height'] == 0: generate_block(b) @@ -244,20 +243,17 @@ def test_upsert_validator(b, node_key, node_keys, ed25519_node_keys): new_validator, None).sign([node_key.private_key]) code, message = b.write_transaction(election, 'broadcast_tx_commit') assert code == 202 - time.sleep(3) - assert b.get_transaction(election.id) tx_vote = gen_vote(election, 0, ed25519_node_keys) assert tx_vote.validate(b) code, message = b.write_transaction(tx_vote, 'broadcast_tx_commit') assert code == 202 - time.sleep(3) - resp = requests.get(b.endpoint + 'validators') + resp = b.get_validators() validator_pub_keys = [] - for v in resp.json()['result']['validators']: - validator_pub_keys.append(v['pub_key']['value']) + for v in resp: + validator_pub_keys.append(v['public_key']['value']) assert (public_key64 in validator_pub_keys) new_validator_set = b.get_validators() @@ -293,19 +289,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 ValidatorElection.has_concluded(b, election.id, [tx_vote0, tx_vote1, tx_vote2]) - assert not ValidatorElection.approved_update(b, 4, [tx_vote0]) - assert not ValidatorElection.approved_update(b, 4, [tx_vote0, tx_vote1]) + assert Election.approved_elections(b, 4, [tx_vote0]) == [] + assert Election.approved_elections(b, 4, [tx_vote0, tx_vote1]) == [] - update = ValidatorElection.approved_update(b, 4, [tx_vote0, tx_vote1, tx_vote2]) - assert update - update_public_key = codecs.encode(update.pub_key.data, 'base64').decode().rstrip('\n') + update = Election.approved_elections(b, 4, [tx_vote0, tx_vote1, tx_vote2]) + assert len(update) == 1 + update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n') assert update_public_key == public_key64 b.store_bulk_transactions([tx_vote0, tx_vote1]) - update = ValidatorElection.approved_update(b, 4, [tx_vote2]) - assert update - update_public_key = codecs.encode(update.pub_key.data, 'base64').decode().rstrip('\n') + update = Election.approved_elections(b, 4, [tx_vote2]) + assert len(update) == 1 + update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n') assert update_public_key == public_key64 # remove validator @@ -326,10 +322,9 @@ def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys): b.store_bulk_transactions([tx_vote0, tx_vote1]) - update = ValidatorElection.approved_update(b, 9, [tx_vote2]) - if update: - update_public_key = codecs.encode(update.pub_key.data, 'base64').decode().rstrip('\n') - assert update + update = Election.approved_elections(b, 9, [tx_vote2]) + assert len(update) == 1 + update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n') assert update_public_key == public_key64 # assert that the public key is not a part of the current validator set @@ -340,22 +335,6 @@ def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys): # ============================================================================ # Helper functions # ============================================================================ -def to_inputs(election, i, ed25519_node_keys): - input0 = election.to_inputs()[i] - votes = election.outputs[i].amount - public_key0 = input0.owners_before[0] - key0 = ed25519_node_keys[public_key0] - return (input0, votes, key0) - - -def gen_vote(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) - return Vote.generate([input_i], - [([election_pub_key], votes_i)], - election_id=election.id)\ - .sign([key_i.private_key]) - def reset_validator_set(b, node_keys, height): validators = [] diff --git a/tests/upsert_validator/test_validator_election.py b/tests/upsert_validator/test_validator_election.py index c1f56fff..205be554 100644 --- a/tests/upsert_validator/test_validator_election.py +++ b/tests/upsert_validator/test_validator_election.py @@ -111,9 +111,9 @@ def test_upsert_validator_invalid_election(b_mock, new_validator, node_key, fixe tx_election.validate(b_mock) -def test_get_status_ongoing(b, ongoing_election, new_validator): +def test_get_status_ongoing(b, ongoing_validator_election, new_validator): status = ValidatorElection.ONGOING - resp = ongoing_election.get_status(b) + resp = ongoing_validator_election.get_status(b) assert resp == status @@ -124,6 +124,9 @@ def test_get_status_concluded(b, concluded_election, new_validator): def test_get_status_inconclusive(b, inconclusive_election, new_validator): + def set_block_height_to_3(): + return {'height': 3} + def custom_mock_get_validators(height): if height >= 3: return [{'pub_key': {'data': 'zL/DasvKulXZzhSNFwx4cLRXKkSM9GPK7Y0nZ4FEylM=', @@ -153,18 +156,19 @@ def test_get_status_inconclusive(b, inconclusive_election, new_validator): 'voting_power': 8}] b.get_validators = custom_mock_get_validators + b.get_latest_block = set_block_height_to_3 status = ValidatorElection.INCONCLUSIVE resp = inconclusive_election.get_status(b) assert resp == status -def test_upsert_validator_show(caplog, ongoing_election, b): +def test_upsert_validator_show(caplog, ongoing_validator_election, b): from bigchaindb.commands.bigchaindb import run_election_show - election_id = ongoing_election.id - public_key = public_key_to_base64(ongoing_election.asset['data']['public_key']['value']) - power = ongoing_election.asset['data']['power'] - node_id = ongoing_election.asset['data']['node_id'] + election_id = ongoing_validator_election.id + public_key = public_key_to_base64(ongoing_validator_election.asset['data']['public_key']['value']) + power = ongoing_validator_election.asset['data']['power'] + node_id = ongoing_validator_election.asset['data']['node_id'] status = ValidatorElection.ONGOING show_args = Namespace(action='show', diff --git a/tests/utils.py b/tests/utils.py index 87c07a79..852be5e6 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,8 +4,10 @@ from functools import singledispatch +from bigchaindb import Vote from bigchaindb.backend.localmongodb.connection import LocalMongoDBConnection from bigchaindb.backend.schema import TABLES +from bigchaindb.elections.election import Election @singledispatch @@ -33,3 +35,20 @@ def generate_block(bigchain): code, message = bigchain.write_transaction(tx, 'broadcast_tx_commit') assert code == 202 time.sleep(2) + + +def to_inputs(election, i, ed25519_node_keys): + input0 = election.to_inputs()[i] + votes = election.outputs[i].amount + public_key0 = input0.owners_before[0] + key0 = ed25519_node_keys[public_key0] + return (input0, votes, key0) + + +def gen_vote(election, i, ed25519_node_keys): + (input_i, votes_i, key_i) = to_inputs(election, i, ed25519_node_keys) + election_pub_key = Election.to_public_key(election.id) + return Vote.generate([input_i], + [([election_pub_key], votes_i)], + election_id=election.id)\ + .sign([key_i.private_key])