From dd84d4eb6f14a7b45b74e653661bc5ff5b61b4cf Mon Sep 17 00:00:00 2001 From: codegeschrei Date: Mon, 10 Sep 2018 09:53:18 +0200 Subject: [PATCH 1/4] Problem: There are unnecessary markers (#2522) * Problem: we have unused and outdated fixtures Solution: clean up fixtures and tests accordingly * Problem: there are still unused fixtures Solution: remove Merlin keys * Problem: There are unnecessary markers Solution: remove the tendermint marker for tests --- pytest.ini | 2 +- tests/assets/test_digital_assets.py | 3 -- tests/assets/test_divisible_assets.py | 3 -- tests/backend/localmongodb/test_connection.py | 2 +- tests/backend/localmongodb/test_queries.py | 2 +- tests/backend/localmongodb/test_schema.py | 3 -- tests/backend/test_connection.py | 3 -- tests/backend/test_generics.py | 5 --- tests/backend/test_utils.py | 3 -- tests/commands/conftest.py | 6 ++-- tests/commands/test_commands.py | 36 ++++--------------- tests/commands/test_utils.py | 2 -- tests/common/test_memoize.py | 2 +- tests/common/test_schema.py | 5 --- tests/common/test_transaction.py | 2 +- tests/db/test_bigchain_api.py | 23 ------------ tests/tendermint/test_core.py | 2 +- tests/tendermint/test_event_stream.py | 3 -- tests/tendermint/test_fastquery.py | 2 +- tests/tendermint/test_integration.py | 1 - tests/tendermint/test_lib.py | 3 -- tests/tendermint/test_utils.py | 5 --- tests/test_config_utils.py | 3 -- tests/test_core.py | 2 -- tests/test_events.py | 1 - tests/test_txlist.py | 2 -- tests/test_utils.py | 2 -- .../test_validator_election.py | 2 +- .../test_validator_election_vote.py | 7 ---- .../validation/test_transaction_structure.py | 2 -- tests/web/test_assets.py | 4 --- tests/web/test_block_tendermint.py | 28 --------------- tests/web/test_blocks.py | 4 +-- tests/web/test_info.py | 1 + tests/web/test_metadata.py | 4 --- tests/web/test_outputs.py | 9 +---- tests/web/test_parameters.py | 2 -- tests/web/test_server.py | 4 --- tests/web/test_transactions.py | 4 --- tests/web/test_validators.py | 4 --- tests/web/test_websocket_server.py | 2 -- 41 files changed, 21 insertions(+), 184 deletions(-) diff --git a/pytest.ini b/pytest.ini index 006bc2bc..625c3e09 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] testpaths = tests/ norecursedirs = .* *.egg *.egg-info env* devenv* docs -addopts = -m tendermint +addopts = -m "not abci" looponfailroots = bigchaindb tests diff --git a/tests/assets/test_digital_assets.py b/tests/assets/test_digital_assets.py index 029db261..9af59245 100644 --- a/tests/assets/test_digital_assets.py +++ b/tests/assets/test_digital_assets.py @@ -6,9 +6,6 @@ import pytest import random -pytestmark = pytest.mark.tendermint - - def test_asset_transfer(b, signed_create_tx, user_pk, user_sk): from bigchaindb.models import Transaction diff --git a/tests/assets/test_divisible_assets.py b/tests/assets/test_divisible_assets.py index a4d1241a..276495cd 100644 --- a/tests/assets/test_divisible_assets.py +++ b/tests/assets/test_divisible_assets.py @@ -8,9 +8,6 @@ import random from bigchaindb.common.exceptions import DoubleSpend -pytestmark = pytest.mark.tendermint - - # CREATE divisible asset # Single input # Single owners_before diff --git a/tests/backend/localmongodb/test_connection.py b/tests/backend/localmongodb/test_connection.py index 9bb3d9aa..eeb0adb8 100644 --- a/tests/backend/localmongodb/test_connection.py +++ b/tests/backend/localmongodb/test_connection.py @@ -9,7 +9,7 @@ import pymongo from pymongo import MongoClient -pytestmark = [pytest.mark.bdb, pytest.mark.tendermint] +pytestmark = pytest.mark.bdb @pytest.fixture diff --git a/tests/backend/localmongodb/test_queries.py b/tests/backend/localmongodb/test_queries.py index 2262d723..dd8621d4 100644 --- a/tests/backend/localmongodb/test_queries.py +++ b/tests/backend/localmongodb/test_queries.py @@ -10,7 +10,7 @@ import pymongo from bigchaindb.backend import connect, query -pytestmark = [pytest.mark.tendermint, pytest.mark.bdb] +pytestmark = pytest.mark.bdb def test_get_txids_filtered(signed_create_tx, signed_transfer_tx): diff --git a/tests/backend/localmongodb/test_schema.py b/tests/backend/localmongodb/test_schema.py index aeadbe05..0d49c4ec 100644 --- a/tests/backend/localmongodb/test_schema.py +++ b/tests/backend/localmongodb/test_schema.py @@ -5,9 +5,6 @@ import pytest -pytestmark = [pytest.mark.bdb, pytest.mark.tendermint] - - def test_init_creates_db_tables_and_indexes(): import bigchaindb from bigchaindb import backend diff --git a/tests/backend/test_connection.py b/tests/backend/test_connection.py index 71d42458..52b0e4b0 100644 --- a/tests/backend/test_connection.py +++ b/tests/backend/test_connection.py @@ -5,9 +5,6 @@ import pytest -pytestmark = pytest.mark.tendermint - - def test_get_connection_raises_a_configuration_error(monkeypatch): from bigchaindb.common.exceptions import ConfigurationError from bigchaindb.backend import connect diff --git a/tests/backend/test_generics.py b/tests/backend/test_generics.py index 98b56bbf..6ddd7331 100644 --- a/tests/backend/test_generics.py +++ b/tests/backend/test_generics.py @@ -2,14 +2,9 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 -import pytest - from pytest import mark, raises -pytestmark = pytest.mark.tendermint - - @mark.parametrize('schema_func_name,args_qty', ( ('create_database', 1), ('create_tables', 1), diff --git a/tests/backend/test_utils.py b/tests/backend/test_utils.py index 61c123ac..16e390af 100644 --- a/tests/backend/test_utils.py +++ b/tests/backend/test_utils.py @@ -8,9 +8,6 @@ from types import ModuleType import pytest -pytestmark = pytest.mark.tendermint - - @pytest.fixture def mock_module(): return ModuleType('mock_module') diff --git a/tests/commands/conftest.py b/tests/commands/conftest.py index ce09ff24..d4591720 100644 --- a/tests/commands/conftest.py +++ b/tests/commands/conftest.py @@ -27,8 +27,8 @@ def mock_db_init_with_existing_db(monkeypatch): @pytest.fixture def mock_processes_start(monkeypatch): - from bigchaindb.utils import Process - monkeypatch.setattr(Process, 'run', lambda *args: None) + from bigchaindb import start + monkeypatch.setattr(start, 'start', lambda *args: None) @pytest.fixture @@ -56,7 +56,7 @@ def run_start_args(request): @pytest.fixture def mocked_setup_logging(mocker): return mocker.patch( - 'bigchaindb.commands.utils.setup_logging', + 'bigchaindb.log.setup_logging', autospec=True, spec_set=True, ) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 5a5d2deb..6ed54425 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -14,7 +14,6 @@ from bigchaindb import ValidatorElection from tests.conftest import node_keys -@pytest.mark.tendermint def test_make_sure_we_dont_remove_any_command(): # thanks to: http://stackoverflow.com/a/18161115/597097 from bigchaindb.commands.bigchaindb import create_parser @@ -33,7 +32,6 @@ def test_make_sure_we_dont_remove_any_command(): assert parser.parse_args(['upsert-validator', 'show', 'ELECTION_ID']).command -@pytest.mark.tendermint @patch('bigchaindb.commands.utils.start') def test_main_entrypoint(mock_start): from bigchaindb.commands.bigchaindb import main @@ -42,22 +40,21 @@ def test_main_entrypoint(mock_start): assert mock_start.called -def test_bigchain_run_start(mock_run_configure, - mock_processes_start, - mock_db_init_with_existing_db, - mocked_setup_logging): - from bigchaindb import config +@patch('bigchaindb.log.setup_logging') +@patch('bigchaindb.commands.bigchaindb._run_init') +@patch('bigchaindb.config_utils.autoconfigure') +def test_bigchain_run_start(mock_setup_logging, mock_run_init, + mock_autoconfigure, mock_processes_start): from bigchaindb.commands.bigchaindb import run_start args = Namespace(config=None, yes=True, skip_initialize_database=False) run_start(args) - mocked_setup_logging.assert_called_once_with(user_log_config=config['log']) + assert mock_setup_logging.called # TODO Please beware, that if debugging, the "-s" switch for pytest will # interfere with capsys. # See related issue: https://github.com/pytest-dev/pytest/issues/128 -@pytest.mark.tendermint @pytest.mark.usefixtures('ignore_local_config_file') def test_bigchain_show_config(capsys): from bigchaindb.commands.bigchaindb import run_show_config @@ -78,7 +75,6 @@ def test_bigchain_show_config(capsys): assert output_config == config -@pytest.mark.tendermint def test_bigchain_run_init_when_db_exists(mocker, capsys): from bigchaindb.commands.bigchaindb import run_init from bigchaindb.common.exceptions import DatabaseAlreadyExists @@ -98,7 +94,6 @@ def test_bigchain_run_init_when_db_exists(mocker, capsys): ) -@pytest.mark.tendermint def test__run_init(mocker): from bigchaindb.commands.bigchaindb import _run_init bigchain_mock = mocker.patch( @@ -114,7 +109,6 @@ def test__run_init(mocker): connection=bigchain_mock.return_value.connection) -@pytest.mark.tendermint @patch('bigchaindb.backend.schema.drop_database') def test_drop_db_when_assumed_yes(mock_db_drop): from bigchaindb.commands.bigchaindb import run_drop @@ -124,7 +118,6 @@ def test_drop_db_when_assumed_yes(mock_db_drop): assert mock_db_drop.called -@pytest.mark.tendermint @patch('bigchaindb.backend.schema.drop_database') def test_drop_db_when_interactive_yes(mock_db_drop, monkeypatch): from bigchaindb.commands.bigchaindb import run_drop @@ -136,7 +129,6 @@ def test_drop_db_when_interactive_yes(mock_db_drop, monkeypatch): assert mock_db_drop.called -@pytest.mark.tendermint @patch('bigchaindb.backend.schema.drop_database') def test_drop_db_when_db_does_not_exist(mock_db_drop, capsys): from bigchaindb import config @@ -151,7 +143,6 @@ def test_drop_db_when_db_does_not_exist(mock_db_drop, capsys): name=config['database']['name']) -@pytest.mark.tendermint @patch('bigchaindb.backend.schema.drop_database') def test_drop_db_does_not_drop_when_interactive_no(mock_db_drop, monkeypatch): from bigchaindb.commands.bigchaindb import run_drop @@ -166,7 +157,6 @@ def test_drop_db_does_not_drop_when_interactive_no(mock_db_drop, monkeypatch): # TODO Beware if you are putting breakpoints in there, and using the '-s' # switch with pytest. It will just hang. Seems related to the monkeypatching of # input_on_stderr. -@pytest.mark.tendermint def test_run_configure_when_config_does_not_exist(monkeypatch, mock_write_config, mock_generate_key_pair, @@ -179,7 +169,6 @@ def test_run_configure_when_config_does_not_exist(monkeypatch, assert return_value is None -@pytest.mark.tendermint def test_run_configure_when_config_does_exist(monkeypatch, mock_write_config, mock_generate_key_pair, @@ -201,7 +190,6 @@ def test_run_configure_when_config_does_exist(monkeypatch, @pytest.mark.skip -@pytest.mark.tendermint @pytest.mark.parametrize('backend', ( 'localmongodb', )) @@ -235,10 +223,9 @@ def test_run_start_when_db_already_exists(mocker, monkeypatch, run_start_args, mocked_setup_logging): - from bigchaindb import config from bigchaindb.commands.bigchaindb import run_start from bigchaindb.common.exceptions import DatabaseAlreadyExists - mocked_start = mocker.patch('bigchaindb.processes.start') + mocked_start = mocker.patch('bigchaindb.start.start') def mock_run_init(): raise DatabaseAlreadyExists() @@ -246,11 +233,9 @@ def test_run_start_when_db_already_exists(mocker, monkeypatch.setattr( 'bigchaindb.commands.bigchaindb._run_init', mock_run_init) run_start(run_start_args) - mocked_setup_logging.assert_called_once_with(user_log_config=config['log']) assert mocked_start.called -@pytest.mark.tendermint @patch('bigchaindb.commands.utils.start') def test_calling_main(start_mock, monkeypatch): from bigchaindb.commands.bigchaindb import main @@ -295,7 +280,6 @@ def test_recover_db_on_start(mock_run_recover, assert mock_start.called -@pytest.mark.tendermint @pytest.mark.bdb def test_run_recover(b, alice, bob): from bigchaindb.commands.bigchaindb import run_recover @@ -364,7 +348,6 @@ def test_upsert_validator_new_with_tendermint(b, priv_validator_path, user_sk, v assert b.get_transaction(election_id) -@pytest.mark.tendermint @pytest.mark.bdb def test_upsert_validator_new_without_tendermint(caplog, b, priv_validator_path, user_sk): from bigchaindb.commands.bigchaindb import run_upsert_validator_new @@ -389,7 +372,6 @@ def test_upsert_validator_new_without_tendermint(caplog, b, priv_validator_path, assert b.get_transaction(election_id) -@pytest.mark.tendermint @pytest.mark.bdb def test_upsert_validator_new_invalid_election(caplog, b, priv_validator_path, user_sk): from bigchaindb.commands.bigchaindb import run_upsert_validator_new @@ -406,7 +388,6 @@ def test_upsert_validator_new_invalid_election(caplog, b, priv_validator_path, u assert caplog.records[0].msg.__class__ == FileNotFoundError -@pytest.mark.tendermint @pytest.mark.bdb def test_upsert_validator_new_election_invalid_power(caplog, b, priv_validator_path, user_sk): from bigchaindb.commands.bigchaindb import run_upsert_validator_new @@ -456,7 +437,6 @@ def test_upsert_validator_approve_with_tendermint(b, priv_validator_path, user_s @pytest.mark.bdb -@pytest.mark.tendermint def test_upsert_validator_approve_without_tendermint(caplog, b, priv_validator_path, new_validator, node_key): from bigchaindb.commands.bigchaindb import run_upsert_validator_approve from argparse import Namespace @@ -476,7 +456,6 @@ def test_upsert_validator_approve_without_tendermint(caplog, b, priv_validator_p assert b.get_transaction(approval_id) -@pytest.mark.tendermint @pytest.mark.bdb def test_upsert_validator_approve_failure(caplog, b, priv_validator_path, new_validator, node_key): from bigchaindb.commands.bigchaindb import run_upsert_validator_approve @@ -501,7 +480,6 @@ def test_upsert_validator_approve_failure(caplog, b, priv_validator_path, new_va assert caplog.records[0].msg == 'Failed to commit vote' -@pytest.mark.tendermint @pytest.mark.bdb def test_upsert_validator_approve_called_with_bad_key(caplog, b, bad_validator_path, new_validator, node_key): from bigchaindb.commands.bigchaindb import run_upsert_validator_approve diff --git a/tests/commands/test_utils.py b/tests/commands/test_utils.py index 11ff41a8..eeac67fc 100644 --- a/tests/commands/test_utils.py +++ b/tests/commands/test_utils.py @@ -10,8 +10,6 @@ import pytest from unittest.mock import patch -pytestmark = pytest.mark.tendermint - @pytest.fixture def reset_bigchaindb_config(monkeypatch): diff --git a/tests/common/test_memoize.py b/tests/common/test_memoize.py index 3cc7f62b..20c84f31 100644 --- a/tests/common/test_memoize.py +++ b/tests/common/test_memoize.py @@ -10,7 +10,7 @@ from bigchaindb.common.crypto import generate_key_pair from bigchaindb.common.memoize import to_dict, from_dict -pytestmark = [pytest.mark.tendermint, pytest.mark.bdb] +pytestmark = pytest.mark.bdb def test_memoize_to_dict(b): diff --git a/tests/common/test_schema.py b/tests/common/test_schema.py index 93fe2e3c..0ca1be68 100644 --- a/tests/common/test_schema.py +++ b/tests/common/test_schema.py @@ -8,8 +8,6 @@ properties related to validation. from unittest.mock import patch -import pytest - from hypothesis import given from hypothesis_regex import regex from pytest import raises @@ -25,9 +23,6 @@ UNSUPPORTED_CRYPTOCONDITION_TYPES = ( 'preimage-sha-256', 'prefix-sha-256', 'rsa-sha-256') -pytestmark = pytest.mark.tendermint - - ################################################################################ # Test of schema utils diff --git a/tests/common/test_transaction.py b/tests/common/test_transaction.py index fc6444dd..8e5a2b6c 100644 --- a/tests/common/test_transaction.py +++ b/tests/common/test_transaction.py @@ -13,7 +13,7 @@ from cryptoconditions import Ed25519Sha256 from pytest import mark, raises from sha3 import sha3_256 -pytestmark = [mark.tendermint, mark.bdb] +pytestmark = mark.bdb def test_input_serialization(ffill_uri, user_pub): diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 1ec1904c..1af41e37 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -12,7 +12,6 @@ pytestmark = pytest.mark.bdb class TestBigchainApi(object): - @pytest.mark.tendermint def test_get_spent_with_double_spend_detected(self, b, alice): from bigchaindb.models import Transaction from bigchaindb.common.exceptions import DoubleSpend @@ -43,7 +42,6 @@ class TestBigchainApi(object): with pytest.raises(CriticalDoubleSpend): b.get_spent(tx.id, 0) - @pytest.mark.tendermint def test_double_inclusion(self, b, alice): from bigchaindb.models import Transaction from bigchaindb.backend.exceptions import OperationError @@ -56,7 +54,6 @@ class TestBigchainApi(object): with pytest.raises(OperationError): b.store_bulk_transactions([tx]) - @pytest.mark.tendermint def test_text_search(self, b, alice): from bigchaindb.models import Transaction @@ -81,7 +78,6 @@ class TestBigchainApi(object): assert len(assets) == 3 @pytest.mark.usefixtures('inputs') - @pytest.mark.tendermint def test_non_create_input_not_found(self, b, user_pk): from cryptoconditions import Ed25519Sha256 from bigchaindb.common.exceptions import InputDoesNotExist @@ -97,7 +93,6 @@ class TestBigchainApi(object): with pytest.raises(InputDoesNotExist): tx.validate(b) - @pytest.mark.tendermint def test_write_transaction(self, b, user_sk, user_pk, alice, create_tx): from bigchaindb.models import Transaction @@ -120,7 +115,6 @@ class TestBigchainApi(object): class TestTransactionValidation(object): - @pytest.mark.tendermint def test_non_create_input_not_found(self, b, signed_transfer_tx): from bigchaindb.common.exceptions import InputDoesNotExist from bigchaindb.common.transaction import TransactionLink @@ -129,7 +123,6 @@ class TestTransactionValidation(object): with pytest.raises(InputDoesNotExist): b.validate_transaction(signed_transfer_tx) - @pytest.mark.tendermint @pytest.mark.usefixtures('inputs') def test_non_create_valid_input_wrong_owner(self, b, user_pk): from bigchaindb.common.crypto import generate_key_pair @@ -147,7 +140,6 @@ class TestTransactionValidation(object): with pytest.raises(InvalidSignature): b.validate_transaction(tx) - @pytest.mark.tendermint @pytest.mark.usefixtures('inputs') def test_non_create_double_spend(self, b, signed_create_tx, signed_transfer_tx, double_spend_tx): @@ -161,7 +153,6 @@ class TestTransactionValidation(object): class TestMultipleInputs(object): - @pytest.mark.tendermint def test_transfer_single_owner_single_input(self, b, inputs, user_pk, user_sk): from bigchaindb.common import crypto @@ -181,7 +172,6 @@ class TestMultipleInputs(object): assert len(tx.inputs) == 1 assert len(tx.outputs) == 1 - @pytest.mark.tendermint def test_single_owner_before_multiple_owners_after_single_input(self, b, user_sk, user_pk, @@ -203,7 +193,6 @@ class TestMultipleInputs(object): assert len(tx.inputs) == 1 assert len(tx.outputs) == 1 - @pytest.mark.tendermint @pytest.mark.usefixtures('inputs') def test_multiple_owners_before_single_owner_after_single_input(self, b, user_sk, @@ -232,7 +221,6 @@ class TestMultipleInputs(object): assert len(transfer_tx.inputs) == 1 assert len(transfer_tx.outputs) == 1 - @pytest.mark.tendermint @pytest.mark.usefixtures('inputs') def test_multiple_owners_before_multiple_owners_after_single_input(self, b, user_sk, @@ -262,7 +250,6 @@ class TestMultipleInputs(object): assert len(tx.inputs) == 1 assert len(tx.outputs) == 1 - @pytest.mark.tendermint def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto from bigchaindb.common.transaction import TransactionLink @@ -290,7 +277,6 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == [TransactionLink(tx.id, 0)] assert owned_inputs_user2 == [TransactionLink(tx_transfer.id, 0)] - @pytest.mark.tendermint def test_get_owned_ids_single_tx_multiple_outputs(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto @@ -326,7 +312,6 @@ class TestMultipleInputs(object): assert owned_inputs_user2 == [TransactionLink(tx_transfer.id, 0), TransactionLink(tx_transfer.id, 1)] - @pytest.mark.tendermint def test_get_owned_ids_multiple_owners(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto from bigchaindb.common.transaction import TransactionLink @@ -359,7 +344,6 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == owned_inputs_user2 assert not spent_user1 - @pytest.mark.tendermint def test_get_spent_single_tx_single_output(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto from bigchaindb.models import Transaction @@ -386,7 +370,6 @@ class TestMultipleInputs(object): spent_inputs_user1 = b.get_spent(input_txid, 0) assert spent_inputs_user1 == tx - @pytest.mark.tendermint def test_get_spent_single_tx_multiple_outputs(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto from bigchaindb.models import Transaction @@ -424,7 +407,6 @@ class TestMultipleInputs(object): # spendable by BigchainDB assert b.get_spent(tx_create.to_inputs()[2].fulfills.txid, 2) is None - @pytest.mark.tendermint def test_get_spent_multiple_owners(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto from bigchaindb.models import Transaction @@ -461,7 +443,6 @@ class TestMultipleInputs(object): assert b.get_spent(unspent.id, 0) is None -@pytest.mark.tendermint def test_get_outputs_filtered_only_unspent(): from bigchaindb.common.transaction import TransactionLink from bigchaindb.lib import BigchainDB @@ -478,7 +459,6 @@ def test_get_outputs_filtered_only_unspent(): assert out == [TransactionLink('b', 2)] -@pytest.mark.tendermint def test_get_outputs_filtered_only_spent(): from bigchaindb.common.transaction import TransactionLink from bigchaindb.lib import BigchainDB @@ -494,7 +474,6 @@ def test_get_outputs_filtered_only_spent(): assert out == [TransactionLink('b', 2)] -@pytest.mark.tendermint @patch('bigchaindb.fastquery.FastQuery.filter_unspent_outputs') @patch('bigchaindb.fastquery.FastQuery.filter_spent_outputs') def test_get_outputs_filtered(filter_spent, filter_unspent): @@ -512,7 +491,6 @@ def test_get_outputs_filtered(filter_spent, filter_unspent): assert out == get_outputs.return_value -@pytest.mark.tendermint def test_cant_spend_same_input_twice_in_tx(b, alice): """Recreate duplicated fulfillments bug https://github.com/bigchaindb/bigchaindb/issues/1099 @@ -536,7 +514,6 @@ def test_cant_spend_same_input_twice_in_tx(b, alice): tx_transfer_signed.validate(b) -@pytest.mark.tendermint def test_transaction_unicode(b, alice): import copy from bigchaindb.common.utils import serialize diff --git a/tests/tendermint/test_core.py b/tests/tendermint/test_core.py index c98ee1ea..b59cc0c6 100644 --- a/tests/tendermint/test_core.py +++ b/tests/tendermint/test_core.py @@ -28,7 +28,7 @@ from bigchaindb.upsert_validator.validator_utils import new_validator_set from bigchaindb.tendermint_utils import public_key_to_base64 -pytestmark = [pytest.mark.tendermint, pytest.mark.bdb] +pytestmark = pytest.mark.bdb def encode_tx_to_bytes(transaction): diff --git a/tests/tendermint/test_event_stream.py b/tests/tendermint/test_event_stream.py index 365ea1e0..f23eeaba 100644 --- a/tests/tendermint/test_event_stream.py +++ b/tests/tendermint/test_event_stream.py @@ -10,7 +10,6 @@ from aiohttp import ClientSession import pytest -@pytest.mark.tendermint def test_process_event_new_block(): from bigchaindb.event_stream import process_event @@ -48,7 +47,6 @@ def test_process_event_new_block(): assert isinstance(block.data['height'], int) -@pytest.mark.tendermint def test_process_event_empty_block(): from bigchaindb.event_stream import process_event @@ -69,7 +67,6 @@ def test_process_event_empty_block(): assert event_queue.empty() -@pytest.mark.tendermint def test_process_unknown_event(): from bigchaindb.event_stream import process_event diff --git a/tests/tendermint/test_fastquery.py b/tests/tendermint/test_fastquery.py index 20eb1889..bf93850f 100644 --- a/tests/tendermint/test_fastquery.py +++ b/tests/tendermint/test_fastquery.py @@ -8,7 +8,7 @@ from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction -pytestmark = [pytest.mark.bdb, pytest.mark.tendermint] +pytestmark = pytest.mark.bdb @pytest.fixture diff --git a/tests/tendermint/test_integration.py b/tests/tendermint/test_integration.py index e91c2be4..490da16b 100644 --- a/tests/tendermint/test_integration.py +++ b/tests/tendermint/test_integration.py @@ -14,7 +14,6 @@ from abci.encoding import read_messages from io import BytesIO -@pytest.mark.tendermint @pytest.mark.bdb def test_app(b, init_chain_request): from bigchaindb import App diff --git a/tests/tendermint/test_lib.py b/tests/tendermint/test_lib.py index 3ea91329..6b384b33 100644 --- a/tests/tendermint/test_lib.py +++ b/tests/tendermint/test_lib.py @@ -18,9 +18,6 @@ from bigchaindb import backend from bigchaindb.lib import Block -pytestmark = pytest.mark.tendermint - - @pytest.mark.bdb def test_asset_is_separated_from_transaciton(b): import copy diff --git a/tests/tendermint/test_utils.py b/tests/tendermint/test_utils.py index 46b36525..39d0b44e 100644 --- a/tests/tendermint/test_utils.py +++ b/tests/tendermint/test_utils.py @@ -10,11 +10,6 @@ try: except ImportError: from sha3 import sha3_256 -import pytest - - -pytestmark = pytest.mark.tendermint - def test_encode_decode_transaction(b): from bigchaindb.tendermint_utils import (encode_transaction, diff --git a/tests/test_config_utils.py b/tests/test_config_utils.py index 35f0e486..72dc4e29 100644 --- a/tests/test_config_utils.py +++ b/tests/test_config_utils.py @@ -13,9 +13,6 @@ import bigchaindb ORIGINAL_CONFIG = copy.deepcopy(bigchaindb._config) -pytestmark = pytest.mark.tendermint - - @pytest.fixture(scope='function', autouse=True) def clean_config(monkeypatch, request): original_config = copy.deepcopy(ORIGINAL_CONFIG) diff --git a/tests/test_core.py b/tests/test_core.py index 03701267..72e5bc1d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4,8 +4,6 @@ import pytest -pytestmark = pytest.mark.tendermint - @pytest.fixture def config(request, monkeypatch): diff --git a/tests/test_events.py b/tests/test_events.py index 77e9a0f5..ab088b99 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -3,7 +3,6 @@ # Code is Apache-2.0 and docs are CC-BY-4.0 import pytest -pytestmark = pytest.mark.tendermint def test_event_handler(): diff --git a/tests/test_txlist.py b/tests/test_txlist.py index d2192632..8566f73d 100644 --- a/tests/test_txlist.py +++ b/tests/test_txlist.py @@ -8,8 +8,6 @@ This test module defines it's own fixture which is used by all the tests. """ import pytest -pytestmark = pytest.mark.tendermint - @pytest.fixture def txlist(b, user_pk, user2_pk, user_sk, user2_sk): diff --git a/tests/test_utils.py b/tests/test_utils.py index 31549be4..46b01bf7 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -7,8 +7,6 @@ from unittest.mock import patch, call import pytest -pytestmark = pytest.mark.tendermint - @pytest.fixture def mock_queue(monkeypatch): diff --git a/tests/upsert_validator/test_validator_election.py b/tests/upsert_validator/test_validator_election.py index 4c3849e4..bad475a3 100644 --- a/tests/upsert_validator/test_validator_election.py +++ b/tests/upsert_validator/test_validator_election.py @@ -13,7 +13,7 @@ from bigchaindb.common.exceptions import (DuplicateTransaction, MultipleInputsError, InvalidPowerChange) -pytestmark = [pytest.mark.tendermint, pytest.mark.bdb] +pytestmark = pytest.mark.bdb def test_upsert_validator_valid_election(b_mock, new_validator, node_key): diff --git a/tests/upsert_validator/test_validator_election_vote.py b/tests/upsert_validator/test_validator_election_vote.py index 4504977a..c395bbc1 100644 --- a/tests/upsert_validator/test_validator_election_vote.py +++ b/tests/upsert_validator/test_validator_election_vote.py @@ -15,7 +15,6 @@ from tests.utils import generate_block pytestmark = [pytest.mark.execute] -@pytest.mark.tendermint @pytest.mark.bdb def test_upsert_validator_valid_election_vote(b_mock, valid_election, ed25519_node_keys): b_mock.store_bulk_transactions([valid_election]) @@ -34,7 +33,6 @@ def test_upsert_validator_valid_election_vote(b_mock, valid_election, ed25519_no assert vote.validate(b_mock) -@pytest.mark.tendermint @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]) @@ -54,7 +52,6 @@ def test_upsert_validator_valid_non_election_vote(b_mock, valid_election, ed2551 .sign([key0.private_key]) -@pytest.mark.tendermint @pytest.mark.bdb def test_upsert_validator_delegate_election_vote(b_mock, valid_election, ed25519_node_keys): alice = generate_key_pair() @@ -91,7 +88,6 @@ def test_upsert_validator_delegate_election_vote(b_mock, valid_election, ed25519 assert key0_casted_vote.validate(b_mock) -@pytest.mark.tendermint @pytest.mark.bdb def test_upsert_validator_invalid_election_vote(b_mock, valid_election, ed25519_node_keys): b_mock.store_bulk_transactions([valid_election]) @@ -112,7 +108,6 @@ def test_upsert_validator_invalid_election_vote(b_mock, valid_election, ed25519_ assert vote.validate(b_mock) -@pytest.mark.tendermint @pytest.mark.bdb def test_valid_election_votes_received(b_mock, valid_election, ed25519_node_keys): alice = generate_key_pair() @@ -158,7 +153,6 @@ def test_valid_election_votes_received(b_mock, valid_election, ed25519_node_keys assert valid_election.get_commited_votes(b_mock) == votes-2 -@pytest.mark.tendermint @pytest.mark.bdb def test_valid_election_conclude(b_mock, valid_election, ed25519_node_keys): @@ -272,7 +266,6 @@ def test_upsert_validator(b, node_key, node_keys, ed25519_node_keys): assert (public_key64 in validator_pub_keys) -@pytest.mark.tendermint @pytest.mark.bdb def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys): reset_validator_set(b, node_keys, 1) diff --git a/tests/validation/test_transaction_structure.py b/tests/validation/test_transaction_structure.py index cecb51d7..32622c87 100644 --- a/tests/validation/test_transaction_structure.py +++ b/tests/validation/test_transaction_structure.py @@ -17,8 +17,6 @@ from bigchaindb.common.exceptions import (AmountError, ThresholdTooDeep) from bigchaindb.models import Transaction -pytestmark = pytest.mark.tendermint - ################################################################################ # Helper functions diff --git a/tests/web/test_assets.py b/tests/web/test_assets.py index 2c658cdc..a5d3260e 100644 --- a/tests/web/test_assets.py +++ b/tests/web/test_assets.py @@ -7,7 +7,6 @@ import pytest ASSETS_ENDPOINT = '/api/v1/assets/' -@pytest.mark.tendermint def test_get_assets_with_empty_text_search(client): res = client.get(ASSETS_ENDPOINT + '?search=') assert res.json == {'status': 400, @@ -15,14 +14,12 @@ def test_get_assets_with_empty_text_search(client): assert res.status_code == 400 -@pytest.mark.tendermint def test_get_assets_with_missing_text_search(client): res = client.get(ASSETS_ENDPOINT) assert res.status_code == 400 @pytest.mark.bdb -@pytest.mark.tendermint def test_get_assets_tendermint(client, b, alice): from bigchaindb.models import Transaction @@ -49,7 +46,6 @@ def test_get_assets_tendermint(client, b, alice): @pytest.mark.bdb -@pytest.mark.tendermint def test_get_assets_limit_tendermint(client, b, alice): from bigchaindb.models import Transaction diff --git a/tests/web/test_block_tendermint.py b/tests/web/test_block_tendermint.py index a92822c4..2f789c55 100644 --- a/tests/web/test_block_tendermint.py +++ b/tests/web/test_block_tendermint.py @@ -9,8 +9,6 @@ from bigchaindb.lib import Block BLOCKS_ENDPOINT = '/api/v1/blocks/' -pytestmark = pytest.mark.tendermint - @pytest.mark.bdb @pytest.mark.usefixtures('inputs') @@ -73,29 +71,3 @@ def test_get_blocks_by_txid_endpoint_returns_empty_list_not_found(client): res = client.get(BLOCKS_ENDPOINT + '?transaction_id=123') assert res.status_code == 200 assert len(res.json) == 0 - - -@pytest.mark.bdb -def test_get_blocks_by_txid_endpoint_returns_400_bad_query_params(client): - res = client.get(BLOCKS_ENDPOINT) - assert res.status_code == 400 - - res = client.get(BLOCKS_ENDPOINT + '?ts_id=123') - assert res.status_code == 400 - assert res.json == { - 'message': { - 'transaction_id': 'Missing required parameter in the JSON body or the post body or the query string' - } - } - - res = client.get(BLOCKS_ENDPOINT + '?transaction_id=123&foo=123') - assert res.status_code == 400 - assert res.json == { - 'message': 'Unknown arguments: foo' - } - - res = client.get(BLOCKS_ENDPOINT + '?transaction_id=123&status=123') - assert res.status_code == 400 - assert res.json == { - 'message': 'Unknown arguments: status' - } diff --git a/tests/web/test_blocks.py b/tests/web/test_blocks.py index aa792b7f..e0130613 100644 --- a/tests/web/test_blocks.py +++ b/tests/web/test_blocks.py @@ -50,7 +50,5 @@ def test_get_blocks_by_txid_endpoint_returns_400_bad_query_params(client): res = client.get(BLOCKS_ENDPOINT + '?transaction_id=123&status=123') assert res.status_code == 400 assert res.json == { - 'message': { - 'status': '123 is not a valid choice' - } + 'message': 'Unknown arguments: status' } diff --git a/tests/web/test_info.py b/tests/web/test_info.py index c2ba9bc3..b91b6f60 100644 --- a/tests/web/test_info.py +++ b/tests/web/test_info.py @@ -43,6 +43,7 @@ def test_api_v1_endpoint(client, wsserver_base_url): 'streams': '{}/api/v1/streams/valid_transactions'.format( wsserver_base_url), 'metadata': '/metadata/', + 'validators': '/validators' } res = client.get('/api/v1') assert res.json == api_v1_info diff --git a/tests/web/test_metadata.py b/tests/web/test_metadata.py index 248ef946..c1e6c5ed 100644 --- a/tests/web/test_metadata.py +++ b/tests/web/test_metadata.py @@ -7,7 +7,6 @@ import pytest METADATA_ENDPOINT = '/api/v1/metadata/' -@pytest.mark.tendermint def test_get_metadata_with_empty_text_search(client): res = client.get(METADATA_ENDPOINT + '?search=') assert res.json == {'status': 400, @@ -15,14 +14,12 @@ def test_get_metadata_with_empty_text_search(client): assert res.status_code == 400 -@pytest.mark.tendermint def test_get_metadata_with_missing_text_search(client): res = client.get(METADATA_ENDPOINT) assert res.status_code == 400 @pytest.mark.bdb -@pytest.mark.tendermint def test_get_metadata_tendermint(client, b, alice): from bigchaindb.models import Transaction @@ -50,7 +47,6 @@ def test_get_metadata_tendermint(client, b, alice): @pytest.mark.bdb -@pytest.mark.tendermint def test_get_metadata_limit_tendermint(client, b, alice): from bigchaindb.models import Transaction diff --git a/tests/web/test_outputs.py b/tests/web/test_outputs.py index 7cf1ab5e..db7b718d 100644 --- a/tests/web/test_outputs.py +++ b/tests/web/test_outputs.py @@ -10,7 +10,6 @@ pytestmark = [pytest.mark.bdb, pytest.mark.usefixtures('inputs')] OUTPUTS_ENDPOINT = '/api/v1/outputs/' -@pytest.mark.tendermint def test_get_outputs_endpoint(client, user_pk): m = MagicMock() m.txid = 'a' @@ -26,7 +25,6 @@ def test_get_outputs_endpoint(client, user_pk): gof.assert_called_once_with(user_pk, None) -@pytest.mark.tendermint def test_get_outputs_endpoint_unspent(client, user_pk): m = MagicMock() m.txid = 'a' @@ -40,7 +38,6 @@ def test_get_outputs_endpoint_unspent(client, user_pk): gof.assert_called_once_with(user_pk, False) -@pytest.mark.tendermint def test_get_outputs_endpoint_spent(client, user_pk): m = MagicMock() m.txid = 'a' @@ -54,13 +51,11 @@ def test_get_outputs_endpoint_spent(client, user_pk): gof.assert_called_once_with(user_pk, True) -@pytest.mark.tendermint def test_get_outputs_endpoint_without_public_key(client): res = client.get(OUTPUTS_ENDPOINT) assert res.status_code == 400 -@pytest.mark.tendermint def test_get_outputs_endpoint_with_invalid_public_key(client): expected = {'message': {'public_key': 'Invalid base58 ed25519 key'}} res = client.get(OUTPUTS_ENDPOINT + '?public_key=abc') @@ -68,7 +63,6 @@ def test_get_outputs_endpoint_with_invalid_public_key(client): assert res.status_code == 400 -@pytest.mark.tendermint def test_get_outputs_endpoint_with_invalid_spent(client, user_pk): expected = {'message': {'spent': 'Boolean value must be "true" or "false" (lowercase)'}} params = '?spent=tru&public_key={}'.format(user_pk) @@ -87,8 +81,7 @@ def test_get_divisble_transactions_returns_500(b, client): TX_ENDPOINT = '/api/v1/transactions' def mine(tx_list): - block = b.create_block(tx_list) - b.write_block(block) + b.store_bulk_transactions(tx_list) alice_priv, alice_pub = crypto.generate_key_pair() bob_priv, bob_pub = crypto.generate_key_pair() diff --git a/tests/web/test_parameters.py b/tests/web/test_parameters.py index 3eb8ab2a..ccf3119b 100644 --- a/tests/web/test_parameters.py +++ b/tests/web/test_parameters.py @@ -4,8 +4,6 @@ import pytest -pytestmark = pytest.mark.tendermint - def test_valid_txid(): from bigchaindb.web.views.parameters import valid_txid diff --git a/tests/web/test_server.py b/tests/web/test_server.py index 5fce3629..215aa980 100644 --- a/tests/web/test_server.py +++ b/tests/web/test_server.py @@ -2,10 +2,6 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 -import pytest - -pytestmark = pytest.mark.tendermint - def test_settings(): import bigchaindb diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index 97386da0..68df7f8b 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -23,7 +23,6 @@ def test_get_transaction_endpoint(client, posted_create_tx): assert res.status_code == 200 -@pytest.mark.tendermint def test_get_transaction_returns_404_if_not_found(client): res = client.get(TX_ENDPOINT + '123') assert res.status_code == 404 @@ -361,7 +360,6 @@ def test_post_wrong_asset_division_transfer_returns_400(b, client, user_pk): assert res.json['message'] == expected_error_message -@pytest.mark.tendermint def test_transactions_get_list_good(client): from functools import partial @@ -388,7 +386,6 @@ def test_transactions_get_list_good(client): ] -@pytest.mark.tendermint def test_transactions_get_list_bad(client): def should_not_be_called(): assert False @@ -405,7 +402,6 @@ def test_transactions_get_list_bad(client): assert client.get(url).status_code == 400 -@pytest.mark.tendermint @patch('requests.post') @pytest.mark.parametrize('mode', [ ('', 'broadcast_tx_async'), diff --git a/tests/web/test_validators.py b/tests/web/test_validators.py index 52420828..0574204f 100644 --- a/tests/web/test_validators.py +++ b/tests/web/test_validators.py @@ -2,10 +2,6 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 -import pytest - -pytestmark = pytest.mark.tendermint - VALIDATORS_ENDPOINT = '/api/v1/validators/' diff --git a/tests/web/test_websocket_server.py b/tests/web/test_websocket_server.py index a3de508a..dff74f21 100644 --- a/tests/web/test_websocket_server.py +++ b/tests/web/test_websocket_server.py @@ -10,8 +10,6 @@ from unittest.mock import patch import pytest -pytestmark = pytest.mark.tendermint - class MockWebSocket: def __init__(self): From c79848d66a684420b717e36bed2ec00281f57855 Mon Sep 17 00:00:00 2001 From: Troy McConaghy Date: Tue, 11 Sep 2018 14:28:55 +0200 Subject: [PATCH 2/4] Problem: Docs say install highest Tendermint version (#2524) Solution: Change the docs to recommend installing a specific Tendermint version. Also update our release process so we change that version number before a release, if necessary. --- RELEASE_PROCESS.md | 1 + .../run-node-as-processes.md | 2 +- docs/server/source/simple-deployment-template/network-setup.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md index f5c7af39..3c24dedf 100644 --- a/RELEASE_PROCESS.md +++ b/RELEASE_PROCESS.md @@ -45,6 +45,7 @@ The following steps are what we do to release a new version of _BigchainDB Serve - In `bigchaindb/version.py`: - update `__version__` to e.g. `0.9.0` (with no `.dev` on the end) - update `__short_version__` to e.g. `0.9` (with no `.dev` on the end) + - In the docs about installing BigchainDB (and Tendermint), and in the associated scripts, recommend/install a version of Tendermint that _actually works_ with the soon-to-be-released version of BigchainDB. You can find all such references by doing a search for the previously-recommended version number, such as `0.22.8`. - In `setup.py`, _maybe_ update the development status item in the `classifiers` list. For example, one allowed value is `"Development Status :: 5 - Production/Stable"`. The [allowed values are listed at pypi.python.org](https://pypi.python.org/pypi?%3Aaction=list_classifiers). 1. **Wait for all the tests to pass!** diff --git a/docs/contributing/source/dev-setup-coding-and-contribution-process/run-node-as-processes.md b/docs/contributing/source/dev-setup-coding-and-contribution-process/run-node-as-processes.md index 7c1556a3..98a5aeb8 100644 --- a/docs/contributing/source/dev-setup-coding-and-contribution-process/run-node-as-processes.md +++ b/docs/contributing/source/dev-setup-coding-and-contribution-process/run-node-as-processes.md @@ -25,7 +25,7 @@ After the installation of MongoDB is complete, run MongoDB using `sudo mongod` ### Installing a Tendermint Executable -Find [the version number of the latest Tendermint release](https://github.com/tendermint/tendermint/releases) and install it using the following, where 0.22.8 should be replaced by the latest released version number: +The version of BigchainDB Server described in these docs only works well with Tendermint 0.22.8 (not a higher version number). Install that: ```bash $ sudo apt install -y unzip diff --git a/docs/server/source/simple-deployment-template/network-setup.md b/docs/server/source/simple-deployment-template/network-setup.md index 6b8057ef..ed6498fc 100644 --- a/docs/server/source/simple-deployment-template/network-setup.md +++ b/docs/server/source/simple-deployment-template/network-setup.md @@ -85,7 +85,7 @@ Note: The `mongodb` package is _not_ the official MongoDB package from MongoDB t #### Install Tendermint -Install a [recent version of Tendermint][tendermint:releases]. BigchainDB Server requires version 0.22.8 or newer. +The version of BigchainDB Server described in these docs only works well with Tendermint 0.22.8 (not a higher version number). Install that: ``` sudo apt install -y unzip From 0fe749d830a88ceaaf285e9bc89ac4b9e7d90962 Mon Sep 17 00:00:00 2001 From: Zachary Bowen Date: Tue, 11 Sep 2018 15:39:46 +0200 Subject: [PATCH 3/4] 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. --- bigchaindb/__init__.py | 6 +- bigchaindb/backend/localmongodb/query.py | 16 +- bigchaindb/backend/localmongodb/schema.py | 10 + bigchaindb/backend/query.py | 13 +- bigchaindb/backend/schema.py | 2 +- bigchaindb/commands/bigchaindb.py | 9 +- bigchaindb/common/schema/__init__.py | 3 +- .../common/schema/transaction_v2.0.yaml | 2 +- .../transaction_validator_election_v2.0.yaml | 2 + ...e_v2.0.yaml => transaction_vote_v2.0.yaml} | 4 +- bigchaindb/core.py | 11 +- bigchaindb/elections/__init__.py | 0 bigchaindb/elections/election.py | 254 +++++++++++++++++ .../vote.py} | 20 +- bigchaindb/lib.py | 20 +- bigchaindb/upsert_validator/__init__.py | 1 - .../upsert_validator/validator_election.py | 260 ++---------------- tests/backend/localmongodb/test_schema.py | 11 +- tests/tendermint/test_core.py | 6 +- tests/tendermint/test_lib.py | 6 +- tests/upsert_validator/conftest.py | 28 +- ..._vote.py => test_upsert_validator_vote.py} | 110 ++++---- .../test_validator_election.py | 8 +- tests/web/test_validators.py | 2 +- 24 files changed, 448 insertions(+), 356 deletions(-) rename bigchaindb/common/schema/{transaction_validator_election_vote_v2.0.yaml => transaction_vote_v2.0.yaml} (87%) create mode 100644 bigchaindb/elections/__init__.py create mode 100644 bigchaindb/elections/election.py rename bigchaindb/{upsert_validator/validator_election_vote.py => elections/vote.py} (79%) rename tests/upsert_validator/{test_validator_election_vote.py => test_upsert_validator_vote.py} (73%) diff --git a/bigchaindb/__init__.py b/bigchaindb/__init__.py index 5e69b684..e1762f5d 100644 --- a/bigchaindb/__init__.py +++ b/bigchaindb/__init__.py @@ -94,9 +94,9 @@ _config = copy.deepcopy(config) from bigchaindb.common.transaction import Transaction # noqa from bigchaindb import models # 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.TRANSFER, models.Transaction) -Transaction.register_type(ValidatorElection.VALIDATOR_ELECTION, ValidatorElection) -Transaction.register_type(ValidatorElectionVote.VALIDATOR_ELECTION_VOTE, ValidatorElectionVote) +Transaction.register_type(ValidatorElection.OPERATION, ValidatorElection) +Transaction.register_type(Vote.OPERATION, Vote) diff --git a/bigchaindb/backend/localmongodb/query.py b/bigchaindb/backend/localmongodb/query.py index 6309c86d..4c016f18 100644 --- a/bigchaindb/backend/localmongodb/query.py +++ b/bigchaindb/backend/localmongodb/query.py @@ -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) def get_validator_set(conn, height=None): query = {} @@ -298,11 +310,11 @@ def get_validator_set(conn, height=None): @register_query(LocalMongoDBConnection) -def get_validator_set_by_election_id(conn, election_id): +def get_election(conn, election_id): query = {'election_id': election_id} cursor = conn.run( - conn.collection('validators') + conn.collection('elections') .find(query, projection={'_id': False}) ) diff --git a/bigchaindb/backend/localmongodb/schema.py b/bigchaindb/backend/localmongodb/schema.py index e58f1def..342a22b7 100644 --- a/bigchaindb/backend/localmongodb/schema.py +++ b/bigchaindb/backend/localmongodb/schema.py @@ -48,6 +48,7 @@ def create_indexes(conn, dbname): create_pre_commit_secondary_index(conn, dbname) create_validators_secondary_index(conn, dbname) create_abci_chains_indexes(conn, dbname) + create_elections_secondary_index(conn, dbname) @register_schema(LocalMongoDBConnection) @@ -144,6 +145,15 @@ def create_abci_chains_indexes(conn, dbname): unique=True,) logger.info('Create `abci_chains.chain_id` secondary index.') + conn.conn[dbname]['abci_chains'].create_index('chain_id', name='chain_id', 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,) diff --git a/bigchaindb/backend/query.py b/bigchaindb/backend/query.py index 7965d5ee..d8f60320 100644 --- a/bigchaindb/backend/query.py +++ b/bigchaindb/backend/query.py @@ -351,6 +351,13 @@ def store_validator_set(conn, validator_update): raise NotImplementedError +@singledispatch +def store_election_results(conn, election): + """Store election results""" + + raise NotImplementedError + + @singledispatch def get_validator_set(conn, height): """Get validator set for a given `height`, if `height` is not specified @@ -361,7 +368,7 @@ def get_validator_set(conn, height): @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 """ @@ -369,13 +376,11 @@ def get_validator_set_by_election_id(conn, election_id): @singledispatch -def get_asset_tokens_for_public_key(connection, asset_id, - public_key, operation): +def get_asset_tokens_for_public_key(connection, asset_id, public_key): """Retrieve a list of tokens of type `asset_id` that are owned by the `public_key`. Args: asset_id (str): Id of the token. public_key (str): base58 encoded public key - operation: filter transaction based on `operation` Returns: Iterator of transaction that list given owner in conditions. """ diff --git a/bigchaindb/backend/schema.py b/bigchaindb/backend/schema.py index 108bd39b..c1d0343d 100644 --- a/bigchaindb/backend/schema.py +++ b/bigchaindb/backend/schema.py @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) # Tables/collections that every backend database must create 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', 'hungarian', 'italian', 'norwegian', 'portuguese', 'romanian', diff --git a/bigchaindb/commands/bigchaindb.py b/bigchaindb/commands/bigchaindb.py index 5f23953b..d548d7b0 100644 --- a/bigchaindb/commands/bigchaindb.py +++ b/bigchaindb/commands/bigchaindb.py @@ -17,9 +17,10 @@ from bigchaindb.utils import load_node_key from bigchaindb.common.exceptions import (DatabaseAlreadyExists, DatabaseDoesNotExist, ValidationError) +from bigchaindb.elections.vote import Vote import bigchaindb from bigchaindb import (backend, ValidatorElection, - BigchainDB, ValidatorElectionVote) + BigchainDB) from bigchaindb.backend import schema from bigchaindb.backend import query 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] election_pub_key = ValidatorElection.to_public_key(tx.id) - approval = ValidatorElectionVote.generate(inputs, - [([election_pub_key], voting_power)], - tx.id).sign([key.private_key]) + approval = Vote.generate(inputs, + [([election_pub_key], voting_power)], + tx.id).sign([key.private_key]) approval.validate(bigchain) resp = bigchain.write_transaction(approval, 'broadcast_tx_commit') diff --git a/bigchaindb/common/schema/__init__.py b/bigchaindb/common/schema/__init__.py index 914e5196..25943675 100644 --- a/bigchaindb/common/schema/__init__.py +++ b/bigchaindb/common/schema/__init__.py @@ -37,8 +37,7 @@ _, TX_SCHEMA_TRANSFER = _load_schema('transaction_transfer_' + _, TX_SCHEMA_VALIDATOR_ELECTION = _load_schema('transaction_validator_election_' + TX_SCHEMA_VERSION) -_, TX_SCHEMA_VALIDATOR_ELECTION_VOTE = _load_schema('transaction_validator_election_vote_' + - TX_SCHEMA_VERSION) +_, TX_SCHEMA_VOTE = _load_schema('transaction_vote_' + TX_SCHEMA_VERSION) def _validate_schema(schema, body): diff --git a/bigchaindb/common/schema/transaction_v2.0.yaml b/bigchaindb/common/schema/transaction_v2.0.yaml index acc8c6b5..562c0d86 100644 --- a/bigchaindb/common/schema/transaction_v2.0.yaml +++ b/bigchaindb/common/schema/transaction_v2.0.yaml @@ -63,7 +63,7 @@ definitions: - CREATE - TRANSFER - VALIDATOR_ELECTION - - VALIDATOR_ELECTION_VOTE + - VOTE asset: type: object additionalProperties: false diff --git a/bigchaindb/common/schema/transaction_validator_election_v2.0.yaml b/bigchaindb/common/schema/transaction_validator_election_v2.0.yaml index d849d516..602ef9e2 100644 --- a/bigchaindb/common/schema/transaction_validator_election_v2.0.yaml +++ b/bigchaindb/common/schema/transaction_validator_election_v2.0.yaml @@ -22,6 +22,8 @@ properties: properties: node_id: type: string + seed: + type: string public_key: type: object additionalProperties: false diff --git a/bigchaindb/common/schema/transaction_validator_election_vote_v2.0.yaml b/bigchaindb/common/schema/transaction_vote_v2.0.yaml similarity index 87% rename from bigchaindb/common/schema/transaction_validator_election_vote_v2.0.yaml rename to bigchaindb/common/schema/transaction_vote_v2.0.yaml index c17fb229..5e7c4763 100644 --- a/bigchaindb/common/schema/transaction_validator_election_vote_v2.0.yaml +++ b/bigchaindb/common/schema/transaction_vote_v2.0.yaml @@ -5,14 +5,14 @@ --- "$schema": "http://json-schema.org/draft-04/schema#" type: object -title: Validator Election Vote Schema - Vote on a validator set change +title: Vote Schema - Vote on an election required: - operation - outputs properties: operation: type: string - value: "VALIDATOR_ELECTION_VOTE" + value: "VOTE" outputs: type: array items: diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 075525bc..62bc6ec3 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -100,7 +100,7 @@ class App(BaseApplication): block = Block(app_hash=app_hash, height=height, transactions=[]) 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'] self.bigchaindb.store_abci_chain(abci_chain_height, genesis.chain_id, True) @@ -209,9 +209,10 @@ class App(BaseApplication): # Check if the current block concluded any validator elections and # update the locally tracked validator set - validator_updates = ValidatorElection.get_validator_update(self.bigchaindb, - self.new_height, - self.block_transactions) + validator_update = ValidatorElection.approved_update(self.bigchaindb, + self.new_height, + self.block_transactions) + update = [validator_update] if validator_update else [] # Store pre-commit state to recover in case there is a crash # during `commit` @@ -220,7 +221,7 @@ class App(BaseApplication): transactions=self.block_txn_ids) logger.debug('Updating PreCommitState: %s', self.new_height) self.bigchaindb.store_pre_commit_state(pre_commit_state._asdict()) - return ResponseEndBlock(validator_updates=validator_updates) + return ResponseEndBlock(validator_updates=update) def commit(self): """Store the new height and along with block hash.""" diff --git a/bigchaindb/elections/__init__.py b/bigchaindb/elections/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bigchaindb/elections/election.py b/bigchaindb/elections/election.py new file mode 100644 index 00000000..0c0219bd --- /dev/null +++ b/bigchaindb/elections/election.py @@ -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': , + 'validators': , + 'election_id': + } + """ + 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 diff --git a/bigchaindb/upsert_validator/validator_election_vote.py b/bigchaindb/elections/vote.py similarity index 79% rename from bigchaindb/upsert_validator/validator_election_vote.py rename to bigchaindb/elections/vote.py index 7620b289..01175290 100644 --- a/bigchaindb/upsert_validator/validator_election_vote.py +++ b/bigchaindb/elections/vote.py @@ -6,16 +6,18 @@ from bigchaindb.common.transaction import Transaction from bigchaindb.common.schema import (_validate_schema, TX_SCHEMA_COMMON, 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 # overriden to re-use methods from parent class - TRANSFER = VALIDATOR_ELECTION_VOTE - ALLOWED_OPERATIONS = (VALIDATOR_ELECTION_VOTE,) + TRANSFER = OPERATION + ALLOWED_OPERATIONS = (OPERATION,) + # Custom validation schema + TX_SCHEMA_CUSTOM = TX_SCHEMA_VOTE def validate(self, bigchain, current_transactions=[]): """Validate election vote transaction @@ -28,7 +30,7 @@ class ValidatorElectionVote(Transaction): bigchain (BigchainDB): an instantiated bigchaindb.lib.BigchainDB object. Returns: - ValidatorElectionVote object + Vote: a Vote object Raises: ValidationError: If the election vote is invalid @@ -39,20 +41,20 @@ class ValidatorElectionVote(Transaction): @classmethod def generate(cls, inputs, recipients, election_id, metadata=None): (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) return election_vote @classmethod 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 """ if not skip_id: cls.validate_id(tx) _validate_schema(TX_SCHEMA_COMMON, tx) _validate_schema(TX_SCHEMA_TRANSFER, tx) - _validate_schema(TX_SCHEMA_VALIDATOR_ELECTION_VOTE, tx) + _validate_schema(cls.TX_SCHEMA_CUSTOM, tx) @classmethod def create(cls, tx_signers, recipients, metadata=None, asset=None): diff --git a/bigchaindb/lib.py b/bigchaindb/lib.py index fae4c76a..f6761aa6 100644 --- a/bigchaindb/lib.py +++ b/bigchaindb/lib.py @@ -428,24 +428,20 @@ class BigchainDB(object): result = self.get_validator_change(height) return [] if result is None else result['validators'] - def get_validators_by_election_id(self, election_id): - result = backend.query.get_validator_set_by_election_id(self.connection, election_id) + def get_election(self, election_id): + result = backend.query.get_election(self.connection, election_id) return result - def delete_validator_update(self): - return backend.query.delete_validator_update(self.connection) - def store_pre_commit_state(self, 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`. NOTE: If the validator set already exists at that `height` then an exception will be raised. """ return backend.query.store_validator_set(self.connection, {'height': height, - 'validators': validators, - 'election_id': election_id}) + 'validators': validators}) def store_abci_chain(self, height, chain_id, is_synced=True): 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) + 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')) diff --git a/bigchaindb/upsert_validator/__init__.py b/bigchaindb/upsert_validator/__init__.py index 90a02a0b..0aa49f35 100644 --- a/bigchaindb/upsert_validator/__init__.py +++ b/bigchaindb/upsert_validator/__init__.py @@ -3,5 +3,4 @@ # 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 diff --git a/bigchaindb/upsert_validator/validator_election.py b/bigchaindb/upsert_validator/validator_election.py index 0b36c268..3daf22eb 100644 --- a/bigchaindb/upsert_validator/validator_election.py +++ b/bigchaindb/upsert_validator/validator_election.py @@ -2,264 +2,48 @@ # 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 bigchaindb import backend -from bigchaindb.common.exceptions import (InvalidSignature, - 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) +from bigchaindb.common.exceptions import InvalidPowerChange +from bigchaindb.elections.election import Election +from bigchaindb.common.schema import (TX_SCHEMA_VALIDATOR_ELECTION) +from .validator_utils import (new_validator_set, encode_validator, 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 # by renaming CREATE to VALIDATOR_ELECTION - CREATE = VALIDATOR_ELECTION - ALLOWED_OPERATIONS = (VALIDATOR_ELECTION,) - # Election Statuses: - 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': , - 'asset': { - 'height': , - 'validators': , - 'election_id': - } - } - """ - 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) + CREATE = OPERATION + ALLOWED_OPERATIONS = (OPERATION,) + TX_SCHEMA_CUSTOM = TX_SCHEMA_VALIDATOR_ELECTION def validate(self, bigchain, current_transactions=[]): - """Validate election transaction - 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 + """For more details refer BEP-21: https://github.com/bigchaindb/BEPs/tree/master/21 """ - 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) - # 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') + super(ValidatorElection, self).validate(bigchain, current_transactions=current_transactions) # NOTE: change more than 1/3 of the current power is not allowed if self.asset['data']['power'] >= (1/3)*sum(current_validators.values()): 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 @classmethod - def generate(cls, initiator, voters, election_data, metadata=None): - (inputs, outputs) = cls.validate_create(initiator, voters, election_data, metadata) - 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) + def validate_schema(cls, tx): + super(ValidatorElection, cls).validate_schema(tx) validate_asset_public_key(tx['asset']['data']['public_key']) @classmethod - def create(cls, tx_signers, recipients, metadata=None, asset=None): - raise NotImplementedError + def on_approval(cls, bigchain, election, new_height): + # 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 - 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') == '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 + 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']) diff --git a/tests/backend/localmongodb/test_schema.py b/tests/backend/localmongodb/test_schema.py index 0d49c4ec..21a9ac4f 100644 --- a/tests/backend/localmongodb/test_schema.py +++ b/tests/backend/localmongodb/test_schema.py @@ -21,7 +21,7 @@ def test_init_creates_db_tables_and_indexes(): collection_names = conn.conn[dbname].collection_names() assert set(collection_names) == { 'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'pre_commit', - 'validators', 'abci_chains', + 'validators', 'elections', 'abci_chains', } 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() 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(): import bigchaindb @@ -78,7 +81,7 @@ def test_create_tables(): collection_names = conn.conn[dbname].collection_names() assert set(collection_names) == { - 'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'validators', + 'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'validators', 'elections', 'pre_commit', 'abci_chains', } @@ -117,6 +120,10 @@ def test_create_secondary_indexes(): assert index_info['utxo']['key'] == [('transaction_id', 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() assert set(indexes.keys()) == {'_id_', 'pre_commit_id'} assert indexes['pre_commit_id']['unique'] diff --git a/tests/tendermint/test_core.py b/tests/tendermint/test_core.py index b59cc0c6..31b4e100 100644 --- a/tests/tendermint/test_core.py +++ b/tests/tendermint/test_core.py @@ -366,8 +366,8 @@ def test_end_block_return_validator_updates(b, init_chain_request): resp = app.end_block(RequestEndBlock(height=99)) assert resp.validator_updates[0] == encode_validator(validator) - updates = b.get_validator_update() - assert updates == [] + updates = b.approved_update() + assert not updates 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] 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_validators = [] diff --git a/tests/tendermint/test_lib.py b/tests/tendermint/test_lib.py index 6b384b33..3eec8903 100644 --- a/tests/tendermint/test_lib.py +++ b/tests/tendermint/test_lib.py @@ -161,11 +161,11 @@ def test_validator_updates(b, validator_pub_key): 'update_id': VALIDATOR_UPDATE_ID} query.store_validator_update(b.connection, validator_update) - updates = b.get_validator_update() - assert updates == [validator_update['validator']] + updates = b.approved_updates() + assert updates == validator_update['validator'] b.delete_validator_update() - assert b.get_validator_update() == [] + assert not b.approved_updates() @pytest.mark.bdb diff --git a/tests/upsert_validator/conftest.py b/tests/upsert_validator/conftest.py index c9bb4870..9ab2dad8 100644 --- a/tests/upsert_validator/conftest.py +++ b/tests/upsert_validator/conftest.py @@ -1,10 +1,11 @@ # 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 unittest.mock import patch import pytest -from bigchaindb import ValidatorElectionVote +from bigchaindb import Vote from bigchaindb.backend.localmongodb import query from bigchaindb.lib import Block 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]) +@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 def ongoing_election(b, valid_election, ed25519_node_keys): validators = b.get_validators(height=1) @@ -62,12 +72,10 @@ def ongoing_election(b, valid_election, ed25519_node_keys): @pytest.fixture def concluded_election(b, ongoing_election, ed25519_node_keys): - validators = b.get_validators(height=1) - validator_update = {'validators': validators, - 'height': 2, - 'election_id': ongoing_election.id} + election_result = {'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 @@ -91,9 +99,9 @@ def vote(election, voter, keys, b): election_pub_key = ValidatorElection.to_public_key(election.id) - v = ValidatorElectionVote.generate([election_input], - [([election_pub_key], votes)], - election_id=election.id)\ - .sign([key.private_key]) + v = Vote.generate([election_input], + [([election_pub_key], votes)], + election_id=election.id)\ + .sign([key.private_key]) b.store_bulk_transactions([v]) return v diff --git a/tests/upsert_validator/test_validator_election_vote.py b/tests/upsert_validator/test_upsert_validator_vote.py similarity index 73% rename from tests/upsert_validator/test_validator_election_vote.py rename to tests/upsert_validator/test_upsert_validator_vote.py index c395bbc1..8567397c 100644 --- a/tests/upsert_validator/test_validator_election_vote.py +++ b/tests/upsert_validator/test_upsert_validator_vote.py @@ -6,10 +6,11 @@ import pytest import codecs 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.crypto import generate_key_pair from bigchaindb.common.exceptions import ValidationError +from bigchaindb.elections.vote import Vote from tests.utils import generate_block 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) - vote = ValidatorElectionVote.generate([input0], - [([election_pub_key], votes)], - election_id=valid_election.id)\ - .sign([key0.private_key]) + vote = Vote.generate([input0], + [([election_pub_key], votes)], + election_id=valid_election.id)\ + .sign([key0.private_key]) 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 with pytest.raises(ValidationError): - ValidatorElectionVote.generate([input0], - [([election_pub_key, key0.public_key], votes)], - election_id=valid_election.id)\ - .sign([key0.private_key]) + Vote.generate([input0], + [([election_pub_key, key0.public_key], votes)], + election_id=valid_election.id)\ + .sign([key0.private_key]) @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] key0 = ed25519_node_keys[public_key0] - delegate_vote = ValidatorElectionVote.generate([input0], - [([alice.public_key], 3), ([key0.public_key], votes-3)], - election_id=valid_election.id)\ - .sign([key0.private_key]) + delegate_vote = Vote.generate([input0], + [([alice.public_key], 3), ([key0.public_key], votes-3)], + election_id=valid_election.id)\ + .sign([key0.private_key]) 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) alice_votes = delegate_vote.to_inputs()[0] - alice_casted_vote = ValidatorElectionVote.generate([alice_votes], - [([election_pub_key], 3)], - election_id=valid_election.id)\ - .sign([alice.private_key]) + alice_casted_vote = Vote.generate([alice_votes], + [([election_pub_key], 3)], + election_id=valid_election.id)\ + .sign([alice.private_key]) assert alice_casted_vote.validate(b_mock) key0_votes = delegate_vote.to_inputs()[1] - key0_casted_vote = ValidatorElectionVote.generate([key0_votes], - [([election_pub_key], votes-3)], - election_id=valid_election.id)\ - .sign([key0.private_key]) + key0_casted_vote = Vote.generate([key0_votes], + [([election_pub_key], votes-3)], + election_id=valid_election.id)\ + .sign([key0.private_key]) 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) - vote = ValidatorElectionVote.generate([input0], - [([election_pub_key], votes+1)], - election_id=valid_election.id)\ - .sign([key0.private_key]) + vote = Vote.generate([input0], + [([election_pub_key], votes+1)], + election_id=valid_election.id)\ + .sign([key0.private_key]) with pytest.raises(AmountError): 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] # delegate some votes to alice - delegate_vote = ValidatorElectionVote.generate([input0], - [([alice.public_key], 4), ([key0.public_key], votes-4)], - election_id=valid_election.id)\ - .sign([key0.private_key]) + delegate_vote = Vote.generate([input0], + [([alice.public_key], 4), ([key0.public_key], votes-4)], + election_id=valid_election.id)\ + .sign([key0.private_key]) b_mock.store_bulk_transactions([delegate_vote]) 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] key0_votes = delegate_vote.to_inputs()[1] - alice_casted_vote = ValidatorElectionVote.generate([alice_votes], - [([election_public_key], 2), ([alice.public_key], 2)], - election_id=valid_election.id)\ - .sign([alice.private_key]) + alice_casted_vote = Vote.generate([alice_votes], + [([election_public_key], 2), ([alice.public_key], 2)], + election_id=valid_election.id)\ + .sign([alice.private_key]) assert alice_casted_vote.validate(b_mock) 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 assert valid_election.get_commited_votes(b_mock) == 2 - key0_casted_vote = ValidatorElectionVote.generate([key0_votes], - [([election_public_key], votes-4)], - election_id=valid_election.id)\ - .sign([key0.private_key]) + key0_casted_vote = Vote.generate([key0_votes], + [([election_public_key], votes-4)], + election_id=valid_election.id)\ + .sign([key0.private_key]) assert key0_casted_vote.validate(b_mock) 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() # 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 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 ValidatorElection.has_concluded(b, election.id, [tx_vote0, tx_vote1, tx_vote2]) - assert ValidatorElection.get_validator_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]) + 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_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n') - assert len(update) == 1 + 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') assert update_public_key == public_key64 b.store_bulk_transactions([tx_vote0, tx_vote1]) - update = ValidatorElection.get_validator_update(b, 4, [tx_vote2]) - update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n') - assert len(update) == 1 + update = ValidatorElection.approved_update(b, 4, [tx_vote2]) + assert update + update_public_key = codecs.encode(update.pub_key.data, 'base64').decode().rstrip('\n') assert update_public_key == public_key64 # 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]) - update = ValidatorElection.get_validator_update(b, 9, [tx_vote2]) - update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n') - assert len(update) == 1 + 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 assert update_public_key == public_key64 # 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): (input_i, votes_i, key_i) = to_inputs(election, i, ed25519_node_keys) election_pub_key = ValidatorElection.to_public_key(election.id) - return ValidatorElectionVote.generate([input_i], - [([election_pub_key], votes_i)], - election_id=election.id)\ - .sign([key_i.private_key]) + 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): @@ -360,4 +362,4 @@ def reset_validator_set(b, node_keys, height): validators.append({'public_key': {'type': 'ed25519-base64', 'value': node_pub}, 'voting_power': 10}) - b.store_validator_set(height, validators, 'election_id') + b.store_validator_set(height, validators) diff --git a/tests/upsert_validator/test_validator_election.py b/tests/upsert_validator/test_validator_election.py index bad475a3..fe30331a 100644 --- a/tests/upsert_validator/test_validator_election.py +++ b/tests/upsert_validator/test_validator_election.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 from argparse import Namespace +from unittest.mock import patch import pytest @@ -72,16 +73,17 @@ def test_upsert_validator_invalid_inputs_election(b_mock, new_validator, node_ke 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) duplicate_election = ValidatorElection.generate([node_key.public_key], voters, new_validator, None).sign([node_key.private_key]) 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): duplicate_election.validate(b_mock) diff --git a/tests/web/test_validators.py b/tests/web/test_validators.py index 0574204f..1daeb18f 100644 --- a/tests/web/test_validators.py +++ b/tests/web/test_validators.py @@ -10,7 +10,7 @@ def test_get_validators_endpoint(b, client): 'pub_key': {'data': '4E2685D9016126864733225BE00F005515200727FBAB1312FC78C8B76831255A', 'type': 'ed25519'}, 'voting_power': 10}] - b.store_validator_set(23, validator_set, 'election_id') + b.store_validator_set(23, validator_set) res = client.get(VALIDATORS_ENDPOINT) assert is_validator(res.json[0]) From b33e3808a65bd4b9f3f861395378dab29383d08e Mon Sep 17 00:00:00 2001 From: Lev Berman Date: Tue, 11 Sep 2018 16:41:27 +0200 Subject: [PATCH 4/4] Problem: Test fails if Tendermint is not up. (#2530) Solution: Mark the test with the abci mark since it requires running BigchainDB and Tendermint. Fails since the mark cleanup in #2522. --- tests/web/test_outputs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/web/test_outputs.py b/tests/web/test_outputs.py index db7b718d..77a3f258 100644 --- a/tests/web/test_outputs.py +++ b/tests/web/test_outputs.py @@ -71,6 +71,7 @@ def test_get_outputs_endpoint_with_invalid_spent(client, user_pk): assert res.status_code == 400 +@pytest.mark.abci @pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_get_divisble_transactions_returns_500(b, client):