first pass rust sdk
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"index": "Introduction",
|
||||
"concepts": "Core Concepts",
|
||||
"examples": "Basic Examples",
|
||||
"message-helpers": "Message Helpers",
|
||||
"message-types": "Message Types",
|
||||
"examples": "Examples",
|
||||
"troubleshooting": "Troubleshooting"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"overview": "Overview",
|
||||
"messages": "Message Based Paradigm",
|
||||
"abstractions": "Connection Abstractions"
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
# Examples
|
||||
|
||||
All the following examples can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/master/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there with:
|
||||
All the following examples can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/master/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there with:
|
||||
|
||||
```sh
|
||||
```sh
|
||||
cargo run --example <NAME_OF_FILE>
|
||||
```
|
||||
|
||||
If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the `sdk`, `tokio`, and `nym_bin_common` crates.
|
||||
|
||||
An example `Cargo.toml` file can be found [here](examples/cargo.md).
|
||||
|
||||
If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the `sdk`, `tokio`, and `nym_bin_common` crates.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"simple": "Simple Send",
|
||||
"builders": "Builder Patterns",
|
||||
"custom-topology": "Custom Network Topologies",
|
||||
"split-send": "Concurrent Send & Receive",
|
||||
"socks": "Socks Proxy",
|
||||
"storage": "Manually Handle Storage",
|
||||
"surbs": "Anonymous Replies"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"builder": "Ephemeral",
|
||||
"builder-with-storage": "With Storage"
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
# Mixnet Client Builder with Storage
|
||||
|
||||
The previous example involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder_with_storage.rs).
|
||||
|
||||
```rust
|
||||
use nym_sdk::mixnet;
|
||||
use nym_sdk::mixnet::MixnetMessageSender;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
nym_bin_common::logging::setup_logging();
|
||||
|
||||
// Specify some config options
|
||||
let config_dir = PathBuf::from("/tmp/mixnet-client");
|
||||
let storage_paths = mixnet::StoragePaths::new_from_dir(&config_dir).unwrap();
|
||||
|
||||
// Create the client with a storage backend, and enable it by giving it some paths. If keys
|
||||
// exists at these paths, they will be loaded, otherwise they will be generated.
|
||||
let client = mixnet::MixnetClientBuilder::new_with_default_storage(storage_paths)
|
||||
.await
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// Now we connect to the mixnet, using keys now stored in the paths provided.
|
||||
let mut client = client.connect_to_mixnet().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = client.nym_address();
|
||||
println!("Our client nym address is: {our_address}");
|
||||
|
||||
// Send a message throught the mixnet to ourselves
|
||||
client
|
||||
.send_plain_message(*our_address, "hello there")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("Waiting for message");
|
||||
if let Some(received) = client.wait_for_messages().await {
|
||||
for r in received {
|
||||
println!("Received: {}", String::from_utf8_lossy(&r.message));
|
||||
}
|
||||
}
|
||||
|
||||
client.disconnect().await;
|
||||
}
|
||||
```
|
||||
|
||||
As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple.
|
||||
|
||||
Assuming our client config is stored in `/tmp/mixnet-client`, the following files are generated:
|
||||
```
|
||||
$ tree /tmp/mixnet-client
|
||||
|
||||
mixnet-client
|
||||
├── ack_key.pem
|
||||
├── db.sqlite
|
||||
├── db.sqlite-shm
|
||||
├── db.sqlite-wal
|
||||
├── gateway_details.json
|
||||
├── gateway_shared.pem
|
||||
├── persistent_reply_store.sqlite
|
||||
├── private_encryption.pem
|
||||
├── private_identity.pem
|
||||
├── public_encryption.pem
|
||||
└── public_identity.pem
|
||||
|
||||
1 directory, 11 files
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
# Mixnet Client Builder
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder.rs).
|
||||
|
||||
```rust
|
||||
use nym_sdk::mixnet;
|
||||
use nym_sdk::mixnet::MixnetMessageSender;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
nym_bin_common::logging::setup_logging();
|
||||
|
||||
// Create client builder, including ephemeral keys. The builder can be usable in the context
|
||||
// where you don't want to connect just yet.
|
||||
let client = mixnet::MixnetClientBuilder::new_ephemeral()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// Now we connect to the mixnet, using ephemeral keys already created
|
||||
let mut client = client.connect_to_mixnet().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = client.nym_address();
|
||||
println!("Our client nym address is: {our_address}");
|
||||
|
||||
// Send a message through the mixnet to ourselves
|
||||
client
|
||||
.send_plain_message(*our_address, "hello there")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("Waiting for message");
|
||||
if let Some(received) = client.wait_for_messages().await {
|
||||
for r in received {
|
||||
println!("Received: {}", String::from_utf8_lossy(&r.message));
|
||||
}
|
||||
}
|
||||
|
||||
client.disconnect().await;
|
||||
}
|
||||
```
|
||||
@@ -1,35 +0,0 @@
|
||||
# Example Cargo File
|
||||
This file imports the basic requirements for running these pieces of example code, and can be used as the basis for your own cargo project.
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "your_app"
|
||||
version = "x.y.z"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# Async runtime
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
|
||||
# Used for (de)serialising incoming and outgoing messages
|
||||
serde = "1.0.152"
|
||||
serde_json = "1.0.91"
|
||||
# Nym clients, addressing, etc
|
||||
nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" }
|
||||
nym-sphinx-addressing = { 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" }
|
||||
# Additional dependencies if you're interacting with Nyx or another Cosmos SDK blockchain
|
||||
cosmrs = "=0.14.0"
|
||||
nym-validator-client = { git = "https://github.com/nymtech/nym", branch = "master" }
|
||||
|
||||
# If you're building an app with a client and server / serivce this might be a useful structure for your repo
|
||||
[[bin]]
|
||||
name = "client"
|
||||
path = "bin/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "service"
|
||||
path = "bin/service.rs"
|
||||
```
|
||||
@@ -1,8 +0,0 @@
|
||||
# Coconut credential generation
|
||||
The following code shows how you can use the SDK to create and use a credential representing paid bandwidth on the Sandbox testnet.
|
||||
|
||||
```rust,noplayground
|
||||
{{#include ../../../../../../sdk/rust/nym-sdk/examples/bandwidth.rs}}
|
||||
```
|
||||
|
||||
You can read more about Coconut credentials (also referred to as `zk-Nym`) [here](https://nymtech.net/docs/coconut.html).
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"custom-network": "Different Options",
|
||||
"custom-provider": "Custom Topology Provider",
|
||||
"manual-topology": "Manually Overwrite Topology"
|
||||
}
|
||||
+5
-12
@@ -1,18 +1,11 @@
|
||||
# Importing and using a custom network topology
|
||||
If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or performing research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html) (`examples/custom_topology_provider.rs`).
|
||||
If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or performing research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html).
|
||||
|
||||
|
||||
There are two ways to do this:
|
||||
|
||||
## Import a custom Nym API endpoint
|
||||
If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood):
|
||||
|
||||
```rust,noplayground
|
||||
{{#include ../../../../../../sdk/rust/nym-sdk/examples/custom_topology_provider.rs}}
|
||||
```
|
||||
## Custom Topology Provider
|
||||
If you are also running a Validator and Nym API for your network, you can specify that endpoint. Clients will then use this endpoint to grab a network topology on startup. You can also use this to specify using a testnet.
|
||||
|
||||
## Import a specific topology manually
|
||||
If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually:
|
||||
|
||||
```rust,noplayground
|
||||
{{#include ../../../../../../sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs}}
|
||||
```
|
||||
If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can also overwrite the grabbed topology manually.
|
||||
@@ -0,0 +1,89 @@
|
||||
# Custom Topology Provider
|
||||
|
||||
If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood).
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/custom_topology_provider.rs)
|
||||
|
||||
```rust
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sdk::mixnet;
|
||||
use nym_sdk::mixnet::MixnetMessageSender;
|
||||
use nym_topology::provider_trait::{async_trait, TopologyProvider};
|
||||
use nym_topology::{nym_topology_from_detailed, NymTopology};
|
||||
use url::Url;
|
||||
|
||||
struct MyTopologyProvider {
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
}
|
||||
|
||||
impl MyTopologyProvider {
|
||||
fn new(nym_api_url: Url) -> MyTopologyProvider {
|
||||
MyTopologyProvider {
|
||||
validator_client: nym_validator_client::client::NymApiClient::new(nym_api_url),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_topology(&self) -> NymTopology {
|
||||
let mixnodes = self
|
||||
.validator_client
|
||||
.get_cached_active_mixnodes()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// in our topology provider only use mixnodes that have mix_id divisible by 3
|
||||
// and have more than 100k nym (i.e. 100'000'000'000 unym) in stake
|
||||
// why? because this is just an example to showcase arbitrary uses and capabilities of this trait
|
||||
let filtered_mixnodes = mixnodes
|
||||
.into_iter()
|
||||
.filter(|mix| {
|
||||
mix.mix_id() % 3 == 0 && mix.total_stake() > "100000000000".parse().unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let gateways = self.validator_client.get_cached_gateways().await.unwrap();
|
||||
|
||||
nym_topology_from_detailed(filtered_mixnodes, gateways)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TopologyProvider for MyTopologyProvider {
|
||||
// this will be manually refreshed on a timer specified inside mixnet client config
|
||||
async fn get_new_topology(&mut self) -> Option<NymTopology> {
|
||||
Some(self.get_topology().await)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
nym_bin_common::logging::setup_logging();
|
||||
|
||||
let nym_api = "https://validator.nymtech.net/api/".parse().unwrap();
|
||||
let my_topology_provider = MyTopologyProvider::new(nym_api);
|
||||
|
||||
// Passing no config makes the client fire up an ephemeral session and figure things out on its own
|
||||
let mut client = mixnet::MixnetClientBuilder::new_ephemeral()
|
||||
.custom_topology_provider(Box::new(my_topology_provider))
|
||||
.build()
|
||||
.unwrap()
|
||||
.connect_to_mixnet()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let our_address = client.nym_address();
|
||||
println!("Our client nym address is: {our_address}");
|
||||
|
||||
// Send a message through the mixnet to ourselves
|
||||
client
|
||||
.send_plain_message(*our_address, "hello there")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("Waiting for message (ctrl-c to exit)");
|
||||
client
|
||||
.on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message)))
|
||||
.await;
|
||||
}
|
||||
```
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
# Manually Overwrite Topology
|
||||
|
||||
If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs)
|
||||
|
||||
```rust
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sdk::mixnet;
|
||||
use nym_sdk::mixnet::MixnetMessageSender;
|
||||
use nym_topology::mix::Layer;
|
||||
use nym_topology::{mix, NymTopology};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
nym_bin_common::logging::setup_logging();
|
||||
|
||||
// Passing no config makes the client fire up an ephemeral session and figure shit out on its own
|
||||
let mut client = mixnet::MixnetClient::connect_new().await.unwrap();
|
||||
let starting_topology = client.read_current_topology().await.unwrap();
|
||||
|
||||
// but we don't like our default topology, we want to use only those very specific, hardcoded, nodes:
|
||||
let mut mixnodes = BTreeMap::new();
|
||||
mixnodes.insert(
|
||||
1,
|
||||
vec![mix::Node {
|
||||
mix_id: 63,
|
||||
owner: None,
|
||||
host: "172.105.92.48".parse().unwrap(),
|
||||
mix_host: "172.105.92.48:1789".parse().unwrap(),
|
||||
identity_key: "GLdR2NRVZBiCoCbv4fNqt9wUJZAnNjGXHkx3TjVAUzrK"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
sphinx_key: "CBmYewWf43iarBq349KhbfYMc9ys2ebXWd4Vp4CLQ5Rq"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
layer: Layer::One,
|
||||
version: "1.1.0".into(),
|
||||
}],
|
||||
);
|
||||
mixnodes.insert(
|
||||
2,
|
||||
vec![mix::Node {
|
||||
mix_id: 23,
|
||||
owner: None,
|
||||
host: "178.79.143.65".parse().unwrap(),
|
||||
mix_host: "178.79.143.65:1789".parse().unwrap(),
|
||||
identity_key: "4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
sphinx_key: "8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
layer: Layer::Two,
|
||||
version: "1.1.0".into(),
|
||||
}],
|
||||
);
|
||||
mixnodes.insert(
|
||||
3,
|
||||
vec![mix::Node {
|
||||
mix_id: 66,
|
||||
owner: None,
|
||||
host: "139.162.247.97".parse().unwrap(),
|
||||
mix_host: "139.162.247.97:1789".parse().unwrap(),
|
||||
identity_key: "66UngapebhJRni3Nj52EW1qcNsWYiuonjkWJzHFsmyYY"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
sphinx_key: "7KyZh8Z8KxuVunqytAJ2eXFuZkCS7BLTZSzujHJZsGa2"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
layer: Layer::Three,
|
||||
version: "1.1.0".into(),
|
||||
}],
|
||||
);
|
||||
|
||||
// but we like the available gateways, so keep using them!
|
||||
// (we like them because the author of this example is too lazy to use the same hardcoded gateway
|
||||
// during client initialisation to make sure we are able to send to ourselves : ) )
|
||||
let custom_topology = NymTopology::new(mixnodes, starting_topology.gateways().to_vec());
|
||||
|
||||
client.manually_overwrite_topology(custom_topology).await;
|
||||
|
||||
// and everything we send now should only ever go via those nodes
|
||||
|
||||
let our_address = client.nym_address();
|
||||
println!("Our client nym address is: {our_address}");
|
||||
|
||||
// Send a message through the mixnet to ourselves
|
||||
client
|
||||
.send_plain_message(*our_address, "hello there")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("Waiting for message (ctrl-c to exit)");
|
||||
client
|
||||
.on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message)))
|
||||
.await;
|
||||
}
|
||||
```
|
||||
@@ -1,28 +0,0 @@
|
||||
# Key Creation and Use
|
||||
The previous example involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys (`examples/builder_with_storage`):
|
||||
|
||||
```rust,noplayground
|
||||
{{#include ../../../../../../sdk/rust/nym-sdk/examples/builder_with_storage.rs}}
|
||||
```
|
||||
|
||||
As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple.
|
||||
|
||||
Assuming our client config is stored in `/tmp/mixnet-client`, the following files are generated:
|
||||
```
|
||||
$ tree /tmp/mixnet-client
|
||||
|
||||
mixnet-client
|
||||
├── ack_key.pem
|
||||
├── db.sqlite
|
||||
├── db.sqlite-shm
|
||||
├── db.sqlite-wal
|
||||
├── gateway_details.json
|
||||
├── gateway_shared.pem
|
||||
├── persistent_reply_store.sqlite
|
||||
├── private_encryption.pem
|
||||
├── private_identity.pem
|
||||
├── public_encryption.pem
|
||||
└── public_identity.pem
|
||||
|
||||
1 directory, 11 files
|
||||
```
|
||||
@@ -1,8 +1,34 @@
|
||||
# Simple Send
|
||||
Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code (`examples/simple.rs`).
|
||||
# Simple Send
|
||||
Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code.
|
||||
|
||||
Simply importing the `nym_sdk` crate into your project allows you to create a client and send traffic through the mixnet.
|
||||
|
||||
```rust,noplayground
|
||||
{{#include ../../../../../../sdk/rust/nym-sdk/examples/simple.rs}}
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/simple.rs)
|
||||
|
||||
```rust
|
||||
use nym_sdk::mixnet;
|
||||
use nym_sdk::mixnet::MixnetMessageSender;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
nym_bin_common::logging::setup_logging();
|
||||
|
||||
// Passing no config makes the client fire up an ephemeral session and figure shit out on its own
|
||||
let mut client = mixnet::MixnetClient::connect_new().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = client.nym_address();
|
||||
println!("Our client nym address is: {our_address}");
|
||||
|
||||
// Send a message through the mixnet to ourselves
|
||||
client
|
||||
.send_plain_message(*our_address, "hello there")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("Waiting for message (ctrl-c to exit)");
|
||||
client
|
||||
.on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message)))
|
||||
.await;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,10 +1,48 @@
|
||||
# Socks Proxy
|
||||
There is also the option to embed the [`socks5-client`](../../../clients/socks5-client.md) into your app code (`examples/socks5.rs`):
|
||||
# Socks Proxy
|
||||
|
||||
```admonish info
|
||||
If you are looking at implementing Nym as a transport layer for a crypto wallet or desktop app, this is probably the best place to start if they can speak SOCKS5, 4a, or 4.
|
||||
```
|
||||
|
||||
```rust,noplayground
|
||||
{{#include ../../../../../../sdk/rust/nym-sdk/examples/socks5.rs}}
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/socks5.rs)
|
||||
|
||||
```rust
|
||||
use nym_sdk::mixnet;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
nym_bin_common::logging::setup_logging();
|
||||
|
||||
println!("Connecting receiver");
|
||||
let mut receiving_client = mixnet::MixnetClient::connect_new().await.unwrap();
|
||||
|
||||
let socks5_config = mixnet::Socks5::new(receiving_client.nym_address().to_string());
|
||||
let sending_client = mixnet::MixnetClientBuilder::new_ephemeral()
|
||||
.socks5_config(socks5_config)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
println!("Connecting sender");
|
||||
let sending_client = sending_client.connect_to_mixnet_via_socks5().await.unwrap();
|
||||
|
||||
let proxy = reqwest::Proxy::all(sending_client.socks5_url()).unwrap();
|
||||
let reqwest_client = reqwest::Client::builder().proxy(proxy).build().unwrap();
|
||||
tokio::spawn(async move {
|
||||
println!("Sending socks5-wrapped http request");
|
||||
// Message should be sent through the mixnet, via socks5
|
||||
// We don't expect to get anything, as there is no network requester on the other end
|
||||
reqwest_client.get("https://nymtech.net").send().await.ok()
|
||||
});
|
||||
|
||||
println!("Waiting for message");
|
||||
if let Some(received) = receiving_client.wait_for_messages().await {
|
||||
for r in received {
|
||||
println!(
|
||||
"Received socks5 message requesting for endpoint: {}",
|
||||
String::from_utf8_lossy(&r.message[10..27])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
receiving_client.disconnect().await;
|
||||
sending_client.disconnect().await;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,6 +1,49 @@
|
||||
# Send and Receive in Different Tasks
|
||||
If you need to split the different actions of your client across different tasks, you can do so like this:
|
||||
If you need to split the different actions of your client across different tasks, you can do so like this. You can think of this analogously to spliting a Tcp Stream into read/write. This functionality is also useful for embedding a sending and receiving client into different tasks.
|
||||
|
||||
```rust, noplayground
|
||||
{{#include ../../../../../../sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs}}
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs)
|
||||
|
||||
```rust
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::StreamExt;
|
||||
use nym_sdk::mixnet;
|
||||
use nym_sdk::mixnet::MixnetMessageSender;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
nym_bin_common::logging::setup_logging();
|
||||
|
||||
// Passing no config makes the client fire up an ephemeral session and figure stuff out on its own
|
||||
let mut client = mixnet::MixnetClient::connect_new().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = *client.nym_address();
|
||||
println!("Our client nym address is: {our_address}");
|
||||
|
||||
let sender = client.split_sender();
|
||||
|
||||
// receiving task
|
||||
let receiving_task_handle = tokio::spawn(async move {
|
||||
if let Some(received) = client.next().await {
|
||||
println!("Received: {}", String::from_utf8_lossy(&received.message));
|
||||
}
|
||||
|
||||
client.disconnect().await;
|
||||
});
|
||||
|
||||
// sending task
|
||||
let sending_task_handle = tokio::spawn(async move {
|
||||
sender
|
||||
.send_plain_message(our_address, "hello from a different task!")
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
// wait for both tasks to be done
|
||||
println!("waiting for shutdown");
|
||||
sending_task_handle.await.unwrap();
|
||||
receiving_task_handle.await.unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,6 +1,226 @@
|
||||
# Manually Handled Storage
|
||||
If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform the actions taken automatically above (`examples/manually_handle_keys_and_config.rs`)
|
||||
# Manually Handle Storage
|
||||
If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform these actions.
|
||||
|
||||
```rust,noplayground
|
||||
{{#include ../../../../../../sdk/rust/nym-sdk/examples/manually_handle_storage.rs}}
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_handle_storage.rs)
|
||||
|
||||
```rust
|
||||
use nym_sdk::mixnet::{
|
||||
self, ActiveGateway, BadGateway, ClientKeys, EmptyReplyStorage, EphemeralCredentialStorage,
|
||||
GatewayRegistration, GatewaysDetailsStore, KeyStore, MixnetClientStorage, MixnetMessageSender,
|
||||
};
|
||||
use nym_topology::provider_trait::async_trait;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
nym_bin_common::logging::setup_logging();
|
||||
|
||||
// Just some plain data to pretend we have some external storage that the application
|
||||
// implementer is using.
|
||||
let mock_storage = MockClientStorage::empty();
|
||||
let mut client = mixnet::MixnetClientBuilder::new_with_storage(mock_storage)
|
||||
.build()
|
||||
.unwrap()
|
||||
.connect_to_mixnet()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = client.nym_address();
|
||||
println!("Our client nym address is: {our_address}");
|
||||
|
||||
// Send important info up the pipe to a buddy
|
||||
client
|
||||
.send_plain_message(*our_address, "hello there")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("Waiting for message");
|
||||
if let Some(received) = client.wait_for_messages().await {
|
||||
for r in received {
|
||||
println!("Received: {}", String::from_utf8_lossy(&r.message));
|
||||
}
|
||||
}
|
||||
|
||||
client.disconnect().await;
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
struct MockClientStorage {
|
||||
pub key_store: MockKeyStore,
|
||||
pub gateway_details_store: MockGatewayDetailsStore,
|
||||
pub reply_store: EmptyReplyStorage,
|
||||
pub credential_store: EphemeralCredentialStorage,
|
||||
}
|
||||
|
||||
impl MockClientStorage {
|
||||
fn empty() -> Self {
|
||||
Self {
|
||||
key_store: MockKeyStore,
|
||||
gateway_details_store: MockGatewayDetailsStore,
|
||||
reply_store: EmptyReplyStorage::default(),
|
||||
credential_store: EphemeralCredentialStorage::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MixnetClientStorage for MockClientStorage {
|
||||
type KeyStore = MockKeyStore;
|
||||
type ReplyStore = EmptyReplyStorage;
|
||||
type CredentialStore = EphemeralCredentialStorage;
|
||||
type GatewaysDetailsStore = MockGatewayDetailsStore;
|
||||
|
||||
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
|
||||
(self.reply_store, self.credential_store)
|
||||
}
|
||||
|
||||
fn key_store(&self) -> &Self::KeyStore {
|
||||
&self.key_store
|
||||
}
|
||||
|
||||
fn reply_store(&self) -> &Self::ReplyStore {
|
||||
&self.reply_store
|
||||
}
|
||||
|
||||
fn credential_store(&self) -> &Self::CredentialStore {
|
||||
&self.credential_store
|
||||
}
|
||||
|
||||
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore {
|
||||
&self.gateway_details_store
|
||||
}
|
||||
}
|
||||
|
||||
struct MockKeyStore;
|
||||
|
||||
#[async_trait]
|
||||
impl KeyStore for MockKeyStore {
|
||||
type StorageError = MyError;
|
||||
|
||||
async fn load_keys(&self) -> Result<ClientKeys, Self::StorageError> {
|
||||
println!("loading stored keys");
|
||||
|
||||
Err(MyError)
|
||||
}
|
||||
|
||||
async fn store_keys(&self, _keys: &ClientKeys) -> Result<(), Self::StorageError> {
|
||||
println!("storing keys");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct MockGatewayDetailsStore;
|
||||
|
||||
#[async_trait]
|
||||
impl GatewaysDetailsStore for MockGatewayDetailsStore {
|
||||
type StorageError = MyError;
|
||||
|
||||
async fn active_gateway(&self) -> Result<ActiveGateway, Self::StorageError> {
|
||||
println!("getting active gateway");
|
||||
|
||||
Err(MyError)
|
||||
}
|
||||
|
||||
async fn set_active_gateway(&self, _gateway_id: &str) -> Result<(), Self::StorageError> {
|
||||
println!("setting active gateway");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn all_gateways(&self) -> Result<Vec<GatewayRegistration>, Self::StorageError> {
|
||||
println!("getting all registered gateways");
|
||||
|
||||
Err(MyError)
|
||||
}
|
||||
|
||||
async fn has_gateway_details(&self, _gateway_id: &str) -> Result<bool, Self::StorageError> {
|
||||
println!("checking for gateway details");
|
||||
|
||||
Err(MyError)
|
||||
}
|
||||
|
||||
async fn load_gateway_details(
|
||||
&self,
|
||||
_gateway_id: &str,
|
||||
) -> Result<GatewayRegistration, Self::StorageError> {
|
||||
println!("loading gateway details");
|
||||
|
||||
Err(MyError)
|
||||
}
|
||||
|
||||
async fn store_gateway_details(
|
||||
&self,
|
||||
_details: &GatewayRegistration,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
println!("storing gateway details");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_gateway_details(&self, _gateway_id: &str) -> Result<(), Self::StorageError> {
|
||||
println!("removing gateway details");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// struct MockReplyStore;
|
||||
//
|
||||
// #[async_trait]
|
||||
// impl ReplyStorageBackend for MockReplyStore {
|
||||
// type StorageError = MyError;
|
||||
//
|
||||
// async fn flush_surb_storage(
|
||||
// &mut self,
|
||||
// _storage: &CombinedReplyStorage,
|
||||
// ) -> Result<(), Self::StorageError> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// async fn init_fresh(&mut self, _fresh: &CombinedReplyStorage) -> Result<(), Self::StorageError> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// struct MockCredentialStore;
|
||||
//
|
||||
// #[async_trait]
|
||||
// impl CredentialStorage for MockCredentialStore {
|
||||
// type StorageError = MyError;
|
||||
//
|
||||
// async fn insert_coconut_credential(
|
||||
// &self,
|
||||
// _voucher_value: String,
|
||||
// _voucher_info: String,
|
||||
// _serial_number: String,
|
||||
// _binding_number: String,
|
||||
// _signature: String,
|
||||
// _epoch_id: String,
|
||||
// ) -> Result<(), Self::StorageError> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, Self::StorageError> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError> {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("foobar")]
|
||||
struct MyError;
|
||||
|
||||
impl From<BadGateway> for MyError {
|
||||
fn from(_: BadGateway) -> Self {
|
||||
MyError
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,10 +1,83 @@
|
||||
# Anonymous Replies with SURBs (Single Use Reply Blocks)
|
||||
Both functions used to send messages through the mixnet (`send_message` and `send_plain_message`) send a pre-determined number of SURBs along with their messages by default.
|
||||
|
||||
You can read more about how SURBs function under the hood [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs).
|
||||
You can read more about how SURBs function under the hood [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs). **TODO change link**
|
||||
|
||||
In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to:
|
||||
In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to.
|
||||
|
||||
```rust,noplayground
|
||||
{{#include ../../../../../../sdk/rust/nym-sdk/examples/surb_reply.rs}}
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/surb_reply.rs)
|
||||
|
||||
```rust
|
||||
use nym_sdk::mixnet::{
|
||||
AnonymousSenderTag, MixnetClientBuilder, MixnetMessageSender, ReconstructedMessage,
|
||||
StoragePaths,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
nym_bin_common::logging::setup_logging();
|
||||
|
||||
// Specify some config options
|
||||
let config_dir = PathBuf::from("/tmp/surb-example");
|
||||
let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap();
|
||||
|
||||
// Create the client with a storage backend, and enable it by giving it some paths. If keys
|
||||
// exists at these paths, they will be loaded, otherwise they will be generated.
|
||||
let client = MixnetClientBuilder::new_with_default_storage(storage_paths)
|
||||
.await
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// Now we connect to the mixnet, using keys now stored in the paths provided.
|
||||
let mut client = client.connect_to_mixnet().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = client.nym_address();
|
||||
println!("\nOur client nym address is: {our_address}");
|
||||
|
||||
// Send a message through the mixnet to ourselves using our nym address
|
||||
client
|
||||
.send_plain_message(*our_address, "hello there")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// we're going to parse the sender_tag (AnonymousSenderTag) from the incoming message and use it to 'reply' to ourselves instead of our Nym address.
|
||||
// we know there will be a sender_tag since the sdk sends SURBs along with messages by default.
|
||||
println!("Waiting for message\n");
|
||||
|
||||
// get the actual message - discard the empty vec sent along with a potential SURB topup request
|
||||
let mut message: Vec<ReconstructedMessage> = Vec::new();
|
||||
while let Some(new_message) = client.wait_for_messages().await {
|
||||
if new_message.is_empty() {
|
||||
continue;
|
||||
}
|
||||
message = new_message;
|
||||
break;
|
||||
}
|
||||
|
||||
let mut parsed = String::new();
|
||||
if let Some(r) = message.first() {
|
||||
parsed = String::from_utf8(r.message.clone()).unwrap();
|
||||
}
|
||||
// parse sender_tag: we will use this to reply to sender without needing their Nym address
|
||||
let return_recipient: AnonymousSenderTag = message[0].sender_tag.unwrap();
|
||||
println!(
|
||||
"\nReceived the following message: {} \nfrom sender with surb bucket {}",
|
||||
parsed, return_recipient
|
||||
);
|
||||
|
||||
// reply to self with it: note we use `send_str_reply` instead of `send_str`
|
||||
println!("Replying with using SURBs");
|
||||
client
|
||||
.send_reply(return_recipient, "hi an0n!")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("Waiting for message (once you see it, ctrl-c to exit)\n");
|
||||
client
|
||||
.on_messages(|msg| println!("\nReceived: {}", String::from_utf8_lossy(&msg.message)))
|
||||
.await;
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user