链眼社区:专注于区块链安全,区块链数据分析, 区块链信息整合,区块链技术服务和区块链技术咨询。

rust-libp2p 入门教程
扫地僧
2022-10-08 21:35:30

本教程旨在为新手提供如何使用 Rust libp2p 实现的动手概述。刚接触 Rust 的人可能想要开始使用 Rust本身,然后再深入了解所有的网络乐趣。这个库大量使用了异步 Rust。如果你不熟悉这个概念,Rust async-book应该很有用。刚接触 libp2p 的人可能更愿意先在libp2p.io上获得一般概述 ,尽管本教程不需要 libp2p 知识。

我们将构建一个小型ping克隆,向对等方发送 ping,期待 pong 作为响应。

一.脚手架

让我们从

  1. 更新到最新的 Rust 工具链,例如:rustup update
  2. 创建一个新的箱子:cargo init p2p_tutorial
  3. 在文件中添加libp2p以及futures依赖 Cargo.toml项。当前的 crate 版本可以在 crates.io找到。我们还将包含async-std“属性”功能以允许async main. 在撰写本文时,我们有:
[package]
name = "p2p_tutorial"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
libp2p = "0.43.0"
futures = "0.3.21"
async-std = { version = "1.10.0", features = ["attributes"] }

二. 网络身份

有了所有的脚手架,我们就可以深入了解 libp2p 的细节。首先,我们需要为我们的本地节点创建一个网络身份async fn main(),并用允许的属性进行main注释async。libp2p 中的身份是通过公钥/私钥对处理的。PeerId节点通过从它们的公钥派生的它们相互识别。现在,将 main.rs 的内容替换为:

use libp2p::{identity, PeerId};
use std::error::Error;

#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let local_key = identity::Keypair::generate_ed25519();
    let local_peer_id = PeerId::from(local_key.public());
    println!("Local peer id: {:?}", local_peer_id);

    Ok(())
}

继续构建并运行上面的代码:cargo run. PeerId应该显示一个唯一的 。

三.运输

接下来我们需要构建一个传输。libp2p 中的传输提供面向连接的通信通道(例如 TCP)以及在认证和加密协议等基础上的升级。从技术上讲,libp2p 传输是实现该Transport特征的任何东西。

我们没有为本教程自己构建传输,而是使用development_transport 创建 TCP 传输的便捷函数来noise进行身份验证加密。

此外,development_transport构建多路复用传输,从而多个逻辑子流可以在同一底层 (TCP) 连接上共存。有关子流多路复用的更多详细信息,请查看 crate::core::muxing和yamux。

use libp2p::{identity, PeerId};
use std::error::Error;

#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let local_key = identity::Keypair::generate_ed25519();
    let local_peer_id = PeerId::from(local_key.public());
    println!("Local peer id: {:?}", local_peer_id);

    let transport = libp2p::development_transport(local_key).await?;

    Ok(())
}

四. 网络行为

现在是时候看看 rust-libp2p 的另一个核心特性: NetworkBehaviour. 前面介绍的 traitTransport 定义了如何在网络上发送字节,而 aNetworkBehaviour定义 了在网络上发送的字节。

为了使这一点更具体,让我们看一下NetworkBehaviourtrait 的简单实现:. 正如您可能已经猜到的那样,类似于老式的 网络工具,libp2p向对等方发送 ping 并期望依次收到 pong。不关心ping 和 pong 消息是如何在 网络上发送的,无论它们是通过 TCP 发送的,它们是通过噪声加密还是 纯文本加密。它只关心网络上发送了什么消息。Ping NetworkBehaviourpingPingPing NetworkBehaviour

这两个特征使我们能够清楚地区分如何发送字节和发送Transport什么字节。NetworkBehaviour

考虑到上述情况,让我们扩展我们的示例, 在最后创建一个:Ping NetworkBehaviour

use libp2p::{identity, PeerId};
use libp2p::ping::{Ping, PingConfig};
use std::error::Error;

#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let local_key = identity::Keypair::generate_ed25519();
    let local_peer_id = PeerId::from(local_key.public());
    println!("Local peer id: {:?}", local_peer_id);

    let transport = libp2p::development_transport(local_key).await?;

    // Create a ping network behaviour.
    //
    // For illustrative purposes, the ping protocol is configured to
    // keep the connection alive, so a continuous sequence of pings
    // can be observed.
    let behaviour = Ping::new(PingConfig::new().with_keep_alive(true));

    Ok(())
}

五. swarm

现在我们有了 aTransport和 a NetworkBehaviour,我们需要一些东西来连接两者,让两者都能取得进展。这项工作由Swarm. 简而言之,aSwarm驱动 a Transport和 aNetworkBehaviour向前,将命令从 the 传递 NetworkBehaviour到 theTransport以及从 the Transport传递事件NetworkBehaviour。

use libp2p::{identity, PeerId};
use libp2p::ping::{Ping, PingConfig};
use libp2p::swarm::Swarm;
use std::error::Error;

#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let local_key = identity::Keypair::generate_ed25519();
    let local_peer_id = PeerId::from(local_key.public());
    println!("Local peer id: {:?}", local_peer_id);

    let transport = libp2p::development_transport(local_key).await?;

    // Create a ping network behaviour.
    //
    // For illustrative purposes, the ping protocol is configured to
    // keep the connection alive, so a continuous sequence of pings
    // can be observed.
    let behaviour = Ping::new(PingConfig::new().with_keep_alive(true));

    let mut swarm = Swarm::new(transport, behaviour, local_peer_id);

    Ok(())
}

