Compare commits

...

2587 commits

Author SHA1 Message Date
kodxana
e536a2be13
Add support for Ledger Nano S Plus 2022-07-20 21:41:43 +02:00
kodxana
2b7b5ce093
Linux docker update 2020-12-16 19:41:49 +01:00
kodxana
82b0f92861
Added alternate server 2020-12-16 19:33:10 +01:00
kodxana
5f11b4cb93
Ledger enumeration fix 2020-12-16 19:23:30 +01:00
kodxana
5716e49a35
Update README.rst 2020-02-24 19:33:49 +01:00
kodxana
26fcfb858b
Update README.rst 2020-02-24 19:33:31 +01:00
kodxana
634916ffd8
Update servers.json 2020-02-19 15:36:07 +01:00
kodxana
5fa4f61a48
Update servers.json 2020-02-19 15:35:27 +01:00
kodxana
d2faa61e37
Update main_window.py 2020-02-19 15:34:36 +01:00
kodxana
227a74c327
Update ecc.py 2020-02-19 15:17:06 +01:00
kodxana
72c79597fa
Update simple_config.py 2020-02-19 14:47:51 +01:00
kodxana
a1e23ac46f
ecc.ECPubkey: also accept bytearray in __init__ 2020-02-19 14:43:52 +01:00
kodxana
1a18e70d92
ecc.ECPubkey: also accept bytearray in __init__ 2020-02-19 14:43:00 +01:00
kodxana
9350f9f26a
Data directory linux fix 2020-02-19 09:34:25 +01:00
kodxana
a0daf2d625
Update README.rst 2020-02-18 13:42:22 +01:00
kodxana
1d71cfb4db
Update README.rst 2020-02-18 13:41:32 +01:00
kodxana
3f92d775b6
Update icons 2020-02-18 13:30:20 +01:00
kodxana
1c141aa26e
Change icons to new 2020-02-18 13:29:45 +01:00
kodxana
94201aa845
Update main_window.py 2020-02-18 10:07:05 +01:00
kodxana
39258cf967
Rebrand Electrum to LBRY Vault 2020-02-18 10:00:07 +01:00
kodxana
6b2df3e4a0
Rebrand Electrum to LBRY Vault 2020-02-18 09:52:20 +01:00
kodxana
65cbb667f5
Rebrand Electrum to LBRY Vault 2020-02-18 09:48:12 +01:00
kodxana
a1850416d1
Merge pull request #3 from kodxana/Rebase_3.3.8_to_Master
Rebase 3.3.8 to master
2020-02-18 08:53:50 +01:00
kodxana
d3f3e5ec57 Rebase Electrum to 3.3.8 2020-02-18 08:44:09 +01:00
kodxana
dbfc9a5df7 Rebase Electrum to 3.3.8 2020-02-17 23:58:06 +01:00
Madiator2011
43a67ef148 Upgrade Electrum base to 3.3.8 2020-02-17 23:42:13 +01:00
SomberNight
a6e3a244e8
add comment re lnchannel channel_states 2020-02-14 16:15:25 +01:00
ThomasV
bb21e01823 (minor) call lnwatcher.add_channel from lnworker.add_channel 2020-02-14 14:15:15 +01:00
ThomasV
471fdd1d97 follow-up previous commit 2020-02-14 13:37:45 +01:00
ThomasV
2a7b5081c9 channel states: make sure that closing_txid is saved if channel is closed 2020-02-14 13:25:04 +01:00
SomberNight
111ef9ebb1
follow-up fixes to storage-db separation
e1ce3aace7
2020-02-13 20:00:12 +01:00
SomberNight
5d8d8f743a
kivy: more reliable saving of "last wallet"
The intended behaviour is that when the user launches the app,
the wallet we try to open is the wallet most recently opened by the user.
The old code in 'on_stop' in practice only got called if the user exited via
repeatedly pressing the system 'back' button.

related #5952
2020-02-13 03:12:32 +01:00
SomberNight
091f6ced58
android build: update buildozer and p4a versions
also update NDK version (new p4a demands it)
2020-02-13 02:25:04 +01:00
SomberNight
ab4e2dd9f0
wallet: fix is_mine/can_sign. don't just rely on ks, also check script
Previously a standard (single-sig) wallet would consider a multisig txin as is_mine
(if the keystore found its pubkey in the txin).

fixes #5948
2020-02-12 18:14:07 +01:00
SomberNight
0d33da2f95
wallet: (sanity) is_mine now guaranteed to handle 'None' input 2020-02-12 18:14:04 +01:00
SomberNight
07f5d6b745
keystore: 'get_tx_derivations' no longer public 2020-02-12 18:14:00 +01:00
ThomasV
beee880dba fix data_loss_protect (missing return, json conversion) 2020-02-12 14:19:31 +01:00
ThomasV
9734546fe9 test_lnpeer: use less side-effects 2020-02-12 10:32:55 +01:00
ThomasV
e3630d87b2 test_lnpeer: test_reestablish_with_old_state 2020-02-12 10:22:22 +01:00
ThomasV
3377627070 reestablish_channel: shorter varnames 2020-02-12 06:18:22 +01:00
ThomasV
69ef9aa3d7 channel_reestablish: assume that DLP is enabled, because we require it 2020-02-11 21:39:23 +01:00
SomberNight
e42e17779d
windows: dll-load 'hack' needs to be applied not only from main script
but also when running tests, or just importing electrum from an interpreter
2020-02-11 20:57:37 +01:00
SomberNight
23a93ef7ad
windows: when running from source, load DLLs from main dir
Load DLLs from inner 'electrum' dir instead of '.dlls'.
To make it consistent with where we expect libsecp256k1 (.dll/.so) be.
(note that while in case of libsecp we specifically already search the main dir,
without this change, other DLLs such as libusb or libzbar would not be found there)
2020-02-11 20:57:34 +01:00
SomberNight
e8118e1845
gitignore: add .so/.dll in inner 'electrum' folder 2020-02-11 20:57:30 +01:00
ThomasV
9ccfa318f8 add new peer_state for the case where we are waiting for the remote party to force close a channel 2020-02-11 20:55:52 +01:00
ThomasV
8688a6530a reestablish_channel: do not send second channel_reestablish message if they are ahead (they should know it) 2020-02-11 20:42:16 +01:00
ThomasV
ece75c3244 test_lnpeer: simple test of channel_reestablish 2020-02-11 19:53:21 +01:00
ThomasV
28dc1928a0
Merge pull request #5947 from SomberNight/202002_ecdsa
make libsecp256k1 a mandatory dependency
2020-02-11 19:05:12 +01:00
SomberNight
f78589ec77
update README to mention libsecp 2020-02-11 17:33:06 +01:00
SomberNight
5b84e714f2
build: workaround for 'realpath' missing on macOS 2020-02-11 16:48:28 +01:00
SomberNight
4cec098d2d
build: create a standalone build script for libsecp256k1
heavily based on Electron-Cash/Electron-Cash@eda015908e
2020-02-11 16:48:24 +01:00
SomberNight
1d72585b7d
ecc: hard fail if libsecp256k1 is not found/usable 2020-02-11 16:46:35 +01:00
SomberNight
de1ca27d63
tests: rm "needs_test_with_all_ecc_implementations" decorator
now libsecp256k1 is the only implementation
2020-02-11 16:46:31 +01:00
SomberNight
0a5ad9fda4
ecc: small API clean-up 2020-02-11 16:42:02 +01:00
SomberNight
288d793893
ecc: use libsecp256k1 for pubkey recovery (from sig and msg) 2020-02-11 16:41:59 +01:00
SomberNight
ab0c70e291
ecc: use libsecp256k1 for signature conversions
(instead of python-ecdsa)
2020-02-11 16:41:56 +01:00
SomberNight
ad408ea832
ecc: use libsecp256k1 for sign/verify/mul/add 2020-02-11 16:41:52 +01:00
SomberNight
2cf2135528
ecc: abstract away some usage of python-ecdsa: bytes<->int conversions 2020-02-11 16:41:49 +01:00
SomberNight
004acb906d
ecc: abstract away some usage of python-ecdsa: randrange 2020-02-11 16:41:45 +01:00
ThomasV
a600873cf9 move wrapper definition outside of main_window class 2020-02-11 11:08:33 +01:00
ThomasV
e1ce3aace7 Separate db from storage
- storage is content-agnostic
 - db and storage are passed to wallet contructor
2020-02-10 17:45:23 +01:00
ThomasV
c61e5db6a9 fixes for text interface 2020-02-08 12:35:07 +01:00
SomberNight
f9960a5fe2
qt: don't clear send tab in broadcast_done
no longer needed as fields are already cleared when user clicks Save/Pay
2020-02-07 20:11:08 +01:00
SomberNight
34392e82b9
cosigner pool: easy fix (works but with worse than previous behaviour)
got broken as part of PSBT changes in #5721
2020-02-07 20:09:40 +01:00
Jin Eguchi
4313bde4c2
appimage: update libudev-dev (#5936) 2020-02-07 11:41:04 +00:00
ThomasV
7bd29ed8fc regtest: wait_for_balance 2020-02-05 08:56:58 +01:00
SomberNight
9d2629c5c3
blockchain.fork: better exception if datadir was deleted while running 2020-02-04 19:04:38 +01:00
SomberNight
f545d2b716
qt update checker: use longer timeout
closes #5899
2020-02-04 18:34:24 +01:00
SomberNight
3835157f41
cli: history commands: only json-encode once
closes #5868
closes #5931
2020-02-04 17:56:52 +01:00
ThomasV
dbceed2647 Restructure wallet storage:
- Perform json deserializations in wallet_db
 - use StoredDict class that keeps tracks of its modifications
2020-02-04 13:35:58 +01:00
ThomasV
0a9e7cb04e (minor) rename class: StoredAttr -> StoredObject 2020-02-04 13:34:57 +01:00
ThomasV
7507942b7a (minor) json_db: add file header, fix formatting 2020-02-04 12:43:04 +01:00
ThomasV
b08947a506 storage upgrade: convert lists to dict (txi, txo, revocation_store channels) 2020-02-04 12:11:18 +01:00
ThomasV
63963323be storage: take the DB lock when writing to disk. 2020-02-03 17:08:34 +01:00
ThomasV
73e656522e regests: organize tests in two classes 2020-02-03 15:16:15 +01:00
ThomasV
149cd9598a Separate JsonDB and WalletDB 2020-02-03 12:36:07 +01:00
ThomasV
8118bd1d72 use setEnabled() for Qt menu items: Network, Lightning, Watchtower 2020-02-02 22:40:23 +01:00
ThomasV
4ec86d36a8 faster and improved regtests
- print the test name before each test
 - start only needed agents (alice, bob, carol)
 - set settle_delay using setconfig instead of restarting daemon
 - test the watchtower ctn in test_watchtower
2020-02-02 15:07:28 +01:00
ThomasV
cded582fe9 Start watchtower if run_watchtower is set, even if lightning is not activated (fix #5896).
Fix parameters of sweepstore.add_sweep_tx, rm dead code.
2020-02-02 12:10:10 +01:00
ThomasV
e876cb0d93
Merge pull request #5913 from roth-a/master
Kivy: Adding address coloring to AddressPopup (Address Details)
2020-02-01 19:38:22 +01:00
Alexander Roth
b6a5f6f2fc Added coloring to the AddressPopup dialog:
- Moved the coloring logic (address_colors) from tx_dialog.py to a new file electrum/gui/kivy/util.py
- Added background_color to <RefLabel> in main.kv
- Calling address_colors in the initialization of AddressPopup and setting the foreground and background color

Code cleanup spaces

Code cleanup spaces

Fixed typo
2020-02-01 18:16:26 +01:00
ThomasV
aa51df0a1a Use attr.s for Feeupdates and Outpoints
Storage upgrade to version 23
2020-02-01 16:45:19 +01:00
ThomasV
7472eba78c lnpeer: code factorization 2020-01-31 13:33:38 +01:00
ThomasV
757467782a Use attr.s instead of namedtuples for channel config 2020-01-31 12:19:26 +01:00
ThomasV
9bd633fb0b
Merge pull request #5917 from wakiyamap/fix_travis_appimage
Fix travis appimage
2020-01-29 13:47:46 +01:00
ThomasV
a9463cb245
Merge pull request #5918 from wakiyamap/fix_travis_regtest
Fix travis regtest
2020-01-29 13:46:32 +01:00
wakiyamap
3314591192 Fix travis regtest 2020-01-29 12:57:15 +09:00
wakiyamap
1237134339 Fix travis appimage 2020-01-29 12:55:23 +09:00
SomberNight
11452722af network dns hacks: split from network.py into its own file 2020-01-22 18:32:57 +00:00
SomberNight
cb88a3b6e4 dns hacks on windows: resolve A and AAAA records in parallel 2020-01-22 18:32:57 +00:00
SomberNight
a5cd34dc08
follow-up prev (oops, only committed part of the changes) 2020-01-22 18:26:29 +01:00
Axel Gembe
d3385e49bb
Build: Install libxkbcommon-x11 in AppImage
Newer distributions do not install libxkbcommon-x11 by default
anymore and Qt depends on it.

-----

taken from ca3e4501cd
2020-01-22 18:16:53 +01:00
SomberNight
b560bc92cc
windows build: maybe fix reproducibility (jsonschema-*.dist-info) 2020-01-22 16:28:51 +01:00
Axel Gembe
4406eebbfe
Build: Uninstall Cython from AppImage
Cython is not needed at runtime.

-----

taken from c64910055d

related #5859
2020-01-22 12:27:17 +01:00
SomberNight
80025a3af4
requirements-hw: re-add Cython
this reverts ec496a8222
Cython must be pinned down for reproducible builds
related #5859
2020-01-22 12:08:30 +01:00
SomberNight
c7a21220d5
mac build: bump pyinstaller version 2020-01-21 20:21:14 +01:00
SomberNight
0edd291efe
mac build: bump python version (3.6.4->3.7.6) 2020-01-21 20:20:40 +01:00
SomberNight
a041a0c075
wallet: log when saving already paid invoice
(maybe it should not be allowed?)
2020-01-21 16:50:45 +01:00
SomberNight
34612c671e
fix incorrect type hint 2020-01-21 15:39:34 +01:00
SomberNight
2880c26d87
qt broadcast tx: don't complain about being "offline" for partial tx 2020-01-21 15:12:25 +01:00
SomberNight
cb41a0fe89
qt send tab paytoedit: use monospace font 2020-01-21 14:32:18 +01:00
SomberNight
2e654c9440
qt main_window: trivial clean-up re is_onchain 2020-01-21 14:22:25 +01:00
SomberNight
e9645db182
qt send tab: show "Pay" button even in watch-only wallets 2020-01-21 14:16:23 +01:00
SomberNight
5fd790dec9
follow-up prev
These lists are only visible when non-empty. This interacts badly with
the internals of maybe_defer_update().
2020-01-21 11:51:02 +01:00
SomberNight
1d0fc6665b
qt: defer refreshing tabs until they are visible
very loosely based on Electron-Cash/Electron-Cash@522e7ca59e
2020-01-19 07:31:50 +01:00
SomberNight
356a0a2865
qt: clean-up in some MyTreeView children (mv code from update to init) 2020-01-19 07:02:48 +01:00
SomberNight
18c6451518
json_db: only deserialize transactions on-demand 2020-01-19 05:49:12 +01:00
SomberNight
0fdbf49f08
tests: also run unit tests with python 3.8 on Travis 2020-01-18 05:52:46 +01:00
SomberNight
6d270364c6
qt paytoedit: properly handle multiple max ('!') outputs 2020-01-18 04:15:44 +01:00
SomberNight
5cfafff55d
qt main_window: rm require_fee_update (dead code) 2020-01-18 04:15:40 +01:00
SomberNight
b16164da4f
qt paytoedit: fixes for pay-to-many (when including "!") 2020-01-18 04:15:26 +01:00
rbrooklyn
3658f87035 Add block explorer support for mynode.local (#5892) 2020-01-17 22:06:34 +00:00
SomberNight
1c4728ecc6
appimage binary: bump python version (3.6.8->3.7.6) 2020-01-16 19:12:24 +01:00
SomberNight
e65ce96f9d
interface: better error msg for main server re SSL cert issues
(logger.warning is shown even without -v, if there is a terminal)

closes #5884
2020-01-12 04:29:39 +01:00
SomberNight
a6dd17bfef
fix daemon (don't close instantly)
follow-up 37747d7469
2020-01-11 00:04:00 +01:00
SomberNight
4fd2745332
windows binaries: update nsis 2020-01-10 23:27:34 +01:00
SomberNight
bab9f68736
windows binaries: update wine 2020-01-10 23:26:47 +01:00
SomberNight
547906d1c0
windows binaries: update pyinstaller to 3.6 2020-01-10 20:37:43 +01:00
SomberNight
bc77091539
requirements: rm pycryptodomex from "binary"-specific list
it's already listed now in the "core" requirements.txt file
2020-01-10 19:04:16 +01:00
SomberNight
7c090f92ce
binaries: use "--no-dependencies" option for pip install
All (incl indirect) dependencies are already listed in deterministic-build/requirements*.txt.
This option makes it easier to manually rm a dependency from that list for e.g. testing.
2020-01-10 19:01:32 +01:00
SomberNight
0b0139c676
network.get_transaction: move some response validation logic from Synchronizer 2020-01-09 19:23:28 +01:00
SomberNight
94888739d3
try to fix "--offline" mode 2020-01-09 19:23:24 +01:00
SomberNight
37747d7469
split network main_taskgroup: create daemon.taskgroup
network.main_taskgroup restarts every time the proxy settings are changed,
many long-running tasks (some introduced with lightning) are not prepared for and do not want this.
2020-01-09 19:23:21 +01:00
Nelson Perez
7968531065 Restoring old behavior of the outpoint copy to clipboard feature (#5879)
* Restoring old behavior of the outpoint copy to clipboard feature

* Small code style adjustments
2020-01-09 01:14:18 +00:00
SomberNight
b14747ecfe
ecc.ECPubkey: add custom __deepcopy__ implementation
With python-ecdsa 0.15, copy.deepcopy(ecdsa.ecdsa.Public_key) started to raise.
This fixes the Travis test failures.

Also rm unused ECPrivkey._privkey attribute.
2020-01-08 20:09:45 +01:00
SomberNight
29cf01524a
qt CPFP: handle empty fee field
fixes #5875
2020-01-07 17:59:17 +01:00
SomberNight
1059191ebc
qt installwizard: fix empty filename crash
Traceback (most recent call last):
  File "...\electrum\electrum\gui\qt\installwizard.py", line 256, in on_filename
    widget_create_new.setVisible(temp_storage and temp_storage.file_exists())
TypeError: setVisible(self, bool): argument 1 has unexpected type 'NoneType'
2020-01-07 16:53:37 +01:00
SomberNight
d2f132738a
wallet: only select mature coins by default
this is a regression from #5721

Removed the `TxInput.is_coinbase` method as I think it is a confusing API,
instead we now have `TxInput.is_coinbase_input` and `TxInput.is_coinbase_output`.

related #5872
2020-01-02 00:43:49 +01:00
SomberNight
6709ec4117
dns hacks on windows: cache dns when using dnspython
related #4421
related #5337
2020-01-01 06:23:51 +01:00
SomberNight
2d57a689d9
network/util: increase default timeout of make_aiohttp_session (30->45s)
related: #5337
2020-01-01 06:21:20 +01:00
SomberNight
96fa03c11b
fix paying bip70 payment request with Kivy GUI 2019-12-31 03:37:50 +01:00
SomberNight
1d0aa4042a
fix paying bip70 payment request with Qt GUI 2019-12-31 03:08:47 +01:00
SomberNight
787ac5fe99
interface: make changing max incoming msg size (1 MB) easier 2019-12-27 17:43:12 +01:00
ghost43
c19f9ee755
Merge pull request #5865 from shyrwall/master
Remove phishing server icarus.tetradrachm.net
2019-12-26 17:14:36 +00:00
Sebastian Hyrwall
967f4d7236 Remove phishing server 2019-12-26 23:47:32 +07:00
SomberNight
b3c0231b2b
appimage build: add notes re investigating reproducibility failure 2019-12-24 03:30:26 +01:00
SomberNight
5f4162deaa
requirements: bump min python-keepkey to 6.3.1
as 6.3.0 had basic functionality (restore from seed) broken
see https://github.com/keepkey/python-keepkey/pull/85
2019-12-21 07:33:36 +01:00
SomberNight
ad5c6284c4
commands/jsonrpc: fix specifying "wallet" to commands that need it 2019-12-21 07:00:30 +01:00
SomberNight
2ca535225d
util.standardize_path: properly handle "~" (user's home directory)
notably this is needed when the shell itself does not get a chance to expand "~",
e.g. when a path is passed via JSON-RPC

>>> os.path.normcase(os.path.realpath(os.path.abspath("~/.electrum/testnet/wallets/delete_me2")))
'/home/user/wspace/electrum/~/.electrum/testnet/wallets/delete_me2'
>>> os.path.normcase(os.path.realpath(os.path.abspath(os.path.expanduser("~/.electrum/testnet/wallets/delete_me2"))))
'/home/user/.electrum/testnet/wallets/delete_me2'
2019-12-21 06:53:10 +01:00
ghost43
3716594331
Merge pull request #5460 from SomberNight/keepkey_enum_20190626
keepkey: use libusb to enumerate devices instead of hid
2019-12-20 00:51:19 +00:00
SomberNight
c8d7075758
requirements: bump min python-keepkey to 6.3.0 2019-12-20 01:49:56 +01:00
SomberNight
a8e81c0bd2
keepkey: use libusb to enumerate devices instead of hid 2019-12-20 01:30:10 +01:00
ghost43
ace61d2d20
Merge pull request #5692 from matejcik/trezor-shamir
Trezor: support for Shamir backup and recovery
2019-12-19 15:54:41 +00:00
SomberNight
18209fc782
trezor: when restoring, hide Shamir options by default
They become visible once user clicks "Show expert settings"
2019-12-19 16:50:35 +01:00
SomberNight
9b28f6df7b
wallet: encrypt storage by default
notably, now also in kivy
2019-12-19 14:22:47 +01:00
SomberNight
9834d6cd94
windows binaries: skip building libusb if already done 2019-12-18 18:23:00 +01:00
SomberNight
eca769c4ca
windows binaries: build libusb ourselves
Latest libusb does not have official binaries, and it contains some bugfixes we want.

related: #5460

based on EchterAgo's work in ee4bdaf9c0
2019-12-18 17:32:02 +01:00
matejcik
006c6c1a58 trezor: use BIP39 backup by default even if Shamir is available 2019-12-18 12:36:13 +01:00
matejcik
da41e4c289 trezor: bump library requirement 2019-12-18 12:36:13 +01:00
matejcik
3fc70bd97a trezor: implement support for Shamir recovery 2019-12-18 12:36:13 +01:00
matejcik
f4e2781786 trezor: link button messages to enum names 2019-12-18 12:36:13 +01:00
SomberNight
79681c90e0
wallet._is_onchain_invoice_paid: support "zero amount" invoice 2019-12-17 22:12:51 +01:00
Axel Gembe
880bd16883
AppImage: Improve binary stripping
Slightly reduces file size, improves build speed and makes build more
reproducible.

The .comment section contained GCC version information which could cause
different build output from just a minor update in GCC. The information is not
needed so we strip this.

The strip command was invoked using xargs, spawning a new process for each file.
This is inefficient as xargs can correctly run the strip command with multiple
file names.

-----

taken from 43aaf9572f
2019-12-17 21:41:17 +01:00
SomberNight
33facd151d
ledger.sign_transaction: always do certain output checks 2019-12-17 21:33:07 +01:00
SomberNight
ee63e84bcf
ledger: faster sign_transaction startup
Only call Ledger_KeyStore.get_client_electrum() once,
as it runs DeviceMgr.scan_devices(), which is slow.
2019-12-17 21:19:57 +01:00
SomberNight
6b8c447eb9
ledger: support sending to OP_RETURN outputs
closes #5849

based on:
ca9b432ff0
7bb27eff84
2019-12-17 21:10:14 +01:00
SomberNight
02baae10d7
kivy: implement opening storage-encrypted wallet files 2019-12-17 18:39:52 +01:00
SomberNight
72491bdf18
synchronizer: request tx from server if we only have partial local tx
Note that there is a slight distinction between
`not tx.is_complete()` and `isinstance(tx, PartialTransaction)`,
which is that technically you can have a PSBT that is already complete
but was not yet converted to a standard bitcoin tx.
2019-12-16 21:15:20 +01:00
SomberNight
7b49832a3f
payment requests: fix explicit "None" expiration
Traceback (most recent call last):
  File "...\electrum\electrum\gui\qt\main_window.py", line 994, in <lambda>
    self.create_invoice_button.clicked.connect(lambda: self.create_invoice(False))
  File "...\electrum\electrum\gui\qt\main_window.py", line 1123, in create_invoice
    key = self.create_bitcoin_request(amount, message, expiry)
  File "...\electrum\electrum\gui\qt\main_window.py", line 1132, in create_bitcoin_request
    addr = self.wallet.get_unused_address()
  File "...\electrum\electrum\wallet.py", line 1452, in wrapper
    addr = func(self, *args, **kwargs)
  File "...\electrum\electrum\wallet.py", line 1465, in get_unused_address
    addrs = self.get_unused_addresses()
  File "...\electrum\electrum\wallet.py", line 1459, in get_unused_addresses
    in_use_by_request = [k for k in self.receive_requests.keys() if self.get_request_status(k)[0] != PR_EXPIRED]
  File "...\electrum\electrum\wallet.py", line 1459, in <listcomp>
    in_use_by_request = [k for k in self.receive_requests.keys() if self.get_request_status(k)[0] != PR_EXPIRED]
  File "...\electrum\electrum\wallet.py", line 1535, in get_request_status
    if exp > 0 and time.time() > timestamp + exp:
TypeError: '>' not supported between instances of 'NoneType' and 'int'
2019-12-16 21:03:34 +01:00
SomberNight
01fc048484
CLI: properly auto-upgrade storage when needed even if storage-encrypted
previously commands would error if user had an encrypted storage that needed upgrading
2019-12-15 20:12:51 +01:00
ThomasV
2c6a1f55fb
Merge pull request #5825 from SomberNight/201912_local_tx_can_be_partial
wallet: allow saving partial tx as local (if it has a txid)
2019-12-15 16:40:46 +01:00
ThomasV
61fc00fb9e
Merge pull request #5840 from SomberNight/201912_py38_win_dlls_source
windows: when running from source, with py3.8+, load DLLs from '.dlls'
2019-12-14 10:51:13 +01:00
SomberNight
93cee1ba4d
windows: when running from source, with py3.8+, load DLLs from '.dlls'
Python 3.8 changed where DLLs are searched for.
see https://docs.python.org/3/whatsnew/3.8.html#bpo-36085-whatsnew
This potentially affect our binaries when we start shipping python 3.8+, however that is not being addressed here. This commit simply addresses the usecase of running from source, on Windows, using python 3.8.

On older Python, a user could build/obtain DLLs and place them anywhere on the system %PATH%, however this no longer works with py3.8, as %PATH% is no longer checked.
With py3.8, instead, we now check if there is a folder named '.dlls' in the top-level project directory, and if so, register that as an additional search path.
A user who wants to run Electrum from source on Windows using python 3.8 or later, with their custom DLLs, should manually create the '.dlls' folder and put their DLLs there. If they also want to switch between e.g. python 3.7 and 3.8, they should also include '.dlls' in the system %PATH%.

When using Electrum, interesting DLLs include at least libsecp256k1.dll, libusb-1.0.dll, libzbar-0.dll.
2019-12-14 06:44:17 +01:00
ThomasV
34d652b0f6 follow-up previous commit 2019-12-13 11:09:18 +01:00
ThomasV
11f1541cdd lnworker: save timestamp regardless of channel state 2019-12-13 11:08:25 +01:00
SomberNight
3a2fe80675
qt: also use BlockWaitingDialog in PreviewTxDialog
as when 'advanced_preview' is set, ConfirmTxDialog is skipped

follow-up 1088cf4444
2019-12-12 21:39:46 +01:00
SomberNight
dbd1c8cf71
qt TxDialog: visibility of widgets should be set after parenting
widget.show() and widget.setVisible(True) results in a blink of an ephemeral window containing the widget;
that is, unless the widget has a parent explicitly set or it can be determined via which layout the widget is placed in.
2019-12-12 21:31:30 +01:00
SomberNight
c9ede07462 wizard: (qt) add dedicated button to create new wallet 2019-12-12 17:54:46 +01:00
ThomasV
7324817ff3
Merge pull request #5833 from SomberNight/201912_qt_blocking_waiting_dialog
Qt: introduce BlockingWaitingDialog
2019-12-12 17:53:03 +01:00
SomberNight
308517d473
python 3.8: adapt to breaking changes re asyncio.CancelledError
(and TimeoutError)

closes #5798
2019-12-11 23:07:47 +01:00
ThomasV
fa9b997c70
Merge pull request #5834 from Electronic-Gulden-Foundation/fix/aiohttp
Aiohttp must be lower than 4.0.0
2019-12-11 09:14:36 +01:00
SomberNight
255bf7caf4
build: update some packages in dockerfiles
Ubuntu no longer serves old version
2019-12-10 23:54:45 +01:00
SomberNight
a5a7c205e3
trivial: add a few log lines for startup 2019-12-10 23:31:58 +01:00
SomberNight
d08ed6410a
python3.8: fix DeprecationWarning in qt/paytoedit
.../electrum/electrum/gui/qt/paytoedit.py:221: DeprecationWarning: an integer is required (got type float).  Implicit conversion to integers using __int__ is deprecated, and may be removed in a future version of Python.
  self.setMinimumHeight(h)
2019-12-10 23:01:01 +01:00
SomberNight
9d0ae2f95b
adapt to aiohttp 4.0
related: #5753
2019-12-10 22:55:11 +01:00
Dennis Ruhe
252f0960fc Aiohttp must be lower than 4.0.0 2019-12-10 22:03:17 +01:00
SomberNight
fcd9752f19
keystore: change derive_pubkey API to return bytes 2019-12-10 20:41:47 +01:00
SomberNight
ea62027599
wallet: faster decrypt_message for Imported_Wallet 2019-12-10 20:08:41 +01:00
SomberNight
869a728317
wallet: use abstract base classes 2019-12-10 19:34:44 +01:00
ThomasV
f08796fe68 Allow requests that never expire 2019-12-10 14:45:29 +01:00
SomberNight
1088cf4444
qt: introduce BlockingWaitingDialog
A variant of WaitingDialog that runs the task in the GUI thread,
blocking the GUI. It is probably a code smell to actually use this,
as operations should not block the GUI... still it provides a middle-ground
between blocking the GUI without giving user-feedback and having to refactor
existing code (to avoid blocking).
2019-12-10 03:34:41 +01:00
SomberNight
daef1a8359
lnworker: don't log InvoiceError
lnworker._pay does not need log_exceptions decorator,
as we properly await the coroutine
2019-12-10 03:19:56 +01:00
SomberNight
b99add59c3
lnworker: introduce PaymentAttemptLog NamedTuple 2019-12-10 03:17:57 +01:00
SomberNight
24ebc77d76
ln chan verifier: fix code rot 2019-12-10 01:14:38 +01:00
SomberNight
0ab88b821c
keystore: use abstract base classes, introduce MPKMixin 2019-12-10 00:31:01 +01:00
SomberNight
f2d42d79ba
qt: rm redundant line: ConfirmTxDialog.update_tx()
already called in ConfirmTxDialog.__init__
2019-12-09 19:14:15 +01:00
SomberNight
d641dfe964
follow-up prev: add comment 2019-12-09 17:43:08 +01:00
ThomasV
0828454ef1
Merge pull request #5830 from SomberNight/20191209_wallet_perf
wallet perf: significant speedup for make_unsigned_transaction and rel.
2019-12-09 17:30:10 +01:00
ThomasV
9d83dea0dc
Merge pull request #5822 from SomberNight/201912_qt_receive_tab_address
qt receive tab: show plain bitcoin address
2019-12-09 10:57:14 +01:00
SomberNight
59c5efb090
keystore: cache BIP32Node.from_xkey(self.xpub)
This results in significant performance improvements for
keystore.can_sign() and wallet._add_txinout_derivation_info()
2019-12-09 03:43:47 +01:00
SomberNight
f73b6b5d23
keystore: cache derive_pubkey 2019-12-09 03:35:20 +01:00
SomberNight
5f6f7da2a1
bitcoin.py: base58 address: make sure all public methods test checksum
Note: the checksum was already being checked in practically all cases, by the caller.
Moved the check here, to the lower level (but still public) method for sanity.
2019-12-08 06:56:19 +01:00
SomberNight
8cf3587aeb
base_encode/base_decode: change to saner API 2019-12-08 06:19:51 +01:00
SomberNight
01f94fcf58
base_encode/base_decode: performance improvement
For example, for 50 KB of random data, and base 43,
previously,
- base_encode took ~38 seconds
- base_decode took ~270 seconds
now,
- base_encode takes ~7.5 seconds
- base_decode takes ~6 seconds
2019-12-08 06:07:01 +01:00
SomberNight
5c9bd2d2b4
ln channel open: save funding tx as local tx into wallet 2019-12-08 04:33:36 +01:00
SomberNight
30dcab0877
wallet: allow saving partial txns as local (but require txid) 2019-12-08 04:32:44 +01:00
SomberNight
369d972aed
qt: handle exceptions when pressing "Max" button
fixes #5783
2019-12-08 03:21:02 +01:00
SomberNight
d2a8028cde
qt receive tab: show plain bitcoin address 2019-12-07 06:06:36 +01:00
SomberNight
20bbe85bce
receive requests: encode lightning invoices as uppercase -> smaller QRs
By encoding bolt11 invoices as uppercase text in QR codes,
we can use the alphanumeric mode, which results in non-negligibly smaller QR codes.
2019-12-07 05:58:58 +01:00
SomberNight
8e89c0c971
wallet: some clean-up re get_address_history vs db.get_addr_history
note: tests needed changing due to behavioural change in wallet.get_receiving_address()
Previously wallet.get_receiving_address used wallet.db.get_addr_history,
now it (indirectly) uses wallet.get_address_history, which now also considers local txns.
2019-12-07 05:42:28 +01:00
SomberNight
d81110014e
qt requests/invoices: use TreeView.sortByColumn instead of model.sort
sort the view, not the model
This way, qt will display the icon indicating the sort order on the relevant column header.
2019-12-07 04:28:08 +01:00
SomberNight
9f9b0954e2
appimage: update package in dockerfile
Ubuntu no longer serves old version
2019-12-06 22:02:17 +01:00
SomberNight
f24dea0277
add SECURITY.md 2019-12-06 21:47:28 +01:00
SomberNight
e0eb3c18eb
qt ConfirmTxDialog: don't catch BaseException for make_tx
not sure what it is supposed to catch...
The examples I could come up with would all be actual bugs;
in which case we should let the exception propagate out to the crash reporter.
2019-12-06 20:41:51 +01:00
ThomasV
a6aa97c3e3
Merge pull request #5820 from SomberNight/201912_ecdsa_sig_r_grinding
ECDSA signatures: grind low R to match with Bitcoin Core (take 2)
2019-12-06 20:38:46 +01:00
ThomasV
2e4cfd0744 fix race in NetworkJobOnDefaultServer constructors 2019-12-06 20:17:52 +01:00
SomberNight
61aebd0f2d
(fix) qt coin selection: signatures for coins would persist in memory
Scenario: select some UTXOs in the 'Coins' tab. Create a tx and sign it.
Close the tx dialog without broadcasting/etc (cancel tx).
Signatures would remain for selected UTXOs.
Create new tx -> invalid sigs.
2019-12-06 19:45:55 +01:00
SomberNight
5b88b8667e
also grind ecdsa low R when using libsecp256k1, and fix tests
note: low R grinding would not have to be duplicated if we trusted the caller
to have done it already (as is the case with the classes in ecc.py), and if
we propagated the choice of "random_k" as part of the nonce_function passed
to libsecp256k1 (which is not currently done)
2019-12-05 20:27:55 +01:00
junderw
d16fd2783c
Add signature Low R grinding to match with Bitcoin Core
Ref: https://github.com/bitcoin/bitcoin/pull/13666

Depends on python-ecdsa pull request to allow for extra_entropy
Ref: https://github.com/warner/python-ecdsa/pull/92
2019-12-05 18:11:11 +01:00
SomberNight
428b63822b
trezor: rm obsolete gui text 2019-12-04 20:54:53 +01:00
SomberNight
69720946c1
appimage: update package in dockerfile
Ubuntu no longer serves old version
2019-12-04 20:53:48 +01:00
Janus Troelsen
3ac8f461a9 Tests: Remove on_channels_updated (#5819) 2019-12-04 19:40:52 +00:00
SomberNight
00a7df13bf
rerun freeze_packages 2019-12-04 20:04:18 +01:00
ThomasV
065e98ad35 on_open_channel: rm call to non-existing method on_channels_updated 2019-12-04 18:35:04 +01:00
SomberNight
d3fd87ebd0
hardware wallets: wizard no longer requests xpub at path "m"
This was done to calculate the bip32 root fingerprint but it broke
the digitalbitbox. The keystore already had a different way to get
the root fingerprint for existing wallets, specifically handling this
case; the code in base_wizard used when creating new wallets was
duplicating that code originally and was then forgotten to be updated.
Now these codepaths are unified.

closes #5816
2019-12-02 19:31:17 +01:00
SomberNight
68dad21fb4
network: make best_effort_reliable smarter and a bit more lenient
related: #5815
2019-12-01 23:24:43 +01:00
ThomasV
dfdc1e1d25 require ecdsa version >= 0.13.3 2019-11-29 18:38:53 +01:00
ThomasV
6659f5c2c0
Merge pull request #5740 from spesmilo/dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
build(deps): bump ecdsa from 0.13.2 to 0.13.3 in /contrib/deterministic-build
2019-11-29 18:35:37 +01:00
ThomasV
7a080352f8
Merge pull request #5809 from SomberNight/201911_invoice_paid_detection
wallet: better (outgoing) invoice "paid" detection
2019-11-29 18:24:08 +01:00
SomberNight
8dbbc21aff
wallet: better (outgoing) invoice "paid" detection
- no more passing around "invoice" in GUIs, invoice "paid" detection is now handled by wallet logic
- a tx can now pay for multiple invoices
- an invoice can now be paid by multiple txs (through partial payments)
- new data structure in storage: prevouts_by_scripthash
  - type: scripthash -> set of (outpoint, value)
  - also, storage upgrade to build this for existing wallets
2019-11-29 15:06:16 +01:00
SomberNight
cfbd83c432
wallet: minor clean-up 2019-11-29 13:12:23 +01:00
SomberNight
c0b5ebcc5d
tests: fix testcase test_restoring_wallet_with_manual_delete 2019-11-29 13:09:21 +01:00
SomberNight
adaa016e78
LNPeerAddr: fix equality tests and hence lnworker._last_tried_peer
follow-up 13d6997355
2019-11-29 00:53:18 +01:00
roth
2ce8dd460b Color-Code Addresses in Kivy UI (#5774)
* Added static coloring of the TX output dialog.

Coloring was suggested in #5750
2019-11-28 20:40:28 +00:00
SomberNight
4007720b34
qt history list: rm and fix magic number 2019-11-27 20:29:49 +01:00
ThomasV
f7fb14a538
Merge pull request #5806 from RCasatta/remove_greenaddress
Remove GreenAddress instant plugin
2019-11-27 18:37:50 +01:00
Riccardo Casatta
8c30ae78be
remove GreenAddress instant plugin
GreenAddress instant feature has been removed from the service, thus
there is no reason anymore to keep the plugin
2019-11-27 17:51:25 +01:00
SomberNight
a13344938f
interface: fix connecting to raw IPv6 (as hostname) on Windows
Changed cert pinning filename as on Windows paths cannot contain a colon ':'.
2019-11-27 13:08:22 +01:00
SomberNight
cee2083134
qt history list: fix UnboundLocalError when searching
closes #5801
2019-11-27 04:30:38 +01:00
SomberNight
d430ec4bfc
interface.deserialize_server: better ipv6 handling 2019-11-26 00:17:00 +01:00
SomberNight
13d6997355
LNPeerAddr: validate arguments
no longer subclassing NamedTuple (as it is difficult to do validation then...)
2019-11-26 00:15:33 +01:00
SomberNight
edba59ef54
LNPeerAddr: nicer str formatting for IPv6 hosts 2019-11-25 21:10:53 +01:00
SomberNight
ddeb176b3d
kivy: fix open_channel (API was changed) 2019-11-23 20:50:30 +01:00
SomberNight
557987d4eb
add/fix some open_channel related type hints 2019-11-23 20:28:46 +01:00
ThomasV
038036f350 minor follow-up prev commit 2019-11-23 19:56:40 +01:00
ThomasV
fd8236538a Open lightning channels with partially signed tx.
Fixes #5379.
2019-11-23 19:49:12 +01:00
ThomasV
9c9ceb702a fix #5729 2019-11-23 15:12:16 +01:00
ThomasV
b9e5edd704 fix #5728 2019-11-23 14:51:39 +01:00
ThomasV
06589df812 simplify add_transaction 2019-11-23 12:46:43 +01:00
ThomasV
fc85dcead6 follow-up previous commit 2019-11-23 11:37:01 +01:00
ThomasV
6c62fb03ac fix #5733 2019-11-23 11:02:31 +01:00
SomberNight
88307357ec
add some type hints
mostly related to hw wallets
2019-11-22 22:59:33 +01:00
SomberNight
770ae6d878
fix tests 2019-11-22 22:11:56 +01:00
SomberNight
bda9a407d9
trivial: don't print frequent-case log line in lnpeer.mark_open 2019-11-22 21:37:15 +01:00
SomberNight
268e245322
lnpeer: only set initialized after both sent AND received "init"
had a trace where we tried to send "funding_locked" before being initialized:

D | lnpeer.Peer.[iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion:9735] | Sending FUNDING_LOCKED
E | lnworker.LNWallet | Exception in on_update_open_channel: AttributeError("'LNTransport' object has no attribute 'sk'")
Traceback (most recent call last):
  File "...\electrum\electrum\util.py", line 999, in wrapper
    return await func(*args, **kwargs)
  File "...\electrum\electrum\lnworker.py", line 674, in on_update_open_channel
    peer.send_funding_locked(chan)
  File "...\electrum\electrum\lnpeer.py", line 876, in send_funding_locked
    self.send_message("funding_locked", channel_id=channel_id, next_per_commitment_point=per_commitment_point_second)
  File "...\electrum\electrum\lnpeer.py", line 102, in send_message
    self.transport.send_bytes(raw_msg)
  File "...\electrum\electrum\lntransport.py", line 93, in send_bytes
    lc = aead_encrypt(self.sk, self.sn(), b'', l)
AttributeError: 'LNTransport' object has no attribute 'sk'
2019-11-22 21:33:56 +01:00
ThomasV
61dfcba092 Refactor channel states:
- persisted states are saved
 - state transitions are checked
 - transient states are stored in channel.peer_state
 - new channel states: 'PREOPENING', 'FUNDED' and 'REDEEMED'
 - upgrade storage to version 21
2019-11-22 20:14:54 +01:00
SomberNight
c31fa059fe
cli: clear up "rbf" arg for "payto" cmd in help text; and use eval_bool
related: #5791

(previously rbf was a str, and it was casted to a bool directly, i.e. only
the empty string "" evaluated as False)
2019-11-22 16:09:42 +01:00
SomberNight
420b1a6636
cli: load_wallet now auto-upgrades the WalletStorage when needed
previously it would bail out and just return False
2019-11-22 15:54:34 +01:00
SomberNight
3d88d6870c
cli: fix load_wallet for storage-encrypted wallets 2019-11-22 15:48:22 +01:00
ThomasV
8e08ca7cb1 simplify network callbacks in lnworker 2019-11-22 15:06:37 +01:00
ThomasV
b469df5283 check channel funding_tx amount and script in save_short_channel_id 2019-11-21 20:16:04 +01:00
SomberNight
c2b0039935
bitcoin.py: remove some remnants of TYPE_ADDRESS, TYPE_SCRIPT 2019-11-21 18:51:38 +01:00
SomberNight
03cc63205f
trivial: use logger.exception instead of traceback.print_exc 2019-11-21 17:55:00 +01:00
SomberNight
6f246a83b3
qt coin selection: allow selecting an empty set
Using this, the user can force "bump fee" not to add new inputs.

closes #5719
2019-11-21 17:46:00 +01:00
SomberNight
11f54aee60
qt utxo list: spend_list is now a set (and renamed)
this is a small performance improvement ("if x in spend_list" was linear)
and the "order" of selected coins does not matter anyway
2019-11-21 17:21:54 +01:00
SomberNight
216d9e3c4d
lnpeer: (fix) force_close_channel was not awaited in some cases 2019-11-21 16:37:43 +01:00
ghost43
d5b27e5be1
Merge pull request #5787 from xaya/http-request-auth
Return 401 from RPC server for missing auth
2019-11-21 15:21:17 +00:00
Daniel Kraft
423c4b0695
Return 401 from RPC server for missing auth.
When no (supported) authentication is passed to the JSON-RPC server,
return a 401 HTTP error code instead of 403.  This indicates to the
client that authentication is required, and also requests that to be
sent using the "basic" method.  The previously-returned code 403 is now
only returned if authentication is passed but not valid.

There are some JSON-RPC clients out there that only send authentication
after a 401 code requested it.  Those fail to connect to the Electrum
RPC interface even if the correct password is configured.  Those same
clients can e.g. connect to Bitcoin Core successfully, which already
implements logic matching this change.

See also https://stackoverflow.com/questions/3297048/403-forbidden-vs-401-unauthorized-http-responses.
2019-11-21 15:16:37 +01:00
SomberNight
6b195437ed
wallet: "future" txns num conf is now negative
flipped the sign so that TxMinedInfo.conf can be consistently used in inequalities
2019-11-21 05:01:59 +01:00
SomberNight
1526bc9ccf
qt: consistently show tooltip when copying to clipboard 2019-11-21 03:01:55 +01:00
Johann Bauer
9fe7917118
Qt: Remove unused color from color scheme 2019-11-20 21:11:41 +01:00
SomberNight
06de2660cf
kivy: support invoices with "max" amount
closes #5781
2019-11-20 19:29:29 +01:00
SomberNight
49284f716b
wallet: bump fee now supports coin selection
related: #5719
2019-11-20 18:43:05 +01:00
SomberNight
fec9677508
qt open channel: minor dialog fixes 2019-11-20 18:00:45 +01:00
SomberNight
cd9477c0da
ln: qt channel open: fix max channel size 2019-11-20 17:45:28 +01:00
SomberNight
ae04434206
ln: update list of fallback nodes
with some popular nodes from 1ml.com
2019-11-20 17:14:14 +01:00
SomberNight
4057140e6a
lightning qr codes: more robust parsing
kivy qr code handling did not accept "lightning:" prefix or uppercase
2019-11-20 03:21:59 +01:00
SomberNight
d1c262def0
qt tx dialog: small clean-up in constructors 2019-11-19 22:17:52 +01:00
SomberNight
97056ae44d
qt send tab: subtract 2fa fee when clicking "spend max" 2019-11-19 21:22:49 +01:00
SomberNight
ca6654c102
qt send tab: don't allow paying multiple invoices that spend max '!' 2019-11-19 20:26:50 +01:00
SomberNight
710e9621b5
qt tx dialog: allow blanking feerate
Previously it was impossible to rm the last character in feerate_edit.
e.g. if you held down "backspace", we would keep refilling the field as soon
as it became empty.
2019-11-19 20:13:16 +01:00
SomberNight
13b858ab26
qt tx dialog: disable "Finalize" btn if tx is None
e.g. NotEnoughFunds due to too high fee
2019-11-19 19:35:57 +01:00
SomberNight
66ddedb97e
qt tx dialog: small fee edit fix
scenario: enter extremely high feerate (which we cannot satisfy) then click into fee_edit.
At that moment, fee_edit is empty and both feerate_edit and fee_edit are considered frozen.
As fee_edit has priority, we would construct a tx with default fee.
Now, instead, we won't construct this default fee tx ~as if the click to fee_edit did not happen.
2019-11-19 19:29:10 +01:00
SomberNight
8bd27851a4
qt tx dialog: only allow "save as local" for complete txns 2019-11-19 18:41:44 +01:00
SomberNight
aa3d817ef2
qt: clean-up imports 2019-11-18 20:56:49 +01:00
SomberNight
b8e4ce9ba1
hardware wallets: handle when label is None
follow-up 56c3de0e1e
2019-11-18 18:13:26 +01:00
ThomasV
aa37979100 fix #5761 2019-11-18 10:22:20 +01:00
ThomasV
b9cf095e1a fix #5761 2019-11-18 05:48:21 +01:00
ThomasV
ade47e331a Merge branch 'master' of github.com:spesmilo/electrum 2019-11-18 05:12:25 +01:00
ThomasV
47e0b4cd02 fix #5765 2019-11-18 05:12:13 +01:00
ghost43
f2ca651dc4
Merge pull request #5775 from JeremyRand/resolver-wallet
Fix missing wallet argument to _resolver
2019-11-18 02:11:06 +00:00
JeremyRand
643bc9d802
Fix missing wallet argument to _resolver 2019-11-18 01:31:33 +00:00
ThomasV
6b9971a466 fix #5767 2019-11-17 19:29:48 +01:00
ThomasV
0a8f511340 fix #5772 2019-11-17 19:25:11 +01:00
ThomasV
a453189d37 fix #5766 2019-11-17 17:08:09 +01:00
ThomasV
af21a4a8d6 fix #5770 2019-11-17 16:58:35 +01:00
ThomasV
3083237363
Merge pull request #5768 from lukechilds/ln-negative-red
Show outgoing Lightning payments with red description
2019-11-17 13:58:16 +01:00
ThomasV
e74f7e7b42 fix #5764 2019-11-17 13:48:19 +01:00
Luke Childs
bcae8ed1ad Show outgoing Lightning paymenst with red description 2019-11-17 15:28:17 +07:00
SomberNight
cbd146ad15
hardware wallets: detect if label changed and update it in wallet file 2019-11-17 01:17:38 +01:00
SomberNight
56c3de0e1e
hardware wallets: better handle label collision when selecting device
related: #5759
2019-11-17 01:15:44 +01:00
SomberNight
6e3875ceab
fix qt seed dialog (follow-up af86c7e3fd) 2019-11-15 23:50:05 +01:00
SomberNight
600b26eed6
hardware wallets: pull udev rules into our repository
README.md heavily based on 060c7fc618/hwilib/udev/README.md
2019-11-15 23:43:43 +01:00
SomberNight
6ebbaa60ef
old_mnemonic: speed up mn_decode
mn_decode is used by mnemonic.make_seed which now takes around 25% less time
2019-11-15 12:08:06 +01:00
ghost43
bc4f22503f
Merge pull request #5758 from mbarkhau/master
Mnemonic performance improvements
2019-11-15 09:59:40 +00:00
Manuel Barkhau
af86c7e3fd fix: cache wordlists 2019-11-14 18:57:18 +00:00
Manuel Barkhau
b3f913340c make Mnemonic.mnemonic_decode faster
list.index(word) is O(n)
dict[word] is O(log(n))

This makes a difference for Mnemonic.make_seed which
calls self.mnemonic_decode repeatedly.
2019-11-14 18:54:14 +00:00
ThomasV
c2c291dd3a fix #5757 2019-11-14 10:30:06 +01:00
ThomasV
78813dcb7d Pass make_tx function to ConfirmTxDialog
- allow 'spend max' when opening a channel (fixes #5698)
 - display amount minus fee when 'max' buttons are pressed
 - estimate fee of channel funding using a template with dummy address
2019-11-14 10:20:19 +01:00
SomberNight
970bd4e95f
qt coin control: introduce second status bar 2019-11-13 19:09:07 +01:00
SomberNight
800c05b32f
qt addresses tab: fix "Spend from" 2019-11-13 05:46:45 +01:00
SomberNight
0c0a6b2145
TxDialog: fix hooks. only show psbt widgets when applicable.
users of 'transaction_dialog' were assuming that dialog.tx is already set
2019-11-12 23:05:01 +01:00
SomberNight
05c496edd1
PreviewTxDialog: small UI changes 2019-11-12 23:04:57 +01:00
SomberNight
9627f32e08
qt utxo list fixes 2019-11-12 23:04:54 +01:00
SomberNight
74790c16f9
PreviewTxDialog: fix RBF checkbox 2019-11-12 23:04:50 +01:00
SomberNight
e7efc3657b
fix Qt tx dialog randomly disappearing (due to gc) 2019-11-12 23:04:47 +01:00
SomberNight
1e77562bcb
qt/confirm_tx_dialog: fix qt warning
QLayout::addChildLayout: layout "" already has a parent
2019-11-12 23:04:42 +01:00
ThomasV
8c3af39a9b add option to pay multiple invoices 2019-11-12 18:37:35 +01:00
ThomasV
dd6cb2caf7 GUI: Separate output selection and transaction finalization.
- Output selection belongs in the Send tab.
 - Tx finalization is performed in a confirmation dialog
   (ConfirmTxDialog or PreviewTxDialog)
 - the fee slider is shown in the confirmation dialog
 - coin control works by selecting items in the coins tab
 - user can save invoices and pay them later
 - ConfirmTxDialog is used when opening channels and sweeping keys
2019-11-12 14:42:06 +01:00
SomberNight
f8c84fbb1e
hardware wallets: create base class for HW Clients. add some type hints 2019-11-11 17:04:12 +01:00
SomberNight
2fec17760d
qt address list: check internal address corruption when copying address
regressing following c721e880d0

note that place_text_on_clipboard is overridden in AddressList
2019-11-11 15:51:23 +01:00
SomberNight
5549f3adbe
CoinChooser: avoid NotEnoughFunds if zero buckets are sufficient
closes #5752

Adapted from @JeremyRand's fix
2019-11-11 15:15:04 +01:00
ThomasV
5773097b08 rename 'copy column' to 'copy' 2019-11-10 07:55:37 +01:00
SomberNight
bf8a58c0b4
tx_from_any: strip whitespaces
see https://github.com/spesmilo/electrum/pull/5721#issuecomment-551876236
2019-11-08 17:51:48 +01:00
SomberNight
365aa189f2
qt FileDialogs for transactions: better file extension filter 2019-11-08 15:22:11 +01:00
SomberNight
9ff7d2c5a7
transactions (qt): fix opening raw bytes files
(both when trying to "load tx from file", and "load tx from text" > "browse file")
2019-11-08 15:10:54 +01:00
SomberNight
85a4811742
transaction.tx_from_any: recognise even more types, and add tests 2019-11-08 15:01:18 +01:00
SomberNight
fef1ddd416
wallet: fix #5748 2019-11-08 13:43:12 +01:00
SomberNight
7b18c91b74
psbt follow-up: fix ln cooperative close, and minor type clean up 2019-11-07 18:28:27 +01:00
ThomasV
707b74d22b
Merge pull request #5721 from SomberNight/201910_psbt
integrate PSBT support natively. WIP
2019-11-07 17:10:20 +01:00
SomberNight
cd49839bc0
transaction: helpful error msg if user tries to load old partial tx 2019-11-07 07:07:02 +01:00
SomberNight
27df235c26
transactions: reading QR codes: clean-up and accept all encodings 2019-11-07 06:33:15 +01:00
SomberNight
29a6e3c019
psbt: implement PSBT_GLOBAL_VERSION field
based on latest BIP-0174 update: bitcoin/bips#849
2019-11-07 03:43:00 +01:00
SomberNight
83740c1a78
psbt: implement CompactSize key types (previously single-byte types)
based on latest BIP-0174 update: bitcoin/bips#849
2019-11-07 03:27:38 +01:00
SomberNight
aa518c0ea5
psbt: allow insecure signing of legacy UTXOs without full previous tx
When "importing" a psbt, we accept witness utxos even for legacy inputs
(warning shown to user in gui).
When "exporting" a psbt, we follow the spec; except when exporting as a QR code,
in which case we include witness utxos for all inputs.
This makes QR codes for psbts with legacy inputs feasible, just like they
were before, with our custom tx serialization format (with the same risk,
of burning coins as miner fees).
2019-11-07 02:40:10 +01:00
SomberNight
74a46689d8
kivy tx dialog: was missing tx.add_input_from_wallet() call
resulted in e.g. incorrect "tx unrelated to wallet" detection for beyond-gap-limit stuff
2019-11-07 02:26:58 +01:00
SomberNight
8a7c3447b3
tx dialog: try harder to show fee 2019-11-07 02:24:16 +01:00
SomberNight
6573e7f1f3
test_wallet_vertical: add test for manual coinjoin 2019-11-06 18:40:16 +01:00
SomberNight
8e09d429c0
psbt: "updater" must swap NON_WITNESS_UTXO for WITNESS_UTXO if txin is segwit 2019-11-06 03:46:00 +01:00
SomberNight
c077d77316
psbt: test_wallet_vertical: add asserts to avoid silent breakage of psbts we create 2019-11-06 03:42:14 +01:00
SomberNight
955caa7292
transaction: to_json() debug methods display bip32 str (not int) paths 2019-11-05 23:39:50 +01:00
SomberNight
46db33df75
psbt: follow-ups: BCDataStream.read_bytes() should return bytes
This fixes keepkey, as in particular the code in the plugin expected
TxOutpoint.txid to be bytes not a bytearray (and the TxOutpoint named tuple
itself claims txid to be bytes).
2019-11-05 23:35:32 +01:00
SomberNight
cc4f6804b0
psbt: follow-ups: fix trezor 2019-11-05 23:32:00 +01:00
SomberNight
dd14a3fde5
psbt: follow-ups: fix digital bitbox 2019-11-05 22:06:46 +01:00
SomberNight
9e86352022
psbt: follow-ups: fix ledger 2019-11-05 21:34:54 +01:00
SomberNight
26a5f212cb
psbt: cleaner API for serialize* methods 2019-11-04 22:25:16 +01:00
SomberNight
1017fefdc9
psbt: only include xpubs for multisig wallets 2019-11-04 22:25:13 +01:00
SomberNight
90b190bbcd
psbt: fix bug re witness_utxo serialization 2019-11-04 22:25:09 +01:00
SomberNight
c8c1ea9c86
qt tx dialog: add export options for coinjoins and for coldcard 2019-11-04 22:25:06 +01:00
SomberNight
d872be7f6b
psbt: don't put xpubs and full paths into tx by def; only while signing 2019-11-04 22:25:02 +01:00
SomberNight
e6c841d05f
psbt: put fake xpubs into globals. keystores handle xfp/der_prefix missing 2019-11-04 22:24:59 +01:00
SomberNight
7eb7eb8674
add support for manual coinjoins 2019-11-04 22:24:55 +01:00
SomberNight
bafe8a2fff
integrate PSBT support natively. WIP 2019-11-04 22:24:36 +01:00
ghost43
6d12ebabbb
qt tx dialog: show dropdown for "export", instead of separate buttons (#5739) 2019-11-04 16:24:55 +00:00
ghost43
53dea824a4
Merge pull request #5742 from Coldcard/master
plugins/coldcard/qt.py: bugfix for API change on main_window.show_transaction
2019-11-04 14:25:51 +00:00
Peter D. Gray
ec2bdbd02d
plugins/coldcard/qt.py: bugfix for API change on main_window.show_transaction 2019-11-04 09:21:41 -05:00
dependabot[bot]
ed5300ba1d
build(deps): bump ecdsa in /contrib/deterministic-build
Bumps [ecdsa](https://github.com/warner/python-ecdsa) from 0.13.2 to 0.13.3.
- [Release notes](https://github.com/warner/python-ecdsa/releases)
- [Changelog](https://github.com/warner/python-ecdsa/blob/master/NEWS)
- [Commits](https://github.com/warner/python-ecdsa/compare/python-ecdsa-0.13.2...python-ecdsa-0.13.3)

Signed-off-by: dependabot[bot] <support@github.com>
2019-11-02 09:23:39 +00:00
SomberNight
3e98123b17
qt settings dialog: small fixes 2019-11-02 06:13:11 +01:00
SomberNight
f72bb03af6
commands: gettransaction cmd now tests txid before returning tx
thanks for @JeremyRand
ref #5660
2019-11-02 05:46:52 +01:00
SomberNight
3bfcfb49c3
fix qt invoices list: handle lightning disabled
closes #5738
2019-11-02 03:44:22 +01:00
SomberNight
ccccaf099f
(trivial) logging "verbosity_filter" was renamed to "LOGGING_SHORTCUT" 2019-10-30 03:24:26 +01:00
SomberNight
75902f8e35
fix logic error in lnworker.on_channel_closed 2019-10-29 20:39:58 +01:00
ThomasV
427f7f8eeb fix #5731 2019-10-29 08:06:53 +01:00
SomberNight
a20317fe2a
fix #5726 2019-10-28 21:17:20 +01:00
ThomasV
7b5869c7bc kivy:
- simplify menu
 - add lightning network dialog
2019-10-25 16:46:52 +02:00
ThomasV
2dd368e76e minor fix: show_transaction params 2019-10-24 18:45:51 +02:00
ThomasV
5c1340b7bd pass both invoice and description to show_transaction 2019-10-24 17:27:14 +02:00
ThomasV
76c22f3e06 follow-up a0ec2690cf 2019-10-24 16:27:54 +02:00
ThomasV
24221584e2 fix clear_requests button 2019-10-24 09:32:40 +02:00
ThomasV
a0ec2690cf Call wallet.set_paid after onchain broadcast. Check if invoices are expired in util.get_request_status 2019-10-23 17:33:46 +02:00
ThomasV
e35bddcc09 kivy: fix can_pay in invoice_dialog 2019-10-23 08:33:16 +02:00
ThomasV
a4944cdcb2 delete channel from db in remove_channel, becaose it is called from lnworker 2019-10-23 08:20:15 +02:00
ThomasV
9fbac40d56 filter out None in wallet.get_invoices 2019-10-23 05:30:16 +02:00
ThomasV
9ff1bd4110 fix test following aac0fe9ae6 2019-10-22 18:54:00 +02:00
ThomasV
2234f93d8b fix fee in lnworker.get_history (sign error) 2019-10-22 18:27:41 +02:00
SomberNight
d9b5ab3086
wallet: fix bump_fee when there are only change outputs
closes #5718
closes #5603
2019-10-22 17:12:23 +02:00
ThomasV
aac0fe9ae6 kivy: show status with color. show inflight attempts. 2019-10-22 15:41:45 +02:00
ThomasV
cd86bec894 kivy: add 'delete channel' button 2019-10-22 12:27:55 +02:00
ThomasV
576fbbd074 kivy: minor fixes 2019-10-22 11:59:16 +02:00
Janus Troelsen
b86b3ec1d1 segwit_addr: Use normal comparison for tuple literal (#5712) 2019-10-16 23:50:17 +00:00
ThomasV
6992e33ecb folllow-up c721e880d0 2019-10-16 16:04:17 +02:00
ThomasV
c721e880d0 Qt: generic add_copy_menu method for MyTreeView lists 2019-10-16 15:50:18 +02:00
ThomasV
0a6ac7c61a fix #5702 2019-10-16 12:01:52 +02:00
ThomasV
d1f4804962 misc Qt fixes
- improve layout of send tab
 - use tabs to show receive widgets
 - add menu item to copy address from request
 - show copied content in copy dialog
2019-10-16 11:46:23 +02:00
ThomasV
d35791ff65 fix #5704 2019-10-16 11:35:50 +02:00
ThomasV
8c22be87b0 fix #5701 2019-10-16 11:13:44 +02:00
ghost43
d480d0b265
Merge pull request #5700 from fiatjaf/bugfixopenchannel
fix expected returned peer address values when opening channel.
2019-10-15 13:43:17 +00:00
SomberNight
106bc6d2b2
follow-up prev 2019-10-15 15:41:18 +02:00
fiatjaf
b476681af7 fix typo: wallet.requests -> wallet.receive_requests (#5706) 2019-10-15 13:22:05 +00:00
fiatjaf
38622c0a99 fix expected returned peer address values when opening channel. 2019-10-15 01:05:37 -03:00
ThomasV
3af7920b63 fix typo 2019-10-14 12:02:04 +02:00
ThomasV
1b0521cabd kivy: toggle lightning dialog 2019-10-14 11:59:04 +02:00
ThomasV
a13cea6f8a add remove_lightning command 2019-10-14 11:18:57 +02:00
ThomasV
db833e1ba3 lnworker: less verbose 2019-10-14 10:42:41 +02:00
ThomasV
c9d403cb7b minor: callback unknown_channels (follow-up 0966edc637) 2019-10-14 10:39:52 +02:00
ThomasV
90ce9f195b Allow user to enable lightning in the GUI. Make it a per-wallet setting. 2019-10-13 20:34:38 +02:00
ThomasV
a201ed44df Qt: add lightning button to status bar 2019-10-12 19:27:14 +02:00
ThomasV
0966edc637 fine-grained callbacks for lightning network dialog 2019-10-12 19:15:51 +02:00
ThomasV
fe550c6c73 payment log: show whether channel have been blacklisted 2019-10-12 18:36:25 +02:00
ThomasV
3897cf725d move handle_error_code_from_failed_htlc to lnworker because it requires access to the network object 2019-10-12 18:22:19 +02:00
ThomasV
af4a3328f7 Qt: separate lightning and watchtower dialogs 2019-10-12 14:30:52 +02:00
ThomasV
f985c53f2f fix #5695 2019-10-12 14:05:17 +02:00
ThomasV
16644ae00f follow-up previous commit 2019-10-12 13:57:54 +02:00
ThomasV
8f86a15f92 improve payment log dialog 2019-10-12 13:47:10 +02:00
ThomasV
5377eb907c follow-up dd0be1541e 2019-10-12 12:59:38 +02:00
SomberNight
2a604b1676
lnonion: get_failure_msg_from_onion_error might raise in python 3.7
this used to work in py3.6 but raises in py3.7 :(
(see https://bugs.python.org/issue34536)
2019-10-12 00:05:38 +02:00
ThomasV
c37d08cec9 bump version number to 4.0.0a0 (alpha version) 2019-10-11 18:13:48 +02:00
ThomasV
dd0be1541e Improve handling of lightning payment status:
- Move 'handle_error_code_from_failed_htlc' to channel_db,
and call it from pay_to_route, because it should not be
called when HTLCs are forwarded.
- Replace 'payment_received' and 'payment_status'
callbacks with 'invoice_status' and 'request_status'.
- Show payment error logs in the Qt GUI
- In the invoices list, show paid invoices for which
we still have the log.
2019-10-11 17:51:33 +02:00
ThomasV
d6d644190e lnworker: return error reason in await_payment 2019-10-11 13:37:54 +02:00
ThomasV
0557738a6b follow-up previous commit 2019-10-11 12:54:00 +02:00
ThomasV
c4ab1e6fad Encapsulate lightning payment events:
- make LNWorker.pending_payments private
 - public methods: payment_sent, payment_received, await_payment
2019-10-11 10:18:28 +02:00
ThomasV
638de63f13 lnworker: rename 'invoices' to 'payments' when they can be in both directions 2019-10-09 20:16:11 +02:00
ThomasV
788d54f9a6 remove another instance of lnworker accessed in lnchannel 2019-10-09 19:54:43 +02:00
ThomasV
8331f0049c Remove lnpeer.payment_preimages:
- we can await lnworker.pending_payments instead, because the preimage is saved
 - also, remove one instance of lnworker being accessed from lnchannel
2019-10-09 19:40:25 +02:00
ThomasV
b08cfac643 fix #5681 2019-10-07 17:24:49 +02:00
SomberNight
a51a2a7f8f
wallet: minor invoices fix
handle "lightning disabled & there are LN invoices in wallet" case
2019-10-07 05:29:34 +02:00
ThomasV
0dc90491b2 do not decode LN invoices in channel_details. fixes #5676 2019-10-05 19:26:26 +02:00
ThomasV
bcb10e6e53 remove redundant test from lnworker._pay, rename pay_to_route parameter to lnaddr 2019-10-04 18:06:53 +02:00
SomberNight
7c283f9cd2
fix tests: follow-up prev 2019-10-01 20:42:34 +02:00
SomberNight
8dabdf8bfb
qt send tab: handle invalid ln invoice; and ln invoice with ln disabled
fixes #5639
fixes #5662
2019-10-01 19:15:26 +02:00
ThomasV
1773bd6cd6
Merge pull request #5658 from vesellov/master
bug fix in  electrum/wallet.py
2019-09-26 13:11:34 +02:00
Veselin Penev
4f82bf9269 bug fix in electrum/wallet.py 2019-09-25 18:56:17 +02:00
ThomasV
f3eeb8817e
Merge pull request #5652 from SomberNight/20190922_config_no_longer_singleton
config: no longer singleton. it is passed to Wallet.__init__
2019-09-22 23:56:08 +02:00
SomberNight
ec372adbb9
tests: fix test_find_path_for_payment. need to close sqlite connection
test was sometimes randomly failing
(always on Windows, as it's illegal to rm open files there)
2019-09-22 21:21:24 +02:00
SomberNight
04edad9984
config: no longer singleton. it is passed to Wallet.__init__
The few other cases that used SimpleConfig.get_instance() now
either get passed a config instance, or they try to get a reference
to something else that has a reference to a config.
(see lnsweep, qt/qrcodewidget, qt/qrtextedit)
2019-09-22 20:46:01 +02:00
ThomasV
d6c7dee547 follow-up previous commit 2019-09-22 17:32:22 +02:00
ThomasV
a35421ab71 qt settings: create services tab for both watchtower and payserver 2019-09-22 17:12:48 +02:00
ThomasV
f08e5541ae Refactor invoices in lnworker.
- use InvoiceInfo (NamedTuple) for normal operations,
   because lndecode operations can be very slow.
 - all invoices/requests are stored in wallet
 - invoice expiration detection is performed in wallet
 - CLI commands: list_invoices, add_request, add_lightning_request
 - revert 0062c6d695 because it forbids self-payments
2019-09-22 16:06:53 +02:00
SomberNight
0a395fefbc
qt send tab: use monospace font in "from" UTXO-selection section 2019-09-22 06:20:57 +02:00
ghost43
f0d69d1dba
Merge pull request #5432 from JeremyRand/rpc-from-coins
Add from_coins arg to payto/paytomany
2019-09-21 20:50:19 +00:00
JeremyRand
7b91cd9cf4
Add from_coins arg to payto/paytomany
Fixes https://github.com/spesmilo/electrum/issues/5430
2019-09-21 20:07:16 +00:00
SomberNight
cbc3e13e28
qt: fix export history 2019-09-21 18:56:13 +02:00
SomberNight
6a32187f01
qt: fix address dialog
(was showing full history, not just for addr)
2019-09-21 18:48:44 +02:00
SomberNight
a1d7d39f68
commands: add type hints for "wallet" param, and fix code rot found via 2019-09-21 02:14:22 +02:00
SomberNight
1bc73b3475
lnworker.sync_with_remote_watchtower: use proxy 2019-09-19 18:50:57 +02:00
SomberNight
c63209fa9a
lnworker: accessing self.channels needs lock 2019-09-19 18:17:03 +02:00
ThomasV
6c055e80ed qt: show_info -> show_error 2019-09-19 12:01:23 +02:00
ThomasV
0062c6d695 lnworker: fix detection of already paid invoices 2019-09-19 11:57:47 +02:00
ThomasV
d6d5b99944 lnworker: fee_msat is expected for all entries in history 2019-09-19 11:06:44 +02:00
ThomasV
46346eacd7 settings: add vbox with stretch 2019-09-19 10:02:25 +02:00
ThomasV
d1a70bf1df settings: move block explorer to 'Transactions' tab 2019-09-19 09:11:46 +02:00
SomberNight
e9a1c05d23
bitcoin.relayfee: minor clean-up 2019-09-18 22:08:19 +02:00
SomberNight
8c1adc2f50
fix dns issue on Windows
closes #5638
2019-09-18 19:11:40 +02:00
SomberNight
c81f5395af
Merge pull request #5440 from Coldcard/multisig
Add multisig support for Coldcard plugin
2019-09-18 18:35:05 +02:00
SomberNight
26ff7a6265
coldcard tx dialog: "export PSBT" button should not raise on foreign ks
tested trezor/coldcard mixed multisig and it worked; no reason to enforce
that the first keystore must be coldcard (order should not matter)
2019-09-18 18:29:33 +02:00
SomberNight
1236b07abf
coldcard: show multisig address: more intuitive error msg
cause of error is probably almost always what msg says
2019-09-18 18:29:32 +02:00
SomberNight
9c83bc1008
coldcard qt: change receive_menu to be similar to other hw plugins
you should not interact with the hw device in the GUI thread if possible...
right-click was lagging
2019-09-18 18:29:32 +02:00
SomberNight
4e6cc93746
coldcard: do link_wallet in load_wallet hook instead
make_unsigned_transaction might not run in some code paths
(e.g. when user uses "Load transaction" UI function)
2019-09-18 18:29:31 +02:00
SomberNight
47c3ac6f1b
coldcard: follow-up prev 2019-09-18 18:29:31 +02:00
Peter D. Gray
c77fe6aafd
build_psbt.py: provide witness vs redeem or both scripts 2019-09-18 18:29:30 +02:00
Peter D. Gray
1692584ae0
coldcard/build_psbt.py: bugfix: dont assume all keystores implement get_derivation, see BIP32Keystore 2019-09-18 18:29:30 +02:00
Peter D. Gray
4baab751a4
Add multisig support for Coldcard plugin
-----

Squashed commit of the following:

commit 69c0d48108314db6f0e100bea2ce5a9a3a0e9a1f
Author: Peter D. Gray <peter@conalgo.com>
Date:   Fri Aug 2 14:51:33 2019 -0400

    deterministic-build/requirements-hw.txt: update to version 0.7.9 of ckcc-protocol for Coldcard

commit 5cd2c528698dfb4ad248844be3c741c25aa33e38
Merge: 5e2a36a3e 537b35586
Author: Peter D. Gray <peter@conalgo.com>
Date:   Fri Aug 2 14:41:59 2019 -0400

    Merge branch 'multisig' of github.com:Coldcard/electrum into multisig

commit 5e2a36a3ee28780a11f789f69896e6e795621bfc
Author: Peter D. Gray <peter@conalgo.com>
Date:   Fri Aug 2 14:41:49 2019 -0400

    Some fixes for p2wsh-p2sh and p2wsh cases

commit 537b35586e0b1e11622a8e7d718b6fd37d47f952
Merge: a9e3ca47e 2a80f6a3a
Author: nvk <rodolfo@rnvk.org>
Date:   Tue Jul 23 11:40:39 2019 -0400

    Merge branch 'master' into multisig

commit a9e3ca47e189bcf0556703a4f2ca0c084638eb73
Author: Peter D. Gray <peter@conalgo.com>
Date:   Mon Jun 24 13:36:41 2019 -0400

    Bugfix: not all keystores have labels

commit 57783ec158af5ca8d63d596638bc3b6ee63b053f
Author: Peter D. Gray <peter@conalgo.com>
Date:   Mon Jun 24 13:36:04 2019 -0400

    Add address format to export data, and bugfix: use xfp_for_keystore()

commit 6f1f7673eaa340d14497b11c2453f03a73b38850
Author: Peter D. Gray <peter@conalgo.com>
Date:   Fri Jun 21 09:06:49 2019 -0400

    Revert "bugfix: P2SH inputs can be signed with extra signatures, more than required"

    This reverts commit 75b6b663eca9e7b5edc9a463f7acd8f1c0f0a61a.

commit c322fb6dd2783e1103f5bf69ce60a365fbaf4bfe
Author: Peter D. Gray <peter@conalgo.com>
Date:   Thu Jun 20 12:57:19 2019 -0400

    Require latest CKCC protocol

commit 69a5b781ebc182851d2e25319b549ec58ea23eb1
Author: Peter D. Gray <peter@conalgo.com>
Date:   Thu Jun 20 12:40:27 2019 -0400

    gui/qt/main_window.py: add co-signer keystore label to wallet info display, and a hook for different buttons

commit 55d506d264dbb341602630c3429134e493995272
Author: Peter D. Gray <peter@conalgo.com>
Date:   Thu Jun 20 12:36:10 2019 -0400

    PSBT Combining/cleanup

commit 75b6b663eca9e7b5edc9a463f7acd8f1c0f0a61a
Author: Peter D. Gray <peter@conalgo.com>
Date:   Thu Jun 20 10:18:02 2019 -0400

    bugfix: P2SH inputs can be signed with extra signatures, more than required

commit 1bde362ddbbfd86520a7cb7bc51e0bcef06be078
Author: Peter D. Gray <peter@conalgo.com>
Date:   Wed Jun 19 09:47:26 2019 -0400

    Combines signed PSBT files

commit cc5c0532e52fbe282e862e20c250cc88ed435cad
Author: Peter D. Gray <peter@conalgo.com>
Date:   Fri Jun 14 13:04:32 2019 -0400

    Working towards multisig

commit cb20da5428ba97237006683133e10b0758999966
Author: Peter D. Gray <peter@conalgo.com>
Date:   Fri Jun 14 13:04:18 2019 -0400

    Refactor/import PSBT handling code into own files

commit 558ef82bb0a8c16fb4e8bd0a6a80190498f1ce57
Author: Peter D. Gray <peter@conalgo.com>
Date:   Tue May 28 13:26:10 2019 -0400

    plugins/hw_wallet/qt.py: show keystore label in tooltip

commit 269299df4a9eb5960b6c6ec0afcbf3ef69ad0be3
Author: Peter D. Gray <peter@conalgo.com>
Date:   Mon May 27 09:32:43 2019 -0400

    Swap endian of xpub fingprint values, so they are shown as BE32 in capitalized hex, rather than 0x%08x (LE32)
2019-09-18 18:29:29 +02:00
SomberNight
bd83ca0286
qt: (trivial) some type hints 2019-09-18 02:10:53 +02:00
SomberNight
30bb7dd6f4
ecc: small clean-up 2019-09-18 02:09:15 +02:00
SomberNight
1669dd9782
simplify prev
following @markblundeberg's suggestion
https://github.com/Electron-Cash/Electron-Cash/pull/1621#issuecomment-532070318
2019-09-17 19:27:28 +02:00
SomberNight
65d896be5a
ecc: also use libsecp256k1 for point addition
time taken to add points changes to around 35% of what it was with python-ecdsa

-----

# benchmark runs before:
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 3.7693 seconds
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 3.8123 seconds
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 3.7937 seconds

# benchmark runs after:
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 1.3127 seconds
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 1.3000 seconds
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 1.3128 seconds

-----

# benchmark script:

import os
import time
from electrum.ecc import generator
from electrum.crypto import sha256

rand_bytes = os.urandom(32)
#rand_bytes = bytes.fromhex('d3d88983b91ee6dfd546ccf89b9a1ffb23b01bf2eef322c2808cb3d951a3c116')
point_pairs = []
for i in range(30000):
    rand_bytes = sha256(rand_bytes)
    rand_int = int.from_bytes(rand_bytes, "big")
    a = generator() * rand_int
    rand_bytes = sha256(rand_bytes)
    rand_int = int.from_bytes(rand_bytes, "big")
    b = generator() * rand_int
    point_pairs.append((a,b))

t0 = time.time()
for a, b in point_pairs:
    c = a + b
t = time.time() - t0
print(f"time taken: {t:.4f} seconds")
2019-09-16 20:43:13 +02:00
SomberNight
49a2dbb021
kivy: receive to ln invoice should be disabled if lightning is disabled 2019-09-16 16:19:19 +02:00
SomberNight
ff9cc4d292
kivy: fix "use change addresses" setting (previously ignored, always on)
this config setting is stored in the WalletStorage unlike most others

closes #5643
2019-09-16 16:01:52 +02:00
SomberNight
0b87ce426f
minor qt send tab fixes. notably 'send max' was broken
follow-up aaed594772
2019-09-16 02:54:32 +02:00
SomberNight
5e04f084b7
qt wizard: follow-up prev; simplify 2019-09-14 16:25:35 +02:00
SomberNight
f44f7d60ab
qt wizard: still show selector window if storage.__init__ raises WFE
(WalletFileException; e.g. if storage version is from the future)
2019-09-14 16:22:06 +02:00
SomberNight
c7346c1eb8
kivy: fix paying onchain invoices
when pasting a new invoice and paying it
Traceback (most recent call last):
  File "/home/user/wspace/electrum/electrum/gui/kivy/uix/screens.py", line 358, in _do_send_onchain
    tx = self.app.wallet.make_unsigned_transaction(coins, outputs, None)
  File "/home/user/wspace/electrum/electrum/wallet.py", line 849, in make_unsigned_transaction
    if o.type == TYPE_ADDRESS:
AttributeError: 'tuple' object has no attribute 'type'

when loading back a saved invoice
Traceback (most recent call last):
  File "/home/user/wspace/electrum/electrum/gui/kivy/uix/screens.py", line 358, in _do_send_onchain
    tx = self.app.wallet.make_unsigned_transaction(coins, outputs, None)
  File "/home/user/wspace/electrum/electrum/wallet.py", line 849, in make_unsigned_transaction
    if o.type == TYPE_ADDRESS:
AttributeError: 'list' object has no attribute 'type'
2019-09-13 15:00:34 +02:00
SomberNight
1f305bba39
qt history tab txn drag and drop: fix #5635 2019-09-13 14:12:40 +02:00
ThomasV
1ecbafb920 add SSL context to watchtower server 2019-09-13 12:26:27 +02:00
ThomasV
26efef9e06 move lightning settings to settings dialog 2019-09-13 11:55:05 +02:00
ThomasV
aaed594772 Simplify invoices and requests.
- We need only two types: PR_TYPE_ONCHAIN and PR_TYPE_LN
 - BIP70 is no longer a type, but an optional field in the dict
 - Invoices in the wallet are indexed by a hash of their serialized list of outputs.
 - Requests are still indexed by address, because we never generate Paytomany requests.
 - Add 'clear_invoices' command to CLI
 - Add 'save invoice' button to Qt
2019-09-12 20:11:20 +02:00
SomberNight
1b332748c3
qt console: fix commands that do not expect a 'wallet' arg 2019-09-12 19:22:55 +02:00
SomberNight
b9af8bf60a
get_tx_fee: further follow-up 7b828359c6 2019-09-12 17:22:35 +02:00
ThomasV
3d04399581 follow-up 7b828359c6 2019-09-12 13:12:48 +02:00
ThomasV
79de0489e3 kivy: do not remove fee from tx value in history (minor) 2019-09-12 12:32:38 +02:00
ThomasV
7b828359c6 simplify get_tx_fee 2019-09-12 12:26:49 +02:00
SomberNight
482605edbb wallet: organise get_tx_fee. store calculated fees. storage version 19. 2019-09-12 08:59:27 +02:00
ThomasV
5c83e8bd1c follow-up 241873f0a4 2019-09-12 08:58:58 +02:00
SomberNight
b138fff9a5
wallet: txi/txo small clean-up 2019-09-12 04:07:17 +02:00
SomberNight
241873f0a4
address_synchronizer.get_history now returns HistoryItem(NamedTuple)s 2019-09-12 04:06:51 +02:00
ThomasV
65b88dca86 return fees in history, show them in kivy GUI 2019-09-11 17:49:40 +02:00
ThomasV
a47a2c1b72 follow-up prev 2019-09-11 17:08:03 +02:00
ThomasV
1d82093ca1 fix bug in lightning get_history: filter settled htlcs first 2019-09-11 17:06:07 +02:00
ThomasV
30092cd68c kivy: request PIN to load_wallet only on android 2019-09-11 16:36:04 +02:00
ThomasV
215dc96de7 define LNWatcher.do_breach_remedy 2019-09-11 11:58:28 +02:00
ThomasV
7370910fee fix simple_config.estimate_fee 2019-09-11 11:49:32 +02:00
SomberNight
c531c72940
kivy: attempt at handling (some) exceptions on startup 2019-09-10 21:29:13 +02:00
SomberNight
a05dab2c4d
storage: read/write sanity checks
related: #4110
supersedes: #4528
2019-09-10 21:17:15 +02:00
SomberNight
4dda162336
qt wizard: turn 'temp_storage' into local variable 2019-09-10 20:19:41 +02:00
SomberNight
9eee36fe00
follow-up prev 2019-09-10 20:18:53 +02:00
SomberNight
098636c69a
fix tests 2019-09-10 19:39:52 +02:00
SomberNight
9c31c1f970
wallet: address_is_old is now checked using SPV (during sync) 2019-09-10 18:26:09 +02:00
SomberNight
b2920db8b8
config: enforce that SimpleConfig is singleton
related: #5629
2019-09-10 18:01:10 +02:00
ThomasV
a43be6657d follow-up on SingleConfig 2019-09-10 17:14:25 +02:00
SomberNight
1a08063928
config: remove 'open_last_wallet' side-effecting
related: #5629
2019-09-10 17:10:52 +02:00
SomberNight
d1026b5afe
follow-up: SimpleConfig is supposed to be singleton
see cefa4762ba
and #5629
2019-09-10 16:38:10 +02:00
ThomasV
16e293c289 follow-up cefa4762ba 2019-09-10 16:24:21 +02:00
ThomasV
8e4fe051d3 add comment in storage._write 2019-09-10 09:26:07 +02:00
ThomasV
cefa4762ba do not create multiple instances of SimpleConfig (fix #5629). Add config field to wallet 2019-09-10 08:57:40 +02:00
SomberNight
bcdb72ae93
qt: add some type hints 2019-09-09 22:19:36 +02:00
SomberNight
ef5a5151e3
daemon: make 'wallets' dict private
especially as keys (paths) need to be standardized, this should not be exposed
2019-09-09 22:15:11 +02:00
SomberNight
befa8ea771
transaction: kill "name", "csv_delay", "cltv_expiry" fields 2019-09-09 19:38:35 +02:00
ThomasV
e5502a58ba uncomment breach_remedy in watchtower 2019-09-09 18:49:14 +02:00
ghost43
ddcd77ab36
Merge pull request #5628 from TheCharlatan/fixMultisigDigitalBitBox
Digital BitBox: Fix sending to self
2019-09-09 14:37:17 +00:00
TheCharlatan
e7079f1bea
Digital BitBox: Fix sending to self
Make sure that a pubkey is only appended to the checkpub array if it
corresponds to a change address. Signing will fail otherwise, if a
non-change pubkey is sent as part of the checkpub list to the Digital
BitBox.
2019-09-09 15:07:51 +02:00
ThomasV
64deb87ade fix #5513 2019-09-09 11:58:32 +02:00
ThomasV
103a37b0ca add wallet_path to kwargs in run_cmdline 2019-09-09 09:58:16 +02:00
ThomasV
46ffab0b55 remove --wallet option for daemon. fixes #3752 2019-09-09 06:28:54 +02:00
SomberNight
d1dea9343e
wallet: address_is_old minor clean-up
also, synchronize was defined twice in AddressSynchronizer
2019-09-09 01:34:29 +02:00
SomberNight
ccc1897f36
qt addresses list: use IntEnum for dropdown filters 2019-09-09 00:24:22 +02:00
ThomasV
86bd3839df follo-up 35761d1241 2019-09-08 19:25:35 +02:00
ThomasV
35761d1241 Save remote policy of chanel in wallet file (for private channels) 2019-09-08 19:14:28 +02:00
SomberNight
1612bca4c8
wine build: add --build to ./configure: prevents cross-comp. misdetect
from Electron-Cash/Electron-Cash/commit/e87021a78dbdeb50e9cfa51ddf0c1ef60c7688e0
2019-09-08 18:55:31 +02:00
SomberNight
b381fd84fb
build: when building libsecp256k1, patch Makefile.am before autogen.sh
apparently this could have caused issues on MacOS

based on Electron-Cash/Electron-Cash@69f6cd0aa0
2019-09-08 18:47:30 +02:00
SomberNight
abde8ff169
wallet: fix maturity off-by-one
based on Electron-Cash/Electron-Cash@c70957eb13
2019-09-08 18:26:04 +02:00
SomberNight
e3d5475f03
kivy: commit png icons (for svg resources) into repo 2019-09-08 17:05:06 +02:00
SomberNight
b1dc281cba
kivy amount_dialog: truncate btc amounts to max precision
closes #5624
2019-09-08 15:41:10 +02:00
ThomasV
d5d9f5b46c fix #5618 2019-09-08 12:06:21 +02:00
SomberNight
83fcdbd561
lnchannel: handle htlc-address collisions
We were previously generating an incorrect commitment_signed msg if there were
multiple htlcs sharing the same scriptPubKey.
2019-09-07 08:54:41 +02:00
SomberNight
00f15d491b
lnpeer: somewhat nicer log messages 2019-09-07 07:29:22 +02:00
SomberNight
d4da4aa56c
lnrouter: fix off-by-one in NUM_MAX_EDGES_IN_PAYMENT_PATH 2019-09-06 18:36:21 +02:00
SomberNight
25c372a3e0
lnworker.invoices access now uses lock
(qt gui thread vs asyncio thread race)

Traceback (most recent call last):
  File "/home/user/wspace/electrum/electrum/gui/qt/main_window.py", line 1685, in do_send
    self.pay_lightning_invoice(self.payto_e.lightning_invoice)
  File "/home/user/wspace/electrum/electrum/gui/qt/main_window.py", line 1667, in pay_lightning_invoice
    self.invoice_list.update()
  File "/home/user/wspace/electrum/electrum/gui/qt/invoice_list.py", line 73, in update
    _list = self.parent.wallet.get_invoices()
  File "/home/user/wspace/electrum/electrum/wallet.py", line 525, in get_invoices
    out += self.lnworker.get_invoices()
  File "/home/user/wspace/electrum/electrum/util.py", line 401, in
    return lambda *args, **kw_args: do_profile(args, kw_args)
  File "/home/user/wspace/electrum/electrum/util.py", line 397, in do_profile
    o = func(*args, **kw_args)
  File "/home/user/wspace/electrum/electrum/lnworker.py", line 1007, in get_invoices
    for key, (invoice, direction, status) in self.invoices.items():
RuntimeError: dictionary changed size during iteration
2019-09-06 18:27:47 +02:00
SomberNight
509df9ddaf
create class for ShortChannelID and use it 2019-09-06 18:09:05 +02:00
SomberNight
251db638af
only require libsecp256k1 if lightning is enabled
related: #5606
2019-09-06 15:08:15 +02:00
ThomasV
e5ff4fc7cd fix #5605 2019-09-06 13:43:00 +02:00
ThomasV
5e92f637a6 fix CLI exception handling 2019-09-06 12:38:13 +02:00
ThomasV
35b0b3a43c Fix CLI. Some commands require wallet_path. Return error on exception. 2019-09-06 11:06:08 +02:00
ThomasV
5faa0ade3d ignore exceptions in payserver 2019-09-06 08:06:26 +02:00
SomberNight
712c3f1248
commands: try to fix tests 2019-09-05 23:22:11 +02:00
SomberNight
edf186da0d
channeldb.load_data: attempt at fixing race
closes #5525
2019-09-05 18:32:45 +02:00
SomberNight
4f741cfccc
lnpeer: longer timeouts 2019-09-05 18:31:51 +02:00
SomberNight
58681e4d07
follow-up prev (commands) 2019-09-05 18:30:04 +02:00
ThomasV
9ec9e1760a CLI: always use the daemon's cmd_runner, and pass the 'wallet'
parameter explicitly to each command that requires it.
Previous code was relying on side effects to set the wallet.
This should fix #5614
2019-09-05 17:57:51 +02:00
SomberNight
0deb12cb2b
kivy: fix ln channel open via scan_qr 2019-09-05 17:06:42 +02:00
ThomasV
45f3e28d38 qt: minor fixes 2019-09-05 15:00:45 +02:00
ThomasV
28da62f51b add PayServer settings to settings_dialog 2019-09-05 14:43:27 +02:00
ThomasV
96d3c36e4a Qt: move settings dialog to a separate module 2019-09-05 13:21:18 +02:00
ThomasV
9d595f1fe1 fix websocket messages 2019-09-05 11:45:28 +02:00
ThomasV
94e7e94e2f fix ssl config names, add new config tab 2019-09-05 11:36:50 +02:00
ThomasV
466c2bd293 for now, use PR_PAID for onchain unconfirmed 2019-09-05 11:33:09 +02:00
ThomasV
128285a050 http server: add ssl and bip70 signed requests 2019-09-05 10:57:50 +02:00
SomberNight
9d65120e59
commands: fix "close_wallet" 2019-09-04 20:16:47 +02:00
SomberNight
1bd9b3a66a
commands: fix "restore" cmd
Previously commands did not run on the asyncio thread but now they do.
"restore" was polling like "while 1: time.sleep()", blocking the event loop.

Now "restore" does not sync the wallet; which is actually cleaner
as previously this wallet would never get unloaded from the daemon (syncing forever).

This is also symmetric with the "create" cmd which also does not try to sync with the network.

However now it became difficult to write a script that restores a wallet and wants to wait
until it gets synced. Workaround for now is to poll with "list_wallets" whether it's synced.

We could create a new command that blocks until the loaded wallet gets synced.
2019-09-04 20:15:54 +02:00
SomberNight
de83ab6d4a
CLI: remove timeout for offline commands
e.g. when interacting with hw wallets (e.g. signmessage)
it does not make sense to time out

also, str(e) of some exceptions such as TimeoutError is ""...
2019-09-04 14:35:04 +02:00
SomberNight
b0902940b5
README: mention submodule init 2019-09-04 14:03:39 +02:00
SomberNight
99b83f7527
fix #5617 2019-09-04 13:53:38 +02:00
SomberNight
a42a773d19
storage: replace STO_EV_* ints with IntEnum 2019-09-04 13:31:49 +02:00
ThomasV
54776ca1d9 disable http server by default 2019-09-04 13:07:44 +02:00
ThomasV
deb97567fb Qt: fix request menu 2019-09-04 12:52:32 +02:00
ThomasV
0d1ffe5642 fix submodule 2019-09-04 12:39:02 +02:00
ThomasV
747ab7a0a2 Integrate http_server (previously in electrum-merchant)
Use submodule to fetch HTML and CSS files
2019-09-04 12:20:05 +02:00
ThomasV
bd57880799 do not raise exception when add_own_channel adds channel update from the cache 2019-09-04 08:01:08 +02:00
ThomasV
e286ed1c13 add_own_channel does not need to be async 2019-09-03 21:01:45 +02:00
SomberNight
9372eacc29
hw wallets: show DeviceUnpairableError nicely in qt gui
need to subclass UserFacingException for main_window.on_error
2019-09-03 19:19:58 +02:00
SomberNight
bb2c3475cc
qt main_window: fix receive_at, rm dead code 2019-09-03 18:56:09 +02:00
SomberNight
8cd8c6612d
hw wallets: fix "show address" functionality in qt receive tab 2019-09-03 18:37:48 +02:00
SomberNight
ff94240139
qt receive tab: fix on-chain pay.requests without amount 2019-09-03 18:33:49 +02:00
SomberNight
c3504cec94
follow-up a4b24df4bb 2019-09-03 17:53:11 +02:00
SomberNight
073a09f926
wallet/keystore: small inheritance clean-up 2019-09-03 16:24:05 +02:00
ThomasV
a4b24df4bb fix json error in listrequests 2019-09-03 14:51:07 +02:00
ThomasV
2801539087 fix create_menu error #5609 2019-09-03 14:50:34 +02:00
SomberNight
ab76a1fe5b
wallet.add_hw_info: also store "is_change" in output_info
as it seems every consumer wants to know this and has its own hacks to
figure it out
2019-09-03 14:34:10 +02:00
SomberNight
ac329797e0
wallet.add_hw_info: minor clean-up 2019-09-03 14:20:00 +02:00
SomberNight
fd70b806de
(trivial) wine build: rm keys in Dockerfile 2019-09-03 14:18:56 +02:00
SomberNight
3d7cb935ff
appimage: don't rm jsonschema-*.dist-info as pkg needs it 2019-09-02 19:21:53 +02:00
ThomasV
7f870f5e09 replace daemon 'start' subdommand with -d 2019-09-02 19:04:08 +02:00
ThomasV
241a37d12d
Merge pull request #5253 from AbdussamadA/update-utxo-list-label
Update utxo list when label is changed on history list
2019-09-02 18:38:45 +02:00
ThomasV
d5691129bb
Merge pull request #5604 from MrNaif2018/master
Fix for onchain_history summary building
2019-09-02 18:35:23 +02:00
SomberNight
74366f5cce
android build: persist gradle datadir
avoids re-downloading hundreds of MB of data on every run
2019-09-02 17:32:48 +02:00
SomberNight
dfa345defc
only build one android apk on Travis (take 3...)
follow-up 8404e07061
2019-09-02 17:03:29 +02:00
ThomasV
a50f935aec Restructure invoices and requests (WIP)
- Terminology: use 'invoices' for outgoing payments, 'requests' for incoming payments
 - At the GUI level, try to handle invoices in a generic way.
 - Display ongoing payments in send tab.
2019-09-02 15:35:44 +02:00
MrNaif2018
1dab0aa719
Fix for onchain_history summary building 2019-09-02 16:24:08 +03:00
SomberNight
3902d774f7
(trivial) travis: move flake8 tests to first stage 2019-09-01 20:18:09 +02:00
SomberNight
8404e07061
only build one android apk on Travis
follow-up 0333632eb0
2019-09-01 20:05:40 +02:00
SomberNight
0534f937ab
local jsonrpc: log exceptions daemon-side 2019-09-01 18:23:01 +02:00
SomberNight
eb5033dfc6
commands: add feerate param to payto/paytomany 2019-09-01 17:52:02 +02:00
SomberNight
0333632eb0
follow-up prev: only build one apk on Travis 2019-09-01 15:49:50 +02:00
SomberNight
54d468f457
android apk: build two apks. ARMv7 and ARMv8 2019-09-01 15:38:26 +02:00
SomberNight
75afd06ca3
android build: don't download Apache ANT on every build 2019-08-31 19:08:41 +02:00
SomberNight
98c8c2127c
android build: update kivy, p4a, buildozer
- also merge https://github.com/kivy/buildozer/pull/957 as prereq for
building multiple apks (one per arch)
- and custom buildozer commit to put target arch into apk name
2019-08-31 17:06:02 +02:00
ThomasV
b99a71d1b3 kivy: call register_callback only from main_window. Display CTN in channel details. 2019-08-31 10:13:20 +02:00
ThomasV
e79253b5e0 Syntax change: Require --offline to run commands without a daemon.
That makes the syntax less ambiguous. It also makes it possible to
implement a CLI that does not import all the electrum modules.
2019-08-31 09:24:00 +02:00
ThomasV
c67fb88e58 remove redundant 'stop' in regtest setUp (should run a bit faster) 2019-08-31 08:49:31 +02:00
ThomasV
0702338912 main script: rm init_daemon (dead code), call sys_exit in init_cmdline 2019-08-31 08:32:06 +02:00
SomberNight
936d1e0a24
pyinstaller binaries: include files needed by new jsonrpc libs
fixes #5599
2019-08-30 21:15:47 +02:00
SomberNight
956bd3baaf
lnpeer: make per-peer TaskGroup a field (as for interfaces), and use it
lnpeer (and interface) response-handling-code should not run in the
network main_taskgroup as the remote could force an exception
to be raised and that would kill the whole network instead of just the peer
2019-08-30 19:51:17 +02:00
SomberNight
9e57a59615
network: handle main_taskgroup dying better. passthrough CancelledError
Previously if a task running in the main_taskgroup raised,
main_taskgroup might have never finished as fx.run (another task running
in main_taskgroup) could not be cancelled (it was swallowing the CancelledError).

Need to be careful with catching all Exceptions or BaseExceptions,
as that might result in a coroutine not being cancellable.
Note that from python 3.8 onwards, CancelledError will inherit from BaseException
instead of Exception, so catching all Exceptions is somewhat less horrible
but this will only really matter if we raise the min py version to 3.8...

Really, all "except BaseException" lines are suspect and at least should be
considered for replacement with "except Exception".

-----

regarding fx.run not being cancellable before, and relevant lines, see:

6197cfbb3b/electrum/network.py (L1171)
0decdffce2/aiorpcx/curio.py (L242)
0decdffce2/aiorpcx/curio.py (L199)
0decdffce2/aiorpcx/curio.py (L208)
0decdffce2/aiorpcx/curio.py (L218)
0decdffce2/aiorpcx/curio.py (L221)
6197cfbb3b/electrum/daemon.py (L194)
6197cfbb3b/electrum/daemon.py (L203)
6197cfbb3b/electrum/exchange_rate.py (L507)
6197cfbb3b/electrum/exchange_rate.py (L79)
2019-08-30 19:46:25 +02:00
ThomasV
6197cfbb3b Revert "Remove early return in create_sweeptxs_for_our_ctx."
This reverts commit d0cfb3ae12.
2019-08-30 17:40:46 +02:00
ThomasV
3e8080b669 test_breach_with_spent_htlc: do not overwrite default_wallet, load toxic_wallet instead 2019-08-30 17:40:09 +02:00
ThomasV
10e186c1d3 revert ed086934e5
(this does not work well with travis)
2019-08-30 15:58:38 +02:00
ThomasV
6f333bd86d make regtests more robust 2019-08-30 15:18:04 +02:00
ThomasV
ed086934e5 In 'daemon start', do not return until the daemon can be reached 2019-08-30 15:15:30 +02:00
ThomasV
5ec1db4d51 Ignore exceptions raised in lnworker.on_network_update.
Exception raised there may cancel the network's main taskgroup.
2019-08-30 12:37:34 +02:00
ThomasV
d0cfb3ae12 Remove early return in create_sweeptxs_for_our_ctx.
- This was added because we did not store the complete htlc history.
 - It makes the result dependent on the current channel state
 - That creates a race condition in sweep_info, because the result is cached.
 - As a result, test_breach_with_spent_htlc was randomly failing.
2019-08-30 09:11:54 +02:00
SomberNight
5f817770af
android build: make sure to use correct pycryptodomex
note: buildozer casts all actual paths to lowercase but not the list of excluded paths...
see 182d13f102/buildozer/__init__.py (L775)
2019-08-29 18:07:37 +02:00
EagleTM
01f582cc14 Remove electrumx.ml because of phishing (#5596) 2019-08-29 12:37:33 +00:00
SomberNight
f403735191
lnpeer: reestablish_channel - don't replay unacked msgs they alrdy have
e.g. Alice sends upd1, upd2, upd3, commitment_signed, upd4, upd5.
Bob receives all of these; and sends a revoke_and_ack but there is
a disconnect before Alice receives the revoke_and_ack.
During reestablish, if Bob claims to have received the commitment_signed,
Alice must not replay the msgs before that; but she should replay upd4 and upd5.
2019-08-29 12:37:25 +02:00
ThomasV
2583decc64
Merge pull request #5593 from wakiyamap/patch-1
Fix travis error
2019-08-29 10:20:34 +02:00
Jin Eguchi
523de5782b
Fix travis error 2019-08-29 04:32:32 +09:00
SomberNight
2ee881f40a
qt channels list: fields should not be editable 2019-08-28 16:54:51 +02:00
SomberNight
db8e6cabb4
bip70 payreq: catch TimeoutError to avoid hanging "please wait" dialog
related #5337
2019-08-27 18:03:01 +02:00
ThomasV
3076eb75ea make parsing lightning qr codes more robust 2019-08-27 17:12:43 +02:00
ThomasV
31a18f83f1 (trivial) fix variable name 2019-08-27 13:35:03 +02:00
ThomasV
7866caf2a7 minor fix: ensure request amount is not None 2019-08-26 16:59:38 +02:00
ThomasV
cf02e32f20
Merge pull request #5537 from xaya/test-verify-header
Unit tests for Blockchain.verify_header
2019-08-26 15:42:15 +02:00
ThomasV
58177c5bf3 Travis: run regtests in separate job 2019-08-26 13:52:55 +02:00
ThomasV
fcfbc937bc buildozer: use log_level=1 2019-08-26 11:35:17 +02:00
ThomasV
d766ded8d4
Merge pull request #5584 from JeremyRand/lnaddr-bech32
lnaddr: Pull in Bech32 and Base58 prefixes from constants
2019-08-25 18:58:47 +02:00
JeremyRand
8be94a9919
lnaddr: Pull in Bech32 and Base58 prefixes from constants
Fixes https://github.com/spesmilo/electrum/issues/5581
2019-08-25 13:15:13 +00:00
ThomasV
2b52ee26e6 store qt-console-history in wallet file (fix #5563) 2019-08-25 11:39:11 +02:00
ThomasV
95383a5820
Merge pull request #5582 from JeremyRand/test-lnchannel-outputs
Use NamedTuple notation for TxOutput in test_lnchannel
2019-08-25 10:17:54 +02:00
ThomasV
2944ae1b2f
Merge pull request #5583 from JeremyRand/test-lnrouter-rev-genesis-bytes
test_lnrouter: Pull in chain_hash from constants
2019-08-25 10:17:06 +02:00
JeremyRand
032810dace
test_lnrouter: Pull in chain_hash from constants 2019-08-25 07:19:36 +00:00
JeremyRand
334d3f2818
Use NamedTuple notation for TxOutput in test_lnchannel
This makes the code more resilient in case additional members are added
to TxOutput later.
2019-08-25 06:51:31 +00:00
ThomasV
ab5a02ba50 kivy: remove hidden state in RefLabel, use it for seed and private keys 2019-08-23 15:46:58 +02:00
ThomasV
e9c32bad19 kivy: remove context menus, cleanup unused files 2019-08-23 12:15:42 +02:00
ThomasV
587f8aa487 Kivy GUI improvements:
- create unique instances of channels_dialog and addresses_dialog
 - display and refresh balances in channels_dialog
 - improve formatting of tx history
 - repurpose left button in receive_tab
2019-08-22 19:04:32 +02:00
ThomasV
8010123c08 Display and refresh the status of incoming payment requests:
- All requests have an expiration date
 - Paid requests are automatically removed from the list
 - Unpaid, unconfirmed and expired requests are displayed
 - Fix a bug in get_payment_status, conf was off by one
2019-08-22 06:00:45 +02:00
ThomasV
336cf81a6d kivy: add expiration button to receive screen 2019-08-20 18:20:54 +02:00
ThomasV
27a9d02b8c kivy: update receive screen after adding request 2019-08-20 16:07:40 +02:00
ThomasV
65cf0ebce8 lnpeer, minor fixes:
- pass orphaned_ids to lnworker.add_new_ids
 - fix enumeration of get_unacked_local_updates()
2019-08-20 13:27:09 +02:00
ThomasV
dd22cb6dff kivy: minor fixes 2019-08-20 09:03:12 +02:00
ThomasV
246cda2928 fix Flake8 tests 2019-08-20 09:03:12 +02:00
ThomasV
a3bff7476c run freeze_packages 2019-08-20 09:03:12 +02:00
ThomasV
9cfeadea70 Turn daemon subcommands into RPCs 2019-08-20 09:03:12 +02:00
ThomasV
4397767a5e minor fix 2019-08-20 09:03:12 +02:00
ThomasV
2e1829bc24 remove jsonrpclib dependency 2019-08-20 09:03:12 +02:00
ThomasV
54257cbcca Rewrite JsonRPC requests using asyncio.
- commands are async
 - the asyncio loop is started and stopped from the main script
 - the daemon's main loop runs in the main thread
 - use jsonrpcserver and jsonrpcclient instead of jsonrpclib
2019-08-20 09:03:12 +02:00
SomberNight
fa5302bcfb (trivial) fix type annotation 2019-08-20 09:03:12 +02:00
SomberNight
bce74717a6 lnpeer: in onion errors, handle channel updates both with and w/o type 2019-08-20 09:03:12 +02:00
SomberNight
beeb81e179 lnpeer: use correct failure codes in _maybe_forward_htlc 2019-08-20 09:03:12 +02:00
SomberNight
e54f0fbafa do not raise BaseException 2019-08-20 09:03:12 +02:00
SomberNight
d955285808 lnrouter/channeldb: small import clean-up 2019-08-20 09:03:12 +02:00
SomberNight
47ee02569a lnpeer: send own outgoing channel updates to remote peer 2019-08-20 09:03:12 +02:00
SomberNight
f0588846d5 channeldb: also store "message_flags" field for channel updates
this is a breaking change for the db format.
As in comment in diff,
"It would make more sense to store the raw gossip messages in the db."
2019-08-20 09:03:12 +02:00
SomberNight
d229bb4e4d lnpeer: restore "temp save orphan channel updates" functionality
needed to handle race where remote might send chan_upd too soon
(before we save the short channel id for the channel after it reaches funding locked)
2019-08-20 09:03:12 +02:00
SomberNight
ba431495db lnworker: fix silent TypeError in _calc_routing_hints_for_invoice 2019-08-20 09:03:12 +02:00
SomberNight
02681c6664 tests: some regtest tests need to mine more blocks to expire CLTVs
as lnutil.MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE was recently bumped
2019-08-20 09:03:12 +02:00
SomberNight
a9295e495c tests: regtest.sh now uses consistent indentation 2019-08-20 09:03:12 +02:00
SomberNight
6b1810f8dc commands.py: fix type of "timeout" arg
was str by default
2019-08-20 09:03:12 +02:00
SomberNight
159fe04daf lnpeer: on_channel_open should not fail on server error 2019-08-20 09:03:12 +02:00
ThomasV
e5286f7598 minor fix: always initialize Commands.lnworker 2019-08-20 09:03:12 +02:00
ThomasV
5e0427392f Daemon: Replace get_server with request.
This function performs a single RPC, and may raise a DaemonNotRunning exception.
2019-08-20 09:03:12 +02:00
ThomasV
28b1569f28 (trivial) fix getbalance 2019-08-20 09:03:12 +02:00
ThomasV
b81feb6550 daemon: simplify get_fd_or_server 2019-08-20 09:03:12 +02:00
SomberNight
a9239bd40f lnpeer: shutdown should wait until no HTLCs remain
in either ctx
2019-08-20 09:03:12 +02:00
SomberNight
2e38bcf416 lnpeer: failed htlc error handling ignored length of channel_update 2019-08-20 09:03:12 +02:00
SomberNight
d2d4d19fcb lnpeer: add a few sanity checks to payment-forwarding (and related) 2019-08-20 09:03:12 +02:00
SomberNight
0973b86925 lnworker: rework "is_dangerous"
"Should channel be closed due to expiring htlcs?"
2019-08-20 09:03:12 +02:00
SomberNight
ce54b5411e lnhtlc: htlcs_by_direction now returns dict keyed by htlc_id 2019-08-20 09:03:12 +02:00
ThomasV
8e2ebddc0b add jsonrpcserver/jsonrpcclient to requirements 2019-08-20 09:03:12 +02:00
ThomasV
b2f61bdc06 use aiohttp + jsonrpcserver in watchtower 2019-08-20 09:03:12 +02:00
ThomasV
de29fe6930 remove unused import 2019-08-20 09:03:12 +02:00
SomberNight
8ad25b3a52 lnpeer: make sure forwarding is disabled by default 2019-08-20 09:03:12 +02:00
SomberNight
a27b03be6d lnhtlc: local update raw messages must not be deleted before acked
In recv_rev() previously all unacked_local_updates were deleted
as it was assumed that all of them have been acked at that point by
the revoke_and_ack itself. However this is not necessarily the case:
see new test case.

renamed log['unacked_local_updates'] to log['unacked_local_updates2']
to avoid breaking existing wallet files
2019-08-20 09:03:12 +02:00
SomberNight
4fc9f243f7 lnpeer: reestablish_channel - always replay unacked local updates
Even if we haven't signed them yet (did not send commitment_signed).
Alternatively, if they are not yet signed, we could discard them here,
like we do already for remote updates above (chan.hm.discard_unsigned_remote_updates).
One of these two options must be done, and before this commit we were not doing either.
2019-08-20 09:03:12 +02:00
ThomasV
98a1c9268a qt: do not show paid requests 2019-08-20 09:03:12 +02:00
ThomasV
bd5c83e906 fix race condition with channel_timestamps 2019-08-20 09:03:12 +02:00
ThomasV
b493219829 require data loss protect 2019-08-20 09:03:12 +02:00
ThomasV
9f8e2c689e test funding_txn_minimum_depth, show it in GUI 2019-08-20 09:03:12 +02:00
ThomasV
bbec1dceda lnpeer: fix and simplify tests in maybe_send_commitment 2019-08-20 09:03:12 +02:00
ThomasV
39bae1c7cf channel_db: load_data should load node_info 2019-08-20 09:03:12 +02:00
ThomasV
46c2d7821f kivy: show pending requests in receive tab instead of dialog 2019-08-20 09:03:12 +02:00
ThomasV
f8038d024b kivy: lnpay_thread 2019-08-20 09:03:12 +02:00
ThomasV
9e78fdbf71 qt gui: simplify signals 2019-08-20 09:03:12 +02:00
ThomasV
e584a7451c simplify tx history: do not use separate columns for lightning 2019-08-20 09:03:12 +02:00
SomberNight
80c52d4808 simple_config.estimate_fee: make sure method never fails
code in lnsweep was already assuming this
2019-08-20 09:03:12 +02:00
SomberNight
7f1b456b93 lnhtlc.discard_unsigned_remote_updates: fix edge case 2019-08-20 09:03:12 +02:00
SomberNight
bb63bd98fb lnpeer: should not mark channel as OPEN if reestablish did not complete 2019-08-20 09:03:12 +02:00
SomberNight
940fc86749 lnpeer: reestablish_channel - fix data_loss_protect edge case 2019-08-20 09:03:12 +02:00
SomberNight
107f271e58 move all ctn book-keeping to lnhtlc (from lnchannel) 2019-08-20 09:03:12 +02:00
SomberNight
44761972cb lnchannel: ctx output-ordering: identical htlcs are ordered by CLTV 2019-08-20 09:03:12 +02:00
SomberNight
b1f606eaed lnchannel: start using "latest" and "next" instead of "current" and "pending"
"current" used to be "oldest_unrevoked"; and pending was "oldest_unrevoked + 1"
but this was very confusing...
so now we have "oldest_unrevoked", "latest", and "next"
where "next" is "latest + 1"
"oldest_unrevoked" and "latest" are either the same or are offset by 1
(but caller should know which one they need)

rm "got_sig_for_next" - it was a redundant sanity check, that really
just complicated things

rm "local_commitment", "remote_commitment", "set_local_commitment",
"set_remote_commitment" - just use "get_latest_commitment" instead
2019-08-20 09:03:12 +02:00
SomberNight
e32807d29d lnworker: only reestablish channels after on-chain state is synchronized 2019-08-20 09:03:12 +02:00
SomberNight
944e4f0ba0 implement data_loss_protect
so that we can spend their_ctx_to_remote even when we lost our state
but have an old backup
2019-08-20 09:03:12 +02:00
SomberNight
fdf8d8609b lnpeer: make feature-bit testing easier
so that we can always test like: self.localfeatures & FEATURE_BIT_OPT
2019-08-20 09:03:12 +02:00
SomberNight
014b921393 lnpeer: reestablish_channel - replay un-acked local updates
Replay un-acked local updates (including commitment_signed) byte-for-byte.
If we have sent them a commitment signature that they "lost" (due to disconnect),
we need to make sure we replay the same local updates, as otherwise they could
end up with two (or more) signed valid commitment transactions at the same ctn.
Multiple valid ctxs at the same ctn is a major headache for pre-signing spending txns,
e.g. for watchtowers, hence we must ensure these ctxs coincide.
2019-08-20 09:03:12 +02:00
SomberNight
e81ae1921b lnpeer: reestablish_channel - discard unsigned remote updates 2019-08-20 09:03:12 +02:00
SomberNight
c046f2cc1c lnhtlc: move 'next_htlc_id' from ChannelConfig to lnhtlc log 2019-08-20 09:03:12 +02:00
SomberNight
c8b19aec2a lnpeer: make reestablish_channel saner
clear up expectations about ctns
2019-08-20 09:03:12 +02:00
SomberNight
a3fd6b3ce8 lnhtlc: rename ctx_pending to revack_pending, and persist it 2019-08-20 09:03:12 +02:00
SomberNight
cd4268c521 lnworker: small clean-up of short_channel_id format 2019-08-20 09:03:12 +02:00
SomberNight
bdbc662a36 lnpeer: channel_reestablished is now a queue (instead of future) 2019-08-20 09:03:12 +02:00
SomberNight
0d84873a75 lnchannel: trivial clean-up 2019-08-20 09:03:12 +02:00
ThomasV
57ec8f51c8 lnpay: check whether invoice has been paid 2019-08-20 09:03:12 +02:00
ThomasV
4b2336304f kivy fix: get_latest_feerate 2019-08-20 09:03:12 +02:00
ThomasV
f9a2e7eeb4 lnworker.get_invoice_status: test if invoice is expired 2019-08-20 09:03:12 +02:00
ThomasV
cac1e87286 use aiohttp+jsonrpcclient to sync with remote watchtower 2019-08-20 09:03:12 +02:00
ThomasV
740381e993 fix: remove unused parameter to add_sweep_tx 2019-08-20 09:03:12 +02:00
ThomasV
fa3eefa479 refactor a few lnchannel methods 2019-08-20 09:03:12 +02:00
ThomasV
cd7ed4c59c fix: constraints.feerate -> get_latest_feerate 2019-08-20 09:03:12 +02:00
ThomasV
f7c05f2602 Synchronize watchtower asynchronously:
- remove remote_commitment_to_be_revoked
- pass old ctns to lnsweep.create_sweeptxs_for_watchtower
- store the ctn of sweeptxs in sweepStore database
- request the highest ctn from sweepstore using get_ctn
- send sweeptxs asynchronously in LNWallet.sync_with_watchtower
2019-08-20 09:03:12 +02:00
SomberNight
f060e53912 (trivial) fix type annotation 2019-08-20 09:03:12 +02:00
SomberNight
087994e39a lnchannel: move fee update logic to lnhtlc (and hopefully fix it) 2019-08-20 09:03:12 +02:00
ThomasV
3d7f7dfc82 revamp fee updates (draft) 2019-08-20 09:03:12 +02:00
SomberNight
7431aac5cd lnhtlc: (fix) was locking in too many updates during commit/revoke 2019-08-20 09:03:12 +02:00
SomberNight
4ccfa39fdd cli: fix add_peer cmd 2019-08-20 09:03:12 +02:00
ThomasV
9045d7b293 cleanup revoke_current_commitment 2019-08-20 09:03:12 +02:00
ThomasV
e43a3bc63a follow-up prev commit: pass is_mine to _edge_cost 2019-08-20 09:03:12 +02:00
ThomasV
b55f9e9e6a Do not route through channels for which we did not receive
both updates, because this often means one of the nodes is
offline.
2019-08-20 09:03:12 +02:00
ThomasV
30e942bead fix: delete from channel_db 2019-08-20 09:03:12 +02:00
ThomasV
32fcad5bc3 channel_db: update channels_for_node when removing channel 2019-08-20 09:03:12 +02:00
ThomasV
2be68ac4d2 Use one LNWatcher instance per wallet 2019-08-20 09:03:12 +02:00
ThomasV
4d76e84218 improve regtest.sh with wait functions 2019-08-20 09:03:12 +02:00
ThomasV
6b90d501ab fix type: list 2019-08-20 09:03:12 +02:00
ThomasV
94fe28b576 regtest: remove cost limit 2019-08-20 09:03:12 +02:00
ThomasV
c7b9bdc5f5 lnwatcher: wait until lnwatcher is fully synchronized before check_onchain_situation 2019-08-20 09:03:12 +02:00
ThomasV
a8ce8109be Perform breach remedy without sweepstore:
- add functions to lnsweep
 - lnworker: analyze candidate ctx and htlc_tx
 - watchtower will be optional
 - add test for breach remedy with spent htlcs
 - save tx name as label
2019-08-20 09:03:12 +02:00
ThomasV
238f3c949c get rid of sql_alchemy 2019-08-20 09:03:12 +02:00
ThomasV
0eab1692d6 Do not store message payloads in channel db.
Use single primary key for addresses.
2019-08-20 09:03:12 +02:00
ThomasV
f2d58d0e3f optimize channel_db:
- use python objects mirrored by sql database
 - write sql to file asynchronously
 - the sql decorator is awaited in sweepstore, not in channel_db
2019-08-20 09:03:12 +02:00
ThomasV
180f6d34be separate channel_db module 2019-08-20 09:03:12 +02:00
ThomasV
06b5299b0f comment out convert, add logging statement 2019-08-20 09:03:12 +02:00
ThomasV
a54cb30cf3 kivy: simplify open_channel dialog 2019-08-20 09:03:12 +02:00
SomberNight
c15267e1f6 pycryptodomex for android 2019-08-20 09:03:12 +02:00
ThomasV
115113f492 remove expensive sql request, python set comparison is faster 2019-08-20 09:03:12 +02:00
ThomasV
af7d7e883c Rework wallet history methods:
- wallet.get_full_history returns onchain and lightning
 - capital gains are returned by get_detailed_history
 - display lightning history in kivy
 - command line: separate lightning and onchain history
2019-08-20 09:03:12 +02:00
SomberNight
7e8be3d2e7 lnpeer: some exception handling clean up
main_loop should dump traces of unexpected exceptions to log.
Coroutines/functions invoked inside main_loop should simply propagate it
up the chain.
Typical exceptions are handled in handle_disconnect without dumping the trace.
2019-08-20 09:03:12 +02:00
SomberNight
efc8948c00 lnworker: set DATA_LOSS_PROTECT flag for LNGossip too
otherwise peers disconnect
2019-08-20 09:03:12 +02:00
SomberNight
e6fc8868b1 qt channels list: add "short channel id" column 2019-08-20 09:03:12 +02:00
SomberNight
aa4027298f do not "import *" 2019-08-20 09:03:12 +02:00
SomberNight
3413eb05b9 qt ChannelsList: some clean-up 2019-08-20 09:03:12 +02:00
SomberNight
5e3e3e41a1 qt receive tab: fix update_receive_address_styling 2019-08-20 09:03:12 +02:00
SomberNight
63217c1ca7 qt main window: hide "Channels" tab if lightning is disabled 2019-08-20 09:03:12 +02:00
ThomasV
70cd29f9e1 GUI refactoring for Kivy and lightning.
This also touches Qt and wallet code.
2019-08-20 09:03:12 +02:00
ThomasV
1a23dcb8d5 display lightning payment attempts using signal 2019-08-20 09:03:12 +02:00
ThomasV
049857d528 on_revoke_and_ack: be robust to exceptions raised in lnwatcher 2019-08-20 09:03:12 +02:00
ThomasV
dff1822c37 fix watchtower: sweep_tx must not be None 2019-08-20 09:03:12 +02:00
ThomasV
d477e3489f lnworker: fix reestablish_peer_for_given_channel by passing chan explicitly 2019-08-20 09:03:12 +02:00
ThomasV
0f00f4f655 fix encode_msg: optional fields were not sent 2019-08-20 09:03:12 +02:00
ThomasV
67f1ade798 send data_loss_protect fields if we support it 2019-08-20 09:03:12 +02:00
ThomasV
9eddb9844c on_commitment_signed: distinguish between exceptions 2019-08-20 09:03:11 +02:00
ThomasV
0913194a53 qt: add clear button to receive tab, show invoice right after it is added 2019-08-20 09:03:11 +02:00
ThomasV
234591624e request_lists: remove non-sense 2019-08-20 09:03:11 +02:00
ThomasV
b8d908d63e lnworker improvements:
- enable option data_loss_protect
 - separate add_peer from open_channel
 - display exceptions raised in open_channel
2019-08-20 09:03:11 +02:00
ThomasV
d2dfa8c558 regtest: use while loops instead of fine-tuned delays 2019-08-20 09:03:11 +02:00
ThomasV
d9b041e64d encapsulate detect_who_closed in channel 2019-08-20 09:03:11 +02:00
ThomasV
24cc3599c7 lnworker: catch exceptions raised by add_future_tx 2019-08-20 09:03:11 +02:00
ThomasV
740ef09883 simplify_lnsweep 2019-08-20 09:03:11 +02:00
ThomasV
9abbd077a5 lnhtlc: use boolean instead of int in ctn_latest 2019-08-20 09:03:11 +02:00
SomberNight
69bffac86a lnhtlc: fix adding htlc between sending commitment_signed and receiving revoke_and_ack 2019-08-20 09:03:11 +02:00
ThomasV
50479086b5 raise PaymentFailure in case of timeout (follow-up previous commit) 2019-08-20 09:03:11 +02:00
ThomasV
dbe8b75659 move lnpay attempts logic to lnworker.pay 2019-08-20 09:03:11 +02:00
ThomasV
3349e941de lnsweep: minor fix 2019-08-20 09:03:11 +02:00
ThomasV
81d340b19c lnworker: do not create sweep transactions before outputs can be redeemed 2019-08-20 09:03:11 +02:00
ThomasV
2b04cb3bc4 fix tests broken by previous commit 2019-08-20 09:03:11 +02:00
ThomasV
8d99fe8243 Let lnworker sweep HTLC outputs after breach, instead of lnwatcher 2019-08-20 09:03:11 +02:00
ThomasV
3dacc525e6 on_network_update: check if channel is closed 2019-08-20 09:03:11 +02:00
ThomasV
7be4cdaf18 redeem htlcs:
- fix bug in lnsweep: lnwatcher transactions were indexed by prev_txid
 - add test for breach remedy with unsettled htlcs
 - add timeout option to lnpay, and replace DO_NOT_SETTLE with SETTLE_DELAY
   so that we can read intermediate commitment tx in regtest
2019-08-20 09:03:11 +02:00
ThomasV
7418bd4552 lnsweep: simplify a few methods 2019-08-20 09:03:11 +02:00
ThomasV
6bbdbf7596 rework on_channel_closed in LNWorker:
- use detect_who_closed; this allows us to redeem to_remote of breach ctx
 - do not redeem to_local of breach ctx, because it is redundant with lnwatcher
 - rename a few methods
2019-08-20 09:03:11 +02:00
SomberNight
930d21c31c channel close handling: detect situation based on output addresses
WIP...
2019-08-20 09:03:11 +02:00
SomberNight
acbb458ef7 set default to_self_delay to 1 day 2019-08-20 09:03:11 +02:00
SomberNight
9a0ba7fa79 ChannelDB: trivial bugfix for get_channels_for_node 2019-08-20 09:03:11 +02:00
ThomasV
c38afe8b07 add to_remote to breach test 2019-08-20 09:03:11 +02:00
ThomasV
765114faf7 add function new_blocks to simplify regtests 2019-08-20 09:03:11 +02:00
SomberNight
18a2a169c7 qt ChannelDetails: fix show_tx 2019-08-20 09:03:11 +02:00
SomberNight
6d8c605307 move lnworker.first_block to constants 2019-08-20 09:03:11 +02:00
SomberNight
a0764c017c lnpeer: process gossip in chunks 2019-08-20 09:03:11 +02:00
SomberNight
62f58c18fe ChannelDB.on_node_announcement: some speed-up
(e.g. for 100 node anns, was ~5 seconds, now 0.7 sec; so still slow)
2019-08-20 09:03:11 +02:00
SomberNight
cffb89002c fix ChannelDB.compare_channels: was raising "too many SQL variables"
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) too many SQL variables
2019-08-20 09:03:11 +02:00
SomberNight
60cd885f74 tests/regtest: stop using "bitcoin-cli generate" (deprecated) 2019-08-20 09:03:11 +02:00
ThomasV
84c6a464e8 gui: channel_details minor fix 2019-08-20 09:03:11 +02:00
ThomasV
a70441f0f1 minor fixes: remove_channel 2019-08-20 09:03:11 +02:00
ThomasV
6d9ef29690 redo LNWorker pay:
- wait until htlc has been fulfilled
 - raise if htlc is not fulfilled
 - return boolean success
 - try multiple paths in GUI
2019-08-20 09:03:11 +02:00
ThomasV
669b84fbd6 gui channels_list fixes 2019-08-20 09:03:11 +02:00
ThomasV
32e517f407 improve lightning_dialog GUI settings 2019-08-20 09:03:11 +02:00
ThomasV
15eecab978 update electrumx starting script 2019-08-20 09:03:11 +02:00
ThomasV
a5570d94f3 channel blacklist: minor fix 2019-08-20 09:03:11 +02:00
ThomasV
fed6c96693 add option to remove channel after it has been closed 2019-08-20 09:03:11 +02:00
ThomasV
3c0df28c98 do not include 'force_closing' in channel states, because it is not part of the peer protocol 2019-08-20 09:03:11 +02:00
ThomasV
0acd0c23d3 fix: key must be unique (sql) 2019-08-20 09:03:11 +02:00
ThomasV
d30307b29e gui: improve display of lightning status 2019-08-20 09:03:11 +02:00
ThomasV
eb4e6bb0de improve filter_channel_updates
blacklist channels that do not really get updated
2019-08-20 09:03:11 +02:00
ThomasV
f4b3d7627d query_short_channel_ids: use Event instead of Lock 2019-08-20 09:03:11 +02:00
ThomasV
d30e894aaf fix: add_channel_update 2019-08-20 09:03:11 +02:00
ThomasV
b055eeace2 prune channels older than two weeks from database 2019-08-20 09:03:11 +02:00
ThomasV
522ce5bb9f verify channel updates in peer's TaskGroup 2019-08-20 09:03:11 +02:00
ThomasV
308dc6aa6b use a single queue for gossip messages, so that they are processed in the correct order 2019-08-20 09:03:11 +02:00
ThomasV
e68f318b12 verify node and channel announcements before entering sql lock 2019-08-20 09:03:11 +02:00
SomberNight
fbafc77f01 lnpeer query_short_channel_ids: BOLT-07 says ids must be sorted
this is why most remote peers were disconnecting upon receiving this msg
2019-08-20 09:03:11 +02:00
SomberNight
afc5717cf0 lnpeer reply_channel_range: handle lnd 2019-08-20 09:03:11 +02:00
ThomasV
0e42fd9f17 parallelize short_channel_id requests 2019-08-20 09:03:11 +02:00
ThomasV
1011245c5e LNGossip: sync channel db using query_channel_range 2019-08-20 09:03:11 +02:00
ThomasV
95376226e8 save lightning invoice descriptions as labels and allow user to edit them 2019-08-20 09:03:11 +02:00
ThomasV
b1f8c42424 post-rebase fix 2019-08-20 09:03:11 +02:00
ThomasV
f5c58c5e19 lightning network dialog 2019-08-20 09:03:11 +02:00
ThomasV
db60634774 use config instead of watchtower_window to decide if we close the app 2019-08-20 09:03:11 +02:00
ThomasV
e53ecb9b77 add labels to lightning history 2019-08-20 09:03:11 +02:00
ThomasV
c4081284bd lightning: GUI improvements 2019-08-20 09:03:11 +02:00
ThomasV
670424f080 get_payments: do not include failed payments 2019-08-20 09:03:11 +02:00
ThomasV
2ec82433b4 wallet: add lnworker in constructor for CLI 2019-08-20 09:03:11 +02:00
ThomasV
446a8b0dd9 fix column names 2019-08-20 09:03:11 +02:00
ThomasV
31684abb51 do not SPV channel announcements; this is too resource intensive 2019-08-20 09:03:11 +02:00
ThomasV
d134937269 update regexp syntax 2019-08-20 09:03:11 +02:00
ThomasV
746dd725c6 follow-up previous 2019-08-20 09:03:11 +02:00
ThomasV
842fff832f enable lightning through command line option 2019-08-20 09:03:11 +02:00
ThomasV
9a3a63d18e logging (follow-up rebase) 2019-08-20 09:03:11 +02:00
SomberNight
e4ed15f683 fix tests 2019-08-20 09:03:11 +02:00
SomberNight
cc57da704b logging: adapt lightning branch to logging changes 2019-08-20 09:03:11 +02:00
ThomasV
730be17aba Use separate lightning nodes for gossip and channel operations. 2019-08-20 09:03:11 +02:00
ThomasV
12743bda93 remove test_require_data_loss_protect 2019-08-20 09:03:11 +02:00
ThomasV
34f22e6681 lnrouter: load data before finding path 2019-08-20 09:03:11 +02:00
ThomasV
dac686b11d minor fix 2019-08-20 09:03:11 +02:00
SomberNight
fda6fb6521 lnhtlc: handle fails asymmetrically 2019-08-20 09:03:11 +02:00
SomberNight
f47519bdf3 lnchan: make_commitment and balance (follow-up prev)
"balance(self, subject, ctn=None)" is underspecified.
whose balance? whose commitment transaction?
this lead to issues...
2019-08-20 09:03:11 +02:00
SomberNight
8632f027da lnhtlc: small clean-up / docstrings 2019-08-20 09:03:11 +02:00
SomberNight
268f05c60c lnhtlc: add type hints 2019-08-20 09:03:11 +02:00
ThomasV
796f199a5b follow-up prev commit 2019-08-20 09:03:11 +02:00
ThomasV
a6469904ee fix verify_sig_for_channel_update: use raw message 2019-08-20 09:03:11 +02:00
ThomasV
407114d3cc lnpeer:
- disable option data_loss_protect
 - disable query_short_channel_ids
 - increase timeouts in pay
2019-08-20 09:03:11 +02:00
ThomasV
e2182f5846 lnwatcher fixes 2019-08-20 09:03:11 +02:00
ThomasV
f0ac81419c increase timeout in open_channel 2019-08-20 09:03:11 +02:00
ThomasV
5eab60621d lnpeer:
- send data_loss_protect fields with channel_reestablish
 - close connection if we receive an unknown channel_reestablish
 - log all exceptions that make us close a connection
 - formatting
2019-08-20 09:03:11 +02:00
ThomasV
b76728d459 lnrouter: fix get_last_good_address: one_or_none -> all 2019-08-20 09:03:11 +02:00
ThomasV
9983578df0 lightning tx: remove amount from label 2019-08-20 09:03:11 +02:00
ThomasV
93e8d4f953 channel_db: fix timestamp_range, reduce verbosity 2019-08-20 09:03:11 +02:00
ThomasV
aa398993cf lnrouter fixes:
- use gossip_queries_req instead of initial_routing_sync
 - add connected nodes to recent peers only after successful init
 - derive timestamp used with gossip_timestamp_filter from channel_db
 - fix query_short_channel_ids:
     1. channel IDs must be sorted with zlib
     2. limit request to 100
     3. do not abuse this to request node_announcements; it is fine not to have all nodes.
 - fix get_recent_peers:
     1. do not set last_connected_date to 'now' if we never connected a node
     2. sql query was misconstructed and was returning only one peer
 - populate FALLBACK_NODE_LIST_MAINNET with nodes that have the requested flags
2019-08-20 09:03:11 +02:00
ThomasV
e7888a50be fix sql conflicts in lnrouter 2019-08-20 09:03:11 +02:00
ThomasV
eae8f1a139 gui: show only initialized peers 2019-08-20 09:03:11 +02:00
ThomasV
f5eb369fb7 lnpeer: spawn wait_for(self.initialized) to kill the TaskGroup if it times out 2019-08-20 09:03:11 +02:00
ThomasV
2c80996fbf lnrouter: fix primary key conflict in Policy update 2019-08-20 09:03:11 +02:00
ThomasV
e7218d798d add get_channel_ctx to CLI, for testing breaches 2019-08-20 09:03:11 +02:00
ThomasV
a698344136 cleanup tests that use regtest:
- separate setup from execution
 - install bitcoind and electrumx in travis
 - use the same framework for lnwatcher and forwarding
 - make tests runnable locally
2019-08-20 09:03:11 +02:00
ThomasV
023d4026b9 fix local_index in channel _shutdown 2019-08-20 09:03:11 +02:00
ThomasV
3abe30e9d8 basic watchtower synchronization 2019-08-20 09:03:11 +02:00
ThomasV
c155293166 follow-up variable renaming 2019-08-20 09:03:11 +02:00
ThomasV
5148397a75 test forwarding 2019-08-20 09:03:11 +02:00
ThomasV
90c42c7f1b fix icons in receive tab 2019-08-20 09:03:11 +02:00
ThomasV
ec97d623a5 force-close channel if unfulfilled htlc is close to cltv expiry 2019-08-20 09:03:11 +02:00
ThomasV
4dc6c6c82e fix tests (follow up prev commit) 2019-08-20 09:03:11 +02:00
ThomasV
8d77a7ecd8 save timestamps in htlc log 2019-08-20 09:03:11 +02:00
ThomasV
ecd9508233 follow-up previous commit 2019-08-20 09:03:11 +02:00
ThomasV
a7d37b72db factorize channel opening code into chan.open_with_first_pcp 2019-08-20 09:03:11 +02:00
ThomasV
c3f6351922 simplify points, remove side-effect in reestablish_channel 2019-08-20 09:03:11 +02:00
ThomasV
b5fd27c64c fix local/remote confusion in reestablish_channel 2019-08-20 09:03:11 +02:00
ThomasV
82491ff083 do not duplicate ctn in channel log and config 2019-08-20 09:03:11 +02:00
ThomasV
8d28188d24 lnhtlc: remove unused field in log 2019-08-20 09:03:11 +02:00
ThomasV
51466930f2 reestablish_peers_and_channels: spawn tasks in for loop 2019-08-20 09:03:11 +02:00
ThomasV
38f1436d78 post rebase fixes 2019-08-20 09:03:11 +02:00
ThomasV
b215d6c4b7 lnhtlc: rename ctnheights -> ctn 2019-08-20 09:03:11 +02:00
ThomasV
02d013421a lnwatcher: store transactions as binary 2019-08-20 09:03:11 +02:00
ThomasV
d8e9a9a49e create parent class for sql databases 2019-08-20 09:03:11 +02:00
ThomasV
b861e2e955 lnwatcher: save sweepstore in sqlite database 2019-08-20 09:03:11 +02:00
ThomasV
bfdf0a7e88 start asyncio loop in test_lnrouter and test_lnpeer 2019-08-20 09:03:11 +02:00
ThomasV
29afe52b4c sqlite: do not use scoped_session 2019-08-20 09:03:11 +02:00
ThomasV
cab0f7d9e0 add sqlalchemy to requirements 2019-08-20 09:03:11 +02:00
ThomasV
436c313790 follow-up previous commit 2019-08-20 09:03:11 +02:00
ThomasV
46aa5c1958 lnrouter: perform SQL requests in a separate thread. persist database. 2019-08-20 09:03:11 +02:00
ThomasV
9f188c087c Flatten the structure of lnrouter, so that DBSession is not used outside of ChannelDB 2019-08-20 09:03:11 +02:00
Janus
95a2174789 sqlite in lnrouter: lnpeer: introduce _gossip_loop for gossip handling separated from message handling 2019-08-20 09:03:11 +02:00
Janus
3442e51fac sqlite in lnrouter: remove useless InDB suffix 2019-08-20 09:03:11 +02:00
Janus
945e1dc4ee sqlite in lnrouter: request missing channel_announcements and node_announcements 2019-08-20 09:03:11 +02:00
Janus
d2d67f1fe1 sqlite in lnrouter: avoid exceptions on shutdown 2019-08-20 09:03:11 +02:00
Janus
dd7c4b3bab sqlite in lnrouter 2019-08-20 09:03:11 +02:00
ThomasV
d94e40d2be add 'txpos' field to lightning history items, in case two transactions have the same timestamp 2019-08-20 09:03:11 +02:00
ThomasV
7a51f034e4 add future transactions to address synchronizer 2019-08-20 09:03:11 +02:00
ThomasV
2994764ad8 history: add column for ln amount 2019-08-20 09:03:11 +02:00
ThomasV
7a0e8bb343 fix amount_msat sign in get_history 2019-08-20 09:03:11 +02:00
ThomasV
43d9e0460e follow-up previous commit 2019-08-20 09:03:11 +02:00
ThomasV
8aa4ce0704 improve watchtower gui 2019-08-20 09:03:11 +02:00
ThomasV
8b12f481da lightning: display forwarded payments as a single history item 2019-08-20 09:03:11 +02:00
ThomasV
3e443535a2 lnchannel: pass reference to lnworker 2019-08-20 09:03:11 +02:00
ThomasV
a8e2f79563 lnchannel: save timestamp when we settle 2019-08-20 09:03:11 +02:00
ThomasV
ed4cecf19c ln_message: trigger notification instead of popup 2019-08-20 09:03:11 +02:00
SomberNight
ef7a59b4a9 lnchannel: save htlc preimages as soon as possible but horribly hacky
will properly clean this up...
2019-08-20 09:03:11 +02:00
SomberNight
7292da24e6 lnchannel: only consider payments finished when we revoke our old ctx
in the old code,
`self.hm.received_in_ctn(self.config[REMOTE].ctn + 1)`
did not really make sense as "received_in_ctn" compares the argument against the LOCAL ctn
2019-08-20 09:03:11 +02:00
SomberNight
a565c500f6 lnhtlc: revert 0c4e7b856f8c96c4f0a33bf3e0d1c8fd8184bd36 2019-08-20 09:03:11 +02:00
SomberNight
5f164bcbe8 travis: don't build binaries on ln branch (also for appimage) 2019-08-20 09:03:11 +02:00
SomberNight
7644c84e07 qt: update whole gui on every LN payment
inefficient... but at least kept updated.
2019-08-20 09:03:11 +02:00
SomberNight
021f5d570e lnpeer: check that remote only sends commit_sig if there are changes 2019-08-20 09:03:11 +02:00
SomberNight
dfc2a35ae6 qt: handle LN invoices better in Send tab 2019-08-20 09:03:11 +02:00
SomberNight
962628ac3d lnworker: minor clean-up re payment_completed 2019-08-20 09:03:11 +02:00
ThomasV
11c0c0d5a1 lnhtlc: fix received_in_ctn (LOCAL->REMOTE) 2019-08-20 09:03:11 +02:00
SomberNight
9206b6225b tmp fix for circular imports 2019-08-20 09:03:11 +02:00
SomberNight
828f07a1ff qt request_list: disable editing existing items 2019-08-20 09:03:11 +02:00
SomberNight
f0b4d1ecce qt request_list: minor fix for context menu 2019-08-20 09:03:11 +02:00
SomberNight
64b2844e81 qt request_list: fix changing between items 2019-08-20 09:03:11 +02:00
Janus
f618bb4a67 lnhtlc: handle settles like adds (asymmetrical across ctns) 2019-08-20 09:03:11 +02:00
SomberNight
3d0b5fc80f more post-rebase fixups 2019-08-20 09:03:11 +02:00
SomberNight
3a2ab149b9 lnchannel: add_htlc and receive_htlc now take and return UpdateAddHtlc
also fix undefined vars in _maybe_forward_htlc and _maybe_fulfill_htlc
in lnpeer
2019-08-20 09:03:11 +02:00
ThomasV
62be0c481c lightning: Save invoices and preimages separately. Save preimages when forwarding 2019-08-20 09:03:11 +02:00
ThomasV
e475617b75 lnpeer: distinguish local and remote pending updates 2019-08-20 09:03:11 +02:00
ThomasV
459f9aaee7 lnchannel: reformatting 2019-08-20 09:03:11 +02:00
ThomasV
4228b926d4 lnpeer: send commitment after receiving updates 2019-08-20 09:03:11 +02:00
ThomasV
5f516bac35 move lightning icon 2019-08-20 09:03:11 +02:00
SomberNight
2976f50b8c lightning post-rebase fixups 2019-08-20 09:03:11 +02:00
ThomasV
3d8e168a85 follow-up previous commit 2019-08-20 09:03:11 +02:00
ThomasV
6eba22b5a8 lnpeer: replace asyncio.sleep with events 2019-08-20 09:03:11 +02:00
ThomasV
86b33a5637 code refactoring: _maybe_fullfill_htlc, _maybe_forward_htlc 2019-08-20 09:03:11 +02:00
ThomasV
5d26f51ad0 lnchannel: fix error message 2019-08-20 09:03:11 +02:00
ThomasV
a40207cbbb Refactor LNPeer in order to support HTLC forwarding:
1. Do not perform channel updates in coroutines, because they would get executed in random order.
 2. After applying channel updates, wait only for the relevant commitment (local or remote) and not for both, because local and remote might be out of sync (BOLT 2).
 3. When waiting for a commitment, wait until a given ctn has been reached, because a queue cannot be shared by several coroutines
2019-08-20 09:03:11 +02:00
ThomasV
50b4f785a9 test_lnpeer: add names 2019-08-20 09:03:11 +02:00
ThomasV
3dce65dc73 Rename lnchan, lnchannel_verifier, lnbase
Auto-completions are a pain if files share a long prefix
2019-08-20 09:03:11 +02:00
Janus
8274a963e6 lnworker: save outgoing invoice when initiating payment 2019-08-20 09:03:11 +02:00
Janus
98e85fd16d qt: channel_details: prevent crash on stuck SENT htlc 2019-08-20 09:03:11 +02:00
Janus
5f1feee331 move lightning message encoding to new lnmsg module 2019-08-20 09:03:11 +02:00
Janus
f5cee9ecf6 lightning: post-rebase fixes, read_QIcon and invalid import 2019-08-20 09:03:11 +02:00
ThomasV
d5006e83e7 test_forwarding: do not set HOME 2019-08-20 09:03:11 +02:00
ThomasV
954e4c8892 lnbase: rename methods, fix tests 2019-08-20 09:03:11 +02:00
ThomasV
909f1e77de script that tests htlc forwarding 2019-08-20 09:03:11 +02:00
ThomasV
43e6e08840 Forward HTLCs 2019-08-20 09:03:11 +02:00
ThomasV
a975ac1571 lnworker: get_channel_by_short_id 2019-08-20 09:03:11 +02:00
ThomasV
fa96efabb5 lnpeer: receive_and_revoke, send_and_revoke 2019-08-20 09:03:11 +02:00
ThomasV
f4b2644620 set short_channel_id regardless of channel state, because peer might be disconnected 2019-08-20 09:03:11 +02:00
ThomasV
b265212fe6 show amount in channel opening/closure 2019-08-20 09:03:11 +02:00
ThomasV
d9813540ac fix: test short_channel_id before removing from channel_db 2019-08-20 09:03:11 +02:00
ThomasV
108a986ef0 history tab: render channel opening/closure on a single line 2019-08-20 09:03:11 +02:00
ThomasV
82e8bcebb6 restructure channel_reestablish, resend funding_locked if needed 2019-08-20 09:03:11 +02:00
ThomasV
8e753f998a fix tests 2019-08-20 09:03:11 +02:00
ThomasV
a3c6f82bb2 move LNPeer handshake back into initialize 2019-08-20 09:03:11 +02:00
ThomasV
42cbe74e95 history: better handling of None timestamps 2019-08-20 09:03:11 +02:00
ThomasV
2a112b867b follow-up previous commit 2019-08-20 09:03:11 +02:00
ThomasV
b5482e4470 create transport and perform handshake before creating Peer 2019-08-20 09:03:11 +02:00
ThomasV
61638664f7 do not add ephemeral addresses to recent peers 2019-08-20 09:03:11 +02:00
ThomasV
472e82e387 fix channel closure when it was requested by the remote party 2019-08-20 09:03:11 +02:00
ThomasV
d383573bc3 CLI: use funding_point in channel_open and channel_close. add host:port to nodeid 2019-08-20 09:03:11 +02:00
ThomasV
0924503cb6 rpartition->rsplit 2019-08-20 09:03:11 +02:00
ThomasV
776caeeff0 follow-up prev commit: use maxsize to sort timestamps 2019-08-20 09:03:11 +02:00
ThomasV
f04e10f61a save channel timestamps, and show lightning payments in history tab 2019-08-20 09:03:11 +02:00
ThomasV
ae402303ca channel: is_closed 2019-08-20 09:03:11 +02:00
ThomasV
f6f5cbee72 fix lnworker.get_balance 2019-08-20 09:03:11 +02:00
ThomasV
b7d93e2e11 gui: display lightning balance in status bar 2019-08-20 09:03:11 +02:00
Janus
18bd934461 ln: show full chan id in list, use Event for initialized, more timeouts, return peer from add_peer, set max_htlc_value_in_flight_msat to capacity 2019-08-20 09:03:11 +02:00
ThomasV
19e60f00bb add watchtower_window 2019-08-20 09:03:11 +02:00
ThomasV
7bb4ea150f gui: show incoming lightning requests, add on-chain icon 2019-08-20 09:03:11 +02:00
ThomasV
2af178a586 Store boolean is_received in lightning invoices. Sort lightning history with timestamp. Minor fixes 2019-08-20 09:03:11 +02:00
Janus
4e3b2b5479 tests: update lnbase test to use 4-tuple for invoices 2019-08-20 09:03:11 +02:00
ThomasV
281d51c002 follow-up prev commit 2019-08-20 09:03:11 +02:00
ThomasV
0e8dba897e lightning:
* store invoices for both directions
* do not store lightning_payments_inflight, lightning_payments_completed in lnworker
* payment history is returned by get_payments method of LNChannel
* command line: lightning history, lightning_invoices
* re-enable push_msat
2019-08-20 09:03:11 +02:00
Janus
d80b709aa4 lnbase: fix on_open_channel, add TODOs for missing validation 2019-08-20 09:03:11 +02:00
Janus
d5ed4309bb revert low max_htlc_value_in_flight_msat, fix test
spec does not mention that there can be an upper bound
on max_htlc_value_in_flight_msat, so don't try to make
any node happy that has a max limit on this.
2019-08-20 09:03:11 +02:00
Janus
e6bd3959e0 ln: handle channel limits better, show remote limits in details dialog, replace rusty's testnet peer (doesn't work currently) 2019-08-20 09:03:11 +02:00
ThomasV
0a08ccc1c6 rename paying -> inflight 2019-08-20 09:03:11 +02:00
ThomasV
b0d6000771 turn lightning_payments_completed into dict. Show status of lightning payments in GUI. Make 'listchannels' available offline 2019-08-20 09:03:11 +02:00
ThomasV
26ced1b343 fix test 2019-08-20 09:03:11 +02:00
ThomasV
d789f11898 remove deterministic derivation for testing 2019-08-20 09:03:11 +02:00
ThomasV
e6d680ec1b instanciate LNWorker without Network 2019-08-20 09:03:11 +02:00
Janus
7cf4f40dcb ln: warn when negotiated feerate is too low 2019-08-20 09:03:11 +02:00
Janus
8fc1779b0d ln: add test_lnwatcher 2019-08-20 09:03:11 +02:00
Janus
129099797a lnworker: handle null whitelist correctly 2019-08-20 09:03:11 +02:00
Janus
3b44cf8c67 lnworker: fix 'channel details' with stuck htlc 2019-08-20 09:03:11 +02:00
Janus
f8dc9b344a lnbase: work around peer not sending funding_locked so channel doesn't get marked open 2019-08-20 09:03:11 +02:00
Janus
6b6097a453 ln: add closechannel cli command 2019-08-20 09:03:11 +02:00
Janus
bd45f3f1c8 lnworker: return txid from force_close_channel as expected 2019-08-20 09:03:11 +02:00
Janus
38396e8ed4 lnwatcher: fix incorrect tuple unpacking in do_breach_remedy 2019-08-20 09:03:11 +02:00
Janus
38d2d4c321 lnchan: fix per_commitment_point number in verify_htlc 2019-08-20 09:03:11 +02:00
Janus
1f1207ecbe ln: request_list: post rebase fixup (remove new_request_button deactivation) 2019-08-20 09:03:11 +02:00
ThomasV
02798aeb39 sort requests by date 2019-08-20 09:03:11 +02:00
ThomasV
3ce0f7f0cd simplify requests list 2019-08-20 09:03:11 +02:00
ThomasV
290a1c61a8 confirmation dialog before force closure 2019-08-20 09:03:11 +02:00
ThomasV
dbcd5fe59d channel_details: minor fix 2019-08-20 09:03:11 +02:00
ThomasV
3c8dea9b28 lightning: simplify request tab 2019-08-20 09:03:11 +02:00
Janus
51bc02557d ln: fix sweeping htlc output from remote ctx (timeout e.g. without htlc tx) 2019-08-20 09:03:11 +02:00
Janus
e56e849505 lnchan refactor
- replace undoing logic with new HTLCManager class
- separate SENT/RECEIVED
- move UpdateAddHtlc to lnutil
2019-08-20 09:03:11 +02:00
Janus
ef88bb1c28 request_list: select new items 2019-08-20 09:03:11 +02:00
ThomasV
d9e7807fff lnpeer: print port in error_messages 2019-08-20 09:03:11 +02:00
Janus
3ccd2fedff ln: two remaining encumberedTx removal remnants 2019-08-20 09:03:11 +02:00
ThomasV
320dc29732 use to_dict (follow-up baa03a469f3e0e0ae61593272f5cb7e2483d49ad) 2019-08-20 09:03:11 +02:00
Janus
9cbf55f977 request_list: do not mention that ln invoice is bolt-11 2019-08-20 09:03:11 +02:00
Janus
720146ee40 request_list: fix qr code display and keyboard selection of lightning invoices 2019-08-20 09:03:11 +02:00
ThomasV
5776b322f7 formatting 2019-08-20 09:03:11 +02:00
ThomasV
b0f39718bb remove useless returns and cryptic names 2019-08-20 09:03:11 +02:00
Janus
2c1fcb2f54 ln: remove EncumberedTransaction 2019-08-20 09:03:11 +02:00
Janus
60508725b6 lnbase: fix sig encoding in mutual close 2019-08-20 09:03:11 +02:00
Janus
3975560db5 lnsweep: use dicts consistently 2019-08-20 09:03:11 +02:00
Janus
06d4224101 lnchan: remove debugging code, commented out code 2019-08-20 09:03:11 +02:00
Janus
2323118bda lnchan: only sign force_close_tx when demanded, assure consistency, fix test 2019-08-20 09:03:11 +02:00
Janus
37a0315aab lnbase: fix NameError 2019-08-20 09:03:11 +02:00
Janus
1d8fe52fa3 lnchan: make force_close_tx() assure that tx is valid, trigger failure in test 2019-08-20 09:03:11 +02:00
ThomasV
cf3e050b7e fix error message 2019-08-20 09:03:11 +02:00
ThomasV
8274067619 fix lnsweep: prevout 2019-08-20 09:03:11 +02:00
ThomasV
7e34554d1e fix test_lnchan 2019-08-20 09:03:11 +02:00
ThomasV
fde9f91902 lnchannel: store pre-signed sweep transactions after each new commitment 2019-08-20 09:03:11 +02:00
ThomasV
3019aa35cf on_close_channel: fix output index, and simplify lnsweep 2019-08-20 09:03:11 +02:00
ThomasV
729ddb8ec3 LNWatcher refactoring:
- do not store non-breach transactions
 - send 'channel_open' and 'channel_closed' events
 - force-closed channels are handled by LNWorker
2019-08-20 09:03:11 +02:00
ThomasV
1b7a3c25d1 lnsweep: return ctx.txid instead of None 2019-08-20 09:03:11 +02:00
Janus
56853da391 qt main_window: do not unregister on shutdown
this is handled differently in lightning, see commit

commit 6e355601261a60d143561f15760cc48f9c81d000
Author: ThomasV <thomasv@electrum.org>
Date:   Sun Jun 3 10:07:56 2018 +0200

    integrate channels_list with existing framework
2019-08-20 09:03:11 +02:00
Janus
545182e0a5 lnsweep: make maybe_create_sweeptx_for_their_ctx_to_local consistent 2019-08-20 09:03:11 +02:00
ThomasV
3aa36c1502 Channel: add current_commitment method 2019-08-20 09:03:11 +02:00
ThomasV
3222e26e01 format message 2019-08-20 09:03:11 +02:00
ThomasV
f4b9d2f47c show lightning network capacity in GUI 2019-08-20 09:03:11 +02:00
Janus
47c07f77b4 lnsweep: fix create_sweeptxs_for_their_just_revoked_ctx
in the case where an htlc is failed, it could happen
that we use the wrong list of htlcs to generate sweep
tx'es. we would use the pending list instead of the
committed list.

observed by sending 12300sat and then 123000sat,
the second payment fails and an AssertionError was
triggered cause the htlc output could not be found
in the ctx.

added some documentation to clarify the behaviour
of lnchan.included_htlcs.
2019-08-20 09:03:11 +02:00
Janus
1fbce71c1f update lightning.json 2019-08-20 09:03:11 +02:00
Janus
4a2a45d7e3 lightning: post rebase qt gui fixes 2019-08-20 09:03:11 +02:00
ThomasV
d493dd1953 add pycryptodomex to requirements 2019-08-20 09:03:11 +02:00
Janus
133e5ec8c3 use gossip_timestamp_filter instead of request_initial_sync 2019-08-20 09:03:11 +02:00
Janus
864d910888 qt: channel_details: add more info: sent/received, channel id, funding tx, short channel id, node id 2019-08-20 09:03:11 +02:00
Janus
762d8be84f lnaddr: make it possible to use lnaddr to decode arbitrary invoices on the cmd line 2019-08-20 09:03:11 +02:00
SomberNight
9256472485 rm 'cryptography' as dependency; use new pycryptodomex version
pycryptodomex 3.7 implemented chacha20_poly1305 and chacha20,
and it is already used (although optionally) to speed up AES,
so we can remove cryptography and make pycryptodomex mandatory for LN
2019-08-20 09:03:11 +02:00
ThomasV
c0a1af2032 fix channel closure:
- add 'CLOSING' state
 - wait until channel has no inflight HTLC
 - end fee negocitation when both parties agree on the fee
   (previously code ended it only when the other party had broadcast)
 - broadcast the closing transaction
2019-08-20 09:03:11 +02:00
ThomasV
5bc74772a2 follow up 'replace properties with functions' 2019-08-20 09:03:11 +02:00
ThomasV
0e3270a1d6 further simplify lnwatcher 2019-08-20 09:03:11 +02:00
SomberNight
dbc4549c0e lnchan: restore process_new_offchain_ctx 2019-08-20 09:03:11 +02:00
SomberNight
0070ae1fb1 fix prev 2019-08-20 09:03:11 +02:00
SomberNight
595cfcbb65 move sweeping methods from lnchan.py to new file
also sweep "received" htlcs from "our" ctx
also sweep htlcs from their ctx (non-breach)
extract ctn; included_htlcs_in_their_latest_ctxs
2019-08-20 09:03:11 +02:00
SomberNight
bc72966442 lnchan: follow-up "replace properties with functions" 2019-08-20 09:03:11 +02:00
ThomasV
88c6eeb966 make LNWatcher inherit AddressSynchronizer 2019-08-20 09:03:11 +02:00
ThomasV
78896897cb lnchan: replace properties with functions 2019-08-20 09:03:11 +02:00
Janus
c339eabd31 qt: channel_details: remove demo code 2019-08-20 09:03:11 +02:00
Janus
e3409d32ef channel details with list of htlcs 2019-08-20 09:03:11 +02:00
Janus
9d32031ca2 Kivy: Lightning support in Receive tab 2019-08-20 09:03:11 +02:00
Janus
ecac8f2880 tests/lnbase: stub on_channels_updated 2019-08-20 09:03:11 +02:00
Janus
7db9a22d63 Kivy: open channel dialog 2019-08-20 09:03:11 +02:00
ThomasV
3430d1aaa3 follow-up prev commit 2019-08-20 09:03:11 +02:00
ThomasV
5422de90a2 lightning: do not handle more than one fee update at a time 2019-08-20 09:03:11 +02:00
Janus
1352b0ce9f Kivy: Support Lightning in Send tab 2019-08-20 09:03:11 +02:00
Janus
f803bb571d kivy: restore channel list to working state, add [force-]closing functionality 2019-08-20 09:03:11 +02:00
Janus
1520338f37 fix ln tests 2019-08-20 09:03:11 +02:00
SomberNight
521fadb8cb lnutil: restructure channel config namedtuples (local/remote config) 2019-08-20 09:03:11 +02:00
Janus
1425628604 add command for listing invoices and their progress, fix list_channels 2019-08-20 09:03:11 +02:00
Janus
783cac1f23 function that returns map from commitment number to list of HTLCs 2019-08-20 09:03:11 +02:00
SomberNight
5b7c801ca4 after rebase fixes 2019-08-20 09:03:11 +02:00
Janus
8a98810df1 do not co-op close channels with pending htlcs 2019-08-20 09:03:11 +02:00
Janus
9cf7aa054d call force_close_channel on LNWorker, not Peer 2019-08-20 09:03:11 +02:00
Janus
c570bc5fb1 avoid leaving FORCE_CLOSING state, rebroadcast closing tx if reorged out 2019-08-20 09:03:11 +02:00
Janus
0ea87278fb move force_close_channel to lnbase, test it, add FORCE_CLOSING state 2019-08-20 09:03:11 +02:00
Janus
6211e656a8 lnwatcher: do not get_transaction before broadcast
this workaround was inserted to avoid losing the interface
when rebroadcasting a transaction already in the mempool
many times. but since the network should make sure we always
have a interface ready, and this problem shouldn't happen on
mainnet, remove the workaround
2019-08-20 09:03:11 +02:00
Janus
c8dcf0b471 lnwatcher: more detailed logging, support notifying test suite of txs 2019-08-20 09:03:11 +02:00
Janus
795ba1f99d lnwatcher: ensure probable spendability of prev_tx
previously, we would try to publish the second_stage
even if we couldn't, because a conflicting transaction
was published (like an htlc success when we close with
htlcs pending with a 1-hop payment and an online
counterparty)
2019-08-20 09:03:11 +02:00
Janus
f9f1805cdf use IntEnum for TxMinedDepth 2019-08-20 09:03:11 +02:00
ThomasV
632f11d5da watchtower: add watch_channel rpc 2019-08-20 09:03:11 +02:00
Janus
39fa13b938 lnchan: use NamedTuple for logs instead of dict with static keys (adds, locked_in, settles, fails) 2019-08-20 09:03:10 +02:00
Janus
72187a4341 lnchan: make sign_next_commitment revert state 2019-08-20 09:03:10 +02:00
Janus
001bb4ca09 remove incorrect docstrings, attribute docstring sources where applicable 2019-08-20 09:03:10 +02:00
SomberNight
c0ae7b5534 after rebase clean-up 2019-08-20 09:03:10 +02:00
Janus
7e76e82152 test_lnbase: add test that pays to another local electrum 2019-08-20 09:03:10 +02:00
SomberNight
ce2b572fa5 lnbase: more type annotations, and minor fix 2019-08-20 09:03:10 +02:00
SomberNight
449ec013fe add licence headers to more files 2019-08-20 09:03:10 +02:00
Janus
f5201327d1 add lnd copyright boilerplate to lnchan, test_lnchan 2019-08-20 09:03:10 +02:00
Janus
85789d8a09 lnbase: mark initialized later, add tests, etc
- consistent node_id sorting
- require OPTION_DATA_LOSS_PROTECT and test it
2019-08-20 09:03:10 +02:00
Janus
a42c1067ab lnworker: fix listchannels 2019-08-20 09:03:10 +02:00
Janus
578faeb91a lnbase: do not assert only one htlc in commitment 2019-08-20 09:03:10 +02:00
Janus
a5a7c1406e lightning channels reserves: use pretty balance in Qt, fix bugs, add tests 2019-08-20 09:03:10 +02:00
ThomasV
eb4e6b2e54 use WaitingDialog to close channels 2019-08-20 09:03:10 +02:00
ThomasV
70dbd8e672 add close_channel method to peer 2019-08-20 09:03:10 +02:00
ThomasV
f985aac8d1 fix typo 2019-08-20 09:03:10 +02:00
ThomasV
a5ab431b4b parse invoices with lightning: prefix 2019-08-20 09:03:10 +02:00
ThomasV
5ca6fbaea7 lnbase: self.channel_reestablished is not a queue 2019-08-20 09:03:10 +02:00
Janus
15b0720f5e lightning channel reserves 2019-08-20 09:03:10 +02:00
SomberNight
54edc9488a lnworker: store invoices based on payment_hash 2019-08-20 09:03:10 +02:00
ThomasV
d9facabc8c lnbase: call save_channel in revoke and receive_revoke 2019-08-20 09:03:10 +02:00
Janus
0dfc9e512b fix co-op close 2019-08-20 09:03:10 +02:00
Janus
d6f62d4e7f follow-up redeeming of local outgoing htlc outputs, fix tests 2019-08-20 09:03:10 +02:00
SomberNight
04ec7e9968 lnutil.make_funding_input: don't return payment pubkeys
order depends on who is initiator anyway
2019-08-20 09:03:10 +02:00
Janus
1f97a9753e redeem htlc outputs of our local commitment transaction back to wallet 2019-08-20 09:03:10 +02:00
SomberNight
f70e679aba some more type annotations that needed conditional imports 2019-08-20 09:03:10 +02:00
SomberNight
f3d1f71e94 lnchan: set diagnostic_name
previously was only set for tests
2019-08-20 09:03:10 +02:00
SomberNight
edff357fad better handling of channel updates for private channels 2019-08-20 09:03:10 +02:00
SomberNight
bd48072e04 lnrouter: can_pay for own channels should use amount_to_forward 2019-08-20 09:03:10 +02:00
SomberNight
2364de930b lnrouter: run Dijkstra in reverse direction 2019-08-20 09:03:10 +02:00
SomberNight
7edbd5682a fix confusion re max path length 2019-08-20 09:03:10 +02:00
SomberNight
2fafd01945 protect against getting robbed through routing fees 2019-08-20 09:03:10 +02:00
SomberNight
c577df8489 lnbase: when opening channel, test if we have enough balance first
and make sure we don't try to create the funding txn from local UTXOs
2019-08-20 09:03:10 +02:00
SomberNight
d511ecdc00 start failing htlcs 2019-08-20 09:03:10 +02:00
SomberNight
ded11b4d9e lnonion: implement error packet construction 2019-08-20 09:03:10 +02:00
SomberNight
dfe61e15c3 invoice 'r' field fallback: change cltv to 1 2019-08-20 09:03:10 +02:00
SomberNight
4b37343c62 unify hardcoded regtest fee 2019-08-20 09:03:10 +02:00
ThomasV
d6b4268fde Qt gui: show messages about payment outcome 2019-08-20 09:03:10 +02:00
ThomasV
02c39a950f encapsulate get_invoice in lnworker 2019-08-20 09:03:10 +02:00
ThomasV
095de2dd22 make on_update_add_htlc async 2019-08-20 09:03:10 +02:00
SomberNight
3ac9858d59 follow-up prev 2019-08-20 09:03:10 +02:00
SomberNight
56c0983e69 fix multi-hop payments 2019-08-20 09:03:10 +02:00
Janus
ac68c8f531 lnchan: add available_to_spend() 2019-08-20 09:03:10 +02:00
Janus
d317bdbd9b lnchan: make function for onion_keys decoding/encoding 2019-08-20 09:03:10 +02:00
ThomasV
c0aee58e4d follow-up 3460ba738e 2019-08-20 09:03:10 +02:00
SomberNight
e6a0b641d5 lnaddr: encode min_final_cltv into invoice 2019-08-20 09:03:10 +02:00
SomberNight
384fd665b3 log if no invoice has no routing hints 2019-08-20 09:03:10 +02:00
ThomasV
d9eb92979b revert rbf on funding tx 2019-08-20 09:03:10 +02:00
SomberNight
a8b9727817 lnbase: fix peer clean-up 2019-08-20 09:03:10 +02:00
ThomasV
87cc312d1e improve suggest_peers; add htlcs to list_channels. 2019-08-20 09:03:10 +02:00
ThomasV
f8894d467f funding tx: use fees from config and set rbf 2019-08-20 09:03:10 +02:00
SomberNight
9de6028fb5 clean-up Peer init 2019-08-20 09:03:10 +02:00
SomberNight
25c2657680 if channel_update for our channel is missing, fill invoice with zeroes 2019-08-20 09:03:10 +02:00
SomberNight
2e5552816c if payment fails with UPDATE onion error, also utilise channel_update for private channels 2019-08-20 09:03:10 +02:00
Janus
962f70c7da ln: add lightning_listen config option 2019-08-20 09:03:10 +02:00
SomberNight
52377dbfa0 lnrouter: use htlc_maximum_msat 2019-08-20 09:03:10 +02:00
SomberNight
ff0aa90ddf lnworker: make add_peer async 2019-08-20 09:03:10 +02:00
ThomasV
8bb23ea2cd follow-up prev commit: channel_flags, message_flags, htlc_maximum_msat 2019-08-20 09:03:10 +02:00
ThomasV
9659d23bde add htlc_maximum_msat feature (bolt7) 2019-08-20 09:03:10 +02:00
SomberNight
79989ad538 lnbase: typo in on_channel_reestablish 2019-08-20 09:03:10 +02:00
SomberNight
a91e244a05 path finding: minor clean-up 2019-08-20 09:03:10 +02:00
Janus
eabe23f6b8 make function for determining who pays fee 2019-08-20 09:03:10 +02:00
SomberNight
cd175f0949 fix prev 2019-08-20 09:03:10 +02:00
ThomasV
9a59ffaf44 lnrouter: filter out unsuitable channels 2019-08-20 09:03:10 +02:00
ThomasV
a0acec9720 gather definitions of LN exceptions 2019-08-20 09:03:10 +02:00
ThomasV
409a336071 fix tests (follow-up previous commit) 2019-08-20 09:03:10 +02:00
ThomasV
445252284f move transport code to its own file 2019-08-20 09:03:10 +02:00
ThomasV
910e85ec01 future and callback are not needed here 2019-08-20 09:03:10 +02:00
Janus
71afa3cc70 lnbase: split out BOLT-08 (Noise) implementation 2019-08-20 09:03:10 +02:00
SomberNight
0578bbd5d0 fix tests 2019-08-20 09:03:10 +02:00
SomberNight
f267400e1e follow-up prev 2019-08-20 09:03:10 +02:00
SomberNight
17ccb79ca4 channel verifier: NetworkJobOnDefaultServer, and some error handling 2019-08-20 09:03:10 +02:00
ThomasV
5a081b2131 start channel verifier in network.start() 2019-08-20 09:03:10 +02:00
ThomasV
626d09b358 add 'recḱless' option to allow using lightning on mainnet 2019-08-20 09:03:10 +02:00
ThomasV
87fb0da5e1 minor fix 2019-08-20 09:03:10 +02:00
ThomasV
9362130fba fix race between network and lnwatcher (network.add_job does not always work) 2019-08-20 09:03:10 +02:00
ThomasV
e761f5b876 add watchtower class, send encumbered tx as json 2019-08-20 09:03:10 +02:00
SomberNight
7589bdc6a9 fix tests 2019-08-20 09:03:10 +02:00
SomberNight
48252318b8 rebase follow-up 2019-08-20 09:03:10 +02:00
Janus
520b5703a4 lnbase: resend revoke_and_ack if necessary 2019-08-20 09:03:10 +02:00
ThomasV
30753ed475 watchtower: use network job, catch exceptions 2019-08-20 09:03:10 +02:00
ThomasV
680b129b4a remote watchtower: initial commit 2019-08-20 09:03:10 +02:00
Janus
94a10e6307 rebase fixup: use new broadcast_transaction API 2019-08-20 09:03:10 +02:00
Janus
601356f5d1 lnbase: use 45000 feerate on regtest for eclair compatibility 2019-08-20 09:03:10 +02:00
ThomasV
46cf18ce5f open_channel: improved success message 2019-08-20 09:03:10 +02:00
ThomasV
6efe5db0d0 run open_channel in a WaitingDialog 2019-08-20 09:03:10 +02:00
Janus
1763d02b05 rename lnhtlc->lnchan, HTLCStateMachine->Channel 2019-08-20 09:03:10 +02:00
Janus
b26dc66567 lnhtlc: only store feerate once, don't store heights since we do not roll back 2019-08-20 09:03:10 +02:00
Janus
e8471e483b lnhtlc: merge config and state, remove unnecessary properties 2019-08-20 09:03:10 +02:00
Janus
1d4c113a35 lnhtlc: remove lookup_htlc, use heterogeneously typed lists 2019-08-20 09:03:10 +02:00
Janus
699368b0b7 lnhtlc: save settled htlc amounts separately 2019-08-20 09:03:10 +02:00
Janus
18d06dd6b4 qt channels_list: use repr() and not str() for exceptions 2019-08-20 09:03:10 +02:00
ThomasV
d439b3c308 fix previous commit 2019-08-20 09:03:10 +02:00
ThomasV
1ebd1baebf follow-up 1c8a4bcfa497b117e4511c2f108dbca8a1adb793 2019-08-20 09:03:10 +02:00
SomberNight
8d4a5bd1d7 lnbase: handle some error codes re htlc failures ('UPDATE' flag) 2019-08-20 09:03:10 +02:00
SomberNight
a8ace7ef4f lnonion: use IntEnum and IntFlag for failure codes 2019-08-20 09:03:10 +02:00
SomberNight
864efa029b handle failing htlc after restart 2019-08-20 09:03:10 +02:00
SomberNight
eced61123d clean up local/global features 2019-08-20 09:03:10 +02:00
SomberNight
4d32478f30 on_channel_reestablish: try to get remote to force close channel if out-of-sync.
see ACINQ/eclair#727 and lightningnetwork/lnd#1904
2019-08-20 09:03:10 +02:00
SomberNight
1946254ef2 on_update_fail_htlc: don't send commitment without changes
c-lightning was force-closing channels with us: "[lnbase:127.0.0.1] error commit_sig with no changes"
2019-08-20 09:03:10 +02:00
SomberNight
bf25d765d9 simplify ChannelDB.on_channel_update 2019-08-20 09:03:10 +02:00
SomberNight
c1473ca97d travis: don't build binaries on ln branch 2019-08-20 09:03:10 +02:00
SomberNight
a06b49ae40 when paying and there are multiple 'r' hints, use one at random 2019-08-20 09:03:10 +02:00
SomberNight
97393d05aa use 'r' field in invoice when making payments (routing hints) 2019-08-20 09:03:10 +02:00
SomberNight
029ec5a5ab make our channels private, and put routing hints in invoices we create 2019-08-20 09:03:10 +02:00
SomberNight
09c3e52e62 lnworker: fix race
sometimes a reestablished channel would not get marked "open"
2019-08-20 09:03:10 +02:00
SomberNight
53802ba382 lnaddr: clean up imports 2019-08-20 09:03:10 +02:00
ThomasV
c1b34dafe2 follow-up previous commit 2019-08-20 09:03:10 +02:00
ThomasV
4441233596 get rid of callbacks in lnwatcher, use network events instead 2019-08-20 09:03:10 +02:00
ThomasV
242ab5ae56 lightning: fix tests 2019-08-20 09:03:10 +02:00
SomberNight
5fbadafdb1 follow-up lnwatcher changes 2019-08-20 09:03:10 +02:00
ThomasV
6e5b36e661 lnwatcher simplification: remove ctn tests and pubkeys 2019-08-20 09:03:10 +02:00
ThomasV
9a88b5605a add more fields to list_channels 2019-08-20 09:03:10 +02:00
ThomasV
6b9de278d4 aiosafe: define user visible exception class 2019-08-20 09:03:10 +02:00
ThomasV
6f3c2b30ed lnbase: propagate error messages received in on_error to their relevant coroutines 2019-08-20 09:03:10 +02:00
ThomasV
11c3ca281c create sweep transaction outside of lnwatcher 2019-08-20 09:03:10 +02:00
SomberNight
72eb179c7a fix race between lnwatcher/lnworker
channels were sometimes not getting re-established
2019-08-20 09:03:10 +02:00
SomberNight
d44afd9633 fix tests 2019-08-20 09:03:10 +02:00
SomberNight
707c7d569d lnbase: Peer handles its own disconnection instead of lnworker 2019-08-20 09:03:10 +02:00
SomberNight
f3dd7ce615 follow-up prev: avoid storage key collision with old 'lightning_privkey' 2019-08-20 09:03:10 +02:00
SomberNight
17457327ef make key derivation reasonable
no more hardcoded secrets, no more key-reuse
2019-08-20 09:03:10 +02:00
Janus
5859054095 fix lnwatcher for channels initiated by remote 2019-08-20 09:03:10 +02:00
SomberNight
fb8deecb57 lnutil: missing import 2019-08-20 09:03:10 +02:00
Janus
c5b7deac6b lnhtlc: don't save FeeUpdates to disk, only keep FeeUpdate in memory while in progress 2019-08-20 09:03:10 +02:00
Janus
8bd6dc2425 ln: fix opening of channels (NameErrors) 2019-08-20 09:03:10 +02:00
Janus
bdf36ac649 lnbase: update gui after accomodating channel opening request 2019-08-20 09:03:10 +02:00
Janus
0405f0d9ad accept channel opening requests initiated by remote 2019-08-20 09:03:10 +02:00
SomberNight
b18a17ef79 lnchannelverifier: (minor) use named fields of namedtuple 2019-08-20 09:03:10 +02:00
SomberNight
c430b39b7d fix lnworker.choose_preferred_address 2019-08-20 09:03:10 +02:00
Janus
139f773c2e new network API: use broadcast_transaction with run_from_other_thread 2019-08-20 09:03:10 +02:00
Janus
1a7b06b690 lnhtlc: multiply weight by feerate before rounding
This resolves the error formerly manifested as:
Traceback (most recent call last):
  File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/packages/jsonrpclib/SimpleJSONRPCServer.py", line 376, in _dispatch
    return func(*params)
  File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/daemon.py", line 292, in run_cmdline
    result = func(*args, **kwargs)
  File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/commands.py", line 87, in func_wrapper
    return func(*args, **kwargs)
  File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/commands.py", line 697, in lnpay
    return f.result()
  File "/usr/lib/python3.6/concurrent/futures/_base.py", line 432, in result
    return self.__get_result()
  File "/usr/lib/python3.6/concurrent/futures/_base.py", line 384, in __get_result
    raise self._exception
  File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/lnbase.py", line 887, in pay
    sig_64, htlc_sigs = chan.sign_next_commitment()
  File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/lnhtlc.py", line 281, in sign_next_commitment
    htlc_tx = make_htlc_tx_with_open_channel(self, *args)
  File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/lnutil.py", line 262, in make_htlc_tx_with_open_channel
    commit.txid(), commit.htlc_output_indices[original_htlc_output_index],
KeyError: 0
2019-08-20 09:03:10 +02:00
Janus
646881f437 lnworker.pay(): lightning-integration support 2019-08-20 09:03:10 +02:00
Janus
a071aafcc7 lnhtlc: generalize balance/pending_commitment code over htlc direction 2019-08-20 09:03:10 +02:00
Janus
c8dc17012f lnworker: add missing import 2019-08-20 09:03:10 +02:00
Janus
cef3a30c5a lnbase: remove code duplication by introducing send_commitment(chan) 2019-08-20 09:03:10 +02:00
Janus
addd8928bf lnbase: remove unnecessary branching and duplicated code in receive_commitment_revoke_ack 2019-08-20 09:03:10 +02:00
Janus
e5f42a3973 lnhtlc: save settled or failed htlcs 2019-08-20 09:03:10 +02:00
Janus
efc8d50570 move connection string decoding to lnworker, fix test_lnutil 2019-08-20 09:03:10 +02:00
Janus
24cf4e7eb0 qt channels_list: min width, not fixed with (for hiDPI displays), use OK as default button 2019-08-20 09:03:10 +02:00
Janus
9862fe5c0c ecc_fast: require libsecp256k1 on lightning, channel graph breaks electrum without 2019-08-20 09:03:10 +02:00
Janus
1988b552e1 commands: add dumpgraph command to see which nodes electrum knows about 2019-08-20 09:03:10 +02:00
Janus
6bec42d18a requirements: require bitstring for lnaddr, cryptography for lnbase 2019-08-20 09:03:10 +02:00
ThomasV
75dd56eff0 avoid code duplication between methods that send channel updates 2019-08-20 09:03:10 +02:00
Janus
5e7117dddd ln: commit fee updates as soon as possible 2019-08-20 09:03:10 +02:00
Janus
5753cf9e05 ln fees: avoid resending same fee update before it has been committed to, docs 2019-08-20 09:03:10 +02:00
ThomasV
22b06ddec2 lnwatcher: fix parameters to broadcast_transaction 2019-08-20 09:03:10 +02:00
Janus
d07adda3c0 lnhtlc: decrease next_htlc_id counter when removing htlcs when saving 2019-08-20 09:03:10 +02:00
ThomasV
1127d3f467 simplify syntax 2019-08-20 09:03:10 +02:00
Janus
878dc17acb ln: don't save htlcs that are not locked in 2019-08-20 09:03:10 +02:00
Janus
2c6925e526 lnhtlc: bip69 ordering of htlc signatues we generate 2019-08-20 09:03:10 +02:00
Janus
e23e0d6c6e ln: avoid duplicated htlc filter code, support multiple htlcs better 2019-08-20 09:03:10 +02:00
Janus
e18a3b5a3d lnutil: remove LI01 sort after Tx.from_io: fixup after rebase on da9d1e6001 2019-08-20 09:03:10 +02:00
Janus
50b3bc939c avoid reading from queues concurrently in pay() 2019-08-20 09:03:10 +02:00
ThomasV
a54631b873 add pong handler to reduce verbosity 2019-08-20 09:03:10 +02:00
Janus
a04e37d050 keep htlc history in case a htlc fails 2019-08-20 09:03:10 +02:00
Janus
cf4f0c5d3a add command to clear ln blacklist, prevent error in pay() from killing Peer 2019-08-20 09:03:10 +02:00
SomberNight
200b012e57 lnchannelverifier: some clean-up 2019-08-20 09:03:10 +02:00
SomberNight
c91fe27e7d fix lnwatcher: network triggers were renamed 2019-08-20 09:03:10 +02:00
SomberNight
d29191b010 rename LNChanAnnVerifier 2019-08-20 09:03:10 +02:00
SomberNight
fc0009206b fix prev 2019-08-20 09:03:10 +02:00
ThomasV
31b1243f25 lnbase: save_channel in revoke 2019-08-20 09:03:10 +02:00
ThomasV
8df569962d fix: broadcast_transaction from non network thread 2019-08-20 09:03:10 +02:00
ThomasV
180eb6d101 partial revert of df24fb00578309b5db27876769306196238ec3f2: process_message should not be async 2019-08-20 09:03:10 +02:00
ThomasV
dc0f03de99 channel verifier: fix request_chunk args 2019-08-20 09:03:10 +02:00
ThomasV
08448fd2f0 add lnworker in start_network 2019-08-20 09:03:10 +02:00
ThomasV
5c5982d9f7 lnwatcher: do not assume addr_sync.synchronizer 2019-08-20 09:03:10 +02:00
Janus
cc7ef49c43 lnhtlc: also check received htlcs when validating commitment sigs 2019-08-20 09:03:10 +02:00
Janus
224226f427 ln: cooperative close with remote peer initiating 2019-08-20 09:03:10 +02:00
Janus
ff902a55ee lnhtlc: don't settle htlc with HTLCStateMachine too early 2019-08-20 09:03:10 +02:00
Janus
d5d9270d0c lnhtlc: save logs and feeupdates 2019-08-20 09:03:10 +02:00
Janus
eca5545004 lnhtlc: don't throw away fee updates or htlcs
also add inject_fees debug command
2019-08-20 09:03:10 +02:00
Janus
02eca03486 lnhtlc: cleanup and save settled htlcs 2019-08-20 09:03:10 +02:00
Janus
6f5209ef85 lnhtlc: test that sent amount is received 2019-08-20 09:03:10 +02:00
Janus
261fefb6f3 lnwatcher rebased 2019-08-20 09:03:10 +02:00
Janus
3eabd70df5 lightning: post aiorpcx rebase fixup 2019-08-20 09:03:10 +02:00
SomberNight
7edba63341 remove our closed channels from channeldb. note some FIXMEs
payment were attempting to use the closed channels.
2019-08-20 09:03:10 +02:00
SomberNight
08d20ce187 lnbase: fix payments 2019-08-20 09:03:10 +02:00
SomberNight
1b030fca78 rebase follow-up: use namedtuples from master in new code (TxOutput, TxMinedStatus) 2019-08-20 09:03:10 +02:00
SomberNight
bab9f163f7 decode onion errors to failure message type 2019-08-20 09:03:10 +02:00
SomberNight
b85aea1541 qt: pay_lightning_invoice - attempt paying multiple times in case of failure 2019-08-20 09:03:10 +02:00
SomberNight
4d1785799b lnbase.pay: test if htlc can be added 2019-08-20 09:03:10 +02:00
SomberNight
9827cda9b1 qt send tab: hide fee fields when paying with lightning 2019-08-20 09:03:10 +02:00
SomberNight
2b4a436572 qt open channel dialog: allow pasting invoices to open a channel 2019-08-20 09:03:10 +02:00
SomberNight
cd893de837 lnrouter: use 'disable' flags from channel updates in path finding 2019-08-20 09:03:10 +02:00
SomberNight
15a6a83107 ln onchain fees: use 2 block ETAs with 150 s/b fallback 2019-08-20 09:03:10 +02:00
SomberNight
f3e5ba6ac1 more reliable peer and channel re-establishing 2019-08-20 09:03:10 +02:00
SomberNight
362a3a5a44 lnworker: fix confusion re what is in self.peers 2019-08-20 09:03:10 +02:00
SomberNight
9f79b7df63 don't connect to same peer multiple times 2019-08-20 09:03:10 +02:00
SomberNight
0b0bc89083 fix tests 2019-08-20 09:03:10 +02:00
SomberNight
c02cc9bb3b persist recent peers. implement dns seed bootstrapping.
dns seeds are currently disabled though, as they always seem to return mainnet nodes.
2019-08-20 09:03:10 +02:00
SomberNight
bc06ded4b9 persist nodes in channel_db on disk 2019-08-20 09:03:10 +02:00
SomberNight
5a05a92b3d move bolt-04 onion stuff to its own module 2019-08-20 09:03:10 +02:00
Janus
9247da5203 ln: share more code with control path for failed htlc, verify ctx'es 2019-08-20 09:03:10 +02:00
Janus
96a16adf30 ln: fix forwarded payment fees by removing separation between fee and amount in htlc objects 2019-08-20 09:03:10 +02:00
Janus
318d25e676 ln: verify received commitment transactions during payment 2019-08-20 09:03:10 +02:00
Janus
3c06b3cee1 ln: use START_INDEX instead of 2**48-1 2019-08-20 09:03:10 +02:00
Janus
a841fa3602 ln: save htlc signatures 2019-08-20 09:03:10 +02:00
Janus
9c442586b2 ln: avoid dust sends breaking channel, avoid inline functions 2019-08-20 09:03:10 +02:00
SomberNight
d0798c336f channeldb: allow injecting trusted edges 2019-08-20 09:03:10 +02:00
Janus
7f0568d83a special case receiving payment after fee update, fee update injector 2019-08-20 09:03:10 +02:00
SomberNight
a5b44d25b0 persist channel db on disk. verify channel gossip sigs. 2019-08-20 09:03:10 +02:00
SomberNight
c1d1826014 start using electrum protocol 1.4 2019-08-20 09:03:10 +02:00
Janus
935f11522d lightning.json with gossip_queries 2019-08-20 09:03:10 +02:00
SomberNight
8ffeb79d01 constants.py: remove duplicate Simnet declaration 2019-08-20 09:03:10 +02:00
Janus
9853cc6f31 ln: do not use mSAT accuracy for commitment fees 2019-08-20 09:03:10 +02:00
Janus
66e7b4d250 ln: fundee must commit to fee first 2019-08-20 09:03:10 +02:00
SomberNight
fb00e29f1c bolt-08 handshake must use ephemeral key 2019-08-20 09:03:10 +02:00
SomberNight
fca5c9379f avoid crash if don't have peer for open channel 2019-08-20 09:03:10 +02:00
Janus
74b180a4e6 use correct dust limit for their to_local/to_remote outputs 2019-08-20 09:03:10 +02:00
Janus
478e484c54 ln: initialize genesis with object, fix method name typo, use depth_target_to_fee 2019-08-20 09:03:10 +02:00
ThomasV
bb4aa1e135 lnwatcher: pass address instead of wallet reference 2019-08-20 09:03:10 +02:00
ThomasV
bc28edf178 encapsulate funding_address_for_channel 2019-08-20 09:03:10 +02:00
SomberNight
8e63471d69 allow paying invoice without amount. min feerate 253 sat/kw. 2019-08-20 09:03:10 +02:00
Janus
ba74844b2e lnbase: fee handling: add todo since depth_to_fee is inappropriate 2019-08-20 09:03:10 +02:00
ThomasV
21ee6f6965 asyncio: do not set event loop from wallet 2019-08-20 09:03:10 +02:00
Janus
2fd5f8613a ln: fix race in on_network_update 2019-08-20 09:03:10 +02:00
Janus
d5cb21143f ln: send update_fee on fee change, handle nodes without data_protect 2019-08-20 09:03:10 +02:00
Janus
12a02a8a1e lnrouter: ignore duplicate channel announcement 2019-08-20 09:03:10 +02:00
ThomasV
9897e41e68 remove unused method 2019-08-20 09:03:10 +02:00
SomberNight
21e907a4e3 ChannelDB: print_graph 2019-08-20 09:03:10 +02:00
SomberNight
135951a13f qt channels list: update channel when detecting close 2019-08-20 09:03:10 +02:00
SomberNight
f2242868fa qt/channels list: show error in dialog 2019-08-20 09:03:10 +02:00
SomberNight
66817d41f9 lnwatcher improvements
- only try sweeping csv-locked to_local if past timelock
- check if outputs are already spent
- no need to keep watching channels for which all outputs are spent and mined deep
2019-08-20 09:03:10 +02:00
Janus
d740475e7a move channel_state into HTLCStateMachine 2019-08-20 09:03:10 +02:00
ThomasV
3caccbebcd follow-up a7e5b9421c014fc0be72696837cb9d77519c5e03 2019-08-20 09:03:10 +02:00
ThomasV
89dfd1cc2c lightning: improve request tab layout 2019-08-20 09:03:10 +02:00
ThomasV
f2b40c69fc do not set background in frozen_style, it does not look good with dark theme 2019-08-20 09:03:10 +02:00
ThomasV
b5f0209a56 fix crash in lnworker main_loop 2019-08-20 09:03:10 +02:00
ThomasV
18bc5aa27b lightning: improve receive in Qt GUI 2019-08-20 09:03:10 +02:00
ThomasV
31b67c422b add peer suggestion to open channel dialog. move add_peer code back to lnworker constructor 2019-08-20 09:03:10 +02:00
ThomasV
2ee41975f9 add lnworker.main_loop to network.futures so it gets cancelled on exit. fix aiosafe verbosity. 2019-08-20 09:03:10 +02:00
ThomasV
44d8c8f995 diagnostic_name: convert to str 2019-08-20 09:03:10 +02:00
ThomasV
8f779f504f LNWorker: connect to multiple peers.
save exceptions in aiosafe.
enable adding peer in GUI.
2019-08-20 09:03:10 +02:00
Janus
35adc3231b lightning: fixup after rebasing on restructured master 2019-08-20 09:03:10 +02:00
ThomasV
1db7a8334a Refresh LN status in GUI using network callback. 2019-08-20 09:03:10 +02:00
Janus
9145d61797 lnhtlc: remove unnecessary double application of pending feerate 2019-08-20 09:03:10 +02:00
Janus
cc48e14618 ln: enable receiving dust htlcs 2019-08-20 09:03:10 +02:00
SomberNight
18627ecd1a lnwatcher: naive code to sweep to_local from our ctx (will not wait for timelock yet) 2019-08-20 09:03:10 +02:00
Janus
55a7e4cec1 ln: use pending_local_commit while closing (won't be revoked) 2019-08-20 09:03:10 +02:00
Janus
ed62a21547 ln: raise our dust/reserve to 546 to be compatible with c-lightning 2019-08-20 09:03:10 +02:00
Janus
b26e028d9b ln: test fix: our commit fee is implicit from outputs 2019-08-20 09:03:10 +02:00
Janus
2dd1cb86fa lnbase: do not revoke more than once if we have missed reading commitments 2019-08-20 09:03:10 +02:00
SomberNight
63d2c3aaf4 lnwatcher: sweep to_remote and to_local outputs if they close 2019-08-20 09:03:10 +02:00
Janus
8573dd3b6a ln: revoke before sending bare ctx in pay(), remove subtraction of trimmed amt from fee 2019-08-20 09:03:10 +02:00
Janus
605d6ff5ca ln: add two trimming tests, avoid negative numbers in htlc trim decision 2019-08-20 09:03:10 +02:00
Janus
03c2b954d9 lnhtlc: fee update upgrade and passes ReciverCommits and SenderCommits tests, fix NameErrors in lnbase 2019-08-20 09:03:10 +02:00
Janus
d95d6fcae9 lnaddr: remove remnants of lightning_payencode directory 2019-08-20 09:03:10 +02:00
Janus
ea3d8cb157 lnaddr: fix imports 2019-08-20 09:03:10 +02:00
ThomasV
722b4c5029 move comment 2019-08-20 09:03:10 +02:00
ThomasV
8346e358b2 move lnaddr.py to lib 2019-08-20 09:03:10 +02:00
Janus
4515c859c4 ln: avoid code duplication 2019-08-20 09:03:10 +02:00
Janus
fe973a5137 ln: avoid recursive dependencies, make new lnutil 2019-08-20 09:03:10 +02:00
Janus
7a3551b5df ln: merge OpenChannel and HTLCStateMachine 2019-08-20 09:03:10 +02:00
Janus
42a56df996 ln: shortcut some OpenChannel fields to traversing too much 2019-08-20 09:03:10 +02:00
Janus
77e9abc655 ln: store HTLCStateMachine in lnworker.channels 2019-08-20 09:03:10 +02:00
SomberNight
0d4593eebf improve Qt Receive tab for LN payment requests 2019-08-20 09:03:10 +02:00
ThomasV
c7e47b74a9 Separate open_channel dialog. In open_channel_coroutine, use host and port from channel announcements 2019-08-20 09:03:10 +02:00
Janus
dbdabcfc5d ln: use new non-classmethod add_signature_to_txin 2019-08-20 09:03:10 +02:00
Janus
b3dad9480c ln: trim dust htlc outputs 2019-08-20 09:03:10 +02:00
ThomasV
8fe70fc0eb do not set channel state in close_channel; the watcher should do it 2019-08-20 09:03:10 +02:00
ThomasV
61983c222a lightning: single shared instance of Watcher, ChannelDB and PathFinder 2019-08-20 09:03:10 +02:00
ThomasV
3fd3b2a74d disable lightning on mainnet 2019-08-20 09:03:10 +02:00
Janus
e7089c1458 ln: improve lnhtlc, passes test 2019-08-20 09:03:10 +02:00
Janus
56d5936661 lnhtlc: use current_per_commitment_point, current_commitment_signature 2019-08-20 09:03:10 +02:00
ThomasV
0ccafb547c fix reestablish_channel 2019-08-20 09:03:10 +02:00
ThomasV
df960700c9 reestablish channels in network callback 2019-08-20 09:03:10 +02:00
ThomasV
322acd93d9 channel watcher class 2019-08-20 09:03:10 +02:00
Janus
4eb370d2e2 ln: add was_announced in test_lnhtlc 2019-08-20 09:03:10 +02:00
Janus
7f206d6e4c ln: close channels 2019-08-20 09:03:10 +02:00
Janus
83c60441cf ln: don't corrupt channels storage when multiple funding_locked are received 2019-08-20 09:03:10 +02:00
Janus
3f73332817 ln: don't break channel when failing htlc 2019-08-20 09:03:10 +02:00
Janus
6f88c55f17 ln: announcement reliability fixes for qt, remove asserts forbidding unbalanced channels 2019-08-20 09:03:10 +02:00
Janus
e9fec66eb4 ln: begin handling htlc failures 2019-08-20 09:03:10 +02:00
SomberNight
6d8cae11dd add minor comment for RouteEdge as clarification 2019-08-20 09:03:10 +02:00
SomberNight
36519a535b LNPathFinder: cltv delta of first edge in a path should be ignored 2019-08-20 09:03:10 +02:00
Janus
a106760469 ln: channel announcements 2019-08-20 09:03:10 +02:00
Janus
275f1e6cbc ln: lnpay: revoke until we get a commitment tx without htlcs 2019-08-20 09:03:10 +02:00
Janus
f169bff89e ln: fix reestablishing channel with no mined funding tx 2019-08-20 09:03:10 +02:00
Janus
3ea6415dc7 ln: fix repeated payments 2019-08-20 09:03:10 +02:00
Janus
399fe08047 ln: avoid code duplication 2019-08-20 09:03:10 +02:00
Janus
d1769472bd ln: save remote's secrets in RevocationStore, not our secrets. call lnhtlc.receive_revocation 2019-08-20 09:03:10 +02:00
Janus
6c8bd2559b lnbase/lnhtlc: use lnhtlc more instead of manually constructing tx'es 2019-08-20 09:03:10 +02:00
Janus
9010ea7e6e lnbase: use sign_next_commitment for initial remote_ctx 2019-08-20 09:03:10 +02:00
Janus
434ce49451 lnbase: use lnhtlc when verifying our initial commitment tx 2019-08-20 09:03:10 +02:00
Janus
8eeaac8dda lnbase: use broadcast_transaction instead of broadcast (follow up e57e55aad) 2019-08-20 09:03:10 +02:00
Janus
3270ac039c test_lnbase: use new Peer API (with lnworker) 2019-08-20 09:03:10 +02:00
Janus
bd0f659f26 ln: remove unneeded forwarding htlc features, check commitment sig using lnhtlc while receiving 2019-08-20 09:03:10 +02:00
Janus
4d25933898 ln: integrate lnhtlc in lnbase, fix multiple lnhtlc bugs 2019-08-20 09:03:10 +02:00
Janus
5ed6f79a33 ln: request_initial_sync, increase our max_htlc_value, fix receiving payment 2019-08-20 09:03:10 +02:00
ThomasV
6e71340e52 do not block GUI with open_channel 2019-08-20 09:03:10 +02:00
ThomasV
40fcf58fec lightning: display remote balance in gui 2019-08-20 09:03:10 +02:00
ThomasV
2b9be294a0 lnbase: mark_open on startup 2019-08-20 09:03:10 +02:00
ThomasV
f1d067f446 revert the introduction of add_invoice_coroutine in a612c2b09 2019-08-20 09:03:10 +02:00
ThomasV
aeb58dbd66 do not pass channel list to update_rows signal, as it is sent to all windows 2019-08-20 09:03:10 +02:00
SomberNight
6f246b90bf wait for peer.initialized in channel_establishment_flow 2019-08-20 09:03:10 +02:00
ThomasV
11c6fce7bf follow up 0b3a882e7d57c8a42be48c491a46dc814eab6acb 2019-08-20 09:03:10 +02:00
ThomasV
2ae4b1862d simplify funding_locked
expose lnworker in peer
update channel_db when channels are open
2019-08-20 09:03:10 +02:00
ThomasV
21c883bd0b Display channel status in the GUI.
Do not convert channel_id to integer; there is no reason to do that.
2019-08-20 09:03:10 +02:00
ThomasV
bf6d28e1f0 integrate channels_list with existing framework 2019-08-20 09:03:10 +02:00
ThomasV
4fe912f4b3 qt: fix unit of lnaddr.amount 2019-08-20 09:03:10 +02:00
ThomasV
6263b472d9 follow-up a612c2b0983ab4c6798156aebf1cd550fb3e0447 2019-08-20 09:03:10 +02:00
Janus
497706afbf ln: htlc state machine (not used yet) 2019-08-20 09:03:10 +02:00
Janus
85e18be7d0 ln: save channels in dict, warn on invoice exceeding max_htlc amount 2019-08-20 09:03:10 +02:00
ThomasV
34d5f1b2e3 lightning: connect send button 2019-08-20 09:03:10 +02:00
ThomasV
12d3877873 lightning GUI: use existing receive and send tabs with lightning invoices 2019-08-20 09:03:10 +02:00
Janus
7d2a6d83d5 ln: don't make invoice if peer can't possibly pay, append _sat to sat
parameters to avoid confusion
2019-08-20 09:03:10 +02:00
ThomasV
b74d4261af lnworker: generate and save private key 2019-08-20 09:03:10 +02:00
ThomasV
af4f0b6daf lnworker: separate invoice creation from payment flow 2019-08-20 09:03:10 +02:00
Janus
ae3971259d ln: restore channels correctly after restart
* save funding_locked_received: if a node already sent us
funding_locked, save it to avoid superfluous messages

* use Queues instead of Futures: this ensure that we don't error if we
receive two messages of the same type, and in avoids having to delete
futures in finally blocks. A queue monitor could be added to detect
queue elements that are not popped.

* request initial routing sync: since we don't store the graph yet, it
is better to request the graph from the Peer so that we can route

* channel_state cleanup: now each channel should have a state, which is
initialized to OPENING and only marked OPEN once we have verified that
the funding_tx has been mined
2019-08-20 09:03:10 +02:00
ThomasV
aafbe74a28 fix channel_reestablish 2019-08-20 09:03:10 +02:00
ThomasV
1f6646fa25 lnbase: fix read_message 2019-08-20 09:03:10 +02:00
Janus
6a8e5d5954 ln: restore functionality 2019-08-20 09:03:09 +02:00
Janus
4268be9093 ln: save remote node_id in channel 2019-08-20 09:03:09 +02:00
SomberNight
8ba63380b4 split lnrouter from lnbase 2019-08-20 09:03:09 +02:00
SomberNight
f6763b6084 remove function H256 2019-08-20 09:03:09 +02:00
ThomasV
762dea6593 fix amount in open_channel, add listchannels command 2019-08-20 09:03:09 +02:00
ThomasV
b71f020fc9 move on_funding_locked to lnworker 2019-08-20 09:03:09 +02:00
ThomasV
0552c61b66 lightning: add payment methods to lnworker 2019-08-20 09:03:09 +02:00
ThomasV
c621ae8f6e lightning: move lnworker code to its own module 2019-08-20 09:03:09 +02:00
ThomasV
f66377604d fix lnaddr.py following rebase 2019-08-20 09:03:09 +02:00
ThomasV
5666188e9e update lnbase after crypto refactoring 2019-08-20 09:03:09 +02:00
Janus
1d8c771440 lnbase: remove lnbase stub 2019-08-20 09:03:09 +02:00
ThomasV
8abd072c89 lnbase: pass password to mktx 2019-08-20 09:03:09 +02:00
ThomasV
5a819611c8 qt: fix password passed to open_channel, cleanup 2019-08-20 09:03:09 +02:00
Janus
6ac15962dc lnbase: mSAT hygiene, multiple multi-hop payments can be received 2019-08-20 09:03:09 +02:00
Janus
10e8a90224 kivy: port lightning ui to lnbase 2019-08-20 09:03:09 +02:00
Janus
18963405ee lightning: remove hub based approach, port qt gui to lnbase 2019-08-20 09:03:09 +02:00
Janus
4fdf1b9b84 lnbase: use small buffer when reading, support new_channel without payment in online test, send channel_reserve_satoshis 2019-08-20 09:03:09 +02:00
Janus
5d375de30e lnbase: use correct cltv_expiry calculation (use invoice) 2019-08-20 09:03:09 +02:00
Janus
eeb027babc lnbase: fix multi-hop payments 2019-08-20 09:03:09 +02:00
Janus
001ab4e3bc lnbase: fix onion-hop payload construction again (cltv currently broken) 2019-08-20 09:03:09 +02:00
Janus
1d267f1226 lnbase: fix multi-hop fees, initial handling of received update_add_htlc during payment 2019-08-20 09:03:09 +02:00
Janus
f6995b99d9 lnbase: calculate cltv_expiry for onion_packet correctly 2019-08-20 09:03:09 +02:00
Janus
6c67ad24f3 lnbase: try multi-hop onion package, type safety 2019-08-20 09:03:09 +02:00
SomberNight
43eb33327e PathFinder: change path element semantics from "from node, take edge" to "to get to node, use edge" 2019-08-20 09:03:09 +02:00
SomberNight
50cc603d91 create route from path, that includes extra info needed for routing 2019-08-20 09:03:09 +02:00
SomberNight
5b1da26041 bolt-04: decryption of errors 2019-08-20 09:03:09 +02:00
Janus
5da3820a28 lnbase online test: use random node key when making new channel, save node key, multiple actions per invocation 2019-08-20 09:03:09 +02:00
Janus
b81fb44952 lnbase: fix pay(), save htlc_id's, generate onion packet correctly 2019-08-20 09:03:09 +02:00
Janus
34da1349e0 lnbase/online_test: save short_channel_id to wallet and build onion packet with it 2019-08-20 09:03:09 +02:00
SomberNight
7ba3f2d54d calc short_channel_id after funding locked 2019-08-20 09:03:09 +02:00
Janus
6bf2714e33 lnbase: initial 'payment to remote' attempt 2019-08-20 09:03:09 +02:00
Janus
928eb886c5 lnbase: formatting, remove imports 2019-08-20 09:03:09 +02:00
Janus
2e23ecb3ca lnbase: verify commitment tx'es again 2019-08-20 09:03:09 +02:00
Janus
73a17c93ee lnbase: infinite amount of incoming payments 2019-08-20 09:03:09 +02:00
Janus
6173c2d7a7 lnbase: two payments working, temporarily disable sig check 2019-08-20 09:03:09 +02:00
Janus
ee87920573 lnbase: store remote revocation store, don't store all remote revocation points, verify ctn numbers in reestablish 2019-08-20 09:03:09 +02:00
Janus
3a20c8ce00 lnbase: add RevocationStore test, remove unnecessary lnd helper functions 2019-08-20 09:03:09 +02:00
Janus
cf82150aab lnbase: compact commitment secret storage 2019-08-20 09:03:09 +02:00
Janus
c5fb090e5c lnbase: no negative commitment number nonsense 2019-08-20 09:03:09 +02:00
Janus
2338d18ab8 lnbase: move channel commitment number increment to function 2019-08-20 09:03:09 +02:00
Janus
2a594e9d0e lnbase: receive repeated payments 2019-08-20 09:03:09 +02:00
Janus
913176b4b1 tests: don't use default lightning_peers in online test 2019-08-20 09:03:09 +02:00
Janus
d9d2989a6a lnbase: channel reestablishment working 2019-08-20 09:03:09 +02:00
SomberNight
a58a345dc3 bolt-04: implement processing of onion packets 2019-08-20 09:03:09 +02:00
SomberNight
053c571d74 minor clean-up of prev. util.xor_bytes 2019-08-20 09:03:09 +02:00
SomberNight
47b1bed539 implement bolt-04 onion packet construction 2019-08-20 09:03:09 +02:00
Janus
60b77f6a00 lnbase: save channel details in wallet, enable running online test with reestablishment_mode 2019-08-20 09:03:09 +02:00
Janus
d3f8fe923c lnbase: move waiting for funding_locked to new function, make function for signing and sig conversion 2019-08-20 09:03:09 +02:00
Janus
d96b80ad1f lnbase: make function for building htlc_tx depending on if it is for_us/we_receive 2019-08-20 09:03:09 +02:00
Janus
309aca69b8 lnbase: verify their htlc signature 2019-08-20 09:03:09 +02:00
ThomasV
6b79052bc9 lnbase: standardize to_bytes calls 2019-08-20 09:03:09 +02:00
Janus
fa80fd4bd5 lnbase: fix custom local to_self_delay, use node privkey derived from timestamp in online test 2019-08-20 09:03:09 +02:00
Janus
517e19ebab test_lnbase_online: pass password=None to channel_establishment_flow 2019-08-20 09:03:09 +02:00
Janus
1363dfb522 lnbase: avoid copying variables, insert newlines 2019-08-20 09:03:09 +02:00
ThomasV
96544b8b58 lnbase: derive keys from wallet keystore 2019-08-20 09:03:09 +02:00
Janus
bdec72dd4b lnbase: avoid local variables, remote useless comments, name basepoints as such 2019-08-20 09:03:09 +02:00
Janus
e1f7eb6cb3 lnbase: set new field in Transaction instead of returning a tuple in make_commitment 2019-08-20 09:03:09 +02:00
Janus
194a2bba16 lnbase: set to_self_delay back to 144, defer cltv_expiry problem 2019-08-20 09:03:09 +02:00
Janus
19d8a13232 lnbase: use correct delay 2019-08-20 09:03:09 +02:00
Janus
20f0464009 lnbase: avoid code duplication, return htlc outpoint dict in make_commitment 2019-08-20 09:03:09 +02:00
Janus
5f38019420 lnbase: simplify commitment transaction building with open channel 2019-08-20 09:03:09 +02:00
Janus
39dcc24133 lnbase: organize channel data 2019-08-20 09:03:09 +02:00
Janus
c2bbc1ec60 lnbase: allow passing KeypairGenerator to channel_establishment_flow, fix derive_privkey 2019-08-20 09:03:09 +02:00
Janus
9f8d6625ec lnbase: receiving invoice payment works 2019-08-20 09:03:09 +02:00
Janus
6d87599964 lnbase: commitment_signed, revoke_and_ack now accepted without errors 2019-08-20 09:03:09 +02:00
SomberNight
b3da13420b bitcoin.py: SCRIPT-related clean-up. transaction.py: construct_witness 2019-08-20 09:03:09 +02:00
Janus
75e7b3af49 lnbase: fix their new commitment transaction (htlc tx construction still incorrect) 2019-08-20 09:03:09 +02:00
ThomasV
71eacb4eab lnbase: fix bug in message parsing 2019-08-20 09:03:09 +02:00
ThomasV
7176b0834c follow up b5eb7dd7683f24f03c80ab8f612658b5f3966eb1 2019-08-20 09:03:09 +02:00
Janus
e9e0d60432 lnbase: attempt at making htlc_signature to send (currently remote fails due to wrong num_htlcs in commitment_signed) 2019-08-20 09:03:09 +02:00
ThomasV
c7e3f7e4e4 simplification 2019-08-20 09:03:09 +02:00
Janus
f32149e609 lnbase: add TODO explaining how to verify htlc_signature given to us 2019-08-20 09:03:09 +02:00
Janus
e98f23c4ed lnbase: verification of new local commitment working 2019-08-20 09:03:09 +02:00
Janus
e264a21c64 lnbase: derive next keys when making updated local commitment transaction 2019-08-20 09:03:09 +02:00
Janus
3c34628ffb lnbase: try to receive payment, work on commitment tx with htlcs 2019-08-20 09:03:09 +02:00
Janus
0f552422a6 lnbase: handle commitment transaction update (receive funds, not working yet) 2019-08-20 09:03:09 +02:00
Janus
1ffaed718c simnet/testnet support in bolt11, set max-htlc-value-in-flight 2019-08-20 09:03:09 +02:00
SomberNight
fd7469745e transaction.py: sign_txin. allow override for get_preimage_script.
test_commitment_tx_with_all_five_HTLCs_untrimmed_minimum_feerate now passes
2019-08-20 09:03:09 +02:00
ThomasV
4d41299f1c redundant: you subscribed only to 'updated' 2019-08-20 09:03:09 +02:00
ThomasV
21be384603 lnbase: verify remote signature 2019-08-20 09:03:09 +02:00
SomberNight
e9bad2e862 channel_establishment_flow: use get_per_commitment_secret_from_seed 2019-08-20 09:03:09 +02:00
Janus
edf6fe7a94 lnbase: improve htlc_tx generation (only localsig wrong) 2019-08-20 09:03:09 +02:00
SomberNight
3e899caaf4 get_per_commitment_secret_from_seed: small clean-up 2019-08-20 09:03:09 +02:00
Janus
b523085fa3 fix derive_secret 2019-08-20 09:03:09 +02:00
SomberNight
576a74a48a get_per_commitment_secret_from_seed (not working yet) 2019-08-20 09:03:09 +02:00
Janus
f0e19ffdfd lnbase: avoid race while waiting for funding_locked, wait for un-reversed hash 2019-08-20 09:03:09 +02:00
Janus
4d3c34e04e complete bolt11 port to ecdsa instead of secp256k1 2019-08-20 09:03:09 +02:00
SomberNight
4aa9d7ea0d channel_establishment_flow: wait for confirmations of funding txn 2019-08-20 09:03:09 +02:00
ThomasV
a4809245b3 add processing flow for funding_locked 2019-08-20 09:03:09 +02:00
ThomasV
2d305bd218 lnbase: fix parameters to make_commitment in htlc test 2019-08-20 09:03:09 +02:00
SomberNight
22f6deacb8 transaction.py: shortcut witness/scriptSig serialisation 2019-08-20 09:03:09 +02:00
Janus
d055507003 lightning channels list: add mock server for testing 2019-08-20 09:03:09 +02:00
Janus
fc822ab927 lnbase: add some comments 2019-08-20 09:03:09 +02:00
SomberNight
f174609158 constants.py: Simnet inherits from Testnet 2019-08-20 09:03:09 +02:00
Janus
8468fc8f68 use same servers for simnet as for regtest 2019-08-20 09:03:09 +02:00
Janus
b22bdae951 lightning-hub: update rpc stubs, do not ignore them in gitignore 2019-08-20 09:03:09 +02:00
ThomasV
5f4328fb3a lnbase: fix initial commitment transaction 2019-08-20 09:03:09 +02:00
SomberNight
815079efe0 refactor storage of channels, path finding 2019-08-20 09:03:09 +02:00
Janus
5b1a5e8786 avoid duplicating bech32 module 2019-08-20 09:03:09 +02:00
Janus
f278833c40 lnbase: more work on make_htlc_tx 2019-08-20 09:03:09 +02:00
Janus
d7244f6708 lnbase: make_htlc_tx 2019-08-20 09:03:09 +02:00
ThomasV
ab7a854f9a fix: use remote_per_commitment_point 2019-08-20 09:03:09 +02:00
ThomasV
8f00bdb0b0 lnbase: derive blinded pubkey 2019-08-20 09:03:09 +02:00
ThomasV
d4377cc073 lnbase: fix variable name 2019-08-20 09:03:09 +02:00
ThomasV
6c4c2267f8 lnbase: add privkey derivation 2019-08-20 09:03:09 +02:00
ThomasV
6d703db971 add test for key derivation 2019-08-20 09:03:09 +02:00
ThomasV
aa8700d0b3 lnbase: key derivation (WIP) 2019-08-20 09:03:09 +02:00
Janus
4b8f279d50 lnbase: test signing of first htlc test case 2019-08-20 09:03:09 +02:00
Janus
fa86dda983 lnbase: make_received_htlc 2019-08-20 09:03:09 +02:00
ThomasV
6ba846bff0 fix hash in make_offered_htlc 2019-08-20 09:03:09 +02:00
ThomasV
2c717194b2 cleanup lnbase tests 2019-08-20 09:03:09 +02:00
Janus
6634027a03 lightning-hub: remove path hack, use relative imports 2019-08-20 09:03:09 +02:00
SomberNight
f8f365f1ea naive route finding 2019-08-20 09:03:09 +02:00
Janus
a23aac76d3 lnbase: offered htlc script construction 2019-08-20 09:03:09 +02:00
ThomasV
1bc6958c83 use acceptable variable names 2019-08-20 09:03:09 +02:00
ThomasV
60d6080fe5 lightning: separate testing from main code 2019-08-20 09:03:09 +02:00
ThomasV
1c3e9b0cf8 lightning: store network view 2019-08-20 09:03:09 +02:00
ThomasV
e4b188c714 lnbase: parse ipv6, fix transport bug 2019-08-20 09:03:09 +02:00
ThomasV
d212c90ed9 lnbase: fix read_message, reduce verbosity 2019-08-20 09:03:09 +02:00
ThomasV
8e7df0be71 lnbase: implement key rotation, request initial sync in localfeatures 2019-08-20 09:03:09 +02:00
ThomasV
969708316c lnbase: verify signature in node_announcement 2019-08-20 09:03:09 +02:00
SomberNight
b662a9d87b bitcoin.py: implement add_number_to_script. fix CSV arg in make_commitment. 2019-08-20 09:03:09 +02:00
ThomasV
57b63cf490 lnbase: fix test 2019-08-20 09:03:09 +02:00
SomberNight
98f46dbaf5 implement script_num_to_hex. fix encoding of argument for CSV in make_commitment 2019-08-20 09:03:09 +02:00
Janus
caadc5a5bb lightning_channels_list: use signals to avoid segfault 2019-08-20 09:03:09 +02:00
SomberNight
aaa67adb5a fixes for make_commitment, but still incorrect destination address (csv arg?) 2019-08-20 09:03:09 +02:00
ThomasV
eca8d13e37 lnbase: fix tx amounts 2019-08-20 09:03:09 +02:00
Janus
a9edd170c7 lightning-hub: include ln relative to current directory 2019-08-20 09:03:09 +02:00
Janus
3fb6951d10 lnbase_test: add first commitment tx with 5 htlcs test 2019-08-20 09:03:09 +02:00
ThomasV
316f9a3954 lnbase: fix locktime and nsequence 2019-08-20 09:03:09 +02:00
Janus
814146f099 lnbase_test: insert remote_signature and compare fields independently 2019-08-20 09:03:09 +02:00
ThomasV
7b6f64a402 lnbase: create unit test for commitment tx 2019-08-20 09:03:09 +02:00
ThomasV
52ae63990f lnbase: fix signature index 2019-08-20 09:03:09 +02:00
ThomasV
ba99795131 lnbase: initial commitment transaction 2019-08-20 09:03:09 +02:00
ThomasV
451d52281a lnbase: fix funding address, funding_output_index 2019-08-20 09:03:09 +02:00
ThomasV
5a4ab74c77 follow up 1aac9e59ed957898fceef99b29b9cc17d7843569 2019-08-20 09:03:09 +02:00
ThomasV
dd73a4596e lnbase: communication privkey belongs to peer 2019-08-20 09:03:09 +02:00
Janus
668c3887d6 lnbase: more parts of channel establishment 2019-08-20 09:03:09 +02:00
Janus
2353415445 lnbase: remove unnecessary try/except 2019-08-20 09:03:09 +02:00
Janus
e7b92b8184 lnbase: lnbase_test must use threadsafe task submission 2019-08-20 09:03:09 +02:00
ThomasV
9781201fa0 lnbase: decorator that handles exceptions 2019-08-20 09:03:09 +02:00
Janus
483ec42156 lnbase: fix shutdown when lnbase has exception in main_loop 2019-08-20 09:03:09 +02:00
Janus
e906e6e90a lnbase: print exceptions from main_loop 2019-08-20 09:03:09 +02:00
Janus
b9ca972445 lnbase: initialize loop variable in main 2019-08-20 09:03:09 +02:00
Janus
9617447a0f lnbase: add lnbase_test 2019-08-20 09:03:09 +02:00
ThomasV
70dd4d1235 lnbase: expose wallet object in LNWorker 2019-08-20 09:03:09 +02:00
Janus
e1824233b5 lnbase: merge initialize and main_loop 2019-08-20 09:03:09 +02:00
Janus
7bd3cbf567 lnbase: handle error during channel establishment 2019-08-20 09:03:09 +02:00
Janus
78119f9733 lnbase: channel establishment flow, avoid using Wallet instance 2019-08-20 09:03:09 +02:00
ThomasV
1f5852bd15 lnbase: use relative imports 2019-08-20 09:03:09 +02:00
Janus
784b06f1b9 lnbase: support simnet/testnet, create accepted open_channel message 2019-08-20 09:03:09 +02:00
Janus
ab2e03fcb5 lnbase: use valid pubkeys in open_channel 2019-08-20 09:03:09 +02:00
Janus
304c4b0222 lnbase: try sending open_channel 2019-08-20 09:03:09 +02:00
ThomasV
d8eedf514d lnbase: add draft handlers 2019-08-20 09:03:09 +02:00
Janus
fc2864cb63 lnbase: avoid reimplementing int.to_bytes 2019-08-20 09:03:09 +02:00
Janus
f2066c4629 lnbase: avoid reimplementing int.from_bytes 2019-08-20 09:03:09 +02:00
Janus
94b87ac7d1 lnbase: do not catch all exceptions, tolerate calculations with variables from kwargs 2019-08-20 09:03:09 +02:00
ThomasV
3795770b37 integrate lnbase with network 2019-08-20 09:03:09 +02:00
ThomasV
0fac793735 lnbase: process ping messages 2019-08-20 09:03:09 +02:00
ThomasV
98707a3624 lnbase: create main loop 2019-08-20 09:03:09 +02:00
ThomasV
bcb268d554 lnbase: save buffer for next read 2019-08-20 09:03:09 +02:00
ThomasV
7421bffaa2 lnbase: Peer class 2019-08-20 09:03:09 +02:00
ThomasV
692bc148bb lnbase: fix json loading and indentation 2019-08-20 09:03:09 +02:00
Janus
c69f812f13 lightning: do not list python files as resources, use lightning spec generated serialization 2019-08-20 09:03:09 +02:00
ThomasV
cbf8d4c781 lightning network base 2019-08-20 09:03:09 +02:00
Janus
094b939a24 lightning: qt channel dialog, fix for shutdown when lightning disabled 2019-08-20 09:03:09 +02:00
Janus
5e1412a839 lightning: channel details popup 2019-08-20 09:03:09 +02:00
Janus
ec89b496bf lightning: do not catch BaseException unnecessarily, fix clearSubscribers, detect passworded wallet correctly 2019-08-20 09:03:09 +02:00
ThomasV
fe1a1b27c6 simplify parameters, add lndhost to config 2019-08-20 09:03:09 +02:00
Janus
0abfcd2b6a lightning: add --simnet and --lightning switches 2019-08-20 09:03:09 +02:00
Janus
ae7bce3385 lightning: paste sample using clipboard 2019-08-20 09:03:09 +02:00
Janus
6aebc0fd5d kivy: fix channel list error handling, close functionality for inactive channels 2019-08-20 09:03:09 +02:00
Janus
1a05838ef2 lightning: assert result type, add invoice qr dialog 2019-08-20 09:03:09 +02:00
Janus
afa5797099 lightning: kivy: open channel button in invoice 2019-08-20 09:03:09 +02:00
Janus
1ab03e8b2a lightning: fix kivy channel close 2019-08-20 09:03:09 +02:00
Janus
d19e8e7f9b lightning: python3.5 compat 2019-08-20 09:03:09 +02:00
Janus
ffe6342882 lightning: fix channels dialog 2019-08-20 09:03:09 +02:00
Janus
13527987bc lightning: add missing import, set console to none initially 2019-08-20 09:03:09 +02:00
Janus
76bd120bdf lightning: do not require lock for broadcast tx, it is thread-safe 2019-08-20 09:03:09 +02:00
Janus
311c53ef1d lightning: save key derivation point 2019-08-20 09:03:09 +02:00
Janus
6ab8badb61 lightning: separate thread for publish transaction 2019-08-20 09:03:09 +02:00
Janus
277e5be229 lightning: use queueing lock 2019-08-20 09:03:09 +02:00
Janus
0f6566d11e lightning: less junk on console, quicker shutdown 2019-08-20 09:03:09 +02:00
Janus
e3ef8d7ec4 lightning: don't receive too much data, workaround by reading byte by byte 2019-08-20 09:03:09 +02:00
Janus
d84eab0418 lightning: complete moving of lightning objects, acquire net/wallet lock while answering lightning requests 2019-08-20 09:03:09 +02:00
Janus
98f6f67c6b lightning: misc patches, launch asyncio loop on separate thread 2019-08-20 09:03:09 +02:00
Janus
ad5aac1383 lightning: march 2018 rebase, without integration 2019-08-20 09:03:09 +02:00
ThomasV
9c454726f4 rename WizardChoiceDialog (ambiguous) 2019-08-20 09:02:33 +02:00
SomberNight
bffc2a1d4b
wizard: try harder to have temporarily stored pw erased from memory 2019-08-14 17:27:38 +02:00
SomberNight
25a1ed048f
qt preferences: cast some config values to bool
to gracefully handle unexpected values

fixes #5562
2019-08-14 17:15:03 +02:00
ThomasV
b42009acb7 signtransaction: pass pubkey to tx.sign (fix #5522) 2019-08-14 12:49:35 +02:00
ThomasV
1d637ef988 fix #5558 2019-08-14 12:00:36 +02:00
ThomasV
aa7aeb9014 disable go_back button in wizard password dialog (fix #5509) 2019-08-14 10:14:28 +02:00
SomberNight
e62d652f14
(trivial) time.clock is deprecated, replace with time.process_time 2019-08-14 02:18:08 +02:00
SomberNight
bf7beba60c
kivy wizard: script type dialog now uses toggle buttons 2019-08-13 20:12:27 +02:00
SomberNight
bcbd52d752
kivy wizard: dedicated button for seed options
previously user had to touch the text field itself
2019-08-13 19:19:50 +02:00
ThomasV
a7b61fcab9 kivy: support bip39 seeds 2019-08-13 17:14:54 +02:00
SomberNight
95ae42b998
kivy wizard: undo Window.bind callbacks
related: #5534
2019-08-13 17:12:42 +02:00
Christian Clauss
e34afd62ce Travis CI: Use flake8 to find Python syntax errors and undefined names (#5467) 2019-08-11 22:35:23 +00:00
ThomasV
9eebb306f2
Merge pull request #5550 from zebra-lucky/fix_qt_gui_qrcodewidget_pen
qt gui: fix qrcodewidget pen for retina display
2019-08-10 10:08:47 +02:00
zebra-lucky
93724ba33a qt gui: fix qrcodewidget pen for retina display 2019-08-10 03:18:15 +03:00
SomberNight
1c75d939d9
commands: change API of "make_seed" and "create" commands
instead of "segwit" boolean, take a "seed_type" optional arg
default seed_type to "segwit"
previously these commands created legacy seeds by defalt
2019-08-09 22:02:01 +02:00
SomberNight
0ec9f79402
rerun freeze_packages 2019-08-09 21:28:35 +02:00
SomberNight
018b962e61
requirements: allow using newer pycryptodomex
upper bound was previously added in cb4784c8ed
issue linked there was fixed in pyinstaller 3.5,
which we now use since ab95eff5aa
2019-08-09 21:27:13 +02:00
SomberNight
fc5248550c
appimage build: change base to ubuntu 16.04
ubuntu 14.04 is EOL
2019-08-09 20:56:20 +02:00
SomberNight
dfad0f43c0
ledger nano: fix monospace font on Windows, and text readability 2019-08-09 20:18:38 +02:00
SomberNight
be4cf321e0
ledger: remove mobile pairing 2FA support for Ledger Nano
service no longer provided by Ledger; app not in Google Play Store any more

based on Electron-Cash/Electron-Cash#1298
2019-08-09 19:54:09 +02:00
SomberNight
ab95eff5aa
build: update pyinstaller to 3.5 2019-08-09 19:13:12 +02:00
SomberNight
c8e2653690
wine build: pin build dependencies
"pip install pyinstaller" was "silently" grabbing unpinned dependencies
2019-08-09 19:05:32 +02:00
SomberNight
d4e16001bb
update block header checkpoints 2019-08-09 18:59:21 +02:00
SomberNight
8c91deb621
storage: better error msg on unsupported seed version 2019-08-09 18:11:50 +02:00
SomberNight
9547fb7b06
qt console: accept kwargs for commands.py methods
e.g. make_seed(nbits=264, segwit=True)

also allow setting "password" as a kwarg
2019-08-06 05:20:53 +02:00
ThomasV
3a35ab2574
Merge pull request #5534 from zebra-lucky/kivy_installwizard_back_button
gui/kivy: fix installwizard back button behaviour
2019-08-02 12:06:06 +02:00
ThomasV
d9a67c6e5d
Merge pull request #5542 from gballet/fix-electrum-env-for-freebsd
Fix electrum-env on FreeBSD
2019-08-02 08:38:08 +02:00
SomberNight
8390da9b7b
bitcoin.py: dedupe pubkeyhash_to_p2pkh_script 2019-07-31 01:18:51 +02:00
SomberNight
a10dc04b28
wallet: fix offline hw wallet signing when not specifying --offline
closes #5532
2019-07-29 13:27:37 +02:00
Daniel Kraft
3f8661b069
Unit tests for Blockchain.verify_header.
The function Blockchain.verify_header was previously not covered by tests
at all.  Even removing all the tests in it would still make the unit tests
pass.  This change adds tests for this important (!) function.
2019-07-28 13:23:20 +02:00
Guillaume Ballet
fc220c17ec Make sure bash can be found on *BSD 2019-07-28 07:08:56 +00:00
zebra-lucky
e953eebdb9 gui/kivy: fix installwizard back button behaviour 2019-07-28 04:04:20 +03:00
Axel Gembe
84ca7ef306
Build: Set a fixed umask before starting Docker
Umask seems to leak into Docker containers and causes the build to
not be reproducible accross different umasks.

-----

taken from Electron-Cash/Electron-Cash@984967b408
2019-07-23 21:24:32 +02:00
ThomasV
2a80f6a3ad
Merge pull request #5520 from ldz1/exchange-fix
Exchange fix
2019-07-21 13:28:19 +02:00
ldz1
d17489e971
Removed dead exchange. 2019-07-21 13:15:06 +02:00
ldz1
7dda20c492
Removed dead exchange. 2019-07-21 13:13:51 +02:00
SomberNight
249e3d496b
appimage build: rm "build" folder if present as it makes build non-reproducible
AFAICT the "build" is created if you "python setup.py install" electrum,
which is now deprecated in any case.
2019-07-19 04:52:26 +02:00
SomberNight
f60f690ca9
change many str(e) to repr(e) as some exceptions were cryptic
it's often valuable to see the type of the exception
(especially as for some exceptions str(e) == '')
2019-07-17 20:12:52 +02:00
SomberNight
40e2b1d6e7
exchange_rate: fix #5495 2019-07-14 14:34:02 +02:00
ThomasV
16f56ccbf0 load version module in make_download 2019-07-11 16:54:47 +02:00
ThomasV
665d6540d7 pass host to upload script 2019-07-11 16:34:33 +02:00
SomberNight
e81f4bdcd1
prepare release 3.3.8 2019-07-11 14:51:54 +02:00
SomberNight
61bf5ce59a
windows build: calculate COFF checksum ourselves
closes #5504
2019-07-10 23:44:51 +02:00
SomberNight
c67705e116
appimage build: build was failing on some host systems
On Ubuntu host, build succeeded; but e.g. on Manjaro host, it failed with:
```
./build.sh: line 233: /opt/electrum/contrib/build-linux/appimage/../../../contrib/build-linux/appimage/.cache/appimage/appimagetool: No such file or directory
```
2019-07-10 20:26:25 +02:00
SomberNight
8a1052330d
wallet: loosen bump_fee sanity check further
fixes #5502
2019-07-10 16:35:40 +02:00
ThomasV
261c492c37
Merge pull request #5494 from SomberNight/tx_signing_perf_20190708
transaction: segwit input signing was doing quadratic hashing
2019-07-09 17:41:56 +02:00
SomberNight
cc42b4a226
transaction: segwit input signing was doing quadratic hashing
performance improvements are negligible for typical transactions though.
some measurements of wall clock time for Transaction.sign (with libsecp256k1):
  0.11 sec -> 0.08 sec    (  61 p2wpkh-p2sh inputs, 1 output)
  2.48 sec -> 0.75 sec    ( 522 p2wpkh-p2sh inputs, 1 output)
 13.2  sec -> 1.8  sec    (1445 p2wpkh inputs, 1 output)
176.4  sec -> 7.6  sec    (5542 p2wpkh inputs, 1 output)
2019-07-09 17:37:02 +02:00
SomberNight
a14016275b
transaction.serialize_preimage: trivial clean-up 2019-07-08 05:58:57 +02:00
SomberNight
b4bf39ee92
qt coins tab: let user filter by prevout_hash/prevout_n 2019-07-08 05:20:26 +02:00
SomberNight
91d8f12f44
servers: follow-up prev 2019-07-06 00:35:03 +02:00
SomberNight
eb92bda597
servers: rm phishing domain
(and update a port)
2019-07-06 00:25:55 +02:00
SomberNight
aadde9be17
transaction: fix remove_signatures
closes #5491
2019-07-05 21:16:58 +02:00
SomberNight
cc9ad3ae90
wallet: fix restore_wallet_from_text edge case
closes #5490
2019-07-05 19:27:44 +02:00
SomberNight
9b82321fc0
verifier: further sanity checks for SPV verification.
Thanks to @JeremyRand
2019-07-05 18:39:40 +02:00
SomberNight
5bf854edcb
android build: make buildozer.spec more similar to upstream example 2019-07-05 00:10:55 +02:00
Axel Gembe
fc65cdaa8a
AppImage: Fix webbrowser.open not opening links
There was an issue where webbrowser.open would invoke a program like
kde-open5 that loaded the systems libQt5DBus, which was not satisfied
with the AppImage's libdbus. To fix this we fork the process, unset
LD_LIBRARY_PATH and then open the URL.

fixes #5425

-----

taken from Electron-Cash/Electron-Cash@00939aafd1
2019-07-05 00:02:26 +02:00
Axel Gembe
69b673b8a1
AppImage: Bundle more binaries to increase compatibility
This slightly increases the AppImage size but allows us to be more
compatible with older distributions.

-----

taken from Electron-Cash/Electron-Cash@96644acd6f
2019-07-04 23:35:52 +02:00
Axel Gembe
dcecf7db4b
Wine Build: Make it less noisy
This suppresses the pip script location warnings, like we already
do for AppImage. It also disables the Wine debugging messages by
setting WINEDEBUG=-all.

-----

taken from Electron-Cash/Electron-Cash@d3685b038e
2019-07-04 22:32:51 +02:00
Axel Gembe
0d1a473bb0
AppImage: Disable pip warnings about script install locations
It warns about scripts being installed in a location that is not on the
path, but that is inconsequential as they are not used.

-----

taken from Electron-Cash/Electron-Cash@9a29017c5d
2019-07-04 22:31:56 +02:00
SomberNight
c9006032d9
qt network dialog: let user edit server host/port in peace
incoming network updates could keep changing the text fields while
user is editing them
2019-07-04 21:46:11 +02:00
SomberNight
1518c7d133
build macOS README: mention how Qt affects min supported macOS version 2019-07-04 20:53:24 +02:00
SomberNight
93d68a4361
exchange_rate: fix #5487 2019-07-04 19:55:03 +02:00
SomberNight
650225e238
crash reporter UX
see #5483
2019-07-04 19:13:12 +02:00
SomberNight
28ca561bba
added trigger_crash method for testing crash reporter
invoke via console as:
electrum.base_crash_reporter.trigger_crash()
2019-07-04 18:06:21 +02:00
SomberNight
94b721baa4
wallet: fix type error in _bump_fee_through_decreasing_outputs
fixes #5483
2019-07-04 17:23:34 +02:00
SomberNight
194bf84418
build readme nits
sudo is needed to rm FRESH_CLONE as docker is running as sudo.
the proper fix would be to have docker not run as sudo...
2019-07-03 21:09:11 +02:00
SomberNight
5ed6a68d8c
update make_locale doc references, and small nits 2019-07-03 17:42:40 +02:00
SomberNight
f1516d60ec
mac build: fix locale in binaries 2019-07-03 17:37:02 +02:00
SomberNight
ec56a4612c
make_tgz: build locale from deterministic submodule 2019-07-03 17:36:29 +02:00
ThomasV
7b7397a8c7 chmod push_locale 2019-07-03 16:20:40 +02:00
ThomasV
5db21134aa separate push and pull locale 2019-07-03 16:19:26 +02:00
ThomasV
aa00fa2a5c update submodule 2019-07-03 16:01:10 +02:00
SomberNight
034c1e0828
prepare release 3.3.7 2019-07-03 15:47:05 +02:00
SomberNight
e431a07258
fix prev: conditional import / type hint failure 2019-07-03 13:56:11 +02:00
SomberNight
d293b2e038
wallet: follow-up prev 2019-07-03 13:40:42 +02:00
ThomasV
37e7add776 Do not pass storage to address_synchronizer 2019-07-03 10:46:30 +02:00
SomberNight
fb76fcc886
trezor: use only Bridge when available
fixes #5420
2019-07-02 21:21:39 +02:00
SomberNight
53893be4c9
crash reporter: in Qt subclass, do network request using WaitingDialog
so it does not block the GUI
2019-07-02 19:27:36 +02:00
SomberNight
1d0f67996e
build-wine: build our own pyinstaller bootloader
This seems to reduce anti-virus false positives.

based on:
Electron-Cash/Electron-Cash@1ac12e4111
Electron-Cash/Electron-Cash@9726498e95
Electron-Cash/Electron-Cash@40b1139d67
2019-07-01 22:22:25 +02:00
SomberNight
423d44bcaf
build-wine: some clean-up. cache downloads. better status messages 2019-07-01 20:18:30 +02:00
SomberNight
6455f515f0
build-wine: don't use gpg keyservers
based on Electron-Cash/Electron-Cash@a582be04d3
2019-07-01 18:01:14 +02:00
SomberNight
7c5247081b
change electrum.png to square (by padding)
ran "appimagelint" and apparently icon file needs to be a square
(could have just created another copy, but I guess a square icon
might make sense in other cases too)
2019-07-01 15:00:21 +02:00
SomberNight
4c63eca896
wallet.bump_fee: loosen sanity check a tiny bit 2019-06-29 16:22:37 +02:00
SomberNight
4f51308eab
coinchooser: clarify docs for make_tx 2019-06-29 16:21:07 +02:00
SomberNight
72d06038a7
synchronizer: fix race in _on_address_status
Triggering needs two consecutive scripthash status changes
in very quick succession. Client gets notification from server,
but then response to "blockchain.scripthash.get_history" will already contain
the changed-again history that has a different status.

20190627T101547.902638Z |     INFO | synchronizer.[default_wallet] | receiving history mwXtx49BCGAiy4tU1r7MBX5VVLWSdtasCL 1
20190627T101547.903262Z |     INFO | synchronizer.[default_wallet] | error: status mismatch: mwXtx49BCGAiy4tU1r7MBX5VVLWSdtasCL
2019-06-29 06:03:14 +02:00
SomberNight
37809bed74
qt high dpi: fix some text fields
There are probably other DPI related issues though.

closes #5471
closes #4597
closes #1927
2019-06-29 05:27:28 +02:00
SomberNight
e7304ce23e
TorDetector: minor clean-up 2019-06-29 04:03:29 +02:00
nachunjae
8a4e307b78 Update block explorer URL for btc.com (#5438)
* update block explorer URL for btc.com
2019-06-29 03:54:53 +02:00
SomberNight
f405c3fbdd
ledger: (trivial) rm some remnants of hw1 setup 2019-06-29 02:28:00 +02:00
SomberNight
935ab9a12f
interface: check if future already done in handle_disconnect
future could get cancelled in network.py in which case set_result raised
2019-06-28 21:13:33 +02:00
SomberNight
c6a54f05f5
wallet: some performance optimisations for get_receiving_addresses
jsondb takes a copy of the whole self.receiving_addresses
good for avoiding race conditions but horrible for performance...
this significantly speeds up at least
- synchronize_sequence, and
- is_beyond_limit (used by Qt AddressList)
2019-06-28 20:20:24 +02:00
SomberNight
a2bffb9137
network: harden against eclipse attacks 2019-06-27 19:10:25 +02:00
SomberNight
baa0293620
android build: persist debug keystore
so that we can upgrade debug installations on the phone and keep the datadir
2019-06-27 07:08:03 +02:00
SomberNight
0fafd8c0a7
fix #4777 again... 2019-06-27 05:00:16 +02:00
SomberNight
7bf6786bf5
build: note whether binary is reproducible in each case 2019-06-26 04:18:24 +02:00
SomberNight
4fc43da344
interface.debug will now also print errors 2019-06-26 01:16:34 +02:00
ThomasV
62e6ca50e1 do not log client-side RPC executions 2019-06-25 15:26:24 +02:00
SomberNight
570c0aeca3
build: make NSIS windows binary deterministic by changing the .ico file
see bitcoin/bitcoin@217208a36d

-----

A lot of time was wasted on this... over the years actually...

Some notes and rant here, for future reference.

During the initial effort to try to make binaries reproducible,
out of the three windows binaries being distributed (standalone, portable, setup),
only the first two were successfully made deterministic.
Later, we started to use Docker-based builds. At that point ThomasV and I
could reproducibly build the same setup/nsis exe but Travis kept building a different one.

Recently I have noticed that if I do two subsequent builds of the setup exe on
the same machine, adding a new file in contrib/build-wine/ between the builds,
then I get different binaries. Playing around with this a bit, it seems:
- other files that are in the same folder as contrib/build-wine/electrum.nsi affect the binary
- only files that are in exactly the same folder matter (not recursively)
- only filenames matter (not permission, owner, timestamps, or file contents)
To see the difference in the binaries, use vbindiff, and disable the compression done
by nsis (SetCompress off).
There is a ~48 byte diff near the very beginning of the "Uninstaller" section.
I am only guessing it is the uninstaller section based on the sizes of the sections
printed by nsis during the build.
I have downloaded the binary built by Travis, and the diff is consistent with this
(i.e. it's the same kind of diff that manifests if I change the filename of one of
the supposedly unrelated files).
Commenting out the "WriteUninstaller" line in .nsi fixes the issue. i.e. if no
uninstaller is created then the binary becomes deterministic.
Commenting out the "!define MUI_ICON" line in .nsi also fixes the issue.
At this point I remembered the above referenced commit by bluematt; which I had
thought we had already followed up on...
Replacing the .ico file fixes the issue.
Note that it's not actually clear what the exact requirements for the .ico file are.
Removing any of the layers in the image seems to introduce non-determinicity.
The new .ico file has layers with resolutions and properties the bitcoin.ico file has.

I guess NSIS must have strict requirements for the icon size, and if a given size icon is missing
it might be creating it itself?? And during the downscaling it uses a non-deterministic
algorithm that initialises some RNG from the directory listing (bauerj's guess somewhat adapted :D).
Just crazy.
2019-06-24 21:51:47 +02:00
SomberNight
9f28f8bcc6
Appimage: follow-up b69249f6c3
libsecp256k1.a needs to be deleted as it's not reproducible...
2019-06-23 04:17:46 +02:00
SomberNight
266484e0fd
Appimage: nits. use "fail"
somewhat based on same script in Electron-Cash/Electron-Cash
2019-06-23 04:13:28 +02:00
Axel Gembe
bb59a1298a
AppImage: Patch Python sysconfigdata
When building in docker on macOS, python builds with .exe extension
because the case insensitive file system of macOS leaks into docker.
This causes the build to result in a different output on macOS compared
to Linux. We simply patch sysconfigdata to remove the extension.

Some more info: https://bugs.python.org/issue27631
2019-06-23 04:13:23 +02:00
SomberNight
212ed8b18b
qt: set WWLabel text to be mouse-selectable by default
this lets user to copy-paste text in e.g. many wizard dialogs
2019-06-23 03:10:09 +02:00
SomberNight
31ba440d1c
build-wine: print some text before "pip install" 2019-06-23 03:09:05 +02:00
SomberNight
ec496a8222
requirements-hw: rm Cython
not actually needed
based on Electron-Cash/Electron-Cash@70de1a2b53
2019-06-23 03:06:36 +02:00
Axel Gembe
b69249f6c3
AppImage: Remove unused binaries
There are a lot of dupliacted files, testing files and unused libraries
present in the AppImage. Removing these reduces the AppImage size
significantly.

-----

taken from Electron-Cash/Electron-Cash@cff5fb1289
2019-06-23 02:56:33 +02:00
Axel Gembe
501fd8f9e5
AppImage: Improve reproducible Python build reliability on Linux
There was a problem where Python would not properly include the faketime
timestamp sometimes. This patch replaces faketime with a patch that is
used by Ubuntu for reproducible builds by exporting BUILD_DATE and
BUILD_TIME with the desired values.

-----

taken from Electron-Cash/Electron-Cash@9532508a3f
2019-06-23 02:47:16 +02:00
Axel Gembe
ae714772c3
AppImage: Make build reproducible
We build our own mksquashfs from squashfskit which supports generating
reproducible squashfs images. We use a small wrapper script to remove
the -mkfs-fixed-time which appimagekit passes but squashfskits
mksquashfs does not support.

-----

taken from Electron-Cash/Electron-Cash@dd1f106f4f
see AppImage/AppImageKit#929
2019-06-23 02:40:29 +02:00
SomberNight
fd5d1dab4f
coinchooser: clear up what "fee_estimator" expects 2019-06-20 22:46:22 +02:00
SomberNight
cb69aa80f7
coinchooser: don't spend buckets with negative effective value
Calculate the effective value of buckets, and filter <0 out.
Note that the filtering is done on the buckets, not per-coin.
This should better preserve the user's privacy in certain cases.

When the user "sends Max", as before, all UTXOs are selected,
even if they are not economical to spend.

see #5433
2019-06-20 22:42:50 +02:00
SomberNight
e0b1bbfc46
tests: new tests for bump_fee and rbf_batching 2019-06-20 22:42:50 +02:00
SomberNight
0c20fcb6b3
tests: fix existing bump_fee tests 2019-06-20 22:42:49 +02:00
SomberNight
8491a2d329
wallet: RBF batching will now reuse the change address 2019-06-20 22:42:49 +02:00
SomberNight
d0a43662bd
wallet: make "increase fee" RBF logic smarter
There are now two internal strategies to bump the fee of a txn.
bump fee method 1: keep all inputs, keep all not is_mine outputs,
                   allow adding new inputs
bump fee method 2: keep all inputs, no new inputs are added,
                   allow decreasing and removing outputs (change is decreased first)
Method 2 is less "safe" as it might end up decreasing e.g. a payment to a merchant;
but e.g. if the user has sent "Max" previously, this is the only way to RBF.

We try method 1 first, and fail-over to method 2.
Previous versions always used method 2.

fixes #3652
2019-06-20 22:42:48 +02:00
SomberNight
8bfe12e047
wallet: split "change address logic" from make_unsigned_transaction 2019-06-20 22:42:48 +02:00
SomberNight
e864fa5088
coinchooser: tweak heuristic scoring.
transactions without any change now get better scores.
transactions with really small change get worse scores.
2019-06-20 22:42:47 +02:00
SomberNight
f409b5da40
coinchooser: refactor so that penalty_func has access to change outputs 2019-06-20 22:42:47 +02:00
SomberNight
6424163d4b
wallet: fix rbf_batching edge case
The old change output was given to coinchooser
as part of possible UTXOs to use.
(Though the coinchooser was really unlikely to select it, as by
definition that UTXO is unconfirmed)
2019-06-20 21:53:24 +02:00
SomberNight
5f71163449
qt crash reporter: add warning that report contents are public 2019-06-20 17:32:21 +02:00
SomberNight
5effaaf428
TxOutput usage: trivial clean-up 2019-06-19 21:59:49 +02:00
SomberNight
c7a8540d06
kivy: show tx fee rate in tx dialog 2019-06-19 21:56:52 +02:00
SomberNight
cb204dd969
coinchooser: better account for fees in penalty_func 2019-06-17 19:55:39 +02:00
SomberNight
e3c26d7c7a
json_db: fix remove_spent_outpoint
method should make sure prevout_n is str...
also wrote failing test
2019-06-15 03:51:11 +02:00
SomberNight
d07caaf601
qt msgbox: when using rich text, set text format to "AutoText" instead
"\n" newlines were ignored for WIF_HELP_TEXT InfoButtons
2019-06-13 17:03:12 +02:00
SomberNight
23ec426b4f
qt history list: tweak sort order of items 2019-06-13 01:03:56 +02:00
SomberNight
811169da4b
plugins: on some systems plugins with relative imports failed to load
this caused electrum to fail to start
potentially only older python 3.6.x are affected

fixes #5421
2019-06-12 20:07:36 +02:00
SomberNight
29ce50a305
follow-up prev
wallet.is_mine needs to tolerate None as input
2019-06-12 18:27:13 +02:00
SomberNight
9e21b76c91
wallet: stricter validation in export_private_key
fixes #5422
2019-06-12 18:09:38 +02:00
SomberNight
c7b64f4794
AppImage: update appimagetool version 2019-06-11 20:24:51 +02:00
SomberNight
63e5119ceb
builds: parallelise "make" by setting "-j4" 2019-06-11 20:02:28 +02:00
SomberNight
9d2b601cc7
update block explorer URL for blockchain.info
closes #5408
2019-06-11 19:19:43 +02:00
SomberNight
7120c344b2
qt seed completer: colour words yellow if only in old electrum list
Some people complained that due to merging the two word lists,
it is difficult to restore from a metal backup, as they planned
to rely on the "4 letter prefixes are unique in bip39 word list" property.
So we colour words that are only in old list.
2019-06-08 15:37:49 +02:00
SomberNight
5c83df7709
android: update kivy, p4a, buildozer 2019-06-07 20:06:15 +02:00
SomberNight
6bdc6f559c
storage: fix bug in convert_version_17
closes #5400
2019-06-06 19:51:37 +02:00
SomberNight
53d189fc7a
storage: fix some madness about get_data_ref() and put() interacting badly
previously load_transactions() had to be called before upgrade();
now we reverse this order.

to reproduce/illustrate issue, before this commit:

try running convert_version_17 and convert_version_18
(e.g. see testcase test_upgrade_from_client_2_9_3_old_seeded_with_realistic_history)
and then in qt console:
>> wallet.storage.db.get_data_ref('spent_outpoints') == wallet.storage.db.spent_outpoints
False
>> wallet.storage.db.get_data_ref('verified_tx3') == wallet.storage.db.verified_tx
False
2019-06-06 19:49:06 +02:00
SomberNight
33308307a4
bip70 payreq: do not show error messages in gui
closes #5393
2019-06-05 19:40:33 +02:00
SomberNight
0553ab7f3f
follow-up prev
PaymentRequest.error is really not intuitive.........
2019-06-05 19:05:58 +02:00
SomberNight
d2de8de356
qt payment requests: fix some races
closes #5283, #5407, #5121
2019-06-05 16:29:33 +02:00
SomberNight
0ec574bcf8
kivy tx_dialog: fix size of buttons in "Options" dropdown 2019-06-04 21:00:48 +02:00
SomberNight
fbcf6f48b9
rerun freeze_packages 2019-06-04 20:36:52 +02:00
SomberNight
046518d7f7
requirements: restrict qdarkstyle to <2.7
qdarkstyle 2.7 pulls in new dependencies
see ColinDuquesnoy/QDarkStyleSheet#182
2019-06-04 20:35:46 +02:00
SomberNight
6cf7aefe28
kivy: offer to copy raw hex tx to clipboard
related: #5405
2019-06-04 19:20:31 +02:00
SomberNight
21ab65e5f7
qt lists right click: fix #5365 2019-06-03 22:21:53 +02:00
SomberNight
0ef853c046
rm dead code 2019-06-03 20:35:37 +02:00
SomberNight
371e1a6ebf
hw: allow bypassing "too old firmware" error when using hw wallets
The framework here is generic enough that it can be used for any hw plugin,
however atm only Trezor is implemented.

closes #5391
2019-05-31 04:09:03 +02:00
SomberNight
7cba46c317
deprecation warnings: only show when running from source 2019-05-28 21:38:27 +02:00
SomberNight
ab81a09de2
interface: hide some server-induced errors from log 2019-05-28 21:23:06 +02:00
SomberNight
d17e6a1b87
interface: fix for aiorpcx 0.18 2019-05-28 06:14:53 +02:00
SomberNight
41802d8094
qt receive tab: "receive address" is now coloured red if already used
closes #3812
closes #5374
2019-05-27 20:24:09 +02:00
SomberNight
41f160dd74
update to aiorpcx 0.18 2019-05-27 19:35:30 +02:00
SomberNight
eaf203dbb5
interface: fix connecting to new servers using self-signed certs
got broken in 6ec1578a90
2019-05-27 19:09:54 +02:00
SomberNight
37da192bf5
wizard/hw: less spammy logs when hw library unavailable
closes #5380
2019-05-26 17:01:01 +02:00
SomberNight
30ffb3d4dc
util: add function "chunks"
taken from ElectrumX
67111a3c4c/electrumx/lib/util.py (L149)
2019-05-26 04:10:32 +02:00
SomberNight
1ebfcc0f36
kivy: "paste" button now works for transactions 2019-05-26 02:46:25 +02:00
SomberNight
c776af41f6
qt: allow QR codes to store a bit more data
by decreasing error correction (about ~26% larger max payload)
2019-05-26 02:13:02 +02:00
SomberNight
aec53ae6af
qt: "Help" and "?" buttons can show rich text
namely "Revealer" plugin uses rich text in its description
2019-05-26 01:27:27 +02:00
SomberNight
e1c1a9d6a2
interface: add comment 2019-05-25 05:20:21 +02:00
SomberNight
3b445d7248
fix #5376 2019-05-25 05:13:57 +02:00
SomberNight
158090bf8b
util.parse_URI: more granular exceptions
related: #5376

first report in #5376 was generated with these changes;
before, the exception was caught and a toast displayed "Not a Bitcoin URI"
2019-05-25 04:55:36 +02:00
SomberNight
a591ccf9b1
interface: follow-up 6ec1578a90 2019-05-22 17:43:33 +02:00
SomberNight
db9a9bbf25
qt settings: restart needed after toggling log_to_file 2019-05-21 20:57:29 +02:00
SomberNight
6ec1578a90
follow-up prev 2019-05-21 18:43:16 +02:00
SomberNight
fecef91ee0
interface was suppressing storage r/w exceptions 2019-05-21 18:11:49 +02:00
ThomasV
cf01788c86
Merge pull request #5367 from SomberNight/issue_5366
keystore/transactions: fix overflow of derivation path indices
2019-05-21 09:52:05 +02:00
SomberNight
6ad24ea3b3
keystore/transactions: fix overflow of derivation path indices
fixes #5366
2019-05-21 02:14:22 +02:00
Jochen Hoenicke
468c35e605 Update Johoe's server address (#5363) 2019-05-19 17:53:22 +02:00
shyrwall
a8b939711a Spreading malware (#5356)
servers: rm phishing servers......
2019-05-18 03:41:58 +02:00
SomberNight
d3f65e24e1
kivy: warn user during "Send" if high fee (change condition)
Specifically, warning was previously triggered if fee > 1 mBTC;
now it is unified with Qt, warning is triggered if feerate > 600 sat/byte.
2019-05-17 20:10:10 +02:00
ThomasV
a762687740
Merge pull request #5354 from JeremyRand/readme-appimage
Add AppImage to "Creating Binaries" in README.md
2019-05-17 11:51:46 +02:00
JeremyRand
10098db59b
Add AppImage to "Creating Binaries" in README.md 2019-05-17 05:30:41 +00:00
ThomasV
9529b418e2 prepare release 3.3.6 2019-05-16 19:02:20 +02:00
SomberNight
763720715b
update release notes 2019-05-16 18:59:33 +02:00
SomberNight
e8bc5bbec4
interface: follow-up 6cc70bc7a2 2019-05-15 19:56:16 +02:00
Axel Gembe
cd52350f5d
AppImage: Remove unused PyQt5 modules
We already delete unused Qt modules, but we weren't deleting their PyQt5 modules.

-----

taken from Electron-Cash/Electron-Cash@e044c94677
2019-05-15 19:04:42 +02:00
Axel Gembe
5afda62ee3
AppImage: Remove Qt.so to prevent importing from PyQt5.Qt
Importing from PyQt5.Qt is an unnecessary fallback that loads every PyQt5 module.

-----

taken from Electron-Cash/Electron-Cash@d69471b31d
2019-05-15 19:03:18 +02:00
Axel Gembe
c3b92aa13a
AppImage: Copy libusb binary into image
pkg2appimage excludes libusb-1.0.so by default for no good reason:

83483c2971/excludelist (L112)

This can cause an issue when the AppImage loads the systems libusb but the
systems libusb in turn loads libudev from the AppImage. The kernel ABI for
libusb will not be changing so it is safe to bundle it into the AppImage.

-----

taken from Electron-Cash/Electron-Cash@25d45fdcbf
2019-05-15 19:01:39 +02:00
SomberNight
e415c0d930
wallet: (fix) synchronizer would also resub closed wallets...
network cb was not removed, so Synchronizer and the wallet itself was
kept in memory; and Synchronizer kept working
2019-05-14 17:04:03 +02:00
SomberNight
6cc70bc7a2
interface: when disconnecting due to RPCError, don't dump traceback 2019-05-14 15:58:02 +02:00
SomberNight
003e6c3e79
fix 2fa wallet creation via qt gui
closes #5334
2019-05-14 15:32:57 +02:00
SomberNight
7aaac2ee30
qt wizard: change wizard_dialog semantics to raise exceptions
Specifically GoBack and UserCancelled will not be suppressed anymore.
Previously, if 'run_next' raised GoBack, that would propagate out fully,
while if 'func' itself raised it would be suppressed. This was confusing.

somewhat related: #5334
2019-05-14 15:31:33 +02:00
SomberNight
099315013e
(trivial) qt main_window: rm unnecessary indendation 2019-05-14 00:03:50 +02:00
SomberNight
f6dfcccf8c
qt: factor out util.MessageBoxMixin.msg_box into function and use it
so these dialogs also get our custom default settings applied,
e.g. their text is selectable by mouse
2019-05-13 23:59:29 +02:00
SomberNight
407e3514cc
wallet: test_addresses_sanity to include (part of) address in exception
related: #5342
2019-05-13 23:20:48 +02:00
SomberNight
4db1535bce
qt wizard: catch wallet/bitcoin exceptions (regression)
fix #5342
2019-05-13 22:56:38 +02:00
SomberNight
d2a80f15a1
kivy fx dialog: fix #5329 2019-05-13 20:05:01 +02:00
SomberNight
fd58a0cb20
json_db: enforce order of 'load_transactions' and 'upgrade'
fixes #5331
2019-05-13 19:21:26 +02:00
SomberNight
a59e3efd3e
qt send tab: fix tx_size and fee calc in case of payment requests
do_update_fee() was always setting
`outputs = self.payto_e.get_outputs(...)`
but this only works `if not self.payment_request`

Minor refactor to re-use logic instead of duplicating code.
2019-05-13 02:52:22 +02:00
SomberNight
dd7b356fcc
kivy wizard: fix #5333 2019-05-11 19:36:57 +02:00
SomberNight
22c08f1522
qt dark: fix2 "In History tab, labels while edited were being clipped"
follow-up 3ed502a728
from Electron-Cash/Electron-Cash@cddde8d21b
2019-05-11 02:17:35 +02:00
SomberNight
808239bbcc
wallet: fix deleting address from Imported_Wallet
closes #4481
2019-05-11 02:08:15 +02:00
SomberNight
46ae86f600
wallet: fix balance_at_timestamp
closes #5326
2019-05-10 19:22:13 +02:00
SomberNight
aab067372c
requirements: pin PyQt5-sip version due to build problems
see https://tickets.metabrainz.org/browse/PICARD-1472
having issues on MacOS to codesign sip.so when PyQt5-sip==4.19.15:
PyQt5/sip.so malformed object (unknown load command 7)
2019-05-09 18:11:50 +02:00
ThomasV
9053cb2218 update version 2019-05-09 16:16:04 +02:00
ThomasV
bea0ac1106 date release notes 2019-05-09 16:15:09 +02:00
ThomasV
9a3ea0e514 update locale 2019-05-09 16:09:05 +02:00
SomberNight
03c3ba0d36
gitignore: add more build folders 2019-05-09 15:59:47 +02:00
ThomasV
1b8673839a buildozer: add tests to exclude_dirs 2019-05-09 15:51:00 +02:00
SomberNight
bc64051139
update release notes 2019-05-08 16:52:53 +02:00
SomberNight
f6a7e6ec7d
logging: don't log to file by default
Leaking addresses/pubkeys/txids is a privacy leak...
but with lightning, logging should be enabled by default, as otherwise
issues would be sometimes impossible to debug...
Well, disable it for now.
2019-05-08 16:52:04 +02:00
SomberNight
3ed502a728
qt dark: fix "In History tab, labels while edited were being clipped" 2019-05-08 16:31:56 +02:00
ThomasV
2a6b02d43e
Merge pull request #5321 from SomberNight/logging_shortcut_filtering_20190507
logging: '-V' cli option can blacklist/whitelist classes with short names
2019-05-08 11:41:32 +02:00
SomberNight
104b8804f7
logging: '-V' cli option can blacklist/whitelist classes with short names
for example, '-V ni' will whitelist the 'Network' and 'Interface' classes
'-V ^ni' will blacklist those instead
2019-05-07 21:07:18 +02:00
ThomasV
92260a798a
Merge pull request #5319 from SomberNight/sync_progress_3_20190507
synchronizer: show progress in GUI (take 3) (req_answered/req_sent)
2019-05-07 18:01:18 +02:00
SomberNight
0e6cf153d7
synchronizer: show progress in GUI 2019-05-07 17:58:06 +02:00
SomberNight
92ad7ec5c0
interface: use itertools.count 2019-05-07 17:24:00 +02:00
SomberNight
e63157c2ab
logging: fix another call with multiple args
did a search with following regex now: logger\..*\(.*,
2019-05-07 01:41:41 +02:00
SomberNight
06cff9ac10
logging: fix call with multiple args 2019-05-06 23:03:19 +02:00
SomberNight
fd09033890
kivy: fix a race at startup
on_history (fx) races with load_wallet
2019-05-06 21:10:52 +02:00
SomberNight
720519f610
fix wine build dir references
follow-up 8e32f49469
2019-05-06 20:19:23 +02:00
SomberNight
a0b711cfea
requirements: bump python-ecdsa minimum
0.9 was not actually enough...
2019-05-06 19:12:40 +02:00
SomberNight
70fd716cbe
kivy: fix IPv6
closes #5176
2019-05-06 19:11:56 +02:00
SomberNight
7a99fdc275
kivy: fix crash in logging.py; platform.platform() not available 2019-05-06 19:10:29 +02:00
SomberNight
8e32f49469
wine build: rm old README 2019-05-06 17:10:36 +02:00
SomberNight
3e8ca80afd
servers: remove phishing server 2019-05-06 01:00:10 +02:00
SomberNight
cb4784c8ed
requirements: use older pycryptodomex for now
see Legrandin/pycryptodome#286
2019-05-05 19:31:58 +02:00
SomberNight
b11cb11d95
qt console: failed to print certain objects with custom __eq__ 2019-05-05 17:59:45 +02:00
SomberNight
f38eed2fad
rerun freeze_packages
One user reported not getting keepkey with fw 6.1.0 detected
with python-keepkey 6.0.3, but detecting it with 6.1.0.
Sounds weird as all the changes look to be tests-related,
and could not reproduce. Still, this should not hurt.
2019-05-05 17:39:24 +02:00
SomberNight
2481d708cc
network: sanitize_tx_broadcast_response - new strings in bitcoind 0.18 2019-05-05 04:34:31 +02:00
SomberNight
00b2fee461
qt dark style: fix padding of PayToEdit
based on Electron-Cash/Electron-Cash@7e69f0e6ea
see ColinDuquesnoy/QDarkStyleSheet#159
2019-05-05 02:14:07 +02:00
SomberNight
201909df51
update release notes 2019-05-04 18:55:45 +02:00
SomberNight
c80c3596a7
logging: expose 'disablefilelogging' option in Qt preferences 2019-05-04 18:30:26 +02:00
SomberNight
e361a8549c
qt paytoedit: enable up/down keys
useful if multiline, and no harm(?) otherwise
2019-05-04 04:42:39 +02:00
SomberNight
aac9826e1b
qt paytoedit: better height adjustment
was sometimes weird...
e.g. pasting several lines of outputs would leave the textedit single line
2019-05-04 04:39:59 +02:00
SomberNight
7278953c63
servers: update mainnet default list (emzy) 2019-05-03 23:03:41 +02:00
SomberNight
7fc9e109ed
servers: update mainnet default list 2019-05-03 20:14:39 +02:00
SomberNight
0c963b0894
network.broadcast_transaction: add "do not trust" text to log messages 2019-05-03 20:11:48 +02:00
SomberNight
bd5b7abd80
rerun freeze_packages
mainly for qdarkstyle
2019-05-03 04:44:18 +02:00
SomberNight
07ec0d41d5
fix prev
unintentionally committed
2019-05-03 03:13:43 +02:00
SomberNight
fd5b1acdc8
commands: fix encrypt/decrypt
based on Electron-Cash/Electron-Cash@62aa08a0ff
2019-05-03 03:10:31 +02:00
SomberNight
387834164c
logging: port ugly accidental side-effect into readable explicit feature
LogFormatterForConsole.format() was mutating its 'record' argument,
which was affecting all handlers/formatters
2019-05-02 16:05:26 +02:00
ThomasV
a3e522efd9
Merge pull request #5296 from SomberNight/logging_20190328
use stdlib logging module instead of print_error
2019-05-02 15:33:29 +02:00
SomberNight
163a814dc4
logging: log exceptions caught by crash reporter 2019-05-02 15:19:11 +02:00
SomberNight
a7b13f4876
logging: make console log lines shorter 2019-05-02 15:19:11 +02:00
SomberNight
f22a23aaf2
logging: fix for kivy 2019-05-02 15:19:10 +02:00
SomberNight
6940c424d1
logging: cli options to filter for modules using -v
old style "-v" still works

filtering examples:
-v=debug,network=error,interface=error      // effectively blacklists network and interface
-v=warning,network=debug,interface=debug    // effectively whitelists network and interface
2019-05-02 15:19:10 +02:00
SomberNight
3385a94753
logging: basics 2019-05-02 15:19:03 +02:00
cluelessperson
4d64e132d7
standardized logging base with ISO8601, UTC, rotating file logs, and more! 2019-05-02 15:07:15 +02:00
ThomasV
ec6f4e9f1c Decouple my GPG pubkey from Animazing 2019-05-02 14:23:31 +02:00
SomberNight
28d70963ec
qt txdialog: word-wrap blockhash 2019-05-02 12:05:27 +02:00
SomberNight
556fa30ddf
interface: partially fix ipv6 proxy
note that network.deserialize_proxy is still broken for ipv6
2019-05-02 12:04:06 +02:00
SomberNight
19ced234aa
qt: maybe batch do_update_fee() on network fee update 2019-05-02 03:09:47 +02:00
SomberNight
d56917f4b1
coinchooser: improve performance significantly
existing code was n^2 in number of UTXOs
this is now mostly linear
(linear if shortcut is hit; otherwise in rare cases still quadratic)

tested using wallet with 800 UTXOs, most of which were needed to make payment
coinchooser.make_tx() went from 18 sec to 0.8 sec
2019-05-02 03:07:34 +02:00
SomberNight
15dda65c52
qt network dialog: "use Tor proxy" cb would get auto-checked incorrectly 2019-05-02 01:20:14 +02:00
SomberNight
b4648eceda
rerun freeze_packages 2019-04-30 21:36:27 +02:00
SomberNight
33f5b6a288
requirements: relax qdarkstyle version
upstream issue ColinDuquesnoy/QDarkStyleSheet#123 now fixed
2019-04-30 21:35:32 +02:00
SomberNight
51e0672da6
update to aiorpcx 0.17 2019-04-30 21:24:39 +02:00
SomberNight
d92a4e8365
keepkey: rm homescreen stuff (remnants from trezor?) 2019-04-29 19:33:54 +02:00
Calin Culianu
05697e51b8
Trezor: Minor nit in settings screen. Image size was shown as H x W
(from Electron-Cash/Electron-Cash@e0e7ff218d)
2019-04-29 19:28:42 +02:00
SomberNight
19d7784616
qt tx dialog: show block height and block hash 2019-04-29 02:48:40 +02:00
SomberNight
205c437d9a
wallet: get_tx_info now returns NamedTuple instead of abomination 2019-04-29 02:32:15 +02:00
SomberNight
271d1a3f1d
qt tx dialog: two columns for tx stats
also show "RBF", and fix "date"
2019-04-29 01:19:06 +02:00
SomberNight
99f9a1b484
qt: warn user if in testnet mode
closes #5295
2019-04-28 06:31:01 +02:00
SomberNight
162d81c156
servers: update testnet default list
closes #5293
2019-04-27 23:10:12 +02:00
luggs-co
b9ef5ea713 Remove luggs' server from default list (#5300) 2019-04-27 17:23:48 +02:00
SomberNight
7bea1cce1f
x509: show cert expiration date in error
related #5292
2019-04-26 01:55:58 +02:00
SomberNight
7584cebbe3
bitcoin: stricter check on WIF for compressed pubkeys
fixes #5290
2019-04-25 14:35:16 +02:00
ghost43
a6762ffebc
Merge pull request #5272 from SomberNight/issue_4638_2
sweep/import key: disallow uncompressed segwit
2019-04-25 14:25:46 +02:00
Aleksey Karpov
3d8bcded79 update bitcoin explorer options (#5285)
removed blocktrail.com, blockr.io, biteasy.com ( this explorers no longer works)
added btc.bitaps.com (mainnet explorer) tbtc.bitaps.com (testnet explorer + coin faucet)
2019-04-24 17:05:11 +02:00
SomberNight
c31fa798c2
util make_aiohttp_session: use longer default timeout
same motivation as for 1110f13c62
2019-04-22 03:07:31 +02:00
SomberNight
2b717a8cef
minor fix re translated string 2019-04-21 03:31:37 +02:00
SomberNight
914d02ecd3
kivy tx dialog: allow removing local transactions
fixes #5156
2019-04-21 03:26:54 +02:00
SomberNight
8d0ec1dec0
kivy tx dialog: "action button" can have multiple options -> dropdown 2019-04-21 03:25:06 +02:00
SomberNight
2adabfd918
kivy: fix history tab fiat values
follow-up 46f1fca7c3
2019-04-21 01:55:46 +02:00
SomberNight
8ad0c248a5
cpfp: include fee already paid by parent tx in calculation
fixes #5244
2019-04-20 02:09:45 +02:00
SomberNight
1f83711c26
util/Fiat: Fiat(No Data) should equal Fiat(No Data)
Decimal('NaN') != Decimal('NaN')

matters in e.g. qt history tab refresh (shortcut)
2019-04-19 20:02:44 +02:00
SomberNight
46a236f167
qt tabs: restore filter state after refresh 2019-04-19 19:12:42 +02:00
SomberNight
d4a2e9634f
bitcoin: disallow importing/sweeping segwit scripts with uncompressed pubkey
fixes #4638
2019-04-19 00:37:28 +02:00
SomberNight
a1d98d4331
sweep/import key: show error in Qt GUI to user as tooltip 2019-04-19 00:15:45 +02:00
SomberNight
1110f13c62
network: allow longer timeouts in _run_new_interface
On Windows box with weird networking setup (VPN, no IPv6, many DNS nameservers
but only some working well), the previous timeouts were too low: they were
reached due to DNS resolution.

related: #4421
2019-04-18 23:05:35 +02:00
ThomasV
9b4e490e3c leave max_button pressed (fix #5251) 2019-04-17 14:18:36 +02:00
SomberNight
c49d5f672c
network: fix proxy case
follow-up "healty spread of servers" d8f3ab0917
2019-04-15 10:49:09 +02:00
SomberNight
2dca91d048
interface: better exception handling for _try_saving_ssl_cert_for_first_time 2019-04-12 22:49:34 +02:00
SomberNight
d8f3ab0917
network: do not connect to multiple servers on same /16
maintain a healthy spread of (IP addresses of) connected servers
2019-04-12 22:32:36 +02:00
SomberNight
7ddc28b0dc
network: cap number of server peers accepted from main server 2019-04-12 22:24:36 +02:00
SomberNight
ecefa47b40
verifier: trivial clean-up 2019-04-12 20:29:05 +02:00
SomberNight
4f2aa53a76
interface: in server.version, send "electrum/3.3.4" as client name
closes #5246
2019-04-12 19:11:25 +02:00
SomberNight
0c1ea909df
kivy: "server lagging" -> show number of blocks 2019-04-12 17:13:10 +02:00
SomberNight
f432320576
appimage build: nits.
also touch symbolic links
2019-04-12 16:54:22 +02:00
SomberNight
baebfc03f1
binary builder dockerfiles: update packages
ubuntu no longer serves those older versions
2019-04-12 16:51:35 +02:00
Abdussamad Abdurrazzaq
920949498a Update utxo list when label is changed on history list 2019-04-07 08:37:57 +05:00
Abdussamad Abdurrazzaq
ffe676bc28 Strip whitespace from amounts when copying. Fix bug in request list where address was copied instead of column specific data (#5228) 2019-04-05 15:59:14 +02:00
SomberNight
441da52b51
fix more DeprecationWarnings 2019-03-27 19:27:45 +01:00
ghost43
5b6357d472
Merge pull request #5211 from FrancisPouliot/master
Add Bylls BTC/CAD rates
2019-03-27 19:03:49 +01:00
SomberNight
4374b809fa
fix-up prev 2019-03-27 19:01:38 +01:00
Francis Pouliot
a9c13d6641
exchange_rate: add Bylls BTC/CAD rates 2019-03-27 19:00:40 +01:00
SomberNight
bdb6d65cf7
fix some DeprecationWarnings 2019-03-27 18:41:42 +01:00
SomberNight
4f70da6e15
make sure DeprecationWarnings are shown 2019-03-27 18:09:26 +01:00
SomberNight
89871a835e
default servers: rm electrum3.hachre.de
see #5224
2019-03-27 16:43:07 +01:00
SomberNight
1b3f428e88
keystore: speedup for Old_KeyStore
from Electron-Cash/Electron-Cash@01177d7deb
2019-03-27 16:30:21 +01:00
SomberNight
bca6ad5241
verifier: fix logic bug. after reorg, some verifs were not undone
after a reorg, in a many fork/orphan chains scenario,
we would sometimes not undo SPV for enough blocks

functions in blockchain.py somewhat based on kyuupichan/bitcoinX@5126bd15ef
2019-03-26 21:01:43 +01:00
SomberNight
9a71120090
blockchain: fix bug when swapping chain with parent
chain might become the parent of some of its former siblings
2019-03-26 20:55:27 +01:00
SomberNight
199199421d
follow-up prev 2019-03-26 20:42:24 +01:00
ThomasV
46f1fca7c3 remove currency from amount fields in exported history 2019-03-26 18:35:46 +01:00
SomberNight
d07fce0826
windows DNS resolution: handle domain with only AAAA records
this is getting too complicated :/
2019-03-26 16:52:11 +01:00
SomberNight
beacf88420
qt invoice list: fix #5222 2019-03-26 16:38:32 +01:00
SomberNight
c4fb58cd74
windows DNS resolution: follow-up 9b0773cf2b
related: #5176, #4421

prev was failing on systems where
IPv6 is not available but DNS can resolve AAAA records
(my artificial test environment with IPv6 disabled was also filtering AAAA DNS)
2019-03-26 03:07:11 +01:00
SomberNight
1cfac928f9
trustedcoin: longer timeout for server signing
fixes #5221
2019-03-25 23:36:52 +01:00
SomberNight
9b0773cf2b
windows DNS resolution: handle IPv6
related: #5176, #4421
2019-03-25 19:15:19 +01:00
ghost43
9161d3a5a1
Merge pull request #5162 from kpstar/master
android all currencies with history checkbox
2019-03-22 19:03:07 +01:00
SomberNight
6311aa24a8
follow-up prev 2019-03-22 18:52:57 +01:00
kpstar
d79992b125
android- all fiat currency with history option 2019-03-22 18:20:52 +01:00
SomberNight
6702c335e8
network: (trivial) ignore empty string donation address 2019-03-22 17:30:12 +01:00
SomberNight
8c3ab63504
labels: don't dump trace if failed to connect to server 2019-03-22 17:24:52 +01:00
SomberNight
c23e949848
exchange_rate: change default provider to CoinGecko
closes #5188
2019-03-22 16:55:57 +01:00
SomberNight
5cbf54d23a
exchange_rate: minor clean-up
based on mhsmith/electrum@7ed3f032dd
2019-03-22 16:48:47 +01:00
SomberNight
95a122596e
fx: add CoinGecko and CoinCap
somewhat based on
Electron-Cash/Electron-Cash@b3c76cd311
Electron-Cash/Electron-Cash@adf8f943a1
Electron-Cash/Electron-Cash@8879e41fa0
2019-03-22 16:35:02 +01:00
SomberNight
df8e2c3cc2
fix Revealer in binaries
fixes #5027
2019-03-22 15:23:43 +01:00
SomberNight
9117ded1cd
appimage: build python with curses and sqlite3 included
related #5213
2019-03-22 14:42:57 +01:00
SomberNight
c2455c89df
rerun freeze_packages
(related 232e59fc60)
2019-03-22 14:34:21 +01:00
SomberNight
232e59fc60
requirements: use PyQt <5.12 in binaries for now. for AppImage
AppImage is built on Ubuntu 14.04 to maximise compatibility
but PyQt 5.12 blobs distributed on PyPI no longer work on 14.04
PyQt 5.11.x works
2019-03-22 14:32:21 +01:00
zebra-lucky
df6cba5af8 fix sign message not accept rich text (#5210)
* fix sign message not accept rich text

* set no rich also to signature/encrypt msg/encrypted fields
2019-03-20 00:46:19 +01:00
SomberNight
3b1ece4c70
interface: if iface conn fails early, don't wait for timeout of 'ready'
Network code is waiting for iface.ready with a timeout.
While network.py code is waiting, the already failed iface takes up a slot
in "connecting".
2019-03-18 21:34:01 +01:00
SomberNight
a2f477b679
rerun freeze_packages 2019-03-18 17:31:55 +01:00
SomberNight
366472549b
requirements: bump min keepkey 2019-03-18 17:29:37 +01:00
ghost43
c325f91c81
Merge pull request #5205 from keepkeyjon/keepkey-webusb
keepkey: support v6.0.0+ firmwares (webusb)
2019-03-18 16:57:45 +01:00
keepkeyjon
ef7af73bd3
keepkey: support v6.0.0+ firmwares (webusb) 2019-03-17 14:08:23 -06:00
SomberNight
a62bf2a53a
trustedcoin: better UX in Qt when cannot connect to TC server
closes #5184
2019-03-16 20:05:10 +01:00
ThomasV
13f3f71125 daemon: in detached modde, redirect strandard file descriptors 2019-03-12 08:32:27 +01:00
ghost43
52af40685e
Merge pull request #5152 from SomberNight/freeze_individual_utxos
Freeze individual UTXOs
2019-03-11 19:12:56 +01:00
SomberNight
3b7b81d82b
qt Coins tab: better tooltip for frozen items 2019-03-11 19:03:32 +01:00
SomberNight
95965c8ef4
qt Coins tab: add "Copy ..." option to context menu 2019-03-11 18:55:49 +01:00
SomberNight
24b0943266
qt Coins tab: reorder columns so that outpoint is first 2019-03-11 18:53:53 +01:00
SomberNight
752c518bdc
add option to freeze individual UTXOs
based on cculianu's work in Electron-Cash/Electron-Cash@fd910cffc8
2019-03-11 18:46:29 +01:00
SomberNight
cd40f2c9b7
trivial wallet clean-up 2019-03-10 18:47:23 +01:00
SomberNight
7b4bb19b34
exchange_rate: raise for http status so empty hist rate dicts don't get saved
and if they had already been saved, treat them as if nothing was saved

context: BitcoinAverage started restricting the historical API endpoints.
they now deny unauthenticated requests. :/
2019-03-10 07:41:49 +01:00
SomberNight
1f7b56b455
labels plugin: no need for 'proxy_set' callback 2019-03-09 17:13:06 +01:00
SomberNight
6aa81a8f56
commands: explicitly mention "?"/":" magic chars for "create" cmd
related: #5185
2019-03-09 16:47:19 +01:00
SomberNight
790c889483
qt network dialog: increase min size 2019-03-07 16:33:30 +01:00
SomberNight
03ab64e39f
appimage: towards deterministic builds
same-machine build almost works.

$ diffoscope dist/electrum-3.3.4-76-geb04551-dirty-x86_64.AppImage1 dist/electrum-3.3.4-76-geb04551-dirty-x86_64.AppImage2
 |############################|  100%                             Time: 0:00:05
--- dist/electrum-3.3.4-76-geb04551-dirty-x86_64.AppImage1
+++ dist/electrum-3.3.4-76-geb04551-dirty-x86_64.AppImage2
├── readelf --wide --decompress --hex-dump=.digest_md5 {}
│ @@ -1,4 +1,4 @@
│
│  Hex dump of section '.digest_md5':
│ -  0x00000000 77e356ea eefe1459 a40f00d9 ab5c0e00 w.V....Y.....\..
│ +  0x00000000 1dda23b5 31f9024c fe6d2755 e930a41a ..#.1..L.m'U.0..
2019-03-06 04:53:18 +01:00
SomberNight
eb04551f9a
accept base43-encoded tx in Qt "Load transaction"
When encoding transactions as QR codes, we encode the tx bytes as base43
first. This makes it easier to load a transaction if the user manually
decodes the QR code.
2019-03-05 17:27:53 +01:00
SomberNight
8f5ca40d10
new cli command: get_tx_status 2019-03-05 17:01:54 +01:00
ThomasV
56ced1dfd5 let DB handle addresses 2019-03-05 08:20:34 +01:00
SomberNight
2abc4f6334
wallet: cache get_addr_balance
notably this makes get_history faster; as 40% of the time is spent in
get_addr_balance (without the cache)
2019-03-04 22:19:33 +01:00
SomberNight
514d0ae958
wallet: towards restoring previous performance 2 2019-03-04 18:16:48 +01:00
SomberNight
8ae6ddcc00
keystore: rm dead code 2019-03-04 17:29:12 +01:00
SomberNight
12b98fa251
wizard: fix regression: unencrypted wallets were not getting upgraded
fixes #5177
2019-03-04 17:23:43 +01:00
SomberNight
bf1c1c2a11
qt qrcodewidget: on MacOS, was grabbing whole screen
from Electron-Cash/Electron-Cash@61d46989e6
2019-03-04 04:24:45 +01:00
SomberNight
43583c1e28
daemon: make sure wallet paths are standardised everywhere 2019-03-04 02:49:41 +01:00
SomberNight
d11481f360
storage: fix path standardisation 2019-03-04 02:48:25 +01:00
SomberNight
93fa9a9d69
wizard: partial revert of 2da6692f73 2019-03-04 02:46:50 +01:00
SomberNight
d55a045405
qt wizard: minor clean-up 2019-03-04 02:20:34 +01:00
SomberNight
2da6692f73
wizard: some fixes
related: #5174
2019-03-04 02:08:23 +01:00
SomberNight
ef1330df5d
[trivial] use namedtuple field by name 2019-03-03 17:34:03 +01:00
SomberNight
7458461f13
wizard: fix decryption of hw wallet files
see #5174
2019-03-03 17:33:13 +01:00
SomberNight
b076f5294f
wizard: allow kwargs in run() 2019-03-03 17:32:00 +01:00
SomberNight
0ac2ca8ed3
qt "new transaction" notifications: change wording
closes #5171
2019-03-02 19:25:54 +01:00
SomberNight
b79f43c360
wallet: fix RBF batching
post-storage_db-merge fixup
2019-03-02 19:16:39 +01:00
SomberNight
94c4cb44d7
fix storage upgrade tests: "fixture 'func' not found" 2019-03-01 21:02:10 +01:00
SomberNight
0c232905a8
wallet: rm dead code 2019-03-01 20:55:21 +01:00
SomberNight
b6d5304e51
qt: close wizard after splitting an old multi-account wallet
instead of trying to open the old pre-split file
2019-03-01 20:50:17 +01:00
SomberNight
b134f04fef
storage: fix convert_version_18 2019-03-01 19:54:09 +01:00
SomberNight
9ecb504739
storage: fix convert_version_17 and add new test case
follow-up 121b8048b0
2019-03-01 19:46:23 +01:00
SomberNight
ef8d7e3227
qt wizard: don't consider old version storage to be incomplete 2019-03-01 19:44:20 +01:00
SomberNight
121b8048b0
json_db: store Transaction objects in memory, not raw hex
to avoid deserializing the same tx multiple times
2019-03-01 17:59:22 +01:00
SomberNight
2ad73050b3
wallet: towards restoring previous performance 2019-03-01 17:59:22 +01:00
SomberNight
8b2c586d30
post-storage_db-merge fixups 2019-03-01 14:14:30 +01:00
SomberNight
d0fa3b431a
wallet: generate addresses in Deterministic_Wallet constructor 2019-02-28 21:22:10 +01:00
SomberNight
7b9047d8d6
scripts: simplify quick_start 2019-02-28 20:26:30 +01:00
SomberNight
b34e1634b6
commands: fix gettransaction 2019-02-28 20:26:30 +01:00
SomberNight
f85b8f349d
wallet: fix bug in restore_wallet_from_text, and write tests 2019-02-28 20:26:29 +01:00
SomberNight
ae80f143e7
commands/wallet: separate out 'create' and 'restore' core parts
so that they are easier to use from python scripts
2019-02-28 20:26:29 +01:00
SomberNight
b2128af958
ledger: suppress error message if user cancels tx signing 2019-02-28 17:56:08 +01:00
ThomasV
2fd4cdcaa9 json_db: add missing lock 2019-02-28 16:11:19 +01:00
ThomasV
943d1ba8f2 json_db: private methods. return tx in remove_transaction 2019-02-28 16:03:36 +01:00
ThomasV
d8c4bf5662 storage: call load_plugins in decrypt 2019-02-28 15:50:37 +01:00
ThomasV
1e519f2dd0 json_db: make get operations threadsafe 2019-02-28 15:47:42 +01:00
ThomasV
3631c27ed7 fix: load trustedcoin plugin for two-step wallet creation 2019-02-28 13:11:00 +01:00
ThomasV
d74f0c0947 storage_db: fix tests, add modified flag to db class 2019-02-28 12:09:36 +01:00
ThomasV
dbca0a0e83 fix tests for json storage 2019-02-28 10:37:48 +01:00
ThomasV
048eb01300 fix json_db list 2019-02-28 10:04:44 +01:00
ThomasV
cb9dcb8e26 load wallet plugin in Wallet factory, fix trustedcoin wizard on kivy 2019-02-28 09:02:58 +01:00
ThomasV
d73f7a2c10 jsondb: convert lists to sets 2019-02-28 09:02:58 +01:00
SomberNight
4b36114d0d small fixups 2019-02-28 09:02:58 +01:00
ThomasV
791e680a96 abstract database away from wallet and address_synchronizer 2019-02-28 09:02:58 +01:00
ThomasV
7f2083f667 separate storage and database (JsonDB) 2019-02-28 09:02:58 +01:00
ThomasV
d5790ea109 wizard: do not use on storage object during wallet creation 2019-02-28 09:02:58 +01:00
SomberNight
d6c2a0af94
exchange_rate: small fixups. BitcoinAverage, BitStamp
- BitcoinAverage seems to have historical rates for all currencies it supports
(as in, if there is spot price, there is also history).
- BitStamp now uses v2 API, also has support for EUR.
- Bitcointoyou does not seem to actually offer histories
(and `request_history` was undefined anyway)
- regenerate currencies.json
2019-02-27 23:49:25 +01:00
SomberNight
4b3a285871
exchange_rate: some clean-up and fixes
- generation of currencies.json was broken
- removed some dead exchanges
2019-02-27 21:48:33 +01:00
SomberNight
a2047e2c68
tests: base43/base58 encoding/decoding 2019-02-25 19:17:38 +01:00
SomberNight
072ce9c7ac
do not raise BaseException 2019-02-25 16:40:51 +01:00
ghost43
6617307730
Merge pull request #5118 from SomberNight/trezor_init_20190213
Trezor: implement "seedless" mode
2019-02-22 18:59:52 +01:00
SomberNight
11733d6bc2
wizard: normalize bip32 derivation path
so that what gets put in storage is "canonical"
(from now on... we could storage upgrade existing wallets
but it's not critical)
2019-02-22 18:50:29 +01:00
SomberNight
85a7aa291e
bip32: refactor whole module. clean-up. 2019-02-22 18:50:24 +01:00
SomberNight
b39c51adf7
mv "electrum seed" stuff from bitcoin.py to mnemonic.py 2019-02-22 18:01:54 +01:00
SomberNight
e7f38467d7
move opcodes to bitcoin.py 2019-02-22 17:39:58 +01:00
SomberNight
c03c17f1c7
transaction: replace custom enum type for opcodes with stdlib enum
based on Electron-Cash/Electron-Cash@99e60b4941
2019-02-22 16:52:08 +01:00
SomberNight
9dedf51afd
trustedcoin: nicer 'ErrorConnectingServer' exception 2019-02-21 23:07:19 +01:00
SomberNight
4ef3eda8da
util: mv create_URI to create_bip21_uri, and small clean-up 2019-02-20 21:19:03 +01:00
SomberNight
40bf049c82
commands: introduce 'removelocaltx'
see #5137
2019-02-20 18:01:43 +01:00
SomberNight
8e6904c7b7
wallet: default to get_addresses() in get_history()
get_addresses() is a superset of history.keys()
(was missing some local transactions in the output of get_history())
2019-02-20 17:52:43 +01:00
Jean P
7618693ca9 Add support for Ledger Nano X and future devices (#5140) 2019-02-20 13:46:26 +01:00
SomberNight
2d6a68545b
qt installwizard: fix small regression
related: #5139 and 8f4967f7d0
2019-02-19 15:40:29 +01:00
SomberNight
d4e209dc3a
trustedcoin: print messages in both direction when debugging 2019-02-18 18:03:42 +01:00
SomberNight
0bf0b1d20b
interface.is_server_ca_signed: don't rely on assert 2019-02-18 18:00:54 +01:00
SomberNight
2f11216986
fix get_fee_text for static fees
mismatching units
probably only affects kivy gui; when using static fees
2019-02-18 17:52:50 +01:00
SomberNight
0de954546a
test python version in main script
for better error text on old python

related: #5008, #5129
2019-02-15 22:06:10 +01:00
SomberNight
92bf409bf0
scripts: add "quick_start" to showcase some basic functionality 2019-02-15 21:14:08 +01:00
ThomasV
25a460f855 Merge branch 'network_walk' 2019-02-15 17:37:59 +01:00
ThomasV
52520490c5 improve network send_multiple_requests 2019-02-15 17:37:15 +01:00
SomberNight
5313591c28
synchronizer: disconnect from server if cannot deserialize txn 2019-02-15 17:22:24 +01:00
SomberNight
7b8114f865
synchronizer: allow server not finding txn sometimes
User has wallet file with history that includes some txid; corresponding
raw tx is not in the "transactions" dict in the file however.
When the synchronizer starts up, it requests this "missing" txn from
the server... but what if the server does not know about it?
Maybe it was reorged and is not in the new best chain,
and not even in mempool. This was not handled previously.

fix #5122
2019-02-14 20:54:55 +01:00
ThomasV
f60d1e7cb6 fix variable name in contrib/sign_version 2019-02-13 23:25:58 +01:00
ThomasV
c725c05947 update make_download 2019-02-13 23:19:34 +01:00
ThomasV
87c596fa1d prepare release 3.3.4 2019-02-13 22:23:30 +01:00
ThomasV
0e78e15fa4 update locale submodule 2019-02-13 22:20:56 +01:00
ThomasV
4e9dd679ab add contrib/sign_version 2019-02-13 21:46:48 +01:00
SomberNight
5387c6d5f6
trezor: during device init hide some options behind an "expert" button 2019-02-13 20:35:24 +01:00
SomberNight
7bbec04a06
trezor: implement "seedless" mode (option during initialization) 2019-02-13 20:35:23 +01:00
SomberNight
2867c2ef7a
update release notes 2019-02-13 20:23:54 +01:00
SomberNight
ec86850a2e
trezor: fix minor error handling issue
AttributeError: 'TrezorFailure' object has no attribute 'message'
2019-02-13 18:33:48 +01:00
SomberNight
e2eb051eed
keystore bip39: minor clean-up 2019-02-13 15:03:32 +01:00
ghost43
c8562f5362
network: reintroduce network.debug (#5093)
network.debug and interface.debug were removed during the asyncio-aiorpcx
network-rewrite.
2019-02-12 20:23:43 +01:00
SomberNight
086372f68a
wallet get_full_history: add from/to_height info to summary 2019-02-12 19:38:15 +01:00
SomberNight
019884a98b
network: follow-up 38ab7ee554 2019-02-12 19:23:58 +01:00
SomberNight
2174fc0676
cli history: add option to filter by block height 2019-02-12 18:38:35 +01:00
SomberNight
38ab7ee554
network: catch untrusted exceptions from server in public methods
and re-raise a wrapper exception (that retains the original exc in a field)

closes #5111
2019-02-12 17:02:15 +01:00
SomberNight
fd62ba874b
kivy: rm dead code 2019-02-11 20:22:03 +01:00
SomberNight
026448837f
no more "import *"
fixes #5101
fixes #5105
2019-02-11 20:21:24 +01:00
SomberNight
8072ad1ad9
network broadcast_transaction: make error text clearer 2019-02-11 16:36:01 +01:00
SomberNight
ebeed4736f
qt utxo_list: show full prevout_n in outpoint column
previously, if prevout_n was >=10, the ":" char or even digits were cut
2019-02-10 21:20:44 +01:00
SomberNight
c23b869d3c
qt MyTreeView subclasses: change "headers" from list to dict 2019-02-10 21:13:53 +01:00
SomberNight
5aafcb2875
qt MyTreeView subclasses: use IntEnum for columns 2019-02-10 21:00:08 +01:00
SomberNight
cd097d6bb8
qt history list: update_tx_mined_status was not updating 'date' for tx
fixes #5096
2019-02-10 06:36:58 +01:00
ThomasV
eb96d422f7 import version module 2019-02-09 12:15:46 +01:00
SomberNight
d78537c8c4
rerun freeze_packages 2019-02-08 19:19:02 +01:00
SomberNight
df9087970b
update block header checkpoints 2019-02-08 16:38:59 +01:00
SomberNight
699562c78d
bump libsecp256k1 version 2019-02-08 16:17:52 +01:00
ThomasV
01eaf0fe4e
Merge pull request #5052 from JeremyRand/utxolist-for-loop
Refactor for loop in UTXOList
2019-02-08 15:53:28 +01:00
ThomasV
b06b8753e6 fix #5088 2019-02-08 12:59:06 +01:00
ThomasV
1da1f0bfea fix #4984 2019-02-08 11:17:48 +01:00
ThomasV
8f4967f7d0 qt wizard: select_storage 2019-02-08 09:10:07 +01:00
ThomasV
beb9f63274 follow-up prev 2019-02-08 08:27:23 +01:00
ThomasV
58c2c15266 follow up 6fb974227b 2019-02-08 08:21:18 +01:00
SomberNight
fc72e661de
requirements: set min version for aiohttp 2019-02-08 02:24:44 +01:00
SomberNight
89bb49e117
mac build: install pinned pip and setuptools earlier
also add --no-use-pep517 option for pyinstaller (see 4b560250a6)
2019-02-07 20:19:07 +01:00
SomberNight
2c71b9da0c
icons: instead of symlinks, just mv "icons" dir
symlinks are really inconvenient on Windows
(when running from cloned source)

follow-up #5055
2019-02-07 20:01:52 +01:00
SomberNight
9beabc0311
fix prev: run make_locale before building android apk 2019-02-07 17:57:15 +01:00
SomberNight
ba08b2279d
kivy build: test and document that make_locale is to be run first 2019-02-07 16:45:09 +01:00
ThomasV
6fb974227b fix #5082 2019-02-07 13:56:11 +01:00
ThomasV
6ade5903dc network: fix send_multiple_requests 2019-02-07 13:30:14 +01:00
SomberNight
7bb3e5336a
trezor: PIN could not be disabled
fixes #5078
2019-02-06 15:50:57 +01:00
ThomasV
4ed8787433 remove 'util.py' from scripts 2019-02-05 20:33:50 +01:00
ThomasV
8f2a730b3b add more details values to history 2019-02-05 18:27:01 +01:00
SomberNight
d6986347e6
qt icons: update remaining QIcon() constructors
follow-up #5053
2019-02-05 00:59:09 +01:00
SomberNight
a5ddb42bfd
win/mac binaries: fix qt icons
follow-up #5055
2019-02-04 22:34:59 +01:00
SomberNight
2de7fd5466
wine build: small clean-up in prepare-wine.sh 2019-02-04 20:27:04 +01:00
SomberNight
001b815c18
wine build: upgrade wine, nsis, python
wine-specific hack no longer needed with new wine version
2019-02-04 20:27:04 +01:00
SomberNight
5a1778b7fe
start using util.resource_path 2019-02-04 20:27:03 +01:00
SomberNight
67d080b34a
mv qt update checker to its own file 2019-02-04 20:27:03 +01:00
SomberNight
6926b8b2d4
qt update checker: handle --offline 2019-02-04 20:27:02 +01:00
SomberNight
68cd37282e
qt: set default "window icon" (only visible on Windows) 2019-02-04 20:27:02 +01:00
SomberNight
9e58d56e6d
qt qrwindow: rm dead code 2019-02-04 20:27:01 +01:00
SomberNight
8412b53ed5
wizard: copy/restore storage when stepping through the wizard
When interacting with wizard, there is a single shared storage instance.
If you go down the tree of dialogs, press "back" a couple times, go
down another branch of dialogs, etc, there are side-effects on storage,
which are never undone.

fixes #5057
fixes #4496
2019-02-04 20:27:01 +01:00
SomberNight
9013f6d59e
wizard: make 'stack' private 2019-02-04 20:27:00 +01:00
SomberNight
bc2a421d87
network: clean up broadcast_transaction
Handle all exceptions in network.py, instead of in gui code.
Send some exceptions to crash reporter; previously gui code
would suppress them.
2019-02-04 20:27:00 +01:00
ThomasV
76ff2f53c5
Merge pull request #5042 from SomberNight/appimage
binaries for Linux: AppImage
2019-02-04 19:16:15 +01:00
SomberNight
66de511828
travis: build appimage for linux 2019-02-03 23:45:34 +01:00
SomberNight
a754f9fe10
initial commit for building AppImages for Linux x86_64 2019-02-03 23:45:30 +01:00
SomberNight
47b07f19b9
build: factor out some utilities to build_tools_util.sh 2019-02-03 23:44:34 +01:00
SomberNight
ca931f476f
fix android build: pin buildozer and pin kivy.
old p4a did not work with new buildozer. kivy master crashes.
kivy latest release has runtime issues (orientation was landscape).
these versions seem to work.
also updated dockerfile to more closely match p4a master.
2019-02-03 23:40:49 +01:00
SomberNight
43487910c7
qt network dialog: use intenum for columns 2019-02-03 20:04:33 +01:00
ThomasV
905e271db9 remove phishing server 2019-02-03 12:28:59 +01:00
SomberNight
52d602b6c1
network: fix get_servers to not modify default list 2019-02-02 20:10:12 +01:00
SomberNight
add3b36f32
build: replace remaining "python setup.py install" with "pip install" 2019-02-02 08:07:48 +01:00
ThomasV
819c3b81e3
Merge pull request #5055 from SomberNight/fix_qt_icons_when_pkg_installed
fix: qt icons not available when installed as python package
2019-02-02 06:26:47 +01:00
SomberNight
4fa87d8595
fix: qt icons not available when installed as python package
follow-up #5053
2019-02-01 23:32:24 +01:00
SomberNight
7ea01e9e91
qt inline icons: change mouse-over cursor 2019-02-01 21:57:18 +01:00
ghost43
7266ecc2b8
contrib/make_tgz: small improvements. (#5040) 2019-02-01 21:20:45 +01:00
ThomasV
f846d1d59a
Merge pull request #5039 from SomberNight/tx_version_bump_to_2
transaction: change default version to 2
2019-02-01 20:48:18 +01:00
ThomasV
f05aabd802
Merge pull request #5053 from SomberNight/qt_icons_file_rm
rm qt icons file
2019-02-01 20:47:16 +01:00
SomberNight
16bac5fd73
rm qt icons file
so we don't need pyrcc5, which is not deterministic,
and so we don't need the submodule for the icons

based on electrumsv/electrumsv@bf8802c2ea
2019-02-01 20:15:28 +01:00
SomberNight
185d02d9df
delete snap file
we don't distribute snaps; not sure if the file is useful for anyone
it's already not working because it references python 3.5
2019-02-01 19:46:20 +01:00
SomberNight
3ad6f738bd
util: rm hfu, cleaner bh2u 2019-02-01 19:02:02 +01:00
JeremyRand
8716bc8cfb
Refactor for loop in UTXOList
This refactor makes UTXOList somewhat easier to subclass.
2019-02-01 06:42:57 +00:00
SomberNight
7f3de8241c
qt/hww: temporarily bundle our own version of safetlib.qt.pinmatrix
until safetlib releases a new version that includes b1eab3dba4

closes #4960
2019-01-31 20:58:04 +01:00
SomberNight
e7d3fd32fb
contrib/freeze_packages.sh: should hard fail if there is an error
exceptions raised by find_restricted_dependencies.py were getting ignored
2019-01-31 19:44:04 +01:00
SomberNight
3ca1b710d6
build: use sha256sum instead of md5sum 2019-01-31 17:01:00 +01:00
SomberNight
d4967faf28
wine build: pin wine signing key. minor refactoring. 2019-01-31 16:37:27 +01:00
SomberNight
c399693049
qt contact list: context menu fixups
fixes #5048
fixes #5049

follow-up 9cff42328d
2019-01-31 12:13:31 +01:00
SomberNight
9bbea9bf2f
wallet: implement wait_for_address_history_to_change API 2019-01-30 21:30:25 +01:00
SomberNight
55e6830cfc
android build: update pinned python-for-android. use newer google NDK.
fixes #5045
2019-01-30 19:30:36 +01:00
SomberNight
0f0cee422e
trezor and clones: sign tx version too 2019-01-30 12:03:52 +01:00
SomberNight
27299092df
hardware cmdline handler: print messages to stderr (take 2)
follow-up 5613f9b903

button_request should not call show_error as error dialogs in Qt block
the GUI thread.
2019-01-29 17:25:02 +01:00
ThomasV
0429fe5960
Merge pull request #5031 from spesmilo/daemon_error_forwarding
daemon: forward TypeError trace to client
2019-01-29 09:37:30 +01:00
Janus
2737744bfe daemon: forward TypeError trace to client 2019-01-29 02:07:10 +01:00
SomberNight
4c6379a936
linux sdist: try to exclude some more garbage files from release tarball 2019-01-28 18:15:05 +01:00
SomberNight
dcd3d6c3d1
linux sdist: rm dead file from MANIFEST.in 2019-01-28 17:36:47 +01:00
SomberNight
15bc097f55
gitignore: update old path 2019-01-28 17:35:52 +01:00
SomberNight
501e725a47
kivy readme: note about running on linux desktop
closes #5037
2019-01-28 15:26:46 +01:00
SomberNight
d820f9ad37
transaction: change default version to 2 2019-01-28 15:11:03 +01:00
duckartes
f9b0c66843 Update: README.rst (#5036)
Correct filename paths under Creating Binaries.
2019-01-27 18:15:37 +01:00
SomberNight
31c08db909
qt update notifications: make url clickable; prevent multiple dialogs 2019-01-27 14:42:37 +01:00
ThomasV
138c98d7d8 add 'get' command to CLI 2019-01-26 16:50:51 +01:00
SomberNight
53310690a5
version notifications: sig check would always fail on testnet 2019-01-26 15:30:30 +01:00
SomberNight
b085d7cc59
document linux release process
closes #5030
2019-01-25 19:14:28 +01:00
ThomasV
0f0bdfe7f0 update release notes 2019-01-25 19:09:29 +01:00
ThomasV
c4774d7a8e update submodules 2019-01-25 18:46:29 +01:00
SomberNight
0bfda7c8c7 validate version update announcements using "bitcoin address" message signing 2019-01-25 18:16:56 +01:00
Johann Bauer
34c99c3b36 [Qt] Add optional update notifications 2019-01-25 18:16:32 +01:00
ThomasV
5613f9b903 hardware cmdline handler: print messages to stderr 2019-01-25 18:16:03 +01:00
SomberNight
4b560250a6
fix wine build: pyinstaller failed to install with new pip
see pypa/pip#6163
2019-01-25 16:46:17 +01:00
ThomasV
4be4444d6b prepare release 3.3.3 2019-01-25 12:06:19 +01:00
sajolida
889d133abd Give visual feedback while starting (#4997)
See https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
2019-01-25 02:33:54 +01:00
SomberNight
5f05469852
rerun freeze_packages
closes #4993
closes #5006
2019-01-25 02:06:30 +01:00
ThomasV
54eb89ccaf
Merge pull request #5026 from JeremyRand/utxolist-headers
Refactor UTXOList headers into class attribute
2019-01-24 13:47:58 +01:00
JeremyRand
b3d8a81e15
Refactor UTXOList headers into class attribute
This makes it more straightforward to subclass UTXOList.
2019-01-24 00:09:36 +00:00
ThomasV
920d4c2b27 simplify qr window 2019-01-23 17:17:13 +01:00
ThomasV
f994cd4a5d draw qrcode with fixed framesize 2019-01-23 16:56:29 +01:00
SomberNight
f2ad116b0b
wizard: better hww debug messages when unpaired_device_infos fails
[DeviceMgr] scanning devices...
[DeviceMgr] failed to create client for ledger at b'0002:0007:00': OSError('open failed',)
[DeviceMgr] error getting device infos for ledger: open failed

^ GUI did not contain any info about failure
2019-01-21 18:44:36 +01:00
SomberNight
5fc715cdee
commands: add convert_xkey for converting between {x,y,z}|{pub,prv} 2019-01-20 15:49:42 +01:00
SomberNight
9cff42328d
qt contact list: fix copying address, sort order
closes #5015
2019-01-19 23:11:21 +01:00
SomberNight
e39e2ed8f1
fix typo
follow-up #5011
closes #5014
2019-01-19 17:50:22 +01:00
ThomasV
c995d5364e
Merge pull request #5011 from SomberNight/tx_broadcast_sanitize_response
network: sanitize tx broadcast response
2019-01-19 11:27:48 +01:00
SomberNight
5403ae7687
network: sanitize tx broadcast response 2019-01-18 20:25:21 +01:00
SomberNight
7ffd928e80
wallet: add comment 2019-01-17 17:19:08 +01:00
SomberNight
d77e4d8f5d
exception formatting: use repr(e) instead of str(e) in messages
repr(e) is more useful
2019-01-17 17:16:19 +01:00
SomberNight
44a2ceab3c
qt history list: fix minor sorting issue
closes #4989
2019-01-17 17:09:22 +01:00
ThomasV
4e10f9e301
Merge pull request #5007 from cculianu/fix_codesign_electrum
[MacOS] Fixed code signing on macos to code sign *all* embedded binaries
2019-01-17 16:22:31 +01:00
SomberNight
c7f3adb67e
trezor: fix minor string formatting re translations
closes #4996
2019-01-16 19:11:04 +01:00
ghost43
dc19cf1fa1
wallet: randomise locktime of transactions a bit. also check if stale. (#4967) 2019-01-16 18:51:59 +01:00
chris-belcher
d5c8a0e0d0 Add flag --skipmerklecheck (#4957)
The --skipmerklecheck optional flag makes Electrum tolerate invalid
merkle proofs from the server. This is useful for building Electrum
servers that need a minimum amount of storage, though of course users
should only enable it if they completely trust the connected server.
2019-01-16 18:48:10 +01:00
Calin Culianu
5ec330680e [MacOS] Fixed code signing on macos to codesign all embdedded binaries
This was pulled from Electron Cash #1110
2019-01-16 10:58:44 +02:00
Johann Bauer
0caf8e30cd Revealer: Fix typo 2019-01-12 22:06:47 +01:00
Johann Bauer
019566b383 Change string formatting to improve translation
For example, "Hide Console" would be "Konsole anzeigen" in German.
Currently, translators can only show "Anzeigen Konsole" which doesn't
make much sense.
2019-01-12 21:58:21 +01:00
Romano
c60583293a [Docker] fix gnupg2 and dirmngr 2019-01-12 21:32:52 +01:00
Johann Bauer
424430723b
[Revealer] Fix spelling mistake 2019-01-08 17:22:53 +01:00
SomberNight
2f789468ea
requirements: lower bound for btchip-python 2019-01-07 11:00:50 +01:00
Tom Kneiphof
8fd84f77c7 Fix ledger transaction version (#4991) 2019-01-07 10:49:10 +01:00
SomberNight
4d0030363b
interface: catch more SOCKS exceptions 2019-01-04 11:00:48 +01:00
SomberNight
fd5ad9ac70
qt network dialog: detect Tor proxy dynamically
keep thread running to detect changes
2019-01-04 10:58:59 +01:00
SomberNight
192ec8596d
trezor: fix matrix recovery
closes #4983
2019-01-04 10:15:26 +01:00
Johann Bauer
bde655ae00 Qt: Show pointer cursor for status bar buttons 2019-01-01 20:39:27 +01:00
ThomasV
549d71fb0b complete release notes 2018-12-28 13:43:18 +01:00
SomberNight
bd1f7b539e
qt: don't import PyQt5.Qt
related #4960
2018-12-26 03:58:50 +01:00
SomberNight
ccec45a564
qt: fix address list context menu race
closes #4961
2018-12-24 19:03:10 +01:00
SomberNight
0bce96d2de
qt crash report: fix formatting
follow-up 5dc240d4ed
2018-12-24 18:52:03 +01:00
ThomasV
5469e3668e fix #4958 2018-12-23 10:02:19 +01:00
SomberNight
dac5af8eca
trustedcoin: friendlier error msg for invalid otp when signing 2018-12-22 09:06:30 +01:00
ThomasV
30845ee776 prepare release 3.2.2 2018-12-21 22:41:12 +01:00
ThomasV
649f8414a9 update icons submodule 2018-12-21 22:32:57 +01:00
Johann Bauer
d0a1bae68f [Qt] Fix wrong orientation of finger in clock icons
Closes: #4954
2018-12-21 21:57:23 +01:00
SomberNight
5dc240d4ed
qt: show_message and friends display plaintext by default 2018-12-21 20:46:47 +01:00
SomberNight
5248613e9d
gui: prepend broadcast_transaction errors with explanatory message 2018-12-21 20:44:38 +01:00
SomberNight
b491a30dd9
kivy network dialog: update server and proxy properly
Previously "proxy" would only get updated when closing and reopening
the network dialog. "server" would only get updated after successful
connection establishment to specified server.
2018-12-21 16:59:43 +01:00
SomberNight
1d303fa9d2
win build: rm win_inet_pton
was needed by PySocks; and we no longer use PySocks
also, it seems the functionality it provided is now part of Python stdlib since 3.4
https://docs.python.org/3/library/socket.html#socket.inet_pton

related: #2358
2018-12-20 18:06:16 +01:00
SomberNight
744bfc1eeb
util.profiler: simplify
follow-up 6192bfce46
closes #4904
2018-12-20 17:09:58 +01:00
ghost43
7773443c17
network: put NetworkTimeout constants together (#4945)
* network: put NetworkTimeout constants together

* fix prev
2018-12-20 16:49:17 +01:00
SomberNight
43461a1866
qt history: fix exporting history
closes #4948
2018-12-20 16:46:58 +01:00
ThomasV
85b712967f prepare release 3.3.1 2018-12-20 13:24:35 +01:00
ThomasV
b1b6b250d1 kivy: do not request PIN for watching-only wallets 2018-12-20 13:23:46 +01:00
ThomasV
2e078493a7 kivy: improve context menu 2018-12-20 12:43:31 +01:00
ThomasV
96b66b7e4f kivy: use on_state instead of on_release 2018-12-20 12:19:54 +01:00
ThomasV
58a9fa0ad5 kivy: use default scroll_distance and scroll_timeout 2018-12-20 11:32:01 +01:00
ghost43
8e5331e5b2
Merge pull request #4932 from SomberNight/revealer_cleanup_20181215
revealer: clean-up, allow restoring v0
2018-12-20 04:24:21 +01:00
SomberNight
caae9f8a6a
revealer: warning re version 0 now includes URL 2018-12-20 04:21:40 +01:00
SomberNight
1b7672f70e
qt: fix invoices tab
closes #4941
2018-12-20 01:09:16 +01:00
SomberNight
fc18912ecd
release notes: mention 2fa, shorten qt 2018-12-19 21:24:38 +01:00
SomberNight
4225e79456
win build: wine upstream gpg key weirdness 2018-12-19 21:22:41 +01:00
ThomasV
df15571b82 osx build: revert to python 3.6.4 2018-12-19 18:37:44 +01:00
ThomasV
383b517405 update submodule (follow-up prev commit) 2018-12-19 18:35:15 +01:00
ThomasV
1ee8d6a380 Merge branch 'master' of github.com:spesmilo/electrum 2018-12-19 18:28:53 +01:00
ThomasV
b2d635060c update submodule 2018-12-19 18:28:34 +01:00
SomberNight
d5591da682
qt history: consider column is hidden in context menu 2018-12-19 17:01:20 +01:00
SomberNight
f0f73380a2
qt history: fix refresh bug ("verified"/fee histogram interplay) 2018-12-19 16:47:26 +01:00
ThomasV
0247802479 update submodules 2018-12-19 13:29:50 +01:00
ThomasV
4360d07918
Merge pull request #4938 from SomberNight/mac_build_qr_scanner_old_xcode
mac: build qr scanner using old xcode
2018-12-19 12:03:41 +01:00
SomberNight
ba33bc4ad8
plugins: fix hook/attr name collision in close()
Revealer plugin has method "password_dialog"
"password_dialog" is also a hook name, but revealer.password_dialog is not a hook
2018-12-19 02:10:47 +01:00
SomberNight
bec1860197
mac build: build qr scanner on separate machine 2018-12-18 22:05:44 +01:00
SomberNight
f54c387172
mac build: bump python version 2018-12-18 22:05:42 +01:00
SomberNight
f160f4bf67
mac build: use old xcode to build qr scanner on El Capitan 2018-12-18 22:05:02 +01:00
SomberNight
fa33d1880c
win build: bump python version 2018-12-18 21:41:29 +01:00
SomberNight
ba4af29bf8
rerun freeze_packages 2018-12-18 21:01:16 +01:00
SomberNight
8d1cb3c36a
bump pyqt version in binaries
closes #4777
2018-12-18 20:52:01 +01:00
SomberNight
8f5f0e46aa
keystore: fail sooner if unsupported version
follow-up #4937
2018-12-18 19:57:58 +01:00
ThomasV
fa389a4e06
Merge pull request #4937 from SomberNight/revert_password_v2
keystore: revert KDF change
2018-12-18 19:35:57 +01:00
SomberNight
0c9a03ac54
keystore: revert KDF change from #4838
making the KDF expensive is blocked on #4909
2018-12-18 15:37:29 +01:00
SomberNight
c59ac49fea
fix greenaddress plugin: follow-up 75f6ab9133 2018-12-17 13:41:00 +01:00
SomberNight
f0868f5a51
revealer: warning re version 0 vulnerability 2018-12-15 09:26:54 +01:00
SomberNight
e7e9f8e7f2
revealer: fix unlucky hex seed causing crash 2018-12-15 09:05:12 +01:00
SomberNight
f969edcf50
revealer: split some core parts out into separate file
for easier testing
2018-12-15 08:52:00 +01:00
SomberNight
94afd7a9ea
revealer: clean-up noise-generation. support regeneration of v0 again 2018-12-15 08:13:30 +01:00
SomberNight
91ef367176
revealer: fix path madness
don't use translated strings in file system paths!
2018-12-15 01:12:59 +01:00
ghost43
5fd83722d8
Merge pull request #4916 from tiagotrs/master
revealer: new interface
closes #4540 
closes #4676
2018-12-15 01:11:14 +01:00
SomberNight
e1ba962fe1
revealer: clean-up prev and fixes 2018-12-15 01:07:35 +01:00
tiagotrs
ff2cdf9f16
small fixes, simplification/improvement of texts 2018-12-15 00:48:44 +01:00
tiagotrs
b41a83ceda
new hook/interface ref #4540 2018-12-15 00:48:40 +01:00
SomberNight
0657bb1b36
test_wallet_vertical: add segwit 2fa test 2018-12-14 23:01:52 +01:00
SomberNight
664b0c234e
wizard: fix imported address wallets
assertion added in 9350709f13 was failing
2018-12-14 22:50:25 +01:00
ThomasV
1e8b34e63e rerun freeze_packages 2018-12-14 08:57:31 +01:00
ThomasV
27caa683fe kivy: show synchronization status in the balance field 2018-12-14 08:27:03 +01:00
ThomasV
75f6ab9133 rm requests from greenaddress plugin 2018-12-14 07:41:26 +01:00
ThomasV
95cb2fbebf remove requests from requirements 2018-12-14 07:19:26 +01:00
SomberNight
8b775fd24a
contrib: import 'requests' in try-except 2018-12-13 23:25:52 +01:00
SomberNight
78f5afff74
use certifi directly instead of requests 2018-12-13 23:11:59 +01:00
SomberNight
c09ac41b27
ssl: use certifi explicitly for aiohttp and electrum-server connections
fixes ssl issues on Android
2018-12-13 22:54:53 +01:00
ThomasV
7a4270f5a4 Qt: camera icon 2018-12-13 17:21:56 +01:00
SomberNight
67b2aebed6
android build: use rebased p4a fork
86eeec7c19
2018-12-13 16:23:58 +01:00
Calin Culianu
14363f8f2f
[Qt] Got rid of qt.util.Timer class and instead replaced the functionality with the more efficient QTimer. Also added disconnection from the timer on window close.
(cherry picked from 19a21eb08d)
2018-12-13 16:00:44 +01:00
ThomasV
3184d6f369 simplify previous commit 2018-12-13 12:10:36 +01:00
SomberNight
ef94af950c
wallet: try detecting internal address corruption 2018-12-12 20:50:53 +01:00
SomberNight
9bbfd610be
qt: don't flash QWidgets on startup before main window is visible
Consider wallet without password set. Using Qt GUI.
When starting the app, before the main window appears, small artefacts
("minimised" windows?) would appear very briefly and then disappear.
2018-12-12 19:58:13 +01:00
SomberNight
363dd12a2a
qt: try even harder not to crash whole app on first start 2018-12-11 21:29:23 +01:00
ThomasV
dd848304e6
Merge pull request #4880 from spesmilo/2fa_segwit
2fa segwit (from ghost43's PR)
2018-12-11 18:33:41 +01:00
ThomasV
4681ac8c23 CLI deserialize: always force full parse 2018-12-11 13:58:05 +01:00
ThomasV
502a4819b6 trustedcoin: do not set wallet.plugin in constructor 2018-12-11 13:08:10 +01:00
ThomasV
467e40b555 trustedcoin: serialize using PARTIAL_TXN_HEADER_MAGIC 2018-12-11 11:46:31 +01:00
ThomasV
040b5b3f88 trustedcoin: fix get_xkeys 2018-12-11 09:59:39 +01:00
SomberNight
84519752c3 trustedcoin: fix prev. remove temp xpubs. 2018-12-11 09:28:35 +01:00
ThomasV
852f2a0d65 trustedcoin: do not require wallet file upgrade 2018-12-11 09:28:35 +01:00
SomberNight
7b90d69443 trustedcoin: p2wpkh billing addresses 2018-12-11 09:28:35 +01:00
SomberNight
eeea4fcb31 rename 2fa non-segwit type to "legacy 2fa" and make segwit the default 2018-12-11 09:28:35 +01:00
ThomasV
df59a43300 fix test 2018-12-11 09:28:35 +01:00
ThomasV
5a93bf054e 2fa segwit (from ghost43's PR) 2018-12-11 09:28:35 +01:00
SomberNight
0ec7005f90
qt history: data() should return QVariant
the docs says so,
and also HistoryList.create_menu() was crashing sometimes re "Copy {}"
2018-12-10 19:42:31 +01:00
SomberNight
4e7b2f3ea3
qt history: use IntEnum for column indices 2018-12-10 19:25:38 +01:00
ghost43
53b64a6367
Merge pull request #4915 from spesmilo/qabstractitemmodel
use QAbstractItemModel in History tab
2018-12-10 18:10:00 +01:00
SomberNight
0ddccd56c7
interface: fix only-genesis regtest case 2018-12-10 17:46:37 +01:00
SomberNight
4791c7f424
qt history: fix toggling fiat capital gains 2018-12-10 16:53:46 +01:00
SomberNight
b0631f90f8
qt history: fix slowness
arghhhhh finalllllllllllly figured it out...
2018-12-10 16:48:55 +01:00
ThomasV
e35ed17200 remove call to undefined method refresh_headers 2018-12-10 13:07:03 +01:00
ThomasV
059fb51893 reintroduce profiler 2018-12-10 10:18:24 +01:00
SomberNight
ca1043ffda
qt history list: hide columns sooner
while wallet was starting up "hidden columns" were visible
2018-12-10 09:31:53 +01:00
SomberNight
5be6966462
qt history list: allow filtering by (partial) txid 2018-12-10 09:31:52 +01:00
SomberNight
0d755b86ab
qt address dialog: HistoryModel needs reference to correct HistoryList
refresh() was hiding/showing the headers of the main HistoryList
2018-12-10 09:31:52 +01:00
SomberNight
a99b92f613
qt history list: optimise fee histogram induced refresh 2018-12-10 09:31:51 +01:00
SomberNight
696db310a5
qt history list: optimise update_item (tx mined status) 2018-12-10 09:31:50 +01:00
SomberNight
b1e15751d6
qt history list: "status"-based sort should also tie-break on height 2018-12-10 09:31:50 +01:00
SomberNight
65e8eef87f
qt history list: use OrderedDictWithIndex for txns 2018-12-10 09:31:49 +01:00
SomberNight
8bb930dd04
fix OrderedDictWithIndex
setitem() would modify the dict of the class. oops.
2018-12-10 09:31:49 +01:00
SomberNight
3c3fac7ca4
qt history list: fix shortcut in refresh() 2018-12-10 09:31:48 +01:00
SomberNight
5e61ad09c1
qt addresses list: fix filtering 2018-12-10 09:31:48 +01:00
SomberNight
48e119b59e
qt history: minor clean-up and sanity checking 2018-12-10 09:31:47 +01:00
SomberNight
e023d8abdd
qt history list: sorting of first column now considers txpos
same block txns were in unnatural order, maybe sort is not stable?
2018-12-10 09:31:47 +01:00
SomberNight
1c0c21159b
qt history list: performance optimisations 2018-12-10 09:31:46 +01:00
Janus
d2ddb255ef
QAbstractItemModel: Release Notes and Address List fiat bug fix 2018-12-10 09:31:46 +01:00
Janus
3960070a50
QAbstractItemModel: fix sorting, QAbstractItemDelegate usage, QVariant usage 2018-12-10 09:31:45 +01:00
Janus
4eb4b341db
QAbstractItemModel: initial version, filter not done 2018-12-10 09:31:39 +01:00
SomberNight
5b9b6a931d
qt network dialog: fix NodesListWidget if there is fork
undo part of 5473320ce4
2018-12-10 08:04:54 +01:00
SomberNight
9607854b67
network: fix switching interface (restart old one)
follow-up b3ff173b45
connection_down was killing the already restarted old interface
2018-12-10 08:03:42 +01:00
SomberNight
62e352a2a8
network: don't let _maintain_sessions die from CancelledError
as then the network would get paralysed and no one can fix it
2018-12-09 20:04:42 +01:00
SomberNight
b3ff173b45
interface: change close() implementation
was getting on lightning branch in some circumstances
RecursionError: maximum recursion depth exceeded while calling a Python object
2018-12-09 20:02:00 +01:00
SomberNight
762082e13d
wine build: dedupe PYTHON_VERSION 2018-12-09 07:17:37 +01:00
ghost43
f59a4f85db
win/mac build: strip parts of pyqt5 from binaries to reduce size (#4901)
When bumping pyinstaller to 3.4, binary sizes had increased drastically.
The main reason seems to be that pyinstaller is pulling in "all" of qt.

based on Electron-Cash/Electron-Cash@4b09969594
2018-12-09 05:09:28 +01:00
benma
6c20340338 bitbox: fix seed command (#4906)
Entropy required to be 64 bytes.
2018-12-08 17:02:24 +01:00
SomberNight
0294844c11
labels plugin qt: only update corresponding window; disconnect signal 2018-12-08 06:56:18 +01:00
SomberNight
258b504000
qt main window: unregister network callbacks 2018-12-08 06:31:28 +01:00
SomberNight
c9482b5ea2
fix prev 2018-12-07 20:59:19 +01:00
SomberNight
c017f788ac
wallet: TxMinedInfo (merged TxMinedStatus and VerifiedTxInfo) 2018-12-07 20:47:28 +01:00
Janus
e1f4865844 digitalbitbox, trustedcoin: proxied http client
use common cross-thread HTTP method, which is put in network.py,
since that is where the proxy is. TrustedCoin tested successfully,
but DigitalBitbox can't be tested completely due to #4903

before this commit, digitalbitbox would not use any proxying
2018-12-07 19:19:40 +01:00
Janus
0169ec880c digitalbitbox: make constant strings 2018-12-07 19:18:33 +01:00
Janus
9a3f2e8fcc digitalbitbox: fix stretch_key bytes/str confusion 2018-12-07 18:41:40 +01:00
SomberNight
31a5d0c2f0
tweak release notes for 3.3 2018-12-07 04:32:17 +01:00
SomberNight
503bd357f4
requirements: bump python-trezor to 0.11 2018-12-07 04:06:51 +01:00
SomberNight
8c3920a0db
hw: check_libraries_available now gets version of incompatible libs
previously we would return early and the user would
just see "missing libraries"
2018-12-06 19:39:58 +01:00
ghost43
1546d65ebe
Merge pull request #4875 from matejcik/trezor-0.11
WIP: Trezor 0.11
2018-12-06 19:38:51 +01:00
SomberNight
20fa7fc2f7
trezor: fix sign_transaction prev_tx 2018-12-06 19:37:12 +01:00
SomberNight
9e86bc586c
trezor: only confirm passphrase when creating wallet
but not when decrypting
2018-12-06 19:37:11 +01:00
SomberNight
605982a2b7
android build: less verbose buildozer logs 2018-12-06 17:25:00 +01:00
SomberNight
2f7573850e
fix prev 2018-12-06 16:05:35 +01:00
SomberNight
8999e92f76
android build: fix warning re ndk_api
"NDK API target was not set manually, using the default of 21 = min(android-api=28, default ndk-api=21)"
2018-12-06 13:43:24 +01:00
SomberNight
a62e5d39ca
android build: add "how to deploy apk on phone" to readme 2018-12-06 05:10:24 +01:00
SomberNight
993374dce7
travis: build android apk 2018-12-06 05:09:08 +01:00
SomberNight
e8a8a17217
test_wallet_vertical: offline sign with old seed 2018-12-05 18:55:19 +01:00
matejcik
8e681c1723 trezor: update name (TREZOR -> Trezor) 2018-12-05 15:44:24 +01:00
matejcik
43acd09df8 trezor: support outdated firmware notifications
Outdated firmware error messages were originally raised from
create_client, which would mean that a client for an outdated device
would not be created.

This had a number of undesirable outcomes due to "client does not exist"
being conflated with "no device is connected".

Instead, we raise in setup_client (which prevents creating new wallets
with outdated devices, BUT shows them in device list), and python-trezor
also raises on most calls (which gives us an error message when opening
wallet and/or trying to do basically anything with it).

This is still suboptimal - i.e., there's currently no way for Electrum to
claim higher version requirement than the underlying python-trezor, and
so minimum_firmware property is pretty much useless ATM.
2018-12-05 14:26:19 +01:00
matejcik
8571cafcc8 trezor: call get_xpub with correct argument
`creating` indicates that this is a new wallet. Which is always the case
in `setup_device`
2018-12-05 14:24:32 +01:00
SomberNight
c3deb16a7d
exchange rate: fix coinbase
closes #4897
2018-12-05 12:26:03 +01:00
SomberNight
cc0db41879
qt history: speed up ensure_fields_available (faster startup) 2018-12-04 22:24:32 +01:00
SomberNight
e35f2c5bed
qt history list: fix #4896 2018-12-04 17:27:02 +01:00
SomberNight
923a9c36cb
util: Satoshis and Fiat should not be namedtuples
undo part of 37b009a342
due to json encoding problems
2018-12-04 16:44:50 +01:00
SomberNight
960855d0aa
wallet history fees: only calculate fees when exporting history
it's expensive, and it slows down startup of large wallets a lot
2018-12-04 16:17:22 +01:00
ThomasV
ebea5b0159 follow-up 5473320ce4: do not call get_full_history in constructor 2018-12-04 12:26:14 +01:00
ThomasV
bd5c82404d do not block load_wallet with watching_only warning 2018-12-04 11:52:31 +01:00
ThomasV
5ae9365f77
Merge pull request #4895 from benma/bitbox
plugins/digitalbitbox: compatibility with firmware v5.0.0
2018-12-04 11:19:00 +01:00
Marko Bencun
92a9cda4fc plugins/digitalbitbox: compatibility with firmware v5.0.0 2018-12-03 22:11:36 +01:00
SomberNight
059beab700
qt history list: small clean-up 2018-12-03 19:12:36 +01:00
SomberNight
ea235a1468
qt dark theme: use correct QR code icon (light/dark) 2018-12-03 17:51:05 +01:00
matejcik
8973bb6f71 Merge branch 'master' into trezor-0.11 2018-12-03 17:00:22 +01:00
SomberNight
d69ef890c0
downgrade qdarkstyle for now
see ColinDuquesnoy/QDarkStyleSheet#123
2018-12-03 16:04:17 +01:00
Janus
0677ce6d52 qt: avoid app.palette().text().color(), doesn't work on dark style 2018-12-03 15:54:21 +01:00
Janus
72957f4d51 qt_standardmodel: only use proxymodel when appropriate 2018-12-03 15:35:54 +01:00
Janus
5473320ce4 qt: use QStandardItemModel 2018-12-03 15:35:54 +01:00
SomberNight
9350709f13
wallet creation: take care not to write plaintext keys to disk
when creating imported privkey wallets the privkeys
were written to disk unencrypted first, then overwritten with ciphertext
2018-12-03 13:02:14 +01:00
SomberNight
ff454ab29d
cli restore: fix imported privkeys with password
closes #4894
2018-12-03 12:46:12 +01:00
ThomasV
e1fb75a81d
Merge pull request #4892 from preserveddarnell/patch-2
Update README.rst
2018-12-03 09:44:53 +01:00
ThomasV
dc46149f1c
Merge pull request #4893 from cculianu/fix_tx_desc_coins_tab
UI: Make Coins Tab -> Details TX Dialog show TX Description (label)
2018-12-03 09:08:30 +01:00
Calin Culianu
4386799fb0 follow-up 2018-12-02 15:20:32 +02:00
Calin Culianu
d2374d62aa UI Pet Peeve: Make Coins Tab -> Details pop up a tx dialog that actually includes the tx description as seen in UTXOList (if available) 2018-12-02 14:53:44 +02:00
Ken
2f4b9aa1f0
Update README.rst 2018-12-01 21:28:46 -05:00
SomberNight
74f6ac27af
wizard/hw: cap transport string
follow-up 32af83b7ae
2018-11-30 20:45:54 +01:00
Janus
ec5f406f49 plugins: labels: dump response if malformed sync server response 2018-11-30 19:16:07 +01:00
SomberNight
fe6367cbcd
network: validate donation address for server 2018-11-30 18:56:35 +01:00
SomberNight
ed22f968f9
text gui: fix network event handler 2018-11-30 17:18:06 +01:00
SomberNight
73e2b09ba8
blockchain: check best chain on disk is consistent with checkpoints
had a corrupted mainnet datadir that had testnet blockchain_headers file
(I had probably corrupted it myself but electrum could not recover from it)
2018-11-30 16:36:37 +01:00
ThomasV
2484c52611
Merge pull request #4838 from SomberNight/keystore_pw_hash2b
keystore: stronger pbkdf for encryption
2018-11-30 11:48:03 +01:00
ThomasV
1165d3f330 update version number 2018-11-30 11:23:01 +01:00
ThomasV
86e42a9081 release notes for 3.3 2018-11-30 11:22:40 +01:00
SomberNight
bddea809ec
storage/blockchain: use os.replace 2018-11-30 04:08:02 +01:00
ThomasV
863ee984fe wallet: cache NaN coin prices, clear cache on new history 2018-11-29 20:47:26 +01:00
SomberNight
ee287740a7
coldcard: fix p2pkh signing for new fw (1.1.0)
PSBT was serialised incorrectly but old fw did not complain
2018-11-29 20:28:27 +01:00
ThomasV
1253e3db1d
Merge pull request #4873 from SomberNight/android_docker
android docker build
2018-11-29 16:34:49 +01:00
SomberNight
124d2e23b7
fix travis macOS build 2018-11-29 13:24:44 +01:00
ThomasV
f4513c12eb follow-up 2018-11-29 11:47:02 +01:00
ThomasV
f0a59f06cd fix module path 2018-11-29 11:46:34 +01:00
ThomasV
d7bf8826fc rename contrib/build-osx as contrib/osx. Move QRReader submodule there. 2018-11-29 11:39:57 +01:00
Calin Culianu
db89286ec3 [macOS] Added QR scanner facility using platform-native helper app. 2018-11-29 10:15:51 +01:00
SomberNight
d0e6b8c89d
hw: fix passphrase dialog with confirmation
closes #4876
2018-11-28 20:54:57 +01:00
SomberNight
243a0e3cf1
android docker: make_apk optionally takes "release" as arg 2018-11-28 19:40:29 +01:00
SomberNight
505cb2f65d
build-wine: update git version 2018-11-28 16:44:15 +01:00
SomberNight
99325618a6
wallet: add FIXME re fiat coin_price calculation 2018-11-28 15:52:38 +01:00
ThomasV
fc2972e977
Merge pull request #4869 from cculianu/add_macos_codesign
[macOS] Added optional code signing capability to the OSX build scripts.
2018-11-28 14:02:29 +01:00
ThomasV
04571d3b20
Merge pull request #4724 from un1t/master
use system language by default
2018-11-28 13:05:42 +01:00
ThomasV
d062548e41
Merge pull request #4861 from SomberNight/blockchain_fork_ids
blockchain: generalise fork handling and follow most work chain
2018-11-28 12:54:57 +01:00
SomberNight
e12af33626
wallet: cache more in get_tx_fee
closes #4879
2018-11-28 12:35:53 +01:00
SomberNight
4a7ce238fd
qt history list: fix sort order of fiat columns 2018-11-27 21:32:55 +01:00
SomberNight
d4d5e32c91
qt history list: fix Qt.UserRole collision 2018-11-27 21:15:31 +01:00
ThomasV
c5b8706225 simplify test 2018-11-27 18:34:36 +01:00
ThomasV
6bf48d0506
Merge pull request #4872 from spesmilo/qt_fiat_fixes
qt history view custom fiat input fixes
2018-11-27 18:16:05 +01:00
Janus
37b009a342 qt history view custom fiat input fixes
previously, when you submitted a fiat value with thousands separator,
it would be discarded.
2018-11-27 17:00:26 +01:00
matejcik
b040db26a7 drop trezor/client.py from build specs 2018-11-27 16:51:49 +01:00
matejcik
c33c907330 trezor: update to trezor 0.11.0 2018-11-27 15:34:19 +01:00
matejcik
5411ad9633 plugins can also check maximum library version 2018-11-27 15:32:33 +01:00
SomberNight
a34d42492d
android docker build 2018-11-27 03:53:22 +01:00
ghost43
12c6a4043b
Merge pull request #4864 from SomberNight/android_build_2018nov
android: build apk using new python3 p4a toolchain
2018-11-26 22:02:52 +01:00
SomberNight
b21064f16f
android: don't use external storage
so that we don't need the extra permission.
also because phones these days have enough internal storage for
the headers; and maybe it's better even for security reasons to
store it there.
no upgrade path is provided for the headers stored on external storage,
we will litter the filesystem and leave them there. they will be
downloaded again into internal storage.
2018-11-26 17:54:07 +01:00
SomberNight
29b697df1a
android: runtime permission dialog for camera 2018-11-26 17:54:07 +01:00
SomberNight
f095b35663
android: build apk using new python3 p4a toolchain 2018-11-26 17:54:05 +01:00
Calin Culianu
d296a1be65 [macOS] Added optional code signing capability to the OSX build scripts. 2018-11-26 13:36:51 +02:00
SomberNight
a53dded50f
bitcoin: avoid floating point in int_to_hex 2018-11-26 01:34:23 +01:00
SomberNight
d7c5949365
prefer int.from_bytes over int('0x'+hex, 16) 2018-11-26 01:16:26 +01:00
SomberNight
67abea567f
rerun freeze packages 2018-11-22 19:41:06 +01:00
SomberNight
0dd3a58a63
requirements: also accept aiorpcx 0.10.x 2018-11-22 19:37:56 +01:00
SomberNight
f04e5fbed6
crypto: fix pkcs7 padding check
related: ricmoo/pyaes#22

in practice, the only strings we would incorrectly accept are
(certain length of) all zero bytes
2018-11-22 18:21:19 +01:00
SomberNight
65ce3deeaa
blockchain: chain hierarchy based on most work, not length 2018-11-22 17:13:43 +01:00
SomberNight
141ff99580
blockchain.py: generalise fork ids to get rid of conflicts 2018-11-22 16:57:22 +01:00
SomberNight
a8e6eaa247
blockchain: fix difficulty retarget
"target" is a 256 bit int, but the "bits" field in the block headers
that is used to represent target is only 32 bits.
We were checking PoW against the untruncated target value, which is a
slightly larger value than the one that can actually be represented,
and hence we would have accepted a slightly lower difficulty chain
than what the consensus requires.
2018-11-22 16:52:51 +01:00
SomberNight
55963bd092
network: oneserver should be bool
fix #4858
2018-11-20 11:59:06 +01:00
SomberNight
36f64d1ad9
bitcoin/ecc: some more type annotations 2018-11-18 22:07:27 +01:00
SomberNight
5376d37c24
history export: include tx fee
closes #3504
2018-11-18 16:46:07 +01:00
SomberNight
32af83b7ae
wizard/hw: show transport type when listing HWDs 2018-11-16 19:03:25 +01:00
SomberNight
eba97f74b4
decorate some methods with @profiler to debug slow startup 2018-11-16 14:39:22 +01:00
ghost43
4d62963efe
qt: count wizards in progress (#4349)
fixes #4348
2018-11-14 22:39:49 +01:00
SomberNight
f767d41409
tests: spanish test case for mnemonic.py, and refactoring 2018-11-14 18:58:27 +01:00
Calin Culianu
75e30ddc9d Show description (label) in TxDialog screen when opened from History (#4775) 2018-11-14 16:43:58 +01:00
SomberNight
e1c66488b1
paymentrequest: don't show PaymentAck to user
mainly because the main "merchant" using bip70 is bitpay, and they
are failing all the PaymentAcks due to the tx is using RBF...
no need to confuse users.

follow-up 1686a97ece
2018-11-14 16:33:41 +01:00
ThomasV
f7f4fef156
Merge pull request #4827 from SomberNight/android_oneserver
implement oneserver option for kivy
2018-11-14 16:13:05 +01:00
SomberNight
e059867314
paymentrequest: be explicit about only allowing "addresses" 2018-11-14 16:04:43 +01:00
ThomasV
a266de6735 PrintError: display verbosity filter 2018-11-14 13:16:08 +01:00
SomberNight
e1b85327be
transaction: clean-up multisig_script 2018-11-14 00:37:03 +01:00
SomberNight
e04e8d2365
plugins: when loading plugins, use newer importlib mechanism
fixes #4842
2018-11-11 23:55:34 +01:00
SomberNight
48b0de7871
keystore: stronger pbkdf for encryption 2018-11-10 16:36:41 +01:00
SomberNight
aceb022f9d
crypto: more type annotations 2018-11-10 13:30:34 +01:00
SomberNight
a6a003a345
RBF batching: fix logic bug 2018-11-09 22:47:41 +01:00
SomberNight
2ab8234e9c
RBF batching: smarter fee handling 2018-11-09 20:04:06 +01:00
SomberNight
d905f0e55e
RBF batching: for now, let user deal with fee problems (honour slider) 2018-11-09 19:15:46 +01:00
SomberNight
436f6a4870
qt history export: include fiat value in csv 2018-11-09 18:48:12 +01:00
SomberNight
71ac3bb305
RBF batching: some fixes 2018-11-09 17:56:42 +01:00
ThomasV
f55db2f90b
add batch_rbf option to Qt GUI 2018-11-09 17:29:31 +01:00
ThomasV
2b8d801b36 if possible, batch new transaction with existing rbf transaction 2018-11-09 16:33:29 +01:00
SomberNight
bd32b88f62
introduce UserFacingException
we should not raise generic Exception when wanting to communicate with
the user. it makes distinguishing programming errors and messages hard,
as the caller will necessarily need to catch all Exceptions then
2018-11-08 19:46:15 +01:00
SomberNight
dace2e5495
trezor: don't let bridge transport failing block all other transports
[trezor] connecting to device at bridge:hid...
[trezor] connected to device at bridge:hid...
Traceback (most recent call last):
  File "...\electrum\electrum\base_wizard.py", line 255, in choose_hw_device
    u = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices)
  File "...\electrum\electrum\plugin.py", line 501, in unpaired_device_infos
    client = self.create_client(device, handler, plugin)
  File "...\electrum\electrum\plugin.py", line 374, in create_client
    client = plugin.create_client(device, handler)
  File "...\electrum\electrum\plugins\trezor\trezor.py", line 124, in create_client
    client = self.client_class(transport, handler, self)
  File "...\electrum\electrum\plugins\trezor\client.py", line 7, in __init__
    ProtocolMixin.__init__(self, transport=transport)
  File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 444, in __init__
    self.init_device()
  File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 454, in init_device
    self.features = expect(proto.Features)(self.call)(init_msg)
  File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 115, in wrapped_f
    ret = f(*args, **kwargs)
  File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 129, in wrapped_f
    client.transport.session_begin()
  File "...\Python36-32\lib\site-packages\trezorlib\transport\__init__.py", line 42, in session_begin
    self.open()
  File "...\Python36-32\lib\site-packages\trezorlib\transport\bridge.py", line 69, in open
    raise TransportException('trezord: Could not acquire session' + get_error(r))
trezorlib.transport.TransportException: trezord: Could not acquire session (error=400 str=wrong previous session)
[DeviceMgr] error getting device infos for trezor: trezord: Could not acquire session (error=400 str=wrong previous session)
2018-11-08 17:07:05 +01:00
SomberNight
47b6d3c52c
wizard: make native segwit (bech32) the default for bip39/hw 2018-11-08 13:01:40 +01:00
ThomasV
3d4773b161 wizard: make segwit/bech32 the default choice during wallet creation 2018-11-07 15:03:54 +01:00
SomberNight
7d114ff32d
cpfp: don't reuse address 2018-11-07 14:48:33 +01:00
ThomasV
39fb5b8f58 use blockstream.info as default block explorer 2018-11-07 12:38:29 +01:00
neoCogent
6d5b28a9c5 add blockstream.info as explorer option (#4829) 2018-11-07 02:18:00 +01:00
SomberNight
8b61d18a9f
transaction.serialize_output: use namedtuple fields 2018-11-06 17:04:12 +01:00
SomberNight
5d52dc204c
coldcard: fix spending when there is no change
follow-up 9037f25da1
2018-11-06 16:17:18 +01:00
SomberNight
1686a97ece
bip70 PRs: use aiohttp instead of requests. use proxy. small fixes. 2018-11-05 19:31:17 +01:00
SomberNight
1b46866e34
qt: re sweeping, minor clean-up 2018-11-05 01:53:35 +01:00
SomberNight
a89e67eeed
network: trivial clean-up 2018-11-04 19:25:23 +01:00
SomberNight
160bc93e26
implement oneserver option for kivy
closes #4826
2018-11-03 17:21:38 +01:00
SomberNight
7a46bd1089
network: minor clean-up 2018-11-03 17:11:08 +01:00
SomberNight
908c979338
network: update hardcoded mainnet servers 2018-11-02 20:38:26 +01:00
SomberNight
1a5c77aa82
follow-up prev 2018-11-02 20:37:37 +01:00
SomberNight
e37da62a1c
fix most "scripts"
related: #4754
2018-11-02 20:14:59 +01:00
SomberNight
5c4a6c0f2b
rm network.add_job
current implementation is prone to race, and is not used anyway
2018-11-02 16:06:47 +01:00
SomberNight
c2ecfaf239
move event loop construction to daemon 2018-11-01 16:30:03 +01:00
SomberNight
ca8eae919f
daemon: clarify error print 2018-10-31 19:59:07 +01:00
SomberNight
0862fdb9a9
plugins: somewhat clearer exception is loading plugin fails
see #4817 (issuecomment-434778055)
2018-10-31 18:33:28 +01:00
SomberNight
386e0d560e
wizard,hw: tell user about errors during plugin init
see #4817 (issuecomment-434765570)
2018-10-31 17:58:47 +01:00
SomberNight
4f7283a3b0
expose electrum version as __version__ 2018-10-31 16:21:04 +01:00
SomberNight
1c63bca2c7
follow-up prev 2018-10-30 19:19:46 +01:00
SomberNight
5b4fada2a0
fix some network.get_transaction calls
see #4814 (issuecomment-434392195)
2018-10-30 19:07:37 +01:00
SomberNight
f53b480f1c
wallet: more powerful add_input_info
tangentially related: #4814

also recognise that input is_mine if tx was not fully parsed
but we have the prevout UTXO
2018-10-29 21:34:44 +01:00
SomberNight
f819e9b6f4
openalias: minor clean-up 2018-10-29 17:09:23 +01:00
SomberNight
af232223ee
windows build script: add note to build from fresh clone 2018-10-29 15:48:04 +01:00
SomberNight
5e0179dac4
qt console: expose more refs, and fix auto-complete for >2 depth 2018-10-29 00:20:45 +01:00
SomberNight
9037f25da1
kill old-style namedtuples 2018-10-28 00:28:29 +02:00
SomberNight
34569d172f
wallet: make importing thousands of addr/privkeys fast
fixes #3101
closes #3106
closes #3113
2018-10-27 17:36:10 +02:00
SomberNight
917b7fa898
network shutdown safety belts 2018-10-26 22:43:33 +02:00
SomberNight
416b687054
storage: add a sanity check
see #4803
2018-10-26 19:31:20 +02:00
SomberNight
78258a3a95
fix #4802 2018-10-26 18:45:36 +02:00
SomberNight
bcdb0c46fc
update to aiorpcx 0.9 and require it 2018-10-26 17:06:42 +02:00
SomberNight
263c9265ae
rerun freeze packages 2018-10-26 16:56:23 +02:00
SomberNight
92d16e8b10
follow-up prev: unshallow no longer needed 2018-10-26 15:56:08 +02:00
SomberNight
2aefc8440a
travis: make sure to have latest tag
The Win/Mac build scripts name the binaries using "git describe --tags",
so reproducibility requires git to find the latest tag.
By default, Travis uses depth=50.
2018-10-26 15:34:46 +02:00
SomberNight
791e0e1a67
move relayfee and dust_threshold to bitcoin.py 2018-10-25 23:08:59 +02:00
SomberNight
99d18a48f2
types: make some import conditional 2018-10-25 23:01:53 +02:00
SomberNight
082a83dd85
rename crypto.Hash to sha256d 2018-10-25 22:28:24 +02:00
SomberNight
a88a2dea82
split bip32 from bitcoin.py 2018-10-25 22:20:33 +02:00
SomberNight
c61e13c1e9
add more block explorers, and change defaults 2018-10-25 18:27:41 +02:00
Andrew Zhuk
07a06b5d15 Update util.py (#4797)
Adding Bitupper Explorer to the list
2018-10-25 17:09:52 +02:00
SomberNight
361ffc0620
correctly handle bitcoin URIs if GUI is already running
see #4796
2018-10-25 00:18:14 +02:00
SomberNight
0e6160bf2d
follow-up prev: bad idea to eval translated string 2018-10-23 03:01:23 +02:00
SomberNight
b68729115a
qt wallet information: added keystore type 2018-10-23 02:54:54 +02:00
SomberNight
2a60a701bf
qt wallet information: show has_seed and watching_only 2018-10-22 23:47:34 +02:00
SomberNight
2d352bc3f0
transaction.BIP69_sort: use namedtuple fields 2018-10-22 20:43:31 +02:00
SomberNight
c4e09fa874
simplify Plugins constructor 2018-10-22 18:21:38 +02:00
SomberNight
81cc20039e
more type annotations in core lib 2018-10-22 16:41:25 +02:00
SomberNight
6958c0ccc3
config: reject non-json-serialisable writes
see #4788
2018-10-21 14:58:55 +02:00
SomberNight
ef2a6359e4
fix SSL log spam on py3.7
based on kyuupichan/electrumx@83813ff1ac
see pooler/electrum-ltc#191
2018-10-21 03:09:47 +02:00
SomberNight
637e65efe3
network.stop: fix await 2018-10-20 23:17:10 +02:00
ghost43
c47533f6cf
Merge pull request #4784 from Coldcard/fix4729
Fix Coldcard plugin's p2wpkh-p2sh support (see issue #4729)
2018-10-19 22:27:46 +02:00
SomberNight
bf18e2bbc9
follow-up prev 2018-10-19 22:24:42 +02:00
SomberNight
10a4c7a6ed
wallet.mktx: add new args: rbf, nonlocal_only
used on lightning branch
2018-10-19 20:48:48 +02:00
SomberNight
e8bc025f5c
verifier: fix race in __init__ 2018-10-19 18:10:04 +02:00
Peter D. Gray
14b4955a6f
Fix p2wpkh-p2sh support per issue #4729 2018-10-18 12:38:47 -04:00
Calin Culianu
1526fd3722 Removal of macOS Info.plist. It isn't being used by anything. (#4773) 2018-10-14 14:12:25 +02:00
SomberNight
2cc77c1c7d
rm system config sample
system-level configs are no longer supported since 04a1809969
2018-10-14 05:13:56 +02:00
SomberNight
60f8cf665e
dnssec: trivial clean-up 2018-10-14 04:23:42 +02:00
SomberNight
0e59bc1bc5
network: "switch unwanted fork" should check what fork we are on..
follow-up #4767
2018-10-14 04:23:10 +02:00
SomberNight
1af225015a
fix some type annotations involving tuples 2018-10-13 05:16:36 +02:00
SomberNight
7c4d6c6801
fix #4771 2018-10-13 04:22:53 +02:00
SomberNight
5afdc14913
util: small clean-up re format_satoshis
related #4771
2018-10-13 04:21:07 +02:00
SomberNight
8fa6bd2aac
network: add_job 2018-10-12 19:03:36 +02:00
ThomasV
e573c6d385
Merge pull request #4770 from SomberNight/kill_aiosafe
rm aiosafe decorator. instead: log_exceptions and ignore_exceptions
2018-10-12 18:53:29 +02:00
SomberNight
e3b372946a
rm aiosafe decorator. instead: log_exceptions and ignore_exceptions 2018-10-12 18:36:48 +02:00
SomberNight
ab441a507a
readme: use 'python3 -m pip install' to install 2018-10-12 17:02:38 +02:00
SomberNight
372921b423
mv NetworkJobOnDefaultServer to util
break ref cycles
2018-10-12 16:09:41 +02:00
脇山P
9ce3814d8b build-wine: update git version (#4769) 2018-10-12 11:44:34 +02:00
ThomasV
684e69763a
Merge pull request #4767 from SomberNight/auto_jump_forks
network: auto-switch servers to preferred fork (or longest chain)
2018-10-12 10:50:47 +02:00
ThomasV
cd5152a02d
Merge pull request #4765 from SomberNight/cli_restore_cmd
cli/rpc: 'restore' and 'create' commands are now available via RPC
2018-10-12 10:48:09 +02:00
SomberNight
1233309ebd
cli/rpc: 'restore' and 'create' commands are now available via RPC 2018-10-11 20:57:15 +02:00
SomberNight
37206ec08e
network: auto-switch servers to preferred fork (or longest chain)
If auto_connect is enabled, allow jumping between forks too.
(Previously auto_connect was only switching servers on a given fork,
not across forks)
If there is a preferred fork set, jump to that (and stay);
if there isn't, always jump to the longest fork.
2018-10-11 20:07:19 +02:00
SomberNight
1ef804c652
small import clean-up 2018-10-11 16:30:30 +02:00
ThomasV
cd5453e477
Merge pull request #4753 from SomberNight/synchronizer_rewrite
restructure synchronizer
2018-10-10 20:46:17 +02:00
SomberNight
150e27608b
wallet: rm electrum_version field 2018-10-10 20:26:12 +02:00
ThomasV
e975727075 follow-up prev commit 2018-10-10 19:26:02 +02:00
ThomasV
bb9871ded7 simplify prev commit 2018-10-10 19:24:24 +02:00
ThomasV
f037f06e74
Merge pull request #4758 from SomberNight/qt_fork_icon
qt network status: display 'fork' in icon when chain split is detected
2018-10-10 19:18:11 +02:00
SomberNight
87b05e1c9e
network: change broadcast_transaction api
raise exceptions instead of weird return values
closes #4433
2018-10-10 15:56:41 +02:00
ThomasV
c7833b8bc0
Merge pull request #4727 from SomberNight/refresh_gui_f5
qt: refresh gui with "F5"
2018-10-10 10:53:00 +02:00
Felix Yan
ad503daaca Fix some typos in RELEASE-NOTES (#4762) 2018-10-09 16:28:27 +02:00
SomberNight
cc18f66793
network: don't save negative ETA fee estimates
-1 means bitcoind could not give an estimate
2018-10-09 12:03:38 +02:00
Mark B Lundeberg
508793b010
qt transaction_dialog: normal close if user presses Esc
(Electron-Cash/Electron-Cash#890)
2018-10-09 01:14:33 +02:00
SomberNight
dc1a31d802
fix tests
follow-up 70cca3bad9
2018-10-07 18:43:35 +02:00
SomberNight
f3f2534877
qt status: display "loading wallet" temporarily
this will likely only be visible for large wallets;
it gets overwritten by update_status()
2018-10-07 17:59:32 +02:00
SomberNight
70cca3bad9
fix #4759 2018-10-07 17:50:52 +02:00
SomberNight
b37695f9c8
linux launcher madness
see #4300
2018-10-06 01:58:30 +02:00
Johann Bauer
1d7bf698f2
Windows: Update copyright notice in installed apps 2018-10-05 16:39:41 +02:00
SomberNight
decb8bfd52
qt network status: display 'fork' in icon when chain split is detected 2018-10-05 00:16:06 +02:00
SomberNight
d759546b32
qt console: fix word wrap 2018-10-03 18:26:09 +02:00
SomberNight
02f108d927
restructure synchronizer
fix CLI notify cmd. fix merchant websockets.
2018-10-03 17:13:46 +02:00
SomberNight
788b5b04fe
ledger: always use finalizeInput in sign_transaction
related #4749
2018-10-02 15:52:24 +02:00
SomberNight
a61953673a
fees: add 1-2 s/b static options 2018-10-02 15:44:09 +02:00
SomberNight
da9d1e6001
network: ensure there is a main interface
scenario with previous code:
auto_connect enabled, there is only one server in regtest environment.
client started before server; client would not switch to server after it is started.
2018-10-01 18:16:37 +02:00
SomberNight
7dd4032cce
daemon: call self.start in __init__, and allow not to listen on jsonrpc 2018-10-01 17:56:51 +02:00
SomberNight
4653a1007c
daemon: more convenient constructor for scripts 2018-10-01 15:49:26 +02:00
Johann Bauer
3f4e632cc4
Travis: Fix crowdin upload 2018-10-01 13:20:05 +02:00
SomberNight
626828e980
fix sweeping 2018-10-01 05:16:03 +02:00
SomberNight
4d43d12abf
transaction: don't convert p2pk to p2pkh address when displaying
also closes #4742
2018-10-01 04:58:26 +02:00
SomberNight
ab1ec57429
trezor and clones: rm dead code
see Electron-Cash/Electron-Cash#872
see Electron-Cash/Electron-Cash#874
2018-09-30 02:10:17 +02:00
SomberNight
8aebb8249a
keepkey: full segwit support
ported from trezor plugin
needs new fw to work (5.8??)

fixes #3462
2018-09-30 01:29:27 +02:00
SomberNight
70c32590a9
hw plugins: fix only_hook_if_libraries_available
follow-up f9a5f2e183
2018-09-30 00:25:36 +02:00
SomberNight
ce5cc135cd
transaction: make get_address_from_output_script safer
closes #4743
2018-09-29 19:47:55 +02:00
SomberNight
53fd6a2df5
transaction: always sort i/o deterministically
this was previously the caller's responsibility; now it's done implicitly when creating a txn
2018-09-28 19:17:45 +02:00
SomberNight
5e4a4ae16b
minor clean-up (prints/types/imports) 2018-09-28 17:58:46 +02:00
SomberNight
32d5305295
fix daemon.load_wallet 2018-09-28 16:43:25 +02:00
SomberNight
071bc27016
setup.py: rm deprecated 'imp'. dedupe min py version 2018-09-28 02:47:36 +02:00
SomberNight
12e79ecd60
qt tx dialog: make input/output fields expand
based on Electron-Cash/Electron-Cash@169c137211
2018-09-27 21:44:18 +02:00
SomberNight
3e2c5e8656
network.best_effort_reliable: force DC if req times out; retry on new iface 2018-09-27 21:15:07 +02:00
SomberNight
4984890265
follow-up prev: make best_effort_reliable react faster to disconnects 2018-09-27 20:04:36 +02:00
SomberNight
6b8ad2d126
fix some CLI/RPC commands 2018-09-27 18:01:25 +02:00
SomberNight
3b9a55fab4
rerun freeze packages 2018-09-26 19:33:12 +02:00
SomberNight
c4f3fbaca0
labels: fix potential threading issues
also handle --offline
2018-09-25 21:23:44 +02:00
SomberNight
deda6535e0
bump min aiorpcx to 0.8.2 2018-09-25 19:22:37 +02:00
SomberNight
33d14e4238
some import clean-up in qt 2018-09-25 18:15:28 +02:00
SomberNight
9d7cf12244
follow-up prev: fix tests 2018-09-25 17:00:43 +02:00
SomberNight
952e9b87e1
network: clean-up. make external API clear. rm interface_lock (mostly). 2018-09-25 16:44:39 +02:00
SomberNight
7cc628dc79
synchronizer: fix adding duplicate addresses race 2018-09-24 17:37:09 +02:00
Ilya Shalyapin
4c8103af3b move get_default_language to gui.qt.util 2018-09-23 14:11:50 +05:00
SomberNight
3be5b4b00f
network: fix some threading issues 2018-09-20 21:07:31 +02:00
SomberNight
1294608571
synchronizer: offload cpu-heavy address generation to other thread 2018-09-20 20:16:03 +02:00
SomberNight
172ddf4aaf
wallet: synchronize_sequence cleaned up a bit 2018-09-20 20:04:50 +02:00
SomberNight
55b582511e
fix deprecation warnings in regexes 2018-09-20 18:31:17 +02:00
SomberNight
e4fd5ec1ae
tox: add python 3.7 to envlist
previous CI build was complaining
2018-09-20 18:25:46 +02:00
SomberNight
002b8a99e2
synchronizer: make 'add' thread-safe, and some clean-up 2018-09-20 18:11:26 +02:00
SomberNight
eccb8ec2d6
normalize wallet file paths
fix #4020
fix #4126
2018-09-20 01:21:42 +02:00
SomberNight
61b5ce0451
fix import error 2018-09-20 01:20:13 +02:00
SomberNight
d50b36d314
daemon: suppress pop wallet failure
follow-up 3ec0ceba3e
related: #4126
2018-09-20 00:55:09 +02:00
SomberNight
9586157479
qt: refresh gui with "F5" 2018-09-19 22:12:02 +02:00
SomberNight
cedd518aea
mark 'blockchain_headers' file as sparse on windows
based on fyookball/electrum@647a6cc26d
2018-09-19 22:09:54 +02:00
SomberNight
855a70bc66
network: new trigger 'blockchain_updated'
follow-up af63913189
needed to update history tab when new blocks come,
to refresh the number of confirmations (icons/tooltips)
2018-09-19 21:56:09 +02:00
SomberNight
cbd91ba5b1
synchronizer: fix race
The synchronizer would sometimes not send 'wallet_updated' triggers
if it was fast enough to do all the work between two 0.1 sec ticks.
(is_up_to_date() would return True both before and after)
2018-09-19 21:41:10 +02:00
SomberNight
8ee1f140d8
interface: split run_fetch_blocks
The 'continue' in the middle was too easy to miss.
We want a 'network_updated' trigger from every interface,
not just the fastest.
2018-09-19 20:30:54 +02:00
SomberNight
f9a5f2e183
fix #4698 2018-09-19 20:02:03 +02:00
SomberNight
8caab35d90
trezor: re-enable bridge transport
It was disabled in 680df7d6b6 due to #4421,
but that has since been fixed.
Also related is #4060; and now that that is closed, the bridge transport
is not proxied anyway.
2018-09-19 18:14:55 +02:00
SomberNight
9161e8c8f4
interface: refuse to overwrite blockchain of main interface
in case of conflicting forks
2018-09-19 17:56:42 +02:00
SomberNight
7e1a784fca
follow-up prev: fix race between load_wallet and network events
[127.0.0.1] Exception in wrapper_func : AttributeError 'ElectrumWindow' object has no attribute 'wallet'
Traceback (most recent call last):
  File "/home/user/wspace/electrum/electrum/util.py", line 839, in f2
    return await f(*args, **kwargs)
  File "/home/user/wspace/electrum/electrum/interface.py", line 245, in wrapper_func
    return await func(self, *args, **kwargs)
  File "/home/user/wspace/electrum/electrum/interface.py", line 260, in run
    await self.open_session(ssl_context, exit_early=False)
  File "/home/user/wspace/electrum/electrum/interface.py", line 357, in open_session
    await group.spawn(self.monitor_connection())
  File "/usr/local/lib/python3.6/dist-packages/aiorpcx/curio.py", line 241, in __aexit__
    await self.join(wait=self._wait)
  File "/usr/local/lib/python3.6/dist-packages/aiorpcx/curio.py", line 214, in join
    raise task.exception()
  File "/home/user/wspace/electrum/electrum/address_synchronizer.py", line 173, in job
    await group.spawn(self.synchronizer.main())
  File "/usr/local/lib/python3.6/dist-packages/aiorpcx/curio.py", line 241, in __aexit__
    await self.join(wait=self._wait)
  File "/usr/local/lib/python3.6/dist-packages/aiorpcx/curio.py", line 214, in join
    raise task.exception()
  File "/home/user/wspace/electrum/electrum/synchronizer.py", line 181, in main
    self.wallet.network.trigger_callback('wallet_updated', self.wallet)
  File "/home/user/wspace/electrum/electrum/network.py", line 267, in trigger_callback
    callback(event, *args)
  File "/home/user/wspace/electrum/electrum/gui/qt/main_window.py", line 300, in on_network
    if wallet == self.wallet:
AttributeError: 'ElectrumWindow' object has no attribute 'wallet'
2018-09-19 17:44:52 +02:00
SomberNight
96b699e534
synchronizer: fix refresh bug 2018-09-19 16:35:30 +02:00
SomberNight
6f0dceb152
fix #4726
follow-up 88fc62e8f7
2018-09-19 15:26:03 +02:00
ghost43
924ee1a672
Merge pull request #4725 from joren485/unreachable_return
Remove unreachable return statement
2018-09-19 13:09:49 +02:00
Joren Vrancken
ae501ca8ed
Remove unreachable return statement 2018-09-19 11:35:29 +02:00
Ilya Shalyapin
d840804818 use system language by default 2018-09-19 13:07:19 +05:00
SomberNight
adc91eb75e
interface: hostname cannot be empty 2018-09-18 20:21:10 +02:00
SomberNight
916cdebacb
network: send out update trigger when stopping/starting network 2018-09-18 19:27:33 +02:00
SomberNight
a2ed08615c
minor.. move imports out of functions 2018-09-18 18:07:12 +02:00
SomberNight
39db32c3ce
follow-up prev 2018-09-18 17:59:02 +02:00
SomberNight
af63913189
network triggers: rm 'updated'; more fine-grained instead
rm 'interfaces'
add 'wallet_updated', add 'network_updated'
2018-09-18 16:49:48 +02:00
SomberNight
fef15f9c02
wallet: minor opt in get_history 2018-09-18 16:41:56 +02:00
SomberNight
825d7c2cbd
interface: subscribe to headers in run_fetch_blocks
so that 'monitor_connection' is already running while waiting for first header
2018-09-18 15:40:32 +02:00
ThomasV
3ec0ceba3e add option to leave daemon running after GUI is closed 2018-09-18 12:05:37 +02:00
SomberNight
67d3d6b5b5
qt: don't update tabs in ElectrumWindow.__init__ directly 2018-09-18 04:19:12 +02:00
SomberNight
01246b0d97
wallet/verifier: when adding into unverified_tx, don't remove from verifier
Not needed since aee2d8e120
And was never really working I guess (race..)
Also, during normal initial history sync, it caused the verifier to request
proofs multiple times.
2018-09-18 03:48:14 +02:00
SomberNight
533bd97a05
qt HistoryList.update_item: perf optimisation 2018-09-18 03:19:24 +02:00
SomberNight
c8f82c71c9
wallet: small perf optimisation in add_transaction 2018-09-18 02:14:23 +02:00
SomberNight
11bf084a1f
network triggers: 'verified' notification now includes wallet
this is a performance optimisation.

measurements using a large wallet with 11k txns:
syncing XPUB for the first time takes 10 seconds. leaving window open, and
syncing same XPUB again in new window takes 30 seconds. in third window,
it takes ~50 seconds. then ~70s. presumably scaling linearly.
this is due to the history_list.update_item call being CPU-heavy.
now all of them take 10 seconds.
2018-09-18 01:40:34 +02:00
SomberNight
24ec7ce6b8
qt network dialog: maybe fix refresh bug 2018-09-17 22:31:31 +02:00
SomberNight
7221fb3231
interface: further simplifications for fork resolution 2018-09-17 22:30:25 +02:00
SomberNight
b3a2bce213
interface: simplify fork resolution logic 2018-09-17 22:30:21 +02:00
SomberNight
435efb47d0
wallet: lock in get_addr_io, get_tx_delta, get_tx_value
probably fixes #4716
2018-09-17 18:50:47 +02:00
SomberNight
1b95cced5d
verifier: perf optimisations
blockchain.read_header is expensive. do cheap tests first
on a wallet with 11k txns, that is synced except for spv proofs,
finishing sync now takes 80 sec instead of 180 sec
2018-09-17 18:31:25 +02:00
SomberNight
e5e3ac0364
fix #4720 2018-09-17 14:44:01 +02:00
SomberNight
aee2d8e120
verifier: fix a race during reorgs
related: 41e088693d
If our guess of a txn getting confirmed at the same height in the new chain
as it was at in the old chain is incorrect, there is a race between the
verifier and the synchronizer. If the verifier wins, the exception will cause
us to disconnect.
2018-09-17 03:35:25 +02:00
SomberNight
dcab22dcc7
verifier: small clean-up 2018-09-16 22:21:49 +02:00
SomberNight
78488ebcbf
aiosafe safety belts
traceback.print_exc was raising, and self.exception did not got set,
and the whole trace was lost. arghhhh
2018-09-16 22:17:20 +02:00
SomberNight
4360a785ad
blockchain: blockchains_lock needed to write/iterate global dict 2018-09-16 18:26:40 +02:00
SomberNight
7dc5665ab1
interface: faster bootstrap of backwards search 2018-09-16 18:18:49 +02:00
SomberNight
4d502eb2bf
qt tx notifications: wait until sync finishes
Comment is no longer relevant. Also, actually it was incorrect.
Each txn is only downloaded once, though 'added' multiple times to the wallet.
The triggers are only sent out by the Synchronizer, once, when downloaded.
The actual reason for the inconsistency was that get_wallet_delta can only
give complete results once the wallet is synced.
2018-09-16 09:40:07 +02:00
SomberNight
9c919e6478
interface: fix off-by-one in request_chunk
was harmless; usually we just downloaded an extra individual header after the chunk
2018-09-16 09:01:53 +02:00
SomberNight
1d711eeadc
interface: split up 'step'; binary search of headers 2018-09-16 08:29:01 +02:00
SomberNight
58a5346d72
network: switch lagging interface 2018-09-16 07:59:36 +02:00
SomberNight
27e42b4826
interface: if header is on other chain already, just switch (regression) 2018-09-16 07:42:25 +02:00
SomberNight
3fc9326c43
interface: try hard not to infinite loop while getting headers 2018-09-16 07:35:11 +02:00
SomberNight
da23e71db1
interface: block header search simplifications 2018-09-16 07:34:05 +02:00
SomberNight
ab94a47b8e
network: mv request_chunk to interface
this is a bugfix: the old code always tried to connect the chunk to
network.blockchain(). the correct behaviour is to connect to the
blockchain of the interface.
2018-09-16 06:09:14 +02:00
SomberNight
1635bc8cb3
blockchain: use HEADER_SIZE named constant instead of magic numbers 2018-09-16 03:06:21 +02:00
SomberNight
a9197236a2
change 'new_transaction' notification to include wallet 2018-09-16 02:48:13 +02:00
SomberNight
2453872a09
synchronizer: rm redundant 'updated' notification 2018-09-16 02:31:56 +02:00
SomberNight
6f5a4a5502
fix prev: rm incorrect assert 2018-09-15 08:23:49 +02:00
SomberNight
482259df8b
interface: further clean-up in 'step' 2018-09-15 07:26:36 +02:00
SomberNight
beb37aafc5
interface: clean-up 'step'; backwards search 2018-09-15 06:44:18 +02:00
SomberNight
2a958499b6
fx: disable checking mime type in get_json
looking at you, CoinDesk..
2018-09-15 00:30:43 +02:00
SomberNight
f38ec93ae9
qt fx settings: restore selected exchange in combobox if list changes 2018-09-14 23:07:13 +02:00
SomberNight
6ccd83397c
fx: asyncio.Event is not thread-safe; also the 'timeout' field was removed 2018-09-14 23:01:28 +02:00
SomberNight
d1f11f5fe9
fix #4717 2018-09-14 16:12:47 +02:00
Filip Gospodinov
f05f3b430a build-wine: fix locale path
`$i` already contains `locale/`.
2018-09-14 14:37:14 +02:00
Filip Gospodinov
bdecef0eaf contrib: bump pyinstaller to 3.4
PyInstaller 3.4 highlights:

* patch for deterministic builds by electrum
* improved support for Qt5-based applications
* added support for Python 3.7
2018-09-14 14:29:19 +02:00
SomberNight
2bd5e0f25d
packaging: check in make_tgz if packages folder exists
related: #4714
2018-09-13 23:29:44 +02:00
SomberNight
2e61359d50
network: stop pending connections when stopping network 2018-09-13 21:20:55 +02:00
SomberNight
23f56ffa8a
network: avoid infinite reconnect loop to same server 2018-09-13 21:02:37 +02:00
SomberNight
e4bd445a38
network.new_interface: clarify how timed out interfaces are closed 2018-09-13 20:50:32 +02:00
SomberNight
64ab8222f7
interface: if request times out, no need to dump trace 2018-09-13 20:17:58 +02:00
SomberNight
819044221b
verifier: need to wait for reorg
fixes race between verifier and block header download.
scenario: client starts, connects to server. while client was offline,
there was a reorg. txn A was not mined in the old chain, but is mined
after reorg. client subscribes to addresses and starts downloading headers,
concurrently. server tells client txn A is mined at height H >= reorg height.
client sees it has block header at height H, asks for SPV proof for txn A.
but the header the client has is still the old one, the verifier was faster
than the block header download (race...). client receives proof. proof is
incorrect for old header. client disconnects.
2018-09-13 19:00:21 +02:00
SomberNight
78e9152723
network: get_servers to always include recent servers 2018-09-13 16:06:41 +02:00
SomberNight
43664d5f11
fixes for stdio/text gui 2018-09-13 15:11:28 +02:00
SomberNight
1f14894c43
network: add server to recent_servers only after checks 2018-09-13 03:45:21 +02:00
SomberNight
a9fcf2fabf
bump min aiorpcx to 0.8.1 2018-09-13 01:21:53 +02:00
SomberNight
c93d137c5e
interface: minor clean-up split out _set_proxy from init 2018-09-13 01:20:20 +02:00
SomberNight
c40468a8d3
interface: disable bw rate limiting done by aiorpcx 2018-09-12 22:58:36 +02:00
SomberNight
2e18e3c62b
adapt to aiorpcx 0.8.1: rm report_crash kwarg from group.spawn 2018-09-12 22:09:59 +02:00
SomberNight
a3fb865db0
follow-up prev
this is already running inside interface.group
2018-09-12 21:22:46 +02:00
SomberNight
6452582a17
network: batch requests in request_server_info 2018-09-12 21:18:08 +02:00
SomberNight
e7fa42ce3e
wallet: don't write to disk when switching servers 2018-09-12 20:25:13 +02:00
SomberNight
cad4fb80c1
interface: throttle messages 2018-09-12 20:17:12 +02:00
SomberNight
47a97279af
rename CustomTaskGroup to SilentTaskGroup 2018-09-12 19:24:58 +02:00
SomberNight
2039c07a2d
interface.mark_ready: handle cancellation 2018-09-12 18:45:15 +02:00
SomberNight
1419a5c60d
interface: change how GracefulDisconnect is handled 2018-09-12 18:43:50 +02:00
SomberNight
6f7a065081
bump aiorpcx version 2018-09-12 18:43:07 +02:00
SomberNight
3842205b8a
keystore: add note regarding xpubkeys 2018-09-12 18:22:34 +02:00
SomberNight
152c6abb86
network: fix another race in session.subscribe
key in session.subscriptions does not imply key in session.cache
2018-09-12 16:58:15 +02:00
SomberNight
9505a203d8
util: rm dead network code 2018-09-12 16:57:12 +02:00
ThomasV
15b21abc99 fix fee_histogram notifications 2018-09-12 12:56:51 +02:00
ThomasV
ce4608ae76 add help text to bump fee dialog 2018-09-12 12:18:27 +02:00
SomberNight
8cd08cc0fa
network: rm dead code; simplify 2018-09-12 01:40:54 +02:00
SomberNight
ab3c3c5ed7
interface: small clean-up 2018-09-11 22:16:30 +02:00
SomberNight
a5b3f809ce
blockchain.py: add type annotations 2018-09-11 22:14:57 +02:00
SomberNight
014c0d3a41
network: update UI when downloading chunks 2018-09-11 21:44:17 +02:00
SomberNight
518c6280e9
interface: minor clean-up re timeouts 2018-09-11 21:23:37 +02:00
SomberNight
6b9a83ae80
don't test with python 3.5
also, typing is no longer needed (part of stdlib from 3.5)
2018-09-11 21:10:47 +02:00
SomberNight
bed35a65c7
bump min python to 3.6 2018-09-11 21:04:36 +02:00
SomberNight
9ffd2de492
Merge branch 'aiorpcx' 2018-09-11 20:52:58 +02:00
SomberNight
ecc296cf67
fix race in session.subscribe 2018-09-11 20:39:16 +02:00
SomberNight
8b8ca14c6d
move get_index from network to session 2018-09-11 20:37:53 +02:00
SomberNight
e829d6bbcf
wallet: put Sync and Verifier in their own TaskGroup, and that into interface.group 2018-09-11 20:24:01 +02:00
SomberNight
19d4bd4837
simplify prev 2018-09-11 18:28:59 +02:00
SomberNight
4e0d179937
rate limit txn notifications in qt 2018-09-11 18:13:52 +02:00
Janus
09dfb0fd1d fix off-by-one error when syncing from genesis w/o checkpoints 2018-09-11 17:16:37 +02:00
ThomasV
3b6af914e1 add multiplexing capability to NotificationSession, simplify interface 2018-09-11 17:06:41 +02:00
SomberNight
1728dff576
fix prev: that's not how you use the context manager... 2018-09-11 12:25:57 +02:00
SomberNight
557334aa36
interface: introduce tip_lock 2018-09-11 11:44:49 +02:00
SomberNight
20957ac4d9
follow-up prev 2018-09-11 02:43:54 +02:00
Calin Culianu
a4396f4f13
Fixed potential bug when clicking in History List on slow wallet synch 2018-09-11 02:38:57 +02:00
SomberNight
19e244a85e
interface: rm unnecessary writes to self.tip 2018-09-10 19:47:36 +02:00
SomberNight
54cc822227
network: send out 'interfaces' event on new_interface
network dialog was not always showing up-to-date data
2018-09-10 19:03:06 +02:00
SomberNight
e2338581eb
broadcast_transaction: introduce async variant 2018-09-10 18:39:10 +02:00
SomberNight
b279d351d8
interface.session: add default timeout to send_request 2018-09-10 17:12:05 +02:00
SomberNight
fffec71fb3
kivy fx: make sure displayed fiat values get updated 2018-09-10 16:43:04 +02:00
SomberNight
3e3d387161
fix kivy: follow-up 3d424077fd 2018-09-10 15:18:11 +02:00
ThomasV
061231494d revert rm requests 2018-09-10 13:26:50 +02:00
ThomasV
e8f87d2e62 remove requests and pysocks from requirements 2018-09-10 13:06:48 +02:00
SomberNight
526319630e
network: minor fix in switch_to_interface 2018-09-10 02:30:27 +02:00
SomberNight
999ae1f713
test_mnemonic: add foreign lang tests 2018-09-10 02:03:42 +02:00
SomberNight
6b2509b106
interface.run: catch OSError instead of subtypes 2018-09-10 01:09:35 +02:00
SomberNight
b2547601a5
rm dead code 2018-09-10 01:08:51 +02:00
SomberNight
97ea4679a7
network: fix monkey-patching in set_proxy 2018-09-10 01:08:28 +02:00
SomberNight
3d424077fd
introduce NetworkParameters namedtuple 2018-09-10 00:59:53 +02:00
SomberNight
ecf4ea9ba7
move (de)serialize_server to interface; and use it 2018-09-09 23:08:44 +02:00
SomberNight
b381a7fdbf
follow-up prev 2018-09-09 22:02:42 +02:00
SomberNight
48a5b8527a
split up interface.run 2018-09-09 21:16:48 +02:00
SomberNight
096b3e6026
network.maintain_sessions: rm redundant 'update' notifications 2018-09-09 05:32:07 +02:00
SomberNight
e3fb991b1b
clean-up network start/stop a bit 2018-09-09 05:05:08 +02:00
SomberNight
cdca74aa39
move max_checkpoint from network to constants 2018-09-09 05:00:09 +02:00
SomberNight
2f224819ac
interface: small clean-up 2018-09-09 01:15:06 +02:00
SomberNight
57cac47944
fix synchronizer: ask for missing txns on start
Previously it could happen that a wallet was fully synced,
except it had missing transactions, and it would not recover from this state.
2018-09-08 22:44:14 +02:00
SomberNight
86733279f6
docker-wine: update package versions
the previous version is no longer available. hopefully these versions are "lts"
ref: 6899ca2527
2018-09-08 19:57:20 +02:00
SomberNight
c5bedbd3ef
wallet: only do fiat history computations if specifically enabled 2018-09-08 19:38:38 +02:00
SomberNight
77d86f074f
verifier: don't try to request same chunk multiple times 2018-09-08 19:11:02 +02:00
SomberNight
b33b2c0945
synchronizer: more batching 2018-09-08 18:38:58 +02:00
SomberNight
c49e563881
verifier: if we fail to verify SPV proof, disconnect from server 2018-09-08 18:10:21 +02:00
SomberNight
4a88ca1a3a
fix --offline option for fx and trustedcoin 2018-09-08 17:56:29 +02:00
SomberNight
86bc59cd60
update mainnet block header checkpoints 2018-09-08 17:32:28 +02:00
Dzhelil Rufat
c9ffffc526 Remove unneccessary imports from the unit testing directory. (#4699) 2018-09-08 17:24:23 +02:00
SomberNight
57e66324cb
batch fee estimates 2018-09-08 15:36:16 +02:00
SomberNight
ddee03d324
interface.run: catch more exceptions 2018-09-08 02:15:51 +02:00
SomberNight
136df7e5ee
wallet: recreate Synchronizer and Verifier when switching servers
not that nice but solves races
2018-09-08 01:34:33 +02:00
SomberNight
32528d6aa6
rm dupe code 2018-09-08 01:10:41 +02:00
SomberNight
64a03c245c
small timeout change
(re KeyError: can happen after proxy settings change)
2018-09-08 00:25:38 +02:00
SomberNight
7500b1fbee
detect lost connection
supersedes #4697
2018-09-07 20:26:45 +02:00
SomberNight
56c3c76d8b
follow-up 26172686b8 2018-09-07 19:54:26 +02:00
SomberNight
fd40dee337
make sure to retry nodes for network 2018-09-07 19:35:35 +02:00
SomberNight
26172686b8
restructure synchronizer/verifier <--> interface coupling 2018-09-07 19:34:28 +02:00
SomberNight
1fa07c920c
network: restore previous API for broadcast_transaction 2018-09-07 17:07:15 +02:00
Janus
52b877ac3d network: add singleton accessor classmethod, port trustedcoin to aiohttp 2018-09-07 11:35:16 +02:00
ThomasV
8f4b57f718 run freeze_packages 2018-09-06 18:49:37 +02:00
Janus
617103bb2a labels: fix saving single label 2018-09-06 18:30:24 +02:00
SomberNight
dc51e82f54
fx: don't dump trace if getting rates fails 2018-09-06 18:25:23 +02:00
Johann Bauer
e5cd2ed52f
Goldcard: Change spelling mistake 2018-09-06 18:15:44 +02:00
ThomasV
96fb75ffc6
Merge pull request #4685 from toxeus/locale
build-wine: avoid untracked changes in submodule
2018-09-06 17:59:25 +02:00
SomberNight
8467f95a28
rm @profiler from Transaction.estimated_size
in certain situations, estimated_size is called hundreds of times, flooding the log
2018-09-06 17:51:13 +02:00
ThomasV
8fe066707a rm import 2018-09-06 17:47:10 +02:00
SomberNight
77aefdfe71
gitignore: add kivy atlas 2018-09-06 17:46:50 +02:00
Janus
573760daf0 remove generated kivy theming 2018-09-06 17:25:11 +02:00
ThomasV
73bf7a92a2
Merge pull request #4690 from spesmilo/aiorpcx-fx
asyncio: port exchange_rate and labels to aiohttp
2018-09-06 17:18:55 +02:00
Janus
be50394f11 aiorpcx: increase crash reporter timeout, avoid is_running in kivy 2018-09-06 17:18:26 +02:00
SomberNight
0ad504bdf0
interface: catch many common exceptions explicitly 2018-09-06 16:45:43 +02:00
Janus
6e80ba7b4f asyncio: labels, crash_reporter, fx: migrate requests use to aiohttp 2018-09-06 16:18:45 +02:00
ThomasV
5ef04a039b move NotificationSession 2018-09-06 15:53:41 +02:00
ThomasV
234273809a set interface.session before marking as ready 2018-09-06 15:44:11 +02:00
SomberNight
0142e0fa22
fix 'daemon load_wallet' over RPC for python > 3.5.3
related: #3764
2018-09-06 15:14:35 +02:00
Janus
d367199553
async block headers: remove BlockHeaderInterface and Conn classes, make self.height a local 2018-09-06 14:17:45 +02:00
Janus
9c363db440
async block headers: avoid duplicate tip fields, handle electrumx server skipping blocks 2018-09-06 14:17:44 +02:00
SomberNight
4d95452ae7
wallet: partial fix for race in on_default_server_changed 2018-09-06 14:17:44 +02:00
SomberNight
2187615c08
verifier: request proofs in batches 2018-09-06 14:17:43 +02:00
Janus
c89020725b
address synchronizer: fetch initial addresses from wallet 2018-09-06 14:17:43 +02:00
SomberNight
14a032a0b1
disconnect from servers on exception 2018-09-06 14:17:42 +02:00
SomberNight
3f0d79f07d
blockchain.py: better handling of missing headers. more restrictive verify_chunk. 2018-09-06 14:17:42 +02:00
SomberNight
2157eae499
fix request_chunk 2018-09-06 14:17:41 +02:00
Janus
e9ceeb85af
async block headers 2018-09-06 14:17:41 +02:00
Janus
19387ff911
aiorpcx: simplify open_session 2018-09-06 14:17:39 +02:00
Janus
f12074397f
aiorpcx: reintroduce periodic fee updates 2018-09-06 14:17:39 +02:00
SomberNight
a4ffa0b22a
interface: clean-up proxy username/pw handling 2018-09-06 14:17:38 +02:00
SomberNight
6700364ac8
interface: fix cert handling
notably os.unlink cannot be inside the "with open"
2018-09-06 14:17:38 +02:00
Janus
9543a108be
aiorpcx: revive some maintain_sockets code, reintroduce NODES_RETRY_INTERVAL and SERVER_RETRY_INTERVAL usage, and fix --oneserver 2018-09-06 14:17:37 +02:00
ThomasV
5117a520ae
fix start_network 2018-09-06 14:17:37 +02:00
Janus
9bfb5fe71f
address synchronizer: use aiorpcx session object in network's interface,
request, fees
2018-09-06 14:17:36 +02:00
Janus
8f36c9167d
aiorpcx: remove callback based code, add session to Interface 2018-09-06 14:17:29 +02:00
Janus
b120584f97
aiorpcx address synchronizer 2018-09-06 14:11:36 +02:00
Janus
f733cb8947
aiorpcx: socks support 2018-09-06 14:11:36 +02:00
ThomasV
c53caecd1e
fix else statement 2018-09-06 14:11:35 +02:00
Janus
89a01a6463
aiorpcx: pin certificates 2018-09-06 14:11:35 +02:00
Janus
8080a713b2
aiorpcx: pass ssl context, sleep after connecting 2018-09-06 14:11:34 +02:00
Janus
97ea0fc439
aiorpcx: replace network loop with asyncio and try to maintain ten sessions 2018-09-06 14:11:20 +02:00
SomberNight
40ceabff79
rm redundant function from util 2018-09-05 18:36:13 +02:00
SomberNight
69a204d726
fix #4657 2018-09-05 18:30:53 +02:00
SomberNight
44fbd8330b
update gitignore 2018-09-05 18:07:24 +02:00
SomberNight
ecbbfdd10c
rerun freeze packages 2018-09-05 15:58:39 +02:00
ThomasV
951fd8a47f bump apk version number 2018-09-05 15:33:31 +02:00
SomberNight
1e3c3a528c
attempt at fixing wallet syncing crash
fix #3998
fix #4689
2018-09-05 15:22:57 +02:00
Janus
73e367dc3b wallet: don't cache NaN coin price
if NaN coin price is cached, historial acquisition prices are not shown
correctly since the historial prices are requested after the full
history is initially shown. As such, "No data" will be shown, even
though the user required using historical pricing.
2018-09-05 14:38:43 +02:00
Calin Culianu
0da1e904fe macOS: Add missing URI handler(#4557)
Backport from Electron Cash -- fix missing bitcoin: URI types form macOS Info.plist
2018-09-04 22:38:38 +02:00
Filip Gospodinov
6c7bfe613f contrib: remove git describe workaround (#4683)
`git describe` fails if no tag can be found, leading
to the whole build script to fail. This is not always
desired. To prevent `git describe` from failing in
this case the `--always` flag can be passed which
causes a short commit hash to be output when a tag
is not present.
2018-09-04 22:32:46 +02:00
Filip Gospodinov
9220545e60 build-wine: avoid untracked changes in submodule
The locale output file can simply be output in the folder
where it's actually needed. This also saves a recursive copy.

This makes `.gitignore` in the electrum-locale submodule
obsolete.
2018-09-04 16:53:53 +02:00
396 changed files with 45200 additions and 15520 deletions

30
.gitignore vendored
View file

@ -4,19 +4,39 @@
build/
dist/
*.egg/
contrib/pyinstaller/
Electrum.egg-info/
electrum/gui/qt/icons_rc.py
locale/
electrum/locale/
.devlocaltmp/
*_trial_temp
packages
env/
.tox/
.buildozer/
bin/
/app.fil
.idea
.mypy_cache
.vscode
# tox files
# icons
electrum/gui/kivy/theming/light-0.png
electrum/gui/kivy/theming/light-1.png
electrum/gui/kivy/theming/light.atlas
# tests/tox
.tox/
.cache/
.coverage
.pytest_cache
# build workspaces
contrib/build-wine/tmp/
contrib/build-wine/fresh_clone/
contrib/build-linux/appimage/build/
contrib/build-linux/appimage/.cache/
contrib/android_debug.keystore
# shared objects
electrum/*.so
electrum/*.so.0
electrum/*.dll
electrum/*.dylib

9
.gitmodules vendored
View file

@ -1,6 +1,9 @@
[submodule "contrib/deterministic-build/electrum-icons"]
path = contrib/deterministic-build/electrum-icons
url = https://github.com/spesmilo/electrum-icons
[submodule "contrib/deterministic-build/electrum-locale"]
path = contrib/deterministic-build/electrum-locale
url = https://github.com/spesmilo/electrum-locale
[submodule "contrib/CalinsQRReader"]
path = contrib/osx/CalinsQRReader
url = https://github.com/spesmilo/CalinsQRReader
[submodule "electrum/www"]
path = electrum/www
url = https://github.com/spesmilo/electrum-http.git

View file

@ -1,16 +1,19 @@
sudo: true
dist: xenial
language: python
python:
- 3.5
- 3.6
- 3.7
- 3.8
git:
depth: false
addons:
apt:
sources:
- sourceline: 'ppa:tah83/secp256k1'
packages:
- libsecp256k1-0
before_install:
- git tag
install:
- pip install -r contrib/requirements/requirements-travis.txt
cache:
@ -20,12 +23,33 @@ cache:
script:
- tox
after_success:
- if [ "$TRAVIS_BRANCH" = "master" ]; then pip install pycurl requests && contrib/make_locale; fi
- if [ "$TRAVIS_BRANCH" = "master" ]; then pip install requests && contrib/push_locale; fi
- coveralls
jobs:
include:
- name: "Regtest functional tests"
language: python
python: 3.7
before_install:
- sudo add-apt-repository -y ppa:bitcoin/bitcoin
- sudo apt-get -qq update
- sudo apt-get install -yq bitcoind
install:
- pip install -r contrib/requirements/requirements.txt
- pip install electrumx
before_script:
- electrum/tests/regtest/start_bitcoind.sh
- electrum/tests/regtest/start_electrumx.sh
script:
- python -m unittest electrum/tests/regtest.py
after_success: True
- name: "Flake8 linter tests"
language: python
install: pip install flake8
script: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
- stage: binary builds
sudo: true
if: branch = master
name: "Windows build"
language: c
python: false
env:
@ -33,25 +57,56 @@ jobs:
services:
- docker
install:
- sudo docker build --no-cache -t electrum-wine-builder-img ./contrib/build-wine/docker/
- sudo docker build --no-cache -t electrum-wine-builder-img ./contrib/build-wine/
script:
- sudo docker run --name electrum-wine-builder-cont -v $PWD:/opt/wine64/drive_c/electrum --rm --workdir /opt/wine64/drive_c/electrum/contrib/build-wine electrum-wine-builder-img ./build.sh
after_success: true
- os: osx
- if: branch = master
name: "Android build"
language: python
python: 3.7
services:
- docker
install:
- pip install requests && ./contrib/pull_locale
- ./contrib/make_packages
- sudo docker build --no-cache -t electrum-android-builder-img electrum/gui/kivy/tools
script:
- sudo chown -R 1000:1000 .
# Output something every minute or Travis kills the job
- while sleep 60; do echo "=====[ $SECONDS seconds still running ]====="; done &
- sudo docker run -it -u 1000:1000 --rm --name electrum-android-builder-cont --env CI=true -v $PWD:/home/user/wspace/electrum --workdir /home/user/wspace/electrum electrum-android-builder-img ./contrib/make_apk
# kill background sleep loop
- kill %1
- ls -la bin
- if [ $(ls bin | grep -c Electrum-*) -eq 0 ]; then exit 1; fi
after_success: true
- if: branch = master
name: "MacOS build"
os: osx
language: c
env:
- TARGET_OS=macOS
python: false
install:
- git fetch --all --tags
- git fetch origin --unshallow
script: ./contrib/build-osx/make_osx
script: ./contrib/osx/make_osx
after_script: ls -lah dist && md5 dist/*
after_success: true
- if: branch = master
name: "AppImage build"
language: c
python: false
services:
- docker
install:
- sudo docker build --no-cache -t electrum-appimage-builder-img ./contrib/build-linux/appimage/
script:
- sudo docker run --name electrum-appimage-builder-cont -v $PWD:/opt/electrum --rm --workdir /opt/electrum/contrib/build-linux/appimage electrum-appimage-builder-img ./build.sh
after_success: true
- stage: release check
install:
- git fetch --all --tags
- git fetch origin --unshallow
script:
- ./contrib/deterministic-build/check_submodules.sh
after_success: true

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>bitcoin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoin</string>
</array>
</dict>
</array>
<key>LSArchitecturePriority</key>
<array>
<string>x86_64</string>
<string>i386</string>
</array>
</dict>
</plist>

View file

@ -1,6 +1,5 @@
include LICENCE RELEASE-NOTES AUTHORS
include README.rst
include electrum.conf.sample
include electrum.desktop
include *.py
include run_electrum
@ -8,11 +7,15 @@ include contrib/requirements/requirements.txt
include contrib/requirements/requirements-hw.txt
recursive-include packages *.py
recursive-include packages cacert.pem
include icons.qrc
graft icons
graft electrum
prune electrum/tests
graft contrib/udev
exclude electrum/*.so
exclude electrum/*.so.0
global-exclude __pycache__
global-exclude *.py[co]
global-exclude *.py[co~]
global-exclude *.py.orig
global-exclude *.py.rej

View file

@ -1,38 +1,37 @@
Electrum - Lightweight Bitcoin client
LBRY Vault - Lightweight LBRY Credit client
=====================================
::
Licence: MIT Licence
Author: Thomas Voegtlin
Language: Python
Homepage: https://electrum.org/
.. image:: https://travis-ci.org/spesmilo/electrum.svg?branch=master
:target: https://travis-ci.org/spesmilo/electrum
:alt: Build Status
.. 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://d322cqt584bo4o.cloudfront.net/electrum/localized.svg
:target: https://crowdin.com/project/electrum
:alt: Help translate Electrum online
Guides
===============
Guide for Ledger devices -
https://kodxana.github.io/LBRY-Vault-website/
Getting started
===============
Electrum is a pure python application. If you want to use the
Qt interface, install the Qt dependencies::
LBRY Vault itself is pure Python, and so are most of the required dependencies.
Non-python dependencies
-----------------------
If you want to use the Qt interface, install the Qt dependencies::
sudo apt-get install python3-pyqt5
For elliptic curve operations, libsecp256k1 is a required dependency::
sudo apt-get install libsecp256k1-0
Alternatively, when running from a cloned repository, a script is provided to build
libsecp256k1 yourself::
./contrib/make_libsecp256k1.sh
Running from tar.gz
-------------------
If you downloaded the official package (tar.gz), you can run
Electrum from its root directory, without installing it on your
LBRY Vault from its root directory without installing it on your
system; all the python dependencies are included in the 'packages'
directory. To run Electrum from its root directory, just do::
@ -40,40 +39,30 @@ 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
pip3 install .[fast]
sudo apt-get install python3-setuptools python3-pip
python3 -m pip install --user .
This will download and install the Python dependencies used by
Electrum, instead of using the 'packages' directory.
The 'fast' extra contains some optional dependencies that we think
are often useful but they are not strictly needed.
LBRY Vault instead of using the 'packages' directory.
If you cloned the git repository, you need to compile extra files
before you can run Electrum. Read the next section, "Development
Version".
before you can run LBRY Vault. Read the next section, "Development
version".
Development version
===================
-------------------
Check out the code from GitHub::
git clone git://github.com/spesmilo/electrum.git
git clone git://github.com/kodxana/LBRY-Vault.git
cd electrum
git submodule update --init
Run install (this should install dependencies)::
pip3 install .[fast]
python3 -m pip install --user .
Render the SVG icons to PNGs (optional)::
for i in lock unlock confirmed status_lagging status_disconnected status_connected_proxy status_connected status_waiting preferences; do convert -background none icons/$i.svg icons/$i.png; done
Compile the icons file for Qt::
sudo apt-get install pyqt5-dev-tools
pyrcc5 icons.qrc -o electrum/gui/qt/icons_rc.py
Compile the protobuf description file::
@ -83,7 +72,7 @@ Compile the protobuf description file::
Create translations (optional)::
sudo apt-get install python-requests gettext
./contrib/make_locale
./contrib/pull_locale
@ -91,25 +80,31 @@ Create translations (optional)::
Creating Binaries
=================
Linux (tarball)
---------------
To create binaries, create the 'packages' directory::
See :code:`contrib/build-linux/README.md`.
./contrib/make_packages
This directory contains the python dependencies used by Electrum.
Linux (AppImage)
----------------
See :code:`contrib/build-linux/appimage/README.md`.
Mac OS X / macOS
--------
----------------
See :code:`contrib/osx/README.md`.
See `contrib/build-osx/`.
Windows
-------
See `contrib/build-wine/`.
See :code:`contrib/build-wine/README.md`.
Android
-------
See `electrum/gui/kivy/Readme.md` file.
See :code:`electrum/gui/kivy/Readme.md`.

View file

@ -1,3 +1,174 @@
# Release 4.0 - (Not released yet; release notes are incomplete)
* Lightning Network
* Qt GUI: Separation between output selection and transaction finalization.
* Http PayServer can be configured from GUI
# Release 3.3.8 - (July 11, 2019)
* fix some bugs with recent bump fee (RBF) improvements (#5483, #5502)
* fix #5491: watch-only wallets could not bump fee in some cases
* appimage: URLs could not be opened on some desktop environments (#5425)
* faster tx signing for segwit inputs for really large txns (#5494)
* A few other minor bugfixes and usability improvements.
# Release 3.3.7 - (July 3, 2019)
* The AppImage Linux x86_64 binary and the Windows setup.exe
(so now all Windows binaries) are now built reproducibly.
* Bump fee (RBF) improvements:
Implemented a new fee-bump strategy that can add new inputs,
so now any tx can be fee-bumped (d0a4366). The old strategy
was to decrease the value of outputs (starting with change).
We will now try the new strategy first, and only use the old
as a fallback (needed e.g. when spending "Max").
* CoinChooser improvements:
- more likely to construct txs without change (when possible)
- less likely to construct txs with really small change (e864fa5)
- will now only spend negative effective value coins when
beneficial for privacy (cb69aa8)
* fix long-standing bug that broke wallets with >65k addresses (#5366)
* Windows binaries: we now build the PyInstaller boot loader ourselves,
as this seems to reduce anti-virus false positives (1d0f679)
* Android: (fix) BIP70 payment requests could not be paid (#5376)
* Android: allow copy-pasting partial transactions from/to clipboard
* Fix a performance regression for large wallets (c6a54f0)
* Qt: fix some high DPI issues related to text fields (37809be)
* Trezor:
- allow bypassing "too old firmware" error (#5391)
- use only the Bridge to scan devices if it is available (#5420)
* hw wallets: (known issue) on Win10-1903, some hw devices
(that also have U2F functionality) can only be detected with
Administrator privileges. (see #5420 and #5437)
A workaround is to run as Admin, or for Trezor to install the Bridge.
* Several other minor bugfixes and usability improvements.
# Release 3.3.6 - (May 16, 2019)
* qt: fix crash during 2FA wallet creation (#5334)
* fix synchronizer not to keep resubscribing to addresses of
already closed wallets (e415c0d9)
* fix removing addresses/keys from imported wallets (#4481)
* kivy: fix crash when aborting 2FA wallet creation (#5333)
* kivy: fix rare crash when changing exchange rate settings (#5329)
* A few other minor bugfixes and usability improvements.
# Release 3.3.5 - (May 9, 2019)
* The logging system has been overhauled (#5296).
Logs can now also optionally be written to disk, disabled by default.
* Fix a bug in synchronizer (#5122) where client could get stuck.
Also, show the progress of history sync in the GUI. (#5319)
* fix Revealer in Windows and MacOS binaries (#5027)
* fiat rate providers:
- added CoinGecko.com and CoinCap.io
- BitcoinAverage now only provides historical exchange rates for
paying customers. Changed default provider to CoinGecko.com (#5188)
* hardware wallets:
- Ledger: Nano X is now recognized (#5140)
- KeepKey:
- device was not getting detected using Windows binary (#5165)
- support firmware 6.0.0+ (#5205)
- Trezor: implemented "seedless" mode (#5118)
* Coin Control in Qt: implemented freezing individual UTXOs
in addition to freezing addresses (#5152)
* TrustedCoin (2FA wallets):
- better error messages (#5184)
- longer signing timeout (#5221)
* Kivy:
- fix bug with local transactions (#5156)
- allow selecting fiat rate providers without historical data (#5162)
* fix CPFP: the fees already paid by the parent were not included in
the calculation, so it always overestimated (#5244)
* Testnet: there is now a warning when the client is started in
testnet mode as there were a number of reports of users getting
scammed through social engineering (#5295)
* CoinChooser: performance of creating transactions has been improved
significantly for large wallets. (d56917f4)
* Importing/sweeping WIF keys: stricter checks (#4638, #5290)
* Electrum protocol: the client's "user agent" has been changed from
"3.3.5" to "electrum/3.3.5". Other libraries connecting to servers
can consider not "spoofing" to be Electrum. (#5246)
* Several other minor bugfixes and usability improvements.
# Release 3.3.4 - (February 13, 2019)
* AppImage: we now also distribute self-contained binaries for x86_64
Linux in the form of an AppImage (#5042). The Python interpreter,
PyQt5, libsecp256k1, PyCryptodomex, zbar, hidapi/libusb (including
hardware wallet libraries) are all bundled. Note that users of
hw wallets still need to set udev rules themselves.
* hw wallets: fix a regression during transaction signing that prompts
the user too many times for confirmations (commit 2729909)
* transactions now set nVersion to 2, to mimic Bitcoin Core
* fix Qt bug that made all hw wallets unusable on Windows 8.1 (#4960)
* fix bugs in wallet creation wizard that resulted in corrupted
wallets being created in rare cases (#5082, #5057)
* fix compatibility with Qt 5.12 (#5109)
# Release 3.3.3 - (January 25, 2019)
* Do not expose users to server error messages (#4968)
* Notify users of new releases. Release announcements must be signed,
and they are verified byElectrum using a hardcoded Bitcoin address.
* Hardware wallet fixes (#4991, #4993, #5006)
* Display only QR code in QRcode Window
* Fixed code signing on MacOS
* Randomise locktime of transactions
# Release 3.3.2 - (December 21, 2018)
* Fix Qt history export bug
* Improve network timeouts
* Prepend server transaction_broadcast error messages with
explanatory message. Render error messages as plain text.
# Release 3.3.1 - (December 20, 2018)
* Qt: Fix invoices tab crash (#4941)
* Android: Minor GUI improvements
# Release 3.3.0 - Hodler's Edition (December 19, 2018)
* The network layer has been rewritten using asyncio and aiorpcx.
In addition to easier maintenance, this makes the client
more robust against misbehaving servers.
* The minimum python version was increased to 3.6
* The blockchain headers and fork handling logic has been generalized.
Clients by default now follow chain based on most work, not length.
* New wallet creation defaults to native segwit (bech32).
* Segwit 2FA: TrustedCoin now supports native segwit p2wsh
two-factor wallets.
* RBF batching (opt-in): If the wallet has an unconfirmed RBF
transaction, new payments will be added to that transaction,
instead of creating new transactions.
* MacOS: support QR code scanner in binaries.
* Android APK:
- build using Google NDK instead of Crystax NDK
- target API 28
- do not use external storage (previously for block headers)
* hardware wallets:
- Coldcard now supports spending from p2wpkh-p2sh,
fixed p2pkh signing for fw 1.1.0
- Archos Safe-T mini: fix #4726 signing issue
- KeepKey: full segwit support
- Trezor: refactoring and compat with python-trezor 0.11
- Digital BitBox: support firmware v5.0.0
* fix bitcoin URI handling when app already running (#4796)
* Qt listings rewritten:
the History tab now uses QAbstractItemModel, the other tabs use
QStandardItemModel. Performance should be better for large wallets.
* Several other minor bugfixes and usability improvements.
# Release 3.2.3 - (September 3, 2018)
* hardware wallet: the Safe-T mini from Archos is now supported.
@ -223,7 +394,7 @@ issue #3374. Users should upgrade to 3.0.5.
* Qt GUI: sweeping now uses the Send tab, allowing fees to be set
* Windows: if using the installer binary, there is now a separate shortcut
for "Electrum Testnet"
* Digital Bitbox: added suport for p2sh-segwit
* Digital Bitbox: added support for p2sh-segwit
* OS notifications for incoming transactions
* better transaction size estimation:
- fees for segwit txns were somewhat underestimated (#3347)
@ -451,7 +622,7 @@ issue #3374. Users should upgrade to 3.0.5.
# Release 2.7.7
* Fix utf8 encoding bug with old wallet seeds (issue #1967)
* Fix delete request from menu (isue #1968)
* Fix delete request from menu (issue #1968)
# Release 2.7.6
* Fixes a critical bug with imported private keys (issue #1966). Keys
@ -814,7 +985,7 @@ issue #3374. Users should upgrade to 3.0.5.
* New 'Receive' tab in the GUI:
- create and manage payment requests, with QR Codes
- the former 'Receive' tab was renamed to 'Addresses'
- the former Point of Sale plugin is replaced by a resizeable
- the former Point of Sale plugin is replaced by a resizable
window that pops up if you click on the QR code
* The 'Send' tab in the Qt GUI supports transactions with multiple
@ -837,7 +1008,7 @@ issue #3374. Users should upgrade to 3.0.5.
* The client accepts servers with a CA-signed SSL certificate.
* ECIES encrypt/decrypt methods, availabe in the GUI and using
* ECIES encrypt/decrypt methods, available in the GUI and using
the command line:
encrypt <pubkey> <message>
decrypt <pubkey> <message>
@ -910,7 +1081,7 @@ bugfixes: connection problems, transactions staying unverified
# Release 1.8.1
* Notification option when receiving new tranactions
* Notification option when receiving new transactions
* Confirm dialogue before sending large amounts
* Alternative datafile location for non-windows systems
* Fix offline wallet creation

19
SECURITY.md Normal file
View file

@ -0,0 +1,19 @@
# Security Policy
## Reporting a Vulnerability
To report security issues send an email to electrumdev@gmail.com.
The following keys may be used to communicate sensitive information to developers:
| Name | Fingerprint |
|------|-------------|
| ThomasV | 6694 D8DE 7BE8 EE56 31BE D950 2BD5 824B 7F94 70E6 |
| SomberNight | 4AD6 4339 DFA0 5E20 B3F6 AD51 E7B7 48CD AF5E 5ED9 |
You can import a key by running the following command with that
individuals fingerprint: `gpg --recv-keys "<fingerprint>"`
Ensure that you put quotes around fingerprints containing spaces.
These public keys can also be found in the Electrum git repository,
in the top-level `pubkeys` folder.

View file

@ -0,0 +1,16 @@
Source tarballs
===============
✗ _This script does not produce reproducible output (yet!)._
1. Prepare python dependencies used by Electrum.
```
contrib/make_packages
```
2. Create source tarball.
```
contrib/make_tgz
```

View file

@ -0,0 +1,31 @@
FROM ubuntu:16.04@sha256:a4fc0c40360ff2224db3a483e5d80e9164fe3fdce2a8439d2686270643974632
ENV LC_ALL=C.UTF-8 LANG=C.UTF-8
RUN apt-get update -q && \
apt-get install -qy \
git=1:2.7.4-0ubuntu1.9 \
wget=1.17.1-1ubuntu1.5 \
make=4.1-6 \
autotools-dev=20150820.1 \
autoconf=2.69-9 \
libtool=2.4.6-0.1 \
xz-utils=5.1.1alpha+20120614-2ubuntu2 \
libssl-dev=1.0.2g-1ubuntu4.18 \
libssl1.0.0=1.0.2g-1ubuntu4.18 \
openssl=1.0.2g-1ubuntu4.18 \
zlib1g-dev=1:1.2.8.dfsg-2ubuntu4.3 \
libffi-dev=3.2.1-4 \
libncurses5-dev=6.0+20160213-1ubuntu1 \
libsqlite3-dev=3.11.0-1ubuntu1.5 \
libusb-1.0-0-dev=2:1.0.20-1 \
libudev-dev=229-4ubuntu21.29 \
gettext=0.19.7-2ubuntu3.1 \
libzbar0=0.10+doc-10ubuntu1 \
libdbus-1-3=1.10.6-1ubuntu3.6 \
libxkbcommon-x11-0=0.5.0-1ubuntu2.1 \
libc6-dev=2.23-0ubuntu11.2 \
&& \
rm -rf /var/lib/apt/lists/* && \
apt-get autoremove -y && \
apt-get clean

View file

@ -0,0 +1,66 @@
AppImage binary for Electrum
============================
✓ _This binary should be reproducible, meaning you should be able to generate
binaries that match the official releases._
This assumes an Ubuntu host, but it should not be too hard to adapt to another
similar system. The host architecture should be x86_64 (amd64).
The docker commands should be executed in the project's root folder.
We currently only build a single AppImage, for x86_64 architecture.
Help to adapt these scripts to build for (some flavor of) ARM would be welcome,
see [issue #5159](https://github.com/spesmilo/electrum/issues/5159).
1. Install Docker
```
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$ sudo apt-get update
$ sudo apt-get install -y docker-ce
```
2. Build image
```
$ sudo docker build -t electrum-appimage-builder-img contrib/build-linux/appimage
```
3. Build binary
```
$ sudo docker run -it \
--name electrum-appimage-builder-cont \
-v $PWD:/opt/electrum \
--rm \
--workdir /opt/electrum/contrib/build-linux/appimage \
electrum-appimage-builder-img \
./build.sh
```
4. The generated binary is in `./dist`.
## FAQ
### How can I see what is included in the AppImage?
Execute the binary as follows: `./electrum*.AppImage --appimage-extract`
### How to investigate diff between binaries if reproducibility fails?
```
cd dist/
./electrum-*-x86_64.AppImage1 --appimage-extract
mv squashfs-root/ squashfs-root1/
./electrum-*-x86_64.AppImage2 --appimage-extract
mv squashfs-root/ squashfs-root2/
$(cd squashfs-root1; find -type f -exec sha256sum '{}' \; > ./../sha256sum1)
$(cd squashfs-root2; find -type f -exec sha256sum '{}' \; > ./../sha256sum2)
diff sha256sum1 sha256sum2 > d
cat d
```
Useful binary comparison tools:
- vbindiff
- diffoscope

View file

@ -0,0 +1,11 @@
#!/bin/bash
set -e
APPDIR="$(dirname "$(readlink -e "$0")")"
export LD_LIBRARY_PATH="${APPDIR}/usr/lib/:${APPDIR}/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH+:$LD_LIBRARY_PATH}"
export PATH="${APPDIR}/usr/bin:${PATH}"
export LDFLAGS="-L${APPDIR}/usr/lib/x86_64-linux-gnu -L${APPDIR}/usr/lib"
exec "${APPDIR}/usr/bin/python3.7" -s "${APPDIR}/usr/bin/electrum" "$@"

View file

@ -0,0 +1,243 @@
#!/bin/bash
set -e
PROJECT_ROOT="$(dirname "$(readlink -e "$0")")/../../.."
CONTRIB="$PROJECT_ROOT/contrib"
CONTRIB_APPIMAGE="$CONTRIB/build-linux/appimage"
DISTDIR="$PROJECT_ROOT/dist"
BUILDDIR="$CONTRIB_APPIMAGE/build/appimage"
APPDIR="$BUILDDIR/electrum.AppDir"
CACHEDIR="$CONTRIB_APPIMAGE/.cache/appimage"
export GCC_STRIP_BINARIES="1"
# pinned versions
PYTHON_VERSION=3.7.6
PKG2APPIMAGE_COMMIT="eb8f3acdd9f11ab19b78f5cb15daa772367daf15"
SQUASHFSKIT_COMMIT="ae0d656efa2d0df2fcac795b6823b44462f19386"
VERSION=`git describe --tags --dirty --always`
APPIMAGE="$DISTDIR/electrum-$VERSION-x86_64.AppImage"
. "$CONTRIB"/build_tools_util.sh
rm -rf "$BUILDDIR"
mkdir -p "$APPDIR" "$CACHEDIR" "$DISTDIR"
# potential leftover from setuptools that might make pip put garbage in binary
rm -rf "$PROJECT_ROOT/build"
info "downloading some dependencies."
download_if_not_exist "$CACHEDIR/functions.sh" "https://raw.githubusercontent.com/AppImage/pkg2appimage/$PKG2APPIMAGE_COMMIT/functions.sh"
verify_hash "$CACHEDIR/functions.sh" "78b7ee5a04ffb84ee1c93f0cb2900123773bc6709e5d1e43c37519f590f86918"
download_if_not_exist "$CACHEDIR/appimagetool" "https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage"
verify_hash "$CACHEDIR/appimagetool" "d918b4df547b388ef253f3c9e7f6529ca81a885395c31f619d9aaf7030499a13"
download_if_not_exist "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tar.xz"
verify_hash "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" "55a2cce72049f0794e9a11a84862e9039af9183603b78bc60d89539f82cf533f"
info "building python."
tar xf "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" -C "$BUILDDIR"
(
cd "$BUILDDIR/Python-$PYTHON_VERSION"
LC_ALL=C export BUILD_DATE=$(date -u -d "@$SOURCE_DATE_EPOCH" "+%b %d %Y")
LC_ALL=C export BUILD_TIME=$(date -u -d "@$SOURCE_DATE_EPOCH" "+%H:%M:%S")
# Patch taken from Ubuntu http://archive.ubuntu.com/ubuntu/pool/main/p/python3.7/python3.7_3.7.6-1.debian.tar.xz
patch -p1 < "$CONTRIB_APPIMAGE/patches/python-3.7-reproducible-buildinfo.diff"
./configure \
--cache-file="$CACHEDIR/python.config.cache" \
--prefix="$APPDIR/usr" \
--enable-ipv6 \
--enable-shared \
-q
make -j4 -s || fail "Could not build Python"
make -s install > /dev/null || fail "Could not install Python"
# When building in docker on macOS, python builds with .exe extension because the
# case insensitive file system of macOS leaks into docker. This causes the build
# to result in a different output on macOS compared to Linux. We simply patch
# sysconfigdata to remove the extension.
# Some more info: https://bugs.python.org/issue27631
sed -i -e 's/\.exe//g' "$APPDIR"/usr/lib/python3.7/_sysconfigdata*
)
info "Building squashfskit"
git clone "https://github.com/squashfskit/squashfskit.git" "$BUILDDIR/squashfskit"
(
cd "$BUILDDIR/squashfskit"
git checkout "$SQUASHFSKIT_COMMIT"
make -C squashfs-tools mksquashfs || fail "Could not build squashfskit"
)
MKSQUASHFS="$BUILDDIR/squashfskit/squashfs-tools/mksquashfs"
"$CONTRIB"/make_libsecp256k1.sh || fail "Could not build libsecp"
cp -f "$PROJECT_ROOT/electrum/libsecp256k1.so.0" "$APPDIR/usr/lib/libsecp256k1.so.0" || fail "Could not copy libsecp to its destination"
appdir_python() {
env \
PYTHONNOUSERSITE=1 \
LD_LIBRARY_PATH="$APPDIR/usr/lib:$APPDIR/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH+:$LD_LIBRARY_PATH}" \
"$APPDIR/usr/bin/python3.7" "$@"
}
python='appdir_python'
info "installing pip."
"$python" -m ensurepip
info "preparing electrum-locale."
(
cd "$PROJECT_ROOT"
git submodule update --init
pushd "$CONTRIB"/deterministic-build/electrum-locale
if ! which msgfmt > /dev/null 2>&1; then
fail "Please install gettext"
fi
for i in ./locale/*; do
dir="$PROJECT_ROOT/electrum/$i/LC_MESSAGES"
mkdir -p $dir
msgfmt --output-file="$dir/electrum.mo" "$i/electrum.po" || true
done
popd
)
info "installing electrum and its dependencies."
mkdir -p "$CACHEDIR/pip_cache"
"$python" -m pip install --no-dependencies --no-warn-script-location --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements.txt"
"$python" -m pip install --no-dependencies --no-warn-script-location --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements-binaries.txt"
"$python" -m pip install --no-dependencies --no-warn-script-location --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements-hw.txt"
"$python" -m pip install --no-dependencies --no-warn-script-location --cache-dir "$CACHEDIR/pip_cache" "$PROJECT_ROOT"
# was only needed during build time, not runtime
"$python" -m pip uninstall -y Cython
info "copying zbar"
cp "/usr/lib/x86_64-linux-gnu/libzbar.so.0" "$APPDIR/usr/lib/libzbar.so.0"
info "desktop integration."
cp "$PROJECT_ROOT/electrum.desktop" "$APPDIR/electrum.desktop"
cp "$PROJECT_ROOT/electrum/gui/icons/electrum.png" "$APPDIR/electrum.png"
# add launcher
cp "$CONTRIB_APPIMAGE/apprun.sh" "$APPDIR/AppRun"
info "finalizing AppDir."
(
export PKG2AICOMMIT="$PKG2APPIMAGE_COMMIT"
. "$CACHEDIR/functions.sh"
cd "$APPDIR"
# copy system dependencies
copy_deps; copy_deps; copy_deps
move_lib
# apply global appimage blacklist to exclude stuff
# move usr/include out of the way to preserve usr/include/python3.7m.
mv usr/include usr/include.tmp
delete_blacklisted
mv usr/include.tmp usr/include
) || fail "Could not finalize AppDir"
info "Copying additional libraries"
(
# On some systems it can cause problems to use the system libusb (on AppImage excludelist)
cp -f /usr/lib/x86_64-linux-gnu/libusb-1.0.so "$APPDIR/usr/lib/libusb-1.0.so" || fail "Could not copy libusb"
# some distros lack libxkbcommon-x11
cp -f /usr/lib/x86_64-linux-gnu/libxkbcommon-x11.so.0 "$APPDIR"/usr/lib/x86_64-linux-gnu || fail "Could not copy libxkbcommon-x11"
)
info "stripping binaries from debug symbols."
# "-R .note.gnu.build-id" also strips the build id
# "-R .comment" also strips the GCC version information
strip_binaries()
{
chmod u+w -R "$APPDIR"
{
printf '%s\0' "$APPDIR/usr/bin/python3.7"
find "$APPDIR" -type f -regex '.*\.so\(\.[0-9.]+\)?$' -print0
} | xargs -0 --no-run-if-empty --verbose strip -R .note.gnu.build-id -R .comment
}
strip_binaries
remove_emptydirs()
{
find "$APPDIR" -type d -empty -print0 | xargs -0 --no-run-if-empty rmdir -vp --ignore-fail-on-non-empty
}
remove_emptydirs
info "removing some unneeded stuff to decrease binary size."
rm -rf "$APPDIR"/usr/{share,include}
PYDIR="$APPDIR"/usr/lib/python3.7
rm -rf "$PYDIR"/{test,ensurepip,lib2to3,idlelib,turtledemo}
rm -rf "$PYDIR"/{ctypes,sqlite3,tkinter,unittest}/test
rm -rf "$PYDIR"/distutils/{command,tests}
rm -rf "$PYDIR"/config-3.7m-x86_64-linux-gnu
rm -rf "$PYDIR"/site-packages/{opt,pip,setuptools,wheel}
rm -rf "$PYDIR"/site-packages/Cryptodome/SelfTest
rm -rf "$PYDIR"/site-packages/{psutil,qrcode,websocket}/tests
for component in connectivity declarative help location multimedia quickcontrols2 serialport webengine websockets xmlpatterns ; do
rm -rf "$PYDIR"/site-packages/PyQt5/Qt/translations/qt${component}_*
rm -rf "$PYDIR"/site-packages/PyQt5/Qt/resources/qt${component}_*
done
rm -rf "$PYDIR"/site-packages/PyQt5/Qt/{qml,libexec}
rm -rf "$PYDIR"/site-packages/PyQt5/{pyrcc.so,pylupdate.so,uic}
rm -rf "$PYDIR"/site-packages/PyQt5/Qt/plugins/{bearer,gamepads,geometryloaders,geoservices,playlistformats,position,renderplugins,sceneparsers,sensors,sqldrivers,texttospeech,webview}
for component in Bluetooth Concurrent Designer Help Location NetworkAuth Nfc Positioning PositioningQuick Qml Quick Sensors SerialPort Sql Test Web Xml ; do
rm -rf "$PYDIR"/site-packages/PyQt5/Qt/lib/libQt5${component}*
rm -rf "$PYDIR"/site-packages/PyQt5/Qt${component}*
done
rm -rf "$PYDIR"/site-packages/PyQt5/Qt.so
# these are deleted as they were not deterministic; and are not needed anyway
find "$APPDIR" -path '*/__pycache__*' -delete
# note that jsonschema-*.dist-info is needed by that package as it uses 'pkg_resources.get_distribution'
# also, see https://gitlab.com/python-devs/importlib_metadata/issues/71
for f in "$PYDIR"/site-packages/jsonschema-*.dist-info; do mv "$f" "$(echo "$f" | sed s/\.dist-info/\.dist-info2/)"; done
for f in "$PYDIR"/site-packages/importlib_metadata-*.dist-info; do mv "$f" "$(echo "$f" | sed s/\.dist-info/\.dist-info2/)"; done
rm -rf "$PYDIR"/site-packages/*.dist-info/
rm -rf "$PYDIR"/site-packages/*.egg-info/
for f in "$PYDIR"/site-packages/jsonschema-*.dist-info2; do mv "$f" "$(echo "$f" | sed s/\.dist-info2/\.dist-info/)"; done
for f in "$PYDIR"/site-packages/importlib_metadata-*.dist-info2; do mv "$f" "$(echo "$f" | sed s/\.dist-info2/\.dist-info/)"; done
find -exec touch -h -d '2000-11-11T11:11:11+00:00' {} +
info "creating the AppImage."
(
cd "$BUILDDIR"
cp "$CACHEDIR/appimagetool" "$CACHEDIR/appimagetool_copy"
# zero out "appimage" magic bytes, as on some systems they confuse the linker
sed -i 's|AI\x02|\x00\x00\x00|' "$CACHEDIR/appimagetool_copy"
chmod +x "$CACHEDIR/appimagetool_copy"
"$CACHEDIR/appimagetool_copy" --appimage-extract
# We build a small wrapper for mksquashfs that removes the -mkfs-fixed-time option
# that mksquashfs from squashfskit does not support. It is not needed for squashfskit.
cat > ./squashfs-root/usr/lib/appimagekit/mksquashfs << EOF
#!/bin/sh
args=\$(echo "\$@" | sed -e 's/-mkfs-fixed-time 0//')
"$MKSQUASHFS" \$args
EOF
env VERSION="$VERSION" ARCH=x86_64 SOURCE_DATE_EPOCH=1530212462 ./squashfs-root/AppRun --no-appstream --verbose "$APPDIR" "$APPIMAGE"
)
info "done."
ls -la "$DISTDIR"
sha256sum "$DISTDIR"/*

View file

@ -0,0 +1,13 @@
# DP: Build getbuildinfo.o with DATE/TIME values when defined
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -766,6 +766,8 @@ Modules/getbuildinfo.o: $(PARSER_OBJS) \
-DGITVERSION="\"`LC_ALL=C $(GITVERSION)`\"" \
-DGITTAG="\"`LC_ALL=C $(GITTAG)`\"" \
-DGITBRANCH="\"`LC_ALL=C $(GITBRANCH)`\"" \
+ $(if $(BUILD_DATE),-DDATE='"$(BUILD_DATE)"') \
+ $(if $(BUILD_TIME),-DTIME='"$(BUILD_TIME)"') \
-o $@ $(srcdir)/Modules/getbuildinfo.c
Modules/getpath.o: $(srcdir)/Modules/getpath.c Makefile

10
contrib/build-osx/make_osx Executable file → Normal file
View file

@ -13,7 +13,7 @@ src_dir=$(dirname "$0")
cd $src_dir/../..
export PYTHONHASHSEED=22
VERSION=`git describe --tags --dirty`
VERSION=`git describe --tags --dirty --always`
which brew > /dev/null 2>&1 || fail "Please install brew from https://brew.sh/ to continue"
@ -30,7 +30,7 @@ fail "Unable to use Python $PYTHON_VERSION"
info "Installing pyinstaller"
python3 -m pip install git+https://github.com/ecdsa/pyinstaller@fix_2952 -I --user || fail "Could not install pyinstaller"
python3 -m pip install -I --user pyinstaller==3.4 || fail "Could not install pyinstaller"
info "Using these versions for building $PACKAGE:"
sw_vers
@ -90,5 +90,11 @@ done
info "Building binary"
pyinstaller --noconfirm --ascii --clean --name $VERSION contrib/build-osx/osx.spec || fail "Could not build binary"
info "Adding bitcoin URI types to Info.plist"
plutil -insert 'CFBundleURLTypes' \
-xml '<array><dict> <key>CFBundleURLName</key> <string>bitcoin</string> <key>CFBundleURLSchemes</key> <array><string>bitcoin</string></array> </dict></array>' \
-- dist/$PACKAGE.app/Contents/Info.plist \
|| fail "Could not add keys to Info.plist. Make sure the program 'plutil' exists and is installed."
info "Creating .DMG"
hdiutil create -fs HFS+ -volname $PACKAGE -srcfolder dist/$PACKAGE.app dist/electrum-$VERSION.dmg || fail "Could not create .DMG"

0
contrib/build-osx/package.sh Executable file → Normal file
View file

View file

@ -0,0 +1,43 @@
FROM ubuntu:18.04@sha256:5f4bdc3467537cbbe563e80db2c3ec95d548a9145d64453b06939c4592d67b6d
ENV LC_ALL=C.UTF-8 LANG=C.UTF-8
RUN dpkg --add-architecture i386 && \
apt-get update -q && \
apt-get install -qy \
wget=1.19.4-1ubuntu2.2 \
gnupg2=2.2.4-1ubuntu1.2 \
dirmngr=2.2.4-1ubuntu1.2 \
python3-software-properties=0.96.24.32.1 \
software-properties-common=0.96.24.32.1
RUN apt-get update -q && \
apt-get install -qy \
git=1:2.17.1-1ubuntu0.5 \
p7zip-full=16.02+dfsg-6 \
make=4.1-9.1ubuntu1 \
mingw-w64=5.0.3-1 \
autotools-dev=20180224.1 \
autoconf=2.69-11 \
libtool=2.4.6-2 \
gettext=0.19.8.1-6
RUN wget -nc https://dl.winehq.org/wine-builds/Release.key && \
echo "c51bcb8cc4a12abfbd7c7660eaf90f49674d15e222c262f27e6c96429111b822 Release.key" | sha256sum -c - && \
apt-key add Release.key && \
rm Release.key && \
wget -nc https://dl.winehq.org/wine-builds/winehq.key && \
echo "78b185fabdb323971d13bd329fefc8038e08559aa51c4996de18db0639a51df6 winehq.key" | sha256sum -c - && \
apt-key add winehq.key && \
rm winehq.key && \
apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/ && \
apt-get update -q && \
apt-get install -qy \
wine-stable-amd64:amd64=4.0.3~bionic \
wine-stable-i386:i386=4.0.3~bionic \
wine-stable:amd64=4.0.3~bionic \
winehq-stable:amd64=4.0.3~bionic
RUN rm -rf /var/lib/apt/lists/* && \
apt-get autoremove -y && \
apt-get clean

View file

@ -1,37 +1,100 @@
Windows Binary Builds
=====================
Windows binaries
================
These scripts can be used for cross-compilation of Windows Electrum executables from Linux/Wine.
✓ _These binaries should be reproducible, meaning you should be able to generate
binaries that match the official releases._
For reproducible builds, see the `docker` folder.
This assumes an Ubuntu (x86_64) host, but it should not be too hard to adapt to another
similar system. The docker commands should be executed in the project's root
folder.
1. Install Docker
```
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$ sudo apt-get update
$ sudo apt-get install -y docker-ce
```
2. Build image
```
$ sudo docker build -t electrum-wine-builder-img contrib/build-wine
```
Note: see [this](https://stackoverflow.com/a/40516974/7499128) if having dns problems
3. Build Windows binaries
It's recommended to build from a fresh clone
(but you can skip this if reproducibility is not necessary).
```
$ FRESH_CLONE=contrib/build-wine/fresh_clone && \
sudo rm -rf $FRESH_CLONE && \
mkdir -p $FRESH_CLONE && \
cd $FRESH_CLONE && \
git clone https://github.com/spesmilo/electrum.git && \
cd electrum
```
And then build from this directory:
```
$ git checkout $REV
$ sudo docker run -it \
--name electrum-wine-builder-cont \
-v $PWD:/opt/wine64/drive_c/electrum \
--rm \
--workdir /opt/wine64/drive_c/electrum/contrib/build-wine \
electrum-wine-builder-img \
./build.sh
```
4. The generated binaries are in `./contrib/build-wine/dist`.
Usage:
Code Signing
============
Electrum Windows builds are signed with a Microsoft Authenticode™ code signing
certificate in addition to the GPG-based signatures.
The advantage of using Authenticode is that Electrum users won't receive a
Windows SmartScreen warning when starting it.
The release signing procedure involves a signer (the holder of the
certificate/key) and one or multiple trusted verifiers:
1. Install the following dependencies:
| Signer | Verifier |
|-----------------------------------------------------------|-----------------------------------|
| Build .exe files using `build.sh` | |
| Sign .exe with `./sign.sh` | |
| Upload signed files to download server | |
| | Build .exe files using `build.sh` |
| | Compare files using `unsign.sh` |
| | Sign .exe file using `gpg -b` |
- dirmngr
- gpg
- 7Zip
- Wine (>= v2)
- (and, for building libsecp256k1)
- mingw-w64
- autotools-dev
- autoconf
- libtool
| Signer and verifiers: |
|-----------------------------------------------------------------------------------------------|
| Upload signatures to 'electrum-signatures' repo, as `$version/$filename.$builder.asc` |
For example:
```
$ sudo apt-get install wine-development dirmngr gnupg2 p7zip-full
$ sudo apt-get install mingw-w64 autotools-dev autoconf libtool
```
Verify Integrity of signed binary
=================================
The binaries are also built by Travis CI, so if you are having problems,
[that script](https://github.com/spesmilo/electrum/blob/master/.travis.yml) might help.
Every user can verify that the official binary was created from the source code in this
repository. To do so, the Authenticode signature needs to be stripped since the signature
is not reproducible.
2. Make sure `/opt` is writable by the current user.
3. Run `build.sh`.
4. The generated binaries are in `./dist`.
This procedure removes the differences between the signed and unsigned binary:
1. Remove the signature from the signed binary using osslsigncode or signtool.
2. Set the COFF image checksum for the signed binary to 0x0. This is necessary
because pyinstaller doesn't generate a checksum.
3. Append null bytes to the _unsigned_ binary until the byte count is a multiple
of 8.
The script `unsign.sh` performs these steps.

95
contrib/build-wine/build-electrum-git.sh Executable file → Normal file
View file

@ -1,40 +1,37 @@
#!/bin/bash
NAME_ROOT=electrum
PYTHON_VERSION=3.6.6
NAME_ROOT=lbry-vault
# These settings probably don't need any change
export WINEPREFIX=/opt/wine64
export WINEDEBUG=-all
export PYTHONDONTWRITEBYTECODE=1
export PYTHONHASHSEED=22
PYHOME=c:/python$PYTHON_VERSION
PYHOME=c:/python3
PYTHON="wine $PYHOME/python.exe -OO -B"
# Let's begin!
cd `dirname $0`
set -e
mkdir -p tmp
cd tmp
here="$(dirname "$(readlink -e "$0")")"
. "$CONTRIB"/build_tools_util.sh
pushd $WINEPREFIX/drive_c/electrum
# Load electrum-icons and electrum-locale for this release
git submodule init
git submodule update
VERSION=`git describe --tags --dirty --always`
info "Last commit: $VERSION"
VERSION=`git describe --tags --dirty || printf 'custom'`
echo "Last commit: $VERSION"
# Load electrum-locale for this release
git submodule update --init
pushd ./contrib/deterministic-build/electrum-locale
if ! which msgfmt > /dev/null 2>&1; then
echo "Please install gettext"
exit 1
fail "Please install gettext"
fi
for i in ./locale/*; do
dir=$i/LC_MESSAGES
dir=$WINEPREFIX/drive_c/electrum/electrum/$i/LC_MESSAGES
mkdir -p $dir
msgfmt --output-file=$dir/electrum.mo $i/electrum.po || true
done
@ -43,38 +40,82 @@ popd
find -exec touch -d '2000-11-11T11:11:11+00:00' {} +
popd
cp $WINEPREFIX/drive_c/electrum/LICENCE .
cp -r $WINEPREFIX/drive_c/electrum/contrib/deterministic-build/electrum-locale/locale $WINEPREFIX/drive_c/electrum/electrum/
cp $WINEPREFIX/drive_c/electrum/contrib/deterministic-build/electrum-icons/icons_rc.py $WINEPREFIX/drive_c/electrum/electrum/gui/qt/
# Install frozen dependencies
$PYTHON -m pip install -r ../../deterministic-build/requirements.txt
$PYTHON -m pip install --no-dependencies --no-warn-script-location -r "$CONTRIB"/deterministic-build/requirements.txt
$PYTHON -m pip install -r ../../deterministic-build/requirements-hw.txt
$PYTHON -m pip install --no-dependencies --no-warn-script-location -r "$CONTRIB"/deterministic-build/requirements-hw.txt
pushd $WINEPREFIX/drive_c/electrum
$PYTHON setup.py install
# see https://github.com/pypa/pip/issues/2195 -- pip makes a copy of the entire directory
info "Pip installing Electrum. This might take a long time if the project folder is large."
$PYTHON -m pip install --no-dependencies --no-warn-script-location .
popd
cd ..
# these are deleted as they were not deterministic; and are not needed anyway
rm "$WINEPREFIX"/drive_c/python3/Lib/site-packages/jsonschema-*.dist-info/RECORD
rm -rf dist/
# build standalone and portable versions
wine "C:/python$PYTHON_VERSION/scripts/pyinstaller.exe" --noconfirm --ascii --clean --name $NAME_ROOT-$VERSION -w deterministic.spec
info "Running pyinstaller..."
wine "$PYHOME/scripts/pyinstaller.exe" --noconfirm --ascii --clean --name $NAME_ROOT-$VERSION -w deterministic.spec
# set timestamps in dist, in order to make the installer reproducible
pushd dist
find -exec touch -d '2000-11-11T11:11:11+00:00' {} +
popd
# build NSIS installer
info "building NSIS installer"
# $VERSION could be passed to the electrum.nsi script, but this would require some rewriting in the script itself.
wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" /DPRODUCT_VERSION=$VERSION electrum.nsi
cd dist
mv electrum-setup.exe $NAME_ROOT-$VERSION-setup.exe
mv lbry-vault-setup.exe $NAME_ROOT-$VERSION-setup.exe
cd ..
echo "Done."
md5sum dist/electrum*exe
info "Padding binaries to 8-byte boundaries, and fixing COFF image checksum in PE header"
# note: 8-byte boundary padding is what osslsigncode uses:
# https://github.com/mtrojnar/osslsigncode/blob/6c8ec4427a0f27c145973450def818e35d4436f6/osslsigncode.c#L3047
(
cd dist
for binary_file in ./*.exe; do
info ">> fixing $binary_file..."
# code based on https://github.com/erocarrera/pefile/blob/bbf28920a71248ed5c656c81e119779c131d9bd4/pefile.py#L5877
python3 <<EOF
pe_file = "$binary_file"
with open(pe_file, "rb") as f:
binary = bytearray(f.read())
pe_offset = int.from_bytes(binary[0x3c:0x3c+4], byteorder="little")
checksum_offset = pe_offset + 88
checksum = 0
# Pad data to 8-byte boundary.
remainder = len(binary) % 8
binary += bytes(8 - remainder)
for i in range(len(binary) // 4):
if i == checksum_offset // 4: # Skip the checksum field
continue
dword = int.from_bytes(binary[i*4:i*4+4], byteorder="little")
checksum = (checksum & 0xffffffff) + dword + (checksum >> 32)
if checksum > 2 ** 32:
checksum = (checksum & 0xffffffff) + (checksum >> 32)
checksum = (checksum & 0xffff) + (checksum >> 16)
checksum = (checksum) + (checksum >> 16)
checksum = checksum & 0xffff
checksum += len(binary)
# Set the checksum
binary[checksum_offset : checksum_offset + 4] = int.to_bytes(checksum, byteorder="little", length=4)
with open(pe_file, "wb") as f:
f.write(binary)
EOF
done
)
sha256sum dist/lbry-vault*.exe

0
contrib/build-wine/build-secp256k1.sh Executable file → Normal file
View file

39
contrib/build-wine/build.sh Executable file → Normal file
View file

@ -1,28 +1,43 @@
#!/bin/bash
# Lucky number
export PYTHONHASHSEED=22
here=$(dirname "$0")
set -e
here="$(dirname "$(readlink -e "$0")")"
test -n "$here" -a -d "$here" || exit
echo "Clearing $here/build and $here/dist..."
export CONTRIB="$here/.."
export PROJECT_ROOT="$CONTRIB/.."
export CACHEDIR="$here/.cache"
export PIP_CACHE_DIR="$CACHEDIR/pip_cache"
export BUILD_TYPE="wine"
export GCC_TRIPLET_HOST="i686-w64-mingw32"
export GCC_TRIPLET_BUILD="x86_64-pc-linux-gnu"
export GCC_STRIP_BINARIES="1"
. "$CONTRIB"/build_tools_util.sh
info "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"
mkdir -p "$CACHEDIR" "$PIP_CACHE_DIR"
$here/build-secp256k1.sh || exit 1
if [ -f "$PROJECT_ROOT/electrum/libsecp256k1-0.dll" ]; then
info "libsecp256k1 already built, skipping"
else
"$CONTRIB"/make_libsecp256k1.sh || fail "Could not build libsecp"
fi
$here/prepare-wine.sh || exit 1
$here/prepare-wine.sh || fail "prepare-wine failed"
echo "Resetting modification time in C:\Python..."
info "Resetting modification time in C:\Python..."
# (Because of some bugs in pyinstaller)
pushd /opt/wine64/drive_c/python*
find -exec touch -d '2000-11-11T11:11:11+00:00' {} +
popd
ls -l /opt/wine64/drive_c/python*
$here/build-electrum-git.sh && \
echo "Done."
$here/build-electrum-git.sh || fail "build-electrum-git failed"
info "Done."

View file

@ -10,8 +10,7 @@ for i, x in enumerate(sys.argv):
else:
raise Exception('no name')
PYTHON_VERSION = '3.6.6'
PYHOME = 'c:/python' + PYTHON_VERSION
PYHOME = 'c:/python3'
home = 'C:\\electrum\\'
@ -23,14 +22,20 @@ hiddenimports += collect_submodules('btchip')
hiddenimports += collect_submodules('keepkeylib')
hiddenimports += collect_submodules('websocket')
hiddenimports += collect_submodules('ckcc')
hiddenimports += ['PyQt5.QtPrintSupport'] # needed by Revealer
# Add libusb binary
binaries = [(PYHOME+"/libusb-1.0.dll", ".")]
# safetlib imports PyQt5.Qt. We use a local updated copy of pinmatrix.py until they
# release a new version that includes https://github.com/archos-safe-t/python-safet/commit/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e
hiddenimports.remove('safetlib.qt.pinmatrix')
binaries = []
# Workaround for "Retro Look":
binaries += [b for b in collect_dynamic_libs('PyQt5') if 'qwindowsvista' in b[0]]
binaries += [('C:/tmp/libsecp256k1.dll', '.')]
binaries += [('C:/tmp/libsecp256k1-0.dll', '.')]
binaries += [('C:/tmp/libusb-1.0.dll', '.')]
datas = [
(home+'electrum/*.json', 'electrum'),
@ -38,12 +43,15 @@ datas = [
(home+'electrum/locale', 'electrum/locale'),
(home+'electrum/plugins', 'electrum/plugins'),
('C:\\Program Files (x86)\\ZBar\\bin\\', '.'),
(home+'electrum/gui/icons', 'electrum/gui/icons'),
]
datas += collect_data_files('trezorlib')
datas += collect_data_files('safetlib')
datas += collect_data_files('btchip')
datas += collect_data_files('keepkeylib')
datas += collect_data_files('ckcc')
datas += collect_data_files('jsonrpcserver')
datas += collect_data_files('jsonrpcclient')
# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
a = Analysis([home+'run_electrum',
@ -57,7 +65,6 @@ a = Analysis([home+'run_electrum',
home+'electrum/commands.py',
home+'electrum/plugins/cosigner_pool/qt.py',
home+'electrum/plugins/email_requests/qt.py',
home+'electrum/plugins/trezor/client.py',
home+'electrum/plugins/trezor/qt.py',
home+'electrum/plugins/safe_t/client.py',
home+'electrum/plugins/safe_t/qt.py',
@ -79,6 +86,24 @@ for d in a.datas:
a.datas.remove(d)
break
# Strip out parts of Qt that we never use. Reduces binary size by tens of MBs. see #4815
qt_bins2remove=('qt5web', 'qt53d', 'qt5game', 'qt5designer', 'qt5quick',
'qt5location', 'qt5test', 'qt5xml', r'pyqt5\qt\qml\qtquick')
print("Removing Qt binaries:", *qt_bins2remove)
for x in a.binaries.copy():
for r in qt_bins2remove:
if x[0].lower().startswith(r):
a.binaries.remove(x)
print('----> Removed x =', x)
qt_data2remove=(r'pyqt5\qt\translations\qtwebengine_locales', )
print("Removing Qt datas:", *qt_data2remove)
for x in a.datas.copy():
for r in qt_data2remove:
if x[0].lower().startswith(r):
a.datas.remove(x)
print('----> Removed x =', x)
# hotfix for #3171 (pre-Win10 binaries)
a.binaries = [x for x in a.binaries if not x[1].lower().startswith(r'c:\windows')]
@ -93,11 +118,11 @@ exe_standalone = EXE(
a.scripts,
a.binaries,
a.datas,
name=os.path.join('build\\pyi.win32\\electrum', cmdline_name + ".exe"),
name=os.path.join('build\\pyi.win32\\electrum-lbry', cmdline_name + ".exe"),
debug=False,
strip=None,
upx=False,
icon=home+'icons/electrum.ico',
icon=home+'electrum/gui/icons/electrum.ico',
console=False)
# console=True makes an annoying black box pop up, but it does make Electrum output command line commands, with this turned off no output will be given but commands can still be used
@ -106,11 +131,11 @@ exe_portable = EXE(
a.scripts,
a.binaries,
a.datas + [ ('is_portable', 'README.md', 'DATA' ) ],
name=os.path.join('build\\pyi.win32\\electrum', cmdline_name + "-portable.exe"),
name=os.path.join('build\\pyi.win32\\electrum-lbry', cmdline_name + "-portable.exe"),
debug=False,
strip=None,
upx=False,
icon=home+'icons/electrum.ico',
icon=home+'electrum/gui/icons/electrum.ico',
console=False)
#####
@ -120,11 +145,11 @@ exe_dependent = EXE(
pyz,
a.scripts,
exclude_binaries=True,
name=os.path.join('build\\pyi.win32\\electrum', cmdline_name),
name=os.path.join('build\\pyi.win32\\electrum-lbry', cmdline_name),
debug=False,
strip=None,
upx=False,
icon=home+'icons/electrum.ico',
icon=home+'electrum/gui/icons/electrum.ico',
console=False)
coll = COLLECT(
@ -135,6 +160,6 @@ coll = COLLECT(
strip=None,
upx=True,
debug=False,
icon=home+'icons/electrum.ico',
icon=home+'electrum/gui/icons/electrum.ico',
console=False,
name=os.path.join('dist', 'electrum'))

View file

@ -8,7 +8,8 @@ RUN dpkg --add-architecture i386 && \
wget=1.19.4-1ubuntu2.1 \
gnupg2=2.2.4-1ubuntu1.1 \
dirmngr=2.2.4-1ubuntu1.1 \
software-properties-common=0.96.24.32.4 \
python3-software-properties=0.96.24.32.1 \
software-properties-common=0.96.24.32.1 \
&& \
wget -nc https://dl.winehq.org/wine-builds/Release.key && \
apt-key add Release.key && \
@ -19,7 +20,7 @@ RUN dpkg --add-architecture i386 && \
wine-stable-i386:i386=3.0.1~bionic \
wine-stable:amd64=3.0.1~bionic \
winehq-stable:amd64=3.0.1~bionic \
git=1:2.17.1-1ubuntu0.1 \
git \
p7zip-full=16.02+dfsg-6 \
make=4.1-9.1ubuntu1 \
mingw-w64=5.0.3-1 \

View file

@ -27,6 +27,19 @@ folder.
3. Build Windows binaries
It's recommended to build from a fresh clone
(but you can skip this if reproducibility is not necessary).
```
$ FRESH_CLONE=contrib/build-wine/fresh_clone && \
rm -rf $FRESH_CLONE && \
mkdir -p $FRESH_CLONE && \
cd $FRESH_CLONE && \
git clone https://github.com/spesmilo/electrum.git && \
cd electrum
```
And then build from this directory:
```
$ git checkout $REV
$ sudo docker run \

View file

@ -6,8 +6,8 @@
;--------------------------------
;Variables
!define PRODUCT_NAME "Electrum"
!define PRODUCT_WEB_SITE "https://github.com/spesmilo/electrum"
!define PRODUCT_NAME "LBRY Vault"
!define PRODUCT_WEB_SITE "https://github.com/tzarebczan/electrum"
!define PRODUCT_PUBLISHER "Electrum Technologies GmbH"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
@ -16,7 +16,7 @@
;Name and file
Name "${PRODUCT_NAME}"
OutFile "dist/electrum-setup.exe"
OutFile "dist/lbry-vault-setup.exe"
;Default installation folder
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
@ -58,7 +58,7 @@
VIAddVersionKey ProductName "${PRODUCT_NAME} Installer"
VIAddVersionKey Comments "The installer for ${PRODUCT_NAME}"
VIAddVersionKey CompanyName "${PRODUCT_NAME}"
VIAddVersionKey LegalCopyright "2013-2016 ${PRODUCT_PUBLISHER}"
VIAddVersionKey LegalCopyright "2013-2018 ${PRODUCT_PUBLISHER}"
VIAddVersionKey FileDescription "${PRODUCT_NAME} Installer"
VIAddVersionKey FileVersion ${PRODUCT_VERSION}
VIAddVersionKey ProductVersion ${PRODUCT_VERSION}
@ -72,7 +72,7 @@
!define MUI_ABORTWARNING
!define MUI_ABORTWARNING_TEXT "Are you sure you wish to abort the installation of ${PRODUCT_NAME}?"
!define MUI_ICON "c:\electrum\icons\electrum.ico"
!define MUI_ICON "c:\electrum\electrum\gui\icons\electrum.ico"
;--------------------------------
;Pages
@ -111,7 +111,7 @@ Section
;Files to pack into the installer
File /r "dist\electrum\*.*"
File "c:\electrum\icons\electrum.ico"
File "c:\electrum\electrum\gui\icons\electrum.ico"
;Store installation folder
WriteRegStr HKCU "Software\${PRODUCT_NAME}" "" $INSTDIR
@ -122,21 +122,23 @@ Section
;Create desktop shortcut
DetailPrint "Creating desktop shortcut..."
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" ""
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\lbry-vault-${PRODUCT_VERSION}.exe" ""
;Create start-menu items
DetailPrint "Creating start-menu items..."
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" "" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" 0
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME} Testnet.lnk" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" "--testnet" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" 0
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\lbry-vault-${PRODUCT_VERSION}.exe" "" "$INSTDIR\lbry-vault-${PRODUCT_VERSION}.exe" 0
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME} Testnet.lnk" "$INSTDIR\lbry-vault-${PRODUCT_VERSION}.exe" "--testnet" "$INSTDIR\lbry-vault-${PRODUCT_VERSION}.exe" 0
;Links bitcoin: URI's to Electrum
WriteRegStr HKCU "Software\Classes\bitcoin" "" "URL:bitcoin Protocol"
WriteRegStr HKCU "Software\Classes\bitcoin" "URL Protocol" ""
WriteRegStr HKCU "Software\Classes\bitcoin" "DefaultIcon" "$\"$INSTDIR\electrum.ico, 0$\""
WriteRegStr HKCU "Software\Classes\bitcoin\shell\open\command" "" "$\"$INSTDIR\electrum-${PRODUCT_VERSION}.exe$\" $\"%1$\""
WriteRegStr HKCU "Software\Classes\bitcoin\shell\open\command" "" "$\"$INSTDIR\lbry-vault-${PRODUCT_VERSION}.exe$\" $\"%1$\""
;Adds an uninstaller possibility to Windows Uninstall or change a program section
WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
@ -167,7 +169,7 @@ Section "Uninstall"
Delete "$SMPROGRAMS\${PRODUCT_NAME}\*.*"
RMDir "$SMPROGRAMS\${PRODUCT_NAME}"
DeleteRegKey HKCU "Software\Classes\bitcoin"
DeleteRegKey HKCU "Software\Classes\lbc"
DeleteRegKey HKCU "Software\${PRODUCT_NAME}"
DeleteRegKey HKCU "${PRODUCT_UNINST_KEY}"
SectionEnd

View file

@ -0,0 +1,108 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: User-ID: Steve Dower (Python Release Signing) <steve.dower@microsoft.com>
Comment: Created: 2015-04-06 02:32
Comment: Type: 4096-bit RSA
Comment: Usage: Signing, Encryption, Certifying User-IDs
Comment: Fingerprint: 7ED10B6531D7C8E1BC296021FC624643487034E5
mQINBFUh1AUBEACdUPt6PwJVO23zGZqgtgBeA9JsO22dk3CMzrwPJdUmMd6mcRWa
vl4BoAba66fuC17GvOgGXimKI+iaw5Vt9QI3uSjUjFSfc24J8T7NB/yAr/0zEcex
raHD2dxT/JpE/iY0yWHxRlitvwGSw1Qlq3NnY8tDI1DJEJD+gBuCktvVvu1FfQTw
6bd+aEq0c4sWJHAOnKLuLH0pNFOznnynAFGPGBBsm/YwYc5BP2JVvka775LUjA+W
1h2Sgg3FAUPIm64pc4Pq6mUo6Tulw72xsWMpCL1/5atXNPXT6rJUOB8euTcNMr4l
1O6GKSsiLeLAuvq4bmhOKtLzjWzXnY1gDVoOfdgpD6o4ZHk4xiVsdVE8hCa/ylz8
1ZwRW2gGo2jP8t3hKciR2i+Qs+6lPNZpeFIxa6Uo9ER1IBgCHHapIR/UdcOFyoS0
MNn7Ui7DLQNM4gI/G17eG9tfvjW2dl4SgFSYWMq/OtXnPDUBGqFUWsn8adOL2PFL
B7kM5ZRTPc5SnY9hoSGa5E20rJZIXcpy1aygRz/xUjoKwNzAySSEyyIorUxZ8KaH
EEBQSsqwe04MXIENqnDozH0/cvP4JXEDSl8EkzMSCWSoavQSIYD5pQppyFQpGHqa
5CuOA25Ja+sgp2xqahtr3fEqZUknPQSoYlnJbaHnzsGSlRAVWMsklsZibQARAQAB
tEBTdGV2ZSBEb3dlciAoUHl0aG9uIFJlbGVhc2UgU2lnbmluZykgPHN0ZXZlLmRv
d2VyQG1pY3Jvc29mdC5jb20+iQEcBBABCAAGBQJYsBphAAoJEEhSKohZ29goZggI
ALKlgyoecD5v3ulh1eoctRqtCOxkAoENEfPt3l5x6N8Wq89yHzf10T1rVioEXOHh
Di1m37DDoQmRJD0sOYQymq10xDGRYAJjyOf3X0pvRkZ+F7T0U4dSV3DasLIHcN26
kRwv1yCYsf0QvhgT6EJZKyUNHtV9qrb9u3A1Zp6epC/EyT8zMZj+21GzTUrnbnug
3Ak9p7+APCZS4Ahh9ZHFuD38MZ7+OwrUd6ot+6cbb1nnQLSAGQOHSp6EP6ktrnsK
zts0L+tzHurxtJgUkR01imJuSFfYpLoZa/L7qXNyEpEUTC/SWzRWD9y2QkM7DLzX
caReVAyJr9rix1lDQbEFIquJAhwEEAEIAAYFAlW2TwMACgkQKeBHm5nIo5fahg/+
IQSSE/yH8Cf82PYI7IGqDVNwRw2o7dq8iscB+fhFHfFFhXANwUUFpzPeDMrMrdmq
Bke7Vg1D3bIFocXYOiNwf2J7f4mBO6OL0VAvDX02Vyh/C2ZSc15uZyU6CWFQMCG8
JOSmgQFs3kMHkL4qtut1Y5reoYesmteIe06UVyRw8yT1R1BkxP2whZ97qwsvUUE9
cVD08wCvH486efw7EswIzYGa1KcZXji0MvjXfksVtkEQQbxMMI7SVXo0345ZReww
buioGL5gvvAPObgU43skORanFHFxiHEKmqgHBHXK/LKqaFUFMKcb4iFTNs2XKrhE
XsEi5EMI1AFsJzjcXRqT50Wi2cZhXeRc70uF6gzqrdWvowa2oOPiO6zGDiTqZCW1
AArk/QBzGtPjVh+nKEdHwnvpK9913UAkAN682h8QkoVPYXOvIKDYZRBr5EfpUyQt
y2r9MYewz0YN4zlGP1PFS9FxncdSZiZJqQVif0CkOp1tdSxLynHcujQgATZNtgcu
X9JwUwPp60MurgOcIZiW3nZw/z/5vzBBadSa9/TIFSJAFNBlqeKdIGQuik0UH+Cz
RRtSFb38F7jMPwr0QUSktuntQ0HWuvNqj4N8DFm45/n5rN190eRotrVDXZmjGein
qWPITuICslGIKAp+Q6y3t7JA71MIbeu/ZY6ZcftOka6JAhwEEAEIAAYFAlZRWicA
CgkQxiNM8COVzQq5bRAAktnXceO3GCivMt9yR1Qr0Ov4A4Q+CJSIL45efLFmS30k
cbkHHtaq+0FZNh2ZaMartC16MUja4a2OUejg53VBhaSVkQrVk/6M/HA6/o6CvIhb
FW/5C+nRWBd5gfvwsWvjrtC3cKZco4wg+yYclkDbSH+2EPDZOKIHpBy46YTz9WQ1
8SJ51WVkNUNiZqRBA6Ny5GFoyd6EpWZYEPelmzNemv3zOrQdVzLV24/mLejcLL2t
KmI6ngX4XViXUCRUU3MH8/V+V2YTQGcTM/6HGaHpN0LTqknf6zEto9q9FiRTaiU2
kzExhBq8Qf+cVqwm+1kMt0FGOgpT47VBWMeUWq62gQ3h5NfAs4DfriLgNURlTC1d
JYAEquFhB/8oBQD1h/d9CjQyk88iib2pJInRBDsK2FcfQBap9iaeBFYoBWTzMQJx
g+RuWK1wIm2n0oqa5urBYZtRHE5RIdDP8ZLogrBOFkfXGJxlRBQD1Gab77qohdp0
SnErGw4Ne3gJH/SNhK+zzHkHERIrRZCR95zdYkKfZ2jyOPzSuABVRigEQVQPCDn0
hbv3cblTCeJYwG2mfRdmfyqSMALKIgXe9yvJ2kl8QgaVOsJjNfQzIKeoHFPIm5Uw
3YB6jgDFc5uzEaH7WSz74A7KhGYjC7huw2TugosHbWxphJKddwxfK1WujYaAeJyJ
AhwEEAEIAAYFAlf2sPIACgkQfb+tds3soNuXEQ//XkWYHmJsKyeDZC8MFU+/vsVq
dhnFs6UXZkvf7MoNFkuMDL+zgVoMpFHftTdyBqNAoEnndakk212jK8YWF8g4kQXI
a9uMRqJLM4mqCl9yco/twJ9z9EMA+JLSXYK0ZbTkLdutSDZEDKgpHbmekx2C1OsW
lRLs9PahF5PAZQs0N+m+LJBnw6bEHOSTv4OE5uVUf9nvdes3OARvkGSEGURNmUaF
chxWtZ/SF1q9Jfj0K/xgs9Gt855oueveRXLIGpjiEVoKH/drsgyKFMJVrpZDDgS4
GVXG8bq3GTFiMAs7BPPd9bjI+jgvqttgItZcYsW/IQK1BIoG6Fere4cPvu+IshCc
km9T8nOK98tZuov8hLbND9mW2d7LChJI1r/HbzbKIl0k6OigdFMrJlun2zmtDxT9
Tp3uxOYSaW2YggcpNUjI28tv6AwoA8okVY93LWjO5kdZGkbliRnf/eJy7NJYn0LO
ogsvMUJClRAGnZTHLEr32Whq0MImlXa43kr6oPJT5dwXXyw5ELstEQztczCd1PYB
kbQHUpD5j3PwgNVOinCnbd4pc/qVtYSqpg2g6TJi1XiJ1638jhn2k+i8wop/dyet
iN8lGR76twYGex9AavEAUpVR9r6qfpp4KBibEhdvL6o2O03RQu17GcRzXSAYzmUi
5U5jZ3dBz5MYUjgUZM+JAhwEEAEKAAYFAllTh9gACgkQXLNh5VL7DRAk7Q//X8eU
hwEvl/d9Sv2kBNCZFjAW3QmZp2L/sxhScJZXrOFzKUdmjap9Xlul1qr6/Wif7YLK
bOdNUI7KziEBn+9SEd90XauoVkzU2F0Jn9ILGQfUHAIpocRTKuCwBrncaBozHQwD
O3Dk33AhZ6lqTv/AVLRKHQXwigGTBJxK4cCEZ+VwK9tKk6BrQB48Rm7pg9HF5ey5
JGPRWgUnn1v0IJN5ysZ5m9ChYbqF8VwvMw0txmgKgvdDKpXbF/S59Bp4TH/7Dr2D
kAeNTcuzTFBaFE+siMgksZIYKZ1VkVoiN2qQA7ZaA5LQbUom0WdrKZGefFfPt9ES
A4wyL3OfxRsmWmd/5Fxrwm1VbzgPoMd1Dc5ExlyqnecdGzDui2bmltNqRJd9ytRq
6YUGYzXp4qQkWO61CoC3mkm2M8Ex7DGbUtXhdg0zoa08w9lXuOtHVhY7XlLWjO1U
p8cp4DVxsN/wOXtyH1pcleGo4aEsgyU/DH57prFLGz7Egp2JhRDHnZmlonWp74G1
VLfqkOqZlqTU4mPA827C8qPCx6cMsRvFS7OEiDBswkFWBKjkUCw4rLC1tBMBCxJW
tZlc+Y0LNyOryJ3h6EJmRIHO57oLen345e1WOi4ROOC/wQMErFk7B3P41Lqmrwb8
HGuKn3ca+Aw70hVrZ+7Q3RRFTLlOS/vv107Fqu6JAjkEEwEIACMFAlUh1AUCGwMH
CwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAAKCRD8YkZDSHA05RfdD/97wPXnoe7e
ipP7UXQ942z1buV6pTGv0Lea2aHn20o2BBjHp97YXroF/e/8W6h+Y+Fq8hWoXdYJ
dC9DVgzJhvbXAIG8VrF6/IDGQ62r4ff/AIyQY+kiCOCCVhjwuqOTjVYw2pYRUcI3
UwXVPeptDSXcIZkHCLtEUnS5YMTdkPuZrAmucCCnfcJtevXbHD2yJYP4vwfXMbal
sNBDKJi6uYAFc4yv+/DyS13rfXJvu2pYGvtRd+fs7mBETvUTubhI440pIss6TX6M
lxWexX6Ty8vI5HCQT281H4zqdbe5GdzGmIx1EiYx1sJbgSBNqCh5sRJY5/BXzVJ3
dfM/Mv5QYY4ulO/qUNFdC8f1cZm0euOo3maB4jY+Sjaff7t0WIz0GufO4dHARwJg
3s0LO9Wf5+z/fbWOMcfvvcfaHNbhaKWk16kslc/g7NYvMfOuleM06YGyGPz//a9c
baX53OiMupNvLlhyPO5NfGppvRn5xAElcAw1RLhHJcgvTtIs/zVVfHPaK41u8A9c
XKnmIUC39K4BGvOpPzEvCdQ2ZbAqzQLmZ1UICr15w1Nfs6uoERJbnuq+JgOPOcOk
ezAWELi5LdZTElnpJpZPTDQ03+3GvxD4R9sR+l5RT8Ul7kF+3PPPzekfQzF+Nisr
BhPFb2lPt3Hw32FgTTIuXCMRTKEBb/6z77kCDQRVIdQFARAAtmnsZ9A8ovJIJ9Rl
WeIylEhHRyQifqzgc/r50uDZVPBjewOA462LjH3+F6zFGEkU+q2aqSe0A0SJPF/W
hj6MNYXLoibxi5D4mGkoIao9ExnXt4LXAc6ogQpY6vFQBJU5Nr8XCefQbm0loa/o
y5uK8JHLWCZ2jAossnVpzDwNeN27+B8h5+OifnWhQCTun1xz5EJiyc0yoBmf46zf
mU4CMUBsPvrXcLmw4J3wp35qmrHg1tNyPhd7VBlikMrgtrWX9IaPZ40dnrGG/WjO
FYB3CKxGb0pTCj7GC4ubxo2upeWZqHLmdIVc7Nzsfp8EcwJbTj+jZ2Zfq6F8y+je
sbgh8CaxYn4hEs23aPYRq5H4/buVmZhUw3/AAL9ZmyX6AtAQ0HktVtQe7ykP7DLs
EpeLG+vPJFY363QeDsLHwOoxnZSfGziVlB4N/KqIkixNWcFTG8GSE1zKcdJVNoW+
3MB3+FtMZWUJhH0FyKg5qLaJCtC7Yo5gsddU+QCqTn6gcZBnMX5j4LaAmW4hh1RX
ffwwsbfviK5uhXQCeUnbUaokieetDx4s6Kay6t9ahTRr0r/Z3VWzvr+xATxNWZzi
xTdezCGOB2ycZ0vq4bKXBuN8CAyOy5X1hf7Rc1BiAVQCILHJDtz0Ak/Hax6DAa2A
Hnx9YlugHQf000KroLEY+GaxqYEAEQEAAYkCHwQYAQgACQUCVSHUBQIbDAAKCRD8
YkZDSHA05RtyEACdOEmGolL1xG6I+lDVdot6oBZqC9e021aLWqCUpWJFDp0m0aTm
CfmOI1gTaFjScxhq1W0GPUoJKUZhk3tlVfdSCtUckI+xuWKEfqJYtvUtTXpK4jDe
aZBovJ3KNpJRIynbr1566zCSQJhHiCGWmE/M5KN3gPsORbCBQXEkONSVsslf1Wm6
6hU6uqSWUaceD+4fl5LClbck1DPWchAP7+uLKPEOtORyH6KRTgKl73zYo7xU1K4Q
MN/1aMjobPkqNvvkXnUNwO7QMz18Nx+WqPc4ksJgW1O1aPQ2qL/ARY5jatZ6BBd7
iytfz7d6JOh0FOIlmhBqbWd7fEGrLsSA+EjBGBwW5BnIMmxP1xhjhwrcI18y8kAK
5UzdW2hbbAlc2rlsuxEc+xOYh8kGcc+mZ1j/aMn4gALsTbSO/0T+YJhfODNnL1dC
j7oPbJGmmG6pb/o7P4azBUVC9lHOuV3XlAPjSmJylnNsV7+PxwPlXlvKgh4S4C4Z
PUc/iPetsxXR2djccOoNxVU4CqJBqYKgul/pUphXkh7QfEKyH+42UETbVhstdBVU
azJ6SeUnv9ClVDGsCEhfEZfNOnOoDzJGxDfESoAw7ih91vIhTyHHsK83p2HLDMLP
ptLzx/0AFBfo6MWGGpd2RSnMWNbvh59wiThlDeI+Das3ln5nsAo67dMYdA==
=fjOq
-----END PGP PUBLIC KEY BLOCK-----

205
contrib/build-wine/prepare-wine.sh Executable file → Normal file
View file

@ -1,150 +1,137 @@
#!/bin/bash
# Please update these carefully, some versions won't work under Wine
NSIS_FILENAME=nsis-3.03-setup.exe
NSIS_FILENAME=nsis-3.05-setup.exe
NSIS_URL=https://prdownloads.sourceforge.net/nsis/$NSIS_FILENAME?download
NSIS_SHA256=bd3b15ab62ec6b0c7a00f46022d441af03277be893326f6fea8e212dc2d77743
NSIS_SHA256=1a3cc9401667547b9b9327a177b13485f7c59c2303d4b6183e7bc9e6c8d6bfdb
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.22.7z
LIBUSB_URL=https://prdownloads.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.22/$LIBUSB_FILENAME?download
LIBUSB_SHA256=671f1a420757b4480e7fadc8313d6fb3cbb75ca00934c417c1efa6e77fb8779b
LIBUSB_REPO="https://github.com/libusb/libusb.git"
LIBUSB_COMMIT=e782eeb2514266f6738e242cdcb18e3ae1ed06fa
# ^ tag v1.0.23
PYTHON_VERSION=3.6.6
PYINSTALLER_REPO="https://github.com/SomberNight/pyinstaller.git"
PYINSTALLER_COMMIT=e934539374e30d1500fcdbe8e4eb0860413935b2
# ^ tag 3.6, plus a custom commit that fixes cross-compilation with MinGW
PYTHON_VERSION=3.6.8
## These settings probably don't need change
export WINEPREFIX=/opt/wine64
#export WINEARCH='win32'
export WINEDEBUG=-all
PYHOME=c:/python$PYTHON_VERSION
PYTHON_FOLDER="python3"
PYHOME="c:/$PYTHON_FOLDER"
PYTHON="wine $PYHOME/python.exe -OO -B"
# based on https://superuser.com/questions/497940/script-to-verify-a-signature-with-gpg
verify_signature() {
local file=$1 keyring=$2 out=
if out=$(gpg --no-default-keyring --keyring "$keyring" --status-fd 1 --verify "$file" 2>/dev/null) &&
echo "$out" | grep -qs "^\[GNUPG:\] VALIDSIG "; then
return 0
else
echo "$out" >&2
exit 1
fi
}
verify_hash() {
local file=$1 expected_hash=$2
actual_hash=$(sha256sum $file | awk '{print $1}')
if [ "$actual_hash" == "$expected_hash" ]; then
return 0
else
echo "$file $actual_hash (unexpected hash)" >&2
rm "$file"
exit 1
fi
}
download_if_not_exist() {
local file_name=$1 url=$2
if [ ! -e $file_name ] ; then
wget -O $PWD/$file_name "$url"
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
here="$(dirname "$(readlink -e "$0")")"
. "$CONTRIB"/build_tools_util.sh
info "Booting wine."
wine 'wineboot'
# HACK to work around https://bugs.winehq.org/show_bug.cgi?id=42474#c22
# needed for python 3.6+
rm -f /opt/wine-stable/lib/wine/fakedlls/api-ms-win-core-path-l1-1-0.dll
rm -f /opt/wine-stable/lib/wine/api-ms-win-core-path-l1-1-0.dll.so
cd /tmp/electrum-build
cd "$CACHEDIR"
mkdir -p $WINEPREFIX/drive_c/tmp
# Install Python
info "Installing Python."
# note: you might need "sudo apt-get install dirmngr" for the following
# 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"
for server in $(shuf -e ha.pool.sks-keyservers.net \
hkp://p80.pool.sks-keyservers.net:80 \
keyserver.ubuntu.com \
hkp://keyserver.ubuntu.com:80) ; do
retry gpg --no-default-keyring --keyring $KEYRING_PYTHON_DEV --keyserver "$server" --recv-keys $KEYLIST_PYTHON_DEV \
&& break || : ;
done
gpg --no-default-keyring --keyring $KEYRING_PYTHON_DEV --import "$here"/gpg_keys/7ED10B6531D7C8E1BC296021FC624643487034E5.asc
PYTHON_DOWNLOADS="$CACHEDIR/python$PYTHON_VERSION"
mkdir -p "$PYTHON_DOWNLOADS"
for msifile in core dev exe lib pip tools; do
echo "Installing $msifile..."
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
download_if_not_exist "$PYTHON_DOWNLOADS/${msifile}.msi" "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi"
download_if_not_exist "$PYTHON_DOWNLOADS/${msifile}.msi.asc" "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi.asc"
verify_signature "$PYTHON_DOWNLOADS/${msifile}.msi.asc" $KEYRING_PYTHON_DEV
wine msiexec /i "$PYTHON_DOWNLOADS/${msifile}.msi" /qb TARGETDIR=$PYHOME
done
# upgrade pip
$PYTHON -m pip install pip --upgrade
info "Installing build dependencies."
$PYTHON -m pip install --no-dependencies --no-warn-script-location -r "$CONTRIB"/deterministic-build/requirements-wine-build.txt
# Install pywin32-ctypes (needed by pyinstaller)
$PYTHON -m pip install pywin32-ctypes==0.1.2
info "Installing dependencies specific to binaries."
$PYTHON -m pip install --no-dependencies --no-warn-script-location -r "$CONTRIB"/deterministic-build/requirements-binaries.txt
# install PySocks
$PYTHON -m pip install win_inet_pton==1.0.1
info "Installing ZBar."
download_if_not_exist "$CACHEDIR/$ZBAR_FILENAME" "$ZBAR_URL"
verify_hash "$CACHEDIR/$ZBAR_FILENAME" "$ZBAR_SHA256"
wine "$CACHEDIR/$ZBAR_FILENAME" /S
$PYTHON -m pip install -r $here/../deterministic-build/requirements-binaries.txt
info "Installing NSIS."
download_if_not_exist "$CACHEDIR/$NSIS_FILENAME" "$NSIS_URL"
verify_hash "$CACHEDIR/$NSIS_FILENAME" "$NSIS_SHA256"
wine "$CACHEDIR/$NSIS_FILENAME" /S
# Install PyInstaller
$PYTHON -m pip install https://github.com/ecdsa/pyinstaller/archive/fix_2952.zip
# Install ZBar
download_if_not_exist $ZBAR_FILENAME "$ZBAR_URL"
verify_hash $ZBAR_FILENAME "$ZBAR_SHA256"
wine "$PWD/$ZBAR_FILENAME" /S
info "Compiling libusb..."
(
cd "$CACHEDIR"
if [ -f "libusb/libusb/.libs/libusb-1.0.dll" ]; then
info "libusb-1.0.dll already built, skipping"
exit 0
fi
rm -rf libusb
mkdir libusb
cd libusb
# Shallow clone
git init
git remote add origin $LIBUSB_REPO
git fetch --depth 1 origin $LIBUSB_COMMIT
git checkout -b pinned FETCH_HEAD
echo "libusb_1_0_la_LDFLAGS += -Wc,-static" >> libusb/Makefile.am
./bootstrap.sh || fail "Could not bootstrap libusb"
host="i686-w64-mingw32"
LDFLAGS="-Wl,--no-insert-timestamp" ./configure \
--host=$host \
--build=x86_64-pc-linux-gnu || fail "Could not run ./configure for libusb"
make -j4 || fail "Could not build libusb"
${host}-strip libusb/.libs/libusb-1.0.dll
) || fail "libusb build failed"
cp "$CACHEDIR/libusb/libusb/.libs/libusb-1.0.dll" $WINEPREFIX/drive_c/tmp/ || fail "Could not copy libusb to its destination"
# Upgrade setuptools (so Electrum can be installed later)
$PYTHON -m pip install setuptools --upgrade
# Install NSIS installer
download_if_not_exist $NSIS_FILENAME "$NSIS_URL"
verify_hash $NSIS_FILENAME "$NSIS_SHA256"
wine "$PWD/$NSIS_FILENAME" /S
# copy libsecp dll (already built)
cp "$PROJECT_ROOT/electrum/libsecp256k1-0.dll" $WINEPREFIX/drive_c/tmp/ || fail "Could not copy libsecp to its destination"
download_if_not_exist $LIBUSB_FILENAME "$LIBUSB_URL"
verify_hash $LIBUSB_FILENAME "$LIBUSB_SHA256"
7z x -olibusb $LIBUSB_FILENAME -aoa
cp libusb/MS32/dll/libusb-1.0.dll $WINEPREFIX/drive_c/python$PYTHON_VERSION/
info "Building PyInstaller."
# we build our own PyInstaller boot loader as the default one has high
# anti-virus false positives
(
cd "$WINEPREFIX/drive_c/electrum"
ELECTRUM_COMMIT_HASH=$(git rev-parse HEAD)
cd "$CACHEDIR"
rm -rf pyinstaller
mkdir pyinstaller
cd pyinstaller
# Shallow clone
git init
git remote add origin $PYINSTALLER_REPO
git fetch --depth 1 origin $PYINSTALLER_COMMIT
git checkout -b pinned FETCH_HEAD
rm -fv PyInstaller/bootloader/Windows-*/run*.exe || true
# add reproducible randomness. this ensures we build a different bootloader for each commit.
# if we built the same one for all releases, that might also get anti-virus false positives
echo "const char *electrum_tag = \"tagged by Electrum@$ELECTRUM_COMMIT_HASH\";" >> ./bootloader/src/pyi_main.c
pushd bootloader
# cross-compile to Windows using host python
python3 ./waf all CC=i686-w64-mingw32-gcc CFLAGS="-static -Wno-dangling-else -Wno-error=unused-value"
popd
# sanity check bootloader is there:
[[ -e PyInstaller/bootloader/Windows-32bit/runw.exe ]] || fail "Could not find runw.exe in target dir!"
) || fail "PyInstaller build failed"
info "Installing PyInstaller."
$PYTHON -m pip install --no-dependencies --no-warn-script-location ./pyinstaller
# add dlls needed for pyinstaller:
cp $WINEPREFIX/drive_c/python$PYTHON_VERSION/Lib/site-packages/PyQt5/Qt/bin/* $WINEPREFIX/drive_c/python$PYTHON_VERSION/
mkdir -p $WINEPREFIX/drive_c/tmp
cp secp256k1/libsecp256k1.dll $WINEPREFIX/drive_c/tmp/
echo "Wine is configured."
info "Wine is configured."

0
contrib/build-wine/sign.sh Executable file → Normal file
View file

22
contrib/build-wine/unsign.sh Executable file → Normal file
View file

@ -24,28 +24,8 @@ for mine in $(ls dist/*.exe); do
echo "Downloading https://download.electrum.org/$version/$f"
wget -q https://download.electrum.org/$version/$f -O signed/$f
out="signed/stripped/$f"
size=$( wc -c < $mine )
# Step 1: Remove PE signature from signed binary
# Remove PE signature from signed binary
osslsigncode remove-signature -in signed/$f -out $out > /dev/null 2>&1
# Step 2: Remove checksum and padding from signed binary
python3 <<EOF
pe_file = "$out"
size= $size
with open(pe_file, "rb") as f:
binary = bytearray(f.read())
pe_offset = int.from_bytes(binary[0x3c:0x3c+4], byteorder="little")
checksum_offset = pe_offset + 88
for b in range(4):
binary[checksum_offset + b] = 0
l = len(binary)
n = l - size
if n > 0:
if binary[-n:] != bytearray(n):
print('expecting failure for', str(pe_file))
binary = binary[:size]
with open(pe_file, "wb") as f:
f.write(binary)
EOF
chmod +x $out
if cmp -s $out $mine; then
echo "Success: $f"

133
contrib/build_tools_util.sh Normal file
View file

@ -0,0 +1,133 @@
#!/usr/bin/env bash
# Set a fixed umask as this leaks into docker containers
umask 0022
RED='\033[0;31m'
BLUE='\033[0;34m'
YELLOW='\033[0;33m'
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
}
function warn {
printf "\r⚠ ${YELLOW}WARNING:${NC} ${1}\n"
}
# based on https://superuser.com/questions/497940/script-to-verify-a-signature-with-gpg
function verify_signature() {
local file=$1 keyring=$2 out=
if out=$(gpg --no-default-keyring --keyring "$keyring" --status-fd 1 --verify "$file" 2>/dev/null) &&
echo "$out" | grep -qs "^\[GNUPG:\] VALIDSIG "; then
return 0
else
echo "$out" >&2
exit 1
fi
}
function verify_hash() {
local file=$1 expected_hash=$2
actual_hash=$(sha256sum $file | awk '{print $1}')
if [ "$actual_hash" == "$expected_hash" ]; then
return 0
else
echo "$file $actual_hash (unexpected hash)" >&2
rm "$file"
exit 1
fi
}
function download_if_not_exist() {
local file_name=$1 url=$2
if [ ! -e $file_name ] ; then
wget -O $file_name "$url"
fi
}
# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh
function 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
}
function gcc_with_triplet()
{
TRIPLET="$1"
CMD="$2"
shift 2
if [ -n "$TRIPLET" ] ; then
"$TRIPLET-$CMD" "$@"
else
"$CMD" "$@"
fi
}
function gcc_host()
{
gcc_with_triplet "$GCC_TRIPLET_HOST" "$@"
}
function gcc_build()
{
gcc_with_triplet "$GCC_TRIPLET_BUILD" "$@"
}
function host_strip()
{
if [ "$GCC_STRIP_BINARIES" -ne "0" ] ; then
case "$BUILD_TYPE" in
linux|wine)
gcc_host strip "$@"
;;
darwin)
# TODO: Strip on macOS?
;;
esac
fi
}
# on MacOS, there is no realpath by default
if ! [ -x "$(command -v realpath)" ]; then
function realpath() {
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
fi
export SOURCE_DATE_EPOCH=1530212462
export PYTHONHASHSEED=22
# Set the build type, overridden by wine build
export BUILD_TYPE="${BUILD_TYPE:-$(uname | tr '[:upper:]' '[:lower:]')}"
# No additional autoconf flags by default
export AUTOCONF_FLAGS=""
# Add host / build flags if the triplets are set
if [ -n "$GCC_TRIPLET_HOST" ] ; then
export AUTOCONF_FLAGS="$AUTOCONF_FLAGS --host=$GCC_TRIPLET_HOST"
fi
if [ -n "$GCC_TRIPLET_BUILD" ] ; then
export AUTOCONF_FLAGS="$AUTOCONF_FLAGS --build=$GCC_TRIPLET_BUILD"
fi
export GCC_STRIP_BINARIES="${GCC_STRIP_BINARIES:-0}"

7
contrib/deterministic-build/check_submodules.sh Executable file → Normal file
View file

@ -18,13 +18,6 @@ function get_git_mtime {
fail=0
for f in icons/* "icons.qrc"; do
if (( $(get_git_mtime "$f") > $(get_git_mtime "contrib/deterministic-build/electrum-icons/") )); then
echo "Modification time of $f (" $(get_git_mtime --readable "$f") ") is newer than"\
"last update of electrum-icons"
fail=1
fi
done
if [ $(date +%s -d "2 weeks ago") -gt $(get_git_mtime "contrib/deterministic-build/electrum-locale/") ]; then
echo "Last update from electrum-locale is older than 2 weeks."\

@ -1 +0,0 @@
Subproject commit 0b8cbcca428ceb791527bcbb2ef2b36b4ab29c73

@ -1 +1 @@
Subproject commit 27e36687f4b0fbd126628bdde80758b63ade7347
Subproject commit aafd932d37f35a1f276909b6ec27d2f7a60e606a

View file

@ -1,7 +1,10 @@
#!/usr/bin/env python3
import sys
import requests
try:
import requests
except ImportError as e:
sys.exit(f"Error: {str(e)}. Try 'sudo python3 -m pip install <module-name>'")
def check_restriction(p, r):

View file

@ -1,56 +1,27 @@
pip==10.0.1 \
--hash=sha256:717cdffb2833be8409433a93746744b59505f42146e8d37de6c62b430e25d6d7 \
--hash=sha256:f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68
pycryptodomex==3.6.4 \
--hash=sha256:0461e88a7199f9e88f9f90c2c1e109e9e1f7bbb94dc6192e5df52829d31510c1 \
--hash=sha256:08d0aba5a72e8af5da118ac4b6a5d75befceca7dd92a031b040ed5ff4417cec2 \
--hash=sha256:0e22d47935d5fa95f556d5f5857576bc6750233964de06a840d58459010c3889 \
--hash=sha256:10ef21d1728ec0b8afc4f8e1d8d9ea66f317154ea18731a4a05bd996cdc33fdf \
--hash=sha256:1962b81eef81bf5c42d625816904a22a0bd23d15ca5d49891a54e3c0d0189d84 \
--hash=sha256:24aae88efe3cbcb4a9cf840b2c352e7de1d6c2c5b3df37ff99b5c7e271e8f3a8 \
--hash=sha256:43ad6d1d7ca545d53360bf412ee70fcb9ede876b4376fc6db06fc7328f70588c \
--hash=sha256:4daabe7c0404e673b9029aa43761c779b9b4df2cbe11ccd94daded6a0acd8808 \
--hash=sha256:4e15af025e02b04b0d0728e8248e4384d3dc7a3a89a020f5bd4d04ef2c5d9d4c \
--hash=sha256:5b4d3c4a069a05972e0ed7111071bbcb4727ac652b5d7e8f786e8ea2fe63306b \
--hash=sha256:67ad8b2ad15a99ae70e287454a112f67d2abaf160ee9c97f9daebf2296066447 \
--hash=sha256:6d7e6fb69d9fd2c57e177f8a9cdf6489a725da77568e3d0a226c7dd18504396a \
--hash=sha256:7907d7a5adde7cd07d19f129a4afa892b68b0b52a07eaf989e48e2677040b4bf \
--hash=sha256:88210edafd564c8ff4a68716aaf0627e3bc43e9c192a33d6f5616743f72c2d9b \
--hash=sha256:8a6b14a90bdcbcdc268acae87126c33bf4250d3842803a93a548d7c10135893a \
--hash=sha256:94a10446ad61965516aecd610a2dd28d79ab1dfd8723903e1bd19ffa985c208e \
--hash=sha256:99bda900a0bf6f9e6c69bdeb6114f7f6730b9d36a47bc1fe144263ce85bfc403 \
--hash=sha256:9dae2e738622bd35ba82fe0b06f773be137a14e6b28defb2e36efc2d809cd28a \
--hash=sha256:a04cd6021ff2756c38135a95f81b980485507bccbff4d2b8f62e537552270471 \
--hash=sha256:a3b61625b60dd5e72556520a77464e2ac568c20b8ad12ea1f4443bf5051dc624 \
--hash=sha256:a9a91fd9e7967a5bad88d542c9fce09323e15d16cb6fa9b8978390e46e68cbdf \
--hash=sha256:afc44f1b595bd736ec3762dd9a2d0ef276a6ac560c85f643acfc4c0bf0c73384 \
--hash=sha256:b5f3c8912b36e6abb843a51eecb414a1161f80c0ca0b65066c23aa449b5f98db \
--hash=sha256:cc07c8b7686dd7093f33067a02b92f4fed860d75ad2bcc4e60624f70fdb94576 \
--hash=sha256:da646eddbe026306fd1cb2c392a9aee4ebea13f2a9add9af303bb3151786a5d8 \
--hash=sha256:df93eaccd5c09e6380fab8f15c06a89944415e4bb9af64a94f467ce4c782ff8e \
--hash=sha256:e667303019770834354c75022ab0324d5ae5bf7cd7015939678033a58f87ee70 \
--hash=sha256:f921219040ce994c9118b7218b7f7b4e9394e507c97cfc869ce5358437fc26cd
PyQt5==5.10.1 \
--hash=sha256:1e652910bd1ffd23a3a48c510ecad23a57a853ed26b782cd54b16658e6f271ac \
--hash=sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b \
--hash=sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7 \
--hash=sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61
setuptools==40.0.0 \
--hash=sha256:012adb8e25fbfd64c652e99e7bab58799a3aaf05d39ab38561f69190a909015f \
--hash=sha256:d68abee4eed409fbe8c302ac4d8429a1ffef912cd047a903b5701c024048dd49
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
wheel==0.31.1 \
--hash=sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c \
--hash=sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f
pip==19.3.1 \
--hash=sha256:21207d76c1031e517668898a6b46a9fb1501c7a4710ef5dfd6a40ad9e6757ea7 \
--hash=sha256:6917c65fc3769ecdc61405d3dfd97afdedd75808d200b2838d7d961cebc0c2c7
PyQt5==5.11.3 \
--hash=sha256:517e4339135c4874b799af0d484bc2e8c27b54850113a68eec40a0b56534f450 \
--hash=sha256:ac1eb5a114b6e7788e8be378be41c5e54b17d5158994504e85e43b5fca006a39 \
--hash=sha256:d2309296a5a79d0a1c0e6c387c30f0398b65523a6dcc8a19cc172e46b949e00d \
--hash=sha256:e85936bae1581bcb908847d2038e5b34237a5e6acc03130099a78930770e7ead
PyQt5-sip==4.19.13 \
--hash=sha256:125f77c087572c9272219cda030a63c2f996b8507592b2a54d7ef9b75f9f054d \
--hash=sha256:14c37b06e3fb7c2234cb208fa461ec4e62b4ba6d8b32ca3753c0b2cfd61b00e3 \
--hash=sha256:1cb2cf52979f9085fc0eab7e0b2438eb4430d4aea8edec89762527e17317175b \
--hash=sha256:4babef08bccbf223ec34464e1ed0a23caeaeea390ca9a3529227d9a57f0d6ee4 \
--hash=sha256:53cb9c1208511cda0b9ed11cffee992a5a2f5d96eb88722569b2ce65ecf6b960 \
--hash=sha256:549449d9461d6c665cbe8af4a3808805c5e6e037cd2ce4fd93308d44a049bfac \
--hash=sha256:5f5b3089b200ff33de3f636b398e7199b57a6b5c1bb724bdb884580a072a14b5 \
--hash=sha256:a4d9bf6e1fa2dd6e73f1873f1a47cee11a6ba0cf9ba8cf7002b28c76823600d0 \
--hash=sha256:a4ee6026216f1fbe25c8847f9e0fbce907df5b908f84816e21af16ec7666e6fe \
--hash=sha256:a91a308a5e0cc99de1e97afd8f09f46dd7ca20cfaa5890ef254113eebaa1adff \
--hash=sha256:b0342540da479d2713edc68fb21f307473f68da896ad5c04215dae97630e0069 \
--hash=sha256:f997e21b4e26a3397cb7b255b8d1db5b9772c8e0c94b6d870a5a0ab5c27eacaa
setuptools==42.0.2 \
--hash=sha256:c5b372090d7c8709ce79a6a66872a91e518f7d65af97fca78135e1cb10d4b940 \
--hash=sha256:c8abd0f3574bc23afd2f6fd2c415ba7d9e097c8a99b845473b0d957ba1e2dac6
wheel==0.33.6 \
--hash=sha256:10c9da68765315ed98850f8e048347c3eb06dd81822dc2ab1d4fde9dc9702646 \
--hash=sha256:f4da1763d3becf2e2cd92a14a7c920f0f00eca30fdde9ea992c836685b9faf28

View file

@ -1,49 +1,56 @@
btchip-python==0.1.27 \
--hash=sha256:e58a941abbb2d8901bf4858baa18012537c60812c7f895f9a039113ecce3032b
certifi==2018.4.16 \
--hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7 \
--hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0
btchip-python==0.1.28 \
--hash=sha256:da09d0d7a6180d428833795ea9a233c3b317ddfcccea8cc6f0eba59435e5dd83
certifi==2019.11.28 \
--hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \
--hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f
chardet==3.0.4 \
--hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \
--hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691
click==6.7 \
--hash=sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d \
--hash=sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b
Cython==0.28.4 \
--hash=sha256:01487236575df8f17b46982071438dce4f7eaf8acc8fb99fca3510d343cd7a28 \
--hash=sha256:0671d17c7a27634d6819246e535241b951141ed0e3f6f2a6d618fd32344dae3e \
--hash=sha256:0e6190d6971c46729f712dd7307a9c0a8c027bfa5b4d8f2edef106b01759926c \
--hash=sha256:202587c754901d0678bd6ff89c707f099987928239049a528470c06c6c922cf8 \
--hash=sha256:345197ba9278cf6a914cb7421dc665a0531a219b0072abf6b0cebfdf68e75725 \
--hash=sha256:3a296b8d6b02f0e01ab04bedea658f43eef5ad2f8e586a820226ead1a677d9b1 \
--hash=sha256:484572a2b22823a967be106137a93f7d634db116b3f7accb37dbd760eda2fa9f \
--hash=sha256:4c67c9c803e50ceff32cc5e4769c50fc8ae8df9c4e5cc592ce8310b5a1076d23 \
--hash=sha256:539038087c321911745fc2e77049209b1231300d481cb4d682b2f95c724814b3 \
--hash=sha256:58113e0683c3688594c112103d7e9f2d0092fd2d8297a220240bea22e184dfdd \
--hash=sha256:65cb25ca4284804293a2404d1be3b5a98818be21a72791649bacbcfa4e431d41 \
--hash=sha256:699e765da2580e34b08473fc0acef3a2d7bcb7f13eb29401cd25236bcf000080 \
--hash=sha256:6b54c3470810cea49a8be90814d05c5325ceb9c5bf429fd86c36fc1b32dfc157 \
--hash=sha256:71ac1629e4eae2ed329be8caf45efea10bfe1af3d8767e12e64b83e4ea5a3250 \
--hash=sha256:722c179d3df8677f3daf45b1a2764678ed4f0aaddbaa7211a8a08ebfd907c0db \
--hash=sha256:76ac2b08d3d956d77b574bb43cbf1d37bd58b9d50c04ba281303e695854ebc46 \
--hash=sha256:7eff1157be9e26bf7494288c89979ca69d593a009e2c7420a739e2cf1e0635f5 \
--hash=sha256:99546c8696d27d0efa639c77b2f8af6e61dc3a5073caae4f27ffd991ca926f42 \
--hash=sha256:a0c263b31d335f29c11f4a9e98fbcd908d0731d4ea99bfd27c1c47caaeb4ca2e \
--hash=sha256:a29c66292605bff962adc26530c030607aa699206b12dfb84f131b0454e15df4 \
--hash=sha256:a4d3724c5a1ddd86d7d830d8e02c40151839b833791dd4b6fe9e144380fa7d37 \
--hash=sha256:aed9f33b19d542eea56c38ef3862ca56147f7903648156cd57eabb0fe47c35d6 \
--hash=sha256:b57e733dd8871d2cc7358c2e0fe33027453afffbcd0ea6a537f54877cad5131c \
--hash=sha256:d5bf4db62236e82955c40bafbaa18d54b20b5ceefa06fb57c7facc443929f4bd \
--hash=sha256:d9272dd71ab78e87fa34a0a59bbd6acc9a9c0005c834a6fc8457ff9619dc6795 \
--hash=sha256:e9d5671bcbb90a41b0832fcb3872fcbaca3d68ff11ea09724dd6cbdf31d947fb \
--hash=sha256:ee54646afb2b73b293c94cf079682d18d404ebd6c01122dc3980f111aec2d8ae \
--hash=sha256:f16a87197939977824609005b73f9ebb291b9653a14e5f27afc1c5d6f981ba39
ecdsa==0.13 \
--hash=sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c \
--hash=sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa
ckcc-protocol==0.8.0 \
--hash=sha256:bad1d1448423472df95ba67621fdd0ad919e625fbe0a4d3ba93648f34ea286e0 \
--hash=sha256:f0851c98b91825d19567d0d3bac1b28044d40a3d5f194c8b04c5338f114d7ad5
click==7.0 \
--hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \
--hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7
construct==2.9.45 \
--hash=sha256:2271a0efd0798679dea825ff47e22a4c550456a5db0ba8baa82f7eae0af0118c
Cython==0.29.10 \
--hash=sha256:0afa0b121b89de619e71587e25702e2b7068d7da2164c47e6eee80c17823a62f \
--hash=sha256:1c608ba76f7a20cc9f0c021b7fe5cb04bc1a70327ae93a9298b1bc3e0edddebe \
--hash=sha256:26229570d6787ff3caa932fe9d802960f51a89239b990d275ae845405ce43857 \
--hash=sha256:2a9deafa437b6154cac2f25bb88e0bfd075a897c8dc847669d6f478d7e3ee6b1 \
--hash=sha256:2f28396fbce6d9d68a40edbf49a6729cf9d92a4d39ff0f501947a89188e9099f \
--hash=sha256:3983dd7b67297db299b403b29b328d9e03e14c4c590ea90aa1ad1d7b35fb178b \
--hash=sha256:4100a3f8e8bbe47d499cdac00e56d5fe750f739701ea52dc049b6c56f5421d97 \
--hash=sha256:51abfaa7b6c66f3f18028876713c8804e73d4c2b6ceddbcbcfa8ec62429377f0 \
--hash=sha256:61c24f4554efdb8fb1ac6c8e75dab301bcdf2b7b739ed0c2b267493bb43163c5 \
--hash=sha256:700ccf921b2fdc9b23910e95b5caae4b35767685e0812343fa7172409f1b5830 \
--hash=sha256:7b41eb2e792822a790cb2a171df49d1a9e0baaa8e81f58077b7380a273b93d5f \
--hash=sha256:803987d3b16d55faa997bfc12e8b97f1091f145930dee229b020487aed8a1f44 \
--hash=sha256:99af5cfcd208c81998dcf44b3ca466dee7e17453cfb50e98b87947c3a86f8753 \
--hash=sha256:9faea1cca34501c7e139bc7ef8e504d532b77865c58592493e2c154a003b450f \
--hash=sha256:a7ba4c9a174db841cfee9a0b92563862a0301d7ca543334666c7266b541f141a \
--hash=sha256:b26071c2313d1880599c69fd831a07b32a8c961ba69d7ccbe5db1cd8d319a4ca \
--hash=sha256:b49dc8e1116abde13a3e6a9eb8da6ab292c5a3325155fb872e39011b110b37e6 \
--hash=sha256:bd40def0fd013569887008baa6da9ca428e3d7247adeeaeada153006227bb2e7 \
--hash=sha256:bfd0db770e8bd4e044e20298dcae6dfc42561f85d17ee546dcd978c8b23066ae \
--hash=sha256:c2fad1efae5889925c8fd7867fdd61f59480e4e0b510f9db096c912e884704f1 \
--hash=sha256:c81aea93d526ccf6bc0b842c91216ee9867cd8792f6725a00f19c8b5837e1715 \
--hash=sha256:da786e039b4ad2bce3d53d4799438cf1f5e01a0108f1b8d78ac08e6627281b1a \
--hash=sha256:deab85a069397540987082d251e9c89e0e5b2e3e044014344ff81f60e211fc4b \
--hash=sha256:e3f1e6224c3407beb1849bdc5ae3150929e593e4cffff6ca41c6ec2b10942c80 \
--hash=sha256:e74eb224e53aae3943d66e2d29fe42322d5753fd4c0641329bccb7efb3a46552 \
--hash=sha256:ee697c7ea65cb14915a64f36874da8ffc2123df43cf8bc952172e04a26656cd6 \
--hash=sha256:f37792b16d11606c28e428460bd6a3d14b8917b109e77cdbe4ca78b0b9a52c87 \
--hash=sha256:fd2906b54cbf879c09d875ad4e4687c58d87f5ed03496063fec1c9065569fd5d
ecdsa==0.14.1 \
--hash=sha256:64c613005f13efec6541bb0a33290d0d03c27abab5f15fbab20fb0ee162bdd8e \
--hash=sha256:e108a5fe92c67639abae3260e43561af914e7fd0d27bae6d2ec1312ae7934dfe
hidapi==0.7.99.post21 \
--hash=sha256:1ac170f4d601c340f2cd52fd06e85c5e77bad7ceac811a7bb54b529f7dc28c24 \
--hash=sha256:6424ad75da0021ce8c1bcd78056a04adada303eff3c561f8d132b85d0a914cb3 \
--hash=sha256:8d3be666f464347022e2b47caf9132287885d9eacc7895314fc8fefcb4e42946 \
--hash=sha256:92878bad7324dee619b7832fbfc60b5360d378aa7c5addbfef0a410d8fd342c7 \
--hash=sha256:b4b1f6aff0192e9be153fe07c1b7576cb7a1ff52e78e3f76d867be95301a8e87 \
--hash=sha256:bf03f06f586ce7d8aeb697a94b7dba12dc9271aae92d7a8d4486360ff711a660 \
--hash=sha256:c76de162937326fcd57aa399f94939ce726242323e65c15c67e183da1f6c26f7 \
@ -51,36 +58,41 @@ hidapi==0.7.99.post21 \
--hash=sha256:d4b5787a04613503357606bb10e59c3e2c1114fa00ee328b838dd257f41cbd7b \
--hash=sha256:e0be1aa6566979266a8fc845ab0e18613f4918cf2c977fe67050f5dc7e2a9a97 \
--hash=sha256:edfb16b16a298717cf05b8c8a9ad1828b6ff3de5e93048ceccd74e6ae4ff0922
idna==2.7 \
--hash=sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e \
--hash=sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16
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
pip==10.0.1 \
--hash=sha256:717cdffb2833be8409433a93746744b59505f42146e8d37de6c62b430e25d6d7 \
--hash=sha256:f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68
protobuf==3.6.0 \
--hash=sha256:12985d9f40c104da2f44ec089449214876809b40fdc5d9e43b93b512b9e74056 \
--hash=sha256:12c97fe27af12fc5d66b23f905ab09dd4fb0c68d5a74a419d914580e6d2e71e3 \
--hash=sha256:327fb9d8a8247bc780b9ea7ed03c0643bc0d22c139b761c9ec1efc7cc3f0923e \
--hash=sha256:3895319db04c0b3baed74fb66be7ba9f4cd8e88a432b8e71032cdf08b2dfee23 \
--hash=sha256:695072063e256d32335d48b9484451f7c7948edc3dbd419469d6a778602682fc \
--hash=sha256:7d786f3ef5b33a04e6538089674f244a3b0f588155016559d950989010af97d0 \
--hash=sha256:8bf82bb7a466a54be7272dcb492f71d55a2453a58d862fb74c3f2083f2768543 \
--hash=sha256:9bbc1ae1c33c1bd3a2fc05a3aec328544d2b039ff0ce6f000063628a32fad777 \
--hash=sha256:9e992c68103ab5635728d29fcf132c669cb4e2db24d012685210276185009d17 \
--hash=sha256:9f1087abb67b34e55108bc610936b34363a7aac692023bcbb17e065c253a1f80 \
--hash=sha256:9fefcb92a3784b446abf3641d9a14dad815bee88e0edd10b9a9e0e144d01a991 \
--hash=sha256:a37836aa47d1b81c2db1a6b7a5e79926062b5d76bd962115a0e615551be2b48d \
--hash=sha256:cca22955443c55cf86f963a4ad7057bca95e4dcde84d6a493066d380cfab3bb0 \
--hash=sha256:d7ac50bc06d31deb07ace6de85556c1d7330e5c0958f3b2af85037d6d1182abf \
--hash=sha256:dfe6899304b898538f4dc94fa0b281b56b70e40f58afa4c6f807805261cbe2e8
idna==2.8 \
--hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \
--hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c
keepkey==6.3.1 \
--hash=sha256:88e2b5291c85c8e8567732f675697b88241082884aa1aba32257f35ee722fc09 \
--hash=sha256:cef1e862e195ece3e42640a0f57d15a63086fd1dedc8b5ddfcbc9c2657f0bb1e \
--hash=sha256:f369d640c65fec7fd8e72546304cdc768c04224a6b9b00a19dc2cd06fa9d2a6b
libusb1==1.7.1 \
--hash=sha256:adf64a4f3f5c94643a1286f8153bcf4bc787c348b38934aacd7fe17fbeebc571
mnemonic==0.19 \
--hash=sha256:4e37eb02b2cbd56a0079cabe58a6da93e60e3e4d6e757a586d9f23d96abea931 \
--hash=sha256:a8d78c5100acfa7df9bab6b9db7390831b0e54490934b718ff9efd68f0d731a6
pip==19.3.1 \
--hash=sha256:21207d76c1031e517668898a6b46a9fb1501c7a4710ef5dfd6a40ad9e6757ea7 \
--hash=sha256:6917c65fc3769ecdc61405d3dfd97afdedd75808d200b2838d7d961cebc0c2c7
protobuf==3.11.1 \
--hash=sha256:0265379852b9e1f76af6d3d3fe4b3c383a595cc937594bda8565cf69a96baabd \
--hash=sha256:200b77e51f17fbc1d3049045f5835f60405dec3a00fe876b9b986592e46d908c \
--hash=sha256:29bd1ed46b2536ad8959401a2f02d2d7b5a309f8e97518e4f92ca6c5ba74dbed \
--hash=sha256:3175d45698edb9a07c1a78a1a4850e674ce8988f20596580158b1d0921d0f057 \
--hash=sha256:34a7270940f86da7a28be466ac541c89b6dbf144a6348b9cf7ac6f56b71006ce \
--hash=sha256:38cbc830a4a5ba9956763b0f37090bfd14dd74e72762be6225de2ceac55f4d03 \
--hash=sha256:665194f5ad386511ac8d8a0bd57b9ab37b8dd2cd71969458777318e774b9cd46 \
--hash=sha256:839bad7d115c77cdff29b488fae6a3ab503ce9a4192bd4c42302a6ea8e5d0f33 \
--hash=sha256:934a9869a7f3b0d84eca460e386fba1f7ba2a0c1a120a2648bc41fadf50efd1c \
--hash=sha256:aecdf12ef6dc7fd91713a6da93a86c2f2a8fe54840a3b1670853a2b7402e77c9 \
--hash=sha256:c4e90bc27c0691c76e09b5dc506133451e52caee1472b8b3c741b7c912ce43ef \
--hash=sha256:c65d135ea2d85d40309e268106dab02d3bea723db2db21c23ecad4163ced210b \
--hash=sha256:c98dea04a1ff41a70aff2489610f280004831798cb36a068013eed04c698903d \
--hash=sha256:d9049aa194378a426f0b2c784e2054565bf6f754d20fcafdee7102a6250556e8 \
--hash=sha256:e028fee51c96de4e81924484c77111dfdea14010ecfc906ea5b252209b0c4de6 \
--hash=sha256:e84ad26fb50091b1ea676403c0dd2bd47663099454aa6d88000b1dafecab0941 \
--hash=sha256:e88a924b591b06d0191620e9c8aa75297b3111066bb09d49a24bae1054a10c13
pyaes==1.6.1 \
--hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
pyblake2==1.1.2 \
--hash=sha256:3757f7ad709b0e1b2a6b3919fa79fe3261f166fc375cd521f2be480f8319dde9 \
--hash=sha256:407e02c7f8f36fcec1b7aa114ddca0c1060c598142ea6f6759d03710b946a7e3 \
@ -91,32 +103,28 @@ pyblake2==1.1.2 \
--hash=sha256:baa2190bfe549e36163aa44664d4ee3a9080b236fc5d42f50dc6fd36bbdc749e \
--hash=sha256:c53417ee0bbe77db852d5fd1036749f03696ebc2265de359fe17418d800196c4 \
--hash=sha256:fbc9fcde75713930bc2a91b149e97be2401f7c9c56d735b46a109210f58d7358
requests==2.19.1 \
--hash=sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1 \
--hash=sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a
safet==0.1.3 \
--hash=sha256:ba80fe9f6ba317ab9514a8726cd3792e68eb46dd419f380d48ae4a0ccae646dc \
--hash=sha256:e5d8e6a87c8bdf1cefd07004181b93fd7631557fdab09d143ba8d1b29291d6dc
setuptools==40.0.0 \
--hash=sha256:012adb8e25fbfd64c652e99e7bab58799a3aaf05d39ab38561f69190a909015f \
--hash=sha256:d68abee4eed409fbe8c302ac4d8429a1ffef912cd047a903b5701c024048dd49
six==1.11.0 \
--hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \
--hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb
trezor==0.10.2 \
--hash=sha256:4dba4d5c53d3ca22884d79fb4aa68905fb8353a5da5f96c734645d8cf537138d \
--hash=sha256:d2b32f25982ab403758d870df1d0de86d0751c106ef1cd1289f452880ce68b84
urllib3==1.23 \
--hash=sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf \
--hash=sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5
websocket-client==0.48.0 \
--hash=sha256:18f1170e6a1b5463986739d9fd45c4308b0d025c1b2f9b88788d8f69e8a5eb4a \
--hash=sha256:db70953ae4a064698b27ae56dcad84d0ee68b7b43cb40940f537738f38f510c1
wheel==0.31.1 \
--hash=sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c \
--hash=sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f
pyaes==1.6.1 \
--hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
ckcc-protocol==0.7.2 \
--hash=sha256:498db4ccdda018cd9f40210f5bd02ddcc98e7df583170b2eab4035c86c3cc03b \
--hash=sha256:31ee5178cfba8895eb2a6b8d06dc7830b51461a0ff767a670a64707c63e6b264
requests==2.22.0 \
--hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \
--hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31
safet==0.1.4 \
--hash=sha256:522c257910f9472e9c77c487425ed286f6721c314653e232bc41c6cedece1bb1 \
--hash=sha256:b152874acdc89ff0c8b2d680bfbf020b3e53527c2ad3404489dd61a548aa56a1
setuptools==42.0.2 \
--hash=sha256:c5b372090d7c8709ce79a6a66872a91e518f7d65af97fca78135e1cb10d4b940 \
--hash=sha256:c8abd0f3574bc23afd2f6fd2c415ba7d9e097c8a99b845473b0d957ba1e2dac6
six==1.13.0 \
--hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \
--hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66
trezor==0.11.5 \
--hash=sha256:711137bb83e7e0aef4009745e0da1b7d258146f246b43e3f7f5b849405088ef1 \
--hash=sha256:cd8aafd70a281daa644c4a3fb021ffac20b7a88e86226ecc8bb3e78e1734a184
typing-extensions==3.7.4.1 \
--hash=sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2 \
--hash=sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d \
--hash=sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575
urllib3==1.25.7 \
--hash=sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293 \
--hash=sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745
wheel==0.33.6 \
--hash=sha256:10c9da68765315ed98850f8e048347c3eb06dd81822dc2ab1d4fde9dc9702646 \
--hash=sha256:f4da1763d3becf2e2cd92a14a7c920f0f00eca30fdde9ea992c836685b9faf28

View file

@ -0,0 +1,19 @@
altgraph==0.16.1 \
--hash=sha256:d6814989f242b2b43025cba7161fc1b8fb487a62cd49c49245d6fd01c18ac997 \
--hash=sha256:ddf5320017147ba7b810198e0b6619bd7b5563aa034da388cea8546b877f9b0c
future==0.18.2 \
--hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d
pefile==2019.4.18 \
--hash=sha256:a5d6e8305c6b210849b47a6174ddf9c452b2888340b8177874b862ba6c207645
pip==19.3.1 \
--hash=sha256:21207d76c1031e517668898a6b46a9fb1501c7a4710ef5dfd6a40ad9e6757ea7 \
--hash=sha256:6917c65fc3769ecdc61405d3dfd97afdedd75808d200b2838d7d961cebc0c2c7
pywin32-ctypes==0.2.0 \
--hash=sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942 \
--hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98
setuptools==42.0.2 \
--hash=sha256:c5b372090d7c8709ce79a6a66872a91e518f7d65af97fca78135e1cb10d4b940 \
--hash=sha256:c8abd0f3574bc23afd2f6fd2c415ba7d9e097c8a99b845473b0d957ba1e2dac6
wheel==0.33.6 \
--hash=sha256:10c9da68765315ed98850f8e048347c3eb06dd81822dc2ab1d4fde9dc9702646 \
--hash=sha256:f4da1763d3becf2e2cd92a14a7c920f0f00eca30fdde9ea992c836685b9faf28

View file

@ -1,69 +1,183 @@
certifi==2018.4.16 \
--hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7 \
--hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0
aiohttp==3.6.2 \
--hash=sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e \
--hash=sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326 \
--hash=sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a \
--hash=sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654 \
--hash=sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a \
--hash=sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4 \
--hash=sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17 \
--hash=sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec \
--hash=sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd \
--hash=sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48 \
--hash=sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59 \
--hash=sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965
aiohttp-socks==0.2.2 \
--hash=sha256:e473ee222b001fe33798957b9ce3352b32c187cf41684f8e2259427925914993 \
--hash=sha256:eebd8939a7c3c1e3e7e1b2552c60039b4c65ef6b8b2351efcbdd98290538e310
aiorpcX==0.18.4 \
--hash=sha256:bec9c0feb328d62ba80b79931b07f7372c98f2891ad51300be0b7163d5ccfb4a \
--hash=sha256:d424a55bcf52ebf1b3610a7809c0748fac91ce926854ad33ce952463bc6017e8
apply-defaults==0.1.4 \
--hash=sha256:1ce26326a61d8773d38a9726a345c6525a91a6120d7333af79ad792dacb6246c
async-timeout==3.0.1 \
--hash=sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f \
--hash=sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3
attrs==19.3.0 \
--hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \
--hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72
bitstring==3.1.6 \
--hash=sha256:7b60b0c300d0d3d0a24ec84abfda4b0eaed3dc56dc90f6cbfe497166c9ad8443 \
--hash=sha256:c97a8e2a136e99b523b27da420736ae5cb68f83519d633794a6a11192f69f8bf \
--hash=sha256:e392819965e7e0246e3cf6a51d5a54e731890ae03ebbfa3cd0e4f74909072096
certifi==2019.11.28 \
--hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \
--hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f
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.7 \
--hash=sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e \
--hash=sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16
jsonrpclib-pelix==0.3.1 \
--hash=sha256:5417b1508d5a50ec64f6e5b88907f111155d52607b218ff3ba9a777afb2e49e3 \
--hash=sha256:bd89a6093bc4d47dc8a096197aacb827359944a4533be5193f3845f57b9f91b4
pip==10.0.1 \
--hash=sha256:717cdffb2833be8409433a93746744b59505f42146e8d37de6c62b430e25d6d7 \
--hash=sha256:f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68
protobuf==3.6.0 \
--hash=sha256:12985d9f40c104da2f44ec089449214876809b40fdc5d9e43b93b512b9e74056 \
--hash=sha256:12c97fe27af12fc5d66b23f905ab09dd4fb0c68d5a74a419d914580e6d2e71e3 \
--hash=sha256:327fb9d8a8247bc780b9ea7ed03c0643bc0d22c139b761c9ec1efc7cc3f0923e \
--hash=sha256:3895319db04c0b3baed74fb66be7ba9f4cd8e88a432b8e71032cdf08b2dfee23 \
--hash=sha256:695072063e256d32335d48b9484451f7c7948edc3dbd419469d6a778602682fc \
--hash=sha256:7d786f3ef5b33a04e6538089674f244a3b0f588155016559d950989010af97d0 \
--hash=sha256:8bf82bb7a466a54be7272dcb492f71d55a2453a58d862fb74c3f2083f2768543 \
--hash=sha256:9bbc1ae1c33c1bd3a2fc05a3aec328544d2b039ff0ce6f000063628a32fad777 \
--hash=sha256:9e992c68103ab5635728d29fcf132c669cb4e2db24d012685210276185009d17 \
--hash=sha256:9f1087abb67b34e55108bc610936b34363a7aac692023bcbb17e065c253a1f80 \
--hash=sha256:9fefcb92a3784b446abf3641d9a14dad815bee88e0edd10b9a9e0e144d01a991 \
--hash=sha256:a37836aa47d1b81c2db1a6b7a5e79926062b5d76bd962115a0e615551be2b48d \
--hash=sha256:cca22955443c55cf86f963a4ad7057bca95e4dcde84d6a493066d380cfab3bb0 \
--hash=sha256:d7ac50bc06d31deb07ace6de85556c1d7330e5c0958f3b2af85037d6d1182abf \
--hash=sha256:dfe6899304b898538f4dc94fa0b281b56b70e40f58afa4c6f807805261cbe2e8
click==6.7 \
--hash=sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d \
--hash=sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b
dnspython==1.16.0 \
--hash=sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01 \
--hash=sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d
ecdsa==0.14.1 \
--hash=sha256:64c613005f13efec6541bb0a33290d0d03c27abab5f15fbab20fb0ee162bdd8e \
--hash=sha256:e108a5fe92c67639abae3260e43561af914e7fd0d27bae6d2ec1312ae7934dfe
idna==2.8 \
--hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \
--hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c
idna_ssl==1.1.0 \
--hash=sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c
importlib-metadata==1.1.0 \
--hash=sha256:b044f07694ef14a6683b097ba56bd081dbc7cdc7c7fe46011e499dfecc082f21 \
--hash=sha256:e6ac600a142cf2db707b1998382cc7fc3b02befb7273876e01b8ad10b9652742
jsonrpcclient==3.3.4 \
--hash=sha256:c50860409b73af9f94b648439caae3b4af80d5ac937f2a8ac7783de3d1050ba9
jsonrpcserver==4.0.5 \
--hash=sha256:240c517f49b0fdd3bfa428c9a7cc581126a0c43eca60d29762da124017d9d9f4
jsonschema==3.2.0 \
--hash=sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163 \
--hash=sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a
more-itertools==8.0.0 \
--hash=sha256:53ff73f186307d9c8ef17a9600309154a6ae27f25579e80af4db8f047ba14bc2 \
--hash=sha256:a0ea684c39bc4315ba7aae406596ef191fd84f873d2d2751f84d64e81a7a2d45
multidict==4.6.1 \
--hash=sha256:07f9a6bf75ad675d53956b2c6a2d4ef2fa63132f33ecc99e9c24cf93beb0d10b \
--hash=sha256:0ffe4d4d28cbe9801952bfb52a8095dd9ffecebd93f84bdf973c76300de783c5 \
--hash=sha256:1b605272c558e4c659dbaf0fb32a53bfede44121bcf77b356e6e906867b958b7 \
--hash=sha256:205a011e636d885af6dd0029e41e3514a46e05bb2a43251a619a6e8348b96fc0 \
--hash=sha256:250632316295f2311e1ed43e6b26a63b0216b866b45c11441886ac1543ca96e1 \
--hash=sha256:2bc9c2579312c68a3552ee816311c8da76412e6f6a9cf33b15152e385a572d2a \
--hash=sha256:318aadf1cfb6741c555c7dd83d94f746dc95989f4f106b25b8a83dfb547f2756 \
--hash=sha256:42cdd649741a14b0602bf15985cad0dd4696a380081a3319cd1ead46fd0f0fab \
--hash=sha256:5159c4975931a1a78bf6602bbebaa366747fce0a56cb2111f44789d2c45e379f \
--hash=sha256:87e26d8b89127c25659e962c61a4c655ec7445d19150daea0759516884ecb8b4 \
--hash=sha256:891b7e142885e17a894d9d22b0349b92bb2da4769b4e675665d0331c08719be5 \
--hash=sha256:8d919034420378132d074bf89df148d0193e9780c9fe7c0e495e895b8af4d8a2 \
--hash=sha256:9c890978e2b37dd0dc1bd952da9a5d9f245d4807bee33e3517e4119c48d66f8c \
--hash=sha256:a37433ce8cdb35fc9e6e47e1606fa1bfd6d70440879038dca7d8dd023197eaa9 \
--hash=sha256:c626029841ada34c030b94a00c573a0c7575fe66489cde148785b6535397d675 \
--hash=sha256:cfec9d001a83dc73580143f3c77e898cf7ad78b27bb5e64dbe9652668fcafec7 \
--hash=sha256:efaf1b18ea6c1f577b1371c0159edbe4749558bfe983e13aa24d0a0c01e1ad7b
pip==19.3.1 \
--hash=sha256:21207d76c1031e517668898a6b46a9fb1501c7a4710ef5dfd6a40ad9e6757ea7 \
--hash=sha256:6917c65fc3769ecdc61405d3dfd97afdedd75808d200b2838d7d961cebc0c2c7
protobuf==3.11.1 \
--hash=sha256:0265379852b9e1f76af6d3d3fe4b3c383a595cc937594bda8565cf69a96baabd \
--hash=sha256:200b77e51f17fbc1d3049045f5835f60405dec3a00fe876b9b986592e46d908c \
--hash=sha256:29bd1ed46b2536ad8959401a2f02d2d7b5a309f8e97518e4f92ca6c5ba74dbed \
--hash=sha256:3175d45698edb9a07c1a78a1a4850e674ce8988f20596580158b1d0921d0f057 \
--hash=sha256:34a7270940f86da7a28be466ac541c89b6dbf144a6348b9cf7ac6f56b71006ce \
--hash=sha256:38cbc830a4a5ba9956763b0f37090bfd14dd74e72762be6225de2ceac55f4d03 \
--hash=sha256:665194f5ad386511ac8d8a0bd57b9ab37b8dd2cd71969458777318e774b9cd46 \
--hash=sha256:839bad7d115c77cdff29b488fae6a3ab503ce9a4192bd4c42302a6ea8e5d0f33 \
--hash=sha256:934a9869a7f3b0d84eca460e386fba1f7ba2a0c1a120a2648bc41fadf50efd1c \
--hash=sha256:aecdf12ef6dc7fd91713a6da93a86c2f2a8fe54840a3b1670853a2b7402e77c9 \
--hash=sha256:c4e90bc27c0691c76e09b5dc506133451e52caee1472b8b3c741b7c912ce43ef \
--hash=sha256:c65d135ea2d85d40309e268106dab02d3bea723db2db21c23ecad4163ced210b \
--hash=sha256:c98dea04a1ff41a70aff2489610f280004831798cb36a068013eed04c698903d \
--hash=sha256:d9049aa194378a426f0b2c784e2054565bf6f754d20fcafdee7102a6250556e8 \
--hash=sha256:e028fee51c96de4e81924484c77111dfdea14010ecfc906ea5b252209b0c4de6 \
--hash=sha256:e84ad26fb50091b1ea676403c0dd2bd47663099454aa6d88000b1dafecab0941 \
--hash=sha256:e88a924b591b06d0191620e9c8aa75297b3111066bb09d49a24bae1054a10c13
pyaes==1.6.1 \
--hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
PySocks==1.6.8 \
--hash=sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672
QDarkStyle==2.5.4 \
--hash=sha256:3eb60922b8c4d9cedecb6897ca4c9f8a259d81bdefe5791976ccdf12432de1f0 \
--hash=sha256:51331fc6490b38c376e6ba8d8c814320c8d2d1c2663055bc396321a7c28fa8be
qrcode==6.0 \
--hash=sha256:037b0db4c93f44586e37f84c3da3f763874fcac85b2974a69a98e399ac78e1bf \
--hash=sha256:de4ffc15065e6ff20a551ad32b6b41264f3c75275675406ddfa8e3530d154be3
requests==2.19.1 \
--hash=sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1 \
--hash=sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a
setuptools==40.0.0 \
--hash=sha256:012adb8e25fbfd64c652e99e7bab58799a3aaf05d39ab38561f69190a909015f \
--hash=sha256:d68abee4eed409fbe8c302ac4d8429a1ffef912cd047a903b5701c024048dd49
six==1.11.0 \
--hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \
--hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb
typing==3.6.4 \
--hash=sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf \
--hash=sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8 \
--hash=sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2
urllib3==1.23 \
--hash=sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf \
--hash=sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5
wheel==0.31.1 \
--hash=sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c \
--hash=sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f
colorama==0.3.9 \
--hash=sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda \
--hash=sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1
pycryptodomex==3.9.4 \
--hash=sha256:0943b65fb41b7403a9def6214061fdd9ab9afd0bbc581e553c72eebe60bded36 \
--hash=sha256:0a1dbb5c4d975a4ea568fb7686550aa225d94023191fb0cca8747dc5b5d77857 \
--hash=sha256:0f43f1608518347fdcb9c8f443fa5cabedd33f94188b13e4196a3a7ba90d169c \
--hash=sha256:11ce5fec5990e34e3981ed14897ba601c83957b577d77d395f1f8f878a179f98 \
--hash=sha256:17a09e38fdc91e4857cf5a7ce82f3c0b229c3977490f2146513e366923fc256b \
--hash=sha256:22d970cee5c096b9123415e183ae03702b2cd4d3ba3f0ced25c4e1aba3967167 \
--hash=sha256:2a1793efcbae3a2264c5e0e492a2629eb10d895d6e5f17dbbd00eb8b489c6bda \
--hash=sha256:30a8a148a0fe482cec1aaf942bbd0ade56ec197c14fe058b2a94318c57e1f991 \
--hash=sha256:32fbbaf964c5184d3f3e349085b0536dd28184b02e2b014fc900f58bbc126339 \
--hash=sha256:347d67faee36d449dc9632da411cc318df52959079062627f1243001b10dc227 \
--hash=sha256:45f4b4e5461a041518baabc52340c249b60833aa84cea6377dc8016a2b33c666 \
--hash=sha256:4717daec0035034b002d31c42e55431c970e3e38a78211f43990e1b7eaf19e28 \
--hash=sha256:51a1ac9e7dda81da444fed8be558a60ec88dfc73b2aa4b0efa310e87acb75838 \
--hash=sha256:53e9dcc8f14783f6300b70da325a50ac1b0a3dbaee323bd9dc3f71d409c197a1 \
--hash=sha256:5519a2ed776e193688b7ddb61ab709303f6eb7d1237081e298283c72acc44271 \
--hash=sha256:583450e8e80a0885c453211ed2bd69ceea634d8c904f23ff8687f677fe810e95 \
--hash=sha256:60f862bd2a07133585a4fc2ce2b1a8ec24746b07ac44307d22ef2b767cb03435 \
--hash=sha256:612091f1d3c84e723bec7cb855cf77576e646045744794c9a3f75ba80737762f \
--hash=sha256:629a87b87c8203b8789ccefc7f2f2faecd2daaeb56bdd0b4e44cd89565f2db07 \
--hash=sha256:6e56ec4c8938fb388b6f250ddd5e21c15e8f25a76e0ad0e2abae9afee09e67b4 \
--hash=sha256:8e8092651844a11ec7fa534395f3dfe99256ce4edca06f128efc9d770d6e1dc1 \
--hash=sha256:8f5f260629876603e08f3ce95c8ccd9b6b83bf9a921c41409046796267f7adc5 \
--hash=sha256:9a6b74f38613f54c56bd759b411a352258f47489bbefd1d57c930a291498b35b \
--hash=sha256:a5a13ebb52c4cd065fb673d8c94f39f30823428a4de19e1f3f828b63a8882d1e \
--hash=sha256:a77ca778a476829876a3a70ae880073379160e4a465d057e3c4e1c79acdf1b8a \
--hash=sha256:a9f7be3d19f79429c2118fd61bc2ec4fa095e93b56fb3a5f3009822402c4380f \
--hash=sha256:dc15a467c4f9e4b43748ba2f97aea66f67812bfd581818284c47cadc81d4caec \
--hash=sha256:e13cdeea23059f7577c230fd580d2c8178e67ebe10e360041abe86c33c316f1c \
--hash=sha256:e45b85c8521bca6bdfaf57e4987743ade53e9f03529dd3adbc9524094c6d55c4 \
--hash=sha256:e87f17867b260f57c88487f943eb4d46c90532652bb37046e764842c3b66cbb1 \
--hash=sha256:ee40a5b156f6c1192bc3082e9d73d0479904433cdda83110546cd67f5a15a5be \
--hash=sha256:ef63ffde3b267043579af8830fc97fc3b9b8a526a24e3ba23af9989d4e9e689a
pyrsistent==0.15.6 \
--hash=sha256:f3b280d030afb652f79d67c5586157c5c1355c9a58dfc7940566e28d28f3df1b
QDarkStyle==2.6.8 \
--hash=sha256:037a54bf0aa5153f8055b65b8b36ac0d0f7648f2fd906c011a4da22eb0f582a2 \
--hash=sha256:fd1abae37d3a0a004089178da7c0b26ec5eb29f965b3e573853b8f280b614dea
qrcode==6.1 \
--hash=sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5 \
--hash=sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369
setuptools==42.0.2 \
--hash=sha256:c5b372090d7c8709ce79a6a66872a91e518f7d65af97fca78135e1cb10d4b940 \
--hash=sha256:c8abd0f3574bc23afd2f6fd2c415ba7d9e097c8a99b845473b0d957ba1e2dac6
six==1.13.0 \
--hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \
--hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66
typing-extensions==3.7.4.1 \
--hash=sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2 \
--hash=sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d \
--hash=sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575
wheel==0.33.6 \
--hash=sha256:10c9da68765315ed98850f8e048347c3eb06dd81822dc2ab1d4fde9dc9702646 \
--hash=sha256:f4da1763d3becf2e2cd92a14a7c920f0f00eca30fdde9ea992c836685b9faf28
yarl==1.4.1 \
--hash=sha256:031e8f56cf085d3b3df6b6bce756369ea7052b82d35ea07b6045f209c819e0e5 \
--hash=sha256:074958fe4578ef3a3d0bdaf96bbc25e4c4db82b7ff523594776fcf3d3f16c531 \
--hash=sha256:2db667ee21f620b446a54a793e467714fc5a446fcc82d93a47e8bde01d69afab \
--hash=sha256:326f2dbaaa17b858ae86f261ae73a266fd820a561fc5142cee9d0fc58448fbd7 \
--hash=sha256:32a3885f542f74d0f4f87057050c6b45529ebd79d0639f56582e741521575bfe \
--hash=sha256:56126ef061b913c3eefecace3404ca88917265d0550b8e32bbbeab29e5c830bf \
--hash=sha256:589ac1e82add13fbdedc04eb0a83400db728e5f1af2bd273392088ca90de7062 \
--hash=sha256:6076bce2ecc6ebf6c92919d77762f80f4c9c6ecc9c1fbaa16567ec59ad7d6f1d \
--hash=sha256:63be649c535d18ab6230efbc06a07f7779cd4336a687672defe70c025349a47b \
--hash=sha256:6642cbc92eaffa586180f669adc772f5c34977e9e849e93f33dc142351e98c9c \
--hash=sha256:6fa05a25f2280e78a514041d4609d39962e7d51525f2439db9ad7a2ae7aac163 \
--hash=sha256:7ed006a220422c33ff0889288be24db56ff0a3008ffe9eaead58a690715ad09b \
--hash=sha256:80c9c213803b50899460cc355f47e66778c3c868f448b7b7de5b1f1858c82c2a \
--hash=sha256:8bae18e2129850e76969b57869dacc72a66cccdbeebce1a28d7f3d439c21a7a3 \
--hash=sha256:ab112fba996a8f48f427e26969f2066d50080df0c24007a8cc6d7ae865e19013 \
--hash=sha256:b1c178ef813940c9a5cbad42ab7b8b76ac08b594b0a6bad91063c968e0466efc \
--hash=sha256:d6eff151c3b23a56a5e4f496805619bc3bdf4f749f63a7a95ad50e8267c17475
zipp==0.6.0 \
--hash=sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e \
--hash=sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335
colorama==0.4.1 \
--hash=sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d \
--hash=sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48

4
contrib/freeze_packages.sh Executable file → Normal file
View file

@ -1,6 +1,8 @@
#!/bin/bash
# Run this after a new release to update dependencies
set -e
venv_dir=~/.electrum-venv
contrib=$(dirname "$0")
@ -8,7 +10,7 @@ which virtualenv > /dev/null 2>&1 || { echo "Please install virtualenv" && exit
python3 -m hashin -h > /dev/null 2>&1 || { python3 -m pip install hashin; }
other_python=$(which python3)
for i in '' '-hw' '-binaries'; do
for i in '' '-hw' '-binaries' '-wine-build'; do
rm -rf "$venv_dir"
virtualenv -p $(which python3) $venv_dir

41
contrib/make_apk Executable file → Normal file
View file

@ -1,7 +1,26 @@
#!/bin/bash
set -e
CONTRIB="$(dirname "$(readlink -e "$0")")"
ROOT_FOLDER="$CONTRIB"/..
PACKAGES="$ROOT_FOLDER"/packages/
LOCALE="$ROOT_FOLDER"/electrum/locale/
if [ ! -d "$LOCALE" ]; then
echo "Run pull_locale first!"
exit 1
fi
if [ ! -d "$PACKAGES" ]; then
echo "Run make_packages first!"
exit 1
fi
pushd ./electrum/gui/kivy/
make theming
if [[ -n "$1" && "$1" == "release" ]] ; then
echo -n Keystore Password:
read -s password
@ -9,9 +28,31 @@ if [[ -n "$1" && "$1" == "release" ]] ; then
export P4A_RELEASE_KEYSTORE_PASSWD=$password
export P4A_RELEASE_KEYALIAS_PASSWD=$password
export P4A_RELEASE_KEYALIAS=electrum
# build two apks
export APP_ANDROID_ARCH=armeabi-v7a
make release
export APP_ANDROID_ARCH=arm64-v8a
make release
else
export P4A_DEBUG_KEYSTORE="$CONTRIB"/android_debug.keystore
export P4A_DEBUG_KEYSTORE_PASSWD=unsafepassword
export P4A_DEBUG_KEYALIAS_PASSWD=unsafepassword
export P4A_DEBUG_KEYALIAS=electrum
# create keystore if needed
if [ ! -f "$P4A_DEBUG_KEYSTORE" ]; then
keytool -genkey -v -keystore "$CONTRIB"/android_debug.keystore \
-alias "$P4A_DEBUG_KEYALIAS" -keyalg RSA -keysize 2048 -validity 10000 \
-dname "CN=mqttserver.ibm.com, OU=ID, O=IBM, L=Hursley, S=Hants, C=GB" \
-storepass "$P4A_DEBUG_KEYSTORE_PASSWD" \
-keypass "$P4A_DEBUG_KEYALIAS_PASSWD"
fi
# build two apks (only one on Travis CI)
export APP_ANDROID_ARCH=armeabi-v7a
make apk
if [ ! $CI ]; then
export APP_ANDROID_ARCH=arm64-v8a
make apk
fi
fi
popd

10
contrib/make_download Executable file → Normal file
View file

@ -2,8 +2,15 @@
import re
import os
import sys
import importlib
from electrum.version import ELECTRUM_VERSION, APK_VERSION
# load version.py; needlessly complicated alternative to "imp.load_source":
version_spec = importlib.util.spec_from_file_location('version', 'electrum/version.py')
version_module = importlib.util.module_from_spec(version_spec)
version_spec.loader.exec_module(version_module)
ELECTRUM_VERSION = version_module.ELECTRUM_VERSION
APK_VERSION = version_module.APK_VERSION
print("version", ELECTRUM_VERSION)
dirname = sys.argv[1]
@ -24,6 +31,7 @@ string = string.replace("##VERSION_APK##", APK_VERSION)
files = {
'tgz': "Electrum-%s.tar.gz" % version,
'appimage': "electrum-%s-x86_64.AppImage" % version,
'zip': "Electrum-%s.zip" % version,
'mac': "electrum-%s.dmg" % version_mac,
'win': "electrum-%s.exe" % version_win,

View file

@ -0,0 +1,49 @@
#!/bin/bash
LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7"
set -e
. $(dirname "$0")/build_tools_util.sh || (echo "Could not source build_tools_util.sh" && exit 1)
here=$(dirname $(realpath "$0" 2> /dev/null || grealpath "$0"))
CONTRIB="$here"
PROJECT_ROOT="$CONTRIB/.."
pkgname="secp256k1"
info "Building $pkgname..."
(
cd $CONTRIB
if [ ! -d secp256k1 ]; then
git clone https://github.com/bitcoin-core/secp256k1.git
fi
cd secp256k1
git reset --hard
git clean -f -x -q
git checkout $LIBSECP_VERSION
if ! [ -x configure ] ; then
echo "libsecp256k1_la_LDFLAGS = -no-undefined" >> Makefile.am
echo "LDFLAGS = -no-undefined" >> Makefile.am
./autogen.sh || fail "Could not run autogen for $pkgname. Please make sure you have automake and libtool installed, and try again."
fi
if ! [ -r config.status ] ; then
./configure \
$AUTOCONF_FLAGS \
--prefix="$here/$pkgname/dist" \
--enable-module-recovery \
--enable-experimental \
--enable-module-ecdh \
--disable-jni \
--disable-tests \
--disable-static \
--enable-shared || fail "Could not configure $pkgname. Please make sure you have a C compiler installed and try again."
fi
make -j4 || fail "Could not build $pkgname"
make install || fail "Could not install $pkgname"
. "$here/$pkgname/dist/lib/libsecp256k1.la"
host_strip "$here/$pkgname/dist/lib/$dlname"
cp -fpv "$here/$pkgname/dist/lib/$dlname" "$PROJECT_ROOT/electrum" || fail "Could not copy the $pkgname binary to its destination"
info "$dlname has been placed in the inner 'electrum' folder."
)

0
contrib/make_locale Executable file → Normal file
View file

11
contrib/make_packages Executable file → Normal file
View file

@ -1,13 +1,10 @@
#!/bin/bash
contrib=$(dirname "$0")
test -n "$contrib" -a -d "$contrib" || exit
CONTRIB="$(dirname "$0")"
test -n "$CONTRIB" -a -d "$CONTRIB" || exit
whereis pip3
if [ $? -ne 0 ] ; then echo "Install pip3" ; exit ; fi
rm "$contrib"/../packages/ -r
rm "$CONTRIB"/../packages/ -r
#Install pure python modules in electrum directory
pip3 install -r $contrib/deterministic-build/requirements.txt -t $contrib/../packages
python3 -m pip install -r "$CONTRIB"/deterministic-build/requirements.txt -t "$CONTRIB"/../packages

44
contrib/make_tgz Executable file → Normal file
View file

@ -1 +1,43 @@
python3 setup.py sdist --format=zip,gztar
#!/bin/bash
set -e
CONTRIB="$(dirname "$(readlink -e "$0")")"
ROOT_FOLDER="$CONTRIB"/..
PACKAGES="$ROOT_FOLDER"/packages/
LOCALE="$ROOT_FOLDER"/electrum/locale/
if [ ! -d "$PACKAGES" ]; then
echo "Run make_packages first!"
exit 1
fi
git submodule update --init
(
rm -rf "$LOCALE"
cd "$CONTRIB/deterministic-build/electrum-locale/"
if ! which msgfmt > /dev/null 2>&1; then
echo "Please install gettext"
exit 1
fi
for i in ./locale/*; do
dir="$ROOT_FOLDER"/electrum/$i/LC_MESSAGES
mkdir -p $dir
msgfmt --output-file=$dir/electrum.mo $i/electrum.po || true
cp $i/electrum.po "$ROOT_FOLDER"/electrum/$i/electrum.po
done
)
(
cd "$ROOT_FOLDER"
echo "'git clean -fd' would delete the following files: >>>"
git clean -fd --dry-run
echo "<<<"
# we could build the kivy atlas potentially?
#(cd electrum/gui/kivy/; make theming) || echo "building kivy atlas failed! skipping."
python3 setup.py --quiet sdist --format=zip,gztar
)

@ -0,0 +1 @@
Subproject commit 59dfc03272751cd29ee311456fa34c40f7ebb7c0

72
contrib/osx/README.md Normal file
View file

@ -0,0 +1,72 @@
Building Mac OS binaries
========================
✗ _This script does not produce reproducible output (yet!).
Please help us remedy this._
This guide explains how to build Electrum binaries for macOS systems.
## 1. Building the binary
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 (or later)
makes the binaries [incompatible with older versions](https://github.com/pyinstaller/pyinstaller/issues/1191).
Another factor for the minimum supported macOS version is the
[bundled Qt version](https://github.com/spesmilo/electrum/issues/3685).
Before starting, make sure that the Xcode command line tools are installed (e.g. you have `git`).
#### 1.1a Get Xcode
Building the QR scanner (CalinsQRReader) requires full Xcode (not just command line tools).
The last Xcode version compatible with El Capitan is Xcode 8.2.1
Get it from [here](https://developer.apple.com/download/more/).
Unfortunately, you need an "Apple ID" account.
After downloading, uncompress it.
Make sure it is the "selected" xcode (e.g.):
sudo xcode-select -s $HOME/Downloads/Xcode.app/Contents/Developer/
#### 1.1b Build QR scanner separately on newer Mac
Alternatively, you can try building just the QR scanner on newer macOS.
On newer Mac, run:
pushd contrib/osx/CalinsQRReader; xcodebuild; popd
cp -r contrib/osx/CalinsQRReader/build prebuilt_qr
Move `prebuilt_qr` to El Capitan: `contrib/osx/CalinsQRReader/prebuilt_qr`.
#### 1.2 Build Electrum
cd electrum
./contrib/osx/make_osx
This creates both a folder named Electrum.app and the .dmg file.
## 2. Building the image deterministically (WIP)
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/osx/package.sh ~/Electrum.app/

23
contrib/osx/base.sh Normal file
View file

@ -0,0 +1,23 @@
#!/usr/bin/env bash
. $(dirname "$0")/../build_tools_util.sh
function DoCodeSignMaybe { # ARGS: infoName fileOrDirName codesignIdentity
infoName="$1"
file="$2"
identity="$3"
deep=""
if [ -z "$identity" ]; then
# we are ok with them not passing anything; master script calls us unconditionally even if no identity is specified
return
fi
if [ -d "$file" ]; then
deep="--deep"
fi
if [ -z "$infoName" ] || [ -z "$file" ] || [ -z "$identity" ] || [ ! -e "$file" ]; then
fail "Argument error to internal function DoCodeSignMaybe()"
fi
info "Code signing ${infoName}..."
codesign -f -v $deep -s "$identity" "$file" || fail "Could not code sign ${infoName}"
}

View file

@ -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
/*

152
contrib/osx/make_osx Normal file
View file

@ -0,0 +1,152 @@
#!/usr/bin/env bash
# Parameterize
PYTHON_VERSION=3.7.6
BUILDDIR=/tmp/electrum-build
PACKAGE=Electrum
GIT_REPO=https://github.com/spesmilo/electrum
LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7"
export GCC_STRIP_BINARIES="1"
. $(dirname "$0")/base.sh
CONTRIB_OSX="$(dirname "$(realpath "$0")")"
CONTRIB="$CONTRIB_OSX/.."
ROOT_FOLDER="$CONTRIB/.."
src_dir=$(dirname "$0")
cd $src_dir/../..
VERSION=`git describe --tags --dirty --always`
which brew > /dev/null 2>&1 || fail "Please install brew from https://brew.sh/ to continue"
which xcodebuild > /dev/null 2>&1 || fail "Please install Xcode and xcode command line tools to continue"
# Code Signing: See https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html
APP_SIGN=""
if [ -n "$1" ]; then
# Test the identity is valid for signing by doing this hack. There is no other way to do this.
cp -f /bin/ls ./CODESIGN_TEST
codesign -s "$1" --dryrun -f ./CODESIGN_TEST > /dev/null 2>&1
res=$?
rm -f ./CODESIGN_TEST
if ((res)); then
fail "Code signing identity \"$1\" appears to be invalid."
fi
unset res
APP_SIGN="$1"
info "Code signing enabled using identity \"$APP_SIGN\""
else
warn "Code signing DISABLED. Specify a valid macOS Developer identity installed on the system as the first argument to this script to enable signing."
fi
info "Installing Python $PYTHON_VERSION"
export PATH="~/.pyenv/bin:~/.pyenv/shims:~/Library/Python/3.7/bin:$PATH"
if [ -d "~/.pyenv" ]; then
pyenv update
else
curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash > /dev/null 2>&1
fi
PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install -s $PYTHON_VERSION && \
pyenv global $PYTHON_VERSION || \
fail "Unable to use Python $PYTHON_VERSION"
info "install dependencies specific to binaries"
# note that this also installs pinned versions of both pip and setuptools
python3 -m pip install --no-dependencies -Ir ./contrib/deterministic-build/requirements-binaries.txt --user \
|| fail "Could not install pyinstaller"
info "Installing pyinstaller"
python3 -m pip install -I --user pyinstaller==3.6 || fail "Could not install pyinstaller"
info "Using these versions for building $PACKAGE:"
sw_vers
python3 --version
echo -n "Pyinstaller "
pyinstaller --version
rm -rf ./dist
git submodule update --init
rm -rf $BUILDDIR > /dev/null 2>&1
mkdir $BUILDDIR
info "generating locale"
(
if ! which msgfmt > /dev/null 2>&1; then
brew install gettext
brew link --force gettext
fi
cd "$CONTRIB"/deterministic-build/electrum-locale
for i in ./locale/*; do
dir="$ROOT_FOLDER"/electrum/$i/LC_MESSAGES
mkdir -p $dir
msgfmt --output-file=$dir/electrum.mo $i/electrum.po || true
done
) || fail "failed generating locale"
info "Downloading libusb..."
curl https://homebrew.bintray.com/bottles/libusb-1.0.22.el_capitan.bottle.tar.gz | \
tar xz --directory $BUILDDIR
cp $BUILDDIR/libusb/1.0.22/lib/libusb-1.0.dylib contrib/osx
echo "82c368dfd4da017ceb32b12ca885576f325503428a4966cc09302cbd62702493 contrib/osx/libusb-1.0.dylib" | \
shasum -a 256 -c || fail "libusb checksum mismatched"
info "Preparing for building libsecp256k1"
brew install autoconf automake libtool
"$CONTRIB"/make_libsecp256k1.sh || fail "Could not build libsecp"
cp "$ROOT_FOLDER"/electrum/libsecp256k1.0.dylib contrib/osx
info "Building CalinsQRReader..."
d=contrib/osx/CalinsQRReader
pushd $d
rm -fr build
# prefer building using xcode ourselves. otherwise fallback to prebuilt binary
xcodebuild || cp -r prebuilt_qr build || fail "Could not build CalinsQRReader"
popd
DoCodeSignMaybe "CalinsQRReader.app" "${d}/build/Release/CalinsQRReader.app" "$APP_SIGN" # If APP_SIGN is empty will be a noop
info "Installing requirements..."
python3 -m pip install --no-dependencies -Ir ./contrib/deterministic-build/requirements.txt --user || \
fail "Could not install requirements"
info "Installing hardware wallet requirements..."
python3 -m pip install --no-dependencies -Ir ./contrib/deterministic-build/requirements-hw.txt --user || \
fail "Could not install hardware wallet requirements"
info "Building $PACKAGE..."
python3 -m pip install --no-dependencies --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"
APP_SIGN="$APP_SIGN" pyinstaller --noconfirm --ascii --clean --name $VERSION contrib/osx/osx.spec || fail "Could not build binary"
info "Adding bitcoin URI types to Info.plist"
plutil -insert 'CFBundleURLTypes' \
-xml '<array><dict> <key>CFBundleURLName</key> <string>bitcoin</string> <key>CFBundleURLSchemes</key> <array><string>bitcoin</string></array> </dict></array>' \
-- dist/$PACKAGE.app/Contents/Info.plist \
|| fail "Could not add keys to Info.plist. Make sure the program 'plutil' exists and is installed."
DoCodeSignMaybe "app bundle" "dist/${PACKAGE}.app" "$APP_SIGN" # If APP_SIGN is empty will be a noop
info "Creating .DMG"
hdiutil create -fs HFS+ -volname $PACKAGE -srcfolder dist/$PACKAGE.app dist/electrum-$VERSION.dmg || fail "Could not create .DMG"
DoCodeSignMaybe ".DMG" "dist/electrum-${VERSION}.dmg" "$APP_SIGN" # If APP_SIGN is empty will be a noop
if [ -z "$APP_SIGN" ]; then
warn "App was built successfully but was not code signed. Users may get security warnings from macOS."
warn "Specify a valid code signing identity as the first argument to this script to enable code signing."
fi

165
contrib/osx/osx.spec Normal file
View file

@ -0,0 +1,165 @@
# -*- mode: python -*-
from PyInstaller.utils.hooks import collect_data_files, collect_submodules, collect_dynamic_libs
import sys, os
PACKAGE='Electrum'
PYPKG='electrum'
MAIN_SCRIPT='run_electrum'
ICONS_FILE=PYPKG + '/gui/icons/electrum.icns'
APP_SIGN = os.environ.get('APP_SIGN', '')
def fail(*msg):
RED='\033[0;31m'
NC='\033[0m' # No Color
print("\r🗯 {}ERROR:{}".format(RED, NC), *msg)
sys.exit(1)
def codesign(identity, binary):
d = os.path.dirname(binary)
saved_dir=None
if d:
# switch to directory of the binary so codesign verbose messages don't include long path
saved_dir = os.path.abspath(os.path.curdir)
os.chdir(d)
binary = os.path.basename(binary)
os.system("codesign -v -f -s '{}' '{}'".format(identity, binary))==0 or fail("Could not code sign " + binary)
if saved_dir:
os.chdir(saved_dir)
def monkey_patch_pyinstaller_for_codesigning(identity):
# Monkey-patch PyInstaller so that we app-sign all binaries *after* they are modified by PyInstaller
# If we app-sign before that point, the signature will be invalid because PyInstaller modifies
# @loader_path in the Mach-O loader table.
try:
import PyInstaller.depend.dylib
_saved_func = PyInstaller.depend.dylib.mac_set_relative_dylib_deps
except (ImportError, NameError, AttributeError):
# Hmm. Likely wrong PyInstaller version.
fail("Could not monkey-patch PyInstaller for code signing. Please ensure that you are using PyInstaller 3.4.")
_signed = set()
def my_func(fn, distname):
_saved_func(fn, distname)
if (fn, distname) not in _signed:
codesign(identity, fn)
_signed.add((fn,distname)) # remember we signed it so we don't sign again
PyInstaller.depend.dylib.mac_set_relative_dylib_deps = my_func
for i, x in enumerate(sys.argv):
if x == '--name':
VERSION = sys.argv[i+1]
break
else:
raise Exception('no version')
electrum = os.path.abspath(".") + "/"
block_cipher = None
# see https://github.com/pyinstaller/pyinstaller/issues/2005
hiddenimports = []
hiddenimports += collect_submodules('trezorlib')
hiddenimports += collect_submodules('safetlib')
hiddenimports += collect_submodules('btchip')
hiddenimports += collect_submodules('keepkeylib')
hiddenimports += collect_submodules('websocket')
hiddenimports += collect_submodules('ckcc')
hiddenimports += ['PyQt5.QtPrintSupport'] # needed by Revealer
# safetlib imports PyQt5.Qt. We use a local updated copy of pinmatrix.py until they
# release a new version that includes https://github.com/archos-safe-t/python-safet/commit/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e
hiddenimports.remove('safetlib.qt.pinmatrix')
datas = [
(electrum + PYPKG + '/*.json', PYPKG),
(electrum + PYPKG + '/wordlist/english.txt', PYPKG + '/wordlist'),
(electrum + PYPKG + '/locale', PYPKG + '/locale'),
(electrum + PYPKG + '/plugins', PYPKG + '/plugins'),
(electrum + PYPKG + '/gui/icons', PYPKG + '/gui/icons'),
]
datas += collect_data_files('trezorlib')
datas += collect_data_files('safetlib')
datas += collect_data_files('btchip')
datas += collect_data_files('keepkeylib')
datas += collect_data_files('ckcc')
datas += collect_data_files('jsonrpcserver')
datas += collect_data_files('jsonrpcclient')
# Add the QR Scanner helper app
datas += [(electrum + "contrib/osx/CalinsQRReader/build/Release/CalinsQRReader.app", "./contrib/osx/CalinsQRReader/build/Release/CalinsQRReader.app")]
# Add libusb so Trezor and Safe-T mini will work
binaries = [(electrum + "contrib/osx/libusb-1.0.dylib", ".")]
binaries += [(electrum + "contrib/osx/libsecp256k1.0.dylib", ".")]
# Workaround for "Retro Look":
binaries += [b for b in collect_dynamic_libs('PyQt5') if 'macstyle' in b[0]]
# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
a = Analysis([electrum+ MAIN_SCRIPT,
electrum+'electrum/gui/qt/main_window.py',
electrum+'electrum/gui/text.py',
electrum+'electrum/util.py',
electrum+'electrum/wallet.py',
electrum+'electrum/simple_config.py',
electrum+'electrum/bitcoin.py',
electrum+'electrum/dnssec.py',
electrum+'electrum/commands.py',
electrum+'electrum/plugins/cosigner_pool/qt.py',
electrum+'electrum/plugins/email_requests/qt.py',
electrum+'electrum/plugins/trezor/qt.py',
electrum+'electrum/plugins/safe_t/client.py',
electrum+'electrum/plugins/safe_t/qt.py',
electrum+'electrum/plugins/keepkey/qt.py',
electrum+'electrum/plugins/ledger/qt.py',
electrum+'electrum/plugins/coldcard/qt.py',
],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[])
# http://stackoverflow.com/questions/19055089/pyinstaller-onefile-warning-pyconfig-h-when-importing-scipy-or-scipy-signal
for d in a.datas:
if 'pyconfig' in d[0]:
a.datas.remove(d)
break
# Strip out parts of Qt that we never use. Reduces binary size by tens of MBs. see #4815
qt_bins2remove=('qtweb', 'qt3d', 'qtgame', 'qtdesigner', 'qtquick', 'qtlocation', 'qttest', 'qtxml')
print("Removing Qt binaries:", *qt_bins2remove)
for x in a.binaries.copy():
for r in qt_bins2remove:
if x[0].lower().startswith(r):
a.binaries.remove(x)
print('----> Removed x =', x)
# If code signing, monkey-patch in a code signing step to pyinstaller. See: https://github.com/spesmilo/electrum/issues/4994
if APP_SIGN:
monkey_patch_pyinstaller_for_codesigning(APP_SIGN)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.datas,
name=PACKAGE,
debug=False,
strip=False,
upx=True,
icon=electrum+ICONS_FILE,
console=False)
app = BUNDLE(exe,
version = VERSION,
name=PACKAGE + '.app',
icon=electrum+ICONS_FILE,
bundle_identifier=None,
info_plist={
'NSHighResolutionCapable': 'True',
'NSSupportsAutomaticGraphicsSwitching': 'True'
}
)

88
contrib/osx/package.sh Normal file
View file

@ -0,0 +1,88 @@
#!/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"
plist=$1/Contents/Info.plist
test -f "$plist" || fail "Info.plist not found"
VERSION=$(grep -1 ShortVersionString $plist |tail -1|gawk 'match($0, /<string>(.*)<\/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/
build_dir=$(dirname "$1")
test -n "$build_dir" -a -d "$build_dir" || exit
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-$VERSION.dmg || fail "Unable to create compressed dmg"
rm Electrum_uncompressed.dmg
echo "Done."
sha256sum electrum-$VERSION.dmg

65
contrib/pull_locale Normal file
View file

@ -0,0 +1,65 @@
#!/usr/bin/env python3
import os
import subprocess
import io
import zipfile
import sys
try:
import requests
except ImportError as e:
sys.exit(f"Error: {str(e)}. Try 'sudo python3 -m pip install <module-name>'")
os.chdir(os.path.dirname(os.path.realpath(__file__)))
os.chdir('..')
cmd = "find electrum -type f -name '*.py' -o -name '*.kv'"
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('electrum/locale'):
os.mkdir('electrum/locale')
cmd = 'xgettext -s --from-code UTF-8 --language Python --no-wrap -f app.fil --output=electrum/locale/messages.pot'
print('Generate template')
os.system(cmd)
os.chdir('electrum')
crowdin_identifier = 'electrum'
crowdin_file_name = 'files[electrum-client/messages.pot]'
locale_file_name = 'locale/messages.pot'
# Download & unzip
print('Download translations')
s = requests.request('GET', 'https://crowdin.com/backend/download/project/' + crowdin_identifier + '.zip').content
zfobj = zipfile.ZipFile(io.BytesIO(s))
print('Unzip translations')
for name in zfobj.namelist():
if not name.startswith('electrum-client/locale'):
continue
if name.endswith('/'):
if not os.path.exists(name[16:]):
os.mkdir(name[16:])
else:
with open(name[16:], 'wb') as output:
output.write(zfobj.read(name))
# Convert .po to .mo
print('Installing')
for lang in os.listdir('locale'):
if lang.startswith('messages'):
continue
# Check LC_MESSAGES folder
mo_dir = 'locale/%s/LC_MESSAGES' % lang
if not os.path.exists(mo_dir):
os.mkdir(mo_dir)
cmd = 'msgfmt --output-file="%s/electrum.mo" "locale/%s/electrum.po"' % (mo_dir,lang)
print('Installing', lang)
os.system(cmd)

59
contrib/push_locale Normal file
View file

@ -0,0 +1,59 @@
#!/usr/bin/env python3
import os
import subprocess
import io
import zipfile
import sys
try:
import requests
except ImportError as e:
sys.exit(f"Error: {str(e)}. Try 'sudo python3 -m pip install <module-name>'")
os.chdir(os.path.dirname(os.path.realpath(__file__)))
os.chdir('..')
cmd = "find electrum -type f -name '*.py' -o -name '*.kv'"
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('electrum/locale'):
os.mkdir('electrum/locale')
cmd = 'xgettext -s --from-code UTF-8 --language Python --no-wrap -f app.fil --output=electrum/locale/messages.pot'
print('Generate template')
os.system(cmd)
os.chdir('electrum')
crowdin_identifier = 'electrum'
crowdin_file_name = 'files[electrum-client/messages.pot]'
locale_file_name = 'locale/messages.pot'
crowdin_api_key = None
filename = os.path.expanduser('~/.crowdin_api_key')
if os.path.exists(filename):
with open(filename) as f:
crowdin_api_key = f.read().strip()
if "crowdin_api_key" in os.environ:
crowdin_api_key = os.environ["crowdin_api_key"]
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:
files = {crowdin_file_name: f}
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)
print("", "export:", "-" * 20, response.text, "-" * 20, sep="\n")

View file

@ -1,2 +1,2 @@
PyQt5<5.11
pycryptodomex
PyQt5<5.12
PyQt5-sip<=4.19.13

View file

@ -1,8 +1,16 @@
# Note: hidapi requires Cython as a build-time dependency (it is not needed at runtime).
# For reproducible builds, the version of Cython must be pinned down.
# Further, the pinned Cython must be installed before hidapi is built;
# otherwise hidapi just downloads the latest Cython. To enforce order,
# Cython must be listed before hidapi. Notably this also applies to
# deterministic-build/requirements-hw.txt where items are lexicographically sorted.
# Hence, we rely on "Cython" preceding "hidapi" lexicographically... :/
# see https://github.com/spesmilo/electrum/issues/5859
Cython>=0.27
trezor[hidapi]>=0.9.0
trezor[hidapi]>=0.11.5
safet[hidapi]>=0.1.0
keepkey
btchip-python
ckcc-protocol>=0.7.2
websocket-client
keepkey>=6.3.1
btchip-python>=0.1.26
ckcc-protocol>=0.7.7
hidapi

View file

@ -0,0 +1,7 @@
pip
setuptools
# needed by pyinstaller:
pefile>=2017.8.1
altgraph
pywin32-ctypes>=0.2.0

View file

@ -1,10 +1,15 @@
pyaes>=0.1a1
ecdsa>=0.9
requests
ecdsa>=0.14
qrcode
protobuf
dnspython
jsonrpclib-pelix
PySocks>=1.6.6
qdarkstyle<3.0
typing>=3.0.0
qdarkstyle<2.7
aiorpcx>=0.18,<0.19
aiohttp>=3.3.0,<4.0.0
aiohttp_socks
certifi
bitstring
pycryptodomex>=3.7
jsonrpcserver
jsonrpcclient
attrs

0
contrib/sign_packages Executable file → Normal file
View file

4
contrib/sign_version Normal file
View file

@ -0,0 +1,4 @@
#!/bin/bash
version=`python3 -c "import electrum; print(electrum.version.ELECTRUM_VERSION)"`
sig=`./run_electrum -w $SIGNING_WALLET signmessage $SIGNING_ADDRESS $version`
echo "{ \"version\":\"$version\", \"signatures\":{ \"$SIGNING_ADDRESS\":\"$sig\"}}"

12
contrib/udev/20-hw1.rules Normal file
View file

@ -0,0 +1,12 @@
# HW.1 / Nano
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1b7c|2b7c|3b7c|4b7c", TAG+="uaccess", TAG+="udev-acl"
# Blue
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0000|0000|0001|0002|0003|0004|0005|0006|0007|0008|0009|000a|000b|000c|000d|000e|000f|0010|0011|0012|0013|0014|0015|0016|0017|0018|0019|001a|001b|001c|001d|001e|001f", TAG+="uaccess", TAG+="udev-acl"
# Nano S
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001|1000|1001|1002|1003|1004|1005|1006|1007|1008|1009|100a|100b|100c|100d|100e|100f|1010|1011|1012|1013|1014|1015|1016|1017|1018|1019|101a|101b|101c|101d|101e|101f", TAG+="uaccess", TAG+="udev-acl"
# Aramis
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0002|2000|2001|2002|2003|2004|2005|2006|2007|2008|2009|200a|200b|200c|200d|200e|200f|2010|2011|2012|2013|2014|2015|2016|2017|2018|2019|201a|201b|201c|201d|201e|201f", TAG+="uaccess", TAG+="udev-acl"
# HW2
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0003|3000|3001|3002|3003|3004|3005|3006|3007|3008|3009|300a|300b|300c|300d|300e|300f|3010|3011|3012|3013|3014|3015|3016|3017|3018|3019|301a|301b|301c|301d|301e|301f", TAG+="uaccess", TAG+="udev-acl"
# Nano X
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0004|4000|4001|4002|4003|4004|4005|4006|4007|4008|4009|400a|400b|400c|400d|400e|400f|4010|4011|4012|4013|4014|4015|4016|4017|4018|4019|401a|401b|401c|401d|401e|401f", TAG+="uaccess", TAG+="udev-acl"

View file

@ -0,0 +1,16 @@
# Linux udev support file.
#
# This is a example udev file for HIDAPI devices which changes the permissions
# to 0666 (world readable/writable) for a specific device on Linux systems.
#
# - Copy this file into /etc/udev/rules.d and unplug and re-plug your Coldcard.
# - Udev does not have to be restarted.
#
# probably not needed:
SUBSYSTEMS=="usb", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666"
# required:
# from <https://github.com/signal11/hidapi/blob/master/udev/99-hid.rules>
KERNEL=="hidraw*", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666"

View file

@ -0,0 +1 @@
SUBSYSTEM=="usb", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="dbb%n", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2402"

View file

@ -0,0 +1,10 @@
# Put this file into /usr/lib/udev/rules.d or /etc/udev/rules.d
# Archos Safe-T mini
SUBSYSTEM=="usb", ATTR{idVendor}=="0e79", ATTR{idProduct}=="6000", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="safe-tr%n"
KERNEL=="hidraw*", ATTRS{idVendor}=="0e79", ATTRS{idProduct}=="6000", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
# Archos Safe-T mini Bootloader
SUBSYSTEM=="usb", ATTR{idVendor}=="0e79", ATTR{idProduct}=="6001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="safe-t%n"
KERNEL=="hidraw*", ATTRS{idVendor}=="0e79", ATTRS{idProduct}=="6001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"

View file

@ -0,0 +1,17 @@
# Trezor: The Original Hardware Wallet
# https://trezor.io/
#
# Put this file into /etc/udev/rules.d
#
# If you are creating a distribution package,
# put this into /usr/lib/udev/rules.d or /lib/udev/rules.d
# depending on your distribution
# Trezor
SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
# Trezor v2
SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"

View file

@ -0,0 +1,11 @@
# KeepKey: Your Private Bitcoin Vault
# http://www.keepkey.com/
# Put this file into /usr/lib/udev/rules.d or /etc/udev/rules.d
# KeepKey HID Firmware/Bootloader
SUBSYSTEM=="usb", ATTR{idVendor}=="2b24", ATTR{idProduct}=="0001", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="keepkey%n"
KERNEL=="hidraw*", ATTRS{idVendor}=="2b24", ATTRS{idProduct}=="0001", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
# KeepKey WebUSB Firmware/Bootloader
SUBSYSTEM=="usb", ATTR{idVendor}=="2b24", ATTR{idProduct}=="0002", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="keepkey%n"
KERNEL=="hidraw*", ATTRS{idVendor}=="2b24", ATTRS{idProduct}=="0002", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"

View file

@ -0,0 +1 @@
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2402", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="dbbf%n"

24
contrib/udev/README.md Normal file
View file

@ -0,0 +1,24 @@
# udev rules
This directory contains all of the udev rules for the supported devices
as retrieved from vendor websites and repositories.
These are necessary for the devices to be usable on Linux environments.
- `20-hw1.rules` (Ledger): https://github.com/LedgerHQ/udev-rules/blob/master/20-hw1.rules
- `51-coinkite.rules` (Coldcard): https://github.com/Coldcard/ckcc-protocol/blob/master/51-coinkite.rules
- `51-hid-digitalbitbox.rules`, `52-hid-digitalbitbox.rules` (Digital Bitbox): https://shiftcrypto.ch/start_linux
- `51-trezor.rules` (Trezor): https://github.com/trezor/trezor-common/blob/master/udev/51-trezor.rules
- `51-usb-keepkey.rules` (Keepkey): https://github.com/keepkey/udev-rules/blob/master/51-usb-keepkey.rules
- `51-safe-t.rules` (Archos): https://github.com/archos-safe-t/safe-t-common/blob/master/udev/51-safe-t.rules
# Usage
Apply these rules by copying them to `/etc/udev/rules.d/` and notifying `udevadm`.
Your user will need to be added to the `plugdev` group, which needs to be created if it does not already exist.
```
$ sudo groupadd plugdev
$ sudo usermod -aG plugdev $(whoami)
$ sudo cp contrib/udev/*.rules /etc/udev/rules.d/
$ sudo udevadm control --reload-rules && sudo udevadm trigger
```

3
contrib/upload Executable file → Normal file
View file

@ -2,13 +2,14 @@
set -e
host=$1
version=`git describe --tags`
echo $version
here=$(dirname "$0")
cd $here/../dist
sftp -oBatchMode=no -b - thomasv@download.electrum.org << !
sftp -oBatchMode=no -b - thomasv@$host << !
cd electrum-downloads
mkdir $version
cd $version

4
electrum-env Executable file → Normal file
View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
#
# This script creates a virtualenv named 'env' and installs all
# python dependencies before activating the env and running Electrum.
@ -17,7 +17,7 @@ if [ -e ./env/bin/activate ]; then
else
virtualenv env -p `which python3`
source ./env/bin/activate
python3 setup.py install
python3 -m pip install .[fast]
fi
export PYTHONPATH="/usr/local/lib/python${PYTHON_VER}/site-packages:$PYTHONPATH"

View file

@ -1,16 +0,0 @@
# Configuration file for the Electrum client
# Settings defined here are shared across wallets
#
# copy this file to /etc/electrum.conf if you want read-only settings
[client]
server = electrum.novit.ro:50001:t
proxy = None
gap_limit = 5
# booleans use python syntax
use_change = True
gui = qt
num_zeros = 2
# default transaction fee is in Satoshis
fee = 10000
winpos-qt = [799, 226, 877, 435]

View file

@ -3,19 +3,20 @@
[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
Name[en_US]=Electrum Bitcoin Wallet
Name=Electrum Bitcoin Wallet
Categories=Finance;Network;
StartupNotify=false
StartupNotify=true
StartupWMClass=electrum
Terminal=false
Type=Application
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

View file

@ -1,14 +1,30 @@
import sys
import os
# these are ~duplicated from run_electrum:
is_bundle = getattr(sys, 'frozen', False)
is_local = not is_bundle and os.path.exists(os.path.join(os.path.dirname(os.path.dirname(__file__)), "electrum.desktop"))
# when running from source, on Windows, also search for DLLs in inner 'electrum' folder
if is_local and os.name == 'nt':
if hasattr(os, 'add_dll_directory'): # requires python 3.8+
os.add_dll_directory(os.path.dirname(__file__))
from .version import ELECTRUM_VERSION
from .util import format_satoshis, print_msg, print_error, set_verbosity
from .util import format_satoshis
from .wallet import Wallet
from .storage import WalletStorage
from .coinchooser import COIN_CHOOSERS
from .network import Network, pick_random_server
from .interface import Connection, Interface
from .simple_config import SimpleConfig, get_config, set_config
from .interface import Interface
from .simple_config import SimpleConfig
from . import bitcoin
from . import transaction
from . import daemon
from .transaction import Transaction
from .plugin import BasePlugin
from .commands import Commands, known_commands
__version__ = ELECTRUM_VERSION

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,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 asyncio
import json
import locale
import traceback
@ -26,14 +27,14 @@ import subprocess
import sys
import os
import requests
from .version import ELECTRUM_VERSION
from .import constants
from . import constants
from .i18n import _
from .util import make_aiohttp_session
from .logging import describe_os_version, Logger
class BaseCrashReporter(object):
class BaseCrashReporter(Logger):
report_server = "https://crashhub.electrum.org"
config_key = "show_crash_reporter"
issue_template = """<h2>Traceback</h2>
@ -58,18 +59,25 @@ class BaseCrashReporter(object):
ASK_CONFIRM_SEND = _("Do you want to send this report?")
def __init__(self, exctype, value, tb):
Logger.__init__(self)
self.exc_args = (exctype, value, tb)
def send_report(self, endpoint="/crash"):
def send_report(self, asyncio_loop, proxy, endpoint="/crash", *, timeout=None):
if constants.net.GENESIS[-4:] not in ["4943", "e26f"] and ".electrum.org" in BaseCrashReporter.report_server:
# Gah! Some kind of altcoin wants to send us crash reports.
raise Exception(_("Missing report URL."))
report = self.get_traceback_info()
report.update(self.get_additional_info())
report = json.dumps(report)
response = requests.post(BaseCrashReporter.report_server + endpoint, data=report)
coro = self.do_post(proxy, BaseCrashReporter.report_server + endpoint, data=report)
response = asyncio.run_coroutine_threadsafe(coro, asyncio_loop).result(timeout)
return response
async def do_post(self, proxy, url, data):
async with make_aiohttp_session(proxy) as session:
async with session.post(url, data=data) as resp:
return await resp.text()
def get_traceback_info(self):
exc_string = str(self.exc_args[1])
stack = traceback.extract_tb(self.exc_args[2])
@ -89,7 +97,7 @@ class BaseCrashReporter(object):
args = {
"app_version": ELECTRUM_VERSION,
"python_version": sys.version,
"os": self.get_os_version(),
"os": describe_os_version(),
"wallet_type": "unknown",
"locale": locale.getdefaultlocale()[0] or "?",
"description": self.get_user_description()
@ -124,5 +132,19 @@ class BaseCrashReporter(object):
def get_wallet_type(self):
raise NotImplementedError
def get_os_version(self):
raise NotImplementedError
def trigger_crash():
# note: do not change the type of the exception, the message,
# or the name of this method. All reports generated through this
# method will be grouped together by the crash reporter, and thus
# don't spam the issue tracker.
class TestingException(Exception):
pass
def crash_test():
raise TestingException("triggered crash for testing purposes")
import threading
t = threading.Thread(target=crash_test)
t.start()

View file

@ -25,16 +25,31 @@
import os
import sys
import copy
import traceback
from functools import partial
from typing import List, TYPE_CHECKING, Tuple, NamedTuple, Any, Dict, Optional
from . import bitcoin
from . import keystore
from .keystore import bip44_derivation, purpose48_derivation
from .wallet import Imported_Wallet, Standard_Wallet, Multisig_Wallet, wallet_types, Wallet
from .storage import STO_EV_USER_PW, STO_EV_XPUB_PW, get_derivation_used_for_hw_device_encryption
from . import mnemonic
from .bip32 import is_bip32_derivation, xpub_type, normalize_bip32_derivation, BIP32Node
from .keystore import bip44_derivation, purpose48_derivation, Hardware_KeyStore, KeyStore
from .wallet import (Imported_Wallet, Standard_Wallet, Multisig_Wallet,
wallet_types, Wallet, Abstract_Wallet)
from .storage import (WalletStorage, StorageEncryptionVersion,
get_derivation_used_for_hw_device_encryption)
from .wallet_db import WalletDB
from .i18n import _
from .util import UserCancelled, InvalidPassword, WalletFileException
from .simple_config import SimpleConfig
from .plugin import Plugins, HardwarePluginLibraryUnavailable
from .logging import Logger
from .plugins.hw_wallet.plugin import OutdatedHwFirmwareException, HW_PluginBase
if TYPE_CHECKING:
from .plugin import DeviceInfo, BasePlugin
# hardware device setup purpose
HWD_SETUP_NEW_WALLET, HWD_SETUP_DECRYPT_WALLET = range(0, 2)
@ -46,53 +61,77 @@ class ScriptTypeNotSupported(Exception): pass
class GoBack(Exception): pass
class BaseWizard(object):
class WizardStackItem(NamedTuple):
action: Any
args: Any
kwargs: Dict[str, Any]
db_data: dict
def __init__(self, config, plugins, storage):
class WizardWalletPasswordSetting(NamedTuple):
password: Optional[str]
encrypt_storage: bool
storage_enc_version: StorageEncryptionVersion
encrypt_keystore: bool
class BaseWizard(Logger):
def __init__(self, config: SimpleConfig, plugins: Plugins):
super(BaseWizard, self).__init__()
Logger.__init__(self)
self.config = config
self.plugins = plugins
self.storage = storage
self.wallet = None
self.stack = []
self.plugin = None
self.keystores = []
self.data = {}
self.pw_args = None # type: Optional[WizardWalletPasswordSetting]
self._stack = [] # type: List[WizardStackItem]
self.plugin = None # type: Optional[BasePlugin]
self.keystores = [] # type: List[KeyStore]
self.is_kivy = config.get('gui') == 'kivy'
self.seed_type = None
def set_icon(self, icon):
pass
def run(self, *args):
def run(self, *args, **kwargs):
action = args[0]
args = args[1:]
self.stack.append((action, args))
db_data = copy.deepcopy(self.data)
self._stack.append(WizardStackItem(action, args, kwargs, db_data))
if not action:
return
if type(action) is tuple:
self.plugin, action = action
if self.plugin and hasattr(self.plugin, action):
f = getattr(self.plugin, action)
f(self, *args)
f(self, *args, **kwargs)
elif hasattr(self, action):
f = getattr(self, action)
f(*args)
f(*args, **kwargs)
else:
raise Exception("unknown action", action)
def can_go_back(self):
return len(self.stack)>1
return len(self._stack) > 1
def go_back(self):
if not self.can_go_back():
return
self.stack.pop()
action, args = self.stack.pop()
self.run(action, *args)
# pop 'current' frame
self._stack.pop()
# pop 'previous' frame
stack_item = self._stack.pop()
# try to undo side effects since we last entered 'previous' frame
# FIXME only self.storage is properly restored
self.data = copy.deepcopy(stack_item.db_data)
# rerun 'previous' frame
self.run(stack_item.action, *stack_item.args, **stack_item.kwargs)
def reset_stack(self):
self._stack = []
def new(self):
name = os.path.basename(self.storage.path)
title = _("Create") + ' ' + name
title = _("Create new wallet")
message = '\n'.join([
_("What kind of wallet do you want to create?")
])
@ -100,49 +139,48 @@ class BaseWizard(object):
('standard', _("Standard wallet")),
('2fa', _("Wallet with two-factor authentication")),
('multisig', _("Multi-signature wallet")),
('imported', _("Import Bitcoin addresses or private keys")),
('imported', _("Import LBRY Credits addresses or private keys")),
]
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type)
def upgrade_storage(self):
def upgrade_db(self, storage, db):
exc = None
def on_finished():
if exc is None:
self.wallet = Wallet(self.storage)
self.terminate()
self.terminate(storage=storage, db=db)
else:
raise exc
def do_upgrade():
nonlocal exc
try:
self.storage.upgrade()
db.upgrade()
except Exception as e:
exc = e
self.waiting_dialog(do_upgrade, _('Upgrading wallet format...'), on_finished=on_finished)
def load_2fa(self):
self.storage.put('wallet_type', '2fa')
self.storage.put('use_trustedcoin', True)
self.data['wallet_type'] = '2fa'
self.data['use_trustedcoin'] = True
self.plugin = self.plugins.load_plugin('trustedcoin')
def on_wallet_type(self, choice):
self.wallet_type = choice
self.data['wallet_type'] = self.wallet_type = choice
if choice == 'standard':
action = 'choose_keystore'
elif choice == 'multisig':
action = 'choose_multisig'
elif choice == '2fa':
self.load_2fa()
action = self.storage.get_action()
action = self.plugin.get_action(self.data)
elif choice == 'imported':
action = 'import_addresses_or_keys'
self.run(action)
def choose_multisig(self):
def on_multisig(m, n):
self.multisig_type = "%dof%d"%(m, n)
self.storage.put('wallet_type', self.multisig_type)
multisig_type = "%dof%d" % (m, n)
self.data['wallet_type'] = multisig_type
self.n = n
self.run('choose_keystore')
self.multisig_dialog(run_next=on_multisig)
@ -172,26 +210,29 @@ class BaseWizard(object):
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
def import_addresses_or_keys(self):
v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x)
title = _("Import Bitcoin Addresses")
message = _("Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys.")
v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x, raise_on_error=True)
title = _("Import LBRY Credits Addresses")
message = _("Enter a list of LBRY Credits addresses (this will create a watching-only wallet), or a list of private keys.")
self.add_xpub_dialog(title=title, message=message, run_next=self.on_import,
is_valid=v, allow_multi=True, show_wif_help=True)
def on_import(self, text):
# create a temporary wallet and exploit that modifications
# will be reflected on self.storage
# text is already sanitized by is_address_list and is_private_keys_list
if keystore.is_address_list(text):
w = Imported_Wallet(self.storage)
for x in text.split():
w.import_address(x)
self.data['addresses'] = {}
for addr in text.split():
assert bitcoin.is_address(addr)
self.data['addresses'][addr] = {}
elif keystore.is_private_key_list(text):
self.data['addresses'] = {}
k = keystore.Imported_KeyStore({})
self.storage.put('keystore', k.dump())
w = Imported_Wallet(self.storage)
for x in keystore.get_private_keys(text):
w.import_private_key(x, None)
self.keystores.append(w.keystore)
keys = keystore.get_private_keys(text)
for pk in keys:
assert bitcoin.is_private_key(pk)
txin_type, pubkey = k.import_privkey(pk, None)
addr = bitcoin.pubkey_to_address(txin_type, pubkey)
self.data['addresses'][addr] = {'type':txin_type, 'pubkey':pubkey}
self.keystores.append(k)
else:
return self.terminate()
return self.run('create_wallet')
@ -213,49 +254,70 @@ class BaseWizard(object):
k = keystore.from_master_key(text)
self.on_keystore(k)
def choose_hw_device(self, purpose=HWD_SETUP_NEW_WALLET):
def choose_hw_device(self, purpose=HWD_SETUP_NEW_WALLET, *, storage=None):
title = _('Hardware Keystore')
# check available plugins
support = self.plugins.get_hardware_support()
if not support:
msg = '\n'.join([
_('No hardware wallet support found on your system.'),
_('Please install the relevant libraries (eg python-trezor for Trezor).'),
])
self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device(purpose))
return
# scan devices
devices = []
supported_plugins = self.plugins.get_hardware_support()
devices = [] # type: List[Tuple[str, DeviceInfo]]
devmgr = self.plugins.device_manager
debug_msg = ''
def failed_getting_device_infos(name, e):
nonlocal debug_msg
err_str_oneline = ' // '.join(str(e).splitlines())
self.logger.warning(f'error getting device infos for {name}: {err_str_oneline}')
indented_error_msg = ' '.join([''] + str(e).splitlines(keepends=True))
debug_msg += f' {name}: (error getting device infos)\n{indented_error_msg}\n'
# scan devices
try:
scanned_devices = devmgr.scan_devices()
except BaseException as e:
devmgr.print_error('error scanning devices: {}'.format(e))
self.logger.info('error scanning devices: {}'.format(repr(e)))
debug_msg = ' {}:\n {}'.format(_('Error scanning devices'), e)
else:
debug_msg = ''
for name, description, plugin in support:
for splugin in supported_plugins:
name, plugin = splugin.name, splugin.plugin
# plugin init errored?
if not plugin:
e = splugin.exception
indented_error_msg = ' '.join([''] + str(e).splitlines(keepends=True))
debug_msg += f' {name}: (error during plugin init)\n'
debug_msg += ' {}\n'.format(_('You might have an incompatible library.'))
debug_msg += f'{indented_error_msg}\n'
continue
# see if plugin recognizes 'scanned_devices'
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))
indented_error_msg = ' '.join([''] + str(e).splitlines(keepends=True))
debug_msg += ' {}:\n{}\n'.format(plugin.name, indented_error_msg)
device_infos = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices,
include_failing_clients=True)
except HardwarePluginLibraryUnavailable as e:
failed_getting_device_infos(name, e)
continue
devices += list(map(lambda x: (name, x), u))
except BaseException as e:
self.logger.exception('')
failed_getting_device_infos(name, e)
continue
device_infos_failing = list(filter(lambda di: di.exception is not None, device_infos))
for di in device_infos_failing:
failed_getting_device_infos(name, di.exception)
device_infos_working = list(filter(lambda di: di.exception is None, device_infos))
devices += list(map(lambda x: (name, x), device_infos_working))
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.') + '\n\n',
_('Debug message') + '\n',
debug_msg
])
self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device(purpose))
msg = (_('No hardware device detected.') + '\n' +
_('To trigger a rescan, press \'Next\'.') + '\n\n')
if sys.platform == 'win32':
msg += _('If your device is not detected on Windows, go to "Settings", "Devices", "Connected devices", '
'and do "Remove device". Then, plug your device again.') + '\n'
msg += _('While this is less than ideal, it might help if you run Electrum as Administrator.') + '\n'
else:
msg += _('On Linux, you might have to add a new permission to your udev rules.') + '\n'
msg += '\n\n'
msg += _('Debug message') + '\n' + debug_msg
self.confirm_dialog(title=title, message=msg,
run_next=lambda x: self.choose_hw_device(purpose, storage=storage))
return
# select device
self.devices = devices
@ -263,13 +325,16 @@ class BaseWizard(object):
for name, info in devices:
state = _("initialized") if info.initialized else _("wiped")
label = info.label or _("An unnamed {}").format(name)
descr = "%s [%s, %s]" % (label, name, state)
try: transport_str = info.device.transport_ui_string[:20]
except: transport_str = 'unknown transport'
descr = f"{label} [{name}, {state}, {transport_str}]"
choices.append(((name, info), descr))
msg = _('Select a device') + ':'
self.choice_dialog(title=title, message=msg, choices=choices, run_next= lambda *args: self.on_device(*args, purpose=purpose))
self.choice_dialog(title=title, message=msg, choices=choices,
run_next=lambda *args: self.on_device(*args, purpose=purpose, storage=storage))
def on_device(self, name, device_info, *, purpose):
self.plugin = self.plugins.get_plugin(name)
def on_device(self, name, device_info, *, purpose, storage=None):
self.plugin = self.plugins.get_plugin(name) # type: HW_PluginBase
try:
self.plugin.setup_device(device_info, self, purpose)
except OSError as e:
@ -279,26 +344,35 @@ class BaseWizard(object):
+ _('Please try again.'))
devmgr = self.plugins.device_manager
devmgr.unpair_id(device_info.device.id_)
self.choose_hw_device(purpose)
self.choose_hw_device(purpose, storage=storage)
return
except OutdatedHwFirmwareException as e:
if self.question(e.text_ignore_old_fw_and_continue(), title=_("Outdated device firmware")):
self.plugin.set_ignore_outdated_fw()
# will need to re-pair
devmgr = self.plugins.device_manager
devmgr.unpair_id(device_info.device.id_)
self.choose_hw_device(purpose, storage=storage)
return
except (UserCancelled, GoBack):
self.choose_hw_device(purpose)
self.choose_hw_device(purpose, storage=storage)
return
except BaseException as e:
traceback.print_exc(file=sys.stderr)
self.logger.exception('')
self.show_error(str(e))
self.choose_hw_device(purpose)
self.choose_hw_device(purpose, storage=storage)
return
if purpose == HWD_SETUP_NEW_WALLET:
def f(derivation, script_type):
derivation = normalize_bip32_derivation(derivation)
self.run('on_hw_derivation', name, device_info, derivation, script_type)
self.derivation_and_script_type_dialog(f)
elif purpose == HWD_SETUP_DECRYPT_WALLET:
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, ())
password = keystore.Xpub.get_pubkey_from_xpub(xpub, ()).hex()
try:
self.storage.decrypt(password)
storage.decrypt(password)
except InvalidPassword:
# try to clear session so that user can type another passphrase
devmgr = self.plugins.device_manager
@ -311,7 +385,7 @@ class BaseWizard(object):
def derivation_and_script_type_dialog(self, f):
message1 = _('Choose the type of addresses in your wallet.')
message2 = '\n'.join([
message2 = ' '.join([
_('You can override the suggested derivation path.'),
_('If you are not sure what this is, leave this field unchanged.')
])
@ -319,12 +393,13 @@ class BaseWizard(object):
# There is no general standard for HD multisig.
# For legacy, this is partially compatible with BIP45; assumes index=0
# For segwit, a custom path is used, as there is no standard at all.
default_choice_idx = 0
choices = [
('standard', 'legacy multisig (p2sh)', "m/45'/0"),
('p2wsh-p2sh', 'p2sh-segwit multisig (p2wsh-p2sh)', purpose48_derivation(0, xtype='p2wsh-p2sh')),
('p2wsh', 'native segwit multisig (p2wsh)', purpose48_derivation(0, xtype='p2wsh')),
('standard', 'legacy multisig (p2sh)', normalize_bip32_derivation("m/45'/0")),
]
else:
default_choice_idx = 0
choices = [
('standard', 'legacy (p2pkh)', bip44_derivation(0, bip43_purpose=44)),
('p2wpkh-p2sh', 'p2sh-segwit (p2wpkh-p2sh)', bip44_derivation(0, bip43_purpose=49)),
@ -334,7 +409,8 @@ class BaseWizard(object):
try:
self.choice_and_line_dialog(
run_next=f, title=_('Script type and Derivation path'), message1=message1,
message2=message2, choices=choices, test_text=bitcoin.is_bip32_derivation)
message2=message2, choices=choices, test_text=is_bip32_derivation,
default_choice_idx=default_choice_idx)
return
except ScriptTypeNotSupported as e:
self.show_error(e)
@ -342,18 +418,23 @@ class BaseWizard(object):
def on_hw_derivation(self, name, device_info, derivation, xtype):
from .keystore import hardware_keystore
devmgr = self.plugins.device_manager
try:
xpub = self.plugin.get_xpub(device_info.device.id_, derivation, xtype, self)
client = devmgr.client_by_id(device_info.device.id_)
if not client: raise Exception("failed to find client for device id")
root_fingerprint = client.request_root_fingerprint_from_device()
except ScriptTypeNotSupported:
raise # this is handled in derivation_dialog
except BaseException as e:
traceback.print_exc(file=sys.stderr)
self.logger.exception('')
self.show_error(e)
return
d = {
'type': 'hardware',
'hw_type': name,
'derivation': derivation,
'root_fingerprint': root_fingerprint,
'xpub': xpub,
'label': device_info.label,
}
@ -378,12 +459,12 @@ class BaseWizard(object):
def restore_from_seed(self):
self.opt_bip39 = True
self.opt_ext = True
is_cosigning_seed = lambda x: bitcoin.seed_type(x) in ['standard', 'segwit']
test = bitcoin.is_seed if self.wallet_type == 'standard' else is_cosigning_seed
is_cosigning_seed = lambda x: mnemonic.seed_type(x) in ['standard', 'segwit']
test = mnemonic.is_seed if self.wallet_type == 'standard' else is_cosigning_seed
self.restore_seed_dialog(run_next=self.on_restore_seed, test=test)
def on_restore_seed(self, seed, is_bip39, is_ext):
self.seed_type = 'bip39' if is_bip39 else bitcoin.seed_type(seed)
self.seed_type = 'bip39' if is_bip39 else mnemonic.seed_type(seed)
if self.seed_type == 'bip39':
f = lambda passphrase: self.on_restore_bip39(seed, passphrase)
self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('')
@ -392,7 +473,7 @@ class BaseWizard(object):
self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('')
elif self.seed_type == 'old':
self.run('create_keystore', seed, '')
elif self.seed_type == '2fa':
elif mnemonic.is_any_2fa_seed_type(self.seed_type):
self.load_2fa()
self.run('on_restore_seed', seed, is_ext)
else:
@ -400,6 +481,7 @@ class BaseWizard(object):
def on_restore_bip39(self, seed, passphrase):
def f(derivation, script_type):
derivation = normalize_bip32_derivation(derivation)
self.run('on_bip43', seed, passphrase, derivation, script_type)
self.derivation_and_script_type_dialog(f)
@ -414,7 +496,6 @@ class BaseWizard(object):
def on_keystore(self, k):
has_xpub = isinstance(k, keystore.Xpub)
if has_xpub:
from .bitcoin import xpub_type
t1 = xpub_type(k.xpub)
if self.wallet_type == 'standard':
if has_xpub and t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']:
@ -442,7 +523,7 @@ class BaseWizard(object):
self.keystores.append(k)
if len(self.keystores) == 1:
xpub = k.get_master_public_key()
self.stack = []
self.reset_stack()
self.run('show_xpub_and_add_cosigners', xpub)
elif len(self.keystores) < self.n:
self.run('choose_keystore')
@ -453,9 +534,9 @@ class BaseWizard(object):
encrypt_keystore = any(k.may_have_password() for k in self.keystores)
# note: the following condition ("if") is duplicated logic from
# wallet.get_available_storage_encryption_version()
if self.wallet_type == 'standard' and isinstance(self.keystores[0], keystore.Hardware_KeyStore):
if self.wallet_type == 'standard' and isinstance(self.keystores[0], Hardware_KeyStore):
# offer encrypting with a pw derived from the hw device
k = self.keystores[0]
k = self.keystores[0] # type: Hardware_KeyStore
try:
k.handler = self.plugin.create_handler(self)
password = k.get_password_for_storage_encryption()
@ -465,68 +546,91 @@ class BaseWizard(object):
self.choose_hw_device()
return
except BaseException as e:
traceback.print_exc(file=sys.stderr)
self.logger.exception('')
self.show_error(str(e))
return
self.request_storage_encryption(
run_next=lambda encrypt_storage: self.on_password(
password,
encrypt_storage=encrypt_storage,
storage_enc_version=STO_EV_XPUB_PW,
storage_enc_version=StorageEncryptionVersion.XPUB_PASSWORD,
encrypt_keystore=False))
else:
# reset stack to disable 'back' button in password dialog
self.reset_stack()
# prompt the user to set an arbitrary password
self.request_password(
run_next=lambda password, encrypt_storage: self.on_password(
password,
encrypt_storage=encrypt_storage,
storage_enc_version=STO_EV_USER_PW,
storage_enc_version=StorageEncryptionVersion.USER_PASSWORD,
encrypt_keystore=encrypt_keystore),
force_disable_encrypt_cb=not encrypt_keystore)
def on_password(self, password, *, encrypt_storage,
storage_enc_version=STO_EV_USER_PW, encrypt_keystore):
self.storage.set_keystore_encryption(bool(password) and encrypt_keystore)
if encrypt_storage:
self.storage.set_password(password, enc_version=storage_enc_version)
def on_password(self, password, *, encrypt_storage: bool,
storage_enc_version=StorageEncryptionVersion.USER_PASSWORD,
encrypt_keystore: bool):
for k in self.keystores:
if k.may_have_password():
k.update_password(None, password)
if self.wallet_type == 'standard':
self.storage.put('seed_type', self.seed_type)
self.data['seed_type'] = self.seed_type
keys = self.keystores[0].dump()
self.storage.put('keystore', keys)
self.wallet = Standard_Wallet(self.storage)
self.run('create_addresses')
self.data['keystore'] = keys
elif self.wallet_type == 'multisig':
for i, k in enumerate(self.keystores):
self.storage.put('x%d/'%(i+1), k.dump())
self.storage.write()
self.wallet = Multisig_Wallet(self.storage)
self.run('create_addresses')
self.data['x%d/'%(i+1)] = k.dump()
elif self.wallet_type == 'imported':
if len(self.keystores) > 0:
keys = self.keystores[0].dump()
self.storage.put('keystore', keys)
self.wallet = Imported_Wallet(self.storage)
self.wallet.storage.write()
self.terminate()
self.data['keystore'] = keys
else:
raise Exception('Unknown wallet type')
self.pw_args = WizardWalletPasswordSetting(password=password,
encrypt_storage=encrypt_storage,
storage_enc_version=storage_enc_version,
encrypt_keystore=encrypt_keystore)
self.terminate()
def create_storage(self, path):
if os.path.exists(path):
raise Exception('file already exists at path')
if not self.pw_args:
return
pw_args = self.pw_args
self.pw_args = None # clean-up so that it can get GC-ed
storage = WalletStorage(path)
if pw_args.encrypt_storage:
storage.set_password(pw_args.password, enc_version=pw_args.storage_enc_version)
db = WalletDB('', manual_upgrades=False)
db.set_keystore_encryption(bool(pw_args.password) and pw_args.encrypt_keystore)
for key, value in self.data.items():
db.put(key, value)
db.load_plugins()
db.write(storage)
return storage, db
def terminate(self, *, storage: Optional[WalletStorage], db: Optional[WalletDB] = None):
raise NotImplementedError() # implemented by subclasses
def show_xpub_and_add_cosigners(self, xpub):
self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore'))
def choose_seed_type(self):
def choose_seed_type(self, message=None, choices=None):
title = _('Choose Seed type')
message = ' '.join([
_("The type of addresses used by your wallet will depend on your seed."),
_("Segwit wallets use bech32 addresses, defined in BIP173."),
_("Please note that websites and other wallets may not support these addresses yet."),
_("Thus, you might want to keep using a non-segwit wallet in order to be able to receive bitcoins during the transition period.")
])
choices = [
('create_standard_seed', _('Standard')),
('create_segwit_seed', _('Segwit')),
]
if message is None:
message = ' '.join([
_("The type of addresses used by your wallet will depend on your seed."),
_("Segwit wallets use bech32 addresses, defined in BIP173."),
_("Please note that websites and other wallets may not support these addresses yet."),
_("Thus, you might want to keep using a non-segwit wallet in order to be able to receive LBRY Credits during the transition period.")
])
if choices is None:
choices = [
('create_standard_seed', _('Legacy')),
]
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
def create_segwit_seed(self): self.create_seed('segwit')
@ -562,11 +666,3 @@ class BaseWizard(object):
self.line_dialog(run_next=f, title=title, message=message, default='', test=lambda x: x==passphrase)
else:
f('')
def create_addresses(self):
def task():
self.wallet.synchronize()
self.wallet.storage.write()
self.terminate()
msg = _("Electrum is generating your addresses, please wait...")
self.waiting_dialog(task, msg)

403
electrum/bip32.py Normal file
View file

@ -0,0 +1,403 @@
# Copyright (C) 2018 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
import hashlib
from typing import List, Tuple, NamedTuple, Union, Iterable, Sequence, Optional
from .util import bfh, bh2u, BitcoinException
from . import constants
from . import ecc
from .crypto import hash_160, hmac_oneshot
from .bitcoin import rev_hex, int_to_hex, EncodeBase58Check, DecodeBase58Check
from .logging import get_logger
_logger = get_logger(__name__)
BIP32_PRIME = 0x80000000
UINT32_MAX = (1 << 32) - 1
def protect_against_invalid_ecpoint(func):
def func_wrapper(*args):
child_index = args[-1]
while True:
is_prime = child_index & BIP32_PRIME
try:
return func(*args[:-1], child_index=child_index)
except ecc.InvalidECPointException:
_logger.warning('bip32 protect_against_invalid_ecpoint: skipping index')
child_index += 1
is_prime2 = child_index & BIP32_PRIME
if is_prime != is_prime2: raise OverflowError()
return func_wrapper
@protect_against_invalid_ecpoint
def CKD_priv(parent_privkey: bytes, parent_chaincode: bytes, child_index: int) -> Tuple[bytes, bytes]:
"""Child private key derivation function (from master private key)
If n is hardened (i.e. the 32nd bit is set), the resulting private key's
corresponding public key can NOT be determined without the master private key.
However, if n is not hardened, the resulting private key's corresponding
public key can be determined without the master private key.
"""
if child_index < 0: raise ValueError('the bip32 index needs to be non-negative')
is_hardened_child = bool(child_index & BIP32_PRIME)
return _CKD_priv(parent_privkey=parent_privkey,
parent_chaincode=parent_chaincode,
child_index=bfh(rev_hex(int_to_hex(child_index, 4))),
is_hardened_child=is_hardened_child)
def _CKD_priv(parent_privkey: bytes, parent_chaincode: bytes,
child_index: bytes, is_hardened_child: bool) -> Tuple[bytes, bytes]:
try:
keypair = ecc.ECPrivkey(parent_privkey)
except ecc.InvalidECPointException as e:
raise BitcoinException('Impossible xprv (not within curve order)') from e
parent_pubkey = keypair.get_public_key_bytes(compressed=True)
if is_hardened_child:
data = bytes([0]) + parent_privkey + child_index
else:
data = parent_pubkey + child_index
I = hmac_oneshot(parent_chaincode, data, hashlib.sha512)
I_left = ecc.string_to_number(I[0:32])
child_privkey = (I_left + ecc.string_to_number(parent_privkey)) % ecc.CURVE_ORDER
if I_left >= ecc.CURVE_ORDER or child_privkey == 0:
raise ecc.InvalidECPointException()
child_privkey = int.to_bytes(child_privkey, length=32, byteorder='big', signed=False)
child_chaincode = I[32:]
return child_privkey, child_chaincode
@protect_against_invalid_ecpoint
def CKD_pub(parent_pubkey: bytes, parent_chaincode: bytes, child_index: int) -> Tuple[bytes, bytes]:
"""Child public key derivation function (from public key only)
This function allows us to find the nth public key, as long as n is
not hardened. If n is hardened, we need the master private key to find it.
"""
if child_index < 0: raise ValueError('the bip32 index needs to be non-negative')
if child_index & BIP32_PRIME: raise Exception('not possible to derive hardened child from parent pubkey')
return _CKD_pub(parent_pubkey=parent_pubkey,
parent_chaincode=parent_chaincode,
child_index=bfh(rev_hex(int_to_hex(child_index, 4))))
# helper function, callable with arbitrary 'child_index' byte-string.
# i.e.: 'child_index' does not need to fit into 32 bits here! (c.f. trustedcoin billing)
def _CKD_pub(parent_pubkey: bytes, parent_chaincode: bytes, child_index: bytes) -> Tuple[bytes, bytes]:
I = hmac_oneshot(parent_chaincode, parent_pubkey + child_index, hashlib.sha512)
pubkey = ecc.ECPrivkey(I[0:32]) + ecc.ECPubkey(parent_pubkey)
if pubkey.is_at_infinity():
raise ecc.InvalidECPointException()
child_pubkey = pubkey.get_public_key_bytes(compressed=True)
child_chaincode = I[32:]
return child_pubkey, child_chaincode
def xprv_header(xtype: str, *, net=None) -> bytes:
if net is None:
net = constants.net
return net.XPRV_HEADERS[xtype].to_bytes(length=4, byteorder="big")
def xpub_header(xtype: str, *, net=None) -> bytes:
if net is None:
net = constants.net
return net.XPUB_HEADERS[xtype].to_bytes(length=4, byteorder="big")
class InvalidMasterKeyVersionBytes(BitcoinException): pass
class BIP32Node(NamedTuple):
xtype: str
eckey: Union[ecc.ECPubkey, ecc.ECPrivkey]
chaincode: bytes
depth: int = 0
fingerprint: bytes = b'\x00'*4 # as in serialized format, this is the *parent's* fingerprint
child_number: bytes = b'\x00'*4
@classmethod
def from_xkey(cls, xkey: str, *, net=None) -> 'BIP32Node':
if net is None:
net = constants.net
xkey = DecodeBase58Check(xkey)
if len(xkey) != 78:
raise BitcoinException('Invalid length for extended key: {}'
.format(len(xkey)))
depth = xkey[4]
fingerprint = xkey[5:9]
child_number = xkey[9:13]
chaincode = xkey[13:13 + 32]
header = int.from_bytes(xkey[0:4], byteorder='big')
if header in net.XPRV_HEADERS_INV:
headers_inv = net.XPRV_HEADERS_INV
is_private = True
elif header in net.XPUB_HEADERS_INV:
headers_inv = net.XPUB_HEADERS_INV
is_private = False
else:
raise InvalidMasterKeyVersionBytes(f'Invalid extended key format: {hex(header)}')
xtype = headers_inv[header]
if is_private:
eckey = ecc.ECPrivkey(xkey[13 + 33:])
else:
eckey = ecc.ECPubkey(xkey[13 + 32:])
return BIP32Node(xtype=xtype,
eckey=eckey,
chaincode=chaincode,
depth=depth,
fingerprint=fingerprint,
child_number=child_number)
@classmethod
def from_rootseed(cls, seed: bytes, *, xtype: str) -> 'BIP32Node':
I = hmac_oneshot(b"Bitcoin seed", seed, hashlib.sha512)
master_k = I[0:32]
master_c = I[32:]
return BIP32Node(xtype=xtype,
eckey=ecc.ECPrivkey(master_k),
chaincode=master_c)
@classmethod
def from_bytes(cls, b: bytes) -> 'BIP32Node':
if len(b) != 78:
raise Exception(f"unexpected xkey raw bytes len {len(b)} != 78")
xkey = EncodeBase58Check(b)
return cls.from_xkey(xkey)
def to_xprv(self, *, net=None) -> str:
payload = self.to_xprv_bytes(net=net)
return EncodeBase58Check(payload)
def to_xprv_bytes(self, *, net=None) -> bytes:
if not self.is_private():
raise Exception("cannot serialize as xprv; private key missing")
payload = (xprv_header(self.xtype, net=net) +
bytes([self.depth]) +
self.fingerprint +
self.child_number +
self.chaincode +
bytes([0]) +
self.eckey.get_secret_bytes())
assert len(payload) == 78, f"unexpected xprv payload len {len(payload)}"
return payload
def to_xpub(self, *, net=None) -> str:
payload = self.to_xpub_bytes(net=net)
return EncodeBase58Check(payload)
def to_xpub_bytes(self, *, net=None) -> bytes:
payload = (xpub_header(self.xtype, net=net) +
bytes([self.depth]) +
self.fingerprint +
self.child_number +
self.chaincode +
self.eckey.get_public_key_bytes(compressed=True))
assert len(payload) == 78, f"unexpected xpub payload len {len(payload)}"
return payload
def to_xkey(self, *, net=None) -> str:
if self.is_private():
return self.to_xprv(net=net)
else:
return self.to_xpub(net=net)
def to_bytes(self, *, net=None) -> bytes:
if self.is_private():
return self.to_xprv_bytes(net=net)
else:
return self.to_xpub_bytes(net=net)
def convert_to_public(self) -> 'BIP32Node':
if not self.is_private():
return self
pubkey = ecc.ECPubkey(self.eckey.get_public_key_bytes())
return self._replace(eckey=pubkey)
def is_private(self) -> bool:
return isinstance(self.eckey, ecc.ECPrivkey)
def subkey_at_private_derivation(self, path: Union[str, Iterable[int]]) -> 'BIP32Node':
if path is None:
raise Exception("derivation path must not be None")
if isinstance(path, str):
path = convert_bip32_path_to_list_of_uint32(path)
if not self.is_private():
raise Exception("cannot do bip32 private derivation; private key missing")
if not path:
return self
depth = self.depth
chaincode = self.chaincode
privkey = self.eckey.get_secret_bytes()
for child_index in path:
parent_privkey = privkey
privkey, chaincode = CKD_priv(privkey, chaincode, child_index)
depth += 1
parent_pubkey = ecc.ECPrivkey(parent_privkey).get_public_key_bytes(compressed=True)
fingerprint = hash_160(parent_pubkey)[0:4]
child_number = child_index.to_bytes(length=4, byteorder="big")
return BIP32Node(xtype=self.xtype,
eckey=ecc.ECPrivkey(privkey),
chaincode=chaincode,
depth=depth,
fingerprint=fingerprint,
child_number=child_number)
def subkey_at_public_derivation(self, path: Union[str, Iterable[int]]) -> 'BIP32Node':
if path is None:
raise Exception("derivation path must not be None")
if isinstance(path, str):
path = convert_bip32_path_to_list_of_uint32(path)
if not path:
return self.convert_to_public()
depth = self.depth
chaincode = self.chaincode
pubkey = self.eckey.get_public_key_bytes(compressed=True)
for child_index in path:
parent_pubkey = pubkey
pubkey, chaincode = CKD_pub(pubkey, chaincode, child_index)
depth += 1
fingerprint = hash_160(parent_pubkey)[0:4]
child_number = child_index.to_bytes(length=4, byteorder="big")
return BIP32Node(xtype=self.xtype,
eckey=ecc.ECPubkey(pubkey),
chaincode=chaincode,
depth=depth,
fingerprint=fingerprint,
child_number=child_number)
def calc_fingerprint_of_this_node(self) -> bytes:
"""Returns the fingerprint of this node.
Note that self.fingerprint is of the *parent*.
"""
# TODO cache this
return hash_160(self.eckey.get_public_key_bytes(compressed=True))[0:4]
def xpub_type(x):
return BIP32Node.from_xkey(x).xtype
def is_xpub(text):
try:
node = BIP32Node.from_xkey(text)
return not node.is_private()
except:
return False
def is_xprv(text):
try:
node = BIP32Node.from_xkey(text)
return node.is_private()
except:
return False
def xpub_from_xprv(xprv):
return BIP32Node.from_xkey(xprv).to_xpub()
def convert_bip32_path_to_list_of_uint32(n: str) -> List[int]:
"""Convert bip32 path to list of uint32 integers with prime flags
m/0/-1/1' -> [0, 0x80000001, 0x80000001]
based on code in trezorlib
"""
if not n:
return []
if n.endswith("/"):
n = n[:-1]
n = n.split('/')
# cut leading "m" if present, but do not require it
if n[0] == "m":
n = n[1:]
path = []
for x in n:
if x == '':
# gracefully allow repeating "/" chars in path.
# makes concatenating paths easier
continue
prime = 0
if x.endswith("'") or x.endswith("h"):
x = x[:-1]
prime = BIP32_PRIME
if x.startswith('-'):
if prime:
raise ValueError(f"bip32 path child index is signalling hardened level in multiple ways")
prime = BIP32_PRIME
child_index = abs(int(x)) | prime
if child_index > UINT32_MAX:
raise ValueError(f"bip32 path child index too large: {child_index} > {UINT32_MAX}")
path.append(child_index)
return path
def convert_bip32_intpath_to_strpath(path: Sequence[int]) -> str:
s = "m/"
for child_index in path:
if not isinstance(child_index, int):
raise TypeError(f"bip32 path child index must be int: {child_index}")
if not (0 <= child_index <= UINT32_MAX):
raise ValueError(f"bip32 path child index out of range: {child_index}")
prime = ""
if child_index & BIP32_PRIME:
prime = "'"
child_index = child_index ^ BIP32_PRIME
s += str(child_index) + prime + '/'
# cut trailing "/"
s = s[:-1]
return s
def is_bip32_derivation(s: str) -> bool:
try:
if not (s == 'm' or s.startswith('m/')):
return False
convert_bip32_path_to_list_of_uint32(s)
except:
return False
else:
return True
def normalize_bip32_derivation(s: Optional[str]) -> Optional[str]:
if s is None:
return None
if not is_bip32_derivation(s):
raise ValueError(f"invalid bip32 derivation: {s}")
ints = convert_bip32_path_to_list_of_uint32(s)
return convert_bip32_intpath_to_strpath(ints)
def is_all_public_derivation(path: Union[str, Iterable[int]]) -> bool:
"""Returns whether all levels in path use non-hardened derivation."""
if isinstance(path, str):
path = convert_bip32_path_to_list_of_uint32(path)
for child_index in path:
if child_index < 0:
raise ValueError('the bip32 index needs to be non-negative')
if child_index & BIP32_PRIME:
return False
return True
def root_fp_and_der_prefix_from_xkey(xkey: str) -> Tuple[Optional[str], Optional[str]]:
"""Returns the root bip32 fingerprint and the derivation path from the
root to the given xkey, if they can be determined. Otherwise (None, None).
"""
node = BIP32Node.from_xkey(xkey)
derivation_prefix = None
root_fingerprint = None
assert node.depth >= 0, node.depth
if node.depth == 0:
derivation_prefix = 'm'
root_fingerprint = node.calc_fingerprint_of_this_node().hex().lower()
elif node.depth == 1:
child_number_int = int.from_bytes(node.child_number, 'big')
derivation_prefix = convert_bip32_intpath_to_strpath([child_number_int])
root_fingerprint = node.fingerprint.hex()
return root_fingerprint, derivation_prefix

View file

@ -24,14 +24,18 @@
# SOFTWARE.
import hashlib
from typing import List
from typing import List, Tuple, TYPE_CHECKING, Optional, Union
from enum import IntEnum
from .util import bfh, bh2u, BitcoinException, print_error, assert_bytes, to_bytes, inv_dict
from .util import bfh, bh2u, BitcoinException, assert_bytes, to_bytes, inv_dict
from . import version
from . import segwit_addr
from . import constants
from . import ecc
from .crypto import Hash, sha256, hash_160, hmac_oneshot
from .crypto import sha256d, sha256, hash_160, hmac_oneshot
if TYPE_CHECKING:
from .network import Network
################################## transactions
@ -41,12 +45,154 @@ COIN = 100000000
TOTAL_COIN_SUPPLY_LIMIT_IN_BTC = 21000000
# supported types of transaction outputs
# TODO kill these with fire
TYPE_ADDRESS = 0
TYPE_PUBKEY = 1
TYPE_SCRIPT = 2
def rev_hex(s):
class opcodes(IntEnum):
# push value
OP_0 = 0x00
OP_FALSE = OP_0
OP_PUSHDATA1 = 0x4c
OP_PUSHDATA2 = 0x4d
OP_PUSHDATA4 = 0x4e
OP_1NEGATE = 0x4f
OP_RESERVED = 0x50
OP_1 = 0x51
OP_TRUE = OP_1
OP_2 = 0x52
OP_3 = 0x53
OP_4 = 0x54
OP_5 = 0x55
OP_6 = 0x56
OP_7 = 0x57
OP_8 = 0x58
OP_9 = 0x59
OP_10 = 0x5a
OP_11 = 0x5b
OP_12 = 0x5c
OP_13 = 0x5d
OP_14 = 0x5e
OP_15 = 0x5f
OP_16 = 0x60
# control
OP_NOP = 0x61
OP_VER = 0x62
OP_IF = 0x63
OP_NOTIF = 0x64
OP_VERIF = 0x65
OP_VERNOTIF = 0x66
OP_ELSE = 0x67
OP_ENDIF = 0x68
OP_VERIFY = 0x69
OP_RETURN = 0x6a
# stack ops
OP_TOALTSTACK = 0x6b
OP_FROMALTSTACK = 0x6c
OP_2DROP = 0x6d
OP_2DUP = 0x6e
OP_3DUP = 0x6f
OP_2OVER = 0x70
OP_2ROT = 0x71
OP_2SWAP = 0x72
OP_IFDUP = 0x73
OP_DEPTH = 0x74
OP_DROP = 0x75
OP_DUP = 0x76
OP_NIP = 0x77
OP_OVER = 0x78
OP_PICK = 0x79
OP_ROLL = 0x7a
OP_ROT = 0x7b
OP_SWAP = 0x7c
OP_TUCK = 0x7d
# splice ops
OP_CAT = 0x7e
OP_SUBSTR = 0x7f
OP_LEFT = 0x80
OP_RIGHT = 0x81
OP_SIZE = 0x82
# bit logic
OP_INVERT = 0x83
OP_AND = 0x84
OP_OR = 0x85
OP_XOR = 0x86
OP_EQUAL = 0x87
OP_EQUALVERIFY = 0x88
OP_RESERVED1 = 0x89
OP_RESERVED2 = 0x8a
# numeric
OP_1ADD = 0x8b
OP_1SUB = 0x8c
OP_2MUL = 0x8d
OP_2DIV = 0x8e
OP_NEGATE = 0x8f
OP_ABS = 0x90
OP_NOT = 0x91
OP_0NOTEQUAL = 0x92
OP_ADD = 0x93
OP_SUB = 0x94
OP_MUL = 0x95
OP_DIV = 0x96
OP_MOD = 0x97
OP_LSHIFT = 0x98
OP_RSHIFT = 0x99
OP_BOOLAND = 0x9a
OP_BOOLOR = 0x9b
OP_NUMEQUAL = 0x9c
OP_NUMEQUALVERIFY = 0x9d
OP_NUMNOTEQUAL = 0x9e
OP_LESSTHAN = 0x9f
OP_GREATERTHAN = 0xa0
OP_LESSTHANOREQUAL = 0xa1
OP_GREATERTHANOREQUAL = 0xa2
OP_MIN = 0xa3
OP_MAX = 0xa4
OP_WITHIN = 0xa5
# crypto
OP_RIPEMD160 = 0xa6
OP_SHA1 = 0xa7
OP_SHA256 = 0xa8
OP_HASH160 = 0xa9
OP_HASH256 = 0xaa
OP_CODESEPARATOR = 0xab
OP_CHECKSIG = 0xac
OP_CHECKSIGVERIFY = 0xad
OP_CHECKMULTISIG = 0xae
OP_CHECKMULTISIGVERIFY = 0xaf
# expansion
OP_NOP1 = 0xb0
OP_CHECKLOCKTIMEVERIFY = 0xb1
OP_NOP2 = OP_CHECKLOCKTIMEVERIFY
OP_CHECKSEQUENCEVERIFY = 0xb2
OP_NOP3 = OP_CHECKSEQUENCEVERIFY
OP_NOP4 = 0xb3
OP_NOP5 = 0xb4
OP_NOP6 = 0xb5
OP_NOP7 = 0xb6
OP_NOP8 = 0xb7
OP_NOP9 = 0xb8
OP_NOP10 = 0xb9
OP_INVALIDOPCODE = 0xff
def hex(self) -> str:
return bytes([self]).hex()
def rev_hex(s: str) -> str:
return bh2u(bfh(s)[::-1])
@ -57,7 +203,7 @@ def int_to_hex(i: int, length: int=1) -> str:
if not isinstance(i, int):
raise TypeError('{} instead of int'.format(i))
range_size = pow(256, length)
if i < -range_size/2 or i >= range_size:
if i < -(range_size//2) or i >= range_size:
raise OverflowError('cannot convert int {} to hex ({} bytes)'.format(i, length))
if i < 0:
# two's complement
@ -92,6 +238,9 @@ def script_num_to_hex(i: int) -> str:
def var_int(i: int) -> str:
# https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer
# https://github.com/bitcoin/bitcoin/blob/efe1ee0d8d7f82150789f1f6840f139289628a2b/src/serialize.h#L247
# "CompactSize"
assert i >= 0, i
if i<0xfd:
return int_to_hex(i)
elif i<=0xffff:
@ -109,15 +258,15 @@ def witness_push(item: str) -> str:
return var_int(len(item) // 2) + item
def op_push(i: int) -> str:
if i<0x4c: # OP_PUSHDATA1
def _op_push(i: int) -> str:
if i < opcodes.OP_PUSHDATA1:
return int_to_hex(i)
elif i<=0xff:
return '4c' + int_to_hex(i)
elif i<=0xffff:
return '4d' + int_to_hex(i,2)
elif i <= 0xff:
return opcodes.OP_PUSHDATA1.hex() + int_to_hex(i, 1)
elif i <= 0xffff:
return opcodes.OP_PUSHDATA2.hex() + int_to_hex(i, 2)
else:
return '4e' + int_to_hex(i,4)
return opcodes.OP_PUSHDATA4.hex() + int_to_hex(i, 4)
def push_script(data: str) -> str:
@ -128,181 +277,170 @@ def push_script(data: str) -> str:
ported from https://github.com/btcsuite/btcd/blob/fdc2bc867bda6b351191b5872d2da8270df00d13/txscript/scriptbuilder.go#L128
"""
data = bfh(data)
from .transaction import opcodes
data_len = len(data)
# "small integer" opcodes
if data_len == 0 or data_len == 1 and data[0] == 0:
return bh2u(bytes([opcodes.OP_0]))
return opcodes.OP_0.hex()
elif data_len == 1 and data[0] <= 16:
return bh2u(bytes([opcodes.OP_1 - 1 + data[0]]))
elif data_len == 1 and data[0] == 0x81:
return bh2u(bytes([opcodes.OP_1NEGATE]))
return opcodes.OP_1NEGATE.hex()
return op_push(data_len) + bh2u(data)
return _op_push(data_len) + bh2u(data)
def add_number_to_script(i: int) -> bytes:
return bfh(push_script(script_num_to_hex(i)))
hash_encode = lambda x: bh2u(x[::-1])
hash_decode = lambda x: bfh(x)[::-1]
hmac_sha_512 = lambda x, y: hmac_oneshot(x, y, hashlib.sha512)
def relayfee(network: 'Network' = None) -> int:
from .simple_config import FEERATE_DEFAULT_RELAY, FEERATE_MAX_RELAY
if network and network.relay_fee is not None:
fee = network.relay_fee
else:
fee = FEERATE_DEFAULT_RELAY
fee = min(fee, FEERATE_MAX_RELAY)
fee = max(fee, 0)
return fee
def is_new_seed(x, prefix=version.SEED_PREFIX):
from . import mnemonic
x = mnemonic.normalize_text(x)
s = bh2u(hmac_sha_512(b"Seed version", x.encode('utf8')))
return s.startswith(prefix)
def dust_threshold(network: 'Network'=None) -> int:
# Change <= dust threshold is added to the tx fee
return 182 * 3 * relayfee(network) // 1000
def is_old_seed(seed):
from . import old_mnemonic, mnemonic
seed = mnemonic.normalize_text(seed)
words = seed.split()
try:
# checks here are deliberately left weak for legacy reasons, see #3149
old_mnemonic.mn_decode(words)
uses_electrum_words = True
except Exception:
uses_electrum_words = False
try:
seed = bfh(seed)
is_hex = (len(seed) == 16 or len(seed) == 32)
except Exception:
is_hex = False
return is_hex or (uses_electrum_words and (len(words) == 12 or len(words) == 24))
def hash_encode(x: bytes) -> str:
return bh2u(x[::-1])
def seed_type(x):
if is_old_seed(x):
return 'old'
elif is_new_seed(x):
return 'standard'
elif is_new_seed(x, version.SEED_PREFIX_SW):
return 'segwit'
elif is_new_seed(x, version.SEED_PREFIX_2FA):
return '2fa'
return ''
is_seed = lambda x: bool(seed_type(x))
def hash_decode(x: str) -> bytes:
return bfh(x)[::-1]
############ functions from pywallet #####################
def hash160_to_b58_address(h160: bytes, addrtype):
s = bytes([addrtype])
s += h160
return base_encode(s+Hash(s)[0:4], base=58)
def hash160_to_b58_address(h160: bytes, addrtype: int) -> str:
s = bytes([addrtype]) + h160
s = s + sha256d(s)[0:4]
return base_encode(s, base=58)
def b58_address_to_hash160(addr):
def b58_address_to_hash160(addr: str) -> Tuple[int, bytes]:
addr = to_bytes(addr, 'ascii')
_bytes = base_decode(addr, 25, base=58)
_bytes = DecodeBase58Check(addr)
if len(_bytes) != 21:
raise Exception(f'expected 21 payload bytes in base58 address. got: {len(_bytes)}')
return _bytes[0], _bytes[1:21]
def hash160_to_p2pkh(h160, *, net=None):
if net is None:
net = constants.net
def hash160_to_p2pkh(h160: bytes, *, net=None) -> str:
if net is None: net = constants.net
return hash160_to_b58_address(h160, net.ADDRTYPE_P2PKH)
def hash160_to_p2sh(h160, *, net=None):
if net is None:
net = constants.net
def hash160_to_p2sh(h160: bytes, *, net=None) -> str:
if net is None: net = constants.net
return hash160_to_b58_address(h160, net.ADDRTYPE_P2SH)
def public_key_to_p2pkh(public_key: bytes) -> str:
return hash160_to_p2pkh(hash_160(public_key))
def public_key_to_p2pkh(public_key: bytes, *, net=None) -> str:
if net is None: net = constants.net
return hash160_to_p2pkh(hash_160(public_key), net=net)
def hash_to_segwit_addr(h, witver, *, net=None):
if net is None:
net = constants.net
def hash_to_segwit_addr(h: bytes, witver: int, *, net=None) -> str:
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)
def public_key_to_p2wpkh(public_key: bytes, *, net=None) -> str:
if net is None: net = constants.net
return hash_to_segwit_addr(hash_160(public_key), witver=0, net=net)
def script_to_p2wsh(script):
return hash_to_segwit_addr(sha256(bfh(script)), witver=0)
def script_to_p2wsh(script: str, *, net=None) -> str:
if net is None: net = constants.net
return hash_to_segwit_addr(sha256(bfh(script)), witver=0, net=net)
def p2wpkh_nested_script(pubkey):
def p2wpkh_nested_script(pubkey: str) -> str:
pkh = bh2u(hash_160(bfh(pubkey)))
return '00' + push_script(pkh)
def p2wsh_nested_script(witness_script):
def p2wsh_nested_script(witness_script: str) -> str:
wsh = bh2u(sha256(bfh(witness_script)))
return '00' + push_script(wsh)
def pubkey_to_address(txin_type, pubkey):
def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str:
if net is None: net = constants.net
if txin_type == 'p2pkh':
return public_key_to_p2pkh(bfh(pubkey))
return public_key_to_p2pkh(bfh(pubkey), net=net)
elif txin_type == 'p2wpkh':
return public_key_to_p2wpkh(bfh(pubkey))
return public_key_to_p2wpkh(bfh(pubkey), net=net)
elif txin_type == 'p2wpkh-p2sh':
scriptSig = p2wpkh_nested_script(pubkey)
return hash160_to_p2sh(hash_160(bfh(scriptSig)))
return hash160_to_p2sh(hash_160(bfh(scriptSig)), net=net)
else:
raise NotImplementedError(txin_type)
def redeem_script_to_address(txin_type, redeem_script):
# TODO this method is confusingly named
def redeem_script_to_address(txin_type: str, scriptcode: str, *, net=None) -> str:
if net is None: net = constants.net
if txin_type == 'p2sh':
return hash160_to_p2sh(hash_160(bfh(redeem_script)))
# given scriptcode is a redeem_script
return hash160_to_p2sh(hash_160(bfh(scriptcode)), net=net)
elif txin_type == 'p2wsh':
return script_to_p2wsh(redeem_script)
# given scriptcode is a witness_script
return script_to_p2wsh(scriptcode, net=net)
elif txin_type == 'p2wsh-p2sh':
scriptSig = p2wsh_nested_script(redeem_script)
return hash160_to_p2sh(hash_160(bfh(scriptSig)))
# given scriptcode is a witness_script
redeem_script = p2wsh_nested_script(scriptcode)
return hash160_to_p2sh(hash_160(bfh(redeem_script)), net=net)
else:
raise NotImplementedError(txin_type)
def script_to_address(script, *, net=None):
def script_to_address(script: str, *, net=None) -> str:
from .transaction import get_address_from_output_script
t, addr = get_address_from_output_script(bfh(script), net=net)
assert t == TYPE_ADDRESS
return addr
return get_address_from_output_script(bfh(script), net=net)
def address_to_script(addr, *, net=None):
if net is None:
net = constants.net
def address_to_script(addr: str, *, net=None) -> str:
if net is None: net = constants.net
if not is_address(addr, net=net):
raise BitcoinException(f"invalid bitcoin address: {addr}")
witver, witprog = segwit_addr.decode(net.SEGWIT_HRP, addr)
if witprog is not None:
if not (0 <= witver <= 16):
raise BitcoinException('impossible witness version: {}'.format(witver))
OP_n = witver + 0x50 if witver > 0 else 0
script = bh2u(bytes([OP_n]))
raise BitcoinException(f'impossible witness version: {witver}')
script = bh2u(add_number_to_script(witver))
script += push_script(bh2u(bytes(witprog)))
return script
addrtype, hash_160 = b58_address_to_hash160(addr)
addrtype, hash_160_ = b58_address_to_hash160(addr)
if addrtype == net.ADDRTYPE_P2PKH:
script = '76a9' # op_dup, op_hash_160
script += push_script(bh2u(hash_160))
script += '88ac' # op_equalverify, op_checksig
script = pubkeyhash_to_p2pkh_script(bh2u(hash_160_))
elif addrtype == net.ADDRTYPE_P2SH:
script = 'a9' # op_hash_160
script += push_script(bh2u(hash_160))
script += '87' # op_equal
script = opcodes.OP_HASH160.hex()
script += push_script(bh2u(hash_160_))
script += opcodes.OP_EQUAL.hex()
else:
raise BitcoinException('unknown address type: {}'.format(addrtype))
raise BitcoinException(f'unknown address type: {addrtype}')
return script
def address_to_scripthash(addr):
def address_to_scripthash(addr: str) -> str:
script = address_to_script(addr)
return script_to_scripthash(script)
def script_to_scripthash(script):
h = sha256(bytes.fromhex(script))[0:32]
def script_to_scripthash(script: str) -> str:
h = sha256(bfh(script))[0:32]
return bh2u(bytes(reversed(h)))
def public_key_to_p2pk_script(pubkey):
script = push_script(pubkey)
script += 'ac' # op_checksig
def public_key_to_p2pk_script(pubkey: str) -> str:
return push_script(pubkey) + opcodes.OP_CHECKSIG.hex()
def pubkeyhash_to_p2pkh_script(pubkey_hash160: str) -> str:
script = bytes([opcodes.OP_DUP, opcodes.OP_HASH160]).hex()
script += push_script(pubkey_hash160)
script += bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG]).hex()
return script
__b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
assert len(__b58chars) == 58
@ -310,7 +448,7 @@ __b43chars = b'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:'
assert len(__b43chars) == 43
def base_encode(v: bytes, base: int) -> str:
def base_encode(v: bytes, *, base: int) -> str:
""" encode v, which is a string of bytes, to base58."""
assert_bytes(v)
if base not in (58, 43):
@ -319,8 +457,11 @@ def base_encode(v: bytes, base: int) -> str:
if base == 43:
chars = __b43chars
long_value = 0
for (i, c) in enumerate(v[::-1]):
long_value += (256**i) * c
power_of_base = 1
for c in v[::-1]:
# naive but slow variant: long_value += (256**i) * c
long_value += power_of_base * c
power_of_base <<= 8
result = bytearray()
while long_value >= base:
div, mod = divmod(long_value, base)
@ -340,7 +481,7 @@ def base_encode(v: bytes, base: int) -> str:
return result.decode('ascii')
def base_decode(v, length, base):
def base_decode(v: Union[bytes, str], *, base: int, length: int = None) -> Optional[bytes]:
""" decode v into a string of len bytes."""
# assert_bytes(v)
v = to_bytes(v, 'ascii')
@ -350,11 +491,14 @@ def base_decode(v, length, base):
if base == 43:
chars = __b43chars
long_value = 0
for (i, c) in enumerate(v[::-1]):
power_of_base = 1
for c in v[::-1]:
digit = chars.find(bytes([c]))
if digit == -1:
raise ValueError('Forbidden character {} for base {}'.format(c, base))
long_value += digit * (base**i)
# naive but slow variant: long_value += digit * (base**i)
long_value += digit * power_of_base
power_of_base *= base
result = bytearray()
while long_value >= 256:
div, mod = divmod(long_value, 256)
@ -378,21 +522,20 @@ class InvalidChecksum(Exception):
pass
def EncodeBase58Check(vchIn):
hash = Hash(vchIn)
def EncodeBase58Check(vchIn: bytes) -> str:
hash = sha256d(vchIn)
return base_encode(vchIn + hash[0:4], base=58)
def DecodeBase58Check(psz):
vchRet = base_decode(psz, None, base=58)
key = vchRet[0:-4]
csum = vchRet[-4:]
hash = Hash(key)
cs32 = hash[0:4]
if cs32 != csum:
raise InvalidChecksum('expected {}, actual {}'.format(bh2u(cs32), bh2u(csum)))
def DecodeBase58Check(psz: Union[bytes, str]) -> bytes:
vchRet = base_decode(psz, base=58)
payload = vchRet[0:-4]
csum_found = vchRet[-4:]
csum_calculated = sha256d(payload)[0:4]
if csum_calculated != csum_found:
raise InvalidChecksum(f'calculated {bh2u(csum_calculated)}, found {bh2u(csum_found)}')
else:
return key
return payload
# backwards compat
@ -409,11 +552,8 @@ WIF_SCRIPT_TYPES = {
WIF_SCRIPT_TYPES_INV = inv_dict(WIF_SCRIPT_TYPES)
PURPOSE48_SCRIPT_TYPES = {
'p2wsh-p2sh': 1, # specifically multisig
'p2wsh': 2, # specifically multisig
}
PURPOSE48_SCRIPT_TYPES_INV = inv_dict(PURPOSE48_SCRIPT_TYPES)
def is_segwit_script_type(txin_type: str) -> bool:
return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh')
def serialize_privkey(secret: bytes, compressed: bool, txin_type: str,
@ -433,7 +573,7 @@ def serialize_privkey(secret: bytes, compressed: bool, txin_type: str,
return '{}:{}'.format(txin_type, base58_wif)
def deserialize_privkey(key: str) -> (str, bytes, bool):
def deserialize_privkey(key: str) -> Tuple[str, bytes, bool]:
if is_minikey(key):
return 'p2pkh', minikey_to_private_key(key), False
@ -463,53 +603,70 @@ def deserialize_privkey(key: str) -> (str, bytes, bool):
if len(vch) not in [33, 34]:
raise BitcoinException('invalid vch len for WIF key: {}'.format(len(vch)))
compressed = len(vch) == 34
compressed = False
if len(vch) == 34:
if vch[33] == 0x01:
compressed = True
else:
raise BitcoinException(f'invalid WIF key. length suggests compressed pubkey, '
f'but last byte is {vch[33]} != 0x01')
if is_segwit_script_type(txin_type) and not compressed:
raise BitcoinException('only compressed public keys can be used in segwit scripts')
secret_bytes = vch[1:33]
# we accept secrets outside curve range; cast into range here:
secret_bytes = ecc.ECPrivkey.normalize_secret_bytes(secret_bytes)
return txin_type, secret_bytes, compressed
def is_compressed(sec):
def is_compressed_privkey(sec: str) -> bool:
return deserialize_privkey(sec)[2]
def address_from_private_key(sec):
def address_from_private_key(sec: str) -> str:
txin_type, privkey, compressed = deserialize_privkey(sec)
public_key = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
return pubkey_to_address(txin_type, public_key)
def is_segwit_address(addr):
def is_segwit_address(addr: str, *, net=None) -> bool:
if net is None: net = constants.net
try:
witver, witprog = segwit_addr.decode(constants.net.SEGWIT_HRP, addr)
witver, witprog = segwit_addr.decode(net.SEGWIT_HRP, addr)
except Exception as e:
return False
return witprog is not None
def is_b58_address(addr):
def is_b58_address(addr: str, *, net=None) -> bool:
if net is None: net = constants.net
try:
# test length, checksum, encoding:
addrtype, h = b58_address_to_hash160(addr)
except Exception as e:
return False
if addrtype not in [constants.net.ADDRTYPE_P2PKH, constants.net.ADDRTYPE_P2SH]:
if addrtype not in [net.ADDRTYPE_P2PKH, net.ADDRTYPE_P2SH]:
return False
return addr == hash160_to_b58_address(h, addrtype)
return True
def is_address(addr):
return is_segwit_address(addr) or is_b58_address(addr)
def is_address(addr: str, *, net=None) -> bool:
if net is None: net = constants.net
return is_segwit_address(addr, net=net) \
or is_b58_address(addr, net=net)
def is_private_key(key):
def is_private_key(key: str, *, raise_on_error=False) -> bool:
try:
k = deserialize_privkey(key)
return k is not False
except:
deserialize_privkey(key)
return True
except BaseException as e:
if raise_on_error:
raise
return False
########### end pywallet functions #######################
def is_minikey(text):
def is_minikey(text: str) -> bool:
# Minikeys are typically 22 or 30 characters, but this routine
# permits any length of 20 or more provided the minikey is valid.
# A valid minikey must begin with an 'S', be in base58, and when
@ -519,264 +676,5 @@ def is_minikey(text):
and all(ord(c) in __b58chars for c in text)
and sha256(text + '?')[0] == 0x00)
def minikey_to_private_key(text):
def minikey_to_private_key(text: str) -> bytes:
return sha256(text)
###################################### BIP32 ##############################
BIP32_PRIME = 0x80000000
def protect_against_invalid_ecpoint(func):
def func_wrapper(*args):
n = args[-1]
while True:
is_prime = n & BIP32_PRIME
try:
return func(*args[:-1], n=n)
except ecc.InvalidECPointException:
print_error('bip32 protect_against_invalid_ecpoint: skipping index')
n += 1
is_prime2 = n & BIP32_PRIME
if is_prime != is_prime2: raise OverflowError()
return func_wrapper
# Child private key derivation function (from master private key)
# k = master private key (32 bytes)
# c = master chain code (extra entropy for key derivation) (32 bytes)
# n = the index of the key we want to derive. (only 32 bits will be used)
# If n is hardened (i.e. the 32nd bit is set), the resulting private key's
# corresponding public key can NOT be determined without the master private key.
# However, if n is not hardened, the resulting private key's corresponding
# public key can be determined without the master private key.
@protect_against_invalid_ecpoint
def CKD_priv(k, c, n):
if n < 0: raise ValueError('the bip32 index needs to be non-negative')
is_prime = n & BIP32_PRIME
return _CKD_priv(k, c, bfh(rev_hex(int_to_hex(n,4))), is_prime)
def _CKD_priv(k, c, s, is_prime):
try:
keypair = ecc.ECPrivkey(k)
except ecc.InvalidECPointException as e:
raise BitcoinException('Impossible xprv (not within curve order)') from e
cK = keypair.get_public_key_bytes(compressed=True)
data = bytes([0]) + k + s if is_prime else cK + s
I = hmac_oneshot(c, data, hashlib.sha512)
I_left = ecc.string_to_number(I[0:32])
k_n = (I_left + ecc.string_to_number(k)) % ecc.CURVE_ORDER
if I_left >= ecc.CURVE_ORDER or k_n == 0:
raise ecc.InvalidECPointException()
k_n = ecc.number_to_string(k_n, ecc.CURVE_ORDER)
c_n = I[32:]
return k_n, c_n
# Child public key derivation function (from public key only)
# K = master public key
# c = master chain code
# n = index of key we want to derive
# This function allows us to find the nth public key, as long as n is
# not hardened. If n is hardened, we need the master private key to find it.
@protect_against_invalid_ecpoint
def CKD_pub(cK, c, n):
if n < 0: raise ValueError('the bip32 index needs to be non-negative')
if n & BIP32_PRIME: raise Exception()
return _CKD_pub(cK, c, bfh(rev_hex(int_to_hex(n,4))))
# helper function, callable with arbitrary string.
# note: 's' does not need to fit into 32 bits here! (c.f. trustedcoin billing)
def _CKD_pub(cK, c, s):
I = hmac_oneshot(c, cK + s, hashlib.sha512)
pubkey = ecc.ECPrivkey(I[0:32]) + ecc.ECPubkey(cK)
if pubkey.is_at_infinity():
raise ecc.InvalidECPointException()
cK_n = pubkey.get_public_key_bytes(compressed=True)
c_n = I[32:]
return cK_n, c_n
def xprv_header(xtype, *, net=None):
if net is None:
net = constants.net
return bfh("%08x" % net.XPRV_HEADERS[xtype])
def xpub_header(xtype, *, net=None):
if net is None:
net = constants.net
return bfh("%08x" % net.XPUB_HEADERS[xtype])
def serialize_xprv(xtype, c, k, depth=0, fingerprint=b'\x00'*4,
child_number=b'\x00'*4, *, net=None):
if not ecc.is_secret_within_curve_range(k):
raise BitcoinException('Impossible xprv (not within curve order)')
xprv = xprv_header(xtype, net=net) \
+ bytes([depth]) + fingerprint + child_number + c + bytes([0]) + k
return EncodeBase58Check(xprv)
def serialize_xpub(xtype, c, cK, depth=0, fingerprint=b'\x00'*4,
child_number=b'\x00'*4, *, net=None):
xpub = xpub_header(xtype, net=net) \
+ bytes([depth]) + fingerprint + child_number + c + cK
return EncodeBase58Check(xpub)
class InvalidMasterKeyVersionBytes(BitcoinException): pass
def deserialize_xkey(xkey, prv, *, net=None):
if net is None:
net = constants.net
xkey = DecodeBase58Check(xkey)
if len(xkey) != 78:
raise BitcoinException('Invalid length for extended key: {}'
.format(len(xkey)))
depth = xkey[4]
fingerprint = xkey[5:9]
child_number = xkey[9:13]
c = xkey[13:13+32]
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 InvalidMasterKeyVersionBytes('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:]
if prv and not ecc.is_secret_within_curve_range(K_or_k):
raise BitcoinException('Impossible xprv (not within curve order)')
return xtype, depth, fingerprint, child_number, c, K_or_k
def deserialize_xpub(xkey, *, net=None):
return deserialize_xkey(xkey, False, net=net)
def deserialize_xprv(xkey, *, net=None):
return deserialize_xkey(xkey, True, net=net)
def xpub_type(x):
return deserialize_xpub(x)[0]
def is_xpub(text):
try:
deserialize_xpub(text)
return True
except:
return False
def is_xprv(text):
try:
deserialize_xprv(text)
return True
except:
return False
def xpub_from_xprv(xprv):
xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)
cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
def bip32_root(seed, xtype):
I = hmac_oneshot(b"Bitcoin seed", seed, hashlib.sha512)
master_k = I[0:32]
master_c = I[32:]
# create xprv first, as that will check if master_k is within curve order
xprv = serialize_xprv(xtype, master_c, master_k)
cK = ecc.ECPrivkey(master_k).get_public_key_bytes(compressed=True)
xpub = serialize_xpub(xtype, master_c, cK)
return xprv, xpub
def xpub_from_pubkey(xtype, cK):
if cK[0] not in (0x02, 0x03):
raise ValueError('Unexpected first byte: {}'.format(cK[0]))
return serialize_xpub(xtype, b'\x00'*32, cK)
def bip32_derivation(s):
if not s.startswith('m/'):
raise ValueError('invalid bip32 derivation path: {}'.format(s))
s = s[2:]
for n in s.split('/'):
if n == '': continue
i = int(n[:-1]) + BIP32_PRIME if n[-1] == "'" else int(n)
yield i
def convert_bip32_path_to_list_of_uint32(n: str) -> List[int]:
"""Convert bip32 path to list of uint32 integers with prime flags
m/0/-1/1' -> [0, 0x80000001, 0x80000001]
based on code in trezorlib
"""
path = []
for x in n.split('/')[1:]:
if x == '': continue
prime = 0
if x.endswith("'"):
x = x.replace('\'', '')
prime = BIP32_PRIME
if x.startswith('-'):
prime = BIP32_PRIME
path.append(abs(int(x)) | prime)
return path
def is_bip32_derivation(x):
try:
[ i for i in bip32_derivation(x)]
return True
except :
return False
def bip32_private_derivation(xprv, branch, sequence):
if not sequence.startswith(branch):
raise ValueError('incompatible branch ({}) and sequence ({})'
.format(branch, sequence))
if branch == sequence:
return xprv, xpub_from_xprv(xprv)
xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)
sequence = sequence[len(branch):]
for n in sequence.split('/'):
if n == '': continue
i = int(n[:-1]) + BIP32_PRIME if n[-1] == "'" else int(n)
parent_k = k
k, c = CKD_priv(k, c, i)
depth += 1
parent_cK = ecc.ECPrivkey(parent_k).get_public_key_bytes(compressed=True)
fingerprint = hash_160(parent_cK)[0:4]
child_number = bfh("%08X"%i)
cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
xprv = serialize_xprv(xtype, c, k, depth, fingerprint, child_number)
return xprv, xpub
def bip32_public_derivation(xpub, branch, sequence):
xtype, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub)
if not sequence.startswith(branch):
raise ValueError('incompatible branch ({}) and sequence ({})'
.format(branch, sequence))
sequence = sequence[len(branch):]
for n in sequence.split('/'):
if n == '': continue
i = int(n)
parent_cK = cK
cK, c = CKD_pub(cK, c, i)
depth += 1
fingerprint = hash_160(parent_cK)[0:4]
child_number = bfh("%08X"%i)
return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
def bip32_private_key(sequence, k, chain):
for i in sequence:
k, chain = CKD_priv(k, chain, i)
return k

View file

@ -22,14 +22,26 @@
# SOFTWARE.
import os
import threading
from typing import Optional, Dict, Mapping, Sequence
import hashlib
import hmac
from . import util
from .bitcoin import Hash, hash_encode, int_to_hex, rev_hex
from .bitcoin import hash_encode, int_to_hex, rev_hex
from .crypto import sha256d
from . import constants
from .util import bfh, bh2u
from .simple_config import SimpleConfig
from .logging import get_logger, Logger
MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
_logger = get_logger(__name__)
HEADER_SIZE = 112 # bytes
MAX_TARGET = 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
GENESIS_BITS = 0x1f00ffff
N_TARGET_TIMESPAN = 150
class MissingHeader(Exception):
pass
@ -37,88 +49,167 @@ class MissingHeader(Exception):
class InvalidHeader(Exception):
pass
def serialize_header(res):
s = int_to_hex(res.get('version'), 4) \
+ rev_hex(res.get('prev_block_hash')) \
+ rev_hex(res.get('merkle_root')) \
+ int_to_hex(int(res.get('timestamp')), 4) \
+ int_to_hex(int(res.get('bits')), 4) \
+ int_to_hex(int(res.get('nonce')), 4)
def serialize_header(header_dict: dict) -> str:
s = int_to_hex(header_dict['version'], 4) \
+ rev_hex(header_dict['prev_block_hash']) \
+ rev_hex(header_dict['merkle_root']) \
+ rev_hex(header_dict['claim_trie_root']) \
+ int_to_hex(int(header_dict['timestamp']), 4) \
+ int_to_hex(int(header_dict['bits']), 4) \
+ int_to_hex(int(header_dict['nonce']), 4)
return s
def deserialize_header(s, height):
def deserialize_header(s: bytes, height: int) -> dict:
if not s:
raise InvalidHeader('Invalid header: {}'.format(s))
if len(s) != 80:
if len(s) != HEADER_SIZE:
raise InvalidHeader('Invalid header length: {}'.format(len(s)))
hex_to_int = lambda s: int('0x' + bh2u(s[::-1]), 16)
hex_to_int = lambda s: int.from_bytes(s, byteorder='little')
h = {}
h['version'] = hex_to_int(s[0:4])
h['prev_block_hash'] = hash_encode(s[4:36])
h['merkle_root'] = hash_encode(s[36:68])
h['timestamp'] = hex_to_int(s[68:72])
h['bits'] = hex_to_int(s[72:76])
h['nonce'] = hex_to_int(s[76:80])
h['claim_trie_root'] = hash_encode(s[68:100])
h['timestamp'] = hex_to_int(s[100:104])
h['bits'] = hex_to_int(s[104:108])
h['nonce'] = hex_to_int(s[108:112])
h['block_height'] = height
return h
def hash_header(header):
def hash_header(header: dict) -> str:
if header is None:
return '0' * 64
if header.get('prev_block_hash') is None:
header['prev_block_hash'] = '00'*32
return hash_encode(Hash(bfh(serialize_header(header))))
return hash_raw_header(serialize_header(header))
def pow_hash_header(header: dict) -> str:
if header is None:
return '0' * 64
return hash_encode(PoWHash(bfh(serialize_header(header))))
def sha256(x):
return hashlib.sha256(x).digest()
def sha512(x):
return hashlib.sha512(x).digest()
def ripemd160(x):
h = hashlib.new('ripemd160')
h.update(x)
return h.digest()
def Hash(x):
return sha256(sha256(x))
def hash_raw_header(header: str) -> str:
return hash_encode(sha256d(bfh(header)))
def PoWHash(x):
r = sha512(Hash(x))
r1 = ripemd160(r[:len(r) // 2])
r2 = ripemd160(r[len(r) // 2:])
r3 = Hash(r1 + r2)
return r3
# key: blockhash hex at forkpoint
# the chain at some key is the best chain that includes the given hash
blockchains = {} # type: Dict[str, Blockchain]
blockchains_lock = threading.RLock()
blockchains = {}
def read_blockchains(config):
blockchains[0] = Blockchain(config, 0, None)
def read_blockchains(config: 'SimpleConfig'):
best_chain = Blockchain(config=config,
forkpoint=0,
parent=None,
forkpoint_hash=constants.net.GENESIS,
prev_hash=None)
blockchains[constants.net.GENESIS] = best_chain
# consistency checks
if best_chain.height() > constants.net.max_checkpoint():
header_after_cp = best_chain.read_header(constants.net.max_checkpoint()+1)
if not header_after_cp or not best_chain.can_connect(header_after_cp, check_height=False):
_logger.info("[blockchain] deleting best chain. cannot connect header after last cp to last cp.")
os.unlink(best_chain.path())
best_chain.update_size()
# forks
fdir = os.path.join(util.get_headers_dir(config), 'forks')
util.make_dir(fdir)
l = filter(lambda x: x.startswith('fork_'), os.listdir(fdir))
l = sorted(l, key = lambda x: int(x.split('_')[1]))
for filename in l:
forkpoint = int(filename.split('_')[2])
parent_id = int(filename.split('_')[1])
b = Blockchain(config, forkpoint, parent_id)
h = b.read_header(b.forkpoint)
if b.parent().can_connect(h, check_height=False):
blockchains[b.forkpoint] = b
# files are named as: fork2_{forkpoint}_{prev_hash}_{first_hash}
l = filter(lambda x: x.startswith('fork2_') and '.' not in x, os.listdir(fdir))
l = sorted(l, key=lambda x: int(x.split('_')[1])) # sort by forkpoint
def delete_chain(filename, reason):
_logger.info(f"[blockchain] deleting chain {filename}: {reason}")
os.unlink(os.path.join(fdir, filename))
def instantiate_chain(filename):
__, forkpoint, prev_hash, first_hash = filename.split('_')
forkpoint = int(forkpoint)
prev_hash = (64-len(prev_hash)) * "0" + prev_hash # left-pad with zeroes
first_hash = (64-len(first_hash)) * "0" + first_hash
# forks below the max checkpoint are not allowed
if forkpoint <= constants.net.max_checkpoint():
delete_chain(filename, "deleting fork below max checkpoint")
return
# find parent (sorting by forkpoint guarantees it's already instantiated)
for parent in blockchains.values():
if parent.check_hash(forkpoint - 1, prev_hash):
break
else:
util.print_error("cannot connect", filename)
return blockchains
delete_chain(filename, "cannot find parent for chain")
return
b = Blockchain(config=config,
forkpoint=forkpoint,
parent=parent,
forkpoint_hash=first_hash,
prev_hash=prev_hash)
# consistency checks
h = b.read_header(b.forkpoint)
if first_hash != hash_header(h):
delete_chain(filename, "incorrect first hash for chain")
return
if not b.parent.can_connect(h, check_height=False):
delete_chain(filename, "cannot connect chain to parent")
return
chain_id = b.get_id()
assert first_hash == chain_id, (first_hash, chain_id)
blockchains[chain_id] = b
def check_header(header):
if type(header) is not dict:
return False
for b in blockchains.values():
if b.check_header(header):
return b
return False
def can_connect(header):
for b in blockchains.values():
if b.can_connect(header):
return b
return False
for filename in l:
instantiate_chain(filename)
class Blockchain(util.PrintError):
def get_best_chain() -> 'Blockchain':
return blockchains[constants.net.GENESIS]
# block hash -> chain work; up to and including that block
_CHAINWORK_CACHE = {
"0000000000000000000000000000000000000000000000000000000000000000": 0, # virtual block at height -1
} # type: Dict[str, int]
class Blockchain(Logger):
"""
Manages blockchain headers and their verification
"""
def __init__(self, config, forkpoint, parent_id):
def __init__(self, config: SimpleConfig, forkpoint: int, parent: Optional['Blockchain'],
forkpoint_hash: str, prev_hash: Optional[str]):
assert isinstance(forkpoint_hash, str) and len(forkpoint_hash) == 64, forkpoint_hash
assert (prev_hash is None) or (isinstance(prev_hash, str) and len(prev_hash) == 64), prev_hash
# assert (parent is None) == (forkpoint == 0)
if 0 < forkpoint <= constants.net.max_checkpoint():
raise Exception(f"cannot fork below max checkpoint. forkpoint: {forkpoint}")
Logger.__init__(self)
self.config = config
self.catch_up = None # interface catching up
self.forkpoint = forkpoint
self.checkpoints = constants.net.CHECKPOINTS
self.parent_id = parent_id
assert parent_id != forkpoint
self.forkpoint = forkpoint # height of first header
self.parent = parent
self._forkpoint_hash = forkpoint_hash # blockhash at forkpoint. "first hash"
self._prev_hash = prev_hash # blockhash immediately before forkpoint
self.lock = threading.RLock()
with self.lock:
self.update_size()
self.update_size()
def with_lock(func):
def func_wrapper(self, *args, **kwargs):
@ -126,84 +217,160 @@ class Blockchain(util.PrintError):
return func(self, *args, **kwargs)
return func_wrapper
def parent(self):
return blockchains[self.parent_id]
@property
def checkpoints(self):
return constants.net.CHECKPOINTS
def get_max_child(self):
children = list(filter(lambda y: y.parent_id==self.forkpoint, blockchains.values()))
def get_max_child(self) -> Optional[int]:
children = self.get_direct_children()
return max([x.forkpoint for x in children]) if children else None
def get_forkpoint(self):
def get_max_forkpoint(self) -> int:
"""Returns the max height where there is a fork
related to this chain.
"""
mc = self.get_max_child()
return mc if mc is not None else self.forkpoint
def get_branch_size(self):
return self.height() - self.get_forkpoint() + 1
def get_direct_children(self) -> Sequence['Blockchain']:
with blockchains_lock:
return list(filter(lambda y: y.parent==self, blockchains.values()))
def get_name(self):
return self.get_hash(self.get_forkpoint()).lstrip('00')[0:10]
def get_parent_heights(self) -> Mapping['Blockchain', int]:
"""Returns map: (parent chain -> height of last common block)"""
with blockchains_lock:
result = {self: self.height()}
chain = self
while True:
parent = chain.parent
if parent is None: break
result[parent] = chain.forkpoint - 1
chain = parent
return result
def check_header(self, header):
def get_height_of_last_common_block_with_chain(self, other_chain: 'Blockchain') -> int:
last_common_block_height = 0
our_parents = self.get_parent_heights()
their_parents = other_chain.get_parent_heights()
for chain in our_parents:
if chain in their_parents:
h = min(our_parents[chain], their_parents[chain])
last_common_block_height = max(last_common_block_height, h)
return last_common_block_height
@with_lock
def get_branch_size(self) -> int:
return self.height() - self.get_max_forkpoint() + 1
def get_name(self) -> str:
return self.get_hash(self.get_max_forkpoint()).lstrip('0')[0:10]
def check_header(self, header: dict) -> bool:
header_hash = hash_header(header)
height = header.get('block_height')
return header_hash == self.get_hash(height)
return self.check_hash(height, header_hash)
def fork(parent, header):
def check_hash(self, height: int, header_hash: str) -> bool:
"""Returns whether the hash of the block at given height
is the given hash.
"""
assert isinstance(header_hash, str) and len(header_hash) == 64, header_hash # hex
try:
return header_hash == self.get_hash(height)
except Exception:
return False
def fork(parent, header: dict) -> 'Blockchain':
if not parent.can_connect(header, check_height=False):
raise Exception("forking header does not connect to parent chain")
forkpoint = header.get('block_height')
self = Blockchain(parent.config, forkpoint, parent.forkpoint)
self = Blockchain(config=parent.config,
forkpoint=forkpoint,
parent=parent,
forkpoint_hash=hash_header(header),
prev_hash=parent.get_hash(forkpoint-1))
self.assert_headers_file_available(parent.path())
open(self.path(), 'w+').close()
self.save_header(header)
# put into global dict. note that in some cases
# save_header might have already put it there but that's OK
chain_id = self.get_id()
with blockchains_lock:
blockchains[chain_id] = self
return self
def height(self):
@with_lock
def height(self) -> int:
return self.forkpoint + self.size() - 1
def size(self):
with self.lock:
return self._size
@with_lock
def size(self) -> int:
return self._size
def update_size(self):
@with_lock
def update_size(self) -> None:
p = self.path()
self._size = os.path.getsize(p)//80 if os.path.exists(p) else 0
self._size = os.path.getsize(p)//HEADER_SIZE if os.path.exists(p) else 0
def verify_header(self, header, prev_hash, target):
_hash = hash_header(header)
@classmethod
def verify_header(self, header: dict, prev_hash: str, target: int, bits: int, expected_header_hash: str=None) -> None:
_hash = pow_hash_header(header)
if expected_header_hash:
_hash2 = hash_header(header)
if expected_header_hash != _hash2:
raise Exception("hash mismatches with expected: {} vs {}".format(expected_header_hash, _hash2))
if prev_hash != header.get('prev_block_hash'):
raise Exception("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')))
if constants.net.TESTNET:
return
bits = self.target_to_bits(target)
if bits != header.get('bits'):
raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits')))
if int('0x' + _hash, 16) > target:
raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target))
def verify_chunk(self, index, data):
num = len(data) // 80
prev_hash = self.get_hash(index * 2016 - 1)
target = self.get_target(index-1)
#if bits != header.get('bits'):
# raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits')))
#if int('0x' + _hash, 16) > target:
# raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target))
def verify_chunk(self, index: int, data: bytes) -> None:
num = len(data) // HEADER_SIZE
start_height = index * 2016
prev_hash = self.get_hash(start_height - 1)
for i in range(num):
raw_header = data[i*80:(i+1) * 80]
height = start_height + i
header = self.read_header(height - 1)
#bits, target = self.get_target2(height - 1, header)
try:
expected_header_hash = self.get_hash(height)
except MissingHeader:
expected_header_hash = None
raw_header = data[i*HEADER_SIZE : (i+1)*HEADER_SIZE]
header = deserialize_header(raw_header, index*2016 + i)
self.verify_header(header, prev_hash, target)
self.verify_header(header, prev_hash, 0, 0, expected_header_hash)
prev_hash = hash_header(header)
@with_lock
def path(self):
d = util.get_headers_dir(self.config)
filename = 'blockchain_headers' if self.parent_id is None else os.path.join('forks', 'fork_%d_%d'%(self.parent_id, self.forkpoint))
if self.parent is None:
filename = 'blockchain_headers'
else:
assert self.forkpoint > 0, self.forkpoint
prev_hash = self._prev_hash.lstrip('0')
first_hash = self._forkpoint_hash.lstrip('0')
basename = f'fork2_{self.forkpoint}_{prev_hash}_{first_hash}'
filename = os.path.join('forks', basename)
return os.path.join(d, filename)
@with_lock
def save_chunk(self, index, chunk):
def save_chunk(self, index: int, chunk: bytes):
assert index >= 0, index
chunk_within_checkpoint_region = index < len(self.checkpoints)
# chunks in checkpoint region are the responsibility of the 'main chain'
if chunk_within_checkpoint_region and self.parent_id is not None:
main_chain = blockchains[0]
if chunk_within_checkpoint_region and self.parent is not None:
main_chain = get_best_chain()
main_chain.save_chunk(index, chunk)
return
delta_height = (index * 2016 - self.forkpoint)
delta_bytes = delta_height * 80
delta_bytes = delta_height * HEADER_SIZE
# if this chunk contains our forkpoint, only save the part after forkpoint
# (the part before is the responsibility of the parent)
if delta_bytes < 0:
@ -213,42 +380,71 @@ class Blockchain(util.PrintError):
self.write(chunk, delta_bytes, truncate)
self.swap_with_parent()
@with_lock
def swap_with_parent(self):
if self.parent_id is None:
return
parent_branch_size = self.parent().height() - self.forkpoint + 1
if parent_branch_size >= self.size():
return
self.print_error("swap", self.forkpoint, self.parent_id)
parent_id = self.parent_id
forkpoint = self.forkpoint
parent = self.parent()
def swap_with_parent(self) -> None:
with self.lock, blockchains_lock:
# do the swap; possibly multiple ones
cnt = 0
while True:
old_parent = self.parent
if not self._swap_with_parent():
break
# make sure we are making progress
cnt += 1
if cnt > len(blockchains):
raise Exception(f'swapping fork with parent too many times: {cnt}')
# we might have become the parent of some of our former siblings
for old_sibling in old_parent.get_direct_children():
if self.check_hash(old_sibling.forkpoint - 1, old_sibling._prev_hash):
old_sibling.parent = self
def _swap_with_parent(self) -> bool:
"""Check if this chain became stronger than its parent, and swap
the underlying files if so. The Blockchain instances will keep
'containing' the same headers, but their ids change and so
they will be stored in different files."""
if self.parent is None:
return False
if self.parent.get_chainwork() >= self.get_chainwork():
return False
self.logger.info(f"swapping {self.forkpoint} {self.parent.forkpoint}")
parent_branch_size = self.parent.height() - self.forkpoint + 1
forkpoint = self.forkpoint # type: Optional[int]
parent = self.parent # type: Optional[Blockchain]
child_old_id = self.get_id()
parent_old_id = parent.get_id()
# swap files
# child takes parent's name
# parent's new name will be something new (not child's old name)
self.assert_headers_file_available(self.path())
child_old_name = self.path()
with open(self.path(), 'rb') as f:
my_data = f.read()
self.assert_headers_file_available(parent.path())
assert forkpoint > parent.forkpoint, (f"forkpoint of parent chain ({parent.forkpoint}) "
f"should be at lower height than children's ({forkpoint})")
with open(parent.path(), 'rb') as f:
f.seek((forkpoint - parent.forkpoint)*80)
parent_data = f.read(parent_branch_size*80)
f.seek((forkpoint - parent.forkpoint)*HEADER_SIZE)
parent_data = f.read(parent_branch_size*HEADER_SIZE)
self.write(parent_data, 0)
parent.write(my_data, (forkpoint - parent.forkpoint)*80)
# store file path
for b in blockchains.values():
b.old_path = b.path()
parent.write(my_data, (forkpoint - parent.forkpoint)*HEADER_SIZE)
# swap parameters
self.parent_id = parent.parent_id; parent.parent_id = parent_id
self.forkpoint = parent.forkpoint; parent.forkpoint = forkpoint
self._size = parent._size; parent._size = parent_branch_size
# move files
for b in blockchains.values():
if b in [self, parent]: continue
if b.old_path != b.path():
self.print_error("renaming", b.old_path, b.path())
os.rename(b.old_path, b.path())
self.parent, parent.parent = parent.parent, self # type: Optional[Blockchain], Optional[Blockchain]
self.forkpoint, parent.forkpoint = parent.forkpoint, self.forkpoint
self._forkpoint_hash, parent._forkpoint_hash = parent._forkpoint_hash, hash_raw_header(bh2u(parent_data[:HEADER_SIZE]))
self._prev_hash, parent._prev_hash = parent._prev_hash, self._prev_hash
# parent's new name
os.replace(child_old_name, parent.path())
self.update_size()
parent.update_size()
# update pointers
blockchains[self.forkpoint] = self
blockchains[parent.forkpoint] = parent
blockchains.pop(child_old_id, None)
blockchains.pop(parent_old_id, None)
blockchains[self.get_id()] = self
blockchains[parent.get_id()] = parent
return True
def get_id(self) -> str:
return self._forkpoint_hash
def assert_headers_file_available(self, path):
if os.path.exists(path):
@ -258,64 +454,76 @@ class Blockchain(util.PrintError):
else:
raise FileNotFoundError('Cannot find headers file but headers_dir is there. Should be at {}'.format(path))
def write(self, data, offset, truncate=True):
@with_lock
def write(self, data: bytes, offset: int, truncate: bool=True) -> None:
filename = self.path()
with self.lock:
self.assert_headers_file_available(filename)
with open(filename, 'rb+') as f:
if truncate and offset != self._size*80:
f.seek(offset)
f.truncate()
self.assert_headers_file_available(filename)
with open(filename, 'rb+') as f:
if truncate and offset != self._size * HEADER_SIZE:
f.seek(offset)
f.write(data)
f.flush()
os.fsync(f.fileno())
self.update_size()
f.truncate()
f.seek(offset)
f.write(data)
f.flush()
os.fsync(f.fileno())
self.update_size()
@with_lock
def save_header(self, header):
def save_header(self, header: dict) -> None:
delta = header.get('block_height') - self.forkpoint
data = bfh(serialize_header(header))
# headers are only _appended_ to the end:
assert delta == self.size()
assert len(data) == 80
self.write(data, delta*80)
assert delta == self.size(), (delta, self.size())
assert len(data) == HEADER_SIZE
self.write(data, delta*HEADER_SIZE)
self.swap_with_parent()
def read_header(self, height):
assert self.parent_id != self.forkpoint
@with_lock
def read_header(self, height: int) -> Optional[dict]:
if height < 0:
return
if height < self.forkpoint:
return self.parent().read_header(height)
return self.parent.read_header(height)
if height > self.height():
return
delta = height - self.forkpoint
name = self.path()
self.assert_headers_file_available(name)
with open(name, 'rb') as f:
f.seek(delta * 80)
h = f.read(80)
if len(h) < 80:
f.seek(delta * HEADER_SIZE)
h = f.read(HEADER_SIZE)
if len(h) < HEADER_SIZE:
raise Exception('Expected to read a full header. This was only {} bytes'.format(len(h)))
if h == bytes([0])*80:
if h == bytes([0])*HEADER_SIZE:
return None
return deserialize_header(h, height)
def get_hash(self, height):
def header_at_tip(self) -> Optional[dict]:
"""Return latest header."""
height = self.height()
return self.read_header(height)
def get_hash(self, height: int) -> str:
def is_height_checkpoint():
within_cp_range = height <= constants.net.max_checkpoint()
at_chunk_boundary = (height+1) % 2016 == 0
return within_cp_range and at_chunk_boundary
if height == -1:
return '0000000000000000000000000000000000000000000000000000000000000000'
elif height == 0:
return constants.net.GENESIS
elif height < len(self.checkpoints) * 2016:
assert (height+1) % 2016 == 0, height
elif is_height_checkpoint():
index = height // 2016
h, t = self.checkpoints[index]
return h
else:
return hash_header(self.read_header(height))
header = self.read_header(height)
if header is None:
raise MissingHeader(height)
return hash_header(header)
def get_target(self, index):
def get_target(self, index: int) -> int:
# compute target from chunk x, used in chunk x+1
if constants.net.TESTNET:
return 0
@ -332,65 +540,165 @@ class Blockchain(util.PrintError):
bits = last.get('bits')
target = self.bits_to_target(bits)
nActualTimespan = last.get('timestamp') - first.get('timestamp')
nTargetTimespan = 14 * 24 * 60 * 60
nActualTimespan = max(nActualTimespan, nTargetTimespan // 4)
nActualTimespan = min(nActualTimespan, nTargetTimespan * 4)
new_target = min(MAX_TARGET, (target * nActualTimespan) // nTargetTimespan)
return new_target
nTargetTimespan = 150
nModulatedTimespan = nTargetTimespan - (nActualTimespan - nTargetTimespan) / 8
nMinTimespan = nTargetTimespan - (nTargetTimespan / 8)
nMaxTimespan = nTargetTimespan + (nTargetTimespan / 2)
if nModulatedTimespan < nMinTimespan:
nModulatedTimespan = nMinTimespan
elif nModulatedTimespan > nMaxTimespan:
nModulatedTimespan = nMaxTimespan
def bits_to_target(self, bits):
bnOld = ArithUint256.SetCompact(bits)
bnNew = bnOld * nModulatedTimespan
# this doesn't work if it is nTargetTimespan even though that
# is what it looks like it should be based on reading the code
# in lbry.cpp
bnNew /= nModulatedTimespan
if bnNew > MAX_TARGET:
bnNew = ArithUint256(MAX_TARGET)
return bnNew.compact(), bnNew._value
def get_target2(self, index, last, chain='main'):
if index == -1:
return GENESIS_BITS, MAX_TARGET
if index == 0:
return GENESIS_BITS, MAX_TARGET
first = self.read_header(index-1)
assert last is not None, "Last shouldn't be none"
# bits to target
bits = last.get('bits')
# print_error("Last bits: ", bits)
self.check_bits(bits)
# new target
nActualTimespan = last.get('timestamp') - first.get('timestamp')
nTargetTimespan = N_TARGET_TIMESPAN
nModulatedTimespan = nTargetTimespan - (nActualTimespan - nTargetTimespan) / 8
nMinTimespan = nTargetTimespan - (nTargetTimespan / 8)
nMaxTimespan = nTargetTimespan + (nTargetTimespan / 2)
if nModulatedTimespan < nMinTimespan:
nModulatedTimespan = nMinTimespan
elif nModulatedTimespan > nMaxTimespan:
nModulatedTimespan = nMaxTimespan
bnOld = ArithUint256.SetCompact(bits)
bnNew = bnOld * nModulatedTimespan
# this doesn't work if it is nTargetTimespan even though that
# is what it looks like it should be based on reading the code
# in lbry.cpp
bnNew /= nModulatedTimespan
if bnNew > MAX_TARGET:
bnNew = ArithUint256(MAX_TARGET)
return bnNew.compact, bnNew._value
def check_bits(self, bits):
bitsN = (bits >> 24) & 0xff
if not (bitsN >= 0x03 and bitsN <= 0x1d):
assert 0x03 <= bitsN <= 0x1f, \
"First part of bits should be in [0x03, 0x1d], but it was {}".format(hex(bitsN))
bitsBase = bits & 0xffffff
assert 0x8000 <= bitsBase <= 0x7fffff, \
"Second part of bits should be in [0x8000, 0x7fffff] but it was {}".format(bitsBase)
@classmethod
def bits_to_target(cls, bits: int) -> int:
bitsN = (bits >> 24) & 0xff
if not (0x03 <= bitsN <= 0x1f):
raise Exception("First part of bits should be in [0x03, 0x1d]")
bitsBase = bits & 0xffffff
if not (bitsBase >= 0x8000 and bitsBase <= 0x7fffff):
if not (0x8000 <= bitsBase <= 0x7fffff):
raise Exception("Second part of bits should be in [0x8000, 0x7fffff]")
return bitsBase << (8 * (bitsN-3))
def target_to_bits(self, target):
@classmethod
def target_to_bits(cls, target: int) -> int:
c = ("%064x" % target)[2:]
while c[:2] == '00' and len(c) > 6:
c = c[2:]
bitsN, bitsBase = len(c) // 2, int('0x' + c[:6], 16)
bitsN, bitsBase = len(c) // 2, int.from_bytes(bfh(c[:6]), byteorder='big')
if bitsBase >= 0x800000:
bitsN += 1
bitsBase >>= 8
return bitsN << 24 | bitsBase
def can_connect(self, header, check_height=True):
def chainwork_of_header_at_height(self, height: int) -> int:
"""work done by single header at given height"""
chunk_idx = height // 2016 - 1
target = self.get_target(chunk_idx)
work = ((2 ** 256 - target - 1) // (target + 1)) + 1
return work
@with_lock
def get_chainwork(self, height=None) -> int:
if height is None:
height = max(0, self.height())
if constants.net.TESTNET:
# On testnet/regtest, difficulty works somewhat different.
# It's out of scope to properly implement that.
return height
last_retarget = height // 2016 * 2016 - 1
cached_height = last_retarget
while _CHAINWORK_CACHE.get(self.get_hash(cached_height)) is None:
if cached_height <= -1:
break
cached_height -= 2016
assert cached_height >= -1, cached_height
running_total = _CHAINWORK_CACHE[self.get_hash(cached_height)]
while cached_height < last_retarget:
cached_height += 2016
work_in_single_header = self.chainwork_of_header_at_height(cached_height)
work_in_chunk = 2016 * work_in_single_header
running_total += work_in_chunk
_CHAINWORK_CACHE[self.get_hash(cached_height)] = running_total
cached_height += 2016
work_in_single_header = self.chainwork_of_header_at_height(cached_height)
work_in_last_partial_chunk = (height % 2016 + 1) * work_in_single_header
return running_total + work_in_last_partial_chunk
def can_connect(self, header: dict, check_height: bool=True) -> bool:
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)
print("cannot connect at height", height)
return False
if height == 0:
return hash_header(header) == constants.net.GENESIS
try:
prev_hash = self.get_hash(height - 1)
except:
return False
if prev_hash != header.get('prev_block_hash'):
return False
try:
target = self.get_target(height // 2016 - 1)
bits, target = self.get_target2(height, header)
except MissingHeader:
return False
try:
self.verify_header(header, prev_hash, target)
self.verify_header(header, prev_hash, target, bits)
except BaseException as e:
print(e)
return False
return True
def connect_chunk(self, idx, hexdata):
def connect_chunk(self, idx: int, hexdata: str) -> bool:
assert idx >= 0, idx
try:
data = bfh(hexdata)
self.verify_chunk(idx, data)
#self.print_error("validated chunk %d" % idx)
self.save_chunk(idx, data)
return True
except BaseException as e:
self.print_error('verify_chunk %d failed'%idx, str(e))
self.logger.info(f'verify_chunk idx {idx} failed: {repr(e)}')
return False
def get_checkpoints(self):
@ -402,3 +710,101 @@ class Blockchain(util.PrintError):
target = self.get_target(index)
cp.append((h, target))
return cp
def check_header(header: dict) -> Optional[Blockchain]:
if type(header) is not dict:
return None
with blockchains_lock: chains = list(blockchains.values())
for b in chains:
if b.check_header(header):
return b
return None
def can_connect(header: dict) -> Optional[Blockchain]:
with blockchains_lock: chains = list(blockchains.values())
for b in chains:
if b.can_connect(header):
return b
return None
class ArithUint256:
# https://github.com/bitcoin/bitcoin/blob/master/src/arith_uint256.cpp
__slots__ = '_value', '_compact'
def __init__(self, value: int) -> None:
self._value = value
self._compact: Optional[int] = None
@classmethod
def SetCompact(cls, nCompact):
return (ArithUint256.from_compact(nCompact))
@classmethod
def from_compact(cls, compact) -> 'ArithUint256':
size = compact >> 24
word = compact & 0x007fffff
if size <= 3:
return cls(word >> 8 * (3 - size))
else:
return cls(word << 8 * (size - 3))
@property
def value(self) -> int:
return self._value
@property
def compact(self) -> int:
if self._compact is None:
self._compact = self._calculate_compact()
return self._compact
@property
def negative(self) -> int:
return self._calculate_compact(negative=True)
@property
def bits(self) -> int:
""" Returns the position of the highest bit set plus one. """
bits = bin(self._value)[2:]
for i, d in enumerate(bits):
if d:
return (len(bits) - i) + 1
return 0
@property
def low64(self) -> int:
return self._value & 0xffffffffffffffff
def _calculate_compact(self, negative=False) -> int:
size = (self.bits + 7) // 8
if size <= 3:
compact = self.low64 << 8 * (3 - size)
else:
compact = ArithUint256(self._value >> 8 * (size - 3)).low64
# The 0x00800000 bit denotes the sign.
# Thus, if it is already set, divide the mantissa by 256 and increase the exponent.
if compact & 0x00800000:
compact >>= 8
size += 1
assert (compact & ~0x007fffff) == 0
assert size < 256
compact |= size << 24
if negative and compact & 0x007fffff:
compact |= 0x00800000
return compact
def __mul__(self, x):
# Take the mod because we are limited to an unsigned 256 bit number
return ArithUint256((self._value * x) % 2 ** 256)
def __truediv__(self, x):
return ArithUint256(int(self._value / x))
def __gt__(self, other):
return self._value > other
def __lt__(self, other):
return self._value < other

598
electrum/channel_db.py Normal file
View file

@ -0,0 +1,598 @@
# -*- coding: utf-8 -*-
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2018 The Electrum developers
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# 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 time
import random
import os
from collections import defaultdict
from typing import Sequence, List, Tuple, Optional, Dict, NamedTuple, TYPE_CHECKING, Set
import binascii
import base64
import asyncio
from .sql_db import SqlDB, sql
from . import constants
from .util import bh2u, profiler, get_headers_dir, bfh, is_ip_address, list_enabled_bits
from .logging import Logger
from .lnutil import LN_GLOBAL_FEATURES_KNOWN_SET, LNPeerAddr, format_short_channel_id, ShortChannelID
from .lnverifier import LNChannelVerifier, verify_sig_for_channel_update
if TYPE_CHECKING:
from .network import Network
class UnknownEvenFeatureBits(Exception): pass
def validate_features(features : int):
enabled_features = list_enabled_bits(features)
for fbit in enabled_features:
if (1 << fbit) not in LN_GLOBAL_FEATURES_KNOWN_SET and fbit % 2 == 0:
raise UnknownEvenFeatureBits()
FLAG_DISABLE = 1 << 1
FLAG_DIRECTION = 1 << 0
class ChannelInfo(NamedTuple):
short_channel_id: ShortChannelID
node1_id: bytes
node2_id: bytes
capacity_sat: Optional[int]
@staticmethod
def from_msg(payload):
features = int.from_bytes(payload['features'], 'big')
validate_features(features)
channel_id = payload['short_channel_id']
node_id_1 = payload['node_id_1']
node_id_2 = payload['node_id_2']
assert list(sorted([node_id_1, node_id_2])) == [node_id_1, node_id_2]
capacity_sat = None
return ChannelInfo(
short_channel_id = ShortChannelID.normalize(channel_id),
node1_id = node_id_1,
node2_id = node_id_2,
capacity_sat = capacity_sat
)
class Policy(NamedTuple):
key: bytes
cltv_expiry_delta: int
htlc_minimum_msat: int
htlc_maximum_msat: Optional[int]
fee_base_msat: int
fee_proportional_millionths: int
channel_flags: int
message_flags: int
timestamp: int
@staticmethod
def from_msg(payload):
return Policy(
key = payload['short_channel_id'] + payload['start_node'],
cltv_expiry_delta = int.from_bytes(payload['cltv_expiry_delta'], "big"),
htlc_minimum_msat = int.from_bytes(payload['htlc_minimum_msat'], "big"),
htlc_maximum_msat = int.from_bytes(payload['htlc_maximum_msat'], "big") if 'htlc_maximum_msat' in payload else None,
fee_base_msat = int.from_bytes(payload['fee_base_msat'], "big"),
fee_proportional_millionths = int.from_bytes(payload['fee_proportional_millionths'], "big"),
message_flags = int.from_bytes(payload['message_flags'], "big"),
channel_flags = int.from_bytes(payload['channel_flags'], "big"),
timestamp = int.from_bytes(payload['timestamp'], "big")
)
def is_disabled(self):
return self.channel_flags & FLAG_DISABLE
@property
def short_channel_id(self) -> ShortChannelID:
return ShortChannelID.normalize(self.key[0:8])
@property
def start_node(self):
return self.key[8:]
class NodeInfo(NamedTuple):
node_id: bytes
features: int
timestamp: int
alias: str
@staticmethod
def from_msg(payload):
node_id = payload['node_id']
features = int.from_bytes(payload['features'], "big")
validate_features(features)
addresses = NodeInfo.parse_addresses_field(payload['addresses'])
alias = payload['alias'].rstrip(b'\x00')
timestamp = int.from_bytes(payload['timestamp'], "big")
return NodeInfo(node_id=node_id, features=features, timestamp=timestamp, alias=alias), [
Address(host=host, port=port, node_id=node_id, last_connected_date=None) for host, port in addresses]
@staticmethod
def parse_addresses_field(addresses_field):
buf = addresses_field
def read(n):
nonlocal buf
data, buf = buf[0:n], buf[n:]
return data
addresses = []
while buf:
atype = ord(read(1))
if atype == 0:
pass
elif atype == 1: # IPv4
ipv4_addr = '.'.join(map(lambda x: '%d' % x, read(4)))
port = int.from_bytes(read(2), 'big')
if is_ip_address(ipv4_addr) and port != 0:
addresses.append((ipv4_addr, port))
elif atype == 2: # IPv6
ipv6_addr = b':'.join([binascii.hexlify(read(2)) for i in range(8)])
ipv6_addr = ipv6_addr.decode('ascii')
port = int.from_bytes(read(2), 'big')
if is_ip_address(ipv6_addr) and port != 0:
addresses.append((ipv6_addr, port))
elif atype == 3: # onion v2
host = base64.b32encode(read(10)) + b'.onion'
host = host.decode('ascii').lower()
port = int.from_bytes(read(2), 'big')
addresses.append((host, port))
elif atype == 4: # onion v3
host = base64.b32encode(read(35)) + b'.onion'
host = host.decode('ascii').lower()
port = int.from_bytes(read(2), 'big')
addresses.append((host, port))
else:
# unknown address type
# we don't know how long it is -> have to escape
# if there are other addresses we could have parsed later, they are lost.
break
return addresses
class Address(NamedTuple):
node_id: bytes
host: str
port: int
last_connected_date: Optional[int]
class CategorizedChannelUpdates(NamedTuple):
orphaned: List # no channel announcement for channel update
expired: List # update older than two weeks
deprecated: List # update older than database entry
good: List # good updates
to_delete: List # database entries to delete
# TODO It would make more sense to store the raw gossip messages in the db.
# That is pretty much a pre-requisite of actively participating in gossip.
create_channel_info = """
CREATE TABLE IF NOT EXISTS channel_info (
short_channel_id VARCHAR(64),
node1_id VARCHAR(66),
node2_id VARCHAR(66),
capacity_sat INTEGER,
PRIMARY KEY(short_channel_id)
)"""
create_policy = """
CREATE TABLE IF NOT EXISTS policy (
key VARCHAR(66),
cltv_expiry_delta INTEGER NOT NULL,
htlc_minimum_msat INTEGER NOT NULL,
htlc_maximum_msat INTEGER,
fee_base_msat INTEGER NOT NULL,
fee_proportional_millionths INTEGER NOT NULL,
channel_flags INTEGER NOT NULL,
message_flags INTEGER NOT NULL,
timestamp INTEGER NOT NULL,
PRIMARY KEY(key)
)"""
create_address = """
CREATE TABLE IF NOT EXISTS address (
node_id VARCHAR(66),
host STRING(256),
port INTEGER NOT NULL,
timestamp INTEGER,
PRIMARY KEY(node_id, host, port)
)"""
create_node_info = """
CREATE TABLE IF NOT EXISTS node_info (
node_id VARCHAR(66),
features INTEGER NOT NULL,
timestamp INTEGER NOT NULL,
alias STRING(64),
PRIMARY KEY(node_id)
)"""
class ChannelDB(SqlDB):
NUM_MAX_RECENT_PEERS = 20
def __init__(self, network: 'Network'):
path = os.path.join(get_headers_dir(network.config), 'channel_db')
super().__init__(network, path, commit_interval=100)
self.num_nodes = 0
self.num_channels = 0
self._channel_updates_for_private_channels = {} # type: Dict[Tuple[bytes, bytes], dict]
self.ca_verifier = LNChannelVerifier(network, self)
# initialized in load_data
self._channels = {} # type: Dict[bytes, ChannelInfo]
self._policies = {}
self._nodes = {}
# node_id -> (host, port, ts)
self._addresses = defaultdict(set) # type: Dict[bytes, Set[Tuple[str, int, int]]]
self._channels_for_node = defaultdict(set)
self.data_loaded = asyncio.Event()
self.network = network # only for callback
def update_counts(self):
self.num_nodes = len(self._nodes)
self.num_channels = len(self._channels)
self.num_policies = len(self._policies)
self.network.trigger_callback('channel_db', self.num_nodes, self.num_channels, self.num_policies)
def get_channel_ids(self):
return set(self._channels.keys())
def add_recent_peer(self, peer: LNPeerAddr):
now = int(time.time())
node_id = peer.pubkey
self._addresses[node_id].add((peer.host, peer.port, now))
self.save_node_address(node_id, peer, now)
def get_200_randomly_sorted_nodes_not_in(self, node_ids):
unshuffled = set(self._nodes.keys()) - node_ids
return random.sample(unshuffled, min(200, len(unshuffled)))
def get_last_good_address(self, node_id) -> Optional[LNPeerAddr]:
r = self._addresses.get(node_id)
if not r:
return None
addr = sorted(list(r), key=lambda x: x[2])[0]
host, port, timestamp = addr
try:
return LNPeerAddr(host, port, node_id)
except ValueError:
return None
def get_recent_peers(self):
assert self.data_loaded.is_set(), "channelDB load_data did not finish yet!"
# FIXME this does not reliably return "recent" peers...
# Also, the list() cast over the whole dict (thousands of elements),
# is really inefficient.
r = [self.get_last_good_address(node_id)
for node_id in list(self._addresses.keys())[-self.NUM_MAX_RECENT_PEERS:]]
return list(reversed(r))
# note: currently channel announcements are trusted by default (trusted=True);
# they are not verified. Verifying them would make the gossip sync
# even slower; especially as servers will start throttling us.
# It would probably put significant strain on servers if all clients
# verified the complete gossip.
def add_channel_announcement(self, msg_payloads, *, trusted=True):
if type(msg_payloads) is dict:
msg_payloads = [msg_payloads]
added = 0
for msg in msg_payloads:
short_channel_id = ShortChannelID(msg['short_channel_id'])
if short_channel_id in self._channels:
continue
if constants.net.rev_genesis_bytes() != msg['chain_hash']:
self.logger.info("ChanAnn has unexpected chain_hash {}".format(bh2u(msg['chain_hash'])))
continue
try:
channel_info = ChannelInfo.from_msg(msg)
except UnknownEvenFeatureBits:
self.logger.info("unknown feature bits")
continue
if trusted:
added += 1
self.add_verified_channel_info(msg)
else:
added += self.ca_verifier.add_new_channel_info(short_channel_id, msg)
self.update_counts()
self.logger.debug('add_channel_announcement: %d/%d'%(added, len(msg_payloads)))
def add_verified_channel_info(self, msg: dict, *, capacity_sat: int = None) -> None:
try:
channel_info = ChannelInfo.from_msg(msg)
except UnknownEvenFeatureBits:
return
channel_info = channel_info._replace(capacity_sat=capacity_sat)
self._channels[channel_info.short_channel_id] = channel_info
self._channels_for_node[channel_info.node1_id].add(channel_info.short_channel_id)
self._channels_for_node[channel_info.node2_id].add(channel_info.short_channel_id)
self.save_channel(channel_info)
def print_change(self, old_policy: Policy, new_policy: Policy):
# print what changed between policies
if old_policy.cltv_expiry_delta != new_policy.cltv_expiry_delta:
self.logger.info(f'cltv_expiry_delta: {old_policy.cltv_expiry_delta} -> {new_policy.cltv_expiry_delta}')
if old_policy.htlc_minimum_msat != new_policy.htlc_minimum_msat:
self.logger.info(f'htlc_minimum_msat: {old_policy.htlc_minimum_msat} -> {new_policy.htlc_minimum_msat}')
if old_policy.htlc_maximum_msat != new_policy.htlc_maximum_msat:
self.logger.info(f'htlc_maximum_msat: {old_policy.htlc_maximum_msat} -> {new_policy.htlc_maximum_msat}')
if old_policy.fee_base_msat != new_policy.fee_base_msat:
self.logger.info(f'fee_base_msat: {old_policy.fee_base_msat} -> {new_policy.fee_base_msat}')
if old_policy.fee_proportional_millionths != new_policy.fee_proportional_millionths:
self.logger.info(f'fee_proportional_millionths: {old_policy.fee_proportional_millionths} -> {new_policy.fee_proportional_millionths}')
if old_policy.channel_flags != new_policy.channel_flags:
self.logger.info(f'channel_flags: {old_policy.channel_flags} -> {new_policy.channel_flags}')
if old_policy.message_flags != new_policy.message_flags:
self.logger.info(f'message_flags: {old_policy.message_flags} -> {new_policy.message_flags}')
def add_channel_updates(self, payloads, max_age=None, verify=True) -> CategorizedChannelUpdates:
orphaned = []
expired = []
deprecated = []
good = []
to_delete = []
# filter orphaned and expired first
known = []
now = int(time.time())
for payload in payloads:
short_channel_id = ShortChannelID(payload['short_channel_id'])
timestamp = int.from_bytes(payload['timestamp'], "big")
if max_age and now - timestamp > max_age:
expired.append(payload)
continue
channel_info = self._channels.get(short_channel_id)
if not channel_info:
orphaned.append(payload)
continue
flags = int.from_bytes(payload['channel_flags'], 'big')
direction = flags & FLAG_DIRECTION
start_node = channel_info.node1_id if direction == 0 else channel_info.node2_id
payload['start_node'] = start_node
known.append(payload)
# compare updates to existing database entries
for payload in known:
timestamp = int.from_bytes(payload['timestamp'], "big")
start_node = payload['start_node']
short_channel_id = ShortChannelID(payload['short_channel_id'])
key = (start_node, short_channel_id)
old_policy = self._policies.get(key)
if old_policy and timestamp <= old_policy.timestamp:
deprecated.append(payload)
continue
good.append(payload)
if verify:
self.verify_channel_update(payload)
policy = Policy.from_msg(payload)
self._policies[key] = policy
self.save_policy(policy)
#
self.update_counts()
return CategorizedChannelUpdates(
orphaned=orphaned,
expired=expired,
deprecated=deprecated,
good=good,
to_delete=to_delete,
)
def add_channel_update(self, payload):
# called from add_own_channel
# the update may be categorized as deprecated because of caching
categorized_chan_upds = self.add_channel_updates([payload], verify=False)
def create_database(self):
c = self.conn.cursor()
c.execute(create_node_info)
c.execute(create_address)
c.execute(create_policy)
c.execute(create_channel_info)
self.conn.commit()
@sql
def save_policy(self, policy):
c = self.conn.cursor()
c.execute("""REPLACE INTO policy (key, cltv_expiry_delta, htlc_minimum_msat, htlc_maximum_msat, fee_base_msat, fee_proportional_millionths, channel_flags, message_flags, timestamp) VALUES (?,?,?,?,?,?,?,?,?)""", list(policy))
@sql
def delete_policy(self, node_id, short_channel_id):
key = short_channel_id + node_id
c = self.conn.cursor()
c.execute("""DELETE FROM policy WHERE key=?""", (key,))
@sql
def save_channel(self, channel_info):
c = self.conn.cursor()
c.execute("REPLACE INTO channel_info (short_channel_id, node1_id, node2_id, capacity_sat) VALUES (?,?,?,?)", list(channel_info))
@sql
def delete_channel(self, short_channel_id):
c = self.conn.cursor()
c.execute("""DELETE FROM channel_info WHERE short_channel_id=?""", (short_channel_id,))
@sql
def save_node(self, node_info):
c = self.conn.cursor()
c.execute("REPLACE INTO node_info (node_id, features, timestamp, alias) VALUES (?,?,?,?)", list(node_info))
@sql
def save_node_address(self, node_id, peer, now):
c = self.conn.cursor()
c.execute("REPLACE INTO address (node_id, host, port, timestamp) VALUES (?,?,?,?)", (node_id, peer.host, peer.port, now))
@sql
def save_node_addresses(self, node_id, node_addresses):
c = self.conn.cursor()
for addr in node_addresses:
c.execute("SELECT * FROM address WHERE node_id=? AND host=? AND port=?", (addr.node_id, addr.host, addr.port))
r = c.fetchall()
if r == []:
c.execute("INSERT INTO address (node_id, host, port, timestamp) VALUES (?,?,?,?)", (addr.node_id, addr.host, addr.port, 0))
def verify_channel_update(self, payload):
short_channel_id = payload['short_channel_id']
short_channel_id = ShortChannelID(short_channel_id)
if constants.net.rev_genesis_bytes() != payload['chain_hash']:
raise Exception('wrong chain hash')
if not verify_sig_for_channel_update(payload, payload['start_node']):
raise Exception(f'failed verifying channel update for {short_channel_id}')
def add_node_announcement(self, msg_payloads):
if type(msg_payloads) is dict:
msg_payloads = [msg_payloads]
old_addr = None
new_nodes = {}
for msg_payload in msg_payloads:
try:
node_info, node_addresses = NodeInfo.from_msg(msg_payload)
except UnknownEvenFeatureBits:
continue
node_id = node_info.node_id
# Ignore node if it has no associated channel (DoS protection)
if node_id not in self._channels_for_node:
#self.logger.info('ignoring orphan node_announcement')
continue
node = self._nodes.get(node_id)
if node and node.timestamp >= node_info.timestamp:
continue
node = new_nodes.get(node_id)
if node and node.timestamp >= node_info.timestamp:
continue
# save
self._nodes[node_id] = node_info
self.save_node(node_info)
for addr in node_addresses:
self._addresses[node_id].add((addr.host, addr.port, 0))
self.save_node_addresses(node_id, node_addresses)
self.logger.debug("on_node_announcement: %d/%d"%(len(new_nodes), len(msg_payloads)))
self.update_counts()
def get_routing_policy_for_channel(self, start_node_id: bytes,
short_channel_id: bytes) -> Optional[Policy]:
if not start_node_id or not short_channel_id: return None
channel_info = self.get_channel_info(short_channel_id)
if channel_info is not None:
return self.get_policy_for_node(short_channel_id, start_node_id)
msg = self._channel_updates_for_private_channels.get((start_node_id, short_channel_id))
if not msg:
return None
return Policy.from_msg(msg) # won't actually be written to DB
def get_old_policies(self, delta):
now = int(time.time())
return list(k for k, v in list(self._policies.items()) if v.timestamp <= now - delta)
def prune_old_policies(self, delta):
l = self.get_old_policies(delta)
if l:
for k in l:
self._policies.pop(k)
self.delete_policy(*k)
self.update_counts()
self.logger.info(f'Deleting {len(l)} old policies')
def get_orphaned_channels(self):
ids = set(x[1] for x in self._policies.keys())
return list(x for x in self._channels.keys() if x not in ids)
def prune_orphaned_channels(self):
l = self.get_orphaned_channels()
if l:
for short_channel_id in l:
self.remove_channel(short_channel_id)
self.update_counts()
self.logger.info(f'Deleting {len(l)} orphaned channels')
def add_channel_update_for_private_channel(self, msg_payload: dict, start_node_id: bytes):
if not verify_sig_for_channel_update(msg_payload, start_node_id):
return # ignore
short_channel_id = ShortChannelID(msg_payload['short_channel_id'])
msg_payload['start_node'] = start_node_id
self._channel_updates_for_private_channels[(start_node_id, short_channel_id)] = msg_payload
def remove_channel(self, short_channel_id: ShortChannelID):
channel_info = self._channels.pop(short_channel_id, None)
if channel_info:
self._channels_for_node[channel_info.node1_id].remove(channel_info.short_channel_id)
self._channels_for_node[channel_info.node2_id].remove(channel_info.short_channel_id)
# delete from database
self.delete_channel(short_channel_id)
def get_node_addresses(self, node_id):
return self._addresses.get(node_id)
@sql
@profiler
def load_data(self):
c = self.conn.cursor()
c.execute("""SELECT * FROM address""")
for x in c:
node_id, host, port, timestamp = x
self._addresses[node_id].add((str(host), int(port), int(timestamp or 0)))
c.execute("""SELECT * FROM channel_info""")
for x in c:
x = (ShortChannelID.normalize(x[0]), *x[1:])
ci = ChannelInfo(*x)
self._channels[ci.short_channel_id] = ci
c.execute("""SELECT * FROM node_info""")
for x in c:
ni = NodeInfo(*x)
self._nodes[ni.node_id] = ni
c.execute("""SELECT * FROM policy""")
for x in c:
p = Policy(*x)
self._policies[(p.start_node, p.short_channel_id)] = p
for channel_info in self._channels.values():
self._channels_for_node[channel_info.node1_id].add(channel_info.short_channel_id)
self._channels_for_node[channel_info.node2_id].add(channel_info.short_channel_id)
self.logger.info(f'load data {len(self._channels)} {len(self._policies)} {len(self._channels_for_node)}')
self.update_counts()
self.count_incomplete_channels()
self.data_loaded.set()
def count_incomplete_channels(self):
out = set()
for short_channel_id, ci in self._channels.items():
p1 = self.get_policy_for_node(short_channel_id, ci.node1_id)
p2 = self.get_policy_for_node(short_channel_id, ci.node2_id)
if p1 is None or p2 is not None:
out.add(short_channel_id)
self.logger.info(f'semi-orphaned: {len(out)}')
def get_policy_for_node(self, short_channel_id: bytes, node_id: bytes) -> Optional['Policy']:
return self._policies.get((node_id, short_channel_id))
def get_channel_info(self, channel_id: bytes) -> ChannelInfo:
return self._channels.get(channel_id)
def get_channels_for_node(self, node_id) -> Set[bytes]:
"""Returns the set of channels that have node_id as one of the endpoints."""
return self._channels_for_node.get(node_id) or set()

File diff suppressed because it is too large Load diff

View file

@ -2658,5 +2658,465 @@
[
"000000000000287fa294ea557835d8c98bfe94c4d8b18d5b10f1b62d68957113",
0
],
[
"000000000001d842f5a0dff13820ba1e151fd8c886e28e648a0be41f3a3f1cb3",
0
],
[
"000000000000906854973b2ec51409f0b78b25b074eef3f0dbb31e1060c07c3d",
0
],
[
"00000000000009e694e22b97a4757bffef74f0ccd832398b3e815171636e3a85",
0
],
[
"0000000000000594b95678610bd47671b1142eb575d1c1d4a0073f69a71a3c65",
0
],
[
"00000000000002ac6d5c058c9932f350aeef84f6e334f4e01b40be4db537f8c2",
0
],
[
"00000000000000c9a91d8277c58eab3bfda59d3068142dd54216129e5597ccbd",
0
],
[
"0000000000000051bff2f64c9078fb346d6a2a209ba5c3ffa0048c6b7027e47f",
0
],
[
"000000000000df3c366a105ce9ed82a4917c9e19f0736493894feaba2542c7cd",
0
],
[
"0000000000007c8006959f91675b2dbf6264a1172279c826ae7f561b70e88b12",
0
],
[
"0000000000015ab3720de7669e8731c84c392aae3509d937b8d883c304e0ca86",
0
],
[
"0000000000016d7156ee43da389020fb5d30f05e11498c54f7e324561d6a6039",
0
],
[
"0000000000009c9592f83d63fe39839080ced253e1d71c52bce576f823b7722a",
0
],
[
"00000000003dee6b438ddf51b831fbedb9d2ee91644aaf5866e3a85c740b3a99",
0
],
[
"00000000000155f5594d8a3ade605d1504ee9a6f6389f1c4516e974698ebb9e4",
0
],
[
"000000000001e21adfc306bf4aa2ad90e3c2aa4a43263d1bbdc70bf9f1593416",
0
],
[
"0000000000008218e84ba7d9850a5c12b77ec5d1348e7cbdfdcb86f8fe929682",
0
],
[
"00000000000054fb41b42b30fff1738104c3edca6dab47c75e4d3565bc4b9e34",
0
],
[
"0000000000002763b825c315ba35959dcc1bd8114627949ede769ac2eece8248",
0
],
[
"00000000000007437044da0baed38a28e2991c6a527f495e91739a8d9c35acbb",
0
],
[
"000000000000032d74ad8eb0a0be6b39b8e095bd9ca8537da93aae15087aafaf",
0
],
[
"000000000000006d4025181f5b54cca6d730cc26313817c6529ba9ed62cc83b3",
0
],
[
"000000001c3ad81ffea0b74d356b6886fd3381506b7c568f96c88a78815ede09",
0
],
[
"000000000140739d224af1254712d8c4e9fb9082b381baf22c628e459157ce49",
0
],
[
"000000000306491c835f1a03c8d1e17645435296d3593dacba8ab1a7d9341d38",
0
],
[
"000000000002b383618b228eb8e4cfcf269ba647b91ac6d60ddd070295709ad1",
0
],
[
"000000000000c90fc724a76407b4405032474fc8d1649817f7ad238b96856c6a",
0
],
[
"0000000000002d5a62b323a5f213152dd84e2f415a3c6c28043c0ccaaddb3229",
0
],
[
"0000000000008c086a21457ba523b682356c760538000a480650cd667a29647a",
0
],
[
"00000000000007c586d36266aa83d8cc702aa29f31e3cc01c6eeac5a0f5f9887",
0
],
[
"0000000000013bf175e35603f24758bf8d40b1f5c266e707e3ba4de6fae43a7f",
0
],
[
"00000000000096841c486983a4333afb2525549abe57e7263723b9782e9cfef1",
0
],
[
"00000000000012dfd7c4e1f40a1dd4833da2d010a33fc65c053871884146c941",
0
],
[
"0000000000000b47eb6bc8c6562b5a30cefcf81623a37f6f61cc7497a530eb33",
0
],
[
"0000000000000021ca4558aeb796f900e581c029d751f89e1a69ae9ba9f6ebb3",
0
],
[
"00000000000000a5bf9029aebb1956200304ffee31bc09f1323ae412d81fa2b2",
0
],
[
"0000000000000046f38ada53de3346d8191f69c8f3c0ba9e1950f5bf291989c4",
0
],
[
"00000000658b5a572ea407ac49a1dccf85d67d0adfc5f613b17fa3fff1d99d51",
0
],
[
"000000005d6be9ae758c520b0061feee99cd0a231f982cc074e4d0ced1f96952",
0
],
[
"0000000001aa4671747707d329a94c398c04aaf2268e551ac5d6a7f29ffd4acd",
0
],
[
"0000000004b441b97963463faca7a933469fabfa3e7b243621159e445e5c192a",
0
],
[
"0000000002ce8842113bc875330fa77f3b984a90806a5ec0bb73321fef3c76c6",
0
],
[
"0000000000019761bf9a1c6f679b880e9fb45b3f6dc1accdbdcfce01368c9377",
0
],
[
"0000000000008a069efd1a7923557be3d9584d307b2555dc0a56d66e74e083e1",
0
],
[
"000000000001c14cec52030659ef7d45318ca574f1633ef69e9c8c9bd7e45289",
0
],
[
"0000000000009cfccb8a27f66f1d9ff40c9d47449f78d82fee2465daca582ab7",
0
],
[
"0000000000007f30cfae7fbb8ff965f70d500b98be202b1dd57ea418500c922d",
0
],
[
"0000000000002cbd2dbab4352fe4979e0d5afc47f21ef575ae0e3bb620a5478a",
0
],
[
"000000000000017a872a5c7a15b3cb6e1ecf9e009759848b85c19ca6e7bd16d2",
0
],
[
"00000000000001ade79216032b49854c966a1061fd3f8c6c56a0d38d0024629e",
0
],
[
"0000000000000090b8dfe4dde9f9f8d675642db97b3649bd147f60d1fc64cd76",
0
],
[
"0000000000000109ed5f0d6fc387ad1bc45db1e522f76adce131067fc64440ec",
0
],
[
"000000000000003105650f0b8e7b4cb466cd32ff5608f59906879aff5cad64a7",
0
],
[
"0000000000000113d4262419a8aa3a4fe928c0ea81893a2d2ffee5258b2085d8",
0
],
[
"00000000000000f15b8a196b1c3568d14b5a7856da2fef7a7f5548266582ff28",
0
],
[
"0000000000000034fb9e91c8b5f7147bd1a4f089d19a266d183df6f8497d1dff",
0
],
[
"000000000000005e51ad800c9e8ab11abb4b945f5ea86b120fa140c8af6301e0",
0
],
[
"00000000000000e903f2002fd08a732fd5380ea1f2dac26bb84d57e247af8ac2",
0
],
[
"000000000015115dac432884296259f508dae6b6f5f15cef17939840f5a295c3",
0
],
[
"000000000029913c80e5f49d413603d91f5fd67b76a7e187f76c077973be6f8a",
0
],
[
"00000000002e864e470ccec1fec0ca5f2053c9a9b8978a40f3482b4d30f683a9",
0
],
[
"00000000001ccf523df85df9abdb7c5bbad5c5fcbd12a4a8eb4700de7291f03b",
0
],
[
"00000000002aa81027df021e3ccde48dff6e7f01a4aba27727308f2ce17f2f1a",
0
],
[
"000000000015a577d71d65bde7e8f5359458336218dc024584f7510b38dc1259",
0
],
[
"00000000003aef1877bcc6817cac497aeb95af3336ba2908e8194f96a2c9fc29",
0
],
[
"00000000000ccd42d542ddca68300ec2a9db2564327108234641535fd51aa7f3",
0
],
[
"000000000000a2652b2e523866f3c4d5c07dc1c204d439b627f2ab2848bfa139",
0
],
[
"0000000000002c065179a394d8da754c2e2db5fed21def076c16c24a902b448d",
0
],
[
"000000000000175a878558186e53b559e494ce7e9f687bf0462d63169bfcce03",
0
],
[
"00000000000007524a71cc81cadbd1ddf9d38848fa8081ad2a72eade4b70d1c1",
0
],
[
"0000000000000159321405d24d99131df6bf69ffeca30c4a949926807c4175ad",
0
],
[
"000000000000016c271ae44c8dca3567b332ec178a243be2a7dfa7a0aef270c3",
0
],
[
"00000000000000a7d62de601cdf73e25c49c1c99717c94ffd574fc657fd42fa8",
0
],
[
"0000000000000052d492170de491c1355d640bae48f4d954009e963f6f9a18c3",
0
],
[
"000000006f5707f2f707b9ddcce2739723e911210b131da4ca1efdff581212ad",
0
],
[
"00000000021be68dc9c33db0c2222e97cd2c06fc43834e8f5292133c45c2abb4",
0
],
[
"00000000019ca3eaf7c39f70a7a1a736f74021abf885bebc5d91aa946496bac5",
0
],
[
"00000000006e4752fbe2627ebb2d0118f7437908a8219f973324727195335209",
0
],
[
"00000000038471612a0955307f367071888985707ec0e42c82f9145caed8fea1",
0
],
[
"000000000004604d2d7d921b21d86f2ade82ded3af33877ec59d47072023d763",
0
],
[
"000000000034a3e45665a8dcbb94e7a218375a5199b3f3ca2cc7b5fe151bb198",
0
],
[
"0000000000043fb2c2ff5db60c6d2d35a633746e8585e04a096a9b55a4787fe6",
0
],
[
"0000000000020d4d8735b66134c1fcdd1d3f3d135b9ff3f70968ef96c227fb75",
0
],
[
"0000000000004f3f4dc1fa11a6ad9bd320413b042eb599c4599a14d341f6825f",
0
],
[
"0000000000001e0a495d23acf46a44f8b569ada39ac70730da5e9109871b77e9",
0
],
[
"00000000000002257a08acca858f239fabb258a7cc1665fc464f6e18e9372d32",
0
],
[
"00000000000002845d416fbfa05a5d40ba5ba5418a64f06443042a53cf1fd608",
0
],
[
"00000000000000fee91a2ae8b8d1bb9a687c9b28b0185723c8ff6ffdac2e9ce4",
0
],
[
"00000000000001d6874b4d88e387098c0b7100ff674d99781fc7045a78216a15",
0
],
[
"00000000000144a03e701c199673d72fc63766bcf0cdaf565f4c941c7ef72971",
0
],
[
"000000009b6cc4d8aee22cca6880e4d7bb30bff2851034ad437d63d3a7278de7",
0
],
[
"0000000023e998d64618475e31b4aee9d83d2bc32cb6d062aa97c0b4651fed08",
0
],
[
"0000000000036f4bf6b42a7776a97872fa24362064c5bc4bc946acb70ab6fbf4",
0
],
[
"0000000001e2252455ffd0cf0b4109ace996a0d2a03999f5cc5c5e08fb6130ac",
0
],
[
"0000000000002713db42d53f0c2d86c904f4e0338652acc1cbda953c530a15bb",
0
],
[
"000000000001b075f9ccc604a50326732f5d42373c4a831978be0e2d830cac75",
0
],
[
"0000000000000bfa7d93c6b36298b933b1a652c95ee9f0de4151e007f3180391",
0
],
[
"000000000002c60a0af1cfeb9c26c60970b354897fd0a94c8e5c414d0767b06b",
0
],
[
"0000000000001f2d9462507a9408859fb0b5f97013d6b4577337b0382340c5aa",
0
],
[
"0000000000000b7428e0d3c6c7fd2df623a74125db4989b1c61c78eeed1bcde5",
0
],
[
"00000000000002e8b4f1fa041a37515c1b76d59994792f1c772c9a4993c194dc",
0
],
[
"0000000000094e70c0cf5185b480542a1faa8392a3f2f7f583d91e033856d7ce",
0
],
[
"000000005b036d8c18ed5d1219e4137bd71438c9b1ba7ff4d10a626e9a7bcc98",
0
],
[
"0000000008745d4a943e958f5cb5084646c0fe1cae57eeab666c3ad0d4ff1dec",
0
],
[
"00000000000f8c5b3455e540d074b5c71709e37f8950975953798d27bdc701fa",
0
],
[
"0000000000050885884f7ac233bb174cf7b33c037f81907f7766afe9d0ad9091",
0
],
[
"000000000002d7cd1043ccd0581a47d6fdf82a7cf1646b61495f917a48ebeb5c",
0
],
[
"000000000003a2b3e3d7ef47829db1672bfd79e49f32ef3a04ec7c4df355392b",
0
],
[
"0000000000032a6c7e5bc3878c1815bc6759594a4736638fdacaa5642be3e649",
0
],
[
"000000000001386a3904f0ba4f25dc7ace09b67a6fe8977e7aecc55813fa9ac5",
0
],
[
"0000000000003fe030a2231da87076679c1d38d323bf56b45ceb49a5128fb4b1",
0
],
[
"000000000000147cd3b6195c6a727cd4fe6b3a879d7934e52bf29020ed9c6fcc",
0
],
[
"00000000000003ed5a0a7176f3f1b3ed26510045af2860e5b6313b358774fbad",
0
],
[
"00000000000000c2952ac8a580895ac13799a9c29badb6599bc4a86c1fc83b6e",
0
],
[
"0000000000000056f49d6f7b8243eecf6597946158efe044b07fd091398e380d",
0
],
[
"000000000000006b039683c36b18ec712346521edce4dc5b81cdaf6475d89bd7",
0
]
]

View file

@ -22,12 +22,15 @@
# 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.
from collections import defaultdict, namedtuple
from collections import defaultdict
from math import floor, log10
from typing import NamedTuple, List, Callable, Sequence, Union, Dict, Tuple
from decimal import Decimal
from .bitcoin import sha256, COIN, TYPE_ADDRESS, is_address
from .transaction import Transaction, TxOutput
from .util import NotEnoughFunds, PrintError
from .bitcoin import sha256, COIN, is_address
from .transaction import Transaction, TxOutput, PartialTransaction, PartialTxInput, PartialTxOutput
from .util import NotEnoughFunds
from .logging import Logger
# A simple deterministic PRNG. Used to deterministically shuffle a
@ -68,54 +71,90 @@ class PRNG:
x[i], x[j] = x[j], x[i]
Bucket = namedtuple('Bucket',
['desc',
'weight', # as in BIP-141
'value', # in satoshis
'coins', # UTXOs
'min_height', # min block height where a coin was confirmed
'witness']) # whether any coin uses segwit
class Bucket(NamedTuple):
desc: str
weight: int # as in BIP-141
value: int # in satoshis
effective_value: int # estimate of value left after subtracting fees. in satoshis
coins: List[PartialTxInput] # UTXOs
min_height: int # min block height where a coin was confirmed
witness: bool # whether any coin uses segwit
def strip_unneeded(bkts, sufficient_funds):
class ScoredCandidate(NamedTuple):
penalty: float
tx: PartialTransaction
buckets: List[Bucket]
def strip_unneeded(bkts: List[Bucket], sufficient_funds) -> List[Bucket]:
'''Remove buckets that are unnecessary in achieving the spend amount'''
bkts = sorted(bkts, key = lambda bkt: bkt.value)
if sufficient_funds([], bucket_value_sum=0):
# none of the buckets are needed
return []
bkts = sorted(bkts, key=lambda bkt: bkt.value, reverse=True)
bucket_value_sum = 0
for i in range(len(bkts)):
if not sufficient_funds(bkts[i + 1:]):
return bkts[i:]
# Shouldn't get here
return bkts
bucket_value_sum += (bkts[i]).value
if sufficient_funds(bkts[:i+1], bucket_value_sum=bucket_value_sum):
return bkts[:i+1]
raise Exception("keeping all buckets is still not enough")
class CoinChooserBase(PrintError):
class CoinChooserBase(Logger):
enable_output_value_rounding = False
def keys(self, coins):
def __init__(self):
Logger.__init__(self)
def keys(self, coins: Sequence[PartialTxInput]) -> Sequence[str]:
raise NotImplementedError
def bucketize_coins(self, coins):
def bucketize_coins(self, coins: Sequence[PartialTxInput], *, fee_estimator_vb):
keys = self.keys(coins)
buckets = defaultdict(list)
buckets = defaultdict(list) # type: Dict[str, List[PartialTxInput]]
for key, coin in zip(keys, coins):
buckets[key].append(coin)
# fee_estimator returns fee to be paid, for given vbytes.
# guess whether it is just returning a constant as follows.
constant_fee = fee_estimator_vb(2000) == fee_estimator_vb(200)
def make_Bucket(desc, coins):
def make_Bucket(desc: str, coins: List[PartialTxInput]):
witness = any(Transaction.is_segwit_input(coin, guess_for_address=True) for coin in coins)
# note that we're guessing whether the tx uses segwit based
# on this single bucket
weight = sum(Transaction.estimated_input_weight(coin, witness)
for coin in coins)
value = sum(coin['value'] for coin in coins)
min_height = min(coin['height'] for coin in coins)
return Bucket(desc, weight, value, coins, min_height, witness)
value = sum(coin.value_sats() for coin in coins)
min_height = min(coin.block_height for coin in coins)
assert min_height is not None
# the fee estimator is typically either a constant or a linear function,
# so the "function:" effective_value(bucket) will be homomorphic for addition
# i.e. effective_value(b1) + effective_value(b2) = effective_value(b1 + b2)
if constant_fee:
effective_value = value
else:
# when converting from weight to vBytes, instead of rounding up,
# keep fractional part, to avoid overestimating fee
fee = fee_estimator_vb(Decimal(weight) / 4)
effective_value = value - fee
return Bucket(desc=desc,
weight=weight,
value=value,
effective_value=effective_value,
coins=coins,
min_height=min_height,
witness=witness)
return list(map(make_Bucket, buckets.keys(), buckets.values()))
def penalty_func(self, tx):
def penalty(candidate):
return 0
return penalty
def penalty_func(self, base_tx, *,
tx_from_buckets: Callable[[List[Bucket]], Tuple[PartialTransaction, List[PartialTxOutput]]]) \
-> Callable[[List[Bucket]], ScoredCandidate]:
raise NotImplementedError
def change_amounts(self, tx, count, fee_estimator, dust_threshold):
def _change_amounts(self, tx: PartialTransaction, count: int, fee_estimator_numchange) -> List[int]:
# Break change up if bigger than max_change
output_amounts = [o.value for o in tx.outputs()]
# Don't split change of less than 0.02 BTC
@ -124,7 +163,7 @@ class CoinChooserBase(PrintError):
# Use N change outputs
for n in range(1, count + 1):
# How much is left if we add this many change outputs?
change_amount = max(0, tx.get_fee() - fee_estimator(n))
change_amount = max(0, tx.get_fee() - fee_estimator_numchange(n))
if change_amount // n <= max_change:
break
@ -161,7 +200,7 @@ class CoinChooserBase(PrintError):
# no more than 10**max_dp_to_round_for_privacy
# e.g. a max of 2 decimal places means losing 100 satoshis to fees
max_dp_to_round_for_privacy = 2 if self.enable_output_value_rounding else 0
N = pow(10, min(max_dp_to_round_for_privacy, zeroes[0]))
N = int(pow(10, min(max_dp_to_round_for_privacy, zeroes[0])))
amount = (remaining // N) * N
amounts.append(amount)
@ -169,111 +208,157 @@ class CoinChooserBase(PrintError):
return amounts
def change_outputs(self, tx, change_addrs, fee_estimator, dust_threshold):
amounts = self.change_amounts(tx, len(change_addrs), fee_estimator,
dust_threshold)
def _change_outputs(self, tx: PartialTransaction, change_addrs, fee_estimator_numchange,
dust_threshold) -> List[PartialTxOutput]:
amounts = self._change_amounts(tx, len(change_addrs), fee_estimator_numchange)
assert min(amounts) >= 0
assert len(change_addrs) >= len(amounts)
assert all([isinstance(amt, int) for amt in amounts])
# If change is above dust threshold after accounting for the
# size of the change output, add it to the transaction.
dust = sum(amount for amount in amounts if amount < dust_threshold)
amounts = [amount for amount in amounts if amount >= dust_threshold]
change = [TxOutput(TYPE_ADDRESS, addr, amount)
change = [PartialTxOutput.from_address_and_value(addr, amount)
for addr, amount in zip(change_addrs, amounts)]
self.print_error('change:', change)
if dust:
self.print_error('not keeping dust', dust)
return change
def make_tx(self, coins, outputs, change_addrs, fee_estimator,
dust_threshold):
"""Select unspent coins to spend to pay outputs. If the change is
greater than dust_threshold (after adding the change output to
the transaction) it is kept, otherwise none is sent and it is
added to the transaction fee.
Note: fee_estimator expects virtual bytes
"""
# Deterministic randomness from coins
utxos = [c['prevout_hash'] + str(c['prevout_n']) for c in coins]
self.p = PRNG(''.join(sorted(utxos)))
# Copy the outputs so when adding change we don't modify "outputs"
tx = Transaction.from_io([], outputs[:])
# Weight of the transaction with no inputs and no change
# Note: this will use legacy tx serialization as the need for "segwit"
# would be detected from inputs. The only side effect should be that the
# marker and flag are excluded, which is compensated in get_tx_weight()
base_weight = tx.estimated_weight()
spent_amount = tx.output_value()
def fee_estimator_w(weight):
return fee_estimator(Transaction.virtual_size_from_weight(weight))
def get_tx_weight(buckets):
total_weight = base_weight + sum(bucket.weight for bucket in buckets)
is_segwit_tx = any(bucket.witness for bucket in buckets)
if is_segwit_tx:
total_weight += 2 # marker and flag
# non-segwit inputs were previously assumed to have
# a witness of '' instead of '00' (hex)
# note that mixed legacy/segwit buckets are already ok
num_legacy_inputs = sum((not bucket.witness) * len(bucket.coins)
for bucket in buckets)
total_weight += num_legacy_inputs
return total_weight
def sufficient_funds(buckets):
'''Given a list of buckets, return True if it has enough
value to pay for the transaction'''
total_input = sum(bucket.value for bucket in buckets)
total_weight = get_tx_weight(buckets)
return total_input >= spent_amount + fee_estimator_w(total_weight)
# Collect the coins into buckets, choose a subset of the buckets
buckets = self.bucketize_coins(coins)
buckets = self.choose_buckets(buckets, sufficient_funds,
self.penalty_func(tx))
def _construct_tx_from_selected_buckets(self, *, buckets: Sequence[Bucket],
base_tx: PartialTransaction, change_addrs,
fee_estimator_w, dust_threshold,
base_weight) -> Tuple[PartialTransaction, List[PartialTxOutput]]:
# make a copy of base_tx so it won't get mutated
tx = PartialTransaction.from_io(base_tx.inputs()[:], base_tx.outputs()[:])
tx.add_inputs([coin for b in buckets for coin in b.coins])
tx_weight = get_tx_weight(buckets)
tx_weight = self._get_tx_weight(buckets, base_weight=base_weight)
# change is sent back to sending address unless specified
if not change_addrs:
change_addrs = [tx.inputs()[0]['address']]
change_addrs = [tx.inputs()[0].address]
# note: this is not necessarily the final "first input address"
# because the inputs had not been sorted at this point
assert is_address(change_addrs[0])
# This takes a count of change outputs and returns a tx fee
output_weight = 4 * Transaction.estimated_output_size(change_addrs[0])
fee = lambda count: fee_estimator_w(tx_weight + count * output_weight)
change = self.change_outputs(tx, change_addrs, fee, dust_threshold)
fee_estimator_numchange = lambda count: fee_estimator_w(tx_weight + count * output_weight)
change = self._change_outputs(tx, change_addrs, fee_estimator_numchange, dust_threshold)
tx.add_outputs(change)
self.print_error("using %d inputs" % len(tx.inputs()))
self.print_error("using buckets:", [bucket.desc for bucket in buckets])
return tx, change
def _get_tx_weight(self, buckets: Sequence[Bucket], *, base_weight: int) -> int:
"""Given a collection of buckets, return the total weight of the
resulting transaction.
base_weight is the weight of the tx that includes the fixed (non-change)
outputs and potentially some fixed inputs. Note that the change outputs
at this point are not yet known so they are NOT accounted for.
"""
total_weight = base_weight + sum(bucket.weight for bucket in buckets)
is_segwit_tx = any(bucket.witness for bucket in buckets)
if is_segwit_tx:
total_weight += 2 # marker and flag
# non-segwit inputs were previously assumed to have
# a witness of '' instead of '00' (hex)
# note that mixed legacy/segwit buckets are already ok
num_legacy_inputs = sum((not bucket.witness) * len(bucket.coins)
for bucket in buckets)
total_weight += num_legacy_inputs
return total_weight
def make_tx(self, *, coins: Sequence[PartialTxInput], inputs: List[PartialTxInput],
outputs: List[PartialTxOutput], change_addrs: Sequence[str],
fee_estimator_vb: Callable, dust_threshold: int) -> PartialTransaction:
"""Select unspent coins to spend to pay outputs. If the change is
greater than dust_threshold (after adding the change output to
the transaction) it is kept, otherwise none is sent and it is
added to the transaction fee.
`inputs` and `outputs` are guaranteed to be a subset of the
inputs and outputs of the resulting transaction.
`coins` are further UTXOs we can choose from.
Note: fee_estimator_vb expects virtual bytes
"""
assert outputs, 'tx outputs cannot be empty'
# Deterministic randomness from coins
utxos = [c.prevout.serialize_to_network() for c in coins]
self.p = PRNG(b''.join(sorted(utxos)))
# Copy the outputs so when adding change we don't modify "outputs"
base_tx = PartialTransaction.from_io(inputs[:], outputs[:])
input_value = base_tx.input_value()
# Weight of the transaction with no inputs and no change
# Note: this will use legacy tx serialization as the need for "segwit"
# would be detected from inputs. The only side effect should be that the
# marker and flag are excluded, which is compensated in get_tx_weight()
# FIXME calculation will be off by this (2 wu) in case of RBF batching
base_weight = base_tx.estimated_weight()
spent_amount = base_tx.output_value()
def fee_estimator_w(weight):
return fee_estimator_vb(Transaction.virtual_size_from_weight(weight))
def sufficient_funds(buckets, *, bucket_value_sum):
'''Given a list of buckets, return True if it has enough
value to pay for the transaction'''
# assert bucket_value_sum == sum(bucket.value for bucket in buckets) # expensive!
total_input = input_value + bucket_value_sum
if total_input < spent_amount: # shortcut for performance
return False
# note re performance: so far this was constant time
# what follows is linear in len(buckets)
total_weight = self._get_tx_weight(buckets, base_weight=base_weight)
return total_input >= spent_amount + fee_estimator_w(total_weight)
def tx_from_buckets(buckets):
return self._construct_tx_from_selected_buckets(buckets=buckets,
base_tx=base_tx,
change_addrs=change_addrs,
fee_estimator_w=fee_estimator_w,
dust_threshold=dust_threshold,
base_weight=base_weight)
# Collect the coins into buckets
all_buckets = self.bucketize_coins(coins, fee_estimator_vb=fee_estimator_vb)
# Filter some buckets out. Only keep those that have positive effective value.
# Note that this filtering is intentionally done on the bucket level
# instead of per-coin, as each bucket should be either fully spent or not at all.
# (e.g. CoinChooserPrivacy ensures that same-address coins go into one bucket)
all_buckets = list(filter(lambda b: b.effective_value > 0, all_buckets))
# Choose a subset of the buckets
scored_candidate = self.choose_buckets(all_buckets, sufficient_funds,
self.penalty_func(base_tx, tx_from_buckets=tx_from_buckets))
tx = scored_candidate.tx
self.logger.info(f"using {len(tx.inputs())} inputs")
self.logger.info(f"using buckets: {[bucket.desc for bucket in scored_candidate.buckets]}")
return tx
def choose_buckets(self, buckets, sufficient_funds, penalty_func):
def choose_buckets(self, buckets: List[Bucket],
sufficient_funds: Callable,
penalty_func: Callable[[List[Bucket]], ScoredCandidate]) -> ScoredCandidate:
raise NotImplemented('To be subclassed')
class CoinChooserRandom(CoinChooserBase):
def bucket_candidates_any(self, buckets, sufficient_funds):
def bucket_candidates_any(self, buckets: List[Bucket], sufficient_funds) -> List[List[Bucket]]:
'''Returns a list of bucket sets.'''
if not buckets:
raise NotEnoughFunds()
if sufficient_funds([], bucket_value_sum=0):
return [[]]
else:
raise NotEnoughFunds()
candidates = set()
# Add all singletons
for n, bucket in enumerate(buckets):
if sufficient_funds([bucket]):
if sufficient_funds([bucket], bucket_value_sum=bucket.value):
candidates.add((n, ))
# And now some random ones
@ -284,20 +369,23 @@ class CoinChooserRandom(CoinChooserBase):
# incrementally combine buckets until sufficient
self.p.shuffle(permutation)
bkts = []
bucket_value_sum = 0
for count, index in enumerate(permutation):
bkts.append(buckets[index])
if sufficient_funds(bkts):
bucket = buckets[index]
bkts.append(bucket)
bucket_value_sum += bucket.value
if sufficient_funds(bkts, bucket_value_sum=bucket_value_sum):
candidates.add(tuple(sorted(permutation[:count + 1])))
break
else:
# FIXME this assumes that the effective value of any bkt is >= 0
# we should make sure not to choose buckets with <= 0 eff. val.
# note: this assumes that the effective value of any bkt is >= 0
raise NotEnoughFunds()
candidates = [[buckets[n] for n in c] for c in candidates]
return [strip_unneeded(c, sufficient_funds) for c in candidates]
def bucket_candidates_prefer_confirmed(self, buckets, sufficient_funds):
def bucket_candidates_prefer_confirmed(self, buckets: List[Bucket],
sufficient_funds) -> List[List[Bucket]]:
"""Returns a list of bucket sets preferring confirmed coins.
Any bucket can be:
@ -315,16 +403,20 @@ class CoinChooserRandom(CoinChooserBase):
bucket_sets = [conf_buckets, unconf_buckets, other_buckets]
already_selected_buckets = []
already_selected_buckets_value_sum = 0
for bkts_choose_from in bucket_sets:
try:
def sfunds(bkts):
return sufficient_funds(already_selected_buckets + bkts)
def sfunds(bkts, *, bucket_value_sum):
bucket_value_sum += already_selected_buckets_value_sum
return sufficient_funds(already_selected_buckets + bkts,
bucket_value_sum=bucket_value_sum)
candidates = self.bucket_candidates_any(bkts_choose_from, sfunds)
break
except NotEnoughFunds:
already_selected_buckets += bkts_choose_from
already_selected_buckets_value_sum += sum(bucket.value for bucket in bkts_choose_from)
else:
raise NotEnoughFunds()
@ -333,12 +425,14 @@ class CoinChooserRandom(CoinChooserBase):
def choose_buckets(self, buckets, sufficient_funds, penalty_func):
candidates = self.bucket_candidates_prefer_confirmed(buckets, sufficient_funds)
penalties = [penalty_func(cand) for cand in candidates]
winner = candidates[penalties.index(min(penalties))]
self.print_error("Bucket sets:", len(buckets))
self.print_error("Winning penalty:", min(penalties))
scored_candidates = [penalty_func(cand) for cand in candidates]
winner = min(scored_candidates, key=lambda x: x.penalty)
self.logger.info(f"Total number of buckets: {len(buckets)}")
self.logger.info(f"Num candidates considered: {len(candidates)}. "
f"Winning penalty: {winner.penalty}")
return winner
class CoinChooserPrivacy(CoinChooserRandom):
"""Attempts to better preserve user privacy.
First, if any coin is spent from a user address, all coins are.
@ -351,26 +445,30 @@ class CoinChooserPrivacy(CoinChooserRandom):
"""
def keys(self, coins):
return [coin['address'] for coin in coins]
return [coin.scriptpubkey.hex() for coin in coins]
def penalty_func(self, tx):
min_change = min(o.value for o in tx.outputs()) * 0.75
max_change = max(o.value for o in tx.outputs()) * 1.33
spent_amount = sum(o.value for o in tx.outputs())
def penalty_func(self, base_tx, *, tx_from_buckets):
min_change = min(o.value for o in base_tx.outputs()) * 0.75
max_change = max(o.value for o in base_tx.outputs()) * 1.33
def penalty(buckets):
def penalty(buckets: List[Bucket]) -> ScoredCandidate:
# Penalize using many buckets (~inputs)
badness = len(buckets) - 1
total_input = sum(bucket.value for bucket in buckets)
# FIXME "change" here also includes fees
change = float(total_input - spent_amount)
tx, change_outputs = tx_from_buckets(buckets)
change = sum(o.value for o in change_outputs)
# Penalize change not roughly in output range
if change < min_change:
if change == 0:
pass # no change is great!
elif change < min_change:
badness += (min_change - change) / (min_change + 10000)
# Penalize really small change; under 1 mBTC ~= using 1 more input
if change < COIN / 1000:
badness += 1
elif change > max_change:
badness += (change - max_change) / (max_change + 10000)
# Penalize large change; 5 BTC excess ~= using 1 more input
badness += change / (COIN * 5)
return badness
return ScoredCandidate(badness, tx, buckets)
return penalty

File diff suppressed because it is too large Load diff

View file

@ -26,6 +26,10 @@
import os
import json
BLOCKS_PER_CHUNK = 96
from .util import inv_dict
from . import bitcoin
def read_json(filename, default):
path = os.path.join(os.path.dirname(__file__), filename)
@ -37,17 +41,35 @@ def read_json(filename, default):
return r
class BitcoinMainnet:
GIT_REPO_URL = "https://github.com/spesmilo/electrum"
GIT_REPO_ISSUES_URL = "https://github.com/spesmilo/electrum/issues"
class AbstractNet:
BLOCK_HEIGHT_FIRST_LIGHTNING_CHANNELS = 0
@classmethod
def max_checkpoint(cls) -> int:
return max(0, len(cls.CHECKPOINTS) * 2016 - 1)
@classmethod
def rev_genesis_bytes(cls) -> bytes:
return bytes.fromhex(bitcoin.rev_hex(cls.GENESIS))
class BitcoinMainnet(AbstractNet):
TESTNET = False
WIF_PREFIX = 0x80
ADDRTYPE_P2PKH = 0
ADDRTYPE_P2SH = 5
SEGWIT_HRP = "bc"
GENESIS = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
WIF_PREFIX = 0x1c
ADDRTYPE_P2PKH = 0x55
ADDRTYPE_P2SH = 0x7A
SEGWIT_HRP = "lbc"
GENESIS = "9c89283ba0f3227f6c03b70216b9f665f0118d5e0fa729cedf4fb34d6a34f463"
DEFAULT_PORTS = {'t': '50001', 's': '50002'}
DEFAULT_SERVERS = read_json('servers.json', {})
CHECKPOINTS = read_json('checkpoints.json', [])
CHECKPOINTS = read_json('bullshit.json', [])
BLOCK_HEIGHT_FIRST_LIGHTNING_CHANNELS = 497000
XPRV_HEADERS = {
'standard': 0x0488ade4, # xprv
@ -56,6 +78,7 @@ class BitcoinMainnet:
'p2wpkh': 0x04b2430c, # zprv
'p2wsh': 0x02aa7a99, # Zprv
}
XPRV_HEADERS_INV = inv_dict(XPRV_HEADERS)
XPUB_HEADERS = {
'standard': 0x0488b21e, # xpub
'p2wpkh-p2sh': 0x049d7cb2, # ypub
@ -63,16 +86,22 @@ class BitcoinMainnet:
'p2wpkh': 0x04b24746, # zpub
'p2wsh': 0x02aa7ed3, # Zpub
}
BIP44_COIN_TYPE = 0
XPUB_HEADERS_INV = inv_dict(XPUB_HEADERS)
BIP44_COIN_TYPE = 140
LN_REALM_BYTE = 0
LN_DNS_SEEDS = [
'nodes.lightning.directory.',
'lseed.bitcoinstats.com.',
]
class BitcoinTestnet:
class BitcoinTestnet(AbstractNet):
TESTNET = True
WIF_PREFIX = 0xef
ADDRTYPE_P2PKH = 111
ADDRTYPE_P2SH = 196
SEGWIT_HRP = "tb"
SEGWIT_HRP = "tlbc"
GENESIS = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
DEFAULT_PORTS = {'t': '51001', 's': '51002'}
DEFAULT_SERVERS = read_json('servers_testnet.json', {})
@ -85,6 +114,7 @@ class BitcoinTestnet:
'p2wpkh': 0x045f18bc, # vprv
'p2wsh': 0x02575048, # Vprv
}
XPRV_HEADERS_INV = inv_dict(XPRV_HEADERS)
XPUB_HEADERS = {
'standard': 0x043587cf, # tpub
'p2wpkh-p2sh': 0x044a5262, # upub
@ -92,23 +122,34 @@ class BitcoinTestnet:
'p2wpkh': 0x045f1cf6, # vpub
'p2wsh': 0x02575483, # Vpub
}
XPUB_HEADERS_INV = inv_dict(XPUB_HEADERS)
BIP44_COIN_TYPE = 1
LN_REALM_BYTE = 1
LN_DNS_SEEDS = [
'test.nodes.lightning.directory.',
'lseed.bitcoinstats.com.',
]
class BitcoinRegtest(BitcoinTestnet):
SEGWIT_HRP = "bcrt"
SEGWIT_HRP = "blbc"
GENESIS = "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
DEFAULT_SERVERS = read_json('servers_regtest.json', {})
CHECKPOINTS = []
LN_DNS_SEEDS = []
class BitcoinSimnet(BitcoinTestnet):
SEGWIT_HRP = "sb"
WIF_PREFIX = 0x64
ADDRTYPE_P2PKH = 0x3f
ADDRTYPE_P2SH = 0x7b
SEGWIT_HRP = "slbc"
GENESIS = "683e86bd5c6d110d91b94b97137ba6bfe02dbbdb8e3dff722a669b5d69d77af6"
DEFAULT_SERVERS = read_json('servers_regtest.json', {})
CHECKPOINTS = []
LN_DNS_SEEDS = []
# don't import net directly, import the module instead (so that net is singleton)
@ -122,7 +163,6 @@ def set_mainnet():
global net
net = BitcoinMainnet
def set_testnet():
global net
net = BitcoinTestnet

View file

@ -21,22 +21,22 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# 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, print_error, to_string
from .util import export_meta, import_meta, to_string
from .logging import Logger
class Contacts(dict):
class Contacts(dict, Logger):
def __init__(self, storage):
self.storage = storage
d = self.storage.get('contacts', {})
def __init__(self, db):
Logger.__init__(self)
self.db = db
d = self.db.get('contacts', {})
try:
self.update(d)
except:
@ -49,7 +49,7 @@ class Contacts(dict):
self[n] = ('address', k)
def save(self):
self.storage.put('contacts', dict(self))
self.db.put('contacts', dict(self))
def import_file(self, path):
import_meta(path, self._validate, self.load_meta)
@ -67,8 +67,9 @@ class Contacts(dict):
def pop(self, key):
if key in self.keys():
dict.pop(self, key)
res = dict.pop(self, key)
self.save()
return res
def resolve(self, k):
if bitcoin.is_address(k):
@ -92,7 +93,7 @@ class Contacts(dict):
'type': 'openalias',
'validated': validated
}
raise Exception("Invalid Bitcoin address or alias", k)
raise Exception("Invalid LBRY Credits address or alias", k)
def resolve_openalias(self, url):
# support email-style addresses, per the OA standard
@ -100,9 +101,9 @@ class Contacts(dict):
try:
records, validated = dnssec.query(url, dns.rdatatype.TXT)
except DNSException as e:
print_error('Error resolving openalias: ', str(e))
self.logger.info(f'Error resolving openalias: {repr(e)}')
return None
prefix = 'btc'
prefix = 'lbc'
for record in records:
string = to_string(record.strings[0], 'utf8')
if string.startswith('oa1:' + prefix):
@ -132,4 +133,3 @@ class Contacts(dict):
if _type != 'address':
data.pop(k)
return data

View file

@ -27,10 +27,12 @@ import base64
import os
import hashlib
import hmac
from typing import Union
import pyaes
from .util import assert_bytes, InvalidPassword, to_bytes, to_string
from .util import assert_bytes, InvalidPassword, to_bytes, to_string, WalletFileException
from .i18n import _
try:
@ -43,26 +45,26 @@ class InvalidPadding(Exception):
pass
def append_PKCS7_padding(data):
def append_PKCS7_padding(data: bytes) -> bytes:
assert_bytes(data)
padlen = 16 - (len(data) % 16)
return data + bytes([padlen]) * padlen
def strip_PKCS7_padding(data):
def strip_PKCS7_padding(data: bytes) -> bytes:
assert_bytes(data)
if len(data) % 16 != 0 or len(data) == 0:
raise InvalidPadding("invalid length")
padlen = data[-1]
if padlen > 16:
raise InvalidPadding("invalid padding byte (large)")
if not (0 < padlen <= 16):
raise InvalidPadding("invalid padding byte (out of range)")
for i in data[-padlen:]:
if i != padlen:
raise InvalidPadding("invalid padding byte (inconsistent)")
return data[0:-padlen]
def aes_encrypt_with_iv(key, iv, data):
def aes_encrypt_with_iv(key: bytes, iv: bytes, data: bytes) -> bytes:
assert_bytes(key, iv, data)
data = append_PKCS7_padding(data)
if AES:
@ -74,7 +76,7 @@ def aes_encrypt_with_iv(key, iv, data):
return e
def aes_decrypt_with_iv(key, iv, data):
def aes_decrypt_with_iv(key: bytes, iv: bytes, data: bytes) -> bytes:
assert_bytes(key, iv, data)
if AES:
cipher = AES.new(key, AES.MODE_CBC, iv)
@ -89,60 +91,125 @@ def aes_decrypt_with_iv(key, iv, data):
raise InvalidPassword()
def EncodeAES(secret, s):
assert_bytes(s)
iv = bytes(os.urandom(16))
ct = aes_encrypt_with_iv(secret, iv, s)
e = iv + ct
def EncodeAES_base64(secret: bytes, msg: bytes) -> bytes:
"""Returns base64 encoded ciphertext."""
e = EncodeAES_bytes(secret, msg)
return base64.b64encode(e)
def DecodeAES(secret, e):
e = bytes(base64.b64decode(e))
iv, e = e[:16], e[16:]
def EncodeAES_bytes(secret: bytes, msg: bytes) -> bytes:
assert_bytes(msg)
iv = bytes(os.urandom(16))
ct = aes_encrypt_with_iv(secret, iv, msg)
return iv + ct
def DecodeAES_base64(secret: bytes, ciphertext_b64: Union[bytes, str]) -> bytes:
ciphertext = bytes(base64.b64decode(ciphertext_b64))
return DecodeAES_bytes(secret, ciphertext)
def DecodeAES_bytes(secret: bytes, ciphertext: bytes) -> bytes:
assert_bytes(ciphertext)
iv, e = ciphertext[:16], ciphertext[16:]
s = aes_decrypt_with_iv(secret, iv, e)
return s
def pw_encode(s, password):
if password:
secret = Hash(password)
return EncodeAES(secret, to_bytes(s, "utf8")).decode('utf8')
PW_HASH_VERSION_LATEST = 1
KNOWN_PW_HASH_VERSIONS = (1, 2, )
SUPPORTED_PW_HASH_VERSIONS = (1, )
assert PW_HASH_VERSION_LATEST in KNOWN_PW_HASH_VERSIONS
assert PW_HASH_VERSION_LATEST in SUPPORTED_PW_HASH_VERSIONS
class UnexpectedPasswordHashVersion(InvalidPassword, WalletFileException):
def __init__(self, version):
self.version = version
def __str__(self):
return "{unexpected}: {version}\n{instruction}".format(
unexpected=_("Unexpected password hash version"),
version=self.version,
instruction=_('You are most likely using an outdated version of Electrum. Please update.'))
class UnsupportedPasswordHashVersion(InvalidPassword, WalletFileException):
def __init__(self, version):
self.version = version
def __str__(self):
return "{unsupported}: {version}\n{instruction}".format(
unsupported=_("Unsupported password hash version"),
version=self.version,
instruction=f"To open this wallet, try 'git checkout password_v{self.version}'.\n"
"Alternatively, restore from seed.")
def _hash_password(password: Union[bytes, str], *, version: int) -> bytes:
pw = to_bytes(password, 'utf8')
if version not in SUPPORTED_PW_HASH_VERSIONS:
raise UnsupportedPasswordHashVersion(version)
if version == 1:
return sha256d(pw)
else:
return s
def pw_decode(s, password):
if password is not None:
secret = Hash(password)
try:
d = to_string(DecodeAES(secret, s), "utf8")
except Exception:
raise InvalidPassword()
return d
else:
return s
assert version not in KNOWN_PW_HASH_VERSIONS
raise UnexpectedPasswordHashVersion(version)
def sha256(x: bytes) -> bytes:
def pw_encode(data: str, password: Union[bytes, str, None], *, version: int) -> str:
if not password:
return data
if version not in KNOWN_PW_HASH_VERSIONS:
raise UnexpectedPasswordHashVersion(version)
# derive key from password
secret = _hash_password(password, version=version)
# encrypt given data
ciphertext = EncodeAES_bytes(secret, to_bytes(data, "utf8"))
ciphertext_b64 = base64.b64encode(ciphertext)
return ciphertext_b64.decode('utf8')
def pw_decode(data: str, password: Union[bytes, str, None], *, version: int) -> str:
if password is None:
return data
if version not in KNOWN_PW_HASH_VERSIONS:
raise UnexpectedPasswordHashVersion(version)
data_bytes = bytes(base64.b64decode(data))
# derive key from password
secret = _hash_password(password, version=version)
# decrypt given data
try:
d = to_string(DecodeAES_bytes(secret, data_bytes), "utf8")
except Exception as e:
raise InvalidPassword() from e
return d
def sha256(x: Union[bytes, str]) -> bytes:
x = to_bytes(x, 'utf8')
return bytes(hashlib.sha256(x).digest())
def Hash(x: bytes) -> bytes:
def sha256d(x: Union[bytes, str]) -> bytes:
x = to_bytes(x, 'utf8')
out = bytes(sha256(sha256(x)))
return out
def hash_160(x: bytes) -> bytes:
return ripemd(sha256(x))
def ripemd(x):
try:
md = hashlib.new('ripemd160')
md.update(sha256(x))
md.update(x)
return md.digest()
except BaseException:
from . import ripemd
md = ripemd.new(sha256(x))
md = ripemd.new(x)
return md.digest()
def hmac_oneshot(key: bytes, msg: bytes, digest) -> bytes:
if hasattr(hmac, 'digest'):
# requires python 3.7+; faster

View file

@ -28,6 +28,7 @@
"BTC",
"BTN",
"BWP",
"BYN",
"BZD",
"CAD",
"CDF",
@ -46,6 +47,7 @@
"DZD",
"EGP",
"ETB",
"ETH",
"EUR",
"FJD",
"FKP",
@ -109,6 +111,7 @@
"NZD",
"OMR",
"PAB",
"PAX",
"PEN",
"PGK",
"PHP",
@ -149,6 +152,7 @@
"UYU",
"UZS",
"VEF",
"VES",
"VND",
"VUV",
"WST",
@ -158,197 +162,27 @@
"XCD",
"XOF",
"XPF",
"XRP",
"YER",
"ZAR",
"ZMW",
"ZWL"
],
"BitStamp": [
"USD"
"USD",
"EUR"
],
"Bitbank": [
"JPY"
],
"BitcoinAverage": [
"AED",
"AFN",
"ALL",
"AMD",
"ANG",
"AOA",
"ARS",
"AUD",
"AWG",
"AZN",
"BAM",
"BBD",
"BDT",
"BGN",
"BHD",
"BIF",
"BMD",
"BND",
"BOB",
"BRL",
"BSD",
"BTN",
"BWP",
"BYN",
"BZD",
"CAD",
"CDF",
"CHF",
"CLF",
"CLP",
"CNH",
"CNY",
"COP",
"CRC",
"CUC",
"CUP",
"CVE",
"CZK",
"DJF",
"DKK",
"DOP",
"DZD",
"EGP",
"ERN",
"ETB",
"EUR",
"FJD",
"FKP",
"GBP",
"GEL",
"GGP",
"GHS",
"GIP",
"GMD",
"GNF",
"GTQ",
"GYD",
"HKD",
"HNL",
"HRK",
"HTG",
"HUF",
"IDR",
"ILS",
"IMP",
"INR",
"IQD",
"IRR",
"ISK",
"JEP",
"JMD",
"JOD",
"JPY",
"KES",
"KGS",
"KHR",
"KMF",
"KPW",
"KRW",
"KWD",
"KYD",
"KZT",
"LAK",
"LBP",
"LKR",
"LRD",
"LSL",
"LYD",
"MAD",
"MDL",
"MGA",
"MKD",
"MMK",
"MNT",
"MOP",
"MRO",
"MUR",
"MVR",
"MWK",
"MXN",
"MYR",
"MZN",
"NAD",
"NGN",
"NIO",
"NOK",
"NPR",
"NZD",
"OMR",
"PAB",
"PEN",
"PGK",
"PHP",
"PKR",
"PLN",
"PYG",
"QAR",
"RON",
"RSD",
"RUB",
"RWF",
"SAR",
"SBD",
"SCR",
"SDG",
"SEK",
"SGD",
"SHP",
"SLL",
"SOS",
"SRD",
"SSP",
"STD",
"SVC",
"SYP",
"SZL",
"THB",
"TJS",
"TMT",
"TND",
"TOP",
"TRY",
"TTD",
"TWD",
"TZS",
"UAH",
"UGX",
"USD",
"UYU",
"UZS",
"VEF",
"VND",
"VUV",
"WST",
"XAF",
"XAG",
"XAU",
"XCD",
"XDR",
"XOF",
"XPD",
"XPF",
"XPT",
"YER",
"ZAR",
"ZMW",
"ZWL"
],
"Bitcointoyou": [
"BRL"
],
"BitcoinVenezuela": [
"ARS",
"ETH",
"EUR",
"LTC",
"USD",
"VEF"
],
"Bitmarket": [
"PLN"
"VEF",
"XMR"
],
"Bitso": [
"MXN"
@ -380,6 +214,12 @@
"TWD",
"USD"
],
"Bylls": [
"CAD"
],
"CoinCap": [
"USD"
],
"CoinDesk": [
"AED",
"AFN",
@ -549,6 +389,63 @@
"ZMW",
"ZWL"
],
"CoinGecko": [
"AED",
"ARS",
"AUD",
"BCH",
"BDT",
"BHD",
"BMD",
"BNB",
"BRL",
"BTC",
"CAD",
"CHF",
"CLP",
"CNY",
"CZK",
"DKK",
"EOS",
"ETH",
"EUR",
"GBP",
"HKD",
"HUF",
"IDR",
"ILS",
"INR",
"JPY",
"KRW",
"KWD",
"LKR",
"LTC",
"MMK",
"MXN",
"MYR",
"NOK",
"NZD",
"PHP",
"PKR",
"PLN",
"RUB",
"SAR",
"SEK",
"SGD",
"THB",
"TRY",
"TWD",
"UAH",
"USD",
"VEF",
"VND",
"XAG",
"XAU",
"XDR",
"XLM",
"XRP",
"ZAR"
],
"Coinbase": [
"AED",
"AFN",
@ -561,6 +458,7 @@
"AWG",
"AZN",
"BAM",
"BAT",
"BBD",
"BCH",
"BDT",
@ -572,6 +470,8 @@
"BOB",
"BRL",
"BSD",
"BSV",
"BTC",
"BTN",
"BWP",
"BYN",
@ -589,14 +489,17 @@
"CUC",
"CVE",
"CZK",
"DAI",
"DJF",
"DKK",
"DOP",
"DZD",
"EEK",
"EGP",
"EOS",
"ERN",
"ETB",
"ETC",
"ETH",
"EUR",
"FJD",
@ -664,6 +567,7 @@
"NPR",
"NZD",
"OMR",
"OXT",
"PAB",
"PEN",
"PGK",
@ -672,10 +576,12 @@
"PLN",
"PYG",
"QAR",
"REP",
"RON",
"RSD",
"RUB",
"RWF",
"SAI",
"SAR",
"SBD",
"SCR",
@ -704,6 +610,7 @@
"UYU",
"UZS",
"VEF",
"VES",
"VND",
"VUV",
"WST",
@ -712,19 +619,21 @@
"XAU",
"XCD",
"XDR",
"XLM",
"XOF",
"XPD",
"XPF",
"XPT",
"XRP",
"XTZ",
"YER",
"ZAR",
"ZEC",
"ZMK",
"ZMW",
"ZRX",
"ZWL"
],
"Foxbit": [
"BRL"
],
"Kraken": [
"CAD",
"EUR",
@ -734,13 +643,14 @@
],
"LocalBitcoins": [
"AED",
"AMD",
"AOA",
"ARS",
"AUD",
"BDT",
"BHD",
"BGN",
"BOB",
"BRL",
"BWP",
"BYN",
"CAD",
"CHF",
@ -757,6 +667,7 @@
"GBP",
"GEL",
"GHS",
"GTQ",
"HKD",
"HRK",
"HUF",
@ -765,16 +676,18 @@
"INR",
"IRR",
"JOD",
"JPY",
"KES",
"KRW",
"KWD",
"KZT",
"LKR",
"LTC",
"MAD",
"MDL",
"MUR",
"MXN",
"MYR",
"NGN",
"NIO",
"NOK",
"NZD",
"PAB",
@ -786,6 +699,7 @@
"RON",
"RSD",
"RUB",
"RWF",
"SAR",
"SEK",
"SGD",
@ -798,26 +712,18 @@
"UGX",
"USD",
"UYU",
"VEF",
"VES",
"VND",
"XAF",
"XOF",
"XRP",
"ZAR",
"ZMW"
],
"MercadoBitcoin": [
"BRL"
],
"NegocieCoins": [
"BRL"
],
"TheRockTrading": [
"EUR"
],
"WEX": [
"EUR",
"RUB",
"USD"
],
"itBit": []
"Zaif": [
"JPY"
]
}

View file

@ -22,29 +22,45 @@
# 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 asyncio
import ast
import os
import time
import traceback
import sys
import threading
from typing import Dict, Optional, Tuple, Iterable
from base64 import b64decode, b64encode
from collections import defaultdict
# from jsonrpc import JSONRPCResponseManager
import jsonrpclib
from .jsonrpc import VerifyingJSONRPCServer
import aiohttp
from aiohttp import web, client_exceptions
import jsonrpcclient
import jsonrpcserver
from jsonrpcserver import response
from jsonrpcclient.clients.aiohttp_client import AiohttpClient
from aiorpcx import TaskGroup
from .version import ELECTRUM_VERSION
from .network import Network
from .util import json_decode, DaemonThread
from .util import print_error, to_string
from .wallet import Wallet
from .util import (json_decode, to_bytes, to_string, profiler, standardize_path, constant_time_compare)
from .util import PR_PAID, PR_EXPIRED, get_request_status
from .util import log_exceptions, ignore_exceptions, randrange
from .wallet import Wallet, Abstract_Wallet
from .storage import WalletStorage
from .wallet_db import WalletDB
from .commands import known_commands, Commands
from .simple_config import SimpleConfig
from .exchange_rate import FxThread
from .plugin import run_hook
from .logging import get_logger, Logger
def get_lockfile(config):
_logger = get_logger(__name__)
class DaemonNotRunning(Exception):
pass
def get_lockfile(config: SimpleConfig):
return os.path.join(config.path, 'daemon')
@ -52,7 +68,7 @@ def remove_lockfile(lockfile):
os.unlink(lockfile)
def get_fd_or_server(config):
def get_file_descriptor(config: SimpleConfig):
'''Tries to create the lockfile, using O_EXCL to
prevent races. If it succeeds it returns the FD.
Otherwise try and connect to the server specified in the lockfile.
@ -61,256 +77,447 @@ def get_fd_or_server(config):
lockfile = get_lockfile(config)
while True:
try:
return os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644), None
return os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644)
except OSError:
pass
server = get_server(config)
if server is not None:
return None, server
# Couldn't connect; remove lockfile and try again.
remove_lockfile(lockfile)
try:
request(config, 'ping')
return None
except DaemonNotRunning:
# Couldn't connect; remove lockfile and try again.
remove_lockfile(lockfile)
def get_server(config):
def request(config: SimpleConfig, endpoint, args=(), timeout=60):
lockfile = get_lockfile(config)
while True:
create_time = None
try:
with open(lockfile) as f:
(host, port), create_time = ast.literal_eval(f.read())
rpc_user, rpc_password = get_rpc_credentials(config)
if rpc_password == '':
# authentication disabled
server_url = 'http://%s:%d' % (host, port)
else:
server_url = 'http://%s:%s@%s:%d' % (
rpc_user, rpc_password, host, port)
server = jsonrpclib.Server(server_url)
# Test daemon is running
server.ping()
return server
except Exception as e:
print_error("[get_server]", e)
if not create_time or create_time < time.time() - 1.0:
return None
except Exception:
raise DaemonNotRunning()
rpc_user, rpc_password = get_rpc_credentials(config)
server_url = 'http://%s:%d' % (host, port)
auth = aiohttp.BasicAuth(login=rpc_user, password=rpc_password)
loop = asyncio.get_event_loop()
async def request_coroutine():
async with aiohttp.ClientSession(auth=auth) as session:
server = AiohttpClient(session, server_url)
f = getattr(server, endpoint)
response = await f(*args)
return response.data.result
try:
fut = asyncio.run_coroutine_threadsafe(request_coroutine(), loop)
return fut.result(timeout=timeout)
except aiohttp.client_exceptions.ClientConnectorError as e:
_logger.info(f"failed to connect to JSON-RPC server {e}")
if not create_time or create_time < time.time() - 1.0:
raise DaemonNotRunning()
# Sleep a bit and try again; it might have just been started
time.sleep(1.0)
def get_rpc_credentials(config):
def get_rpc_credentials(config: SimpleConfig) -> Tuple[str, str]:
rpc_user = config.get('rpcuser', None)
rpc_password = config.get('rpcpassword', None)
if rpc_user is None or rpc_password is None:
rpc_user = 'user'
import ecdsa, base64
bits = 128
nbytes = bits // 8 + (bits % 8 > 0)
pw_int = ecdsa.util.randrange(pow(2, bits))
pw_b64 = base64.b64encode(
pw_int = randrange(pow(2, bits))
pw_b64 = b64encode(
pw_int.to_bytes(nbytes, 'big'), b'-_')
rpc_password = to_string(pw_b64, 'ascii')
config.set_key('rpcuser', rpc_user)
config.set_key('rpcpassword', rpc_password, save=True)
elif rpc_password == '':
from .util import print_stderr
print_stderr('WARNING: RPC authentication is disabled.')
_logger.warning('RPC authentication is disabled.')
return rpc_user, rpc_password
class Daemon(DaemonThread):
class WatchTowerServer(Logger):
def __init__(self, config, fd, is_gui):
DaemonThread.__init__(self)
def __init__(self, network):
Logger.__init__(self)
self.config = network.config
self.network = network
self.lnwatcher = network.local_watchtower
self.app = web.Application()
self.app.router.add_post("/", self.handle)
self.methods = jsonrpcserver.methods.Methods()
self.methods.add(self.get_ctn)
self.methods.add(self.add_sweep_tx)
async def handle(self, request):
request = await request.text()
self.logger.info(f'{request}')
response = await jsonrpcserver.async_dispatch(request, methods=self.methods)
if response.wanted:
return web.json_response(response.deserialized(), status=response.http_status)
else:
return web.Response()
async def run(self):
host = self.config.get('watchtower_host')
port = self.config.get('watchtower_port', 12345)
self.runner = web.AppRunner(self.app)
await self.runner.setup()
site = web.TCPSite(self.runner, host, port, ssl_context=self.config.get_ssl_context())
await site.start()
async def get_ctn(self, *args):
return await self.lnwatcher.sweepstore.get_ctn(*args)
async def add_sweep_tx(self, *args):
return await self.lnwatcher.sweepstore.add_sweep_tx(*args)
class PayServer(Logger):
def __init__(self, daemon: 'Daemon'):
Logger.__init__(self)
self.daemon = daemon
self.config = daemon.config
self.pending = defaultdict(asyncio.Event)
self.daemon.network.register_callback(self.on_payment, ['payment_received'])
async def on_payment(self, evt, wallet, key, status):
if status == PR_PAID:
await self.pending[key].set()
@ignore_exceptions
@log_exceptions
async def run(self):
host = self.config.get('payserver_host', 'localhost')
port = self.config.get('payserver_port')
root = self.config.get('payserver_root', '/r')
app = web.Application()
app.add_routes([web.post('/api/create_invoice', self.create_request)])
app.add_routes([web.get('/api/get_invoice', self.get_request)])
app.add_routes([web.get('/api/get_status', self.get_status)])
app.add_routes([web.get('/bip70/{key}.bip70', self.get_bip70_request)])
app.add_routes([web.static(root, 'electrum/www')])
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, port=port, host=host, ssl_context=self.config.get_ssl_context())
await site.start()
async def create_request(self, request):
params = await request.post()
wallet = self.daemon.wallet
if 'amount_sat' not in params or not params['amount_sat'].isdigit():
raise web.HTTPUnsupportedMediaType()
amount = int(params['amount_sat'])
message = params['message'] or "donation"
payment_hash = await wallet.lnworker._add_invoice_coro(amount, message, 3600)
key = payment_hash.hex()
raise web.HTTPFound(self.root + '/pay?id=' + key)
async def get_request(self, r):
key = r.query_string
request = self.daemon.wallet.get_request(key)
return web.json_response(request)
async def get_bip70_request(self, r):
from .paymentrequest import make_request
key = r.match_info['key']
request = self.daemon.wallet.get_request(key)
if not request:
return web.HTTPNotFound()
pr = make_request(self.config, request)
return web.Response(body=pr.SerializeToString(), content_type='application/bitcoin-paymentrequest')
async def get_status(self, request):
ws = web.WebSocketResponse()
await ws.prepare(request)
key = request.query_string
info = self.daemon.wallet.get_request(key)
if not info:
await ws.send_str('unknown invoice')
await ws.close()
return ws
if info.get('status') == PR_PAID:
await ws.send_str(f'paid')
await ws.close()
return ws
if info.get('status') == PR_EXPIRED:
await ws.send_str(f'expired')
await ws.close()
return ws
while True:
try:
await asyncio.wait_for(self.pending[key].wait(), 1)
break
except asyncio.TimeoutError:
# send data on the websocket, to keep it alive
await ws.send_str('waiting')
await ws.send_str('paid')
await ws.close()
return ws
class AuthenticationError(Exception):
pass
class AuthenticationInvalidOrMissing(AuthenticationError):
pass
class AuthenticationCredentialsInvalid(AuthenticationError):
pass
class Daemon(Logger):
@profiler
def __init__(self, config: SimpleConfig, fd=None, *, listen_jsonrpc=True):
Logger.__init__(self)
self.auth_lock = asyncio.Lock()
self.running = False
self.running_lock = threading.Lock()
self.config = config
if config.get('offline'):
self.network = None
else:
self.network = Network(config)
self.network.start()
if fd is None and listen_jsonrpc:
fd = get_file_descriptor(config)
if fd is None:
raise Exception('failed to lock daemon; already running?')
self.asyncio_loop = asyncio.get_event_loop()
self.network = None
if not config.get('offline'):
self.network = Network(config, daemon=self)
self.fx = FxThread(config, self.network)
if self.network:
self.network.add_jobs([self.fx])
self.gui = None
self.wallets = {}
self.gui_object = None
# path -> wallet; make sure path is standardized.
self._wallets = {} # type: Dict[str, Abstract_Wallet]
daemon_jobs = []
# Setup JSONRPC server
self.init_server(config, fd, is_gui)
if listen_jsonrpc:
daemon_jobs.append(self.start_jsonrpc(config, fd))
# request server
self.pay_server = None
if not config.get('offline') and self.config.get('run_payserver'):
self.pay_server = PayServer(self)
daemon_jobs.append(self.pay_server.run())
# server-side watchtower
self.watchtower = None
if not config.get('offline') and self.config.get('run_watchtower'):
self.watchtower = WatchTowerServer(self.network)
daemon_jobs.append(self.watchtower.run)
if self.network:
self.network.start(jobs=[self.fx.run])
def init_server(self, config, fd, is_gui):
host = config.get('rpchost', '127.0.0.1')
port = config.get('rpcport', 0)
self.taskgroup = TaskGroup()
asyncio.run_coroutine_threadsafe(self._run(jobs=daemon_jobs), self.asyncio_loop)
rpc_user, rpc_password = get_rpc_credentials(config)
@log_exceptions
async def _run(self, jobs: Iterable = None):
if jobs is None:
jobs = []
try:
server = VerifyingJSONRPCServer((host, port), logRequests=False,
rpc_user=rpc_user, rpc_password=rpc_password)
except Exception as e:
self.print_error('Warning: cannot initialize RPC server on host', host, e)
self.server = None
os.close(fd)
return
os.write(fd, bytes(repr((server.socket.getsockname(), time.time())), 'utf8'))
os.close(fd)
self.server = server
server.timeout = 0.1
server.register_function(self.ping, 'ping')
if is_gui:
server.register_function(self.run_gui, 'gui')
else:
server.register_function(self.run_daemon, 'daemon')
self.cmd_runner = Commands(self.config, None, self.network)
for cmdname in known_commands:
server.register_function(getattr(self.cmd_runner, cmdname), cmdname)
server.register_function(self.run_cmdline, 'run_cmdline')
async with self.taskgroup as group:
[await group.spawn(job) for job in jobs]
await group.spawn(asyncio.Event().wait) # run forever (until cancel)
except BaseException as e:
self.logger.exception('daemon.taskgroup died.')
finally:
self.logger.info("stopping daemon.taskgroup")
def ping(self):
async def authenticate(self, headers):
if self.rpc_password == '':
# RPC authentication is disabled
return
auth_string = headers.get('Authorization', None)
if auth_string is None:
raise AuthenticationInvalidOrMissing('CredentialsMissing')
basic, _, encoded = auth_string.partition(' ')
if basic != 'Basic':
raise AuthenticationInvalidOrMissing('UnsupportedType')
encoded = to_bytes(encoded, 'utf8')
credentials = to_string(b64decode(encoded), 'utf8')
username, _, password = credentials.partition(':')
if not (constant_time_compare(username, self.rpc_user)
and constant_time_compare(password, self.rpc_password)):
await asyncio.sleep(0.050)
raise AuthenticationCredentialsInvalid('Invalid Credentials')
async def handle(self, request):
async with self.auth_lock:
try:
await self.authenticate(request.headers)
except AuthenticationInvalidOrMissing:
return web.Response(headers={"WWW-Authenticate": "Basic realm=Electrum"},
text='Unauthorized', status=401)
except AuthenticationCredentialsInvalid:
return web.Response(text='Forbidden', status=403)
request = await request.text()
response = await jsonrpcserver.async_dispatch(request, methods=self.methods)
if isinstance(response, jsonrpcserver.response.ExceptionResponse):
self.logger.error(f"error handling request: {request}", exc_info=response.exc)
# this exposes the error message to the client
response.message = str(response.exc)
if response.wanted:
return web.json_response(response.deserialized(), status=response.http_status)
else:
return web.Response()
async def start_jsonrpc(self, config: SimpleConfig, fd):
self.app = web.Application()
self.app.router.add_post("/", self.handle)
self.rpc_user, self.rpc_password = get_rpc_credentials(config)
self.methods = jsonrpcserver.methods.Methods()
self.methods.add(self.ping)
self.methods.add(self.gui)
self.cmd_runner = Commands(config=self.config, network=self.network, daemon=self)
for cmdname in known_commands:
self.methods.add(getattr(self.cmd_runner, cmdname))
self.methods.add(self.run_cmdline)
self.host = config.get('rpchost', '127.0.0.1')
self.port = config.get('rpcport', 0)
self.runner = web.AppRunner(self.app)
await self.runner.setup()
site = web.TCPSite(self.runner, self.host, self.port)
await site.start()
socket = site._server.sockets[0]
os.write(fd, bytes(repr((socket.getsockname(), time.time())), 'utf8'))
os.close(fd)
async def ping(self):
return True
def run_daemon(self, config_options):
config = SimpleConfig(config_options)
sub = config.get('subcommand')
assert sub in [None, 'start', 'stop', 'status', 'load_wallet', 'close_wallet']
if sub in [None, 'start']:
response = "Daemon already running"
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
run_hook('load_wallet', wallet, None)
response = wallet is not None
elif sub == 'close_wallet':
path = config.get_wallet_path()
if path in self.wallets:
self.stop_wallet(path)
response = True
async def gui(self, config_options):
if self.gui_object:
if hasattr(self.gui_object, 'new_window'):
path = self.config.get_wallet_path(use_gui_last_wallet=True)
self.gui_object.new_window(path, config_options.get('url'))
response = "ok"
else:
response = False
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],
'blockchain_height': self.network.get_local_height(),
'server_height': self.network.get_server_height(),
'spv_nodes': len(self.network.get_interfaces()),
'connected': self.network.is_connected(),
'auto_connect': p[4],
'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:
response = "Daemon offline"
elif sub == 'stop':
self.stop()
response = "Daemon stopped"
return response
def run_gui(self, config_options):
config = SimpleConfig(config_options)
if self.gui:
#if hasattr(self.gui, 'new_window'):
# path = config.get_wallet_path()
# self.gui.new_window(path, config.get('url'))
# response = "ok"
#else:
# response = "error: current GUI does not support multiple windows"
response = "error: Electrum GUI already running"
response = "error: current GUI does not support multiple windows"
else:
response = "Error: Electrum is running in daemon mode. Please stop the daemon first."
return response
def load_wallet(self, path, password):
def load_wallet(self, path, password, *, manual_upgrades=True) -> Optional[Abstract_Wallet]:
path = standardize_path(path)
# wizard will be launched if we return
if path in self.wallets:
wallet = self.wallets[path]
if path in self._wallets:
wallet = self._wallets[path]
return wallet
storage = WalletStorage(path, manual_upgrades=True)
storage = WalletStorage(path)
if not storage.file_exists():
return
if storage.is_encrypted():
if not password:
return
storage.decrypt(password)
if storage.requires_split():
# read data, pass it to db
db = WalletDB(storage.read(), manual_upgrades=manual_upgrades)
if db.requires_split():
return
if storage.get_action():
if db.requires_upgrade():
return
wallet = Wallet(storage)
wallet.start_threads(self.network)
self.wallets[path] = wallet
if db.get_action():
return
wallet = Wallet(db, storage, config=self.config)
wallet.start_network(self.network)
self._wallets[path] = wallet
self.wallet = wallet
return wallet
def add_wallet(self, wallet):
def add_wallet(self, wallet: Abstract_Wallet) -> None:
path = wallet.storage.path
self.wallets[path] = wallet
path = standardize_path(path)
self._wallets[path] = wallet
def get_wallet(self, path):
return self.wallets.get(path)
def get_wallet(self, path: str) -> Abstract_Wallet:
path = standardize_path(path)
return self._wallets.get(path)
def stop_wallet(self, path):
wallet = self.wallets.pop(path)
def get_wallets(self) -> Dict[str, Abstract_Wallet]:
return dict(self._wallets) # copy
def delete_wallet(self, path: str) -> bool:
self.stop_wallet(path)
if os.path.exists(path):
os.unlink(path)
return True
return False
def stop_wallet(self, path: str) -> bool:
"""Returns True iff a wallet was found."""
path = standardize_path(path)
wallet = self._wallets.pop(path, None)
if not wallet:
return False
wallet.stop_threads()
return True
def run_cmdline(self, config_options):
password = config_options.get('password')
new_password = config_options.get('new_password')
config = SimpleConfig(config_options)
# FIXME this is ugly...
config.fee_estimates = self.network.config.fee_estimates.copy()
config.mempool_fees = self.network.config.mempool_fees.copy()
cmdname = config.get('cmd')
async def run_cmdline(self, config_options):
cmdname = config_options['cmd']
cmd = known_commands[cmdname]
if cmd.requires_wallet:
path = config.get_wallet_path()
wallet = self.wallets.get(path)
if wallet is None:
return {'error': 'Wallet "%s" is not loaded. Use "electrum daemon load_wallet"'%os.path.basename(path) }
else:
wallet = None
# arguments passed to function
args = map(lambda x: config.get(x), cmd.params)
args = [config_options.get(x) for x in cmd.params]
# decode json arguments
args = [json_decode(i) for i in args]
# options
kwargs = {}
for x in cmd.options:
kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x))
cmd_runner = Commands(config, wallet, self.network)
func = getattr(cmd_runner, cmd.name)
result = func(*args, **kwargs)
kwargs[x] = config_options.get(x)
if cmd.requires_wallet:
kwargs['wallet_path'] = config_options.get('wallet_path')
func = getattr(self.cmd_runner, cmd.name)
# fixme: not sure how to retrieve message in jsonrpcclient
try:
result = await func(*args, **kwargs)
except Exception as e:
result = {'error':str(e)}
return result
def run(self):
while self.is_running():
self.server.handle_request() if self.server else time.sleep(0.1)
for k, wallet in self.wallets.items():
wallet.stop_threads()
if self.network:
self.print_error("shutting down network")
self.network.stop()
self.network.join()
def run_daemon(self):
self.running = True
try:
while self.is_running():
time.sleep(0.1)
except KeyboardInterrupt:
self.running = False
self.on_stop()
def stop(self):
self.print_error("stopping, removing lockfile")
remove_lockfile(get_lockfile(self.config))
DaemonThread.stop(self)
def is_running(self):
with self.running_lock:
return self.running and not self.taskgroup.closed()
def init_gui(self, config, plugins):
def stop(self):
with self.running_lock:
self.running = False
def on_stop(self):
if self.gui_object:
self.gui_object.stop()
# stop network/wallets
for k, wallet in self._wallets.items():
wallet.stop_threads()
if self.network:
self.logger.info("shutting down network")
self.network.stop()
self.logger.info("stopping taskgroup")
fut = asyncio.run_coroutine_threadsafe(self.taskgroup.cancel_remaining(), self.asyncio_loop)
try:
fut.result(timeout=2)
except (asyncio.TimeoutError, asyncio.CancelledError):
pass
self.logger.info("removing lockfile")
remove_lockfile(get_lockfile(self.config))
self.logger.info("stopped")
def run_gui(self, config, plugins):
threading.current_thread().setName('GUI')
gui_name = config.get('gui', 'qt')
if gui_name in ['lite', 'classic']:
gui_name = 'qt'
self.logger.info(f'launching GUI: {gui_name}')
gui = __import__('electrum.gui.' + gui_name, fromlist=['electrum'])
self.gui = gui.ElectrumGui(config, self, plugins)
self.gui_object = gui.ElectrumGui(config, self, plugins)
try:
self.gui.main()
self.gui_object.main()
except BaseException as e:
traceback.print_exc(file=sys.stdout)
self.logger.exception('')
# app will exit now
self.on_stop()

100
electrum/dns_hacks.py Normal file
View file

@ -0,0 +1,100 @@
# Copyright (C) 2020 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
import sys
import socket
import concurrent
from concurrent import futures
import ipaddress
from typing import Optional
import dns
import dns.resolver
from .logging import get_logger
_logger = get_logger(__name__)
_dns_threads_executor = None # type: Optional[concurrent.futures.Executor]
def configure_dns_depending_on_proxy(is_proxy: bool) -> None:
# Store this somewhere so we can un-monkey-patch:
if not hasattr(socket, "_getaddrinfo"):
socket._getaddrinfo = socket.getaddrinfo
if is_proxy:
# prevent dns leaks, see http://stackoverflow.com/questions/13184205/dns-over-proxy
socket.getaddrinfo = lambda *args: [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))]
else:
if sys.platform == 'win32':
# On Windows, socket.getaddrinfo takes a mutex, and might hold it for up to 10 seconds
# when dns-resolving. To speed it up drastically, we resolve dns ourselves, outside that lock.
# See https://github.com/spesmilo/electrum/issues/4421
_prepare_windows_dns_hack()
socket.getaddrinfo = _fast_getaddrinfo
else:
socket.getaddrinfo = socket._getaddrinfo
def _prepare_windows_dns_hack():
# enable dns cache
resolver = dns.resolver.get_default_resolver()
if resolver.cache is None:
resolver.cache = dns.resolver.Cache()
# prepare threads
global _dns_threads_executor
if _dns_threads_executor is None:
_dns_threads_executor = concurrent.futures.ThreadPoolExecutor(max_workers=20,
thread_name_prefix='dns_resolver')
def _fast_getaddrinfo(host, *args, **kwargs):
def needs_dns_resolving(host):
try:
ipaddress.ip_address(host)
return False # already valid IP
except ValueError:
pass # not an IP
if str(host) in ('localhost', 'localhost.',):
return False
return True
def resolve_with_dnspython(host):
addrs = []
expected_errors = (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
concurrent.futures.CancelledError, concurrent.futures.TimeoutError)
ipv6_fut = _dns_threads_executor.submit(dns.resolver.query, host, dns.rdatatype.AAAA)
ipv4_fut = _dns_threads_executor.submit(dns.resolver.query, host, dns.rdatatype.A)
# try IPv6
try:
answers = ipv6_fut.result()
addrs += [str(answer) for answer in answers]
except expected_errors as e:
pass
except BaseException as e:
_logger.info(f'dnspython failed to resolve dns (AAAA) for {repr(host)} with error: {repr(e)}')
# try IPv4
try:
answers = ipv4_fut.result()
addrs += [str(answer) for answer in answers]
except expected_errors as e:
# dns failed for some reason, e.g. dns.resolver.NXDOMAIN this is normal.
# Simply report back failure; except if we already have some results.
if not addrs:
raise socket.gaierror(11001, 'getaddrinfo failed') from e
except BaseException as e:
# Possibly internal error in dnspython :( see #4483 and #5638
_logger.info(f'dnspython failed to resolve dns (A) for {repr(host)} with error: {repr(e)}')
if addrs:
return addrs
# Fall back to original socket.getaddrinfo to resolve dns.
return [host]
addrs = [host]
if needs_dns_resolving(host):
addrs = resolve_with_dnspython(host)
list_of_list_of_socketinfos = [socket._getaddrinfo(addr, *args, **kwargs) for addr in addrs]
list_of_socketinfos = [item for lst in list_of_list_of_socketinfos for item in lst]
return list_of_socketinfos

View file

@ -101,8 +101,8 @@ def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
keyptr = keyptr[2:]
rsa_e = keyptr[0:bytes]
rsa_n = keyptr[bytes:]
n = ecdsa.util.string_to_number(rsa_n)
e = ecdsa.util.string_to_number(rsa_e)
n = int.from_bytes(rsa_n, byteorder='big', signed=False)
e = int.from_bytes(rsa_e, byteorder='big', signed=False)
pubkey = rsakey.RSAKey(n, e)
sig = rrsig.signature
@ -110,24 +110,22 @@ def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
if rrsig.algorithm == ECDSAP256SHA256:
curve = ecdsa.curves.NIST256p
key_len = 32
digest_len = 32
elif rrsig.algorithm == ECDSAP384SHA384:
curve = ecdsa.curves.NIST384p
key_len = 48
digest_len = 48
else:
# shouldn't happen
raise ValidationFailure('unknown ECDSA curve')
keyptr = candidate_key.key
x = ecdsa.util.string_to_number(keyptr[0:key_len])
y = ecdsa.util.string_to_number(keyptr[key_len:key_len * 2])
x = int.from_bytes(keyptr[0:key_len], byteorder='big', signed=False)
y = int.from_bytes(keyptr[key_len:key_len * 2], byteorder='big', signed=False)
assert ecdsa.ecdsa.point_is_valid(curve.generator, x, y)
point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order)
verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, curve)
r = rrsig.signature[:key_len]
s = rrsig.signature[key_len:]
sig = ecdsa.ecdsa.Signature(ecdsa.util.string_to_number(r),
ecdsa.util.string_to_number(s))
sig = ecdsa.ecdsa.Signature(int.from_bytes(r, byteorder='big', signed=False),
int.from_bytes(s, byteorder='big', signed=False))
else:
raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
@ -141,7 +139,7 @@ def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
rrnamebuf = rrname.to_digestable(origin)
rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
rrsig.original_ttl)
rrlist = sorted(rdataset);
rrlist = sorted(rdataset)
for rr in rrlist:
hash.update(rrnamebuf)
hash.update(rrfixed)
@ -158,7 +156,7 @@ def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
return
elif _is_ecdsa(rrsig.algorithm):
diglong = ecdsa.util.string_to_number(digest)
diglong = int.from_bytes(digest, byteorder='big', signed=False)
if verifying_key.pubkey.verifies(diglong, sig):
return
@ -175,7 +173,10 @@ dns.dnssec.validate = dns.dnssec._validate
from .util import print_error
from .logging import get_logger
_logger = get_logger(__name__)
# hard-coded trust anchors (root KSKs)
@ -264,8 +265,7 @@ def query(url, rtype):
out = get_and_validate(ns, url, rtype)
validated = True
except BaseException as e:
#traceback.print_exc(file=sys.stderr)
print_error("DNSSEC error:", str(e))
_logger.info(f"DNSSEC error: {repr(e)}")
resolver = dns.resolver.get_default_resolver()
out = resolver.query(url, rtype)
validated = False

View file

@ -24,186 +24,153 @@
# SOFTWARE.
import base64
import hmac
import hashlib
from typing import Union
import functools
from typing import Union, Tuple, Optional
from ctypes import (
byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer,
CFUNCTYPE, POINTER, cast
)
from .util import bfh, bh2u, assert_bytes, to_bytes, InvalidPassword, profiler, randrange
from .crypto import (sha256d, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot)
from . import constants
from .logging import get_logger
from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED
_logger = get_logger(__name__)
import ecdsa
from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1
from ecdsa.curves import SECP256k1
from ecdsa.ellipticcurve import Point
from ecdsa.util import string_to_number, number_to_string
from .util import bfh, bh2u, assert_bytes, print_error, to_bytes, InvalidPassword, profiler
from .crypto import (Hash, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot)
from .ecc_fast import do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1
def string_to_number(b: bytes) -> int:
return int.from_bytes(b, byteorder='big', signed=False)
do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()
CURVE_ORDER = SECP256k1.order
def sig_string_from_der_sig(der_sig: bytes) -> bytes:
r, s = get_r_and_s_from_der_sig(der_sig)
return sig_string_from_r_and_s(r, s)
def generator():
return ECPubkey.from_point(generator_secp256k1)
def der_sig_from_sig_string(sig_string: bytes) -> bytes:
r, s = get_r_and_s_from_sig_string(sig_string)
return der_sig_from_r_and_s(r, s)
def point_at_infinity():
return ECPubkey(None)
def der_sig_from_r_and_s(r: int, s: int) -> bytes:
sig_string = (int.to_bytes(r, length=32, byteorder="big") +
int.to_bytes(s, length=32, byteorder="big"))
sig = create_string_buffer(64)
ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string)
if not ret:
raise Exception("Bad signature")
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
der_sig = create_string_buffer(80) # this much space should be enough
der_sig_size = c_size_t(len(der_sig))
ret = _libsecp256k1.secp256k1_ecdsa_signature_serialize_der(_libsecp256k1.ctx, der_sig, byref(der_sig_size), sig)
if not ret:
raise Exception("failed to serialize DER sig")
der_sig_size = der_sig_size.value
return bytes(der_sig)[:der_sig_size]
def sig_string_from_der_sig(der_sig, order=CURVE_ORDER):
r, s = ecdsa.util.sigdecode_der(der_sig, order)
return ecdsa.util.sigencode_string(r, s, order)
def der_sig_from_sig_string(sig_string, order=CURVE_ORDER):
r, s = ecdsa.util.sigdecode_string(sig_string, order)
return ecdsa.util.sigencode_der_canonize(r, s, order)
def der_sig_from_r_and_s(r, s, order=CURVE_ORDER):
return ecdsa.util.sigencode_der_canonize(r, s, order)
def get_r_and_s_from_der_sig(der_sig, order=CURVE_ORDER):
r, s = ecdsa.util.sigdecode_der(der_sig, order)
def get_r_and_s_from_der_sig(der_sig: bytes) -> Tuple[int, int]:
assert isinstance(der_sig, bytes)
sig = create_string_buffer(64)
ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_der(_libsecp256k1.ctx, sig, der_sig, len(der_sig))
if not ret:
raise Exception("Bad signature")
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
compact_signature = create_string_buffer(64)
_libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
r = int.from_bytes(compact_signature[:32], byteorder="big")
s = int.from_bytes(compact_signature[32:], byteorder="big")
return r, s
def get_r_and_s_from_sig_string(sig_string, order=CURVE_ORDER):
r, s = ecdsa.util.sigdecode_string(sig_string, order)
def get_r_and_s_from_sig_string(sig_string: bytes) -> Tuple[int, int]:
if not (isinstance(sig_string, bytes) and len(sig_string) == 64):
raise Exception("sig_string must be bytes, and 64 bytes exactly")
sig = create_string_buffer(64)
ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string)
if not ret:
raise Exception("Bad signature")
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
compact_signature = create_string_buffer(64)
_libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
r = int.from_bytes(compact_signature[:32], byteorder="big")
s = int.from_bytes(compact_signature[32:], byteorder="big")
return r, s
def sig_string_from_r_and_s(r, s, order=CURVE_ORDER):
return ecdsa.util.sigencode_string_canonize(r, s, order)
def sig_string_from_r_and_s(r: int, s: int) -> bytes:
sig_string = (int.to_bytes(r, length=32, byteorder="big") +
int.to_bytes(s, length=32, byteorder="big"))
sig = create_string_buffer(64)
ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string)
if not ret:
raise Exception("Bad signature")
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
compact_signature = create_string_buffer(64)
_libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
return bytes(compact_signature)
def point_to_ser(P, compressed=True) -> bytes:
if isinstance(P, tuple):
assert len(P) == 2, 'unexpected point: %s' % P
x, y = P
else:
x, y = P.x(), P.y()
if x is None or y is None: # infinity
return None
if compressed:
return bfh(('%02x' % (2+(y&1))) + ('%064x' % x))
return bfh('04'+('%064x' % x)+('%064x' % y))
def _x_and_y_from_pubkey_bytes(pubkey: bytes) -> Tuple[int, int]:
pubkey_ptr = create_string_buffer(64)
assert isinstance(pubkey, bytes), f'pubkey must be bytes, not {type(pubkey)}'
ret = _libsecp256k1.secp256k1_ec_pubkey_parse(
_libsecp256k1.ctx, pubkey_ptr, pubkey, len(pubkey))
if not ret:
raise InvalidECPointException('public key could not be parsed or is invalid')
def get_y_coord_from_x(x, odd=True):
curve = curve_secp256k1
_p = curve.p()
_a = curve.a()
_b = curve.b()
for offset in range(128):
Mx = x + offset
My2 = pow(Mx, 3, _p) + _a * pow(Mx, 2, _p) + _b % _p
My = pow(My2, (_p + 1) // 4, _p)
if curve.contains_point(Mx, My):
if odd == bool(My & 1):
return My
return _p - My
raise Exception('ECC_YfromX: No Y found')
def ser_to_point(ser: bytes) -> (int, int):
if ser[0] not in (0x02, 0x03, 0x04):
raise ValueError('Unexpected first byte: {}'.format(ser[0]))
if ser[0] == 0x04:
return string_to_number(ser[1:33]), string_to_number(ser[33:])
x = string_to_number(ser[1:])
return x, get_y_coord_from_x(x, ser[0] == 0x03)
def _ser_to_python_ecdsa_point(ser: bytes) -> ecdsa.ellipticcurve.Point:
x, y = ser_to_point(ser)
try:
return Point(curve_secp256k1, x, y, CURVE_ORDER)
except:
raise InvalidECPointException()
pubkey_serialized = create_string_buffer(65)
pubkey_size = c_size_t(65)
_libsecp256k1.secp256k1_ec_pubkey_serialize(
_libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey_ptr, SECP256K1_EC_UNCOMPRESSED)
pubkey_serialized = bytes(pubkey_serialized)
assert pubkey_serialized[0] == 0x04, pubkey_serialized
x = int.from_bytes(pubkey_serialized[1:33], byteorder='big', signed=False)
y = int.from_bytes(pubkey_serialized[33:65], byteorder='big', signed=False)
return x, y
class InvalidECPointException(Exception):
"""e.g. not on curve, or infinity"""
class _MyVerifyingKey(ecdsa.VerifyingKey):
@classmethod
def from_signature(klass, sig, recid, h, curve): # TODO use libsecp??
""" See http://www.secg.org/download/aid-780/sec1-v2.pdf, chapter 4.1.6 """
from ecdsa import util, numbertheory
from . import msqr
curveFp = curve.curve
G = curve.generator
order = G.order()
# extract r,s from signature
r, s = util.sigdecode_string(sig, order)
# 1.1
x = r + (recid//2) * order
# 1.3
alpha = ( x * x * x + curveFp.a() * x + curveFp.b() ) % curveFp.p()
beta = msqr.modular_sqrt(alpha, curveFp.p())
y = beta if (beta - recid) % 2 == 0 else curveFp.p() - beta
# 1.4 the constructor checks that nR is at infinity
try:
R = Point(curveFp, x, y, order)
except:
raise InvalidECPointException()
# 1.5 compute e from message:
e = string_to_number(h)
minus_e = -e % order
# 1.6 compute Q = r^-1 (sR - eG)
inv_r = numbertheory.inverse_mod(r,order)
try:
Q = inv_r * ( s * R + minus_e * G )
except:
raise InvalidECPointException()
return klass.from_public_point( Q, curve )
class _MySigningKey(ecdsa.SigningKey):
"""Enforce low S values in signatures"""
def sign_number(self, number, entropy=None, k=None):
r, s = ecdsa.SigningKey.sign_number(self, number, entropy, k)
if s > CURVE_ORDER//2:
s = CURVE_ORDER - s
return r, s
class _PubkeyForPointAtInfinity:
point = ecdsa.ellipticcurve.INFINITY
@functools.total_ordering
class ECPubkey(object):
def __init__(self, b: bytes):
def __init__(self, b: Optional[bytes]):
if b is not None:
assert_bytes(b)
point = _ser_to_python_ecdsa_point(b)
self._pubkey = ecdsa.ecdsa.Public_key(generator_secp256k1, point)
assert isinstance(b, (bytes, bytearray)), f'pubkey must be bytes-like, not {type(b)}'
if isinstance(b, bytearray):
b = bytes(b)
self._x, self._y = _x_and_y_from_pubkey_bytes(b)
else:
self._pubkey = _PubkeyForPointAtInfinity()
self._x, self._y = None, None
@classmethod
def from_sig_string(cls, sig_string: bytes, recid: int, msg_hash: bytes):
def from_sig_string(cls, sig_string: bytes, recid: int, msg_hash: bytes) -> 'ECPubkey':
assert_bytes(sig_string)
if len(sig_string) != 64:
raise Exception('Wrong encoding')
raise Exception(f'wrong encoding used for signature? len={len(sig_string)} (should be 64)')
if recid < 0 or recid > 3:
raise ValueError('recid is {}, but should be 0 <= recid <= 3'.format(recid))
ecdsa_verifying_key = _MyVerifyingKey.from_signature(sig_string, recid, msg_hash, curve=SECP256k1)
ecdsa_point = ecdsa_verifying_key.pubkey.point
return ECPubkey.from_point(ecdsa_point)
sig65 = create_string_buffer(65)
ret = _libsecp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact(
_libsecp256k1.ctx, sig65, sig_string, recid)
if not ret:
raise Exception('failed to parse signature')
pubkey = create_string_buffer(64)
ret = _libsecp256k1.secp256k1_ecdsa_recover(_libsecp256k1.ctx, pubkey, sig65, msg_hash)
if not ret:
raise InvalidECPointException('failed to recover public key')
return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey)
@classmethod
def from_signature65(cls, sig: bytes, msg_hash: bytes):
def from_signature65(cls, sig: bytes, msg_hash: bytes) -> Tuple['ECPubkey', bool]:
if len(sig) != 65:
raise Exception("Wrong encoding")
raise Exception(f'wrong encoding used for signature? len={len(sig)} (should be 65)')
nV = sig[0]
if nV < 27 or nV >= 35:
raise Exception("Bad encoding")
@ -216,25 +183,70 @@ class ECPubkey(object):
return cls.from_sig_string(sig[1:], recid, msg_hash), compressed
@classmethod
def from_point(cls, point):
_bytes = point_to_ser(point, compressed=False) # faster than compressed
def from_x_and_y(cls, x: int, y: int) -> 'ECPubkey':
_bytes = (b'\x04'
+ int.to_bytes(x, length=32, byteorder='big', signed=False)
+ int.to_bytes(y, length=32, byteorder='big', signed=False))
return ECPubkey(_bytes)
def get_public_key_bytes(self, compressed=True):
if self.is_at_infinity(): raise Exception('point is at infinity')
return point_to_ser(self.point(), compressed)
x = int.to_bytes(self.x(), length=32, byteorder='big', signed=False)
y = int.to_bytes(self.y(), length=32, byteorder='big', signed=False)
if compressed:
header = b'\x03' if self.y() & 1 else b'\x02'
return header + x
else:
header = b'\x04'
return header + x + y
def get_public_key_hex(self, compressed=True):
return bh2u(self.get_public_key_bytes(compressed))
def point(self) -> (int, int):
return self._pubkey.point.x(), self._pubkey.point.y()
def point(self) -> Tuple[int, int]:
return self.x(), self.y()
def x(self) -> int:
return self._x
def y(self) -> int:
return self._y
def _to_libsecp256k1_pubkey_ptr(self):
pubkey = create_string_buffer(64)
public_pair_bytes = self.get_public_key_bytes(compressed=False)
ret = _libsecp256k1.secp256k1_ec_pubkey_parse(
_libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes))
if not ret:
raise Exception('public key could not be parsed or is invalid')
return pubkey
@classmethod
def _from_libsecp256k1_pubkey_ptr(cls, pubkey) -> 'ECPubkey':
pubkey_serialized = create_string_buffer(65)
pubkey_size = c_size_t(65)
_libsecp256k1.secp256k1_ec_pubkey_serialize(
_libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey, SECP256K1_EC_UNCOMPRESSED)
return ECPubkey(bytes(pubkey_serialized))
def __repr__(self):
if self.is_at_infinity():
return f"<ECPubkey infinity>"
return f"<ECPubkey {self.get_public_key_hex()}>"
def __mul__(self, other: int):
if not isinstance(other, int):
raise TypeError('multiplication not defined for ECPubkey and {}'.format(type(other)))
ecdsa_point = self._pubkey.point * other
return self.from_point(ecdsa_point)
other %= CURVE_ORDER
if self.is_at_infinity() or other == 0:
return POINT_AT_INFINITY
pubkey = self._to_libsecp256k1_pubkey_ptr()
ret = _libsecp256k1.secp256k1_ec_pubkey_tweak_mul(_libsecp256k1.ctx, pubkey, other.to_bytes(32, byteorder="big"))
if not ret:
return POINT_AT_INFINITY
return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey)
def __rmul__(self, other: int):
return self * other
@ -242,19 +254,40 @@ class ECPubkey(object):
def __add__(self, other):
if not isinstance(other, ECPubkey):
raise TypeError('addition not defined for ECPubkey and {}'.format(type(other)))
ecdsa_point = self._pubkey.point + other._pubkey.point
return self.from_point(ecdsa_point)
if self.is_at_infinity(): return other
if other.is_at_infinity(): return self
def __eq__(self, other):
return self._pubkey.point.x() == other._pubkey.point.x() \
and self._pubkey.point.y() == other._pubkey.point.y()
pubkey1 = self._to_libsecp256k1_pubkey_ptr()
pubkey2 = other._to_libsecp256k1_pubkey_ptr()
pubkey_sum = create_string_buffer(64)
pubkey1 = cast(pubkey1, c_char_p)
pubkey2 = cast(pubkey2, c_char_p)
array_of_pubkey_ptrs = (c_char_p * 2)(pubkey1, pubkey2)
ret = _libsecp256k1.secp256k1_ec_pubkey_combine(_libsecp256k1.ctx, pubkey_sum, array_of_pubkey_ptrs, 2)
if not ret:
return POINT_AT_INFINITY
return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey_sum)
def __eq__(self, other) -> bool:
if not isinstance(other, ECPubkey):
return False
return self.point() == other.point()
def __ne__(self, other):
return not (self == other)
def verify_message_for_address(self, sig65: bytes, message: bytes) -> None:
def __hash__(self):
return hash(self.point())
def __lt__(self, other):
if not isinstance(other, ECPubkey):
raise TypeError('comparison not defined for ECPubkey and {}'.format(type(other)))
return (self.x() or 0) < (other.x() or 0)
def verify_message_for_address(self, sig65: bytes, message: bytes, algo=lambda x: sha256d(msg_magic(x))) -> None:
assert_bytes(message)
h = Hash(msg_magic(message))
h = algo(message)
public_key, compressed = self.from_signature65(sig65, h)
# check public key
if public_key != self:
@ -262,23 +295,31 @@ class ECPubkey(object):
# check message
self.verify_message_hash(sig65[1:], h)
# TODO return bool instead of raising
def verify_message_hash(self, sig_string: bytes, msg_hash: bytes) -> None:
assert_bytes(sig_string)
if len(sig_string) != 64:
raise Exception('Wrong encoding')
ecdsa_point = self._pubkey.point
verifying_key = _MyVerifyingKey.from_public_point(ecdsa_point, curve=SECP256k1)
verifying_key.verify_digest(sig_string, msg_hash, sigdecode=ecdsa.util.sigdecode_string)
raise Exception(f'wrong encoding used for signature? len={len(sig_string)} (should be 64)')
if not (isinstance(msg_hash, bytes) and len(msg_hash) == 32):
raise Exception("msg_hash must be bytes, and 32 bytes exactly")
def encrypt_message(self, message: bytes, magic: bytes = b'BIE1'):
sig = create_string_buffer(64)
ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string)
if not ret:
raise Exception("Bad signature")
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
pubkey = self._to_libsecp256k1_pubkey_ptr()
if 1 != _libsecp256k1.secp256k1_ecdsa_verify(_libsecp256k1.ctx, sig, msg_hash, pubkey):
raise Exception("Bad signature")
def encrypt_message(self, message: bytes, magic: bytes = b'BIE1') -> bytes:
"""
ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac
"""
assert_bytes(message)
randint = ecdsa.util.randrange(CURVE_ORDER)
ephemeral_exponent = number_to_string(randint, CURVE_ORDER)
ephemeral = ECPrivkey(ephemeral_exponent)
ephemeral = ECPrivkey.generate_random_key()
ecdh_key = (self * ephemeral.secret_scalar).get_public_key_bytes(compressed=True)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
@ -294,25 +335,47 @@ class ECPubkey(object):
return CURVE_ORDER
def is_at_infinity(self):
return self == point_at_infinity()
return self == POINT_AT_INFINITY
@classmethod
def is_pubkey_bytes(cls, b: bytes):
try:
ECPubkey(b)
return True
except:
return False
GENERATOR = ECPubkey(bytes.fromhex('0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
'483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8'))
CURVE_ORDER = 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141
POINT_AT_INFINITY = ECPubkey(None)
def msg_magic(message: bytes) -> bytes:
from .bitcoin import var_int
length = bfh(var_int(len(message)))
return b"\x18Bitcoin Signed Message:\n" + length + message
return b"\x18LBRYcrd Signed Message:\n" + length + message
def verify_message_with_address(address: str, sig65: bytes, message: bytes):
def verify_signature(pubkey: bytes, sig: bytes, h: bytes) -> bool:
try:
ECPubkey(pubkey).verify_message_hash(sig, h)
except:
return False
return True
def verify_message_with_address(address: str, sig65: bytes, message: bytes, *, net=None):
from .bitcoin import pubkey_to_address
assert_bytes(sig65, message)
if net is None: net = constants.net
try:
h = Hash(msg_magic(message))
h = sha256d(msg_magic(message))
public_key, compressed = ECPubkey.from_signature65(sig65, h)
# check public key using the address
pubkey_hex = public_key.get_public_key_hex(compressed)
for txin_type in ['p2pkh','p2wpkh','p2wpkh-p2sh']:
addr = pubkey_to_address(txin_type, pubkey_hex)
addr = pubkey_to_address(txin_type, pubkey_hex, net=net)
if address == addr:
break
else:
@ -321,7 +384,7 @@ def verify_message_with_address(address: str, sig65: bytes, message: bytes):
public_key.verify_message_hash(sig65[1:], h)
return True
except Exception as e:
print_error("Verification error: {0}".format(e))
_logger.info(f"Verification error: {repr(e)}")
return False
@ -342,13 +405,12 @@ class ECPrivkey(ECPubkey):
raise InvalidECPointException('Invalid secret scalar (not within curve order)')
self.secret_scalar = secret
point = generator_secp256k1 * secret
super().__init__(point_to_ser(point))
self._privkey = ecdsa.ecdsa.Private_key(self._pubkey, secret)
pubkey = GENERATOR * secret
super().__init__(pubkey.get_public_key_bytes(compressed=False))
@classmethod
def from_secret_scalar(cls, secret_scalar: int):
secret_bytes = number_to_string(secret_scalar, CURVE_ORDER)
secret_bytes = int.to_bytes(secret_scalar, length=32, byteorder='big', signed=False)
return ECPrivkey(secret_bytes)
@classmethod
@ -364,32 +426,64 @@ class ECPrivkey(ECPubkey):
scalar = string_to_number(privkey_bytes) % CURVE_ORDER
if scalar == 0:
raise Exception('invalid EC private key scalar: zero')
privkey_32bytes = number_to_string(scalar, CURVE_ORDER)
privkey_32bytes = int.to_bytes(scalar, length=32, byteorder='big', signed=False)
return privkey_32bytes
def sign(self, data: bytes, sigencode=None, sigdecode=None) -> bytes:
def __repr__(self):
return f"<ECPrivkey {self.get_public_key_hex()}>"
@classmethod
def generate_random_key(cls):
randint = randrange(CURVE_ORDER)
ephemeral_exponent = int.to_bytes(randint, length=32, byteorder='big', signed=False)
return ECPrivkey(ephemeral_exponent)
def get_secret_bytes(self) -> bytes:
return int.to_bytes(self.secret_scalar, length=32, byteorder='big', signed=False)
def sign(self, msg_hash: bytes, sigencode=None) -> bytes:
if not (isinstance(msg_hash, bytes) and len(msg_hash) == 32):
raise Exception("msg_hash to be signed must be bytes, and 32 bytes exactly")
if sigencode is None:
sigencode = sig_string_from_r_and_s
if sigdecode is None:
sigdecode = get_r_and_s_from_sig_string
private_key = _MySigningKey.from_secret_exponent(self.secret_scalar, curve=SECP256k1)
sig = private_key.sign_digest_deterministic(data, hashfunc=hashlib.sha256, sigencode=sigencode)
public_key = private_key.get_verifying_key()
if not public_key.verify_digest(sig, data, sigdecode=sigdecode):
raise Exception('Sanity check verifying our own signature failed.')
privkey_bytes = self.secret_scalar.to_bytes(32, byteorder="big")
nonce_function = None
sig = create_string_buffer(64)
def sign_with_extra_entropy(extra_entropy):
ret = _libsecp256k1.secp256k1_ecdsa_sign(
_libsecp256k1.ctx, sig, msg_hash, privkey_bytes,
nonce_function, extra_entropy)
if not ret:
raise Exception('the nonce generation function failed, or the private key was invalid')
compact_signature = create_string_buffer(64)
_libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
r = int.from_bytes(compact_signature[:32], byteorder="big")
s = int.from_bytes(compact_signature[32:], byteorder="big")
return r, s
r, s = sign_with_extra_entropy(extra_entropy=None)
counter = 0
while r >= 2**255: # grind for low R value https://github.com/bitcoin/bitcoin/pull/13666
counter += 1
extra_entropy = counter.to_bytes(32, byteorder="little")
r, s = sign_with_extra_entropy(extra_entropy=extra_entropy)
sig_string = sig_string_from_r_and_s(r, s)
self.verify_message_hash(sig_string, msg_hash)
sig = sigencode(r, s)
return sig
def sign_transaction(self, hashed_preimage: bytes) -> bytes:
return self.sign(hashed_preimage,
sigencode=der_sig_from_r_and_s,
sigdecode=get_r_and_s_from_der_sig)
return self.sign(hashed_preimage, sigencode=der_sig_from_r_and_s)
def sign_message(self, message: bytes, is_compressed: bool) -> bytes:
def sign_message(self, message: bytes, is_compressed: bool, algo=lambda x: sha256d(msg_magic(x))) -> bytes:
def bruteforce_recid(sig_string):
for recid in range(4):
sig65 = construct_sig65(sig_string, recid, is_compressed)
try:
self.verify_message_for_address(sig65, message)
self.verify_message_for_address(sig65, message, algo)
return sig65, recid
except Exception as e:
continue
@ -397,15 +491,13 @@ class ECPrivkey(ECPubkey):
raise Exception("error: cannot sign message. no recid fits..")
message = to_bytes(message, 'utf8')
msg_hash = Hash(msg_magic(message))
sig_string = self.sign(msg_hash,
sigencode=sig_string_from_r_and_s,
sigdecode=get_r_and_s_from_sig_string)
msg_hash = algo(message)
sig_string = self.sign(msg_hash, sigencode=sig_string_from_r_and_s)
sig65, recid = bruteforce_recid(sig_string)
return sig65
def decrypt_message(self, encrypted, magic=b'BIE1'):
encrypted = base64.b64decode(encrypted)
def decrypt_message(self, encrypted: Union[str, bytes], magic: bytes=b'BIE1') -> bytes:
encrypted = base64.b64decode(encrypted) # type: bytes
if len(encrypted) < 85:
raise Exception('invalid ciphertext: length')
magic_found = encrypted[:4]
@ -415,12 +507,9 @@ class ECPrivkey(ECPubkey):
if magic_found != magic:
raise Exception('invalid ciphertext: invalid magic bytes')
try:
ecdsa_point = _ser_to_python_ecdsa_point(ephemeral_pubkey_bytes)
except AssertionError as e:
ephemeral_pubkey = ECPubkey(ephemeral_pubkey_bytes)
except InvalidECPointException as e:
raise Exception('invalid ciphertext: invalid ephemeral pubkey') from e
if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, ecdsa_point.x(), ecdsa_point.y()):
raise Exception('invalid ciphertext: invalid ephemeral pubkey')
ephemeral_pubkey = ECPubkey.from_point(ecdsa_point)
ecdh_key = (ephemeral_pubkey * self.secret_scalar).get_public_key_bytes(compressed=True)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
@ -429,6 +518,6 @@ class ECPrivkey(ECPubkey):
return aes_decrypt_with_iv(key_e, iv, ciphertext)
def construct_sig65(sig_string, recid, is_compressed):
def construct_sig65(sig_string: bytes, recid: int, is_compressed: bool) -> bytes:
comp = 4 if is_compressed else 0
return bytes([27 + recid + comp]) + sig_string

View file

@ -5,14 +5,15 @@ import os
import sys
import traceback
import ctypes
from ctypes.util import find_library
from ctypes import (
byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer, CFUNCTYPE, POINTER
byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer,
CFUNCTYPE, POINTER, cast
)
import ecdsa
from .logging import get_logger
from .util import print_stderr, print_error
_logger = get_logger(__name__)
SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1)
@ -32,19 +33,32 @@ SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BI
SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION)
class LibModuleMissing(Exception): pass
def load_library():
if sys.platform == 'darwin':
library_path = 'libsecp256k1.0.dylib'
library_paths = (os.path.join(os.path.dirname(__file__), 'libsecp256k1.0.dylib'),
'libsecp256k1.0.dylib')
elif sys.platform in ('windows', 'win32'):
library_path = 'libsecp256k1.dll'
library_paths = (os.path.join(os.path.dirname(__file__), 'libsecp256k1-0.dll'),
'libsecp256k1-0.dll')
elif 'ANDROID_DATA' in os.environ:
library_path = 'libsecp256k1.so'
else:
library_path = 'libsecp256k1.so.0'
library_paths = ('libsecp256k1.so',)
else: # desktop Linux and similar
library_paths = (os.path.join(os.path.dirname(__file__), 'libsecp256k1.so.0'),
'libsecp256k1.so.0')
secp256k1 = ctypes.cdll.LoadLibrary(library_path)
secp256k1 = None
for libpath in library_paths:
try:
secp256k1 = ctypes.cdll.LoadLibrary(libpath)
except:
pass
else:
break
if not secp256k1:
print_stderr('[ecc] warning: libsecp256k1 library failed to load')
_logger.error('libsecp256k1 library failed to load')
return None
try:
@ -78,146 +92,48 @@ def load_library():
secp256k1.secp256k1_ecdsa_signature_serialize_compact.argtypes = [c_void_p, c_char_p, c_char_p]
secp256k1.secp256k1_ecdsa_signature_serialize_compact.restype = c_int
secp256k1.secp256k1_ecdsa_signature_parse_der.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t]
secp256k1.secp256k1_ecdsa_signature_parse_der.restype = c_int
secp256k1.secp256k1_ecdsa_signature_serialize_der.argtypes = [c_void_p, c_char_p, c_void_p, c_char_p]
secp256k1.secp256k1_ecdsa_signature_serialize_der.restype = c_int
secp256k1.secp256k1_ec_pubkey_tweak_mul.argtypes = [c_void_p, c_char_p, c_char_p]
secp256k1.secp256k1_ec_pubkey_tweak_mul.restype = c_int
secp256k1.secp256k1_ec_pubkey_combine.argtypes = [c_void_p, c_char_p, c_void_p, c_size_t]
secp256k1.secp256k1_ec_pubkey_combine.restype = c_int
# --enable-module-recovery
try:
secp256k1.secp256k1_ecdsa_recover.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p]
secp256k1.secp256k1_ecdsa_recover.restype = c_int
secp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact.argtypes = [c_void_p, c_char_p, c_char_p, c_int]
secp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact.restype = c_int
except (OSError, AttributeError):
raise LibModuleMissing('libsecp256k1 library found but it was built '
'without required module (--enable-module-recovery)')
secp256k1.ctx = secp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)
r = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32))
if r:
return secp256k1
else:
print_stderr('[ecc] warning: secp256k1_context_randomize failed')
ret = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32))
if not ret:
_logger.error('secp256k1_context_randomize failed')
return None
except (OSError, AttributeError):
#traceback.print_exc(file=sys.stderr)
print_stderr('[ecc] warning: libsecp256k1 library was found and loaded but there was an error when using it')
return secp256k1
except (OSError, AttributeError) as e:
_logger.error(f'libsecp256k1 library was found and loaded but there was an error when using it: {repr(e)}')
return None
class _patched_functions:
prepared_to_patch = False
monkey_patching_active = False
def _prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
if not _libsecp256k1:
return
# save original functions so that we can undo patching (needed for tests)
_patched_functions.orig_sign = staticmethod(ecdsa.ecdsa.Private_key.sign)
_patched_functions.orig_verify = staticmethod(ecdsa.ecdsa.Public_key.verifies)
_patched_functions.orig_mul = staticmethod(ecdsa.ellipticcurve.Point.__mul__)
curve_secp256k1 = ecdsa.ecdsa.curve_secp256k1
curve_order = ecdsa.curves.SECP256k1.order
point_at_infinity = ecdsa.ellipticcurve.INFINITY
def mul(self: ecdsa.ellipticcurve.Point, other: int):
if self.curve() != curve_secp256k1:
# this operation is not on the secp256k1 curve; use original implementation
return _patched_functions.orig_mul(self, other)
other %= curve_order
if self == point_at_infinity or other == 0:
return point_at_infinity
pubkey = create_string_buffer(64)
public_pair_bytes = b'\4' + self.x().to_bytes(32, byteorder="big") + self.y().to_bytes(32, byteorder="big")
r = _libsecp256k1.secp256k1_ec_pubkey_parse(
_libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes))
if not r:
return False
r = _libsecp256k1.secp256k1_ec_pubkey_tweak_mul(_libsecp256k1.ctx, pubkey, other.to_bytes(32, byteorder="big"))
if not r:
return point_at_infinity
pubkey_serialized = create_string_buffer(65)
pubkey_size = c_size_t(65)
_libsecp256k1.secp256k1_ec_pubkey_serialize(
_libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey, SECP256K1_EC_UNCOMPRESSED)
x = int.from_bytes(pubkey_serialized[1:33], byteorder="big")
y = int.from_bytes(pubkey_serialized[33:], byteorder="big")
return ecdsa.ellipticcurve.Point(curve_secp256k1, x, y, curve_order)
def sign(self: ecdsa.ecdsa.Private_key, hash: int, random_k: int):
# note: random_k is ignored
if self.public_key.curve != curve_secp256k1:
# this operation is not on the secp256k1 curve; use original implementation
return _patched_functions.orig_sign(self, hash, random_k)
secret_exponent = self.secret_multiplier
nonce_function = None
sig = create_string_buffer(64)
sig_hash_bytes = hash.to_bytes(32, byteorder="big")
_libsecp256k1.secp256k1_ecdsa_sign(
_libsecp256k1.ctx, sig, sig_hash_bytes, secret_exponent.to_bytes(32, byteorder="big"), nonce_function, None)
compact_signature = create_string_buffer(64)
_libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
r = int.from_bytes(compact_signature[:32], byteorder="big")
s = int.from_bytes(compact_signature[32:], byteorder="big")
return ecdsa.ecdsa.Signature(r, s)
def verify(self: ecdsa.ecdsa.Public_key, hash: int, signature: ecdsa.ecdsa.Signature):
if self.curve != curve_secp256k1:
# this operation is not on the secp256k1 curve; use original implementation
return _patched_functions.orig_verify(self, hash, signature)
sig = create_string_buffer(64)
input64 = signature.r.to_bytes(32, byteorder="big") + signature.s.to_bytes(32, byteorder="big")
r = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, input64)
if not r:
return False
r = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
public_pair_bytes = b'\4' + self.point.x().to_bytes(32, byteorder="big") + self.point.y().to_bytes(32, byteorder="big")
pubkey = create_string_buffer(64)
r = _libsecp256k1.secp256k1_ec_pubkey_parse(
_libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes))
if not r:
return False
return 1 == _libsecp256k1.secp256k1_ecdsa_verify(_libsecp256k1.ctx, sig, hash.to_bytes(32, byteorder="big"), pubkey)
# save new functions so that we can (re-)do patching
_patched_functions.fast_sign = sign
_patched_functions.fast_verify = verify
_patched_functions.fast_mul = mul
_patched_functions.prepared_to_patch = True
def do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
if not _libsecp256k1:
# FIXME print_error will always print as 'verbosity' is not yet initialised
print_error('[ecc] info: libsecp256k1 library not available, falling back to python-ecdsa. '
'This means signing operations will be slower.')
return
if not _patched_functions.prepared_to_patch:
raise Exception("can't patch python-ecdsa without preparations")
ecdsa.ecdsa.Private_key.sign = _patched_functions.fast_sign
ecdsa.ecdsa.Public_key.verifies = _patched_functions.fast_verify
ecdsa.ellipticcurve.Point.__mul__ = _patched_functions.fast_mul
# ecdsa.ellipticcurve.Point.__add__ = ... # TODO??
_patched_functions.monkey_patching_active = True
def undo_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
if not _libsecp256k1:
return
if not _patched_functions.prepared_to_patch:
raise Exception("can't patch python-ecdsa without preparations")
ecdsa.ecdsa.Private_key.sign = _patched_functions.orig_sign
ecdsa.ecdsa.Public_key.verifies = _patched_functions.orig_verify
ecdsa.ellipticcurve.Point.__mul__ = _patched_functions.orig_mul
_patched_functions.monkey_patching_active = False
def is_using_fast_ecc():
return _patched_functions.monkey_patching_active
_libsecp256k1 = None
try:
_libsecp256k1 = load_library()
except:
_libsecp256k1 = None
#traceback.print_exc(file=sys.stderr)
except BaseException as e:
_logger.error(f'failed to load libsecp256k1: {repr(e)}')
_prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()
if _libsecp256k1 is None:
# hard fail:
sys.exit(f"Error: Failed to load libsecp256k1.")

View file

@ -1,18 +1,29 @@
import asyncio
from datetime import datetime
import inspect
import requests
import sys
import os
import json
from threading import Thread
import time
import csv
import decimal
from decimal import Decimal
from typing import Sequence, Optional
from aiorpcx.curio import timeout_after, TaskTimeout, TaskGroup
from .bitcoin import COIN
from .i18n import _
from .util import PrintError, ThreadJob, make_dir
from .util import (ThreadJob, make_dir, log_exceptions,
make_aiohttp_session, resource_path)
from .network import Network
from .simple_config import SimpleConfig
from .logging import Logger
DEFAULT_ENABLED = False
DEFAULT_CURRENCY = "EUR"
DEFAULT_EXCHANGE = "CoinGecko" # default exchange should ideally provide historical rates
# See https://en.wikipedia.org/wiki/ISO_4217
@ -24,67 +35,82 @@ CCY_PRECISIONS = {'BHD': 3, 'BIF': 0, 'BYR': 0, 'CLF': 4, 'CLP': 0,
'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0}
class ExchangeBase(PrintError):
class ExchangeBase(Logger):
def __init__(self, on_quotes, on_history):
Logger.__init__(self)
self.history = {}
self.quotes = {}
self.on_quotes = on_quotes
self.on_history = on_history
def get_json(self, site, get_string):
async def get_raw(self, site, get_string):
# APIs must have https
url = ''.join(['https://', site, get_string])
response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'}, timeout=10)
return response.json()
network = Network.get_instance()
proxy = network.proxy if network else None
async with make_aiohttp_session(proxy) as session:
async with session.get(url) as response:
response.raise_for_status()
return await response.text()
def get_csv(self, site, get_string):
async def get_json(self, site, get_string):
# APIs must have https
url = ''.join(['https://', site, get_string])
response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'})
reader = csv.DictReader(response.content.decode().split('\n'))
network = Network.get_instance()
proxy = network.proxy if network else None
async with make_aiohttp_session(proxy) as session:
async with session.get(url) as response:
response.raise_for_status()
# set content_type to None to disable checking MIME type
return await response.json(content_type=None)
async def get_csv(self, site, get_string):
raw = await self.get_raw(site, get_string)
reader = csv.DictReader(raw.split('\n'))
return list(reader)
def name(self):
return self.__class__.__name__
def update_safe(self, ccy):
async def update_safe(self, ccy):
try:
self.print_error("getting fx quotes for", ccy)
self.quotes = self.get_rates(ccy)
self.print_error("received fx quotes")
self.logger.info(f"getting fx quotes for {ccy}")
self.quotes = await self.get_rates(ccy)
self.logger.info("received fx quotes")
except asyncio.CancelledError:
# CancelledError must be passed-through for cancellation to work
raise
except BaseException as e:
self.print_error("failed fx quotes:", e)
self.logger.info(f"failed fx quotes: {repr(e)}")
self.quotes = {}
self.on_quotes()
def update(self, ccy):
t = Thread(target=self.update_safe, args=(ccy,))
t.setDaemon(True)
t.start()
def read_historical_rates(self, ccy, cache_dir):
def read_historical_rates(self, ccy, cache_dir) -> Optional[dict]:
filename = os.path.join(cache_dir, self.name() + '_'+ ccy)
if os.path.exists(filename):
timestamp = os.stat(filename).st_mtime
try:
with open(filename, 'r', encoding='utf-8') as f:
h = json.loads(f.read())
h['timestamp'] = timestamp
except:
h = None
else:
h = None
if h:
self.history[ccy] = h
self.on_history()
if not os.path.exists(filename):
return None
timestamp = os.stat(filename).st_mtime
try:
with open(filename, 'r', encoding='utf-8') as f:
h = json.loads(f.read())
except:
return None
if not h: # e.g. empty dict
return None
h['timestamp'] = timestamp
self.history[ccy] = h
self.on_history()
return h
def get_historical_rates_safe(self, ccy, cache_dir):
@log_exceptions
async def get_historical_rates_safe(self, ccy, cache_dir):
try:
self.print_error("requesting fx history for", ccy)
h = self.request_history(ccy)
self.print_error("received fx history for", ccy)
self.logger.info(f"requesting fx history for {ccy}")
h = await self.request_history(ccy)
self.logger.info(f"received fx history for {ccy}")
except BaseException as e:
self.print_error("failed fx history:", e)
self.logger.info(f"failed fx history: {repr(e)}")
return
filename = os.path.join(cache_dir, self.name() + '_' + ccy)
with open(filename, 'w', encoding='utf-8') as f:
@ -100,9 +126,7 @@ class ExchangeBase(PrintError):
if h is None:
h = self.read_historical_rates(ccy, cache_dir)
if h is None or h['timestamp'] < time.time() - 24*3600:
t = Thread(target=self.get_historical_rates_safe, args=(ccy, cache_dir))
t.setDaemon(True)
t.start()
asyncio.get_event_loop().create_task(self.get_historical_rates_safe(ccy, cache_dir))
def history_ccys(self):
return []
@ -110,43 +134,38 @@ class ExchangeBase(PrintError):
def historical_rate(self, ccy, d_t):
return self.history.get(ccy, {}).get(d_t.strftime('%Y-%m-%d'), 'NaN')
def get_currencies(self):
rates = self.get_rates('')
async def request_history(self, ccy):
raise NotImplementedError() # implemented by subclasses
async def get_rates(self, ccy):
raise NotImplementedError() # implemented by subclasses
async def get_currencies(self):
rates = await self.get_rates('')
return sorted([str(a) for (a, b) in rates.items() if b is not None and len(a)==3])
class BitcoinAverage(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('apiv2.bitcoinaverage.com', '/indices/global/ticker/short')
class BitcoinAverage(ExchangeBase):
# note: historical rates used to be freely available
# but this is no longer the case. see #5188
async def get_rates(self, ccy):
json = await self.get_json('apiv2.bitcoinaverage.com', '/indices/global/ticker/short')
return dict([(r.replace("BTC", ""), Decimal(json[r]['last']))
for r in json if r != 'timestamp'])
def history_ccys(self):
return ['AUD', 'BRL', 'CAD', 'CHF', 'CNY', 'EUR', 'GBP', 'IDR', 'ILS',
'MXN', 'NOK', 'NZD', 'PLN', 'RON', 'RUB', 'SEK', 'SGD', 'USD',
'ZAR']
def request_history(self, ccy):
history = self.get_csv('apiv2.bitcoinaverage.com',
"/indices/global/history/BTC%s?period=alltime&format=csv" % ccy)
return dict([(h['DateTime'][:10], h['Average'])
for h in history])
class Bitcointoyou(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('bitcointoyou.com', "/API/ticker.aspx")
async def get_rates(self, ccy):
json = await self.get_json('bitcointoyou.com', "/API/ticker.aspx")
return {'BRL': Decimal(json['ticker']['last'])}
def history_ccys(self):
return ['BRL']
class BitcoinVenezuela(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.bitcoinvenezuela.com', '/')
async def get_rates(self, ccy):
json = await self.get_json('api.bitcoinvenezuela.com', '/')
rates = [(r, json['BTC'][r]) for r in json['BTC']
if json['BTC'][r] is not None] # Giving NULL for LTC
return dict(rates)
@ -154,99 +173,108 @@ class BitcoinVenezuela(ExchangeBase):
def history_ccys(self):
return ['ARS', 'EUR', 'USD', 'VEF']
def request_history(self, ccy):
return self.get_json('api.bitcoinvenezuela.com',
"/historical/index.php?coin=BTC")[ccy +'_BTC']
async def request_history(self, ccy):
json = await self.get_json('api.bitcoinvenezuela.com',
"/historical/index.php?coin=BTC")
return json[ccy +'_BTC']
class Bitbank(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('public.bitbank.cc', '/btc_jpy/ticker')
async def get_rates(self, ccy):
json = await self.get_json('public.bitbank.cc', '/btc_jpy/ticker')
return {'JPY': Decimal(json['data']['last'])}
class BitFlyer(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('bitflyer.jp', '/api/echo/price')
async def get_rates(self, ccy):
json = await self.get_json('bitflyer.jp', '/api/echo/price')
return {'JPY': Decimal(json['mid'])}
class Bitmarket(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('www.bitmarket.pl', '/json/BTCPLN/ticker.json')
return {'PLN': Decimal(json['last'])}
class BitPay(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('bitpay.com', '/api/rates')
async def get_rates(self, ccy):
json = await self.get_json('bitpay.com', '/api/rates')
return dict([(r['code'], Decimal(r['rate'])) for r in json])
class Bitso(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.bitso.com', '/v2/ticker')
async def get_rates(self, ccy):
json = await self.get_json('api.bitso.com', '/v2/ticker')
return {'MXN': Decimal(json['last'])}
class BitStamp(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('www.bitstamp.net', '/api/ticker/')
return {'USD': Decimal(json['last'])}
async def get_currencies(self):
return ['USD', 'EUR']
async def get_rates(self, ccy):
if ccy in CURRENCIES[self.name()]:
json = await self.get_json('www.bitstamp.net', f'/api/v2/ticker/btc{ccy.lower()}/')
return {ccy: Decimal(json['last'])}
return {}
class Bitvalor(ExchangeBase):
def get_rates(self,ccy):
json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
async def get_rates(self,ccy):
json = await self.get_json('api.bitvalor.com', '/v1/ticker.json')
return {'BRL': Decimal(json['ticker_1h']['total']['last'])}
class BlockchainInfo(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('blockchain.info', '/ticker')
async def get_rates(self, ccy):
json = await self.get_json('blockchain.info', '/ticker')
return dict([(r, Decimal(json[r]['15m'])) for r in json])
class BTCChina(ExchangeBase):
class Bylls(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('data.btcchina.com', '/data/ticker')
return {'CNY': Decimal(json['ticker']['last'])}
class BTCParalelo(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('btcparalelo.com', '/api/price')
return {'VEF': Decimal(json['price'])}
async def get_rates(self, ccy):
json = await self.get_json('bylls.com', '/api/price?from_currency=BTC&to_currency=CAD')
return {'CAD': Decimal(json['public_price']['to_price'])}
class Coinbase(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('coinbase.com',
'/api/v1/currencies/exchange_rates')
return dict([(r[7:].upper(), Decimal(json[r]))
for r in json if r.startswith('btc_to_')])
async def get_rates(self, ccy):
json = await self.get_json('api.coinbase.com',
'/v2/exchange-rates?currency=BTC')
return {ccy: Decimal(rate) for (ccy, rate) in json["data"]["rates"].items()}
class CoinCap(ExchangeBase):
async def get_rates(self, ccy):
json = await self.get_json('api.coincap.io', '/v2/rates/bitcoin/')
return {'USD': Decimal(json['data']['rateUsd'])}
def history_ccys(self):
return ['USD']
async def request_history(self, ccy):
# Currently 2000 days is the maximum in 1 API call
# (and history starts on 2017-03-23)
history = await self.get_json('api.coincap.io',
'/v2/assets/bitcoin/history?interval=d1&limit=2000')
return dict([(datetime.utcfromtimestamp(h['time']/1000).strftime('%Y-%m-%d'), h['priceUsd'])
for h in history['data']])
class CoinDesk(ExchangeBase):
def get_currencies(self):
dicts = self.get_json('api.coindesk.com',
async def get_currencies(self):
dicts = await self.get_json('api.coindesk.com',
'/v1/bpi/supported-currencies.json')
return [d['currency'] for d in dicts]
def get_rates(self, ccy):
json = self.get_json('api.coindesk.com',
async def get_rates(self, ccy):
json = await self.get_json('api.coindesk.com',
'/v1/bpi/currentprice/%s.json' % ccy)
result = {ccy: Decimal(json['bpi'][ccy]['rate_float'])}
return result
@ -257,35 +285,40 @@ class CoinDesk(ExchangeBase):
def history_ccys(self):
return self.history_starts().keys()
def request_history(self, ccy):
async def request_history(self, ccy):
start = self.history_starts()[ccy]
end = datetime.today().strftime('%Y-%m-%d')
# Note ?currency and ?index don't work as documented. Sigh.
query = ('/v1/bpi/historical/close.json?start=%s&end=%s'
% (start, end))
json = self.get_json('api.coindesk.com', query)
json = await self.get_json('api.coindesk.com', query)
return json['bpi']
class Coinsecure(ExchangeBase):
class CoinGecko(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.coinsecure.in', '/v0/noauth/newticker')
return {'INR': Decimal(json['lastprice'] / 100.0 )}
async def get_rates(self, ccy):
json = await self.get_json('api.coingecko.com', '/api/v3/exchange_rates')
return dict([(ccy.upper(), Decimal(d['value']))
for ccy, d in json['rates'].items()])
def history_ccys(self):
# CoinGecko seems to have historical data for all ccys it supports
return CURRENCIES[self.name()]
class Foxbit(ExchangeBase):
async def request_history(self, ccy):
history = await self.get_json('api.coingecko.com',
'/api/v3/coins/bitcoin/market_chart?vs_currency=%s&days=max' % ccy)
def get_rates(self,ccy):
json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
return {'BRL': Decimal(json['ticker_1h']['exchanges']['FOX']['last'])}
return dict([(datetime.utcfromtimestamp(h[0]/1000).strftime('%Y-%m-%d'), h[1])
for h in history['prices']])
class itBit(ExchangeBase):
def get_rates(self, ccy):
async def get_rates(self, ccy):
ccys = ['USD', 'EUR', 'SGD']
json = self.get_json('api.itbit.com', '/v1/markets/XBT%s/ticker' % ccy)
json = await self.get_json('api.itbit.com', '/v1/markets/XBT%s/ticker' % ccy)
result = dict.fromkeys(ccys)
if ccy in ccys:
result[ccy] = Decimal(json['lastPrice'])
@ -294,10 +327,10 @@ class itBit(ExchangeBase):
class Kraken(ExchangeBase):
def get_rates(self, ccy):
async def get_rates(self, ccy):
ccys = ['EUR', 'USD', 'CAD', 'GBP', 'JPY']
pairs = ['XBT%s' % c for c in ccys]
json = self.get_json('api.kraken.com',
json = await self.get_json('api.kraken.com',
'/0/public/Ticker?pair=%s' % ','.join(pairs))
return dict((k[-3:], Decimal(float(v['c'][0])))
for k, v in json['result'].items())
@ -305,61 +338,44 @@ class Kraken(ExchangeBase):
class LocalBitcoins(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('localbitcoins.com',
async def get_rates(self, ccy):
json = await self.get_json('localbitcoins.com',
'/bitcoinaverage/ticker-all-currencies/')
return dict([(r, Decimal(json[r]['rates']['last'])) for r in json])
class MercadoBitcoin(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
async def get_rates(self, ccy):
json = await self.get_json('api.bitvalor.com', '/v1/ticker.json')
return {'BRL': Decimal(json['ticker_1h']['exchanges']['MBT']['last'])}
class NegocieCoins(ExchangeBase):
def get_rates(self,ccy):
json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
async def get_rates(self,ccy):
json = await self.get_json('api.bitvalor.com', '/v1/ticker.json')
return {'BRL': Decimal(json['ticker_1h']['exchanges']['NEG']['last'])}
class TheRockTrading(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.therocktrading.com',
async def get_rates(self, ccy):
json = await self.get_json('api.therocktrading.com',
'/v1/funds/BTCEUR/ticker')
return {'EUR': Decimal(json['last'])}
class Unocoin(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('www.unocoin.com', 'trade?buy')
return {'INR': Decimal(json)}
class WEX(ExchangeBase):
def get_rates(self, ccy):
json_eur = self.get_json('wex.nz', '/api/3/ticker/btc_eur')
json_rub = self.get_json('wex.nz', '/api/3/ticker/btc_rur')
json_usd = self.get_json('wex.nz', '/api/3/ticker/btc_usd')
return {'EUR': Decimal(json_eur['btc_eur']['last']),
'RUB': Decimal(json_rub['btc_rur']['last']),
'USD': Decimal(json_usd['btc_usd']['last'])}
class Winkdex(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('winkdex.com', '/api/v0/price')
async def get_rates(self, ccy):
json = await self.get_json('winkdex.com', '/api/v0/price')
return {'USD': Decimal(json['price'] / 100.0)}
def history_ccys(self):
return ['USD']
def request_history(self, ccy):
json = self.get_json('winkdex.com',
async def request_history(self, ccy):
json = await self.get_json('winkdex.com',
"/api/v0/series?start_time=1342915200")
history = json['series'][0]['results']
return dict([(h['timestamp'][:10], h['price'] / 100.0)
@ -367,8 +383,8 @@ class Winkdex(ExchangeBase):
class Zaif(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.zaif.jp', '/api/1/last_price/btc_jpy')
async def get_rates(self, ccy):
json = await self.get_json('api.zaif.jp', '/api/1/last_price/btc_jpy')
return {'JPY': Decimal(json['last_price'])}
@ -381,26 +397,39 @@ def dictinvert(d):
return inv
def get_exchanges_and_currencies():
import os, json
path = os.path.join(os.path.dirname(__file__), 'currencies.json')
# load currencies.json from disk
path = resource_path('currencies.json')
try:
with open(path, 'r', encoding='utf-8') as f:
return json.loads(f.read())
except:
pass
# or if not present, generate it now.
print("cannot find currencies.json. will regenerate it now.")
d = {}
is_exchange = lambda obj: (inspect.isclass(obj)
and issubclass(obj, ExchangeBase)
and obj != ExchangeBase)
exchanges = dict(inspect.getmembers(sys.modules[__name__], is_exchange))
for name, klass in exchanges.items():
exchange = klass(None, None)
async def get_currencies_safe(name, exchange):
try:
d[name] = exchange.get_currencies()
d[name] = await exchange.get_currencies()
print(name, "ok")
except:
print(name, "error")
continue
async def query_all_exchanges_for_their_ccys_over_network():
async with timeout_after(10):
async with TaskGroup() as group:
for name, klass in exchanges.items():
exchange = klass(None, None)
await group.spawn(get_currencies_safe(name, exchange))
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(query_all_exchanges_for_their_ccys_over_network())
except Exception as e:
pass
with open(path, 'w', encoding='utf-8') as f:
f.write(json.dumps(d, indent=4, sort_keys=True))
return d
@ -423,51 +452,72 @@ def get_exchanges_by_ccy(history=True):
class FxThread(ThreadJob):
def __init__(self, config, network):
def __init__(self, config: SimpleConfig, network: Network):
ThreadJob.__init__(self)
self.config = config
self.network = network
if self.network:
self.network.register_callback(self.set_proxy, ['proxy_set'])
self.ccy = self.get_currency()
self.history_used_spot = False
self.ccy_combo = None
self.hist_checkbox = None
self.cache_dir = os.path.join(config.path, 'cache')
self._trigger = asyncio.Event()
self._trigger.set()
self.set_exchange(self.config_exchange())
make_dir(self.cache_dir)
def get_currencies(self, h):
d = get_exchanges_by_ccy(h)
def set_proxy(self, trigger_name, *args):
self._trigger.set()
@staticmethod
def get_currencies(history: bool) -> Sequence[str]:
d = get_exchanges_by_ccy(history)
return sorted(d.keys())
def get_exchanges_by_ccy(self, ccy, h):
d = get_exchanges_by_ccy(h)
@staticmethod
def get_exchanges_by_ccy(ccy: str, history: bool) -> Sequence[str]:
d = get_exchanges_by_ccy(history)
return d.get(ccy, [])
@staticmethod
def remove_thousands_separator(text):
return text.replace(',', '') # FIXME use THOUSAND_SEPARATOR in util
def ccy_amount_str(self, amount, commas):
prec = CCY_PRECISIONS.get(self.ccy, 2)
fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec))
fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec)) # FIXME use util.THOUSAND_SEPARATOR and util.DECIMAL_POINT
try:
rounded_amount = round(amount, prec)
except decimal.InvalidOperation:
rounded_amount = amount
return fmt_str.format(rounded_amount)
def run(self):
# This runs from the plugins thread which catches exceptions
if self.is_enabled():
if self.timeout ==0 and self.show_history():
self.exchange.get_historical_rates(self.ccy, self.cache_dir)
if self.timeout <= time.time():
self.timeout = time.time() + 150
self.exchange.update(self.ccy)
async def run(self):
while True:
# approx. every 2.5 minutes, refresh spot price
try:
async with timeout_after(150):
await self._trigger.wait()
self._trigger.clear()
# we were manually triggered, so get historical rates
if self.is_enabled() and self.show_history():
self.exchange.get_historical_rates(self.ccy, self.cache_dir)
except TaskTimeout:
pass
if self.is_enabled():
await self.exchange.update_safe(self.ccy)
def is_enabled(self):
return bool(self.config.get('use_exchange_rate'))
return bool(self.config.get('use_exchange_rate', DEFAULT_ENABLED))
def set_enabled(self, b):
return self.config.set_key('use_exchange_rate', bool(b))
self.config.set_key('use_exchange_rate', bool(b))
self.trigger_update()
def get_history_config(self):
return bool(self.config.get('history_rates'))
def get_history_config(self, *, default=False):
return bool(self.config.get('history_rates', default))
def set_history_config(self, b):
self.config.set_key('history_rates', bool(b))
@ -486,10 +536,10 @@ class FxThread(ThreadJob):
def get_currency(self):
'''Use when dynamic fetching is needed'''
return self.config.get("currency", "EUR")
return self.config.get("currency", DEFAULT_CURRENCY)
def config_exchange(self):
return self.config.get('use_exchange', 'BitcoinAverage')
return self.config.get('use_exchange', DEFAULT_EXCHANGE)
def show_history(self):
return self.is_enabled() and self.get_history_config() and self.ccy in self.exchange.history_ccys()
@ -497,18 +547,23 @@ class FxThread(ThreadJob):
def set_currency(self, ccy):
self.ccy = ccy
self.config.set_key('currency', ccy, True)
self.timeout = 0 # Because self.ccy changes
self.trigger_update()
self.on_quotes()
def trigger_update(self):
if self.network:
self.network.asyncio_loop.call_soon_threadsafe(self._trigger.set)
def set_exchange(self, name):
class_ = globals().get(name, BitcoinAverage)
self.print_error("using exchange", name)
class_ = globals().get(name) or globals().get(DEFAULT_EXCHANGE)
self.logger.info(f"using exchange {name}")
if self.config_exchange() != name:
self.config.set_key('use_exchange', name, True)
self.exchange = class_(self.on_quotes, self.on_history)
assert issubclass(class_, ExchangeBase), f"unexpected type {class_} for {name}"
self.exchange = class_(self.on_quotes, self.on_history) # type: ExchangeBase
# A new exchange means new fx quotes, initially empty. Force
# a quote refresh
self.timeout = 0
self.trigger_update()
self.exchange.read_historical_rates(self.ccy, self.cache_dir)
def on_quotes(self):
@ -519,8 +574,8 @@ class FxThread(ThreadJob):
if self.network:
self.network.trigger_callback('on_history')
def exchange_rate(self):
'''Returns None, or the exchange rate as a Decimal'''
def exchange_rate(self) -> Decimal:
"""Returns the exchange rate as a Decimal"""
rate = self.exchange.quotes.get(self.ccy)
if rate is None:
return Decimal('NaN')
@ -556,9 +611,11 @@ class FxThread(ThreadJob):
rate = self.exchange.historical_rate(self.ccy, d_t)
# Frequently there is no rate for today, until tomorrow :)
# Use spot quotes in that case
if rate == 'NaN' and (datetime.today().date() - d_t.date()).days <= 2:
if rate in ('NaN', None) and (datetime.today().date() - d_t.date()).days <= 2:
rate = self.exchange.quotes.get(self.ccy, 'NaN')
self.history_used_spot = True
if rate is None:
rate = 'NaN'
return Decimal(rate)
def historical_value_str(self, satoshis, d_t):
@ -571,3 +628,6 @@ class FxThread(ThreadJob):
from .util import timestamp_to_datetime
date = timestamp_to_datetime(timestamp)
return self.history_rate(date)
assert globals().get(DEFAULT_EXCHANGE), f"default exchange {DEFAULT_EXCHANGE} does not exist"

View file

@ -1,5 +1,5 @@
# To create a new GUI, please add its code to this directory.
# Three objects are passed to the ElectrumGui: config, daemon and plugins
# The Wallet object is instanciated by the GUI
# The Wallet object is instantiated by the GUI
# Notifications about network events are sent to the GUI by using network.register_callback()

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Some files were not shown because too many files have changed in this diff Show more