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_PORT = getenv('TENDERMINT_PORT', '46657')
|
||||
ENDPOINT = 'http://{}:{}/'.format(TENDERMINT_HOST, TENDERMINT_PORT)
|
||||
MODE_LIST = ('broadcast_tx_async',
|
||||
'broadcast_tx_sync',
|
||||
'broadcast_tx_commit')
|
||||
|
||||
|
||||
class BigchainDB(Bigchain):
|
||||
|
||||
def post_transaction(self, transaction):
|
||||
def post_transaction(self, transaction, mode):
|
||||
"""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 = {
|
||||
'method': 'broadcast_tx_async',
|
||||
'method': mode,
|
||||
'jsonrpc': '2.0',
|
||||
'params': [encode_transaction(transaction.to_dict())],
|
||||
'id': str(uuid4())
|
||||
@ -35,11 +41,10 @@ class BigchainDB(Bigchain):
|
||||
# TODO: handle connection errors!
|
||||
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.
|
||||
"""Submit a valid transaction to the mempool."""
|
||||
|
||||
self.post_transaction(transaction)
|
||||
self.post_transaction(transaction, mode)
|
||||
|
||||
def store_transaction(self, transaction):
|
||||
"""Store a valid transaction to the transactions collection."""
|
||||
|
@ -29,4 +29,14 @@ def valid_operation(op):
|
||||
return 'CREATE'
|
||||
if op == '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:
|
||||
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']
|
||||
|
||||
# `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)
|
||||
)
|
||||
else:
|
||||
bigchain.write_transaction(tx_obj)
|
||||
bigchain.write_transaction(tx_obj, mode)
|
||||
|
||||
response = jsonify(tx)
|
||||
response.status_code = 202
|
||||
|
@ -58,7 +58,7 @@ Content-Type: application/json
|
||||
"""
|
||||
|
||||
TPLS['post-tx-request'] = """\
|
||||
POST /api/v1/transactions/ HTTP/1.1
|
||||
POST /api/v1/transactions?mode=async HTTP/1.1
|
||||
Host: example.com
|
||||
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.
|
||||
|
||||
|
||||
.. 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::
|
||||
|
||||
This option is only available when using BigchainDB with Tendermint.
|
||||
|
||||
.. note::
|
||||
|
||||
The posted `transaction
|
||||
<https://docs.bigchaindb.com/projects/server/en/latest/data-models/transaction-model.html>`_
|
||||
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>`_
|
||||
to build a valid transaction.
|
||||
|
||||
:query string mode: (Optional) One of the three supported modes to send a transaction: ``async``, ``sync``, ``commit``.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. literalinclude:: http-samples/post-tx-request.http
|
||||
@ -162,6 +173,11 @@ Transactions
|
||||
: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
|
||||
-------------------
|
||||
|
||||
|
@ -76,10 +76,45 @@ def test_write_and_post_transaction(mock_post, b):
|
||||
.sign([alice.private_key]).to_dict()
|
||||
|
||||
tx = b.validate_transaction(tx)
|
||||
b.write_transaction(tx)
|
||||
b.write_transaction(tx, 'broadcast_tx_async')
|
||||
|
||||
assert mock_post.called
|
||||
args, kwargs = mock_post.call_args
|
||||
assert 'broadcast_tx_async' == kwargs['json']['method']
|
||||
encoded_tx = [encode_transaction(tx.to_dict())]
|
||||
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)):
|
||||
url = '{}{}'.format(TX_ENDPOINT, '123')
|
||||
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