From 96dc8a245ea53ba79bb2f96b2c572321febcfcfb Mon Sep 17 00:00:00 2001 From: z-bowen Date: Thu, 30 Aug 2018 15:14:52 +0200 Subject: [PATCH] Problem: Logic for `upsert-validator show` is convoluted Solution: Rewrote the function to be much simpler --- bigchaindb/backend/localmongodb/query.py | 17 +++++ bigchaindb/backend/query.py | 8 ++ bigchaindb/core.py | 2 +- bigchaindb/lib.py | 9 ++- .../upsert_validator/validator_election.py | 73 +++++++------------ tests/upsert_validator/conftest.py | 25 ++++--- .../test_validator_election_vote.py | 2 +- 7 files changed, 78 insertions(+), 58 deletions(-) diff --git a/bigchaindb/backend/localmongodb/query.py b/bigchaindb/backend/localmongodb/query.py index c85e0854..9b855ba4 100644 --- a/bigchaindb/backend/localmongodb/query.py +++ b/bigchaindb/backend/localmongodb/query.py @@ -299,6 +299,23 @@ def get_validator_set(conn, height=None): return list(cursor)[0] +@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}) + ) + + results = list(cursor) + + if len(results) > 0: + return results[0] + else: + return None + + @register_query(LocalMongoDBConnection) def get_asset_tokens_for_public_key(conn, asset_id, public_key): query = {'outputs.public_keys': [public_key], diff --git a/bigchaindb/backend/query.py b/bigchaindb/backend/query.py index b2cef080..4d62c633 100644 --- a/bigchaindb/backend/query.py +++ b/bigchaindb/backend/query.py @@ -360,6 +360,14 @@ def get_validator_set(conn, height): 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 def get_asset_tokens_for_public_key(connection, asset_id, public_key, operation): diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 45f04824..621960fa 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -54,7 +54,7 @@ class App(BaseApplication): validator_set = [vutils.decode_validator(v) for v in genesis.validators] block = Block(app_hash='', height=0, transactions=[]) self.bigchaindb.store_block(block._asdict()) - self.bigchaindb.store_validator_set(1, validator_set, '') + self.bigchaindb.store_validator_set(1, validator_set, None) return ResponseInitChain() def info(self, request): diff --git a/bigchaindb/lib.py b/bigchaindb/lib.py index 170550ea..05ca3e69 100644 --- a/bigchaindb/lib.py +++ b/bigchaindb/lib.py @@ -421,11 +421,18 @@ class BigchainDB(object): def fastquery(self): 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): - result = backend.query.get_validator_set(self.connection, height) + result = self.get_validator_change(height) validators = 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): return backend.query.delete_validator_update(self.connection) diff --git a/bigchaindb/upsert_validator/validator_election.py b/bigchaindb/upsert_validator/validator_election.py index 6509f648..7d9a6fbc 100644 --- a/bigchaindb/upsert_validator/validator_election.py +++ b/bigchaindb/upsert_validator/validator_election.py @@ -5,7 +5,6 @@ import base58 from bigchaindb import backend -from bigchaindb.backend.localmongodb.query import get_asset_tokens_for_public_key from bigchaindb.common.exceptions import (InvalidSignature, MultipleInputsError, InvalidProposer, @@ -42,6 +41,21 @@ class ValidatorElection(Transaction): # of `CREATE` and any validation on `CREATE` in the parent class should apply to it super().__init__(operation, asset, inputs, outputs, metadata, version, hash_id) + @classmethod + def get_validator_change(cls, bigchain, height=None): + """Return the latest change to the validator set + + :return: { + 'height': , + 'asset': { + 'height': , + 'validators': , + 'election_id': + } + } + """ + return bigchain.get_validator_change(height) + @classmethod def get_validators(cls, bigchain, height=None): """Return a dictionary of validators with key as `public_key` and @@ -231,51 +245,20 @@ class ValidatorElection(Transaction): return [encode_validator(election.asset['data'])] return [] - def _vote_ratio(self, bigchain, height): - cast_votes = self._get_vote_ids(bigchain) - votes = [(tx['outputs'][0]['amount'], bigchain.get_block_containing_tx(tx['id'])[0]) for tx in cast_votes] - votes_cast = [int(vote[0]) for vote in votes if vote[1] <= height] - total_votes_cast = sum(votes_cast) - total_votes = sum([voter.amount for voter in self.outputs]) - vote_ratio = total_votes_cast/total_votes - return vote_ratio + def get_validator_update_by_election_id(self, election_id, bigchain): + result = bigchain.get_validators_by_election_id(election_id) + return result - def _get_vote_ids(self, bigchain): - election_key = self.to_public_key(self.id) - votes = get_asset_tokens_for_public_key(bigchain.connection, self.id, election_key) - return votes - - def initial_height(self, bigchain): - heights = bigchain.get_block_containing_tx(self.id) - initial_height = 0 - if len(heights) != 0: - initial_height = min(bigchain.get_block_containing_tx(self.id)) - return initial_height - - def get_status(self, bigchain, height=None): - - initial_validators = self.get_validators(bigchain, height=self.initial_height(bigchain)) - - # get all heights where a vote was cast - vote_heights = set([bigchain.get_block_containing_tx(tx['id'])[0] for tx in self._get_vote_ids(bigchain)]) - - # find the least height where the vote succeeds - confirmation_height = None - confirmed_heights = [h for h in vote_heights if self._vote_ratio(bigchain, h) > self.ELECTION_THRESHOLD] - if height: - confirmed_heights = [h for h in confirmed_heights if h <= height] - if len(confirmed_heights) > 0: - confirmation_height = min(confirmed_heights) - - # get the validator set at the confirmation height/current height - if confirmation_height: - final_validators = self.get_validators(bigchain, height=confirmation_height) - else: - final_validators = self.get_validators(bigchain) - - if initial_validators != final_validators: - return self.INCONCLUSIVE - elif confirmation_height: + 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 diff --git a/tests/upsert_validator/conftest.py b/tests/upsert_validator/conftest.py index b5dde04e..20906ada 100644 --- a/tests/upsert_validator/conftest.py +++ b/tests/upsert_validator/conftest.py @@ -48,34 +48,39 @@ def valid_election_b(b, node_key, new_validator): @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]) - vote_0 = vote(valid_election, 0, ed25519_node_keys, b) - vote_1 = vote(valid_election, 1, ed25519_node_keys, b) - block_2 = Block(app_hash='hash_2', height=2, transactions=[vote_0.id, vote_1.id]) b.store_block(block_1._asdict()) - b.store_block(block_2._asdict()) return valid_election @pytest.fixture def concluded_election(b, ongoing_election, ed25519_node_keys): - vote_2 = vote(ongoing_election, 2, ed25519_node_keys, b) - block_4 = Block(app_hash='hash_4', height=4, transactions=[vote_2.id]) - b.store_block(block_4._asdict()) + 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, concluded_election, new_validator): +def inconclusive_election(b, ongoing_election, new_validator): validators = b.get_validators(height=1) validators[0]['voting_power'] = 15 validator_update = {'validators': validators, - 'height': 3, + 'height': 2, 'election_id': 'some_other_election'} query.store_validator_set(b.connection, validator_update) - return concluded_election + return ongoing_election def vote(election, voter, keys, b): diff --git a/tests/upsert_validator/test_validator_election_vote.py b/tests/upsert_validator/test_validator_election_vote.py index f829c853..6c92af99 100644 --- a/tests/upsert_validator/test_validator_election_vote.py +++ b/tests/upsert_validator/test_validator_election_vote.py @@ -234,7 +234,7 @@ def test_upsert_validator(b, node_key, node_keys, ed25519_node_keys): latest_block = b.get_latest_block() # reset the validator set - b.store_validator_set(latest_block['height'], validators) + b.store_validator_set(latest_block['height'], validators, 'previous_election_id') power = 1 public_key = '9B3119650DF82B9A5D8A12E38953EA47475C09F0C48A4E6A0ECE182944B24403'