Skip to content
This repository has been archived by the owner on Jan 13, 2023. It is now read-only.

Commit

Permalink
Merge pull request #234 from pdecol/99-find-transaction-objects
Browse files Browse the repository at this point in the history
Implement find transaction objects command
  • Loading branch information
lzpap committed Sep 17, 2019
2 parents f6d3c5d + e2b90a9 commit f014740
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 31 deletions.
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ can also build the documentation locally:

#. Install extra dependencies (you only have to do this once)::

pip install '.[docs-builder]'
pip install .[docs-builder]

.. tip::

To install the CCurl extension and the documentation builder tools
together, use the following command::

pip install '.[ccurl,docs-builder]'
pip install .[ccurl,docs-builder]

#. Switch to the ``docs`` directory::

Expand Down
34 changes: 34 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,40 @@ This method returns a ``dict`` with the following items:
broadcast/stored. Should be the same as the value of the ``trytes``
parameter.


``find_transaction_objects``
----------------------------

A more extensive version of the core API ``find_transactions`` that returns
transaction objects instead of hashes.

Effectively, this is ``find_transactions`` + ``get_trytes`` + converting
the trytes into transaction objects. It accepts the same parameters
as ``find_transactions``

Find the transactions which match the specified input.
All input values are lists, for which a list of return values
(transaction hashes), in the same order, is returned for all
individual elements. Using multiple of these input fields returns the
intersection of the values.

Parameters
~~~~~~~~~~

- ``bundles: Optional[Iterable[BundleHash]]``: List of bundle IDs.
- ``addresses: Optional[Iterable[Address]]``: List of addresses.
- ``tags: Optional[Iterable[Tag]]``: List of tags.
- ``param: Optional[Iterable[TransactionHash]]``: List of approvee
transaction IDs.

Return
~~~~~~

This method returns a ``dict`` with the following items:

- ``transactions: List[Transaction]``: List of Transaction objects that
match the input

``get_account_data``
--------------------

Expand Down
44 changes: 44 additions & 0 deletions iota/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,50 @@ def broadcast_and_store(self, trytes):
"""
return extended.BroadcastAndStoreCommand(self.adapter)(trytes=trytes)

def find_transaction_objects(
self,
bundles=None, # type: Optional[Iterable[BundleHash]]
addresses=None, # type: Optional[Iterable[Address]]
tags=None, # type: Optional[Iterable[Tag]]
approvees=None, # type: Optional[Iterable[TransactionHash]]
):
# type: (...) -> dict
"""
A more extensive version of :py:meth:`find_transactions` that
returns transaction objects instead of hashes.
Effectively, this is ``find_transactions`` + ``get_trytes`` +
converting the trytes into transaction objects.
It accepts the same parameters as :py:meth:`find_transactions`
:param bundles:
List of bundle IDs.
:param addresses:
List of addresses.
:param tags:
List of tags.
:param approvees:
List of approvee transaction IDs.
:return:
Dict with the following structure::
{
'transactions': List[Transaction],
List of Transaction objects that match the input.
}
"""
return extended.FindTransactionObjectsCommand(self.adapter)(
bundles=bundles,
addresses=addresses,
tags=tags,
approvees=approvees,
)

def get_account_data(self, start=0, stop=None, inclusion_states=False, security_level=None):
# type: (int, Optional[int], bool, Optional[int]) -> dict
"""
Expand Down
1 change: 1 addition & 0 deletions iota/commands/extended/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
unicode_literals

from .broadcast_and_store import *
from .find_transaction_objects import *
from .get_account_data import *
from .get_bundles import *
from .get_inputs import *
Expand Down
55 changes: 55 additions & 0 deletions iota/commands/extended/find_transaction_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# coding=utf-8
from __future__ import absolute_import, division, print_function, \
unicode_literals

from typing import Iterable, List, Optional

from iota import Address, BundleHash, Tag, Transaction, TransactionHash
from iota.commands.core import GetTrytesCommand, FindTransactionsCommand

__all__ = [
'FindTransactionObjectsCommand',
]


class FindTransactionObjectsCommand(FindTransactionsCommand):
"""
Executes `FindTransactionObjects` command.
See :py:meth:`iota.api.StrictIota.find_transaction_objects`.
"""
command = 'findTransactionObjects'

def get_response_filter(self):
pass

def _execute(self, request):
bundles = request\
.get('bundles') # type: Optional[Iterable[BundleHash]]
addresses = request\
.get('addresses') # type: Optional[Iterable[Address]]
tags = request\
.get('tags') # type: Optional[Iterable[Tag]]
approvees = request\
.get('approvees') # type: Optional[Iterable[TransactionHash]]

ft_response = FindTransactionsCommand(adapter=self.adapter)(
bundles=bundles,
addresses=addresses,
tags=tags,
approvees=approvees,
)

hashes = ft_response['hashes']
transactions = []
if hashes:
gt_response = GetTrytesCommand(adapter=self.adapter)(hashes=hashes)

transactions = list(map(
Transaction.from_tryte_string,
gt_response.get('trytes') or [],
)) # type: List[Transaction]

return {
'transactions': transactions,
}
9 changes: 4 additions & 5 deletions iota/commands/extended/is_reattachable.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

from iota import Address
from iota.commands import FilterCommand, RequestFilter, ResponseFilter
from iota.commands.extended import GetLatestInclusionCommand
from iota.commands.extended.utils import find_transaction_objects
from iota.commands.extended import FindTransactionObjectsCommand, \
GetLatestInclusionCommand
from iota.filters import Trytes

