mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Problem: upsert-validator 'approve' command does not transfer vote to election public key (#2480)
Solution: Transfer vote to the election public key; generalize implemenation to handle election txn type * Problem: Upsert valdiator 'new' doesn't accept public in base64 format Solution: Tendermint stores all keys in base64 format so it would suitable to abandon base58 encoding in favour of base64 encoding * Problem: Not test for invalid execution of upsert-validator 'new' Solution: Write tests to when invalid power or private key path has been supplied * Problem: Exceptions are not informational when executing upsert-validator Solution: generate error logs or print statement indicating success
This commit is contained in:
parent
dfadbff60f
commit
8e97c753eb
@ -16,7 +16,7 @@ import sys
|
||||
from bigchaindb.utils import load_node_key
|
||||
from bigchaindb.common.exceptions import (DatabaseAlreadyExists,
|
||||
DatabaseDoesNotExist,
|
||||
OperationError, KeypairMismatchException)
|
||||
ValidationError)
|
||||
import bigchaindb
|
||||
from bigchaindb import (backend, ValidatorElection,
|
||||
BigchainDB, ValidatorElectionVote)
|
||||
@ -27,6 +27,8 @@ from bigchaindb.commands import utils
|
||||
from bigchaindb.commands.utils import (configure_bigchaindb,
|
||||
input_on_stderr)
|
||||
from bigchaindb.log import setup_logging
|
||||
from bigchaindb.tendermint_utils import public_key_from_base64
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -119,29 +121,36 @@ def run_upsert_validator_new(args, bigchain):
|
||||
'sk': the path to the private key of the node calling the election (str)
|
||||
}
|
||||
:param bigchain: an instance of BigchainDB
|
||||
:return: election_id (tx_id)
|
||||
:raises: OperationError if the write transaction fails for any reason
|
||||
:return: election_id or `False` in case of failure
|
||||
"""
|
||||
|
||||
new_validator = {
|
||||
'public_key': args.public_key,
|
||||
'public_key': public_key_from_base64(args.public_key),
|
||||
'power': args.power,
|
||||
'node_id': args.node_id
|
||||
}
|
||||
|
||||
key = load_node_key(args.sk)
|
||||
try:
|
||||
key = load_node_key(args.sk)
|
||||
voters = ValidatorElection.recipients(bigchain)
|
||||
election = ValidatorElection.generate([key.public_key],
|
||||
voters,
|
||||
new_validator, None).sign([key.private_key])
|
||||
election.validate(bigchain)
|
||||
except ValidationError as e:
|
||||
logger.error(e)
|
||||
return False
|
||||
except FileNotFoundError as fd_404:
|
||||
logger.error(fd_404)
|
||||
return False
|
||||
|
||||
voters = ValidatorElection.recipients(bigchain)
|
||||
|
||||
election = ValidatorElection.generate([key.public_key],
|
||||
voters,
|
||||
new_validator, None).sign([key.private_key])
|
||||
election.validate(bigchain)
|
||||
resp = bigchain.write_transaction(election, 'broadcast_tx_commit')
|
||||
if resp == (202, ''):
|
||||
logger.info('[SUCCESS] Submitted proposal with id: {}'.format(election.id))
|
||||
return election.id
|
||||
else:
|
||||
raise OperationError('Failed to commit election')
|
||||
logger.error('Failed to commit election proposal')
|
||||
return False
|
||||
|
||||
|
||||
def run_upsert_validator_approve(args, bigchain):
|
||||
@ -153,8 +162,7 @@ def run_upsert_validator_approve(args, bigchain):
|
||||
'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
|
||||
:return: success log message or `False` in case of error
|
||||
"""
|
||||
|
||||
key = load_node_key(args.sk)
|
||||
@ -163,22 +171,24 @@ def run_upsert_validator_approve(args, bigchain):
|
||||
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.'
|
||||
)
|
||||
logger.error('The key you provided does not match any of the eligible voters in this election.')
|
||||
return False
|
||||
|
||||
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])
|
||||
election_pub_key = ValidatorElection.to_public_key(tx.id)
|
||||
approval = ValidatorElectionVote.generate(inputs,
|
||||
[([election_pub_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.')
|
||||
logger.info('[SUCCESS] Your vote has been submitted')
|
||||
return approval.id
|
||||
else:
|
||||
raise OperationError('Failed to vote for election')
|
||||
logger.error('Failed to commit vote')
|
||||
return False
|
||||
|
||||
|
||||
def _run_init():
|
||||
|
@ -220,6 +220,7 @@ class ValidatorElection(Transaction):
|
||||
updated_validator_set = new_validator_set(curr_validator_set,
|
||||
new_height, validator_updates)
|
||||
|
||||
updated_validator_set = [v for v in updated_validator_set if v['voting_power'] > 0]
|
||||
bigchain.store_validator_set(new_height+1, updated_validator_set)
|
||||
return [encode_validator(election.asset['data'])]
|
||||
return []
|
||||
|
@ -83,44 +83,59 @@ configuration file as documented under
|
||||
|
||||
## bigchaindb upsert-validator
|
||||
|
||||
**This is an experimental feature. Users are advised not to use it in production.**
|
||||
|
||||
|
||||
Manage elections to add, update, or remove a validator from the validators set of the local node. The upsert-validator subcommands implement [BEP-21](https://github.com/bigchaindb/BEPs/tree/master/21). Check it out if you need more details on how this is orchestrated.
|
||||
Manage elections to add, update, or remove a validator from the validators set. The upsert-validator subcommands implement [BEP-21](https://github.com/bigchaindb/BEPs/tree/master/21), please refer it for more details.
|
||||
|
||||
Election management is broken into several subcommands. Below is the command line syntax for each,
|
||||
|
||||
#### upsert-validator new
|
||||
|
||||
Calls a new election, proposing a change to the validator set.
|
||||
Create a new election which proposes a change to the validator set. An election can be used to add/update/remove a validator from the validator set.
|
||||
|
||||
Below is the command line syntax and the return value,
|
||||
|
||||
```bash
|
||||
$ bigchaindb upsert-validator new E_PUBKEY E_POWER E_NODE_ID --private-key PATH_TO_YOUR_PRIVATE_KEY
|
||||
<election_id>
|
||||
[SUCCESS] Submitted proposal with id: <election_id>
|
||||
```
|
||||
|
||||
Here, `E_PUBKEY`, `E_POWER`, and `E_NODE_ID` are the public key, proposed power, and node id of the validator being voted on. `--private-key` should be the path to wherever the private key for your validator node is stored, (*not* the private key itself.). For example, to add a new validator, provide the public key and node id for some node not already in the validator set, along with whatever voting power you'd like them to have. To remove an existing validator, provide their public key and node id, and set `E_POWER` to `0`.
|
||||
- `E_PUBKEY`: Public key of the node to be added/updated/removed.
|
||||
- `E_POWER`: The new power for the `E_PUBKEY`. NOTE, if power is set to `0` then `E_PUBKEY` will be removed from the validator set when the election concludes.
|
||||
- `E_NODE_ID`: Node id of `E_PUBKEY`. The node operator of `E_PUBKEY` can generate the node id via `tendermint show_node_id`.
|
||||
- `--private-key`: The path to Tendermint's private key which can be generally found at `/home/user/.tendermint/config/priv_validator.json`. For example, to add a new validator, provide the public key and node id for some node not already in the validator set, along with whatever voting power you'd like them to have. To remove an existing validator, provide their public key and node id, and set `E_POWER` to `0`. Please note that the private key provided here is of the node which is generating this election i.e.
|
||||
|
||||
|
||||
NOTE: A change to the validator set can only be proposed by one of the exisitng validators.
|
||||
|
||||
Example usage,
|
||||
|
||||
```bash
|
||||
$ bigchaindb upsert-validator new B0E42D2589A455EAD339A035D6CE1C8C3E25863F268120AA0162AD7D003A4014 1 12345 --private-key /home/user/.tendermint/config/priv_validator.json
|
||||
$ bigchaindb upsert-validator new HHG0IQRybpT6nJMIWWFWhMczCLHt6xcm7eP52GnGuPY= 1 fb7140f03a4ffad899fabbbf655b97e0321add66 --private-key /home/user/.tendermint/config/priv_validator.json
|
||||
[SUCCESS] Submitted proposal with id: 04a067582cf03eba2b53b82e4adb5ece424474cbd4f7183780855a93ac5e3caa
|
||||
```
|
||||
|
||||
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.
|
||||
If the command succeeds, it will create an election and return an `election_id`. A successful execution of the above command **doesn't** imply that the validator set will be immediately updated but rather it means the proposal has been succcessfully accepted by the network. Once the `election_id` has been generated the node operator should share this `election_id` with other validators in the network and urge them to approve the proposal. Note that the node operator should themsleves also approve the proposal.
|
||||
|
||||
|
||||
**NOTE**: The election proposal consists of vote tokens allocated to each current validator as per their voting power. Validators then cast their votes to approve the change to the validator set by spending their vote tokens.
|
||||
|
||||
|
||||
#### upsert-validator approve
|
||||
Approve an election by voting for it.
|
||||
Below is the command line syntax and the return value,
|
||||
|
||||
Approve an election by voting for it. The propsal generated by executing `bigchaindb upsert-valdiator approve ...` can approved by the validators using this command. The validator who is approving the proposal will spend all their votes i.e. if the validator has a network power of `10` then they will cast `10` votes for the proposal.`
|
||||
Below is the command line syntax and the return value,
|
||||
|
||||
```bash
|
||||
$ bigchaindb upsert-validator approve <election_id> --private-key PATH_TO_YOUR_PRIVATE_KEY
|
||||
[SUCCESS] Your vote has been submitted
|
||||
```
|
||||
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`.
|
||||
|
||||
- `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
|
||||
[SUCCESS] Your vote has been submitted
|
||||
```
|
||||
If the command succeeds, a message will be returned, that the vote was submitted successfully.
|
||||
|
||||
If the command succeeds a message will be returned stating that the vote was submitted successfully. Once a proposal has been approved by sufficent validators (more than `2/3` of the total voting power) then the proposed change is applied to the network. For example, consider a network wherein the total power is `90` then the proposed changed applied only after `60` (`2/3 * 90`) have been received.
|
||||
|
@ -3,6 +3,7 @@
|
||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
from argparse import Namespace
|
||||
@ -10,7 +11,6 @@ from argparse import Namespace
|
||||
import pytest
|
||||
|
||||
from bigchaindb import ValidatorElection
|
||||
from bigchaindb.common.exceptions import KeypairMismatchException
|
||||
from tests.conftest import node_keys
|
||||
|
||||
|
||||
@ -379,7 +379,7 @@ 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):
|
||||
def test_upsert_validator_new_without_tendermint(caplog, b, priv_validator_path, user_sk):
|
||||
from bigchaindb.commands.bigchaindb import run_upsert_validator_new
|
||||
|
||||
def mock_write(tx, mode):
|
||||
@ -392,24 +392,67 @@ def test_upsert_validator_new_without_tendermint(b, priv_validator_path, user_sk
|
||||
args = Namespace(action='new',
|
||||
public_key='CJxdItf4lz2PwEf4SmYNAu/c/VpmX39JEgC5YpH7fxg=',
|
||||
power=1,
|
||||
node_id='12345',
|
||||
node_id='fb7140f03a4ffad899fabbbf655b97e0321add66',
|
||||
sk=priv_validator_path,
|
||||
config={})
|
||||
resp = run_upsert_validator_new(args, b)
|
||||
|
||||
assert b.get_transaction(resp)
|
||||
with caplog.at_level(logging.INFO):
|
||||
election_id = run_upsert_validator_new(args, b)
|
||||
assert caplog.records[0].msg == '[SUCCESS] Submitted proposal with id: ' + election_id
|
||||
assert b.get_transaction(election_id)
|
||||
|
||||
|
||||
@pytest.mark.tendermint
|
||||
@pytest.mark.bdb
|
||||
def test_upsert_validator_new_invalid_election(caplog, b, priv_validator_path, user_sk):
|
||||
from bigchaindb.commands.bigchaindb import run_upsert_validator_new
|
||||
|
||||
args = Namespace(action='new',
|
||||
public_key='CJxdItf4lz2PwEf4SmYNAu/c/VpmX39JEgC5YpH7fxg=',
|
||||
power=10,
|
||||
node_id='fb7140f03a4ffad899fabbbf655b97e0321add66',
|
||||
sk='/tmp/invalid/path/key.json',
|
||||
config={})
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
assert not run_upsert_validator_new(args, b)
|
||||
assert caplog.records[0].msg.__class__ == FileNotFoundError
|
||||
|
||||
|
||||
@pytest.mark.tendermint
|
||||
@pytest.mark.bdb
|
||||
def test_upsert_validator_new_election_invalid_power(caplog, b, priv_validator_path, user_sk):
|
||||
from bigchaindb.commands.bigchaindb import run_upsert_validator_new
|
||||
from bigchaindb.common.exceptions import InvalidPowerChange
|
||||
|
||||
def mock_write(tx, mode):
|
||||
b.store_bulk_transactions([tx])
|
||||
return (400, '')
|
||||
|
||||
b.write_transaction = mock_write
|
||||
b.get_validators = mock_get
|
||||
args = Namespace(action='new',
|
||||
public_key='CJxdItf4lz2PwEf4SmYNAu/c/VpmX39JEgC5YpH7fxg=',
|
||||
power=10,
|
||||
node_id='fb7140f03a4ffad899fabbbf655b97e0321add66',
|
||||
sk=priv_validator_path,
|
||||
config={})
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
assert not run_upsert_validator_new(args, b)
|
||||
assert caplog.records[0].msg.__class__ == InvalidPowerChange
|
||||
|
||||
|
||||
@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
|
||||
from bigchaindb.commands.bigchaindb import (run_upsert_validator_new,
|
||||
run_upsert_validator_approve)
|
||||
|
||||
public_key = '8eJ8q9ZQpReWyQT5aFCiwtZ5wDZC4eDnCen88p3tQ6ie'
|
||||
public_key = 'CJxdItf4lz2PwEf4SmYNAu/c/VpmX39JEgC5YpH7fxg='
|
||||
new_args = Namespace(action='new',
|
||||
public_key=public_key,
|
||||
power=1,
|
||||
node_id='12345',
|
||||
node_id='fb7140f03a4ffad899fabbbf655b97e0321add66',
|
||||
sk=priv_validator_path,
|
||||
config={})
|
||||
|
||||
@ -426,7 +469,7 @@ def test_upsert_validator_approve_with_tendermint(b, priv_validator_path, user_s
|
||||
|
||||
@pytest.mark.bdb
|
||||
@pytest.mark.tendermint
|
||||
def test_upsert_validator_approve_without_tendermint(b, priv_validator_path, new_validator, node_key):
|
||||
def test_upsert_validator_approve_without_tendermint(caplog, b, priv_validator_path, new_validator, node_key):
|
||||
from bigchaindb.commands.bigchaindb import run_upsert_validator_approve
|
||||
from argparse import Namespace
|
||||
|
||||
@ -438,15 +481,41 @@ def test_upsert_validator_approve_without_tendermint(b, priv_validator_path, new
|
||||
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)
|
||||
with caplog.at_level(logging.INFO):
|
||||
approval_id = run_upsert_validator_approve(args, b)
|
||||
assert caplog.records[0].msg == '[SUCCESS] Your vote has been submitted'
|
||||
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):
|
||||
@pytest.mark.bdb
|
||||
def test_upsert_validator_approve_failure(caplog, 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)
|
||||
|
||||
def mock_write(tx, mode):
|
||||
b.store_bulk_transactions([tx])
|
||||
return (400, '')
|
||||
|
||||
b.write_transaction = mock_write
|
||||
|
||||
# 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={})
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
assert not run_upsert_validator_approve(args, b)
|
||||
assert caplog.records[0].msg == 'Failed to commit vote'
|
||||
|
||||
|
||||
@pytest.mark.tendermint
|
||||
@pytest.mark.bdb
|
||||
def test_upsert_validator_approve_called_with_bad_key(caplog, b, bad_validator_path, new_validator, node_key):
|
||||
from bigchaindb.commands.bigchaindb import run_upsert_validator_approve
|
||||
from argparse import Namespace
|
||||
|
||||
@ -458,8 +527,10 @@ def test_upsert_validator_approve_called_with_bad_key(b, bad_validator_path, new
|
||||
sk=bad_validator_path,
|
||||
config={})
|
||||
|
||||
with pytest.raises(KeypairMismatchException):
|
||||
run_upsert_validator_approve(args, b)
|
||||
with caplog.at_level(logging.ERROR):
|
||||
assert not run_upsert_validator_approve(args, b)
|
||||
assert caplog.records[0].msg == 'The key you provided does not match any of '\
|
||||
'the eligible voters in this election.'
|
||||
|
||||
|
||||
def mock_get(height):
|
||||
|
@ -310,11 +310,37 @@ def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys):
|
||||
b.store_bulk_transactions([tx_vote0, tx_vote1])
|
||||
|
||||
update = ValidatorElection.get_validator_update(b, 4, [tx_vote2])
|
||||
print('update', update)
|
||||
update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n')
|
||||
assert len(update) == 1
|
||||
assert update_public_key == public_key64
|
||||
|
||||
# remove validator
|
||||
power = 0
|
||||
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)
|
||||
|
||||
b.store_bulk_transactions([tx_vote0, tx_vote1])
|
||||
|
||||
update = ValidatorElection.get_validator_update(b, 9, [tx_vote2])
|
||||
update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n')
|
||||
assert len(update) == 1
|
||||
assert update_public_key == public_key64
|
||||
|
||||
# assert that the public key is not a part of the current validator set
|
||||
for v in b.get_validators(10):
|
||||
assert not v['pub_key']['data'] == public_key64
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Helper functions
|
||||
|
Loading…
x
Reference in New Issue
Block a user