electrum-personal-server

Maximally lightweight electrum server for a single user
git clone https://git.parazyd.org/electrum-personal-server
Log | Files | Refs | README

commit b57e130024b51ed4cbc0eee9982afd1a57644c41
parent 247e63061ef4686fa54660883e35931276874cc1
Author: chris-belcher <chris-belcher@users.noreply.github.com>
Date:   Wed, 13 Jun 2018 14:14:43 +0100

Move tests to py.test framework

Create new directory test/ with all the test_*.py files containing test
code.

Diffstat:
MREADME.md | 20+++++++++++++++++---
Aelectrumpersonalserver/__init__.py | 10++++++++++
Melectrumpersonalserver/deterministicwallet.py | 216-------------------------------------------------------------------------------
Melectrumpersonalserver/merkleproof.py | 128+------------------------------------------------------------------------------
Melectrumpersonalserver/transactionmonitor.py | 441-------------------------------------------------------------------------------
Atest/test_deterministic_wallets.py | 198+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/test_merkleproof.py | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/test_parse_mpks.py | 29+++++++++++++++++++++++++++++
Atest/test_transactionmonitor.py | 346+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 731 insertions(+), 787 deletions(-)

diff --git a/README.md b/README.md @@ -145,16 +145,30 @@ for Bitcoin Core. ## Contributing -This is an open source project which happily accepts coding contributions from +Donate to help make Electrum Personal Server even better: `bc1q5d8l0w33h65e2l5x7ty6wgnvkvlqcz0wfaslpz` or `12LMDTSTWxaUg6dGtuMCVLtr2EyEN6Jimg`. + +This is open source project which happily accepts coding contributions from anyone. Please keep lines under 80 characters in length and ideally don't add any external dependencies to keep this as easy to install as possible. -Donate to help make Electrum Personal Server even better: `bc1q5d8l0w33h65e2l5x7ty6wgnvkvlqcz0wfaslpz` or `12LMDTSTWxaUg6dGtuMCVLtr2EyEN6Jimg`. - I can be contacted on freenode IRC on the `#bitcoin` and `#electrum` channels, by email or on [twitter](https://twitter.com/chris_belcher_/). My PGP key fingerprint is: `0A8B 038F 5E10 CC27 89BF CFFF EF73 4EA6 77F3 1129`. +#### Testing + +pytest is used for automated testing. On Debian-like systems install with +`pip3 install pytest pytest-cov` + +Run the tests with: + + $ PYTHONPATH=.:$PYTHONPATH py.test-3 + +Create the coverage report with: + + $ PYTHONPATH=.:$PYTHONPATH py.test-3 --cov-report=html --cov + $ open htmlcov/index.html + ## Media Coverage * https://bitcoinmagazine.com/articles/electrum-personal-server-will-give-users-full-node-security-they-need/ diff --git a/electrumpersonalserver/__init__.py b/electrumpersonalserver/__init__.py @@ -0,0 +1,10 @@ + + +from electrumpersonalserver.merkleproof import convert_core_to_electrum_merkle_proof +from electrumpersonalserver.jsonrpc import JsonRpc, JsonRpcError +from electrumpersonalserver.hashes import (to_bytes, sha256, bh2u, + script_to_scripthash, get_status_electrum, bfh, hash_encode, + hash_decode, Hash, hash_merkle_root, hash_160, script_to_address, + address_to_script, address_to_scripthash, bytes_fmt) +from electrumpersonalserver.transactionmonitor import TransactionMonitor, import_addresses, ADDRESSES_LABEL +from electrumpersonalserver.deterministicwallet import parse_electrum_master_public_key, DeterministicWallet diff --git a/electrumpersonalserver/deterministicwallet.py b/electrumpersonalserver/deterministicwallet.py @@ -211,219 +211,3 @@ class MultisigP2WSH_P2SHWallet(MultisigWallet): #op_hash160 length hash160 op_equal return "a914" + sh + "87" -# electrum has its own tests here -#https://github.com/spesmilo/electrum/blob/03b40a3c0a7dd84e76bc0d0ea2ad390dafc92250/lib/tests/test_wallet_vertical.py - -electrum_keydata_test_vectors = [ - #p2pkh wallet - ("xpub661MyMwAqRbcGVQTLtBFzc3ENvyZHoUEhWRdGwoqLZaf5wXP9VcDY2VJV7usvsFLZz" + - "2RUTVhCVXYXc3S8zpLyAFbDFcfrpUiwLoE9VWH2yz", #pubkey - ["76a914b1847c763c9a9b12631ab42335751c1bf843880c88ac" #recv scriptpubkeys - ,"76a914d8b6b932e892fad5132ea888111adac2171c5af588ac" - ,"76a914e44b19ef74814f977ae4e2823dd0a0b33480472a88ac"], - ["76a914d2c2905ca383a5b8f94818cb7903498061a6286688ac" #change scriptpubkeys - ,"76a914e7b4ddb7cede132e84ba807defc092cf52e005b888ac" - ,"76a91433bdb046a1d373728d7844df89aa24f788443a4588ac"]) - , #p2wpkh wallet - ("zpub6mr7wBKy3oJn89TCiXUAPBWpTTTx58BgEjPLzDNf5kMThvd6xchrobPTsJ5mP" + - "w3NJ7zRhckN8cv4FhQBfwurZzNE5uTW5C5PYqNTkRAnTkP", #pubkey - ['00142b82c61a7a48b7b10801f0eb247af46821bd33f5' #recv scriptpubkeys - ,'0014073dc6bcbb18d6468c5996bdeba926f6805b74b1' - ,'001400fa0b5cb21e8d442a7bd61af3d558a62be0c9aa'], - ['00144f4a0655a4b586be1e08d97a2f55125120b84c69' #change scriptpubkeys - ,'0014ef7967a7a56c23bbc9f317e612c93a5e23d25ffe' - ,'0014ad768a11730bf54d10c72184d53239de0f310bc9']) - ,#p2sh 2of2 multisig wallet - ("2 tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" + - "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ tpubD6NzVbkrYhZ4WjgNYq2nF" + - "TbiSLW2SZAzs4g5JHLqwQ3AmR3tCWpqsZJJEoZuP5HAEBNxgYQhtWMezszoaeTCg6FWGQB" + - "T74sszGaxaf64o5s", #m=2, 2 pubkeys, n=len(pubkeys) - ['a914fe30a46a4e1b41f9bb758448fd84ee4628c103e187' #recv - ,'a914dad5dd605871560ae5d219cd6275e6ad19bc6b9987' - ,'a914471e158e2db190acdd8c76ed6d2ade102fe1e8ac87' - ,'a914013449715a32f21d1a8a2b95a01b40eb41ada16f87' - ,'a914ae3dd25567fb7c2f87be41220dd14025ca68b0e087' - ,'a91462b90344947b610c4eadb7dd460fee3f32fefe7687' - ,'a914d4388c7d5771ebf26b6e650c42e60e4cf7d4c5a187' - ,'a914e4f0832e56591d01b71c72b9a3777dc8f9d9a92e87' - ,'a914a5d5accd96d27403c7663b92fdb57299d7a871eb87' - ,'a914f8f2c6ef2d80f972e4d8b418a15337a3c38af37f87' - ,'a914a2bd2f67fac7c24e609b574ccc8cfaa2f90ebf8c87' - ,'a914a56298a7decde1d18306f55d9305577c3fce690187' - ,'a91430f2f83238ac29125a539055fa59efc86a73a23987' - ,'a914263b4585d0735c5065987922af359d5eabeb880d87' - ,'a91455d9d47113fb8b37705bdf6d4107d438afd63e4687' - ,'a914970d754163b8957b73f4e8baaf23dea5f6e3db2287' - ,'a914facbc921203a9ffd751cc246a884918beaac21b687' - ,'a914fc7556833eca1e0f84c6d7acb875e645f7ed4e9687' - ,'a914bbfe6a032d633f113b5d605e3a97cc08a47cc87d87' - ,'a91403d733c4ca337b5fa1de95970ba6f898a9d36c4887' - ,'a9148af27dc7c950e17c11e164065e672cd60ae3d48d87' - ,'a914c026aa45377f2a4a62136bac1d3350c318fee5c587' - ,'a9146337f59e3ea55e73725c9f2fc52a5ca5d68c361687'], - ['a914aeaebf9d567ab8a6813e89668e16f40bf419408e87' #change - ,'a914f2a6264dd3975297fa2a5a8e17321299a44f76d987' - ,'a9142067a6c47958090a645137cc0898c0c7bbc69b5387' - ,'a914210840f77ea5b7eb11cb55e5d719a93b7746fb9387' - ,'a914163db6b8ca00362be63a26502c5f7bf64787506b87' - ,'a91479b2c527594059c056e5367965ae92bbcf63512187']) - ,#p2sh 2of3 multisig wallet - ("2 tpubD6NzVbkrYhZ4WwaMJ3od4hANxdMVpb63Du3ERq1xjtowxVJEcTbGH2rFd9TFXxw" + - "KJRKDn9vQjDPxFeaku6BHW6wHn2KPF1ijS4LwgwQFJ3B tpubD6NzVbkrYhZ4Wjv4ZRPD6" + - "MNdiLmfvXztbKuuatkqHjukU3S6GXhmKnbAF5eU9bR2Nryiq8v67emUUSM1VUrAx5wcZ19" + - "AsaGg3ZLmjbbwLXr tpubD6NzVbkrYhZ4Xxa2fEp7YsbnFnwuQNaogijbiX42Deqd4NiAD" + - "tqNU6AXCU2d2kPFWBpAGG7K3HAKYwUfZBPgTLkfQp2dDg9SLVnkgYPgEXN", - ['a914167c95beb25b984ace517d4346e6cdbf1381793687', #recv addrs - 'a914378bbda1ba7a713de18c3ba3c366f42212bfb45087', - 'a9142a5c9881c70906180f37dd02d8c830e9b6328d4a87', - 'a914ffe0832375b72ee5307bfa502896ba28cc470ee987', - 'a9147607d40e039fbea57d9c04e48b198c9fcf3356c187', - 'a9148d9582ad4cf0581c6e0697e4cba6a12e66ca1a0087', - 'a914d153a743b315ba19690823119019e16e3762104d87', - 'a914b4accc89e48610043e70371153fd8cb5a3eef34287', - 'a91406febca615e3631253fd75a1d819436e1d046e0487', - 'a914b863cbb888c6b28291cb87a2390539e28be37a9587', - 'a914ec39094e393184d2c352a29b9d7a3caddaccb6cf87', - 'a914da4faa4babbdf611caf511d287133f06c1c3244a87', - 'a9146e64561d0c5e2e9159ecff65db02e04b3277402487', - 'a914377d66386972492192ae827fb2208596af0941d187', - 'a914448d364ff2374449e57df13db33a40f5b099997c87', - 'a914f24b875d2cb99e0b138ab0e6dd65027932b3c6e787', - 'a914aa4bcee53406b1ef6c83852e3844e38a3a9d9f3087', - 'a9145e5ec40fdab54be0d6e21107bc38c39df97e37fc87', - 'a9141de4d402c82f4e9b0e6b792b331232a5405ebd3f87', - 'a9148873ee280e51f9c64d257dd6dedc8712fd652cc687'], - ['a9142cc87d7562a85029a57cc37026e12dab72223db287', #change - 'a91499f4aee0b274f0b3ab48549a2c58cd667a62c0cb87', - 'a91497a89cd5ada3a766a1275f8151e9256fcf537f6c87', - 'a9147ffc9f3a3b60635ea1783243274f4d07ab617cb487', - 'a9143423113ab913d86fd47e55488a0c559e18b457b987', - 'a914a28a3773a37c52ff6fd7dff497d0eaf80a46febb87']) - , #p2wsh 1of2 multisig wallet - ("1 Vpub5fAqpSRkLmvXwqbuR61MaKMSwj5z5xUBwanaz3qnJ5MgaBDpFSLUvKTiNK9zHp" + - "dvrg2LHHXkKxSXBHNWNpZz9b1VqADjmcCs3arSoxN3F3r Vpub5fvEo4MUpbVs9sZqr45" + - "zmRVEsTcQ49MA9m3MLht3XzdZvS9eMXLLu1H6TL1j2SMnykHqXNzG5ycMyQmFDvEE5B32" + - "sP8TmRe6wW8HjBgMssh", - #recv scriptpubkeys - ['002031fbaa839e96fc1abaf3453b9f770e0ccfe2d8e3e990bb381fdcb7db4722986a', - '0020820ae739b36f4feb1c299ced201db383bbcf1634e0071e489b385f43c2323761', - '0020eff05f4d14aa1968a7142b1009aa57a6208fb01b212f8b8f7df63645d26a1292', - '002049c6e17979dca380ffb66295d27f609bea2879d4f0b590c96c70ff12260a8721', - '002002bf2430fc7ebc6fb27da1cb80e52702edcc62a29f65c997e5c924dcd98411bd', - '0020c7a58dcf9633453ba12860b57c14af67d87d022be5c52bf6be7a6abdc295c6e0', - '0020136696059a5e932c72f4f0a05fa7f52faf9b54f1b7694e15acce710e6cc9e89d', - '0020c372e880227f35c2ee35d0724bf05cea95e74dcb3e6aa67ff15f561a29c0645d', - '002095c705590e2b84996fa44bff64179b26669e53bbd58d76bb6bbb5c5498a981ce', - '00207217754dae083c3c365c7e1ce3ad889ca2bd88e4f809cec66b9987adc390aa26', - '0020bee30906450e099357cc96a1f472c1ef70089cd4a0cba96749adfe1c9a2f9e87', - '0020b1838b3d5a386ad6c90eeae9a27a9b812e32ce06376f261dea89e405bc8209d9', - '0020231a3d05886efff601f0702d4c8450dfcce8d6a4bd90f17f7ff76f5c25c632de', - '002071220f3941b5f65aca90e464db4291cd5ea63f37fa858fd5b66d5019f0dbab0f', - '0020fc3c7db9f0e773f9f9c725d4286ddcc88db9575c45b2441d458018150eb4ef10', - '00209f037bfc98dee2fc0d3cca54df09b2d20e92a0133fa381a4dd74c49e4d0a89f5', - '0020c9060d0554ba2ca92048e1772e806d796ba41f10bf6aee2653a9eba96b05c944', - '0020a7cb1dd2730dba564f414ed8d9312370ff89c34df1441b83125cb4d97a96005a', - '00209fddc9b4e070b887dec034ed74f15f62d075a3ac8cf6eb95a88c635e0207534c', - '0020c48f9c50958ab8e386a8bd3888076f31d12e5cf011ff46cc83c6fadfe6d47d20', - '0020a659f4621dca404571917e73dedb26b6d7c49a07dacbf15890760ac0583d3267'], - #change scriptpubkeys - ['002030213b5d3b6988b86aa13a9eaca08e718d51f32dc130c70981abb0102173c791', - '002027bd198f9783a58e9bc4d3fdbd1c75cc74154905cce1d23c7bd3e051695418fe', - '0020c1fd2cdebf120d3b1dc990dfdaca62382ff9525beeb6a79a908ddecb40e2162c', - '00207a3e478266e5fe49fe22e3d8f04d3adda3b6a0835806a0db1f77b84d0ba7f79c', - '002059e66462023ecd54e20d4dce286795e7d5823af511989736edc0c7a844e249f5', - '0020bd8077906dd367d6d107d960397e46db2daba5793249f1f032d8d7e12e6f193c']) - , #p2wpkh-p2sh - ("upub5E4QEumGPNTmSKD95TrYX2xqLwwvBULbRzzHkrpW9WKKCB1y9DEfPXDnUyQjLjmVs" + - "7gSd7k5vRb1FoSb6BjyiWNg4arkJLaqk1jULzbwA5q", - ["a914ae8f84a06668742f713d0743c1f54d248040e63387", #recv - "a914c2e9bdcc48596b8cce418042ade72198fddf3cd987", - "a914a44b6ad63ccef0ae1741eaccee99bf2fa83f842987", - "a9148cf1c891d96a0be07893d0bddcf00ed5dad2c46e87", - "a91414d677b32f2409f4dfb3073d382c302bcd6ed33587", - "a9141b284bee7198d5134512f37ef60e4048864b4bd687"], - ["a914a5aacff65860440893107b01912dc8f60cadab2b87", #change - "a914dcd74ebc8bfc5cf0535717a3e833592d54b3c48687", - "a91446793cae4c2b8149ade61c1627b96b90599bc08787", - "a91439f3776831f321125bdb5099fbbd654923f8316c87"]) - , #p2wpkh-p2sh - ("ypub6XrRLtXNB7NQo3vDaMNnffXVJe1WVaebXcb4ncpTHHADLuFYmf2CcPn96YzUbMt8s" + - "HSMmtr1mCcMgCBLqNdY2hrXXcdiLxCdD9e2dChBLun", - ["a91429c2ad045bbb162ef3c2d9cacb9812bec463061787", #recv - "a91433ec6bb67b113978d9cfd307a97fd15bc0a5a62087", - "a91450523020275ccbf4e916a0d8523ae42391ad988a87", - "a91438c2e5e76a874d86cfc914fe9fc1868b6afb5c5487"], - ["a91475f608698bb735120a17699fee854bce9a8dc8d387", - "a91477e69344ef53587051c85a06a52a646457b44e6c87", - "a914607c98ea34fbdffe39fee161ae2ffd5517bf1a5587"]) - , #old mnemonic mpk - ("e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d" + - "5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442b3", - ["76a9149cd3dfb0d87a861770ae4e268e74b45335cf00ab88ac", #recv - "76a914c30f2af6a79296b6531bf34dba14c8419be8fb7d88ac", - "76a9145eb4eeaefcf9a709f8671444933243fbd05366a388ac", - "76a914f96669095e6df76cfdf5c7e49a1909f002e123d088ac"], - ["76a914ca14915184a2662b5d1505ce7142c8ca066c70e288ac", #change - "76a9148942ac692ace81019176c4fb0ac408b18b49237f88ac", - "76a914e1232622a96a04f5e5a24ca0792bb9c28b089d6e88ac"]) - , #p2wsh-p2sh 2of2 multisig - ("2 Ypub6hWbqA2p47QgsLt5J4nxrR3ngu8xsPGb7PdV8CDh48KyNngNqPKSqertAqYhQ4u" + - "mELu1UsZUCYfj9XPA6AdSMZWDZQobwF7EJ8uNrECaZg1 Ypub6iNDhL4WWq5kFZcdFqHHw" + - "X4YTH4rYGp8xbndpRrY7WNZFFRfogSrL7wRTajmVHgR46AT1cqUG1mrcRd7h1WXwBsgX2Q" + - "vT3zFbBCDiSDLkau", - ["a91428060ade179c792fac07fc8817fd150ce7cdd3f987", #recv - "a9145ba5ed441b9f3e22f71193d4043b645183e6aeee87", - "a91484cc1f317b7d5afff115916f1e27319919601d0187", - "a9144001695a154cac4d118af889d3fdcaf929af315787", - "a914897888f3152a27cbd7611faf6aa01085931e542a87"], - ["a91454dbb52de65795d144f3c4faeba0e37d9765c85687", #change - "a914f725cbd61c67f34ed40355f243b5bb0650ce61c587", - "a9143672bcd3d02d3ea7c3205ddbc825028a0d2a781987"]) -] - -electrum_bad_keydata_test_vectors = [ - "zpub661MyMwAqRbcGVQTLtBFzc3ENvyZHoUEhWRdGwoqLZaf5wXP9VcDY2VJV7usvsFLZz" + - "2RUTVhCVXYXc3S8zpLyAFbDFcfrpUiwLoE9VWH2yz", #bad checksum - "a tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" + - "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ tpubD6NzVbkrYhZ4WjgNYq2nF" + - "TbiSLW2SZAzs4g5JHLqwQ3AmR3tCWpqsZJJEoZuP5HAEBNxgYQhtWMezszoaeTCg6FWGQB" + - "T74sszGaxaf64o5s", #unparsable m number - "2 tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" + - "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ Vpub5fAqpSRkLmvXwqbuR61M" + - "aKMSwj5z5xUBwanaz3qnJ5MgaBDpFSLUvKTiNK9zHpdvrg2LHHXkKxSXBHNWNpZz9b1Vq" + - "ADjmcCs3arSoxN3F3r" #inconsistent magic -] - -def test(): - for keydata, recv_spks, change_spks in electrum_keydata_test_vectors: - initial_count = 15 - gaplimit = 5 - wal = parse_electrum_master_public_key(keydata, gaplimit) - spks = wal.get_scriptpubkeys(0, 0, initial_count) - #for test, generate 15, check that the last 5 lead to gap limit overrun - for i in range(initial_count - gaplimit): - ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[i]]) - assert ret == None - for i in range(gaplimit): - index = i + initial_count - gaplimit - ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[index]]) - assert ret != None and ret[0] == i+1 - last_index_add = 3 - last_index = initial_count - gaplimit + last_index_add - ret = wal.have_scriptpubkeys_overrun_gaplimit(spks[2:last_index]) - assert ret[0] == last_index_add - assert wal.get_scriptpubkeys(0, 0, len(recv_spks)) == recv_spks - assert wal.get_scriptpubkeys(1, 0, len(change_spks)) == change_spks - for keydata in electrum_bad_keydata_test_vectors: - try: - parse_electrum_master_public_key(keydata, 5) - raised_error = False - except (ValueError, Exception): - raised_error = True - assert raised_error - print("All tests passed successfully") - -if __name__ == "__main__": - test() - pass - diff --git a/electrumpersonalserver/merkleproof.py b/electrumpersonalserver/merkleproof.py @@ -4,7 +4,7 @@ import binascii from math import ceil, log from electrumpersonalserver.hashes import hash_encode, hash_decode -from electrumpersonalserver.hashes import Hash, hash_merkle_root +from electrumpersonalserver.hashes import Hash #lots of ideas and code taken from bitcoin core and breadwallet #https://github.com/bitcoin/bitcoin/blob/master/src/merkleblock.h @@ -143,129 +143,3 @@ def convert_core_to_electrum_merkle_proof(proof): "merkleroot": hash_encode(merkle_root)} return result -merkle_proof_test_vectors = [ - #txcount 819, pos 5 - "0300000026e696fba00f0a43907239305eed9e55824e0e376636380f000000000000000" + - "04f8a2ce51d6c69988029837688cbfc2f580799fa1747456b9c80ab808c1431acd0b07f" + - "5543201618cadcfbf7330300000b0ff1e0050fed22ca360e0935e053b0fe098f6f9e090" + - "f5631013361620d964fe2fd88544ae10b40621e1cd24bb4306e3815dc237f77118a45d7" + - "5ada9ee362314b70573732bce59615a3bcc1bbacd04b33b7819198212216b5d62d75be5" + - "9221ada17ba4fb2476b689cccd3be54732fd5630832a94f11fa3f0dafd6f904d43219e0" + - "d7de110158446b5b598bd241f7b5df4da0ebc7d30e7748d487917b718df51c681174e6a" + - "bab8042cc7c1c436221c098f06a56134f9247a812126d675d69c82ba1c715cfc0cde462" + - "fd1fbe5dc87f6b8db2b9c060fcd59a20e7fe8e921c3676937a873ff88684f4be4d015f2" + - "4f26af6d2cf78335e9218bcceba4507d0b4ba6cb933aa01ef77ae5eb411893ec0f74b69" + - "590fb0f5118ac937c02ccd47e9d90be78becd11ecf854d7d268eeb479b74d137278c0a5" + - "017d29e90cd5b35a4680201824fb0eb4f404e20dfeaec4d50549030b7e7e220b02eb210" + - "5f3d2e8bcc94d547214a9d03ff1600", - #txcount 47, pos 9 - "0100000053696a625fbd16df418575bce0c4148886c422774fca5fcab80100000000000" + - "01532bfe4f9c4f56cd141028e5b59384c133740174b74b1982c7f01020b90ce05577c67" + - "508bdb051a7ec2ef942f000000076cde2eb7efa90b36d48aed612e559ff2ba638d8d400" + - "b14b0c58df00c6a6c33b65dc8fa02f4ca56e1f4dcf17186fa9bbd990ce150b6e2dc9e9e" + - "56bb4f270fe56fde6bdd73a7a7e82767714862888e6b759568fb117674ad23050e29311" + - "97494d457efb72efdb9cb79cd4a435724908a0eb31ec7f7a67ee03837319e098b43edad" + - "3be9af75ae7b30db6f4f93ba0fdd941fdf70fe8cc38982e03bd292f5bd02f28137d343f" + - "908c7d6417379afe8349a257af3ca1f74f623be6a416fe1aa96a8f259983f2cf32121bc" + - "e203955a378b3b44f132ea6ab94c7829a6c3b360c9f8da8e74027701", - #txcount 2582, pos 330 - "000000206365d5e1d8b7fdf0b846cfa902115c1b8ced9dd49cb17800000000000000000" + - "01032e829e1f9a5a09d0492f9cd3ec0762b7facea555989c3927d3d975fd4078c771849" + - "5a45960018edd3b9e0160a00000dfe856a7d5d77c23ebf85c68b5eb303d85e56491ed6d" + - "204372625d0b4383df5a44d6e46d2db09d936b9f5d0b53e0dbcb3efb7773d457369c228" + - "fd1ce6e11645e366a58b3fc1e8a7c916710ce29a87265a6729a3b221b47ea9c8e6f4870" + - "7b112b8d67e5cfb3db5f88b042dc49e4e5bc2e61c28e1e0fbcba4c741bb5c75cac58ca0" + - "4161a7377d70f3fd19a3e248ed918c91709b49afd3760f89ed2fefbcc9c23447ccb40a2" + - "be7aba22b07189b0bf90c62db48a9fe37227e12c7af8c1d4c22f9f223530dacdd5f3ad8" + - "50ad4badf16cc24049a65334f59bf28c15cecda1a4cf3f2937bd70ee84f66569ce8ef95" + - "1d50cca46d60337e6c697685b38ad217967bbe6801d03c44fcb808cd035be31888380a2" + - "df1be14b6ff100de83cab0dce250e2b40ca3b47e8309f848646bee63b6185c176d84f15" + - "46a482e7a65a87d1a2d0d5a2b683e2cae0520df1e3525a71d71e1f551abd7d238c3bcb4" + - "ecaeea7d5988745fa421a8604a99857426957a2ccfa7cd8df145aa8293701989dd20750" + - "5923fcb339843944ce3d21dc259bcda9c251ed90d4e55af2cf5b15432050084f513ac74" + - "c0bdd4b6046fb70100", - #txcount 2861, pos 2860, last tx with many duplicated nodes down the tree - "00000020c656c90b521a2bbca14174f2939b882a28d23d86144b0e00000000000000000" + - "0cf5185a8e369c3de5f15e039e777760994fd66184b619d839dace3aec9953fd6d86159" + - "5ac1910018ee097a972d0b0000078d20d71c3114dbf52bb13f2c18f987891e8854d2d29" + - "f61c0b3d3256afcef7c0b1e6f76d6431f93390ebd28dbb81ad7c8f08459e85efeb23cc7" + - "2df2c5612215bf53dd4ab3703886bc8c82cb78ba761855e495fb5dc371cd8fe25ae974b" + - "df42269e267caf898a9f34cbf2350eaaa4afbeaea70636b5a3b73682186817db5b33290" + - "bd5c696bd8d0322671ff70c5447fcd7bdc127e5b84350c6b14b5a3b86b424d7db38d39f" + - "171f57e255a31c6c53415e3d65408b6519a40aacc49cad8e70646d4cb0d23d4a63068e6" + - "c220efc8a2781e9e774efdd334108d7453043bd3c8070d0e5903ad5b07", - #txcount 7, pos 6, duplicated entry in the last depth, at tx level - "0100000056e02c6d3278c754e0699517834741f7c4ad3dcbfeb7803a346200000000000" + - "0af3bdd5dd465443fd003e9281455e60aae573dd4d46304d7ba17276ea33d506488cbb4" + - "4dacb5001b9ebb193b0700000003cd3abb2eb7583165f36b56add7268be9027ead4cc8f" + - "888ec650d3b1c1f4de28a0ff7c8b463b2042d09598f0e5e5905de362aa1cf75252adc22" + - "719b8e1bc969adcfbc4782b8eafc9352263770b91a0f189ae051cbe0e26046c2b14cf3d" + - "8be0bc40135", - #txcount 6, pos 5, duplicated entry in the last-but-one depth - "01000000299edfd28524eae4fb6012e4087afdb6e1b912db85e612374b0300000000000" + - "0e16572394f8578a47bf36e15cd16faa5e3b9e18805cf4e271ae4ef95aa8cea7eb31fa1" + - "4e4b6d0b1a42857d960600000003f52b45ed953924366dab3e92707145c78615b639751" + - "ecb7be1c5ecc09b592ed588ca0e15a89e9049a2dbcadf4d8362bd1f74a6972f176617b5" + - "8a5466c8a4121fc3e2d6fa66c8637b387ef190ab46d6e9c9dae4bbccd871c72372b3dbc" + - "6edefea012d", - #txcount 5, pos 4, duplicated on the last and second last depth - "010000004d891de57908d89e9e5585648978d7650adc67e856c2d8c18c1800000000000" + - "04746fd317bffecd4ffb320239caa06685bafe0c1b5463b24d636e45788796657843d1b" + - "4d4c86041be68355c40500000002d8e26c89c46477f2407d866d2badbd98e43e732a670" + - "e96001faf1744b27e5fdd018733d72e31a2d6a0d94f2a3b35fcc66fb110c40c5bbff82b" + - "f87606553d541d011d", - #txcount 2739, pos 0, coinbase tx - "000000209f283da030c6e6d0ff5087a87c430d140ed6b4564fa34d00000000000000000" + - "0ec1513723e3652c6b8e777c41eb267ad8dd2025e85228840f5cfca7ffe1fb331afff8a" + - "5af8e961175e0f7691b30a00000df403e21a4751fbd52457f535378ac2dcf111199e9ea" + - "6f78f6c2663cb99b58203438d8f3b26f7f2804668c1df7d394a4726363d4873b2d85b71" + - "2e44cf4f5e4f33f22a8f3a1672846bd7c4570c668e6ee12befda23bfa3d0fcd30b1b079" + - "19b01c40b1e31b6d34fcdbb99539d46eb97a3ae15386f1ab0f28ecacadd9fc3fa4ce49a" + - "1a1839d815229f54036c8a3035d91e80e8dc127b62032b4e652550b4fc0aee0f6e85a14" + - "307d85ed9dde62acff9a0f7e3b52370a10d6c83ec13a0b4a8fafe87af368a167d7e9b63" + - "3b84b6ea65f1ce5e8ccc1840be0a4dab0099e25afccc7f2fdbda54cd65ecbac8d9a550c" + - "108b4e18d3af59129d373fde4c80848858fd6f7fc1e27387a38833473ca8a47729fa6e1" + - "cc14b584c14dad768108ff18cc6acdc9c31d32dc71c3c80856664a3fff870fe419a59aa" + - "9033356590475d36086f0b3c0ece34c0f3756675c610fb980ff3363af6f9c0918a7c677" + - "23371849de9c1026515c2900a80b3aee4f2625c8f48cd5eb967560ee8ebe58a8d41c331" + - "f6d5199795735d4f0494bdf592d166fa291062733619f0f133605087365639de2d9d5d6" + - "921f4b4204ff1f0000", - #txcount 1, pos 0, coinbase tx in an empty block, tree with height 1 - "010000000508085c47cc849eb80ea905cc7800a3be674ffc57263cf210c59d8d0000000" + - "0112ba175a1e04b14ba9e7ea5f76ab640affeef5ec98173ac9799a852fa39add320cd66" + - "49ffff001d1e2de5650100000001112ba175a1e04b14ba9e7ea5f76ab640affeef5ec98" + - "173ac9799a852fa39add30101", - #txcount 2, pos 1, tree with height 2 - "010000004e24a2880cd72d9bde7502087bd3756819794dc7548f68dd68dc30010000000" + - "02793fce9cdf91b4f84760571bf6009d5f0ffaddbfdc9234ef58a036096092117b10f4b" + - "4cfd68011c903e350b0200000002ee50562fc6f995eff2df61be0d5f943bac941149aa2" + - "1aacb32adc130c0f17d6a2077a642b1eabbc5120e31566a11e2689aa4d39b01cce9a190" + - "2360baa5e4328e0105" -] - -def test(): - for proof in merkle_proof_test_vectors: - try: - electrum_proof = convert_core_to_electrum_merkle_proof(proof) - #print(electrum_proof) - implied_merkle_root = hash_merkle_root( - electrum_proof["merkle"], electrum_proof["txid"], - electrum_proof["pos"]) - assert implied_merkle_root == electrum_proof["merkleroot"] - except ValueError: - import traceback - traceback.print_exc() - assert 0 - print("All tests passed") - -''' -proof = "" -def chunks(d, n): - return [d[x:x + n] for x in range(0, len(d), n)] -#print(proof) -print("\" + \n\"".join(chunks(proof, 71))) -''' - -if __name__ == "__main__": - test() - diff --git a/electrumpersonalserver/transactionmonitor.py b/electrumpersonalserver/transactionmonitor.py @@ -270,19 +270,6 @@ class TransactionMonitor(object): "subscribed"], updated_scrhashes) return updated_scrhashes - #tests: - #build address history where reorgable txes are found - #an unconfirmed tx arrives, gets confirmed, reaches the safe threshold - # and gets removed from list - #a confirmed tx arrives, reaches safe threshold and gets removed - #an unconfirmed tx arrives, confirms, gets reorgd out, returns to - # unconfirmed - #an unconfirmed tx arrives, confirms, gets reorgd out and conflicted - #an unconfirmed tx arrives, confirms, gets reorgd out and confirmed at - # a different height - #an unconfirmed tx arrives, confirms, gets reorgd out and confirmed in - # the same height - def check_for_reorganizations(self): elements_removed = [] elements_added = [] @@ -475,431 +462,3 @@ class TransactionMonitor(object): new_history_element["height"], matching_scripthashes)) return set(updated_scripthashes) - -## start tests here - -class TestJsonRpc(object): - def __init__(self, txlist, utxoset, block_heights): - self.txlist = txlist - self.utxoset = utxoset - self.block_heights = block_heights - self.imported_addresses = [] - - def call(self, method, params): - if method == "listtransactions": - count = int(params[1]) - skip = int(params[2]) - return self.txlist[skip:skip + count] - elif method == "gettransaction": - for t in self.txlist: - if t["txid"] == params[0]: - return t - raise JsonRpcError({"code": None, "message": None}) - elif method == "decoderawtransaction": - for t in self.txlist: - if t["hex"] == params[0]: - return t - assert 0 - elif method == "gettxout": - for u in self.utxoset: - if u["txid"] == params[0] and u["vout"] == params[1]: - return u - assert 0 - elif method == "getblockheader": - if params[0] not in self.block_heights: - assert 0 - return {"height": self.block_heights[params[0]]} - elif method == "decodescript": - return {"addresses": [test_spk_to_address(params[0])]} - elif method == "importaddress": - self.imported_addresses.append(params[0]) - else: - raise ValueError("unknown method in test jsonrpc") - - def add_transaction(self, tx): - self.txlist.append(tx) - - def get_imported_addresses(self): - return self.imported_addresses - -from electrumpersonalserver.deterministicwallet import DeterministicWallet - -class TestDeterministicWallet(DeterministicWallet): - """Empty deterministic wallets""" - def __init__(self): - pass - - def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys): - return None #not overrun - - def get_new_scriptpubkeys(self, change, count): - pass - -def test_spk_to_address(spk): - return spk + "-address" - -def assert_address_history_tx(address_history, spk, height, txid, subscribed): - history_element = address_history[hashes.script_to_scripthash(spk)] - assert history_element["history"][0]["height"] == height - assert history_element["history"][0]["tx_hash"] == txid - #fee always zero, its easier to test because otherwise you have - # to use Decimal to stop float weirdness - if height == 0: - assert history_element["history"][0]["fee"] == 0 - assert history_element["subscribed"] == subscribed - -def test(): - #debugf = lambda x: print("[DEBUG] " + x) - #logf = lambda x: print("[ LOG] " + x) - debugf = lambda x: x - logf = debugf - - #empty deterministic wallets - deterministic_wallets = [TestDeterministicWallet()] - test_spk1 = "deadbeefdeadbeefdeadbeefdeadbeef" - test_containing_block1 = "blockhash-placeholder1" - test_paying_in_tx1 = { - "txid": "placeholder-test-txid1", - "vin": [{"txid": "placeholder-unknown-input-txid", "vout": 0}], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spk1}}], - "address": test_spk_to_address(test_spk1), - "category": "receive", - "confirmations": 1, - "blockhash": test_containing_block1, - "hex": "placeholder-test-txhex1" - } - test_spk2 = "deadbeefdeadbeefdeadbeef" - test_containing_block2 = "blockhash-placeholder2" - test_paying_in_tx2 = { - "txid": "placeholder-test-txid2", - "vin": [{"txid": "placeholder-unknown-input-txid", "vout": 0}], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spk2}}], - "address": test_spk_to_address(test_spk2), - "category": "receive", - "confirmations": 1, - "blockhash": test_containing_block2, - "hex": "placeholder-test-txhex2" - } - - ###single confirmed tx in wallet belonging to us, address history built - rpc = TestJsonRpc([test_paying_in_tx1], [], - {test_containing_block1: 420000}) - txmonitor1 = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) - assert txmonitor1.build_address_history([test_spk1]) - assert len(txmonitor1.address_history) == 1 - assert_address_history_tx(txmonitor1.address_history, spk=test_spk1, - height=420000, txid=test_paying_in_tx1["txid"], subscribed=False) - - ###two confirmed txes in wallet belonging to us, addr history built - rpc = TestJsonRpc([test_paying_in_tx1, test_paying_in_tx2], [], - {test_containing_block1: 1, test_containing_block2: 2}) - deterministic_wallets = [TestDeterministicWallet()] - txmonitor2 = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) - assert txmonitor2.build_address_history([test_spk1, test_spk2]) - assert len(txmonitor2.address_history) == 2 - assert_address_history_tx(txmonitor2.address_history, spk=test_spk1, - height=1, txid=test_paying_in_tx1["txid"], subscribed=False) - assert_address_history_tx(txmonitor2.address_history, spk=test_spk2, - height=2, txid=test_paying_in_tx2["txid"], subscribed=False) - - ###one unconfirmed tx in wallet belonging to us, with confirmed inputs, - ### addr history built, then tx confirms, not subscribed to address - test_spk3 = "deadbeefdeadbeef" - test_containing_block3 = "blockhash-placeholder3" - input_utxo3 = {"txid": "placeholder-unknown-input-txid", "vout": 0, - "value": 1, "confirmations": 1} - test_paying_in_tx3 = { - "txid": "placeholder-test-txid3", - "vin": [input_utxo3], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spk3}}], - "address": test_spk_to_address(test_spk3), - "category": "receive", - "confirmations": 0, - "blockhash": test_containing_block3, - "hex": "placeholder-test-txhex3" - } - rpc = TestJsonRpc([test_paying_in_tx3], [input_utxo3], - {test_containing_block3: 10}) - txmonitor3 = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) - assert txmonitor3.build_address_history([test_spk3]) - assert len(txmonitor3.address_history) == 1 - assert_address_history_tx(txmonitor3.address_history, spk=test_spk3, - height=0, txid=test_paying_in_tx3["txid"], subscribed=False) - assert len(list(txmonitor3.check_for_updated_txes())) == 0 - test_paying_in_tx3["confirmations"] = 1 #tx confirms - #not subscribed so still only returns an empty list - assert len(list(txmonitor3.check_for_updated_txes())) == 0 - assert_address_history_tx(txmonitor3.address_history, spk=test_spk3, - height=10, txid=test_paying_in_tx3["txid"], subscribed=False) - - ###build empty address history, subscribe one address - ### an unconfirmed tx appears, then confirms - test_spk4 = "deadbeefdeadbeefaa" - test_containing_block4 = "blockhash-placeholder4" - input_utxo4 = {"txid": "placeholder-unknown-input-txid", "vout": 0, - "value": 1, "confirmations": 1} - test_paying_in_tx4 = { - "txid": "placeholder-test-txid4", - "vin": [input_utxo4], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spk4}}], - "address": test_spk_to_address(test_spk4), - "category": "receive", - "confirmations": 0, - "blockhash": test_containing_block4, - "hex": "placeholder-test-txhex4" - } - rpc = TestJsonRpc([], [input_utxo4], {test_containing_block4: 10}) - txmonitor4 = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) - assert txmonitor4.build_address_history([test_spk4]) - assert len(txmonitor4.address_history) == 1 - sh4 = hashes.script_to_scripthash(test_spk4) - assert len(txmonitor4.get_electrum_history(sh4)) == 0 - txmonitor4.subscribe_address(sh4) - # unconfirm transaction appears - assert len(list(txmonitor4.check_for_updated_txes())) == 0 - rpc.add_transaction(test_paying_in_tx4) - assert len(list(txmonitor4.check_for_updated_txes())) == 1 - assert_address_history_tx(txmonitor4.address_history, spk=test_spk4, - height=0, txid=test_paying_in_tx4["txid"], subscribed=True) - # transaction confirms - test_paying_in_tx4["confirmations"] = 1 - assert len(list(txmonitor4.check_for_updated_txes())) == 1 - assert_address_history_tx(txmonitor4.address_history, spk=test_spk4, - height=10, txid=test_paying_in_tx4["txid"], subscribed=True) - - ###transaction that has nothing to do with our wallet - test_spk5 = "deadbeefdeadbeefbb" - test_containing_block5 = "blockhash-placeholder5" - test_paying_in_tx5 = { - "txid": "placeholder-test-txid5", - "vin": [{"txid": "placeholder-unknown-input-txid", "vout": 0}], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spk5}}], - "address": test_spk_to_address(test_spk5), - "category": "receive", - "confirmations": 0, - "blockhash": test_containing_block5, - "hex": "placeholder-test-txhex5" - } - test_spk5_1 = "deadbeefdeadbeefcc" - rpc = TestJsonRpc([test_paying_in_tx5], [], {test_containing_block4: 10}) - txmonitor5 = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) - assert txmonitor5.build_address_history([test_spk5_1]) - assert len(txmonitor5.address_history) == 1 - assert len(txmonitor5.get_electrum_history(hashes.script_to_scripthash( - test_spk5_1))) == 0 - - ###transaction which arrives to an address which already has a tx on it - test_spk6 = "deadbeefdeadbeefdd" - test_containing_block6 = "blockhash-placeholder6" - test_paying_in_tx6 = { - "txid": "placeholder-test-txid6", - "vin": [{"txid": "placeholder-unknown-input-txid", "vout": 0}], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spk6}}], - "address": test_spk_to_address(test_spk6), - "category": "receive", - "confirmations": 1, - "blockhash": test_containing_block6, - "hex": "placeholder-test-txhex6" - } - test_paying_in_tx6_1 = { - "txid": "placeholder-test-txid6_1", - "vin": [{"txid": "placeholder-unknown-input-txid", "vout": 0}], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spk6}}], - "address": test_spk_to_address(test_spk6), - "category": "receive", - "confirmations": 1, - "blockhash": test_containing_block6, - "hex": "placeholder-test-txhex6" - } - rpc = TestJsonRpc([test_paying_in_tx6], [], {test_containing_block6: 10}) - txmonitor6 = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) - assert txmonitor6.build_address_history([test_spk6]) - sh = hashes.script_to_scripthash(test_spk6) - assert len(txmonitor6.get_electrum_history(sh)) == 1 - rpc.add_transaction(test_paying_in_tx6_1) - assert len(txmonitor6.get_electrum_history(sh)) == 1 - txmonitor6.check_for_updated_txes() - assert len(txmonitor6.get_electrum_history(sh)) == 2 - - ###transaction spending FROM one of our addresses - test_spk7 = "deadbeefdeadbeefee" - test_input_containing_block7 = "blockhash-input-placeholder7" - test_input_tx7 = { - "txid": "placeholder-input-test-txid7", - "vin": [{"txid": "placeholder-unknown-input-txid", "vout": 0}], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spk7}}], - "address": test_spk_to_address(test_spk7), - "category": "send", - "confirmations": 2, - "blockhash": test_input_containing_block7, - "hex": "placeholder-input-test-txhex7" - } - test_containing_block7 = "blockhash-placeholder7" - test_paying_from_tx7 = { - "txid": "placeholder-test-txid7", - "vin": [{"txid": test_input_tx7["txid"], "vout": 0}], - "vout": [{"value": 1, "scriptPubKey": {"hex": "deadbeef"}}], - "address": test_spk_to_address(test_spk7), - "category": "receive", - "confirmations": 1, - "blockhash": test_containing_block7, - "hex": "placeholder-test-txhex7" - } - rpc = TestJsonRpc([test_input_tx7, test_paying_from_tx7], [], - {test_containing_block7: 9, test_input_containing_block7: 8}) - txmonitor7 = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) - assert txmonitor7.build_address_history([test_spk7]) - sh = hashes.script_to_scripthash(test_spk7) - assert len(txmonitor7.get_electrum_history(sh)) == 2 - - ###transaction from one address to the other, both addresses in wallet - test_spk8 = "deadbeefdeadbeefee" - test_spk8_1 = "deadbeefdeadbeefff" - test_input_containing_block8 = "blockhash-input-placeholder8" - test_input_tx8 = { - "txid": "placeholder-input-test-txid8", - "vin": [{"txid": "placeholder-unknown-input-txid", "vout": 0}], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spk8}}], - "address": test_spk_to_address(test_spk8), - "category": "send", - "confirmations": 2, - "blockhash": test_input_containing_block8, - "hex": "placeholder-input-test-txhex8" - } - test_containing_block8 = "blockhash-placeholder8" - test_paying_from_tx8 = { - "txid": "placeholder-test-txid8", - "vin": [{"txid": test_input_tx8["txid"], "vout": 0}], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spk8_1}}], - "address": test_spk_to_address(test_spk8), - "category": "receive", - "confirmations": 1, - "blockhash": test_containing_block8, - "hex": "placeholder-test-txhex8" - } - rpc = TestJsonRpc([test_input_tx8, test_paying_from_tx8], [], - {test_containing_block8: 9, test_input_containing_block8: 8}) - txmonitor8 = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) - assert txmonitor8.build_address_history([test_spk8, test_spk8_1]) - assert len(txmonitor8.get_electrum_history(hashes.script_to_scripthash( - test_spk8))) == 2 - assert len(txmonitor8.get_electrum_history(hashes.script_to_scripthash( - test_spk8_1))) == 1 - - ###overrun gap limit so import address is needed - test_spk9 = "deadbeefdeadbeef00" - test_containing_block9 = "blockhash-placeholder9" - test_paying_in_tx9 = { - "txid": "placeholder-test-txid9", - "vin": [{"txid": "placeholder-unknown-input-txid", "vout": 0}], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spk9}}], - "address": test_spk_to_address(test_spk9), - "category": "receive", - "confirmations": 1, - "blockhash": test_containing_block9, - "hex": "placeholder-test-txhex9" - } - test_spk9_imported = "deadbeefdeadbeef11" - class TestImportDeterministicWallet(DeterministicWallet): - def __init__(self): - pass - - def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys): - return {0: 1} #overrun by one - - def get_new_scriptpubkeys(self, change, count): - return [test_spk9_imported] - - rpc = TestJsonRpc([], [], {test_containing_block9: 10}) - txmonitor9 = TransactionMonitor(rpc, [TestImportDeterministicWallet()], - debugf, logf) - assert txmonitor9.build_address_history([test_spk9]) - assert len(txmonitor9.address_history) == 1 - assert len(list(txmonitor9.check_for_updated_txes())) == 0 - assert len(txmonitor9.get_electrum_history(hashes.script_to_scripthash( - test_spk9))) == 0 - rpc.add_transaction(test_paying_in_tx9) - assert len(list(txmonitor9.check_for_updated_txes())) == 0 - assert len(txmonitor9.get_electrum_history(hashes.script_to_scripthash( - test_spk9))) == 1 - assert len(txmonitor9.get_electrum_history(hashes.script_to_scripthash( - test_spk9_imported))) == 0 - assert len(rpc.get_imported_addresses()) == 1 - assert rpc.get_imported_addresses()[0] == test_spk_to_address( - test_spk9_imported) - - ###conflicted transaction in history being sync'd - test_spkA = "deadbeefdeadbeefcccc" - test_paying_in_txA = { - "txid": "placeholder-test-txidA", - "vin": [{"txid": "placeholder-unknown-input-txid", "vout": 0}], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spkA}}], - "address": test_spk_to_address(test_spkA), - "category": "receive", - "confirmations": -1, - "hex": "placeholder-test-txhexA" - } - rpc = TestJsonRpc([test_paying_in_txA], [], {}) - txmonitorA = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) - assert txmonitorA.build_address_history([test_spkA]) - assert len(txmonitorA.address_history) == 1 - assert len(txmonitorA.get_electrum_history(hashes.script_to_scripthash( - test_spkA))) == 0 #shouldnt show up after build history - rpc.add_transaction(test_paying_in_txA) - assert len(list(txmonitorA.check_for_updated_txes())) == 0 - assert len(txmonitorA.get_electrum_history(hashes.script_to_scripthash( - test_spkA))) == 0 #shouldnt show up after tx is added - - ###an unconfirmed tx being broadcast, another conflicting tx being - ### confirmed, the first tx then becomes conflicted - test_spkB = "deadbeefdeadbeefbb" - test_containing_blockB = "blockhash-placeholderB" - input_utxoB = {"txid": "placeholder-unknown-input-txid", "vout": 0, - "value": 1, "confirmations": 1} - test_paying_in_txB = { - "txid": "placeholder-test-txidB", - "vin": [input_utxoB], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spkB}}], - "address": test_spk_to_address(test_spkB), - "category": "receive", - "confirmations": 0, - "blockhash": test_containing_blockB, - "hex": "placeholder-test-txhexB" - } - test_paying_in_txB_2 = { - "txid": "placeholder-test-txidB_2", - "vin": [input_utxoB], - "vout": [{"value": 1, "scriptPubKey": {"hex": test_spkB}}], - "address": test_spk_to_address(test_spkB), - "category": "receive", - "confirmations": 0, - "blockhash": test_containing_blockB, - "hex": "placeholder-test-txhexB" - } - rpc = TestJsonRpc([test_paying_in_txB], [input_utxoB], - {test_containing_blockB: 10}) - txmonitorB = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) - assert txmonitorB.build_address_history([test_spkB]) - assert len(txmonitorB.address_history) == 1 - shB = hashes.script_to_scripthash(test_spkB) - assert len(txmonitorB.get_electrum_history(shB)) == 1 - assert_address_history_tx(txmonitorB.address_history, spk=test_spkB, - height=0, txid=test_paying_in_txB["txid"], subscribed=False) - # a conflicting transaction confirms - rpc.add_transaction(test_paying_in_txB_2) - test_paying_in_txB["confirmations"] = -1 - test_paying_in_txB_2["confirmations"] = 1 - assert len(list(txmonitorB.check_for_updated_txes())) == 0 - assert len(txmonitorB.get_electrum_history(shB)) == 1 - assert_address_history_tx(txmonitorB.address_history, spk=test_spkB, - height=10, txid=test_paying_in_txB_2["txid"], subscribed=False) - - #other possible stuff to test: - #finding confirmed and unconfirmed tx, in that order, then both confirm - #finding unconfirmed and confirmed tx, in that order, then both confirm - - print("\nAll tests passed") - -if __name__ == "__main__": - test() - diff --git a/test/test_deterministic_wallets.py b/test/test_deterministic_wallets.py @@ -0,0 +1,198 @@ + +import pytest + +from electrumpersonalserver import parse_electrum_master_public_key + +# electrum has its own tests here +#https://github.com/spesmilo/electrum/blob/03b40a3c0a7dd84e76bc0d0ea2ad390dafc92250/lib/tests/test_wallet_vertical.py + +@pytest.mark.parametrize( + "master_public_key, recv_spks, change_spks", + [ + #p2pkh wallet + ("xpub661MyMwAqRbcGVQTLtBFzc3ENvyZHoUEhWRdGwoqLZaf5wXP9VcDY2VJV7usvsFLZz" + + "2RUTVhCVXYXc3S8zpLyAFbDFcfrpUiwLoE9VWH2yz", #pubkey + ["76a914b1847c763c9a9b12631ab42335751c1bf843880c88ac" #recv scriptpubkeys + ,"76a914d8b6b932e892fad5132ea888111adac2171c5af588ac" + ,"76a914e44b19ef74814f977ae4e2823dd0a0b33480472a88ac"], + ["76a914d2c2905ca383a5b8f94818cb7903498061a6286688ac" #change scriptpubkeys + ,"76a914e7b4ddb7cede132e84ba807defc092cf52e005b888ac" + ,"76a91433bdb046a1d373728d7844df89aa24f788443a4588ac"]) + , #p2wpkh wallet + ("zpub6mr7wBKy3oJn89TCiXUAPBWpTTTx58BgEjPLzDNf5kMThvd6xchrobPTsJ5mP" + + "w3NJ7zRhckN8cv4FhQBfwurZzNE5uTW5C5PYqNTkRAnTkP", #pubkey + ['00142b82c61a7a48b7b10801f0eb247af46821bd33f5' #recv scriptpubkeys + ,'0014073dc6bcbb18d6468c5996bdeba926f6805b74b1' + ,'001400fa0b5cb21e8d442a7bd61af3d558a62be0c9aa'], + ['00144f4a0655a4b586be1e08d97a2f55125120b84c69' #change scriptpubkeys + ,'0014ef7967a7a56c23bbc9f317e612c93a5e23d25ffe' + ,'0014ad768a11730bf54d10c72184d53239de0f310bc9']) + ,#p2sh 2of2 multisig wallet + ("2 tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" + + "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ tpubD6NzVbkrYhZ4WjgNYq2nF" + + "TbiSLW2SZAzs4g5JHLqwQ3AmR3tCWpqsZJJEoZuP5HAEBNxgYQhtWMezszoaeTCg6FWGQB" + + "T74sszGaxaf64o5s", #m=2, 2 pubkeys, n=len(pubkeys) + ['a914fe30a46a4e1b41f9bb758448fd84ee4628c103e187' #recv + ,'a914dad5dd605871560ae5d219cd6275e6ad19bc6b9987' + ,'a914471e158e2db190acdd8c76ed6d2ade102fe1e8ac87' + ,'a914013449715a32f21d1a8a2b95a01b40eb41ada16f87' + ,'a914ae3dd25567fb7c2f87be41220dd14025ca68b0e087' + ,'a91462b90344947b610c4eadb7dd460fee3f32fefe7687' + ,'a914d4388c7d5771ebf26b6e650c42e60e4cf7d4c5a187' + ,'a914e4f0832e56591d01b71c72b9a3777dc8f9d9a92e87' + ,'a914a5d5accd96d27403c7663b92fdb57299d7a871eb87' + ,'a914f8f2c6ef2d80f972e4d8b418a15337a3c38af37f87' + ,'a914a2bd2f67fac7c24e609b574ccc8cfaa2f90ebf8c87' + ,'a914a56298a7decde1d18306f55d9305577c3fce690187' + ,'a91430f2f83238ac29125a539055fa59efc86a73a23987' + ,'a914263b4585d0735c5065987922af359d5eabeb880d87' + ,'a91455d9d47113fb8b37705bdf6d4107d438afd63e4687' + ,'a914970d754163b8957b73f4e8baaf23dea5f6e3db2287' + ,'a914facbc921203a9ffd751cc246a884918beaac21b687' + ,'a914fc7556833eca1e0f84c6d7acb875e645f7ed4e9687' + ,'a914bbfe6a032d633f113b5d605e3a97cc08a47cc87d87' + ,'a91403d733c4ca337b5fa1de95970ba6f898a9d36c4887' + ,'a9148af27dc7c950e17c11e164065e672cd60ae3d48d87' + ,'a914c026aa45377f2a4a62136bac1d3350c318fee5c587' + ,'a9146337f59e3ea55e73725c9f2fc52a5ca5d68c361687'], + ['a914aeaebf9d567ab8a6813e89668e16f40bf419408e87' #change + ,'a914f2a6264dd3975297fa2a5a8e17321299a44f76d987' + ,'a9142067a6c47958090a645137cc0898c0c7bbc69b5387' + ,'a914210840f77ea5b7eb11cb55e5d719a93b7746fb9387' + ,'a914163db6b8ca00362be63a26502c5f7bf64787506b87' + ,'a91479b2c527594059c056e5367965ae92bbcf63512187']) + ,#p2sh 2of3 multisig wallet + ("2 tpubD6NzVbkrYhZ4WwaMJ3od4hANxdMVpb63Du3ERq1xjtowxVJEcTbGH2rFd9TFXxw" + + "KJRKDn9vQjDPxFeaku6BHW6wHn2KPF1ijS4LwgwQFJ3B tpubD6NzVbkrYhZ4Wjv4ZRPD6" + + "MNdiLmfvXztbKuuatkqHjukU3S6GXhmKnbAF5eU9bR2Nryiq8v67emUUSM1VUrAx5wcZ19" + + "AsaGg3ZLmjbbwLXr tpubD6NzVbkrYhZ4Xxa2fEp7YsbnFnwuQNaogijbiX42Deqd4NiAD" + + "tqNU6AXCU2d2kPFWBpAGG7K3HAKYwUfZBPgTLkfQp2dDg9SLVnkgYPgEXN", + ['a914167c95beb25b984ace517d4346e6cdbf1381793687', #recv addrs + 'a914378bbda1ba7a713de18c3ba3c366f42212bfb45087', + 'a9142a5c9881c70906180f37dd02d8c830e9b6328d4a87', + 'a914ffe0832375b72ee5307bfa502896ba28cc470ee987', + 'a9147607d40e039fbea57d9c04e48b198c9fcf3356c187', + 'a9148d9582ad4cf0581c6e0697e4cba6a12e66ca1a0087', + 'a914d153a743b315ba19690823119019e16e3762104d87', + 'a914b4accc89e48610043e70371153fd8cb5a3eef34287', + 'a91406febca615e3631253fd75a1d819436e1d046e0487', + 'a914b863cbb888c6b28291cb87a2390539e28be37a9587', + 'a914ec39094e393184d2c352a29b9d7a3caddaccb6cf87', + 'a914da4faa4babbdf611caf511d287133f06c1c3244a87', + 'a9146e64561d0c5e2e9159ecff65db02e04b3277402487', + 'a914377d66386972492192ae827fb2208596af0941d187', + 'a914448d364ff2374449e57df13db33a40f5b099997c87', + 'a914f24b875d2cb99e0b138ab0e6dd65027932b3c6e787', + 'a914aa4bcee53406b1ef6c83852e3844e38a3a9d9f3087', + 'a9145e5ec40fdab54be0d6e21107bc38c39df97e37fc87', + 'a9141de4d402c82f4e9b0e6b792b331232a5405ebd3f87', + 'a9148873ee280e51f9c64d257dd6dedc8712fd652cc687'], + ['a9142cc87d7562a85029a57cc37026e12dab72223db287', #change + 'a91499f4aee0b274f0b3ab48549a2c58cd667a62c0cb87', + 'a91497a89cd5ada3a766a1275f8151e9256fcf537f6c87', + 'a9147ffc9f3a3b60635ea1783243274f4d07ab617cb487', + 'a9143423113ab913d86fd47e55488a0c559e18b457b987', + 'a914a28a3773a37c52ff6fd7dff497d0eaf80a46febb87']) + , #p2wsh 1of2 multisig wallet + ("1 Vpub5fAqpSRkLmvXwqbuR61MaKMSwj5z5xUBwanaz3qnJ5MgaBDpFSLUvKTiNK9zHp" + + "dvrg2LHHXkKxSXBHNWNpZz9b1VqADjmcCs3arSoxN3F3r Vpub5fvEo4MUpbVs9sZqr45" + + "zmRVEsTcQ49MA9m3MLht3XzdZvS9eMXLLu1H6TL1j2SMnykHqXNzG5ycMyQmFDvEE5B32" + + "sP8TmRe6wW8HjBgMssh", + #recv scriptpubkeys + ['002031fbaa839e96fc1abaf3453b9f770e0ccfe2d8e3e990bb381fdcb7db4722986a', + '0020820ae739b36f4feb1c299ced201db383bbcf1634e0071e489b385f43c2323761', + '0020eff05f4d14aa1968a7142b1009aa57a6208fb01b212f8b8f7df63645d26a1292', + '002049c6e17979dca380ffb66295d27f609bea2879d4f0b590c96c70ff12260a8721', + '002002bf2430fc7ebc6fb27da1cb80e52702edcc62a29f65c997e5c924dcd98411bd', + '0020c7a58dcf9633453ba12860b57c14af67d87d022be5c52bf6be7a6abdc295c6e0', + '0020136696059a5e932c72f4f0a05fa7f52faf9b54f1b7694e15acce710e6cc9e89d', + '0020c372e880227f35c2ee35d0724bf05cea95e74dcb3e6aa67ff15f561a29c0645d', + '002095c705590e2b84996fa44bff64179b26669e53bbd58d76bb6bbb5c5498a981ce', + '00207217754dae083c3c365c7e1ce3ad889ca2bd88e4f809cec66b9987adc390aa26', + '0020bee30906450e099357cc96a1f472c1ef70089cd4a0cba96749adfe1c9a2f9e87', + '0020b1838b3d5a386ad6c90eeae9a27a9b812e32ce06376f261dea89e405bc8209d9', + '0020231a3d05886efff601f0702d4c8450dfcce8d6a4bd90f17f7ff76f5c25c632de', + '002071220f3941b5f65aca90e464db4291cd5ea63f37fa858fd5b66d5019f0dbab0f', + '0020fc3c7db9f0e773f9f9c725d4286ddcc88db9575c45b2441d458018150eb4ef10', + '00209f037bfc98dee2fc0d3cca54df09b2d20e92a0133fa381a4dd74c49e4d0a89f5', + '0020c9060d0554ba2ca92048e1772e806d796ba41f10bf6aee2653a9eba96b05c944', + '0020a7cb1dd2730dba564f414ed8d9312370ff89c34df1441b83125cb4d97a96005a', + '00209fddc9b4e070b887dec034ed74f15f62d075a3ac8cf6eb95a88c635e0207534c', + '0020c48f9c50958ab8e386a8bd3888076f31d12e5cf011ff46cc83c6fadfe6d47d20', + '0020a659f4621dca404571917e73dedb26b6d7c49a07dacbf15890760ac0583d3267'], + #change scriptpubkeys + ['002030213b5d3b6988b86aa13a9eaca08e718d51f32dc130c70981abb0102173c791', + '002027bd198f9783a58e9bc4d3fdbd1c75cc74154905cce1d23c7bd3e051695418fe', + '0020c1fd2cdebf120d3b1dc990dfdaca62382ff9525beeb6a79a908ddecb40e2162c', + '00207a3e478266e5fe49fe22e3d8f04d3adda3b6a0835806a0db1f77b84d0ba7f79c', + '002059e66462023ecd54e20d4dce286795e7d5823af511989736edc0c7a844e249f5', + '0020bd8077906dd367d6d107d960397e46db2daba5793249f1f032d8d7e12e6f193c']) + , #p2wpkh-p2sh + ("upub5E4QEumGPNTmSKD95TrYX2xqLwwvBULbRzzHkrpW9WKKCB1y9DEfPXDnUyQjLjmVs" + + "7gSd7k5vRb1FoSb6BjyiWNg4arkJLaqk1jULzbwA5q", + ["a914ae8f84a06668742f713d0743c1f54d248040e63387", #recv + "a914c2e9bdcc48596b8cce418042ade72198fddf3cd987", + "a914a44b6ad63ccef0ae1741eaccee99bf2fa83f842987", + "a9148cf1c891d96a0be07893d0bddcf00ed5dad2c46e87", + "a91414d677b32f2409f4dfb3073d382c302bcd6ed33587", + "a9141b284bee7198d5134512f37ef60e4048864b4bd687"], + ["a914a5aacff65860440893107b01912dc8f60cadab2b87", #change + "a914dcd74ebc8bfc5cf0535717a3e833592d54b3c48687", + "a91446793cae4c2b8149ade61c1627b96b90599bc08787", + "a91439f3776831f321125bdb5099fbbd654923f8316c87"]) + , #p2wpkh-p2sh + ("ypub6XrRLtXNB7NQo3vDaMNnffXVJe1WVaebXcb4ncpTHHADLuFYmf2CcPn96YzUbMt8s" + + "HSMmtr1mCcMgCBLqNdY2hrXXcdiLxCdD9e2dChBLun", + ["a91429c2ad045bbb162ef3c2d9cacb9812bec463061787", #recv + "a91433ec6bb67b113978d9cfd307a97fd15bc0a5a62087", + "a91450523020275ccbf4e916a0d8523ae42391ad988a87", + "a91438c2e5e76a874d86cfc914fe9fc1868b6afb5c5487"], + ["a91475f608698bb735120a17699fee854bce9a8dc8d387", + "a91477e69344ef53587051c85a06a52a646457b44e6c87", + "a914607c98ea34fbdffe39fee161ae2ffd5517bf1a5587"]) + , #old mnemonic mpk + ("e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d" + + "5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442b3", + ["76a9149cd3dfb0d87a861770ae4e268e74b45335cf00ab88ac", #recv + "76a914c30f2af6a79296b6531bf34dba14c8419be8fb7d88ac", + "76a9145eb4eeaefcf9a709f8671444933243fbd05366a388ac", + "76a914f96669095e6df76cfdf5c7e49a1909f002e123d088ac"], + ["76a914ca14915184a2662b5d1505ce7142c8ca066c70e288ac", #change + "76a9148942ac692ace81019176c4fb0ac408b18b49237f88ac", + "76a914e1232622a96a04f5e5a24ca0792bb9c28b089d6e88ac"]) + , #p2wsh-p2sh 2of2 multisig + ("2 Ypub6hWbqA2p47QgsLt5J4nxrR3ngu8xsPGb7PdV8CDh48KyNngNqPKSqertAqYhQ4u" + + "mELu1UsZUCYfj9XPA6AdSMZWDZQobwF7EJ8uNrECaZg1 Ypub6iNDhL4WWq5kFZcdFqHHw" + + "X4YTH4rYGp8xbndpRrY7WNZFFRfogSrL7wRTajmVHgR46AT1cqUG1mrcRd7h1WXwBsgX2Q" + + "vT3zFbBCDiSDLkau", + ["a91428060ade179c792fac07fc8817fd150ce7cdd3f987", #recv + "a9145ba5ed441b9f3e22f71193d4043b645183e6aeee87", + "a91484cc1f317b7d5afff115916f1e27319919601d0187", + "a9144001695a154cac4d118af889d3fdcaf929af315787", + "a914897888f3152a27cbd7611faf6aa01085931e542a87"], + ["a91454dbb52de65795d144f3c4faeba0e37d9765c85687", #change + "a914f725cbd61c67f34ed40355f243b5bb0650ce61c587", + "a9143672bcd3d02d3ea7c3205ddbc825028a0d2a781987"]) + ] +) + +def test_deterministic_wallets(master_public_key, recv_spks, change_spks): + initial_count = 15 + gaplimit = 5 + wal = parse_electrum_master_public_key(master_public_key, gaplimit) + spks = wal.get_scriptpubkeys(0, 0, initial_count) + #for test, generate 15, check that the last 5 lead to gap limit overrun + for i in range(initial_count - gaplimit): + ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[i]]) + assert ret == None + for i in range(gaplimit): + index = i + initial_count - gaplimit + ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[index]]) + assert ret != None and ret[0] == i+1 + last_index_add = 3 + last_index = initial_count - gaplimit + last_index_add + ret = wal.have_scriptpubkeys_overrun_gaplimit(spks[2:last_index]) + assert ret[0] == last_index_add + assert wal.get_scriptpubkeys(0, 0, len(recv_spks)) == recv_spks + assert wal.get_scriptpubkeys(1, 0, len(change_spks)) == change_spks + diff --git a/test/test_merkleproof.py b/test/test_merkleproof.py @@ -0,0 +1,130 @@ + +import pytest + +from electrumpersonalserver import (convert_core_to_electrum_merkle_proof, + hash_merkle_root) + +''' +# code for printing out proofs not longer than 80 per line +proof = "" +def chunks(d, n): + return [d[x:x + n] for x in range(0, len(d), n)] +print("\" + \n\"".join(chunks(proof, 71))) +''' + +@pytest.mark.parametrize( + "proof", + [ + #txcount 819, pos 5 + "0300000026e696fba00f0a43907239305eed9e55824e0e376636380f000000000000000" + + "04f8a2ce51d6c69988029837688cbfc2f580799fa1747456b9c80ab808c1431acd0b07f" + + "5543201618cadcfbf7330300000b0ff1e0050fed22ca360e0935e053b0fe098f6f9e090" + + "f5631013361620d964fe2fd88544ae10b40621e1cd24bb4306e3815dc237f77118a45d7" + + "5ada9ee362314b70573732bce59615a3bcc1bbacd04b33b7819198212216b5d62d75be5" + + "9221ada17ba4fb2476b689cccd3be54732fd5630832a94f11fa3f0dafd6f904d43219e0" + + "d7de110158446b5b598bd241f7b5df4da0ebc7d30e7748d487917b718df51c681174e6a" + + "bab8042cc7c1c436221c098f06a56134f9247a812126d675d69c82ba1c715cfc0cde462" + + "fd1fbe5dc87f6b8db2b9c060fcd59a20e7fe8e921c3676937a873ff88684f4be4d015f2" + + "4f26af6d2cf78335e9218bcceba4507d0b4ba6cb933aa01ef77ae5eb411893ec0f74b69" + + "590fb0f5118ac937c02ccd47e9d90be78becd11ecf854d7d268eeb479b74d137278c0a5" + + "017d29e90cd5b35a4680201824fb0eb4f404e20dfeaec4d50549030b7e7e220b02eb210" + + "5f3d2e8bcc94d547214a9d03ff1600", + #txcount 47, pos 9 + "0100000053696a625fbd16df418575bce0c4148886c422774fca5fcab80100000000000" + + "01532bfe4f9c4f56cd141028e5b59384c133740174b74b1982c7f01020b90ce05577c67" + + "508bdb051a7ec2ef942f000000076cde2eb7efa90b36d48aed612e559ff2ba638d8d400" + + "b14b0c58df00c6a6c33b65dc8fa02f4ca56e1f4dcf17186fa9bbd990ce150b6e2dc9e9e" + + "56bb4f270fe56fde6bdd73a7a7e82767714862888e6b759568fb117674ad23050e29311" + + "97494d457efb72efdb9cb79cd4a435724908a0eb31ec7f7a67ee03837319e098b43edad" + + "3be9af75ae7b30db6f4f93ba0fdd941fdf70fe8cc38982e03bd292f5bd02f28137d343f" + + "908c7d6417379afe8349a257af3ca1f74f623be6a416fe1aa96a8f259983f2cf32121bc" + + "e203955a378b3b44f132ea6ab94c7829a6c3b360c9f8da8e74027701", + #txcount 2582, pos 330 + "000000206365d5e1d8b7fdf0b846cfa902115c1b8ced9dd49cb17800000000000000000" + + "01032e829e1f9a5a09d0492f9cd3ec0762b7facea555989c3927d3d975fd4078c771849" + + "5a45960018edd3b9e0160a00000dfe856a7d5d77c23ebf85c68b5eb303d85e56491ed6d" + + "204372625d0b4383df5a44d6e46d2db09d936b9f5d0b53e0dbcb3efb7773d457369c228" + + "fd1ce6e11645e366a58b3fc1e8a7c916710ce29a87265a6729a3b221b47ea9c8e6f4870" + + "7b112b8d67e5cfb3db5f88b042dc49e4e5bc2e61c28e1e0fbcba4c741bb5c75cac58ca0" + + "4161a7377d70f3fd19a3e248ed918c91709b49afd3760f89ed2fefbcc9c23447ccb40a2" + + "be7aba22b07189b0bf90c62db48a9fe37227e12c7af8c1d4c22f9f223530dacdd5f3ad8" + + "50ad4badf16cc24049a65334f59bf28c15cecda1a4cf3f2937bd70ee84f66569ce8ef95" + + "1d50cca46d60337e6c697685b38ad217967bbe6801d03c44fcb808cd035be31888380a2" + + "df1be14b6ff100de83cab0dce250e2b40ca3b47e8309f848646bee63b6185c176d84f15" + + "46a482e7a65a87d1a2d0d5a2b683e2cae0520df1e3525a71d71e1f551abd7d238c3bcb4" + + "ecaeea7d5988745fa421a8604a99857426957a2ccfa7cd8df145aa8293701989dd20750" + + "5923fcb339843944ce3d21dc259bcda9c251ed90d4e55af2cf5b15432050084f513ac74" + + "c0bdd4b6046fb70100", + #txcount 2861, pos 2860, last tx with many duplicated nodes down the tree + "00000020c656c90b521a2bbca14174f2939b882a28d23d86144b0e00000000000000000" + + "0cf5185a8e369c3de5f15e039e777760994fd66184b619d839dace3aec9953fd6d86159" + + "5ac1910018ee097a972d0b0000078d20d71c3114dbf52bb13f2c18f987891e8854d2d29" + + "f61c0b3d3256afcef7c0b1e6f76d6431f93390ebd28dbb81ad7c8f08459e85efeb23cc7" + + "2df2c5612215bf53dd4ab3703886bc8c82cb78ba761855e495fb5dc371cd8fe25ae974b" + + "df42269e267caf898a9f34cbf2350eaaa4afbeaea70636b5a3b73682186817db5b33290" + + "bd5c696bd8d0322671ff70c5447fcd7bdc127e5b84350c6b14b5a3b86b424d7db38d39f" + + "171f57e255a31c6c53415e3d65408b6519a40aacc49cad8e70646d4cb0d23d4a63068e6" + + "c220efc8a2781e9e774efdd334108d7453043bd3c8070d0e5903ad5b07", + #txcount 7, pos 6, duplicated entry in the last depth, at tx level + "0100000056e02c6d3278c754e0699517834741f7c4ad3dcbfeb7803a346200000000000" + + "0af3bdd5dd465443fd003e9281455e60aae573dd4d46304d7ba17276ea33d506488cbb4" + + "4dacb5001b9ebb193b0700000003cd3abb2eb7583165f36b56add7268be9027ead4cc8f" + + "888ec650d3b1c1f4de28a0ff7c8b463b2042d09598f0e5e5905de362aa1cf75252adc22" + + "719b8e1bc969adcfbc4782b8eafc9352263770b91a0f189ae051cbe0e26046c2b14cf3d" + + "8be0bc40135", + #txcount 6, pos 5, duplicated entry in the last-but-one depth + "01000000299edfd28524eae4fb6012e4087afdb6e1b912db85e612374b0300000000000" + + "0e16572394f8578a47bf36e15cd16faa5e3b9e18805cf4e271ae4ef95aa8cea7eb31fa1" + + "4e4b6d0b1a42857d960600000003f52b45ed953924366dab3e92707145c78615b639751" + + "ecb7be1c5ecc09b592ed588ca0e15a89e9049a2dbcadf4d8362bd1f74a6972f176617b5" + + "8a5466c8a4121fc3e2d6fa66c8637b387ef190ab46d6e9c9dae4bbccd871c72372b3dbc" + + "6edefea012d", + #txcount 5, pos 4, duplicated on the last and second last depth + "010000004d891de57908d89e9e5585648978d7650adc67e856c2d8c18c1800000000000" + + "04746fd317bffecd4ffb320239caa06685bafe0c1b5463b24d636e45788796657843d1b" + + "4d4c86041be68355c40500000002d8e26c89c46477f2407d866d2badbd98e43e732a670" + + "e96001faf1744b27e5fdd018733d72e31a2d6a0d94f2a3b35fcc66fb110c40c5bbff82b" + + "f87606553d541d011d", + #txcount 2739, pos 0, coinbase tx + "000000209f283da030c6e6d0ff5087a87c430d140ed6b4564fa34d00000000000000000" + + "0ec1513723e3652c6b8e777c41eb267ad8dd2025e85228840f5cfca7ffe1fb331afff8a" + + "5af8e961175e0f7691b30a00000df403e21a4751fbd52457f535378ac2dcf111199e9ea" + + "6f78f6c2663cb99b58203438d8f3b26f7f2804668c1df7d394a4726363d4873b2d85b71" + + "2e44cf4f5e4f33f22a8f3a1672846bd7c4570c668e6ee12befda23bfa3d0fcd30b1b079" + + "19b01c40b1e31b6d34fcdbb99539d46eb97a3ae15386f1ab0f28ecacadd9fc3fa4ce49a" + + "1a1839d815229f54036c8a3035d91e80e8dc127b62032b4e652550b4fc0aee0f6e85a14" + + "307d85ed9dde62acff9a0f7e3b52370a10d6c83ec13a0b4a8fafe87af368a167d7e9b63" + + "3b84b6ea65f1ce5e8ccc1840be0a4dab0099e25afccc7f2fdbda54cd65ecbac8d9a550c" + + "108b4e18d3af59129d373fde4c80848858fd6f7fc1e27387a38833473ca8a47729fa6e1" + + "cc14b584c14dad768108ff18cc6acdc9c31d32dc71c3c80856664a3fff870fe419a59aa" + + "9033356590475d36086f0b3c0ece34c0f3756675c610fb980ff3363af6f9c0918a7c677" + + "23371849de9c1026515c2900a80b3aee4f2625c8f48cd5eb967560ee8ebe58a8d41c331" + + "f6d5199795735d4f0494bdf592d166fa291062733619f0f133605087365639de2d9d5d6" + + "921f4b4204ff1f0000", + #txcount 1, pos 0, coinbase tx in an empty block, tree with height 1 + "010000000508085c47cc849eb80ea905cc7800a3be674ffc57263cf210c59d8d0000000" + + "0112ba175a1e04b14ba9e7ea5f76ab640affeef5ec98173ac9799a852fa39add320cd66" + + "49ffff001d1e2de5650100000001112ba175a1e04b14ba9e7ea5f76ab640affeef5ec98" + + "173ac9799a852fa39add30101", + #txcount 2, pos 1, tree with height 2 + "010000004e24a2880cd72d9bde7502087bd3756819794dc7548f68dd68dc30010000000" + + "02793fce9cdf91b4f84760571bf6009d5f0ffaddbfdc9234ef58a036096092117b10f4b" + + "4cfd68011c903e350b0200000002ee50562fc6f995eff2df61be0d5f943bac941149aa2" + + "1aacb32adc130c0f17d6a2077a642b1eabbc5120e31566a11e2689aa4d39b01cce9a190" + + "2360baa5e4328e0105" + ] +) + +def test_merkleproof(proof): + try: + electrum_proof = convert_core_to_electrum_merkle_proof(proof) + #print(electrum_proof) + implied_merkle_root = hash_merkle_root( + electrum_proof["merkle"], electrum_proof["txid"], + electrum_proof["pos"]) + assert implied_merkle_root == electrum_proof["merkleroot"] + except ValueError: + import traceback + traceback.print_exc() + assert 0 + diff --git a/test/test_parse_mpks.py b/test/test_parse_mpks.py @@ -0,0 +1,29 @@ + +import pytest + +from electrumpersonalserver import parse_electrum_master_public_key + + +@pytest.mark.parametrize( + "bad_master_public_key", + [ + "zpub661MyMwAqRbcGVQTLtBFzc3ENvyZHoUEhWRdGwoqLZaf5wXP9VcDY2VJV7usvsFLZz" + + "2RUTVhCVXYXc3S8zpLyAFbDFcfrpUiwLoE9VWH2yz", #bad checksum + "a tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" + + "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ tpubD6NzVbkrYhZ4WjgNYq2nF" + + "TbiSLW2SZAzs4g5JHLqwQ3AmR3tCWpqsZJJEoZuP5HAEBNxgYQhtWMezszoaeTCg6FWGQB" + + "T74sszGaxaf64o5s", #unparsable m number + "2 tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" + + "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ Vpub5fAqpSRkLmvXwqbuR61M" + + "aKMSwj5z5xUBwanaz3qnJ5MgaBDpFSLUvKTiNK9zHpdvrg2LHHXkKxSXBHNWNpZz9b1Vq" + + "ADjmcCs3arSoxN3F3r" #inconsistent magic + ] +) + +def test_parse_bad_mpk(bad_master_public_key): + try: + parse_electrum_master_public_key(bad_master_public_key, 5) + raised_error = False + except (ValueError, Exception): + raised_error = True + assert raised_error diff --git a/test/test_transactionmonitor.py b/test/test_transactionmonitor.py @@ -0,0 +1,346 @@ + +import pytest + +from electrumpersonalserver import (DeterministicWallet, TransactionMonitor, + JsonRpcError, script_to_scripthash) + +class DummyJsonRpc(object): + def __init__(self, txlist, utxoset, block_heights): + self.txlist = txlist + self.utxoset = utxoset + self.block_heights = block_heights + self.imported_addresses = [] + + def call(self, method, params): + if method == "listtransactions": + count = int(params[1]) + skip = int(params[2]) + return self.txlist[skip:skip + count] + elif method == "gettransaction": + for t in self.txlist: + if t["txid"] == params[0]: + return t + raise JsonRpcError() + elif method == "decoderawtransaction": + for t in self.txlist: + if t["hex"] == params[0]: + return t + assert 0 + elif method == "gettxout": + for u in self.utxoset: + if u["txid"] == params[0] and u["vout"] == params[1]: + return u + assert 0 + elif method == "getblockheader": + if params[0] not in self.block_heights: + assert 0 + return {"height": self.block_heights[params[0]]} + elif method == "decodescript": + return {"addresses": [dummy_spk_to_address(params[0])]} + elif method == "importaddress": + self.imported_addresses.append(params[0]) + else: + raise ValueError("unknown method in dummy jsonrpc") + + def add_transaction(self, tx): + self.txlist.append(tx) + + def get_imported_addresses(self): + return self.imported_addresses + + +class DummyDeterministicWallet(DeterministicWallet): + """Empty deterministic wallets""" + def __init__(self): + pass + + def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys): + return None #not overrun + + def get_new_scriptpubkeys(self, change, count): + pass + + +def dummy_spk_to_address(spk): + return spk + "-address" + +debugf = lambda x: print("[DEBUG] " + x) +logf = lambda x: print("[ LOG] " + x) + +deterministic_wallets = [DummyDeterministicWallet()] +dummy_id_g = [1000] + +def create_dummy_spk(): #script pub key + dummy_id = dummy_id_g[0] + dummy_id_g[0] += 1 + return "deadbeef" + str(dummy_id) + +def create_dummy_funding_tx(confirmations=1, output_spk=None, + input_txid="placeholder-unknown-input-txid"): + dummy_id = dummy_id_g[0] + dummy_id_g[0] += 1 + + if output_spk == None: + dummy_spk = "deadbeef" + str(dummy_id) #scriptpubkey + else: + dummy_spk = output_spk + dummy_containing_block = "blockhash-placeholder" + str(dummy_id) + containing_block_height = dummy_id + dummy_tx = { + "txid": "placeholder-test-txid" + str(dummy_id), + "vin": [{"txid": input_txid, "vout": 0, "value": 1, + "confirmations": 1}], + "vout": [{"value": 1, "scriptPubKey": {"hex": dummy_spk}}], + "address": dummy_spk_to_address(dummy_spk), + "category": "receive", + "confirmations": confirmations, + "blockhash": dummy_containing_block, + "hex": "placeholder-test-txhex" + str(dummy_id) + } + return dummy_spk, containing_block_height, dummy_tx + +def assert_address_history_tx(address_history, spk, height, txid, subscribed): + history_element = address_history[script_to_scripthash(spk)] + assert history_element["history"][0]["height"] == height + assert history_element["history"][0]["tx_hash"] == txid + #fee always zero, its easier to test because otherwise you have + # to use Decimal to stop float weirdness + if height == 0: + assert history_element["history"][0]["fee"] == 0 + assert history_element["subscribed"] == subscribed + +def test_single_tx(): + ###single confirmed tx in wallet belonging to us, address history built + dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx() + + rpc = DummyJsonRpc([dummy_tx], [], + {dummy_tx["blockhash"]: containing_block_height}) + txmonitor = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) + assert txmonitor.build_address_history([dummy_spk]) + assert len(txmonitor.address_history) == 1 + assert_address_history_tx(txmonitor.address_history, spk=dummy_spk, + height=containing_block_height, txid=dummy_tx["txid"], subscribed=False) + +def test_two_txes(): + ###two confirmed txes in wallet belonging to us, addr history built + dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx() + dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx() + + rpc = DummyJsonRpc([dummy_tx1, dummy_tx2], [], + {dummy_tx1["blockhash"]: containing_block_height1, + dummy_tx2["blockhash"]: containing_block_height2}) + txmonitor = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) + assert txmonitor.build_address_history([dummy_spk1, dummy_spk2]) + assert len(txmonitor.address_history) == 2 + assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1, + height=containing_block_height1, txid=dummy_tx1["txid"], + subscribed=False) + assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2, + height=containing_block_height2, txid=dummy_tx2["txid"], + subscribed=False) + +def test_non_subscribed_confirmation(): + ###one unconfirmed tx in wallet belonging to us, with confirmed inputs, + ### addr history built, then tx confirms, not subscribed to address + dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx( + confirmations=0) + + rpc = DummyJsonRpc([dummy_tx], [dummy_tx["vin"][0]], + {dummy_tx["blockhash"]: containing_block_height}) + txmonitor = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) + assert txmonitor.build_address_history([dummy_spk]) + assert len(txmonitor.address_history) == 1 + assert_address_history_tx(txmonitor.address_history, spk=dummy_spk, + height=0, txid=dummy_tx["txid"], subscribed=False) + assert len(list(txmonitor.check_for_updated_txes())) == 0 + dummy_tx["confirmations"] = 1 #tx confirms + #not subscribed so still only returns an empty list + assert len(list(txmonitor.check_for_updated_txes())) == 0 + assert_address_history_tx(txmonitor.address_history, spk=dummy_spk, + height=containing_block_height, txid=dummy_tx["txid"], subscribed=False) + +def test_tx_arrival_then_confirmation(): + ###build empty address history, subscribe one address + ### an unconfirmed tx appears, then confirms + dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx( + confirmations=0) + + rpc = DummyJsonRpc([], [dummy_tx["vin"][0]], {dummy_tx["blockhash"]: + containing_block_height}) + txmonitor = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) + assert txmonitor.build_address_history([dummy_spk]) + assert len(txmonitor.address_history) == 1 + sh = script_to_scripthash(dummy_spk) + assert len(txmonitor.get_electrum_history(sh)) == 0 + txmonitor.subscribe_address(sh) + # unconfirm transaction appears + assert len(list(txmonitor.check_for_updated_txes())) == 0 + rpc.add_transaction(dummy_tx) + assert len(list(txmonitor.check_for_updated_txes())) == 1 + assert_address_history_tx(txmonitor.address_history, spk=dummy_spk, + height=0, txid=dummy_tx["txid"], subscribed=True) + # transaction confirms + dummy_tx["confirmations"] = 1 + assert len(list(txmonitor.check_for_updated_txes())) == 1 + assert_address_history_tx(txmonitor.address_history, spk=dummy_spk, + height=containing_block_height, txid=dummy_tx["txid"], subscribed=True) + +def test_unrelated_tx(): + ###transaction that has nothing to do with our wallet + dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx( + confirmations=0) + our_dummy_spk = create_dummy_spk() + + rpc = DummyJsonRpc([dummy_tx], [], {dummy_tx["blockhash"]: + containing_block_height}) + txmonitor = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) + assert txmonitor.build_address_history([our_dummy_spk]) + assert len(txmonitor.address_history) == 1 + assert len(txmonitor.get_electrum_history(script_to_scripthash( + our_dummy_spk))) == 0 + +def test_address_reuse(): + ###transaction which arrives to an address which already has a tx on it + dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx() + dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx( + output_spk=dummy_spk1) + + rpc = DummyJsonRpc([dummy_tx1], [], {dummy_tx1["blockhash"]: + containing_block_height1, dummy_tx2["blockhash"]: + containing_block_height2}) + txmonitor = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) + assert txmonitor.build_address_history([dummy_spk1]) + sh = script_to_scripthash(dummy_spk1) + assert len(txmonitor.get_electrum_history(sh)) == 1 + rpc.add_transaction(dummy_tx2) + assert len(txmonitor.get_electrum_history(sh)) == 1 + txmonitor.check_for_updated_txes() + assert len(txmonitor.get_electrum_history(sh)) == 2 + +def test_from_address(): + ###transaction spending FROM one of our addresses + dummy_spk1, containing_block_height1, input_tx = create_dummy_funding_tx() + dummy_spk2, containing_block_height2, spending_tx = create_dummy_funding_tx( + input_txid=input_tx["txid"]) + + rpc = DummyJsonRpc([input_tx, spending_tx], [], + {input_tx["blockhash"]: containing_block_height1, + spending_tx["blockhash"]: containing_block_height2}) + txmonitor = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) + assert txmonitor.build_address_history([dummy_spk1]) + sh = script_to_scripthash(dummy_spk1) + assert len(txmonitor.get_electrum_history(sh)) == 2 + +def test_tx_within_wallet(): + ###transaction from one address to the other, both addresses in wallet + dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx() + dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx( + input_txid=dummy_tx1["txid"]) + + rpc = DummyJsonRpc([dummy_tx1, dummy_tx2], [], + {dummy_tx1["blockhash"]: containing_block_height1, + dummy_tx2["blockhash"]: containing_block_height2}) + txmonitor = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) + assert txmonitor.build_address_history([dummy_spk1, dummy_spk2]) + assert len(txmonitor.get_electrum_history(script_to_scripthash( + dummy_spk1))) == 2 + assert len(txmonitor.get_electrum_history(script_to_scripthash( + dummy_spk2))) == 1 + +def test_overrun_gap_limit(): + ###overrun gap limit so import address is needed + dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx() + dummy_spk_imported = create_dummy_spk() + + class DummyImportDeterministicWallet(DeterministicWallet): + def __init__(self): + pass + + def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys): + return {0: 1} #overrun by one + + def get_new_scriptpubkeys(self, change, count): + return [dummy_spk_imported] + + rpc = DummyJsonRpc([], [], {dummy_tx["blockhash"]: containing_block_height}) + txmonitor = TransactionMonitor(rpc, [DummyImportDeterministicWallet()], + debugf, logf) + assert txmonitor.build_address_history([dummy_spk]) + assert len(txmonitor.address_history) == 1 + assert len(list(txmonitor.check_for_updated_txes())) == 0 + assert len(txmonitor.get_electrum_history(script_to_scripthash( + dummy_spk))) == 0 + rpc.add_transaction(dummy_tx) + assert len(list(txmonitor.check_for_updated_txes())) == 0 + assert len(txmonitor.get_electrum_history(script_to_scripthash( + dummy_spk))) == 1 + assert len(txmonitor.get_electrum_history(script_to_scripthash( + dummy_spk_imported))) == 0 + assert len(rpc.get_imported_addresses()) == 1 + assert rpc.get_imported_addresses()[0] == dummy_spk_to_address( + dummy_spk_imported) + +def test_conflicted_tx(): + ###conflicted transaction should get rejected + dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx( + confirmations=-1) + + rpc = DummyJsonRpc([dummy_tx], [], {}) + txmonitor = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) + assert txmonitor.build_address_history([dummy_spk]) + assert len(txmonitor.address_history) == 1 + assert len(txmonitor.get_electrum_history(script_to_scripthash( + dummy_spk))) == 0 #shouldnt show up after build history b/c conflicted + rpc.add_transaction(dummy_tx) + assert len(list(txmonitor.check_for_updated_txes())) == 0 + assert len(txmonitor.get_electrum_history(script_to_scripthash( + dummy_spk))) == 0 #incoming tx is not added too + +def test_double_spend(): + ###an unconfirmed tx being broadcast, another conflicting tx being + ### confirmed, the first tx gets conflicted status + + dummy_spk, containing_block_height1, dummy_tx1 = create_dummy_funding_tx( + confirmations=0) + dummy_spk_, containing_block_height2, dummy_tx2 = create_dummy_funding_tx( + confirmations=0, input_txid=dummy_tx1["vin"][0], output_spk=dummy_spk) + #two unconfirmed txes spending the same input, so they are in conflict + + rpc = DummyJsonRpc([dummy_tx1], [dummy_tx1["vin"][0]], + {dummy_tx1["blockhash"]: containing_block_height1, + dummy_tx2["blockhash"]: containing_block_height2}) + txmonitor = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) + assert txmonitor.build_address_history([dummy_spk]) + assert len(txmonitor.address_history) == 1 + sh = script_to_scripthash(dummy_spk) + assert len(txmonitor.get_electrum_history(sh)) == 1 + assert_address_history_tx(txmonitor.address_history, spk=dummy_spk, + height=0, txid=dummy_tx1["txid"], subscribed=False) + # a conflicting transaction confirms + rpc.add_transaction(dummy_tx2) + dummy_tx1["confirmations"] = -1 + dummy_tx2["confirmations"] = 1 + assert len(list(txmonitor.check_for_updated_txes())) == 0 + assert len(txmonitor.get_electrum_history(sh)) == 1 + assert_address_history_tx(txmonitor.address_history, spk=dummy_spk, + height=containing_block_height2, txid=dummy_tx2["txid"], + subscribed=False) + + +#other possible stuff to test: +#finding confirmed and unconfirmed tx, in that order, then both confirm +#finding unconfirmed and confirmed tx, in that order, then both confirm + +#tests about conflicts: +#build address history where reorgable txes are found +#an unconfirmed tx arrives, gets confirmed, reaches the safe threshold +# and gets removed from list +#a confirmed tx arrives, reaches safe threshold and gets removed +#an unconfirmed tx arrives, confirms, gets reorgd out, returns to +# unconfirmed +#an unconfirmed tx arrives, confirms, gets reorgd out and conflicted +#an unconfirmed tx arrives, confirms, gets reorgd out and confirmed at +# a different height +#an unconfirmed tx arrives, confirms, gets reorgd out and confirmed in +# the same height +