Problem: Election conclusion logic is spread accross multiple files

Solution: Aggregate the logic in ValidatorElection class
This commit is contained in:
Vanshdeep Singh 2018-08-14 16:44:32 +02:00
parent 84a0d3f197
commit b2839668d6
10 changed files with 144 additions and 58 deletions

View File

@ -306,12 +306,15 @@ def get_validator_set(conn, height=None):
@register_query(LocalMongoDBConnection) @register_query(LocalMongoDBConnection)
def get_received_votes_for_election(conn, election_id, election_public_key): def get_asset_tokens_for_public_keys(conn, asset_id, public_keys, operation=None):
query = {'outputs.public_keys': public_keys,
'asset.id': asset_id}
if operation:
query['operation'] = operation
cursor = conn.run( cursor = conn.run(
conn.collection('transactions').aggregate([ conn.collection('transactions').aggregate([
{'$match': {'outputs.public_keys': election_public_key, {'$match': query},
'operation': 'VALIDATOR_ELECTION_VOTE',
'asset.id': election_id}},
{'$project': {'_id': False}} {'$project': {'_id': False}}
])) ]))
return cursor return cursor

View File

@ -370,11 +370,13 @@ def get_validator_set(conn, height):
@singledispatch @singledispatch
def get_received_votes_for_election(connection, election_id, election_public_key): def get_asset_tokens_for_public_keys(connection, asset_id,
public_keys, operation):
"""Retrieve a list of `VALIDATOR_ELECTION_VOTE`s `txids` that are owned by `owner`. """Retrieve a list of `VALIDATOR_ELECTION_VOTE`s `txids` that are owned by `owner`.
Args: Args:
election_id (str): Id of the election. election_id (str): Id of the election.
election_public_key (str): base58 encoded public key of the election election_public_key (str): base58 encoded public key of the election
operation: filter transaction based on `operation`
Returns: Returns:
Iterator of transaction that list given owner in conditions. Iterator of transaction that list given owner in conditions.
""" """

View File

@ -1,7 +1,6 @@
"""This module contains all the goodness to integrate BigchainDB """This module contains all the goodness to integrate BigchainDB
with Tendermint.""" with Tendermint."""
import logging import logging
import codecs
from abci.application import BaseApplication from abci.application import BaseApplication
from abci.types_pb2 import ( from abci.types_pb2 import (
@ -12,16 +11,15 @@ from abci.types_pb2 import (
ResponseDeliverTx, ResponseDeliverTx,
ResponseEndBlock, ResponseEndBlock,
ResponseCommit, ResponseCommit,
Validator,
PubKey
) )
from bigchaindb import BigchainDB from bigchaindb import BigchainDB
from bigchaindb.tendermint_utils import (decode_transaction, from bigchaindb.tendermint_utils import (decode_transaction,
public_key_to_base64,
calculate_hash) calculate_hash)
from bigchaindb.lib import Block, PreCommitState from bigchaindb.lib import Block, PreCommitState
from bigchaindb.backend.query import PRE_COMMIT_ID from bigchaindb.backend.query import PRE_COMMIT_ID
from bigchaindb.upsert_validator import ValidatorElection
import bigchaindb.upsert_validator.validator_utils as vutils
CodeTypeOk = 0 CodeTypeOk = 0
@ -47,7 +45,7 @@ class App(BaseApplication):
def init_chain(self, genesis): def init_chain(self, genesis):
"""Initialize chain with block of height 0""" """Initialize chain with block of height 0"""
validator_set = [decode_validator(v) for v in genesis.validators] validator_set = [vutils.decode_validator(v) for v in genesis.validators]
block = Block(app_hash='', height=0, transactions=[]) block = Block(app_hash='', height=0, 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(1, validator_set)
@ -135,13 +133,14 @@ class App(BaseApplication):
# TODO: calculate if an election has concluded # TODO: calculate if an election has concluded
# NOTE: ensure the local validator set is updated # NOTE: ensure the local validator set is updated
validator_updates = self.bigchaindb.get_validator_update(self.block_transactions) validator_updates = ValidatorElection.get_validator_update(self.bigchaindb,
self.new_height,
self.block_transactions)
# if validator_updates:
# validator_set = new_validator_set(self.bigchaindb, self.new_height, validator_updates)
# self.bigchaindb.store_validator_set(self.new_height+1, validator_set)
if validator_updates: # validator_updates = [vutils.encode_validator(v) for v in validator_updates]
validator_set = new_validator_set(self.bigchaindb, self.new_height, validator_updates)
self.bigchaindb.store_validator_set(self.new_height+1, validator_set)
validator_updates = [encode_validator(v) for v in validator_updates]
# Store pre-commit state to recover in case there is a crash # Store pre-commit state to recover in case there is a crash
# during `commit` # during `commit`
@ -172,37 +171,3 @@ class App(BaseApplication):
self.block_txn_ids) self.block_txn_ids)
logger.benchmark('COMMIT_BLOCK, height:%s', self.new_height) logger.benchmark('COMMIT_BLOCK, height:%s', self.new_height)
return ResponseCommit(data=data) return ResponseCommit(data=data)
def encode_validator(v):
ed25519_public_key = v['public_key']
# NOTE: tendermint expects public to be encoded in go-amino format
pub_key = PubKey(type='ed25519',
data=bytes.fromhex(ed25519_public_key))
return Validator(pub_key=pub_key,
address=b'',
power=v['power'])
def decode_validator(v):
return {'pub_key': {'type': v.pub_key.type,
'data': codecs.encode(v.pub_key.data, 'base64').decode().rstrip('\n')},
'voting_power': v.power}
def new_validator_set(bigchain, height, updates):
validators = bigchain.get_validators(height)
validators_dict = {}
for v in validators:
validators_dict[v['pub_key']['data']] = v
updates_dict = {}
for u in updates:
public_key64 = public_key_to_base64(u['public_key'])
updates_dict[public_key64] = {'pub_key': {'type': 'ed25519',
'data': public_key64},
'voting_power': u['power']}
new_validators_dict = {**validators_dict, **updates_dict}
return list(new_validators_dict.values())

