Merge commit '82e88cb' into test

This commit is contained in:
Jon Layton 2018-02-24 20:21:58 -06:00
commit 5325f842ab
16 changed files with 114 additions and 37 deletions

View File

@ -1,3 +1,8 @@
# Release 3.0.6 :
* Fix transaction parsing bug #3788
# Release 3.0.5 : (Security update) # Release 3.0.5 : (Security update)
This is a follow-up to the 3.0.4 release, which did not completely fix This is a follow-up to the 3.0.4 release, which did not completely fix

View File

@ -39,10 +39,7 @@ done
popd popd
pushd electrum pushd electrum
if [ ! -z "$1" ]; then git checkout $BRANCH
git checkout $1
fi
VERSION=`git describe --tags` VERSION=`git describe --tags`
echo "Last commit: $VERSION" echo "Last commit: $VERSION"
find -exec touch -d '2000-11-11T11:11:11+00:00' {} + find -exec touch -d '2000-11-11T11:11:11+00:00' {} +

View File

@ -23,6 +23,6 @@ cd tmp
$PYTHON -m pip install setuptools --upgrade $PYTHON -m pip install setuptools --upgrade
$PYTHON -m pip install cython --upgrade $PYTHON -m pip install cython --upgrade
$PYTHON -m pip install trezor==0.7.16 --upgrade $PYTHON -m pip install trezor==0.7.16 --upgrade
$PYTHON -m pip install keepkey==4.0.0 --upgrade $PYTHON -m pip install keepkey==4.0.2 --upgrade
$PYTHON -m pip install btchip-python==0.1.23 --upgrade $PYTHON -m pip install btchip-python==0.1.24 --upgrade

View File

@ -281,7 +281,8 @@ def run_offline_command(config, config_options):
# arguments passed to function # arguments passed to function
args = [config.get(x) for x in cmd.params] args = [config.get(x) for x in cmd.params]
# decode json arguments # decode json arguments
args = list(map(json_decode, args)) if cmdname not in ('setconfig',):
args = list(map(json_decode, args))
# options # options
kwargs = {} kwargs = {}
for x in cmd.options: for x in cmd.options:

View File

@ -928,6 +928,10 @@ class ElectrumWindow(App):
return return
if not self.wallet.can_export(): if not self.wallet.can_export():
return return
key = str(self.wallet.export_private_key(addr, password)[0]) try:
pk_label.data = key key = str(self.wallet.export_private_key(addr, password)[0])
pk_label.data = key
except InvalidPassword:
self.show_error("Invalid PIN")
return
self.protected(_("Enter your PIN code in order to decrypt your private key"), show_private_key, (addr, pk_label)) self.protected(_("Enter your PIN code in order to decrypt your private key"), show_private_key, (addr, pk_label))

View File

@ -2666,7 +2666,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
unit_combo = QComboBox() unit_combo = QComboBox()
unit_combo.addItems(units) unit_combo.addItems(units)
unit_combo.setCurrentIndex(units.index(self.base_unit())) unit_combo.setCurrentIndex(units.index(self.base_unit()))
def on_unit(x): def on_unit(x, nz):
unit_result = units[unit_combo.currentIndex()] unit_result = units[unit_combo.currentIndex()]
if self.base_unit() == unit_result: if self.base_unit() == unit_result:
return return
@ -2681,13 +2681,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
else: else:
raise Exception('Unknown base unit') raise Exception('Unknown base unit')
self.config.set_key('decimal_point', self.decimal_point, True) self.config.set_key('decimal_point', self.decimal_point, True)
nz.setMaximum(self.decimal_point)
self.history_list.update() self.history_list.update()
self.request_list.update() self.request_list.update()
self.address_list.update() self.address_list.update()
for edit, amount in zip(edits, amounts): for edit, amount in zip(edits, amounts):
edit.setAmount(amount) edit.setAmount(amount)
self.update_status() self.update_status()
unit_combo.currentIndexChanged.connect(on_unit) unit_combo.currentIndexChanged.connect(lambda x: on_unit(x, nz))
gui_widgets.append((unit_label, unit_combo)) gui_widgets.append((unit_label, unit_combo))
block_explorers = sorted(util.block_explorer_info().keys()) block_explorers = sorted(util.block_explorer_info().keys())

