コード例 #1
0
class GetTrytesResponseFilter(BaseFilterTestCase):
    filter_type = GetTrytesCommand(MockAdapter()).get_response_filter
    skip_value_check = True

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

        # Define some valid tryte sequences that we can re-use between
        # tests.
        self.trytes1 = 'RBTC9D9DCDQAEASBYBCCKBFA'
        self.trytes2 = \
            'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA'

    def test_pass_transactions(self):
        """
    The response contains data for multiple transactions.
    """
        filter_ = self._filter({
            'trytes': [
                # In real life, these values would be a lot longer, but for the
                # purposes of this test, any sequence of trytes will do.
                self.trytes1,
                self.trytes2,
            ],

            'duration': 42,
        })

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

            {
                'trytes': [
                    TryteString(self.trytes1),
                    TryteString(self.trytes2),
                ],

                'duration': 42,
            },
        )

    def test_pass_no_transactions(self):
        """
    The response does not contain any transactions.
    """
        response = {
            'trytes': [],
            'duration': 42,
        }

        filter_ = self._filter(response)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(filter_.cleaned_data, response)
コード例 #2
0
ファイル: utils.py プロジェクト: grendias/iota.lib.py
def find_transaction_objects(adapter, **kwargs):
    # type: (BaseAdapter, dict) -> 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 []
コード例 #3
0
ファイル: get_bundles.py プロジェクト: jinankjain/iota.lib.py
  def _traverse_bundle(self, txn_hash, target_bundle_hash=None):
    # type: (TransactionHash, Optional[BundleHash]) -> List[Transaction]
    """
    Recursively traverse the Tangle, collecting transactions until we
    hit a new bundle.

    This method is (usually) faster than ``findTransactions``, and it
    ensures we don't collect transactions from replayed bundles.
    """
    trytes = GetTrytesCommand(self.adapter)(hashes=[txn_hash])['trytes'] # type: List[TryteString]

    if not trytes:
      raise with_context(
        exc = BadApiResponse(
          'Bundle transactions not visible (``exc.context`` has more info).',
        ),

        context = {
          'transaction_hash':   txn_hash,
          'target_bundle_hash': target_bundle_hash,
        },
      )

    transaction = Transaction.from_tryte_string(trytes[0])

    if (not target_bundle_hash) and transaction.current_index:
      raise with_context(
        exc = BadApiResponse(
          '``_traverse_bundle`` started with a non-tail transaction '
          '(``exc.context`` has more info).',
        ),

        context = {
          'transaction_object': transaction,
          'target_bundle_hash': target_bundle_hash,
        },
      )

    if target_bundle_hash:
      if target_bundle_hash != transaction.bundle_hash:
        # We've hit a different bundle; we can stop now.
        return []
    else:
      target_bundle_hash = transaction.bundle_hash

    if transaction.current_index == transaction.last_index == 0:
      # Bundle only has one transaction.
      return [transaction]

    # Recursively follow the trunk transaction, to fetch the next
    # transaction in the bundle.
    return [transaction] + self._traverse_bundle(
      txn_hash            = transaction.trunk_transaction_hash,
      target_bundle_hash  = target_bundle_hash
    )
