예제 #1
0
 def __init__(self, plugin, window):
     super().__init__()  # top-level QObject, no parent()
     self.server = TimeoutServerProxy('http://%s:%d' % (HOST, PORT),
                                      allow_none=True,
                                      timeout=2.0)
     self.listener = Listener(self)
     self.plugin_ref = Weak.ref(plugin)
     self.window_ref = Weak.ref(window)
     self.cosigner_receive_signal.connect(self.on_receive)
예제 #2
0
    def __init__(self):
        super().__init__(
        )  # Top-level window. Parent needs to hold a reference to us and clean us up appropriately.
        self.setWindowTitle('Oregano - ' + _('Payment Request'))
        self.label = ''
        self.amount = 0
        self.setFocusPolicy(Qt.NoFocus)
        self.setSizePolicy(QSizePolicy.MinimumExpanding,
                           QSizePolicy.MinimumExpanding)

        main_box = QHBoxLayout(self)
        main_box.setContentsMargins(12, 12, 12, 12)
        self.qrw = QRCodeWidget()
        self.qrw.setSizePolicy(QSizePolicy.MinimumExpanding,
                               QSizePolicy.MinimumExpanding)
        main_box.addWidget(self.qrw, 2)

        vbox = QVBoxLayout()
        vbox.setContentsMargins(12, 12, 12, 12)
        main_box.addLayout(vbox, 2)
        main_box.addStretch(1)

        self.address_label = WWLabel()
        self.address_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        vbox.addWidget(self.address_label)

        self.msg_label = WWLabel()
        self.msg_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        vbox.addWidget(self.msg_label)

        self.amount_label = WWLabel()
        self.amount_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        vbox.addWidget(self.amount_label)

        self.op_return_label = WWLabel()
        self.op_return_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        vbox.addWidget(self.op_return_label)

        vbox.addStretch(2)

        copyBut = QPushButton(_("Copy QR Image"))
        saveBut = QPushButton(_("Save QR Image"))
        vbox.addLayout(Buttons(copyBut, saveBut))

        weakSelf = Weak.ref(
            self
        )  # Qt & Python GC hygeine: don't hold references to self in non-method slots as it appears Qt+Python GC don't like this too much and may leak memory in that case.
        weakQ = Weak.ref(self.qrw)
        weakBut = Weak.ref(copyBut)
        copyBut.clicked.connect(lambda: copy_to_clipboard(weakQ(), weakBut()))
        saveBut.clicked.connect(lambda: save_to_file(weakQ(), weakSelf()))
예제 #3
0
 def init_qt(self, gui):
     if self.initted: return  # already initted
     self.print_error("Initializing...")
     for window in gui.windows:
         self.on_new_window(window)
     Plugin.Instance_ref = Weak.ref(self)
     self.initted = True
예제 #4
0
파일: qt.py 프로젝트: cculianu/Oregano
 def settings_widget(self, window):
     while window and window_parent(window) and not isinstance(window_parent(window), ElectrumWindow):
         # MacOS fixup -- find window.parent() because we can end up with window.parent() not an ElectrumWindow
         window = window_parent(window)
     windowRef = Weak.ref(window)
     return EnterButton(_('Settings'),
                        partial(self.settings_dialog, windowRef))
예제 #5
0
 def __init__(self, state):
     super().__init__()
     self.daemon = True
     self.state_ref = Weak.ref(state)
     self.received = set()
     self.keyhashes = []
     self.timeoutQ = queue.Queue(
     )  # this queue's sole purpose is to provide an interruptible sleep
예제 #6
0
 def transaction_dialog(self, d):
     window, state = self._find_window_and_state_for_wallet(d.wallet)
     if window and state:
         d.cosigner_send_button = b = QPushButton(_("Send to cosigner"))
         b.wallet_ref = Weak.ref(window.wallet)
         b.clicked.connect(lambda: Plugin.do_send_static(d))
         d.buttons.insert(0, b)
         self.transaction_dialog_update(d)
