Ejemplo n.º 1
0
    def test_get_addresses_error_step_zero(self):
        """
    Providing a ``step`` value of 0 to ``get_addresses``.
    """
        ag = AddressGenerator(seed=b'')

        with self.assertRaises(ValueError):
            ag.get_addresses(start=0, step=0)
Ejemplo n.º 2
0
    def test_get_addresses_error_count_too_small(self):
        """
    Providing a ``count`` value less than 1 to ``get_addresses``.

    :py:class:`AddressGenerator` can potentially generate an infinite
    number of addresses, so there is no "end" to offset against.
    """
        ag = AddressGenerator(seed=b'')

        with self.assertRaises(ValueError):
            ag.get_addresses(start=0, count=0)
Ejemplo n.º 3
0
    def test_get_addresses_multiple(self):
        """
    Generating multiple addresses in one go.
    """
        # Seed is not important for this test; it is only used by
        # :py:class:`KeyGenerator`, which we will mock in this test.
        ag = AddressGenerator(seed=b'')

        # noinspection PyUnresolvedReferences
        with patch.object(ag, '_get_digest', self._mock_get_digest):
            addresses = ag.get_addresses(start=1, count=2)

        self.assertListEqual(addresses, [self.addy1, self.addy2])
Ejemplo n.º 4
0
    def test_local_cache(self):
        """
    Installing a cache only for a single instance of
    :py:class:`AddressGenerator`.
    """
        mock_generate_address = Mock(return_value=self.addy)

        with patch(
                'cornode.crypto.addresses.AddressGenerator._generate_address',
                mock_generate_address,
        ):
            generator1 = AddressGenerator(Seed.random())

            # Install the cache locally.
            generator1.cache = MemoryAddressCache()

            addy1 = generator1.get_addresses(42)
            mock_generate_address.assert_called_once()

            # The second time we try to generate the same address, it is
            # fetched from the cache.
            addy2 = generator1.get_addresses(42)
            mock_generate_address.assert_called_once()
            self.assertEqual(addy2, addy1)

            # Create a new instance to verify it has its own cache.
            generator2 = AddressGenerator(generator1.seed)

            # The generator has its own cache instance, so even though the
            # resulting address is the same, it is not fetched from cache.
            addy3 = generator2.get_addresses(42)
            self.assertEqual(mock_generate_address.call_count, 2)
            self.assertEqual(addy3, addy1)
Ejemplo n.º 5
0
    def test_generator_with_offset(self):
        """
    Creating a generator that starts at an offset greater than 0.
    """
        # Seed is not important for this test; it is only used by
        # :py:class:`KeyGenerator`, which we will mock in this test.
        ag = AddressGenerator(seed=b'')

        # noinspection PyUnresolvedReferences
        with patch.object(ag, '_get_digest', self._mock_get_digest):
            generator = ag.create_iterator(start=1, step=2)

            self.assertEqual(next(generator), self.addy1)
            self.assertEqual(next(generator), self.addy3)
Ejemplo n.º 6
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,
            ),
        }
Ejemplo n.º 7
0
    def test_global_cache(self):
        """
    Installing a cache that affects all :py:class:`AddressGenerator`
    instances.
    """
        # Install the cache globally.
        AddressGenerator.cache = MemoryAddressCache()

        mock_generate_address = Mock(return_value=self.addy)

        with patch(
                'cornode.crypto.addresses.AddressGenerator._generate_address',
                mock_generate_address,
        ):
            generator1 = AddressGenerator(Seed.random())

            addy1 = generator1.get_addresses(42)
            mock_generate_address.assert_called_once()

            # The second time we try to generate the same address, it is
            # fetched from the cache.
            addy2 = generator1.get_addresses(42)
            mock_generate_address.assert_called_once()
            self.assertEqual(addy2, addy1)

            # Create a new AddressGenerator and verify it uses the same
            # cache.
            generator2 = AddressGenerator(generator1.seed)

            # Cache is global, so the cached address is returned again.
            addy3 = generator2.get_addresses(42)
            mock_generate_address.assert_called_once()
            self.assertEqual(addy3, addy1)
