Skip to content

Commit 3a1f70a

Browse files
authored
Add fallback for introducer. (#19333)
* Add fallback for introducer. * Lint. * Lint. * Lint. * Fix test. * Add test. * Lint.:
1 parent 6707961 commit 3a1f70a

File tree

6 files changed

+78
-3
lines changed

6 files changed

+78
-3
lines changed

chia/_tests/simulation/test_simulation.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
from collections.abc import AsyncIterator
66

77
import aiohttp
8+
import dns.rdataclass
9+
import dns.rdatatype
10+
import dns.rdtypes.IN.A
11+
import dns.rdtypes.IN.AAAA
812
import pytest
913
from chia_rs.sized_bytes import bytes32
1014
from chia_rs.sized_ints import uint8, uint16, uint32, uint64
@@ -64,6 +68,17 @@ async def extra_node(self_hostname) -> AsyncIterator[FullNodeAPI | FullNodeSimul
6468
yield service._api
6569

6670

71+
class FakeDNSResolver:
72+
async def resolve(self, qname, rdtype, lifetime):
73+
if rdtype == "A":
74+
record = dns.rdtypes.IN.A.A(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4")
75+
return [record]
76+
elif rdtype == "AAAA":
77+
record = dns.rdtypes.IN.AAAA.AAAA(dns.rdataclass.IN, dns.rdatatype.AAAA, "::1")
78+
return [record]
79+
return []
80+
81+
6782
class TestSimulation:
6883
@pytest.mark.limit_consensus_modes(reason="This test only supports one running at a time.")
6984
@pytest.mark.anyio
@@ -499,3 +514,16 @@ async def test_create_coins_with_invalid_amounts_raises(
499514

500515
with pytest.raises(Exception, match="Coins must have a positive value"):
501516
await full_node_api.create_coins_with_amounts(amounts=amounts, wallet=wallet)
517+
518+
@pytest.mark.limit_consensus_modes(reason="This test only supports one running at a time.")
519+
@pytest.mark.anyio
520+
async def test_introducer_fallback_to_dns(self, simulation):
521+
full_system: FullSystem
522+
full_system, _ = simulation
523+
introducer = full_system.introducer
524+
introducer.introducer.dns_servers = ["127.0.0.1"]
525+
introducer.introducer.resolver = FakeDNSResolver()
526+
peers = await introducer.introducer.get_peers_from_dns(num_peers=10)
527+
assert len(peers) == 2
528+
assert any(peer.host == "1.2.3.4" for peer in peers)
529+
assert any(peer.host == "::1" for peer in peers)

chia/_tests/util/setup_nodes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ async def setup_full_system_inner(
390390
introducer_service = await async_exit_stack.enter_async_context(setup_introducer(shared_b_tools, uint16(0)))
391391
introducer = introducer_service._api
392392
introducer_server = introducer_service._node.server
393+
introducer.introducer.dns_servers = []
393394

394395
# Then start the full node so we can use the port for the farmer and timelord
395396
node_1 = await async_exit_stack.enter_async_context(

chia/introducer/introducer.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
import asyncio
44
import contextlib
55
import logging
6+
import random
67
import time
78
from collections.abc import AsyncIterator
89
from typing import TYPE_CHECKING, Any, ClassVar, Optional, cast
910

10-
from chia_rs.sized_ints import uint64
11+
import dns.asyncresolver
12+
from chia_rs.sized_ints import uint16, uint64
1113

1214
from chia.rpc.rpc_server import StateChangedProtocol, default_get_connections
1315
from chia.server.introducer_peers import VettedPeer
1416
from chia.server.outbound_message import NodeType
1517
from chia.server.server import ChiaServer
1618
from chia.server.ws_connection import WSChiaConnection
19+
from chia.types.peer_info import TimestampedPeerInfo
1720
from chia.util.task_referencer import create_referenced_task
1821

1922

@@ -32,9 +35,12 @@ def server(self) -> ChiaServer:
3235

3336
return self._server
3437

35-
def __init__(self, max_peers_to_send: int, recent_peer_threshold: int):
38+
def __init__(self, max_peers_to_send: int, recent_peer_threshold: int, default_port: int, dns_servers: list[str]):
3639
self.max_peers_to_send = max_peers_to_send
3740
self.recent_peer_threshold = recent_peer_threshold
41+
self.default_port = uint16(default_port)
42+
self.dns_servers = dns_servers
43+
self.resolver = dns.asyncresolver.Resolver()
3844
self._shut_down = False
3945
self._server: Optional[ChiaServer] = None
4046
self.log = logging.getLogger(__name__)
@@ -121,3 +127,24 @@ async def _vetting_loop(self):
121127
peer.vetted = max(peer.vetted + 1, 1)
122128
except Exception as e:
123129
self.log.error(e)
130+
131+
async def get_peers_from_dns(self, num_peers: int) -> list[TimestampedPeerInfo]:
132+
if len(self.dns_servers) == 0:
133+
return []
134+
135+
peers: list[TimestampedPeerInfo] = []
136+
dns_address = random.choice(self.dns_servers)
137+
for rdtype in ("A", "AAAA"):
138+
result = await self.resolver.resolve(qname=dns_address, rdtype=rdtype, lifetime=30)
139+
for ip in result:
140+
peers.append(
141+
TimestampedPeerInfo(
142+
ip.to_text(),
143+
self.default_port,
144+
uint64(0),
145+
)
146+
)
147+
if len(peers) > num_peers:
148+
break
149+
150+
return peers

chia/introducer/introducer_api.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ async def request_peers_introducer(
6565
if len(peers) >= max_peers:
6666
break
6767

68+
if len(peers) < max_peers:
69+
peers_needed = max_peers - len(peers)
70+
self.introducer.log.info(f"Querying dns servers for {peers_needed} peers")
71+
extra_peers = await self.introducer.get_peers_from_dns(peers_needed)
72+
self.introducer.log.info(f"Received {len(extra_peers)} peers from dns server")
73+
peers.extend(extra_peers)
74+
6875
self.introducer.log.info(f"Sending vetted {peers}")
6976

7077
msg = make_msg(ProtocolMessageTypes.respond_peers_introducer, RespondPeersIntroducer(peers))

chia/server/start_introducer.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,18 @@ def create_introducer_service(
3636
if advertised_port is None:
3737
advertised_port = service_config["port"]
3838

39-
node = Introducer(service_config["max_peers_to_send"], service_config["recent_peer_threshold"])
39+
try:
40+
default_port = service_config["network_overrides"]["config"][network_id]["default_full_node_port"]
41+
except KeyError:
42+
raise Exception(f"Specify default_full_node_port for network {network_id}")
43+
44+
dns_servers = service_config.get("dns_servers", [])
45+
if dns_servers == [] and network_id == "mainnet":
46+
dns_servers.append("dns-introducer.chia.net")
47+
48+
node = Introducer(
49+
service_config["max_peers_to_send"], service_config["recent_peer_threshold"], default_port, dns_servers
50+
)
4051
peer_api = IntroducerAPI(node)
4152

4253
return Service(

chia/util/initial-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ introducer:
478478
logging: *logging
479479
network_overrides: *network_overrides
480480
selected_network: *selected_network
481+
dns_servers: *dns_servers
481482

482483
ssl:
483484
public_crt: "config/ssl/full_node/public_full_node.crt"

0 commit comments

Comments
 (0)