Merge commit '82e88cb' into test
This commit is contained in:
commit
5325f842ab
|
@ -1,3 +1,8 @@
|
|||
# Release 3.0.6 :
|
||||
|
||||
* Fix transaction parsing bug #3788
|
||||
|
||||
|
||||
# Release 3.0.5 : (Security update)
|
||||
|
||||
This is a follow-up to the 3.0.4 release, which did not completely fix
|
||||
|
|
|
@ -39,10 +39,7 @@ done
|
|||
popd
|
||||
|
||||
pushd electrum
|
||||
if [ ! -z "$1" ]; then
|
||||
git checkout $1
|
||||
fi
|
||||
|
||||
git checkout $BRANCH
|
||||
VERSION=`git describe --tags`
|
||||
echo "Last commit: $VERSION"
|
||||
find -exec touch -d '2000-11-11T11:11:11+00:00' {} +
|
||||
|
|
|
@ -23,6 +23,6 @@ cd tmp
|
|||
$PYTHON -m pip install setuptools --upgrade
|
||||
$PYTHON -m pip install cython --upgrade
|
||||
$PYTHON -m pip install trezor==0.7.16 --upgrade
|
||||
$PYTHON -m pip install keepkey==4.0.0 --upgrade
|
||||
$PYTHON -m pip install btchip-python==0.1.23 --upgrade
|
||||
$PYTHON -m pip install keepkey==4.0.2 --upgrade
|
||||
$PYTHON -m pip install btchip-python==0.1.24 --upgrade
|
||||
|
||||
|
|
|
@ -281,7 +281,8 @@ def run_offline_command(config, config_options):
|
|||
# arguments passed to function
|
||||
args = [config.get(x) for x in cmd.params]
|
||||
# decode json arguments
|
||||
args = list(map(json_decode, args))
|
||||
if cmdname not in ('setconfig',):
|
||||
args = list(map(json_decode, args))
|
||||
# options
|
||||
kwargs = {}
|
||||
for x in cmd.options:
|
||||
|
|
|
@ -928,6 +928,10 @@ class ElectrumWindow(App):
|
|||
return
|
||||
if not self.wallet.can_export():
|
||||
return
|
||||
key = str(self.wallet.export_private_key(addr, password)[0])
|
||||
pk_label.data = key
|
||||
try:
|
||||
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))
|
||||
|
|
|
@ -2666,7 +2666,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
unit_combo = QComboBox()
|
||||
unit_combo.addItems(units)
|
||||
unit_combo.setCurrentIndex(units.index(self.base_unit()))
|
||||
def on_unit(x):
|
||||
def on_unit(x, nz):
|
||||
unit_result = units[unit_combo.currentIndex()]
|
||||
if self.base_unit() == unit_result:
|
||||
return
|
||||
|
@ -2681,13 +2681,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
else:
|
||||
raise Exception('Unknown base unit')
|
||||
self.config.set_key('decimal_point', self.decimal_point, True)
|
||||
nz.setMaximum(self.decimal_point)
|
||||
self.history_list.update()
|
||||
self.request_list.update()
|
||||
self.address_list.update()
|
||||
for edit, amount in zip(edits, amounts):
|
||||
edit.setAmount(amount)
|
||||
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))
|
||||
|
||||
block_explorers = sorted(util.block_explorer_info().keys())
|
||||
|
|
|
@ -59,11 +59,7 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
|||
data = ''
|
||||
if not data:
|
||||
data = ''
|
||||
if self.allow_multi:
|
||||
new_text = self.text() + data + '\n'
|
||||
else:
|
||||
new_text = data
|
||||
self.setText(new_text)
|
||||
self.setText(data)
|
||||
return data
|
||||
|
||||
def contextMenuEvent(self, e):
|
||||
|
|
BIN
icons/unpaid.png
BIN
icons/unpaid.png
Binary file not shown.
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 23 KiB |
|
@ -34,7 +34,7 @@ from functools import wraps
|
|||
from decimal import Decimal
|
||||
|
||||
from .import util
|
||||
from .util import bfh, bh2u, format_satoshis
|
||||
from .util import bfh, bh2u, format_satoshis, json_decode
|
||||
from .import bitcoin
|
||||
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
|
||||
from .i18n import _
|
||||
|
@ -151,10 +151,8 @@ class Commands:
|
|||
@command('')
|
||||
def setconfig(self, key, value):
|
||||
"""Set a configuration variable. 'value' may be a string or a Python expression."""
|
||||
try:
|
||||
value = ast.literal_eval(value)
|
||||
except:
|
||||
pass
|
||||
if key not in ('rpcuser', 'rpcpassword'):
|
||||
value = json_decode(value)
|
||||
self.config.set_key(key, value)
|
||||
return True
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import sys
|
|||
from threading import Thread
|
||||
import time
|
||||
import csv
|
||||
import decimal
|
||||
from decimal import Decimal
|
||||
|
||||
from .bitcoin import COIN
|
||||
|
@ -165,7 +166,11 @@ class FxThread(ThreadJob):
|
|||
def ccy_amount_str(self, amount, commas):
|
||||
prec = CCY_PRECISIONS.get(self.ccy, 2)
|
||||
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):
|
||||
# This runs from the plugins thread which catches exceptions
|
||||
|
|
|
@ -231,6 +231,10 @@ class TestTransaction(unittest.TestCase):
|
|||
tx = transaction.Transaction('010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000')
|
||||
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):
|
||||
|
||||
|
|
|
@ -45,6 +45,14 @@ class SerializationError(Exception):
|
|||
""" Thrown when there's a problem deserializing or serializing """
|
||||
|
||||
|
||||
class UnknownTxinType(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NotRecognizedRedeemScript(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BCDataStream(object):
|
||||
def __init__(self):
|
||||
self.input = None
|
||||
|
@ -302,10 +310,23 @@ def parse_scriptSig(d, _bytes):
|
|||
if match_decoded(decoded, match):
|
||||
item = decoded[0][1]
|
||||
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['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:
|
||||
# payto_pubkey
|
||||
# assert item[0] == 0x30
|
||||
# pay-to-pubkey
|
||||
d['type'] = 'p2pk'
|
||||
d['address'] = "(pubkey)"
|
||||
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 ]
|
||||
if not match_decoded(dec2, match_multisig):
|
||||
print_error("cannot find address in input script", bh2u(s))
|
||||
return
|
||||
raise NotRecognizedRedeemScript()
|
||||
x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]]
|
||||
pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys]
|
||||
redeemScript = multisig_script(pubkeys, m)
|
||||
|
@ -430,21 +451,40 @@ def parse_witness(vds, txin):
|
|||
if n == 0xffffffff:
|
||||
txin['value'] = vds.read_uint64()
|
||||
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))
|
||||
|
||||
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':
|
||||
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])
|
||||
m, n, x_pubkeys, pubkeys, witnessScript = parse_redeemScript(bfh(w[-1]))
|
||||
txin['num_sig'] = m
|
||||
txin['x_pubkeys'] = x_pubkeys
|
||||
txin['pubkeys'] = pubkeys
|
||||
txin['witnessScript'] = witnessScript
|
||||
else:
|
||||
elif txin['type'] == 'p2wpkh-p2sh' or n == 2:
|
||||
txin['num_sig'] = 1
|
||||
txin['x_pubkeys'] = [w[1]]
|
||||
txin['pubkeys'] = [safe_parse_pubkey(w[1])]
|
||||
txin['signatures'] = parse_sig([w[0]])
|
||||
else:
|
||||
raise UnknownTxinType()
|
||||
|
||||
def parse_output(vds, i):
|
||||
d = {}
|
||||
|
@ -466,6 +506,23 @@ def deserialize(raw):
|
|||
d['inputs'] = [parse_input(vds) for i in range(n_vin)]
|
||||
n_vout = vds.read_compact_size()
|
||||
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()
|
||||
return d
|
||||
|
||||
|
@ -657,7 +714,9 @@ class Transaction:
|
|||
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)
|
||||
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:
|
||||
value_field = ''
|
||||
else:
|
||||
|
@ -666,7 +725,8 @@ class Transaction:
|
|||
|
||||
@classmethod
|
||||
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
|
||||
def is_segwit_inputtype(cls, txin_type):
|
||||
|
|
|
@ -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):
|
||||
from locale import localeconv
|
||||
if x is None:
|
||||
return 'Unknown'
|
||||
return 'unknown'
|
||||
x = int(x) # Some callers pass Decimal
|
||||
scale_factor = pow (10, decimal_point)
|
||||
integer_part = "{:n}".format(int(abs(x) / scale_factor))
|
||||
|
|
|
@ -367,7 +367,8 @@ class Abstract_Wallet(PrintError):
|
|||
def add_unverified_tx(self, tx_hash, tx_height):
|
||||
if tx_height == 0 and tx_hash in self.verified_tx:
|
||||
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
|
||||
if tx_hash not in self.verified_tx:
|
||||
|
|
|
@ -84,7 +84,8 @@ class WsClientThread(util.DaemonThread):
|
|||
l = self.subscriptions.get(addr, [])
|
||||
l.append((ws, amount))
|
||||
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):
|
||||
|
@ -100,10 +101,13 @@ class WsClientThread(util.DaemonThread):
|
|||
result = r.get('result')
|
||||
if result is None:
|
||||
continue
|
||||
if method == 'blockchain.address.subscribe':
|
||||
self.network.send([('blockchain.address.get_balance', params)], self.response_queue.put)
|
||||
elif method == 'blockchain.address.get_balance':
|
||||
addr = params[0]
|
||||
if method == 'blockchain.scripthash.subscribe':
|
||||
self.network.send([('blockchain.scripthash.get_balance', params)], self.response_queue.put)
|
||||
elif method == 'blockchain.scripthash.get_balance':
|
||||
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, [])
|
||||
for ws, amount in l:
|
||||
if not ws.closed:
|
||||
|
|
|
@ -60,6 +60,7 @@ class Ledger_Client():
|
|||
def versiontuple(self, v):
|
||||
return tuple(map(int, (v.split("."))))
|
||||
|
||||
|
||||
def test_pin_unlocked(func):
|
||||
"""Function decorator to test the Ledger for being unlocked, and if not,
|
||||
raise a human-readable exception.
|
||||
|
|
Loading…
Reference in New Issue