Skip to content
This repository has been archived by the owner on Jan 13, 2023. It is now read-only.

Snapshot proof commands #244

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions docs/addresses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,11 @@ any other financial service.
These performance issues will be fixed in a future version of the library;
please bear with us!

In the meantime, if you are using Python 3, you can install a C extension
In the meantime, you can install a C extension
that boosts PyOTA's performance significantly (speedups of 60x are common!).

To install the extension, run ``pip install pyota[ccurl]``.

**Important:** The extension is not yet compatible with Python 2.

If you are familiar with Python 2's C API, we'd love to hear from you!
Check the `GitHub issue <https://github.com/todofixthis/pyota-ccurl/issues/4>`_
for more information.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙀 Wow, good catch!


PyOTA provides two methods for generating addresses:

Using the API
Expand Down Expand Up @@ -60,7 +54,9 @@ method, using the following parameters:
(defaults to 1).
- If ``None``, the API will generate addresses until it finds one that
has not been used (has no transactions associated with it on the
Tangle). It will then return the unused address and discard the rest.
Tangle, has no balance and was not spent from). This makes the command
safe to use even after a snapshot has been taken. It will then return the
unused address and discard the rest.
- ``security_level: int``: Determines the security level of the
generated addresses. See `Security Levels <#security-levels>`__
below.
Expand Down
22 changes: 15 additions & 7 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ Parameters
parameter behaves like the ``stop`` attribute in a ``slice`` object;
the stop index is *not* included in the result.

- If ``None`` (default), then this method will check every address
until it finds one without any transfers.
- If ``None`` (default), then this method will not stop until it finds
an unused address. This is one without any transactions, that has no
balance and that was not spent from. Note that a snapshot, which removes
transactions, can cause this API call to stop earlier.

- ``inclusion_states: bool`` Whether to also fetch the inclusion states
of the transfers. This requires an additional API call to the node,
Expand Down Expand Up @@ -145,7 +147,9 @@ Parameters
- Note that this parameter behaves like the ``stop`` attribute in a
``slice`` object; the stop index is *not* included in the result.
- If ``None`` (default), then this method will not stop until it finds
an unused address.
an unused address. This is one without any transactions, that has no
balance and that was not spent from. Note that a snapshot, which removes
transactions, can cause this API call to stop earlier.
- ``threshold: Optional[int]``: If set, determines the minimum
threshold for a successful result:
- As soon as this threshold is reached, iteration will stop.
Expand Down Expand Up @@ -199,11 +203,13 @@ Generates one or more new addresses from the seed.
Parameters
~~~~~~~~~~

- ``index: int``: Specify the index of the new address (must be >= 1).
- ``index: int``: Specify the index of the new address (must be >= 0).
- ``count: Optional[int]``: Number of addresses to generate (must be >=
1).
- If ``None``, this method will scan the Tangle to find the next
available unused address and return that.
available unused address and return that. This is one without any
transactions, that has no balance and that was not spent from. This makes
the command safe to use even after a snapshot has been taken.
- ``security_level: int``: Number of iterations to use when generating
new addresses. Lower values generate addresses faster, higher values
result in more secure signatures in transactions.
Expand All @@ -228,8 +234,10 @@ Parameters
- ``stop: Optional[int]``: Stop before this index.
- Note that this parameter behaves like the ``stop`` attribute in a
``slice`` object; the stop index is *not* included in the result.
- If ``None`` (default), then this method will check every address
until it finds one without any transfers.
- If ``None`` (default), then this method will not stop until it finds
an unused address. This is one without any transactions, that has no
balance and that was not spent from. Note that a snapshot, which removes
transactions, can cause this API call to stop earlier.

