Problem: Need a method to initiate a new upsert-validator election

Solution: Added subcommand `new` to `upsert_validator`
This commit is contained in:
z-bowen 2018-08-08 10:48:17 +02:00
parent 2799e50ec0
commit 2a3d728d84
6 changed files with 167 additions and 64 deletions

View File

@ -1,5 +1,4 @@
ARG python_version=3.6
FROM python:${python_version}
FROM python:3.6
LABEL maintainer "dev@bigchaindb.com"
RUN apt-get update \
@ -8,30 +7,3 @@ RUN apt-get update \
&& pip install pynacl \
&& apt-get autoremove \
&& apt-get clean
ARG backend
ARG abci_status
# When developing with Python in a docker container, we are using PYTHONBUFFERED
# to force stdin, stdout and stderr to be totally unbuffered and to capture logs/outputs
ENV PYTHONUNBUFFERED 0
ENV BIGCHAINDB_DATABASE_PORT 27017
ENV BIGCHAINDB_DATABASE_BACKEND $backend
ENV BIGCHAINDB_SERVER_BIND 0.0.0.0:9984
ENV BIGCHAINDB_WSSERVER_HOST 0.0.0.0
ENV BIGCHAINDB_WSSERVER_SCHEME ws
ENV BIGCHAINDB_WSSERVER_ADVERTISED_HOST 0.0.0.0
ENV BIGCHAINDB_WSSERVER_ADVERTISED_SCHEME ws
ENV BIGCHAINDB_TENDERMINT_PORT 26657
ENV BIGCHAINDB_CI_ABCI ${abci_status}
RUN mkdir -p /usr/src/app
COPY . /usr/src/app/
WORKDIR /usr/src/app
RUN pip install --no-cache-dir --process-dependency-links -e .[dev]
RUN bigchaindb -y configure

View File