__all__ = [
Expand All @@ -33,10 +33,9 @@ def _execute(self, request):
addresses = request['addresses'] # type: List[Address]

# fetch full transaction objects
transactions = find_transaction_objects(
adapter=self.adapter,
transactions = FindTransactionObjectsCommand(adapter=self.adapter)(
addresses=addresses,
)
)['transactions']

# Map and filter transactions which have zero value.
# If multiple transactions for the same address are returned,
Expand Down
27 changes: 3 additions & 24 deletions iota/commands/extended/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,14 @@
from iota.adapter import BaseAdapter
from iota.commands.core.find_transactions import FindTransactionsCommand
from iota.commands.core.get_trytes import GetTrytesCommand
from iota.commands.extended import FindTransactionObjectsCommand
from iota.commands.extended.get_bundles import GetBundlesCommand
from iota.commands.extended.get_latest_inclusion import \
GetLatestInclusionCommand
from iota.crypto.addresses import AddressGenerator
from iota.crypto.types import Seed


def find_transaction_objects(adapter, **kwargs):
# type: (BaseAdapter, **Iterable) -> List[Transaction]
"""
Finds transactions matching the specified criteria, fetches the
corresponding trytes and converts them into Transaction objects.
"""
ft_response = FindTransactionsCommand(adapter)(**kwargs)

hashes = ft_response['hashes']

if hashes:
gt_response = GetTrytesCommand(adapter)(hashes=hashes)

return list(map(
Transaction.from_tryte_string,
gt_response.get('trytes') or [],
)) # type: List[Transaction]

return []


def iter_used_addresses(
adapter, # type: BaseAdapter
seed, # type: Seed
Expand Down Expand Up @@ -103,10 +83,9 @@ def get_bundles_from_transaction_hashes(
non_tail_bundle_hashes.add(txn.bundle_hash)

if non_tail_bundle_hashes:
for txn in find_transaction_objects(
adapter=adapter,
for txn in FindTransactionObjectsCommand(adapter=adapter)(
bundles=list(non_tail_bundle_hashes),
):
)['transactions']:
if txn.is_tail:
if txn.hash not in tail_transaction_hashes:
all_transactions.append(txn)
Expand Down
118 changes: 118 additions & 0 deletions test/commands/extended/find_transaction_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# coding=utf-8
from __future__ import absolute_import, division, print_function, \
unicode_literals

from unittest import TestCase

import mock

from iota import Iota, MockAdapter, Transaction
from iota.commands.extended import FindTransactionObjectsCommand


class FindTransactionObjectsCommandTestCase(TestCase):
# noinspection SpellCheckingInspection
def setUp(self):
super(FindTransactionObjectsCommandTestCase, self).setUp()

self.adapter = MockAdapter()
self.command = FindTransactionObjectsCommand(self.adapter)

# Define values that we can reuse across tests.
self.address = 'A' * 81
self.transaction_hash = \
b'BROTOVRCAEMFLRWGPVWDPDTBRAMLHVCHQDEHXLCWH' \
b'KKXLVDFCPIJEUZTPPFMPQQ9KOHAEUAMMVJN99999'
self.trytes = \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999999999999999999999999999999999999999999999999' \
b'99999999999999999AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA99999999999999999999999999' \
b'9QC9999999999999999999999999PQYJHAD99999999999999999999WHIUDFV' \
b'IFXNBJVEHYPLDADIDINGAWMHYIJNPYUDWXCAWL9GSKTUIZLJGGFIXEIYTJEDQZ' \
b'TIYRXHC9PBWBDSOTEJTQTYYSZLVTFLDQMZSGLHKLYVJOLMXIJJRTGS9RYBXLAT' \
b'ZJXBVBCPUGWRUKZJYLBGPKRKWIA9999FPYHMFFWMMKOHTSAPMMATZQLWXJSPMT' \
b'JSRQIPMDCQXFFMXMHCYDKVJCFSRECAVALCOFIYCJLNRZZZ9999999999999999' \
b'999999999999999KITCXNZOF999999999MMMMMMMMMEA9999F9999999999999' \
b'9999999'

def test_wireup(self):
"""
Verify that the command is wired up correctly.
"""
self.assertIsInstance(
Iota(self.adapter).findTransactionObjects,
FindTransactionObjectsCommand,
)

def test_transaction_found(self):
"""
A transaction is found with the inputs. A transaction object is
returned
"""
with mock.patch(
'iota.commands.core.find_transactions.FindTransactionsCommand.'
'_execute',
mock.Mock(return_value={'hashes': [self.transaction_hash, ]}),
):
with mock.patch(
'iota.commands.core.get_trytes.GetTrytesCommand._execute',
mock.Mock(return_value={'trytes': [self.trytes, ]}),
):
response = self.command(addresses=[self.address])

self.assertEqual(len(response['transactions']), 1)
transaction = response['transactions'][0]
self.assertIsInstance(transaction, Transaction)
self.assertEqual(transaction.address, self.address)

def test_no_transactions_fround(self):
"""
No transaction is found with the inputs. An empty list is returned
"""
with mock.patch(
'iota.commands.core.find_transactions.FindTransactionsCommand.'
'_execute',
mock.Mock(return_value={'hashes': []}),
):
response = self.command(addresses=[self.address])

self.assertDictEqual(
response,
{
'transactions': [],
},
)

0 comments on commit f014740

Please sign in to comment.