Skip to content

Commit

Permalink
docs: add pk records insertion example
Browse files Browse the repository at this point in the history
In the original issue I mention that a lot of external dependencies would be needed to insert records into the IPFS DHT. While this is still true for IPNS-type records, I found that PK-type records can be created with no need for external code. Thus, I decided to try and enhance the already existing example and add the option to insert a PK record there.

Resolves #2263.

Pull-Request: #4567.
  • Loading branch information
pradt2 committed Oct 9, 2023
1 parent 1bfaf1c commit 8114894
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 51 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,11 @@ jobs:
with:
save-if: ${{ github.ref == 'refs/heads/master' }}

- name: Run ipfs-kad example
run: cd ./examples/ipfs-kad/ && RUST_LOG=libp2p_swarm=debug,libp2p_kad=trace,libp2p_tcp=debug cargo run
- name: Run ipfs-kad example - get peers
run: cd ./examples/ipfs-kad/ && RUST_LOG=libp2p_swarm=debug,libp2p_kad=trace,libp2p_tcp=debug cargo run -- get-peers

- name: Run ipfs-kad example - put PK record
run: cd ./examples/ipfs-kad/ && RUST_LOG=libp2p_swarm=debug,libp2p_kad=trace,libp2p_tcp=debug cargo run -- put-pk-record

examples:
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions examples/ipfs-kad/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ license = "MIT"
[dependencies]
tokio = { version = "1.12", features = ["rt-multi-thread", "macros"] }
async-trait = "0.1"
clap = { version = "4.3.23", features = ["derive"] }
env_logger = "0.10"
futures = "0.3.28"
anyhow = "1.0.75"
libp2p = { path = "../../libp2p", features = [ "tokio", "dns", "kad", "noise", "tcp", "websocket", "yamux", "rsa"] }

[lints]
Expand Down
57 changes: 49 additions & 8 deletions examples/ipfs-kad/README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
## Description

This example showcases the usage of **libp2p** to interact with the Kademlia protocol on the IPFS network.
The code demonstrates how to perform Kademlia queries to find the closest peers to a specific peer ID.
By running this example, users can gain a better understanding of how the Kademlia protocol operates and performs queries on the IPFS network.
The code demonstrates how to:
- perform Kademlia queries to find the closest peers to a specific peer ID
- insert PK records into the Kademlia DHT

By running this example, users can gain a better understanding of how the Kademlia protocol operates and performs operations on the IPFS network.

## Usage

The example code demonstrates how to perform Kademlia queries on the IPFS network using the Rust P2P Library.

### Getting closes peers

By specifying a peer ID as a parameter, the code will search for the closest peers to the given peer ID.

### Parameters
#### Parameters

Run the example code:

```sh
cargo run [PEER_ID]
cargo run -- get-peers [PEER_ID]
```

Replace `[PEER_ID]` with the base58-encoded peer ID you want to search for.
If no peer ID is provided, a random peer ID will be generated.

## Example Output
#### Example Output

Upon running the example code, you will see the output in the console.
The output will display the result of the Kademlia query, including the closest peers to the specified peer ID.

### Successful Query Output
#### Successful Query Output

If the Kademlia query successfully finds closest peers, the output will be:

Expand All @@ -34,7 +40,7 @@ Searching for the closest peers to [PEER_ID]
Query finished with closest peers: [peer1, peer2, peer3]
```

### Failed Query Output
#### Failed Query Output

If the Kademlia query times out or there are no reachable peers, the output will indicate the failure:

Expand All @@ -43,8 +49,43 @@ Searching for the closest peers to [PEER_ID]
Query finished with no closest peers.
```

### Inserting PK records into the DHT

By specifying `put-pk-record` as a subcommand, the code will insert the generated public key as a PK record into the DHT.

#### Parameters

Run the example code:

```sh
cargo run -- put-pk-record
```

#### Example Output

Upon running the example code, you will see the output in the console.
The output will display the result of the Kademlia operation.

#### Successful Operation Output

If the Kademlia query successfully finds closest peers, the output will be:

```sh
Putting PK record into the DHT
Successfully inserted the PK record
```

#### Failed Query Output

If the Kademlia operation times out or there are no reachable peers, the output will indicate the failure:

```sh
Putting PK record into the DHT
Failed to insert the PK record
```

## Conclusion

In conclusion, this example provides a practical demonstration of using the Rust P2P Library to interact with the Kademlia protocol on the IPFS network.
By examining the code and running the example, users can gain insights into the inner workings of Kademlia and how it performs queries to find the closest peers.
By examining the code and running the example, users can gain insights into the inner workings of Kademlia and how it performs various basic actions like getting the closes peers or inserting records into the DHT.
This knowledge can be valuable when developing peer-to-peer applications or understanding decentralized networks.
129 changes: 88 additions & 41 deletions examples/ipfs-kad/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@

#![doc = include_str!("../README.md")]

use std::num::NonZeroUsize;
use std::ops::Add;
use std::time::{Duration, Instant};

