Problem: There is no way to vote for an election (#2458)

* Problem: `run_upsert_validator_approve` was getting the voting power in a convoluted way

Solution: Changed it to get the voting power from the election outputs

* Problem: `run_upsert_validator_approve` casts votes for *all* voters, not just the user calling the command

Solution: Filter vote txs by the users public key

* Problem: Docs needed a more specific description of how to input the path to the private-key file

Solution: Changed the wording a bit
This commit is contained in:
codegeschrei 2018-08-24 09:52:00 +02:00 committed by Vanshdeep Singh
parent a16d561f54
commit d31ab9fb40
8 changed files with 265 additions and 33 deletions

View File

@ -16,9 +16,10 @@ import sys
from bigchaindb.utils import load_node_key
from bigchaindb.common.exceptions import (DatabaseAlreadyExists,
DatabaseDoesNotExist,
OperationError)
OperationError, KeypairMismatchException)
import bigchaindb
from bigchaindb import backend, ValidatorElection, BigchainDB
from bigchaindb import (backend, ValidatorElection,
BigchainDB, ValidatorElectionVote)
from bigchaindb.backend import schema
from bigchaindb.backend import query
from bigchaindb.backend.query import PRE_COMMIT_ID
@ -143,6 +144,43 @@ def run_upsert_validator_new(args, bigchain):
raise OperationError('Failed to commit election')
def run_upsert_validator_approve(args, bigchain):
"""Approve an election to add/update/remove a validator to an existing BigchainDB network
:param args: dict
args = {
'election_id': the election_id of the election (str)
'sk': the path to the private key of the signer (str)
}
:param bigchain: an instance of BigchainDB
:return: a success message
:raises: OperationError if the write transaction fails for any reason
"""
key = load_node_key(args.sk)
tx = bigchain.get_transaction(args.election_id)
voting_powers = [v.amount for v in tx.outputs if key.public_key in v.public_keys]
if len(voting_powers) > 0:
voting_power = voting_powers[0]
else:
raise KeypairMismatchException(
'The key you provided does not match any of the eligible voters in this election.'
)
inputs = [i for i in tx.to_inputs() if key.public_key in i.owners_before]
approval = ValidatorElectionVote.generate(inputs, [
([key.public_key], voting_power)], tx.id).sign([key.private_key])
approval.validate(bigchain)
resp = bigchain.write_transaction(approval, 'broadcast_tx_commit')
if resp == (202, ''):
print('Your vote has been submitted.')
return approval.id
else:
raise OperationError('Failed to vote for election')
def _run_init():
bdb = bigchaindb.BigchainDB()
@ -264,6 +302,14 @@ def create_parser():
dest='sk',
help='Path to the private key of the election initiator.')
approve_election_parser = validator_subparser.add_parser('approve',
help='Approve the election.')
approve_election_parser.add_argument('election_id',
help='The election_id of the election.')
approve_election_parser.add_argument('--private-key',
dest='sk',
help='Path to the private key of the election initiator.')
# parsers for showing/exporting config values
subparsers.add_parser('show-config',
help='Show the current configuration')

View File

@ -41,7 +41,6 @@ class ValidatorElection(Transaction):
"""Return a dictionary of validators with key as `public_key` and
value as the `voting_power`
"""
validators = {}
for validator in bigchain.get_validators(height):
# NOTE: we assume that Tendermint encodes public key in base64

View File

