Problem: Users trying to use an incompatible version of Tendermint (#2541)

-  Hard-wire the supported Tendermint version(s) right in the code of BigchainDB Server. Check the version of Tendermint and disconnect if Tendermint version is an unsupported one.
- Expose a CLI command bigchaindb tendermint-version to display the supported Tendermint versions.
- PR also takes care the long list of warnings we get when we run tests.
    - Updated deprecated pymongo methods
    - Do not call pytest fixtures directly.
    - Also added the doc for the new cli command
This commit is contained in:
Muawia Khan 2018-09-14 15:37:18 +02:00 committed by Vanshdeep Singh
parent 754730a045
commit bd39076522
13 changed files with 151 additions and 75 deletions

View File

@ -234,7 +234,7 @@ def store_unspent_outputs(conn, *unspent_outputs):
def delete_unspent_outputs(conn, *unspent_outputs):
if unspent_outputs:
return conn.run(
conn.collection('utxos').remove({
conn.collection('utxos').delete_many({
'$or': [{
'$and': [
{'transaction_id': unspent_output['transaction_id']},
@ -258,7 +258,7 @@ def store_pre_commit_state(conn, state):
commit_id = state['commit_id']
return conn.run(
conn.collection('pre_commit')
.update({'commit_id': commit_id}, state, upsert=True)
.replace_one({'commit_id': commit_id}, state, upsert=True)
)

View File

@ -29,6 +29,7 @@ from bigchaindb.commands.utils import (configure_bigchaindb,
from bigchaindb.log import setup_logging
from bigchaindb.tendermint_utils import public_key_from_base64
from bigchaindb.commands.election_types import elections
from bigchaindb.version import __tm_supported_versions__
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@ -280,6 +281,15 @@ def run_start(args):
start(args)
def run_tendermint_version(args):
"""Show the supported Tendermint version(s)"""
supported_tm_ver = {
'description': 'BigchainDB supports the following Tendermint version(s)',
'tendermint': __tm_supported_versions__,
}
print(json.dumps(supported_tm_ver, indent=4, sort_keys=True))
def create_parser():
parser = argparse.ArgumentParser(
description='Control your BigchainDB node.',
@ -360,6 +370,9 @@ def create_parser():
action='store_true',
help='Skip database initialization')
subparsers.add_parser('tendermint-version',
help='Show the Tendermint supported versions')
start_parser.add_argument('--experimental-parallel-validation',
dest='experimental_parallel_validation',
default=False,

View File

@ -20,6 +20,8 @@ from abci.types_pb2 import (
)
from bigchaindb import BigchainDB
from bigchaindb.version import __tm_supported_versions__
from bigchaindb.utils import tendermint_version_is_compatible
from bigchaindb.tendermint_utils import (decode_transaction,
calculate_hash)
from bigchaindb.lib import Block, PreCommitState
@ -115,6 +117,14 @@ class App(BaseApplication):
self.abort_if_abci_chain_is_not_synced()
# Check if BigchainDB supports the Tendermint version
if not (hasattr(request, 'version') and tendermint_version_is_compatible(request.version)):
logger.error(f'Unsupported Tendermint version: {getattr(request, "version", "no version")}.'
f' Currently, BigchainDB only supports {__tm_supported_versions__}. Exiting!')
sys.exit(1)
logger.info(f"Tendermint version: {request.version}")
r = ResponseInfo()
block = self.bigchaindb.get_latest_block()
if block:

View File

@ -9,7 +9,8 @@ import multiprocessing as mp
import json
import setproctitle
from packaging import version
from bigchaindb.version import __tm_supported_versions__
from bigchaindb.tendermint_utils import key_from_base64
from bigchaindb.common.crypto import key_pair_from_ed25519_key
@ -185,3 +186,23 @@ def load_node_key(path):
priv_key = priv_validator['priv_key']['value']
hex_private_key = key_from_base64(priv_key)
return key_pair_from_ed25519_key(hex_private_key)
def tendermint_version_is_compatible(running_tm_ver):
"""
Check Tendermint compatability with BigchainDB server
:param running_tm_ver: Version number of the connected Tendermint instance
:type running_tm_ver: str
:return: True/False depending on the compatability with BigchainDB server
:rtype: bool
"""
# Splitting because version can look like this e.g. 0.22.8-40d6dc2e
tm_ver = running_tm_ver.split('-')
if not tm_ver:
return False
for ver in __tm_supported_versions__:
if version.parse(ver) == version.parse(tm_ver[0]):
return True
return False

View File

@ -4,3 +4,5 @@
__version__ = '2.0.0b5'
__short_version__ = '2.0b5'
# supported Tendermint version
__tm_supported_versions__ = ["0.22.8"]

View File

@ -158,4 +158,17 @@ $ bigchaindb election show ELECTION_ID
status=<status>
```
The election data is the same set of arguments used in the `election new` command that originally triggered the election. `status` takes three possible values, `ongoing`, if the election has not yet reached a 2/3 majority, `concluded`, if the election reached the 2/3 majority needed to pass, or `inconclusive`, if the validator set changed while the election was in process, rendering it undecidable.
The election data is the same set of arguments used in the `election new` command that originally triggered the election. `status` takes three possible values, `ongoing`, if the election has not yet reached a 2/3 majority, `concluded`, if the election reached the 2/3 majority needed to pass, or `inconclusive`, if the validator set changed while the election was in process, rendering it undecidable.
## bigchaindb tendermint-version
Show the Tendermint versions supported by BigchainDB server.
```bash
$ bigchaindb tendermint-version
{
"description": "BigchainDB supports the following Tendermint version(s)",
"tendermint": [
"0.22.8"
]
}
```

View File

@ -58,7 +58,7 @@ def test_write_assets():
cursor = conn.db.assets.find({}, projection={'_id': False})\
.sort('id', pymongo.ASCENDING)
assert cursor.count() == 3
assert cursor.collection.count_documents({}) == 3
assert list(cursor) == assets[:-1]
@ -180,7 +180,7 @@ def test_write_metadata():
cursor = conn.db.metadata.find({}, projection={'_id': False})\
.sort('id', pymongo.ASCENDING)
assert cursor.count() == 3
assert cursor.collection.count_documents({}) == 3
assert list(cursor) == metadata
@ -244,7 +244,7 @@ def test_store_block():
transactions=[])
query.store_block(conn, block._asdict())
cursor = conn.db.blocks.find({}, projection={'_id': False})
assert cursor.count() == 1
assert cursor.collection.count_documents({}) == 1
def test_get_block():
@ -267,14 +267,14 @@ def test_delete_zero_unspent_outputs(db_context, utxoset):
unspent_outputs, utxo_collection = utxoset
delete_res = query.delete_unspent_outputs(db_context.conn)
assert delete_res is None
assert utxo_collection.count() == 3
assert utxo_collection.find(
assert utxo_collection.count_documents({}) == 3
assert utxo_collection.count_documents(
{'$or': [
{'transaction_id': 'a', 'output_index': 0},
{'transaction_id': 'b', 'output_index': 0},
{'transaction_id': 'a', 'output_index': 1},
]}
).count() == 3
) == 3
def test_delete_one_unspent_outputs(db_context, utxoset):
@ -282,15 +282,15 @@ def test_delete_one_unspent_outputs(db_context, utxoset):
unspent_outputs, utxo_collection = utxoset
delete_res = query.delete_unspent_outputs(db_context.conn,
unspent_outputs[0])
assert delete_res['n'] == 1
assert utxo_collection.find(
assert delete_res.raw_result['n'] == 1
assert utxo_collection.count_documents(
{'$or': [
{'transaction_id': 'a', 'output_index': 1},
{'transaction_id': 'b', 'output_index': 0},
]}
).count() == 2
assert utxo_collection.find(
{'transaction_id': 'a', 'output_index': 0}).count() == 0
) == 2
assert utxo_collection.count_documents(
{'transaction_id': 'a', 'output_index': 0}) == 0
def test_delete_many_unspent_outputs(db_context, utxoset):
@ -298,22 +298,22 @@ def test_delete_many_unspent_outputs(db_context, utxoset):
unspent_outputs, utxo_collection = utxoset
delete_res = query.delete_unspent_outputs(db_context.conn,
*unspent_outputs[::2])
assert delete_res['n'] == 2
assert utxo_collection.find(
assert delete_res.raw_result['n'] == 2
assert utxo_collection.count_documents(
{'$or': [
{'transaction_id': 'a', 'output_index': 0},
{'transaction_id': 'b', 'output_index': 0},
]}
).count() == 0
assert utxo_collection.find(
{'transaction_id': 'a', 'output_index': 1}).count() == 1
) == 0
assert utxo_collection.count_documents(
{'transaction_id': 'a', 'output_index': 1}) == 1
def test_store_zero_unspent_output(db_context, utxo_collection):
from bigchaindb.backend import query
res = query.store_unspent_outputs(db_context.conn)
assert res is None
assert utxo_collection.count() == 0
assert utxo_collection.count_documents({}) == 0
def test_store_one_unspent_output(db_context,
@ -322,10 +322,10 @@ def test_store_one_unspent_output(db_context,
res = query.store_unspent_outputs(db_context.conn, unspent_output_1)
assert res.acknowledged
assert len(res.inserted_ids) == 1
assert utxo_collection.find(
assert utxo_collection.count_documents(
{'transaction_id': unspent_output_1['transaction_id'],
'output_index': unspent_output_1['output_index']}
).count() == 1
) == 1
def test_store_many_unspent_outputs(db_context,
@ -334,15 +334,15 @@ def test_store_many_unspent_outputs(db_context,
res = query.store_unspent_outputs(db_context.conn, *unspent_outputs)
assert res.acknowledged
assert len(res.inserted_ids) == 3
assert utxo_collection.find(
assert utxo_collection.count_documents(
{'transaction_id': unspent_outputs[0]['transaction_id']}
).count() == 3
) == 3
def test_get_unspent_outputs(db_context, utxoset):
from bigchaindb.backend import query
cursor = query.get_unspent_outputs(db_context.conn)
assert cursor.count() == 3
assert cursor.collection.count_documents({}) == 3
retrieved_utxoset = list(cursor)
unspent_outputs, utxo_collection = utxoset
assert retrieved_utxoset == list(
@ -361,7 +361,7 @@ def test_store_pre_commit_state(db_context):
query.store_pre_commit_state(db_context.conn, state._asdict())
cursor = db_context.conn.db.pre_commit.find({'commit_id': 'test'},
projection={'_id': False})
assert cursor.count() == 1
assert cursor.collection.count_documents({}) == 1
def test_get_pre_commit_state(db_context):
@ -372,7 +372,7 @@ def test_get_pre_commit_state(db_context):
height=3,
transactions=[])
db_context.conn.db.pre_commit.insert(state._asdict())
db_context.conn.db.pre_commit.insert_one(state._asdict())
resp = query.get_pre_commit_state(db_context.conn, 'test2')
assert resp == state._asdict()

View File

@ -16,7 +16,7 @@ def test_init_creates_db_tables_and_indexes():
init_database()
collection_names = conn.conn[dbname].collection_names()
collection_names = conn.conn[dbname].list_collection_names()
assert set(collection_names) == {
'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'pre_commit',
'validators', 'elections', 'abci_chains',
@ -57,7 +57,7 @@ def test_init_database_is_graceful_if_db_exists():
dbname = bigchaindb.config['database']['name']
# The db is set up by the fixtures
assert dbname in conn.conn.database_names()
assert dbname in conn.conn.list_database_names()
init_database()
@ -75,7 +75,7 @@ def test_create_tables():
schema.create_database(conn, dbname)
schema.create_tables(conn, dbname)
collection_names = conn.conn[dbname].collection_names()
collection_names = conn.conn[dbname].list_collection_names()
assert set(collection_names) == {
'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'validators', 'elections',
'pre_commit', 'abci_chains',
@ -115,6 +115,6 @@ def test_drop(dummy_db):
from bigchaindb.backend import schema
conn = backend.connect()
assert dummy_db in conn.conn.database_names()
assert dummy_db in conn.conn.list_database_names()
schema.drop_database(conn, dummy_db)
assert dummy_db not in conn.conn.database_names()
assert dummy_db not in conn.conn.list_database_names()

View File

@ -11,7 +11,6 @@ from argparse import Namespace
import pytest
from bigchaindb import ValidatorElection
from tests.conftest import node_keys
def test_make_sure_we_dont_remove_any_command():
@ -30,6 +29,7 @@ def test_make_sure_we_dont_remove_any_command():
assert parser.parse_args(['election', 'approve', 'ELECTION_ID', '--private-key',
'TEMP_PATH_TO_PRIVATE_KEY']).command
assert parser.parse_args(['election', 'show', 'ELECTION_ID']).command
assert parser.parse_args(['tendermint-version']).command
@patch('bigchaindb.commands.utils.start')
@ -226,6 +226,9 @@ def test_calling_main(start_mock, monkeypatch):
subparsers.add_parser.assert_any_call('drop', help='Drop the database')
subparsers.add_parser.assert_any_call('start', help='Start BigchainDB')
subparsers.add_parser.assert_any_call('tendermint-version',
help='Show the Tendermint supported '
'versions')
assert start_mock.called is True
@ -468,11 +471,21 @@ def test_election_approve_called_with_bad_key(caplog, b, bad_validator_path, new
'the eligible voters in this election.'
def test_bigchain_tendermint_version(capsys):
from bigchaindb.commands.bigchaindb import run_tendermint_version
args = Namespace(config=None)
_, _ = capsys.readouterr()
run_tendermint_version(args)
output_config = json.loads(capsys.readouterr()[0])
from bigchaindb.version import __tm_supported_versions__
assert len(output_config["tendermint"]) == len(__tm_supported_versions__)
assert sorted(output_config["tendermint"]) == sorted(__tm_supported_versions__)
def mock_get_validators(height):
keys = node_keys()
pub_key = list(keys.keys())[0]
return [
{'public_key': {'value': pub_key,
{'public_key': {'value': "zL/DasvKulXZzhSNFwx4cLRXKkSM9GPK7Y0nZ4FEylM=",
'type': 'ed25519-base64'},
'voting_power': 10}
]

View File

@ -198,16 +198,6 @@ def alice():
return generate_key_pair()
@pytest.fixture
def alice_privkey(alice):
return alice.private_key
@pytest.fixture
def alice_pubkey(alice):
return alice.public_key
@pytest.fixture
def bob():
from bigchaindb.common.crypto import generate_key_pair
@ -300,10 +290,10 @@ def inputs(user_pk, b, alice):
for height in range(1, 4):
transactions = [
Transaction.create(
[alice_pubkey(alice)],
[alice.public_key],
[([user_pk], 1)],
metadata={'msg': random.random()},
).sign([alice_privkey(alice)])
).sign([alice.private_key])
for _ in range(10)
]
tx_ids = [tx.id for tx in transactions]

View File

@ -26,6 +26,7 @@ from bigchaindb.core import (CodeTypeOk,
from bigchaindb.lib import Block
from bigchaindb.upsert_validator.validator_utils import new_validator_set
from bigchaindb.tendermint_utils import public_key_to_base64
from bigchaindb.version import __tm_supported_versions__
pytestmark = pytest.mark.bdb
@ -168,7 +169,7 @@ def test_init_chain_recognizes_new_chain_after_migration(b):
def test_info(b):
r = RequestInfo()
r = RequestInfo(version=__tm_supported_versions__[0])
app = App(b)
res = app.info(r)

View File

@ -11,6 +11,7 @@ import pytest
from abci.server import ProtocolHandler
from abci.encoding import read_messages
from bigchaindb.version import __tm_supported_versions__
from io import BytesIO
@ -24,7 +25,8 @@ def test_app(b, init_chain_request):
app = App(b)
p = ProtocolHandler(app)
data = p.process('info', types.Request(info=types.RequestInfo(version='2')))
data = p.process('info',
types.Request(info=types.RequestInfo(version=__tm_supported_versions__[0])))
res = next(read_messages(BytesIO(data), types.Response))
assert res
assert res.info.last_block_app_hash == b''
@ -139,3 +141,14 @@ def test_post_transaction_responses(tendermint_ws_url, b):
code, message = b.write_transaction(double_spend, mode)
assert code == 500
assert message == 'Transaction validation failed'
@pytest.mark.bdb
def test_exit_when_tm_ver_not_supported(b):
from bigchaindb import App
app = App(b)
p = ProtocolHandler(app)
with pytest.raises(SystemExit):
p.process('info', types.Request(info=types.RequestInfo(version='2')))

View File

@ -173,12 +173,12 @@ def test_update_utxoset(b, signed_create_tx, signed_transfer_tx, db_context):
mongo_client = MongoClient(host=db_context.host, port=db_context.port)
b.update_utxoset(signed_create_tx)
utxoset = mongo_client[db_context.name]['utxos']
assert utxoset.count() == 1
assert utxoset.count_documents({}) == 1
utxo = utxoset.find_one()
assert utxo['transaction_id'] == signed_create_tx.id
assert utxo['output_index'] == 0
b.update_utxoset(signed_transfer_tx)
assert utxoset.count() == 1
assert utxoset.count_documents({}) == 1
utxo = utxoset.find_one()
assert utxo['transaction_id'] == signed_transfer_tx.id
assert utxo['output_index'] == 0
@ -195,7 +195,7 @@ def test_store_transaction(mocker, b, signed_create_tx,
b.store_bulk_transactions([signed_create_tx])
# mongo_client = MongoClient(host=db_context.host, port=db_context.port)
# utxoset = mongo_client[db_context.name]['utxos']
# assert utxoset.count() == 1
# assert utxoset.count_documents({}) == 1
# utxo = utxoset.find_one()
# assert utxo['transaction_id'] == signed_create_tx.id
# assert utxo['output_index'] == 0
@ -217,7 +217,7 @@ def test_store_transaction(mocker, b, signed_create_tx,
mocked_store_metadata.reset_mock()
mocked_store_transaction.reset_mock()
b.store_bulk_transactions([signed_transfer_tx])
# assert utxoset.count() == 1
# assert utxoset.count_documents({}) == 1
# utxo = utxoset.find_one()
# assert utxo['transaction_id'] == signed_transfer_tx.id
# assert utxo['output_index'] == 0
@ -245,7 +245,7 @@ def test_store_bulk_transaction(mocker, b, signed_create_tx,
b.store_bulk_transactions((signed_create_tx,))
# mongo_client = MongoClient(host=db_context.host, port=db_context.port)
# utxoset = mongo_client[db_context.name]['utxos']
# assert utxoset.count() == 1
# assert utxoset.count_documents({}) == 1
# utxo = utxoset.find_one()
# assert utxo['transaction_id'] == signed_create_tx.id
# assert utxo['output_index'] == 0
@ -266,7 +266,7 @@ def test_store_bulk_transaction(mocker, b, signed_create_tx,
mocked_store_metadata.reset_mock()
mocked_store_transactions.reset_mock()
b.store_bulk_transactions((signed_transfer_tx,))
# assert utxoset.count() == 1
# assert utxoset.count_documents({}) == 1
# utxo = utxoset.find_one()
# assert utxo['transaction_id'] == signed_transfer_tx.id
# assert utxo['output_index'] == 0
@ -288,51 +288,51 @@ def test_delete_zero_unspent_outputs(b, utxoset):
unspent_outputs, utxo_collection = utxoset
delete_res = b.delete_unspent_outputs()
assert delete_res is None
assert utxo_collection.count() == 3
assert utxo_collection.find(
assert utxo_collection.count_documents({}) == 3
assert utxo_collection.count_documents(
{'$or': [
{'transaction_id': 'a', 'output_index': 0},
{'transaction_id': 'b', 'output_index': 0},
{'transaction_id': 'a', 'output_index': 1},
]}
).count() == 3
) == 3
@pytest.mark.bdb
def test_delete_one_unspent_outputs(b, utxoset):
unspent_outputs, utxo_collection = utxoset
delete_res = b.delete_unspent_outputs(unspent_outputs[0])
assert delete_res['n'] == 1
assert utxo_collection.find(
assert delete_res.raw_result['n'] == 1
assert utxo_collection.count_documents(
{'$or': [
{'transaction_id': 'a', 'output_index': 1},
{'transaction_id': 'b', 'output_index': 0},
]}
).count() == 2
assert utxo_collection.find(
{'transaction_id': 'a', 'output_index': 0}).count() == 0
) == 2
assert utxo_collection.count_documents(
{'transaction_id': 'a', 'output_index': 0}) == 0
@pytest.mark.bdb
def test_delete_many_unspent_outputs(b, utxoset):
unspent_outputs, utxo_collection = utxoset
delete_res = b.delete_unspent_outputs(*unspent_outputs[::2])
assert delete_res['n'] == 2
assert utxo_collection.find(
assert delete_res.raw_result['n'] == 2
assert utxo_collection.count_documents(
{'$or': [
{'transaction_id': 'a', 'output_index': 0},
{'transaction_id': 'b', 'output_index': 0},
]}
).count() == 0
assert utxo_collection.find(
{'transaction_id': 'a', 'output_index': 1}).count() == 1
) == 0
assert utxo_collection.count_documents(
{'transaction_id': 'a', 'output_index': 1}) == 1
@pytest.mark.bdb
def test_store_zero_unspent_output(b, utxo_collection):
res = b.store_unspent_outputs()
assert res is None
assert utxo_collection.count() == 0
assert utxo_collection.count_documents({}) == 0
@pytest.mark.bdb
@ -340,10 +340,10 @@ def test_store_one_unspent_output(b, unspent_output_1, utxo_collection):
res = b.store_unspent_outputs(unspent_output_1)
assert res.acknowledged
assert len(res.inserted_ids) == 1
assert utxo_collection.find(
assert utxo_collection.count_documents(
{'transaction_id': unspent_output_1['transaction_id'],
'output_index': unspent_output_1['output_index']}
).count() == 1
) == 1
@pytest.mark.bdb
@ -351,9 +351,9 @@ def test_store_many_unspent_outputs(b, unspent_outputs, utxo_collection):
res = b.store_unspent_outputs(*unspent_outputs)
assert res.acknowledged
assert len(res.inserted_ids) == 3
assert utxo_collection.find(
assert utxo_collection.count_documents(
{'transaction_id': unspent_outputs[0]['transaction_id']}
).count() == 3
) == 3
def test_get_utxoset_merkle_root_when_no_utxo(b):