def test_fail_multisigInput_multiple(self):
        """
    ``multisigInput`` is an array.

    This is not valid; a bundle may only contain a single multisig
    input.
    """
        self.assertFilterErrors(
            {
                'changeAddress':
                Address(self.trytes_1),
                'multisigInput': [
                    MultisigAddress(
                        digests=[self.digest_1, self.digest_2],
                        trytes=self.trytes_2,
                    ),
                    MultisigAddress(
                        digests=[self.digest_1, self.digest_2],
                        trytes=self.trytes_2,
                    )
                ],
                'transfers': [
                    ProposedTransaction(
                        address=Address(self.trytes_3),
                        value=42,
                    ),
                ],
            },
            {
                'multisigInput': [f.Type.CODE_WRONG_TYPE],
            },
        )
    def test_pass_changeAddress_multisig_address(self):
        """
    ``changeAddress`` is allowed to be a MultisigAddress.
    """
        change_addy =\
          MultisigAddress(
            digests = [self.digest_1, self.digest_2],
            trytes  = self.trytes_1
          )

        filter_ = self._filter({
            'changeAddress':
            change_addy,
            'multisigInput':
            MultisigAddress(
                digests=[self.digest_1, self.digest_2],
                trytes=self.trytes_2,
            ),
            'transfers': [
                ProposedTransaction(
                    address=Address(self.trytes_3),
                    value=42,
                ),
            ],
        })

        self.assertFilterPasses(filter_)
        self.assertIs(filter_.cleaned_data['changeAddress'], change_addy)
    def test_fail_transfers_wrong_type(self):
        """
    ``transfers`` is not an array.
    """
        self.assertFilterErrors(
            {
                'changeAddress':
                Address(self.trytes_1),
                'multisigInput':
                MultisigAddress(
                    digests=[self.digest_1, self.digest_2],
                    trytes=self.trytes_2,
                ),

                # ``transfers`` must be an array, even if there's only one
                # transaction.
                'transfers':
                ProposedTransaction(
                    address=Address(self.trytes_3),
                    value=42,
                ),
            },
            {
                'transfers': [f.Array.CODE_WRONG_TYPE],
            },
        )
    async def test_error_insufficient_inputs(self):
        """
    The multisig input does not contain sufficient IOTAs to cover the
    spends.
    """
        self.adapter.seed_response(
            command=GetBalancesCommand.command,
            response={
                'balances': [42],
                'duration': 86,
            },
        )

        with self.assertRaises(ValueError):
            await self.command(
                transfers=[
                    ProposedTransaction(
                        address=Address(self.trytes_1),
                        value=101,
                    ),
                ],
                multisigInput=MultisigAddress(
                    digests=[self.digest_1, self.digest_2],
                    trytes=self.trytes_2,
                ),
            )
    def test_fail_unexpected_parameters(self):
        """
    Request contains unexpected parameters.
    """
        self.assertFilterErrors(
            {
                'changeAddress':
                Address(self.trytes_1),
                'multisigInput':
                MultisigAddress(
                    digests=[self.digest_1, self.digest_2],
                    trytes=self.trytes_2,
                ),
                'transfers': [
                    ProposedTransaction(
                        address=Address(self.trytes_3),
                        value=42,
                    ),
                ],

                # Oh come on!
                'foo':
                'bar',
            },
            {
                'foo': [f.FilterMapper.CODE_EXTRA_KEY],
            },
        )
    def test_fail_transfers_contents_invalid(self):
        """
    ``transfers`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'changeAddress':
                Address(self.trytes_1),
                'multisigInput':
                MultisigAddress(
                    digests=[self.digest_1, self.digest_2],
                    trytes=self.trytes_2,
                ),
                'transfers': [
                    None,
                    42,

                    # This one's valid, actually; just making sure that the
                    # filter doesn't cheat.
                    ProposedTransaction(
                        address=Address(self.trytes_3),
                        value=42,
                    ),
                    Address(self.trytes_3),
                ],
            },
            {
                'transfers.0': [f.Required.CODE_EMPTY],
                'transfers.1': [f.Type.CODE_WRONG_TYPE],
                'transfers.3': [f.Type.CODE_WRONG_TYPE],
            },
        )
    def test_pass_compatible_types(self):
        """
    Request contains values that can be converted to the expected
    types.
    """
        txn =\
          ProposedTransaction(
            address = Address(self.trytes_3),
            value   = 42,
          )

        filter_ =\
          self._filter({
            # ``changeAddress`` can be any value that resolves to an
            # :py:class:`Address`.
            'changeAddress': self.trytes_1,

            # ``multisigInput`` must be a :py:class:`MultisigInput` object.
            'multisigInput':
              MultisigAddress(
                digests = [self.digest_1, self.digest_2],
                trytes  = self.trytes_2,
              ),

            # ``transfers`` must contain an array of
            # :py:class:`ProposedTransaction` objects.
            'transfers': [txn],
          })

        self.assertFilterPasses(filter_)

        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'changeAddress':
                Address(self.trytes_1),
                'multisigInput':
                MultisigAddress(
                    digests=[self.digest_1, self.digest_2],
                    trytes=self.trytes_2,
                ),
                'transfers': [txn],
            },
        )
示例#8
0
    def test_add_inputs_error_multiple(self):
        """
    Attempting to add multiple multisig inputs.

    This is not currently supported.
    """
        with self.assertRaises(ValueError):
            # noinspection SpellCheckingInspection
            self.bundle.add_inputs([
                MultisigAddress(
                    trytes=self.trytes_1,
                    digests=[self.digest_1, self.digest_2],
                    balance=42,
                ),
                MultisigAddress(
                    trytes=self.trytes_2,
                    digests=[self.digest_2, self.digest_1],
                ),
            ])
    def test_pass_optional_parameters_excluded(self):
        """
    Request omits optional parameters.
    """
        txn =\
          ProposedTransaction(
            address = Address(self.trytes_3),
            value   = 42,
          )

        filter_ =\
          self._filter({
            # ``changeAddress`` is optional.
            # Technically, it's required if there are unspent inputs, but
            # the filter has no way to know whether this is the case.
            # 'changeAddress': self.trytes_1,

            # These parameters are required.
            'multisigInput':
              MultisigAddress(
                digests = [self.digest_1, self.digest_2],
                trytes  = self.trytes_2,
              ),

            'transfers': [txn],
          })

        self.assertFilterPasses(filter_)

        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'changeAddress':
                None,
                'multisigInput':
                MultisigAddress(
                    digests=[self.digest_1, self.digest_2],
                    trytes=self.trytes_2,
                ),
                'transfers': [txn],
            },
        )
示例#10
0
 def test_add_inputs_error_digests_empty(self):
     """
 Adding a multisig input with unknown digests.
 """
     with self.assertRaises(ValueError):
         self.bundle.add_inputs([
             MultisigAddress(
                 trytes=self.trytes_1,
                 digests=[],
             ),
         ])
示例#11
0
 def test_add_inputs_error_balance_null(self):
     """
 Adding a multisig input with null balance.
 """
     with self.assertRaises(ValueError):
         self.bundle.add_inputs([
             MultisigAddress(
                 trytes=self.trytes_1,
                 digests=[self.digest_1, self.digest_2],
                 # balance = 42,
             ),
         ])
示例#12
0
    async def test_happy_path(self):
        """
    Generating a multisig address.
    """
        result = await self.command(digests=[self.digest_1, self.digest_2])

        self.assertDictEqual(
            result,
            {
                'address':
                MultisigAddress(
                    trytes=b'ZYKDKGXTMGINTQLUMVNBBI9XCEI9BWYF9YOPCBFT'
                    b'UUJZWM9YIWHNYZEWOPEVRVLKZCPRKLCQD9BR9FVLC',
                    digests=[self.digest_1, self.digest_2],
                ),
            },
        )
    def test_fail_transfers_empty(self):
        """
    ``transfers`` is an array, but it's empty.
    """
        self.assertFilterErrors(
            {
                'changeAddress':
                Address(self.trytes_1),
                'multisigInput':
                MultisigAddress(
                    digests=[self.digest_1, self.digest_2],
                    trytes=self.trytes_2,
                ),

                # This is a variation on the ``null`` test.
                'transfers': [],
            },
            {
                'transfers': [f.Required.CODE_EMPTY],
            },
        )
示例#14
0
    def get_address(self):
        # type: () -> MultisigAddress
        """
    Returns the new multisig address.

    Note that you can continue to add digests after extracting an
    address; the next address will use *all* of the digests that have
    been added so far.
    """
        if not self._digests:
            raise ValueError(
                'Must call ``add_digest`` at least once '
                'before calling ``get_address``.', )

        if not self._address:
            address_trits = [0] * HASH_LENGTH
            self._sponge.squeeze(address_trits)

            self._address =\
              MultisigAddress.from_trits(address_trits, digests=self._digests[:])

        return self._address
    def test_fail_transfers_null(self):
        """
    ``transfers`` is null.
    """
        self.assertFilterErrors(
            {
                'changeAddress':
                Address(self.trytes_1),
                'multisigInput':
                MultisigAddress(
                    digests=[self.digest_1, self.digest_2],
                    trytes=self.trytes_2,
                ),

                # On second thought, I changed my mind.
                'transfers':
                None,
            },
            {
                'transfers': [f.Required.CODE_EMPTY],
            },
        )
示例#16
0
    def test_add_inputs_happy_path(self):
        """
    Adding a multisig input to a bundle.
    """
        # noinspection SpellCheckingInspection
        self.bundle.add_transaction(
            ProposedTransaction(
                address=Address(self.trytes_1),
                value=42,
            ), )

        self.bundle.add_inputs([
            MultisigAddress(
                trytes=self.trytes_2,
                digests=[self.digest_1, self.digest_2],
                balance=42,
            )
        ])

        # The multisig input requires a total of 4 transactions to store
        # all the signatures.  Including the spend, that makes 5
        # transactions in total.
        self.assertEqual(len(self.bundle), 5)
    async def test_error_zero_iotas_transferred(self):
        """
    The bundle doesn't spend any IOTAs.

    This is considered an error case because
    :py:meth:`MultisigIota.prepare_multisig_transfer` is specialized
    for generating bundles that require multisig inputs.  Any bundle
    that doesn't require multisig functionality should be generated
    using :py:meth:`iota.api.Iota.prepare_transfer` instead.
    """
        with self.assertRaises(ValueError):
            await self.command(
                transfers=[
                    ProposedTransaction(
                        address=Address(self.trytes_1),
                        value=0,
                    ),
                ],
                multisigInput=MultisigAddress(
                    digests=[self.digest_1, self.digest_2],
                    trytes=self.trytes_2,
                ),
            )
示例#18
0
  def get_address(self):
    # type: () -> MultisigAddress
    """
    Returns the new multisig address.

    Note that you can continue to add digests after extracting an
    address; the next address will use *all* of the digests that have
    been added so far.
    """
    if not self._digests:
      raise ValueError(
        'Must call ``add_digest`` at least once '
        'before calling ``get_address``.',
      )

    if not self._address:
      address_trits = [0] * HASH_LENGTH
      self._sponge.squeeze(address_trits)

      self._address =\
        MultisigAddress.from_trits(address_trits, digests=self._digests[:])

    return self._address
    async def test_error_unspent_inputs_no_change_address(self):
        """
    The bundle has unspent inputs, but no change address was specified.

    Unlike :py:meth:`iota.api.Iota.prepare_transfer` where all of the
    inputs are owned by the same seed, creating a multisig transfer
    usually involves multiple people.

    It would be unfair to the participants of the transaction if we
    were to automatically generate a change address using the seed of
    whoever happened to invoke the
    :py:meth:`MultisigIota.prepare_multisig_transfer` method!
    """
        self.adapter.seed_response(
            command=GetBalancesCommand.command,
            response={
                'balances': [101],
                'duration': 86,
            },
        )

        with self.assertRaises(ValueError):
            await self.command(
                transfers=[
                    ProposedTransaction(
                        address=Address(self.trytes_1),
                        value=42,
                    ),
                ],
                multisigInput=MultisigAddress(
                    digests=[self.digest_1, self.digest_2],
                    trytes=self.trytes_2,
                ),

                # changeAddress = Address(self.trytes_3),
            )
    def test_pass_happy_path(self):
        """
    Request is valid.
    """
        request = {
            'changeAddress':
            Address(self.trytes_1),
            'multisigInput':
            MultisigAddress(
                digests=[self.digest_1, self.digest_2],
                trytes=self.trytes_2,
            ),
            'transfers': [
                ProposedTransaction(
                    address=Address(self.trytes_3),
                    value=42,
                ),
            ],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(filter_.cleaned_data, request)
 def test_fail_changeAddress_wrong_type(self):
     """
 ``changeAddress`` is not a TrytesCompatible value.
 """
     self.assertFilterErrors(
         {
             'changeAddress':
             42,
             'multisigInput':
             MultisigAddress(
                 digests=[self.digest_1, self.digest_2],
                 trytes=self.trytes_2,
             ),
             'transfers': [
                 ProposedTransaction(
                     address=Address(self.trytes_3),
                     value=42,
                 ),
             ],
         },
         {
             'changeAddress': [f.Type.CODE_WRONG_TYPE],
         },
     )
    async def test_unspent_inputs_with_change_address(self):
        """
    The bundle has unspent inputs, so it uses the provided change
    address.
    """
        self.adapter.seed_response(
            command=GetBalancesCommand.command,
            response={
                'balances': [101],
                'duration': 86,
            },
        )

        pmt_result =\
          await self.command(
            transfers = [
              ProposedTransaction(
                address = Address(self.trytes_1),
                value   = 42,
              ),
            ],

            multisigInput =
              MultisigAddress(
                digests = [self.digest_1, self.digest_2],
                trytes  = self.trytes_2,
              ),

            changeAddress = Address(self.trytes_3),
          )

        bundle = Bundle.from_tryte_strings(pmt_result['trytes'])

        self.assertEqual(len(bundle), 6)

        # Spend Transaction
        txn_1 = bundle[0]
        self.assertEqual(txn_1.address, self.trytes_1)
        self.assertEqual(txn_1.value, 42)

        # Input 1, Part 1 of 4
        txn_2 = bundle[1]
        self.assertEqual(txn_2.address, self.trytes_2)
        self.assertEqual(txn_2.value, -101)
        self.assertEqual(txn_2.signature_message_fragment, Fragment(b''))

        # Input 1, Part 2 of 4
        txn_3 = bundle[2]
        self.assertEqual(txn_3.address, self.trytes_2)
        self.assertEqual(txn_3.value, 0)
        self.assertEqual(txn_3.signature_message_fragment, Fragment(b''))

        # Input 1, Part 3 of 4
        txn_4 = bundle[3]
        self.assertEqual(txn_4.address, self.trytes_2)
        self.assertEqual(txn_4.value, 0)
        self.assertEqual(txn_4.signature_message_fragment, Fragment(b''))

        # Input 1, Part 4 of 4
        txn_5 = bundle[4]
        self.assertEqual(txn_5.address, self.trytes_2)
        self.assertEqual(txn_5.value, 0)
        self.assertEqual(txn_5.signature_message_fragment, Fragment(b''))

        # Change
        txn_6 = bundle[5]
        self.assertEqual(txn_6.address, self.trytes_3)
        self.assertEqual(txn_6.value, 59)
    async def test_happy_path(self):
        """
    Preparing a bundle with a multisig input.
    """
        self.adapter.seed_response(
            command=GetBalancesCommand.command,
            response={
                'balances': [42],

                # Would it be cheeky to put "7½ million years" here?
                'duration': 86,
            },
        )

        pmt_result =\
          await self.command(
            transfers = [
              ProposedTransaction(
                address = Address(self.trytes_1),
                value   = 42,
              ),
            ],

            multisigInput =
              MultisigAddress(
                digests = [self.digest_1, self.digest_2],
                trytes  = self.trytes_2,
              ),
          )

        # The command returns the raw trytes.  This is useful in a
        # real-world scenario because trytes are easier to transfer between
        # each entity that needs to apply their signature.
        #
        # However, for purposes of this test, we will convert the trytes
        # back into a bundle so that we can inspect the end result more
        # easily.
        bundle = Bundle.from_tryte_strings(pmt_result['trytes'])

        #
        # This bundle looks almost identical to what you would expect from
        # :py:meth:`iota.api.Iota.prepare_transfer`, except:
        # - There are 4 inputs (to hold all of the signature fragments).
        # - The inputs are unsigned.
        #
        self.assertEqual(len(bundle), 5)

        # Spend Transaction
        txn_1 = bundle[0]
        self.assertEqual(txn_1.address, self.trytes_1)
        self.assertEqual(txn_1.value, 42)

        # Input 1, Part 1 of 4
        txn_2 = bundle[1]
        self.assertEqual(txn_2.address, self.trytes_2)
        self.assertEqual(txn_2.value, -42)
        self.assertEqual(txn_2.signature_message_fragment, Fragment(b''))

        # Input 1, Part 2 of 4
        txn_3 = bundle[2]
        self.assertEqual(txn_3.address, self.trytes_2)
        self.assertEqual(txn_3.value, 0)
        self.assertEqual(txn_3.signature_message_fragment, Fragment(b''))

        # Input 1, Part 3 of 4
        txn_4 = bundle[3]
        self.assertEqual(txn_4.address, self.trytes_2)
        self.assertEqual(txn_4.value, 0)
        self.assertEqual(txn_4.signature_message_fragment, Fragment(b''))

        # Input 1, Part 4 of 4
        txn_5 = bundle[4]
        self.assertEqual(txn_5.address, self.trytes_2)
        self.assertEqual(txn_5.value, 0)
        self.assertEqual(txn_5.signature_message_fragment, Fragment(b''))