mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Integrate output search api
This commit is contained in:
parent
1e104ad2c4
commit
2b3a9fa6f6
@ -111,3 +111,27 @@ def get_txids_filtered(conn, asset_id, operation=None):
|
|||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def text_search(*args, **kwargs):
|
def text_search(*args, **kwargs):
|
||||||
return mongodb.query.text_search(*args, **kwargs)
|
return mongodb.query.text_search(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_owned_ids(conn, owner):
|
||||||
|
cursor = conn.run(
|
||||||
|
conn.collection('transactions').aggregate([
|
||||||
|
{'$match': {'outputs.public_keys': owner}},
|
||||||
|
{'$project': {'_id': False}}
|
||||||
|
]))
|
||||||
|
return cursor
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_spending_transactions(conn, inputs):
|
||||||
|
cursor = conn.run(
|
||||||
|
conn.collection('transactions').aggregate([
|
||||||
|
{'$match': {
|
||||||
|
'inputs.fulfills': {
|
||||||
|
'$in': inputs,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
{'$project': {'_id': False}}
|
||||||
|
]))
|
||||||
|
return cursor
|
||||||
|
48
bigchaindb/tendermint/fastquery.py
Normal file
48
bigchaindb/tendermint/fastquery.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from bigchaindb.utils import condition_details_has_owner
|
||||||
|
from bigchaindb.backend import query
|
||||||
|
from bigchaindb.common.transaction import TransactionLink
|
||||||
|
|
||||||
|
|
||||||
|
class FastQuery():
|
||||||
|
"""
|
||||||
|
Database queries that join on block results from a single node.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_outputs_by_public_key(self, public_key):
|
||||||
|
"""
|
||||||
|
Get outputs for a public key
|
||||||
|
"""
|
||||||
|
txs = list(query.get_owned_ids(self.connection, public_key))
|
||||||
|
return [TransactionLink(tx['id'], index)
|
||||||
|
for tx in txs
|
||||||
|
for index, output in enumerate(tx['outputs'])
|
||||||
|
if condition_details_has_owner(output['condition']['details'],
|
||||||
|
public_key)]
|
||||||
|
|
||||||
|
def filter_spent_outputs(self, outputs):
|
||||||
|
"""
|
||||||
|
Remove outputs that have been spent
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outputs: list of TransactionLink
|
||||||
|
"""
|
||||||
|
links = [o.to_dict() for o in outputs]
|
||||||
|
txs = list(query.get_spending_transactions(self.connection, links))
|
||||||
|
spends = {TransactionLink.from_dict(input_['fulfills'])
|
||||||
|
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]
|
||||||
|
txs = list(query.get_spending_transactions(self.connection, links))
|
||||||
|
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]
|
@ -11,6 +11,7 @@ from bigchaindb import Bigchain
|
|||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Transaction
|
||||||
from bigchaindb.common.exceptions import SchemaValidationError, ValidationError
|
from bigchaindb.common.exceptions import SchemaValidationError, ValidationError
|
||||||
from bigchaindb.tendermint.utils import encode_transaction
|
from bigchaindb.tendermint.utils import encode_transaction
|
||||||
|
from bigchaindb.tendermint import fastquery
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -117,5 +118,9 @@ class BigchainDB(Bigchain):
|
|||||||
return False
|
return False
|
||||||
return transaction
|
return transaction
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fastquery(self):
|
||||||
|
return fastquery.FastQuery(self.connection, self.me)
|
||||||
|
|
||||||
|
|
||||||
Block = namedtuple('Block', ('app_hash', 'height'))
|
Block = namedtuple('Block', ('app_hash', 'height'))
|
||||||
|
@ -3,10 +3,9 @@ from copy import deepcopy
|
|||||||
import pytest
|
import pytest
|
||||||
import pymongo
|
import pymongo
|
||||||
|
|
||||||
pytestmark = [pytest.mark.tendermint, pytest.mark.localmongodb]
|
pytestmark = [pytest.mark.tendermint, pytest.mark.localmongodb, pytest.mark.bdb]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
|
||||||
def test_get_txids_filtered(signed_create_tx, signed_transfer_tx):
|
def test_get_txids_filtered(signed_create_tx, signed_transfer_tx):
|
||||||
from bigchaindb.backend import connect, query
|
from bigchaindb.backend import connect, query
|
||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Transaction
|
||||||
@ -32,7 +31,6 @@ def test_get_txids_filtered(signed_create_tx, signed_transfer_tx):
|
|||||||
assert txids == {signed_transfer_tx.id}
|
assert txids == {signed_transfer_tx.id}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
|
||||||
def test_write_assets():
|
def test_write_assets():
|
||||||
from bigchaindb.backend import connect, query
|
from bigchaindb.backend import connect, query
|
||||||
conn = connect()
|
conn = connect()
|
||||||
@ -57,7 +55,6 @@ def test_write_assets():
|
|||||||
assert list(cursor) == assets[:-1]
|
assert list(cursor) == assets[:-1]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
|
||||||
def test_get_assets():
|
def test_get_assets():
|
||||||
from bigchaindb.backend import connect, query
|
from bigchaindb.backend import connect, query
|
||||||
conn = connect()
|
conn = connect()
|
||||||
@ -74,8 +71,40 @@ def test_get_assets():
|
|||||||
assert query.get_asset(conn, asset['id'])
|
assert query.get_asset(conn, asset['id'])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.bdb
|
|
||||||
def test_text_search():
|
def test_text_search():
|
||||||
from ..mongodb.test_queries import test_text_search
|
from ..mongodb.test_queries import test_text_search
|
||||||
|
|
||||||
test_text_search('assets')
|
test_text_search('assets')
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_owned_ids(signed_create_tx, user_pk):
|
||||||
|
from bigchaindb.backend import connect, query
|
||||||
|
conn = connect()
|
||||||
|
|
||||||
|
# insert a transaction
|
||||||
|
conn.db.transactions.insert_one(signed_create_tx.to_dict())
|
||||||
|
|
||||||
|
txns = list(query.get_owned_ids(conn, user_pk))
|
||||||
|
|
||||||
|
assert txns[0] == signed_create_tx.to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_spending_transactions(user_pk):
|
||||||
|
from bigchaindb.backend import connect, query
|
||||||
|
from bigchaindb.models import Transaction
|
||||||
|
conn = connect()
|
||||||
|
|
||||||
|
out = [([user_pk], 1)]
|
||||||
|
tx1 = Transaction.create([user_pk], out * 3)
|
||||||
|
inputs = tx1.to_inputs()
|
||||||
|
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
|
||||||
|
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
|
||||||
|
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
|
||||||
|
txns = [tx.to_dict() for tx in [tx1, tx2, tx3, tx4]]
|
||||||
|
conn.db.transactions.insert_many(txns)
|
||||||
|
|
||||||
|
links = [inputs[0].fulfills.to_dict(), inputs[2].fulfills.to_dict()]
|
||||||
|
txns = list(query.get_spending_transactions(conn, links))
|
||||||
|
|
||||||
|
# tx3 not a member because input 1 not asked for
|
||||||
|
assert txns == [tx2.to_dict(), tx4.to_dict()]
|
||||||
|
@ -92,7 +92,7 @@ def test_deliver_transfer_tx__double_spend_fails(b):
|
|||||||
carly = generate_key_pair()
|
carly = generate_key_pair()
|
||||||
|
|
||||||
asset = {
|
asset = {
|
||||||
"msg": "live long and prosper"
|
'msg': 'live long and prosper'
|
||||||
}
|
}
|
||||||
|
|
||||||
tx = Transaction.create([alice.public_key],
|
tx = Transaction.create([alice.public_key],
|
||||||
|
@ -3,11 +3,10 @@ import pytest
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def app(request):
|
def app(request):
|
||||||
from bigchaindb import config
|
|
||||||
from bigchaindb.web import server
|
from bigchaindb.web import server
|
||||||
from bigchaindb.tendermint.lib import BigchainDB
|
from bigchaindb.tendermint.lib import BigchainDB
|
||||||
|
|
||||||
if config['database']['backend'] == 'localmongodb':
|
if request.config.getoption('--database-backend') == 'localmongodb':
|
||||||
app = server.create_app(debug=True, bigchaindb_factory=BigchainDB)
|
app = server.create_app(debug=True, bigchaindb_factory=BigchainDB)
|
||||||
else:
|
else:
|
||||||
app = server.create_app(debug=True)
|
app = server.create_app(debug=True)
|
||||||
|
@ -6,11 +6,12 @@ pytestmark = [pytest.mark.bdb, pytest.mark.usefixtures('inputs')]
|
|||||||
OUTPUTS_ENDPOINT = '/api/v1/outputs/'
|
OUTPUTS_ENDPOINT = '/api/v1/outputs/'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.tendermint
|
||||||
def test_get_outputs_endpoint(client, user_pk):
|
def test_get_outputs_endpoint(client, user_pk):
|
||||||
m = MagicMock()
|
m = MagicMock()
|
||||||
m.txid = 'a'
|
m.txid = 'a'
|
||||||
m.output = 0
|
m.output = 0
|
||||||
with patch('bigchaindb.core.Bigchain.get_outputs_filtered') as gof:
|
with patch('bigchaindb.tendermint.lib.BigchainDB.get_outputs_filtered') as gof:
|
||||||
gof.return_value = [m, m]
|
gof.return_value = [m, m]
|
||||||
res = client.get(OUTPUTS_ENDPOINT + '?public_key={}'.format(user_pk))
|
res = client.get(OUTPUTS_ENDPOINT + '?public_key={}'.format(user_pk))
|
||||||
assert res.json == [
|
assert res.json == [
|
||||||
@ -21,11 +22,12 @@ def test_get_outputs_endpoint(client, user_pk):
|
|||||||
gof.assert_called_once_with(user_pk, None)
|
gof.assert_called_once_with(user_pk, None)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.tendermint
|
||||||
def test_get_outputs_endpoint_unspent(client, user_pk):
|
def test_get_outputs_endpoint_unspent(client, user_pk):
|
||||||
m = MagicMock()
|
m = MagicMock()
|
||||||
m.txid = 'a'
|
m.txid = 'a'
|
||||||
m.output = 0
|
m.output = 0
|
||||||
with patch('bigchaindb.core.Bigchain.get_outputs_filtered') as gof:
|
with patch('bigchaindb.tendermint.lib.BigchainDB.get_outputs_filtered') as gof:
|
||||||
gof.return_value = [m]
|
gof.return_value = [m]
|
||||||
params = '?spent=False&public_key={}'.format(user_pk)
|
params = '?spent=False&public_key={}'.format(user_pk)
|
||||||
res = client.get(OUTPUTS_ENDPOINT + params)
|
res = client.get(OUTPUTS_ENDPOINT + params)
|
||||||
@ -34,11 +36,12 @@ def test_get_outputs_endpoint_unspent(client, user_pk):
|
|||||||
gof.assert_called_once_with(user_pk, False)
|
gof.assert_called_once_with(user_pk, False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.tendermint
|
||||||
def test_get_outputs_endpoint_spent(client, user_pk):
|
def test_get_outputs_endpoint_spent(client, user_pk):
|
||||||
m = MagicMock()
|
m = MagicMock()
|
||||||
m.txid = 'a'
|
m.txid = 'a'
|
||||||
m.output = 0
|
m.output = 0
|
||||||
with patch('bigchaindb.core.Bigchain.get_outputs_filtered') as gof:
|
with patch('bigchaindb.tendermint.lib.BigchainDB.get_outputs_filtered') as gof:
|
||||||
gof.return_value = [m]
|
gof.return_value = [m]
|
||||||
params = '?spent=true&public_key={}'.format(user_pk)
|
params = '?spent=true&public_key={}'.format(user_pk)
|
||||||
res = client.get(OUTPUTS_ENDPOINT + params)
|
res = client.get(OUTPUTS_ENDPOINT + params)
|
||||||
@ -47,11 +50,13 @@ def test_get_outputs_endpoint_spent(client, user_pk):
|
|||||||
gof.assert_called_once_with(user_pk, True)
|
gof.assert_called_once_with(user_pk, True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.tendermint
|
||||||
def test_get_outputs_endpoint_without_public_key(client):
|
def test_get_outputs_endpoint_without_public_key(client):
|
||||||
res = client.get(OUTPUTS_ENDPOINT)
|
res = client.get(OUTPUTS_ENDPOINT)
|
||||||
assert res.status_code == 400
|
assert res.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.tendermint
|
||||||
def test_get_outputs_endpoint_with_invalid_public_key(client):
|
def test_get_outputs_endpoint_with_invalid_public_key(client):
|
||||||
expected = {'message': {'public_key': 'Invalid base58 ed25519 key'}}
|
expected = {'message': {'public_key': 'Invalid base58 ed25519 key'}}
|
||||||
res = client.get(OUTPUTS_ENDPOINT + '?public_key=abc')
|
res = client.get(OUTPUTS_ENDPOINT + '?public_key=abc')
|
||||||
@ -59,6 +64,7 @@ def test_get_outputs_endpoint_with_invalid_public_key(client):
|
|||||||
assert res.status_code == 400
|
assert res.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.tendermint
|
||||||
def test_get_outputs_endpoint_with_invalid_spent(client, user_pk):
|
def test_get_outputs_endpoint_with_invalid_spent(client, user_pk):
|
||||||
expected = {'message': {'spent': 'Boolean value must be "true" or "false" (lowercase)'}}
|
expected = {'message': {'spent': 'Boolean value must be "true" or "false" (lowercase)'}}
|
||||||
params = '?spent=tru&public_key={}'.format(user_pk)
|
params = '?spent=tru&public_key={}'.format(user_pk)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user