Merge pull request #7 from spesmilo/master

Pulling upstream changes
This commit is contained in:
Vivek Teega 2018-09-28 18:02:50 +05:30 committed by GitHub
commit 826a56311c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 471 additions and 409 deletions

View File

@ -37,9 +37,9 @@ PyQt5==5.10.1 \
--hash=sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b \ --hash=sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b \
--hash=sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7 \ --hash=sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7 \
--hash=sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61 --hash=sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61
setuptools==40.2.0 \ setuptools==40.4.3 \
--hash=sha256:47881d54ede4da9c15273bac65f9340f8929d4f0213193fa7894be384f2dcfa6 \ --hash=sha256:acbc5740dd63f243f46c2b4b8e2c7fd92259c2ddb55a4115b16418a2ed371b15 \
--hash=sha256:ea3796a48a207b46ea36a9d26de4d0cc87c953a683a7b314ea65d666930ea8e6 --hash=sha256:ce4137d58b444bac11a31d4e0c1805c69d89e8ed4e91fde1999674ecc2f6f9ff
SIP==4.19.8 \ SIP==4.19.8 \
--hash=sha256:09f9a4e6c28afd0bafedb26ffba43375b97fe7207bd1a0d3513f79b7d168b331 \ --hash=sha256:09f9a4e6c28afd0bafedb26ffba43375b97fe7207bd1a0d3513f79b7d168b331 \
--hash=sha256:105edaaa1c8aa486662226360bd3999b4b89dd56de3e314d82b83ed0587d8783 \ --hash=sha256:105edaaa1c8aa486662226360bd3999b4b89dd56de3e314d82b83ed0587d8783 \

View File

@ -9,9 +9,9 @@ chardet==3.0.4 \
ckcc-protocol==0.7.2 \ ckcc-protocol==0.7.2 \
--hash=sha256:31ee5178cfba8895eb2a6b8d06dc7830b51461a0ff767a670a64707c63e6b264 \ --hash=sha256:31ee5178cfba8895eb2a6b8d06dc7830b51461a0ff767a670a64707c63e6b264 \
--hash=sha256:498db4ccdda018cd9f40210f5bd02ddcc98e7df583170b2eab4035c86c3cc03b --hash=sha256:498db4ccdda018cd9f40210f5bd02ddcc98e7df583170b2eab4035c86c3cc03b
click==6.7 \ click==7.0 \
--hash=sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d \ --hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \
--hash=sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b --hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7
Cython==0.28.5 \ Cython==0.28.5 \
--hash=sha256:022592d419fc754509d0e0461eb2958dbaa45fb60d51c8a61778c58994edbe36 \ --hash=sha256:022592d419fc754509d0e0461eb2958dbaa45fb60d51c8a61778c58994edbe36 \
--hash=sha256:07659f4c57582104d9486c071de512fbd7e087a3a630535298442cc0e20a3f5a \ --hash=sha256:07659f4c57582104d9486c071de512fbd7e087a3a630535298442cc0e20a3f5a \
@ -102,9 +102,9 @@ requests==2.19.1 \
safet==0.1.4 \ safet==0.1.4 \
--hash=sha256:522c257910f9472e9c77c487425ed286f6721c314653e232bc41c6cedece1bb1 \ --hash=sha256:522c257910f9472e9c77c487425ed286f6721c314653e232bc41c6cedece1bb1 \
--hash=sha256:b152874acdc89ff0c8b2d680bfbf020b3e53527c2ad3404489dd61a548aa56a1 --hash=sha256:b152874acdc89ff0c8b2d680bfbf020b3e53527c2ad3404489dd61a548aa56a1
setuptools==40.2.0 \ setuptools==40.4.3 \
--hash=sha256:47881d54ede4da9c15273bac65f9340f8929d4f0213193fa7894be384f2dcfa6 \ --hash=sha256:acbc5740dd63f243f46c2b4b8e2c7fd92259c2ddb55a4115b16418a2ed371b15 \
--hash=sha256:ea3796a48a207b46ea36a9d26de4d0cc87c953a683a7b314ea65d666930ea8e6 --hash=sha256:ce4137d58b444bac11a31d4e0c1805c69d89e8ed4e91fde1999674ecc2f6f9ff
six==1.11.0 \ six==1.11.0 \
--hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \ --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \
--hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb
@ -114,9 +114,9 @@ trezor==0.10.2 \
urllib3==1.23 \ urllib3==1.23 \
--hash=sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf \ --hash=sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf \
--hash=sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5 --hash=sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5
websocket-client==0.52.0 \ websocket-client==0.53.0 \
--hash=sha256:03763384c530b331ec3822d0b52ffdc28c3aeb8a900ac8c98b2ceea3128a7b4e \ --hash=sha256:c42b71b68f9ef151433d6dcc6a7cb98ac72d2ad1e3a74981ca22bc5d9134f166 \
--hash=sha256:3c9924675eaf0b27ae22feeeab4741bb4149b94820bd3a143eeaf8b62f64d821 --hash=sha256:f5889b1d0a994258cfcbc8f2dc3e457f6fc7b32a8d74873033d12e4eab4bdf63
wheel==0.31.1 \ wheel==0.31.1 \
--hash=sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c \ --hash=sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c \
--hash=sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f --hash=sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f

View File

@ -23,9 +23,9 @@ aiohttp==3.4.4 \
--hash=sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07 --hash=sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07
aiohttp_socks==0.1.6 \ aiohttp_socks==0.1.6 \
--hash=sha256:943148a3797ba9ffb6df6ddb006ffdd40538885b410589d589bda42a8e8bcd5a --hash=sha256:943148a3797ba9ffb6df6ddb006ffdd40538885b410589d589bda42a8e8bcd5a
aiorpcX==0.7.3 \ aiorpcX==0.8.2 \
--hash=sha256:24dd4fe2f65f743cb74c8626570470e325bb777bb66d1932e7d2965ae71d1164 \ --hash=sha256:980d1d85a831688163ad087a1c1a88b6695a06e5e9914824676bab4251b2b1f2 \
--hash=sha256:5120ca40beef6b6a45d3a7055e343815401385dc607da2fd93baca2762c8a97d --hash=sha256:e53ff8917a87843875526be1261d80171f5ad09187917ff29dfdc003c1526a65
async_timeout==3.0.0 \ async_timeout==3.0.0 \
--hash=sha256:474d4bc64cee20603e225eb1ece15e248962958b45a3648a9f5cc29e827a610c \ --hash=sha256:474d4bc64cee20603e225eb1ece15e248962958b45a3648a9f5cc29e827a610c \
--hash=sha256:b3c0ddc416736619bd4a95ca31de8da6920c3b9a140c64dbef2b2fa7bf521287 --hash=sha256:b3c0ddc416736619bd4a95ca31de8da6920c3b9a140c64dbef2b2fa7bf521287
@ -52,36 +52,36 @@ idna_ssl==1.1.0 \
jsonrpclib-pelix==0.3.1 \ jsonrpclib-pelix==0.3.1 \
--hash=sha256:5417b1508d5a50ec64f6e5b88907f111155d52607b218ff3ba9a777afb2e49e3 \ --hash=sha256:5417b1508d5a50ec64f6e5b88907f111155d52607b218ff3ba9a777afb2e49e3 \
--hash=sha256:bd89a6093bc4d47dc8a096197aacb827359944a4533be5193f3845f57b9f91b4 --hash=sha256:bd89a6093bc4d47dc8a096197aacb827359944a4533be5193f3845f57b9f91b4
multidict==4.4.0 \ multidict==4.4.2 \
--hash=sha256:112eeeddd226af681dc82b756ed34aa7b6d98f9c4a15760050298c21d715473d \ --hash=sha256:05eeab69bf2b0664644c62bd92fabb045163e5b8d4376a31dfb52ce0210ced7b \
--hash=sha256:13b64ecb692effcabc5e29569ba9b5eb69c35112f990a16d6833ec3a9d9f8ec0 \ --hash=sha256:0c85880efa7cadb18e3b5eef0aa075dc9c0a3064cbbaef2e20be264b9cf47a64 \
--hash=sha256:1725373fb8f18c2166f8e0e5789851ccf98453c849b403945fa4ef59a16ca44e \ --hash=sha256:136f5a4a6a4adeacc4dc820b8b22f0a378fb74f326e259c54d1817639d1d40a0 \
--hash=sha256:2061a50b7cae60a1f987503a995b2fc38e47027a937a355a124306ed9c629041 \ --hash=sha256:14906ad3347c7d03e9101749b16611cf2028547716d0840838d3c5e2b3b0f2d3 \
--hash=sha256:35b062288a9a478f627c520fd27983160fc97591017d170f966805b428d17e07 \ --hash=sha256:1ade4a3b71b1bf9e90c5f3d034a87fe4949c087ef1f6cd727fdd766fe8bbd121 \
--hash=sha256:467b134bcc227b91b8e2ef8d2931f28b50bf7eb7a04c0403d102ded22e66dbfc \ --hash=sha256:22939a00a511a59f9ecc0158b8db728afef57975ce3782b3a265a319d05b9b12 \
--hash=sha256:475a3ece8bb450e49385414ebfae7f8fdb33f62f1ac0c12935c1cfb1b7c1076a \ --hash=sha256:2b86b02d872bc5ba5b3a4530f6a7ba0b541458ab4f7c1429a12ac326231203f7 \
--hash=sha256:49b885287e227a24545a1126d9ac17ae43138610713dc6219b781cc0ad5c6dfc \ --hash=sha256:3c11e92c3dfc321014e22fb442bc9eb70e01af30d6ce442026b0c35723448c66 \
--hash=sha256:4c95b2725592adb5c46642be2875c1234c32af841732c5504c17726b92082021 \ --hash=sha256:4ba3bd26f282b201fdbce351f1c5d17ceb224cbedb73d6e96e6ce391b354aacc \
--hash=sha256:4ea7ed00f4be0f7335c9a2713a65ac3d986be789ce5ebc10821da9664cbe6b85 \ --hash=sha256:4c6e78d042e93751f60672989efbd6a6bc54213ed7ff695fff82784bbb9ea035 \
--hash=sha256:5e2d5e1d999e941b4a626aea46bdc4206877cf727107fdaa9d46a8a773a6e49b \ --hash=sha256:4d80d1901b89cc935a6cf5b9fd89df66565272722fe2e5473168927a9937e0ca \
--hash=sha256:8039c520ef7bb9ec7c3db3df14c570be6362f43c200ae9854d2422d4ffe175a4 \ --hash=sha256:4fcf71d33178a00cc34a57b29f5dab1734b9ce0f1c97fb34666deefac6f92037 \
--hash=sha256:81459a0ebcca09c1fcb8fe887ed13cf267d9b60fe33718fc5fd1a2a1ab49470a \ --hash=sha256:52f7670b41d4b4d97866ebc38121de8bcb9813128b7c4942b07794d08193c0ab \
--hash=sha256:847c3b7b9ca3268e883685dc1347a4d09f84de7bd7597310044d847590447492 \ --hash=sha256:5368e2b7649a26b7253c6c9e53241248aab9da49099442f5be238fde436f18c9 \
--hash=sha256:8551d1db45f0ca4e8ec99130767009a29a4e0dc6558a4a6808491bcd3472d325 \ --hash=sha256:5bb65fbb48999044938f0c0508e929b14a9b8bf4939d8263e9ea6691f7b54663 \
--hash=sha256:8fa7679ffe615e0c1c7b80946ab4194669be74848719adf2d7867b5e861eb073 \ --hash=sha256:60672bb5577472800fcca1ac9dae232d1461db9f20f055184be8ce54b0052572 \
--hash=sha256:a42a36f09f0f907579ff0fde547f2fde8a739a69efe4a2728835979d2bb5e17b \ --hash=sha256:669e9be6d148fc0283f53e17dd140cde4dc7c87edac8319147edd5aa2a830771 \
--hash=sha256:a5fcad0070685c5b2d04b468bf5f4c735f5c176432f495ad055fcc4bc0a79b23 \ --hash=sha256:6a0b7a804e8d1716aa2c72e73210b48be83d25ba9ec5cf52cf91122285707bb1 \
--hash=sha256:ae22195b2a7494619b73c01129ddcddc0dfaa9e42727404b1d9a77253da3f420 \ --hash=sha256:79034ea3da3cf2a815e3e52afdc1f6c1894468c98bdce5d2546fa2342585497f \
--hash=sha256:b360e82bdbbd862e1ce2a41cc3bbd0ab614350e813ca74801b34aac0f73465aa \ --hash=sha256:79247feeef6abcc11137ad17922e865052f23447152059402fc320f99ff544bb \
--hash=sha256:b96417899344c5e96bef757f4963a72d02e52653a4e0f99bbea3a531cedac59f \ --hash=sha256:81671c2049e6bf42c7fd11a060f8bc58f58b7b3d6f3f951fc0b15e376a6a5a98 \
--hash=sha256:b9e921140b797093edfc13ac08dc2a4fd016dd711dc42bb0e1aaf180e48425a7 \ --hash=sha256:82ac4a5cb56cc9280d4ae52c2d2ebcd6e0668dd0f9ef17f0a9d7c82bd61e24fa \
--hash=sha256:c5022b94fc330e6d177f3eb38097fb52c7df96ca0e04842c068cf0d9fc38b1e6 \ --hash=sha256:9436267dbbaa49dad18fbbb54f85386b0f5818d055e7b8e01d219661b6745279 \
--hash=sha256:cf2b117f2a8d951638efc7592fb72d3eeb2d38cc2194c26ba7f00e7190451d92 \ --hash=sha256:94e4140bb1343115a1afd6d84ebf8fca5fb7bfb50e1c2cbd6f2fb5d3117ef102 \
--hash=sha256:d79620b542d9d0e23ae9790ca2fe44f1af40ffad9936efa37bd14954bc3e2818 \ --hash=sha256:a2cab366eae8a0ffe0813fd8e335cf0d6b9bb6c5227315f53bb457519b811537 \
--hash=sha256:e2860691c11d10dac7c91bddae44f6211b3da4122d9a2ebb509c2247674d6070 \ --hash=sha256:a596019c3eafb1b0ae07db9f55a08578b43c79adb1fe1ab1fd818430ae59ee6f \
--hash=sha256:e3a293553715afecf7e10ea02da40593f9d7f48fe48a74fc5dd3ce08a0c46188 \ --hash=sha256:e8848ae3cd6a784c29fae5055028bee9bffcc704d8bcad09bd46b42b44a833e2 \
--hash=sha256:e465be3fe7e992e5a6e16731afa6f41cb6ca53afccb4f28ea2fa6457783edf15 \ --hash=sha256:e8a048bfd7d5a280f27527d11449a509ddedf08b58a09a24314828631c099306 \
--hash=sha256:e6d27895ef922bc859d969452f247bfbe5345d9aba69b9c8dbe1ea7704f0c5d9 --hash=sha256:f6dd28a0ac60e2426a6918f36f1b4e2620fc785a0de7654cd206ba842eee57fd
pip==18.0 \ pip==18.0 \
--hash=sha256:070e4bf493c7c2c9f6a08dd797dd3c066d64074c38e9e8a0fb4e6541f266d96c \ --hash=sha256:070e4bf493c7c2c9f6a08dd797dd3c066d64074c38e9e8a0fb4e6541f266d96c \
--hash=sha256:a0e11645ee37c90b40c46d607070c4fd583e2cd46231b1c06e389c5e814eed76 --hash=sha256:a0e11645ee37c90b40c46d607070c4fd583e2cd46231b1c06e389c5e814eed76
@ -103,8 +103,6 @@ protobuf==3.6.1 \
--hash=sha256:fcfc907746ec22716f05ea96b7f41597dfe1a1c088f861efb8a0d4f4196a6f10 --hash=sha256:fcfc907746ec22716f05ea96b7f41597dfe1a1c088f861efb8a0d4f4196a6f10
pyaes==1.6.1 \ pyaes==1.6.1 \
--hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f --hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
PySocks==1.6.8 \
--hash=sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672
QDarkStyle==2.5.4 \ QDarkStyle==2.5.4 \
--hash=sha256:3eb60922b8c4d9cedecb6897ca4c9f8a259d81bdefe5791976ccdf12432de1f0 \ --hash=sha256:3eb60922b8c4d9cedecb6897ca4c9f8a259d81bdefe5791976ccdf12432de1f0 \
--hash=sha256:51331fc6490b38c376e6ba8d8c814320c8d2d1c2663055bc396321a7c28fa8be --hash=sha256:51331fc6490b38c376e6ba8d8c814320c8d2d1c2663055bc396321a7c28fa8be
@ -114,16 +112,12 @@ qrcode==6.0 \
requests==2.19.1 \ requests==2.19.1 \
--hash=sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1 \ --hash=sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1 \
--hash=sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a --hash=sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a
setuptools==40.2.0 \ setuptools==40.4.3 \
--hash=sha256:47881d54ede4da9c15273bac65f9340f8929d4f0213193fa7894be384f2dcfa6 \ --hash=sha256:acbc5740dd63f243f46c2b4b8e2c7fd92259c2ddb55a4115b16418a2ed371b15 \
--hash=sha256:ea3796a48a207b46ea36a9d26de4d0cc87c953a683a7b314ea65d666930ea8e6 --hash=sha256:ce4137d58b444bac11a31d4e0c1805c69d89e8ed4e91fde1999674ecc2f6f9ff
six==1.11.0 \ six==1.11.0 \
--hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \ --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \
--hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb
typing==3.6.6 \
--hash=sha256:4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d \
--hash=sha256:57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4 \
--hash=sha256:a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a
urllib3==1.23 \ urllib3==1.23 \
--hash=sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf \ --hash=sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf \
--hash=sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5 --hash=sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5

