Browse Source

Merge branch 'master' of github.com:spesmilo/electrum

283
slush 12 years ago
parent
commit
36bc0184f4
  1. 12
      RELEASE-NOTES
  2. 5
      app.fil
  3. 2
      electrum
  4. 11
      gui/gui_classic.py
  5. 8
      gui/gui_gtk.py
  6. 4
      gui/gui_text.py
  7. 21
      lib/bitcoin.py
  8. 7
      lib/deserialize.py
  9. 11
      lib/verifier.py
  10. 4
      lib/version.py
  11. 94
      lib/wallet.py
  12. 3
      make_packages

12
RELEASE-NOTES

@ -1,3 +1,15 @@
# Release 1.7.2:
* Transactions that are in the same block are displayed in chronological order in the history.
* The client computes transaction priority and rejects zero-fee transactions that need a fee.
* The default fee was lowered to 200 uBTC per kb.
* Due to an internal format change, your history may be pruned when
you open your wallet for the first time after upgrading to 1.7.2. If
this is the case, please visit a full server to restore your full
history. You will only need to do that once.
# Release 1.7.1: bugfixes. # Release 1.7.1: bugfixes.

5
app.fil

@ -2,3 +2,8 @@ gui/gui_gtk.py
gui/gui_classic.py gui/gui_classic.py
gui/gui_lite.py gui/gui_lite.py
gui/history_widget.py gui/history_widget.py
plugins/aliases.py
plugins/pointofsale.py
plugins/labels.py
plugins/qrscanner.py
plugins/virtualkeyboard.py

2
electrum

@ -72,7 +72,7 @@ def arg_parser():
parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses") parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance of listed addresses") parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance of listed addresses")
parser.add_option("-l", "--labels", action="store_true", dest="show_labels", default=False, help="show the labels of listed addresses") parser.add_option("-l", "--labels", action="store_true", dest="show_labels", default=False, help="show the labels of listed addresses")
parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee") parser.add_option("-f", "--fee", dest="tx_fee", default=None, help="set tx fee")
parser.add_option("-F", "--fromaddr", dest="from_addr", default=None, help="set source address for payto/mktx. 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.") parser.add_option("-F", "--fromaddr", dest="from_addr", default=None, help="set source address for payto/mktx. 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.")
parser.add_option("-c", "--changeaddr", dest="change_addr", default=None, help="set the change address for payto/mktx. default is a spare address, or the source address if it's not in the wallet") parser.add_option("-c", "--changeaddr", dest="change_addr", default=None, help="set the change address for payto/mktx. default is a spare address, or the source address if it's not in the wallet")
parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h") parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")

11
gui/gui_classic.py

@ -32,6 +32,7 @@ from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui import PyQt4.QtGui as QtGui
from electrum.interface import DEFAULT_SERVERS from electrum.interface import DEFAULT_SERVERS
from electrum.bitcoin import MIN_RELAY_TX_FEE
try: try:
import icons_rc import icons_rc
@ -795,6 +796,10 @@ class ElectrumWindow(QMainWindow):
self.show_message(str(e)) self.show_message(str(e))
return return
if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
return
self.run_hook('send_tx', tx) self.run_hook('send_tx', tx)
if label: if label:
@ -1928,11 +1933,11 @@ class ElectrumWindow(QMainWindow):
fee_e = QLineEdit() fee_e = QLineEdit()
fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) ) fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
grid_wallet.addWidget(fee_e, 0, 2) grid_wallet.addWidget(fee_e, 0, 2)
msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \ msg = _('Fee per kilobyte of transaction.') + ' ' \
+ _('Recommended value') + ': 0.001' + _('Recommended value') + ': 0.0001'
grid_wallet.addWidget(HelpButton(msg), 0, 3) grid_wallet.addWidget(HelpButton(msg), 0, 3)
fee_e.textChanged.connect(lambda: numbify(fee_e,False)) fee_e.textChanged.connect(lambda: numbify(fee_e,False))
if not self.config.is_modifiable('fee'): if not self.config.is_modifiable('fee_per_kb'):
for w in [fee_e, fee_label]: w.setEnabled(False) for w in [fee_e, fee_label]: w.setEnabled(False)
usechange_label = QLabel(_('Use change addresses')) usechange_label = QLabel(_('Use change addresses'))

