From 1451c66a8f65e87555fce8a7111ddef898381598 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Mon, 5 Mar 2018 23:07:52 +0100 Subject: [PATCH 001/174] Check if value has 'is_nan' before calling it Fixes: #4034 --- lib/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.py b/lib/util.py index 1714c78d4..9dbf21c85 100644 --- a/lib/util.py +++ b/lib/util.py @@ -113,7 +113,7 @@ class Fiat(object): return 'Fiat(%s)'% self.__str__() def __str__(self): - if self.value.is_nan(): + if hasattr(self.value, 'is_nan') and self.value.is_nan(): return _('No Data') else: return "{:.2f}".format(self.value) + ' ' + self.ccy From 4c81a77cccbd4886ac70ff136ee3ef7d4c37ea92 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Mon, 5 Mar 2018 23:24:12 +0100 Subject: [PATCH 002/174] Revert "Check if value has 'is_nan' before calling it" This reverts commit 1451c66a8f65e87555fce8a7111ddef898381598. --- lib/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.py b/lib/util.py index 9dbf21c85..1714c78d4 100644 --- a/lib/util.py +++ b/lib/util.py @@ -113,7 +113,7 @@ class Fiat(object): return 'Fiat(%s)'% self.__str__() def __str__(self): - if hasattr(self.value, 'is_nan') and self.value.is_nan(): + if self.value.is_nan(): return _('No Data') else: return "{:.2f}".format(self.value) + ' ' + self.ccy From bb5e19549420cd7c2348b311cdbe153683e5ed18 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 06:09:13 +0100 Subject: [PATCH 003/174] update frozen dependencies fix #4031 --- contrib/deterministic-build/requirements-binaries.txt | 6 +++--- contrib/deterministic-build/requirements-hw.txt | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contrib/deterministic-build/requirements-binaries.txt b/contrib/deterministic-build/requirements-binaries.txt index 381b4378f..8fffe6cee 100644 --- a/contrib/deterministic-build/requirements-binaries.txt +++ b/contrib/deterministic-build/requirements-binaries.txt @@ -1,5 +1,5 @@ pycryptodomex==3.4.12 -PyQt5==5.10 -sip==4.19.7 +PyQt5==5.10.1 +sip==4.19.8 six==1.11.0 -websocket-client==0.46.0 +websocket-client==0.47.0 diff --git a/contrib/deterministic-build/requirements-hw.txt b/contrib/deterministic-build/requirements-hw.txt index 8e0ba52f0..5052054ec 100644 --- a/contrib/deterministic-build/requirements-hw.txt +++ b/contrib/deterministic-build/requirements-hw.txt @@ -13,6 +13,7 @@ pbkdf2==1.3 protobuf==3.5.1 pyblake2==1.1.0 requests==2.18.4 +rlp==0.6.0 six==1.11.0 -trezor==0.9.0 +trezor==0.9.1 urllib3==1.22 From 3be703ac0a613dd2ebaa51df977dfb5d79681055 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 06:40:59 +0100 Subject: [PATCH 004/174] fix #4037 No history summary for wallet without any tx-history. --- gui/qt/history_list.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gui/qt/history_list.py b/gui/qt/history_list.py index 3140af001..41cb2c092 100644 --- a/gui/qt/history_list.py +++ b/gui/qt/history_list.py @@ -161,6 +161,9 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): def show_summary(self): h = self.summary + if not h: + self.parent.show_message(_("Nothing to summarize.")) + return start_date = h.get('start_date') end_date = h.get('end_date') format_amount = lambda x: self.parent.format_amount(x.value) + ' ' + self.parent.base_unit() From ab042a0914bea81dd8c66cca091633720c1eab80 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 6 Mar 2018 07:08:38 +0100 Subject: [PATCH 005/174] fix #4040 --- gui/qt/main_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index d2d318426..bc7613468 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -2627,7 +2627,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): fee_type_label = HelpLabel(_('Fee estimation') + ':', msg) fee_type_combo = QComboBox() fee_type_combo.addItems([_('Static'), _('ETA'), _('Mempool')]) - fee_type_combo.setCurrentIndex(1 if self.config.use_mempool_fees() else 0) + fee_type_combo.setCurrentIndex((2 if self.config.use_mempool_fees() else 1) if self.config.is_dynfee() else 0) def on_fee_type(x): self.config.set_key('mempool_fees', x==2) self.config.set_key('dynamic_fees', x>0) From ade7cce1441afda5af3de536d5231f586c298399 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 6 Mar 2018 07:13:35 +0100 Subject: [PATCH 006/174] fix #4034 --- lib/wallet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index 28597b851..ca3a18ea7 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -987,11 +987,11 @@ class Abstract_Wallet(PrintError): def get_full_history(self, domain=None, from_timestamp=None, to_timestamp=None, fx=None, show_addresses=False): from .util import timestamp_to_datetime, Satoshis, Fiat out = [] - capital_gains = 0 income = 0 expenditures = 0 - fiat_income = 0 - fiat_expenditures = 0 + capital_gains = Decimal(0) + fiat_income = Decimal(0) + fiat_expenditures = Decimal(0) h = self.get_history(domain) for tx_hash, height, conf, timestamp, value, balance in h: if from_timestamp and (timestamp or time.time()) < from_timestamp: From 6b7d5abd29bc726a55f32b7e28cc226704fc1e6f Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 07:13:35 +0100 Subject: [PATCH 007/174] fix #4039 --- gui/qt/address_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/address_list.py b/gui/qt/address_list.py index 104f367a8..6eb0cb236 100644 --- a/gui/qt/address_list.py +++ b/gui/qt/address_list.py @@ -123,7 +123,7 @@ class AddressList(MyTreeWidget): address_item.setText(0, _('receiving')) address_item.setBackground(0, ColorScheme.GREEN.as_color(True)) address_item.setFont(1, QFont(MONOSPACE_FONT)) - address_item.setData(1, Qt.UserRole, address) + address_item.setData(0, Qt.UserRole, address) # column 0; independent from address column if self.wallet.is_frozen(address): address_item.setBackground(1, ColorScheme.BLUE.as_color(True)) if self.wallet.is_beyond_limit(address): From ba3ac1b6485a93666ae2a78275bb0771519319b0 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 09:21:58 +0100 Subject: [PATCH 008/174] fee ui qt: fee_e and feerate_e was sometimes not getting filled --- gui/qt/main_window.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index bc7613468..e0406df0a 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -1329,8 +1329,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): # actual fees often differ somewhat. if freeze_feerate or self.fee_slider.is_active(): displayed_feerate = self.feerate_e.get_amount() - displayed_feerate = displayed_feerate // 1000 if displayed_feerate else 0 - displayed_fee = displayed_feerate * size + if displayed_feerate: + displayed_feerate = displayed_feerate // 1000 + else: + # fallback to actual fee + displayed_feerate = fee // size if fee is not None else None + self.feerate_e.setAmount(displayed_feerate) + displayed_fee = displayed_feerate * size if displayed_feerate is not None else None self.fee_e.setAmount(displayed_fee) else: if freeze_fee: From e5cba92564d2b68283fe4a3e2218ab1c19eea667 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 10:34:52 +0100 Subject: [PATCH 009/174] transaction serialization: fix segwit coinbase case. adjust unit tests so that they would have caught it. --- lib/tests/test_transaction.py | 561 ++++++++++++++++++++-------------- lib/transaction.py | 12 +- 2 files changed, 349 insertions(+), 224 deletions(-) diff --git a/lib/tests/test_transaction.py b/lib/tests/test_transaction.py index cc800454c..c92c6fff7 100644 --- a/lib/tests/test_transaction.py +++ b/lib/tests/test_transaction.py @@ -1,9 +1,8 @@ import unittest + from lib import transaction from lib.bitcoin import TYPE_ADDRESS - from lib.keystore import xpubkey_to_address - from lib.util import bh2u unsigned_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000005701ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' @@ -167,450 +166,568 @@ class TestTransaction(unittest.TestCase): tx = transaction.Transaction(v2_blob) self.assertEqual(tx.txid(), "b97f9180173ab141b61b9f944d841e60feec691d6daab4d4d932b24dd36606fe") +##### + + def _run_naive_tests_on_tx(self, raw_tx, txid): + tx = transaction.Transaction(raw_tx) + self.assertEqual(txid, tx.txid()) + self.assertEqual(raw_tx, tx.serialize()) + self.assertTrue(tx.estimated_size() >= 0) + def test_txid_coinbase_to_p2pk(self): - tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4103400d0302ef02062f503253482f522cfabe6d6dd90d39663d10f8fd25ec88338295d4c6ce1c90d4aeb368d8bdbadcc1da3b635801000000000000000474073e03ffffffff013c25cf2d01000000434104b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e6537a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7bac00000000') - self.assertEqual('dbaf14e1c476e76ea05a8b71921a46d6b06f0a950f17c5f9f1a03b8fae467f10', tx.txid()) + raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4103400d0302ef02062f503253482f522cfabe6d6dd90d39663d10f8fd25ec88338295d4c6ce1c90d4aeb368d8bdbadcc1da3b635801000000000000000474073e03ffffffff013c25cf2d01000000434104b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e6537a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7bac00000000' + txid = 'dbaf14e1c476e76ea05a8b71921a46d6b06f0a950f17c5f9f1a03b8fae467f10' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_coinbase_to_p2pkh(self): - tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff25033ca0030400001256124d696e656420627920425443204775696c640800000d41000007daffffffff01c00d1298000000001976a91427a1f12771de5cc3b73941664b2537c15316be4388ac00000000') - self.assertEqual('4328f9311c6defd9ae1bd7f4516b62acf64b361eb39dfcf09d9925c5fd5c61e8', tx.txid()) + raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff25033ca0030400001256124d696e656420627920425443204775696c640800000d41000007daffffffff01c00d1298000000001976a91427a1f12771de5cc3b73941664b2537c15316be4388ac00000000' + txid = '4328f9311c6defd9ae1bd7f4516b62acf64b361eb39dfcf09d9925c5fd5c61e8' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_segwit_coinbase_to_p2pk(self): - tx = transaction.Transaction('020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502cd010101ffffffff0240be402500000000232103f4e686cdfc96f375e7c338c40c9b85f4011bb843a3e62e46a1de424ef87e9385ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000') - self.assertEqual('fb5a57c24e640a6d8d831eb6e41505f3d54363c507da3733b098d820e3803301', tx.txid()) + raw_tx = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502cd010101ffffffff0240be402500000000232103f4e686cdfc96f375e7c338c40c9b85f4011bb843a3e62e46a1de424ef87e9385ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000' + txid = 'fb5a57c24e640a6d8d831eb6e41505f3d54363c507da3733b098d820e3803301' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_segwit_coinbase_to_p2pkh(self): - tx = transaction.Transaction('020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502c3010101ffffffff0240be4025000000001976a9141ea896d897483e0eb33dd6423f4a07970d0a0a2788ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000') - self.assertEqual('ed3d100577477d799107eba97e76770b3efa253c7200e9abfb43da5d2b33513e', tx.txid()) + raw_tx = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502c3010101ffffffff0240be4025000000001976a9141ea896d897483e0eb33dd6423f4a07970d0a0a2788ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000' + txid = 'ed3d100577477d799107eba97e76770b3efa253c7200e9abfb43da5d2b33513e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pk_to_p2pkh(self): - tx = transaction.Transaction('010000000118231a31d2df84f884ced6af11dc24306319577d4d7c340124a7e2dd9c314077000000004847304402200b6c45891aed48937241907bc3e3868ee4c792819821fcde33311e5a3da4789a02205021b59692b652a01f5f009bd481acac2f647a7d9c076d71d85869763337882e01fdffffff016c95052a010000001976a9149c4891e7791da9e622532c97f43863768264faaf88ac00000000') - self.assertEqual('90ba90a5b115106d26663fce6c6215b8699c5d4b2672dd30756115f3337dddf9', tx.txid()) + raw_tx = '010000000118231a31d2df84f884ced6af11dc24306319577d4d7c340124a7e2dd9c314077000000004847304402200b6c45891aed48937241907bc3e3868ee4c792819821fcde33311e5a3da4789a02205021b59692b652a01f5f009bd481acac2f647a7d9c076d71d85869763337882e01fdffffff016c95052a010000001976a9149c4891e7791da9e622532c97f43863768264faaf88ac00000000' + txid = '90ba90a5b115106d26663fce6c6215b8699c5d4b2672dd30756115f3337dddf9' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pk_to_p2sh(self): - tx = transaction.Transaction('0100000001e4643183d6497823576d17ac2439fb97eba24be8137f312e10fcc16483bb2d070000000048473044022032bbf0394dfe3b004075e3cbb3ea7071b9184547e27f8f73f967c4b3f6a21fa4022073edd5ae8b7b638f25872a7a308bb53a848baa9b9cc70af45fcf3c683d36a55301fdffffff011821814a0000000017a9143c640bc28a346749c09615b50211cb051faff00f8700000000') - self.assertEqual('172bdf5a690b874385b98d7ab6f6af807356f03a26033c6a65ab79b4ac2085b5', tx.txid()) + raw_tx = '0100000001e4643183d6497823576d17ac2439fb97eba24be8137f312e10fcc16483bb2d070000000048473044022032bbf0394dfe3b004075e3cbb3ea7071b9184547e27f8f73f967c4b3f6a21fa4022073edd5ae8b7b638f25872a7a308bb53a848baa9b9cc70af45fcf3c683d36a55301fdffffff011821814a0000000017a9143c640bc28a346749c09615b50211cb051faff00f8700000000' + txid = '172bdf5a690b874385b98d7ab6f6af807356f03a26033c6a65ab79b4ac2085b5' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pk_to_p2wpkh(self): - tx = transaction.Transaction('01000000015e5e2bf15f5793fdfd01e0ccd380033797ed2d4dba9498426ca84904176c26610000000049483045022100c77aff69f7ab4bb148f9bccffc5a87ee893c4f7f7f96c97ba98d2887a0f632b9022046367bdb683d58fa5b2e43cfc8a9c6d57724a27e03583942d8e7b9afbfeea5ab01fdffffff017289824a00000000160014460fc70f208bffa9abf3ae4abbd2f629d9cdcf5900000000') - self.assertEqual('ca554b1014952f900aa8cf6e7ab02137a6fdcf933ad6a218de3891a2ef0c350d', tx.txid()) + raw_tx = '01000000015e5e2bf15f5793fdfd01e0ccd380033797ed2d4dba9498426ca84904176c26610000000049483045022100c77aff69f7ab4bb148f9bccffc5a87ee893c4f7f7f96c97ba98d2887a0f632b9022046367bdb683d58fa5b2e43cfc8a9c6d57724a27e03583942d8e7b9afbfeea5ab01fdffffff017289824a00000000160014460fc70f208bffa9abf3ae4abbd2f629d9cdcf5900000000' + txid = 'ca554b1014952f900aa8cf6e7ab02137a6fdcf933ad6a218de3891a2ef0c350d' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pkh_to_p2pkh(self): - tx = transaction.Transaction('0100000001f9dd7d33f315617530dd72264b5d9c69b815626cce3f66266d1015b1a590ba90000000006a4730440220699bfee3d280a499daf4af5593e8750b54fef0557f3c9f717bfa909493a84f60022057718eec7985b7796bb8630bf6ea2e9bf2892ac21bd6ab8f741a008537139ffe012103b4289890b40590447b57f773b5843bf0400e9cead08be225fac587b3c2a8e973fdffffff01ec24052a010000001976a914ce9ff3d15ed5f3a3d94b583b12796d063879b11588ac00000000') - self.assertEqual('24737c68f53d4b519939119ed83b2a8d44d716d7f3ca98bcecc0fbb92c2085ce', tx.txid()) + raw_tx = '0100000001f9dd7d33f315617530dd72264b5d9c69b815626cce3f66266d1015b1a590ba90000000006a4730440220699bfee3d280a499daf4af5593e8750b54fef0557f3c9f717bfa909493a84f60022057718eec7985b7796bb8630bf6ea2e9bf2892ac21bd6ab8f741a008537139ffe012103b4289890b40590447b57f773b5843bf0400e9cead08be225fac587b3c2a8e973fdffffff01ec24052a010000001976a914ce9ff3d15ed5f3a3d94b583b12796d063879b11588ac00000000' + txid = '24737c68f53d4b519939119ed83b2a8d44d716d7f3ca98bcecc0fbb92c2085ce' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pkh_to_p2sh(self): - tx = transaction.Transaction('010000000195232c30f6611b9f2f82ec63f5b443b132219c425e1824584411f3d16a7a54bc000000006b4830450221009f39ac457dc8ff316e5cc03161c9eff6212d8694ccb88d801dbb32e85d8ed100022074230bb05e99b85a6a50d2b71e7bf04d80be3f1d014ea038f93943abd79421d101210317be0f7e5478e087453b9b5111bdad586038720f16ac9658fd16217ffd7e5785fdffffff0200e40b540200000017a914d81df3751b9e7dca920678cc19cac8d7ec9010b08718dfd63c2c0000001976a914303c42b63569ff5b390a2016ff44651cd84c7c8988acc7010000') - self.assertEqual('155e4740fa59f374abb4e133b87247dccc3afc233cb97c2bf2b46bba3094aedc', tx.txid()) + raw_tx = '010000000195232c30f6611b9f2f82ec63f5b443b132219c425e1824584411f3d16a7a54bc000000006b4830450221009f39ac457dc8ff316e5cc03161c9eff6212d8694ccb88d801dbb32e85d8ed100022074230bb05e99b85a6a50d2b71e7bf04d80be3f1d014ea038f93943abd79421d101210317be0f7e5478e087453b9b5111bdad586038720f16ac9658fd16217ffd7e5785fdffffff0200e40b540200000017a914d81df3751b9e7dca920678cc19cac8d7ec9010b08718dfd63c2c0000001976a914303c42b63569ff5b390a2016ff44651cd84c7c8988acc7010000' + txid = '155e4740fa59f374abb4e133b87247dccc3afc233cb97c2bf2b46bba3094aedc' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pkh_to_p2wpkh(self): - tx = transaction.Transaction('0100000001ce85202cb9fbc0ecbc98caf3d716d7448d2a3bd89e113999514b3df5687c7324000000006b483045022100adab7b6cb1179079c9dfc0021f4db0346730b7c16555fcc4363059dcdd95f653022028bcb816f4fb98615fb8f4b18af3ad3708e2d72f94a6466cc2736055860422cf012102a16a25148dd692462a691796db0a4a5531bcca970a04107bf184a2c9f7fd8b12fdffffff012eb6042a010000001600147d0170de18eecbe84648979d52b666dddee0b47400000000') - self.assertEqual('ed29e100499e2a3a64a2b0cb3a68655b9acd690d29690fa541be530462bf3d3c', tx.txid()) + raw_tx = '0100000001ce85202cb9fbc0ecbc98caf3d716d7448d2a3bd89e113999514b3df5687c7324000000006b483045022100adab7b6cb1179079c9dfc0021f4db0346730b7c16555fcc4363059dcdd95f653022028bcb816f4fb98615fb8f4b18af3ad3708e2d72f94a6466cc2736055860422cf012102a16a25148dd692462a691796db0a4a5531bcca970a04107bf184a2c9f7fd8b12fdffffff012eb6042a010000001600147d0170de18eecbe84648979d52b666dddee0b47400000000' + txid = 'ed29e100499e2a3a64a2b0cb3a68655b9acd690d29690fa541be530462bf3d3c' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2sh_to_p2pkh(self): - tx = transaction.Transaction('01000000000101f9823f87af35d158e7dc81a67011f4e511e3f6cab07ac108e524b0ff8b950b39000000002322002041f0237866eb72e4a75cd6faf5ccd738703193907d883aa7b3a8169c636706a9fdffffff020065cd1d000000001976a9148150cd6cf729e7e262699875fec1f760b0aab3cc88acc46f9a3b0000000017a91433ccd0f95a7b9d8eef68be40bb59c64d6e14d87287040047304402205ca97126a5956c2deaa956a2006d79a348775d727074a04b71d9c18eb5e5525402207b9353497af15881100a2786adab56c8930c02d46cc1a8b55496c06e22d3459b01483045022100b4fa898057927c2d920ae79bca752dda58202ea8617d3e6ed96cbd5d1c0eb2fc02200824c0e742d1b4d643cec439444f5d8779c18d4f42c2c87cce24044a3babf2df0147522102db78786b3c214826bd27010e3c663b02d67144499611ee3f2461c633eb8f1247210377082028c124098b59a5a1e0ea7fd3ebca72d59c793aecfeedd004304bac15cd52aec9010000') - self.assertEqual('17e1d498ba82503e3bfa81ac4897a57e33f3d36b41bcf4765ba604466c478986', tx.txid()) + raw_tx = '01000000000101f9823f87af35d158e7dc81a67011f4e511e3f6cab07ac108e524b0ff8b950b39000000002322002041f0237866eb72e4a75cd6faf5ccd738703193907d883aa7b3a8169c636706a9fdffffff020065cd1d000000001976a9148150cd6cf729e7e262699875fec1f760b0aab3cc88acc46f9a3b0000000017a91433ccd0f95a7b9d8eef68be40bb59c64d6e14d87287040047304402205ca97126a5956c2deaa956a2006d79a348775d727074a04b71d9c18eb5e5525402207b9353497af15881100a2786adab56c8930c02d46cc1a8b55496c06e22d3459b01483045022100b4fa898057927c2d920ae79bca752dda58202ea8617d3e6ed96cbd5d1c0eb2fc02200824c0e742d1b4d643cec439444f5d8779c18d4f42c2c87cce24044a3babf2df0147522102db78786b3c214826bd27010e3c663b02d67144499611ee3f2461c633eb8f1247210377082028c124098b59a5a1e0ea7fd3ebca72d59c793aecfeedd004304bac15cd52aec9010000' + txid = '17e1d498ba82503e3bfa81ac4897a57e33f3d36b41bcf4765ba604466c478986' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2sh_to_p2sh(self): - tx = transaction.Transaction('01000000000101b58520acb479ab656a3c03263af0567380aff6b67a8db98543870b695adf2b170000000017160014cfd2b9f7ed9d4d4429ed6946dbb3315f75e85f14fdffffff020065cd1d0000000017a91485f5681bec38f9f07ae9790d7f27c2bb90b5b63c87106ab32c0000000017a914ff402e164dfce874435641ae9ac41fc6fb14c4e18702483045022100b3d1c89c7c92151ed1df78815924569446782776b6a2c170ca5d74c5dd1ad9b102201d7bab1974fd2aa66546dd15c1f1e276d787453cec31b55a2bd97b050abf20140121024a1742ece86df3dbce4717c228cf51e625030cef7f5e6dde33a4fffdd17569eac7010000') - self.assertEqual('ead0e7abfb24ddbcd6b89d704d7a6091e43804a458baa930adf6f1cb5b6b42f7', tx.txid()) + raw_tx = '01000000000101b58520acb479ab656a3c03263af0567380aff6b67a8db98543870b695adf2b170000000017160014cfd2b9f7ed9d4d4429ed6946dbb3315f75e85f14fdffffff020065cd1d0000000017a91485f5681bec38f9f07ae9790d7f27c2bb90b5b63c87106ab32c0000000017a914ff402e164dfce874435641ae9ac41fc6fb14c4e18702483045022100b3d1c89c7c92151ed1df78815924569446782776b6a2c170ca5d74c5dd1ad9b102201d7bab1974fd2aa66546dd15c1f1e276d787453cec31b55a2bd97b050abf20140121024a1742ece86df3dbce4717c228cf51e625030cef7f5e6dde33a4fffdd17569eac7010000' + txid = 'ead0e7abfb24ddbcd6b89d704d7a6091e43804a458baa930adf6f1cb5b6b42f7' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2sh_to_p2wpkh(self): - tx = transaction.Transaction('010000000001018689476c4604a65b76f4bc416bd3f3337ea59748ac81fa3b3e5082ba98d4e1170100000023220020ae40340707f9726c0f453c3d47c96e7f3b7b4b85608eb3668b69bbef9c7ab374fdffffff0218b2cc1d0000000017a914f2fdd81e606ff2ab804d7bb46bf8838a711c277b870065cd1d0000000016001496ad8959c1f0382984ecc4da61c118b4c8751e5104004730440220387b9e7d402fbcada9ba55a27a8d0563eafa9904ebd2f8f7e3d86e4b45bc0ec202205f37fa0e2bf8cbd384f804562651d7c6f69adce5db4c1a5b9103250a47f73e6b01473044022074903f4dd4fd6b32289be909eb5109924740daa55e79be6dbd728687683f9afa02205d934d981ca12cbec450611ca81dc4127f8da5e07dd63d41049380502de3f15401475221025c3810b37147105106cef970f9b91d3735819dee4882d515c1187dbd0b8f0c792103e007c492323084f1c103beff255836408af89bb9ae7f2fcf60502c28ff4b0c9152aeca010000') - self.assertEqual('6f294c84cbd0241650931b4c1be3dfb2f175d682c7a9538b30b173e1083deed3', tx.txid()) + raw_tx = '010000000001018689476c4604a65b76f4bc416bd3f3337ea59748ac81fa3b3e5082ba98d4e1170100000023220020ae40340707f9726c0f453c3d47c96e7f3b7b4b85608eb3668b69bbef9c7ab374fdffffff0218b2cc1d0000000017a914f2fdd81e606ff2ab804d7bb46bf8838a711c277b870065cd1d0000000016001496ad8959c1f0382984ecc4da61c118b4c8751e5104004730440220387b9e7d402fbcada9ba55a27a8d0563eafa9904ebd2f8f7e3d86e4b45bc0ec202205f37fa0e2bf8cbd384f804562651d7c6f69adce5db4c1a5b9103250a47f73e6b01473044022074903f4dd4fd6b32289be909eb5109924740daa55e79be6dbd728687683f9afa02205d934d981ca12cbec450611ca81dc4127f8da5e07dd63d41049380502de3f15401475221025c3810b37147105106cef970f9b91d3735819dee4882d515c1187dbd0b8f0c792103e007c492323084f1c103beff255836408af89bb9ae7f2fcf60502c28ff4b0c9152aeca010000' + txid = '6f294c84cbd0241650931b4c1be3dfb2f175d682c7a9538b30b173e1083deed3' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2wpkh_to_p2pkh(self): - tx = transaction.Transaction('0100000000010197e6bf4a70bc118e3a8d9842ed80422e335679dfc29b5ba0f9123f6a5863b8470000000000fdffffff02402bca7f130000001600146f579c953d9e7e7719f2baa20bde22eb5f24119200e87648170000001976a9140cd8fa5fd81c3acf33f93efd179b388de8dd693388ac0247304402204ff33b3ea8fb270f62409bfc257457ca5eb1fec5e4d3a7c11aa487207e131d4d022032726b998e338e5245746716e5cd0b40d32b69d1535c3d841f049d98a5d819b1012102dc3ce3220363aff579eb2c45c973e8b186a829c987c3caea77c61975666e7d1bc8010000') - self.assertEqual('c721ed35767a3a209b688e68e3bb136a72d2b631fe81c56be8bdbb948c343dbc', tx.txid()) + raw_tx = '0100000000010197e6bf4a70bc118e3a8d9842ed80422e335679dfc29b5ba0f9123f6a5863b8470000000000fdffffff02402bca7f130000001600146f579c953d9e7e7719f2baa20bde22eb5f24119200e87648170000001976a9140cd8fa5fd81c3acf33f93efd179b388de8dd693388ac0247304402204ff33b3ea8fb270f62409bfc257457ca5eb1fec5e4d3a7c11aa487207e131d4d022032726b998e338e5245746716e5cd0b40d32b69d1535c3d841f049d98a5d819b1012102dc3ce3220363aff579eb2c45c973e8b186a829c987c3caea77c61975666e7d1bc8010000' + txid = 'c721ed35767a3a209b688e68e3bb136a72d2b631fe81c56be8bdbb948c343dbc' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2wpkh_to_p2sh(self): - tx = transaction.Transaction('010000000001013c3dbf620453be41a50f69290d69cd9a5b65683acbb0a2643a2a9e4900e129ed0000000000fdffffff02002f68590000000017a914c7c4dcd0ddf70f15c6df13b4a4d56e9f13c49b2787a0429cd000000000160014e514e3ecf89731e7853e4f3a20983484c569d3910247304402205368cc548209303db5a8f2ebc282bd0f7af0d080ce0f7637758587f94d3971fb0220098cec5752554758bc5fa4de332b980d5e0054a807541581dc5e4de3ed29647501210233717cd73d95acfdf6bd72c4fb5df27cd6bd69ce947daa3f4a442183a97877efc8010000') - self.assertEqual('390b958bffb024e508c17ab0caf6e311e5f41170a681dce758d135af873f82f9', tx.txid()) + raw_tx = '010000000001013c3dbf620453be41a50f69290d69cd9a5b65683acbb0a2643a2a9e4900e129ed0000000000fdffffff02002f68590000000017a914c7c4dcd0ddf70f15c6df13b4a4d56e9f13c49b2787a0429cd000000000160014e514e3ecf89731e7853e4f3a20983484c569d3910247304402205368cc548209303db5a8f2ebc282bd0f7af0d080ce0f7637758587f94d3971fb0220098cec5752554758bc5fa4de332b980d5e0054a807541581dc5e4de3ed29647501210233717cd73d95acfdf6bd72c4fb5df27cd6bd69ce947daa3f4a442183a97877efc8010000' + txid = '390b958bffb024e508c17ab0caf6e311e5f41170a681dce758d135af873f82f9' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2wpkh_to_p2wpkh(self): - tx = transaction.Transaction('010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000') - self.assertEqual('51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e', tx.txid()) + raw_tx = '010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000' + txid = '51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_input_p2wsh_p2sh_not_multisig(self): - tx = transaction.Transaction('0100000000010160f84fdcda039c3ca1b20038adea2d49a53db92f7c467e8def13734232bb610804000000232200202814720f16329ab81cb8867c4d447bd13255931f23e6655944c9ada1797fcf88ffffffff0ba3dcfc04000000001976a91488124a57c548c9e7b1dd687455af803bd5765dea88acc9f44900000000001976a914da55045a0ccd40a56ce861946d13eb861eb5f2d788ac49825e000000000017a914ca34d4b190e36479aa6e0023cfe0a8537c6aa8dd87680c0d00000000001976a914651102524c424b2e7c44787c4f21e4c54dffafc088acf02fa9000000000017a914ee6c596e6f7066466d778d4f9ba633a564a6e95d874d250900000000001976a9146ca7976b48c04fd23867748382ee8401b1d27c2988acf5119600000000001976a914cf47d5dcdba02fd547c600697097252d38c3214a88ace08a12000000000017a914017bef79d92d5ec08c051786bad317e5dd3befcf87e3d76201000000001976a9148ec1b88b66d142bcbdb42797a0fd402c23e0eec288ac718f6900000000001976a914e66344472a224ce6f843f2989accf435ae6a808988ac65e51300000000001976a914cad6717c13a2079066f876933834210ebbe68c3f88ac0347304402201a4907c4706104320313e182ecbb1b265b2d023a79586671386de86bb47461590220472c3db9fc99a728ebb9b555a72e3481d20b181bd059a9c1acadfb853d90c96c01210338a46f2a54112fef8803c8478bc17e5f8fc6a5ec276903a946c1fafb2e3a8b181976a914eda8660085bf607b82bd18560ca8f3a9ec49178588ac00000000') - self.assertEqual('e9933221a150f78f9f224899f8568ff6422ffcc28ca3d53d87936368ff7c4b1d', tx.txid()) + raw_tx = '0100000000010160f84fdcda039c3ca1b20038adea2d49a53db92f7c467e8def13734232bb610804000000232200202814720f16329ab81cb8867c4d447bd13255931f23e6655944c9ada1797fcf88ffffffff0ba3dcfc04000000001976a91488124a57c548c9e7b1dd687455af803bd5765dea88acc9f44900000000001976a914da55045a0ccd40a56ce861946d13eb861eb5f2d788ac49825e000000000017a914ca34d4b190e36479aa6e0023cfe0a8537c6aa8dd87680c0d00000000001976a914651102524c424b2e7c44787c4f21e4c54dffafc088acf02fa9000000000017a914ee6c596e6f7066466d778d4f9ba633a564a6e95d874d250900000000001976a9146ca7976b48c04fd23867748382ee8401b1d27c2988acf5119600000000001976a914cf47d5dcdba02fd547c600697097252d38c3214a88ace08a12000000000017a914017bef79d92d5ec08c051786bad317e5dd3befcf87e3d76201000000001976a9148ec1b88b66d142bcbdb42797a0fd402c23e0eec288ac718f6900000000001976a914e66344472a224ce6f843f2989accf435ae6a808988ac65e51300000000001976a914cad6717c13a2079066f876933834210ebbe68c3f88ac0347304402201a4907c4706104320313e182ecbb1b265b2d023a79586671386de86bb47461590220472c3db9fc99a728ebb9b555a72e3481d20b181bd059a9c1acadfb853d90c96c01210338a46f2a54112fef8803c8478bc17e5f8fc6a5ec276903a946c1fafb2e3a8b181976a914eda8660085bf607b82bd18560ca8f3a9ec49178588ac00000000' + txid = 'e9933221a150f78f9f224899f8568ff6422ffcc28ca3d53d87936368ff7c4b1d' + self._run_naive_tests_on_tx(raw_tx, txid) # input: p2sh, not multisig def test_txid_regression_issue_3899(self): - tx = transaction.Transaction('0100000004328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c010000000b0009630330472d5fae685bffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c020000000b0009630359646d5fae6858ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c030000000b000963034bd4715fae6854ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c040000000b000963036de8705fae6860ffffffff0130750000000000001976a914b5abca61d20f9062fb1fdbb880d9d93bac36675188ac00000000') - self.assertEqual('f570d5d1e965ee61bcc7005f8fefb1d3abbed9d7ddbe035e2a68fa07e5fc4a0d', tx.txid()) + raw_tx = '0100000004328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c010000000b0009630330472d5fae685bffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c020000000b0009630359646d5fae6858ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c030000000b000963034bd4715fae6854ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c040000000b000963036de8705fae6860ffffffff0130750000000000001976a914b5abca61d20f9062fb1fdbb880d9d93bac36675188ac00000000' + txid = 'f570d5d1e965ee61bcc7005f8fefb1d3abbed9d7ddbe035e2a68fa07e5fc4a0d' + self._run_naive_tests_on_tx(raw_tx, txid) # these transactions are from Bitcoin Core unit tests ---> # https://github.com/bitcoin/bitcoin/blob/11376b5583a283772c82f6d32d0007cdbf5b8ef0/src/test/data/tx_valid.json def test_txid_bitcoin_core_0001(self): - tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') - self.assertEqual('23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63', tx.txid()) + raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000' + txid = '23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0002(self): - tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') - self.assertEqual('fcabc409d8e685da28536e1e5ccc91264d755cd4c57ed4cae3dbaa4d3b93e8ed', tx.txid()) + raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000' + txid = 'fcabc409d8e685da28536e1e5ccc91264d755cd4c57ed4cae3dbaa4d3b93e8ed' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0003(self): - tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a01ff47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') - self.assertEqual('c9aa95f2c48175fdb70b34c23f1c3fc44f869b073a6f79b1343fbce30c3cb575', tx.txid()) + raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a01ff47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000' + txid = 'c9aa95f2c48175fdb70b34c23f1c3fc44f869b073a6f79b1343fbce30c3cb575' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0004(self): - tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000495147304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') - self.assertEqual('da94fda32b55deb40c3ed92e135d69df7efc4ee6665e0beb07ef500f407c9fd2', tx.txid()) + raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000495147304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000' + txid = 'da94fda32b55deb40c3ed92e135d69df7efc4ee6665e0beb07ef500f407c9fd2' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0005(self): - tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000494f47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') - self.assertEqual('f76f897b206e4f78d60fe40f2ccb542184cfadc34354d3bb9bdc30cc2f432b86', tx.txid()) + raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000494f47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000' + txid = 'f76f897b206e4f78d60fe40f2ccb542184cfadc34354d3bb9bdc30cc2f432b86' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0006(self): - tx = transaction.Transaction('01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68350228c159766676a14f5815084ba166432aab46198d4cca98fa3e9981d0a90b2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d00000000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac00000000') - self.assertEqual('c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73', tx.txid()) + raw_tx = '01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68350228c159766676a14f5815084ba166432aab46198d4cca98fa3e9981d0a90b2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d00000000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac00000000' + txid = 'c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0007(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000') - self.assertEqual('e41ffe19dff3cbedb413a2ca3fbbcd05cb7fd7397ffa65052f8928aa9c700092', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000' + txid = 'e41ffe19dff3cbedb413a2ca3fbbcd05cb7fd7397ffa65052f8928aa9c700092' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0008(self): - tx = transaction.Transaction('01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000') - self.assertEqual('f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb', tx.txid()) + raw_tx = '01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000' + txid = 'f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0009(self): - tx = transaction.Transaction('01000000020002000000000000000000000000000000000000000000000000000000000000000000000151ffffffff0001000000000000000000000000000000000000000000000000000000000000000000006b483045022100c9cdd08798a28af9d1baf44a6c77bcc7e279f47dc487c8c899911bc48feaffcc0220503c5c50ae3998a733263c5c0f7061b483e2b56c4c41b456e7d2f5a78a74c077032102d5c25adb51b61339d2b05315791e21bbe80ea470a49db0135720983c905aace0ffffffff010000000000000000015100000000') - self.assertEqual('b56471690c3ff4f7946174e51df68b47455a0d29344c351377d712e6d00eabe5', tx.txid()) + raw_tx = '01000000020002000000000000000000000000000000000000000000000000000000000000000000000151ffffffff0001000000000000000000000000000000000000000000000000000000000000000000006b483045022100c9cdd08798a28af9d1baf44a6c77bcc7e279f47dc487c8c899911bc48feaffcc0220503c5c50ae3998a733263c5c0f7061b483e2b56c4c41b456e7d2f5a78a74c077032102d5c25adb51b61339d2b05315791e21bbe80ea470a49db0135720983c905aace0ffffffff010000000000000000015100000000' + txid = 'b56471690c3ff4f7946174e51df68b47455a0d29344c351377d712e6d00eabe5' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0010(self): - tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000') - self.assertEqual('99517e5b47533453cc7daa332180f578be68b80370ecfe84dbfff7f19d791da4', tx.txid()) + raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000' + txid = '99517e5b47533453cc7daa332180f578be68b80370ecfe84dbfff7f19d791da4' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0011(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100c66c9cdf4c43609586d15424c54707156e316d88b0a1534c9e6b0d4f311406310221009c0fe51dbc9c4ab7cc25d3fdbeccf6679fe6827f08edf2b4a9f16ee3eb0e438a0123210338e8034509af564c62644c07691942e0c056752008a173c89f60ab2a88ac2ebfacffffffff010000000000000000015100000000') - self.assertEqual('ab097537b528871b9b64cb79a769ae13c3c3cd477cc9dddeebe657eabd7fdcea', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100c66c9cdf4c43609586d15424c54707156e316d88b0a1534c9e6b0d4f311406310221009c0fe51dbc9c4ab7cc25d3fdbeccf6679fe6827f08edf2b4a9f16ee3eb0e438a0123210338e8034509af564c62644c07691942e0c056752008a173c89f60ab2a88ac2ebfacffffffff010000000000000000015100000000' + txid = 'ab097537b528871b9b64cb79a769ae13c3c3cd477cc9dddeebe657eabd7fdcea' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0012(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100e1eadba00d9296c743cb6ecc703fd9ddc9b3cd12906176a226ae4c18d6b00796022100a71aef7d2874deff681ba6080f1b278bac7bb99c61b08a85f4311970ffe7f63f012321030c0588dc44d92bdcbf8e72093466766fdc265ead8db64517b0c542275b70fffbacffffffff010040075af0750700015100000000') - self.assertEqual('4d163e00f1966e9a1eab8f9374c3e37f4deb4857c247270e25f7d79a999d2dc9', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100e1eadba00d9296c743cb6ecc703fd9ddc9b3cd12906176a226ae4c18d6b00796022100a71aef7d2874deff681ba6080f1b278bac7bb99c61b08a85f4311970ffe7f63f012321030c0588dc44d92bdcbf8e72093466766fdc265ead8db64517b0c542275b70fffbacffffffff010040075af0750700015100000000' + txid = '4d163e00f1966e9a1eab8f9374c3e37f4deb4857c247270e25f7d79a999d2dc9' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0013(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022027deccc14aa6668e78a8c9da3484fbcd4f9dcc9bb7d1b85146314b21b9ae4d86022100d0b43dece8cfb07348de0ca8bc5b86276fa88f7f2138381128b7c36ab2e42264012321029bb13463ddd5d2cc05da6e84e37536cb9525703cfd8f43afdb414988987a92f6acffffffff020040075af075070001510000000000000000015100000000') - self.assertEqual('9fe2ef9dde70e15d78894a4800b7df3bbfb1addb9a6f7d7c204492fdb6ee6cc4', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022027deccc14aa6668e78a8c9da3484fbcd4f9dcc9bb7d1b85146314b21b9ae4d86022100d0b43dece8cfb07348de0ca8bc5b86276fa88f7f2138381128b7c36ab2e42264012321029bb13463ddd5d2cc05da6e84e37536cb9525703cfd8f43afdb414988987a92f6acffffffff020040075af075070001510000000000000000015100000000' + txid = '9fe2ef9dde70e15d78894a4800b7df3bbfb1addb9a6f7d7c204492fdb6ee6cc4' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0014(self): - tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff010000000000000000015100000000') - self.assertEqual('99d3825137602e577aeaf6a2e3c9620fd0e605323dc5265da4a570593be791d4', tx.txid()) + raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff010000000000000000015100000000' + txid = '99d3825137602e577aeaf6a2e3c9620fd0e605323dc5265da4a570593be791d4' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0015(self): - tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6451515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151ffffffff010000000000000000015100000000') - self.assertEqual('c0d67409923040cc766bbea12e4c9154393abef706db065ac2e07d91a9ba4f84', tx.txid()) + raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6451515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151ffffffff010000000000000000015100000000' + txid = 'c0d67409923040cc766bbea12e4c9154393abef706db065ac2e07d91a9ba4f84' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0016(self): - tx = transaction.Transaction('010000000200010000000000000000000000000000000000000000000000000000000000000000000049483045022100d180fd2eb9140aeb4210c9204d3f358766eb53842b2a9473db687fa24b12a3cc022079781799cd4f038b85135bbe49ec2b57f306b2bb17101b17f71f000fcab2b6fb01ffffffff0002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000') - self.assertEqual('c610d85d3d5fdf5046be7f123db8a0890cee846ee58de8a44667cfd1ab6b8666', tx.txid()) + raw_tx = '010000000200010000000000000000000000000000000000000000000000000000000000000000000049483045022100d180fd2eb9140aeb4210c9204d3f358766eb53842b2a9473db687fa24b12a3cc022079781799cd4f038b85135bbe49ec2b57f306b2bb17101b17f71f000fcab2b6fb01ffffffff0002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000' + txid = 'c610d85d3d5fdf5046be7f123db8a0890cee846ee58de8a44667cfd1ab6b8666' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0017(self): - tx = transaction.Transaction('01000000020001000000000000000000000000000000000000000000000000000000000000000000004948304502203a0f5f0e1f2bdbcd04db3061d18f3af70e07f4f467cbc1b8116f267025f5360b022100c792b6e215afc5afc721a351ec413e714305cb749aae3d7fee76621313418df101010000000002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000') - self.assertEqual('a647a7b3328d2c698bfa1ee2dd4e5e05a6cea972e764ccb9bd29ea43817ca64f', tx.txid()) + raw_tx = '01000000020001000000000000000000000000000000000000000000000000000000000000000000004948304502203a0f5f0e1f2bdbcd04db3061d18f3af70e07f4f467cbc1b8116f267025f5360b022100c792b6e215afc5afc721a351ec413e714305cb749aae3d7fee76621313418df101010000000002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000' + txid = 'a647a7b3328d2c698bfa1ee2dd4e5e05a6cea972e764ccb9bd29ea43817ca64f' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0018(self): - tx = transaction.Transaction('010000000370ac0a1ae588aaf284c308d67ca92c69a39e2db81337e563bf40c59da0a5cf63000000006a4730440220360d20baff382059040ba9be98947fd678fb08aab2bb0c172efa996fd8ece9b702201b4fb0de67f015c90e7ac8a193aeab486a1f587e0f54d0fb9552ef7f5ce6caec032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff7d815b6447e35fbea097e00e028fb7dfbad4f3f0987b4734676c84f3fcd0e804010000006b483045022100c714310be1e3a9ff1c5f7cacc65c2d8e781fc3a88ceb063c6153bf950650802102200b2d0979c76e12bb480da635f192cc8dc6f905380dd4ac1ff35a4f68f462fffd032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff3f1f097333e4d46d51f5e77b53264db8f7f5d2e18217e1099957d0f5af7713ee010000006c493046022100b663499ef73273a3788dea342717c2640ac43c5a1cf862c9e09b206fcb3f6bb8022100b09972e75972d9148f2bdd462e5cb69b57c1214b88fc55ca638676c07cfc10d8032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff0380841e00000000001976a914bfb282c70c4191f45b5a6665cad1682f2c9cfdfb88ac80841e00000000001976a9149857cc07bed33a5cf12b9c5e0500b675d500c81188ace0fd1c00000000001976a91443c52850606c872403c0601e69fa34b26f62db4a88ac00000000') - self.assertEqual('afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae', tx.txid()) + raw_tx = '010000000370ac0a1ae588aaf284c308d67ca92c69a39e2db81337e563bf40c59da0a5cf63000000006a4730440220360d20baff382059040ba9be98947fd678fb08aab2bb0c172efa996fd8ece9b702201b4fb0de67f015c90e7ac8a193aeab486a1f587e0f54d0fb9552ef7f5ce6caec032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff7d815b6447e35fbea097e00e028fb7dfbad4f3f0987b4734676c84f3fcd0e804010000006b483045022100c714310be1e3a9ff1c5f7cacc65c2d8e781fc3a88ceb063c6153bf950650802102200b2d0979c76e12bb480da635f192cc8dc6f905380dd4ac1ff35a4f68f462fffd032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff3f1f097333e4d46d51f5e77b53264db8f7f5d2e18217e1099957d0f5af7713ee010000006c493046022100b663499ef73273a3788dea342717c2640ac43c5a1cf862c9e09b206fcb3f6bb8022100b09972e75972d9148f2bdd462e5cb69b57c1214b88fc55ca638676c07cfc10d8032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff0380841e00000000001976a914bfb282c70c4191f45b5a6665cad1682f2c9cfdfb88ac80841e00000000001976a9149857cc07bed33a5cf12b9c5e0500b675d500c81188ace0fd1c00000000001976a91443c52850606c872403c0601e69fa34b26f62db4a88ac00000000' + txid = 'afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0019(self): - tx = transaction.Transaction('01000000012312503f2491a2a97fcd775f11e108a540a5528b5d4dee7a3c68ae4add01dab300000000fdfe0000483045022100f6649b0eddfdfd4ad55426663385090d51ee86c3481bdc6b0c18ea6c0ece2c0b0220561c315b07cffa6f7dd9df96dbae9200c2dee09bf93cc35ca05e6cdf613340aa0148304502207aacee820e08b0b174e248abd8d7a34ed63b5da3abedb99934df9fddd65c05c4022100dfe87896ab5ee3df476c2655f9fbe5bd089dccbef3e4ea05b5d121169fe7f5f4014c695221031d11db38972b712a9fe1fc023577c7ae3ddb4a3004187d41c45121eecfdbb5b7210207ec36911b6ad2382860d32989c7b8728e9489d7bbc94a6b5509ef0029be128821024ea9fac06f666a4adc3fc1357b7bec1fd0bdece2b9d08579226a8ebde53058e453aeffffffff0180380100000000001976a914c9b99cddf847d10685a4fabaa0baf505f7c3dfab88ac00000000') - self.assertEqual('f4b05f978689c89000f729cae187dcfbe64c9819af67a4f05c0b4d59e717d64d', tx.txid()) + raw_tx = '01000000012312503f2491a2a97fcd775f11e108a540a5528b5d4dee7a3c68ae4add01dab300000000fdfe0000483045022100f6649b0eddfdfd4ad55426663385090d51ee86c3481bdc6b0c18ea6c0ece2c0b0220561c315b07cffa6f7dd9df96dbae9200c2dee09bf93cc35ca05e6cdf613340aa0148304502207aacee820e08b0b174e248abd8d7a34ed63b5da3abedb99934df9fddd65c05c4022100dfe87896ab5ee3df476c2655f9fbe5bd089dccbef3e4ea05b5d121169fe7f5f4014c695221031d11db38972b712a9fe1fc023577c7ae3ddb4a3004187d41c45121eecfdbb5b7210207ec36911b6ad2382860d32989c7b8728e9489d7bbc94a6b5509ef0029be128821024ea9fac06f666a4adc3fc1357b7bec1fd0bdece2b9d08579226a8ebde53058e453aeffffffff0180380100000000001976a914c9b99cddf847d10685a4fabaa0baf505f7c3dfab88ac00000000' + txid = 'f4b05f978689c89000f729cae187dcfbe64c9819af67a4f05c0b4d59e717d64d' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0020(self): - tx = transaction.Transaction('0100000001f709fa82596e4f908ee331cb5e0ed46ab331d7dcfaf697fe95891e73dac4ebcb000000008c20ca42095840735e89283fec298e62ac2ddea9b5f34a8cbb7097ad965b87568100201b1b01dc829177da4a14551d2fc96a9db00c6501edfa12f22cd9cefd335c227f483045022100a9df60536df5733dd0de6bc921fab0b3eee6426501b43a228afa2c90072eb5ca02201c78b74266fac7d1db5deff080d8a403743203f109fbcabf6d5a760bf87386d20100ffffffff01c075790000000000232103611f9a45c18f28f06f19076ad571c344c82ce8fcfe34464cf8085217a2d294a6ac00000000') - self.assertEqual('cc60b1f899ec0a69b7c3f25ddf32c4524096a9c5b01cbd84c6d0312a0c478984', tx.txid()) + raw_tx = '0100000001f709fa82596e4f908ee331cb5e0ed46ab331d7dcfaf697fe95891e73dac4ebcb000000008c20ca42095840735e89283fec298e62ac2ddea9b5f34a8cbb7097ad965b87568100201b1b01dc829177da4a14551d2fc96a9db00c6501edfa12f22cd9cefd335c227f483045022100a9df60536df5733dd0de6bc921fab0b3eee6426501b43a228afa2c90072eb5ca02201c78b74266fac7d1db5deff080d8a403743203f109fbcabf6d5a760bf87386d20100ffffffff01c075790000000000232103611f9a45c18f28f06f19076ad571c344c82ce8fcfe34464cf8085217a2d294a6ac00000000' + txid = 'cc60b1f899ec0a69b7c3f25ddf32c4524096a9c5b01cbd84c6d0312a0c478984' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0021(self): - tx = transaction.Transaction('01000000012c651178faca83be0b81c8c1375c4b0ad38d53c8fe1b1c4255f5e795c25792220000000049483045022100d6044562284ac76c985018fc4a90127847708c9edb280996c507b28babdc4b2a02203d74eca3f1a4d1eea7ff77b528fde6d5dc324ec2dbfdb964ba885f643b9704cd01ffffffff010100000000000000232102c2410f8891ae918cab4ffc4bb4a3b0881be67c7a1e7faa8b5acf9ab8932ec30cac00000000') - self.assertEqual('1edc7f214659d52c731e2016d258701911bd62a0422f72f6c87a1bc8dd3f8667', tx.txid()) + raw_tx = '01000000012c651178faca83be0b81c8c1375c4b0ad38d53c8fe1b1c4255f5e795c25792220000000049483045022100d6044562284ac76c985018fc4a90127847708c9edb280996c507b28babdc4b2a02203d74eca3f1a4d1eea7ff77b528fde6d5dc324ec2dbfdb964ba885f643b9704cd01ffffffff010100000000000000232102c2410f8891ae918cab4ffc4bb4a3b0881be67c7a1e7faa8b5acf9ab8932ec30cac00000000' + txid = '1edc7f214659d52c731e2016d258701911bd62a0422f72f6c87a1bc8dd3f8667' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0022(self): - tx = transaction.Transaction('0100000001f725ea148d92096a79b1709611e06e94c63c4ef61cbae2d9b906388efd3ca99c000000000100ffffffff0101000000000000002321028a1d66975dbdf97897e3a4aef450ebeb5b5293e4a0b4a6d3a2daaa0b2b110e02ac00000000') - self.assertEqual('018adb7133fde63add9149a2161802a1bcf4bdf12c39334e880c073480eda2ff', tx.txid()) + raw_tx = '0100000001f725ea148d92096a79b1709611e06e94c63c4ef61cbae2d9b906388efd3ca99c000000000100ffffffff0101000000000000002321028a1d66975dbdf97897e3a4aef450ebeb5b5293e4a0b4a6d3a2daaa0b2b110e02ac00000000' + txid = '018adb7133fde63add9149a2161802a1bcf4bdf12c39334e880c073480eda2ff' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0023(self): - tx = transaction.Transaction('0100000001be599efaa4148474053c2fa031c7262398913f1dc1d9ec201fd44078ed004e44000000004900473044022022b29706cb2ed9ef0cb3c97b72677ca2dfd7b4160f7b4beb3ba806aa856c401502202d1e52582412eba2ed474f1f437a427640306fd3838725fab173ade7fe4eae4a01ffffffff010100000000000000232103ac4bba7e7ca3e873eea49e08132ad30c7f03640b6539e9b59903cf14fd016bbbac00000000') - self.assertEqual('1464caf48c708a6cc19a296944ded9bb7f719c9858986d2501cf35068b9ce5a2', tx.txid()) + raw_tx = '0100000001be599efaa4148474053c2fa031c7262398913f1dc1d9ec201fd44078ed004e44000000004900473044022022b29706cb2ed9ef0cb3c97b72677ca2dfd7b4160f7b4beb3ba806aa856c401502202d1e52582412eba2ed474f1f437a427640306fd3838725fab173ade7fe4eae4a01ffffffff010100000000000000232103ac4bba7e7ca3e873eea49e08132ad30c7f03640b6539e9b59903cf14fd016bbbac00000000' + txid = '1464caf48c708a6cc19a296944ded9bb7f719c9858986d2501cf35068b9ce5a2' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0024(self): - tx = transaction.Transaction('010000000112b66d5e8c7d224059e946749508efea9d66bf8d0c83630f080cf30be8bb6ae100000000490047304402206ffe3f14caf38ad5c1544428e99da76ffa5455675ec8d9780fac215ca17953520220779502985e194d84baa36b9bd40a0dbd981163fa191eb884ae83fc5bd1c86b1101ffffffff010100000000000000232103905380c7013e36e6e19d305311c1b81fce6581f5ee1c86ef0627c68c9362fc9fac00000000') - self.assertEqual('1fb73fbfc947d52f5d80ba23b67c06a232ad83fdd49d1c0a657602f03fbe8f7a', tx.txid()) + raw_tx = '010000000112b66d5e8c7d224059e946749508efea9d66bf8d0c83630f080cf30be8bb6ae100000000490047304402206ffe3f14caf38ad5c1544428e99da76ffa5455675ec8d9780fac215ca17953520220779502985e194d84baa36b9bd40a0dbd981163fa191eb884ae83fc5bd1c86b1101ffffffff010100000000000000232103905380c7013e36e6e19d305311c1b81fce6581f5ee1c86ef0627c68c9362fc9fac00000000' + txid = '1fb73fbfc947d52f5d80ba23b67c06a232ad83fdd49d1c0a657602f03fbe8f7a' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0025(self): - tx = transaction.Transaction('0100000001b0ef70cc644e0d37407e387e73bfad598d852a5aa6d691d72b2913cebff4bceb000000004a00473044022068cd4851fc7f9a892ab910df7a24e616f293bcb5c5fbdfbc304a194b26b60fba022078e6da13d8cb881a22939b952c24f88b97afd06b4c47a47d7f804c9a352a6d6d0100ffffffff0101000000000000002321033bcaa0a602f0d44cc9d5637c6e515b0471db514c020883830b7cefd73af04194ac00000000') - self.assertEqual('24cecfce0fa880b09c9b4a66c5134499d1b09c01cc5728cd182638bea070e6ab', tx.txid()) + raw_tx = '0100000001b0ef70cc644e0d37407e387e73bfad598d852a5aa6d691d72b2913cebff4bceb000000004a00473044022068cd4851fc7f9a892ab910df7a24e616f293bcb5c5fbdfbc304a194b26b60fba022078e6da13d8cb881a22939b952c24f88b97afd06b4c47a47d7f804c9a352a6d6d0100ffffffff0101000000000000002321033bcaa0a602f0d44cc9d5637c6e515b0471db514c020883830b7cefd73af04194ac00000000' + txid = '24cecfce0fa880b09c9b4a66c5134499d1b09c01cc5728cd182638bea070e6ab' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0026(self): - tx = transaction.Transaction('0100000001c188aa82f268fcf08ba18950f263654a3ea6931dabc8bf3ed1d4d42aaed74cba000000004b0000483045022100940378576e069aca261a6b26fb38344e4497ca6751bb10905c76bb689f4222b002204833806b014c26fd801727b792b1260003c55710f87c5adbd7a9cb57446dbc9801ffffffff0101000000000000002321037c615d761e71d38903609bf4f46847266edc2fb37532047d747ba47eaae5ffe1ac00000000') - self.assertEqual('9eaa819e386d6a54256c9283da50c230f3d8cd5376d75c4dcc945afdeb157dd7', tx.txid()) + raw_tx = '0100000001c188aa82f268fcf08ba18950f263654a3ea6931dabc8bf3ed1d4d42aaed74cba000000004b0000483045022100940378576e069aca261a6b26fb38344e4497ca6751bb10905c76bb689f4222b002204833806b014c26fd801727b792b1260003c55710f87c5adbd7a9cb57446dbc9801ffffffff0101000000000000002321037c615d761e71d38903609bf4f46847266edc2fb37532047d747ba47eaae5ffe1ac00000000' + txid = '9eaa819e386d6a54256c9283da50c230f3d8cd5376d75c4dcc945afdeb157dd7' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0027(self): - tx = transaction.Transaction('01000000012432b60dc72cebc1a27ce0969c0989c895bdd9e62e8234839117f8fc32d17fbc000000004a493046022100a576b52051962c25e642c0fd3d77ee6c92487048e5d90818bcf5b51abaccd7900221008204f8fb121be4ec3b24483b1f92d89b1b0548513a134e345c5442e86e8617a501ffffffff010000000000000000016a00000000') - self.assertEqual('46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa', tx.txid()) + raw_tx = '01000000012432b60dc72cebc1a27ce0969c0989c895bdd9e62e8234839117f8fc32d17fbc000000004a493046022100a576b52051962c25e642c0fd3d77ee6c92487048e5d90818bcf5b51abaccd7900221008204f8fb121be4ec3b24483b1f92d89b1b0548513a134e345c5442e86e8617a501ffffffff010000000000000000016a00000000' + txid = '46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0028(self): - tx = transaction.Transaction('01000000014710b0e7cf9f8930de259bdc4b84aa5dfb9437b665a3e3a21ff26e0bf994e183000000004a493046022100a166121a61b4eeb19d8f922b978ff6ab58ead8a5a5552bf9be73dc9c156873ea02210092ad9bc43ee647da4f6652c320800debcf08ec20a094a0aaf085f63ecb37a17201ffffffff010000000000000000016a00000000') - self.assertEqual('8d66836045db9f2d7b3a75212c5e6325f70603ee27c8333a3bce5bf670d9582e', tx.txid()) + raw_tx = '01000000014710b0e7cf9f8930de259bdc4b84aa5dfb9437b665a3e3a21ff26e0bf994e183000000004a493046022100a166121a61b4eeb19d8f922b978ff6ab58ead8a5a5552bf9be73dc9c156873ea02210092ad9bc43ee647da4f6652c320800debcf08ec20a094a0aaf085f63ecb37a17201ffffffff010000000000000000016a00000000' + txid = '8d66836045db9f2d7b3a75212c5e6325f70603ee27c8333a3bce5bf670d9582e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0029(self): - tx = transaction.Transaction('01000000015ebaa001d8e4ec7a88703a3bcf69d98c874bca6299cca0f191512bf2a7826832000000004948304502203bf754d1c6732fbf87c5dcd81258aefd30f2060d7bd8ac4a5696f7927091dad1022100f5bcb726c4cf5ed0ed34cc13dadeedf628ae1045b7cb34421bc60b89f4cecae701ffffffff010000000000000000016a00000000') - self.assertEqual('aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8', tx.txid()) + raw_tx = '01000000015ebaa001d8e4ec7a88703a3bcf69d98c874bca6299cca0f191512bf2a7826832000000004948304502203bf754d1c6732fbf87c5dcd81258aefd30f2060d7bd8ac4a5696f7927091dad1022100f5bcb726c4cf5ed0ed34cc13dadeedf628ae1045b7cb34421bc60b89f4cecae701ffffffff010000000000000000016a00000000' + txid = 'aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0030(self): - tx = transaction.Transaction('010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a900000000924830450221009c0a27f886a1d8cb87f6f595fbc3163d28f7a81ec3c4b252ee7f3ac77fd13ffa02203caa8dfa09713c8c4d7ef575c75ed97812072405d932bd11e6a1593a98b679370148304502201e3861ef39a526406bad1e20ecad06be7375ad40ddb582c9be42d26c3a0d7b240221009d0a3985e96522e59635d19cc4448547477396ce0ef17a58e7d74c3ef464292301ffffffff010000000000000000016a00000000') - self.assertEqual('6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190', tx.txid()) + raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a900000000924830450221009c0a27f886a1d8cb87f6f595fbc3163d28f7a81ec3c4b252ee7f3ac77fd13ffa02203caa8dfa09713c8c4d7ef575c75ed97812072405d932bd11e6a1593a98b679370148304502201e3861ef39a526406bad1e20ecad06be7375ad40ddb582c9be42d26c3a0d7b240221009d0a3985e96522e59635d19cc4448547477396ce0ef17a58e7d74c3ef464292301ffffffff010000000000000000016a00000000' + txid = '6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0031(self): - tx = transaction.Transaction('010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a48304502207a6974a77c591fa13dff60cabbb85a0de9e025c09c65a4b2285e47ce8e22f761022100f0efaac9ff8ac36b10721e0aae1fb975c90500b50c56e8a0cc52b0403f0425dd0100ffffffff010000000000000000016a00000000') - self.assertEqual('892464645599cc3c2d165adcc612e5f982a200dfaa3e11e9ce1d228027f46880', tx.txid()) + raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a48304502207a6974a77c591fa13dff60cabbb85a0de9e025c09c65a4b2285e47ce8e22f761022100f0efaac9ff8ac36b10721e0aae1fb975c90500b50c56e8a0cc52b0403f0425dd0100ffffffff010000000000000000016a00000000' + txid = '892464645599cc3c2d165adcc612e5f982a200dfaa3e11e9ce1d228027f46880' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0032(self): - tx = transaction.Transaction('010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a483045022100fa4a74ba9fd59c59f46c3960cf90cbe0d2b743c471d24a3d5d6db6002af5eebb02204d70ec490fd0f7055a7c45f86514336e3a7f03503dacecabb247fc23f15c83510151ffffffff010000000000000000016a00000000') - self.assertEqual('578db8c6c404fec22c4a8afeaf32df0e7b767c4dda3478e0471575846419e8fc', tx.txid()) + raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a483045022100fa4a74ba9fd59c59f46c3960cf90cbe0d2b743c471d24a3d5d6db6002af5eebb02204d70ec490fd0f7055a7c45f86514336e3a7f03503dacecabb247fc23f15c83510151ffffffff010000000000000000016a00000000' + txid = '578db8c6c404fec22c4a8afeaf32df0e7b767c4dda3478e0471575846419e8fc' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0033(self): - tx = transaction.Transaction('0100000001e0be9e32f1f89c3d916c4f21e55cdcd096741b895cc76ac353e6023a05f4f7cc00000000d86149304602210086e5f736a2c3622ebb62bd9d93d8e5d76508b98be922b97160edc3dcca6d8c47022100b23c312ac232a4473f19d2aeb95ab7bdf2b65518911a0d72d50e38b5dd31dc820121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac4730440220508fa761865c8abd81244a168392876ee1d94e8ed83897066b5e2df2400dad24022043f5ee7538e87e9c6aef7ef55133d3e51da7cc522830a9c4d736977a76ef755c0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') - self.assertEqual('974f5148a0946f9985e75a240bb24c573adbbdc25d61e7b016cdbb0a5355049f', tx.txid()) + raw_tx = '0100000001e0be9e32f1f89c3d916c4f21e55cdcd096741b895cc76ac353e6023a05f4f7cc00000000d86149304602210086e5f736a2c3622ebb62bd9d93d8e5d76508b98be922b97160edc3dcca6d8c47022100b23c312ac232a4473f19d2aeb95ab7bdf2b65518911a0d72d50e38b5dd31dc820121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac4730440220508fa761865c8abd81244a168392876ee1d94e8ed83897066b5e2df2400dad24022043f5ee7538e87e9c6aef7ef55133d3e51da7cc522830a9c4d736977a76ef755c0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000' + txid = '974f5148a0946f9985e75a240bb24c573adbbdc25d61e7b016cdbb0a5355049f' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0034(self): - tx = transaction.Transaction('01000000013c6f30f99a5161e75a2ce4bca488300ca0c6112bde67f0807fe983feeff0c91001000000e608646561646265656675ab61493046022100ce18d384221a731c993939015e3d1bcebafb16e8c0b5b5d14097ec8177ae6f28022100bcab227af90bab33c3fe0a9abfee03ba976ee25dc6ce542526e9b2e56e14b7f10121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac493046022100c3b93edcc0fd6250eb32f2dd8a0bba1754b0f6c3be8ed4100ed582f3db73eba2022100bf75b5bd2eff4d6bf2bda2e34a40fcc07d4aa3cf862ceaa77b47b81eff829f9a01ab21038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') - self.assertEqual('b0097ec81df231893a212657bf5fe5a13b2bff8b28c0042aca6fc4159f79661b', tx.txid()) + raw_tx = '01000000013c6f30f99a5161e75a2ce4bca488300ca0c6112bde67f0807fe983feeff0c91001000000e608646561646265656675ab61493046022100ce18d384221a731c993939015e3d1bcebafb16e8c0b5b5d14097ec8177ae6f28022100bcab227af90bab33c3fe0a9abfee03ba976ee25dc6ce542526e9b2e56e14b7f10121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac493046022100c3b93edcc0fd6250eb32f2dd8a0bba1754b0f6c3be8ed4100ed582f3db73eba2022100bf75b5bd2eff4d6bf2bda2e34a40fcc07d4aa3cf862ceaa77b47b81eff829f9a01ab21038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000' + txid = 'b0097ec81df231893a212657bf5fe5a13b2bff8b28c0042aca6fc4159f79661b' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0035(self): - tx = transaction.Transaction('01000000016f3dbe2ca96fa217e94b1017860be49f20820dea5c91bdcb103b0049d5eb566000000000fd1d0147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac47304402203757e937ba807e4a5da8534c17f9d121176056406a6465054bdd260457515c1a02200f02eccf1bec0f3a0d65df37889143c2e88ab7acec61a7b6f5aa264139141a2b0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') - self.assertEqual('feeba255656c80c14db595736c1c7955c8c0a497622ec96e3f2238fbdd43a7c9', tx.txid()) + raw_tx = '01000000016f3dbe2ca96fa217e94b1017860be49f20820dea5c91bdcb103b0049d5eb566000000000fd1d0147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac47304402203757e937ba807e4a5da8534c17f9d121176056406a6465054bdd260457515c1a02200f02eccf1bec0f3a0d65df37889143c2e88ab7acec61a7b6f5aa264139141a2b0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000' + txid = 'feeba255656c80c14db595736c1c7955c8c0a497622ec96e3f2238fbdd43a7c9' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0036(self): - tx = transaction.Transaction('01000000012139c555ccb81ee5b1e87477840991ef7b386bc3ab946b6b682a04a621006b5a01000000fdb40148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f2204148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390175ac4830450220646b72c35beeec51f4d5bc1cbae01863825750d7f490864af354e6ea4f625e9c022100f04b98432df3a9641719dbced53393022e7249fb59db993af1118539830aab870148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a580039017521038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') - self.assertEqual('a0c984fc820e57ddba97f8098fa640c8a7eb3fe2f583923da886b7660f505e1e', tx.txid()) + raw_tx = '01000000012139c555ccb81ee5b1e87477840991ef7b386bc3ab946b6b682a04a621006b5a01000000fdb40148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f2204148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390175ac4830450220646b72c35beeec51f4d5bc1cbae01863825750d7f490864af354e6ea4f625e9c022100f04b98432df3a9641719dbced53393022e7249fb59db993af1118539830aab870148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a580039017521038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000' + txid = 'a0c984fc820e57ddba97f8098fa640c8a7eb3fe2f583923da886b7660f505e1e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0037(self): - tx = transaction.Transaction('0100000002f9cbafc519425637ba4227f8d0a0b7160b4e65168193d5af39747891de98b5b5000000006b4830450221008dd619c563e527c47d9bd53534a770b102e40faa87f61433580e04e271ef2f960220029886434e18122b53d5decd25f1f4acb2480659fea20aabd856987ba3c3907e0121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffff42e7988254800876b69f24676b3e0205b77be476512ca4d970707dd5c60598ab00000000fd260100483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a53034930460221008431bdfa72bc67f9d41fe72e94c88fb8f359ffa30b33c72c121c5a877d922e1002210089ef5fc22dd8bfc6bf9ffdb01a9862d27687d424d1fefbab9e9c7176844a187a014c9052483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7153aeffffffff01a08601000000000017a914d8dacdadb7462ae15cd906f1878706d0da8660e68700000000') - self.assertEqual('5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f', tx.txid()) + raw_tx = '0100000002f9cbafc519425637ba4227f8d0a0b7160b4e65168193d5af39747891de98b5b5000000006b4830450221008dd619c563e527c47d9bd53534a770b102e40faa87f61433580e04e271ef2f960220029886434e18122b53d5decd25f1f4acb2480659fea20aabd856987ba3c3907e0121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffff42e7988254800876b69f24676b3e0205b77be476512ca4d970707dd5c60598ab00000000fd260100483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a53034930460221008431bdfa72bc67f9d41fe72e94c88fb8f359ffa30b33c72c121c5a877d922e1002210089ef5fc22dd8bfc6bf9ffdb01a9862d27687d424d1fefbab9e9c7176844a187a014c9052483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7153aeffffffff01a08601000000000017a914d8dacdadb7462ae15cd906f1878706d0da8660e68700000000' + txid = '5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0038(self): - tx = transaction.Transaction('0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000') - self.assertEqual('ded7ff51d89a4e1ec48162aee5a96447214d93dfb3837946af2301a28f65dbea', tx.txid()) + raw_tx = '0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000' + txid = 'ded7ff51d89a4e1ec48162aee5a96447214d93dfb3837946af2301a28f65dbea' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0039(self): - tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000') - self.assertEqual('3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e', tx.txid()) + raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000' + txid = '3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0040(self): - tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d') - self.assertEqual('abd62b4627d8d9b2d95fcfd8c87e37d2790637ce47d28018e3aece63c1d62649', tx.txid()) + raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d' + txid = 'abd62b4627d8d9b2d95fcfd8c87e37d2790637ce47d28018e3aece63c1d62649' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0041(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d') - self.assertEqual('58b6de8413603b7f556270bf48caedcf17772e7105f5419f6a80be0df0b470da', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d' + txid = '58b6de8413603b7f556270bf48caedcf17772e7105f5419f6a80be0df0b470da' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0042(self): - tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffffff') - self.assertEqual('5f99c0abf511294d76cbe144d86b77238a03e086974bc7a8ea0bdb2c681a0324', tx.txid()) + raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffffff' + txid = '5f99c0abf511294d76cbe144d86b77238a03e086974bc7a8ea0bdb2c681a0324' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0043(self): - tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000') - self.assertEqual('25d35877eaba19497710666473c50d5527d38503e3521107a3fc532b74cd7453', tx.txid()) + raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000' + txid = '25d35877eaba19497710666473c50d5527d38503e3521107a3fc532b74cd7453' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0044(self): - tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000feffffff') - self.assertEqual('1b9aef851895b93c62c29fbd6ca4d45803f4007eff266e2f96ff11e9b6ef197b', tx.txid()) + raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000feffffff' + txid = '1b9aef851895b93c62c29fbd6ca4d45803f4007eff266e2f96ff11e9b6ef197b' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0045(self): - tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000') - self.assertEqual('3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e', tx.txid()) + raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000' + txid = '3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0046(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000000251b1000000000100000000000000000001000000') - self.assertEqual('f53761038a728b1f17272539380d96e93f999218f8dcb04a8469b523445cd0fd', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000000251b1000000000100000000000000000001000000' + txid = 'f53761038a728b1f17272539380d96e93f999218f8dcb04a8469b523445cd0fd' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0047(self): - tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000030251b1000000000100000000000000000001000000') - self.assertEqual('d193f0f32fceaf07bb25c897c8f99ca6f69a52f6274ca64efc2a2e180cb97fc1', tx.txid()) + raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000030251b1000000000100000000000000000001000000' + txid = 'd193f0f32fceaf07bb25c897c8f99ca6f69a52f6274ca64efc2a2e180cb97fc1' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0048(self): - tx = transaction.Transaction('010000000132211bdd0d568506804eef0d8cc3db68c3d766ab9306cdfcc0a9c89616c8dbb1000000006c493045022100c7bb0faea0522e74ff220c20c022d2cb6033f8d167fb89e75a50e237a35fd6d202203064713491b1f8ad5f79e623d0219ad32510bfaa1009ab30cbee77b59317d6e30001210237af13eb2d84e4545af287b919c2282019c9691cc509e78e196a9d8274ed1be0ffffffff0100000000000000001976a914f1b3ed2eda9a2ebe5a9374f692877cdf87c0f95b88ac00000000') - self.assertEqual('50a1e0e6a134a564efa078e3bd088e7e8777c2c0aec10a752fd8706470103b89', tx.txid()) + raw_tx = '010000000132211bdd0d568506804eef0d8cc3db68c3d766ab9306cdfcc0a9c89616c8dbb1000000006c493045022100c7bb0faea0522e74ff220c20c022d2cb6033f8d167fb89e75a50e237a35fd6d202203064713491b1f8ad5f79e623d0219ad32510bfaa1009ab30cbee77b59317d6e30001210237af13eb2d84e4545af287b919c2282019c9691cc509e78e196a9d8274ed1be0ffffffff0100000000000000001976a914f1b3ed2eda9a2ebe5a9374f692877cdf87c0f95b88ac00000000' + txid = '50a1e0e6a134a564efa078e3bd088e7e8777c2c0aec10a752fd8706470103b89' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0049(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000') - self.assertEqual('e2207d1aaf6b74e5d98c2fa326d2dc803b56b30a3f90ce779fa5edb762f38755', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000' + txid = 'e2207d1aaf6b74e5d98c2fa326d2dc803b56b30a3f90ce779fa5edb762f38755' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0050(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff00000100000000000000000000000000') - self.assertEqual('f335864f7c12ec7946d2c123deb91eb978574b647af125a414262380c7fbd55c', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff00000100000000000000000000000000' + txid = 'f335864f7c12ec7946d2c123deb91eb978574b647af125a414262380c7fbd55c' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0051(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000') - self.assertEqual('d1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000' + txid = 'd1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0052(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000') - self.assertEqual('3a13e1b6371c545147173cc4055f0ed73686a9f73f092352fb4b39ca27d360e6', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000' + txid = '3a13e1b6371c545147173cc4055f0ed73686a9f73f092352fb4b39ca27d360e6' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0053(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff40000100000000000000000000000000') - self.assertEqual('bffda23e40766d292b0510a1b556453c558980c70c94ab158d8286b3413e220d', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff40000100000000000000000000000000' + txid = 'bffda23e40766d292b0510a1b556453c558980c70c94ab158d8286b3413e220d' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0054(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000') - self.assertEqual('01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000' + txid = '01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0055(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000') - self.assertEqual('f6d2359c5de2d904e10517d23e7c8210cca71076071bbf46de9fbd5f6233dbf1', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000' + txid = 'f6d2359c5de2d904e10517d23e7c8210cca71076071bbf46de9fbd5f6233dbf1' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0056(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000') - self.assertEqual('19c2b7377229dae7aa3e50142a32fd37cef7171a01682f536e9ffa80c186f6c9', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000' + txid = '19c2b7377229dae7aa3e50142a32fd37cef7171a01682f536e9ffa80c186f6c9' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0057(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000') - self.assertEqual('c9dda3a24cc8a5acb153d1085ecd2fecf6f87083122f8cdecc515b1148d4c40d', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000' + txid = 'c9dda3a24cc8a5acb153d1085ecd2fecf6f87083122f8cdecc515b1148d4c40d' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0058(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000') - self.assertEqual('d1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000' + txid = 'd1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0059(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000') - self.assertEqual('01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000' + txid = '01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0060(self): - tx = transaction.Transaction('02000000010001000000000000000000000000000000000000000000000000000000000000000000000251b2010000000100000000000000000000000000') - self.assertEqual('4b5e0aae1251a9dc66b4d5f483f1879bf518ea5e1765abc5a9f2084b43ed1ea7', tx.txid()) + raw_tx = '02000000010001000000000000000000000000000000000000000000000000000000000000000000000251b2010000000100000000000000000000000000' + txid = '4b5e0aae1251a9dc66b4d5f483f1879bf518ea5e1765abc5a9f2084b43ed1ea7' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0061(self): - tx = transaction.Transaction('0200000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2010000000100000000000000000000000000') - self.assertEqual('5f16eb3ca4581e2dfb46a28140a4ee15f85e4e1c032947da8b93549b53c105f5', tx.txid()) + raw_tx = '0200000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2010000000100000000000000000000000000' + txid = '5f16eb3ca4581e2dfb46a28140a4ee15f85e4e1c032947da8b93549b53c105f5' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0062(self): - tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') - self.assertEqual('b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004', tx.txid()) + raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000' + txid = 'b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0063(self): - tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000') - self.assertEqual('b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004', tx.txid()) + raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000' + txid = 'b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0064(self): - tx = transaction.Transaction('01000000000101000100000000000000000000000000000000000000000000000000000000000000000000171600144c9c3dfac4207d5d8cb89df5722cb3d712385e3fffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') - self.assertEqual('fee125c6cd142083fabd0187b1dd1f94c66c89ec6e6ef6da1374881c0c19aece', tx.txid()) + raw_tx = '01000000000101000100000000000000000000000000000000000000000000000000000000000000000000171600144c9c3dfac4207d5d8cb89df5722cb3d712385e3fffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000' + txid = 'fee125c6cd142083fabd0187b1dd1f94c66c89ec6e6ef6da1374881c0c19aece' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0065(self): - tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000023220020ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000') - self.assertEqual('5f32557914351fee5f89ddee6c8983d476491d29e601d854e3927299e50450da', tx.txid()) + raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000023220020ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000' + txid = '5f32557914351fee5f89ddee6c8983d476491d29e601d854e3927299e50450da' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0066(self): - tx = transaction.Transaction('0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff05540b0000000000000151d0070000000000000151840300000000000001513c0f00000000000001512c010000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71000000000000') - self.assertEqual('07dfa2da3d67c8a2b9f7bd31862161f7b497829d5da90a88ba0f1a905e7a43f7', tx.txid()) + raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff05540b0000000000000151d0070000000000000151840300000000000001513c0f00000000000001512c010000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71000000000000' + txid = '07dfa2da3d67c8a2b9f7bd31862161f7b497829d5da90a88ba0f1a905e7a43f7' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0067(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0068(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff0484030000000000000151d0070000000000000151540b0000000000000151c800000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('f92bb6e4f3ff89172f23ef647f74c13951b665848009abb5862cdf7a0412415a', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff0484030000000000000151d0070000000000000151540b0000000000000151c800000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = 'f92bb6e4f3ff89172f23ef647f74c13951b665848009abb5862cdf7a0412415a' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0069(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0070(self): - tx = transaction.Transaction('0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff04b60300000000000001519e070000000000000151860b00000000000001009600000000000000015100000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('e657e25fc9f2b33842681613402759222a58cf7dd504d6cdc0b69a0b8c2e7dcb', tx.txid()) + raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff04b60300000000000001519e070000000000000151860b00000000000001009600000000000000015100000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = 'e657e25fc9f2b33842681613402759222a58cf7dd504d6cdc0b69a0b8c2e7dcb' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0071(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0072(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff04b60300000000000001519e070000000000000151860b0000000000000100960000000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('4ede5e22992d43d42ccdf6553fb46e448aa1065ba36423f979605c1e5ab496b8', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff04b60300000000000001519e070000000000000151860b0000000000000100960000000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '4ede5e22992d43d42ccdf6553fb46e448aa1065ba36423f979605c1e5ab496b8' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0073(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0074(self): - tx = transaction.Transaction('01000000000103000100000000000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000100000000ffffffff000100000000000000000000000000000000000000000000000000000000000002000000000200000003e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('cfe9f4b19f52b8366860aec0d2b5815e329299b2e9890d477edd7f1182be7ac8', tx.txid()) + raw_tx = '01000000000103000100000000000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000100000000ffffffff000100000000000000000000000000000000000000000000000000000000000002000000000200000003e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = 'cfe9f4b19f52b8366860aec0d2b5815e329299b2e9890d477edd7f1182be7ac8' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0075(self): - tx = transaction.Transaction('0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('aee8f4865ca40fa77ff2040c0d7de683bea048b103d42ca406dc07dd29d539cb', tx.txid()) + raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = 'aee8f4865ca40fa77ff2040c0d7de683bea048b103d42ca406dc07dd29d539cb' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0076(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0077(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623ffffffffff1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623ffffffffff1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0078(self): - tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015102fd08020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002755100000000') - self.assertEqual('d93ab9e12d7c29d2adc13d5cdf619d53eec1f36eb6612f55af52be7ba0448e97', tx.txid()) + raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015102fd08020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002755100000000' + txid = 'd93ab9e12d7c29d2adc13d5cdf619d53eec1f36eb6612f55af52be7ba0448e97' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0079(self): - tx = transaction.Transaction('0100000000010c00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff0001000000000000000000000000000000000000000000000000000000000000020000006a473044022026c2e65b33fcd03b2a3b0f25030f0244bd23cc45ae4dec0f48ae62255b1998a00220463aa3982b718d593a6b9e0044513fd67a5009c2fdccc59992cffc2b167889f4012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000030000006a4730440220008bd8382911218dcb4c9f2e75bf5c5c3635f2f2df49b36994fde85b0be21a1a02205a539ef10fb4c778b522c1be852352ea06c67ab74200977c722b0bc68972575a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000040000006b483045022100d9436c32ff065127d71e1a20e319e4fe0a103ba0272743dbd8580be4659ab5d302203fd62571ee1fe790b182d078ecfd092a509eac112bea558d122974ef9cc012c7012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000050000006a47304402200e2c149b114ec546015c13b2b464bbcb0cdc5872e6775787527af6cbc4830b6c02207e9396c6979fb15a9a2b96ca08a633866eaf20dc0ff3c03e512c1d5a1654f148012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000060000006b483045022100b20e70d897dc15420bccb5e0d3e208d27bdd676af109abbd3f88dbdb7721e6d6022005836e663173fbdfe069f54cde3c2decd3d0ea84378092a5d9d85ec8642e8a41012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff00010000000000000000000000000000000000000000000000000000000000000700000000ffffffff00010000000000000000000000000000000000000000000000000000000000000800000000ffffffff00010000000000000000000000000000000000000000000000000000000000000900000000ffffffff00010000000000000000000000000000000000000000000000000000000000000a00000000ffffffff00010000000000000000000000000000000000000000000000000000000000000b0000006a47304402206639c6e05e3b9d2675a7f3876286bdf7584fe2bbd15e0ce52dd4e02c0092cdc60220757d60b0a61fc95ada79d23746744c72bac1545a75ff6c2c7cdb6ae04e7e9592012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0ce8030000000000000151e9030000000000000151ea030000000000000151eb030000000000000151ec030000000000000151ed030000000000000151ee030000000000000151ef030000000000000151f0030000000000000151f1030000000000000151f2030000000000000151f30300000000000001510248304502210082219a54f61bf126bfc3fa068c6e33831222d1d7138c6faa9d33ca87fd4202d6022063f9902519624254d7c2c8ea7ba2d66ae975e4e229ae38043973ec707d5d4a83012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022017fb58502475848c1b09f162cb1688d0920ff7f142bed0ef904da2ccc88b168f02201798afa61850c65e77889cbcd648a5703b487895517c88f85cdd18b021ee246a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000000247304402202830b7926e488da75782c81a54cd281720890d1af064629ebf2e31bf9f5435f30220089afaa8b455bbeb7d9b9c3fe1ed37d07685ade8455c76472cda424d93e4074a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022026326fcdae9207b596c2b05921dbac11d81040c4d40378513670f19d9f4af893022034ecd7a282c0163b89aaa62c22ec202cef4736c58cd251649bad0d8139bcbf55012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71024730440220214978daeb2f38cd426ee6e2f44131a33d6b191af1c216247f1dd7d74c16d84a02205fdc05529b0bc0c430b4d5987264d9d075351c4f4484c16e91662e90a72aab24012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402204a6e9f199dc9672cf2ff8094aaa784363be1eb62b679f7ff2df361124f1dca3302205eeb11f70fab5355c9c8ad1a0700ea355d315e334822fa182227e9815308ee8f012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('b83579db5246aa34255642768167132a0c3d2932b186cd8fb9f5490460a0bf91', tx.txid()) + raw_tx = '0100000000010c00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff0001000000000000000000000000000000000000000000000000000000000000020000006a473044022026c2e65b33fcd03b2a3b0f25030f0244bd23cc45ae4dec0f48ae62255b1998a00220463aa3982b718d593a6b9e0044513fd67a5009c2fdccc59992cffc2b167889f4012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000030000006a4730440220008bd8382911218dcb4c9f2e75bf5c5c3635f2f2df49b36994fde85b0be21a1a02205a539ef10fb4c778b522c1be852352ea06c67ab74200977c722b0bc68972575a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000040000006b483045022100d9436c32ff065127d71e1a20e319e4fe0a103ba0272743dbd8580be4659ab5d302203fd62571ee1fe790b182d078ecfd092a509eac112bea558d122974ef9cc012c7012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000050000006a47304402200e2c149b114ec546015c13b2b464bbcb0cdc5872e6775787527af6cbc4830b6c02207e9396c6979fb15a9a2b96ca08a633866eaf20dc0ff3c03e512c1d5a1654f148012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000060000006b483045022100b20e70d897dc15420bccb5e0d3e208d27bdd676af109abbd3f88dbdb7721e6d6022005836e663173fbdfe069f54cde3c2decd3d0ea84378092a5d9d85ec8642e8a41012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff00010000000000000000000000000000000000000000000000000000000000000700000000ffffffff00010000000000000000000000000000000000000000000000000000000000000800000000ffffffff00010000000000000000000000000000000000000000000000000000000000000900000000ffffffff00010000000000000000000000000000000000000000000000000000000000000a00000000ffffffff00010000000000000000000000000000000000000000000000000000000000000b0000006a47304402206639c6e05e3b9d2675a7f3876286bdf7584fe2bbd15e0ce52dd4e02c0092cdc60220757d60b0a61fc95ada79d23746744c72bac1545a75ff6c2c7cdb6ae04e7e9592012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0ce8030000000000000151e9030000000000000151ea030000000000000151eb030000000000000151ec030000000000000151ed030000000000000151ee030000000000000151ef030000000000000151f0030000000000000151f1030000000000000151f2030000000000000151f30300000000000001510248304502210082219a54f61bf126bfc3fa068c6e33831222d1d7138c6faa9d33ca87fd4202d6022063f9902519624254d7c2c8ea7ba2d66ae975e4e229ae38043973ec707d5d4a83012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022017fb58502475848c1b09f162cb1688d0920ff7f142bed0ef904da2ccc88b168f02201798afa61850c65e77889cbcd648a5703b487895517c88f85cdd18b021ee246a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000000247304402202830b7926e488da75782c81a54cd281720890d1af064629ebf2e31bf9f5435f30220089afaa8b455bbeb7d9b9c3fe1ed37d07685ade8455c76472cda424d93e4074a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022026326fcdae9207b596c2b05921dbac11d81040c4d40378513670f19d9f4af893022034ecd7a282c0163b89aaa62c22ec202cef4736c58cd251649bad0d8139bcbf55012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71024730440220214978daeb2f38cd426ee6e2f44131a33d6b191af1c216247f1dd7d74c16d84a02205fdc05529b0bc0c430b4d5987264d9d075351c4f4484c16e91662e90a72aab24012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402204a6e9f199dc9672cf2ff8094aaa784363be1eb62b679f7ff2df361124f1dca3302205eeb11f70fab5355c9c8ad1a0700ea355d315e334822fa182227e9815308ee8f012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = 'b83579db5246aa34255642768167132a0c3d2932b186cd8fb9f5490460a0bf91' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0080(self): - tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000') - self.assertEqual('2b1e44fff489d09091e5e20f9a01bbc0e8d80f0662e629fd10709cdb4922a874', tx.txid()) + raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000' + txid = '2b1e44fff489d09091e5e20f9a01bbc0e8d80f0662e629fd10709cdb4922a874' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0081(self): - tx = transaction.Transaction('0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff01d00700000000000001510003483045022100e078de4e96a0e05dcdc0a414124dd8475782b5f3f0ed3f607919e9a5eeeb22bf02201de309b3a3109adb3de8074b3610d4cf454c49b61247a2779a0bcbf31c889333032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc711976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac00000000') - self.assertEqual('60ebb1dd0b598e20dd0dd462ef6723dd49f8f803b6a2492926012360119cfdd7', tx.txid()) + raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff01d00700000000000001510003483045022100e078de4e96a0e05dcdc0a414124dd8475782b5f3f0ed3f607919e9a5eeeb22bf02201de309b3a3109adb3de8074b3610d4cf454c49b61247a2779a0bcbf31c889333032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc711976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac00000000' + txid = '60ebb1dd0b598e20dd0dd462ef6723dd49f8f803b6a2492926012360119cfdd7' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0082(self): - tx = transaction.Transaction('0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff02e8030000000000000151e90300000000000001510247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') - self.assertEqual('ed0c7f4163e275f3f77064f471eac861d01fdf55d03aa6858ebd3781f70bf003', tx.txid()) + raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff02e8030000000000000151e90300000000000001510247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000' + txid = 'ed0c7f4163e275f3f77064f471eac861d01fdf55d03aa6858ebd3781f70bf003' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0083(self): - tx = transaction.Transaction('0100000000010200010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff02e9030000000000000151e80300000000000001510248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') - self.assertEqual('f531ddf5ce141e1c8a7fdfc85cc634e5ff686f446a5cf7483e9dbe076b844862', tx.txid()) + raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff02e9030000000000000151e80300000000000001510248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000' + txid = 'f531ddf5ce141e1c8a7fdfc85cc634e5ff686f446a5cf7483e9dbe076b844862' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0084(self): - tx = transaction.Transaction('01000000020001000000000000000000000000000000000000000000000000000000000000000000004847304402202a0b4b1294d70540235ae033d78e64b4897ec859c7b6f1b2b1d8a02e1d46006702201445e756d2254b0f1dfda9ab8e1e1bc26df9668077403204f32d16a49a36eb6983ffffffff00010000000000000000000000000000000000000000000000000000000000000100000049483045022100acb96cfdbda6dc94b489fd06f2d720983b5f350e31ba906cdbd800773e80b21c02200d74ea5bdf114212b4bbe9ed82c36d2e369e302dff57cb60d01c428f0bd3daab83ffffffff02e8030000000000000151e903000000000000015100000000') - self.assertEqual('98229b70948f1c17851a541f1fe532bf02c408267fecf6d7e174c359ae870654', tx.txid()) + raw_tx = '01000000020001000000000000000000000000000000000000000000000000000000000000000000004847304402202a0b4b1294d70540235ae033d78e64b4897ec859c7b6f1b2b1d8a02e1d46006702201445e756d2254b0f1dfda9ab8e1e1bc26df9668077403204f32d16a49a36eb6983ffffffff00010000000000000000000000000000000000000000000000000000000000000100000049483045022100acb96cfdbda6dc94b489fd06f2d720983b5f350e31ba906cdbd800773e80b21c02200d74ea5bdf114212b4bbe9ed82c36d2e369e302dff57cb60d01c428f0bd3daab83ffffffff02e8030000000000000151e903000000000000015100000000' + txid = '98229b70948f1c17851a541f1fe532bf02c408267fecf6d7e174c359ae870654' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0085(self): - tx = transaction.Transaction('01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000') - self.assertEqual('570e3730deeea7bd8bc92c836ccdeb4dd4556f2c33f2a1f7b889a4cb4e48d3ab', tx.txid()) + raw_tx = '01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000' + txid = '570e3730deeea7bd8bc92c836ccdeb4dd4556f2c33f2a1f7b889a4cb4e48d3ab' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0086(self): - tx = transaction.Transaction('01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000') - self.assertEqual('e0b8142f587aaa322ca32abce469e90eda187f3851043cc4f2a0fff8c13fc84e', tx.txid()) + raw_tx = '01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000' + txid = 'e0b8142f587aaa322ca32abce469e90eda187f3851043cc4f2a0fff8c13fc84e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0087(self): - tx = transaction.Transaction('0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000') - self.assertEqual('b9ecf72df06b8f98f8b63748d1aded5ffc1a1186f8a302e63cf94f6250e29f4d', tx.txid()) + raw_tx = '0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000' + txid = 'b9ecf72df06b8f98f8b63748d1aded5ffc1a1186f8a302e63cf94f6250e29f4d' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0088(self): - tx = transaction.Transaction('0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000') - self.assertEqual('27eae69aff1dd4388c0fa05cbbfe9a3983d1b0b5811ebcd4199b86f299370aac', tx.txid()) + raw_tx = '0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000' + txid = '27eae69aff1dd4388c0fa05cbbfe9a3983d1b0b5811ebcd4199b86f299370aac' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0089(self): - tx = transaction.Transaction('010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0121037a3fb04bcdb09eba90f69961ba1692a3528e45e67c85b200df820212d7594d334aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000') - self.assertEqual('22d020638e3b7e1f2f9a63124ac76f5e333c74387862e3675f64b25e960d3641', tx.txid()) + raw_tx = '010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0121037a3fb04bcdb09eba90f69961ba1692a3528e45e67c85b200df820212d7594d334aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000' + txid = '22d020638e3b7e1f2f9a63124ac76f5e333c74387862e3675f64b25e960d3641' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0090(self): - tx = transaction.Transaction('0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000') - self.assertEqual('2862bc0c69d2af55da7284d1b16a7cddc03971b77e5a97939cca7631add83bf5', tx.txid()) + raw_tx = '0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000' + txid = '2862bc0c69d2af55da7284d1b16a7cddc03971b77e5a97939cca7631add83bf5' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0091(self): - tx = transaction.Transaction('01000000019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a662896581b0000fd6f01004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601522102cd74a2809ffeeed0092bc124fd79836706e41f048db3f6ae9df8708cefb83a1c2102e615999372426e46fd107b76eaf007156a507584aa2cc21de9eee3bdbd26d36c4c9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175ffffffff0101000000000000000000000000') - self.assertEqual('1aebf0c98f01381765a8c33d688f8903e4d01120589ac92b78f1185dc1f4119c', tx.txid()) + raw_tx = '01000000019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a662896581b0000fd6f01004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601522102cd74a2809ffeeed0092bc124fd79836706e41f048db3f6ae9df8708cefb83a1c2102e615999372426e46fd107b76eaf007156a507584aa2cc21de9eee3bdbd26d36c4c9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175ffffffff0101000000000000000000000000' + txid = '1aebf0c98f01381765a8c33d688f8903e4d01120589ac92b78f1185dc1f4119c' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0092(self): - tx = transaction.Transaction('010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000') - self.assertEqual('45d17fb7db86162b2b6ca29fa4e163acf0ef0b54110e49b819bda1f948d423a3', tx.txid()) + raw_tx = '010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000' + txid = '45d17fb7db86162b2b6ca29fa4e163acf0ef0b54110e49b819bda1f948d423a3' + self._run_naive_tests_on_tx(raw_tx, txid) # txns from Bitcoin Core ends <--- diff --git a/lib/transaction.py b/lib/transaction.py index 4ee57d4e1..fa095d84f 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -607,6 +607,8 @@ class Transaction: @classmethod def get_sorted_pubkeys(self, txin): # sort pubkeys and x_pubkeys, using the order of pubkeys + if txin['type'] == 'coinbase': + return [], [] x_pubkeys = txin['x_pubkeys'] pubkeys = txin.get('pubkeys') if pubkeys is None: @@ -707,6 +709,8 @@ class Transaction: def get_siglist(self, txin, estimate_size=False): # if we have enough signatures, we use the actual pubkeys # otherwise, use extended pubkeys (with bip32 derivation) + if txin['type'] == 'coinbase': + return [], [] num_sig = txin.get('num_sig', 1) if estimate_size: pubkey_size = self.estimate_pubkey_size_for_txin(txin) @@ -728,10 +732,12 @@ class Transaction: @classmethod def serialize_witness(self, txin, estimate_size=False): - add_w = lambda x: var_int(len(x)//2) + x if not self.is_segwit_input(txin): return '00' + if txin['type'] == 'coinbase': + return txin['witness'] pubkeys, sig_list = self.get_siglist(txin, estimate_size) + add_w = lambda x: var_int(len(x) // 2) + x if txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']: witness = var_int(2) + add_w(sig_list[0]) + add_w(pubkeys[0]) elif txin['type'] in ['p2wsh', 'p2wsh-p2sh']: @@ -790,7 +796,9 @@ class Transaction: return script @classmethod - def is_txin_complete(self, txin): + def is_txin_complete(cls, txin): + if txin['type'] == 'coinbase': + return True num_sig = txin.get('num_sig', 1) x_signatures = txin['signatures'] signatures = list(filter(None, x_signatures)) From 851fe0dad512dbf9888638566135d1f8cd0dd853 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 11:22:49 +0100 Subject: [PATCH 010/174] fix #4036 --- gui/qt/qrtextedit.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gui/qt/qrtextedit.py b/gui/qt/qrtextedit.py index aef68f053..cc5c5cf0e 100644 --- a/gui/qt/qrtextedit.py +++ b/gui/qt/qrtextedit.py @@ -46,9 +46,13 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin): fileName, __ = QFileDialog.getOpenFileName(self, 'select file') if not fileName: return - with open(fileName, "r") as f: - data = f.read() - self.setText(data) + try: + with open(fileName, "r") as f: + data = f.read() + except BaseException as e: + self.show_error(_('Error opening file') + ':\n' + str(e)) + else: + self.setText(data) def qr_input(self): from electrum import qrscanner, get_config From 7ec2bcd70fd7b2192a1f0a9e48f093085ad4b876 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Tue, 6 Mar 2018 11:55:43 +0100 Subject: [PATCH 011/174] Use internal GPU on macOS Closes: #4030 --- contrib/build-osx/osx.spec | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/build-osx/osx.spec b/contrib/build-osx/osx.spec index a2c02f39b..2e399ede0 100644 --- a/contrib/build-osx/osx.spec +++ b/contrib/build-osx/osx.spec @@ -93,7 +93,8 @@ app = BUNDLE(exe, name=PACKAGE + '.app', icon=electrum+ICONS_FILE, bundle_identifier=None, - info_plist = { - 'NSHighResolutionCapable':'True' + info_plist={ + 'NSHighResolutionCapable': 'True', + 'NSSupportsAutomaticGraphicsSwitching': 'True' } ) From 135c5e805d4ab0c48e474e93e1491507fbaaae0c Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 12:41:37 +0100 Subject: [PATCH 012/174] fix #4046 --- lib/transaction.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/transaction.py b/lib/transaction.py index fa095d84f..aac770392 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -439,16 +439,16 @@ def parse_input(vds): d['prevout_hash'] = prevout_hash d['prevout_n'] = prevout_n d['sequence'] = sequence + d['x_pubkeys'] = [] + d['pubkeys'] = [] + d['signatures'] = {} + d['address'] = None + d['num_sig'] = 0 if prevout_hash == '00'*32: d['type'] = 'coinbase' d['scriptSig'] = bh2u(scriptSig) else: - d['x_pubkeys'] = [] - d['pubkeys'] = [] - d['signatures'] = {} - d['address'] = None d['type'] = 'unknown' - d['num_sig'] = 0 if scriptSig: d['scriptSig'] = bh2u(scriptSig) try: From efd92aba67081cb0e6807912cc2a385f17d93b96 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 13:48:50 +0100 Subject: [PATCH 013/174] qt history tab: text alignment in columns --- gui/qt/history_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/history_list.py b/gui/qt/history_list.py index 41cb2c092..c7648c1f7 100644 --- a/gui/qt/history_list.py +++ b/gui/qt/history_list.py @@ -256,7 +256,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): item.setIcon(3, QIcon(":icons/seal")) for i in range(len(entry)): if i>3: - item.setTextAlignment(i, Qt.AlignRight) + item.setTextAlignment(i, Qt.AlignRight | Qt.AlignVCenter) if i!=2: item.setFont(i, QFont(MONOSPACE_FONT)) if value and value < 0: From c750ec153beb13b15f3ed955ecfec2f4e376e4bc Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 6 Mar 2018 14:58:00 +0100 Subject: [PATCH 014/174] new kivy gui --- gui/kivy/main.kv | 20 +-- gui/kivy/main_window.py | 19 ++- gui/kivy/uix/context_menu.py | 1 - gui/kivy/uix/dialogs/addresses.py | 212 ++++++++++++++++++++++++++++ gui/kivy/uix/dialogs/invoices.py | 172 ++++++++++++++++++++++ gui/kivy/uix/dialogs/requests.py | 157 ++++++++++++++++++++ gui/kivy/uix/screens.py | 201 +------------------------- gui/kivy/uix/ui_screens/address.kv | 90 ------------ gui/kivy/uix/ui_screens/invoices.kv | 66 --------- gui/kivy/uix/ui_screens/receive.kv | 23 ++- gui/kivy/uix/ui_screens/requests.kv | 66 --------- gui/kivy/uix/ui_screens/send.kv | 23 +-- 12 files changed, 598 insertions(+), 452 deletions(-) create mode 100644 gui/kivy/uix/dialogs/addresses.py create mode 100644 gui/kivy/uix/dialogs/invoices.py create mode 100644 gui/kivy/uix/dialogs/requests.py delete mode 100644 gui/kivy/uix/ui_screens/address.kv delete mode 100644 gui/kivy/uix/ui_screens/invoices.kv delete mode 100644 gui/kivy/uix/ui_screens/requests.kv diff --git a/gui/kivy/main.kv b/gui/kivy/main.kv index 95cfe8def..83b880d73 100644 --- a/gui/kivy/main.kv +++ b/gui/kivy/main.kv @@ -372,9 +372,6 @@ tab_height: '48dp' tab_width: panel.width/3 strip_border: 0, 0, 0, 0 - InvoicesScreen: - id: invoices_screen - tab: invoices_tab SendScreen: id: send_screen tab: send_tab @@ -384,29 +381,18 @@ ReceiveScreen: id: receive_screen tab: receive_tab - AddressScreen: - id: address_screen - tab: address_tab - CleanHeader: - id: invoices_tab - text: _('Invoices') - slide: 0 CleanHeader: id: send_tab text: _('Send') - slide: 1 + slide: 0 CleanHeader: id: history_tab text: _('Balance') - slide: 2 + slide: 1 CleanHeader: id: receive_tab text: _('Receive') - slide: 3 - CleanHeader: - id: address_tab - text: _('Addresses') - slide: 4 + slide: 2 diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py index 7ca40bf8d..c719907c8 100644 --- a/gui/kivy/main_window.py +++ b/gui/kivy/main_window.py @@ -824,7 +824,6 @@ class ElectrumWindow(App): d = LabelDialog(_('Enter description'), text, callback) d.open() - @profiler def amount_dialog(self, screen, show_max): from .uix.dialogs.amount_dialog import AmountDialog amount = screen.amount @@ -836,6 +835,24 @@ class ElectrumWindow(App): popup = AmountDialog(show_max, amount, cb) popup.open() + def invoices_dialog(self, screen): + from .uix.dialogs.invoices import InvoicesDialog + popup = InvoicesDialog(self, screen, None) + popup.update() + popup.open() + + def requests_dialog(self, screen): + from .uix.dialogs.requests import RequestsDialog + popup = RequestsDialog(self, screen, None) + popup.update() + popup.open() + + def addresses_dialog(self, screen): + from .uix.dialogs.addresses import AddressesDialog + popup = AddressesDialog(self, screen, None) + popup.update() + popup.open() + def fee_dialog(self, label, dt): from .uix.dialogs.fee_dialog import FeeDialog def cb(): diff --git a/gui/kivy/uix/context_menu.py b/gui/kivy/uix/context_menu.py index dee0212af..884a2098e 100644 --- a/gui/kivy/uix/context_menu.py +++ b/gui/kivy/uix/context_menu.py @@ -47,7 +47,6 @@ class ContextMenu(Bubble): l = MenuItem() l.text = _(k) def func(f=v): - Clock.schedule_once(lambda dt: self.hide(), 0.1) Clock.schedule_once(lambda dt: f(obj), 0.15) l.on_release = func self.ids.buttons.add_widget(l) diff --git a/gui/kivy/uix/dialogs/addresses.py b/gui/kivy/uix/dialogs/addresses.py new file mode 100644 index 000000000..1866251f2 --- /dev/null +++ b/gui/kivy/uix/dialogs/addresses.py @@ -0,0 +1,212 @@ +from kivy.app import App +from kivy.factory import Factory +from kivy.properties import ObjectProperty +from kivy.lang import Builder +from decimal import Decimal + +Builder.load_string(''' + + text_size: self.width, None + halign: 'left' + valign: 'top' + + + address: '' + memo: '' + amount: '' + status: '' + BoxLayout: + spacing: '8dp' + height: '32dp' + orientation: 'vertical' + Widget + AddressLabel: + text: root.address + shorten: True + Widget + AddressLabel: + text: (root.amount if root.status == 'Funded' else root.status) + ' ' + root.memo + color: .699, .699, .699, 1 + font_size: '13sp' + shorten: True + Widget + + + id: popup + title: _('Addresses') + message: '' + pr_status: 'Pending' + show_change: 0 + show_used: 0 + on_message: + self.parent.update() + BoxLayout: + id:box + padding: '12dp', '70dp', '12dp', '12dp' + spacing: '12dp' + orientation: 'vertical' + size_hint: 1, 1.1 + BoxLayout: + spacing: '6dp' + size_hint: 1, None + orientation: 'horizontal' + AddressFilter: + opacity: 1 + size_hint: 1, None + height: self.minimum_height + spacing: '5dp' + AddressButton: + id: search + text: {0:_('Receiving'), 1:_('Change'), 2:_('All')}[root.show_change] + on_release: + root.show_change = (root.show_change + 1) % 3 + Clock.schedule_once(lambda dt: root.update()) + AddressFilter: + opacity: 1 + size_hint: 1, None + height: self.minimum_height + spacing: '5dp' + AddressButton: + id: search + text: {0:_('All'), 1:_('Unused'), 2:_('Funded'), 3:_('Used')}[root.show_used] + on_release: + root.show_used = (root.show_used + 1) % 4 + Clock.schedule_once(lambda dt: root.update()) + AddressFilter: + opacity: 1 + size_hint: 1, None + height: self.minimum_height + spacing: '5dp' + canvas.before: + Color: + rgba: 0.9, 0.9, 0.9, 1 + AddressButton: + id: change + text: root.message if root.message else _('Search') + on_release: Clock.schedule_once(lambda dt: app.description_dialog(addr_screen)) + ScrollView: + GridLayout: + cols: 1 + id: search_container + size_hint_y: None + height: self.minimum_height +''') + + +from electrum_gui.kivy.i18n import _ +from electrum_gui.kivy.uix.context_menu import ContextMenu + + +class AddressesDialog(Factory.Popup): + + def __init__(self, app, screen, callback): + Factory.Popup.__init__(self) + self.app = app + self.screen = screen + self.callback = callback + self.cards = {} + self.context_menu = None + + def get_card(self, addr, balance, is_used, label): + ci = self.cards.get(addr) + if ci is None: + ci = Factory.AddressItem() + ci.screen = self + ci.address = addr + self.cards[addr] = ci + + ci.memo = label + ci.amount = self.app.format_amount_and_units(balance) + request = self.app.wallet.get_payment_request(addr, self.app.electrum_config) + if is_used: + ci.status = _('Used') + else: + ci.status = _('Funded') if balance > 0 else _('Unused') + return ci + + + def update(self): + self.menu_actions = [(_('Use'), self.do_show), (_('Details'), self.do_view)] + wallet = self.app.wallet + if self.show_change == 0: + _list = wallet.get_receiving_addresses() + elif self.show_change == 1: + _list = wallet.get_change_addresses() + else: + _list = wallet.get_addresses() + search = self.screen.message + container = self.ids.search_container + container.clear_widgets() + n = 0 + for address in _list: + label = wallet.labels.get(address, '') + balance = sum(wallet.get_addr_balance(address)) + is_used = wallet.is_used(address) + if self.show_used == 1 and (balance or is_used): + continue + if self.show_used == 2 and balance == 0: + continue + if self.show_used == 3 and not is_used: + continue + card = self.get_card(address, balance, is_used, label) + if search and not self.ext_search(card, search): + continue + container.add_widget(card) + n += 1 + if not n: + msg = _('No address matching your search') + container.add_widget(EmptyLabel(text=msg)) + + def do_show(self, obj): + self.hide_menu() + self.dismiss() + self.app.show_request(obj.address) + + def do_view(self, obj): + req = self.app.wallet.get_payment_request(obj.address, self.app.electrum_config) + if req: + c, u, x = self.app.wallet.get_addr_balance(obj.address) + balance = c + u + x + if balance > 0: + req['fund'] = balance + status = req.get('status') + amount = req.get('amount') + address = req['address'] + if amount: + status = req.get('status') + status = request_text[status] + else: + received_amount = self.app.wallet.get_addr_received(address) + status = self.app.format_amount_and_units(received_amount) + self.app.show_pr_details(req, status, False) + + else: + req = { 'address': obj.address, 'status' : obj.status } + status = obj.status + c, u, x = self.app.wallet.get_addr_balance(obj.address) + balance = c + u + x + if balance > 0: + req['fund'] = balance + self.app.show_addr_details(req, status) + + def do_delete(self, obj): + from .dialogs.question import Question + def cb(result): + if result: + self.app.wallet.remove_payment_request(obj.address, self.app.electrum_config) + self.update() + d = Question(_('Delete request?'), cb) + d.open() + + def ext_search(self, card, search): + return card.memo.find(search) >= 0 or card.amount.find(search) >= 0 + + def show_menu(self, obj): + self.hide_menu() + self.context_menu = ContextMenu(obj, self.menu_actions) + self.ids.box.add_widget(self.context_menu) + + def hide_menu(self): + if self.context_menu is not None: + self.ids.box.remove_widget(self.context_menu) + self.context_menu = None diff --git a/gui/kivy/uix/dialogs/invoices.py b/gui/kivy/uix/dialogs/invoices.py new file mode 100644 index 000000000..5ba90f496 --- /dev/null +++ b/gui/kivy/uix/dialogs/invoices.py @@ -0,0 +1,172 @@ +from kivy.app import App +from kivy.factory import Factory +from kivy.properties import ObjectProperty +from kivy.lang import Builder +from decimal import Decimal + +Builder.load_string(''' + + #color: .305, .309, .309, 1 + text_size: self.width, None + halign: 'left' + valign: 'top' + + + requestor: '' + memo: '' + amount: '' + status: '' + date: '' + icon: 'atlas://gui/kivy/theming/light/important' + Image: + id: icon + source: root.icon + size_hint: None, 1 + width: self.height *.54 + mipmap: True + BoxLayout: + spacing: '8dp' + height: '32dp' + orientation: 'vertical' + Widget + InvoicesLabel: + text: root.requestor + shorten: True + Widget + InvoicesLabel: + text: root.memo + color: .699, .699, .699, 1 + font_size: '13sp' + shorten: True + Widget + BoxLayout: + spacing: '8dp' + height: '32dp' + orientation: 'vertical' + Widget + InvoicesLabel: + text: root.amount + font_size: '15sp' + halign: 'right' + width: '110sp' + Widget + InvoicesLabel: + text: root.status + font_size: '13sp' + halign: 'right' + color: .699, .699, .699, 1 + Widget + + + + id: popup + title: _('Invoices') + BoxLayout: + id: box + orientation: 'vertical' + spacing: '1dp' + ScrollView: + GridLayout: + cols: 1 + id: invoices_container + size_hint: 1, None + height: self.minimum_height + spacing: '2dp' + padding: '12dp' +''') + +from kivy.properties import BooleanProperty +from electrum_gui.kivy.i18n import _ +from electrum.util import format_time +from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED +from electrum_gui.kivy.uix.context_menu import ContextMenu + +invoice_text = { + PR_UNPAID:_('Pending'), + PR_UNKNOWN:_('Unknown'), + PR_PAID:_('Paid'), + PR_EXPIRED:_('Expired') +} +pr_icon = { + PR_UNPAID: 'atlas://gui/kivy/theming/light/important', + PR_UNKNOWN: 'atlas://gui/kivy/theming/light/important', + PR_PAID: 'atlas://gui/kivy/theming/light/confirmed', + PR_EXPIRED: 'atlas://gui/kivy/theming/light/close' +} + + +class InvoicesDialog(Factory.Popup): + + def __init__(self, app, screen, callback): + Factory.Popup.__init__(self) + self.app = app + self.screen = screen + self.callback = callback + self.cards = {} + self.context_menu = None + + def get_card(self, pr): + key = pr.get_id() + ci = self.cards.get(key) + if ci is None: + ci = Factory.InvoiceItem() + ci.key = key + ci.screen = self + self.cards[key] = ci + ci.requestor = pr.get_requestor() + ci.memo = pr.get_memo() + amount = pr.get_amount() + if amount: + ci.amount = self.app.format_amount_and_units(amount) + status = self.app.wallet.invoices.get_status(ci.key) + ci.status = invoice_text[status] + ci.icon = pr_icon[status] + else: + ci.amount = _('No Amount') + ci.status = '' + exp = pr.get_expiration_date() + ci.date = format_time(exp) if exp else _('Never') + return ci + + def update(self): + self.menu_actions = [('Pay', self.do_pay), ('Details', self.do_view), ('Delete', self.do_delete)] + invoices_list = self.ids.invoices_container + invoices_list.clear_widgets() + _list = self.app.wallet.invoices.sorted_list() + for pr in _list: + ci = self.get_card(pr) + invoices_list.add_widget(ci) + if not _list: + msg = _('This screen shows the list of payment requests that have been sent to you. You may also use it to store contact addresses.') + invoices_list.add_widget(EmptyLabel(text=msg)) + + def do_pay(self, obj): + self.hide_menu() + self.dismiss() + pr = self.app.wallet.invoices.get(obj.key) + self.app.on_pr(pr) + + def do_view(self, obj): + pr = self.app.wallet.invoices.get(obj.key) + pr.verify(self.app.wallet.contacts) + self.app.show_pr_details(pr.get_dict(), obj.status, True) + + def do_delete(self, obj): + from .question import Question + def cb(result): + if result: + self.app.wallet.invoices.remove(obj.key) + self.hide_menu() + self.update() + d = Question(_('Delete invoice?'), cb) + d.open() + + def show_menu(self, obj): + self.hide_menu() + self.context_menu = ContextMenu(obj, self.menu_actions) + self.ids.box.add_widget(self.context_menu) + + def hide_menu(self): + if self.context_menu is not None: + self.ids.box.remove_widget(self.context_menu) + self.context_menu = None diff --git a/gui/kivy/uix/dialogs/requests.py b/gui/kivy/uix/dialogs/requests.py new file mode 100644 index 000000000..266a40436 --- /dev/null +++ b/gui/kivy/uix/dialogs/requests.py @@ -0,0 +1,157 @@ +from kivy.app import App +from kivy.factory import Factory +from kivy.properties import ObjectProperty +from kivy.lang import Builder +from decimal import Decimal + +Builder.load_string(''' + + #color: .305, .309, .309, 1 + text_size: self.width, None + halign: 'left' + valign: 'top' + + + address: '' + memo: '' + amount: '' + status: '' + date: '' + icon: 'atlas://gui/kivy/theming/light/important' + Image: + id: icon + source: root.icon + size_hint: None, 1 + width: self.height *.54 + mipmap: True + BoxLayout: + spacing: '8dp' + height: '32dp' + orientation: 'vertical' + Widget + RequestLabel: + text: root.address + shorten: True + Widget + RequestLabel: + text: root.memo + color: .699, .699, .699, 1 + font_size: '13sp' + shorten: True + Widget + BoxLayout: + spacing: '8dp' + height: '32dp' + orientation: 'vertical' + Widget + RequestLabel: + text: root.amount + halign: 'right' + font_size: '15sp' + Widget + RequestLabel: + text: root.status + halign: 'right' + font_size: '13sp' + color: .699, .699, .699, 1 + Widget + + + id: popup + title: _('Requests') + BoxLayout: + id:box + orientation: 'vertical' + spacing: '1dp' + ScrollView: + GridLayout: + cols: 1 + id: requests_container + size_hint: 1, None + height: self.minimum_height + spacing: '2dp' + padding: '12dp' +''') + +from kivy.properties import BooleanProperty +from electrum_gui.kivy.i18n import _ +from electrum.util import format_time +from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED +from electrum_gui.kivy.uix.context_menu import ContextMenu + +pr_icon = { + PR_UNPAID: 'atlas://gui/kivy/theming/light/important', + PR_UNKNOWN: 'atlas://gui/kivy/theming/light/important', + PR_PAID: 'atlas://gui/kivy/theming/light/confirmed', + PR_EXPIRED: 'atlas://gui/kivy/theming/light/close' +} +request_text = { + PR_UNPAID: _('Pending'), + PR_UNKNOWN: _('Unknown'), + PR_PAID: _('Received'), + PR_EXPIRED: _('Expired') +} + + +class RequestsDialog(Factory.Popup): + + def __init__(self, app, screen, callback): + Factory.Popup.__init__(self) + self.app = app + self.screen = screen + self.callback = callback + self.cards = {} + self.context_menu = None + + def get_card(self, req): + address = req['address'] + ci = self.cards.get(address) + if ci is None: + ci = Factory.RequestItem() + ci.address = address + ci.screen = self + self.cards[address] = ci + + amount = req.get('amount') + ci.amount = self.app.format_amount_and_units(amount) if amount else '' + ci.memo = req.get('memo', '') + status, conf = self.app.wallet.get_request_status(address) + ci.status = request_text[status] + ci.icon = pr_icon[status] + #exp = pr.get_expiration_date() + #ci.date = format_time(exp) if exp else _('Never') + return ci + + def update(self): + self.menu_actions = [(_('Show'), self.do_show), (_('Delete'), self.do_delete)] + requests_list = self.ids.requests_container + requests_list.clear_widgets() + _list = self.app.wallet.get_sorted_requests(self.app.electrum_config) + for pr in _list: + ci = self.get_card(pr) + requests_list.add_widget(ci) + + def do_show(self, obj): + self.hide_menu() + self.dismiss() + self.app.show_request(obj.address) + + def do_delete(self, req): + from .question import Question + def cb(result): + if result: + self.app.wallet.remove_payment_request(req.address, self.app.electrum_config) + self.hide_menu() + self.update() + d = Question(_('Delete request'), cb) + d.open() + + def show_menu(self, obj): + self.hide_menu() + self.context_menu = ContextMenu(obj, self.menu_actions) + self.ids.box.add_widget(self.context_menu) + + def hide_menu(self): + if self.context_menu is not None: + self.ids.box.remove_widget(self.context_menu) + self.context_menu = None diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py index 5664d439d..255e2d009 100644 --- a/gui/kivy/uix/screens.py +++ b/gui/kivy/uix/screens.py @@ -390,205 +390,10 @@ class ReceiveScreen(CScreen): addr = self.get_new_address() if not addr: self.app.show_info(_('Please use the existing requests first.')) - else: - self.save_request() - self.app.show_info(_('New request added to your list.')) - - -invoice_text = { - PR_UNPAID:_('Pending'), - PR_UNKNOWN:_('Unknown'), - PR_PAID:_('Paid'), - PR_EXPIRED:_('Expired') -} -request_text = { - PR_UNPAID: _('Pending'), - PR_UNKNOWN: _('Unknown'), - PR_PAID: _('Received'), - PR_EXPIRED: _('Expired') -} -pr_icon = { - PR_UNPAID: 'atlas://gui/kivy/theming/light/important', - PR_UNKNOWN: 'atlas://gui/kivy/theming/light/important', - PR_PAID: 'atlas://gui/kivy/theming/light/confirmed', - PR_EXPIRED: 'atlas://gui/kivy/theming/light/close' -} - - -class InvoicesScreen(CScreen): - kvname = 'invoices' - cards = {} - - def get_card(self, pr): - key = pr.get_id() - ci = self.cards.get(key) - if ci is None: - ci = Factory.InvoiceItem() - ci.key = key - ci.screen = self - self.cards[key] = ci - - ci.requestor = pr.get_requestor() - ci.memo = pr.get_memo() - amount = pr.get_amount() - if amount: - ci.amount = self.app.format_amount_and_units(amount) - status = self.app.wallet.invoices.get_status(ci.key) - ci.status = invoice_text[status] - ci.icon = pr_icon[status] - else: - ci.amount = _('No Amount') - ci.status = '' - exp = pr.get_expiration_date() - ci.date = format_time(exp) if exp else _('Never') - return ci - - def update(self): - self.menu_actions = [('Pay', self.do_pay), ('Details', self.do_view), ('Delete', self.do_delete)] - invoices_list = self.screen.ids.invoices_container - invoices_list.clear_widgets() - _list = self.app.wallet.invoices.sorted_list() - for pr in _list: - ci = self.get_card(pr) - invoices_list.add_widget(ci) - if not _list: - msg = _('This screen shows the list of payment requests that have been sent to you. You may also use it to store contact addresses.') - invoices_list.add_widget(EmptyLabel(text=msg)) - - def do_pay(self, obj): - pr = self.app.wallet.invoices.get(obj.key) - self.app.on_pr(pr) - - def do_view(self, obj): - pr = self.app.wallet.invoices.get(obj.key) - pr.verify(self.app.wallet.contacts) - self.app.show_pr_details(pr.get_dict(), obj.status, True) - - def do_delete(self, obj): - from .dialogs.question import Question - def cb(result): - if result: - self.app.wallet.invoices.remove(obj.key) - self.app.update_tab('invoices') - d = Question(_('Delete invoice?'), cb) - d.open() - - -address_icon = { - 'Pending' : 'atlas://gui/kivy/theming/light/important', - 'Paid' : 'atlas://gui/kivy/theming/light/confirmed' -} - -class AddressScreen(CScreen): - kvname = 'address' - cards = {} - - def get_card(self, addr, balance, is_used, label): - ci = self.cards.get(addr) - if ci is None: - ci = Factory.AddressItem() - ci.screen = self - ci.address = addr - self.cards[addr] = ci - - ci.memo = label - ci.amount = self.app.format_amount_and_units(balance) - request = self.app.wallet.get_payment_request(addr, self.app.electrum_config) - if is_used: - ci.status = _('Used') - elif request: - status, conf = self.app.wallet.get_request_status(addr) - requested_amount = request.get('amount') - # make sure that requested amount is > 0 - if status == PR_PAID: - s = _('Request paid') - elif status == PR_UNPAID: - s = _('Request pending') - elif status == PR_EXPIRED: - s = _('Request expired') - else: - s = '' - ci.status = s + ': ' + self.app.format_amount_and_units(requested_amount) - else: - ci.status = _('Funded') if balance>0 else _('Unused') - return ci - - - def update(self): - self.menu_actions = [('Receive', self.do_show), ('Details', self.do_view)] - wallet = self.app.wallet - if self.screen.show_change == 0: - _list = wallet.get_receiving_addresses() - elif self.screen.show_change == 1: - _list = wallet.get_change_addresses() - else: - _list = wallet.get_addresses() - search = self.screen.message - container = self.screen.ids.search_container - container.clear_widgets() - n = 0 - for address in _list: - label = wallet.labels.get(address, '') - balance = sum(wallet.get_addr_balance(address)) - is_used = wallet.is_used(address) - if self.screen.show_used == 1 and (balance or is_used): - continue - if self.screen.show_used == 2 and balance == 0: - continue - if self.screen.show_used == 3 and not is_used: - continue - card = self.get_card(address, balance, is_used, label) - if search and not self.ext_search(card, search): - continue - container.add_widget(card) - n += 1 - if not n: - msg = _('No address matching your search') - container.add_widget(EmptyLabel(text=msg)) - - def do_show(self, obj): - self.app.show_request(obj.address) - - def do_view(self, obj): - req = self.app.wallet.get_payment_request(obj.address, self.app.electrum_config) - if req: - c, u, x = self.app.wallet.get_addr_balance(obj.address) - balance = c + u + x - if balance > 0: - req['fund'] = balance - status = req.get('status') - amount = req.get('amount') - address = req['address'] - if amount: - status = req.get('status') - status = request_text[status] - else: - received_amount = self.app.wallet.get_addr_received(address) - status = self.app.format_amount_and_units(received_amount) - self.app.show_pr_details(req, status, False) - - else: - req = { 'address': obj.address, 'status' : obj.status } - status = obj.status - c, u, x = self.app.wallet.get_addr_balance(obj.address) - balance = c + u + x - if balance > 0: - req['fund'] = balance - self.app.show_addr_details(req, status) - - def do_delete(self, obj): - from .dialogs.question import Question - def cb(result): - if result: - self.app.wallet.remove_payment_request(obj.address, self.app.electrum_config) - self.update() - d = Question(_('Delete request?'), cb) - d.open() - - def ext_search(self, card, search): - return card.memo.find(search) >= 0 or card.amount.find(search) >= 0 - + def do_save(self): + self.save_request() + self.app.show_info(_('Request was saved.')) class TabbedCarousel(Factory.TabbedPanel): diff --git a/gui/kivy/uix/ui_screens/address.kv b/gui/kivy/uix/ui_screens/address.kv deleted file mode 100644 index 07bb43677..000000000 --- a/gui/kivy/uix/ui_screens/address.kv +++ /dev/null @@ -1,90 +0,0 @@ -#:import _ electrum_gui.kivy.i18n._ -#:import Decimal decimal.Decimal -#:set btc_symbol chr(171) -#:set mbtc_symbol chr(187) -#:set font_light 'gui/kivy/data/fonts/Roboto-Condensed.ttf' - - - text_size: self.width, None - halign: 'left' - valign: 'top' - - - address: '' - memo: '' - amount: '' - status: '' - BoxLayout: - spacing: '8dp' - height: '32dp' - orientation: 'vertical' - Widget - AddressLabel: - text: root.address - shorten: True - Widget - AddressLabel: - text: (root.amount if root.status == 'Funded' else root.status) + ' ' + root.memo - color: .699, .699, .699, 1 - font_size: '13sp' - shorten: True - Widget - -AddressScreen: - id: addr_screen - name: 'address' - message: '' - pr_status: 'Pending' - show_change: 0 - show_used: 0 - on_message: - self.parent.update() - BoxLayout - padding: '12dp', '70dp', '12dp', '12dp' - spacing: '12dp' - orientation: 'vertical' - size_hint: 1, 1.1 - BoxLayout: - spacing: '6dp' - size_hint: 1, None - orientation: 'horizontal' - AddressFilter: - opacity: 1 - size_hint: 1, None - height: self.minimum_height - spacing: '5dp' - AddressButton: - id: search - text: {0:_('Receiving'), 1:_('Change'), 2:_('All')}[root.show_change] - on_release: - root.show_change = (root.show_change + 1) % 3 - Clock.schedule_once(lambda dt: app.address_screen.update()) - AddressFilter: - opacity: 1 - size_hint: 1, None - height: self.minimum_height - spacing: '5dp' - AddressButton: - id: search - text: {0:_('All'), 1:_('Unused'), 2:_('Funded'), 3:_('Used')}[root.show_used] - on_release: - root.show_used = (root.show_used + 1) % 4 - Clock.schedule_once(lambda dt: app.address_screen.update()) - AddressFilter: - opacity: 1 - size_hint: 1, None - height: self.minimum_height - spacing: '5dp' - canvas.before: - Color: - rgba: 0.9, 0.9, 0.9, 1 - AddressButton: - id: change - text: root.message if root.message else _('Search') - on_release: Clock.schedule_once(lambda dt: app.description_dialog(addr_screen)) - ScrollView: - GridLayout: - cols: 1 - id: search_container - size_hint_y: None - height: self.minimum_height diff --git a/gui/kivy/uix/ui_screens/invoices.kv b/gui/kivy/uix/ui_screens/invoices.kv deleted file mode 100644 index 9621a8af6..000000000 --- a/gui/kivy/uix/ui_screens/invoices.kv +++ /dev/null @@ -1,66 +0,0 @@ - - #color: .305, .309, .309, 1 - text_size: self.width, None - halign: 'left' - valign: 'top' - - - requestor: '' - memo: '' - amount: '' - status: '' - date: '' - icon: 'atlas://gui/kivy/theming/light/important' - Image: - id: icon - source: root.icon - size_hint: None, 1 - width: self.height *.54 - mipmap: True - BoxLayout: - spacing: '8dp' - height: '32dp' - orientation: 'vertical' - Widget - InvoicesLabel: - text: root.requestor - shorten: True - Widget - InvoicesLabel: - text: root.memo - color: .699, .699, .699, 1 - font_size: '13sp' - shorten: True - Widget - BoxLayout: - spacing: '8dp' - height: '32dp' - orientation: 'vertical' - Widget - InvoicesLabel: - text: root.amount - font_size: '15sp' - halign: 'right' - width: '110sp' - Widget - InvoicesLabel: - text: root.status - font_size: '13sp' - halign: 'right' - color: .699, .699, .699, 1 - Widget - - -InvoicesScreen: - name: 'invoices' - BoxLayout: - orientation: 'vertical' - spacing: '1dp' - ScrollView: - GridLayout: - cols: 1 - id: invoices_container - size_hint: 1, None - height: self.minimum_height - spacing: '2dp' - padding: '12dp' diff --git a/gui/kivy/uix/ui_screens/receive.kv b/gui/kivy/uix/ui_screens/receive.kv index c58b77ec7..650be40ff 100644 --- a/gui/kivy/uix/ui_screens/receive.kv +++ b/gui/kivy/uix/ui_screens/receive.kv @@ -70,7 +70,7 @@ ReceiveScreen: id: address_label text: s.address if s.address else _('Bitcoin Address') shorten: True - disabled: True + on_release: Clock.schedule_once(lambda dt: app.addresses_dialog(s)) CardSeparator: opacity: message_selection.opacity color: blue_bottom.foreground_color @@ -110,16 +110,31 @@ ReceiveScreen: BoxLayout: size_hint: 1, None height: '48dp' + IconButton: + icon: 'atlas://gui/kivy/theming/light/save' + size_hint: 0.6, None + height: '48dp' + on_release: s.parent.do_save() Button: - text: _('Copy') + text: _('Requests') size_hint: 1, None height: '48dp' - on_release: s.parent.do_copy() + on_release: Clock.schedule_once(lambda dt: app.requests_dialog(s)) Button: - text: _('Share') + text: _('Copy') size_hint: 1, None height: '48dp' + on_release: s.parent.do_copy() + IconButton: + icon: 'atlas://gui/kivy/theming/light/share' + size_hint: 0.6, None + height: '48dp' on_release: s.parent.do_share() + BoxLayout: + size_hint: 1, None + height: '48dp' + Widget + size_hint: 2, 1 Button: text: _('New') size_hint: 1, None diff --git a/gui/kivy/uix/ui_screens/requests.kv b/gui/kivy/uix/ui_screens/requests.kv deleted file mode 100644 index 1e39ec7db..000000000 --- a/gui/kivy/uix/ui_screens/requests.kv +++ /dev/null @@ -1,66 +0,0 @@ - - #color: .305, .309, .309, 1 - text_size: self.width, None - halign: 'left' - valign: 'top' - - - address: '' - memo: '' - amount: '' - status: '' - date: '' - icon: 'atlas://gui/kivy/theming/light/important' - Image: - id: icon - source: root.icon - size_hint: None, 1 - width: self.height *.54 - mipmap: True - BoxLayout: - spacing: '8dp' - height: '32dp' - orientation: 'vertical' - Widget - RequestLabel: - text: root.address - shorten: True - Widget - RequestLabel: - text: root.memo - color: .699, .699, .699, 1 - font_size: '13sp' - shorten: True - Widget - BoxLayout: - spacing: '8dp' - height: '32dp' - orientation: 'vertical' - Widget - RequestLabel: - text: root.amount - halign: 'right' - font_size: '15sp' - Widget - RequestLabel: - text: root.status - halign: 'right' - font_size: '13sp' - color: .699, .699, .699, 1 - Widget - - - -RequestsScreen: - name: 'requests' - BoxLayout: - orientation: 'vertical' - spacing: '1dp' - ScrollView: - GridLayout: - cols: 1 - id: requests_container - size_hint_y: None - height: self.minimum_height - spacing: '2dp' - padding: '12dp' diff --git a/gui/kivy/uix/ui_screens/send.kv b/gui/kivy/uix/ui_screens/send.kv index f2a361e37..7269cb9b6 100644 --- a/gui/kivy/uix/ui_screens/send.kv +++ b/gui/kivy/uix/ui_screens/send.kv @@ -34,6 +34,7 @@ SendScreen: text: s.address if s.address else _('Recipient') shorten: True on_release: Clock.schedule_once(lambda dt: app.show_info(_('Copy and paste the recipient address using the Paste button, or use the camera to scan a QR code.'))) + #on_release: Clock.schedule_once(lambda dt: app.popup_dialog('contacts')) CardSeparator: opacity: int(not root.is_pr) color: blue_bottom.foreground_color @@ -93,25 +94,29 @@ SendScreen: size_hint: 1, None height: '48dp' IconButton: - id: qr size_hint: 0.6, 1 - on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=app.on_qr)) - icon: 'atlas://gui/kivy/theming/light/camera' + on_release: s.parent.do_save() + icon: 'atlas://gui/kivy/theming/light/save' + Button: + text: _('Invoices') + size_hint: 1, 1 + on_release: Clock.schedule_once(lambda dt: app.invoices_dialog(s)) Button: text: _('Paste') on_release: s.parent.do_paste() - Button: - text: _('Clear') - on_release: s.parent.do_clear() IconButton: + id: qr size_hint: 0.6, 1 - on_release: s.parent.do_save() - icon: 'atlas://gui/kivy/theming/light/save' + on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=app.on_qr)) + icon: 'atlas://gui/kivy/theming/light/camera' BoxLayout: size_hint: 1, None height: '48dp' + Button: + text: _('Clear') + on_release: s.parent.do_clear() Widget: - size_hint: 2, 1 + size_hint: 1, 1 Button: text: _('Pay') size_hint: 1, 1 From a6e23ae275da6dddb6ed93b5a895d6b119e6fd8d Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 6 Mar 2018 15:13:19 +0100 Subject: [PATCH 015/174] share icon file --- gui/kivy/theming/light/share.png | Bin 0 -> 3325 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 gui/kivy/theming/light/share.png diff --git a/gui/kivy/theming/light/share.png b/gui/kivy/theming/light/share.png new file mode 100644 index 0000000000000000000000000000000000000000..d0dc761d4544fc091d2e59a22109fc60ffaa0a61 GIT binary patch literal 3325 zcmZu!cTm&Y68dJZqAM%fplM?oB zd7D9kBYmc+rVL#Fv+`O?QwbRg4|UU*06+!#$3Q?<4l5x^=B=TnLbggoN^uX2q&##5 z0P0u`WkmzO`Tg8LlQcul{&q)Jx9-c}l|937Nic=ZjRUdzOI8*Vt+4;ZdyhD zyc#(_Z)yKMB(s_GPWF6MS@?cxNv7r^#s+L6g#e&HI7NpwSQrgZ|4-P> zuw1R57wB?Y#|p`TtC8L1?s`ovn}f1;biIxnJ*Is!Ha;2K#Ga0I5mLMyrc(ZBv@I*q z6?|jrRwWIlnUm}H@x^o9>J_I=*`n+HJ=gBPsFqgzhJ!`(l0`rW3K%C>b)Qh4j+Wn* zvGE@y{qI-YrGC5vY)KGk2*vWsmSC{`3|l?PZO`?yudI(EDT=lDjmmt zE(=gN$ib-``qJhj0HUt?)wN8G!5dyKFJSEV5vUkLKuMHGHYT8%S`3HhqR5ShCm{*N zbd##X5__lNhr;4Jz7=3rurXN@EpJqUxE}|8Wm1vc2E3Jx4Z(l<8NH(OzBKg#%97|F z1U?#Z7XL8Iq${YJn z-eprKsY_IaBq`4n!_phUZg%#IgsJid(9B5|B9^EIg|qE7!`~jNf_?viDrao zTIV$^KSZ`gpHQckBD?x-_Dvkg{3aII;a>Cu~+{X;=X{7rSn612tY-ySUv3PSa> zbkGo0V-#hDRF>fj^2%2d&jRtP+D4RkK^XokG_ZGRrEZ;hX7l6DTNqg<@#j23b84en z72n#yw>6?lC+_@Fig8N#L}Zu!HUx9+6>!XP813UpEntW9^?}hC9&iiX+GEN4Io*lkfwD5wxux(zsdW zStXJ5CH?yRxdiS7u~ve47(QP=(t@6pNtDr2Ho7HtpH9P#-mv*D>(%Pnf-KF(v>b8dVCCQklKd}ykD2dMrY<}SgK{tUGVZx@ z1pj;~rzaxxy`t;Qi&FgB#_b6O{&9aUDV7e?<&2Bto_9Y>l}9E{<*_9KInVif65oyv zHvf5K``U6x$;sIwvXkM#&TdC%?7o!l%j=$NW+Peo1rFJ==2XeO3X(j78<{O!^>_An z1Bxxnrwu;Nws{D^U2ugD#kLc8+@ri66dlQv$QEFP)GuW>Gg5^YxLdW$4^oQMyN zd5DtpMOZ(549#VK2Ld0s8H3r??!Q0dynWG+lwk*Hwun%|$vaoeU|aDbKfRgl^blE! z+KJe|nU4nQ*X&#?=k8#{KtK8%wjQ2E!WU*k7ydzE_ztC*mh4?{4{`70aRked0RD&B zjxC?plq|$=mmum%SEo8Z9&6p#)2WTn=g|Z)(Xu#MU%s_6cxThNUQ8 zTXGwA)zNrA=Yx#hUxaMPS6;{QK)GR22RF_6lt3Xel~?}C`Dx+#`9Mt9&60PgM;wgP zKb{|(6G4`Ra!B01cDM2(uv3oJFpMTBB3R_CPyXn-Y|60^dyqW$I%&2U=)~}vl-1jT zs2c_6gEWKunPZ)4t9o|L09&?8O}p2u_u4ge+b$%K|7p-_P=)0wQxKoG=(K~cD--}4y@2yk`%DW*rIrYj zv-KtX;)T{jC_g3!5R{0$UvY(b=qrJ?EA0+avw5{c+LC)EeM{Pfqt*^oW5&v`O#kG% z$y&^%7A5x~zsyZKPR`zZ3}>W;29)>|(aKhtjeYBPqx6s#PV(X3i?he4JY$4Gk#Ic( zo2960vFQ+TFLtS^1+vud1{JCdzCEy zzR>-^$p1km`clDd(yyD8CG06J3c1gg< zNycS2;<`lE6?U!5?@G%Xdt`wxK=2SyBX#auiJDind4GM(I=*pCbC!`RzvAB$sYNpW z6Yz%fC@D^Jt{b8t(3DqJm)Gqw^Nrks!S{mh2PF2by^E}KM*05Gv}n;)M3@1VVhb{1 zB|3f}TP5h@C7s`d+s7f@P89|7z4Fz3cY3((y?w0EIk%{F{;4#rpy5imDpsDS>fAsj zj(M4e_o86CB?6&L*0+6$yCiCrGWm;0h~@w-?Z64RskrPqcm7#0blPpxCJftp{j;Yx zfqLRsrYkKbc$sX!Uokbz%&8NOc363$Y2piiGKkV2$PBprng0HuE9?6M&tK_NE+h@+ z5uQA$P61t1vGS2uli&xq%Om9hjA zDr96)B6W}KeZQLXf}Ri6*eRvnJtToUD+YoiUF6P0dV+32XQK$19C{oPgoPjyyUvfD z*EDjF)AEXyEHD=k&BT55GeWb9gc|}v$uKg{2CuuOjjo_M|36gfc5+bq-?-!$1R!)1 z`6W3}D7z&?{Qcg&Z{Tg~_qzS;JGaWFIjEr>a>+EmY~1IVtDb#Bc`aKolX@R=V5kU^ z?$8TghyZSlOOA!R>s%A6Ypv&2267wD-_Z4iC20cXKvRjDz+#$yOG?F%87|$bmdu%V z{%&XIObkWd148o$rYhImdU7qpYx$|z-gz}>2r0IVt*@pVD?wxk4Tj3ds0G*Ml_RNo+6EuUVWScx*mxX0j1SG{>?nKlWP_FH6xM|ft z1D~2*8BqyD05k-Q4?Pb)*f(*~V#_GADdpkQwW1@;k-{LU0s<5@KBGrzu>IOc&#m$~ zdK}djdudP9q%7I`h?kR}J~!^1Z~c_qwG)(=)*NbMZcMfKt(Dq7ppjI&&- ziqmarPfd+Og>3D>TNI9g_3H%Dc(xK;xV*7W7RV0rlZ7FlB=j)f2I v4=P)UB@*=B(_-Ac|D)^wOYL`8oYAK9o$vOycUKcWDu9NHwsPfT>#% Date: Tue, 6 Mar 2018 16:07:33 +0100 Subject: [PATCH 016/174] fix #4032 --- lib/contacts.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/contacts.py b/lib/contacts.py index 0015a8610..3e062f327 100644 --- a/lib/contacts.py +++ b/lib/contacts.py @@ -22,13 +22,14 @@ # SOFTWARE. import re import dns +from dns.exception import DNSException import json import traceback import sys from . import bitcoin from . import dnssec -from .util import export_meta, import_meta +from .util import export_meta, import_meta, print_error class Contacts(dict): @@ -96,7 +97,11 @@ class Contacts(dict): def resolve_openalias(self, url): # support email-style addresses, per the OA standard url = url.replace('@', '.') - records, validated = dnssec.query(url, dns.rdatatype.TXT) + try: + records, validated = dnssec.query(url, dns.rdatatype.TXT) + except DNSException as e: + print_error('Error resolving openalias: ', str(e)) + return None prefix = 'btc' for record in records: string = record.strings[0] From 0c2547543d69a0d9758f9687cb1c00c3569f5926 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 16:08:49 +0100 Subject: [PATCH 017/174] fix openalias py3 --- lib/contacts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/contacts.py b/lib/contacts.py index 3e062f327..03b8d3ecc 100644 --- a/lib/contacts.py +++ b/lib/contacts.py @@ -29,7 +29,7 @@ import sys from . import bitcoin from . import dnssec -from .util import export_meta, import_meta, print_error +from .util import export_meta, import_meta, print_error, to_string class Contacts(dict): @@ -104,7 +104,7 @@ class Contacts(dict): return None prefix = 'btc' for record in records: - string = record.strings[0] + string = to_string(record.strings[0], 'utf8') if string.startswith('oa1:' + prefix): address = self.find_regex(string, r'recipient_address=([A-Za-z0-9]+)') name = self.find_regex(string, r'recipient_name=([^;]+)') From d0025491760aab9040ba31e2683adb220fd80345 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 16:37:17 +0100 Subject: [PATCH 018/174] wallet.get_full_history: only do fiat calc, if enabled --- lib/wallet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index ca3a18ea7..7e9a884cb 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1032,7 +1032,7 @@ class Abstract_Wallet(PrintError): else: income += value # fiat computations - if fx is not None: + if fx and fx.is_enabled(): date = timestamp_to_datetime(timestamp) fiat_value = self.get_fiat_value(tx_hash, fx.ccy) fiat_default = fiat_value is None @@ -1069,7 +1069,7 @@ class Abstract_Wallet(PrintError): 'income': Satoshis(income), 'expenditures': Satoshis(expenditures) } - if fx: + if fx and fx.is_enabled(): unrealized = self.unrealized_gains(domain, fx.timestamp_rate, fx.ccy) summary['capital_gains'] = Fiat(capital_gains, fx.ccy) summary['fiat_income'] = Fiat(fiat_income, fx.ccy) From db0e3cd2091bc3ede16106ca794f96de10eeaab9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 16:56:51 +0100 Subject: [PATCH 019/174] speed-up wallet.get_full_history: cache coin_price --- lib/wallet.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index 7e9a884cb..3d5488f2c 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -229,6 +229,8 @@ class Abstract_Wallet(PrintError): self.invoices = InvoiceStore(self.storage) self.contacts = Contacts(self.storage) + self.coin_price_cache = {} + def diagnostic_name(self): return self.basename() @@ -1757,15 +1759,22 @@ class Abstract_Wallet(PrintError): Acquisition price of a coin. This assumes that either all inputs are mine, or no input is mine. """ + cache_key = "{}:{}:{}".format(str(txid), str(ccy), str(txin_value)) + result = self.coin_price_cache.get(cache_key, None) + if result is not None: + return result if self.txi.get(txid, {}) != {}: - return self.average_price(txid, price_func, ccy) * txin_value/Decimal(COIN) + result = self.average_price(txid, price_func, ccy) * txin_value/Decimal(COIN) else: fiat_value = self.get_fiat_value(txid, ccy) if fiat_value is not None: - return fiat_value + result = fiat_value else: p = self.price_at_timestamp(txid, price_func) - return p * txin_value/Decimal(COIN) + result = p * txin_value/Decimal(COIN) + self.coin_price_cache[cache_key] = result + return result + class Simple_Wallet(Abstract_Wallet): # wallet with a single keystore From e0cda39ae5ebe84eb8fb0a188c9965e7dcf8dbfe Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 17:15:14 +0100 Subject: [PATCH 020/174] only cache expensive case --- lib/wallet.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index 3d5488f2c..892d66a12 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1765,15 +1765,15 @@ class Abstract_Wallet(PrintError): return result if self.txi.get(txid, {}) != {}: result = self.average_price(txid, price_func, ccy) * txin_value/Decimal(COIN) + self.coin_price_cache[cache_key] = result + return result else: fiat_value = self.get_fiat_value(txid, ccy) if fiat_value is not None: - result = fiat_value + return fiat_value else: p = self.price_at_timestamp(txid, price_func) - result = p * txin_value/Decimal(COIN) - self.coin_price_cache[cache_key] = result - return result + return p * txin_value/Decimal(COIN) class Simple_Wallet(Abstract_Wallet): From 3a5a0af48f4df77bae8f4c9df1eeddbee267e4f0 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 17:20:22 +0100 Subject: [PATCH 021/174] fix #4047 --- gui/qt/main_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index e0406df0a..6ced61be3 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -664,7 +664,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): fiat_e.is_last_edited = (edit == fiat_e) amount = edit.get_amount() rate = self.fx.exchange_rate() if self.fx else None - if rate is None or amount is None: + if rate.is_nan() or amount is None: if edit is fiat_e: btc_e.setText("") if fee_e: From dc2bb7d81f0da8b290954ba11a27ebeb8f4a07e8 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 17:26:59 +0100 Subject: [PATCH 022/174] fix prev --- gui/qt/main_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 6ced61be3..27a4b0165 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -663,7 +663,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): edit.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet()) fiat_e.is_last_edited = (edit == fiat_e) amount = edit.get_amount() - rate = self.fx.exchange_rate() if self.fx else None + rate = self.fx.exchange_rate() if self.fx else Decimal('NaN') if rate.is_nan() or amount is None: if edit is fiat_e: btc_e.setText("") From ca07399937495579d3f3d010f751a87a196a1613 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 6 Mar 2018 18:07:38 +0100 Subject: [PATCH 023/174] fix #4050 --- gui/kivy/uix/dialogs/invoices.py | 3 --- gui/kivy/uix/screens.py | 2 -- 2 files changed, 5 deletions(-) diff --git a/gui/kivy/uix/dialogs/invoices.py b/gui/kivy/uix/dialogs/invoices.py index 5ba90f496..d8e4f884d 100644 --- a/gui/kivy/uix/dialogs/invoices.py +++ b/gui/kivy/uix/dialogs/invoices.py @@ -136,9 +136,6 @@ class InvoicesDialog(Factory.Popup): for pr in _list: ci = self.get_card(pr) invoices_list.add_widget(ci) - if not _list: - msg = _('This screen shows the list of payment requests that have been sent to you. You may also use it to store contact addresses.') - invoices_list.add_widget(EmptyLabel(text=msg)) def do_pay(self, obj): self.hide_menu() diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py index 255e2d009..d8e283362 100644 --- a/gui/kivy/uix/screens.py +++ b/gui/kivy/uix/screens.py @@ -27,8 +27,6 @@ from .context_menu import ContextMenu from electrum_gui.kivy.i18n import _ -class EmptyLabel(Factory.Label): - pass class CScreen(Factory.Screen): __events__ = ('on_activate', 'on_deactivate', 'on_enter', 'on_leave') From 971a6979ee04055482371f30647b4ad9c886c3df Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 6 Mar 2018 19:36:57 +0100 Subject: [PATCH 024/174] kivy: do not open invoices, requests dialogs if list is empty --- gui/kivy/main_window.py | 10 ++++++++++ gui/kivy/uix/screens.py | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py index c719907c8..be9409d65 100644 --- a/gui/kivy/main_window.py +++ b/gui/kivy/main_window.py @@ -837,12 +837,22 @@ class ElectrumWindow(App): def invoices_dialog(self, screen): from .uix.dialogs.invoices import InvoicesDialog + if len(self.wallet.invoices.sorted_list()) == 0: + self.show_info(' '.join([ + _('No saved invoices.'), + _('Signed invoices are saved automatically when you scan them.'), + _('You may also save unsigned requests or contact addresses using the save button.') + ])) + return popup = InvoicesDialog(self, screen, None) popup.update() popup.open() def requests_dialog(self, screen): from .uix.dialogs.requests import RequestsDialog + if len(self.wallet.get_sorted_requests(self.electrum_config)) == 0: + self.show_info(_('No saved requests.')) + return popup = RequestsDialog(self, screen, None) popup.update() popup.open() diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py index d8e283362..a4cd31442 100644 --- a/gui/kivy/uix/screens.py +++ b/gui/kivy/uix/screens.py @@ -217,7 +217,6 @@ class SendScreen(CScreen): pr = make_unsigned_request(req).SerializeToString() pr = PaymentRequest(pr) self.app.wallet.invoices.add(pr) - self.app.update_tab('invoices') self.app.show_info(_("Invoice saved")) if pr.is_pr(): self.screen.is_pr = True From b7f7da6cde5cb2d6d6b70be18a10deccee2a656b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 20:24:23 +0100 Subject: [PATCH 025/174] fix kivy addresses search --- gui/kivy/uix/dialogs/addresses.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gui/kivy/uix/dialogs/addresses.py b/gui/kivy/uix/dialogs/addresses.py index 1866251f2..8f4b3fa62 100644 --- a/gui/kivy/uix/dialogs/addresses.py +++ b/gui/kivy/uix/dialogs/addresses.py @@ -39,7 +39,7 @@ Builder.load_string(''' show_change: 0 show_used: 0 on_message: - self.parent.update() + self.update() BoxLayout: id:box padding: '12dp', '70dp', '12dp', '12dp' @@ -83,7 +83,7 @@ Builder.load_string(''' AddressButton: id: change text: root.message if root.message else _('Search') - on_release: Clock.schedule_once(lambda dt: app.description_dialog(addr_screen)) + on_release: Clock.schedule_once(lambda dt: app.description_dialog(popup)) ScrollView: GridLayout: cols: 1 @@ -97,6 +97,10 @@ from electrum_gui.kivy.i18n import _ from electrum_gui.kivy.uix.context_menu import ContextMenu +class EmptyLabel(Factory.Label): + pass + + class AddressesDialog(Factory.Popup): def __init__(self, app, screen, callback): @@ -134,7 +138,7 @@ class AddressesDialog(Factory.Popup): _list = wallet.get_change_addresses() else: _list = wallet.get_addresses() - search = self.screen.message + search = self.message container = self.ids.search_container container.clear_widgets() n = 0 From e243918207d897ef63d6b194da55ba202c9932a6 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 6 Mar 2018 23:43:30 +0100 Subject: [PATCH 026/174] fix #4055 also remove dead code --- plugins/trustedcoin/trustedcoin.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py index b8e832e66..30e509f5c 100644 --- a/plugins/trustedcoin/trustedcoin.py +++ b/plugins/trustedcoin/trustedcoin.py @@ -214,26 +214,6 @@ class Wallet_2fa(Multisig_Wallet): def get_user_id(self): return get_user_id(self.storage) - def get_max_amount(self, config, inputs, recipient, fee): - from electrum.transaction import Transaction - sendable = sum(map(lambda x:x['value'], inputs)) - for i in inputs: - self.add_input_info(i) - xf = self.extra_fee(config) - _type, addr = recipient - if xf and sendable >= xf: - billing_address = self.billing_info['billing_address'] - sendable -= xf - outputs = [(_type, addr, sendable), - (TYPE_ADDRESS, billing_address, xf)] - else: - outputs = [(_type, addr, sendable)] - dummy_tx = Transaction.from_io(inputs, outputs) - if fee is None: - fee = self.estimate_fee(config, dummy_tx.estimated_size()) - amount = max(0, sendable - fee) - return amount, fee - def min_prepay(self): return min(self.price_per_tx.keys()) @@ -348,12 +328,17 @@ class TrustedCoinPlugin(BasePlugin): def get_tx_extra_fee(self, wallet, tx): if type(wallet) != Wallet_2fa: return + if wallet.billing_info is None: + assert wallet.can_sign_without_server() + return None address = wallet.billing_info['billing_address'] for _type, addr, amount in tx.outputs(): if _type == TYPE_ADDRESS and addr == address: return address, amount def request_billing_info(self, wallet): + if wallet.can_sign_without_server(): + return self.print_error("request billing info") billing_info = server.get(wallet.get_user_id()[1]) billing_address = make_billing_address(wallet, billing_info['billing_index']) From fc9cb0ca1c29eaf5400b864270d5514dbe3abd8e Mon Sep 17 00:00:00 2001 From: Yuki Inoue Date: Wed, 7 Mar 2018 13:51:06 +0900 Subject: [PATCH 027/174] daemon status shows current wallet path All the jsonrpc calls against electrum daemon is affected by the implicit parameter of wallet in the cmd_runner. AFAIK, there is no way to check the value of this wallet. This can be trouble some if multiple wallets are loaded, or load_wallet command is executed several times. This patch makes it viewable on the `daemon status`. --- lib/daemon.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/daemon.py b/lib/daemon.py index b3242215e..5bfa2fdf8 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -185,6 +185,9 @@ class Daemon(DaemonThread): elif sub == 'status': if self.network: p = self.network.get_parameters() + current_wallet = self.cmd_runner.wallet + current_wallet_path = current_wallet.storage.path \ + if current_wallet else None response = { 'path': self.network.config.path, 'server': p[0], @@ -196,6 +199,7 @@ class Daemon(DaemonThread): 'version': ELECTRUM_VERSION, 'wallets': {k: w.is_up_to_date() for k, w in self.wallets.items()}, + 'current_wallet': current_wallet_path, 'fee_per_kb': self.config.fee_per_kb(), } else: From b02252d664128b117fc6d6608f8f11b94fb329bb Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 7 Mar 2018 14:43:14 +0100 Subject: [PATCH 028/174] fix #4065 --- lib/x509.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/x509.py b/lib/x509.py index 52dd45f54..b7ec4389e 100644 --- a/lib/x509.py +++ b/lib/x509.py @@ -284,7 +284,7 @@ class X509(object): return self.AKI if self.AKI else repr(self.issuer) def get_common_name(self): - return self.subject.get('2.5.4.3', 'unknown').decode() + return self.subject.get('2.5.4.3', b'unknown').decode() def get_signature(self): return self.cert_sig_algo, self.signature, self.data From 192e8959339919ea29305573369ff40ce1e7eda2 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 7 Mar 2018 16:11:20 +0100 Subject: [PATCH 029/174] hw plugins: catch exceptions for custom enumeration functions. related: #4060 --- lib/plugins.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/plugins.py b/lib/plugins.py index 9f58611f0..12d1c0089 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -533,7 +533,13 @@ class DeviceMgr(ThreadJob, PrintError): # Let plugin handlers enumerate devices we don't know about for f in self.enumerate_func: - devices.extend(f()) + try: + new_devices = f() + except BaseException as e: + self.print_error('custom device enum failed. func {}, error {}' + .format(str(f), str(e))) + else: + devices.extend(new_devices) # Now find out what was disconnected pairs = [(dev.path, dev.id_) for dev in devices] From e72947c8fd50575d69cb005a52f99aeef66f4186 Mon Sep 17 00:00:00 2001 From: Marcel O'Neil Date: Wed, 7 Mar 2018 15:15:54 -0500 Subject: [PATCH 030/174] prevent requirements.txt from being installed to /usr --- MANIFEST.in | 2 ++ setup.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 4fa5491a6..b7355bd9b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,8 @@ include electrum.conf.sample include electrum.desktop include *.py include electrum +include contrib/requirements/requirements.txt +include contrib/requirements/requirements-hw.txt recursive-include lib *.py recursive-include gui *.py recursive-include plugins *.py diff --git a/setup.py b/setup.py index 63581a614..030993b59 100755 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ version = imp.load_source('version', 'lib/version.py') if sys.version_info[:3] < (3, 4, 0): sys.exit("Error: Electrum requires Python version >= 3.4.0...") -data_files = ['contrib/requirements/' + r for r in ['requirements.txt', 'requirements-hw.txt']] +data_files = [] if platform.system() in ['Linux', 'FreeBSD', 'DragonFly']: parser = argparse.ArgumentParser() From c1d14b9677af4c58b47514901e4b8ae74b11d64b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 8 Mar 2018 02:45:39 +0100 Subject: [PATCH 031/174] trezor: try and allow transports to fail independently related: #4060 --- plugins/trezor/trezor.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/trezor/trezor.py b/plugins/trezor/trezor.py index 322c2da31..b0dd933fb 100644 --- a/plugins/trezor/trezor.py +++ b/plugins/trezor/trezor.py @@ -127,8 +127,28 @@ class TrezorPlugin(HW_PluginBase): self.device_manager().register_enumerate_func(self.enumerate) def enumerate(self): - from trezorlib.device import TrezorDevice - return [Device(d.get_path(), -1, d.get_path(), 'TREZOR', 0) for d in TrezorDevice.enumerate()] + try: + from trezorlib.transport import all_transports + except ImportError: + # compat for trezorlib < 0.9.2 + def all_transports(): + from trezorlib.transport_bridge import BridgeTransport + from trezorlib.transport_hid import HidTransport + from trezorlib.transport_udp import UdpTransport + from trezorlib.transport_webusb import WebUsbTransport + return (BridgeTransport, HidTransport, UdpTransport, WebUsbTransport) + + devices = [] + for transport in all_transports(): + try: + new_devices = transport.enumerate() + except BaseException as e: + self.print_error('enumerate failed for {}. error {}' + .format(transport.__name__, str(e))) + else: + devices.extend(new_devices) + + return [Device(d.get_path(), -1, d.get_path(), 'TREZOR', 0) for d in devices] def create_client(self, device, handler): from trezorlib.device import TrezorDevice From 449734f3a0b8fb2396d88bf8b9fec2c6fb71d9f2 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 8 Mar 2018 09:49:11 +0100 Subject: [PATCH 032/174] fix #4075 --- gui/qt/console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/console.py b/gui/qt/console.py index bde05a3df..2b49d93c6 100644 --- a/gui/qt/console.py +++ b/gui/qt/console.py @@ -223,7 +223,7 @@ class Console(QtWidgets.QPlainTextEdit): exec(command, self.namespace, self.namespace) except SystemExit: self.close() - except Exception: + except BaseException: traceback_lines = traceback.format_exc().split('\n') # Remove traceback mentioning this file, and a linebreak for i in (3,2,1,-1): From 2deae196d92b8ce8097812fd5381c61416499cae Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 8 Mar 2018 10:14:32 +0100 Subject: [PATCH 033/174] this probably fixes #4072 --- lib/qrscanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/qrscanner.py b/lib/qrscanner.py index 6212b4e68..56e0a18af 100644 --- a/lib/qrscanner.py +++ b/lib/qrscanner.py @@ -36,7 +36,7 @@ else: try: libzbar = ctypes.cdll.LoadLibrary(name) -except OSError: +except BaseException: libzbar = None From f38ca73dae307441e2a90dbf23bff184e8a99b39 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 8 Mar 2018 11:52:01 +0100 Subject: [PATCH 034/174] setup.py: install optional modules. fixes #3927 --- setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 030993b59..3df887b46 100755 --- a/setup.py +++ b/setup.py @@ -42,9 +42,6 @@ setup( name="Electrum", version=version.ELECTRUM_VERSION, install_requires=requirements, - extras_require={ - 'hardware': requirements_hw, - }, packages=[ 'electrum', 'electrum_gui', @@ -89,3 +86,8 @@ setup( url="https://electrum.org", long_description="""Lightweight Bitcoin Wallet""" ) + +# Optional modules (not required to run Electrum) +import pip +opt_modules = requirements_hw + ['pycryptodomex'] +[ pip.main(['install', m]) for m in opt_modules ] From b3bd166620b6ca4eda82685611aa9d838a126c5b Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Thu, 8 Mar 2018 21:55:24 +0100 Subject: [PATCH 035/174] Move Cosignerpool to HTTPS --- plugins/cosigner_pool/qt.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/cosigner_pool/qt.py b/plugins/cosigner_pool/qt.py index 52d6a8f34..524d62b76 100644 --- a/plugins/cosigner_pool/qt.py +++ b/plugins/cosigner_pool/qt.py @@ -43,9 +43,7 @@ import sys import traceback -PORT = 12344 -HOST = 'cosigner.electrum.org' -server = ServerProxy('http://%s:%d'%(HOST,PORT), allow_none=True) +server = ServerProxy('https://cosigner.electrum.org/', allow_none=True) class Listener(util.DaemonThread): From 6f0a7ff42026966f7df8b3b8840f519e1b6b41e8 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 9 Mar 2018 01:38:35 +0100 Subject: [PATCH 036/174] fix #4077 --- gui/qt/transaction_dialog.py | 4 ++-- lib/wallet.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py index 4fc589dd1..8344f82b3 100644 --- a/gui/qt/transaction_dialog.py +++ b/gui/qt/transaction_dialog.py @@ -182,8 +182,8 @@ class TxDialog(QDialog, MessageBoxMixin): if success: self.prompt_if_unsaved = True self.saved = False - self.save_button.setDisabled(False) - self.save_button.setToolTip("") + self.save_button.setDisabled(False) + self.save_button.setToolTip("") self.update() self.main_window.pop_top_level_window(self) diff --git a/lib/wallet.py b/lib/wallet.py index 892d66a12..74992cd0b 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -778,6 +778,9 @@ class Abstract_Wallet(PrintError): return conflicting_txns def add_transaction(self, tx_hash, tx): + assert tx_hash, tx_hash + assert tx, tx + assert tx.is_complete() # we need self.transaction_lock but get_tx_height will take self.lock # so we need to take that too here, to enforce order of locks with self.lock, self.transaction_lock: From df6f950b2d10c9fb1090ee4a65326e45a580588e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 9 Mar 2018 01:47:56 +0100 Subject: [PATCH 037/174] fix #4078 --- gui/qt/installwizard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py index a6e335125..b72dada2d 100644 --- a/gui/qt/installwizard.py +++ b/gui/qt/installwizard.py @@ -184,7 +184,8 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): try: self.storage = WalletStorage(path, manual_upgrades=True) self.next_button.setEnabled(True) - except IOError: + except BaseException: + traceback.print_exc(file=sys.stderr) self.storage = None self.next_button.setEnabled(False) if self.storage: From c13e05770150c5210783c3d42d3d2b1a683f18b4 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 9 Mar 2018 03:18:53 +0100 Subject: [PATCH 038/174] fix #4080 --- lib/wallet.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/wallet.py b/lib/wallet.py index 74992cd0b..0d87d715b 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -200,6 +200,8 @@ class Abstract_Wallet(PrintError): self.load_transactions() self.build_spent_outpoints() + self.test_addresses_sanity() + # load requests self.receive_requests = self.storage.get('payment_requests', {}) @@ -329,6 +331,12 @@ class Abstract_Wallet(PrintError): self.receiving_addresses = d.get('receiving', []) self.change_addresses = d.get('change', []) + def test_addresses_sanity(self): + addrs = self.get_receiving_addresses() + if len(addrs) > 0: + if not bitcoin.is_address(addrs[0]): + raise Exception('The addresses in this wallet are not bitcoin addresses.') + def synchronize(self): pass From 08aee6a857bebd0b8ca2bb9d9b7623a4bea0ec24 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 9 Mar 2018 14:58:13 +0100 Subject: [PATCH 039/174] logging - use self.print_error instead of util.print_error --- lib/interface.py | 2 +- lib/network.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/interface.py b/lib/interface.py index ac1495fbd..f7fe82420 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -144,7 +144,7 @@ class TcpConnection(threading.Thread, util.PrintError): context = self.get_ssl_context(cert_reqs=ssl.CERT_REQUIRED, ca_certs=ca_path) s = context.wrap_socket(s, do_handshake_on_connect=True) except ssl.SSLError as e: - print_error(e) + self.print_error(e) s = None except: return diff --git a/lib/network.py b/lib/network.py index b803a7820..872e96f78 100644 --- a/lib/network.py +++ b/lib/network.py @@ -677,7 +677,7 @@ class Network(util.DaemonThread): # check cached response for subscriptions r = self.sub_cache.get(k) if r is not None: - util.print_error("cache hit", k) + self.print_error("cache hit", k) callback(r) else: message_id = self.queue_request(method, params) From 084b1e86efc31b324ec1fb22425c88771095dd6a Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Fri, 9 Mar 2018 18:59:15 +0100 Subject: [PATCH 040/174] Add missing files to localization --- app.fil | 66 +++++++++++++++++++++++++++++++-------------- contrib/make_locale | 2 +- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/app.fil b/app.fil index 835b6748f..a84737d00 100644 --- a/app.fil +++ b/app.fil @@ -1,29 +1,55 @@ -gui/qt/__init__.py -gui/qt/main_window.py -gui/qt/history_list.py +gui/qt/address_dialog.py +gui/qt/address_list.py gui/qt/contact_list.py -gui/qt/invoice_list.py -gui/qt/request_list.py +gui/qt/exception_window.py +gui/qt/fee_slider.py +gui/qt/history_list.py +gui/qt/__init__.py gui/qt/installwizard.py +gui/qt/invoice_list.py +gui/qt/main_window.py gui/qt/network_dialog.py gui/qt/password_dialog.py -gui/qt/util.py -gui/qt/seed_dialog.py -gui/qt/transaction_dialog.py -gui/qt/address_dialog.py gui/qt/qrcodewidget.py gui/qt/qrtextedit.py gui/qt/qrwindow.py -gui/kivy/main.kv -gui/kivy/main_window.py -gui/kivy/uix/dialogs/__init__.py -gui/kivy/uix/dialogs/fee_dialog.py -gui/kivy/uix/dialogs/installwizard.py -gui/kivy/uix/dialogs/settings.py -gui/kivy/uix/dialogs/wallets.py -gui/kivy/uix/ui_screens/history.kv -gui/kivy/uix/ui_screens/receive.kv -gui/kivy/uix/ui_screens/send.kv +gui/qt/request_list.py +gui/qt/seed_dialog.py +gui/qt/transaction_dialog.py +gui/qt/util.py +gui/qt/utxo_list.py +lib/plot.py +plugins/audio_modem/__init__.py +plugins/audio_modem/qt.py +plugins/cosigner_pool/__init__.py +plugins/cosigner_pool/qt.py +plugins/digitalbitbox/digitalbitbox.py +plugins/digitalbitbox/__init__.py +plugins/digitalbitbox/qt.py +plugins/email_requests/__init__.py +plugins/email_requests/qt.py +plugins/greenaddress_instant/__init__.py +plugins/greenaddress_instant/qt.py +plugins/hw_wallet/plugin.py +plugins/hw_wallet/qt.py +plugins/keepkey/clientbase.py +plugins/keepkey/__init__.py +plugins/keepkey/plugin.py +plugins/keepkey/qt_generic.py +plugins/labels/__init__.py +plugins/labels/labels.py plugins/labels/qt.py -plugins/trezor/qt.py +plugins/ledger/auth2fa.py +plugins/ledger/__init__.py +plugins/ledger/ledger.py +plugins/ledger/qt.py +plugins/trezor/clientbase.py +plugins/trezor/__init__.py +plugins/trezor/qt_generic.py +plugins/trezor/trezor.py +plugins/trustedcoin/cmdline.py +plugins/trustedcoin/__init__.py +plugins/trustedcoin/qt.py +plugins/trustedcoin/trustedcoin.py +plugins/virtualkeyboard/__init__.py plugins/virtualkeyboard/qt.py diff --git a/contrib/make_locale b/contrib/make_locale index 9243a8b4f..1dc74cc10 100755 --- a/contrib/make_locale +++ b/contrib/make_locale @@ -10,7 +10,7 @@ os.chdir('..') # Generate fresh translation template if not os.path.exists('lib/locale'): os.mkdir('lib/locale') -cmd = 'xgettext -s --no-wrap -f app.fil --output=lib/locale/messages.pot' +cmd = 'xgettext -s --from-code UTF-8 --no-wrap -f app.fil --output=lib/locale/messages.pot' print('Generate template') os.system(cmd) From 9372c31b5d3ba7fdb0caace5643d96f0fb7ff6e8 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 9 Mar 2018 19:10:57 +0100 Subject: [PATCH 041/174] fix #4084 --- lib/plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins.py b/lib/plugins.py index 12d1c0089..dcac6145b 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -495,7 +495,7 @@ class DeviceMgr(ThreadJob, PrintError): if info.label == keystore.label: return info msg = _("Please select which {} device to use:").format(plugin.device) - descriptions = [info.label + ' (%s)'%(_("initialized") if info.initialized else _("wiped")) for info in infos] + descriptions = [str(info.label) + ' (%s)'%(_("initialized") if info.initialized else _("wiped")) for info in infos] c = handler.query_choice(msg, descriptions) if c is None: raise UserCancelled() From eab5bcf62fa784eb1f2358db4cadee37e6e03bf9 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Fri, 9 Mar 2018 19:13:42 +0100 Subject: [PATCH 042/174] Find files to translate automatically --- .gitignore | 1 + MANIFEST.in | 1 - app.fil | 55 --------------------------------------------- contrib/make_locale | 11 +++++++++ 4 files changed, 12 insertions(+), 56 deletions(-) delete mode 100644 app.fil diff --git a/.gitignore b/.gitignore index dae35e75f..804917e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ env/ .tox/ .buildozer/ bin/ +/app.fil # tox files .cache/ diff --git a/MANIFEST.in b/MANIFEST.in index b7355bd9b..b028b14c5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -11,7 +11,6 @@ recursive-include gui *.py recursive-include plugins *.py recursive-include packages *.py recursive-include packages cacert.pem -include app.fil include icons.qrc recursive-include icons * recursive-include scripts * diff --git a/app.fil b/app.fil deleted file mode 100644 index a84737d00..000000000 --- a/app.fil +++ /dev/null @@ -1,55 +0,0 @@ -gui/qt/address_dialog.py -gui/qt/address_list.py -gui/qt/contact_list.py -gui/qt/exception_window.py -gui/qt/fee_slider.py -gui/qt/history_list.py -gui/qt/__init__.py -gui/qt/installwizard.py -gui/qt/invoice_list.py -gui/qt/main_window.py -gui/qt/network_dialog.py -gui/qt/password_dialog.py -gui/qt/qrcodewidget.py -gui/qt/qrtextedit.py -gui/qt/qrwindow.py -gui/qt/request_list.py -gui/qt/seed_dialog.py -gui/qt/transaction_dialog.py -gui/qt/util.py -gui/qt/utxo_list.py -lib/plot.py -plugins/audio_modem/__init__.py -plugins/audio_modem/qt.py -plugins/cosigner_pool/__init__.py -plugins/cosigner_pool/qt.py -plugins/digitalbitbox/digitalbitbox.py -plugins/digitalbitbox/__init__.py -plugins/digitalbitbox/qt.py -plugins/email_requests/__init__.py -plugins/email_requests/qt.py -plugins/greenaddress_instant/__init__.py -plugins/greenaddress_instant/qt.py -plugins/hw_wallet/plugin.py -plugins/hw_wallet/qt.py -plugins/keepkey/clientbase.py -plugins/keepkey/__init__.py -plugins/keepkey/plugin.py -plugins/keepkey/qt_generic.py -plugins/labels/__init__.py -plugins/labels/labels.py -plugins/labels/qt.py -plugins/ledger/auth2fa.py -plugins/ledger/__init__.py -plugins/ledger/ledger.py -plugins/ledger/qt.py -plugins/trezor/clientbase.py -plugins/trezor/__init__.py -plugins/trezor/qt_generic.py -plugins/trezor/trezor.py -plugins/trustedcoin/cmdline.py -plugins/trustedcoin/__init__.py -plugins/trustedcoin/qt.py -plugins/trustedcoin/trustedcoin.py -plugins/virtualkeyboard/__init__.py -plugins/virtualkeyboard/qt.py diff --git a/contrib/make_locale b/contrib/make_locale index 1dc74cc10..386e0f6e1 100755 --- a/contrib/make_locale +++ b/contrib/make_locale @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import os +import subprocess import io import zipfile import requests @@ -7,6 +8,16 @@ import requests os.chdir(os.path.dirname(os.path.realpath(__file__))) os.chdir('..') +code_directories = 'lib gui plugins' +cmd = "grep 'from electrum.i18n import _' {} -rl".format(code_directories) + +files = subprocess.check_output(cmd, shell=True) + +with open("app.fil", "wb") as f: + f.write(files) + +print("Found {} files to translate".format(len(files.splitlines()))) + # Generate fresh translation template if not os.path.exists('lib/locale'): os.mkdir('lib/locale') From e890ec02d5eca35c980456dfb9205a3c3f7f5633 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Fri, 9 Mar 2018 23:17:39 +0100 Subject: [PATCH 043/174] Pass all possible files to xgettext --- contrib/make_locale | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/make_locale b/contrib/make_locale index 386e0f6e1..a55507919 100755 --- a/contrib/make_locale +++ b/contrib/make_locale @@ -8,8 +8,8 @@ import requests os.chdir(os.path.dirname(os.path.realpath(__file__))) os.chdir('..') -code_directories = 'lib gui plugins' -cmd = "grep 'from electrum.i18n import _' {} -rl".format(code_directories) +code_directories = '{gui,plugins}' +cmd = "find {} -type f -name '*.py' -o -name '*.kv'".format(code_directories) files = subprocess.check_output(cmd, shell=True) @@ -21,7 +21,7 @@ print("Found {} files to translate".format(len(files.splitlines()))) # Generate fresh translation template if not os.path.exists('lib/locale'): os.mkdir('lib/locale') -cmd = 'xgettext -s --from-code UTF-8 --no-wrap -f app.fil --output=lib/locale/messages.pot' +cmd = 'xgettext -s --from-code UTF-8 --language Python --no-wrap -f app.fil --output=lib/locale/messages.pot' print('Generate template') os.system(cmd) From 2915cb98b20c75f4253aae2b10da49c2d361f170 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Fri, 9 Mar 2018 23:22:15 +0100 Subject: [PATCH 044/174] Follow-up e890ec02d --- contrib/make_locale | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/make_locale b/contrib/make_locale index a55507919..681730610 100755 --- a/contrib/make_locale +++ b/contrib/make_locale @@ -8,7 +8,7 @@ import requests os.chdir(os.path.dirname(os.path.realpath(__file__))) os.chdir('..') -code_directories = '{gui,plugins}' +code_directories = '{gui,plugins,lib}' cmd = "find {} -type f -name '*.py' -o -name '*.kv'".format(code_directories) files = subprocess.check_output(cmd, shell=True) From 3c505660a6c0827b72d12bd9bb7d5756974d5285 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Fri, 9 Mar 2018 23:52:34 +0100 Subject: [PATCH 045/174] Fix localization related issues with digitalbitbox --- plugins/digitalbitbox/digitalbitbox.py | 83 ++++++++++++++------------ 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/plugins/digitalbitbox/digitalbitbox.py b/plugins/digitalbitbox/digitalbitbox.py index 623290543..71507cd9c 100644 --- a/plugins/digitalbitbox/digitalbitbox.py +++ b/plugins/digitalbitbox/digitalbitbox.py @@ -106,7 +106,7 @@ class DigitalBitbox_Client(): def dbb_has_password(self): reply = self.hid_send_plain(b'{"ping":""}') if 'ping' not in reply: - raise Exception('Device communication error. Please unplug and replug your Digital Bitbox.') + raise Exception(_('Device communication error. Please unplug and replug your Digital Bitbox.')) if reply['ping'] == 'password': return True return False @@ -124,9 +124,11 @@ class DigitalBitbox_Client(): if password is None: return None if len(password) < 4: - msg = _("Password must have at least 4 characters.\r\n\r\nEnter password:") + msg = _("Password must have at least 4 characters.") \ + + "\n\n" + _("Enter password:") elif len(password) > 64: - msg = _("Password must have less than 64 characters.\r\n\r\nEnter password:") + msg = _("Password must have less than 64 characters.") \ + + "\n\n" + _("Enter password:") else: return password.encode('utf8') @@ -137,9 +139,11 @@ class DigitalBitbox_Client(): if password is None: return False if len(password) < 4: - msg = _("Password must have at least 4 characters.\r\n\r\nEnter password:") + msg = _("Password must have at least 4 characters.") + \ + "\n\n" + _("Enter password:") elif len(password) > 64: - msg = _("Password must have less than 64 characters.\r\n\r\nEnter password:") + msg = _("Password must have less than 64 characters.") + \ + "\n\n" + _("Enter password:") else: self.password = password.encode('utf8') return True @@ -150,10 +154,11 @@ class DigitalBitbox_Client(): if self.password is None and not self.dbb_has_password(): if not self.setupRunning: return False # A fresh device cannot connect to an existing wallet - msg = _("An uninitialized Digital Bitbox is detected. " \ - "Enter a new password below.\r\n\r\n REMEMBER THE PASSWORD!\r\n\r\n" \ - "You cannot access your coins or a backup without the password.\r\n" \ - "A backup is saved automatically when generating a new wallet.") + msg = _("An uninitialized Digital Bitbox is detected.") + " " + \ + _("Enter a new password below.") + "\n\n" + \ + _("REMEMBER THE PASSWORD!") + "\n\n" + \ + _("You cannot access your coins or a backup without the password.") + "\n" + \ + _("A backup is saved automatically when generating a new wallet.") if self.password_dialog(msg): reply = self.hid_send_plain(b'{"password":"' + self.password + b'"}') else: @@ -168,14 +173,14 @@ class DigitalBitbox_Client(): if 'error' in reply: self.password = None if reply['error']['code'] == 109: - msg = _("Incorrect password entered.\r\n\r\n" \ - + reply['error']['message'] + "\r\n\r\n" \ - "Enter your Digital Bitbox password:") + msg = _("Incorrect password entered.") + "\n\n" + \ + + reply['error']['message'] + "\n\n" + \ + _("Enter your Digital Bitbox password:") else: # Should never occur - msg = _("Unexpected error occurred.\r\n\r\n" \ - + reply['error']['message'] + "\r\n\r\n" \ - "Enter your Digital Bitbox password:") + msg = _("Unexpected error occurred.") + "\n\n" + \ + + reply['error']['message'] + "\n\n" + \ + _("Enter your Digital Bitbox password:") # Initialize device if not yet initialized if not self.setupRunning: @@ -191,7 +196,7 @@ class DigitalBitbox_Client(): def recover_or_erase_dialog(self): - msg = _("The Digital Bitbox is already seeded. Choose an option:\n") + msg = _("The Digital Bitbox is already seeded. Choose an option:") + "\n" choices = [ (_("Create a wallet using the current seed")), (_("Load a wallet from the micro SD card (the current seed is overwritten)")), @@ -208,13 +213,13 @@ class DigitalBitbox_Client(): return else: if self.hid_send_encrypt(b'{"device":"info"}')['device']['lock']: - raise Exception("Full 2FA enabled. This is not supported yet.") + raise Exception(_("Full 2FA enabled. This is not supported yet.")) # Use existing seed self.isInitialized = True def seed_device_dialog(self): - msg = _("Choose how to initialize your Digital Bitbox:\n") + msg = _("Choose how to initialize your Digital Bitbox:") + "\n" choices = [ (_("Generate a new random wallet")), (_("Load a wallet from the micro SD card")) @@ -280,9 +285,9 @@ class DigitalBitbox_Client(): def dbb_erase(self): - self.handler.show_message(_("Are you sure you want to erase the Digital Bitbox?\r\n\r\n" \ - "To continue, touch the Digital Bitbox's light for 3 seconds.\r\n\r\n" \ - "To cancel, briefly touch the light or wait for the timeout.")) + self.handler.show_message(_("Are you sure you want to erase the Digital Bitbox?") + "\n\n" + + _("To continue, touch the Digital Bitbox's light for 3 seconds.") + "\n\n" + + _("To cancel, briefly touch the light or wait for the timeout.")) hid_reply = self.hid_send_encrypt(b'{"reset":"__ERASE__"}') self.handler.finished() if 'error' in hid_reply: @@ -305,9 +310,9 @@ class DigitalBitbox_Client(): raise Exception('Canceled by user') key = self.stretch_key(key) if show_msg: - self.handler.show_message(_("Loading backup...\r\n\r\n" \ - "To continue, touch the Digital Bitbox's light for 3 seconds.\r\n\r\n" \ - "To cancel, briefly touch the light or wait for the timeout.")) + self.handler.show_message(_("Loading backup...") + "\n\n" + + _("To continue, touch the Digital Bitbox's light for 3 seconds.") + "\n\n" + + _("To cancel, briefly touch the light or wait for the timeout.")) msg = b'{"seed":{"source": "backup", "key": "%s", "filename": "%s"}}' % (key, backups['backup'][f].encode('utf8')) hid_reply = self.hid_send_encrypt(msg) self.handler.finished() @@ -365,7 +370,7 @@ class DigitalBitbox_Client(): else: self.hid_send_frame(msg) r = self.hid_read_frame() - r = r.rstrip(b' \t\r\n\0') + r = r.rstrip(b' \t\n\0') r = r.replace(b"\0", b'') r = to_string(r, 'utf8') reply = json.loads(r) @@ -441,12 +446,12 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): dbb_client = self.plugin.get_client(self) if not dbb_client.is_paired(): - raise Exception("Could not sign message.") + raise Exception(_("Could not sign message.")) reply = dbb_client.hid_send_encrypt(msg) - self.handler.show_message(_("Signing message ...\r\n\r\n" \ - "To continue, touch the Digital Bitbox's blinking light for 3 seconds.\r\n\r\n" \ - "To cancel, briefly touch the blinking light or wait for the timeout.")) + self.handler.show_message(_("Signing message ...") + "\n\n" + + _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + "\n\n" + + _("To cancel, briefly touch the blinking light or wait for the timeout.")) reply = dbb_client.hid_send_encrypt(msg) # Send twice, first returns an echo for smart verification (not implemented) self.handler.finished() @@ -454,7 +459,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): raise Exception(reply['error']['message']) if 'sign' not in reply: - raise Exception("Could not sign message.") + raise Exception(_("Could not sign message.")) if 'recid' in reply['sign'][0]: # firmware > v2.1.1 @@ -463,7 +468,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): pk = point_to_ser(pk.pubkey.point, compressed) addr = public_key_to_p2pkh(pk) if verify_message(addr, sig, message) is False: - raise Exception("Could not sign message") + raise Exception(_("Could not sign message")) elif 'pubkey' in reply['sign'][0]: # firmware <= v2.1.1 for i in range(4): @@ -475,7 +480,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): except Exception: continue else: - raise Exception("Could not sign message") + raise Exception(_("Could not sign message")) except BaseException as e: @@ -576,14 +581,14 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): self.plugin.comserver_post_notification(reply) if steps > 1: - self.handler.show_message(_("Signing large transaction. Please be patient ...\r\n\r\n" \ - "To continue, touch the Digital Bitbox's blinking light for 3 seconds. " \ - "(Touch " + str(step + 1) + " of " + str(int(steps)) + ")\r\n\r\n" \ - "To cancel, briefly touch the blinking light or wait for the timeout.\r\n\r\n")) + self.handler.show_message(_("Signing large transaction. Please be patient ...") + "\n\n" + + _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + " " + + _("(Touch {} of {})").format((step + 1), steps) + "\n\n" + + _("To cancel, briefly touch the blinking light or wait for the timeout.") + "\n\n") else: - self.handler.show_message(_("Signing transaction ...\r\n\r\n" \ - "To continue, touch the Digital Bitbox's blinking light for 3 seconds.\r\n\r\n" \ - "To cancel, briefly touch the blinking light or wait for the timeout.")) + self.handler.show_message(_("Signing transaction...") + "\n\n" + + _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + "\n\n" + + _("To cancel, briefly touch the blinking light or wait for the timeout.")) # Send twice, first returns an echo for smart verification reply = dbb_client.hid_send_encrypt(msg) From 92a701b97a979cdc71250d9223a8411d5ee54ed9 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 10 Mar 2018 00:06:19 +0100 Subject: [PATCH 046/174] Follow-up 2915cb98b20c --- contrib/make_locale | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/make_locale b/contrib/make_locale index 681730610..2831cfeb3 100755 --- a/contrib/make_locale +++ b/contrib/make_locale @@ -8,7 +8,7 @@ import requests os.chdir(os.path.dirname(os.path.realpath(__file__))) os.chdir('..') -code_directories = '{gui,plugins,lib}' +code_directories = 'gui plugins lib' cmd = "find {} -type f -name '*.py' -o -name '*.kv'".format(code_directories) files = subprocess.check_output(cmd, shell=True) From 895bd019fb16d1a9d643006babc995de4cbf7872 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 10 Mar 2018 00:09:17 +0100 Subject: [PATCH 047/174] Follow-up 3c505660a6: One search/replace too much --- plugins/digitalbitbox/digitalbitbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/digitalbitbox/digitalbitbox.py b/plugins/digitalbitbox/digitalbitbox.py index 71507cd9c..a472d9f97 100644 --- a/plugins/digitalbitbox/digitalbitbox.py +++ b/plugins/digitalbitbox/digitalbitbox.py @@ -370,7 +370,7 @@ class DigitalBitbox_Client(): else: self.hid_send_frame(msg) r = self.hid_read_frame() - r = r.rstrip(b' \t\n\0') + r = r.rstrip(b' \t\r\n\0') r = r.replace(b"\0", b'') r = to_string(r, 'utf8') reply = json.loads(r) From 6f5a4677d18b3cf3851e852d6ced3f190be26d59 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 10 Mar 2018 00:23:51 +0100 Subject: [PATCH 048/174] clean up imports in lib/plot.py --- lib/plot.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/plot.py b/lib/plot.py index 5bd6add64..6d0d02250 100644 --- a/lib/plot.py +++ b/lib/plot.py @@ -1,11 +1,7 @@ -from PyQt5.QtGui import * -from electrum.i18n import _ - - import datetime from collections import defaultdict -from electrum.bitcoin import COIN +from PyQt5.QtGui import * import matplotlib matplotlib.use('Qt5Agg') import matplotlib.pyplot as plt @@ -13,6 +9,9 @@ import matplotlib.dates as md from matplotlib.patches import Ellipse from matplotlib.offsetbox import AnchoredOffsetbox, TextArea, DrawingArea, HPacker +from .i18n import _ +from .bitcoin import COIN + class NothingToPlotException(Exception): def __str__(self): From f3d254ff1ebb6f36f12fd314d73e535ed2f7ecdc Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 10 Mar 2018 00:37:16 +0100 Subject: [PATCH 049/174] follow-up prev --- lib/plot.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/plot.py b/lib/plot.py index 6d0d02250..3174d1b26 100644 --- a/lib/plot.py +++ b/lib/plot.py @@ -1,13 +1,10 @@ import datetime from collections import defaultdict -from PyQt5.QtGui import * import matplotlib matplotlib.use('Qt5Agg') import matplotlib.pyplot as plt import matplotlib.dates as md -from matplotlib.patches import Ellipse -from matplotlib.offsetbox import AnchoredOffsetbox, TextArea, DrawingArea, HPacker from .i18n import _ from .bitcoin import COIN From d994d27704934d7bad4fc483b31172a2e70e0bf9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 10 Mar 2018 03:59:01 +0100 Subject: [PATCH 050/174] hw wallet encryption unlock: clear session on incorrect passphrase --- gui/qt/installwizard.py | 2 -- lib/base_wizard.py | 12 ++++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py index b72dada2d..283dff18a 100644 --- a/gui/qt/installwizard.py +++ b/gui/qt/installwizard.py @@ -246,8 +246,6 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): try: self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET) except InvalidPassword as e: - # FIXME if we get here because of mistyped passphrase - # then that passphrase gets "cached" QMessageBox.information( None, _('Error'), _('Failed to decrypt using this hardware device.') + '\n' + diff --git a/lib/base_wizard.py b/lib/base_wizard.py index 793a96fb7..98394382a 100644 --- a/lib/base_wizard.py +++ b/lib/base_wizard.py @@ -33,7 +33,7 @@ from .keystore import bip44_derivation from .wallet import Imported_Wallet, Standard_Wallet, Multisig_Wallet, wallet_types from .storage import STO_EV_USER_PW, STO_EV_XPUB_PW, get_derivation_used_for_hw_device_encryption from .i18n import _ -from .util import UserCancelled +from .util import UserCancelled, InvalidPassword # hardware device setup purpose HWD_SETUP_NEW_WALLET, HWD_SETUP_DECRYPT_WALLET = range(0, 2) @@ -259,7 +259,15 @@ class BaseWizard(object): derivation = get_derivation_used_for_hw_device_encryption() xpub = self.plugin.get_xpub(device_info.device.id_, derivation, 'standard', self) password = keystore.Xpub.get_pubkey_from_xpub(xpub, ()) - self.storage.decrypt(password) + try: + self.storage.decrypt(password) + except InvalidPassword: + # try to clear session so that user can type another passphrase + devmgr = self.plugins.device_manager + client = devmgr.client_by_id(device_info.device.id_) + if hasattr(client, 'clear_session'): # FIXME not all hw wallet plugins have this + client.clear_session() + raise else: raise Exception('unknown purpose: %s' % purpose) From 2c6cf7f80cda5ee78fa1b8d670ae53de169b99f9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 10 Mar 2018 08:00:41 +0100 Subject: [PATCH 051/174] careful with exceptions.. --- lib/wallet.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index 0d87d715b..9f4f2baa9 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1939,14 +1939,15 @@ class Imported_Wallet(Simple_Wallet): try: txin_type, pubkey = self.keystore.import_privkey(sec, pw) except Exception: - raise BaseException('Invalid private key', sec) + neutered_privkey = str(sec)[:3] + '..' + str(sec)[-2:] + raise BaseException('Invalid private key', neutered_privkey) if txin_type in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']: if redeem_script is not None: - raise BaseException('Cannot use redeem script with', txin_type, sec) + raise BaseException('Cannot use redeem script with', txin_type) addr = bitcoin.pubkey_to_address(txin_type, pubkey) elif txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']: if redeem_script is None: - raise BaseException('Redeem script required for', txin_type, sec) + raise BaseException('Redeem script required for', txin_type) addr = bitcoin.redeem_script_to_address(txin_type, redeem_script) else: raise NotImplementedError(txin_type) From 99647fc070ad1aa988c3fc5fa16476a71564fd3f Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 10 Mar 2018 08:16:19 +0100 Subject: [PATCH 052/174] careful with exceptions.. --- lib/bitcoin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 18d2afd14..1d7831465 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -481,7 +481,8 @@ def deserialize_privkey(key): assert txin_type in SCRIPT_TYPES vch = DecodeBase58Check(key) if not vch: - raise BaseException("cannot deserialize", key) + neutered_privkey = str(key)[:3] + '..' + str(key)[-2:] + raise BaseException("cannot deserialize", neutered_privkey) if txin_type is None: # keys exported in version 3.0.x encoded script type in first byte From d71d22d279b57dbe1f1509b7ba37fc421b2f173a Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 10 Mar 2018 14:55:06 +0100 Subject: [PATCH 053/174] Fix Typo --- gui/qt/main_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 27a4b0165..ccd051e4e 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -1182,7 +1182,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.fee_adv_controls.setVisible(False) self.preview_button = EnterButton(_("Preview"), self.do_preview) - self.preview_button.setToolTip(_('Display the details of your transactions before signing it.')) + self.preview_button.setToolTip(_('Display the details of your transaction before signing it.')) self.send_button = EnterButton(_("Send"), self.do_send) self.clear_button = EnterButton(_("Clear"), self.do_clear) buttons = QHBoxLayout() From e31c2d491d4301c6ec44e977ee22f2bc81381410 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 11 Mar 2018 07:18:07 +0100 Subject: [PATCH 054/174] fix #4093 --- lib/bitcoin.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 1d7831465..731d10f51 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -408,7 +408,10 @@ def base_decode(v, length, base): chars = __b43chars long_value = 0 for (i, c) in enumerate(v[::-1]): - long_value += chars.find(bytes([c])) * (base**i) + digit = chars.find(bytes([c])) + if digit == -1: + raise ValueError('Forbidden character {} for base {}'.format(c, base)) + long_value += digit * (base**i) result = bytearray() while long_value >= 256: div, mod = divmod(long_value, 256) @@ -428,6 +431,10 @@ def base_decode(v, length, base): return bytes(result) +class InvalidChecksum(Exception): + pass + + def EncodeBase58Check(vchIn): hash = Hash(vchIn) return base_encode(vchIn + hash[0:4], base=58) @@ -440,7 +447,7 @@ def DecodeBase58Check(psz): hash = Hash(key) cs32 = hash[0:4] if cs32 != csum: - return None + raise InvalidChecksum('expected {}, actual {}'.format(bh2u(cs32), bh2u(csum))) else: return key @@ -479,8 +486,9 @@ def deserialize_privkey(key): if ':' in key: txin_type, key = key.split(sep=':', maxsplit=1) assert txin_type in SCRIPT_TYPES - vch = DecodeBase58Check(key) - if not vch: + try: + vch = DecodeBase58Check(key) + except BaseException: neutered_privkey = str(key)[:3] + '..' + str(key)[-2:] raise BaseException("cannot deserialize", neutered_privkey) From 78692327373252714792607ae49a0daee06bae15 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sun, 11 Mar 2018 11:26:19 +0100 Subject: [PATCH 055/174] Switch labelsync to electrum.org --- plugins/labels/labels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/labels/labels.py b/plugins/labels/labels.py index 3e778d544..0033b2d85 100644 --- a/plugins/labels/labels.py +++ b/plugins/labels/labels.py @@ -16,7 +16,7 @@ class LabelsPlugin(BasePlugin): def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name) - self.target_host = 'labels.bauerj.eu' + self.target_host = 'labels.electrum.org' self.wallets = {} def encode(self, wallet, msg): From a2ebad009f4a4d7551c9c4c05ae12f7a9cbb5698 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 11 Mar 2018 11:30:10 +0100 Subject: [PATCH 056/174] labels plugin: better exception handling --- plugins/labels/labels.py | 89 +++++++++++++++++++++++----------------- plugins/labels/qt.py | 21 +++++++--- 2 files changed, 68 insertions(+), 42 deletions(-) diff --git a/plugins/labels/labels.py b/plugins/labels/labels.py index 0033b2d85..b175e9e64 100644 --- a/plugins/labels/labels.py +++ b/plugins/labels/labels.py @@ -45,7 +45,7 @@ class LabelsPlugin(BasePlugin): @hook def set_label(self, wallet, item, label): - if not wallet in self.wallets: + if wallet not in self.wallets: return if not item: return @@ -55,7 +55,7 @@ class LabelsPlugin(BasePlugin): "walletNonce": nonce, "externalId": self.encode(wallet, item), "encryptedLabel": self.encode(wallet, label)} - t = threading.Thread(target=self.do_request, + t = threading.Thread(target=self.do_request_safe, args=["POST", "/label", False, bundle]) t.setDaemon(True) t.start() @@ -78,8 +78,18 @@ class LabelsPlugin(BasePlugin): raise BaseException(response["error"]) return response + def do_request_safe(self, *args, **kwargs): + try: + self.do_request(*args, **kwargs) + except BaseException as e: + #traceback.print_exc(file=sys.stderr) + self.print_error('error doing request') + def push_thread(self, wallet): - wallet_id = self.wallets[wallet][2] + wallet_data = self.wallets.get(wallet, None) + if not wallet_data: + raise Exception('Wallet {} not loaded'.format(wallet)) + wallet_id = wallet_data[2] bundle = {"labels": [], "walletId": wallet_id, "walletNonce": self.get_nonce(wallet)} @@ -95,42 +105,47 @@ class LabelsPlugin(BasePlugin): self.do_request("POST", "/labels", True, bundle) def pull_thread(self, wallet, force): - wallet_id = self.wallets[wallet][2] + wallet_data = self.wallets.get(wallet, None) + if not wallet_data: + raise Exception('Wallet {} not loaded'.format(wallet)) + wallet_id = wallet_data[2] nonce = 1 if force else self.get_nonce(wallet) - 1 self.print_error("asking for labels since nonce", nonce) + response = self.do_request("GET", ("/labels/since/%d/for/%s" % (nonce, wallet_id) )) + if response["labels"] is None: + self.print_error('no new labels') + return + result = {} + for label in response["labels"]: + try: + key = self.decode(wallet, label["externalId"]) + value = self.decode(wallet, label["encryptedLabel"]) + except: + continue + try: + json.dumps(key) + json.dumps(value) + except: + self.print_error('error: no json', key) + continue + result[key] = value + + for key, value in result.items(): + if force or not wallet.labels.get(key): + wallet.labels[key] = value + + self.print_error("received %d labels" % len(response)) + # do not write to disk because we're in a daemon thread + wallet.storage.put('labels', wallet.labels) + self.set_nonce(wallet, response["nonce"] + 1) + self.on_pulled(wallet) + + def pull_thread_safe(self, wallet, force): try: - response = self.do_request("GET", ("/labels/since/%d/for/%s" % (nonce, wallet_id) )) - if response["labels"] is None: - self.print_error('no new labels') - return - result = {} - for label in response["labels"]: - try: - key = self.decode(wallet, label["externalId"]) - value = self.decode(wallet, label["encryptedLabel"]) - except: - continue - try: - json.dumps(key) - json.dumps(value) - except: - self.print_error('error: no json', key) - continue - result[key] = value - - for key, value in result.items(): - if force or not wallet.labels.get(key): - wallet.labels[key] = value - - self.print_error("received %d labels" % len(response)) - # do not write to disk because we're in a daemon thread - wallet.storage.put('labels', wallet.labels) - self.set_nonce(wallet, response["nonce"] + 1) - self.on_pulled(wallet) - - except Exception as e: - traceback.print_exc(file=sys.stderr) - self.print_error("could not retrieve labels") + self.pull_thread(wallet, force) + except BaseException as e: + # traceback.print_exc(file=sys.stderr) + self.print_error('could not retrieve labels') def start_wallet(self, wallet): nonce = self.get_nonce(wallet) @@ -144,7 +159,7 @@ class LabelsPlugin(BasePlugin): wallet_id = hashlib.sha256(mpk).hexdigest() self.wallets[wallet] = (password, iv, wallet_id) # If there is an auth token we can try to actually start syncing - t = threading.Thread(target=self.pull_thread, args=(wallet, False)) + t = threading.Thread(target=self.pull_thread_safe, args=(wallet, False)) t.setDaemon(True) t.start() diff --git a/plugins/labels/qt.py b/plugins/labels/qt.py index 5fa9ee3a0..c608ccf78 100644 --- a/plugins/labels/qt.py +++ b/plugins/labels/qt.py @@ -1,4 +1,6 @@ from functools import partial +import traceback +import sys from PyQt5.QtGui import * from PyQt5.QtCore import * @@ -37,10 +39,12 @@ class Plugin(LabelsPlugin): hbox.addWidget(QLabel("Label sync options:")) upload = ThreadedButton("Force upload", partial(self.push_thread, wallet), - partial(self.done_processing, d)) + partial(self.done_processing_success, d), + partial(self.done_processing_error, d)) download = ThreadedButton("Force download", partial(self.pull_thread, wallet, True), - partial(self.done_processing, d)) + partial(self.done_processing_success, d), + partial(self.done_processing_error, d)) vbox = QVBoxLayout() vbox.addWidget(upload) vbox.addWidget(download) @@ -54,13 +58,20 @@ class Plugin(LabelsPlugin): def on_pulled(self, wallet): self.obj.labels_changed_signal.emit(wallet) - def done_processing(self, dialog, result): + def done_processing_success(self, dialog, result): dialog.show_message(_("Your labels have been synchronised.")) + def done_processing_error(self, dialog, result): + traceback.print_exception(*result, file=sys.stderr) + dialog.show_error(_("Error synchronising labels") + ':\n' + str(result[:2])) + @hook - def on_new_window(self, window): + def load_wallet(self, wallet, window): + # FIXME if the user just enabled the plugin, this hook won't be called + # as the wallet is already loaded, and hence the plugin will be in + # a non-functional state for that window self.obj.labels_changed_signal.connect(window.update_tabs) - self.start_wallet(window.wallet) + self.start_wallet(wallet) @hook def on_close_window(self, window): From d3065f73bf01a86dc50d902f0a118af4710e802d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 11 Mar 2018 13:28:13 +0100 Subject: [PATCH 057/174] follow-up 3c505660a6c0827b72d12bd9bb7d5756974d5285 --- plugins/digitalbitbox/digitalbitbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/digitalbitbox/digitalbitbox.py b/plugins/digitalbitbox/digitalbitbox.py index a472d9f97..599c8f1c0 100644 --- a/plugins/digitalbitbox/digitalbitbox.py +++ b/plugins/digitalbitbox/digitalbitbox.py @@ -174,12 +174,12 @@ class DigitalBitbox_Client(): self.password = None if reply['error']['code'] == 109: msg = _("Incorrect password entered.") + "\n\n" + \ - + reply['error']['message'] + "\n\n" + \ + reply['error']['message'] + "\n\n" + \ _("Enter your Digital Bitbox password:") else: # Should never occur msg = _("Unexpected error occurred.") + "\n\n" + \ - + reply['error']['message'] + "\n\n" + \ + reply['error']['message'] + "\n\n" + \ _("Enter your Digital Bitbox password:") # Initialize device if not yet initialized From 20a881b6a4d4cce2a35b5e8b51478c1b8e273c1d Mon Sep 17 00:00:00 2001 From: Chuong Vu Date: Sun, 11 Mar 2018 18:49:30 -0700 Subject: [PATCH 058/174] Update doc for OSX build Avoids the confusion that I just had.. --- contrib/build-osx/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/build-osx/README.md b/contrib/build-osx/README.md index af43e05ce..fd8d1d011 100644 --- a/contrib/build-osx/README.md +++ b/contrib/build-osx/README.md @@ -12,6 +12,7 @@ This assumes that the Xcode command line tools (and thus git) are already instal - ./make_osx + $ cd electrum + $ ./contrib/build-osx/make_osx ## 2. Done From a048a00594f00da0cd800b9b25a8a2cde341db1e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 12 Mar 2018 04:00:27 +0100 Subject: [PATCH 059/174] close #4102 close #3337 --- lib/storage.py | 4 +++- lib/wallet.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/storage.py b/lib/storage.py index d87a339a8..1b1079a32 100644 --- a/lib/storage.py +++ b/lib/storage.py @@ -51,6 +51,8 @@ FINAL_SEED_VERSION = 16 # electrum >= 2.7 will set this to prevent def multisig_type(wallet_type): '''If wallet_type is mofn multi-sig, return [m, n], otherwise return None.''' + if not wallet_type: + return None match = re.match('(\d+)of(\d+)', wallet_type) if match: match = [int(x) for x in match.group(1, 2)] @@ -417,7 +419,7 @@ class WalletStorage(PrintError): d['seed'] = seed self.put(key, d) else: - raise + raise Exception('Unable to tell wallet type. Is this even a wallet file?') # remove junk self.put('master_public_key', None) self.put('master_public_keys', None) diff --git a/lib/wallet.py b/lib/wallet.py index 9f4f2baa9..48a11d4fc 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -2301,5 +2301,5 @@ class Wallet(object): return Multisig_Wallet if wallet_type in wallet_constructors: return wallet_constructors[wallet_type] - raise RuntimeError("Unknown wallet type: " + wallet_type) + raise RuntimeError("Unknown wallet type: " + str(wallet_type)) From 0603f9f2b4c6e23fbe9dc0bf79803a86f9aea9e5 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 12 Mar 2018 10:18:09 +0100 Subject: [PATCH 060/174] fix #4108 --- lib/base_wizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/base_wizard.py b/lib/base_wizard.py index 98394382a..016fbdf52 100644 --- a/lib/base_wizard.py +++ b/lib/base_wizard.py @@ -164,7 +164,7 @@ class BaseWizard(object): k = keystore.Imported_KeyStore({}) self.storage.put('keystore', k.dump()) w = Imported_Wallet(self.storage) - for x in text.split(): + for x in keystore.get_private_keys(text): w.import_private_key(x, None) self.keystores.append(w.keystore) else: From cf866adfe3063f3603be28216606d9448ad0da19 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 12 Mar 2018 10:30:56 +0100 Subject: [PATCH 061/174] fix #4109 --- lib/wallet.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index 48a11d4fc..40ca6c736 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1737,11 +1737,12 @@ class Abstract_Wallet(PrintError): def txin_value(self, txin): txid = txin['prevout_hash'] prev_n = txin['prevout_n'] - for address, d in self.txo[txid].items(): + for address, d in self.txo.get(txid, {}).items(): for n, v, cb in d: if n == prev_n: return v - raise BaseException('unknown txin value') + # may occur if wallet is not synchronized + return None def price_at_timestamp(self, txid, price_func): height, conf, timestamp = self.get_tx_height(txid) @@ -1770,6 +1771,8 @@ class Abstract_Wallet(PrintError): Acquisition price of a coin. This assumes that either all inputs are mine, or no input is mine. """ + if txin_value is None: + return Decimal('NaN') cache_key = "{}:{}:{}".format(str(txid), str(ccy), str(txin_value)) result = self.coin_price_cache.get(cache_key, None) if result is not None: From 152ec1447ced1a33a372d1684caa8be2ae1755c2 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 12 Mar 2018 11:56:00 +0100 Subject: [PATCH 062/174] fix #4100: spent_outpoints does not track everything --- lib/wallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/wallet.py b/lib/wallet.py index 40ca6c736..c5495de26 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -890,7 +890,7 @@ class Abstract_Wallet(PrintError): # undo spent_outpoints that are in pruned_txo for ser, hh in list(self.pruned_txo.items()): if hh == tx_hash: - self.spent_outpoints.pop(ser) + self.spent_outpoints.pop(ser, None) self.pruned_txo.pop(ser) # add tx to pruned_txo, and undo the txi addition From c3e26a1e2b8b579c64f765d02e015df65e82934c Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 12 Mar 2018 12:19:45 +0100 Subject: [PATCH 063/174] fix #4098 --- lib/wallet.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index c5495de26..10bb77b0b 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1628,13 +1628,14 @@ class Abstract_Wallet(PrintError): return True def get_sorted_requests(self, config): - def f(x): + def f(addr): try: - addr = x.get('address') - return self.get_address_index(addr) or addr + return self.get_address_index(addr) except: - return addr - return sorted(map(lambda x: self.get_payment_request(x, config), self.receive_requests.keys()), key=f) + return + keys = map(lambda x: (f(x), x), self.receive_requests.keys()) + sorted_keys = sorted(filter(lambda x: x[0] is not None, keys)) + return [self.get_payment_request(x[1], config) for x in sorted_keys] def get_fingerprint(self): raise NotImplementedError() From 79edd2dbf1ad98ed255d85bace7b4622a1f4560e Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Mon, 12 Mar 2018 16:58:05 +0100 Subject: [PATCH 064/174] Fix crowdin upload --- contrib/make_locale | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/contrib/make_locale b/contrib/make_locale index 2831cfeb3..3ab4b21cf 100755 --- a/contrib/make_locale +++ b/contrib/make_locale @@ -28,11 +28,11 @@ os.system(cmd) os.chdir('lib') crowdin_identifier = 'electrum' -crowdin_file_name = 'electrum-client/messages.pot' +crowdin_file_name = 'files[electrum-client/messages.pot]' locale_file_name = 'locale/messages.pot' crowdin_api_key = None -filename = '~/.crowdin_api_key' +filename = os.path.expanduser('~/.crowdin_api_key') if os.path.exists(filename): with open(filename) as f: crowdin_api_key = f.read().strip() @@ -44,13 +44,14 @@ if crowdin_api_key: # Push to Crowdin print('Push to Crowdin') url = ('https://api.crowdin.com/api/project/' + crowdin_identifier + '/update-file?key=' + crowdin_api_key) - with open(locale_file_name,'rb') as f: + with open(locale_file_name, 'rb') as f: files = {crowdin_file_name: f} - requests.request('POST', url, files=files) + response = requests.request('POST', url, files=files) + print("", "update-file:", "-"*20, response.text, "-"*20, sep="\n") # Build translations print('Build translations') - response = requests.request('GET', 'https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key).content - print(response) + response = requests.request('GET', 'https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key) + print("", "export:", "-" * 20, response.text, "-" * 20, sep="\n") # Download & unzip print('Download translations') From 3234917ea1356143b837183a5d6009724b1f18c4 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 12 Mar 2018 18:27:36 +0100 Subject: [PATCH 065/174] release notes of version 3.1.1 --- RELEASE-NOTES | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 258efb5d2..dff649b56 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,5 +1,19 @@ -# Release 3.1 - (March 5, 2018) +# Release 3.1.1 - (March 12, 2018) + + * fix #4031: Trezor T support + * partial fix #4060: proxy and hardware wallet can't be used together + * fix #4039: can't set address labels + * fix crash related to coinbase transactions + * MacOS: use internal graphics card + * fix openalias related crashes + * speed-up capital gains calculations + * hw wallet encryption: re-prompt for passphrase if incorrect + * other minor fixes. + + + +# Release 3.1.0 - (March 5, 2018) * Memory-pool based fee estimation. Dynamic fees can target a desired depth in the memory pool. This feature is optional, and ETA-based From 87aee100470be979bb324d186559441e5c914b6a Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 12 Mar 2018 21:23:37 +0100 Subject: [PATCH 066/174] fix #4111 --- plugins/cosigner_pool/qt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/cosigner_pool/qt.py b/plugins/cosigner_pool/qt.py index 524d62b76..e00edd3ba 100644 --- a/plugins/cosigner_pool/qt.py +++ b/plugins/cosigner_pool/qt.py @@ -173,7 +173,8 @@ class Plugin(BasePlugin): for window, xpub, K, _hash in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue - message = bitcoin.encrypt_message(bfh(tx.raw), bh2u(K)).decode('ascii') + raw_tx_bytes = bfh(str(tx)) + message = bitcoin.encrypt_message(raw_tx_bytes, bh2u(K)).decode('ascii') try: server.put(_hash, message) except Exception as e: From 8e79d095110a2ea89cad675a8db4bfdf7d1ac4ce Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 12 Mar 2018 21:50:56 +0100 Subject: [PATCH 067/174] disallow adding receive requests without valid is_mine addresses --- gui/kivy/uix/screens.py | 17 ++++++++++++----- lib/wallet.py | 5 +++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py index a4cd31442..bc94e3979 100644 --- a/gui/kivy/uix/screens.py +++ b/gui/kivy/uix/screens.py @@ -371,13 +371,20 @@ class ReceiveScreen(CScreen): def save_request(self): addr = self.screen.address if not addr: - return + return False amount = self.screen.amount message = self.screen.message amount = self.app.get_amount(amount) if amount else 0 req = self.app.wallet.make_payment_request(addr, amount, message, None) - self.app.wallet.add_payment_request(req, self.app.electrum_config) - self.app.update_tab('requests') + try: + self.app.wallet.add_payment_request(req, self.app.electrum_config) + added_request = True + except Exception as e: + self.app.show_error(_('Error adding payment request') + ':\n' + str(e)) + added_request = False + finally: + self.app.update_tab('requests') + return added_request def on_amount_or_message(self): self.save_request() @@ -389,8 +396,8 @@ class ReceiveScreen(CScreen): self.app.show_info(_('Please use the existing requests first.')) def do_save(self): - self.save_request() - self.app.show_info(_('Request was saved.')) + if self.save_request(): + self.app.show_info(_('Request was saved.')) class TabbedCarousel(Factory.TabbedPanel): diff --git a/lib/wallet.py b/lib/wallet.py index 10bb77b0b..a6adf9c7c 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1588,6 +1588,11 @@ class Abstract_Wallet(PrintError): def add_payment_request(self, req, config): addr = req['address'] + if not bitcoin.is_address(addr): + raise Exception(_('Invalid Bitcoin address.')) + if not self.is_mine(addr): + raise Exception(_('Address not in wallet.')) + amount = req.get('amount') message = req.get('memo') self.receive_requests[addr] = req From a6841cbd5fb826eb12cf562eaeaf4929248a6dc8 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 13 Mar 2018 01:02:03 +0100 Subject: [PATCH 068/174] fix #4099: serialisation of txns with negative version number --- lib/bitcoin.py | 3 +++ lib/tests/test_transaction.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 731d10f51..cd277f4d7 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -144,6 +144,9 @@ def rev_hex(s): def int_to_hex(i, length=1): assert isinstance(i, int) + if i < 0: + # two's complement + i = pow(256, length) + i s = hex(i)[2:].rstrip('L') s = "0"*(2*length - len(s)) + s return rev_hex(s) diff --git a/lib/tests/test_transaction.py b/lib/tests/test_transaction.py index c92c6fff7..794099f37 100644 --- a/lib/tests/test_transaction.py +++ b/lib/tests/test_transaction.py @@ -265,6 +265,11 @@ class TestTransaction(unittest.TestCase): txid = 'f570d5d1e965ee61bcc7005f8fefb1d3abbed9d7ddbe035e2a68fa07e5fc4a0d' self._run_naive_tests_on_tx(raw_tx, txid) + def test_txid_negative_version_num(self): + raw_tx = 'f0b47b9a01ecf5e5c3bbf2cf1f71ecdc7f708b0b222432e914b394e24aad1494a42990ddfc000000008b483045022100852744642305a99ad74354e9495bf43a1f96ded470c256cd32e129290f1fa191022030c11d294af6a61b3da6ed2c0c296251d21d113cfd71ec11126517034b0dcb70014104a0fe6e4a600f859a0932f701d3af8e0ecd4be886d91045f06a5a6b931b95873aea1df61da281ba29cadb560dad4fc047cf47b4f7f2570da4c0b810b3dfa7e500ffffffff0240420f00000000001976a9147eeacb8a9265cd68c92806611f704fc55a21e1f588ac05f00d00000000001976a914eb3bd8ccd3ba6f1570f844b59ba3e0a667024a6a88acff7f0000' + txid = 'c659729a7fea5071361c2c1a68551ca2bf77679b27086cc415adeeb03852e369' + self._run_naive_tests_on_tx(raw_tx, txid) + # these transactions are from Bitcoin Core unit tests ---> # https://github.com/bitcoin/bitcoin/blob/11376b5583a283772c82f6d32d0007cdbf5b8ef0/src/test/data/tx_valid.json From 1f1ce26211d5fc7e4ee1c52244c6dc79e68941a0 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 13 Mar 2018 06:22:38 +0100 Subject: [PATCH 069/174] fix transaction dialog for p2pk input --- gui/qt/transaction_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py index 8344f82b3..904828d23 100644 --- a/gui/qt/transaction_dialog.py +++ b/gui/qt/transaction_dialog.py @@ -289,7 +289,7 @@ class TxDialog(QDialog, MessageBoxMixin): cursor.insertText(prevout_hash[-8:] + ":%-4d " % prevout_n, ext) addr = x.get('address') if addr == "(pubkey)": - _addr = self.wallet.find_pay_to_pubkey_address(prevout_hash, prevout_n) + _addr = self.wallet.get_txin_address(x) if _addr: addr = _addr if addr is None: From b043c872eb0ce167948293a9a241dba54d6691a9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 13 Mar 2018 06:37:02 +0100 Subject: [PATCH 070/174] fix paying to script --- gui/qt/paytoedit.py | 6 ++++-- lib/transaction.py | 12 ++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py index d13d80390..8adbfcaee 100644 --- a/gui/qt/paytoedit.py +++ b/gui/qt/paytoedit.py @@ -93,9 +93,11 @@ class PayToEdit(ScanQRTextEdit): for word in x.split(): if word[0:3] == 'OP_': assert word in opcodes.lookup - script += chr(opcodes.lookup[word]) + opcode_int = opcodes.lookup[word] + assert opcode_int < 256 # opcode is single-byte + script += bitcoin.int_to_hex(opcode_int) else: - script += push_script(word).decode('hex') + script += push_script(word) return script def parse_amount(self, x): diff --git a/lib/transaction.py b/lib/transaction.py index aac770392..5fca0b8bc 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -229,10 +229,10 @@ opcodes = Enumeration("Opcodes", [ "OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160", "OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG", "OP_CHECKMULTISIGVERIFY", - ("OP_SINGLEBYTE_END", 0xF0), - ("OP_DOUBLEBYTE_BEGIN", 0xF000), - "OP_PUBKEY", "OP_PUBKEYHASH", - ("OP_INVALIDOPCODE", 0xFFFF), + ("OP_NOP1", 0xB0), + ("OP_CHECKLOCKTIMEVERIFY", 0xB1), ("OP_CHECKSEQUENCEVERIFY", 0xB2), + "OP_NOP4", "OP_NOP5", "OP_NOP6", "OP_NOP7", "OP_NOP8", "OP_NOP9", "OP_NOP10", + ("OP_INVALIDOPCODE", 0xFF), ]) @@ -242,10 +242,6 @@ def script_GetOp(_bytes): vch = None opcode = _bytes[i] i += 1 - if opcode >= opcodes.OP_SINGLEBYTE_END: - opcode <<= 8 - opcode |= _bytes[i] - i += 1 if opcode <= opcodes.OP_PUSHDATA4: nSize = opcode From 8ad111945ad372656122abc38ea194c0ec29e3a5 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 13 Mar 2018 07:05:56 +0100 Subject: [PATCH 071/174] paytoedit: data to be pushed on stack needs to be hex --- gui/qt/paytoedit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py index 8adbfcaee..f37d44ee5 100644 --- a/gui/qt/paytoedit.py +++ b/gui/qt/paytoedit.py @@ -31,6 +31,7 @@ from .qrtextedit import ScanQRTextEdit import re from decimal import Decimal from electrum import bitcoin +from electrum.util import bfh from . import util @@ -97,6 +98,7 @@ class PayToEdit(ScanQRTextEdit): assert opcode_int < 256 # opcode is single-byte script += bitcoin.int_to_hex(opcode_int) else: + bfh(word) # to test it is hex data script += push_script(word) return script From b009c56b9db8abef998e0cdf5d2457256543cb4e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 13 Mar 2018 10:42:56 +0100 Subject: [PATCH 072/174] kivy: save requests only with the save button --- gui/kivy/uix/screens.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py index a4cd31442..ff4bafc31 100644 --- a/gui/kivy/uix/screens.py +++ b/gui/kivy/uix/screens.py @@ -380,7 +380,6 @@ class ReceiveScreen(CScreen): self.app.update_tab('requests') def on_amount_or_message(self): - self.save_request() Clock.schedule_once(lambda dt: self.update_qr()) def do_new(self): From e0122f8c63080a32e3484f4917ef485af09e0114 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 13 Mar 2018 14:54:21 +0100 Subject: [PATCH 073/174] disable save button for partially signed tx --- gui/qt/transaction_dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py index 904828d23..d53997baf 100644 --- a/gui/qt/transaction_dialog.py +++ b/gui/qt/transaction_dialog.py @@ -179,7 +179,8 @@ class TxDialog(QDialog, MessageBoxMixin): def sign(self): def sign_done(success): - if success: + # note: with segwit we could save partially signed tx, because they have a txid + if self.tx.is_complete(): self.prompt_if_unsaved = True self.saved = False self.save_button.setDisabled(False) From 4137ae94a0e16d2de885b98d0ebf3d23adcb81ac Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 13 Mar 2018 15:07:19 +0100 Subject: [PATCH 074/174] flush certificate file; might fix #4059 --- lib/interface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/interface.py b/lib/interface.py index f7fe82420..2e76d9870 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -174,6 +174,8 @@ class TcpConnection(threading.Thread, util.PrintError): temporary_path = cert_path + '.temp' with open(temporary_path,"w") as f: f.write(cert) + f.flush() + os.fsync(f.fileno()) else: is_new = False From 38ec65716c98950a874074480cd9321b2b240c17 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 13 Mar 2018 15:31:29 +0100 Subject: [PATCH 075/174] fix #4116 --- gui/qt/main_window.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index ccd051e4e..466ef2c5d 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -409,13 +409,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): filename, __ = QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) if not filename: return - new_path = os.path.join(wallet_folder, filename) if new_path != path: try: shutil.copy2(path, new_path) self.show_message(_("A copy of your wallet file was created in")+" '%s'" % str(new_path), title=_("Wallet backup created")) - except (IOError, os.error) as reason: + except BaseException as reason: self.show_critical(_("Electrum was unable to copy your wallet file to the specified location.") + "\n" + str(reason), title=_("Unable to create backup")) def update_recently_visited(self, filename): From 71c5e4f6bcfcfbf8944b1a1d84693a1a65b694bd Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Tue, 13 Mar 2018 17:36:21 +0100 Subject: [PATCH 076/174] Add badge for crowdin to README --- README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9b1832354..8458581e5 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,9 @@ Electrum - Lightweight Bitcoin client .. image:: https://coveralls.io/repos/github/spesmilo/electrum/badge.svg?branch=master :target: https://coveralls.io/github/spesmilo/electrum?branch=master :alt: Test coverage statistics - +.. image:: https://img.shields.io/badge/help-translating-blue.svg + :target: https://crowdin.com/project/electrum + :alt: Help translating Electrum online From 7e6fba0513b6a8db0a0546e8184f7d7a66008585 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Tue, 13 Mar 2018 23:38:43 +0100 Subject: [PATCH 077/174] Make generated .app deterministic --- contrib/build-osx/make_osx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contrib/build-osx/make_osx b/contrib/build-osx/make_osx index e5a656049..c459503e9 100755 --- a/contrib/build-osx/make_osx +++ b/contrib/build-osx/make_osx @@ -77,6 +77,13 @@ fail "Could not install hardware wallet requirements" info "Building $PACKAGE..." python3 setup.py install --user > /dev/null || fail "Could not build $PACKAGE" +info "Faking timestamps..." +for d in ~/Library/Python/ ~/.pyenv .; do + pushd $d + find . -exec touch -t '200101220000' {} + + popd +done + info "Building binary" pyinstaller --noconfirm --ascii --name $VERSION contrib/build-osx/osx.spec || fail "Could not build binary" From 0f5cabc7f6500a3143f7ad8e71ab9cb84083ee09 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 14 Mar 2018 12:42:42 +0100 Subject: [PATCH 078/174] fix #4122 --- electrum | 7 +++++-- plugins/hw_wallet/cmdline.py | 3 +++ plugins/keepkey/clientbase.py | 3 +++ plugins/keepkey/qt_generic.py | 2 +- plugins/trezor/clientbase.py | 3 +++ plugins/trezor/qt_generic.py | 2 +- 6 files changed, 16 insertions(+), 4 deletions(-) diff --git a/electrum b/electrum index 78924631e..783d5c13b 100755 --- a/electrum +++ b/electrum @@ -93,7 +93,7 @@ from electrum import constants from electrum import SimpleConfig, Network from electrum.wallet import Wallet, Imported_Wallet from electrum.storage import WalletStorage, get_derivation_used_for_hw_device_encryption -from electrum.util import print_msg, print_stderr, json_encode, json_decode +from electrum.util import print_msg, print_stderr, json_encode, json_decode, UserCancelled from electrum.util import set_verbosity, InvalidPassword from electrum.commands import get_parser, known_commands, Commands, config_variables from electrum import daemon @@ -295,7 +295,10 @@ def get_password_for_hw_device_encrypted_storage(plugins): name, device_info = devices[0] plugin = plugins.get_plugin(name) derivation = get_derivation_used_for_hw_device_encryption() - xpub = plugin.get_xpub(device_info.device.id_, derivation, 'standard', plugin.handler) + try: + xpub = plugin.get_xpub(device_info.device.id_, derivation, 'standard', plugin.handler) + except UserCancelled: + sys.exit(0) password = keystore.Xpub.get_pubkey_from_xpub(xpub, ()) return password diff --git a/plugins/hw_wallet/cmdline.py b/plugins/hw_wallet/cmdline.py index 999f82994..6cd27a001 100644 --- a/plugins/hw_wallet/cmdline.py +++ b/plugins/hw_wallet/cmdline.py @@ -32,6 +32,9 @@ class CmdLineHandler: def show_message(self, msg, on_cancel=None): print_msg(msg) + def show_error(self, msg): + print_msg(msg) + def update_status(self, b): print_error('trezor status', b) diff --git a/plugins/keepkey/clientbase.py b/plugins/keepkey/clientbase.py index 6b33c9d43..b354798a2 100644 --- a/plugins/keepkey/clientbase.py +++ b/plugins/keepkey/clientbase.py @@ -50,6 +50,9 @@ class GuiMixin(object): else: msg = _("Enter your current {} PIN:") pin = self.handler.get_pin(msg.format(self.device)) + if len(pin) > 9: + self.handler.show_error(_('The PIN cannot be longer than 9 characters.')) + pin = '' # to cancel below if not pin: return self.proto.Cancel() return self.proto.PinMatrixAck(pin=pin) diff --git a/plugins/keepkey/qt_generic.py b/plugins/keepkey/qt_generic.py index a66e8f3d2..7cdc50769 100644 --- a/plugins/keepkey/qt_generic.py +++ b/plugins/keepkey/qt_generic.py @@ -250,7 +250,7 @@ class QtPlugin(QtPluginBase): vbox.addWidget(QLabel(msg)) vbox.addWidget(text) pin = QLineEdit() - pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,10}'))) + pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}'))) pin.setMaximumWidth(100) hbox_pin = QHBoxLayout() hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py index 6e10d4c49..e7f0d43c8 100644 --- a/plugins/trezor/clientbase.py +++ b/plugins/trezor/clientbase.py @@ -50,6 +50,9 @@ class GuiMixin(object): else: msg = _("Enter your current {} PIN:") pin = self.handler.get_pin(msg.format(self.device)) + if len(pin) > 9: + self.handler.show_error(_('The PIN cannot be longer than 9 characters.')) + pin = '' # to cancel below if not pin: return self.proto.Cancel() return self.proto.PinMatrixAck(pin=pin) diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py index 808f83a6a..032a02f16 100644 --- a/plugins/trezor/qt_generic.py +++ b/plugins/trezor/qt_generic.py @@ -251,7 +251,7 @@ class QtPlugin(QtPluginBase): vbox.addWidget(QLabel(msg)) vbox.addWidget(text) pin = QLineEdit() - pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,10}'))) + pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}'))) pin.setMaximumWidth(100) hbox_pin = QHBoxLayout() hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) From 5e5134b76ffec8ed9702f5661a1d16e9e52a24c8 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 14 Mar 2018 14:59:27 +0100 Subject: [PATCH 079/174] remove custom entropy option again (follow-up e0c38b3), because seeds can be extended with passphrase --- lib/commands.py | 12 ++---------- lib/mnemonic.py | 23 ++++++++--------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/lib/commands.py b/lib/commands.py index 23c35b896..9106b1ca4 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -159,19 +159,13 @@ class Commands: return True @command('') - def make_seed(self, nbits=132, entropy=1, language=None, segwit=False): + def make_seed(self, nbits=132, language=None, segwit=False): """Create a seed""" from .mnemonic import Mnemonic t = 'segwit' if segwit else 'standard' - s = Mnemonic(language).make_seed(t, nbits, custom_entropy=entropy) + s = Mnemonic(language).make_seed(t, nbits) return s - @command('') - def check_seed(self, seed, entropy=1, language=None): - """Check that a seed was generated with given entropy""" - from .mnemonic import Mnemonic - return Mnemonic(language).check_seed(seed, entropy) - @command('n') def getaddresshistory(self, address): """Return the transaction history of any address. Note: This is a @@ -697,7 +691,6 @@ command_options = { '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"), 'segwit': (None, "Create segwit seed"), 'language': ("-L", "Default language for wordlist"), 'privkey': (None, "Private key. Set to '?' to get a prompt."), @@ -726,7 +719,6 @@ arg_types = { 'nbits': int, 'imax': int, 'year': int, - 'entropy': int, 'tx': tx_from_str, 'pubkeys': json_loads, 'jsontx': json_loads, diff --git a/lib/mnemonic.py b/lib/mnemonic.py index 7096e20f6..846dcc74f 100644 --- a/lib/mnemonic.py +++ b/lib/mnemonic.py @@ -157,28 +157,21 @@ class Mnemonic(object): i = i*n + k return i - def check_seed(self, seed, custom_entropy): - assert is_new_seed(seed) - i = self.mnemonic_decode(seed) - return i % custom_entropy == 0 - - def make_seed(self, seed_type='standard', num_bits=132, custom_entropy=1): + def make_seed(self, seed_type='standard', num_bits=132): prefix = version.seed_prefix(seed_type) # increase num_bits in order to obtain a uniform distibution for the last word bpw = math.log(len(self.wordlist), 2) - num_bits = int(math.ceil(num_bits/bpw) * bpw) - # handle custom entropy; make sure we add at least 16 bits - n_custom = int(math.ceil(math.log(custom_entropy, 2))) - n = max(16, num_bits - n_custom) - print_error("make_seed", prefix, "adding %d bits"%n) - my_entropy = 1 - while my_entropy < pow(2, n - bpw): + # rounding + n = int(math.ceil(num_bits/bpw) * bpw) + print_error("make_seed. prefix: '%s'"%prefix, "entropy: %d bits"%n) + entropy = 1 + while entropy < pow(2, n - bpw): # try again if seed would not contain enough words - my_entropy = ecdsa.util.randrange(pow(2, n)) + entropy = ecdsa.util.randrange(pow(2, n)) nonce = 0 while True: nonce += 1 - i = custom_entropy * (my_entropy + nonce) + i = entropy + nonce seed = self.mnemonic_encode(i) assert i == self.mnemonic_decode(seed) if is_old_seed(seed): From 8589c1b0bb79c3c6a8506d5ef875bd1e2bb055e6 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 14 Mar 2018 15:18:26 +0100 Subject: [PATCH 080/174] ledger: don't throw exception if user cancels signing Used to show "Exception : Invalid status 6985", which is not really user-friendly. This now mimics the behaviour with Trezor where we silently ignore cancellation (not showing any popup). --- plugins/ledger/ledger.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py index 3d34bc9da..9ab52709d 100644 --- a/plugins/ledger/ledger.py +++ b/plugins/ledger/ledger.py @@ -229,6 +229,16 @@ class Ledger_KeyStore(Hardware_KeyStore): self.client = None raise Exception(message) + def set_and_unset_signing(func): + """Function decorator to set and unset self.signing.""" + def wrapper(self, *args, **kwargs): + try: + self.signing = True + return func(self, *args, **kwargs) + finally: + self.signing = False + return wrapper + def address_id_stripped(self, address): # Strip the leading "m/" change, index = self.get_address_index(address) @@ -239,8 +249,8 @@ class Ledger_KeyStore(Hardware_KeyStore): def decrypt_message(self, pubkey, message, password): raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device)) + @set_and_unset_signing def sign_message(self, sequence, message, password): - self.signing = True message = message.encode('utf8') message_hash = hashlib.sha256(message).hexdigest().upper() # prompt for the PIN before displaying the dialog if necessary @@ -259,16 +269,17 @@ class Ledger_KeyStore(Hardware_KeyStore): except BTChipException as e: if e.sw == 0x6a80: self.give_error("Unfortunately, this message cannot be signed by the Ledger wallet. Only alphanumerical messages shorter than 140 characters are supported. Please remove any extra characters (tab, carriage return) and retry.") + elif e.sw == 0x6985: # cancelled by user + return b'' else: self.give_error(e, True) except UserWarning: self.handler.show_error(_('Cancelled by user')) - return '' + return b'' except Exception as e: self.give_error(e, True) finally: self.handler.finished() - self.signing = False # Parse the ASN.1 signature rLength = signature[3] r = signature[4 : 4 + rLength] @@ -281,12 +292,11 @@ class Ledger_KeyStore(Hardware_KeyStore): # And convert it return bytes([27 + 4 + (signature[0] & 0x01)]) + r + s - + @set_and_unset_signing def sign_transaction(self, tx, password): if tx.is_complete(): return client = self.get_client() - self.signing = True inputs = [] inputsPaths = [] pubKeys = [] @@ -446,6 +456,12 @@ class Ledger_KeyStore(Hardware_KeyStore): except UserWarning: self.handler.show_error(_('Cancelled by user')) return + except BTChipException as e: + if e.sw == 0x6985: # cancelled by user + return + else: + traceback.print_exc(file=sys.stderr) + self.give_error(e, True) except BaseException as e: traceback.print_exc(file=sys.stdout) self.give_error(e, True) @@ -456,10 +472,9 @@ class Ledger_KeyStore(Hardware_KeyStore): signingPos = inputs[i][4] txin['signatures'][signingPos] = bh2u(signatures[i]) tx.raw = tx.serialize() - self.signing = False + @set_and_unset_signing def show_address(self, sequence, txin_type): - self.signing = True client = self.get_client() address_path = self.get_derivation()[2:] + "/%d/%d"%sequence self.handler.show_message(_("Showing address ...")) @@ -478,7 +493,6 @@ class Ledger_KeyStore(Hardware_KeyStore): self.handler.show_error(e) finally: self.handler.finished() - self.signing = False class LedgerPlugin(HW_PluginBase): libraries_available = BTCHIP From 37853ab9391b64198f67db1c1a48c27752478e98 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 15 Mar 2018 06:08:13 +0100 Subject: [PATCH 081/174] trezor/keepkey: don't show empty error on PIN prompt cancel --- lib/base_wizard.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/base_wizard.py b/lib/base_wizard.py index 016fbdf52..f8dfed3a7 100644 --- a/lib/base_wizard.py +++ b/lib/base_wizard.py @@ -243,6 +243,9 @@ class BaseWizard(object): devmgr.unpair_id(device_info.device.id_) self.choose_hw_device(purpose) return + except UserCancelled: + self.choose_hw_device(purpose) + return except BaseException as e: self.show_error(str(e)) self.choose_hw_device(purpose) From 8dd19a5920d5e586d6f03df405f07515482167fd Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 15 Mar 2018 06:14:57 +0100 Subject: [PATCH 082/174] wizard: remove unused method --- lib/base_wizard.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/base_wizard.py b/lib/base_wizard.py index f8dfed3a7..b9f792e92 100644 --- a/lib/base_wizard.py +++ b/lib/base_wizard.py @@ -470,10 +470,6 @@ class BaseWizard(object): def show_xpub_and_add_cosigners(self, xpub): self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) - def on_cosigner(self, text, password, i): - k = keystore.from_master_key(text, password) - self.on_keystore(k) - def choose_seed_type(self): title = _('Choose Seed type') message = ' '.join([ From 9837a02c951a505e7a13fab3867e894f9e54d9d2 Mon Sep 17 00:00:00 2001 From: Jason Bruderer Date: Wed, 14 Mar 2018 23:46:23 -0600 Subject: [PATCH 083/174] Fix "same wallet can be opened multiple times via InstallWizard" (#4076) * Fix #4073 * Account for if the wallet is already in the daemon * Only start a new thread if it doesn't exist * Modify run_and_get_wallet to not return duplicate wallets * Inform user if encrypted wallet is already open in memory --- gui/qt/__init__.py | 66 +++++++++++++++++++++-------------------- gui/qt/installwizard.py | 21 +++++++++---- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/gui/qt/__init__.py b/gui/qt/__init__.py index 0879208f9..221fdf1dc 100644 --- a/gui/qt/__init__.py +++ b/gui/qt/__init__.py @@ -185,42 +185,44 @@ class ElectrumGui: def start_new_window(self, path, uri): '''Raises the window for the wallet if it is open. Otherwise - opens the wallet and creates a new window for it.''' - for w in self.windows: - if w.wallet.storage.path == path: - w.bring_to_top() - break - else: + opens the wallet and creates a new window for it''' + try: + wallet = self.daemon.load_wallet(path, None) + except BaseException as e: + traceback.print_exc(file=sys.stdout) + d = QMessageBox(QMessageBox.Warning, _('Error'), + _('Cannot load wallet:') + '\n' + str(e)) + d.exec_() + return + if not wallet: + storage = WalletStorage(path, manual_upgrades=True) + wizard = InstallWizard(self.config, self.app, self.plugins, storage) try: - wallet = self.daemon.load_wallet(path, None) - except BaseException as e: - traceback.print_exc(file=sys.stdout) - d = QMessageBox(QMessageBox.Warning, _('Error'), - _('Cannot load wallet:') + '\n' + str(e)) - d.exec_() - return + wallet = wizard.run_and_get_wallet(self.daemon.get_wallet) + except UserCancelled: + pass + except GoBack as e: + print_error('[start_new_window] Exception caught (GoBack)', e) + wizard.terminate() if not wallet: - storage = WalletStorage(path, manual_upgrades=True) - wizard = InstallWizard(self.config, self.app, self.plugins, storage) - try: - wallet = wizard.run_and_get_wallet() - except UserCancelled: - pass - except GoBack as e: - print_error('[start_new_window] Exception caught (GoBack)', e) - wizard.terminate() - if not wallet: - return + return + + if not self.daemon.get_wallet(wallet.storage.path): + # wallet was not in memory wallet.start_threads(self.daemon.network) self.daemon.add_wallet(wallet) - try: - w = self.create_window_for_wallet(wallet) - except BaseException as e: - traceback.print_exc(file=sys.stdout) - d = QMessageBox(QMessageBox.Warning, _('Error'), - _('Cannot create window for wallet:') + '\n' + str(e)) - d.exec_() - return + try: + for w in self.windows: + if w.wallet.storage.path == wallet.storage.path: + w.bring_to_top() + return + w = self.create_window_for_wallet(wallet) + except BaseException as e: + traceback.print_exc(file=sys.stdout) + d = QMessageBox(QMessageBox.Warning, _('Error'), + _('Cannot create window for wallet:') + '\n' + str(e)) + d.exec_() + return if uri: w.pay_to_URI(uri) w.bring_to_top() diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py index 283dff18a..6bef0da35 100644 --- a/gui/qt/installwizard.py +++ b/gui/qt/installwizard.py @@ -148,7 +148,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame. - def run_and_get_wallet(self): + def run_and_get_wallet(self, get_wallet_from_daemon): vbox = QVBoxLayout() hbox = QHBoxLayout() @@ -181,8 +181,12 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): def on_filename(filename): path = os.path.join(wallet_folder, filename) + wallet_from_memory = get_wallet_from_daemon(path) try: - self.storage = WalletStorage(path, manual_upgrades=True) + if wallet_from_memory: + self.storage = wallet_from_memory.storage + else: + self.storage = WalletStorage(path, manual_upgrades=True) self.next_button.setEnabled(True) except BaseException: traceback.print_exc(file=sys.stderr) @@ -193,7 +197,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): msg =_("This file does not exist.") + '\n' \ + _("Press 'Next' to create this wallet, or choose another file.") pw = False - else: + elif not wallet_from_memory: if self.storage.is_encrypted_with_user_pw(): msg = _("This file is encrypted with a password.") + '\n' \ + _('Enter your password or choose another file.') @@ -205,6 +209,10 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): else: msg = _("Press 'Next' to open this wallet.") pw = False + else: + msg = _("This file is already open in memory.") + "\n" \ + + _("Press 'Next' to create/focus window.") + pw = False else: msg = _('Cannot read file') pw = False @@ -229,6 +237,9 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): return if not self.storage.file_exists(): break + wallet_from_memory = get_wallet_from_daemon(self.storage.path) + if wallet_from_memory: + return wallet_from_memory if self.storage.file_exists() and self.storage.is_encrypted(): if self.storage.is_encrypted_with_user_pw(): password = self.pw_e.text() @@ -251,7 +262,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): _('Failed to decrypt using this hardware device.') + '\n' + _('If you use a passphrase, make sure it is correct.')) self.stack = [] - return self.run_and_get_wallet() + return self.run_and_get_wallet(get_wallet_from_daemon) except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.information(None, _('Error'), str(e)) @@ -301,8 +312,6 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): self.wallet = Wallet(self.storage) return self.wallet - - def finished(self): """Called in hardware client wrapper, in order to close popups.""" return From 1bc564063611425df15766e9847213629b7351cf Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 15 Mar 2018 08:25:31 +0100 Subject: [PATCH 084/174] qt main_window change_password_dialog: catch InvalidPassword instead --- gui/qt/main_window.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 466ef2c5d..53dabfb05 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -47,7 +47,7 @@ from electrum.i18n import _ from electrum.util import (format_time, format_satoshis, PrintError, format_satoshis_plain, NotEnoughFunds, UserCancelled, NoDynamicFeeEstimates, profiler, - export_meta, import_meta, bh2u, bfh) + export_meta, import_meta, bh2u, bfh, InvalidPassword) from electrum import Transaction from electrum import util, bitcoin, commands, coinchooser from electrum import paymentrequest @@ -1972,10 +1972,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): return try: self.wallet.update_password(old_password, new_password, encrypt_file) - except BaseException as e: + except InvalidPassword as e: self.show_error(str(e)) return - except: + except BaseException: traceback.print_exc(file=sys.stdout) self.show_error(_('Failed to update password')) return From 38d94bfa7a25dc575fa472e84b69afe01872119d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 15 Mar 2018 13:06:21 +0100 Subject: [PATCH 085/174] fix #4082 --- plugins/trezor/trezor.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/plugins/trezor/trezor.py b/plugins/trezor/trezor.py index b0dd933fb..d9da690eb 100644 --- a/plugins/trezor/trezor.py +++ b/plugins/trezor/trezor.py @@ -132,11 +132,28 @@ class TrezorPlugin(HW_PluginBase): except ImportError: # compat for trezorlib < 0.9.2 def all_transports(): - from trezorlib.transport_bridge import BridgeTransport - from trezorlib.transport_hid import HidTransport - from trezorlib.transport_udp import UdpTransport - from trezorlib.transport_webusb import WebUsbTransport - return (BridgeTransport, HidTransport, UdpTransport, WebUsbTransport) + transports = [] + try: + from trezorlib.transport_bridge import BridgeTransport + transports.append(BridgeTransport) + except BaseException: + pass + try: + from trezorlib.transport_hid import HidTransport + transports.append(HidTransport) + except BaseException: + pass + try: + from trezorlib.transport_udp import UdpTransport + transports.append(UdpTransport) + except BaseException: + pass + try: + from trezorlib.transport_webusb import WebUsbTransport + transports.append(WebUsbTransport) + except BaseException: + pass + return transports devices = [] for transport in all_transports(): From a02bd2c18343b0fe11103cbf8912d4096f407e37 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Thu, 15 Mar 2018 14:00:02 +0100 Subject: [PATCH 086/174] Travis: Make Windows build more reliable by trying different key servers --- contrib/build-wine/prepare-wine.sh | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index c1bed9beb..d19ca7365 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -54,6 +54,27 @@ download_if_not_exist() { fi } +# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh +retry() { + local result=0 + local count=1 + while [ $count -le 3 ]; do + [ $result -ne 0 ] && { + echo -e "\nThe command \"$@\" failed. Retrying, $count of 3.\n" >&2 + } + ! { "$@"; result=$?; } + [ $result -eq 0 ] && break + count=$(($count + 1)) + sleep 1 + done + + [ $count -gt 3 ] && { + echo -e "\nThe command \"$@\" failed 3 times.\n" >&2 + } + + return $result +} + # Let's begin! here=$(dirname $(readlink -e $0)) set -e @@ -74,8 +95,8 @@ cd /tmp/electrum-build # keys from https://www.python.org/downloads/#pubkeys KEYLIST_PYTHON_DEV="531F072D39700991925FED0C0EDDC5F26A45C816 26DEA9D4613391EF3E25C9FF0A5B101836580288 CBC547978A3964D14B9AB36A6AF053F07D9DC8D2 C01E1CAD5EA2C4F0B8E3571504C367C218ADD4FF 12EF3DC38047DA382D18A5B999CDEA9DA4135B38 8417157EDBE73D9EAC1E539B126EB563A74B06BF DBBF2EEBF925FAADCF1F3FFFD9866941EA5BBD71 2BA0DB82515BBB9EFFAC71C5C9BE28DEE6DF025C 0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D C9B104B3DD3AA72D7CCB1066FB9921286F5E1540 97FC712E4C024BBEA48A61ED3A5CA953F73C700D 7ED10B6531D7C8E1BC296021FC624643487034E5" KEYRING_PYTHON_DEV="keyring-electrum-build-python-dev.gpg" -KEYSERVER_PYTHON_DEV="hkp://keys.gnupg.net" -gpg --no-default-keyring --keyring $KEYRING_PYTHON_DEV --keyserver $KEYSERVER_PYTHON_DEV --recv-keys $KEYLIST_PYTHON_DEV +KEYSERVER_PYTHON_DEV="hkp://pool.sks-keyservers.net" +retry gpg --no-default-keyring --keyring $KEYRING_PYTHON_DEV --keyserver $KEYSERVER_PYTHON_DEV --recv-keys $KEYLIST_PYTHON_DEV for msifile in core dev exe lib pip tools; do echo "Installing $msifile..." wget -nc "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi" From 71dff2a06e3d462a2833d50002c32b2103dd0a7c Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 15 Mar 2018 14:17:27 +0100 Subject: [PATCH 087/174] fix #4128 --- gui/qt/main_window.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 53dabfb05..4741a1406 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -2308,7 +2308,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.pay_to_URI(data) return # else if the user scanned an offline signed tx - data = bh2u(bitcoin.base_decode(data, length=None, base=43)) + try: + data = bh2u(bitcoin.base_decode(data, length=None, base=43)) + except BaseException as e: + self.show_error((_('Could not decode QR code')+':\n{}').format(e)) + return tx = self.tx_from_text(data) if not tx: return From afa4cbfcbb8e552d1bc59147b5b109a0f6c08599 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 15 Mar 2018 18:34:30 +0100 Subject: [PATCH 088/174] fix #4082 --- lib/plugins.py | 2 +- plugins/trezor/trezor.py | 42 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/plugins.py b/lib/plugins.py index dcac6145b..2406c56ac 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -466,7 +466,7 @@ class DeviceMgr(ThreadJob, PrintError): devices = [dev for dev in devices if not self.xpub_by_id(dev.id_)] infos = [] for device in devices: - if not device.product_key in plugin.DEVICE_IDS: + if device.product_key not in plugin.DEVICE_IDS: continue client = self.create_client(device, handler, plugin) if not client: diff --git a/plugins/trezor/trezor.py b/plugins/trezor/trezor.py index d9da690eb..5f6798243 100644 --- a/plugins/trezor/trezor.py +++ b/plugins/trezor/trezor.py @@ -126,7 +126,11 @@ class TrezorPlugin(HW_PluginBase): self.device_manager().register_enumerate_func(self.enumerate) - def enumerate(self): + @staticmethod + def _all_transports(): + """Reimplemented trezorlib.transport.all_transports for old trezorlib. + Remove this when we start to require trezorlib 0.9.2 + """ try: from trezorlib.transport import all_transports except ImportError: @@ -154,9 +158,14 @@ class TrezorPlugin(HW_PluginBase): except BaseException: pass return transports + return all_transports() + def _enumerate_devices(self): + """Just like trezorlib.transport.enumerate_devices, + but with exception catching, so that transports can fail separately. + """ devices = [] - for transport in all_transports(): + for transport in self._all_transports(): try: new_devices = transport.enumerate() except BaseException as e: @@ -164,14 +173,39 @@ class TrezorPlugin(HW_PluginBase): .format(transport.__name__, str(e))) else: devices.extend(new_devices) + return devices + def enumerate(self): + devices = self._enumerate_devices() return [Device(d.get_path(), -1, d.get_path(), 'TREZOR', 0) for d in devices] + def _get_transport(self, path=None): + """Reimplemented trezorlib.transport.get_transport for old trezorlib. + Remove this when we start to require trezorlib 0.9.2 + """ + try: + from trezorlib.transport import get_transport + except ImportError: + # compat for trezorlib < 0.9.2 + def get_transport(path=None, prefix_search=False): + if path is None: + try: + return self._enumerate_devices()[0] + except IndexError: + raise Exception("No TREZOR device found") from None + + def match_prefix(a, b): + return a.startswith(b) or b.startswith(a) + transports = [t for t in self._all_transports() if match_prefix(path, t.PATH_PREFIX)] + if transports: + return transports[0].find_by_path(path) + raise Exception("Unknown path prefix '%s'" % path) + return get_transport(path) + def create_client(self, device, handler): - from trezorlib.device import TrezorDevice try: self.print_error("connecting to device at", device.path) - transport = TrezorDevice.find_by_path(device.path) + transport = self._get_transport(device.path) except BaseException as e: self.print_error("cannot connect at", device.path, str(e)) return None From 9eb04fac1a4dd9c563f6d8a5eedbf9c510860212 Mon Sep 17 00:00:00 2001 From: Yura Pakhuchiy Date: Fri, 16 Mar 2018 01:40:34 +0700 Subject: [PATCH 089/174] Add testnet mode to .desktop file Easy way to start testnet version from linux DE --- electrum.desktop | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/electrum.desktop b/electrum.desktop index 92540ea41..d7e919f26 100644 --- a/electrum.desktop +++ b/electrum.desktop @@ -14,4 +14,8 @@ StartupNotify=false Terminal=false Type=Application MimeType=x-scheme-handler/bitcoin; +Actions=Testnet; +[Desktop Action Testnet] +Exec=electrum --testnet %u +Name=Testnet mode From aabd9f01ee1b847e761e91a10326f1bd234647ac Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 15 Mar 2018 20:03:12 +0100 Subject: [PATCH 090/174] wizard hw devices: only scan once --- lib/base_wizard.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/base_wizard.py b/lib/base_wizard.py index b9f792e92..a7fc27f65 100644 --- a/lib/base_wizard.py +++ b/lib/base_wizard.py @@ -202,14 +202,19 @@ class BaseWizard(object): # scan devices devices = [] devmgr = self.plugins.device_manager - for name, description, plugin in support: - try: - # FIXME: side-effect: unpaired_device_info sets client.handler - u = devmgr.unpaired_device_infos(None, plugin) - except: - devmgr.print_error("error", name) - continue - devices += list(map(lambda x: (name, x), u)) + try: + scanned_devices = devmgr.scan_devices() + except BaseException as e: + devmgr.print_error('error scanning devices: {}'.format(e)) + else: + for name, description, plugin in support: + try: + # FIXME: side-effect: unpaired_device_info sets client.handler + u = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices) + except BaseException as e: + devmgr.print_error('error getting device infos for {}: {}'.format(name, e)) + continue + devices += list(map(lambda x: (name, x), u)) if not devices: msg = ''.join([ _('No hardware device detected.') + '\n', From c0ae266d39786662fee5ae9d03788e2d4d372b14 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 16 Mar 2018 00:55:45 +0100 Subject: [PATCH 091/174] DeviceMgr: scan_devices can work without hid --- lib/plugins.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/plugins.py b/lib/plugins.py index 2406c56ac..4a8a2b882 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -506,17 +506,15 @@ class DeviceMgr(ThreadJob, PrintError): handler.win.wallet.save_keystore() return info - def scan_devices(self): - # All currently supported hardware libraries use hid, so we - # assume it here. This can be easily abstracted if necessary. - # Note this import must be local so those without hardware - # wallet libraries are not affected. - import hid - self.print_error("scanning devices...") + def _scan_devices_with_hid(self): + try: + import hid + except ImportError: + return [] + with self.hid_lock: hid_list = hid.enumerate(0, 0) - # First see what's connected that we know about devices = [] for d in hid_list: product_key = (d['vendor_id'], d['product_id']) @@ -530,6 +528,13 @@ class DeviceMgr(ThreadJob, PrintError): id_ += str(interface_number) + str(usage_page) devices.append(Device(d['path'], interface_number, id_, product_key, usage_page)) + return devices + + def scan_devices(self): + self.print_error("scanning devices...") + + # First see what's connected that we know about + devices = self._scan_devices_with_hid() # Let plugin handlers enumerate devices we don't know about for f in self.enumerate_func: @@ -541,7 +546,7 @@ class DeviceMgr(ThreadJob, PrintError): else: devices.extend(new_devices) - # Now find out what was disconnected + # find out what was disconnected pairs = [(dev.path, dev.id_) for dev in devices] disconnected_ids = [] with self.lock: From 8a5d27dcf44e31200f2b50fa3aee39180b5dfb47 Mon Sep 17 00:00:00 2001 From: Laser Yuan Date: Fri, 16 Mar 2018 12:06:33 +0800 Subject: [PATCH 092/174] Fix the error: locktime is always reset to zero when serialize the json data The "lockTime" field in the json object was ignored due to the wrong attribute name "locktime" was called. --- lib/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commands.py b/lib/commands.py index 9106b1ca4..8435cf9ec 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -201,7 +201,7 @@ class Commands: keypairs = {} inputs = jsontx.get('inputs') outputs = jsontx.get('outputs') - locktime = jsontx.get('locktime', 0) + locktime = jsontx.get('lockTime', 0) for txin in inputs: if txin.get('output'): prevout_hash, prevout_n = txin['output'].split(':') From c79de3ab3c7531ab6d45c8c92b6ada0c620daf09 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 16 Mar 2018 22:06:38 +0100 Subject: [PATCH 093/174] fix #4139 --- lib/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/network.py b/lib/network.py index 872e96f78..d40951fde 100644 --- a/lib/network.py +++ b/lib/network.py @@ -563,7 +563,7 @@ class Network(util.DaemonThread): self.notify('fee') elif method == 'blockchain.relayfee': if error is None: - self.relay_fee = int(result * COIN) + self.relay_fee = int(result * COIN) if result is not None else None self.print_error("relayfee", self.relay_fee) elif method == 'blockchain.block.get_chunk': self.on_get_chunk(interface, response) From 680df7d6b60ffcf66f6c47eb73697da1a8613405 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 16 Mar 2018 23:19:52 +0100 Subject: [PATCH 094/174] trezor: move the transport-related reimplemented parts into a separate module. disable the bridge transport. The bridge transport uses requests.post, which uses socket.getaddrinfo under the hood, which on some OSes (MacOS, Windows) in CPython takes a lock. The enumerate method for the bridge transport can block for 10-30 seconds while waiting for this lock. --- plugins/trezor/transport.py | 95 +++++++++++++++++++++++++++++++++++++ plugins/trezor/trezor.py | 78 ++---------------------------- 2 files changed, 99 insertions(+), 74 deletions(-) create mode 100644 plugins/trezor/transport.py diff --git a/plugins/trezor/transport.py b/plugins/trezor/transport.py new file mode 100644 index 000000000..5ce686c69 --- /dev/null +++ b/plugins/trezor/transport.py @@ -0,0 +1,95 @@ +from electrum.util import PrintError + + +class TrezorTransport(PrintError): + + @staticmethod + def all_transports(): + """Reimplemented trezorlib.transport.all_transports so that we can + enable/disable specific transports. + """ + try: + # only to detect trezorlib version + from trezorlib.transport import all_transports + except ImportError: + # old trezorlib. compat for trezorlib < 0.9.2 + transports = [] + #try: + # from trezorlib.transport_bridge import BridgeTransport + # transports.append(BridgeTransport) + #except BaseException: + # pass + try: + from trezorlib.transport_hid import HidTransport + transports.append(HidTransport) + except BaseException: + pass + try: + from trezorlib.transport_udp import UdpTransport + transports.append(UdpTransport) + except BaseException: + pass + try: + from trezorlib.transport_webusb import WebUsbTransport + transports.append(WebUsbTransport) + except BaseException: + pass + else: + # new trezorlib. + transports = [] + #try: + # from trezorlib.transport.bridge import BridgeTransport + # transports.append(BridgeTransport) + #except BaseException: + # pass + try: + from trezorlib.transport.hid import HidTransport + transports.append(HidTransport) + except BaseException: + pass + try: + from trezorlib.transport.udp import UdpTransport + transports.append(UdpTransport) + except BaseException: + pass + try: + from trezorlib.transport.webusb import WebUsbTransport + transports.append(WebUsbTransport) + except BaseException: + pass + return transports + return transports + + def enumerate_devices(self): + """Just like trezorlib.transport.enumerate_devices, + but with exception catching, so that transports can fail separately. + """ + devices = [] + for transport in self.all_transports(): + try: + new_devices = transport.enumerate() + except BaseException as e: + self.print_error('enumerate failed for {}. error {}' + .format(transport.__name__, str(e))) + else: + devices.extend(new_devices) + return devices + + def get_transport(self, path=None): + """Reimplemented trezorlib.transport.get_transport, + (1) for old trezorlib + (2) to be able to disable specific transports + (3) to call our own enumerate_devices that catches exceptions + """ + if path is None: + try: + return self.enumerate_devices()[0] + except IndexError: + raise Exception("No TREZOR device found") from None + + def match_prefix(a, b): + return a.startswith(b) or b.startswith(a) + transports = [t for t in self.all_transports() if match_prefix(path, t.PATH_PREFIX)] + if transports: + return transports[0].find_by_path(path) + raise Exception("Unknown path prefix '%s'" % path) diff --git a/plugins/trezor/trezor.py b/plugins/trezor/trezor.py index 5f6798243..082c183c4 100644 --- a/plugins/trezor/trezor.py +++ b/plugins/trezor/trezor.py @@ -117,6 +117,7 @@ class TrezorPlugin(HW_PluginBase): return from . import client + from . import transport import trezorlib.ckd_public import trezorlib.messages self.client_class = client.TrezorClient @@ -124,88 +125,17 @@ class TrezorPlugin(HW_PluginBase): self.types = trezorlib.messages self.DEVICE_IDS = ('TREZOR',) + self.transport_handler = transport.TrezorTransport() self.device_manager().register_enumerate_func(self.enumerate) - @staticmethod - def _all_transports(): - """Reimplemented trezorlib.transport.all_transports for old trezorlib. - Remove this when we start to require trezorlib 0.9.2 - """ - try: - from trezorlib.transport import all_transports - except ImportError: - # compat for trezorlib < 0.9.2 - def all_transports(): - transports = [] - try: - from trezorlib.transport_bridge import BridgeTransport - transports.append(BridgeTransport) - except BaseException: - pass - try: - from trezorlib.transport_hid import HidTransport - transports.append(HidTransport) - except BaseException: - pass - try: - from trezorlib.transport_udp import UdpTransport - transports.append(UdpTransport) - except BaseException: - pass - try: - from trezorlib.transport_webusb import WebUsbTransport - transports.append(WebUsbTransport) - except BaseException: - pass - return transports - return all_transports() - - def _enumerate_devices(self): - """Just like trezorlib.transport.enumerate_devices, - but with exception catching, so that transports can fail separately. - """ - devices = [] - for transport in self._all_transports(): - try: - new_devices = transport.enumerate() - except BaseException as e: - self.print_error('enumerate failed for {}. error {}' - .format(transport.__name__, str(e))) - else: - devices.extend(new_devices) - return devices - def enumerate(self): - devices = self._enumerate_devices() + devices = self.transport_handler.enumerate_devices() return [Device(d.get_path(), -1, d.get_path(), 'TREZOR', 0) for d in devices] - def _get_transport(self, path=None): - """Reimplemented trezorlib.transport.get_transport for old trezorlib. - Remove this when we start to require trezorlib 0.9.2 - """ - try: - from trezorlib.transport import get_transport - except ImportError: - # compat for trezorlib < 0.9.2 - def get_transport(path=None, prefix_search=False): - if path is None: - try: - return self._enumerate_devices()[0] - except IndexError: - raise Exception("No TREZOR device found") from None - - def match_prefix(a, b): - return a.startswith(b) or b.startswith(a) - transports = [t for t in self._all_transports() if match_prefix(path, t.PATH_PREFIX)] - if transports: - return transports[0].find_by_path(path) - raise Exception("Unknown path prefix '%s'" % path) - return get_transport(path) - def create_client(self, device, handler): try: self.print_error("connecting to device at", device.path) - transport = self._get_transport(device.path) + transport = self.transport_handler.get_transport(device.path) except BaseException as e: self.print_error("cannot connect at", device.path, str(e)) return None From 61a45edee0d9d51baa0eefa6cd61de99edeba0b7 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 17 Mar 2018 22:56:20 +0100 Subject: [PATCH 095/174] Catch wallet file related exceptions in Qt wizard. --- gui/qt/__init__.py | 16 ++++++++++++---- lib/bitcoin.py | 13 ++++++++----- lib/keystore.py | 20 ++++++++++++-------- lib/storage.py | 26 ++++++++++++++------------ lib/util.py | 6 ++++++ lib/wallet.py | 18 ++++++++++-------- plugins/trustedcoin/trustedcoin.py | 2 +- 7 files changed, 63 insertions(+), 38 deletions(-) diff --git a/gui/qt/__init__.py b/gui/qt/__init__.py index 221fdf1dc..426dfd2c6 100644 --- a/gui/qt/__init__.py +++ b/gui/qt/__init__.py @@ -44,7 +44,8 @@ from electrum import WalletStorage # from electrum.synchronizer import Synchronizer # from electrum.verifier import SPV # from electrum.util import DebugMem -from electrum.util import UserCancelled, print_error +from electrum.util import (UserCancelled, print_error, + WalletFileException, BitcoinException) # from electrum.wallet import Abstract_Wallet from .installwizard import InstallWizard, GoBack @@ -191,7 +192,7 @@ class ElectrumGui: except BaseException as e: traceback.print_exc(file=sys.stdout) d = QMessageBox(QMessageBox.Warning, _('Error'), - _('Cannot load wallet:') + '\n' + str(e)) + _('Cannot load wallet') + ' (1):\n' + str(e)) d.exec_() return if not wallet: @@ -203,7 +204,14 @@ class ElectrumGui: pass except GoBack as e: print_error('[start_new_window] Exception caught (GoBack)', e) - wizard.terminate() + except (WalletFileException, BitcoinException) as e: + traceback.print_exc(file=sys.stderr) + d = QMessageBox(QMessageBox.Warning, _('Error'), + _('Cannot load wallet') + ' (2):\n' + str(e)) + d.exec_() + return + finally: + wizard.terminate() if not wallet: return @@ -220,7 +228,7 @@ class ElectrumGui: except BaseException as e: traceback.print_exc(file=sys.stdout) d = QMessageBox(QMessageBox.Warning, _('Error'), - _('Cannot create window for wallet:') + '\n' + str(e)) + _('Cannot create window for wallet') + ':\n' + str(e)) d.exec_() return if uri: diff --git a/lib/bitcoin.py b/lib/bitcoin.py index cd277f4d7..6aeac404e 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -32,7 +32,7 @@ import json import ecdsa import pyaes -from .util import bfh, bh2u, to_string +from .util import bfh, bh2u, to_string, BitcoinException from . import version from .util import print_error, InvalidPassword, assert_bytes, to_bytes, inv_dict from . import segwit_addr @@ -349,7 +349,7 @@ def address_to_script(addr): script += push_script(bh2u(hash_160)) script += '87' # op_equal else: - raise BaseException('unknown address type') + raise BitcoinException('unknown address type: {}'.format(addrtype)) return script def address_to_scripthash(addr): @@ -493,7 +493,8 @@ def deserialize_privkey(key): vch = DecodeBase58Check(key) except BaseException: neutered_privkey = str(key)[:3] + '..' + str(key)[-2:] - raise BaseException("cannot deserialize", neutered_privkey) + raise BitcoinException("cannot deserialize privkey {}" + .format(neutered_privkey)) if txin_type is None: # keys exported in version 3.0.x encoded script type in first byte @@ -888,7 +889,8 @@ def deserialize_xkey(xkey, prv, *, net=None): net = constants.net xkey = DecodeBase58Check(xkey) if len(xkey) != 78: - raise BaseException('Invalid length') + raise BitcoinException('Invalid length for extended key: {}' + .format(len(xkey))) depth = xkey[4] fingerprint = xkey[5:9] child_number = xkey[9:13] @@ -896,7 +898,8 @@ def deserialize_xkey(xkey, prv, *, net=None): header = int('0x' + bh2u(xkey[0:4]), 16) headers = net.XPRV_HEADERS if prv else net.XPUB_HEADERS if header not in headers.values(): - raise BaseException('Invalid xpub format', hex(header)) + raise BitcoinException('Invalid extended key format: {}' + .format(hex(header))) xtype = list(headers.keys())[list(headers.values()).index(header)] n = 33 if prv else 32 K_or_k = xkey[13+n:] diff --git a/lib/keystore.py b/lib/keystore.py index 676db247d..e13e3cd67 100644 --- a/lib/keystore.py +++ b/lib/keystore.py @@ -29,7 +29,8 @@ from unicodedata import normalize from . import bitcoin from .bitcoin import * from . import constants -from .util import PrintError, InvalidPassword, hfu +from .util import (PrintError, InvalidPassword, hfu, WalletFileException, + BitcoinException) from .mnemonic import Mnemonic, load_wordlist from .plugins import run_hook @@ -615,7 +616,8 @@ def xpubkey_to_address(x_pubkey): mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey) pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1]) else: - raise BaseException("Cannot parse pubkey") + raise BitcoinException("Cannot parse pubkey. prefix: {}" + .format(x_pubkey[0:2])) if pubkey: address = public_key_to_p2pkh(bfh(pubkey)) return pubkey, address @@ -634,14 +636,15 @@ def hardware_keystore(d): if hw_type in hw_keystores: constructor = hw_keystores[hw_type] return constructor(d) - raise BaseException('unknown hardware type', hw_type) + raise WalletFileException('unknown hardware type: {}'.format(hw_type)) def load_keystore(storage, name): - w = storage.get('wallet_type', 'standard') d = storage.get(name, {}) t = d.get('type') if not t: - raise BaseException('wallet format requires update') + raise WalletFileException( + 'Wallet format requires update.\n' + 'Cannot find keystore for name {}'.format(name)) if t == 'old': k = Old_KeyStore(d) elif t == 'imported': @@ -651,7 +654,8 @@ def load_keystore(storage, name): elif t == 'hardware': k = hardware_keystore(d) else: - raise BaseException('unknown wallet type', t) + raise WalletFileException( + 'Unknown type {} for keystore named {}'.format(t, name)) return k @@ -709,7 +713,7 @@ def from_seed(seed, passphrase, is_p2sh): xtype = 'p2wsh' if is_p2sh else 'p2wpkh' keystore.add_xprv_from_seed(bip32_seed, xtype, der) else: - raise BaseException(t) + raise BitcoinException('Unexpected seed type {}'.format(t)) return keystore def from_private_key_list(text): @@ -743,5 +747,5 @@ def from_master_key(text): elif is_xpub(text): k = from_xpub(text) else: - raise BaseException('Invalid key') + raise BitcoinException('Invalid master key') return k diff --git a/lib/storage.py b/lib/storage.py index 1b1079a32..86979db4b 100644 --- a/lib/storage.py +++ b/lib/storage.py @@ -33,7 +33,7 @@ import pbkdf2, hmac, hashlib import base64 import zlib -from .util import PrintError, profiler, InvalidPassword +from .util import PrintError, profiler, InvalidPassword, WalletFileException from .plugins import run_hook, plugin_loaders from .keystore import bip44_derivation from . import bitcoin @@ -113,7 +113,7 @@ class WalletStorage(PrintError): if not self.manual_upgrades: if self.requires_split(): - raise BaseException("This wallet has multiple accounts and must be split") + raise WalletFileException("This wallet has multiple accounts and must be split") if self.requires_upgrade(): self.upgrade() @@ -174,7 +174,7 @@ class WalletStorage(PrintError): elif v == STO_EV_XPUB_PW: return b'BIE2' else: - raise Exception('no encryption magic for version: %s' % v) + raise WalletFileException('no encryption magic for version: %s' % v) def decrypt(self, password): ec_key = self.get_key(password) @@ -320,7 +320,7 @@ class WalletStorage(PrintError): storage2.write() result.append(new_path) else: - raise BaseException("This wallet has multiple accounts and must be split") + raise WalletFileException("This wallet has multiple accounts and must be split") return result def requires_upgrade(self): @@ -419,7 +419,7 @@ class WalletStorage(PrintError): d['seed'] = seed self.put(key, d) else: - raise Exception('Unable to tell wallet type. Is this even a wallet file?') + raise WalletFileException('Unable to tell wallet type. Is this even a wallet file?') # remove junk self.put('master_public_key', None) self.put('master_public_keys', None) @@ -543,7 +543,7 @@ class WalletStorage(PrintError): else: addresses.append(addr) if addresses and keypairs: - raise BaseException('mixed addresses and privkeys') + raise WalletFileException('mixed addresses and privkeys') elif addresses: self.put('addresses', addresses) self.put('accounts', None) @@ -553,7 +553,7 @@ class WalletStorage(PrintError): self.put('keypairs', keypairs) self.put('accounts', None) else: - raise BaseException('no addresses or privkeys') + raise WalletFileException('no addresses or privkeys') def convert_account(self): if not self._is_upgrade_method_needed(0, 13): @@ -566,9 +566,9 @@ class WalletStorage(PrintError): if cur_version > max_version: return False elif cur_version < min_version: - raise BaseException( - ('storage upgrade: unexpected version %d (should be %d-%d)' - % (cur_version, min_version, max_version))) + raise WalletFileException( + 'storage upgrade: unexpected version {} (should be {}-{})' + .format(cur_version, min_version, max_version)) else: return True @@ -584,7 +584,9 @@ class WalletStorage(PrintError): if not seed_version: seed_version = OLD_SEED_VERSION if len(self.get('master_public_key','')) == 128 else NEW_SEED_VERSION if seed_version > FINAL_SEED_VERSION: - raise BaseException('This version of Electrum is too old to open this wallet') + raise WalletFileException('This version of Electrum is too old to open this wallet.\n' + '(highest supported storage version: {}, version of this file: {})' + .format(FINAL_SEED_VERSION, seed_version)) if seed_version==14 and self.get('seed_type') == 'segwit': self.raise_unsupported_version(seed_version) if seed_version >=12: @@ -607,4 +609,4 @@ class WalletStorage(PrintError): else: # creation was complete if electrum was run from source msg += "\nPlease open this file with Electrum 1.9.8, and move your coins to a new wallet." - raise BaseException(msg) + raise WalletFileException(msg) diff --git a/lib/util.py b/lib/util.py index 1714c78d4..76015a2dc 100644 --- a/lib/util.py +++ b/lib/util.py @@ -84,6 +84,12 @@ class TimeoutException(Exception): return self.message +class WalletFileException(Exception): pass + + +class BitcoinException(Exception): pass + + # Throw this exception to unwind the stack like when an error occurs. # However unlike other exceptions the user won't be informed. class UserCancelled(Exception): diff --git a/lib/wallet.py b/lib/wallet.py index 10bb77b0b..48154bb85 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -44,7 +44,8 @@ import sys from .i18n import _ from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler, - format_satoshis, NoDynamicFeeEstimates, TimeoutException) + format_satoshis, NoDynamicFeeEstimates, TimeoutException, + WalletFileException, BitcoinException) from .bitcoin import * from .version import * @@ -131,6 +132,7 @@ def sweep_preparations(privkeys, network, imax=100): find_utxos_for_privkey('p2pk', privkey, compressed) if not inputs: raise BaseException(_('No inputs found. (Note that inputs need to be confirmed)')) + # FIXME actually inputs need not be confirmed now, see https://github.com/kyuupichan/electrumx/issues/365 return inputs, keypairs @@ -335,7 +337,7 @@ class Abstract_Wallet(PrintError): addrs = self.get_receiving_addresses() if len(addrs) > 0: if not bitcoin.is_address(addrs[0]): - raise Exception('The addresses in this wallet are not bitcoin addresses.') + raise WalletFileException('The addresses in this wallet are not bitcoin addresses.') def synchronize(self): pass @@ -1166,7 +1168,7 @@ class Abstract_Wallet(PrintError): _type, data, value = o if _type == TYPE_ADDRESS: if not is_address(data): - raise BaseException("Invalid bitcoin address:" + data) + raise BaseException("Invalid bitcoin address: {}".format(data)) if value == '!': if i_max is not None: raise BaseException("More than one output set to spend max") @@ -1339,7 +1341,7 @@ class Abstract_Wallet(PrintError): def bump_fee(self, tx, delta): if tx.is_final(): - raise BaseException(_("Cannot bump fee: transaction is final")) + raise BaseException(_('Cannot bump fee') + ': ' + _('transaction is final')) inputs = copy.deepcopy(tx.inputs()) outputs = copy.deepcopy(tx.outputs()) for txin in inputs: @@ -1370,7 +1372,7 @@ class Abstract_Wallet(PrintError): if delta > 0: continue if delta > 0: - raise BaseException(_('Cannot bump fee: could not find suitable outputs')) + raise BaseException(_('Cannot bump fee') + ': ' + _('could not find suitable outputs')) locktime = self.get_local_height() tx_new = Transaction.from_io(inputs, outputs, locktime=locktime) tx_new.BIP_LI01_sort() @@ -1944,14 +1946,14 @@ class Imported_Wallet(Simple_Wallet): txin_type, pubkey = self.keystore.import_privkey(sec, pw) except Exception: neutered_privkey = str(sec)[:3] + '..' + str(sec)[-2:] - raise BaseException('Invalid private key', neutered_privkey) + raise BitcoinException('Invalid private key: {}'.format(neutered_privkey)) if txin_type in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']: if redeem_script is not None: - raise BaseException('Cannot use redeem script with', txin_type) + raise BitcoinException('Cannot use redeem script with script type {}'.format(txin_type)) addr = bitcoin.pubkey_to_address(txin_type, pubkey) elif txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']: if redeem_script is None: - raise BaseException('Redeem script required for', txin_type) + raise BitcoinException('Redeem script required for script type {}'.format(txin_type)) addr = bitcoin.redeem_script_to_address(txin_type, redeem_script) else: raise NotImplementedError(txin_type) diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py index 30e509f5c..f20c1172c 100644 --- a/plugins/trustedcoin/trustedcoin.py +++ b/plugins/trustedcoin/trustedcoin.py @@ -406,7 +406,7 @@ class TrustedCoinPlugin(BasePlugin): xprv1, xpub1 = self.get_xkeys(seed, passphrase, "m/0'/") xprv2, xpub2 = self.get_xkeys(seed, passphrase, "m/1'/") else: - raise BaseException('unrecognized seed length') + raise BaseException('unrecognized seed length: {} words'.format(n)) return xprv1, xpub1, xprv2, xpub2 def create_keystore(self, wizard, seed, passphrase): From 45b03d930df44352558dc408168894967547c07b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 18 Mar 2018 01:13:02 +0100 Subject: [PATCH 096/174] make ExceptionWindow inherit from MessageBoxMixin the stack trace from the report is now user selectable --- gui/qt/exception_window.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gui/qt/exception_window.py b/gui/qt/exception_window.py index fa2912834..15242bf0c 100644 --- a/gui/qt/exception_window.py +++ b/gui/qt/exception_window.py @@ -38,6 +38,8 @@ from PyQt5.QtWidgets import * from electrum.i18n import _ from electrum import ELECTRUM_VERSION, bitcoin, constants +from .util import MessageBoxMixin + issue_template = """

Traceback

 {traceback}
@@ -54,7 +56,7 @@ issue_template = """

Traceback

report_server = "https://crashhub.electrum.org/crash" -class Exception_Window(QWidget): +class Exception_Window(QWidget, MessageBoxMixin): _active_window = None def __init__(self, main_window, exctype, value, tb): @@ -75,7 +77,9 @@ class Exception_Window(QWidget): 'information:'))) collapse_info = QPushButton(_("Show report contents")) - collapse_info.clicked.connect(lambda: QMessageBox.about(self, "Report contents", self.get_report_string())) + collapse_info.clicked.connect( + lambda: self.msg_box(QMessageBox.NoIcon, + self, "Report contents", self.get_report_string())) main_box.addWidget(collapse_info) main_box.addWidget(QLabel(_("Please briefly describe what led to the error (optional):"))) From 3fb75bc6063c9e527d19c20cb75f32e70ef65a15 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 18 Mar 2018 01:46:09 +0100 Subject: [PATCH 097/174] dbb: do show_address in different thread --- plugins/digitalbitbox/digitalbitbox.py | 10 ++++++++++ plugins/digitalbitbox/qt.py | 11 +++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/plugins/digitalbitbox/digitalbitbox.py b/plugins/digitalbitbox/digitalbitbox.py index 599c8f1c0..d81353508 100644 --- a/plugins/digitalbitbox/digitalbitbox.py +++ b/plugins/digitalbitbox/digitalbitbox.py @@ -724,3 +724,13 @@ class DigitalBitboxPlugin(HW_PluginBase): if client is not None: client.check_device_dialog() return client + + def show_address(self, wallet, keystore, address): + change, index = wallet.get_address_index(address) + keypath = '%s/%d/%d' % (keystore.derivation, change, index) + xpub = self.get_client(keystore)._get_xpub(keypath) + verify_request_payload = { + "type": 'p2pkh', + "echo": xpub['echo'], + } + self.comserver_post_notification(verify_request_payload) diff --git a/plugins/digitalbitbox/qt.py b/plugins/digitalbitbox/qt.py index 8930d6244..1978ce8cc 100644 --- a/plugins/digitalbitbox/qt.py +++ b/plugins/digitalbitbox/qt.py @@ -1,3 +1,5 @@ +from functools import partial + from ..hw_wallet.qt import QtHandlerBase, QtPluginBase from .digitalbitbox import DigitalBitboxPlugin @@ -30,14 +32,7 @@ class Plugin(DigitalBitboxPlugin, QtPluginBase): if len(addrs) == 1: def show_address(): - change, index = wallet.get_address_index(addrs[0]) - keypath = '%s/%d/%d' % (keystore.derivation, change, index) - xpub = self.get_client(keystore)._get_xpub(keypath) - verify_request_payload = { - "type": 'p2pkh', - "echo": xpub['echo'], - } - self.comserver_post_notification(verify_request_payload) + keystore.thread.add(partial(self.show_address, wallet, keystore, addrs[0])) menu.addAction(_("Show on {}").format(self.device), show_address) From ee3ab5361efec791d75f29757b8b05d1b7b24862 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 18 Mar 2018 01:55:15 +0100 Subject: [PATCH 098/174] dbb: handle password prompt cancellation better --- plugins/digitalbitbox/digitalbitbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/digitalbitbox/digitalbitbox.py b/plugins/digitalbitbox/digitalbitbox.py index d81353508..e40a53fee 100644 --- a/plugins/digitalbitbox/digitalbitbox.py +++ b/plugins/digitalbitbox/digitalbitbox.py @@ -168,7 +168,7 @@ class DigitalBitbox_Client(): msg = _("Enter your Digital Bitbox password:") while self.password is None: if not self.password_dialog(msg): - return False + raise UserCancelled() reply = self.hid_send_encrypt(b'{"led":"blink"}') if 'error' in reply: self.password = None From 2ce2b368466a848afdf8c85b65fad98a5f338254 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 18 Mar 2018 02:14:47 +0100 Subject: [PATCH 099/174] ledger: newer fw throws different exception when not in bitcoin mode --- plugins/ledger/ledger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py index 9ab52709d..f95d70091 100644 --- a/plugins/ledger/ledger.py +++ b/plugins/ledger/ledger.py @@ -180,8 +180,8 @@ class Ledger_Client(): try: self.perform_hw1_preflight() except BTChipException as e: - if (e.sw == 0x6d00): - raise BaseException("Device not in Bitcoin mode") + if (e.sw == 0x6d00 or e.sw == 0x6f00): + raise BaseException(_("Device not in Bitcoin mode")) from e raise e self.preflightDone = True From 85b36e027f8cada43aa755fdcef9291ab45a5e06 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 18 Mar 2018 03:54:28 +0100 Subject: [PATCH 100/174] fix a bug with hw devices. if a device is unplugged and then replugged before we notice (via scan_devices) then it will get into an unusable state, throwing all kinds of low level exceptions when we don't expect it. affects ledger, keepkey, dbb, but for some reason not trezor. --- lib/plugins.py | 2 +- plugins/digitalbitbox/digitalbitbox.py | 7 +++++++ plugins/keepkey/clientbase.py | 8 ++++++++ plugins/ledger/ledger.py | 14 +++++++------- plugins/trezor/clientbase.py | 8 ++++++++ 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/lib/plugins.py b/lib/plugins.py index 4a8a2b882..4942899e7 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -552,7 +552,7 @@ class DeviceMgr(ThreadJob, PrintError): with self.lock: connected = {} for client, pair in self.clients.items(): - if pair in pairs: + if pair in pairs and client.has_usable_connection_with_device(): connected[client] = pair else: disconnected_ids.append(pair[1]) diff --git a/plugins/digitalbitbox/digitalbitbox.py b/plugins/digitalbitbox/digitalbitbox.py index e40a53fee..334c2f679 100644 --- a/plugins/digitalbitbox/digitalbitbox.py +++ b/plugins/digitalbitbox/digitalbitbox.py @@ -82,6 +82,13 @@ class DigitalBitbox_Client(): def is_paired(self): return self.password is not None + def has_usable_connection_with_device(self): + try: + self.dbb_has_password() + except BaseException: + return False + return True + def _get_xpub(self, bip32_path): if self.check_device_dialog(): return self.hid_send_encrypt(b'{"xpub": "%s"}' % bip32_path.encode('utf8')) diff --git a/plugins/keepkey/clientbase.py b/plugins/keepkey/clientbase.py index b354798a2..acbdb200a 100644 --- a/plugins/keepkey/clientbase.py +++ b/plugins/keepkey/clientbase.py @@ -113,6 +113,14 @@ class KeepKeyClientBase(GuiMixin, PrintError): def is_pairable(self): return not self.features.bootloader_mode + def has_usable_connection_with_device(self): + try: + res = self.ping("electrum pinging device") + assert res == "electrum pinging device" + except BaseException: + return False + return True + def used(self): self.last_operation = time.time() diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py index f95d70091..1fbd4ebb0 100644 --- a/plugins/ledger/ledger.py +++ b/plugins/ledger/ledger.py @@ -57,6 +57,13 @@ class Ledger_Client(): def i4b(self, x): return pack('>I', x) + def has_usable_connection_with_device(self): + try: + self.dongleObject.getFirmwareVersion() + except BaseException: + return False + return True + def test_pin_unlocked(func): """Function decorator to test the Ledger for being unlocked, and if not, raise a human-readable exception. @@ -513,13 +520,6 @@ class LedgerPlugin(HW_PluginBase): if self.libraries_available: self.device_manager().register_devices(self.DEVICE_IDS) - def btchip_is_connected(self, keystore): - try: - self.get_client(keystore).getFirmwareVersion() - except Exception as e: - return False - return True - def get_btchip_device(self, device): ledger = False if (device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c) or (device.product_key[0] == 0x2581 and device.product_key[1] == 0x4b7c) or (device.product_key[0] == 0x2c97): diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py index e7f0d43c8..d1126e071 100644 --- a/plugins/trezor/clientbase.py +++ b/plugins/trezor/clientbase.py @@ -119,6 +119,14 @@ class TrezorClientBase(GuiMixin, PrintError): def is_pairable(self): return not self.features.bootloader_mode + def has_usable_connection_with_device(self): + try: + res = self.ping("electrum pinging device") + assert res == "electrum pinging device" + except BaseException: + return False + return True + def used(self): self.last_operation = time.time() From 22061b4555d4e038a2e783fa5de7efef4b7e06bc Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 18 Mar 2018 06:18:48 +0100 Subject: [PATCH 101/174] transaction.get_address_from_output_script now handles witness version > 0 --- lib/bitcoin.py | 12 ++++++------ lib/tests/test_transaction.py | 22 +++++++++++++++++++++- lib/transaction.py | 8 +++++--- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index cd277f4d7..373da1976 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -44,7 +44,7 @@ from . import constants COINBASE_MATURITY = 100 COIN = 100000000 -# supported types of transction outputs +# supported types of transaction outputs TYPE_ADDRESS = 0 TYPE_PUBKEY = 1 TYPE_SCRIPT = 2 @@ -285,14 +285,14 @@ def hash160_to_p2sh(h160): def public_key_to_p2pkh(public_key): return hash160_to_p2pkh(hash_160(public_key)) -def hash_to_segwit_addr(h): - return segwit_addr.encode(constants.net.SEGWIT_HRP, 0, h) +def hash_to_segwit_addr(h, witver): + return segwit_addr.encode(constants.net.SEGWIT_HRP, witver, h) def public_key_to_p2wpkh(public_key): - return hash_to_segwit_addr(hash_160(public_key)) + return hash_to_segwit_addr(hash_160(public_key), witver=0) def script_to_p2wsh(script): - return hash_to_segwit_addr(sha256(bfh(script))) + return hash_to_segwit_addr(sha256(bfh(script)), witver=0) def p2wpkh_nested_script(pubkey): pkh = bh2u(hash_160(bfh(pubkey))) @@ -306,7 +306,7 @@ def pubkey_to_address(txin_type, pubkey): if txin_type == 'p2pkh': return public_key_to_p2pkh(bfh(pubkey)) elif txin_type == 'p2wpkh': - return hash_to_segwit_addr(hash_160(bfh(pubkey))) + return public_key_to_p2wpkh(bfh(pubkey)) elif txin_type == 'p2wpkh-p2sh': scriptSig = p2wpkh_nested_script(pubkey) return hash160_to_p2sh(hash_160(bfh(scriptSig))) diff --git a/lib/tests/test_transaction.py b/lib/tests/test_transaction.py index 794099f37..0b51561bb 100644 --- a/lib/tests/test_transaction.py +++ b/lib/tests/test_transaction.py @@ -3,7 +3,7 @@ import unittest from lib import transaction from lib.bitcoin import TYPE_ADDRESS from lib.keystore import xpubkey_to_address -from lib.util import bh2u +from lib.util import bh2u, bfh unsigned_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000005701ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000006c493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' @@ -166,6 +166,26 @@ class TestTransaction(unittest.TestCase): tx = transaction.Transaction(v2_blob) self.assertEqual(tx.txid(), "b97f9180173ab141b61b9f944d841e60feec691d6daab4d4d932b24dd36606fe") + def test_get_address_from_output_script(self): + # the inverse of this test is in test_bitcoin: test_address_to_script + addr_from_script = lambda script: transaction.get_address_from_output_script(bfh(script)) + ADDR = transaction.TYPE_ADDRESS + + # bech32 native segwit + # test vectors from BIP-0173 + self.assertEqual((ADDR, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'), addr_from_script('0014751e76e8199196d454941c45d1b3a323f1433bd6')) + self.assertEqual((ADDR, 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'), addr_from_script('5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6')) + self.assertEqual((ADDR, 'bc1sw50qa3jx3s'), addr_from_script('6002751e')) + self.assertEqual((ADDR, 'bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), addr_from_script('5210751e76e8199196d454941c45d1b3a323')) + + # base58 p2pkh + self.assertEqual((ADDR, '14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), addr_from_script('76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac')) + self.assertEqual((ADDR, '1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv'), addr_from_script('76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac')) + + # base58 p2sh + self.assertEqual((ADDR, '35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), addr_from_script('a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487')) + self.assertEqual((ADDR, '3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), addr_from_script('a914f47c8954e421031ad04ecd8e7752c9479206b9d387')) + ##### def _run_naive_tests_on_tx(self, raw_tx, txid): diff --git a/lib/transaction.py b/lib/transaction.py index aac770392..522658730 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -423,9 +423,11 @@ def get_address_from_output_script(_bytes): return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1]) # segwit address - match = [ opcodes.OP_0, opcodes.OP_PUSHDATA4 ] - if match_decoded(decoded, match): - return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1]) + possible_witness_versions = [opcodes.OP_0] + list(range(opcodes.OP_1, opcodes.OP_16 + 1)) + for witver, opcode in enumerate(possible_witness_versions): + match = [ opcode, opcodes.OP_PUSHDATA4 ] + if match_decoded(decoded, match): + return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1], witver=witver) return TYPE_SCRIPT, bh2u(_bytes) From 36d52dfd5a60eba1469d08478d17ea713517ae0d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 19 Mar 2018 01:04:02 +0100 Subject: [PATCH 102/174] fix #4129 --- lib/simple_config.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/simple_config.py b/lib/simple_config.py index 23bad22ad..de8fffae4 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -211,9 +211,14 @@ class SimpleConfig(PrintError): return path = os.path.join(self.path, "config") s = json.dumps(self.user_config, indent=4, sort_keys=True) - with open(path, "w") as f: - f.write(s) - os.chmod(path, stat.S_IREAD | stat.S_IWRITE) + try: + with open(path, "w") as f: + f.write(s) + os.chmod(path, stat.S_IREAD | stat.S_IWRITE) + except FileNotFoundError: + # datadir probably deleted while running... + if os.path.exists(self.path): # or maybe not? + raise def get_wallet_path(self): """Set the path of the wallet.""" From 5fef1e7980e6c9811448ad7d9fb6afa4460ac7fc Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 19 Mar 2018 01:26:57 +0100 Subject: [PATCH 103/174] close #4125 perhaps not proper fix but at least more descriptive exception text --- lib/blockchain.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/blockchain.py b/lib/blockchain.py index f6fe31dcf..cf516069a 100644 --- a/lib/blockchain.py +++ b/lib/blockchain.py @@ -255,6 +255,10 @@ class Blockchain(util.PrintError): with open(name, 'rb') as f: f.seek(delta * 80) h = f.read(80) + elif not os.path.exists(util.get_headers_dir(self.config)): + raise Exception('Electrum datadir does not exist. Was it deleted while running?') + else: + raise Exception('Cannot find headers file but datadir is there. Should be at {}'.format(name)) if h == bytes([0])*80: return None return deserialize_header(h, height) From 800ea1e3005e391ae152e51c0b5d7bc8a78157f9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 20 Mar 2018 00:54:29 +0100 Subject: [PATCH 104/174] typos in gui/qt/main_window.py --- gui/qt/main_window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 4741a1406..024eacefb 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -2629,7 +2629,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): msg = '\n'.join([ _('Time based: fee rate is based on average confirmation time estimates'), - _('Mempool based: fee rate is targetting a depth in the memory pool') + _('Mempool based: fee rate is targeting a depth in the memory pool') ] ) fee_type_label = HelpLabel(_('Fee estimation') + ':', msg) @@ -2656,7 +2656,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): use_rbf_cb.setChecked(self.config.get('use_rbf', True)) use_rbf_cb.setToolTip( _('If you check this box, your transactions will be marked as non-final,') + '\n' + \ - _('and you will have the possiblity, while they are unconfirmed, to replace them with transactions that pay higher fees.') + '\n' + \ + _('and you will have the possibility, while they are unconfirmed, to replace them with transactions that pay higher fees.') + '\n' + \ _('Note that some merchants do not accept non-final transactions until they are confirmed.')) def on_use_rbf(x): self.config.set_key('use_rbf', x == Qt.Checked) From 7d11812f5582d557467981ef8ebc0e17c7905016 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 20 Mar 2018 02:04:41 +0100 Subject: [PATCH 105/174] trezor/keepkey/dbb: provide info for all is_mine txn outputs --- lib/wallet.py | 2 +- plugins/keepkey/plugin.py | 111 +++++++++++++++++++++++-------------- plugins/ledger/ledger.py | 3 +- plugins/trezor/trezor.py | 114 ++++++++++++++++++++++++-------------- 4 files changed, 144 insertions(+), 86 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index 48154bb85..c3a566789 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1444,7 +1444,7 @@ class Abstract_Wallet(PrintError): xpubs = self.get_master_public_keys() for txout in tx.outputs(): _type, addr, amount = txout - if self.is_change(addr): + if self.is_mine(addr): index = self.get_address_index(addr) pubkeys = self.get_public_keys(addr) # sort xpubs using the order of pubkeys diff --git a/plugins/keepkey/plugin.py b/plugins/keepkey/plugin.py index a81a328bd..20c34ca4d 100644 --- a/plugins/keepkey/plugin.py +++ b/plugins/keepkey/plugin.py @@ -303,56 +303,83 @@ class KeepKeyCompatiblePlugin(HW_PluginBase): return inputs def tx_outputs(self, derivation, tx, segwit=False): + + def create_output_by_derivation(info): + index, xpubs, m = info + if len(xpubs) == 1: + script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOADDRESS + address_n = self.client_class.expand_path(derivation + "/%d/%d" % index) + txoutputtype = self.types.TxOutputType( + amount=amount, + script_type=script_type, + address_n=address_n, + ) + else: + script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOMULTISIG + address_n = self.client_class.expand_path("/%d/%d" % index) + nodes = map(self.ckd_public.deserialize, xpubs) + pubkeys = [self.types.HDNodePathType(node=node, address_n=address_n) for node in nodes] + multisig = self.types.MultisigRedeemScriptType( + pubkeys=pubkeys, + signatures=[b''] * len(pubkeys), + m=m) + txoutputtype = self.types.TxOutputType( + multisig=multisig, + amount=amount, + address_n=self.client_class.expand_path(derivation + "/%d/%d" % index), + script_type=script_type) + return txoutputtype + + def create_output_by_address(): + txoutputtype = self.types.TxOutputType() + txoutputtype.amount = amount + if _type == TYPE_SCRIPT: + txoutputtype.script_type = self.types.PAYTOOPRETURN + txoutputtype.op_return_data = address[2:] + elif _type == TYPE_ADDRESS: + if is_segwit_address(address): + txoutputtype.script_type = self.types.PAYTOWITNESS + else: + addrtype, hash_160 = b58_address_to_hash160(address) + if addrtype == constants.net.ADDRTYPE_P2PKH: + txoutputtype.script_type = self.types.PAYTOADDRESS + elif addrtype == constants.net.ADDRTYPE_P2SH: + txoutputtype.script_type = self.types.PAYTOSCRIPTHASH + else: + raise BaseException('addrtype: ' + str(addrtype)) + txoutputtype.address = address + return txoutputtype + + def is_any_output_on_change_branch(): + for _type, address, amount in tx.outputs(): + info = tx.output_info.get(address) + if info is not None: + index, xpubs, m = info + if index[0] == 1: + return True + return False + outputs = [] has_change = False + any_output_on_change_branch = is_any_output_on_change_branch() for _type, address, amount in tx.outputs(): + use_create_by_derivation = False + info = tx.output_info.get(address) if info is not None and not has_change: - has_change = True # no more than one change address - addrtype, hash_160 = b58_address_to_hash160(address) index, xpubs, m = info - if len(xpubs) == 1: - script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOADDRESS - address_n = self.client_class.expand_path(derivation + "/%d/%d"%index) - txoutputtype = self.types.TxOutputType( - amount = amount, - script_type = script_type, - address_n = address_n, - ) - else: - script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOMULTISIG - address_n = self.client_class.expand_path("/%d/%d"%index) - nodes = map(self.ckd_public.deserialize, xpubs) - pubkeys = [ self.types.HDNodePathType(node=node, address_n=address_n) for node in nodes] - multisig = self.types.MultisigRedeemScriptType( - pubkeys = pubkeys, - signatures = [b''] * len(pubkeys), - m = m) - txoutputtype = self.types.TxOutputType( - multisig = multisig, - amount = amount, - address_n = self.client_class.expand_path(derivation + "/%d/%d"%index), - script_type = script_type) + on_change_branch = index[0] == 1 + # prioritise hiding outputs on the 'change' branch from user + # because no more than one change address allowed + if on_change_branch == any_output_on_change_branch: + use_create_by_derivation = True + has_change = True + + if use_create_by_derivation: + txoutputtype = create_output_by_derivation(info) else: - txoutputtype = self.types.TxOutputType() - txoutputtype.amount = amount - if _type == TYPE_SCRIPT: - txoutputtype.script_type = self.types.PAYTOOPRETURN - txoutputtype.op_return_data = address[2:] - elif _type == TYPE_ADDRESS: - if is_segwit_address(address): - txoutputtype.script_type = self.types.PAYTOWITNESS - else: - addrtype, hash_160 = b58_address_to_hash160(address) - if addrtype == constants.net.ADDRTYPE_P2PKH: - txoutputtype.script_type = self.types.PAYTOADDRESS - elif addrtype == constants.net.ADDRTYPE_P2SH: - txoutputtype.script_type = self.types.PAYTOSCRIPTHASH - else: - raise BaseException('addrtype: ' + str(addrtype)) - txoutputtype.address = address - + txoutputtype = create_output_by_address() outputs.append(txoutputtype) return outputs diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py index 1fbd4ebb0..4d7c49fd5 100644 --- a/plugins/ledger/ledger.py +++ b/plugins/ledger/ledger.py @@ -377,7 +377,8 @@ class Ledger_KeyStore(Hardware_KeyStore): for _type, address, amount in tx.outputs(): assert _type == TYPE_ADDRESS info = tx.output_info.get(address) - if (info is not None) and (len(tx.outputs()) != 1): + if (info is not None) and len(tx.outputs()) > 1 \ + and info[0][0] == 1: # "is on 'change' branch" index, xpubs, m = info changePath = self.get_derivation()[2:] + "/%d/%d"%index changeAmount = amount diff --git a/plugins/trezor/trezor.py b/plugins/trezor/trezor.py index 082c183c4..cbd14fd81 100644 --- a/plugins/trezor/trezor.py +++ b/plugins/trezor/trezor.py @@ -380,56 +380,86 @@ class TrezorPlugin(HW_PluginBase): return inputs def tx_outputs(self, derivation, tx, script_gen=SCRIPT_GEN_LEGACY): + + def create_output_by_derivation(info): + index, xpubs, m = info + if len(xpubs) == 1: + if script_gen == SCRIPT_GEN_NATIVE_SEGWIT: + script_type = self.types.OutputScriptType.PAYTOWITNESS + elif script_gen == SCRIPT_GEN_P2SH_SEGWIT: + script_type = self.types.OutputScriptType.PAYTOP2SHWITNESS + else: + script_type = self.types.OutputScriptType.PAYTOADDRESS + address_n = self.client_class.expand_path(derivation + "/%d/%d" % index) + txoutputtype = self.types.TxOutputType( + amount=amount, + script_type=script_type, + address_n=address_n, + ) + else: + if script_gen == SCRIPT_GEN_NATIVE_SEGWIT: + script_type = self.types.OutputScriptType.PAYTOWITNESS + elif script_gen == SCRIPT_GEN_P2SH_SEGWIT: + script_type = self.types.OutputScriptType.PAYTOP2SHWITNESS + else: + script_type = self.types.OutputScriptType.PAYTOMULTISIG + address_n = self.client_class.expand_path("/%d/%d" % index) + nodes = map(self.ckd_public.deserialize, xpubs) + pubkeys = [self.types.HDNodePathType(node=node, address_n=address_n) for node in nodes] + multisig = self.types.MultisigRedeemScriptType( + pubkeys=pubkeys, + signatures=[b''] * len(pubkeys), + m=m) + txoutputtype = self.types.TxOutputType( + multisig=multisig, + amount=amount, + address_n=self.client_class.expand_path(derivation + "/%d/%d" % index), + script_type=script_type) + return txoutputtype + + def create_output_by_address(): + txoutputtype = self.types.TxOutputType() + txoutputtype.amount = amount + if _type == TYPE_SCRIPT: + txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN + txoutputtype.op_return_data = address[2:] + elif _type == TYPE_ADDRESS: + txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS + txoutputtype.address = address + return txoutputtype + + def is_any_output_on_change_branch(): + for _type, address, amount in tx.outputs(): + info = tx.output_info.get(address) + if info is not None: + index, xpubs, m = info + if index[0] == 1: + return True + return False + outputs = [] has_change = False + any_output_on_change_branch = is_any_output_on_change_branch() for _type, address, amount in tx.outputs(): + use_create_by_derivation = False + info = tx.output_info.get(address) if info is not None and not has_change: - has_change = True # no more than one change address index, xpubs, m = info - if len(xpubs) == 1: - if script_gen == SCRIPT_GEN_NATIVE_SEGWIT: - script_type = self.types.OutputScriptType.PAYTOWITNESS - elif script_gen == SCRIPT_GEN_P2SH_SEGWIT: - script_type = self.types.OutputScriptType.PAYTOP2SHWITNESS - else: - script_type = self.types.OutputScriptType.PAYTOADDRESS - address_n = self.client_class.expand_path(derivation + "/%d/%d"%index) - txoutputtype = self.types.TxOutputType( - amount = amount, - script_type = script_type, - address_n = address_n, - ) - else: - if script_gen == SCRIPT_GEN_NATIVE_SEGWIT: - script_type = self.types.OutputScriptType.PAYTOWITNESS - elif script_gen == SCRIPT_GEN_P2SH_SEGWIT: - script_type = self.types.OutputScriptType.PAYTOP2SHWITNESS - else: - script_type = self.types.OutputScriptType.PAYTOMULTISIG - address_n = self.client_class.expand_path("/%d/%d"%index) - nodes = map(self.ckd_public.deserialize, xpubs) - pubkeys = [ self.types.HDNodePathType(node=node, address_n=address_n) for node in nodes] - multisig = self.types.MultisigRedeemScriptType( - pubkeys = pubkeys, - signatures = [b''] * len(pubkeys), - m = m) - txoutputtype = self.types.TxOutputType( - multisig = multisig, - amount = amount, - address_n = self.client_class.expand_path(derivation + "/%d/%d"%index), - script_type = script_type) + on_change_branch = index[0] == 1 + # prioritise hiding outputs on the 'change' branch from user + # because no more than one change address allowed + # note: ^ restriction can be removed once we require fw + # that has https://github.com/trezor/trezor-mcu/pull/306 + if on_change_branch == any_output_on_change_branch: + use_create_by_derivation = True + has_change = True + + if use_create_by_derivation: + txoutputtype = create_output_by_derivation(info) else: - txoutputtype = self.types.TxOutputType() - txoutputtype.amount = amount - if _type == TYPE_SCRIPT: - txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN - txoutputtype.op_return_data = address[2:] - elif _type == TYPE_ADDRESS: - txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS - txoutputtype.address = address - + txoutputtype = create_output_by_address() outputs.append(txoutputtype) return outputs From 17359780885885f016c1039de70e2c5c01372e06 Mon Sep 17 00:00:00 2001 From: Filip Gospodinov Date: Tue, 20 Mar 2018 12:25:04 +0100 Subject: [PATCH 106/174] setup.py: fix icon path for user installation Before, the user installation used the wrong directory to store the application's icon resulting in the desktop environment falling back to an ugly default icon. Now, the correct icon is displayed in the menu. "By default, apps should look in $HOME/.icons (for backwards compatibility), in $XDG_DATA_DIRS/icons and in /usr/share/pixmaps (in that order)." https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3df887b46..7a2c86e02 100755 --- a/setup.py +++ b/setup.py @@ -27,15 +27,17 @@ if platform.system() in ['Linux', 'FreeBSD', 'DragonFly']: parser.add_argument('--root=', dest='root_path', metavar='dir', default='/') opts, _ = parser.parse_known_args(sys.argv[1:]) usr_share = os.path.join(sys.prefix, "share") + icons_dirname = 'pixmaps' if not os.access(opts.root_path + usr_share, os.W_OK) and \ not os.access(opts.root_path, os.W_OK): + icons_dirname = 'icons' if 'XDG_DATA_HOME' in os.environ.keys(): usr_share = os.environ['XDG_DATA_HOME'] else: usr_share = os.path.expanduser('~/.local/share') data_files += [ (os.path.join(usr_share, 'applications/'), ['electrum.desktop']), - (os.path.join(usr_share, 'pixmaps/'), ['icons/electrum.png']) + (os.path.join(usr_share, icons_dirname), ['icons/electrum.png']) ] setup( From 2d944919742c99647370812ced13a2230d9ade7d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 20 Mar 2018 14:15:54 +0100 Subject: [PATCH 107/174] fix #4152. and clean up imports --- plugins/ledger/qt.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/plugins/ledger/qt.py b/plugins/ledger/qt.py index cfdd7c383..9bffe5738 100644 --- a/plugins/ledger/qt.py +++ b/plugins/ledger/qt.py @@ -1,15 +1,13 @@ -import threading - -from PyQt5.Qt import QInputDialog, QLineEdit, QVBoxLayout, QLabel +#from btchip.btchipPersoWizard import StartBTChipPersoDialog from electrum.i18n import _ from electrum.plugins import hook from electrum.wallet import Standard_Wallet +from electrum_gui.qt.util import * + from .ledger import LedgerPlugin from ..hw_wallet.qt import QtHandlerBase, QtPluginBase -from electrum_gui.qt.util import * -#from btchip.btchipPersoWizard import StartBTChipPersoDialog class Plugin(LedgerPlugin, QtPluginBase): icon_unpaired = ":icons/ledger_unpaired.png" @@ -77,11 +75,7 @@ class Ledger_Handler(QtHandlerBase): return def setup_dialog(self): + self.show_error(_('Initialization of Ledger HW devices is currently disabled.')) + return dialog = StartBTChipPersoDialog() dialog.exec_() - - - - - - From 77c8010517ffc1f1bb3bda3e37db77f6c4c524d8 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 20 Mar 2018 17:02:49 +0100 Subject: [PATCH 108/174] bitcoin.py: parameterise a few address-related methods with network --- lib/bitcoin.py | 34 +++++++++++++++++++++------------- lib/transaction.py | 9 +++++---- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 02c630bb4..0c04355d1 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -264,7 +264,7 @@ def hash_160(public_key): return md.digest() -def hash160_to_b58_address(h160, addrtype, witness_program_version=1): +def hash160_to_b58_address(h160, addrtype): s = bytes([addrtype]) s += h160 return base_encode(s+Hash(s)[0:4], base=58) @@ -276,17 +276,23 @@ def b58_address_to_hash160(addr): return _bytes[0], _bytes[1:21] -def hash160_to_p2pkh(h160): - return hash160_to_b58_address(h160, constants.net.ADDRTYPE_P2PKH) +def hash160_to_p2pkh(h160, *, net=None): + if net is None: + net = constants.net + return hash160_to_b58_address(h160, net.ADDRTYPE_P2PKH) -def hash160_to_p2sh(h160): - return hash160_to_b58_address(h160, constants.net.ADDRTYPE_P2SH) +def hash160_to_p2sh(h160, *, net=None): + if net is None: + net = constants.net + return hash160_to_b58_address(h160, net.ADDRTYPE_P2SH) def public_key_to_p2pkh(public_key): return hash160_to_p2pkh(hash_160(public_key)) -def hash_to_segwit_addr(h, witver): - return segwit_addr.encode(constants.net.SEGWIT_HRP, witver, h) +def hash_to_segwit_addr(h, witver, *, net=None): + if net is None: + net = constants.net + return segwit_addr.encode(net.SEGWIT_HRP, witver, h) def public_key_to_p2wpkh(public_key): return hash_to_segwit_addr(hash_160(public_key), witver=0) @@ -325,14 +331,16 @@ def redeem_script_to_address(txin_type, redeem_script): raise NotImplementedError(txin_type) -def script_to_address(script): +def script_to_address(script, *, net=None): from .transaction import get_address_from_output_script - t, addr = get_address_from_output_script(bfh(script)) + t, addr = get_address_from_output_script(bfh(script), net=net) assert t == TYPE_ADDRESS return addr -def address_to_script(addr): - witver, witprog = segwit_addr.decode(constants.net.SEGWIT_HRP, addr) +def address_to_script(addr, *, net=None): + if net is None: + net = constants.net + witver, witprog = segwit_addr.decode(net.SEGWIT_HRP, addr) if witprog is not None: assert (0 <= witver <= 16) OP_n = witver + 0x50 if witver > 0 else 0 @@ -340,11 +348,11 @@ def address_to_script(addr): script += push_script(bh2u(bytes(witprog))) return script addrtype, hash_160 = b58_address_to_hash160(addr) - if addrtype == constants.net.ADDRTYPE_P2PKH: + if addrtype == net.ADDRTYPE_P2PKH: script = '76a9' # op_dup, op_hash_160 script += push_script(bh2u(hash_160)) script += '88ac' # op_equalverify, op_checksig - elif addrtype == constants.net.ADDRTYPE_P2SH: + elif addrtype == net.ADDRTYPE_P2SH: script = 'a9' # op_hash_160 script += push_script(bh2u(hash_160)) script += '87' # op_equal diff --git a/lib/transaction.py b/lib/transaction.py index 522658730..68cd99255 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -402,7 +402,8 @@ def parse_redeemScript(s): redeemScript = multisig_script(pubkeys, m) return m, n, x_pubkeys, pubkeys, redeemScript -def get_address_from_output_script(_bytes): + +def get_address_from_output_script(_bytes, *, net=None): decoded = [x for x in script_GetOp(_bytes)] # The Genesis Block, self-payments, and pay-by-IP-address payments look like: @@ -415,19 +416,19 @@ def get_address_from_output_script(_bytes): # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ] if match_decoded(decoded, match): - return TYPE_ADDRESS, hash160_to_p2pkh(decoded[2][1]) + return TYPE_ADDRESS, hash160_to_p2pkh(decoded[2][1], net=net) # p2sh match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ] if match_decoded(decoded, match): - return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1]) + return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1], net=net) # segwit address possible_witness_versions = [opcodes.OP_0] + list(range(opcodes.OP_1, opcodes.OP_16 + 1)) for witver, opcode in enumerate(possible_witness_versions): match = [ opcode, opcodes.OP_PUSHDATA4 ] if match_decoded(decoded, match): - return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1], witver=witver) + return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1], witver=witver, net=net) return TYPE_SCRIPT, bh2u(_bytes) From 6ef921cbbee61e819baf0e21ab95508c41c973ac Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 20 Mar 2018 17:07:55 +0100 Subject: [PATCH 109/174] ledger hw1: testnet 'support' for security card 2fa; and minor clean-up --- plugins/ledger/auth2fa.py | 40 ++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/plugins/ledger/auth2fa.py b/plugins/ledger/auth2fa.py index add619a82..bdb87f1ea 100644 --- a/plugins/ledger/auth2fa.py +++ b/plugins/ledger/auth2fa.py @@ -1,16 +1,24 @@ +import os +import hashlib +import logging +import json +import copy from binascii import hexlify, unhexlify +import websocket + from PyQt5.Qt import QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel import PyQt5.QtCore as QtCore from PyQt5.QtWidgets import * +from btchip.btchip import * + from electrum.i18n import _ from electrum_gui.qt.util import * from electrum.util import print_msg - -import os, hashlib, websocket, logging, json, copy +from electrum import constants, bitcoin from electrum_gui.qt.qrcodewidget import QRCodeWidget -from btchip.btchip import * + DEBUG = False @@ -37,7 +45,7 @@ class LedgerAuthDialog(QDialog): self.handler = handler self.txdata = data self.idxs = self.txdata['keycardData'] if self.txdata['confirmationType'] > 1 else '' - self.setMinimumWidth(600) + self.setMinimumWidth(650) self.setWindowTitle(_("Ledger Wallet Authentication")) self.cfg = copy.deepcopy(self.handler.win.wallet.get_keystore().cfg) self.dongle = self.handler.win.wallet.get_keystore().get_client().dongle @@ -110,17 +118,23 @@ class LedgerAuthDialog(QDialog): card = QVBoxLayout() self.cardbox.setLayout(card) self.addrtext = QTextEdit() - self.addrtext.setStyleSheet("QTextEdit { color:blue; background-color:lightgray; padding:15px 10px; border:none; font-size:20pt; }") + self.addrtext.setStyleSheet("QTextEdit { color:blue; background-color:lightgray; padding:15px 10px; border:none; font-size:20pt; font-family:monospace; }") self.addrtext.setReadOnly(True) - self.addrtext.setMaximumHeight(120) + self.addrtext.setMaximumHeight(130) card.addWidget(self.addrtext) def pin_changed(s): if len(s) < len(self.idxs): i = self.idxs[len(s)] addr = self.txdata['address'] - addr = addr[:i] + '' + addr[i:i+1] + '' + addr[i+1:] - self.addrtext.setHtml(str(addr)) + if not constants.net.TESTNET: + text = addr[:i] + '' + addr[i:i+1] + '' + addr[i+1:] + else: + # pin needs to be created from mainnet address + addr_mainnet = bitcoin.script_to_address(bitcoin.address_to_script(addr), net=constants.BitcoinMainnet) + addr_mainnet = addr_mainnet[:i] + '' + addr_mainnet[i:i+1] + '' + addr_mainnet[i+1:] + text = str(addr) + '\n' + str(addr_mainnet) + self.addrtext.setHtml(str(text)) else: self.addrtext.setHtml(_("Press Enter")) @@ -179,8 +193,8 @@ class LedgerAuthDialog(QDialog): self.pinbox.setVisible(self.cfg['mode'] == 0) self.cardbox.setVisible(self.cfg['mode'] == 1) self.pintxt.setFocus(True) if self.cfg['mode'] == 0 else self.cardtxt.setFocus(True) - self.setMaximumHeight(200) - + self.setMaximumHeight(400) + def do_pairing(self): rng = os.urandom(16) pairID = (hexlify(rng) + hexlify(hashlib.sha256(rng).digest()[0:1])).decode('utf-8') @@ -338,11 +352,7 @@ class LedgerWebSocket(QThread): ws.send( self.txreq ) debug_msg("Req Sent", self.txreq) + def debug_msg(*args): if DEBUG: print_msg(*args) - - - - - From a00439b6f8a6507a142d7b5b29a793d340a183a9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 22 Mar 2018 07:27:18 +0100 Subject: [PATCH 110/174] fix #4158 --- gui/qt/main_window.py | 3 +-- lib/bitcoin.py | 1 + lib/keystore.py | 4 ++++ lib/wallet.py | 14 ++++++++++++-- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 024eacefb..90c21e636 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -1771,8 +1771,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): def remove_address(self, addr): if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")): self.wallet.delete_address(addr) - self.address_list.update() - self.history_list.update() + self.need_update.set() # history, addresses, coins self.clear_receive_tab() def get_coins(self): diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 0c04355d1..f43d1047b 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -465,6 +465,7 @@ def DecodeBase58Check(psz): # backwards compat # extended WIF for segwit (used in 3.0.x; but still used internally) +# the keys in this dict should be a superset of what Imported Wallets can import SCRIPT_TYPES = { 'p2pkh':0, 'p2wpkh':1, diff --git a/lib/keystore.py b/lib/keystore.py index e13e3cd67..f4b3d0166 100644 --- a/lib/keystore.py +++ b/lib/keystore.py @@ -143,6 +143,10 @@ class Imported_KeyStore(Software_KeyStore): # re-serialize the key so the internal storage format is consistent serialized_privkey = serialize_privkey( privkey, compressed, txin_type, internal_use=True) + # NOTE: if the same pubkey is reused for multiple addresses (script types), + # there will only be one pubkey-privkey pair for it in self.keypairs, + # and the privkey will encode a txin_type but that txin_type can not be trusted. + # Removing keys complicates this further. self.keypairs[pubkey] = pw_encode(serialized_privkey, password) return txin_type, pubkey diff --git a/lib/wallet.py b/lib/wallet.py index 48154bb85..ca9dc391a 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1929,8 +1929,18 @@ class Imported_Wallet(Simple_Wallet): pubkey = self.get_public_key(address) self.addresses.pop(address) if pubkey: - self.keystore.delete_imported_key(pubkey) - self.save_keystore() + # delete key iff no other address uses it (e.g. p2pkh and p2wpkh for same key) + for txin_type in bitcoin.SCRIPT_TYPES.keys(): + try: + addr2 = bitcoin.pubkey_to_address(txin_type, pubkey) + except NotImplementedError: + pass + else: + if addr2 in self.addresses: + break + else: + self.keystore.delete_imported_key(pubkey) + self.save_keystore() self.storage.put('addresses', self.addresses) self.storage.write() From c0a42b756be0494e3b45d539f68c0f5269c858de Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 22 Mar 2018 08:18:27 +0100 Subject: [PATCH 111/174] fix #4159 --- gui/qt/main_window.py | 12 ++++++++++-- lib/simple_config.py | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 90c21e636..f16ad1d2d 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -396,7 +396,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.show_warning(msg, title=_('Information')) def open_wallet(self): - wallet_folder = self.get_wallet_folder() + try: + wallet_folder = self.get_wallet_folder() + except FileNotFoundError as e: + self.show_error(str(e)) + return filename, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) if not filename: return @@ -440,7 +444,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): return os.path.dirname(os.path.abspath(self.config.get_wallet_path())) def new_wallet(self): - wallet_folder = self.get_wallet_folder() + try: + wallet_folder = self.get_wallet_folder() + except FileNotFoundError as e: + self.show_error(str(e)) + return i = 1 while True: filename = "wallet_%d" % i diff --git a/lib/simple_config.py b/lib/simple_config.py index de8fffae4..d78ffb67e 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -233,6 +233,10 @@ class SimpleConfig(PrintError): return path # default path + if not os.path.exists(self.path): + raise FileNotFoundError( + _('Electrum datadir does not exist. Was it deleted while running?') + '\n' + + _('Should be at {}').format(self.path)) dirpath = os.path.join(self.path, "wallets") if not os.path.exists(dirpath): if os.path.islink(dirpath): From db9cb63e79ec199db1983ba20137d692bc6807b7 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 22 Mar 2018 15:46:23 +0100 Subject: [PATCH 112/174] ledger: mixed up error code.. follow-up 2ce2b368466a848afdf8c85b65fad98a5f338254 --- plugins/ledger/ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py index 4d7c49fd5..2cb0c223a 100644 --- a/plugins/ledger/ledger.py +++ b/plugins/ledger/ledger.py @@ -187,7 +187,7 @@ class Ledger_Client(): try: self.perform_hw1_preflight() except BTChipException as e: - if (e.sw == 0x6d00 or e.sw == 0x6f00): + if (e.sw == 0x6d00 or e.sw == 0x6700): raise BaseException(_("Device not in Bitcoin mode")) from e raise e self.preflightDone = True From cd19f788ccedd7ac050141cdd15c9905011c951e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 22 Mar 2018 15:48:48 +0100 Subject: [PATCH 113/174] fix ledger: nano s did not work reliably since last fw update follow LedgerHQ/btchip-python@6e985b558f22d1fc0367f5c1730822750337cf94 --- plugins/ledger/ledger.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py index 2cb0c223a..e6c42d155 100644 --- a/plugins/ledger/ledger.py +++ b/plugins/ledger/ledger.py @@ -523,8 +523,15 @@ class LedgerPlugin(HW_PluginBase): def get_btchip_device(self, device): ledger = False - if (device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c) or (device.product_key[0] == 0x2581 and device.product_key[1] == 0x4b7c) or (device.product_key[0] == 0x2c97): - ledger = True + if device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c: + ledger = True + if device.product_key[0] == 0x2581 and device.product_key[1] == 0x4b7c: + ledger = True + if device.product_key[0] == 0x2c97: + if device.interface_number == 0 or device.usage_page == 0xffa0: + ledger = True + else: + return None # non-compatible interface of a nano s or blue dev = hid.device() dev.open_path(device.path) dev.set_nonblocking(True) From d146c369bff58a9b0f37dd46224b771a5f96cbce Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 22 Mar 2018 21:48:51 +0100 Subject: [PATCH 114/174] fix #4164 --- lib/plugins.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/plugins.py b/lib/plugins.py index 4942899e7..17f68dac2 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -482,9 +482,14 @@ class DeviceMgr(ThreadJob, PrintError): infos = self.unpaired_device_infos(handler, plugin, devices) if infos: break - msg = _('Please insert your {}. Verify the cable is ' - 'connected and that no other application is using it.\n\n' - 'Try to connect again?').format(plugin.device) + msg = _('Please insert your {}').format(plugin.device) + if keystore.label: + msg += ' ({})'.format(keystore.label) + msg += '. {}\n\n{}'.format( + _('Verify the cable is connected and that ' + 'no other application is using it.'), + _('Try to connect again?') + ) if not handler.yes_no_question(msg): raise UserCancelled() devices = None From 24d0f3783b64121c74f8306ff4a3b89ab9e7abae Mon Sep 17 00:00:00 2001 From: Filip Gospodinov Date: Fri, 23 Mar 2018 13:05:06 +0100 Subject: [PATCH 115/174] electrum.desktop: improve user installation Before, the desktop entry file would only work correctly if the user has added `$HOME/.local/bin` to his or her PATH. The PATH variable is an implementation detail and the user shouldn't be exposed to it. Now, the shell is used to look for executables in the aforementioned folder. System-wide installations remain intact. --- electrum.desktop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electrum.desktop b/electrum.desktop index d7e919f26..086ed881f 100644 --- a/electrum.desktop +++ b/electrum.desktop @@ -3,7 +3,7 @@ [Desktop Entry] Comment=Lightweight Bitcoin Client -Exec=electrum %u +Exec=sh -c 'PATH="$HOME/.local/bin:$PATH" electrum %u' GenericName[en_US]=Bitcoin Wallet GenericName=Bitcoin Wallet Icon=electrum @@ -17,5 +17,5 @@ MimeType=x-scheme-handler/bitcoin; Actions=Testnet; [Desktop Action Testnet] -Exec=electrum --testnet %u +Exec=sh -c 'PATH="$HOME/.local/bin:$PATH" electrum --testnet %u' Name=Testnet mode From 14c45f09159644616167b9912b496fc97fc00f77 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Fri, 23 Mar 2018 14:04:11 +0100 Subject: [PATCH 116/174] email_requests: Test connection and catch exceptions --- plugins/email_requests/qt.py | 59 ++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/plugins/email_requests/qt.py b/plugins/email_requests/qt.py index a4facccf3..e68910f9e 100644 --- a/plugins/email_requests/qt.py +++ b/plugins/email_requests/qt.py @@ -22,7 +22,7 @@ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. - +import random import time import threading import base64 @@ -45,11 +45,12 @@ from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit, from electrum.plugins import BasePlugin, hook from electrum.paymentrequest import PaymentRequest from electrum.i18n import _ +from electrum.util import PrintError from electrum_gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton, WindowModalDialog, get_parent_main_window) -class Processor(threading.Thread): +class Processor(threading.Thread, PrintError): polling_interval = 5*60 def __init__(self, imap_server, username, password, callback): @@ -59,6 +60,8 @@ class Processor(threading.Thread): self.password = password self.imap_server = imap_server self.on_receive = callback + self.M = None + self.connect_wait = 100 # ms, between failed connection attempts def poll(self): try: @@ -80,13 +83,18 @@ class Processor(threading.Thread): self.on_receive(pr_str) def run(self): - self.M = imaplib.IMAP4_SSL(self.imap_server) - self.M.login(self.username, self.password) while True: - self.poll() - time.sleep(self.polling_interval) - self.M.close() - self.M.logout() + try: + self.M = imaplib.IMAP4_SSL(self.imap_server) + self.M.login(self.username, self.password) + except imaplib.IMAP4.error as e: + self.print_error(e) + self.connect_wait *= 2 + # Reconnect when host changes + while self.M and self.M.host == self.imap_server: + self.poll() + time.sleep(self.polling_interval) + time.sleep(random.randint(0, self.connect_wait)) def send(self, recipient, message, payment_request): msg = MIMEMultipart() @@ -98,10 +106,13 @@ class Processor(threading.Thread): encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="payreq.btc"') msg.attach(part) - s = smtplib.SMTP_SSL(self.imap_server, timeout=2) - s.login(self.username, self.password) - s.sendmail(self.username, [recipient], msg.as_string()) - s.quit() + try: + s = smtplib.SMTP_SSL(self.imap_server, timeout=2) + s.login(self.username, self.password) + s.sendmail(self.username, [recipient], msg.as_string()) + s.quit() + except smtplib.SMTPException as e: + self.print_error(e) class QEmailSignalObject(QObject): @@ -225,3 +236,27 @@ class Plugin(BasePlugin): password = str(password_e.text()) self.config.set_key('email_password', password) self.password = password + + check_connection = CheckConnectionThread(server, username, password) + check_connection.connection_error_signal.connect(lambda e: window.show_message( + _("Unable to connect to mail server:\n {}").format(e) + "\n" + + _("Please check your connection and credentials.") + )) + check_connection.start() + + +class CheckConnectionThread(QThread): + connection_error_signal = pyqtSignal(str) + + def __init__(self, server, username, password): + super().__init__() + self.server = server + self.username = username + self.password = password + + def run(self): + try: + conn = imaplib.IMAP4_SSL(self.server) + conn.login(self.username, self.password) + except BaseException as e: + self.connection_error_signal.emit(str(e)) From 8f31d224aa30bf9fc1a080e5e52e08b9c984caab Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Fri, 23 Mar 2018 15:17:31 +0100 Subject: [PATCH 117/174] email_requests: Catch all connection related exceptions Closes: #4160 Closes: #4105 --- plugins/email_requests/qt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/email_requests/qt.py b/plugins/email_requests/qt.py index e68910f9e..ed9293c2b 100644 --- a/plugins/email_requests/qt.py +++ b/plugins/email_requests/qt.py @@ -87,7 +87,7 @@ class Processor(threading.Thread, PrintError): try: self.M = imaplib.IMAP4_SSL(self.imap_server) self.M.login(self.username, self.password) - except imaplib.IMAP4.error as e: + except BaseException as e: self.print_error(e) self.connect_wait *= 2 # Reconnect when host changes @@ -111,7 +111,7 @@ class Processor(threading.Thread, PrintError): s.login(self.username, self.password) s.sendmail(self.username, [recipient], msg.as_string()) s.quit() - except smtplib.SMTPException as e: + except BaseException as e: self.print_error(e) From 9b7536e75c7fc12cbca0aa3e2ebf0e98117d3f08 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 23 Mar 2018 21:47:51 +0100 Subject: [PATCH 118/174] use explicit utf-8 encoding when opening files in text mode --- lib/exchange_rate.py | 8 ++++---- lib/interface.py | 6 +++--- lib/mnemonic.py | 2 +- lib/network.py | 6 +++--- lib/paymentrequest.py | 10 +++++----- lib/simple_config.py | 4 ++-- lib/storage.py | 4 ++-- lib/util.py | 4 ++-- lib/wallet.py | 2 +- lib/websockets.py | 2 +- lib/x509.py | 2 +- 11 files changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/exchange_rate.py b/lib/exchange_rate.py index b6a965fe7..b810c2022 100644 --- a/lib/exchange_rate.py +++ b/lib/exchange_rate.py @@ -66,7 +66,7 @@ class ExchangeBase(PrintError): if os.path.exists(filename): timestamp = os.stat(filename).st_mtime try: - with open(filename, 'r') as f: + with open(filename, 'r', encoding='utf-8') as f: h = json.loads(f.read()) h['timestamp'] = timestamp except: @@ -87,7 +87,7 @@ class ExchangeBase(PrintError): self.print_error("failed fx history:", e) return filename = os.path.join(cache_dir, self.name() + '_' + ccy) - with open(filename, 'w') as f: + with open(filename, 'w', encoding='utf-8') as f: f.write(json.dumps(h)) h['timestamp'] = time.time() self.history[ccy] = h @@ -382,7 +382,7 @@ def get_exchanges_and_currencies(): import os, json path = os.path.join(os.path.dirname(__file__), 'currencies.json') try: - with open(path, 'r') as f: + with open(path, 'r', encoding='utf-8') as f: return json.loads(f.read()) except: pass @@ -399,7 +399,7 @@ def get_exchanges_and_currencies(): except: print(name, "error") continue - with open(path, 'w') as f: + with open(path, 'w', encoding='utf-8') as f: f.write(json.dumps(d, indent=4, sort_keys=True)) return d diff --git a/lib/interface.py b/lib/interface.py index 2e76d9870..9155640b3 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -172,7 +172,7 @@ class TcpConnection(threading.Thread, util.PrintError): # workaround android bug cert = re.sub("([^\n])-----END CERTIFICATE-----","\\1\n-----END CERTIFICATE-----",cert) temporary_path = cert_path + '.temp' - with open(temporary_path,"w") as f: + with open(temporary_path, "w", encoding='utf-8') as f: f.write(cert) f.flush() os.fsync(f.fileno()) @@ -201,7 +201,7 @@ class TcpConnection(threading.Thread, util.PrintError): os.unlink(rej) os.rename(temporary_path, rej) else: - with open(cert_path) as f: + with open(cert_path, encoding='utf-8') as f: cert = f.read() try: b = pem.dePem(cert, 'CERTIFICATE') @@ -398,7 +398,7 @@ def test_certificates(): certs = os.listdir(mydir) for c in certs: p = os.path.join(mydir,c) - with open(p) as f: + with open(p, encoding='utf-8') as f: cert = f.read() check_cert(c, cert) diff --git a/lib/mnemonic.py b/lib/mnemonic.py index 846dcc74f..02038df05 100644 --- a/lib/mnemonic.py +++ b/lib/mnemonic.py @@ -91,7 +91,7 @@ def normalize_text(seed): def load_wordlist(filename): path = os.path.join(os.path.dirname(__file__), 'wordlist', filename) - with open(path, 'r') as f: + with open(path, 'r', encoding='utf-8') as f: s = f.read().strip() s = unicodedata.normalize('NFKD', s) lines = s.split('\n') diff --git a/lib/network.py b/lib/network.py index d40951fde..12ddcc231 100644 --- a/lib/network.py +++ b/lib/network.py @@ -246,7 +246,7 @@ class Network(util.DaemonThread): return [] path = os.path.join(self.config.path, "recent_servers") try: - with open(path, "r") as f: + with open(path, "r", encoding='utf-8') as f: data = f.read() return json.loads(data) except: @@ -258,7 +258,7 @@ class Network(util.DaemonThread): path = os.path.join(self.config.path, "recent_servers") s = json.dumps(self.recent_servers, indent=4, sort_keys=True) try: - with open(path, "w") as f: + with open(path, "w", encoding='utf-8') as f: f.write(s) except: pass @@ -1089,7 +1089,7 @@ class Network(util.DaemonThread): def export_checkpoints(self, path): # run manually from the console to generate checkpoints cp = self.blockchain().get_checkpoints() - with open(path, 'w') as f: + with open(path, 'w', encoding='utf-8') as f: f.write(json.dumps(cp, indent=4)) def max_checkpoint(self): diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py index 471186705..d58a41d94 100644 --- a/lib/paymentrequest.py +++ b/lib/paymentrequest.py @@ -89,7 +89,7 @@ def get_payment_request(url): error = "payment URL not pointing to a valid server" elif u.scheme == 'file': try: - with open(u.path, 'r') as f: + with open(u.path, 'r', encoding='utf-8') as f: data = f.read() except IOError: data = None @@ -385,9 +385,9 @@ def check_ssl_config(config): from . import pem key_path = config.get('ssl_privkey') cert_path = config.get('ssl_chain') - with open(key_path, 'r') as f: + with open(key_path, 'r', encoding='utf-8') as f: params = pem.parse_private_key(f.read()) - with open(cert_path, 'r') as f: + with open(cert_path, 'r', encoding='utf-8') as f: s = f.read() bList = pem.dePemList(s, "CERTIFICATE") # verify chain @@ -405,10 +405,10 @@ def check_ssl_config(config): def sign_request_with_x509(pr, key_path, cert_path): from . import pem - with open(key_path, 'r') as f: + with open(key_path, 'r', encoding='utf-8') as f: params = pem.parse_private_key(f.read()) privkey = rsakey.RSAKey(*params) - with open(cert_path, 'r') as f: + with open(cert_path, 'r', encoding='utf-8') as f: s = f.read() bList = pem.dePemList(s, "CERTIFICATE") certificates = pb2.X509Certificates() diff --git a/lib/simple_config.py b/lib/simple_config.py index d78ffb67e..34c62d3f3 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -212,7 +212,7 @@ class SimpleConfig(PrintError): path = os.path.join(self.path, "config") s = json.dumps(self.user_config, indent=4, sort_keys=True) try: - with open(path, "w") as f: + with open(path, "w", encoding='utf-8') as f: f.write(s) os.chmod(path, stat.S_IREAD | stat.S_IWRITE) except FileNotFoundError: @@ -498,7 +498,7 @@ def read_user_config(path): if not os.path.exists(config_path): return {} try: - with open(config_path, "r") as f: + with open(config_path, "r", encoding='utf-8') as f: data = f.read() result = json.loads(data) except: diff --git a/lib/storage.py b/lib/storage.py index 86979db4b..59673d423 100644 --- a/lib/storage.py +++ b/lib/storage.py @@ -77,7 +77,7 @@ class WalletStorage(PrintError): self.modified = False self.pubkey = None if self.file_exists(): - with open(self.path, "r") as f: + with open(self.path, "r", encoding='utf-8') as f: self.raw = f.read() self._encryption_version = self._init_encryption_version() if not self.is_encrypted(): @@ -257,7 +257,7 @@ class WalletStorage(PrintError): s = s.decode('utf8') temp_path = "%s.tmp.%s" % (self.path, os.getpid()) - with open(temp_path, "w") as f: + with open(temp_path, "w", encoding='utf-8') as f: f.write(s) f.flush() os.fsync(f.fileno()) diff --git a/lib/util.py b/lib/util.py index 76015a2dc..03dea3938 100644 --- a/lib/util.py +++ b/lib/util.py @@ -810,7 +810,7 @@ def versiontuple(v): def import_meta(path, validater, load_meta): try: - with open(path, 'r') as f: + with open(path, 'r', encoding='utf-8') as f: d = validater(json.loads(f.read())) load_meta(d) #backwards compatibility for JSONDecodeError @@ -824,7 +824,7 @@ def import_meta(path, validater, load_meta): def export_meta(meta, fileName): try: - with open(fileName, 'w+') as f: + with open(fileName, 'w+', encoding='utf-8') as f: json.dump(meta, f, indent=4, sort_keys=True) except (IOError, os.error) as e: traceback.print_exc(file=sys.stderr) diff --git a/lib/wallet.py b/lib/wallet.py index 209f16712..6dc4b8e5e 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1616,7 +1616,7 @@ class Abstract_Wallet(PrintError): f.write(pr.SerializeToString()) # reload req = self.get_payment_request(addr, config) - with open(os.path.join(path, key + '.json'), 'w') as f: + with open(os.path.join(path, key + '.json'), 'w', encoding='utf-8') as f: f.write(json.dumps(req)) return req diff --git a/lib/websockets.py b/lib/websockets.py index 415556b33..587119f97 100644 --- a/lib/websockets.py +++ b/lib/websockets.py @@ -64,7 +64,7 @@ class WsClientThread(util.DaemonThread): # read json file rdir = self.config.get('requests_dir') n = os.path.join(rdir, 'req', request_id[0], request_id[1], request_id, request_id + '.json') - with open(n) as f: + with open(n, encoding='utf-8') as f: s = f.read() d = json.loads(s) addr = d.get('address') diff --git a/lib/x509.py b/lib/x509.py index b7ec4389e..e4b94d404 100644 --- a/lib/x509.py +++ b/lib/x509.py @@ -313,7 +313,7 @@ def load_certificates(ca_path): ca_list = {} ca_keyID = {} # ca_path = '/tmp/tmp.txt' - with open(ca_path, 'r') as f: + with open(ca_path, 'r', encoding='utf-8') as f: s = f.read() bList = pem.dePemList(s, "CERTIFICATE") for b in bList: From 502d72c1673d4d764af82b6bfabaa504c58b0eab Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 24 Mar 2018 00:00:22 +0100 Subject: [PATCH 119/174] ledger: send derivation for change output --- plugins/ledger/ledger.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py index e6c42d155..598f92f1e 100644 --- a/plugins/ledger/ledger.py +++ b/plugins/ledger/ledger.py @@ -418,7 +418,12 @@ class Ledger_KeyStore(Hardware_KeyStore): if segwitTransaction: self.get_client().startUntrustedTransaction(True, inputIndex, chipInputs, redeemScripts[inputIndex]) - outputData = self.get_client().finalizeInputFull(txOutput) + if changePath: + # we don't set meaningful outputAddress, amount and fees + # as we only care about the alternateEncoding==True branch + outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx)) + else: + outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: @@ -441,7 +446,12 @@ class Ledger_KeyStore(Hardware_KeyStore): while inputIndex < len(inputs): self.get_client().startUntrustedTransaction(firstTransaction, inputIndex, chipInputs, redeemScripts[inputIndex]) - outputData = self.get_client().finalizeInputFull(txOutput) + if changePath: + # we don't set meaningful outputAddress, amount and fees + # as we only care about the alternateEncoding==True branch + outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx)) + else: + outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput if firstTransaction: transactionOutput = outputData['outputData'] From fa91cbf762587880fa32f51742be746cfadd3815 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 24 Mar 2018 18:34:51 +0100 Subject: [PATCH 120/174] network - send_subscriptions: copy subscribed_addresses before iterating see #4146 (this should at least avoid the RuntimeError) --- lib/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/network.py b/lib/network.py index d40951fde..b9f3c6894 100644 --- a/lib/network.py +++ b/lib/network.py @@ -319,7 +319,7 @@ class Network(util.DaemonThread): self.queue_request('server.peers.subscribe', []) self.request_fee_estimates() self.queue_request('blockchain.relayfee', []) - for h in self.subscribed_addresses: + for h in list(self.subscribed_addresses): self.queue_request('blockchain.scripthash.subscribe', [h]) def request_fee_estimates(self): From 7939b32a1f8aadaf4d726d00f526fea374068496 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 24 Mar 2018 23:51:35 +0100 Subject: [PATCH 121/174] minor kivy import fix --- gui/kivy/uix/menus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/kivy/uix/menus.py b/gui/kivy/uix/menus.py index 6b6efa9a7..17490054e 100644 --- a/gui/kivy/uix/menus.py +++ b/gui/kivy/uix/menus.py @@ -7,7 +7,7 @@ from kivy.uix.bubble import Bubble, BubbleButton from kivy.properties import ListProperty from kivy.uix.widget import Widget -from electrum_gui.i18n import _ +from electrum_gui.kivy.i18n import _ class ContextMenuItem(Widget): '''abstract class From b75d82491bfe939af14e1436708a7be06751ed16 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 22 Mar 2018 16:39:01 +0100 Subject: [PATCH 122/174] kivy: request PIN code on startup --- gui/kivy/main.kv | 3 +- gui/kivy/main_window.py | 69 +++++++++--------- gui/kivy/uix/dialogs/installwizard.py | 28 +++----- gui/kivy/uix/dialogs/password_dialog.py | 95 ++++++++++++++++++------- gui/kivy/uix/dialogs/settings.py | 3 +- 5 files changed, 112 insertions(+), 86 deletions(-) diff --git a/gui/kivy/main.kv b/gui/kivy/main.kv index 83b880d73..6db14c8f0 100644 --- a/gui/kivy/main.kv +++ b/gui/kivy/main.kv @@ -285,7 +285,8 @@ : size_hint: 1, None - height: '48dp' + height: '60dp' + font_size: '30dp' on_release: self.parent.update_amount(self.text) diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py index be9409d65..d353e4bfb 100644 --- a/gui/kivy/main_window.py +++ b/gui/kivy/main_window.py @@ -244,6 +244,7 @@ class ElectrumWindow(App): self.tabs = None self.is_exit = False self.wallet = None + self.pause_time = 0 App.__init__(self)#, **kwargs) @@ -445,7 +446,6 @@ class ElectrumWindow(App): #win.softinput_mode = 'below_target' self.on_size(win, win.size) self.init_ui() - self.load_wallet_by_name(self.electrum_config.get_wallet_path()) # init plugins run_hook('init_kivy', self) # fiat currency @@ -467,6 +467,8 @@ class ElectrumWindow(App): self.network.register_callback(self.on_fee, ['fee']) self.network.register_callback(self.on_quotes, ['on_quotes']) self.network.register_callback(self.on_history, ['on_history']) + # load wallet + self.load_wallet_by_name(self.electrum_config.get_wallet_path()) # URI passed in config uri = self.electrum_config.get('url') if uri: @@ -484,17 +486,18 @@ class ElectrumWindow(App): wallet.start_threads(self.daemon.network) self.daemon.add_wallet(wallet) self.load_wallet(wallet) - self.on_resume() def load_wallet_by_name(self, path): if not path: return + if self.wallet and self.wallet.storage.path == path: + return wallet = self.daemon.load_wallet(path, None) if wallet: - if wallet != self.wallet: - self.stop_wallet() + if wallet.has_password(): + self.password_dialog(wallet, _('Enter PIN code'), lambda x: self.load_wallet(wallet), self.stop) + else: self.load_wallet(wallet) - self.on_resume() else: Logger.debug('Electrum: Wallet not found. Launching install wizard') storage = WalletStorage(path) @@ -504,6 +507,7 @@ class ElectrumWindow(App): wizard.run(action) def on_stop(self): + Logger.info('on_stop') self.stop_wallet() def stop_wallet(self): @@ -617,6 +621,8 @@ class ElectrumWindow(App): @profiler def load_wallet(self, wallet): + if self.wallet: + self.stop_wallet() self.wallet = wallet self.update_wallet() # Once GUI has been initialized check if we want to announce something @@ -625,6 +631,7 @@ class ElectrumWindow(App): self.receive_screen.clear() self.update_tabs() run_hook('load_wallet', wallet, self) + print('load wallet done', self.wallet) def update_status(self, *dt): self.num_blocks = self.network.get_local_height() @@ -684,12 +691,16 @@ class ElectrumWindow(App): Logger.Error('Notification: needs plyer; `sudo pip install plyer`') def on_pause(self): + self.pause_time = time.time() # pause nfc if self.nfcscanner: self.nfcscanner.nfc_disable() return True def on_resume(self): + now = time.time() + if self.wallet.has_password and now - self.pause_time > 60: + self.password_dialog(self.wallet, _('Enter PIN'), None, self.stop) if self.nfcscanner: self.nfcscanner.nfc_enable() @@ -875,7 +886,8 @@ class ElectrumWindow(App): def protected(self, msg, f, args): if self.wallet.has_password(): - self.password_dialog(msg, f, args) + on_success = lambda pw: f(*(args + (pw,))) + self.password_dialog(self.wallet, msg, on_success, lambda: None) else: f(*(args + (None,))) @@ -887,7 +899,7 @@ class ElectrumWindow(App): def _delete_wallet(self, b): if b: - basename = os.path.basename(self.wallet.storage.path) + basename = self.wallet.basename() self.protected(_("Enter your PIN code to confirm deletion of {}").format(basename), self.__delete_wallet, ()) def __delete_wallet(self, pw): @@ -925,40 +937,23 @@ class ElectrumWindow(App): if passphrase: label.text += '\n\n' + _('Passphrase') + ': ' + passphrase - def change_password(self, cb): - if self.wallet.has_password(): - self.protected(_("Changing PIN code.") + '\n' + _("Enter your current PIN:"), self._change_password, (cb,)) - else: - self._change_password(cb, None) - - def _change_password(self, cb, old_password): - if self.wallet.has_password(): - if old_password is None: - return - try: - self.wallet.check_password(old_password) - except InvalidPassword: - self.show_error("Invalid PIN") - return - self.password_dialog(_('Enter new PIN'), self._change_password2, (cb, old_password,)) - - def _change_password2(self, cb, old_password, new_password): - self.password_dialog(_('Confirm new PIN'), self._change_password3, (cb, old_password, new_password)) - - def _change_password3(self, cb, old_password, new_password, confirmed_password): - if new_password == confirmed_password: - self.wallet.update_password(old_password, new_password) - cb() - else: - self.show_error("PIN numbers do not match") + def password_dialog(self, wallet, msg, on_success, on_failure): + from .uix.dialogs.password_dialog import PasswordDialog + if self._password_dialog is None: + self._password_dialog = PasswordDialog() + self._password_dialog.init(self, wallet, msg, on_success, on_failure) + self._password_dialog.open() - def password_dialog(self, msg, f, args): + def change_password(self, cb): from .uix.dialogs.password_dialog import PasswordDialog - def callback(pw): - Clock.schedule_once(lambda x: f(*(args + (pw,))), 0.1) if self._password_dialog is None: self._password_dialog = PasswordDialog() - self._password_dialog.init(msg, callback) + message = _("Changing PIN code.") + '\n' + _("Enter your current PIN:") + def on_success(old_password, new_password): + self.wallet.update_password(old_password, new_password) + self.show_info(_("Your PIN code was updated")) + on_failure = lambda: self.show_error(_("PIN codes do not match")) + self._password_dialog.init(self, self.wallet, message, on_success, on_failure, is_change=1) self._password_dialog.open() def export_private_keys(self, pk_label, addr): diff --git a/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py index da76ef75b..849263e6f 100644 --- a/gui/kivy/uix/dialogs/installwizard.py +++ b/gui/kivy/uix/dialogs/installwizard.py @@ -802,28 +802,18 @@ class InstallWizard(BaseWizard, Widget): app = App.get_running_app() Clock.schedule_once(lambda dt: app.show_error(msg)) - def password_dialog(self, message, callback): + def request_password(self, run_next, force_disable_encrypt_cb=False): + def on_success(old_pin, pin): + assert old_pin is None + run_next(pin, False) + def on_failure(): + self.show_error(_('PIN mismatch')) + self.run('request_password', run_next) popup = PasswordDialog() - popup.init(message, callback) + app = App.get_running_app() + popup.init(app, None, _('Choose PIN code'), on_success, on_failure, is_change=2) popup.open() - def request_password(self, run_next, force_disable_encrypt_cb=False): - def callback(pin): - if pin: - self.run('confirm_password', pin, run_next) - else: - run_next(None, None) - self.password_dialog('Choose a PIN code', callback) - - def confirm_password(self, pin, run_next): - def callback(conf): - if conf == pin: - run_next(pin, False) - else: - self.show_error(_('PIN mismatch')) - self.run('request_password', run_next) - self.password_dialog('Confirm your PIN code', callback) - def action_dialog(self, action, run_next): f = getattr(self, action) f() diff --git a/gui/kivy/uix/dialogs/password_dialog.py b/gui/kivy/uix/dialogs/password_dialog.py index 274b8a185..670179928 100644 --- a/gui/kivy/uix/dialogs/password_dialog.py +++ b/gui/kivy/uix/dialogs/password_dialog.py @@ -5,35 +5,42 @@ from kivy.lang import Builder from decimal import Decimal from kivy.clock import Clock +from electrum.util import InvalidPassword +from electrum_gui.kivy.i18n import _ + Builder.load_string(''' id: popup - title: _('PIN Code') + title: 'Electrum' message: '' - size_hint: 0.9, 0.9 BoxLayout: + size_hint: 1, 1 orientation: 'vertical' Widget: - size_hint: 1, 1 + size_hint: 1, 0.05 Label: + font_size: '20dp' text: root.message text_size: self.width, None size: self.texture_size Widget: - size_hint: 1, 1 + size_hint: 1, 0.05 Label: id: a - text: ' * '*len(kb.password) + ' o '*(6-len(kb.password)) + font_size: '50dp' + text: '*'*len(kb.password) + '-'*(6-len(kb.password)) + size: self.texture_size Widget: - size_hint: 1, 1 + size_hint: 1, 0.05 GridLayout: id: kb + size_hint: 1, None + height: self.minimum_height update_amount: popup.update_password password: '' on_password: popup.on_password(self.password) - size_hint: 1, None - height: '200dp' + spacing: '2dp' cols: 3 KButton: text: '1' @@ -59,30 +66,44 @@ Builder.load_string(''' text: '0' KButton: text: '<' - BoxLayout: - size_hint: 1, None - height: '48dp' - Widget: - size_hint: 0.5, None - Button: - size_hint: 0.5, None - height: '48dp' - text: _('Cancel') - on_release: - popup.dismiss() - popup.callback(None) ''') class PasswordDialog(Factory.Popup): - #def __init__(self, message, callback): - # Factory.Popup.__init__(self) - - def init(self, message, callback): + def init(self, app, wallet, message, on_success, on_failure, is_change=0): + self.app = app + self.wallet = wallet self.message = message - self.callback = callback + self.on_success = on_success + self.on_failure = on_failure self.ids.kb.password = '' + self.success = False + self.is_change = is_change + self.pw = None + self.new_password = None + self.title = 'Electrum' + (' - ' + self.wallet.basename() if self.wallet else '') + + def check_password(self, password): + if self.is_change > 1: + return True + try: + self.wallet.check_password(password) + return True + except InvalidPassword as e: + return False + + def on_dismiss(self): + if not self.success: + if self.on_failure: + self.on_failure() + else: + # keep dialog open + return True + else: + if self.on_success: + args = (self.pw, self.new_password) if self.is_change else (self.pw,) + Clock.schedule_once(lambda dt: self.on_success(*args), 0.1) def update_password(self, c): kb = self.ids.kb @@ -97,5 +118,25 @@ class PasswordDialog(Factory.Popup): def on_password(self, pw): if len(pw) == 6: - self.dismiss() - Clock.schedule_once(lambda dt: self.callback(pw), 0.1) + if self.check_password(pw): + if self.is_change == 0: + self.success = True + self.pw = pw + self.message = _('Please wait...') + self.dismiss() + elif self.is_change == 1: + self.pw = pw + self.message = _('Enter new PIN') + self.ids.kb.password = '' + self.is_change = 2 + elif self.is_change == 2: + self.new_password = pw + self.message = _('Confirm new PIN') + self.ids.kb.password = '' + self.is_change = 3 + elif self.is_change == 3: + self.success = pw == self.new_password + self.dismiss() + else: + self.app.show_error(_('Wrong PIN')) + self.ids.kb.password = '' diff --git a/gui/kivy/uix/dialogs/settings.py b/gui/kivy/uix/dialogs/settings.py index a3b3a5788..3a93a4f09 100644 --- a/gui/kivy/uix/dialogs/settings.py +++ b/gui/kivy/uix/dialogs/settings.py @@ -36,9 +36,8 @@ Builder.load_string(''' action: partial(root.language_dialog, self) CardSeparator SettingsItem: - status: '' if root.disable_pin else ('ON' if root.use_encryption else 'OFF') disabled: root.disable_pin - title: _('PIN code') + ': ' + self.status + title: _('PIN code') description: _("Change your PIN code.") action: partial(root.change_password, self) CardSeparator From d9c05914256d7f46b1004d07011324045e065a8f Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 25 Mar 2018 23:08:25 +0200 Subject: [PATCH 123/174] do not set cmd_runner.wallet when wallet is loaded. closes #4062 --- lib/daemon.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/daemon.py b/lib/daemon.py index b3242215e..c1e74ff6b 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -173,7 +173,6 @@ class Daemon(DaemonThread): elif sub == 'load_wallet': path = config.get_wallet_path() wallet = self.load_wallet(path, config.get('password')) - self.cmd_runner.wallet = wallet response = wallet is not None elif sub == 'close_wallet': path = config.get_wallet_path() From 032f40640ecbdf2c38cf5987888057ccb2e05bcc Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 25 Mar 2018 23:38:55 +0200 Subject: [PATCH 124/174] revert previous commit, use #4062 --- lib/daemon.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/daemon.py b/lib/daemon.py index c1e74ff6b..ab75625d3 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -173,6 +173,8 @@ class Daemon(DaemonThread): elif sub == 'load_wallet': path = config.get_wallet_path() wallet = self.load_wallet(path, config.get('password')) + if wallet is not None: + self.cmd_runner.wallet = wallet response = wallet is not None elif sub == 'close_wallet': path = config.get_wallet_path() From b1aba416e4eeb3f64663562d48fc120c5a78e42c Mon Sep 17 00:00:00 2001 From: Lucas Betschart Date: Mon, 26 Mar 2018 12:17:44 +0200 Subject: [PATCH 125/174] Use HTTPS for weblinks --- gui/qt/main_window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index f16ad1d2d..98bd6d401 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -538,7 +538,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): help_menu = menubar.addMenu(_("&Help")) help_menu.addAction(_("&About"), self.show_about) - help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org")) + help_menu.addAction(_("&Official website"), lambda: webbrowser.open("https://electrum.org")) help_menu.addSeparator() help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://docs.electrum.org/")).setShortcut(QKeySequence.HelpContents) help_menu.addAction(_("&Report Bug"), self.show_report_bug) @@ -2673,7 +2673,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n'\ + _('The following alias providers are available:') + '\n'\ + '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\n'\ - + 'For more information, see http://openalias.org' + + 'For more information, see https://openalias.org' alias_label = HelpLabel(_('OpenAlias') + ':', msg) alias = self.config.get('alias','') alias_e = QLineEdit(alias) From f854232837cb04960f1d116742c44de7f4e78ef7 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sun, 25 Mar 2018 20:19:56 +0200 Subject: [PATCH 126/174] Add script to build dmg on Linux --- contrib/build-osx/README.md | 32 ++++++-- contrib/build-osx/base.sh | 15 ++++ contrib/build-osx/cdrkit-deterministic.patch | 86 ++++++++++++++++++++ contrib/build-osx/make_osx | 26 ++---- contrib/build-osx/package.sh | 77 ++++++++++++++++++ 5 files changed, 210 insertions(+), 26 deletions(-) create mode 100644 contrib/build-osx/base.sh create mode 100644 contrib/build-osx/cdrkit-deterministic.patch create mode 100755 contrib/build-osx/package.sh diff --git a/contrib/build-osx/README.md b/contrib/build-osx/README.md index fd8d1d011..c1e96d90b 100644 --- a/contrib/build-osx/README.md +++ b/contrib/build-osx/README.md @@ -2,17 +2,35 @@ Building Mac OS binaries ======================== This guide explains how to build Electrum binaries for macOS systems. -We build our binaries on El Capitan (10.11.6) as building it on High Sierra -makes the binaries incompatible with older versions. -This assumes that the Xcode command line tools (and thus git) are already installed. +The build process consists of two steps: +## 1. Building the binary -## 1. Run the script +This needs to be done on a system running macOS or OS X. We use El Capitan (10.11.6) as building it on High Sierra +makes the binaries incompatible with older versions. +Before starting, make sure that the Xcode command line tools are installed (e.g. you have `git`). - $ cd electrum - $ ./contrib/build-osx/make_osx + cd electrum + ./contrib/build-osx/make_osx + +This creates a folder named Electrum.app. -## 2. Done +## 2. Building the image +The usual way to distribute macOS applications is to use image files containing the +application. Although these images can be created on a Mac with the built-in `hdiutil`, +they are not deterministic. + +Instead, we use the toolchain that Bitcoin uses: genisoimage and libdmg-hfsplus. +These tools do not work on macOS, so you need a separate Linux machine (or VM). + +Copy the Electrum.app directory over and install the dependencies, e.g.: + + apt install libcap-dev cmake make gcc faketime + +Then you can just invoke `package.sh` with the path to the app: + + cd electrum + ./contrib/build-osx/package.sh ~/Electrum.app/ \ No newline at end of file diff --git a/contrib/build-osx/base.sh b/contrib/build-osx/base.sh new file mode 100644 index 000000000..5dfb15578 --- /dev/null +++ b/contrib/build-osx/base.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +RED='\033[0;31m' +BLUE='\033[0,34m' +NC='\033[0m' # No Color +function info { + printf "\r💬 ${BLUE}INFO:${NC} ${1}\n" +} +function fail { + printf "\r🗯 ${RED}ERROR:${NC} ${1}\n" + exit 1 +} + +build_dir=$(dirname "$1") +test -n "$build_dir" -a -d "$build_dir" || exit diff --git a/contrib/build-osx/cdrkit-deterministic.patch b/contrib/build-osx/cdrkit-deterministic.patch new file mode 100644 index 000000000..d01e5b75e --- /dev/null +++ b/contrib/build-osx/cdrkit-deterministic.patch @@ -0,0 +1,86 @@ +--- cdrkit-1.1.11.old/genisoimage/tree.c 2008-10-21 19:57:47.000000000 -0400 ++++ cdrkit-1.1.11/genisoimage/tree.c 2013-12-06 00:23:18.489622668 -0500 +@@ -1139,8 +1139,9 @@ + scan_directory_tree(struct directory *this_dir, char *path, + struct directory_entry *de) + { +- DIR *current_dir; ++ int current_file; + char whole_path[PATH_MAX]; ++ struct dirent **d_list; + struct dirent *d_entry; + struct directory *parent; + int dflag; +@@ -1164,7 +1165,8 @@ + this_dir->dir_flags |= DIR_WAS_SCANNED; + + errno = 0; /* Paranoia */ +- current_dir = opendir(path); ++ //current_dir = opendir(path); ++ current_file = scandir(path, &d_list, NULL, alphasort); + d_entry = NULL; + + /* +@@ -1173,12 +1175,12 @@ + */ + old_path = path; + +- if (current_dir) { ++ if (current_file >= 0) { + errno = 0; +- d_entry = readdir(current_dir); ++ d_entry = d_list[0]; + } + +- if (!current_dir || !d_entry) { ++ if (current_file < 0 || !d_entry) { + int ret = 1; + + #ifdef USE_LIBSCHILY +@@ -1191,8 +1193,8 @@ + de->isorec.flags[0] &= ~ISO_DIRECTORY; + ret = 0; + } +- if (current_dir) +- closedir(current_dir); ++ if(d_list) ++ free(d_list); + return (ret); + } + #ifdef ABORT_DEEP_ISO_ONLY +@@ -1208,7 +1210,7 @@ + errmsgno(EX_BAD, "use Rock Ridge extensions via -R or -r,\n"); + errmsgno(EX_BAD, "or allow deep ISO9660 directory nesting via -D.\n"); + } +- closedir(current_dir); ++ free(d_list); + return (1); + } + #endif +@@ -1250,13 +1252,13 @@ + * The first time through, skip this, since we already asked + * for the first entry when we opened the directory. + */ +- if (dflag) +- d_entry = readdir(current_dir); ++ if (dflag && current_file >= 0) ++ d_entry = d_list[current_file]; + dflag++; + +- if (!d_entry) ++ if (current_file < 0) + break; +- ++ current_file--; + /* OK, got a valid entry */ + + /* If we do not want all files, then pitch the backups. */ +@@ -1348,7 +1350,7 @@ + insert_file_entry(this_dir, whole_path, d_entry->d_name); + #endif /* APPLE_HYB */ + } +- closedir(current_dir); ++ free(d_list); + + #ifdef APPLE_HYB + /* \ No newline at end of file diff --git a/contrib/build-osx/make_osx b/contrib/build-osx/make_osx index c459503e9..07645e865 100755 --- a/contrib/build-osx/make_osx +++ b/contrib/build-osx/make_osx @@ -1,28 +1,16 @@ -#!/bin/bash -RED='\033[0;31m' -BLUE='\033[0,34m' -NC='\033[0m' # No Color -function info { - printf "\r💬 ${BLUE}INFO:${NC} ${1}\n" -} -function fail { - printf "\r🗯 ${RED}ERROR:${NC} ${1}\n" - exit 1 -} - -build_dir=$(dirname "$0") -test -n "$build_dir" -a -d "$build_dir" || exit -cd $build_dir/../.. - -export PYTHONHASHSEED=22 -VERSION=`git describe --tags` +#!/usr/bin/env bash -# Paramterize +# Parameterize PYTHON_VERSION=3.6.4 BUILDDIR=/tmp/electrum-build PACKAGE=Electrum GIT_REPO=https://github.com/spesmilo/electrum +. $(dirname "$0")/base.sh +cd $build_dir/../.. + +export PYTHONHASHSEED=22 +VERSION=`git describe --tags` info "Installing Python $PYTHON_VERSION" export PATH="~/.pyenv/bin:~/.pyenv/shims:~/Library/Python/3.6/bin:$PATH" diff --git a/contrib/build-osx/package.sh b/contrib/build-osx/package.sh new file mode 100755 index 000000000..e4582a51c --- /dev/null +++ b/contrib/build-osx/package.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +cdrkit_version=1.1.11 +cdrkit_download_path=http://distro.ibiblio.org/fatdog/source/600/c +cdrkit_file_name=cdrkit-${cdrkit_version}.tar.bz2 +cdrkit_sha256_hash=b50d64c214a65b1a79afe3a964c691931a4233e2ba605d793eb85d0ac3652564 +cdrkit_patches=cdrkit-deterministic.patch +genisoimage=genisoimage-$cdrkit_version + +libdmg_url=https://github.com/theuni/libdmg-hfsplus + + +export LD_PRELOAD=$(locate libfaketime.so.1) +export FAKETIME="2000-01-22 00:00:00" +export PATH=$PATH:~/bin + +. $(dirname "$0")/base.sh + +if [ -z "$1" ]; then + echo "Usage: $0 Electrum.app" + exit -127 +fi + +mkdir -p ~/bin + +if ! which ${genisoimage} > /dev/null 2>&1; then + mkdir -p /tmp/electrum-macos + cd /tmp/electrum-macos + info "Downloading cdrkit $cdrkit_version" + wget -nc ${cdrkit_download_path}/${cdrkit_file_name} + tar xvf ${cdrkit_file_name} + + info "Patching genisoimage" + cd cdrkit-${cdrkit_version} + patch -p1 < ../cdrkit-deterministic.patch + + info "Building genisoimage" + cmake . -Wno-dev + make genisoimage + cp genisoimage/genisoimage ~/bin/${genisoimage} +fi + +if ! which dmg > /dev/null 2>&1; then + mkdir -p /tmp/electrum-macos + cd /tmp/electrum-macos + info "Downloading libdmg" + LD_PRELOAD= git clone ${libdmg_url} + cd libdmg-hfsplus + info "Building libdmg" + cmake . + make + cp dmg/dmg ~/bin +fi + +${genisoimage} -version || fail "Unable to install genisoimage" +dmg -|| fail "Unable to install libdmg" + +rm -rf /tmp/electrum-macos/image > /dev/null 2>&1 +mkdir /tmp/electrum-macos/image/ +cp -r $1 /tmp/electrum-macos/image/ + +cd $build_dir + +${genisoimage} \ + -no-cache-inodes \ + -D \ + -l \ + -probe \ + -V "Electrum" \ + -no-pad \ + -r \ + -dir-mode 0755 \ + -apple \ + -o Electrum_uncompressed.dmg \ + /tmp/electrum-macos/image || fail "Unable to create uncompressed dmg" + +dmg dmg Electrum_uncompressed.dmg Electrum.dmg || fail "Unable to create compressed dmg" From 553006c7e59542c9ae2ebc96f756ae1d22752881 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 27 Mar 2018 16:51:39 +0200 Subject: [PATCH 127/174] follow-up previous commit --- contrib/build-osx/base.sh | 3 --- contrib/build-osx/make_osx | 4 +++- contrib/build-osx/package.sh | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contrib/build-osx/base.sh b/contrib/build-osx/base.sh index 5dfb15578..c5a5c0d69 100644 --- a/contrib/build-osx/base.sh +++ b/contrib/build-osx/base.sh @@ -10,6 +10,3 @@ function fail { printf "\r🗯 ${RED}ERROR:${NC} ${1}\n" exit 1 } - -build_dir=$(dirname "$1") -test -n "$build_dir" -a -d "$build_dir" || exit diff --git a/contrib/build-osx/make_osx b/contrib/build-osx/make_osx index 07645e865..27c013438 100755 --- a/contrib/build-osx/make_osx +++ b/contrib/build-osx/make_osx @@ -7,7 +7,9 @@ PACKAGE=Electrum GIT_REPO=https://github.com/spesmilo/electrum . $(dirname "$0")/base.sh -cd $build_dir/../.. + +src_dir=$(dirname "$0") +cd $src_dir/../.. export PYTHONHASHSEED=22 VERSION=`git describe --tags` diff --git a/contrib/build-osx/package.sh b/contrib/build-osx/package.sh index e4582a51c..6371be2ef 100755 --- a/contrib/build-osx/package.sh +++ b/contrib/build-osx/package.sh @@ -59,6 +59,8 @@ rm -rf /tmp/electrum-macos/image > /dev/null 2>&1 mkdir /tmp/electrum-macos/image/ cp -r $1 /tmp/electrum-macos/image/ +build_dir=$(dirname "$1") +test -n "$build_dir" -a -d "$build_dir" || exit cd $build_dir ${genisoimage} \ From 6fd3d07b4b4064e3df1a7292d7655e90b0eb1297 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 27 Mar 2018 17:31:44 +0200 Subject: [PATCH 128/174] osx builds: use version number from Info.plist --- contrib/build-osx/package.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/contrib/build-osx/package.sh b/contrib/build-osx/package.sh index 6371be2ef..dcbc29388 100755 --- a/contrib/build-osx/package.sh +++ b/contrib/build-osx/package.sh @@ -55,6 +55,11 @@ fi ${genisoimage} -version || fail "Unable to install genisoimage" dmg -|| fail "Unable to install libdmg" +plist=$1/Contents/Info.plist +test -f "$plist" || fail "Info.plist not found" +VERSION=$(grep -1 ShortVersionString $plist |tail -1|gawk 'match($0, /(.*)<\/string>/, a) {print a[1]}') +echo $VERSION + rm -rf /tmp/electrum-macos/image > /dev/null 2>&1 mkdir /tmp/electrum-macos/image/ cp -r $1 /tmp/electrum-macos/image/ @@ -76,4 +81,8 @@ ${genisoimage} \ -o Electrum_uncompressed.dmg \ /tmp/electrum-macos/image || fail "Unable to create uncompressed dmg" -dmg dmg Electrum_uncompressed.dmg Electrum.dmg || fail "Unable to create compressed dmg" +dmg dmg Electrum_uncompressed.dmg electrum-$VERSION.dmg || fail "Unable to create compressed dmg" +rm Electrum_uncompressed.dmg + +echo "Done." +md5sum electrum-$VERSION.dmg From c9c7f34ddfdbaafd1fd32b8f741ee166be7dfe2b Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 27 Mar 2018 18:26:23 +0200 Subject: [PATCH 129/174] prepare version 3.1.2 --- RELEASE-NOTES | 24 +++++++++++++++--------- lib/version.py | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index dff649b56..edbb27ffb 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,15 +1,21 @@ +# Release 3.1.2 - (March 27, 2018) + + * Kivy/android: request PIN on startup + * Improve OSX build process + * Fix various bugs with hardware wallets + * Other minor bugfixes # Release 3.1.1 - (March 12, 2018) - * fix #4031: Trezor T support - * partial fix #4060: proxy and hardware wallet can't be used together - * fix #4039: can't set address labels - * fix crash related to coinbase transactions - * MacOS: use internal graphics card - * fix openalias related crashes - * speed-up capital gains calculations - * hw wallet encryption: re-prompt for passphrase if incorrect - * other minor fixes. + * fix #4031: Trezor T support + * partial fix #4060: proxy and hardware wallet can't be used together + * fix #4039: can't set address labels + * fix crash related to coinbase transactions + * MacOS: use internal graphics card + * fix openalias related crashes + * speed-up capital gains calculations + * hw wallet encryption: re-prompt for passphrase if incorrect + * other minor fixes. diff --git a/lib/version.py b/lib/version.py index 1d653e591..dbce6c2a5 100644 --- a/lib/version.py +++ b/lib/version.py @@ -1,4 +1,4 @@ -ELECTRUM_VERSION = '3.1' # version of the client package +ELECTRUM_VERSION = '3.1.2' # version of the client package PROTOCOL_VERSION = '1.2' # protocol version requested # The hash of the mnemonic seed must begin with this From ec3346342625149a1be086a8bff8b7b4ac80d3e7 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 27 Mar 2018 20:17:37 +0200 Subject: [PATCH 130/174] fix #4184 --- lib/blockchain.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/blockchain.py b/lib/blockchain.py index cf516069a..7ec9aa8af 100644 --- a/lib/blockchain.py +++ b/lib/blockchain.py @@ -317,6 +317,8 @@ class Blockchain(util.PrintError): return bitsN << 24 | bitsBase def can_connect(self, header, check_height=True): + if header is None: + return False height = header['block_height'] if check_height and self.height() != height - 1: #self.print_error("cannot connect at height", height) From bf4207e9fcd51227bddd47b35059af318e6f97ff Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 27 Mar 2018 22:21:07 +0300 Subject: [PATCH 131/174] electrum-env: run from script's dir --- electrum-env | 1 + 1 file changed, 1 insertion(+) diff --git a/electrum-env b/electrum-env index c05b2d1ab..1c2e6a4e3 100755 --- a/electrum-env +++ b/electrum-env @@ -11,6 +11,7 @@ PYTHON_VER="$(python3 -c 'import sys; print(sys.version[:3])')" +cd $(dirname $0) if [ -e ./env/bin/activate ]; then source ./env/bin/activate else From 79efc546082ce82baf6d0fee46bcfe9cb3e4c3f7 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 27 Mar 2018 21:36:39 +0200 Subject: [PATCH 132/174] freeze_packages (partial): up btchip-python --- contrib/deterministic-build/requirements-hw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/deterministic-build/requirements-hw.txt b/contrib/deterministic-build/requirements-hw.txt index 5052054ec..75a5c37af 100644 --- a/contrib/deterministic-build/requirements-hw.txt +++ b/contrib/deterministic-build/requirements-hw.txt @@ -1,4 +1,4 @@ -btchip-python==0.1.24 +btchip-python==0.1.26 certifi==2018.1.18 chardet==3.0.4 click==6.7 From 5926438847f40190847866fe431332ca58f28676 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 27 Mar 2018 23:40:46 +0200 Subject: [PATCH 133/174] fix #3294 --- lib/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.py b/lib/util.py index 03dea3938..1c12747eb 100644 --- a/lib/util.py +++ b/lib/util.py @@ -418,7 +418,7 @@ def format_satoshis(x, is_diff=False, num_zeros = 0, decimal_point = 8, whitespa return 'unknown' x = int(x) # Some callers pass Decimal scale_factor = pow (10, decimal_point) - integer_part = "{:n}".format(int(abs(x) / scale_factor)) + integer_part = "{:d}".format(int(abs(x) / scale_factor)) if x < 0: integer_part = '-' + integer_part elif is_diff: From 445ef3c6ac3f9afcb48b977e25415e4429c47d31 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 28 Mar 2018 10:46:08 +0200 Subject: [PATCH 134/174] remove print statement --- gui/kivy/main_window.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py index d353e4bfb..1d702f425 100644 --- a/gui/kivy/main_window.py +++ b/gui/kivy/main_window.py @@ -631,7 +631,6 @@ class ElectrumWindow(App): self.receive_screen.clear() self.update_tabs() run_hook('load_wallet', wallet, self) - print('load wallet done', self.wallet) def update_status(self, *dt): self.num_blocks = self.network.get_local_height() From 104ea477dee8829e9e3f05bf17c6c29496a42c5d Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 28 Mar 2018 10:52:58 +0200 Subject: [PATCH 135/174] fix #4136: revert to extras_require --- setup.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 7a2c86e02..58c938e5e 100755 --- a/setup.py +++ b/setup.py @@ -44,6 +44,9 @@ setup( name="Electrum", version=version.ELECTRUM_VERSION, install_requires=requirements, + extras_require={ + 'full': requirements_hw + ['pycryptodomex'], + }, packages=[ 'electrum', 'electrum_gui', @@ -88,8 +91,3 @@ setup( url="https://electrum.org", long_description="""Lightweight Bitcoin Wallet""" ) - -# Optional modules (not required to run Electrum) -import pip -opt_modules = requirements_hw + ['pycryptodomex'] -[ pip.main(['install', m]) for m in opt_modules ] From 377825a4584caf1034c8e89e540e8ccee95d3af9 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 28 Mar 2018 10:59:26 +0200 Subject: [PATCH 136/174] update release date --- RELEASE-NOTES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index edbb27ffb..db3337900 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,4 +1,4 @@ -# Release 3.1.2 - (March 27, 2018) +# Release 3.1.2 - (March 28, 2018) * Kivy/android: request PIN on startup * Improve OSX build process From facf833bac60d00d745c0bafc0ee4a08d2ba1fb9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 28 Mar 2018 11:54:24 +0200 Subject: [PATCH 137/174] README: install "full" extra --- README.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 8458581e5..d7a11d7b3 100644 --- a/README.rst +++ b/README.rst @@ -41,10 +41,12 @@ directory. To run Electrum from its root directory, just do:: You can also install Electrum on your system, by running this command:: sudo apt-get install python3-setuptools - python3 setup.py install + pip3 install .[full] This will download and install the Python dependencies used by Electrum, instead of using the 'packages' directory. +The 'full' extra contains some optional dependencies that we think +are often useful but they are not strictly needed. If you cloned the git repository, you need to compile extra files before you can run Electrum. Read the next section, "Development @@ -62,7 +64,7 @@ Check out the code from Github:: Run install (this should install dependencies):: - python3 setup.py install + pip3 install .[full] Compile the icons file for Qt:: From d69318ff148f99c2b77ba8acd17a7988797191cd Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 28 Mar 2018 21:00:38 +0300 Subject: [PATCH 138/174] trezor: pass transport parameter explicitly as a keyword argument (#4194) --- plugins/trezor/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/trezor/client.py b/plugins/trezor/client.py index 90710781f..89b5c2927 100644 --- a/plugins/trezor/client.py +++ b/plugins/trezor/client.py @@ -3,8 +3,8 @@ from .clientbase import TrezorClientBase class TrezorClient(TrezorClientBase, ProtocolMixin, BaseClient): def __init__(self, transport, handler, plugin): - BaseClient.__init__(self, transport) - ProtocolMixin.__init__(self, transport) + BaseClient.__init__(self, transport=transport) + ProtocolMixin.__init__(self, transport=transport) TrezorClientBase.__init__(self, handler, plugin, proto) From ffbd0ccecd719c37c7f7182c4a8c47fe7a449390 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 30 Mar 2018 22:05:55 +0200 Subject: [PATCH 139/174] fix #2670 --- plugins/keepkey/clientbase.py | 8 +++++++- plugins/trezor/clientbase.py | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/keepkey/clientbase.py b/plugins/keepkey/clientbase.py index acbdb200a..9e1875573 100644 --- a/plugins/keepkey/clientbase.py +++ b/plugins/keepkey/clientbase.py @@ -69,7 +69,13 @@ class GuiMixin(object): if passphrase is None: return self.proto.Cancel() passphrase = bip39_normalize_passphrase(passphrase) - return self.proto.PassphraseAck(passphrase=passphrase) + + ack = self.proto.PassphraseAck(passphrase=passphrase) + length = len(ack.passphrase) + if length > 50: + self.handler.show_error(_("Too long passphrase ({} > 50 chars).").format(length)) + return self.proto.Cancel() + return ack def callback_WordRequest(self, msg): self.step += 1 diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py index d1126e071..1c3207412 100644 --- a/plugins/trezor/clientbase.py +++ b/plugins/trezor/clientbase.py @@ -72,7 +72,13 @@ class GuiMixin(object): if passphrase is None: return self.proto.Cancel() passphrase = bip39_normalize_passphrase(passphrase) - return self.proto.PassphraseAck(passphrase=passphrase) + + ack = self.proto.PassphraseAck(passphrase=passphrase) + length = len(ack.passphrase) + if length > 50: + self.handler.show_error(_("Too long passphrase ({} > 50 chars).").format(length)) + return self.proto.Cancel() + return ack def callback_PassphraseStateRequest(self, msg): return self.proto.PassphraseStateAck() From fb65493963a129adf9c1cf53b887ed8dac19b1ce Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 30 Mar 2018 22:31:29 +0200 Subject: [PATCH 140/174] remove unused variable --- plugins/keepkey/plugin.py | 4 ---- plugins/ledger/ledger.py | 1 - plugins/trezor/trezor.py | 3 --- 3 files changed, 8 deletions(-) diff --git a/plugins/keepkey/plugin.py b/plugins/keepkey/plugin.py index 20c34ca4d..fa857c40d 100644 --- a/plugins/keepkey/plugin.py +++ b/plugins/keepkey/plugin.py @@ -1,5 +1,3 @@ -import threading - from binascii import hexlify, unhexlify from electrum.util import bfh, bh2u @@ -72,8 +70,6 @@ class KeepKeyCompatiblePlugin(HW_PluginBase): def __init__(self, parent, config, name): HW_PluginBase.__init__(self, parent, config, name) - self.main_thread = threading.current_thread() - # FIXME: move to base class when Ledger is fixed if self.libraries_available: self.device_manager().register_devices(self.DEVICE_IDS) diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py index 598f92f1e..36f51142c 100644 --- a/plugins/ledger/ledger.py +++ b/plugins/ledger/ledger.py @@ -573,7 +573,6 @@ class LedgerPlugin(HW_PluginBase): def get_client(self, keystore, force_pair=True): # All client interaction should not be in the main GUI thread - #assert self.main_thread != threading.current_thread() devmgr = self.device_manager() handler = keystore.handler with devmgr.hid_lock: diff --git a/plugins/trezor/trezor.py b/plugins/trezor/trezor.py index cbd14fd81..edf4d57bf 100644 --- a/plugins/trezor/trezor.py +++ b/plugins/trezor/trezor.py @@ -1,5 +1,3 @@ -import threading - from binascii import hexlify, unhexlify from electrum.util import bfh, bh2u, versiontuple @@ -93,7 +91,6 @@ class TrezorPlugin(HW_PluginBase): def __init__(self, parent, config, name): HW_PluginBase.__init__(self, parent, config, name) - self.main_thread = threading.current_thread() try: # Minimal test if python-trezor is installed From d213a7cd4f09d85fdc75a1d4c9fe38473dac7220 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 30 Mar 2018 22:55:46 +0200 Subject: [PATCH 141/174] update frozen dependencies --- contrib/deterministic-build/requirements-binaries.txt | 2 +- contrib/deterministic-build/requirements-hw.txt | 6 +++--- contrib/deterministic-build/requirements.txt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/deterministic-build/requirements-binaries.txt b/contrib/deterministic-build/requirements-binaries.txt index 8fffe6cee..fc0f67910 100644 --- a/contrib/deterministic-build/requirements-binaries.txt +++ b/contrib/deterministic-build/requirements-binaries.txt @@ -1,4 +1,4 @@ -pycryptodomex==3.4.12 +pycryptodomex==3.5.1 PyQt5==5.10.1 sip==4.19.8 six==1.11.0 diff --git a/contrib/deterministic-build/requirements-hw.txt b/contrib/deterministic-build/requirements-hw.txt index 75a5c37af..e7fb375ca 100644 --- a/contrib/deterministic-build/requirements-hw.txt +++ b/contrib/deterministic-build/requirements-hw.txt @@ -2,7 +2,7 @@ btchip-python==0.1.26 certifi==2018.1.18 chardet==3.0.4 click==6.7 -Cython==0.27.3 +Cython==0.28.1 ecdsa==0.13 hidapi==0.7.99.post21 idna==2.6 @@ -10,8 +10,8 @@ keepkey==4.0.2 libusb1==1.6.4 mnemonic==0.18 pbkdf2==1.3 -protobuf==3.5.1 -pyblake2==1.1.0 +protobuf==3.5.2.post1 +pyblake2==1.1.1 requests==2.18.4 rlp==0.6.0 six==1.11.0 diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt index e7d8925b8..572892bff 100644 --- a/contrib/deterministic-build/requirements.txt +++ b/contrib/deterministic-build/requirements.txt @@ -5,10 +5,10 @@ ecdsa==0.13 idna==2.6 jsonrpclib-pelix==0.3.1 pbkdf2==1.3 -protobuf==3.5.1 +protobuf==3.5.2.post1 pyaes==1.6.1 PySocks==1.6.8 -qrcode==5.3 +qrcode==6.0 requests==2.18.4 six==1.11.0 urllib3==1.22 From deab75fe20259f685f40ab1260006be830c28013 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 31 Mar 2018 00:11:57 +0200 Subject: [PATCH 142/174] windows build script: cache pip previously pip was caching into the wine directory, which we delete and recreate on every run --- contrib/build-wine/build.sh | 4 ++++ contrib/build-wine/prepare-wine.sh | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/build-wine/build.sh b/contrib/build-wine/build.sh index 8bf650626..9870ec7e3 100755 --- a/contrib/build-wine/build.sh +++ b/contrib/build-wine/build.sh @@ -13,6 +13,10 @@ echo "Clearing $here/build and $here/dist..." rm "$here"/build/* -rf rm "$here"/dist/* -rf +mkdir -p /tmp/electrum-build +mkdir -p /tmp/electrum-build/pip-cache +export PIP_CACHE_DIR="/tmp/electrum-build/pip-cache" + $here/prepare-wine.sh || exit 1 echo "Resetting modification time in C:\Python..." diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index d19ca7365..5e5864e05 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -86,8 +86,6 @@ echo "done" wine 'wineboot' -mkdir -p /tmp/electrum-build - cd /tmp/electrum-build # Install Python From 9a12022537cb8e67dc38180ce4282f29089cf5b4 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 31 Mar 2018 00:14:26 +0200 Subject: [PATCH 143/174] windows build script: update python, nsis, libusb --- contrib/build-wine/build-electrum-git.sh | 2 +- contrib/build-wine/deterministic.spec | 4 +++- contrib/build-wine/prepare-wine.sh | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh index 2fd6ea007..c3b92d62c 100755 --- a/contrib/build-wine/build-electrum-git.sh +++ b/contrib/build-wine/build-electrum-git.sh @@ -1,7 +1,7 @@ #!/bin/bash NAME_ROOT=electrum -PYTHON_VERSION=3.5.4 +PYTHON_VERSION=3.6.5 # These settings probably don't need any change export WINEPREFIX=/opt/wine64 diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec index 33dfd60ba..5b08dbf34 100644 --- a/contrib/build-wine/deterministic.spec +++ b/contrib/build-wine/deterministic.spec @@ -10,6 +10,8 @@ for i, x in enumerate(sys.argv): else: raise BaseException('no name') +PYTHON_VERSION = '3.6.5' +PYHOME = 'c:/python' + PYTHON_VERSION home = 'C:\\electrum\\' @@ -21,7 +23,7 @@ hiddenimports += collect_submodules('keepkeylib') hiddenimports += collect_submodules('websocket') # Add libusb binary -binaries = [("c:/python3.5.4/libusb-1.0.dll", ".")] +binaries = [(PYHOME+"/libusb-1.0.dll", ".")] # Workaround for "Retro Look": binaries += [b for b in collect_dynamic_libs('PyQt5') if 'qwindowsvista' in b[0]] diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index 5e5864e05..27c34e7f4 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -1,19 +1,19 @@ #!/bin/bash # Please update these carefully, some versions won't work under Wine -NSIS_FILENAME=nsis-3.02.1-setup.exe +NSIS_FILENAME=nsis-3.03-setup.exe NSIS_URL=https://prdownloads.sourceforge.net/nsis/$NSIS_FILENAME?download -NSIS_SHA256=736c9062a02e297e335f82252e648a883171c98e0d5120439f538c81d429552e +NSIS_SHA256=bd3b15ab62ec6b0c7a00f46022d441af03277be893326f6fea8e212dc2d77743 ZBAR_FILENAME=zbarw-20121031-setup.exe ZBAR_URL=https://sourceforge.net/projects/zbarw/files/$ZBAR_FILENAME/download ZBAR_SHA256=177e32b272fa76528a3af486b74e9cb356707be1c5ace4ed3fcee9723e2c2c02 -LIBUSB_FILENAME=libusb-1.0.21.7z -LIBUSB_URL=https://prdownloads.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.21/$LIBUSB_FILENAME?download -LIBUSB_SHA256=acdde63a40b1477898aee6153f9d91d1a2e8a5d93f832ca8ab876498f3a6d2b8 +LIBUSB_FILENAME=libusb-1.0.22.7z +LIBUSB_URL=https://prdownloads.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.22/$LIBUSB_FILENAME?download +LIBUSB_SHA256=671f1a420757b4480e7fadc8313d6fb3cbb75ca00934c417c1efa6e77fb8779b -PYTHON_VERSION=3.5.4 +PYTHON_VERSION=3.6.5 ## These settings probably don't need change export WINEPREFIX=/opt/wine64 From 97c295924acd3d9d5b7f1397dd35b3cf7ef5c24e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 31 Mar 2018 00:23:41 +0200 Subject: [PATCH 144/174] mac build script: update libusb --- contrib/build-osx/make_osx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/build-osx/make_osx b/contrib/build-osx/make_osx index 27c013438..60b160afa 100755 --- a/contrib/build-osx/make_osx +++ b/contrib/build-osx/make_osx @@ -51,9 +51,9 @@ cp $BUILDDIR/electrum-icons/icons_rc.py ./gui/qt/ info "Downloading libusb..." -curl https://homebrew.bintray.com/bottles/libusb-1.0.21.el_capitan.bottle.tar.gz | \ +curl https://homebrew.bintray.com/bottles/libusb-1.0.22.el_capitan.bottle.tar.gz | \ tar xz --directory $BUILDDIR -cp $BUILDDIR/libusb/1.0.21/lib/libusb-1.0.dylib contrib/build-osx +cp $BUILDDIR/libusb/1.0.22/lib/libusb-1.0.dylib contrib/build-osx info "Installing requirements..." python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user && \ From 3c28e34919dd356a5040d61bf10e5fe8075f2334 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 31 Mar 2018 17:49:38 +0200 Subject: [PATCH 145/174] Check package hashes when generating binaries --- contrib/build-osx/make_osx | 6 +- contrib/build-wine/build-electrum-git.sh | 4 +- contrib/build-wine/prepare-wine.sh | 2 +- .../requirements-binaries.txt | 53 ++++++- .../deterministic-build/requirements-hw.txt | 129 +++++++++++++++--- contrib/deterministic-build/requirements.txt | 71 ++++++++-- contrib/freeze_packages.sh | 18 ++- 7 files changed, 236 insertions(+), 47 deletions(-) diff --git a/contrib/build-osx/make_osx b/contrib/build-osx/make_osx index 60b160afa..a7aa6cb3d 100755 --- a/contrib/build-osx/make_osx +++ b/contrib/build-osx/make_osx @@ -56,12 +56,12 @@ tar xz --directory $BUILDDIR cp $BUILDDIR/libusb/1.0.22/lib/libusb-1.0.dylib contrib/build-osx info "Installing requirements..." -python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user && \ -python3 -m pip install -Ir ./contrib/deterministic-build/requirements-binaries.txt --user || \ +python3 -m pip install -Ir --require-hashes ./contrib/deterministic-build/requirements.txt --user && \ +python3 -m pip install -Ir --require-hashes ./contrib/deterministic-build/requirements-binaries.txt --user || \ fail "Could not install requirements" info "Installing hardware wallet requirements..." -python3 -m pip install -Ir ./contrib/deterministic-build/requirements-hw.txt --user || \ +python3 -m pip install -Ir --require-hashes ./contrib/deterministic-build/requirements-hw.txt --user || \ fail "Could not install hardware wallet requirements" info "Building $PACKAGE..." diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh index c3b92d62c..3463d58f8 100755 --- a/contrib/build-wine/build-electrum-git.sh +++ b/contrib/build-wine/build-electrum-git.sh @@ -56,9 +56,9 @@ cp -r electrum-locale/locale $WINEPREFIX/drive_c/electrum/lib/ cp electrum-icons/icons_rc.py $WINEPREFIX/drive_c/electrum/gui/qt/ # Install frozen dependencies -$PYTHON -m pip install -r ../../deterministic-build/requirements.txt +$PYTHON -m pip install -r --require-hashes ../../deterministic-build/requirements.txt -$PYTHON -m pip install -r ../../deterministic-build/requirements-hw.txt +$PYTHON -m pip install -r --require-hashes ../../deterministic-build/requirements-hw.txt pushd $WINEPREFIX/drive_c/electrum $PYTHON setup.py install diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index 27c34e7f4..e20fd197a 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -112,7 +112,7 @@ $PYTHON -m pip install pywin32-ctypes==0.1.2 # install PySocks $PYTHON -m pip install win_inet_pton==1.0.1 -$PYTHON -m pip install -r $here/../deterministic-build/requirements-binaries.txt +$PYTHON -m pip install -r --require-hashes $here/../deterministic-build/requirements-binaries.txt # Install PyInstaller $PYTHON -m pip install https://github.com/ecdsa/pyinstaller/archive/fix_2952.zip diff --git a/contrib/deterministic-build/requirements-binaries.txt b/contrib/deterministic-build/requirements-binaries.txt index fc0f67910..d2c7c1b43 100644 --- a/contrib/deterministic-build/requirements-binaries.txt +++ b/contrib/deterministic-build/requirements-binaries.txt @@ -1,5 +1,48 @@ -pycryptodomex==3.5.1 -PyQt5==5.10.1 -sip==4.19.8 -six==1.11.0 -websocket-client==0.47.0 +pycryptodomex==3.5.1 \ + --hash=sha256:16ab612ca9164e971dc00f8fe895ac835e8bfe64c3174b368f80172ff5a98300 \ + --hash=sha256:299a79efba6152ea438cc37f7349161e7bbd914f918342cad6316a4a5f29f2d7 \ + --hash=sha256:2a55e8fd69c84287b44e2c9c07eaad314e76680b86e873774314c27266728670 \ + --hash=sha256:2f71dc2b91288cf4a164287858eaccdc7053bf5765ebc47c5188f94eccc35e80 \ + --hash=sha256:2f81caf3ee08f00a957fd074c33430e8781958c616e864c5a1e709fb954750bc \ + --hash=sha256:3d4c77f1d4273ae753e49dac5c916f2278b0dd354a0c5f2a29fcf88bbae4efa9 \ + --hash=sha256:563512542dcab3e95d8cef70e45cc5a43ef35ff84bc040c388b305015343e51e \ + --hash=sha256:5d058decae88f86833a430afc0517df815d9efa4255b3a6d576c7fb305cb56d4 \ + --hash=sha256:5ec5903197d256b4559ff5c6a4756c34219ec81aff92be1174681623ba1e6383 \ + --hash=sha256:67f6573ff84ce7f7ea8ffc01ba5821c15dc85bf43291e4f8e11d7b6e2d5f504e \ + --hash=sha256:729da9aa2b8ea0bd8e35bc89ecd1ff4e482e6e9c2275e2e19de8b68dd8156fb5 \ + --hash=sha256:82df0a7cda5c94e9e4c62fb8d6507d5418f6593c8ed1b40b538a771ca003b597 \ + --hash=sha256:91b87c3abb24da1a980cb0f05e150eb0525235129bc5cb59277ea96860677f0a \ + --hash=sha256:a02b1b17d7c86b12bc1d4ede75846a7971e7df6d75508cd0696e383c18cad4ce \ + --hash=sha256:a36d5a5b73e51d66e3f1da53ce00e56de860a9c529f2811bb8d95374d9da06df \ + --hash=sha256:a7d836d6284c4734841c7c9d851be546650302ebca281de851129c22f1298ad5 \ + --hash=sha256:bb60d38111ebc383a5a1c909545562926c66c846d03fc65ba7b8a3487cb23078 \ + --hash=sha256:bf2e6cf6e78c8e6d63eeaa9641cad5008a382af98f2dc25cb7c6444f13133df9 \ + --hash=sha256:e303a4a1b242d3277e8dea07ab4e3737d0d1ed122990c713d6f88b0dda10c378 \ + --hash=sha256:e378bd7a09257a7a9a58f7f04b088991cf23a99847e9f42d6f996b4e52a11c33 \ + --hash=sha256:e75e7fe725dd5989e89da25a2fe7e3d35ed8123ac30eaae2f2340d0ba0431a88 \ + --hash=sha256:eac46844350302c93f3fd3eaa37353ee9e25cffcd1c574dfffb22de157ddce17 \ + --hash=sha256:f0ca00abf69827e78415050780cf838c7af9f378e591611210e25a03d6d0ea90 +PyQt5==5.10.1 \ + --hash=sha256:1e652910bd1ffd23a3a48c510ecad23a57a853ed26b782cd54b16658e6f271ac \ + --hash=sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b \ + --hash=sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7 \ + --hash=sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61 +SIP==4.19.8 \ + --hash=sha256:09f9a4e6c28afd0bafedb26ffba43375b97fe7207bd1a0d3513f79b7d168b331 \ + --hash=sha256:105edaaa1c8aa486662226360bd3999b4b89dd56de3e314d82b83ed0587d8783 \ + --hash=sha256:1bb10aac55bd5ab0e2ee74b3047aa2016cfa7932077c73f602a6f6541af8cd51 \ + --hash=sha256:265ddf69235dd70571b7d4da20849303b436192e875ce7226be7144ca702a45c \ + --hash=sha256:52074f7cb5488e8b75b52f34ec2230bc75d22986c7fe5cd3f2d266c23f3349a7 \ + --hash=sha256:5ff887a33839de8fc77d7f69aed0259b67a384dc91a1dc7588e328b0b980bde2 \ + --hash=sha256:74da4ddd20c5b35c19cda753ce1e8e1f71616931391caeac2de7a1715945c679 \ + --hash=sha256:7d69e9cf4f8253a3c0dfc5ba6bb9ac8087b8239851f22998e98cb35cfe497b68 \ + --hash=sha256:97bb93ee0ef01ba90f57be2b606e08002660affd5bc380776dd8b0fcaa9e093a \ + --hash=sha256:cf98150a99e43fda7ae22abe655b6f202e491d6291486548daa56cb15a2fcf85 \ + --hash=sha256:d9023422127b94d11c1a84bfa94933e959c484f2c79553c1ef23c69fe00d25f8 \ + --hash=sha256:e72955e12f4fccf27aa421be383453d697b8a44bde2cc26b08d876fd492d0174 +six==1.11.0 \ + --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \ + --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb +websocket-client==0.47.0 \ + --hash=sha256:188b68b14fdb2d8eb1a111f21b9ffd2dbf1dbc4e4c1d28cf2c37cdbf1dd1cae6 \ + --hash=sha256:a453dc4dfa6e0db3d8fd7738a308a88effe6240c59f3226eb93e8f020c216149 diff --git a/contrib/deterministic-build/requirements-hw.txt b/contrib/deterministic-build/requirements-hw.txt index e7fb375ca..63d7fd80c 100644 --- a/contrib/deterministic-build/requirements-hw.txt +++ b/contrib/deterministic-build/requirements-hw.txt @@ -1,19 +1,110 @@ -btchip-python==0.1.26 -certifi==2018.1.18 -chardet==3.0.4 -click==6.7 -Cython==0.28.1 -ecdsa==0.13 -hidapi==0.7.99.post21 -idna==2.6 -keepkey==4.0.2 -libusb1==1.6.4 -mnemonic==0.18 -pbkdf2==1.3 -protobuf==3.5.2.post1 -pyblake2==1.1.1 -requests==2.18.4 -rlp==0.6.0 -six==1.11.0 -trezor==0.9.1 -urllib3==1.22 +btchip-python==0.1.26 \ + --hash=sha256:427d67c5b4f4709605c51dd91d5d44a2ad8f541693673817765271e4b3a1461e +certifi==2018.1.18 \ + --hash=sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296 \ + --hash=sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +click==6.7 \ + --hash=sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d \ + --hash=sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b +Cython==0.28.1 \ + --hash=sha256:0a44c3645a84724d4f7f7c1126f3807cad14271f210bb1a9a15bfd330c8d20b9 \ + --hash=sha256:152ee5f345012ca3bb7cc71da2d3736ee20f52cd8476e4d49e5e25c5a4102b12 \ + --hash=sha256:15cbde95cdf6a346c63c0b38b895aa3186d0a49adcaf42b9e19c4cb0473cb921 \ + --hash=sha256:175273eb6a90af364b9b2aea74e3c3eebcde9caec02d617cd8886c0933ec3304 \ + --hash=sha256:1a434e7621ca6671ce949893fef94b13a580853ba976e729f68dda5ab270ee8a \ + --hash=sha256:1da199a5be7c486ee89b4a8bda7f00f9fd98b800d9af3a1fe3fc63e68dadea85 \ + --hash=sha256:26196ff8fe00e7c4c5815a310c368edfc1a1c8a3c90ac9220435d1b01e795cf7 \ + --hash=sha256:2b73b062658511167dde37a51acb80ae6ddea1ffa284ebdbc47a900f21e63c70 \ + --hash=sha256:2b9aa64473fefbe988e36a30915732a0e2a75ffe0f3e81a70e3f8d367c2e4119 \ + --hash=sha256:32638aefc164404ac70e5f86558cd3aece34df17db16da905abf5e664073bae4 \ + --hash=sha256:330c95c03e39835976d1410f5fa877b75fcc5c50dc146d4c02377fc719b1f8c9 \ + --hash=sha256:38b499fa317cf6939e46317457240553b13e974f08ed387523f10af26e269f6c \ + --hash=sha256:3c60caa0075aa1f1c5e10b5352d2e8bb9a18361ce9fca50fa7d4baea67804ade \ + --hash=sha256:3fc9b945541cadb5a10316e48b5e73f4b6f635b7d30156f502b66f3766545b23 \ + --hash=sha256:427299c47cfe04d97f9f09ea55570c05898a87b64caf6ddebb529b6f64e5d228 \ + --hash=sha256:4e07e619af85e7c1ec2344ecab558929bb4acbca25f8f170d07dc677e8ee413f \ + --hash=sha256:5010e048fb9791522fe626bd40137b8a847ba77a6e656bb64d6d7acdc45ece32 \ + --hash=sha256:70bc2806fc9e5affcf74c0d4fa12cba57ffb94cdfc28b975692c8df56ea86402 \ + --hash=sha256:7cdd5303121024269610236876d9f4beb6a909a1ea5d7bc48e7bbf5d8774a3f2 \ + --hash=sha256:80856aa45004514a3ff5e102bd18fbd5230d234311de1f83d4e5b97ef42f6c93 \ + --hash=sha256:996ae959ab2600b8ad4c80981afc32e89b42d0abe3234c48e960e40180459cb2 \ + --hash=sha256:b5c2e31be55bc61d3c758889d06b16d84f4fda944832fbed63c113ec2dbc5f97 \ + --hash=sha256:c39b3a042bf5ded7c8336c82b1fa817e1f46a7ef16d41d66b3d3340e7a3b60ed \ + --hash=sha256:d08f5dd2fbf7d1506c9d986a8352b2423170002ddb635b184d2a916d2b5df0d6 \ + --hash=sha256:d2b636c16931663aeb8ffb91cf871a912e66e7200755ce68aa8c502c16eb366f \ + --hash=sha256:e27e12834ac315c7d67ca697a325d42ff8395e9b82fb62c8665bb583eb0df589 \ + --hash=sha256:e312dd688b97e9f97199a8e4ba18b65db2747157630761d27193a18761b23fed \ + --hash=sha256:e47bbe74d6c87fab2e22f1580aa3e4a8e7482b4265b1fc76685fc49f90e18f99 \ + --hash=sha256:ea113ff58e96152738e646b8ee77b41d3994735df77bee0a26cd413a67e03c0f \ + --hash=sha256:ea22d79133583b5b0f856dbfda097363228ae0a3d77431deaba90634e5d4853a +ecdsa==0.13 \ + --hash=sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c \ + --hash=sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa +hidapi==0.7.99.post21 \ + --hash=sha256:1ac170f4d601c340f2cd52fd06e85c5e77bad7ceac811a7bb54b529f7dc28c24 \ + --hash=sha256:8d3be666f464347022e2b47caf9132287885d9eacc7895314fc8fefcb4e42946 \ + --hash=sha256:b4b1f6aff0192e9be153fe07c1b7576cb7a1ff52e78e3f76d867be95301a8e87 \ + --hash=sha256:bf03f06f586ce7d8aeb697a94b7dba12dc9271aae92d7a8d4486360ff711a660 \ + --hash=sha256:c76de162937326fcd57aa399f94939ce726242323e65c15c67e183da1f6c26f7 \ + --hash=sha256:d4ad1e46aef98783a9e6274d523b8b1e766acfc3d72828cd44a337564d984cfa \ + --hash=sha256:d4b5787a04613503357606bb10e59c3e2c1114fa00ee328b838dd257f41cbd7b \ + --hash=sha256:e0be1aa6566979266a8fc845ab0e18613f4918cf2c977fe67050f5dc7e2a9a97 \ + --hash=sha256:edfb16b16a298717cf05b8c8a9ad1828b6ff3de5e93048ceccd74e6ae4ff0922 +idna==2.6 \ + --hash=sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f \ + --hash=sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4 +keepkey==4.0.2 \ + --hash=sha256:cddee60ae405841cdff789cbc54168ceaeb2282633420f2be155554c25c69138 +libusb1==1.6.4 \ + --hash=sha256:8c930d9c1d037d9c83924c82608aa6a1adcaa01ca0e4a23ee0e8e18d7eee670d +mnemonic==0.18 \ + --hash=sha256:02a7306a792370f4a0c106c2cf1ce5a0c84b9dbd7e71c6792fdb9ad88a727f1d +pbkdf2==1.3 \ + --hash=sha256:ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979 +protobuf==3.5.2.post1 \ + --hash=sha256:01ccd6d03449ae75b779fb5bf4ed62177d61afe3c5e6465ccf3f8b2e1a84afbe \ + --hash=sha256:1d92cc30b0b46cced33adde5853d920179eb5ea8eecdee9552502a7f29cc3f21 \ + --hash=sha256:242e4c7ae565267a8bc8b92d707177f915607ea4bd73244bec6cbf4a49b96661 \ + --hash=sha256:3b60685732bd0cbdc802dfcb6071efbcf5d927ce3127c13c33ea1a8efae3aa76 \ + --hash=sha256:3f655e1f99c3e14d56ca900af1b9a4715b691319a295cc38939d7f77eabd5e7c \ + --hash=sha256:560a38e692a69957a70ba0e5839aa67430efd63072bf91b0539dac19055694cd \ + --hash=sha256:5c1c8f6a0a68a874e3beff89255959dd80fad45870e96c88944a1b81a22dd5f5 \ + --hash=sha256:628a3bf0794a8b3cabb18db11eb67cc10e0cc6e5525d557ae7b682bb73fa2018 \ + --hash=sha256:7222d6616108b33ad6cbeff8117062a73c43cdc8fa8f64f6a322ebeb663e710e \ + --hash=sha256:76ef6ca3c50e4cfd044861586d5f1b352e0fe7f17f883df6c165bad5b4d0e10a \ + --hash=sha256:7c193e6964e752bd056735594826c5b03274ceb8f07349d3ae47d9766250ba96 \ + --hash=sha256:869e12bcfb5759e683f53ec1dd6155b7be034065431da289f0cb4510040a0799 \ + --hash=sha256:905414e5ea6cdb78d8730f66335755152b46685fcb9fc2f2134024e3ea9e8dcc \ + --hash=sha256:ac0067e3c60737865ed72bb7416e02297d229d960902802d874c0e167128c809 \ + --hash=sha256:adf716a89c9cc1891ead79a861c427071ef59172f0e11967b00565a9547b3bd0 \ + --hash=sha256:bcfa99f5a82f5eaaf6e5cee5bfdca5a1670f5740aec1d93dae170645ed1a16b0 \ + --hash=sha256:cc94079ae6cbcea5ae194464a30f3223f075e06a0446f52bca9ddbeb6e9f412a \ + --hash=sha256:d5d9edfdc5a3a01d06062d677b121081629782edf0e05ca1be14f15bb947eeee \ + --hash=sha256:e269ab7a50bf0fa6fe6a88ea7dcc7a1079ae9450d9ab9b7730ac32916d55508b \ + --hash=sha256:e7fd33a3474cbe18fd5b5620784a0fa21fcae3e402b1806e29c6b450c7f61706 +pyblake2==1.1.1 \ + --hash=sha256:11c1d9d94cbaf5a4834aadf7f57bcb29eae1d174721269f242ca891f62cd6502 \ + --hash=sha256:427e7e91d644c3b9952e84145e211e4e3197fc4a3a0dbbd87b6da6b6cfa0a0df \ + --hash=sha256:4903d64e1a24f0cf2f8b8a1e0aaab12898951112b370ab9600651a4be4387c99 \ + --hash=sha256:6886b050521aed0293b2f67a3e1da74ea6080e4be19b57d9e1ae3d6ff10e223a \ + --hash=sha256:8cc4198ce61dddd33c9e66a216fc70be04fab66d02baa79e6bdebd83f16af57e \ + --hash=sha256:8ec8e9087d13c99b354ab6d8b4cadb1758633db5946ff95a6bc7ac538b6d7b3d \ + --hash=sha256:a785faf939810dca4aef525b6f59890fdcabdef09228cb30f4d77c3021707846 \ + --hash=sha256:e51b86e685045e2f8896d581b230effb1cc69f1134e11318f3607d98fa5ba95c \ + --hash=sha256:f51051de4eb27dc63c525a562daf9ead14e3e3583f096b9b90d3a360b5ca4995 +requests==2.18.4 \ + --hash=sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b \ + --hash=sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e +rlp==0.6.0 \ + --hash=sha256:87879a0ba1479b760cee98af165de2eee95258b261faa293199f60742be96f34 +six==1.11.0 \ + --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \ + --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb +trezor==0.9.1 \ + --hash=sha256:a481191011bade98f1e9f1201e7c72a83945050657bbc90dc4ac32dc8b8b46a4 +urllib3==1.22 \ + --hash=sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b \ + --hash=sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt index 572892bff..3c400d955 100644 --- a/contrib/deterministic-build/requirements.txt +++ b/contrib/deterministic-build/requirements.txt @@ -1,14 +1,57 @@ -certifi==2018.1.18 -chardet==3.0.4 -dnspython==1.15.0 -ecdsa==0.13 -idna==2.6 -jsonrpclib-pelix==0.3.1 -pbkdf2==1.3 -protobuf==3.5.2.post1 -pyaes==1.6.1 -PySocks==1.6.8 -qrcode==6.0 -requests==2.18.4 -six==1.11.0 -urllib3==1.22 +certifi==2018.1.18 \ + --hash=sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296 \ + --hash=sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +dnspython==1.15.0 \ + --hash=sha256:40f563e1f7a7b80dc5a4e76ad75c23da53d62f1e15e6e517293b04e1f84ead7c \ + --hash=sha256:861e6e58faa730f9845aaaa9c6c832851fbf89382ac52915a51f89c71accdd31 +ecdsa==0.13 \ + --hash=sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c \ + --hash=sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa +idna==2.6 \ + --hash=sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f \ + --hash=sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4 +jsonrpclib-pelix==0.3.1 \ + --hash=sha256:5417b1508d5a50ec64f6e5b88907f111155d52607b218ff3ba9a777afb2e49e3 \ + --hash=sha256:bd89a6093bc4d47dc8a096197aacb827359944a4533be5193f3845f57b9f91b4 +pbkdf2==1.3 \ + --hash=sha256:ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979 +protobuf==3.5.2.post1 \ + --hash=sha256:01ccd6d03449ae75b779fb5bf4ed62177d61afe3c5e6465ccf3f8b2e1a84afbe \ + --hash=sha256:1d92cc30b0b46cced33adde5853d920179eb5ea8eecdee9552502a7f29cc3f21 \ + --hash=sha256:242e4c7ae565267a8bc8b92d707177f915607ea4bd73244bec6cbf4a49b96661 \ + --hash=sha256:3b60685732bd0cbdc802dfcb6071efbcf5d927ce3127c13c33ea1a8efae3aa76 \ + --hash=sha256:3f655e1f99c3e14d56ca900af1b9a4715b691319a295cc38939d7f77eabd5e7c \ + --hash=sha256:560a38e692a69957a70ba0e5839aa67430efd63072bf91b0539dac19055694cd \ + --hash=sha256:5c1c8f6a0a68a874e3beff89255959dd80fad45870e96c88944a1b81a22dd5f5 \ + --hash=sha256:628a3bf0794a8b3cabb18db11eb67cc10e0cc6e5525d557ae7b682bb73fa2018 \ + --hash=sha256:7222d6616108b33ad6cbeff8117062a73c43cdc8fa8f64f6a322ebeb663e710e \ + --hash=sha256:76ef6ca3c50e4cfd044861586d5f1b352e0fe7f17f883df6c165bad5b4d0e10a \ + --hash=sha256:7c193e6964e752bd056735594826c5b03274ceb8f07349d3ae47d9766250ba96 \ + --hash=sha256:869e12bcfb5759e683f53ec1dd6155b7be034065431da289f0cb4510040a0799 \ + --hash=sha256:905414e5ea6cdb78d8730f66335755152b46685fcb9fc2f2134024e3ea9e8dcc \ + --hash=sha256:ac0067e3c60737865ed72bb7416e02297d229d960902802d874c0e167128c809 \ + --hash=sha256:adf716a89c9cc1891ead79a861c427071ef59172f0e11967b00565a9547b3bd0 \ + --hash=sha256:bcfa99f5a82f5eaaf6e5cee5bfdca5a1670f5740aec1d93dae170645ed1a16b0 \ + --hash=sha256:cc94079ae6cbcea5ae194464a30f3223f075e06a0446f52bca9ddbeb6e9f412a \ + --hash=sha256:d5d9edfdc5a3a01d06062d677b121081629782edf0e05ca1be14f15bb947eeee \ + --hash=sha256:e269ab7a50bf0fa6fe6a88ea7dcc7a1079ae9450d9ab9b7730ac32916d55508b \ + --hash=sha256:e7fd33a3474cbe18fd5b5620784a0fa21fcae3e402b1806e29c6b450c7f61706 +pyaes==1.6.1 \ + --hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f +PySocks==1.6.8 \ + --hash=sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672 +qrcode==6.0 \ + --hash=sha256:037b0db4c93f44586e37f84c3da3f763874fcac85b2974a69a98e399ac78e1bf \ + --hash=sha256:de4ffc15065e6ff20a551ad32b6b41264f3c75275675406ddfa8e3530d154be3 +requests==2.18.4 \ + --hash=sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b \ + --hash=sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e +six==1.11.0 \ + --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \ + --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb +urllib3==1.22 \ + --hash=sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b \ + --hash=sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f diff --git a/contrib/freeze_packages.sh b/contrib/freeze_packages.sh index 3471e528b..840825199 100755 --- a/contrib/freeze_packages.sh +++ b/contrib/freeze_packages.sh @@ -5,18 +5,30 @@ venv_dir=~/.electrum-venv contrib=$(dirname "$0") which virtualenv > /dev/null 2>&1 || { echo "Please install virtualenv" && exit 1; } +python3 -m hashin -h > /dev/null 2>&1 || { python3 -m pip install hashin; } +other_python=$(which python3) for i in '' '-hw' '-binaries'; do - rm "$venv_dir" -rf + rm -rf "$venv_dir" virtualenv -p $(which python3) $venv_dir source $venv_dir/bin/activate - echo "Installing $i dependencies" + echo "Installing $m dependencies" python -m pip install -r $contrib/requirements/requirements${i}.txt --upgrade - pip freeze | sed '/^Electrum/ d' > $contrib/deterministic-build/requirements${i}.txt + echo "OK." + + requirements=$(pip freeze) + echo "Generating package hashes..." + + echo "$requirements" | while IFS= read -r requirement ; do + echo -e "\r Hashing $requirement..." + $other_python -m hashin -r $contrib/deterministic-build/requirements${i}.txt ${requirement} + done + + echo "OK." done echo "Done. Updated requirements" From 6afe71fe0e0b0adceae56092e2ec32b80b42fc85 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 31 Mar 2018 18:12:50 +0200 Subject: [PATCH 146/174] Follow-up 3c28e3491 --- contrib/build-osx/make_osx | 6 +++--- contrib/build-wine/build-electrum-git.sh | 4 ++-- contrib/build-wine/prepare-wine.sh | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/build-osx/make_osx b/contrib/build-osx/make_osx index a7aa6cb3d..5810a6ac0 100755 --- a/contrib/build-osx/make_osx +++ b/contrib/build-osx/make_osx @@ -56,12 +56,12 @@ tar xz --directory $BUILDDIR cp $BUILDDIR/libusb/1.0.22/lib/libusb-1.0.dylib contrib/build-osx info "Installing requirements..." -python3 -m pip install -Ir --require-hashes ./contrib/deterministic-build/requirements.txt --user && \ -python3 -m pip install -Ir --require-hashes ./contrib/deterministic-build/requirements-binaries.txt --user || \ +python3 -m pip install --require-hashes -Ir ./contrib/deterministic-build/requirements.txt --user && \ +python3 -m pip install --require-hashes -Ir ./contrib/deterministic-build/requirements-binaries.txt --user || \ fail "Could not install requirements" info "Installing hardware wallet requirements..." -python3 -m pip install -Ir --require-hashes ./contrib/deterministic-build/requirements-hw.txt --user || \ +python3 -m pip install --require-hashes -Ir ./contrib/deterministic-build/requirements-hw.txt --user || \ fail "Could not install hardware wallet requirements" info "Building $PACKAGE..." diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh index 3463d58f8..1f974dfc8 100755 --- a/contrib/build-wine/build-electrum-git.sh +++ b/contrib/build-wine/build-electrum-git.sh @@ -56,9 +56,9 @@ cp -r electrum-locale/locale $WINEPREFIX/drive_c/electrum/lib/ cp electrum-icons/icons_rc.py $WINEPREFIX/drive_c/electrum/gui/qt/ # Install frozen dependencies -$PYTHON -m pip install -r --require-hashes ../../deterministic-build/requirements.txt +$PYTHON -m pip install --require-hashes -r ../../deterministic-build/requirements.txt -$PYTHON -m pip install -r --require-hashes ../../deterministic-build/requirements-hw.txt +$PYTHON -m pip install --require-hashes -r ../../deterministic-build/requirements-hw.txt pushd $WINEPREFIX/drive_c/electrum $PYTHON setup.py install diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index e20fd197a..ea63b6913 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -112,7 +112,7 @@ $PYTHON -m pip install pywin32-ctypes==0.1.2 # install PySocks $PYTHON -m pip install win_inet_pton==1.0.1 -$PYTHON -m pip install -r --require-hashes $here/../deterministic-build/requirements-binaries.txt +$PYTHON -m pip install --require-hashes -r $here/../deterministic-build/requirements-binaries.txt # Install PyInstaller $PYTHON -m pip install https://github.com/ecdsa/pyinstaller/archive/fix_2952.zip From ae80fb4f971c0c42277127209be5ef0eba791eae Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 31 Mar 2018 18:22:23 +0200 Subject: [PATCH 147/174] Follow-up 6afe71fe --- contrib/build-osx/make_osx | 6 +++--- contrib/build-wine/build-electrum-git.sh | 4 ++-- contrib/build-wine/prepare-wine.sh | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/build-osx/make_osx b/contrib/build-osx/make_osx index 5810a6ac0..60b160afa 100755 --- a/contrib/build-osx/make_osx +++ b/contrib/build-osx/make_osx @@ -56,12 +56,12 @@ tar xz --directory $BUILDDIR cp $BUILDDIR/libusb/1.0.22/lib/libusb-1.0.dylib contrib/build-osx info "Installing requirements..." -python3 -m pip install --require-hashes -Ir ./contrib/deterministic-build/requirements.txt --user && \ -python3 -m pip install --require-hashes -Ir ./contrib/deterministic-build/requirements-binaries.txt --user || \ +python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user && \ +python3 -m pip install -Ir ./contrib/deterministic-build/requirements-binaries.txt --user || \ fail "Could not install requirements" info "Installing hardware wallet requirements..." -python3 -m pip install --require-hashes -Ir ./contrib/deterministic-build/requirements-hw.txt --user || \ +python3 -m pip install -Ir ./contrib/deterministic-build/requirements-hw.txt --user || \ fail "Could not install hardware wallet requirements" info "Building $PACKAGE..." diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh index 1f974dfc8..c3b92d62c 100755 --- a/contrib/build-wine/build-electrum-git.sh +++ b/contrib/build-wine/build-electrum-git.sh @@ -56,9 +56,9 @@ cp -r electrum-locale/locale $WINEPREFIX/drive_c/electrum/lib/ cp electrum-icons/icons_rc.py $WINEPREFIX/drive_c/electrum/gui/qt/ # Install frozen dependencies -$PYTHON -m pip install --require-hashes -r ../../deterministic-build/requirements.txt +$PYTHON -m pip install -r ../../deterministic-build/requirements.txt -$PYTHON -m pip install --require-hashes -r ../../deterministic-build/requirements-hw.txt +$PYTHON -m pip install -r ../../deterministic-build/requirements-hw.txt pushd $WINEPREFIX/drive_c/electrum $PYTHON setup.py install diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index ea63b6913..89dbca440 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -112,7 +112,7 @@ $PYTHON -m pip install pywin32-ctypes==0.1.2 # install PySocks $PYTHON -m pip install win_inet_pton==1.0.1 -$PYTHON -m pip install --require-hashes -r $here/../deterministic-build/requirements-binaries.txt +$PYTHON -m pip install $here/../deterministic-build/requirements-binaries.txt # Install PyInstaller $PYTHON -m pip install https://github.com/ecdsa/pyinstaller/archive/fix_2952.zip From 14aa7c0a3b13ddb8ad3328c25c24d171c195bbcf Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 31 Mar 2018 18:33:45 +0200 Subject: [PATCH 148/174] Follow-up ae80fb4f --- contrib/build-wine/prepare-wine.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index 89dbca440..27c34e7f4 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -112,7 +112,7 @@ $PYTHON -m pip install pywin32-ctypes==0.1.2 # install PySocks $PYTHON -m pip install win_inet_pton==1.0.1 -$PYTHON -m pip install $here/../deterministic-build/requirements-binaries.txt +$PYTHON -m pip install -r $here/../deterministic-build/requirements-binaries.txt # Install PyInstaller $PYTHON -m pip install https://github.com/ecdsa/pyinstaller/archive/fix_2952.zip From 70d827b984abfbaf2f01934aaf51412cf9682608 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 31 Mar 2018 22:19:09 +0200 Subject: [PATCH 149/174] Add a script that finds dependencies for other OSs This is used to make sure we also freeze versions for packages that will only be used on Windows or OS X, while the freezing script is most likely only be run on Linux. --- .../find_restricted_dependencies.py | 38 +++++++++++++++++++ contrib/deterministic-build/requirements.txt | 3 ++ contrib/freeze_packages.sh | 7 +++- 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100755 contrib/deterministic-build/find_restricted_dependencies.py diff --git a/contrib/deterministic-build/find_restricted_dependencies.py b/contrib/deterministic-build/find_restricted_dependencies.py new file mode 100755 index 000000000..846a0c75d --- /dev/null +++ b/contrib/deterministic-build/find_restricted_dependencies.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import sys + +import requests + + +def check_restriction(p, r): + # See: https://www.python.org/dev/peps/pep-0496/ + # Hopefully we don't need to parse the whole microlanguage + if "extra" in r and "[" not in p: + return False + for marker in ["os_name", "platform_release", "sys_platform", "platform_system"]: + if marker in r: + return True + + +for p in sys.stdin.read().split(): + p = p.strip() + if not p: + continue + assert "==" in p, "This script expects a list of packages with pinned version, e.g. package==1.2.3, not {}".format(p) + p, v = p.rsplit("==", 1) + try: + data = requests.get("https://pypi.org/pypi/{}/{}/json".format(p, v)).json()["info"] + except ValueError: + raise BaseException("Package could not be found: {}=={}".format(p, v)) + try: + for r in data["requires_dist"]: + if ";" not in r: + continue + d, restricted = r.split(";", 1) + if check_restriction(d, restricted): + print(d, sep=" ") + print("Installing {} from {} although it is only needed for {}".format(d, p, restricted), file=sys.stderr) + except TypeError: + # Has no dependencies at all + continue + diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt index 3c400d955..58b0e83dc 100644 --- a/contrib/deterministic-build/requirements.txt +++ b/contrib/deterministic-build/requirements.txt @@ -55,3 +55,6 @@ six==1.11.0 \ urllib3==1.22 \ --hash=sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b \ --hash=sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f +colorama==0.3.9 \ + --hash=sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda \ + --hash=sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1 diff --git a/contrib/freeze_packages.sh b/contrib/freeze_packages.sh index 840825199..623eddaad 100755 --- a/contrib/freeze_packages.sh +++ b/contrib/freeze_packages.sh @@ -21,9 +21,14 @@ for i in '' '-hw' '-binaries'; do echo "OK." requirements=$(pip freeze) + restricted=$(echo $requirements | $other_python ./deterministic-build/find_restricted_dependencies.py) + requirements="$requirements $restricted" + echo "Generating package hashes..." + rm $contrib/deterministic-build/requirements${i}.txt + touch $contrib/deterministic-build/requirements${i}.txt - echo "$requirements" | while IFS= read -r requirement ; do + for requirement in $requirements; do echo -e "\r Hashing $requirement..." $other_python -m hashin -r $contrib/deterministic-build/requirements${i}.txt ${requirement} done From 08e7a5f05f567a9844aa4ca79c8aad62fa2c9290 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 1 Apr 2018 11:18:15 +0200 Subject: [PATCH 150/174] remove pyjnius thread monkey patching; now in pyjnius --- electrum | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/electrum b/electrum index 783d5c13b..4c4bf27cc 100755 --- a/electrum +++ b/electrum @@ -26,21 +26,6 @@ import os import sys -# from https://gist.github.com/tito/09c42fb4767721dc323d -import threading -try: - import jnius -except: - jnius = None -if jnius: - orig_thread_run = threading.Thread.run - def thread_check_run(*args, **kwargs): - try: - return orig_thread_run(*args, **kwargs) - finally: - jnius.detach() - threading.Thread.run = thread_check_run - script_dir = os.path.dirname(os.path.realpath(__file__)) is_bundle = getattr(sys, 'frozen', False) is_local = not is_bundle and os.path.exists(os.path.join(script_dir, "electrum.desktop")) From 1eae16aa3e78a435d9de1de4f85bb4929cda6d5e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 1 Apr 2018 15:50:24 +0200 Subject: [PATCH 151/174] tests: class TestCaseForTestnet --- lib/tests/__init__.py | 16 ++++++++++++++++ lib/tests/test_bitcoin.py | 16 ++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/tests/__init__.py b/lib/tests/__init__.py index e69de29bb..c6e8240fc 100644 --- a/lib/tests/__init__.py +++ b/lib/tests/__init__.py @@ -0,0 +1,16 @@ +import unittest + +from lib import constants + + +class TestCaseForTestnet(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + constants.set_testnet() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + constants.set_mainnet() diff --git a/lib/tests/test_bitcoin.py b/lib/tests/test_bitcoin.py index a5f734357..712b31da5 100644 --- a/lib/tests/test_bitcoin.py +++ b/lib/tests/test_bitcoin.py @@ -13,7 +13,9 @@ from lib.bitcoin import ( is_b58_address, address_to_scripthash, is_minikey, is_compressed, is_xpub, xpub_type, is_xprv, is_bip32_derivation, seed_type) from lib.util import bfh -from lib import constants + +from . import TestCaseForTestnet + try: import ecdsa @@ -164,17 +166,7 @@ class Test_bitcoin(unittest.TestCase): self.assertEqual(address_to_script('3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387') -class Test_bitcoin_testnet(unittest.TestCase): - - @classmethod - def setUpClass(cls): - super().setUpClass() - constants.set_testnet() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - constants.set_mainnet() +class Test_bitcoin_testnet(TestCaseForTestnet): def test_address_to_script(self): # bech32 native segwit From 9de4d19c5a183d8ca61edddb456218f0ed0dbfc3 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 1 Apr 2018 16:44:59 +0200 Subject: [PATCH 152/174] tests: refactoring in test_wallet_vertical.py --- lib/tests/test_wallet_vertical.py | 85 +++++++++++++++++-------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/lib/tests/test_wallet_vertical.py b/lib/tests/test_wallet_vertical.py index a29690ea9..923d82af9 100644 --- a/lib/tests/test_wallet_vertical.py +++ b/lib/tests/test_wallet_vertical.py @@ -9,32 +9,35 @@ import lib.wallet as wallet from plugins.trustedcoin import trustedcoin -# TODO passphrase/seed_extension -class TestWalletKeystoreAddressIntegrity(unittest.TestCase): +class WalletIntegrityHelper: gap_limit = 1 # make tests run faster - def _check_seeded_keystore_sanity(self, ks): - self.assertTrue (ks.is_deterministic()) - self.assertFalse(ks.is_watching_only()) - self.assertFalse(ks.can_import()) - self.assertTrue (ks.has_seed()) - - def _check_xpub_keystore_sanity(self, ks): - self.assertTrue (ks.is_deterministic()) - self.assertTrue (ks.is_watching_only()) - self.assertFalse(ks.can_import()) - self.assertFalse(ks.has_seed()) - - def _create_standard_wallet(self, ks): + @classmethod + def check_seeded_keystore_sanity(cls, test_obj, ks): + test_obj.assertTrue(ks.is_deterministic()) + test_obj.assertFalse(ks.is_watching_only()) + test_obj.assertFalse(ks.can_import()) + test_obj.assertTrue(ks.has_seed()) + + @classmethod + def check_xpub_keystore_sanity(cls, test_obj, ks): + test_obj.assertTrue(ks.is_deterministic()) + test_obj.assertTrue(ks.is_watching_only()) + test_obj.assertFalse(ks.can_import()) + test_obj.assertFalse(ks.has_seed()) + + @classmethod + def create_standard_wallet(cls, ks): store = storage.WalletStorage('if_this_exists_mocking_failed_648151893') store.put('keystore', ks.dump()) - store.put('gap_limit', self.gap_limit) + store.put('gap_limit', cls.gap_limit) w = wallet.Standard_Wallet(store) w.synchronize() return w - def _create_multisig_wallet(self, ks1, ks2, ks3=None): + @classmethod + def create_multisig_wallet(cls, ks1, ks2, ks3=None): """Creates a 2-of-2 or 2-of-3 multisig wallet.""" store = storage.WalletStorage('if_this_exists_mocking_failed_648151893') store.put('x%d/' % 1, ks1.dump()) @@ -45,11 +48,15 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): multisig_type = "%dof%d" % (2, 3) store.put('x%d/' % 3, ks3.dump()) store.put('wallet_type', multisig_type) - store.put('gap_limit', self.gap_limit) + store.put('gap_limit', cls.gap_limit) w = wallet.Multisig_Wallet(store) w.synchronize() return w + +# TODO passphrase/seed_extension +class TestWalletKeystoreAddressIntegrity(unittest.TestCase): + @mock.patch.object(storage.WalletStorage, '_write') def test_electrum_seed_standard(self, mock_write): seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song' @@ -57,13 +64,13 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): ks = keystore.from_seed(seed_words, '', False) - self._check_seeded_keystore_sanity(ks) + WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks) self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore)) self.assertEqual(ks.xprv, 'xprv9s21ZrQH143K32jECVM729vWgGq4mUDJCk1ozqAStTphzQtCTuoFmFafNoG1g55iCnBTXUzz3zWnDb5CVLGiFvmaZjuazHDL8a81cPQ8KL6') self.assertEqual(ks.xpub, 'xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52CwBdDWroaZf8U') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2pkh') self.assertEqual(w.get_receiving_addresses()[0], '1NNkttn1YvVGdqBW4PR6zvc3Zx3H5owKRf') @@ -76,13 +83,13 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): ks = keystore.from_seed(seed_words, '', False) - self._check_seeded_keystore_sanity(ks) + WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks) self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore)) self.assertEqual(ks.xprv, 'zprvAZswDvNeJeha8qZ8g7efN3FXYVJLaEUsE9TW6qXDEbVe74AZ75c2sZFZXPNFzxnhChDQ89oC8C5AjWwHmH1HeRKE1c4kKBQAmjUDdKDUZw2') self.assertEqual(ks.xpub, 'zpub6nsHdRuY92FsMKdbn9BfjBCG6X8pyhCibNP6uDvpnw2cyrVhecvHRMa3Ne8kdJZxjxgwnpbHLkcR4bfnhHy6auHPJyDTQ3kianeuVLdkCYQ') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2wpkh') self.assertEqual(w.get_receiving_addresses()[0], 'bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af') @@ -95,12 +102,12 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): ks = keystore.from_seed(seed_words, '', False) - self._check_seeded_keystore_sanity(ks) + WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks) self.assertTrue(isinstance(ks, keystore.Old_KeyStore)) self.assertEqual(ks.mpk, 'e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442b3') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2pkh') self.assertEqual(w.get_receiving_addresses()[0], '1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo') @@ -130,10 +137,10 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): 'x2/': {'xpub': xpub2}}) xpub3 = trustedcoin.make_xpub(trustedcoin.get_signing_xpub(), long_user_id) ks3 = keystore.from_xpub(xpub3) - self._check_xpub_keystore_sanity(ks3) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks3) self.assertTrue(isinstance(ks3, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2, ks3) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2, ks3) self.assertEqual(w.txin_type, 'p2sh') self.assertEqual(w.get_receiving_addresses()[0], '35L8XmCDoEBKeaWRjvmZvoZvhp8BXMMMPV') @@ -151,7 +158,7 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(ks.xprv, 'xprv9zGLcNEb3cHUKizLVBz6RYeE9bEZAVPjH2pD1DEzCnPcsemWc3d3xTao8sfhfUmDLMq6e3RcEMEvJG1Et8dvfL8DV4h7mwm9J6AJsW9WXQD') self.assertEqual(ks.xpub, 'xpub6DFh1smUsyqmYD4obDX6ngaxhd53Zx7aeFjoobebm7vbkT6f9awJWFuGzBT9FQJEWFBL7UyhMXtYzRcwDuVbcxtv9Ce2W9eMm4KXLdvdbjv') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2pkh') self.assertEqual(w.get_receiving_addresses()[0], '16j7Dqk3Z9DdTdBtHcCVLaNQy9MTgywUUo') @@ -169,7 +176,7 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(ks.xprv, 'yprvAJEYHeNEPcyBoQYM7sGCxDiNCTX65u4ANgZuSGTrKN5YCC9MP84SBayrgaMyZV7zvkHrr3HVPTK853s2SPk4EttPazBZBmz6QfDkXeE8Zr7') self.assertEqual(ks.xpub, 'ypub6XDth9u8DzXV1tcpDtoDKMf6kVMaVMn1juVWEesTshcX4zUVvfNgjPJLXrD9N7AdTLnbHFL64KmBn3SNaTe69iZYbYCqLCCNPZKbLz9niQ4') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2wpkh-p2sh') self.assertEqual(w.get_receiving_addresses()[0], '35ohQTdNykjkF1Mn9nAVEFjupyAtsPAK1W') @@ -188,7 +195,7 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(ks.xprv, 'zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE') self.assertEqual(ks.xpub, 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2wpkh') self.assertEqual(w.get_receiving_addresses()[0], 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu') @@ -200,17 +207,17 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(bitcoin.seed_type(seed_words), 'standard') ks1 = keystore.from_seed(seed_words, '', True) - self._check_seeded_keystore_sanity(ks1) + WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks1) self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore)) self.assertEqual(ks1.xprv, 'xprv9s21ZrQH143K3t9vo23J3hajRbzvkRLJ6Y1zFrUFAfU3t8oooMPfb7f87cn5KntgqZs5nipZkCiBFo5ZtaSD2eDo7j7CMuFV8Zu6GYLTpY6') self.assertEqual(ks1.xpub, 'xpub661MyMwAqRbcGNEPu3aJQqXTydqR9t49Tkwb4Esrj112kw8xLthv8uybxvaki4Ygt9xiwZUQGeFTG7T2TUzR3eA4Zp3aq5RXsABHFBUrq4c') # electrum seed: ghost into match ivory badge robot record tackle radar elbow traffic loud ks2 = keystore.from_xpub('xpub661MyMwAqRbcGfCPEkkyo5WmcrhTq8mi3xuBS7VEZ3LYvsgY1cCFDbenT33bdD12axvrmXhuX3xkAbKci3yZY9ZEk8vhLic7KNhLjqdh5ec') - self._check_xpub_keystore_sanity(ks2) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2) self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2) self.assertEqual(w.txin_type, 'p2sh') self.assertEqual(w.get_receiving_addresses()[0], '32ji3QkAgXNz6oFoRfakyD3ys1XXiERQYN') @@ -222,17 +229,17 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(bitcoin.seed_type(seed_words), 'segwit') ks1 = keystore.from_seed(seed_words, '', True) - self._check_seeded_keystore_sanity(ks1) + WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks1) self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore)) self.assertEqual(ks1.xprv, 'ZprvAjxLRqPiDfPDxXrm8JvcoCGRAW6xUtktucG6AMtdzaEbTEJN8qcECvujfhtDU3jLJ9g3Dr3Gz5m1ypfMs8iSUh62gWyHZ73bYLRWyeHf6y4') self.assertEqual(ks1.xpub, 'Zpub6xwgqLvc42wXB1wEELTdALD9iXwStMUkGqBgxkJFYumaL2dWgNvUkjEDWyDFZD3fZuDWDzd1KQJ4NwVHS7hs6H6QkpNYSShfNiUZsgMdtNg') # electrum seed: hedgehog sunset update estate number jungle amount piano friend donate upper wool ks2 = keystore.from_xpub('Zpub6y4oYeETXAbzLNg45wcFDGwEG3vpgsyMJybiAfi2pJtNF3i3fJVxK2BeZJaw7VeKZm192QHvXP3uHDNpNmNDbQft9FiMzkKUhNXQafUMYUY') - self._check_xpub_keystore_sanity(ks2) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2) self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2) self.assertEqual(w.txin_type, 'p2wsh') self.assertEqual(w.get_receiving_addresses()[0], 'bc1qvzezdcv6vs5h45ugkavp896e0nde5c5lg5h0fwe2xyfhnpkxq6gq7pnwlc') @@ -251,10 +258,10 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): # bip39 seed: tray machine cook badge night page project uncover ritual toward person enact # der: m/45'/0 ks2 = keystore.from_xpub('xpub6B26nSWddbWv7J3qQn9FbwPPQktSBdPQfLfHhRK4375QoZq8fvM8rQey1koGSTxC5xVoMzNMaBETMUmCqmXzjc8HyAbN7LqrvE4ovGRwNGg') - self._check_xpub_keystore_sanity(ks2) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2) self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2) self.assertEqual(w.txin_type, 'p2sh') self.assertEqual(w.get_receiving_addresses()[0], '3JPTQ2nitVxXBJ1yhMeDwH6q417UifE3bN') @@ -272,10 +279,10 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): # bip39 seed: slab mixture skin evoke harsh tattoo rare crew sphere extend balcony frost # der: m/49'/0'/0' ks2 = keystore.from_xpub('Ypub6iNDhL4WWq5kFZcdFqHHwX4YTH4rYGp8xbndpRrY7WNZFFRfogSrL7wRTajmVHgR46AT1cqUG1mrcRd7h1WXwBsgX2QvT3zFbBCDiSDLkau') - self._check_xpub_keystore_sanity(ks2) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2) self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2) self.assertEqual(w.txin_type, 'p2wsh-p2sh') self.assertEqual(w.get_receiving_addresses()[0], '35LeC45QgCVeRor1tJD6LiDgPbybBXisns') From 192288c02585c4fcc3f3a8eb0fccab506152943c Mon Sep 17 00:00:00 2001 From: fivepiece Date: Sun, 1 Apr 2018 17:48:54 +0300 Subject: [PATCH 153/174] change testnet p2wsh-p2sh prefix to 0x024289ef to reflect Upub (#4210) * change testnet p2wsh-p2sh prefix to 0x024289ef to reflect Upub * add testnet p2sh-p2wsh 2of2 multisig test --- lib/constants.py | 2 +- lib/tests/test_wallet_vertical.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/constants.py b/lib/constants.py index ec35cbe3e..0eed179c0 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -87,7 +87,7 @@ class BitcoinTestnet: XPUB_HEADERS = { 'standard': 0x043587cf, # tpub 'p2wpkh-p2sh': 0x044a5262, # upub - 'p2wsh-p2sh': 0x024285ef, # Upub + 'p2wsh-p2sh': 0x024289ef, # Upub 'p2wpkh': 0x045f1cf6, # vpub 'p2wsh': 0x02575483, # Vpub } diff --git a/lib/tests/test_wallet_vertical.py b/lib/tests/test_wallet_vertical.py index 923d82af9..712eafd67 100644 --- a/lib/tests/test_wallet_vertical.py +++ b/lib/tests/test_wallet_vertical.py @@ -5,6 +5,7 @@ import lib.bitcoin as bitcoin import lib.keystore as keystore import lib.storage as storage import lib.wallet as wallet +from lib import constants from plugins.trustedcoin import trustedcoin @@ -287,3 +288,26 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(w.get_receiving_addresses()[0], '35LeC45QgCVeRor1tJD6LiDgPbybBXisns') self.assertEqual(w.get_change_addresses()[0], '39RhtDchc6igmx5tyoimhojFL1ZbQBrXa6') + + @mock.patch.object(storage.WalletStorage, '_write') + def test_bip39_multisig_seed_p2sh_segwit_testnet(self, mock_write): + constants.set_testnet() + # bip39 seed: finish seminar arrange erosion sunny coil insane together pretty lunch lunch rose + # der: m/49'/1'/0' + # NOTE: there is currently no bip43 standard derivation path for p2wsh-p2sh + ks1 = keystore.from_xprv('Uprv9BEixD3As2LK5h6G2SNT3cTqbZpsWYPceKTSuVAm1yuSybxSvQz2MV1o8cHTtctQmj4HAenb3eh5YJv4YRZjv35i8fofVnNbs4Dd2B4i5je') + self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore)) + self.assertEqual(ks1.xpub, 'Upub5QE5Mia4hPtcJBAj8TuTQkQa9bfMv17U1YP3hsaNaKSRrQHbTxJGuHLGyv3MbKZixuPyjfXGUdbTjE4KwyFcX8YD7PX5ybTDbP11UT8UpZR') + + # bip39 seed: square page wood spy oil story rebel give milk screen slide shuffle + # der: m/49'/1'/0' + ks2 = keystore.from_xpub('Upub5QRzUGRJuWJe5MxGzwgQAeyJjzcdGTXkkq77w6EfBkCyf5iWppSaZ4caY2MgWcU9LP4a4uE5apUFN4wLoENoe9tpu26mrUxeGsH84dN3JFh') + self._check_xpub_keystore_sanity(ks2) + self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) + + w = self._create_multisig_wallet(ks1, ks2) + self.assertEqual(w.txin_type, 'p2wsh-p2sh') + + self.assertEqual(w.get_receiving_addresses()[0], '2MzsfTfTGomPRne6TkctMmoDj6LwmVkDrMt') + self.assertEqual(w.get_change_addresses()[0], '2NFp9w8tbYYP9Ze2xQpeYBJQjx3gbXymHX7') + constants.set_mainnet() From ff57c198b4f7acfb981b95bac6e69e769c0a75c4 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 1 Apr 2018 16:52:24 +0200 Subject: [PATCH 154/174] fix prev --- lib/tests/test_wallet_vertical.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/tests/test_wallet_vertical.py b/lib/tests/test_wallet_vertical.py index 712eafd67..355688b84 100644 --- a/lib/tests/test_wallet_vertical.py +++ b/lib/tests/test_wallet_vertical.py @@ -9,6 +9,8 @@ from lib import constants from plugins.trustedcoin import trustedcoin +from . import TestCaseForTestnet + class WalletIntegrityHelper: @@ -56,7 +58,7 @@ class WalletIntegrityHelper: # TODO passphrase/seed_extension -class TestWalletKeystoreAddressIntegrity(unittest.TestCase): +class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase): @mock.patch.object(storage.WalletStorage, '_write') def test_electrum_seed_standard(self, mock_write): @@ -289,9 +291,11 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(w.get_receiving_addresses()[0], '35LeC45QgCVeRor1tJD6LiDgPbybBXisns') self.assertEqual(w.get_change_addresses()[0], '39RhtDchc6igmx5tyoimhojFL1ZbQBrXa6') + +class TestWalletKeystoreAddressIntegrityForTestnet(TestCaseForTestnet): + @mock.patch.object(storage.WalletStorage, '_write') def test_bip39_multisig_seed_p2sh_segwit_testnet(self, mock_write): - constants.set_testnet() # bip39 seed: finish seminar arrange erosion sunny coil insane together pretty lunch lunch rose # der: m/49'/1'/0' # NOTE: there is currently no bip43 standard derivation path for p2wsh-p2sh @@ -302,12 +306,11 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): # bip39 seed: square page wood spy oil story rebel give milk screen slide shuffle # der: m/49'/1'/0' ks2 = keystore.from_xpub('Upub5QRzUGRJuWJe5MxGzwgQAeyJjzcdGTXkkq77w6EfBkCyf5iWppSaZ4caY2MgWcU9LP4a4uE5apUFN4wLoENoe9tpu26mrUxeGsH84dN3JFh') - self._check_xpub_keystore_sanity(ks2) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2) self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2) self.assertEqual(w.txin_type, 'p2wsh-p2sh') self.assertEqual(w.get_receiving_addresses()[0], '2MzsfTfTGomPRne6TkctMmoDj6LwmVkDrMt') self.assertEqual(w.get_change_addresses()[0], '2NFp9w8tbYYP9Ze2xQpeYBJQjx3gbXymHX7') - constants.set_mainnet() From 58273fc841c8ca80698cdafe790f8152f264183c Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 1 Apr 2018 16:56:17 +0200 Subject: [PATCH 155/174] tests: xpub version bytes --- lib/tests/test_bitcoin.py | 76 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/lib/tests/test_bitcoin.py b/lib/tests/test_bitcoin.py index 712b31da5..d19ea6697 100644 --- a/lib/tests/test_bitcoin.py +++ b/lib/tests/test_bitcoin.py @@ -11,8 +11,9 @@ from lib.bitcoin import ( var_int, op_push, address_to_script, regenerate_key, verify_message, deserialize_privkey, serialize_privkey, is_segwit_address, is_b58_address, address_to_scripthash, is_minikey, is_compressed, is_xpub, - xpub_type, is_xprv, is_bip32_derivation, seed_type) + xpub_type, is_xprv, is_bip32_derivation, seed_type, EncodeBase58Check) from lib.util import bfh +from lib import constants from . import TestCaseForTestnet @@ -259,6 +260,79 @@ class Test_xprv_xpub(unittest.TestCase): self.assertFalse(is_bip32_derivation("")) self.assertFalse(is_bip32_derivation("m/q8462")) + def test_version_bytes(self): + xprv_headers_b58 = { + 'standard': 'xprv', + 'p2wpkh-p2sh': 'yprv', + 'p2wsh-p2sh': 'Yprv', + 'p2wpkh': 'zprv', + 'p2wsh': 'Zprv', + } + xpub_headers_b58 = { + 'standard': 'xpub', + 'p2wpkh-p2sh': 'ypub', + 'p2wsh-p2sh': 'Ypub', + 'p2wpkh': 'zpub', + 'p2wsh': 'Zpub', + } + for xtype, xkey_header_bytes in constants.net.XPRV_HEADERS.items(): + xkey_header_bytes = bfh("%08x" % xkey_header_bytes) + xkey_bytes = xkey_header_bytes + bytes([0] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype])) + + xkey_bytes = xkey_header_bytes + bytes([255] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype])) + + for xtype, xkey_header_bytes in constants.net.XPUB_HEADERS.items(): + xkey_header_bytes = bfh("%08x" % xkey_header_bytes) + xkey_bytes = xkey_header_bytes + bytes([0] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype])) + + xkey_bytes = xkey_header_bytes + bytes([255] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype])) + + +class Test_xprv_xpub_testnet(TestCaseForTestnet): + + def test_version_bytes(self): + xprv_headers_b58 = { + 'standard': 'tprv', + 'p2wpkh-p2sh': 'uprv', + 'p2wsh-p2sh': 'Uprv', + 'p2wpkh': 'vprv', + 'p2wsh': 'Vprv', + } + xpub_headers_b58 = { + 'standard': 'tpub', + 'p2wpkh-p2sh': 'upub', + 'p2wsh-p2sh': 'Upub', + 'p2wpkh': 'vpub', + 'p2wsh': 'Vpub', + } + for xtype, xkey_header_bytes in constants.net.XPRV_HEADERS.items(): + xkey_header_bytes = bfh("%08x" % xkey_header_bytes) + xkey_bytes = xkey_header_bytes + bytes([0] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype])) + + xkey_bytes = xkey_header_bytes + bytes([255] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype])) + + for xtype, xkey_header_bytes in constants.net.XPUB_HEADERS.items(): + xkey_header_bytes = bfh("%08x" % xkey_header_bytes) + xkey_bytes = xkey_header_bytes + bytes([0] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype])) + + xkey_bytes = xkey_header_bytes + bytes([255] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype])) + class Test_keyImport(unittest.TestCase): From 831dda0fcb844d586b7a4eb47811ced7e298e299 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sun, 1 Apr 2018 17:52:31 +0200 Subject: [PATCH 156/174] Wine build: Revert to Python 3.5 and download if file was changed --- contrib/build-wine/prepare-wine.sh | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index 27c34e7f4..7c3802818 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -13,7 +13,7 @@ LIBUSB_FILENAME=libusb-1.0.22.7z LIBUSB_URL=https://prdownloads.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.22/$LIBUSB_FILENAME?download LIBUSB_SHA256=671f1a420757b4480e7fadc8313d6fb3cbb75ca00934c417c1efa6e77fb8779b -PYTHON_VERSION=3.6.5 +PYTHON_VERSION=3.5.4 ## These settings probably don't need change export WINEPREFIX=/opt/wine64 @@ -97,8 +97,8 @@ KEYSERVER_PYTHON_DEV="hkp://pool.sks-keyservers.net" retry gpg --no-default-keyring --keyring $KEYRING_PYTHON_DEV --keyserver $KEYSERVER_PYTHON_DEV --recv-keys $KEYLIST_PYTHON_DEV for msifile in core dev exe lib pip tools; do echo "Installing $msifile..." - wget -nc "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi" - wget -nc "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi.asc" + wget -N -c "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi" + wget -N -c "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi.asc" verify_signature "${msifile}.msi.asc" $KEYRING_PYTHON_DEV wine msiexec /i "${msifile}.msi" /qb TARGETDIR=C:/python$PYTHON_VERSION done @@ -136,11 +136,6 @@ verify_hash $LIBUSB_FILENAME "$LIBUSB_SHA256" cp libusb/MS32/dll/libusb-1.0.dll $WINEPREFIX/drive_c/python$PYTHON_VERSION/ -# Install UPX -#wget -O upx.zip "https://downloads.sourceforge.net/project/upx/upx/3.08/upx308w.zip" -#unzip -o upx.zip -#cp upx*/upx.exe . - # add dlls needed for pyinstaller: cp $WINEPREFIX/drive_c/python$PYTHON_VERSION/Lib/site-packages/PyQt5/Qt/bin/* $WINEPREFIX/drive_c/python$PYTHON_VERSION/ From c7c43d2336376e996b510670d7f5581df0328f62 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sun, 1 Apr 2018 18:03:44 +0200 Subject: [PATCH 157/174] Change Python version in pyinstaller spec file --- contrib/build-wine/build-electrum-git.sh | 2 +- contrib/build-wine/deterministic.spec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh index c3b92d62c..2fd6ea007 100755 --- a/contrib/build-wine/build-electrum-git.sh +++ b/contrib/build-wine/build-electrum-git.sh @@ -1,7 +1,7 @@ #!/bin/bash NAME_ROOT=electrum -PYTHON_VERSION=3.6.5 +PYTHON_VERSION=3.5.4 # These settings probably don't need any change export WINEPREFIX=/opt/wine64 diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec index 5b08dbf34..caaec5088 100644 --- a/contrib/build-wine/deterministic.spec +++ b/contrib/build-wine/deterministic.spec @@ -10,7 +10,7 @@ for i, x in enumerate(sys.argv): else: raise BaseException('no name') -PYTHON_VERSION = '3.6.5' +PYTHON_VERSION = '3.5.4' PYHOME = 'c:/python' + PYTHON_VERSION home = 'C:\\electrum\\' From bfccfc7e7410601dfb8e1d73eb05cbc8763ca14b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 1 Apr 2018 18:41:17 +0200 Subject: [PATCH 158/174] follow-up 70d827b984abfbaf2f01934aaf51412cf9682608 --- contrib/freeze_packages.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/freeze_packages.sh b/contrib/freeze_packages.sh index 623eddaad..5f140dec7 100755 --- a/contrib/freeze_packages.sh +++ b/contrib/freeze_packages.sh @@ -21,7 +21,7 @@ for i in '' '-hw' '-binaries'; do echo "OK." requirements=$(pip freeze) - restricted=$(echo $requirements | $other_python ./deterministic-build/find_restricted_dependencies.py) + restricted=$(echo $requirements | $other_python $contrib/deterministic-build/find_restricted_dependencies.py) requirements="$requirements $restricted" echo "Generating package hashes..." From 10a0b0ad7f97666143e3172542970f765c54ecfd Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 1 Apr 2018 22:53:02 +0200 Subject: [PATCH 159/174] make_packages was failing error was: In --require-hashes mode, all requirements must have their versions pinned with ==. These do not: setuptools from https://pypi.python.org/packages/20/d7/04a0b689d3035143e2ff288f4b9ee4bf6ed80585cc121c90bfd85a1a8c2e/setuptools-39.0.1-py2.py3-none-any.whl#md5=ca299c7acd13a72e1171a3697f2b99bc (from protobuf==3.5.2.post1->-r ./contrib/deterministic-build/requirements.txt (line 21)) --- contrib/deterministic-build/requirements-binaries.txt | 9 +++++++++ contrib/deterministic-build/requirements-hw.txt | 9 +++++++++ contrib/deterministic-build/requirements.txt | 9 +++++++++ contrib/freeze_packages.sh | 2 +- 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/contrib/deterministic-build/requirements-binaries.txt b/contrib/deterministic-build/requirements-binaries.txt index d2c7c1b43..2516789ab 100644 --- a/contrib/deterministic-build/requirements-binaries.txt +++ b/contrib/deterministic-build/requirements-binaries.txt @@ -1,3 +1,6 @@ +pip==9.0.3 \ + --hash=sha256:7bf48f9a693be1d58f49f7af7e0ae9fe29fd671cde8a55e6edca3581c4ef5796 \ + --hash=sha256:c3ede34530e0e0b2381e7363aded78e0c33291654937e7373032fda04e8803e5 pycryptodomex==3.5.1 \ --hash=sha256:16ab612ca9164e971dc00f8fe895ac835e8bfe64c3174b368f80172ff5a98300 \ --hash=sha256:299a79efba6152ea438cc37f7349161e7bbd914f918342cad6316a4a5f29f2d7 \ @@ -27,6 +30,9 @@ PyQt5==5.10.1 \ --hash=sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b \ --hash=sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7 \ --hash=sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61 +setuptools==39.0.1 \ + --hash=sha256:8010754433e3211b9cdbbf784b50f30e80bf40fc6b05eb5f865fab83300599b8 \ + --hash=sha256:bec7badf0f60e7fc8153fac47836edc41b74e5d541d7692e614e635720d6a7c7 SIP==4.19.8 \ --hash=sha256:09f9a4e6c28afd0bafedb26ffba43375b97fe7207bd1a0d3513f79b7d168b331 \ --hash=sha256:105edaaa1c8aa486662226360bd3999b4b89dd56de3e314d82b83ed0587d8783 \ @@ -46,3 +52,6 @@ six==1.11.0 \ websocket-client==0.47.0 \ --hash=sha256:188b68b14fdb2d8eb1a111f21b9ffd2dbf1dbc4e4c1d28cf2c37cdbf1dd1cae6 \ --hash=sha256:a453dc4dfa6e0db3d8fd7738a308a88effe6240c59f3226eb93e8f020c216149 +wheel==0.31.0 \ + --hash=sha256:1ae8153bed701cb062913b72429bcf854ba824f973735427681882a688cb55ce \ + --hash=sha256:9cdc8ab2cc9c3c2e2727a4b67c22881dbb0e1c503d592992594c5e131c867107 diff --git a/contrib/deterministic-build/requirements-hw.txt b/contrib/deterministic-build/requirements-hw.txt index 63d7fd80c..88cd12e75 100644 --- a/contrib/deterministic-build/requirements-hw.txt +++ b/contrib/deterministic-build/requirements-hw.txt @@ -64,6 +64,9 @@ mnemonic==0.18 \ --hash=sha256:02a7306a792370f4a0c106c2cf1ce5a0c84b9dbd7e71c6792fdb9ad88a727f1d pbkdf2==1.3 \ --hash=sha256:ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979 +pip==9.0.3 \ + --hash=sha256:7bf48f9a693be1d58f49f7af7e0ae9fe29fd671cde8a55e6edca3581c4ef5796 \ + --hash=sha256:c3ede34530e0e0b2381e7363aded78e0c33291654937e7373032fda04e8803e5 protobuf==3.5.2.post1 \ --hash=sha256:01ccd6d03449ae75b779fb5bf4ed62177d61afe3c5e6465ccf3f8b2e1a84afbe \ --hash=sha256:1d92cc30b0b46cced33adde5853d920179eb5ea8eecdee9552502a7f29cc3f21 \ @@ -100,6 +103,9 @@ requests==2.18.4 \ --hash=sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e rlp==0.6.0 \ --hash=sha256:87879a0ba1479b760cee98af165de2eee95258b261faa293199f60742be96f34 +setuptools==39.0.1 \ + --hash=sha256:8010754433e3211b9cdbbf784b50f30e80bf40fc6b05eb5f865fab83300599b8 \ + --hash=sha256:bec7badf0f60e7fc8153fac47836edc41b74e5d541d7692e614e635720d6a7c7 six==1.11.0 \ --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \ --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb @@ -108,3 +114,6 @@ trezor==0.9.1 \ urllib3==1.22 \ --hash=sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b \ --hash=sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f +wheel==0.31.0 \ + --hash=sha256:1ae8153bed701cb062913b72429bcf854ba824f973735427681882a688cb55ce \ + --hash=sha256:9cdc8ab2cc9c3c2e2727a4b67c22881dbb0e1c503d592992594c5e131c867107 diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt index 58b0e83dc..8ec2501cf 100644 --- a/contrib/deterministic-build/requirements.txt +++ b/contrib/deterministic-build/requirements.txt @@ -18,6 +18,9 @@ jsonrpclib-pelix==0.3.1 \ --hash=sha256:bd89a6093bc4d47dc8a096197aacb827359944a4533be5193f3845f57b9f91b4 pbkdf2==1.3 \ --hash=sha256:ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979 +pip==9.0.3 \ + --hash=sha256:7bf48f9a693be1d58f49f7af7e0ae9fe29fd671cde8a55e6edca3581c4ef5796 \ + --hash=sha256:c3ede34530e0e0b2381e7363aded78e0c33291654937e7373032fda04e8803e5 protobuf==3.5.2.post1 \ --hash=sha256:01ccd6d03449ae75b779fb5bf4ed62177d61afe3c5e6465ccf3f8b2e1a84afbe \ --hash=sha256:1d92cc30b0b46cced33adde5853d920179eb5ea8eecdee9552502a7f29cc3f21 \ @@ -49,12 +52,18 @@ qrcode==6.0 \ requests==2.18.4 \ --hash=sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b \ --hash=sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e +setuptools==39.0.1 \ + --hash=sha256:8010754433e3211b9cdbbf784b50f30e80bf40fc6b05eb5f865fab83300599b8 \ + --hash=sha256:bec7badf0f60e7fc8153fac47836edc41b74e5d541d7692e614e635720d6a7c7 six==1.11.0 \ --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \ --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb urllib3==1.22 \ --hash=sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b \ --hash=sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f +wheel==0.31.0 \ + --hash=sha256:1ae8153bed701cb062913b72429bcf854ba824f973735427681882a688cb55ce \ + --hash=sha256:9cdc8ab2cc9c3c2e2727a4b67c22881dbb0e1c503d592992594c5e131c867107 colorama==0.3.9 \ --hash=sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda \ --hash=sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1 diff --git a/contrib/freeze_packages.sh b/contrib/freeze_packages.sh index 5f140dec7..19d6b5fcc 100755 --- a/contrib/freeze_packages.sh +++ b/contrib/freeze_packages.sh @@ -20,7 +20,7 @@ for i in '' '-hw' '-binaries'; do echo "OK." - requirements=$(pip freeze) + requirements=$(pip freeze --all) restricted=$(echo $requirements | $other_python $contrib/deterministic-build/find_restricted_dependencies.py) requirements="$requirements $restricted" From 4d15d4e4598390026a990ff2d3a818a180407734 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 2 Apr 2018 05:30:58 +0200 Subject: [PATCH 160/174] payto: get RBF setting from config --- lib/commands.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/commands.py b/lib/commands.py index 8435cf9ec..f751e7d20 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -412,6 +412,8 @@ class Commands: tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr) if locktime != None: tx.locktime = locktime + if rbf is None: + rbf = self.config.get('use_rbf', True) if rbf: tx.set_rbf(True) if not unsigned: @@ -420,7 +422,7 @@ class Commands: return tx @command('wp') - def payto(self, destination, amount, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=False, password=None, locktime=None): + def payto(self, destination, amount, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=None, password=None, locktime=None): """Create a transaction. """ tx_fee = satoshis(fee) domain = from_addr.split(',') if from_addr else None @@ -428,7 +430,7 @@ class Commands: return tx.as_dict() @command('wp') - def paytomany(self, outputs, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=False, password=None, locktime=None): + def paytomany(self, outputs, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=None, password=None, locktime=None): """Create a multi-output transaction. """ tx_fee = satoshis(fee) domain = from_addr.split(',') if from_addr else None From 4703d93b0f04a472ba9388da2f1e5eec5f3dc112 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 2 Apr 2018 19:54:01 +0200 Subject: [PATCH 161/174] fix #4216 --- gui/qt/history_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/history_list.py b/gui/qt/history_list.py index c7648c1f7..1af2ff97c 100644 --- a/gui/qt/history_list.py +++ b/gui/qt/history_list.py @@ -411,7 +411,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): lines.append([item['txid'], item.get('label', ''), item['confirmations'], item['value'], item['date']]) else: lines.append(item) - with open(fileName, "w+") as f: + with open(fileName, "w+", encoding='utf-8') as f: if is_csv: import csv transaction = csv.writer(f, lineterminator='\n') From 5a508f7b8d07835d54aec8b9c22a9b2c25609357 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 3 Apr 2018 01:50:37 +0200 Subject: [PATCH 162/174] fix #4218 --- lib/keystore.py | 13 +++++++++++++ lib/wallet.py | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/keystore.py b/lib/keystore.py index f4b3d0166..968323310 100644 --- a/lib/keystore.py +++ b/lib/keystore.py @@ -76,6 +76,8 @@ class KeyStore(PrintError): return False return bool(self.get_tx_derivations(tx)) + def ready_to_sign(self): + return not self.is_watching_only() class Software_KeyStore(KeyStore): @@ -536,6 +538,17 @@ class Hardware_KeyStore(KeyStore, Xpub): password = self.get_pubkey_from_xpub(xpub, ()) return password + def has_usable_connection_with_device(self): + if not hasattr(self, 'plugin'): + return False + client = self.plugin.get_client(self, force_pair=False) + if client is None: + return False + return client.has_usable_connection_with_device() + + def ready_to_sign(self): + return super().ready_to_sign() and self.has_usable_connection_with_device() + def bip39_normalize_passphrase(passphrase): return normalize('NFKD', passphrase or '') diff --git a/lib/wallet.py b/lib/wallet.py index 6dc4b8e5e..f5596c9f3 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1458,8 +1458,8 @@ class Abstract_Wallet(PrintError): # hardware wallets require extra info if any([(isinstance(k, Hardware_KeyStore) and k.can_sign(tx)) for k in self.get_keystores()]): self.add_hw_info(tx) - # sign - for k in self.get_keystores(): + # sign. start with ready keystores. + for k in sorted(self.get_keystores(), key=lambda ks: ks.ready_to_sign(), reverse=True): try: if k.can_sign(tx): k.sign_transaction(tx, password) From 42582b6a643460e0cae64c8b6ad4998da11a461b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 3 Apr 2018 02:48:55 +0200 Subject: [PATCH 163/174] fix #4219 --- gui/qt/main_window.py | 3 +++ lib/paymentrequest.py | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 98bd6d401..79b226026 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -1838,6 +1838,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): def show_invoice(self, key): pr = self.invoices.get(key) + if pr is None: + self.show_error('Cannot find payment request in wallet.') + return pr.verify(self.contacts) self.show_pr_details(pr) diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py index d58a41d94..d5a898375 100644 --- a/lib/paymentrequest.py +++ b/lib/paymentrequest.py @@ -453,7 +453,11 @@ class InvoiceStore(object): def set_paid(self, pr, txid): pr.tx = txid - self.paid[txid] = pr.get_id() + pr_id = pr.get_id() + self.paid[txid] = pr_id + if pr_id not in self.invoices: + # in case the user had deleted it previously + self.add(pr) def load(self, d): for k, v in d.items(): From 6eb38d6b8c7bb3f03fda773d402bd619d8fea3a5 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 3 Apr 2018 14:14:36 +0200 Subject: [PATCH 164/174] Qt wizard: raise GoBack from None results in cleaner traces --- gui/qt/installwizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py index 6bef0da35..e1830ebfa 100644 --- a/gui/qt/installwizard.py +++ b/gui/qt/installwizard.py @@ -348,7 +348,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): if not result and raise_on_cancel: raise UserCancelled if result == 1: - raise GoBack + raise GoBack from None self.title.setVisible(False) self.back_button.setEnabled(False) self.next_button.setEnabled(False) From 13bd10e1cbb12e9efd412ed2bc8e0f36134895b9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 3 Apr 2018 14:21:22 +0200 Subject: [PATCH 165/174] wizard: add naive debug message to hw device scan if none are found --- lib/base_wizard.py | 9 ++++++++- lib/plugins.py | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/base_wizard.py b/lib/base_wizard.py index a7fc27f65..ba29f1ff0 100644 --- a/lib/base_wizard.py +++ b/lib/base_wizard.py @@ -206,21 +206,28 @@ class BaseWizard(object): scanned_devices = devmgr.scan_devices() except BaseException as e: devmgr.print_error('error scanning devices: {}'.format(e)) + debug_msg = ' {}:\n {}'.format(_('Error scanning devices'), e) else: + debug_msg = '' for name, description, plugin in support: try: # FIXME: side-effect: unpaired_device_info sets client.handler u = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices) except BaseException as e: devmgr.print_error('error getting device infos for {}: {}'.format(name, e)) + debug_msg += ' {}:\n {}\n'.format(plugin.name, e) continue devices += list(map(lambda x: (name, x), u)) + if not debug_msg: + debug_msg = ' {}'.format(_('No exceptions encountered.')) if not devices: msg = ''.join([ _('No hardware device detected.') + '\n', _('To trigger a rescan, press \'Next\'.') + '\n\n', _('If your device is not detected on Windows, go to "Settings", "Devices", "Connected devices", and do "Remove device". Then, plug your device again.') + ' ', - _('On Linux, you might have to add a new permission to your udev rules.'), + _('On Linux, you might have to add a new permission to your udev rules.') + '\n\n', + _('Debug message') + '\n', + debug_msg ]) self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device(purpose)) return diff --git a/lib/plugins.py b/lib/plugins.py index 17f68dac2..b9c539948 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -461,6 +461,8 @@ class DeviceMgr(ThreadJob, PrintError): def unpaired_device_infos(self, handler, plugin, devices=None): '''Returns a list of DeviceInfo objects: one for each connected, unpaired device accepted by the plugin.''' + if not plugin.libraries_available: + raise Exception('Missing libraries for {}'.format(plugin.name)) if devices is None: devices = self.scan_devices() devices = [dev for dev in devices if not self.xpub_by_id(dev.id_)] From ffe69cb32802cb3a53f91efe5ba83e3d3890a6a0 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 3 Apr 2018 19:18:10 +0200 Subject: [PATCH 166/174] fix #4220 --- lib/wallet.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/wallet.py b/lib/wallet.py index f5596c9f3..7c20ed55d 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1531,7 +1531,10 @@ class Abstract_Wallet(PrintError): baseurl = 'file://' + rdir rewrite = config.get('url_rewrite') if rewrite: - baseurl = baseurl.replace(*rewrite) + try: + baseurl = baseurl.replace(*rewrite) + except BaseException as e: + self.print_stderr('Invalid config setting for "url_rewrite". err:', e) out['request_url'] = os.path.join(baseurl, 'req', key[0], key[1], key, key) out['URI'] += '&r=' + out['request_url'] out['index_url'] = os.path.join(baseurl, 'index.html') + '?id=' + key From 17512f7f47588a05c780c23d6972dedaa1b49516 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 3 Apr 2018 22:16:29 +0200 Subject: [PATCH 167/174] wallet: speed up get_address_history --- lib/wallet.py | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index 7c20ed55d..7f3e2fdbf 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -39,6 +39,7 @@ from functools import partial from collections import defaultdict from numbers import Number from decimal import Decimal +import itertools import sys @@ -188,7 +189,12 @@ class Abstract_Wallet(PrintError): self.synchronizer = None self.verifier = None - self.gap_limit_for_change = 6 # constant + self.gap_limit_for_change = 6 # constant + + # locks: if you need to take multiple ones, acquire them in the order they are defined here! + self.lock = threading.RLock() + self.transaction_lock = threading.RLock() + # saved fields self.use_change = storage.get('use_change', True) self.multiple_change = storage.get('multiple_change', False) @@ -219,10 +225,6 @@ class Abstract_Wallet(PrintError): # wallet.up_to_date is true when the wallet is synchronized (stronger requirement) self.up_to_date = False - # locks: if you need to take multiple ones, acquire them in the order they are defined here! - self.lock = threading.RLock() - self.transaction_lock = threading.RLock() - self.check_history() # save wallet type the first time @@ -253,6 +255,7 @@ class Abstract_Wallet(PrintError): self.pruned_txo = self.storage.get('pruned_txo', {}) tx_list = self.storage.get('transactions', {}) self.transactions = {} + self._history_local = {} # address -> set(txid) for tx_hash, raw in tx_list.items(): tx = Transaction(raw) self.transactions[tx_hash] = tx @@ -260,6 +263,8 @@ class Abstract_Wallet(PrintError): and (tx_hash not in self.pruned_txo.values()): self.print_error("removing unreferenced tx", tx_hash) self.transactions.pop(tx_hash) + else: + self._add_tx_to_local_history(tx_hash) @profiler def save_transactions(self, write=False): @@ -669,8 +674,9 @@ class Abstract_Wallet(PrintError): def get_addr_balance(self, address): received, sent = self.get_addr_io(address) c = u = x = 0 + local_height = self.get_local_height() for txo, (tx_height, v, is_cb) in received.items(): - if is_cb and tx_height + COINBASE_MATURITY > self.get_local_height(): + if is_cb and tx_height + COINBASE_MATURITY > local_height: x += v elif tx_height > 0: c += v @@ -732,12 +738,30 @@ class Abstract_Wallet(PrintError): # we need self.transaction_lock but get_tx_height will take self.lock # so we need to take that too here, to enforce order of locks with self.lock, self.transaction_lock: - for tx_hash in self.transactions: - if addr in self.txi.get(tx_hash, []) or addr in self.txo.get(tx_hash, []): - tx_height = self.get_tx_height(tx_hash)[0] - h.append((tx_hash, tx_height)) + related_txns = self._history_local.get(addr, set()) + for tx_hash in related_txns: + tx_height = self.get_tx_height(tx_hash)[0] + h.append((tx_hash, tx_height)) return h + def _add_tx_to_local_history(self, txid): + with self.transaction_lock: + for addr in itertools.chain(self.txi.get(txid, []), self.txo.get(txid, [])): + cur_hist = self._history_local.get(addr, set()) + cur_hist.add(txid) + self._history_local[addr] = cur_hist + + def _remove_tx_from_local_history(self, txid): + with self.transaction_lock: + for addr in itertools.chain(self.txi.get(txid, []), self.txo.get(txid, [])): + cur_hist = self._history_local.get(addr, set()) + try: + cur_hist.remove(txid) + except KeyError: + pass + else: + self._history_local[addr] = cur_hist + def get_txin_address(self, txi): addr = txi.get('address') if addr != "(pubkey)": @@ -876,6 +900,8 @@ class Abstract_Wallet(PrintError): if dd.get(addr) is None: dd[addr] = [] dd[addr].append((ser, v)) + # add to local history + self._add_tx_to_local_history(tx_hash) # save self.transactions[tx_hash] = tx return True @@ -895,6 +921,8 @@ class Abstract_Wallet(PrintError): self.spent_outpoints.pop(ser, None) self.pruned_txo.pop(ser) + self._remove_tx_from_local_history(tx_hash) + # add tx to pruned_txo, and undo the txi addition for next_tx, dd in self.txi.items(): for addr, l in list(dd.items()): From 92c45ac29a5204ff3e72df592a6f9088a502d88f Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 4 Apr 2018 01:22:49 +0200 Subject: [PATCH 168/174] fix prev: txns might be added in any order --- lib/wallet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/wallet.py b/lib/wallet.py index 7f3e2fdbf..9c4cda55f 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -900,6 +900,7 @@ class Abstract_Wallet(PrintError): if dd.get(addr) is None: dd[addr] = [] dd[addr].append((ser, v)) + self._add_tx_to_local_history(next_tx) # add to local history self._add_tx_to_local_history(tx_hash) # save From 76dfc2d3b864e912797b9386c6b5ae6e9a327363 Mon Sep 17 00:00:00 2001 From: Filip Gospodinov Date: Wed, 4 Apr 2018 11:41:07 +0200 Subject: [PATCH 169/174] Fix invalid desktop entry (#4224) Closes: #4221 --- electrum.desktop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electrum.desktop b/electrum.desktop index 086ed881f..5e57c8dda 100644 --- a/electrum.desktop +++ b/electrum.desktop @@ -3,7 +3,7 @@ [Desktop Entry] Comment=Lightweight Bitcoin Client -Exec=sh -c 'PATH="$HOME/.local/bin:$PATH" electrum %u' +Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\" electrum %u" GenericName[en_US]=Bitcoin Wallet GenericName=Bitcoin Wallet Icon=electrum @@ -17,5 +17,5 @@ MimeType=x-scheme-handler/bitcoin; Actions=Testnet; [Desktop Action Testnet] -Exec=sh -c 'PATH="$HOME/.local/bin:$PATH" electrum --testnet %u' +Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\" electrum --testnet %u" Name=Testnet mode From 2f408e5d07ca8ae142751998e844109a1d5ba0da Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 4 Apr 2018 15:47:11 +0200 Subject: [PATCH 170/174] make qt HistoryList.on_update() faster by caching icons --- gui/qt/history_list.py | 8 ++++---- gui/qt/invoice_list.py | 2 +- gui/qt/request_list.py | 4 ++-- gui/qt/util.py | 13 +++++++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/gui/qt/history_list.py b/gui/qt/history_list.py index 1af2ff97c..8b4baf575 100644 --- a/gui/qt/history_list.py +++ b/gui/qt/history_list.py @@ -235,7 +235,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): label = tx_item['label'] status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp) has_invoice = self.wallet.invoices.paid.get(tx_hash) - icon = QIcon(":icons/" + TX_ICONS[status]) + icon = self.icon_cache.get(":icons/" + TX_ICONS[status]) v_str = self.parent.format_amount(value, True, whitespaces=True) balance_str = self.parent.format_amount(balance, whitespaces=True) entry = ['', tx_hash, status_str, label, v_str, balance_str] @@ -253,7 +253,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): item.setToolTip(0, str(conf) + " confirmation" + ("s" if conf != 1 else "")) item.setData(0, SortableTreeWidgetItem.DataRole, (status, conf)) if has_invoice: - item.setIcon(3, QIcon(":icons/seal")) + item.setIcon(3, self.icon_cache.get(":icons/seal")) for i in range(len(entry)): if i>3: item.setTextAlignment(i, Qt.AlignRight | Qt.AlignVCenter) @@ -302,7 +302,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): def update_item(self, tx_hash, height, conf, timestamp): status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp) - icon = QIcon(":icons/" + TX_ICONS[status]) + icon = self.icon_cache.get(":icons/" + TX_ICONS[status]) items = self.findItems(tx_hash, Qt.UserRole|Qt.MatchContains|Qt.MatchRecursive, column=1) if items: item = items[0] @@ -347,7 +347,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): if child_tx: menu.addAction(_("Child pays for parent"), lambda: self.parent.cpfp(tx, child_tx)) if pr_key: - menu.addAction(QIcon(":icons/seal"), _("View invoice"), lambda: self.parent.show_invoice(pr_key)) + menu.addAction(self.icon_cache.get(":icons/seal"), _("View invoice"), lambda: self.parent.show_invoice(pr_key)) if tx_URL: menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL)) menu.exec_(self.viewport().mapToGlobal(position)) diff --git a/gui/qt/invoice_list.py b/gui/qt/invoice_list.py index 586dd71c5..462aadd85 100644 --- a/gui/qt/invoice_list.py +++ b/gui/qt/invoice_list.py @@ -48,7 +48,7 @@ class InvoiceList(MyTreeWidget): exp = pr.get_expiration_date() date_str = format_time(exp) if exp else _('Never') item = QTreeWidgetItem([date_str, requestor, pr.memo, self.parent.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status,'')]) - item.setIcon(4, QIcon(pr_icons.get(status))) + item.setIcon(4, self.icon_cache.get(pr_icons.get(status))) item.setData(0, Qt.UserRole, key) item.setFont(1, QFont(MONOSPACE_FONT)) item.setFont(3, QFont(MONOSPACE_FONT)) diff --git a/gui/qt/request_list.py b/gui/qt/request_list.py index 59b084ff9..dacae503e 100644 --- a/gui/qt/request_list.py +++ b/gui/qt/request_list.py @@ -98,10 +98,10 @@ class RequestList(MyTreeWidget): amount_str = self.parent.format_amount(amount) if amount else "" item = QTreeWidgetItem([date, address, '', message, amount_str, pr_tooltips.get(status,'')]) if signature is not None: - item.setIcon(2, QIcon(":icons/seal.png")) + item.setIcon(2, self.icon_cache.get(":icons/seal.png")) item.setToolTip(2, 'signed by '+ requestor) if status is not PR_UNKNOWN: - item.setIcon(6, QIcon(pr_icons.get(status))) + item.setIcon(6, self.icon_cache.get(pr_icons.get(status))) self.addTopLevelItem(item) diff --git a/gui/qt/util.py b/gui/qt/util.py index 306c64a55..b8fc1cdae 100644 --- a/gui/qt/util.py +++ b/gui/qt/util.py @@ -393,6 +393,8 @@ class MyTreeWidget(QTreeWidget): self.addChild = self.addTopLevelItem self.insertChild = self.insertTopLevelItem + self.icon_cache = IconCache() + # Control which columns are editable self.editor = None self.pending_update = False @@ -779,6 +781,17 @@ class SortableTreeWidgetItem(QTreeWidgetItem): return self.text(column) < other.text(column) +class IconCache: + + def __init__(self): + self.__cache = {} + + def get(self, file_name): + if file_name not in self.__cache: + self.__cache[file_name] = QIcon(file_name) + return self.__cache[file_name] + + if __name__ == "__main__": app = QApplication([]) t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done")) From de4fe9db693b0bdf2f97ce558552d0068fb977d9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 5 Apr 2018 08:32:02 +0200 Subject: [PATCH 171/174] fix a race condition in synchronizer wallet.synchronizer gets assigned a newly constructed Synchronizer instance. Synchronizer in tx_response refers to the value of wallet.synchronizer. If the wallet has a missing txn, there could be a race condition that synchronizer asks for a txn and we get the callback from the network WHILE the constructor is still running, in which case wallet.synchronizer would still be None and we would consider the callback "orphan", and the wallet would get "stuck" synchronizing. --- lib/synchronizer.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/synchronizer.py b/lib/synchronizer.py index 4b81810db..f3f4cbb5f 100644 --- a/lib/synchronizer.py +++ b/lib/synchronizer.py @@ -50,6 +50,8 @@ class Synchronizer(ThreadJob): self.requested_histories = {} self.requested_addrs = set() self.lock = Lock() + + self.initialized = False self.initialize() def parse_response(self, response): @@ -84,7 +86,7 @@ class Synchronizer(ThreadJob): return bh2u(hashlib.sha256(status.encode('ascii')).digest()) def on_address_status(self, response): - if self.wallet.synchronizer is None: + if self.wallet.synchronizer is None and self.initialized: return # we have been killed, this was just an orphan callback params, result = self.parse_response(response) if not params: @@ -100,7 +102,7 @@ class Synchronizer(ThreadJob): self.requested_addrs.remove(addr) def on_address_history(self, response): - if self.wallet.synchronizer is None: + if self.wallet.synchronizer is None and self.initialized: return # we have been killed, this was just an orphan callback params, result = self.parse_response(response) if not params: @@ -131,7 +133,7 @@ class Synchronizer(ThreadJob): self.requested_histories.pop(addr) def tx_response(self, response): - if self.wallet.synchronizer is None: + if self.wallet.synchronizer is None and self.initialized: return # we have been killed, this was just an orphan callback params, result = self.parse_response(response) if not params: @@ -183,6 +185,7 @@ class Synchronizer(ThreadJob): if self.requested_tx: self.print_error("missing tx", self.requested_tx) self.subscribe_to_addresses(set(self.wallet.get_addresses())) + self.initialized = True def run(self): '''Called from the network proxy thread main loop.''' From ad6dd73a032517651d6e029163628d67039a36d2 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 5 Apr 2018 11:27:12 +0200 Subject: [PATCH 172/174] do not catch OSError in pipe.send (fix ANR on some versions of Android) --- lib/interface.py | 4 ++-- lib/util.py | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/interface.py b/lib/interface.py index 9155640b3..1847602b0 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -297,8 +297,8 @@ class Interface(util.PrintError): wire_requests = self.unsent_requests[0:n] try: self.pipe.send_all([make_dict(*r) for r in wire_requests]) - except socket.error as e: - self.print_error("socket error:", e) + except BaseException as e: + self.print_error("pipe send error:", e) return False self.unsent_requests = self.unsent_requests[n:] for request in wire_requests: diff --git a/lib/util.py b/lib/util.py index 1c12747eb..68067c435 100644 --- a/lib/util.py +++ b/lib/util.py @@ -736,10 +736,6 @@ class SocketPipe: print_error("SSLError:", e) time.sleep(0.1) continue - except OSError as e: - print_error("OSError", e) - time.sleep(0.1) - continue class QueuePipe: From d2a1179087f89fc121b0ca998bc052734ad87417 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 5 Apr 2018 12:22:01 +0200 Subject: [PATCH 173/174] fix #4227 --- lib/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/commands.py b/lib/commands.py index f751e7d20..0b507ff45 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -34,7 +34,7 @@ from functools import wraps from decimal import Decimal from .import util -from .util import bfh, bh2u, format_satoshis, json_decode, print_error +from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_encode from .import bitcoin from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS from .i18n import _ @@ -451,7 +451,7 @@ class Commands: from .exchange_rate import FxThread fx = FxThread(self.config, None) kwargs['fx'] = fx - return self.wallet.get_full_history(**kwargs) + return json_encode(self.wallet.get_full_history(**kwargs)) @command('w') def setlabel(self, key, label): From e4dad0a42565a32824e8202c27e9f7b19ac790ed Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 5 Apr 2018 12:52:21 +0200 Subject: [PATCH 174/174] fix #4198 --- lib/synchronizer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/synchronizer.py b/lib/synchronizer.py index f3f4cbb5f..b43fd221e 100644 --- a/lib/synchronizer.py +++ b/lib/synchronizer.py @@ -108,8 +108,11 @@ class Synchronizer(ThreadJob): if not params: return addr = params[0] + server_status = self.requested_histories.get(addr) + if server_status is None: + self.print_error("receiving history (unsolicited)", addr, len(result)) + return self.print_error("receiving history", addr, len(result)) - server_status = self.requested_histories[addr] hashes = set(map(lambda item: item['tx_hash'], result)) hist = list(map(lambda item: (item['tx_hash'], item['height']), result)) # tx_fees