View File

@ -6,6 +6,6 @@ protobuf
dnspython dnspython
jsonrpclib-pelix jsonrpclib-pelix
qdarkstyle<3.0 qdarkstyle<3.0
aiorpcx>=0.8.1,<0.9 aiorpcx>=0.8.2,<0.9
aiohttp aiohttp
aiohttp_socks aiohttp_socks

View File

@ -181,7 +181,7 @@ class Commands:
walletless server query, results are not checked by SPV. walletless server query, results are not checked by SPV.
""" """
sh = bitcoin.address_to_scripthash(address) sh = bitcoin.address_to_scripthash(address)
return self.network.get_history_for_scripthash(sh) return self.network.run_from_another_thread(self.network.get_history_for_scripthash(sh))
@command('w') @command('w')
def listunspent(self): def listunspent(self):
@ -199,7 +199,7 @@ class Commands:
is a walletless server query, results are not checked by SPV. is a walletless server query, results are not checked by SPV.
""" """
sh = bitcoin.address_to_scripthash(address) sh = bitcoin.address_to_scripthash(address)
return self.network.listunspent_for_scripthash(sh) return self.network.run_from_another_thread(self.network.listunspent_for_scripthash(sh))
@command('') @command('')
def serialize(self, jsontx): def serialize(self, jsontx):
@ -255,7 +255,7 @@ class Commands:
def broadcast(self, tx): def broadcast(self, tx):
"""Broadcast a transaction to the network. """ """Broadcast a transaction to the network. """
tx = Transaction(tx) tx = Transaction(tx)
return self.network.broadcast_transaction_from_non_network_thread(tx) return self.network.run_from_another_thread(self.network.broadcast_transaction(tx))
@command('') @command('')
def createmultisig(self, num, pubkeys): def createmultisig(self, num, pubkeys):
@ -322,7 +322,7 @@ class Commands:
server query, results are not checked by SPV. server query, results are not checked by SPV.
""" """
sh = bitcoin.address_to_scripthash(address) sh = bitcoin.address_to_scripthash(address)
out = self.network.get_balance_for_scripthash(sh) out = self.network.run_from_another_thread(self.network.get_balance_for_scripthash(sh))
out["confirmed"] = str(Decimal(out["confirmed"])/COIN) out["confirmed"] = str(Decimal(out["confirmed"])/COIN)
out["unconfirmed"] = str(Decimal(out["unconfirmed"])/COIN) out["unconfirmed"] = str(Decimal(out["unconfirmed"])/COIN)
return out return out
@ -331,7 +331,7 @@ class Commands:
def getmerkle(self, txid, height): def getmerkle(self, txid, height):
"""Get Merkle branch of a transaction included in a block. Electrum """Get Merkle branch of a transaction included in a block. Electrum
uses this to verify transactions (Simple Payment Verification).""" uses this to verify transactions (Simple Payment Verification)."""
return self.network.get_merkle_for_transaction(txid, int(height)) return self.network.run_from_another_thread(self.network.get_merkle_for_transaction(txid, int(height)))
@command('n') @command('n')
def getservers(self): def getservers(self):
@ -517,7 +517,7 @@ class Commands:
if self.wallet and txid in self.wallet.transactions: if self.wallet and txid in self.wallet.transactions:
tx = self.wallet.transactions[txid] tx = self.wallet.transactions[txid]
else: else:
raw = self.network.get_transaction(txid) raw = self.network.run_from_another_thread(self.network.get_transaction(txid))
if raw: if raw:
tx = Transaction(raw) tx = Transaction(raw)
else: else:
@ -637,6 +637,7 @@ class Commands:
@command('n') @command('n')
def notify(self, address, URL): def notify(self, address, URL):
"""Watch an address. Every time the address changes, a http POST is sent to the URL.""" """Watch an address. Every time the address changes, a http POST is sent to the URL."""
raise NotImplementedError() # TODO this method is currently broken
def callback(x): def callback(x):
import urllib.request import urllib.request
headers = {'content-type':'application/json'} headers = {'content-type':'application/json'}

View File

@ -28,11 +28,11 @@ import os
import time import time
import traceback import traceback
import sys import sys
import threading
# from jsonrpc import JSONRPCResponseManager
import jsonrpclib import jsonrpclib
from .jsonrpc import VerifyingJSONRPCServer
from .jsonrpc import VerifyingJSONRPCServer
from .version import ELECTRUM_VERSION from .version import ELECTRUM_VERSION
from .network import Network from .network import Network
from .util import json_decode, DaemonThread from .util import json_decode, DaemonThread
@ -129,7 +129,7 @@ class Daemon(DaemonThread):
self.network = Network(config) self.network = Network(config)
self.fx = FxThread(config, self.network) self.fx = FxThread(config, self.network)
if self.network: if self.network:
self.network.start(self.fx.run()) self.network.start([self.fx.run])
self.gui = None self.gui = None
self.wallets = {} self.wallets = {}
# Setup JSONRPC server # Setup JSONRPC server
@ -308,6 +308,7 @@ class Daemon(DaemonThread):
gui_name = 'qt' gui_name = 'qt'
gui = __import__('electrum.gui.' + gui_name, fromlist=['electrum']) gui = __import__('electrum.gui.' + gui_name, fromlist=['electrum'])
self.gui = gui.ElectrumGui(config, self, plugins) self.gui = gui.ElectrumGui(config, self, plugins)
threading.current_thread().setName('GUI')
try: try:
self.gui.main() self.gui.main()
except BaseException as e: except BaseException as e:

View File

@ -1,5 +1,5 @@
# To create a new GUI, please add its code to this directory. # To create a new GUI, please add its code to this directory.
# Three objects are passed to the ElectrumGui: config, daemon and plugins # 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() # Notifications about network events are sent to the GUI by using network.register_callback()

View File

