Compare commits

...

30 Commits

Author SHA1 Message Date
Jędrzej Stuczyński dce0d161ea replace logger 2024-10-15 17:11:21 +01:00
Jędrzej Stuczyński 853a62bc5b added debug symbols to release builds 2024-10-14 18:53:09 +01:00
Jędrzej Stuczyński 1927614803 added temporary and experimental memory profiling 2024-10-11 14:34:28 +01:00
Jędrzej Stuczyński 75a5192c6d Merge pull request #4958 from nymtech/bugfix/websocket-message-handling
bugfix: replace unreachable macro with an error return
2024-10-09 17:24:17 +01:00
Jędrzej Stuczyński 25ad0920cf bugfix: replace unreachable macro with an error return 2024-10-09 17:15:41 +01:00
Bogdan-Ștefan Neacşu a4c6f51fe0 Don't kill gateway on handle drop (#4934) 2024-09-27 11:02:39 +02:00
benedetta davico f76300669a Merge pull request #4931 from nymtech/feature/wedel-merge-conflicts
Wedel release to develop
2024-09-26 13:46:24 +02:00
Jędrzej Stuczyński 333ace1f97 Merge branch 'release/2024.11-wedel' into feature/wedel-merge-conflicts 2024-09-26 08:56:11 +01:00
Dinko Zdravac 487bf6732e Assume offline mode in sqlx (#4926)
* Assume offline mode

* PR feedback
2024-09-25 13:28:36 +02:00
Jędrzej Stuczyński 5d4a0fef55 Merge pull request #4871 from nymtech/chore/remove-another-mixnet-migration
chore: remove queued migration for adding explicit admin
2024-09-25 09:37:49 +01:00
Jon Häggblad 1627146c0e Make ip-packet-request VERSION pub (#4925) 2024-09-25 09:56:32 +02:00
Dinko Zdravac ae40a00b8f Data Observatory stub (#4905)
* Data Observatory stub

* Fix sqlx in CI

* Add troubleshooting tips for sqlx

* Update CI paths to trigger for this package

* Add this to CI upload binary build
2024-09-24 16:48:15 +02:00
Jon Häggblad 7f3c0470e0 Fix argument to cargo-deny action (#4922) 2024-09-24 13:17:35 +02:00
Bogdan-Ștefan Neacşu 1bc26ed79f Expose error type (#4924) 2024-09-24 11:48:54 +02:00
mx 60fa5cfeb8 Max/rust sdk stream abstraction (#4743)
* add TcpProxyClient and TcpProxyServer abstractions to SDK 
* add single connection example
* add multi-connection example 
* add simple echo server to `tools/`: used for multi-connection example 
* update FFI toml files: switched to local imports 
* add proxy bindings to `ffi/shared`
* add proxy bindings and example to `ffi/go` 
* add note to `ffi/cpp` about lack of Proxy bindings for the moment
2024-09-24 09:29:46 +00:00
Jon Häggblad 3b7088aeea Fix nymvpn.com url in mainnet defaults (#4920) 2024-09-24 10:25:27 +02:00
benedettadavico 803850be74 bump versions & update changelog 2024-09-23 10:00:20 +02:00
Jędrzej Stuczyński 3dc62a9a60 Merge pull request #4892 from nymtech/bugfix/ticketbook-false-double-spending
Bugfix/ticketbook false double spending
2024-09-19 09:44:43 +01:00
Jędrzej Stuczyński 5753b79997 slightly refactored bandwidth tracking 2024-09-18 11:27:35 +01:00
Jędrzej Stuczyński 2a6aa13ecd fixed client bandwidth being not correctly deducted 2024-09-18 11:12:24 +01:00
benedetta davico d5c9e1d8cb Merge pull request #4899 from nymtech/jon/cherry-pick-4894-into-wedel
Backport #4894 to fix ci
2024-09-18 09:28:15 +02:00
Jon Häggblad 87751894d9 Fix apt install in ci-build-upload-binaries.yml (#4894) 2024-09-18 09:07:45 +02:00
Jędrzej Stuczyński c8c3928575 put client bandwidth (gateway-side) behind shared pointer 2024-09-17 18:40:24 +01:00
Jędrzej Stuczyński 2fa8da8117 making sure there can be only a single client task claiming more bandwidth 2024-09-17 18:39:49 +01:00
Jędrzej Stuczyński 4548ef4d05 adding extra logs 2024-09-17 18:39:01 +01:00
Jędrzej Stuczyński 7f147ee2b0 Merge pull request #4891 from nymtech/bugfix/ticketbook-aux-imports
fix: allow updating globally stored signatures
2024-09-17 15:33:34 +01:00
Jędrzej Stuczyński 48bcd7e802 fix: allow updating globally stored signatures 2024-09-17 14:21:42 +01:00
benedettadavico aad028be3f update qa env 2024-09-13 11:48:49 +02:00
Bogdan-Ștefan Neacşu 6db3b34bcb Bump defguard to github latest version (#4872)
* Bump defguard to github latest version

* Fix comment location
2024-09-12 13:49:33 +02:00
Jędrzej Stuczyński f9383578da chore: remove queued migration for adding explicit admin 2024-09-12 11:03:51 +01:00
92 changed files with 4402 additions and 407 deletions
@@ -26,6 +26,7 @@ on:
- "nym-api/**"
- "nym-node/**"
- "nym-outfox/**"
- 'nym-data-observatory/**'
- "nym-validator-rewarder/**"
- "sdk/rust/nym-sdk/**"
- "service-providers/**"
@@ -96,6 +97,7 @@ jobs:
target/release/nym-socks5-client
target/release/nym-api
target/release/nym-network-requester
target/release/nym-data-observatory
target/release/nym-cli
target/release/nymvisor
target/release/nym-node
@@ -113,6 +115,7 @@ jobs:
cp target/release/nym-socks5-client $OUTPUT_DIR
cp target/release/nym-api $OUTPUT_DIR
cp target/release/nym-network-requester $OUTPUT_DIR
cp target/release/nym-data-observatory $OUTPUT_DIR
cp target/release/nymvisor $OUTPUT_DIR
cp target/release/nym-node $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
+1
View File
@@ -16,6 +16,7 @@ on:
- 'nym-api/**'
- 'nym-node/**'
- 'nym-outfox/**'
- 'nym-data-observatory/**'
- 'nym-validator-rewarder/**'
- 'tools/**'
- 'wasm/**'
+1 -1
View File
@@ -22,4 +22,4 @@ jobs:
with:
log-level: warn
command: check ${{ matrix.checks }}
argument: --all-features
arguments: --all-features
+76
View File
@@ -4,6 +4,82 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2024.11-wedel] (2024-09-23)
- Backport #4894 to fix ci ([#4899])
- Bugfix/ticketbook false double spending ([#4892])
- fix: allow updating globally stored signatures ([#4891])
- [DOCs/operators]: Document changelog for patch/2024.10-caramello ([#4886])
- [DOCs/operators]: Post release docs updates ([#4874])
- Bump defguard to github latest version ([#4872])
- chore: removed completed queued mixnet migration ([#4865])
- Disable push trigger and add missing paths in ci-build ([#4864])
- Fix linux conditional in ci-build.yml ([#4863])
- Remove golang workaround in ci-sdk-wasm ([#4858])
- Revert runner for ci-docs ([#4855])
- Move credential verification into common crate ([#4853])
- Fix test failure in ipr request size ([#4844])
- Start switching over jobs to arc-ubuntu-20.04 ([#4843])
- Use ecash credential type for bandwidth value ([#4840])
- Create nym-repo-setup debian package and nym-vpn meta package ([#4837])
- Remove serde_crate named import ([#4832])
- Run cargo autoinherit following last weeks dependabot updates ([#4831])
- revamped ticketbook serialisation and exposed additional cli methods ([#4827])
- Expose wireguard details on self described endpoint ([#4825])
- Remove unused wireguard flag from SDK ([#4823])
- Add `axum` server to `nym-api` ([#4803])
- Run cargo-autoinherit for a few new crates ([#4801])
- Update dependabot ([#4796])
- Fix clippy for unwrap_or_default ([#4783])
- Enable dependabot version upgrades for root rust workspace ([#4778])
- Persist used wireguard private IPs ([#4771])
- Avoid race on ip and registration structures ([#4766])
- docs/hotfix ([#4765])
- chore: remove repetitive words ([#4763])
- Make gateway latency check generic ([#4759])
- Remove duplicate stat count for retransmissions ([#4756])
- Update peer refresh value ([#4754])
- Remove deprecated mark_as_success and use new disarm ([#4751])
- Add get_mixnodes_described to validator_client ([#4725])
- New Network Monitor ([#4610])
[#4899]: https://github.com/nymtech/nym/pull/4899
[#4892]: https://github.com/nymtech/nym/pull/4892
[#4891]: https://github.com/nymtech/nym/pull/4891
[#4886]: https://github.com/nymtech/nym/pull/4886
[#4874]: https://github.com/nymtech/nym/pull/4874
[#4872]: https://github.com/nymtech/nym/pull/4872
[#4865]: https://github.com/nymtech/nym/pull/4865
[#4864]: https://github.com/nymtech/nym/pull/4864
[#4863]: https://github.com/nymtech/nym/pull/4863
[#4858]: https://github.com/nymtech/nym/pull/4858
[#4855]: https://github.com/nymtech/nym/pull/4855
[#4853]: https://github.com/nymtech/nym/pull/4853
[#4844]: https://github.com/nymtech/nym/pull/4844
[#4843]: https://github.com/nymtech/nym/pull/4843
[#4840]: https://github.com/nymtech/nym/pull/4840
[#4837]: https://github.com/nymtech/nym/pull/4837
[#4832]: https://github.com/nymtech/nym/pull/4832
[#4831]: https://github.com/nymtech/nym/pull/4831
[#4827]: https://github.com/nymtech/nym/pull/4827
[#4825]: https://github.com/nymtech/nym/pull/4825
[#4823]: https://github.com/nymtech/nym/pull/4823
[#4803]: https://github.com/nymtech/nym/pull/4803
[#4801]: https://github.com/nymtech/nym/pull/4801
[#4796]: https://github.com/nymtech/nym/pull/4796
[#4783]: https://github.com/nymtech/nym/pull/4783
[#4778]: https://github.com/nymtech/nym/pull/4778
[#4771]: https://github.com/nymtech/nym/pull/4771
[#4766]: https://github.com/nymtech/nym/pull/4766
[#4765]: https://github.com/nymtech/nym/pull/4765
[#4763]: https://github.com/nymtech/nym/pull/4763
[#4759]: https://github.com/nymtech/nym/pull/4759
[#4756]: https://github.com/nymtech/nym/pull/4756
[#4754]: https://github.com/nymtech/nym/pull/4754
[#4751]: https://github.com/nymtech/nym/pull/4751
[#4725]: https://github.com/nymtech/nym/pull/4725
[#4610]: https://github.com/nymtech/nym/pull/4610
## [2024.10-caramello] (2024-09-10)
- Backport 4844 and 4845 ([#4857])
Generated
+509 -38
View File
@@ -265,6 +265,47 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "askama"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
dependencies = [
"askama_derive",
"askama_escape",
]
[[package]]
name = "askama_derive"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83"
dependencies = [
"askama_parser",
"basic-toml",
"mime",
"mime_guess",
"proc-macro2",
"quote",
"serde",
"syn 2.0.66",
]
[[package]]
name = "askama_escape"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]]
name = "askama_parser"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0"
dependencies = [
"nom",
]
[[package]]
name = "async-channel"
version = "1.9.0"
@@ -535,6 +576,15 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "basic-toml"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8"
dependencies = [
"serde",
]
[[package]]
name = "binascii"
version = "0.1.4"
@@ -686,7 +736,7 @@ checksum = "bc0bdbcf2078e0ba8a74e1fe0cf36f54054a04485759b61dfd60b174658e9607"
dependencies = [
"bit-vec",
"getrandom",
"siphasher",
"siphasher 1.0.1",
]
[[package]]
@@ -782,6 +832,20 @@ dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a"
dependencies = [
"camino",
"cargo-platform",
"semver 1.0.23",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "cargo_metadata"
version = "0.18.1"
@@ -1848,11 +1912,10 @@ checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
[[package]]
name = "defguard_wireguard_rs"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba16f17698d4b389907310af018b0c3a80b025bba9c38d947cbc6dd70921743"
version = "0.4.7"
source = "git+https://github.com/DefGuard/wireguard-rs.git?rev=v0.4.7#ef1cf3714629bf5016fb38cbb7320451dc69fb09"
dependencies = [
"base64 0.21.7",
"base64 0.22.1",
"libc",
"log",
"netlink-packet-core",
@@ -1861,7 +1924,7 @@ dependencies = [
"netlink-packet-utils",
"netlink-packet-wireguard",
"netlink-sys",
"nix 0.27.1",
"nix 0.29.0",
"serde",
"thiserror",
]
@@ -1983,13 +2046,33 @@ dependencies = [
"subtle 2.5.0",
]
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys 0.3.7",
]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
"dirs-sys 0.4.1",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
@@ -2060,6 +2143,26 @@ dependencies = [
"spki",
]
[[package]]
name = "echo-server"
version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"bytecodec",
"bytes",
"dashmap",
"dirs 5.0.1",
"nym-sdk",
"serde",
"tokio",
"tokio-stream",
"tokio-util",
"tracing",
"tracing-subscriber",
"uuid",
]
[[package]]
name = "ed25519"
version = "2.2.3"
@@ -2119,6 +2222,9 @@ name = "either"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
dependencies = [
"serde",
]
[[package]]
name = "elliptic-curve"
@@ -2211,7 +2317,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "explorer-api"
version = "1.1.39"
version = "1.1.40"
dependencies = [
"chrono",
"clap 4.5.17",
@@ -2357,7 +2463,7 @@ dependencies = [
"atomic 0.6.0",
"pear",
"serde",
"toml",
"toml 0.8.14",
"uncased",
"version_check",
]
@@ -2433,6 +2539,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
[[package]]
name = "fs-err"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41"
dependencies = [
"autocfg",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
@@ -2709,6 +2824,17 @@ dependencies = [
"web-sys",
]
[[package]]
name = "goblin"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6b4de4a8eb6c46a8c77e1d3be942cb9a8bf073c22374578e5ba4b08ed0ff68"
dependencies = [
"log",
"plain",
"scroll",
]
[[package]]
name = "group"
version = "0.13.0"
@@ -3717,6 +3843,16 @@ dependencies = [
"serde",
]
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest 0.10.7",
]
[[package]]
name = "memchr"
version = "2.7.2"
@@ -3876,14 +4012,15 @@ dependencies = [
[[package]]
name = "netlink-packet-route"
version = "0.17.1"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66"
checksum = "55e5bda7ca0f9ac5e75b5debac3b75e29a8ac8e2171106a2c3bb466389a8dd83"
dependencies = [
"anyhow",
"bitflags 1.3.2",
"bitflags 2.5.0",
"byteorder",
"libc",
"log",
"netlink-packet-core",
"netlink-packet-utils",
]
@@ -3943,7 +4080,6 @@ dependencies = [
"bitflags 2.5.0",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
@@ -3956,6 +4092,7 @@ dependencies = [
"cfg-if",
"cfg_aliases",
"libc",
"memoffset",
]
[[package]]
@@ -4086,7 +4223,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "nym-api"
version = "1.1.43"
version = "1.1.44"
dependencies = [
"anyhow",
"async-trait",
@@ -4104,7 +4241,7 @@ dependencies = [
"cw2",
"cw3",
"cw4",
"dirs",
"dirs 5.0.1",
"futures",
"getset",
"humantime-serde",
@@ -4157,6 +4294,9 @@ dependencies = [
"tap",
"tempfile",
"thiserror",
"tikv-jemalloc-ctl",
"tikv-jemalloc-sys",
"tikv-jemallocator",
"time",
"tokio",
"tokio-stream",
@@ -4332,7 +4472,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.41"
version = "1.1.42"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -4406,18 +4546,18 @@ dependencies = [
"thiserror",
"time",
"tokio",
"toml",
"toml 0.8.14",
"url",
"zeroize",
]
[[package]]
name = "nym-client"
version = "1.1.40"
version = "1.1.41"
dependencies = [
"bs58",
"clap 4.5.17",
"dirs",
"dirs 5.0.1",
"futures",
"log",
"nym-bandwidth-controller",
@@ -4664,12 +4804,12 @@ dependencies = [
name = "nym-config"
version = "0.1.0"
dependencies = [
"dirs",
"dirs 5.0.1",
"handlebars",
"log",
"nym-network-defaults",
"serde",
"toml",
"toml 0.8.14",
"url",
]
@@ -4696,6 +4836,20 @@ dependencies = [
"tracing",
]
[[package]]
name = "nym-cpp-ffi"
version = "0.1.2"
dependencies = [
"anyhow",
"bs58",
"lazy_static",
"nym-bin-common",
"nym-ffi-shared",
"nym-sdk",
"nym-sphinx-anonymous-replies",
"tokio",
]
[[package]]
name = "nym-credential-storage"
version = "0.1.0"
@@ -4822,6 +4976,30 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nym-data-observatory"
version = "0.1.0"
dependencies = [
"anyhow",
"axum 0.7.5",
"chrono",
"nym-bin-common",
"nym-network-defaults",
"nym-node-requests",
"nym-task",
"serde",
"serde_json",
"sqlx",
"tokio",
"tokio-util",
"tower-http",
"tracing",
"tracing-subscriber",
"utoipa",
"utoipa-swagger-ui",
"utoipauto",
]
[[package]]
name = "nym-dkg"
version = "0.1.0"
@@ -4921,6 +5099,21 @@ dependencies = [
"url",
]
[[package]]
name = "nym-ffi-shared"
version = "0.2.0"
dependencies = [
"anyhow",
"bs58",
"lazy_static",
"nym-bin-common",
"nym-sdk",
"nym-sphinx-anonymous-replies",
"tokio",
"uniffi",
"uniffi_build",
]
[[package]]
name = "nym-gateway"
version = "1.1.36"
@@ -4933,7 +5126,7 @@ dependencies = [
"colored",
"dashmap",
"defguard_wireguard_rs",
"dirs",
"dirs 5.0.1",
"dotenvy",
"futures",
"humantime-serde",
@@ -5057,6 +5250,22 @@ dependencies = [
"tracing",
]
[[package]]
name = "nym-go-ffi"
version = "0.2.0"
dependencies = [
"anyhow",
"lazy_static",
"nym-bin-common",
"nym-ffi-shared",
"nym-sdk",
"nym-sphinx-anonymous-replies",
"thiserror",
"tokio",
"uniffi",
"uniffi_build",
]
[[package]]
name = "nym-group-contract-common"
version = "0.1.0"
@@ -5259,7 +5468,7 @@ dependencies = [
"clap 4.5.17",
"colored",
"cupid",
"dirs",
"dirs 5.0.1",
"futures",
"humantime-serde",
"lazy_static",
@@ -5290,7 +5499,7 @@ dependencies = [
"time",
"tokio",
"tokio-util",
"toml",
"toml 0.8.14",
"url",
]
@@ -5383,14 +5592,14 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.41"
version = "1.1.42"
dependencies = [
"addr",
"anyhow",
"async-trait",
"bs58",
"clap 4.5.17",
"dirs",
"dirs 5.0.1",
"futures",
"humantime-serde",
"ipnetwork 0.20.0",
@@ -5434,12 +5643,12 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.1.7"
version = "1.1.8"
dependencies = [
"anyhow",
"bip39",
"bs58",
"cargo_metadata",
"cargo_metadata 0.18.1",
"celes",
"clap 4.5.17",
"colored",
@@ -5470,7 +5679,7 @@ dependencies = [
"sysinfo",
"thiserror",
"tokio",
"toml",
"toml 0.8.14",
"tracing",
"url",
"zeroize",
@@ -5639,9 +5848,12 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"bincode",
"bip39",
"bytecodec",
"bytes",
"dashmap",
"dirs 5.0.1",
"dotenvy",
"futures",
"hex",
@@ -5670,13 +5882,17 @@ dependencies = [
"pretty_env_logger",
"rand",
"reqwest 0.12.4",
"serde",
"tap",
"thiserror",
"tokio",
"tokio-stream",
"tokio-util",
"toml",
"toml 0.8.14",
"tracing",
"tracing-subscriber",
"url",
"uuid",
"zeroize",
]
@@ -5716,7 +5932,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.40"
version = "1.1.41"
dependencies = [
"bs58",
"clap 4.5.17",
@@ -5752,7 +5968,7 @@ name = "nym-socks5-client-core"
version = "0.1.0"
dependencies = [
"anyhow",
"dirs",
"dirs 5.0.1",
"futures",
"log",
"nym-bandwidth-controller",
@@ -6244,7 +6460,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.6"
version = "0.1.7"
dependencies = [
"anyhow",
"bytes",
@@ -6323,6 +6539,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "oneshot-uniffi"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c548d5c78976f6955d72d0ced18c48ca07030f7a1d4024529fedd7c1c01b29c"
[[package]]
name = "oorandom"
version = "11.1.3"
@@ -6726,6 +6948,12 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "plain"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "plotters"
version = "0.3.6"
@@ -7772,6 +8000,26 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "scroll"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da"
dependencies = [
"scroll_derive",
]
[[package]]
name = "scroll_derive"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
]
[[package]]
name = "sct"
version = "0.7.1"
@@ -8145,6 +8393,12 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "siphasher"
version = "1.0.1"
@@ -8287,12 +8541,14 @@ checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029"
dependencies = [
"ahash 0.7.8",
"atoi",
"base64 0.13.1",
"bitflags 1.3.2",
"byteorder",
"bytes",
"chrono",
"crc",
"crossbeam-queue",
"dirs 4.0.0",
"dotenvy",
"either",
"event-listener",
@@ -8304,17 +8560,24 @@ dependencies = [
"futures-util",
"hashlink",
"hex",
"hkdf",
"hmac",
"indexmap 1.9.3",
"itoa",
"libc",
"libsqlite3-sys",
"log",
"md-5",
"memchr",
"once_cell",
"paste",
"percent-encoding",
"rand",
"rustls 0.20.9",
"rustls-pemfile 1.0.4",
"serde",
"serde_json",
"sha1",
"sha2 0.10.8",
"smallvec",
"sqlformat",
@@ -8325,6 +8588,7 @@ dependencies = [
"tokio-stream",
"url",
"webpki-roots 0.22.6",
"whoami",
]
[[package]]
@@ -8336,9 +8600,12 @@ dependencies = [
"dotenvy",
"either",
"heck 0.4.1",
"hex",
"once_cell",
"proc-macro2",
"quote",
"serde",
"serde_json",
"sha2 0.10.8",
"sqlx-core",
"sqlx-rt",
@@ -8419,6 +8686,12 @@ dependencies = [
"loom",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stringprep"
version = "0.1.5"
@@ -8684,7 +8957,7 @@ dependencies = [
"serde",
"serde_json",
"tendermint 0.37.0",
"toml",
"toml 0.8.14",
"url",
]
@@ -8798,7 +9071,7 @@ dependencies = [
"thiserror",
"time",
"tokio",
"toml",
"toml 0.8.14",
"tracing",
"url",
"zeroize",
@@ -8862,6 +9135,37 @@ dependencies = [
"threadpool",
]
[[package]]
name = "tikv-jemalloc-ctl"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f21f216790c8df74ce3ab25b534e0718da5a1916719771d3fec23315c99e468b"
dependencies = [
"libc",
"paste",
"tikv-jemalloc-sys",
]
[[package]]
name = "tikv-jemalloc-sys"
version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "tikv-jemallocator"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865"
dependencies = [
"libc",
"tikv-jemalloc-sys",
]
[[package]]
name = "time"
version = "0.3.36"
@@ -9073,6 +9377,15 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "toml"
version = "0.8.14"
@@ -9554,6 +9867,138 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "uniffi"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21345172d31092fd48c47fd56c53d4ae9e41c4b1f559fb8c38c1ab1685fd919f"
dependencies = [
"anyhow",
"camino",
"clap 4.5.17",
"uniffi_bindgen",
"uniffi_build",
"uniffi_core",
"uniffi_macros",
]
[[package]]
name = "uniffi_bindgen"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd992f2929a053829d5875af1eff2ee3d7a7001cb3b9a46cc7895f2caede6940"
dependencies = [
"anyhow",
"askama",
"camino",
"cargo_metadata 0.15.4",
"clap 4.5.17",
"fs-err",
"glob",
"goblin",
"heck 0.4.1",
"once_cell",
"paste",
"serde",
"toml 0.5.11",
"uniffi_meta",
"uniffi_testing",
"uniffi_udl",
]
[[package]]
name = "uniffi_build"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "001964dd3682d600084b3aaf75acf9c3426699bc27b65e96bb32d175a31c74e9"
dependencies = [
"anyhow",
"camino",
"uniffi_bindgen",
]
[[package]]
name = "uniffi_checksum_derive"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55137c122f712d9330fd985d66fa61bdc381752e89c35708c13ce63049a3002c"
dependencies = [
"quote",
"syn 2.0.66",
]
[[package]]
name = "uniffi_core"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6121a127a3af1665cd90d12dd2b3683c2643c5103281d0fed5838324ca1fad5b"
dependencies = [
"anyhow",
"bytes",
"camino",
"log",
"once_cell",
"oneshot-uniffi",
"paste",
"static_assertions",
]
[[package]]
name = "uniffi_macros"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11cf7a58f101fcedafa5b77ea037999b88748607f0ef3a33eaa0efc5392e92e4"
dependencies = [
"bincode",
"camino",
"fs-err",
"once_cell",
"proc-macro2",
"quote",
"serde",
"syn 2.0.66",
"toml 0.5.11",
"uniffi_build",
"uniffi_meta",
]
[[package]]
name = "uniffi_meta"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71dc8573a7b1ac4b71643d6da34888273ebfc03440c525121f1b3634ad3417a2"
dependencies = [
"anyhow",
"bytes",
"siphasher 0.3.11",
"uniffi_checksum_derive",
]
[[package]]
name = "uniffi_testing"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "118448debffcb676ddbe8c5305fb933ab7e0123753e659a71dc4a693f8d9f23c"
dependencies = [
"anyhow",
"camino",
"cargo_metadata 0.15.4",
"fs-err",
"once_cell",
]
[[package]]
name = "uniffi_udl"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "889edb7109c6078abe0e53e9b4070cf74a6b3468d141bdf5ef1bd4d1dc24a1c3"
dependencies = [
"anyhow",
"uniffi_meta",
"uniffi_testing",
"weedle2",
]
[[package]]
name = "uninit"
version = "0.5.1"
@@ -9704,9 +10149,9 @@ dependencies = [
[[package]]
name = "uuid"
version = "1.8.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
dependencies = [
"getrandom",
"serde",
@@ -9732,7 +10177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525"
dependencies = [
"anyhow",
"cargo_metadata",
"cargo_metadata 0.18.1",
"cfg-if",
"regex",
"rustc_version 0.4.0",
@@ -9777,6 +10222,12 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.93"
@@ -10014,6 +10465,26 @@ dependencies = [
"rustls-pki-types",
]
[[package]]
name = "weedle2"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e79c5206e1f43a2306fd64bdb95025ee4228960f2e6c5a8b173f3caaf807741"
dependencies = [
"nom",
]
[[package]]
name = "whoami"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d"
dependencies = [
"redox_syscall 0.5.1",
"wasite",
"web-sys",
]
[[package]]
name = "winapi"
version = "0.3.9"
+12 -2
View File
@@ -5,6 +5,7 @@
panic = "abort"
opt-level = "s"
overflow-checks = true
debug = true
[profile.dev]
panic = "abort"
@@ -103,6 +104,9 @@ members = [
"mixnode",
"sdk/lib/socks5-listener",
"sdk/rust/nym-sdk",
"sdk/ffi/shared",
"sdk/ffi/go",
"sdk/ffi/cpp",
"service-providers/authenticator",
"service-providers/common",
"service-providers/ip-packet-router",
@@ -111,11 +115,13 @@ members = [
"nym-api",
"nym-browser-extension/storage",
"nym-api/nym-api-requests",
"nym-data-observatory",
"nym-node",
"nym-node/nym-node-http-api",
"nym-node/nym-node-requests",
"nym-outfox",
"nym-validator-rewarder",
"tools/echo-server",
"tools/internal/ssl-inject",
# "tools/internal/sdk-version-bump",
"tools/internal/testnet-manager",
@@ -130,6 +136,9 @@ members = [
"wasm/mix-fetch",
"wasm/node-tester",
"wasm/zknym-lib",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/echo-server",
]
default-members = [
@@ -139,6 +148,7 @@ default-members = [
"gateway",
"mixnode",
"nym-api",
"nym-data-observatory",
"nym-node",
"nym-validator-rewarder",
"service-providers/authenticator",
@@ -153,7 +163,6 @@ exclude = [
"nym-wallet",
"nym-vpn/ui/src-tauri",
"cpu-cycles",
"sdk/ffi/cpp",
]
[workspace.package]
@@ -213,7 +222,8 @@ ctr = "0.9.1"
cupid = "0.6.1"
curve25519-dalek = "4.1"
dashmap = "5.5.3"
defguard_wireguard_rs = "0.4.2"
# We want https://github.com/DefGuard/wireguard-rs/pull/64 , but there's no crates.io release being pushed out anymore
defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.7" }
digest = "0.10.7"
dirs = "5.0"
doc-comment = "0.3"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.40"
version = "1.1.41"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.40"
version = "1.1.41"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+1
View File
@@ -6,6 +6,7 @@ pub mod v2;
mod error;
pub use error::Error;
pub use v2 as latest;
pub const CURRENT_VERSION: u8 = 2;
@@ -26,7 +26,7 @@ pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
pub const BANDWIDTH_CAP_PER_DAY: i64 = 1024 * 1024 * 1024; // 1 GB
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InitMessage {
@@ -66,7 +66,7 @@ pub struct RegistredData {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RemainingBandwidthData {
pub available_bandwidth: u64,
pub available_bandwidth: i64,
}
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
@@ -2,21 +2,37 @@
// SPDX-License-Identifier: Apache-2.0
use si_scale::helpers::bibytes2;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use time::OffsetDateTime;
#[derive(Clone, Default)]
pub(crate) struct BandwidthClaimGuard {
inner: Arc<ClientBandwidthInner>,
}
impl Drop for BandwidthClaimGuard {
fn drop(&mut self) {
let old = self.inner.claiming_more.swap(false, Ordering::SeqCst);
assert!(
old,
"critical failure: there were multiple BandwidthClaimGuard existing"
)
}
}
#[derive(Clone)]
pub struct ClientBandwidth {
inner: Arc<ClientBandwidthInner>,
}
#[derive(Default)]
struct ClientBandwidthInner {
/// the actual bandwidth amount (in bytes) available
available: AtomicI64,
/// flag to indicate whether this client is currently in the process of claiming additional bandwidth
claiming_more: AtomicBool,
/// defines the timestamp when the bandwidth information has been logged to the logs stream
last_logged_ts: AtomicI64,
@@ -29,11 +45,28 @@ impl ClientBandwidth {
ClientBandwidth {
inner: Arc::new(ClientBandwidthInner {
available: AtomicI64::new(0),
claiming_more: AtomicBool::new(false),
last_logged_ts: AtomicI64::new(0),
last_updated_ts: AtomicI64::new(0),
}),
}
}
pub(crate) fn begin_bandwidth_claim(&self) -> Option<BandwidthClaimGuard> {
if self
.inner
.claiming_more
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
Some(BandwidthClaimGuard {
inner: self.inner.clone(),
})
} else {
None
}
}
pub(crate) fn remaining(&self) -> i64 {
self.inner.available.load(Ordering::Acquire)
}
@@ -724,6 +724,11 @@ impl<C, St> GatewayClient<C, St> {
return Err(GatewayClientError::NoBandwidthControllerAvailable);
}
let Some(_claim_guard) = self.bandwidth.begin_bandwidth_claim() else {
debug!("there's already an existing bandwidth claim ongoing");
return Ok(());
};
warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while");
if !self.cfg.bandwidth.require_tickets {
info!("The client is running in disabled credentials mode - attempting to claim bandwidth without a credential");
@@ -171,10 +171,20 @@ impl SqliteEcashTicketbookManager {
data: &[u8],
) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO master_verification_key(epoch_id, serialised_key, serialization_revision) VALUES (?, ?, ?)",
r#"
INSERT OR IGNORE INTO master_verification_key(epoch_id, serialised_key, serialization_revision) VALUES (?, ?, ?);
UPDATE master_verification_key
SET
serialised_key = ?,
serialization_revision = ?
WHERE epoch_id = ?
"#,
epoch_id,
data,
serialisation_revision
serialisation_revision,
data,
serialisation_revision,
epoch_id
)
.execute(&self.connection_pool)
.await?;
@@ -204,10 +214,20 @@ impl SqliteEcashTicketbookManager {
data: &[u8],
) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO coin_indices_signatures(epoch_id, serialised_signatures, serialization_revision) VALUES (?, ?, ?)",
r#"
INSERT OR IGNORE INTO coin_indices_signatures(epoch_id, serialised_signatures, serialization_revision) VALUES (?, ?, ?);
UPDATE coin_indices_signatures
SET
serialised_signatures = ?,
serialization_revision = ?
WHERE epoch_id = ?
"#,
epoch_id,
data,
serialisation_revision
serialisation_revision,
data,
serialisation_revision,
epoch_id,
)
.execute(&self.connection_pool)
.await?;
@@ -240,13 +260,21 @@ impl SqliteEcashTicketbookManager {
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO expiration_date_signatures(expiration_date, epoch_id, serialised_signatures, serialization_revision)
VALUES (?, ?, ?, ?)
INSERT OR IGNORE INTO expiration_date_signatures(expiration_date, epoch_id, serialised_signatures, serialization_revision)
VALUES (?, ?, ?, ?);
UPDATE expiration_date_signatures
SET
serialised_signatures = ?,
serialization_revision = ?
WHERE expiration_date = ?
"#,
expiration_date,
epoch_id,
data,
serialisation_revision
serialisation_revision,
data,
serialisation_revision,
expiration_date
)
.execute(&self.connection_pool)
.await?;
@@ -1,18 +1,17 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::*;
use crate::BandwidthFlushingBehaviourConfig;
use crate::ClientBandwidth;
use nym_credentials::ecash::utils::ecash_today;
use nym_credentials_interface::{AvailableBandwidth, Bandwidth};
use nym_credentials_interface::Bandwidth;
use nym_gateway_requests::ServerResponse;
use nym_gateway_storage::Storage;
use si_scale::helpers::bibytes2;
use time::OffsetDateTime;
use tracing::*;
use crate::error::*;
use crate::BandwidthFlushingBehaviourConfig;
use crate::ClientBandwidth;
const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth = Bandwidth::new_unchecked(64 * 1024 * 1024 * 1024); // 64GB
#[derive(Clone)]
@@ -41,13 +40,13 @@ impl<S: Storage + Clone + 'static> BandwidthStorageManager<S> {
}
}
pub fn available_bandwidth(&self) -> AvailableBandwidth {
self.client_bandwidth.bandwidth
pub async fn available_bandwidth(&self) -> i64 {
self.client_bandwidth.available().await
}
async fn sync_expiration(&mut self) -> Result<()> {
self.storage
.set_expiration(self.client_id, self.client_bandwidth.bandwidth.expiration)
.set_expiration(self.client_id, self.client_bandwidth.expiration().await)
.await?;
Ok(())
}
@@ -61,17 +60,17 @@ impl<S: Storage + Clone + 'static> BandwidthStorageManager<S> {
self.increase_bandwidth(FREE_TESTNET_BANDWIDTH_VALUE, ecash_today())
.await?;
let available_total = self.client_bandwidth.bandwidth.bytes;
let available_total = self.client_bandwidth.available().await;
Ok(ServerResponse::Bandwidth { available_total })
}
#[instrument(skip_all)]
pub async fn try_use_bandwidth(&mut self, required_bandwidth: i64) -> Result<i64> {
if self.client_bandwidth.bandwidth.expired() {
if self.client_bandwidth.expired().await {
self.expire_bandwidth().await?;
}
let available_bandwidth = self.client_bandwidth.bandwidth.bytes;
let available_bandwidth = self.client_bandwidth.available().await;
if available_bandwidth < required_bandwidth {
return Err(Error::OutOfBandwidth {
@@ -90,8 +89,7 @@ impl<S: Storage + Clone + 'static> BandwidthStorageManager<S> {
async fn expire_bandwidth(&mut self) -> Result<()> {
self.storage.reset_bandwidth(self.client_id).await?;
self.client_bandwidth.bandwidth = Default::default();
self.client_bandwidth.update_sync_data();
self.client_bandwidth.expire_bandwidth().await;
Ok(())
}
@@ -101,31 +99,31 @@ impl<S: Storage + Clone + 'static> BandwidthStorageManager<S> {
///
/// * `amount`: amount to decrease the available bandwidth by.
async fn consume_bandwidth(&mut self, amount: i64) -> Result<()> {
self.client_bandwidth.bandwidth.bytes -= amount;
self.client_bandwidth.bytes_delta_since_sync -= amount;
self.client_bandwidth.decrease_bandwidth(amount).await;
// since we're going to be operating on a fair use policy anyway, even if we crash and let extra few packets
// through, that's completely fine
if self.client_bandwidth.should_sync(self.bandwidth_cfg) {
self.sync_bandwidth().await?;
if self.client_bandwidth.should_sync(self.bandwidth_cfg).await {
self.sync_storage_bandwidth().await?;
}
Ok(())
}
#[instrument(level = "trace", skip_all)]
async fn sync_bandwidth(&mut self) -> Result<()> {
async fn sync_storage_bandwidth(&mut self) -> Result<()> {
trace!("syncing client bandwidth with the underlying storage");
let updated = self
.storage
.increase_bandwidth(self.client_id, self.client_bandwidth.bytes_delta_since_sync)
.increase_bandwidth(
self.client_id,
self.client_bandwidth.delta_since_sync().await,
)
.await?;
trace!(updated);
self.client_bandwidth.bandwidth.bytes = updated;
self.client_bandwidth.update_sync_data();
self.client_bandwidth
.resync_bandwidth_with_storage(updated)
.await;
Ok(())
}
@@ -140,13 +138,14 @@ impl<S: Storage + Clone + 'static> BandwidthStorageManager<S> {
bandwidth: Bandwidth,
expiration: OffsetDateTime,
) -> Result<()> {
self.client_bandwidth.bandwidth.bytes += bandwidth.value() as i64;
self.client_bandwidth.bytes_delta_since_sync += bandwidth.value() as i64;
self.client_bandwidth.bandwidth.expiration = expiration;
self.client_bandwidth
.increase_bandwidth(bandwidth.value() as i64, expiration)
.await;
// any increases to bandwidth should get flushed immediately
// (we don't want to accidentally miss somebody claiming a gigabyte voucher)
self.sync_expiration().await?;
self.sync_bandwidth().await
self.sync_storage_bandwidth().await?;
Ok(())
}
}
@@ -1,10 +1,12 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::time::Duration;
use nym_credentials::ecash::utils::ecash_today;
use nym_credentials_interface::AvailableBandwidth;
use std::sync::Arc;
use std::time::Duration;
use time::OffsetDateTime;
use tokio::sync::RwLock;
const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5);
const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB
@@ -28,10 +30,15 @@ impl Default for BandwidthFlushingBehaviourConfig {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct ClientBandwidth {
inner: Arc<RwLock<ClientBandwidthInner>>,
}
#[derive(Debug)]
struct ClientBandwidthInner {
pub(crate) bandwidth: AvailableBandwidth,
pub(crate) last_flushed: OffsetDateTime,
pub(crate) last_synced: OffsetDateTime,
/// the number of bytes the client had during the last sync.
/// it is used to determine whether the current value should be synced with the storage
@@ -43,28 +50,74 @@ pub struct ClientBandwidth {
impl ClientBandwidth {
pub fn new(bandwidth: AvailableBandwidth) -> ClientBandwidth {
ClientBandwidth {
bandwidth,
last_flushed: OffsetDateTime::now_utc(),
bytes_at_last_sync: bandwidth.bytes,
bytes_delta_since_sync: 0,
inner: Arc::new(RwLock::new(ClientBandwidthInner {
bandwidth,
last_synced: OffsetDateTime::now_utc(),
bytes_at_last_sync: bandwidth.bytes,
bytes_delta_since_sync: 0,
})),
}
}
pub(crate) fn should_sync(&self, cfg: BandwidthFlushingBehaviourConfig) -> bool {
if self.bytes_delta_since_sync.abs() >= cfg.client_bandwidth_max_delta_flushing_amount {
pub(crate) async fn should_sync(&self, cfg: BandwidthFlushingBehaviourConfig) -> bool {
let guard = self.inner.read().await;
if guard.bytes_delta_since_sync.abs() >= cfg.client_bandwidth_max_delta_flushing_amount {
return true;
}
if self.last_flushed + cfg.client_bandwidth_max_flushing_rate < OffsetDateTime::now_utc() {
if guard.last_synced + cfg.client_bandwidth_max_flushing_rate < OffsetDateTime::now_utc() {
return true;
}
false
}
pub(crate) fn update_sync_data(&mut self) {
self.last_flushed = OffsetDateTime::now_utc();
self.bytes_at_last_sync = self.bandwidth.bytes;
self.bytes_delta_since_sync = 0;
pub(crate) async fn available(&self) -> i64 {
self.inner.read().await.bandwidth.bytes
}
pub(crate) async fn delta_since_sync(&self) -> i64 {
self.inner.read().await.bytes_delta_since_sync
}
pub(crate) async fn expiration(&self) -> OffsetDateTime {
self.inner.read().await.bandwidth.expiration
}
pub(crate) async fn expired(&self) -> bool {
self.expiration().await < ecash_today()
}
pub(crate) async fn decrease_bandwidth(&self, decrease: i64) {
let mut guard = self.inner.write().await;
guard.bandwidth.bytes -= decrease;
guard.bytes_delta_since_sync -= decrease;
}
pub(crate) async fn increase_bandwidth(&self, increase: i64, new_expiration: OffsetDateTime) {
let mut guard = self.inner.write().await;
guard.bandwidth.bytes += increase;
guard.bandwidth.expiration = new_expiration;
guard.bytes_delta_since_sync += increase;
}
pub(crate) async fn expire_bandwidth(&self) {
let mut guard = self.inner.write().await;
guard.bandwidth = AvailableBandwidth::default();
guard.last_synced = OffsetDateTime::now_utc();
guard.bytes_at_last_sync = 0;
guard.bytes_delta_since_sync = 0;
}
pub(crate) async fn resync_bandwidth_with_storage(&self, stored: i64) {
let mut guard = self.inner.write().await;
guard.bandwidth.bytes = stored;
guard.bytes_at_last_sync = stored;
guard.bytes_delta_since_sync = 0;
guard.last_synced = OffsetDateTime::now_utc();
}
}
+2 -2
View File
@@ -150,7 +150,7 @@ impl<S: Storage + Clone + 'static> CredentialVerifier<S> {
Ok(self
.bandwidth_storage_manager
.client_bandwidth
.bandwidth
.bytes)
.available()
.await)
}
}
+1 -1
View File
@@ -2,4 +2,4 @@ pub mod conversion;
pub mod request;
pub mod response;
const VERSION: u8 = 6;
pub const VERSION: u8 = 6;
+1 -1
View File
@@ -3,4 +3,4 @@ pub mod request;
pub mod response;
pub mod signature;
const VERSION: u8 = 7;
pub const VERSION: u8 = 7;
+1 -1
View File
@@ -32,7 +32,7 @@ pub const NYXD_URL: &str = "https://rpc.nymtech.net";
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
pub const NYXD_WS: &str = "wss://rpc.nymtech.net/websocket";
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
pub const NYM_VPN_API: &str = "https://nymvpn.net/api/";
pub const NYM_VPN_API: &str = "https://nymvpn.com/api/";
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
pub const EXIT_POLICY_URL: &str =
+1
View File
@@ -114,6 +114,7 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
address: wireguard_data.inner.config().private_ip.to_string(),
port: wireguard_data.inner.config().announced_port as u32,
peers,
mtu: None,
};
wg_api.configure_interface(&interface_config)?;
+2 -2
View File
@@ -226,7 +226,7 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
.read()
.await
.available_bandwidth()
.bytes as u64
.await
} else {
let peer = self
.host_information
@@ -236,7 +236,7 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
.get(key)
.ok_or(Error::PeerMismatch)?
.clone();
BANDWIDTH_CAP_PER_DAY.saturating_sub(peer.rx_bytes + peer.tx_bytes)
BANDWIDTH_CAP_PER_DAY.saturating_sub((peer.rx_bytes + peer.tx_bytes) as i64)
};
Ok(Some(RemainingBandwidthData {
+2 -1
View File
@@ -39,7 +39,8 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
let timeout_check_interval = tokio_stream::wrappers::IntervalStream::new(
tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK),
);
let task_client = task_client.fork(format!("peer{public_key}"));
let mut task_client = task_client.fork(format!("peer-{public_key}"));
task_client.disarm();
PeerHandle {
storage,
public_key,
+1 -3
View File
@@ -538,15 +538,13 @@ pub fn query(
#[entry_point]
pub fn migrate(
mut deps: DepsMut<'_>,
deps: DepsMut<'_>,
_env: Env,
msg: MigrateMsg,
) -> Result<Response, MixnetContractError> {
set_build_information!(deps.storage)?;
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
crate::queued_migrations::explicit_contract_admin(deps.branch())?;
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
// and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh
// environment, one of the contracts will HAVE TO go through a migration
+1 -19
View File
@@ -1,20 +1,2 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use cosmwasm_std::DepsMut;
use mixnet_contract_common::error::MixnetContractError;
pub(crate) fn explicit_contract_admin(deps: DepsMut) -> Result<(), MixnetContractError> {
// we need to read the deprecated field to migrate it over
#[allow(deprecated)]
// SAFETY: this value should ALWAYS exist on the first execution of this migration;
// as a matter of fact, it should ALWAYS continue existing until another migration
#[allow(clippy::expect_used)]
let existing_admin = mixnet_params_storage::CONTRACT_STATE
.load(deps.storage)?
.owner
.expect("the contract state is corrupt - there's no admin set");
mixnet_params_storage::ADMIN.set(deps, Some(existing_admin))?;
Ok(())
}
+4 -7
View File
@@ -11,16 +11,13 @@ STAKE_DENOM_DISPLAY=nyx
DENOMS_EXPONENT=6
MIXNET_CONTRACT_ADDRESS=n1hm4y6fzgxgu688jgf7ek66px6xkrtmn3gyk8fax3eawhp68c2d5qujz296
ECASH_CONTRACT_ADDRESS=n14y2x8a60knc5jjfeztt84kw8x8l5pwdgnqg256v0p9v4p7t2q6eswxyusw
GROUP_CONTRACT_ADDRESS=n1qp35fcj0v9u3trhaps5v9q0lc42t4m6aty2wryss75ee8zuqnsqqdcreyq
MULTISIG_CONTRACT_ADDRESS=n1qa4hswlcjmttulj0q9qa46jf64f93pecl6tydcsjldfe0hy5ju0sdmwzya
COCONUT_DKG_CONTRACT_ADDRESS=n1ayrk6wp6w5lf6njtnfjwljmtcc9vevv5sxwkz7uq24rp2pw67t0qhmmxdd
ECASH_CONTRACT_ADDRESS=n13xspq62y9gq6nueqmywxcdv2yep4p6nzv98w2889k25v3nhdy2dq2rkrk7
GROUP_CONTRACT_ADDRESS=n13l7rwuwktklrwskc7m6lv70zws07en85uma28j7dxwsz9y5hvvhspl7a2t
MULTISIG_CONTRACT_ADDRESS=n138c9pyf7f3hyx0j3t6vmsz7ultnw2wj0lu6hzndep9z5grgq9haqlc25k0
COCONUT_DKG_CONTRACT_ADDRESS=n1pk8jgr6y4c5k93gz7qf3xc0hvygmp7csk88c2tf8l39tkq6834wq2a6dtr
VESTING_CONTRACT_ADDRESS=n1jlzdxnyces4hrhqz68dqk28mrw5jgwtcfq0c2funcwrmw0dx9l9s8nnnvj
REWARDING_VALIDATOR_ADDRESS=n1rfvpsynktze6wvn6ldskj8xgwfzzk5v6pnff39
EXPLORER_API=https://qa-network-explorer.qa.nymte.ch/api
NYXD="https://qa-validator.qa.nymte.ch"
NYM_API="https://qa-nym-api.qa.nymte.ch/api"
DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600"
EXIT_POLICY="https://nymtech.net/.wellknown/network-requester/exit-policy.txt"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.39"
version = "1.1.40"
edition = "2021"
license.workspace = true
@@ -27,7 +27,7 @@ use nym_gateway_storage::{error::StorageError, Storage};
use nym_sphinx::forwarding::packet::MixPacket;
use nym_task::TaskClient;
use nym_validator_client::coconut::EcashApiError;
use rand::{CryptoRng, Rng};
use rand::{random, CryptoRng, Rng};
use std::{process, time::Duration};
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
@@ -236,11 +236,7 @@ where
enc_credential: Vec<u8>,
iv: Vec<u8>,
) -> Result<ServerResponse, RequestHandlingError> {
// TODO: change it into a span field instead once we move to tracing
debug!(
"handling e-cash bandwidth request from {}",
self.client.address
);
debug!("handling e-cash bandwidth request");
let credential = ClientControlRequest::try_from_enc_ecash_credential(
enc_credential,
@@ -253,7 +249,11 @@ where
self.bandwidth_storage_manager.clone(),
);
let available_total = verifier.verify().await?;
let available_total = verifier
.verify()
.await
.inspect_err(|verification_failure| debug!("{verification_failure}"))?;
trace!("available total bandwidth: {available_total}");
Ok(ServerResponse::Bandwidth { available_total })
}
@@ -340,20 +340,17 @@ where
&mut self,
ciphertext: Vec<u8>,
nonce: Vec<u8>,
) -> Message {
) -> Result<ServerResponse, RequestHandlingError> {
let Ok(req) = ClientRequest::decrypt(&ciphertext, &nonce, &self.client.shared_keys) else {
return RequestHandlingError::InvalidEncryptedTextRequest.into_error_message();
return Err(RequestHandlingError::InvalidEncryptedTextRequest);
};
match req {
ClientRequest::UpgradeKey {
hkdf_salt,
derived_key_digest,
} => self
.handle_key_upgrade(hkdf_salt, derived_key_digest)
.await
.into_ws_message(),
_ => RequestHandlingError::UnknownEncryptedTextRequest.into_error_message(),
} => self.handle_key_upgrade(hkdf_salt, derived_key_digest).await,
_ => Err(RequestHandlingError::UnknownEncryptedTextRequest),
}
}
@@ -366,59 +363,64 @@ where
/// * `raw_request`: raw message to handle.
async fn handle_text(&mut self, raw_request: String) -> Message {
trace!("text request");
match ClientControlRequest::try_from(raw_request) {
Err(e) => RequestHandlingError::InvalidTextRequest(e).into_error_message(),
Ok(request) => match request {
ClientControlRequest::EncryptedRequest { ciphertext, nonce } => {
self.handle_encrypted_text_request(ciphertext, nonce).await
}
ClientControlRequest::EcashCredential { enc_credential, iv } => self
.handle_ecash_bandwidth(enc_credential, iv)
.await
.into_ws_message(),
ClientControlRequest::BandwidthCredential { .. } => {
RequestHandlingError::IllegalRequest {
additional_context: "coconut credential are not longer supported".into(),
}
.into_error_message()
}
ClientControlRequest::BandwidthCredentialV2 { .. } => {
RequestHandlingError::IllegalRequest {
additional_context: "coconut credential are not longer supported".into(),
}
.into_error_message()
}
ClientControlRequest::ClaimFreeTestnetBandwidth => self
.bandwidth_storage_manager
.handle_claim_testnet_bandwidth()
.await
.map_err(|e| e.into())
.into_ws_message(),
ClientControlRequest::SupportedProtocol { .. } => self
.inner
.handle_supported_protocol_request()
.into_ws_message(),
other @ ClientControlRequest::Authenticate { .. } => {
RequestHandlingError::IllegalRequest {
additional_context: format!(
"received illegal message of type {} in an authenticated client",
other.name()
),
}
.into_error_message()
}
other @ ClientControlRequest::RegisterHandshakeInitRequest { .. } => {
RequestHandlingError::IllegalRequest {
additional_context: format!(
"received illegal message of type {} in an authenticated client",
other.name()
),
}
.into_error_message()
}
_ => RequestHandlingError::UnknownTextRequest.into_error_message(),
},
let request = match ClientControlRequest::try_from(raw_request) {
Ok(req) => {
debug!("received request of type {}", req.name());
req
}
Err(err) => {
debug!("request was malformed: {err}");
return RequestHandlingError::InvalidTextRequest(err).into_error_message();
}
};
match request {
ClientControlRequest::EncryptedRequest { ciphertext, nonce } => {
self.handle_encrypted_text_request(ciphertext, nonce).await
}
ClientControlRequest::EcashCredential { enc_credential, iv } => {
self.handle_ecash_bandwidth(enc_credential, iv).await
}
ClientControlRequest::BandwidthCredential { .. } => {
Err(RequestHandlingError::IllegalRequest {
additional_context: "coconut credential are not longer supported".into(),
})
}
ClientControlRequest::BandwidthCredentialV2 { .. } => {
Err(RequestHandlingError::IllegalRequest {
additional_context: "coconut credential are not longer supported".into(),
})
}
ClientControlRequest::ClaimFreeTestnetBandwidth => self
.bandwidth_storage_manager
.handle_claim_testnet_bandwidth()
.await
.map_err(|e| e.into()),
ClientControlRequest::SupportedProtocol { .. } => {
Ok(self.inner.handle_supported_protocol_request())
}
other @ ClientControlRequest::Authenticate { .. } => {
Err(RequestHandlingError::IllegalRequest {
additional_context: format!(
"received illegal message of type {} in an authenticated client",
other.name()
),
})
}
other @ ClientControlRequest::RegisterHandshakeInitRequest { .. } => {
Err(RequestHandlingError::IllegalRequest {
additional_context: format!(
"received illegal message of type {} in an authenticated client",
other.name()
),
})
}
_ => Err(RequestHandlingError::UnknownTextRequest),
}
.inspect(|res| debug!(response = ?res, "success"))
.inspect_err(|err| debug!(error = %err, "failure"))
.into_ws_message()
}
/// Handles pong message received from the client.
@@ -452,12 +454,13 @@ where
/// # Arguments
///
/// * `raw_request`: raw received websocket message.
#[instrument(level = "debug", skip_all,
fields(
client = %self.client.address.as_base58_string()
)
)]
async fn handle_request(&mut self, raw_request: Message) -> Option<Message> {
// TODO: this should be added via tracing
debug!(
"handling request from {}",
self.client.address.as_base58_string()
);
trace!("new request");
// apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore
// them and let's test that claim. If that's not the case, just copy code from
@@ -478,8 +481,8 @@ where
where
S: AsyncRead + AsyncWrite + Unpin,
{
let tag: u64 = rand::thread_rng().gen();
debug!("Got request to ping our connection: {}", tag);
let tag: u64 = random();
debug!("got request to ping our connection: {tag}");
self.inner
.send_websocket_message(Message::Ping(tag.to_be_bytes().to_vec()))
.await?;
@@ -73,6 +73,9 @@ pub(crate) enum InitialAuthenticationError {
#[error("Only 'Register' or 'Authenticate' requests are allowed")]
InvalidRequest,
#[error("received a Message of type {typ} which was not expected in this context")]
UnexpectedMessageType { typ: String },
#[error("Experienced connection error: {0}")]
ConnectionError(#[from] WsError),
@@ -420,7 +423,7 @@ where
// we can't handle clients with higher protocol than ours
// (perhaps we could try to negotiate downgrade on our end? sounds like a nice future improvement)
if client_protocol_version <= CURRENT_PROTOCOL_VERSION {
info!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!");
debug!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!");
Ok(CURRENT_PROTOCOL_VERSION)
} else {
let err = InitialAuthenticationError::IncompatibleProtocol {
@@ -861,9 +864,27 @@ where
Message::Binary(_) => {
return Err(InitialAuthenticationError::BinaryRequestWithoutAuthentication);
}
_ => unreachable!(
"the underlying tunsgenite stream should be handling other message types"
),
other => {
if other.is_ping() {
debug!("unexpected ping message!");
return Err(InitialAuthenticationError::UnexpectedMessageType {
typ: "ping".to_string(),
});
} else if other.is_pong() {
debug!("unexpected pong message!");
return Err(InitialAuthenticationError::UnexpectedMessageType {
typ: "pong".to_string(),
});
} else if other.is_close() {
debug!("unexpected close message!");
return Err(InitialAuthenticationError::UnexpectedMessageType {
typ: "close".to_string(),
});
}
// at this point this is definitely unreachable, but just in case, let's not panic...
return Err(InitialAuthenticationError::InvalidRequest);
}
};
text.parse()
+8 -3
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.43"
version = "1.1.44"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
@@ -76,7 +76,7 @@ axum = { workspace = true, features = ["tokio"], optional = true }
axum-extra = { workspace = true, features = ["typed-header"], optional = true }
tower-http = { workspace = true, features = ["cors", "trace"], optional = true }
utoipa = { workspace = true, features = ["axum_extras", "time"], optional = true }
utoipa-swagger-ui = { workspace = true, features = ["axum"], optional = true}
utoipa-swagger-ui = { workspace = true, features = ["axum"], optional = true }
utoipauto = { workspace = true, optional = true }
tracing-subscriber = { workspace = true, features = ["env-filter"], optional = true }
tracing = { workspace = true, optional = true }
@@ -112,7 +112,7 @@ cw4 = { workspace = true }
nym-dkg = { path = "../common/dkg", features = ["cw-types"] }
nym-gateway-client = { path = "../common/client-libs/gateway-client" }
nym-inclusion-probability = { path = "../common/inclusion-probability" }
nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"]}
nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] }
nym-vesting-contract-common = { path = "../common/cosmwasm-smart-contracts/vesting-contract" }
nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" }
nym-multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" }
@@ -129,6 +129,10 @@ nym-node-requests = { path = "../nym-node/nym-node-requests" }
nym-types = { path = "../common/types" }
nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa"] }
tikv-jemallocator = { version = "0.6", optional = true, features = ["profiling"] }
tikv-jemalloc-sys = { version = "0.6", optional = true, features = ["stats", "profiling", "unprefixed_malloc_on_supported_platforms"] }
tikv-jemalloc-ctl = { version = "0.6", optional = true, features = ["use_std", "stats", "profiling"] }
[features]
no-reward = []
generate-ts = ["ts-rs"]
@@ -143,6 +147,7 @@ axum = ["dep:axum",
"nym-http-api-common/utoipa",
"nym-mixnet-contract-common/utoipa"
]
memory-prof = ["tikv-jemallocator", "tikv-jemalloc-ctl", "tikv-jemalloc-sys"]
[build-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
+13 -2
View File
@@ -24,7 +24,7 @@ use circulating_supply_api::cache::CirculatingSupplyCache;
use clap::Parser;
use ecash::dkg::controller::DkgController;
use node_status_api::NodeStatusCache;
use nym_bin_common::logging::setup_logging;
use nym_bin_common::logging::{setup_logging, setup_tracing_logger};
use nym_config::defaults::NymNetworkDetails;
use nym_contract_cache::cache::NymContractCache;
use nym_sphinx::receiver::SphinxMessageReceiver;
@@ -44,6 +44,10 @@ pub(crate) mod nym_nodes;
mod status;
pub(crate) mod support;
#[cfg(feature = "memory-prof")]
#[global_allocator]
static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
#[cfg(feature = "axum")]
mod v2;
@@ -58,9 +62,16 @@ async fn main() -> Result<(), anyhow::Error> {
cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] {
// instrument tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time
console_subscriber::init();
} else {
setup_tracing_logger();
}}
setup_logging();
// setup_tracing_logger();
// std::env::set_var("MALLOC_CONF", "prof:true,lg_prof_interval:28");
// setup_tracing_logger();
// TODO rocket: replace with tracing logger once rocket is eliminated from code
info!("Starting nym api...");
+131
View File
@@ -0,0 +1,131 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_status_api::models::RocketErrorResponse;
use okapi::openapi3::{OpenApi, Responses};
use rocket::http::Status;
use rocket::response::Responder;
use rocket::{response, Request, Route};
use rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::response::OpenApiResponderInner;
use rocket_okapi::settings::OpenApiSettings;
use rocket_okapi::util::ensure_status_code_exists;
use rocket_okapi::{openapi, openapi_get_routes_spec};
// code taken from https://github.dev/GreptimeTeam/greptimedb/blob/develop/src/cmd/src/bin/greptime.rs
#[cfg(feature = "memory-prof")]
pub mod memory_prof {
const PROF_DUMP: &[u8] = b"prof.dump\0";
// const OPT_PROF: &[u8] = b"opt.prof\0";
use anyhow::{bail, Context};
use nym_config::{must_get_home, DEFAULT_NYM_APIS_DIR, NYM_DIR};
use std::ffi::{c_char, CString};
use time::OffsetDateTime;
use tokio::fs::create_dir_all;
use tokio::io::AsyncReadExt;
pub async fn dump_profile() -> anyhow::Result<Vec<u8>> {
if !is_prof_enabled()? {
bail!("memory profiling is not enabled")
}
let now = OffsetDateTime::now_utc();
let dump_path = must_get_home()
.join(NYM_DIR)
.join(DEFAULT_NYM_APIS_DIR)
.join("memory_dumps")
.join(format!("{}", now.unix_timestamp()))
.join("nym-api.hprof");
let parent = dump_path.parent().unwrap();
create_dir_all(&parent).await?;
info!("using {} for the memory dump", dump_path.display());
let path = dump_path
.to_str()
.context("the temp dir contained invalid characters")?
.to_string();
let mut bytes = CString::new(path.as_str())
.context("could not construct a CString out of the path")?
.into_bytes_with_nul();
{
// #safety: we always expect a valid temp file path to write profiling data to.
let ptr = bytes.as_mut_ptr() as *mut c_char;
unsafe {
tikv_jemalloc_ctl::raw::write(PROF_DUMP, ptr).context(format!(
"failed to dump profiling data to {}",
dump_path.display()
))?
}
}
let mut f = tokio::fs::File::open(path.as_str())
.await
.context("failed to open the dump file")?;
let mut buf = vec![];
let _ = f
.read_to_end(&mut buf)
.await
.context("failed to read the dump file")?;
Ok(buf)
}
fn is_prof_enabled() -> anyhow::Result<bool> {
Ok(tikv_jemalloc_ctl::profiling::prof::read()?)
// Ok(unsafe {
// tikv_jemalloc_ctl::raw::read::<bool>(OPT_PROF)
// .context("failed to check the OPT_PROF")?
// })
}
}
pub struct BinaryResponse {
inner: Vec<u8>,
}
impl<'r, 'o: 'r> Responder<'r, 'o> for BinaryResponse {
fn respond_to(self, _req: &'r Request<'_>) -> response::Result<'o> {
let mut res = rocket::Response::new();
res.set_sized_body(self.inner.len(), std::io::Cursor::new(self.inner));
Ok(res)
}
}
impl OpenApiResponderInner for BinaryResponse {
fn responses(_gen: &mut OpenApiGenerator) -> rocket_okapi::Result<Responses> {
let mut responses = Responses::default();
ensure_status_code_exists(&mut responses, 200);
Ok(responses)
}
}
/// foomp
#[cfg(feature = "memory-prof")]
#[openapi(tag = "profiling")]
#[get("/mem")]
pub async fn mem_prof_handler() -> Result<BinaryResponse, RocketErrorResponse> {
let dump_data = memory_prof::dump_profile()
.await
.map_err(|err| RocketErrorResponse::new(err.to_string(), Status::InternalServerError))?;
Ok(BinaryResponse { inner: dump_data })
}
#[cfg(not(feature = "memory-prof"))]
#[openapi(tag = "profiling")]
#[get("/mem")]
pub async fn mem_prof_handler() -> RocketErrorResponse {
RocketErrorResponse::new("The 'mem-prof' feature is disabled", Status::NotImplemented)
}
pub(crate) fn api_status_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
settings:
mem_prof_handler
]
}
+3
View File
@@ -28,6 +28,8 @@ use rocket_okapi::swagger_ui::make_swagger_ui;
pub(crate) mod helpers;
pub(crate) mod openapi;
pub(crate) mod mem_prof;
pub(crate) async fn setup_rocket(
config: &Config,
network_details: NetworkDetails,
@@ -52,6 +54,7 @@ pub(crate) async fn setup_rocket(
"/api-status" => api_status_routes(&openapi_settings),
"/ecash" => ecash::routes_open_api(&openapi_settings, config.coconut_signer.enabled),
"" => nym_node_routes_deprecated(&openapi_settings),
"/prof" => mem_prof::api_status_routes(&openapi_settings),
// => when we move those routes, we'll need to add a redirection for backwards compatibility
"/unstable/nym-nodes" => nym_node_routes_next(&openapi_settings)
+40
View File
@@ -0,0 +1,40 @@
# Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
# SPDX-License-Identifier: Apache-2.0
[package]
name = "nym-data-observatory"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
[dependencies]
anyhow = { workspace = true }
axum = { workspace = true, features = ["tokio"] }
chrono = { workspace = true }
nym-bin-common = { path = "../common/bin-common" }
nym-network-defaults = { path = "../common/network-defaults" }
nym-task = { path = "../common/task" }
nym-node-requests = { path = "../nym-node/nym-node-requests", features = ["openapi"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "postgres", "offline"] }
tokio = { workspace = true, features = ["process"] }
tokio-util = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
tower-http = { workspace = true, features = ["cors", "trace"] }
utoipa = { workspace = true, features = ["axum_extras", "time"] }
utoipa-swagger-ui = { workspace = true, features = ["axum"] }
utoipauto = { workspace = true }
[build-dependencies]
anyhow = { workspace = true }
tokio = { workspace = true, features = ["macros" ] }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "postgres"] }
+93
View File
@@ -0,0 +1,93 @@
# Making `sqlx` work
Some of the errors encountered and possible solutions.
## `The cargo feature offline has to be enabled to use SQLX_OFFLINE`
Did you enable `offline` **cargo feature** of `sqlx` dependency in your
`Cargo.toml`?
Also, it may happen if you have a version mismatch beetween
- `sqlx` cargo dependency in `Cargo.toml`
- `sqlx-cli` installed by cargo
To ensure correct version, do
```
cargo uninstall sqlx-cli
cargo install --version <version> sqlx-cli
```
where `<version>` matches the version of sqlx in your `Cargo.toml`.
## `Error: failed to connect to database: password authentication failed`
If it's in-code, make sure you don't "double authenticate", i.e.
- if username and password are already specified in `DATABASE_URL`
- then, you don't have to use
```rust
ConnectOptions::from_str(&database_Url)
.username() // unnecessary
.password() // unnecessary
```
If it's outside of code (i.e. when running `cargo check`)
- make sure password doesn't have any special characters that could be
interpreted by the command line/shell weirdly, like `$#\` etc.
## Cannot generate `sqlx-data.json`
In order for `sqlx` to generate schema for "offline" work (without DB
connection), as of `v0.6.3` you first **need an active DB connection**.
So make sure
- DB is running
- `DATABASE_URL` is set correctly
- `SQLX_OFFLINE` isn't exported to true
Then run `cargo sqlx prepare`
After you have the file, you can ignore `DATABASE_URL` and terminate the DB
instance. This file represents the DB schema, so when your migrations change,
you'll need to re-generate it
Make sure to commit the file to VCS if you want to avoid re-doing this again on
each machine (e.g. other developers, CI).
## Generated `sqlx-data.json` looks like this
```json
{
"db": "PostgreSQL"
}
```
after running `cargo sqlx prepare`
### Similar to:
```
warning: no queries found; do you have the `offline` feature enabled
```
### Possible solutions
- does your `sqlx-cli` version match `sqlx` version from `Cargo.toml`?
- do you have `offline` cargo feature enabled?
- make sure to `cargo clean` after these updates
## Any many, many more
- `EOF while parsing a value at line`
- `failed to find data for query`
### Possible solutions
- Usually a DB connection issue
- Retry everything
- Throw in a `cargo clean -p <your package>` for good measure
+58
View File
@@ -0,0 +1,58 @@
use anyhow::Result;
use sqlx::{Connection, PgConnection};
use std::io::Write;
use std::{collections::HashMap, fs::File};
const POSTGRES_USER: &str = "nym";
const POSTGRES_PASSWORD: &str = "password123";
const POSTGRES_DB: &str = "data_obs_db";
/// if schema changes, rerun `cargo sqlx prepare` with a running DB
/// https://github.com/launchbadge/sqlx/blob/main/sqlx-cli/README.md#enable-building-in-offline-mode-with-query
#[tokio::main]
async fn main() -> Result<()> {
let db_url =
format!("postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@localhost:5432/{POSTGRES_DB}");
// if a live DB is reachable, use that
if PgConnection::connect(&db_url).await.is_ok() {
export_db_variables(&db_url)?;
println!("cargo::rustc-env=SQLX_OFFLINE=false");
run_migrations(&db_url).await?;
} else {
// by default, run in offline mode
println!("cargo::rustc-env=SQLX_OFFLINE=true");
}
rerun_if_changed();
Ok(())
}
fn export_db_variables(db_url: &str) -> Result<()> {
let mut map = HashMap::new();
map.insert("POSTGRES_USER", POSTGRES_USER);
map.insert("POSTGRES_PASSWORD", POSTGRES_PASSWORD);
map.insert("POSTGRES_DB", POSTGRES_DB);
map.insert("DATABASE_URL", db_url);
let mut file = File::create(".env")?;
for (var, value) in map.iter() {
println!("cargo::rustc-env={}={}", var, value);
writeln!(file, "{}={}", var, value).expect("Failed to write to dotenv file");
}
Ok(())
}
async fn run_migrations(db_url: &str) -> Result<()> {
let mut conn = PgConnection::connect(db_url).await?;
sqlx::migrate!("./migrations").run(&mut conn).await?;
Ok(())
}
fn rerun_if_changed() {
println!("cargo::rerun-if-changed=migrations");
println!("cargo::rerun-if-changed=src/db/queries");
}
+13
View File
@@ -0,0 +1,13 @@
services:
postgres:
image: postgres:13
container_name: nym-data-observatory-pg
env_file:
- .env
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
@@ -0,0 +1,6 @@
CREATE TABLE responses (
id SERIAL PRIMARY KEY,
joke_id VARCHAR NOT NULL UNIQUE,
joke TEXT NOT NULL,
date_created INTEGER NOT NULL
);
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
source .env
# Launching a container in such a way that it's destroyed after you detach from the terminal:
docker compose up
# docker exec -it nym-data-observatory-pg /bin/bash
# psql -U youruser -d yourdb
echo "Tearing down containers to have a clean slate"
docker compose down -v
+79
View File
@@ -0,0 +1,79 @@
{
"db": "PostgreSQL",
"249faa11b88b749f50342bb5c9cc41d20896db543eed74a6f320c041bcbb723d": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Int4"
]
}
},
"query": "INSERT INTO responses\n (joke_id, joke, date_created)\n VALUES\n ($1, $2, $3)\n ON CONFLICT(joke_id) DO UPDATE SET\n joke=excluded.joke,\n date_created=excluded.date_created;"
},
"aff7fbd06728004d2f2226d20c32f1482df00de2dc1d2b4debbb2e12553d997b": {
"describe": {
"columns": [
{
"name": "joke_id",
"ordinal": 0,
"type_info": "Varchar"
},
{
"name": "joke",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "date_created",
"ordinal": 2,
"type_info": "Int4"
}
],
"nullable": [
false,
false,
false
],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "SELECT joke_id, joke, date_created FROM responses WHERE joke_id = $1"
},
"e53f479f8cead3dc8aa1875e5d450ad69686cf6a109e37d6c3f0623c3e9f91d0": {
"describe": {
"columns": [
{
"name": "joke_id",
"ordinal": 0,
"type_info": "Varchar"
},
{
"name": "joke",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "date_created",
"ordinal": 2,
"type_info": "Int4"
}
],
"nullable": [
false,
false,
false
],
"parameters": {
"Left": []
}
},
"query": "SELECT joke_id, joke, date_created FROM responses"
}
}
@@ -0,0 +1,61 @@
use core::str;
use serde::Deserialize;
use tokio::process::Command;
use tokio::task::JoinHandle;
use tokio::time::Duration;
use crate::db::{self, DbPool};
const REFRESH_DELAY: Duration = Duration::from_secs(15);
const FAILURE_RETRY_DELAY: Duration = Duration::from_secs(60 * 2);
pub(crate) async fn spawn_in_background(db_pool: DbPool) -> JoinHandle<()> {
loop {
tracing::info!("Running in a loop 🏃");
if let Err(e) = some_network_action(&db_pool).await {
tracing::error!(
"❌ Run failed: {e}, retrying in {}s...",
FAILURE_RETRY_DELAY.as_secs()
);
tokio::time::sleep(FAILURE_RETRY_DELAY).await;
} else {
tracing::info!(
"✅ Run successful, sleeping for {}s...",
REFRESH_DELAY.as_secs()
);
tokio::time::sleep(REFRESH_DELAY).await;
}
}
}
#[derive(Deserialize, Debug)]
pub(crate) struct Response {
#[serde(rename(deserialize = "id"))]
pub(crate) joke_id: String,
pub(crate) joke: String,
#[serde(rename(deserialize = "status"))]
pub(crate) _status: u16,
}
async fn some_network_action(pool: &DbPool) -> anyhow::Result<()> {
// for demonstration purposes only. You should use reqwest if you need it
let output = Command::new("curl")
.arg("-H")
.arg("Accept: application/json")
.arg("https://icanhazdadjoke.com/")
.output()
.await?;
if !output.status.success() {
anyhow::bail!("Curl command failed with status: {}", output.status);
}
let response_str = str::from_utf8(&output.stdout)?;
let joke_response: Response = serde_json::from_str(response_str)?;
tracing::info!("{:?}", joke_response.joke);
db::queries::insert_joke(pool, joke_response.into()).await?;
Ok(())
}
+40
View File
@@ -0,0 +1,40 @@
use anyhow::{anyhow, Result};
use sqlx::{migrate::Migrator, postgres::PgConnectOptions, ConnectOptions, PgPool};
use std::str::FromStr;
pub(crate) mod models;
pub(crate) mod queries;
pub(crate) const DATABASE_URL_ENV_VAR: &str = "DATABASE_URL";
static MIGRATOR: Migrator = sqlx::migrate!("./migrations");
pub(crate) type DbPool = PgPool;
pub(crate) struct Storage {
pool: DbPool,
}
impl Storage {
pub async fn init() -> Result<Self> {
let connection_url = std::env::var(DATABASE_URL_ENV_VAR).map_err(anyhow::Error::from)?;
let connect_options = {
let mut connect_options = PgConnectOptions::from_str(&connection_url)?;
let connect_options = connect_options.disable_statement_logging();
(*connect_options).clone()
};
let pool = DbPool::connect_with(connect_options)
.await
.map_err(|err| anyhow!("Failed to connect to {}: {}", &connection_url, err))?;
MIGRATOR.run(&pool).await?;
Ok(Storage { pool })
}
/// Cloning pool is cheap, it's the same underlying set of connections
pub async fn pool_owned(&self) -> DbPool {
self.pool.clone()
}
}
+22
View File
@@ -0,0 +1,22 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::background_task::Response;
#[derive(Serialize, Deserialize, Debug, ToSchema)]
pub(crate) struct JokeDto {
pub(crate) joke_id: String,
pub(crate) joke: String,
pub(crate) date_created: i32,
}
impl From<Response> for JokeDto {
fn from(value: Response) -> Self {
Self {
joke_id: value.joke_id,
joke: value.joke,
// casting not smart, can implicitly panic, don't do this in prod
date_created: chrono::offset::Utc::now().timestamp() as i32,
}
}
}
@@ -0,0 +1,39 @@
use crate::db::{models::JokeDto, DbPool};
pub(crate) async fn insert_joke(pool: &DbPool, joke: JokeDto) -> anyhow::Result<()> {
let mut conn = pool.acquire().await?;
sqlx::query!(
"INSERT INTO responses
(joke_id, joke, date_created)
VALUES
($1, $2, $3)
ON CONFLICT(joke_id) DO UPDATE SET
joke=excluded.joke,
date_created=excluded.date_created;",
joke.joke_id,
joke.joke,
joke.date_created as i32,
)
.execute(&mut *conn)
.await?;
Ok(())
}
pub(crate) async fn select_joke_by_id(pool: &DbPool, joke_id: &str) -> anyhow::Result<JokeDto> {
sqlx::query_as!(
JokeDto,
"SELECT joke_id, joke, date_created FROM responses WHERE joke_id = $1",
joke_id
)
.fetch_one(pool)
.await
.map_err(anyhow::Error::from)
}
pub(crate) async fn select_all(pool: &DbPool) -> anyhow::Result<Vec<JokeDto>> {
sqlx::query_as!(JokeDto, "SELECT joke_id, joke, date_created FROM responses",)
.fetch_all(pool)
.await
.map_err(anyhow::Error::from)
}
@@ -0,0 +1,5 @@
// group queries in files by theme
mod joke;
// re-exporting allows us to access all queries via `queries::bla``
pub(crate) use joke::{insert_joke, select_all, select_joke_by_id};
@@ -0,0 +1,78 @@
use axum::{
extract::{Path, State},
Json, Router,
};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::{
db::{
models::JokeDto,
queries::{self, select_joke_by_id},
},
http::{
error::{Error, HttpResult},
state::AppState,
},
};
pub(crate) fn routes() -> Router<AppState> {
Router::new()
.route("/", axum::routing::get(jokes))
.route("/:joke_id", axum::routing::get(joke_by_id))
.route("/fetch_another", axum::routing::get(fetch_another))
}
#[utoipa::path(
tag = "Dad Jokes",
get,
path = "/v1/jokes",
responses(
(status = 200, body = Vec<JokeDto>)
)
)]
async fn jokes(State(state): State<AppState>) -> HttpResult<Json<Vec<JokeDto>>> {
queries::select_all(state.db_pool())
.await
.map(Json::from)
.map_err(|_| Error::internal())
}
#[derive(Deserialize, IntoParams)]
#[into_params(parameter_in = Path)]
struct JokeIdParam {
joke_id: String,
}
#[utoipa::path(
tag = "Dad Jokes",
get,
params(
JokeIdParam
),
path = "/v1/jokes/{joke_id}",
responses(
(status = 200, body = JokeDto)
)
)]
async fn joke_by_id(
Path(JokeIdParam { joke_id }): Path<JokeIdParam>,
State(state): State<AppState>,
) -> HttpResult<Json<JokeDto>> {
select_joke_by_id(state.db_pool(), &joke_id)
.await
.map(Json::from)
.map_err(|_| Error::not_found(joke_id))
}
#[utoipa::path(
tag = "Dad Jokes",
get,
path = "/v1/jokes/fetch_another",
responses(
(status = 200, body = String)
)
)]
async fn fetch_another(State(_state): State<AppState>) -> HttpResult<Json<String>> {
Ok(Json(String::from("Done boss, check the DB")))
}
@@ -0,0 +1,21 @@
use axum::{extract::State, Json, Router};
use crate::http::{error::HttpResult, state::AppState};
pub(crate) fn routes() -> Router<AppState> {
Router::new().route("/", axum::routing::get(mixnodes))
}
#[utoipa::path(
tag = "Mixnodes",
get,
path = "/v1/mixnodes",
responses(
(status = 200, body = String)
)
)]
async fn mixnodes(State(_state): State<AppState>) -> HttpResult<Json<serde_json::Value>> {
Ok(Json(
serde_json::json!({"message": "😎 Nothing to see here, move along 😎"}),
))
}
+79
View File
@@ -0,0 +1,79 @@
use anyhow::anyhow;
use axum::{response::Redirect, Router};
use tokio::net::ToSocketAddrs;
use tower_http::{cors::CorsLayer, trace::TraceLayer};
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
use crate::http::{api_docs, server::HttpServer, state::AppState};
pub(crate) mod jokes;
pub(crate) mod mixnodes;
pub(crate) struct RouterBuilder {
unfinished_router: Router<AppState>,
}
impl RouterBuilder {
pub(crate) fn with_default_routes() -> Self {
let router = Router::new()
.merge(
SwaggerUi::new("/swagger")
.url("/api-docs/openapi.json", api_docs::ApiDoc::openapi()),
)
.route(
"/",
axum::routing::get(|| async { Redirect::permanent("/swagger") }),
)
.nest(
"/v1",
Router::new()
.nest("/jokes", jokes::routes())
.nest("/mixnodes", mixnodes::routes()),
);
Self {
unfinished_router: router,
}
}
pub(crate) fn with_state(self, state: AppState) -> RouterWithState {
RouterWithState {
router: self.finalize_routes().with_state(state),
}
}
fn finalize_routes(self) -> Router<AppState> {
// layers added later wrap earlier layers
self.unfinished_router
// CORS layer needs to wrap other API layers
.layer(setup_cors())
// logger should be outermost layer
.layer(TraceLayer::new_for_http())
}
}
pub(crate) struct RouterWithState {
router: Router,
}
impl RouterWithState {
pub(crate) async fn build_server<A: ToSocketAddrs>(
self,
bind_address: A,
) -> anyhow::Result<HttpServer> {
tokio::net::TcpListener::bind(bind_address)
.await
.map(|listener| HttpServer::new(self.router, listener))
.map_err(|err| anyhow!("Couldn't bind to address due to {}", err))
}
}
fn setup_cors() -> CorsLayer {
use axum::http::Method;
CorsLayer::new()
.allow_origin(tower_http::cors::Any)
.allow_methods([Method::POST, Method::GET, Method::PATCH, Method::OPTIONS])
.allow_headers(tower_http::cors::Any)
.allow_credentials(false)
}
@@ -0,0 +1,91 @@
use axum::Router;
use core::net::SocketAddr;
use tokio::{net::TcpListener, task::JoinHandle};
use tokio_util::sync::{CancellationToken, WaitForCancellationFutureOwned};
use crate::{
db::DbPool,
http::{api::RouterBuilder, state::AppState},
};
/// Return handles that allow for graceful shutdown of server + awaiting its
/// background tokio task
pub(crate) async fn start_http_api(
db_pool: DbPool,
http_port: u16,
nym_http_cache_ttl: u64,
) -> anyhow::Result<ShutdownHandles> {
let router_builder = RouterBuilder::with_default_routes();
let state = AppState::new(db_pool, nym_http_cache_ttl);
let router = router_builder.with_state(state);
let bind_addr = format!("0.0.0.0:{}", http_port);
let server = router.build_server(bind_addr).await?;
Ok(start_server(server))
}
fn start_server(server: HttpServer) -> ShutdownHandles {
// one copy is stored to trigger a graceful shutdown later
let shutdown_button = CancellationToken::new();
// other copy is given to server to listen for a shutdown
let shutdown_receiver = shutdown_button.clone();
let shutdown_receiver = shutdown_receiver.cancelled_owned();
let server_handle = tokio::spawn(async move { server.run(shutdown_receiver).await });
ShutdownHandles {
server_handle,
shutdown_button,
}
}
pub(crate) struct ShutdownHandles {
server_handle: JoinHandle<std::io::Result<()>>,
shutdown_button: CancellationToken,
}
impl ShutdownHandles {
/// Send graceful shutdown signal to server and wait for server task to complete
pub(crate) async fn shutdown(self) -> anyhow::Result<()> {
self.shutdown_button.cancel();
match self.server_handle.await {
Ok(Ok(_)) => {
tracing::info!("HTTP server shut down without errors");
}
Ok(Err(err)) => {
tracing::error!("HTTP server terminated with: {err}");
anyhow::bail!(err)
}
Err(err) => {
tracing::error!("Server task panicked: {err}");
}
};
Ok(())
}
}
pub(crate) struct HttpServer {
router: Router,
listener: TcpListener,
}
impl HttpServer {
pub(crate) fn new(router: Router, listener: TcpListener) -> Self {
Self { router, listener }
}
pub(crate) async fn run(self, receiver: WaitForCancellationFutureOwned) -> std::io::Result<()> {
// into_make_service_with_connect_info allows us to see client ip address
axum::serve(
self.listener,
self.router
.into_make_service_with_connect_info::<SocketAddr>(),
)
.with_graceful_shutdown(receiver)
.await
}
}
+10
View File
@@ -0,0 +1,10 @@
use utoipa::OpenApi;
use utoipauto::utoipauto;
// manually import external structs which are behind feature flags because they
// can't be automatically discovered
// https://github.com/ProbablyClem/utoipauto/issues/13#issuecomment-1974911829
#[utoipauto(paths = "./nym-data-observatory/src")]
#[derive(OpenApi)]
#[openapi(info(title = "Nym API"), tags(), components(schemas()))]
pub(super) struct ApiDoc;
+23
View File
@@ -0,0 +1,23 @@
use crate::read_env_var;
#[derive(Debug)]
pub(crate) struct Config {
http_port: u16,
}
const HTTP_PORT_DEFAULT: u16 = 8000;
impl Config {
pub(crate) fn from_env() -> Self {
Self {
http_port: read_env_var("HTTP_PORT")
.unwrap_or(HTTP_PORT_DEFAULT.to_string())
.parse()
.unwrap_or(HTTP_PORT_DEFAULT),
}
}
pub(crate) fn http_port(&self) -> u16 {
self.http_port
}
}
+28
View File
@@ -0,0 +1,28 @@
pub(crate) type HttpResult<T> = Result<T, Error>;
pub(crate) struct Error {
message: String,
status: axum::http::StatusCode,
}
impl Error {
pub(crate) fn not_found(message: String) -> Self {
Self {
message,
status: axum::http::StatusCode::NOT_FOUND,
}
}
pub(crate) fn internal() -> Self {
Self {
message: String::from("Internal server error"),
status: axum::http::StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
impl axum::response::IntoResponse for Error {
fn into_response(self) -> axum::response::Response {
(self.status, self.message).into_response()
}
}
+6
View File
@@ -0,0 +1,6 @@
pub(crate) mod api;
pub(crate) mod api_docs;
pub(crate) mod config;
pub(crate) mod error;
pub(crate) mod server;
pub(crate) mod state;
+91
View File
@@ -0,0 +1,91 @@
use axum::Router;
use core::net::SocketAddr;
use tokio::{net::TcpListener, task::JoinHandle};
use tokio_util::sync::{CancellationToken, WaitForCancellationFutureOwned};
use crate::{
db::DbPool,
http::{api::RouterBuilder, state::AppState},
};
/// Return handles that allow for graceful shutdown of server + awaiting its
/// background tokio task
pub(crate) async fn start_http_api(
db_pool: DbPool,
http_port: u16,
) -> anyhow::Result<ShutdownHandles> {
let router_builder = RouterBuilder::with_default_routes();
let state = AppState::new(db_pool);
let router = router_builder.with_state(state);
let bind_addr = format!("0.0.0.0:{}", http_port);
let server = router.build_server(bind_addr).await?;
Ok(start_server(server))
}
fn start_server(server: HttpServer) -> ShutdownHandles {
// one copy is stored to trigger a graceful shutdown later
let shutdown_button = CancellationToken::new();
// other copy is given to server to listen for a shutdown
let shutdown_receiver = shutdown_button.clone();
let shutdown_receiver = shutdown_receiver.cancelled_owned();
let server_handle = tokio::spawn(async move { server.run(shutdown_receiver).await });
ShutdownHandles {
server_handle,
shutdown_button,
}
}
pub(crate) struct ShutdownHandles {
server_handle: JoinHandle<std::io::Result<()>>,
shutdown_button: CancellationToken,
}
impl ShutdownHandles {
/// Send graceful shutdown signal to server and wait for server task to complete
pub(crate) async fn shutdown(self) -> anyhow::Result<()> {
self.shutdown_button.cancel();
match self.server_handle.await {
Ok(Ok(_)) => {
tracing::info!("HTTP server shut down without errors");
}
Ok(Err(err)) => {
tracing::error!("HTTP server terminated with: {err}");
anyhow::bail!(err)
}
Err(err) => {
tracing::error!("Server task panicked: {err}");
}
};
Ok(())
}
}
pub(crate) struct HttpServer {
router: Router,
listener: TcpListener,
}
impl HttpServer {
pub(crate) fn new(router: Router, listener: TcpListener) -> Self {
Self { router, listener }
}
pub(crate) async fn run(self, receiver: WaitForCancellationFutureOwned) -> std::io::Result<()> {
// into_make_service_with_connect_info allows us to see client ip address
// in middleware, for logging, TLS, routing etc.
axum::serve(
self.listener,
self.router
.into_make_service_with_connect_info::<SocketAddr>(),
)
.with_graceful_shutdown(receiver)
.await
}
}
+16
View File
@@ -0,0 +1,16 @@
use crate::db::DbPool;
#[derive(Debug, Clone)]
pub(crate) struct AppState {
db_pool: DbPool,
}
impl AppState {
pub(crate) fn new(db_pool: DbPool) -> Self {
Self { db_pool }
}
pub(crate) fn db_pool(&self) -> &DbPool {
&self.db_pool
}
}
+43
View File
@@ -0,0 +1,43 @@
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{filter::Directive, EnvFilter};
pub(crate) fn setup_tracing_logger() {
fn directive_checked(directive: String) -> Directive {
directive.parse().expect("Failed to parse log directive")
}
let log_builder = tracing_subscriber::fmt()
// Use a more compact, abbreviated log format
.compact()
// Display source code file paths
.with_file(true)
// Display source code line numbers
.with_line_number(true)
// Don't display the event's target (module path)
.with_target(false);
let mut filter = EnvFilter::builder()
// if RUST_LOG isn't set, set default level
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy();
// these crates are more granularly filtered
let filter_crates = [
"nym_bin_common",
"nym_explorer_client",
"nym_network_defaults",
"nym_validator_client",
"reqwest",
"rustls",
"hyper",
"sqlx",
"h2",
"tendermint_rpc",
"tower_http",
"axum",
];
for crate_name in filter_crates {
filter = filter.add_directive(directive_checked(format!("{}=warn", crate_name)));
}
log_builder.with_env_filter(filter).init();
}
+52
View File
@@ -0,0 +1,52 @@
use nym_network_defaults::setup_env;
use nym_task::signal::wait_for_signal;
use crate::http::config;
mod background_task;
mod db;
mod http;
mod logging;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
logging::setup_tracing_logger();
// if dotenv file is present, load its values
// otherwise, default to mainnet
setup_env(Some(".env"));
let conf = config::Config::from_env();
tracing::debug!("Using config:\n{:?}", conf);
let storage = db::Storage::init().await?;
let db_pool = storage.pool_owned().await;
tokio::spawn(async move {
background_task::spawn_in_background(db_pool).await;
tracing::info!("Started task");
});
let shutdown_handles =
http::server::start_http_api(storage.pool_owned().await, conf.http_port())
.await
.expect("Failed to start server");
tracing::info!("Started HTTP server on port {}", conf.http_port());
wait_for_signal().await;
if let Err(err) = shutdown_handles.shutdown().await {
tracing::error!("{err}");
};
Ok(())
}
// TODO dz move this to common
fn read_env_var(env_var: &str) -> anyhow::Result<String> {
std::env::var(env_var)
.map_err(|_| anyhow::anyhow!("You need to set {}", env_var))
.map(|value| {
tracing::trace!("{}={}", env_var, value);
value
})
}
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node"
version = "1.1.7"
version = "1.1.8"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+43 -55
View File
@@ -538,7 +538,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
dependencies = [
"camino",
"cargo-platform",
"semver 1.0.22",
"semver",
"serde",
"serde_json",
"thiserror",
@@ -1141,7 +1141,7 @@ dependencies = [
"cosmwasm-std",
"cw2",
"schemars",
"semver 1.0.22",
"semver",
"serde",
"thiserror",
]
@@ -1156,7 +1156,7 @@ dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"schemars",
"semver 1.0.22",
"semver",
"serde",
"thiserror",
]
@@ -2158,7 +2158,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.1.0",
"indexmap 2.0.0",
"indexmap 2.5.0",
"slab",
"tokio",
"tokio-util",
@@ -2190,9 +2190,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.14.0"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heck"
@@ -2559,12 +2559,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.0.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
"hashbrown 0.14.5",
"serde",
]
@@ -3115,7 +3115,7 @@ dependencies = [
"log",
"pretty_env_logger",
"schemars",
"semver 0.11.0",
"semver",
"serde",
"utoipa",
"vergen",
@@ -3173,7 +3173,7 @@ dependencies = [
"log",
"nym-network-defaults",
"serde",
"toml 0.7.6",
"toml 0.8.19",
"url",
]
@@ -3517,12 +3517,9 @@ dependencies = [
"base64 0.22.1",
"log",
"nym-config",
"nym-crypto",
"nym-network-defaults",
"serde",
"serde_json",
"thiserror",
"utoipa",
"x25519-dalek",
]
@@ -4555,7 +4552,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver 1.0.22",
"semver",
]
[[package]]
@@ -4798,31 +4795,13 @@ dependencies = [
[[package]]
name = "semver"
version = "0.11.0"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
dependencies = [
"serde",
]
[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
[[package]]
name = "serde"
version = "1.0.210"
@@ -4897,9 +4876,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.3"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
@@ -5450,7 +5429,7 @@ dependencies = [
"raw-window-handle",
"regex",
"rfd",
"semver 1.0.22",
"semver",
"serde",
"serde_json",
"serde_repr",
@@ -5483,7 +5462,7 @@ dependencies = [
"cargo_toml",
"heck 0.4.1",
"json-patch",
"semver 1.0.22",
"semver",
"serde_json",
"tauri-utils",
"winres",
@@ -5504,7 +5483,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"semver 1.0.22",
"semver",
"serde",
"serde_json",
"sha2 0.10.8",
@@ -5587,7 +5566,7 @@ dependencies = [
"phf 0.10.1",
"proc-macro2",
"quote",
"semver 1.0.22",
"semver",
"serde",
"serde_json",
"serde_with",
@@ -5682,7 +5661,7 @@ dependencies = [
"serde",
"serde_json",
"tendermint 0.37.0",
"toml 0.8.2",
"toml 0.8.19",
"url",
]
@@ -5735,7 +5714,7 @@ dependencies = [
"pin-project",
"rand 0.8.5",
"reqwest 0.11.22",
"semver 1.0.22",
"semver",
"serde",
"serde_bytes",
"serde_json",
@@ -5954,21 +5933,21 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.2"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.20.2",
"toml_edit 0.22.22",
]
[[package]]
name = "toml_datetime"
version = "0.6.3"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
@@ -5979,24 +5958,24 @@ version = "0.19.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
dependencies = [
"indexmap 2.0.0",
"indexmap 2.5.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
"winnow 0.5.10",
]
[[package]]
name = "toml_edit"
version = "0.20.2"
version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap 2.0.0",
"indexmap 2.5.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
"winnow 0.6.20",
]
[[package]]
@@ -6213,7 +6192,7 @@ version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23"
dependencies = [
"indexmap 2.0.0",
"indexmap 2.5.0",
"serde",
"serde_json",
"utoipa-gen",
@@ -6870,6 +6849,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.50.0"
+5 -5
View File
@@ -1,7 +1,8 @@
[package]
name = "nym-cpp-ffi"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
license.workspace = true
[lib]
name = "nym_cpp_ffi"
@@ -11,13 +12,12 @@ crate-type = ["cdylib"]
# Async runtime
tokio = { version = "1", features = ["full"] }
# Nym clients, addressing, packet format, common tools (logging), ffi shared
nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-bin-common = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sdk = { path = "../../rust/nym-sdk/" }
nym-bin-common = { path = "../../../common/bin-common" }
nym-sphinx-anonymous-replies = { path = "../../../common/nymsphinx/anonymous-replies" }
nym-ffi-shared = { path = "../shared" }
lazy_static = "1.4.0"
# error handling
anyhow = "1.0.75"
# base58 en/decoding
bs58 = "0.5.0"
+23 -23
View File
@@ -1,46 +1,46 @@
# C++ FFI
> ⚠️ This is an initial version of this library in order to give developers something to experiment with. If you use this code to begin testing out Mixnet integration and run into issues, errors, or have feedback, please feel free to open an issue; feedback from developers trying to use it will help us improve it. If you have questions feel free to reach out via our [Matrix channel](https://matrix.to/#/#dev:nymtech.chat).
# C++ FFI
> ⚠️ This is an initial version of this library in order to give developers something to experiment with. If you use this code to begin testing out Mixnet integration and run into issues, errors, or have feedback, please feel free to open an issue; feedback from developers trying to use it will help us improve it. If you have questions feel free to reach out via our [Matrix channel](https://matrix.to/#/#dev:nymtech.chat).
This repo contains:
* `lib.rs`: an initial version of bindings for interacting with the Mixnet via the Rust SDK from C++. These are essentially match statements wrapping imported functions from the `nym-ffi-shared` lib allowing for nicer [error handling](#error-handling-).
* `main.cpp`: an example of using this library, relying on `Boost` for threads.
* `lib.rs`: an initial version of bindings for interacting with the Mixnet via the Rust SDK from C++. These are essentially match statements wrapping imported functions from the `nym-ffi-shared` lib allowing for nicer [error handling](#error-handling-).
* `main.cpp`: an example of using this library, relying on `Boost` for threads.
The example `.cpp` file is a simple example flow of:
* setting up Nym client logging
The example `.cpp` file is a simple example flow of:
* setting up Nym client logging
* creating an ephemeral Nym client (no key storage / persistent address - this will come in a future iteration)
* getting its [Nym address](https://nymtech.net/docs/clients/addressing-system.html)
* using that address to send a message to yourself via the Mixnet
* using that address to send a message to yourself via the Mixnet
* listen for and parse the incoming message for the `sender_tag` used for [anonymous replies with SURBs](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs)
* send a reply to yourself using SURBs
## Installation
Prerequisites:
> Unlike the Go FFI code, this code does not yet have bindings for the TcpProxyClient/Server. This will happen in the future.
## Installation
Prerequisites:
* Rust
* C++
* C++
* [Boost](https://www.boost.org/) which can be installed with:
```
# Arch / Manjaro
yay -S boost boost-libs
# Arch / Manjaro
yay -S boost boost-libs
# Debian / Ubuntu
# Debian / Ubuntu
sudo apt install libboost-all-dev
```
## Usage
The `build.sh` script in the root of the repository speeds up the task of building and linking the Rust and C++ code.
* if want to quickly recompile your code run it as-is with `./build.sh`
* if you want to clean build both the Rust and C++ code after removing existing compiled binaries run it with the optional `clean` argument: `./build.sh clean`.
> Make sure to run the script from the root of the project directory.
The `build.sh` script in the root of the repository speeds up the task of building and linking the Rust and C++ code.
* if want to quickly recompile your code run it as-is with `./build.sh`
* if you want to clean build both the Rust and C++ code after removing existing compiled binaries run it with the optional `clean` argument: `./build.sh clean`.
This script will:
> Make sure to run the script from the root of the project directory.
This script will:
* (optionally if called with `clean` argument) remove existing Rust and C++ artifacts
* build `lib.rs` with the `--release` flag
* compile `main.cpp`, linking `lib.rs`
* compile `main.cpp`, linking `lib.rs`
* set value of `LD_LIBRARY_PATH` to the Rust code in `target/release/`
* run the compiled `main`
## Error Handling
## Error Handling
When calling a function across the FFI boundary (e.g.) `reply`, the Rust code is matching the output of an `_internal` function - `Res` or `Err` - to a member of the `StatusCode` enum. This allows for both Rust-style error handling and the ease of returning a `c_int` across the FFI boundary, which can be used by C++ for its own error handling / conditional logic.
+2 -3
View File
@@ -20,8 +20,8 @@ build_artifacts_and_link() {
cargo build --release &&
cd src/ &&
printf "compiling cpp \n"
g++ -std=c++11 -o main main.cpp -ldl -lpthread -L../target/release -lnym_cpp_ffi -lboost_thread &&
export LD_LIBRARY_PATH=../target/release:$LD_LIBRARY_PATH
g++ -std=c++11 -o main main.cpp -ldl -lpthread -L../../../../target/release -lnym_cpp_ffi -lboost_thread &&
export LD_LIBRARY_PATH=../../../../target/release:$LD_LIBRARY_PATH
# check output for name of rust lib - can be helpful if you've changed e.g. the name of a file and the compilation is failing
# printf "ldd main: \n"
# ldd main
@@ -48,4 +48,3 @@ else
exit 1
fi
fi
+3 -2
View File
@@ -1,7 +1,8 @@
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_ffi_shared;
// TODO REMOVE when you're working on new CPP branch
#![allow(clippy::all)]
// use nym_ffi_shared;
use std::ffi::{c_char, c_int, CStr, CString};
use nym_sphinx_anonymous_replies::requests::AnonymousSenderTag;
+6 -1
View File
@@ -1,12 +1,17 @@
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// TODO REMOVE when you're working on new CPP branch
#![allow(clippy::all)]
pub mod types {
use std::ffi::c_char;
// TODO change all the numbers / replace -2 with prxy?
#[derive(Debug)]
pub enum StatusCode {
NoError = 0,
ClientInitError = -1,
ClientUninitialisedError = -2,
// ClientUninitialisedError = -2,
SelfAddrError = -3,
SendMsgError = -4,
ReplyError = -5,
+9 -8
View File
@@ -1,19 +1,20 @@
[package]
name = "nym-go-ffi" #"goffitest"
version = "0.1.0"
name = "nym-go-ffi" #"goffitest"
version = "0.2.0"
edition = "2021"
license.workspace = true
[lib]
crate-type = ["cdylib"]
name = "nym_go_ffi" #"go_ffi"
name = "nym_go_ffi" #"go_ffi"
[dependencies]
# Bindgen
uniffi = { version = "0.25.2", features = ["cli"] }
# Nym clients, addressing, packet format, common tools (logging), ffi shared
nym-bin-common = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sdk = { path = "../../rust/nym-sdk/" }
nym-bin-common = { path = "../../../common/bin-common" }
nym-sphinx-anonymous-replies = { path = "../../../common/nymsphinx/anonymous-replies" }
nym-ffi-shared = { path = "../shared" }
# Async runtime
tokio = { version = "1", features = ["full"] }
@@ -23,8 +24,8 @@ anyhow = "1.0.79"
thiserror = "1.0.56"
[build-dependencies]
uniffi = { version = "0.25.2", features = ["build" ] }
uniffi_build = { version = "0.25.2", features=["builtin-bindgen"] }
uniffi = { version = "0.25.2", features = ["build"] }
uniffi_build = { version = "0.25.2", features = ["builtin-bindgen"] }
[[bin]]
name = "uniffi-bindgen"
+16 -24
View File
@@ -1,27 +1,20 @@
# Go FFI
> ⚠️ This is an initial version of this library in order to give developers something to experiment with. If you use this code to begin testing out Mixnet integration and run into issues, errors, or have feedback, please feel free to open an issue; feedback from developers trying to use it will help us improve it. If you have questions feel free to reach out via our [Matrix channel](https://matrix.to/#/#dev:nymtech.chat).
This repo contains:
* `lib.rs`: an initial version of bindings for interacting with the Mixnet via the Rust SDK from Go. These are essentially match statemtns wrapping imported functions from the `nym-ffi-shared` lib.
* `ffi/`: a directory containing:
This repo contains:
* `lib.rs`: an initial version of bindings for interacting with the Mixnet via the Rust SDK from Go. These are essentially match statemtns wrapping imported functions from the `nym-ffi-shared` lib.
* `ffi/`: a directory containing:
* the `bindings/` files generated using [`uniffi-bindgen-go`](https://github.com/NordSecurity/uniffi-bindgen-go)
* [`example.go`](./example.go): an example of using this library.
* [`example.go`](./example.go): an example of using the mixnet client functionality.
* [`proxy_example.go`](./proxy_example.go): an example of using the TcpProxy functionality.
The `example.go` file is an example flow of:
* setting up Nym client logging
* creating an ephemeral Nym client (no key storage / persistent address - this will come in a future iteration)
* getting its [Nym address](https://nymtech.net/docs/clients/addressing-system.html)
* using that address to send a message to yourself via the Mixnet
* listen for and parse the incoming message for the `sender_tag` used for [anonymous replies with SURBs](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs)
* send a reply to yourself using SURBs
## Useage - Consuming the Library
You can import the bindings as normal and interact with them as shown in the example files. These files import the bindings from this repository (hence the `go.mod` and `go.sum` in the crate root) but you can import them remotely as usual.
## Useage - Consuming the Library
You can import the bindings as normal and interact with them as shown in the [example file](./example.go). This example imports the bindings from the this repository (hence the `go.mod` and `go.sum` in the crate root) but you can import them remotely as usual.
## Useage - Developing on the Library
If you want to fork and add new features/functions to this library use the following instructions to rebuild the Go bindings.
## Useage - Developing on the Library
If you want to fork and add new features/functions to this library use the following instructions to rebuild the Go bindings.
Rust functions exposed to the Go binding library are in `./src/lib.rs`.
Rust functions exposed to the Go binding library are in `./src/lib.rs`.
The `build.sh` script in the root of the repository speeds up the task of building and linking the Rust and Go code.
* if want to quickly recompile your code run it as-is with `./build.sh`
@@ -29,19 +22,18 @@ The `build.sh` script in the root of the repository speeds up the task of buildi
> Make sure to run the script from the root of the project directory, and that your LD PATH is set first!
> ```
> RUST_BINARIES=target/release
> echo 'export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:'${RUST_BINARIES} >> ~/.zshrc
> source ~/.zshrc
> RUST_BINARIES=../../../target/release
> echo 'export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:'${RUST_BINARIES} >> ~/.zshrc
> source ~/.zshrc
> ```
This script will:
* (optionally if called with `clean` argument) remove existing Rust and Go artifacts
* build `lib.rs` with the `--release` flag
* compile the Go bindings
* compile the Go bindings
**WIP** you need to manually add the following `cgo` flags to the generated bindings immediately underneath LN3 (`// #include <bindings.h`). In the future this will be automated in `build.sh`:
**WIP** you need to manually add the following `cgo` flags to the generated bindings immediately underneath LN3 (`// #include <bindings.h`). In the future this will be automated in `build.sh`:
```
// #cgo LDFLAGS: -L../../target/release -lnym_go_ffi
// #cgo LDFLAGS: -L../../../../../target/release -lnym_go_ffi
```
-1
View File
@@ -1,4 +1,3 @@
fn main() {
uniffi::generate_scaffolding("src/bindings.udl").unwrap();
}
+1 -8
View File
@@ -15,14 +15,7 @@ build_artifacts() {
printf "building go bindings \n"
uniffi-bindgen-go $UDL_PATH --out-dir $GO_DIR
printf "bindings built \n\n"
# something not right with these - having to add it manually to bindings.go for the moment
# pushd $GO_DIR/bindings
# echo $(pwd)
# LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}:../../target/release" \
# CGO_LDFLAGS="-L../target/release -lnym_go_ffi -lm -ldl" \
# CGO_ENABLED=1 \
# go run ../main.go
# TODO pull in auto binding from https://github.com/NordSecurity/uniffi-bindgen-go/blob/main/test_bindings.sh (removes need for manual addition of cgo flags)
}
clean_artifacts() {
+9
View File
@@ -6,6 +6,15 @@ import (
"time"
)
/*
Flow showing:
- setting up Nym client logging
- creating an ephemeral Nym client (no key storage / persistent address - this will come in a future iteration)
- getting its [Nym address](https://nymtech.net/docs/clients/addressing-system.html)
- using that address to send a message to yourself via the Mixnet
- listen for and parse the incoming message for the `sender_tag` used for [anonymous replies with SURBs] (https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs)
- send a reply to yourself using SURBs
*/
func main() {
// initialise Nym client logging - this is quite verbose but very informative
+289 -30
View File
@@ -1,7 +1,7 @@
package bindings
// #include <bindings.h>
// #cgo LDFLAGS: -L../../target/release -lnym_go_ffi
// #cgo LDFLAGS: -L../../../../../target/release -lnym_go_ffi
import "C"
import (
@@ -379,6 +379,42 @@ func uniffiCheckChecksums() {
panic("bindings: uniffi_nym_go_ffi_checksum_func_listen_for_incoming: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_nym_go_ffi_checksum_func_new_proxy_client(uniffiStatus)
})
if checksum != 14386 {
// If this happens try cleaning and rebuilding your project
panic("bindings: uniffi_nym_go_ffi_checksum_func_new_proxy_client: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_nym_go_ffi_checksum_func_new_proxy_client_default(uniffiStatus)
})
if checksum != 23215 {
// If this happens try cleaning and rebuilding your project
panic("bindings: uniffi_nym_go_ffi_checksum_func_new_proxy_client_default: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_nym_go_ffi_checksum_func_new_proxy_server(uniffiStatus)
})
if checksum != 40789 {
// If this happens try cleaning and rebuilding your project
panic("bindings: uniffi_nym_go_ffi_checksum_func_new_proxy_server: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_nym_go_ffi_checksum_func_proxy_server_address(uniffiStatus)
})
if checksum != 1079 {
// If this happens try cleaning and rebuilding your project
panic("bindings: uniffi_nym_go_ffi_checksum_func_proxy_server_address: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_nym_go_ffi_checksum_func_reply(uniffiStatus)
@@ -388,6 +424,24 @@ func uniffiCheckChecksums() {
panic("bindings: uniffi_nym_go_ffi_checksum_func_reply: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_nym_go_ffi_checksum_func_run_proxy_client(uniffiStatus)
})
if checksum != 45441 {
// If this happens try cleaning and rebuilding your project
panic("bindings: uniffi_nym_go_ffi_checksum_func_run_proxy_client: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_nym_go_ffi_checksum_func_run_proxy_server(uniffiStatus)
})
if checksum != 57536 {
// If this happens try cleaning and rebuilding your project
panic("bindings: uniffi_nym_go_ffi_checksum_func_run_proxy_server: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_nym_go_ffi_checksum_func_send_message(uniffiStatus)
@@ -399,6 +453,30 @@ func uniffiCheckChecksums() {
}
}
type FfiConverterUint64 struct{}
var FfiConverterUint64INSTANCE = FfiConverterUint64{}
func (FfiConverterUint64) Lower(value uint64) C.uint64_t {
return C.uint64_t(value)
}
func (FfiConverterUint64) Write(writer io.Writer, value uint64) {
writeUint64(writer, value)
}
func (FfiConverterUint64) Lift(value C.uint64_t) uint64 {
return uint64(value)
}
func (FfiConverterUint64) Read(reader io.Reader) uint64 {
return readUint64(reader)
}
type FfiDestroyerUint64 struct{}
func (FfiDestroyerUint64) Destroy(_ uint64) {}
type FfiConverterString struct{}
var FfiConverterStringINSTANCE = FfiConverterString{}
@@ -547,11 +625,15 @@ func (err GoWrapError) Unwrap() error {
// Err* are used for checking error type with `errors.Is`
var ErrGoWrapErrorClientInitError = fmt.Errorf("GoWrapErrorClientInitError")
var ErrGoWrapErrorClientUninitialisedError = fmt.Errorf("GoWrapErrorClientUninitialisedError")
var ErrGoWrapErrorSelfAddrError = fmt.Errorf("GoWrapErrorSelfAddrError")
var ErrGoWrapErrorSendMsgError = fmt.Errorf("GoWrapErrorSendMsgError")
var ErrGoWrapErrorReplyError = fmt.Errorf("GoWrapErrorReplyError")
var ErrGoWrapErrorListenError = fmt.Errorf("GoWrapErrorListenError")
var ErrGoWrapErrorProxyInitError = fmt.Errorf("GoWrapErrorProxyInitError")
var ErrGoWrapErrorProxyRunError = fmt.Errorf("GoWrapErrorProxyRunError")
var ErrGoWrapErrorServerInitError = fmt.Errorf("GoWrapErrorServerInitError")
var ErrGoWrapErrorAddressGetterError = fmt.Errorf("GoWrapErrorAddressGetterError")
var ErrGoWrapErrorServerRunError = fmt.Errorf("GoWrapErrorServerRunError")
// Variant structs
type GoWrapErrorClientInitError struct {
@@ -572,24 +654,6 @@ func (self GoWrapErrorClientInitError) Is(target error) bool {
return target == ErrGoWrapErrorClientInitError
}
type GoWrapErrorClientUninitialisedError struct {
message string
}
func NewGoWrapErrorClientUninitialisedError() *GoWrapError {
return &GoWrapError{
err: &GoWrapErrorClientUninitialisedError{},
}
}
func (err GoWrapErrorClientUninitialisedError) Error() string {
return fmt.Sprintf("ClientUninitialisedError: %s", err.message)
}
func (self GoWrapErrorClientUninitialisedError) Is(target error) bool {
return target == ErrGoWrapErrorClientUninitialisedError
}
type GoWrapErrorSelfAddrError struct {
message string
}
@@ -662,6 +726,96 @@ func (self GoWrapErrorListenError) Is(target error) bool {
return target == ErrGoWrapErrorListenError
}
type GoWrapErrorProxyInitError struct {
message string
}
func NewGoWrapErrorProxyInitError() *GoWrapError {
return &GoWrapError{
err: &GoWrapErrorProxyInitError{},
}
}
func (err GoWrapErrorProxyInitError) Error() string {
return fmt.Sprintf("ProxyInitError: %s", err.message)
}
func (self GoWrapErrorProxyInitError) Is(target error) bool {
return target == ErrGoWrapErrorProxyInitError
}
type GoWrapErrorProxyRunError struct {
message string
}
func NewGoWrapErrorProxyRunError() *GoWrapError {
return &GoWrapError{
err: &GoWrapErrorProxyRunError{},
}
}
func (err GoWrapErrorProxyRunError) Error() string {
return fmt.Sprintf("ProxyRunError: %s", err.message)
}
func (self GoWrapErrorProxyRunError) Is(target error) bool {
return target == ErrGoWrapErrorProxyRunError
}
type GoWrapErrorServerInitError struct {
message string
}
func NewGoWrapErrorServerInitError() *GoWrapError {
return &GoWrapError{
err: &GoWrapErrorServerInitError{},
}
}
func (err GoWrapErrorServerInitError) Error() string {
return fmt.Sprintf("ServerInitError: %s", err.message)
}
func (self GoWrapErrorServerInitError) Is(target error) bool {
return target == ErrGoWrapErrorServerInitError
}
type GoWrapErrorAddressGetterError struct {
message string
}
func NewGoWrapErrorAddressGetterError() *GoWrapError {
return &GoWrapError{
err: &GoWrapErrorAddressGetterError{},
}
}
func (err GoWrapErrorAddressGetterError) Error() string {
return fmt.Sprintf("AddressGetterError: %s", err.message)
}
func (self GoWrapErrorAddressGetterError) Is(target error) bool {
return target == ErrGoWrapErrorAddressGetterError
}
type GoWrapErrorServerRunError struct {
message string
}
func NewGoWrapErrorServerRunError() *GoWrapError {
return &GoWrapError{
err: &GoWrapErrorServerRunError{},
}
}
func (err GoWrapErrorServerRunError) Error() string {
return fmt.Sprintf("ServerRunError: %s", err.message)
}
func (self GoWrapErrorServerRunError) Is(target error) bool {
return target == ErrGoWrapErrorServerRunError
}
type FfiConverterTypeGoWrapError struct{}
var FfiConverterTypeGoWrapErrorINSTANCE = FfiConverterTypeGoWrapError{}
@@ -682,15 +836,23 @@ func (c FfiConverterTypeGoWrapError) Read(reader io.Reader) error {
case 1:
return &GoWrapError{&GoWrapErrorClientInitError{message}}
case 2:
return &GoWrapError{&GoWrapErrorClientUninitialisedError{message}}
case 3:
return &GoWrapError{&GoWrapErrorSelfAddrError{message}}
case 4:
case 3:
return &GoWrapError{&GoWrapErrorSendMsgError{message}}
case 5:
case 4:
return &GoWrapError{&GoWrapErrorReplyError{message}}
case 6:
case 5:
return &GoWrapError{&GoWrapErrorListenError{message}}
case 6:
return &GoWrapError{&GoWrapErrorProxyInitError{message}}
case 7:
return &GoWrapError{&GoWrapErrorProxyRunError{message}}
case 8:
return &GoWrapError{&GoWrapErrorServerInitError{message}}
case 9:
return &GoWrapError{&GoWrapErrorAddressGetterError{message}}
case 10:
return &GoWrapError{&GoWrapErrorServerRunError{message}}
default:
panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeGoWrapError.Read()", errorID))
}
@@ -701,22 +863,67 @@ func (c FfiConverterTypeGoWrapError) Write(writer io.Writer, value *GoWrapError)
switch variantValue := value.err.(type) {
case *GoWrapErrorClientInitError:
writeInt32(writer, 1)
case *GoWrapErrorClientUninitialisedError:
writeInt32(writer, 2)
case *GoWrapErrorSelfAddrError:
writeInt32(writer, 3)
writeInt32(writer, 2)
case *GoWrapErrorSendMsgError:
writeInt32(writer, 4)
writeInt32(writer, 3)
case *GoWrapErrorReplyError:
writeInt32(writer, 5)
writeInt32(writer, 4)
case *GoWrapErrorListenError:
writeInt32(writer, 5)
case *GoWrapErrorProxyInitError:
writeInt32(writer, 6)
case *GoWrapErrorProxyRunError:
writeInt32(writer, 7)
case *GoWrapErrorServerInitError:
writeInt32(writer, 8)
case *GoWrapErrorAddressGetterError:
writeInt32(writer, 9)
case *GoWrapErrorServerRunError:
writeInt32(writer, 10)
default:
_ = variantValue
panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeGoWrapError.Write", value))
}
}
type FfiConverterOptionalString struct{}
var FfiConverterOptionalStringINSTANCE = FfiConverterOptionalString{}
func (c FfiConverterOptionalString) Lift(rb RustBufferI) *string {
return LiftFromRustBuffer[*string](c, rb)
}
func (_ FfiConverterOptionalString) Read(reader io.Reader) *string {
if readInt8(reader) == 0 {
return nil
}
temp := FfiConverterStringINSTANCE.Read(reader)
return &temp
}
func (c FfiConverterOptionalString) Lower(value *string) RustBuffer {
return LowerIntoRustBuffer[*string](c, value)
}
func (_ FfiConverterOptionalString) Write(writer io.Writer, value *string) {
if value == nil {
writeInt8(writer, 0)
} else {
writeInt8(writer, 1)
FfiConverterStringINSTANCE.Write(writer, *value)
}
}
type FfiDestroyerOptionalString struct{}
func (_ FfiDestroyerOptionalString) Destroy(value *string) {
if value != nil {
FfiDestroyerString{}.Destroy(*value)
}
}
func GetSelfAddress() (string, error) {
_uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeGoWrapError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_nym_go_ffi_fn_func_get_self_address(_uniffiStatus)
@@ -756,6 +963,42 @@ func ListenForIncoming() (IncomingMessage, error) {
}
}
func NewProxyClient(serverAddress string, listenAddress string, listenPort string, closeTimeout uint64, env *string) error {
_, _uniffiErr := rustCallWithError(FfiConverterTypeGoWrapError{}, func(_uniffiStatus *C.RustCallStatus) bool {
C.uniffi_nym_go_ffi_fn_func_new_proxy_client(FfiConverterStringINSTANCE.Lower(serverAddress), FfiConverterStringINSTANCE.Lower(listenAddress), FfiConverterStringINSTANCE.Lower(listenPort), FfiConverterUint64INSTANCE.Lower(closeTimeout), FfiConverterOptionalStringINSTANCE.Lower(env), _uniffiStatus)
return false
})
return _uniffiErr
}
func NewProxyClientDefault(serverAddress string, env *string) error {
_, _uniffiErr := rustCallWithError(FfiConverterTypeGoWrapError{}, func(_uniffiStatus *C.RustCallStatus) bool {
C.uniffi_nym_go_ffi_fn_func_new_proxy_client_default(FfiConverterStringINSTANCE.Lower(serverAddress), FfiConverterOptionalStringINSTANCE.Lower(env), _uniffiStatus)
return false
})
return _uniffiErr
}
func NewProxyServer(upstreamAddress string, configDir string, env *string) error {
_, _uniffiErr := rustCallWithError(FfiConverterTypeGoWrapError{}, func(_uniffiStatus *C.RustCallStatus) bool {
C.uniffi_nym_go_ffi_fn_func_new_proxy_server(FfiConverterStringINSTANCE.Lower(upstreamAddress), FfiConverterStringINSTANCE.Lower(configDir), FfiConverterOptionalStringINSTANCE.Lower(env), _uniffiStatus)
return false
})
return _uniffiErr
}
func ProxyServerAddress() (string, error) {
_uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeGoWrapError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_nym_go_ffi_fn_func_proxy_server_address(_uniffiStatus)
})
if _uniffiErr != nil {
var _uniffiDefaultValue string
return _uniffiDefaultValue, _uniffiErr
} else {
return FfiConverterStringINSTANCE.Lift(_uniffiRV), _uniffiErr
}
}
func Reply(recipient []byte, message string) error {
_, _uniffiErr := rustCallWithError(FfiConverterTypeGoWrapError{}, func(_uniffiStatus *C.RustCallStatus) bool {
C.uniffi_nym_go_ffi_fn_func_reply(FfiConverterBytesINSTANCE.Lower(recipient), FfiConverterStringINSTANCE.Lower(message), _uniffiStatus)
@@ -764,6 +1007,22 @@ func Reply(recipient []byte, message string) error {
return _uniffiErr
}
func RunProxyClient() error {
_, _uniffiErr := rustCallWithError(FfiConverterTypeGoWrapError{}, func(_uniffiStatus *C.RustCallStatus) bool {
C.uniffi_nym_go_ffi_fn_func_run_proxy_client(_uniffiStatus)
return false
})
return _uniffiErr
}
func RunProxyServer() error {
_, _uniffiErr := rustCallWithError(FfiConverterTypeGoWrapError{}, func(_uniffiStatus *C.RustCallStatus) bool {
C.uniffi_nym_go_ffi_fn_func_run_proxy_server(_uniffiStatus)
return false
})
return _uniffiErr
}
func SendMessage(recipient string, message string) error {
_, _uniffiErr := rustCallWithError(FfiConverterTypeGoWrapError{}, func(_uniffiStatus *C.RustCallStatus) bool {
C.uniffi_nym_go_ffi_fn_func_send_message(FfiConverterStringINSTANCE.Lower(recipient), FfiConverterStringINSTANCE.Lower(message), _uniffiStatus)
+58
View File
@@ -84,12 +84,46 @@ RustBuffer uniffi_nym_go_ffi_fn_func_listen_for_incoming(
RustCallStatus* out_status
);
void uniffi_nym_go_ffi_fn_func_new_proxy_client(
RustBuffer server_address,
RustBuffer listen_address,
RustBuffer listen_port,
uint64_t close_timeout,
RustBuffer env,
RustCallStatus* out_status
);
void uniffi_nym_go_ffi_fn_func_new_proxy_client_default(
RustBuffer server_address,
RustBuffer env,
RustCallStatus* out_status
);
void uniffi_nym_go_ffi_fn_func_new_proxy_server(
RustBuffer upstream_address,
RustBuffer config_dir,
RustBuffer env,
RustCallStatus* out_status
);
RustBuffer uniffi_nym_go_ffi_fn_func_proxy_server_address(
RustCallStatus* out_status
);
void uniffi_nym_go_ffi_fn_func_reply(
RustBuffer recipient,
RustBuffer message,
RustCallStatus* out_status
);
void uniffi_nym_go_ffi_fn_func_run_proxy_client(
RustCallStatus* out_status
);
void uniffi_nym_go_ffi_fn_func_run_proxy_server(
RustCallStatus* out_status
);
void uniffi_nym_go_ffi_fn_func_send_message(
RustBuffer recipient,
RustBuffer message,
@@ -411,10 +445,34 @@ uint16_t uniffi_nym_go_ffi_checksum_func_listen_for_incoming(
RustCallStatus* out_status
);
uint16_t uniffi_nym_go_ffi_checksum_func_new_proxy_client(
RustCallStatus* out_status
);
uint16_t uniffi_nym_go_ffi_checksum_func_new_proxy_client_default(
RustCallStatus* out_status
);
uint16_t uniffi_nym_go_ffi_checksum_func_new_proxy_server(
RustCallStatus* out_status
);
uint16_t uniffi_nym_go_ffi_checksum_func_proxy_server_address(
RustCallStatus* out_status
);
uint16_t uniffi_nym_go_ffi_checksum_func_reply(
RustCallStatus* out_status
);
uint16_t uniffi_nym_go_ffi_checksum_func_run_proxy_client(
RustCallStatus* out_status
);
uint16_t uniffi_nym_go_ffi_checksum_func_run_proxy_server(
RustCallStatus* out_status
);
uint16_t uniffi_nym_go_ffi_checksum_func_send_message(
RustCallStatus* out_status
);
+158
View File
@@ -0,0 +1,158 @@
package main
import (
"bufio"
"fmt"
"net"
"nymffi/go-nym/bindings"
"os"
"time"
)
func runProxyClient() {
run_err := bindings.RunProxyClient()
if run_err != nil {
fmt.Println(run_err)
return
}
}
func runProxyServer() {
run_err := bindings.RunProxyServer()
if run_err != nil {
fmt.Println(run_err)
return
}
}
// connects to the proxy server and listens out for incoming as you would with a normal tcp connection
func startTcpListener() {
ln, err := net.Listen("tcp", ":9000")
if err != nil {
fmt.Println(err)
return
}
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println(err)
continue
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 1024)
_, err := conn.Read(buf)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Server-side tcp received: %s", buf)
_, err = conn.Write(buf)
if err != nil {
fmt.Println(err)
return
}
}
/*
Flow showing:
- setting up Nym client logging
- creating instances of both the NymProxyClient and NymProxyServer
- running both in goroutines to kick off the process
- starting a vanilla tcp listener/echo in a goroutine connected to the ProxyServer instance
- pinging that via a tcp client and waiting for the reply: this is (under the hood) sent anonymously via SURBs - the ProxyServer and 'server-side' tcp listener never know the Nym address or IP of the ProxyClient/'client-side' tcp client.
*/
func main() {
// our mixnet client config file defining which network to use
var env_path = "../../../envs/canary.env"
// the tcp socket our server communicates with - the remote host your client is trying to hit
var upstreamAddress = "127.0.0.1:9000"
// where the keys and persistent storage for SURBs is located (this path will be prepended with the value of $HOME in the rust lib)
var configDir = "/tmp/go-proxy-server-example"
// tcp socket port our proxy client communicates with
var clientPort = "8080"
// timeout for ephemeral client to shutdown connection after sending Close message enum once it has sent all of the other messages (in seconds): this is used by the ProxyServer for session management
var clientTimeout uint64 = 60
bindings.InitLogging()
// checking loading proper env
file, err := os.Open(env_path)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
// init a proxy server
build_serv_err := bindings.NewProxyServer(upstreamAddress, configDir, &env_path)
if build_serv_err != nil {
fmt.Println(build_serv_err)
return
}
// get proxy addr
proxyAddr, get_addr_err := bindings.ProxyServerAddress()
if get_addr_err != nil {
fmt.Println("(Go) Error:", get_addr_err)
return
}
fmt.Println("(Go) server address:")
fmt.Println(proxyAddr)
// run it in a goroutine
go runProxyServer()
// initialise a proxy client
build_err := bindings.NewProxyClient(proxyAddr, "127.0.0.1", clientPort, clientTimeout, &env_path)
if build_err != nil {
fmt.Println(build_err)
return
}
// run it in a goroutine
go runProxyClient()
// connect 'server-side' tcp socket to ProxyServer
go startTcpListener()
// send a oneshot message, wait for the echo, and close. you will see the session uuid info and the fact that the proxy_client logs it will be closing the session in <clientTimeout>.
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
fmt.Println(err)
return
}
_, err = conn.Write([]byte("Hello, server: oneshot ping\n"))
if err != nil {
fmt.Println(err)
return
}
buf := make([]byte, 1024)
_, read_err := conn.Read(buf)
if read_err != nil {
fmt.Println(read_err)
return
}
fmt.Printf("Client-side tcp received: %s", buf)
conn.Close()
// sleep so that the nym client processes can catch up - in reality you'd have another process
// running to keep logging going, so this is only necessary for this reference
time.Sleep(60 * time.Second)
fmt.Println("(Go) end go example")
}
+18 -2
View File
@@ -1,11 +1,15 @@
[Error]
enum GoWrapError {
"ClientInitError",
"ClientUninitialisedError",
"SelfAddrError",
"SendMsgError",
"ReplyError",
"ListenError"
"ListenError",
"ProxyInitError",
"ProxyRunError",
"ServerInitError",
"AddressGetterError",
"ServerRunError"
};
dictionary IncomingMessage {
@@ -25,4 +29,16 @@ namespace bindings {
void reply(bytes recipient, string message);
[Throws=GoWrapError]
IncomingMessage listen_for_incoming();
[Throws=GoWrapError]
void new_proxy_client(string server_address, string listen_address, string listen_port, u64 close_timeout, string? env);
[Throws=GoWrapError]
void new_proxy_client_default(string server_address, string? env);
[Throws=GoWrapError]
void run_proxy_client();
[Throws=GoWrapError]
void new_proxy_server(string upstream_address, string config_dir, string? env);
[Throws=GoWrapError]
string proxy_server_address();
[Throws=GoWrapError]
void run_proxy_server();
};
+81 -5
View File
@@ -5,12 +5,11 @@ use nym_sdk::mixnet::Recipient;
use nym_sphinx_anonymous_replies::requests::AnonymousSenderTag;
uniffi::include_scaffolding!("bindings");
#[allow(clippy::enum_variant_names)]
#[derive(Debug, thiserror::Error)]
enum GoWrapError {
#[error("Couldn't init client")]
ClientInitError {},
#[error("Client is uninitialised: init client first")]
ClientUninitialisedError {},
#[error("Error getting self address")]
SelfAddrError {},
#[error("Error sending message")]
@@ -19,6 +18,16 @@ enum GoWrapError {
ReplyError {},
#[error("Could not start listening")]
ListenError {},
#[error("Couldn't init proxy client")]
ProxyInitError {},
#[error("Couldn't run proxy client")]
ProxyRunError {},
#[error("Couldn't init proxy server")]
ServerInitError {},
#[error("Couldn't get proxy server address")]
AddressGetterError {},
#[error("Couldn't run proxy server")]
ServerRunError {},
}
#[no_mangle]
@@ -44,7 +53,8 @@ fn get_self_address() -> Result<String, GoWrapError> {
#[no_mangle]
fn send_message(recipient: String, message: String) -> Result<(), GoWrapError> {
let nym_recipient_type = Recipient::try_from_base58_string(recipient).unwrap();
let nym_recipient_type =
Recipient::try_from_base58_string(recipient).expect("couldn't create Recipient");
match nym_ffi_shared::send_message_internal(nym_recipient_type, &message) {
Ok(_) => Ok(()),
Err(_) => Err(GoWrapError::SendMsgError {}),
@@ -72,11 +82,77 @@ fn listen_for_incoming() -> Result<IncomingMessage, GoWrapError> {
match nym_ffi_shared::listen_for_incoming_internal() {
Ok(received) => {
let message = String::from_utf8_lossy(&received.message).to_string();
// maybe change this to raw bytes to send over TODO
let sender = received.sender_tag.unwrap().to_bytes().to_vec(); //.to_base58_string();
let sender = received.sender_tag.unwrap().to_bytes().to_vec();
let incoming = IncomingMessage { message, sender };
Ok(incoming)
}
Err(_) => Err(GoWrapError::ListenError {}),
}
}
#[no_mangle]
fn new_proxy_client(
server_address: String,
listen_address: String,
listen_port: String,
close_timeout: u64,
env: Option<String>,
) -> Result<(), GoWrapError> {
let server_nym_addr =
Recipient::try_from_base58_string(server_address).expect("couldn't create Recipient");
match nym_ffi_shared::proxy_client_new_internal(
server_nym_addr,
&listen_address,
&listen_port,
close_timeout,
env,
) {
Ok(_) => Ok(()),
Err(_) => Err(GoWrapError::ProxyInitError {}),
}
}
#[no_mangle]
fn new_proxy_client_default(
server_address: String,
env: Option<String>,
) -> Result<(), GoWrapError> {
let server_nym_addr =
Recipient::try_from_base58_string(server_address).expect("couldn't create Recipient");
match nym_ffi_shared::proxy_client_new_defaults_internal(server_nym_addr, env) {
Ok(_) => Ok(()),
Err(_) => Err(GoWrapError::ProxyInitError {}),
}
}
fn run_proxy_client() -> Result<(), GoWrapError> {
match nym_ffi_shared::proxy_client_run_internal() {
Ok(_) => Ok(()),
Err(_) => Err(GoWrapError::ProxyRunError {}),
}
}
fn new_proxy_server(
upstream_address: String,
config_dir: String,
env: Option<String>,
) -> Result<(), GoWrapError> {
match nym_ffi_shared::proxy_server_new_internal(&upstream_address, &config_dir, env) {
Ok(_) => Ok(()),
Err(_) => Err(GoWrapError::ServerInitError {}),
}
}
fn proxy_server_address() -> Result<String, GoWrapError> {
match nym_ffi_shared::proxy_server_address_internal() {
Ok(address) => Ok(address.to_string()),
Err(_) => Err(GoWrapError::AddressGetterError {}),
}
}
fn run_proxy_server() -> Result<(), GoWrapError> {
match nym_ffi_shared::proxy_server_run_internal() {
Ok(_) => Ok(()),
Err(_) => Err(GoWrapError::ServerRunError {}),
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
fn main() {
uniffi::uniffi_bindgen_main()
}
}
+7 -9
View File
@@ -1,16 +1,16 @@
[package]
name = "nym-ffi-shared"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license.workspace = true
# TODO change to load relative + remove this from the workspace exclude list
[dependencies]
# Async runtime
tokio = { version = "1", features = ["full"] }
# Nym clients, addressing, packet format, common tools (logging)
nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-bin-common = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sdk = { path = "../../rust/nym-sdk/" }
nym-bin-common = { path = "../../../common/bin-common" }
nym-sphinx-anonymous-replies = { path = "../../../common/nymsphinx/anonymous-replies" }
# static var macro
lazy_static = "1.4.0"
# error handling
@@ -21,7 +21,5 @@ bs58 = "0.5.0"
uniffi = { version = "0.25.2", features = ["cli"] }
[build-dependencies]
uniffi = { version = "0.25.2", features = ["build" ] }
uniffi_build = { version = "0.25.2", features=["builtin-bindgen"] }
uniffi = { version = "0.25.2", features = ["build"] }
uniffi_build = { version = "0.25.2", features = ["builtin-bindgen"] }
+162 -7
View File
@@ -3,19 +3,24 @@
use anyhow::{anyhow, bail};
use lazy_static::lazy_static;
use nym_sdk::mixnet::{MixnetClient, MixnetMessageSender, ReconstructedMessage, Recipient};
use nym_sdk::mixnet::{
MixnetClient, MixnetClientBuilder, MixnetMessageSender, Recipient, ReconstructedMessage,
StoragePaths,
};
use nym_sdk::tcp_proxy::{NymProxyClient, NymProxyServer};
use nym_sphinx_anonymous_replies::requests::AnonymousSenderTag;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use tokio::runtime::Runtime;
// NYM_CLIENT: Static reference (only init-ed once) to:
// NYM_CLIENT/PROXIES: Static reference (only init-ed once) to:
// - Arc: share ownership
// - Mutex: thread-safe way to share data between threads
// - Option: init-ed or not
// RUNTIME: Tokio runtime: no need to pass back to C and deal with raw pointers as it was previously
lazy_static! {
static ref NYM_PROXY_CLIENT: Arc<Mutex<Option<NymProxyClient>>> = Arc::new(Mutex::new(None));
static ref NYM_PROXY_SERVER: Arc<Mutex<Option<NymProxyServer>>> = Arc::new(Mutex::new(None));
static ref NYM_CLIENT: Arc<Mutex<Option<MixnetClient>>> = Arc::new(Mutex::new(None));
static ref RUNTIME: Runtime = Runtime::new().unwrap();
}
@@ -30,7 +35,30 @@ pub fn init_ephemeral_internal() -> anyhow::Result<(), anyhow::Error> {
if let Ok(ref mut client) = client {
**client = Some(init_client);
} else {
anyhow!("couldnt lock NYM_CLIENT");
return Err(anyhow!("couldnt lock ephemeral NYM_CLIENT"));
}
Ok::<(), anyhow::Error>(())
})?;
}
Ok(())
}
pub fn init_default_storage_internal(config_dir: PathBuf) -> anyhow::Result<(), anyhow::Error> {
if NYM_CLIENT.lock().unwrap().as_ref().is_some() {
bail!("client already exists");
} else {
RUNTIME.block_on(async move {
let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap();
let init_client = MixnetClientBuilder::new_with_default_storage(storage_paths)
.await?
.build()?
.connect_to_mixnet()
.await?;
let mut client = NYM_CLIENT.try_lock();
if let Ok(ref mut client) = client {
**client = Some(init_client);
} else {
return Err(anyhow!("couldnt lock NYM_CLIENT"));
}
Ok::<(), anyhow::Error>(())
})?;
@@ -50,6 +78,8 @@ pub fn get_self_address_internal() -> anyhow::Result<String, anyhow::Error> {
Ok(nym_client.nym_address().to_string())
}
// TODO split sender
pub fn send_message_internal(
recipient: Recipient,
message: &str,
@@ -62,7 +92,6 @@ pub fn send_message_internal(
.as_ref()
.ok_or_else(|| anyhow!("could not get client as_ref()"))?;
// send message
RUNTIME.block_on(async move {
nym_client.send_plain_message(recipient, message).await?;
Ok::<(), anyhow::Error>(())
@@ -70,6 +99,8 @@ pub fn send_message_internal(
Ok(())
}
// TODO send_raw_message_internal
pub fn reply_internal(
recipient: AnonymousSenderTag,
message: &str,
@@ -100,7 +131,10 @@ pub fn listen_for_incoming_internal() -> anyhow::Result<ReconstructedMessage, an
let message = RUNTIME.block_on(async move {
let received = wait_for_non_empty_message(client).await?;
Ok::<ReconstructedMessage, anyhow::Error>(ReconstructedMessage {message: received.message, sender_tag: received.sender_tag})
Ok::<ReconstructedMessage, anyhow::Error>(ReconstructedMessage {
message: received.message,
sender_tag: received.sender_tag,
})
})?;
Ok(message)
@@ -118,3 +152,124 @@ pub async fn wait_for_non_empty_message(
}
bail!("(Rust) did not receive any non-empty message")
}
pub fn proxy_client_new_internal(
server_address: Recipient,
listen_address: &str,
listen_port: &str,
close_timeout: u64,
env: Option<String>,
) -> anyhow::Result<(), anyhow::Error> {
if NYM_PROXY_CLIENT.lock().unwrap().as_ref().is_some() {
bail!("proxy client already exists");
} else {
RUNTIME.block_on(async move {
let init_proxy_client = NymProxyClient::new(
server_address,
listen_address,
listen_port,
close_timeout,
env,
)
.await?;
let mut client = NYM_PROXY_CLIENT.try_lock();
if let Ok(ref mut client) = client {
**client = Some(init_proxy_client);
} else {
return Err(anyhow!("couldnt lock NYM_PROXY_CLIENT"));
}
Ok::<(), anyhow::Error>(())
})?;
}
Ok(())
}
pub fn proxy_client_new_defaults_internal(
server_address: Recipient,
env: Option<String>,
) -> anyhow::Result<(), anyhow::Error> {
if NYM_PROXY_CLIENT.lock().unwrap().as_ref().is_some() {
bail!("proxy client already exists");
} else {
RUNTIME.block_on(async move {
let init_proxy_client = NymProxyClient::new_with_defaults(server_address, env).await?;
let mut client = NYM_PROXY_CLIENT.try_lock();
if let Ok(ref mut client) = client {
**client = Some(init_proxy_client);
} else {
return Err(anyhow!("couldn't lock PROXY_CLIENT"));
}
Ok::<(), anyhow::Error>(())
})?;
}
Ok(())
}
pub fn proxy_client_run_internal() -> anyhow::Result<(), anyhow::Error> {
let proxy_client = NYM_PROXY_CLIENT
.lock()
.expect("could not lock NYM_PROXY_CLIENT");
if proxy_client.is_none() {
bail!("Client is not yet initialised");
}
let proxy = proxy_client
.as_ref()
.ok_or_else(|| anyhow!("could not get proxy_client as_ref()"))?;
RUNTIME.block_on(async move {
proxy.run().await?;
Ok::<(), anyhow::Error>(())
})?;
Ok(())
}
pub fn proxy_server_new_internal(
upstream_address: &str,
config_dir: &str,
env: Option<String>,
) -> anyhow::Result<(), anyhow::Error> {
if NYM_PROXY_SERVER.lock().unwrap().as_ref().is_some() {
bail!("proxy client already exists");
} else {
RUNTIME.block_on(async move {
let init_proxy_server = NymProxyServer::new(upstream_address, config_dir, env).await?;
let mut client = NYM_PROXY_SERVER.try_lock();
if let Ok(ref mut client) = client {
**client = Some(init_proxy_server);
} else {
return Err(anyhow!("couldn't lock PROXY_SERVER"));
}
Ok::<(), anyhow::Error>(())
})?;
}
Ok(())
}
pub fn proxy_server_run_internal() -> anyhow::Result<(), anyhow::Error> {
let mut proxy_server = NYM_PROXY_SERVER
.lock()
.expect("could not lock NYM_PROXY_CLIENT");
if proxy_server.is_none() {
bail!("Server is not yet initialised");
}
let proxy = proxy_server
.as_mut()
.ok_or_else(|| anyhow!("could not get proxy_client as_ref()"))?;
RUNTIME.block_on(async move {
proxy.run_with_shutdown().await?;
Ok::<(), anyhow::Error>(())
})?;
Ok(())
}
pub fn proxy_server_address_internal() -> anyhow::Result<Recipient, anyhow::Error> {
let mut proxy_server = NYM_PROXY_SERVER
.lock()
.expect("could not lock NYM_PROXY_CLIENT");
if proxy_server.is_none() {
bail!("Server is not yet initialised");
}
let proxy = proxy_server
.as_mut()
.ok_or_else(|| anyhow!("could not get proxy_client as_ref()"))?;
Ok(proxy.nym_address().to_owned())
}
+14 -1
View File
@@ -40,12 +40,25 @@ zeroize = { workspace = true }
futures = { workspace = true }
log = { workspace = true }
rand = { workspace = true }
rand = { workspace = true, features = ["small_rng"] }
tap = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true }
toml = { workspace = true }
# tcpproxy dependencies
anyhow.workspace = true
dashmap.workspace = true
tokio.workspace = true
tokio-stream.workspace = true
tokio-util.workspace = true
uuid = { version = "1", features = ["v4", "serde"] }
bincode = "1.0"
serde = { version = "1", features = ["derive"] }
tracing.workspace = true
tracing-subscriber = "0.3"
dirs.workspace = true
[dev-dependencies]
anyhow = { workspace = true }
dotenvy = { workspace = true }
+38
View File
@@ -0,0 +1,38 @@
use futures::StreamExt;
use nym_network_defaults::setup_env;
use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;
// An example of creating a client relying on a testnet, in this case Sandbox.
#[tokio::main]
async fn main() -> anyhow::Result<()> {
nym_bin_common::logging::setup_logging();
// relative root is `sdk/rust/nym-sdk/` for fallback file path
let env_path =
std::env::var("NYM_ENV_PATH").unwrap_or_else(|_| "../../../envs/sandbox.env".to_string());
setup_env(Some(&env_path));
let sandbox_network = mixnet::NymNetworkDetails::new_from_env();
let mixnet_client = mixnet::MixnetClientBuilder::new_ephemeral()
.network_details(sandbox_network)
.build()?;
let mut client = mixnet_client.connect_to_mixnet().await?;
let our_address = client.nym_address();
// Send a message throughout the mixnet to ourselves
client
.send_plain_message(*our_address, "hello there")
.await?;
println!("Waiting for message");
if let Some(received) = client.next().await {
println!("Received: {}", String::from_utf8_lossy(&received.message));
} else {
eprintln!("Failed to receive message.");
}
client.disconnect().await;
Ok(())
}
@@ -0,0 +1,147 @@
use nym_sdk::mixnet::Recipient;
use nym_sdk::tcp_proxy;
use rand::rngs::SmallRng;
use rand::Rng;
use rand::SeedableRng;
use serde::{Deserialize, Serialize};
use std::env;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::signal;
use tokio_stream::StreamExt;
use tokio_util::codec;
#[derive(Serialize, Deserialize, Debug)]
struct ExampleMessage {
message_id: i8,
message_bytes: Vec<u8>,
tcp_conn: i8,
}
// This example just starts off a bunch of Tcp connections on a loop to a remote endpoint: in this case the TcpListener behind the NymProxyServer instance on the echo server found in `nym/tools/echo-server/`. It pipes a few messages to it, logs the replies, and keeps track of the number of replies received per connection.
//
// To run:
// - run the echo server with `cargo run`
// - run this example with `cargo run --example tcp_proxy_multistream -- <ECHO_SERVER_NYM_ADDRESS> <ENV_FILE_PATH> <CLIENT_PORT>` e.g.
// cargo run --example tcp_proxy_multistream -- DMHyxo8n6sKWHHTVvjRVDxDSMX8gYXRU1AQ6UpwsrWiB.6STYCWGWyRxqn2juWdgjMkAMsT9EaAzPpLWq5zkS68MB@CJG5zTcmoLijmDrtAiLV9PZHxNz8LQu6hmgA89V2RxxL ../../../envs/canary.env 8080
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let server_address = env::args().nth(1).expect("Server address not provided");
let server: Recipient =
Recipient::try_from_base58_string(&server_address).expect("Invalid server address");
// Comment this out to just see println! statements from this example.
// Nym client logging is very informative but quite verbose.
// The Message Decay related logging gives you an ideas of the internals of the proxy message ordering: you need to switch
// to DEBUG to see the contents of the msg buffer, sphinx packet chunking, etc.
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
let env_path = env::args().nth(2).expect("Env file not specified");
let env = env_path.to_string();
let listen_port = env::args().nth(3).expect("Port not specified");
// Within the TcpProxyClient, individual client shutdown is triggered by the timeout.
let proxy_client =
tcp_proxy::NymProxyClient::new(server, "127.0.0.1", &listen_port, 45, Some(env)).await?;
tokio::spawn(async move {
proxy_client.run().await?;
Ok::<(), anyhow::Error>(())
});
println!("waiting for everything to be set up..");
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
println!("done. sending bytes");
// In the info traces you will see the different session IDs being set up, one for each TcpStream.
for i in 0..4 {
let conn_id = i;
println!("Starting TCP connection {}", conn_id);
let local_tcp_addr = format!("127.0.0.1:{}", listen_port.clone());
tokio::spawn(async move {
// Now the client and server proxies are running we can create and pipe traffic to/from
// a socket on the same port as our ProxyClient instance as if we were just communicating
// between a client and host via a normal TcpStream - albeit with a decent amount of additional latency.
//
// The assumption regarding integration is that you know what you're sending, and will do proper
// framing before and after, know what data types you're expecting; the proxies are just piping bytes
// back and forth using tokio's `Bytecodec` under the hood.
let stream = TcpStream::connect(local_tcp_addr).await?;
let (read, mut write) = stream.into_split();
// Lets just send a bunch of messages to the server with variable delays between them, with a message and tcp connection ids to keep track of ordering on the server side (for illustrative purposes **only**; keeping track of anonymous replies is handled by the proxy under the hood with Single Use Reply Blocks (SURBs); for this illustration we want some kind of app-level message id, but irl most of the time you'll probably be parsing on e.g. the incoming response type instead)
tokio::spawn(async move {
for i in 0..4 {
let mut rng = SmallRng::from_entropy();
let delay: f64 = rng.gen_range(2.5..5.0);
tokio::time::sleep(tokio::time::Duration::from_secs_f64(delay)).await;
let random_bytes = gen_bytes_fixed(i as usize);
let msg = ExampleMessage {
message_id: i,
message_bytes: random_bytes,
tcp_conn: conn_id,
};
let serialised = bincode::serialize(&msg)?;
write
.write_all(&serialised)
.await
.expect("couldn't write to stream");
println!(
">> client sent {}: {} bytes on conn {}",
&i,
msg.message_bytes.len(),
&conn_id
);
}
Ok::<(), anyhow::Error>(())
});
tokio::spawn(async move {
let mut reply_counter = 0;
let codec = codec::BytesCodec::new();
let mut framed_read = codec::FramedRead::new(read, codec);
while let Some(Ok(bytes)) = framed_read.next().await {
match bincode::deserialize::<ExampleMessage>(&bytes) {
Ok(msg) => {
println!(
"<< client received {}: {} bytes on conn {}",
msg.message_id,
msg.message_bytes.len(),
msg.tcp_conn
);
reply_counter += 1;
println!(
"tcp connection {} replies received {}/4",
msg.tcp_conn, reply_counter
);
}
Err(e) => {
println!("<< client received something that wasn't an example message of {} bytes. error: {}", bytes.len(), e);
}
}
}
});
Ok::<(), anyhow::Error>(())
});
let mut rng = SmallRng::from_entropy();
let delay: f64 = rng.gen_range(4.5..7.0);
tokio::time::sleep(tokio::time::Duration::from_secs_f64(delay)).await;
}
// Once timeout is passed, you can either wait for graceful shutdown or just hard stop it.
signal::ctrl_c().await?;
println!("CTRL+C received, shutting down");
Ok(())
}
// emulate a series of small messages followed by a closing larger one
fn gen_bytes_fixed(i: usize) -> Vec<u8> {
let amounts = [10, 15, 50, 1000];
let len = amounts[i];
let mut rng = rand::thread_rng();
(0..len).map(|_| rng.gen::<u8>()).collect()
}
@@ -0,0 +1,192 @@
use nym_sdk::tcp_proxy;
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::sync::atomic::{AtomicU8, Ordering};
use tokio::io::AsyncWriteExt;
use tokio::net::{TcpListener, TcpStream};
use tokio::signal;
use tokio_stream::StreamExt;
use tokio_util::codec;
#[derive(Serialize, Deserialize, Debug)]
struct ExampleMessage {
message_id: i8,
message_bytes: Vec<u8>,
}
// This is a basic example which opens a single TCP connection and writes a bunch of messages between a client and an echo
// server, so only uses a single session under the hood and doesn't really show off the message ordering capabilities; this is mainly
// just a quick introductory illustration on how:
// - the mixnet does message ordering
// - the NymProxyClient and NymProxyServer can be hooked into and used to communicate between two otherwise pretty vanilla TcpStreams
//
// For a more irl example checkout tcp_proxy_multistream.rs
//
// Run this with:
// `cargo run --example tcp_proxy_single_connection <SERVER_LISTEN_PORT> <ENV_FILE_PATH> <CLIENT_LISTEN_PATH>` e.g.
// `cargo run --example tcp_proxy_single_connection 8081 ../../../envs/canary.env 8080 `
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Keep track of sent/received messages
// let counter = Arc::new(Mutex::new(0));
let counter = AtomicU8::new(0);
// Comment this out to just see println! statements from this example, as Nym client logging is very informative but quite verbose.
// The Message Decay related logging gives you an ideas of the internals of the proxy message ordering. To see the contents of the msg buffer, sphinx packet chunking, etc change the tracing::Level to DEBUG.
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
let server_port = env::args()
.nth(1)
.expect("Server listen port not specified");
let upstream_tcp_addr = format!("127.0.0.1:{}", server_port);
// This dir gets cleaned up at the end: NOTE if you switch env between tests without letting the file do the automatic cleanup, make sure to manually remove this directory up before running again, otherwise your client will attempt to use these keys for the new env
let home_dir = dirs::home_dir().expect("Unable to get home directory");
let conf_path = format!("{}/tmp/nym-proxy-server-config", home_dir.display());
let env_path = env::args().nth(2).expect("Env file not specified");
let env = env_path.to_string();
let client_port = env::args().nth(3).expect("Port not specified");
let mut proxy_server =
tcp_proxy::NymProxyServer::new(&upstream_tcp_addr, &conf_path, Some(env_path.clone()))
.await?;
let proxy_nym_addr = proxy_server.nym_address();
// We'll run the instance with a long timeout since we're sending everything down the same Tcp connection, so should be using a single session.
// Within the TcpProxyClient, individual client shutdown is triggered by the timeout.
let proxy_client =
tcp_proxy::NymProxyClient::new(*proxy_nym_addr, "127.0.0.1", &client_port, 60, Some(env))
.await?;
tokio::spawn(async move {
proxy_server.run_with_shutdown().await?;
Ok::<(), anyhow::Error>(())
});
tokio::spawn(async move {
proxy_client.run().await?;
Ok::<(), anyhow::Error>(())
});
// 'Server side' thread: echo back incoming as response to the messages sent in the 'client side' thread below
tokio::spawn(async move {
let listener = TcpListener::bind(upstream_tcp_addr).await?;
loop {
let (socket, _) = listener.accept().await.unwrap();
let (read, mut write) = socket.into_split();
let codec = codec::BytesCodec::new();
let mut framed_read = codec::FramedRead::new(read, codec);
while let Some(Ok(bytes)) = framed_read.next().await {
match bincode::deserialize::<ExampleMessage>(&bytes) {
Ok(msg) => {
println!(
"<< server received {}: {} bytes",
msg.message_id,
msg.message_bytes.len()
);
let msg = ExampleMessage {
message_id: msg.message_id,
message_bytes: msg.message_bytes,
};
let serialised = bincode::serialize(&msg)?;
write
.write_all(&serialised)
.await
.expect("couldnt send reply");
println!(
">> server sent {}: {} bytes",
msg.message_id,
msg.message_bytes.len()
);
}
Err(e) => {
println!("<< server received something that wasn't an example message of {} bytes. error: {}", bytes.len(), e);
}
}
}
}
#[allow(unreachable_code)]
Ok::<(), anyhow::Error>(())
});
// Just wait for Nym clients to connect, TCP clients to bind, etc.
println!("waiting for everything to be set up..");
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
println!("done. sending bytes");
// Now the client and server proxies are running we can create and pipe traffic to/from
// a socket on the same port as our ProxyClient instance as if we were just communicating
// between a client and host via a normal TcpStream - albeit with a decent amount of additional latency.
//
// The assumption regarding integration is that you know what you're sending, and will do proper
// framing before and after, know what data types you're expecting, etc; the proxies are just piping bytes
// back and forth using tokio's `Bytecodec` under the hood.
let local_tcp_addr = format!("127.0.0.1:{}", client_port);
let stream = TcpStream::connect(local_tcp_addr).await?;
let (read, mut write) = stream.into_split();
// 'Client side' thread; lets just send a bunch of messages to the server with variable delays between them, with an id to keep track of ordering in the printlns; the mixnet only guarantees message delivery, not ordering. You might not be necessarily streaming traffic in this manner IRL, but this example is a good illustration of how messages travel through the mixnet.
// - On the level of individual messages broken into multiple packets, the Proxy abstraction deals with making sure that everything is sent between the sockets in the corrent order.
// - On the level of different messages, this is not enforced: you might see in the logs that message 1 arrives at the server and is reconstructed after message 2.
tokio::spawn(async move {
let mut rng = SmallRng::from_entropy();
for i in 0..10 {
let random_bytes = gen_bytes_fixed(i as usize);
let msg = ExampleMessage {
message_id: i,
message_bytes: random_bytes,
};
let serialised = bincode::serialize(&msg)?;
write
.write_all(&serialised)
.await
.expect("couldn't write to stream");
println!(">> client sent {}: {} bytes", &i, msg.message_bytes.len());
let delay = rng.gen_range(3.0..7.0);
tokio::time::sleep(tokio::time::Duration::from_secs_f64(delay)).await;
}
Ok::<(), anyhow::Error>(())
});
let codec = codec::BytesCodec::new();
let mut framed_read = codec::FramedRead::new(read, codec);
while let Some(Ok(bytes)) = framed_read.next().await {
match bincode::deserialize::<ExampleMessage>(&bytes) {
Ok(msg) => {
println!(
"<< client received {}: {} bytes",
msg.message_id,
msg.message_bytes.len()
);
counter.fetch_add(1, Ordering::SeqCst);
println!(
":: messages received back: {:?}/10",
counter.load(Ordering::SeqCst)
);
}
Err(e) => {
println!("<< client received something that wasn't an example message of {} bytes. error: {}", bytes.len(), e);
}
}
}
// Once timeout is passed, you can either wait for graceful shutdown or just hard stop it.
signal::ctrl_c().await?;
println!(":: CTRL+C received, shutting down + cleanup up proxy server config files");
fs::remove_dir_all(conf_path)?;
Ok(())
}
fn gen_bytes_fixed(i: usize) -> Vec<u8> {
// let amounts = vec![1, 10, 50, 100, 150, 200, 350, 500, 750, 1000];
let amounts = [158, 1088, 505, 1001, 150, 200, 3500, 500, 750, 100];
let len = amounts[i];
let mut rng = rand::thread_rng();
(0..len).map(|_| rng.gen::<u8>()).collect()
}
+2
View File
@@ -1,11 +1,13 @@
//! Rust SDK for the Nym platform
//!
//! The main component currently is [`mixnet`].
//! [`tcp_proxy`] is probably a good place to start for anyone wanting to integrate with existing app code and read/write from a socket.
mod error;
pub mod bandwidth;
pub mod mixnet;
pub mod tcp_proxy;
pub use error::{Error, Result};
pub use nym_client_core::client::mix_traffic::transceiver::*;
+208
View File
@@ -0,0 +1,208 @@
//! Proxy abstractions for interacting with the mixnet like a tcp socket
//!
//!
//! # Basic example
//!
//! ```no_run
//! use bincode;
//! use dirs;
//! use nym_sdk::tcp_proxy;
//! use rand::rngs::SmallRng;
//! use rand::{Rng, SeedableRng};
//! use serde::{Deserialize, Serialize};
//! use std::env;
//! use std::fs;
//! use std::sync::atomic::{AtomicU8, Ordering};
//! use tokio::io::AsyncWriteExt;
//! use tokio::net::{TcpListener, TcpStream};
//! use tokio::signal;
//! use tokio_stream::StreamExt;
//! use tokio_util::codec;
//! use tracing_subscriber;
//!
//! #[derive(Serialize, Deserialize, Debug)]
//! struct ExampleMessage {
//! message_id: i8,
//! message_bytes: Vec<u8>,
//! }
//!
//! // This is a basic example which opens a single TCP connection //! and writes a bunch of messages between a client and an echo
//! // server, so only uses a single session under the hood and //! doesn't really show off the message ordering capabilities; this is mainly
//! // just a quick introductory illustration on how:
//! // - the mixnet does message ordering
//! // - the NymProxyClient and NymProxyServer can be hooked into //! and used to communicate between two otherwise pretty vanilla TcpStreams
//! //
//! // For a more irl example checkout tcp_proxy_multistream.rs
//! //
//! // Run this with:
//! // `cargo run --example tcp_proxy_single_connection <SERVER_LISTEN_PORT> <ENV_FILE_PATH> <CLIENT_LISTEN_PATH>` e.g.
//! // `cargo run --example tcp_proxy_single_connection 8081 ../../../envs/canary.env 8080 `
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! // Keep track of sent/received messages
//! // let counter = Arc::new(Mutex::new(0));
//! let counter = AtomicU8::new(0);
//!
//! // Comment this out to just see println! statements from this example, as Nym client logging is very informative but quite verbose.
//! // The Message Decay related logging gives you an ideas of the internals of the proxy message ordering. To see the contents of the msg buffer, sphinx packet chunking, etc change the tracing::Level to DEBUG.
//! tracing_subscriber::fmt()
//! .with_max_level(tracing::Level::INFO)
//! .init();
//!
//! let server_port = env::args()
//! .nth(1)
//! .expect("Server listen port not specified");
//! let upstream_tcp_addr = format!("127.0.0.1:{}", server_port);
//!
//! // This dir gets cleaned up at the end: NOTE if you switch env between tests without letting the file do the automatic cleanup, make sure to manually remove this directory up before running again, otherwise your client will attempt to use these keys for the new env
//! let home_dir = dirs::home_dir().expect("Unable to get home directory");
//! let conf_path = format!("{}/tmp/nym-proxy-server-config", home_dir.display());
//!
//! let env_path = env::args().nth(2).expect("Env file not specified");
//! let env = env_path.to_string();
//! let client_port = env::args().nth(3).expect("Port not specified");
//!
//! let mut proxy_server =
//! tcp_proxy::NymProxyServer::new(&upstream_tcp_addr, &conf_path, Some(env_path.clone()))
//! .await?;
//! let proxy_nym_addr = proxy_server.nym_address();
//!
//! // We'll run the instance with a long timeout since we're sending everything down the same Tcp connection, so should be using a single session.
//! // Within the TcpProxyClient, individual client shutdown is triggered by the timeout.
//! let proxy_client =
//! tcp_proxy::NymProxyClient::new(*proxy_nym_addr, "127.0.0.1", &client_port, 60, Some(env))
//! .await?;
//!
//! tokio::spawn(async move {
//! let _ = proxy_server.run_with_shutdown().await?;
//! Ok::<(), anyhow::Error>(())
//! });
//!
//! tokio::spawn(async move {
//! let _ = proxy_client.run().await?;
//! Ok::<(), anyhow::Error>(())
//! });
//!
//! // 'Server side' thread: echo back incoming as response to the messages sent in the 'client side' thread below
//! tokio::spawn(async move {
//! let listener = TcpListener::bind(upstream_tcp_addr).await?;
//! loop {
//! let (socket, _) = listener.accept().await.unwrap();
//! let (read, mut write) = socket.into_split();
//! let codec = codec::BytesCodec::new();
//! let mut framed_read = codec::FramedRead::new(read, codec);
//! while let Some(Ok(bytes)) = framed_read.next().await {
//! match bincode::deserialize::<ExampleMessage> (&bytes) {
//! Ok(msg) => {
//! println!(
//! "<< server received {}: {} bytes",
//! msg.message_id,
//! msg.message_bytes.len()
//! );
//! let msg = ExampleMessage {
//! message_id: msg.message_id,
//! message_bytes: msg.message_bytes,
//! };
//! let serialised = bincode::serialize(&msg)?;
//! write
//! .write_all(&serialised)
//! .await
//! .expect("couldnt send reply");
//! println!(
//! ">> server sent {}: {} bytes",
//! msg.message_id,
//! msg.message_bytes.len()
//! );
//! }
//! Err(e) => {
//! println!("<< server received something that wasn't an example message of {} bytes. error: {}", bytes.len(), e);
//! }
//! }
//! }
//! }
//! #[allow(unreachable_code)]
//! Ok::<(), anyhow::Error>(())
//! });
//!
//! // Just wait for Nym clients to connect, TCP clients to bind, etc.
//! println!("waiting for everything to be set up..");
//! tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
//! println!("done. sending bytes");
//!
//! // Now the client and server proxies are running we can create and pipe traffic to/from
//! // a socket on the same port as our ProxyClient instance as if we were just communicating
//! // between a client and host via a normal TcpStream - albeit with a decent amount of additional latency.
//! //
//! // The assumption regarding integration is that you know what you're sending, and will do proper
//! // framing before and after, know what data types you're expecting, etc; the proxies are just piping bytes
//! // back and forth using tokio's `Bytecodec` under the hood.
//! let local_tcp_addr = format!("127.0.0.1:{}", client_port);
//! let stream = TcpStream::connect(local_tcp_addr).await?;
//! let (read, mut write) = stream.into_split();
//!
//! // 'Client side' thread; lets just send a bunch of messages to the server with variable delays between them, with an id to keep track of ordering in the printlns; the mixnet only guarantees message delivery, not ordering. You might not be necessarily streaming traffic in this manner IRL, but this example is a good illustration of how messages travel through the mixnet.
//! // - On the level of individual messages broken into multiple packets, the Proxy abstraction deals with making sure that everything is sent between the sockets in the correct order.
//! // - On the level of different messages, this is not enforced: you might see in the logs that message 1 arrives at the server and is reconstructed after message 2.
//! tokio::spawn(async move {
//! let mut rng = SmallRng::from_entropy();
//! for i in 0..10 {
//! let random_bytes = gen_bytes_fixed(i as usize);
//! let msg = ExampleMessage {
//! message_id: i,
//! message_bytes: random_bytes,
//! };
//! let serialised = bincode::serialize(&msg)?;
//! write
//! .write_all(&serialised)
//! .await
//! .expect("couldn't write to stream");
//! println!(">> client sent {}: {} bytes", &i, msg.message_bytes.len());
//! let delay = rng.gen_range(3.0..7.0);
//! tokio::time::sleep(tokio::time::Duration::from_secs_f64(delay.clone())).await;
//! }
//! Ok::<(), anyhow::Error>(())
//! });
//!
//! let codec = codec::BytesCodec::new();
//! let mut framed_read = codec::FramedRead::new(read, codec);
//! while let Some(Ok(bytes)) = framed_read.next().await {
//! match bincode::deserialize::<ExampleMessage>(&bytes) {
//! Ok(msg) => {
//! println!(
//! "<< client received {}: {} bytes",
//! msg.message_id,
//! msg.message_bytes.len()
//! );
//! counter.fetch_add(1, Ordering::SeqCst);
//! println!(
//! ":: messages received back: {:?}/10",
//! counter.load(Ordering::SeqCst)
//! );
//! }
//! Err(e) => {
//! println!("<< client received something that wasn't an example message of {} bytes. error: {}", bytes.len(), e);
//! }
//! }
//! }
//!
//! // Once timeout is passed, you can either wait for graceful shutdown or just hard stop it.
//! signal::ctrl_c().await?;
//! println!(":: CTRL+C received, shutting down + cleanup up proxy server config files");
//! fs::remove_dir_all(conf_path)?;
//! Ok(())
//! }
//!
//! fn gen_bytes_fixed(i: usize) -> Vec<u8> {
//! // let amounts = vec![1, 10, 50, 100, 150, 200, 350, 500, 750, 1000];
//! let amounts = vec![158, 1088, 505, 1001, 150, 200, 3500, 500, 750, 100];
//! let len = amounts[i];
//! let mut rng = rand::thread_rng();
//! (0..len).map(|_| rng.gen::<u8>()).collect()
//! }
//! ```
mod tcp_proxy_client;
mod tcp_proxy_server;
pub use tcp_proxy_client::NymProxyClient;
pub use tcp_proxy_server::NymProxyServer;
@@ -0,0 +1,228 @@
use crate::mixnet::{IncludedSurbs, MixnetClientBuilder, MixnetMessageSender, NymNetworkDetails};
use std::sync::Arc;
#[path = "utils.rs"]
mod utils;
use anyhow::Result;
use dashmap::DashSet;
use nym_network_defaults::setup_env;
use nym_sphinx::addressing::Recipient;
use tokio::{
net::{TcpListener, TcpStream},
sync::oneshot,
};
use tokio_stream::StreamExt;
use tokio_util::codec::{BytesCodec, FramedRead};
use tracing::{debug, info, instrument, warn};
use utils::{MessageBuffer, Payload, ProxiedMessage};
const DEFAULT_CLOSE_TIMEOUT: u64 = 60;
const DEFAULT_LISTEN_HOST: &str = "127.0.0.1";
const DEFAULT_LISTEN_PORT: &str = "8080";
pub struct NymProxyClient {
server_address: Recipient,
listen_address: String,
listen_port: String,
close_timeout: u64,
}
impl NymProxyClient {
pub async fn new(
server_address: Recipient,
listen_address: &str,
listen_port: &str,
close_timeout: u64,
env: Option<String>,
) -> Result<Self> {
debug!("loading env file: {:?}", env);
setup_env(env);
Ok(NymProxyClient {
server_address,
listen_address: listen_address.to_string(),
listen_port: listen_port.to_string(),
close_timeout,
})
}
// server_address is the Nym address of the NymProxyServer to communicate with.
pub async fn new_with_defaults(server_address: Recipient, env: Option<String>) -> Result<Self> {
debug!("loading env file: {:?}", env);
setup_env(env); // Defaults to mainnet if empty
Ok(NymProxyClient {
server_address,
listen_address: DEFAULT_LISTEN_HOST.to_string(),
listen_port: DEFAULT_LISTEN_PORT.to_string(),
close_timeout: DEFAULT_CLOSE_TIMEOUT,
})
}
pub async fn run(&self) -> Result<()> {
info!("Connecting to mixnet server at {}", self.server_address);
let listener =
TcpListener::bind(format!("{}:{}", self.listen_address, self.listen_port)).await?;
loop {
let (stream, _) = listener.accept().await?;
tokio::spawn(NymProxyClient::handle_incoming(
stream,
self.server_address,
self.close_timeout,
));
}
}
// The main body of our logic, triggered on each accepted incoming tcp connection. To deal with assumptions about
// streaming we have to implement an abstract session for each set of outgoing messages atop each connection, with message
// IDs to deal with the fact that the mixnet does not enforce message ordering.
//
// There is an initial thread which does a bunch of setup logic
// - Create a random session ID
// - Create a Nym Client (and split into read/write clients for concurrent read/write)
// - Split incoming TcpStream into OwnedReadHalf and OwnedWriteHalf for concurrent read/write
//
// Then we spawn 2 tasks:
// - 'Outgoing' thread => frames incoming bytes from OwnedReadHalf and pipe through the mixnet & trigger session close.
// - 'Incoming' thread => orders incoming messages from the Mixnet via placing them in a MessageBuffer and using tick(), as well as manage session closing.
#[instrument]
async fn handle_incoming(
stream: TcpStream,
server_address: Recipient,
close_timeout: u64,
) -> Result<()> {
// ID for creation of session abstraction; new session ID per new connection accepted by our tcp listener above.
let session_id = uuid::Uuid::new_v4();
// Used to communicate end of session between 'Outgoing' and 'Incoming' tasks
let (tx, mut rx) = oneshot::channel();
// Client creation can fail for multiple reasons like bad network connection: this loop just allows us to
// retry in a loop until we can successfully connect without having to restart the entire function
info!(":: Starting session: {}", session_id);
info!(":: creating client...");
let mut client = loop {
let net = NymNetworkDetails::new_from_env();
// TODO change to builder but ephemeral
// match MixnetClient::connect_new().await {
match MixnetClientBuilder::new_ephemeral()
.network_details(net)
.build()?
.connect_to_mixnet()
.await
{
Ok(client) => break client,
Err(err) => {
warn!(":: Error creating client: {:?}, will retry in 100ms", err);
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
};
let client_addr = &client.nym_address();
info!(":: client created: {}", &client_addr);
// Split our tcpstream into OwnedRead and OwnedWrite halves for concurrent read/writing
let (read, mut write) = stream.into_split();
// Since we're just trying to pipe whatever bytes our client/server are normally sending to each other,
// the bytescodec is fine to use here; we're trying to avoid modifying this stream e.g. in the process of Sphinx packet
// creation and adding padding to the payload whilst also sidestepping the need to manually manage an intermediate buffer of the
// incoming bytes from the tcp stream and writing them to our server with our Nym client.
let codec = BytesCodec::new();
let mut framed_read = FramedRead::new(read, codec);
// Much like the tcpstream, split our Nym client into a sender and receiver for concurrent read/write
let sender = client.split_sender();
// The server / service provider address our client is sending messages to will remain static
let server_addr = server_address;
// Store outgoing messages in instance of Dashset abstraction
let messages_account = Arc::new(DashSet::new());
// Wrap in an Arc for memsafe concurrent access
let sent_messages_account = Arc::clone(&messages_account);
// 'Outgoing' thread
tokio::spawn(async move {
let mut message_id = 0;
// While able to read from OwnedReadHalf of TcpStream:
// - increment our messageID - we need to ensure message ordering on both client and server.
// - create instance of ProxiedMessage abstraction with framed bytes: this is really just the message data payload in the form of those bytes
// & session and messageIDs.
// - Serialise + send message through the mixnet to the Service Provider.
// - Repeat these steps, but sending a message with a payload containing a Close signal for this session; since we have message ordering implemented
// we can fire off the session close signal without having to wait on making sure the server has received the rest of the messages.
// - Trigger our session timeout alert in the 'Incoming' thread select! loop via tx end of our oneshot channel.
while let Some(Ok(bytes)) = framed_read.next().await {
message_id += 1;
sent_messages_account.insert(message_id);
let message =
ProxiedMessage::new(Payload::Data(bytes.to_vec()), session_id, message_id);
let coded_message = bincode::serialize(&message)?;
sender
.send_message(server_addr, &coded_message, IncludedSurbs::Amount(100))
.await?;
info!(
"Sent message with id {} for session {} of {} bytes",
message_id,
session_id,
bytes.len()
);
}
message_id += 1;
let message = ProxiedMessage::new(Payload::Close, session_id, message_id);
let coded_message = bincode::serialize(&message)?;
sender
.send_message(server_addr, &coded_message, IncludedSurbs::Amount(100))
.await?;
info!(":: Closing read end of session: {}", session_id);
tx.send(true)
.map_err(|_| anyhow::anyhow!("Could not send close signal"))?;
Ok::<(), anyhow::Error>(())
});
// 'Incoming' thread
tokio::spawn(async move {
// Abstraction containing logic ordering: all our incoming messages need to be parsed based on their messageIDs per session.
// All the message-ordering and time-tracking methods are defined in utils.rs, mostly used in .tick().
let mut msg_buffer = MessageBuffer::new();
// Select!-ing one of following options:
// - rx is triggered by tx to log the session will end in ARGS.close_timeout time, break from this loop to pass to loop below
// - Deserialise incoming mixnet message, push to msg buffer and tick() to order and write to OwnedWriteHalf.
// - call tick() once per 100ms if neither of the above have occurred.
loop {
tokio::select! {
_ = &mut rx => {
info!(":: Closing write end of session: {} in {} seconds", session_id, close_timeout);
break
}
Some(message) = client.next() => {
let message = bincode::deserialize::<ProxiedMessage>(&message.message)?;
msg_buffer.push(message);
msg_buffer.tick(&mut write).await?;
},
_ = tokio::time::sleep(tokio::time::Duration::from_millis(100)) => {
msg_buffer.tick(&mut write).await?;
}
}
}
// Select!-ing one of following options:
// - Deserialise incoming mixnet message, push to msg buffer and tick() to order and write next messageID in line to OwnedWriteHalf.
// - Sleep for session timeout and return, kills thread with Ok(()).
loop {
tokio::select! {
Some(message) = client.next() => {
let message = bincode::deserialize::<ProxiedMessage>(&message.message)?;
msg_buffer.push(message);
msg_buffer.tick(&mut write).await?;
},
_ = tokio::time::sleep(tokio::time::Duration::from_secs(close_timeout)) => {
info!(":: Closing write end of session: {}", session_id);
info!(":: Triggering client shutdown");
client.disconnect().await;
return Ok::<(), anyhow::Error>(())
}
}
}
});
tokio::signal::ctrl_c().await?;
Ok(())
}
}
@@ -0,0 +1,229 @@
use crate::mixnet::{
AnonymousSenderTag, MixnetClient, MixnetClientBuilder, MixnetClientSender, MixnetMessageSender,
NymNetworkDetails, StoragePaths,
};
use anyhow::Result;
use dashmap::DashSet;
use nym_network_defaults::setup_env;
use nym_sphinx::addressing::Recipient;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::sync::watch::Receiver;
use tokio::sync::RwLock;
use tokio_stream::StreamExt;
use tracing::{debug, error, info};
#[allow(clippy::duplicate_mod)]
#[path = "utils.rs"]
mod utils;
use utils::{MessageBuffer, Payload, ProxiedMessage};
use uuid::Uuid;
pub struct NymProxyServer {
upstream_address: String,
session_map: DashSet<Uuid>,
mixnet_client: MixnetClient,
mixnet_client_sender: Arc<RwLock<MixnetClientSender>>,
tx: tokio::sync::watch::Sender<Option<(ProxiedMessage, AnonymousSenderTag)>>,
rx: tokio::sync::watch::Receiver<Option<(ProxiedMessage, AnonymousSenderTag)>>,
}
impl NymProxyServer {
pub async fn new(
upstream_address: &str,
config_dir: &str,
env: Option<String>,
) -> Result<Self> {
info!(":: creating client...");
// We're wanting to build a client with a constant address, vs the ephemeral in-memory data storage of the NymProxyClient clients.
// Following a builder pattern, having to manually connect to the mixnet below.
let config_dir = PathBuf::from(config_dir);
debug!("loading env file: {:?}", env);
setup_env(env); // Defaults to mainnet if empty
let net = NymNetworkDetails::new_from_env();
let storage_paths = StoragePaths::new_from_dir(&config_dir)?;
let client = MixnetClientBuilder::new_with_default_storage(storage_paths)
.await?
.network_details(net)
.build()?;
let client = client.connect_to_mixnet().await?;
// Since we're splitting the client in the main thread, we have to wrap the sender side of the client in an Arc<RwLock>>.
let sender = Arc::new(RwLock::new(client.split_sender()));
// Used for passing the incoming Mixnet message => session_handler().
let (tx, rx) =
tokio::sync::watch::channel::<Option<(ProxiedMessage, AnonymousSenderTag)>>(None);
info!(":: client created: {}", client.nym_address());
Ok(NymProxyServer {
upstream_address: upstream_address.to_string(),
session_map: DashSet::new(),
mixnet_client: client,
mixnet_client_sender: sender,
tx,
rx,
})
}
pub fn nym_address(&self) -> &Recipient {
self.mixnet_client.nym_address()
}
pub fn mixnet_client_mut(&mut self) -> &mut MixnetClient {
&mut self.mixnet_client
}
pub fn session_map(&self) -> &DashSet<Uuid> {
&self.session_map
}
pub fn mixnet_client_sender(&self) -> Arc<RwLock<MixnetClientSender>> {
Arc::clone(&self.mixnet_client_sender)
}
pub fn tx(&self) -> tokio::sync::watch::Sender<Option<(ProxiedMessage, AnonymousSenderTag)>> {
self.tx.clone()
}
pub fn rx(&self) -> tokio::sync::watch::Receiver<Option<(ProxiedMessage, AnonymousSenderTag)>> {
self.rx.clone()
}
// The main body of our logic, triggered on each received new sessionID. To deal with assumptions about
// streaming we have to implement an abstract session for each set of outgoing messages atop each connection, with message
// IDs to deal with the fact that the mixnet does not enforce message ordering.
//
// There is an initial thread which does a bunch of setup logic:
// - Create a TcpStream connecting to our upstream server process.
// - Split incoming TcpStream into OwnedReadHalf and OwnedWriteHalf for concurrent read/write.
// - Create an Arc to store our session SURB - used for anonymous replies.
//
// Then we spawn 2 tasks:
// - 'Incoming' thread => deals with parsing and storing the SURB (used in Mixnet replies), deserialising and passing the incoming data from the Mixnet to the upstream server.
// - 'Outgoing' thread => frames bytes coming from TcpStream (the server) and deals with ordering + sending reply anonymously => Mixnet.
async fn session_handler(
upstream_address: String,
session_id: Uuid,
mut rx: Receiver<Option<(ProxiedMessage, AnonymousSenderTag)>>,
sender: Arc<RwLock<MixnetClientSender>>,
) -> Result<()> {
let global_surb = Arc::new(RwLock::new(None));
let stream = TcpStream::connect(upstream_address).await?;
// Split our tcpstream into OwnedRead and OwnedWrite halves for concurrent read/writing.
let (read, mut write) = stream.into_split();
// Used for anonymous replies per session. Initially parsed from the incoming message.
let send_side_surb = Arc::clone(&global_surb);
tokio::spawn(async move {
let mut message_id = 0;
// Since we're just trying to pipe whatever bytes our client/server are normally sending to each other,
// the bytescodec is fine to use here; we're trying to avoid modifying this stream e.g. in the process of Sphinx packet
// creation and adding padding to the payload whilst also sidestepping the need to manually manage an intermediate buffer of the
// incoming bytes from the tcp stream and writing them to our server with our Nym client.
let codec = tokio_util::codec::BytesCodec::new();
let mut framed_read = tokio_util::codec::FramedRead::new(read, codec);
// While able to read from OwnedReadHalf of TcpStream:
// - Keep track of outgoing messageIDs.
// - Read and store incoming SURB.
// - Send serialised reply => Mixnet via SURB.
// - If tick() returns true, close session.
while let Some(Ok(bytes)) = framed_read.next().await {
info!("server received {} bytes", bytes.len());
let reply =
ProxiedMessage::new(Payload::Data(bytes.to_vec()), session_id, message_id);
message_id += 1;
let surb = send_side_surb.read().await;
if let Some(surb) = *surb {
sender
.write()
.await
.send_reply(surb, bincode::serialize(&reply)?)
.await?
}
info!(
"Sent reply with id {} for session {}",
message_id, session_id
);
}
Ok::<(), anyhow::Error>(())
});
let messages_accounter = Arc::new(DashSet::new());
messages_accounter.insert(1);
let mut msg_buffer = MessageBuffer::new();
loop {
tokio::select! {
_ = rx.changed() => {
let value = rx.borrow_and_update().clone();
if let Some((message, surb)) = value {
if message.session_id() != session_id {
continue;
}
msg_buffer.push(message);
let local_surb = Arc::clone(&global_surb);
{
*local_surb.write().await = Some(surb);
}
let should_close = msg_buffer.tick(&mut write).await?;
if should_close {
info!(":: Closing write end of session: {}", session_id);
break;
}
}
}
_ = tokio::time::sleep(tokio::time::Duration::from_millis(100)) => {
msg_buffer.tick(&mut write).await?;
}
}
}
// This times out after 60 seconds by default.
#[allow(unreachable_code)]
Ok(())
}
pub async fn run_with_shutdown(&mut self) -> Result<()> {
// On our Mixnet client getting a new message:
// - Check if the attached sessionID exists.
// - If !sessionID, spawn a new session_handler() task.
// - Send the message down tx => rx in our handler.
while let Some(new_message) = &self.mixnet_client_mut().next().await {
let message: ProxiedMessage = bincode::deserialize(&new_message.message)?;
let session_id = message.session_id();
// If we've already got message from an existing session, continue, else add it to the session mapping and spawn a new handler().
if self.session_map().contains(&message.session_id()) {
debug!("Got message for an existing session");
} else {
self.session_map().insert(message.session_id());
debug!("Got message for a new session");
tokio::spawn(Self::session_handler(
self.upstream_address.clone(),
session_id,
self.rx(),
self.mixnet_client_sender(),
));
info!("Spawned a new session handler: {}", message.session_id());
}
debug!("Sending message for session {}", message.session_id());
if let Some(sender_tag) = new_message.sender_tag {
self.tx.send(Some((message, sender_tag)))?
} else {
error!("No sender tag found, we can't send a reply without it!")
}
}
tokio::signal::ctrl_c().await?;
Ok(())
}
}
+196
View File
@@ -0,0 +1,196 @@
use std::{collections::HashSet, fmt, ops::Deref, time::Instant};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use tokio::{io::AsyncWriteExt as _, net::tcp::OwnedWriteHalf};
use tracing::{debug, info};
use uuid::Uuid;
// Keeps track of
// - incoming and unsorted messages wrapped in DecayWrapper for keeping track of when they were received
// - the expected next message ID (reset on .tick())
#[derive(Debug)]
pub struct MessageBuffer {
buffer: Vec<DecayWrapper<ProxiedMessage>>,
next_msg_id: u16,
}
impl MessageBuffer {
pub fn new() -> Self {
MessageBuffer {
buffer: Vec::new(),
next_msg_id: 0,
}
}
pub fn push(&mut self, msg: ProxiedMessage) {
self.buffer.push(DecayWrapper::new(msg));
}
pub fn retain<F>(&mut self, f: F)
where
F: FnMut(&DecayWrapper<ProxiedMessage>) -> bool,
{
self.buffer.retain(f);
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.buffer.len()
}
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub fn iter(&self) -> std::slice::Iter<DecayWrapper<ProxiedMessage>> {
self.buffer.iter()
}
// Used by the client to create and manipulate a buffer of messages to write => OwnedWriteHalf.
// Used by the server for this + to conditionally decide whether to kill a session on returning true.
// #[instrument]
pub async fn tick(&mut self, write: &mut OwnedWriteHalf) -> Result<bool> {
if self.is_empty() {
return Ok(false);
}
debug!("Messages in buffer:");
for msg in self.iter() {
debug!("{}", msg.inner());
}
// Iterate over self, filtering messages where msg.decayed() = true (aka message is older than 2 seconds), or where msg.message_id is less than next_msg_id. Then collect and order according to message_id.
let mut send_buffer = self
.iter()
.filter(|msg| msg.decayed() || msg.message_id() <= self.next_msg_id)
.map(|msg| msg.inner())
.collect::<Vec<&ProxiedMessage>>();
send_buffer.sort_by(|a, b| a.message_id.cmp(&b.message_id()));
if send_buffer.is_empty() {
debug!("send buf is empty");
return Ok(false);
}
let mut sent_messages = HashSet::new();
// Send collected & ordered messages down OwnedReadHalf until matching on Close enum, in which case exit & cause server to start session shutdown.
for msg in send_buffer {
match &msg.message() {
Payload::Data(data) => {
write.write_all(data).await?;
info!("Wrote message {} to stream", msg.message_id())
}
Payload::Close => {
return Ok(true);
}
}
// Store sent messages in hashmap.
sent_messages.insert(msg.message_id());
}
// Iterate through sent, find the largest message ID and add 1, set this as expected next message ID.
self.next_msg_id = sent_messages
.iter()
.max()
.expect("This is safe since we know we've set something")
+ 1;
// Retain as next_msg_id in MessageBuffer instance for parsing potential further incoming msgs.
self.retain(|msg| !sent_messages.contains(&msg.inner().message_id()));
info!("next_msg_id is: {}", self.next_msg_id.clone());
Ok(false)
}
}
// Wrapper used for tracking the 'age' of a message from when it was received.
// Used in the ordering logic in MessageBuffer.tick().
#[derive(Debug)]
pub struct DecayWrapper<T> {
value: T,
start: Instant,
decay: u64,
}
impl<T> DecayWrapper<T> {
pub fn decayed(&self) -> bool {
debug!("Decayed: {:?}", self.start.elapsed().as_secs() > self.decay);
self.start.elapsed().as_secs() > self.decay
}
pub fn new(value: T) -> Self {
DecayWrapper {
value,
start: Instant::now(),
decay: 6,
}
}
#[allow(dead_code)]
pub fn into_inner(self) -> T {
self.value
}
pub fn inner(&self) -> &T {
&self.value
}
}
impl<T> Deref for DecayWrapper<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ProxiedMessage {
message: Payload,
session_id: Uuid,
message_id: u16,
}
impl ProxiedMessage {
pub fn new(message: Payload, session_id: Uuid, message_id: u16) -> Self {
ProxiedMessage {
message,
session_id,
message_id,
}
}
pub fn message(&self) -> &Payload {
&self.message
}
pub fn session_id(&self) -> Uuid {
self.session_id
}
pub fn message_id(&self) -> u16 {
self.message_id
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum Payload {
Data(Vec<u8>),
Close,
}
impl fmt::Display for ProxiedMessage {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let message = match self.message() {
Payload::Data(ref data) => format!("Data({})", data.len()),
Payload::Close => "Close".to_string(),
};
write!(
f,
"ProxiedMessage {{ message: {}, session_id: {}, message_id: {} }}",
message,
self.session_id(),
self.message_id()
)
}
}
@@ -4,7 +4,7 @@
[package]
name = "nym-network-requester"
license = "GPL-3.0"
version = "1.1.41"
version = "1.1.42"
authors.workspace = true
edition.workspace = true
rust-version = "1.70"
+26
View File
@@ -0,0 +1,26 @@
[package]
name = "echo-server"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
anyhow.workspace = true
dashmap.workspace = true
tokio = { workspace = true, features = ["full"] }
tokio-stream.workspace = true
tokio-util.workspace = true
uuid = { version = "1", features = ["v4", "serde"] }
bincode = "1.0"
serde = { version = "1", features = ["derive"] }
tracing.workspace = true
tracing-subscriber = "0.3"
bytecodec = { workspace = true }
nym-sdk = { path = "../../sdk/rust/nym-sdk/" }
bytes.workspace = true
dirs.workspace = true
+9
View File
@@ -0,0 +1,9 @@
# Nym Echo Server
This is an initial minimal implementation of an echo server built using the `NymProxyServer` Rust SDK abstraction.
## Usage
```
cargo build --release
../../target/release/echo-server <PORT> <PATH_TO_ENV_FILE> e.g. ../../target/release/echo-server 9000 ../../envs/canary.env
```
+161
View File
@@ -0,0 +1,161 @@
use anyhow::Result;
use bytes::Bytes;
use nym_sdk::tcp_proxy;
use std::env;
use std::fs;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use tokio::io::AsyncWriteExt;
use tokio::net::{TcpListener, TcpStream};
use tokio::signal;
use tokio::sync::broadcast;
use tokio::task;
use tokio_stream::StreamExt;
use tracing::{error, info, warn};
struct Metrics {
total_conn: AtomicU64,
active_conn: AtomicU64,
bytes_recv: AtomicU64,
bytes_sent: AtomicU64,
}
impl Metrics {
fn new() -> Self {
Self {
total_conn: AtomicU64::new(0),
active_conn: AtomicU64::new(0),
bytes_recv: AtomicU64::new(0),
bytes_sent: AtomicU64::new(0),
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
// if you run this with DEBUG you see the msg buffer on the ProxyServer, but its quite chatty
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
let server_port = env::args()
.nth(1)
.expect("Server listen port not specified");
let tcp_addr = format!("127.0.0.1:{}", server_port);
// This dir gets cleaned up at the end: NOTE if you switch env between tests without letting the file do the automatic cleanup, make sure to manually remove this directory up before running again, otherwise your client will attempt to use these keys for the new env
let home_dir = dirs::home_dir().expect("Unable to get home directory");
let conf_path = format!("{}/tmp/nym-proxy-server-config", home_dir.display());
let env_path = env::args().nth(2).expect("Env file not specified");
let env = env_path.to_string();
let mut proxy_server = tcp_proxy::NymProxyServer::new(&tcp_addr, &conf_path, Some(env.clone()))
.await
.unwrap();
let proxy_nym_addr = *proxy_server.nym_address();
info!("ProxyServer listening out on {}", proxy_nym_addr);
task::spawn(async move {
proxy_server.run_with_shutdown().await?;
Ok::<(), anyhow::Error>(())
});
let (shutdown_sender, _) = broadcast::channel(1);
let metrics = Arc::new(Metrics::new());
let all_metrics = Arc::clone(&metrics);
tokio::spawn(async move {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
info!(
"Metrics: total_connections={}, active_connections={}, bytes_received={}, bytes_sent={}",
all_metrics.total_conn.load(Ordering::Relaxed),
all_metrics.active_conn.load(Ordering::Relaxed),
all_metrics.bytes_recv.load(Ordering::Relaxed),
all_metrics.bytes_sent.load(Ordering::Relaxed),
);
}
});
let listener = TcpListener::bind(tcp_addr).await?;
loop {
tokio::select! {
_ = signal::ctrl_c() => {
info!("Shutdown signal received, closing server...");
let _ = shutdown_sender.send(());
// TODO we need something like this for the ProxyServer client
break;
}
Ok((socket, _)) = listener.accept() => {
let connection_metrics = Arc::clone(&metrics);
let shutdown_rx = shutdown_sender.subscribe();
connection_metrics.total_conn.fetch_add(1, Ordering::Relaxed);
connection_metrics.active_conn.fetch_add(1, Ordering::Relaxed);
tokio::spawn(async move {
handle_incoming(socket, connection_metrics, shutdown_rx).await;
});
}
}
}
signal::ctrl_c().await?;
info!("Received CTRL+C");
fs::remove_dir_all(conf_path)?;
while metrics.active_conn.load(Ordering::Relaxed) > 0 {
info!("Waiting on active connections to close: sleeping 100ms");
// TODO some kind of hard kill here for the ProxyServer
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
Ok(())
}
async fn handle_incoming(
socket: TcpStream,
metrics: Arc<Metrics>,
mut shutdown_rx: broadcast::Receiver<()>,
) {
let (read, mut write) = socket.into_split();
let codec = tokio_util::codec::BytesCodec::new();
let mut framed_read = tokio_util::codec::FramedRead::new(read, codec);
loop {
tokio::select! {
Some(result) = framed_read.next() => {
match result {
Ok(bytes) => {
let len = bytes.len();
metrics.bytes_recv.fetch_add(len as u64, Ordering::Relaxed);
if let Err(e) = write.write_all(&bytes).await {
error!("Failed to write to stream with err: {}", e);
break;
}
metrics.bytes_sent.fetch_add(len as u64, Ordering::Relaxed);
}
Err(e) => {
error!("Failed to read from stream with err: {}", e);
break;
}
}
}
_ = shutdown_rx.recv() => {
warn!("Shutdown signal received, closing connection");
break;
}
// TODO need to work out a way that if this timesout and breaks but you dont hang up the conn on the client end you can reconnect..maybe. If we just use this as a ping echo server I dont think this is a problem
// EDIT I'm not actually sure we want this functionality? Measuring active connections might be useful though
_ = tokio::time::sleep(tokio::time::Duration::from_secs(120)) => {
info!("Timeout reached, assuming we wont get more messages on this conn, closing");
let close_message = "Closing conn, reconnect if you want to ping again";
let bytes: Bytes = close_message.into();
write.write_all(&bytes).await.expect("Couldn't write to socket");
break;
}
}
}
metrics
.active_conn
.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
info!("Connection closed");
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-cli"
version = "1.1.41"
version = "1.1.42"
authors.workspace = true
edition = "2021"
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nymvisor"
version = "0.1.6"
version = "0.1.7"
authors.workspace = true
repository.workspace = true
homepage.workspace = true