mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Separate pending and effective validator updates. (#2556)
* Separate pending and effective validator updates. - Pending validator updates do not prevent elections from concluding. - ValidatorElection overrides has_conclude to not conclude when there is a pending update for the matching height. - Effective validator updates deem past elections inconclusive. * Problem: Looking for election block is inefficient. Solution: Record placed elections, update the records upon election conclusion. * Clarify the conclusion order in Election.process_blocks. * Insert election records in bulk. Otherwise, one can significantly slow nodes down by posting a whole bunch of unique elections. * Change get_election to use find_one. * Calculate total votes without making extra query. * Fix the pending valset check. * Fix election test setup.
This commit is contained in:
parent
39be7a2fdf
commit
24ca0b32a9
@ -282,16 +282,26 @@ def store_validator_set(conn, validators_update):
|
|||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def store_election_results(conn, election):
|
def store_election(conn, election_id, height, is_concluded):
|
||||||
return conn.run(
|
return conn.run(
|
||||||
conn.collection('elections').replace_one(
|
conn.collection('elections').replace_one(
|
||||||
{'election_id': election['election_id']},
|
{'election_id': election_id,
|
||||||
election,
|
'height': height},
|
||||||
|
{'election_id': election_id,
|
||||||
|
'height': height,
|
||||||
|
'is_concluded': is_concluded},
|
||||||
upsert=True,
|
upsert=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_elections(conn, elections):
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('elections').insert_many(elections)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_validator_set(conn, height=None):
|
def get_validator_set(conn, height=None):
|
||||||
query = {}
|
query = {}
|
||||||
@ -312,13 +322,12 @@ def get_validator_set(conn, height=None):
|
|||||||
def get_election(conn, election_id):
|
def get_election(conn, election_id):
|
||||||
query = {'election_id': election_id}
|
query = {'election_id': election_id}
|
||||||
|
|
||||||
cursor = conn.run(
|
return conn.run(
|
||||||
conn.collection('elections')
|
conn.collection('elections')
|
||||||
.find(query, projection={'_id': False})
|
.find_one(query, projection={'_id': False},
|
||||||
|
sort=[('height', DESCENDING)])
|
||||||
)
|
)
|
||||||
|
|
||||||
return next(cursor, None)
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_asset_tokens_for_public_key(conn, asset_id, public_key):
|
def get_asset_tokens_for_public_key(conn, asset_id, public_key):
|
||||||
|
@ -45,7 +45,8 @@ INDEXES = {
|
|||||||
('commit_id', dict(name='pre_commit_id', unique=True)),
|
('commit_id', dict(name='pre_commit_id', unique=True)),
|
||||||
],
|
],
|
||||||
'elections': [
|
'elections': [
|
||||||
('election_id', dict(name='election_id', unique=True)),
|
([('height', DESCENDING), ('election_id', ASCENDING)],
|
||||||
|
dict(name='election_id_height', unique=True)),
|
||||||
],
|
],
|
||||||
'validators': [
|
'validators': [
|
||||||
('height', dict(name='height', unique=True)),
|
('height', dict(name='height', unique=True)),
|
||||||
|
@ -352,8 +352,15 @@ def store_validator_set(conn, validator_update):
|
|||||||
|
|
||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
def store_election_results(conn, election):
|
def store_election(conn, election_id, height, is_concluded):
|
||||||
"""Store election results"""
|
"""Store election record"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@singledispatch
|
||||||
|
def store_elections(conn, elections):
|
||||||
|
"""Store election records in bulk"""
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@ -369,7 +376,7 @@ def get_validator_set(conn, height):
|
|||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
def get_election(conn, election_id):
|
def get_election(conn, election_id):
|
||||||
"""Return a validator set change with the specified election_id
|
"""Return the election record
|
||||||
"""
|
"""
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -215,10 +215,9 @@ class App(BaseApplication):
|
|||||||
else:
|
else:
|
||||||
self.block_txn_hash = block['app_hash']
|
self.block_txn_hash = block['app_hash']
|
||||||
|
|
||||||
# Process all concluded elections in the current block and get any update to the validator set
|
validator_update = Election.process_block(self.bigchaindb,
|
||||||
update = Election.approved_elections(self.bigchaindb,
|
self.new_height,
|
||||||
self.new_height,
|
self.block_transactions)
|
||||||
self.block_transactions)
|
|
||||||
|
|
||||||
# Store pre-commit state to recover in case there is a crash during `commit`
|
# Store pre-commit state to recover in case there is a crash during `commit`
|
||||||
pre_commit_state = PreCommitState(commit_id=PRE_COMMIT_ID,
|
pre_commit_state = PreCommitState(commit_id=PRE_COMMIT_ID,
|
||||||
@ -226,7 +225,7 @@ class App(BaseApplication):
|
|||||||
transactions=self.block_txn_ids)
|
transactions=self.block_txn_ids)
|
||||||
logger.debug('Updating PreCommitState: %s', self.new_height)
|
logger.debug('Updating PreCommitState: %s', self.new_height)
|
||||||
self.bigchaindb.store_pre_commit_state(pre_commit_state._asdict())
|
self.bigchaindb.store_pre_commit_state(pre_commit_state._asdict())
|
||||||
return ResponseEndBlock(validator_updates=update)
|
return ResponseEndBlock(validator_updates=validator_update)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
"""Store the new height and along with block hash."""
|
"""Store the new height and along with block hash."""
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# 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 collections import defaultdict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import base58
|
import base58
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
@ -22,9 +22,13 @@ from bigchaindb.common.schema import (_validate_schema,
|
|||||||
|
|
||||||
|
|
||||||
class Election(Transaction):
|
class Election(Transaction):
|
||||||
|
"""Represents election transactions.
|
||||||
|
|
||||||
|
To implement a custom election, create a class deriving from this one
|
||||||
|
with OPERATION set to the election operation, ALLOWED_OPERATIONS
|
||||||
|
set to (OPERATION,), CREATE set to OPERATION.
|
||||||
|
"""
|
||||||
|
|
||||||
# NOTE: this transaction class extends create so the operation inheritance is achieved
|
|
||||||
# by setting an ELECTION_TYPE and renaming CREATE = ELECTION_TYPE and ALLOWED_OPERATIONS = (ELECTION_TYPE,)
|
|
||||||
OPERATION = None
|
OPERATION = None
|
||||||
# Custom validation schema
|
# Custom validation schema
|
||||||
TX_SCHEMA_CUSTOM = None
|
TX_SCHEMA_CUSTOM = None
|
||||||
@ -34,7 +38,6 @@ class Election(Transaction):
|
|||||||
INCONCLUSIVE = 'inconclusive'
|
INCONCLUSIVE = 'inconclusive'
|
||||||
# Vote ratio to approve an election
|
# Vote ratio to approve an election
|
||||||
ELECTION_THRESHOLD = 2 / 3
|
ELECTION_THRESHOLD = 2 / 3
|
||||||
CHANGES_VALIDATOR_SET = True
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_validator_change(cls, bigchain):
|
def get_validator_change(cls, bigchain):
|
||||||
@ -45,8 +48,10 @@ class Election(Transaction):
|
|||||||
'validators': <validator_set>
|
'validators': <validator_set>
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
height = bigchain.get_latest_block()['height']
|
latest_block = bigchain.get_latest_block()
|
||||||
return bigchain.get_validator_change(height)
|
if latest_block is None:
|
||||||
|
return None
|
||||||
|
return bigchain.get_validator_change(latest_block['height'])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_validators(cls, bigchain, height=None):
|
def get_validators(cls, bigchain, height=None):
|
||||||
@ -186,49 +191,52 @@ class Election(Transaction):
|
|||||||
election_pk))
|
election_pk))
|
||||||
return self.count_votes(election_pk, txns, dict.get)
|
return self.count_votes(election_pk, txns, dict.get)
|
||||||
|
|
||||||
def has_concluded(self, bigchain, current_votes=[], height=None):
|
def has_concluded(self, bigchain, current_votes=[]):
|
||||||
"""Check if the election can be concluded or not.
|
"""Check if the election can be concluded or not.
|
||||||
|
|
||||||
* Elections can only be concluded if the current validator set
|
* Elections can only be concluded if the validator set has not changed
|
||||||
is exactly equal to the validator set encoded in the election outputs.
|
since the election was initiated.
|
||||||
* Elections can be concluded only if the current votes form a supermajority.
|
* Elections can be concluded only if the current votes form a supermajority.
|
||||||
|
|
||||||
Custom elections may override this function and introduce additional checks.
|
Custom elections may override this function and introduce additional checks.
|
||||||
"""
|
"""
|
||||||
|
if self.has_validator_set_changed(bigchain):
|
||||||
|
return False
|
||||||
|
|
||||||
election_pk = self.to_public_key(self.id)
|
election_pk = self.to_public_key(self.id)
|
||||||
votes_committed = self.get_commited_votes(bigchain, election_pk)
|
votes_committed = self.get_commited_votes(bigchain, election_pk)
|
||||||
votes_current = self.count_votes(election_pk, current_votes)
|
votes_current = self.count_votes(election_pk, current_votes)
|
||||||
current_validators = self.get_validators(bigchain, height)
|
|
||||||
|
|
||||||
if self.is_same_topology(current_validators, self.outputs):
|
total_votes = sum(output.amount for output in self.outputs)
|
||||||
total_votes = sum(current_validators.values())
|
if (votes_committed < (2/3) * total_votes) and \
|
||||||
if (votes_committed < (2/3) * total_votes) and \
|
(votes_committed + votes_current >= (2/3)*total_votes):
|
||||||
(votes_committed + votes_current >= (2/3)*total_votes):
|
return True
|
||||||
return True
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_status(self, bigchain):
|
def get_status(self, bigchain):
|
||||||
concluded = self.get_election(self.id, bigchain)
|
election = self.get_election(self.id, bigchain)
|
||||||
if concluded:
|
if election and election['is_concluded']:
|
||||||
return self.CONCLUDED
|
return self.CONCLUDED
|
||||||
|
|
||||||
latest_change = self.get_validator_change(bigchain)
|
return self.INCONCLUSIVE if self.has_validator_set_changed(bigchain) else self.ONGOING
|
||||||
latest_change_height = latest_change['height']
|
|
||||||
election_height = bigchain.get_block_containing_tx(self.id)[0]
|
|
||||||
|
|
||||||
if latest_change_height >= election_height:
|
def has_validator_set_changed(self, bigchain):
|
||||||
return self.INCONCLUSIVE
|
latest_change = self.get_validator_change(bigchain)
|
||||||
else:
|
if latest_change is None:
|
||||||
return self.ONGOING
|
return False
|
||||||
|
|
||||||
|
latest_change_height = latest_change['height']
|
||||||
|
|
||||||
|
election = self.get_election(self.id, bigchain)
|
||||||
|
|
||||||
|
return latest_change_height > election['height']
|
||||||
|
|
||||||
def get_election(self, election_id, bigchain):
|
def get_election(self, election_id, bigchain):
|
||||||
result = bigchain.get_election(election_id)
|
return bigchain.get_election(election_id)
|
||||||
return result
|
|
||||||
|
|
||||||
@classmethod
|
def store(self, bigchain, height, is_concluded):
|
||||||
def store_election_results(cls, bigchain, election, height):
|
bigchain.store_election(self.id, height, is_concluded)
|
||||||
bigchain.store_election_results(height, election)
|
|
||||||
|
|
||||||
def show_election(self, bigchain):
|
def show_election(self, bigchain):
|
||||||
data = self.asset['data']
|
data = self.asset['data']
|
||||||
@ -243,45 +251,61 @@ class Election(Transaction):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def approved_elections(cls, bigchain, new_height, txns):
|
def process_block(cls, bigchain, new_height, txns):
|
||||||
elections = defaultdict(list)
|
"""Looks for election and vote transactions inside the block, records
|
||||||
|
and processes elections.
|
||||||
|
|
||||||
|
Every election is recorded in the database.
|
||||||
|
|
||||||
|
Every vote has a chance to conclude the corresponding election. When
|
||||||
|
an election is concluded, the corresponding database record is
|
||||||
|
marked as such.
|
||||||
|
|
||||||
|
Elections and votes are processed in the order in which they
|
||||||
|
appear in the block. Elections are concluded in the order of
|
||||||
|
appearance of their first votes in the block.
|
||||||
|
|
||||||
|
For every election concluded in the block, calls its `on_approval`
|
||||||
|
method. The returned value of the last `on_approval`, if any,
|
||||||
|
is a validator set update to be applied in one of the following blocks.
|
||||||
|
|
||||||
|
`on_approval` methods are implemented by elections of particular type.
|
||||||
|
The method may contain side effects but should be idempotent. To account
|
||||||
|
for other concluded elections, if it requires so, the method should
|
||||||
|
rely on the database state.
|
||||||
|
"""
|
||||||
|
# elections placed in this block
|
||||||
|
initiated_elections = []
|
||||||
|
# elections voted for in this block and their votes
|
||||||
|
elections = OrderedDict()
|
||||||
for tx in txns:
|
for tx in txns:
|
||||||
|
if isinstance(tx, Election):
|
||||||
|
initiated_elections.append({'election_id': tx.id,
|
||||||
|
'height': new_height,
|
||||||
|
'is_concluded': False})
|
||||||
if not isinstance(tx, Vote):
|
if not isinstance(tx, Vote):
|
||||||
continue
|
continue
|
||||||
election_id = tx.asset['id']
|
election_id = tx.asset['id']
|
||||||
|
if election_id not in elections:
|
||||||
|
elections[election_id] = []
|
||||||
elections[election_id].append(tx)
|
elections[election_id].append(tx)
|
||||||
|
|
||||||
validator_set_updated = False
|
if initiated_elections:
|
||||||
validator_set_change = []
|
bigchain.store_elections(initiated_elections)
|
||||||
|
|
||||||
|
validator_update = None
|
||||||
for election_id, votes in elections.items():
|
for election_id, votes in elections.items():
|
||||||
election = bigchain.get_transaction(election_id)
|
election = bigchain.get_transaction(election_id)
|
||||||
if election is None:
|
if election is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not election.has_concluded(bigchain, votes, new_height):
|
if not election.has_concluded(bigchain, votes):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if election.makes_validator_set_change():
|
validator_update = election.on_approval(bigchain, new_height)
|
||||||
if validator_set_updated:
|
election.store(bigchain, new_height, is_concluded=True)
|
||||||
continue
|
|
||||||
validator_set_change.append(election.get_validator_set_change(bigchain, new_height))
|
|
||||||
validator_set_updated = True
|
|
||||||
|
|
||||||
election.on_approval(bigchain, election, new_height)
|
return [validator_update] if validator_update else []
|
||||||
election.store_election_results(bigchain, election, new_height)
|
|
||||||
|
|
||||||
return validator_set_change
|
def on_approval(self, bigchain, new_height):
|
||||||
|
|
||||||
def makes_validator_set_change(self):
|
|
||||||
return self.CHANGES_VALIDATOR_SET
|
|
||||||
|
|
||||||
def get_validator_set_change(self, bigchain, new_height):
|
|
||||||
if self.makes_validator_set_change():
|
|
||||||
return self.change_validator_set(bigchain, new_height)
|
|
||||||
|
|
||||||
def change_validator_set(self, bigchain, new_height):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def on_approval(cls, bigchain, election, new_height):
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -436,8 +436,7 @@ class BigchainDB(object):
|
|||||||
return [] if result is None else result['validators']
|
return [] if result is None else result['validators']
|
||||||
|
|
||||||
def get_election(self, election_id):
|
def get_election(self, election_id):
|
||||||
result = backend.query.get_election(self.connection, election_id)
|
return backend.query.get_election(self.connection, election_id)
|
||||||
return result
|
|
||||||
|
|
||||||
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)
|
||||||
@ -481,13 +480,12 @@ class BigchainDB(object):
|
|||||||
|
|
||||||
self.store_abci_chain(block['height'] + 1, new_chain_id, False)
|
self.store_abci_chain(block['height'] + 1, new_chain_id, False)
|
||||||
|
|
||||||
def store_election_results(self, height, election):
|
def store_election(self, election_id, height, is_concluded):
|
||||||
"""Store election results
|
return backend.query.store_election(self.connection, election_id,
|
||||||
:param height: the block height at which the election concluded
|
height, is_concluded)
|
||||||
:param election: a concluded election
|
|
||||||
"""
|
def store_elections(self, elections):
|
||||||
return backend.query.store_election_results(self.connection, {'height': height,
|
return backend.query.store_elections(self.connection, elections)
|
||||||
'election_id': election.id})
|
|
||||||
|
|
||||||
|
|
||||||
Block = namedtuple('Block', ('app_hash', 'height', 'transactions'))
|
Block = namedtuple('Block', ('app_hash', 'height', 'transactions'))
|
||||||
|
@ -8,7 +8,6 @@ class ChainMigrationElection(Election):
|
|||||||
CREATE = OPERATION
|
CREATE = OPERATION
|
||||||
ALLOWED_OPERATIONS = (OPERATION,)
|
ALLOWED_OPERATIONS = (OPERATION,)
|
||||||
TX_SCHEMA_CUSTOM = TX_SCHEMA_CHAIN_MIGRATION_ELECTION
|
TX_SCHEMA_CUSTOM = TX_SCHEMA_CHAIN_MIGRATION_ELECTION
|
||||||
CHANGES_VALIDATOR_SET = False
|
|
||||||
|
|
||||||
def has_concluded(self, bigchaindb, *args, **kwargs):
|
def has_concluded(self, bigchaindb, *args, **kwargs):
|
||||||
chain = bigchaindb.get_latest_abci_chain()
|
chain = bigchaindb.get_latest_abci_chain()
|
||||||
@ -19,6 +18,5 @@ class ChainMigrationElection(Election):
|
|||||||
|
|
||||||
return super().has_concluded(bigchaindb, *args, **kwargs)
|
return super().has_concluded(bigchaindb, *args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
def on_approval(self, bigchain, *args, **kwargs):
|
||||||
def on_approval(cls, bigchain, election, new_height):
|
|
||||||
bigchain.migrate_abci_chain()
|
bigchain.migrate_abci_chain()
|
||||||
|
@ -36,18 +36,28 @@ class ValidatorElection(Election):
|
|||||||
super(ValidatorElection, cls).validate_schema(tx)
|
super(ValidatorElection, cls).validate_schema(tx)
|
||||||
validate_asset_public_key(tx['asset']['data']['public_key'])
|
validate_asset_public_key(tx['asset']['data']['public_key'])
|
||||||
|
|
||||||
def change_validator_set(self, bigchain, new_height):
|
def has_concluded(self, bigchain, *args, **kwargs):
|
||||||
# The new validator set comes into effect from height = new_height+1
|
latest_block = bigchain.get_latest_block()
|
||||||
# (upcoming changes to Tendermint will change this to height = new_height+2)
|
if latest_block is not None:
|
||||||
|
latest_block_height = latest_block['height']
|
||||||
|
latest_validator_change = bigchain.get_validator_change()['height']
|
||||||
|
|
||||||
|
# TODO change to `latest_block_height + 3` when upgrading to Tendermint 0.24.0.
|
||||||
|
if latest_validator_change == latest_block_height + 2:
|
||||||
|
# do not conclude the election if there is a change assigned already
|
||||||
|
return False
|
||||||
|
|
||||||
|
return super().has_concluded(bigchain, *args, **kwargs)
|
||||||
|
|
||||||
|
def on_approval(self, bigchain, new_height):
|
||||||
validator_updates = [self.asset['data']]
|
validator_updates = [self.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,
|
||||||
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
|
||||||
bigchain.store_validator_set(new_height+1, updated_validator_set)
|
if v['voting_power'] > 0]
|
||||||
return encode_validator(self.asset['data'])
|
|
||||||
|
|
||||||
@classmethod
|
# TODO change to `new_height + 2` when upgrading to Tendermint 0.24.0.
|
||||||
def on_approval(cls, bigchain, election, new_height):
|
bigchain.store_validator_set(new_height + 1, updated_validator_set)
|
||||||
pass
|
return encode_validator(self.asset['data'])
|
||||||
|
@ -3,51 +3,6 @@
|
|||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
|
|
||||||
def test_init_creates_db_tables_and_indexes():
|
|
||||||
import bigchaindb
|
|
||||||
from bigchaindb import backend
|
|
||||||
from bigchaindb.backend.schema import init_database
|
|
||||||
|
|
||||||
conn = backend.connect()
|
|
||||||
dbname = bigchaindb.config['database']['name']
|
|
||||||
|
|
||||||
# the db is set up by the fixture so we need to remove it
|
|
||||||
conn.conn.drop_database(dbname)
|
|
||||||
|
|
||||||
init_database()
|
|
||||||
|
|
||||||
collection_names = conn.conn[dbname].list_collection_names()
|
|
||||||
assert set(collection_names) == {
|
|
||||||
'transactions', 'assets', 'metadata', 'blocks', 'utxos', 'pre_commit',
|
|
||||||
'validators', 'elections', 'abci_chains',
|
|
||||||
}
|
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['assets'].index_information().keys()
|
|
||||||
assert set(indexes) == {'_id_', 'asset_id', 'text'}
|
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['transactions'].index_information().keys()
|
|
||||||
assert set(indexes) == {
|
|
||||||
'_id_', 'transaction_id', 'asset_id', 'outputs', 'inputs'}
|
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['blocks'].index_information().keys()
|
|
||||||
assert set(indexes) == {'_id_', 'height'}
|
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['utxos'].index_information().keys()
|
|
||||||
assert set(indexes) == {'_id_', 'utxo'}
|
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['pre_commit'].index_information().keys()
|
|
||||||
assert set(indexes) == {'_id_', 'pre_commit_id'}
|
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['validators'].index_information().keys()
|
|
||||||
assert set(indexes) == {'_id_', 'height'}
|
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['abci_chains'].index_information().keys()
|
|
||||||
assert set(indexes) == {'_id_', 'height', 'chain_id'}
|
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['elections'].index_information().keys()
|
|
||||||
assert set(indexes) == {'_id_', 'election_id'}
|
|
||||||
|
|
||||||
|
|
||||||
def test_init_database_is_graceful_if_db_exists():
|
def test_init_database_is_graceful_if_db_exists():
|
||||||
import bigchaindb
|
import bigchaindb
|
||||||
from bigchaindb import backend
|
from bigchaindb import backend
|
||||||
@ -102,8 +57,8 @@ def test_create_tables():
|
|||||||
('output_index', 1)]
|
('output_index', 1)]
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['elections'].index_information()
|
indexes = conn.conn[dbname]['elections'].index_information()
|
||||||
assert set(indexes.keys()) == {'_id_', 'election_id'}
|
assert set(indexes.keys()) == {'_id_', 'election_id_height'}
|
||||||
assert indexes['election_id']['unique']
|
assert indexes['election_id_height']['unique']
|
||||||
|
|
||||||
indexes = conn.conn[dbname]['pre_commit'].index_information()
|
indexes = conn.conn[dbname]['pre_commit'].index_information()
|
||||||
assert set(indexes.keys()) == {'_id_', 'pre_commit_id'}
|
assert set(indexes.keys()) == {'_id_', 'pre_commit_id'}
|
||||||
|
@ -712,12 +712,13 @@ def valid_upsert_validator_election_2(b_mock, node_key, new_validator):
|
|||||||
def ongoing_validator_election(b, valid_upsert_validator_election, ed25519_node_keys):
|
def ongoing_validator_election(b, valid_upsert_validator_election, ed25519_node_keys):
|
||||||
validators = b.get_validators(height=1)
|
validators = b.get_validators(height=1)
|
||||||
genesis_validators = {'validators': validators,
|
genesis_validators = {'validators': validators,
|
||||||
'height': 0,
|
'height': 0}
|
||||||
'election_id': None}
|
|
||||||
query.store_validator_set(b.connection, genesis_validators)
|
query.store_validator_set(b.connection, genesis_validators)
|
||||||
|
|
||||||
b.store_bulk_transactions([valid_upsert_validator_election])
|
b.store_bulk_transactions([valid_upsert_validator_election])
|
||||||
block_1 = Block(app_hash='hash_1', height=1, transactions=[valid_upsert_validator_election.id])
|
query.store_election(b.connection, valid_upsert_validator_election.id, 1,
|
||||||
|
is_concluded=False)
|
||||||
|
block_1 = Block(app_hash='hash_1', height=1,
|
||||||
|
transactions=[valid_upsert_validator_election.id])
|
||||||
b.store_block(block_1._asdict())
|
b.store_block(block_1._asdict())
|
||||||
return valid_upsert_validator_election
|
return valid_upsert_validator_election
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from bigchaindb.upsert_validator.validator_election import ValidatorElection
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
@pytest.mark.bdb
|
||||||
def test_approved_elections_concludes_all_elections(b):
|
def test_process_block_concludes_all_elections(b):
|
||||||
validators = generate_validators([1] * 4)
|
validators = generate_validators([1] * 4)
|
||||||
b.store_validator_set(1, [v['storage'] for v in validators])
|
b.store_validator_set(1, [v['storage'] for v in validators])
|
||||||
|
|
||||||
@ -17,28 +17,30 @@ def test_approved_elections_concludes_all_elections(b):
|
|||||||
|
|
||||||
public_key = validators[0]['public_key']
|
public_key = validators[0]['public_key']
|
||||||
private_key = validators[0]['private_key']
|
private_key = validators[0]['private_key']
|
||||||
election, votes = generate_election(b,
|
|
||||||
ValidatorElection,
|
|
||||||
public_key, private_key,
|
|
||||||
new_validator['election'])
|
|
||||||
txs = [election]
|
|
||||||
total_votes = votes
|
|
||||||
|
|
||||||
election, votes = generate_election(b,
|
election, votes = generate_election(b,
|
||||||
ChainMigrationElection,
|
ChainMigrationElection,
|
||||||
public_key, private_key,
|
public_key, private_key,
|
||||||
{})
|
{})
|
||||||
|
|
||||||
|
txs = [election]
|
||||||
|
total_votes = votes
|
||||||
|
|
||||||
|
election, votes = generate_election(b,
|
||||||
|
ValidatorElection,
|
||||||
|
public_key, private_key,
|
||||||
|
new_validator['election'])
|
||||||
txs += [election]
|
txs += [election]
|
||||||
total_votes += votes
|
total_votes += votes
|
||||||
|
|
||||||
b.store_abci_chain(1, 'chain-X')
|
b.store_abci_chain(1, 'chain-X')
|
||||||
|
Election.process_block(b, 1, txs)
|
||||||
b.store_block(Block(height=1,
|
b.store_block(Block(height=1,
|
||||||
transactions=[tx.id for tx in txs],
|
transactions=[tx.id for tx in txs],
|
||||||
app_hash='')._asdict())
|
app_hash='')._asdict())
|
||||||
b.store_bulk_transactions(txs)
|
b.store_bulk_transactions(txs)
|
||||||
|
|
||||||
Election.approved_elections(b, 1, total_votes)
|
Election.process_block(b, 2, total_votes)
|
||||||
|
|
||||||
validators = b.get_validators()
|
validators = b.get_validators()
|
||||||
assert len(validators) == 5
|
assert len(validators) == 5
|
||||||
@ -53,12 +55,11 @@ def test_approved_elections_concludes_all_elections(b):
|
|||||||
}
|
}
|
||||||
|
|
||||||
for tx in txs:
|
for tx in txs:
|
||||||
election = b.get_election(tx.id)
|
assert b.get_election(tx.id)['is_concluded']
|
||||||
assert election
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
@pytest.mark.bdb
|
||||||
def test_approved_elections_applies_only_one_validator_update(b):
|
def test_process_block_approves_only_one_validator_update(b):
|
||||||
validators = generate_validators([1] * 4)
|
validators = generate_validators([1] * 4)
|
||||||
b.store_validator_set(1, [v['storage'] for v in validators])
|
b.store_validator_set(1, [v['storage'] for v in validators])
|
||||||
|
|
||||||
@ -82,24 +83,123 @@ def test_approved_elections_applies_only_one_validator_update(b):
|
|||||||
txs += [election]
|
txs += [election]
|
||||||
total_votes += votes
|
total_votes += votes
|
||||||
|
|
||||||
|
Election.process_block(b, 1, txs)
|
||||||
b.store_block(Block(height=1,
|
b.store_block(Block(height=1,
|
||||||
transactions=[tx.id for tx in txs],
|
transactions=[tx.id for tx in txs],
|
||||||
app_hash='')._asdict())
|
app_hash='')._asdict())
|
||||||
b.store_bulk_transactions(txs)
|
b.store_bulk_transactions(txs)
|
||||||
|
|
||||||
Election.approved_elections(b, 1, total_votes)
|
Election.process_block(b, 2, total_votes)
|
||||||
|
|
||||||
validators = b.get_validators()
|
validators = b.get_validators()
|
||||||
assert len(validators) == 5
|
assert len(validators) == 5
|
||||||
assert new_validator['storage'] in validators
|
assert new_validator['storage'] in validators
|
||||||
assert another_validator['storage'] not in validators
|
assert another_validator['storage'] not in validators
|
||||||
|
|
||||||
assert b.get_election(txs[0].id)
|
assert b.get_election(txs[0].id)['is_concluded']
|
||||||
assert not b.get_election(txs[1].id)
|
assert not b.get_election(txs[1].id)['is_concluded']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
@pytest.mark.bdb
|
||||||
def test_approved_elections_applies_only_one_migration(b):
|
def test_process_block_approves_after_pending_validator_update(b):
|
||||||
|
validators = generate_validators([1] * 4)
|
||||||
|
b.store_validator_set(1, [v['storage'] for v in validators])
|
||||||
|
|
||||||
|
new_validator = generate_validators([1])[0]
|
||||||
|
|
||||||
|
public_key = validators[0]['public_key']
|
||||||
|
private_key = validators[0]['private_key']
|
||||||
|
election, votes = generate_election(b,
|
||||||
|
ValidatorElection,
|
||||||
|
public_key, private_key,
|
||||||
|
new_validator['election'])
|
||||||
|
txs = [election]
|
||||||
|
total_votes = votes
|
||||||
|
|
||||||
|
another_validator = generate_validators([1])[0]
|
||||||
|
|
||||||
|
election, votes = generate_election(b,
|
||||||
|
ValidatorElection,
|
||||||
|
public_key, private_key,
|
||||||
|
another_validator['election'])
|
||||||
|
txs += [election]
|
||||||
|
total_votes += votes
|
||||||
|
|
||||||
|
election, votes = generate_election(b,
|
||||||
|
ChainMigrationElection,
|
||||||
|
public_key, private_key,
|
||||||
|
{})
|
||||||
|
|
||||||
|
txs += [election]
|
||||||
|
total_votes += votes
|
||||||
|
|
||||||
|
b.store_abci_chain(1, 'chain-X')
|
||||||
|
Election.process_block(b, 1, txs)
|
||||||
|
b.store_block(Block(height=1,
|
||||||
|
transactions=[tx.id for tx in txs],
|
||||||
|
app_hash='')._asdict())
|
||||||
|
b.store_bulk_transactions(txs)
|
||||||
|
|
||||||
|
Election.process_block(b, 2, total_votes)
|
||||||
|
|
||||||
|
validators = b.get_validators()
|
||||||
|
assert len(validators) == 5
|
||||||
|
assert new_validator['storage'] in validators
|
||||||
|
assert another_validator['storage'] not in validators
|
||||||
|
|
||||||
|
assert b.get_election(txs[0].id)['is_concluded']
|
||||||
|
assert not b.get_election(txs[1].id)['is_concluded']
|
||||||
|
assert b.get_election(txs[2].id)['is_concluded']
|
||||||
|
|
||||||
|
assert b.get_latest_abci_chain() == {'height': 2,
|
||||||
|
'chain_id': 'chain-X-migrated-at-height-1',
|
||||||
|
'is_synced': False}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.bdb
|
||||||
|
def test_process_block_does_not_approve_after_validator_update(b):
|
||||||
|
validators = generate_validators([1] * 4)
|
||||||
|
b.store_validator_set(1, [v['storage'] for v in validators])
|
||||||
|
|
||||||
|
new_validator = generate_validators([1])[0]
|
||||||
|
|
||||||
|
public_key = validators[0]['public_key']
|
||||||
|
private_key = validators[0]['private_key']
|
||||||
|
election, votes = generate_election(b,
|
||||||
|
ValidatorElection,
|
||||||
|
public_key, private_key,
|
||||||
|
new_validator['election'])
|
||||||
|
txs = [election]
|
||||||
|
total_votes = votes
|
||||||
|
|
||||||
|
b.store_block(Block(height=1,
|
||||||
|
transactions=[tx.id for tx in txs],
|
||||||
|
app_hash='')._asdict())
|
||||||
|
Election.process_block(b, 1, txs)
|
||||||
|
b.store_bulk_transactions(txs)
|
||||||
|
|
||||||
|
second_election, second_votes = generate_election(b,
|
||||||
|
ChainMigrationElection,
|
||||||
|
public_key, private_key,
|
||||||
|
{})
|
||||||
|
|
||||||
|
Election.process_block(b, 2, total_votes + [second_election])
|
||||||
|
|
||||||
|
b.store_block(Block(height=2,
|
||||||
|
transactions=[v.id for v in total_votes + [second_election]],
|
||||||
|
app_hash='')._asdict())
|
||||||
|
|
||||||
|
b.store_abci_chain(1, 'chain-X')
|
||||||
|
Election.process_block(b, 3, second_votes)
|
||||||
|
|
||||||
|
assert not b.get_election(second_election.id)['is_concluded']
|
||||||
|
assert b.get_latest_abci_chain() == {'height': 1,
|
||||||
|
'chain_id': 'chain-X',
|
||||||
|
'is_synced': True}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.bdb
|
||||||
|
def test_process_block_applies_only_one_migration(b):
|
||||||
validators = generate_validators([1] * 4)
|
validators = generate_validators([1] * 4)
|
||||||
b.store_validator_set(1, [v['storage'] for v in validators])
|
b.store_validator_set(1, [v['storage'] for v in validators])
|
||||||
|
|
||||||
@ -121,12 +221,13 @@ def test_approved_elections_applies_only_one_migration(b):
|
|||||||
total_votes += votes
|
total_votes += votes
|
||||||
|
|
||||||
b.store_abci_chain(1, 'chain-X')
|
b.store_abci_chain(1, 'chain-X')
|
||||||
|
Election.process_block(b, 1, txs)
|
||||||
b.store_block(Block(height=1,
|
b.store_block(Block(height=1,
|
||||||
transactions=[tx.id for tx in txs],
|
transactions=[tx.id for tx in txs],
|
||||||
app_hash='')._asdict())
|
app_hash='')._asdict())
|
||||||
b.store_bulk_transactions(txs)
|
b.store_bulk_transactions(txs)
|
||||||
|
|
||||||
Election.approved_elections(b, 1, total_votes)
|
Election.process_block(b, 1, total_votes)
|
||||||
chain = b.get_latest_abci_chain()
|
chain = b.get_latest_abci_chain()
|
||||||
assert chain
|
assert chain
|
||||||
assert chain == {
|
assert chain == {
|
||||||
@ -135,9 +236,9 @@ def test_approved_elections_applies_only_one_migration(b):
|
|||||||
'chain_id': 'chain-X-migrated-at-height-1',
|
'chain_id': 'chain-X-migrated-at-height-1',
|
||||||
}
|
}
|
||||||
|
|
||||||
assert b.get_election(txs[0].id)
|
assert b.get_election(txs[0].id)['is_concluded']
|
||||||
assert not b.get_election(txs[1].id)
|
assert not b.get_election(txs[1].id)['is_concluded']
|
||||||
|
|
||||||
|
|
||||||
def test_approved_elections_gracefully_handles_empty_block(b):
|
def test_process_block_gracefully_handles_empty_block(b):
|
||||||
Election.approved_elections(b, 1, [])
|
Election.process_block(b, 1, [])
|
||||||
|
@ -28,10 +28,8 @@ def fixed_seed_election(b_mock, node_key, new_validator):
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def concluded_election(b, ongoing_validator_election, ed25519_node_keys):
|
def concluded_election(b, ongoing_validator_election, ed25519_node_keys):
|
||||||
election_result = {'height': 2,
|
query.store_election(b.connection, ongoing_validator_election.id,
|
||||||
'election_id': ongoing_validator_election.id}
|
2, is_concluded=True)
|
||||||
|
|
||||||
query.store_election_results(b.connection, election_result)
|
|
||||||
return ongoing_validator_election
|
return ongoing_validator_election
|
||||||
|
|
||||||
|
|
||||||
|
@ -289,10 +289,10 @@ def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys):
|
|||||||
assert not election.has_concluded(b, [tx_vote0, tx_vote1])
|
assert not election.has_concluded(b, [tx_vote0, tx_vote1])
|
||||||
assert election.has_concluded(b, [tx_vote0, tx_vote1, tx_vote2])
|
assert election.has_concluded(b, [tx_vote0, tx_vote1, tx_vote2])
|
||||||
|
|
||||||
assert Election.approved_elections(b, 4, [tx_vote0]) == []
|
assert Election.process_block(b, 4, [tx_vote0]) == []
|
||||||
assert Election.approved_elections(b, 4, [tx_vote0, tx_vote1]) == []
|
assert Election.process_block(b, 4, [tx_vote0, tx_vote1]) == []
|
||||||
|
|
||||||
update = Election.approved_elections(b, 4, [tx_vote0, tx_vote1, tx_vote2])
|
update = Election.process_block(b, 4, [tx_vote0, tx_vote1, tx_vote2])
|
||||||
assert len(update) == 1
|
assert len(update) == 1
|
||||||
update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n')
|
update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n')
|
||||||
assert update_public_key == public_key64
|
assert update_public_key == public_key64
|
||||||
@ -315,7 +315,7 @@ def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys):
|
|||||||
|
|
||||||
b.store_bulk_transactions([tx_vote0, tx_vote1])
|
b.store_bulk_transactions([tx_vote0, tx_vote1])
|
||||||
|
|
||||||
update = Election.approved_elections(b, 9, [tx_vote2])
|
update = Election.process_block(b, 9, [tx_vote2])
|
||||||
assert len(update) == 1
|
assert len(update) == 1
|
||||||
update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n')
|
update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n')
|
||||||
assert update_public_key == public_key64
|
assert update_public_key == public_key64
|
||||||
|
Loading…
x
Reference in New Issue
Block a user