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