Skip to content

Commit

Permalink
Generalize election management commands (#2515)
Browse files Browse the repository at this point in the history
* Problem: `ValidatorElection` and `MigrationElection` need to inherit from a common `Election` class

Solution: Factored the common logic out of `ValidatorElection` and moved it to `Election` parent class

* Problem: Adding base58 as a requirement seems to break the build...

Solution: Reverting the changes

* Problem: Responding to a suggestion for improved method naming

Solution: Refactored `get_result_by_election_id` to `get_election_result_by_id`

* Problem: No need to store different types of elections in their own tables

Solution: Remove `DB_TABLE` property from `Election` class

* Revert "Problem: No need to store different types of elections in their own tables"

This reverts commit db45374.

* Problem: Missed a method in `Bigchain` class when updating the naming for an election method

Solution: Finished the refactoring

* Problem: Need a table to store data for all elections

Solution: Created the `elections` table with secondary_index `election_id`

* Problem: `Election` class needs to be updated to store concluded elections in the `elections` table

Solution: Updated the class to use the new table

* Problem: `UpsertValidatorVote` can be generalized to just be `Vote`

Solution: Renamed, refactored and moved the `Vote` class to tie in with the more general `Election` base class

* Problem: Error in docstring return signature

Solution: Fixed the docstring

* Problem: Hardcoded reference to the `VOTE_TYPE` in `Election` base class

Solution: Pointed the reference to the class variable

* Problem: Schema still refers to `ValidatorElectionVote` instead of `Vote`

Solution:  Renamed `TX_SCHEMA_VALIDATOR_ELECTION_VOTE` as `TX_SCHEMA_VOTE`

* Problem: `Election` class variable `ELECTION_TYPE` is overly specific

Solution: Renamed `ELECTION_TYPE` to `OPERATION`

* Problem: Command line options for `upsert-validator` can be generalized to manage any type of election

Solution: Refactored the CLI to manage generalized elections

* Problem: Default for `show_election` not implemented for `Election` class

Solution: Create a default method that work if all fields in the 'asset[data]' can be displayed without additional formatting

* Problem: Multiple small issues with style etc.

Solution: Addressed comments from PR

* Problem: `Election` class variable to `VOTE_TYPE` unnecessary

Solution: Removed the variable and hardcoded everything to use the `Vote` class

* Problem: Minor style issues with PR

Solution: Addressing comments

* Problem: Changes to format for validator keys broke some tests

Solution: Aligned the tests to reflect the changed key format

* Problem: `election show` command displaying the base56 public key

Solution: Cast any public key to base64

* Problem: `election_parser` help message still refers to upsert-validator

Solution: Updated the help message
  • Loading branch information
z-bowen authored and kansi committed Sep 12, 2018
1 parent 5a44084 commit 8a7650c
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 92 deletions.
76 changes: 35 additions & 41 deletions bigchaindb/commands/bigchaindb.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
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, public_key_to_base64
from bigchaindb.tendermint_utils import public_key_from_base64
from bigchaindb.commands.election_types import elections

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -101,16 +102,20 @@ def run_configure(args):


@configure_bigchaindb
def run_upsert_validator(args):
"""Initiate and manage elections to change the validator set"""
def run_election(args):
"""Initiate and manage elections"""

b = BigchainDB()

# Call the function specified by args.action, as defined above
globals()[f'run_upsert_validator_{args.action}'](args, b)
globals()[f'run_election_{args.action}'](args, b)


def run_upsert_validator_new(args, bigchain):
def run_election_new(args, bigchain):
globals()[f'run_election_new_{args.election_type}'](args, bigchain)


def run_election_new_upsert_validator(args, bigchain):
"""Initiates an election to add/update/remove a validator to an existing BigchainDB network
:param args: dict
Expand Down Expand Up @@ -154,8 +159,8 @@ def run_upsert_validator_new(args, bigchain):
return False


def run_upsert_validator_approve(args, bigchain):
"""Approve an election to add/update/remove a validator to an existing BigchainDB network
def run_election_approve(args, bigchain):
"""Approve an election
:param args: dict
args = {
Expand Down Expand Up @@ -192,8 +197,8 @@ def run_upsert_validator_approve(args, bigchain):
return False


def run_upsert_validator_show(args, bigchain):
"""Retrieves information about an upsert-validator election
def run_election_show(args, bigchain):
"""Retrieves information about an election
:param args: dict
args = {
Expand All @@ -207,14 +212,7 @@ def run_upsert_validator_show(args, bigchain):
logger.error(f'No election found with election_id {args.election_id}')
return

new_validator = election.asset['data']

public_key = public_key_to_base64(new_validator['public_key']['value'])
power = new_validator['power']
node_id = new_validator['node_id']
status = election.get_status(bigchain)

response = f'public_key={public_key}\npower={power}\nnode_id={node_id}\nstatus={status}'
response = election.show_election(bigchain)

logger.info(response)

Expand Down Expand Up @@ -317,41 +315,37 @@ def create_parser():
help='The backend to use. It can only be '
'"localmongodb", currently.')

# parser for managing validator elections
validator_parser = subparsers.add_parser('upsert-validator',
help='Add/update/delete a validator.')

validator_subparser = validator_parser.add_subparsers(title='Action',
dest='action')

new_election_parser = validator_subparser.add_parser('new',
help='Calls a new election.')
# parser for managing elections
election_parser = subparsers.add_parser('election',
help='Manage elections.')

new_election_parser.add_argument('public_key',
help='Public key of the validator to be added/updated/removed.')
election_subparser = election_parser.add_subparsers(title='Action',
dest='action')

new_election_parser.add_argument('power',
type=int,
help='The proposed power for the validator. '
'Setting to 0 will remove the validator.')
new_election_parser = election_subparser.add_parser('new',
help='Calls a new election.')

new_election_parser.add_argument('node_id',
help='The node_id of the validator.')
new_election_subparser = new_election_parser.add_subparsers(title='Election_Type',
dest='election_type')

new_election_parser.add_argument('--private-key',
dest='sk',
help='Path to the private key of the election initiator.')
# Parser factory for each type of new election, so we get a bunch of commands that look like this:
# election new <some_election_type> <args>...
for name, data in elections.items():
args = data['args']
generic_parser = new_election_subparser.add_parser(name, help=data['help'])
for arg, kwargs in args.items():
generic_parser.add_argument(arg, **kwargs)

approve_election_parser = validator_subparser.add_parser('approve',
help='Approve the election.')
approve_election_parser = election_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.')

show_election_parser = validator_subparser.add_parser('show',
help='Provides information about an election.')
show_election_parser = election_subparser.add_parser('show',
help='Provides information about an election.')

show_election_parser.add_argument('election_id',
help='The transaction id of the election you wish to query.')
Expand Down
20 changes: 20 additions & 0 deletions bigchaindb/commands/election_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
elections = {
'upsert-validator': {
'help': 'Propose a change to the validator set',
'args': {
'public_key': {
'help': 'Public key of the validator to be added/updated/removed.'
},
'power': {
'type': int,
'help': 'The proposed power for the validator. Setting to 0 will remove the validator.'},
'node_id': {
'help': 'The node_id of the validator.'
},
'--private-key': {
'dest': 'sk',
'help': 'Path to the private key of the election initiator.'
}
}
}
}
14 changes: 13 additions & 1 deletion bigchaindb/elections/election.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
InvalidProposer,
UnequalValidatorSet,
DuplicateTransaction)
from bigchaindb.tendermint_utils import key_from_base64
from bigchaindb.tendermint_utils import key_from_base64, public_key_to_base64
from bigchaindb.common.crypto import (public_key_from_ed25519_key)
from bigchaindb.common.transaction import Transaction
from bigchaindb.common.schema import (_validate_schema,
Expand Down Expand Up @@ -229,6 +229,18 @@ def get_election(self, election_id, bigchain):
def store_election_results(cls, bigchain, election, height):
bigchain.store_election_results(height, election)

def show_election(self, bigchain):
data = self.asset['data']
if 'public_key' in data.keys():
data['public_key'] = public_key_to_base64(data['public_key']['value'])
response = ''
for k, v in data.items():
if k != 'seed':
response += f'{k}={v}\n'
response += f'status={self.get_status(bigchain)}'

return response

@classmethod
def approved_update(cls, bigchain, new_height, txns):
votes = {}
Expand Down
42 changes: 23 additions & 19 deletions docs/server/source/server-reference/bigchaindb-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,25 @@ configuration file as documented under
[Configuration Settings](configuration.html).


## bigchaindb upsert-validator
## bigchaindb election

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.
Manage elections to manage the BigChainDB network. The specifics of the election process are defined in [BEP-18](https://github.com/bigchaindb/BEPs/tree/master/18), please refer it for more details.

Election management is broken into several subcommands. Below is the command line syntax for each,

#### upsert-validator new
#### election new

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.
Create a new election which proposes a change to your BigChainDB network.

There are multiple types of election, which each take different parameters. Below is a short description of each type of election, as well as their command line syntax and the return value.

###### election new upsert-validator

Create an election 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
$ bigchaindb election new upsert-validator E_PUBKEY E_POWER E_NODE_ID --private-key PATH_TO_YOUR_PRIVATE_KEY
[SUCCESS] Submitted proposal with id: <election_id>
```

Expand All @@ -109,7 +114,7 @@ NOTE: A change to the validator set can only be proposed by one of the exisitng
Example usage,

```bash
$ bigchaindb upsert-validator new HHG0IQRybpT6nJMIWWFWhMczCLHt6xcm7eP52GnGuPY= 1 fb7140f03a4ffad899fabbbf655b97e0321add66 --private-key /home/user/.tendermint/config/priv_validator.json
$ bigchaindb election new upsert-validator HHG0IQRybpT6nJMIWWFWhMczCLHt6xcm7eP52GnGuPY= 1 fb7140f03a4ffad899fabbbf655b97e0321add66 --private-key /home/user/.tendermint/config/priv_validator.json
[SUCCESS] Submitted proposal with id: 04a067582cf03eba2b53b82e4adb5ece424474cbd4f7183780855a93ac5e3caa
```

Expand All @@ -119,13 +124,14 @@ If the command succeeds, it will create an election and return an `election_id`.
**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
#### election approve

Approve an election by voting for it. The proposal generated by executing `bigchaindb election new ...` can be 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.

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
$ bigchaindb election approve <election_id> --private-key PATH_TO_YOUR_PRIVATE_KEY
[SUCCESS] Your vote has been submitted
```

Expand All @@ -134,24 +140,22 @@ $ bigchaindb upsert-validator approve <election_id> --private-key PATH_TO_YOUR_P

Example usage,
```bash
$ bigchaindb upsert-validator approve 04a067582cf03eba2b53b82e4adb5ece424474cbd4f7183780855a93ac5e3caa --private-key /home/user/.tendermint/config/priv_validator.json
$ bigchaindb election 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 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.
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 is applied only after `60` (`2/3 * 90`) have been received.

#### upsert-validator show
#### election show

Retrieves information about an election initiated by `upsert-validator new`.
Retrieves information about an election initiated by `election new`.

Below is the command line syntax and the return value,

```bash
$ bigchaindb upsert-validator show ELECTION_ID
public_key=<e_pub_key>
power=<e_power>
node_id=<e_node_id>
$ bigchaindb election show ELECTION_ID
<election_data>
status=<status>
```

The `public_key`, `power`, and `node_id` are the same values used in the `upsert-validator new` command that originally triggered the election. `status` takes three possible values, `ongoing`, if the election has not yet reached a 2/3 majority, `concluded`, if the election reached the 2/3 majority needed to pass, or `inconclusive`, if the validator set changed while the election was in process, rendering it undecidable.
The election data is the same set of arguments used in the `election new` command that originally triggered the election. `status` takes three possible values, `ongoing`, if the election has not yet reached a 2/3 majority, `concluded`, if the election reached the 2/3 majority needed to pass, or `inconclusive`, if the validator set changed while the election was in process, rendering it undecidable.

0 comments on commit 8a7650c

Please sign in to comment.