@ -16,6 +16,7 @@ from electrum.plugin import run_hook
from electrum.util import format_satoshis, format_satoshis_plain from electrum.util import format_satoshis, format_satoshis_plain
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
from electrum import blockchain from electrum import blockchain
from electrum.network import Network
from .i18n import _ from .i18n import _
from kivy.app import App from kivy.app import App
@ -96,7 +97,7 @@ class ElectrumWindow(App):
def on_auto_connect(self, instance, x): def on_auto_connect(self, instance, x):
net_params = self.network.get_parameters() net_params = self.network.get_parameters()
net_params = net_params._replace(auto_connect=self.auto_connect) net_params = net_params._replace(auto_connect=self.auto_connect)
self.network.set_parameters(net_params) self.network.run_from_another_thread(self.network.set_parameters(net_params))
def toggle_auto_connect(self, x): def toggle_auto_connect(self, x):
self.auto_connect = not self.auto_connect self.auto_connect = not self.auto_connect
@ -116,9 +117,10 @@ class ElectrumWindow(App):
from .uix.dialogs.choice_dialog import ChoiceDialog from .uix.dialogs.choice_dialog import ChoiceDialog
chains = self.network.get_blockchains() chains = self.network.get_blockchains()
def cb(name): def cb(name):
for index, b in blockchain.blockchains.items(): with blockchain.blockchains_lock: blockchain_items = list(blockchain.blockchains.items())
for index, b in blockchain_items:
if name == b.get_name(): if name == b.get_name():
self.network.follow_chain(index) self.network.run_from_another_thread(self.network.follow_chain(index))
names = [blockchain.blockchains[b].get_name() for b in chains] names = [blockchain.blockchains[b].get_name() for b in chains]
if len(names) > 1: if len(names) > 1:
cur_chain = self.network.blockchain().get_name() cur_chain = self.network.blockchain().get_name()
@ -265,7 +267,7 @@ class ElectrumWindow(App):
title = _('Electrum App') title = _('Electrum App')
self.electrum_config = config = kwargs.get('config', None) self.electrum_config = config = kwargs.get('config', None)
self.language = config.get('language', 'en') self.language = config.get('language', 'en')
self.network = network = kwargs.get('network', None) self.network = network = kwargs.get('network', None) # type: Network
if self.network: if self.network:
self.num_blocks = self.network.get_local_height() self.num_blocks = self.network.get_local_height()
self.num_nodes = len(self.network.get_interfaces()) self.num_nodes = len(self.network.get_interfaces())
@ -708,7 +710,7 @@ class ElectrumWindow(App):
status = _("Offline") status = _("Offline")
elif self.network.is_connected(): elif self.network.is_connected():
server_height = self.network.get_server_height() server_height = self.network.get_server_height()
server_lag = self.network.get_local_height() - server_height server_lag = self.num_blocks - server_height
if not self.wallet.up_to_date or server_height == 0: if not self.wallet.up_to_date or server_height == 0:
status = _("Synchronizing...") status = _("Synchronizing...")
elif server_lag > 1: elif server_lag > 1:
@ -885,7 +887,8 @@ class ElectrumWindow(App):
Clock.schedule_once(lambda dt: on_success(tx)) Clock.schedule_once(lambda dt: on_success(tx))
def _broadcast_thread(self, tx, on_complete): def _broadcast_thread(self, tx, on_complete):
ok, txid = self.network.broadcast_transaction_from_non_network_thread(tx) ok, txid = self.network.run_from_another_thread(
self.network.broadcast_transaction(tx))
Clock.schedule_once(lambda dt: on_complete(ok, txid)) Clock.schedule_once(lambda dt: on_complete(ok, txid))
def broadcast(self, tx, pr=None): def broadcast(self, tx, pr=None):

View File

@ -159,8 +159,9 @@ class SettingsDialog(Factory.Popup):
return proxy.get('host') +':' + proxy.get('port') if proxy else _('None') return proxy.get('host') +':' + proxy.get('port') if proxy else _('None')
def proxy_dialog(self, item, dt): def proxy_dialog(self, item, dt):
network = self.app.network
if self._proxy_dialog is None: if self._proxy_dialog is None:
net_params = self.app.network.get_parameters() net_params = network.get_parameters()
proxy = net_params.proxy proxy = net_params.proxy
def callback(popup): def callback(popup):
nonlocal net_params nonlocal net_params
@ -175,7 +176,7 @@ class SettingsDialog(Factory.Popup):
else: else:
proxy = None proxy = None
net_params = net_params._replace(proxy=proxy) net_params = net_params._replace(proxy=proxy)
self.app.network.set_parameters(net_params) network.run_from_another_thread(network.set_parameters(net_params))
item.status = self.proxy_status() item.status = self.proxy_status()
popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/proxy.kv') popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/proxy.kv')
popup.ids.mode.text = proxy.get('mode') if proxy else 'None' popup.ids.mode.text = proxy.get('mode') if proxy else 'None'

View File

@ -72,6 +72,6 @@ Popup:
proxy['password']=str(root.ids.password.text) proxy['password']=str(root.ids.password.text)
if proxy['mode']=='none': proxy = None if proxy['mode']=='none': proxy = None
net_params = net_params._replace(proxy=proxy) net_params = net_params._replace(proxy=proxy)
app.network.set_parameters(net_params) app.network.run_from_another_thread(app.network.set_parameters(net_params))
app.proxy_config = proxy if proxy else {} app.proxy_config = proxy if proxy else {}
nd.dismiss() nd.dismiss()

View File

@ -58,5 +58,5 @@ Popup:
on_release: on_release:
net_params = app.network.get_parameters() net_params = app.network.get_parameters()
net_params = net_params._replace(host=str(root.ids.host.text), port=str(root.ids.port.text)) net_params = net_params._replace(host=str(root.ids.host.text), port=str(root.ids.port.text))
app.network.set_parameters(net_params) app.network.run_from_another_thread(app.network.set_parameters(net_params))
nd.dismiss() nd.dismiss()

View File

@ -42,12 +42,8 @@ from electrum.i18n import _, set_language
from electrum.plugin import run_hook from electrum.plugin import run_hook
from electrum.storage import WalletStorage from electrum.storage import WalletStorage
from electrum.base_wizard import GoBack from electrum.base_wizard import GoBack
# from electrum.synchronizer import Synchronizer
# from electrum.verifier import SPV
# from electrum.util import DebugMem
from electrum.util import (UserCancelled, PrintError, from electrum.util import (UserCancelled, PrintError,
WalletFileException, BitcoinException) WalletFileException, BitcoinException)
# from electrum.wallet import Abstract_Wallet
from .installwizard import InstallWizard from .installwizard import InstallWizard

View File

@ -26,8 +26,10 @@
from PyQt5.QtGui import * from PyQt5.QtGui import *
from PyQt5.QtCore import * from PyQt5.QtCore import *
from PyQt5.QtWidgets import * from PyQt5.QtWidgets import *
from .util import ButtonsTextEdit from .util import ButtonsTextEdit
class CompletionTextEdit(ButtonsTextEdit): class CompletionTextEdit(ButtonsTextEdit):
def __init__(self, parent=None): def __init__(self, parent=None):

View File

@ -1,11 +1,16 @@
# source: http://stackoverflow.com/questions/2758159/how-to-embed-a-python-interpreter-in-a-pyqt-widget # source: http://stackoverflow.com/questions/2758159/how-to-embed-a-python-interpreter-in-a-pyqt-widget
import sys, os, re import sys
import traceback, platform import os
import re
import traceback
import platform
from PyQt5 import QtCore from PyQt5 import QtCore
from PyQt5 import QtGui from PyQt5 import QtGui
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from electrum import util from electrum import util
from electrum.i18n import _ from electrum.i18n import _

View File

@ -24,14 +24,16 @@
# SOFTWARE. # SOFTWARE.
import webbrowser import webbrowser
from electrum.i18n import _
from electrum.bitcoin import is_address
from electrum.util import block_explorer_URL
from electrum.plugin import run_hook
from PyQt5.QtGui import * from PyQt5.QtGui import *
from PyQt5.QtCore import * from PyQt5.QtCore import *
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QFileDialog, QMenu, QTreeWidgetItem) QAbstractItemView, QFileDialog, QMenu, QTreeWidgetItem)
from electrum.i18n import _
from electrum.bitcoin import is_address
from electrum.util import block_explorer_URL
from electrum.plugin import run_hook
from .util import MyTreeWidget, import_meta_gui, export_meta_gui from .util import MyTreeWidget, import_meta_gui, export_meta_gui

View File

@ -1,9 +1,11 @@
from electrum.i18n import _ import threading
from PyQt5.QtGui import * from PyQt5.QtGui import *
from PyQt5.QtCore import * from PyQt5.QtCore import *
from PyQt5.QtWidgets import QSlider, QToolTip from PyQt5.QtWidgets import QSlider, QToolTip
import threading from electrum.i18n import _
class FeeSlider(QSlider): class FeeSlider(QSlider):

View File

@ -28,10 +28,11 @@ import datetime
from datetime import date from datetime import date
from electrum.address_synchronizer import TX_HEIGHT_LOCAL from electrum.address_synchronizer import TX_HEIGHT_LOCAL
from .util import *
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import block_explorer_URL, profiler, print_error, TxMinedStatus from electrum.util import block_explorer_URL, profiler, print_error, TxMinedStatus
from .util import *
try: try:
from electrum.plot import plot_history, NothingToPlotException from electrum.plot import plot_history, NothingToPlotException
except: except:

View File

@ -22,8 +22,12 @@
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
import sys, time, threading import sys
import os, json, traceback import time
import threading
import os
import traceback
import json
import shutil import shutil
import weakref import weakref
import webbrowser import webbrowser
@ -36,8 +40,6 @@ import queue
from PyQt5.QtGui import * from PyQt5.QtGui import *
from PyQt5.QtCore import * from PyQt5.QtCore import *
import PyQt5.QtCore as QtCore import PyQt5.QtCore as QtCore
from .exception_window import Exception_Hook
from PyQt5.QtWidgets import * from PyQt5.QtWidgets import *
from electrum import (keystore, simple_config, ecc, constants, util, bitcoin, commands, from electrum import (keystore, simple_config, ecc, constants, util, bitcoin, commands,
@ -56,6 +58,7 @@ from electrum.transaction import Transaction, TxOutput
from electrum.address_synchronizer import AddTransactionException from electrum.address_synchronizer import AddTransactionException
from electrum.wallet import Multisig_Wallet, CannotBumpFee from electrum.wallet import Multisig_Wallet, CannotBumpFee
from .exception_window import Exception_Hook
from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit
from .qrcodewidget import QRCodeWidget, QRDialog from .qrcodewidget import QRCodeWidget, QRDialog
from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
@ -1642,7 +1645,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if pr and pr.has_expired(): if pr and pr.has_expired():
self.payment_request = None self.payment_request = None
return False, _("Payment request has expired") return False, _("Payment request has expired")
status, msg = self.network.broadcast_transaction_from_non_network_thread(tx) status, msg = self.network.run_from_another_thread(
self.network.broadcast_transaction(tx))
if pr and status is True: if pr and status is True:
self.invoices.set_paid(pr, tx.txid()) self.invoices.set_paid(pr, tx.txid())
self.invoices.save() self.invoices.save()
@ -2510,7 +2514,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
for addr, pk in pklist.items(): for addr, pk in pklist.items():
transaction.writerow(["%34s"%addr,pk]) transaction.writerow(["%34s"%addr,pk])
else: else:
import json
f.write(json.dumps(pklist, indent = 4)) f.write(json.dumps(pklist, indent = 4))
def do_import_labels(self): def do_import_labels(self):

View File

@ -34,6 +34,7 @@ from electrum.i18n import _
from electrum import constants, blockchain from electrum import constants, blockchain
from electrum.util import print_error from electrum.util import print_error
from electrum.interface import serialize_server, deserialize_server from electrum.interface import serialize_server, deserialize_server
from electrum.network import Network
from .util import * from .util import *
@ -97,7 +98,7 @@ class NodesListWidget(QTreeWidget):
pt.setX(50) pt.setX(50)
self.customContextMenuRequested.emit(pt) self.customContextMenuRequested.emit(pt)
def update(self, network): def update(self, network: Network):
self.clear() self.clear()
self.addChild = self.addTopLevelItem self.addChild = self.addTopLevelItem
chains = network.get_blockchains() chains = network.get_blockchains()
@ -187,7 +188,7 @@ class ServerListWidget(QTreeWidget):
class NetworkChoiceLayout(object): class NetworkChoiceLayout(object):
def __init__(self, network, config, wizard=False): def __init__(self, network: Network, config, wizard=False):
self.network = network self.network = network
self.config = config self.config = config
self.protocol = None self.protocol = None
@ -361,7 +362,7 @@ class NetworkChoiceLayout(object):
status = _("Connected to {0} nodes.").format(n) if n else _("Not connected") status = _("Connected to {0} nodes.").format(n) if n else _("Not connected")
self.status_label.setText(status) self.status_label.setText(status)
chains = self.network.get_blockchains() chains = self.network.get_blockchains()
if len(chains)>1: if len(chains) > 1:
chain = self.network.blockchain() chain = self.network.blockchain()
forkpoint = chain.get_forkpoint() forkpoint = chain.get_forkpoint()
name = chain.get_name() name = chain.get_name()
@ -410,15 +411,14 @@ class NetworkChoiceLayout(object):
self.set_server() self.set_server()
def follow_branch(self, index): def follow_branch(self, index):
self.network.follow_chain(index) self.network.run_from_another_thread(self.network.follow_chain(index))
self.update() self.update()
def follow_server(self, server): def follow_server(self, server):
self.network.switch_to_interface(server)
net_params = self.network.get_parameters() net_params = self.network.get_parameters()
host, port, protocol = deserialize_server(server) host, port, protocol = deserialize_server(server)
net_params = net_params._replace(host=host, port=port, protocol=protocol) net_params = net_params._replace(host=host, port=port, protocol=protocol)
self.network.set_parameters(net_params) self.network.run_from_another_thread(self.network.set_parameters(net_params))
self.update() self.update()
def server_changed(self, x): def server_changed(self, x):
@ -451,7 +451,7 @@ class NetworkChoiceLayout(object):
net_params = net_params._replace(host=str(self.server_host.text()), net_params = net_params._replace(host=str(self.server_host.text()),
port=str(self.server_port.text()), port=str(self.server_port.text()),
auto_connect=self.autoconnect_cb.isChecked()) auto_connect=self.autoconnect_cb.isChecked())
self.network.set_parameters(net_params) self.network.run_from_another_thread(self.network.set_parameters(net_params))
def set_proxy(self): def set_proxy(self):
net_params = self.network.get_parameters() net_params = self.network.get_parameters()
@ -465,7 +465,7 @@ class NetworkChoiceLayout(object):
proxy = None proxy = None
self.tor_cb.setChecked(False) self.tor_cb.setChecked(False)
net_params = net_params._replace(proxy=proxy) net_params = net_params._replace(proxy=proxy)
self.network.set_parameters(net_params) self.network.run_from_another_thread(self.network.set_parameters(net_params))
def suggest_proxy(self, found_proxy): def suggest_proxy(self, found_proxy):
self.tor_proxy = found_proxy self.tor_proxy = found_proxy

View File

@ -23,16 +23,19 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from PyQt5.QtCore import Qt
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from electrum.i18n import _
from .util import *
import re import re
import math import math
from PyQt5.QtCore import Qt
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from electrum.i18n import _
from electrum.plugin import run_hook from electrum.plugin import run_hook
from .util import *
def check_password_strength(password): def check_password_strength(password):
''' '''

View File

@ -23,10 +23,11 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from PyQt5.QtGui import *
import re import re
from decimal import Decimal from decimal import Decimal
from PyQt5.QtGui import *
from electrum import bitcoin from electrum import bitcoin
from electrum.util import bfh from electrum.util import bfh
from electrum.transaction import TxOutput from electrum.transaction import TxOutput
@ -40,6 +41,7 @@ RE_ALIAS = '(.*?)\s*\<([0-9A-Za-z]{1,})\>'
frozen_style = "QWidget { background-color:none; border:none;}" frozen_style = "QWidget { background-color:none; border:none;}"
normal_style = "QPlainTextEdit { }" normal_style = "QPlainTextEdit { }"
class PayToEdit(CompletionTextEdit, ScanQRTextEdit): class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
def __init__(self, win): def __init__(self, win):

View File

@ -1,3 +1,5 @@
import os
import qrcode
from PyQt5.QtCore import * from PyQt5.QtCore import *
from PyQt5.QtGui import * from PyQt5.QtGui import *
@ -5,9 +7,6 @@ import PyQt5.QtGui as QtGui
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QApplication, QVBoxLayout, QTextEdit, QHBoxLayout, QPushButton, QWidget) QApplication, QVBoxLayout, QTextEdit, QHBoxLayout, QPushButton, QWidget)
import os
import qrcode
import electrum import electrum
from electrum.i18n import _ from electrum.i18n import _
from .util import WindowModalDialog from .util import WindowModalDialog

