class TxBuilder(BaseDock): tool_name = 'Transaction Builder' description = 'Transaction Builder helps you create transactions.' is_large = True category = Category.Tx def __init__(self, handler): super(TxBuilder, self).__init__(handler) self.raw_tx.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.raw_tx.customContextMenuRequested.connect(self.context_menu) @augmenter def item_actions(self, *args): return [ItemAction(self.tool_name, 'Transaction', 'Edit', self.deserialize_item)] def init_data(self): self.tx = None def create_layout(self): vbox = QVBoxLayout() self.tabs = tabs = QTabWidget() tabs.addTab(self.create_version_locktime_tab(), '&Version/Locktime') tabs.addTab(self.create_inputs_tab(), '&Inputs') tabs.addTab(self.create_outputs_tab(), '&Outputs') tabs.addTab(self.create_review_tab(), '&Review') self.setFocusProxy(self.tabs) self.tx_field_widgets = [] tabs.insertTab(3, self.create_other_tab(), 'Ot&her') self.adjust_tx_fields() # Build the tx if the Review tab is selected. def maybe_build(i): if str(tabs.tabText(i)) == '&Review': self.build_transaction() tabs.currentChanged.connect(maybe_build) vbox.addWidget(tabs) return vbox def context_menu(self, position): menu = self.raw_tx.createStandardContextMenu(position) txt = str(self.raw_tx.toPlainText()) if txt: self.handler.add_plugin_actions(self, menu, txt) menu.exec_(self.raw_tx.viewport().mapToGlobal(position)) def create_version_locktime_tab(self): form = QFormLayout() self.version_edit = AmountEdit() self.version_edit.setText('1') self.version_edit.setWhatsThis('Use this field to specify the version of your transaction. In Bitcoin, transactions are currently version 1.') self.locktime_edit = AmountEdit() self.locktime_edit.setText('0') self.locktime_edit.setWhatsThis('Use this field to specify the locktime of your transaction. For most common transactions, locktime is zero.') version_desc = QLabel('A transaction\'s version determines how it is interpreted.\n\nBitcoin transactions are currently version 1.') locktime_desc = QLabel('A transaction\'s locktime defines the earliest time or block that it may be added to the blockchain.\n\nLocktime only applies if it\'s non-zero and at least one input has a Sequence that\'s not the maximum possible value.') for i in [version_desc, locktime_desc]: i.setWordWrap(True) for i in [self.version_edit, self.locktime_edit]: i.setFont(monospace_font) form.addRow(version_desc) form.addRow('Version:', self.version_edit) form.addRow(Separator()) form.addRow(locktime_desc) form.addRow('Locktime:', self.locktime_edit) w = QWidget() w.setLayout(form) return w def create_inputs_tab(self): form = QFormLayout() self.inputs_tree = InputsTree() self.inputs_tree.view.setWhatsThis('The inputs of your transaction are displayed here.') self.inputs_editor = InputsEditor(self.handler.gui, self.inputs_tree) self.inputs_editor.setEnabled(False) def update_enabled_widgets(): num_inputs = len(self.inputs_tree.get_inputs()) self.inputs_editor.setEnabled(num_inputs > 0) def add_input(): outpoint = CMutableOutPoint(n=0) new_input = CMutableTxIn(prevout=outpoint) self.inputs_tree.add_input(new_input) update_enabled_widgets() if len(self.inputs_tree.get_inputs()) > 0: self.inputs_tree.view.selectRow(self.inputs_tree.model.rowCount() - 1) update_enabled_widgets() add_input_button = QPushButton('New input') add_input_button.setToolTip('Add a new input') add_input_button.setWhatsThis('Clicking this button will add a new input to your transaction.') add_input_button.clicked.connect(add_input) form.addRow(self.inputs_tree) form.addRow(Separator()) form.addRow(self.inputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_input_button])) w = QWidget() w.setLayout(form) return w def create_outputs_tab(self): form = QFormLayout() self.outputs_tree = OutputsTree() self.outputs_tree.view.setWhatsThis('The outputs of your transaction are displayed here.') self.outputs_editor = OutputsEditor(self.handler.gui, self.outputs_tree) self.outputs_editor.setEnabled(False) def update_enabled_widgets(): num_outputs = len(self.outputs_tree.get_outputs()) self.outputs_editor.setEnabled(num_outputs > 0) def add_output(): new_output = CMutableTxOut(0) self.outputs_tree.add_output(new_output) update_enabled_widgets() if len(self.outputs_tree.get_outputs()) > 0: self.outputs_tree.view.selectRow(self.outputs_tree.model.rowCount() - 1) update_enabled_widgets() add_output_button = QPushButton('New output') add_output_button.setToolTip('Add a new output') add_output_button.setWhatsThis('Clicking this button will add a new output to your transaction.') add_output_button.clicked.connect(add_output) form.addRow(self.outputs_tree) form.addRow(Separator()) form.addRow(self.outputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_output_button])) w = QWidget() w.setLayout(form) return w def create_review_tab(self): form = QFormLayout() self.raw_tx = QTextEdit() self.raw_tx.setWhatsThis('The transaction you build is displayed here.') self.raw_tx.setReadOnly(True) self.tx_widget = TxWidget() form.addRow('Raw Tx:', self.raw_tx) form.addRow(self.tx_widget) w = QWidget() w.setLayout(form) return w def create_other_tab(self): self.tx_fields_layout = QFormLayout() w = QWidget() w.setLayout(self.tx_fields_layout) return w def deserialize_item(self, item): self.deserialize_raw(item.raw()) def deserialize_raw(self, rawtx): """Update editor widgets with rawtx's data.""" self.needsFocus.emit() try: tx = Transaction.deserialize(x(rawtx)) except Exception: return else: self.version_edit.set_amount(tx.nVersion) self.inputs_tree.model.set_tx(tx) self.outputs_tree.model.set_tx(tx) self.locktime_edit.set_amount(tx.nLockTime) for name, w in self.tx_field_widgets: if name in ['nVersion', 'vin', 'vout', 'nLockTime']: continue try: value = getattr(tx, name) except AttributeError: continue if isinstance(w, AmountEdit): w.set_amount(value) else: w.setText(str(value)) self.build_transaction() def build_transaction(self): self.tx_widget.clear() self.tx = tx = Transaction() tx.nVersion = self.version_edit.get_amount() tx.vin = self.inputs_tree.get_inputs() tx.vout = self.outputs_tree.get_outputs() tx.nLockTime = self.locktime_edit.get_amount() for name, w in self.tx_field_widgets: if not name in [field[0] for field in tx.fields]: continue value = str(w.text()) default = getattr(tx, name) if isinstance(default, int): value = w.get_amount() setattr(tx, name, value) self.raw_tx.setText(bitcoin.core.b2x(tx.serialize())) self.tx_widget.set_tx(tx) def on_option_changed(self, key): if key in ['chainparams']: self.needsUpdate.emit() def adjust_tx_fields(self): """Show or hide tx field widgets.""" tx_fields = chainparams.get_tx_fields() for field in tx_fields: name = field[0] if name in ['nVersion', 'vin', 'vout', 'nLockTime']: continue default_value = field[3] if name not in [j[0] for j in self.tx_field_widgets]: widget = QLineEdit() if isinstance(default_value, int): # Special case for timestamp fields. if name == 'Timestamp': widget = TimestampWidget() widget.timestamp_raw.setReadOnly(False) else: widget = AmountEdit() widget.setText(str(default_value)) label = QLabel(''.join([name, ':'])) self.tx_field_widgets.append((name, widget)) self.tx_fields_layout.addRow(label, widget) tx_field_names = [i[0] for i in tx_fields] for name, w in self.tx_field_widgets: l = self.tx_fields_layout.labelForField(w) if name in tx_field_names: w.show() l.show() else: w.hide() l.hide() if tx_field_names == ['nVersion', 'vin', 'vout', 'nLockTime']: self.tabs.setTabEnabled(3, False) else: self.tabs.setTabEnabled(3, True) def refresh_data(self): self.adjust_tx_fields() self.build_transaction()
class TxBuilder(BaseDock): tool_name = 'Transaction Builder' description = 'Transaction Builder helps you create transactions.' is_large = True category = Category.Tx def __init__(self, handler): super(TxBuilder, self).__init__(handler) self.raw_tx.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.raw_tx.customContextMenuRequested.connect(self.context_menu) @augmenter def item_actions(self, *args): return [ ItemAction(self.tool_name, 'Transaction', 'Edit', self.deserialize_item) ] def init_data(self): self.tx = None def create_layout(self): vbox = QVBoxLayout() self.tabs = tabs = QTabWidget() tabs.addTab(self.create_version_locktime_tab(), '&Version/Locktime') tabs.addTab(self.create_inputs_tab(), '&Inputs') tabs.addTab(self.create_outputs_tab(), '&Outputs') tabs.addTab(self.create_review_tab(), '&Review') tabs.addTab(self.create_sign_tab(), 'Sig&n') self.setFocusProxy(self.tabs) self.tx_field_widgets = [] tabs.insertTab(3, self.create_other_tab(), 'Ot&her') self.adjust_tx_fields() # Build the tx if the Review tab is selected. def maybe_build(i): if str(tabs.tabText(i)) == '&Review' or str( tabs.tabText(i)) == 'Sig&n': self.build_transaction() tabs.currentChanged.connect(maybe_build) vbox.addWidget(tabs) return vbox def context_menu(self, position): menu = self.raw_tx.createStandardContextMenu(position) txt = str(self.raw_tx.toPlainText()) if txt: self.handler.add_plugin_actions(self, menu, txt) menu.exec_(self.raw_tx.viewport().mapToGlobal(position)) def create_version_locktime_tab(self): form = QFormLayout() self.version_edit = AmountEdit() self.version_edit.setText('1') self.version_edit.setWhatsThis( 'Use this field to specify the version of your transaction. In Bitcoin, transactions are currently version 1.' ) self.locktime_edit = AmountEdit() self.locktime_edit.setText('0') self.locktime_edit.setWhatsThis( 'Use this field to specify the locktime of your transaction. For most common transactions, locktime is zero.' ) version_desc = QLabel( 'A transaction\'s version determines how it is interpreted.\n\nBitcoin transactions are currently version 1.' ) locktime_desc = QLabel( 'A transaction\'s locktime defines the earliest time or block that it may be added to the blockchain.\n\nLocktime only applies if it\'s non-zero and at least one input has a Sequence that\'s not the maximum possible value.' ) for i in [version_desc, locktime_desc]: i.setWordWrap(True) for i in [self.version_edit, self.locktime_edit]: i.setFont(monospace_font) form.addRow(version_desc) form.addRow('Version:', self.version_edit) form.addRow(Separator()) form.addRow(locktime_desc) form.addRow('Locktime:', self.locktime_edit) w = QWidget() w.setLayout(form) return w def create_inputs_tab(self): form = QFormLayout() self.inputs_tree = InputsTree() self.inputs_tree.view.setWhatsThis( 'The inputs of your transaction are displayed here.') self.inputs_editor = InputsEditor(self.handler.gui, self.inputs_tree) self.inputs_editor.setEnabled(False) def update_enabled_widgets(): num_inputs = len(self.inputs_tree.get_inputs()) self.inputs_editor.setEnabled(num_inputs > 0) def add_input(): outpoint = CMutableOutPoint(n=0) new_input = CMutableTxIn(prevout=outpoint) self.inputs_tree.add_input(new_input) update_enabled_widgets() if len(self.inputs_tree.get_inputs()) > 0: self.inputs_tree.view.selectRow( self.inputs_tree.model.rowCount() - 1) update_enabled_widgets() add_input_button = QPushButton('New input') add_input_button.setToolTip('Add a new input') add_input_button.setWhatsThis( 'Clicking this button will add a new input to your transaction.') add_input_button.clicked.connect(add_input) form.addRow(self.inputs_tree) form.addRow(Separator()) form.addRow(self.inputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_input_button])) w = QWidget() w.setLayout(form) return w def create_outputs_tab(self): form = QFormLayout() self.outputs_tree = OutputsTree() self.outputs_tree.view.setWhatsThis( 'The outputs of your transaction are displayed here.') self.outputs_editor = OutputsEditor(self.handler.gui, self.outputs_tree) self.outputs_editor.setEnabled(False) def update_enabled_widgets(): num_outputs = len(self.outputs_tree.get_outputs()) self.outputs_editor.setEnabled(num_outputs > 0) def add_output(): new_output = CMutableTxOut(0) self.outputs_tree.add_output(new_output) update_enabled_widgets() if len(self.outputs_tree.get_outputs()) > 0: self.outputs_tree.view.selectRow( self.outputs_tree.model.rowCount() - 1) update_enabled_widgets() add_output_button = QPushButton('New output') add_output_button.setToolTip('Add a new output') add_output_button.setWhatsThis( 'Clicking this button will add a new output to your transaction.') add_output_button.clicked.connect(add_output) form.addRow(self.outputs_tree) form.addRow(Separator()) form.addRow(self.outputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_output_button])) w = QWidget() w.setLayout(form) return w def create_review_tab(self): form = QFormLayout() self.raw_tx = QTextEdit() self.raw_tx.setWhatsThis( 'The transaction you build is displayed here.') self.raw_tx.setReadOnly(True) self.tx_widget = TxWidget() form.addRow('Raw Tx:', self.raw_tx) form.addRow(self.tx_widget) w = QWidget() w.setLayout(form) return w def create_other_tab(self): self.tx_fields_layout = QFormLayout() w = QWidget() w.setLayout(self.tx_fields_layout) return w def create_sign_tab(self): self.sighash_widget = SigHashWidget(self) return self.sighash_widget def deserialize_item(self, item): self.deserialize_raw(item.raw()) def deserialize_raw(self, rawtx): """Update editor widgets with rawtx's data.""" self.needsFocus.emit() try: tx = Transaction.deserialize(x(rawtx)) except Exception: return else: self.version_edit.set_amount(tx.nVersion) self.inputs_tree.model.set_tx(tx) self.outputs_tree.model.set_tx(tx) self.locktime_edit.set_amount(tx.nLockTime) for name, w in self.tx_field_widgets: if name in ['nVersion', 'vin', 'vout', 'nLockTime']: continue try: value = getattr(tx, name) except AttributeError: continue if isinstance(w, AmountEdit): w.set_amount(value) else: w.setText(str(value)) self.build_transaction() def build_transaction(self): self.tx_widget.clear() self.sighash_widget.clear() self.tx = tx = Transaction() tx.nVersion = self.version_edit.get_amount() tx.vin = self.inputs_tree.get_inputs() tx.vout = self.outputs_tree.get_outputs() tx.nLockTime = self.locktime_edit.get_amount() for name, w in self.tx_field_widgets: if not name in [field[0] for field in tx.fields]: continue value = str(w.text()) default = getattr(tx, name) if isinstance(default, int): value = w.get_amount() setattr(tx, name, value) self.raw_tx.setText(bitcoin.core.b2x(tx.serialize())) self.tx_widget.set_tx(tx) self.sighash_widget.set_tx(tx) def on_option_changed(self, key): if key == 'chainparams': self.needsUpdate.emit() def adjust_tx_fields(self): """Show or hide tx field widgets.""" tx_fields = chainparams.get_tx_fields() for field in tx_fields: name = field[0] if name in ['nVersion', 'vin', 'vout', 'nLockTime']: continue default_value = field[3] if name not in [j[0] for j in self.tx_field_widgets]: widget = QLineEdit() if isinstance(default_value, int): # Special case for timestamp fields. if name == 'Timestamp': widget = TimestampWidget() widget.timestamp_raw.setReadOnly(False) else: widget = AmountEdit() widget.setText(str(default_value)) label = QLabel(''.join([name, ':'])) self.tx_field_widgets.append((name, widget)) self.tx_fields_layout.addRow(label, widget) tx_field_names = [i[0] for i in tx_fields] for name, w in self.tx_field_widgets: l = self.tx_fields_layout.labelForField(w) if name in tx_field_names: w.show() l.show() else: w.hide() l.hide() if tx_field_names == ['nVersion', 'vin', 'vout', 'nLockTime']: self.tabs.setTabEnabled(3, False) else: self.tabs.setTabEnabled(3, True) def refresh_data(self): self.adjust_tx_fields() self.build_transaction()
class TxAnalyzer(BaseDock): tool_name = 'Transaction Analyzer' description = 'Deserializes transactions and verifies their inputs.' is_large = True category = Category.Tx def __init__(self, handler): super(TxAnalyzer, self).__init__(handler) self.raw_tx_edit.textChanged.emit() def init_data(self): self.tx = None def init_actions(self): deserialize = ('Deserialize', self.deserialize_raw) verify = ('Verify inputs', self.do_verify_inputs) self.advertised_actions[RAW_TX] = [deserialize, verify] def create_layout(self): form = QFormLayout() self.raw_tx_edit = QPlainTextEdit() self.raw_tx_edit.setTabChangesFocus(True) self.raw_tx_edit.setFont(monospace_font) self.raw_tx_edit.setContextMenuPolicy(Qt.CustomContextMenu) self.raw_tx_edit.customContextMenuRequested.connect(self.context_menu) self.raw_tx_edit.textChanged.connect(self.check_raw_tx) self.setFocusProxy(self.raw_tx_edit) self.raw_tx_invalid = QLabel('Cannot parse transaction.') self.raw_tx_invalid.setProperty('hasError', True) self.tabs = tabs = QTabWidget() tabs.addTab(self.create_deserialize_tab(), 'Deserialize') tabs.addTab(self.create_verify_tab(), 'Verify') tabs.setTabToolTip(0, 'View the transaction in human-readable form') tabs.setTabToolTip(1, 'Download previous transactions and verify inputs') form.addRow('Raw Tx:', self.raw_tx_edit) form.addRow(self.raw_tx_invalid) form.addRow(tabs) return form def create_deserialize_tab(self): form = QFormLayout() self.deserialize_button = QPushButton('Deserialize') self.deserialize_button.clicked.connect(self.deserialize) btn_hbox = floated_buttons([self.deserialize_button]) self.tx_widget = TxWidget() self.tx_widget.inputs_tree.view.customContextMenuRequested.disconnect( self.tx_widget.inputs_tree.customContextMenu) self.tx_widget.inputs_tree.view.customContextMenuRequested.connect( self.inputs_context_menu) form.addRow(self.tx_widget) w = QWidget() w.setLayout(form) return w def create_verify_tab(self): form = QFormLayout() form.setRowWrapPolicy(QFormLayout.WrapLongRows) self.inputs_box = QSpinBox() self.verify_button = QPushButton('Verify') self.verify_button.clicked.connect(self.verify_input) self.verify_all_button = QPushButton('Verify All') self.verify_all_button.clicked.connect(self.verify_inputs) self.result_edit = QLineEdit() self.result_edit.setReadOnly(True) self.inputs_table = InputStatusTable() form.addRow('Verify Input:', floated_buttons([self.inputs_box, self.verify_button])) form.addRow(floated_buttons([self.verify_all_button])) form.addRow('Result:', self.result_edit) form.addRow(self.inputs_table) w = QWidget() w.setLayout(form) return w def context_menu(self, position): menu = QMenu() if self.tx: txt = str(self.raw_tx_edit.toPlainText()) self.handler.add_plugin_actions(self, menu, RAW_TX, txt) menu.exec_(self.mapToGlobal(position)) def inputs_context_menu(self, position): inputs = self.tx_widget.inputs_tree def inputs_context_verify(): self.tabs.setCurrentIndex(1) row = inputs.view.selectedIndexes()[0].row() self.do_verify_input(self.tx, row) menu = QMenu() if self.tx: menu = inputs.context_menu() menu.addAction('Verify script', inputs_context_verify) menu.exec_(inputs.view.viewport().mapToGlobal(position)) def clear(self): self.result_edit.clear() self.tx_widget.clear() self.inputs_table.clear() def check_raw_tx(self): txt = str(self.raw_tx_edit.toPlainText()) # Variable substitution if txt.startswith('$'): var_value = self.handler.get_plugin('Variables').dock.get_key( txt[1:]) if var_value: self.raw_tx_edit.setPlainText(var_value) return tx = None valid = True try: tx = Transaction.deserialize(txt.decode('hex')) except Exception: valid = False self.tx = tx self.deserialize_button.setEnabled(valid) self.inputs_box.setEnabled(valid) self.verify_button.setEnabled(valid) self.verify_all_button.setEnabled(valid) self.tx_widget.setEnabled(valid) self.clear() if valid: self.raw_tx_invalid.hide() self.inputs_box.setRange(0, len(tx.vin) - 1) self.deserialize() elif txt: self.raw_tx_invalid.show() else: self.raw_tx_invalid.hide() def deserialize_raw(self, txt): """Deserialize a raw transaction.""" self.needsFocus.emit() self.raw_tx_edit.setPlainText(txt) self.deserialize() def deserialize(self): self.clear() self.tx_widget.set_tx(self.tx) self.inputs_table.set_tx(self.tx) self.status_message('Deserialized transaction {}'.format( bitcoin.core.b2lx(self.tx.GetHash()))) def do_verify_input(self, tx, in_idx): raw_prev_tx = None tx_in = tx.vin[in_idx] txid = b2lx(tx_in.prevout.hash) prev_out_n = tx_in.prevout.n try: raw_prev_tx = self.handler.download_blockchain_data( 'raw_transaction', txid) except Exception as e: self.status_message(str(e), True) return False try: prev_tx = Transaction.deserialize(raw_prev_tx.decode('hex')) result = bitcoin.core.scripteval.VerifyScript( tx_in.scriptSig, prev_tx.vout[prev_out_n].scriptPubKey, tx, in_idx) self.result_edit.setText( 'Successfully verified input {}'.format(in_idx)) self.inputs_table.set_verified(in_idx, True) except Exception as e: self.result_edit.setText(str(e)) self.inputs_table.set_verified(in_idx, False) self.status_message(str(e), True) return False return True def do_verify_inputs(self, txt): self.needsFocus.emit() self.raw_tx_edit.setPlainText(txt) tx = Transaction.deserialize(txt.decode('hex')) failed_inputs = [] self.result_edit.setText('Verifying...') for i in range(len(tx.vin)): if not self.do_verify_input(tx, i): failed_inputs.append(i) result = 'Successfully verified all inputs.' ret_val = True if failed_inputs: result = 'Failed to verify inputs: {}'.format(failed_inputs) ret_val = False if len(tx.vin) == 0: result = 'Transaction has no inputs.' self.result_edit.setText(result) return ret_val def verify_input(self): tx = None try: txt = str(self.raw_tx_edit.toPlainText()) tx = Transaction.deserialize(txt.decode('hex')) except Exception: self.status_message('Could not deserialize transaction.', True) return in_idx = self.inputs_box.value() if in_idx >= len(tx.vin): self.status_message('Input {} does not exist.'.format(in_idx), True) return self.do_verify_input(tx, in_idx) def verify_inputs(self): txt = str(self.raw_tx_edit.toPlainText()) self.do_verify_inputs(txt) def refresh_data(self): if self.tx: self.deserialize() def on_option_changed(self, key): if key == 'chainparams': self.raw_tx_edit.textChanged.emit()
class TxBuilder(BaseDock): tool_name = "Transaction Builder" description = "Transaction Builder helps you create transactions." is_large = True category = Category.Tx def __init__(self, handler): super(TxBuilder, self).__init__(handler) self.raw_tx.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.raw_tx.customContextMenuRequested.connect(self.context_menu) def init_data(self): self.tx = None def init_actions(self): self.advertised_actions[RAW_TX] = [("Edit", self.deserialize_raw)] def create_layout(self): vbox = QVBoxLayout() self.tabs = tabs = QTabWidget() tabs.addTab(self.create_version_locktime_tab(), "&Version/Locktime") tabs.addTab(self.create_inputs_tab(), "&Inputs") tabs.addTab(self.create_outputs_tab(), "&Outputs") tabs.addTab(self.create_review_tab(), "&Review") self.setFocusProxy(self.tabs) self.tx_field_widgets = [] tabs.insertTab(3, self.create_other_tab(), "Ot&her") self.adjust_tx_fields() # Build the tx if the Review tab is selected. def maybe_build(i): if str(tabs.tabText(i)) == "&Review": self.build_transaction() tabs.currentChanged.connect(maybe_build) vbox.addWidget(tabs) return vbox def context_menu(self, position): menu = self.raw_tx.createStandardContextMenu(position) txt = str(self.raw_tx.toPlainText()) if txt: self.handler.add_plugin_actions(self, menu, RAW_TX, txt) menu.exec_(self.raw_tx.viewport().mapToGlobal(position)) def create_version_locktime_tab(self): form = QFormLayout() self.version_edit = AmountEdit() self.version_edit.setText("1") self.locktime_edit = AmountEdit() self.locktime_edit.setText("0") version_desc = QLabel( "A transaction's version determines how it is interpreted.\n\nBitcoin transactions are currently version 1." ) locktime_desc = QLabel( "A transaction's locktime defines the earliest time or block that it may be added to the blockchain.\n\nLocktime only applies if it's non-zero and at least one input has a Sequence that's not the maximum possible value." ) for i in [version_desc, locktime_desc]: i.setWordWrap(True) for i in [self.version_edit, self.locktime_edit]: i.setFont(monospace_font) form.addRow(version_desc) form.addRow("Version:", self.version_edit) form.addRow(Separator()) form.addRow(locktime_desc) form.addRow("Locktime:", self.locktime_edit) w = QWidget() w.setLayout(form) return w def create_inputs_tab(self): form = QFormLayout() self.inputs_tree = InputsTree() self.inputs_editor = InputsEditor(self.handler.gui, self.inputs_tree) self.inputs_editor.setEnabled(False) def update_enabled_widgets(): num_inputs = len(self.inputs_tree.get_inputs()) self.inputs_editor.setEnabled(num_inputs > 0) def add_input(): outpoint = CMutableOutPoint(n=0) new_input = CMutableTxIn(prevout=outpoint) self.inputs_tree.add_input(new_input) update_enabled_widgets() if len(self.inputs_tree.get_inputs()) > 0: self.inputs_tree.view.selectRow(self.inputs_tree.model.rowCount() - 1) update_enabled_widgets() add_input_button = QPushButton("New input") add_input_button.setToolTip("Add a new input") add_input_button.clicked.connect(add_input) form.addRow(self.inputs_tree) form.addRow(Separator()) form.addRow(self.inputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_input_button])) w = QWidget() w.setLayout(form) return w def create_outputs_tab(self): form = QFormLayout() self.outputs_tree = OutputsTree() self.outputs_editor = OutputsEditor(self.handler.gui, self.outputs_tree) self.outputs_editor.setEnabled(False) def update_enabled_widgets(): num_outputs = len(self.outputs_tree.get_outputs()) self.outputs_editor.setEnabled(num_outputs > 0) def add_output(): new_output = CMutableTxOut(0) self.outputs_tree.add_output(new_output) update_enabled_widgets() if len(self.outputs_tree.get_outputs()) > 0: self.outputs_tree.view.selectRow(self.outputs_tree.model.rowCount() - 1) update_enabled_widgets() add_output_button = QPushButton("New output") add_output_button.setToolTip("Add a new output") add_output_button.clicked.connect(add_output) form.addRow(self.outputs_tree) form.addRow(Separator()) form.addRow(self.outputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_output_button])) w = QWidget() w.setLayout(form) return w def create_review_tab(self): form = QFormLayout() self.raw_tx = QTextEdit() self.raw_tx.setReadOnly(True) self.tx_widget = TxWidget() form.addRow("Raw Tx:", self.raw_tx) form.addRow(self.tx_widget) w = QWidget() w.setLayout(form) return w def create_other_tab(self): self.tx_fields_layout = QFormLayout() w = QWidget() w.setLayout(self.tx_fields_layout) return w def deserialize_raw(self, rawtx): """Update editor widgets with rawtx's data.""" self.needsFocus.emit() try: tx = Transaction.deserialize(x(rawtx)) except Exception: return else: self.version_edit.set_amount(tx.nVersion) self.inputs_tree.model.set_tx(tx) self.outputs_tree.model.set_tx(tx) self.locktime_edit.set_amount(tx.nLockTime) for name, w in self.tx_field_widgets: if name in ["nVersion", "vin", "vout", "nLockTime"]: continue value = getattr(tx, name) if isinstance(w, AmountEdit): w.set_amount(value) else: w.setText(str(value)) self.build_transaction() def build_transaction(self): self.tx_widget.clear() self.tx = tx = Transaction() tx.nVersion = self.version_edit.get_amount() tx.vin = self.inputs_tree.get_inputs() tx.vout = self.outputs_tree.get_outputs() tx.nLockTime = self.locktime_edit.get_amount() for name, w in self.tx_field_widgets: if not name in [field[0] for field in tx.fields]: continue value = str(w.text()) default = getattr(tx, name) if isinstance(default, int): value = w.get_amount() setattr(tx, name, value) self.raw_tx.setText(bitcoin.core.b2x(tx.serialize())) self.tx_widget.set_tx(tx) def on_option_changed(self, key): if key in ["chainparams"]: self.needsUpdate.emit() def adjust_tx_fields(self): """Show or hide tx field widgets.""" tx_fields = chainparams.get_tx_fields() for field in tx_fields: name = field[0] if name in ["nVersion", "vin", "vout", "nLockTime"]: continue default_value = field[3] if name not in [j[0] for j in self.tx_field_widgets]: widget = QLineEdit() if isinstance(default_value, int): # Special case for timestamp fields. if name == "Timestamp": widget = TimestampWidget() widget.timestamp_raw.setReadOnly(False) else: widget = AmountEdit() widget.setText(str(default_value)) label = QLabel("".join([name, ":"])) self.tx_field_widgets.append((name, widget)) self.tx_fields_layout.addRow(label, widget) tx_field_names = [i[0] for i in tx_fields] for name, w in self.tx_field_widgets: l = self.tx_fields_layout.labelForField(w) if name in tx_field_names: w.show() l.show() else: w.hide() l.hide() if tx_field_names == ["nVersion", "vin", "vout", "nLockTime"]: self.tabs.setTabEnabled(3, False) else: self.tabs.setTabEnabled(3, True) def refresh_data(self): self.adjust_tx_fields() self.build_transaction()
class TxAnalyzer(BaseDock): tool_name = 'Transaction Analyzer' description = 'Deserializes transactions and verifies their inputs.' is_large = True category = Category.Tx def __init__(self, handler): super(TxAnalyzer, self).__init__(handler) self.raw_tx_edit.textChanged.emit() @augmenter def item_actions(self, arg): actions = [ ItemAction(self.tool_name, 'Transaction', 'Deserialize', self.deserialize_item), ItemAction(self.tool_name, 'Transaction', 'Verify inputs', self.verify_item_inputs) ] return actions def init_data(self): self.tx = None def create_layout(self): form = QFormLayout() self.raw_tx_edit = QPlainTextEdit() self.raw_tx_edit.setWhatsThis('Enter a serialized transaction here. If you have a raw transaction stored in the Variables tool, you can enter the variable name preceded by a "$", and the variable value will be substituted automatically.') self.raw_tx_edit.setTabChangesFocus(True) self.raw_tx_edit.setFont(monospace_font) self.raw_tx_edit.setContextMenuPolicy(Qt.CustomContextMenu) self.raw_tx_edit.customContextMenuRequested.connect(self.context_menu) self.raw_tx_edit.textChanged.connect(self.check_raw_tx) self.setFocusProxy(self.raw_tx_edit) self.raw_tx_invalid = QLabel('Cannot parse transaction.') self.raw_tx_invalid.setProperty('hasError', True) self.tabs = tabs = QTabWidget() tabs.addTab(self.create_deserialize_tab(), 'Deserialize') tabs.addTab(self.create_verify_tab(), 'Verify') tabs.setTabToolTip(0, 'View the transaction in human-readable form') tabs.setTabToolTip(1, 'Download previous transactions and verify inputs') form.addRow('Raw Tx:', self.raw_tx_edit) form.addRow(self.raw_tx_invalid) form.addRow(tabs) return form def create_deserialize_tab(self): form = QFormLayout() self.deserialize_button = QPushButton('Deserialize') self.deserialize_button.clicked.connect(self.deserialize) btn_hbox = floated_buttons([self.deserialize_button]) self.tx_widget = TxWidget() self.tx_widget.inputs_tree.view.customContextMenuRequested.disconnect(self.tx_widget.inputs_tree.customContextMenu) self.tx_widget.inputs_tree.view.customContextMenuRequested.connect(self.inputs_context_menu) self.tx_widget.outputs_tree.view.customContextMenuRequested.disconnect(self.tx_widget.outputs_tree.customContextMenu) self.tx_widget.outputs_tree.view.customContextMenuRequested.connect(self.outputs_context_menu) form.addRow(self.tx_widget) w = QWidget() w.setLayout(form) return w def create_verify_tab(self): form = QFormLayout() form.setRowWrapPolicy(QFormLayout.WrapLongRows) self.inputs_box = QSpinBox() self.inputs_box.setToolTip('Input to verify') self.verify_button = QPushButton('Verify') self.verify_button.clicked.connect(self.verify_input) self.verify_button.setToolTip('Verify an input') self.verify_button.setWhatsThis('This button will attempt to verify an input.\n\nIf no plugin is available to retrieve blockchain data, such as the "Blockchain" or "Wallet RPC" plugins, this will not function. The plugin used to retrieve blockchain data can be changed in the Settings dialog.') self.verify_all_button = QPushButton('Verify All') self.verify_all_button.clicked.connect(self.verify_inputs) self.verify_all_button.setToolTip('Verify all inputs') self.verify_all_button.setWhatsThis('This button will attempt to verify all inputs.\n\nIf no plugin is available to retrieve blockchain data, such as the "Blockchain" or "Wallet RPC" plugins, this will not function. The plugin used to retrieve blockchain data can be changed in the Settings dialog.') self.result_edit = QLineEdit() self.result_edit.setToolTip('Verification result') self.result_edit.setWhatsThis('The result of verifying an input is shown here.') self.result_edit.setReadOnly(True) self.inputs_table = InputStatusTable() self.inputs_table.setToolTip('Verification results') self.inputs_table.setWhatsThis('This table displays which inputs you have verified for the transaction being analyzed.') form.addRow('Verify Input:', floated_buttons([self.inputs_box, self.verify_button])) form.addRow(floated_buttons([self.verify_all_button])) form.addRow('Result:', self.result_edit) form.addRow(self.inputs_table) w = QWidget() w.setLayout(form) return w def context_menu(self, position): menu = QMenu() if self.tx: txt = str(self.raw_tx_edit.toPlainText()) self.handler.add_plugin_actions(self, menu, txt) menu.exec_(self.raw_tx_edit.viewport().mapToGlobal(position)) def inputs_context_menu(self, position): inputs = self.tx_widget.inputs_tree if not len(inputs.view.selectedIndexes()) or not self.tx: return def inputs_context_verify(): self.tabs.setCurrentIndex(1) row = inputs.view.selectedIndexes()[0].row() self.do_verify_input(self.tx, row) menu = inputs.context_menu() if not self.tx.is_coinbase(): menu.addAction('Verify script', inputs_context_verify) self.handler.add_plugin_actions(self, menu, str(inputs.model.data(inputs.view.selectedIndexes()[2]).toString())) menu.exec_(inputs.view.viewport().mapToGlobal(position)) def outputs_context_menu(self, position): outputs = self.tx_widget.outputs_tree if not len(outputs.view.selectedIndexes()) or not self.tx: return menu = outputs.context_menu() self.handler.add_plugin_actions(self, menu, str(outputs.model.data(outputs.view.selectedIndexes()[1]).toString())) menu.exec_(outputs.view.viewport().mapToGlobal(position)) def clear(self): self.result_edit.clear() self.tx_widget.clear() self.inputs_table.clear() def check_raw_tx(self): txt = str(self.raw_tx_edit.toPlainText()) # Variable substitution if txt.startswith('$'): var_value = self.handler.get_plugin('Variables').ui.get_key(txt[1:]) if var_value: self.raw_tx_edit.setPlainText(var_value) return tx = None valid = True try: tx = Transaction.deserialize(txt.decode('hex')) except Exception: valid = False self.tx = tx self.deserialize_button.setEnabled(valid) self.inputs_box.setEnabled(valid) self.verify_button.setEnabled(valid) self.verify_all_button.setEnabled(valid) self.tx_widget.setEnabled(valid) self.clear() if valid: self.raw_tx_invalid.hide() self.inputs_box.setRange(0, len(tx.vin) - 1) self.deserialize() elif txt: self.raw_tx_invalid.show() else: self.raw_tx_invalid.hide() def deserialize_raw(self, txt): """Deserialize a raw transaction.""" self.needsFocus.emit() self.raw_tx_edit.setPlainText(txt) self.deserialize() def deserialize_item(self, item): """Deserialize a Transaction item.""" self.needsFocus.emit() self.raw_tx_edit.setPlainText(item.raw()) self.deserialize() def deserialize(self): self.clear() self.tx_widget.set_tx(self.tx) self.inputs_table.set_tx(self.tx) self.status_message('Deserialized transaction {}'.format(bitcoin.core.b2lx(self.tx.GetHash()))) def do_verify_input(self, tx, in_idx): raw_prev_tx = None tx_in = tx.vin[in_idx] txid = b2lx(tx_in.prevout.hash) prev_out_n = tx_in.prevout.n try: raw_prev_tx = self.handler.download_blockchain_data('raw_transaction', txid) except Exception as e: self.status_message(str(e), True) return False try: prev_tx = Transaction.deserialize(raw_prev_tx.decode('hex')) result = bitcoin.core.scripteval.VerifyScript(tx_in.scriptSig, prev_tx.vout[prev_out_n].scriptPubKey, tx, in_idx) self.result_edit.setText('Successfully verified input {}'.format(in_idx)) self.inputs_table.set_verified(in_idx, True) except Exception as e: self.result_edit.setText(str(e)) self.inputs_table.set_verified(in_idx, False) self.status_message(str(e), True) return False return True def do_verify_inputs(self, txt): self.needsFocus.emit() self.raw_tx_edit.setPlainText(txt) tx = Transaction.deserialize(txt.decode('hex')) failed_inputs = [] self.result_edit.setText('Verifying...') for i in range(len(tx.vin)): if not self.do_verify_input(tx, i): failed_inputs.append(i) result = 'Successfully verified all inputs.' ret_val = True if failed_inputs: result = 'Failed to verify inputs: {}'.format(failed_inputs) ret_val = False if len(tx.vin) == 0: result = 'Transaction has no inputs.' self.result_edit.setText(result) return ret_val def verify_input(self): tx = None try: txt = str(self.raw_tx_edit.toPlainText()) tx = Transaction.deserialize(txt.decode('hex')) except Exception: self.status_message('Could not deserialize transaction.', True) return in_idx = self.inputs_box.value() if in_idx >= len(tx.vin): self.status_message('Input {} does not exist.'.format(in_idx), True) return self.do_verify_input(tx, in_idx) def verify_inputs(self): txt = str(self.raw_tx_edit.toPlainText()) self.do_verify_inputs(txt) def verify_item_inputs(self, item): self.do_verify_inputs(item.raw()) def refresh_data(self): if self.tx: self.deserialize() def on_option_changed(self, key): if key == 'chainparams': self.raw_tx_edit.textChanged.emit()
class TxAnalyzer(BaseDock): tool_name = 'Transaction Analyzer' description = 'Deserializes transactions and verifies their inputs.' is_large = True category = Category.Tx TAB_DESERIALIZE = 0 TAB_VERIFY = 1 def __init__(self, handler): super(TxAnalyzer, self).__init__(handler) self.raw_tx_edit.textChanged.emit() @augmenter def item_actions(self, arg): actions = [ ItemAction(self.tool_name, 'Transaction', 'Deserialize', self.deserialize_item), ItemAction(self.tool_name, 'Transaction', 'Verify inputs', self.verify_item_inputs) ] return actions def init_data(self): self.tx = None def create_layout(self): self.raw_tx_edit = QPlainTextEdit() self.raw_tx_edit.setWhatsThis('Enter a serialized transaction here. If you have a raw transaction stored in the Variables tool, you can enter the variable name preceded by a "$", and the variable value will be substituted automatically.') self.raw_tx_edit.setTabChangesFocus(True) self.raw_tx_edit.setFont(monospace_font) self.raw_tx_edit.setContextMenuPolicy(Qt.CustomContextMenu) self.raw_tx_edit.customContextMenuRequested.connect(self.context_menu) self.raw_tx_edit.textChanged.connect(self.check_raw_tx) self.handler.substitute_variables(self.raw_tx_edit) self.setFocusProxy(self.raw_tx_edit) self.raw_tx_invalid = QLabel('Cannot parse transaction.') self.raw_tx_invalid.setProperty('hasError', True) self.tabs = tabs = QTabWidget() tabs.addTab(self.create_deserialize_tab(), 'Deserialize') tabs.addTab(self.create_verify_tab(), 'Verify') tabs.setTabToolTip(self.TAB_DESERIALIZE, 'View the transaction in human-readable form') tabs.setTabToolTip(self.TAB_VERIFY, 'Download previous transactions and verify inputs') form = QFormLayout() form.setContentsMargins(0, 0, 0, 2) form.addRow('Raw Tx:', self.raw_tx_edit) vbox = QVBoxLayout() vbox.addLayout(form) vbox.addWidget(self.raw_tx_invalid) vbox.addWidget(tabs) return vbox def create_deserialize_tab(self): self.tx_widget = TxWidget() self.tx_widget.inputs_tree.view.customContextMenuRequested.disconnect(self.tx_widget.inputs_tree.customContextMenu) self.tx_widget.inputs_tree.view.customContextMenuRequested.connect(self.inputs_context_menu) self.tx_widget.outputs_tree.view.customContextMenuRequested.disconnect(self.tx_widget.outputs_tree.customContextMenu) self.tx_widget.outputs_tree.view.customContextMenuRequested.connect(self.outputs_context_menu) vbox = QVBoxLayout() vbox.addWidget(self.tx_widget) w = QWidget() w.setLayout(vbox) return w def create_verify_tab(self): form = QFormLayout() form.setRowWrapPolicy(QFormLayout.WrapLongRows) self.inputs_box = QSpinBox() self.inputs_box.setToolTip('Input to verify') self.verify_button = QPushButton('Verify') self.verify_button.clicked.connect(self.verify_input) self.verify_button.setToolTip('Verify an input') self.verify_button.setWhatsThis('This button will attempt to verify an input.\n\nIf no plugin is available to retrieve blockchain data, such as the "Blockchain" or "Wallet RPC" plugins, this will not function. The plugin used to retrieve blockchain data can be changed in the Settings dialog.') self.verify_all_button = QPushButton('Verify All') self.verify_all_button.clicked.connect(self.verify_inputs) self.verify_all_button.setToolTip('Verify all inputs') self.verify_all_button.setWhatsThis('This button will attempt to verify all inputs.\n\nIf no plugin is available to retrieve blockchain data, such as the "Blockchain" or "Wallet RPC" plugins, this will not function. The plugin used to retrieve blockchain data can be changed in the Settings dialog.') self.result_edit = QLineEdit() self.result_edit.setToolTip('Verification result') self.result_edit.setWhatsThis('The result of verifying an input is shown here.') self.result_edit.setReadOnly(True) self.inputs_table_model = InputsStatusModel() self.inputs_table = QTableView() self.inputs_table.setModel(self.inputs_table_model) self.inputs_table.horizontalHeader().setResizeMode(0, QHeaderView.ResizeToContents) self.inputs_table.horizontalHeader().setResizeMode(2, QHeaderView.Stretch) self.inputs_table.verticalHeader().setVisible(False) for i in [self.inputs_table.horizontalHeader(), self.inputs_table.verticalHeader()]: i.setHighlightSections(False) self.inputs_table.setToolTip('Verification results') self.inputs_table.setWhatsThis('This table displays which inputs you have verified for the transaction being analyzed.') form.addRow('Verify Input:', floated_buttons([self.inputs_box, self.verify_button])) form.addRow(floated_buttons([self.verify_all_button])) form.addRow('Result:', self.result_edit) form.addRow(self.inputs_table) w = QWidget() w.setLayout(form) return w def context_menu(self, position): menu = QMenu() if self.tx: txt = str(self.raw_tx_edit.toPlainText()) self.handler.add_plugin_actions(self, menu, txt) menu.exec_(self.raw_tx_edit.viewport().mapToGlobal(position)) def inputs_context_menu(self, position): inputs = self.tx_widget.inputs_tree if not len(inputs.view.selectedIndexes()) or not self.tx: return def inputs_context_verify(): self.tabs.setCurrentIndex(self.TAB_VERIFY) row = inputs.view.selectedIndexes()[0].row() self.do_verify_input(self.tx, row) menu = inputs.context_menu() if not self.tx.is_coinbase(): menu.addAction('Verify script', inputs_context_verify) self.handler.add_plugin_actions(self, menu, str(inputs.model.data(inputs.view.selectedIndexes()[2]).toString())) menu.exec_(inputs.view.viewport().mapToGlobal(position)) def outputs_context_menu(self, position): outputs = self.tx_widget.outputs_tree if not len(outputs.view.selectedIndexes()) or not self.tx: return menu = outputs.context_menu() self.handler.add_plugin_actions(self, menu, str(outputs.model.data(outputs.view.selectedIndexes()[1]).toString())) menu.exec_(outputs.view.viewport().mapToGlobal(position)) def clear(self): self.result_edit.clear() self.tx_widget.clear() self.inputs_table_model.clear() def check_raw_tx(self): txt = str(self.raw_tx_edit.toPlainText()) # Variable substitution if txt.startswith('$'): return tx = None valid = True try: tx = Transaction.deserialize(txt.decode('hex')) except Exception: valid = False self.tx = tx self.inputs_box.setEnabled(valid) self.verify_button.setEnabled(valid) self.verify_all_button.setEnabled(valid) self.tx_widget.setEnabled(valid) self.clear() if valid: self.raw_tx_invalid.hide() self.inputs_box.setRange(0, len(tx.vin) - 1) self.deserialize() elif txt: self.raw_tx_invalid.show() else: self.raw_tx_invalid.hide() def deserialize_raw(self, txt): """Deserialize a raw transaction.""" self.needsFocus.emit() self.tabs.setCurrentIndex(self.TAB_DESERIALIZE) self.raw_tx_edit.setPlainText(txt) def deserialize_item(self, item): """Deserialize a Transaction item.""" self.deserialize_raw(item.raw()) def deserialize(self): self.tx_widget.set_tx(self.tx) self.inputs_table_model.set_tx(self.tx) self.info('Deserialized transaction %s' % bitcoin.core.b2lx(self.tx.GetHash())) def do_verify_input(self, tx, in_idx): if tx.is_coinbase(): self.result_edit.setText('Error: Cannot verify coinbase transactions.') self.error('Attempted to verify coinbase transaction.') return False raw_prev_tx = None tx_in = tx.vin[in_idx] txid = b2lx(tx_in.prevout.hash) prev_out_n = tx_in.prevout.n try: raw_prev_tx = self.handler.download_blockchain_data('raw_transaction', txid) except Exception as e: self.error(str(e)) return False try: prev_tx = Transaction.deserialize(raw_prev_tx.decode('hex')) result = bitcoin.core.scripteval.VerifyScript(tx_in.scriptSig, prev_tx.vout[prev_out_n].scriptPubKey, tx, in_idx) self.result_edit.setText('Successfully verified input {}'.format(in_idx)) self.inputs_table_model.set_verified(in_idx, True) except Exception as e: self.result_edit.setText(str(e)) self.inputs_table_model.set_verified(in_idx, False) self.error(str(e)) return False return True def do_verify_inputs(self, tx): if tx.is_coinbase(): self.result_edit.setText('Error: Cannot verify coinbase transactions.') self.error('Attempted to verify coinbase transaction.') return False failed_inputs = [] self.result_edit.setText('Verifying...') for i in range(len(tx.vin)): if not self.do_verify_input(tx, i): failed_inputs.append(i) result = 'Successfully verified all inputs.' ret_val = True if failed_inputs: result = 'Failed to verify inputs: {}'.format(failed_inputs) ret_val = False if len(tx.vin) == 0: result = 'Transaction has no inputs.' self.result_edit.setText(result) return ret_val def verify_input(self): in_idx = self.inputs_box.value() if in_idx >= len(self.tx.vin): self.error('Input {} does not exist.'.format(in_idx)) return self.do_verify_input(self.tx, in_idx) def verify_inputs(self): self.do_verify_inputs(self.tx) def verify_item_inputs(self, item): self.deserialize_item(item) self.tabs.setCurrentIndex(self.TAB_VERIFY) self.verify_all_button.animateClick() def on_option_changed(self, key): if key == 'chainparams': self.check_raw_tx()
class TxAnalyzer(BaseDock): tool_name = 'Transaction Analyzer' description = 'Deserializes transactions and verifies their inputs.' is_large = True category = Category.Tx def __init__(self, handler): super(TxAnalyzer, self).__init__(handler) self.raw_tx_edit.textChanged.emit() def init_data(self): self.tx = None def init_actions(self): deserialize = ('Deserialize', self.deserialize_raw) verify = ('Verify inputs', self.do_verify_inputs) self.advertised_actions[RAW_TX] = [deserialize, verify] def create_layout(self): form = QFormLayout() self.raw_tx_edit = QPlainTextEdit() self.raw_tx_edit.setTabChangesFocus(True) self.raw_tx_edit.setFont(monospace_font) self.raw_tx_edit.setContextMenuPolicy(Qt.CustomContextMenu) self.raw_tx_edit.customContextMenuRequested.connect(self.context_menu) self.raw_tx_edit.textChanged.connect(self.check_raw_tx) self.setFocusProxy(self.raw_tx_edit) self.raw_tx_invalid = QLabel('Cannot parse transaction.') self.raw_tx_invalid.setProperty('hasError', True) self.tabs = tabs = QTabWidget() tabs.addTab(self.create_deserialize_tab(), 'Deserialize') tabs.addTab(self.create_verify_tab(), 'Verify') tabs.setTabToolTip(0, 'View the transaction in human-readable form') tabs.setTabToolTip(1, 'Download previous transactions and verify inputs') form.addRow('Raw Tx:', self.raw_tx_edit) form.addRow(self.raw_tx_invalid) form.addRow(tabs) return form def create_deserialize_tab(self): form = QFormLayout() self.deserialize_button = QPushButton('Deserialize') self.deserialize_button.clicked.connect(self.deserialize) btn_hbox = floated_buttons([self.deserialize_button]) self.tx_widget = TxWidget() self.tx_widget.inputs_tree.view.customContextMenuRequested.disconnect(self.tx_widget.inputs_tree.customContextMenu) self.tx_widget.inputs_tree.view.customContextMenuRequested.connect(self.inputs_context_menu) form.addRow(self.tx_widget) w = QWidget() w.setLayout(form) return w def create_verify_tab(self): form = QFormLayout() form.setRowWrapPolicy(QFormLayout.WrapLongRows) self.inputs_box = QSpinBox() self.verify_button = QPushButton('Verify') self.verify_button.clicked.connect(self.verify_input) self.verify_all_button = QPushButton('Verify All') self.verify_all_button.clicked.connect(self.verify_inputs) self.result_edit = QLineEdit() self.result_edit.setReadOnly(True) self.inputs_table = InputStatusTable() form.addRow('Verify Input:', floated_buttons([self.inputs_box, self.verify_button])) form.addRow(floated_buttons([self.verify_all_button])) form.addRow('Result:', self.result_edit) form.addRow(self.inputs_table) w = QWidget() w.setLayout(form) return w def context_menu(self, position): menu = QMenu() if self.tx: txt = str(self.raw_tx_edit.toPlainText()) self.handler.add_plugin_actions(self, menu, RAW_TX, txt) menu.exec_(self.mapToGlobal(position)) def inputs_context_menu(self, position): inputs = self.tx_widget.inputs_tree def inputs_context_verify(): self.tabs.setCurrentIndex(1) row = inputs.view.selectedIndexes()[0].row() self.do_verify_input(self.tx, row) menu = QMenu() if self.tx: menu = inputs.context_menu() menu.addAction('Verify script', inputs_context_verify) menu.exec_(inputs.view.viewport().mapToGlobal(position)) def clear(self): self.result_edit.clear() self.tx_widget.clear() self.inputs_table.clear() def check_raw_tx(self): txt = str(self.raw_tx_edit.toPlainText()) # Variable substitution if txt.startswith('$'): var_value = self.handler.get_plugin('Variables').dock.get_key(txt[1:]) if var_value: self.raw_tx_edit.setPlainText(var_value) return tx = None valid = True try: tx = Transaction.deserialize(txt.decode('hex')) except Exception: valid = False self.tx = tx self.deserialize_button.setEnabled(valid) self.inputs_box.setEnabled(valid) self.verify_button.setEnabled(valid) self.verify_all_button.setEnabled(valid) self.tx_widget.setEnabled(valid) self.clear() if valid: self.raw_tx_invalid.hide() self.inputs_box.setRange(0, len(tx.vin) - 1) self.deserialize() elif txt: self.raw_tx_invalid.show() else: self.raw_tx_invalid.hide() def deserialize_raw(self, txt): """Deserialize a raw transaction.""" self.needsFocus.emit() self.raw_tx_edit.setPlainText(txt) self.deserialize() def deserialize(self): self.clear() self.tx_widget.set_tx(self.tx) self.inputs_table.set_tx(self.tx) self.status_message('Deserialized transaction {}'.format(bitcoin.core.b2lx(self.tx.GetHash()))) def do_verify_input(self, tx, in_idx): raw_prev_tx = None tx_in = tx.vin[in_idx] txid = b2lx(tx_in.prevout.hash) prev_out_n = tx_in.prevout.n try: raw_prev_tx = self.handler.download_blockchain_data('raw_transaction', txid) except Exception as e: self.status_message(str(e), True) return False try: prev_tx = Transaction.deserialize(raw_prev_tx.decode('hex')) result = bitcoin.core.scripteval.VerifyScript(tx_in.scriptSig, prev_tx.vout[prev_out_n].scriptPubKey, tx, in_idx) self.result_edit.setText('Successfully verified input {}'.format(in_idx)) self.inputs_table.set_verified(in_idx, True) except Exception as e: self.result_edit.setText(str(e)) self.inputs_table.set_verified(in_idx, False) self.status_message(str(e), True) return False return True def do_verify_inputs(self, txt): self.needsFocus.emit() self.raw_tx_edit.setPlainText(txt) tx = Transaction.deserialize(txt.decode('hex')) failed_inputs = [] self.result_edit.setText('Verifying...') for i in range(len(tx.vin)): if not self.do_verify_input(tx, i): failed_inputs.append(i) result = 'Successfully verified all inputs.' ret_val = True if failed_inputs: result = 'Failed to verify inputs: {}'.format(failed_inputs) ret_val = False if len(tx.vin) == 0: result = 'Transaction has no inputs.' self.result_edit.setText(result) return ret_val def verify_input(self): tx = None try: txt = str(self.raw_tx_edit.toPlainText()) tx = Transaction.deserialize(txt.decode('hex')) except Exception: self.status_message('Could not deserialize transaction.', True) return in_idx = self.inputs_box.value() if in_idx >= len(tx.vin): self.status_message('Input {} does not exist.'.format(in_idx), True) return self.do_verify_input(tx, in_idx) def verify_inputs(self): txt = str(self.raw_tx_edit.toPlainText()) self.do_verify_inputs(txt) def refresh_data(self): if self.tx: self.deserialize() def on_option_changed(self, key): if key == 'chainparams': self.raw_tx_edit.textChanged.emit()