def test_pass_all_parameters(self): """ The request contains valid values for all parameters. """ # Raw trytes are extracted to match the IRI's JSON protocol. request = { 'bundles': [ text_type(BundleHash(self.trytes1)), text_type(BundleHash(self.trytes2)), ], 'addresses': [ text_type(Address(self.trytes1)), text_type(Address(self.trytes2)), ], 'tags': [ text_type(Tag(self.trytes1)), text_type(Tag(self.trytes3)), ], 'approvees': [ text_type(TransactionHash(self.trytes1)), text_type(TransactionHash(self.trytes3)), ], } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request)
def test_fail_transfers_contents_invalid(self): """ ``transfers`` is a non-empty array, but it contains invalid values. """ self.assertFilterErrors( { 'transfers': [ None, # This value is valid; just adding it to make sure the filter # doesn't cheat! ProposedTransaction(address=Address(self.trytes2), value=42), { 'address': Address(self.trytes2), 'value': 42 }, ], 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), }, { 'transfers.0': [f.Required.CODE_EMPTY], 'transfers.2': [f.Type.CODE_WRONG_TYPE], }, )
def test_pass_addresses_only(self): """ The request only includes addresses. """ request = { 'addresses': [ Address(self.trytes1), Address(self.trytes2), ], } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'addresses': [ text_type(Address(self.trytes1)), text_type(Address(self.trytes2)), ], # Null criteria are not included in the request. # https://github.com/iotaledger/iota.lib.py/issues/96 # 'approvees': [], # 'bundles': [], # 'tags': [], }, )
def test_security_level(self): """ Generating addresses with a different security level. """ response =\ self.command( count = 2, index = 0, securityLevel = 1, seed = self.seed, ) # noinspection SpellCheckingInspection self.assertDictEqual( response, { 'addresses': [ Address( b'ERBTZTPT9SKDQEGETKMZLYNRQMZYZIDENGWCSGRF' b'9TLURIEFVKUBSWOIMLMWTWMWTTHSUREPISXDPLCQC', ), Address( b'QVHEMGYHVMCFAISJKTWPFSKDAFRZHXQZK9E9KOUQ' b'LOLVBN9BFAZDDY9O9EYYMHMDWZAKXI9OPBPEYM9FC', ), ], }, )
def test_get_addresses_single(self): """ Generating a single address. """ ag = AddressGenerator(self.seed_1) # noinspection SpellCheckingInspection self.assertListEqual( ag.get_addresses(start=0), [ Address( b'DLEIS9XU9V9T9OURAKDUSQWBQEYFGJLRPRVEWKN9' b'SSUGIHBEIPBPEWISSAURGTQKWKWNHXGCBQTWNOGIY', ), ], ) # noinspection SpellCheckingInspection self.assertListEqual( ag.get_addresses(start=10), [ Address( b'XLXFTFBXUOOHRJDVBDBFEBDQDUKSLSOCLUYWGLAP' b'R9FUROUHPFINIUFKYSRTFMNWKNEPDZATWXIVWJMDD', ), ], )
def test_pass_happy_path(self): """ Request is valid. """ request = { 'changeAddress': Address(self.trytes_1), 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [ ProposedTransaction( address = Address(self.trytes_3), value = 42, ), ], } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request)
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_fail_transfers_wrong_type(self): """ ``transfers`` is not an array. """ self.assertFilterErrors( { 'changeAddress': Address(self.trytes_1), 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), # ``transfers`` must be an array, even if there's only one # transaction. 'transfers': ProposedTransaction( address = Address(self.trytes_3), value = 42, ), }, { 'transfers': [f.Array.CODE_WRONG_TYPE], }, )
def setUp(self): super(GetAccountDataCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = GetAccountDataCommand(self.adapter) # Define some tryte sequences we can re-use between tests. self.addy1 =\ Address( b'TESTVALUEONE9DONTUSEINPRODUCTION99999YDZ' b'E9TAFAJGJA9CECKDAEPHBICDR9LHFCOFRBQDHC9IG', key_index = 0, ) self.addy2 =\ Address( b'TESTVALUETWO9DONTUSEINPRODUCTION99999TES' b'GINEIDLEEHRAOGEBMDLENFDAFCHEIHZ9EBZDD9YHL', key_index = 1, ) self.hash1 =\ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999O99IDB' b'MBPAPDXBSDWAMHV9DASEGCOGHBV9VAF9UGRHFDPFJ' ) self.hash2 =\ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999OCNCHC' b'TEPBHEPBJEWFXERHSCQCH9TAAANDBBCCHCIDEAVBV' )
def test_fail_multisigInput_wrong_type(self): """ ``multisigInput`` is not a MultisigAddress. """ self.assertFilterErrors( { 'changeAddress': Address(self.trytes_1), # This value must be a MultisigAddress, so that we know the # total security level of the digests used to create it. 'multisigInput': Address(self.trytes_2), 'transfers': [ ProposedTransaction( address = Address(self.trytes_3), value = 42, ), ], }, { 'multisigInput': [f.Type.CODE_WRONG_TYPE], }, )
def test_generator_checksum(self): """ Creating a generator with checksums on the addresses. """ ag = AddressGenerator( self.seed_2, security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, checksum=True) generator = ag.create_iterator() # noinspection SpellCheckingInspection self.assertEqual( next(generator), Address( b'FNKCVJPUANHNWNBAHFBTCONMCUBC9KCZ9EKREBCJ' b'AFMABCTEPLGGXDJXVGPXDCFOUCRBWFJFLEAVOEUPY' b'ADHVCBXFD', ), ) # noinspection SpellCheckingInspection self.assertEqual( next(generator), Address( b'MSYILYYZLSJ99TDMGQHDOBWGHTBARCBGJZE9PIMQ' b'LTEXJXKTDREGVTPA9NDGGLQHTMGISGRAKSLYPGWMB' b'WIKQRCIOD', ), )
def test_fail_unexpected_parameters(self): """ Request contains unexpected parameters. """ self.assertFilterErrors( { 'changeAddress': Address(self.trytes_1), 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [ ProposedTransaction( address = Address(self.trytes_3), value = 42, ), ], # Oh come on! 'foo': 'bar', }, { 'foo': [f.FilterMapper.CODE_EXTRA_KEY], }, )
def setUp(self): super(GetNewAddressesCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = GetNewAddressesCommand(self.adapter) self.seed =\ Seed( b'TESTVALUE9DONTUSEINPRODUCTION99999ZDCCUF' b'CBBIQCLGMEXAVFQEOF9DRAB9VCEBAGXAF9VF9FLHP', ) self.addy_1 =\ Address( b'NYMWLBUJEISSACZZBRENC9HEHYQXHCGQHSNHVCEA' b'ZDCTEVNGSDUEKTSYBSQGMVJRIEDHWDYSEYCFAZAH9', ) self.addy_2 =\ Address( b'NTPSEVZHQITARYWHIRTSIFSERINLRYVXLGIQKKHY' b'IWYTLQUUHDWSOVXLIKVJTYZBFKLABWRBFYVSMD9NB', ) self.addy_1_checksum =\ Address( b'NYMWLBUJEISSACZZBRENC9HEHYQXHCGQHSNHVCEA' b'ZDCTEVNGSDUEKTSYBSQGMVJRIEDHWDYSEYCFAZAH' b'9T9FPJROTW', )
def setUp(self): super(GetInputsCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = GetInputsCommand(self.adapter) # Define some valid tryte sequences that we can reuse between # tests. self.addy0 =\ Address( trytes = b'TESTVALUE9DONTUSEINPRODUCTION99999FIODSG' b'IC9CCIFCNBTBDFIEHHE9RBAEVGK9JECCLCPBIINAX', key_index = 0, ) self.addy1 =\ Address( trytes = b'TESTVALUE9DONTUSEINPRODUCTION999999EPCNH' b'MBTEH9KDVFMHHESDOBTFFACCGBFGACEDCDDCGICIL', key_index = 1, ) self.addy2 =\ Address( trytes = b'TESTVALUE9DONTUSEINPRODUCTION99999YDOHWF' b'U9PFOFHGKFACCCBGDALGI9ZBEBABFAMBPDSEQ9XHJ', key_index = 2, )
def test_checksum_valid(self): """ An address is created with a valid checksum. """ addy = Address( b'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFWYWZRE' b'9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVAITCOXAQSD', ) self.assertTrue(addy.is_checksum_valid()) self.assertEqual( binary_type(addy.with_valid_checksum()), b'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFWYWZRE' b'9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVAITCOXAQSD', )
def test_error_insufficient_inputs(self): """ The multisig input does not contain sufficient IOTAs to cover the spends. """ self.adapter.seed_response( command = GetBalancesCommand.command, response = { 'balances': [42], 'duration': 86, }, ) with self.assertRaises(ValueError): self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), value = 101, ), ], multisigInput = MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), )
def test_error_zero_iotas_transferred(self): """ The bundle doesn't spend any IOTAs. This is considered an error case because :py:meth:`MultisigIota.prepare_multisig_transfer` is specialized for generating bundles that require multisig inputs. Any bundle that doesn't require multisig functionality should be generated using :py:meth:`iota_async.api.Iota.prepare_transfer` instead. """ with self.assertRaises(ValueError): self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), value = 0, ), ], multisigInput = MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), )
def test_pass_changeAddress_multisig_address(self): """ ``changeAddress`` is allowed to be a MultisigAddress. """ change_addy =\ MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_1 ) filter_ = self._filter({ 'changeAddress': change_addy, 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [ ProposedTransaction( address = Address(self.trytes_3), value = 42, ), ], }) self.assertFilterPasses(filter_) self.assertIs(filter_.cleaned_data['changeAddress'], change_addy)
def test_init_automatic_pad(self): """ Addresses are automatically padded to 81 trytes. """ addy = Address(b'JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOK' b'HXXXGJHJDQPOMDOMNRDKYCZRUFZROZDADTHZC') self.assertEqual( binary_type(addy), # Note the extra 9's added to the end. b'JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOK' b'HXXXGJHJDQPOMDOMNRDKYCZRUFZROZDADTHZC9999', ) # This attribute will make more sense once we start working with # address checksums. self.assertEqual( binary_type(addy.address), b'JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOK' b'HXXXGJHJDQPOMDOMNRDKYCZRUFZROZDADTHZC9999', ) # Checksum is not generated automatically. self.assertIsNone(addy.checksum)
def test_checksum_null(self): """ An address is created without a checksum. """ trytes = (b'ZKIUDZXQYQAWSHPKSAATJXPAQZPGYCDCQDRSMWWCGQJNI' b'PCOORMDRNREDUDKBMUYENYTFVUNEWDBAKXMV') addy = Address(trytes) self.assertFalse(addy.is_checksum_valid()) self.assertEqual( binary_type(addy.with_valid_checksum()), b'ZKIUDZXQYQAWSHPKSAATJXPAQZPGYCDCQDRSMWWCGQJNI' b'PCOORMDRNREDUDKBMUYENYTFVUNEWDBAKXMVJJJGBARPB', )
def test_success_duplicate_digest(self): """ Using the same digest multiple times in the same multisig address? It's unconventional, admittedly, but the maths work out, so.. """ builder = MultisigAddressBuilder() builder.add_digest(self.digest_1) builder.add_digest(self.digest_2) # I have no idea why you'd want to do this, but that's why it's not # my job to make those kinds of decisions. builder.add_digest(self.digest_1) addy = builder.get_address() self.assertIsInstance(addy, MultisigAddress) # noinspection SpellCheckingInspection self.assertEqual( addy, Address( b'JXJLZDJENNRODT9VEIRPVDX9YRLMDYDEXCQUYFIU' b'XFKFJOYOGTJPEIBEKDNEFRFVVVSQFBGMNZRBGFARD', ), ) # Note that ``digest_1`` appears twice, because we added it twice. self.assertListEqual( addy.digests, [self.digest_1, self.digest_2, self.digest_1], )
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', ), }, )
def test_fail_changeAddress_wrong_type(self): """ ``changeAddress`` is not a TrytesCompatible value. """ self.assertFilterErrors( { 'changeAddress': 42, 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [ ProposedTransaction( address = Address(self.trytes_3), value = 42, ), ], }, { 'changeAddress': [f.Type.CODE_WRONG_TYPE], }, )
def test_sign_input_at_error_already_signed(self): """ Attempting to sign an input that is already signed. """ # Add a transaction so that we can finalize the bundle. # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999QARFLF' b'TDVATBVFTFCGEHLFJBMHPBOBOHFBSGAGWCM9PG9GX'), value=42, )) self.bundle.add_inputs([self.input_0_bal_eq_42]) self.bundle.finalize() # The existing signature fragment doesn't have to be valid; it just # has to be not empty. self.bundle[1].signature_message_fragment = Fragment(b'A') private_key =\ KeyGenerator(self.seed).get_key_for(self.input_0_bal_eq_42) with self.assertRaises(ValueError): self.bundle.sign_input_at(1, private_key)
def test_add_inputs_security_level(self): """ Each input's security level determines the number of transactions we will need in order to store the entire signature. """ # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address( b'TESTVALUE9DONTUSEINPRODUCTION99999XE9IVG' b'EFNDOCQCMERGUATCIEGGOHPHGFIAQEZGNHQ9W99CH', ), value=84, ), ) self.bundle.add_inputs([ self.input_4_bal_eq_42_sl_2, self.input_5_bal_eq_42_sl_3, ]) self.bundle.finalize() # Each input's security level determines how many transactions will # be needed to hold all of its signature fragments: # 1 spend + 2 fragments for input 0 + 3 fragments for input 1 self.assertEqual(len(self.bundle), 6)
def test_pass_compatible_types(self): """ The request contains values that can be converted to the expected types. """ filter_ = self._filter({ 'bundles': [ self.trytes1.encode('ascii'), BundleHash(self.trytes2), ], 'addresses': [ self.trytes1.encode('ascii'), Address(self.trytes2), ], 'tags': [ self.trytes1.encode('ascii'), Tag(self.trytes3), ], 'approvees': [ self.trytes1.encode('ascii'), TransactionHash(self.trytes3), ], }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { # Raw trytes are extracted to match the IRI's JSON protocol. 'bundles': [ text_type(BundleHash(self.trytes1)), text_type(BundleHash(self.trytes2)), ], 'addresses': [ text_type(Address(self.trytes1)), text_type(Address(self.trytes2)), ], 'tags': [ text_type(Tag(self.trytes1)), text_type(Tag(self.trytes3)), ], 'approvees': [ text_type(TransactionHash(self.trytes1)), text_type(TransactionHash(self.trytes3)), ], }, )
def test_checksum_invalid(self): """ An address is created with an invalid checksum. """ trytes = (b'IGKUOZGEFNSVJXETLIBKRSUZAWMYSVDPMHGQPCETEFNZP' b'XSJLZMBLAWDRLUBWPIPKFNEPADIWMXMYYRKQ') addy = Address(trytes + b'XYYNAFRMB' # <- Last tryte s/b 'A'. ) self.assertFalse(addy.is_checksum_valid()) self.assertEqual( binary_type(addy.with_valid_checksum()), b'IGKUOZGEFNSVJXETLIBKRSUZAWMYSVDPMHGQPCETEFNZP' b'XSJLZMBLAWDRLUBWPIPKFNEPADIWMXMYYRKQXYYNAFRMA', )
def test_pass_compatible_types(self): """ Request contains values that can be converted to the expected types. """ txn =\ ProposedTransaction( address = Address(self.trytes_3), value = 42, ) filter_ =\ self._filter({ # ``changeAddress`` can be any value that resolves to an # :py:class:`Address`. 'changeAddress': self.trytes_1, # ``multisigInput`` must be a :py:class:`MultisigInput` object. 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), # ``transfers`` must contain an array of # :py:class:`ProposedTransaction` objects. 'transfers': [txn], }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'changeAddress': Address(self.trytes_1), 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [txn], }, )
def test_fail_single_address(self): """ The incoming request contains a single address """ request = {'addresses': Address(self.address_1)} self.assertFilterErrors(request, { 'addresses': [f.Type.CODE_WRONG_TYPE], })
def test_send_unspent_inputs_to_error_already_finalized(self): """ Invoking ``send_unspent_inputs_to`` on a bundle that is already finalized. """ # Add 1 transaction so that we can finalize the bundle. # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999XE9IVG' b'EFNDOCQCMERGUATCIEGGOHPHGFIAQEZGNHQ9W99CH'), value=0, )) self.bundle.finalize() with self.assertRaises(RuntimeError): self.bundle.send_unspent_inputs_to(Address(b''))