View File

@ -59,11 +59,7 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
data = '' data = ''
if not data: if not data:
data = '' data = ''
if self.allow_multi: self.setText(data)
new_text = self.text() + data + '\n'
else:
new_text = data
self.setText(new_text)
return data return data
def contextMenuEvent(self, e): def contextMenuEvent(self, e):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -34,7 +34,7 @@ from functools import wraps
from decimal import Decimal from decimal import Decimal
from .import util from .import util
from .util import bfh, bh2u, format_satoshis from .util import bfh, bh2u, format_satoshis, json_decode
from .import bitcoin from .import bitcoin
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
from .i18n import _ from .i18n import _
@ -151,10 +151,8 @@ class Commands:
@command('') @command('')
def setconfig(self, key, value): def setconfig(self, key, value):
"""Set a configuration variable. 'value' may be a string or a Python expression.""" """Set a configuration variable. 'value' may be a string or a Python expression."""
try: if key not in ('rpcuser', 'rpcpassword'):
value = ast.literal_eval(value) value = json_decode(value)
except:
pass
self.config.set_key(key, value) self.config.set_key(key, value)
return True return True

View File

@ -5,6 +5,7 @@ import sys
from threading import Thread from threading import Thread
import time import time
import csv import csv
import decimal
from decimal import Decimal from decimal import Decimal
from .bitcoin import COIN from .bitcoin import COIN
@ -165,7 +166,11 @@ class FxThread(ThreadJob):
def ccy_amount_str(self, amount, commas): def ccy_amount_str(self, amount, commas):
prec = CCY_PRECISIONS.get(self.ccy, 2) prec = CCY_PRECISIONS.get(self.ccy, 2)
fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec)) fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec))
return fmt_str.format(round(amount, prec)) try:
rounded_amount = round(amount, prec)
except decimal.InvalidOperation:
rounded_amount = amount
return fmt_str.format(rounded_amount)
def run(self): def run(self):
# This runs from the plugins thread which catches exceptions # This runs from the plugins thread which catches exceptions

View File

@ -231,6 +231,10 @@ class TestTransaction(unittest.TestCase):
tx = transaction.Transaction('010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000') tx = transaction.Transaction('010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000')
self.assertEqual('51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e', tx.txid()) self.assertEqual('51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e', tx.txid())
def test_txid_input_p2wsh_p2sh_not_multisig(self):
tx = transaction.Transaction('0100000000010160f84fdcda039c3ca1b20038adea2d49a53db92f7c467e8def13734232bb610804000000232200202814720f16329ab81cb8867c4d447bd13255931f23e6655944c9ada1797fcf88ffffffff0ba3dcfc04000000001976a91488124a57c548c9e7b1dd687455af803bd5765dea88acc9f44900000000001976a914da55045a0ccd40a56ce861946d13eb861eb5f2d788ac49825e000000000017a914ca34d4b190e36479aa6e0023cfe0a8537c6aa8dd87680c0d00000000001976a914651102524c424b2e7c44787c4f21e4c54dffafc088acf02fa9000000000017a914ee6c596e6f7066466d778d4f9ba633a564a6e95d874d250900000000001976a9146ca7976b48c04fd23867748382ee8401b1d27c2988acf5119600000000001976a914cf47d5dcdba02fd547c600697097252d38c3214a88ace08a12000000000017a914017bef79d92d5ec08c051786bad317e5dd3befcf87e3d76201000000001976a9148ec1b88b66d142bcbdb42797a0fd402c23e0eec288ac718f6900000000001976a914e66344472a224ce6f843f2989accf435ae6a808988ac65e51300000000001976a914cad6717c13a2079066f876933834210ebbe68c3f88ac0347304402201a4907c4706104320313e182ecbb1b265b2d023a79586671386de86bb47461590220472c3db9fc99a728ebb9b555a72e3481d20b181bd059a9c1acadfb853d90c96c01210338a46f2a54112fef8803c8478bc17e5f8fc6a5ec276903a946c1fafb2e3a8b181976a914eda8660085bf607b82bd18560ca8f3a9ec49178588ac00000000')
self.assertEqual('e9933221a150f78f9f224899f8568ff6422ffcc28ca3d53d87936368ff7c4b1d', tx.txid())
class NetworkMock(object): class NetworkMock(object):

