Skip to content
This repository was archived by the owner on Oct 23, 2022. It is now read-only.

Commit bac542a

Browse files
bors[bot]Joonas Koivunenkoivunej
authored
Merge #406
406: fix restore_bootstrappers doesn't enable content discovery r=koivunej a=koivunej Fixes #405 and also: - makes sure ipfs.restore_bootstrappers is used in the `dht_popular_content_discovery` test - adds a test case making sure we can parse all config::BOOTSTRAP_NODES - ~adds some FIXMEs we couldn't decide yet on the other /bootstrap impl~ handled - expose ipfs::config to allow http to access ipfs::config::BOOTSTRAP_NODES to enable the http api semantics - keep exposing the delta semantics in ipfs.restore_bootstrappers - use ipfs.restore_bootstrappers in examples/fetch_and_cat.rs via new `--default-bootstrappers` option/mode - add tracing to *_bootstrapper methods Co-authored-by: Joonas Koivunen <joonas@equilibrium.co> Co-authored-by: Joonas Koivunen <joonas.koivunen@gmail.com>
2 parents 337ad5b + d026fff commit bac542a

File tree

7 files changed

+156
-53
lines changed

7 files changed

+156
-53
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Next
22

3+
* fix: restore_bootstrappers doesn't enable content discovery [#406]
4+
5+
[#406]: https://github.com/rs-ipfs/rust-ipfs/pull/406
6+
37
# 0.2.0
48

59
First real release, with big changes and feature improvements. Started tracking

examples/fetch_and_cat.rs

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,27 @@ async fn main() {
1818
// The other connecting or connected peer must be providing the requested CID or this will hang
1919
// forever.
2020

21-
let (path, target) = match parse_options() {
21+
let (bootstrappers, path, target) = match parse_options() {
2222
Ok(Some(tuple)) => tuple,
2323
Ok(None) => {
24-
eprintln!("Usage: fetch_and_cat <IPFS_PATH | CID> [MULTIADDR]");
2524
eprintln!(
26-
"Example will accept connections and print all bytes of the unixfs file to \
27-
stdout."
25+
"Usage: fetch_and_cat [--default-bootstrappers] <IPFS_PATH | CID> [MULTIADDR]"
26+
);
27+
eprintln!();
28+
eprintln!(
29+
"Example will try to find the file by the given IPFS_PATH and print its contents to stdout."
30+
);
31+
eprintln!();
32+
eprintln!("The example has three modes in the order of precedence:");
33+
eprintln!(
34+
"1. When --default-bootstrappers is given, use default bootstrappers to find the content"
35+
);
36+
eprintln!(
37+
"2. When IPFS_PATH and MULTIADDR are given, connect to MULTIADDR to get the file"
38+
);
39+
eprintln!(
40+
"3. When only IPFS_PATH is given, wait to be connected to by another ipfs node"
2841
);
29-
eprintln!("If second argument is present, it is expected to be a Multiaddr with \
30-
peer_id. The given Multiaddr will be connected to instead of awaiting an incoming connection.");
3142
exit(0);
3243
}
3344
Err(e) => {
@@ -54,7 +65,11 @@ async fn main() {
5465
// the libp2p.
5566
tokio::task::spawn(fut);
5667

57-
if let Some(target) = target {
68+
if bootstrappers == BootstrapperOption::RestoreDefault {
69+
// applications wishing to find content on the global IPFS swarm should restore the latest
70+
// bootstrappers which are hopefully updated between releases
71+
ipfs.restore_bootstrappers().await.unwrap();
72+
} else if let Some(target) = target {
5873
ipfs.connect(target).await.unwrap();
5974
} else {
6075
let (_, addresses) = ipfs.identity().await.unwrap();
@@ -81,20 +96,12 @@ async fn main() {
8196
pin_mut!(stream);
8297

8398
let mut stdout = tokio::io::stdout();
84-
let mut total = 0;
8599

86100
loop {
87101
// This could be made more performant by polling the stream while writing to stdout.
88102
match stream.next().await {
89103
Some(Ok(bytes)) => {
90-
total += bytes.len();
91104
stdout.write_all(&bytes).await.unwrap();
92-
93-
eprintln!(
94-
"Received: {:>12} bytes, Total: {:>12} bytes",
95-
bytes.len(),
96-
total
97-
);
98105
}
99106
Some(Err(e)) => {
100107
eprintln!("Error: {}", e);
@@ -103,12 +110,34 @@ async fn main() {
103110
None => break,
104111
}
105112
}
113+
}
106114

107-
eprintln!("Total received: {} bytes", total);
115+
#[derive(PartialEq)]
116+
enum BootstrapperOption {
117+
RestoreDefault,
118+
ConnectionsOnly,
108119
}
109120

110-
fn parse_options() -> Result<Option<(IpfsPath, Option<MultiaddrWithPeerId>)>, Error> {
111-
let mut args = env::args().skip(1);
121+
fn parse_options(
122+
) -> Result<Option<(BootstrapperOption, IpfsPath, Option<MultiaddrWithPeerId>)>, Error> {
123+
let mut args = env::args().skip(1).peekable();
124+
125+
// by default use only the manual connections
126+
let mut bootstrappers = BootstrapperOption::ConnectionsOnly;
127+
128+
while let Some(option) = args.peek() {
129+
if !option.starts_with("--") {
130+
break;
131+
}
132+
133+
let option = args.next().expect("already checked when peeking");
134+
135+
if option == "--default-bootstrappers" {
136+
bootstrappers = BootstrapperOption::RestoreDefault;
137+
} else {
138+
return Err(anyhow::format_err!("unknown option: {}", option));
139+
}
140+
}
112141

113142
let path = if let Some(path) = args.next() {
114143
path.parse::<IpfsPath>()
@@ -129,5 +158,5 @@ fn parse_options() -> Result<Option<(IpfsPath, Option<MultiaddrWithPeerId>)>, Er
129158
None
130159
};
131160

132-
Ok(Some((path, target)))
161+
Ok(Some((bootstrappers, path, target)))
133162
}

http/src/v0/bootstrap.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use warp::{query, Filter, Rejection, Reply};
55

66
#[derive(Debug, Serialize)]
77
#[serde(rename_all = "PascalCase")]
8-
struct Response {
9-
peers: Vec<String>,
8+
struct Response<S: AsRef<str>> {
9+
peers: Vec<S>,
1010
}
1111

1212
#[derive(Debug, Deserialize)]
@@ -48,7 +48,8 @@ pub struct BootstrapAddQuery {
4848
timeout: Option<StringSerialized<humantime::Duration>>,
4949
}
5050

51-
// used in both bootstrap_add_query and bootstrap_restore_query
51+
// optionally timed-out wrapper around [`Ipfs::restore_bootstrappers`] with stringified errors, used
52+
// in both bootstrap_add_query and bootstrap_restore_query
5253
async fn restore_helper<T: IpfsTypes>(
5354
ipfs: Ipfs<T>,
5455
timeout: &Option<StringSerialized<humantime::Duration>>,
@@ -82,7 +83,14 @@ async fn bootstrap_add_query<T: IpfsTypes>(
8283
.map_err(StringError::from)?
8384
.to_string()]
8485
} else if default == Some(true) {
85-
restore_helper(ipfs, &timeout).await?
86+
// HTTP api documents `?default=true` as deprecated
87+
let _ = restore_helper(ipfs, &timeout).await?;
88+
89+
// return a list of all known bootstrap nodes as js-ipfs does
90+
ipfs::config::BOOTSTRAP_NODES
91+
.iter()
92+
.map(|&s| String::from(s))
93+
.collect()
8694
} else {
8795
return Err(warp::reject::custom(StringError::from(
8896
"invalid query string",
@@ -94,6 +102,7 @@ async fn bootstrap_add_query<T: IpfsTypes>(
94102
Ok(warp::reply::json(&response))
95103
}
96104

105+
/// https://docs.ipfs.io/reference/http/api/#api-v0-bootstrap-add
97106
pub fn bootstrap_add<T: IpfsTypes>(
98107
ipfs: &Ipfs<T>,
99108
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
@@ -107,7 +116,8 @@ pub struct BootstrapClearQuery {
107116
timeout: Option<StringSerialized<humantime::Duration>>,
108117
}
109118

110-
// used in both bootstrap_clear_query and bootstrap_rm_query
119+
// optionally timed-out wrapper over [`Ipfs::clear_bootstrappers`] used in both
120+
// `bootstrap_clear_query` and `bootstrap_rm_query`.
111121
async fn clear_helper<T: IpfsTypes>(
112122
ipfs: Ipfs<T>,
113123
timeout: &Option<StringSerialized<humantime::Duration>>,
@@ -192,12 +202,17 @@ async fn bootstrap_restore_query<T: IpfsTypes>(
192202
ipfs: Ipfs<T>,
193203
query: BootstrapRestoreQuery,
194204
) -> Result<impl Reply, Rejection> {
195-
let peers = restore_helper(ipfs, &query.timeout).await?;
205+
let _ = restore_helper(ipfs, &query.timeout).await?;
206+
207+
// similar to add?default=true; returns a list of all bootstrap nodes, not only the added ones
208+
let peers = ipfs::config::BOOTSTRAP_NODES.to_vec();
196209
let response = Response { peers };
197210

198211
Ok(warp::reply::json(&response))
199212
}
200213

214+
/// https://docs.ipfs.io/reference/http/api/#api-v0-bootstrap-add-default, similar functionality
215+
/// also available via /bootstrap/add?default=true through [`bootstrap_add`].
201216
pub fn bootstrap_restore<T: IpfsTypes>(
202217
ipfs: &Ipfs<T>,
203218
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {

src/config.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
11
//! Static configuration (the bootstrap node(s)).
22
3+
/// The supported bootstrap nodes (/dnsaddr is not yet supported). This will be updated to contain
4+
/// the latest known supported IPFS bootstrap peers.
5+
// FIXME: it would be nice to parse these into MultiaddrWithPeerId with const fn.
36
pub const BOOTSTRAP_NODES: &[&str] =
47
&["/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ"];
8+
9+
#[cfg(test)]
10+
mod tests {
11+
use crate::p2p::MultiaddrWithPeerId;
12+
13+
#[test]
14+
fn bootstrap_nodes_are_multiaddr_with_peerid() {
15+
super::BOOTSTRAP_NODES
16+
.iter()
17+
.try_for_each(|s| s.parse::<MultiaddrWithPeerId>().map(|_| ()))
18+
.unwrap();
19+
}
20+
}

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
//! [js-ipfs]: https://github.com/ipfs/js-ipfs/
1818
//#![deny(missing_docs)]
1919

20-
mod config;
20+
pub mod config;
2121
pub mod dag;
2222
pub mod error;
2323
#[macro_use]
@@ -1113,6 +1113,8 @@ impl<Types: IpfsTypes> Ipfs<Types> {
11131113
}
11141114

11151115
/// Extend the list of used bootstrapper nodes with an additional address.
1116+
/// Return value cannot be used to determine if the `addr` was a new bootstrapper, subject to
1117+
/// change.
11161118
pub async fn add_bootstrapper(&self, addr: MultiaddrWithPeerId) -> Result<Multiaddr, Error> {
11171119
async move {
11181120
let (tx, rx) = oneshot_channel();
@@ -1129,6 +1131,8 @@ impl<Types: IpfsTypes> Ipfs<Types> {
11291131
}
11301132

11311133
/// Remove an address from the currently used list of bootstrapper nodes.
1134+
/// Return value cannot be used to determine if the `addr` was an actual bootstrapper, subject to
1135+
/// change.
11321136
pub async fn remove_bootstrapper(&self, addr: MultiaddrWithPeerId) -> Result<Multiaddr, Error> {
11331137
async move {
11341138
let (tx, rx) = oneshot_channel();

src/p2p/behaviour.rs

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -607,12 +607,15 @@ impl<Types: IpfsTypes> Behaviour<Types> {
607607
addr: MultiaddrWithPeerId,
608608
) -> Result<Multiaddr, anyhow::Error> {
609609
let ret = addr.clone().into();
610-
self.swarm.bootstrappers.insert(addr.clone());
611-
let MultiaddrWithPeerId {
612-
multiaddr: _,
613-
peer_id,
614-
} = addr.clone();
615-
self.kademlia.add_address(&peer_id, addr.into());
610+
if self.swarm.bootstrappers.insert(addr.clone()) {
611+
let MultiaddrWithPeerId {
612+
multiaddr: ma,
613+
peer_id,
614+
} = addr;
615+
self.kademlia.add_address(&peer_id, ma.into());
616+
// the return value of add_address doesn't implement Debug
617+
trace!(peer_id=%peer_id, "tried to add a bootstrapper");
618+
}
616619
Ok(ret)
617620
}
618621

@@ -621,24 +624,67 @@ impl<Types: IpfsTypes> Behaviour<Types> {
621624
addr: MultiaddrWithPeerId,
622625
) -> Result<Multiaddr, anyhow::Error> {
623626
let ret = addr.clone().into();
624-
self.swarm.bootstrappers.remove(&addr);
627+
if self.swarm.bootstrappers.remove(&addr) {
628+
let peer_id = addr.peer_id;
629+
let prefix: Multiaddr = addr.multiaddr.into();
630+
631+
if let Some(e) = self.kademlia.remove_address(&peer_id, &prefix) {
632+
info!(peer_id=%peer_id, status=?e.status, "removed bootstrapper");
633+
} else {
634+
warn!(peer_id=%peer_id, "attempted to remove an unknown bootstrapper");
635+
}
636+
}
625637
Ok(ret)
626638
}
627639

628640
pub fn clear_bootstrappers(&mut self) -> Vec<Multiaddr> {
629-
self.swarm.bootstrappers.drain().map(|a| a.into()).collect()
641+
let removed = self.swarm.bootstrappers.drain();
642+
let mut ret = Vec::with_capacity(removed.len());
643+
644+
for addr_with_peer_id in removed {
645+
let peer_id = &addr_with_peer_id.peer_id;
646+
let prefix: Multiaddr = addr_with_peer_id.multiaddr.clone().into();
647+
648+
if let Some(e) = self.kademlia.remove_address(peer_id, &prefix) {
649+
info!(peer_id=%peer_id, status=?e.status, "cleared bootstrapper");
650+
ret.push(addr_with_peer_id.into());
651+
} else {
652+
error!(peer_id=%peer_id, "attempted to clear an unknown bootstrapper");
653+
}
654+
}
655+
656+
ret
630657
}
631658

632659
pub fn restore_bootstrappers(&mut self) -> Result<Vec<Multiaddr>, anyhow::Error> {
660+
let mut ret = Vec::new();
661+
633662
for addr in BOOTSTRAP_NODES {
634-
let addr = addr.parse::<MultiaddrWithPeerId>().unwrap();
635-
self.swarm.bootstrappers.insert(addr);
663+
let addr = addr
664+
.parse::<MultiaddrWithPeerId>()
665+
.expect("see test bootstrap_nodes_are_multiaddr_with_peerid");
666+
if self.swarm.bootstrappers.insert(addr.clone()) {
667+
let MultiaddrWithPeerId {
668+
multiaddr: ma,
669+
peer_id,
670+
} = addr.clone();
671+
672+
// this is intentionally the multiaddr without peerid turned into plain multiaddr:
673+
// libp2p cannot dial addresses which include peerids.
674+
let ma: Multiaddr = ma.into();
675+
676+
// same as with add_bootstrapper: the return value from kademlia.add_address
677+
// doesn't implement Debug
678+
self.kademlia.add_address(&peer_id, ma.clone());
679+
trace!(peer_id=%peer_id, "tried to restore a bootstrapper");
680+
681+
// report with the peerid
682+
let reported: Multiaddr = addr.into();
683+
ret.push(reported);
684+
}
636685
}
637686

638-
Ok(BOOTSTRAP_NODES
639-
.iter()
640-
.map(|addr| addr.parse().unwrap())
641-
.collect())
687+
Ok(ret)
642688
}
643689
}
644690

tests/kademlia.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use cid::{Cid, Codec};
22
use ipfs::{p2p::MultiaddrWithPeerId, Block, Node};
3-
use libp2p::{kad::Quorum, multiaddr::Protocol, Multiaddr, PeerId};
3+
use libp2p::{kad::Quorum, multiaddr::Protocol, Multiaddr};
44
use multihash::Sha2_256;
55
use tokio::time::timeout;
66

@@ -139,20 +139,9 @@ async fn dht_get_closest_peers() {
139139
#[ignore = "targets an actual bootstrapper, so random failures can happen"]
140140
#[tokio::test(max_threads = 1)]
141141
async fn dht_popular_content_discovery() {
142-
let (bootstrapper_id, bootstrapper_addr): (PeerId, Multiaddr) = (
143-
"QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ"
144-
.parse()
145-
.unwrap(),
146-
"/ip4/104.131.131.82/tcp/4001".parse().unwrap(),
147-
);
148-
149142
let peer = Node::new("a").await;
150143

151-
// connect it to one of the well-known bootstrappers
152-
assert!(peer
153-
.add_peer(bootstrapper_id, bootstrapper_addr)
154-
.await
155-
.is_ok());
144+
peer.restore_bootstrappers().await.unwrap();
156145

157146
// the Cid of the IPFS logo
158147
let cid: Cid = "bafkreicncneocapbypwwe3gl47bzvr3pkpxmmobzn7zr2iaz67df4kjeiq"

0 commit comments

Comments
 (0)