예제 #7
0
파일: qt.py 프로젝트: cculianu/Oregano
 def settings_dialog(self, windowRef):
     window = windowRef() # NB: window is the internal plugins dialog and not the wallet window
     if not window or not isinstance(window_parent(window), ElectrumWindow): return
     wallet = window_parent(window).wallet
     d = WindowModalDialog(window.top_level_window(), _("Label Settings"))
     d.ok_button = OkButton(d)
     dlgRef = Weak.ref(d)
     if wallet in self.wallets:
         class MySigs(QObject):
             ok_button_disable_sig = pyqtSignal(bool)
         d.sigs = MySigs(d)
         d.sigs.ok_button_disable_sig.connect(d.ok_button.setDisabled) # disable ok button while the TaskThread runs ..
         hbox = QHBoxLayout()
         hbox.addWidget(QLabel(_("LabelSync options:")))
         upload = ThreadedButton(_("Force upload"),
                                 partial(Weak(self.do_force_upload), wallet, dlgRef),
                                 partial(Weak(self.done_processing), dlgRef),
                                 partial(Weak(self.error_processing), dlgRef))
         download = ThreadedButton(_("Force download"),
                                   partial(Weak(self.do_force_download), wallet, dlgRef),
                                   partial(Weak(self.done_processing), dlgRef),
                                   partial(Weak(self.error_processing), dlgRef))
         d.thread_buts = (upload, download)
         d.finished.connect(partial(Weak(self.on_dlg_finished), dlgRef))
         vbox = QVBoxLayout()
         vbox.addWidget(upload)
         vbox.addWidget(download)
         hbox.addLayout(vbox)
         vbox = QVBoxLayout(d)
         vbox.addLayout(hbox)
     else:
         vbox = QVBoxLayout(d)
         if wallet.network:
             # has network, so the fact that the wallet isn't in the list means it's incompatible
             l = QLabel('<b>' + _("LabelSync not supported for this wallet type") + '</b>')
             l.setAlignment(Qt.AlignCenter)
             vbox.addWidget(l)
             l = QLabel(_("(Only deterministic wallets are supported)"))
             l.setAlignment(Qt.AlignCenter)
             vbox.addWidget(l)
         else:
             # Does not have network, so we won't speak of incompatibility, but instead remind user offline mode means OFFLINE! ;)
             l = QLabel(_("You are using Oregano in offline mode; restart Oregano if you want to get connected"))
             l.setWordWrap(True)
             vbox.addWidget(l)
     vbox.addSpacing(20)
     vbox.addLayout(Buttons(d.ok_button))
     return bool(d.exec_())
예제 #8
0
 def initiate_fetch_input_data(self, force):
     weakSelfRef = Weak.ref(self)
     def dl_prog(pct):
         slf = weakSelfRef()
         if slf:
             slf._dl_pct = pct
             slf.throttled_update_sig.emit()
     def dl_done():
         slf = weakSelfRef()
         if slf:
             slf._dl_pct = None
             slf.throttled_update_sig.emit()
             slf.dl_done_sig.emit()
     dl_retries = 0
     def dl_done_mainthread():
         nonlocal dl_retries
         slf = weakSelfRef()
         if slf:
             if slf._closed:
                 return
             dl_retries += 1
             fee = slf.try_calculate_fee()
             if fee is None and dl_retries < 2:
                 if not self.is_fetch_input_data():
                     slf.print_error("input fetch incomplete; network use is disabled in GUI")
                     return
                 # retry at most once -- in case a slow server scrwed us up
                 slf.print_error("input fetch appears incomplete; retrying download once ...")
                 slf.tx.fetch_input_data(self.wallet, done_callback=dl_done, prog_callback=dl_prog, force=True, use_network=self.is_fetch_input_data())  # in this case we reallly do force
             elif fee is not None:
                 slf.print_error("input fetch success")
             else:
                 slf.print_error("input fetch failed")
     try: self.dl_done_sig.disconnect()  # disconnect previous
     except TypeError: pass
     self.dl_done_sig.connect(dl_done_mainthread, Qt.QueuedConnection)
     self.tx.fetch_input_data(self.wallet, done_callback=dl_done, prog_callback=dl_prog, force=force, use_network=self.is_fetch_input_data())
