country jurisdiction added to hub UDP protocol

This commit is contained in:
Lex Berezhny 2021-05-21 14:31:03 -04:00
parent 73e239cc5f
commit 5f0426c840
4 changed files with 57 additions and 23 deletions

View file

@ -32,6 +32,17 @@ def calculate_sha384_file_hash(file_path):
return sha384.digest() return sha384.digest()
def country_int_to_str(country: int) -> str:
r = LocationMessage.Country.Name(country)
return r[1:] if r.startswith('R') else r
def country_str_to_int(country: str) -> int:
if len(country) == 3:
country = 'R' + country
return LocationMessage.Country.Value(country)
class Dimmensional(Metadata): class Dimmensional(Metadata):
__slots__ = () __slots__ = ()
@ -423,14 +434,11 @@ class Language(Metadata):
@property @property
def region(self) -> str: def region(self) -> str:
if self.message.region: if self.message.region:
r = LocationMessage.Country.Name(self.message.region) return country_int_to_str(self.message.region)
return r[1:] if r.startswith('R') else r
@region.setter @region.setter
def region(self, region: str): def region(self, region: str):
if len(region) == 3: self.message.region = country_str_to_int(region)
region = 'R'+region
self.message.region = LocationMessage.Country.Value(region)
class LanguageList(BaseMessageList[Language]): class LanguageList(BaseMessageList[Language]):

View file

@ -74,6 +74,7 @@ class Env:
self.anon_logs = self.boolean('ANON_LOGS', False) self.anon_logs = self.boolean('ANON_LOGS', False)
self.log_sessions = self.integer('LOG_SESSIONS', 3600) self.log_sessions = self.integer('LOG_SESSIONS', 3600)
self.allow_lan_udp = self.boolean('ALLOW_LAN_UDP', False) self.allow_lan_udp = self.boolean('ALLOW_LAN_UDP', False)
self.country = self.default('COUNTRY', 'US')
# Peer discovery # Peer discovery
self.peer_discovery = self.peer_discovery_enum() self.peer_discovery = self.peer_discovery_enum()
self.peer_announce = self.boolean('PEER_ANNOUNCE', True) self.peer_announce = self.boolean('PEER_ANNOUNCE', True)

View file

@ -114,7 +114,7 @@ class Server:
await self.start_prometheus() await self.start_prometheus()
if self.env.udp_port: if self.env.udp_port:
await self.bp.status_server.start( await self.bp.status_server.start(
0, bytes.fromhex(self.bp.coin.GENESIS_HASH)[::-1], 0, bytes.fromhex(self.bp.coin.GENESIS_HASH)[::-1], self.env.country,
self.env.host, self.env.udp_port, self.env.allow_lan_udp self.env.host, self.env.udp_port, self.env.allow_lan_udp
) )
await _start_cancellable(self.bp.fetch_and_process_blocks) await _start_cancellable(self.bp.fetch_and_process_blocks)

View file

