itCopy interface.py to old_interface.py. - electrum - Electrum Bitcoin wallet Err parazyd.org 70 hgit clone https://git.parazyd.org/electrum URL:https://git.parazyd.org/electrum parazyd.org 70 1Log /git/electrum/log.gph parazyd.org 70 1Files /git/electrum/files.gph parazyd.org 70 1Refs /git/electrum/refs.gph parazyd.org 70 1Submodules /git/electrum/file/.gitmodules.gph parazyd.org 70 i--- Err parazyd.org 70 1commit 139fc78e196ec76ebc22ec7b50a8e6b84522062b /git/electrum/commit/139fc78e196ec76ebc22ec7b50a8e6b84522062b.gph parazyd.org 70 1parent 24e4aa3ab91cd1d7ace8c1996ce86249ed4c7492 /git/electrum/commit/24e4aa3ab91cd1d7ace8c1996ce86249ed4c7492.gph parazyd.org 70 hAuthor: parazyd URL:mailto:parazyd@dyne.org parazyd.org 70 iDate: Fri, 12 Mar 2021 18:02:43 +0100 Err parazyd.org 70 i Err parazyd.org 70 iCopy interface.py to old_interface.py. Err parazyd.org 70 i Err parazyd.org 70 iDiffstat: Err parazyd.org 70 i A electrum/old_interface.py | 1132 +++++++++++++++++++++++++++++++ Err parazyd.org 70 i Err parazyd.org 70 i1 file changed, 1132 insertions(+), 0 deletions(-) Err parazyd.org 70 i--- Err parazyd.org 70 1diff --git a/electrum/old_interface.py b/electrum/old_interface.py /git/electrum/file/electrum/old_interface.py.gph parazyd.org 70 it@@ -0,0 +1,1132 @@ Err parazyd.org 70 i+#!/usr/bin/env python Err parazyd.org 70 i+# Err parazyd.org 70 i+# Electrum - lightweight Bitcoin client Err parazyd.org 70 i+# Copyright (C) 2011 thomasv@gitorious Err parazyd.org 70 i+# Err parazyd.org 70 i+# Permission is hereby granted, free of charge, to any person Err parazyd.org 70 i+# obtaining a copy of this software and associated documentation files Err parazyd.org 70 i+# (the "Software"), to deal in the Software without restriction, Err parazyd.org 70 i+# including without limitation the rights to use, copy, modify, merge, Err parazyd.org 70 i+# publish, distribute, sublicense, and/or sell copies of the Software, Err parazyd.org 70 i+# and to permit persons to whom the Software is furnished to do so, Err parazyd.org 70 i+# subject to the following conditions: Err parazyd.org 70 i+# Err parazyd.org 70 i+# The above copyright notice and this permission notice shall be Err parazyd.org 70 i+# included in all copies or substantial portions of the Software. Err parazyd.org 70 i+# Err parazyd.org 70 i+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Err parazyd.org 70 i+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF Err parazyd.org 70 i+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND Err parazyd.org 70 i+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS Err parazyd.org 70 i+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN Err parazyd.org 70 i+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN Err parazyd.org 70 i+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE Err parazyd.org 70 i+# SOFTWARE. Err parazyd.org 70 i+import os Err parazyd.org 70 i+import re Err parazyd.org 70 i+import ssl Err parazyd.org 70 i+import sys Err parazyd.org 70 i+import traceback Err parazyd.org 70 i+import asyncio Err parazyd.org 70 i+import socket Err parazyd.org 70 i+from typing import Tuple, Union, List, TYPE_CHECKING, Optional, Set, NamedTuple, Any, Sequence Err parazyd.org 70 i+from collections import defaultdict Err parazyd.org 70 i+from ipaddress import IPv4Network, IPv6Network, ip_address, IPv6Address, IPv4Address Err parazyd.org 70 i+import itertools Err parazyd.org 70 i+import logging Err parazyd.org 70 i+import hashlib Err parazyd.org 70 i+import functools Err parazyd.org 70 i+ Err parazyd.org 70 i+import aiorpcx Err parazyd.org 70 i+from aiorpcx import TaskGroup Err parazyd.org 70 i+from aiorpcx import RPCSession, Notification, NetAddress, NewlineFramer Err parazyd.org 70 i+from aiorpcx.curio import timeout_after, TaskTimeout Err parazyd.org 70 i+from aiorpcx.jsonrpc import JSONRPC, CodeMessageError Err parazyd.org 70 i+from aiorpcx.rawsocket import RSClient Err parazyd.org 70 i+import certifi Err parazyd.org 70 i+ Err parazyd.org 70 i+from .util import (ignore_exceptions, log_exceptions, bfh, SilentTaskGroup, MySocksProxy, Err parazyd.org 70 i+ is_integer, is_non_negative_integer, is_hash256_str, is_hex_str, Err parazyd.org 70 i+ is_int_or_float, is_non_negative_int_or_float) Err parazyd.org 70 i+from . import util Err parazyd.org 70 i+from . import x509 Err parazyd.org 70 i+from . import pem Err parazyd.org 70 i+from . import version Err parazyd.org 70 i+from . import blockchain Err parazyd.org 70 i+from .blockchain import Blockchain, HEADER_SIZE Err parazyd.org 70 i+from . import bitcoin Err parazyd.org 70 i+from . import constants Err parazyd.org 70 i+from .i18n import _ Err parazyd.org 70 i+from .logging import Logger Err parazyd.org 70 i+from .transaction import Transaction Err parazyd.org 70 i+ Err parazyd.org 70 i+if TYPE_CHECKING: Err parazyd.org 70 i+ from .network import Network Err parazyd.org 70 i+ from .simple_config import SimpleConfig Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+ca_path = certifi.where() Err parazyd.org 70 i+ Err parazyd.org 70 i+BUCKET_NAME_OF_ONION_SERVERS = 'onion' Err parazyd.org 70 i+ Err parazyd.org 70 i+MAX_INCOMING_MSG_SIZE = 1_000_000 # in bytes Err parazyd.org 70 i+ Err parazyd.org 70 i+_KNOWN_NETWORK_PROTOCOLS = {'t', 's'} Err parazyd.org 70 i+PREFERRED_NETWORK_PROTOCOL = 's' Err parazyd.org 70 i+assert PREFERRED_NETWORK_PROTOCOL in _KNOWN_NETWORK_PROTOCOLS Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+class NetworkTimeout: Err parazyd.org 70 i+ # seconds Err parazyd.org 70 i+ class Generic: Err parazyd.org 70 i+ NORMAL = 30 Err parazyd.org 70 i+ RELAXED = 45 Err parazyd.org 70 i+ MOST_RELAXED = 600 Err parazyd.org 70 i+ Err parazyd.org 70 i+ class Urgent(Generic): Err parazyd.org 70 i+ NORMAL = 10 Err parazyd.org 70 i+ RELAXED = 20 Err parazyd.org 70 i+ MOST_RELAXED = 60 Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def assert_non_negative_integer(val: Any) -> None: Err parazyd.org 70 i+ if not is_non_negative_integer(val): Err parazyd.org 70 i+ raise RequestCorrupted(f'{val!r} should be a non-negative integer') Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def assert_integer(val: Any) -> None: Err parazyd.org 70 i+ if not is_integer(val): Err parazyd.org 70 i+ raise RequestCorrupted(f'{val!r} should be an integer') Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def assert_int_or_float(val: Any) -> None: Err parazyd.org 70 i+ if not is_int_or_float(val): Err parazyd.org 70 i+ raise RequestCorrupted(f'{val!r} should be int or float') Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def assert_non_negative_int_or_float(val: Any) -> None: Err parazyd.org 70 i+ if not is_non_negative_int_or_float(val): Err parazyd.org 70 i+ raise RequestCorrupted(f'{val!r} should be a non-negative int or float') Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def assert_hash256_str(val: Any) -> None: Err parazyd.org 70 i+ if not is_hash256_str(val): Err parazyd.org 70 i+ raise RequestCorrupted(f'{val!r} should be a hash256 str') Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def assert_hex_str(val: Any) -> None: Err parazyd.org 70 i+ if not is_hex_str(val): Err parazyd.org 70 i+ raise RequestCorrupted(f'{val!r} should be a hex str') Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def assert_dict_contains_field(d: Any, *, field_name: str) -> Any: Err parazyd.org 70 i+ if not isinstance(d, dict): Err parazyd.org 70 i+ raise RequestCorrupted(f'{d!r} should be a dict') Err parazyd.org 70 i+ if field_name not in d: Err parazyd.org 70 i+ raise RequestCorrupted(f'required field {field_name!r} missing from dict') Err parazyd.org 70 i+ return d[field_name] Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def assert_list_or_tuple(val: Any) -> None: Err parazyd.org 70 i+ if not isinstance(val, (list, tuple)): Err parazyd.org 70 i+ raise RequestCorrupted(f'{val!r} should be a list or tuple') Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+class NotificationSession(RPCSession): Err parazyd.org 70 i+ Err parazyd.org 70 i+ def __init__(self, *args, interface: 'Interface', **kwargs): Err parazyd.org 70 i+ super(NotificationSession, self).__init__(*args, **kwargs) Err parazyd.org 70 i+ self.subscriptions = defaultdict(list) Err parazyd.org 70 i+ self.cache = {} Err parazyd.org 70 i+ self.default_timeout = NetworkTimeout.Generic.NORMAL Err parazyd.org 70 i+ self._msg_counter = itertools.count(start=1) Err parazyd.org 70 i+ self.interface = interface Err parazyd.org 70 i+ self.cost_hard_limit = 0 # disable aiorpcx resource limits Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def handle_request(self, request): Err parazyd.org 70 i+ self.maybe_log(f"--> {request}") Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ if isinstance(request, Notification): Err parazyd.org 70 i+ params, result = request.args[:-1], request.args[-1] Err parazyd.org 70 i+ key = self.get_hashable_key_for_rpc_call(request.method, params) Err parazyd.org 70 i+ if key in self.subscriptions: Err parazyd.org 70 i+ self.cache[key] = result Err parazyd.org 70 i+ for queue in self.subscriptions[key]: Err parazyd.org 70 i+ await queue.put(request.args) Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ raise Exception(f'unexpected notification') Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ raise Exception(f'unexpected request. not a notification') Err parazyd.org 70 i+ except Exception as e: Err parazyd.org 70 i+ self.interface.logger.info(f"error handling request {request}. exc: {repr(e)}") Err parazyd.org 70 i+ await self.close() Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def send_request(self, *args, timeout=None, **kwargs): Err parazyd.org 70 i+ # note: semaphores/timeouts/backpressure etc are handled by Err parazyd.org 70 i+ # aiorpcx. the timeout arg here in most cases should not be set Err parazyd.org 70 i+ msg_id = next(self._msg_counter) Err parazyd.org 70 i+ self.maybe_log(f"<-- {args} {kwargs} (id: {msg_id})") Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ # note: RPCSession.send_request raises TaskTimeout in case of a timeout. Err parazyd.org 70 i+ # TaskTimeout is a subclass of CancelledError, which is *suppressed* in TaskGroups Err parazyd.org 70 i+ response = await asyncio.wait_for( Err parazyd.org 70 i+ super().send_request(*args, **kwargs), Err parazyd.org 70 i+ timeout) Err parazyd.org 70 i+ except (TaskTimeout, asyncio.TimeoutError) as e: Err parazyd.org 70 i+ raise RequestTimedOut(f'request timed out: {args} (id: {msg_id})') from e Err parazyd.org 70 i+ except CodeMessageError as e: Err parazyd.org 70 i+ self.maybe_log(f"--> {repr(e)} (id: {msg_id})") Err parazyd.org 70 i+ raise Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ self.maybe_log(f"--> {response} (id: {msg_id})") Err parazyd.org 70 i+ return response Err parazyd.org 70 i+ Err parazyd.org 70 i+ def set_default_timeout(self, timeout): Err parazyd.org 70 i+ self.sent_request_timeout = timeout Err parazyd.org 70 i+ self.max_send_delay = timeout Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def subscribe(self, method: str, params: List, queue: asyncio.Queue): Err parazyd.org 70 i+ # note: until the cache is written for the first time, Err parazyd.org 70 i+ # each 'subscribe' call might make a request on the network. Err parazyd.org 70 i+ key = self.get_hashable_key_for_rpc_call(method, params) Err parazyd.org 70 i+ self.subscriptions[key].append(queue) Err parazyd.org 70 i+ if key in self.cache: Err parazyd.org 70 i+ result = self.cache[key] Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ result = await self.send_request(method, params) Err parazyd.org 70 i+ self.cache[key] = result Err parazyd.org 70 i+ await queue.put(params + [result]) Err parazyd.org 70 i+ Err parazyd.org 70 i+ def unsubscribe(self, queue): Err parazyd.org 70 i+ """Unsubscribe a callback to free object references to enable GC.""" Err parazyd.org 70 i+ # note: we can't unsubscribe from the server, so we keep receiving Err parazyd.org 70 i+ # subsequent notifications Err parazyd.org 70 i+ for v in self.subscriptions.values(): Err parazyd.org 70 i+ if queue in v: Err parazyd.org 70 i+ v.remove(queue) Err parazyd.org 70 i+ Err parazyd.org 70 i+ @classmethod Err parazyd.org 70 i+ def get_hashable_key_for_rpc_call(cls, method, params): Err parazyd.org 70 i+ """Hashable index for subscriptions and cache""" Err parazyd.org 70 i+ return str(method) + repr(params) Err parazyd.org 70 i+ Err parazyd.org 70 i+ def maybe_log(self, msg: str) -> None: Err parazyd.org 70 i+ if not self.interface: return Err parazyd.org 70 i+ if self.interface.debug or self.interface.network.debug: Err parazyd.org 70 i+ self.interface.logger.debug(msg) Err parazyd.org 70 i+ Err parazyd.org 70 i+ def default_framer(self): Err parazyd.org 70 i+ # overridden so that max_size can be customized Err parazyd.org 70 i+ max_size = int(self.interface.network.config.get('network_max_incoming_msg_size', Err parazyd.org 70 i+ MAX_INCOMING_MSG_SIZE)) Err parazyd.org 70 i+ return NewlineFramer(max_size=max_size) Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+class NetworkException(Exception): pass Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+class GracefulDisconnect(NetworkException): Err parazyd.org 70 i+ log_level = logging.INFO Err parazyd.org 70 i+ Err parazyd.org 70 i+ def __init__(self, *args, log_level=None, **kwargs): Err parazyd.org 70 i+ Exception.__init__(self, *args, **kwargs) Err parazyd.org 70 i+ if log_level is not None: Err parazyd.org 70 i+ self.log_level = log_level Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+class RequestTimedOut(GracefulDisconnect): Err parazyd.org 70 i+ def __str__(self): Err parazyd.org 70 i+ return _("Network request timed out.") Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+class RequestCorrupted(Exception): pass Err parazyd.org 70 i+ Err parazyd.org 70 i+class ErrorParsingSSLCert(Exception): pass Err parazyd.org 70 i+class ErrorGettingSSLCertFromServer(Exception): pass Err parazyd.org 70 i+class ErrorSSLCertFingerprintMismatch(Exception): pass Err parazyd.org 70 i+class InvalidOptionCombination(Exception): pass Err parazyd.org 70 i+class ConnectError(NetworkException): pass Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+class _RSClient(RSClient): Err parazyd.org 70 i+ async def create_connection(self): Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ return await super().create_connection() Err parazyd.org 70 i+ except OSError as e: Err parazyd.org 70 i+ # note: using "from e" here will set __cause__ of ConnectError Err parazyd.org 70 i+ raise ConnectError(e) from e Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+class ServerAddr: Err parazyd.org 70 i+ Err parazyd.org 70 i+ def __init__(self, host: str, port: Union[int, str], *, protocol: str = None): Err parazyd.org 70 i+ assert isinstance(host, str), repr(host) Err parazyd.org 70 i+ if protocol is None: Err parazyd.org 70 i+ protocol = 's' Err parazyd.org 70 i+ if not host: Err parazyd.org 70 i+ raise ValueError('host must not be empty') Err parazyd.org 70 i+ if host[0] == '[' and host[-1] == ']': # IPv6 Err parazyd.org 70 i+ host = host[1:-1] Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ net_addr = NetAddress(host, port) # this validates host and port Err parazyd.org 70 i+ except Exception as e: Err parazyd.org 70 i+ raise ValueError(f"cannot construct ServerAddr: invalid host or port (host={host}, port={port})") from e Err parazyd.org 70 i+ if protocol not in _KNOWN_NETWORK_PROTOCOLS: Err parazyd.org 70 i+ raise ValueError(f"invalid network protocol: {protocol}") Err parazyd.org 70 i+ self.host = str(net_addr.host) # canonical form (if e.g. IPv6 address) Err parazyd.org 70 i+ self.port = int(net_addr.port) Err parazyd.org 70 i+ self.protocol = protocol Err parazyd.org 70 i+ self._net_addr_str = str(net_addr) Err parazyd.org 70 i+ Err parazyd.org 70 i+ @classmethod Err parazyd.org 70 i+ def from_str(cls, s: str) -> 'ServerAddr': Err parazyd.org 70 i+ # host might be IPv6 address, hence do rsplit: Err parazyd.org 70 i+ host, port, protocol = str(s).rsplit(':', 2) Err parazyd.org 70 i+ return ServerAddr(host=host, port=port, protocol=protocol) Err parazyd.org 70 i+ Err parazyd.org 70 i+ @classmethod Err parazyd.org 70 i+ def from_str_with_inference(cls, s: str) -> Optional['ServerAddr']: Err parazyd.org 70 i+ """Construct ServerAddr from str, guessing missing details. Err parazyd.org 70 i+ Ongoing compatibility not guaranteed. Err parazyd.org 70 i+ """ Err parazyd.org 70 i+ if not s: Err parazyd.org 70 i+ return None Err parazyd.org 70 i+ items = str(s).rsplit(':', 2) Err parazyd.org 70 i+ if len(items) < 2: Err parazyd.org 70 i+ return None # although maybe we could guess the port too? Err parazyd.org 70 i+ host = items[0] Err parazyd.org 70 i+ port = items[1] Err parazyd.org 70 i+ if len(items) >= 3: Err parazyd.org 70 i+ protocol = items[2] Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ protocol = PREFERRED_NETWORK_PROTOCOL Err parazyd.org 70 i+ return ServerAddr(host=host, port=port, protocol=protocol) Err parazyd.org 70 i+ Err parazyd.org 70 i+ def to_friendly_name(self) -> str: Err parazyd.org 70 i+ # note: this method is closely linked to from_str_with_inference Err parazyd.org 70 i+ if self.protocol == 's': # hide trailing ":s" Err parazyd.org 70 i+ return self.net_addr_str() Err parazyd.org 70 i+ return str(self) Err parazyd.org 70 i+ Err parazyd.org 70 i+ def __str__(self): Err parazyd.org 70 i+ return '{}:{}'.format(self.net_addr_str(), self.protocol) Err parazyd.org 70 i+ Err parazyd.org 70 i+ def to_json(self) -> str: Err parazyd.org 70 i+ return str(self) Err parazyd.org 70 i+ Err parazyd.org 70 i+ def __repr__(self): Err parazyd.org 70 i+ return f'' Err parazyd.org 70 i+ Err parazyd.org 70 i+ def net_addr_str(self) -> str: Err parazyd.org 70 i+ return self._net_addr_str Err parazyd.org 70 i+ Err parazyd.org 70 i+ def __eq__(self, other): Err parazyd.org 70 i+ if not isinstance(other, ServerAddr): Err parazyd.org 70 i+ return False Err parazyd.org 70 i+ return (self.host == other.host Err parazyd.org 70 i+ and self.port == other.port Err parazyd.org 70 i+ and self.protocol == other.protocol) Err parazyd.org 70 i+ Err parazyd.org 70 i+ def __ne__(self, other): Err parazyd.org 70 i+ return not (self == other) Err parazyd.org 70 i+ Err parazyd.org 70 i+ def __hash__(self): Err parazyd.org 70 i+ return hash((self.host, self.port, self.protocol)) Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def _get_cert_path_for_host(*, config: 'SimpleConfig', host: str) -> str: Err parazyd.org 70 i+ filename = host Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ ip = ip_address(host) Err parazyd.org 70 i+ except ValueError: Err parazyd.org 70 i+ pass Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ if isinstance(ip, IPv6Address): Err parazyd.org 70 i+ filename = f"ipv6_{ip.packed.hex()}" Err parazyd.org 70 i+ return os.path.join(config.path, 'certs', filename) Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+class Interface(Logger): Err parazyd.org 70 i+ Err parazyd.org 70 i+ LOGGING_SHORTCUT = 'i' Err parazyd.org 70 i+ Err parazyd.org 70 i+ def __init__(self, *, network: 'Network', server: ServerAddr, proxy: Optional[dict]): Err parazyd.org 70 i+ self.ready = asyncio.Future() Err parazyd.org 70 i+ self.got_disconnected = asyncio.Event() Err parazyd.org 70 i+ self.server = server Err parazyd.org 70 i+ Logger.__init__(self) Err parazyd.org 70 i+ assert network.config.path Err parazyd.org 70 i+ self.cert_path = _get_cert_path_for_host(config=network.config, host=self.host) Err parazyd.org 70 i+ self.blockchain = None # type: Optional[Blockchain] Err parazyd.org 70 i+ self._requested_chunks = set() # type: Set[int] Err parazyd.org 70 i+ self.network = network Err parazyd.org 70 i+ self.proxy = MySocksProxy.from_proxy_dict(proxy) Err parazyd.org 70 i+ self.session = None # type: Optional[NotificationSession] Err parazyd.org 70 i+ self._ipaddr_bucket = None Err parazyd.org 70 i+ Err parazyd.org 70 i+ # Latest block header and corresponding height, as claimed by the server. Err parazyd.org 70 i+ # Note that these values are updated before they are verified. Err parazyd.org 70 i+ # Especially during initial header sync, verification can take a long time. Err parazyd.org 70 i+ # Failing verification will get the interface closed. Err parazyd.org 70 i+ self.tip_header = None Err parazyd.org 70 i+ self.tip = 0 Err parazyd.org 70 i+ Err parazyd.org 70 i+ self.fee_estimates_eta = {} Err parazyd.org 70 i+ Err parazyd.org 70 i+ # Dump network messages (only for this interface). Set at runtime from the console. Err parazyd.org 70 i+ self.debug = False Err parazyd.org 70 i+ Err parazyd.org 70 i+ self.taskgroup = SilentTaskGroup() Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def spawn_task(): Err parazyd.org 70 i+ task = await self.network.taskgroup.spawn(self.run()) Err parazyd.org 70 i+ if sys.version_info >= (3, 8): Err parazyd.org 70 i+ task.set_name(f"interface::{str(server)}") Err parazyd.org 70 i+ asyncio.run_coroutine_threadsafe(spawn_task(), self.network.asyncio_loop) Err parazyd.org 70 i+ Err parazyd.org 70 i+ @property Err parazyd.org 70 i+ def host(self): Err parazyd.org 70 i+ return self.server.host Err parazyd.org 70 i+ Err parazyd.org 70 i+ @property Err parazyd.org 70 i+ def port(self): Err parazyd.org 70 i+ return self.server.port Err parazyd.org 70 i+ Err parazyd.org 70 i+ @property Err parazyd.org 70 i+ def protocol(self): Err parazyd.org 70 i+ return self.server.protocol Err parazyd.org 70 i+ Err parazyd.org 70 i+ def diagnostic_name(self): Err parazyd.org 70 i+ return self.server.net_addr_str() Err parazyd.org 70 i+ Err parazyd.org 70 i+ def __str__(self): Err parazyd.org 70 i+ return f"" Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def is_server_ca_signed(self, ca_ssl_context): Err parazyd.org 70 i+ """Given a CA enforcing SSL context, returns True if the connection Err parazyd.org 70 i+ can be established. Returns False if the server has a self-signed Err parazyd.org 70 i+ certificate but otherwise is okay. Any other failures raise. Err parazyd.org 70 i+ """ Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ await self.open_session(ca_ssl_context, exit_early=True) Err parazyd.org 70 i+ except ConnectError as e: Err parazyd.org 70 i+ cause = e.__cause__ Err parazyd.org 70 i+ if isinstance(cause, ssl.SSLError) and cause.reason == 'CERTIFICATE_VERIFY_FAILED': Err parazyd.org 70 i+ # failures due to self-signed certs are normal Err parazyd.org 70 i+ return False Err parazyd.org 70 i+ raise Err parazyd.org 70 i+ return True Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def _try_saving_ssl_cert_for_first_time(self, ca_ssl_context): Err parazyd.org 70 i+ ca_signed = await self.is_server_ca_signed(ca_ssl_context) Err parazyd.org 70 i+ if ca_signed: Err parazyd.org 70 i+ if self._get_expected_fingerprint(): Err parazyd.org 70 i+ raise InvalidOptionCombination("cannot use --serverfingerprint with CA signed servers") Err parazyd.org 70 i+ with open(self.cert_path, 'w') as f: Err parazyd.org 70 i+ # empty file means this is CA signed, not self-signed Err parazyd.org 70 i+ f.write('') Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ await self._save_certificate() Err parazyd.org 70 i+ Err parazyd.org 70 i+ def _is_saved_ssl_cert_available(self): Err parazyd.org 70 i+ if not os.path.exists(self.cert_path): Err parazyd.org 70 i+ return False Err parazyd.org 70 i+ with open(self.cert_path, 'r') as f: Err parazyd.org 70 i+ contents = f.read() Err parazyd.org 70 i+ if contents == '': # CA signed Err parazyd.org 70 i+ if self._get_expected_fingerprint(): Err parazyd.org 70 i+ raise InvalidOptionCombination("cannot use --serverfingerprint with CA signed servers") Err parazyd.org 70 i+ return True Err parazyd.org 70 i+ # pinned self-signed cert Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ b = pem.dePem(contents, 'CERTIFICATE') Err parazyd.org 70 i+ except SyntaxError as e: Err parazyd.org 70 i+ self.logger.info(f"error parsing already saved cert: {e}") Err parazyd.org 70 i+ raise ErrorParsingSSLCert(e) from e Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ x = x509.X509(b) Err parazyd.org 70 i+ except Exception as e: Err parazyd.org 70 i+ self.logger.info(f"error parsing already saved cert: {e}") Err parazyd.org 70 i+ raise ErrorParsingSSLCert(e) from e Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ x.check_date() Err parazyd.org 70 i+ except x509.CertificateError as e: Err parazyd.org 70 i+ self.logger.info(f"certificate has expired: {e}") Err parazyd.org 70 i+ os.unlink(self.cert_path) # delete pinned cert only in this case Err parazyd.org 70 i+ return False Err parazyd.org 70 i+ self._verify_certificate_fingerprint(bytearray(b)) Err parazyd.org 70 i+ return True Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def _get_ssl_context(self): Err parazyd.org 70 i+ if self.protocol != 's': Err parazyd.org 70 i+ # using plaintext TCP Err parazyd.org 70 i+ return None Err parazyd.org 70 i+ Err parazyd.org 70 i+ # see if we already have cert for this server; or get it for the first time Err parazyd.org 70 i+ ca_sslc = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=ca_path) Err parazyd.org 70 i+ if not self._is_saved_ssl_cert_available(): Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ await self._try_saving_ssl_cert_for_first_time(ca_sslc) Err parazyd.org 70 i+ except (OSError, ConnectError, aiorpcx.socks.SOCKSError) as e: Err parazyd.org 70 i+ raise ErrorGettingSSLCertFromServer(e) from e Err parazyd.org 70 i+ # now we have a file saved in our certificate store Err parazyd.org 70 i+ siz = os.stat(self.cert_path).st_size Err parazyd.org 70 i+ if siz == 0: Err parazyd.org 70 i+ # CA signed cert Err parazyd.org 70 i+ sslc = ca_sslc Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ # pinned self-signed cert Err parazyd.org 70 i+ sslc = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=self.cert_path) Err parazyd.org 70 i+ sslc.check_hostname = 0 Err parazyd.org 70 i+ return sslc Err parazyd.org 70 i+ Err parazyd.org 70 i+ def handle_disconnect(func): Err parazyd.org 70 i+ @functools.wraps(func) Err parazyd.org 70 i+ async def wrapper_func(self: 'Interface', *args, **kwargs): Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ return await func(self, *args, **kwargs) Err parazyd.org 70 i+ except GracefulDisconnect as e: Err parazyd.org 70 i+ self.logger.log(e.log_level, f"disconnecting due to {repr(e)}") Err parazyd.org 70 i+ except aiorpcx.jsonrpc.RPCError as e: Err parazyd.org 70 i+ self.logger.warning(f"disconnecting due to {repr(e)}") Err parazyd.org 70 i+ self.logger.debug(f"(disconnect) trace for {repr(e)}", exc_info=True) Err parazyd.org 70 i+ finally: Err parazyd.org 70 i+ self.got_disconnected.set() Err parazyd.org 70 i+ await self.network.connection_down(self) Err parazyd.org 70 i+ # if was not 'ready' yet, schedule waiting coroutines: Err parazyd.org 70 i+ self.ready.cancel() Err parazyd.org 70 i+ return wrapper_func Err parazyd.org 70 i+ Err parazyd.org 70 i+ @ignore_exceptions # do not kill network.taskgroup Err parazyd.org 70 i+ @log_exceptions Err parazyd.org 70 i+ @handle_disconnect Err parazyd.org 70 i+ async def run(self): Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ ssl_context = await self._get_ssl_context() Err parazyd.org 70 i+ except (ErrorParsingSSLCert, ErrorGettingSSLCertFromServer) as e: Err parazyd.org 70 i+ self.logger.info(f'disconnecting due to: {repr(e)}') Err parazyd.org 70 i+ return Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ await self.open_session(ssl_context) Err parazyd.org 70 i+ except (asyncio.CancelledError, ConnectError, aiorpcx.socks.SOCKSError) as e: Err parazyd.org 70 i+ # make SSL errors for main interface more visible (to help servers ops debug cert pinning issues) Err parazyd.org 70 i+ if (isinstance(e, ConnectError) and isinstance(e.__cause__, ssl.SSLError) Err parazyd.org 70 i+ and self.is_main_server() and not self.network.auto_connect): Err parazyd.org 70 i+ self.logger.warning(f'Cannot connect to main server due to SSL error ' Err parazyd.org 70 i+ f'(maybe cert changed compared to "{self.cert_path}"). Exc: {repr(e)}') Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ self.logger.info(f'disconnecting due to: {repr(e)}') Err parazyd.org 70 i+ return Err parazyd.org 70 i+ Err parazyd.org 70 i+ def _mark_ready(self) -> None: Err parazyd.org 70 i+ if self.ready.cancelled(): Err parazyd.org 70 i+ raise GracefulDisconnect('conn establishment was too slow; *ready* future was cancelled') Err parazyd.org 70 i+ if self.ready.done(): Err parazyd.org 70 i+ return Err parazyd.org 70 i+ Err parazyd.org 70 i+ assert self.tip_header Err parazyd.org 70 i+ chain = blockchain.check_header(self.tip_header) Err parazyd.org 70 i+ if not chain: Err parazyd.org 70 i+ self.blockchain = blockchain.get_best_chain() Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ self.blockchain = chain Err parazyd.org 70 i+ assert self.blockchain is not None Err parazyd.org 70 i+ Err parazyd.org 70 i+ self.logger.info(f"set blockchain with height {self.blockchain.height()}") Err parazyd.org 70 i+ Err parazyd.org 70 i+ self.ready.set_result(1) Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def _save_certificate(self) -> None: Err parazyd.org 70 i+ if not os.path.exists(self.cert_path): Err parazyd.org 70 i+ # we may need to retry this a few times, in case the handshake hasn't completed Err parazyd.org 70 i+ for _ in range(10): Err parazyd.org 70 i+ dercert = await self._fetch_certificate() Err parazyd.org 70 i+ if dercert: Err parazyd.org 70 i+ self.logger.info("succeeded in getting cert") Err parazyd.org 70 i+ self._verify_certificate_fingerprint(dercert) Err parazyd.org 70 i+ with open(self.cert_path, 'w') as f: Err parazyd.org 70 i+ cert = ssl.DER_cert_to_PEM_cert(dercert) Err parazyd.org 70 i+ # workaround android bug Err parazyd.org 70 i+ cert = re.sub("([^\n])-----END CERTIFICATE-----","\\1\n-----END CERTIFICATE-----",cert) Err parazyd.org 70 i+ f.write(cert) Err parazyd.org 70 i+ # even though close flushes we can't fsync when closed. Err parazyd.org 70 i+ # and we must flush before fsyncing, cause flush flushes to OS buffer Err parazyd.org 70 i+ # fsync writes to OS buffer to disk Err parazyd.org 70 i+ f.flush() Err parazyd.org 70 i+ os.fsync(f.fileno()) Err parazyd.org 70 i+ break Err parazyd.org 70 i+ await asyncio.sleep(1) Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ raise GracefulDisconnect("could not get certificate after 10 tries") Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def _fetch_certificate(self) -> bytes: Err parazyd.org 70 i+ sslc = ssl.SSLContext() Err parazyd.org 70 i+ async with _RSClient(session_factory=RPCSession, Err parazyd.org 70 i+ host=self.host, port=self.port, Err parazyd.org 70 i+ ssl=sslc, proxy=self.proxy) as session: Err parazyd.org 70 i+ asyncio_transport = session.transport._asyncio_transport # type: asyncio.BaseTransport Err parazyd.org 70 i+ ssl_object = asyncio_transport.get_extra_info("ssl_object") # type: ssl.SSLObject Err parazyd.org 70 i+ return ssl_object.getpeercert(binary_form=True) Err parazyd.org 70 i+ Err parazyd.org 70 i+ def _get_expected_fingerprint(self) -> Optional[str]: Err parazyd.org 70 i+ if self.is_main_server(): Err parazyd.org 70 i+ return self.network.config.get("serverfingerprint") Err parazyd.org 70 i+ Err parazyd.org 70 i+ def _verify_certificate_fingerprint(self, certificate): Err parazyd.org 70 i+ expected_fingerprint = self._get_expected_fingerprint() Err parazyd.org 70 i+ if not expected_fingerprint: Err parazyd.org 70 i+ return Err parazyd.org 70 i+ fingerprint = hashlib.sha256(certificate).hexdigest() Err parazyd.org 70 i+ fingerprints_match = fingerprint.lower() == expected_fingerprint.lower() Err parazyd.org 70 i+ if not fingerprints_match: Err parazyd.org 70 i+ util.trigger_callback('cert_mismatch') Err parazyd.org 70 i+ raise ErrorSSLCertFingerprintMismatch('Refusing to connect to server due to cert fingerprint mismatch') Err parazyd.org 70 i+ self.logger.info("cert fingerprint verification passed") Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def get_block_header(self, height, assert_mode): Err parazyd.org 70 i+ self.logger.info(f'requesting block header {height} in mode {assert_mode}') Err parazyd.org 70 i+ # use lower timeout as we usually have network.bhi_lock here Err parazyd.org 70 i+ timeout = self.network.get_network_timeout_seconds(NetworkTimeout.Urgent) Err parazyd.org 70 i+ res = await self.session.send_request('blockchain.block.header', [height], timeout=timeout) Err parazyd.org 70 i+ return blockchain.deserialize_header(bytes.fromhex(res), height) Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def request_chunk(self, height: int, tip=None, *, can_return_early=False): Err parazyd.org 70 i+ if not is_non_negative_integer(height): Err parazyd.org 70 i+ raise Exception(f"{repr(height)} is not a block height") Err parazyd.org 70 i+ index = height // 2016 Err parazyd.org 70 i+ if can_return_early and index in self._requested_chunks: Err parazyd.org 70 i+ return Err parazyd.org 70 i+ self.logger.info(f"requesting chunk from height {height}") Err parazyd.org 70 i+ size = 2016 Err parazyd.org 70 i+ if tip is not None: Err parazyd.org 70 i+ size = min(size, tip - index * 2016 + 1) Err parazyd.org 70 i+ size = max(size, 0) Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ self._requested_chunks.add(index) Err parazyd.org 70 i+ res = await self.session.send_request('blockchain.block.headers', [index * 2016, size]) Err parazyd.org 70 i+ finally: Err parazyd.org 70 i+ self._requested_chunks.discard(index) Err parazyd.org 70 i+ assert_dict_contains_field(res, field_name='count') Err parazyd.org 70 i+ assert_dict_contains_field(res, field_name='hex') Err parazyd.org 70 i+ assert_dict_contains_field(res, field_name='max') Err parazyd.org 70 i+ assert_non_negative_integer(res['count']) Err parazyd.org 70 i+ assert_non_negative_integer(res['max']) Err parazyd.org 70 i+ assert_hex_str(res['hex']) Err parazyd.org 70 i+ if len(res['hex']) != HEADER_SIZE * 2 * res['count']: Err parazyd.org 70 i+ raise RequestCorrupted('inconsistent chunk hex and count') Err parazyd.org 70 i+ # we never request more than 2016 headers, but we enforce those fit in a single response Err parazyd.org 70 i+ if res['max'] < 2016: Err parazyd.org 70 i+ raise RequestCorrupted(f"server uses too low 'max' count for block.headers: {res['max']} < 2016") Err parazyd.org 70 i+ if res['count'] != size: Err parazyd.org 70 i+ raise RequestCorrupted(f"expected {size} headers but only got {res['count']}") Err parazyd.org 70 i+ conn = self.blockchain.connect_chunk(index, res['hex']) Err parazyd.org 70 i+ if not conn: Err parazyd.org 70 i+ return conn, 0 Err parazyd.org 70 i+ return conn, res['count'] Err parazyd.org 70 i+ Err parazyd.org 70 i+ def is_main_server(self) -> bool: Err parazyd.org 70 i+ return (self.network.interface == self or Err parazyd.org 70 i+ self.network.interface is None and self.network.default_server == self.server) Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def open_session(self, sslc, exit_early=False): Err parazyd.org 70 i+ session_factory = lambda *args, iface=self, **kwargs: NotificationSession(*args, **kwargs, interface=iface) Err parazyd.org 70 i+ async with _RSClient(session_factory=session_factory, Err parazyd.org 70 i+ host=self.host, port=self.port, Err parazyd.org 70 i+ ssl=sslc, proxy=self.proxy) as session: Err parazyd.org 70 i+ self.session = session # type: NotificationSession Err parazyd.org 70 i+ self.session.set_default_timeout(self.network.get_network_timeout_seconds(NetworkTimeout.Generic)) Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ ver = await session.send_request('server.version', [self.client_name(), version.PROTOCOL_VERSION]) Err parazyd.org 70 i+ except aiorpcx.jsonrpc.RPCError as e: Err parazyd.org 70 i+ raise GracefulDisconnect(e) # probably 'unsupported protocol version' Err parazyd.org 70 i+ if exit_early: Err parazyd.org 70 i+ return Err parazyd.org 70 i+ if ver[1] != version.PROTOCOL_VERSION: Err parazyd.org 70 i+ raise GracefulDisconnect(f'server violated protocol-version-negotiation. ' Err parazyd.org 70 i+ f'we asked for {version.PROTOCOL_VERSION!r}, they sent {ver[1]!r}') Err parazyd.org 70 i+ if not self.network.check_interface_against_healthy_spread_of_connected_servers(self): Err parazyd.org 70 i+ raise GracefulDisconnect(f'too many connected servers already ' Err parazyd.org 70 i+ f'in bucket {self.bucket_based_on_ipaddress()}') Err parazyd.org 70 i+ self.logger.info(f"connection established. version: {ver}") Err parazyd.org 70 i+ Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ async with self.taskgroup as group: Err parazyd.org 70 i+ await group.spawn(self.ping) Err parazyd.org 70 i+ await group.spawn(self.request_fee_estimates) Err parazyd.org 70 i+ await group.spawn(self.run_fetch_blocks) Err parazyd.org 70 i+ await group.spawn(self.monitor_connection) Err parazyd.org 70 i+ except aiorpcx.jsonrpc.RPCError as e: Err parazyd.org 70 i+ if e.code in (JSONRPC.EXCESSIVE_RESOURCE_USAGE, Err parazyd.org 70 i+ JSONRPC.SERVER_BUSY, Err parazyd.org 70 i+ JSONRPC.METHOD_NOT_FOUND): Err parazyd.org 70 i+ raise GracefulDisconnect(e, log_level=logging.WARNING) from e Err parazyd.org 70 i+ raise Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def monitor_connection(self): Err parazyd.org 70 i+ while True: Err parazyd.org 70 i+ await asyncio.sleep(1) Err parazyd.org 70 i+ if not self.session or self.session.is_closing(): Err parazyd.org 70 i+ raise GracefulDisconnect('session was closed') Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def ping(self): Err parazyd.org 70 i+ while True: Err parazyd.org 70 i+ await asyncio.sleep(300) Err parazyd.org 70 i+ await self.session.send_request('server.ping') Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def request_fee_estimates(self): Err parazyd.org 70 i+ from .simple_config import FEE_ETA_TARGETS Err parazyd.org 70 i+ while True: Err parazyd.org 70 i+ async with TaskGroup() as group: Err parazyd.org 70 i+ fee_tasks = [] Err parazyd.org 70 i+ for i in FEE_ETA_TARGETS: Err parazyd.org 70 i+ fee_tasks.append((i, await group.spawn(self.get_estimatefee(i)))) Err parazyd.org 70 i+ for nblock_target, task in fee_tasks: Err parazyd.org 70 i+ fee = task.result() Err parazyd.org 70 i+ if fee < 0: continue Err parazyd.org 70 i+ self.fee_estimates_eta[nblock_target] = fee Err parazyd.org 70 i+ self.network.update_fee_estimates() Err parazyd.org 70 i+ await asyncio.sleep(60) Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def close(self, *, force_after: int = None): Err parazyd.org 70 i+ """Closes the connection and waits for it to be closed. Err parazyd.org 70 i+ We try to flush buffered data to the wire, so this can take some time. Err parazyd.org 70 i+ """ Err parazyd.org 70 i+ if force_after is None: Err parazyd.org 70 i+ # We give up after a while and just abort the connection. Err parazyd.org 70 i+ # Note: specifically if the server is running Fulcrum, waiting seems hopeless, Err parazyd.org 70 i+ # the connection must be aborted (see https://github.com/cculianu/Fulcrum/issues/76) Err parazyd.org 70 i+ force_after = 1 # seconds Err parazyd.org 70 i+ if self.session: Err parazyd.org 70 i+ await self.session.close(force_after=force_after) Err parazyd.org 70 i+ # monitor_connection will cancel tasks Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def run_fetch_blocks(self): Err parazyd.org 70 i+ header_queue = asyncio.Queue() Err parazyd.org 70 i+ await self.session.subscribe('blockchain.headers.subscribe', [], header_queue) Err parazyd.org 70 i+ while True: Err parazyd.org 70 i+ item = await header_queue.get() Err parazyd.org 70 i+ raw_header = item[0] Err parazyd.org 70 i+ height = raw_header['height'] Err parazyd.org 70 i+ header = blockchain.deserialize_header(bfh(raw_header['hex']), height) Err parazyd.org 70 i+ self.tip_header = header Err parazyd.org 70 i+ self.tip = height Err parazyd.org 70 i+ if self.tip < constants.net.max_checkpoint(): Err parazyd.org 70 i+ raise GracefulDisconnect('server tip below max checkpoint') Err parazyd.org 70 i+ self._mark_ready() Err parazyd.org 70 i+ await self._process_header_at_tip() Err parazyd.org 70 i+ # header processing done Err parazyd.org 70 i+ util.trigger_callback('blockchain_updated') Err parazyd.org 70 i+ util.trigger_callback('network_updated') Err parazyd.org 70 i+ await self.network.switch_unwanted_fork_interface() Err parazyd.org 70 i+ await self.network.switch_lagging_interface() Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def _process_header_at_tip(self): Err parazyd.org 70 i+ height, header = self.tip, self.tip_header Err parazyd.org 70 i+ async with self.network.bhi_lock: Err parazyd.org 70 i+ if self.blockchain.height() >= height and self.blockchain.check_header(header): Err parazyd.org 70 i+ # another interface amended the blockchain Err parazyd.org 70 i+ self.logger.info(f"skipping header {height}") Err parazyd.org 70 i+ return Err parazyd.org 70 i+ _, height = await self.step(height, header) Err parazyd.org 70 i+ # in the simple case, height == self.tip+1 Err parazyd.org 70 i+ if height <= self.tip: Err parazyd.org 70 i+ await self.sync_until(height) Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def sync_until(self, height, next_height=None): Err parazyd.org 70 i+ if next_height is None: Err parazyd.org 70 i+ next_height = self.tip Err parazyd.org 70 i+ last = None Err parazyd.org 70 i+ while last is None or height <= next_height: Err parazyd.org 70 i+ prev_last, prev_height = last, height Err parazyd.org 70 i+ if next_height > height + 10: Err parazyd.org 70 i+ could_connect, num_headers = await self.request_chunk(height, next_height) Err parazyd.org 70 i+ if not could_connect: Err parazyd.org 70 i+ if height <= constants.net.max_checkpoint(): Err parazyd.org 70 i+ raise GracefulDisconnect('server chain conflicts with checkpoints or genesis') Err parazyd.org 70 i+ last, height = await self.step(height) Err parazyd.org 70 i+ continue Err parazyd.org 70 i+ util.trigger_callback('network_updated') Err parazyd.org 70 i+ height = (height // 2016 * 2016) + num_headers Err parazyd.org 70 i+ assert height <= next_height+1, (height, self.tip) Err parazyd.org 70 i+ last = 'catchup' Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ last, height = await self.step(height) Err parazyd.org 70 i+ assert (prev_last, prev_height) != (last, height), 'had to prevent infinite loop in interface.sync_until' Err parazyd.org 70 i+ return last, height Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def step(self, height, header=None): Err parazyd.org 70 i+ assert 0 <= height <= self.tip, (height, self.tip) Err parazyd.org 70 i+ if header is None: Err parazyd.org 70 i+ header = await self.get_block_header(height, 'catchup') Err parazyd.org 70 i+ Err parazyd.org 70 i+ chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) Err parazyd.org 70 i+ if chain: Err parazyd.org 70 i+ self.blockchain = chain if isinstance(chain, Blockchain) else self.blockchain Err parazyd.org 70 i+ # note: there is an edge case here that is not handled. Err parazyd.org 70 i+ # we might know the blockhash (enough for check_header) but Err parazyd.org 70 i+ # not have the header itself. e.g. regtest chain with only genesis. Err parazyd.org 70 i+ # this situation resolves itself on the next block Err parazyd.org 70 i+ return 'catchup', height+1 Err parazyd.org 70 i+ Err parazyd.org 70 i+ can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](height) Err parazyd.org 70 i+ if not can_connect: Err parazyd.org 70 i+ self.logger.info(f"can't connect {height}") Err parazyd.org 70 i+ height, header, bad, bad_header = await self._search_headers_backwards(height, header) Err parazyd.org 70 i+ chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) Err parazyd.org 70 i+ can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](height) Err parazyd.org 70 i+ assert chain or can_connect Err parazyd.org 70 i+ if can_connect: Err parazyd.org 70 i+ self.logger.info(f"could connect {height}") Err parazyd.org 70 i+ height += 1 Err parazyd.org 70 i+ if isinstance(can_connect, Blockchain): # not when mocking Err parazyd.org 70 i+ self.blockchain = can_connect Err parazyd.org 70 i+ self.blockchain.save_header(header) Err parazyd.org 70 i+ return 'catchup', height Err parazyd.org 70 i+ Err parazyd.org 70 i+ good, bad, bad_header = await self._search_headers_binary(height, bad, bad_header, chain) Err parazyd.org 70 i+ return await self._resolve_potential_chain_fork_given_forkpoint(good, bad, bad_header) Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def _search_headers_binary(self, height, bad, bad_header, chain): Err parazyd.org 70 i+ assert bad == bad_header['block_height'] Err parazyd.org 70 i+ _assert_header_does_not_check_against_any_chain(bad_header) Err parazyd.org 70 i+ Err parazyd.org 70 i+ self.blockchain = chain if isinstance(chain, Blockchain) else self.blockchain Err parazyd.org 70 i+ good = height Err parazyd.org 70 i+ while True: Err parazyd.org 70 i+ assert good < bad, (good, bad) Err parazyd.org 70 i+ height = (good + bad) // 2 Err parazyd.org 70 i+ self.logger.info(f"binary step. good {good}, bad {bad}, height {height}") Err parazyd.org 70 i+ header = await self.get_block_header(height, 'binary') Err parazyd.org 70 i+ chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) Err parazyd.org 70 i+ if chain: Err parazyd.org 70 i+ self.blockchain = chain if isinstance(chain, Blockchain) else self.blockchain Err parazyd.org 70 i+ good = height Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ bad = height Err parazyd.org 70 i+ bad_header = header Err parazyd.org 70 i+ if good + 1 == bad: Err parazyd.org 70 i+ break Err parazyd.org 70 i+ Err parazyd.org 70 i+ mock = 'mock' in bad_header and bad_header['mock']['connect'](height) Err parazyd.org 70 i+ real = not mock and self.blockchain.can_connect(bad_header, check_height=False) Err parazyd.org 70 i+ if not real and not mock: Err parazyd.org 70 i+ raise Exception('unexpected bad header during binary: {}'.format(bad_header)) Err parazyd.org 70 i+ _assert_header_does_not_check_against_any_chain(bad_header) Err parazyd.org 70 i+ Err parazyd.org 70 i+ self.logger.info(f"binary search exited. good {good}, bad {bad}") Err parazyd.org 70 i+ return good, bad, bad_header Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def _resolve_potential_chain_fork_given_forkpoint(self, good, bad, bad_header): Err parazyd.org 70 i+ assert good + 1 == bad Err parazyd.org 70 i+ assert bad == bad_header['block_height'] Err parazyd.org 70 i+ _assert_header_does_not_check_against_any_chain(bad_header) Err parazyd.org 70 i+ # 'good' is the height of a block 'good_header', somewhere in self.blockchain. Err parazyd.org 70 i+ # bad_header connects to good_header; bad_header itself is NOT in self.blockchain. Err parazyd.org 70 i+ Err parazyd.org 70 i+ bh = self.blockchain.height() Err parazyd.org 70 i+ assert bh >= good, (bh, good) Err parazyd.org 70 i+ if bh == good: Err parazyd.org 70 i+ height = good + 1 Err parazyd.org 70 i+ self.logger.info(f"catching up from {height}") Err parazyd.org 70 i+ return 'no_fork', height Err parazyd.org 70 i+ Err parazyd.org 70 i+ # this is a new fork we don't yet have Err parazyd.org 70 i+ height = bad + 1 Err parazyd.org 70 i+ self.logger.info(f"new fork at bad height {bad}") Err parazyd.org 70 i+ forkfun = self.blockchain.fork if 'mock' not in bad_header else bad_header['mock']['fork'] Err parazyd.org 70 i+ b = forkfun(bad_header) # type: Blockchain Err parazyd.org 70 i+ self.blockchain = b Err parazyd.org 70 i+ assert b.forkpoint == bad Err parazyd.org 70 i+ return 'fork', height Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def _search_headers_backwards(self, height, header): Err parazyd.org 70 i+ async def iterate(): Err parazyd.org 70 i+ nonlocal height, header Err parazyd.org 70 i+ checkp = False Err parazyd.org 70 i+ if height <= constants.net.max_checkpoint(): Err parazyd.org 70 i+ height = constants.net.max_checkpoint() Err parazyd.org 70 i+ checkp = True Err parazyd.org 70 i+ header = await self.get_block_header(height, 'backward') Err parazyd.org 70 i+ chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) Err parazyd.org 70 i+ can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](height) Err parazyd.org 70 i+ if chain or can_connect: Err parazyd.org 70 i+ return False Err parazyd.org 70 i+ if checkp: Err parazyd.org 70 i+ raise GracefulDisconnect("server chain conflicts with checkpoints") Err parazyd.org 70 i+ return True Err parazyd.org 70 i+ Err parazyd.org 70 i+ bad, bad_header = height, header Err parazyd.org 70 i+ _assert_header_does_not_check_against_any_chain(bad_header) Err parazyd.org 70 i+ with blockchain.blockchains_lock: chains = list(blockchain.blockchains.values()) Err parazyd.org 70 i+ local_max = max([0] + [x.height() for x in chains]) if 'mock' not in header else float('inf') Err parazyd.org 70 i+ height = min(local_max + 1, height - 1) Err parazyd.org 70 i+ while await iterate(): Err parazyd.org 70 i+ bad, bad_header = height, header Err parazyd.org 70 i+ delta = self.tip - height Err parazyd.org 70 i+ height = self.tip - 2 * delta Err parazyd.org 70 i+ Err parazyd.org 70 i+ _assert_header_does_not_check_against_any_chain(bad_header) Err parazyd.org 70 i+ self.logger.info(f"exiting backward mode at {height}") Err parazyd.org 70 i+ return height, header, bad, bad_header Err parazyd.org 70 i+ Err parazyd.org 70 i+ @classmethod Err parazyd.org 70 i+ def client_name(cls) -> str: Err parazyd.org 70 i+ return f'electrum/{version.ELECTRUM_VERSION}' Err parazyd.org 70 i+ Err parazyd.org 70 i+ def is_tor(self): Err parazyd.org 70 i+ return self.host.endswith('.onion') Err parazyd.org 70 i+ Err parazyd.org 70 i+ def ip_addr(self) -> Optional[str]: Err parazyd.org 70 i+ session = self.session Err parazyd.org 70 i+ if not session: return None Err parazyd.org 70 i+ peer_addr = session.remote_address() Err parazyd.org 70 i+ if not peer_addr: return None Err parazyd.org 70 i+ return str(peer_addr.host) Err parazyd.org 70 i+ Err parazyd.org 70 i+ def bucket_based_on_ipaddress(self) -> str: Err parazyd.org 70 i+ def do_bucket(): Err parazyd.org 70 i+ if self.is_tor(): Err parazyd.org 70 i+ return BUCKET_NAME_OF_ONION_SERVERS Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ ip_addr = ip_address(self.ip_addr()) # type: Union[IPv4Address, IPv6Address] Err parazyd.org 70 i+ except ValueError: Err parazyd.org 70 i+ return '' Err parazyd.org 70 i+ if not ip_addr: Err parazyd.org 70 i+ return '' Err parazyd.org 70 i+ if ip_addr.is_loopback: # localhost is exempt Err parazyd.org 70 i+ return '' Err parazyd.org 70 i+ if ip_addr.version == 4: Err parazyd.org 70 i+ slash16 = IPv4Network(ip_addr).supernet(prefixlen_diff=32-16) Err parazyd.org 70 i+ return str(slash16) Err parazyd.org 70 i+ elif ip_addr.version == 6: Err parazyd.org 70 i+ slash48 = IPv6Network(ip_addr).supernet(prefixlen_diff=128-48) Err parazyd.org 70 i+ return str(slash48) Err parazyd.org 70 i+ return '' Err parazyd.org 70 i+ Err parazyd.org 70 i+ if not self._ipaddr_bucket: Err parazyd.org 70 i+ self._ipaddr_bucket = do_bucket() Err parazyd.org 70 i+ return self._ipaddr_bucket Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def get_merkle_for_transaction(self, tx_hash: str, tx_height: int) -> dict: Err parazyd.org 70 i+ if not is_hash256_str(tx_hash): Err parazyd.org 70 i+ raise Exception(f"{repr(tx_hash)} is not a txid") Err parazyd.org 70 i+ if not is_non_negative_integer(tx_height): Err parazyd.org 70 i+ raise Exception(f"{repr(tx_height)} is not a block height") Err parazyd.org 70 i+ # do request Err parazyd.org 70 i+ res = await self.session.send_request('blockchain.transaction.get_merkle', [tx_hash, tx_height]) Err parazyd.org 70 i+ # check response Err parazyd.org 70 i+ block_height = assert_dict_contains_field(res, field_name='block_height') Err parazyd.org 70 i+ merkle = assert_dict_contains_field(res, field_name='merkle') Err parazyd.org 70 i+ pos = assert_dict_contains_field(res, field_name='pos') Err parazyd.org 70 i+ # note: tx_height was just a hint to the server, don't enforce the response to match it Err parazyd.org 70 i+ assert_non_negative_integer(block_height) Err parazyd.org 70 i+ assert_non_negative_integer(pos) Err parazyd.org 70 i+ assert_list_or_tuple(merkle) Err parazyd.org 70 i+ for item in merkle: Err parazyd.org 70 i+ assert_hash256_str(item) Err parazyd.org 70 i+ return res Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def get_transaction(self, tx_hash: str, *, timeout=None) -> str: Err parazyd.org 70 i+ if not is_hash256_str(tx_hash): Err parazyd.org 70 i+ raise Exception(f"{repr(tx_hash)} is not a txid") Err parazyd.org 70 i+ raw = await self.session.send_request('blockchain.transaction.get', [tx_hash], timeout=timeout) Err parazyd.org 70 i+ # validate response Err parazyd.org 70 i+ if not is_hex_str(raw): Err parazyd.org 70 i+ raise RequestCorrupted(f"received garbage (non-hex) as tx data (txid {tx_hash}): {raw!r}") Err parazyd.org 70 i+ tx = Transaction(raw) Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ tx.deserialize() # see if raises Err parazyd.org 70 i+ except Exception as e: Err parazyd.org 70 i+ raise RequestCorrupted(f"cannot deserialize received transaction (txid {tx_hash})") from e Err parazyd.org 70 i+ if tx.txid() != tx_hash: Err parazyd.org 70 i+ raise RequestCorrupted(f"received tx does not match expected txid {tx_hash} (got {tx.txid()})") Err parazyd.org 70 i+ return raw Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def get_history_for_scripthash(self, sh: str) -> List[dict]: Err parazyd.org 70 i+ if not is_hash256_str(sh): Err parazyd.org 70 i+ raise Exception(f"{repr(sh)} is not a scripthash") Err parazyd.org 70 i+ # do request Err parazyd.org 70 i+ res = await self.session.send_request('blockchain.scripthash.get_history', [sh]) Err parazyd.org 70 i+ # check response Err parazyd.org 70 i+ assert_list_or_tuple(res) Err parazyd.org 70 i+ prev_height = 1 Err parazyd.org 70 i+ for tx_item in res: Err parazyd.org 70 i+ height = assert_dict_contains_field(tx_item, field_name='height') Err parazyd.org 70 i+ assert_dict_contains_field(tx_item, field_name='tx_hash') Err parazyd.org 70 i+ assert_integer(height) Err parazyd.org 70 i+ assert_hash256_str(tx_item['tx_hash']) Err parazyd.org 70 i+ if height in (-1, 0): Err parazyd.org 70 i+ assert_dict_contains_field(tx_item, field_name='fee') Err parazyd.org 70 i+ assert_non_negative_integer(tx_item['fee']) Err parazyd.org 70 i+ prev_height = - float("inf") # this ensures confirmed txs can't follow mempool txs Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ # check monotonicity of heights Err parazyd.org 70 i+ if height < prev_height: Err parazyd.org 70 i+ raise RequestCorrupted(f'heights of confirmed txs must be in increasing order') Err parazyd.org 70 i+ prev_height = height Err parazyd.org 70 i+ hashes = set(map(lambda item: item['tx_hash'], res)) Err parazyd.org 70 i+ if len(hashes) != len(res): Err parazyd.org 70 i+ # Either server is sending garbage... or maybe if server is race-prone Err parazyd.org 70 i+ # a recently mined tx could be included in both last block and mempool? Err parazyd.org 70 i+ # Still, it's simplest to just disregard the response. Err parazyd.org 70 i+ raise RequestCorrupted(f"server history has non-unique txids for sh={sh}") Err parazyd.org 70 i+ return res Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def listunspent_for_scripthash(self, sh: str) -> List[dict]: Err parazyd.org 70 i+ if not is_hash256_str(sh): Err parazyd.org 70 i+ raise Exception(f"{repr(sh)} is not a scripthash") Err parazyd.org 70 i+ # do request Err parazyd.org 70 i+ res = await self.session.send_request('blockchain.scripthash.listunspent', [sh]) Err parazyd.org 70 i+ # check response Err parazyd.org 70 i+ assert_list_or_tuple(res) Err parazyd.org 70 i+ for utxo_item in res: Err parazyd.org 70 i+ assert_dict_contains_field(utxo_item, field_name='tx_pos') Err parazyd.org 70 i+ assert_dict_contains_field(utxo_item, field_name='value') Err parazyd.org 70 i+ assert_dict_contains_field(utxo_item, field_name='tx_hash') Err parazyd.org 70 i+ assert_dict_contains_field(utxo_item, field_name='height') Err parazyd.org 70 i+ assert_non_negative_integer(utxo_item['tx_pos']) Err parazyd.org 70 i+ assert_non_negative_integer(utxo_item['value']) Err parazyd.org 70 i+ assert_non_negative_integer(utxo_item['height']) Err parazyd.org 70 i+ assert_hash256_str(utxo_item['tx_hash']) Err parazyd.org 70 i+ return res Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def get_balance_for_scripthash(self, sh: str) -> dict: Err parazyd.org 70 i+ if not is_hash256_str(sh): Err parazyd.org 70 i+ raise Exception(f"{repr(sh)} is not a scripthash") Err parazyd.org 70 i+ # do request Err parazyd.org 70 i+ res = await self.session.send_request('blockchain.scripthash.get_balance', [sh]) Err parazyd.org 70 i+ # check response Err parazyd.org 70 i+ assert_dict_contains_field(res, field_name='confirmed') Err parazyd.org 70 i+ assert_dict_contains_field(res, field_name='unconfirmed') Err parazyd.org 70 i+ assert_non_negative_integer(res['confirmed']) Err parazyd.org 70 i+ assert_non_negative_integer(res['unconfirmed']) Err parazyd.org 70 i+ return res Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def get_txid_from_txpos(self, tx_height: int, tx_pos: int, merkle: bool): Err parazyd.org 70 i+ if not is_non_negative_integer(tx_height): Err parazyd.org 70 i+ raise Exception(f"{repr(tx_height)} is not a block height") Err parazyd.org 70 i+ if not is_non_negative_integer(tx_pos): Err parazyd.org 70 i+ raise Exception(f"{repr(tx_pos)} should be non-negative integer") Err parazyd.org 70 i+ # do request Err parazyd.org 70 i+ res = await self.session.send_request( Err parazyd.org 70 i+ 'blockchain.transaction.id_from_pos', Err parazyd.org 70 i+ [tx_height, tx_pos, merkle], Err parazyd.org 70 i+ ) Err parazyd.org 70 i+ # check response Err parazyd.org 70 i+ if merkle: Err parazyd.org 70 i+ assert_dict_contains_field(res, field_name='tx_hash') Err parazyd.org 70 i+ assert_dict_contains_field(res, field_name='merkle') Err parazyd.org 70 i+ assert_hash256_str(res['tx_hash']) Err parazyd.org 70 i+ assert_list_or_tuple(res['merkle']) Err parazyd.org 70 i+ for node_hash in res['merkle']: Err parazyd.org 70 i+ assert_hash256_str(node_hash) Err parazyd.org 70 i+ else: Err parazyd.org 70 i+ assert_hash256_str(res) Err parazyd.org 70 i+ return res Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def get_fee_histogram(self) -> Sequence[Tuple[Union[float, int], int]]: Err parazyd.org 70 i+ # do request Err parazyd.org 70 i+ res = await self.session.send_request('mempool.get_fee_histogram') Err parazyd.org 70 i+ # check response Err parazyd.org 70 i+ assert_list_or_tuple(res) Err parazyd.org 70 i+ prev_fee = float('inf') Err parazyd.org 70 i+ for fee, s in res: Err parazyd.org 70 i+ assert_non_negative_int_or_float(fee) Err parazyd.org 70 i+ assert_non_negative_integer(s) Err parazyd.org 70 i+ if fee >= prev_fee: # check monotonicity Err parazyd.org 70 i+ raise RequestCorrupted(f'fees must be in decreasing order') Err parazyd.org 70 i+ prev_fee = fee Err parazyd.org 70 i+ return res Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def get_server_banner(self) -> str: Err parazyd.org 70 i+ # do request Err parazyd.org 70 i+ res = await self.session.send_request('server.banner') Err parazyd.org 70 i+ # check response Err parazyd.org 70 i+ if not isinstance(res, str): Err parazyd.org 70 i+ raise RequestCorrupted(f'{res!r} should be a str') Err parazyd.org 70 i+ return res Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def get_donation_address(self) -> str: Err parazyd.org 70 i+ # do request Err parazyd.org 70 i+ res = await self.session.send_request('server.donation_address') Err parazyd.org 70 i+ # check response Err parazyd.org 70 i+ if not res: # ignore empty string Err parazyd.org 70 i+ return '' Err parazyd.org 70 i+ if not bitcoin.is_address(res): Err parazyd.org 70 i+ # note: do not hard-fail -- allow server to use future-type Err parazyd.org 70 i+ # bitcoin address we do not recognize Err parazyd.org 70 i+ self.logger.info(f"invalid donation address from server: {repr(res)}") Err parazyd.org 70 i+ res = '' Err parazyd.org 70 i+ return res Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def get_relay_fee(self) -> int: Err parazyd.org 70 i+ """Returns the min relay feerate in sat/kbyte.""" Err parazyd.org 70 i+ # do request Err parazyd.org 70 i+ res = await self.session.send_request('blockchain.relayfee') Err parazyd.org 70 i+ # check response Err parazyd.org 70 i+ assert_non_negative_int_or_float(res) Err parazyd.org 70 i+ relayfee = int(res * bitcoin.COIN) Err parazyd.org 70 i+ relayfee = max(0, relayfee) Err parazyd.org 70 i+ return relayfee Err parazyd.org 70 i+ Err parazyd.org 70 i+ async def get_estimatefee(self, num_blocks: int) -> int: Err parazyd.org 70 i+ """Returns a feerate estimate for getting confirmed within Err parazyd.org 70 i+ num_blocks blocks, in sat/kbyte. Err parazyd.org 70 i+ """ Err parazyd.org 70 i+ if not is_non_negative_integer(num_blocks): Err parazyd.org 70 i+ raise Exception(f"{repr(num_blocks)} is not a num_blocks") Err parazyd.org 70 i+ # do request Err parazyd.org 70 i+ res = await self.session.send_request('blockchain.estimatefee', [num_blocks]) Err parazyd.org 70 i+ # check response Err parazyd.org 70 i+ if res != -1: Err parazyd.org 70 i+ assert_non_negative_int_or_float(res) Err parazyd.org 70 i+ res = int(res * bitcoin.COIN) Err parazyd.org 70 i+ return res Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def _assert_header_does_not_check_against_any_chain(header: dict) -> None: Err parazyd.org 70 i+ chain_bad = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) Err parazyd.org 70 i+ if chain_bad: Err parazyd.org 70 i+ raise Exception('bad_header must not check!') Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def check_cert(host, cert): Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ b = pem.dePem(cert, 'CERTIFICATE') Err parazyd.org 70 i+ x = x509.X509(b) Err parazyd.org 70 i+ except: Err parazyd.org 70 i+ traceback.print_exc(file=sys.stdout) Err parazyd.org 70 i+ return Err parazyd.org 70 i+ Err parazyd.org 70 i+ try: Err parazyd.org 70 i+ x.check_date() Err parazyd.org 70 i+ expired = False Err parazyd.org 70 i+ except: Err parazyd.org 70 i+ expired = True Err parazyd.org 70 i+ Err parazyd.org 70 i+ m = "host: %s\n"%host Err parazyd.org 70 i+ m += "has_expired: %s\n"% expired Err parazyd.org 70 i+ util.print_msg(m) Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+# Used by tests Err parazyd.org 70 i+def _match_hostname(name, val): Err parazyd.org 70 i+ if val == name: Err parazyd.org 70 i+ return True Err parazyd.org 70 i+ Err parazyd.org 70 i+ return val.startswith('*.') and name.endswith(val[1:]) Err parazyd.org 70 i+ Err parazyd.org 70 i+ Err parazyd.org 70 i+def test_certificates(): Err parazyd.org 70 i+ from .simple_config import SimpleConfig Err parazyd.org 70 i+ config = SimpleConfig() Err parazyd.org 70 i+ mydir = os.path.join(config.path, "certs") Err parazyd.org 70 i+ certs = os.listdir(mydir) Err parazyd.org 70 i+ for c in certs: Err parazyd.org 70 i+ p = os.path.join(mydir,c) Err parazyd.org 70 i+ with open(p, encoding='utf-8') as f: Err parazyd.org 70 i+ cert = f.read() Err parazyd.org 70 i+ check_cert(c, cert) Err parazyd.org 70 i+ Err parazyd.org 70 i+if __name__ == "__main__": Err parazyd.org 70 i+ test_certificates() Err parazyd.org 70 .