def transfer(self, entity: Entity, to: AddressLike, amount: int, fee: int): """ Transfers wealth from one account to another account :param private_key_bin: The bytes of the private key of the source address :param to_address: The bytes of the targeted address to send funds to :param amount: The amount of funds being transfered :param fee: The fee associated with the transfer :return: The digest of the submitted transaction :raises: ApiError on any failures """ ENDPOINT = 'transfer' # format the data to be closed by the transaction # wildcard for the moment shard_mask = BitVector() # build up the basic transaction information tx = Transaction() tx.from_address = Address(entity) tx.valid_until = 10000 tx.charge_rate = 1 tx.charge_limit = fee tx.add_transfer(to, amount) tx.add_signer(entity) # encode and sign the transaction encoded_tx = encode_transaction(tx, [entity]) # submit the transaction return self._post_tx_json(encoded_tx, ENDPOINT)
def test_simple_transfer(self): EXPECTED_DIGEST = "257c5e1de3a0a895d66d57792f1a44425336bb05bc6c8479be83b4572e1b4d45" EXPECTED_PAYLOAD = \ "a1640000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d44235130ac5aab442e39f" \ "9aa27118956695229212dd2f1ab5b714e9f6bd581511c1010000000000000000000000000418c2a33af8bd2cba7f" \ "a714a840a308a217aa4483880b1ef14b4fdffe08ab956e3f4b921cec33be7c258cfd7025a2b9a942770e5b17758b" \ "cc4961bbdc75a0251c" # build the payload bytes for the transaction with mock.patch('random.getrandbits') as mock_counter: mock_counter.side_effect = [0] payload = Transaction() payload.from_address = IDENTITIES[0] payload.add_transfer(IDENTITIES[1], 256) payload.add_signer(IDENTITIES[0]) # sign the final transaction payload.sign(ENTITIES[0]) transaction_bytes = transaction.encode_transaction(payload) self.assertIsExpectedTx(payload, transaction_bytes, EXPECTED_PAYLOAD) # attempt to decode a transaction from the generated bytes buffer = io.BytesIO(transaction_bytes) success, tx = transaction.decode_transaction(buffer) self.assertTrue(success) self.assertTxAreEqual(payload, tx) # Check payload digest self.assertEqual(sha256_hex(payload.encode_payload()), EXPECTED_DIGEST)
def test_multiple_transfers(self): EXPECTED_DIGEST = "35c19ceff72218a36f9807c370e5625efc36e43ce4e9f2047f1b820162b8b3d9" EXPECTED_PAYLOAD = \ "a1660000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d4014235130ac5aab442e3" \ "9f9aa27118956695229212dd2f1ab5b714e9f6bd581511c1010020f478c7f74b50c187bf9a8836f382bd62977bae" \ "eaf19625608e7e912aa60098c10200da2e9c3191e3768d1c59ea43f6318367ed9b21e6974f46a60d0dd8976740af" \ "6dc2000186a000000000000000000000000418c2a33af8bd2cba7fa714a840a308a217aa4483880b1ef14b4fdffe" \ "08ab956e3f4b921cec33be7c258cfd7025a2b9a942770e5b17758bcc4961bbdc75a0251c" # build the payload bytes for the transaction with mock.patch('random.getrandbits') as mock_counter: mock_counter.side_effect = [0] payload = Transaction() payload.from_address = IDENTITIES[0] payload.add_transfer(IDENTITIES[1], 256) payload.add_transfer(IDENTITIES[2], 512) payload.add_transfer(IDENTITIES[3], 100000) payload.add_signer(IDENTITIES[0]) # sign the final transaction payload.sign(ENTITIES[0]) transaction_bytes = transaction.encode_transaction(payload) self.assertIsExpectedTx(payload, transaction_bytes, EXPECTED_PAYLOAD) # attempt to decode a transaction from the generated bytes buffer = io.BytesIO(transaction_bytes) success, tx = transaction.decode_transaction(buffer) self.assertTrue(success) self.assertTxAreEqual(payload, tx) # Check payload digest self.assertEqual(sha256_hex(payload.encode_payload()), EXPECTED_DIGEST)
def test_simple_transfer(self): EXPECTED_DIGEST = "7fff24ca77fbb23b9b3c460104ab1a74011bafe3965e15dc6ea6f25a4ac44392" EXPECTED_PAYLOAD = \ "a1440000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d44235130ac5aab442e39f" \ "9aa27118956695229212dd2f1ab5b714e9f6bd581511c1010000000000000000000000000418c2a33af8bd2cba7f" \ "a714a840a308a217aa4483880b1ef14b4fdffe08ab956e3f4b921cec33be7c258cfd7025a2b9a942770e5b17758b" \ "cc4961bbdc75a0251c" # build the payload bytes for the transaction with mock.patch('random.getrandbits') as mock_counter: mock_counter.side_effect = [0] payload = Transaction() payload.from_address = IDENTITIES[0] payload.add_transfer(IDENTITIES[1], 256) payload.add_signer(IDENTITIES[0]) # sign the final transaction transaction_bytes = encode_transaction(payload, [ENTITIES[0]]) self.assertIsExpectedTx(payload, transaction_bytes, EXPECTED_PAYLOAD) # attempt to decode a transaction from the generated bytes buffer = io.BytesIO(transaction_bytes) success, tx = decode_transaction(buffer) self.assertTrue(success) self.assertTxAreEqual(payload, tx) # Check payload digest buffer = io.BytesIO() encode_payload(buffer, payload) self.assertEqual(sha256_hash(buffer.getvalue()), EXPECTED_DIGEST)
def test_validity_ranges(self): EXPECTED_PAYLOAD = "a12700532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d4024235130ac5aab442e39f9aa27118956695229212dd2f1ab5b714e9f6bd581511c103e820f478c7f74b50c187bf9a8836f382bd62977baeeaf19625608e7e912aa60098c103e8da2e9c3191e3768d1c59ea43f6318367ed9b21e6974f46a60d0dd8976740af6dc103e8e6672a9d98da667e5dc25b2bca8acf9644a7ac0797f01cb5968abf39de011df2c103e864c0c8c103e8c2000f42400418c2a33af8bd2cba7fa714a840a308a217aa4483880b1ef14b4fdffe08ab956e3f4b921cec33be7c258cfd7025a2b9a942770e5b17758bcc4961bbdc75a0251c" # build the payload bytes for the transaction payload = Transaction() payload.from_address = IDENTITIES[0] payload.add_transfer(IDENTITIES[1], 1000) payload.add_transfer(IDENTITIES[2], 1000) payload.add_transfer(IDENTITIES[3], 1000) payload.add_transfer(IDENTITIES[4], 1000) payload.add_signer(IDENTITIES[0]) payload.charge_rate = 1000 payload.charge_limit = 1000000 payload.valid_from = 100 payload.valid_until = 200 # sign the final transaction transaction_bytes = encode_transaction(payload, [ENTITIES[0]]) self.assertIsExpectedTx(payload, transaction_bytes, EXPECTED_PAYLOAD) # attempt to decode a transaction from the generated bytes buffer = io.BytesIO(transaction_bytes) success, tx = decode_transaction(buffer) self.assertTrue(success) self.assertTxAreEqual(payload, tx)
def test_simple_transfer(self): EXPECTED_PAYLOAD = "a12400532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d44235130ac5aab442e39f9aa27118956695229212dd2f1ab5b714e9f6bd581511c101000000000418c2a33af8bd2cba7fa714a840a308a217aa4483880b1ef14b4fdffe08ab956e3f4b921cec33be7c258cfd7025a2b9a942770e5b17758bcc4961bbdc75a0251c" # build the payload bytes for the transaction payload = Transaction() payload.from_address = IDENTITIES[0] payload.add_transfer(IDENTITIES[1], 256) payload.add_signer(IDENTITIES[0]) # sign the final transaction transaction_bytes = encode_transaction(payload, [ENTITIES[0]]) self.assertIsExpectedTx(payload, transaction_bytes, EXPECTED_PAYLOAD) # attempt to decode a transaction from the generated bytes buffer = io.BytesIO(transaction_bytes) success, tx = decode_transaction(buffer) self.assertTrue(success) self.assertTxAreEqual(payload, tx)
def test_validity_ranges(self): EXPECTED_DIGEST = "98f10e8aa0bf4507db9eca66f0ff0b6a3eff35fe4def9ed86150c7ce72e71e80" EXPECTED_PAYLOAD = \ "a1470000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d4024235130ac5aab442e3" \ "9f9aa27118956695229212dd2f1ab5b714e9f6bd581511c103e820f478c7f74b50c187bf9a8836f382bd62977bae" \ "eaf19625608e7e912aa60098c103e8da2e9c3191e3768d1c59ea43f6318367ed9b21e6974f46a60d0dd8976740af" \ "6dc103e8e6672a9d98da667e5dc25b2bca8acf9644a7ac0797f01cb5968abf39de011df2c103e864c0c8c103e8c2" \ "000f424000000000000000000418c2a33af8bd2cba7fa714a840a308a217aa4483880b1ef14b4fdffe08ab956e3f" \ "4b921cec33be7c258cfd7025a2b9a942770e5b17758bcc4961bbdc75a0251c" # build the payload bytes for the transaction with mock.patch('random.getrandbits') as mock_counter: mock_counter.side_effect = [0] payload = Transaction() payload.from_address = IDENTITIES[0] payload.add_transfer(IDENTITIES[1], 1000) payload.add_transfer(IDENTITIES[2], 1000) payload.add_transfer(IDENTITIES[3], 1000) payload.add_transfer(IDENTITIES[4], 1000) payload.add_signer(IDENTITIES[0]) payload.charge_rate = 1000 payload.charge_limit = 1000000 payload.valid_from = 100 payload.valid_until = 200 # sign the final transaction transaction_bytes = encode_transaction(payload, [ENTITIES[0]]) self.assertIsExpectedTx(payload, transaction_bytes, EXPECTED_PAYLOAD) # attempt to decode a transaction from the generated bytes buffer = io.BytesIO(transaction_bytes) success, tx = decode_transaction(buffer) self.assertTrue(success) self.assertTxAreEqual(payload, tx) # Check payload digest buffer = io.BytesIO() encode_payload(buffer, payload) self.assertEqual(sha256_hash(buffer.getvalue()), EXPECTED_DIGEST)
def test_multiple_transfers(self): EXPECTED_PAYLOAD = "a12600532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d4014235130ac5aab442e39f9aa27118956695229212dd2f1ab5b714e9f6bd581511c1010020f478c7f74b50c187bf9a8836f382bd62977baeeaf19625608e7e912aa60098c10200da2e9c3191e3768d1c59ea43f6318367ed9b21e6974f46a60d0dd8976740af6dc2000186a00000000418c2a33af8bd2cba7fa714a840a308a217aa4483880b1ef14b4fdffe08ab956e3f4b921cec33be7c258cfd7025a2b9a942770e5b17758bcc4961bbdc75a0251c" # build the payload bytes for the transaction payload = Transaction() payload.from_address = IDENTITIES[0] payload.add_transfer(IDENTITIES[1], 256) payload.add_transfer(IDENTITIES[2], 512) payload.add_transfer(IDENTITIES[3], 100000) payload.add_signer(IDENTITIES[0]) # sign the final transaction transaction_bytes = encode_transaction(payload, [ENTITIES[0]]) self.assertIsExpectedTx(payload, transaction_bytes, EXPECTED_PAYLOAD) # attempt to decode a transaction from the generated bytes buffer = io.BytesIO(transaction_bytes) success, tx = decode_transaction(buffer) self.assertTrue(success) self.assertTxAreEqual(payload, tx)
def decode_transaction(stream: io.BytesIO) -> (bool, Transaction): # ensure the at the magic is correctly configured magic = stream.read(1)[0] if magic != MAGIC: raise RuntimeError('Unable to parse transaction from stream, invalid magic') # extract the header bytes header = stream.read(2) # parse the header types version = (header[0] & 0xE0) >> 5 charge_unit_flag = bool((header[0] & 0x08) >> 3) transfer_flag = bool((header[0] & 0x04) >> 2) multiple_transfers_flag = bool((header[0] & 0x02) >> 1) valid_from_flag = bool((header[0] & 0x01)) contract_type = (header[1] & 0xC0) >> 6 signature_count_minus1 = (header[1] & 0x3F) num_signatures = signature_count_minus1 + 1 # ensure that the version is correct if version != VERSION: raise RuntimeError('Unable to parse transaction from stream, incompatible version') # Ready empty reserved byte stream.read(1) tx = Transaction() # decode the address from the thread tx.from_address = address.decode(stream) if transfer_flag: # determine the number of transfers that are present in the transaction if multiple_transfers_flag: transfer_count = integer.decode(stream) + 2 else: transfer_count = 1 for n in range(transfer_count): to = address.decode(stream) amount = integer.decode(stream) tx.add_transfer(to, amount) if valid_from_flag: tx.valid_from = integer.decode(stream) tx.valid_until = integer.decode(stream) tx.charge_rate = integer.decode(stream) assert not charge_unit_flag, "Currently the charge unit field is not supported" tx.charge_limit = integer.decode(stream) if contract_type != NO_CONTRACT: contract_header = int(stream.read(1)[0]) wildcard = bool(contract_header & 0x80) shard_mask = BitVector() if not wildcard: extended_shard_mask_flag = bool(contract_header & 0x40) if not extended_shard_mask_flag: if contract_header & 0x10: mask = 0xf bit_size = 4 else: mask = 0x3 bit_size = 2 # extract the shard mask from the header shard_mask = BitVector.from_bytes(bytes([contract_header & mask]), bit_size) else: bit_length = 1 << ((contract_header & 0x3F) + 3) byte_length = bit_length // 8 assert (bit_length % 8) == 0 # this should be enforced as part of the spec # extract the mask from the next N bytes shard_mask = BitVector.from_bytes(stream.read(byte_length), bit_length) if contract_type == SMART_CONTRACT or contract_type == SYNERGETIC: contract_digest = address.decode(stream) contract_address = address.decode(stream) tx.target_contract(contract_digest, contract_address, shard_mask) elif contract_type == CHAIN_CODE: encoded_chain_code_name = bytearray.decode(stream) tx.target_chain_code(encoded_chain_code_name.decode('ascii'), shard_mask) else: # this is mostly a guard against a desync between this function and `_map_contract_mode` raise RuntimeError("Unhandled contract type") tx.action = bytearray.decode(stream).decode('ascii') tx.data = bytearray.decode(stream) # Read counter value tx.counter = struct.unpack('<Q', stream.read(8))[0] if signature_count_minus1 == 0x3F: additional_signatures = integer.decode(stream) num_signatures += additional_signatures # extract all the signing public keys from the stream public_keys = [identity.decode(stream) for _ in range(num_signatures)] # extract full copy of the payload payload_bytes = stream.getvalue()[:stream.tell()] verified = [] for ident in public_keys: # for n in range(num_signatures): # extract the signature from the stream signature = bytearray.decode(stream) # verify if this signature is correct verified.append(ident.verify(payload_bytes, signature)) # build a metadata object to store in the tx tx._signers[ident] = { 'signature': signature, 'verified': verified[-1], } return all(verified), tx