Problem: Logic for upsert-validator show is convoluted

Solution: Rewrote the function to be much simpler
This commit is contained in:
z-bowen 2018-08-30 15:14:52 +02:00
parent 4c64b6c52c
commit 96dc8a245e
7 changed files with 78 additions and 58 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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': <block_height>,
'asset': {
'height': <block_height>,
'validators': <validator_set>,
'election_id': <election_id_that_approved_the_change>
}
}
"""
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

View File

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

View File

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