mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Rename unspent -> spent in outputs endpoint
- If spent=None return all outputs - If spent=True return only spent outputs - If spent=False return only unspent outputs - Updated documentation - Add the ability to return only spent outputs in core - Added and update tests
This commit is contained in:
parent
8d60796765
commit
b523ba3fe5
@ -402,20 +402,33 @@ class Bigchain(object):
|
||||
:obj:`list` of TransactionLink: list of ``txid`` s and ``output`` s
|
||||
pointing to another transaction's condition
|
||||
"""
|
||||
return self.get_outputs_filtered(owner, include_spent=False)
|
||||
return self.get_outputs_filtered(owner, spent=False)
|
||||
|
||||
@property
|
||||
def fastquery(self):
|
||||
return fastquery.FastQuery(self.connection, self.me)
|
||||
|
||||
def get_outputs_filtered(self, owner, include_spent=True):
|
||||
def get_outputs_filtered(self, owner, spent=None):
|
||||
"""
|
||||
Get a list of output links filtered on some criteria
|
||||
|
||||
Args:
|
||||
owner (str): base58 encoded public_key.
|
||||
spent (bool): If ``True`` return only the spent outputs. If
|
||||
``False`` return only unspent outputs. If spent is
|
||||
not specified (``None``) return all outputs.
|
||||
|
||||
Returns:
|
||||
:obj:`list` of TransactionLink: list of ``txid`` s and ``output`` s
|
||||
pointing to another transaction's condition
|
||||
"""
|
||||
outputs = self.fastquery.get_outputs_by_public_key(owner)
|
||||
if not include_spent:
|
||||
outputs = self.fastquery.filter_spent_outputs(outputs)
|
||||
return outputs
|
||||
if spent is None:
|
||||
return outputs
|
||||
elif spent is True:
|
||||
return self.fastquery.filter_unspent_outputs(outputs)
|
||||
elif spent is False:
|
||||
return self.fastquery.filter_spent_outputs(outputs)
|
||||
|
||||
def get_transactions_filtered(self, asset_id, operation=None):
|
||||
"""
|
||||
|
@ -68,3 +68,18 @@ class FastQuery:
|
||||
for tx in txs
|
||||
for input_ in tx['inputs']}
|
||||
return [ff for ff in outputs if ff not in spends]
|
||||
|
||||
def filter_unspent_outputs(self, outputs):
|
||||
"""
|
||||
Remove outputs that have not been spent
|
||||
|
||||
Args:
|
||||
outputs: list of TransactionLink
|
||||
"""
|
||||
links = [o.to_dict() for o in outputs]
|
||||
res = query.get_spending_transactions(self.connection, links)
|
||||
txs = [tx for _, tx in self.filter_valid_items(res)]
|
||||
spends = {TransactionLink.from_dict(input_['fulfills'])
|
||||
for tx in txs
|
||||
for input_ in tx['inputs']}
|
||||
return [ff for ff in outputs if ff in spends]
|
||||
|
@ -15,14 +15,12 @@ class OutputListApi(Resource):
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('public_key', type=parameters.valid_ed25519,
|
||||
required=True)
|
||||
parser.add_argument('unspent', type=parameters.valid_bool)
|
||||
args = parser.parse_args()
|
||||
parser.add_argument('spent', type=parameters.valid_bool)
|
||||
args = parser.parse_args(strict=True)
|
||||
|
||||
pool = current_app.config['bigchain_pool']
|
||||
include_spent = not args['unspent']
|
||||
|
||||
with pool() as bigchain:
|
||||
outputs = bigchain.get_outputs_filtered(args['public_key'],
|
||||
include_spent)
|
||||
args['spent'])
|
||||
return [{'transaction_id': output.txid, 'output': output.output}
|
||||
for output in outputs]
|
||||
|
@ -166,21 +166,29 @@ Transaction Outputs
|
||||
-------------------
|
||||
|
||||
The ``/api/v1/outputs`` endpoint returns transactions outputs filtered by a
|
||||
given public key, and optionally filtered to only include outputs that have
|
||||
not already been spent.
|
||||
given public key, and optionally filtered to only include either spent or
|
||||
unspent outputs.
|
||||
|
||||
|
||||
.. http:get:: /api/v1/outputs?public_key={public_key}
|
||||
.. http:get:: /api/v1/outputs
|
||||
|
||||
Get transaction outputs by public key. The `public_key` parameter must be
|
||||
Get transaction outputs by public key. The ``public_key`` parameter must be
|
||||
a base58 encoded ed25519 public key associated with transaction output
|
||||
ownership.
|
||||
|
||||
Returns a list of transaction outputs.
|
||||
|
||||
:param public_key: Base58 encoded public key associated with output ownership. This parameter is mandatory and without it the endpoint will return a ``400`` response code.
|
||||
:param unspent: Boolean value ("true" or "false") indicating if the result set should be limited to outputs that are available to spend. Defaults to "false".
|
||||
:param public_key: Base58 encoded public key associated with output
|
||||
ownership. This parameter is mandatory and without it
|
||||
the endpoint will return a ``400`` response code.
|
||||
:param spent: Boolean value ("true" or "false") indicating if the result set
|
||||
should include only spent or only unspent outputs. If not
|
||||
specified the result includes all the outputs (both spent
|
||||
and unspent).
|
||||
|
||||
.. http:get:: /api/v1/outputs?public_key={public_key}
|
||||
|
||||
Return all outputs, both spent and unspent, for the ``public_key``.
|
||||
|
||||
**Example request**:
|
||||
|
||||
@ -210,6 +218,62 @@ not already been spent.
|
||||
:statuscode 200: A list of outputs were found and returned in the body of the response.
|
||||
:statuscode 400: The request wasn't understood by the server, e.g. the ``public_key`` querystring was not included in the request.
|
||||
|
||||
.. http:get:: /api/v1/outputs?public_key={public_key}&spent=true
|
||||
|
||||
Return all **spent** outputs for ``public_key``.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/outputs?public_key=1AAAbbb...ccc&spent=true HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"output": 0,
|
||||
"transaction_id": "2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e"
|
||||
}
|
||||
]
|
||||
|
||||
:statuscode 200: A list of outputs were found and returned in the body of the response.
|
||||
:statuscode 400: The request wasn't understood by the server, e.g. the ``public_key`` querystring was not included in the request.
|
||||
|
||||
.. http:get:: /api/v1/outputs?public_key={public_key}&spent=false
|
||||
|
||||
Return all **unspent** outputs for ``public_key``.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/outputs?public_key=1AAAbbb...ccc&spent=false HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"output": 1,
|
||||
"transaction_id": "2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e"
|
||||
}
|
||||
]
|
||||
|
||||
:statuscode 200: A list of outputs were found and returned in the body of the response.
|
||||
:statuscode 400: The request wasn't understood by the server, e.g. the ``public_key`` querystring was not included in the request.
|
||||
|
||||
|
||||
Statuses
|
||||
--------------------------------
|
||||
|
@ -1194,7 +1194,7 @@ def test_get_owned_ids_calls_get_outputs_filtered():
|
||||
with patch('bigchaindb.core.Bigchain.get_outputs_filtered') as gof:
|
||||
b = Bigchain()
|
||||
res = b.get_owned_ids('abc')
|
||||
gof.assert_called_once_with('abc', include_spent=False)
|
||||
gof.assert_called_once_with('abc', spent=False)
|
||||
assert res == gof()
|
||||
|
||||
|
||||
@ -1206,21 +1206,36 @@ def test_get_outputs_filtered_only_unspent():
|
||||
TransactionLink('b', 2)]
|
||||
with patch('bigchaindb.fastquery.FastQuery.filter_spent_outputs') as filter_spent:
|
||||
filter_spent.return_value = [TransactionLink('b', 2)]
|
||||
out = Bigchain().get_outputs_filtered('abc', include_spent=False)
|
||||
out = Bigchain().get_outputs_filtered('abc', spent=False)
|
||||
get_outputs.assert_called_once_with('abc')
|
||||
assert out == [TransactionLink('b', 2)]
|
||||
|
||||
|
||||
def test_get_outputs_filtered():
|
||||
def test_get_outputs_filtered_only_spent():
|
||||
from bigchaindb.common.transaction import TransactionLink
|
||||
from bigchaindb.core import Bigchain
|
||||
with patch('bigchaindb.fastquery.FastQuery.get_outputs_by_public_key') as get_outputs:
|
||||
get_outputs.return_value = [TransactionLink('a', 1),
|
||||
TransactionLink('b', 2)]
|
||||
with patch('bigchaindb.fastquery.FastQuery.filter_spent_outputs') as filter_spent:
|
||||
out = Bigchain().get_outputs_filtered('abc')
|
||||
with patch('bigchaindb.fastquery.FastQuery.filter_unspent_outputs') as filter_spent:
|
||||
filter_spent.return_value = [TransactionLink('b', 2)]
|
||||
out = Bigchain().get_outputs_filtered('abc', spent=True)
|
||||
get_outputs.assert_called_once_with('abc')
|
||||
assert out == [TransactionLink('b', 2)]
|
||||
|
||||
|
||||
@patch('bigchaindb.fastquery.FastQuery.filter_unspent_outputs')
|
||||
@patch('bigchaindb.fastquery.FastQuery.filter_spent_outputs')
|
||||
def test_get_outputs_filtered(filter_spent, filter_unspent):
|
||||
from bigchaindb.common.transaction import TransactionLink
|
||||
from bigchaindb.core import Bigchain
|
||||
with patch('bigchaindb.fastquery.FastQuery.get_outputs_by_public_key') as get_outputs:
|
||||
get_outputs.return_value = [TransactionLink('a', 1),
|
||||
TransactionLink('b', 2)]
|
||||
out = Bigchain().get_outputs_filtered('abc')
|
||||
get_outputs.assert_called_once_with('abc')
|
||||
filter_spent.assert_not_called()
|
||||
filter_unspent.assert_not_called()
|
||||
assert out == get_outputs.return_value
|
||||
|
||||
|
||||
|
@ -127,4 +127,4 @@ def test_get_spent_issue_1271(b, alice, bob, carol):
|
||||
assert b.get_spent(tx_2.id, 0) == tx_5
|
||||
assert not b.get_spent(tx_5.id, 0)
|
||||
assert b.get_outputs_filtered(alice.public_key)
|
||||
assert b.get_outputs_filtered(alice.public_key, include_spent=False)
|
||||
assert b.get_outputs_filtered(alice.public_key, spent=False)
|
||||
|
@ -84,3 +84,39 @@ def test_filter_spent_outputs(b, user_pk):
|
||||
tx2.to_inputs()[0].fulfills,
|
||||
tx4.to_inputs()[0].fulfills
|
||||
}
|
||||
|
||||
|
||||
def test_filter_unspent_outputs(b, user_pk):
|
||||
out = [([user_pk], 1)]
|
||||
tx1 = Transaction.create([user_pk], out * 3)
|
||||
|
||||
# There are 3 inputs
|
||||
inputs = tx1.to_inputs()
|
||||
|
||||
# Each spent individually
|
||||
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
|
||||
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
|
||||
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
|
||||
|
||||
# The CREATE and first TRANSFER are valid. tx2 produces a new unspent.
|
||||
for tx in [tx1, tx2]:
|
||||
block = Block([tx])
|
||||
b.write_block(block)
|
||||
b.write_vote(b.vote(block.id, '', True))
|
||||
|
||||
# The second TRANSFER is invalid. inputs[1] remains unspent.
|
||||
block = Block([tx3])
|
||||
b.write_block(block)
|
||||
b.write_vote(b.vote(block.id, '', False))
|
||||
|
||||
# The third TRANSFER is undecided. It procuces a new unspent.
|
||||
block = Block([tx4])
|
||||
b.write_block(block)
|
||||
|
||||
outputs = b.fastquery.get_outputs_by_public_key(user_pk)
|
||||
spents = b.fastquery.filter_unspent_outputs(outputs)
|
||||
|
||||
assert set(spents) == {
|
||||
inputs[0].fulfills,
|
||||
inputs[2].fulfills
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ def test_get_outputs_endpoint(client, user_pk):
|
||||
{'transaction_id': 'a', 'output': 0}
|
||||
]
|
||||
assert res.status_code == 200
|
||||
gof.assert_called_once_with(user_pk, True)
|
||||
gof.assert_called_once_with(user_pk, None)
|
||||
|
||||
|
||||
def test_get_outputs_endpoint_unspent(client, user_pk):
|
||||
@ -27,13 +27,26 @@ def test_get_outputs_endpoint_unspent(client, user_pk):
|
||||
m.output = 0
|
||||
with patch('bigchaindb.core.Bigchain.get_outputs_filtered') as gof:
|
||||
gof.return_value = [m]
|
||||
params = '?unspent=true&public_key={}'.format(user_pk)
|
||||
params = '?spent=False&public_key={}'.format(user_pk)
|
||||
res = client.get(OUTPUTS_ENDPOINT + params)
|
||||
assert res.json == [{'transaction_id': 'a', 'output': 0}]
|
||||
assert res.status_code == 200
|
||||
gof.assert_called_once_with(user_pk, False)
|
||||
|
||||
|
||||
def test_get_outputs_endpoint_spent(client, user_pk):
|
||||
m = MagicMock()
|
||||
m.txid = 'a'
|
||||
m.output = 0
|
||||
with patch('bigchaindb.core.Bigchain.get_outputs_filtered') as gof:
|
||||
gof.return_value = [m]
|
||||
params = '?spent=true&public_key={}'.format(user_pk)
|
||||
res = client.get(OUTPUTS_ENDPOINT + params)
|
||||
assert res.json == [{'transaction_id': 'a', 'output': 0}]
|
||||
assert res.status_code == 200
|
||||
gof.assert_called_once_with(user_pk, True)
|
||||
|
||||
|
||||
def test_get_outputs_endpoint_without_public_key(client):
|
||||
res = client.get(OUTPUTS_ENDPOINT)
|
||||
assert res.status_code == 400
|
||||
@ -47,8 +60,8 @@ def test_get_outputs_endpoint_with_invalid_public_key(client):
|
||||
|
||||
|
||||
def test_get_outputs_endpoint_with_invalid_unspent(client, user_pk):
|
||||
expected = {'message': {'unspent': 'Boolean value must be "true" or "false" (lowercase)'}}
|
||||
params = '?unspent=tru&public_key={}'.format(user_pk)
|
||||
expected = {'message': {'spent': 'Boolean value must be "true" or "false" (lowercase)'}}
|
||||
params = '?spent=tru&public_key={}'.format(user_pk)
|
||||
res = client.get(OUTPUTS_ENDPOINT + params)
|
||||
assert expected == res.json
|
||||
assert res.status_code == 400
|
||||
|
Loading…
x
Reference in New Issue
Block a user