예제 #1
0
def iter_used_addresses(
        adapter,  # type: BaseAdapter
        seed,  # type: Seed
        start,  # type: int
        security_level=None,  # type: Optional[int]
):
    # type: (...) -> Generator[Tuple[Address, List[TransactionHash]], None, None]
    """
    Scans the Tangle for used addresses.

    This is basically the opposite of invoking ``getNewAddresses`` with
    ``stop=None``.
    """
    if security_level is None:
        security_level = AddressGenerator.DEFAULT_SECURITY_LEVEL

    ft_command = FindTransactionsCommand(adapter)

    for addy in AddressGenerator(seed, security_level).create_iterator(start):
        ft_response = ft_command(addresses=[addy])

        if ft_response['hashes']:
            yield addy, ft_response['hashes']
        else:
            break

        # Reset the command so that we can call it again.
        ft_command.reset()
예제 #2
0
    def _find_addresses(self, seed, index, count, security_level, checksum):
        # type: (Seed, int, Optional[int], int, bool) -> List[Address]
        """
        Find addresses matching the command parameters.
        """
        generator = AddressGenerator(seed, security_level, checksum)

        if count is None:
            # Connect to Tangle and find the first unused address.
            for addy in generator.create_iterator(start=index):
                # We use addy.address here because the commands do
                # not work on an address with a checksum
                response = WereAddressesSpentFromCommand(self.adapter)(
                    addresses=[addy.address], )
                if response['states'][0]:
                    continue

                response = FindTransactionsCommand(self.adapter)(
                    addresses=[addy.address], )
                if response.get('hashes'):
                    continue

                return [addy]

        return generator.get_addresses(start=index, count=count)
예제 #3
0
    def _execute(self, request):
        inclusion_states = request['inclusionStates']  # type: bool
        seed = request['seed']  # type: Seed
        start = request['start']  # type: int
        stop = request['stop']  # type: Optional[int]

        # Determine the addresses we will be scanning, and pull their
        # transaction hashes.
        if stop is None:
            my_hashes = list(
                chain(*(hashes for _, hashes in iter_used_addresses(
                    self.adapter, seed, start))))
        else:
            ft_response = \
                FindTransactionsCommand(self.adapter)(
                    addresses=
                    AddressGenerator(seed).get_addresses(start, stop - start),
                )

            my_hashes = ft_response['hashes']

        return {
            'bundles':
            get_bundles_from_transaction_hashes(
                adapter=self.adapter,
                transaction_hashes=my_hashes,
                inclusion_states=inclusion_states,
            ),
        }
예제 #4
0
class FindTransactionsResponseFilterTestCase(BaseFilterTestCase):
    filter_type = FindTransactionsCommand(MockAdapter()).get_response_filter
    skip_value_check = True

    # noinspection SpellCheckingInspection
    def setUp(self):
        super(FindTransactionsResponseFilterTestCase, self).setUp()

        # Define a few valid values here that we can reuse across multiple
        # tests.
        self.trytes1 = b'RBTC9D9DCDQAEASBYBCCKBFA'
        self.trytes2 =\
          b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA'

    def test_no_results(self):
        """
    The incoming response contains no hashes.
    """
        response = {
            'hashes': [],
            'duration': 42,
        }

        filter_ = self._filter(response)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(filter_.cleaned_data, response)

    # noinspection SpellCheckingInspection
    def test_search_results(self):
        """
    The incoming response contains lots of hashes.
    """
        filter_ = self._filter({
            'hashes': [
                'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFW'
                'YWZRE9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVA',
                'ZJVYUGTDRPDYFGFXMKOTV9ZWSGFK9CFPXTITQLQN'
                'LPPG9YNAARMKNKYQO9GSCSBIOTGMLJUFLZWSY9999',
            ],
            'duration':
            42,
        })

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'hashes': [
                    TransactionHash(
                        b'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFW'
                        b'YWZRE9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVA', ),
                    TransactionHash(
                        b'ZJVYUGTDRPDYFGFXMKOTV9ZWSGFK9CFPXTITQLQN'
                        b'LPPG9YNAARMKNKYQO9GSCSBIOTGMLJUFLZWSY9999', ),
                ],
                'duration':
                42,
            },
        )
예제 #5
0
    async def _find_addresses(self, seed: Seed, index: int,
                              count: Optional[int], security_level: int,
                              checksum: bool) -> List[Address]:
        """
        Find addresses matching the command parameters.
        """
        generator = AddressGenerator(seed, security_level, checksum)

        if count is None:
            # Connect to Tangle and find the first unused address.
            for addy in generator.create_iterator(start=index):
                # We use addy.address here because the commands do
                # not work on an address with a checksum
                # Execute two checks concurrently
                responses = await asyncio.gather(
                    WereAddressesSpentFromCommand(self.adapter)(
                        addresses=[addy.address], ),
                    FindTransactionsCommand(self.adapter)(
                        addresses=[addy.address], ),
                )
                # responses[0] -> was it spent from?
                # responses[1] -> any transaction found?
                if responses[0]['states'][0] or responses[1].get('hashes'):
                    continue

                return [addy]

        return generator.get_addresses(start=index, count=count)
예제 #6
0
    def _find_addresses(self, seed, index, count):
        """
    Find addresses matching the command parameters.
    """
        # type: (Seed, int, Optional[int]) -> List[Address]
        generator = AddressGenerator(seed)

        if count is None:
            # Connect to Tangle and find the first address without any
            # transactions.
            for addy in generator.create_iterator(start=index):
                response = FindTransactionsCommand(
                    self.adapter)(addresses=[addy])

                if not response.get('hashes'):
                    return [addy]

        return generator.get_addresses(start=index, count=count)
예제 #7
0
    def _find_transactions(self, **kwargs):
        # type: (dict) -> List[Transaction]
        """
    Finds transactions matching the specified criteria, fetches the
    corresponding trytes and converts them into Transaction objects.
    """
        ft_response = FindTransactionsCommand(self.adapter)(**kwargs)

        hashes = ft_response.get('hashes') or []

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

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

        return []
예제 #8
0
def iter_used_addresses(adapter, seed, start):
  # type: (BaseAdapter, Seed, int) -> Generator[Tuple[Address, List[TransactionHash]]]
  """
  Scans the Tangle for used addresses.

  This is basically the opposite of invoking ``getNewAddresses`` with
  ``stop=None``.
  """
  ft_command = FindTransactionsCommand(adapter)

  for addy in AddressGenerator(seed).create_iterator(start):
    ft_response = ft_command(addresses=[addy])

    if ft_response['hashes']:
      yield addy, ft_response['hashes']
    else:
      break

    # Reset the command so that we can call it again.
    ft_command.reset()
