mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Problem: Election conclusion logic is spread accross multiple files
Solution: Aggregate the logic in ValidatorElection class
This commit is contained in:
parent
84a0d3f197
commit
b2839668d6
@ -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
|
||||||
|
|||||||
@ -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.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -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())
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 []
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
37
bigchaindb/upsert_validator/validator_utils.py
Normal file
37
bigchaindb/upsert_validator/validator_utils.py
Normal 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())
|
||||||
@ -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:
|
||||||
|
|||||||
@ -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])
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user