import asyncio
import os
from test_framework.environment import (
testing_tcp_ssl,
testing_websocket_ssl,
testing_https,
)
from test_framework.dummyelectrumserver import Protocol, dummy_electrum
from test_framework.util import assert_equal, wait_for
from test_framework.electrumutil import (
ElectrumTestFramework,
)
os.environ["ROSTRUM_ANNOUNCE"] = "1"
class FeatureDiscoveryTest(ElectrumTestFramework):
async def run_test(self):
self.nodes[0].generate(1)
await self.test_node_accepts()
await self.test_switch_to_hostname()
await self.test_invalid_ports()
await self.test_tcp_and_ssl()
await self.remote_announces_to_us()
async def announce_ourself(self, cli, dummy, features):
resp = await cli.call("server.add_peer", features)
assert resp, "remote server rejected us"
self.info("Remote peer accepted our add request")
self.info("waiting for server to connect to us...")
await wait_for(60, lambda: dummy.got_remote_connection)
self.info("remote peer connected")
async def has_accepted_us():
res = await cli.call("server.peers.subscribe")
return len(res) > 0
await wait_for(10, has_accepted_us)
self.info("remote server added us")
async def test_node_accepts(self):
async with dummy_electrum() as (dummy, cli):
await self.announce_ourself(cli, dummy, dummy.features())
res = await cli.call("server.peers.subscribe")
ip_addr, hostname, meta = res.pop()
assert_equal("127.0.0.1", ip_addr)
assert_equal("localhost", hostname)
assert f"v{dummy.protocol_max}" in meta
assert f"t{dummy.tcp_port}" in meta
async def test_switch_to_hostname(self):
await self.restart_node(0)
async with dummy_electrum() as (dummy, cli):
features = dummy.features()
features["hosts"] = {"127.0.0.1": {"tcp_port": dummy.tcp_port}}
await self.announce_ourself(cli, dummy, features)
res = await cli.call("server.peers.subscribe")
ip_addr, hostname, meta = res.pop()
assert_equal("127.0.0.1", ip_addr)
assert_equal("localhost", hostname)
assert f"v{dummy.protocol_max}" in meta, meta
assert f"t{dummy.tcp_port}" in meta, meta
async def test_invalid_ports(self):
await self.restart_node(0)
async with dummy_electrum(protocols=[Protocol.TCP]) as (dummy, cli):
features = dummy.features()
features["hosts"] = {
"127.0.0.1": {"ssl_port": 12345, "tcp_port": dummy.tcp_port}
}
await self.announce_ourself(cli, dummy, features)
res = await cli.call("server.peers.subscribe")
_, _, meta = res.pop()
assert f"t{dummy.tcp_port}" in meta
assert not "s12345" in meta
await self.restart_node(0)
async with dummy_electrum(protocols=[Protocol.SSL]) as (dummy, cli):
features = dummy.features()
features["hosts"] = {
"127.0.0.1": {"ssl_port": dummy.ssl_port, "tcp_port": 12345}
}
await self.announce_ourself(cli, dummy, features)
res = await cli.call("server.peers.subscribe")
_, _, meta = res.pop()
assert f"s{dummy.ssl_port}" in meta
assert not "t12345" in meta
async def test_tcp_and_ssl(self):
await self.restart_node(0)
async with dummy_electrum(protocols=[Protocol.TCP, Protocol.SSL]) as (
dummy,
cli,
):
features = dummy.features()
features["hosts"] = {
"127.0.0.1": {"ssl_port": dummy.ssl_port, "tcp_port": dummy.tcp_port}
}
await self.announce_ourself(cli, dummy, features)
res = await cli.call("server.peers.subscribe")
_, _, meta = res.pop()
assert f"s{dummy.ssl_port}" in meta
assert f"t{dummy.tcp_port}" in meta
await self.restart_node(0)
async with dummy_electrum(protocols=[Protocol.TCP, Protocol.SSL]) as (
dummy,
cli,
):
features = dummy.features()
features["hosts"] = {"127.0.0.1": {"tcp_port": dummy.tcp_port}}
await self.announce_ourself(cli, dummy, features)
res = await cli.call("server.peers.subscribe")
_, _, meta = res.pop()
assert f"s{dummy.ssl_port}" in meta
assert f"t{dummy.tcp_port}" in meta
await self.restart_node(0)
async with dummy_electrum(protocols=[Protocol.TCP, Protocol.SSL]) as (
dummy,
cli,
):
features = dummy.features()
features["hosts"] = {
"127.0.0.1": {"ssl_port": dummy.ssl_port, "tcp_port": 12345}
}
await self.announce_ourself(cli, dummy, features)
res = await cli.call("server.peers.subscribe")
_, _, meta = res.pop()
assert f"s{dummy.ssl_port}" in meta
assert f"t{dummy.tcp_port}" in meta
async def remote_announces_to_us(self):
await self.restart_node(0)
async with dummy_electrum() as (dummy, cli):
await self.announce_ourself(cli, dummy, dummy.features())
await wait_for(60, lambda: len(dummy.peers) > 0)
assert_equal(1, len(dummy.peers))
announcement = dummy.peers.pop()
if testing_tcp_ssl() or testing_websocket_ssl() or testing_https():
assert "example.org" in announcement["hosts"], announcement
else:
assert "localhost" in announcement["hosts"], announcement
assert "genesis_hash" in announcement
assert "hash_function" in announcement
assert "protocol_max" in announcement
assert "hosts" in announcement
assert "server_version" in announcement
await wait_for(30, lambda: dummy.requested_peers)
if __name__ == "__main__":
asyncio.run(FeatureDiscoveryTest().main())