예제 #9
0
def iter_used_addresses(adapter, seed, start):
    # type: (BaseAdapter, Seed, int) -> Generator[Tuple[Address, List[TransactionHash]]]
    """
  Scans the Tangle for used addresses.

  This is basically the opposite of invoking ``getNewAddresses`` with
  ``stop=None``.
  """
    ft_command = FindTransactionsCommand(adapter)

    for addy in AddressGenerator(seed).create_iterator(start):
        ft_response = ft_command(addresses=[addy])

        if ft_response['hashes']:
            yield addy, ft_response['hashes']
        else:
            break

        # Reset the command so that we can call it again.
        ft_command.reset()
예제 #10
0
  def _find_addresses(self, seed, index, count, security_level, checksum):
    # type: (Seed, int, Optional[int], int, bool) -> List[Address]
    """
    Find addresses matching the command parameters.
    """
    generator = AddressGenerator(seed, security_level, checksum)

    if count is None:
      # Connect to Tangle and find the first address without any
      # transactions.
      for addy in generator.create_iterator(start=index):
        # We use addy.address here because FindTransactions does
        # not work on an address with a checksum
        response = FindTransactionsCommand(self.adapter)(
           addresses=[addy.address]
        )

        if not response.get('hashes'):
          return [addy]

    return generator.get_addresses(start=index, count=count)
예제 #11
0
async def iter_used_addresses(
        adapter: BaseAdapter,
        seed: Seed,
        start: int,
        security_level: Optional[int] = None,
        # 'typing' only supports AsyncGenerator from python 3.6.1, so put it
        # as string literal here.
) -> 'AsyncGenerator[Tuple[Address, List[TransactionHash]], None]':
    """
    Scans the Tangle for used addresses. A used address is an address that
    was spent from or has a transaction.

    This is basically the opposite of invoking ``getNewAddresses`` with
    ``count=None``.

    .. important::
        This is an async generator!

    """
    if security_level is None:
        security_level = AddressGenerator.DEFAULT_SECURITY_LEVEL

    ft_command = FindTransactionsCommand(adapter)
    wasf_command = WereAddressesSpentFromCommand(adapter)

    for addy in AddressGenerator(seed, security_level).create_iterator(start):
        ft_response = await ft_command(addresses=[addy])

        if ft_response['hashes']:
            yield addy, ft_response['hashes']
        else:
            wasf_response = await wasf_command(addresses=[addy])
            if wasf_response['states'][0]:
                yield addy, []
            else:
                break

        # Reset the commands so that we can call them again.
        ft_command.reset()
        wasf_command.reset()
예제 #12
0
    async def _execute(self, request: dict) -> dict:
        inclusion_states: bool = request['inclusionStates']
        seed: Seed = request['seed']
        start: int = request['start']
        stop: Optional[int] = request['stop']
        security_level: Optional[int] = request['security_level']

        if stop is None:
            my_addresses: List[Address] = []
            my_hashes: List[TransactionHash] = []

            async for addy, hashes in iter_used_addresses(
                    self.adapter, seed, start, security_level):
                my_addresses.append(addy)
                my_hashes.extend(hashes)
        else:
            ft_command = FindTransactionsCommand(self.adapter)

            my_addresses = (AddressGenerator(seed,
                                             security_level).get_addresses(
                                                 start, stop - start))
            my_hashes = (await ft_command(addresses=my_addresses
                                          )).get('hashes') or []

        account_balance = 0
        if my_addresses:
            # Load balances for the addresses that we generated.
            gb_response = (await GetBalancesCommand(self.adapter)
                           (addresses=my_addresses))

            for i, balance in enumerate(gb_response['balances']):
                my_addresses[i].balance = balance
                account_balance += balance

        return {
            'addresses':
            list(sorted(my_addresses, key=attrgetter('key_index'))),
            'balance':
            account_balance,
            'bundles':
            await get_bundles_from_transaction_hashes(
                adapter=self.adapter,
                transaction_hashes=my_hashes,
                inclusion_states=inclusion_states,
            ),
        }
예제 #13
0
    def _execute(self, request):
        inclusion_states = request['inclusionStates']  # type: bool
        seed = request['seed']  # type: Seed
        start = request['start']  # type: int
        stop = request['stop']  # type: Optional[int]

        if stop is None:
            my_addresses = []  # type: List[Address]
            my_hashes = []  # type: List[TransactionHash]

            for addy, hashes in iter_used_addresses(self.adapter, seed, start):
                my_addresses.append(addy)
                my_hashes.extend(hashes)
        else:
            ft_command = FindTransactionsCommand(self.adapter)

            my_addresses = AddressGenerator(seed).get_addresses(
                start, stop - start)
            my_hashes = ft_command(addresses=my_addresses).get('hashes') or []

        account_balance = 0
        if my_hashes:
            # Load balances for the addresses that we generated.
            gb_response = GetBalancesCommand(
                self.adapter)(addresses=my_addresses)

            for i, balance in enumerate(gb_response['balances']):
                my_addresses[i].balance = balance
                account_balance += balance

        return {
            'addresses':
            list(sorted(my_addresses, key=attrgetter('key_index'))),
            'balance':
            account_balance,
            'bundles':
            get_bundles_from_transaction_hashes(
                adapter=self.adapter,
                transaction_hashes=my_hashes,
                inclusion_states=inclusion_states,
            ),
        }
예제 #14
0
    def _execute(self, request):
        inclusion_states = request['inclusionStates']  # type: bool
        address = request['address']  # type: Address

        # Determine the addresses we will be scanning, and pull their
        # transaction hashes.

        ft_response =\
            FindTransactionsCommand(self.adapter)(
                addresses =
                    address
            )

        my_hashes = ft_response['hashes']

        return {
            'bundles':
            get_bundles_from_transaction_hashes(
                adapter=self.adapter,
                transaction_hashes=my_hashes,
                inclusion_states=inclusion_states,
            ),
        }