コード例 #4
0
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 []
コード例 #5
0
    def _traverse_bundle(self, txn_hash, target_bundle_hash):
        """
        Recursively traverse the Tangle, collecting transactions until
        we hit a new bundle.

        This method is (usually) faster than ``findTransactions``, and
        it ensures we don't collect transactions from replayed bundles.
        """
        trytes = (GetTrytesCommand(self.adapter)(hashes=[txn_hash])['trytes']
                  )  # type: List[TryteString]

        # If no tx was found by the node for txn_hash, it returns 9s,
        # so we check here if it returned all 9s trytes.
        if not trytes or trytes == [TransactionTrytes('')]:
            raise with_context(
                exc=BadApiResponse(
                    'Could not get trytes of bundle transaction from the Tangle. '
                    'Bundle transactions not visible.'
                    '(``exc.context`` has more info).', ),
                context={
                    'transaction_hash': txn_hash,
                    'target_bundle_hash': target_bundle_hash,
                },
            )

        transaction = Transaction.from_tryte_string(trytes[0])

        if (not target_bundle_hash) and transaction.current_index:
            raise with_context(
                exc=BadApiResponse(
                    '``_traverse_bundle`` started with a non-tail transaction '
                    '(``exc.context`` has more info).', ),
                context={
                    'transaction_object': transaction,
                    'target_bundle_hash': target_bundle_hash,
                },
            )

        if target_bundle_hash:
            if target_bundle_hash != transaction.bundle_hash:
                # We've hit a different bundle; we can stop now.
                return []
        else:
            target_bundle_hash = transaction.bundle_hash

        if transaction.current_index == transaction.last_index == 0:
            # Bundle only has one transaction.
            return [transaction]

        # Recursively follow the trunk transaction, to fetch the next
        # transaction in the bundle.
        return [transaction] + self._traverse_bundle(
            transaction.trunk_transaction_hash, target_bundle_hash)
