diff --git a/iota/commands/extended/broadcast_and_store.py b/iota/commands/extended/broadcast_and_store.py index 4c3007e..c28526b 100644 --- a/iota/commands/extended/broadcast_and_store.py +++ b/iota/commands/extended/broadcast_and_store.py @@ -1,34 +1,34 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from iota.commands import FilterCommand from iota.commands.core.broadcast_transactions import \ - BroadcastTransactionsCommand + BroadcastTransactionsCommand from iota.commands.core.store_transactions import StoreTransactionsCommand __all__ = [ - 'BroadcastAndStoreCommand', + 'BroadcastAndStoreCommand', ] class BroadcastAndStoreCommand(FilterCommand): - """ - Executes ``broadcastAndStore`` extended API command. + """ + Executes ``broadcastAndStore`` extended API command. - See :py:meth:`iota.api.Iota.broadcast_and_store` for more info. - """ - command = 'broadcastAndStore' + See :py:meth:`iota.api.Iota.broadcast_and_store` for more info. + """ + command = 'broadcastAndStore' - def get_request_filter(self): - pass + def get_request_filter(self): + pass - def get_response_filter(self): - pass + def get_response_filter(self): + pass - def _execute(self, request): - BroadcastTransactionsCommand(self.adapter)(**request) - StoreTransactionsCommand(self.adapter)(**request) - return { - 'trytes': request['trytes'], - } + def _execute(self, request): + BroadcastTransactionsCommand(self.adapter)(**request) + StoreTransactionsCommand(self.adapter)(**request) + return { + 'trytes': request['trytes'], + } diff --git a/iota/commands/extended/get_account_data.py b/iota/commands/extended/get_account_data.py index 4b602f6..12ef466 100644 --- a/iota/commands/extended/get_account_data.py +++ b/iota/commands/extended/get_account_data.py @@ -1,145 +1,153 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from operator import attrgetter from typing import List, Optional import filters as f + from iota import Address, TransactionHash 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.extended.utils import get_bundles_from_transaction_hashes, \ - iter_used_addresses + iter_used_addresses from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed from iota.filters import Trytes __all__ = [ - 'GetAccountDataCommand', + 'GetAccountDataCommand', ] class GetAccountDataCommand(FilterCommand): - """ - Executes ``getAccountData`` extended API command. - - See :py:meth:`iota.api.Iota.get_account_data` for more info. - """ - command = 'getAccountData' - - def get_request_filter(self): - return GetAccountDataRequestFilter() - - def get_response_filter(self): - pass - - 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, - ), - } + """ + Executes ``getAccountData`` extended API command. + + See :py:meth:`iota.api.Iota.get_account_data` for more info. + """ + command = 'getAccountData' + + def get_request_filter(self): + return GetAccountDataRequestFilter() + + def get_response_filter(self): + pass + + 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, + ), + } class GetAccountDataRequestFilter(RequestFilter): - MAX_INTERVAL = 500 - - CODE_INTERVAL_INVALID = 'interval_invalid' - CODE_INTERVAL_TOO_BIG = 'interval_too_big' - - templates = { - CODE_INTERVAL_INVALID: '``start`` must be <= ``stop``', - CODE_INTERVAL_TOO_BIG: '``stop`` - ``start`` must be <= {max_interval}', - } - - def __init__(self): - super(GetAccountDataRequestFilter, self).__init__( - { - # Required parameters. - 'seed': f.Required | Trytes(result_type=Seed), - - # Optional parameters. - 'stop': f.Type(int) | f.Min(0), - 'start': f.Type(int) | f.Min(0) | f.Optional(0), - - 'inclusionStates': f.Type(bool) | f.Optional(False), - }, - - allow_missing_keys = { - 'stop', - 'inclusionStates', - 'start', - }, - ) - - def _apply(self, value): - # noinspection PyProtectedMember - filtered = super(GetAccountDataRequestFilter, self)._apply(value) - - if self._has_errors: - return filtered - - if filtered['stop'] is not None: - if filtered['start'] > filtered['stop']: - filtered['start'] = self._invalid_value( - value = filtered['start'], - reason = self.CODE_INTERVAL_INVALID, - sub_key = 'start', - - context = { - 'start': filtered['start'], - 'stop': filtered['stop'], - }, - ) - elif (filtered['stop'] - filtered['start']) > self.MAX_INTERVAL: - filtered['stop'] = self._invalid_value( - value = filtered['stop'], - reason = self.CODE_INTERVAL_TOO_BIG, - sub_key = 'stop', - - context = { - 'start': filtered['start'], - 'stop': filtered['stop'], - }, - - template_vars = { - 'max_interval': self.MAX_INTERVAL, - }, + MAX_INTERVAL = 500 + + CODE_INTERVAL_INVALID = 'interval_invalid' + CODE_INTERVAL_TOO_BIG = 'interval_too_big' + + templates = { + CODE_INTERVAL_INVALID: '``start`` must be <= ``stop``', + CODE_INTERVAL_TOO_BIG: '``stop`` - ``start`` must be <= {max_interval}', + } + + def __init__(self): + super(GetAccountDataRequestFilter, self).__init__( + { + # Required parameters. + 'seed': f.Required | Trytes(Seed), + + # Optional parameters. + 'stop': f.Type(int) | f.Min(0), + 'start': f.Type(int) | f.Min(0) | f.Optional(0), + + 'inclusionStates': f.Type(bool) | f.Optional(False), + }, + + allow_missing_keys={ + 'stop', + 'inclusionStates', + 'start', + }, ) - return filtered + def _apply(self, value): + # noinspection PyProtectedMember + filtered = super(GetAccountDataRequestFilter, self)._apply(value) + + if self._has_errors: + return filtered + + if filtered['stop'] is not None: + if filtered['start'] > filtered['stop']: + filtered['start'] = self._invalid_value( + value=filtered['start'], + reason=self.CODE_INTERVAL_INVALID, + sub_key='start', + + context={ + 'start': filtered['start'], + 'stop': filtered['stop'], + }, + ) + + elif (filtered['stop'] - filtered['start']) > self.MAX_INTERVAL: + filtered['stop'] = self._invalid_value( + value=filtered['stop'], + reason=self.CODE_INTERVAL_TOO_BIG, + sub_key='stop', + + context={ + 'start': filtered['start'], + 'stop': filtered['stop'], + }, + + template_vars={ + 'max_interval': self.MAX_INTERVAL, + }, + ) + + return filtered diff --git a/iota/commands/extended/get_bundles.py b/iota/commands/extended/get_bundles.py index dab84ce..bec1efc 100644 --- a/iota/commands/extended/get_bundles.py +++ b/iota/commands/extended/get_bundles.py @@ -1,12 +1,13 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from typing import List, Optional import filters as f + from iota import BadApiResponse, Bundle, BundleHash, Transaction, \ - TransactionHash, TryteString + TransactionHash, TryteString from iota.commands import FilterCommand, RequestFilter from iota.commands.core.get_trytes import GetTrytesCommand from iota.exceptions import with_context @@ -14,108 +15,111 @@ from iota.transaction.validator import BundleValidator __all__ = [ - 'GetBundlesCommand', + 'GetBundlesCommand', ] class GetBundlesCommand(FilterCommand): - """ - Executes ``getBundles`` extended API command. - - See :py:meth:`iota.api.Iota.get_bundles` for more info. - """ - command = 'getBundles' - - def get_request_filter(self): - return GetBundlesRequestFilter() - - def get_response_filter(self): - pass - - def _execute(self, request): - transaction_hash = request['transaction'] # type: TransactionHash - - bundle = Bundle(self._traverse_bundle(transaction_hash)) - validator = BundleValidator(bundle) - - if not validator.is_valid(): - raise with_context( - exc = BadApiResponse( - 'Bundle failed validation (``exc.context`` has more info).', - ), - - context = { - 'bundle': bundle, - 'errors': validator.errors, - }, - ) - - return { - # Always return a list, so that we have the necessary structure - # to return multiple bundles in a future iteration. - 'bundles': [bundle], - } - - 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. + Executes ``getBundles`` extended API command. - This method is (usually) faster than ``findTransactions``, and it - ensures we don't collect transactions from replayed bundles. + See :py:meth:`iota.api.Iota.get_bundles` for more info. """ - 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 - ) - + command = 'getBundles' + + def get_request_filter(self): + return GetBundlesRequestFilter() + + def get_response_filter(self): + pass + + def _execute(self, request): + transaction_hash = request['transaction'] # type: TransactionHash + + bundle = Bundle(self._traverse_bundle(transaction_hash)) + validator = BundleValidator(bundle) + + if not validator.is_valid(): + raise with_context( + exc=BadApiResponse( + 'Bundle failed validation (``exc.context`` has more info).', + ), + + context={ + 'bundle': bundle, + 'errors': validator.errors, + }, + ) + + return { + # Always return a list, so that we have the necessary + # structure to return multiple bundles in a future + # iteration. + 'bundles': [bundle], + } + + 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 + ) class GetBundlesRequestFilter(RequestFilter): - def __init__(self): - super(GetBundlesRequestFilter, self).__init__({ - 'transaction': f.Required | Trytes(result_type=TransactionHash), - }) + def __init__(self): + super(GetBundlesRequestFilter, self).__init__({ + 'transaction': f.Required | Trytes(TransactionHash), + }) diff --git a/iota/commands/extended/get_inputs.py b/iota/commands/extended/get_inputs.py index 77d62c1..0349c0c 100644 --- a/iota/commands/extended/get_inputs.py +++ b/iota/commands/extended/get_inputs.py @@ -1,6 +1,6 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from typing import Optional @@ -16,151 +16,164 @@ from iota.filters import SecurityLevel, Trytes __all__ = [ - 'GetInputsCommand', + 'GetInputsCommand', ] class GetInputsCommand(FilterCommand): - """ - Executes ``getInputs`` extended API command. - - See :py:meth:`iota.api.Iota.get_inputs` for more info. - """ - command = 'getInputs' - - def get_request_filter(self): - return GetInputsRequestFilter() - - def get_response_filter(self): - pass - - 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] - security_level = request['securityLevel'] # int - - # Determine the addresses we will be scanning. - if stop is None: - addresses =\ - [addy for addy, _ in iter_used_addresses(self.adapter, seed, start, security_level=security_level)] - else: - addresses = AddressGenerator(seed, security_level).get_addresses(start, stop - start) - - 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'], - }, - ) + """ + Executes ``getInputs`` extended API command. + + See :py:meth:`iota.api.Iota.get_inputs` for more info. + """ + command = 'getInputs' + + def get_request_filter(self): + return GetInputsRequestFilter() + + def get_response_filter(self): + pass + + 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] + security_level = request['securityLevel'] # int + + # Determine the addresses we will be scanning. + if stop is None: + addresses = [addy for addy, _ in iter_used_addresses( + adapter=self.adapter, + seed=seed, + start=start, + security_level=security_level + )] + else: + addresses = ( + AddressGenerator(seed, security_level).get_addresses( + start=start, + count=stop - start, + ) + ) + + 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'], + }, + ) class GetInputsRequestFilter(RequestFilter): - MAX_INTERVAL = 500 - - CODE_INTERVAL_INVALID = 'interval_invalid' - CODE_INTERVAL_TOO_BIG = 'interval_too_big' - - templates = { - CODE_INTERVAL_INVALID: '``start`` must be <= ``stop``', - CODE_INTERVAL_TOO_BIG: '``stop`` - ``start`` must be <= {max_interval}', - } - - def __init__(self): - super(GetInputsRequestFilter, self).__init__( - { - # These arguments are optional. - 'stop': f.Type(int) | f.Min(0), - 'start': f.Type(int) | f.Min(0) | f.Optional(0), - 'threshold': f.Type(int) | f.Min(0), - - 'securityLevel': SecurityLevel, - - # These arguments are required. - 'seed': f.Required | Trytes(result_type=Seed), - }, - - allow_missing_keys={ - 'stop', - 'start', - 'threshold', - 'securityLevel', - } - ) - - def _apply(self, value): - # noinspection PyProtectedMember - filtered = super(GetInputsRequestFilter, self)._apply(value) - - if self._has_errors: - return filtered - - if filtered['stop'] is not None: - if filtered['start'] > filtered['stop']: - filtered['start'] = self._invalid_value( - value = filtered['start'], - reason = self.CODE_INTERVAL_INVALID, - sub_key = 'start', - - context = { - 'start': filtered['start'], - 'stop': filtered['stop'], - }, - ) - elif (filtered['stop'] - filtered['start']) > self.MAX_INTERVAL: - filtered['stop'] = self._invalid_value( - value = filtered['stop'], - reason = self.CODE_INTERVAL_TOO_BIG, - sub_key = 'stop', - - context = { - 'start': filtered['start'], - 'stop': filtered['stop'], - }, - - template_vars = { - 'max_interval': self.MAX_INTERVAL, - }, + MAX_INTERVAL = 500 + + CODE_INTERVAL_INVALID = 'interval_invalid' + CODE_INTERVAL_TOO_BIG = 'interval_too_big' + + templates = { + CODE_INTERVAL_INVALID: '``start`` must be <= ``stop``', + CODE_INTERVAL_TOO_BIG: '``stop`` - ``start`` must be <= {max_interval}', + } + + def __init__(self): + super(GetInputsRequestFilter, self).__init__( + { + # These arguments are optional. + 'stop': f.Type(int) | f.Min(0), + 'start': f.Type(int) | f.Min(0) | f.Optional(0), + 'threshold': f.Type(int) | f.Min(0), + + 'securityLevel': SecurityLevel, + + # These arguments are required. + 'seed': f.Required | Trytes(Seed), + }, + + allow_missing_keys={ + 'stop', + 'start', + 'threshold', + 'securityLevel', + } ) - return filtered + def _apply(self, value): + filtered = super(GetInputsRequestFilter, self)._apply(value) + + if self._has_errors: + return filtered + + if filtered['stop'] is not None: + if filtered['start'] > filtered['stop']: + filtered['start'] = self._invalid_value( + value=filtered['start'], + reason=self.CODE_INTERVAL_INVALID, + sub_key='start', + + context={ + 'start': filtered['start'], + 'stop': filtered['stop'], + }, + ) + + elif (filtered['stop'] - filtered['start']) > self.MAX_INTERVAL: + filtered['stop'] = self._invalid_value( + value=filtered['stop'], + reason=self.CODE_INTERVAL_TOO_BIG, + sub_key='stop', + + context={ + 'start': filtered['start'], + 'stop': filtered['stop'], + }, + + template_vars={ + 'max_interval': self.MAX_INTERVAL, + }, + ) + + return filtered diff --git a/iota/commands/extended/get_latest_inclusion.py b/iota/commands/extended/get_latest_inclusion.py index 4d42a35..cf96bbf 100644 --- a/iota/commands/extended/get_latest_inclusion.py +++ b/iota/commands/extended/get_latest_inclusion.py @@ -1,10 +1,11 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from typing import List import filters as f + from iota import TransactionHash from iota.commands import FilterCommand, RequestFilter from iota.commands.core.get_inclusion_states import GetInclusionStatesCommand @@ -12,45 +13,44 @@ from iota.filters import Trytes __all__ = [ - 'GetLatestInclusionCommand', + 'GetLatestInclusionCommand', ] class GetLatestInclusionCommand(FilterCommand): - """ - Executes ``getLatestInclusion`` extended API command. + """ + Executes ``getLatestInclusion`` extended API command. - See :py:meth:`iota.api.Iota.get_latest_inclusion` for more info. - """ - command = 'getLatestInclusion' + See :py:meth:`iota.api.Iota.get_latest_inclusion` for more info. + """ + command = 'getLatestInclusion' - def get_request_filter(self): - return GetLatestInclusionRequestFilter() + def get_request_filter(self): + return GetLatestInclusionRequestFilter() - def get_response_filter(self): - pass + def get_response_filter(self): + pass - def _execute(self, request): - hashes = request['hashes'] # type: List[TransactionHash] + def _execute(self, request): + hashes = request['hashes'] # type: List[TransactionHash] - gni_response = GetNodeInfoCommand(self.adapter)() + gni_response = GetNodeInfoCommand(self.adapter)() - gis_response = GetInclusionStatesCommand(self.adapter)( - transactions = hashes, - tips = [gni_response['latestSolidSubtangleMilestone']], - ) + gis_response = GetInclusionStatesCommand(self.adapter)( + transactions=hashes, + tips=[gni_response['latestSolidSubtangleMilestone']], + ) - return { - 'states': dict(zip(hashes, gis_response['states'])), - } + return { + 'states': dict(zip(hashes, gis_response['states'])), + } class GetLatestInclusionRequestFilter(RequestFilter): - def __init__(self): - super(GetLatestInclusionRequestFilter, self).__init__({ - 'hashes': ( - f.Required - | f.Array - | f.FilterRepeater(f.Required | Trytes(result_type=TransactionHash)) - ), - }) + def __init__(self): + super(GetLatestInclusionRequestFilter, self).__init__({ + 'hashes': + f.Required | f.Array | f.FilterRepeater( + f.Required | Trytes(TransactionHash), + ), + }) diff --git a/iota/commands/extended/get_new_addresses.py b/iota/commands/extended/get_new_addresses.py index 552561d..fa9d088 100644 --- a/iota/commands/extended/get_new_addresses.py +++ b/iota/commands/extended/get_new_addresses.py @@ -1,6 +1,6 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from typing import List, Optional @@ -14,86 +14,82 @@ from iota.filters import SecurityLevel, Trytes __all__ = [ - 'GetNewAddressesCommand', + 'GetNewAddressesCommand', ] class GetNewAddressesCommand(FilterCommand): - """ - Executes ``getNewAddresses`` extended API command. - - See :py:meth:`iota.api.Iota.get_new_addresses` for more info. - """ - command = 'getNewAddresses' - - def get_request_filter(self): - return GetNewAddressesRequestFilter() - - def get_response_filter(self): - pass - - def _execute(self, request): - checksum = request['checksum'] # type: bool - count = request['count'] # type: Optional[int] - index = request['index'] # type: int - security_level = request['securityLevel'] # type: int - seed = request['seed'] # type: Seed - - return { - 'addresses': - self._find_addresses(seed, index, count, security_level, checksum), - } - - def _find_addresses(self, seed, index, count, security_level, checksum): - # type: (Seed, int, Optional[int], int, bool) -> List[Address] """ - Find addresses matching the command parameters. - """ - generator = AddressGenerator(seed, security_level, checksum) - - if count is None: - # Connect to Tangle and find the first address without any - # transactions. - for addy in generator.create_iterator(start=index): - # We use addy.address here because FindTransactions does - # not work on an address with a checksum - response = FindTransactionsCommand(self.adapter)( - addresses=[addy.address] - ) - - if not response.get('hashes'): - return [addy] + Executes ``getNewAddresses`` extended API command. - return generator.get_addresses(start=index, count=count) + See :py:meth:`iota.api.Iota.get_new_addresses` for more info. + """ + command = 'getNewAddresses' + + def get_request_filter(self): + return GetNewAddressesRequestFilter() + + def get_response_filter(self): + pass + + def _execute(self, request): + checksum = request['checksum'] # type: bool + count = request['count'] # type: Optional[int] + index = request['index'] # type: int + security_level = request['securityLevel'] # type: int + seed = request['seed'] # type: Seed + + return { + 'addresses': + self._find_addresses( + seed, + index, + count, + security_level, + checksum, + ), + } + + def _find_addresses(self, seed, index, count, security_level, checksum): + # type: (Seed, int, Optional[int], int, bool) -> List[Address] + """ + Find addresses matching the command parameters. + """ + generator = AddressGenerator(seed, security_level, checksum) + + if count is None: + # Connect to Tangle and find the first address without any + # transactions. + for addy in generator.create_iterator(start=index): + # We use addy.address here because FindTransactions does + # not work on an address with a checksum + response = FindTransactionsCommand(self.adapter)( + addresses=[addy.address], + ) + + if not response.get('hashes'): + return [addy] + + return generator.get_addresses(start=index, count=count) class GetNewAddressesRequestFilter(RequestFilter): - MAX_SECURITY_LEVEL = 3 - """ - Max allowed value for ``securityLevel``. - - Note that :py:class:`AddressGenerator` does not enforce a limit, just - in case you are sure that you REALLY know what you are doing. - """ - - def __init__(self): - super(GetNewAddressesRequestFilter, self).__init__( - { - # Everything except ``seed`` is optional. - - 'checksum': f.Type(bool) | f.Optional(default=False), - 'count': f.Type(int) | f.Min(1), - 'index': f.Type(int) | f.Min(0) | f.Optional(default=0), - - 'securityLevel': SecurityLevel, - - 'seed': f.Required | Trytes(result_type=Seed), - }, - - allow_missing_keys = { - 'checksum', - 'count', - 'index', - 'securityLevel', - }, - ) + def __init__(self): + super(GetNewAddressesRequestFilter, self).__init__( + { + # Everything except ``seed`` is optional. + 'checksum': f.Type(bool) | f.Optional(default=False), + 'count': f.Type(int) | f.Min(1), + 'index': f.Type(int) | f.Min(0) | f.Optional(default=0), + 'securityLevel': SecurityLevel, + + 'seed': f.Required | Trytes(Seed), + }, + + allow_missing_keys={ + 'checksum', + 'count', + 'index', + 'securityLevel', + }, + ) diff --git a/iota/commands/extended/get_transfers.py b/iota/commands/extended/get_transfers.py index e337042..a8e5e9a 100644 --- a/iota/commands/extended/get_transfers.py +++ b/iota/commands/extended/get_transfers.py @@ -1,134 +1,136 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from itertools import chain from typing import Optional import filters as f + from iota.commands import FilterCommand, RequestFilter from iota.commands.core.find_transactions import FindTransactionsCommand from iota.commands.extended.utils import get_bundles_from_transaction_hashes, \ - iter_used_addresses + iter_used_addresses from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed from iota.filters import Trytes __all__ = [ - 'GetTransfersCommand', + 'GetTransfersCommand', ] class GetTransfersCommand(FilterCommand): - """ - Executes ``getTransfers`` extended API command. - - See :py:meth:`iota.api.Iota.get_transfers` for more info. - """ - command = 'getTransfers' - - def get_request_filter(self): - return GetTransfersRequestFilter() - - def get_response_filter(self): - pass - - 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), - ) + """ + Executes ``getTransfers`` extended API command. + + See :py:meth:`iota.api.Iota.get_transfers` for more info. + """ + command = 'getTransfers' + + def get_request_filter(self): + return GetTransfersRequestFilter() + + def get_response_filter(self): + pass + + 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, + ), + } + + +class GetTransfersRequestFilter(RequestFilter): + MAX_INTERVAL = 500 - my_hashes = ft_response['hashes'] + CODE_INTERVAL_INVALID = 'interval_invalid' + CODE_INTERVAL_TOO_BIG = 'interval_too_big' - return { - 'bundles': - get_bundles_from_transaction_hashes( - adapter = self.adapter, - transaction_hashes = my_hashes, - inclusion_states = inclusion_states, - ), + templates = { + CODE_INTERVAL_INVALID: '``start`` must be <= ``stop``', + CODE_INTERVAL_TOO_BIG: '``stop`` - ``start`` must be <= {max_interval}', } + def __init__(self): + super(GetTransfersRequestFilter, self).__init__( + { + # Required parameters. + 'seed': f.Required | Trytes(Seed), -class GetTransfersRequestFilter(RequestFilter): - MAX_INTERVAL = 500 - - CODE_INTERVAL_INVALID = 'interval_invalid' - CODE_INTERVAL_TOO_BIG = 'interval_too_big' - - templates = { - CODE_INTERVAL_INVALID: '``start`` must be <= ``stop``', - CODE_INTERVAL_TOO_BIG: '``stop`` - ``start`` must be <= {max_interval}', - } - - def __init__(self): - super(GetTransfersRequestFilter, self).__init__( - { - # Required parameters. - 'seed': f.Required | Trytes(result_type=Seed), - - # Optional parameters. - 'stop': f.Type(int) | f.Min(0), - 'start': f.Type(int) | f.Min(0) | f.Optional(0), - - 'inclusionStates': f.Type(bool) | f.Optional(False), - }, - - allow_missing_keys = { - 'stop', - 'inclusionStates', - 'start', - }, - ) - - def _apply(self, value): - # noinspection PyProtectedMember - filtered = super(GetTransfersRequestFilter, self)._apply(value) - - if self._has_errors: - return filtered - - if filtered['stop'] is not None: - if filtered['start'] > filtered['stop']: - filtered['start'] = self._invalid_value( - value = filtered['start'], - reason = self.CODE_INTERVAL_INVALID, - sub_key = 'start', - - context = { - 'start': filtered['start'], - 'stop': filtered['stop'], - }, - ) - elif (filtered['stop'] - filtered['start']) > self.MAX_INTERVAL: - filtered['stop'] = self._invalid_value( - value = filtered['stop'], - reason = self.CODE_INTERVAL_TOO_BIG, - sub_key = 'stop', - - context = { - 'start': filtered['start'], - 'stop': filtered['stop'], - }, - - template_vars = { - 'max_interval': self.MAX_INTERVAL, - }, + # Optional parameters. + 'stop': f.Type(int) | f.Min(0), + 'start': f.Type(int) | f.Min(0) | f.Optional(0), + + 'inclusionStates': f.Type(bool) | f.Optional(False), + }, + + allow_missing_keys={ + 'stop', + 'inclusionStates', + 'start', + }, ) - return filtered + def _apply(self, value): + # noinspection PyProtectedMember + filtered = super(GetTransfersRequestFilter, self)._apply(value) + + if self._has_errors: + return filtered + + if filtered['stop'] is not None: + if filtered['start'] > filtered['stop']: + filtered['start'] = self._invalid_value( + value=filtered['start'], + reason=self.CODE_INTERVAL_INVALID, + sub_key='start', + + context={ + 'start': filtered['start'], + 'stop': filtered['stop'], + }, + ) + + elif (filtered['stop'] - filtered['start']) > self.MAX_INTERVAL: + filtered['stop'] = self._invalid_value( + value=filtered['stop'], + reason=self.CODE_INTERVAL_TOO_BIG, + sub_key='stop', + + context={ + 'start': filtered['start'], + 'stop': filtered['stop'], + }, + + template_vars={ + 'max_interval': self.MAX_INTERVAL, + }, + ) + + return filtered diff --git a/iota/commands/extended/is_reattachable.py b/iota/commands/extended/is_reattachable.py index b308567..8238dc6 100644 --- a/iota/commands/extended/is_reattachable.py +++ b/iota/commands/extended/is_reattachable.py @@ -1,5 +1,7 @@ # coding=utf-8 -from __future__ import absolute_import, division, print_function, unicode_literals +from __future__ import absolute_import, division, print_function, \ + unicode_literals + from typing import List import filters as f @@ -11,65 +13,76 @@ from iota.filters import Trytes __all__ = [ - 'IsReattachableCommand', + 'IsReattachableCommand', ] class IsReattachableCommand(FilterCommand): - """ - Executes ``isReattachable`` extended API command. - """ - command = 'isReattachable' + """ + Executes ``isReattachable`` extended API command. + """ + command = 'isReattachable' - def get_request_filter(self): - return IsReattachableRequestFilter() + def get_request_filter(self): + return IsReattachableRequestFilter() - def get_response_filter(self): - return IsReattachableResponseFilter() + def get_response_filter(self): + return IsReattachableResponseFilter() - def _execute(self, request): - addresses = request['addresses'] # type: List[Address] + def _execute(self, request): + addresses = request['addresses'] # type: List[Address] - # fetch full transaction objects - transactions = find_transaction_objects(adapter=self.adapter, **{'addresses': addresses}) + # fetch full transaction objects + transactions = find_transaction_objects( + adapter=self.adapter, + addresses=addresses, + ) - # map and filter transactions, which have zero value. - # If multiple transactions for the same address are returned the one with the - # highest attachment_timestamp is selected - transactions = sorted(transactions, key=lambda t: t.attachment_timestamp) - transaction_map = {t.address: t.hash for t in transactions if t.value > 0} + # Map and filter transactions which have zero value. + # If multiple transactions for the same address are returned, + # the one with the highest ``attachment_timestamp`` is selected. + transactions = sorted( + transactions, + key=lambda t: t.attachment_timestamp + ) - # fetch inclusion states - inclusion_states = GetLatestInclusionCommand(adapter=self.adapter)(hashes=list(transaction_map.values())) - inclusion_states = inclusion_states['states'] + transaction_map = { + t.address: t.hash + for t in transactions + if t.value > 0 + } + + # Fetch inclusion states. + inclusion_states = GetLatestInclusionCommand(adapter=self.adapter)( + hashes=list(transaction_map.values()), + ) + inclusion_states = inclusion_states['states'] - return { - 'reattachable': [not inclusion_states[transaction_map[address]] for address in addresses] - } + return { + 'reattachable': [ + not inclusion_states[transaction_map[address]] + for address in addresses + ], + } class IsReattachableRequestFilter(RequestFilter): - def __init__(self): - super(IsReattachableRequestFilter, self).__init__( - { - 'addresses': ( - f.Required - | f.Array - | f.FilterRepeater( - f.Required - | Trytes(result_type=Address) - | f.Unicode(encoding='ascii', normalize=False) - ) + def __init__(self): + super(IsReattachableRequestFilter, self).__init__( + { + 'addresses': + f.Required | f.Array | f.FilterRepeater( + f.Required | + Trytes(Address) | + f.Unicode(encoding='ascii', normalize=False), + ), + }, ) - } - ) class IsReattachableResponseFilter(ResponseFilter): - def __init__(self): - super(IsReattachableResponseFilter, self).__init__({ - 'reattachable': ( - f.Required - | f.Array - | f.FilterRepeater(f.Type(bool))) - }) + def __init__(self): + super(IsReattachableResponseFilter, self).__init__({ + 'reattachable': + f.Required | f.Array | f.FilterRepeater(f.Type(bool)), + }) diff --git a/iota/commands/extended/prepare_transfer.py b/iota/commands/extended/prepare_transfer.py index cd3cb90..dd725b8 100644 --- a/iota/commands/extended/prepare_transfer.py +++ b/iota/commands/extended/prepare_transfer.py @@ -1,13 +1,13 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from typing import List, Optional import filters as f from iota import Address, BadApiResponse, ProposedBundle, \ - ProposedTransaction + ProposedTransaction from iota.commands import FilterCommand, RequestFilter from iota.commands.core.get_balances import GetBalancesCommand from iota.commands.extended.get_inputs import GetInputsCommand @@ -18,131 +18,136 @@ from iota.filters import GeneratedAddress, SecurityLevel, Trytes __all__ = [ - 'PrepareTransferCommand', + 'PrepareTransferCommand', ] class PrepareTransferCommand(FilterCommand): - """ - Executes ``prepareTransfer`` extended API command. - - See :py:meth:`iota.api.Iota.prepare_transfer` for more info. - """ - command = 'prepareTransfer' - - def get_request_filter(self): - return PrepareTransferRequestFilter() - - def get_response_filter(self): - pass - - def _execute(self, request): - # Required parameters. - seed = request['seed'] # type: Seed - bundle = ProposedBundle(request['transfers']) - - # Optional parameters. - change_address = request.get('changeAddress') # type: Optional[Address] - proposed_inputs = request.get('inputs') # type: Optional[List[Address]] - security_level = request['securityLevel'] # type: int - - want_to_spend = bundle.balance - if want_to_spend > 0: - # We are spending inputs, so we need to gather and sign them. - if proposed_inputs is None: - # No inputs provided. Scan addresses for unspent inputs. - gi_response = GetInputsCommand(self.adapter)( - seed = seed, - threshold = want_to_spend, - securityLevel=security_level, - ) - - confirmed_inputs = gi_response['inputs'] - else: - # Inputs provided. Check to make sure we have sufficient - # balance. - available_to_spend = 0 - confirmed_inputs = [] # type: List[Address] - - gb_response = GetBalancesCommand(self.adapter)( - addresses = [i.address for i in proposed_inputs], - ) + """ + Executes ``prepareTransfer`` extended API command. - for i, balance in enumerate(gb_response.get('balances') or []): - input_ = proposed_inputs[i] - - if balance > 0: - available_to_spend += balance - - # Update the address balance from the API response, just in - # case somebody tried to cheat. - input_.balance = balance - confirmed_inputs.append(input_) - - if available_to_spend < want_to_spend: - raise with_context( - exc = BadApiResponse( - 'Insufficient balance; found {found}, need {need} ' - '(``exc.context`` has more info).'.format( - found = available_to_spend, - need = want_to_spend, - ), - ), - - context = { - 'available_to_spend': available_to_spend, - 'confirmed_inputs': confirmed_inputs, - 'request': request, - 'want_to_spend': want_to_spend, - }, - ) - - bundle.add_inputs(confirmed_inputs) + See :py:meth:`iota.api.Iota.prepare_transfer` for more info. + """ + command = 'prepareTransfer' - if bundle.balance < 0: - if not change_address: - change_address =\ - GetNewAddressesCommand(self.adapter)(seed=seed, securityLevel=security_level)['addresses'][0] + def get_request_filter(self): + return PrepareTransferRequestFilter() - bundle.send_unspent_inputs_to(change_address) + def get_response_filter(self): + pass - bundle.finalize() - - if confirmed_inputs: - bundle.sign_inputs(KeyGenerator(seed)) - else: - bundle.finalize() + def _execute(self, request): + # Required parameters. + seed = request['seed'] # type: Seed + bundle = ProposedBundle(request['transfers']) - return { - 'trytes': bundle.as_tryte_strings(), - } + # Optional parameters. + change_address = request.get('changeAddress') # type: Optional[Address] + proposed_inputs = request.get('inputs') # type: Optional[List[Address]] + security_level = request['securityLevel'] # type: int + + want_to_spend = bundle.balance + if want_to_spend > 0: + # We are spending inputs, so we need to gather and sign + # them. + if proposed_inputs is None: + # No inputs provided. Scan addresses for unspent + # inputs. + gi_response = GetInputsCommand(self.adapter)( + seed=seed, + threshold=want_to_spend, + securityLevel=security_level, + ) + + confirmed_inputs = gi_response['inputs'] + else: + # Inputs provided. Check to make sure we have + # sufficient balance. + available_to_spend = 0 + confirmed_inputs = [] # type: List[Address] + + gb_response = GetBalancesCommand(self.adapter)( + addresses=[i.address for i in proposed_inputs], + ) + + for i, balance in enumerate(gb_response.get('balances') or []): + input_ = proposed_inputs[i] + + if balance > 0: + available_to_spend += balance + + # Update the address balance from the API + # response, just in case somebody tried to + # cheat. + input_.balance = balance + confirmed_inputs.append(input_) + + if available_to_spend < want_to_spend: + raise with_context( + exc=BadApiResponse( + 'Insufficient balance; found {found}, need {need} ' + '(``exc.context`` has more info).'.format( + found=available_to_spend, + need=want_to_spend, + ), + ), + + context={ + 'available_to_spend': available_to_spend, + 'confirmed_inputs': confirmed_inputs, + 'request': request, + 'want_to_spend': want_to_spend, + }, + ) + + bundle.add_inputs(confirmed_inputs) + + if bundle.balance < 0: + if not change_address: + change_address = \ + GetNewAddressesCommand(self.adapter)( + seed=seed, + securityLevel=security_level, + )['addresses'][0] + + bundle.send_unspent_inputs_to(change_address) + + bundle.finalize() + + if confirmed_inputs: + bundle.sign_inputs(KeyGenerator(seed)) + else: + bundle.finalize() + + return { + 'trytes': bundle.as_tryte_strings(), + } class PrepareTransferRequestFilter(RequestFilter): - def __init__(self): - super(PrepareTransferRequestFilter, self).__init__( - { - # Required parameters. - 'seed': f.Required | Trytes(result_type=Seed), - - 'transfers': ( - f.Required - | f.Array - | f.FilterRepeater(f.Required | f.Type(ProposedTransaction)) - ), + def __init__(self): + super(PrepareTransferRequestFilter, self).__init__( + { + # Required parameters. + 'seed': f.Required | Trytes(Seed), + + 'transfers': + f.Required | f.Array | f.FilterRepeater( + f.Required | f.Type(ProposedTransaction), + ), + + # Optional parameters. + 'changeAddress': Trytes(Address), + 'securityLevel': SecurityLevel, + + # Note that ``inputs`` is allowed to be an empty array. + 'inputs': + f.Array | f.FilterRepeater(f.Required | GeneratedAddress), + }, - # Optional parameters. - 'changeAddress': Trytes(result_type=Address), - 'securityLevel': SecurityLevel, - - # Note that ``inputs`` is allowed to be an empty array. - 'inputs': - f.Array | f.FilterRepeater(f.Required | GeneratedAddress), - }, - - allow_missing_keys = { - 'changeAddress', - 'inputs', - 'securityLevel', - }, - ) + allow_missing_keys={ + 'changeAddress', + 'inputs', + 'securityLevel', + }, + ) diff --git a/iota/commands/extended/promote_transaction.py b/iota/commands/extended/promote_transaction.py index ccc6e3e..3bcbd1a 100644 --- a/iota/commands/extended/promote_transaction.py +++ b/iota/commands/extended/promote_transaction.py @@ -1,68 +1,68 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals import filters as f -from iota import ( - Bundle, TransactionHash, Address, ProposedTransaction, BadApiResponse, -) + +from iota import Address, BadApiResponse, ProposedTransaction, TransactionHash from iota.commands import FilterCommand, RequestFilter from iota.commands.core.check_consistency import CheckConsistencyCommand from iota.commands.extended.send_transfer import SendTransferCommand from iota.filters import Trytes __all__ = [ - 'PromoteTransactionCommand', + 'PromoteTransactionCommand', ] class PromoteTransactionCommand(FilterCommand): - """ - Executes ``promoteTransaction`` extended API command. + """ + Executes ``promoteTransaction`` extended API command. - See :py:meth:`iota.api.Iota.promote_transaction` for more information. - """ - command = 'promoteTransaction' + See :py:meth:`iota.api.Iota.promote_transaction` for more + information. + """ + command = 'promoteTransaction' - def get_request_filter(self): - return PromoteTransactionRequestFilter() + def get_request_filter(self): + return PromoteTransactionRequestFilter() - def get_response_filter(self): - pass + def get_response_filter(self): + pass - def _execute(self, request): - depth = request['depth'] # type: int - min_weight_magnitude = request['minWeightMagnitude'] # type: int - transaction = request['transaction'] # type: TransactionHash + def _execute(self, request): + depth = request['depth'] # type: int + min_weight_magnitude = request['minWeightMagnitude'] # type: int + transaction = request['transaction'] # type: TransactionHash - cc_response = CheckConsistencyCommand(self.adapter)(tails=[transaction]) - if cc_response['state'] is False: - raise BadApiResponse( - 'Transaction {transaction} is not promotable. ' - 'You should reattach first.'.format(transaction=transaction) - ) + cc_response = CheckConsistencyCommand(self.adapter)(tails=[transaction]) + if cc_response['state'] is False: + raise BadApiResponse( + 'Transaction {transaction} is not promotable. ' + 'You should reattach first.'.format(transaction=transaction) + ) - spam_transfer = ProposedTransaction( - address=Address(b''), - value=0, - ) + spam_transfer = ProposedTransaction( + address=Address(b''), + value=0, + ) - return SendTransferCommand(self.adapter)( - seed=spam_transfer.address, - depth=depth, - transfers=[spam_transfer], - minWeightMagnitude=min_weight_magnitude, - reference=transaction, - ) + return SendTransferCommand(self.adapter)( + seed=spam_transfer.address, + depth=depth, + transfers=[spam_transfer], + minWeightMagnitude=min_weight_magnitude, + reference=transaction, + ) class PromoteTransactionRequestFilter(RequestFilter): - def __init__(self): - super(PromoteTransactionRequestFilter, self).__init__({ - 'depth': f.Required | f.Type(int) | f.Min(1), - 'transaction': f.Required | Trytes(result_type=TransactionHash), + def __init__(self): + super(PromoteTransactionRequestFilter, self).__init__({ + 'depth': f.Required | f.Type(int) | f.Min(1), + 'transaction': f.Required | Trytes(TransactionHash), - # Loosely-validated; testnet nodes require a different value than - # mainnet. - 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), - }) + # Loosely-validated; testnet nodes require a different value + # than mainnet. + 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), + }) diff --git a/iota/commands/extended/replay_bundle.py b/iota/commands/extended/replay_bundle.py index e29eb35..aff8e1e 100644 --- a/iota/commands/extended/replay_bundle.py +++ b/iota/commands/extended/replay_bundle.py @@ -1,63 +1,59 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals - -from typing import List + unicode_literals import filters as f -from iota import Bundle -from iota import TransactionHash + +from iota import Bundle, TransactionHash from iota.commands import FilterCommand, RequestFilter from iota.commands.extended.get_bundles import GetBundlesCommand from iota.commands.extended.send_trytes import SendTrytesCommand from iota.filters import Trytes __all__ = [ - 'ReplayBundleCommand', + 'ReplayBundleCommand', ] class ReplayBundleCommand(FilterCommand): - """ - Executes ``replayBundle`` extended API command. - - See :py:meth:`iota.api.Iota.replay_bundle` for more information. - """ - command = 'replayBundle' - - def get_request_filter(self): - return ReplayBundleRequestFilter() + """ + Executes ``replayBundle`` extended API command. - def get_response_filter(self): - pass + See :py:meth:`iota.api.Iota.replay_bundle` for more information. + """ + command = 'replayBundle' - def _execute(self, request): - depth = request['depth'] # type: int - min_weight_magnitude = request['minWeightMagnitude'] # type: int - transaction = request['transaction'] # type: TransactionHash + def get_request_filter(self): + return ReplayBundleRequestFilter() - gb_response = GetBundlesCommand(self.adapter)(transaction=transaction) + def get_response_filter(self): + pass - # Note that we only replay the first bundle returned by - # ``getBundles``. - bundle = gb_response['bundles'][0] # type: Bundle + def _execute(self, request): + depth = request['depth'] # type: int + min_weight_magnitude = request['minWeightMagnitude'] # type: int + transaction = request['transaction'] # type: TransactionHash - return SendTrytesCommand(self.adapter)( - depth = depth, - minWeightMagnitude = min_weight_magnitude, + 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 - trytes = bundle.as_tryte_strings(), - ) + return SendTrytesCommand(self.adapter)( + depth=depth, + minWeightMagnitude=min_weight_magnitude, + trytes=bundle.as_tryte_strings(), + ) class ReplayBundleRequestFilter(RequestFilter): - def __init__(self): - super(ReplayBundleRequestFilter, self).__init__({ - 'depth': f.Required | f.Type(int) | f.Min(1), - 'transaction': f.Required | Trytes(result_type=TransactionHash), - - # Loosely-validated; testnet nodes require a different value than - # mainnet. - 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), - }) + def __init__(self): + super(ReplayBundleRequestFilter, self).__init__({ + 'depth': f.Required | f.Type(int) | f.Min(1), + 'transaction': f.Required | Trytes(TransactionHash), + + # Loosely-validated; testnet nodes require a different value + # than mainnet. + 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), + }) diff --git a/iota/commands/extended/send_transfer.py b/iota/commands/extended/send_transfer.py index 7c5060d..ada6607 100644 --- a/iota/commands/extended/send_transfer.py +++ b/iota/commands/extended/send_transfer.py @@ -1,6 +1,6 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from typing import List, Optional @@ -14,87 +14,86 @@ from iota.filters import SecurityLevel, Trytes __all__ = [ - 'SendTransferCommand', + 'SendTransferCommand', ] class SendTransferCommand(FilterCommand): - """ - Executes ``sendTransfer`` extended API command. - - See :py:meth:`iota.api.Iota.send_transfer` for more info. - """ - command = 'sendTransfer' - - def get_request_filter(self): - return SendTransferRequestFilter() - - def get_response_filter(self): - pass - - def _execute(self, request): - change_address = request['changeAddress'] # type: Optional[Address] - depth = request['depth'] # type: int - inputs = request['inputs'] # type: Optional[List[Address]] - min_weight_magnitude = request['minWeightMagnitude'] # type: int - seed = request['seed'] # type: Seed - transfers = request['transfers'] # type: List[ProposedTransaction] - reference = request['reference'] # type: Optional[TransactionHash] - security_level = request['securityLevel'] # int - - pt_response = PrepareTransferCommand(self.adapter)( - changeAddress = change_address, - inputs = inputs, - seed = seed, - transfers = transfers, - securityLevel = security_level, - ) - - st_response = SendTrytesCommand(self.adapter)( - depth = depth, - minWeightMagnitude = min_weight_magnitude, - trytes = pt_response['trytes'], - reference = reference, - ) - - return { - 'bundle': Bundle.from_tryte_strings(st_response['trytes']), - } + """ + Executes ``sendTransfer`` extended API command. + + See :py:meth:`iota.api.Iota.send_transfer` for more info. + """ + command = 'sendTransfer' + + def get_request_filter(self): + return SendTransferRequestFilter() + + def get_response_filter(self): + pass + + def _execute(self, request): + change_address = request['changeAddress'] # type: Optional[Address] + depth = request['depth'] # type: int + inputs = request['inputs'] # type: Optional[List[Address]] + min_weight_magnitude = request['minWeightMagnitude'] # type: int + seed = request['seed'] # type: Seed + transfers = request['transfers'] # type: List[ProposedTransaction] + reference = request['reference'] # type: Optional[TransactionHash] + security_level = request['securityLevel'] # int + + pt_response = PrepareTransferCommand(self.adapter)( + changeAddress=change_address, + inputs=inputs, + seed=seed, + transfers=transfers, + securityLevel=security_level, + ) + + st_response = SendTrytesCommand(self.adapter)( + depth=depth, + minWeightMagnitude=min_weight_magnitude, + trytes=pt_response['trytes'], + reference=reference, + ) + + return { + 'bundle': Bundle.from_tryte_strings(st_response['trytes']), + } class SendTransferRequestFilter(RequestFilter): - def __init__(self): - super(SendTransferRequestFilter, self).__init__( - { - # Required parameters. - 'depth': f.Required | f.Type(int) | f.Min(1), - 'seed': f.Required | Trytes(result_type=Seed), - - # Loosely-validated; testnet nodes require a different value - # than mainnet. - 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), - - 'transfers': ( - f.Required - | f.Array - | f.FilterRepeater(f.Required | f.Type(ProposedTransaction)) - ), - - # Optional parameters. - 'changeAddress': Trytes(result_type=Address), - 'securityLevel': SecurityLevel, - - # Note that ``inputs`` is allowed to be an empty array. - 'inputs': - f.Array | f.FilterRepeater(f.Required | Trytes(result_type=Address)), - - 'reference': Trytes(result_type=TransactionHash), - }, - - allow_missing_keys = { - 'changeAddress', - 'inputs', - 'reference', - 'securityLevel', - }, - ) + def __init__(self): + super(SendTransferRequestFilter, self).__init__( + { + # Required parameters. + 'depth': f.Required | f.Type(int) | f.Min(1), + 'seed': f.Required | Trytes(result_type=Seed), + + # Loosely-validated; testnet nodes require a different + # value than mainnet. + 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), + + 'transfers': + f.Required | f.Array | f.FilterRepeater( + f.Required | f.Type(ProposedTransaction), + ), + + # Optional parameters. + 'changeAddress': Trytes(result_type=Address), + 'securityLevel': SecurityLevel, + + # Note that ``inputs`` is allowed to be an empty array. + 'inputs': + f.Array | f.FilterRepeater(f.Required | Trytes(Address)), + + 'reference': Trytes(TransactionHash), + }, + + allow_missing_keys={ + 'changeAddress', + 'inputs', + 'reference', + 'securityLevel', + }, + ) diff --git a/iota/commands/extended/send_trytes.py b/iota/commands/extended/send_trytes.py index 1b43d84..7e54d38 100644 --- a/iota/commands/extended/send_trytes.py +++ b/iota/commands/extended/send_trytes.py @@ -1,85 +1,86 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from typing import List, Optional import filters as f -from iota import TransactionTrytes, TryteString, TransactionHash + +from iota import TransactionHash, TransactionTrytes, TryteString from iota.commands import FilterCommand, RequestFilter from iota.commands.core.attach_to_tangle import AttachToTangleCommand from iota.commands.core.get_transactions_to_approve import \ - GetTransactionsToApproveCommand + GetTransactionsToApproveCommand from iota.commands.extended.broadcast_and_store import BroadcastAndStoreCommand from iota.filters import Trytes __all__ = [ - 'SendTrytesCommand', + 'SendTrytesCommand', ] class SendTrytesCommand(FilterCommand): - """ - Executes `sendTrytes` extended API command. + """ + Executes `sendTrytes` extended API command. - See :py:meth:`iota.api.IotaApi.send_trytes` for more info. - """ - command = 'sendTrytes' + See :py:meth:`iota.api.IotaApi.send_trytes` for more info. + """ + command = 'sendTrytes' - def get_request_filter(self): - return SendTrytesRequestFilter() + def get_request_filter(self): + return SendTrytesRequestFilter() - def get_response_filter(self): - pass + def get_response_filter(self): + pass - def _execute(self, request): - depth = request['depth'] # type: int - min_weight_magnitude = request['minWeightMagnitude'] # type: int - trytes = request['trytes'] # type: List[TryteString] - reference = request['reference'] # type: Optional[TransactionHash] + def _execute(self, request): + depth = request['depth'] # type: int + min_weight_magnitude = request['minWeightMagnitude'] # type: int + trytes = request['trytes'] # type: List[TryteString] + reference = request['reference'] # type: Optional[TransactionHash] - # Call ``getTransactionsToApprove`` to locate trunk and branch - # transactions so that we can attach the bundle to the Tangle. - gta_response = GetTransactionsToApproveCommand(self.adapter)( - depth=depth, - reference=reference, - ) + # Call ``getTransactionsToApprove`` to locate trunk and branch + # transactions so that we can attach the bundle to the Tangle. + gta_response = GetTransactionsToApproveCommand(self.adapter)( + depth=depth, + reference=reference, + ) - att_response = AttachToTangleCommand(self.adapter)( - branchTransaction = gta_response.get('branchTransaction'), - trunkTransaction = gta_response.get('trunkTransaction'), + att_response = AttachToTangleCommand(self.adapter)( + branchTransaction=gta_response.get('branchTransaction'), + trunkTransaction=gta_response.get('trunkTransaction'), - minWeightMagnitude = min_weight_magnitude, - trytes = trytes, - ) + minWeightMagnitude=min_weight_magnitude, + trytes=trytes, + ) - # ``trytes`` now have POW! - trytes = att_response['trytes'] + # ``trytes`` now have POW! + trytes = att_response['trytes'] - BroadcastAndStoreCommand(self.adapter)(trytes=trytes) + BroadcastAndStoreCommand(self.adapter)(trytes=trytes) - return { - 'trytes': trytes, - } + return { + 'trytes': trytes, + } class SendTrytesRequestFilter(RequestFilter): - def __init__(self): - super(SendTrytesRequestFilter, self).__init__({ - 'depth': f.Required | f.Type(int) | f.Min(1), + def __init__(self): + super(SendTrytesRequestFilter, self).__init__({ + 'depth': f.Required | f.Type(int) | f.Min(1), - 'trytes': - f.Required - | f.Array - | f.FilterRepeater(f.Required | Trytes(result_type=TransactionTrytes)), + 'trytes': + f.Required | f.Array | f.FilterRepeater( + f.Required | Trytes(TransactionTrytes), + ), - # Loosely-validated; testnet nodes require a different value than - # mainnet. - 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), + # Loosely-validated; testnet nodes require a different value + # than mainnet. + 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), - 'reference': Trytes(result_type=TransactionHash), - }, + 'reference': Trytes(TransactionHash), + }, - allow_missing_keys = { - 'reference', - }) + allow_missing_keys={ + 'reference', + }) diff --git a/iota/commands/extended/utils.py b/iota/commands/extended/utils.py index a3a2807..740ec43 100644 --- a/iota/commands/extended/utils.py +++ b/iota/commands/extended/utils.py @@ -1,17 +1,17 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from typing import Generator, Iterable, List, Optional, Tuple from iota import Address, Bundle, Transaction, \ - TransactionHash + TransactionHash from iota.adapter import BaseAdapter from iota.commands.core.find_transactions import FindTransactionsCommand from iota.commands.core.get_trytes import GetTrytesCommand from iota.commands.extended.get_bundles import GetBundlesCommand from iota.commands.extended.get_latest_inclusion import \ - GetLatestInclusionCommand + GetLatestInclusionCommand from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed @@ -27,114 +27,119 @@ def find_transaction_objects(adapter, **kwargs): hashes = ft_response['hashes'] if hashes: - gt_response = GetTrytesCommand(adapter)(hashes=hashes) + gt_response = GetTrytesCommand(adapter)(hashes=hashes) - return list(map( - Transaction.from_tryte_string, - gt_response.get('trytes') or [], - )) # type: List[Transaction] + return list(map( + Transaction.from_tryte_string, + gt_response.get('trytes') or [], + )) # type: List[Transaction] return [] -def iter_used_addresses(adapter, seed, start, security_level=None): - # type: (BaseAdapter, Seed, int, Optional[int]) -> Generator[Tuple[Address, List[TransactionHash]]] - """ - Scans the Tangle for used addresses. +def iter_used_addresses( + adapter, # type: BaseAdapter + seed, # type: Seed + start, # type: int + security_level=None, # type: Optional[int] +): + # type: (...) -> Generator[Tuple[Address, List[TransactionHash]], None, None] + """ + Scans the Tangle for used addresses. - This is basically the opposite of invoking ``getNewAddresses`` with - ``stop=None``. - """ - if security_level is None: - security_level = AddressGenerator.DEFAULT_SECURITY_LEVEL + This is basically the opposite of invoking ``getNewAddresses`` with + ``stop=None``. + """ + if security_level is None: + security_level = AddressGenerator.DEFAULT_SECURITY_LEVEL - ft_command = FindTransactionsCommand(adapter) + ft_command = FindTransactionsCommand(adapter) - for addy in AddressGenerator(seed, security_level=security_level).create_iterator(start): - ft_response = ft_command(addresses=[addy]) + 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 + if ft_response['hashes']: + yield addy, ft_response['hashes'] + else: + break - # Reset the command so that we can call it again. - ft_command.reset() + # Reset the command so that we can call it again. + ft_command.reset() def get_bundles_from_transaction_hashes( - adapter, - transaction_hashes, - inclusion_states, + 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), - ) + # 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 [] - for txn in tail_transactions: - txn.is_confirmed = gli_response['states'].get(txn.hash) + my_bundles = [] # type: List[Bundle] - # 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] + # 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: - for bundle in txn_bundles: - bundle.is_confirmed = txn.is_confirmed + 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) + my_bundles.extend(txn_bundles) - return list(sorted( - my_bundles, - key = lambda bundle_: bundle_.tail_transaction.timestamp, - )) + return list(sorted( + my_bundles, + key=lambda bundle_: bundle_.tail_transaction.timestamp, + ))