electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 6b8ad2d1263124fcc157536bd41c219736d552ae
parent 3b9a55fab4165f935b60525e45b5f4138930b78f
Author: SomberNight <somber.night@protonmail.com>
Date:   Thu, 27 Sep 2018 18:01:25 +0200

fix some CLI/RPC commands

Diffstat:
Melectrum/commands.py | 11++++++-----
Melectrum/interface.py | 4+++-
Melectrum/network.py | 46++++++++++++++++++++++++++++++++++++++++++----
Melectrum/synchronizer.py | 5+++--
4 files changed, 54 insertions(+), 12 deletions(-)

diff --git a/electrum/commands.py b/electrum/commands.py @@ -181,7 +181,7 @@ class Commands: walletless server query, results are not checked by SPV. """ sh = bitcoin.address_to_scripthash(address) - return self.network.get_history_for_scripthash(sh) + return self.network.run_from_another_thread(self.network.get_history_for_scripthash(sh)) @command('w') def listunspent(self): @@ -199,7 +199,7 @@ class Commands: is a walletless server query, results are not checked by SPV. """ sh = bitcoin.address_to_scripthash(address) - return self.network.listunspent_for_scripthash(sh) + return self.network.run_from_another_thread(self.network.listunspent_for_scripthash(sh)) @command('') def serialize(self, jsontx): @@ -322,7 +322,7 @@ class Commands: server query, results are not checked by SPV. """ sh = bitcoin.address_to_scripthash(address) - out = self.network.get_balance_for_scripthash(sh) + out = self.network.run_from_another_thread(self.network.get_balance_for_scripthash(sh)) out["confirmed"] = str(Decimal(out["confirmed"])/COIN) out["unconfirmed"] = str(Decimal(out["unconfirmed"])/COIN) return out @@ -331,7 +331,7 @@ class Commands: def getmerkle(self, txid, height): """Get Merkle branch of a transaction included in a block. Electrum uses this to verify transactions (Simple Payment Verification).""" - return self.network.get_merkle_for_transaction(txid, int(height)) + return self.network.run_from_another_thread(self.network.get_merkle_for_transaction(txid, int(height))) @command('n') def getservers(self): @@ -517,7 +517,7 @@ class Commands: if self.wallet and txid in self.wallet.transactions: tx = self.wallet.transactions[txid] else: - raw = self.network.get_transaction(txid) + raw = self.network.run_from_another_thread(self.network.get_transaction(txid)) if raw: tx = Transaction(raw) else: @@ -637,6 +637,7 @@ class Commands: @command('n') def notify(self, address, URL): """Watch an address. Every time the address changes, a http POST is sent to the URL.""" + raise NotImplementedError() # TODO this method is currently broken def callback(x): import urllib.request headers = {'content-type':'application/json'} diff --git a/electrum/interface.py b/electrum/interface.py @@ -76,7 +76,7 @@ class NotificationSession(ClientSession): super().send_request(*args, **kwargs), timeout) except asyncio.TimeoutError as e: - raise GracefulDisconnect('request timed out: {}'.format(args)) from e + raise RequestTimedOut('request timed out: {}'.format(args)) from e async def subscribe(self, method, params, queue): # note: until the cache is written for the first time, @@ -105,6 +105,7 @@ class NotificationSession(ClientSession): class GracefulDisconnect(Exception): pass +class RequestTimedOut(GracefulDisconnect): pass class ErrorParsingSSLCert(Exception): pass class ErrorGettingSSLCertFromServer(Exception): pass @@ -140,6 +141,7 @@ class Interface(PrintError): self._requested_chunks = set() self.network = network self._set_proxy(proxy) + self.session = None self.tip_header = None self.tip = 0 diff --git a/electrum/network.py b/electrum/network.py @@ -45,7 +45,7 @@ from .bitcoin import COIN from . import constants from . import blockchain from .blockchain import Blockchain, HEADER_SIZE -from .interface import Interface, serialize_server, deserialize_server +from .interface import Interface, serialize_server, deserialize_server, RequestTimedOut from .version import PROTOCOL_VERSION from .simple_config import SimpleConfig @@ -638,13 +638,34 @@ class Network(PrintError): with b.lock: b.update_size() - async def get_merkle_for_transaction(self, tx_hash, tx_height): + def best_effort_reliable(func): + async def make_reliable_wrapper(self, *args, **kwargs): + for i in range(10): + iface = self.interface + session = iface.session if iface else None + if not session: + # no main interface; try again + await asyncio.sleep(0.1) + continue + try: + return await func(self, *args, **kwargs) + except RequestTimedOut: + if self.interface != iface: + # main interface changed; try again + continue + raise + raise Exception('no interface to do request on... gave up.') + return make_reliable_wrapper + + @best_effort_reliable + async def get_merkle_for_transaction(self, tx_hash: str, tx_height: int) -> dict: return await self.interface.session.send_request('blockchain.transaction.get_merkle', [tx_hash, tx_height]) + @best_effort_reliable async def broadcast_transaction(self, tx, timeout=10): try: out = await self.interface.session.send_request('blockchain.transaction.broadcast', [str(tx)], timeout=timeout) - except asyncio.TimeoutError as e: + except RequestTimedOut as e: return False, "error: operation timed out" except Exception as e: return False, "error: " + str(e) @@ -653,10 +674,27 @@ class Network(PrintError): return False, "error: " + out return True, out + @best_effort_reliable async def request_chunk(self, height, tip=None, *, can_return_early=False): return await self.interface.request_chunk(height, tip=tip, can_return_early=can_return_early) - def blockchain(self): + @best_effort_reliable + async def get_transaction(self, tx_hash: str) -> str: + return await self.interface.session.send_request('blockchain.transaction.get', [tx_hash]) + + @best_effort_reliable + async def get_history_for_scripthash(self, sh: str) -> List[dict]: + return await self.interface.session.send_request('blockchain.scripthash.get_history', [sh]) + + @best_effort_reliable + async def listunspent_for_scripthash(self, sh: str) -> List[dict]: + return await self.interface.session.send_request('blockchain.scripthash.listunspent', [sh]) + + @best_effort_reliable + async def get_balance_for_scripthash(self, sh: str) -> dict: + return await self.interface.session.send_request('blockchain.scripthash.get_balance', [sh]) + + def blockchain(self) -> Blockchain: interface = self.interface if interface and interface.blockchain is not None: self.blockchain_index = interface.blockchain.forkpoint diff --git a/electrum/synchronizer.py b/electrum/synchronizer.py @@ -51,6 +51,7 @@ class Synchronizer(PrintError): ''' def __init__(self, wallet): self.wallet = wallet + self.network = wallet.network self.asyncio_loop = wallet.network.asyncio_loop self.requested_tx = {} self.requested_histories = {} @@ -86,7 +87,7 @@ class Synchronizer(PrintError): # request address history self.requested_histories[addr] = status h = address_to_scripthash(addr) - result = await self.session.send_request("blockchain.scripthash.get_history", [h]) + result = await self.network.get_history_for_scripthash(h) self.print_error("receiving history", addr, len(result)) hashes = set(map(lambda item: item['tx_hash'], result)) hist = list(map(lambda item: (item['tx_hash'], item['height']), result)) @@ -125,7 +126,7 @@ class Synchronizer(PrintError): await group.spawn(self._get_transaction, tx_hash) async def _get_transaction(self, tx_hash): - result = await self.session.send_request('blockchain.transaction.get', [tx_hash]) + result = await self.network.get_transaction(tx_hash) tx = Transaction(result) try: tx.deserialize()