View File

@ -1,10 +1,10 @@
from electrum.i18n import _
from electrum.plugin import run_hook
from PyQt5.QtGui import * from PyQt5.QtGui import *
from PyQt5.QtCore import * from PyQt5.QtCore import *
from PyQt5.QtWidgets import QFileDialog from PyQt5.QtWidgets import QFileDialog
from electrum.i18n import _
from electrum.plugin import run_hook
from .util import ButtonsTextEdit, MessageBoxMixin, ColorScheme from .util import ButtonsTextEdit, MessageBoxMixin, ColorScheme

View File

@ -41,6 +41,7 @@ else:
column_index = 4 column_index = 4
class QR_Window(QWidget): class QR_Window(QWidget):
def __init__(self, win): def __init__(self, win):

View File

@ -23,13 +23,15 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QTreeWidgetItem, QMenu
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import format_time, age from electrum.util import format_time, age
from electrum.plugin import run_hook from electrum.plugin import run_hook
from electrum.paymentrequest import PR_UNKNOWN from electrum.paymentrequest import PR_UNKNOWN
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QTreeWidgetItem, QMenu
from .util import MyTreeWidget, pr_tooltips, pr_icons from .util import MyTreeWidget, pr_tooltips, pr_icons

View File

@ -38,7 +38,6 @@ from electrum.bitcoin import base_encode
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import run_hook from electrum.plugin import run_hook
from electrum import simple_config from electrum import simple_config
from electrum.util import bfh from electrum.util import bfh
from electrum.transaction import SerializationError from electrum.transaction import SerializationError
@ -119,8 +118,6 @@ class TxDialog(QDialog, MessageBoxMixin):
self.add_io(vbox) self.add_io(vbox)
vbox.addStretch(1)
self.sign_button = b = QPushButton(_("Sign")) self.sign_button = b = QPushButton(_("Sign"))
b.clicked.connect(self.sign) b.clicked.connect(self.sign)
@ -300,10 +297,9 @@ class TxDialog(QDialog, MessageBoxMixin):
def format_amount(amt): def format_amount(amt):
return self.main_window.format_amount(amt, whitespaces=True) return self.main_window.format_amount(amt, whitespaces=True)
i_text = QTextEdit() i_text = QTextEditWithDefaultSize()
i_text.setFont(QFont(MONOSPACE_FONT)) i_text.setFont(QFont(MONOSPACE_FONT))
i_text.setReadOnly(True) i_text.setReadOnly(True)
i_text.setMaximumHeight(100)
cursor = i_text.textCursor() cursor = i_text.textCursor()
for x in self.tx.inputs(): for x in self.tx.inputs():
if x['type'] == 'coinbase': if x['type'] == 'coinbase':
@ -322,10 +318,9 @@ class TxDialog(QDialog, MessageBoxMixin):
vbox.addWidget(i_text) vbox.addWidget(i_text)
vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs()))) vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs())))
o_text = QTextEdit() o_text = QTextEditWithDefaultSize()
o_text.setFont(QFont(MONOSPACE_FONT)) o_text.setFont(QFont(MONOSPACE_FONT))
o_text.setReadOnly(True) o_text.setReadOnly(True)
o_text.setMaximumHeight(100)
cursor = o_text.textCursor() cursor = o_text.textCursor()
for addr, v in self.tx.get_outputs(): for addr, v in self.tx.get_outputs():
cursor.insertText(addr, text_format(addr)) cursor.insertText(addr, text_format(addr))
@ -334,3 +329,8 @@ class TxDialog(QDialog, MessageBoxMixin):
cursor.insertText(format_amount(v), ext) cursor.insertText(format_amount(v), ext)
cursor.insertBlock() cursor.insertBlock()
vbox.addWidget(o_text) vbox.addWidget(o_text)
class QTextEditWithDefaultSize(QTextEdit):
def sizeHint(self):
return QSize(0, 100)

View File

@ -22,9 +22,11 @@
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from .util import *
from electrum.i18n import _ from electrum.i18n import _
from .util import *
class UTXOList(MyTreeWidget): class UTXOList(MyTreeWidget):
filter_columns = [0, 2] # Address, Label filter_columns = [0, 2] # Address, Label

View File

@ -1,15 +1,18 @@
from decimal import Decimal from decimal import Decimal
_ = lambda x:x import getpass
#from i18n import _ import datetime
from electrum import WalletStorage, Wallet from electrum import WalletStorage, Wallet
from electrum.util import format_satoshis, set_verbosity from electrum.util import format_satoshis, set_verbosity
from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
from electrum.transaction import TxOutput from electrum.transaction import TxOutput
import getpass, datetime
_ = lambda x:x # i18n
# minimal fdisk like gui for console usage # minimal fdisk like gui for console usage
# written by rofl0r, with some bits stolen from the text gui (ncurses) # written by rofl0r, with some bits stolen from the text gui (ncurses)
class ElectrumGui: class ElectrumGui:
def __init__(self, config, daemon, plugins): def __init__(self, config, daemon, plugins):
@ -200,7 +203,8 @@ class ElectrumGui:
self.wallet.labels[tx.txid()] = self.str_description self.wallet.labels[tx.txid()] = self.str_description
print(_("Please wait...")) print(_("Please wait..."))
status, msg = self.network.broadcast_transaction_from_non_network_thread(tx) status, msg = self.network.run_from_another_thread(
self.network.broadcast_transaction(tx))
if status: if status:
print(_('Payment sent.')) print(_('Payment sent.'))

View File

@ -1,5 +1,8 @@
import tty, sys import tty
import curses, datetime, locale import sys
import curses
import datetime
import locale
from decimal import Decimal from decimal import Decimal
import getpass import getpass
@ -15,7 +18,6 @@ from electrum.interface import deserialize_server
_ = lambda x:x _ = lambda x:x
class ElectrumGui: class ElectrumGui:
def __init__(self, config, daemon, plugins): def __init__(self, config, daemon, plugins):
@ -365,7 +367,8 @@ class ElectrumGui:
self.wallet.labels[tx.txid()] = self.str_description self.wallet.labels[tx.txid()] = self.str_description
self.show_message(_("Please wait..."), getchar=False) self.show_message(_("Please wait..."), getchar=False)
status, msg = self.network.broadcast_transaction_from_non_network_thread(tx) status, msg = self.network.run_from_another_thread(
self.network.broadcast_transaction(tx))
if status: if status:
self.show_message(_('Payment sent.')) self.show_message(_('Payment sent.'))
@ -410,7 +413,8 @@ class ElectrumGui:
return False return False
if out.get('server') or out.get('proxy'): if out.get('server') or out.get('proxy'):
proxy = electrum.network.deserialize_proxy(out.get('proxy')) if out.get('proxy') else proxy_config proxy = electrum.network.deserialize_proxy(out.get('proxy')) if out.get('proxy') else proxy_config
self.network.set_parameters(NetworkParameters(host, port, protocol, proxy, auto_connect)) net_params = NetworkParameters(host, port, protocol, proxy, auto_connect)
self.network.run_from_another_thread(self.network.set_parameters(net_params))
def settings_dialog(self): def settings_dialog(self):
fee = str(Decimal(self.config.fee_per_kb()) / COIN) fee = str(Decimal(self.config.fee_per_kb()) / COIN)

View File

