Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,28 @@ stopped at v0.4 until 2026-09-30, when the entries v0.5 to v0.6.7 were written f

## Unreleased

Nothing yet.
v0.8.3 (wallet; client-only, no node or chain change):

- **`rand` no longer exits 1 after a successful submit** (#141): `submitted <kind> <hash>` is
printed on stderr as soon as the node accepts a transaction; status polls that time out or are
reset are retried with backoff until the wait's deadline; a wait that never sees the commit exits
**4** ("submitted, confirmation unknown", with a `rand tx <hash>` hint). Exit 1 is a real
failure only. New global `--wait-timeout <SECS>` / `RAND_WAIT_TIMEOUT`. Exit codes in
`docs/cli.md`.
- **Behaviour change: `rand token create --initial` and `--fixed-supply` read display units** at
`--decimals` (#142), as `token mint --amount`, `token burn` and `send --asset` always have:
`--decimals 6 --initial 1000000` is now a million tokens, not one. The base-unit form is
`--initial-units` / `--fixed-supply-units`. Scripts that passed base units must switch to the
`-units` flags. `token create` echoes the mint as `minted <display> <SYMBOL> (<N> units)`.
- **Privacy: the scan no longer reveals which leaves are the wallet's** (#147): `rand` pages
`rand_getCommitments` over the whole range in fixed 500-row pages and decides ownership locally,
so its requests do not depend on what it owns; it no longer reads `rand_getScanLeaves`. Cost: at
most ~2.26× the bytes of the v0.8.0–v0.8.2 heads scan (3 885 vs 1 720 bytes of JSON a leaf on
chain 21's envelope). `--scan-chunk` is accepted and ignored, with a warning.
- **`--proxy <URL>` / `RAND_PROXY`** (#148): every request to the node and to a paired prover goes
through a SOCKS5 (`socks5h://` for Tor, `socks5://`) or HTTP(S) CONNECT proxy; a dead proxy fails
the command, never a direct connection. reqwest's `socks` feature is enabled in the client (no
new crates: reqwest 0.12.28 proxies through hyper-util). Tor recipe in `docs/cli.md`.

## v0.8.2 — 2026-10-27 (chain 21, node-only; the genesis-gated rules wait for the next cut)

Expand Down
3 changes: 2 additions & 1 deletion crates/randprotocol-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ randprotocol-msig = { workspace = true }
randprotocol-prover = { workspace = true, default-features = false, features = ["parallel"] }
# Available memory for the local-proving gate (`memory.rs`); the same version and features the prover's service build uses.
sysinfo = { version = "0.33", default-features = false, features = ["system"] }
reqwest = { workspace = true }
# `socks`: `rand --proxy socks5h://…` (Tor, issue #148).
reqwest = { workspace = true, features = ["socks"] }
tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
Expand Down
545 changes: 472 additions & 73 deletions crates/randprotocol-client/src/lib.rs

Large diffs are not rendered by default.

183 changes: 140 additions & 43 deletions crates/randprotocol-client/src/main.rs

Large diffs are not rendered by default.

36 changes: 31 additions & 5 deletions crates/randprotocol-client/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,16 @@ pub struct RemoteProver {
fee_announced: std::sync::atomic::AtomicBool,
}

/// The prover link's HTTP client: 10 s to connect, 60 s a request, through `proxy` or the
/// process's default proxy.
fn prover_http(proxy: Option<reqwest::Proxy>) -> Result<reqwest::Client> {
crate::http_builder(proxy)
.connect_timeout(Duration::from_secs(10))
.timeout(Duration::from_secs(60))
.build()
.context("building the prover's http client")
}

/// Reads the digest a bundle proof of the guest `hc` publishes, at a FRI profile.
pub(crate) type PublishedDigest = std::sync::Arc<dyn Fn(FriProfile, &Word8, &[u8]) -> Result<Word8> + Send + Sync>;

Expand All @@ -368,11 +378,7 @@ fn executor_digest(profile: FriProfile, hc: &Word8, proof: &[u8]) -> Result<Word

impl RemoteProver {
pub fn new(paired: PairedProver) -> RemoteProver {
let http = reqwest::Client::builder()
.connect_timeout(Duration::from_secs(10))
.timeout(Duration::from_secs(60))
.build()
.expect("an http client");
let http = prover_http(None).expect("an http client");
let no_verify = std::env::var("RAND_PROVER_NO_VERIFY").is_ok_and(|v| v == "1");
RemoteProver {
paired,
Expand All @@ -388,6 +394,14 @@ impl RemoteProver {
}
}

/// This prover, reached through the proxy at `url` (`--proxy`, issue #148) — or, for `None`,
/// the process's default ([`crate::set_default_proxy`]), which [`RemoteProver::new`] already
/// uses.
pub fn with_proxy(mut self, url: Option<&str>) -> Result<RemoteProver> {
self.http = prover_http(url.map(crate::parse_proxy).transpose()?)?;
Ok(self)
}

/// The cap on a quoted fee (`--max-prover-fee`, base units); 0 refuses any fee.
pub fn with_max_fee(mut self, cap: u64) -> RemoteProver {
self.max_fee = cap;
Expand Down Expand Up @@ -1044,6 +1058,18 @@ mod tests {
}
}

/// Issue #148: with a proxy the wallet reaches its paired prover only through it.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_remote_prover_with_a_proxy_is_reached_through_it() {
let (paired, state) = fake(Finish::Done { flip: 0, publish_flip: 0, proof_len: 100 }, true, |_| {}).await;
let target = paired.url.trim_start_matches("http://").trim_end_matches('/').to_string();
let (proxy, seen) = crate::socks_stub::socks5_stub().await;
let r = RemoteProver::new(paired).with_proxy(Some(&proxy)).unwrap();
r.info().await.unwrap();
assert_eq!(state.lock().unwrap().seen, vec!["prover_info".to_string()]);
assert!(seen.lock().unwrap().contains(&target), "{:?} via {target}", seen.lock().unwrap());
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_fingerprint_mismatch_is_refused_at_info() {
let (paired, state) = fake(Finish::Done { flip: 0, publish_flip: 0, proof_len: 100 }, true, |i| {
Expand Down
Loading
Loading