@ -110,3 +110,17 @@ $ bigchaindb upsert-validator new B0E42D2589A455EAD339A035D6CE1C8C3E25863F268120
```
If the command succeeds, it will create an election and return an `election_id`. Elections consist of one vote token per voting power, issued to the members of the validator set. Validators can cast their votes to approve the change to the validator set by spending their vote tokens. The status of the election can be monitored by providing the `election_id` to the `show` subcommand.
#### upsert-validator approve
Approve an election by voting for it.
Below is the command line syntax and the return value,
```bash
$ bigchaindb upsert-validator approve <election_id> --private-key PATH_TO_YOUR_PRIVATE_KEY
```
Here, `<election_id>` is the transaction id of the election the approval should be given for. `--private-key` should be the path to Tendermint's private key which can be generally found at `/home/user/.tendermint/config/priv_validator.json`.
Example usage,
```bash
$ bigchaindb upsert-validator approve 04a067582cf03eba2b53b82e4adb5ece424474cbd4f7183780855a93ac5e3caa --private-key /home/user/.tendermint/config/priv_validator.json
```
If the command succeeds, a message will be returned, that the vote was submitted successfully.

View File

@ -9,6 +9,10 @@ from argparse import Namespace
import pytest
from bigchaindb import ValidatorElection
from bigchaindb.common.exceptions import KeypairMismatchException
from tests.conftest import node_keys
@pytest.mark.tendermint
def test_make_sure_we_dont_remove_any_command():
@ -24,6 +28,8 @@ def test_make_sure_we_dont_remove_any_command():
assert parser.parse_args(['start']).command
assert parser.parse_args(['upsert-validator', 'new', 'TEMP_PUB_KEYPAIR', '10', 'TEMP_NODE_ID',
'--private-key', 'TEMP_PATH_TO_PRIVATE_KEY']).command
assert parser.parse_args(['upsert-validator', 'approve', 'ELECTION_ID', '--private-key',
'TEMP_PATH_TO_PRIVATE_KEY']).command
@pytest.mark.tendermint
@ -358,13 +364,6 @@ def test_upsert_validator_new_with_tendermint(b, priv_validator_path, user_sk, m
time.sleep(3)
def mock_get():
return [
{'pub_key': {'value': 'zL/DasvKulXZzhSNFwx4cLRXKkSM9GPK7Y0nZ4FEylM=',
'type': 'tendermint/PubKeyEd25519'},
'voting_power': 10}
]
# b.get_validators = mock_get
# mock_get_validators = mock_get
# monkeypatch.setattr('requests.get', mock_get)
@ -385,16 +384,9 @@ def test_upsert_validator_new_with_tendermint(b, priv_validator_path, user_sk, m
@pytest.mark.tendermint
@pytest.mark.bdb
def test_upsert_validator_new_without_tendermint(b, priv_validator_path, user_sk, monkeypatch):
def test_upsert_validator_new_without_tendermint(b, priv_validator_path, user_sk):
from bigchaindb.commands.bigchaindb import run_upsert_validator_new
def mock_get(height):
return [
{'pub_key': {'data': 'zL/DasvKulXZzhSNFwx4cLRXKkSM9GPK7Y0nZ4FEylM=',
'type': 'tendermint/PubKeyEd25519'},
'voting_power': 10}
]
def mock_write(tx, mode):
b.store_bulk_transactions([tx])
return (202, '')
@ -411,3 +403,100 @@ def test_upsert_validator_new_without_tendermint(b, priv_validator_path, user_sk
resp = run_upsert_validator_new(args, b)
assert b.get_transaction(resp)
@pytest.mark.abci
def test_upsert_validator_approve_with_tendermint(b, priv_validator_path, user_sk, validators):
from bigchaindb.commands.bigchaindb import run_upsert_validator_new, \
run_upsert_validator_approve
public_key = '8eJ8q9ZQpReWyQT5aFCiwtZ5wDZC4eDnCen88p3tQ6ie'
new_args = Namespace(action='new',
public_key=public_key,
power=1,
node_id='12345',
sk=priv_validator_path,
config={})
election_id = run_upsert_validator_new(new_args, b)
args = Namespace(action='approve',
election_id=election_id,
sk=priv_validator_path,
config={})
approve = run_upsert_validator_approve(args, b)
assert b.get_transaction(approve)
@pytest.mark.bdb
@pytest.mark.tendermint
def test_upsert_validator_approve_without_tendermint(b, priv_validator_path, new_validator, node_key):
from bigchaindb.commands.bigchaindb import run_upsert_validator_approve
from argparse import Namespace
b, election_id = call_election(b, new_validator, node_key)
# call run_upsert_validator_approve with args that point to the election
args = Namespace(action='approve',
election_id=election_id,
sk=priv_validator_path,
config={})
approval_id = run_upsert_validator_approve(args, b)
# assert returned id is in the db
assert b.get_transaction(approval_id)
@pytest.mark.bdb
@pytest.mark.tendermint
def test_upsert_validator_approve_called_with_bad_key(b, bad_validator_path, new_validator, node_key):
from bigchaindb.commands.bigchaindb import run_upsert_validator_approve
from argparse import Namespace
b, election_id = call_election(b, new_validator, node_key)
# call run_upsert_validator_approve with args that point to the election, but a bad signing key
args = Namespace(action='approve',
election_id=election_id,
sk=bad_validator_path,
config={})
with pytest.raises(KeypairMismatchException):
run_upsert_validator_approve(args, b)
def mock_get(height):
keys = node_keys()
pub_key = list(keys.keys())[0]
return [
{'pub_key': {'data': pub_key,
'type': 'tendermint/PubKeyEd25519'},
'voting_power': 10}
]
def call_election(b, new_validator, node_key):
def mock_write(tx, mode):
b.store_bulk_transactions([tx])
return (202, '')
# patch the validator set. We now have one validator with power 10
b.get_validators = mock_get
b.write_transaction = mock_write
# our voters is a list of length 1, populated from our mocked validator
voters = ValidatorElection.recipients(b)
# and our voter is the public key from the voter list
voter = node_key.public_key
valid_election = ValidatorElection.generate([voter],
voters,
new_validator, None).sign([node_key.private_key])
# patch in an election with a vote issued to the user
election_id = valid_election.id
b.store_bulk_transactions([valid_election])
return b, election_id

View File

@ -665,7 +665,7 @@ def ed25519_node_keys(node_keys):
return node_keys_dict
@pytest.fixture(scope='session')
@pytest.fixture
def node_keys():
return {'zL/DasvKulXZzhSNFwx4cLRXKkSM9GPK7Y0nZ4FEylM=':
'cM5oW4J0zmUSZ/+QRoRlincvgCwR0pEjFoY//ZnnjD3Mv8Nqy8q6VdnOFI0XDHhwtFcqRIz0Y8rtjSdngUTKUw==',
@ -677,7 +677,7 @@ def node_keys():
'uz8bYgoL4rHErWT1gjjrnA+W7bgD/uDQWSRKDmC8otc95wnnxJo1GxYlmh0OaqOkJaobpu13BcUcvITjRFiVgw=='}
@pytest.fixture(scope='session')
@pytest.fixture
def priv_validator_path(node_keys):
(public_key, private_key) = list(node_keys.items())[0]
priv_validator = {
@ -699,3 +699,79 @@ def priv_validator_path(node_keys):
json.dump(priv_validator, socket)
socket.close()
return path
@pytest.fixture
def bad_validator_path(node_keys):
(public_key, private_key) = list(node_keys.items())[1]
priv_validator = {
'address': '84F787D95E196DC5DE5F972666CFECCA36801426',
'pub_key': {
'type': 'AC26791624DE60',
'value': public_key
},
'last_height': 0,
'last_round': 0,
'last_step': 0,
'priv_key': {
'type': '954568A3288910',
'value': private_key
}
}
fd, path = tempfile.mkstemp()
socket = os.fdopen(fd, 'w')
json.dump(priv_validator, socket)
socket.close()
return path
@pytest.fixture
def validators(b, node_keys):
from bigchaindb.backend import query
height = get_block_height(b)
original_validators = b.get_validators()
(public_key, private_key) = list(node_keys.items())[0]
validator_set = [{'address': 'F5426F0980E36E03044F74DD414248D29ABCBDB2',
'pub_key': {
'data': public_key,
'type': 'ed25519'},
'voting_power': 10}]
validator_update = {'validators': validator_set,
'height': height + 1}
query.store_validator_set(b.connection, validator_update)
yield
height = get_block_height(b)
validator_update = {'validators': original_validators,
'height': height}
query.store_validator_set(b.connection, validator_update)
def get_block_height(b):
if b.get_latest_block():
height = b.get_latest_block()['height']
else:
height = 0
return height
@pytest.fixture
def new_validator():
public_key = '1718D2DBFF00158A0852A17A01C78F4DCF3BA8E4FB7B8586807FAC182A535034'
power = 1
node_id = 'fake_node_id'
return {'public_key': public_key,
'power': power,
'node_id': node_id}

View File

@ -14,17 +14,6 @@ def b_mock(b, network_validators):
return b
@pytest.fixture
def new_validator():
public_key = '1718D2DBFF00158A0852A17A01C78F4DCF3BA8E4FB7B8586807FAC182A535034'
power = 1
node_id = 'fake_node_id'
return {'public_key': public_key,
'power': power,
'node_id': node_id}
def mock_get_validators(network_validators):
def validator_set(height):
validators = []

View File

@ -10,7 +10,7 @@ from bigchaindb.upsert_validator import ValidatorElection, ValidatorElectionVote
from bigchaindb.common.exceptions import AmountError
from bigchaindb.common.crypto import generate_key_pair
from bigchaindb.common.exceptions import ValidationError
from tests.utils import generate_block
pytestmark = [pytest.mark.execute]
@ -219,10 +219,13 @@ def test_valid_election_conclude(b_mock, valid_election, ed25519_node_keys):
@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, ed25519_node_keys):
import time
import requests
if b.get_latest_block()['height'] == 0:
generate_block(b)
(node_pub, _) = list(node_keys.items())[0]
validators = [{'pub_key': {'type': 'ed25519',

View File

@ -21,3 +21,19 @@ def flush_localmongo_db(connection, dbname):
connection.conn[dbname].metadata.delete_many({})
connection.conn[dbname].utxos.delete_many({})
connection.conn[dbname].validators.delete_many({})
def generate_block(bigchain):
from bigchaindb.common.crypto import generate_key_pair
from bigchaindb.models import Transaction
import time
alice = generate_key_pair()
tx = Transaction.create([alice.public_key],
[([alice.public_key], 1)],
asset=None)\
.sign([alice.private_key])
code, message = bigchain.write_transaction(tx, 'broadcast_tx_commit')
assert code == 202
time.sleep(2)