示例#1
0
 def _execute(self, request):
     # Given tail hash, fetches the bundle from the tangle
     # and validates it.
     # Returns List[List[TransactionTrytes]]
     # (outer list has one item in current implementation)
     bundle = GetBundlesCommand(
         self.adapter)(transactions=[request['tail_hash']])
     BroadcastTransactionsCommand(self.adapter)(trytes=bundle[0])
     return {
         'trytes': bundle[0],
     }
示例#2
0
    def _execute(self, request):
        depth = request['depth']  # type: int
        min_weight_magnitude = request['minWeightMagnitude']  # type: int
        transaction = request['transaction']  # type: TransactionHash

        gb_response = GetBundlesCommand(self.adapter)(transaction=transaction)

        # Note that we only replay the first bundle returned by
        # ``getBundles``.
        bundle = gb_response['bundles'][0]  # type: Bundle

        return SendTrytesCommand(self.adapter)(
            depth=depth,
            minWeightMagnitude=min_weight_magnitude,
            trytes=bundle.as_tryte_strings(),
        )
示例#3
0
class GetBundlesRequestFilterTestCase(BaseFilterTestCase):
    filter_type = GetBundlesCommand(MockAdapter()).get_request_filter
    skip_value_check = True

    def setUp(self):
        super(GetBundlesRequestFilterTestCase, self).setUp()

        # noinspection SpellCheckingInspection
        self.transaction = ('TESTVALUE9DONTUSEINPRODUCTION99999KPZOTR'
                            'VDB9GZDJGZSSDCBIX9QOK9PAV9RMDBGDXLDTIZTWQ')

    def test_pass_happy_path(self):
        """
    Request is valid.
    """
        # Raw trytes are extracted to match the IRI's JSON protocol.
        request = {
            'transaction': self.transaction,
        }

        filter_ = self._filter(request)

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

    def test_pass_compatible_types(self):
        """
    Request contains values that can be converted to the expected
    types.
    """
        filter_ = self._filter({
            # Any TrytesCompatible value will work here.
            'transaction':
            TransactionHash(self.transaction),
        })

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'transaction': self.transaction,
            },
        )

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

    def test_fail_unexpected_parameters(self):
        """
    Request contains unexpected parameters.
    """
        self.assertFilterErrors(
            {
                'transaction': TransactionHash(self.transaction),

                # SAY "WHAT" AGAIN!
                'what': 'augh!',
            },
            {
                'what': [f.FilterMapper.CODE_EXTRA_KEY],
            },
        )

    def test_fail_transaction_wrong_type(self):
        """
    ``transaction`` is not a TrytesCompatible value.
    """
        self.assertFilterErrors(
            {
                'transaction': 42,
            },
            {
                'transaction': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_transaction_not_trytes(self):
        """
    ``transaction`` contains invalid characters.
    """
        self.assertFilterErrors(
            {
                'transaction':
                b'not valid; must contain only uppercase and "9"',
            },
            {
                'transaction': [Trytes.CODE_NOT_TRYTES],
            },
        )
示例#4
0
    def setUp(self):
        super(GetBundlesCommandTestCase, self).setUp()

        self.adapter = MockAdapter()
        self.command = GetBundlesCommand(self.adapter)
示例#5
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,
        ))