@ -51,8 +51,6 @@ class NotificationSession(ClientSession):
self.subscriptions = defaultdict(list) self.subscriptions = defaultdict(list)
self.cache = {} self.cache = {}
self.in_flight_requests_semaphore = asyncio.Semaphore(100) self.in_flight_requests_semaphore = asyncio.Semaphore(100)
# disable bandwidth limiting (used by superclass):
self.bw_limit = 0
async def handle_request(self, request): async def handle_request(self, request):
# note: if server sends malformed request and we raise, the superclass # note: if server sends malformed request and we raise, the superclass
@ -78,7 +76,7 @@ class NotificationSession(ClientSession):
super().send_request(*args, **kwargs), super().send_request(*args, **kwargs),
timeout) timeout)
except asyncio.TimeoutError as e: except asyncio.TimeoutError as e:
raise GracefulDisconnect('request timed out: {}'.format(args)) from e raise RequestTimedOut('request timed out: {}'.format(args)) from e
async def subscribe(self, method, params, queue): async def subscribe(self, method, params, queue):
# note: until the cache is written for the first time, # note: until the cache is written for the first time,
@ -107,11 +105,8 @@ class NotificationSession(ClientSession):
class GracefulDisconnect(Exception): pass class GracefulDisconnect(Exception): pass
class RequestTimedOut(GracefulDisconnect): pass
class ErrorParsingSSLCert(Exception): pass class ErrorParsingSSLCert(Exception): pass
class ErrorGettingSSLCertFromServer(Exception): pass class ErrorGettingSSLCertFromServer(Exception): pass
@ -135,8 +130,8 @@ def serialize_server(host: str, port: Union[str, int], protocol: str) -> str:
class Interface(PrintError): class Interface(PrintError):
def __init__(self, network, server, config_path, proxy): def __init__(self, network, server, config_path, proxy):
self.exception = None
self.ready = asyncio.Future() self.ready = asyncio.Future()
self.got_disconnected = asyncio.Future()
self.server = server self.server = server
self.host, self.port, self.protocol = deserialize_server(self.server) self.host, self.port, self.protocol = deserialize_server(self.server)
self.port = int(self.port) self.port = int(self.port)
@ -146,12 +141,16 @@ class Interface(PrintError):
self._requested_chunks = set() self._requested_chunks = set()
self.network = network self.network = network
self._set_proxy(proxy) self._set_proxy(proxy)
self.session = None
self.tip_header = None self.tip_header = None
self.tip = 0 self.tip = 0
# TODO combine? # note that an interface dying MUST NOT kill the whole network,
self.fut = asyncio.get_event_loop().create_task(self.run()) # hence exceptions raised by "run" need to be caught not to kill
# main_taskgroup! the aiosafe decorator does this.
asyncio.run_coroutine_threadsafe(
self.network.main_taskgroup.spawn(self.run()), self.network.asyncio_loop)
self.group = SilentTaskGroup() self.group = SilentTaskGroup()
def diagnostic_name(self): def diagnostic_name(self):
@ -239,31 +238,30 @@ class Interface(PrintError):
sslc.check_hostname = 0 sslc.check_hostname = 0
return sslc return sslc
def handle_graceful_disconnect(func): def handle_disconnect(func):
async def wrapper_func(self, *args, **kwargs): async def wrapper_func(self, *args, **kwargs):
try: try:
return await func(self, *args, **kwargs) return await func(self, *args, **kwargs)
except GracefulDisconnect as e: except GracefulDisconnect as e:
self.print_error("disconnecting gracefully. {}".format(e)) self.print_error("disconnecting gracefully. {}".format(e))
self.exception = e finally:
await self.network.connection_down(self.server)
self.got_disconnected.set_result(1)
return wrapper_func return wrapper_func
@aiosafe @aiosafe
@handle_graceful_disconnect @handle_disconnect
async def run(self): async def run(self):
try: try:
ssl_context = await self._get_ssl_context() ssl_context = await self._get_ssl_context()
except (ErrorParsingSSLCert, ErrorGettingSSLCertFromServer) as e: except (ErrorParsingSSLCert, ErrorGettingSSLCertFromServer) as e:
self.exception = e self.print_error('disconnecting due to: {} {}'.format(e, type(e)))
return return
try: try:
await self.open_session(ssl_context, exit_early=False) await self.open_session(ssl_context, exit_early=False)
except (asyncio.CancelledError, OSError, aiorpcx.socks.SOCKSFailure) as e: except (asyncio.CancelledError, OSError, aiorpcx.socks.SOCKSFailure) as e:
self.print_error('disconnecting due to: {} {}'.format(e, type(e))) self.print_error('disconnecting due to: {} {}'.format(e, type(e)))
self.exception = e
return return
# should never get here (can only exit via exception)
assert False
def mark_ready(self): def mark_ready(self):
if self.ready.cancelled(): if self.ready.cancelled():
@ -352,9 +350,9 @@ class Interface(PrintError):
self.print_error("connection established. version: {}".format(ver)) self.print_error("connection established. version: {}".format(ver))
async with self.group as group: async with self.group as group:
await group.spawn(self.ping()) await group.spawn(self.ping)
await group.spawn(self.run_fetch_blocks()) await group.spawn(self.run_fetch_blocks)
await group.spawn(self.monitor_connection()) await group.spawn(self.monitor_connection)
# NOTE: group.__aexit__ will be called here; this is needed to notice exceptions in the group! # NOTE: group.__aexit__ will be called here; this is needed to notice exceptions in the group!
async def monitor_connection(self): async def monitor_connection(self):
@ -368,11 +366,8 @@ class Interface(PrintError):
await asyncio.sleep(300) await asyncio.sleep(300)
await self.session.send_request('server.ping') await self.session.send_request('server.ping')
def close(self): async def close(self):
async def job(): await self.group.cancel_remaining()
self.fut.cancel()
await self.group.cancel_remaining()
asyncio.run_coroutine_threadsafe(job(), self.network.asyncio_loop)
async def run_fetch_blocks(self): async def run_fetch_blocks(self):
header_queue = asyncio.Queue() header_queue = asyncio.Queue()
@ -389,7 +384,7 @@ class Interface(PrintError):
self.mark_ready() self.mark_ready()
await self._process_header_at_tip() await self._process_header_at_tip()
self.network.trigger_callback('network_updated') self.network.trigger_callback('network_updated')
self.network.switch_lagging_interface() await self.network.switch_lagging_interface()
async def _process_header_at_tip(self): async def _process_header_at_tip(self):
height, header = self.tip, self.tip_header height, header = self.tip, self.tip_header
@ -517,7 +512,7 @@ class Interface(PrintError):
return 'fork_conflict', height return 'fork_conflict', height
self.print_error('forkpoint conflicts with existing fork', branch.path()) self.print_error('forkpoint conflicts with existing fork', branch.path())
self._raise_if_fork_conflicts_with_default_server(branch) self._raise_if_fork_conflicts_with_default_server(branch)
self._disconnect_from_interfaces_on_conflicting_blockchain(branch) await self._disconnect_from_interfaces_on_conflicting_blockchain(branch)
branch.write(b'', 0) branch.write(b'', 0)
branch.save_header(bad_header) branch.save_header(bad_header)
self.blockchain = branch self.blockchain = branch
@ -543,8 +538,8 @@ class Interface(PrintError):
if chain_to_delete == chain_of_default_server: if chain_to_delete == chain_of_default_server:
raise GracefulDisconnect('refusing to overwrite blockchain of default server') raise GracefulDisconnect('refusing to overwrite blockchain of default server')
def _disconnect_from_interfaces_on_conflicting_blockchain(self, chain: Blockchain) -> None: async def _disconnect_from_interfaces_on_conflicting_blockchain(self, chain: Blockchain) -> None:
ifaces = self.network.disconnect_from_interfaces_on_given_blockchain(chain) ifaces = await self.network.disconnect_from_interfaces_on_given_blockchain(chain)
if not ifaces: return if not ifaces: return
servers = [interface.server for interface in ifaces] servers = [interface.server for interface in ifaces]
self.print_error("forcing disconnect of other interfaces: {}".format(servers)) self.print_error("forcing disconnect of other interfaces: {}".format(servers))

View File