View File

@ -1,3 +1,3 @@
from bigchaindb.upsert_validator.validator_election import ValidatorElection # noqa
from bigchaindb.upsert_validator.validator_election_vote import ValidatorElectionVote # noqa from bigchaindb.upsert_validator.validator_election_vote import ValidatorElectionVote # noqa
from bigchaindb.upsert_validator.validator_election import ValidatorElection # noqa

View File

@ -14,6 +14,8 @@ from bigchaindb.common.schema import (_validate_schema,
TX_SCHEMA_VALIDATOR_ELECTION, TX_SCHEMA_VALIDATOR_ELECTION,
TX_SCHEMA_COMMON, TX_SCHEMA_COMMON,
TX_SCHEMA_CREATE) TX_SCHEMA_CREATE)
from . import ValidatorElectionVote
from .validator_utils import (new_validator_set, encode_validator)
class ValidatorElection(Transaction): class ValidatorElection(Transaction):
@ -83,7 +85,7 @@ class ValidatorElection(Transaction):
bigchain (BigchainDB): an instantiated bigchaindb.lib.BigchainDB object. bigchain (BigchainDB): an instantiated bigchaindb.lib.BigchainDB object.
Returns: Returns:
`True` if the election is valid ValidatorElection object
Raises: Raises:
ValidationError: If the election is invalid ValidationError: If the election is invalid
@ -174,7 +176,10 @@ class ValidatorElection(Transaction):
def get_commited_votes(self, bigchain, election_pk=None): def get_commited_votes(self, bigchain, election_pk=None):
if election_pk is None: if election_pk is None:
election_pk = self.to_public_key(self.id) election_pk = self.to_public_key(self.id)
txns = list(backend.query.get_received_votes_for_election(bigchain.connection, self.id, election_pk)) txns = list(backend.query.get_asset_tokens_for_public_keys(bigchain.connection,
self.id,
[election_pk],
'VALIDATOR_ELECTION_VOTE'))
return self.count_votes(election_pk, txns) return self.count_votes(election_pk, txns)
@classmethod @classmethod
@ -200,3 +205,27 @@ class ValidatorElection(Transaction):
return election return election
return False return False
@classmethod
def get_validator_update(cls, bigchain, new_height, txns):
votes = {}
for txn in txns:
if isinstance(txn, ValidatorElectionVote):
election_id = txn.asset['id']
election_votes = votes.get(election_id, [])
election_votes.append(txn)
votes[election_id] = election_votes
election = cls.conclude(bigchain, election_id, election_votes)
# Once an election concludes any other conclusion for the same
# or any other election is invalidated
if election:
# The new validator set comes into effect from height = new_height+1
validator_updates = [election.asset['data']]
curr_validator_set = bigchain.get_validators(new_height)
updated_validator_set = new_validator_set(curr_validator_set,
new_height, validator_updates)
bigchain.store_validator_set(new_height+1, updated_validator_set)
return [encode_validator(election.asset['data'])]
return []

View File

@ -24,7 +24,7 @@ class ValidatorElectionVote(Transaction):
bigchain (BigchainDB): an instantiated bigchaindb.lib.BigchainDB object. bigchain (BigchainDB): an instantiated bigchaindb.lib.BigchainDB object.
Returns: Returns:
`True` if the election vote is valid ValidatorElectionVote object
Raises: Raises:
ValidationError: If the election vote is invalid ValidationError: If the election vote is invalid

View File