8
gui/gui_gtk.py

@ -35,6 +35,7 @@ MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monosp
from electrum.util import format_satoshis from electrum.util import format_satoshis
from electrum.interface import DEFAULT_SERVERS from electrum.interface import DEFAULT_SERVERS
from electrum.bitcoin import MIN_RELAY_TX_FEE
def numbify(entry, is_int = False): def numbify(entry, is_int = False):
text = entry.get_text().strip() text = entry.get_text().strip()
@ -198,7 +199,7 @@ def run_settings_dialog(wallet, parent):
fee_entry.connect('changed', numbify, False) fee_entry.connect('changed', numbify, False)
fee_entry.show() fee_entry.show()
fee.pack_start(fee_entry,False,False, 10) fee.pack_start(fee_entry,False,False, 10)
add_help_button(fee, 'Fee per transaction input. Transactions involving multiple inputs tend to have a higher fee. Recommended value:0.0005') add_help_button(fee, 'Fee per kilobyte of transaction. Recommended value:0.0001')
fee.show() fee.show()
vbox.pack_start(fee, False,False, 5) vbox.pack_start(fee, False,False, 5)
@ -844,6 +845,11 @@ class ElectrumWindow:
self.show_message(str(e)) self.show_message(str(e))
return return
if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
self.show_message( "This transaction requires a higher fee, or it will not be propagated by the network." )
return
if label: if label:
self.wallet.labels[tx.hash()] = label self.wallet.labels[tx.hash()] = label

4
gui/gui_text.py

@ -318,14 +318,14 @@ class ElectrumGui:
def settings_dialog(self): def settings_dialog(self):
out = self.run_dialog('Settings', [ out = self.run_dialog('Settings', [
{'label':'Default GUI', 'type':'list', 'choices':['classic','lite','gtk','text'], 'value':self.config.get('gui')}, {'label':'Default GUI', 'type':'list', 'choices':['classic','lite','gtk','text'], 'value':self.config.get('gui')},
{'label':'Default fee', 'type':'satoshis', 'value': format_satoshis(self.config.get('fee')).strip() } {'label':'Default fee', 'type':'satoshis', 'value': format_satoshis(self.config.get('fee_per_kb')).strip() }
], buttons = 1) ], buttons = 1)
if out: if out:
if out.get('Default GUI'): if out.get('Default GUI'):
self.config.set_key('gui', out['Default GUI'], True) self.config.set_key('gui', out['Default GUI'], True)
if out.get('Default fee'): if out.get('Default fee'):
fee = int ( Decimal( out['Default fee']) *10000000 ) fee = int ( Decimal( out['Default fee']) *10000000 )
self.config.set_key('fee', fee, True) self.config.set_key('fee_per_kb', fee, True)
def password_dialog(self): def password_dialog(self):

21
lib/bitcoin.py

@ -577,6 +577,7 @@ class BIP32Sequence:
################################## transactions ################################## transactions
MIN_RELAY_TX_FEE = 10000
class Transaction: class Transaction:
@ -876,6 +877,26 @@ class Transaction:
return out return out
def requires_fee(self, verifier):
# see https://en.bitcoin.it/wiki/Transaction_fees
threshold = 57600000
size = len(self.raw)/2
if size >= 10000:
return True
for o in self.outputs:
value = o[1]
if value < 1000000:
return True
sum = 0
for i in self.inputs:
age = verifier.get_confirmations(i["tx_hash"])[0]
sum += i["value"] * age
priority = sum / size
print_error(priority, threshold)
return priority < threshold
def test_bip32(): def test_bip32():

7
lib/deserialize.py

@ -323,7 +323,12 @@ def match_decoded(decoded, to_match):
return True return True
def get_address_from_input_script(bytes): def get_address_from_input_script(bytes):
decoded = [ x for x in script_GetOp(bytes) ] try:
decoded = [ x for x in script_GetOp(bytes) ]
except:
# coinbase transactions raise an exception
print_error("cannot find address in input script", bytes.encode('hex'))
return [], [], "(None)"
# non-generated TxIn transactions push a signature # non-generated TxIn transactions push a signature
# (seventy-something bytes) and then their public key # (seventy-something bytes) and then their public key

