From 46d7e5bb9cb80a74d79f858a6c68a896c7e9ca83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florkiewicz?= Date: Tue, 12 Mar 2024 23:15:13 +0100 Subject: [PATCH] chore: Add doc requirement to CI, add missing docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: MikoĊ‚aj Florkiewicz --- .github/workflows/ci.yml | 10 ++- examples/node.rs | 173 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 24 +++++- src/message.rs | 3 - src/multihasher.rs | 13 +++ src/utils.rs | 2 + 6 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 examples/node.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c67eba..0240744 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,8 +12,16 @@ jobs: steps: - uses: actions/checkout@v1 - name: Run clippy - run: cargo clippy --all --all-targets -- -D warnings + run: cargo clippy --all --all-targets -- -D warnings -D missing-docs + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Run rustdoc check + env: + RUSTDOCFLAGS: -D warnings + run: cargo doc fmt: runs-on: ubuntu-latest diff --git a/examples/node.rs b/examples/node.rs new file mode 100644 index 0000000..997f868 --- /dev/null +++ b/examples/node.rs @@ -0,0 +1,173 @@ +//! Example bitswap node implementation allowing with basic interaction over cli +//! +//! It shows off example way to setup beetswap behaviour with a libp2p swarm and then either +//! connecting to another node and/or listening for incoming connections. In both cases both +//! the server and the client parts of the bitswap are running. +//! +//! Example invocations: +//! +//! Listen on port `9898` and serve single block with contents "12345" +//! ```sh +//! cargo run --example=node -- -l 9898 --preload-blockstore-string 12345 +//! ``` +//! +//! Connect to `10.0.0.101` on port `9898` and ask for CID `bafkreic...` (it's a CID of "12345" string above). You can specify multiple CIDs at once. +//! ```sh +//! cargo run --example=node -- -p /ip4/10.0.0.101/tcp/9898 bafkreiczsrdrvoybcevpzqmblh3my5fu6ui3tgag3jm3hsxvvhaxhswpyu +//! ``` +//! +//! Listen on port `9898` and requests provided CID from them until it gets correct response with +//! data +//! ```sh +//! cargo run --example=node -- -l 9898 bafkreiczsrdrvoybcevpzqmblh3my5fu6ui3tgag3jm3hsxvvhaxhswpyu +//! ``` +use std::collections::HashMap; +use std::time::Duration; + +use anyhow::Result; +use blockstore::{ + block::{Block, CidError}, + Blockstore, InMemoryBlockstore, +}; +use cid::Cid; +use clap::Parser; +use libp2p::{ + futures::StreamExt, identify, swarm::NetworkBehaviour, swarm::SwarmEvent, tcp, Multiaddr, + SwarmBuilder, +}; +use multihash_codetable::{Code, MultihashDigest}; +use tracing::{debug, info}; + +const MAX_MULTIHASH_LENGHT: usize = 64; +const RAW_CODEC: u64 = 0x55; + +#[derive(Debug, Parser)] +struct Args { + /// Peers to connect to + #[arg(short, long = "peer")] + pub(crate) peers: Vec, + + /// CIDs to request + pub(crate) cids: Vec, + + /// Listen on provided port + #[arg(short, long = "listen")] + pub(crate) listen_port: Option, + + /// Load provided string into blockstore on start + #[arg(long)] + pub(crate) preload_blockstore_string: Vec, +} + +#[derive(NetworkBehaviour)] +struct Behaviour { + identify: identify::Behaviour, + bitswap: beetswap::Behaviour>, +} + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { + let args = Args::parse(); + + let _guard = init_tracing(); + + let store = InMemoryBlockstore::new(); + for preload_string in args.preload_blockstore_string { + let block = StringBlock(preload_string); + let cid = block.cid()?; + info!("inserted {cid} with content '{}'", block.0); + store.put_keyed(&cid, block.data()).await?; + } + + let mut swarm = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + libp2p_noise::Config::new, + libp2p_yamux::Config::default, + )? + .with_behaviour(|key| Behaviour { + identify: identify::Behaviour::new(identify::Config::new( + "/ipfs/id/1.0.0".to_string(), + key.public(), + )), + bitswap: beetswap::Behaviour::new(store), + })? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) + .build(); + + if let Some(port) = args.listen_port { + swarm.listen_on(format!("/ip4/0.0.0.0/tcp/{port}").parse()?)?; + } + + for peer in args.peers { + swarm.dial(peer)?; + } + + let mut queries = HashMap::new(); + for cid in args.cids { + let query_id = swarm.behaviour_mut().bitswap.get(&cid); + queries.insert(query_id, cid); + info!("requested cid {cid}: {query_id:?}"); + } + + loop { + match swarm.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => debug!("Listening on {address:?}"), + // Prints peer id identify info is being sent to. + SwarmEvent::Behaviour(BehaviourEvent::Identify(identify)) => match identify { + identify::Event::Sent { peer_id, .. } => { + info!("Sent identify info to {peer_id:?}"); + } + identify::Event::Received { info, .. } => { + info!("Received {info:?}") + } + _ => (), + }, + SwarmEvent::Behaviour(BehaviourEvent::Bitswap(bitswap)) => match bitswap { + beetswap::Event::GetQueryResponse { query_id, data } => { + let cid = queries.get(&query_id).expect("unknown cid received"); + info!("received response for {cid:?}: {data:?}"); + } + beetswap::Event::GetQueryError { query_id, error } => { + let cid = queries.get(&query_id).expect("unknown cid received"); + info!("received error for {cid:?}: {error}"); + } + }, + _ => (), + } + } +} + +struct StringBlock(pub String); + +impl Block<64> for StringBlock { + fn cid(&self) -> Result { + let hash = Code::Sha2_256.digest(self.0.as_ref()); + Ok(Cid::new_v1(RAW_CODEC, hash)) + } + + fn data(&self) -> &[u8] { + self.0.as_ref() + } +} + +fn init_tracing() -> tracing_appender::non_blocking::WorkerGuard { + let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stdout()); + + let filter = tracing_subscriber::EnvFilter::builder() + .with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into()) + .from_env_lossy(); + + tracing_subscriber::fmt() + .event_format( + tracing_subscriber::fmt::format() + .with_file(true) + .with_line_number(true), + ) + .with_env_filter(filter) + .with_writer(non_blocking) + .init(); + + guard +} diff --git a/src/lib.rs b/src/lib.rs index ca3087f..4bdcafb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +#![cfg_attr(docs_rs, feature(doc_cfg))] +#![doc = include_str!("../README.md")] + use std::sync::{Arc, Mutex}; use std::task::{Context, Poll}; @@ -52,19 +55,35 @@ where /// Event produced by [`Behaviour`]. #[derive(Debug)] pub enum Event { - GetQueryResponse { query_id: QueryId, data: Vec }, - GetQueryError { query_id: QueryId, error: Error }, + /// Requested block has been successfuly retrieved + GetQueryResponse { + /// Id of the query, returned by [`Behaviour::get`] + query_id: QueryId, + /// Data of the requested block + data: Vec, + }, + /// Error occurred while fetching block + GetQueryError { + /// Id of the query, returned by [`Behaviour::get`] + query_id: QueryId, + /// Error that occurred when getting the data + error: Error, + }, } /// Representation of all the errors that can occur when interacting with this crate. #[derive(Debug, thiserror::Error)] pub enum Error { + /// Encountered CID with multihash longer than max set when creating the [`Behaviour`] #[error("Invalid multihash size")] InvalidMultihashSize, + /// Invalid protocol prefix provided when building `Behaviour`, see + /// [`BehaviourBuilder::protocol_prefix`] #[error("Invalid protocol prefix: {0}")] InvalidProtocolPrefix(String), + /// Error received when interacting with blockstore #[error("Blockstore error: {0}")] Blockstore(#[from] BlockstoreError), } @@ -212,6 +231,7 @@ pub enum ToHandlerEvent { QueueOutgoingMessages(Vec<(Vec, Vec)>), } +#[doc(hidden)] pub enum StreamRequester { Client, Server, diff --git a/src/message.rs b/src/message.rs index 97774f5..deb40f9 100644 --- a/src/message.rs +++ b/src/message.rs @@ -13,9 +13,6 @@ pub(crate) const MAX_MESSAGE_SIZE: usize = 4 * 1024 * 1024; pub(crate) struct Codec; -#[derive(Debug, thiserror::Error)] -pub(crate) enum CodecError {} - impl Encoder for Codec { type Item<'a> = &'a Message; type Error = io::Error; diff --git a/src/multihasher.rs b/src/multihasher.rs index d6499e6..78d3ae3 100644 --- a/src/multihasher.rs +++ b/src/multihasher.rs @@ -1,3 +1,13 @@ +//! Module responsible for calculating hashes for data received +//! +//! For interoperability `StandardMultihasher` is registered by default, which uses hashes +//! provided by [`multihash_codetable::Code`]. If you need to register your own multihashes, +//! you can implement [`Multihasher`] trait and then register the struct with +//! [`BehaviourBuilder::register_multihasher`] when creating the behaviour. +//! +//! [`BehaviourBuilder::register_multihasher`]: +//! crate::builder::BehaviourBuilder::register_multihasher + use std::collections::VecDeque; use std::fmt::{self, Display}; @@ -40,10 +50,13 @@ pub enum MultihasherError { } impl MultihasherError { + /// Custom error, causes block to be ignored pub fn custom(e: impl Display) -> MultihasherError { MultihasherError::Custom(e.to_string()) } + /// Custom fatal error, causes block to be ignored and stream from which it was received to + /// close pub fn custom_fatal(e: impl Display) -> MultihasherError { MultihasherError::CustomFatal(e.to_string()) } diff --git a/src/utils.rs b/src/utils.rs index 95f1cba..569bab9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,5 @@ +//! Helpers used in and provided by the crate + use cid::CidGeneric; use libp2p_core::multihash::Multihash; use libp2p_swarm::StreamProtocol;