예제 #15
0
    def _execute(self, request):
        stop = request['stop']  # type: Optional[int]
        inclusion_states = request['inclusionStates']  # type: bool
        seed = request['seed']  # type: Seed
        start = request['start']  # type: int

        generator = AddressGenerator(seed)
        ft_command = FindTransactionsCommand(self.adapter)

        # Determine the addresses we will be scanning, and pull their
        # transaction hashes.
        if stop is None:
            # This is similar to the ``getNewAddresses`` command, except it
            # is interested in all the addresses that `getNewAddresses`
            # skips.
            hashes = []
            for addy in generator.create_generator(start):
                ft_response = ft_command(addresses=[addy])

                if ft_response.get('hashes'):
                    hashes += ft_response['hashes']
                else:
                    break

                # Reset the command so that we can call it again.
                ft_command.reset()
        else:
            ft_response =\
              ft_command(addresses=generator.get_addresses(start, stop - start))

            hashes = ft_response.get('hashes') or []

        all_bundles = []  # type: List[Bundle]

        if hashes:
            # Sort transactions into tail and non-tail.
            tail_transaction_hashes = set()
            non_tail_bundle_hashes = set()

            gt_response = GetTrytesCommand(self.adapter)(hashes=hashes)
            all_transactions = list(
                map(
                    Transaction.from_tryte_string,
                    gt_response['trytes'],
                ))  # type: List[Transaction]

            for txn in all_transactions:
                if txn.is_tail:
                    tail_transaction_hashes.add(txn.hash)
                else:
                    # Capture the bundle ID instead of the transaction hash so that
                    # we can query the node to find the tail transaction for that
                    # bundle.
                    non_tail_bundle_hashes.add(txn.bundle_hash)

            if non_tail_bundle_hashes:
                for txn in self._find_transactions(
                        bundles=list(non_tail_bundle_hashes)):
                    if txn.is_tail:
                        if txn.hash not in tail_transaction_hashes:
                            all_transactions.append(txn)
                            tail_transaction_hashes.add(txn.hash)

            # Filter out all non-tail transactions.
            tail_transactions = [
                txn for txn in all_transactions
                if txn.hash in tail_transaction_hashes
            ]

            # Attach inclusion states, if requested.
            if inclusion_states:
                gli_response = GetLatestInclusionCommand(self.adapter)(
                    hashes=list(tail_transaction_hashes), )

                for txn in tail_transactions:
                    txn.is_confirmed = gli_response['states'].get(txn.hash)

            # Find the bundles for each transaction.
            for txn in tail_transactions:
                gb_response = GetBundlesCommand(
                    self.adapter)(transaction=txn.hash)
                txn_bundles = gb_response['bundles']  # type: List[Bundle]

                if inclusion_states:
                    for bundle in txn_bundles:
                        bundle.is_confirmed = txn.is_confirmed

                all_bundles.extend(txn_bundles)

        return {
            # Sort bundles by tail transaction timestamp.
            'bundles':
            list(
                sorted(
                    all_bundles,
                    key=lambda bundle_: bundle_.tail_transaction.timestamp,
                )),
        }
