Beispiel #1
0
class GetBalancesResponseFilterTestCase(BaseFilterTestCase):
    filter_type = GetBalancesCommand(MockAdapter()).get_response_filter
    skip_value_check = True

    def test_balances(self):
        """
    Typical ``getBalances`` response.
    """
        filter_ = self._filter({
            'balances': ['114544444', '0', '8175737'],
            'duration':
            42,
            'milestoneIndex':
            128,
            'milestone':
            'INRTUYSZCWBHGFGGXXPWRWBZACYAFGVRRP9VYEQJ'
            'OHYD9URMELKWAFYFMNTSP9MCHLXRGAFMBOZPZ9999',
        })

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'balances': [114544444, 0, 8175737],
                'duration':
                42,
                'milestoneIndex':
                128,
                'milestone':
                Address(
                    b'INRTUYSZCWBHGFGGXXPWRWBZACYAFGVRRP9VYEQJ'
                    b'OHYD9URMELKWAFYFMNTSP9MCHLXRGAFMBOZPZ9999', ),
            },
        )
Beispiel #2
0
class GetBalancesResponseFilterTestCase(BaseFilterTestCase):
    filter_type = GetBalancesCommand(MockAdapter()).get_response_filter
    skip_value_check = True

    def setUp(self):
        super(GetBalancesResponseFilterTestCase, self).setUp()

        # Define a few valid values that we can reuse across tests.
        self.trytes1 = ('TESTVALUE9DONTUSEINPRODUCTION99999EKJZZT'
                        'SOGJOUNVEWLDPKGTGAOIZIPMGBLHC9LMQNHLGXGYX')

    def test_balances(self):
        """
        Typical ``getBalances`` response.
        """
        filter_ = self._filter({
            'balances': ['114544444', '0', '8175737'],
            'duration': 42,
            'milestoneIndex': 128,
            'references': [self.trytes1]
        })

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'balances': [114544444, 0, 8175737],
                'duration': 42,
                'milestoneIndex': 128,
                'references': [self.trytes1]
            },
        )
Beispiel #3
0
    def _execute(self, request):
        stop = request['stop']  # type: Optional[int]
        seed = request['seed']  # type: Seed
        start = request['start']  # type: int
        threshold = request['threshold']  # type: Optional[int]
        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'],
                },
            )
Beispiel #4
0
    def _execute(self, request):
        inclusion_states = request['inclusionStates']  # type: bool
        seed = request['seed']  # type: Seed
        start = request['start']  # type: int
        stop = request['stop']  # type: Optional[int]
        security_level = request['security_level']  # 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, security_level):
                my_addresses.append(addy)
                my_hashes.extend(hashes)
        else:
            ft_command = FindTransactionsCommand(self.adapter)

            my_addresses = (
                AddressGenerator(seed, security_level).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,
                ),
        }
