tadded support for bech32 addresses, requires bitcoin core 0.16 - electrum-personal-server - Maximally lightweight electrum server for a single user
HTML git clone https://git.parazyd.org/electrum-personal-server
DIR Log
DIR Files
DIR Refs
DIR README
---
DIR commit f4e9cfba198c30c6f4209351a0966cdc2a36e4f0
DIR parent 726ce096461b3d5321d01d2b60cd492cae10face
HTML Author: chris-belcher <chris-belcher@users.noreply.github.com>
Date: Mon, 26 Feb 2018 16:15:36 +0000
added support for bech32 addresses, requires bitcoin core 0.16
Diffstat:
M README.md | 8 ++++----
M config.cfg_sample | 6 +++---
M server.py | 28 +++++++++++++---------------
M util.py | 53 +++++++++++++++++++++++--------
4 files changed, 60 insertions(+), 35 deletions(-)
---
DIR diff --git a/README.md b/README.md
t@@ -21,8 +21,10 @@ See also the Electrum bitcoin wallet [website](https://electrum.org/).
## How To Use
-This application requires python3 and a Bitcoin full node built with wallet
-capability.
+This application requires 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
t@@ -66,8 +68,6 @@ features such as:
* Deterministic wallets and master public keys are not supported. Addresses
must be imported individually.
-* Bech32 bitcoin addresses are not supported.
-
* The Electrum server protocol has a caveat about multiple transactions included
in the same block. So there may be weird behaviour if that happens.
DIR diff --git a/config.cfg_sample b/config.cfg_sample
t@@ -5,12 +5,12 @@
[wallets]
## Add addresses to this section
-# These are just random addresses I found on a blockchain explorer
+# These are just random example addresses found on a blockchain explorer
# A key can be anything
addr = 1DuqpoeTB9zLvVCXQG53VbMxvMkijk494n
# A comma separated list is also accepted
-my_test_addresses = 1KKszdQEpXgSY4EvsnGcGEzbQFmdcFwuNS,1EuEVUtQQ8hmuMHNdMdjLwjpkm6Ef7RYVk
+my_test_addresses = 1KKszdQEpXgSY4EvsnGcGEzbQFmdcFwuNS,1EuEVUtQQ8hmuMHNdMdjLwjpkm6Ef7RYVk,bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
# And space separated
more_test_addresses = 3Hh7QujVLqz11tiQsnUE5CSL16WEHBmiyR 1Arzu6mWZuXGTF9yR2hZhMgBJgu1Xh2bNC 1PXRLo1FQoZyF1Jhnz4qbG5x8Bo3pFpybz
t@@ -23,7 +23,7 @@ password = password
#how often in seconds to poll for new transactions when electrum not connected
poll_interval_listening = 30
#how often in seconds to poll for new transactions when electrum is connected
-poll_interval_connected = 5
+poll_interval_connected = 2
[electrum-server]
#0.0.0.0 to accept connections from any IP
DIR diff --git a/server.py b/server.py
t@@ -57,7 +57,6 @@ from decimal import Decimal
from jsonrpc import JsonRpc, JsonRpcError
import util
-import bitcoin as btc
ADDRESSES_LABEL = "electrum-watchonly-addresses"
t@@ -327,18 +326,17 @@ def run_electrum_server(hostport, rpc, address_history, unconfirmed_txes,
def get_input_and_output_scriptpubkeys(rpc, txid):
gettx = rpc.call("gettransaction", [txid])
- txd = btc.deserialize(gettx["hex"])
- output_scriptpubkeys = [sc['script'] for sc in txd['outs']]
+ txd = rpc.call("decoderawtransaction", [gettx["hex"]])
+ output_scriptpubkeys = [out["scriptPubKey"]["hex"] for out in txd["vout"]]
input_scriptpubkeys = []
- for ins in txd["ins"]:
+ for inn in txd["vin"]:
try:
- wallet_tx = rpc.call("gettransaction", [ins["outpoint"][
- "hash"]])
+ wallet_tx = rpc.call("gettransaction", [inn["txid"]])
except JsonRpcError:
#wallet doesnt know about this tx, so the input isnt ours
continue
- script = btc.deserialize(str(wallet_tx["hex"]))["outs"][ins[
- "outpoint"]["index"]]["script"]
+ input_decoded = rpc.call("decoderawtransaction", [wallet_tx["hex"]])
+ script = input_decoded["vout"][inn["vout"]]["scriptPubKey"]["hex"]
input_scriptpubkeys.append(script)
return output_scriptpubkeys, input_scriptpubkeys, txd
t@@ -346,12 +344,10 @@ def generate_new_history_element(rpc, tx, txd):
if tx["confirmations"] == 0:
unconfirmed_input = False
total_input_value = 0
- for ins in txd["ins"]:
- utxo = rpc.call("gettxout", [ins["outpoint"]["hash"],
- ins["outpoint"]["index"], True])
+ for inn in txd["vin"]:
+ utxo = rpc.call("gettxout", [inn["txid"], inn["vout"], True])
if utxo is None:
- utxo = rpc.call("gettxout", [ins["outpoint"]["hash"],
- ins["outpoint"]["index"], False])
+ utxo = rpc.call("gettxout", [inn["txid"], inn["vout"], False])
if utxo is None:
debug("utxo not found(!)")
#TODO detect this and figure out how to tell
t@@ -360,7 +356,8 @@ def generate_new_history_element(rpc, tx, txd):
unconfirmed_input = unconfirmed_input or utxo["confirmations"] == 0
debug("total_input_value = " + str(total_input_value))
- fee = total_input_value - sum([sc["value"] for sc in txd["outs"]])
+ fee = total_input_value - sum([int(Decimal(out["value"])*Decimal(1e8))
+ for out in txd["vout"]])
height = -1 if unconfirmed_input else 0
new_history_element = ({"tx_hash": tx["txid"], "height": height,
"fee": fee})
t@@ -505,7 +502,7 @@ def build_address_history_index(rpc, wallet_addresses):
st = time.time()
address_history = {}
for addr in wallet_addresses:
- scripthash = util.address_to_scripthash(addr)
+ scripthash = util.address_to_scripthash(addr, rpc)
address_history[scripthash] = {'addr': addr, 'history': [],
'subscribed': False}
wallet_addr_scripthashes = set(address_history.keys())
t@@ -530,6 +527,7 @@ def build_address_history_index(rpc, wallet_addresses):
continue
if tx["txid"] in obtained_txids:
continue
+ debug("adding obtained tx=" + str(tx["txid"]))
obtained_txids.add(tx["txid"])
#obtain all the addresses this transaction is involved with
DIR diff --git a/util.py b/util.py
t@@ -31,9 +31,11 @@ def script_to_scripthash(script):
h = sha256(bytes.fromhex(script))[0:32]
return bh2u(bytes(reversed(h)))
-def address_to_scripthash(addr):
- script = btc.address_to_script(addr)
- return script_to_scripthash(script)
+def address_to_script(addr, rpc):
+ return rpc.call("validateaddress", [addr])["scriptPubKey"]
+
+def address_to_scripthash(addr, rpc):
+ return script_to_scripthash(address_to_script(addr, rpc))
#the 'result' field in the blockchain.scripthash.subscribe method
# reply uses this as a summary of the address
t@@ -66,8 +68,10 @@ def hash_merkle_root(merkle_s, target_hash, pos):
def script_to_address(script):
+ #TODO why is this even here? its not used anywhere, maybe old code
#TODO bech32 addresses
- #TODO testnet, although everything uses scripthash so the address vbyte doesnt matter
+ #TODO testnet, although everything uses scripthash so the address
+ # vbyte doesnt matter
return btc.script_to_address(script, 0x00)
def calc_tree_width(height, txcount):
t@@ -85,23 +89,27 @@ def decend_merkle_tree(hashes, flags, height, txcount, pos):
left = decend_merkle_tree(hashes, flags, height-1, txcount, pos*2)
#bitcoin has a rule that if theres an odd number of nodes in
# the merkle tree, the last hash is duplicated
+ #in the electrum format we must hash together the duplicate
+ # tree branch
if pos*2+1 < calc_tree_width(height-1, txcount):
right = decend_merkle_tree(hashes, flags, height-1,
txcount, pos*2+1)
else:
- right = left
- #TODO decend down one branch and hash it up, place in right
+ if isinstance(left, tuple):
+ right = expand_tree_hashing(left)
+ else:
+ right = left
return (left, right)
else:
hs = next(hashes)
- hs = hs[:4] + '...' + hs[-4:]
- print(hs)
+ #hs = hs[:4] + '...' + hs[-4:]
+ #print(hs)
return hs
else:
#txid node
hs = next(hashes)
- hs = hs[:4] + '...' + hs[-4:]
- print(hs)
+ #hs = hs[:4] + '...' + hs[-4:]
+ #print(hs)
if flag:
return "tx:" + str(pos) + ":" + hs
else:
t@@ -130,6 +138,25 @@ def expand_tree_electrum_format(node, result):
if not isinstance(right, tuple):
result.append(right)
+def deserialize_hash_node(node):
+ if node.startswith("tx"):
+ return node.split(":")[2]
+ else:
+ return node
+
+#recurse down into the tree, hashing everything and returning root hash
+def expand_tree_hashing(node):
+ left, right = node
+ if isinstance(left, tuple):
+ hash_left = expand_tree_hashing(left)
+ else:
+ hash_left = deserialize_hash_node(left)
+ if isinstance(right, tuple):
+ hash_right = expand_tree_hashing(right)
+ else:
+ hash_right = deserialize_hash_node(right)
+ return hash_encode(Hash(hash_decode(hash_left) + hash_decode(hash_right)))
+
#https://github.com/bitcoin/bitcoin/blob/master/src/merkleblock.h
#https://github.com/breadwallet/breadwallet-core/blob/master/BRMerkleBlock.c
def convert_core_to_electrum_merkle_proof(proof):
t@@ -279,20 +306,19 @@ merkle_test_vectors = [
parazyd.org:70 /git/electrum-personal-server/commit/f4e9cfba198c30c6f4209351a0966cdc2a36e4f0.gph:237: line too long