Return
~~~~~~
Expand Down
8 changes: 4 additions & 4 deletions iota/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -830,8 +830,7 @@ def get_new_addresses(
Generates one or more new addresses from the seed.

:param index:
The key index of the first new address to generate (must be
>= 1).
The key index of the first new address to generate (must be >= 0).

:param count:
Number of addresses to generate (must be >= 1).
Expand All @@ -841,8 +840,9 @@ def get_new_addresses(
inside a loop.

If ``None``, this method will progressively generate
addresses and scan the Tangle until it finds one that has no
transactions referencing it.
addresses and scan the Tangle until it finds one that is unused.
This is if no transactions are referencing it and it has no balance
and it was not spent from before.

:param security_level:
Number of iterations to use when generating new addresses.
Expand Down
2 changes: 1 addition & 1 deletion iota/commands/extended/get_account_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def _execute(self, request):
my_hashes = ft_command(addresses=my_addresses).get('hashes') or []

account_balance = 0
if my_hashes:
if my_addresses:
# Load balances for the addresses that we generated.
gb_response = (
GetBalancesCommand(self.adapter)(addresses=my_addresses)
Expand Down
25 changes: 20 additions & 5 deletions iota/commands/extended/get_new_addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
from iota import Address
from iota.commands import FilterCommand, RequestFilter
from iota.commands.core.find_transactions import FindTransactionsCommand
from iota.commands.core.get_balances import GetBalancesCommand
from iota.commands.core.were_addresses_spent_from import \
WereAddressesSpentFromCommand
from iota.crypto.addresses import AddressGenerator
from iota.crypto.types import Seed
from iota.filters import SecurityLevel, Trytes
Expand Down Expand Up @@ -58,17 +61,29 @@ def _find_addresses(self, seed, index, count, security_level, checksum):
generator = AddressGenerator(seed, security_level, checksum)

if count is None:
# Connect to Tangle and find the first address without any
# transactions.
# Connect to Tangle and find the first unused address.
for addy in generator.create_iterator(start=index):
# We use addy.address here because FindTransactions does
# 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 = GetBalancesCommand(self.adapter)(
addresses=[addy.address],
)
if response['balances'][0] != 0:
continue

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

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

return generator.get_addresses(start=index, count=count)

Expand Down
26 changes: 21 additions & 5 deletions iota/commands/extended/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
TransactionHash
from iota.adapter import BaseAdapter
from iota.commands.core.find_transactions import FindTransactionsCommand
from iota.commands.core.get_balances import GetBalancesCommand
from iota.commands.core.get_trytes import GetTrytesCommand
from iota.commands.core.were_addresses_spent_from import \
WereAddressesSpentFromCommand
from iota.commands.extended import FindTransactionObjectsCommand
from iota.commands.extended.get_bundles import GetBundlesCommand
from iota.commands.extended.get_latest_inclusion import \
Expand All @@ -25,26 +28,39 @@ def iter_used_addresses(
):
# type: (...) -> Generator[Tuple[Address, List[TransactionHash]], None, None]
"""
Scans the Tangle for used addresses.
Scans the Tangle for used addresses. A used address is an address that
was spent from or has a balance or has a transaction.

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

ft_command = FindTransactionsCommand(adapter)
wasf_command = WereAddressesSpentFromCommand(adapter)
gb_command = GetBalancesCommand(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.
wasp_response = wasf_command(addresses=[addy])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
wasp_response = wasf_command(addresses=[addy])
wasf_response = wasf_command(addresses=[addy])

if wasp_response['states'][0]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if wasp_response['states'][0]:
if wasf_response['states'][0]:

yield addy, []
else:
gb_response = gb_command(addresses=[addy])
if gb_response['balances'][0] != 0:
yield addy, []
else:
break

# Reset the commands so that we can call them again.
ft_command.reset()
wasf_command.reset()
gb_command.reset()


def get_bundles_from_transaction_hashes(
Expand Down
25 changes: 25 additions & 0 deletions test/commands/extended/get_account_data_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,28 @@ def test_no_transactions(self):
'bundles': [],
},
)

def test_balance_is_found_for_address_without_transaction(self):
"""
If an address has a balance but no transaction due to a snapshot the
balance should still be found and returned.
"""
with mock.patch(
'iota.commands.extended.get_account_data.iter_used_addresses',
mock.Mock(return_value=[(self.addy1, [])]),
):
self.adapter.seed_response('getBalances', {
'balances': [42],
})

response = self.command(seed=Seed.random())

self.assertDictEqual(
response,

{
'addresses': [self.addy1],
'balance': 42,
'bundles': [],
},
)
Loading