Esempio n. 1
0
 def on_btnShowCollateralPathAddress_clicked(self, checked):
     if self.masternode.collateralBip32Path:
         try:
             if self.main_dlg.connect_hardware_wallet():
                 hw_session = self.main_dlg.hw_session
                 addr = hw_intf.get_address(
                     hw_session, self.masternode.collateralBip32Path, True,
                     f'Displaying address for the BIP32 path <b>{self.masternode.collateralBip32Path}</b>.'
                     f'<br>Click the confirmation button on your device.')
         except CancelException:
             pass
Esempio n. 2
0
 def on_btnBip32PathToAddress_clicked(self, checked):
     if self.masternode.collateralBip32Path:
         if self.main_dlg.connect_hardware_wallet():
             try:
                 hw_session = self.main_dlg.hw_session
                 addr = hw_intf.get_address(hw_session, self.masternode.collateralBip32Path, show_display=True)
                 if addr:
                     self.masternode.collateralAddress = addr.strip()
                     self.edtCollateralAddress.setText(addr.strip())
                     self.set_modified()
                     self.update_ui_controls_state()
             except CancelException:
                 pass
    def on_btnLoadTransactions_clicked(self):
        if not self.main_ui.connectHardwareWallet():
            return

        try:
            path = self.edtSourceBip32Path.text()
            if path:
                # check if address mathes address read from bip32 path
                try:
                    address = get_address(self.main_ui, path)
                    if not address:
                        self.errorMsg(
                            "Couldn't read address for the specified BIP32 path: %s."
                            % path)
                    self.utxos_source = [(address, path)]
                    self.load_utxos()
                    if len(self.utxos) == 0:
                        self.setMessage(
                            '<span style="color:red">There is no Unspent Transaction Outputs '
                            '<i>(UTXOs)</i> for this address: %s.</span>' %
                            address)
                    else:
                        self.setMessage(
                            'Unspent Transaction Outputs <i>(UTXOs)</i> for: %s.</span>'
                            % address)

                except HardwareWalletPinException as e:
                    raise

                except Exception as e:
                    logging.exception(
                        'Exception while reading address for BIP32 path (%s).'
                        % path)
                    self.errorMsg('Invalid BIP32 path.')
                    self.edtSourceBip32Path.setFocus()
            else:
                self.errorMsg('Enter the BIP32 path.')
                self.edtSourceBip32Path.setFocus()
        except Exception:
            logging.exception(
                'Exception while loading unspent transaction outputs.')
            raise
    def on_btnSend_clicked(self):
        """
        Sends funds to GoByte address specified by user.
        """

        amount, utxos = self.get_selected_utxos()
        if len(utxos):
            try:
                if not self.main_ui.connect_hardware_wallet():
                    return
            except HardwareWalletCancelException:
                return

            bip32_to_address = {}  # for saving addresses read from HW by BIP32 path
            total_satoshis = 0
            coinbase_locked_exist = False

            # verify if:
            #  - utxo is the masternode collateral transation
            #  - the utxo GoByte (signing) address matches the hardware wallet address for a given path
            for utxo_idx, utxo in enumerate(utxos):
                total_satoshis += utxo['satoshis']
                logging.info(f'UTXO satosis: {utxo["satoshis"]}')
                if utxo['collateral']:
                    if self.queryDlg(
                            "Warning: you are going to transfer masternode's collateral (1000 GoByte) transaction "
                            "output. Proceeding will result in broken masternode.\n\n"
                            "Do you really want to continue?",
                            buttons=QMessageBox.Yes | QMessageBox.Cancel,
                            default_button=QMessageBox.Cancel, icon=QMessageBox.Warning) == QMessageBox.Cancel:
                        return
                if utxo['coinbase_locked']:
                    coinbase_locked_exist = True

                bip32_path = utxo.get('bip32_path', None)
                if not bip32_path:
                    self.errorMsg('No BIP32 path for UTXO: %s. Cannot continue.' % utxo['txid'])
                    return

                addr_hw = bip32_to_address.get(bip32_path, None)
                if not addr_hw:
                    addr_hw = get_address(self.main_ui.hw_session, bip32_path)
                    bip32_to_address[bip32_path] = addr_hw
                if addr_hw != utxo['address']:
                    self.errorMsg("<html style=\"font-weight:normal\">GoByte address inconsistency between UTXO (%d) and HW path: %s.<br><br>"
                                 "<b>HW address</b>: %s<br>"
                                 "<b>UTXO address</b>: %s<br><br>"
                                 "Cannot continue.</html>" %
                                  (utxo_idx+1, bip32_path, addr_hw, utxo['address']))
                    return

            if coinbase_locked_exist:
                if self.queryDlg(
                        "Warning: you have selected at least one coinbase transaction without the required number of "
                        "confirmations (100). Your transaction will probably be rejected by the network.\n\n"
                        "Do you really want to continue?",
                        buttons=QMessageBox.Yes | QMessageBox.Cancel,
                        default_button=QMessageBox.Cancel, icon=QMessageBox.Warning) == QMessageBox.Cancel:
                    return
            try:
                dest_data = self.wdg_dest_adresses.get_tx_destination_data()
                if dest_data:
                    total_satoshis_actual = 0
                    for dd in dest_data:
                        total_satoshis_actual += dd[1]
                        logging.info(f'dest amount: {dd[1]}')

                    fee = self.wdg_dest_adresses.get_tx_fee()
                    use_is = self.wdg_dest_adresses.get_use_instant_send()
                    logging.info(f'fee: {fee}')
                    if total_satoshis != total_satoshis_actual + fee:
                        logging.warning(f'total_satoshis ({total_satoshis}) != total_satoshis_real '
                                        f'({total_satoshis_actual}) + fee ({fee})')
                        logging.warning(f'total_satoshis_real + fee: {total_satoshis_actual + fee}')

                        if abs(total_satoshis - total_satoshis_actual - fee) > 10:
                            raise Exception('Data validation failure')

                    try:
                        serialized_tx, amount_to_send = prepare_transfer_tx(
                            self.main_ui.hw_session, utxos, dest_data, fee, self.rawtransactions)
                    except HardwareWalletCancelException:
                        # user cancelled the operations
                        hw_intf.cancel_hw_operation(self.main_ui.hw_session.hw_client)
                        return
                    except Exception:
                        logging.exception('Exception when preparing the transaction.')
                        raise

                    tx_hex = serialized_tx.hex()
                    logging.info('Raw signed transaction: ' + tx_hex)
                    if len(tx_hex) > 90000:
                        self.errorMsg("Transaction's length exceeds 90000 bytes. Select less UTXOs and try again.")
                    else:
                        tx_dlg = TransactionDlg(self, self.main_ui.config, self.gobyted_intf, tx_hex, use_is)
                        if tx_dlg.exec_():
                            amount, sel_utxos = self.get_selected_utxos()
                            if sel_utxos:
                                # mark and uncheck all spent utxox
                                for utxo_idx, utxo in enumerate(sel_utxos):
                                    utxo['spent_date'] = time.time()

                                self.table_model.beginResetModel()
                                self.table_model.endResetModel()
            except Exception as e:
                logging.exception('Unknown error occurred.')
                self.errorMsg(str(e))
        else:
            self.errorMsg('No UTXO to send.')
    def get_collateral_tx_address_thread(self, ctrl: CtrlObject):
        txes_cnt = 0
        msg = ''
        break_scanning = False
        ctrl.dlg_config_fun(dlg_title="Validating collateral transaction.",
                            show_progress_bar=False)
        ctrl.display_msg_fun('Verifying collateral transaction...')

        def check_break_scanning():
            nonlocal break_scanning
            if self.finishing or break_scanning:
                # stop the scanning process if the dialog finishes or the address/bip32path has been found
                raise BreakFetchTransactionsException()

        def fetch_txes_feeback(tx_cnt: int):
            nonlocal msg, txes_cnt
            txes_cnt += tx_cnt
            ctrl.display_msg_fun(msg + '<br><br>' +
                                 'Number of transactions fetched so far: ' +
                                 str(txes_cnt))

        def on_msg_link_activated(link: str):
            nonlocal break_scanning
            if link == 'break':
                break_scanning = True

        try:
            tx = self.dashd_intf.getrawtransaction(self.dmn_collateral_tx,
                                                   1,
                                                   skip_cache=True)
        except Exception as e:
            raise Exception(
                'Cannot get the collateral transaction due to the following errror: '
                + str(e))

        vouts = tx.get('vout')
        if vouts:
            if self.dmn_collateral_tx_index < len(vouts):
                vout = vouts[self.dmn_collateral_tx_index]
                spk = vout.get('scriptPubKey')
                if not spk:
                    raise Exception(
                        f'The collateral transaction ({self.dmn_collateral_tx}) output '
                        f'({self.dmn_collateral_tx_index}) doesn\'t have value in the scriptPubKey '
                        f'field.')
                ads = spk.get('addresses')
                if not ads or len(ads) < 0:
                    raise Exception(
                        'The collateral transaction output doesn\'t have the Dash address assigned.'
                    )
                self.dmn_collateral_tx_address = ads[0]
            else:
                raise Exception(
                    f'Transaction {self.dmn_collateral_tx} doesn\'t have output with index: '
                    f'{self.dmn_collateral_tx_index}')
        else:
            raise Exception('Invalid collateral transaction')

        ctrl.display_msg_fun(
            'Verifying the collateral transaction address on your hardware wallet.'
        )
        if not self.main_dlg.connect_hardware_wallet():
            return False

        if self.dmn_collateral_tx_address_path:
            addr = hw_intf.get_address(self.main_dlg.hw_session,
                                       self.dmn_collateral_tx_address_path)
            msg = ''
            if addr != self.dmn_collateral_tx_address:
                log.warning(
                    f'The address returned by the hardware wallet ({addr}) for the BIP32 path '
                    f'{self.dmn_collateral_tx_address_path} differs from the address stored the mn configuration '
                    f'(self.dmn_collateral_tx_address). Need to scan wallet for a correct BIP32 path.'
                )

                msg = '<span style="color:red">The BIP32 path of the collateral address from your mn config is incorret.<br></span>' \
                      f'Trying to find the BIP32 path of the address {self.dmn_collateral_tx_address} in your wallet.' \
                      f'<br>This may take a while (<a href="break">break</a>)...'
                self.dmn_collateral_tx_address_path = ''
        else:
            msg = 'Looking for a BIP32 path of the Dash address related to the masternode collateral.<br>' \
                  'This may take a while (<a href="break">break</a>)....'

        if not self.dmn_collateral_tx_address_path and not self.finishing:
            lbl = ctrl.get_msg_label_control()
            if lbl:

                def set():
                    lbl.setOpenExternalLinks(False)
                    lbl.setTextInteractionFlags(lbl.textInteractionFlags()
                                                & ~Qt.TextSelectableByMouse)
                    lbl.linkActivated.connect(on_msg_link_activated)
                    lbl.repaint()

                WndUtils.call_in_main_thread(set)

            ctrl.display_msg_fun(msg)

            # fetch the transactions that involved the addresses stored in the wallet - during this
            # all the used addresses are revealed
            addr = self.bip44_wallet.scan_wallet_for_address(
                self.dmn_collateral_tx_address, check_break_scanning,
                fetch_txes_feeback)
            if not addr:
                if not break_scanning:
                    WndUtils.errorMsg(
                        f'Couldn\'t find a BIP32 path of the collateral address ({self.dmn_collateral_tx_address}).'
                    )
                return False
            else:
                self.dmn_collateral_tx_address_path = addr.bip32_path

        return True
    def on_btnSend_clicked(self):
        """
        Sends funds to Dash address specified by user.
        """
        utxos = self.table_model.getSelectedUtxos()
        if len(utxos):
            address = self.edtDestAddress.text()
            if address:
                if not self.main_ui.connectHardwareWallet():
                    return

                bip32_to_address = {
                }  # for saving addresses read from HW by BIP32 path

                # check if user selected masternode collateral transaction; if so display warning
                # also check if UTXO dash address matches address of BIP32 path in HW
                for utxo_idx, utxo in enumerate(utxos):
                    if utxo['collateral']:
                        if self.queryDlg(
                                "Warning: you are going to transfer masternode's collateral (1000 Dash) transaction "
                                "output. Proceeding will result in broken masternode.\n\n"
                                "Do you really want to continue?",
                                buttons=QMessageBox.Yes | QMessageBox.Cancel,
                                default_button=QMessageBox.Cancel,
                                icon=QMessageBox.Warning
                        ) == QMessageBox.Cancel:
                            return
                    bip32_path = utxo.get('bip32_path', None)
                    if not bip32_path:
                        self.errorMsg(
                            'No BIP32 path for UTXO: %s. Cannot continue.' %
                            utxo['txid'])
                        return

                    addr_hw = bip32_to_address.get(bip32_path, None)
                    if not addr_hw:
                        addr_hw = get_address(self.main_ui, bip32_path)
                        bip32_to_address[bip32_path] = addr_hw
                    if addr_hw != utxo['address']:
                        self.errorMsg(
                            "Dash address inconsistency between UTXO (%d) and a HW's path: %s.\n\n"
                            "<b>HW address</b>: %s\n"
                            "<b>UTXO address</b>: %s\n\n"
                            "Cannot continue." % (utxo_idx + 1, bip32_path,
                                                  addr_hw, utxo['address']))
                        return

                try:
                    if self.dashd_intf.validateaddress(address).get(
                            'isvalid', False):
                        fee = self.edtTxFee.value() * 1e8

                        try:
                            serialized_tx, amount_to_send = prepare_transfer_tx(
                                self.main_ui, utxos, address, fee,
                                self.rawtransactions)
                        except Exception:
                            logging.exception(
                                'Exception when preparing the transaction.')
                            raise

                        tx_hex = serialized_tx.hex()
                        logging.info('Raw signed transaction: ' + tx_hex)
                        if len(tx_hex) > 90000:
                            self.errorMsg(
                                "Transaction's length exceeds 90000 bytes. Select less UTXOs and try again."
                            )
                        else:
                            if SCREENSHOT_MODE:
                                self.warnMsg('Inside screenshot mode')

                            if self.queryDlg(
                                    'Broadcast signed transaction?\n\n'
                                    '<b>Destination address</b>: %s\n'
                                    '<b>Amount to send</b>: %s Dash\n'
                                    '<b>Fee</b>: %s Dash\n'
                                    '<b>Size</b>: %d bytes' %
                                (address, str(round(amount_to_send / 1e8, 8)),
                                 str(round(fee / 1e8, 8)), len(tx_hex) / 2),
                                    buttons=QMessageBox.Yes
                                    | QMessageBox.Cancel,
                                    default_button=QMessageBox.Yes
                            ) == QMessageBox.Yes:

                                # decoded_tx = self.dashd_intf.decoderawtransaction(tx_hex)

                                if SCREENSHOT_MODE:
                                    txid = '2195aecd5575e37fedf30e6a7ae317c6ba3650a004dc7e901210ac454f61a2e8'
                                else:
                                    txid = self.dashd_intf.sendrawtransaction(
                                        tx_hex)

                                if txid:
                                    block_explorer = self.main_ui.config.block_explorer_tx
                                    if block_explorer:
                                        url = block_explorer.replace(
                                            '%TXID%', txid)
                                        message = 'Transaction sent. TX ID: <a href="%s">%s</a>' % (
                                            url, txid)
                                    else:
                                        message = 'Transaction sent. TX ID: %s' % txid

                                    logging.info('Sent transaction: ' + txid)
                                    self.infoMsg(message)
                                else:
                                    self.errorMsg(
                                        'Problem with sending transaction: no txid returned'
                                    )
                    else:
                        self.errorMsg(
                            'Invalid destination Dash address (%s).' % address)
                except Exception as e:
                    self.errorMsg(str(e))
            else:
                self.errorMsg('Missing destination Dash address.')
        else:
            self.errorMsg('No UTXO to send.')