From 5284cfb46d828a6c488003ee1a849ea78cedb419 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Sat, 23 Feb 2019 15:35:37 +0200 Subject: [PATCH] Fix #60, fix #48 -- Finally addressed 'Spend Coins' wonkiness There were bugs when selecting coins from the coins tab and doing 'spend' while CashShuffle was running. The coins list would not get properly maintained and would default to an empty list, despite the UI showing coins. The bugs have been fixed and in addition, the UI now indicates WHICH coins in the spend-from list are actually being considered by graying out ineligible coins. Thanks @sploit and @emergentreasons for bringing this to my attention! --- gui/qt/main_window.py | 23 ++++++++++++++++++++--- plugins/shuffle/client.py | 12 ++++++++++++ plugins/shuffle/qt.py | 6 +++++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 832dcb5edc60..234057ec3831 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -1452,7 +1452,13 @@ def set_pay_from(self, coins): self.pay_from = list(coins) self.redraw_from_list() - def redraw_from_list(self): + def redraw_from_list(self, *, spendable=None): + ''' Optional kwarg spendable indicates *which* of the UTXOs in the + self.pay_from list are actually spendable. If this arg is specifid, + coins in the self.pay_from list that aren't also in the 'spendable' list + will be grayed out in the UI, to indicate that they will not be used. + Otherwise all coins will be non-gray (default). + (Added for CashShuffle 02/23/2019) ''' self.from_list.clear() self.from_label.setHidden(len(self.pay_from) == 0) self.from_list.setHidden(len(self.pay_from) == 0) @@ -1461,9 +1467,17 @@ def format(x): h = x['prevout_hash'] return '{}...{}:{:d}\t{}'.format(h[0:10], h[-10:], x['prevout_n'], x['address']) + def grayify(twi): + b = twi.foreground(0) + b.setColor(Qt.gray) + for i in range(twi.columnCount()): + twi.setForeground(i, b) for item in self.pay_from: - self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ])) + twi = QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]) + if spendable is not None and item not in spendable: + grayify(twi) + self.from_list.addTopLevelItem(twi) def get_contact_payto(self, key): _type, label = self.contacts.get(key) @@ -1955,10 +1969,13 @@ def remove_address(self, addr): def get_coins(self, isInvoice = False): coins = [] if self.pay_from: - coins = self.pay_from + coins = self.pay_from.copy() else: coins = self.wallet.get_spendable_coins(None, self.config, isInvoice) run_hook("spendable_coin_filter", self, coins) # may modify coins -- used by CashShuffle if in shuffle = ENABLED mode. + if self.pay_from: + # coins may have been filtered, so indicate this in the UI + self.redraw_from_list(spendable=coins) return coins def spend_coins(self, coins): diff --git a/plugins/shuffle/client.py b/plugins/shuffle/client.py index cae2e3cb9374..78357f5b1e48 100644 --- a/plugins/shuffle/client.py +++ b/plugins/shuffle/client.py @@ -592,6 +592,18 @@ def get_coin_for_shuffling(scale, coins): thr.start() return True + def is_coin_busy_shuffling(self, utxo_name_or_dict): + ''' Checks the extant running threads (if any) for a match to coin. + This is a very accurate real-time indication that a coins is busy + shuffling. Used by the spendable_coin_filter in qt.py.''' + if isinstance(utxo_name_or_dict, dict): + name = get_name(utxo_name_or_dict) + else: + name = utxo_name_or_dict + # name must be an str at this point! + with self.wallet.lock, self.wallet.transaction_lock: + return any(thr for thr in self.threads.values() if thr and thr.coin == name) + def is_wallet_ready(self): return bool( self.wallet and self.wallet.is_up_to_date() and self.wallet.network and self.wallet.network.is_connected() diff --git a/plugins/shuffle/qt.py b/plugins/shuffle/qt.py index b0e3461d5324..673edfdb0492 100644 --- a/plugins/shuffle/qt.py +++ b/plugins/shuffle/qt.py @@ -99,6 +99,10 @@ def doChk(): cache[name] = answer return answer +def is_coin_busy_shuffling(window, utxo_or_name): + ''' Convenience wrapper for BackgroundProcess.is_coin_busy_shuffling ''' + bp = getattr(window, 'background_process', None) + return bool(bp and bp.is_coin_busy_shuffling(utxo_or_name)) def get_shuffled_and_unshuffled_coin_totals(wallet, exclude_frozen = False, mature = False, confirmed_only = False): ''' Returns a 3-tuple of tuples of (amount_total, num_utxos) that are 'shuffled', 'unshuffled' and 'unshuffled_but_in_progress', respectively. ''' @@ -619,7 +623,7 @@ def spendable_coin_filter(self, window, coins): elif spend_mode == extra.SpendingModeUnshuffled: # in Cash-Shuffle mode + unshuffled spending we can ONLY spend unshuffled coins! for coin in coins.copy(): - if is_coin_shuffled(window.wallet, coin): + if is_coin_shuffled(window.wallet, coin) or is_coin_busy_shuffling(window, coin): coins.remove(coin)