11
lib/verifier.py

@ -35,7 +35,7 @@ class WalletVerifier(threading.Thread):
self.transactions = {} # requested verifications (with height sent by the requestor) self.transactions = {} # requested verifications (with height sent by the requestor)
self.interface.register_channel('verifier') self.interface.register_channel('verifier')
self.verified_tx = config.get('verified_tx2',{}) # height, timestamp of verified transactions self.verified_tx = config.get('verified_tx3',{}) # height, timestamp of verified transactions
self.merkle_roots = config.get('merkle_roots',{}) # hashed by me self.merkle_roots = config.get('merkle_roots',{}) # hashed by me
self.targets = config.get('targets',{}) # compute targets self.targets = config.get('targets',{}) # compute targets
@ -50,7 +50,7 @@ class WalletVerifier(threading.Thread):
""" return the number of confirmations of a monitored transaction. """ """ return the number of confirmations of a monitored transaction. """
with self.lock: with self.lock:
if tx in self.verified_tx: if tx in self.verified_tx:
height, timestamp = self.verified_tx[tx] height, timestamp, pos = self.verified_tx[tx]
conf = (self.local_height - height + 1) conf = (self.local_height - height + 1)
else: else:
conf = 0 conf = 0
@ -183,7 +183,8 @@ class WalletVerifier(threading.Thread):
def verify_merkle(self, tx_hash, result): def verify_merkle(self, tx_hash, result):
tx_height = result.get('block_height') tx_height = result.get('block_height')
self.merkle_roots[tx_hash] = self.hash_merkle_root(result['merkle'], tx_hash, result.get('pos')) pos = result.get('pos')
self.merkle_roots[tx_hash] = self.hash_merkle_root(result['merkle'], tx_hash, pos)
header = self.read_header(tx_height) header = self.read_header(tx_height)
if not header: return if not header: return
assert header.get('merkle_root') == self.merkle_roots[tx_hash] assert header.get('merkle_root') == self.merkle_roots[tx_hash]
@ -191,9 +192,9 @@ class WalletVerifier(threading.Thread):
header = self.read_header(tx_height) header = self.read_header(tx_height)
timestamp = header.get('timestamp') timestamp = header.get('timestamp')
with self.lock: with self.lock:
self.verified_tx[tx_hash] = (tx_height, timestamp) self.verified_tx[tx_hash] = (tx_height, timestamp, pos)
print_error("verified %s"%tx_hash) print_error("verified %s"%tx_hash)
self.config.set_key('verified_tx2', self.verified_tx, True) self.config.set_key('verified_tx3', self.verified_tx, True)
self.interface.trigger_callback('updated') self.interface.trigger_callback('updated')

4
lib/version.py

@ -1,4 +1,4 @@
ELECTRUM_VERSION = "1.7.1" # version of the client package ELECTRUM_VERSION = "1.7.2" # version of the client package
PROTOCOL_VERSION = '0.6' # protocol version requested PROTOCOL_VERSION = '0.6' # protocol version requested
SEED_VERSION = 4 # bump this every time the seed generation is modified SEED_VERSION = 4 # bump this every time the seed generation is modified
TRANSLATION_ID = 3992 # version of the wiki page TRANSLATION_ID = 4012 # version of the wiki page

94
lib/wallet.py

