commit 2ea59aad14cd3c22d13b003d550e86edb7a29c37
parent 61b69f38568aafcb17db711c92ec80e2eea21443
Author: ThomasV <thomasv@electrum.org>
Date: Wed, 29 Nov 2017 13:45:02 +0100
fix #3345: do not require a wallet in order to sweep
Diffstat:
4 files changed, 89 insertions(+), 88 deletions(-)
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
@@ -2364,9 +2364,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
address_e.textChanged.connect(on_address)
if not d.exec_():
return
-
+ from electrum.wallet import sweep
try:
- tx = self.wallet.sweep(get_pk(), self.network, self.config, get_address(), None)
+ tx = sweep(get_pk(), self.network, self.config, get_address(), None)
except BaseException as e:
self.show_message(str(e))
return
diff --git a/lib/commands.py b/lib/commands.py
@@ -383,16 +383,17 @@ class Commands:
raise BaseException('cannot verify alias', x)
return out['address']
- @command('nw')
+ @command('n')
def sweep(self, privkey, destination, fee=None, nocheck=False, imax=100):
"""Sweep private keys. Returns a transaction that spends UTXOs from
privkey to a destination address. The transaction is not
broadcasted."""
+ from .wallet import sweep
tx_fee = satoshis(fee)
privkeys = privkey.split()
self.nocheck = nocheck
- dest = self._resolver(destination)
- tx = self.wallet.sweep(privkeys, self.network, self.config, dest, tx_fee, imax)
+ #dest = self._resolver(destination)
+ tx = sweep(privkeys, self.network, self.config, destination, tx_fee, imax)
return tx.as_dict() if tx else None
@command('wp')
@@ -707,7 +708,7 @@ command_options = {
'nocheck': (None, "Do not verify aliases"),
'imax': (None, "Maximum number of inputs"),
'fee': ("-f", "Transaction fee (in BTC)"),
- 'from_addr': ("-F", "Source address. If it isn't in the wallet, it will ask for the private key unless supplied in the format public_key:private_key. It's not saved in the wallet."),
+ 'from_addr': ("-F", "Source address (must be a wallet address; use sweep to spend from non-wallet address)."),
'change_addr': ("-c", "Change address. Default is a spare address, or the source address if it's not in the wallet"),
'nbits': (None, "Number of bits of entropy"),
'entropy': (None, "Custom entropy"),
diff --git a/lib/simple_config.py b/lib/simple_config.py
@@ -249,6 +249,9 @@ class SimpleConfig(PrintError):
fee_rate = self.get('fee_per_kb', self.max_fee_rate()/2)
return fee_rate
+ def estimate_fee(self, size):
+ return int(self.fee_per_kb() * size / 1000.)
+
def update_fee_estimates(self, key, value):
self.fee_estimates[key] = value
self.fee_estimates_last_updated[key] = time.time()
diff --git a/lib/wallet.py b/lib/wallet.py
@@ -67,6 +67,81 @@ TX_STATUS = [
]
+
+def relayfee(network):
+ RELAY_FEE = 5000
+ MAX_RELAY_FEE = 50000
+ f = network.relay_fee if network and network.relay_fee else RELAY_FEE
+ return min(f, MAX_RELAY_FEE)
+
+def dust_threshold(network):
+ # Change <= dust threshold is added to the tx fee
+ return 182 * 3 * relayfee(network) / 1000
+
+
+def append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax):
+ if txin_type != 'p2pk':
+ address = bitcoin.pubkey_to_address(txin_type, pubkey)
+ sh = bitcoin.address_to_scripthash(address)
+ else:
+ script = bitcoin.public_key_to_p2pk_script(pubkey)
+ sh = bitcoin.script_to_scripthash(script)
+ address = '(pubkey)'
+ u = network.synchronous_get(('blockchain.scripthash.listunspent', [sh]))
+ for item in u:
+ if len(inputs) >= imax:
+ break
+ item['address'] = address
+ item['type'] = txin_type
+ item['prevout_hash'] = item['tx_hash']
+ item['prevout_n'] = item['tx_pos']
+ item['pubkeys'] = [pubkey]
+ item['x_pubkeys'] = [pubkey]
+ item['signatures'] = [None]
+ item['num_sig'] = 1
+ inputs.append(item)
+
+def sweep(privkeys, network, config, recipient, fee=None, imax=100):
+
+ def find_utxos_for_privkey(txin_type, privkey, compressed):
+ pubkey = bitcoin.public_key_from_private_key(privkey, compressed)
+ append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax)
+ keypairs[pubkey] = privkey, compressed
+ inputs = []
+ keypairs = {}
+ for sec in privkeys:
+ txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
+ find_utxos_for_privkey(txin_type, privkey, compressed)
+ # do other lookups to increase support coverage
+ if is_minikey(sec):
+ # minikeys don't have a compressed byte
+ # we lookup both compressed and uncompressed pubkeys
+ find_utxos_for_privkey(txin_type, privkey, not compressed)
+ elif txin_type == 'p2pkh':
+ # WIF serialization does not distinguish p2pkh and p2pk
+ # we also search for pay-to-pubkey outputs
+ find_utxos_for_privkey('p2pk', privkey, compressed)
+ if not inputs:
+ raise BaseException(_('No inputs found. (Note that inputs need to be confirmed)'))
+ total = sum(i.get('value') for i in inputs)
+ if fee is None:
+ outputs = [(TYPE_ADDRESS, recipient, total)]
+ tx = Transaction.from_io(inputs, outputs)
+ fee = config.estimate_fee(tx.estimated_size())
+ if total - fee < 0:
+ raise BaseException(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d'%(total, fee))
+ if total - fee < dust_threshold(network):
+ raise BaseException(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network)))
+
+ outputs = [(TYPE_ADDRESS, recipient, total - fee)]
+ locktime = network.get_local_height()
+
+ tx = Transaction.from_io(inputs, outputs, locktime=locktime)
+ tx.set_rbf(True)
+ tx.sign(keypairs)
+ return tx
+
+
class Abstract_Wallet(PrintError):
"""
Wallet classes are created to handle various address generation methods.
@@ -89,7 +164,6 @@ class Abstract_Wallet(PrintError):
self.multiple_change = storage.get('multiple_change', False)
self.labels = storage.get('labels', {})
self.frozen_addresses = set(storage.get('frozen_addresses',[]))
- self.stored_height = storage.get('stored_height', 0) # last known height (for offline mode)
self.history = storage.get('addr_history',{}) # address -> list(txid, height)
self.load_keystore()
@@ -316,7 +390,7 @@ class Abstract_Wallet(PrintError):
def get_local_height(self):
""" return last known height if we are offline """
- return self.network.get_local_height() if self.network else self.stored_height
+ return self.network.get_local_height() if self.network else self.storage.get('stored_height', 0)
def get_tx_height(self, tx_hash):
""" return the height and timestamp of a verified transaction. """
@@ -776,14 +850,10 @@ class Abstract_Wallet(PrintError):
return status, status_str
def relayfee(self):
- RELAY_FEE = 5000
- MAX_RELAY_FEE = 50000
- f = self.network.relay_fee if self.network and self.network.relay_fee else RELAY_FEE
- return min(f, MAX_RELAY_FEE)
+ return relayfee(self.network)
def dust_threshold(self):
- # Change <= dust threshold is added to the tx fee
- return 182 * 3 * self.relayfee() / 1000
+ return dust_threshold(self.network)
def make_unsigned_transaction(self, inputs, outputs, config, fixed_fee=None, change_addr=None):
# check outputs
@@ -826,7 +896,7 @@ class Abstract_Wallet(PrintError):
# Fee estimator
if fixed_fee is None:
- fee_estimator = partial(self.estimate_fee, config)
+ fee_estimator = self.config.estimate_fee
else:
fee_estimator = lambda size: fixed_fee
@@ -853,85 +923,12 @@ class Abstract_Wallet(PrintError):
run_hook('make_unsigned_transaction', self, tx)
return tx
- def estimate_fee(self, config, size):
- fee = int(config.fee_per_kb() * size / 1000.)
- return fee
-
def mktx(self, outputs, password, config, fee=None, change_addr=None, domain=None):
coins = self.get_spendable_coins(domain, config)
tx = self.make_unsigned_transaction(coins, outputs, config, fee, change_addr)
self.sign_transaction(tx, password)
return tx
- def _append_utxos_to_inputs(self, inputs, network, pubkey, txin_type, imax):
- if txin_type != 'p2pk':
- address = bitcoin.pubkey_to_address(txin_type, pubkey)
- sh = bitcoin.address_to_scripthash(address)
- else:
- script = bitcoin.public_key_to_p2pk_script(pubkey)
- sh = bitcoin.script_to_scripthash(script)
- address = '(pubkey)'
- u = network.synchronous_get(('blockchain.scripthash.listunspent', [sh]))
- for item in u:
- if len(inputs) >= imax:
- break
- item['address'] = address
- item['type'] = txin_type
- item['prevout_hash'] = item['tx_hash']
- item['prevout_n'] = item['tx_pos']
- item['pubkeys'] = [pubkey]
- item['x_pubkeys'] = [pubkey]
- item['signatures'] = [None]
- item['num_sig'] = 1
- inputs.append(item)
-
- def sweep(self, privkeys, network, config, recipient, fee=None, imax=100):
-
- def find_utxos_for_privkey(txin_type, privkey, compressed):
- pubkey = bitcoin.public_key_from_private_key(privkey, compressed)
- self._append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax)
- keypairs[pubkey] = privkey, compressed
-
- inputs = []
- keypairs = {}
- for sec in privkeys:
- txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
-
- find_utxos_for_privkey(txin_type, privkey, compressed)
-
- # do other lookups to increase support coverage
- if is_minikey(sec):
- # minikeys don't have a compressed byte
- # we lookup both compressed and uncompressed pubkeys
- find_utxos_for_privkey(txin_type, privkey, not compressed)
- elif txin_type == 'p2pkh':
- # WIF serialization does not distinguish p2pkh and p2pk
- # we also search for pay-to-pubkey outputs
- find_utxos_for_privkey('p2pk', privkey, compressed)
-
- if not inputs:
- raise BaseException(_('No inputs found. (Note that inputs need to be confirmed)'))
-
- total = sum(i.get('value') for i in inputs)
- if fee is None:
- outputs = [(TYPE_ADDRESS, recipient, total)]
- tx = Transaction.from_io(inputs, outputs)
- fee = self.estimate_fee(config, tx.estimated_size())
-
- if total - fee < 0:
- raise BaseException(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d'%(total, fee))
-
- if total - fee < self.dust_threshold():
- raise BaseException(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, self.dust_threshold()))
-
- outputs = [(TYPE_ADDRESS, recipient, total - fee)]
- locktime = self.get_local_height()
-
- tx = Transaction.from_io(inputs, outputs, locktime=locktime)
- tx.set_rbf(True)
- tx.sign(keypairs)
- return tx
-
def is_frozen(self, addr):
return addr in self.frozen_addresses