diff --git a/docs/core_api.rst b/docs/core_api.rst index 529b71f..b2026a0 100644 --- a/docs/core_api.rst +++ b/docs/core_api.rst @@ -84,11 +84,6 @@ iri/references/api-reference>`__. .. automethod:: Iota.get_node_info .. automethod:: AsyncIota.get_node_info -``get_tips`` ------------- -.. automethod:: Iota.get_tips -.. automethod:: AsyncIota.get_tips - ``get_transactions_to_approve`` ------------------------------- .. automethod:: Iota.get_transactions_to_approve diff --git a/docs/extended_api.rst b/docs/extended_api.rst index abb437a..56f3502 100644 --- a/docs/extended_api.rst +++ b/docs/extended_api.rst @@ -52,11 +52,6 @@ tasks such as sending and receiving transfers. .. automethod:: Iota.get_inputs .. automethod:: AsyncIota.get_inputs -``get_latest_inclusion`` ------------------------- -.. automethod:: Iota.get_latest_inclusion -.. automethod:: AsyncIota.get_latest_inclusion - ``get_new_addresses`` --------------------- .. automethod:: Iota.get_new_addresses @@ -72,6 +67,11 @@ tasks such as sending and receiving transfers. .. automethod:: Iota.get_transfers .. automethod:: AsyncIota.get_transfers +``is_confirmed`` +----------------- +.. automethod:: Iota.is_confirmed +.. automethod:: AsyncIota.is_confirmed + ``is_promotable`` ----------------- .. automethod:: Iota.is_promotable diff --git a/iota/api.py b/iota/api.py index 1f42cec..23bb611 100644 --- a/iota/api.py +++ b/iota/api.py @@ -314,13 +314,11 @@ def find_transactions( def get_balances( self, addresses: Iterable[Address], - threshold: int = 100, tips: Optional[Iterable[TransactionHash]] = None, ) -> dict: """ - Similar to :py:meth:`get_inclusion_states`. Returns the - confirmed balance which a list of addresses have at the latest - confirmed milestone. + Returns the confirmed balance which a list of addresses have at the + latest confirmed milestone. In addition to the balances, it also returns the milestone as well as the index with which the confirmed balance was @@ -330,9 +328,6 @@ def get_balances( :param Iterable[Address] addresses: List of addresses to get the confirmed balance for. - :param int threshold: - Confirmation threshold between 0 and 100. - :param Optional[Iterable[TransactionHash]] tips: Tips whose history of transactions to traverse to find the balance. @@ -364,7 +359,6 @@ def get_balances( return asyncio.get_event_loop().run_until_complete( super().get_balances( addresses, - threshold, tips, ) ) @@ -372,22 +366,16 @@ def get_balances( def get_inclusion_states( self, transactions: Iterable[TransactionHash], - tips: Iterable[TransactionHash] ) -> dict: """ Get the inclusion states of a set of transactions. This is for determining if a transaction was accepted and confirmed by the - network or not. You can search for multiple tips (and thus, - milestones) to get past inclusion states of transactions. + network or not. :param Iterable[TransactionHash] transactions: List of transactions you want to get the inclusion state for. - :param Iterable[TransactionHash] tips: - List of tips (including milestones) you want to search for - the inclusion state. - :return: ``dict`` with the following structure:: @@ -410,10 +398,12 @@ def get_inclusion_states( return asyncio.get_event_loop().run_until_complete( super().get_inclusion_states( transactions, - tips, ) ) + # Add an alias for this call, more descriptive + is_confirmed = get_inclusion_states + def get_missing_transactions(self) -> dict: """ Returns all transaction hashes that a node is currently requesting @@ -568,33 +558,6 @@ def get_node_info(self) -> dict: super().get_node_info() ) - def get_tips(self) -> dict: - """ - Returns the list of tips (transactions which have no other - transactions referencing them). - - :return: - ``dict`` with the following structure:: - - { - 'hashes': List[TransactionHash], - List of tip transaction hashes. - 'duration': int, - Number of milliseconds it took to complete the request. - } - - References: - - - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettips - - https://docs.iota.org/docs/dev-essentials/0.1/references/glossary - """ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().get_tips() - ) - def get_transactions_to_approve( self, depth: int, @@ -1201,36 +1164,6 @@ def get_inputs( ) ) - def get_latest_inclusion( - self, - hashes: Iterable[TransactionHash] - ) -> Dict[str, Dict[TransactionHash, bool]]: - """ - Fetches the inclusion state for the specified transaction - hashes, as of the latest milestone that the node has processed. - - Effectively, this is :py:meth:`get_node_info` + - :py:meth:`get_inclusion_states`. - - :param Iterable[TransactionHash] hashes: - List of transaction hashes. - - :return: - ``dict`` with the following structure:: - - { - "states": Dict[TransactionHash, bool] - ``dict`` with one boolean per transaction hash in - ``hashes``. - } - - """ - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().get_latest_inclusion(hashes) - ) - def get_new_addresses( self, index: int = 0, diff --git a/iota/api_async.py b/iota/api_async.py index 7a85003..63f9cff 100644 --- a/iota/api_async.py +++ b/iota/api_async.py @@ -316,13 +316,11 @@ async def find_transactions( async def get_balances( self, addresses: Iterable[Address], - threshold: int = 100, tips: Optional[Iterable[TransactionHash]] = None, ) -> dict: """ - Similar to :py:meth:`get_inclusion_states`. Returns the - confirmed balance which a list of addresses have at the latest - confirmed milestone. + Returns the confirmed balance which a list of addresses have at the + latest confirmed milestone. In addition to the balances, it also returns the milestone as well as the index with which the confirmed balance was @@ -332,9 +330,6 @@ async def get_balances( :param Iterable[Address] addresses: List of addresses to get the confirmed balance for. - :param int threshold: - Confirmation threshold between 0 and 100. - :param Optional[Iterable[TransactionHash]] tips: Tips whose history of transactions to traverse to find the balance. @@ -362,29 +357,22 @@ async def get_balances( """ return await core.GetBalancesCommand(self.adapter)( addresses=addresses, - threshold=threshold, tips=tips, ) async def get_inclusion_states( self, transactions: Iterable[TransactionHash], - tips: Iterable[TransactionHash] ) -> dict: """ Get the inclusion states of a set of transactions. This is for determining if a transaction was accepted and confirmed by the - network or not. You can search for multiple tips (and thus, - milestones) to get past inclusion states of transactions. + network or not. :param Iterable[TransactionHash] transactions: List of transactions you want to get the inclusion state for. - :param Iterable[TransactionHash] tips: - List of tips (including milestones) you want to search for - the inclusion state. - :return: ``dict`` with the following structure:: @@ -403,9 +391,11 @@ async def get_inclusion_states( """ return await core.GetInclusionStatesCommand(self.adapter)( transactions=transactions, - tips=tips, ) + # Add an alias, more descriptive + is_confirmed = get_inclusion_states + async def get_missing_transactions(self) -> dict: """ Returns all transaction hashes that a node is currently requesting @@ -540,28 +530,6 @@ async def get_node_info(self) -> dict: """ return await core.GetNodeInfoCommand(self.adapter)() - async def get_tips(self) -> dict: - """ - Returns the list of tips (transactions which have no other - transactions referencing them). - - :return: - ``dict`` with the following structure:: - - { - 'hashes': List[TransactionHash], - List of tip transaction hashes. - 'duration': int, - Number of milliseconds it took to complete the request. - } - - References: - - - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettips - - https://docs.iota.org/docs/dev-essentials/0.1/references/glossary - """ - return await core.GetTipsCommand(self.adapter)() - async def get_transactions_to_approve( self, depth: int, @@ -1123,32 +1091,6 @@ async def get_inputs( securityLevel=security_level ) - async def get_latest_inclusion( - self, - hashes: Iterable[TransactionHash] - ) -> Dict[str, Dict[TransactionHash, bool]]: - """ - Fetches the inclusion state for the specified transaction - hashes, as of the latest milestone that the node has processed. - - Effectively, this is :py:meth:`get_node_info` + - :py:meth:`get_inclusion_states`. - - :param Iterable[TransactionHash] hashes: - List of transaction hashes. - - :return: - ``dict`` with the following structure:: - - { - "states": Dict[TransactionHash, bool] - ``dict`` with one boolean per transaction hash in - ``hashes``. - } - - """ - return await extended.GetLatestInclusionCommand(self.adapter)(hashes=hashes) - async def get_new_addresses( self, index: int = 0, diff --git a/iota/commands/core/__init__.py b/iota/commands/core/__init__.py index 8acf194..3436625 100644 --- a/iota/commands/core/__init__.py +++ b/iota/commands/core/__init__.py @@ -17,7 +17,6 @@ from .get_neighbors import * from .get_node_api_configuration import * from .get_node_info import * -from .get_tips import * from .get_transactions_to_approve import * from .get_trytes import * from .interrupt_attaching_to_tangle import * diff --git a/iota/commands/core/get_balances.py b/iota/commands/core/get_balances.py index 5c54751..06a91f7 100644 --- a/iota/commands/core/get_balances.py +++ b/iota/commands/core/get_balances.py @@ -35,17 +35,11 @@ def __init__(self) -> None: f.Unicode(encoding='ascii', normalize=False), ), - 'threshold': - f.Type(int) | - f.Min(0) | - f.Max(100) | - f.Optional(default=100), - 'tips': StringifiedTrytesArray(TransactionHash), }, allow_missing_keys={ - 'threshold', 'tips', + 'tips', }, ) diff --git a/iota/commands/core/get_inclusion_states.py b/iota/commands/core/get_inclusion_states.py index c7f70ea..87b29dd 100644 --- a/iota/commands/core/get_inclusion_states.py +++ b/iota/commands/core/get_inclusion_states.py @@ -31,14 +31,5 @@ def __init__(self) -> None: # Required parameters. 'transactions': StringifiedTrytesArray(TransactionHash) | f.Required, - - # Optional parameters. - 'tips': - StringifiedTrytesArray(TransactionHash) | - f.Optional(default=[]), - }, - - allow_missing_keys={ - 'tips', }, ) diff --git a/iota/commands/core/get_node_info.py b/iota/commands/core/get_node_info.py index 14f9471..6b1f450 100644 --- a/iota/commands/core/get_node_info.py +++ b/iota/commands/core/get_node_info.py @@ -1,6 +1,6 @@ import filters as f -from iota import TransactionHash +from iota import TransactionHash, Address from iota.commands import FilterCommand, RequestFilter, ResponseFilter from iota.filters import Trytes @@ -34,6 +34,8 @@ def __init__(self) -> None: class GetNodeInfoResponseFilter(ResponseFilter): def __init__(self) -> None: super(GetNodeInfoResponseFilter, self).__init__({ + 'coordinatorAddress': + f.ByteString(encoding='ascii') | Trytes(Address), 'latestMilestone': f.ByteString(encoding='ascii') | Trytes(TransactionHash), diff --git a/iota/commands/core/get_tips.py b/iota/commands/core/get_tips.py deleted file mode 100644 index 77d7375..0000000 --- a/iota/commands/core/get_tips.py +++ /dev/null @@ -1,41 +0,0 @@ -import filters as f - -from iota.commands import FilterCommand, RequestFilter, ResponseFilter -from iota.filters import Trytes -from iota.transaction.types import TransactionHash - -__all__ = [ - 'GetTipsCommand', -] - - -class GetTipsCommand(FilterCommand): - """ - Executes ``getTips`` command. - - See :py:meth:`iota.api.StrictIota.get_tips`. - """ - command = 'getTips' - - def get_request_filter(self): - return GetTipsRequestFilter() - - def get_response_filter(self): - return GetTipsResponseFilter() - - -class GetTipsRequestFilter(RequestFilter): - def __init__(self) -> None: - # ``getTips`` doesn't accept any parameters. - # Using a filter here just to enforce that the request is empty. - super(GetTipsRequestFilter, self).__init__({}) - - -class GetTipsResponseFilter(ResponseFilter): - def __init__(self) -> None: - super(GetTipsResponseFilter, self).__init__({ - 'hashes': - f.Array | f.FilterRepeater( - f.ByteString(encoding='ascii') | Trytes(TransactionHash), - ), - }) diff --git a/iota/commands/extended/__init__.py b/iota/commands/extended/__init__.py index 1ab530e..5b9130c 100644 --- a/iota/commands/extended/__init__.py +++ b/iota/commands/extended/__init__.py @@ -14,7 +14,6 @@ from .get_account_data import * from .get_bundles import * from .get_inputs import * -from .get_latest_inclusion import * from .get_new_addresses import * from .get_transaction_objects import * from .get_transfers import * diff --git a/iota/commands/extended/get_latest_inclusion.py b/iota/commands/extended/get_latest_inclusion.py deleted file mode 100644 index afd5e3e..0000000 --- a/iota/commands/extended/get_latest_inclusion.py +++ /dev/null @@ -1,52 +0,0 @@ -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 -from iota.commands.core.get_node_info import GetNodeInfoCommand -from iota.filters import Trytes - -__all__ = [ - 'GetLatestInclusionCommand', -] - - -class GetLatestInclusionCommand(FilterCommand): - """ - Executes ``getLatestInclusion`` extended API command. - - See :py:meth:`iota.api.Iota.get_latest_inclusion` for more info. - """ - command = 'getLatestInclusion' - - def get_request_filter(self): - return GetLatestInclusionRequestFilter() - - def get_response_filter(self): - pass - - async def _execute(self, request: dict) -> dict: - hashes: List[TransactionHash] = request['hashes'] - - gni_response = await GetNodeInfoCommand(self.adapter)() - - gis_response = await GetInclusionStatesCommand(self.adapter)( - transactions=hashes, - tips=[gni_response['latestSolidSubtangleMilestone']], - ) - - return { - 'states': dict(zip(hashes, gis_response['states'])), - } - - -class GetLatestInclusionRequestFilter(RequestFilter): - def __init__(self) -> None: - super(GetLatestInclusionRequestFilter, self).__init__({ - 'hashes': - f.Required | f.Array | f.FilterRepeater( - f.Required | Trytes(TransactionHash), - ), - }) diff --git a/iota/commands/extended/is_reattachable.py b/iota/commands/extended/is_reattachable.py index 97b3dea..6b4c117 100644 --- a/iota/commands/extended/is_reattachable.py +++ b/iota/commands/extended/is_reattachable.py @@ -4,9 +4,9 @@ from iota import Address from iota.commands import FilterCommand, RequestFilter, ResponseFilter -from iota.commands.extended import FindTransactionObjectsCommand, \ - GetLatestInclusionCommand -from iota.filters import StringifiedTrytesArray +from iota.commands.extended import FindTransactionObjectsCommand +from iota.commands.core import GetInclusionStatesCommand +from iota.filters import Trytes, StringifiedTrytesArray __all__ = [ 'IsReattachableCommand', @@ -48,14 +48,15 @@ async def _execute(self, request: dict) -> dict: } # Fetch inclusion states. - inclusion_states = await GetLatestInclusionCommand(adapter=self.adapter)( - hashes=list(transaction_map.values()), + inclusion_states = GetInclusionStatesCommand(adapter=self.adapter)( + transactions=list(transaction_map.values()), ) - inclusion_states = inclusion_states['states'] + inclusion_states_map = dict(zip( + list(transaction_map.keys()), inclusion_states['states'])) return { 'reattachable': [ - not inclusion_states[transaction_map[address]] + not inclusion_states_map[address] for address in addresses ], } diff --git a/iota/commands/extended/utils.py b/iota/commands/extended/utils.py index e68353b..4519f73 100644 --- a/iota/commands/extended/utils.py +++ b/iota/commands/extended/utils.py @@ -10,8 +10,8 @@ WereAddressesSpentFromCommand from iota.commands.extended import FindTransactionObjectsCommand from iota.commands.extended.get_bundles import GetBundlesCommand -from iota.commands.extended.get_latest_inclusion import \ - GetLatestInclusionCommand +from iota.commands.core.get_inclusion_states import \ + GetInclusionStatesCommand from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed @@ -123,12 +123,12 @@ async def get_bundles_from_transaction_hashes( # Attach inclusion states, if requested. if inclusion_states: - gli_response = await GetLatestInclusionCommand(adapter)( - hashes=list(tail_transaction_hashes), + gli_response = await GetInclusionStatesCommand(adapter)( + transactions=list(tail_transaction_hashes), ) - for txn in tail_transactions: - txn.is_confirmed = gli_response['states'].get(txn.hash) + for txn, state in zip(tail_transactions, gli_response['states']): + txn.is_confirmed = state # Find the bundles for each transaction. txn_bundles: List[Bundle] = (await GetBundlesCommand(adapter)( diff --git a/setup.py b/setup.py index 89444b5..813d979 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ name='PyOTA', description='IOTA API library for Python', url='https://github.com/iotaledger/iota.py', - version='3.0.0b2', + version='3.1.0b1', long_description=long_description, diff --git a/test/commands/core/get_balances_test.py b/test/commands/core/get_balances_test.py index acbe919..9398f6a 100644 --- a/test/commands/core/get_balances_test.py +++ b/test/commands/core/get_balances_test.py @@ -40,8 +40,6 @@ def test_pass_happy_path(self): request = { # Raw trytes are extracted to match the IRI's JSON protocol. 'addresses': [self.trytes1, self.trytes2], - - 'threshold': 80, } filter_ = self._filter(request) @@ -56,8 +54,6 @@ def test_pass_happy_path_with_tips(self): request = { 'addresses': [self.trytes1, self.trytes2], - 'threshold': 80, - 'tips': [self.trytes3], } @@ -76,8 +72,6 @@ def test_pass_compatible_types(self): Address(self.trytes1), bytearray(self.trytes2.encode('ascii')), ], - - 'threshold': 80, } filter_ = self._filter(request) @@ -88,28 +82,6 @@ def test_pass_compatible_types(self): { 'addresses': [self.trytes1, self.trytes2], - 'threshold': 80, - }, - ) - - def test_pass_threshold_optional(self): - """ - The incoming request does not contain a ``threshold`` value, so the - default value is assumed. - """ - request = { - 'addresses': [Address(self.trytes1)], - } - - filter_ = self._filter(request) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'addresses': [Address(self.trytes1)], - 'threshold': 100, }, ) @@ -133,13 +105,12 @@ def test_fail_unexpected_parameters(self): { 'addresses': [Address(self.trytes1)], - # I've had a perfectly wonderful evening. - # But this wasn't it. - 'foo': 'bar', + # `threshold` parameter deprecated in IRI 1.8.6 + 'threshold': 100, }, { - 'foo': [f.FilterMapper.CODE_EXTRA_KEY], + 'threshold': [f.FilterMapper.CODE_EXTRA_KEY], }, ) @@ -202,71 +173,6 @@ def test_fail_addresses_contents_invalid(self): }, ) - def test_fail_threshold_float(self): - """ - `threshold` is a float. - """ - self.assertFilterErrors( - { - # Even with an empty fpart, floats are not accepted. - 'threshold': 86.0, - - 'addresses': [Address(self.trytes1)], - }, - - { - 'threshold': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_threshold_string(self): - """ - ``threshold`` is a string. - """ - self.assertFilterErrors( - { - 'threshold': '86', - - 'addresses': [Address(self.trytes1)], - }, - - { - 'threshold': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_threshold_too_small(self): - """ - ``threshold`` is less than 0. - """ - self.assertFilterErrors( - { - 'threshold': -1, - - 'addresses': [Address(self.trytes1)], - }, - - { - 'threshold': [f.Min.CODE_TOO_SMALL], - }, - ) - - def test_fail_threshold_too_big(self): - """ - ``threshold`` is greater than 100. - """ - self.assertFilterErrors( - { - 'threshold': 101, - - 'addresses': [Address(self.trytes1)], - }, - - { - 'threshold': [f.Max.CODE_TOO_BIG], - }, - ) - def test_fail_tips_contents_invalid(self): """ ``tips`` is an array, but it contains invalid values. diff --git a/test/commands/core/get_inclusion_states_test.py b/test/commands/core/get_inclusion_states_test.py index dd49efc..a052eb4 100644 --- a/test/commands/core/get_inclusion_states_test.py +++ b/test/commands/core/get_inclusion_states_test.py @@ -33,11 +33,6 @@ def test_pass_happy_path(self): request = { # Raw trytes are extracted to match the IRI's JSON protocol. 'transactions': [self.trytes1, self.trytes2], - - # These values would normally be different from - # ``transactions``, but for purposes of this unit test, we just - # need to make sure the format is correct. - 'tips': [self.trytes1, self.trytes2], } filter_ = self._filter(request) @@ -45,24 +40,6 @@ def test_pass_happy_path(self): self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request) - def test_pass_optional_parameters_omitted(self): - """ - The request omits optional parameters. - """ - filter_ = self._filter({ - 'transactions': [self.trytes1, self.trytes2], - }) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'tips': [], - 'transactions': [self.trytes1, self.trytes2], - }, - ) - def test_pass_compatible_types(self): """ The request contains values that can be converted to expected @@ -73,11 +50,6 @@ def test_pass_compatible_types(self): TransactionHash(self.trytes1), bytearray(self.trytes2.encode('ascii')), ], - - 'tips': [ - TransactionHash(self.trytes1), - bytearray(self.trytes2.encode('ascii')), - ], }) self.assertFilterPasses(filter_) @@ -85,7 +57,6 @@ def test_pass_compatible_types(self): filter_.cleaned_data, { - 'tips': [self.trytes1, self.trytes2], 'transactions': [self.trytes1, self.trytes2], }, ) @@ -110,12 +81,12 @@ def test_fail_unexpected_parameters(self): { 'transactions': [TransactionHash(self.trytes1)], - # I bring scientists, you bring a rock star. - 'foo': 'bar', + # 'tips' deprecated in IRI 1.9.0 + 'tips': [self.trytes1] }, { - 'foo': [f.FilterMapper.CODE_EXTRA_KEY], + 'tips': [f.FilterMapper.CODE_EXTRA_KEY], }, ) @@ -195,55 +166,6 @@ def test_fail_transactions_contents_invalid(self): }, ) - def test_fail_tips_wrong_type(self): - """ - ``tips`` is not an array. - """ - self.assertFilterErrors( - { - 'tips': TransactionHash(self.trytes2), - - 'transactions': [TransactionHash(self.trytes1)], - }, - - { - 'tips': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_tips_contents_invalid(self): - """ - ``tips`` contains invalid values. - """ - self.assertFilterErrors( - { - 'tips': [ - b'', - True, - None, - b'not valid trytes', - - # This is actually valid; I just added it to make sure the - # filter isn't cheating! - TryteString(self.trytes1), - - 2130706433, - b'9' * 82, - ], - - 'transactions': [TransactionHash(self.trytes1)], - }, - - { - 'tips.0': [f.Required.CODE_EMPTY], - 'tips.1': [f.Type.CODE_WRONG_TYPE], - 'tips.2': [f.Required.CODE_EMPTY], - 'tips.3': [Trytes.CODE_NOT_TRYTES], - 'tips.5': [f.Type.CODE_WRONG_TYPE], - 'tips.6': [Trytes.CODE_WRONG_FORMAT], - }, - ) - class GetInclusionStatesCommandTestCase(TestCase): def setUp(self): @@ -251,6 +173,11 @@ def setUp(self): self.adapter = MockAdapter() + self.trytes1 = ( + 'TESTVALUE9DONTUSEINPRODUCTION99999GCXWZZ' + 'ZKNRIZENRRXGPAGJOSSWQQOJDD9VGQRMEFCOIFLQB' + ) + def test_wireup(self): """ Verify that the command is wired up correctly. (sync) @@ -263,7 +190,8 @@ def test_wireup(self): api = Iota(self.adapter) - response = api.get_inclusion_states('transactions', 'tips') + # Don't need to call with proper args here. + response = api.get_inclusion_states('transactions') self.assertTrue(mocked_command.called) @@ -285,11 +213,25 @@ async def test_wireup_async(self): api = AsyncIota(self.adapter) - response = await api.get_inclusion_states('transactions', 'tips') + response = await api.get_inclusion_states('transactions') self.assertTrue(mocked_command.called) self.assertEqual( response, 'You found me!' - ) \ No newline at end of file + ) + + def test_fail_on_tips(self): + """ + Fail on provided 'tips' parameter. + + Deprecated in IRI 1.8.6 + """ + api = Iota(self.adapter) + + with self.assertRaises(TypeError): + response = api.get_inclusion_states( + transactions=[self.trytes1], + tips=[self.trytes1] + ) diff --git a/test/commands/core/get_node_info_test.py b/test/commands/core/get_node_info_test.py index 1b3249a..b919a0a 100644 --- a/test/commands/core/get_node_info_test.py +++ b/test/commands/core/get_node_info_test.py @@ -2,7 +2,7 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TransactionHash, AsyncIota +from iota import Iota, TransactionHash, AsyncIota, Address from iota.adapter import MockAdapter, async_return from iota.commands.core.get_node_info import GetNodeInfoCommand from test import patch, MagicMock, async_test @@ -50,6 +50,9 @@ def test_pass_happy_path(self): response = { 'appName': 'IRI', 'appVersion': '1.0.8.nu', + 'coordinatorAddress': + 'BUPQSYFUFMUOJVGVFKPCOULCQNTJPYJOTATSILTN' + 'ZKOPQWHENIDIH9HJEUOTNV9LNGHCTHMHWOPLZOJCJ', 'duration': 1, 'jreAvailableProcessors': 4, 'jreFreeMemory': 91707424, @@ -81,6 +84,11 @@ def test_pass_happy_path(self): { 'appName': 'IRI', 'appVersion': '1.0.8.nu', + 'coordinatorAddress': + Address( + b'BUPQSYFUFMUOJVGVFKPCOULCQNTJPYJOTATSILTN' + b'ZKOPQWHENIDIH9HJEUOTNV9LNGHCTHMHWOPLZOJCJ' + ), 'duration': 1, 'jreAvailableProcessors': 4, 'jreFreeMemory': 91707424, diff --git a/test/commands/core/get_tips_test.py b/test/commands/core/get_tips_test.py deleted file mode 100644 index 8894baf..0000000 --- a/test/commands/core/get_tips_test.py +++ /dev/null @@ -1,181 +0,0 @@ -from unittest import TestCase - -import filters as f -from filters.test import BaseFilterTestCase - -from iota import Address, Iota, AsyncIota -from iota.adapter import MockAdapter, async_return -from iota.commands.core.get_tips import GetTipsCommand -from iota.transaction.types import TransactionHash -from test import patch, MagicMock, async_test - - -class GetTipsRequestFilterTestCase(BaseFilterTestCase): - filter_type = GetTipsCommand(MockAdapter()).get_request_filter - skip_value_check = True - - def test_pass_empty(self): - """ - The incoming request is (correctly) empty. - """ - request = {} - - filter_ = self._filter(request) - - self.assertFilterPasses(filter_) - self.assertDictEqual(filter_.cleaned_data, request) - - def test_fail_unexpected_parameters(self): - """ - The incoming request contains unexpected parameters. - """ - self.assertFilterErrors( - { - # All you had to do was nothing! How did you screw that up?! - 'foo': 'bar', - }, - - { - 'foo': [f.FilterMapper.CODE_EXTRA_KEY], - }, - ) - - -class GetTipsResponseFilterTestCase(BaseFilterTestCase): - filter_type = GetTipsCommand(MockAdapter()).get_response_filter - skip_value_check = True - - def test_pass_lots_of_hashes(self): - """ - The response contains lots of hashes. - """ - response = { - 'hashes': [ - 'YVXJOEOP9JEPRQUVBPJMB9MGIB9OMTIJJLIUYPM9' - 'YBIWXPZ9PQCCGXYSLKQWKHBRVA9AKKKXXMXF99999', - - 'ZUMARCWKZOZRMJM9EEYJQCGXLHWXPRTMNWPBRCAG' - 'SGQNRHKGRUCIYQDAEUUEBRDBNBYHAQSSFZZQW9999', - - 'QLQECHDVQBMXKD9YYLBMGQLLIQ9PSOVDRLYCLLFM' - 'S9O99XIKCUHWAFWSTARYNCPAVIQIBTVJROOYZ9999', - ], - - 'duration': 4 - } - - filter_ = self._filter(response) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'hashes': [ - Address( - b'YVXJOEOP9JEPRQUVBPJMB9MGIB9OMTIJJLIUYPM9' - b'YBIWXPZ9PQCCGXYSLKQWKHBRVA9AKKKXXMXF99999' - ), - - Address( - b'ZUMARCWKZOZRMJM9EEYJQCGXLHWXPRTMNWPBRCAG' - b'SGQNRHKGRUCIYQDAEUUEBRDBNBYHAQSSFZZQW9999' - ), - - Address( - b'QLQECHDVQBMXKD9YYLBMGQLLIQ9PSOVDRLYCLLFM' - b'S9O99XIKCUHWAFWSTARYNCPAVIQIBTVJROOYZ9999' - ), - ], - - 'duration': 4, - } - ) - - def test_pass_no_hashes(self): - """ - The response doesn't contain any hashes. - """ - response = { - 'hashes': [], - 'duration': 4, - } - - filter_ = self._filter(response) - - self.assertFilterPasses(filter_) - self.assertDictEqual(filter_.cleaned_data, response) - - -class GetTipsCommandTestCase(TestCase): - def setUp(self): - super(GetTipsCommandTestCase, self).setUp() - - self.adapter = MockAdapter() - - def test_wireup(self): - """ - Verify that the command is wired up correctly. (sync) - - The API method indeed calls the appropiate command. - """ - with patch('iota.commands.core.get_tips.GetTipsCommand.__call__', - MagicMock(return_value=async_return('You found me!')) - ) as mocked_command: - - api = Iota(self.adapter) - - response = api.get_tips() - - self.assertTrue(mocked_command.called) - - self.assertEqual( - response, - 'You found me!' - ) - - @async_test - async def test_wireup_async(self): - """ - Verify that the command is wired up correctly. (async) - - The API method indeed calls the appropiate command. - """ - with patch('iota.commands.core.get_tips.GetTipsCommand.__call__', - MagicMock(return_value=async_return('You found me!')) - ) as mocked_command: - - api = AsyncIota(self.adapter) - - response = await api.get_tips() - - self.assertTrue(mocked_command.called) - - self.assertEqual( - response, - 'You found me!' - ) - - def test_type_coercion(self): - """ - The result is coerced to the proper type. - - https://github.com/iotaledger/iota.py/issues/130 - """ - self.adapter.seed_response('getTips', { - 'duration': 42, - 'hashes': [ - 'TESTVALUE9DONTUSEINPRODUCTION99999ANSVWB' - 'CZ9ABZYUK9YYXFRLROGMCMQHRARDQPNMHHZSZ9999', - - 'TESTVALUE9DONTUSEINPRODUCTION99999HCZURL' - 'NFWEDRFCYHWTYGUEMJLJ9ZIJTFASAVSEAZJGA9999', - ], - }) - - gt_response = Iota(self.adapter).get_tips() - - self.assertEqual( - list(map(type, gt_response['hashes'])), - [TransactionHash] * 2, - ) diff --git a/test/commands/extended/get_latest_inclusion_test.py b/test/commands/extended/get_latest_inclusion_test.py deleted file mode 100644 index 130fd40..0000000 --- a/test/commands/extended/get_latest_inclusion_test.py +++ /dev/null @@ -1,271 +0,0 @@ -from unittest import TestCase - -import filters as f -from filters.test import BaseFilterTestCase - -from iota import Iota, AsyncIota, TransactionHash, TryteString -from iota.adapter import MockAdapter, async_return -from iota.commands.extended.get_latest_inclusion import \ - GetLatestInclusionCommand -from iota.filters import Trytes -from test import patch, MagicMock, async_test - - -class GetLatestInclusionRequestFilterTestCase(BaseFilterTestCase): - filter_type = GetLatestInclusionCommand(MockAdapter()).get_request_filter - skip_value_check = True - - def setUp(self): - super(GetLatestInclusionRequestFilterTestCase, self).setUp() - - self.hash1 = ( - 'TESTVALUE9DONTUSEINPRODUCTION99999DXSCAD' - 'YBVDCTTBLHFYQATFZPYPCBG9FOUKIGMYIGLHM9NEZ' - ) - - self.hash2 = ( - 'TESTVALUE9DONTUSEINPRODUCTION99999EMFYSM' - 'HWODIAPUTTFDLQRLYIDAUIPJXXEXZZSBVKZEBWGAN' - ) - - def test_pass_happy_path(self): - """ - Request is valid. - """ - request = { - # Raw trytes are extracted to match the IRI's JSON protocol. - 'hashes': [self.hash1, self.hash2], - } - - filter_ = self._filter(request) - - self.assertFilterPasses(filter_) - self.assertDictEqual(filter_.cleaned_data, request) - - def test_pass_compatible_types(self): - """ - Request contains values that can be converted to the expected - types. - """ - filter_ = self._filter({ - 'hashes': [ - # Any TrytesCompatible value can be used here. - TransactionHash(self.hash1), - bytearray(self.hash2.encode('ascii')), - ], - }) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - # Raw trytes are extracted to match the IRI's JSON protocol. - 'hashes': [self.hash1, self.hash2], - }, - ) - - def test_fail_empty(self): - """ - Request is empty. - """ - self.assertFilterErrors( - {}, - - { - 'hashes': [f.FilterMapper.CODE_MISSING_KEY], - }, - ) - - def test_fail_unexpected_parameters(self): - """ - Request contains unexpected parameters. - """ - self.assertFilterErrors( - { - 'hashes': [TransactionHash(self.hash1)], - - # Uh, before we dock, I think we ought to discuss the bonus - # situation. - 'foo': 'bar', - }, - - { - 'foo': [f.FilterMapper.CODE_EXTRA_KEY], - }, - ) - - def test_fail_hashes_null(self): - """ - ``hashes`` is null. - """ - self.assertFilterErrors( - { - 'hashes': None, - }, - - { - 'hashes': [f.Required.CODE_EMPTY], - }, - ) - - def test_fail_hashes_wrong_type(self): - """ - ``hashes`` is not an array. - """ - self.assertFilterErrors( - { - # It's gotta be an array, even if there's only one hash. - 'hashes': TransactionHash(self.hash1), - }, - - { - 'hashes': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_hashes_empty(self): - """ - ``hashes`` is an array, but it is empty. - """ - self.assertFilterErrors( - { - 'hashes': [], - }, - - { - 'hashes': [f.Required.CODE_EMPTY], - }, - ) - - def test_fail_hashes_contents_invalid(self): - """ - ``hashes`` is a non-empty array, but it contains invalid values. - """ - self.assertFilterErrors( - { - 'hashes': [ - b'', - True, - None, - b'not valid trytes', - - # This is actually valid; I just added it to make sure the - # filter isn't cheating! - TryteString(self.hash1), - - 2130706433, - b'9' * 82, - ], - }, - - { - 'hashes.0': [f.Required.CODE_EMPTY], - 'hashes.1': [f.Type.CODE_WRONG_TYPE], - 'hashes.2': [f.Required.CODE_EMPTY], - 'hashes.3': [Trytes.CODE_NOT_TRYTES], - 'hashes.5': [f.Type.CODE_WRONG_TYPE], - 'hashes.6': [Trytes.CODE_WRONG_FORMAT], - }, - ) - - -class GetLatestInclusionCommandTestCase(TestCase): - def setUp(self): - super(GetLatestInclusionCommandTestCase, self).setUp() - - self.adapter = MockAdapter() - self.command = GetLatestInclusionCommand(self.adapter) - - # Define some tryte sequences that we can re-use across tests. - self.milestone =\ - TransactionHash( - b'TESTVALUE9DONTUSEINPRODUCTION99999W9KDIH' - b'BALAYAFCADIDU9HCXDKIXEYDNFRAKHN9IEIDZFWGJ' - ) - - self.hash1 =\ - TransactionHash( - b'TESTVALUE9DONTUSEINPRODUCTION99999TBPDM9' - b'ADFAWCKCSFUALFGETFIFG9UHIEFE9AYESEHDUBDDF' - ) - - self.hash2 =\ - TransactionHash( - b'TESTVALUE9DONTUSEINPRODUCTION99999CIGCCF' - b'KIUFZF9EP9YEYGQAIEXDTEAAUGAEWBBASHYCWBHDX' - ) - - def test_wireup(self): - """ - Verify that the command is wired up correctly. (sync) - - The API method indeed calls the appropiate command. - """ - with patch('iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', - MagicMock(return_value=async_return('You found me!')) - ) as mocked_command: - - api = Iota(self.adapter) - - # Don't need to call with proper args here. - response = api.get_latest_inclusion('hashes') - - self.assertTrue(mocked_command.called) - - self.assertEqual( - response, - 'You found me!' - ) - - @async_test - async def test_wireup_async(self): - """ - Verify that the command is wired up correctly. (async) - - The API method indeed calls the appropiate command. - """ - with patch('iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', - MagicMock(return_value=async_return('You found me!')) - ) as mocked_command: - - api = AsyncIota(self.adapter) - - # Don't need to call with proper args here. - response = await api.get_latest_inclusion('hashes') - - self.assertTrue(mocked_command.called) - - self.assertEqual( - response, - 'You found me!' - ) - - @async_test - async def test_happy_path(self): - """ - Successfully requesting latest inclusion state. - """ - self.adapter.seed_response('getNodeInfo', { - # ``getNodeInfo`` returns lots of info, but the only value that - # matters for this test is ``latestSolidSubtangleMilestone``. - 'latestSolidSubtangleMilestone': self.milestone, - }, - ) - - self.adapter.seed_response('getInclusionStates', { - 'states': [True, False], - }) - - response = await self.command(hashes=[self.hash1, self.hash2]) - - self.assertDictEqual( - response, - - { - 'states': { - self.hash1: True, - self.hash2: False, - }, - } - ) diff --git a/test/commands/extended/get_transfers_test.py b/test/commands/extended/get_transfers_test.py index 4b1cd48..81c90c9 100644 --- a/test/commands/extended/get_transfers_test.py +++ b/test/commands/extended/get_transfers_test.py @@ -768,10 +768,8 @@ def create_generator(ag, start, step=1): 'bundles': [Bundle([transaction])], })) - mock_get_latest_inclusion = mock.Mock(return_value=async_return({ - 'states': { - transaction.hash: True, - }, + mock_get_inclusion_states = mock.Mock(return_value=async_return({ + 'states': [True], })) with mock.patch( @@ -783,8 +781,8 @@ def create_generator(ag, start, step=1): mock_get_bundles, ): with mock.patch( - 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand._execute', - mock_get_latest_inclusion, + 'iota.commands.core.get_inclusion_states.GetInclusionStatesCommand._execute', + mock_get_inclusion_states, ): response = await self.command( seed = Seed.random(), diff --git a/test/commands/extended/utils_test.py b/test/commands/extended/utils_test.py index 23c87c4..d842e26 100644 --- a/test/commands/extended/utils_test.py +++ b/test/commands/extended/utils_test.py @@ -450,7 +450,7 @@ async def test_happy_path(self): ) with mock.patch( - 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', + 'iota.commands.core.get_inclusion_states.GetInclusionStatesCommand.__call__', MagicMock(return_value=async_return({ 'states': {self.single_bundle.tail_transaction.hash: True}})) ) as mocked_glis: @@ -470,7 +470,7 @@ async def test_happy_path(self): ) mocked_glis.assert_called_once_with( - hashes=[self.single_bundle.tail_transaction.hash] + transactions=[self.single_bundle.tail_transaction.hash] ) mocked_get_bundles.assert_called_once_with( @@ -494,7 +494,7 @@ async def test_happy_path_no_inclusion(self): ) with mock.patch( - 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', + 'iota.commands.core.get_inclusion_states.GetInclusionStatesCommand.__call__', MagicMock(return_value=async_return({'states': { self.single_bundle.tail_transaction.hash: True }})) @@ -582,7 +582,7 @@ async def test_multiple_tail_transactions(self): ) with mock.patch( - 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', + 'iota.commands.core.get_inclusion_states.GetInclusionStatesCommand.__call__', MagicMock(return_value=async_return({'states': { self.single_bundle.tail_transaction.hash: True, self.three_tx_bundle.tail_transaction.hash: True @@ -620,11 +620,11 @@ async def test_multiple_tail_transactions(self): # Get the keyword arguments from that call _, _, mocked_glis_kwargs = mocked_glis.mock_calls[0] - # 'hashes' keyword's value should be a list of hashes it was called + # 'transactions' keyword's value should be a list of hashes it was called # with. Due to the set -> list conversion in the src code, we can't # be sure of the order of the elements, so we check by value. self.assertCountEqual( - mocked_glis_kwargs.get('hashes'), + mocked_glis_kwargs.get('transactions'), [ self.three_tx_bundle.tail_transaction.hash, self.single_bundle.tail_transaction.hash, @@ -675,7 +675,7 @@ async def test_non_tail(self): ) with mock.patch( - 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', + 'iota.commands.core.get_inclusion_states.GetInclusionStatesCommand.__call__', MagicMock(return_value=async_return({'states': { self.three_tx_bundle.tail_transaction.hash: True }})) @@ -706,7 +706,7 @@ async def test_non_tail(self): ) mocked_glis.assert_called_once_with( - hashes=[self.three_tx_bundle.tail_transaction.hash] + transactions=[self.three_tx_bundle.tail_transaction.hash] ) mocked_get_bundles.assert_called_once_with( @@ -731,7 +731,7 @@ async def test_ordered_by_timestamp(self): ) with mock.patch( - 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', + 'iota.commands.core.get_inclusion_states.GetInclusionStatesCommand.__call__', MagicMock(return_value=async_return({'states': { self.three_tx_bundle.tail_transaction.hash: True, self.single_bundle.tail_transaction.hash: True, @@ -773,11 +773,11 @@ async def test_ordered_by_timestamp(self): # Get the keyword arguments from that call _, _, mocked_glis_kwargs = mocked_glis.mock_calls[0] - # 'hashes' keyword's value should be a list of hashes it was called + # 'transactions' keyword's value should be a list of hashes it was called # with. Due to the set -> list conversion in the src code, we can't # be sure of the order of the elements, so we check by value. self.assertCountEqual( - mocked_glis_kwargs.get('hashes'), + mocked_glis_kwargs.get('transactions'), [ self.three_tx_bundle.tail_transaction.hash, self.single_bundle.tail_transaction.hash, @@ -796,4 +796,4 @@ async def test_ordered_by_timestamp(self): ) self.assertTrue( response[1].is_confirmed - ) \ No newline at end of file + )