六. 多地址

就位后Swarm,我们都准备好监听传入的连接。我们只需要将地址传递给Swarm,就像 for 一样 std::net::TcpListener::bind。但是,我们没有传递 IP 地址,而是传递了一个Multiaddrlibp2p 的另一个核心概念,值得一看。

AMultiaddr是一个自描述的网络地址和协议栈,用于建立到对等点的连接。Multiaddr可以在 docs.libp2p.io/concepts/addressing 及其规范存储库 github.com/multiformats/multiaddr找到一个很好的介绍 。

让我们的本地节点监听一个新的套接字。此套接字同时侦听多个网络接口。对于每个网络接口,都会创建一个新的侦听地址。随着接口变得可用或不可用,这些可能会随着时间而改变。例如,在我们的 TCP 传输的情况下,它可能(除其他外)侦听环回接口 (localhost)/ip4/127.0.0.1/tcp/24915以及本地网络/ip4/192.168.178.25/tcp/24915。

此外,如果在 CLI 上提供,让我们指示我们的本地节点拨打远程对等点。

use libp2p::{identity, Multiaddr, PeerId};
use libp2p::ping::{Ping, PingConfig};
use libp2p::swarm::{Swarm, dial_opts::DialOpts};
use std::error::Error;

#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let local_key = identity::Keypair::generate_ed25519();
    let local_peer_id = PeerId::from(local_key.public());
    println!("Local peer id: {:?}", local_peer_id);

    let transport = libp2p::development_transport(local_key).await?;

    // Create a ping network behaviour.
    //
    // For illustrative purposes, the ping protocol is configured to
    // keep the connection alive, so a continuous sequence of pings
    // can be observed.
    let behaviour = Ping::new(PingConfig::new().with_keep_alive(true));

    let mut swarm = Swarm::new(transport, behaviour, local_peer_id);

    // Tell the swarm to listen on all interfaces and a random, OS-assigned
    // port.
    swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;

    // Dial the peer identified by the multi-address given as the second
    // command-line argument, if any.
    if let Some(addr) = std::env::args().nth(1) {
        let remote: Multiaddr = addr.parse()?;
        swarm.dial(remote)?;
        println!("Dialed {}", addr)
    }

    Ok(())
}

七. 不断轮询 Swarm

我们现在一切就绪。最后一步是驱动Swarm循环,允许它侦听传入连接并建立传出连接,以防我们在 CLI 上指定地址。

use futures::prelude::*;
use libp2p::ping::{Ping, PingConfig};
use libp2p::swarm::{Swarm, SwarmEvent, dial_opts::DialOpts};
use libp2p::{identity, Multiaddr, PeerId};
use std::error::Error;

#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let local_key = identity::Keypair::generate_ed25519();
    let local_peer_id = PeerId::from(local_key.public());
    println!("Local peer id: {:?}", local_peer_id);

    let transport = libp2p::development_transport(local_key).await?;

    // Create a ping network behaviour.
    //
    // For illustrative purposes, the ping protocol is configured to
    // keep the connection alive, so a continuous sequence of pings
    // can be observed.
    let behaviour = Ping::new(PingConfig::new().with_keep_alive(true));

    let mut swarm = Swarm::new(transport, behaviour, local_peer_id);

    // Tell the swarm to listen on all interfaces and a random, OS-assigned
    // port.
    swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;

    // Dial the peer identified by the multi-address given as the second
    // command-line argument, if any.
    if let Some(addr) = std::env::args().nth(1) {
        let remote: Multiaddr = addr.parse()?;
        swarm.dial(remote)?;
        println!("Dialed {}", addr)
    }

    loop {
        match swarm.select_next_some().await {
            SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {:?}", address),
            SwarmEvent::Behaviour(event) => println!("{:?}", event),
            _ => {}
        }
    }

}

八. 运行两个节点

为方便起见,上面创建的示例也在 examples/ping.rs. 因此,您可以从教程中创建的您自己的项目中运行以下命令,也可以从 rust-libp2p 存储库的根目录运行以下命令。请注意,在前一种情况下,您需要忽略该--example ping参数。

你需要两个终端。在第一个终端窗口中运行:

cargo run --example ping

它将打印 PeerId 和新的监听地址,例如

Local peer id: PeerId("12D3KooWT1As4mwh3KYBnNTw9bSrRbYQGJTm9SSte82JSumqgCQG")
Listening on "/ip4/127.0.0.1/tcp/24915"
Listening on "/ip4/192.168.178.25/tcp/24915"
Listening on "/ip4/172.17.0.1/tcp/24915"
Listening on "/ip6/::1/tcp/24915"

在第二个终端窗口中,使用以下命令启动示例的新实例:

cargo run --example ping -- /ip4/127.0.0.1/tcp/24915

注意:Multiaddr最后是Multiaddr终端窗口一中较早打印的内容之一。两个对等点必须位于与地址关联的同一网络中。在我们的例子中,可以使用任何打印的地址,因为两个对等点都在同一设备上运行。

两个节点将建立连接,每 15 秒相互发送一次 ping 和 pong 消息。

合作伙伴