Ejemplo n.º 8
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]

        # Determine the addresses we will be scanning.
        if stop is None:
            addresses =\
              [addy for addy, _ in iter_used_addresses(self.adapter, seed, start)]
        else:
            addresses = AddressGenerator(seed).get_addresses(start, stop)

        if addresses:
            # Load balances for the addresses that we generated.
            gb_response = GetBalancesCommand(self.adapter)(addresses=addresses)
        else:
            gb_response = {'balances': []}

        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'],
                },
            )
Ejemplo n.º 9
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)
Ejemplo n.º 10
0
    def test_get_addresses_single(self):
        """
    Generating a single address.
    """
        # Seed is not important for this test; it is only used by
        # :py:class:`KeyGenerator`, which we will mock in this test.
        ag = AddressGenerator(seed=b'')

        # noinspection PyUnresolvedReferences
        with patch.object(ag, '_get_digest', self._mock_get_digest):
            addresses = ag.get_addresses(start=0)

        self.assertListEqual(addresses, [self.addy0])

        # noinspection PyUnresolvedReferences
        with patch.object(ag, '_get_digest', self._mock_get_digest):
            # You can provide any positive integer as the ``start`` value.
            addresses = ag.get_addresses(start=2)

        self.assertListEqual(addresses, [self.addy2])
Ejemplo n.º 11
0
    def test_get_addresses_step_negative(self):
        """
    Providing a negative ``step`` value to ``get_addresses``.

    This is probably a weird use case, but what the heck.
    """
        # Seed is not important for this test; it is only used by
        # :py:class:`KeyGenerator`, which we will mock in this test.
        ag = AddressGenerator(seed=b'')

        # noinspection PyUnresolvedReferences
        with patch.object(ag, '_get_digest', self._mock_get_digest):
            addresses = ag.get_addresses(start=1, count=2, step=-1)

        self.assertListEqual(
            addresses,

            # This is the same as ``ag.get_addresses(start=0, count=2)``, but
            # the order is reversed.
            [self.addy1, self.addy0],
        )
Ejemplo n.º 12
0
    def test_cache_miss_seed(self):
        """
    Cached addresses are keyed by seed.
    """
        AddressGenerator.cache = MemoryAddressCache()

        mock_generate_address = Mock(return_value=self.addy)

        with patch(
                'cornode.crypto.addresses.AddressGenerator._generate_address',
                mock_generate_address,
        ):
            generator1 = AddressGenerator(Seed.random())
            generator1.get_addresses(42)
            mock_generate_address.assert_called_once()

            generator2 = AddressGenerator(Seed.random())
            generator2.get_addresses(42)
            self.assertEqual(mock_generate_address.call_count, 2)
Ejemplo n.º 13
0
    def test_address_from_digest(self):
        """
    Generating an address from a private key digest.
    """
        digest =\
          Digest(
            trytes =
              b'ABQXVJNER9MPMXMBPNMFBMDGTXRWSYHNZKGAGUOI'
              b'JKOJGZVGHCUXXGFZEMMGDSGWDCKJXO9ILLFAKGGZE',

            key_index = 0,
          )

        self.assertEqual(
            AddressGenerator.address_from_digest(digest),
            Address(b'QLOEDSBXXOLLUJYLEGKEPYDRIJJTPIMEPKMFHUVJ'
                    b'MPMLYYCLPQPANEVDSERQWPVNHCAXYRLAYMBHJLWWR'),
        )
Ejemplo n.º 14
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,
            ),
        }
Ejemplo n.º 15
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()
Ejemplo n.º 16
0
 def get_address():
     generator = AddressGenerator(seed)
     generated.extend(generator.get_addresses(0))