コード例 #6
0
def get_bundles_from_transaction_hashes(
    adapter,
    transaction_hashes,
    inclusion_states,
):
    # type: (BaseAdapter, Iterable[TransactionHash], bool) -> List[Bundle]
    """
    Given a set of transaction hashes, returns the corresponding bundles,
    sorted by tail transaction timestamp.
    """
    transaction_hashes = list(transaction_hashes)
    if not transaction_hashes:
        return []

    my_bundles = []  # type: List[Bundle]

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

    gt_response = GetTrytesCommand(adapter)(hashes=transaction_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 find_transaction_objects(
                adapter=adapter,
                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(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(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

        my_bundles.extend(txn_bundles)

    return list(
        sorted(
            my_bundles,
            key=lambda bundle_: bundle_.tail_transaction.timestamp,
        ))
コード例 #7
0
class GetTrytesRequestFilterTestCase(BaseFilterTestCase):
    filter_type = GetTrytesCommand(MockAdapter()).get_request_filter
    skip_value_check = True

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

        # Define some valid tryte sequences that we can re-use between
        # tests.
        self.trytes1 = (
            'TESTVALUE9DONTUSEINPRODUCTION99999DLPDTB'
            'XBXYOMQ9IWPKCDPNWBENBGHCSZDLRLZZ9VZEOHPLC'
        )

        self.trytes2 = (
            'TESTVALUE9DONTUSEINPRODUCTION99999HEXCAN'
            'LFTVWRDZJHDWJGVOOUWBXAHKVWDNNOCICGXXBKAEN'
        )

    def test_pass_happy_path(self):
        """
    The request is valid.
    """
        request = {
            # Raw trytes are extracted to match the IRI's JSON protocol.
            'hashes': [self.trytes1, self.trytes2],
        }

        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({
            # Any sequence that can be converted into an ASCII representation
            # of a TransactionHash is valid.
            'hashes': [
                TransactionHash(self.trytes1),
                bytearray(self.trytes2.encode('ascii')),
            ],
        })

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

            {
                'hashes': [self.trytes1, self.trytes2],
            },
        )

    def test_fail_empty(self):
        """
    The request is empty.
    """
        self.assertFilterErrors(
            {},

            {
                'hashes': [f.FilterMapper.CODE_MISSING_KEY],
            },
        )

    def test_fail_unexpected_parameters(self):
        """
    The request contains unexpected parameters.
    """
        self.assertFilterErrors(
            {
                'hashes': [TransactionHash(self.trytes1)],

                # This is why we can't have nice things!
                'foo': 'bar',
            },

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

    def test_fail_hashes_null(self):
        """
    ``hashes`` is null.
    """
        self.assertFilterErrors(
            {
                'hashes': None,
            },

            {
                'hashes': [f.Required.CODE_EMPTY],
            },
        )

    def test_fail_hashes_wrong_type(self):
        """
    ``hashes`` is not an array.
    """
        self.assertFilterErrors(
            {
                # ``hashes`` must be an array, even if we're only querying
                # against a single transaction.
                'hashes': TransactionHash(self.trytes1),
            },

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

    def test_fail_hashes_empty(self):
        """
    ``hashes`` is an array, but it is empty.
    """
        self.assertFilterErrors(
            {
                'hashes': [],
            },

            {
                'hashes': [f.Required.CODE_EMPTY],
            },
        )

    def test_fail_hashes_contents_invalid(self):
        """
    ``hashes`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'hashes': [
                    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' * 82,
                ],
            },

            {
                'hashes.0': [f.Required.CODE_EMPTY],
                'hashes.1': [f.Type.CODE_WRONG_TYPE],
                'hashes.2': [f.Required.CODE_EMPTY],
                'hashes.3': [Trytes.CODE_NOT_TRYTES],
                'hashes.5': [f.Type.CODE_WRONG_TYPE],
                'hashes.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )
コード例 #8
0
class GetTrytesRequestFilterTestCase(BaseFilterTestCase):
    filter_type = GetTrytesCommand(MockAdapter()).get_request_filter
    skip_value_check = True

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

        # Define some valid tryte sequences that we can re-use between
        # tests.
        self.trytes1 = (b'OAATQS9VQLSXCLDJVJJVYUGONXAXOFMJOZNSYWRZ'
                        b'SWECMXAQQURHQBJNLD9IOFEPGZEPEMPXCIVRX9999')

        self.trytes2 = (b'ZIJGAJ9AADLRPWNCYNNHUHRRAC9QOUDATEDQUMTN'
                        b'OTABUVRPTSTFQDGZKFYUUIE9ZEBIVCCXXXLKX9999')

    def test_pass_happy_path(self):
        """
    The request is valid.
    """
        request = {
            'hashes': [
                TransactionHash(self.trytes1),
                TransactionHash(self.trytes2),
            ],
        }

        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({
            'hashes': [
                # Any sequence that can be converted into a TransactionHash is
                # valid.
                binary_type(self.trytes1),
                bytearray(self.trytes2),
            ],
        })

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'hashes': [
                    TransactionHash(self.trytes1),
                    TransactionHash(self.trytes2),
                ],
            },
        )

    def test_fail_empty(self):
        """
    The request is empty.
    """
        self.assertFilterErrors(
            {},
            {
                'hashes': [f.FilterMapper.CODE_MISSING_KEY],
            },
        )

    def test_fail_unexpected_parameters(self):
        """
    The request contains unexpected parameters.
    """
        self.assertFilterErrors(
            {
                'hashes': [TransactionHash(self.trytes1)],

                # This is why we can't have nice things!
                'foo': 'bar',
            },
            {
                'foo': [f.FilterMapper.CODE_EXTRA_KEY],
            },
        )

    def test_fail_hashes_null(self):
        """
    ``hashes`` is null.
    """
        self.assertFilterErrors(
            {
                'hashes': None,
            },
            {
                'hashes': [f.Required.CODE_EMPTY],
            },
        )

    def test_fail_hashes_wrong_type(self):
        """
    ``hashes`` is not an array.
    """
        self.assertFilterErrors(
            {
                # ``hashes`` must be an array, even if we're only querying
                # against a single transaction.
                'hashes': TransactionHash(self.trytes1),
            },
            {
                'hashes': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_hashes_empty(self):
        """
    ``hashes`` is an array, but it is empty.
    """
        self.assertFilterErrors(
            {
                'hashes': [],
            },
            {
                'hashes': [f.Required.CODE_EMPTY],
            },
        )

    def test_fail_hashes_contents_invalid(self):
        """
    ``hashes`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'hashes': [
                    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' * 82,
                ],
            },
            {
                'hashes.0': [f.Required.CODE_EMPTY],
                'hashes.1': [f.Type.CODE_WRONG_TYPE],
                'hashes.2': [f.Type.CODE_WRONG_TYPE],
                'hashes.3': [f.Required.CODE_EMPTY],
                'hashes.4': [Trytes.CODE_NOT_TRYTES],
                'hashes.6': [f.Type.CODE_WRONG_TYPE],
                'hashes.7': [Trytes.CODE_WRONG_FORMAT],
            },
        )
コード例 #9
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,
                )),
        }