def test_calc_allowed_decimals(self): decimal = Transaction.calc_allowed_decimals(10000000000000000000) self.assertEqual(decimal, 0) decimal = Transaction.calc_allowed_decimals(1) self.assertEqual(decimal, 19) decimal = Transaction.calc_allowed_decimals(2) self.assertEqual(decimal, 18)
def rollback_tx_metadata(state: State, block, batch): fee_reward = 0 for protobuf_txn in block.transactions: txn = Transaction.from_pbdata(protobuf_txn) fee_reward += txn.fee TransactionMetadata.remove_tx_metadata(state, txn, batch) txn = Transaction.from_pbdata( block.transactions[0]) # Coinbase Transaction state._update_total_coin_supply(fee_reward - txn.amount, batch) LastTransactions._remove_last_tx(state, block, batch)
def tx_push(ctx, txblob): """ Sends a signed transaction blob to a node """ tx = None try: txbin = parse_hexblob(txblob) pbdata = xrd_pb2.Transaction() pbdata.ParseFromString(txbin) tx = Transaction.from_pbdata(pbdata) except Exception as e: click.echo("tx blob is not valid") quit(1) tmp_json = tx.to_json() # FIXME: binary fields are represented in base64. Improve output print(tmp_json) if len(tx.signature) == 0: click.echo('Signature missing') quit(1) stub = ctx.obj.get_stub_public_api() pushTransactionReq = xrd_pb2.PushTransactionReq(transaction_signed=tx.pbdata) pushTransactionResp = stub.PushTransaction(pushTransactionReq, timeout=CONNECTION_TIMEOUT) print(pushTransactionResp.error_code)
def update_tx_metadata(state: State, block, batch) -> bool: fee_reward = 0 for protobuf_txn in block.transactions: txn = Transaction.from_pbdata(protobuf_txn) fee_reward += txn.fee if not TransactionMetadata.put_tx_metadata( state, txn, block.block_number, block.timestamp, batch): return False txn = Transaction.from_pbdata( block.transactions[0]) # Coinbase Transaction state._update_total_coin_supply(txn.amount - fee_reward, batch) LastTransactions._update_last_tx(state, block, batch) return True
def test_from_json(self, m_logger): tx = Transaction.from_json(test_json_TransferToken) tx.sign(self.alice) self.assertIsInstance(tx, TransferTokenTransaction) # Test that common Transaction components were copied over. self.assertEqual( '010300a1da274e68c88b0ccf448e0b1916fa789b01eb2ed4e9ad565ce264c9390782a9c61ac02f', bin2hstr(tx.addr_from)) self.assertEqual( '01030038ea6375069f8272cc1a6601b3c76c21519455603d370036b97c779ada356' '5854e3983bd564298c49ae2e7fa6e28d4b954d8cd59398f1225b08d6144854aee0e', bin2hstr(tx.PK)) self.assertEqual(b'000000000000000', tx.token_txhash) self.assertEqual(200000, tx.total_amount) self.assertEqual( '390b159b34cffd29d4271a19679ff227df2ccd471078f177a7b58ca5f5d999f0', bin2hstr(tx.txhash)) self.assertEqual(10, tx.ots_key) # z = bin2hstr(tx.signature) # print('"', end='') # for i in range(len(z)): # print(z[i], end='') # if (i + 1) % 64 == 0: # print('" \\', end='') # print('') # print(' ' * len('test_signature_TransferToken = '), end='') # print('"', end='') self.assertEqual(test_signature_TransferToken, bin2hstr(tx.signature)) self.assertEqual(1, tx.fee)
def test_from_json(self, m_logger): tx = Transaction.from_json(test_json_Token) tx.sign(self.alice) self.assertIsInstance(tx, TokenTransaction) # Test that common Transaction components were copied over. self.assertEqual('010300a1da274e68c88b0ccf448e0b1916fa789b01eb2ed4e9ad565ce264c9390782a9c61ac02f', bin2hstr(tx.addr_from)) self.assertEqual('01030038ea6375069f8272cc1a6601b3c76c21519455603d370036b97c779ada356' '5854e3983bd564298c49ae2e7fa6e28d4b954d8cd59398f1225b08d6144854aee0e', bin2hstr(tx.PK)) self.assertEqual(b'xrd', tx.symbol) self.assertEqual(b'Quantum Resistant Ledger', tx.name) self.assertEqual('010317463dcd581b679b4754f46c6425125489a2826894e3c42a590efb6806450ce6bf52716c', bin2hstr(tx.owner)) self.assertEqual('ff84da605e9c9cd04d68503be7922110b4cc147837f8687ad18aa54b7bc5632d', bin2hstr(tx.txhash)) self.assertEqual(10, tx.ots_key) self.assertEqual(test_signature_Token, bin2hstr(tx.signature)) total_supply = 0 for initial_balance in tx.initial_balances: total_supply += initial_balance.amount self.assertEqual(600000000, total_supply) self.assertEqual(1, tx.fee)
def test_from_json(self, m_logger): tx = Transaction.from_json(test_json_Simple) tx.sign(self.alice) self.assertIsInstance(tx, TransferTransaction) # Test that common Transaction components were copied over. self.assertEqual(0, tx.nonce) self.assertEqual( '010300a1da274e68c88b0ccf448e0b1916fa789b01eb2ed4e9ad565ce264c9390782a9c61ac02f', bin2hstr(tx.addr_from)) self.assertEqual( '01030038ea6375069f8272cc1a6601b3c76c21519455603d370036b97c779ada356' '5854e3983bd564298c49ae2e7fa6e28d4b954d8cd59398f1225b08d6144854aee0e', bin2hstr(tx.PK)) self.assertEqual( '554f546305d4aed6ec71c759942b721b904ab9d65eeac3c954c08c652181c4e8', bin2hstr(tx.txhash)) self.assertEqual(10, tx.ots_key) self.assertEqual(test_signature_Simple, bin2hstr(tx.signature)) # Test that specific content was copied over. self.assertEqual( '0103001d65d7e59aed5efbeae64246e0f3184d7c42411421eb385ba30f2c1c005a85ebc4419cfd', bin2hstr(tx.addrs_to[0])) self.assertEqual(100, tx.total_amount) self.assertEqual(1, tx.fee)
def PushTransaction(self, request: xrd_pb2.PushTransactionReq, context) -> xrd_pb2.PushTransactionResp: logger.debug("[PublicAPI] PushTransaction") answer = xrd_pb2.PushTransactionResp() try: tx = Transaction.from_pbdata(request.transaction_signed) tx.update_txhash() # FIXME: Full validation takes too much time. At least verify there is a signature # the validation happens later in the tx pool if len(tx.signature) > 1000: self.xrdnode.submit_send_tx(tx) answer.error_code = xrd_pb2.PushTransactionResp.SUBMITTED answer.tx_hash = tx.txhash else: answer.error_description = 'Signature too short' answer.error_code = xrd_pb2.PushTransactionResp.VALIDATION_FAILED except Exception as e: error_str = traceback.format_exception(None, e, e.__traceback__) answer.error_description = str(''.join(error_str)) answer.error_code = xrd_pb2.PushTransactionResp.ERROR return answer
def get_tx_metadata(state: State, txhash: bytes): try: tx_metadata = TransactionMetadata.deserialize( state._db.get_raw(txhash)) data, block_number = tx_metadata.transaction, tx_metadata.block_number return Transaction.from_pbdata(data), block_number except Exception: return None
def test_validate_tx2(self, m_logger): tx = Transaction.from_json(test_json_MessageTransaction) tx.sign(self.alice) self.assertTrue(tx.validate_or_raise()) tx._data.transaction_hash = b'abc' # Should fail, as we have modified with invalid transaction_hash with self.assertRaises(ValueError): tx.validate_or_raise()
def test_update_mining_address(self): self.block.update_mining_address(dev_config=config.dev, mining_address=bob.address) coinbase_tx = Transaction.from_pbdata(self.block.transactions[0]) self.assertTrue(isinstance(coinbase_tx, CoinBase)) self.assertEqual(coinbase_tx.addr_to, bob.address) hashedtransactions = [] for tx in self.block.transactions: hashedtransactions.append(tx.transaction_hash) self.assertEqual(self.block.blockheader.tx_merkle_root, merkle_tx_hash(hashedtransactions))
def add_tx_from_block_to_pool(self, block: Block, current_block_number): """ Move all transactions from block to transaction pool. :param block: :return: """ for protobuf_tx in block.transactions[1:]: if not self.add_tx_to_pool(Transaction.from_pbdata(protobuf_tx), current_block_number): logger.warning('Failed to Add transaction into transaction pool') logger.warning('Block #%s %s', block.block_number, bin2hstr(block.headerhash)) return
def update_mining_address(self, dev_config: DevConfig, mining_address: bytes): coinbase_tx = Transaction.from_pbdata(self.transactions[0]) coinbase_tx.update_mining_address(mining_address) hashedtransactions = [] for tx in self.transactions: hashedtransactions.append(tx.transaction_hash) self.blockheader.update_merkle_root(dev_config, merkle_tx_hash(hashedtransactions)) self._data.header.MergeFrom(self.blockheader.pbdata)
def _remove_last_tx(state: State, block: Block, batch): if len(block.transactions) == 0: return try: last_txn = LastTransactions.deserialize( state._db.get_raw(b'last_txn')) except KeyError: return for protobuf_txn in block.transactions: txn = Transaction.from_pbdata(protobuf_txn) i = 0 while i < len(last_txn.tx_metadata): tx = Transaction.from_pbdata( last_txn.tx_metadata[i].transaction) if txn.txhash == tx.txhash: del last_txn.tx_metadata[i] break i += 1 state._db.put_raw(b'last_txn', last_txn.serialize(), batch)
def get_genesis_with_only_coin_base_txn(coin_base_reward_addr, dev_config): g = GenesisBlock() coin_base_tx = Transaction.from_pbdata(g.transactions[0]) coin_base_tx.update_mining_address(coin_base_reward_addr) # Remove all other transaction except CoinBase txn del g.transactions[:] g.pbdata.transactions.extend([coin_base_tx.pbdata]) g.blockheader.generate_headerhash(dev_config) return g
def valid_payment_permission(public_stub, master_address_state, payment_xmss, json_slave_txn): access_type = master_address_state.get_slave_permission(payment_xmss.pk) if access_type == -1: tx = Transaction.from_json(json_slave_txn) public_stub.PushTransaction(request=xrd_pb2.PushTransactionReq( transaction_signed=tx.pbdata)) return None if access_type == 0: return True return False
def tx_inspect(ctx, txblob): """ Inspected a transaction blob """ tx = None try: txbin = parse_hexblob(txblob) pbdata = xrd_pb2.Transaction() pbdata.ParseFromString(txbin) tx = Transaction.from_pbdata(pbdata) except Exception as e: click.echo("tx blob is not valid") quit(1) tmp_json = tx.to_json() # FIXME: binary fields are represented in base64. Improve output print(tmp_json)
def get_last_txs(state: State): try: last_txn = LastTransactions.deserialize( state._db.get_raw(b'last_txn')) except KeyError: return [] except Exception as e: # noqa logger.warning("[get_last_txs] Exception during call %s", e) return [] txs = [] for tx_metadata in last_txn.tx_metadata: data = tx_metadata.transaction tx = Transaction.from_pbdata(data) txs.append(tx) return txs
def _update_last_tx(state: State, block: Block, batch): if len(block.transactions) == 0: return last_txn = LastTransactions() try: last_txn = LastTransactions.deserialize( state._db.get_raw(b'last_txn')) except KeyError: pass for protobuf_txn in block.transactions[-20:]: txn = Transaction.from_pbdata(protobuf_txn) if isinstance(txn, CoinBase): continue last_txn.add(txn, block.block_number, block.timestamp) state._db.put_raw(b'last_txn', last_txn.serialize(), batch)
def remove_tx_in_block_from_pool(self, block_obj: Block): for protobuf_tx in block_obj.transactions[1:]: # Ignore first transaction, as it is a coinbase txn tx = Transaction.from_pbdata(protobuf_tx) if tx.ots_key < config.dev.max_ots_tracking_index: idx = self.get_tx_index_from_pool(tx.txhash) if idx > -1: del self.transaction_pool[idx] else: i = 0 while i < len(self.transaction_pool): txn = self.transaction_pool[i][1].transaction if txn.PK == tx.PK: if txn.ots_key >= config.dev.max_ots_tracking_index: if txn.ots_key <= tx.ots_key: del self.transaction_pool[i] continue i += 1 heapq.heapify(self.transaction_pool)
def test_from_json(self, m_logger): tx = Transaction.from_json(test_json_MessageTransaction) tx.sign(self.alice) self.assertIsInstance(tx, MessageTransaction) # Test that common Transaction components were copied over. self.assertEqual('010300a1da274e68c88b0ccf448e0b1916fa789b01eb2ed4e9ad565ce264c9390782a9c61ac02f', bin2hstr(tx.addr_from)) self.assertEqual('01030038ea6375069f8272cc1a6601b3c76c21519455603d370036b97c779ada356' '5854e3983bd564298c49ae2e7fa6e28d4b954d8cd59398f1225b08d6144854aee0e', bin2hstr(tx.PK)) self.assertEqual(b'Test Message', tx.message_hash) self.assertEqual('cbe7c40a86e82b8b6ac4e7df812f882183bd85d60f335cd83483d6831e61f4ec', bin2hstr(tx.txhash)) self.assertEqual(10, tx.ots_key) self.assertEqual(test_signature_MessageTransaction, bin2hstr(tx.signature)) self.assertEqual(1, tx.fee)
def handle_token_transaction(source, message: xrdlegacy_pb2.LegacyMessage): """ Token Transaction This function processes whenever a Transaction having subtype TOKEN is received. :return: """ P2PBaseObserver._validate_message(message, xrdlegacy_pb2.LegacyMessage.TK) try: tx = Transaction.from_pbdata(message.tkData) except Exception as e: logger.error( 'Token Txn rejected - unable to decode serialised data - closing connection' ) logger.exception(e) source.loseConnection() return if source.factory.master_mr.isRequested(tx.get_message_hash(), source): source.factory.add_unprocessed_txn(tx, source.peer.ip)
def handle_tx(source, message: xrdlegacy_pb2.LegacyMessage): """ Transaction Executed whenever a new TX type message is received. :return: """ P2PBaseObserver._validate_message(message, xrdlegacy_pb2.LegacyMessage.TX) try: tx = Transaction.from_pbdata(message.txData) except Exception as e: logger.error( 'Message Txn rejected - unable to decode serialised data - closing connection' ) logger.exception(e) source.loseConnection() return # NOTE: Connects to MR if source.factory.master_mr.isRequested(tx.get_message_hash(), source): source.factory.add_unprocessed_txn(tx, source.peer.ip)
def handle_lattice(source, message: xrdlegacy_pb2.LegacyMessage): """ Receives Lattice Public Key Transaction :param source: :param message: :return: """ P2PBaseObserver._validate_message(message, xrdlegacy_pb2.LegacyMessage.LT) try: tx = Transaction.from_pbdata(message.ltData) except Exception as e: logger.error( 'lattice_public_key rejected - unable to decode serialised data - closing connection' ) logger.exception(e) source.loseConnection() return if source.factory.master_mr.isRequested(tx.get_message_hash(), source): source.factory.add_unprocessed_txn(tx, source.peer.ip)
def handle_multi_sig_vote(source, message: xrdlegacy_pb2.LegacyMessage): """ Handles Multi Sig Transaction :param source: :param message: :return: """ P2PBaseObserver._validate_message(message, xrdlegacy_pb2.LegacyMessage.MV) try: tx = Transaction.from_pbdata(message.mvData) except Exception as e: logger.error( 'multi_sig_vote txn rejected - unable to decode serialised data - closing connection' ) logger.exception(e) source.loseConnection() return if source.factory.master_mr.isRequested(tx.get_message_hash(), source): source.factory.add_unprocessed_txn(tx, source.peer.ip)
def create_block(self, last_block, mining_nonce, tx_pool: TransactionPool, miner_address) -> Optional[Block]: seed_block = self._chain_manager.get_block_by_number( self._qn.get_seed_height(last_block.block_number + 1)) dev_config = self._chain_manager.get_config_by_block_number( block_number=last_block.block_number + 1) dummy_block = Block.create(dev_config=dev_config, block_number=last_block.block_number + 1, prev_headerhash=last_block.headerhash, prev_timestamp=last_block.timestamp, transactions=[], miner_address=miner_address, seed_height=seed_block.block_number, seed_hash=seed_block.headerhash) dummy_block.set_nonces(dev_config, mining_nonce, 0) t_pool2 = tx_pool.transactions block_size = dummy_block.size block_size_limit = self._chain_manager.get_block_size_limit( last_block, dev_config) transactions = [] state_container = self._chain_manager.new_state_container( set(), last_block.block_number, True, None) for tx_set in t_pool2: tx = tx_set[1].transaction # Skip Transactions for later, which doesn't fit into block if block_size + tx.size + dev_config.tx_extra_overhead > block_size_limit: continue if not self._chain_manager.update_state_container( tx, state_container): logger.error("[create_block] Error updating state_container") return None if not tx.validate_all(state_container, check_nonce=False): if not state_container.revert_update(): return None tx_pool.remove_tx_from_pool(tx) continue if not self._chain_manager.apply_txn(tx, state_container): logger.error("[create_block] Failed to apply txn") if not state_container.revert_update(): return None continue addr_from_pk_state = state_container.addresses_state[tx.addr_from] addr_from_pk = Transaction.get_slave(tx) if addr_from_pk: addr_from_pk_state = state_container.addresses_state[ addr_from_pk] tx._data.nonce = addr_from_pk_state.nonce block_size += tx.size + dev_config.tx_extra_overhead transactions.append(tx) block = Block.create(dev_config=dev_config, block_number=last_block.block_number + 1, prev_headerhash=last_block.headerhash, prev_timestamp=last_block.timestamp, transactions=transactions, miner_address=miner_address, seed_height=seed_block.block_number, seed_hash=seed_block.headerhash) return block
def validate(self, chain_manager, future_blocks: OrderedDict) -> bool: if chain_manager.get_block_is_duplicate(self): logger.warning('Duplicate Block #%s %s', self.block_number, bin2hstr(self.headerhash)) return False parent_block = chain_manager.get_block(self.prev_headerhash) dev_config = chain_manager.get_config_by_block_number( self.block_number) # If parent block not found in state, then check if its in the future block list if not parent_block: try: parent_block = future_blocks[self.prev_headerhash] except KeyError: logger.warning('Parent block not found') logger.warning('Parent block headerhash %s', bin2hstr(self.prev_headerhash)) return False if not self._validate_parent_child_relation(parent_block): logger.warning('Failed to validate blocks parent child relation') return False if not chain_manager.validate_mining_nonce(self.blockheader, dev_config): logger.warning('Failed PoW Validation') return False if len(self.transactions) == 0: return False try: state_container = chain_manager.new_state_container( set(), self.block_number, False, None) coinbase_txn = Transaction.from_pbdata(self.transactions[0]) coinbase_amount = coinbase_txn.amount if not coinbase_txn.validate_all(state_container): return False except Exception as e: logger.warning('Exception %s', e) return False # Build transaction merkle tree, calculate fee reward, and then see if BlockHeader also agrees. hashedtransactions = [] for tx in self.transactions: tx = Transaction.from_pbdata(tx) hashedtransactions.append(tx.txhash) fee_reward = 0 for index in range(1, len(self.transactions)): fee_reward += self.transactions[index].fee qn = Qryptonight() seed_block = chain_manager.get_block_by_number( qn.get_seed_height(self.block_number)) self.blockheader._seed_height = seed_block.block_number self.blockheader._seed_hash = seed_block.headerhash if not self.blockheader.validate(fee_reward, coinbase_amount, merkle_tx_hash(hashedtransactions), dev_config): return False return True
def gen_blocks(block_count, state, miner_address): blocks = [] block = None with mock.patch('xrd.core.misc.ntp.getTime') as time_mock: time_mock.return_value = 1615270948 addresses_state = dict() for i in range(0, block_count): if i == 0: block = GenesisBlock() for genesis_balance in GenesisBlock().genesis_balance: bytes_addr = genesis_balance.address addresses_state[ bytes_addr] = OptimizedAddressState.get_default( bytes_addr) addresses_state[ bytes_addr]._data.balance = genesis_balance.balance else: block = Block.create(dev_config=config.dev, block_number=i, prev_headerhash=block.headerhash, prev_timestamp=block.timestamp, transactions=[], miner_address=miner_address, seed_height=None, seed_hash=None) addresses_set = ChainManager.set_affected_address(block) coin_base_tx = Transaction.from_pbdata(block.transactions[0]) coin_base_tx.set_affected_address(addresses_set) chain_manager = ChainManager(state) state_container = chain_manager.new_state_container( addresses_set, block.block_number, False, None) coin_base_tx.apply(state, state_container) for tx_idx in range(1, len(block.transactions)): tx = Transaction.from_pbdata(block.transactions[tx_idx]) if not chain_manager.update_state_container( tx, state_container): return False tx.apply(state, state_container) block.set_nonces(dev_config=config.dev, mining_nonce=10, extra_nonce=0) blocks.append(block) metadata = BlockMetadata() metadata.set_block_difficulty(StringToUInt256('256')) BlockMetadata.put_block_metadata(state, block.headerhash, metadata, None) Block.put_block(state, block, None) bm = xrd_pb2.BlockNumberMapping( headerhash=block.headerhash, prev_headerhash=block.prev_headerhash) Block.put_block_number_mapping(state, block.block_number, bm, None) state.update_mainchain_height(block.block_number, None) OptimizedAddressState.put_optimized_addresses_state( state, addresses_state) return blocks
def GetObject(self, request: xrd_pb2.GetObjectReq, context) -> xrd_pb2.GetObjectResp: logger.debug("[PublicAPI] GetObject") answer = xrd_pb2.GetObjectResp() answer.found = False # FIXME: We need a unified way to access and validate data. query = bytes( request.query ) # query will be as a string, if Q is detected convert, etc. try: if AddressState.address_is_valid(query): if self.xrdnode.get_address_is_used(query): address_state = self.xrdnode.get_optimized_address_state( query) if address_state is not None: answer.found = True answer.address_state.CopyFrom(address_state.pbdata) return answer except ValueError: pass transaction_block_number = self.xrdnode.get_transaction(query) transaction = None blockheader = None if transaction_block_number: transaction, block_number = transaction_block_number answer.found = True block = self.xrdnode.get_block_from_index(block_number) blockheader = block.blockheader.pbdata timestamp = block.blockheader.timestamp else: transaction_timestamp = self.xrdnode.get_unconfirmed_transaction( query) if transaction_timestamp: transaction, timestamp = transaction_timestamp answer.found = True if transaction: txextended = xrd_pb2.TransactionExtended( header=blockheader, tx=transaction.pbdata, addr_from=transaction.addr_from, size=transaction.size, timestamp_seconds=timestamp) answer.transaction.CopyFrom(txextended) return answer # NOTE: This is temporary, indexes are accepted for blocks try: block = self.xrdnode.get_block_from_hash(query) if block is None or (block.block_number == 0 and block.prev_headerhash != config.user.genesis_prev_headerhash): query_str = query.decode() query_index = int(query_str) block = self.xrdnode.get_block_from_index(query_index) if not block: return answer answer.found = True block_extended = xrd_pb2.BlockExtended() block_extended.header.CopyFrom(block.blockheader.pbdata) block_extended.size = block.size for transaction in block.transactions: tx = Transaction.from_pbdata(transaction) extended_tx = xrd_pb2.TransactionExtended( tx=transaction, addr_from=tx.addr_from, size=tx.size, timestamp_seconds=block.blockheader.timestamp) block_extended.extended_transactions.extend([extended_tx]) answer.block_extended.CopyFrom(block_extended) return answer except Exception: pass return answer
def test_messageTxn_sign(self): with set_xrd_dir('wallet_ver1'): with State() as db_state: p2p_factory = Mock(spec=P2PFactory) p2p_factory.pow = Mock(spec=POW) chain_manager = ChainManager(db_state) xrdnode = xrdNode(mining_address=b'') xrdnode.set_chain_manager(chain_manager) xrdnode._p2pfactory = p2p_factory xrdnode._pow = p2p_factory.pow xrdnode._peer_addresses = ['127.0.0.1', '192.168.1.1'] service = PublicAPIService(xrdnode) context = Mock(spec=ServicerContext) alice = get_alice_xmss() my_message = b'Hello xrd!' request = xrd_pb2.MessageTxnReq( message=my_message, fee=12, xmss_pk=alice.pk ) response = service.GetMessageTxn(request=request, context=context) context.set_code.assert_not_called() context.set_details.assert_not_called() self.assertIsNotNone(response) self.assertIsNotNone(response.extended_transaction_unsigned.tx) self.assertEqual('message', response.extended_transaction_unsigned.tx.WhichOneof('transactionType')) self.assertEqual(12, response.extended_transaction_unsigned.tx.fee) self.assertEqual(alice.pk, response.extended_transaction_unsigned.tx.public_key) self.assertEqual(0, response.extended_transaction_unsigned.tx.nonce) self.assertEqual(b'', response.extended_transaction_unsigned.tx.signature) self.assertEqual(b'', response.extended_transaction_unsigned.tx.transaction_hash) self.assertEqual(my_message, response.extended_transaction_unsigned.tx.message.message_hash) tmp_hash_pre = response.extended_transaction_unsigned.tx.master_addr tmp_hash_pre += response.extended_transaction_unsigned.tx.fee.to_bytes(8, byteorder='big', signed=False) tmp_hash_pre += response.extended_transaction_unsigned.tx.message.message_hash tmp_hash = sha256(tmp_hash_pre) hash_found = bin2hstr(Transaction.from_pbdata(response.extended_transaction_unsigned.tx). get_data_hash()) self.assertEqual(hash_found, bin2hstr(tmp_hash)) signed_transaction = response.extended_transaction_unsigned.tx signed_transaction.signature = alice.sign(tmp_hash) req_push = xrd_pb2.PushTransactionReq(transaction_signed=signed_transaction) resp_push = service.PushTransaction(req_push, context=context) context.set_code.assert_not_called() context.set_details.assert_not_called() self.assertIsNotNone(resp_push) self.assertEqual(xrd_pb2.PushTransactionResp.SUBMITTED, resp_push.error_code) self.assertEqual('b7ee814a548a6bbb8d97b2d3a0eb9e1f8b6ceee49b764e3c7b23d104aca6abeb', bin2hstr(resp_push.tx_hash))