Merge branch 'master' of github.com:bigchaindb/bigchaindb

This commit is contained in:
z-bowen 2018-08-15 11:53:57 +02:00
commit 645f26c1aa
11 changed files with 242 additions and 395 deletions

View File

@ -34,4 +34,3 @@ COPY . /usr/src/app/
WORKDIR /usr/src/app WORKDIR /usr/src/app
RUN pip install --no-cache-dir --process-dependency-links -e .[dev] RUN pip install --no-cache-dir --process-dependency-links -e .[dev]
RUN bigchaindb -y configure RUN bigchaindb -y configure

View File

@ -9,19 +9,19 @@ import copy
import json import json
import sys import sys
from bigchaindb.utils import load_node_key
from bigchaindb.common.exceptions import (DatabaseAlreadyExists, from bigchaindb.common.exceptions import (DatabaseAlreadyExists,
DatabaseDoesNotExist, DatabaseDoesNotExist,
MultipleValidatorOperationError) OperationError)
import bigchaindb import bigchaindb
from bigchaindb import backend from bigchaindb import backend, ValidatorElection, BigchainDB
from bigchaindb.backend import schema from bigchaindb.backend import schema
from bigchaindb.backend import query 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 import utils
from bigchaindb.commands.utils import (configure_bigchaindb, from bigchaindb.commands.utils import (configure_bigchaindb,
input_on_stderr) input_on_stderr)
from bigchaindb.log import setup_logging from bigchaindb.log import setup_logging
from bigchaindb.tendermint_utils import public_key_from_base64
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -95,21 +95,48 @@ def run_configure(args):
@configure_bigchaindb @configure_bigchaindb
def run_upsert_validator(args): 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() b = BigchainDB()
public_key = public_key_from_base64(args.public_key)
validator = {'pub_key': {'type': 'ed25519', # Call the function specified by args.action, as defined above
'data': public_key}, globals()[f'run_upsert_validator_{args.action}'](args, b)
'power': args.power}
validator_update = {'validator': validator,
'update_id': VALIDATOR_UPDATE_ID} def run_upsert_validator_new(args, bigchain):
try: """Initiates an election to add/update/remove a validator to an existing BigchainDB network
query.store_validator_update(b.connection, validator_update)
except MultipleValidatorOperationError: :param args: dict
logger.error('A validator update is pending to be applied. ' args = {
'Please re-try after the current update has ' 'public_key': the public key of the proposed peer, (str)
'been processed.') 'power': the proposed validator power for the new peer, (str)
'node_id': the node_id of the new peer (str)
'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
"""
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(): def _run_init():
@ -208,16 +235,30 @@ def create_parser():
help='The backend to use. It can only be ' help='The backend to use. It can only be '
'"localmongodb", currently.') '"localmongodb", currently.')
# parser for managing validator elections
validator_parser = subparsers.add_parser('upsert-validator', 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', validator_subparser = validator_parser.add_subparsers(title='Action',
help='Public key of the validator.') dest='action')
validator_parser.add_argument('power', new_election_parser = validator_subparser.add_parser('new',
type=int, help='Calls a new election.')
help='Voting power of the validator. '
'Setting it to 0 will delete the validator.') 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='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.')
# parsers for showing/exporting config values # parsers for showing/exporting config values
subparsers.add_parser('show-config', subparsers.add_parser('show-config',

View File

@ -96,10 +96,6 @@ class ThresholdTooDeep(ValidationError):
"""Raised if threshold condition is too deep""" """Raised if threshold condition is too deep"""
class GenesisBlockAlreadyExistsError(ValidationError):
"""Raised when trying to create the already existing genesis block"""
class MultipleValidatorOperationError(ValidationError): class MultipleValidatorOperationError(ValidationError):
"""Raised when a validator update pending but new request is submited""" """Raised when a validator update pending but new request is submited"""

View File

@ -4,7 +4,3 @@ class BigchainDBError(Exception):
class CriticalDoubleSpend(BigchainDBError): class CriticalDoubleSpend(BigchainDBError):
"""Data integrity error that requires attention""" """Data integrity error that requires attention"""
class CriticalDoubleInclusion(BigchainDBError):
"""Data integrity error that requires attention"""

View File

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

View File

@ -2,9 +2,13 @@ import contextlib
import threading import threading
import queue import queue
import multiprocessing as mp import multiprocessing as mp
import json
import setproctitle import setproctitle
from bigchaindb.tendermint_utils import key_from_base64
from bigchaindb.common.crypto import key_pair_from_ed25519_key
class ProcessGroup(object): class ProcessGroup(object):
@ -31,7 +35,8 @@ class ProcessGroup(object):
class Process(mp.Process): class Process(mp.Process):
"""Wrapper around multiprocessing.Process that uses """Wrapper around multiprocessing.Process that uses
setproctitle to set the name of the process when running setproctitle to set the name of the process when running
the target task.""" the target task.
"""
def run(self): def run(self):
setproctitle.setproctitle(self.name) setproctitle.setproctitle(self.name)
@ -167,3 +172,12 @@ class Lazy:
self.stack = [] self.stack = []
return last return last
# Load Tendermint's public and private key from the file path
def load_node_key(path):
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

@ -11,10 +11,11 @@ Libraries and Tools Maintained by the BigchainDB Team
Community-Driven Libraries and Tools Community-Driven Libraries and Tools
------------------------------------ ------------------------------------
.. note:: .. warning::
Some of these projects are a work in progress, Some of these projects are a work in progress,
but may still be useful. but may still be useful.
Others might not work with the latest version of BigchainDB.
* `Haskell transaction builder <https://github.com/bigchaindb/bigchaindb-hs>`_ * `Haskell transaction builder <https://github.com/bigchaindb/bigchaindb-hs>`_
* `Go driver <https://github.com/zbo14/envoke/blob/master/bigchain/bigchain.go>`_ * `Go driver <https://github.com/zbo14/envoke/blob/master/bigchain/bigchain.go>`_

View File

@ -80,22 +80,27 @@ configuration file as documented under
**This is an experimental feature. Users are advised not to use it in production.** **This is an experimental feature. Users are advised not to use it in production.**
Add, update, or remove a validator from the validators set of the local node. The command implements [3/UPSERT-VALIDATORS](https://github.com/bigchaindb/BEPs/tree/master/3), 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 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.
Below is the command line syntax, 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.
Below is the command line syntax and the return value,
```bash ```bash
$ bigchaindb upsert-validator PUBLIC_KEY_OF_VALIDATOR POWER $ bigchaindb upsert-validator new E_PUBKEY E_POWER E_NODE_ID --private-key PATH_TO_YOUR_PRIVATE_KEY
<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`.
Example usage, Example usage,
```bash ```bash
$ bigchaindb upsert-validator B0E42D2589A455EAD339A035D6CE1C8C3E25863F268120AA0162AD7D003A4014 10 $ bigchaindb upsert-validator new B0E42D2589A455EAD339A035D6CE1C8C3E25863F268120AA0162AD7D003A4014 1 12345 --private-key /home/user/.tendermint/config/priv_validator.json
``` ```
If the command is returns without any error then a request to update the validator set has been successfully submitted. So, even if the command has been successfully executed it doesn't imply that the validator set has been updated. In order to check whether the change has been applied, the node operator can execute `curl http://node_ip:9984/api/v1/validators` which will list the current validators set. Refer to the [validators](/http-client-server-api.html#validators) section of the HTTP API docs for more detail. 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.
Note:
- When `POWER`is set to `0` then the validator will be removed from the validator set.
- Upsert requests are handled once per block i.e. the validators set is updated once a new block is committed. So, the node operator is not allowed to submit a new upsert request until the current request has been processed. Furthermore, if Tendermint is started with `--consensus.create_empty_blocks=false`, and there are no new incoming transactions then the validators set update is delayed until any new transactions are received and a new block can be committed.

View File

@ -18,7 +18,8 @@ def test_make_sure_we_dont_remove_any_command():
assert parser.parse_args(['init']).command assert parser.parse_args(['init']).command
assert parser.parse_args(['drop']).command assert parser.parse_args(['drop']).command
assert parser.parse_args(['start']).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
@pytest.mark.tendermint @pytest.mark.tendermint
@ -341,15 +342,70 @@ class MockResponse():
return {'result': {'latest_block_height': self.height}} return {'result': {'latest_block_height': self.height}}
# @pytest.mark.execute
# @patch('bigchaindb.lib.BigchainDB.get_validators')
# @pytest.mark.abci
@pytest.mark.skip @pytest.mark.skip
@patch('bigchaindb.config_utils.autoconfigure') def test_upsert_validator_new_with_tendermint(b, priv_validator_path, user_sk, monkeypatch):
@patch('bigchaindb.backend.query.store_validator_update') """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={})
resp = run_upsert_validator_new(args, b)
time.sleep(3)
assert b.get_transaction(resp)
@pytest.mark.tendermint @pytest.mark.tendermint
def test_upsert_validator(mock_autoconfigure, mock_store_validator_update): @pytest.mark.bdb
from bigchaindb.commands.bigchaindb import run_upsert_validator 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=', def mock_get():
power='10', config={}) return [
run_upsert_validator(args) {'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 1. setup test database before starting the tests
2. delete test database after running the tests 2. delete test database after running the tests
""" """
import json
import os import os
import copy import copy
import random import random
import tempfile
from collections import namedtuple from collections import namedtuple
from logging import getLogger from logging import getLogger
from logging.config import dictConfig from logging.config import dictConfig
@ -329,17 +330,19 @@ def _get_height(b):
def inputs(user_pk, b, alice): def inputs(user_pk, b, alice):
from bigchaindb.models import Transaction from bigchaindb.models import Transaction
# create blocks with transactions for `USER` to spend # create blocks with transactions for `USER` to spend
for block in range(4): for height in range(1, 4):
transactions = [ transactions = [
Transaction.create( Transaction.create(
[alice_pubkey(alice)], [alice_pubkey(alice)],
[([user_pk], 1)], [([user_pk], 1)],
metadata={'msg': random.random()}, metadata={'msg': random.random()},
).sign([alice_privkey(alice)]).to_dict() ).sign([alice_privkey(alice)])
for _ in range(10) for _ in range(10)
] ]
block = Block(app_hash='', height=_get_height(b), transactions=transactions) tx_ids = [tx.id for tx in transactions]
block = Block(app_hash='hash'+str(height), height=height, transactions=tx_ids)
b.store_block(block._asdict()) b.store_block(block._asdict())
b.store_bulk_transactions(transactions)
@pytest.fixture @pytest.fixture
@ -668,3 +671,27 @@ def node_keys():
'83VINXdj2ynOHuhvSZz5tGuOE5oYzIi0mEximkX1KYMlt/Csu8JUjA4+by2Pz3fqSLshhuYYeM+IpvqcBl6BEA==', '83VINXdj2ynOHuhvSZz5tGuOE5oYzIi0mEximkX1KYMlt/Csu8JUjA4+by2Pz3fqSLshhuYYeM+IpvqcBl6BEA==',
'PecJ58SaNRsWJZodDmqjpCWqG6btdwXFHLyE40RYlYM=': 'PecJ58SaNRsWJZodDmqjpCWqG6btdwXFHLyE40RYlYM=':
'uz8bYgoL4rHErWT1gjjrnA+W7bgD/uDQWSRKDmC8otc95wnnxJo1GxYlmh0OaqOkJaobpu13BcUcvITjRFiVgw=='} '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

View File

@ -7,196 +7,74 @@ from base58 import b58decode
pytestmark = pytest.mark.bdb pytestmark = pytest.mark.bdb
@pytest.mark.skipif(reason='Some tests throw a ResourceWarning that might result in some weird '
'exceptions while running the tests. The problem seems to *not* '
'interfere with the correctness of the tests. ')
def test_remove_unclosed_sockets():
pass
class TestBigchainApi(object): class TestBigchainApi(object):
@pytest.mark.genesis @pytest.mark.tendermint
def test_get_last_voted_block_cyclic_blockchain(self, b, monkeypatch, alice): def test_get_spent_with_double_inclusion_detected(self, b, alice):
from bigchaindb.common.crypto import PrivateKey
from bigchaindb.common.exceptions import CyclicBlockchainError
from bigchaindb.common.utils import serialize
from bigchaindb.models import Transaction
tx = Transaction.create([alice.public_key], [([alice.public_key], 1)])
tx = tx.sign([alice.private_key])
monkeypatch.setattr('time.time', lambda: 1)
block1 = b.create_block([tx])
b.write_block(block1)
# Manipulate vote to create a cyclic Blockchain
vote = b.vote(block1.id, b.get_last_voted_block().id, True)
vote['vote']['previous_block'] = block1.id
vote_data = serialize(vote['vote'])
vote['signature'] = PrivateKey(alice.private_key).sign(vote_data.encode())
b.write_vote(vote)
with pytest.raises(CyclicBlockchainError):
b.get_last_voted_block()
@pytest.mark.genesis
def test_try_voting_while_constructing_cyclic_blockchain(self, b,
monkeypatch, alice):
from bigchaindb.common.exceptions import CyclicBlockchainError
from bigchaindb.models import Transaction
tx = Transaction.create([alice.public_key], [([alice.public_key], 1)])
tx = tx.sign([alice.private_key])
block1 = b.create_block([tx])
# We can simply submit twice the same block id and check if `Bigchain`
# throws
with pytest.raises(CyclicBlockchainError):
b.vote(block1.id, block1.id, True)
@pytest.mark.genesis
def test_has_previous_vote_when_already_voted(self, b, monkeypatch, alice):
from bigchaindb.models import Transaction from bigchaindb.models import Transaction
from bigchaindb.backend.exceptions import OperationError
tx = Transaction.create([alice.public_key], [([alice.public_key], 1)]) tx = Transaction.create([alice.public_key], [([alice.public_key], 1)])
tx = tx.sign([alice.private_key]) tx = tx.sign([alice.private_key])
monkeypatch.setattr('time.time', lambda: 1) b.store_bulk_transactions([tx])
block = b.create_block([tx])
b.write_block(block)
assert b.has_previous_vote(block.id) is False
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
assert b.has_previous_vote(block.id) is True
@pytest.mark.genesis
def test_get_spent_with_double_inclusion_detected(self, b, monkeypatch, alice):
from bigchaindb.exceptions import CriticalDoubleInclusion
from bigchaindb.models import Transaction
tx = Transaction.create([alice.public_key], [([alice.public_key], 1)])
tx = tx.sign([alice.private_key])
monkeypatch.setattr('time.time', lambda: 1000000000)
block1 = b.create_block([tx])
b.write_block(block1)
monkeypatch.setattr('time.time', lambda: 1000000020)
transfer_tx = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 1)], transfer_tx = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 1)],
asset_id=tx.id) asset_id=tx.id)
transfer_tx = transfer_tx.sign([alice.private_key]) transfer_tx = transfer_tx.sign([alice.private_key])
block2 = b.create_block([transfer_tx]) b.store_bulk_transactions([transfer_tx])
b.write_block(block2)
monkeypatch.setattr('time.time', lambda: 1000000030)
transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 1)], transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 1)],
asset_id=tx.id) asset_id=tx.id)
transfer_tx2 = transfer_tx2.sign([alice.private_key]) transfer_tx2 = transfer_tx2.sign([alice.private_key])
block3 = b.create_block([transfer_tx2]) with pytest.raises(OperationError):
b.write_block(block3) b.store_bulk_transactions([transfer_tx2])
# Vote both block2 and block3 valid @pytest.mark.tendermint
vote = b.vote(block2.id, b.get_last_voted_block().id, True) def test_get_spent_with_double_spend_detected(self, b, alice):
b.write_vote(vote)
vote = b.vote(block3.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
with pytest.raises(CriticalDoubleInclusion):
b.get_spent(tx.id, 0)
@pytest.mark.genesis
def test_get_spent_with_double_spend_detected(self, b, monkeypatch, alice):
from bigchaindb.exceptions import CriticalDoubleSpend
from bigchaindb.models import Transaction from bigchaindb.models import Transaction
from bigchaindb.common.exceptions import DoubleSpend
from bigchaindb.exceptions import CriticalDoubleSpend
tx = Transaction.create([alice.public_key], [([alice.public_key], 1)]) tx = Transaction.create([alice.public_key], [([alice.public_key], 1)])
tx = tx.sign([alice.private_key]) tx = tx.sign([alice.private_key])
monkeypatch.setattr('time.time', lambda: 1000000000) b.store_bulk_transactions([tx])
block1 = b.create_block([tx])
b.write_block(block1)
monkeypatch.setattr('time.time', lambda: 1000000020)
transfer_tx = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 1)], transfer_tx = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 1)],
asset_id=tx.id) asset_id=tx.id)
transfer_tx = transfer_tx.sign([alice.private_key]) transfer_tx = transfer_tx.sign([alice.private_key])
block2 = b.create_block([transfer_tx])
b.write_block(block2)
monkeypatch.setattr('time.time', lambda: 1000000030)
transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 2)], transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 2)],
asset_id=tx.id) asset_id=tx.id)
transfer_tx2 = transfer_tx2.sign([alice.private_key]) transfer_tx2 = transfer_tx2.sign([alice.private_key])
block3 = b.create_block([transfer_tx2])
b.write_block(block3)
# Vote both block2 and block3 valid with pytest.raises(DoubleSpend):
vote = b.vote(block2.id, b.get_last_voted_block().id, True) b.validate_transaction(transfer_tx2, [transfer_tx])
b.write_vote(vote)
vote = b.vote(block3.id, b.get_last_voted_block().id, True) b.store_bulk_transactions([transfer_tx])
b.write_vote(vote)
with pytest.raises(DoubleSpend):
b.validate_transaction(transfer_tx2)
b.store_bulk_transactions([transfer_tx2])
with pytest.raises(CriticalDoubleSpend): with pytest.raises(CriticalDoubleSpend):
b.get_spent(tx.id, 0) b.get_spent(tx.id, 0)
@pytest.mark.genesis @pytest.mark.tendermint
def test_get_block_status_for_tx_with_double_inclusion(self, b, monkeypatch, alice): def test_get_block_status_for_tx_with_double_inclusion(self, b, alice):
from bigchaindb.exceptions import CriticalDoubleInclusion
from bigchaindb.models import Transaction from bigchaindb.models import Transaction
from bigchaindb.backend.exceptions import OperationError
tx = Transaction.create([alice.public_key], [([alice.public_key], 1)]) tx = Transaction.create([alice.public_key], [([alice.public_key], 1)])
tx = tx.sign([alice.private_key]) tx = tx.sign([alice.private_key])
monkeypatch.setattr('time.time', lambda: 1000000000) b.store_bulk_transactions([tx])
block1 = b.create_block([tx])
b.write_block(block1)
monkeypatch.setattr('time.time', lambda: 1000000020) with pytest.raises(OperationError):
block2 = b.create_block([tx]) b.store_bulk_transactions([tx])
b.write_block(block2)
# Vote both blocks valid (creating a double spend) @pytest.mark.tendermint
vote = b.vote(block1.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
vote = b.vote(block2.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
with pytest.raises(CriticalDoubleInclusion):
b.get_blocks_status_containing_tx(tx.id)
@pytest.mark.genesis
def test_get_transaction_in_invalid_and_valid_block(self, monkeypatch, b, alice):
from bigchaindb.models import Transaction
monkeypatch.setattr('time.time', lambda: 1000000000)
tx1 = Transaction.create([alice.public_key], [([alice.public_key], 1)],
metadata={'msg': 1})
tx1 = tx1.sign([alice.private_key])
block1 = b.create_block([tx1])
b.write_block(block1)
monkeypatch.setattr('time.time', lambda: 1000000020)
tx2 = Transaction.create([alice.public_key], [([alice.public_key], 1)],
metadata={'msg': 2})
tx2 = tx2.sign([alice.private_key])
block2 = b.create_block([tx2])
b.write_block(block2)
# vote the first block invalid
vote = b.vote(block1.id, b.get_last_voted_block().id, False)
b.write_vote(vote)
# vote the second block valid
vote = b.vote(block2.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
assert b.get_transaction(tx1.id) is None
assert b.get_transaction(tx2.id) == tx2
@pytest.mark.genesis
def test_text_search(self, b, alice): def test_text_search(self, b, alice):
from bigchaindb.models import Transaction from bigchaindb.models import Transaction
from bigchaindb.backend.exceptions import OperationError from bigchaindb.backend.exceptions import OperationError
@ -215,13 +93,8 @@ class TestBigchainApi(object):
tx3 = Transaction.create([alice.public_key], [([alice.public_key], 1)], tx3 = Transaction.create([alice.public_key], [([alice.public_key], 1)],
asset=asset3).sign([alice.private_key]) asset=asset3).sign([alice.private_key])
# create the block # write the transactions to the DB
block = b.create_block([tx1, tx2, tx3]) b.store_bulk_transactions([tx1, tx2, tx3])
b.write_block(block)
# vote valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
# get the assets through text search # get the assets through text search
# this query only works with MongoDB # this query only works with MongoDB
@ -232,182 +105,13 @@ class TestBigchainApi(object):
else: else:
assert len(assets) == 3 assert len(assets) == 3
@pytest.mark.genesis
def test_text_search_returns_valid_only(self, monkeypatch, b, alice):
from bigchaindb.models import Transaction
from bigchaindb.backend.exceptions import OperationError
from bigchaindb.backend.localmongodb.connection import LocalMongoDBConnection
asset_valid = {'msg': 'Hello BigchainDB!'}
asset_invalid = {'msg': 'Goodbye BigchainDB!'}
monkeypatch.setattr('time.time', lambda: 1000000000)
tx1 = Transaction.create([alice.public_key], [([alice.public_key], 1)],
asset=asset_valid)
tx1 = tx1.sign([alice.private_key])
block1 = b.create_block([tx1])
b.write_block(block1)
monkeypatch.setattr('time.time', lambda: 1000000020)
tx2 = Transaction.create([alice.public_key], [([alice.public_key], 1)],
asset=asset_invalid)
tx2 = tx2.sign([alice.private_key])
block2 = b.create_block([tx2])
b.write_block(block2)
# vote the first block valid
vote = b.vote(block1.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
# vote the second block invalid
vote = b.vote(block2.id, b.get_last_voted_block().id, False)
b.write_vote(vote)
# get assets with text search
try:
assets = list(b.text_search('bigchaindb'))
except OperationError:
assert not isinstance(b.connection, LocalMongoDBConnection)
return
# should only return one asset
assert len(assets) == 1
# should return the asset created by tx1
assert assets[0] == {
'data': {'msg': 'Hello BigchainDB!'},
'id': tx1.id
}
@pytest.mark.usefixtures('inputs')
def test_write_transaction(self, b, user_pk, user_sk):
from bigchaindb.models import Transaction
input_tx = b.get_owned_ids(user_pk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [([user_pk], 1)],
asset_id=input_tx.id)
tx = tx.sign([user_sk])
b.write_transaction(tx)
tx_from_db, status = b.get_transaction(tx.id, include_status=True)
assert tx_from_db.to_dict() == tx.to_dict()
@pytest.mark.usefixtures('inputs')
def test_read_transaction(self, b, user_pk, user_sk):
from bigchaindb.models import Transaction
input_tx = b.get_owned_ids(user_pk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [([user_pk], 1)],
asset_id=input_tx.id)
tx = tx.sign([user_sk])
b.write_transaction(tx)
# create block and write it to the bighcain before retrieving the transaction
block = b.create_block([tx])
b.write_block(block)
response, status = b.get_transaction(tx.id, include_status=True)
# add validity information, which will be returned
assert tx.to_dict() == response.to_dict()
@pytest.mark.usefixtures('inputs')
def test_read_transaction_invalid_block(self, b, user_pk, user_sk):
from bigchaindb.models import Transaction
input_tx = b.get_owned_ids(user_pk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [([user_pk], 1)],
asset_id=input_tx.id)
tx = tx.sign([user_sk])
# There's no need to b.write_transaction(tx) to the backlog
# create block
block = b.create_block([tx])
b.write_block(block)
# vote the block invalid
vote = b.vote(block.id, b.get_last_voted_block().id, False)
b.write_vote(vote)
response = b.get_transaction(tx.id)
# should be None, because invalid blocks are ignored
# and a copy of the tx is not in the backlog
assert response is None
@pytest.mark.usefixtures('inputs')
def test_genesis_block(self, b):
from bigchaindb.backend import query
block = query.get_genesis_block(b.connection)
assert len(block['block']['transactions']) == 1
assert block['block']['transactions'][0]['operation'] == 'GENESIS'
assert block['block']['transactions'][0]['inputs'][0]['fulfills'] is None
@pytest.mark.genesis
def test_create_genesis_block_fails_if_table_not_empty(self, b):
from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError
with pytest.raises(GenesisBlockAlreadyExistsError):
b.create_genesis_block()
@pytest.mark.skipif(reason='This test may not make sense after changing the chainification mode')
def test_get_last_block(self, b):
from bigchaindb.backend import query
# get the number of blocks
num_blocks = query.count_blocks(b.connection)
# get the last block
last_block = b.get_last_block()
assert last_block['block']['block_number'] == num_blocks - 1
@pytest.mark.skipif(reason='This test may not make sense after changing the chainification mode')
def test_get_last_block_id(self, b):
last_block = b.get_last_block()
last_block_id = b.get_last_block_id()
assert last_block_id == last_block['id']
@pytest.mark.skipif(reason='This test may not make sense after changing the chainification mode')
def test_get_previous_block(self, b):
last_block = b.get_last_block()
new_block = b.create_block([])
b.write_block(new_block)
prev_block = b.get_previous_block(new_block)
assert prev_block == last_block
@pytest.mark.skipif(reason='This test may not make sense after changing the chainification mode')
def test_get_previous_block_id(self, b):
last_block = b.get_last_block()
new_block = b.create_block([])
b.write_block(new_block)
prev_block_id = b.get_previous_block_id(new_block)
assert prev_block_id == last_block['id']
def test_create_empty_block(self, b):
from bigchaindb.common.exceptions import OperationError
with pytest.raises(OperationError) as excinfo:
b.create_block([])
assert excinfo.value.args[0] == 'Empty block creation is not allowed'
@pytest.mark.usefixtures('inputs') @pytest.mark.usefixtures('inputs')
@pytest.mark.tendermint
def test_non_create_input_not_found(self, b, user_pk): def test_non_create_input_not_found(self, b, user_pk):
from cryptoconditions import Ed25519Sha256 from cryptoconditions import Ed25519Sha256
from bigchaindb.common.exceptions import InputDoesNotExist from bigchaindb.common.exceptions import InputDoesNotExist
from bigchaindb.common.transaction import Input, TransactionLink from bigchaindb.common.transaction import Input, TransactionLink
from bigchaindb.models import Transaction from bigchaindb.models import Transaction
from bigchaindb import BigchainDB
# Create an input for a non existing transaction # Create an input for a non existing transaction
input = Input(Ed25519Sha256(public_key=b58decode(user_pk)), input = Input(Ed25519Sha256(public_key=b58decode(user_pk)),
@ -415,21 +119,28 @@ class TestBigchainApi(object):
TransactionLink('somethingsomething', 0)) TransactionLink('somethingsomething', 0))
tx = Transaction.transfer([input], [([user_pk], 1)], tx = Transaction.transfer([input], [([user_pk], 1)],
asset_id='mock_asset_link') asset_id='mock_asset_link')
with pytest.raises(InputDoesNotExist): with pytest.raises(InputDoesNotExist):
tx.validate(BigchainDB()) tx.validate(b)
def test_count_backlog(self, b, user_pk, alice): @pytest.mark.tendermint
from bigchaindb.backend import query def test_write_transaction(self, b, user_sk, user_pk, alice, create_tx):
from bigchaindb.models import Transaction from bigchaindb.models import Transaction
for i in range(4): asset1 = {'msg': 'BigchainDB 1'}
tx = Transaction.create([alice.public_key], [([user_pk], 1)],
metadata={'msg': i}) \
.sign([alice.private_key])
b.write_transaction(tx)
assert query.count_backlog(b.connection) == 4 tx = Transaction.create([alice.public_key], [([alice.public_key], 1)],
asset=asset1).sign([alice.private_key])
b.store_bulk_transactions([tx])
tx_from_db = b.get_transaction(tx.id)
before = tx.to_dict()
after = tx_from_db.to_dict()
assert before['asset']['data'] == after['asset']['data']
before.pop('asset', None)
after.pop('asset', None)
assert before == after
class TestTransactionValidation(object): class TestTransactionValidation(object):