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 cbd7953d8857566b756d0302bf423bd1b882f726
parent eb0bfa16fe6e1889aecb3e9ec0c2c2a951c7c7ca
Author: chris-belcher <chris-belcher@users.noreply.github.com>
Date:   Sun,  8 Apr 2018 16:42:06 +0100

fixed crash bug when an unconfirmed tx becomes conflicted due to another tx confirming

Diffstat:
Melectrumpersonalserver/transactionmonitor.py | 104++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
1 file changed, 77 insertions(+), 27 deletions(-)

diff --git a/electrumpersonalserver/transactionmonitor.py b/electrumpersonalserver/transactionmonitor.py @@ -252,7 +252,7 @@ class TransactionMonitor(object): return updated_scrhashes def check_for_confirmations(self): - confirmed_txes_scrhashes = [] + tx_scrhashes_removed_from_mempool = [] self.debug("check4con unconfirmed_txes = " + pprint.pformat(self.unconfirmed_txes)) for uc_txid, scrhashes in self.unconfirmed_txes.items(): @@ -260,20 +260,24 @@ class TransactionMonitor(object): self.debug("uc_txid=" + uc_txid + " => " + str(tx)) if tx["confirmations"] == 0: continue #still unconfirmed - self.log("A transaction confirmed: " + uc_txid) - confirmed_txes_scrhashes.append((uc_txid, scrhashes)) - block = self.rpc.call("getblockheader", [tx["blockhash"]]) + tx_scrhashes_removed_from_mempool.append((uc_txid, scrhashes)) + if tx["confirmations"] > 0: + self.log("A transaction confirmed: " + uc_txid) + block = self.rpc.call("getblockheader", [tx["blockhash"]]) + elif tx["confirmations"] == -1: + self.log("A transaction became conflicted: " + uc_txid) for scrhash in scrhashes: #delete the old unconfirmed entry in address_history deleted_entries = [h for h in self.address_history[scrhash][ "history"] if h["tx_hash"] == uc_txid] for d_his in deleted_entries: self.address_history[scrhash]["history"].remove(d_his) - #create the new confirmed entry in address_history - self.address_history[scrhash]["history"].append({"height": - block["height"], "tx_hash": uc_txid}) + if tx["confirmations"] > 0: + #create the new confirmed entry in address_history + self.address_history[scrhash]["history"].append({"height": + block["height"], "tx_hash": uc_txid}) updated_scrhashes = set() - for tx, scrhashes in confirmed_txes_scrhashes: + for tx, scrhashes in tx_scrhashes_removed_from_mempool: del self.unconfirmed_txes[tx] updated_scrhashes.update(set(scrhashes)) return updated_scrhashes @@ -440,8 +444,11 @@ def assert_address_history_tx(address_history, spk, height, txid, subscribed): assert history_element["subscribed"] == subscribed def test(): + #debugf = lambda x: print("[DEBUG] " + x) + #logf = lambda x: print("[ LOG] " + x) debugf = lambda x: x - logf = lambda x: x + logf = debugf + #empty deterministic wallets deterministic_wallets = [TestDeterministicWallet()] test_spk1 = "deadbeefdeadbeefdeadbeefdeadbeef" @@ -716,28 +723,71 @@ def test(): assert rpc.get_imported_addresses()[0] == test_spk_to_address( test_spk9_imported) - ###conflicted transaction - test_spk10 = "deadbeefdeadbeefcccc" - test_paying_in_tx10 = { - "txid": "placeholder-test-txid10", + ###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_spk10}}], - "address": test_spk_to_address(test_spk10), + "vout": [{"value": 1, "scriptPubKey": {"hex": test_spkA}}], + "address": test_spk_to_address(test_spkA), "category": "receive", "confirmations": -1, - "hex": "placeholder-test-txhex10" + "hex": "placeholder-test-txhexA" } - rpc = TestJsonRpc([test_paying_in_tx10], [], {}) - txmonitor10 = TransactionMonitor(rpc, deterministic_wallets, debugf, logf) - assert txmonitor10.build_address_history([test_spk10]) - assert len(txmonitor10.address_history) == 1 - assert len(txmonitor10.get_electrum_history(hashes.script_to_scripthash( - test_spk10))) == 0 #shouldnt show up after build history - rpc.add_transaction(test_paying_in_tx10) - assert len(list(txmonitor10.check_for_updated_txes())) == 0 - assert len(txmonitor10.get_electrum_history(hashes.script_to_scripthash( - test_spk10))) == 0 #shouldnt show up after tx is added - + 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