@ -0,0 +1,37 @@
import codecs
from abci.types_pb2 import (Validator,
PubKey)
from bigchaindb.tendermint_utils import public_key_to_base64
def encode_validator(v):
ed25519_public_key = v['public_key']
# NOTE: tendermint expects public to be encoded in go-amino format
pub_key = PubKey(type='ed25519',
data=bytes.fromhex(ed25519_public_key))
return Validator(pub_key=pub_key,
address=b'',
power=v['power'])
def decode_validator(v):
return {'pub_key': {'type': v.pub_key.type,
'data': codecs.encode(v.pub_key.data, 'base64').decode().rstrip('\n')},
'voting_power': v.power}
def new_validator_set(validators, height, updates):
validators_dict = {}
for v in validators:
validators_dict[v['pub_key']['data']] = v
updates_dict = {}
for u in updates:
public_key64 = public_key_to_base64(u['public_key'])
updates_dict[public_key64] = {'pub_key': {'type': 'ed25519',
'data': public_key64},
'voting_power': u['power']}
new_validators_dict = {**validators_dict, **updates_dict}
return list(new_validators_dict.values())

View File

@ -8,7 +8,8 @@ from abci.types_pb2 import (
from bigchaindb.core import (CodeTypeOk, from bigchaindb.core import (CodeTypeOk,
CodeTypeError, CodeTypeError,
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
@ -233,7 +234,7 @@ 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)
updated_validator_set = new_validator_set(b, 1, updates) updated_validator_set = new_validator_set(b.get_validators(1), 1, updates)
updated_validators = [] updated_validators = []
for u in updates: for u in updates:

View File

@ -40,3 +40,11 @@ def valid_election(b_mock, 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 valid_election_b(b, node_key, new_validator):
voters = ValidatorElection.recipients(b)
return ValidatorElection.generate([node_key.public_key],
voters,
new_validator, None).sign([node_key.private_key])

View File

@ -1,5 +1,7 @@
import pytest import pytest
import codecs
from bigchaindb.tendermint_utils import public_key_to_base64
from bigchaindb.upsert_validator import ValidatorElection, ValidatorElectionVote from bigchaindb.upsert_validator import ValidatorElection, ValidatorElectionVote
from bigchaindb.common.exceptions import AmountError from bigchaindb.common.exceptions import AmountError
from bigchaindb.common.crypto import generate_key_pair from bigchaindb.common.crypto import generate_key_pair
@ -194,7 +196,7 @@ def test_valid_election_conclude(b_mock, valid_election, ed25519_node_keys):
@pytest.mark.tendermint @pytest.mark.tendermint
@pytest.mark.bdb @pytest.mark.bdb
def test_get_validator_update(b_mock, valid_election, ed25519_node_keys): def test_get_validator_update_conclude(b_mock, valid_election, ed25519_node_keys):
# store election # store election
b_mock.store_bulk_transactions([valid_election]) b_mock.store_bulk_transactions([valid_election])
@ -218,7 +220,6 @@ def test_get_validator_update(b_mock, valid_election, ed25519_node_keys):
@pytest.mark.abci @pytest.mark.abci
def test_upsert_validator(b, node_key, node_keys, new_validator, ed25519_node_keys): def test_upsert_validator(b, node_key, node_keys, new_validator, ed25519_node_keys):
from bigchaindb.tendermint_utils import public_key_to_base64
import time import time
import requests import requests
@ -269,6 +270,37 @@ def test_upsert_validator(b, node_key, node_keys, new_validator, ed25519_node_ke
assert (public_key64 in validator_pub_keys) assert (public_key64 in validator_pub_keys)
@pytest.mark.tendermint
@pytest.mark.bdb
def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys):
reset_validator_set(b, node_keys, 1)
power = 1
public_key = '9B3119650DF82B9A5D8A12E38953EA47475C09F0C48A4E6A0ECE182944B24403'
public_key64 = public_key_to_base64(public_key)
new_validator = {'public_key': public_key,
'node_id': 'some_node_id',
'power': power}
voters = ValidatorElection.recipients(b)
election = ValidatorElection.generate([node_key.public_key],
voters,
new_validator).sign([node_key.private_key])
# store election
b.store_bulk_transactions([election])
tx_vote0 = gen_vote(election, 0, ed25519_node_keys)
tx_vote1 = gen_vote(election, 1, ed25519_node_keys)
tx_vote2 = gen_vote(election, 2, ed25519_node_keys)
assert ValidatorElection.conclude(b, election.id, [tx_vote0, tx_vote1, tx_vote2])
update = ValidatorElection.get_validator_update(b, 4, [tx_vote0, tx_vote1, tx_vote2])
assert len(update) == 1
update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n')
assert update_public_key == public_key64
# ============================================================================ # ============================================================================
# Helper functions # Helper functions
# ============================================================================ # ============================================================================
@ -287,3 +319,12 @@ def gen_vote(election, i, ed25519_node_keys):
[([election_pub_key], votes_i)], [([election_pub_key], votes_i)],
election_id=election.id)\ election_id=election.id)\
.sign([key_i.private_key]) .sign([key_i.private_key])
def reset_validator_set(b, node_keys, height):
validators = []
for (node_pub, _) in node_keys.items():
validators.append({'pub_key': {'type': 'ed25519',
'data': node_pub},
'voting_power': 10})
b.store_validator_set(height, validators)