def make_unsigned_transaction(self, amount, fee, all_inputs, outputs, changes): "make unsigned transaction" dust = dust_threshold(self.network) coins = {} tx_inputs = [] amounts = {} try: for player in all_inputs: inputs_coins = self.get_coins(all_inputs[player]) # if there is no coins on input it terminates the process if inputs_coins: coins[player] = inputs_coins else: return None except: return None for player, pubkey_utxos in coins.items(): amounts[player] = 0 for pubkey, utxos in pubkey_utxos.items(): for utxo in utxos: utxo['type'] = 'p2pkh' utxo['address'] = Address.from_pubkey(pubkey) utxo['pubkeys'] = [pubkey] utxo['x_pubkeys'] = [pubkey] utxo['prevout_hash'] = utxo['tx_hash'] utxo['prevout_n'] = utxo['tx_pos'] utxo['signatures'] = [None] utxo['num_sig'] = 1 tx_inputs.append(utxo) amounts[player] += utxo['value'] tx_inputs.sort(key=lambda x: x['prevout_hash'] + str(x["tx_pos"])) tx_outputs = [(TYPE_ADDRESS, Address.from_string(output), int(amount)) for output in outputs] transaction = Transaction.from_io(tx_inputs, tx_outputs) tx_changes = [ (TYPE_ADDRESS, Address.from_string(changes[player]), int(amounts[player] - amount - fee)) for player in sorted(changes) if Address.is_valid(changes[player]) and int(amounts[player] - amount - fee) >= dust ] transaction.add_outputs(tx_changes) return transaction
def dust_threshold(self): return dust_threshold(self.network)
def _make_protocol_thread(self, scale, coins): def get_coin_for_shuffling(scale, coins): if not getattr(self.wallet, "is_coin_shuffled", None): raise RuntimeWarning('Wallet lacks is_coin_shuffled method!') unshuffled_coins = [coin for coin in coins # Note: the 'is False' is intentional -- we are interested in coins that we know for SURE are not shuffled. # is_coin_shuffled() also returns None in cases where the tx isn't in the history (a rare occurrence) if self.wallet.is_coin_shuffled(coin) is False] upper_amount = min(scale*10 + self.FEE, self.UPPER_BOUND) lower_amount = scale + self.FEE unshuffled_coins_on_scale = [coin for coin in unshuffled_coins # exclude coins out of range and 'done' coins still in history # also exclude coinbase coins (see issue #64) if coin['value'] < upper_amount and coin['value'] >= lower_amount and get_name(coin) not in self.done_utxos and not coin['coinbase']] unshuffled_coins_on_scale.sort(key=lambda x: (x['value'], -x['height'])) # sort by value, preferring older coins on tied value if unshuffled_coins_on_scale: return unshuffled_coins_on_scale[-1] # take the largest,oldest on the scale return None # / coin = get_coin_for_shuffling(scale, coins) if not coin: return try: private_key = self.wallet.export_private_key(coin['address'], self.get_password()) except InvalidPassword: # This shouldn't normally happen but can if the user JUST changed their password in the GUI thread # and we didn't yet get informed of the new password. In which case we give up for now and 10 seconds later # (the next 'period' time), this coin will be picked up again. raise RuntimeWarning('Invalid Password caught when trying to export a private key -- if this keeps happening tell the devs!') utxo_name = get_name(coin) self.wallet.set_frozen_coin_state([utxo_name], True) self._coins_busy_shuffling.add(utxo_name) self.wallet.storage.put(COINS_FROZEN_BY_SHUFFLING, list(self._coins_busy_shuffling)) inputs = {} sks = {} public_key = self.wallet.get_public_key(coin['address']) sk = regenerate_key(deserialize_privkey(private_key)[1]) inputs[public_key] = [utxo_name] sks[public_key] = sk id_sk = generate_random_sk() id_pub = id_sk.GetPubKey(True).hex() output = None for address in self.wallet.get_unused_addresses(): if address not in self.wallet._addresses_cashshuffle_reserved: output = address break while not output: address = self.wallet.create_new_address(for_change = False) if address not in self.wallet._addresses_cashshuffle_reserved: output = address # Reserve the output address so other threads don't use it self.wallet._addresses_cashshuffle_reserved.add(output) # NB: only modify this when holding wallet locks # Check if we will really use the change address. We won't be receving to it if the change is below dust threshold (see #67) will_receive_change = coin['value'] - scale - self.FEE >= dust_threshold(Network.get_instance()) if will_receive_change: change = self.wallet.cashshuffle_get_new_change_address(for_shufflethread=True) # We anticipate using the change address in the shuffle tx, so reserve this address self.wallet._addresses_cashshuffle_reserved.add(change) else: # We still have to specify a change address to the protocol even if it won't be used. :/ # We'll just take whatever address. The leftover dust amount will go to fee. change = self.wallet.get_change_addresses()[0] self.print_error("Scale {} Coin {} OutAddr {} {} {} make_protocol_thread".format(scale, utxo_name, output.to_storage_string(), "Change" if will_receive_change else "FakeChange",change.to_storage_string())) #self.print_error("Reserved addresses:", self.wallet._addresses_cashshuffle_reserved) ctimeout = 12.5 if (Network.get_instance() and Network.get_instance().get_proxies()) else 5.0 # allow for 12.5 second connection timeouts if using a proxy server thr = ProtocolThread(host=self.host, port=self.port, ssl=self.ssl, comm_timeout=self.timeout, ctimeout=ctimeout, # comm timeout and connect timeout coin=utxo_name, amount=scale, fee=self.FEE, total_amount=coin['value'], addr_new_addr=output, change_addr=change, fake_change=not will_receive_change, sk=id_sk, sks=sks, inputs=inputs, pubk=id_pub, logger=None) thr.logger = ChannelSendLambda(lambda msg: self.protocol_thread_callback(thr, msg)) self.threads[scale] = thr coins.remove(coin) thr.start() return True
def _make_protocol_thread(self, scale, coins, scale_lower_bound, scale_upper_bound): def get_coin_for_shuffling(scale, coins, scale_lower_bound, scale_upper_bound): upper_bound = min(scale_upper_bound, self.UPPER_BOUND) lower_bound = max(scale_lower_bound, self.LOWER_BOUND) unshuffled_coins_on_scale = [coin for coin in coins # exclude coins out of range and 'done' coins still in history # also exclude coinbase coins (see issue #64) if (coin['value'] < upper_bound and coin['value'] >= lower_bound) ] unshuffled_coins_on_scale.sort(key=lambda x: (x['value'], -x['height'])) # sort by value, preferring older coins on tied value if unshuffled_coins_on_scale: return unshuffled_coins_on_scale[-1] # take the largest,oldest on the scale return None # / coin = get_coin_for_shuffling(scale, coins, scale_lower_bound, scale_upper_bound) if not coin: return try: private_key = self.wallet.export_private_key(coin['address'], self.get_password()) except InvalidPassword: # This shouldn't normally happen but can if the user JUST changed their password in the GUI thread # and we didn't yet get informed of the new password. In which case we give up for now and 10 seconds later # (the next 'period' time), this coin will be picked up again. raise RuntimeWarning('Invalid Password caught when trying to export a private key -- if this keeps happening tell the devs!') utxo_name = CoinUtils.get_name(coin) self.wallet.set_frozen_coin_state([utxo_name], True) self._coins_busy_shuffling.add(utxo_name) self.wallet.storage.put(ConfKeys.PerWallet.COINS_FROZEN_BY_SHUFFLING, list(self._coins_busy_shuffling)) inputs = {} sks = {} public_key = self.wallet.get_public_key(coin['address']) sk = regenerate_key(deserialize_privkey(private_key)[1]) inputs[public_key] = [utxo_name] sks[public_key] = sk id_sk = self.generate_random_sk() id_pub = id_sk.GetPubKey(True).hex() output = self.wallet.cashshuffle_get_new_change_address(for_shufflethread=2) # Check if we will really use the change address. We definitely won't # be receving to it if the change is below dust threshold (see #67). # Furthermore, we may not receive change even if this check predicts we # will due to #68. may_receive_change = coin['value'] - scale - self.FEE >= dust_threshold(Network.get_instance()) if may_receive_change: # We anticipate (maybe) using the change address in the shuffle tx, # so reserve this address. Note that due to "smallest player raises # shuffle amount" rules in version=200+ (#68) we WON'T necessarily # USE this change address. (In that case it will be freed up later # after shuffling anyway so no address leaking occurs). # We just reserve it if we think we MAY need it. change = self.wallet.cashshuffle_get_new_change_address(for_shufflethread=1) else: # We *definitely* won't receive any change no matter who # participates because we are very close to scale. # (The leftover dust amount will go to fee.) # We still have to specify a change address to the protocol even if # it definitely won't be used. :/ # We'll just take a hard-coded address whose private key is the # number 1337 (we do it this way so we don't leak anything # identifying every time we shuffle). # Don't worry: It's 100% guaranteed we won't be using this address. change = self._dummy_address self.print_error("Scale {} Coin {} OutAddr {} {} {} make_protocol_thread".format(scale, utxo_name, output.to_storage_string(), "Change" if may_receive_change else "FakeChange", change.to_storage_string())) #self.print_error("Reserved addresses:", self.wallet._addresses_cashshuffle_reserved) ctimeout = 12.5 if (Network.get_instance() and Network.get_instance().get_proxies()) else 5.0 # allow for 12.5 second connection timeouts if using a proxy server thr = ProtocolThread(host=self.host, port=self.port, ssl=self.ssl, comm_timeout=self.timeout, ctimeout=ctimeout, # comm timeout and connect timeout coin=utxo_name, scale=scale, fee=self.FEE, coin_value=coin['value'], addr_new_addr=output, change_addr=change, sk=id_sk, sks=sks, inputs=inputs, pubk=id_pub, logger=None, version=self.version, typ=self.type) thr.logger = ChannelSendLambda(lambda msg: self.protocol_thread_callback(thr, msg)) cls = type(self) cls.latest_shuffle_settings = cls.ShuffleSettings(thr.type, Messages.TYPE_NAME_DICT[thr.type], thr.version, scale, coin['value'], self.FEE) self.threads[scale] = thr coins.remove(coin) thr.start() return True