Skip to content

WIP: Ping interop in py-libp2p and rust-libp2p #620

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions interop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
These commands are to be run in `./interop/exec`

## Redis

```bash
docker run -p 6379:6379 -it redis:latest
```

## Listener

```bash
transport=tcp ip=0.0.0.0 is_dialer=false redis_addr=6379 test_timeout_seconds=180 security=insecure muxer=mplex python3 native_ping.py
```

## Dialer

```bash
transport=tcp ip=0.0.0.0 is_dialer=true port=8001 redis_addr=6379 port=8001 test_timeout_seconds=180 security=insecure muxer=mplex python3 native_ping.py
```
Empty file added interop/__init__.py
Empty file.
157 changes: 157 additions & 0 deletions interop/arch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from dataclasses import (
dataclass,
)
import logging

from cryptography.hazmat.primitives.asymmetric import (
x25519,
)
import multiaddr
import redis
import trio

from libp2p import (
new_host,
)
from libp2p.crypto.keys import (
KeyPair,
)
from libp2p.crypto.rsa import (
create_new_key_pair,
)
from libp2p.custom_types import (
TProtocol,
)
from libp2p.security.insecure.transport import (
PLAINTEXT_PROTOCOL_ID,
InsecureTransport,
)
from libp2p.security.noise.transport import (
Transport as NoiseTransport,
)
import libp2p.security.secio.transport as secio
from libp2p.stream_muxer.mplex.mplex import (
MPLEX_PROTOCOL_ID,
Mplex,
)
from libp2p.stream_muxer.yamux.yamux import (
PROTOCOL_ID as YAMUX_PROTOCOL_ID,
Yamux,
)

# Configure detailed logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.StreamHandler(),
logging.FileHandler("ping_debug.log", mode="w", encoding="utf-8"),
],
)


def generate_new_rsa_identity() -> KeyPair:
return create_new_key_pair()


def create_noise_keypair():
"""Create a Noise protocol keypair for secure communication"""
try:
x25519_private_key = x25519.X25519PrivateKey.generate()

class NoisePrivateKey:
def __init__(self, key):
self._key = key

def to_bytes(self):
return self._key.private_bytes_raw()

def public_key(self):
return NoisePublicKey(self._key.public_key())

def get_public_key(self):
return NoisePublicKey(self._key.public_key())

class NoisePublicKey:
def __init__(self, key):
self._key = key

def to_bytes(self):
return self._key.public_bytes_raw()

return NoisePrivateKey(x25519_private_key)
except Exception as e:
logging.error(f"Failed to create Noise keypair: {e}")
return None


async def build_host(transport: str, ip: str, port: str, sec_protocol: str, muxer: str):
match (sec_protocol, muxer):
case ("insecure", "mplex"):
key_pair = create_new_key_pair()
host = new_host(
key_pair,
{TProtocol(MPLEX_PROTOCOL_ID): Mplex},
{
TProtocol(PLAINTEXT_PROTOCOL_ID): InsecureTransport(key_pair),
TProtocol(secio.ID): secio.Transport(key_pair),
},
)
muladdr = multiaddr.Multiaddr(f"/ip4/{ip}/tcp/{port}")
return (host, muladdr)
case ("insecure", "yamux"):
key_pair = create_new_key_pair()
host = new_host(
key_pair,
{TProtocol(YAMUX_PROTOCOL_ID): Yamux},
{
TProtocol(PLAINTEXT_PROTOCOL_ID): InsecureTransport(key_pair),
TProtocol(secio.ID): secio.Transport(key_pair),
},
)
muladdr = multiaddr.Multiaddr(f"/ip4/{ip}/tcp/{port}")
return (host, muladdr)
case ("noise", "yamux"):
key_pair = generate_new_rsa_identity()
logging.debug("Generated RSA keypair")

noise_privkey = create_noise_keypair()
if not noise_privkey:
print("[ERROR] Failed to create Noise keypair")
return 1
logging.debug("Generated Noise keypair")

noise_transport = NoiseTransport(key_pair, noise_privkey)
logging.debug(f"Noise transport initialized: {noise_transport}")
sec_opt = {TProtocol("/noise"): noise_transport}
muxer_opt = {TProtocol(YAMUX_PROTOCOL_ID): Yamux}

logging.info(f"Using muxer: {muxer_opt}")

host = new_host(key_pair=key_pair, sec_opt=sec_opt, muxer_opt=muxer_opt)
muladdr = multiaddr.Multiaddr(f"/ip4/{ip}/tcp/{port}")
return (host, muladdr)
case _:
raise ValueError("Protocols not supported")


@dataclass
class RedisClient:
client: redis.Redis

def brpop(self, key: str, timeout: float) -> list[str]:
result = self.client.brpop([key], timeout)
return [result[1]] if result else []

def rpush(self, key: str, value: str) -> None:
self.client.rpush(key, value)


async def main():
client = RedisClient(redis.Redis(host="localhost", port=6379, db=0))
client.rpush("test", "hello")
print(client.blpop("test", timeout=5))


if __name__ == "__main__":
trio.run(main)
54 changes: 54 additions & 0 deletions interop/exec/config/mod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from dataclasses import (
dataclass,
)
import os


def str_to_bool(val: str) -> bool:
return val.lower() in ("true", "1")


class ConfigError(Exception):
"""Raised when the required environment variables are missing or invalid"""


@dataclass
class Config:
transport: str
sec_protocol: str | None
muxer: str | None
ip: str
is_dialer: bool
test_timeout: int
redis_addr: str
port: str

@classmethod
def from_env(cls) -> "Config":
try:
transport = os.environ["transport"]
ip = os.environ["ip"]
except KeyError as e:
raise ConfigError(f"{e.args[0]} env variable not set") from None

try:
is_dialer = str_to_bool(os.environ.get("is_dialer", "true"))
test_timeout = int(os.environ.get("test_timeout", "180"))
except ValueError as e:
raise ConfigError(f"Invalid value in env: {e}") from None

redis_addr = os.environ.get("redis_addr", 6379)
sec_protocol = os.environ.get("security")
muxer = os.environ.get("muxer")
port = os.environ.get("port", "8000")

return cls(
transport=transport,
sec_protocol=sec_protocol,
muxer=muxer,
ip=ip,
is_dialer=is_dialer,
test_timeout=test_timeout,
redis_addr=redis_addr,
port=port,
)
33 changes: 33 additions & 0 deletions interop/exec/native_ping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import trio

from interop.exec.config.mod import (
Config,
ConfigError,
)
from interop.lib import (
run_test,
)


async def main() -> None:
try:
config = Config.from_env()
except ConfigError as e:
print(f"Config error: {e}")
return

# Uncomment and implement when ready
_ = await run_test(
config.transport,
config.ip,
config.port,
config.is_dialer,
config.test_timeout,
config.redis_addr,
config.sec_protocol,
config.muxer,
)


if __name__ == "__main__":
trio.run(main)
Loading
Loading