use anyhow::{bail, Result};
use clap::Parser;
use futures::StreamExt;
use libp2p::kad;
use libp2p::kad::record::store::MemoryStore;
use libp2p::{
identity,
bytes::BufMut,
identity, kad,
swarm::{SwarmBuilder, SwarmEvent},
tokio_development_transport, PeerId,
};
use std::{env, error::Error, time::Duration};

const BOOTNODES: [&str; 4] = [
"QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
Expand All @@ -38,22 +42,22 @@ const BOOTNODES: [&str; 4] = [
];

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
async fn main() -> Result<()> {
env_logger::init();

// Create a random key for ourselves.
let local_key = identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_key.public());

// Set up a an encrypted DNS-enabled TCP Transport over the yamux protocol
let transport = tokio_development_transport(local_key)?;
let transport = tokio_development_transport(local_key.clone())?;

// Create a swarm to manage peers and events.
let mut swarm = {
// Create a Kademlia behaviour.
let mut cfg = kad::Config::default();
cfg.set_query_timeout(Duration::from_secs(5 * 60));
let store = MemoryStore::new(local_peer_id);
let store = kad::store::MemoryStore::new(local_peer_id);
let mut behaviour = kad::Behaviour::with_config(local_peer_id, store, cfg);

// Add the bootnodes to the local routing table. `libp2p-dns` built
Expand All @@ -66,47 +70,90 @@ async fn main() -> Result<(), Box<dyn Error>> {
SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build()
};

// Order Kademlia to search for a peer.
let to_search = env::args()
.nth(1)
.map(|p| p.parse())
.transpose()?
.unwrap_or_else(PeerId::random);
let cli_opt = Opt::parse();

match cli_opt.argument {
CliArgument::GetPeers { peer_id } => {
let peer_id = peer_id.unwrap_or(PeerId::random());
println!("Searching for the closest peers to {peer_id}");
swarm.behaviour_mut().get_closest_peers(peer_id);
}
CliArgument::PutPkRecord {} => {
println!("Putting PK record into the DHT");

let mut pk_record_key = vec![];
pk_record_key.put_slice("/pk/".as_bytes());
pk_record_key.put_slice(local_peer_id.to_bytes().as_slice());

println!("Searching for the closest peers to {to_search}");
swarm.behaviour_mut().get_closest_peers(to_search);
let mut pk_record =
kad::Record::new(pk_record_key, local_key.public().encode_protobuf());
pk_record.publisher = Some(local_peer_id);
pk_record.expires = Some(Instant::now().add(Duration::from_secs(60)));

swarm
.behaviour_mut()
.put_record(pk_record, kad::Quorum::N(NonZeroUsize::new(3).unwrap()))?;
}
}

loop {
let event = swarm.select_next_some().await;
if let SwarmEvent::Behaviour(kad::Event::OutboundQueryProgressed {
result: kad::QueryResult::GetClosestPeers(result),
..
}) = event
{
match result {
Ok(ok) => {
if !ok.peers.is_empty() {
println!("Query finished with closest peers: {:#?}", ok.peers)
} else {
// The example is considered failed as there
// should always be at least 1 reachable peer.
println!("Query finished with no closest peers.")
}
}
Err(kad::GetClosestPeersError::Timeout { peers, .. }) => {
if !peers.is_empty() {
println!("Query timed out with closest peers: {peers:#?}")
} else {
// The example is considered failed as there
// should always be at least 1 reachable peer.
println!("Query timed out with no closest peers.");
}

match event {
SwarmEvent::Behaviour(kad::Event::OutboundQueryProgressed {
result: kad::QueryResult::GetClosestPeers(Ok(ok)),
..
}) => {
// The example is considered failed as there
// should always be at least 1 reachable peer.
if ok.peers.is_empty() {
bail!("Query finished with no closest peers.")
}
};

break;
println!("Query finished with closest peers: {:#?}", ok.peers);

return Ok(());
}
SwarmEvent::Behaviour(kad::Event::OutboundQueryProgressed {
result:
kad::QueryResult::GetClosestPeers(Err(kad::GetClosestPeersError::Timeout {
..
})),
..
}) => {
bail!("Query for closest peers timed out")
}
SwarmEvent::Behaviour(kad::Event::OutboundQueryProgressed {
result: kad::QueryResult::PutRecord(Ok(_)),
..
}) => {
println!("Successfully inserted the PK record");

return Ok(());
}
SwarmEvent::Behaviour(kad::Event::OutboundQueryProgressed {
result: kad::QueryResult::PutRecord(Err(err)),
..
}) => {
bail!(anyhow::Error::new(err).context("Failed to insert the PK record"));
}
_ => {}
}
}
}

#[derive(Parser, Debug)]
#[clap(name = "libp2p Kademlia DHT example")]
struct Opt {
#[clap(subcommand)]
argument: CliArgument,
}

Ok(())
#[derive(Debug, Parser)]
enum CliArgument {
GetPeers {
#[clap(long)]
peer_id: Option<PeerId>,
},
PutPkRecord {},
}

0 comments on commit 8114894

Please sign in to comment.