mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge pull request #1066 from bigchaindb/1021/http_outputs_endpoint
1021/http outputs endpoint
This commit is contained in:
commit
cd7d65b63e
@ -159,7 +159,7 @@ class TransactionLink(object):
|
|||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
# TODO: If `other !== TransactionLink` return `False`
|
# TODO: If `other !== TransactionLink` return `False`
|
||||||
return self.to_dict() == self.to_dict()
|
return self.to_dict() == other.to_dict()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, link):
|
def from_dict(cls, link):
|
||||||
|
@ -373,8 +373,9 @@ class Bigchain(object):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_owned_ids(self, owner):
|
def get_outputs(self, owner):
|
||||||
"""Retrieve a list of ``txid`` s that can be used as inputs.
|
"""Retrieve a list of links to transaction outputs for a given public
|
||||||
|
key.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
owner (str): base58 encoded public key.
|
owner (str): base58 encoded public key.
|
||||||
@ -383,10 +384,9 @@ class Bigchain(object):
|
|||||||
:obj:`list` of TransactionLink: list of ``txid`` s and ``output`` s
|
:obj:`list` of TransactionLink: list of ``txid`` s and ``output`` s
|
||||||
pointing to another transaction's condition
|
pointing to another transaction's condition
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# get all transactions in which owner is in the `owners_after` list
|
# get all transactions in which owner is in the `owners_after` list
|
||||||
response = backend.query.get_owned_ids(self.connection, owner)
|
response = backend.query.get_owned_ids(self.connection, owner)
|
||||||
owned = []
|
links = []
|
||||||
|
|
||||||
for tx in response:
|
for tx in response:
|
||||||
# disregard transactions from invalid blocks
|
# disregard transactions from invalid blocks
|
||||||
@ -411,11 +411,30 @@ class Bigchain(object):
|
|||||||
# subfulfillment for `owner`
|
# subfulfillment for `owner`
|
||||||
if utils.condition_details_has_owner(output['condition']['details'], owner):
|
if utils.condition_details_has_owner(output['condition']['details'], owner):
|
||||||
tx_link = TransactionLink(tx['id'], index)
|
tx_link = TransactionLink(tx['id'], index)
|
||||||
# check if input was already spent
|
links.append(tx_link)
|
||||||
if not self.get_spent(tx_link.txid, tx_link.output):
|
return links
|
||||||
owned.append(tx_link)
|
|
||||||
|
|
||||||
return owned
|
def get_owned_ids(self, owner):
|
||||||
|
"""Retrieve a list of ``txid`` s that can be used as inputs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
owner (str): base58 encoded public key.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
: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)
|
||||||
|
|
||||||
|
def get_outputs_filtered(self, owner, include_spent=True):
|
||||||
|
"""
|
||||||
|
Get a list of output links filtered on some criteria
|
||||||
|
"""
|
||||||
|
outputs = self.get_outputs(owner)
|
||||||
|
if not include_spent:
|
||||||
|
outputs = [o for o in outputs
|
||||||
|
if not self.get_spent(o.txid, o.output)]
|
||||||
|
return outputs
|
||||||
|
|
||||||
def get_transactions_filtered(self, asset_id, operation=None):
|
def get_transactions_filtered(self, asset_id, operation=None):
|
||||||
"""
|
"""
|
||||||
|
@ -5,7 +5,7 @@ from bigchaindb.web.views import (
|
|||||||
info,
|
info,
|
||||||
statuses,
|
statuses,
|
||||||
transactions as tx,
|
transactions as tx,
|
||||||
unspents,
|
outputs,
|
||||||
votes,
|
votes,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ ROUTES_API_V1 = [
|
|||||||
r('statuses/', statuses.StatusApi),
|
r('statuses/', statuses.StatusApi),
|
||||||
r('transactions/<string:tx_id>', tx.TransactionApi),
|
r('transactions/<string:tx_id>', tx.TransactionApi),
|
||||||
r('transactions', tx.TransactionListApi),
|
r('transactions', tx.TransactionListApi),
|
||||||
r('unspents/', unspents.UnspentListApi),
|
r('outputs/', outputs.OutputListApi),
|
||||||
r('votes/', votes.VotesApi),
|
r('votes/', votes.VotesApi),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
28
bigchaindb/web/views/outputs.py
Normal file
28
bigchaindb/web/views/outputs.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from flask import current_app
|
||||||
|
from flask_restful import reqparse, Resource
|
||||||
|
|
||||||
|
from bigchaindb.web.views import parameters
|
||||||
|
|
||||||
|
|
||||||
|
class OutputListApi(Resource):
|
||||||
|
def get(self):
|
||||||
|
"""API endpoint to retrieve a list of links to transaction
|
||||||
|
outputs.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A :obj:`list` of :cls:`str` of links to outputs.
|
||||||
|
"""
|
||||||
|
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()
|
||||||
|
|
||||||
|
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)
|
||||||
|
# NOTE: We pass '..' as a path to create a valid relative URI
|
||||||
|
return [u.to_uri('..') for u in outputs]
|
@ -1,23 +0,0 @@
|
|||||||
from flask import current_app
|
|
||||||
from flask_restful import reqparse, Resource
|
|
||||||
|
|
||||||
|
|
||||||
class UnspentListApi(Resource):
|
|
||||||
def get(self):
|
|
||||||
"""API endpoint to retrieve a list of links to transactions's
|
|
||||||
conditions that have not been used in any previous transaction.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A :obj:`list` of :cls:`str` of links to unfulfilled conditions.
|
|
||||||
"""
|
|
||||||
parser = reqparse.RequestParser()
|
|
||||||
parser.add_argument('public_key', type=str, location='args',
|
|
||||||
required=True)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
pool = current_app.config['bigchain_pool']
|
|
||||||
|
|
||||||
with pool() as bigchain:
|
|
||||||
unspents = bigchain.get_owned_ids(args['public_key'])
|
|
||||||
# NOTE: We pass '..' as a path to create a valid relative URI
|
|
||||||
return [u.to_uri('..') for u in unspents]
|
|
@ -436,6 +436,15 @@ def test_cast_transaction_link_to_boolean():
|
|||||||
assert bool(TransactionLink(False, False)) is True
|
assert bool(TransactionLink(False, False)) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_transaction_link_eq():
|
||||||
|
from bigchaindb.common.transaction import TransactionLink
|
||||||
|
|
||||||
|
assert TransactionLink(1, 2) == TransactionLink(1, 2)
|
||||||
|
assert TransactionLink(2, 2) != TransactionLink(1, 2)
|
||||||
|
assert TransactionLink(1, 1) != TransactionLink(1, 2)
|
||||||
|
assert TransactionLink(2, 1) != TransactionLink(1, 2)
|
||||||
|
|
||||||
|
|
||||||
def test_add_input_to_tx(user_input, asset_definition):
|
def test_add_input_to_tx(user_input, asset_definition):
|
||||||
from bigchaindb.common.transaction import Transaction
|
from bigchaindb.common.transaction import Transaction
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
pytestmark = pytest.mark.bdb
|
pytestmark = pytest.mark.bdb
|
||||||
|
|
||||||
@ -1156,3 +1157,38 @@ class TestMultipleInputs(object):
|
|||||||
# check that the other remain marked as unspent
|
# check that the other remain marked as unspent
|
||||||
for unspent in transactions[1:]:
|
for unspent in transactions[1:]:
|
||||||
assert b.get_spent(unspent.id, 0) is None
|
assert b.get_spent(unspent.id, 0) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_owned_ids_calls_get_outputs_filtered():
|
||||||
|
from bigchaindb.core import Bigchain
|
||||||
|
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)
|
||||||
|
assert res == gof()
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_outputs_filtered_only_unspent():
|
||||||
|
from bigchaindb.common.transaction import TransactionLink
|
||||||
|
from bigchaindb.core import Bigchain
|
||||||
|
with patch('bigchaindb.core.Bigchain.get_outputs') as get_outputs:
|
||||||
|
get_outputs.return_value = [TransactionLink('a', 1),
|
||||||
|
TransactionLink('b', 2)]
|
||||||
|
with patch('bigchaindb.core.Bigchain.get_spent') as get_spent:
|
||||||
|
get_spent.side_effect = [True, False]
|
||||||
|
out = Bigchain().get_outputs_filtered('abc', include_spent=False)
|
||||||
|
get_outputs.assert_called_once_with('abc')
|
||||||
|
assert out == [TransactionLink('b', 2)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_outputs_filtered():
|
||||||
|
from bigchaindb.common.transaction import TransactionLink
|
||||||
|
from bigchaindb.core import Bigchain
|
||||||
|
with patch('bigchaindb.core.Bigchain.get_outputs') as get_outputs:
|
||||||
|
get_outputs.return_value = [TransactionLink('a', 1),
|
||||||
|
TransactionLink('b', 2)]
|
||||||
|
with patch('bigchaindb.core.Bigchain.get_spent') as get_spent:
|
||||||
|
out = Bigchain().get_outputs_filtered('abc')
|
||||||
|
get_outputs.assert_called_once_with('abc')
|
||||||
|
get_spent.assert_not_called()
|
||||||
|
assert out == get_outputs.return_value
|
||||||
|
49
tests/web/test_outputs.py
Normal file
49
tests/web/test_outputs.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
pytestmark = [pytest.mark.bdb, pytest.mark.usefixtures('inputs')]
|
||||||
|
|
||||||
|
OUTPUTS_ENDPOINT = '/api/v1/outputs/'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_outputs_endpoint(client, user_pk):
|
||||||
|
m = MagicMock()
|
||||||
|
m.to_uri.side_effect = lambda s: 'a%sb' % s
|
||||||
|
with patch('bigchaindb.core.Bigchain.get_outputs_filtered') as gof:
|
||||||
|
gof.return_value = [m, m]
|
||||||
|
res = client.get(OUTPUTS_ENDPOINT + '?public_key={}'.format(user_pk))
|
||||||
|
assert res.json == ['a..b', 'a..b']
|
||||||
|
assert res.status_code == 200
|
||||||
|
gof.assert_called_once_with(user_pk, True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_outputs_endpoint_unspent(client, user_pk):
|
||||||
|
m = MagicMock()
|
||||||
|
m.to_uri.side_effect = lambda s: 'a%sb' % s
|
||||||
|
with patch('bigchaindb.core.Bigchain.get_outputs_filtered') as gof:
|
||||||
|
gof.return_value = [m]
|
||||||
|
params = '?unspent=true&public_key={}'.format(user_pk)
|
||||||
|
res = client.get(OUTPUTS_ENDPOINT + params)
|
||||||
|
assert res.json == ['a..b']
|
||||||
|
assert res.status_code == 200
|
||||||
|
gof.assert_called_once_with(user_pk, False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_outputs_endpoint_without_public_key(client):
|
||||||
|
res = client.get(OUTPUTS_ENDPOINT)
|
||||||
|
assert res.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_outputs_endpoint_with_invalid_public_key(client):
|
||||||
|
expected = {'message': {'public_key': 'Invalid base58 ed25519 key'}}
|
||||||
|
res = client.get(OUTPUTS_ENDPOINT + '?public_key=abc')
|
||||||
|
assert expected == res.json
|
||||||
|
assert res.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
res = client.get(OUTPUTS_ENDPOINT + params)
|
||||||
|
assert expected == res.json
|
||||||
|
assert res.status_code == 400
|
@ -1,24 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
pytestmark = [pytest.mark.bdb, pytest.mark.usefixtures('inputs')]
|
|
||||||
|
|
||||||
UNSPENTS_ENDPOINT = '/api/v1/unspents/'
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_unspents_endpoint(b, client, user_pk):
|
|
||||||
expected = [u.to_uri('..') for u in b.get_owned_ids(user_pk)]
|
|
||||||
res = client.get(UNSPENTS_ENDPOINT + '?public_key={}'.format(user_pk))
|
|
||||||
assert expected == res.json
|
|
||||||
assert res.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_unspents_endpoint_without_public_key(client):
|
|
||||||
res = client.get(UNSPENTS_ENDPOINT)
|
|
||||||
assert res.status_code == 400
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_unspents_endpoint_with_unused_public_key(client):
|
|
||||||
expected = []
|
|
||||||
res = client.get(UNSPENTS_ENDPOINT + '?public_key=abc')
|
|
||||||
assert expected == res.json
|
|
||||||
assert res.status_code == 200
|
|
Loading…
x
Reference in New Issue
Block a user