mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge remote-tracking branch 'ssh_upstream/master' into feat/memoize
This commit is contained in:
commit
1333d565a3
@ -5,5 +5,5 @@ RUN pip install --upgrade \
|
|||||||
pycco \
|
pycco \
|
||||||
websocket-client~=0.47.0 \
|
websocket-client~=0.47.0 \
|
||||||
pytest~=3.0 \
|
pytest~=3.0 \
|
||||||
bigchaindb-driver==0.5.1 \
|
bigchaindb-driver==0.5.2 \
|
||||||
blns
|
blns
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
import time
|
|
||||||
import logging
|
import logging
|
||||||
from ssl import CERT_REQUIRED
|
from ssl import CERT_REQUIRED
|
||||||
|
|
||||||
@ -88,23 +87,6 @@ class LocalMongoDBConnection(Connection):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.replicaset:
|
|
||||||
# we should only return a connection if the replica set is
|
|
||||||
# initialized. initialize_replica_set will check if the
|
|
||||||
# replica set is initialized else it will initialize it.
|
|
||||||
initialize_replica_set(self.host,
|
|
||||||
self.port,
|
|
||||||
self.connection_timeout,
|
|
||||||
self.dbname,
|
|
||||||
self.ssl,
|
|
||||||
self.login,
|
|
||||||
self.password,
|
|
||||||
self.ca_cert,
|
|
||||||
self.certfile,
|
|
||||||
self.keyfile,
|
|
||||||
self.keyfile_passphrase,
|
|
||||||
self.crlfile)
|
|
||||||
|
|
||||||
# FYI: the connection process might raise a
|
# FYI: the connection process might raise a
|
||||||
# `ServerSelectionTimeoutError`, that is a subclass of
|
# `ServerSelectionTimeoutError`, that is a subclass of
|
||||||
# `ConnectionFailure`.
|
# `ConnectionFailure`.
|
||||||
@ -140,8 +122,6 @@ class LocalMongoDBConnection(Connection):
|
|||||||
|
|
||||||
return client
|
return client
|
||||||
|
|
||||||
# `initialize_replica_set` might raise `ConnectionFailure`,
|
|
||||||
# `OperationFailure` or `ConfigurationError`.
|
|
||||||
except (pymongo.errors.ConnectionFailure,
|
except (pymongo.errors.ConnectionFailure,
|
||||||
pymongo.errors.OperationFailure) as exc:
|
pymongo.errors.OperationFailure) as exc:
|
||||||
logger.info('Exception in _connect(): {}'.format(exc))
|
logger.info('Exception in _connect(): {}'.format(exc))
|
||||||
@ -153,120 +133,3 @@ class LocalMongoDBConnection(Connection):
|
|||||||
MONGO_OPTS = {
|
MONGO_OPTS = {
|
||||||
'socketTimeoutMS': 20000,
|
'socketTimeoutMS': 20000,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def initialize_replica_set(host, port, connection_timeout, dbname, ssl, login,
|
|
||||||
password, ca_cert, certfile, keyfile,
|
|
||||||
keyfile_passphrase, crlfile):
|
|
||||||
"""Initialize a replica set. If already initialized skip."""
|
|
||||||
|
|
||||||
# Setup a MongoDB connection
|
|
||||||
# The reason we do this instead of `backend.connect` is that
|
|
||||||
# `backend.connect` will connect you to a replica set but this fails if
|
|
||||||
# you try to connect to a replica set that is not yet initialized
|
|
||||||
try:
|
|
||||||
# The presence of ca_cert, certfile, keyfile, crlfile implies the
|
|
||||||
# use of certificates for TLS connectivity.
|
|
||||||
if ca_cert is None or certfile is None or keyfile is None or \
|
|
||||||
crlfile is None:
|
|
||||||
conn = pymongo.MongoClient(host,
|
|
||||||
port,
|
|
||||||
serverselectiontimeoutms=connection_timeout,
|
|
||||||
ssl=ssl,
|
|
||||||
**MONGO_OPTS)
|
|
||||||
if login is not None and password is not None:
|
|
||||||
conn[dbname].authenticate(login, password)
|
|
||||||
else:
|
|
||||||
logger.info('Connecting to MongoDB over TLS/SSL...')
|
|
||||||
conn = pymongo.MongoClient(host,
|
|
||||||
port,
|
|
||||||
serverselectiontimeoutms=connection_timeout,
|
|
||||||
ssl=ssl,
|
|
||||||
ssl_ca_certs=ca_cert,
|
|
||||||
ssl_certfile=certfile,
|
|
||||||
ssl_keyfile=keyfile,
|
|
||||||
ssl_pem_passphrase=keyfile_passphrase,
|
|
||||||
ssl_crlfile=crlfile,
|
|
||||||
ssl_cert_reqs=CERT_REQUIRED,
|
|
||||||
**MONGO_OPTS)
|
|
||||||
if login is not None:
|
|
||||||
logger.info('Authenticating to the database...')
|
|
||||||
conn[dbname].authenticate(login, mechanism='MONGODB-X509')
|
|
||||||
|
|
||||||
except (pymongo.errors.ConnectionFailure,
|
|
||||||
pymongo.errors.OperationFailure) as exc:
|
|
||||||
logger.info('Exception in _connect(): {}'.format(exc))
|
|
||||||
raise ConnectionError(str(exc)) from exc
|
|
||||||
except pymongo.errors.ConfigurationError as exc:
|
|
||||||
raise ConfigurationError from exc
|
|
||||||
|
|
||||||
_check_replica_set(conn)
|
|
||||||
host = '{}:{}'.format(bigchaindb.config['database']['host'],
|
|
||||||
bigchaindb.config['database']['port'])
|
|
||||||
config = {'_id': bigchaindb.config['database']['replicaset'],
|
|
||||||
'members': [{'_id': 0, 'host': host}]}
|
|
||||||
|
|
||||||
try:
|
|
||||||
conn.admin.command('replSetInitiate', config)
|
|
||||||
except pymongo.errors.OperationFailure as exc_info:
|
|
||||||
if exc_info.details['codeName'] == 'AlreadyInitialized':
|
|
||||||
return
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
_wait_for_replica_set_initialization(conn)
|
|
||||||
logger.info('Initialized replica set')
|
|
||||||
finally:
|
|
||||||
if conn is not None:
|
|
||||||
logger.info('Closing initial connection to MongoDB')
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
def _check_replica_set(conn):
|
|
||||||
"""Checks if the replSet option was enabled either through the command
|
|
||||||
line option or config file and if it matches the one provided by
|
|
||||||
bigchaindb configuration.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
The setting we are looking for will have a different name depending
|
|
||||||
if it was set by the config file (`replSetName`) or by command
|
|
||||||
line arguments (`replSet`).
|
|
||||||
|
|
||||||
Raise:
|
|
||||||
:exc:`~ConfigurationError`: If mongod was not started with the
|
|
||||||
replSet option.
|
|
||||||
"""
|
|
||||||
options = conn.admin.command('getCmdLineOpts')
|
|
||||||
try:
|
|
||||||
repl_opts = options['parsed']['replication']
|
|
||||||
repl_set_name = repl_opts.get('replSetName', repl_opts.get('replSet'))
|
|
||||||
except KeyError:
|
|
||||||
raise ConfigurationError('mongod was not started with'
|
|
||||||
' the replSet option.')
|
|
||||||
|
|
||||||
bdb_repl_set_name = bigchaindb.config['database']['replicaset']
|
|
||||||
if repl_set_name != bdb_repl_set_name:
|
|
||||||
raise ConfigurationError('The replicaset configuration of '
|
|
||||||
'bigchaindb (`{}`) needs to match '
|
|
||||||
'the replica set name from MongoDB'
|
|
||||||
' (`{}`)'.format(bdb_repl_set_name,
|
|
||||||
repl_set_name))
|
|
||||||
|
|
||||||
|
|
||||||
def _wait_for_replica_set_initialization(conn):
|
|
||||||
"""Wait for a replica set to finish initialization.
|
|
||||||
|
|
||||||
If a replica set is being initialized for the first time it takes some
|
|
||||||
time. Nodes need to discover each other and an election needs to take
|
|
||||||
place. During this time the database is not writable so we need to wait
|
|
||||||
before continuing with the rest of the initialization
|
|
||||||
"""
|
|
||||||
|
|
||||||
# I did not find a better way to do this for now.
|
|
||||||
# To check if the database is ready we will poll the mongodb logs until
|
|
||||||
# we find the line that says the database is ready
|
|
||||||
logger.info('Waiting for mongodb replica set initialization')
|
|
||||||
while True:
|
|
||||||
logs = conn.admin.command('getLog', 'rs')['log']
|
|
||||||
if any('database writes are now permitted' in line for line in logs):
|
|
||||||
return
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|||||||
@ -294,7 +294,19 @@ def get_validator_set(conn, height=None):
|
|||||||
.limit(1)
|
.limit(1)
|
||||||
)
|
)
|
||||||
|
|
||||||
return list(cursor)[0]
|
return next(cursor, None)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_validator_set_by_election_id(conn, election_id):
|
||||||
|
query = {'election_id': election_id}
|
||||||
|
|
||||||
|
cursor = conn.run(
|
||||||
|
conn.collection('validators')
|
||||||
|
.find(query, projection={'_id': False})
|
||||||
|
)
|
||||||
|
|
||||||
|
return next(cursor, None)
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
@ -308,3 +320,23 @@ def get_asset_tokens_for_public_key(conn, asset_id, public_key):
|
|||||||
{'$project': {'_id': False}}
|
{'$project': {'_id': False}}
|
||||||
]))
|
]))
|
||||||
return cursor
|
return cursor
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_abci_chain(conn, height, chain_id, is_synced=True):
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('abci_chains').replace_one(
|
||||||
|
{'height': height},
|
||||||
|
{'height': height, 'chain_id': chain_id,
|
||||||
|
'is_synced': is_synced},
|
||||||
|
upsert=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_latest_abci_chain(conn):
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('abci_chains')
|
||||||
|
.find_one(projection={'_id': False}, sort=[('height', DESCENDING)])
|
||||||
|
)
|
||||||
|
|||||||
@ -47,6 +47,7 @@ def create_indexes(conn, dbname):
|
|||||||
create_utxos_secondary_index(conn, dbname)
|
create_utxos_secondary_index(conn, dbname)
|
||||||
create_pre_commit_secondary_index(conn, dbname)
|
create_pre_commit_secondary_index(conn, dbname)
|
||||||
create_validators_secondary_index(conn, dbname)
|
create_validators_secondary_index(conn, dbname)
|
||||||
|
create_abci_chains_indexes(conn, dbname)
|
||||||
|
|
||||||
|
|
||||||
@register_schema(LocalMongoDBConnection)
|
@register_schema(LocalMongoDBConnection)
|
||||||
@ -133,3 +134,16 @@ def create_validators_secondary_index(conn, dbname):
|
|||||||
conn.conn[dbname]['validators'].create_index('height',
|
conn.conn[dbname]['validators'].create_index('height',
|
||||||
name='height',
|
name='height',
|
||||||
unique=True,)
|
unique=True,)
|
||||||
|
|
||||||
|
|
||||||
|
def create_abci_chains_indexes(conn, dbname):
|
||||||
|
logger.info('Create `abci_chains.height` secondary index.')
|
||||||
|
|
||||||
|
conn.conn[dbname]['abci_chains'].create_index('height',
|
||||||
|
name='height',
|
||||||
|
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)
|
||||||
|
|||||||
@ -360,6 +360,14 @@ def get_validator_set(conn, height):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@singledispatch
|
||||||
|
def get_validator_set_by_election_id(conn, election_id):
|
||||||
|
"""Return a validator set change with the specified election_id
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
def get_asset_tokens_for_public_key(connection, asset_id,
|
def get_asset_tokens_for_public_key(connection, asset_id,
|
||||||
public_key, operation):
|
public_key, operation):
|
||||||
@ -372,3 +380,23 @@ def get_asset_tokens_for_public_key(connection, asset_id,
|
|||||||
Iterator of transaction that list given owner in conditions.
|
Iterator of transaction that list given owner in conditions.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@singledispatch
|
||||||
|
def store_abci_chain(conn, height, chain_id, is_synced=True):
|
||||||
|
"""Create or update an ABCI chain at the given height.
|
||||||
|
Usually invoked in the beginning of the ABCI communications (height=0)
|
||||||
|
or when ABCI client (like Tendermint) is migrated (any height).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
is_synced: True if the chain is known by both ABCI client and server
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@singledispatch
|
||||||
|
def get_latest_abci_chain(conn):
|
||||||
|
"""Returns the ABCI chain stored at the biggest height, if any,
|
||||||
|
None otherwise.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|||||||
@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# Tables/collections that every backend database must create
|
# Tables/collections that every backend database must create
|
||||||
TABLES = ('transactions', 'blocks', 'assets', 'metadata',
|
TABLES = ('transactions', 'blocks', 'assets', 'metadata',
|
||||||
'validators', 'pre_commit', 'utxos')
|
'validators', 'pre_commit', 'utxos', 'abci_chains')
|
||||||
|
|
||||||
VALID_LANGUAGES = ('danish', 'dutch', 'english', 'finnish', 'french', 'german',
|
VALID_LANGUAGES = ('danish', 'dutch', 'english', 'finnish', 'french', 'german',
|
||||||
'hungarian', 'italian', 'norwegian', 'portuguese', 'romanian',
|
'hungarian', 'italian', 'norwegian', 'portuguese', 'romanian',
|
||||||
|
|||||||
@ -27,8 +27,7 @@ from bigchaindb.commands import utils
|
|||||||
from bigchaindb.commands.utils import (configure_bigchaindb,
|
from bigchaindb.commands.utils import (configure_bigchaindb,
|
||||||
input_on_stderr)
|
input_on_stderr)
|
||||||
from bigchaindb.log import setup_logging
|
from bigchaindb.log import setup_logging
|
||||||
from bigchaindb.tendermint_utils import public_key_from_base64
|
from bigchaindb.tendermint_utils import public_key_from_base64, public_key_to_base64
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -191,6 +190,35 @@ def run_upsert_validator_approve(args, bigchain):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def run_upsert_validator_show(args, bigchain):
|
||||||
|
"""Retrieves information about an upsert-validator election
|
||||||
|
|
||||||
|
:param args: dict
|
||||||
|
args = {
|
||||||
|
'election_id': the transaction_id for an election (str)
|
||||||
|
}
|
||||||
|
:param bigchain: an instance of BigchainDB
|
||||||
|
"""
|
||||||
|
|
||||||
|
election = bigchain.get_transaction(args.election_id)
|
||||||
|
if not election:
|
||||||
|
logger.error(f'No election found with election_id {args.election_id}')
|
||||||
|
return
|
||||||
|
|
||||||
|
new_validator = election.asset['data']
|
||||||
|
|
||||||
|
public_key = public_key_to_base64(new_validator['public_key'])
|
||||||
|
power = new_validator['power']
|
||||||
|
node_id = new_validator['node_id']
|
||||||
|
status = election.get_status(bigchain)
|
||||||
|
|
||||||
|
response = f'public_key={public_key}\npower={power}\nnode_id={node_id}\nstatus={status}'
|
||||||
|
|
||||||
|
logger.info(response)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def _run_init():
|
def _run_init():
|
||||||
bdb = bigchaindb.BigchainDB()
|
bdb = bigchaindb.BigchainDB()
|
||||||
|
|
||||||
@ -320,6 +348,12 @@ def create_parser():
|
|||||||
dest='sk',
|
dest='sk',
|
||||||
help='Path to the private key of the election initiator.')
|
help='Path to the private key of the election initiator.')
|
||||||
|
|
||||||
|
show_election_parser = validator_subparser.add_parser('show',
|
||||||
|
help='Provides information about an election.')
|
||||||
|
|
||||||
|
show_election_parser.add_argument('election_id',
|
||||||
|
help='The transaction id of the election you wish to query.')
|
||||||
|
|
||||||
# parsers for showing/exporting config values
|
# parsers for showing/exporting config values
|
||||||
subparsers.add_parser('show-config',
|
subparsers.add_parser('show-config',
|
||||||
help='Show the current configuration')
|
help='Show the current configuration')
|
||||||
|
|||||||
@ -101,7 +101,7 @@ class Input(object):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
fulfillment = self.fulfillment.serialize_uri()
|
fulfillment = self.fulfillment.serialize_uri()
|
||||||
except (TypeError, AttributeError, ASN1EncodeError):
|
except (TypeError, AttributeError, ASN1EncodeError, ASN1DecodeError):
|
||||||
fulfillment = _fulfillment_to_details(self.fulfillment)
|
fulfillment = _fulfillment_to_details(self.fulfillment)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -168,7 +168,7 @@ def _fulfillment_to_details(fulfillment):
|
|||||||
if fulfillment.type_name == 'ed25519-sha-256':
|
if fulfillment.type_name == 'ed25519-sha-256':
|
||||||
return {
|
return {
|
||||||
'type': 'ed25519-sha-256',
|
'type': 'ed25519-sha-256',
|
||||||
'public_key': base58.b58encode(fulfillment.public_key),
|
'public_key': base58.b58encode(fulfillment.public_key).decode(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if fulfillment.type_name == 'threshold-sha-256':
|
if fulfillment.type_name == 'threshold-sha-256':
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
with Tendermint.
|
with Tendermint.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
from abci.application import BaseApplication
|
from abci.application import BaseApplication
|
||||||
from abci.types_pb2 import (
|
from abci.types_pb2 import (
|
||||||
@ -47,22 +48,76 @@ class App(BaseApplication):
|
|||||||
self.block_transactions = []
|
self.block_transactions = []
|
||||||
self.validators = None
|
self.validators = None
|
||||||
self.new_height = None
|
self.new_height = None
|
||||||
|
self.chain = self.bigchaindb.get_latest_abci_chain()
|
||||||
|
|
||||||
|
def log_abci_migration_error(self, chain_id, validators):
|
||||||
|
logger.error(f'An ABCI chain migration is in process. ' +
|
||||||
|
'Download the new ABCI client and configure it with ' +
|
||||||
|
'chain_id={chain_id} and validators={validators}.')
|
||||||
|
|
||||||
|
def abort_if_abci_chain_is_not_synced(self):
|
||||||
|
if self.chain is None or self.chain['is_synced']:
|
||||||
|
return
|
||||||
|
|
||||||
|
validators = self.bigchaindb.get_validators()
|
||||||
|
self.log_abci_migration_error(self.chain['chain_id'], validators)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
def init_chain(self, genesis):
|
def init_chain(self, genesis):
|
||||||
"""Initialize chain with block of height 0"""
|
"""Initialize chain upon genesis or a migration"""
|
||||||
|
|
||||||
validator_set = [vutils.decode_validator(v) for v in genesis.validators]
|
app_hash = ''
|
||||||
block = Block(app_hash='', height=0, transactions=[])
|
height = 0
|
||||||
|
|
||||||
|
known_chain = self.bigchaindb.get_latest_abci_chain()
|
||||||
|
if known_chain is not None:
|
||||||
|
chain_id = known_chain['chain_id']
|
||||||
|
|
||||||
|
if known_chain['is_synced']:
|
||||||
|
msg = f'Got invalid InitChain ABCI request ({genesis}) - ' + \
|
||||||
|
'the chain {chain_id} is already synced.'
|
||||||
|
logger.error(msg)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if chain_id != genesis.chain_id:
|
||||||
|
validators = self.bigchaindb.get_validators()
|
||||||
|
self.log_abci_migration_error(chain_id, validators)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# set migration values for app hash and height
|
||||||
|
block = self.bigchaindb.get_latest_block()
|
||||||
|
app_hash = '' if block is None else block['app_hash']
|
||||||
|
height = 0 if block is None else block['height'] + 1
|
||||||
|
|
||||||
|
known_validators = self.bigchaindb.get_validators()
|
||||||
|
validator_set = [vutils.decode_validator(v)
|
||||||
|
for v in genesis.validators]
|
||||||
|
|
||||||
|
if known_validators and known_validators != validator_set:
|
||||||
|
self.log_abci_migration_error(known_chain['chain_id'],
|
||||||
|
known_validators)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
block = Block(app_hash=app_hash, height=height, transactions=[])
|
||||||
self.bigchaindb.store_block(block._asdict())
|
self.bigchaindb.store_block(block._asdict())
|
||||||
self.bigchaindb.store_validator_set(1, validator_set)
|
self.bigchaindb.store_validator_set(height + 1, validator_set, None)
|
||||||
|
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)
|
||||||
|
self.chain = {'height': abci_chain_height, 'is_synced': True,
|
||||||
|
'chain_id': genesis.chain_id}
|
||||||
return ResponseInitChain()
|
return ResponseInitChain()
|
||||||
|
|
||||||
def info(self, request):
|
def info(self, request):
|
||||||
"""Return height of the latest committed block."""
|
"""Return height of the latest committed block."""
|
||||||
|
|
||||||
|
self.abort_if_abci_chain_is_not_synced()
|
||||||
|
|
||||||
r = ResponseInfo()
|
r = ResponseInfo()
|
||||||
block = self.bigchaindb.get_latest_block()
|
block = self.bigchaindb.get_latest_block()
|
||||||
if block:
|
if block:
|
||||||
r.last_block_height = block['height']
|
chain_shift = 0 if self.chain is None else self.chain['height']
|
||||||
|
r.last_block_height = block['height'] - chain_shift
|
||||||
r.last_block_app_hash = block['app_hash'].encode('utf-8')
|
r.last_block_app_hash = block['app_hash'].encode('utf-8')
|
||||||
else:
|
else:
|
||||||
r.last_block_height = 0
|
r.last_block_height = 0
|
||||||
@ -77,6 +132,8 @@ class App(BaseApplication):
|
|||||||
raw_tx: a raw string (in bytes) transaction.
|
raw_tx: a raw string (in bytes) transaction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.abort_if_abci_chain_is_not_synced()
|
||||||
|
|
||||||
logger.benchmark('CHECK_TX_INIT')
|
logger.benchmark('CHECK_TX_INIT')
|
||||||
logger.debug('check_tx: %s', raw_transaction)
|
logger.debug('check_tx: %s', raw_transaction)
|
||||||
transaction = decode_transaction(raw_transaction)
|
transaction = decode_transaction(raw_transaction)
|
||||||
@ -95,8 +152,11 @@ class App(BaseApplication):
|
|||||||
req_begin_block: block object which contains block header
|
req_begin_block: block object which contains block header
|
||||||
and block hash.
|
and block hash.
|
||||||
"""
|
"""
|
||||||
|
self.abort_if_abci_chain_is_not_synced()
|
||||||
|
|
||||||
|
chain_shift = 0 if self.chain is None else self.chain['height']
|
||||||
logger.benchmark('BEGIN BLOCK, height:%s, num_txs:%s',
|
logger.benchmark('BEGIN BLOCK, height:%s, num_txs:%s',
|
||||||
req_begin_block.header.height,
|
req_begin_block.header.height + chain_shift,
|
||||||
req_begin_block.header.num_txs)
|
req_begin_block.header.num_txs)
|
||||||
|
|
||||||
self.block_txn_ids = []
|
self.block_txn_ids = []
|
||||||
@ -109,6 +169,9 @@ class App(BaseApplication):
|
|||||||
Args:
|
Args:
|
||||||
raw_tx: a raw string (in bytes) transaction.
|
raw_tx: a raw string (in bytes) transaction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.abort_if_abci_chain_is_not_synced()
|
||||||
|
|
||||||
logger.debug('deliver_tx: %s', raw_transaction)
|
logger.debug('deliver_tx: %s', raw_transaction)
|
||||||
transaction = self.bigchaindb.is_valid_transaction(
|
transaction = self.bigchaindb.is_valid_transaction(
|
||||||
decode_transaction(raw_transaction), self.block_transactions)
|
decode_transaction(raw_transaction), self.block_transactions)
|
||||||
@ -130,7 +193,11 @@ class App(BaseApplication):
|
|||||||
height (int): new height of the chain.
|
height (int): new height of the chain.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
height = request_end_block.height
|
self.abort_if_abci_chain_is_not_synced()
|
||||||
|
|
||||||
|
chain_shift = 0 if self.chain is None else self.chain['height']
|
||||||
|
|
||||||
|
height = request_end_block.height + chain_shift
|
||||||
self.new_height = height
|
self.new_height = height
|
||||||
block_txn_hash = calculate_hash(self.block_txn_ids)
|
block_txn_hash = calculate_hash(self.block_txn_ids)
|
||||||
block = self.bigchaindb.get_latest_block()
|
block = self.bigchaindb.get_latest_block()
|
||||||
@ -158,6 +225,8 @@ class App(BaseApplication):
|
|||||||
def commit(self):
|
def commit(self):
|
||||||
"""Store the new height and along with block hash."""
|
"""Store the new height and along with block hash."""
|
||||||
|
|
||||||
|
self.abort_if_abci_chain_is_not_synced()
|
||||||
|
|
||||||
data = self.block_txn_hash.encode('utf-8')
|
data = self.block_txn_hash.encode('utf-8')
|
||||||
|
|
||||||
# register a new block only when new transactions are received
|
# register a new block only when new transactions are received
|
||||||
|
|||||||
@ -426,10 +426,16 @@ class BigchainDB(object):
|
|||||||
def fastquery(self):
|
def fastquery(self):
|
||||||
return fastquery.FastQuery(self.connection)
|
return fastquery.FastQuery(self.connection)
|
||||||
|
|
||||||
|
def get_validator_change(self, height=None):
|
||||||
|
return backend.query.get_validator_set(self.connection, height)
|
||||||
|
|
||||||
def get_validators(self, height=None):
|
def get_validators(self, height=None):
|
||||||
result = backend.query.get_validator_set(self.connection, height)
|
result = self.get_validator_change(height)
|
||||||
validators = result['validators']
|
return [] if result is None else result['validators']
|
||||||
return validators
|
|
||||||
|
def get_validators_by_election_id(self, election_id):
|
||||||
|
result = backend.query.get_validator_set_by_election_id(self.connection, election_id)
|
||||||
|
return result
|
||||||
|
|
||||||
def delete_validator_update(self):
|
def delete_validator_update(self):
|
||||||
return backend.query.delete_validator_update(self.connection)
|
return backend.query.delete_validator_update(self.connection)
|
||||||
@ -437,13 +443,45 @@ class BigchainDB(object):
|
|||||||
def store_pre_commit_state(self, state):
|
def store_pre_commit_state(self, state):
|
||||||
return backend.query.store_pre_commit_state(self.connection, state)
|
return backend.query.store_pre_commit_state(self.connection, state)
|
||||||
|
|
||||||
def store_validator_set(self, height, validators):
|
def store_validator_set(self, height, validators, election_id):
|
||||||
"""Store validator set at a given `height`.
|
"""Store validator set at a given `height`.
|
||||||
NOTE: If the validator set already exists at that `height` then an
|
NOTE: If the validator set already exists at that `height` then an
|
||||||
exception will be raised.
|
exception will be raised.
|
||||||
"""
|
"""
|
||||||
return backend.query.store_validator_set(self.connection, {'height': height,
|
return backend.query.store_validator_set(self.connection, {'height': height,
|
||||||
'validators': validators})
|
'validators': validators,
|
||||||
|
'election_id': election_id})
|
||||||
|
|
||||||
|
def store_abci_chain(self, height, chain_id, is_synced=True):
|
||||||
|
return backend.query.store_abci_chain(self.connection, height,
|
||||||
|
chain_id, is_synced)
|
||||||
|
|
||||||
|
def get_latest_abci_chain(self):
|
||||||
|
return backend.query.get_latest_abci_chain(self.connection)
|
||||||
|
|
||||||
|
def migrate_abci_chain(self):
|
||||||
|
"""Generate and record a new ABCI chain ID. New blocks are not
|
||||||
|
accepted until we receive an InitChain ABCI request with
|
||||||
|
the matching chain ID and validator set.
|
||||||
|
|
||||||
|
Chain ID is generated based on the current chain and height.
|
||||||
|
`chain-X` => `chain-X-migrated-at-height-5`.
|
||||||
|
`chain-X-migrated-at-height-5` => `chain-X-migrated-at-height-21`.
|
||||||
|
|
||||||
|
If there is no known chain (we are at genesis), the function returns.
|
||||||
|
"""
|
||||||
|
latest_chain = self.get_latest_abci_chain()
|
||||||
|
if latest_chain is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
block = self.get_latest_block()
|
||||||
|
|
||||||
|
suffix = '-migrated-at-height-'
|
||||||
|
chain_id = latest_chain['chain_id']
|
||||||
|
block_height_str = str(block['height'])
|
||||||
|
new_chain_id = chain_id.split(suffix)[0] + suffix + block_height_str
|
||||||
|
|
||||||
|
self.store_abci_chain(block['height'] + 1, new_chain_id, False)
|
||||||
|
|
||||||
|
|
||||||
Block = namedtuple('Block', ('app_hash', 'height', 'transactions'))
|
Block = namedtuple('Block', ('app_hash', 'height', 'transactions'))
|
||||||
|
|||||||
@ -29,6 +29,26 @@ class ValidatorElection(Transaction):
|
|||||||
# by renaming CREATE to VALIDATOR_ELECTION
|
# by renaming CREATE to VALIDATOR_ELECTION
|
||||||
CREATE = VALIDATOR_ELECTION
|
CREATE = VALIDATOR_ELECTION
|
||||||
ALLOWED_OPERATIONS = (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': <block_height>,
|
||||||
|
'asset': {
|
||||||
|
'height': <block_height>,
|
||||||
|
'validators': <validator_set>,
|
||||||
|
'election_id': <election_id_that_approved_the_change>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
return bigchain.get_validator_change(height)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_validators(cls, bigchain, height=None):
|
def get_validators(cls, bigchain, height=None):
|
||||||
@ -146,7 +166,7 @@ class ValidatorElection(Transaction):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def to_public_key(cls, election_id):
|
def to_public_key(cls, election_id):
|
||||||
return base58.b58encode(bytes.fromhex(election_id))
|
return base58.b58encode(bytes.fromhex(election_id)).decode()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def count_votes(cls, election_pk, transactions, getter=getattr):
|
def count_votes(cls, election_pk, transactions, getter=getattr):
|
||||||
@ -212,9 +232,27 @@ class ValidatorElection(Transaction):
|
|||||||
validator_updates = [election.asset['data']]
|
validator_updates = [election.asset['data']]
|
||||||
curr_validator_set = bigchain.get_validators(new_height)
|
curr_validator_set = bigchain.get_validators(new_height)
|
||||||
updated_validator_set = new_validator_set(curr_validator_set,
|
updated_validator_set = new_validator_set(curr_validator_set,
|
||||||
new_height, validator_updates)
|
validator_updates)
|
||||||
|
|
||||||
updated_validator_set = [v for v in updated_validator_set if v['voting_power'] > 0]
|
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)
|
bigchain.store_validator_set(new_height+1, updated_validator_set, election.id)
|
||||||
return [encode_validator(election.asset['data'])]
|
return [encode_validator(election.asset['data'])]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def get_validator_update_by_election_id(self, election_id, bigchain):
|
||||||
|
result = bigchain.get_validators_by_election_id(election_id)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_status(self, bigchain):
|
||||||
|
concluded = self.get_validator_update_by_election_id(self.id, bigchain)
|
||||||
|
if concluded:
|
||||||
|
return self.CONCLUDED
|
||||||
|
|
||||||
|
latest_change = self.get_validator_change(bigchain)
|
||||||
|
latest_change_height = latest_change['height']
|
||||||
|
election_height = bigchain.get_block_containing_tx(self.id)[0]
|
||||||
|
|
||||||
|
if latest_change_height >= election_height:
|
||||||
|
return self.INCONCLUSIVE
|
||||||
|
else:
|
||||||
|
return self.ONGOING
|
||||||
|
|||||||
@ -21,7 +21,7 @@ def decode_validator(v):
|
|||||||
'voting_power': v.power}
|
'voting_power': v.power}
|
||||||
|
|
||||||
|
|
||||||
def new_validator_set(validators, height, updates):
|
def new_validator_set(validators, updates):
|
||||||
validators_dict = {}
|
validators_dict = {}
|
||||||
for v in validators:
|
for v in validators:
|
||||||
validators_dict[v['pub_key']['data']] = v
|
validators_dict[v['pub_key']['data']] = v
|
||||||
|
|||||||
@ -12,10 +12,6 @@ Ideally, each node in a BigchainDB network is owned and controlled by a differen
|
|||||||
|
|
||||||
We use the phrase "BigchainDB consortium" (or just "consortium") to refer to the set of people and/or organizations who run the nodes of a BigchainDB network. A consortium requires some form of governance to make decisions such as membership and policies. The exact details of the governance process are determined by each consortium, but it can be very decentralized.
|
We use the phrase "BigchainDB consortium" (or just "consortium") to refer to the set of people and/or organizations who run the nodes of a BigchainDB network. A consortium requires some form of governance to make decisions such as membership and policies. The exact details of the governance process are determined by each consortium, but it can be very decentralized.
|
||||||
|
|
||||||
If sharding is turned on (i.e. if the number of shards is larger than one), then the actual data is decentralized in that no one node stores all the data.
|
|
||||||
|
|
||||||
Every node has its own locally-stored list of the public keys of other consortium members: the so-called keyring. There's no centrally-stored or centrally-shared keyring.
|
|
||||||
|
|
||||||
A consortium can increase its decentralization (and its resilience) by increasing its jurisdictional diversity, geographic diversity, and other kinds of diversity. This idea is expanded upon in [the section on node diversity](diversity.html).
|
A consortium can increase its decentralization (and its resilience) by increasing its jurisdictional diversity, geographic diversity, and other kinds of diversity. This idea is expanded upon in [the section on node diversity](diversity.html).
|
||||||
|
|
||||||
There’s no node that has a long-term special position in the BigchainDB network. All nodes run the same software and perform the same duties.
|
There’s no node that has a long-term special position in the BigchainDB network. All nodes run the same software and perform the same duties.
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
Code is Apache-2.0 and docs are CC-BY-4.0
|
Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
|
.. _permissions-in-bigchaindb:
|
||||||
|
|
||||||
Permissions in BigchainDB
|
Permissions in BigchainDB
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
|||||||
@ -8,16 +8,8 @@ BigchainDB and Smart Contracts
|
|||||||
|
|
||||||
One can store the source code of any smart contract (i.e. a computer program) in BigchainDB, but BigchainDB won't run arbitrary smart contracts.
|
One can store the source code of any smart contract (i.e. a computer program) in BigchainDB, but BigchainDB won't run arbitrary smart contracts.
|
||||||
|
|
||||||
BigchainDB will run the subset of smart contracts expressible using `Crypto-Conditions <https://tools.ietf.org/html/draft-thomas-crypto-conditions-03>`_.
|
BigchainDB can be used to enforce who has permission to transfer assets, both fungible assets and non-fungible assets. It will prevent double-spending. In other words, a BigchainDB network could be used instead of an ERC-20 (fungible token) or ERC-721 (non-fungible token) smart contract.
|
||||||
|
|
||||||
The owners of an asset can impose conditions on it that must be met for the asset to be transferred to new owners. Examples of possible conditions (crypto-conditions) include:
|
Asset transfer permissions can also be interpreted as write permissions, so they can be used to control who can write to a log, journal or audit trail. There is more about that idea in :ref:`the page about permissions in BigchainDB <permissions-in-bigchaindb>`.
|
||||||
|
|
||||||
- The current owner must sign the transfer transaction (one which transfers ownership to new owners).
|
A BigchainDB network can be connected to other blockchain networks, via oracles or inter-chain communications protocols. That means BigchainDB can be used as part of a solution that uses *other* blockchains to run arbitrary smart contracts.
|
||||||
- Three out of five current owners must sign the transfer transaction.
|
|
||||||
- (Shannon and Kelly) or Morgan must sign the transfer transaction.
|
|
||||||
|
|
||||||
Crypto-conditions can be quite complex. They can't include loops or recursion and therefore will always run/check in finite time.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
We used the word "owners" somewhat loosely above. A more accurate word might be fulfillers, signers, controllers, or transfer-enablers. See the section titled **A Note about Owners** in the relevant `BigchainDB Transactions Spec <https://github.com/bigchaindb/BEPs/tree/master/tx-specs/>`_.
|
|
||||||
|
|||||||
@ -139,3 +139,19 @@ $ bigchaindb upsert-validator approve 04a067582cf03eba2b53b82e4adb5ece424474cbd4
|
|||||||
```
|
```
|
||||||
|
|
||||||
If the command succeeds a message will be returned stating that the vote was submitted successfully. Once a proposal has been approved by sufficent validators (more than `2/3` of the total voting power) then the proposed change is applied to the network. For example, consider a network wherein the total power is `90` then the proposed changed applied only after `60` (`2/3 * 90`) have been received.
|
If the command succeeds a message will be returned stating that the vote was submitted successfully. Once a proposal has been approved by sufficent validators (more than `2/3` of the total voting power) then the proposed change is applied to the network. For example, consider a network wherein the total power is `90` then the proposed changed applied only after `60` (`2/3 * 90`) have been received.
|
||||||
|
|
||||||
|
#### upsert-validator show
|
||||||
|
|
||||||
|
Retrieves information about an election initiated by `upsert-validator new`.
|
||||||
|
|
||||||
|
Below is the command line syntax and the return value,
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ bigchaindb upsert-validator show ELECTION_ID
|
||||||
|
public_key=<e_pub_key>
|
||||||
|
power=<e_power>
|
||||||
|
node_id=<e_node_id>
|
||||||
|
status=<status>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `public_key`, `power`, and `node_id` are the same values used in the `upsert-validator 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.
|
||||||
@ -39,9 +39,34 @@ The settings with names of the form `database.*` are for the backend database
|
|||||||
* `database.name` is a user-chosen name for the database inside MongoDB, e.g. `bigchain`.
|
* `database.name` is a user-chosen name for the database inside MongoDB, e.g. `bigchain`.
|
||||||
* `database.connection_timeout` is the maximum number of milliseconds that BigchainDB will wait before giving up on one attempt to connect to the backend database.
|
* `database.connection_timeout` is the maximum number of milliseconds that BigchainDB will wait before giving up on one attempt to connect to the backend database.
|
||||||
* `database.max_tries` is the maximum number of times that BigchainDB will try to establish a connection with the backend database. If 0, then it will try forever.
|
* `database.max_tries` is the maximum number of times that BigchainDB will try to establish a connection with the backend database. If 0, then it will try forever.
|
||||||
* `database.replicaset` is the name of the MongoDB replica set. The default value is `null` because in BighainDB 2.0+, each BigchainDB node has its own independent MongoDB database and no replica set is necessary.
|
* `database.replicaset` is the name of the MongoDB replica set. The default value is `null` because in BigchainDB 2.0+, each BigchainDB node has its own independent MongoDB database and no replica set is necessary. Replica set must already exist if this option is configured, BigchainDB will not create it.
|
||||||
* `database.login` and `database.password` are the login and password used to authenticate to the backend database, specified in plaintext.
|
* `database.ssl` must be `true` or `false`. It tells BigchainDB Server whether it should connect to MongoDB using TLS/SSL or not. The default value is `false`.
|
||||||
* `database.ssl` determines if BigchainDB connects to MongoDB over TLS/SSL or not. It can be set to `true` or `false`.
|
|
||||||
|
There are three ways for BigchainDB Server to authenticate itself with MongoDB (or a specific MongoDB database): no authentication, username/password, and x.509 certificate authentication.
|
||||||
|
|
||||||
|
**No Authentication**
|
||||||
|
|
||||||
|
If you use all the default BigchainDB configuration settings, then no authentication will be used.
|
||||||
|
|
||||||
|
**Username/Password Authentication**
|
||||||
|
|
||||||
|
To use username/password authentication, a MongoDB instance must already be running somewhere (maybe in another machine), it must already have a database for use by BigchainDB (usually named `bigchain`, which is the default `database.name`), and that database must already have a "readWrite" user with associated username and password. To create such a user, login to your MongoDB instance as Admin and run the following commands:
|
||||||
|
|
||||||
|
```text
|
||||||
|
use <database.name>
|
||||||
|
db.createUser({user: "<database.login>", pwd: "<database.password>", roles: [{role: "readWrite", db: "<database.name>"}]})
|
||||||
|
```
|
||||||
|
|
||||||
|
* `database.login` is the user's username.
|
||||||
|
* `database.password` is the user's password, given in plaintext.
|
||||||
|
* `database.ca_cert`, `database.certfile`, `database.keyfile`, `database.crlfile`, and `database.keyfile_passphrase` are not used so they can have their default values.
|
||||||
|
|
||||||
|
**x.509 Certificate Authentication**
|
||||||
|
|
||||||
|
To use x.509 certificate authentication, a MongoDB instance must be running somewhere (maybe in another machine), it must already have a database for use by BigchainDB (usually named `bigchain`, which is the default `database.name`), and that database must be set up to use x.509 authentication. See the MongoDB docs about how to do that.
|
||||||
|
|
||||||
|
* `database.login` is the user's username.
|
||||||
|
* `database.password` isn't used so the default value (`null`) is fine.
|
||||||
* `database.ca_cert`, `database.certfile`, `database.keyfile` and `database.crlfile` are the paths to the CA, signed certificate, private key and certificate revocation list files respectively.
|
* `database.ca_cert`, `database.certfile`, `database.keyfile` and `database.crlfile` are the paths to the CA, signed certificate, private key and certificate revocation list files respectively.
|
||||||
* `database.keyfile_passphrase` is the private key decryption passphrase, specified in plaintext.
|
* `database.keyfile_passphrase` is the private key decryption passphrase, specified in plaintext.
|
||||||
|
|
||||||
|
|||||||
2
setup.py
2
setup.py
@ -79,7 +79,7 @@ install_requires = [
|
|||||||
# TODO Consider not installing the db drivers, or putting them in extras.
|
# TODO Consider not installing the db drivers, or putting them in extras.
|
||||||
'pymongo~=3.6',
|
'pymongo~=3.6',
|
||||||
'pysha3~=1.0.2',
|
'pysha3~=1.0.2',
|
||||||
'cryptoconditions~=0.6.0.dev',
|
'cryptoconditions~=0.7.2',
|
||||||
'python-rapidjson~=0.6.0',
|
'python-rapidjson~=0.6.0',
|
||||||
'logstats~=0.2.1',
|
'logstats~=0.2.1',
|
||||||
'flask>=0.10.1',
|
'flask>=0.10.1',
|
||||||
|
|||||||
@ -7,7 +7,6 @@ from unittest import mock
|
|||||||
import pytest
|
import pytest
|
||||||
import pymongo
|
import pymongo
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
from pymongo.database import Database
|
|
||||||
|
|
||||||
|
|
||||||
pytestmark = [pytest.mark.bdb, pytest.mark.tendermint]
|
pytestmark = [pytest.mark.bdb, pytest.mark.tendermint]
|
||||||
@ -109,87 +108,3 @@ def test_connection_with_credentials(mock_authenticate):
|
|||||||
password='secret')
|
password='secret')
|
||||||
conn.connect()
|
conn.connect()
|
||||||
assert mock_authenticate.call_count == 1
|
assert mock_authenticate.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_check_replica_set_not_enabled(mongodb_connection):
|
|
||||||
from bigchaindb.backend.localmongodb.connection import _check_replica_set
|
|
||||||
from bigchaindb.common.exceptions import ConfigurationError
|
|
||||||
|
|
||||||
# no replSet option set
|
|
||||||
cmd_line_opts = {'argv': ['mongod', '--dbpath=/data'],
|
|
||||||
'ok': 1.0,
|
|
||||||
'parsed': {'storage': {'dbPath': '/data'}}}
|
|
||||||
with mock.patch.object(Database, 'command', return_value=cmd_line_opts):
|
|
||||||
with pytest.raises(ConfigurationError):
|
|
||||||
_check_replica_set(mongodb_connection)
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_replica_set_command_line(mongodb_connection,
|
|
||||||
mock_cmd_line_opts):
|
|
||||||
from bigchaindb.backend.localmongodb.connection import _check_replica_set
|
|
||||||
|
|
||||||
# replSet option set through the command line
|
|
||||||
with mock.patch.object(Database, 'command',
|
|
||||||
return_value=mock_cmd_line_opts):
|
|
||||||
assert _check_replica_set(mongodb_connection) is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_replica_set_config_file(mongodb_connection, mock_config_opts):
|
|
||||||
from bigchaindb.backend.localmongodb.connection import _check_replica_set
|
|
||||||
|
|
||||||
# replSet option set through the config file
|
|
||||||
with mock.patch.object(Database, 'command', return_value=mock_config_opts):
|
|
||||||
assert _check_replica_set(mongodb_connection) is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_replica_set_name_mismatch(mongodb_connection,
|
|
||||||
mock_cmd_line_opts):
|
|
||||||
from bigchaindb.backend.localmongodb.connection import _check_replica_set
|
|
||||||
from bigchaindb.common.exceptions import ConfigurationError
|
|
||||||
|
|
||||||
# change the replica set name so it does not match the bigchaindb config
|
|
||||||
mock_cmd_line_opts['parsed']['replication']['replSet'] = 'rs0'
|
|
||||||
|
|
||||||
with mock.patch.object(Database, 'command',
|
|
||||||
return_value=mock_cmd_line_opts):
|
|
||||||
with pytest.raises(ConfigurationError):
|
|
||||||
_check_replica_set(mongodb_connection)
|
|
||||||
|
|
||||||
|
|
||||||
def test_wait_for_replica_set_initialization(mongodb_connection):
|
|
||||||
from bigchaindb.backend.localmongodb.connection import _wait_for_replica_set_initialization # noqa
|
|
||||||
|
|
||||||
with mock.patch.object(Database, 'command') as mock_command:
|
|
||||||
mock_command.side_effect = [
|
|
||||||
{'log': ['a line']},
|
|
||||||
{'log': ['database writes are now permitted']},
|
|
||||||
]
|
|
||||||
|
|
||||||
# check that it returns
|
|
||||||
assert _wait_for_replica_set_initialization(mongodb_connection) is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_replica_set(mock_cmd_line_opts):
|
|
||||||
from bigchaindb.backend.localmongodb.connection import initialize_replica_set
|
|
||||||
|
|
||||||
with mock.patch.object(Database, 'command') as mock_command:
|
|
||||||
mock_command.side_effect = [
|
|
||||||
mock_cmd_line_opts,
|
|
||||||
None,
|
|
||||||
{'log': ['database writes are now permitted']},
|
|
||||||
]
|
|
||||||
|
|
||||||
# check that it returns
|
|
||||||
assert initialize_replica_set('host', 1337, 1000, 'dbname', False, None, None,
|
|
||||||
None, None, None, None, None) is None
|
|
||||||
|
|
||||||
# test it raises OperationError if anything wrong
|
|
||||||
with mock.patch.object(Database, 'command') as mock_command:
|
|
||||||
mock_command.side_effect = [
|
|
||||||
mock_cmd_line_opts,
|
|
||||||
pymongo.errors.OperationFailure(None, details={'codeName': ''})
|
|
||||||
]
|
|
||||||
|
|
||||||
with pytest.raises(pymongo.errors.OperationFailure):
|
|
||||||
initialize_replica_set('host', 1337, 1000, 'dbname', False, None,
|
|
||||||
None, None, None, None, None, None) is None
|
|
||||||
|
|||||||
@ -7,6 +7,9 @@ from copy import deepcopy
|
|||||||
import pytest
|
import pytest
|
||||||
import pymongo
|
import pymongo
|
||||||
|
|
||||||
|
from bigchaindb.backend import connect, query
|
||||||
|
|
||||||
|
|
||||||
pytestmark = [pytest.mark.tendermint, pytest.mark.bdb]
|
pytestmark = [pytest.mark.tendermint, pytest.mark.bdb]
|
||||||
|
|
||||||
|
|
||||||
@ -380,7 +383,7 @@ def test_validator_update():
|
|||||||
conn = connect()
|
conn = connect()
|
||||||
|
|
||||||
def gen_validator_update(height):
|
def gen_validator_update(height):
|
||||||
return {'data': 'somedata', 'height': height}
|
return {'data': 'somedata', 'height': height, 'election_id': f'election_id_at_height_{height}'}
|
||||||
|
|
||||||
for i in range(1, 100, 10):
|
for i in range(1, 100, 10):
|
||||||
value = gen_validator_update(i)
|
value = gen_validator_update(i)
|
||||||
@ -394,3 +397,51 @@ def test_validator_update():
|
|||||||
|
|
||||||
v91 = query.get_validator_set(conn)
|
v91 = query.get_validator_set(conn)
|
||||||
assert v91['height'] == 91
|
assert v91['height'] == 91
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('description,stores,expected', [
|
||||||
|
(
|
||||||
|
'Query empty database.',
|
||||||
|
[],
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Store one chain with the default value for `is_synced`.',
|
||||||
|
[
|
||||||
|
{'height': 0, 'chain_id': 'some-id'},
|
||||||
|
],
|
||||||
|
{'height': 0, 'chain_id': 'some-id', 'is_synced': True},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Store one chain with a custom value for `is_synced`.',
|
||||||
|
[
|
||||||
|
{'height': 0, 'chain_id': 'some-id', 'is_synced': False},
|
||||||
|
],
|
||||||
|
{'height': 0, 'chain_id': 'some-id', 'is_synced': False},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Store one chain, then update it.',
|
||||||
|
[
|
||||||
|
{'height': 0, 'chain_id': 'some-id', 'is_synced': True},
|
||||||
|
{'height': 0, 'chain_id': 'new-id', 'is_synced': False},
|
||||||
|
],
|
||||||
|
{'height': 0, 'chain_id': 'new-id', 'is_synced': False},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Store a chain, update it, store another chain.',
|
||||||
|
[
|
||||||
|
{'height': 0, 'chain_id': 'some-id', 'is_synced': True},
|
||||||
|
{'height': 0, 'chain_id': 'some-id', 'is_synced': False},
|
||||||
|
{'height': 10, 'chain_id': 'another-id', 'is_synced': True},
|
||||||
|
],
|
||||||
|
{'height': 10, 'chain_id': 'another-id', 'is_synced': True},
|
||||||
|
),
|
||||||
|
])
|
||||||
|
def test_store_abci_chain(description, stores, expected):
|
||||||
|
conn = connect()
|
||||||
|
|
||||||
|
for store in stores:
|
||||||
|
query.store_abci_chain(conn, **store)
|
||||||
|
|
||||||
|
actual = query.get_latest_abci_chain(conn)
|
||||||
|
assert expected == actual, description
|
||||||
|
|||||||
@ -24,7 +24,7 @@ def test_init_creates_db_tables_and_indexes():
|
|||||||
collection_names = conn.conn[dbname].collection_names()
|
collection_names = conn.conn[dbname].collection_names()
|
||||||
assert set(collection_names) == {
|
assert set(collection_names) == {
|
||||||
'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'pre_commit',
|
'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'pre_commit',
|
||||||
'validators'
|
'validators', 'abci_chains',
|
||||||
}
|
}
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['assets'].index_information().keys()
|
indexes = conn.conn[dbname]['assets'].index_information().keys()
|
||||||
@ -46,6 +46,9 @@ def test_init_creates_db_tables_and_indexes():
|
|||||||
indexes = conn.conn[dbname]['validators'].index_information().keys()
|
indexes = conn.conn[dbname]['validators'].index_information().keys()
|
||||||
assert set(indexes) == {'_id_', 'height'}
|
assert set(indexes) == {'_id_', 'height'}
|
||||||
|
|
||||||
|
indexes = conn.conn[dbname]['abci_chains'].index_information().keys()
|
||||||
|
assert set(indexes) == {'_id_', 'height', 'chain_id'}
|
||||||
|
|
||||||
|
|
||||||
def test_init_database_fails_if_db_exists():
|
def test_init_database_fails_if_db_exists():
|
||||||
import bigchaindb
|
import bigchaindb
|
||||||
@ -79,7 +82,8 @@ def test_create_tables():
|
|||||||
collection_names = conn.conn[dbname].collection_names()
|
collection_names = conn.conn[dbname].collection_names()
|
||||||
assert set(collection_names) == {
|
assert set(collection_names) == {
|
||||||
'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'validators',
|
'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'validators',
|
||||||
'pre_commit'}
|
'pre_commit', 'abci_chains',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_create_secondary_indexes():
|
def test_create_secondary_indexes():
|
||||||
|
|||||||
@ -347,34 +347,20 @@ class MockResponse():
|
|||||||
return {'result': {'latest_block_height': self.height}}
|
return {'result': {'latest_block_height': self.height}}
|
||||||
|
|
||||||
|
|
||||||
# @pytest.mark.execute
|
@pytest.mark.abci
|
||||||
# @patch('bigchaindb.lib.BigchainDB.get_validators')
|
def test_upsert_validator_new_with_tendermint(b, priv_validator_path, user_sk, validators):
|
||||||
# @pytest.mark.abci
|
|
||||||
@pytest.mark.skip
|
|
||||||
def test_upsert_validator_new_with_tendermint(b, priv_validator_path, user_sk, monkeypatch):
|
|
||||||
"""WIP: Will be fixed and activated in the next PR
|
|
||||||
"""
|
|
||||||
from bigchaindb.commands.bigchaindb import run_upsert_validator_new
|
from bigchaindb.commands.bigchaindb import run_upsert_validator_new
|
||||||
import time
|
|
||||||
|
|
||||||
time.sleep(3)
|
new_args = Namespace(action='new',
|
||||||
|
public_key='8eJ8q9ZQpReWyQT5aFCiwtZ5wDZC4eDnCen88p3tQ6ie',
|
||||||
# b.get_validators = mock_get
|
|
||||||
# mock_get_validators = mock_get
|
|
||||||
# monkeypatch.setattr('requests.get', mock_get)
|
|
||||||
|
|
||||||
proposer_key = b.get_validators()[0]['pub_key']['value']
|
|
||||||
|
|
||||||
args = Namespace(action='new',
|
|
||||||
public_key=proposer_key,
|
|
||||||
power=1,
|
power=1,
|
||||||
node_id='12345',
|
node_id='unique_node_id_for_test_upsert_validator_new_with_tendermint',
|
||||||
sk=priv_validator_path,
|
sk=priv_validator_path,
|
||||||
config={})
|
config={})
|
||||||
resp = run_upsert_validator_new(args, b)
|
|
||||||
time.sleep(3)
|
|
||||||
|
|
||||||
assert b.get_transaction(resp)
|
election_id = run_upsert_validator_new(new_args, b)
|
||||||
|
|
||||||
|
assert b.get_transaction(election_id)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.tendermint
|
@pytest.mark.tendermint
|
||||||
@ -386,7 +372,7 @@ def test_upsert_validator_new_without_tendermint(caplog, b, priv_validator_path,
|
|||||||
b.store_bulk_transactions([tx])
|
b.store_bulk_transactions([tx])
|
||||||
return (202, '')
|
return (202, '')
|
||||||
|
|
||||||
b.get_validators = mock_get
|
b.get_validators = mock_get_validators
|
||||||
b.write_transaction = mock_write
|
b.write_transaction = mock_write
|
||||||
|
|
||||||
args = Namespace(action='new',
|
args = Namespace(action='new',
|
||||||
@ -430,7 +416,7 @@ def test_upsert_validator_new_election_invalid_power(caplog, b, priv_validator_p
|
|||||||
return (400, '')
|
return (400, '')
|
||||||
|
|
||||||
b.write_transaction = mock_write
|
b.write_transaction = mock_write
|
||||||
b.get_validators = mock_get
|
b.get_validators = mock_get_validators
|
||||||
args = Namespace(action='new',
|
args = Namespace(action='new',
|
||||||
public_key='CJxdItf4lz2PwEf4SmYNAu/c/VpmX39JEgC5YpH7fxg=',
|
public_key='CJxdItf4lz2PwEf4SmYNAu/c/VpmX39JEgC5YpH7fxg=',
|
||||||
power=10,
|
power=10,
|
||||||
@ -533,7 +519,7 @@ def test_upsert_validator_approve_called_with_bad_key(caplog, b, bad_validator_p
|
|||||||
'the eligible voters in this election.'
|
'the eligible voters in this election.'
|
||||||
|
|
||||||
|
|
||||||
def mock_get(height):
|
def mock_get_validators(height):
|
||||||
keys = node_keys()
|
keys = node_keys()
|
||||||
pub_key = list(keys.keys())[0]
|
pub_key = list(keys.keys())[0]
|
||||||
return [
|
return [
|
||||||
@ -550,7 +536,7 @@ def call_election(b, new_validator, node_key):
|
|||||||
return (202, '')
|
return (202, '')
|
||||||
|
|
||||||
# patch the validator set. We now have one validator with power 10
|
# patch the validator set. We now have one validator with power 10
|
||||||
b.get_validators = mock_get
|
b.get_validators = mock_get_validators
|
||||||
b.write_transaction = mock_write
|
b.write_transaction = mock_write
|
||||||
|
|
||||||
# our voters is a list of length 1, populated from our mocked validator
|
# our voters is a list of length 1, populated from our mocked validator
|
||||||
|
|||||||
@ -72,6 +72,12 @@ def test_configure_bigchaindb_configures_bigchaindb():
|
|||||||
logging.CRITICAL)
|
logging.CRITICAL)
|
||||||
)))
|
)))
|
||||||
def test_configure_bigchaindb_logging(log_level):
|
def test_configure_bigchaindb_logging(log_level):
|
||||||
|
# TODO: See following comment:
|
||||||
|
# This is a dirty test. If a test *preceding* this test makes use of the logger, and then another test *after* this
|
||||||
|
# test also makes use of the logger, somehow we get logger.disabled == True, and the later test fails. We need to
|
||||||
|
# either engineer this somehow to leave the test env in the same state as it finds it, or make an assessment
|
||||||
|
# whether or not we even need this test, and potentially just remove it.
|
||||||
|
|
||||||
from bigchaindb.commands.utils import configure_bigchaindb
|
from bigchaindb.commands.utils import configure_bigchaindb
|
||||||
|
|
||||||
@configure_bigchaindb
|
@configure_bigchaindb
|
||||||
|
|||||||
@ -93,7 +93,7 @@ def test_output_serialization(user_Ed25519, user_pub):
|
|||||||
'uri': user_Ed25519.condition_uri,
|
'uri': user_Ed25519.condition_uri,
|
||||||
'details': {
|
'details': {
|
||||||
'type': 'ed25519-sha-256',
|
'type': 'ed25519-sha-256',
|
||||||
'public_key': b58encode(user_Ed25519.public_key),
|
'public_key': b58encode(user_Ed25519.public_key).decode(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'public_keys': [user_pub],
|
'public_keys': [user_pub],
|
||||||
@ -114,7 +114,7 @@ def test_output_deserialization(user_Ed25519, user_pub):
|
|||||||
'uri': user_Ed25519.condition_uri,
|
'uri': user_Ed25519.condition_uri,
|
||||||
'details': {
|
'details': {
|
||||||
'type': 'ed25519-sha-256',
|
'type': 'ed25519-sha-256',
|
||||||
'public_key': b58encode(user_Ed25519.public_key),
|
'public_key': b58encode(user_Ed25519.public_key).decode(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'public_keys': [user_pub],
|
'public_keys': [user_pub],
|
||||||
|
|||||||
@ -635,6 +635,10 @@ def bad_validator_path(node_keys):
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def validators(b, node_keys):
|
def validators(b, node_keys):
|
||||||
from bigchaindb.backend import query
|
from bigchaindb.backend import query
|
||||||
|
import time
|
||||||
|
|
||||||
|
def timestamp(): # we need this to force unique election_ids for setup and teardown of fixtures
|
||||||
|
return str(time.time())
|
||||||
|
|
||||||
height = get_block_height(b)
|
height = get_block_height(b)
|
||||||
|
|
||||||
@ -649,7 +653,8 @@ def validators(b, node_keys):
|
|||||||
'voting_power': 10}]
|
'voting_power': 10}]
|
||||||
|
|
||||||
validator_update = {'validators': validator_set,
|
validator_update = {'validators': validator_set,
|
||||||
'height': height + 1}
|
'height': height + 1,
|
||||||
|
'election_id': f'setup_at_{timestamp()}'}
|
||||||
|
|
||||||
query.store_validator_set(b.connection, validator_update)
|
query.store_validator_set(b.connection, validator_update)
|
||||||
|
|
||||||
@ -658,7 +663,8 @@ def validators(b, node_keys):
|
|||||||
height = get_block_height(b)
|
height = get_block_height(b)
|
||||||
|
|
||||||
validator_update = {'validators': original_validators,
|
validator_update = {'validators': original_validators,
|
||||||
'height': height}
|
'height': height,
|
||||||
|
'election_id': f'teardown_at_{timestamp()}'}
|
||||||
|
|
||||||
query.store_validator_set(b.connection, validator_update)
|
query.store_validator_set(b.connection, validator_update)
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,8 @@ def validator_pub_key():
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def init_chain_request():
|
def init_chain_request():
|
||||||
addr = codecs.decode(b'9FD479C869C7D7E7605BF99293457AA5D80C3033', 'hex')
|
addr = codecs.decode(b'9FD479C869C7D7E7605BF99293457AA5D80C3033', 'hex')
|
||||||
pk = codecs.decode(b'VAgFZtYw8bNR5TMZHFOBDWk9cAmEu3/c6JgRBmddbbI=', 'base64')
|
pk = codecs.decode(b'VAgFZtYw8bNR5TMZHFOBDWk9cAmEu3/c6JgRBmddbbI=',
|
||||||
|
'base64')
|
||||||
val_a = types.Validator(address=addr, power=10,
|
val_a = types.Validator(address=addr, power=10,
|
||||||
pub_key=types.PubKey(type='ed25519', data=pk))
|
pub_key=types.PubKey(type='ed25519', data=pk))
|
||||||
|
|
||||||
|
|||||||
@ -2,17 +2,28 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
|
import codecs
|
||||||
import json
|
import json
|
||||||
import pytest
|
import pytest
|
||||||
|
import random
|
||||||
|
|
||||||
from abci.types_pb2 import (
|
from abci.types_pb2 import (
|
||||||
|
PubKey,
|
||||||
|
ResponseInitChain,
|
||||||
|
RequestInitChain,
|
||||||
|
RequestInfo,
|
||||||
RequestBeginBlock,
|
RequestBeginBlock,
|
||||||
RequestEndBlock
|
RequestEndBlock,
|
||||||
|
Validator,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bigchaindb import App
|
||||||
|
from bigchaindb.backend.localmongodb import query
|
||||||
|
from bigchaindb.common.crypto import generate_key_pair
|
||||||
from bigchaindb.core import (CodeTypeOk,
|
from bigchaindb.core import (CodeTypeOk,
|
||||||
CodeTypeError,
|
CodeTypeError,
|
||||||
)
|
)
|
||||||
|
from bigchaindb.lib import Block
|
||||||
from bigchaindb.upsert_validator.validator_utils import new_validator_set
|
from bigchaindb.upsert_validator.validator_utils import new_validator_set
|
||||||
from bigchaindb.tendermint_utils import public_key_to_base64
|
from bigchaindb.tendermint_utils import public_key_to_base64
|
||||||
|
|
||||||
@ -24,6 +35,173 @@ def encode_tx_to_bytes(transaction):
|
|||||||
return json.dumps(transaction.to_dict()).encode('utf8')
|
return json.dumps(transaction.to_dict()).encode('utf8')
|
||||||
|
|
||||||
|
|
||||||
|
def generate_address():
|
||||||
|
return ''.join(random.choices('1,2,3,4,5,6,7,8,9,A,B,C,D,E,F'.split(','),
|
||||||
|
k=40)).encode()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_validator():
|
||||||
|
addr = codecs.decode(generate_address(), 'hex')
|
||||||
|
pk, _ = generate_key_pair()
|
||||||
|
pub_key = PubKey(type='ed25519', data=pk.encode())
|
||||||
|
val = Validator(address=addr, power=10, pub_key=pub_key)
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def generate_init_chain_request(chain_id, vals=None):
|
||||||
|
vals = vals if vals is not None else [generate_validator()]
|
||||||
|
return RequestInitChain(validators=vals, chain_id=chain_id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_chain_successfully_registers_chain(b):
|
||||||
|
request = generate_init_chain_request('chain-XYZ')
|
||||||
|
res = App(b).init_chain(request)
|
||||||
|
assert res == ResponseInitChain()
|
||||||
|
chain = query.get_latest_abci_chain(b.connection)
|
||||||
|
assert chain == {'height': 0, 'chain_id': 'chain-XYZ', 'is_synced': True}
|
||||||
|
assert query.get_latest_block(b.connection) == {
|
||||||
|
'height': 0,
|
||||||
|
'app_hash': '',
|
||||||
|
'transactions': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_chain_ignores_invalid_init_chain_requests(b):
|
||||||
|
validators = [generate_validator()]
|
||||||
|
request = generate_init_chain_request('chain-XYZ', validators)
|
||||||
|
res = App(b).init_chain(request)
|
||||||
|
assert res == ResponseInitChain()
|
||||||
|
|
||||||
|
validator_set = query.get_validator_set(b.connection)
|
||||||
|
|
||||||
|
invalid_requests = [
|
||||||
|
request, # the same request again
|
||||||
|
# different validator set
|
||||||
|
generate_init_chain_request('chain-XYZ'),
|
||||||
|
# different chain ID
|
||||||
|
generate_init_chain_request('chain-ABC', validators),
|
||||||
|
]
|
||||||
|
for r in invalid_requests:
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
App(b).init_chain(r)
|
||||||
|
# assert nothing changed - neither validator set, nor chain ID
|
||||||
|
new_validator_set = query.get_validator_set(b.connection)
|
||||||
|
assert new_validator_set == validator_set
|
||||||
|
new_chain_id = query.get_latest_abci_chain(b.connection)['chain_id']
|
||||||
|
assert new_chain_id == 'chain-XYZ'
|
||||||
|
assert query.get_latest_block(b.connection) == {
|
||||||
|
'height': 0,
|
||||||
|
'app_hash': '',
|
||||||
|
'transactions': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_chain_recognizes_new_chain_after_migration(b):
|
||||||
|
validators = [generate_validator()]
|
||||||
|
request = generate_init_chain_request('chain-XYZ', validators)
|
||||||
|
res = App(b).init_chain(request)
|
||||||
|
assert res == ResponseInitChain()
|
||||||
|
|
||||||
|
validator_set = query.get_validator_set(b.connection)['validators']
|
||||||
|
|
||||||
|
# simulate a migration
|
||||||
|
query.store_block(b.connection, Block(app_hash='', height=1,
|
||||||
|
transactions=[])._asdict())
|
||||||
|
b.migrate_abci_chain()
|
||||||
|
|
||||||
|
# the same or other mismatching requests are ignored
|
||||||
|
invalid_requests = [
|
||||||
|
request,
|
||||||
|
generate_init_chain_request('unknown', validators),
|
||||||
|
generate_init_chain_request('chain-XYZ'),
|
||||||
|
generate_init_chain_request('chain-XYZ-migrated-at-height-1'),
|
||||||
|
]
|
||||||
|
for r in invalid_requests:
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
App(b).init_chain(r)
|
||||||
|
assert query.get_latest_abci_chain(b.connection) == {
|
||||||
|
'chain_id': 'chain-XYZ-migrated-at-height-1',
|
||||||
|
'is_synced': False,
|
||||||
|
'height': 2,
|
||||||
|
}
|
||||||
|
new_validator_set = query.get_validator_set(b.connection)['validators']
|
||||||
|
assert new_validator_set == validator_set
|
||||||
|
|
||||||
|
# a request with the matching chain ID and matching validator set
|
||||||
|
# completes the migration
|
||||||
|
request = generate_init_chain_request('chain-XYZ-migrated-at-height-1',
|
||||||
|
validators)
|
||||||
|
res = App(b).init_chain(request)
|
||||||
|
assert res == ResponseInitChain()
|
||||||
|
assert query.get_latest_abci_chain(b.connection) == {
|
||||||
|
'chain_id': 'chain-XYZ-migrated-at-height-1',
|
||||||
|
'is_synced': True,
|
||||||
|
'height': 2,
|
||||||
|
}
|
||||||
|
assert query.get_latest_block(b.connection) == {
|
||||||
|
'height': 2,
|
||||||
|
'app_hash': '',
|
||||||
|
'transactions': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
# requests with old chain ID and other requests are ignored
|
||||||
|
invalid_requests = [
|
||||||
|
request,
|
||||||
|
generate_init_chain_request('chain-XYZ', validators),
|
||||||
|
generate_init_chain_request('chain-XYZ-migrated-at-height-1'),
|
||||||
|
]
|
||||||
|
for r in invalid_requests:
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
App(b).init_chain(r)
|
||||||
|
assert query.get_latest_abci_chain(b.connection) == {
|
||||||
|
'chain_id': 'chain-XYZ-migrated-at-height-1',
|
||||||
|
'is_synced': True,
|
||||||
|
'height': 2,
|
||||||
|
}
|
||||||
|
new_validator_set = query.get_validator_set(b.connection)['validators']
|
||||||
|
assert new_validator_set == validator_set
|
||||||
|
assert query.get_latest_block(b.connection) == {
|
||||||
|
'height': 2,
|
||||||
|
'app_hash': '',
|
||||||
|
'transactions': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_info(b):
|
||||||
|
r = RequestInfo()
|
||||||
|
app = App(b)
|
||||||
|
|
||||||
|
res = app.info(r)
|
||||||
|
assert res.last_block_height == 0
|
||||||
|
assert res.last_block_app_hash == b''
|
||||||
|
|
||||||
|
b.store_block(Block(app_hash='1', height=1, transactions=[])._asdict())
|
||||||
|
res = app.info(r)
|
||||||
|
assert res.last_block_height == 1
|
||||||
|
assert res.last_block_app_hash == b'1'
|
||||||
|
|
||||||
|
# simulate a migration and assert the height is shifted
|
||||||
|
b.store_abci_chain(2, 'chain-XYZ')
|
||||||
|
app = App(b)
|
||||||
|
b.store_block(Block(app_hash='2', height=2, transactions=[])._asdict())
|
||||||
|
res = app.info(r)
|
||||||
|
assert res.last_block_height == 0
|
||||||
|
assert res.last_block_app_hash == b'2'
|
||||||
|
|
||||||
|
b.store_block(Block(app_hash='3', height=3, transactions=[])._asdict())
|
||||||
|
res = app.info(r)
|
||||||
|
assert res.last_block_height == 1
|
||||||
|
assert res.last_block_app_hash == b'3'
|
||||||
|
|
||||||
|
# it's always the latest migration that is taken into account
|
||||||
|
b.store_abci_chain(4, 'chain-XYZ-new')
|
||||||
|
app = App(b)
|
||||||
|
b.store_block(Block(app_hash='4', height=4, transactions=[])._asdict())
|
||||||
|
res = app.info(r)
|
||||||
|
assert res.last_block_height == 0
|
||||||
|
assert res.last_block_app_hash == b'4'
|
||||||
|
|
||||||
|
|
||||||
def test_check_tx__signed_create_is_ok(b):
|
def test_check_tx__signed_create_is_ok(b):
|
||||||
from bigchaindb import App
|
from bigchaindb import App
|
||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Transaction
|
||||||
@ -57,7 +235,6 @@ def test_check_tx__unsigned_create_is_error(b):
|
|||||||
assert result.code == CodeTypeError
|
assert result.code == CodeTypeError
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
|
||||||
def test_deliver_tx__valid_create_updates_db(b, init_chain_request):
|
def test_deliver_tx__valid_create_updates_db(b, init_chain_request):
|
||||||
from bigchaindb import App
|
from bigchaindb import App
|
||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Transaction
|
||||||
@ -225,6 +402,17 @@ def test_store_pre_commit_state_in_end_block(b, alice, init_chain_request):
|
|||||||
assert resp['height'] == 100
|
assert resp['height'] == 100
|
||||||
assert resp['transactions'] == [tx.id]
|
assert resp['transactions'] == [tx.id]
|
||||||
|
|
||||||
|
# simulate a chain migration and assert the height is shifted
|
||||||
|
b.store_abci_chain(100, 'new-chain')
|
||||||
|
app = App(b)
|
||||||
|
app.begin_block(begin_block)
|
||||||
|
app.deliver_tx(encode_tx_to_bytes(tx))
|
||||||
|
app.end_block(RequestEndBlock(height=1))
|
||||||
|
resp = query.get_pre_commit_state(b.connection, PRE_COMMIT_ID)
|
||||||
|
assert resp['commit_id'] == PRE_COMMIT_ID
|
||||||
|
assert resp['height'] == 101
|
||||||
|
assert resp['transactions'] == [tx.id]
|
||||||
|
|
||||||
|
|
||||||
def test_new_validator_set(b):
|
def test_new_validator_set(b):
|
||||||
node1 = {'pub_key': {'type': 'ed25519',
|
node1 = {'pub_key': {'type': 'ed25519',
|
||||||
@ -237,8 +425,8 @@ def test_new_validator_set(b):
|
|||||||
|
|
||||||
validators = [node1]
|
validators = [node1]
|
||||||
updates = [node1_new_power, node2]
|
updates = [node1_new_power, node2]
|
||||||
b.store_validator_set(1, validators)
|
b.store_validator_set(1, validators, 'election_id')
|
||||||
updated_validator_set = new_validator_set(b.get_validators(1), 1, updates)
|
updated_validator_set = new_validator_set(b.get_validators(1), updates)
|
||||||
|
|
||||||
updated_validators = []
|
updated_validators = []
|
||||||
for u in updates:
|
for u in updates:
|
||||||
@ -247,3 +435,45 @@ def test_new_validator_set(b):
|
|||||||
'voting_power': u['power']})
|
'voting_power': u['power']})
|
||||||
|
|
||||||
assert updated_validator_set == updated_validators
|
assert updated_validator_set == updated_validators
|
||||||
|
|
||||||
|
|
||||||
|
def test_info_aborts_if_chain_is_not_synced(b):
|
||||||
|
b.store_abci_chain(0, 'chain-XYZ', False)
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
App(b).info(RequestInfo())
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_tx_aborts_if_chain_is_not_synced(b):
|
||||||
|
b.store_abci_chain(0, 'chain-XYZ', False)
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
App(b).check_tx('some bytes')
|
||||||
|
|
||||||
|
|
||||||
|
def test_begin_aborts_if_chain_is_not_synced(b):
|
||||||
|
b.store_abci_chain(0, 'chain-XYZ', False)
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
App(b).info(RequestBeginBlock())
|
||||||
|
|
||||||
|
|
||||||
|
def test_deliver_tx_aborts_if_chain_is_not_synced(b):
|
||||||
|
b.store_abci_chain(0, 'chain-XYZ', False)
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
App(b).deliver_tx('some bytes')
|
||||||
|
|
||||||
|
|
||||||
|
def test_end_block_aborts_if_chain_is_not_synced(b):
|
||||||
|
b.store_abci_chain(0, 'chain-XYZ', False)
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
App(b).info(RequestEndBlock())
|
||||||
|
|
||||||
|
|
||||||
|
def test_commit_aborts_if_chain_is_not_synced(b):
|
||||||
|
b.store_abci_chain(0, 'chain-XYZ', False)
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
App(b).commit()
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import pytest
|
|||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
|
|
||||||
from bigchaindb import backend
|
from bigchaindb import backend
|
||||||
|
from bigchaindb.lib import Block
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.tendermint
|
pytestmark = pytest.mark.tendermint
|
||||||
@ -441,3 +442,35 @@ def test_validation_with_transaction_buffer(b):
|
|||||||
assert not b.is_valid_transaction(create_tx, [create_tx])
|
assert not b.is_valid_transaction(create_tx, [create_tx])
|
||||||
assert not b.is_valid_transaction(transfer_tx, [create_tx, transfer_tx])
|
assert not b.is_valid_transaction(transfer_tx, [create_tx, transfer_tx])
|
||||||
assert not b.is_valid_transaction(double_spend, [create_tx, transfer_tx])
|
assert not b.is_valid_transaction(double_spend, [create_tx, transfer_tx])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.bdb
|
||||||
|
def test_migrate_abci_chain_yields_on_genesis(b):
|
||||||
|
b.migrate_abci_chain()
|
||||||
|
latest_chain = b.get_latest_abci_chain()
|
||||||
|
assert latest_chain is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.bdb
|
||||||
|
@pytest.mark.parametrize('chain,block_height,expected', [
|
||||||
|
(
|
||||||
|
(1, 'chain-XYZ', True),
|
||||||
|
4,
|
||||||
|
{'height': 5, 'chain_id': 'chain-XYZ-migrated-at-height-4',
|
||||||
|
'is_synced': False},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(5, 'chain-XYZ-migrated-at-height-4', True),
|
||||||
|
13,
|
||||||
|
{'height': 14, 'chain_id': 'chain-XYZ-migrated-at-height-13',
|
||||||
|
'is_synced': False},
|
||||||
|
),
|
||||||
|
])
|
||||||
|
def test_migrate_abci_chain_generates_new_chains(b, chain, block_height,
|
||||||
|
expected):
|
||||||
|
b.store_abci_chain(*chain)
|
||||||
|
b.store_block(Block(app_hash='', height=block_height,
|
||||||
|
transactions=[])._asdict())
|
||||||
|
b.migrate_abci_chain()
|
||||||
|
latest_chain = b.get_latest_abci_chain()
|
||||||
|
assert latest_chain == expected
|
||||||
|
|||||||
@ -12,6 +12,7 @@ except ImportError:
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.tendermint
|
pytestmark = pytest.mark.tendermint
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from bigchaindb import ValidatorElectionVote
|
||||||
|
from bigchaindb.backend.localmongodb import query
|
||||||
|
from bigchaindb.lib import Block
|
||||||
from bigchaindb.upsert_validator import ValidatorElection
|
from bigchaindb.upsert_validator import ValidatorElection
|
||||||
|
|
||||||
|
|
||||||
@ -41,3 +44,56 @@ def valid_election_b(b, node_key, new_validator):
|
|||||||
return ValidatorElection.generate([node_key.public_key],
|
return ValidatorElection.generate([node_key.public_key],
|
||||||
voters,
|
voters,
|
||||||
new_validator, None).sign([node_key.private_key])
|
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)
|
||||||
|
genesis_validators = {'validators': validators,
|
||||||
|
'height': 0,
|
||||||
|
'election_id': None}
|
||||||
|
query.store_validator_set(b.connection, genesis_validators)
|
||||||
|
|
||||||
|
b.store_bulk_transactions([valid_election])
|
||||||
|
block_1 = Block(app_hash='hash_1', height=1, transactions=[valid_election.id])
|
||||||
|
b.store_block(block_1._asdict())
|
||||||
|
return valid_election
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def concluded_election(b, ongoing_election, ed25519_node_keys):
|
||||||
|
validators = b.get_validators(height=1)
|
||||||
|
validator_update = {'validators': validators,
|
||||||
|
'height': 2,
|
||||||
|
'election_id': ongoing_election.id}
|
||||||
|
|
||||||
|
query.store_validator_set(b.connection, validator_update)
|
||||||
|
return ongoing_election
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def inconclusive_election(b, ongoing_election, new_validator):
|
||||||
|
validators = b.get_validators(height=1)
|
||||||
|
validators[0]['voting_power'] = 15
|
||||||
|
validator_update = {'validators': validators,
|
||||||
|
'height': 2,
|
||||||
|
'election_id': 'some_other_election'}
|
||||||
|
|
||||||
|
query.store_validator_set(b.connection, validator_update)
|
||||||
|
return ongoing_election
|
||||||
|
|
||||||
|
|
||||||
|
def vote(election, voter, keys, b):
|
||||||
|
election_input = election.to_inputs()[voter]
|
||||||
|
votes = election.outputs[voter].amount
|
||||||
|
public_key = election_input.owners_before[0]
|
||||||
|
key = keys[public_key]
|
||||||
|
|
||||||
|
election_pub_key = ValidatorElection.to_public_key(election.id)
|
||||||
|
|
||||||
|
v = ValidatorElectionVote.generate([election_input],
|
||||||
|
[([election_pub_key], votes)],
|
||||||
|
election_id=election.id)\
|
||||||
|
.sign([key.private_key])
|
||||||
|
b.store_bulk_transactions([v])
|
||||||
|
return v
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
# Copyright BigchainDB GmbH and BigchainDB contributors
|
# Copyright BigchainDB GmbH and BigchainDB contributors
|
||||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
from argparse import Namespace
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from bigchaindb.tendermint_utils import public_key_to_base64
|
||||||
from bigchaindb.upsert_validator import ValidatorElection
|
from bigchaindb.upsert_validator import ValidatorElection
|
||||||
from bigchaindb.common.exceptions import (DuplicateTransaction,
|
from bigchaindb.common.exceptions import (DuplicateTransaction,
|
||||||
UnequalValidatorSet,
|
UnequalValidatorSet,
|
||||||
@ -57,11 +59,8 @@ def test_upsert_validator_invalid_inputs_election(b_mock, new_validator, node_ke
|
|||||||
election.validate(b_mock)
|
election.validate(b_mock)
|
||||||
|
|
||||||
|
|
||||||
def test_upsert_validator_invalid_election(b_mock, new_validator, node_key):
|
def test_upsert_validator_invalid_election(b_mock, new_validator, node_key, valid_election):
|
||||||
voters = ValidatorElection.recipients(b_mock)
|
voters = ValidatorElection.recipients(b_mock)
|
||||||
valid_election = ValidatorElection.generate([node_key.public_key],
|
|
||||||
voters,
|
|
||||||
new_validator, None).sign([node_key.private_key])
|
|
||||||
duplicate_election = ValidatorElection.generate([node_key.public_key],
|
duplicate_election = ValidatorElection.generate([node_key.public_key],
|
||||||
voters,
|
voters,
|
||||||
new_validator, None).sign([node_key.private_key])
|
new_validator, None).sign([node_key.private_key])
|
||||||
@ -95,3 +94,67 @@ def test_upsert_validator_invalid_election(b_mock, new_validator, node_key):
|
|||||||
|
|
||||||
with pytest.raises(UnequalValidatorSet):
|
with pytest.raises(UnequalValidatorSet):
|
||||||
tx_election.validate(b_mock)
|
tx_election.validate(b_mock)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_status_ongoing(b, ongoing_election, new_validator):
|
||||||
|
status = ValidatorElection.ONGOING
|
||||||
|
resp = ongoing_election.get_status(b)
|
||||||
|
assert resp == status
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_status_concluded(b, concluded_election, new_validator):
|
||||||
|
status = ValidatorElection.CONCLUDED
|
||||||
|
resp = concluded_election.get_status(b)
|
||||||
|
assert resp == status
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_status_inconclusive(b, inconclusive_election, new_validator):
|
||||||
|
def custom_mock_get_validators(height):
|
||||||
|
if height >= 3:
|
||||||
|
return [{'pub_key': {'data': 'zL/DasvKulXZzhSNFwx4cLRXKkSM9GPK7Y0nZ4FEylM=',
|
||||||
|
'type': 'AC26791624DE60'},
|
||||||
|
'voting_power': 15},
|
||||||
|
{'pub_key': {'data': 'GIijU7GBcVyiVUcB0GwWZbxCxdk2xV6pxdvL24s/AqM=',
|
||||||
|
'type': 'AC26791624DE60'},
|
||||||
|
'voting_power': 7},
|
||||||
|
{'pub_key': {'data': 'JbfwrLvCVIwOPm8tj8936ki7IYbmGHjPiKb6nAZegRA=',
|
||||||
|
'type': 'AC26791624DE60'},
|
||||||
|
'voting_power': 10},
|
||||||
|
{'pub_key': {'data': 'PecJ58SaNRsWJZodDmqjpCWqG6btdwXFHLyE40RYlYM=',
|
||||||
|
'type': 'AC26791624DE60'},
|
||||||
|
'voting_power': 8}]
|
||||||
|
else:
|
||||||
|
return [{'pub_key': {'data': 'zL/DasvKulXZzhSNFwx4cLRXKkSM9GPK7Y0nZ4FEylM=',
|
||||||
|
'type': 'AC26791624DE60'},
|
||||||
|
'voting_power': 9},
|
||||||
|
{'pub_key': {'data': 'GIijU7GBcVyiVUcB0GwWZbxCxdk2xV6pxdvL24s/AqM=',
|
||||||
|
'type': 'AC26791624DE60'},
|
||||||
|
'voting_power': 7},
|
||||||
|
{'pub_key': {'data': 'JbfwrLvCVIwOPm8tj8936ki7IYbmGHjPiKb6nAZegRA=',
|
||||||
|
'type': 'AC26791624DE60'},
|
||||||
|
'voting_power': 10},
|
||||||
|
{'pub_key': {'data': 'PecJ58SaNRsWJZodDmqjpCWqG6btdwXFHLyE40RYlYM=',
|
||||||
|
'type': 'AC26791624DE60'},
|
||||||
|
'voting_power': 8}]
|
||||||
|
|
||||||
|
b.get_validators = custom_mock_get_validators
|
||||||
|
status = ValidatorElection.INCONCLUSIVE
|
||||||
|
resp = inconclusive_election.get_status(b)
|
||||||
|
assert resp == status
|
||||||
|
|
||||||
|
|
||||||
|
def test_upsert_validator_show(caplog, ongoing_election, b):
|
||||||
|
from bigchaindb.commands.bigchaindb import run_upsert_validator_show
|
||||||
|
|
||||||
|
election_id = ongoing_election.id
|
||||||
|
public_key = public_key_to_base64(ongoing_election.asset['data']['public_key'])
|
||||||
|
power = ongoing_election.asset['data']['power']
|
||||||
|
node_id = ongoing_election.asset['data']['node_id']
|
||||||
|
status = ValidatorElection.ONGOING
|
||||||
|
|
||||||
|
show_args = Namespace(action='show',
|
||||||
|
election_id=election_id)
|
||||||
|
|
||||||
|
msg = run_upsert_validator_show(show_args, b)
|
||||||
|
|
||||||
|
assert msg == f'public_key={public_key}\npower={power}\nnode_id={node_id}\nstatus={status}'
|
||||||
|
|||||||
@ -234,7 +234,7 @@ def test_upsert_validator(b, node_key, node_keys, ed25519_node_keys):
|
|||||||
|
|
||||||
latest_block = b.get_latest_block()
|
latest_block = b.get_latest_block()
|
||||||
# reset the validator set
|
# reset the validator set
|
||||||
b.store_validator_set(latest_block['height'], validators)
|
b.store_validator_set(latest_block['height'], validators, 'previous_election_id')
|
||||||
|
|
||||||
power = 1
|
power = 1
|
||||||
public_key = '9B3119650DF82B9A5D8A12E38953EA47475C09F0C48A4E6A0ECE182944B24403'
|
public_key = '9B3119650DF82B9A5D8A12E38953EA47475C09F0C48A4E6A0ECE182944B24403'
|
||||||
@ -368,4 +368,4 @@ def reset_validator_set(b, node_keys, height):
|
|||||||
validators.append({'pub_key': {'type': 'ed25519',
|
validators.append({'pub_key': {'type': 'ed25519',
|
||||||
'data': node_pub},
|
'data': node_pub},
|
||||||
'voting_power': 10})
|
'voting_power': 10})
|
||||||
b.store_validator_set(height, validators)
|
b.store_validator_set(height, validators, 'election_id')
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
from functools import singledispatch
|
from functools import singledispatch
|
||||||
|
|
||||||
from bigchaindb.backend.localmongodb.connection import LocalMongoDBConnection
|
from bigchaindb.backend.localmongodb.connection import LocalMongoDBConnection
|
||||||
|
from bigchaindb.backend.schema import TABLES
|
||||||
|
|
||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
@ -14,13 +15,8 @@ def flush_db(connection, dbname):
|
|||||||
|
|
||||||
@flush_db.register(LocalMongoDBConnection)
|
@flush_db.register(LocalMongoDBConnection)
|
||||||
def flush_localmongo_db(connection, dbname):
|
def flush_localmongo_db(connection, dbname):
|
||||||
connection.conn[dbname].bigchain.delete_many({})
|
for t in TABLES:
|
||||||
connection.conn[dbname].blocks.delete_many({})
|
getattr(connection.conn[dbname], t).delete_many({})
|
||||||
connection.conn[dbname].transactions.delete_many({})
|
|
||||||
connection.conn[dbname].assets.delete_many({})
|
|
||||||
connection.conn[dbname].metadata.delete_many({})
|
|
||||||
connection.conn[dbname].utxos.delete_many({})
|
|
||||||
connection.conn[dbname].validators.delete_many({})
|
|
||||||
|
|
||||||
|
|
||||||
def generate_block(bigchain):
|
def generate_block(bigchain):
|
||||||
|
|||||||
@ -14,7 +14,7 @@ def test_get_validators_endpoint(b, client):
|
|||||||
'pub_key': {'data': '4E2685D9016126864733225BE00F005515200727FBAB1312FC78C8B76831255A',
|
'pub_key': {'data': '4E2685D9016126864733225BE00F005515200727FBAB1312FC78C8B76831255A',
|
||||||
'type': 'ed25519'},
|
'type': 'ed25519'},
|
||||||
'voting_power': 10}]
|
'voting_power': 10}]
|
||||||
b.store_validator_set(23, validator_set)
|
b.store_validator_set(23, validator_set, 'election_id')
|
||||||
|
|
||||||
res = client.get(VALIDATORS_ENDPOINT)
|
res = client.get(VALIDATORS_ENDPOINT)
|
||||||
assert is_validator(res.json[0])
|
assert is_validator(res.json[0])
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user