mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge pull request #1996 from bigchaindb/1995-mode-API
add mode parameter to transaction endpoint, fixes #1995
This commit is contained in:
commit
c156d0bfe8
@ -19,15 +19,21 @@ logger = logging.getLogger(__name__)
|
|||||||
TENDERMINT_HOST = getenv('TENDERMINT_HOST', 'localhost')
|
TENDERMINT_HOST = getenv('TENDERMINT_HOST', 'localhost')
|
||||||
TENDERMINT_PORT = getenv('TENDERMINT_PORT', '46657')
|
TENDERMINT_PORT = getenv('TENDERMINT_PORT', '46657')
|
||||||
ENDPOINT = 'http://{}:{}/'.format(TENDERMINT_HOST, TENDERMINT_PORT)
|
ENDPOINT = 'http://{}:{}/'.format(TENDERMINT_HOST, TENDERMINT_PORT)
|
||||||
|
MODE_LIST = ('broadcast_tx_async',
|
||||||
|
'broadcast_tx_sync',
|
||||||
|
'broadcast_tx_commit')
|
||||||
|
|
||||||
|
|
||||||
class BigchainDB(Bigchain):
|
class BigchainDB(Bigchain):
|
||||||
|
|
||||||
def post_transaction(self, transaction):
|
def post_transaction(self, transaction, mode):
|
||||||
"""Submit a valid transaction to the mempool."""
|
"""Submit a valid transaction to the mempool."""
|
||||||
|
if not mode or mode not in MODE_LIST:
|
||||||
|
raise ValidationError(('Mode must be one of the following {}.')
|
||||||
|
.format(', '.join(MODE_LIST)))
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'method': 'broadcast_tx_async',
|
'method': mode,
|
||||||
'jsonrpc': '2.0',
|
'jsonrpc': '2.0',
|
||||||
'params': [encode_transaction(transaction.to_dict())],
|
'params': [encode_transaction(transaction.to_dict())],
|
||||||
'id': str(uuid4())
|
'id': str(uuid4())
|
||||||
@ -35,11 +41,10 @@ class BigchainDB(Bigchain):
|
|||||||
# TODO: handle connection errors!
|
# TODO: handle connection errors!
|
||||||
requests.post(ENDPOINT, json=payload)
|
requests.post(ENDPOINT, json=payload)
|
||||||
|
|
||||||
def write_transaction(self, transaction):
|
def write_transaction(self, transaction, mode):
|
||||||
# This method offers backward compatibility with the Web API.
|
# This method offers backward compatibility with the Web API.
|
||||||
"""Submit a valid transaction to the mempool."""
|
"""Submit a valid transaction to the mempool."""
|
||||||
|
self.post_transaction(transaction, mode)
|
||||||
self.post_transaction(transaction)
|
|
||||||
|
|
||||||
def store_transaction(self, transaction):
|
def store_transaction(self, transaction):
|
||||||
"""Store a valid transaction to the transactions collection."""
|
"""Store a valid transaction to the transactions collection."""
|
||||||
|
@ -29,4 +29,14 @@ def valid_operation(op):
|
|||||||
return 'CREATE'
|
return 'CREATE'
|
||||||
if op == 'TRANSFER':
|
if op == 'TRANSFER':
|
||||||
return 'TRANSFER'
|
return 'TRANSFER'
|
||||||
raise ValueError('Operation must be "CREATE" or "TRANSFER')
|
raise ValueError('Operation must be "CREATE" or "TRANSFER"')
|
||||||
|
|
||||||
|
|
||||||
|
def valid_mode(mode):
|
||||||
|
if mode == 'async':
|
||||||
|
return 'broadcast_tx_async'
|
||||||
|
if mode == 'sync':
|
||||||
|
return 'broadcast_tx_sync'
|
||||||
|
if mode == 'commit':
|
||||||
|
return 'broadcast_tx_commit'
|
||||||
|
raise ValueError('Mode must be "async", "sync" or "commit"')
|
||||||
|
@ -55,6 +55,12 @@ class TransactionListApi(Resource):
|
|||||||
Return:
|
Return:
|
||||||
A ``dict`` containing the data about the transaction.
|
A ``dict`` containing the data about the transaction.
|
||||||
"""
|
"""
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument('mode', type=parameters.valid_mode,
|
||||||
|
default='broadcast_tx_async')
|
||||||
|
args = parser.parse_args()
|
||||||
|
mode = str(args['mode'])
|
||||||
|
|
||||||
pool = current_app.config['bigchain_pool']
|
pool = current_app.config['bigchain_pool']
|
||||||
|
|
||||||
# `force` will try to format the body of the POST request even if the
|
# `force` will try to format the body of the POST request even if the
|
||||||
@ -85,7 +91,7 @@ class TransactionListApi(Resource):
|
|||||||
'Invalid transaction ({}): {}'.format(type(e).__name__, e)
|
'Invalid transaction ({}): {}'.format(type(e).__name__, e)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
bigchain.write_transaction(tx_obj)
|
bigchain.write_transaction(tx_obj, mode)
|
||||||
|
|
||||||
response = jsonify(tx)
|
response = jsonify(tx)
|
||||||
response.status_code = 202
|
response.status_code = 202
|
||||||
|
@ -58,7 +58,7 @@ Content-Type: application/json
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
TPLS['post-tx-request'] = """\
|
TPLS['post-tx-request'] = """\
|
||||||
POST /api/v1/transactions/ HTTP/1.1
|
POST /api/v1/transactions?mode=async HTTP/1.1
|
||||||
Host: example.com
|
Host: example.com
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
|
@ -124,11 +124,20 @@ Transactions
|
|||||||
:statuscode 400: The request wasn't understood by the server, e.g. the ``asset_id`` querystring was not included in the request.
|
:statuscode 400: The request wasn't understood by the server, e.g. the ``asset_id`` querystring was not included in the request.
|
||||||
|
|
||||||
|
|
||||||
.. http:post:: /api/v1/transactions
|
.. http:post:: /api/v1/transactions?mode={mode}
|
||||||
|
|
||||||
Push a new transaction.
|
Tendermint offers a `broadcast API
|
||||||
|
<http://tendermint.readthedocs.io/projects/tools/en/master/using-tendermint.html#broadcast-api>`_ with three different modes to post transactions.
|
||||||
|
By setting the mode, a new transaction can be pushed with a different mode than the default. The default mode is ``async``, which
|
||||||
|
will return immediately and not wait to see if the transaction is valid. The ``sync`` mode will return after the transaction is validated, while ``commit``
|
||||||
|
returns after the transaction is committed to a block.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
This option is only available when using BigchainDB with Tendermint.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
The posted `transaction
|
The posted `transaction
|
||||||
<https://docs.bigchaindb.com/projects/server/en/latest/data-models/transaction-model.html>`_
|
<https://docs.bigchaindb.com/projects/server/en/latest/data-models/transaction-model.html>`_
|
||||||
should be structurally valid and not spending an already spent output.
|
should be structurally valid and not spending an already spent output.
|
||||||
@ -137,6 +146,8 @@ Transactions
|
|||||||
<https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_
|
<https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_
|
||||||
to build a valid transaction.
|
to build a valid transaction.
|
||||||
|
|
||||||
|
:query string mode: (Optional) One of the three supported modes to send a transaction: ``async``, ``sync``, ``commit``.
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. literalinclude:: http-samples/post-tx-request.http
|
.. literalinclude:: http-samples/post-tx-request.http
|
||||||
@ -162,6 +173,11 @@ Transactions
|
|||||||
:statuscode 400: The transaction was malformed and not accepted in the ``BACKLOG``.
|
:statuscode 400: The transaction was malformed and not accepted in the ``BACKLOG``.
|
||||||
|
|
||||||
|
|
||||||
|
.. http:post:: /api/v1/transactions
|
||||||
|
|
||||||
|
This endpoint (without any parameters) will push a new transaction. If BigchainDB is used with Tendermint, the default mode ``async`` is used.
|
||||||
|
|
||||||
|
|
||||||
Transaction Outputs
|
Transaction Outputs
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -76,10 +76,45 @@ def test_write_and_post_transaction(mock_post, b):
|
|||||||
.sign([alice.private_key]).to_dict()
|
.sign([alice.private_key]).to_dict()
|
||||||
|
|
||||||
tx = b.validate_transaction(tx)
|
tx = b.validate_transaction(tx)
|
||||||
b.write_transaction(tx)
|
b.write_transaction(tx, 'broadcast_tx_async')
|
||||||
|
|
||||||
assert mock_post.called
|
assert mock_post.called
|
||||||
args, kwargs = mock_post.call_args
|
args, kwargs = mock_post.call_args
|
||||||
assert 'broadcast_tx_async' == kwargs['json']['method']
|
assert 'broadcast_tx_async' == kwargs['json']['method']
|
||||||
encoded_tx = [encode_transaction(tx.to_dict())]
|
encoded_tx = [encode_transaction(tx.to_dict())]
|
||||||
assert encoded_tx == kwargs['json']['params']
|
assert encoded_tx == kwargs['json']['params']
|
||||||
|
|
||||||
|
|
||||||
|
@patch('requests.post')
|
||||||
|
@pytest.mark.parametrize('mode', [
|
||||||
|
'broadcast_tx_async',
|
||||||
|
'broadcast_tx_sync',
|
||||||
|
'broadcast_tx_commit'
|
||||||
|
])
|
||||||
|
def test_post_transaction_valid_modes(mock_post, b, mode):
|
||||||
|
from bigchaindb.models import Transaction
|
||||||
|
from bigchaindb.common.crypto import generate_key_pair
|
||||||
|
alice = generate_key_pair()
|
||||||
|
tx = Transaction.create([alice.public_key],
|
||||||
|
[([alice.public_key], 1)],
|
||||||
|
asset=None) \
|
||||||
|
.sign([alice.private_key]).to_dict()
|
||||||
|
tx = b.validate_transaction(tx)
|
||||||
|
b.write_transaction(tx, mode)
|
||||||
|
|
||||||
|
args, kwargs = mock_post.call_args
|
||||||
|
assert mode == kwargs['json']['method']
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_transaction_invalid_mode(b):
|
||||||
|
from bigchaindb.models import Transaction
|
||||||
|
from bigchaindb.common.crypto import generate_key_pair
|
||||||
|
from bigchaindb.common.exceptions import ValidationError
|
||||||
|
alice = generate_key_pair()
|
||||||
|
tx = Transaction.create([alice.public_key],
|
||||||
|
[([alice.public_key], 1)],
|
||||||
|
asset=None) \
|
||||||
|
.sign([alice.private_key]).to_dict()
|
||||||
|
tx = b.validate_transaction(tx)
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
b.write_transaction(tx, 'nope')
|
||||||
|
@ -410,3 +410,41 @@ def test_return_only_valid_transaction(client):
|
|||||||
get_transaction_patched(Bigchain.TX_IN_BACKLOG)):
|
get_transaction_patched(Bigchain.TX_IN_BACKLOG)):
|
||||||
url = '{}{}'.format(TX_ENDPOINT, '123')
|
url = '{}{}'.format(TX_ENDPOINT, '123')
|
||||||
assert client.get(url).status_code == 404
|
assert client.get(url).status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.tendermint
|
||||||
|
@patch('requests.post')
|
||||||
|
@pytest.mark.parametrize('mode', [
|
||||||
|
('', 'broadcast_tx_async'),
|
||||||
|
('?mode=async', 'broadcast_tx_async'),
|
||||||
|
('?mode=sync', 'broadcast_tx_sync'),
|
||||||
|
('?mode=commit', 'broadcast_tx_commit'),
|
||||||
|
])
|
||||||
|
def test_post_transaction_valid_modes(mock_post, client, mode):
|
||||||
|
from bigchaindb.models import Transaction
|
||||||
|
from bigchaindb.common.crypto import generate_key_pair
|
||||||
|
alice = generate_key_pair()
|
||||||
|
tx = Transaction.create([alice.public_key],
|
||||||
|
[([alice.public_key], 1)],
|
||||||
|
asset=None) \
|
||||||
|
.sign([alice.private_key])
|
||||||
|
mode_endpoint = TX_ENDPOINT + mode[0]
|
||||||
|
client.post(mode_endpoint, data=json.dumps(tx.to_dict()))
|
||||||
|
args, kwargs = mock_post.call_args
|
||||||
|
assert mode[1] == kwargs['json']['method']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.tendermint
|
||||||
|
def test_post_transaction_invalid_mode(client):
|
||||||
|
from bigchaindb.models import Transaction
|
||||||
|
from bigchaindb.common.crypto import generate_key_pair
|
||||||
|
alice = generate_key_pair()
|
||||||
|
tx = Transaction.create([alice.public_key],
|
||||||
|
[([alice.public_key], 1)],
|
||||||
|
asset=None) \
|
||||||
|
.sign([alice.private_key])
|
||||||
|
mode_endpoint = TX_ENDPOINT + '?mode=nope'
|
||||||
|
response = client.post(mode_endpoint, data=json.dumps(tx.to_dict()))
|
||||||
|
assert '400 BAD REQUEST' in response.status
|
||||||
|
assert 'Mode must be "async", "sync" or "commit"' ==\
|
||||||
|
json.loads(response.data.decode('utf8'))['message']['mode']
|
||||||
|
Loading…
x
Reference in New Issue
Block a user