Merge a44a5703d11bc04db4fb8d4021b4819692014003 into 955fd86a7f4323f61ae7c30ada53dccdbed575aa

This commit is contained in:
Matt Smith 2016-03-22 15:29:25 +00:00
commit ce3374caa5
5 changed files with 130 additions and 4 deletions

View File

@ -90,6 +90,24 @@ class Client:
tx, private_key=self.private_key)
return self._push(signed_tx)
def validate(self, tx):
"""Validate a transaction object.
If tx is a `CREATE` transaction, this method will return (True, '') even
without the federation signature as long as the transaction is otherwise
valid.
Args:
tx (dict): the transaction object to be validated
Return:
(bool, str): (True, '') if the tx is valid, else (False, errormsg)
"""
res = requests.post(self.api_endpoint + '/transactions/validate/',
json=tx)
return (res.json()['valid'], res.json()['error'])
def _push(self, tx):
"""Submit a transaction to the Federation.

View File

@ -199,7 +199,9 @@ def verify_signature(signed_transaction):
if 'assignee' in data:
data.pop('assignee')
signature = data.pop('signature')
signature = data.pop('signature', None)
if not signature: return False
public_key_base58 = signed_transaction['transaction']['current_owner']
public_key = PublicKey(public_key_base58)
return public_key.verify(serialize(data), signature)

View File

@ -7,7 +7,7 @@ For more information please refer to the documentation in Apiary:
import flask
from flask import current_app, request, Blueprint
from bigchaindb import util
from bigchaindb import util, exceptions
basic_views = Blueprint('basic_views', __name__)
@ -37,6 +37,7 @@ def get_transaction(tx_id):
bigchain = current_app.config['bigchain']
tx = bigchain.get_transaction(tx_id)
if not tx: flask.abort(404)
return flask.jsonify(**tx)
@ -47,12 +48,13 @@ def create_transaction():
Return:
A JSON string containing the data about the transaction.
"""
bigchain = current_app.config['bigchain']
val = {}
# `force` will try to format the body of the POST request even if the `content-type` header is not
# set to `application/json`
# `force` will try to format the body of the POST request even if the
# `content-type` header is not set to `application/json`
tx = request.get_json(force=True)
if tx['transaction']['operation'] == 'CREATE':
@ -67,3 +69,38 @@ def create_transaction():
return flask.jsonify(**tx)
@basic_views.route('/transactions/validate/', methods=['POST'])
def validate_transaction():
"""API endpoint to validate transactions without pushing them to the
Federation. No federation node signature is required for `CREATE`
transactions.
Return:
A JSON object with the `valid` field populated with a boolean value
and the `error` field populated with an error message or an empty str
"""
bigchain = current_app.config['bigchain']
tx = request.get_json(force=True)
# Always validate TRANSFER signatures; but only validate CREATE signatures
# if present.
validate_sig = True
# If a CREATE doesn't have the signature populated, then we treat it as
# an input to the `create` function and transform it.
if tx['transaction']['operation'] == 'CREATE' and 'signature' not in tx:
validate_sig = False
tx = util.transform_create(tx)
try:
bigchain.validate_transaction(tx)
except exceptions.InvalidSignature as e:
# We skipped signing CREATEs with the node's private key, so expect this
if validate_sig:
return flask.jsonify(valid=False, error=repr(e))
except Exception as e:
return flask.jsonify(valid=False, error=repr(e))
return flask.jsonify(valid=True, error='')

View File

@ -57,3 +57,11 @@ def test_client_can_transfer_assets(mock_requests_post, client):
assert util.verify_signature(tx)
def test_client_can_validate_transaction(mock_requests_post, client):
from bigchaindb import util
assert client.validate({'valid': True,
'error': ''}) == (True, '')
assert client.validate({'valid': False,
'error': 'Some Error'}) == (False, 'Some Error')

View File

@ -6,8 +6,38 @@ from bigchaindb import util
TX_ENDPOINT = '/api/v1/transactions/'
VALIDATE_ENDPOINT = '/api/v1/transactions/validate/'
@pytest.fixture
def valid_create_transaction(user_public_key):
return util.create_tx(
current_owner=None,
new_owner=user_public_key,
tx_input=None,
operation='CREATE',
payload={
'IPFS_key': 'QmfQ5QAjvg4GtA3wg3adpnDJug8ktA1BxurVqBD8rtgVjP',
'creator': 'Johnathan Plunkett',
'title': 'The Winds of Plast'})
@pytest.fixture
def valid_transfer_transaction(user_public_key, user_private_key):
# Requires an tx_input param to create a *valid* transfer tx
def make_tx(tx_input):
return util.create_and_sign_tx(
private_key=user_private_key,
current_owner=user_public_key,
new_owner=user_public_key,
tx_input=tx_input, #Fill_me_in
operation='TRANSFER',
payload={
'IPFS_key': 'QmfQ5QAjvg4GtA3wg3adpnDJug8ktA1BxurVqBD8rtgVjP',
'creator': 'Johnathan Plunkett',
'title': 'The Winds of Plast 2: The Plastening'})
return make_tx
@pytest.mark.usefixtures('inputs')
def test_get_transaction_endpoint(b, client, user_public_key):
input_tx = b.get_owned_ids(user_public_key).pop()
@ -40,3 +70,34 @@ def test_post_transfer_transaction_endpoint(b, client):
assert res.json['transaction']['current_owner'] == from_keypair[1]
assert res.json['transaction']['new_owner'] == to_keypair[1]
@pytest.mark.usefixtures('inputs')
def test_post_validate_transaction_endpoint(b, client, user_public_key,
valid_create_transaction,
valid_transfer_transaction):
# Validate valid CREATE tx
res = client.post(VALIDATE_ENDPOINT,
data=json.dumps(valid_create_transaction))
assert res.json['valid'] == True
assert res.json['error'] == ''
# Validate invalid CREATE tx
valid_create_transaction.update({'signature': 'junk'})
res = client.post(VALIDATE_ENDPOINT,
data=json.dumps(valid_create_transaction))
assert res.json['valid'] == False
assert res.json['error'] == \
"OperationError('Only federation nodes can use the operation `CREATE`',)"
# Validate valid TRANSFER tx
res = client.post(VALIDATE_ENDPOINT, data=json.dumps(
valid_transfer_transaction(b.get_owned_ids(user_public_key).pop())))
assert res.json['valid'] == True
assert res.json['error'] == ''
# Validate invalid TRANSFER tx
res = client.post(VALIDATE_ENDPOINT, data=json.dumps(
valid_transfer_transaction(None)))
assert res.json['valid'] == False
assert res.json['error'] == \
"ValueError('Only `CREATE` transactions can have null inputs',)"