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)
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(
conn.collection('transactions').aggregate([
{'$match': {'outputs.public_keys': election_public_key,
'operation': 'VALIDATOR_ELECTION_VOTE',
'asset.id': election_id}},
{'$match': query},
{'$project': {'_id': False}}
]))
return cursor

View File

@ -370,11 +370,13 @@ def get_validator_set(conn, height):
@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`.
Args:
election_id (str): Id of the election.
election_public_key (str): base58 encoded public key of the election
operation: filter transaction based on `operation`
Returns:
Iterator of transaction that list given owner in conditions.
"""

View File

@ -1,7 +1,6 @@
"""This module contains all the goodness to integrate BigchainDB
with Tendermint."""
import logging
import codecs
from abci.application import BaseApplication
from abci.types_pb2 import (
@ -12,16 +11,15 @@ from abci.types_pb2 import (
ResponseDeliverTx,
ResponseEndBlock,
ResponseCommit,
Validator,
PubKey
)
from bigchaindb import BigchainDB
from bigchaindb.tendermint_utils import (decode_transaction,
public_key_to_base64,
calculate_hash)
from bigchaindb.lib import Block, PreCommitState
from bigchaindb.backend.query import PRE_COMMIT_ID
from bigchaindb.upsert_validator import ValidatorElection
import bigchaindb.upsert_validator.validator_utils as vutils
CodeTypeOk = 0
@ -47,7 +45,7 @@ class App(BaseApplication):
def init_chain(self, genesis):
"""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=[])
self.bigchaindb.store_block(block._asdict())
self.bigchaindb.store_validator_set(1, validator_set)
@ -135,13 +133,14 @@ class App(BaseApplication):
# TODO: calculate if an election has concluded
# 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_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]
# validator_updates = [vutils.encode_validator(v) for v in validator_updates]
# Store pre-commit state to recover in case there is a crash
# during `commit`
@ -172,37 +171,3 @@ class App(BaseApplication):
self.block_txn_ids)
logger.benchmark('COMMIT_BLOCK, height:%s', self.new_height)
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 import ValidatorElection # noqa

View File

@ -14,6 +14,8 @@ from bigchaindb.common.schema import (_validate_schema,
TX_SCHEMA_VALIDATOR_ELECTION,
TX_SCHEMA_COMMON,
TX_SCHEMA_CREATE)
from . import ValidatorElectionVote
from .validator_utils import (new_validator_set, encode_validator)
class ValidatorElection(Transaction):
@ -83,7 +85,7 @@ class ValidatorElection(Transaction):
bigchain (BigchainDB): an instantiated bigchaindb.lib.BigchainDB object.
Returns:
`True` if the election is valid
ValidatorElection object
Raises:
ValidationError: If the election is invalid
@ -174,7 +176,10 @@ class ValidatorElection(Transaction):
def get_commited_votes(self, bigchain, election_pk=None):
if election_pk is None:
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)
@classmethod
@ -200,3 +205,27 @@ class ValidatorElection(Transaction):
return election
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.
Returns:
`True` if the election vote is valid
ValidatorElectionVote object
Raises:
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,
CodeTypeError,
new_validator_set)
)
from bigchaindb.upsert_validator.validator_utils import new_validator_set
from bigchaindb.tendermint_utils import public_key_to_base64
@ -233,7 +234,7 @@ def test_new_validator_set(b):
validators = [node1]
updates = [node1_new_power, node2]
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 = []
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],
voters,
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 codecs
from bigchaindb.tendermint_utils import public_key_to_base64
from bigchaindb.upsert_validator import ValidatorElection, ValidatorElectionVote
from bigchaindb.common.exceptions import AmountError
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.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
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
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 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)
@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
# ============================================================================
@ -287,3 +319,12 @@ def gen_vote(election, i, ed25519_node_keys):
[([election_pub_key], votes_i)],
election_id=election.id)\
.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)