@ -4,6 +4,7 @@ from time import perf_counter
import logging import logging
from typing import Optional, Tuple, NamedTuple from typing import Optional, Tuple, NamedTuple
from lbry.utils import LRUCache, is_valid_public_ipv4 from lbry.utils import LRUCache, is_valid_public_ipv4
from lbry.schema.attrs import country_str_to_int, country_int_to_str
# from prometheus_client import Counter # from prometheus_client import Counter
@ -13,6 +14,9 @@ _MAGIC = 1446058291 # genesis blocktime (which is actually wrong)
_PAD_BYTES = b'\x00' * 64 _PAD_BYTES = b'\x00' * 64
PROTOCOL_VERSION = 1
class SPVPing(NamedTuple): class SPVPing(NamedTuple):
magic: int magic: int
protocol_version: int protocol_version: int
@ -22,8 +26,8 @@ class SPVPing(NamedTuple):
return struct.pack(b'!lB64s', *self) return struct.pack(b'!lB64s', *self)
@staticmethod @staticmethod
def make(protocol_version=1) -> bytes: def make() -> bytes:
return SPVPing(_MAGIC, protocol_version, _PAD_BYTES).encode() return SPVPing(_MAGIC, PROTOCOL_VERSION, _PAD_BYTES).encode()
@classmethod @classmethod
def decode(cls, packet: bytes): def decode(cls, packet: bytes):
@ -39,14 +43,27 @@ class SPVPong(NamedTuple):
height: int height: int
tip: bytes tip: bytes
source_address_raw: bytes source_address_raw: bytes
country: int
def encode(self): def encode(self):
return struct.pack(b'!BBl32s4s', *self) return struct.pack(b'!BBL32s4sH', *self)
@staticmethod @staticmethod
def make(height: int, tip: bytes, flags: int, protocol_version: int = 1) -> bytes: def encode_address(address: str):
# note: drops the last 4 bytes so the result can be cached and have addresses added to it as needed return bytes(int(b) for b in address.split("."))
return SPVPong(protocol_version, flags, height, tip, b'\x00\x00\x00\x00').encode()[:38]
@classmethod
def make(cls, flags: int, height: int, tip: bytes, source_address: str, country: str) -> bytes:
return SPVPong(
PROTOCOL_VERSION, flags, height, tip,
cls.encode_address(source_address),
country_str_to_int(country)
).encode()
@classmethod
def make_sans_source_address(cls, flags: int, height: int, tip: bytes, country: str) -> Tuple[bytes, bytes]:
pong = cls.make(flags, height, tip, '0.0.0.0', country)
return pong[:38], pong[42:]
@classmethod @classmethod
def decode(cls, packet: bytes): def decode(cls, packet: bytes):
@ -60,23 +77,30 @@ class SPVPong(NamedTuple):
def ip_address(self) -> str: def ip_address(self) -> str:
return ".".join(map(str, self.source_address_raw)) return ".".join(map(str, self.source_address_raw))
@property
def country_name(self):
return country_int_to_str(self.country)
def __repr__(self) -> str: def __repr__(self) -> str:
return f"SPVPong(external_ip={self.ip_address}, version={self.protocol_version}, " \ return f"SPVPong(external_ip={self.ip_address}, version={self.protocol_version}, " \
f"available={'True' if self.flags & 1 > 0 else 'False'}," \ f"available={'True' if self.flags & 1 > 0 else 'False'}," \
f" height={self.height}, tip={self.tip[::-1].hex()})" f" height={self.height}, tip={self.tip[::-1].hex()}, country={self.country_name})"
class SPVServerStatusProtocol(asyncio.DatagramProtocol): class SPVServerStatusProtocol(asyncio.DatagramProtocol):
PROTOCOL_VERSION = 1
def __init__(self, height: int, tip: bytes, throttle_cache_size: int = 1024, throttle_reqs_per_sec: int = 10, def __init__(
allow_localhost: bool = False, allow_lan: bool = False): self, height: int, tip: bytes, country: str,
throttle_cache_size: int = 1024, throttle_reqs_per_sec: int = 10,
allow_localhost: bool = False, allow_lan: bool = False
):
super().__init__() super().__init__()
self.transport: Optional[asyncio.transports.DatagramTransport] = None self.transport: Optional[asyncio.transports.DatagramTransport] = None
self._height = height self._height = height
self._tip = tip self._tip = tip
self._flags = 0 self._flags = 0
self._cached_response = None self._country = country
self._left_cache = self._right_cache = None
self.update_cached_response() self.update_cached_response()
self._throttle = LRUCache(throttle_cache_size) self._throttle = LRUCache(throttle_cache_size)
self._should_log = LRUCache(throttle_cache_size) self._should_log = LRUCache(throttle_cache_size)
@ -85,7 +109,9 @@ class SPVServerStatusProtocol(asyncio.DatagramProtocol):
self._allow_lan = allow_lan self._allow_lan = allow_lan
def update_cached_response(self): def update_cached_response(self):
self._cached_response = SPVPong.make(self._height, self._tip, self._flags, self.PROTOCOL_VERSION) self._left_cache, self._right_cache = SPVPong.make_sans_source_address(
self._flags, max(0, self._height), self._tip, self._country
)
def set_unavailable(self): def set_unavailable(self):
self._flags &= 0b11111110 self._flags &= 0b11111110
@ -112,7 +138,7 @@ class SPVServerStatusProtocol(asyncio.DatagramProtocol):
return False return False
def make_pong(self, host): def make_pong(self, host):
return self._cached_response + bytes(int(b) for b in host.split(".")) return self._left_cache + SPVPong.encode_address(host) + self._right_cache
def datagram_received(self, data: bytes, addr: Tuple[str, int]): def datagram_received(self, data: bytes, addr: Tuple[str, int]):
if self.should_throttle(addr[0]): if self.should_throttle(addr[0]):
@ -144,13 +170,13 @@ class StatusServer:
def __init__(self): def __init__(self):
self._protocol: Optional[SPVServerStatusProtocol] = None self._protocol: Optional[SPVServerStatusProtocol] = None
async def start(self, height: int, tip: bytes, interface: str, port: int, allow_lan: bool = False): async def start(self, height: int, tip: bytes, country: str, interface: str, port: int, allow_lan: bool = False):
if self.is_running: if self.is_running:
return return
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
interface = interface if interface.lower() != 'localhost' else '127.0.0.1' interface = interface if interface.lower() != 'localhost' else '127.0.0.1'
self._protocol = SPVServerStatusProtocol( self._protocol = SPVServerStatusProtocol(
height, tip, allow_localhost=interface == '127.0.0.1', allow_lan=allow_lan height, tip, country, allow_localhost=interface == '127.0.0.1', allow_lan=allow_lan
) )
await loop.create_datagram_endpoint(lambda: self._protocol, (interface, port)) await loop.create_datagram_endpoint(lambda: self._protocol, (interface, port))
log.info("started udp status server on %s:%i", interface, port) log.info("started udp status server on %s:%i", interface, port)
@ -178,13 +204,12 @@ class StatusServer:
class SPVStatusClientProtocol(asyncio.DatagramProtocol): class SPVStatusClientProtocol(asyncio.DatagramProtocol):
PROTOCOL_VERSION = 1
def __init__(self, responses: asyncio.Queue): def __init__(self, responses: asyncio.Queue):
super().__init__() super().__init__()
self.transport: Optional[asyncio.transports.DatagramTransport] = None self.transport: Optional[asyncio.transports.DatagramTransport] = None
self.responses = responses self.responses = responses
self._ping_packet = SPVPing.make(self.PROTOCOL_VERSION) self._ping_packet = SPVPing.make()
def datagram_received(self, data: bytes, addr: Tuple[str, int]): def datagram_received(self, data: bytes, addr: Tuple[str, int]):
try: try: