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 c5a18b67def9de93021404b603e4317d8c573d80
parent a1fe3f1b72007ab7cc6c360dc79d5137949e8ef1
Author: chris-belcher <chris-belcher@users.noreply.github.com>
Date:   Mon, 26 Mar 2018 22:21:06 +0100

Rewrote some of README, commented code some more

Diffstat:
MREADME.md | 80+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mconfig.cfg_sample | 4++--
Mserver.py | 1+
Mtransactionmonitor.py | 33+++++++++++++++++++++++----------
4 files changed, 68 insertions(+), 50 deletions(-)

diff --git a/README.md b/README.md @@ -23,13 +23,10 @@ actually genuine, the same applies for bitcoin. Full node wallets are also important for privacy. Using Electrum under default configuration requires it to send all your bitcoin addresses to some server. -That server can then easily spy on you. Full nodes download the entire -blockchain and scan it for the user's own addresses, and therefore don't reveal -to anyone else which bitcoin addresses they are interested in. - -Using Electrum with Electrum Personal Server is probably the most -resource-efficient way right now to use a hardware wallet connected to your -own full node. +That server can then easily spy on you. Full node wallets like Electrum Personal +Server would download the entire blockchain and scan it for the user's own +addresses, and therefore don't reveal to anyone else which bitcoin addresses +they are interested in. For a longer explaination of this project, see the [mailing list email](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-February/015707.html) @@ -37,29 +34,31 @@ and [bitcointalk thread](https://bitcointalk.org/index.php?topic=2664747.msg2717 See also the Electrum bitcoin wallet [website](https://electrum.org/). -## How To Use +## How To -This application requires python3 and a Bitcoin full node version 0.16 or -higher. Make sure you +* Download and install python3 and a Bitcoin full node version 0.16 or higher. Make sure you [verify the digital signatures](https://bitcoin.stackexchange.com/questions/50185/how-to-verify-bitcoin-core-release-signing-keys) of any binaries before running them, or compile from source. -Download the latest release or clone the git repository. Enter the directory -and rename the file `config.cfg_sample` to `config.cfg`, edit this file to -configure everything about the server. Add your wallet master public keys or -watch-only addresses to the `[master-public-keys]` and `[watch-only-addresses]` -sections. +* Download the [latest release](https://github.com/chris-belcher/electrum-personal-server/releases) or clone the git repository. Enter the directory +and rename the file `config.cfg_sample` to `config.cfg`. + +* Edit the file `config.cfg` to configure everything about the server. Add your +wallet master public keys or watch-only addresses to the `[master-public-keys]` +and `[watch-only-addresses]` sections. Master public keys for an Electrum wallet +can be found in the Electrum client menu `Wallet` -> `Information`. -Finally run `./server.py` on Linux or double-click `run-server.bat` on Windows. +* Run `./server.py` on Linux or double-click `run-server.bat` on Windows. The first time the server is run it will import all configured addresses as -watch-only into the Bitcoin node, and then exit giving you a chance to -rescan if your wallet contains historical transactions. +watch-only into the Bitcoin node, and then exit. If the wallets contain +historical transactions you can use the rescan script to make them appear. -Tell Electrum to connect to the server in `Tools` -> `Server`, usually -`localhost` if running on the same machine. Make sure the port number matches -whats written in `config.cfg`. +* Run the server again which will start Electrum Personal Server. Tell Electrum +wallet to connect to it in `Tools` -> `Server`. By default the server details +are `localhost` if running on the same machine. Make sure the port number +matches what is written in `config.cfg` (port 50002 by default). -You can also try this with on [testnet bitcoin](https://en.bitcoin.it/wiki/Testnet). +Electrum Personal Server also works on [testnet bitcoin](https://en.bitcoin.it/wiki/Testnet). Electrum can be started in testnet mode with the command line flag `--testnet`. #### Exposure to the Internet @@ -69,32 +68,27 @@ able to synchronize their wallet, and they could potentially learn all your wallet addresses. By default the server will accept connections only from `localhost` so you -should either run Electrum wallet from the same computer or use a SSH tunnel to -another computer. +should either run Electrum wallet from the same computer or use a SSH tunnel +from another computer. #### How is this different from other Electrum servers ? They are different approaches with different tradeoffs. Electrum Personal Server is compatible with pruning, blocksonly and txindex=0, uses less CPU and -RAM and doesn't require an index of every bitcoin address ever used; the -tradeoff is when recovering an old wallet, you must to import your wallet into -it first and you may need to rescan, so it loses the "instant on" feature of -Electrum wallet. Other Electrum server implementations will be able to sync -your wallet immediately even if you have historical transactions, and they can -serve multiple Electrum wallets at once. +RAM, is suitable for being used intermittently rather than needing to be +always-on, and doesn't require an index of every bitcoin address ever used. The +tradeoff is when recovering an old wallet, you must to import your wallet first +and you may need to rescan, so it loses the "instant on" feature of Electrum +wallet. Other Electrum server implementations will be able to sync your wallet +immediately even if you have historical transactions, and they can serve +multiple Electrum connections at once. Definitely check out implementations like [ElectrumX](https://github.com/kyuupichan/electrumx/) if you're interested in this sort of thing. ## Project Readiness -This project is in alpha stages as there are several essential missing -features such as: - -* The Electrum server protocol has a caveat about multiple transactions included - in the same block. So there may be weird behaviour if that happens. - -* There's no way to turn off debug messages, so the console will be spammed by - them when used. +This project is in beta release. It should be usable by any reasonably-technical +bitcoin user. When trying this, make sure you report any crashes, odd behaviour, transactions appearing as `Not Verified` or times when Electrum disconnects (which @@ -102,6 +96,16 @@ indicates the server behaved unexpectedly). Someone should try running this on a Raspberry PI. +#### Caveat about pruning + +Electrum Personal Server is fully compatible with pruning, except for one thing. +Merkle proofs are read from disk. If pruning is enabled and if that specific +block has been deleted from disk, then no merkle proof can be sent to Electrum +which will display the transaction as `Not Verified` in the wallet interface. + +One day this may be improved on by writing new code for Bitcoin Core. See the +discussion [here](https://bitcointalk.org/index.php?topic=3167572.0). + ## Contributing I welcome contributions. Please keep lines under 80 characters in length and diff --git a/config.cfg_sample b/config.cfg_sample @@ -4,9 +4,9 @@ [master-public-keys] ## Add electrum master public keys to this section -## Create a wallet in electrum then go Wallet -> Information +## Create a wallet in electrum then go Wallet -> Information to get the mpk -#any_key_works = xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF +#any_name_works = xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF # Multisig wallets use format `required-signatures [list of master pub keys]` #multisig_wallet = 2 xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF diff --git a/server.py b/server.py @@ -320,6 +320,7 @@ def get_scriptpubkeys_to_monitor(rpc, config): import_needed = True watch_only_addresses_to_import = wallet_addresses - imported_addresses + #if addresses need to be imported then return them if import_needed: addresses_to_import = [hashes.script_to_address(spk, rpc) for spk in spks_to_import] diff --git a/transactionmonitor.py b/transactionmonitor.py @@ -6,11 +6,21 @@ from jsonrpc import JsonRpcError import server as s import hashes +#internally this code uses scriptPubKeys, it only converts to bitcoin addresses +# when importing to bitcoind or checking whether enough addresses have been +# imported +#the electrum protocol uses sha256(scriptpubkey) as a key for lookups +# this code calls them scripthashes + class TransactionMonitor(object): + """ + Class which monitors the bitcoind wallet for new transactions + and builds a history datastructure for sending to electrum + """ def __init__(self, rpc, deterministic_wallets): self.rpc = rpc self.deterministic_wallets = deterministic_wallets - self.last_known_recent_txid = None + self.last_known_wallet_txid = None self.address_history = None self.unconfirmed_txes = None @@ -113,10 +123,10 @@ class TransactionMonitor(object): if len(ret) > 0: #txid doesnt uniquely identify transactions from listtransactions #but the tuple (txid, address) does - self.last_known_recent_txid = (ret[-1]["txid"], ret[-1]["address"]) + self.last_known_wallet_txid = (ret[-1]["txid"], ret[-1]["address"]) else: - self.last_known_recent_txid = None - s.debug("last_known_recent_txid = " + str(self.last_known_recent_txid)) + self.last_known_wallet_txid = None + s.debug("last_known_wallet_txid = " + str(self.last_known_wallet_txid)) et = time.time() s.debug("address_history =\n" + pprint.pformat(address_history)) @@ -245,15 +255,15 @@ class TransactionMonitor(object): ret = self.rpc.call("listtransactions", ["*", tx_request_count, 0, True]) ret = ret[::-1] - if self.last_known_recent_txid == None: + if self.last_known_wallet_txid == None: recent_tx_index = len(ret) #=0 means no new txes break else: txid_list = [(tx["txid"], tx["address"]) for tx in ret] recent_tx_index = next((i for i, (txid, addr) in enumerate(txid_list) if - txid == self.last_known_recent_txid[0] and - addr == self.last_known_recent_txid[1]), -1) + txid == self.last_known_wallet_txid[0] and + addr == self.last_known_wallet_txid[1]), -1) if recent_tx_index != -1: break tx_request_count *= 2 @@ -264,9 +274,9 @@ class TransactionMonitor(object): str(ret)) # str([(t["txid"], t["address"]) for t in ret])) if len(ret) > 0: - self.last_known_recent_txid = (ret[0]["txid"], ret[0]["address"]) - s.debug("last_known_recent_txid = " + str( - self.last_known_recent_txid)) + self.last_known_wallet_txid = (ret[0]["txid"], ret[0]["address"]) + s.debug("last_known_wallet_txid = " + str( + self.last_known_wallet_txid)) assert(recent_tx_index != -1) if recent_tx_index == 0: return set() @@ -350,6 +360,7 @@ class TestJsonRpc(object): 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 @@ -663,6 +674,8 @@ def test(): 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) #other possible stuff to test: #finding confirmed and unconfirmed tx, in that order, then both confirm