|
|
|
@ -29,7 +29,7 @@ import sys |
|
|
|
import traceback |
|
|
|
import traceback |
|
|
|
import asyncio |
|
|
|
import asyncio |
|
|
|
import socket |
|
|
|
import socket |
|
|
|
from typing import Tuple, Union, List, TYPE_CHECKING, Optional, Set, NamedTuple, Any |
|
|
|
from typing import Tuple, Union, List, TYPE_CHECKING, Optional, Set, NamedTuple, Any, Sequence |
|
|
|
from collections import defaultdict |
|
|
|
from collections import defaultdict |
|
|
|
from ipaddress import IPv4Network, IPv6Network, ip_address, IPv6Address, IPv4Address |
|
|
|
from ipaddress import IPv4Network, IPv6Network, ip_address, IPv6Address, IPv4Address |
|
|
|
import itertools |
|
|
|
import itertools |
|
|
|
@ -46,13 +46,14 @@ import certifi |
|
|
|
|
|
|
|
|
|
|
|
from .util import (ignore_exceptions, log_exceptions, bfh, SilentTaskGroup, MySocksProxy, |
|
|
|
from .util import (ignore_exceptions, log_exceptions, bfh, SilentTaskGroup, MySocksProxy, |
|
|
|
is_integer, is_non_negative_integer, is_hash256_str, is_hex_str, |
|
|
|
is_integer, is_non_negative_integer, is_hash256_str, is_hex_str, |
|
|
|
is_real_number) |
|
|
|
is_int_or_float, is_non_negative_int_or_float) |
|
|
|
from . import util |
|
|
|
from . import util |
|
|
|
from . import x509 |
|
|
|
from . import x509 |
|
|
|
from . import pem |
|
|
|
from . import pem |
|
|
|
from . import version |
|
|
|
from . import version |
|
|
|
from . import blockchain |
|
|
|
from . import blockchain |
|
|
|
from .blockchain import Blockchain, HEADER_SIZE |
|
|
|
from .blockchain import Blockchain, HEADER_SIZE |
|
|
|
|
|
|
|
from . import bitcoin |
|
|
|
from . import constants |
|
|
|
from . import constants |
|
|
|
from .i18n import _ |
|
|
|
from .i18n import _ |
|
|
|
from .logging import Logger |
|
|
|
from .logging import Logger |
|
|
|
@ -96,9 +97,14 @@ def assert_integer(val: Any) -> None: |
|
|
|
raise RequestCorrupted(f'{val!r} should be an integer') |
|
|
|
raise RequestCorrupted(f'{val!r} should be an integer') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def assert_real_number(val: Any, *, as_str: bool = False) -> None: |
|
|
|
def assert_int_or_float(val: Any) -> None: |
|
|
|
if not is_real_number(val, as_str=as_str): |
|
|
|
if not is_int_or_float(val): |
|
|
|
raise RequestCorrupted(f'{val!r} should be a number') |
|
|
|
raise RequestCorrupted(f'{val!r} should be int or float') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def assert_non_negative_int_or_float(val: Any) -> None: |
|
|
|
|
|
|
|
if not is_non_negative_int_or_float(val): |
|
|
|
|
|
|
|
raise RequestCorrupted(f'{val!r} should be a non-negative int or float') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def assert_hash256_str(val: Any) -> None: |
|
|
|
def assert_hash256_str(val: Any) -> None: |
|
|
|
@ -656,14 +662,13 @@ class Interface(Logger): |
|
|
|
|
|
|
|
|
|
|
|
async def request_fee_estimates(self): |
|
|
|
async def request_fee_estimates(self): |
|
|
|
from .simple_config import FEE_ETA_TARGETS |
|
|
|
from .simple_config import FEE_ETA_TARGETS |
|
|
|
from .bitcoin import COIN |
|
|
|
|
|
|
|
while True: |
|
|
|
while True: |
|
|
|
async with TaskGroup() as group: |
|
|
|
async with TaskGroup() as group: |
|
|
|
fee_tasks = [] |
|
|
|
fee_tasks = [] |
|
|
|
for i in FEE_ETA_TARGETS: |
|
|
|
for i in FEE_ETA_TARGETS: |
|
|
|
fee_tasks.append((i, await group.spawn(self.session.send_request('blockchain.estimatefee', [i])))) |
|
|
|
fee_tasks.append((i, await group.spawn(self.get_estimatefee(i)))) |
|
|
|
for nblock_target, task in fee_tasks: |
|
|
|
for nblock_target, task in fee_tasks: |
|
|
|
fee = int(task.result() * COIN) |
|
|
|
fee = task.result() |
|
|
|
if fee < 0: continue |
|
|
|
if fee < 0: continue |
|
|
|
self.fee_estimates_eta[nblock_target] = fee |
|
|
|
self.fee_estimates_eta[nblock_target] = fee |
|
|
|
self.network.update_fee_estimates() |
|
|
|
self.network.update_fee_estimates() |
|
|
|
@ -983,6 +988,61 @@ class Interface(Logger): |
|
|
|
assert_hash256_str(res) |
|
|
|
assert_hash256_str(res) |
|
|
|
return res |
|
|
|
return res |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def get_fee_histogram(self) -> Sequence[Tuple[Union[float, int], int]]: |
|
|
|
|
|
|
|
# do request |
|
|
|
|
|
|
|
res = await self.session.send_request('mempool.get_fee_histogram') |
|
|
|
|
|
|
|
# check response |
|
|
|
|
|
|
|
assert_list_or_tuple(res) |
|
|
|
|
|
|
|
for fee, s in res: |
|
|
|
|
|
|
|
assert_non_negative_int_or_float(fee) |
|
|
|
|
|
|
|
assert_non_negative_integer(s) |
|
|
|
|
|
|
|
return res |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def get_server_banner(self) -> str: |
|
|
|
|
|
|
|
# do request |
|
|
|
|
|
|
|
res = await self.session.send_request('server.banner') |
|
|
|
|
|
|
|
# check response |
|
|
|
|
|
|
|
if not isinstance(res, str): |
|
|
|
|
|
|
|
raise RequestCorrupted(f'{res!r} should be a str') |
|
|
|
|
|
|
|
return res |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def get_donation_address(self) -> str: |
|
|
|
|
|
|
|
# do request |
|
|
|
|
|
|
|
res = await self.session.send_request('server.donation_address') |
|
|
|
|
|
|
|
# check response |
|
|
|
|
|
|
|
if not res: # ignore empty string |
|
|
|
|
|
|
|
return '' |
|
|
|
|
|
|
|
if not bitcoin.is_address(res): |
|
|
|
|
|
|
|
# note: do not hard-fail -- allow server to use future-type |
|
|
|
|
|
|
|
# bitcoin address we do not recognize |
|
|
|
|
|
|
|
self.logger.info(f"invalid donation address from server: {repr(res)}") |
|
|
|
|
|
|
|
res = '' |
|
|
|
|
|
|
|
return res |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def get_relay_fee(self) -> int: |
|
|
|
|
|
|
|
"""Returns the min relay feerate in sat/kbyte.""" |
|
|
|
|
|
|
|
# do request |
|
|
|
|
|
|
|
res = await self.session.send_request('blockchain.relayfee') |
|
|
|
|
|
|
|
# check response |
|
|
|
|
|
|
|
assert_non_negative_int_or_float(res) |
|
|
|
|
|
|
|
relayfee = int(res * bitcoin.COIN) |
|
|
|
|
|
|
|
relayfee = max(0, relayfee) |
|
|
|
|
|
|
|
return relayfee |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def get_estimatefee(self, num_blocks: int) -> int: |
|
|
|
|
|
|
|
"""Returns a feerate estimate for getting confirmed within |
|
|
|
|
|
|
|
num_blocks blocks, in sat/kbyte. |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
if not is_non_negative_integer(num_blocks): |
|
|
|
|
|
|
|
raise Exception(f"{repr(num_blocks)} is not a num_blocks") |
|
|
|
|
|
|
|
# do request |
|
|
|
|
|
|
|
res = await self.session.send_request('blockchain.estimatefee', [num_blocks]) |
|
|
|
|
|
|
|
# check response |
|
|
|
|
|
|
|
if res != -1: |
|
|
|
|
|
|
|
assert_non_negative_int_or_float(res) |
|
|
|
|
|
|
|
res = int(res * bitcoin.COIN) |
|
|
|
|
|
|
|
return res |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _assert_header_does_not_check_against_any_chain(header: dict) -> None: |
|
|
|
def _assert_header_does_not_check_against_any_chain(header: dict) -> None: |
|
|
|
chain_bad = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) |
|
|
|
chain_bad = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) |
|
|
|
|