@ -74,7 +74,7 @@ class Wallet:
self.seed_version = config.get('seed_version', SEED_VERSION) self.seed_version = config.get('seed_version', SEED_VERSION)
self.gap_limit = config.get('gap_limit', 5) self.gap_limit = config.get('gap_limit', 5)
self.use_change = config.get('use_change',True) self.use_change = config.get('use_change',True)
self.fee = int(config.get('fee',100000)) self.fee = int(config.get('fee_per_kb',20000))
self.num_zeros = int(config.get('num_zeros',0)) self.num_zeros = int(config.get('num_zeros',0))
self.use_encryption = config.get('use_encryption', False) self.use_encryption = config.get('use_encryption', False)
self.seed = config.get('seed', '') # encrypted self.seed = config.get('seed', '') # encrypted
@ -103,7 +103,6 @@ class Wallet:
# not saved # not saved
self.prevout_values = {} # my own transaction outputs self.prevout_values = {} # my own transaction outputs
self.spent_outputs = [] self.spent_outputs = []
self.receipt = None # next receipt
# spv # spv
self.verifier = None self.verifier = None
@ -114,6 +113,7 @@ class Wallet:
self.up_to_date = False self.up_to_date = False
self.lock = threading.Lock() self.lock = threading.Lock()
self.transaction_lock = threading.Lock()
self.tx_event = threading.Event() self.tx_event = threading.Event()
if self.seed_version != SEED_VERSION: if self.seed_version != SEED_VERSION:
@ -429,13 +429,10 @@ class Wallet:
def update_tx_outputs(self, tx_hash): def update_tx_outputs(self, tx_hash):
tx = self.transactions.get(tx_hash) tx = self.transactions.get(tx_hash)
i = 0
for item in tx.outputs: for i, (addr, value) in enumerate(tx.outputs):
addr, value = item
key = tx_hash+ ':%d'%i key = tx_hash+ ':%d'%i
with self.lock: self.prevout_values[key] = value
self.prevout_values[key] = value
i += 1
for item in tx.inputs: for item in tx.inputs:
if self.is_mine(item.get('address')): if self.is_mine(item.get('address')):
@ -453,13 +450,11 @@ class Wallet:
for tx_hash, tx_height in h: for tx_hash, tx_height in h:
tx = self.transactions.get(tx_hash) tx = self.transactions.get(tx_hash)
if not tx: continue if not tx: continue
i = 0
for item in tx.outputs: for i, (addr, value) in enumerate(tx.outputs):
addr, value = item
if addr == address: if addr == address:
key = tx_hash + ':%d'%i key = tx_hash + ':%d'%i
received_coins.append(key) received_coins.append(key)
i +=1
for tx_hash, tx_height in h: for tx_hash, tx_height in h:
tx = self.transactions.get(tx_hash) tx = self.transactions.get(tx_hash)
@ -474,13 +469,10 @@ class Wallet:
if key in received_coins: if key in received_coins:
v -= value v -= value
i = 0 for i, (addr, value) in enumerate(tx.outputs):
for item in tx.outputs:
addr, value = item
key = tx_hash + ':%d'%i key = tx_hash + ':%d'%i
if addr == address: if addr == address:
v += value v += value
i += 1
if tx_height: if tx_height:
c += v c += v
@ -565,14 +557,20 @@ class Wallet:
total += v total += v
inputs.append( item ) inputs.append( item )
fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee if fixed_fee is None:
estimated_size = len(inputs) * 180 + 80 # this assumes non-compressed keys
fee = self.fee * int(round(estimated_size/1024.))
if fee == 0: fee = self.fee
else:
fee = fixed_fee
if total >= amount + fee: break if total >= amount + fee: break
else: else:
#print "not enough funds: %s %s"%(format_satoshis(total), format_satoshis(fee))
inputs = [] inputs = []
return inputs, total, fee return inputs, total, fee
def add_tx_change( self, outputs, amount, fee, total, change_addr=None ): def add_tx_change( self, outputs, amount, fee, total, change_addr=None ):
change_amount = total - ( amount + fee ) change_amount = total - ( amount + fee )
if change_amount != 0: if change_amount != 0:
@ -602,19 +600,17 @@ class Wallet:
def receive_tx_callback(self, tx_hash, tx, tx_height): def receive_tx_callback(self, tx_hash, tx, tx_height):
if not self.check_new_tx(tx_hash, tx): if not self.check_new_tx(tx_hash, tx):
# may happen due to pruning # may happen due to pruning
print_error("received transaction that is no longer referenced in history", tx_hash) print_error("received transaction that is no longer referenced in history", tx_hash)
return return
with self.lock: with self.transaction_lock:
self.transactions[tx_hash] = tx self.transactions[tx_hash] = tx
if self.verifier and tx_height>0:
#tx_height = tx.get('height') self.verifier.add(tx_hash, tx_height)
if self.verifier and tx_height>0: self.update_tx_outputs(tx_hash)
self.verifier.add(tx_hash, tx_height)
self.update_tx_outputs(tx_hash)
self.save() self.save()
@ -636,29 +632,29 @@ class Wallet:
def get_tx_history(self): def get_tx_history(self):
with self.lock: with self.transaction_lock:
history = self.transactions.items() history = self.transactions.items()
history.sort(key = lambda x: self.verifier.get_height(x[0]) if self.verifier.get_height(x[0]) else 1e12) history.sort(key = lambda x: self.verifier.verified_tx.get(x[0]) if self.verifier.verified_tx.get(x[0]) else (1e12,0,0))
result = [] result = []
balance = 0 balance = 0
for tx_hash, tx in history: for tx_hash, tx in history:
is_mine, v, fee = self.get_tx_value(tx) is_mine, v, fee = self.get_tx_value(tx)
if v is not None: balance += v if v is not None: balance += v
c, u = self.get_balance() c, u = self.get_balance()
if balance != c+u: if balance != c+u:
v_str = format_satoshis( c+u - balance, True, self.num_zeros) #v_str = format_satoshis( c+u - balance, True, self.num_zeros)
result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) ) result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
balance = c + u - balance balance = c + u - balance
for tx_hash, tx in history: for tx_hash, tx in history:
conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None) conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
is_mine, value, fee = self.get_tx_value(tx) is_mine, value, fee = self.get_tx_value(tx)
if value is not None: if value is not None:
balance += value balance += value
result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) ) result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
return result return result
@ -768,9 +764,6 @@ class Wallet:
out = self.tx_result out = self.tx_result
if out != tx_hash: if out != tx_hash:
return False, "error: " + out return False, "error: " + out
if self.receipt:
self.receipts[tx_hash] = self.receipt
self.receipt = None
return True, out return True, out
@ -831,7 +824,7 @@ class Wallet:
s = { s = {
'use_encryption': self.use_encryption, 'use_encryption': self.use_encryption,
'use_change': self.use_change, 'use_change': self.use_change,
'fee': self.fee, 'fee_per_kb': self.fee,
'accounts': self.accounts, 'accounts': self.accounts,
'addr_history': self.history, 'addr_history': self.history,
'labels': self.labels, 'labels': self.labels,
@ -859,6 +852,12 @@ class Wallet:
self.verifier.add(tx_hash, tx_height) self.verifier.add(tx_hash, tx_height)
# if we are on a pruning server, remove unverified transactions
vr = self.verifier.transactions.keys() + self.verifier.verified_tx.keys()
for tx_hash in self.transactions.keys():
if tx_hash not in vr:
self.transactions.pop(tx_hash)
def check_new_history(self, addr, hist): def check_new_history(self, addr, hist):
@ -903,6 +902,7 @@ class Wallet:
ext_requests.append( ('blockchain.address.get_history', [_addr]) ) ext_requests.append( ('blockchain.address.get_history', [_addr]) )
ext_h = self.interface.synchronous_get(ext_requests) ext_h = self.interface.synchronous_get(ext_requests)
print_error("sync:", ext_requests, ext_h)
height = None height = None
for h in ext_h: for h in ext_h:
if h == ['*']: continue if h == ['*']: continue

3
make_packages

@ -25,7 +25,8 @@ if __name__ == '__main__':
shutil.copytree("aes",'dist/e4a-%s/aes'%version) shutil.copytree("aes",'dist/e4a-%s/aes'%version)
shutil.copytree("lib",'dist/e4a-%s/lib'%version) shutil.copytree("lib",'dist/e4a-%s/lib'%version)
os.mkdir('dist/e4a-%s/gui'%version) os.mkdir('dist/e4a-%s/gui'%version)
shutil.copy("gui/gui_android.py",'dist/e4a-%s/gui'%version) for n in ['gui_android.py', 'pyqrnative.py', 'bmp.py']:
shutil.copy("gui/%s"%n,'dist/e4a-%s/gui'%version)
open('dist/e4a-%s/gui/__init__.py'%version,'w').close() open('dist/e4a-%s/gui/__init__.py'%version,'w').close()
os.chdir("dist") os.chdir("dist")

Loading…
Cancel
Save