From 09b4cda71a4d39c1be421db612ad808116f073c3 Mon Sep 17 00:00:00 2001 From: Jordan Santell Date: Mon, 24 Oct 2022 10:03:43 -0700 Subject: [PATCH] Initial work on `noosphere-p2p`, the libp2p-based DHT underpinings of a name system. (#93) * Initial work on noosphere-p2p, the libp2p-based DHT underpinings of a name system. * Revise noosphere-p2p implementation from feedback, introducing DHTNodeBuilder with non-libp2p interface. --- .github/workflows/run_test_suite.yaml | 2 +- .vscode/settings.json | 11 +- rust/Cargo.lock | 1402 ++++++++++++++++-- rust/Cargo.toml | 4 +- rust/README.md | 11 +- rust/noosphere-p2p/Cargo.toml | 21 + rust/noosphere-p2p/README.md | 3 + rust/noosphere-p2p/src/dht/behaviour.rs | 75 + rust/noosphere-p2p/src/dht/builder.rs | 218 +++ rust/noosphere-p2p/src/dht/channel.rs | 207 +++ rust/noosphere-p2p/src/dht/config.rs | 74 + rust/noosphere-p2p/src/dht/errors.rs | 107 ++ rust/noosphere-p2p/src/dht/mod.rs | 16 + rust/noosphere-p2p/src/dht/node.rs | 178 +++ rust/noosphere-p2p/src/dht/processor.rs | 475 ++++++ rust/noosphere-p2p/src/dht/swarm.rs | 31 + rust/noosphere-p2p/src/dht/transport.rs | 29 + rust/noosphere-p2p/src/dht/types.rs | 130 ++ rust/noosphere-p2p/src/lib.rs | 5 + rust/noosphere-p2p/tests/integration_test.rs | 105 ++ rust/noosphere-p2p/tests/utils/mod.rs | 132 ++ rust/noosphere/src/authority/key_material.rs | 16 + 22 files changed, 3145 insertions(+), 107 deletions(-) create mode 100644 rust/noosphere-p2p/Cargo.toml create mode 100644 rust/noosphere-p2p/README.md create mode 100644 rust/noosphere-p2p/src/dht/behaviour.rs create mode 100644 rust/noosphere-p2p/src/dht/builder.rs create mode 100644 rust/noosphere-p2p/src/dht/channel.rs create mode 100644 rust/noosphere-p2p/src/dht/config.rs create mode 100644 rust/noosphere-p2p/src/dht/errors.rs create mode 100644 rust/noosphere-p2p/src/dht/mod.rs create mode 100644 rust/noosphere-p2p/src/dht/node.rs create mode 100644 rust/noosphere-p2p/src/dht/processor.rs create mode 100644 rust/noosphere-p2p/src/dht/swarm.rs create mode 100644 rust/noosphere-p2p/src/dht/transport.rs create mode 100644 rust/noosphere-p2p/src/dht/types.rs create mode 100644 rust/noosphere-p2p/src/lib.rs create mode 100644 rust/noosphere-p2p/tests/integration_test.rs create mode 100644 rust/noosphere-p2p/tests/utils/mod.rs diff --git a/.github/workflows/run_test_suite.yaml b/.github/workflows/run_test_suite.yaml index e11e52e46..267ce4cd4 100644 --- a/.github/workflows/run_test_suite.yaml +++ b/.github/workflows/run_test_suite.yaml @@ -17,7 +17,7 @@ jobs: - name: 'Install environment packages' run: | sudo apt-get update -qqy - sudo apt-get install jq + sudo apt-get install jq protobuf-compiler cmake - name: 'Run Rust native target tests' working-directory: ./rust run: cargo test diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f993a78a..50490dc99 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,13 @@ { "rust-analyzer.cargo.target": null, - "lldb.launch.cwd": "${workspaceFolder}" + "lldb.launch.cwd": "${workspaceFolder}", + "rust-analyzer.procMacro.enable": true, + "rust-analyzer.procMacro.ignored": { + "libp2p_swarm_derive": [ + "NetworkBehaviour" + ], + "async-trait": [ + "async_trait" + ] + } } \ No newline at end of file diff --git a/rust/Cargo.lock b/rust/Cargo.lock index e2e8e8a4c..6a6eb1de6 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -3,21 +3,58 @@ version = 3 [[package]] -name = "aho-corasick" -version = "0.7.19" +name = "aead" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "memchr", + "generic-array", ] [[package]] -name = "ansi_term" -version = "0.12.1" +name = "aes" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ - "winapi", + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.7", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", ] [[package]] @@ -38,6 +75,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "asn1_der" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" + [[package]] name = "async-channel" version = "1.7.1" @@ -192,9 +235,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", @@ -213,6 +256,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "asynchronous-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + [[package]] name = "atomic-waker" version = "1.0.0" @@ -330,9 +386,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64ct" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" [[package]] name = "bitflags" @@ -340,6 +396,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blake2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +dependencies = [ + "digest 0.10.5", +] + [[package]] name = "blake2b_simd" version = "1.0.0" @@ -424,9 +489,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byteorder" @@ -467,6 +532,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "cid" version = "0.8.6" @@ -481,11 +571,20 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clap" -version = "4.0.9" +version = "4.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30607dd93c420c6f1f80b544be522a0238a7db35e6a12968d28910983fee0df0" +checksum = "06badb543e734a2d6568e19a40af66ed5364360b9226184926f89d229b4b4267" dependencies = [ "atty", "bitflags", @@ -498,9 +597,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.0.9" +version = "4.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a307492e1a34939f79d3b6b9650bd2b971513cd775436bf2b78defeb5af00b" +checksum = "c42f169caba89a7d512b5418b09864543eeb4d497416c917d7137863bd2076ad" dependencies = [ "heck", "proc-macro-error", @@ -673,14 +772,23 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", "syn", ] +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -694,6 +802,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "curve25519-dalek" +version = "4.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4033478fbf70d6acf2655ac70da91ee65852d69daf7a67bf7a2f518fb47aafcf" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "data-encoding" version = "2.3.2" @@ -757,13 +878,52 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "dns-parser" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" +dependencies = [ + "byteorder", + "quick-error", +] + +[[package]] +name = "dtoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6eee2d5d0d113f015688310da018bd1d864d86bd567c8fca9c266889e1bfa" + +[[package]] +name = "ed25519" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + [[package]] name = "ed25519-zebra" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 3.2.0", "hex", "rand_core 0.6.4", "serde", @@ -787,6 +947,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_logger" version = "0.7.1" @@ -818,6 +990,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fnv" version = "1.0.7" @@ -918,6 +1096,7 @@ dependencies = [ "futures-core", "futures-task", "futures-util", + "num_cpus", ] [[package]] @@ -964,6 +1143,12 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.24" @@ -1079,6 +1264,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "globset" version = "0.4.9" @@ -1128,6 +1323,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "headers" @@ -1186,9 +1384,9 @@ dependencies = [ [[package]] name = "home" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408" dependencies = [ "winapi", ] @@ -1199,6 +1397,17 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371fb981840150b1a54f7cb117bf6699f7466a1d4861daac33bc6fe2b5abea0" +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http" version = "0.2.8" @@ -1276,6 +1485,17 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.3.0" @@ -1286,6 +1506,34 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if-addrs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "if-watch" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065c008e570a43c00de6aed9714035e5ea6a498c255323db9091722af6ee67dd" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "rtnetlink", + "system-configuration", + "windows", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -1309,6 +1557,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ipconfig" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" +dependencies = [ + "socket2", + "widestring", + "winapi", + "winreg 0.7.0", +] + [[package]] name = "ipnet" version = "2.5.0" @@ -1326,9 +1586,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "js-sys" @@ -1365,9 +1625,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.134" +version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" [[package]] name = "libipld-cbor" @@ -1414,51 +1674,347 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" [[package]] -name = "lock_api" -version = "0.4.9" +name = "libp2p" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "ec878fda12ebec479186b3914ebc48ff180fa4c51847e11a1a68bf65249e02c1" dependencies = [ - "autocfg", - "scopeguard", + "bytes", + "futures", + "futures-timer", + "getrandom 0.2.7", + "instant", + "lazy_static", + "libp2p-core", + "libp2p-dns", + "libp2p-identify", + "libp2p-kad", + "libp2p-mdns", + "libp2p-metrics", + "libp2p-mplex", + "libp2p-noise", + "libp2p-swarm", + "libp2p-swarm-derive", + "libp2p-tcp", + "libp2p-yamux", + "multiaddr", + "parking_lot 0.12.1", + "pin-project", + "smallvec", ] [[package]] -name = "log" -version = "0.4.17" +name = "libp2p-core" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "799676bb0807c788065e57551c6527d461ad572162b0519d1958946ff9e0539d" dependencies = [ - "cfg-if", - "value-bag", + "asn1_der", + "bs58", + "ed25519-dalek", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "lazy_static", + "log", + "multiaddr", + "multihash", + "multistream-select", + "parking_lot 0.12.1", + "pin-project", + "prost", + "prost-build", + "rand 0.8.5", + "rw-stream-sink", + "sha2 0.10.6", + "smallvec", + "thiserror", + "unsigned-varint", + "void", + "zeroize", ] [[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "matchers" -version = "0.1.0" +name = "libp2p-dns" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "2322c9fb40d99101def6a01612ee30500c89abbbecb6297b3cd252903a4c1720" dependencies = [ - "regex-automata", + "futures", + "libp2p-core", + "log", + "parking_lot 0.12.1", + "smallvec", + "trust-dns-resolver", ] [[package]] -name = "matchit" -version = "0.5.0" +name = "libp2p-identify" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +checksum = "dcf9a121f699e8719bda2e6e9e9b6ddafc6cff4602471d6481c1067930ccb29b" +dependencies = [ + "asynchronous-codec", + "futures", + "futures-timer", + "libp2p-core", + "libp2p-swarm", + "log", + "lru", + "prost", + "prost-build", + "prost-codec", + "smallvec", + "thiserror", + "void", +] [[package]] -name = "memchr" -version = "2.5.0" +name = "libp2p-kad" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "6721c200e2021f6c3fab8b6cf0272ead8912d871610ee194ebd628cecf428f22" +dependencies = [ + "arrayvec", + "asynchronous-codec", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-swarm", + "log", + "prost", + "prost-build", + "rand 0.8.5", + "sha2 0.10.6", + "smallvec", + "thiserror", + "uint", + "unsigned-varint", + "void", +] + +[[package]] +name = "libp2p-mdns" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761704e727f7d68d58d7bc2231eafae5fc1b9814de24290f126df09d4bd37a15" +dependencies = [ + "data-encoding", + "dns-parser", + "futures", + "if-watch", + "libp2p-core", + "libp2p-swarm", + "log", + "rand 0.8.5", + "smallvec", + "socket2", + "tokio", + "void", +] + +[[package]] +name = "libp2p-metrics" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee31b08e78b7b8bfd1c4204a9dd8a87b4fcdf6dafc57eb51701c1c264a81cb9" +dependencies = [ + "libp2p-core", + "libp2p-identify", + "libp2p-kad", + "libp2p-swarm", + "prometheus-client", +] + +[[package]] +name = "libp2p-mplex" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692664acfd98652de739a8acbb0a0d670f1d67190a49be6b4395e22c37337d89" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "libp2p-core", + "log", + "nohash-hasher", + "parking_lot 0.12.1", + "rand 0.8.5", + "smallvec", + "unsigned-varint", +] + +[[package]] +name = "libp2p-noise" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048155686bd81fe6cb5efdef0c6290f25ad32a0a42e8f4f72625cf6a505a206f" +dependencies = [ + "bytes", + "curve25519-dalek 3.2.0", + "futures", + "lazy_static", + "libp2p-core", + "log", + "prost", + "prost-build", + "rand 0.8.5", + "sha2 0.10.6", + "snow", + "static_assertions", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "libp2p-swarm" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46d13df7c37807965d82930c0e4b04a659efcb6cca237373b206043db5398ecf" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "log", + "pin-project", + "rand 0.8.5", + "smallvec", + "thiserror", + "void", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eddc4497a8b5a506013c40e8189864f9c3a00db2b25671f428ae9007f3ba32" +dependencies = [ + "heck", + "quote", + "syn", +] + +[[package]] +name = "libp2p-tcp" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9839d96761491c6d3e238e70554b856956fca0ab60feb9de2cd08eed4473fa92" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "log", + "socket2", + "tokio", +] + +[[package]] +name = "libp2p-yamux" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30f079097a21ad017fc8139460630286f02488c8c13b26affb46623aa20d8845" +dependencies = [ + "futures", + "libp2p-core", + "log", + "parking_lot 0.12.1", + "thiserror", + "yamux", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", + "value-bag", +] + +[[package]] +name = "lru" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" @@ -1494,7 +2050,25 @@ dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.36.1", +] + +[[package]] +name = "multiaddr" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c580bfdd8803cce319b047d239559a22f809094aaea4ac13902a1fdcfcd4261" +dependencies = [ + "arrayref", + "bs58", + "byteorder", + "data-encoding", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint", + "url", ] [[package]] @@ -1541,6 +2115,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "multistream-select" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bc41247ec209813e2fd414d6e16b9d94297dacf3cd613fa6ef09cd4d9755c10" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint", +] + [[package]] name = "native-tls" version = "0.2.10" @@ -1559,12 +2153,95 @@ dependencies = [ "tempfile", ] +[[package]] +name = "netlink-packet-core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" +dependencies = [ + "anyhow", + "byteorder", + "libc", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" +dependencies = [ + "anyhow", + "bitflags", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25af9cf0dc55498b7bd94a1508af7a78706aa0ab715a73c5169273e03c84845e" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-proto" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92b654097027250401127914afb37cb1f311df6610a9891ff07a757e94199027" +dependencies = [ + "async-io", + "bytes", + "futures", + "libc", + "log", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "noosphere" version = "0.1.0" @@ -1751,6 +2428,22 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "noosphere-p2p" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures", + "libp2p", + "noosphere", + "rand 0.8.5", + "test-log", + "tokio", + "tracing", + "tracing-subscriber", + "ucan-key-support", +] + [[package]] name = "noosphere-storage" version = "0.1.0" @@ -1786,6 +2479,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num" version = "0.3.1" @@ -1956,6 +2659,12 @@ version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking" version = "2.0.0" @@ -1980,7 +2689,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", + "parking_lot_core 0.9.4", ] [[package]] @@ -1999,31 +2708,37 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] +[[package]] +name = "paste" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" + [[package]] name = "path-absolutize" -version = "3.0.13" +version = "3.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3de4b40bd9736640f14c438304c09538159802388febb02c8abaae0846c1f13" +checksum = "0f1d4993b16f7325d90c18c3c6a3327db7808752db8d208cea0acee0abd52c52" dependencies = [ "path-dedot", ] [[package]] name = "path-dedot" -version = "3.0.17" +version = "3.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d611d5291372b3738a34ebf0d1f849e58b1dcc1101032f76a346eaa1f8ddbb5b" +checksum = "9a81540d94551664b72b72829b12bd167c73c9d25fbac0e04fafa8023f7e4901" dependencies = [ "once_cell", ] @@ -2058,6 +2773,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project" version = "1.0.12" @@ -2132,6 +2857,29 @@ dependencies = [ "winapi", ] +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -2174,13 +2922,108 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.46" +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus-client" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cd1b99916654a69008fd66b4f9397fbe08e6e51dfe23d4417acf5d3b8cb87c" +dependencies = [ + "dtoa", + "itoa", + "parking_lot 0.12.1", + "prometheus-client-derive-text-encode", +] + +[[package]] +name = "prometheus-client-derive-text-encode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a455fbcb954c1a7decf3c586e860fd7889cddf4b8e164be736dbac95a953cd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost", + "prost-types", + "regex", + "tempfile", + "which", +] + +[[package]] +name = "prost-codec" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011ae9ff8359df7915f97302d591cdd9e0e27fbd5a4ddc5bd13b71079bb20987" +dependencies = [ + "asynchronous-codec", + "bytes", + "prost", + "thiserror", + "unsigned-varint", +] + +[[package]] +name = "prost-derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" +dependencies = [ + "bytes", + "prost", +] + +[[package]] +name = "quick-error" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" -dependencies = [ - "unicode-ident", -] +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quickcheck" @@ -2352,7 +3195,17 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "winreg 0.10.1", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", ] [[package]] @@ -2369,6 +3222,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rsa" version = "0.6.1" @@ -2389,6 +3257,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rtnetlink" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" +dependencies = [ + "async-global-executor", + "futures", + "log", + "netlink-packet-route", + "netlink-proto", + "nix", + "thiserror", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2401,7 +3284,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.14", ] [[package]] @@ -2410,6 +3302,17 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +[[package]] +name = "rw-stream-sink" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26338f5e09bb721b85b135ea05af7767c90b52f6de4f087d4f4a3a9d64e7dc04" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + [[package]] name = "ryu" version = "1.0.11" @@ -2423,7 +3326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -2470,6 +3373,12 @@ dependencies = [ "semver-parser", ] +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" + [[package]] name = "semver-parser" version = "0.7.0" @@ -2528,9 +3437,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074" dependencies = [ "itoa", "ryu", @@ -2633,9 +3542,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2904bea16a1ae962b483322a1c7b81d976029203aea1f461e51cd7705db7ba9" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ "digest 0.10.5", "keccak", @@ -2659,6 +3568,12 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + [[package]] name = "slab" version = "0.4.7" @@ -2690,6 +3605,23 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "snow" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774d05a3edae07ce6d68ea6984f3c05e9bba8927e3dd591e3b479e5b03213d0d" +dependencies = [ + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek 4.0.0-pre.1", + "rand_core 0.6.4", + "ring", + "rustc_version 0.4.0", + "sha2 0.10.6", + "subtle", +] + [[package]] name = "socket2" version = "0.4.7" @@ -2716,6 +3648,12 @@ dependencies = [ "der", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stdweb" version = "0.4.20" @@ -2723,7 +3661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" dependencies = [ "discard", - "rustc_version", + "rustc_version 0.2.3", "serde", "serde_json", "stdweb-derive", @@ -2816,9 +3754,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" dependencies = [ "proc-macro2", "quote", @@ -2843,6 +3781,27 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "system-configuration" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75182f12f490e953596550b65ee31bda7c8e043d9386174b353bda50838c3fd" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "temp-dir" version = "0.1.11" @@ -2883,6 +3842,17 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-log" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thiserror" version = "1.0.37" @@ -2998,9 +3968,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite", @@ -3086,9 +4056,9 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" @@ -3098,9 +4068,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", @@ -3111,9 +4081,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -3122,9 +4092,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", "valuable", @@ -3143,12 +4113,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ - "ansi_term", "matchers", + "nu-ansi-term", "once_cell", "regex", "sharded-slab", @@ -3159,6 +4129,51 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trust-dns-proto" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "rand 0.8.5", + "smallvec", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "lru-cache", + "parking_lot 0.12.1", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", + "trust-dns-proto", +] + [[package]] name = "try-lock" version = "0.2.3" @@ -3219,6 +4234,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "uint" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicase" version = "2.6.0" @@ -3236,9 +4263,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-normalization" @@ -3261,11 +4288,31 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "unsigned-varint" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" +dependencies = [ + "asynchronous-codec", + "bytes", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" @@ -3274,7 +4321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna", + "idna 0.3.0", "percent-encoding", "serde", ] @@ -3313,6 +4360,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "waker-fn" version = "1.1.0" @@ -3450,6 +4503,17 @@ dependencies = [ "cc", ] +[[package]] +name = "which" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "whoami" version = "1.2.3" @@ -3461,6 +4525,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "widestring" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" + [[package]] name = "winapi" version = "0.3.9" @@ -3492,49 +4562,158 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" +dependencies = [ + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", +] + [[package]] name = "windows-sys" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] + [[package]] name = "winreg" version = "0.10.1" @@ -3554,6 +4733,31 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "x25519-dalek" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" +dependencies = [ + "curve25519-dalek 3.2.0", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "yamux" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot 0.12.1", + "rand 0.8.5", + "static_assertions", +] + [[package]] name = "zeroize" version = "1.5.7" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index adb9a369b..3473d45fe 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -7,8 +7,10 @@ members = [ "noosphere-api", "noosphere-fs", "noosphere-into", - "noosphere-cli" + "noosphere-cli", + "noosphere-p2p", ] + # See: https://github.com/rust-lang/rust/issues/90148#issuecomment-949194352 resolver = "2" diff --git a/rust/README.md b/rust/README.md index 611934e4d..c1b250bf1 100644 --- a/rust/README.md +++ b/rust/README.md @@ -2,13 +2,14 @@ Core implementation. -## Build notes +## Setup + +Several dependencies are needed to build Noosphere: OpenSSL, Protobufs, and Cmake. -You may need OpenSSL development files to be installed locally: +* Linux apt: `sudo apt install libssl-dev protobuf-compiler cmake` +* MacOS Homebrew: `brew install openssl protobuf cmake` -```sh -sudo apt install libssl-dev -``` +## Build notes 1. To build, make sure you have the latest rust build environment: https://rustup.rs/ diff --git a/rust/noosphere-p2p/Cargo.toml b/rust/noosphere-p2p/Cargo.toml new file mode 100644 index 000000000..58b41d29e --- /dev/null +++ b/rust/noosphere-p2p/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "noosphere-p2p" +version = "0.1.0" +edition = "2021" +rust-version = "1.60.0" + +[dependencies] +anyhow = "^1" +tracing = "0.1" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +noosphere = { path = "../noosphere" } +ucan-key-support = { version = "0.7.0-alpha.1" } +futures = "0.3.1" +tokio = { version = "1.15", features = ["io-util", "io-std", "sync", "macros", "rt", "rt-multi-thread"] } +tracing-subscriber = { version = "~0.3", features = ["env-filter"] } +libp2p = { version = "0.49.0", default-features = false, features = [ "identify", "dns", "tcp", "tokio", "noise", "mplex", "yamux", "kad" ] } + +[dev-dependencies] +rand = { version = "0.8.5" } +test-log = { version = "0.2.11", default-features = false, features = ["trace"] } diff --git a/rust/noosphere-p2p/README.md b/rust/noosphere-p2p/README.md new file mode 100644 index 000000000..6cfca7a95 --- /dev/null +++ b/rust/noosphere-p2p/README.md @@ -0,0 +1,3 @@ +# noosphere-p2p + +The P2P components of noosphere. diff --git a/rust/noosphere-p2p/src/dht/behaviour.rs b/rust/noosphere-p2p/src/dht/behaviour.rs new file mode 100644 index 000000000..17921de33 --- /dev/null +++ b/rust/noosphere-p2p/src/dht/behaviour.rs @@ -0,0 +1,75 @@ +use crate::dht::DHTConfig; +use libp2p::{ + identify::{Behaviour as Identify, Config as IdentifyConfig, Event as IdentifyEvent}, + kad::{Kademlia, KademliaConfig, KademliaEvent}, + swarm, + swarm::{ConnectionHandler, IntoConnectionHandler, SwarmEvent}, +}; +use libp2p::{kad, multiaddr}; +use libp2p::{NetworkBehaviour, PeerId}; +use std::time::Duration; + +#[derive(Debug)] +pub enum DHTEvent { + Kademlia(KademliaEvent), + Identify(IdentifyEvent), +} + +impl From for DHTEvent { + fn from(event: KademliaEvent) -> Self { + DHTEvent::Kademlia(event) + } +} + +impl From for DHTEvent { + fn from(event: IdentifyEvent) -> Self { + DHTEvent::Identify(event) + } +} + +pub type DHTSwarmEvent = SwarmEvent< + ::OutEvent, + <<::ConnectionHandler as IntoConnectionHandler>::Handler as ConnectionHandler>::Error>; + +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "DHTEvent", event_process = false)] +pub struct DHTBehaviour { + pub identify: Identify, + pub kad: Kademlia, +} + +impl DHTBehaviour { + pub fn new(config: &DHTConfig, local_peer_id: PeerId) -> Self { + let kad = { + let mut cfg = KademliaConfig::default(); + cfg.set_query_timeout(Duration::from_secs(config.query_timeout.into())); + + // TODO(#99): Use SphereFS storage + let store = kad::record::store::MemoryStore::new(local_peer_id); + let mut kad = Kademlia::with_config(local_peer_id, store, cfg); + + // Add the bootnodes to the local routing table. + for multiaddress in &config.bootstrap_peers { + let mut addr = multiaddress.to_owned(); + if let Some(multiaddr::Protocol::P2p(p2p_hash)) = addr.pop() { + let peer_id = PeerId::from_multihash(p2p_hash).unwrap(); + // Do not add a peer with the same peer id, for example + // a set of N bootstrap nodes using a static list of + // N addresses/peer IDs. + if peer_id != local_peer_id { + kad.add_address(&peer_id, addr); + } + } + } + kad + }; + + let identify = { + let config = IdentifyConfig::new("ipfs/1.0.0".into(), config.keypair.public()) + .with_agent_version(format!("noosphere-p2p/{}", env!("CARGO_PKG_VERSION"))); + Identify::new(config) + }; + + DHTBehaviour { kad, identify } + } +} diff --git a/rust/noosphere-p2p/src/dht/builder.rs b/rust/noosphere-p2p/src/dht/builder.rs new file mode 100644 index 000000000..71e236763 --- /dev/null +++ b/rust/noosphere-p2p/src/dht/builder.rs @@ -0,0 +1,218 @@ +use crate::dht::{DHTConfig, DHTNode}; +use anyhow::anyhow; +use libp2p; +use noosphere::authority::ed25519_key_to_bytes; +use ucan_key_support::ed25519::Ed25519KeyMaterial; + +/// [DHTNodeBuilder] is the primary external interface for +/// creating a new [DHTNode]. +/// +/// # Examples +/// +/// ``` +/// use tokio; +/// use noosphere::authority::generate_ed25519_key; +/// use noosphere_p2p::dht::DHTNodeBuilder; +/// use anyhow::{Result, Error}; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Error> { +/// let bootstrap_key = generate_ed25519_key(); +/// let bootstrap = DHTNodeBuilder::default() +/// .listening_address("/ip4/127.0.0.1/tcp/30000") +/// .key_material(&bootstrap_key) +/// .build()?; +/// +/// let bootstrap_address: String = bootstrap.p2p_address().to_string(); +/// let bootstrap_peers = vec![bootstrap_address.as_str()]; +/// let client_key = generate_ed25519_key(); +/// let client_node = DHTNodeBuilder::default() +/// .listening_address("/ip4/127.0.0.1/tcp/20000") +/// .key_material(&client_key) +/// .bootstrap_peers(&bootstrap_peers) +/// .build()?; +/// Ok(()) +/// } +/// ``` +/// +pub struct DHTNodeBuilder<'a> { + bootstrap_interval: u64, + bootstrap_peers: Option<&'a Vec<&'a str>>, + key_material: Option<&'a Ed25519KeyMaterial>, + listening_address: Option<&'a str>, + peer_dialing_interval: u64, + query_timeout: u32, +} + +impl<'a> DHTNodeBuilder<'a> { + /// If bootstrap peers are provided, how often, + /// in seconds, should the bootstrap process execute + /// to keep routing tables fresh. + pub fn bootstrap_interval(mut self, interval: u64) -> Self { + self.bootstrap_interval = interval; + self + } + + /// Peer addresses to query to update routing tables + /// during bootstrap. A standalone bootstrap node would + /// have this field empty. + pub fn bootstrap_peers(mut self, peers: &'a Vec<&'a str>) -> Self { + self.bootstrap_peers = Some(peers); + self + } + + /// Public/private keypair for DHT node. + pub fn key_material(mut self, key_material: &'a Ed25519KeyMaterial) -> Self { + self.key_material = Some(key_material); + self + } + + /// Address to listen for incoming connections. + pub fn listening_address(mut self, address: &'a str) -> Self { + self.listening_address = Some(address); + self + } + + /// How frequently, in seconds, the DHT attempts to + /// dial peers found in its kbucket. Outside of tests, + /// should not be lower than 5 seconds. + pub fn peer_dialing_interval(mut self, interval: u64) -> Self { + self.peer_dialing_interval = interval; + self + } + + pub fn query_timeout(mut self, timeout: u32) -> Self { + self.query_timeout = timeout; + self + } + + pub fn build(self) -> Result { + let keypair = if let Some(km) = self.key_material { + key_material_to_libp2p_keypair(km)? + } else { + libp2p::identity::Keypair::generate_ed25519() + }; + + let listening_address = if let Some(addr) = self.listening_address { + addr.parse::()? + } else { + "/ip4/127.0.0.1/tcp/0" + .parse::() + .expect("default listening address is parseable.") + }; + + let bootstrap_peers: Vec = if let Some(peers) = self.bootstrap_peers { + peers + .iter() + .map(|s| s.parse::()) + .collect::, _>>() + .map_err(|e| anyhow!(e.to_string()))? + } else { + vec![] + }; + + DHTNode::new(DHTConfig { + bootstrap_interval: self.bootstrap_interval, + bootstrap_peers, + keypair, + listening_address, + peer_dialing_interval: self.peer_dialing_interval, + query_timeout: self.query_timeout, + }) + .map_err(|e| anyhow!(e.to_string())) + } +} + +impl<'a> Default for DHTNodeBuilder<'a> { + fn default() -> Self { + Self { + bootstrap_interval: 5 * 60, + bootstrap_peers: None, + key_material: None, + listening_address: None, + peer_dialing_interval: 5, + query_timeout: 5 * 60, + } + } +} + +pub fn key_material_to_libp2p_keypair( + key_material: &Ed25519KeyMaterial, +) -> Result { + let mut bytes = ed25519_key_to_bytes(key_material)?; + let kp = libp2p::identity::ed25519::Keypair::decode(&mut bytes) + .map_err(|_| anyhow!("Could not decode ED25519 key."))?; + Ok(libp2p::identity::Keypair::Ed25519(kp)) +} + +#[cfg(test)] +mod tests { + use super::*; + use noosphere::authority::generate_ed25519_key; + use tokio; + + #[test] + fn test_key_material_to_libp2p_keypair() -> Result<(), anyhow::Error> { + let zebra_keys = generate_ed25519_key(); + let keypair: libp2p::identity::ed25519::Keypair = + match key_material_to_libp2p_keypair(&zebra_keys) { + Ok(kp) => match kp { + libp2p::identity::Keypair::Ed25519(keypair) => Ok(keypair), + }, + Err(e) => Err(e), + }?; + let zebra_private_key = zebra_keys.1.expect("Has private key"); + let dalek_public_key = keypair.public().encode(); + let dalek_private_key = keypair.secret(); + + let in_public_key = zebra_keys.0.as_ref(); + let in_private_key = zebra_private_key.as_ref(); + let out_public_key = dalek_public_key.as_ref(); + let out_private_key = dalek_private_key.as_ref(); + assert_eq!(in_public_key, out_public_key); + assert_eq!(in_private_key, out_private_key); + Ok(()) + } + + #[tokio::test] + async fn test_dht_node_builder() -> Result<(), anyhow::Error> { + let key_material = generate_ed25519_key(); + let expected_libp2p_keypair = key_material_to_libp2p_keypair(&key_material)?; + let expected_peer_id = libp2p::PeerId::from(expected_libp2p_keypair.public()); + let bootstrap_peers = vec![ + "/ip4/127.0.0.50/tcp/33333/p2p/12D3KooWH8WgH9mgbMXrKX4veokUznvEn6Ycwg4qaGNi83nLkoUK", + "/ip4/127.0.0.50/tcp/33334/p2p/12D3KooWMWo6tNGRx1G4TNqvr4SnHyVXSReC3tdX6zoJothXxV2c", + ]; + let listening_address = "/ip4/10.0.0.1/tcp/12000"; + + let node = DHTNodeBuilder::default() + .listening_address(listening_address) + .key_material(&key_material) + .bootstrap_peers(&bootstrap_peers) + .bootstrap_interval(33) + .peer_dialing_interval(11) + .query_timeout(22) + .build()?; + + let config = node.config(); + assert_eq!( + config.listening_address, + listening_address.parse::().unwrap() + ); + assert_eq!(node.peer_id(), &expected_peer_id); + assert_eq!(config.keypair.public(), expected_libp2p_keypair.public()); + assert_eq!(config.bootstrap_peers.len(), 2); + assert_eq!( + config.bootstrap_peers[0], + bootstrap_peers[0].parse::().unwrap() + ); + assert_eq!( + config.bootstrap_peers[1], + bootstrap_peers[1].parse::().unwrap() + ); + assert_eq!(config.bootstrap_interval, 33); + assert_eq!(config.peer_dialing_interval, 11); + assert_eq!(config.query_timeout, 22); + Ok(()) + } +} diff --git a/rust/noosphere-p2p/src/dht/channel.rs b/rust/noosphere-p2p/src/dht/channel.rs new file mode 100644 index 000000000..874d7960f --- /dev/null +++ b/rust/noosphere-p2p/src/dht/channel.rs @@ -0,0 +1,207 @@ +use core::{fmt, result::Result}; +use tokio; +use tokio::sync::{mpsc, mpsc::error::SendError, oneshot, oneshot::error::RecvError}; + + +impl std::error::Error for ChannelError {} +impl fmt::Display for ChannelError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ChannelError::SendError => write!(fmt, "channel send error"), + ChannelError::RecvError => write!(fmt, "channel receiver error"), + } + } +} +/// Error type to wrap the potential tokio sync errors, +/// and distinguish between user-land respond errors. +#[derive(Debug)] +pub enum ChannelError { + SendError, + RecvError, +} + +impl From>> for ChannelError { + fn from(_: SendError>) -> Self { + ChannelError::SendError + } +} + +impl From for ChannelError { + fn from(_: RecvError) -> Self { + ChannelError::RecvError + } +} + +/// Represents a request to be processed in `MessageProcessor`, +/// sent from the associated `MessageClient`. +pub struct Message { + pub request: Q, + sender: oneshot::Sender>, +} + +impl Message { + pub fn respond(self, response: Result) -> bool { + self.sender.send(response).map_or_else(|_| false, |_| true) + } +} + +impl fmt::Debug for Message { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Message") + .field("request", &self.request) + .finish() + } +} + +/// Sends requests to the associated `MessageProcessor`. +/// +/// Instances are created by the +/// [`message_channel`](message_channel) function. +pub struct MessageClient { + tx: mpsc::UnboundedSender>, +} + +impl MessageClient { + // TBD if/how "synchronous" requests will work. + #[allow(dead_code)] + pub fn send_request(&self, request: Q) -> Result<(), ChannelError> { + self.send_request_impl(request) + .map(|_| Ok(())) + .map_err(|e| ChannelError::from(e))? + } + + pub async fn send_request_async(&self, request: Q) -> Result, ChannelError> { + let rx = self + .send_request_impl(request) + .map_err(|e| ChannelError::from(e))?; + rx.await.map_err(|e| e.into()) + } + + fn send_request_impl( + &self, + request: Q, + ) -> Result>, SendError>> { + let (tx, rx) = oneshot::channel::>(); + let message = Message { + sender: tx, + request, + }; + + self.tx.send(message).and_then(|_| Ok(rx)) + } +} + +/// Receives requests from the associated `MessageClient`, +/// and optionally sends a response. +/// +/// Instances are created by the +/// [`message_channel`](message_channel) function. +pub struct MessageProcessor { + rx: mpsc::UnboundedReceiver>, +} + +impl MessageProcessor { + pub async fn pull_message(&mut self) -> Option> { + self.rx.recv().await + } +} + +/// Creates a pair of bound `MessageClient` and `MessageProcessor`. +pub fn message_channel() -> (MessageClient, MessageProcessor) { + let (tx, rx) = mpsc::unbounded_channel::>(); + let processor = MessageProcessor:: { rx }; + let client = MessageClient:: { tx }; + (client, processor) +} + +#[cfg(test)] +mod tests { + enum Request { + Ping(), + SetFlag(u32), + Shutdown(), + Throw(), + } + + enum Response { + Pong(), + GenericResult(bool), + } + struct TestError { + pub message: String, + } + use super::*; + #[tokio::test] + async fn test_message_channel() -> Result<(), Box> { + let (client, mut processor) = message_channel::(); + + tokio::spawn(async move { + let mut set_flags: usize = 0; + + loop { + let message = processor.pull_message().await; + match message { + Some(m) => match m.request { + Request::Ping() => { + let success = m.respond(Ok(Response::Pong())); + assert!(success, "receiver not closed"); + } + Request::Throw() => { + m.respond(Err(TestError { + message: String::from("thrown!"), + })); + } + Request::SetFlag(_) => { + set_flags += 1; + let success = m.respond(Ok(Response::GenericResult(true))); + assert!( + !success, + "one-way requests should not successfully respond." + ); + } + Request::Shutdown() => { + assert_eq!(set_flags, 10, "One-way requests successfully processed."); + let success = m.respond(Ok(Response::GenericResult(true))); + assert!(success); + return; + } + }, + None => panic!("message queue empty"), + } + } + }); + + let res = client.send_request_async(Request::Ping()).await?; + assert!(match res { + Ok(Response::Pong()) => true, + _ => false, + }); + + for n in 0..10 { + client.send_request(Request::SetFlag(n))?; + } + + let res = client.send_request_async(Request::Throw()).await?; + assert!( + match res { + Ok(_) => false, + Err(TestError { message }) => { + assert_eq!(message, String::from("thrown!")); + true + } + }, + "User Error propagates to client." + ); + + let res = client.send_request_async(Request::Shutdown()).await?; + assert!( + match res { + Ok(Response::GenericResult(success)) => success, + _ => false, + }, + "successfully shutdown processing thread." + ); + + Ok(()) + } +} diff --git a/rust/noosphere-p2p/src/dht/config.rs b/rust/noosphere-p2p/src/dht/config.rs new file mode 100644 index 000000000..adec1f5c8 --- /dev/null +++ b/rust/noosphere-p2p/src/dht/config.rs @@ -0,0 +1,74 @@ +use libp2p; + +#[derive(Clone, Debug)] +pub struct DHTConfig { + /// If bootstrap peers are provided, how often, + /// in seconds, should the bootstrap process execute + /// to keep routing tables fresh. + pub bootstrap_interval: u64, + /// Peer addresses to query to update routing tables + /// during bootstrap. A standalone bootstrap node would + /// have this field empty. + pub bootstrap_peers: Vec, + pub keypair: libp2p::identity::Keypair, + /// Address to listen for incoming connections. + pub listening_address: libp2p::Multiaddr, + /// How frequently, in seconds, the DHT attempts to + /// dial peers found in its kbucket. Outside of tests, + /// should not be lower than 5 seconds. + pub peer_dialing_interval: u64, + pub query_timeout: u32, +} + +impl DHTConfig { + /// Computes the [libp2p::PeerId] and [libp2p::Multiaddr] + /// listening address from the provided [DHTConfig]. + pub fn get_peer_id_and_address(config: &DHTConfig) -> (libp2p::PeerId, libp2p::Multiaddr) { + let peer_id = libp2p::PeerId::from(config.keypair.public()); + let mut addr = config.listening_address.clone(); + addr.push(libp2p::multiaddr::Protocol::P2p(peer_id.clone().into())); + (peer_id, addr) + } +} + +impl Default for DHTConfig { + fn default() -> Self { + Self { + bootstrap_interval: 5 * 60, + bootstrap_peers: vec![], + keypair: libp2p::identity::Keypair::generate_ed25519(), + listening_address: "/ip4/127.0.0.1/tcp/0" + .parse::() + .expect("Default address is parseable."), + peer_dialing_interval: 5, + query_timeout: 5 * 60, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use libp2p::multiaddr::Protocol; + use std::error::Error; + + #[test] + fn test_dhtconfig_get_peer_id_and_address() -> Result<(), Box> { + let mut config = DHTConfig::default(); + config.listening_address = "/ip4/127.0.0.50/tcp/33333".parse::()?; + let keypair = &config.keypair; + let (peer_id, mut address) = DHTConfig::get_peer_id_and_address(&config); + + assert_eq!(peer_id, libp2p::PeerId::from(keypair.public())); + assert_eq!( + address.pop().unwrap(), + Protocol::P2p(peer_id.clone().into()) + ); + assert_eq!(address.pop().unwrap(), Protocol::Tcp(33333)); + assert_eq!( + address.pop().unwrap(), + Protocol::Ip4("127.0.0.50".parse().unwrap()) + ); + Ok(()) + } +} diff --git a/rust/noosphere-p2p/src/dht/errors.rs b/rust/noosphere-p2p/src/dht/errors.rs new file mode 100644 index 000000000..bfc1df83c --- /dev/null +++ b/rust/noosphere-p2p/src/dht/errors.rs @@ -0,0 +1,107 @@ +use crate::dht::channel::ChannelError; +use anyhow; +use libp2p::{kad, kad::record::store::Error as KadStorageError, TransportError}; +use std::fmt; +use std::io; + +#[derive(Debug)] +pub enum DHTError { + Error(String), + IO(io::ErrorKind), + LibP2PTransportError(Option), + LibP2PStorageError(KadStorageError), + LibP2PGetRecordError(kad::GetRecordError), + LibP2PBootstrapError(kad::BootstrapError), + LibP2PPutRecordError(kad::PutRecordError), + LibP2PAddProviderError(kad::AddProviderError), + LibP2PGetProvidersError(kad::GetProvidersError), + NotConnected, + NoKnownPeers, +} + +impl std::error::Error for DHTError {} +impl fmt::Display for DHTError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DHTError::NotConnected => write!(fmt, "DHT not running"), + DHTError::NoKnownPeers => write!(fmt, "no known peers"), + DHTError::LibP2PTransportError(e) => write!(fmt, "{:#?}", e), + DHTError::LibP2PStorageError(e) => write!(fmt, "{:#?}", e), + DHTError::LibP2PGetRecordError(e) => write!(fmt, "{:#?}", e), + DHTError::LibP2PPutRecordError(e) => write!(fmt, "{:#?}", e), + DHTError::LibP2PBootstrapError(e) => write!(fmt, "{:#?}", e), + DHTError::LibP2PAddProviderError(e) => write!(fmt, "{:#?}", e), + DHTError::LibP2PGetProvidersError(e) => write!(fmt, "{:#?}", e), + DHTError::IO(k) => write!(fmt, "{:#?}", k), + DHTError::Error(m) => write!(fmt, "{:#?}", m), + } + } +} + +impl From for DHTError { + fn from(e: ChannelError) -> Self { + match e { + ChannelError::RecvError => DHTError::Error("RecvError".into()), + ChannelError::SendError => DHTError::Error("SendError".into()), + } + } +} + +impl From for DHTError { + fn from(e: anyhow::Error) -> Self { + DHTError::Error(e.to_string()) + } +} + +impl From for DHTError { + fn from(e: io::Error) -> Self { + DHTError::IO(e.kind()) + } +} + +impl From> for DHTError { + fn from(e: TransportError) -> Self { + match e { + TransportError::MultiaddrNotSupported(addr) => { + DHTError::LibP2PTransportError(Some(addr)) + } + TransportError::Other(_) => DHTError::LibP2PTransportError(None), + } + } +} + +impl From for DHTError { + fn from(e: KadStorageError) -> Self { + DHTError::LibP2PStorageError(e) + } +} + +impl From for DHTError { + fn from(e: kad::GetRecordError) -> Self { + DHTError::LibP2PGetRecordError(e) + } +} + +impl From for DHTError { + fn from(e: kad::PutRecordError) -> Self { + DHTError::LibP2PPutRecordError(e) + } +} + +impl From for DHTError { + fn from(e: kad::BootstrapError) -> Self { + DHTError::LibP2PBootstrapError(e) + } +} + +impl From for DHTError { + fn from(e: kad::AddProviderError) -> Self { + DHTError::LibP2PAddProviderError(e) + } +} + +impl From for DHTError { + fn from(e: kad::GetProvidersError) -> Self { + DHTError::LibP2PGetProvidersError(e) + } +} diff --git a/rust/noosphere-p2p/src/dht/mod.rs b/rust/noosphere-p2p/src/dht/mod.rs new file mode 100644 index 000000000..02e7a92c0 --- /dev/null +++ b/rust/noosphere-p2p/src/dht/mod.rs @@ -0,0 +1,16 @@ +mod behaviour; +mod builder; +mod channel; +mod config; +mod errors; +mod node; +mod processor; +mod swarm; +mod transport; +mod types; + +pub use builder::DHTNodeBuilder; +pub use config::DHTConfig; +pub use errors::DHTError; +pub use node::DHTNode; +pub use types::{DHTNetworkInfo, DHTStatus}; diff --git a/rust/noosphere-p2p/src/dht/node.rs b/rust/noosphere-p2p/src/dht/node.rs new file mode 100644 index 000000000..77132af48 --- /dev/null +++ b/rust/noosphere-p2p/src/dht/node.rs @@ -0,0 +1,178 @@ +use crate::dht::{ + builder::DHTNodeBuilder, + channel::message_channel, + errors::DHTError, + processor::DHTProcessor, + types::{DHTMessageClient, DHTNetworkInfo, DHTRequest, DHTResponse, DHTStatus}, + DHTConfig, +}; +use std::time::Duration; +use tokio; + +macro_rules! ensure_response { + ($response:expr, $matcher:pat => $statement:expr) => { + match $response { + $matcher => $statement, + _ => Err(DHTError::Error("Unexpected".into())), + } + }; +} + +/// Represents a DHT node running in a network thread, providing +/// async methods for operating the node. +pub struct DHTNode { + config: DHTConfig, + state: DHTStatus, + client: DHTMessageClient, + thread_handle: tokio::task::JoinHandle>, + peer_id: libp2p::PeerId, + p2p_address: libp2p::Multiaddr, +} + +impl DHTNode { + /// Creates a new [DHTNode], spawning a networking thread. + /// Prefer to use [DHTNodeBuilder] to create a [DHTNode]. + pub fn new(config: DHTConfig) -> Result { + let (client, processor) = message_channel::(); + let (peer_id, p2p_address) = DHTConfig::get_peer_id_and_address(&config); + let thread_handle = DHTProcessor::spawn(&config, &peer_id, &p2p_address, processor)?; + + Ok(DHTNode { + config, + peer_id, + p2p_address, + state: DHTStatus::Active, + client, + thread_handle, + }) + } + + /// Returns a new [DHTNodeBuilder] instance, the primary way of creating + /// a new [DHTNode]. + pub fn builder<'a>() -> DHTNodeBuilder<'a> { + DHTNodeBuilder::default() + } + + /// Teardown the network processing thread. + pub fn terminate(&mut self) -> Result<(), DHTError> { + self.ensure_state(DHTStatus::Active)?; + self.state = DHTStatus::Terminated; + self.thread_handle.abort(); + Ok(()) + } + + /// Returns a reference to the [DHTConfig] used to + /// initialize this node. + pub fn config(&self) -> &DHTConfig { + &self.config + } + + /// Returns the [libp2p::PeerId] of the current node. + pub fn peer_id(&self) -> &libp2p::PeerId { + &self.peer_id + } + + /// Returns the listening address of this node. + pub fn p2p_address(&self) -> &libp2p::Multiaddr { + &self.p2p_address + } + + pub fn status(&self) -> DHTStatus { + self.state.clone() + } + + /// Resolves once there are at least `requested_peers` peers + /// in the network. + pub async fn wait_for_peers(&self, requested_peers: usize) -> Result<(), DHTError> { + // TODO(#101) Need to add a mechanism for non-Query based requests, + // like sending events, or triggering a peer check on + // new connection established. For now, we poll here. + loop { + let info = self.network_info().await?; + if info.num_peers >= requested_peers { + return Ok(()); + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + } + + /// Instructs the node to initiate the bootstrap process, + /// resolving once the process begins successfully. + /// Generally, this method is usually not necessary, as nodes + /// automatically bootstrap themselves. + /// Fails if node is not in an active state, or bootstrapping + /// unable to start. + pub async fn bootstrap(&self) -> Result<(), DHTError> { + let request = DHTRequest::Bootstrap; + let response = self.send_request(request).await?; + ensure_response!(response, DHTResponse::Success => Ok(())) + } + + /// Returns the current state of the network. + /// Fails if node is not in an active state. + pub async fn network_info(&self) -> Result { + let request = DHTRequest::GetNetworkInfo; + let response = self.send_request(request).await?; + ensure_response!(response, DHTResponse::GetNetworkInfo(info) => Ok(info)) + } + + /// Sets the record keyed by `name` with `value` and propagates + /// to peers. + /// Fails if node is not in an active state or cannot set the record + /// on any peers. + pub async fn set_record(&self, name: Vec, value: Vec) -> Result, DHTError> { + let request = DHTRequest::SetRecord { name, value }; + let response = self.send_request(request).await?; + ensure_response!(response, DHTResponse::SetRecord { name } => Ok(name)) + } + + /// Fetches the record keyed by `name` from the network. + /// Return value may be `Ok(None)` if query finished without finding + /// any matching values. + /// Fails if node is not in an active state. + pub async fn get_record(&self, name: Vec) -> Result>, DHTError> { + let request = DHTRequest::GetRecord { name }; + let response = self.send_request(request).await?; + ensure_response!(response, DHTResponse::GetRecord { value, .. } => Ok(Some(value))) + } + + /// Instructs the node to tell its peers that it is providing + /// the record for `name`. + /// Fails if node is not in an active state. + pub async fn start_providing(&self, name: Vec) -> Result<(), DHTError> { + let request = DHTRequest::StartProviding { name }; + let response = self.send_request(request).await?; + ensure_response!(response, DHTResponse::StartProviding { name: _ } => Ok(())) + } + + /// Queries the network to find peers that are providing `name`. + /// Fails if node is not in an active state. + pub async fn get_providers(&self, name: Vec) -> Result, DHTError> { + let request = DHTRequest::GetProviders { name }; + let response = self.send_request(request).await?; + ensure_response!(response, DHTResponse::GetProviders { providers, name: _ } => Ok(providers)) + } + + async fn send_request(&self, request: DHTRequest) -> Result { + self.ensure_state(DHTStatus::Active)?; + self.client + .send_request_async(request) + .await + .map_err(|e| DHTError::from(e)) + .and_then(|res| res) + } + + /// Returns `Ok(())` if current status matches expected status. + /// Otherwise, returns a [DHTError]. + fn ensure_state(&self, expected_status: DHTStatus) -> Result<(), DHTError> { + if self.state != expected_status { + if expected_status == DHTStatus::Active { + Err(DHTError::NotConnected) + } else { + Err(DHTError::Error("invalid state".into())) + } + } else { + Ok(()) + } + } +} diff --git a/rust/noosphere-p2p/src/dht/processor.rs b/rust/noosphere-p2p/src/dht/processor.rs new file mode 100644 index 000000000..41cb34c36 --- /dev/null +++ b/rust/noosphere-p2p/src/dht/processor.rs @@ -0,0 +1,475 @@ +use crate::dht::{ + behaviour::{DHTEvent, DHTSwarmEvent}, + errors::DHTError, + swarm::{build_swarm, DHTSwarm}, + types::{DHTMessage, DHTMessageProcessor, DHTRequest, DHTResponse}, + DHTConfig, +}; +use libp2p::futures::StreamExt; +use libp2p::kad; +use libp2p::{ + identify::Event as IdentifyEvent, + kad::{ + kbucket::{Distance, NodeStatus}, + record::{store::RecordStore, Key}, + KademliaEvent, PeerRecord, QueryResult, Quorum, Record, + }, + swarm::{ + dial_opts::{DialOpts, PeerCondition}, + SwarmEvent, + }, + PeerId, +}; +use std::fmt; +use std::{collections::HashMap, time::Duration}; +use tokio; + +/// The processing component of a [DHTNode]/[DHTProcessor] pair. Consumers +/// should only interface with a [DHTProcessor] via [DHTNode]. +pub struct DHTProcessor { + config: DHTConfig, + p2p_address: libp2p::Multiaddr, + peer_id: PeerId, + processor: DHTMessageProcessor, + swarm: DHTSwarm, + requests: HashMap, + kad_last_range: Option<(Distance, Distance)>, +} + +// Temporary(?), exploring processing both requests that +// are bound by kad::QueryId, and requests that do not tie +// into DHT queries (like WaitForPeers). +macro_rules! store_request { + ($self:expr, $message:expr, $result:expr) => { + let result: Result = $result.map_err(|e| e.into()); + match result { + Ok(query_id) => { + $self.requests.insert(query_id, $message); + } + Err(e) => { + $message.respond(Err(e)); + } + }; + }; +} + +impl DHTProcessor { + /// Creates a new [DHTProcessor] and spawns a networking thread for processing. + /// The processor can only be accessed through channels via the corresponding + /// [DHTNode]. + pub(crate) fn spawn( + config: &DHTConfig, + peer_id: &PeerId, + p2p_address: &libp2p::Multiaddr, + processor: DHTMessageProcessor, + ) -> Result>, DHTError> { + let swarm = build_swarm(peer_id, config)?; + let mut node = DHTProcessor { + config: config.to_owned(), + p2p_address: p2p_address.to_owned(), + peer_id: peer_id.to_owned(), + processor, + swarm, + requests: HashMap::default(), + kad_last_range: None, + }; + + Ok(tokio::spawn(async move { node.process().await })) + } + + /// Begin processing requests and connections on the DHT network + /// in the current thread. Executes until the loop is broken, via + /// either an unhandlable error or a terminate message (not yet implemented). + async fn process(&mut self) -> Result<(), DHTError> { + self.start_listening()?; + + // Queue up bootstrapping this node both immediately, and every + // `bootstrap_interval` seconds. + let mut bootstrap_tick = + tokio::time::interval(Duration::from_secs(self.config.bootstrap_interval)); + + // Traverse and potentially dial peers on this interval. + let mut peer_dialing_tick = + tokio::time::interval(Duration::from_secs(self.config.peer_dialing_interval)); + + Ok(loop { + tokio::select! { + message = self.processor.pull_message() => { + match message { + Some(m) => self.process_message(m), + // This occurs when sender is closed (client dropped). + // Exit the process loop for thread clean up. + None => { + error!("DHT processing loop unexpectedly closed."); + break + }, + } + } + event = self.swarm.select_next_some() => { + self.process_swarm_event(event) + } + _ = bootstrap_tick.tick() => self.execute_bootstrap()?, + _ = peer_dialing_tick.tick() => self.dial_next_peer(), + } + }) + } + + /// Processes an incoming DHTMessage. Will attempt to respond + /// immediately if possible (synchronous error or pulling value from cache), + /// otherwise, the message will be mapped to a query, where it can be fulfilled + /// later, most likely in `process_kad_result()`. + fn process_message(&mut self, message: DHTMessage) { + dht_event_trace(self, &message); + + let behaviour = self.swarm.behaviour_mut(); + + // Process client requests. + match message.request { + DHTRequest::GetProviders { ref name } => { + store_request!( + self, + message, + Ok::(behaviour.kad.get_providers(Key::new(name))) + ); + } + /* + DHTRequest::WaitForPeers(peers) => { + let info = self.swarm.network_info(); + if info.num_peers() >= peers { + message.respond(Ok(DHTResponse::Success)); + } else { + store_request!(self, message); + } + } + */ + DHTRequest::Bootstrap => { + message.respond( + self.execute_bootstrap() + .and_then(|_| Ok(DHTResponse::Success)), + ); + } + DHTRequest::GetNetworkInfo => { + let info = self.swarm.network_info(); + message.respond(Ok(DHTResponse::GetNetworkInfo(info.into()))); + } + DHTRequest::StartProviding { ref name } => { + store_request!(self, message, behaviour.kad.start_providing(Key::new(name))); + } + DHTRequest::GetRecord { ref name } => { + store_request!( + self, + message, + Ok::( + behaviour.kad.get_record(Key::new(name), Quorum::One) + ) + ); + } + DHTRequest::SetRecord { + ref name, + ref value, + } => { + let record = Record { + key: Key::new(name), + value: value.clone(), + publisher: None, + expires: None, + }; + store_request!(self, message, behaviour.kad.put_record(record, Quorum::One)); + } + }; + } + + /// Processes an incoming SwarmEvent, triggered from swarm activity or + /// a swarm query. If a SwarmEvent has an associated DHTQuery, + /// the pending query will be fulfilled. + fn process_swarm_event(&mut self, event: DHTSwarmEvent) { + dht_event_trace(self, &event); + match event { + SwarmEvent::Behaviour(DHTEvent::Kademlia(e)) => self.process_kad_event(e), + SwarmEvent::Behaviour(DHTEvent::Identify(e)) => self.process_identify_event(e), + // The following events are currently handled only for debug logging. + SwarmEvent::NewListenAddr { address: _, .. } => {} + SwarmEvent::ConnectionEstablished { peer_id: _, .. } => {} + SwarmEvent::ConnectionClosed { + peer_id: _, + cause: _, + .. + } => {} + SwarmEvent::IncomingConnection { + local_addr: _, + send_back_addr: _, + } => {} + SwarmEvent::IncomingConnectionError { + local_addr: _, + send_back_addr: _, + error: _, + } => {} + SwarmEvent::OutgoingConnectionError { + peer_id: _, + error: _, + } => {} + SwarmEvent::BannedPeer { peer_id: _, .. } => {} + SwarmEvent::ExpiredListenAddr { + listener_id: _, + address: _, + } => {} + SwarmEvent::ListenerClosed { + listener_id: _, + addresses: _, + reason: _, + } => {} + SwarmEvent::ListenerError { + listener_id: _, + error: _, + } => {} + SwarmEvent::Dialing(_) => {} + } + } + + fn process_kad_event(&mut self, event: KademliaEvent) { + match event { + KademliaEvent::OutboundQueryCompleted { id, result, .. } => match result { + QueryResult::GetRecord(Ok(ok)) => { + for PeerRecord { + record: Record { key, value, .. }, + .. + } in ok.records + { + if let Some(message) = self.requests.remove(&id) { + message.respond(Ok(DHTResponse::GetRecord { + name: key.to_vec(), + value, + })); + } + } + } + QueryResult::GetRecord(Err(e)) => { + if let Some(message) = self.requests.remove(&id) { + message.respond(Err(DHTError::from(e))); + } + } + QueryResult::PutRecord(Ok(kad::PutRecordOk { key })) => { + if let Some(message) = self.requests.remove(&id) { + message.respond(Ok(DHTResponse::SetRecord { name: key.to_vec() })); + } + } + QueryResult::PutRecord(Err(e)) => { + match e.clone() { + kad::PutRecordError::Timeout { + ref key, + quorum: _, + success: _, + } + | kad::PutRecordError::QuorumFailed { + ref key, + quorum: _, + success: _, + } => { + let record = self.swarm.behaviour_mut().kad.store_mut().get(key); + trace!("Has internal record? {:?}", record); + } + } + if let Some(message) = self.requests.remove(&id) { + message.respond(Err(DHTError::from(e))); + } + } + QueryResult::StartProviding(Ok(kad::AddProviderOk { key })) => { + if let Some(message) = self.requests.remove(&id) { + message.respond(Ok(DHTResponse::StartProviding { name: key.to_vec() })); + } + } + QueryResult::StartProviding(Err(e)) => { + if let Some(message) = self.requests.remove(&id) { + message.respond(Err(DHTError::from(e))); + } + } + QueryResult::GetProviders(Ok(kad::GetProvidersOk { + providers, + key, + closest_peers: _, + })) => { + if let Some(message) = self.requests.remove(&id) { + message.respond(Ok(DHTResponse::GetProviders { + providers: providers.into_iter().collect(), + name: key.to_vec(), + })); + } + } + QueryResult::GetProviders(Err(e)) => { + if let Some(message) = self.requests.remove(&id) { + message.respond(Err(DHTError::from(e))); + } + } + QueryResult::Bootstrap(Ok(kad::BootstrapOk { + peer: _, + num_remaining: _, + })) => {} + QueryResult::Bootstrap(Err(kad::BootstrapError::Timeout { + peer: _, + num_remaining: _, + })) => {} + _ => {} + }, + KademliaEvent::InboundRequest { request } => match request { + kad::InboundRequest::FindNode { + num_closer_peers: _, + } => {} + kad::InboundRequest::GetProvider { + num_closer_peers: _, + num_provider_peers: _, + } => {} + kad::InboundRequest::AddProvider { record: _ } => {} + kad::InboundRequest::GetRecord { + num_closer_peers: _, + present_locally: _, + } => {} + kad::InboundRequest::PutRecord { source, record, .. } => match record { + Some(rec) => { + if let Err(_) = self.swarm.behaviour_mut().kad.store_mut().put(rec.clone()) + { + warn!("InboundRequest::PutRecord failed: {:?} {:?}", rec, source); + } + } + None => warn!("InboundRequest::PutRecord failed; empty record"), + }, + }, + KademliaEvent::RoutingUpdated { + peer: _, + is_new_peer: _, + addresses: _, + .. + } => {} + KademliaEvent::UnroutablePeer { peer: _ } => {} + KademliaEvent::RoutablePeer { + peer: _, + address: _, + } => {} + KademliaEvent::PendingRoutablePeer { + peer: _, + address: _, + } => {} + } + } + + fn process_identify_event(&mut self, event: IdentifyEvent) { + match event { + IdentifyEvent::Received { peer_id, info } => { + if info + .protocols + .iter() + .any(|p| p.as_bytes() == kad::protocol::DEFAULT_PROTO_NAME) + { + for addr in &info.listen_addrs { + self.swarm + .behaviour_mut() + .kad + .add_address(&peer_id, addr.clone()); + } + } + } + _ => {} + } + } + + /// Traverses the kbuckets to dial potential peers that + /// are not yet connected. Implementation inspired by iroh: + /// https://github.com/n0-computer/iroh/blob/main/iroh-p2p/src/node.rs + fn dial_next_peer(&mut self) { + let mut to_dial = None; + for kbucket in self.swarm.behaviour_mut().kad.kbuckets() { + if let Some(range) = self.kad_last_range { + if kbucket.range() == range { + continue; + } + } + + // find the first disconnected node + for entry in kbucket.iter() { + if entry.status == NodeStatus::Disconnected { + let peer_id = entry.node.key.preimage(); + + let dial_opts = DialOpts::peer_id(*peer_id) + .condition(PeerCondition::Disconnected) + .addresses(entry.node.value.clone().into_vec()) + .extend_addresses_through_behaviour() + .build(); + to_dial = Some((dial_opts, kbucket.range())); + break; + } + } + } + + if let Some((dial_opts, range)) = to_dial { + if let Err(e) = self.swarm.dial(dial_opts) { + warn!("failed to dial: {:?}", e); + } + self.kad_last_range = Some(range); + } + } + + fn start_listening(&mut self) -> Result<(), DHTError> { + let addr = self.p2p_address.clone(); + dht_event_trace(self, &format!("Start listening on {}", addr)); + self.swarm + .listen_on(addr) + .and_then(|_| Ok(())) + .map_err(|e| DHTError::from(e)) + } + + fn execute_bootstrap(&mut self) -> Result<(), DHTError> { + dht_event_trace(self, &"Execute bootstrap"); + if self.config.bootstrap_peers.is_empty() { + // Bootstrapping can't occur without bootstrap nodes + return Ok(()); + } + self.swarm + .behaviour_mut() + .kad + .bootstrap() + .and_then(|_| Ok(())) + .map_err(|_| DHTError::NoKnownPeers) + } +} + +impl fmt::Debug for DHTProcessor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DHTNode") + .field("peer_id", &self.peer_id) + .field("p2p_address", &self.p2p_address) + .field("config", &self.config) + .finish() + } +} + +// #[cfg(test)] +/// Logging utility. Unfortunately, integration tests do not work +/// with `#[cfg(test)]` to enable the option of rendering the full +/// peer id during non-testing (one process, one peer id) scenarios. +/// https://doc.rust-lang.org/book/ch11-03-test-organization.html +fn dht_event_trace(processor: &DHTProcessor, data: &T) { + // Convert a full PeerId to a shorter, more identifiable + // string for comparison in logs during tests, where multiple nodes + // are shared by a single process. All Ed25519 keys have + // the prefix `12D3KooW`, so skip the commonalities and use + // the next 6 characters for logging. + let peer_id_b58 = processor.peer_id.to_base58(); + trace!( + "\nFrom ..{:#?}..\n{:#?}", + peer_id_b58 + .get(8..14) + .or_else(|| Some("INVALID PEER ID")) + .unwrap(), + data + ); +} + +/* +#[cfg(not(test))] +fn dht_event_trace(processor: &DHTProcessor, data: &T) { + trace!( + "\nFrom ..{:#?}..\n{:#?}", + processor.peer_id.to_base58(), + data + ); +} +*/ diff --git a/rust/noosphere-p2p/src/dht/swarm.rs b/rust/noosphere-p2p/src/dht/swarm.rs new file mode 100644 index 000000000..7d4c0a30c --- /dev/null +++ b/rust/noosphere-p2p/src/dht/swarm.rs @@ -0,0 +1,31 @@ +use crate::dht::behaviour::DHTBehaviour; +use crate::dht::errors::DHTError; +use crate::dht::transport::build_transport; +use crate::dht::DHTConfig; +use libp2p::{swarm::SwarmBuilder, PeerId}; +use std::{boxed::Box, future::Future, pin::Pin}; +use tokio; + +pub type DHTSwarm = libp2p::swarm::Swarm; + +struct ExecutorHandle { + handle: tokio::runtime::Handle, +} + +impl libp2p::core::Executor for ExecutorHandle { + fn exec(&self, future: Pin + Send + 'static>>) { + self.handle.spawn(future); + } +} + +pub fn build_swarm(local_peer_id: &PeerId, config: &DHTConfig) -> Result { + let transport = build_transport(&config.keypair).map_err(|e| DHTError::from(e))?; + let behaviour = DHTBehaviour::new(&config, local_peer_id.to_owned()); + + let handle = tokio::runtime::Handle::current(); + let executor_handle = Box::new(ExecutorHandle { handle }); + let swarm = SwarmBuilder::new(transport, behaviour, local_peer_id.to_owned()) + .executor(executor_handle) + .build(); + Ok(swarm) +} diff --git a/rust/noosphere-p2p/src/dht/transport.rs b/rust/noosphere-p2p/src/dht/transport.rs new file mode 100644 index 000000000..da2c65019 --- /dev/null +++ b/rust/noosphere-p2p/src/dht/transport.rs @@ -0,0 +1,29 @@ +use libp2p::{ + core::muxing::StreamMuxerBox, core::transport::Boxed, core::upgrade, dns, mplex, noise, tcp, + yamux, PeerId, Transport, +}; +use std::{io, result::Result}; + +/// Creates the Transport mechanism that describes how peers communicate. +/// Currently, mostly an inlined form of `libp2p::tokio_development_transport`. +pub(crate) fn build_transport( + keypair: &libp2p::identity::Keypair, +) -> Result, io::Error> { + let transport = dns::TokioDnsConfig::system(tcp::TokioTcpTransport::new( + tcp::GenTcpConfig::new().nodelay(true), + ))?; + + let noise_keys = noise::Keypair::::new() + .into_authentic(&keypair) + .expect("Noise key generation failed."); + + Ok(transport + .upgrade(upgrade::Version::V1) + .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) + .multiplex(upgrade::SelectUpgrade::new( + yamux::YamuxConfig::default(), + mplex::MplexConfig::default(), + )) + .timeout(std::time::Duration::from_secs(20)) + .boxed()) +} diff --git a/rust/noosphere-p2p/src/dht/types.rs b/rust/noosphere-p2p/src/dht/types.rs new file mode 100644 index 000000000..31aa6ab15 --- /dev/null +++ b/rust/noosphere-p2p/src/dht/types.rs @@ -0,0 +1,130 @@ +use crate::dht::channel::{Message, MessageClient, MessageProcessor}; +use crate::dht::errors::DHTError; +use libp2p::swarm::NetworkInfo; +use std::{fmt, str}; + +#[derive(Clone, PartialEq, Debug)] +pub enum DHTStatus { + Active, + Terminated, + Error(String), +} + +#[derive(Debug, PartialEq)] +pub struct DHTNetworkInfo { + pub num_peers: usize, + pub num_connections: u32, + pub num_pending: u32, + pub num_established: u32, +} + +impl From for DHTNetworkInfo { + fn from(info: NetworkInfo) -> Self { + let c = info.connection_counters(); + DHTNetworkInfo { + num_peers: info.num_peers(), + num_connections: c.num_connections(), + num_pending: c.num_pending(), + num_established: c.num_established(), + } + } +} + +#[derive(Debug)] +pub enum DHTRequest { + Bootstrap, + //WaitForPeers(usize), + GetNetworkInfo, + GetRecord { name: Vec }, + SetRecord { name: Vec, value: Vec }, + StartProviding { name: Vec }, + GetProviders { name: Vec }, +} + +impl fmt::Display for DHTRequest { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + //DHTRequest::WaitForPeers(peers) => write!(fmt, "DHTRequest::WaitForPeers({})", peers), + DHTRequest::Bootstrap => write!(fmt, "DHTRequest::Bootstrap"), + DHTRequest::GetNetworkInfo => write!(fmt, "DHTRequest::GetNetworkInfo"), + DHTRequest::GetRecord { name } => write!( + fmt, + "DHTRequest::GetRecord {{ name={:?} }}", + str::from_utf8(name) + ), + DHTRequest::SetRecord { name, value } => write!( + fmt, + "DHTRequest::SetRecord {{ name={:?}, value={:?} }}", + str::from_utf8(name), + str::from_utf8(value) + ), + DHTRequest::StartProviding { name } => write!( + fmt, + "DHTRequest::StartProviding {{ name={:?} }}", + str::from_utf8(name) + ), + DHTRequest::GetProviders { name } => write!( + fmt, + "DHTRequest::GetProviders {{ name={:?} }}", + str::from_utf8(name) + ), + } + } +} + +#[derive(Debug)] +pub enum DHTResponse { + Success, + GetNetworkInfo(DHTNetworkInfo), + GetRecord { + name: Vec, + value: Vec, + }, + SetRecord { + name: Vec, + }, + StartProviding { + name: Vec, + }, + GetProviders { + name: Vec, + providers: Vec, + }, +} + +impl fmt::Display for DHTResponse { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DHTResponse::Success => write!(fmt, "DHTResponse::Success"), + DHTResponse::GetNetworkInfo(info) => { + write!(fmt, "DHTResponse::GetNetworkInfo {:?}", info) + } + DHTResponse::GetRecord { name, value } => write!( + fmt, + "DHTResponse::GetRecord {{ name={:?}, value={:?} }}", + str::from_utf8(name), + str::from_utf8(value) + ), + DHTResponse::SetRecord { name } => write!( + fmt, + "DHTResponse::SetRecord {{ name={:?} }}", + str::from_utf8(name) + ), + DHTResponse::StartProviding { name } => write!( + fmt, + "DHTResponse::StartProviding {{ name={:?} }}", + str::from_utf8(name) + ), + DHTResponse::GetProviders { name, providers } => write!( + fmt, + "DHTResponse::GetProviders {{ name={:?}, providers={:?} }}", + str::from_utf8(name), + providers + ), + } + } +} + +pub type DHTMessage = Message; +pub type DHTMessageProcessor = MessageProcessor; +pub type DHTMessageClient = MessageClient; diff --git a/rust/noosphere-p2p/src/lib.rs b/rust/noosphere-p2p/src/lib.rs new file mode 100644 index 000000000..3d81109a3 --- /dev/null +++ b/rust/noosphere-p2p/src/lib.rs @@ -0,0 +1,5 @@ +#[macro_use] +extern crate tracing; + +#[cfg(not(target_arch = "wasm32"))] +pub mod dht; diff --git a/rust/noosphere-p2p/tests/integration_test.rs b/rust/noosphere-p2p/tests/integration_test.rs new file mode 100644 index 000000000..39f38321e --- /dev/null +++ b/rust/noosphere-p2p/tests/integration_test.rs @@ -0,0 +1,105 @@ +#![cfg(not(target_arch = "wasm32"))] +#![cfg(test)] +use noosphere_p2p::dht::{DHTError, DHTNetworkInfo, DHTNode, DHTStatus}; +use std::str; +use test_log; +use tokio; +//use tracing::*; +mod utils; +use utils::{create_test_config, generate_multiaddr, initialize_network, swarm_command}; + +/// Testing a detached DHTNode as a server with no peers. +#[test_log::test(tokio::test)] +async fn test_dhtnode_base_case() -> Result<(), DHTError> { + let mut config = create_test_config(); + config.listening_address = generate_multiaddr(); + let mut node = DHTNode::new(config)?; + + assert_eq!(node.status(), DHTStatus::Active, "DHT is active"); + + let info = node.network_info().await?; + assert_eq!( + info, + DHTNetworkInfo { + num_connections: 0, + num_established: 0, + num_peers: 0, + num_pending: 0, + } + ); + assert_eq!( + node.bootstrap().await?, + (), + "bootstrap() should succeed, even without peers to bootstrap." + ); + + node.terminate()?; + assert_eq!(node.status(), DHTStatus::Terminated, "DHT is terminated"); + Ok(()) +} + +/// Tests many client nodes connecting to a single bootstrap node, +/// and ensuring clients become peers. +#[test_log::test(tokio::test)] +async fn test_dhtnode_bootstrap() -> Result<(), DHTError> { + let num_clients = 5; + let (mut bootstrap_nodes, mut client_nodes) = initialize_network(1, num_clients).await?; + let bootstrap = bootstrap_nodes.pop().unwrap(); + + for info in swarm_command(&mut client_nodes, |c| c.network_info()).await? { + assert_eq!(info.num_peers, num_clients); + // TODO(#100) the number of connections seem inconsistent?? + //assert_eq!(info.num_connections, num_clients as u32); + //assert_eq!(info.num_established, num_clients as u32); + assert_eq!(info.num_pending, 0); + } + + let info = bootstrap.network_info().await?; + assert_eq!(info.num_peers, num_clients); + // TODO(#100) the number of connections seem inconsistent?? + //assert_eq!(info.num_connections, num_clients as u32); + //assert_eq!(info.num_established, num_clients as u32); + assert_eq!(info.num_pending, 0); + + Ok(()) +} + +/// Testing primitive set_record/get_record between two +/// non-bootstrap peers. +#[test_log::test(tokio::test)] +async fn test_dhtnode_simple() -> Result<(), DHTError> { + let num_clients = 2; + let (mut _bootstrap_nodes, mut client_nodes) = initialize_network(1, num_clients).await?; + + let client_a = client_nodes.pop().unwrap(); + let client_b = client_nodes.pop().unwrap(); + client_a + .set_record( + String::from("foo").into_bytes(), + String::from("bar").into_bytes(), + ) + .await?; + let result = client_b + .get_record(String::from("foo").into_bytes()) + .await?; + assert_eq!(str::from_utf8(result.as_ref().unwrap()).unwrap(), "bar"); + Ok(()) +} + +/// Testing primitive start_providing/get_providers between two +/// non-bootstrap peers. +#[test_log::test(tokio::test)] +async fn test_dhtnode_providers() -> Result<(), DHTError> { + let num_clients = 2; + let (mut _bootstrap_nodes, mut client_nodes) = initialize_network(1, num_clients).await?; + + let client_a = client_nodes.pop().unwrap(); + let client_b = client_nodes.pop().unwrap(); + client_a.start_providing(Vec::from("foo")).await?; + + let providers = client_b.get_providers(Vec::from("foo")).await?; + println!("{:#?}", providers); + assert_eq!(providers.len(), 1); + assert_eq!(&providers[0], client_a.peer_id()); + Ok(()) +} diff --git a/rust/noosphere-p2p/tests/utils/mod.rs b/rust/noosphere-p2p/tests/utils/mod.rs new file mode 100644 index 000000000..f58292bc9 --- /dev/null +++ b/rust/noosphere-p2p/tests/utils/mod.rs @@ -0,0 +1,132 @@ +#![cfg(test)] +use futures::future::try_join_all; +use libp2p::{self, Multiaddr}; +use noosphere_p2p::dht::{DHTConfig, DHTError, DHTNode}; +use rand::{thread_rng, Rng}; +use std::future::Future; +use std::time::Duration; +use tokio; +//use tracing::*; + +pub fn generate_multiaddr() -> Multiaddr { + let mut addr = "/ip4/127.0.0.1" + .parse::() + .expect("Default IP address"); + addr.push(libp2p::multiaddr::Protocol::Tcp( + thread_rng().gen_range(49152..65535), + )); + addr +} + +pub async fn wait_ms(ms: u64) { + tokio::time::sleep(Duration::from_millis(ms)).await; +} + +pub async fn await_or_timeout( + timeout_ms: u64, + future: impl Future, + message: String, +) -> T { + tokio::select! { + _ = wait_ms(timeout_ms) => { panic!("timed out: {}", message); } + result = future => { result } + } +} + +pub fn create_test_config() -> DHTConfig { + let mut config = DHTConfig::default(); + config.peer_dialing_interval = 1; + config +} + +pub async fn swarm_command<'a, TFuture, F, T, E>( + nodes: &'a mut Vec, + func: F, +) -> Result, E> +where + F: FnMut(&'a mut DHTNode) -> TFuture, + TFuture: Future>, +{ + let futures: Vec<_> = nodes.iter_mut().map(func).collect(); + try_join_all(futures).await +} + +pub fn create_client_nodes_with_bootstrap_peers( + bootstrap_count: usize, + client_count: usize, +) -> Result<(Vec, Vec), DHTError> { + let bootstrap_nodes = create_bootstrap_nodes(bootstrap_count)?; + let bootstrap_addresses: Vec = bootstrap_nodes + .iter() + .map(|node| node.p2p_address().clone()) + .collect(); + + let mut client_nodes: Vec = vec![]; + for _ in 0..client_count { + let mut config = create_test_config(); + config.listening_address = generate_multiaddr(); + config.bootstrap_peers = bootstrap_addresses.clone(); + client_nodes.push(DHTNode::new(config)?); + } + Ok((bootstrap_nodes, client_nodes)) +} + +/// Creates `count` bootstrap nodes, each node using all other +/// bootstrap nodes as bootstrap peers. +pub fn create_bootstrap_nodes(count: usize) -> Result, DHTError> { + let mut configs: Vec<(DHTConfig, Multiaddr)> = vec![]; + for _ in 0..count { + let mut config = create_test_config(); + config.listening_address = generate_multiaddr(); + let (_peer_id, p2p_address) = DHTConfig::get_peer_id_and_address(&config); + configs.push((config, p2p_address)); + } + + let mut handles: Vec = vec![]; + let mut index = 0; + for c in &configs { + let mut config = c.0.to_owned(); + for i in 0..count { + if i != index { + config + .bootstrap_peers + .push(configs[i as usize].1.to_owned()); + } + } + handles.push(DHTNode::new(config)?); + index += 1; + } + Ok(handles) +} + +pub async fn initialize_network( + bootstrap_count: usize, + client_count: usize, +) -> Result<(Vec, Vec), DHTError> { + let (mut bootstrap_nodes, mut client_nodes) = + create_client_nodes_with_bootstrap_peers(bootstrap_count, client_count)?; + let expected_peers = client_count + bootstrap_count - 1; + // Wait a few, since nodes need to announce each other via Identify, + // which adds their address to the routing table. Kick off + // another bootstrap process after that. + // @TODO Figure out if bootstrapping is needed after identify-exchange, + // as that typically happens on a ~5 minute timer. + wait_ms(700).await; + swarm_command(&mut client_nodes, |c| c.bootstrap()).await?; + + // Wait for the peers to establish connections. + await_or_timeout( + 2000, + swarm_command(&mut client_nodes, |c| c.wait_for_peers(expected_peers)), + format!("waiting for {} peers", expected_peers), + ) + .await?; + + await_or_timeout( + 2000, + swarm_command(&mut bootstrap_nodes, |c| c.wait_for_peers(expected_peers)), + format!("waiting for {} peers", expected_peers), + ) + .await?; + Ok((bootstrap_nodes, client_nodes)) +} diff --git a/rust/noosphere/src/authority/key_material.rs b/rust/noosphere/src/authority/key_material.rs index 8707da26b..9cb024b20 100644 --- a/rust/noosphere/src/authority/key_material.rs +++ b/rust/noosphere/src/authority/key_material.rs @@ -36,3 +36,19 @@ pub fn ed25519_key_to_mnemonic(key_material: &Ed25519KeyMaterial) -> Result Result<([u8; ED25519_KEYPAIR_LENGTH])> { + let public_key = key_material.0; + let private_key: Ed25519PrivateKey = key_material + .1 + .ok_or_else(|| anyhow!("Private key required in order to deserialize."))?; + + let mut bytes: [u8; ED25519_KEYPAIR_LENGTH] = [0u8; ED25519_KEYPAIR_LENGTH]; + bytes[..ED25519_KEY_LENGTH].copy_from_slice(private_key.as_ref()); + bytes[ED25519_KEY_LENGTH..].copy_from_slice(public_key.as_ref()); + Ok(bytes) +}