@ -32,19 +32,20 @@ import json
import sys import sys
import ipaddress import ipaddress
import asyncio import asyncio
from typing import NamedTuple, Optional, Sequence from typing import NamedTuple, Optional, Sequence, List
import traceback
import dns import dns
import dns.resolver import dns.resolver
from aiorpcx import TaskGroup from aiorpcx import TaskGroup
from . import util from . import util
from .util import PrintError, print_error, aiosafe, bfh from .util import PrintError, print_error, aiosafe, bfh, SilentTaskGroup
from .bitcoin import COIN from .bitcoin import COIN
from . import constants from . import constants
from . import blockchain from . import blockchain
from .blockchain import Blockchain from .blockchain import Blockchain, HEADER_SIZE
from .interface import Interface, serialize_server, deserialize_server from .interface import Interface, serialize_server, deserialize_server, RequestTimedOut
from .version import PROTOCOL_VERSION from .version import PROTOCOL_VERSION
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
@ -160,14 +161,6 @@ INSTANCE = None
class Network(PrintError): class Network(PrintError):
"""The Network class manages a set of connections to remote electrum """The Network class manages a set of connections to remote electrum
servers, each connected socket is handled by an Interface() object. servers, each connected socket is handled by an Interface() object.
Connections are initiated by a Connection() thread which stops once
the connection succeeds or fails.
Our external API:
- Member functions get_header(), get_interfaces(), get_local_height(),
get_parameters(), get_server_height(), get_status_value(),
is_connected(), set_parameters(), stop()
""" """
verbosity_filter = 'n' verbosity_filter = 'n'
@ -195,14 +188,18 @@ class Network(PrintError):
if not self.default_server: if not self.default_server:
self.default_server = pick_random_server() self.default_server = pick_random_server()
# locks: if you need to take multiple ones, acquire them in the order they are defined here! self.main_taskgroup = None
self._jobs = []
# locks
self.restart_lock = asyncio.Lock()
self.bhi_lock = asyncio.Lock() self.bhi_lock = asyncio.Lock()
self.interface_lock = threading.RLock() # <- re-entrant
self.callback_lock = threading.Lock() self.callback_lock = threading.Lock()
self.recent_servers_lock = threading.RLock() # <- re-entrant self.recent_servers_lock = threading.RLock() # <- re-entrant
self.interfaces_lock = threading.Lock() # for mutating/iterating self.interfaces
self.server_peers = {} # returned by interface (servers that the main interface knows about) self.server_peers = {} # returned by interface (servers that the main interface knows about)
self.recent_servers = self.read_recent_servers() # note: needs self.recent_servers_lock self.recent_servers = self._read_recent_servers() # note: needs self.recent_servers_lock
self.banner = '' self.banner = ''
self.donation_address = '' self.donation_address = ''
@ -219,26 +216,30 @@ class Network(PrintError):
# kick off the network. interface is the main server we are currently # kick off the network. interface is the main server we are currently
# communicating with. interfaces is the set of servers we are connecting # communicating with. interfaces is the set of servers we are connecting
# to or have an ongoing connection with # to or have an ongoing connection with
self.interface = None # note: needs self.interface_lock self.interface = None # type: Interface
self.interfaces = {} # note: needs self.interface_lock self.interfaces = {}
self.auto_connect = self.config.get('auto_connect', True) self.auto_connect = self.config.get('auto_connect', True)
self.connecting = set() self.connecting = set()
self.server_queue = None self.server_queue = None
self.server_queue_group = None self.proxy = None
self.asyncio_loop = asyncio.get_event_loop() self.asyncio_loop = asyncio.get_event_loop()
self.start_network(deserialize_server(self.default_server)[2], #self.asyncio_loop.set_debug(1)
deserialize_proxy(self.config.get('proxy'))) self._run_forever = asyncio.Future()
self._thread = threading.Thread(target=self.asyncio_loop.run_until_complete,
args=(self._run_forever,),
name='Network')
self._thread.start()
def run_from_another_thread(self, coro):
assert self._thread != threading.current_thread(), 'must not be called from network thread'
fut = asyncio.run_coroutine_threadsafe(coro, self.asyncio_loop)
return fut.result()
@staticmethod @staticmethod
def get_instance(): def get_instance():
return INSTANCE return INSTANCE
def with_interface_lock(func):
def func_wrapper(self, *args, **kwargs):
with self.interface_lock:
return func(self, *args, **kwargs)
return func_wrapper
def with_recent_servers_lock(func): def with_recent_servers_lock(func):
def func_wrapper(self, *args, **kwargs): def func_wrapper(self, *args, **kwargs):
with self.recent_servers_lock: with self.recent_servers_lock:
@ -266,7 +267,7 @@ class Network(PrintError):
else: else:
self.asyncio_loop.call_soon_threadsafe(callback, event, *args) self.asyncio_loop.call_soon_threadsafe(callback, event, *args)
def read_recent_servers(self): def _read_recent_servers(self):
if not self.config.path: if not self.config.path:
return [] return []
path = os.path.join(self.config.path, "recent_servers") path = os.path.join(self.config.path, "recent_servers")
@ -278,7 +279,7 @@ class Network(PrintError):
return [] return []
@with_recent_servers_lock @with_recent_servers_lock
def save_recent_servers(self): def _save_recent_servers(self):
if not self.config.path: if not self.config.path:
return return
path = os.path.join(self.config.path, "recent_servers") path = os.path.join(self.config.path, "recent_servers")
@ -289,11 +290,11 @@ class Network(PrintError):
except: except:
pass pass
@with_interface_lock
def get_server_height(self): def get_server_height(self):
return self.interface.tip if self.interface else 0 interface = self.interface
return interface.tip if interface else 0
def server_is_lagging(self): async def _server_is_lagging(self):
sh = self.get_server_height() sh = self.get_server_height()
if not sh: if not sh:
self.print_error('no height for main interface') self.print_error('no height for main interface')
@ -304,7 +305,7 @@ class Network(PrintError):
self.print_error('%s is lagging (%d vs %d)' % (self.default_server, sh, lh)) self.print_error('%s is lagging (%d vs %d)' % (self.default_server, sh, lh))
return result return result
def set_status(self, status): def _set_status(self, status):
self.connection_status = status self.connection_status = status
self.notify('status') self.notify('status')
@ -315,7 +316,7 @@ class Network(PrintError):
def is_connecting(self): def is_connecting(self):
return self.connection_status == 'connecting' return self.connection_status == 'connecting'
async def request_server_info(self, interface): async def _request_server_info(self, interface):
await interface.ready await interface.ready
session = interface.session session = interface.session
@ -340,9 +341,9 @@ class Network(PrintError):
await group.spawn(get_donation_address) await group.spawn(get_donation_address)
await group.spawn(get_server_peers) await group.spawn(get_server_peers)
await group.spawn(get_relay_fee) await group.spawn(get_relay_fee)
await group.spawn(self.request_fee_estimates(interface)) await group.spawn(self._request_fee_estimates(interface))
async def request_fee_estimates(self, interface): async def _request_fee_estimates(self, interface):
session = interface.session session = interface.session
from .simple_config import FEE_ETA_TARGETS from .simple_config import FEE_ETA_TARGETS
self.config.requested_fee_estimates() self.config.requested_fee_estimates()
@ -389,10 +390,10 @@ class Network(PrintError):
if self.is_connected(): if self.is_connected():
return self.donation_address return self.donation_address
@with_interface_lock def get_interfaces(self) -> List[str]:
def get_interfaces(self): """The list of servers for the connected interfaces."""
'''The interfaces that are in connected state''' with self.interfaces_lock:
return list(self.interfaces.keys()) return list(self.interfaces)
@with_recent_servers_lock @with_recent_servers_lock
def get_servers(self): def get_servers(self):
@ -407,31 +408,31 @@ class Network(PrintError):
if host not in out: if host not in out:
out[host] = {protocol: port} out[host] = {protocol: port}
# add servers received from main interface # add servers received from main interface
if self.server_peers: server_peers = self.server_peers
out.update(filter_version(self.server_peers.copy())) if server_peers:
out.update(filter_version(server_peers.copy()))
# potentially filter out some # potentially filter out some
if self.config.get('noonion'): if self.config.get('noonion'):
out = filter_noonion(out) out = filter_noonion(out)
return out return out
@with_interface_lock def _start_interface(self, server):
def start_interface(self, server):
if server not in self.interfaces and server not in self.connecting: if server not in self.interfaces and server not in self.connecting:
if server == self.default_server: if server == self.default_server:
self.print_error("connecting to %s as new interface" % server) self.print_error("connecting to %s as new interface" % server)
self.set_status('connecting') self._set_status('connecting')
self.connecting.add(server) self.connecting.add(server)
self.server_queue.put(server) self.server_queue.put(server)
def start_random_interface(self): def _start_random_interface(self):
with self.interface_lock: with self.interfaces_lock:
exclude_set = self.disconnected_servers | set(self.interfaces) | self.connecting exclude_set = self.disconnected_servers | set(self.interfaces) | self.connecting
server = pick_random_server(self.get_servers(), self.protocol, exclude_set) server = pick_random_server(self.get_servers(), self.protocol, exclude_set)
if server: if server:
self.start_interface(server) self._start_interface(server)
return server return server
def set_proxy(self, proxy: Optional[dict]): def _set_proxy(self, proxy: Optional[dict]):
self.proxy = proxy self.proxy = proxy
# Store these somewhere so we can un-monkey-patch # Store these somewhere so we can un-monkey-patch
if not hasattr(socket, "_getaddrinfo"): if not hasattr(socket, "_getaddrinfo"):
@ -467,10 +468,10 @@ class Network(PrintError):
addr = str(answers[0]) addr = str(answers[0])
else: else:
addr = host addr = host
except dns.exception.DNSException: except dns.exception.DNSException as e:
# dns failed for some reason, e.g. dns.resolver.NXDOMAIN # dns failed for some reason, e.g. dns.resolver.NXDOMAIN
# this is normal. Simply report back failure: # this is normal. Simply report back failure:
raise socket.gaierror(11001, 'getaddrinfo failed') raise socket.gaierror(11001, 'getaddrinfo failed') from e
except BaseException as e: except BaseException as e:
# Possibly internal error in dnspython :( see #4483 # Possibly internal error in dnspython :( see #4483
# Fall back to original socket.getaddrinfo to resolve dns. # Fall back to original socket.getaddrinfo to resolve dns.
@ -478,48 +479,8 @@ class Network(PrintError):
addr = host addr = host
return socket._getaddrinfo(addr, *args, **kwargs) return socket._getaddrinfo(addr, *args, **kwargs)
@with_interface_lock @aiosafe
def start_network(self, protocol: str, proxy: Optional[dict]): async def set_parameters(self, net_params: NetworkParameters):
assert not self.interface and not self.interfaces
assert not self.connecting and not self.server_queue
assert not self.server_queue_group
self.print_error('starting network')
self.disconnected_servers = set([]) # note: needs self.interface_lock
self.protocol = protocol
self._init_server_queue()
self.set_proxy(proxy)
self.start_interface(self.default_server)
self.trigger_callback('network_updated')
def _init_server_queue(self):
self.server_queue = queue.Queue()
self.server_queue_group = server_queue_group = TaskGroup()
async def job():
forever = asyncio.Event()
async with server_queue_group as group:
await group.spawn(forever.wait())
asyncio.run_coroutine_threadsafe(job(), self.asyncio_loop)
@with_interface_lock
def stop_network(self):
self.print_error("stopping network")
for interface in list(self.interfaces.values()):
self.close_interface(interface)
if self.interface:
self.close_interface(self.interface)
assert self.interface is None
assert not self.interfaces
self.connecting.clear()
self._stop_server_queue()
self.trigger_callback('network_updated')
def _stop_server_queue(self):
# Get a new queue - no old pending connections thanks!
self.server_queue = None
asyncio.run_coroutine_threadsafe(self.server_queue_group.cancel_remaining(), self.asyncio_loop)
self.server_queue_group = None
def set_parameters(self, net_params: NetworkParameters):
proxy = net_params.proxy proxy = net_params.proxy
proxy_str = serialize_proxy(proxy) proxy_str = serialize_proxy(proxy)
host, port, protocol = net_params.host, net_params.port, net_params.protocol host, port, protocol = net_params.host, net_params.port, net_params.protocol
@ -538,30 +499,30 @@ class Network(PrintError):
# abort if changes were not allowed by config # abort if changes were not allowed by config
if self.config.get('server') != server_str or self.config.get('proxy') != proxy_str: if self.config.get('server') != server_str or self.config.get('proxy') != proxy_str:
return return
self.auto_connect = net_params.auto_connect
if self.proxy != proxy or self.protocol != protocol:
# Restart the network defaulting to the given server
with self.interface_lock:
self.stop_network()
self.default_server = server_str
self.start_network(protocol, proxy)
elif self.default_server != server_str:
self.switch_to_interface(server_str)
else:
self.switch_lagging_interface()
def switch_to_random_interface(self): async with self.restart_lock:
self.auto_connect = net_params.auto_connect
if self.proxy != proxy or self.protocol != protocol:
# Restart the network defaulting to the given server
await self._stop()
self.default_server = server_str
await self._start()
elif self.default_server != server_str:
await self.switch_to_interface(server_str)
else:
await self.switch_lagging_interface()
async def _switch_to_random_interface(self):
'''Switch to a random connected server other than the current one''' '''Switch to a random connected server other than the current one'''
servers = self.get_interfaces() # Those in connected state servers = self.get_interfaces() # Those in connected state
if self.default_server in servers: if self.default_server in servers:
servers.remove(self.default_server) servers.remove(self.default_server)
if servers: if servers:
self.switch_to_interface(random.choice(servers)) await self.switch_to_interface(random.choice(servers))
@with_interface_lock async def switch_lagging_interface(self):
def switch_lagging_interface(self):
'''If auto_connect and lagging, switch interface''' '''If auto_connect and lagging, switch interface'''
if self.server_is_lagging() and self.auto_connect: if await self._server_is_lagging() and self.auto_connect:
# switch to one that has the correct header (not height) # switch to one that has the correct header (not height)
header = self.blockchain().read_header(self.get_local_height()) header = self.blockchain().read_header(self.get_local_height())
def filt(x): def filt(x):
@ -569,111 +530,105 @@ class Network(PrintError):
b = header b = header
assert type(a) is type(b) assert type(a) is type(b)
return a == b return a == b
filtered = list(map(lambda x: x[0], filter(filt, self.interfaces.items())))
with self.interfaces_lock: interfaces_items = list(self.interfaces.items())
filtered = list(map(lambda x: x[0], filter(filt, interfaces_items)))
if filtered: if filtered:
choice = random.choice(filtered) choice = random.choice(filtered)
self.switch_to_interface(choice) await self.switch_to_interface(choice)
@with_interface_lock async def switch_to_interface(self, server: str):
def switch_to_interface(self, server): """Switch to server as our main interface. If no connection exists,
'''Switch to server as our interface. If no connection exists nor queue interface to be started. The actual switch will
being opened, start a thread to connect. The actual switch will happen when the interface becomes ready.
happen on receipt of the connection notification. Do nothing """
if server already is our interface.'''
self.default_server = server self.default_server = server
old_interface = self.interface
old_server = old_interface.server if old_interface else None
# Stop any current interface in order to terminate subscriptions,
# and to cancel tasks in interface.group.
# However, for headers sub, give preference to this interface
# over unknown ones, i.e. start it again right away.
if old_server and old_server != server:
await self._close_interface(old_interface)
if len(self.interfaces) <= self.num_server:
self._start_interface(old_server)
if server not in self.interfaces: if server not in self.interfaces:
self.interface = None self.interface = None
self.start_interface(server) self._start_interface(server)
return return
i = self.interfaces[server] i = self.interfaces[server]
if self.interface != i: if old_interface != i:
self.print_error("switching to", server) self.print_error("switching to", server)
blockchain_updated = False blockchain_updated = i.blockchain != self.blockchain()
if self.interface is not None:
blockchain_updated = i.blockchain != self.interface.blockchain
# Stop any current interface in order to terminate subscriptions,
# and to cancel tasks in interface.group.
# However, for headers sub, give preference to this interface
# over unknown ones, i.e. start it again right away.
old_server = self.interface.server
self.close_interface(self.interface)
if old_server != server and len(self.interfaces) <= self.num_server:
self.start_interface(old_server)
self.interface = i self.interface = i
asyncio.run_coroutine_threadsafe( await i.group.spawn(self._request_server_info(i))
i.group.spawn(self.request_server_info(i)), self.asyncio_loop)
self.trigger_callback('default_server_changed') self.trigger_callback('default_server_changed')
self.set_status('connected') self._set_status('connected')
self.trigger_callback('network_updated') self.trigger_callback('network_updated')
if blockchain_updated: self.trigger_callback('blockchain_updated') if blockchain_updated: self.trigger_callback('blockchain_updated')
@with_interface_lock async def _close_interface(self, interface):
def close_interface(self, interface):
if interface: if interface:
if interface.server in self.interfaces: with self.interfaces_lock:
self.interfaces.pop(interface.server) if self.interfaces.get(interface.server) == interface:
self.interfaces.pop(interface.server)
if interface.server == self.default_server: if interface.server == self.default_server:
self.interface = None self.interface = None
interface.close() await interface.close()
@with_recent_servers_lock @with_recent_servers_lock
def add_recent_server(self, server): def _add_recent_server(self, server):
# list is ordered # list is ordered
if server in self.recent_servers: if server in self.recent_servers:
self.recent_servers.remove(server) self.recent_servers.remove(server)
self.recent_servers.insert(0, server) self.recent_servers.insert(0, server)
self.recent_servers = self.recent_servers[0:20] self.recent_servers = self.recent_servers[0:20]
self.save_recent_servers() self._save_recent_servers()
@with_interface_lock async def connection_down(self, server):
def connection_down(self, server):
'''A connection to server either went down, or was never made. '''A connection to server either went down, or was never made.
We distinguish by whether it is in self.interfaces.''' We distinguish by whether it is in self.interfaces.'''
self.disconnected_servers.add(server) self.disconnected_servers.add(server)
if server == self.default_server: if server == self.default_server:
self.set_status('disconnected') self._set_status('disconnected')
if server in self.interfaces: interface = self.interfaces.get(server, None)
self.close_interface(self.interfaces[server]) if interface:
await self._close_interface(interface)
self.trigger_callback('network_updated') self.trigger_callback('network_updated')
@aiosafe @aiosafe
async def new_interface(self, server): async def _run_new_interface(self, server):
interface = Interface(self, server, self.config.path, self.proxy) interface = Interface(self, server, self.config.path, self.proxy)
timeout = 10 if not self.proxy else 20 timeout = 10 if not self.proxy else 20
try: try:
await asyncio.wait_for(interface.ready, timeout) await asyncio.wait_for(interface.ready, timeout)
except BaseException as e: except BaseException as e:
#import traceback
#traceback.print_exc() #traceback.print_exc()
self.print_error(server, "couldn't launch because", str(e), str(type(e))) self.print_error(server, "couldn't launch because", str(e), str(type(e)))
# note: connection_down will not call interface.close() as await interface.close()
# interface is not yet in self.interfaces. OTOH, calling
# interface.close() here will sometimes raise deep inside the
# asyncio internal select.select... instead, interface will close
# itself when it detects the cancellation of interface.ready;
# however this might take several seconds...
self.connection_down(server)
return return
else: else:
with self.interface_lock: with self.interfaces_lock:
assert server not in self.interfaces
self.interfaces[server] = interface self.interfaces[server] = interface
finally: finally:
with self.interface_lock: try: self.connecting.remove(server)
try: self.connecting.remove(server) except KeyError: pass
except KeyError: pass
if server == self.default_server: if server == self.default_server:
self.switch_to_interface(server) await self.switch_to_interface(server)
self.add_recent_server(server) self._add_recent_server(server)
self.trigger_callback('network_updated') self.trigger_callback('network_updated')
def init_headers_file(self): async def _init_headers_file(self):
b = blockchain.blockchains[0] b = blockchain.blockchains[0]
filename = b.path() filename = b.path()
length = 80 * len(constants.net.CHECKPOINTS) * 2016 length = HEADER_SIZE * len(constants.net.CHECKPOINTS) * 2016
if not os.path.exists(filename) or os.path.getsize(filename) < length: if not os.path.exists(filename) or os.path.getsize(filename) < length:
with open(filename, 'wb') as f: with open(filename, 'wb') as f:
if length > 0: if length > 0:
@ -683,18 +638,46 @@ class Network(PrintError):
with b.lock: with b.lock:
b.update_size() b.update_size()
async def get_merkle_for_transaction(self, tx_hash, tx_height): def best_effort_reliable(func):
async def make_reliable_wrapper(self, *args, **kwargs):
for i in range(10):
iface = self.interface
# retry until there is a main interface
if not iface:
await asyncio.sleep(0.1)
continue # try again
# wait for it to be usable
iface_ready = iface.ready
iface_disconnected = iface.got_disconnected
await asyncio.wait([iface_ready, iface_disconnected], return_when=asyncio.FIRST_COMPLETED)
if not iface_ready.done() or iface_ready.cancelled():
await asyncio.sleep(0.1)
continue # try again
# try actual request
success_fut = asyncio.ensure_future(func(self, *args, **kwargs))
await asyncio.wait([success_fut, iface_disconnected], return_when=asyncio.FIRST_COMPLETED)
if success_fut.done() and not success_fut.cancelled():
if success_fut.exception():
try:
raise success_fut.exception()
except RequestTimedOut:
await iface.close()
await iface_disconnected
continue # try again
return success_fut.result()
# otherwise; try again
raise Exception('no interface to do request on... gave up.')
return make_reliable_wrapper
@best_effort_reliable
async def get_merkle_for_transaction(self, tx_hash: str, tx_height: int) -> dict:
return await self.interface.session.send_request('blockchain.transaction.get_merkle', [tx_hash, tx_height]) return await self.interface.session.send_request('blockchain.transaction.get_merkle', [tx_hash, tx_height])
def broadcast_transaction_from_non_network_thread(self, tx, timeout=10): @best_effort_reliable
# note: calling this from the network thread will deadlock it
fut = asyncio.run_coroutine_threadsafe(self.broadcast_transaction(tx, timeout=timeout), self.asyncio_loop)
return fut.result()
async def broadcast_transaction(self, tx, timeout=10): async def broadcast_transaction(self, tx, timeout=10):
try: try:
out = await self.interface.session.send_request('blockchain.transaction.broadcast', [str(tx)], timeout=timeout) out = await self.interface.session.send_request('blockchain.transaction.broadcast', [str(tx)], timeout=timeout)
except asyncio.TimeoutError as e: except RequestTimedOut as e:
return False, "error: operation timed out" return False, "error: operation timed out"
except Exception as e: except Exception as e:
return False, "error: " + str(e) return False, "error: " + str(e)
@ -703,104 +686,144 @@ class Network(PrintError):
return False, "error: " + out return False, "error: " + out
return True, out return True, out
@best_effort_reliable
async def request_chunk(self, height, tip=None, *, can_return_early=False): async def request_chunk(self, height, tip=None, *, can_return_early=False):
return await self.interface.request_chunk(height, tip=tip, can_return_early=can_return_early) return await self.interface.request_chunk(height, tip=tip, can_return_early=can_return_early)
@with_interface_lock @best_effort_reliable
def blockchain(self): async def get_transaction(self, tx_hash: str) -> str:
if self.interface and self.interface.blockchain is not None: return await self.interface.session.send_request('blockchain.transaction.get', [tx_hash])
self.blockchain_index = self.interface.blockchain.forkpoint
@best_effort_reliable
async def get_history_for_scripthash(self, sh: str) -> List[dict]:
return await self.interface.session.send_request('blockchain.scripthash.get_history', [sh])
@best_effort_reliable
async def listunspent_for_scripthash(self, sh: str) -> List[dict]:
return await self.interface.session.send_request('blockchain.scripthash.listunspent', [sh])
@best_effort_reliable
async def get_balance_for_scripthash(self, sh: str) -> dict:
return await self.interface.session.send_request('blockchain.scripthash.get_balance', [sh])
def blockchain(self) -> Blockchain:
interface = self.interface
if interface and interface.blockchain is not None:
self.blockchain_index = interface.blockchain.forkpoint
return blockchain.blockchains[self.blockchain_index] return blockchain.blockchains[self.blockchain_index]
@with_interface_lock
def get_blockchains(self): def get_blockchains(self):
out = {} # blockchain_id -> list(interfaces) out = {} # blockchain_id -> list(interfaces)
with blockchain.blockchains_lock: blockchain_items = list(blockchain.blockchains.items()) with blockchain.blockchains_lock: blockchain_items = list(blockchain.blockchains.items())
with self.interfaces_lock: interfaces_values = list(self.interfaces.values())
for chain_id, bc in blockchain_items: for chain_id, bc in blockchain_items:
r = list(filter(lambda i: i.blockchain==bc, list(self.interfaces.values()))) r = list(filter(lambda i: i.blockchain==bc, interfaces_values))
if r: if r:
out[chain_id] = r out[chain_id] = r
return out return out
@with_interface_lock async def disconnect_from_interfaces_on_given_blockchain(self, chain: Blockchain) -> Sequence[Interface]:
def disconnect_from_interfaces_on_given_blockchain(self, chain: Blockchain) -> Sequence[Interface]:
chain_id = chain.forkpoint chain_id = chain.forkpoint
ifaces = self.get_blockchains().get(chain_id) or [] ifaces = self.get_blockchains().get(chain_id) or []
for interface in ifaces: for interface in ifaces:
self.connection_down(interface.server) await self.connection_down(interface.server)
return ifaces return ifaces
def follow_chain(self, index): async def follow_chain(self, chain_id):
bc = blockchain.blockchains.get(index) bc = blockchain.blockchains.get(chain_id)
if bc: if bc:
self.blockchain_index = index self.blockchain_index = chain_id
self.config.set_key('blockchain_index', index) self.config.set_key('blockchain_index', chain_id)
with self.interface_lock: with self.interfaces_lock: interfaces_values = list(self.interfaces.values())
interfaces = list(self.interfaces.values()) for iface in interfaces_values:
for i in interfaces: if iface.blockchain == bc:
if i.blockchain == bc: await self.switch_to_interface(iface.server)
self.switch_to_interface(i.server)
break break
else: else:
raise Exception('blockchain not found', index) raise Exception('blockchain not found', chain_id)
with self.interface_lock: if self.interface:
if self.interface: net_params = self.get_parameters()
net_params = self.get_parameters() host, port, protocol = deserialize_server(self.interface.server)
host, port, protocol = deserialize_server(self.interface.server) net_params = net_params._replace(host=host, port=port, protocol=protocol)
net_params = net_params._replace(host=host, port=port, protocol=protocol) await self.set_parameters(net_params)
self.set_parameters(net_params)
def get_local_height(self): def get_local_height(self):
return self.blockchain().height() return self.blockchain().height()
def export_checkpoints(self, path): def export_checkpoints(self, path):
# run manually from the console to generate checkpoints """Run manually to generate blockchain checkpoints.
Kept for console use only.
"""
cp = self.blockchain().get_checkpoints() cp = self.blockchain().get_checkpoints()
with open(path, 'w', encoding='utf-8') as f: with open(path, 'w', encoding='utf-8') as f:
f.write(json.dumps(cp, indent=4)) f.write(json.dumps(cp, indent=4))
def start(self, fx=None): async def _start(self, jobs=None):
self.main_taskgroup = TaskGroup() if jobs is None: jobs = self._jobs
self._jobs = jobs
assert not self.main_taskgroup
self.main_taskgroup = SilentTaskGroup()
async def main(): async def main():
self.init_headers_file() try:
async with self.main_taskgroup as group: await self._init_headers_file()
await group.spawn(self.maintain_sessions()) async with self.main_taskgroup as group:
if fx: await group.spawn(fx) await group.spawn(self._maintain_sessions())
self._wrapper_thread = threading.Thread(target=self.asyncio_loop.run_until_complete, args=(main(),)) [await group.spawn(job) for job in jobs]
self._wrapper_thread.start() except Exception as e:
traceback.print_exc(file=sys.stderr)
raise e
asyncio.run_coroutine_threadsafe(main(), self.asyncio_loop)
assert not self.interface and not self.interfaces
assert not self.connecting and not self.server_queue
self.print_error('starting network')
self.disconnected_servers = set([])
self.protocol = deserialize_server(self.default_server)[2]
self.server_queue = queue.Queue()
self._set_proxy(deserialize_proxy(self.config.get('proxy')))
self._start_interface(self.default_server)
self.trigger_callback('network_updated')
def start(self, jobs=None):
asyncio.run_coroutine_threadsafe(self._start(jobs=jobs), self.asyncio_loop)
async def _stop(self, full_shutdown=False):
self.print_error("stopping network")
try:
asyncio.wait_for(await self.main_taskgroup.cancel_remaining(), timeout=2)
except asyncio.TimeoutError: pass
self.main_taskgroup = None
assert self.interface is None
assert not self.interfaces
self.connecting.clear()
self.server_queue = None
self.trigger_callback('network_updated')
if full_shutdown:
self._run_forever.set_result(1)
def stop(self): def stop(self):
asyncio.run_coroutine_threadsafe(self.main_taskgroup.cancel_remaining(), self.asyncio_loop) assert self._thread != threading.current_thread(), 'must not be called from network thread'
fut = asyncio.run_coroutine_threadsafe(self._stop(full_shutdown=True), self.asyncio_loop)
fut.result()
def join(self): def join(self):
self._wrapper_thread.join(1) self._thread.join(1)
async def maintain_sessions(self): async def _maintain_sessions(self):
while True: while True:
# launch already queued up new interfaces
while self.server_queue.qsize() > 0: while self.server_queue.qsize() > 0:
server = self.server_queue.get() server = self.server_queue.get()
await self.server_queue_group.spawn(self.new_interface(server)) await self.main_taskgroup.spawn(self._run_new_interface(server))
remove = []
for k, i in self.interfaces.items():
if i.fut.done() and not i.exception:
assert False, "interface future should not finish without exception"
if i.exception:
if not i.fut.done():
try: i.fut.cancel()
except Exception as e: self.print_error('exception while cancelling fut', e)
try:
raise i.exception
except BaseException as e:
self.print_error(i.server, "errored because:", str(e), str(type(e)))
remove.append(k)
for k in remove:
self.connection_down(k)
# nodes # maybe queue new interfaces to be launched later
now = time.time() now = time.time()
for i in range(self.num_server - len(self.interfaces) - len(self.connecting)): for i in range(self.num_server - len(self.interfaces) - len(self.connecting)):
self.start_random_interface() self._start_random_interface()
if now - self.nodes_retry_time > NODES_RETRY_INTERVAL: if now - self.nodes_retry_time > NODES_RETRY_INTERVAL:
self.print_error('network: retrying connections') self.print_error('network: retrying connections')
self.disconnected_servers = set([]) self.disconnected_servers = set([])
@ -810,16 +833,16 @@ class Network(PrintError):
if not self.is_connected(): if not self.is_connected():
if self.auto_connect: if self.auto_connect:
if not self.is_connecting(): if not self.is_connecting():
self.switch_to_random_interface() await self._switch_to_random_interface()
else: else:
if self.default_server in self.disconnected_servers: if self.default_server in self.disconnected_servers:
if now - self.server_retry_time > SERVER_RETRY_INTERVAL: if now - self.server_retry_time > SERVER_RETRY_INTERVAL:
self.disconnected_servers.remove(self.default_server) self.disconnected_servers.remove(self.default_server)
self.server_retry_time = now self.server_retry_time = now
else: else:
self.switch_to_interface(self.default_server) await self.switch_to_interface(self.default_server)
else: else:
if self.config.is_fee_estimates_update_required(): if self.config.is_fee_estimates_update_required():
await self.interface.group.spawn(self.request_fee_estimates, self.interface) await self.interface.group.spawn(self._request_fee_estimates, self.interface)
await asyncio.sleep(0.1) await asyncio.sleep(0.1)

View File

@ -47,6 +47,7 @@ class Plugins(DaemonThread):
@profiler @profiler
def __init__(self, config, is_local, gui_name): def __init__(self, config, is_local, gui_name):
DaemonThread.__init__(self) DaemonThread.__init__(self)
self.setName('Plugins')
self.pkgpath = os.path.dirname(plugins.__file__) self.pkgpath = os.path.dirname(plugins.__file__)
self.config = config self.config = config
self.hw_wallets = {} self.hw_wallets = {}

View File

@ -54,7 +54,7 @@ class LabelsPlugin(BasePlugin):
"walletNonce": nonce, "walletNonce": nonce,
"externalId": self.encode(wallet, item), "externalId": self.encode(wallet, item),
"encryptedLabel": self.encode(wallet, label)} "encryptedLabel": self.encode(wallet, label)}
asyncio.get_event_loop().create_task(self.do_post_safe("/label", bundle)) asyncio.run_coroutine_threadsafe(self.do_post_safe("/label", bundle), wallet.network.asyncio_loop)
# Caller will write the wallet # Caller will write the wallet
self.set_nonce(wallet, nonce + 1) self.set_nonce(wallet, nonce + 1)
@ -134,12 +134,15 @@ class LabelsPlugin(BasePlugin):
await self.pull_thread(wallet, force) await self.pull_thread(wallet, force)
def pull(self, wallet, force): def pull(self, wallet, force):
if not wallet.network: raise Exception(_('You are offline.'))
return asyncio.run_coroutine_threadsafe(self.pull_thread(wallet, force), wallet.network.asyncio_loop).result() return asyncio.run_coroutine_threadsafe(self.pull_thread(wallet, force), wallet.network.asyncio_loop).result()
def push(self, wallet): def push(self, wallet):
if not wallet.network: raise Exception(_('You are offline.'))
return asyncio.run_coroutine_threadsafe(self.push_thread(wallet), wallet.network.asyncio_loop).result() return asyncio.run_coroutine_threadsafe(self.push_thread(wallet), wallet.network.asyncio_loop).result()
def start_wallet(self, wallet): def start_wallet(self, wallet):
if not wallet.network: return # 'offline' mode
nonce = self.get_nonce(wallet) nonce = self.get_nonce(wallet)
self.print_error("wallet", wallet.basename(), "nonce is", nonce) self.print_error("wallet", wallet.basename(), "nonce is", nonce)
mpk = wallet.get_fingerprint() mpk = wallet.get_fingerprint()
@ -151,11 +154,12 @@ class LabelsPlugin(BasePlugin):
wallet_id = hashlib.sha256(mpk).hexdigest() wallet_id = hashlib.sha256(mpk).hexdigest()
self.wallets[wallet] = (password, iv, wallet_id) self.wallets[wallet] = (password, iv, wallet_id)
# If there is an auth token we can try to actually start syncing # If there is an auth token we can try to actually start syncing
asyncio.get_event_loop().create_task(self.pull_safe_thread(wallet, False)) asyncio.run_coroutine_threadsafe(self.pull_safe_thread(wallet, False), wallet.network.asyncio_loop)
self.proxy = wallet.network.proxy self.proxy = wallet.network.proxy
wallet.network.register_callback(self.set_proxy, ['proxy_set']) wallet.network.register_callback(self.set_proxy, ['proxy_set'])
def stop_wallet(self, wallet): def stop_wallet(self, wallet):
if not wallet.network: return # 'offline' mode
wallet.network.unregister_callback('proxy_set') wallet.network.unregister_callback('proxy_set')
self.wallets.pop(wallet, None) self.wallets.pop(wallet, None)

View File

@ -51,6 +51,7 @@ class Synchronizer(PrintError):
''' '''
def __init__(self, wallet): def __init__(self, wallet):
self.wallet = wallet self.wallet = wallet
self.network = wallet.network
self.asyncio_loop = wallet.network.asyncio_loop self.asyncio_loop = wallet.network.asyncio_loop
self.requested_tx = {} self.requested_tx = {}
self.requested_histories = {} self.requested_histories = {}
@ -73,6 +74,7 @@ class Synchronizer(PrintError):
asyncio.run_coroutine_threadsafe(self._add(addr), self.asyncio_loop) asyncio.run_coroutine_threadsafe(self._add(addr), self.asyncio_loop)
async def _add(self, addr): async def _add(self, addr):
if addr in self.requested_addrs: return
self.requested_addrs.add(addr) self.requested_addrs.add(addr)
await self.add_queue.put(addr) await self.add_queue.put(addr)
@ -85,7 +87,7 @@ class Synchronizer(PrintError):
# request address history # request address history
self.requested_histories[addr] = status self.requested_histories[addr] = status
h = address_to_scripthash(addr) h = address_to_scripthash(addr)
result = await self.session.send_request("blockchain.scripthash.get_history", [h]) result = await self.network.get_history_for_scripthash(h)
self.print_error("receiving history", addr, len(result)) self.print_error("receiving history", addr, len(result))
hashes = set(map(lambda item: item['tx_hash'], result)) hashes = set(map(lambda item: item['tx_hash'], result))
hist = list(map(lambda item: (item['tx_hash'], item['height']), result)) hist = list(map(lambda item: (item['tx_hash'], item['height']), result))
@ -124,7 +126,7 @@ class Synchronizer(PrintError):
await group.spawn(self._get_transaction, tx_hash) await group.spawn(self._get_transaction, tx_hash)
async def _get_transaction(self, tx_hash): async def _get_transaction(self, tx_hash):
result = await self.session.send_request('blockchain.transaction.get', [tx_hash]) result = await self.network.get_transaction(tx_hash)
tx = Transaction(result) tx = Transaction(result)
try: try:
tx.deserialize() tx.deserialize()

View File

@ -7,10 +7,17 @@ from electrum.simple_config import SimpleConfig
from electrum import blockchain from electrum import blockchain
from electrum.interface import Interface from electrum.interface import Interface
class MockTaskGroup:
async def spawn(self, x): return
class MockNetwork:
main_taskgroup = MockTaskGroup()
asyncio_loop = asyncio.get_event_loop()
class MockInterface(Interface): class MockInterface(Interface):
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
super().__init__(None, 'mock-server:50000:t', self.config.electrum_path(), None) super().__init__(MockNetwork(), 'mock-server:50000:t', self.config.electrum_path(), None)
self.q = asyncio.Queue() self.q = asyncio.Queue()
self.blockchain = blockchain.Blockchain(self.config, 2002, None) self.blockchain = blockchain.Blockchain(self.config, 2002, None)
self.tip = 12 self.tip = 12

View File

@ -47,7 +47,6 @@ class SPV(PrintError):
def __init__(self, network, wallet): def __init__(self, network, wallet):
self.wallet = wallet self.wallet = wallet
self.network = network self.network = network
self.blockchain = network.blockchain()
self.merkle_roots = {} # txid -> merkle root (once it has been verified) self.merkle_roots = {} # txid -> merkle root (once it has been verified)
self.requested_merkle = set() # txid set of pending requests self.requested_merkle = set() # txid set of pending requests
@ -55,18 +54,14 @@ class SPV(PrintError):
return '{}:{}'.format(self.__class__.__name__, self.wallet.diagnostic_name()) return '{}:{}'.format(self.__class__.__name__, self.wallet.diagnostic_name())
async def main(self, group: TaskGroup): async def main(self, group: TaskGroup):
self.blockchain = self.network.blockchain()
while True: while True:
await self._maybe_undo_verifications() await self._maybe_undo_verifications()
await self._request_proofs(group) await self._request_proofs(group)
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
async def _request_proofs(self, group: TaskGroup): async def _request_proofs(self, group: TaskGroup):
blockchain = self.network.blockchain() local_height = self.blockchain.height()
if not blockchain:
self.print_error("no blockchain")
return
local_height = self.network.get_local_height()
unverified = self.wallet.get_unverified_txs() unverified = self.wallet.get_unverified_txs()
for tx_hash, tx_height in unverified.items(): for tx_hash, tx_height in unverified.items():
@ -77,7 +72,7 @@ class SPV(PrintError):
if tx_height <= 0 or tx_height > local_height: if tx_height <= 0 or tx_height > local_height:
continue continue
# if it's in the checkpoint region, we still might not have the header # if it's in the checkpoint region, we still might not have the header
header = blockchain.read_header(tx_height) header = self.blockchain.read_header(tx_height)
if header is None: if header is None:
if tx_height < constants.net.max_checkpoint(): if tx_height < constants.net.max_checkpoint():
await group.spawn(self.network.request_chunk(tx_height, None, can_return_early=True)) await group.spawn(self.network.request_chunk(tx_height, None, can_return_early=True))

View File

@ -5,23 +5,30 @@
import os import os
import sys import sys
import platform import platform
import imp import importlib.util
import argparse import argparse
import subprocess import subprocess
from setuptools import setup, find_packages from setuptools import setup, find_packages
from setuptools.command.install import install from setuptools.command.install import install
MIN_PYTHON_VERSION = "3.6"
_min_python_version_tuple = tuple(map(int, (MIN_PYTHON_VERSION.split("."))))
if sys.version_info[:3] < _min_python_version_tuple:
sys.exit("Error: Electrum requires Python version >= {}...".format(MIN_PYTHON_VERSION))
with open('contrib/requirements/requirements.txt') as f: with open('contrib/requirements/requirements.txt') as f:
requirements = f.read().splitlines() requirements = f.read().splitlines()
with open('contrib/requirements/requirements-hw.txt') as f: with open('contrib/requirements/requirements-hw.txt') as f:
requirements_hw = f.read().splitlines() requirements_hw = f.read().splitlines()
version = imp.load_source('version', 'electrum/version.py') # load version.py; needlessly complicated alternative to "imp.load_source":
version_spec = importlib.util.spec_from_file_location('version', 'electrum/version.py')
if sys.version_info[:3] < (3, 6, 0): version_module = version = importlib.util.module_from_spec(version_spec)
sys.exit("Error: Electrum requires Python version >= 3.6.0...") version_spec.loader.exec_module(version_module)
data_files = [] data_files = []
@ -71,7 +78,7 @@ class CustomInstallCommand(install):
setup( setup(
name="Electrum", name="Electrum",
version=version.ELECTRUM_VERSION, version=version.ELECTRUM_VERSION,
python_requires='>=3.6', python_requires='>={}'.format(MIN_PYTHON_VERSION),
install_requires=requirements, install_requires=requirements,
extras_require=extras_require, extras_require=extras_require,
packages=[ packages=[