@ -9,19 +9,19 @@ import copy
import json
import sys
from bigchaindb.utils import load_node_key
from bigchaindb.common.exceptions import (DatabaseAlreadyExists,
DatabaseDoesNotExist,
MultipleValidatorOperationError)
OperationError)
import bigchaindb
from bigchaindb import backend
from bigchaindb import backend, ValidatorElection, BigchainDB
from bigchaindb.backend import schema
from bigchaindb.backend import query
from bigchaindb.backend.query import VALIDATOR_UPDATE_ID, PRE_COMMIT_ID
from bigchaindb.backend.query import PRE_COMMIT_ID
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__)
@ -95,21 +95,35 @@ def run_configure(args):
@configure_bigchaindb
def run_upsert_validator(args):
"""Store validators which should be synced with Tendermint"""
"""Initiate and manage elections to change the validator set"""
b = bigchaindb.BigchainDB()
public_key = public_key_from_base64(args.public_key)
validator = {'pub_key': {'type': 'ed25519',
'data': public_key},
'power': args.power}
validator_update = {'validator': validator,
'update_id': VALIDATOR_UPDATE_ID}
try:
query.store_validator_update(b.connection, validator_update)
except MultipleValidatorOperationError:
logger.error('A validator update is pending to be applied. '
'Please re-try after the current update has '
'been processed.')
b = BigchainDB()
# Call the function specified by args.action, as defined above
globals()[f'run_upsert_validator_{args.action}'](args, b)
def run_upsert_validator_new(args, bigchain):
new_validator = {
'public_key': args.public_key,
'power': args.power,
'node_id': args.node_id
}
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)
resp = bigchain.write_transaction(election, 'broadcast_tx_commit')
if resp == (202, ''):
return election.id
else:
raise OperationError('Failed to commit election')
def _run_init():
@ -208,16 +222,35 @@ 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')
help='Add/update/delete a validator.')
validator_parser.add_argument('public_key',
help='Public key of the validator.')
validator_subparser = validator_parser.add_subparsers(title='Action',
dest='action')
validator_parser.add_argument('power',
new_election_parser = validator_subparser.add_parser('new',
help='Calls a new election.')
new_election_parser.add_argument('public_key',
help='Public key of the validator to be added/updated/removed.')
new_election_parser.add_argument('power',
type=int,
help='Voting power of the validator. '
'Setting it to 0 will delete the validator.')
help='The proposed power for the validator. '
'Setting to 0 will remove the validator.')
new_election_parser.add_argument('node_id',
help='The node_id of the validator.')
new_election_parser.add_argument('--private-key',
dest='sk',
help='Path to the private key of the election initiator.')
show_election_subparser = validator_subparser.add_parser('show',
help='Show election information.')
show_election_subparser.add_argument('election_id')
# parsers for showing/exporting config values
subparsers.add_parser('show-config',

View File

@ -467,6 +467,7 @@ class BigchainDB(object):
for v in validators:
v.pop('accum')
v.pop('address')
v['voting_power'] = int(v['voting_power'])
return validators

View File

@ -167,3 +167,14 @@ class Lazy:
self.stack = []
return last
# Load Tendermint's public and private key from the file path
def load_node_key(path):
import json
from bigchaindb.tendermint_utils import key_from_base64
from bigchaindb.common.crypto import key_pair_from_ed25519_key
with open(path) as json_data:
priv_validator = json.load(json_data)
priv_key = priv_validator['priv_key']['value']
hex_private_key = key_from_base64(priv_key)
return key_pair_from_ed25519_key(hex_private_key)

View File

@ -18,7 +18,9 @@ def test_make_sure_we_dont_remove_any_command():
assert parser.parse_args(['init']).command
assert parser.parse_args(['drop']).command
assert parser.parse_args(['start']).command
assert parser.parse_args(['upsert-validator', 'TEMP_PUB_KEYPAIR', '10']).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', 'show', 'TEMP_ELECTION_ID']).command
@pytest.mark.tendermint
@ -341,14 +343,74 @@ class MockResponse():
return {'result': {'latest_block_height': self.height}}
@patch('bigchaindb.config_utils.autoconfigure')
@patch('bigchaindb.backend.query.store_validator_update')
#@pytest.mark.execute
#@patch('bigchaindb.lib.BigchainDB.get_validators')
#@pytest.mark.abci
def test_upsert_validator_new_with_tendermint(b, priv_validator_path, user_sk, monkeypatch):
"""
WIP: Will be fixed and activated in the next PR
"""
from bigchaindb.commands.bigchaindb import run_upsert_validator_new
import time
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)
proposer_key = b.get_validators()[0]['pub_key']['value']
args = Namespace(action='new',
public_key=proposer_key,
power=1,
node_id='12345',
sk=priv_validator_path,
config={})
print("*****ARGS*****")
print(args)
resp = run_upsert_validator_new(args, b)
print("*****TX ID*****")
print(resp)
time.sleep(3)
assert b.get_transaction(resp)
@pytest.mark.tendermint
def test_upsert_validator(mock_autoconfigure, mock_store_validator_update):
from bigchaindb.commands.bigchaindb import run_upsert_validator
@pytest.mark.bdb
def test_upsert_validator_new_without_tendermint(b, priv_validator_path, user_sk, monkeypatch):
from bigchaindb.commands.bigchaindb import run_upsert_validator_new
args = Namespace(public_key='CJxdItf4lz2PwEf4SmYNAu/c/VpmX39JEgC5YpH7fxg=',
power='10', config={})
run_upsert_validator(args)
def mock_get():
return [
{'pub_key': {'value': 'zL/DasvKulXZzhSNFwx4cLRXKkSM9GPK7Y0nZ4FEylM=',
'type': 'tendermint/PubKeyEd25519'},
'voting_power': 10}
]
assert mock_store_validator_update.called
def mock_write(tx, mode):
b.store_transaction(tx)
return (202, '')
b.get_validators = mock_get
b.write_transaction = mock_write
monkeypatch.setattr('requests.get', mock_get)
args = Namespace(action='new',
public_key='CJxdItf4lz2PwEf4SmYNAu/c/VpmX39JEgC5YpH7fxg=',
power=1,
node_id='12345',
sk=priv_validator_path,
config={})
resp = run_upsert_validator_new(args, b)
assert b.get_transaction(resp)

View File

@ -4,10 +4,11 @@ Tasks:
1. setup test database before starting the tests
2. delete test database after running the tests
"""
import json
import os
import copy
import random
import tempfile
from collections import namedtuple
from logging import getLogger
from logging.config import dictConfig
@ -657,3 +658,26 @@ def node_keys():
'83VINXdj2ynOHuhvSZz5tGuOE5oYzIi0mEximkX1KYMlt/Csu8JUjA4+by2Pz3fqSLshhuYYeM+IpvqcBl6BEA==',
'PecJ58SaNRsWJZodDmqjpCWqG6btdwXFHLyE40RYlYM=':
'uz8bYgoL4rHErWT1gjjrnA+W7bgD/uDQWSRKDmC8otc95wnnxJo1GxYlmh0OaqOkJaobpu13BcUcvITjRFiVgw=='}
@pytest.fixture(scope='session')
def priv_validator_path(node_keys):
(public_key, private_key) = list(node_keys.items())[0]
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