예제 #9
0
class Plugin(BasePlugin):

    Instance_ref = Weak.ref(_Dead(
    ))  # Make sure Instance_ref is always defined, defaults to dead object

    def __init__(self, parent, config, name):
        BasePlugin.__init__(self, parent, config, name)
        self.windows = []
        self.initted = False

    @hook
    def init_qt(self, gui):
        if self.initted: return  # already initted
        self.print_error("Initializing...")
        for window in gui.windows:
            self.on_new_window(window)
        Plugin.Instance_ref = Weak.ref(self)
        self.initted = True

    @hook
    def on_new_window(self, window):
        try:
            wallet = window.wallet
        except AttributeError:
            # this can happen if wallet is not started up properly
            self.print_error(
                "WARNING: Window {} lacks a wallet -- startup race condition likely. FIXME!"
                .format(window.diagnostic_name()))
            return
        if isinstance(wallet, Multisig_Wallet):
            window.cosigner_pool_state = state = State(self, window)
            self.windows.append(window)
            self.update(window)
            # un-gray-out buttons for tx dialogs left around related to this window
            for b in Plugin.get_all_cosigner_buttons():
                if b.wallet_ref() == wallet:
                    b.setEnabled(True)

    @hook
    def on_close_window(self, window):
        if window in self.windows:
            state = getattr(window, 'cosigner_pool_state', None)
            if state:
                if state.listener:
                    self.print_error("shutting down listener for",
                                     window.diagnostic_name())
                    state.listener.stop_join()
                state.deleteLater()
                delattr(window, 'cosigner_pool_state')
            self.print_error("unregistered for window",
                             window.diagnostic_name())
            self.windows.remove(window)
            # gray out buttons for tx dialogs left around related to this window
            for b in Plugin.get_all_cosigner_buttons():
                if b.wallet_ref() == window.wallet:
                    b.setEnabled(False)

    @staticmethod
    def get_all_cosigner_buttons():
        ret = []
        app = QApplication.instance()
        for w in app.topLevelWidgets():
            if isinstance(w, TxDialog):
                but = getattr(w, 'cosigner_send_button', None)
                if but: ret.append(but)
        return ret

    def is_available(self):
        return True

    def on_close(self):
        for w in self.windows.copy():
            self.on_close_window(w)
        self.windows = []
        self.initted = False
        super().on_close()

    def update(self, window):
        wallet = window.wallet
        state = window.cosigner_pool_state
        if not state:
            self.print_error("No cosigner pool state object for window",
                             window.diagnostic_name())
            return
        listener = state.listener
        state.keys = []
        state.cosigner_list = []
        for key, keystore in wallet.keystores.items():
            xpub = keystore.get_master_public_key()
            K = bitcoin.deserialize_xpub(xpub)[-1]
            _hash = bh2u(bitcoin.Hash(K))
            if not keystore.is_watching_only():
                state.keys.append((key, _hash))
            else:
                state.cosigner_list.append((xpub, K, _hash))
        listener.set_keyhashes([t[1] for t in state.keys])
        if not listener.is_running():
            self.print_error("Starting listener for", window.diagnostic_name())
            listener.start()

    @hook
    def transaction_dialog(self, d):
        window, state = self._find_window_and_state_for_wallet(d.wallet)
        if window and state:
            d.cosigner_send_button = b = QPushButton(_("Send to cosigner"))
            b.wallet_ref = Weak.ref(window.wallet)
            b.clicked.connect(lambda: Plugin.do_send_static(d))
            d.buttons.insert(0, b)
            self.transaction_dialog_update(d)

    @hook
    def transaction_dialog_update(self, d):
        window, state = self._find_window_and_state_for_wallet(d.wallet)
        but = getattr(d, 'cosigner_send_button', None)
        if not but or not window or not state or d.tx.is_complete(
        ) or d.wallet.can_sign(d.tx):
            but and but.hide()
            return
        for xpub, K, _hash in state.cosigner_list:
            if self.cosigner_can_sign(d.tx, xpub):
                but and but.show()
                break
        else:
            but and but.hide()

    def _find_window_and_state_for_wallet(self, wallet):
        for window in self.windows:
            if window.wallet == wallet:
                return window, window.cosigner_pool_state
        return None, None

    def cosigner_can_sign(self, tx, cosigner_xpub):
        from oregano.keystore import is_xpubkey, parse_xpubkey
        xpub_set = set([])
        for txin in tx.inputs():
            for x_pubkey in txin['x_pubkeys']:
                if is_xpubkey(x_pubkey):
                    xpub, s = parse_xpubkey(x_pubkey)
                    xpub_set.add(xpub)
        return cosigner_xpub in xpub_set

    @staticmethod
    def do_send_static(d):
        ''' Decouples button slot from running instance in case user stops/restarts the plugin while TxDialogs are up. '''
        plugin = Plugin.Instance_ref()
        if plugin:
            plugin.do_send(d)
        else:
            print_error("[cosigner_pool] No plugin.")

    def do_send(self, d):
        tx = d.tx
        window, state = self._find_window_and_state_for_wallet(d.wallet)
        if not tx or not window or not state:
            self.print_error("Missing tx or window or state")
            return
        for xpub, K, _hash in state.cosigner_list:
            if not self.cosigner_can_sign(tx, xpub):
                continue
            message = bitcoin.encrypt_message(bfh(tx.raw),
                                              bh2u(K)).decode('ascii')
            try:
                state.server.put(_hash, message)
            except Exception as e:
                traceback.print_exc(file=sys.stdout)
                window.show_error(
                    _("Failed to send transaction to cosigning pool."))
                return
            d.show_message(
                _("Your transaction was sent to the cosigning pool.") + '\n' +
                _("Open your cosigner wallet to retrieve it."))

    def on_receive(self, window, keyhash, message):
        self.print_error("signal arrived for", keyhash, "@",
                         window.diagnostic_name())
        state = getattr(window, 'cosigner_pool_state', None)
        if not state:
            self.print_error("Error: state object not found")
            return
        keys = state.keys
        for key, _hash in keys:
            if _hash == keyhash:
                break
        else:
            self.print_error("keyhash not found")
            return

        wallet = window.wallet
        if isinstance(wallet.keystore, keystore.Hardware_KeyStore):
            window.show_warning(
                _('An encrypted transaction was retrieved from cosigning pool.'
                  ) + '\n' +
                _('However, hardware wallets do not support message decryption, '
                  'which makes them not compatible with the current design of cosigner pool.'
                  ))
            return
        password = None
        if wallet.has_password():
            password = window.password_dialog(
                _('An encrypted transaction was retrieved from cosigning pool.'
                  ) + '\n' + _('Please enter your password to decrypt it.'))
            if not password:
                return
        else:
            details = (_(
                "If you choose 'Yes', it will be decrypted and a transaction window will be shown, giving you the opportunity to sign the transaction."
            ) + "\n\n" + _(
                "If you choose 'No', you will be asked again later (the next time this wallet window is opened)."
            ))
            ret = window.msg_box(
                icon=QMessageBox.Question,
                parent=None,
                title=_("Cosigner Pool"),
                buttons=QMessageBox.Yes | QMessageBox.No,
                text=
                _("An encrypted transaction was retrieved from cosigning pool."
                  ) + '\n' + _("Do you want to open it now?"),
                detail_text=details)
            if ret != QMessageBox.Yes:
                return

        err, badpass = "******", False
        try:
            xprv = wallet.keystore.get_master_private_key(password)
        except InvalidPassword as e:
            err, badpass = str(e), True
            xprv = None
        if not xprv:
            window.show_error(err)
            if badpass:
                self.on_receive(window, keyhash, message)  # try again
            return
        try:
            k = bh2u(bitcoin.deserialize_xprv(xprv)[-1])
            EC = bitcoin.EC_KEY(bfh(k))
            message = bh2u(EC.decrypt_message(message))
        except Exception as e:
            traceback.print_exc(file=sys.stdout)
            window.show_error(repr(e))
            return

        state.listener.clear(keyhash)
        tx = transaction.Transaction(message)
        show_transaction(tx, window, prompt_if_unsaved=True)
