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 post_test(self, function: Callable, action: str, factory_function: Callable, entity: Entity, *args): with patch('fetchai.ledger.api.TokenApi._post_tx_json') as mock_post, \ patch('fetchai.ledger.api.token.TokenTxFactory.' + factory_function.__name__, autospec=factory_function) as mock_factory, \ patch('fetchai.ledger.api.TokenApi._set_validity_period') as mock_set_valid, \ patch('warnings.warn') as mock_warnings, \ patch('fetchai.ledger.serialisation.transaction.encode_transaction') as mock_encode: # the transaction factory should generate a known transaction tx = Transaction() tx.sign = MagicMock() tx.sign.side_effect = [None] mock_factory.side_effect = [tx] mock_post.side_effect = ['result'] mock_encode.side_effect = ['encoded'] # run the production code result = function(entity, *args) mock_factory.assert_called_once_with(entity, *args, [entity]) mock_set_valid.assert_called_once_with(tx) tx.sign.assert_called_once_with(entity) mock_encode.assert_called_once_with(tx) mock_post.assert_called_once_with('encoded', action) self.assertEqual(result, 'result')
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 _create_skeleton_tx(self, fee: int, validity_period: Optional[int] = None): # build up the basic transaction information tx = Transaction() tx.charge_rate = 1 tx.charge_limit = fee self._set_validity_period(tx, validity_period) return tx
def _set_validity_period(self, tx: Transaction, validity_period: Optional[int] = None): validity_period = validity_period or DEFAULT_BLOCK_VALIDITY_PERIOD # query what the current block number is on the node current_block = self.current_block_number() # populate both the valid from and valid until tx.valid_from = current_block tx.valid_until = current_block + validity_period return tx.valid_until
def test_multiple_transfers(self): EXPECTED_DIGEST = "7e6a95a8c773755d349209d8f3bb60ec2a5f3c683075540f953863f124eb1250" EXPECTED_PAYLOAD = \ "a1460000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d4014235130ac5aab442e3" \ "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 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 _create_skeleton_tx(self, fee: int, validity_period: Optional[int] = None): validity_period = validity_period or DEFAULT_BLOCK_VALIDITY_PERIOD # query what the current block number is on the node current_block = self._current_block_number() # build up the basic transaction information tx = Transaction() tx.valid_until = current_block + validity_period tx.charge_rate = 1 tx.charge_limit = fee return tx
def test_merge_tx_signatures(self): payload = self.mstx.encode_payload() partials = [] for signer in self.multi_sig_board: tx = Transaction.decode_payload(payload) tx.sign(signer) partials.append(tx.encode_partial()) for partial in partials: _, partial_tx = Transaction.decode_partial(partial) self.assertTrue(self.mstx.merge_signatures(partial_tx)) self.assertTrue(self.mstx.is_valid())
def test_invalid_sig(self): self.mstx.add_signature(self.multi_sig_board[0], b'invalid') encoded = self.mstx.encode_partial() success, tx2 = Transaction.decode_partial(encoded) self.assertFalse(success) self.assertFalse(tx2.is_valid())
def test_encoding_of_complete_tx(self): self.tx.sign(self.source_identity) self.assertFalse(self.tx.is_incomplete) encoded = self.tx.encode() recovered_tx = Transaction.decode(encoded) self.assertIsNotNone(recovered_tx) self.assertEqual(self.tx, recovered_tx)
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_partial_serialize(self): self.mstx.sign(self.multi_sig_board[0]) self.mstx.sign(self.multi_sig_board[2]) self.assertEqual(len(self.mstx.present_signers), 2) encoded = self.mstx.encode_partial() success, tx2 = Transaction.decode_partial(encoded) self.assertFalse(success) # not all the signatures are populated self.assertEqual(self.mstx, tx2) # the body of the payload should be the same self.assertEqual(list(self.mstx.signatures), list(tx2.signatures)) # the signature present should all be the same self.assertEqual(len(tx2.present_signers), 2)
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 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 action(self, contract_digest: Address, contract_owner: Address, action: str, fee: int, signers: EntityList, *args): shard_mask = BitVector() # build up the basic transaction information tx = Transaction() tx.from_address = Address(contract_owner) tx.valid_until = 10000 tx.charge_rate = 1 tx.charge_limit = int(fee) tx.target_contract(contract_digest, contract_owner, shard_mask) tx.action = str(action) tx.data = self._encode_msgpack_payload(*args) for signer in signers: tx.add_signer(signer) encoded_tx = encode_transaction(tx, signers) return self._post_tx_json(encoded_tx, None)
def create(self, owner: Entity, contract: 'SmartContract', fee: int): ENDPOINT = 'create' # 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(owner) tx.valid_until = 10000 tx.charge_rate = 1 tx.charge_limit = fee tx.target_chain_code(self.API_PREFIX, shard_mask) tx.action = ENDPOINT tx.data = self._encode_json({ 'text': contract.encoded_source, 'digest': contract.digest.to_hex(), }) tx.add_signer(owner) # encode and sign the transaction encoded_tx = encode_transaction(tx, [owner]) # update the contracts owner contract.owner = owner # submit the transaction return self._post_tx_json(encoded_tx, ENDPOINT)
def test_contract_with_large_shard_mask(self): EXPECTED_DIGEST = "86a1c9e380fe5154243af3d50603aadb75327513373c0e2917b8319d3391b3ae" EXPECTED_PAYLOAD = \ "a1618000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d464c0c8c103e8c2000f42" \ "4041eaab0b666f6f2e6261722e62617a066c61756e63680000000000000000000418c2a33af8bd2cba7fa714a840" \ "a308a217aa4483880b1ef14b4fdffe08ab956e3f4b921cec33be7c258cfd7025a2b9a942770e5b17758bcc4961bb" \ "dc75a0251c" mask = BitVector(16) mask.set(15, 1) mask.set(14, 1) mask.set(13, 1) mask.set(11, 1) mask.set(9, 1) mask.set(7, 1) mask.set(5, 1) mask.set(3, 1) mask.set(1, 1) mask.set(0, 1) # 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_signer(IDENTITIES[0]) payload.charge_rate = 1000 payload.charge_limit = 1000000 payload.valid_from = 100 payload.valid_until = 200 payload.target_chain_code('foo.bar.baz', mask) payload.action = 'launch' # 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_contract_with_4bit_shard_mask(self): EXPECTED_DIGEST = "7915d6393fb07dbb4ff6896ef0f57025e5153b744d3a652b0f4815f129a9033c" EXPECTED_PAYLOAD = \ "a1418000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d464c0c8c103e8c2000f42" \ "401c0b666f6f2e6261722e62617a066c61756e63680000000000000000000418c2a33af8bd2cba7fa714a840a308" \ "a217aa4483880b1ef14b4fdffe08ab956e3f4b921cec33be7c258cfd7025a2b9a942770e5b17758bcc4961bbdc75" \ "a0251c" mask = BitVector(4) mask.set(3, 1) mask.set(2, 1) # 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_signer(IDENTITIES[0]) payload.charge_rate = 1000 payload.charge_limit = 1000000 payload.valid_from = 100 payload.valid_until = 200 payload.target_chain_code('foo.bar.baz', mask) payload.action = 'launch' # 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 _create_skeleton_tx(cls, fee: int) -> Transaction: # build up the basic transaction information tx = Transaction() tx.charge_rate = 1 tx.charge_limit = fee return tx
def test_synergetic_data_submission(self): EXPECTED_DIGEST = "261ba516c9b7b4d3ecb39f349dbb0a35db0d9fc362f2b9cc81c7d844be4d0081" EXPECTED_PAYLOAD = \ "a160c000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d4c1271001c3000000e8d4" \ "a5100080e6672a9d98da667e5dc25b2bca8acf9644a7ac0797f01cb5968abf39de011df204646174610f7b227661" \ "6c7565223a20313233347d00000000000000000418c2a33af8bd2cba7fa714a840a308a217aa4483880b1ef14b4f" \ "dffe08ab956e3f4b921cec33be7c258cfd7025a2b9a942770e5b17758bcc4961bbdc75a0251c" # 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.valid_until = 10000 payload.target_synergetic_data(Address(IDENTITIES[4]), BitVector()) payload.charge_rate = 1 payload.charge_limit = 1000000000000 payload.action = 'data' payload.data = json.dumps({'value': 1234}).encode('ascii') 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 submit_signed_tx(self, tx: Transaction): if not tx.is_valid(): raise RuntimeError('Signed transaction failed validation checks') return self.tokens.submit_signed_tx(tx)
def main(): # create the APIs api = LedgerApi(HOST, PORT) # we generate an identity from a known key, which contains funds. multi_sig_identity = Entity.from_hex( "6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b") # generate a board to control multi-sig account, with variable voting weights board = [ Entity.from_hex( "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8" ), Entity.from_hex( "4083a476c4872f25cb40839ac8d994924bcef12d83e2ba4bd3ed6c9705959860" ), Entity.from_hex( "20293422c4b5faefba3422ed436427f2d37f310673681e98ac8637b04e756de3" ), Entity.from_hex( "d5f10ad865fff147ae7fcfdc98b755452a27a345975c8b9b3433ff16f23495fb" ), ] voting_weights = { board[0]: 1, board[1]: 1, board[2]: 1, board[3]: 2, } # generate another entity as a target for transfers other_identity = Entity.from_hex( "7da0e3fa62a916238decd4f54d43301c809595d66dd469f82f29e076752b155c") # submit deed print("\nCreating deed...") deed = Deed() for sig, weight in voting_weights.items(): deed.set_signee(sig, weight) deed.set_operation(Operation.amend, 4) deed.set_operation(Operation.transfer, 3) api.sync(api.tokens.deed(multi_sig_identity, deed, 500)) # display balance before print("\nBefore remote-multisig transfer") print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) print() # scatter/gather example print("Generating transaction and distributing to signers...") # add intended signers to transaction ref_tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, signatories=board) api.set_validity_period(ref_tx) # make a reference payload that can be used in this script for validation reference_payload = ref_tx.encode_payload() # have signers individually sign transaction signed_txs = [] for signer in board: # signer builds their own transaction to compare to note that each of the signers will need to agree on all # parts of the message including the validity period and the counter signer_tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, signatories=board) signer_tx.counter = ref_tx.counter signer_tx.valid_until = ref_tx.valid_until signer_tx.valid_from = ref_tx.valid_from # sanity check each of the signers payload should match the reference payload assert signer_tx.encode_payload() == reference_payload # signers locally sign there version of the transaction signer_tx.sign(signer) # simulate distribution of signed partial transactions signed_txs.append(signer_tx.encode_partial()) # gather and encode final transaction - this step in theory can be done by all the signers provided they are # received all the signature shares print("Gathering and combining signed transactions...") partial_txs = [Transaction.decode_partial(s)[1] for s in signed_txs] # merge them together into one fully signed transaction success, tx = Transaction.merge(partial_txs) assert success # this indicates that all the signatures have been merged and that the transaction now validates # submit the transaction api.sync(api.submit_signed_tx(tx)) print("\nAfter remote multisig-transfer") print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) # round-robin example print("\nGenerating transaction and sending down the line of signers...") # create the basis for the transaction tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, signatories=board) api.set_validity_period(tx) # serialize and send to be signed tx_payload = tx.encode_payload() # have signers individually sign transaction and pass on to next signer for signer in board: # build the target transaction signer_tx = Transaction.decode_payload(tx_payload) # Signer decodes payload to inspect transaction signer_tx.sign(signer) # ensure that when we merge the signers signature into the payload that it is correct assert tx.merge_signatures(signer_tx) # once all the partial signatures have been merged then it makes sense print("Collecting final signed transaction...") assert tx.is_valid() api.sync(api.submit_signed_tx(tx)) print("\nAfter remote multisig-transfer") print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity))
def wealth(self, entity: Entity, amount: int): """ Creates wealth for specified account :param private_key_bin: The bytes of the private key of the targeted address :param amount: The amount of wealth to be generated :param fee: The fee value to be used for the transaction :return: The digest of the submitted transaction :raises: ApiError on any failures """ ENDPOINT = 'wealth' # 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 = 10 tx.target_chain_code(self.API_PREFIX, shard_mask) tx.action = 'wealth' tx.add_signer(entity) # format the transaction payload tx.data = self._encode_json({ 'address': entity.public_key, 'amount': amount }) # encode and sign the transaction encoded_tx = encode_transaction(tx, [entity]) # submit the transaction return self._post_tx_json(encoded_tx, ENDPOINT)
def test_validity_ranges(self): EXPECTED_DIGEST = "5451e302ba1fd323b623c1c9a0fc626b9c9249bb5d91ec60be1bb924efa3f1ac" EXPECTED_PAYLOAD = \ "a1670000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d4024235130ac5aab442e3" \ "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 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(tx, tx) # Check payload digest self.assertEqual(sha256_hex(payload.encode_payload()), EXPECTED_DIGEST)
def submit_data(self, entity: Entity, digest: Address, **kwargs): # build up the basic transaction information tx = Transaction() tx.from_address = Address(entity) tx.valid_until = 10000 tx.target_contract(digest, Address(entity), BitVector()) tx.charge_rate = 1 tx.charge_limit = 1000000000000 tx.action = 'data' tx.synergetic_data_submission = True tx.data = self._encode_json(dict(**kwargs)) tx.add_signer(entity) # encode the transaction encoded_tx = encode_transaction(tx, [entity]) # submit the transaction to the catch-all endpoint return self._post_tx_json(encoded_tx, None)
def test_contract_with_4bit_shard_mask(self): EXPECTED_DIGEST = "e1ac018356792e492aaac92bf6928af1e47ed987761b81cafb51f1106f403eee" EXPECTED_PAYLOAD = \ "a1618000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d464c0c8c103e8c2000f42" \ "401c0b666f6f2e6261722e62617a066c61756e63680000000000000000000418c2a33af8bd2cba7fa714a840a308" \ "a217aa4483880b1ef14b4fdffe08ab956e3f4b921cec33be7c258cfd7025a2b9a942770e5b17758bcc4961bbdc75" \ "a0251c" mask = BitVector(4) mask.set(3, 1) mask.set(2, 1) # 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_signer(IDENTITIES[0]) payload.charge_rate = 1000 payload.charge_limit = 1000000 payload.valid_from = 100 payload.valid_until = 200 payload.target_chain_code('foo.bar.baz', mask) payload.action = 'launch' # 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_chain_code(self): EXPECTED_DIGEST = "25cc72ca7d4871aaaabd027af129ecd4327adde5ec0c9977bfe11018d4bab64a" EXPECTED_PAYLOAD = \ "a1608000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d400c103e8c2000f424080" \ "0b666f6f2e6261722e62617a066c61756e636802676f00000000000000000418c2a33af8bd2cba7fa714a840a308" \ "a217aa4483880b1ef14b4fdffe08ab956e3f4b921cec33be7c258cfd7025a2b9a942770e5b17758bcc4961bbdc75" \ "a0251c" # 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_signer(IDENTITIES[0]) payload.charge_rate = 1000 payload.charge_limit = 1000000 payload.target_chain_code('foo.bar.baz', BitVector()) payload.action = 'launch' payload.data = 'go'.encode('ascii') # 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_smart_contract(self): EXPECTED_DIGEST = "032a72029ae2ac5cdbf9e07cf57d9ecab97a6de34ed5cdf785d1d98037cd5dcd" EXPECTED_PAYLOAD = \ "a1404000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d400c103e8c2000f424080" \ "da2e9c3191e3768d1c59ea43f6318367ed9b21e6974f46a60d0dd8976740af6de6672a9d98da667e5dc25b2bca8a" \ "cf9644a7ac0797f01cb5968abf39de011df2066c61756e636802676f00000000000000000418c2a33af8bd2cba7f" \ "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_signer(IDENTITIES[0]) payload.charge_rate = 1000 payload.charge_limit = 1000000 payload.target_contract(IDENTITIES[3], IDENTITIES[4], BitVector()) payload.action = 'launch' payload.data = 'go'.encode('ascii') # 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 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
def test_smart_contract(self): EXPECTED_DIGEST = "9ea094e71cbe846192429db3d7e8b02b649730c8b525c3268eb9ff5633c27130" EXPECTED_PAYLOAD = \ "a1604000532398dd883d1990f7dad3fde6a53a53347afc2680a04748f7f15ad03cadc4d400c103e8c2000f424080" \ "e6672a9d98da667e5dc25b2bca8acf9644a7ac0797f01cb5968abf39de011df2066c61756e636802676f00000000" \ "000000000418c2a33af8bd2cba7fa714a840a308a217aa4483880b1ef14b4fdffe08ab956e3f4b921cec33be7c25" \ "8cfd7025a2b9a942770e5b17758bcc4961bbdc75a0251c" # 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_signer(IDENTITIES[0]) payload.charge_rate = 1000 payload.charge_limit = 1000000 payload.target_contract(Address(IDENTITIES[4]), BitVector()) payload.action = 'launch' payload.data = 'go'.encode('ascii') # 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)