Beispiel #5
0
class GetBalancesRequestFilterTestCase(BaseFilterTestCase):
    filter_type = GetBalancesCommand(MockAdapter()).get_request_filter
    skip_value_check = True

    def setUp(self):
        super(GetBalancesRequestFilterTestCase, self).setUp()

        # Define a few valid values that we can reuse across tests.
        self.trytes1 = ('TESTVALUE9DONTUSEINPRODUCTION99999EKJZZT'
                        'SOGJOUNVEWLDPKGTGAOIZIPMGBLHC9LMQNHLGXGYX')

        self.trytes2 = ('TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZ'
                        'ZWLL9MYGUTLSYVSIFJ9NGALTRMCQVIIOVEQOITYTE')

        self.trytes3 = ('TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZ'
                        'ASKDFJWOEFJSKLDJFWEIOFFJSKDJFWIOEFJSKDF9E')

    def test_pass_happy_path(self):
        """
        Typical invocation of ``getBalances``.
        """
        request = {
            # Raw trytes are extracted to match the IRI's JSON protocol.
            'addresses': [self.trytes1, self.trytes2],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(filter_.cleaned_data, request)

    def test_pass_happy_path_with_tips(self):
        """
        Typical invocation of ``getBalances`` with tips.
        """
        request = {
            'addresses': [self.trytes1, self.trytes2],
            'tips': [self.trytes3],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(filter_.cleaned_data, request)

    def test_pass_compatible_types(self):
        """
        The incoming request contains values that can be converted to the
        expected types.
        """
        request = {
            'addresses': [
                Address(self.trytes1),
                bytearray(self.trytes2.encode('ascii')),
            ],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'addresses': [self.trytes1, self.trytes2],
            },
        )

    def test_fail_empty(self):
        """
        The incoming request is empty.
        """
        self.assertFilterErrors(
            {},
            {
                'addresses': [f.FilterMapper.CODE_MISSING_KEY],
            },
        )

    def test_fail_unexpected_parameters(self):
        """
        The incoming request contains unexpected parameters.
        """
        self.assertFilterErrors(
            {
                'addresses': [Address(self.trytes1)],

                # `threshold` parameter deprecated in IRI 1.8.6
                'threshold': 100,
            },
            {
                'threshold': [f.FilterMapper.CODE_EXTRA_KEY],
            },
        )

    def test_fail_addresses_wrong_type(self):
        """
        ``addresses`` is not an array.
        """
        self.assertFilterErrors(
            {
                'addresses': Address(self.trytes1),
            },
            {
                'addresses': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_addresses_empty(self):
        """
        ``addresses`` is an array, but it's empty.
        """
        self.assertFilterErrors(
            {
                'addresses': [],
            },
            {
                'addresses': [f.Required.CODE_EMPTY],
            },
        )

    def test_fail_addresses_contents_invalid(self):
        """
        ``addresses`` is an array, but it contains invalid values.
        """
        self.assertFilterErrors(
            {
                'addresses': [
                    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.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                'addresses.0': [f.Required.CODE_EMPTY],
                'addresses.1': [f.Type.CODE_WRONG_TYPE],
                'addresses.2': [f.Required.CODE_EMPTY],
                'addresses.3': [Trytes.CODE_NOT_TRYTES],
                'addresses.5': [f.Type.CODE_WRONG_TYPE],
                'addresses.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )

    def test_fail_tips_contents_invalid(self):
        """
        ``tips`` is an array, but it contains invalid values.
        """
        self.assertFilterErrors(
            {
                'addresses': [self.trytes1],
                '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.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                '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],
            },
        )
Beispiel #6
0
    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]]

        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,
                )

                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)['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(),
        }
Beispiel #7
0
class GetBalancesRequestFilterTestCase(BaseFilterTestCase):
    filter_type = GetBalancesCommand(MockAdapter()).get_request_filter
    # print('type(filter_type)001: ', type(filter_type))
    # # <class 'method'>
    # print('filter_type 001: ', filter_type)
    # # <bound method GetBalancesCommand.get_request_filter of <iota.commands.core.get_balances.GetBalancesCommand object at 0x0000029B415C8F60>>
    skip_value_check = True

    # noinspection SpellCheckingInspection
    def setUp(self):
        # U should know this already, due the line below
        # # this is going to run "def setUp()" which is defined in "BaseFilterTestCase"
        super(GetBalancesRequestFilterTestCase, self).setUp()

        # Define a few valid values that we can reuse across tests.
        self.trytes1 = ('TESTVALUE9DONTUSEINPRODUCTION99999EKJZZT'
                        'SOGJOUNVEWLDPKGTGAOIZIPMGBLHC9LMQNHLGXGYX')

        self.trytes2 = ('TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZ'
                        'ZWLL9MYGUTLSYVSIFJ9NGALTRMCQVIIOVEQOITYTE')

    def test_pass_happy_path(self):
        """
    Typical invocation of ``getBalances``.
    """
        request = {
            # Raw trytes are extracted to match the IRI's JSON protocol.
            'addresses': [self.trytes1, self.trytes2],
            'threshold': 80,
        }

        # once again, iota is going to use filters/test.py and "unittest" for testing
        #
        #
        # "def _filter()" below is going to be defined in
        # # site-packages/filters/test.py
        filter_ = self._filter(request)
        print_var_type_n_val(var001=filter_,
                             pointer="#XCVBFFbvvbf234")  #XCVBFFbvvbf234
        # Value:
        # # GetBalancesRequestFilter(FilterChain(Type(Mapping, allow_subclass=True) | FilterMapper(addresses=FilterChain(Required(allow_none=False) | Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | AddressNoChecksum() | Unicode(encoding='ascii')))), threshold=FilterChain(Type(int, allow_subclass=True) | Min(0, exclusive=False) | Max(100, exclusive=False) | Optional(default=100)))))

        # Type: <class 'filters.handlers.FilterRunner'>
        # the function below (assertFilterPasses()) is going to be defined in
        # # site-packages/filters/test.py
        # #
        # # the function below Asserts that the FilterRunner returns the specified value,
        # # # without errors.
        self.assertFilterPasses(filter_)  #assertFilterPasses()
        self.assertDictEqual(filter_.cleaned_data, request)

    def test_pass_compatible_types(self):
        """
    The incoming request contains values that can be converted to the
    expected types.
    """
        request = {
            'addresses': [
                Address(self.trytes1),
                bytearray(self.trytes2.encode('ascii')),
            ],
            'threshold':
            80,
        }

        filter_ = self._filter(request)
        print_var_type_n_val(var001=request,
                             pointer="#EDFEsdfr345")  #EDFEsdfr345
        # Value:
        # # {'addresses': [Address(b'TESTVALUE9DONTUSEINPRODUCTION99999EKJZZTSOGJOUNVEWLDPKGTGAOIZIPMGBLHC9LMQNHLGXGYX'), bytearray(b'TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZZWLL9MYGUTLSYVSIFJ9NGALTRMCQVIIOVEQOITYTE')], 'threshold': 80}

        # Type: <class 'dict'>
        print_var_type_n_val(var001=filter_,
                             pointer="#XCVBFFbvvbf234")  #XCVBFFbvvbf234
        # Value:
        # # GetBalancesRequestFilter(FilterChain(Type(Mapping, allow_subclass=True) | FilterMapper(addresses=FilterChain(Required(allow_none=False) | Array(Sequence, allow_subclass=True) | FilterRepeater(FilterChain(Required(allow_none=False) | AddressNoChecksum() | Unicode(encoding='ascii')))), threshold=FilterChain(Type(int, allow_subclass=True) | Min(0, exclusive=False) | Max(100, exclusive=False) | Optional(default=100)))))

        # Type: <class 'filters.handlers.FilterRunner'>

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                '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,
            },
        )

    def test_fail_empty(self):
        """
    The incoming request is empty.
    """
        self.assertFilterErrors(
            {},
            {
                'addresses': [f.FilterMapper.CODE_MISSING_KEY],
            },
        )

    def test_fail_unexpected_parameters(self):
        """
    The incoming request contains unexpected parameters.
    """
        self.assertFilterErrors(
            {
                'addresses': [Address(self.trytes1)],

                # I've had a perfectly wonderful evening.
                # But this wasn't it.
                'foo': 'bar',
            },
            {
                'foo': [f.FilterMapper.CODE_EXTRA_KEY],
            },
        )

    def test_fail_addresses_wrong_type(self):
        """
    ``addresses`` is not an array.
    """
        self.assertFilterErrors(
            {
                'addresses': Address(self.trytes1),
            },
            {
                'addresses': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_addresses_empty(self):
        """
    ``addresses`` is an array, but it's empty.
    """
        self.assertFilterErrors(
            {
                'addresses': [],
            },
            {
                'addresses': [f.Required.CODE_EMPTY],
            },
        )

    def test_fail_addresses_contents_invalid(self):
        """
    ``addresses`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'addresses': [
                    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.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                'addresses.0': [f.Required.CODE_EMPTY],
                'addresses.1': [f.Type.CODE_WRONG_TYPE],
                'addresses.2': [f.Required.CODE_EMPTY],
                'addresses.3': [Trytes.CODE_NOT_TRYTES],
                'addresses.5': [f.Type.CODE_WRONG_TYPE],
                'addresses.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )

    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],
            },
        )
Beispiel #8
0
class GetBalancesRequestFilterTestCase(BaseFilterTestCase):
    filter_type = GetBalancesCommand(MockAdapter()).get_request_filter
    skip_value_check = True

    # noinspection SpellCheckingInspection
    def setUp(self):
        super(GetBalancesRequestFilterTestCase, self).setUp()

        # Define a few valid values that we can reuse across tests.
        self.trytes1 = ('TESTVALUE9DONTUSEINPRODUCTION99999EKJZZT'
                        'SOGJOUNVEWLDPKGTGAOIZIPMGBLHC9LMQNHLGXGYX')

        self.trytes2 = ('TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZ'
                        'ZWLL9MYGUTLSYVSIFJ9NGALTRMCQVIIOVEQOITYTE')

        self.trytes3 = ('TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZ'
                        'ASKDFJWOEFJSKLDJFWEIOFFJSKDJFWIOEFJSKDF9E')

    def test_pass_happy_path(self):
        """
        Typical invocation of ``getBalances``.
        """
        request = {
            # Raw trytes are extracted to match the IRI's JSON protocol.
            'addresses': [self.trytes1, self.trytes2],
            'threshold': 80,
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(filter_.cleaned_data, request)

    def test_pass_happy_path_with_tips(self):
        """
        Typical invocation of ``getBalances`` with tips.
        """
        request = {
            'addresses': [self.trytes1, self.trytes2],
            'threshold': 80,
            'tips': [self.trytes3],
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(filter_.cleaned_data, request)

    def test_pass_compatible_types(self):
        """
        The incoming request contains values that can be converted to the
        expected types.
        """
        request = {
            'addresses': [
                Address(self.trytes1),
                bytearray(self.trytes2.encode('ascii')),
            ],
            'threshold':
            80,
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                '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,
            },
        )

    def test_fail_empty(self):
        """
        The incoming request is empty.
        """
        self.assertFilterErrors(
            {},
            {
                'addresses': [f.FilterMapper.CODE_MISSING_KEY],
            },
        )

    def test_fail_unexpected_parameters(self):
        """
        The incoming request contains unexpected parameters.
        """
        self.assertFilterErrors(
            {
                'addresses': [Address(self.trytes1)],

                # I've had a perfectly wonderful evening.
                # But this wasn't it.
                'foo': 'bar',
            },
            {
                'foo': [f.FilterMapper.CODE_EXTRA_KEY],
            },
        )

    def test_fail_addresses_wrong_type(self):
        """
        ``addresses`` is not an array.
        """
        self.assertFilterErrors(
            {
                'addresses': Address(self.trytes1),
            },
            {
                'addresses': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_addresses_empty(self):
        """
        ``addresses`` is an array, but it's empty.
        """
        self.assertFilterErrors(
            {
                'addresses': [],
            },
            {
                'addresses': [f.Required.CODE_EMPTY],
            },
        )

    def test_fail_addresses_contents_invalid(self):
        """
        ``addresses`` is an array, but it contains invalid values.
        """
        self.assertFilterErrors(
            {
                'addresses': [
                    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.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                'addresses.0': [f.Required.CODE_EMPTY],
                'addresses.1': [f.Type.CODE_WRONG_TYPE],
                'addresses.2': [f.Required.CODE_EMPTY],
                'addresses.3': [Trytes.CODE_NOT_TRYTES],
                'addresses.5': [f.Type.CODE_WRONG_TYPE],
                'addresses.6': [Trytes.CODE_WRONG_FORMAT],
            },
        )

    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.
        """
        self.assertFilterErrors(
            {
                'addresses': [self.trytes1],
                '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.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                '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],
            },
        )
Beispiel #9
0
class GetBalancesRequestFilterTestCase(BaseFilterTestCase):
    filter_type = GetBalancesCommand(MockAdapter()).get_request_filter
    skip_value_check = True

    # noinspection SpellCheckingInspection
    def setUp(self):
        super(GetBalancesRequestFilterTestCase, self).setUp()

        # Define a few valid values that we can reuse across tests.
        self.trytes1 = (b'ORLSCIMM9ZONOUSPYYWLOEMXQZLYEHCBEDQSHZ'
                        b'OGOPZCZCDZYTDPGEEUXWUZ9FQYCT9OGS9PICOOX')

        self.trytes2 = (b'HHKUSTHZPUPONLCHXUGFYEHATTMFOSSHEUHYS'
                        b'ZUKBODYHZM99IR9KOXLZXVUOJM9LQKCQJBWMTY')

    def test_pass_happy_path(self):
        """
    Typical invocation of ``getBalances``.
    """
        request = {
            'addresses': [
                Address(self.trytes1),
                Address(self.trytes2),
            ],
            'threshold': 80,
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(filter_.cleaned_data, request)

    def test_pass_compatible_types(self):
        """
    The incoming request contains values that can be converted to the
    expected types.
    """
        request = {
            'addresses': [
                binary_type(self.trytes1),
                bytearray(self.trytes2),
            ],
            'threshold': 80,
        }

        filter_ = self._filter(request)

        self.assertFilterPasses(filter_)
        self.assertDictEqual(
            filter_.cleaned_data,
            {
                'addresses': [
                    Address(self.trytes1),
                    Address(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,
            },
        )

    def test_fail_empty(self):
        """
    The incoming request is empty.
    """
        self.assertFilterErrors(
            {},
            {
                'addresses': [f.FilterMapper.CODE_MISSING_KEY],
            },
        )

    def test_fail_unexpected_parameters(self):
        """
    The incoming request contains unexpected parameters.
    """
        self.assertFilterErrors(
            {
                'addresses': [Address(self.trytes1)],

                # I've had a perfectly wonderful evening.
                # But this wasn't it.
                'foo': 'bar',
            },
            {
                'foo': [f.FilterMapper.CODE_EXTRA_KEY],
            },
        )

    def test_fail_addresses_wrong_type(self):
        """
    ``addresses`` is not an array.
    """
        self.assertFilterErrors(
            {
                'addresses': Address(self.trytes1),
            },
            {
                'addresses': [f.Type.CODE_WRONG_TYPE],
            },
        )

    def test_fail_addresses_empty(self):
        """
    ``addresses`` is an array, but it's empty.
    """
        self.assertFilterErrors(
            {
                'addresses': [],
            },
            {
                'addresses': [f.Required.CODE_EMPTY],
            },
        )

    def test_fail_addresses_contents_invalid(self):
        """
    ``addresses`` is an array, but it contains invalid values.
    """
        self.assertFilterErrors(
            {
                'addresses': [
                    b'',
                    text_type(self.trytes1, 'ascii'),
                    True,
                    None,
                    b'not valid trytes',

                    # This is actually valid; I just added it to make sure the
                    # filter isn't cheating!
                    TryteString(self.trytes2),
                    2130706433,
                    b'9' * 82,
                ],
            },
            {
                'addresses.0': [f.Required.CODE_EMPTY],
                'addresses.1': [f.Type.CODE_WRONG_TYPE],
                'addresses.2': [f.Type.CODE_WRONG_TYPE],
                'addresses.3': [f.Required.CODE_EMPTY],
                'addresses.4': [Trytes.CODE_NOT_TRYTES],
                'addresses.6': [f.Type.CODE_WRONG_TYPE],
                'addresses.7': [Trytes.CODE_WRONG_FORMAT],
            },
        )

    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 _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]]

    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,
        )

        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)['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(),
    }
Beispiel #11
0
    def _execute(self, request):
        stop = request['stop']  # type: Optional[int]
        seed = request['seed']  # type: Seed
        start = request['start']  # type: int
        threshold = request['threshold']  # type: Optional[int]

        generator = AddressGenerator(seed)

        # Determine the addresses we will be scanning.
        if stop is None:
            # This is similar to the ``getNewAddresses`` command, except it
            # is interested in all the addresses that `getNewAddresses`
            # skips.
            addresses = []  # type: List[Address]
            for addy in generator.create_generator(start):
                ft_response = FindTransactionsCommand(
                    self.adapter)(addresses=[addy])

                if ft_response.get('hashes'):
                    addresses.append(addy)
                else:
                    break
        else:
            addresses = generator.get_addresses(start, stop)

        # Load balances for the addresses that we generated.
        gb_response = GetBalancesCommand(self.adapter)(addresses=addresses)

        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'],
                },
            )