def update_io(self, i_text, o_text): ext = QTextCharFormat() rec = QTextCharFormat() rec.setBackground(QBrush(ColorScheme.GREEN.as_color(background=True))) rec.setToolTip(_("Wallet receive address")) chg = QTextCharFormat() chg.setBackground(QBrush(QColor("yellow"))) chg.setToolTip(_("Wallet change address")) def text_format(addr): if isinstance(addr, Address) and self.wallet.is_mine(addr): return chg if self.wallet.is_change(addr) else rec return ext def format_amount(amt): return self.main_window.format_amount(amt, whitespaces=True) i_text.clear() cursor = i_text.textCursor() for txin in self.tx.inputs: if txin.is_coinbase(): cursor.insertText('coinbase') else: prev_hash = hash_to_hex_str(txin.prev_hash) prev_idx = txin.prev_idx cursor.insertText(f'{prev_hash}:{prev_idx:<6d}', ext) addr = txin.address if isinstance(addr, PublicKey): addr = addr.toAddress() if addr is None: addr_text = _('unknown') else: addr_text = addr.to_string() cursor.insertText(addr_text, text_format(addr)) if txin.value is not None: cursor.insertText(format_amount(txin.value), ext) cursor.insertBlock() o_text.clear() cursor = o_text.textCursor() for tx_output in self.tx.outputs: text, kind = tx_output_to_display_text(tx_output) cursor.insertText(text, text_format(kind)) if len(text) > 42: # for long outputs, make a linebreak. cursor.insertBlock() text = '\u21b3' cursor.insertText(text, ext) # insert enough spaces until column 43, to line up amounts cursor.insertText(' ' * (43 - len(text)), ext) cursor.insertText(format_amount(tx_output.value), ext) cursor.insertBlock()
def update_io(self, i_text: QTextEdit, o_text: QTextEdit): ext = QTextCharFormat() rec = QTextCharFormat() rec.setBackground(QBrush(ColorScheme.GREEN.as_color(background=True))) rec.setToolTip(_("Wallet receive key")) # chg = QTextCharFormat() # chg.setBackground(QBrush(QColor("yellow"))) # chg.setToolTip(_("Wallet change key")) def verify_own_output(output: XTxOutput) -> bool: if not output.x_pubkeys: return False for x_pubkey in output.x_pubkeys: result = self._main_window._wallet.resolve_xpubkey(x_pubkey) if result is not None: account, keyinstance_id = result return account.get_script_for_id( keyinstance_id) == output.script_pubkey return False known_txos = set(self._account._utxos) | set(self._account._stxos) def text_format(utxo_key: Tuple[bytes, int]) -> QTextCharFormat: nonlocal known_txos return rec if utxo_key in known_txos else ext def format_amount(amt: int) -> str: return self._main_window.format_amount(amt, whitespaces=True) i_text.clear() cursor = i_text.textCursor() for txin in self.tx.inputs: if txin.is_coinbase(): cursor.insertText('coinbase') else: prev_hash_hex = hash_to_hex_str(txin.prev_hash) cursor.insertText(f'{prev_hash_hex}:{txin.prev_idx:<6d}', ext) txo_key = (txin.prev_hash, txin.prev_idx) if txo_key in known_txos: txo_text = _("Mine") else: txo_text = _("Unknown") cursor.insertText(txo_text, text_format(txo_key)) if txin.value is not None: cursor.insertText(format_amount(txin.value), ext) cursor.insertBlock() o_text.clear() cursor = o_text.textCursor() tx_hash: bytes = self.tx.hash() for tx_index, tx_output in enumerate(self.tx.outputs): text, kind = tx_output_to_display_text(tx_output) out_format = ext if verify_own_output(tx_output): out_format = rec elif (self._tx_hash, tx_index) in known_txos: out_format = rec cursor.insertText(text, out_format) if len(text) > 42: # for long outputs, make a linebreak. cursor.insertBlock() text = '\u21b3' cursor.insertText(text, ext) # insert enough spaces until column 43, to line up amounts cursor.insertText(' ' * (43 - len(text)), ext) cursor.insertText(format_amount(tx_output.value), ext) cursor.insertBlock()
def _update_io(self, i_table: MyTreeWidget, o_table: MyTreeWidget) -> None: def get_xtxoutput_account( output: XTxOutput) -> Tuple[Optional[AbstractAccount], int]: if output.x_pubkeys: for x_pubkey in output.x_pubkeys: result = self._main_window._wallet.resolve_xpubkey( x_pubkey) if result is not None: account, keyinstance_id = result if account.get_script_for_id( keyinstance_id) == output.script_pubkey: return account, keyinstance_id # TODO: Document when this happens break return None, -1 def get_keyinstance_id(account: AbstractAccount, txo_key: TxoKeyType) -> Optional[int]: utxo = account._utxos.get(txo_key) if utxo is not None: return utxo.keyinstance_id stxo_keyinstance_id = account._stxos.get(txo_key) if stxo_keyinstance_id is not None: return stxo_keyinstance_id return None def compare_key_path(account: AbstractAccount, keyinstance_id: int, leading_path: Sequence[int]) -> bool: key_path = account.get_derivation_path(keyinstance_id) return key_path is not None and key_path[:len(leading_path )] == leading_path def name_for_account(account: AbstractAccount) -> str: name = account.display_name() return f"{account.get_id()}: {name}" is_tx_complete = self.tx.is_complete() is_tx_known = self._account and self._account.have_transaction_data( self._tx_hash) prev_txos = self._coin_service.get_outputs([ TxoKeyType(txin.prev_hash, txin.prev_idx) for txin in self.tx.inputs ]) prev_txo_dict = { TxoKeyType(r.tx_hash, r.tx_index): r for r in prev_txos } self._spent_value_label.setText( _("Spent input value") + ": " + app_state.format_amount(sum(r.value for r in prev_txos))) for tx_index, txin in enumerate(self.tx.inputs): account_name = "" source_text = "" amount_text = "" is_receiving = is_change = is_broken = False txo_key = TxoKeyType(txin.prev_hash, txin.prev_idx) if txin.is_coinbase(): source_text = "<coinbase>" else: prev_hash_hex = hash_to_hex_str(txin.prev_hash) source_text = f"{prev_hash_hex}:{txin.prev_idx}" # There are only certain kinds of transactions that have values on the inputs, # likely deserialised incomplete transactions from cosigners. Others? value = txin.value if self._account is not None: keyinstance_id = get_keyinstance_id(self._account, txo_key) is_receiving = compare_key_path(self._account, keyinstance_id, RECEIVING_SUBPATH) is_change = compare_key_path(self._account, keyinstance_id, CHANGE_SUBPATH) account_name = name_for_account(self._account) prev_txo = prev_txo_dict.get(txo_key, None) if prev_txo is not None and is_tx_complete: value = prev_txo.value if is_tx_known: # The transaction has been added to the account. is_broken = (prev_txo.flags & TransactionOutputFlag.IS_SPENT) == 0 else: # The transaction was most likely loaded from external source and is # being viewed but has not been added to the account. is_broken = (prev_txo.flags & TransactionOutputFlag.IS_SPENT) != 0 amount_text = app_state.format_amount(value, whitespaces=True) item = QTreeWidgetItem( [str(tx_index), account_name, source_text, amount_text]) item.setData(InputColumns.INDEX, Roles.TX_HASH, txin.prev_hash) item.setData(InputColumns.INDEX, Roles.IS_MINE, is_change or is_receiving) if is_receiving: item.setBackground(InputColumns.SOURCE, self._receiving_brush) if is_change: item.setBackground(InputColumns.SOURCE, self._change_brush) if is_broken: item.setBackground(InputColumns.SOURCE, self._broken_brush) item.setTextAlignment(InputColumns.AMOUNT, Qt.AlignRight | Qt.AlignVCenter) item.setFont(InputColumns.AMOUNT, self._monospace_font) i_table.addTopLevelItem(item) # TODO: Rewrite this to be lot simpler when we have better TXO management. At this time # we do not track UTXOs except when a transaction is known to the network as we rely on # the server state to map key usage to transactions or something. We need to completely # rewrite that and then rewrite this. Anyway, that is why signed tx outputs do not get # identified and colourised. received_value = 0 for tx_index, tx_output in enumerate(self.tx.outputs): text, _kind = tx_output_to_display_text(tx_output) if isinstance(_kind, Unknown_Output): text = script_bytes_to_asm(tx_output.script_pubkey) # In the longer run we will have some form of abstraction for incomplete transactions # that maps where the keys come from, but for now we manually map them to the limited # key hierarchy that currently exists. xtxo_account, xtxo_keyinstance_id = get_xtxoutput_account( tx_output) accounts: List[AbstractAccount] = [] if xtxo_account is not None: accounts.append(xtxo_account) if self._account is not None and xtxo_account is not self._account: accounts.append(self._account) account_id: Optional[int] = None account_name = "" keyinstance_id: Optional[int] = None is_receiving = is_change = False txo_key = TxoKeyType(self._tx_hash, tx_index) for account in accounts: if is_tx_complete: keyinstance_id = get_keyinstance_id(account, txo_key) elif account is xtxo_account and xtxo_keyinstance_id != -1: keyinstance_id = xtxo_keyinstance_id if keyinstance_id is not None: account_id = account.get_id() is_receiving = compare_key_path(account, keyinstance_id, RECEIVING_SUBPATH) is_change = compare_key_path(account, keyinstance_id, CHANGE_SUBPATH) account_name = name_for_account(account) received_value += tx_output.value break amount_text = app_state.format_amount(tx_output.value, whitespaces=True) item = QTreeWidgetItem( [str(tx_index), account_name, text, amount_text]) item.setData(OutputColumns.INDEX, Roles.IS_MINE, is_change or is_receiving) item.setData(OutputColumns.INDEX, Roles.ACCOUNT_ID, account_id) item.setData(OutputColumns.INDEX, Roles.KEY_ID, keyinstance_id) if is_receiving: item.setBackground(OutputColumns.DESTINATION, self._receiving_brush) if is_change: item.setBackground(OutputColumns.DESTINATION, self._change_brush) item.setTextAlignment(OutputColumns.AMOUNT, Qt.AlignRight | Qt.AlignVCenter) item.setFont(OutputColumns.AMOUNT, self._monospace_font) o_table.addTopLevelItem(item) self._received_value_label.setText( _("Received output value") + ": " + app_state.format_amount(received_value))
def _update_io(self, i_table: MyTreeWidget, o_table: MyTreeWidget) -> None: def get_xtxoutput_account( output: XTxOutput) -> Optional[AbstractAccount]: if not output.x_pubkeys: return None for x_pubkey in output.x_pubkeys: result = self._main_window._wallet.resolve_xpubkey(x_pubkey) if result is not None: account, keyinstance_id = result if account.get_script_for_id( keyinstance_id) == output.script_pubkey: return account return None return None known_txos: Set[Tuple[bytes, int]] = set() if self._account is not None: known_txos = set(self._account._utxos) | set(self._account._stxos) def compare_key_path(account: AbstractAccount, txo_key: TxoKeyType, leading_path: Sequence[int]) -> bool: utxo = account._utxos.get(txo_key) if utxo is not None: key_path = account.get_derivation_path(utxo.keyinstance_id) if key_path is not None and key_path[:len(leading_path )] == leading_path: return True stxo_keyinstance_id = account._stxos.get(txo_key) if stxo_keyinstance_id is not None: key_path = account.get_derivation_path(stxo_keyinstance_id) if key_path is not None and key_path[:len(leading_path )] == leading_path: return True return False def name_for_account(account: AbstractAccount) -> str: name = account.display_name() return f"{account.get_id()}: {name}" for tx_index, txin in enumerate(self.tx.inputs): account_name = "" source_text = "" amount_text = "" is_receiving = is_change = False if txin.is_coinbase(): source_text = "<coinbase>" else: prev_hash_hex = hash_to_hex_str(txin.prev_hash) source_text = f"{prev_hash_hex}:{txin.prev_idx}" if self._account is not None: txo_key = TxoKeyType(txin.prev_hash, txin.prev_idx) is_receiving = compare_key_path(self._account, txo_key, RECEIVING_SUBPATH) is_change = compare_key_path(self._account, txo_key, CHANGE_SUBPATH) account_name = name_for_account(self._account) # TODO(rt12): When does a txin have a value? Loaded incomplete transactions only? if txin.value is not None: amount_text = app_state.format_amount(txin.value, whitespaces=True) item = QTreeWidgetItem( [str(tx_index), account_name, source_text, amount_text]) # item.setData(0, Qt.UserRole, row.paymentrequest_id) if is_receiving: item.setBackground(2, self._receiving_brush) if is_change: item.setBackground(2, self._change_brush) item.setTextAlignment(3, Qt.AlignRight | Qt.AlignVCenter) item.setFont(3, self._monospace_font) i_table.addTopLevelItem(item) for tx_index, tx_output in enumerate(self.tx.outputs): text, _kind = tx_output_to_display_text(tx_output) if isinstance(_kind, Unknown_Output): text = script_bytes_to_asm(tx_output.script_pubkey) account = get_xtxoutput_account(tx_output) accounts: List[AbstractAccount] = [] if account is not None: accounts.append(account) if self._account is not None and account is not self._account: accounts.append(self._account) account_name = "" is_receiving = is_change = False txo_key = TxoKeyType(self._tx_hash, tx_index) for account in accounts: if txo_key in account._stxos or txo_key in account._utxos: is_receiving = compare_key_path(account, txo_key, RECEIVING_SUBPATH) is_change = compare_key_path(account, txo_key, CHANGE_SUBPATH) account_name = name_for_account(account) break amount_text = app_state.format_amount(tx_output.value, whitespaces=True) item = QTreeWidgetItem( [str(tx_index), account_name, text, amount_text]) # item.setData(0, Qt.UserRole, row.paymentrequest_id) if is_receiving: item.setBackground(2, self._receiving_brush) if is_change: item.setBackground(2, self._change_brush) item.setTextAlignment(3, Qt.AlignRight | Qt.AlignVCenter) item.setFont(3, self._monospace_font) o_table.addTopLevelItem(item)