예제 #16
0
class FindTransactionsRequestFilterTestCase(BaseFilterTestCase):
    filter_type = FindTransactionsCommand(MockAdapter()).get_request_filter
    skip_value_check = True

    # noinspection SpellCheckingInspection
    def setUp(self):
        super(FindTransactionsRequestFilterTestCase, self).setUp()

        # Define a few valid values that we can reuse across tests.
        self.trytes1 = 'RBTC9D9DCDQAEASBYBCCKBFA'
        self.trytes2 =\
          'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA'
        self.trytes3 = '999999999999999999999999999'

    def test_pass_all_parameters(self):
        """
    The request contains valid values for all parameters.
    """
        # Raw trytes are extracted to match the IRI's JSON protocol.
        request = {
            'bundles': [
                text_type(BundleHash(self.trytes1)),
                text_type(BundleHash(self.trytes2)),
            ],
            'addresses': [
                text_type(Address(self.trytes1)),
                text_type(Address(self.trytes2)),
            ],
            'tags': [
                text_type(Tag(self.trytes1)),
                text_type(Tag(self.trytes3)),
            ],
            'approvees': [
                text_type(TransactionHash(self.trytes1)),
                text_type(TransactionHash(self.trytes3)),
            ],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(filter_.cleaned_data, request)

    def test_pass_compatible_types(self):
        """
    The request contains values that can be converted to the expected
    types.
    """
        filter_ = self._filter({
            'bundles': [
                self.trytes1.encode('ascii'),
                BundleHash(self.trytes2),
            ],
            'addresses': [
                self.trytes1.encode('ascii'),
                Address(self.trytes2),
            ],
            'tags': [
                self.trytes1.encode('ascii'),
                Tag(self.trytes3),
            ],
            'approvees': [
                self.trytes1.encode('ascii'),
                TransactionHash(self.trytes3),
            ],
        })

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                # Raw trytes are extracted to match the IRI's JSON protocol.
                'bundles': [
                    text_type(BundleHash(self.trytes1)),
                    text_type(BundleHash(self.trytes2)),
                ],
                'addresses': [
                    text_type(Address(self.trytes1)),
                    text_type(Address(self.trytes2)),
                ],
                'tags': [
                    text_type(Tag(self.trytes1)),
                    text_type(Tag(self.trytes3)),
                ],
                'approvees': [
                    text_type(TransactionHash(self.trytes1)),
                    text_type(TransactionHash(self.trytes3)),
                ],
            },
        )

    def test_pass_bundles_only(self):
        """
    The request only includes bundles.
    """
        request = {
            'bundles': [
                BundleHash(self.trytes1),
                BundleHash(self.trytes2),
            ],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'bundles': [
                    text_type(BundleHash(self.trytes1)),
                    text_type(BundleHash(self.trytes2)),
                ],

                # Null criteria are not included in the request.
                # https://github.com/iotaledger/iota.lib.py/issues/96
                # 'addresses':  [],
                # 'approvees':  [],
                # 'tags':       [],
            },
        )

    def test_pass_addresses_only(self):
        """
    The request only includes addresses.
    """
        request = {
            'addresses': [
                Address(self.trytes1),
                Address(self.trytes2),
            ],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'addresses': [
                    text_type(Address(self.trytes1)),
                    text_type(Address(self.trytes2)),
                ],

                # Null criteria are not included in the request.
                # https://github.com/iotaledger/iota.lib.py/issues/96
                # 'approvees':  [],
                # 'bundles':    [],
                # 'tags':       [],
            },
        )

    def test_pass_tags_only(self):
        """
    The request only includes tags.
    """
        request = {
            'tags': [
                Tag(self.trytes1),
                Tag(self.trytes3),
            ],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'tags': [
                    text_type(Tag(self.trytes1)),
                    text_type(Tag(self.trytes3)),
                ],

                # Null criteria are not included in the request.
                # https://github.com/iotaledger/iota.lib.py/issues/96
                # 'addresses':  [],
                # 'approvees':  [],
                # 'bundles':    [],
            },
        )

    def test_pass_approvees_only(self):
        """
    The request only includes approvees.
    """
        request = {
            'approvees': [
                TransactionHash(self.trytes1),
                TransactionHash(self.trytes3),
            ],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'approvees': [
                    text_type(TransactionHash(self.trytes1)),
                    text_type(TransactionHash(self.trytes3)),
                ],

                # Null criteria are not included in the request.
                # https://github.com/iotaledger/iota.lib.py/issues/96
                # 'addresses':  [],
                # 'bundles':    [],
                # 'tags':       [],
            },
        )

    def test_fail_empty(self):
        """
    The request does not contain any parameters.
    """
        self.assertFilterErrors(
            {},
            {
                '': [FindTransactionsRequestFilter.CODE_NO_SEARCH_VALUES],
            },
        )

    def test_fail_all_parameters_null(self):
        """
    The request contains all parameters, but every one is null.
    """
        self.assertFilterErrors(
            {
                'addresses': None,
                'approvees': None,
                'bundles': None,
                'tags': None,
            },
            {
                '': [FindTransactionsRequestFilter.CODE_NO_SEARCH_VALUES],
            },
        )

    def test_success_all_parameters_empty(self):
        """
    All of the parameters are empty lists.

    This is technically valid, though probably not very useful.
    """
        self.assertFilterPasses(
            {
                'addresses': [],
                'approvees': [],
                'bundles': [],
                'tags': [],
            }, )

    def test_fail_unexpected_parameters(self):
        """
    The request contains unexpected parameters.
    """
        self.assertFilterErrors(
            {
                'addresses': [Address(self.trytes1)],
                'approvees': [TransactionHash(self.trytes1)],
                'bundles': [BundleHash(self.trytes1)],
                'tags': [Tag(self.trytes1)],

                # Hey, you're not allowed in he-argh!
                'foo': 'bar',
            },
            {
                'foo': [f.FilterMapper.CODE_EXTRA_KEY],
            },
        )

    def test_fail_bundles_wrong_type(self):
        """
    ``bundles`` is not an array.
    """
        self.assertFilterErrors(
            {
                'bundles': BundleHash(self.trytes1),
            },
            {
                'bundles': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_bundles_contents_invalid(self):
        """
    ``bundles`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'bundles': [
                    b'',
                    True,
                    None,
                    b'not valid trytes',

                    # This is actually valid; I just added it to make sure the
                    # filter isn't cheating!
                    TryteString(self.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                'bundles.0': [f.Required.CODE_EMPTY],
                'bundles.1': [f.Type.CODE_WRONG_TYPE],
                'bundles.2': [f.Required.CODE_EMPTY],
                'bundles.3': [Trytes.CODE_NOT_TRYTES],
                'bundles.5': [f.Type.CODE_WRONG_TYPE],
                'bundles.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )

    def test_fail_addresses_wrong_type(self):
        """
    ``addresses`` is not an array.
    """
        self.assertFilterErrors(
            {
                'addresses': Address(self.trytes1),
            },
            {
                'addresses': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_addresses_contents_invalid(self):
        """
    ``addresses`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'addresses': [
                    b'',
                    True,
                    None,
                    b'not valid trytes',

                    # This is actually valid; I just added it to make sure the
                    # filter isn't cheating!
                    TryteString(self.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                'addresses.0': [f.Required.CODE_EMPTY],
                'addresses.1': [f.Type.CODE_WRONG_TYPE],
                'addresses.2': [f.Required.CODE_EMPTY],
                'addresses.3': [Trytes.CODE_NOT_TRYTES],
                'addresses.5': [f.Type.CODE_WRONG_TYPE],
                'addresses.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )

    def test_fail_tags_wrong_type(self):
        """
    ``tags`` is not an array.
    """
        self.assertFilterErrors(
            {
                'tags': Tag(self.trytes1),
            },
            {
                'tags': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_tags_contents_invalid(self):
        """
    ``tags`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'tags': [
                    b'',
                    True,
                    None,
                    b'not valid trytes',

                    # This is actually valid; I just added it to make sure the
                    # filter isn't cheating!
                    TryteString(self.trytes1),
                    2130706433,
                    b'9' * 28,
                ],
            },
            {
                'tags.0': [f.Required.CODE_EMPTY],
                'tags.1': [f.Type.CODE_WRONG_TYPE],
                'tags.2': [f.Required.CODE_EMPTY],
                'tags.3': [Trytes.CODE_NOT_TRYTES],
                'tags.5': [f.Type.CODE_WRONG_TYPE],
                'tags.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )

    def test_fail_approvees_wrong_type(self):
        """
    ``approvees`` is not an array.
    """
        self.assertFilterErrors(
            {
                'approvees': TransactionHash(self.trytes1),
            },
            {
                'approvees': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_approvees_contents_invalid(self):
        """
    ``approvees`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'approvees': [
                    b'',
                    True,
                    None,
                    b'not valid trytes',

                    # This is actually valid; I just added it to make sure the
                    # filter isn't cheating!
                    TryteString(self.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                'approvees.0': [f.Required.CODE_EMPTY],
                'approvees.1': [f.Type.CODE_WRONG_TYPE],
                'approvees.2': [f.Required.CODE_EMPTY],
                'approvees.3': [Trytes.CODE_NOT_TRYTES],
                'approvees.5': [f.Type.CODE_WRONG_TYPE],
                'approvees.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )
예제 #17
0
    def _execute(self, request):
        stop = request['stop']  # type: Optional[int]
        seed = request['seed']  # type: Seed
        start = request['start']  # type: int
        threshold = request['threshold']  # type: Optional[int]

        generator = AddressGenerator(seed)

        # Determine the addresses we will be scanning.
        if stop is None:
            # This is similar to the ``getNewAddresses`` command, except it
            # is interested in all the addresses that `getNewAddresses`
            # skips.
            addresses = []  # type: List[Address]
            for addy in generator.create_generator(start):
                ft_response = FindTransactionsCommand(
                    self.adapter)(addresses=[addy])

                if ft_response.get('hashes'):
                    addresses.append(addy)
                else:
                    break
        else:
            addresses = generator.get_addresses(start, stop)

        # Load balances for the addresses that we generated.
        gb_response = GetBalancesCommand(self.adapter)(addresses=addresses)

        result = {
            'inputs': [],
            'totalBalance': 0,
        }

        threshold_met = threshold is None

        for i, balance in enumerate(gb_response['balances']):
            addresses[i].balance = balance

            if balance:
                result['inputs'].append(addresses[i])
                result['totalBalance'] += balance

                if (threshold
                        is not None) and (result['totalBalance'] >= threshold):
                    threshold_met = True
                    break

        if threshold_met:
            return result
        else:
            # This is an exception case, but note that we attach the result
            # to the exception context so that it can be used for
            # troubleshooting.
            raise with_context(
                exc=BadApiResponse(
                    'Accumulated balance {balance} is less than threshold {threshold} '
                    '(``exc.context`` contains more information).'.format(
                        threshold=threshold,
                        balance=result['totalBalance'],
                    ), ),
                context={
                    'inputs': result['inputs'],
                    'request': request,
                    'total_balance': result['totalBalance'],
                },
            )
예제 #18
0
class FindTransactionsRequestFilterTestCase(BaseFilterTestCase):
    filter_type = FindTransactionsCommand(MockAdapter()).get_request_filter
    print_var_type_n_val(var001=filter_type,
                         pointer="#ZERTsdfgrd1234jh")  #ZERTsdfgrd1234jh
    # Value:
    # # <bound method FindTransactionsCommand.get_request_filter of <iota.commands.core.find_transactions.FindTransactionsCommand object at 0x0000021C318C8A58>>

    # Type: <class 'method'>
    skip_value_check = True

    # noinspection SpellCheckingInspection
    def setUp(self):
        super(FindTransactionsRequestFilterTestCase, self).setUp()

        # Define a few valid values that we can reuse across tests.
        self.trytes1 = 'RBTC9D9DCDQAEASBYBCCKBFA'
        self.trytes2 =\
          'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA'
        self.trytes3 = '999999999999999999999999999'

    def test_pass_all_parameters(self):
        """
    The request contains valid values for all parameters.
    """
        # Raw trytes are extracted to match the IRI's JSON protocol.
        request = {
            'bundles': [
                text_type(
                    TransactionHash(self.trytes1)
                    # # self.trytes1 = 'RBTC9D9DCDQAEASBYBCCKBFA'
                ),
                text_type(TransactionHash(self.trytes2)),
            ],
            'addresses': [
                text_type(Address(self.trytes1)),
                text_type(Address(self.trytes2)),
            ],
            'tags': [
                text_type(Tag(self.trytes1)),
                text_type(
                    Tag(self.trytes3)
                    # # self.trytes3 = '999999999999999999999999999'
                ),
            ],
            'approvees': [
                text_type(TransactionHash(self.trytes1)),
                text_type(TransactionHash(self.trytes3)),
            ],
        }
        print_var_type_n_val(
            var001=request,
            pointer="#XXXXCVBVCZZZzzzer12345")  #XXXXCVBVCZZZzzzer12345
        # Value:
        # # {'bundles': ['RBTC9D9DCDQAEASBYBCCKBFA999999999999999999999999999999999999999999999999999999999', 'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA99999999999999999999999'], 'addresses': ['RBTC9D9DCDQAEASBYBCCKBFA999999999999999999999999999999999999999999999999999999999', 'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA99999999999999999999999'], 'tags': ['RBTC9D9DCDQAEASBYBCCKBFA999', '999999999999999999999999999'], 'approvees': ['RBTC9D9DCDQAEASBYBCCKBFA999999999999999999999999999999999999999999999999999999999', '999999999999999999999999999999999999999999999999999999999999999999999999999999999']}

        # Type: <class 'dict'>
        filter_ = self._filter(request)  #here001
        print_var_type_n_val(var001=filter_,
                             pointer="#WXCVRDFGTFGHV")  #WXCVRDFGTFGHV
        # Value:
        # # FindTransactionsRequestFilter(FilterChain(Type(Mapping, allow_subclass=True) | FilterMapper(addresses=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | AddressNoChecksum() | Unicode(encoding='ascii')))), approvees=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | Trytes() | Unicode(encoding='ascii')))), bundles=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | Trytes() | Unicode(encoding='ascii')))), tags=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | Trytes() | Unicode(encoding='ascii')))))))

        # Type: <class 'filters.handlers.FilterRunner'>
        self.assertFilterPasses(filter_)
        self.assertDictEqual(filter_.cleaned_data, request)
        # print('filter_.cleaned_data: ', filter_.cleaned_data)
        # # {'tags': ['RBTC9D9DCDQAEASBYBCCKBFA999', '999999999999999999999999999']}

    def test_pass_compatible_types(self):
        """
    The request contains values that can be converted to the expected
    types.
    """
        filter_ = self._filter({
            'bundles': [
                self.trytes1.encode('ascii'),
                TransactionHash(self.trytes2),
            ],
            'addresses': [
                self.trytes1.encode('ascii'),
                Address(self.trytes2),
            ],
            'tags': [
                self.trytes1.encode('ascii'),
                Tag(self.trytes3),
            ],
            'approvees': [
                self.trytes1.encode('ascii'),
                TransactionHash(self.trytes3),
            ],
        })
        print_var_type_n_val(
            var001=filter_,
            pointer="#WXCVBFGrtrer12349999")  #WXCVBFGrtrer12349999
        # Value:
        # # FindTransactionsRequestFilter(FilterChain(Type(Mapping, allow_subclass=True) | FilterMapper(addresses=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | AddressNoChecksum() | Unicode(encoding='ascii')))), approvees=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | Trytes() | Unicode(encoding='ascii')))), bundles=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | Trytes() | Unicode(encoding='ascii')))), tags=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | Trytes() | Unicode(encoding='ascii')))))))

        # Type: <class 'filters.handlers.FilterRunner'>
        self.assertFilterPasses(filter_)
        # to_find: where is the command above is being created
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                # Raw trytes are extracted to match the IRI's JSON protocol.
                'bundles': [
                    text_type(TransactionHash(self.trytes1)),
                    text_type(TransactionHash(self.trytes2)),
                ],
                'addresses': [
                    text_type(Address(self.trytes1)),
                    text_type(Address(self.trytes2)),
                ],
                'tags': [
                    text_type(Tag(self.trytes1)),
                    text_type(Tag(self.trytes3)),
                ],
                'approvees': [
                    text_type(TransactionHash(self.trytes1)),
                    text_type(TransactionHash(self.trytes3)),
                ],
            },
        )

    def test_pass_bundles_only(self):
        """
    The request only includes bundles.
    """
        request = {
            'bundles': [
                TransactionHash(self.trytes1),
                TransactionHash(self.trytes2),
            ],
        }

        filter_ = self._filter(request)
        print_var_type_n_val(var001=filter_,
                             pointer="#5555ttyhgffbbvcvbaazezZZZSSS"
                             )  #5555ttyhgffbbvcvbaazezZZZSSS
        # Value:
        # # FindTransactionsRequestFilter(FilterChain(Type(Mapping, allow_subclass=True) | FilterMapper(addresses=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | AddressNoChecksum() | Unicode(encoding='ascii')))), approvees=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | Trytes() | Unicode(encoding='ascii')))), bundles=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | Trytes() | Unicode(encoding='ascii')))), tags=FilterChain(Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | Trytes() | Unicode(encoding='ascii')))))))

        # Type: <class 'filters.handlers.FilterRunner'>

        self.assertFilterPasses(filter_)

        print_var_type_n_val(
            var001=text_type(TransactionHash(self.trytes1)),
            pointer="#4543ERTGF555666aaaqsCE")  #4543ERTGF555666aaaqsCE
        # Value: RBTC9D9DCDQAEASBYBCCKBFA999999999999999999999999999999999999999999999999999999999
        # Type: <class 'str'>
        print_var_type_n_val(var001=TransactionHash(self.trytes1),
                             pointer="#XCEDFGtrefg654")  #XCEDFGtrefg654
        # Value: RBTC9D9DCDQAEASBYBCCKBFA999999999999999999999999999999999999999999999999999999999
        # Type: <class 'iota.transaction.types.TransactionHash'>
        print_var_type_n_val(var001=self.trytes1,
                             pointer="#XCVDFFERRRTdd4544")  #XCVDFFERRRTdd4544
        # Value: RBTC9D9DCDQAEASBYBCCKBFA
        # Type: <class 'str'>

        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'bundles': [
                    text_type(TransactionHash(self.trytes1)),
                    text_type(TransactionHash(self.trytes2)),
                ],

                # Null criteria are not included in the request.
                # https://github.com/iotaledger/iota.lib.py/issues/96
                # 'addresses':  [],
                # 'approvees':  [],
                # 'tags':       [],
            },
        )

    def test_pass_addresses_only(self):
        """
    The request only includes addresses.
    """

        request = {
            'addresses': [
                Address(self.trytes1),
                # print("type(Address(self.trytes1)): ", type(Address(self.trytes1)))
                # # <class 'iota.types.Address'>
                # print('Address(self.trytes1): ', Address(self.trytes1))
                # # RBTC9D9DCDQAEASBYBCCKBFA999999999999999999999999999999999999999999999999999999999
                Address(self.trytes2),
            ],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'addresses': [
                    text_type(Address(self.trytes1)),
                    # print("type(Address(self.trytes1)): ", type(Address(self.trytes1)))
                    # # <class 'iota.types.Address'>
                    # print('Address(self.trytes1): ', Address(self.trytes1))
                    # # RBTC9D9DCDQAEASBYBCCKBFA999999999999999999999999999999999999999999999999999999999
                    text_type(Address(self.trytes2)),
                ],

                # Null criteria are not included in the request.
                # https://github.com/iotaledger/iota.lib.py/issues/96
                # 'approvees':  [],
                # 'bundles':    [],
                # 'tags':       [],
            },
        )

    def test_pass_tags_only(self):
        """
    The request only includes tags.
    """
        # print("type(Tag(self.trytes1)): ", type(Tag(self.trytes1)))
        # # <class 'iota.types.Tag'>
        # print('Tag(self.trytes1): ', Tag(self.trytes1))
        # # RBTC9D9DCDQAEASBYBCCKBFA999
        request = {
            'tags': [
                Tag(self.trytes1),
                Tag(self.trytes3),
            ],
        }

        filter_ = self._filter(request)
        # print('filter_.cleaned_data: ', filter_.cleaned_data)
        # # {'tags': ['RBTC9D9DCDQAEASBYBCCKBFA999', '999999999999999999999999999']}

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'tags': [
                    text_type(Tag(self.trytes1)),
                    text_type(Tag(self.trytes3)),
                ],

                # Null criteria are not included in the request.
                # https://github.com/iotaledger/iota.lib.py/issues/96
                # 'addresses':  [],
                # 'approvees':  [],
                # 'bundles':    [],
            },
        )

    def test_pass_approvees_only(self):
        """
    The request only includes approvees.
    """
        request = {
            'approvees': [
                TransactionHash(self.trytes1),
                TransactionHash(self.trytes3),
            ],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'approvees': [
                    text_type(TransactionHash(self.trytes1)),
                    text_type(TransactionHash(self.trytes3)),
                ],

                # Null criteria are not included in the request.
                # https://github.com/iotaledger/iota.lib.py/issues/96
                # 'addresses':  [],
                # 'bundles':    [],
                # 'tags':       [],
            },
        )

    def test_fail_empty(self):
        """
    The request does not contain any parameters.
    """
        self.assertFilterErrors(
            {},
            {
                '': [FindTransactionsRequestFilter.CODE_NO_SEARCH_VALUES],
            },
        )

    def test_fail_all_parameters_null(self):
        """
    The request contains all parameters, but every one is null.
    """
        self.assertFilterErrors(
            {
                'addresses': None,
                'approvees': None,
                'bundles': None,
                'tags': None,
            },
            {
                '': [FindTransactionsRequestFilter.CODE_NO_SEARCH_VALUES],
            },
        )

    def test_success_all_parameters_empty(self):
        """
    All of the parameters are empty lists.

    This is technically valid, though probably not very useful.
    """
        self.assertFilterPasses(
            {
                'addresses': [],
                'approvees': [],
                'bundles': [],
                'tags': [],
            }, )

    def test_fail_unexpected_parameters(self):
        """
    The request contains unexpected parameters.
    """
        self.assertFilterErrors(
            {
                'addresses': [Address(self.trytes1)],
                'approvees': [TransactionHash(self.trytes1)],
                'bundles': [TransactionHash(self.trytes1)],
                'tags': [Tag(self.trytes1)],

                # Hey, you're not allowed in he-argh!
                'foo': 'bar',
            },
            {
                'foo': [f.FilterMapper.CODE_EXTRA_KEY],
            },
        )

    def test_fail_bundles_wrong_type(self):
        """
    ``bundles`` is not an array.
    """
        self.assertFilterErrors(
            {
                'bundles': TransactionHash(self.trytes1),
            },
            {
                'bundles': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_bundles_contents_invalid(self):
        """
    ``bundles`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'bundles': [
                    b'',
                    True,
                    None,
                    b'not valid trytes',

                    # This is actually valid; I just added it to make sure the
                    # filter isn't cheating!
                    TryteString(self.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                'bundles.0': [f.Required.CODE_EMPTY],
                'bundles.1': [f.Type.CODE_WRONG_TYPE],
                'bundles.2': [f.Required.CODE_EMPTY],
                'bundles.3': [Trytes.CODE_NOT_TRYTES],
                'bundles.5': [f.Type.CODE_WRONG_TYPE],
                'bundles.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )

    def test_fail_addresses_wrong_type(self):
        """
    ``addresses`` is not an array.
    """
        self.assertFilterErrors(
            {
                'addresses': Address(self.trytes1),
            },
            {
                'addresses': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_addresses_contents_invalid(self):
        """
    ``addresses`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'addresses': [
                    b'',
                    True,
                    None,
                    b'not valid trytes',

                    # This is actually valid; I just added it to make sure the
                    # filter isn't cheating!
                    TryteString(self.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                'addresses.0': [f.Required.CODE_EMPTY],
                'addresses.1': [f.Type.CODE_WRONG_TYPE],
                'addresses.2': [f.Required.CODE_EMPTY],
                'addresses.3': [Trytes.CODE_NOT_TRYTES],
                'addresses.5': [f.Type.CODE_WRONG_TYPE],
                'addresses.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )

    def test_fail_tags_wrong_type(self):
        """
    ``tags`` is not an array.
    """
        self.assertFilterErrors(
            {
                'tags': Tag(self.trytes1),
            },
            {
                'tags': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_tags_contents_invalid(self):
        """
    ``tags`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'tags': [
                    b'',
                    True,
                    None,
                    b'not valid trytes',

                    # This is actually valid; I just added it to make sure the
                    # filter isn't cheating!
                    TryteString(self.trytes1),
                    2130706433,
                    b'9' * 28,
                ],
            },
            {
                'tags.0': [f.Required.CODE_EMPTY],
                'tags.1': [f.Type.CODE_WRONG_TYPE],
                'tags.2': [f.Required.CODE_EMPTY],
                'tags.3': [Trytes.CODE_NOT_TRYTES],
                'tags.5': [f.Type.CODE_WRONG_TYPE],
                'tags.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )

    def test_fail_approvees_wrong_type(self):
        """
    ``approvees`` is not an array.
    """
        self.assertFilterErrors(
            {
                'approvees': TransactionHash(self.trytes1),
            },
            {
                'approvees': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_approvees_contents_invalid(self):
        """
    ``approvees`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'approvees': [
                    b'',
                    True,
                    None,
                    b'not valid trytes',

                    # This is actually valid; I just added it to make sure the
                    # filter isn't cheating!
                    TryteString(self.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                'approvees.0': [f.Required.CODE_EMPTY],
                'approvees.1': [f.Type.CODE_WRONG_TYPE],
                'approvees.2': [f.Required.CODE_EMPTY],
                'approvees.3': [Trytes.CODE_NOT_TRYTES],
                'approvees.5': [f.Type.CODE_WRONG_TYPE],
                'approvees.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )
예제 #19
0
class FindTransactionsRequestFilterTestCase(BaseFilterTestCase):
  filter_type = FindTransactionsCommand(MockAdapter()).get_request_filter
  skip_value_check = True

  # noinspection SpellCheckingInspection
  def setUp(self):
    super(FindTransactionsRequestFilterTestCase, self).setUp()

    # Define a few valid values that we can reuse across tests.
    self.trytes1 = b'RBTC9D9DCDQAEASBYBCCKBFA'
    self.trytes2 =\
      b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA'
    self.trytes3 = b'999999999999999999999999999'

  def test_pass_all_parameters(self):
    """
    The request contains valid values for all parameters.
    """
    request = {
      'bundles': [
        TransactionHash(self.trytes1),
        TransactionHash(self.trytes2),
      ],

      'addresses': [
        Address(self.trytes1),
        Address(self.trytes2),
      ],

      'tags': [
        Tag(self.trytes1),
        Tag(self.trytes3),
      ],

      'approvees': [
        TransactionHash(self.trytes1),
        TransactionHash(self.trytes3),
      ],
    }

    filter_ = self._filter(request)

    self.assertFilterPasses(filter_)
    self.assertDictEqual(filter_.cleaned_data, request)

  def test_pass_compatible_types(self):
    """
    The request contains values that can be converted to the expected
    types.
    """
    filter_ = self._filter({
      'bundles': [
        binary_type(self.trytes1),
        bytearray(self.trytes2),
      ],

      'addresses': [
        binary_type(self.trytes1),
        bytearray(self.trytes2),
      ],

      'tags': [
        binary_type(self.trytes1),
        bytearray(self.trytes3),
      ],

      'approvees': [
        binary_type(self.trytes1),
        bytearray(self.trytes3),
      ],
    })

    self.assertFilterPasses(filter_)
    self.assertDictEqual(
      filter_.cleaned_data,

      {
        'bundles': [
          TransactionHash(self.trytes1),
          TransactionHash(self.trytes2),
        ],

        'addresses': [
          Address(self.trytes1),
          Address(self.trytes2),
        ],

        'tags': [
          Tag(self.trytes1),
          Tag(self.trytes3),
        ],

        'approvees': [
          TransactionHash(self.trytes1),
          TransactionHash(self.trytes3),
        ],
      },
    )

  def test_pass_bundles_only(self):
    """
    The request only includes bundles.
    """
    request = {
      'bundles': [
        TransactionHash(self.trytes1),
        TransactionHash(self.trytes2),
      ],
    }

    filter_ = self._filter(request)

    self.assertFilterPasses(filter_)
    self.assertDictEqual(
      filter_.cleaned_data,

      {
        'bundles': [
          TransactionHash(self.trytes1),
          TransactionHash(self.trytes2),
        ],

        'addresses':  [],
        'approvees':  [],
        'tags':       [],
      },
    )

  def test_pass_addresses_only(self):
    """
    The request only includes addresses.
    """
    request = {
      'addresses': [
        Address(self.trytes1),
        Address(self.trytes2),
      ],
    }

    filter_ = self._filter(request)

    self.assertFilterPasses(filter_)
    self.assertDictEqual(
      filter_.cleaned_data,

      {
        'addresses': [
          Address(self.trytes1),
          Address(self.trytes2),
        ],

        'approvees':  [],
        'bundles':    [],
        'tags':       [],
      },
    )

  def test_pass_tags_only(self):
    """
    The request only includes tags.
    """
    request = {
      'tags': [
        Tag(self.trytes1),
        Tag(self.trytes3),
      ],
    }

    filter_ = self._filter(request)

    self.assertFilterPasses(filter_)
    self.assertDictEqual(
      filter_.cleaned_data,

      {
        'tags': [
          Tag(self.trytes1),
          Tag(self.trytes3),
        ],

        'addresses':  [],
        'approvees':  [],
        'bundles':    [],
      },
    )

  def test_pass_approvees_only(self):
    """
    The request only includes approvees.
    """
    request = {
      'approvees': [
        TransactionHash(self.trytes1),
        TransactionHash(self.trytes3),
      ],
    }

    filter_ = self._filter(request)

    self.assertFilterPasses(filter_)
    self.assertDictEqual(
      filter_.cleaned_data,

      {
        'approvees': [
          TransactionHash(self.trytes1),
          TransactionHash(self.trytes3),
        ],

        'addresses':  [],
        'bundles':    [],
        'tags':       [],
      },
    )

  def test_fail_empty(self):
    """
    The request does not contain any parameters.
    """
    self.assertFilterErrors(
      {},

      {
        '': [FindTransactionsRequestFilter.CODE_NO_SEARCH_VALUES],
      },
    )

  def test_fail_all_parameters_empty(self):
    """
    The request contains all parameters, but every one is empty.
    """
    self.assertFilterErrors(
      {
        'addresses':  [],
        'approvees':  [],
        'bundles':    [],
        'tags':       [],
      },

      {
        '': [FindTransactionsRequestFilter.CODE_NO_SEARCH_VALUES],
      },
    )

  def test_fail_unexpected_parameters(self):
    """
    The request contains unexpected parameters.
    """
    self.assertFilterErrors(
      {
        'addresses':  [Address(self.trytes1)],
        'approvees':  [TransactionHash(self.trytes1)],
        'bundles':    [TransactionHash(self.trytes1)],
        'tags':       [Tag(self.trytes1)],

        # Hey, you're not allowed in he-argh!
        'foo': 'bar',
      },

      {
        'foo': [f.FilterMapper.CODE_EXTRA_KEY],
      },
    )

  def test_fail_bundles_wrong_type(self):
    """
    ``bundles`` is not an array.
    """
    self.assertFilterErrors(
      {
        'bundles': TransactionHash(self.trytes1),
      },

      {
        'bundles': [f.Type.CODE_WRONG_TYPE],
      },
    )

  def test_fail_bundles_contents_invalid(self):
    """
    ``bundles`` is an array, but it contains invalid values.
    """
    self.assertFilterErrors(
      {
        'bundles': [
          b'',
          text_type(self.trytes1, 'ascii'),
          True,
          None,
          b'not valid trytes',

          # This is actually valid; I just added it to make sure the
          # filter isn't cheating!
          TryteString(self.trytes2),

          2130706433,
          b'9' * 82,
        ],
      },

      {
        'bundles.0':  [f.Required.CODE_EMPTY],
        'bundles.1':  [f.Type.CODE_WRONG_TYPE],
        'bundles.2':  [f.Type.CODE_WRONG_TYPE],
        'bundles.3':  [f.Required.CODE_EMPTY],
        'bundles.4':  [Trytes.CODE_NOT_TRYTES],
        'bundles.6':  [f.Type.CODE_WRONG_TYPE],
        'bundles.7':  [Trytes.CODE_WRONG_FORMAT],
      },
    )

  def test_fail_addresses_wrong_type(self):
    """
    ``addresses`` is not an array.
    """
    self.assertFilterErrors(
      {
        'addresses':  Address(self.trytes1),
      },

      {
        'addresses': [f.Type.CODE_WRONG_TYPE],
      },
    )

  def test_fail_addresses_contents_invalid(self):
    """
    ``addresses`` is an array, but it contains invalid values.
    """
    self.assertFilterErrors(
      {
        'addresses': [
          b'',
          text_type(self.trytes1, 'ascii'),
          True,
          None,
          b'not valid trytes',

          # This is actually valid; I just added it to make sure the
          # filter isn't cheating!
          TryteString(self.trytes2),

          2130706433,
          b'9' * 82,
        ],
      },

      {
        'addresses.0':  [f.Required.CODE_EMPTY],
        'addresses.1':  [f.Type.CODE_WRONG_TYPE],
        'addresses.2':  [f.Type.CODE_WRONG_TYPE],
        'addresses.3':  [f.Required.CODE_EMPTY],
        'addresses.4':  [Trytes.CODE_NOT_TRYTES],
        'addresses.6':  [f.Type.CODE_WRONG_TYPE],
        'addresses.7':  [Trytes.CODE_WRONG_FORMAT],
      },
    )

  def test_fail_tags_wrong_type(self):
    """
    ``tags`` is not an array.
    """
    self.assertFilterErrors(
      {
        'tags':  Tag(self.trytes1),
      },

      {
        'tags': [f.Type.CODE_WRONG_TYPE],
      },
    )

  def test_fail_tags_contents_invalid(self):
    """
    ``tags`` is an array, but it contains invalid values.
    """
    self.assertFilterErrors(
      {
        'tags': [
          b'',
          text_type(self.trytes1, 'ascii'),
          True,
          None,
          b'not valid trytes',

          # This is actually valid; I just added it to make sure the
          # filter isn't cheating!
          TryteString(self.trytes1),

          2130706433,
          b'9' * 28,
        ],
      },

      {
        'tags.0':  [f.Required.CODE_EMPTY],
        'tags.1':  [f.Type.CODE_WRONG_TYPE],
        'tags.2':  [f.Type.CODE_WRONG_TYPE],
        'tags.3':  [f.Required.CODE_EMPTY],
        'tags.4':  [Trytes.CODE_NOT_TRYTES],
        'tags.6':  [f.Type.CODE_WRONG_TYPE],
        'tags.7':  [Trytes.CODE_WRONG_FORMAT],
      },
    )

  def test_fail_approvees_wrong_type(self):
    """
    ``approvees`` is not an array.
    """
    self.assertFilterErrors(
      {
        'approvees': TransactionHash(self.trytes1),
      },

      {
        'approvees': [f.Type.CODE_WRONG_TYPE],
      },
    )

  def test_fail_approvees_contents_invalid(self):
    """
    ``approvees`` is an array, but it contains invalid values.
    """
    self.assertFilterErrors(
      {
        'approvees': [
          b'',
          text_type(self.trytes1, 'ascii'),
          True,
          None,
          b'not valid trytes',

          # This is actually valid; I just added it to make sure the
          # filter isn't cheating!
          TryteString(self.trytes2),

          2130706433,
          b'9' * 82,
        ],
      },

      {
        'approvees.0':  [f.Required.CODE_EMPTY],
        'approvees.1':  [f.Type.CODE_WRONG_TYPE],
        'approvees.2':  [f.Type.CODE_WRONG_TYPE],
        'approvees.3':  [f.Required.CODE_EMPTY],
        'approvees.4':  [Trytes.CODE_NOT_TRYTES],
        'approvees.6':  [f.Type.CODE_WRONG_TYPE],
        'approvees.7':  [Trytes.CODE_WRONG_FORMAT],
      },
    )