示例#6
0
    def setUp(self):
        super(GetBundlesCommandTestCase, self).setUp()

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

        # Tail transaction hash
        self.tx_hash = TransactionHash(
            'TOYJPHKMLQNDVLDHDILARUJCCIUMQBLUSWPCTIVA'
            'DRXICGYDGSVPXFTILFFGAPICYHGGJ9OHXINFX9999'
        )

        self.bundle_trytes = [
            # Order is important if we don't convert to bundle representation.
            # Tail transaction should be the first.
            TransactionTrytes(
                'NBTCPCFDEACCPCBDVC9DTCQAJ9RBTC9D9DCDQAEAKDCDFD9DSCFAJ9VBCDJDTCQAJ9'
                'ZBMDYBCCKB99999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999SYRABNN9JD9PNDL'
                'IKUNCECUELTOHNLFMVD99999999999A99999999PDQWLVVDPUU9VIBODGMRIAZPGQX'
                'DOGSEXIHKIBWSLDAWUKZCZMK9Z9YZSPCKBDJSVDPRQLJSTKUMTNVSXFSEWUNJOEGNU'
                'I9QOCRFMYSIFAZLJHKZBPQZZYFG9ORYCRDX9TOMJPFCRB9R9KPUUGFPVOWYXFIWEW9'
                '999BGUEHHGAIWWQBCJZHZAQOWZMAIDAFUZBVMUVPWQJLUGGQKNKLMGTWXXNZKUCBJL'
                'EDAMYVRGABAWBY9999SYRABNN9JD9PNDLIKUNCECUELTOQZPSBDILVHJQVCEOICFAD'
                'YKZVGMOAXJRQNTCKMHGTAUMPGJJMX9LNF'
            ),

            TransactionTrytes(
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999999999999999999999999999999999999999999999999999999999999'
                '999999999WUQXEGBVIECGIWO9IGSYKWWPYCIVUJJGSJPWGIAFJPYSF9NSQOHWAHS9P'
                '9PWQHOBXNNQIF9IRHVQXKPZW999999999999999999999999999XZUIENOTTBKJMDP'
                'RXWGQYG9PWGTHNLFMVD99A99999999A99999999PDQWLVVDPUU9VIBODGMRIAZPGQX'
                'DOGSEXIHKIBWSLDAWUKZCZMK9Z9YZSPCKBDJSVDPRQLJSTKUMTNVSXBGUEHHGAIWWQ'
                'BCJZHZAQOWZMAIDAFUZBVMUVPWQJLUGGQKNKLMGTWXXNZKUCBJLEDAMYVRGABAWBY9'
                '999MYIYBTGIOQYYZFJBLIAWMPSZEFFTXUZPCDIXSLLQDQSFYGQSQOGSPKCZNLVSZ9L'
                'MCUWVNGEN9EJEW9999XZUIENOTTBKJMDPRXWGQYG9PWGTXUO9AXMP9FLMDRMADLRPW'
                'CZCJBROYCDRJMYU9HDYJM9NDBFUPIZVTR'
            ),
        ]

        # Add a spam tx. When this is returned, traverse_bundle knows it hit a
        # different bundle and should stop.
        self.spam_trytes = TransactionTrytes(
            'SPAMSPAMSPAM999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999999999999999999'
            '999999999999999999999999999999999999999999999999999JECDITWO9999999'
            '999999999999ONLFMVD99999999999999999999VVCHSQSRVFKSBONDWB9EAQEMQOY'
            'YRBIZHTBJLYNAVDHZPUZAZ9LYHXWKBEJ9IPR9FAMFLT9EEOHVYWUPRHHSRCILCLWFD'
            'GBYBFFOKMCSAPVD9VGZZRRGBLGMZMXD9RMZQDBLMGN9BATWZGULRBCYQEIKIRBPHC9'
            '999KTLTRSYOWBD9HVNP9GCUABARNGMYXUZKXWRPGOPETZLKYYC9Z9EYXIWVARUBMBM'
            'BPXGORN9WPBLY99999ZRBVQWULRFXDNDYZKRKIXPZQT9JJJH9FZU9PVWZJWLXBPODP'
            'EHMKTTAGEPLPHUQCZNLDSHERONOMHJCOI'
        )
