tnetwork.get_transaction: move some response validation logic from Synchronizer - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 0b0139c676d158039e7ff2b24d674eb422b06a27 DIR parent 94888739d346f970b735daa69f4bb0cec2f73a3b HTML Author: SomberNight <somber.night@protonmail.com> Date: Thu, 9 Jan 2020 19:22:58 +0100 network.get_transaction: move some response validation logic from Synchronizer Diffstat: M electrum/interface.py | 5 +++++ M electrum/network.py | 21 ++++++++++++++++----- M electrum/synchronizer.py | 9 --------- 3 files changed, 21 insertions(+), 14 deletions(-) --- DIR diff --git a/electrum/interface.py b/electrum/interface.py t@@ -181,6 +181,8 @@ class RequestTimedOut(GracefulDisconnect): return _("Network request timed out.") +class RequestCorrupted(GracefulDisconnect): pass + class ErrorParsingSSLCert(Exception): pass class ErrorGettingSSLCertFromServer(Exception): pass class ConnectError(NetworkException): pass t@@ -258,6 +260,9 @@ class Interface(Logger): def diagnostic_name(self): return str(NetAddress(self.host, self.port)) + def __str__(self): + return f"<Interface {self.diagnostic_name()}>" + def _set_proxy(self, proxy: dict): if proxy: username, pw = proxy.get('user'), proxy.get('password') DIR diff --git a/electrum/network.py b/electrum/network.py t@@ -53,10 +53,11 @@ from .bitcoin import COIN from . import constants from . import blockchain from . import bitcoin +from .transaction import Transaction from .blockchain import Blockchain, HEADER_SIZE from .interface import (Interface, serialize_server, deserialize_server, RequestTimedOut, NetworkTimeout, BUCKET_NAME_OF_ONION_SERVERS, - NetworkException) + NetworkException, RequestCorrupted) from .version import PROTOCOL_VERSION from .simple_config import SimpleConfig from .i18n import _ t@@ -66,7 +67,6 @@ if TYPE_CHECKING: from .channel_db import ChannelDB from .lnworker import LNGossip from .lnwatcher import WatchTower - from .transaction import Transaction from .daemon import Daemon t@@ -871,7 +871,7 @@ class Network(Logger): if success_fut.exception(): try: raise success_fut.exception() - except RequestTimedOut: + except (RequestTimedOut, RequestCorrupted): await iface.close() await iface.got_disconnected continue # try again t@@ -1068,8 +1068,19 @@ class Network(Logger): async def get_transaction(self, tx_hash: str, *, timeout=None) -> str: if not is_hash256_str(tx_hash): raise Exception(f"{repr(tx_hash)} is not a txid") - return await self.interface.session.send_request('blockchain.transaction.get', [tx_hash], - timeout=timeout) + iface = self.interface + raw = await iface.session.send_request('blockchain.transaction.get', [tx_hash], timeout=timeout) + # validate response + tx = Transaction(raw) + try: + tx.deserialize() # see if raises + except Exception as e: + self.logger.warning(f"cannot deserialize received transaction (txid {tx_hash}). from {str(iface)}") + raise RequestCorrupted() from e # TODO ban server? + if tx.txid() != tx_hash: + self.logger.warning(f"received tx does not match expected txid {tx_hash} (got {tx.txid()}). from {str(iface)}") + raise RequestCorrupted() # TODO ban server? + return raw @best_effort_reliable @catch_server_exceptions DIR diff --git a/electrum/synchronizer.py b/electrum/synchronizer.py t@@ -221,15 +221,6 @@ class Synchronizer(SynchronizerBase): finally: self._requests_answered += 1 tx = Transaction(raw_tx) - try: - tx.deserialize() # see if raises - except Exception as e: - # possible scenarios: - # 1: server is sending garbage - # 2: there is a bug in the deserialization code - # 3: there was a segwit-like upgrade that changed the tx structure - # that we don't know about - raise SynchronizerFailure(f"cannot deserialize transaction {tx_hash}") from e if tx_hash != tx.txid(): raise SynchronizerFailure(f"received tx does not match expected txid ({tx_hash} != {tx.txid()})") tx_height = self.requested_tx.pop(tx_hash)