Merge branch 'release/v0.4.0-rc.1'

This commit is contained in:
Dave Hrycyszyn
2020-01-28 12:06:31 +00:00
89 changed files with 2296 additions and 1488 deletions
+1
View File
@@ -5,3 +5,4 @@
.idea
target
.env
/.vscode/settings.json
+121
View File
@@ -0,0 +1,121 @@
# Changelog
## [Unreleased](https://github.com/nymtech/nym/tree/HEAD)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.3.3...HEAD)
**Closed issues:**
- Check Sphinx packet length in client [\#98](https://github.com/nymtech/nym/issues/98)
- workflow test [\#97](https://github.com/nymtech/nym/issues/97)
- Client SemVer [\#85](https://github.com/nymtech/nym/issues/85)
- Move PemStore [\#74](https://github.com/nymtech/nym/issues/74)
- Periodic client refresh [\#70](https://github.com/nymtech/nym/issues/70)
- Logging [\#68](https://github.com/nymtech/nym/issues/68)
- Nym-client refactor [\#67](https://github.com/nymtech/nym/issues/67)
- Stop panicking! [\#66](https://github.com/nymtech/nym/issues/66)
- Fix Mixnode Panic on Sphinx packet replay [\#65](https://github.com/nymtech/nym/issues/65)
- Convert older code to start using logging [\#35](https://github.com/nymtech/nym/issues/35)
- Convert to non-url-safe base64 \(everywhere\) [\#28](https://github.com/nymtech/nym/issues/28)
- If a thread blows at startup, panic the entire application [\#15](https://github.com/nymtech/nym/issues/15)
- Split provider/mod.rs [\#10](https://github.com/nymtech/nym/issues/10)
**Merged pull requests:**
- Feature/health checker with existing keys [\#105](https://github.com/nymtech/nym/pull/105) ([jstuczyn](https://github.com/jstuczyn))
- Feature/remove topology equality check [\#104](https://github.com/nymtech/nym/pull/104) ([futurechimp](https://github.com/futurechimp))
- Feature/base58 [\#102](https://github.com/nymtech/nym/pull/102) ([futurechimp](https://github.com/futurechimp))
- Feature/panic improvements [\#101](https://github.com/nymtech/nym/pull/101) ([jstuczyn](https://github.com/jstuczyn))
- Feature/fix sphinx unwraps [\#100](https://github.com/nymtech/nym/pull/100) ([futurechimp](https://github.com/futurechimp))
- Feature/check packet length [\#99](https://github.com/nymtech/nym/pull/99) ([futurechimp](https://github.com/futurechimp))
- Feature/version filtering improvements [\#96](https://github.com/nymtech/nym/pull/96) ([futurechimp](https://github.com/futurechimp))
- Feature/refreshing topology [\#94](https://github.com/nymtech/nym/pull/94) ([jstuczyn](https://github.com/jstuczyn))
- Feature/consistent logging [\#93](https://github.com/nymtech/nym/pull/93) ([futurechimp](https://github.com/futurechimp))
- Feature/semver client [\#92](https://github.com/nymtech/nym/pull/92) ([futurechimp](https://github.com/futurechimp))
- Feature/client refactor [\#91](https://github.com/nymtech/nym/pull/91) ([jstuczyn](https://github.com/jstuczyn))
- Release builds should no longer silently fail - everything will be im… [\#89](https://github.com/nymtech/nym/pull/89) ([jstuczyn](https://github.com/jstuczyn))
## [v0.3.3](https://github.com/nymtech/nym/tree/v0.3.3) (2020-01-20)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.3.2...v0.3.3)
**Fixed bugs:**
- Nym client crashing and disconnecting local websocket with complaint about binary data [\#56](https://github.com/nymtech/nym/issues/56)
**Closed issues:**
- Websocket text fix [\#64](https://github.com/nymtech/nym/issues/64)
- Restore nym-client lib in crate [\#63](https://github.com/nymtech/nym/issues/63)
- Make websocket not crash on ping or pong messages [\#62](https://github.com/nymtech/nym/issues/62)
- Messages returned by fetch are base64 encoded [\#55](https://github.com/nymtech/nym/issues/55)
- Check layer 1 connectivity at client start [\#38](https://github.com/nymtech/nym/issues/38)
- Check required sfw-provider args [\#27](https://github.com/nymtech/nym/issues/27)
- Make Electron app work with new Rust mixnet client [\#16](https://github.com/nymtech/nym/issues/16)
- Take version numbers into account when picking routes [\#14](https://github.com/nymtech/nym/issues/14)
**Merged pull requests:**
- Feature/websocket improvements [\#88](https://github.com/nymtech/nym/pull/88) ([jstuczyn](https://github.com/jstuczyn))
- Using println rather than log for startup banner, it's not an error [\#87](https://github.com/nymtech/nym/pull/87) ([futurechimp](https://github.com/futurechimp))
- Feature/nym client lib [\#61](https://github.com/nymtech/nym/pull/61) ([jstuczyn](https://github.com/jstuczyn))
## [v0.3.2](https://github.com/nymtech/nym/tree/v0.3.2) (2020-01-17)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.3.1...v0.3.2)
**Merged pull requests:**
- Feature/separate presence address [\#59](https://github.com/nymtech/nym/pull/59) ([jstuczyn](https://github.com/jstuczyn))
## [v0.3.1](https://github.com/nymtech/nym/tree/v0.3.1) (2020-01-16)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.3.0...v0.3.1)
**Merged pull requests:**
- Bugfix/presence client crash [\#58](https://github.com/nymtech/nym/pull/58) ([jstuczyn](https://github.com/jstuczyn))
## [v0.3.0](https://github.com/nymtech/nym/tree/v0.3.0) (2020-01-14)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.2.0...v0.3.0)
**Closed issues:**
- Error referring to mismatched types caused by topology [\#46](https://github.com/nymtech/nym/issues/46)
- Provider needs to announce two of its addresses \(+ issue of hardcoded port\) [\#32](https://github.com/nymtech/nym/issues/32)
- Port run\_network.sh from old Go mixnet [\#30](https://github.com/nymtech/nym/issues/30)
- Health Checker inside Validator [\#29](https://github.com/nymtech/nym/issues/29)
- Combine Rust repositories into a single master repo containing multiple crates [\#24](https://github.com/nymtech/nym/issues/24)
- Fix the version numbers on provider and mixnode [\#23](https://github.com/nymtech/nym/issues/23)
- Travis releases for Nym [\#22](https://github.com/nymtech/nym/issues/22)
- Add version number to presence [\#4](https://github.com/nymtech/nym/issues/4)
- Add version number to presence [\#1](https://github.com/nymtech/nym/issues/1)
**Merged pull requests:**
- Feature/client topology filtering [\#54](https://github.com/nymtech/nym/pull/54) ([jstuczyn](https://github.com/jstuczyn))
- print public key for nym client tools [\#53](https://github.com/nymtech/nym/pull/53) ([mileschet](https://github.com/mileschet))
- Showing binding warning on binding to localhost, 0.0.0.0 or 127.0.0.1 [\#52](https://github.com/nymtech/nym/pull/52) ([jstuczyn](https://github.com/jstuczyn))
- validator: moving sample config files into sample configs directory [\#51](https://github.com/nymtech/nym/pull/51) ([futurechimp](https://github.com/futurechimp))
- Feature/validator health checker [\#50](https://github.com/nymtech/nym/pull/50) ([jstuczyn](https://github.com/jstuczyn))
- Improving websocket connection errors [\#49](https://github.com/nymtech/nym/pull/49) ([futurechimp](https://github.com/futurechimp))
- Feature/crypto crate [\#48](https://github.com/nymtech/nym/pull/48) ([jstuczyn](https://github.com/jstuczyn))
- Feature/chill log messages [\#43](https://github.com/nymtech/nym/pull/43) ([futurechimp](https://github.com/futurechimp))
- persistence: improving PEM file reading and parsing failure messages [\#42](https://github.com/nymtech/nym/pull/42) ([futurechimp](https://github.com/futurechimp))
- Providing a nicer error than "failed on unwrap\(\)" when topology retri… [\#41](https://github.com/nymtech/nym/pull/41) ([futurechimp](https://github.com/futurechimp))
- Prettying up sfw-provider start sequence a bit. [\#40](https://github.com/nymtech/nym/pull/40) ([futurechimp](https://github.com/futurechimp))
- Removing the run command from code and documentation [\#39](https://github.com/nymtech/nym/pull/39) ([futurechimp](https://github.com/futurechimp))
- Feature/script/localnet [\#37](https://github.com/nymtech/nym/pull/37) ([futurechimp](https://github.com/futurechimp))
- Feature/arguments fix [\#36](https://github.com/nymtech/nym/pull/36) ([jstuczyn](https://github.com/jstuczyn))
- Feature/crates cleanup [\#34](https://github.com/nymtech/nym/pull/34) ([jstuczyn](https://github.com/jstuczyn))
- Features/version number to presence [\#25](https://github.com/nymtech/nym/pull/25) ([aniampio](https://github.com/aniampio))
- Feature/minor client changes [\#7](https://github.com/nymtech/nym/pull/7) ([jstuczyn](https://github.com/jstuczyn))
## [v0.2.0](https://github.com/nymtech/nym/tree/v0.2.0) (2020-01-07)
[Full Changelog](https://github.com/nymtech/nym/compare/3c64a2facd753f4f2f431e7f888e54842e2bc64e...v0.2.0)
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
Generated
+141 -120
View File
@@ -3,6 +3,10 @@
[[package]]
name = "addressing"
version = "0.1.0"
dependencies = [
"log",
"pretty_env_logger",
]
[[package]]
name = "adler32"
@@ -70,9 +74,9 @@ checksum = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff"
[[package]]
name = "arrayref"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
@@ -115,9 +119,9 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "backtrace"
version = "0.3.40"
version = "0.3.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
checksum = "b4b1549d804b6c73f4817df2ba073709e96e426f12987127c48e6745568c350b"
dependencies = [
"backtrace-sys",
"cfg-if",
@@ -209,6 +213,12 @@ dependencies = [
"byte-tools",
]
[[package]]
name = "bs58"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b170cd256a3f9fa6b9edae3e44a7dfdfc77e8124dbc3e2612d75f9c3e2396dae"
[[package]]
name = "built"
version = "0.3.2"
@@ -246,9 +256,9 @@ dependencies = [
[[package]]
name = "bytes"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10004c15deb332055f7a4a208190aed362cf9a7c2f6ab70a305fba50e1105f38"
checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
[[package]]
name = "c2-chacha"
@@ -330,9 +340,9 @@ dependencies = [
[[package]]
name = "colored"
version = "1.9.1"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f930f8b286023ed451756fe2527d73484d667adf9e905e9932e81d52996a343a"
checksum = "8815e2ab78f3a59928fc32e141fbeece88320a240e43f47b2fd64ea3a88a5b3d"
dependencies = [
"atty",
"lazy_static",
@@ -341,9 +351,9 @@ dependencies = [
[[package]]
name = "constant_time_eq"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "cookie"
@@ -456,10 +466,13 @@ dependencies = [
name = "crypto"
version = "0.1.0"
dependencies = [
"base64 0.11.0",
"bs58",
"curve25519-dalek",
"rand 0.7.2",
"log",
"pretty_env_logger",
"rand 0.7.3",
"rand_os",
"sphinx",
]
[[package]]
@@ -514,7 +527,9 @@ dependencies = [
name = "directory-client"
version = "0.1.0"
dependencies = [
"log",
"mockito",
"pretty_env_logger",
"reqwest",
"serde",
"topology",
@@ -582,19 +597,6 @@ dependencies = [
"termcolor",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "error-chain"
version = "0.12.1"
@@ -856,12 +858,15 @@ dependencies = [
"itertools",
"log",
"mix-client",
"pretty_env_logger",
"provider-client",
"rand 0.7.3",
"rand_os",
"serde",
"serde_derive",
"sfw-provider-requests",
"sphinx",
"tokio 0.2.9",
"tokio 0.2.10",
"topology",
]
@@ -874,12 +879,6 @@ dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e"
[[package]]
name = "hkdf"
version = "0.8.0"
@@ -1005,11 +1004,11 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.3.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2"
checksum = "0b54058f0a6ff80b6803da8faf8997cde53872b38f4023728f6830b06cd3c0dc"
dependencies = [
"autocfg 0.1.7",
"autocfg 1.0.0",
]
[[package]]
@@ -1041,15 +1040,15 @@ dependencies = [
[[package]]
name = "itoa"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
[[package]]
name = "jobserver"
version = "0.1.18"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "230ae9adf468173aecd4176c7233bddc84a15871a586c5971ace9a55f881c075"
checksum = "67b06c1b455f1cf4269a8cfc320ab930a810e2375a42af5075eb8a8b36405ce0"
dependencies = [
"libc",
]
@@ -1150,9 +1149,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memchr"
version = "2.2.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223"
[[package]]
name = "memoffset"
@@ -1256,9 +1255,14 @@ dependencies = [
name = "mix-client"
version = "0.1.0"
dependencies = [
"addressing",
"log",
"pretty_env_logger",
"rand 0.7.3",
"rand_distr",
"sphinx",
"tokio 0.2.9",
"tokio 0.2.10",
"topology",
]
[[package]]
@@ -1274,7 +1278,7 @@ dependencies = [
"lazy_static",
"log",
"percent-encoding 1.0.1",
"rand 0.7.2",
"rand 0.7.3",
"regex",
"serde_json",
]
@@ -1329,9 +1333,9 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.11.1"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
dependencies = [
"hermit-abi",
"libc",
@@ -1339,32 +1343,31 @@ dependencies = [
[[package]]
name = "nym-client"
version = "0.3.3"
version = "0.4.0-rc.1"
dependencies = [
"addressing",
"base64 0.11.0",
"bs58",
"built",
"clap",
"crypto",
"curve25519-dalek",
"directory-client",
"dirs",
"env_logger 0.7.1",
"dotenv",
"futures 0.3.1",
"healthcheck",
"hex",
"log",
"mix-client",
"pem",
"pemstore",
"pretty_env_logger",
"provider-client",
"rand 0.7.2",
"rand_distr",
"reqwest",
"serde",
"serde_json",
"sfw-provider-requests",
"sphinx",
"tokio 0.2.9",
"tokio 0.2.10",
"tokio-tungstenite",
"topology",
"tungstenite",
@@ -1372,49 +1375,53 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "0.3.3"
version = "0.4.0-rc.1"
dependencies = [
"addressing",
"base64 0.11.0",
"bs58",
"built",
"clap",
"curve25519-dalek",
"directory-client",
"dotenv",
"futures 0.3.1",
"log",
"pretty_env_logger",
"sphinx",
"tokio 0.2.9",
"tokio 0.2.10",
]
[[package]]
name = "nym-sfw-provider"
version = "0.3.3"
version = "0.4.0-rc.1"
dependencies = [
"base64 0.11.0",
"bs58",
"built",
"clap",
"crypto",
"curve25519-dalek",
"directory-client",
"dotenv",
"futures 0.3.1",
"hex",
"hmac",
"log",
"rand 0.7.2",
"pretty_env_logger",
"rand 0.7.3",
"serde",
"serde_json",
"sfw-provider-requests",
"sha2",
"sphinx",
"tokio 0.2.9",
"tokio 0.2.10",
]
[[package]]
name = "nym-validator"
version = "0.1.0"
version = "0.4.0-rc.1"
dependencies = [
"built",
"clap",
"crypto",
"dotenv",
"futures 0.3.1",
"healthcheck",
@@ -1422,7 +1429,7 @@ dependencies = [
"pretty_env_logger",
"serde",
"serde_derive",
"tokio 0.2.9",
"tokio 0.2.10",
"toml",
]
@@ -1502,6 +1509,16 @@ dependencies = [
"regex",
]
[[package]]
name = "pemstore"
version = "0.1.0"
dependencies = [
"crypto",
"log",
"pem",
"pretty_env_logger",
]
[[package]]
name = "percent-encoding"
version = "1.0.1"
@@ -1516,18 +1533,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pin-project"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94b90146c7216e4cb534069fb91366de4ea0ea353105ee45ed297e2d1619e469"
checksum = "75fca1c4ff21f60ca2d37b80d72b63dab823a9d19d3cda3a81d18bc03f0ba8c5"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44ca92f893f0656d3cba8158dd0f2b99b94de256a4a54e870bd6922fcc6c8355"
checksum = "6544cd4e4ecace61075a6ec78074beeef98d58aa9a3d07d053d993b2946a90d6"
dependencies = [
"proc-macro2",
"quote",
@@ -1536,9 +1553,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.1.2"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8822eb8bb72452f038ebf6048efa02c3fe22bf83f76519c9583e47fc194a422"
checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
[[package]]
name = "pin-utils"
@@ -1565,7 +1582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074"
dependencies = [
"chrono",
"env_logger 0.6.2",
"env_logger",
"log",
]
@@ -1588,9 +1605,9 @@ checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
[[package]]
name = "proc-macro2"
version = "1.0.7"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc"
checksum = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548"
dependencies = [
"unicode-xid",
]
@@ -1601,9 +1618,10 @@ version = "0.1.0"
dependencies = [
"futures 0.3.1",
"log",
"pretty_env_logger",
"sfw-provider-requests",
"sphinx",
"tokio 0.2.9",
"tokio 0.2.10",
]
[[package]]
@@ -1655,9 +1673,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
@@ -1716,7 +1734,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2"
dependencies = [
"rand 0.7.2",
"rand 0.7.3",
]
[[package]]
@@ -1807,12 +1825,11 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
[[package]]
name = "redox_users"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d"
checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431"
dependencies = [
"failure",
"rand_os",
"getrandom",
"redox_syscall",
"rust-argon2",
]
@@ -1880,13 +1897,14 @@ dependencies = [
[[package]]
name = "rust-argon2"
version = "0.5.1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
dependencies = [
"base64 0.10.1",
"base64 0.11.0",
"blake2b_simd",
"crossbeam-utils 0.6.6",
"constant_time_eq",
"crossbeam-utils 0.7.0",
]
[[package]]
@@ -1984,9 +2002,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.44"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
checksum = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b"
dependencies = [
"itoa",
"ryu",
@@ -2082,19 +2100,20 @@ dependencies = [
[[package]]
name = "sphinx"
version = "0.1.0"
source = "git+https://github.com/nymtech/sphinx?rev=1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f#1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f"
source = "git+https://github.com/nymtech/sphinx?rev=8424f4b0933b4a7f64ae828b2edefc5ba43a9e79#8424f4b0933b4a7f64ae828b2edefc5ba43a9e79"
dependencies = [
"aes-ctr",
"arrayref",
"base64 0.11.0",
"blake2",
"bs58",
"byteorder",
"chacha",
"curve25519-dalek",
"hkdf",
"hmac",
"lioness",
"rand 0.7.2",
"log",
"rand 0.7.3",
"rand_distr",
"rand_os",
"sha2",
@@ -2138,9 +2157,9 @@ checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
[[package]]
name = "syn"
version = "1.0.13"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8"
checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5"
dependencies = [
"proc-macro2",
"quote",
@@ -2167,7 +2186,7 @@ checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
dependencies = [
"cfg-if",
"libc",
"rand 0.7.2",
"rand 0.7.3",
"redox_syscall",
"remove_dir_all",
"winapi 0.3.8",
@@ -2175,11 +2194,11 @@ dependencies = [
[[package]]
name = "termcolor"
version = "1.0.5"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e"
checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
dependencies = [
"wincolor",
"winapi-util",
]
[[package]]
@@ -2193,9 +2212,9 @@ dependencies = [
[[package]]
name = "thread_local"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ddf1ad580c7e3d1efff877d972bcc93f995556b9087a5a259630985c88ceab"
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
dependencies = [
"lazy_static",
]
@@ -2232,11 +2251,11 @@ dependencies = [
[[package]]
name = "tokio"
version = "0.2.9"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffa2fdcfa937b20cb3c822a635ceecd5fc1a27a6a474527e5516aa24b8c8820a"
checksum = "c1fc73332507b971a5010664991a441b5ee0de92017f5a0e8b00fd684573045b"
dependencies = [
"bytes 0.5.3",
"bytes 0.5.4",
"fnv",
"futures-core",
"iovec",
@@ -2386,15 +2405,15 @@ dependencies = [
"futures 0.3.1",
"log",
"pin-project",
"tokio 0.2.9",
"tokio 0.2.10",
"tungstenite",
]
[[package]]
name = "toml"
version = "0.5.5"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf"
checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
dependencies = [
"serde",
]
@@ -2404,13 +2423,14 @@ name = "topology"
version = "0.1.0"
dependencies = [
"addressing",
"base64 0.11.0",
"curve25519-dalek",
"bs58",
"itertools",
"log",
"rand 0.7.2",
"pretty_env_logger",
"rand 0.7.3",
"serde",
"sphinx",
"version-checker",
]
[[package]]
@@ -2442,7 +2462,7 @@ dependencies = [
"input_buffer",
"log",
"native-tls",
"rand 0.7.2",
"rand 0.7.3",
"sha-1",
"url 2.1.1",
"utf-8",
@@ -2474,9 +2494,9 @@ dependencies = [
[[package]]
name = "unicode-normalization"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf"
checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
dependencies = [
"smallvec 1.1.0",
]
@@ -2533,6 +2553,10 @@ dependencies = [
[[package]]
name = "validator-client"
version = "0.1.0"
dependencies = [
"log",
"pretty_env_logger",
]
[[package]]
name = "vcpkg"
@@ -2546,6 +2570,13 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
[[package]]
name = "version-checker"
version = "0.1.0"
dependencies = [
"semver",
]
[[package]]
name = "version_check"
version = "0.1.5"
@@ -2605,9 +2636,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80"
dependencies = [
"winapi 0.3.8",
]
@@ -2618,16 +2649,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "wincolor"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96f5016b18804d24db43cebf3c77269e7569b8954a8464501c216cc5e070eaa9"
dependencies = [
"winapi 0.3.8",
"winapi-util",
]
[[package]]
name = "winreg"
version = "0.6.2"
+4
View File
@@ -1,3 +1,6 @@
[profile.release]
panic = "abort"
[workspace]
members = [
@@ -8,6 +11,7 @@ members = [
"common/addressing",
"common/crypto",
"common/healthcheck",
"common/pemstore",
"common/topology",
"mixnode",
"nym-client",
+2 -2
View File
@@ -4,8 +4,8 @@ This repository contains the full Nym platform, written in Rust.
The platform is composed of multiple Rust crates. Top-level executable binary crates include:
* client - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
* mixnode - the mixnode crate.
* mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
* nym-client - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
* sfw-provider - a store-and-forward service provider. The provider acts sort of like a mailbox for mixnet messages.
* validator - currently just starting development. Handles consensus ordering of transactions, mixmining, and coconut credential generation and validation.
+2
View File
@@ -7,3 +7,5 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4"
pretty_env_logger = "0.3"
+3 -3
View File
@@ -62,8 +62,8 @@ pub fn encoded_bytes_from_socket_address(address: SocketAddr) -> [u8; 32] {
address_bytes
}
pub fn socket_address_from_encoded_bytes(b: [u8; 32]) -> SocketAddr {
let address_type: AddressType = b[0].try_into().unwrap();
pub fn socket_address_from_encoded_bytes(b: [u8; 32]) -> Result<SocketAddr, AddressTypeError> {
let address_type: AddressType = b[0].try_into()?;
let port: u16 = u16::from_be_bytes([b[1], b[2]]);
@@ -76,5 +76,5 @@ pub fn socket_address_from_encoded_bytes(b: [u8; 32]) -> SocketAddr {
}
};
SocketAddr::new(ip, port)
Ok(SocketAddr::new(ip, port))
}
@@ -7,6 +7,8 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4"
pretty_env_logger = "0.3"
reqwest = "0.9.22"
serde = { version = "1.0.104", features = ["derive"] }
@@ -1,244 +0,0 @@
use crate::requests::presence_topology_get::PresenceTopologyGetRequester;
use crate::{Client, Config, DirectoryClient};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::io;
use std::net::ToSocketAddrs;
use topology::{CocoNode, MixNode, MixProviderNode, NymTopology};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CocoPresence {
pub host: String,
pub pub_key: String,
pub last_seen: u64,
pub version: String,
}
impl Into<topology::CocoNode> for CocoPresence {
fn into(self) -> topology::CocoNode {
topology::CocoNode {
host: self.host,
pub_key: self.pub_key,
last_seen: self.last_seen,
version: self.version,
}
}
}
impl From<topology::CocoNode> for CocoPresence {
fn from(cn: CocoNode) -> Self {
CocoPresence {
host: cn.host,
pub_key: cn.pub_key,
last_seen: cn.last_seen,
version: cn.version,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MixNodePresence {
pub host: String,
pub pub_key: String,
pub layer: u64,
pub last_seen: u64,
pub version: String,
}
impl TryInto<topology::MixNode> for MixNodePresence {
type Error = io::Error;
fn try_into(self) -> Result<MixNode, Self::Error> {
let resolved_hostname = self.host.to_socket_addrs()?.next();
if resolved_hostname.is_none() {
return Err(io::Error::new(
io::ErrorKind::Other,
"no valid socket address",
));
}
Ok(topology::MixNode {
host: resolved_hostname.unwrap(),
pub_key: self.pub_key,
layer: self.layer,
last_seen: self.last_seen,
version: self.version,
})
}
}
impl From<topology::MixNode> for MixNodePresence {
fn from(mn: MixNode) -> Self {
MixNodePresence {
host: mn.host.to_string(),
pub_key: mn.pub_key,
layer: mn.layer,
last_seen: mn.last_seen,
version: mn.version,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MixProviderPresence {
pub client_listener: String,
pub mixnet_listener: String,
pub pub_key: String,
pub registered_clients: Vec<MixProviderClient>,
pub last_seen: u64,
pub version: String,
}
impl Into<topology::MixProviderNode> for MixProviderPresence {
fn into(self) -> topology::MixProviderNode {
topology::MixProviderNode {
client_listener: self.client_listener.parse().unwrap(),
mixnet_listener: self.mixnet_listener.parse().unwrap(),
pub_key: self.pub_key,
registered_clients: self
.registered_clients
.into_iter()
.map(|c| c.into())
.collect(),
last_seen: self.last_seen,
version: self.version,
}
}
}
impl From<topology::MixProviderNode> for MixProviderPresence {
fn from(mpn: MixProviderNode) -> Self {
MixProviderPresence {
client_listener: mpn.client_listener.to_string(),
mixnet_listener: mpn.mixnet_listener.to_string(),
pub_key: mpn.pub_key,
registered_clients: mpn
.registered_clients
.into_iter()
.map(|c| c.into())
.collect(),
last_seen: mpn.last_seen,
version: mpn.version,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MixProviderClient {
pub pub_key: String,
}
impl Into<topology::MixProviderClient> for MixProviderClient {
fn into(self) -> topology::MixProviderClient {
topology::MixProviderClient {
pub_key: self.pub_key,
}
}
}
impl From<topology::MixProviderClient> for MixProviderClient {
fn from(mpc: topology::MixProviderClient) -> Self {
MixProviderClient {
pub_key: mpc.pub_key,
}
}
}
// Topology shows us the current state of the overall Nym network
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Topology {
pub coco_nodes: Vec<CocoPresence>,
pub mix_nodes: Vec<MixNodePresence>,
pub mix_provider_nodes: Vec<MixProviderPresence>,
}
impl NymTopology for Topology {
fn new(directory_server: String) -> Self {
println!("Using directory server: {:?}", directory_server);
let directory_config = Config {
base_url: directory_server,
};
let directory = Client::new(directory_config);
let topology = directory
.presence_topology
.get()
.expect("Failed to retrieve network topology.");
topology
}
fn new_from_nodes(
mix_nodes: Vec<MixNode>,
mix_provider_nodes: Vec<MixProviderNode>,
coco_nodes: Vec<CocoNode>,
) -> Self {
Topology {
coco_nodes: coco_nodes.into_iter().map(|node| node.into()).collect(),
mix_nodes: mix_nodes.into_iter().map(|node| node.into()).collect(),
mix_provider_nodes: mix_provider_nodes
.into_iter()
.map(|node| node.into())
.collect(),
}
}
fn get_mix_nodes(&self) -> Vec<topology::MixNode> {
self.mix_nodes
.iter()
.filter_map(|x| x.clone().try_into().ok())
.collect()
}
fn get_mix_provider_nodes(&self) -> Vec<topology::MixProviderNode> {
self.mix_provider_nodes
.iter()
.map(|x| x.clone().into())
.collect()
}
fn get_coco_nodes(&self) -> Vec<topology::CocoNode> {
self.coco_nodes.iter().map(|x| x.clone().into()).collect()
}
}
#[cfg(test)]
mod converting_mixnode_presence_into_topology_mixnode {
use super::*;
#[test]
fn it_returns_error_on_unresolvable_hostname() {
let unresolvable_hostname = "foomp.foomp.foomp:1234";
let mix_presence = MixNodePresence {
host: unresolvable_hostname.to_string(),
pub_key: "".to_string(),
layer: 0,
last_seen: 0,
version: "".to_string(),
};
let result: Result<topology::MixNode, io::Error> = mix_presence.try_into();
assert!(result.is_err())
}
#[test]
fn it_returns_resolved_ip_on_resolvable_hostname() {
let resolvable_hostname = "nymtech.net:1234";
let mix_presence = MixNodePresence {
host: resolvable_hostname.to_string(),
pub_key: "".to_string(),
layer: 0,
last_seen: 0,
version: "".to_string(),
};
let result: Result<topology::MixNode, io::Error> = mix_presence.try_into();
assert!(result.is_ok())
}
}
@@ -0,0 +1,33 @@
use serde::{Deserialize, Serialize};
use topology::coco;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CocoPresence {
pub host: String,
pub pub_key: String,
pub last_seen: u64,
pub version: String,
}
impl Into<topology::coco::Node> for CocoPresence {
fn into(self) -> topology::coco::Node {
topology::coco::Node {
host: self.host,
pub_key: self.pub_key,
last_seen: self.last_seen,
version: self.version,
}
}
}
impl From<topology::coco::Node> for CocoPresence {
fn from(cn: coco::Node) -> Self {
CocoPresence {
host: cn.host,
pub_key: cn.pub_key,
last_seen: cn.last_seen,
version: cn.version,
}
}
}
@@ -0,0 +1,49 @@
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::io;
use std::net::ToSocketAddrs;
use topology::mix;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MixNodePresence {
pub host: String,
pub pub_key: String,
pub layer: u64,
pub last_seen: u64,
pub version: String,
}
impl TryInto<topology::mix::Node> for MixNodePresence {
type Error = io::Error;
fn try_into(self) -> Result<topology::mix::Node, Self::Error> {
let resolved_hostname = self.host.to_socket_addrs()?.next();
if resolved_hostname.is_none() {
return Err(io::Error::new(
io::ErrorKind::Other,
"no valid socket address",
));
}
Ok(topology::mix::Node {
host: resolved_hostname.unwrap(),
pub_key: self.pub_key,
layer: self.layer,
last_seen: self.last_seen,
version: self.version,
})
}
}
impl From<topology::mix::Node> for MixNodePresence {
fn from(mn: mix::Node) -> Self {
MixNodePresence {
host: mn.host.to_string(),
pub_key: mn.pub_key,
layer: mn.layer,
last_seen: mn.last_seen,
version: mn.version,
}
}
}
@@ -0,0 +1,108 @@
use crate::requests::presence_topology_get::PresenceTopologyGetRequester;
use crate::{Client, Config, DirectoryClient};
use log::*;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use topology::coco;
use topology::mix;
use topology::provider;
use topology::NymTopology;
pub mod coconodes;
pub mod mixnodes;
pub mod providers;
// Topology shows us the current state of the overall Nym network
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Topology {
pub coco_nodes: Vec<coconodes::CocoPresence>,
pub mix_nodes: Vec<mixnodes::MixNodePresence>,
pub mix_provider_nodes: Vec<providers::MixProviderPresence>,
}
impl NymTopology for Topology {
fn new(directory_server: String) -> Self {
debug!("Using directory server: {:?}", directory_server);
let directory_config = Config {
base_url: directory_server,
};
let directory = Client::new(directory_config);
let topology = directory
.presence_topology
.get()
.expect("Failed to retrieve network topology.");
topology
}
fn new_from_nodes(
mix_nodes: Vec<mix::Node>,
mix_provider_nodes: Vec<provider::Node>,
coco_nodes: Vec<coco::Node>,
) -> Self {
Topology {
coco_nodes: coco_nodes.into_iter().map(|node| node.into()).collect(),
mix_nodes: mix_nodes.into_iter().map(|node| node.into()).collect(),
mix_provider_nodes: mix_provider_nodes
.into_iter()
.map(|node| node.into())
.collect(),
}
}
fn mix_nodes(&self) -> Vec<mix::Node> {
self.mix_nodes
.iter()
.filter_map(|x| x.clone().try_into().ok())
.collect()
}
fn providers(&self) -> Vec<provider::Node> {
self.mix_provider_nodes
.iter()
.map(|x| x.clone().into())
.collect()
}
fn coco_nodes(&self) -> Vec<topology::coco::Node> {
self.coco_nodes.iter().map(|x| x.clone().into()).collect()
}
}
#[cfg(test)]
mod converting_mixnode_presence_into_topology_mixnode {
use super::*;
#[test]
fn it_returns_error_on_unresolvable_hostname() {
let unresolvable_hostname = "foomp.foomp.foomp:1234";
let mix_presence = mixnodes::MixNodePresence {
host: unresolvable_hostname.to_string(),
pub_key: "".to_string(),
layer: 0,
last_seen: 0,
version: "".to_string(),
};
let result: Result<mix::Node, std::io::Error> = mix_presence.try_into();
assert!(result.is_err())
}
#[test]
fn it_returns_resolved_ip_on_resolvable_hostname() {
let resolvable_hostname = "nymtech.net:1234";
let mix_presence = mixnodes::MixNodePresence {
host: resolvable_hostname.to_string(),
pub_key: "".to_string(),
layer: 0,
last_seen: 0,
version: "".to_string(),
};
let result: Result<topology::mix::Node, std::io::Error> = mix_presence.try_into();
assert!(result.is_ok())
}
}
@@ -0,0 +1,69 @@
use serde::{Deserialize, Serialize};
use topology::provider;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MixProviderPresence {
pub client_listener: String,
pub mixnet_listener: String,
pub pub_key: String,
pub registered_clients: Vec<MixProviderClient>,
pub last_seen: u64,
pub version: String,
}
impl Into<topology::provider::Node> for MixProviderPresence {
fn into(self) -> topology::provider::Node {
topology::provider::Node {
client_listener: self.client_listener.parse().unwrap(),
mixnet_listener: self.mixnet_listener.parse().unwrap(),
pub_key: self.pub_key,
registered_clients: self
.registered_clients
.into_iter()
.map(|c| c.into())
.collect(),
last_seen: self.last_seen,
version: self.version,
}
}
}
impl From<topology::provider::Node> for MixProviderPresence {
fn from(mpn: provider::Node) -> Self {
MixProviderPresence {
client_listener: mpn.client_listener.to_string(),
mixnet_listener: mpn.mixnet_listener.to_string(),
pub_key: mpn.pub_key,
registered_clients: mpn
.registered_clients
.into_iter()
.map(|c| c.into())
.collect(),
last_seen: mpn.last_seen,
version: mpn.version,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MixProviderClient {
pub pub_key: String,
}
impl Into<topology::provider::Client> for MixProviderClient {
fn into(self) -> topology::provider::Client {
topology::provider::Client {
pub_key: self.pub_key,
}
}
}
impl From<topology::provider::Client> for MixProviderClient {
fn from(mpc: topology::provider::Client) -> Self {
MixProviderClient {
pub_key: mpc.pub_key,
}
}
}
@@ -1,4 +1,4 @@
use crate::presence::CocoPresence;
use crate::presence::coconodes::CocoPresence;
use reqwest::Response;
pub struct Request {
@@ -73,7 +73,7 @@ mod metrics_get_request {
#[cfg(test)]
mod fixtures {
use crate::presence::CocoPresence;
use crate::presence::coconodes::CocoPresence;
pub fn new_presence() -> CocoPresence {
CocoPresence {
@@ -1,4 +1,4 @@
use crate::presence::MixNodePresence;
use crate::presence::mixnodes::MixNodePresence;
use reqwest::Response;
pub struct Request {
@@ -73,7 +73,7 @@ mod metrics_get_request {
#[cfg(test)]
mod fixtures {
use crate::presence::MixNodePresence;
use crate::presence::mixnodes::MixNodePresence;
pub fn new_presence() -> MixNodePresence {
MixNodePresence {
@@ -1,4 +1,4 @@
use crate::presence::MixProviderPresence;
use crate::presence::providers::MixProviderPresence;
use reqwest::Response;
pub struct Request {
@@ -72,7 +72,7 @@ mod metrics_get_request {
}
#[cfg(test)]
mod fixtures {
use crate::presence::MixProviderPresence;
use crate::presence::providers::MixProviderPresence;
pub fn new_presence() -> MixProviderPresence {
MixProviderPresence {
+10 -1
View File
@@ -8,7 +8,16 @@ edition = "2018"
[dependencies]
log = "0.4.8"
pretty_env_logger = "0.3"
rand = "0.7.2"
rand_distr = "0.2.2"
tokio = { version = "0.2", features = ["full"] }
## internal
addressing = {path = "../../addressing"}
topology = {path = "../../topology"}
## will be moved to proper dependencies once released
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
sphinx = { git = "https://github.com/nymtech/sphinx", rev="8424f4b0933b4a7f64ae828b2edefc5ba43a9e79" }
# sphinx = { path = "../../../../sphinx"}
+4 -2
View File
@@ -3,6 +3,9 @@ use sphinx::SphinxPacket;
use std::net::SocketAddr;
use tokio::prelude::*;
pub mod packet;
pub mod poisson;
pub struct MixClient {}
impl MixClient {
@@ -17,8 +20,7 @@ impl MixClient {
mix_addr: SocketAddr,
) -> Result<(), Box<dyn std::error::Error>> {
let bytes = packet.to_bytes();
info!("socket addr: {:?}", mix_addr);
debug!("Sending to the following address: {:?}", mix_addr);
let mut stream = tokio::net::TcpStream::connect(mix_addr).await?;
stream.write_all(&bytes[..]).await?;
+82
View File
@@ -0,0 +1,82 @@
use addressing;
use addressing::AddressTypeError;
use sphinx::route::{Destination, DestinationAddressBytes, SURBIdentifier};
use sphinx::SphinxPacket;
use std::net::SocketAddr;
use topology::{NymTopology, NymTopologyError};
pub const LOOP_COVER_MESSAGE_PAYLOAD: &[u8] = b"The cake is a lie!";
pub const LOOP_COVER_MESSAGE_AVERAGE_DELAY: f64 = 2.0;
#[derive(Debug)]
pub enum SphinxPacketEncapsulationError {
NoValidProvidersError,
InvalidTopologyError,
SphinxEncapsulationError(sphinx::header::SphinxUnwrapError),
InvalidFirstMixAddress,
}
impl From<topology::NymTopologyError> for SphinxPacketEncapsulationError {
fn from(_: NymTopologyError) -> Self {
use SphinxPacketEncapsulationError::*;
InvalidTopologyError
}
}
// it is correct error we're converting from, it just has an unfortunate name
// related issue: https://github.com/nymtech/sphinx/issues/40
impl From<sphinx::header::SphinxUnwrapError> for SphinxPacketEncapsulationError {
fn from(err: sphinx::header::SphinxUnwrapError) -> Self {
use SphinxPacketEncapsulationError::*;
SphinxEncapsulationError(err)
}
}
impl From<AddressTypeError> for SphinxPacketEncapsulationError {
fn from(_: AddressTypeError) -> Self {
use SphinxPacketEncapsulationError::*;
InvalidFirstMixAddress
}
}
pub fn loop_cover_message<T: NymTopology>(
our_address: DestinationAddressBytes,
surb_id: SURBIdentifier,
topology: &T,
) -> Result<(SocketAddr, SphinxPacket), SphinxPacketEncapsulationError> {
let destination = Destination::new(our_address, surb_id);
encapsulate_message(
destination,
LOOP_COVER_MESSAGE_PAYLOAD.to_vec(),
topology,
LOOP_COVER_MESSAGE_AVERAGE_DELAY,
)
}
pub fn encapsulate_message<T: NymTopology>(
recipient: Destination,
message: Vec<u8>,
topology: &T,
average_delay: f64,
) -> Result<(SocketAddr, SphinxPacket), SphinxPacketEncapsulationError> {
let mut providers = topology.providers();
if providers.len() == 0 {
return Err(SphinxPacketEncapsulationError::NoValidProvidersError);
}
// unwrap is fine here as we asserted there is at least single provider
let provider = providers.pop().unwrap().into();
let route = topology.route_to(provider)?;
let delays = sphinx::header::delays::generate(route.len(), average_delay);
// build the packet
let packet = sphinx::SphinxPacket::new(message, &route[..], &recipient, &delays)?;
// we know the mix route must be valid otherwise we would have already returned an error
let first_node_address =
addressing::socket_address_from_encoded_bytes(route.first().unwrap().address.to_bytes())?;
Ok((first_node_address, packet))
}
+9
View File
@@ -0,0 +1,9 @@
use rand_distr::{Distribution, Exp};
pub fn sample(average_delay: f64) -> f64 {
// this is our internal code used by our traffic streams
// the error is only thrown if average delay is less than 0, which will never happen
// so call to unwrap is perfectly safe here
let exp = Exp::new(1.0 / average_delay).unwrap();
exp.sample(&mut rand::thread_rng())
}
+2 -1
View File
@@ -9,10 +9,11 @@ edition = "2018"
[dependencies]
futures = "0.3.1"
log = "0.4.8"
pretty_env_logger = "0.3"
tokio = { version = "0.2", features = ["full"] }
## internal
sfw-provider-requests = { path = "../../../sfw-provider/sfw-provider-requests" }
## will be moved to proper dependencies once released
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
sphinx = { git = "https://github.com/nymtech/sphinx", rev="8424f4b0933b4a7f64ae828b2edefc5ba43a9e79" }
+18 -14
View File
@@ -1,5 +1,5 @@
use futures::io::Error;
use log::info;
use log::*;
use sfw_provider_requests::requests::{ProviderRequest, PullRequest, RegisterRequest};
use sfw_provider_requests::responses::{
ProviderResponse, ProviderResponseError, PullResponse, RegisterResponse,
@@ -65,34 +65,34 @@ impl ProviderClient {
pub async fn send_request(&self, bytes: Vec<u8>) -> Result<Vec<u8>, ProviderClientError> {
let mut socket = tokio::net::TcpStream::connect(self.provider_network_address).await?;
info!("keep alive: {:?}", socket.keepalive());
socket.set_keepalive(Some(Duration::from_secs(2))).unwrap();
socket.set_keepalive(Some(Duration::from_secs(2)))?;
socket.write_all(&bytes[..]).await?;
if let Err(_e) = socket.shutdown(Shutdown::Write) {
// TODO: make it a silent log once we have a proper logging library
// eprintln!("failed to close write part of the socket; err = {:?}", e)
if let Err(e) = socket.shutdown(Shutdown::Write) {
warn!("failed to close write part of the socket; err = {:?}", e)
}
let mut response = Vec::new();
socket.read_to_end(&mut response).await?;
if let Err(_e) = socket.shutdown(Shutdown::Read) {
// TODO: make it a silent log once we have a proper logging library
// eprintln!("failed to close read part of the socket; err = {:?}", e)
if let Err(e) = socket.shutdown(Shutdown::Read) {
debug!("failed to close read part of the socket; err = {:?}. It was probably already closed by the provider", e)
}
Ok(response)
}
pub async fn retrieve_messages(&self) -> Result<Vec<Vec<u8>>, ProviderClientError> {
if self.auth_token.is_none() {
return Err(ProviderClientError::EmptyAuthTokenError);
}
let auth_token = match self.auth_token {
Some(token) => token,
None => {
return Err(ProviderClientError::EmptyAuthTokenError);
}
};
let pull_request = PullRequest::new(self.our_address, self.auth_token.unwrap());
let pull_request = PullRequest::new(self.our_address, auth_token);
let bytes = pull_request.to_bytes();
let response = self.send_request(bytes).await?;
info!("Received the following response: {:?}", response);
let parsed_response = PullResponse::from_bytes(&response)?;
Ok(parsed_response.messages)
@@ -111,4 +111,8 @@ impl ProviderClient {
Ok(parsed_response.auth_token)
}
pub fn is_registered(&self) -> bool {
self.auth_token.is_some()
}
}
@@ -7,3 +7,5 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4"
pretty_env_logger = "0.3"
+6 -1
View File
@@ -7,7 +7,12 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = "0.11.0"
bs58 = "0.3.0"
curve25519-dalek = "1.2.3"
log = "0.4"
pretty_env_logger = "0.3"
rand = "0.7.2"
rand_os = "0.1"
## will be moved to proper dependencies once released
sphinx = { git = "https://github.com/nymtech/sphinx", rev="8424f4b0933b4a7f64ae828b2edefc5ba43a9e79" }
+1
View File
@@ -56,6 +56,7 @@ impl MixnetEncryptionPrivateKey for PrivateKey {
fn from_bytes(b: &[u8]) -> Self {
let mut bytes = [0; 32];
bytes.copy_from_slice(&b[..]);
// due to trait restriction we have no choice but to panic if this fails
let key = Scalar::from_canonical_bytes(bytes).unwrap();
Self(key)
}
+22 -8
View File
@@ -2,9 +2,11 @@ use crate::encryption::{
MixnetEncryptionKeyPair, MixnetEncryptionPrivateKey, MixnetEncryptionPublicKey,
};
use crate::{encryption, PemStorable};
use bs58;
use curve25519_dalek::scalar::Scalar;
use sphinx::route::DestinationAddressBytes;
pub trait MixnetIdentityKeyPair<Priv, Pub>
pub trait MixnetIdentityKeyPair<Priv, Pub>: Clone
where
Priv: MixnetIdentityPrivateKey,
Pub: MixnetIdentityPublicKey,
@@ -18,16 +20,20 @@ where
}
pub trait MixnetIdentityPublicKey:
Sized + PemStorable + for<'a> From<&'a <Self as MixnetIdentityPublicKey>::PrivateKeyMaterial>
Sized
+ PemStorable
+ Clone
+ for<'a> From<&'a <Self as MixnetIdentityPublicKey>::PrivateKeyMaterial>
{
// we need to couple public and private keys together
type PrivateKeyMaterial: MixnetIdentityPrivateKey<PublicKeyMaterial = Self>;
fn derive_address(&self) -> DestinationAddressBytes;
fn to_bytes(&self) -> Vec<u8>;
fn from_bytes(b: &[u8]) -> Self;
}
pub trait MixnetIdentityPrivateKey: Sized + PemStorable {
pub trait MixnetIdentityPrivateKey: Sized + PemStorable + Clone {
// we need to couple public and private keys together
type PublicKeyMaterial: MixnetIdentityPublicKey<PrivateKeyMaterial = Self>;
@@ -44,7 +50,7 @@ pub trait MixnetIdentityPrivateKey: Sized + PemStorable {
// for time being define a dummy identity using x25519 encryption keys (as we've done so far)
// and replace it with proper keys, like ed25519 later on
#[derive(Clone)]
pub struct DummyMixIdentityKeyPair {
pub private_key: DummyMixIdentityPrivateKey,
pub public_key: DummyMixIdentityPublicKey,
@@ -83,6 +89,14 @@ pub struct DummyMixIdentityPublicKey(encryption::x25519::PublicKey);
impl MixnetIdentityPublicKey for DummyMixIdentityPublicKey {
type PrivateKeyMaterial = DummyMixIdentityPrivateKey;
fn derive_address(&self) -> DestinationAddressBytes {
let mut temporary_address = [0u8; 32];
let public_key_bytes = self.to_bytes();
temporary_address.copy_from_slice(&public_key_bytes[..]);
temporary_address
}
fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes()
}
@@ -99,13 +113,13 @@ impl PemStorable for DummyMixIdentityPublicKey {
}
impl DummyMixIdentityPublicKey {
pub fn to_b64_string(&self) -> String {
base64::encode_config(&self.to_bytes(), base64::URL_SAFE)
pub fn to_base58_string(&self) -> String {
bs58::encode(&self.to_bytes()).into_string()
}
#[allow(dead_code)]
fn from_b64_string(val: String) -> Self {
Self::from_bytes(&base64::decode_config(&val, base64::URL_SAFE).unwrap())
fn from_base58_string(val: String) -> Self {
Self::from_bytes(&bs58::decode(&val).into_vec().unwrap())
}
}
+5 -1
View File
@@ -10,6 +10,9 @@ edition = "2018"
futures = "0.3.1"
itertools = "0.8.2"
log = "0.4.8"
pretty_env_logger = "0.3"
rand_os = "0.1"
rand = "0.7.2"
serde = "1.0.104"
serde_derive = "1.0.104"
tokio = { version = "0.2", features = ["full"] }
@@ -24,6 +27,7 @@ sfw-provider-requests = { path = "../../sfw-provider/sfw-provider-requests" }
topology = {path = "../topology" }
## will be moved to proper dependencies once released
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
sphinx = { git = "https://github.com/nymtech/sphinx", rev="8424f4b0933b4a7f64ae828b2edefc5ba43a9e79" }
# sphinx = { path = "../../../sphinx"}
[dev-dependencies]
+25 -3
View File
@@ -1,8 +1,10 @@
use crate::result::HealthCheckResult;
use crypto::identity::{MixnetIdentityKeyPair, MixnetIdentityPrivateKey, MixnetIdentityPublicKey};
use directory_client::requests::presence_topology_get::PresenceTopologyGetRequester;
use directory_client::DirectoryClient;
use log::{debug, error, info, trace};
use std::fmt::{Error, Formatter};
use std::marker::PhantomData;
use std::time::Duration;
use topology::NymTopologyError;
@@ -34,15 +36,29 @@ impl From<topology::NymTopologyError> for HealthCheckerError {
}
}
pub struct HealthChecker {
pub struct HealthChecker<IDPair, Priv, Pub>
where
IDPair: MixnetIdentityKeyPair<Priv, Pub>,
Priv: MixnetIdentityPrivateKey,
Pub: MixnetIdentityPublicKey,
{
directory_client: directory_client::Client,
interval: Duration,
num_test_packets: usize,
resolution_timeout: Duration,
identity_keypair: IDPair,
_phantom_private: PhantomData<Priv>,
_phantom_public: PhantomData<Pub>,
}
impl HealthChecker {
pub fn new(config: config::HealthCheck) -> Self {
impl<IDPair, Priv, Pub> HealthChecker<IDPair, Priv, Pub>
where
IDPair: crypto::identity::MixnetIdentityKeyPair<Priv, Pub>,
Priv: crypto::identity::MixnetIdentityPrivateKey,
Pub: crypto::identity::MixnetIdentityPublicKey,
{
pub fn new(config: config::HealthCheck, identity_keypair: IDPair) -> Self {
debug!(
"healthcheck will be using the following directory server: {:?}",
config.directory_server
@@ -53,11 +69,16 @@ impl HealthChecker {
interval: Duration::from_secs_f64(config.interval),
resolution_timeout: Duration::from_secs_f64(config.resolution_timeout),
num_test_packets: config.num_test_packets,
identity_keypair,
_phantom_private: PhantomData,
_phantom_public: PhantomData,
}
}
pub async fn do_check(&self) -> Result<HealthCheckResult, HealthCheckerError> {
trace!("going to perform a healthcheck!");
let current_topology = match self.directory_client.presence_topology.get() {
Ok(topology) => topology,
Err(err) => {
@@ -71,6 +92,7 @@ impl HealthChecker {
current_topology,
self.num_test_packets,
self.resolution_timeout,
&self.identity_keypair,
)
.await;
healthcheck_result.sort_scores();
+61 -27
View File
@@ -1,12 +1,12 @@
use crypto::identity::{DummyMixIdentityKeyPair, MixnetIdentityKeyPair, MixnetIdentityPublicKey};
use crypto::identity::{MixnetIdentityKeyPair, MixnetIdentityPrivateKey, MixnetIdentityPublicKey};
use itertools::Itertools;
use log::{debug, error, trace, warn};
use log::{debug, error, info, trace, warn};
use mix_client::MixClient;
use provider_client::ProviderClient;
use provider_client::{ProviderClient, ProviderClientError};
use sphinx::header::delays::Delay;
use sphinx::route::{Destination, Node as SphinxNode};
use std::collections::HashMap;
use topology::MixProviderNode;
use topology::provider;
#[derive(Debug, PartialEq, Clone)]
pub enum PathStatus {
@@ -23,28 +23,36 @@ pub(crate) struct PathChecker {
layer_one_clients: HashMap<[u8; 32], Option<MixClient>>,
paths_status: HashMap<Vec<u8>, PathStatus>,
our_destination: Destination,
check_id: [u8; 16],
}
impl PathChecker {
pub(crate) async fn new(
providers: Vec<MixProviderNode>,
ephemeral_keys: DummyMixIdentityKeyPair,
) -> Self {
pub(crate) async fn new<IDPair, Priv, Pub>(
providers: Vec<provider::Node>,
identity_keys: &IDPair,
check_id: [u8; 16],
) -> Self
where
IDPair: MixnetIdentityKeyPair<Priv, Pub>,
Priv: MixnetIdentityPrivateKey,
Pub: MixnetIdentityPublicKey,
{
let mut provider_clients = HashMap::new();
let mut temporary_address = [0u8; 32];
let public_key_bytes = ephemeral_keys.public_key().to_bytes();
temporary_address.copy_from_slice(&public_key_bytes[..]);
let address = identity_keys.public_key().derive_address();
for provider in providers {
let mut provider_client =
ProviderClient::new(provider.client_listener, temporary_address, None);
let mut provider_client = ProviderClient::new(provider.client_listener, address, None);
let insertion_result = match provider_client.register().await {
Ok(token) => {
debug!("registered at provider {}", provider.pub_key);
provider_client.update_token(token);
provider_clients.insert(provider.get_pub_key_bytes(), Some(provider_client))
}
Err(ProviderClientError::ClientAlreadyRegisteredError) => {
info!("We were already registered");
provider_clients.insert(provider.get_pub_key_bytes(), Some(provider_client))
}
Err(err) => {
warn!(
"failed to register at provider {} - {:?}",
@@ -62,15 +70,19 @@ impl PathChecker {
PathChecker {
provider_clients,
layer_one_clients: HashMap::new(),
our_destination: Destination::new(temporary_address, Default::default()),
our_destination: Destination::new(address, Default::default()),
paths_status: HashMap::new(),
check_id,
}
}
// iteration is used to distinguish packets sent through the same path (as the healthcheck
// may try to send say 10 packets through given path)
fn unique_path_key(path: &Vec<SphinxNode>, iteration: u8) -> Vec<u8> {
std::iter::once(iteration)
fn unique_path_key(path: &Vec<SphinxNode>, check_id: [u8; 16], iteration: u8) -> Vec<u8> {
check_id
.iter()
.cloned()
.chain(std::iter::once(iteration))
.chain(
path.iter()
.map(|node| node.pub_key.to_bytes().to_vec())
@@ -80,10 +92,10 @@ impl PathChecker {
}
pub(crate) fn path_key_to_node_keys(path_key: Vec<u8>) -> Vec<[u8; 32]> {
assert_eq!(path_key.len() % 32, 1);
assert_eq!(path_key.len() % 32, 17);
path_key
.into_iter()
.skip(1) // remove first byte as it represents the iteration number which we do not care about now
.skip(16 + 1) // remove 16 + 1 bytes as it represents check_id and the iteration number which we do not care about now
.chunks(32)
.into_iter()
.map(|key_chunk| {
@@ -135,6 +147,8 @@ impl PathChecker {
if msg == sfw_provider_requests::DUMMY_MESSAGE_CONTENT {
// finish iterating the loop as the messages might not be ordered
should_stop = true;
} else if msg[..16] != self.check_id {
warn!("received response from previous healthcheck")
} else {
provider_messages.push(msg);
}
@@ -153,25 +167,38 @@ impl PathChecker {
let mut provider_messages = Vec::new();
for provider_client in self.provider_clients.values() {
// if it was none all associated paths were already marked as unhealthy
if provider_client.is_some() {
let pc = provider_client.as_ref().unwrap();
provider_messages.extend(self.resolve_pending_provider_checks(pc).await);
}
let pc = match provider_client {
Some(pc) => pc,
None => continue,
};
provider_messages.extend(self.resolve_pending_provider_checks(pc).await);
}
self.update_path_statuses(provider_messages);
}
pub(crate) async fn send_test_packet(&mut self, path: &Vec<SphinxNode>, iteration: u8) {
if path.len() == 0 {
warn!("trying to send test packet through an empty path!");
return;
}
debug!("Checking path: {:?} ({})", path, iteration);
let path_identifier = PathChecker::unique_path_key(path, iteration);
let path_identifier = PathChecker::unique_path_key(path, self.check_id, iteration);
// check if there is even any point in sending the packet
// does provider exist?
let provider_client = self
.provider_clients
.get(&path.last().unwrap().pub_key.to_bytes())
.get(
&path
.last()
.expect("We checked the path to contain at least one entry")
.pub_key
.to_bytes(),
)
.unwrap();
if provider_client.is_none() {
@@ -186,10 +213,15 @@ impl PathChecker {
return;
}
let layer_one_mix = path.first().unwrap();
let layer_one_mix = path
.first()
.expect("We checked the path to contain at least one entry");
let first_node_key = layer_one_mix.pub_key.to_bytes();
// we generated the bytes data so unwrap is fine
let first_node_address =
addressing::socket_address_from_encoded_bytes(layer_one_mix.address.to_bytes());
addressing::socket_address_from_encoded_bytes(layer_one_mix.address.to_bytes())
.unwrap();
let first_node_client = self
.layer_one_clients
@@ -208,10 +240,12 @@ impl PathChecker {
return;
}
// we already checked for 'None' case
let first_node_client = first_node_client.as_ref().unwrap();
let delays: Vec<_> = path.iter().map(|_| Delay::new(0)).collect();
// all of the data used to create the packet was created by us
let packet = sphinx::SphinxPacket::new(
path_identifier.clone(),
&path[..],
@@ -223,7 +257,7 @@ impl PathChecker {
debug!("sending test packet to {}", first_node_address);
match first_node_client.send(packet, first_node_address).await {
Err(err) => {
warn!("failed to send packet to {} - {}", first_node_address, err);
info!("failed to send packet to {} - {}", first_node_address, err);
if self
.paths_status
.insert(path_identifier, PathStatus::Unhealthy)
+36 -23
View File
@@ -1,7 +1,8 @@
use crate::path_check::{PathChecker, PathStatus};
use crate::score::NodeScore;
use crypto::identity::{DummyMixIdentityKeyPair, MixnetIdentityKeyPair};
use crypto::identity::{MixnetIdentityKeyPair, MixnetIdentityPrivateKey, MixnetIdentityPublicKey};
use log::{debug, error, info, warn};
use rand_os::rand_core::RngCore;
use sphinx::route::NodeAddressBytes;
use std::collections::HashMap;
use std::fmt::{Error, Formatter};
@@ -14,9 +15,9 @@ pub struct HealthCheckResult(Vec<NodeScore>);
impl std::fmt::Display for HealthCheckResult {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "NETWORK HEALTH\n==============\n")?;
self.0
.iter()
.for_each(|score| write!(f, "{}\n", score).unwrap());
for score in self.0.iter() {
write!(f, "{}\n", score)?
}
Ok(())
}
}
@@ -28,8 +29,8 @@ impl HealthCheckResult {
fn zero_score<T: NymTopology>(topology: T) -> Self {
warn!("The network is unhealthy, could not send any packets - returning zero score!");
let mixes = topology.get_mix_nodes();
let providers = topology.get_mix_provider_nodes();
let mixes = topology.mix_nodes();
let providers = topology.providers();
let health = mixes
.into_iter()
@@ -58,10 +59,10 @@ impl HealthCheckResult {
score_threshold: f64,
) -> T {
let filtered_mix_nodes = topology
.get_mix_nodes()
.mix_nodes()
.into_iter()
.filter(|node| {
match self.node_score(NodeAddressBytes::from_b64_string(node.pub_key.clone())) {
match self.node_score(NodeAddressBytes::from_base58_string(node.pub_key.clone())) {
None => {
error!("Unknown node in topology - {:?}", node);
false
@@ -72,10 +73,10 @@ impl HealthCheckResult {
.collect();
let filtered_provider_nodes = topology
.get_mix_provider_nodes()
.providers()
.into_iter()
.filter(|node| {
match self.node_score(NodeAddressBytes::from_b64_string(node.pub_key.clone())) {
match self.node_score(NodeAddressBytes::from_base58_string(node.pub_key.clone())) {
None => {
error!("Unknown node in topology - {:?}", node);
false
@@ -85,7 +86,7 @@ impl HealthCheckResult {
})
.collect();
// coco nodes remain unchanged as no healthcheck is being run on them or time being
let filtered_coco_nodes = topology.get_coco_nodes();
let filtered_coco_nodes = topology.coco_nodes();
T::new_from_nodes(
filtered_mix_nodes,
@@ -94,15 +95,31 @@ impl HealthCheckResult {
)
}
pub async fn calculate<T: NymTopology>(
fn generate_check_id() -> [u8; 16] {
let mut id = [0u8; 16];
let mut rng = rand_os::OsRng::new().unwrap();
rng.fill_bytes(&mut id);
id
}
pub async fn calculate<T, IDPair, Priv, Pub>(
topology: T,
iterations: usize,
resolution_timeout: Duration,
) -> Self {
identity_keys: &IDPair,
) -> Self
where
T: NymTopology,
IDPair: MixnetIdentityKeyPair<Priv, Pub>,
Priv: MixnetIdentityPrivateKey,
Pub: MixnetIdentityPublicKey,
{
// currently healthchecker supports only up to 255 iterations - if we somehow
// find we need more, it's relatively easy change
assert!(iterations <= 255);
let check_id = Self::generate_check_id();
let all_paths = match topology.all_paths() {
Ok(paths) => paths,
Err(_) => return Self::zero_score(topology),
@@ -110,21 +127,17 @@ impl HealthCheckResult {
// create entries for all nodes
let mut score_map = HashMap::new();
topology.get_mix_nodes().into_iter().for_each(|node| {
topology.mix_nodes().into_iter().for_each(|node| {
score_map.insert(node.get_pub_key_bytes(), NodeScore::from_mixnode(node));
});
topology
.get_mix_provider_nodes()
.into_iter()
.for_each(|node| {
score_map.insert(node.get_pub_key_bytes(), NodeScore::from_provider(node));
});
topology.providers().into_iter().for_each(|node| {
score_map.insert(node.get_pub_key_bytes(), NodeScore::from_provider(node));
});
let ephemeral_keys = DummyMixIdentityKeyPair::new();
let providers = topology.get_mix_provider_nodes();
let providers = topology.providers();
let mut path_checker = PathChecker::new(providers, ephemeral_keys).await;
let mut path_checker = PathChecker::new(providers, identity_keys, check_id).await;
for i in 0..iterations {
debug!("running healthcheck iteration {} / {}", i + 1, iterations);
for path in &all_paths {
+7 -6
View File
@@ -4,7 +4,8 @@ use std::cmp::Ordering;
use std::fmt::Error;
use std::fmt::Formatter;
use std::net::SocketAddr;
use topology::{MixNode, MixProviderNode};
use topology::mix;
use topology::provider;
// TODO: should 'nodetype' really be part of healthcheck::score
@@ -91,10 +92,10 @@ impl PartialEq for NodeScore {
}
impl NodeScore {
pub(crate) fn from_mixnode(node: MixNode) -> Self {
pub(crate) fn from_mixnode(node: mix::Node) -> Self {
NodeScore {
typ: NodeType::Mix,
pub_key: NodeAddressBytes::from_b64_string(node.pub_key),
pub_key: NodeAddressBytes::from_base58_string(node.pub_key),
addresses: vec![node.host],
version: node.version,
layer: format!("layer {}", node.layer),
@@ -103,10 +104,10 @@ impl NodeScore {
}
}
pub(crate) fn from_provider(node: MixProviderNode) -> Self {
pub(crate) fn from_provider(node: provider::Node) -> Self {
NodeScore {
typ: NodeType::MixProvider,
pub_key: NodeAddressBytes::from_b64_string(node.pub_key),
pub_key: NodeAddressBytes::from_base58_string(node.pub_key),
addresses: vec![node.mixnet_listener, node.client_listener],
version: node.version,
layer: format!("provider"),
@@ -148,7 +149,7 @@ impl std::fmt::Display for NodeScore {
return Err(std::fmt::Error);
}
};
let stringified_key = self.pub_key.to_b64_string();
let stringified_key = self.pub_key.to_base58_string();
write!(
f,
"({})\t{}/{}\t({}%)\t|| {}\tv{} <{}> - {}",
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "pemstore"
version = "0.1.0"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4"
pem = "0.7.0"
pretty_env_logger = "0.3"
## internal
crypto = {path = "../crypto"}
+15
View File
@@ -0,0 +1,15 @@
use std::path::PathBuf;
pub trait PathFinder {
fn config_dir(&self) -> PathBuf;
fn private_identity_key(&self) -> PathBuf;
fn public_identity_key(&self) -> PathBuf;
// Optional:
fn private_encryption_key(&self) -> Option<PathBuf> {
None
}
fn public_encryption_key(&self) -> Option<PathBuf> {
None
}
}
@@ -1,18 +1,10 @@
use crate::persistence::pathfinder::Pathfinder;
use crate::pathfinder::PathFinder;
use pem::{encode, parse, Pem};
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::PathBuf;
pub fn read_mix_identity_keypair_from_disk(
id: String,
) -> crypto::identity::DummyMixIdentityKeyPair {
let pathfinder = Pathfinder::new(id);
let pem_store = PemStore::new(pathfinder);
let keypair = pem_store.read_identity();
keypair
}
#[allow(dead_code)]
pub fn read_mix_encryption_keypair_from_disk(_id: String) -> crypto::encryption::x25519::KeyPair {
unimplemented!()
@@ -25,51 +17,58 @@ pub struct PemStore {
}
impl PemStore {
pub fn new(pathfinder: Pathfinder) -> PemStore {
pub fn new<P: PathFinder>(pathfinder: P) -> PemStore {
PemStore {
config_dir: pathfinder.config_dir,
private_mix_key: pathfinder.private_mix_key,
public_mix_key: pathfinder.public_mix_key,
config_dir: pathfinder.config_dir(),
private_mix_key: pathfinder.private_identity_key(),
public_mix_key: pathfinder.public_identity_key(),
}
}
pub fn read_identity<IDPair, Priv, Pub>(&self) -> IDPair
pub fn read_identity<IDPair, Priv, Pub>(&self) -> io::Result<IDPair>
where
IDPair: crypto::identity::MixnetIdentityKeyPair<Priv, Pub>,
Priv: crypto::identity::MixnetIdentityPrivateKey,
Pub: crypto::identity::MixnetIdentityPublicKey,
{
let private_pem = self.read_pem_file(self.private_mix_key.clone());
let public_pem = self.read_pem_file(self.public_mix_key.clone());
let private_pem = self.read_pem_file(self.private_mix_key.clone())?;
let public_pem = self.read_pem_file(self.public_mix_key.clone())?;
let key_pair = IDPair::from_bytes(&private_pem.contents, &public_pem.contents);
assert_eq!(key_pair.private_key().pem_type(), private_pem.tag);
assert_eq!(key_pair.public_key().pem_type(), public_pem.tag);
if key_pair.private_key().pem_type() != private_pem.tag {
return Err(io::Error::new(
io::ErrorKind::Other,
"unexpected private key pem tag",
));
}
key_pair
if key_pair.public_key().pem_type() != public_pem.tag {
return Err(io::Error::new(
io::ErrorKind::Other,
"unexpected public key pem tag",
));
}
Ok(key_pair)
}
fn read_pem_file(&self, filepath: PathBuf) -> Pem {
let mut pem_bytes = File::open(filepath).expect("Could not open stored keys from disk.");
fn read_pem_file(&self, filepath: PathBuf) -> io::Result<Pem> {
let mut pem_bytes = File::open(filepath)?;
let mut buf = Vec::new();
pem_bytes
.read_to_end(&mut buf)
.expect("PEM bytes reading failed.");
let pem = parse(&buf).expect("PEM parsing failed while reading keys");
pem
pem_bytes.read_to_end(&mut buf)?;
parse(&buf).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}
// This should be refactored and made more generic for when we have other kinds of
// KeyPairs that we want to persist (e.g. validator keypairs, or keys for
// signing vs encryption). However, for the moment, it does the job.
pub fn write_identity<IDPair, Priv, Pub>(&self, key_pair: IDPair)
pub fn write_identity<IDPair, Priv, Pub>(&self, key_pair: IDPair) -> io::Result<()>
where
IDPair: crypto::identity::MixnetIdentityKeyPair<Priv, Pub>,
Priv: crypto::identity::MixnetIdentityPrivateKey,
Pub: crypto::identity::MixnetIdentityPublicKey,
{
std::fs::create_dir_all(self.config_dir.clone()).unwrap();
std::fs::create_dir_all(self.config_dir.clone())?;
let private_key = key_pair.private_key();
let public_key = key_pair.public_key();
@@ -77,22 +76,25 @@ impl PemStore {
self.private_mix_key.clone(),
private_key.to_bytes(),
private_key.pem_type(),
);
)?;
self.write_pem_file(
self.public_mix_key.clone(),
public_key.to_bytes(),
public_key.pem_type(),
);
)?;
Ok(())
}
fn write_pem_file(&self, filepath: PathBuf, data: Vec<u8>, tag: String) {
fn write_pem_file(&self, filepath: PathBuf, data: Vec<u8>, tag: String) -> io::Result<()> {
let pem = Pem {
tag,
contents: data,
};
let key = encode(&pem);
let mut file = File::create(filepath).unwrap();
file.write_all(key.as_bytes()).unwrap();
let mut file = File::create(filepath)?;
file.write_all(key.as_bytes())?;
Ok(())
}
}
+6 -4
View File
@@ -7,15 +7,17 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = "0.11.0"
curve25519-dalek = "1.2.3"
bs58 = "0.3.0"
itertools = "0.8.2"
log = "0.4.8"
log = "0.4"
pretty_env_logger = "0.3"
rand = "0.7.2"
serde = { version = "1.0.104", features = ["derive"] }
## internal
addressing = {path = "../addressing"}
version-checker = {path = "../version-checker" }
## will be moved to proper dependencies once released
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
sphinx = { git = "https://github.com/nymtech/sphinx", rev="8424f4b0933b4a7f64ae828b2edefc5ba43a9e79" }
# sphinx = { path = "../../../sphinx"}
+15
View File
@@ -0,0 +1,15 @@
use crate::filter;
#[derive(Debug, Clone)]
pub struct Node {
pub host: String,
pub pub_key: String,
pub last_seen: u64,
pub version: String,
}
impl filter::Versioned for Node {
fn version(&self) -> String {
self.version.clone()
}
}
+18
View File
@@ -0,0 +1,18 @@
pub trait Versioned: Clone {
fn version(&self) -> String;
}
pub trait VersionFilterable<T> {
fn filter_by_version(&self, expected_version: &str) -> Self;
}
impl<T: Versioned> VersionFilterable<T> for Vec<T> {
fn filter_by_version(&self, expected_version: &str) -> Self {
self.iter()
.filter(|node| {
version_checker::is_minor_version_compatible(&node.version(), expected_version)
})
.cloned()
.collect()
}
}
+36 -114
View File
@@ -1,102 +1,29 @@
use addressing;
use curve25519_dalek::montgomery::MontgomeryPoint;
use crate::filter::VersionFilterable;
use itertools::Itertools;
use rand::seq::IteratorRandom;
use sphinx::route::{Node as SphinxNode, NodeAddressBytes};
use sphinx::route::Node as SphinxNode;
use std::cmp::max;
use std::collections::HashMap;
use std::net::SocketAddr;
#[derive(Debug, Clone)]
pub struct MixNode {
pub host: SocketAddr,
pub pub_key: String,
pub layer: u64,
pub last_seen: u64,
pub version: String,
}
pub mod coco;
mod filter;
pub mod mix;
pub mod provider;
impl Into<SphinxNode> for MixNode {
fn into(self) -> SphinxNode {
let address_bytes = addressing::encoded_bytes_from_socket_address(self.host);
let key_bytes = self.get_pub_key_bytes();
let key = MontgomeryPoint(key_bytes);
SphinxNode::new(NodeAddressBytes::from_bytes(address_bytes), key)
}
}
impl MixNode {
pub fn get_pub_key_bytes(&self) -> [u8; 32] {
let decoded_key_bytes = base64::decode_config(&self.pub_key, base64::URL_SAFE).unwrap();
let mut key_bytes = [0; 32];
key_bytes.copy_from_slice(&decoded_key_bytes[..]);
key_bytes
}
}
#[derive(Debug, Clone)]
pub struct MixProviderClient {
pub pub_key: String,
}
#[derive(Debug, Clone)]
pub struct MixProviderNode {
pub client_listener: SocketAddr,
pub mixnet_listener: SocketAddr,
pub pub_key: String,
pub registered_clients: Vec<MixProviderClient>,
pub last_seen: u64,
pub version: String,
}
impl Into<SphinxNode> for MixProviderNode {
fn into(self) -> SphinxNode {
let address_bytes = addressing::encoded_bytes_from_socket_address(self.mixnet_listener);
let key_bytes = self.get_pub_key_bytes();
let key = MontgomeryPoint(key_bytes);
SphinxNode::new(NodeAddressBytes::from_bytes(address_bytes), key)
}
}
impl MixProviderNode {
pub fn get_pub_key_bytes(&self) -> [u8; 32] {
let decoded_key_bytes = base64::decode_config(&self.pub_key, base64::URL_SAFE).unwrap();
let mut key_bytes = [0; 32];
key_bytes.copy_from_slice(&decoded_key_bytes[..]);
key_bytes
}
}
#[derive(Debug, Clone)]
pub struct CocoNode {
pub host: String,
pub pub_key: String,
pub last_seen: u64,
pub version: String,
}
#[derive(Debug)]
pub enum NymTopologyError {
InvalidMixLayerError,
MissingLayerError(Vec<u64>),
}
pub trait NymTopology: Sized {
pub trait NymTopology: Sized + std::fmt::Debug + Send + Sync {
fn new(directory_server: String) -> Self;
fn new_from_nodes(
mix_nodes: Vec<MixNode>,
mix_provider_nodes: Vec<MixProviderNode>,
coco_nodes: Vec<CocoNode>,
mix_nodes: Vec<mix::Node>,
mix_provider_nodes: Vec<provider::Node>,
coco_nodes: Vec<coco::Node>,
) -> Self;
fn get_mix_nodes(&self) -> Vec<MixNode>;
fn get_mix_provider_nodes(&self) -> Vec<MixProviderNode>;
fn get_coco_nodes(&self) -> Vec<CocoNode>;
fn make_layered_topology(&self) -> Result<HashMap<u64, Vec<MixNode>>, NymTopologyError> {
let mut layered_topology: HashMap<u64, Vec<MixNode>> = HashMap::new();
fn mix_nodes(&self) -> Vec<mix::Node>;
fn providers(&self) -> Vec<provider::Node>;
fn coco_nodes(&self) -> Vec<coco::Node>;
fn make_layered_topology(&self) -> Result<HashMap<u64, Vec<mix::Node>>, NymTopologyError> {
let mut layered_topology: HashMap<u64, Vec<mix::Node>> = HashMap::new();
let mut highest_layer = 0;
for mix in self.get_mix_nodes() {
for mix in self.mix_nodes() {
// we need to have extra space for provider
if mix.layer > sphinx::constants::MAX_PATH_LENGTH as u64 {
return Err(NymTopologyError::InvalidMixLayerError);
@@ -124,10 +51,14 @@ pub trait NymTopology: Sized {
Ok(layered_topology)
}
// Tries to get a route through the mix network
fn mix_route(&self) -> Result<Vec<SphinxNode>, NymTopologyError> {
let mut layered_topology = self.make_layered_topology()?;
let num_layers = layered_topology.len();
let route = (1..=num_layers as u64)
// unwrap is safe for 'remove' as it it failed, it implied the entry never existed
// in the map in the first place which would contradict what we've just done
.map(|layer| layered_topology.remove(&layer).unwrap()) // for each layer
.map(|nodes| nodes.into_iter().choose(&mut rand::thread_rng()).unwrap()) // choose random node
.map(|random_node| random_node.into()) // and convert it into sphinx specific node format
@@ -136,7 +67,7 @@ pub trait NymTopology: Sized {
Ok(route)
}
// sets a route to specific provider
// Sets up a route to a specific provider
fn route_to(&self, provider_node: SphinxNode) -> Result<Vec<SphinxNode>, NymTopologyError> {
Ok(self
.mix_route()?
@@ -147,7 +78,7 @@ pub trait NymTopology: Sized {
fn all_paths(&self) -> Result<Vec<Vec<SphinxNode>>, NymTopologyError> {
let mut layered_topology = self.make_layered_topology()?;
let providers = self.get_mix_provider_nodes();
let providers = self.providers();
let sorted_layers: Vec<Vec<SphinxNode>> = (1..=layered_topology.len() as u64)
.map(|layer| layered_topology.remove(&layer).unwrap()) // get all nodes per layer
@@ -167,30 +98,17 @@ pub trait NymTopology: Sized {
fn filter_node_versions(
&self,
mix_version: &str,
provider_version: &str,
coco_version: &str,
expected_mix_version: &str,
expected_provider_version: &str,
expected_coco_version: &str,
) -> Self {
let filtered_mixes = self
.get_mix_nodes()
.iter()
.cloned()
.filter(|mix_node| mix_node.version == mix_version)
.collect();
let filtered_providers = self
.get_mix_provider_nodes()
.iter()
.cloned()
.filter(|provider_node| provider_node.version == provider_version)
.collect();
let filtered_coco_nodes = self
.get_coco_nodes()
.iter()
.cloned()
.filter(|coco_node| coco_node.version == coco_version)
.collect();
let mixes = self.mix_nodes().filter_by_version(expected_mix_version);
let providers = self
.providers()
.filter_by_version(expected_provider_version);
let cocos = self.coco_nodes().filter_by_version(expected_coco_version);
Self::new_from_nodes(filtered_mixes, filtered_providers, filtered_coco_nodes)
Self::new_from_nodes(mixes, providers, cocos)
}
fn can_construct_path_through(&self) -> bool {
@@ -201,4 +119,8 @@ pub trait NymTopology: Sized {
}
}
// TODO: tests...
#[derive(Debug)]
pub enum NymTopologyError {
InvalidMixLayerError,
MissingLayerError(Vec<u64>),
}
+37
View File
@@ -0,0 +1,37 @@
use crate::filter;
use sphinx::route::Node as SphinxNode;
use sphinx::route::NodeAddressBytes;
use std::net::SocketAddr;
#[derive(Debug, Clone)]
pub struct Node {
pub host: SocketAddr,
pub pub_key: String,
pub layer: u64,
pub last_seen: u64,
pub version: String,
}
impl Node {
pub fn get_pub_key_bytes(&self) -> [u8; 32] {
let mut key_bytes = [0; 32];
bs58::decode(&self.pub_key).into(&mut key_bytes).unwrap();
key_bytes
}
}
impl filter::Versioned for Node {
fn version(&self) -> String {
self.version.clone()
}
}
impl Into<SphinxNode> for Node {
fn into(self) -> SphinxNode {
let address_bytes = addressing::encoded_bytes_from_socket_address(self.host);
let key_bytes = self.get_pub_key_bytes();
let key = sphinx::key::new(key_bytes);
SphinxNode::new(NodeAddressBytes::from_bytes(address_bytes), key)
}
}
+43
View File
@@ -0,0 +1,43 @@
use crate::filter;
use sphinx::route::Node as SphinxNode;
use sphinx::route::NodeAddressBytes;
use std::net::SocketAddr;
#[derive(Debug, Clone)]
pub struct Client {
pub pub_key: String,
}
#[derive(Debug, Clone)]
pub struct Node {
pub client_listener: SocketAddr,
pub mixnet_listener: SocketAddr,
pub pub_key: String,
pub registered_clients: Vec<Client>,
pub last_seen: u64,
pub version: String,
}
impl Node {
pub fn get_pub_key_bytes(&self) -> [u8; 32] {
let mut key_bytes = [0; 32];
bs58::decode(&self.pub_key).into(&mut key_bytes).unwrap();
key_bytes
}
}
impl filter::Versioned for Node {
fn version(&self) -> String {
self.version.clone()
}
}
impl Into<SphinxNode> for Node {
fn into(self) -> SphinxNode {
let address_bytes = addressing::encoded_bytes_from_socket_address(self.mixnet_listener);
let key_bytes = self.get_pub_key_bytes();
let key = sphinx::key::new(key_bytes);
SphinxNode::new(NodeAddressBytes::from_bytes(address_bytes), key)
}
}
+3
View File
@@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "version-checker"
version = "0.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
semver = "0.9.0"
+67
View File
@@ -0,0 +1,67 @@
use semver::Version;
use semver::VersionReq;
/// Checks whether given `version` is compatible with a given semantic version requirement `req`
/// according to major-minor semver rules. The semantic version requirement can be passed as a full,
/// concrete version number, because that's what we'll have in our Cargo.toml files (e.g. 0.3.2).
/// The patch number in the requirement gets dropped and replaced with a wildcard (0.3.*) as all
/// minor versions should be compatible with each other.
pub fn is_minor_version_compatible(version: &str, req: &str) -> bool {
let version = match Version::parse(version) {
Ok(v) => v,
Err(_) => return false,
};
let tmp = match Version::parse(req) {
Ok(v) => v,
Err(_) => return false,
};
let wildcard = format!("{}.{}.*", tmp.major, tmp.minor).to_string();
let semver_requirement = VersionReq::parse(&wildcard).expect("panicked on semver requirement parsing. This should never happen as inputs should already have been sanitized.");
semver_requirement.matches(&version)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_0_3_0_is_compatible_with_requirement_0_3_x() {
assert!(is_minor_version_compatible("0.3.0", "0.3.2"));
}
#[test]
fn version_0_3_1_is_compatible_with_minimum_requirement_0_3_x() {
assert!(is_minor_version_compatible("0.3.1", "0.3.2"));
}
#[test]
fn version_0_3_2_is_compatible_with_minimum_requirement_0_3_x() {
assert!(is_minor_version_compatible("0.3.2", "0.3.0"));
}
#[test]
fn version_0_2_0_is_not_compatible_with_requirement_0_3_x() {
assert!(!is_minor_version_compatible("0.2.0", "0.3.2"));
}
#[test]
fn version_0_4_0_is_not_compatible_with_requirement_0_3_x() {
assert!(!is_minor_version_compatible("0.4.0", "0.3.2"));
}
#[test]
fn version_1_3_2_is_not_compatible_with_requirement_0_3_x() {
assert!(!is_minor_version_compatible("1.3.2", "0.3.2"));
}
#[test]
fn returns_false_on_foo_version() {
assert!(!is_minor_version_compatible("foo", "0.3.2"));
}
#[test]
fn returns_false_on_bar_version() {
assert!(!is_minor_version_compatible("0.3.2", "bar"));
}
}
-39
View File
@@ -1,39 +0,0 @@
# nym-mixnode Changelog
## 0.3.3
* Version increase for consistency with `nym-client`
## 0.3.2
* added separate announce address
* allows announcing dns hostname instead of an ip address
## 0.3.1
* Fixed crash when directory server goes down
## 0.3.0
* cleaned up a lot of internal dependencies
* reporting version to the directory server
* printing warning on trying to bind to "localhost", "127.0.0.1" or "0.0.0.0"
* more informative error messages
* generalised identity keys
* generalised Topology handling
* started slow transition to `log` crate by `nym-client`
* start of 'MixMining'
* start of validator node
## 0.2.0
* removed the `--local` flag
* introduced `--directory` argument to support arbitrary directory servers. Leaving it out will point the node at the "https://directory.nymtech.net" alpha testnet server
* the `host` argument is now required
* IPv6 support
* mixnode version number is now shown at node start
* directory server location is now shown at node start
## 0.1.0 - Initial Release
* The bare minimum set of features required by a Nym Mixnode
+6 -4
View File
@@ -1,18 +1,20 @@
[package]
build = "build.rs"
name = "nym-mixnode"
version = "0.3.3"
version = "0.4.0-rc.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = "0.11.0"
bs58 = "0.3.0"
clap = "2.33.0"
curve25519-dalek = "1.2.3"
dotenv = "0.15.0"
futures = "0.3.1"
log = "0.4.8"
log = "0.4"
pretty_env_logger = "0.3"
tokio = { version = "0.2", features = ["full"] }
## internal
@@ -20,7 +22,7 @@ addressing = {path = "../common/addressing" }
directory-client = { path = "../common/clients/directory-client" }
## will be moved to proper dependencies once released
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
sphinx = { git = "https://github.com/nymtech/sphinx", rev="8424f4b0933b4a7f64ae828b2edefc5ba43a9e79" }
[build-dependencies]
built = "0.3.2"
+5 -1
View File
@@ -1,10 +1,14 @@
use clap::{App, Arg, ArgMatches, SubCommand};
use log::*;
use std::process;
mod mix_peer;
mod node;
fn main() {
dotenv::dotenv().ok();
pretty_env_logger::init();
let arg_matches = App::new("Nym Mixnode")
.version(built_info::PKG_VERSION)
.author("Nymtech")
@@ -54,7 +58,7 @@ fn main() {
.get_matches();
if let Err(e) = execute(arg_matches) {
println!("{}", e);
error!("{}", e);
process::exit(1);
}
}
+18 -4
View File
@@ -1,4 +1,5 @@
use addressing;
use addressing::AddressTypeError;
use sphinx::route::NodeAddressBytes;
use std::error::Error;
use std::net::SocketAddr;
@@ -9,14 +10,27 @@ pub struct MixPeer {
connection: SocketAddr,
}
#[derive(Debug)]
pub enum MixPeerError {
InvalidAddressError,
}
impl From<addressing::AddressTypeError> for MixPeerError {
fn from(_: AddressTypeError) -> Self {
use MixPeerError::*;
InvalidAddressError
}
}
impl MixPeer {
// note that very soon `next_hop_address` will be changed to `next_hop_metadata`
pub fn new(next_hop_address: NodeAddressBytes) -> MixPeer {
pub fn new(next_hop_address: NodeAddressBytes) -> Result<MixPeer, MixPeerError> {
let next_hop_socket_address =
addressing::socket_address_from_encoded_bytes(next_hop_address.to_bytes());
MixPeer {
addressing::socket_address_from_encoded_bytes(next_hop_address.to_bytes())?;
Ok(MixPeer {
connection: next_hop_socket_address,
}
})
}
pub async fn send(&self, bytes: Vec<u8>) -> Result<(), Box<dyn Error>> {
+42 -18
View File
@@ -6,6 +6,7 @@ use curve25519_dalek::scalar::Scalar;
use futures::channel::mpsc;
use futures::lock::Mutex;
use futures::SinkExt;
use log::*;
use sphinx::header::delays::Delay as SphinxDelay;
use sphinx::{ProcessedPacket, SphinxPacket};
use std::net::SocketAddr;
@@ -30,7 +31,7 @@ pub struct Config {
impl Config {
pub fn public_key_string(&self) -> String {
let key_bytes = self.public_key.to_bytes().to_vec();
base64::encode_config(&key_bytes, base64::URL_SAFE)
bs58::encode(&key_bytes).into_string()
}
}
@@ -38,6 +39,8 @@ impl Config {
pub enum MixProcessingError {
SphinxRecoveryError,
ReceivedFinalHopError,
SphinxProcessingError,
InvalidHopAddress,
}
impl From<sphinx::ProcessingError> for MixProcessingError {
@@ -107,20 +110,32 @@ impl PacketProcessor {
) -> Result<ForwardingData, MixProcessingError> {
// we received something resembling a sphinx packet, report it!
let processing_data = processing_data.lock().await;
let mut received_sender = processing_data.received_metrics_tx.clone();
let mut received_metrics_tx = processing_data.received_metrics_tx.clone();
received_sender.send(()).await.unwrap();
// if unwrap failed it means our metrics reporter died, so we should exit application and
// force restart
if received_metrics_tx.send(()).await.is_err() {
error!("failed to send metrics data to the controller - the underlying thread probably died!");
std::process::exit(1);
}
let packet = SphinxPacket::from_bytes(packet_data.to_vec())?;
let (next_packet, next_hop_address, delay) =
match packet.process(processing_data.secret_key) {
ProcessedPacket::ProcessedPacketForwardHop(packet, address, delay) => {
Ok(ProcessedPacket::ProcessedPacketForwardHop(packet, address, delay)) => {
(packet, address, delay)
}
_ => return Err(MixProcessingError::ReceivedFinalHopError),
Ok(_) => return Err(MixProcessingError::ReceivedFinalHopError),
Err(e) => {
warn!("Failed to unwrap Sphinx packet: {:?}", e);
return Err(MixProcessingError::SphinxProcessingError);
}
};
let next_mix = MixPeer::new(next_hop_address);
let next_mix = match MixPeer::new(next_hop_address) {
Ok(next_mix) => next_mix,
Err(_) => return Err(MixProcessingError::InvalidHopAddress),
};
let fwd_data = ForwardingData::new(
next_packet,
@@ -134,13 +149,18 @@ impl PacketProcessor {
async fn wait_and_forward(mut forwarding_data: ForwardingData) {
let delay_duration = Duration::from_nanos(forwarding_data.delay.get_value());
tokio::time::delay_for(delay_duration).await;
forwarding_data
if forwarding_data
.sent_metrics_tx
.send(forwarding_data.recipient.to_string())
.await
.unwrap();
.is_err()
{
error!("failed to send metrics data to the controller - the underlying thread probably died!");
std::process::exit(1);
}
println!("RECIPIENT: {:?}", forwarding_data.recipient);
trace!("RECIPIENT: {:?}", forwarding_data.recipient);
match forwarding_data
.recipient
.send(forwarding_data.packet.to_bytes())
@@ -148,7 +168,7 @@ impl PacketProcessor {
{
Ok(()) => (),
Err(e) => {
println!(
warn!(
"failed to write bytes to next mix peer. err = {:?}",
e.to_string()
);
@@ -182,7 +202,6 @@ impl MixNode {
mut socket: tokio::net::TcpStream,
processing_data: Arc<Mutex<ProcessingData>>,
) {
// NOTE: processing_data is copied here!!
let mut buf = [0u8; sphinx::PACKET_SIZE];
// In a loop, read data from the socket and write the data back.
@@ -190,27 +209,33 @@ impl MixNode {
match socket.read(&mut buf).await {
// socket closed
Ok(n) if n == 0 => {
println!("Remote connection closed.");
trace!("Remote connection closed.");
return;
}
Ok(_) => {
let fwd_data = PacketProcessor::process_sphinx_data_packet(
let fwd_data = match PacketProcessor::process_sphinx_data_packet(
buf.as_ref(),
processing_data.clone(),
)
.await
.unwrap();
{
Ok(fwd_data) => fwd_data,
Err(e) => {
warn!("failed to process sphinx packet: {:?}", e);
return;
}
};
PacketProcessor::wait_and_forward(fwd_data).await;
}
Err(e) => {
println!("failed to read from socket; err = {:?}", e);
warn!("failed to read from socket; err = {:?}", e);
return;
}
};
// Write the some data back
if let Err(e) = socket.write_all(b"foomp").await {
println!("failed to write reply to socket; err = {:?}", e);
warn!("failed to write reply to socket; err = {:?}", e);
return;
}
}
@@ -226,8 +251,7 @@ impl MixNode {
let directory_cfg = directory_client::Config {
base_url: self.directory_server.clone(),
};
let pub_key_str =
base64::encode_config(&self.public_key.to_bytes().to_vec(), base64::URL_SAFE);
let pub_key_str = bs58::encode(&self.public_key.to_bytes().to_vec()).into_string();
rt.spawn({
let presence_notifier = presence::Notifier::new(&config);
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::node;
use directory_client::presence::MixNodePresence;
use directory_client::presence::mixnodes::MixNodePresence;
use directory_client::requests::presence_mixnodes_post::PresenceMixNodesPoster;
use directory_client::DirectoryClient;
use log::{debug, error};
-41
View File
@@ -1,41 +0,0 @@
# nym-client Changelog
## 0.3.3
* websocket handling of 'ping', 'pong' and 'close' messages
* websocket not crashing on binary messages
* websocket returning text rather than base64
* restored `nym-client` lib functionality
## 0.3.2
* allows receiving topology with dns hostname instead of an ip address
## 0.3.1
* Version increase for consistency with `nym-mixnode` and `nym-sfw-provider`
## 0.3.0
* cleaned up a lot of internal dependencies
* reporting version to the directory server
* printing warning on trying to bind to "localhost", "127.0.0.1" or "0.0.0.0"
* more informative error messages
* generalised identity keys
* generalised Topology handling
* started slow transition to `log` crate by `nym-client`
* start of 'MixMining'
* start of validator node
## 0.2.0
* removed the `--local` flag
* introduced `--directory` argument to support arbitrary directory servers. Leaving it out will point the node at the "https://directory.nymtech.net" alpha testnet server
* IPv6 support
* client version number is now shown at node start
* directory server location is now shown at node start
* decrease default delays
## 0.1.0 - Initial Release
* The bare minimum set of features required by a Nym Client
+8 -8
View File
@@ -1,7 +1,7 @@
[package]
build = "build.rs"
name = "nym-client"
version = "0.3.3"
version = "0.4.0-rc.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
@@ -12,17 +12,15 @@ name = "nym_client"
path = "src/lib.rs"
[dependencies]
base64 = "0.11.0"
bs58 = "0.3.0"
clap = "2.33.0"
curve25519-dalek = "1.2.3"
dirs = "2.0.2"
env_logger = "0.7.1"
dotenv = "0.15.0"
futures = "0.3.1"
hex = "0.4.0"
log = "0.4.8"
log = "0.4"
pem = "0.7.0"
rand = "0.7.2"
rand_distr = "0.2.2"
pretty_env_logger = "0.3"
reqwest = "0.9.22"
serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.44"
@@ -35,12 +33,14 @@ crypto = {path = "../common/crypto"}
directory-client = { path = "../common/clients/directory-client" }
healthcheck = { path = "../common/healthcheck" }
mix-client = { path = "../common/clients/mix-client" }
pemstore = {path = "../common/pemstore"}
provider-client = { path = "../common/clients/provider-client" }
sfw-provider-requests = { path = "../sfw-provider/sfw-provider-requests" }
topology = {path = "../common/topology" }
## will be moved to proper dependencies once released
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
sphinx = { git = "https://github.com/nymtech/sphinx", rev="8424f4b0933b4a7f64ae828b2edefc5ba43a9e79" }
# sphinx = { path = "../../sphinx"}
# putting this explicitly below everything and most likely, the next time we look into it, it will already have a proper release
tokio-tungstenite = { git = "https://github.com/snapview/tokio-tungstenite", rev="308d9680c0e59dd1e8651659a775c05df937934e" }
@@ -0,0 +1,53 @@
use crate::client::mix_traffic::MixMessage;
use crate::client::topology_control::TopologyInnerRef;
use crate::client::LOOP_COVER_AVERAGE_DELAY;
use futures::channel::mpsc;
use log::{error, info, trace, warn};
use sphinx::route::Destination;
use std::time::Duration;
use topology::NymTopology;
pub(crate) async fn start_loop_cover_traffic_stream<T: NymTopology>(
tx: mpsc::UnboundedSender<MixMessage>,
our_info: Destination,
topology_ctrl_ref: TopologyInnerRef<T>,
) {
info!("Starting loop cover traffic stream");
loop {
trace!("next cover message!");
let delay = mix_client::poisson::sample(LOOP_COVER_AVERAGE_DELAY);
let delay_duration = Duration::from_secs_f64(delay);
tokio::time::delay_for(delay_duration).await;
let read_lock = topology_ctrl_ref.read().await;
let topology = match read_lock.topology.as_ref() {
None => {
warn!("No valid topology detected - won't send any loop cover message this time");
continue;
}
Some(topology) => topology,
};
let cover_message = match mix_client::packet::loop_cover_message(
our_info.address,
our_info.identifier,
topology,
) {
Ok(message) => message,
Err(err) => {
error!(
"Somehow we managed to create an invalid cover message - {:?}",
err
);
continue;
}
};
// if this one fails, there's no retrying because it means that either:
// - we run out of memory
// - the receiver channel is closed
// in either case there's no recovery and we can only panic
tx.unbounded_send(MixMessage::new(cover_message.0, cover_message.1))
.unwrap();
}
}
+37
View File
@@ -0,0 +1,37 @@
use futures::channel::mpsc;
use futures::StreamExt;
use log::{debug, error, info, trace};
use sphinx::SphinxPacket;
use std::net::SocketAddr;
pub(crate) struct MixMessage(SocketAddr, SphinxPacket);
impl MixMessage {
pub(crate) fn new(address: SocketAddr, packet: SphinxPacket) -> Self {
MixMessage(address, packet)
}
}
pub(crate) struct MixTrafficController;
impl MixTrafficController {
pub(crate) async fn run(mut rx: mpsc::UnboundedReceiver<MixMessage>) {
info!("Mix Traffic Controller started!");
let mix_client = mix_client::MixClient::new();
while let Some(mix_message) = rx.next().await {
debug!("Got a mix_message for {:?}", mix_message.0);
let send_res = mix_client.send(mix_message.1, mix_message.0).await;
match send_res {
Ok(_) => {
trace!("sent a mix message");
}
// TODO: should there be some kind of threshold of failed messages
// that if reached, the application blows?
Err(e) => error!(
"We failed to send the message to {} :( - {:?}",
mix_message.0, e
),
};
}
}
}
+249
View File
@@ -0,0 +1,249 @@
use crate::client::mix_traffic::MixTrafficController;
use crate::client::received_buffer::ReceivedMessagesBuffer;
use crate::client::topology_control::TopologyInnerRef;
use crate::sockets::tcp;
use crate::sockets::ws;
use crypto::identity::{MixnetIdentityKeyPair, MixnetIdentityPrivateKey, MixnetIdentityPublicKey};
use directory_client::presence::Topology;
use futures::channel::mpsc;
use futures::join;
use log::*;
use sfw_provider_requests::AuthToken;
use sphinx::route::Destination;
use std::marker::PhantomData;
use std::net::SocketAddr;
use tokio::runtime::Runtime;
use topology::NymTopology;
mod cover_traffic_stream;
mod mix_traffic;
mod provider_poller;
mod real_traffic_stream;
pub mod received_buffer;
pub mod topology_control;
// TODO: all of those constants should probably be moved to config file
const LOOP_COVER_AVERAGE_DELAY: f64 = 0.5;
// seconds
const MESSAGE_SENDING_AVERAGE_DELAY: f64 = 0.5;
// seconds;
const FETCH_MESSAGES_DELAY: f64 = 1.0; // seconds;
const TOPOLOGY_REFRESH_RATE: f64 = 10.0; // seconds
pub enum SocketType {
TCP,
WebSocket,
None,
}
pub struct NymClient<IDPair, Priv, Pub>
where
IDPair: MixnetIdentityKeyPair<Priv, Pub> + Send + Sync,
Priv: MixnetIdentityPrivateKey + Send + Sync,
Pub: MixnetIdentityPublicKey + Send + Sync,
{
keypair: IDPair,
// to be used by "send" function or socket, etc
pub input_tx: mpsc::UnboundedSender<InputMessage>,
input_rx: mpsc::UnboundedReceiver<InputMessage>,
socket_listening_address: SocketAddr,
directory: String,
auth_token: Option<AuthToken>,
socket_type: SocketType,
_phantom_private: PhantomData<Priv>,
_phantom_public: PhantomData<Pub>,
}
#[derive(Debug)]
pub struct InputMessage(pub Destination, pub Vec<u8>);
impl<IDPair: 'static, Priv: 'static, Pub: 'static> NymClient<IDPair, Priv, Pub>
where
IDPair: MixnetIdentityKeyPair<Priv, Pub> + Send + Sync,
Priv: MixnetIdentityPrivateKey + Send + Sync,
Pub: MixnetIdentityPublicKey + Send + Sync,
{
pub fn new(
keypair: IDPair,
socket_listening_address: SocketAddr,
directory: String,
auth_token: Option<AuthToken>,
socket_type: SocketType,
) -> Self {
let (input_tx, input_rx) = mpsc::unbounded::<InputMessage>();
NymClient {
keypair,
input_tx,
input_rx,
socket_listening_address,
directory,
auth_token,
socket_type,
_phantom_private: PhantomData,
_phantom_public: PhantomData,
}
}
async fn get_provider_socket_address<T: NymTopology>(
&self,
topology_ctrl_ref: TopologyInnerRef<T>,
) -> SocketAddr {
// this is temporary and assumes there exists only a single provider.
topology_ctrl_ref.read().await.topology.as_ref().unwrap()
.providers()
.first()
.expect("Could not get a provider from the initial network topology, are you using the right directory server?")
.client_listener
}
pub fn start(self) -> Result<(), Box<dyn std::error::Error>> {
info!("Starting nym client");
let mut rt = Runtime::new()?;
// channels for inter-component communication
// mix_tx is the transmitter for any component generating sphinx packets that are to be sent to the mixnet
// they are used by cover traffic stream and real traffic stream
// mix_rx is the receiver used by MixTrafficController that sends the actual traffic
let (mix_tx, mix_rx) = mpsc::unbounded();
// poller_input_tx is the transmitter of messages fetched from the provider - used by ProviderPoller
// poller_input_rx is the receiver for said messages - used by ReceivedMessagesBuffer
let (poller_input_tx, poller_input_rx) = mpsc::unbounded();
// received_messages_buffer_output_tx is the transmitter for *REQUESTS* for messages contained in ReceivedMessagesBuffer - used by sockets
// the requests contain a oneshot channel to send a reply on
// received_messages_buffer_output_rx is the received for the said requests - used by ReceivedMessagesBuffer
let (received_messages_buffer_output_tx, received_messages_buffer_output_rx) =
mpsc::unbounded();
let self_address = self.keypair.public_key().derive_address();
// generate same type of keys we have as our identity
let healthcheck_keys = IDPair::new();
// TODO: when we switch to our graph topology, we need to remember to change 'Topology' type
let topology_controller =
rt.block_on(topology_control::TopologyControl::<Topology, _, _, _>::new(
self.directory.clone(),
TOPOLOGY_REFRESH_RATE,
healthcheck_keys,
));
let provider_client_listener_address =
rt.block_on(self.get_provider_socket_address(topology_controller.get_inner_ref()));
let mut provider_poller = provider_poller::ProviderPoller::new(
poller_input_tx,
provider_client_listener_address,
self_address,
self.auth_token,
);
// registration
if let Err(err) = rt.block_on(provider_poller.perform_initial_registration()) {
panic!("Failed to perform initial registration: {:?}", err);
};
// setup all of futures for the components running on the client
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
let received_messages_buffer_controllers_future = rt.spawn(
ReceivedMessagesBuffer::new()
.start_controllers(poller_input_rx, received_messages_buffer_output_rx),
);
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
let mix_traffic_future = rt.spawn(MixTrafficController::run(mix_rx));
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
let loop_cover_traffic_future =
rt.spawn(cover_traffic_stream::start_loop_cover_traffic_stream(
mix_tx.clone(),
Destination::new(self_address, Default::default()),
topology_controller.get_inner_ref(),
));
// cloning arguments required by OutQueueControl; required due to move
let input_rx = self.input_rx;
let topology_ref = topology_controller.get_inner_ref();
// future constantly pumping traffic at some specified average rate
// if a real message is available on 'input_rx' that might have been received from say
// the websocket, the real message is used, otherwise a loop cover message is generated
// the pumped traffic goes to the MixTrafficController
let out_queue_control_future = rt.spawn(async move {
real_traffic_stream::OutQueueControl::new(
mix_tx,
input_rx,
Destination::new(self_address, Default::default()),
topology_ref,
)
.run_out_queue_control()
.await
});
// future constantly trying to fetch any received messages from the provider
// the received messages are sent to ReceivedMessagesBuffer to be available to rest of the system
let provider_polling_future = rt.spawn(provider_poller.start_provider_polling());
// a temporary workaround for starting socket listener of specified type
// in the future the actual socket handler should start THIS client instead
match self.socket_type {
SocketType::WebSocket => {
rt.spawn(ws::start_websocket(
self.socket_listening_address,
self.input_tx,
received_messages_buffer_output_tx,
self_address,
topology_controller.get_inner_ref(),
));
}
SocketType::TCP => {
rt.spawn(tcp::start_tcpsocket(
self.socket_listening_address,
self.input_tx,
received_messages_buffer_output_tx,
self_address,
topology_controller.get_inner_ref(),
));
}
SocketType::None => (),
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
let topology_refresher_future = rt.spawn(topology_controller.run_refresher());
rt.block_on(async {
let future_results = join!(
received_messages_buffer_controllers_future,
mix_traffic_future,
loop_cover_traffic_future,
out_queue_control_future,
provider_polling_future,
topology_refresher_future,
);
assert!(
future_results.0.is_ok()
&& future_results.1.is_ok()
&& future_results.2.is_ok()
&& future_results.3.is_ok()
&& future_results.4.is_ok()
&& future_results.5.is_ok()
);
});
// this line in theory should never be reached as the runtime should be permanently blocked on traffic senders
error!("The client went kaput...");
Ok(())
}
}
+88
View File
@@ -0,0 +1,88 @@
use crate::client::FETCH_MESSAGES_DELAY;
use futures::channel::mpsc;
use log::{debug, error, info, trace, warn};
use provider_client::ProviderClientError;
use sfw_provider_requests::AuthToken;
use sphinx::route::DestinationAddressBytes;
use std::net::SocketAddr;
use std::time::Duration;
pub(crate) struct ProviderPoller {
provider_client: provider_client::ProviderClient,
poller_tx: mpsc::UnboundedSender<Vec<Vec<u8>>>,
}
impl ProviderPoller {
pub(crate) fn new(
poller_tx: mpsc::UnboundedSender<Vec<Vec<u8>>>,
provider_client_listener_address: SocketAddr,
client_address: DestinationAddressBytes,
auth_token: Option<AuthToken>,
) -> Self {
ProviderPoller {
provider_client: provider_client::ProviderClient::new(
provider_client_listener_address,
client_address,
auth_token,
),
poller_tx,
}
}
// This method is only temporary until registration is moved to `client init`
pub(crate) async fn perform_initial_registration(&mut self) -> Result<(), ProviderClientError> {
debug!("performing initial provider registration");
if !self.provider_client.is_registered() {
let auth_token = match self.provider_client.register().await {
// in this particular case we can ignore this error
Err(ProviderClientError::ClientAlreadyRegisteredError) => return Ok(()),
Err(err) => return Err(err),
Ok(token) => token,
};
self.provider_client.update_token(auth_token)
} else {
warn!("did not perform registration - we were already registered")
}
Ok(())
}
pub(crate) async fn start_provider_polling(self) {
info!("Starting provider poller");
let loop_message = &mix_client::packet::LOOP_COVER_MESSAGE_PAYLOAD.to_vec();
let dummy_message = &sfw_provider_requests::DUMMY_MESSAGE_CONTENT.to_vec();
let delay_duration = Duration::from_secs_f64(FETCH_MESSAGES_DELAY);
let extended_delay_duration = Duration::from_secs_f64(FETCH_MESSAGES_DELAY * 10.0);
loop {
debug!("Polling provider...");
let messages = match self.provider_client.retrieve_messages().await {
Err(err) => {
error!("Failed to query the provider for messages: {:?}, ... Going to wait {:?} before retrying", err, extended_delay_duration);
tokio::time::delay_for(extended_delay_duration).await;
continue;
}
Ok(messages) => messages,
};
let good_messages = messages
.into_iter()
.filter(|message| message != loop_message && message != dummy_message)
.collect();
trace!("Obtained the following messages: {:?}", good_messages);
// if this one fails, there's no retrying because it means that either:
// - we run out of memory
// - the receiver channel is closed
// in either case there's no recovery and we can only panic
self.poller_tx.unbounded_send(good_messages).unwrap();
tokio::time::delay_for(delay_duration).await;
}
}
}
@@ -0,0 +1,132 @@
use crate::client::mix_traffic::MixMessage;
use crate::client::topology_control::TopologyInnerRef;
use crate::client::{InputMessage, MESSAGE_SENDING_AVERAGE_DELAY};
use futures::channel::mpsc;
use futures::task::{Context, Poll};
use futures::{Future, Stream, StreamExt};
use log::{error, info, trace, warn};
use sphinx::route::Destination;
use std::pin::Pin;
use std::time::Duration;
use tokio::time;
use topology::NymTopology;
// have a rather low value for test sake
const AVERAGE_PACKET_DELAY: f64 = 0.1;
pub(crate) struct OutQueueControl<T: NymTopology> {
delay: time::Delay,
mix_tx: mpsc::UnboundedSender<MixMessage>,
input_rx: mpsc::UnboundedReceiver<InputMessage>,
our_info: Destination,
topology_ctrl_ref: TopologyInnerRef<T>,
}
pub(crate) enum StreamMessage {
Cover,
Real(InputMessage),
}
impl<T: NymTopology> Stream for OutQueueControl<T> {
type Item = StreamMessage;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
// it is not yet time to return a message
if Pin::new(&mut self.delay).poll(cx).is_pending() {
return Poll::Pending;
};
// we know it's time to send a message, so let's prepare delay for the next one
// Get the `now` by looking at the current `delay` deadline
let now = self.delay.deadline();
let next_poisson_delay =
Duration::from_secs_f64(mix_client::poisson::sample(MESSAGE_SENDING_AVERAGE_DELAY));
// The next interval value is `next_poisson_delay` after the one that just
// yielded.
let next = now + next_poisson_delay;
self.delay.reset(next);
// decide what kind of message to send
match Pin::new(&mut self.input_rx).poll_next(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
// if there's an actual message - return it
Poll::Ready(Some(real_message)) => Poll::Ready(Some(StreamMessage::Real(real_message))),
// otherwise construct a dummy one
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
}
}
}
impl<T: NymTopology> OutQueueControl<T> {
pub(crate) fn new(
mix_tx: mpsc::UnboundedSender<MixMessage>,
input_rx: mpsc::UnboundedReceiver<InputMessage>,
our_info: Destination,
topology: TopologyInnerRef<T>,
) -> Self {
let initial_delay = time::delay_for(Duration::from_secs_f64(MESSAGE_SENDING_AVERAGE_DELAY));
OutQueueControl {
delay: initial_delay,
mix_tx,
input_rx,
our_info,
topology_ctrl_ref: topology,
}
}
pub(crate) async fn run_out_queue_control(mut self) {
info!("starting out queue controller");
while let Some(next_message) = self.next().await {
trace!("created new message");
let read_lock = self.topology_ctrl_ref.read().await;
let topology = match read_lock.topology.as_ref() {
None => {
warn!(
"No valid topology detected - won't send any loop cover message this time"
);
continue;
}
Some(topology) => topology,
};
let next_packet = match next_message {
StreamMessage::Cover => mix_client::packet::loop_cover_message(
self.our_info.address,
self.our_info.identifier,
topology,
),
StreamMessage::Real(real_message) => mix_client::packet::encapsulate_message(
real_message.0,
real_message.1,
topology,
AVERAGE_PACKET_DELAY,
),
};
let next_packet = match next_packet {
Ok(message) => message,
Err(err) => {
error!(
"Somehow we managed to create an invalid traffic message - {:?}",
err
);
continue;
}
};
// if this one fails, there's no retrying because it means that either:
// - we run out of memory
// - the receiver channel is closed
// in either case there's no recovery and we can only panic
self.mix_tx
.unbounded_send(MixMessage::new(next_packet.0, next_packet.1))
.unwrap();
}
}
}
+89
View File
@@ -0,0 +1,89 @@
use futures::channel::{mpsc, oneshot};
use futures::lock::Mutex as FMutex;
use futures::StreamExt;
use log::{error, info, trace};
use std::sync::Arc;
pub type BufferResponse = oneshot::Sender<Vec<Vec<u8>>>;
pub(crate) struct ReceivedMessagesBuffer {
inner: Arc<FMutex<Inner>>,
}
impl ReceivedMessagesBuffer {
pub(crate) fn new() -> Self {
ReceivedMessagesBuffer {
inner: Arc::new(FMutex::new(Inner::new())),
}
}
pub(crate) async fn start_controllers(
self,
poller_rx: mpsc::UnboundedReceiver<Vec<Vec<u8>>>, // to receive new messages
query_receiver: mpsc::UnboundedReceiver<BufferResponse>, // to receive requests to acquire all stored messages
) {
let input_controller_future = tokio::spawn(Self::run_poller_input_controller(
self.inner.clone(),
poller_rx,
));
let output_controller_future = tokio::spawn(Self::run_query_output_controller(
self.inner,
query_receiver,
));
futures::future::select(input_controller_future, output_controller_future).await;
panic!("One of the received buffer controllers failed!")
}
pub(crate) async fn run_poller_input_controller(
buf: Arc<FMutex<Inner>>,
mut poller_rx: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
) {
info!("Started Received Messages Buffer Input Controller");
while let Some(new_messages) = poller_rx.next().await {
Inner::add_new_messages(&*buf, new_messages).await;
}
}
pub(crate) async fn run_query_output_controller(
buf: Arc<FMutex<Inner>>,
mut query_receiver: mpsc::UnboundedReceiver<BufferResponse>,
) {
info!("Started Received Messages Buffer Output Controller");
while let Some(request) = query_receiver.next().await {
let messages = Inner::acquire_and_empty(&*buf).await;
if let Err(failed_messages) = request.send(messages) {
error!(
"Failed to send the messages to the requester. Adding them back to the buffer"
);
Inner::add_new_messages(&*buf, failed_messages).await;
}
}
}
}
pub(crate) struct Inner {
messages: Vec<Vec<u8>>,
}
impl Inner {
fn new() -> Self {
Inner {
messages: Vec::new(),
}
}
async fn add_new_messages(buf: &FMutex<Self>, msgs: Vec<Vec<u8>>) {
trace!("Adding new messages to the buffer! {:?}", msgs);
let mut unlocked = buf.lock().await;
unlocked.messages.extend(msgs);
}
async fn acquire_and_empty(buf: &FMutex<Self>) -> Vec<Vec<u8>> {
trace!("Emptying the buffer and returning all messages");
let mut unlocked = buf.lock().await;
std::mem::replace(&mut unlocked.messages, Vec::new())
}
}
+148
View File
@@ -0,0 +1,148 @@
use crate::built_info;
use crypto::identity::{MixnetIdentityKeyPair, MixnetIdentityPrivateKey, MixnetIdentityPublicKey};
use healthcheck::HealthChecker;
use log::{error, info, trace, warn};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock as FRwLock;
use topology::NymTopology;
const NODE_HEALTH_THRESHOLD: f64 = 0.0;
// auxiliary type for ease of use
pub type TopologyInnerRef<T> = Arc<FRwLock<Inner<T>>>;
pub(crate) struct TopologyControl<T, IDPair, Priv, Pub>
where
T: NymTopology,
IDPair: MixnetIdentityKeyPair<Priv, Pub>,
Priv: MixnetIdentityPrivateKey,
Pub: MixnetIdentityPublicKey,
{
directory_server: String,
inner: Arc<FRwLock<Inner<T>>>,
health_checker: HealthChecker<IDPair, Priv, Pub>,
refresh_rate: f64,
}
#[derive(Debug)]
enum TopologyError {
HealthCheckError,
NoValidPathsError,
}
impl<T, IDPair, Priv, Pub> TopologyControl<T, IDPair, Priv, Pub>
where
T: NymTopology,
IDPair: MixnetIdentityKeyPair<Priv, Pub>,
Priv: MixnetIdentityPrivateKey,
Pub: MixnetIdentityPublicKey,
{
pub(crate) async fn new(
directory_server: String,
refresh_rate: f64,
identity_keypair: IDPair,
) -> Self {
// topology control run a healthcheck to determine healthy-ish nodes:
// this is a temporary solution as the healthcheck will eventually be moved to validators
let healthcheck_config = healthcheck::config::HealthCheck {
directory_server: directory_server.clone(),
// those are literally irrelevant when running single check
interval: 100000.0,
resolution_timeout: 5.0,
num_test_packets: 2,
};
let health_checker = healthcheck::HealthChecker::new(healthcheck_config, identity_keypair);
let mut topology_control = TopologyControl {
directory_server,
refresh_rate,
inner: Arc::new(FRwLock::new(Inner::new(None))),
health_checker,
};
// best effort approach to try to get a valid topology after call to 'new'
let initial_topology = match topology_control.get_current_compatible_topology().await {
Ok(topology) => Some(topology),
Err(err) => {
error!("Initial topology is invalid - {:?}. Right now it will be impossible to send any packets through the mixnet!", err);
None
}
};
topology_control
.update_global_topology(initial_topology)
.await;
topology_control
}
async fn get_current_compatible_topology(&self) -> Result<T, TopologyError> {
let full_topology = T::new(self.directory_server.clone());
let healthcheck_result = self.health_checker.do_check().await;
let healthcheck_scores = match healthcheck_result {
Err(err) => {
error!("Error while performing the healthcheck: {:?}", err);
return Err(TopologyError::HealthCheckError);
}
Ok(scores) => scores,
};
let healthy_topology =
healthcheck_scores.filter_topology_by_score(&full_topology, NODE_HEALTH_THRESHOLD);
let versioned_healthy_topology = healthy_topology.filter_node_versions(
built_info::PKG_VERSION,
built_info::PKG_VERSION,
built_info::PKG_VERSION,
);
// make sure you can still send a packet through the network:
if !versioned_healthy_topology.can_construct_path_through() {
return Err(TopologyError::NoValidPathsError);
}
Ok(versioned_healthy_topology)
}
pub(crate) fn get_inner_ref(&self) -> Arc<FRwLock<Inner<T>>> {
self.inner.clone()
}
async fn update_global_topology(&mut self, new_topology: Option<T>) {
// acquire write lock
let mut write_lock = self.inner.write().await;
write_lock.topology = new_topology;
}
pub(crate) async fn run_refresher(mut self) {
info!("Starting topology refresher");
let delay_duration = Duration::from_secs_f64(self.refresh_rate);
loop {
trace!("Refreshing the topology");
let new_topology_res = self.get_current_compatible_topology().await;
let new_topology = match new_topology_res {
Ok(topology) => Some(topology),
Err(err) => {
warn!("the obtained topology seems to be invalid - {:?}, it will be impossible to send packets through", err);
None
}
};
self.update_global_topology(new_topology).await;
tokio::time::delay_for(delay_duration).await;
}
}
}
pub struct Inner<T: NymTopology> {
pub topology: Option<T>,
}
impl<T: NymTopology> Inner<T> {
fn new(topology: Option<T>) -> Self {
Inner { topology }
}
}
-387
View File
@@ -1,387 +0,0 @@
use crate::built_info;
use crate::sockets::tcp;
use crate::sockets::ws;
use crate::utils;
use directory_client::presence::Topology;
use futures::channel::{mpsc, oneshot};
use futures::join;
use futures::lock::Mutex as FMutex;
use futures::select;
use futures::{SinkExt, StreamExt};
use log::*;
use sfw_provider_requests::AuthToken;
use sphinx::route::{Destination, DestinationAddressBytes};
use sphinx::SphinxPacket;
use std::io;
use std::io::prelude::*;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tokio::runtime::Runtime;
use topology::NymTopology;
const LOOP_COVER_AVERAGE_DELAY: f64 = 0.5;
// seconds
const MESSAGE_SENDING_AVERAGE_DELAY: f64 = 0.5;
// seconds;
const FETCH_MESSAGES_DELAY: f64 = 1.0; // seconds;
// provider-poller sends polls service provider; receives messages
// provider-poller sends (TX) to ReceivedBufferController (RX)
// ReceivedBufferController sends (TX) to ... ??Client??
// outQueueController sends (TX) to TrafficStreamController (RX)
// TrafficStreamController sends messages to mixnet
// ... ??Client?? sends (TX) to outQueueController (RX)
// Loop cover traffic stream just sends messages to mixnet without any channel communication
struct MixMessage(SocketAddr, SphinxPacket);
struct MixTrafficController;
impl MixTrafficController {
// this was way more difficult to implement than what this code may suggest...
async fn run(mut rx: mpsc::UnboundedReceiver<MixMessage>) {
let mix_client = mix_client::MixClient::new();
while let Some(mix_message) = rx.next().await {
info!(
"[MIX TRAFFIC CONTROL] - got a mix_message for {:?}",
mix_message.0
);
let send_res = mix_client.send(mix_message.1, mix_message.0).await;
match send_res {
Ok(_) => {
print!(".");
io::stdout().flush().ok().expect("Could not flush stdout");
}
Err(e) => error!("We failed to send the message :( - {:?}", e),
};
}
}
}
pub type BufferResponse = oneshot::Sender<Vec<Vec<u8>>>;
struct ReceivedMessagesBuffer {
messages: Vec<Vec<u8>>,
}
impl ReceivedMessagesBuffer {
fn add_arc_futures_mutex(self) -> Arc<FMutex<Self>> {
Arc::new(FMutex::new(self))
}
fn new() -> Self {
ReceivedMessagesBuffer {
messages: Vec::new(),
}
}
async fn add_new_messages(buf: Arc<FMutex<Self>>, msgs: Vec<Vec<u8>>) {
info!("Adding new messages to the buffer! {:?}", msgs);
let mut unlocked = buf.lock().await;
unlocked.messages.extend(msgs);
}
async fn run_poller_input_controller(
buf: Arc<FMutex<Self>>,
mut poller_rx: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
) {
while let Some(new_messages) = poller_rx.next().await {
ReceivedMessagesBuffer::add_new_messages(buf.clone(), new_messages).await;
}
}
async fn acquire_and_empty(buf: Arc<FMutex<Self>>) -> Vec<Vec<u8>> {
let mut unlocked = buf.lock().await;
std::mem::replace(&mut unlocked.messages, Vec::new())
}
async fn run_query_output_controller(
buf: Arc<FMutex<Self>>,
mut query_receiver: mpsc::UnboundedReceiver<BufferResponse>,
) {
while let Some(request) = query_receiver.next().await {
let messages = ReceivedMessagesBuffer::acquire_and_empty(buf.clone()).await;
// if this fails, the whole application needs to blow
// because currently only this thread would fail
request.send(messages).unwrap();
}
}
}
pub enum SocketType {
TCP,
WebSocket,
None,
}
pub struct NymClient {
// to be replaced by something else I guess
address: DestinationAddressBytes,
pub input_tx: mpsc::UnboundedSender<InputMessage>,
// to be used by "send" function or socket, etc
input_rx: mpsc::UnboundedReceiver<InputMessage>,
socket_listening_address: SocketAddr,
directory: String,
auth_token: Option<AuthToken>,
socket_type: SocketType,
}
#[derive(Debug)]
pub struct InputMessage(pub Destination, pub Vec<u8>);
impl NymClient {
pub fn new(
address: DestinationAddressBytes,
socket_listening_address: SocketAddr,
directory: String,
auth_token: Option<AuthToken>,
socket_type: SocketType,
) -> Self {
let (input_tx, input_rx) = mpsc::unbounded::<InputMessage>();
NymClient {
address,
input_tx,
input_rx,
socket_listening_address,
directory,
auth_token,
socket_type,
}
}
async fn start_loop_cover_traffic_stream(
mut tx: mpsc::UnboundedSender<MixMessage>,
our_info: Destination,
topology: Topology,
) {
loop {
info!("[LOOP COVER TRAFFIC STREAM] - next cover message!");
let delay = utils::poisson::sample(LOOP_COVER_AVERAGE_DELAY);
let delay_duration = Duration::from_secs_f64(delay);
tokio::time::delay_for(delay_duration).await;
let cover_message =
utils::sphinx::loop_cover_message(our_info.address, our_info.identifier, &topology);
tx.send(MixMessage(cover_message.0, cover_message.1))
.await
.unwrap();
}
}
async fn control_out_queue(
mut mix_tx: mpsc::UnboundedSender<MixMessage>,
mut input_rx: mpsc::UnboundedReceiver<InputMessage>,
our_info: Destination,
topology: Topology,
) {
loop {
info!("[OUT QUEUE] here I will be sending real traffic (or loop cover if nothing is available)");
// TODO: consider replacing select macro with our own proper future definition with polling
let traffic_message = select! {
real_message = input_rx.next() => {
info!("[OUT QUEUE] - we got a real message!");
if real_message.is_none() {
error!("Unexpected 'None' real message!");
std::process::exit(1);
}
let real_message = real_message.unwrap();
println!("real: {:?}", real_message);
utils::sphinx::encapsulate_message(real_message.0, real_message.1, &topology)
},
default => {
info!("[OUT QUEUE] - no real message - going to send extra loop cover");
utils::sphinx::loop_cover_message(our_info.address, our_info.identifier, &topology)
}
};
mix_tx
.send(MixMessage(traffic_message.0, traffic_message.1))
.await
.unwrap();
let delay_duration = Duration::from_secs_f64(MESSAGE_SENDING_AVERAGE_DELAY);
tokio::time::delay_for(delay_duration).await;
}
}
async fn start_provider_polling(
provider_client: provider_client::ProviderClient,
mut poller_tx: mpsc::UnboundedSender<Vec<Vec<u8>>>,
) {
let loop_message = &utils::sphinx::LOOP_COVER_MESSAGE_PAYLOAD.to_vec();
let dummy_message = &sfw_provider_requests::DUMMY_MESSAGE_CONTENT.to_vec();
loop {
let delay_duration = Duration::from_secs_f64(FETCH_MESSAGES_DELAY);
tokio::time::delay_for(delay_duration).await;
info!("[FETCH MSG] - Polling provider...");
let messages = provider_client.retrieve_messages().await.unwrap();
let good_messages = messages
.into_iter()
.filter(|message| message != loop_message && message != dummy_message)
.collect();
// if any of those fails, whole application should blow...
poller_tx.send(good_messages).await.unwrap();
}
}
pub fn start(self) -> Result<(), Box<dyn std::error::Error>> {
let score_threshold = 0.0;
println!("Starting nym client");
let mut rt = Runtime::new()?;
println!("Trying to obtain valid, healthy, topology");
let full_topology = Topology::new(self.directory.clone());
// run a healthcheck to determine healthy-ish nodes:
// this is a temporary solution as the healthcheck will eventually be moved to validators
let healthcheck_config = healthcheck::config::HealthCheck {
directory_server: self.directory.clone(),
// those are literally unrelevant when running single check
interval: 100000.0,
resolution_timeout: 5.0,
num_test_packets: 2,
};
let healthcheck = healthcheck::HealthChecker::new(healthcheck_config);
let healthcheck_result = rt.block_on(healthcheck.do_check());
let healthcheck_scores = match healthcheck_result {
Err(err) => {
error!(
"failed to perform healthcheck to determine healthy topology - {:?}",
err
);
return Err(Box::new(err));
}
Ok(scores) => scores,
};
let healthy_topology =
healthcheck_scores.filter_topology_by_score(&full_topology, score_threshold);
// for time being assume same versioning, i.e. if client is running X.Y.Z,
// we're expecting mixes, providers and coconodes to also be running X.Y.Z
let versioned_healthy_topology = healthy_topology.filter_node_versions(
built_info::PKG_VERSION,
built_info::PKG_VERSION,
built_info::PKG_VERSION,
);
// make sure you can still send a packet through the network:
if !versioned_healthy_topology.can_construct_path_through() {
error!("No valid path exists in the topology");
// TODO: replace panic with proper return type
panic!("No valid path exists in the topology");
}
// this is temporary and assumes there exists only a single provider.
let provider_client_listener_address: SocketAddr = versioned_healthy_topology
.get_mix_provider_nodes()
.first()
.expect("Could not get a provider from the supplied network topology, are you using the right directory server?")
.client_listener;
let mut provider_client = provider_client::ProviderClient::new(
provider_client_listener_address,
self.address,
self.auth_token,
);
// registration
rt.block_on(async {
match self.auth_token {
None => {
let auth_token = provider_client.register().await.unwrap();
provider_client.update_token(auth_token);
info!("Obtained new token! - {:?}", auth_token);
}
Some(token) => println!("Already got the token! - {:?}", token),
}
});
// channels for intercomponent communication
let (mix_tx, mix_rx) = mpsc::unbounded();
let (poller_input_tx, poller_input_rx) = mpsc::unbounded();
let (received_messages_buffer_output_tx, received_messages_buffer_output_rx) =
mpsc::unbounded();
let received_messages_buffer = ReceivedMessagesBuffer::new().add_arc_futures_mutex();
let received_messages_buffer_input_controller_future =
rt.spawn(ReceivedMessagesBuffer::run_poller_input_controller(
received_messages_buffer.clone(),
poller_input_rx,
));
let received_messages_buffer_output_controller_future =
rt.spawn(ReceivedMessagesBuffer::run_query_output_controller(
received_messages_buffer,
received_messages_buffer_output_rx,
));
let mix_traffic_future = rt.spawn(MixTrafficController::run(mix_rx));
let loop_cover_traffic_future = rt.spawn(NymClient::start_loop_cover_traffic_stream(
mix_tx.clone(),
Destination::new(self.address, Default::default()),
versioned_healthy_topology.clone(),
));
let out_queue_control_future = rt.spawn(NymClient::control_out_queue(
mix_tx,
self.input_rx,
Destination::new(self.address, Default::default()),
versioned_healthy_topology.clone(),
));
let provider_polling_future = rt.spawn(NymClient::start_provider_polling(
provider_client,
poller_input_tx,
));
match self.socket_type {
SocketType::WebSocket => {
rt.spawn(ws::start_websocket(
self.socket_listening_address,
self.input_tx,
received_messages_buffer_output_tx,
self.address,
versioned_healthy_topology,
));
}
SocketType::TCP => {
rt.spawn(tcp::start_tcpsocket(
self.socket_listening_address,
self.input_tx,
received_messages_buffer_output_tx,
self.address,
versioned_healthy_topology,
));
}
SocketType::None => (),
}
rt.block_on(async {
let future_results = join!(
received_messages_buffer_input_controller_future,
received_messages_buffer_output_controller_future,
mix_traffic_future,
loop_cover_traffic_future,
out_queue_control_future,
provider_polling_future,
);
assert!(
future_results.0.is_ok()
&& future_results.1.is_ok()
&& future_results.2.is_ok()
&& future_results.3.is_ok()
&& future_results.4.is_ok()
&& future_results.5.is_ok()
);
});
// this line in theory should never be reached as the runtime should be permanently blocked on traffic senders
eprintln!("The client went kaput...");
Ok(())
}
}
+4 -4
View File
@@ -1,18 +1,18 @@
use crate::persistence::pathfinder::Pathfinder;
use crate::persistence::pemstore::PemStore;
use crate::config::persistance::pathfinder::ClientPathfinder;
use clap::ArgMatches;
use crypto::identity::MixnetIdentityKeyPair;
use pemstore::pemstore::PemStore;
pub fn execute(matches: &ArgMatches) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap().to_string(); // required for now
let pathfinder = Pathfinder::new(id);
let pathfinder = ClientPathfinder::new(id);
println!("Writing keypairs to {:?}...", pathfinder.config_dir);
let mix_keys = crypto::identity::DummyMixIdentityKeyPair::new();
let pem_store = PemStore::new(pathfinder);
pem_store.write_identity(mix_keys);
pem_store.write_identity(mix_keys).unwrap();
println!("Client configuration completed.\n\n\n")
}
+10 -10
View File
@@ -1,8 +1,8 @@
use crate::clients::{NymClient, SocketType};
use crate::persistence::pemstore;
use crate::client::{NymClient, SocketType};
use crate::config::persistance::pathfinder::ClientPathfinder;
use clap::ArgMatches;
use crypto::identity::{MixnetIdentityKeyPair, MixnetIdentityPublicKey};
use crypto::identity::DummyMixIdentityKeyPair;
use pemstore::pemstore::PemStore;
use std::net::ToSocketAddrs;
pub fn execute(matches: &ArgMatches) {
@@ -26,17 +26,17 @@ pub fn execute(matches: &ArgMatches) {
.next()
.expect("Failed to extract the socket address from the iterator");
let keypair = pemstore::read_mix_identity_keypair_from_disk(id);
// TODO: currently we know we are reading the 'DummyMixIdentityKeyPair', but how to properly assert the type?
let keypair: DummyMixIdentityKeyPair = PemStore::new(ClientPathfinder::new(id))
.read_identity()
.unwrap();
// TODO: reading auth_token from disk (if exists);
println!("Public key: {}", keypair.public_key.to_b64_string());
println!("Public key: {}", keypair.public_key.to_base58_string());
let mut temporary_address = [0u8; 32];
let public_key_bytes = keypair.public_key().to_bytes();
temporary_address.copy_from_slice(&public_key_bytes[..]);
let auth_token = None;
let client = NymClient::new(
temporary_address,
keypair,
socket_address.clone(),
directory_server,
auth_token,
+11 -10
View File
@@ -1,8 +1,8 @@
use crate::clients::{NymClient, SocketType};
use crate::persistence::pemstore;
use crate::client::{NymClient, SocketType};
use crate::config::persistance::pathfinder::ClientPathfinder;
use clap::ArgMatches;
use crypto::identity::{MixnetIdentityKeyPair, MixnetIdentityPublicKey};
use crypto::identity::DummyMixIdentityKeyPair;
use pemstore::pemstore::PemStore;
use std::net::ToSocketAddrs;
pub fn execute(matches: &ArgMatches) {
@@ -26,17 +26,18 @@ pub fn execute(matches: &ArgMatches) {
.next()
.expect("Failed to extract the socket address from the iterator");
let keypair = pemstore::read_mix_identity_keypair_from_disk(id);
// TODO: currently we know we are reading the 'DummyMixIdentityKeyPair', but how to properly assert the type?
let keypair: DummyMixIdentityKeyPair = PemStore::new(ClientPathfinder::new(id))
.read_identity()
.unwrap();
// TODO: reading auth_token from disk (if exists);
println!("Public key: {}", keypair.public_key.to_b64_string());
println!("Public key: {}", keypair.public_key.to_base58_string());
let mut temporary_address = [0u8; 32];
let public_key_bytes = keypair.public_key().to_bytes();
temporary_address.copy_from_slice(&public_key_bytes[..]);
let auth_token = None;
let client = NymClient::new(
temporary_address,
keypair,
socket_address,
directory_server,
auth_token,
+1
View File
@@ -0,0 +1 @@
pub mod persistance;
+1
View File
@@ -0,0 +1 @@
pub mod pathfinder;
@@ -1,21 +1,36 @@
use pemstore::pathfinder::PathFinder;
use std::path::PathBuf;
pub struct Pathfinder {
pub struct ClientPathfinder {
pub config_dir: PathBuf,
pub private_mix_key: PathBuf,
pub public_mix_key: PathBuf,
}
impl Pathfinder {
pub fn new(id: String) -> Pathfinder {
impl ClientPathfinder {
pub fn new(id: String) -> Self {
let os_config_dir = dirs::config_dir().unwrap(); // grabs the OS default config dir
let config_dir = os_config_dir.join("nym").join("clients").join(id);
let private_mix_key = config_dir.join("private.pem");
let public_mix_key = config_dir.join("public.pem");
Pathfinder {
ClientPathfinder {
config_dir,
private_mix_key,
public_mix_key,
}
}
}
impl PathFinder for ClientPathfinder {
fn config_dir(&self) -> PathBuf {
self.config_dir.clone()
}
fn private_identity_key(&self) -> PathBuf {
self.private_mix_key.clone()
}
fn public_identity_key(&self) -> PathBuf {
self.public_mix_key.clone()
}
}
+3 -7
View File
@@ -1,8 +1,4 @@
#![recursion_limit = "256"]
pub mod built_info;
pub mod clients;
pub mod commands;
pub mod persistence;
pub mod sockets;
pub mod utils;
pub mod client;
pub mod config;
mod sockets;
+4 -6
View File
@@ -1,16 +1,14 @@
#![recursion_limit = "256"]
use clap::{App, Arg, ArgMatches, SubCommand};
pub mod built_info;
pub mod clients;
pub mod client;
mod commands;
mod persistence;
pub mod config;
mod sockets;
pub mod utils;
fn main() {
env_logger::init();
dotenv::dotenv().ok();
pretty_env_logger::init();
let arg_matches = App::new("Nym Client")
.version(built_info::PKG_VERSION)
-1
View File
@@ -1 +0,0 @@
// TODO: we can put all the TOML config templating code in here once we get to that.
+62 -53
View File
@@ -1,17 +1,17 @@
use crate::clients::BufferResponse;
use crate::clients::InputMessage;
use directory_client::presence::Topology;
use crate::client::received_buffer::BufferResponse;
use crate::client::topology_control::TopologyInnerRef;
use crate::client::InputMessage;
use futures::channel::{mpsc, oneshot};
use futures::future::FutureExt;
use futures::io::Error;
use futures::SinkExt;
use log::*;
use sphinx::route::{Destination, DestinationAddressBytes};
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::io;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::prelude::*;
use topology::NymTopology;
const SEND_REQUEST_PREFIX: u8 = 1;
const FETCH_REQUEST_PREFIX: u8 = 2;
@@ -94,54 +94,65 @@ impl ClientRequest {
recipient_address: DestinationAddressBytes,
mut input_tx: mpsc::UnboundedSender<InputMessage>,
) -> ServerResponse {
println!(
"send handle. sending to: {:?}, msg: {:?}",
recipient_address, msg
);
trace!("sending to: {:?}, msg: {:?}", recipient_address, msg);
if msg.len() > sphinx::constants::MAXIMUM_PLAINTEXT_LENGTH {
return ServerResponse::Error {
message: format!(
"too long message. Sent {} bytes while the maximum is {}",
msg.len(),
sphinx::constants::MAXIMUM_PLAINTEXT_LENGTH
)
.to_string(),
};
}
let dummy_surb = [0; 16];
let input_msg = InputMessage(Destination::new(recipient_address, dummy_surb), msg);
println!("ALMOST ABOUT TO SOMEDAY SEND {:?}", input_msg);
input_tx.send(input_msg).await.unwrap();
ServerResponse::Send
}
async fn handle_fetch(mut msg_query: mpsc::UnboundedSender<BufferResponse>) -> ServerResponse {
println!("fetch handle");
trace!("handle_fetch called");
let (res_tx, res_rx) = oneshot::channel();
if msg_query.send(res_tx).await.is_err() {
return ServerResponse::Error {
message: "Server failed to receive messages".to_string(),
};
let e = "Nym-client TCP socket failed to receive messages".to_string();
error!("{}", e);
return ServerResponse::Error { message: e };
}
let messages = res_rx.map(|msg| msg).await;
let messages = match res_rx.map(|msg| msg).await {
Ok(messages) => messages,
Err(e) => {
warn!("Failed to fetch client messages - {:?}", e);
return ServerResponse::Error {
message: format!("Server failed to receive messages").to_string(),
};
}
};
if messages.is_err() {
return ServerResponse::Error {
message: "Server failed to receive messages".to_string(),
};
}
let messages = messages.unwrap();
println!("fetched {} messages", messages.len());
trace!("fetched {} messages", messages.len());
ServerResponse::Fetch { messages }
}
async fn handle_get_clients(topology: &Topology) -> ServerResponse {
println!("get clients handle");
let clients = topology
.mix_provider_nodes
.iter()
.flat_map(|provider| provider.registered_clients.iter())
.map(|client| base64::decode_config(&client.pub_key, base64::URL_SAFE).unwrap()) // TODO: this can potentially throw an error
.collect();
ServerResponse::GetClients { clients }
async fn handle_get_clients<T: NymTopology>(topology: &TopologyInnerRef<T>) -> ServerResponse {
let topology_data = &topology.read().await.topology;
match topology_data {
Some(topology) => {
let clients = topology
.providers()
.iter()
.flat_map(|provider| provider.registered_clients.iter())
.filter_map(|client| bs58::decode(&client.pub_key).into_vec().ok())
.collect();
ServerResponse::GetClients { clients }
}
None => ServerResponse::Error {
message: "Invalid network topology".to_string(),
},
}
}
async fn handle_own_details(self_address_bytes: DestinationAddressBytes) -> ServerResponse {
println!("own details handle");
ServerResponse::OwnDetails {
address: self_address_bytes.to_vec(),
}
@@ -189,8 +200,8 @@ fn encode_fetched_messages(messages: Vec<Vec<u8>>) -> Vec<u8> {
}
fn encode_list_of_clients(clients: Vec<Vec<u8>>) -> Vec<u8> {
println!("clients: {:?}", clients);
// we can just concat all clients since all of them got to be 32 bytes long
debug!("client: {:?}", clients);
// we can just concat all client since all of them got to be 32 bytes long
// (if not, then we have bigger problem somewhere up the line)
// converts [[1,2,3],[4,5,6],...] into [1,2,3,4,5,6,...]
@@ -203,9 +214,9 @@ impl ServerResponse {
}
}
async fn handle_connection(
async fn handle_connection<T: NymTopology>(
data: &[u8],
request_handling_data: RequestHandlingData,
request_handling_data: RequestHandlingData<T>,
) -> Result<ServerResponse, TCPSocketError> {
let request = ClientRequest::try_from(data)?;
let response = match request {
@@ -218,7 +229,7 @@ async fn handle_connection(
}
ClientRequest::Fetch => ClientRequest::handle_fetch(request_handling_data.msg_query).await,
ClientRequest::GetClients => {
ClientRequest::handle_get_clients(request_handling_data.topology.borrow()).await
ClientRequest::handle_get_clients(&request_handling_data.topology).await
}
ClientRequest::OwnDetails => {
ClientRequest::handle_own_details(request_handling_data.self_address).await
@@ -228,26 +239,24 @@ async fn handle_connection(
Ok(response)
}
struct RequestHandlingData {
struct RequestHandlingData<T: NymTopology> {
msg_input: mpsc::UnboundedSender<InputMessage>,
msg_query: mpsc::UnboundedSender<BufferResponse>,
self_address: DestinationAddressBytes,
topology: Arc<Topology>,
topology: TopologyInnerRef<T>,
}
async fn accept_connection(
async fn accept_connection<T: 'static + NymTopology>(
mut socket: tokio::net::TcpStream,
msg_input: mpsc::UnboundedSender<InputMessage>,
msg_query: mpsc::UnboundedSender<BufferResponse>,
self_address: DestinationAddressBytes,
topology: Topology,
topology: TopologyInnerRef<T>,
) {
let address = socket
.peer_addr()
.expect("connected streams should have a peer address");
println!("Peer address: {}", address);
let topology = Arc::new(topology);
debug!("Peer address: {}", address);
let mut buf = [0u8; 2048];
@@ -258,7 +267,7 @@ async fn accept_connection(
let response = match socket.read(&mut buf).await {
// socket closed
Ok(n) if n == 0 => {
println!("Remote connection closed.");
trace!("Remote connection closed.");
return;
}
Ok(n) => {
@@ -274,25 +283,25 @@ async fn accept_connection(
}
}
Err(e) => {
eprintln!("failed to read from socket; err = {:?}", e);
warn!("failed to read from socket; err = {:?}", e);
return;
}
};
let response_vec: Vec<u8> = response.into();
if let Err(e) = socket.write_all(&response_vec).await {
eprintln!("failed to write reply to socket; err = {:?}", e);
warn!("failed to write reply to socket; err = {:?}", e);
return;
}
}
}
pub async fn start_tcpsocket(
pub async fn start_tcpsocket<T: 'static + NymTopology>(
address: SocketAddr,
message_tx: mpsc::UnboundedSender<InputMessage>,
received_messages_query_tx: mpsc::UnboundedSender<BufferResponse>,
self_address: DestinationAddressBytes,
topology: Topology,
topology: TopologyInnerRef<T>,
) -> Result<(), TCPSocketError> {
let mut listener = tokio::net::TcpListener::bind(address).await?;
@@ -308,6 +317,6 @@ pub async fn start_tcpsocket(
));
}
eprintln!("The tcpsocket went kaput...");
error!("The tcpsocket went kaput...");
Ok(())
}
+47 -46
View File
@@ -1,6 +1,6 @@
use crate::clients::BufferResponse;
use crate::clients::InputMessage;
use directory_client::presence::Topology;
use crate::client::received_buffer::BufferResponse;
use crate::client::topology_control::TopologyInnerRef;
use crate::client::InputMessage;
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures::channel::{mpsc, oneshot};
use futures::future::FutureExt;
@@ -12,20 +12,21 @@ use sphinx::route::{Destination, DestinationAddressBytes};
use std::convert::TryFrom;
use std::io;
use std::net::SocketAddr;
use topology::NymTopology;
use tungstenite::protocol::frame::coding::CloseCode;
use tungstenite::protocol::{CloseFrame, Message};
struct Connection {
struct Connection<T: NymTopology> {
address: SocketAddr,
msg_input: mpsc::UnboundedSender<InputMessage>,
msg_query: mpsc::UnboundedSender<BufferResponse>,
rx: UnboundedReceiver<Message>,
self_address: DestinationAddressBytes,
topology: Topology,
topology: TopologyInnerRef<T>,
tx: UnboundedSender<Message>,
}
impl Connection {
impl<T: NymTopology> Connection<T> {
async fn handle_text_message(&self, msg: String) -> ServerResponse {
debug!("Handling text message request");
trace!("Content: {:?}", msg.clone());
@@ -48,9 +49,7 @@ impl Connection {
ClientRequest::handle_send(message, recipient_address, self.msg_input.clone()).await
}
ClientRequest::Fetch => ClientRequest::handle_fetch(self.msg_query.clone()).await,
ClientRequest::GetClients => {
ClientRequest::handle_get_clients(self.topology.clone()).await
}
ClientRequest::GetClients => ClientRequest::handle_get_clients(&self.topology).await,
ClientRequest::OwnDetails => ClientRequest::handle_own_details(self.self_address).await,
}
}
@@ -189,32 +188,24 @@ impl ClientRequest {
mut input_tx: mpsc::UnboundedSender<InputMessage>,
) -> ServerResponse {
let message_bytes = msg.into_bytes();
// TODO: wait until 0.4.0 release to replace those constants with newly exposed
// sphinx::constants::MAXIMUM_PLAINTEXT_LENGTH
// we can't do it now for compatibility reasons as most recent sphinx revision
// has breaking changes due to packet format changes
let maximum_plaintext_length = sphinx::constants::PAYLOAD_SIZE
- sphinx::constants::SECURITY_PARAMETER
- sphinx::constants::DESTINATION_ADDRESS_LENGTH
- 1;
if message_bytes.len() > maximum_plaintext_length {
if message_bytes.len() > sphinx::constants::MAXIMUM_PLAINTEXT_LENGTH {
return ServerResponse::Error {
message: format!(
"too long message. Sent {} bytes while the maximum is {}",
"message too long. Sent {} bytes, but the maximum is {}",
message_bytes.len(),
maximum_plaintext_length
sphinx::constants::MAXIMUM_PLAINTEXT_LENGTH
)
.to_string(),
};
}
let address_vec = match base64::decode_config(&recipient_address, base64::URL_SAFE) {
let address_vec = match bs58::decode(&recipient_address).into_vec() {
Err(e) => {
return ServerResponse::Error {
message: e.to_string(),
}
};
}
Ok(hex) => hex,
Ok(bytes) => bytes,
};
if address_vec.len() != 32 {
@@ -243,16 +234,17 @@ impl ClientRequest {
};
}
let messages = res_rx.map(|msg| msg).await;
if messages.is_err() {
warn!("Failed to handle_fetch. messages is an error");
return ServerResponse::Error {
message: "Server failed to receive messages".to_string(),
};
}
let messages = match res_rx.map(|msg| msg).await {
Ok(messages) => messages,
Err(e) => {
warn!("Failed to fetch client messages - {:?}", e);
return ServerResponse::Error {
message: format!("Server failed to receive messages").to_string(),
};
}
};
let messages = messages
.unwrap()
.into_iter()
.map(|message| {
match std::str::from_utf8(&message) {
@@ -269,18 +261,26 @@ impl ClientRequest {
ServerResponse::Fetch { messages }
}
async fn handle_get_clients(topology: Topology) -> ServerResponse {
let clients = topology
.mix_provider_nodes
.into_iter()
.flat_map(|provider| provider.registered_clients.into_iter())
.map(|client| client.pub_key)
.collect();
ServerResponse::GetClients { clients }
async fn handle_get_clients<T: NymTopology>(topology: &TopologyInnerRef<T>) -> ServerResponse {
let topology_data = &topology.read().await.topology;
match topology_data {
Some(topology) => {
let clients = topology
.providers()
.iter()
.flat_map(|provider| provider.registered_clients.iter())
.map(|client| client.pub_key.clone())
.collect();
ServerResponse::GetClients { clients }
}
None => ServerResponse::Error {
message: "Invalid network topology".to_string(),
},
}
}
async fn handle_own_details(self_address_bytes: DestinationAddressBytes) -> ServerResponse {
let self_address = base64::encode_config(&self_address_bytes, base64::URL_SAFE);
let self_address = bs58::encode(&self_address_bytes).into_string();
ServerResponse::OwnDetails {
address: self_address,
}
@@ -306,14 +306,13 @@ impl Into<Message> for ServerResponse {
}
}
async fn accept_connection(
async fn accept_connection<T: 'static + NymTopology>(
stream: tokio::net::TcpStream,
msg_input: mpsc::UnboundedSender<InputMessage>,
msg_query: mpsc::UnboundedSender<BufferResponse>,
self_address: DestinationAddressBytes,
topology: Topology,
topology: TopologyInnerRef<T>,
) {
warn!("accept_connection");
let address = stream
.peer_addr()
.expect("connected streams should have a peer address");
@@ -337,6 +336,8 @@ async fn accept_connection(
msg_query,
self_address,
};
// TODO: make sure this actually doesn't leak memory...
tokio::spawn(conn.handle());
while let Some(message) = ws_stream.next().await {
@@ -385,12 +386,12 @@ async fn accept_connection(
}
}
pub async fn start_websocket(
pub async fn start_websocket<T: 'static + NymTopology>(
address: SocketAddr,
message_tx: mpsc::UnboundedSender<InputMessage>,
received_messages_query_tx: mpsc::UnboundedSender<BufferResponse>,
self_address: DestinationAddressBytes,
topology: Topology,
topology: TopologyInnerRef<T>,
) -> Result<(), WebSocketError> {
let mut listener = tokio::net::TcpListener::bind(address).await?;
@@ -406,6 +407,6 @@ pub async fn start_websocket(
));
}
eprintln!("The websocket went kaput...");
error!("The websocket went kaput...");
Ok(())
}
-63
View File
@@ -1,63 +0,0 @@
pub fn zero_pad_to_32(mut bytes: Vec<u8>) -> [u8; 32] {
assert!(bytes.len() <= 32);
if bytes.len() != 32 {
bytes.resize(32, 0);
}
let mut padded_bytes = [0; 32];
padded_bytes.copy_from_slice(&bytes[..]);
padded_bytes
}
#[cfg(test)]
mod zero_padding_to_32_bytes {
use super::*;
#[cfg(test)]
mod with_empty_input {
use super::*;
#[test]
fn it_returns_32_zeros() {
let input = vec![];
let result = zero_pad_to_32(input);
assert_eq!([0u8; 32], result);
}
}
#[cfg(test)]
mod with_all_bytes_set_to_1 {
use super::*;
#[test]
fn it_returns_32_ones() {
let input = vec![1u8; 32];
let result = zero_pad_to_32(input);
assert_eq!([1u8; 32], result);
}
}
#[cfg(test)]
mod with_3_bytes_set {
use super::*;
#[test]
fn it_returns_input_zero_padded_to_32_bytes() {
let input = vec![1u8; 3];
let result = zero_pad_to_32(input);
let expected_content = vec![
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,
];
assert_eq!(expected_content, result.to_vec());
}
}
#[cfg(test)]
mod with_oversized_input {
use super::*;
#[test]
#[should_panic]
fn it_panics() {
let input = vec![1u8; 33];
zero_pad_to_32(input);
}
}
}
-3
View File
@@ -1,3 +0,0 @@
pub mod bytes;
pub mod poisson;
pub mod sphinx;
-6
View File
@@ -1,6 +0,0 @@
use rand_distr::{Distribution, Exp};
pub fn sample(average_delay: f64) -> f64 {
let exp = Exp::new(1.0 / average_delay).unwrap();
exp.sample(&mut rand::thread_rng())
}
-40
View File
@@ -1,40 +0,0 @@
use addressing;
use sphinx::route::{Destination, DestinationAddressBytes, SURBIdentifier};
use sphinx::SphinxPacket;
use std::net::SocketAddr;
use topology::NymTopology;
pub const LOOP_COVER_MESSAGE_PAYLOAD: &[u8] = b"The cake is a lie!";
pub fn loop_cover_message<T: NymTopology>(
our_address: DestinationAddressBytes,
surb_id: SURBIdentifier,
topology: &T,
) -> (SocketAddr, SphinxPacket) {
let destination = Destination::new(our_address, surb_id);
encapsulate_message(destination, LOOP_COVER_MESSAGE_PAYLOAD.to_vec(), topology)
}
pub fn encapsulate_message<T: NymTopology>(
recipient: Destination,
message: Vec<u8>,
topology: &T,
) -> (SocketAddr, SphinxPacket) {
let mut providers = topology.get_mix_provider_nodes();
let provider = providers.pop().unwrap().into();
let route = topology.route_to(provider).unwrap();
// Set average packet delay to an arbitrary but at least not super-slow value for testing.
let average_delay = 0.1;
let delays = sphinx::header::delays::generate(route.len(), average_delay);
// build the packet
let packet = sphinx::SphinxPacket::new(message, &route[..], &recipient, &delays).unwrap();
let first_node_address =
addressing::socket_address_from_encoded_bytes(route.first().unwrap().address.to_bytes());
(first_node_address, packet)
}
-38
View File
@@ -1,38 +0,0 @@
# nym-sfw-provider Changelog
## 0.3.3
* Version increase for consistency with `nym-client`
## 0.3.2
* Version increase for consistency with `nym-mixnode` and `nym-client`
## 0.3.1
* Fixed crash when directory server goes down
## 0.3.0
* cleaned up a lot of internal dependencies
* reporting version to the directory server
* printing warning on trying to bind to "localhost", "127.0.0.1" or "0.0.0.0"
* more informative error messages
* generalised identity keys
* generalised Topology handling
* started slow transition to `log` crate by `nym-client`
* start of 'MixMining'
* start of validator node
## 0.2.0
* removed the `--local` flag
* introduced `--directory` argument to support arbitrary directory servers. Leaving it out will point the node at the "https://directory.nymtech.net" alpha testnet server
* IPv6 support
* provider version number is now shown at node start
* directory server location is now shown at node start
## 0.1.0 - Initial Release
* The bare minimum set of features required by a Nym Store and Forward Provider
+6 -5
View File
@@ -1,19 +1,20 @@
[package]
build = "build.rs"
name = "nym-sfw-provider"
version = "0.3.3"
version = "0.4.0-rc.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = "0.11.0"
bs58 = "0.3.0"
clap = "2.33.0"
curve25519-dalek = "1.2.3"
hex = "0.4.0"
dotenv = "0.15.0"
futures = "0.3.1"
log = "0.4.8"
log = "0.4"
pretty_env_logger = "0.3"
rand = "0.7.2"
tokio = { version = "0.2.4", features = ["full"] }
sha2 = "0.8.0"
@@ -27,7 +28,7 @@ directory-client = { path = "../common/clients/directory-client" }
sfw-provider-requests = { path = "./sfw-provider-requests" }
## will be moved to proper dependencies once released
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
sphinx = { git = "https://github.com/nymtech/sphinx", rev="8424f4b0933b4a7f64ae828b2edefc5ba43a9e79" }
[build-dependencies]
built = "0.3.2"
@@ -7,4 +7,4 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
sphinx = { git = "https://github.com/nymtech/sphinx", rev="8424f4b0933b4a7f64ae828b2edefc5ba43a9e79" }
@@ -45,8 +45,8 @@ fn read_be_u16(input: &mut &[u8]) -> u16 {
u16::from_be_bytes(int_bytes.try_into().unwrap())
}
// TODO: currently this allows for maximum 64kB payload - if we go over that in sphinx,
// we need to update this code.
// TODO: currently this allows for maximum 64kB payload -
// if we go over that in sphinx we need to update this code.
impl ProviderResponse for PullResponse {
// num_msgs || len1 || len2 || ... || msg1 || msg2 || ...
fn to_bytes(&self) -> Vec<u8> {
+5 -1
View File
@@ -1,6 +1,7 @@
use crate::provider::ServiceProvider;
use clap::{App, Arg, ArgMatches, SubCommand};
use crypto::identity::MixnetIdentityKeyPair;
use log::error;
use std::net::ToSocketAddrs;
use std::path::PathBuf;
use std::process;
@@ -8,6 +9,9 @@ use std::process;
pub mod provider;
fn main() {
dotenv::dotenv().ok();
pretty_env_logger::init();
let arg_matches = App::new("Nym Service Provider")
.version(built_info::PKG_VERSION)
.author("Nymtech")
@@ -65,7 +69,7 @@ fn main() {
.get_matches();
if let Err(e) = execute(arg_matches) {
println!("{}", e);
error!("{}", e);
process::exit(1);
}
}
@@ -3,6 +3,7 @@ use crate::provider::ClientLedger;
use crypto::identity::{DummyMixIdentityPrivateKey, MixnetIdentityPrivateKey};
use futures::lock::Mutex as FMutex;
use hmac::{Hmac, Mac};
use log::*;
use sfw_provider_requests::requests::{
ProviderRequestError, ProviderRequests, PullRequest, RegisterRequest,
};
@@ -82,7 +83,7 @@ impl ClientRequestProcessor {
processing_data: Arc<ClientProcessingData>,
) -> Result<Vec<u8>, ClientProcessingError> {
let client_request = ProviderRequests::from_bytes(&data)?;
println!("Received the following request: {:?}", client_request);
trace!("Received the following request: {:?}", client_request);
match client_request {
ProviderRequests::Register(req) => Ok(ClientRequestProcessor::register_new_client(
req,
@@ -106,7 +107,6 @@ impl ClientRequestProcessor {
// Wait for https://github.com/nymtech/nym-sfw-provider/issues/19 to resolve.
let unlocked_ledger = processing_data.registered_clients_ledger.lock().await;
println!("Processing pull!");
if unlocked_ledger.has_token(req.auth_token) {
// drop the mutex so that we could do IO without blocking others wanting to get the lock
drop(unlocked_ledger);
@@ -124,7 +124,10 @@ impl ClientRequestProcessor {
req: RegisterRequest,
processing_data: Arc<ClientProcessingData>,
) -> Result<RegisterResponse, ClientProcessingError> {
println!("Processing register new client request!");
debug!(
"Processing register new client request: {:?}",
req.destination_address
);
let mut unlocked_ledger = processing_data.registered_clients_ledger.lock().await;
let auth_token = ClientRequestProcessor::generate_new_auth_token(
@@ -145,14 +148,15 @@ impl ClientRequestProcessor {
client_address: sphinx::route::DestinationAddressBytes,
store_dir: &Path,
) -> io::Result<()> {
let client_dir_name = hex::encode(client_address);
let client_dir_name = bs58::encode(client_address).into_string();
let full_store_dir = store_dir.join(client_dir_name);
std::fs::create_dir_all(full_store_dir)
}
fn generate_new_auth_token(data: Vec<u8>, key: DummyMixIdentityPrivateKey) -> AuthToken {
let mut auth_token_raw =
HmacSha256::new_varkey(&key.to_bytes()).expect("HMAC can take key of any size");
// also note that `new_varkey` doesn't even have an execution branch returning an error
let mut auth_token_raw = HmacSha256::new_varkey(&key.to_bytes())
.expect("HMAC should be able take key of any size");
auth_token_raw.input(&data);
let mut auth_token = [0u8; 32];
auth_token.copy_from_slice(&auth_token_raw.result().code().to_vec());
+18 -6
View File
@@ -1,5 +1,6 @@
use crate::provider::storage::StoreData;
use crypto::identity::DummyMixIdentityPrivateKey;
use log::{error, warn};
use sphinx::{ProcessedPacket, SphinxPacket};
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
@@ -8,11 +9,12 @@ use std::sync::{Arc, RwLock};
// DUPLICATE WITH MIXNODE CODE!!!
#[derive(Debug)]
pub enum MixProcessingError {
SphinxRecoveryError,
ReceivedForwardHopError,
FileIOFailure,
InvalidPayload,
NonMatchingRecipient,
FileIOFailure,
ReceivedForwardHopError,
SphinxRecoveryError,
SphinxProcessingError,
}
impl From<sphinx::ProcessingError> for MixProcessingError {
@@ -60,13 +62,23 @@ impl MixPacketProcessor {
processing_data: &RwLock<MixProcessingData>,
) -> Result<StoreData, MixProcessingError> {
let packet = SphinxPacket::from_bytes(packet_data.to_vec())?;
let read_processing_data = processing_data.read().unwrap();
let read_processing_data = match processing_data.read() {
Ok(guard) => guard,
Err(e) => {
error!("processing data lock was poisoned! - {:?}", e);
std::process::exit(1)
}
};
let (client_address, client_surb_id, payload) =
match packet.process(read_processing_data.secret_key.as_scalar()) {
ProcessedPacket::ProcessedPacketFinalHop(client_address, surb_id, payload) => {
Ok(ProcessedPacket::ProcessedPacketFinalHop(client_address, surb_id, payload)) => {
(client_address, surb_id, payload)
}
_ => return Err(MixProcessingError::ReceivedForwardHopError),
Ok(_) => return Err(MixProcessingError::ReceivedForwardHopError),
Err(e) => {
warn!("Error unwrapping Sphinx packet: {:?}", e);
return Err(MixProcessingError::SphinxProcessingError);
}
};
// TODO: should provider try to be recovering plaintext? this would potentially make client retrieve messages of non-constant length,
+23 -17
View File
@@ -2,9 +2,10 @@ use crate::provider::client_handling::{ClientProcessingData, ClientRequestProces
use crate::provider::mix_handling::{MixPacketProcessor, MixProcessingData};
use crate::provider::storage::ClientStorage;
use crypto::identity::{DummyMixIdentityPrivateKey, DummyMixIdentityPublicKey};
use directory_client::presence::MixProviderClient;
use directory_client::presence::providers::MixProviderClient;
use futures::io::Error;
use futures::lock::Mutex as FMutex;
use log::*;
use sfw_provider_requests::AuthToken;
use sphinx::route::DestinationAddressBytes;
use std::collections::HashMap;
@@ -86,7 +87,7 @@ impl ClientLedger {
fn current_clients(&self) -> Vec<MixProviderClient> {
self.0
.iter()
.map(|(_, v)| base64::encode_config(v, base64::URL_SAFE))
.map(|(_, v)| bs58::encode(v).into_string())
.map(|pub_key| MixProviderClient { pub_key })
.collect()
}
@@ -132,7 +133,7 @@ impl ServiceProvider {
match socket.read(&mut buf).await {
// socket closed
Ok(n) if n == 0 => {
println!("Remote connection closed.");
trace!("Remote connection closed.");
return;
}
Ok(_) => {
@@ -142,28 +143,35 @@ impl ServiceProvider {
) {
Ok(sd) => sd,
Err(e) => {
eprintln!("failed to process sphinx packet; err = {:?}", e);
warn!("failed to process sphinx packet; err = {:?}", e);
return;
}
};
let processing_data_lock = match processing_data.read() {
Ok(guard) => guard,
Err(e) => {
error!("processing data lock was poisoned! - {:?}", e);
std::process::exit(1)
}
};
ClientStorage::store_processed_data(
store_data,
processing_data.read().unwrap().store_dir.as_path(),
processing_data_lock.store_dir.as_path(),
)
.unwrap_or_else(|e| {
eprintln!("failed to store processed sphinx message; err = {:?}", e);
error!("failed to store processed sphinx message; err = {:?}", e);
return;
});
}
Err(e) => {
eprintln!("failed to read from socket; err = {:?}", e);
warn!("failed to read from socket; err = {:?}", e);
return;
}
};
// Write the some data back
if let Err(e) = socket.write_all(b"foomp").await {
eprintln!("failed to write reply to socket; err = {:?}", e);
warn!("failed to write reply to socket; err = {:?}", e);
return;
}
}
@@ -171,10 +179,10 @@ impl ServiceProvider {
async fn send_response(mut socket: tokio::net::TcpStream, data: &[u8]) {
if let Err(e) = socket.write_all(data).await {
eprintln!("failed to write reply to socket; err = {:?}", e)
warn!("failed to write reply to socket; err = {:?}", e)
}
if let Err(e) = socket.shutdown(Shutdown::Write) {
eprintln!("failed to close write part of the socket; err = {:?}", e)
warn!("failed to close write part of the socket; err = {:?}", e)
}
}
@@ -189,7 +197,7 @@ impl ServiceProvider {
let response = match socket.read(&mut buf).await {
// socket closed
Ok(n) if n == 0 => {
println!("Remote connection closed.");
trace!("Remote connection closed.");
Err(())
}
Ok(n) => {
@@ -200,29 +208,27 @@ impl ServiceProvider {
.await
{
Err(e) => {
eprintln!("failed to process client request; err = {:?}", e);
warn!("failed to process client request; err = {:?}", e);
Err(())
}
Ok(res) => Ok(res),
}
}
Err(e) => {
eprintln!("failed to read from socket; err = {:?}", e);
warn!("failed to read from socket; err = {:?}", e);
Err(())
}
};
if let Err(e) = socket.shutdown(Shutdown::Read) {
eprintln!("failed to close read part of the socket; err = {:?}", e)
warn!("failed to close read part of the socket; err = {:?}", e)
}
match response {
Ok(res) => {
println!("should send this response! {:?}", res);
ServiceProvider::send_response(socket, &res).await;
}
_ => {
println!("we failed...");
ServiceProvider::send_response(socket, b"bad foomp").await;
}
}
@@ -308,7 +314,7 @@ impl ServiceProvider {
});
// this line in theory should never be reached as the runtime should be permanently blocked on listeners
eprintln!("The server went kaput...");
error!("The server went kaput...");
Ok(())
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::provider::ClientLedger;
use crypto::identity::DummyMixIdentityPublicKey;
use directory_client::presence::MixProviderPresence;
use directory_client::presence::providers::MixProviderPresence;
use directory_client::requests::presence_providers_post::PresenceMixProviderPoster;
use directory_client::DirectoryClient;
use futures::lock::Mutex as FMutex;
@@ -34,7 +34,7 @@ impl Notifier {
net_client,
client_listener: client_listener.to_string(),
mixnet_listener: mixnet_listener.to_string(),
pub_key: pub_key.to_b64_string(),
pub_key: pub_key.to_base58_string(),
client_ledger,
}
}
+33 -18
View File
@@ -1,4 +1,5 @@
use crate::provider::{MESSAGE_RETRIEVAL_LIMIT, STORED_MESSAGE_FILENAME_LENGTH};
use log::*;
use rand::Rng;
use sfw_provider_requests::DUMMY_MESSAGE_CONTENT;
use sphinx::route::{DestinationAddressBytes, SURBIdentifier};
@@ -59,10 +60,10 @@ impl ClientStorage {
}
pub fn store_processed_data(store_data: StoreData, store_dir: &Path) -> io::Result<()> {
let client_dir_name = hex::encode(store_data.client_address);
let client_dir_name = bs58::encode(store_data.client_address).into_string();
let full_store_dir = store_dir.join(client_dir_name);
let full_store_path = full_store_dir.join(ClientStorage::generate_random_file_name());
println!(
debug!(
"going to store: {:?} in file: {:?}",
store_data.message, full_store_path
);
@@ -80,27 +81,19 @@ impl ClientStorage {
client_address: DestinationAddressBytes,
store_dir: &Path,
) -> Result<Vec<Vec<u8>>, StoreError> {
let client_dir_name = hex::encode(client_address);
let client_dir_name = bs58::encode(client_address).into_string();
let full_store_dir = store_dir.join(client_dir_name);
println!("going to lookup: {:?}!", full_store_dir);
trace!("going to lookup: {:?}!", full_store_dir);
if !full_store_dir.exists() {
return Err(StoreError::ClientDoesntExistError);
}
let msgs: Vec<_> = std::fs::read_dir(full_store_dir)?
.map(|entry| entry.unwrap())
.filter(|entry| {
let is_file = entry.metadata().unwrap().is_file();
if !is_file {
eprintln!(
"potentially corrupted client inbox! - found a non-file - {:?}",
entry.path()
);
}
is_file
})
.filter_map(|entry| entry.ok())
.filter(|entry| ClientStorage::is_valid_file(entry))
.map(|entry| {
// Not yet sure how to exactly get rid of those unwraps
let content = std::fs::read(entry.path()).unwrap();
ClientStorage::delete_file(entry.path()).unwrap();
content
@@ -109,16 +102,38 @@ impl ClientStorage {
.take(MESSAGE_RETRIEVAL_LIMIT)
.collect();
println!("retrieved the following data: {:?}", msgs);
Ok(msgs)
}
fn is_valid_file(entry: &std::fs::DirEntry) -> bool {
let metadata = match entry.metadata() {
Ok(meta) => meta,
Err(e) => {
error!(
"potentially corrupted client inbox! ({:?} - failed to read its metadata - {:?}",
entry.path(),
e,
);
return false;
}
};
let is_file = metadata.is_file();
if !is_file {
error!(
"potentially corrupted client inbox! - found a non-file - {:?}",
entry.path()
);
}
is_file
}
// TODO: THIS NEEDS A LOCKING MECHANISM!!! (or a db layer on top - basically 'ClientStorage' on steroids)
// TODO 2: This should only be called AFTER we sent the reply. Because if client's connection failed after sending request
// the messages would be deleted but he wouldn't have received them
fn delete_file(path: PathBuf) -> io::Result<()> {
println!("Here {:?} will be deleted!", path);
trace!("Here {:?} will be deleted!", path);
std::fs::remove_file(path) // another argument for db layer -> remove_file is NOT guaranteed to immediately get rid of the file
}
}
-5
View File
@@ -1,5 +0,0 @@
# nym-validator Changelog
## 0.1.0 - Initial Release
* Initial take on the health checker that is going to be run by the Nym Validator
+4 -3
View File
@@ -1,7 +1,7 @@
[package]
build = "build.rs"
name = "nym-validator"
version = "0.1.0"
version = "0.4.0-rc.1"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
edition = "2018"
@@ -12,14 +12,15 @@ clap = "2.33.0"
# Read notes https://crates.io/crates/dotenv - tl;dr: don't use in production, set environmental variables properly.
dotenv = "0.15.0"
futures = "0.3.1"
log = "0.4.8"
pretty_env_logger = "0.3.1"
log = "0.4"
pretty_env_logger = "0.3"
serde = "1.0.104"
serde_derive = "1.0.104"
tokio = { version = "0.2", features = ["full"] }
toml = "0.5.5"
## internal
crypto = {path = "../common/crypto"}
healthcheck = {path = "../common/healthcheck" }
[build-dependencies]
+1 -9
View File
@@ -1,7 +1,6 @@
use crate::validator::config::Config;
use crate::validator::Validator;
use clap::{App, Arg, ArgMatches, SubCommand};
use dotenv;
use log::{error, trace};
use std::process;
use toml;
@@ -9,14 +8,7 @@ use toml;
mod validator;
fn main() {
// load environment variables from .env file
if dotenv::dotenv().is_err() {
eprint!("failed to read .env file - the logging is unlikely to work correctly")
}
// if we want to log to file or use different logger, we'd need to replace it here.
// a better alternative, but way more complex would be `slog` crate - we should
// perhaps research it at some point.
dotenv::dotenv().ok();
pretty_env_logger::init();
let arg_matches = App::new("Nym Validator")
+20 -4
View File
@@ -1,20 +1,36 @@
use crate::validator::config::Config;
use crypto::identity::{
DummyMixIdentityKeyPair, DummyMixIdentityPrivateKey, DummyMixIdentityPublicKey,
MixnetIdentityKeyPair, MixnetIdentityPrivateKey, MixnetIdentityPublicKey,
};
use healthcheck::HealthChecker;
use log::debug;
use tokio::runtime::Runtime;
pub mod config;
pub struct Validator {
heath_check: HealthChecker,
// allow for a generic validator
pub struct Validator<IDPair, Priv, Pub>
where
IDPair: MixnetIdentityKeyPair<Priv, Pub>,
Priv: MixnetIdentityPrivateKey,
Pub: MixnetIdentityPublicKey,
{
#[allow(dead_code)]
identity_keypair: IDPair,
heath_check: HealthChecker<IDPair, Priv, Pub>,
}
impl Validator {
// but for time being, since it's a dummy one, have it use dummy keys
impl Validator<DummyMixIdentityKeyPair, DummyMixIdentityPrivateKey, DummyMixIdentityPublicKey> {
pub fn new(config: Config) -> Self {
debug!("validator new");
let dummy_keypair = DummyMixIdentityKeyPair::new();
Validator {
heath_check: HealthChecker::new(config.health_check),
identity_keypair: dummy_keypair.clone(),
heath_check: HealthChecker::new(config.health_check, dummy_keypair),
}
}