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:
Lev Berman 2018-09-21 10:51:57 +02:00 committed by Vanshdeep Singh
parent 39be7a2fdf
commit 24ca0b32a9
13 changed files with 272 additions and 171 deletions

View File

@ -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):

View File

@ -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)),

View File

@ -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

View File

@ -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."""

View File

@ -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

View File

@ -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'))

View File

@ -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()

View File

@ -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'])

View File

@ -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'}

View File

@ -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

View File

@ -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, [])

View File

@ -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

View File

@ -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