View File

@ -45,6 +45,14 @@ class SerializationError(Exception):
""" Thrown when there's a problem deserializing or serializing """ """ Thrown when there's a problem deserializing or serializing """
class UnknownTxinType(Exception):
pass
class NotRecognizedRedeemScript(Exception):
pass
class BCDataStream(object): class BCDataStream(object):
def __init__(self): def __init__(self):
self.input = None self.input = None
@ -302,10 +310,23 @@ def parse_scriptSig(d, _bytes):
if match_decoded(decoded, match): if match_decoded(decoded, match):
item = decoded[0][1] item = decoded[0][1]
if item[0] == 0: if item[0] == 0:
# segwit embedded into p2sh
# witness version 0
# segwit embedded into p2sh
d['address'] = bitcoin.hash160_to_p2sh(bitcoin.hash_160(item)) d['address'] = bitcoin.hash160_to_p2sh(bitcoin.hash_160(item))
d['type'] = 'p2wpkh-p2sh' if len(item) == 22 else 'p2wsh-p2sh' if len(item) == 22:
d['type'] = 'p2wpkh-p2sh'
elif len(item) == 34:
d['type'] = 'p2wsh-p2sh'
else:
print_error("unrecognized txin type", bh2u(item))
elif opcodes.OP_1 <= item[0] <= opcodes.OP_16:
# segwit embedded into p2sh
# witness version 1-16
pass
else: else:
# payto_pubkey # assert item[0] == 0x30
# pay-to-pubkey
d['type'] = 'p2pk' d['type'] = 'p2pk'
d['address'] = "(pubkey)" d['address'] = "(pubkey)"
d['signatures'] = [bh2u(item)] d['signatures'] = [bh2u(item)]
@ -361,7 +382,7 @@ def parse_redeemScript(s):
match_multisig = [ op_m ] + [opcodes.OP_PUSHDATA4]*n + [ op_n, opcodes.OP_CHECKMULTISIG ] match_multisig = [ op_m ] + [opcodes.OP_PUSHDATA4]*n + [ op_n, opcodes.OP_CHECKMULTISIG ]
if not match_decoded(dec2, match_multisig): if not match_decoded(dec2, match_multisig):
print_error("cannot find address in input script", bh2u(s)) print_error("cannot find address in input script", bh2u(s))
return raise NotRecognizedRedeemScript()
x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]] x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]]
pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys] pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys]
redeemScript = multisig_script(pubkeys, m) redeemScript = multisig_script(pubkeys, m)
@ -430,21 +451,40 @@ def parse_witness(vds, txin):
if n == 0xffffffff: if n == 0xffffffff:
txin['value'] = vds.read_uint64() txin['value'] = vds.read_uint64()
n = vds.read_compact_size() n = vds.read_compact_size()
# now 'n' is the number of items in the witness
w = list(bh2u(vds.read_bytes(vds.read_compact_size())) for i in range(n)) w = list(bh2u(vds.read_bytes(vds.read_compact_size())) for i in range(n))
add_w = lambda x: var_int(len(x) // 2) + x
txin['witness'] = var_int(n) + ''.join(add_w(i) for i in w)
# FIXME: witness version > 0 will probably fail here.
# For native segwit, we would need the scriptPubKey of the parent txn
# to determine witness program version, and properly parse the witness.
# In case of p2sh-segwit, we can tell based on the scriptSig in this txn.
# The code below assumes witness version 0.
# p2sh-segwit should work in that case; for native segwit we need to tell
# between p2wpkh and p2wsh; we do this based on number of witness items,
# hence (FIXME) p2wsh with n==2 (maybe n==1 ?) will probably fail.
# If v==0 and n==2, we need parent scriptPubKey to distinguish between p2wpkh and p2wsh.
if txin['type'] == 'coinbase': if txin['type'] == 'coinbase':
pass pass
elif n > 2: elif txin['type'] == 'p2wsh-p2sh' or n > 2:
try:
m, n, x_pubkeys, pubkeys, witnessScript = parse_redeemScript(bfh(w[-1]))
except NotRecognizedRedeemScript:
raise UnknownTxinType()
txin['signatures'] = parse_sig(w[1:-1]) txin['signatures'] = parse_sig(w[1:-1])
m, n, x_pubkeys, pubkeys, witnessScript = parse_redeemScript(bfh(w[-1]))
txin['num_sig'] = m txin['num_sig'] = m
txin['x_pubkeys'] = x_pubkeys txin['x_pubkeys'] = x_pubkeys
txin['pubkeys'] = pubkeys txin['pubkeys'] = pubkeys
txin['witnessScript'] = witnessScript txin['witnessScript'] = witnessScript
else: elif txin['type'] == 'p2wpkh-p2sh' or n == 2:
txin['num_sig'] = 1 txin['num_sig'] = 1
txin['x_pubkeys'] = [w[1]] txin['x_pubkeys'] = [w[1]]
txin['pubkeys'] = [safe_parse_pubkey(w[1])] txin['pubkeys'] = [safe_parse_pubkey(w[1])]
txin['signatures'] = parse_sig([w[0]]) txin['signatures'] = parse_sig([w[0]])
else:
raise UnknownTxinType()
def parse_output(vds, i): def parse_output(vds, i):
d = {} d = {}
@ -466,6 +506,23 @@ def deserialize(raw):
d['inputs'] = [parse_input(vds) for i in range(n_vin)] d['inputs'] = [parse_input(vds) for i in range(n_vin)]
n_vout = vds.read_compact_size() n_vout = vds.read_compact_size()
d['outputs'] = [parse_output(vds, i) for i in range(n_vout)] d['outputs'] = [parse_output(vds, i) for i in range(n_vout)]
if is_segwit:
for i in range(n_vin):
txin = d['inputs'][i]
try:
parse_witness(vds, txin)
except UnknownTxinType:
txin['type'] = 'unknown'
# FIXME: GUI might show 'unknown' address (e.g. for a non-multisig p2wsh)
continue
# segwit-native script
if not txin.get('scriptSig'):
if txin['num_sig'] == 1:
txin['type'] = 'p2wpkh'
txin['address'] = bitcoin.public_key_to_p2wpkh(bfh(txin['pubkeys'][0]))
else:
txin['type'] = 'p2wsh'
txin['address'] = bitcoin.script_to_p2wsh(txin['witnessScript'])
d['lockTime'] = vds.read_uint32() d['lockTime'] = vds.read_uint32()
return d return d
@ -657,7 +714,9 @@ class Transaction:
witness_script = multisig_script(pubkeys, txin['num_sig']) witness_script = multisig_script(pubkeys, txin['num_sig'])
witness = var_int(n) + '00' + ''.join(add_w(x) for x in sig_list) + add_w(witness_script) witness = var_int(n) + '00' + ''.join(add_w(x) for x in sig_list) + add_w(witness_script)
else: else:
raise BaseException('wrong txin type') witness = txin.get('witness', None)
if not witness:
raise BaseException('wrong txin type:', txin['type'])
if self.is_txin_complete(txin) or estimate_size: if self.is_txin_complete(txin) or estimate_size:
value_field = '' value_field = ''
else: else:
@ -666,7 +725,8 @@ class Transaction:
@classmethod @classmethod
def is_segwit_input(cls, txin): def is_segwit_input(cls, txin):
return cls.is_segwit_inputtype(txin['type']) has_nonzero_witness = txin.get('witness', '00') != '00'
return cls.is_segwit_inputtype(txin['type']) or has_nonzero_witness
@classmethod @classmethod
def is_segwit_inputtype(cls, txin_type): def is_segwit_inputtype(cls, txin_type):

View File

@ -347,7 +347,7 @@ def format_satoshis_plain(x, decimal_point = 8):
def format_satoshis(x, is_diff=False, num_zeros = 0, decimal_point = 8, whitespaces=False): def format_satoshis(x, is_diff=False, num_zeros = 0, decimal_point = 8, whitespaces=False):
from locale import localeconv from locale import localeconv
if x is None: if x is None:
return 'Unknown' return 'unknown'
x = int(x) # Some callers pass Decimal x = int(x) # Some callers pass Decimal
scale_factor = pow (10, decimal_point) scale_factor = pow (10, decimal_point)
integer_part = "{:n}".format(int(abs(x) / scale_factor)) integer_part = "{:n}".format(int(abs(x) / scale_factor))

View File

@ -367,7 +367,8 @@ class Abstract_Wallet(PrintError):
def add_unverified_tx(self, tx_hash, tx_height): def add_unverified_tx(self, tx_hash, tx_height):
if tx_height == 0 and tx_hash in self.verified_tx: if tx_height == 0 and tx_hash in self.verified_tx:
self.verified_tx.pop(tx_hash) self.verified_tx.pop(tx_hash)
self.verifier.merkle_roots.pop(tx_hash, None) if self.verifier:
self.verifier.merkle_roots.pop(tx_hash, None)
# tx will be verified only if height > 0 # tx will be verified only if height > 0
if tx_hash not in self.verified_tx: if tx_hash not in self.verified_tx:

View File

@ -84,7 +84,8 @@ class WsClientThread(util.DaemonThread):
l = self.subscriptions.get(addr, []) l = self.subscriptions.get(addr, [])
l.append((ws, amount)) l.append((ws, amount))
self.subscriptions[addr] = l self.subscriptions[addr] = l
self.network.send([('blockchain.address.subscribe', [addr])], self.response_queue.put) h = self.network.addr_to_scripthash(addr)
self.network.send([('blockchain.scripthash.subscribe', [h])], self.response_queue.put)
def run(self): def run(self):
@ -100,10 +101,13 @@ class WsClientThread(util.DaemonThread):
result = r.get('result') result = r.get('result')
if result is None: if result is None:
continue continue
if method == 'blockchain.address.subscribe': if method == 'blockchain.scripthash.subscribe':
self.network.send([('blockchain.address.get_balance', params)], self.response_queue.put) self.network.send([('blockchain.scripthash.get_balance', params)], self.response_queue.put)
elif method == 'blockchain.address.get_balance': elif method == 'blockchain.scripthash.get_balance':
addr = params[0] h = params[0]
addr = self.network.h2addr.get(h, None)
if addr is None:
util.print_error("can't find address for scripthash: %s" % h)
l = self.subscriptions.get(addr, []) l = self.subscriptions.get(addr, [])
for ws, amount in l: for ws, amount in l:
if not ws.closed: if not ws.closed:

View File

@ -60,6 +60,7 @@ class Ledger_Client():
def versiontuple(self, v): def versiontuple(self, v):
return tuple(map(int, (v.split(".")))) return tuple(map(int, (v.split("."))))
def test_pin_unlocked(func): def test_pin_unlocked(func):
"""Function decorator to test the Ledger for being unlocked, and if not, """Function decorator to test the Ledger for being unlocked, and if not,
raise a human-readable exception. raise a human-readable exception.