예제 #10
0
    def __init__(self, tx, parent, desc, prompt_if_unsaved):
        '''Transactions in the wallet will show their description.
        Pass desc to give a description for txs not yet in the wallet.
        '''
        # We want to be a top-level window
        QDialog.__init__(self, parent=None)
        # Take a copy; it might get updated in the main window by
        # e.g. the FX plugin.  If this happens during or after a long
        # sign operation the signatures are lost.
        self.tx = copy.deepcopy(tx)
        self.tx.deserialize()
        self.main_window = parent
        self.wallet = parent.wallet
        self.prompt_if_unsaved = prompt_if_unsaved
        self.saved = False
        self.desc = desc
        self.cashaddr_signal_slots = []
        self._dl_pct = None
        self._closed = False
        self.tx_hash = self.tx.txid_fast() if self.tx.raw and self.tx.is_complete() else None
        self.tx_height = self.wallet.get_tx_height(self.tx_hash)[0] or None
        self.block_hash = None
        Weak.finalization_print_error(self)  # track object lifecycle

        self.setMinimumWidth(750)
        self.setWindowTitle(_("Transaction"))

        vbox = QVBoxLayout()
        self.setLayout(vbox)

        self.tx_hash_e  = ButtonsLineEdit()
        l = QLabel(_("&Transaction ID:"))
        l.setBuddy(self.tx_hash_e)
        vbox.addWidget(l)
        self.tx_hash_e.addCopyButton()
        weakSelfRef = Weak.ref(self)
        qr_show = lambda: weakSelfRef() and weakSelfRef().main_window.show_qrcode(str(weakSelfRef().tx_hash_e.text()), _("Transaction ID"), parent=weakSelfRef())
        icon = ":icons/qrcode_white.svg" if ColorScheme.dark_scheme else ":icons/qrcode.svg"
        self.tx_hash_e.addButton(icon, qr_show, _("Show as QR code"))
        self.tx_hash_e.setReadOnly(True)
        vbox.addWidget(self.tx_hash_e)
        self.tx_desc = QLabel()
        vbox.addWidget(self.tx_desc)
        self.status_label = QLabel()
        vbox.addWidget(self.status_label)
        self.date_label = QLabel()
        vbox.addWidget(self.date_label)
        self.amount_label = QLabel()
        vbox.addWidget(self.amount_label)
        self.size_label = QLabel()
        vbox.addWidget(self.size_label)
        self.fee_label = QLabel()
        vbox.addWidget(self.fee_label)

        for l in (self.tx_desc, self.status_label, self.date_label, self.amount_label, self.size_label, self.fee_label):
            # make these labels selectable by mouse in case user wants to copy-paste things in tx dialog
            l.setTextInteractionFlags(l.textInteractionFlags() | Qt.TextSelectableByMouse)

        def open_be_url(link):
            if link:
                try:
                    kind, thing = link.split(':')
                    url = web.BE_URL(self.main_window.config, kind, thing)
                except:
                    url = None
                if url:
                    webopen( url )
                else:
                    self.show_error(_('Unable to open in block explorer. Please be sure your block explorer is configured correctly in preferences.'))

        self.status_label.linkActivated.connect(open_be_url)

        self.add_io(vbox)

        self.freeze_button = b = QPushButton(self._make_freeze_button_text())
        b.setToolTip(_("Lock/unlock the coin(s) being spent in this transaction.\n\n"
                       "Use this facility if you wish to broadcast this transaction later,\n"
                       "in order to prevent its inputs from being accidentally spent."))
        b.clicked.connect(self.do_freeze_unfreeze)

        self.sign_button = b = QPushButton(_("&Sign"))
        b.clicked.connect(self.sign)
        b.setToolTip(_("Sign the transaction"))

        self.broadcast_button = b = QPushButton(_("&Broadcast"))
        b.clicked.connect(self.do_broadcast)
        b.setToolTip(_("Submit the transaction to the blockchain"))
        self.last_broadcast_time = 0

        self.save_button = b = QPushButton(_("S&ave"))
        b.setToolTip(_("Save the transaction to a file"))
        b.clicked.connect(self.save)

        self.cancel_button = b = CloseButton(self)

        self.qr_button = b = QPushButton()
        b.setToolTip(_("Show transaction QR code"))
        b.setIcon(QIcon(icon))
        b.clicked.connect(self.show_qr)
        b.setShortcut(QKeySequence(Qt.ALT + Qt.Key_Q))

        self.copy_button = b = CopyButton(lambda: str(weakSelfRef() and weakSelfRef().tx),
                                          callback=lambda: weakSelfRef() and weakSelfRef().show_message(
                                              _("Transaction raw hex copied to clipboard.")))
        b.setToolTip(_("Copy transaction raw hex to the clipboard"))


        # Action buttons
        self.buttons = [self.freeze_button, self.sign_button, self.broadcast_button, self.cancel_button]
        # Transaction sharing buttons
        self.sharing_buttons = [self.copy_button, self.qr_button, self.save_button]

        run_hook('transaction_dialog', self)

        hbox = QHBoxLayout()
        hbox.addLayout(Buttons(*self.sharing_buttons))
        hbox.addStretch(1)
        hbox.addLayout(Buttons(*self.buttons))
        vbox.addLayout(hbox)

        if self.tx_height:
            # this avoids downloading the block_height info if we already have it.
            self.tx.ephemeral['block_height'] = self.tx_height

        self.throttled_update_sig.connect(self.throttled_update, Qt.QueuedConnection)
        self.initiate_fetch_input_data(True)

        self.update()

        # connect slots so we update in realtime as blocks come in, etc
        parent.history_updated_signal.connect(self.update_tx_if_in_wallet)
        parent.labels_updated_signal.connect(self.update_tx_if_in_wallet)
        parent.network_signal.connect(self.got_verified_tx)