Compare commits

...

12 Commits

Author SHA1 Message Date
Diego Prats
e084396969
feat: use firebase instead of mixpanel in for event tracking (#32)
Co-authored-by: Diego Prats <diegoprats@Diegos-MacBook-Pro-m3.local>
Co-authored-by: Collin Jackson <collin@nexus.xyz>
2024-11-12 17:03:59 -08:00
Collin Jackson
d9507aa2ac
ci: automatically lint and build CLI (#33) 2024-11-08 13:45:30 -08:00
Collin Jackson
f7fe621554
chore: Don't send analytics during devnet (#31) 2024-11-07 11:05:40 -08:00
Collin Jackson
c41425c55c
docs: Update Discord link text (#20) 2024-10-29 10:55:02 -07:00
Collin Jackson
b0698ec2d2
docs: Known issue for linking email (#23) 2024-10-27 14:56:19 -07:00
Collin Jackson
b0b1c6fd1e
chore: remove beta stats link (#22) 2024-10-27 11:13:56 -07:00
Collin Jackson
f6d168cd7c
feat: increase precision of proving speed, release-only analytics (#19) 2024-10-23 12:04:17 -07:00
Collin Jackson
26f1652952
docs: Update installation instructions with prerequisites, links (#18) 2024-10-23 11:47:15 -07:00
Toan Pham
0bff6e31df
fix: panic app when cannot send prove (#15)
Co-authored-by: Toan Pham <toan@betanet.co.il>
2024-10-21 05:08:32 -07:00
Collin Jackson
7b3383123c
docs: Update README.md install instructions (#17) 2024-10-18 12:41:20 -07:00
Collin Jackson
9006b955d2
docs: Update README.md instructions for demand key (#16) 2024-10-18 12:25:25 -07:00
hcotry
a48c121a3c
fix: Hz of proved Program (#12)
Signed-off-by: hcotry <hcotry@163.com>
2024-10-18 12:23:34 -07:00
9 changed files with 297 additions and 67 deletions

64
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: ci
on:
push:
branches:
- main
pull_request:
branches:
- "**"
jobs:
build:
name: Lint CLI
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
sparse-checkout: |
clients/cli
proto
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Install protoc
uses: arduino/setup-protoc@v3
- name: Set up Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: ./clients/cli
- name: Format
working-directory: clients/cli
run: |
rustfmt src/**/*.rs --check --edition 2021
- name: Build
working-directory: clients/cli
run: |
cargo build --profile=ci-build
- name: Run cargo clippy
working-directory: clients/cli
run: |
cargo clippy --profile=ci-build --no-deps --all-targets --workspace -- -D warnings
- name: Test
working-directory: clients/cli
run: |
cargo test --profile=ci-build --tests
- name: Ensure checked in generated files are up to date
run: |
if [ -n "$(git status --porcelain)" ]; then \
echo "There are uncommitted changes in working tree after building."; \
git status; \
git --no-pager diff; \
exit 1; \
else \
echo "Git working tree is clean"; \
fi;

View File

@ -39,9 +39,8 @@ If you believe that you have uncovered a bug, please describe it to the best of
As a starting point, in a bug report we will pretty much always want: As a starting point, in a bug report we will pretty much always want:
- the Nexus network version/revision you are using;
- the platform you are on, ideally both the operating system (Windows, macOS, or Linux) and the machine architecture (_e.g.,_ if you're using an M-series Mac) if you know them; - the platform you are on, ideally both the operating system (Windows, macOS, or Linux) and the machine architecture (_e.g.,_ if you're using an M-series Mac) if you know them;
- console logs from the CLI or web application showing errors ands status messages; - console logs from the CLI or web application showing errors and status messages;
- concrete and comprehensive steps to reproduce the bug. - concrete and comprehensive steps to reproduce the bug.
Code snippets should be as minimal as possible. It is always better if you can reproduce the bug with a small snippet that focuses on your Nexus zkVM usage rather than on the surrounding code in your project. This will help collaborators verify, reproduce, and zero in on a fix. Code snippets should be as minimal as possible. It is always better if you can reproduce the bug with a small snippet that focuses on your Nexus zkVM usage rather than on the surrounding code in your project. This will help collaborators verify, reproduce, and zero in on a fix.

View File

@ -1567,7 +1567,7 @@ dependencies = [
[[package]] [[package]]
name = "nexus-network" name = "nexus-network"
version = "0.1.0" version = "0.3.4"
dependencies = [ dependencies = [
"ark-bn254", "ark-bn254",
"ark-crypto-primitives", "ark-crypto-primitives",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "nexus-network" name = "nexus-network"
version = "0.1.0" version = "0.3.4"
edition = "2021" edition = "2021"
[[bin]] [[bin]]
@ -10,6 +10,23 @@ path = "src/prover.rs"
[build-dependencies] [build-dependencies]
prost-build = "0.13" prost-build = "0.13"
[profile.dev]
opt-level = 1
[profile.release]
lto = "fat"
strip = true
codegen-units = 1
[profile.ci-build]
inherits = "dev"
opt-level = 0
debug = 0
strip = "none"
lto = false
codegen-units = 256
incremental = true
[dependencies] [dependencies]
async-stream = "0.3" async-stream = "0.3"
clap = { version = "4.5", features = ["derive"] } clap = { version = "4.5", features = ["derive"] }
@ -83,8 +100,3 @@ ark-vesta = { git = "https://github.com/arkworks-rs/curves/", rev = "8c0256a" }
ark-bls12-381 = { git = "https://github.com/arkworks-rs/curves/", rev = "3fded1f" } ark-bls12-381 = { git = "https://github.com/arkworks-rs/curves/", rev = "3fded1f" }
zstd-sys = { git = "https://github.com/gyscos/zstd-rs" } zstd-sys = { git = "https://github.com/gyscos/zstd-rs" }
[profile.release]
strip = true
lto = true
codegen-units = 1

View File

@ -1,23 +1,59 @@
# network-cli # Network CLI
Command line interface (CLI) for accessing the Nexus Network. Highest-performance option for proving. The command line interface (CLI) lets you run a prover node and contribute proofs to the Nexus network.
It is the highest-performance option for proving.
## Prerequisites
If you don't have these dependencies already, install them first.
### Linux
```
sudo apt update
sudo apt upgrade
sudo apt install build-essential pkg-config libssl-dev git-all
```
### macOS
If you have [installed Homebrew](https://brew.sh/) to manage packages on OS X,
run this command to install Git.
```
brew install git
```
### Windows
[Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install),
then see Linux instructions above.
## Quick start ## Quick start
``` ```
curl https://cli.nexus.xyz/install.sh | sh curl https://cli.nexus.xyz/ | sh
``` ```
If you do not already have Rust, you will be prompted to install it. If you do not already have Rust, you will be prompted to install it.
## Terms of Use ## Terms of Use
Use of the CLI is subject to the [Terms of Use](https://nexus.xyz/terms_of_use). Use of the CLI is subject to the [Terms of Use](https://nexus.xyz/terms-of-use).
The first time you run it, it prompts you to accept the terms. To accept the terms The first time you run it, it prompts you to accept the terms. To accept the terms
noninteractively (for example, in a continuous integration environment), noninteractively (for example, in a continuous integration environment),
add `NONINTERACTIVE=1` before `sh`. add `NONINTERACTIVE=1` before `sh`.
## Known issues ## Known issues
Currently only proving is supported. Submitting programs to the network is in private beta. * Only the latest version of the CLI is currently supported.
To request an API key, [contact us](https://forms.gle/183D9bcDHUdbxCV5A). * Prebuilt binaries are not yet available.
* Linking email to prover id is currently available on the web version only.
* Counting cycles proved is not yet available in the CLI.
* Only proving is supported. Submitting programs to the network is in private beta.
To request an API key, contact us at growth@nexus.xyz.
## Resources
* [Network FAQ](https://nexus.xyz/network#network-faqs)
* [Discord server](https://discord.gg/nexus-xyz)

View File

@ -1,4 +1,4 @@
use crate::config::analytics_token; use crate::config::{analytics_id, analytics_api_key};
use chrono::Datelike; use chrono::Datelike;
use chrono::Timelike; use chrono::Timelike;
use reqwest::header::{ACCEPT, CONTENT_TYPE}; use reqwest::header::{ACCEPT, CONTENT_TYPE};
@ -16,14 +16,27 @@ pub fn track(
) { ) {
println!("{}", description); println!("{}", description);
let token = analytics_token(ws_addr_string); let firebase_app_id = analytics_id(ws_addr_string);
if token.is_empty() { let firebase_api_key = analytics_api_key(ws_addr_string);
if firebase_app_id.is_empty() {
return; return;
} }
let local_now = chrono::offset::Local::now(); let local_now = chrono::offset::Local::now();
// For tracking events, we use the Firebase Measurement Protocol
// Firebase is mostly designed for mobile and web apps, but for our use case of a CLI,
// we can use the Measurement Protocol to track events by POST to a URL.
// The only thing that may be unexpected is that the URL we use includes a firebase key
// Firebase format for properties for Measurement protocol:
// https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=firebase#payload
// https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=firebase#payload_query_parameters
let mut properties = json!({ let mut properties = json!({
"token": token,
"time": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(), "time": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(),
// app_instance_id is the standard key Firebase uses this key to track the same user across sessions
// It is a bit redundant, but I wanted to keep the recommended format Firebase uses to minimize surprises
// I still left the distinct_id key as well for backwards compatibility
"app_instance_id": event_properties["prover_id"],
"distinct_id": event_properties["prover_id"], "distinct_id": event_properties["prover_id"],
"prover_type": "volunteer", "prover_type": "volunteer",
"client_type": "cli", "client_type": "cli",
@ -36,15 +49,27 @@ pub fn track(
for (k, v) in event_properties.as_object().unwrap() { for (k, v) in event_properties.as_object().unwrap() {
properties[k] = v.clone(); properties[k] = v.clone();
} }
// Firebase format for events
let body = json!({ let body = json!({
"event": event_name, "app_instance_id": event_properties["prover_id"],
"properties": properties "events": [{
"name": event_name,
"params": properties
}],
}); });
tokio::spawn(async move { tokio::spawn(async move {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let _ = client let _ = client
.post("https://api.mixpanel.com/track?ip=1") // URL is the Google Analytics endpoint for Firebase: https://stackoverflow.com/questions/50355752/firebase-analytics-from-remote-rest-api
.body(format!("[{}]", body.to_string())) .post(format!(
"https://www.google-analytics.com/mp/collect?firebase_app_id={}&api_secret={}",
firebase_app_id,
firebase_api_key
))
.body(format!("[{}]", body))
.header(ACCEPT, "text/plain") .header(ACCEPT, "text/plain")
.header(CONTENT_TYPE, "application/json") .header(CONTENT_TYPE, "application/json")
.send() .send()

View File

@ -1,11 +1,78 @@
pub fn analytics_token(ws_addr_string: &str) -> String { // Debug version of analytics_id
if ws_addr_string.starts_with("wss://dev.orchestrator.nexus.xyz:443/") { #[cfg(debug_assertions)]
return "504d4d443854f2cd10e2e385aca81aa4".into(); pub fn analytics_id(_ws_addr_string: &str) -> String {
} else if ws_addr_string.starts_with("wss://staging.orchestrator.nexus.xyz:443/") { // Use one of the tokens in the release version if debugging analytics
return "30bcb58893992aabc5aec014e7b903d2".into(); "".into()
} else if ws_addr_string.starts_with("wss://beta.orchestrator.nexus.xyz:443/") {
return "3c16d3853f4258414c9c9109bbbdef0e".into();
} else {
return "".into();
};
} }
// Debug version of analytics_api_key
#[cfg(debug_assertions)]
pub fn analytics_api_key(_ws_addr_string: &str) -> String {
// Use one of the tokens in the release version if debugging analytics
"".into()
}
// The following enum is used to determine the environment from the web socket string
#[derive(Debug)]
#[cfg(not(debug_assertions))]
enum Environment {
Dev,
Staging,
Beta,
Unknown,
}
// The web socket addresses for the different environments
#[cfg(not(debug_assertions))]
mod web_socket_urls {
pub const DEV: &str = "wss://dev.orchestrator.nexus.xyz:443/";
pub const STAGING: &str = "wss://staging.orchestrator.nexus.xyz:443/";
pub const BETA: &str = "wss://beta.orchestrator.nexus.xyz:443/";
}
// the firebase APP IDS by environment
#[cfg(not(debug_assertions))]
mod firebase {
pub const DEV_APP_ID: &str = "1:954530464230:web:f0a14de14ef7bcdaa99627";
pub const STAGING_APP_ID: &str = "1:222794630996:web:1758d64a85eba687eaaac1";
pub const BETA_APP_ID: &str = "1:279395003658:web:04ee2c524474d683d75ef3";
// Analytics keys for the different environments
// These are keys that allow the measurement protocol to write to the analytics database
// They are not sensitive. Worst case, if a malicious actor obtains the secret, they could potentially send false or misleading data to your GA4 property
pub const DEV_API_SECRET: &str = "8ySxiKrtT8a76zClqqO8IQ";
pub const STAGING_API_SECRET: &str = "OI7H53soRMSDWfJf1ittHQ";
pub const BETA_API_SECRET: &str = "gxxzKAQLSl-uYI0eKbIi_Q";
}
// Release versions (existing code)
#[cfg(not(debug_assertions))]
pub fn analytics_id(ws_addr_string: &str) -> String {
// Determine the environment from the web socket string (ws_addr_string)
let env = match ws_addr_string {
web_socket_urls::DEV => Environment::Dev,
web_socket_urls::STAGING => Environment::Staging,
web_socket_urls::BETA => Environment::Beta,
_ => Environment::Unknown,
};
// Return the appropriate Firebase App ID based on the environment
match env {
Environment::Dev => firebase::DEV_APP_ID.to_string(),
Environment::Staging => firebase::STAGING_APP_ID.to_string(),
Environment::Beta => firebase::BETA_APP_ID.to_string(),
Environment::Unknown => String::new(),
}
}
#[cfg(not(debug_assertions))]
pub fn analytics_api_key(ws_addr_string: &str) -> String {
match ws_addr_string {
web_socket_urls::DEV => firebase::DEV_API_SECRET.to_string(),
web_socket_urls::STAGING => firebase::STAGING_API_SECRET.to_string(),
web_socket_urls::BETA => firebase::BETA_API_SECRET.to_string(),
_ => String::new(),
}
}

View File

@ -32,9 +32,8 @@ use nexus_core::{
init_circuit_trace, key::CanonicalSerialize, pp::gen_vm_pp, prove_seq_step, types::*, init_circuit_trace, key::CanonicalSerialize, pp::gen_vm_pp, prove_seq_step, types::*,
}, },
}; };
use std::env; use rand::RngCore;
use zstd::stream::Encoder; use zstd::stream::Encoder;
use rand::{ RngCore };
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct Args { struct Args {
@ -119,16 +118,35 @@ async fn main() {
contents: Some(prover_request::Contents::Registration( contents: Some(prover_request::Contents::Registration(
ProverRequestRegistration { ProverRequestRegistration {
prover_type: ProverType::Volunteer.into(), prover_type: ProverType::Volunteer.into(),
prover_id: prover_id.clone().into(), prover_id: prover_id.clone(),
estimated_proof_cycles_hertz: None, estimated_proof_cycles_hertz: None,
}, },
)), )),
}; };
client let mut retries = 0;
let max_retries = 5;
while let Err(e) = client
.send(Message::Binary(registration.encode_to_vec())) .send(Message::Binary(registration.encode_to_vec()))
.await .await
.unwrap(); {
eprintln!(
"Failed to send message: {:?}, attempt {}/{}",
e,
retries + 1,
max_retries
);
retries += 1;
if retries >= max_retries {
eprintln!("Max retries reached, exiting...");
break;
}
// Add a delay before retrying
tokio::time::sleep(tokio::time::Duration::from_secs(u64::pow(2, retries))).await;
}
track( track(
"register".into(), "register".into(),
@ -136,9 +154,6 @@ async fn main() {
&ws_addr_string, &ws_addr_string,
json!({"ws_addr_string": ws_addr_string, "prover_id": prover_id}), json!({"ws_addr_string": ws_addr_string, "prover_id": prover_id}),
); );
println!(
"Network stats are available at https://beta.nexus.xyz/."
);
loop { loop {
let program_message = match client.next().await.unwrap().unwrap() { let program_message = match client.next().await.unwrap().unwrap() {
Message::Binary(b) => b, Message::Binary(b) => b,
@ -169,7 +184,7 @@ async fn main() {
); );
let mut vm: NexusVM<MerkleTrie> = let mut vm: NexusVM<MerkleTrie> =
parse_elf(&elf_bytes.as_ref()).expect("error loading and parsing RISC-V instruction"); parse_elf(elf_bytes.as_ref()).expect("error loading and parsing RISC-V instruction");
vm.syscalls.set_input(&input); vm.syscalls.set_input(&input);
// TODO(collinjackson): Get outputs // TODO(collinjackson): Get outputs
@ -211,8 +226,8 @@ async fn main() {
track( track(
"progress".into(), "progress".into(),
format!( format!(
"Program trace is {} steps. Proving from {} to {}...", "Program trace is {} steps. Proving {} steps starting at {}...",
total_steps, start, end total_steps, steps_to_prove, start
), ),
&ws_addr_string, &ws_addr_string,
json!({ json!({
@ -233,19 +248,21 @@ async fn main() {
completed_fraction = steps_proven as f32 / steps_to_prove as f32; completed_fraction = steps_proven as f32 / steps_to_prove as f32;
let progress = ProverRequest { let progress = ProverRequest {
contents: Some(prover_request::Contents::Progress(Progress { contents: Some(prover_request::Contents::Progress(Progress {
completed_fraction: completed_fraction, completed_fraction,
steps_in_trace: total_steps as i32, steps_in_trace: total_steps as i32,
steps_to_prove: steps_to_prove as i32, steps_to_prove: steps_to_prove as i32,
steps_proven: steps_proven as i32, steps_proven,
})), })),
}; };
let progress_duration = SystemTime::now().duration_since(progress_time).unwrap(); let progress_duration = SystemTime::now().duration_since(progress_time).unwrap();
let cycles_proven = steps_proven * 4; let cycles_proven = steps_proven * 4;
let proof_cycles_hertz = k * 1000 / progress_duration.as_millis(); let proof_cycles_hertz = k as f64 * 1000.0 / progress_duration.as_millis() as f64;
let proof_cycles_per_minute = k * 60 * 1000 / progress_duration.as_millis();
track( track(
"progress".into(), "progress".into(),
format!("Proved step {} at {} Hz.", step, proof_cycles_hertz), format!(
"Proved step {} at {:.2} proof cycles/sec.",
step, proof_cycles_hertz
),
&ws_addr_string, &ws_addr_string,
json!({ json!({
"completed_fraction": completed_fraction, "completed_fraction": completed_fraction,
@ -256,15 +273,31 @@ async fn main() {
"k": k, "k": k,
"progress_duration_millis": progress_duration.as_millis(), "progress_duration_millis": progress_duration.as_millis(),
"proof_cycles_hertz": proof_cycles_hertz, "proof_cycles_hertz": proof_cycles_hertz,
"proof_cycles_per_minute": proof_cycles_per_minute,
"prover_id": prover_id, "prover_id": prover_id,
}), }),
); );
progress_time = SystemTime::now(); progress_time = SystemTime::now();
client
.send(Message::Binary(progress.encode_to_vec())) let mut retries = 0;
.await let max_retries = 5;
.unwrap(); while let Err(e) = client.send(Message::Binary(progress.encode_to_vec())).await {
eprintln!(
"Failed to send message: {:?}, attempt {}/{}",
e,
retries + 1,
max_retries
);
retries += 1;
if retries >= max_retries {
eprintln!("Max retries reached, exiting...");
break;
}
// Add a delay before retrying
tokio::time::sleep(tokio::time::Duration::from_secs(u64::pow(2, retries))).await;
}
if step == end - 1 { if step == end - 1 {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut writer = Box::new(&mut buf); let mut writer = Box::new(&mut buf);
@ -279,22 +312,24 @@ async fn main() {
proof: Some(proof::Proof::NovaBytes(buf)), proof: Some(proof::Proof::NovaBytes(buf)),
})), })),
}; };
let duration = SystemTime::now().duration_since(start_time).unwrap();
let proof_cycles_hertz =
cycles_proven as f64 * 1000.0 / duration.as_millis() as f64;
client client
.send(Message::Binary(response.encode_to_vec())) .send(Message::Binary(response.encode_to_vec()))
.await .await
.unwrap(); .unwrap();
let duration = SystemTime::now().duration_since(start_time).unwrap();
let proof_cycles_hertz = cycles_proven * 1000 / duration.as_millis();
let proof_cycles_per_minute = cycles_proven * 60 * 1000 / duration.as_millis();
track( track(
"proof".into(), "proof".into(),
format!("Proof sent! You proved at {} Hz.", proof_cycles_hertz), format!(
"Proof sent! Overall speed was {:.2} proof cycles/sec.",
proof_cycles_hertz
),
&ws_addr_string, &ws_addr_string,
json!({ json!({
"proof_duration_sec": duration.as_secs(), "proof_duration_sec": duration.as_secs(),
"proof_duration_millis": duration.as_millis(), "proof_duration_millis": duration.as_millis(),
"proof_cycles_hertz": proof_cycles_hertz, "proof_cycles_hertz": proof_cycles_hertz,
"proof_cycles_per_minute": proof_cycles_per_minute,
"prover_id": prover_id, "prover_id": prover_id,
}), }),
); );

View File

@ -1,8 +0,0 @@
#!/bin/sh
mkdir -p clients/flutter/lib/src/generated
protoc --experimental_allow_proto3_optional --dart_out=grpc:clients/flutter/lib/src/generated -Iproto proto/orchestrator.proto
dart format clients/flutter/lib/src/generated
(cd clients/dummy_client && cargo build || echo clients/dummy_client not found, possibly due to a sparse checkout.)
(cd clients/cli && cargo build || echo clients/cli not found, possibly due to a sparse checkout.)
(cd orchestrator && cargo build || echo orchestrator/ not found, possibly due a sparse checkout.)