示例#7
0
class GetBundlesRequestFilterTestCase(BaseFilterTestCase):
    filter_type = GetBundlesCommand(MockAdapter()).get_request_filter
    skip_value_check = True

    def setUp(self):
        super(GetBundlesRequestFilterTestCase, self).setUp()

        self.transactions = [
            (
                'TESTVALUE9DONTUSEINPRODUCTION99999KPZOTR'
                'VDB9GZDJGZSSDCBIX9QOK9PAV9RMDBGDXLDTIZTWQ'
            ),
            (
                'TESTVALUE9DONTUSEINPRODUCTION99999TAXQBF'
                'ZMUQLZ9RXRRXQOUSAMGAPEKTZNERIKSDYGHQA9999'
            ),
        ]

    def test_pass_happy_path(self):
        """
        Request is valid.
        """
        # Raw trytes are extracted to match the IRI's JSON protocol.
        request = {
            'transactions': self.transactions,
        }

        filter_ = self._filter(request)

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

    def test_pass_compatible_types(self):
        """
        Request contains values that can be converted to the expected
        types.
        """
        # Convert first to TranscationHash
        tx_hashes = []
        for tx in self.transactions:
            tx_hashes.append(TransactionHash(tx))

        filter_ = self._filter({
            # Any TrytesCompatible value will work here.
            'transactions': tx_hashes,
        })

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

            {
                'transactions': self.transactions,
            },
        )

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

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

    def test_fail_unexpected_parameters(self):
        """
        Request contains unexpected parameters.
        """
        self.assertFilterErrors(
            {
                'transactions': self.transactions,

                # SAY "WHAT" AGAIN!
                'what': 'augh!',
            },

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

    def test_fail_transaction_wrong_type(self):
        """
        ``transactions`` contains no TrytesCompatible value.
        """
        self.assertFilterErrors(
            {
                'transactions': [42],
            },

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

    def test_fail_transaction_not_trytes(self):
        """
        ``transactions`` contains invalid characters.
        """
        self.assertFilterErrors(
            {
                'transactions': [b'not valid; must contain only uppercase and "9"'],
            },

            {
                'transactions.0': [Trytes.CODE_NOT_TRYTES],
            },
        )

    def test_fail_no_list(self):
        """
        ``transactions`` has one hash rather than a list of hashes.
        """
        self.assertFilterErrors(
            {
                'transactions': self.transactions[0],
            },

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

    def test_fail_transactions_contents_invalid(self):
        """
        ``transactions`` is a non-empty array, but it contains invlaid values.
        """
        self.assertFilterErrors(
            {
                'transactions': [
                    b'',
                    True,
                    None,
                    b'not valid transaction hash',

                    # A valid tx hash, this should not produce error
                    TransactionHash(self.transactions[0]),

                    65498731,
                    b'9' * (TransactionHash.LEN +1),
                ],
            },

            {
                'transactions.0': [f.Required.CODE_EMPTY],
                'transactions.1': [f.Type.CODE_WRONG_TYPE],
                'transactions.2': [f.Required.CODE_EMPTY],
                'transactions.3': [Trytes.CODE_NOT_TRYTES],
                'transactions.5': [f.Type.CODE_WRONG_TYPE],
                'transactions.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )
示例#8
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,
                )),
        }
class GetBundlesRequestFilterTestCase(BaseFilterTestCase):
  filter_type = GetBundlesCommand(MockAdapter()).get_request_filter
  skip_value_check = True

  def setUp(self):
    super(GetBundlesRequestFilterTestCase, self).setUp()

    # noinspection SpellCheckingInspection
    self.transaction = (
      b'ORLSCIMM9ZONOUSPYYWLOEMXQZLYEHCBEDQSHZOG'
      b'OPZCZCDZYTDPGEEUXWUZ9FQYCT9OGS9PICOOX9999'
    )

  def test_pass_happy_path(self):
    """
    Request is valid.
    """
    request = {
      'transaction': TransactionHash(self.transaction)
    }

    filter_ = self._filter(request)

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

  def test_pass_compatible_types(self):
    """
    Request contains values that can be converted to the expected
    types.
    """
    filter_ = self._filter({
      # Any TrytesCompatible value will work here.
      'transaction': binary_type(self.transaction),
    })

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

      {
        'transaction': TransactionHash(self.transaction),
      },
    )

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

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

  def test_fail_unexpected_parameters(self):
    """
    Request contains unexpected parameters.
    """
    self.assertFilterErrors(
      {
        'transaction': TransactionHash(self.transaction),

        # SAY "WHAT" AGAIN!
        'what': 'augh!',
      },

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

  def test_fail_transaction_wrong_type(self):
    """
    ``transaction`` is not a TrytesCompatible value.
    """
    self.assertFilterErrors(
      {
        'transaction': text_type(self.transaction, 'ascii'),
      },

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

  def test_fail_transaction_not_trytes(self):
    """
    ``transaction`` contains invalid characters.
    """
    self.assertFilterErrors(
      {
        'transaction': b'not valid; must contain only uppercase and "9"',
      },

      {
        'transaction': [Trytes.CODE_NOT_TRYTES],
      },
    )