From 182ca46b28916a97770c3a52c776dd5280f2d4d6 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sun, 9 Mar 2025 16:16:03 -0700 Subject: [PATCH 1/5] add client to WASM test --- .github/workflows/publish.yml | 5 +++++ .github/workflows/tests.yml | 2 ++ Cargo.toml | 1 + client/Cargo.toml | 26 ++++++++++++++++++++++++++ client/README.md | 10 ++++++++++ 5 files changed, 44 insertions(+) create mode 100644 client/Cargo.toml create mode 100644 client/README.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 548d1f71..d2f134df 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,6 +22,11 @@ jobs: continue-on-error: true env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + - name: Publish client + run: cargo publish --manifest-path client/Cargo.toml + continue-on-error: true + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - name: Publish chain run: cargo publish --manifest-path chain/Cargo.toml continue-on-error: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1a3c57a4..e7043fb6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -75,3 +75,5 @@ jobs: run: rustup target add wasm32-unknown-unknown - name: Build types run: cargo build --target wasm32-unknown-unknown --release --manifest-path types/Cargo.toml && du -h target/wasm32-unknown-unknown/release/alto_types.wasm + - name: Build client + run: cargo build --target wasm32-unknown-unknown --release --manifest-path client/Cargo.toml && du -h target/wasm32-unknown-unknown/release/alto_client.wasm diff --git a/Cargo.toml b/Cargo.toml index e86dd475..35b0f1a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "chain", + "client", "types", ] resolver = "2" diff --git a/client/Cargo.toml b/client/Cargo.toml new file mode 100644 index 00000000..65edf142 --- /dev/null +++ b/client/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "alto-client" +version = "0.0.2" +publish = true +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Utilities for interacting with alto." +readme = "README.md" +homepage = "https://alto.commonware.xyz" +repository = "https://github.com/commonwarexyz/alto/tree/main/client" +documentation = "https://docs.rs/alto-client" + +[lib] +crate-type = ["rlib", "cdylib"] + +[dependencies] +commonware-cryptography = { workspace = true } +commonware-utils = { workspace = true } +bytes = { workspace = true } +rand = { workspace = true } +thiserror = { workspace = true } + +# Enable "js" feature when WASM is target +[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom] +version = "0.2.15" +features = ["js"] diff --git a/client/README.md b/client/README.md new file mode 100644 index 00000000..f697e3ab --- /dev/null +++ b/client/README.md @@ -0,0 +1,10 @@ +# alto-client + +[![Crates.io](https://img.shields.io/crates/v/alto-client.svg)](https://crates.io/crates/alto-client) +[![Docs.rs](https://docs.rs/alto-client/badge.svg)](https://docs.rs/alto-client) + +Utilities for interacting with `alto`. + +## Status + +`alto-client` is **ALPHA** software and is not yet recommended for production use. Developers should expect breaking changes and occasional instability. \ No newline at end of file From 8ff6348c886694f349cabfaf9e9d9a3ee44c1d7b Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sun, 9 Mar 2025 16:21:38 -0700 Subject: [PATCH 2/5] compiles --- Cargo.lock | 74 +++++++++ client/Cargo.toml | 5 + client/src/consensus.rs | 351 ++++++++++++++++++++++++++++++++++++++++ client/src/lib.rs | 68 ++++++++ client/src/utils.rs | 20 +++ 5 files changed, 518 insertions(+) create mode 100644 client/src/consensus.rs create mode 100644 client/src/lib.rs create mode 100644 client/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index f79546f6..7ce274d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,23 @@ dependencies = [ "uuid", ] +[[package]] +name = "alto-client" +version = "0.0.2" +dependencies = [ + "alto-types", + "bytes", + "commonware-cryptography", + "commonware-utils", + "futures", + "getrandom 0.2.15", + "rand", + "reqwest", + "thiserror 2.0.12", + "tokio", + "tokio-tungstenite", +] + [[package]] name = "alto-types" version = "0.0.2" @@ -566,6 +583,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -2890,6 +2913,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.9.9" @@ -3271,6 +3305,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.13" @@ -3389,6 +3437,26 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http 0.2.12", + "httparse", + "log", + "native-tls", + "rand", + "sha-1", + "thiserror 1.0.69", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.18.0" @@ -3440,6 +3508,12 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf16_iter" version = "1.0.5" diff --git a/client/Cargo.toml b/client/Cargo.toml index 65edf142..b2a638b7 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -14,11 +14,16 @@ documentation = "https://docs.rs/alto-client" crate-type = ["rlib", "cdylib"] [dependencies] +alto-types = { workspace = true } commonware-cryptography = { workspace = true } commonware-utils = { workspace = true } bytes = { workspace = true } rand = { workspace = true } thiserror = { workspace = true } +futures = { workspace = true } +reqwest = "0.12.12" +tokio-tungstenite = { version = "0.17", features = ["native-tls"] } +tokio = { version = "1.40.0", features = ["full"] } # Enable "js" feature when WASM is target [target.'cfg(target_arch = "wasm32")'.dependencies.getrandom] diff --git a/client/src/consensus.rs b/client/src/consensus.rs new file mode 100644 index 00000000..8ade689e --- /dev/null +++ b/client/src/consensus.rs @@ -0,0 +1,351 @@ +use crate::{Client, Error, IndexQuery, Query}; +use alto_types::{ + Block, Finalization, Finalized, Kind, Notarization, Notarized, Nullification, Seed, +}; +use futures::{channel::mpsc::unbounded, Stream, StreamExt}; +use tokio_tungstenite::{connect_async, tungstenite::Message}; + +fn seed_upload_path(base: String) -> String { + format!("{}/seed", base) +} + +fn seed_get_path(base: String, query: &IndexQuery) -> String { + format!("{}/seed/{}", base, query.serialize()) +} + +fn nullification_upload_path(base: String) -> String { + format!("{}/nullification", base) +} + +fn nullification_get_path(base: String, query: &IndexQuery) -> String { + format!("{}/nullification/{}", base, query.serialize()) +} + +fn notarization_upload_path(base: String) -> String { + format!("{}/notarization", base) +} + +fn notarization_get_path(base: String, query: &IndexQuery) -> String { + format!("{}/notarization/{}", base, query.serialize()) +} + +fn finalization_upload_path(base: String) -> String { + format!("{}/finalization", base) +} + +fn finalization_get_path(base: String, query: &IndexQuery) -> String { + format!("{}/finalization/{}", base, query.serialize()) +} + +fn consensus_register_path(base: String) -> String { + format!("{}/consensus/ws", base) +} + +pub enum BlockPayload { + Finalized(Box), + Block(Block), +} + +pub enum ConsensusMessage { + Seed(Seed), + Nullification(Nullification), + Notarization(Notarized), + Finalization(Finalized), +} + +/// There is no block upload path. Blocks are uploaded as a byproduct of notarization +/// and finalization uploads. +fn block_get_path(base: String, query: &Query) -> String { + format!("{}/block/{}", base, query.serialize()) +} + +impl Client { + pub async fn seed_upload(&self, seed: Seed) -> Result<(), Error> { + let request = seed.serialize(); + let client = reqwest::Client::new(); + let result = client + .post(seed_upload_path(self.uri.clone())) + .body(request) + .send() + .await + .map_err(Error::Reqwest)?; + if !result.status().is_success() { + return Err(Error::Failed(result.status())); + } + Ok(()) + } + + pub async fn seed_get(&self, query: IndexQuery) -> Result { + // Get the seed + let client = reqwest::Client::new(); + let result = client + .get(seed_get_path(self.uri.clone(), &query)) + .send() + .await + .map_err(Error::Reqwest)?; + if !result.status().is_success() { + return Err(Error::Failed(result.status())); + } + let bytes = result.bytes().await.map_err(Error::Reqwest)?; + let result = Seed::deserialize(Some(&self.public), &bytes).ok_or(Error::InvalidData)?; + + // Verify the seed matches the query + match query { + IndexQuery::Latest => {} + IndexQuery::Index(index) => { + if result.view != index { + return Err(Error::InvalidData); + } + } + } + Ok(result) + } + + pub async fn nullification_upload(&self, nullification: Nullification) -> Result<(), Error> { + let request = nullification.serialize(); + let client = reqwest::Client::new(); + let result = client + .post(nullification_upload_path(self.uri.clone())) + .body(request) + .send() + .await + .map_err(Error::Reqwest)?; + if !result.status().is_success() { + return Err(Error::Failed(result.status())); + } + Ok(()) + } + + pub async fn nullification_get(&self, query: IndexQuery) -> Result { + // Get the nullification + let client = reqwest::Client::new(); + let result = client + .get(nullification_get_path(self.uri.clone(), &query)) + .send() + .await + .map_err(Error::Reqwest)?; + if !result.status().is_success() { + return Err(Error::Failed(result.status())); + } + let bytes = result.bytes().await.map_err(Error::Reqwest)?; + let result = + Nullification::deserialize(Some(&self.public), &bytes).ok_or(Error::InvalidData)?; + + // Verify the nullification matches the query + match query { + IndexQuery::Latest => {} + IndexQuery::Index(index) => { + if result.view != index { + return Err(Error::InvalidData); + } + } + } + Ok(result) + } + + pub async fn notarization_upload( + &self, + proof: Notarization, + block: Block, + ) -> Result<(), Error> { + let request = Notarized::new(proof, block).serialize(); + let client = reqwest::Client::new(); + let result = client + .post(notarization_upload_path(self.uri.clone())) + .body(request) + .send() + .await + .map_err(Error::Reqwest)?; + if !result.status().is_success() { + return Err(Error::Failed(result.status())); + } + Ok(()) + } + + pub async fn notarization_get(&self, query: IndexQuery) -> Result { + // Get the notarization + let client = reqwest::Client::new(); + let result = client + .get(notarization_get_path(self.uri.clone(), &query)) + .send() + .await + .map_err(Error::Reqwest)?; + if !result.status().is_success() { + return Err(Error::Failed(result.status())); + } + let bytes = result.bytes().await.map_err(Error::Reqwest)?; + let result = + Notarized::deserialize(Some(&self.public), &bytes).ok_or(Error::InvalidData)?; + + // Verify the notarization matches the query + match query { + IndexQuery::Latest => {} + IndexQuery::Index(index) => { + if result.proof.view != index { + return Err(Error::InvalidData); + } + } + } + Ok(result) + } + + pub async fn finalization_upload( + &self, + proof: Finalization, + block: Block, + ) -> Result<(), Error> { + let request = Finalized::new(proof, block).serialize(); + let client = reqwest::Client::new(); + let result = client + .post(finalization_upload_path(self.uri.clone())) + .body(request) + .send() + .await + .map_err(Error::Reqwest)?; + if !result.status().is_success() { + return Err(Error::Failed(result.status())); + } + Ok(()) + } + + pub async fn finalization_get(&self, query: IndexQuery) -> Result { + // Get the finalization + let client = reqwest::Client::new(); + let result = client + .get(finalization_get_path(self.uri.clone(), &query)) + .send() + .await + .map_err(Error::Reqwest)?; + if !result.status().is_success() { + return Err(Error::Failed(result.status())); + } + let bytes = result.bytes().await.map_err(Error::Reqwest)?; + let result = + Finalized::deserialize(Some(&self.public), &bytes).ok_or(Error::InvalidData)?; + + // Verify the finalization matches the query + match query { + IndexQuery::Latest => {} + IndexQuery::Index(index) => { + if result.proof.view != index { + return Err(Error::InvalidData); + } + } + } + Ok(result) + } + + pub async fn block_get(&self, query: Query) -> Result { + // Get the block + let client = reqwest::Client::new(); + let result = client + .get(block_get_path(self.uri.clone(), &query)) + .send() + .await + .map_err(Error::Reqwest)?; + if !result.status().is_success() { + return Err(Error::Failed(result.status())); + } + let bytes = result.bytes().await.map_err(Error::Reqwest)?; + + // Verify the block matches the query + let result = match query { + Query::Latest => { + let result = + Finalized::deserialize(Some(&self.public), &bytes).ok_or(Error::InvalidData)?; + BlockPayload::Finalized(Box::new(result)) + } + Query::Index(index) => { + let result = + Finalized::deserialize(Some(&self.public), &bytes).ok_or(Error::InvalidData)?; + if result.block.height != index { + return Err(Error::InvalidData); + } + BlockPayload::Finalized(Box::new(result)) + } + Query::Digest(digest) => { + let result = Block::deserialize(&bytes).ok_or(Error::InvalidData)?; + if result.digest() != digest { + return Err(Error::InvalidData); + } + BlockPayload::Block(result) + } + }; + Ok(result) + } + + pub async fn consensus_register( + &self, + ) -> Result>, Error> { + // Connect to the websocket endpoint + let (stream, _) = connect_async(consensus_register_path(self.ws_uri.clone())) + .await + .map_err(Error::from)?; + let (_, read) = stream.split(); + + // Create an unbounded channel for streaming consensus messages + let public = self.public.clone(); + let (sender, receiver) = unbounded(); + tokio::spawn(async move { + read.for_each(|message| async { + match message { + Ok(Message::Binary(data)) => { + // Get kind + let kind = data[0]; + let Some(kind) = Kind::from_u8(kind) else { + let _ = sender.unbounded_send(Err(Error::InvalidData)); + return; + }; + let data = &data[1..]; + + // Deserialize the message + match kind { + Kind::Seed => { + if let Some(seed) = Seed::deserialize(Some(&public), data) { + let _ = sender.unbounded_send(Ok(ConsensusMessage::Seed(seed))); + } else { + let _ = sender.unbounded_send(Err(Error::InvalidData)); + } + } + Kind::Notarization => { + if let Some(payload) = Notarized::deserialize(Some(&public), data) { + let _ = sender.unbounded_send(Ok( + ConsensusMessage::Notarization(payload), + )); + } else { + let _ = sender.unbounded_send(Err(Error::InvalidData)); + } + } + Kind::Nullification => { + if let Some(nullification) = + Nullification::deserialize(Some(&public), data) + { + let _ = sender.unbounded_send(Ok( + ConsensusMessage::Nullification(nullification), + )); + } else { + let _ = sender.unbounded_send(Err(Error::InvalidData)); + } + } + Kind::Finalization => { + if let Some(payload) = Finalized::deserialize(Some(&public), data) { + let _ = sender.unbounded_send(Ok( + ConsensusMessage::Finalization(payload), + )); + } else { + let _ = sender.unbounded_send(Err(Error::InvalidData)); + } + } + } + } + Ok(_) => {} // Ignore non-binary messages. + Err(e) => { + let _ = sender.unbounded_send(Err(Error::from(e))); + } + } + }) + .await; + }); + Ok(receiver) + } +} diff --git a/client/src/lib.rs b/client/src/lib.rs new file mode 100644 index 00000000..eb8399e1 --- /dev/null +++ b/client/src/lib.rs @@ -0,0 +1,68 @@ +use commonware_cryptography::{bls12381, sha256::Digest}; +use commonware_utils::hex; +use thiserror::Error; + +pub mod consensus; +pub mod utils; + +const LATEST: &str = "latest"; + +pub enum Query { + Latest, + Index(u64), + Digest(Digest), +} + +impl Query { + pub fn serialize(&self) -> String { + match self { + Query::Latest => LATEST.to_string(), + Query::Index(index) => hex(&index.to_be_bytes()), + Query::Digest(digest) => hex(digest), + } + } +} + +pub enum IndexQuery { + Latest, + Index(u64), +} + +impl IndexQuery { + pub fn serialize(&self) -> String { + match self { + IndexQuery::Latest => LATEST.to_string(), + IndexQuery::Index(index) => hex(&index.to_be_bytes()), + } + } +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("reqwest error: {0}")] + Reqwest(#[from] reqwest::Error), + #[error("tungstenite error: {0}")] + Tungstenite(#[from] tokio_tungstenite::tungstenite::Error), + #[error("failed: {0}")] + Failed(reqwest::StatusCode), + #[error("invalid data")] + InvalidData, +} + +pub struct Client { + uri: String, + ws_uri: String, + public: bls12381::PublicKey, +} + +impl Client { + pub fn new(uri: &str, public: bls12381::PublicKey) -> Self { + let uri = uri.to_string(); + let ws_uri = uri.replace("http", "ws"); + Self { + uri, + ws_uri, + public, + } + } +} diff --git a/client/src/utils.rs b/client/src/utils.rs new file mode 100644 index 00000000..8d2d6a19 --- /dev/null +++ b/client/src/utils.rs @@ -0,0 +1,20 @@ +use crate::{Client, Error}; + +fn healthy_path(base: String) -> String { + format!("{}/health", base) +} + +impl Client { + pub async fn health(&self) -> Result<(), Error> { + let client = reqwest::Client::new(); + let result = client + .get(healthy_path(self.uri.clone())) + .send() + .await + .map_err(Error::from)?; + if !result.status().is_success() { + return Err(Error::Failed(result.status())); + } + Ok(()) + } +} From c30246e2cca99f67db9a4238386a5bbfce3795ad Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sun, 9 Mar 2025 16:41:54 -0700 Subject: [PATCH 3/5] remove WASM test --- .github/workflows/tests.yml | 2 -- Cargo.lock | 1 - client/Cargo.toml | 8 -------- 3 files changed, 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e7043fb6..1a3c57a4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -75,5 +75,3 @@ jobs: run: rustup target add wasm32-unknown-unknown - name: Build types run: cargo build --target wasm32-unknown-unknown --release --manifest-path types/Cargo.toml && du -h target/wasm32-unknown-unknown/release/alto_types.wasm - - name: Build client - run: cargo build --target wasm32-unknown-unknown --release --manifest-path client/Cargo.toml && du -h target/wasm32-unknown-unknown/release/alto_client.wasm diff --git a/Cargo.lock b/Cargo.lock index 7ce274d2..b93733c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,7 +76,6 @@ dependencies = [ "commonware-cryptography", "commonware-utils", "futures", - "getrandom 0.2.15", "rand", "reqwest", "thiserror 2.0.12", diff --git a/client/Cargo.toml b/client/Cargo.toml index b2a638b7..311c9100 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -10,9 +10,6 @@ homepage = "https://alto.commonware.xyz" repository = "https://github.com/commonwarexyz/alto/tree/main/client" documentation = "https://docs.rs/alto-client" -[lib] -crate-type = ["rlib", "cdylib"] - [dependencies] alto-types = { workspace = true } commonware-cryptography = { workspace = true } @@ -24,8 +21,3 @@ futures = { workspace = true } reqwest = "0.12.12" tokio-tungstenite = { version = "0.17", features = ["native-tls"] } tokio = { version = "1.40.0", features = ["full"] } - -# Enable "js" feature when WASM is target -[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom] -version = "0.2.15" -features = ["js"] From 9d3f13b7ca98da0ec5562ae8edd6dd288545bcf3 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sun, 9 Mar 2025 16:45:50 -0700 Subject: [PATCH 4/5] add client cleanup --- client/src/consensus.rs | 53 +++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/client/src/consensus.rs b/client/src/consensus.rs index 8ade689e..810a25fa 100644 --- a/client/src/consensus.rs +++ b/client/src/consensus.rs @@ -3,7 +3,7 @@ use alto_types::{ Block, Finalization, Finalized, Kind, Notarization, Notarized, Nullification, Seed, }; use futures::{channel::mpsc::unbounded, Stream, StreamExt}; -use tokio_tungstenite::{connect_async, tungstenite::Message}; +use tokio_tungstenite::{connect_async, tungstenite::Message as TMessage}; fn seed_upload_path(base: String) -> String { format!("{}/seed", base) @@ -37,28 +37,28 @@ fn finalization_get_path(base: String, query: &IndexQuery) -> String { format!("{}/finalization/{}", base, query.serialize()) } -fn consensus_register_path(base: String) -> String { +/// There is no block upload path. Blocks are uploaded as a byproduct of notarization +/// and finalization uploads. +fn block_get_path(base: String, query: &Query) -> String { + format!("{}/block/{}", base, query.serialize()) +} + +fn register_path(base: String) -> String { format!("{}/consensus/ws", base) } -pub enum BlockPayload { +pub enum Payload { Finalized(Box), Block(Block), } -pub enum ConsensusMessage { +pub enum Message { Seed(Seed), Nullification(Nullification), Notarization(Notarized), Finalization(Finalized), } -/// There is no block upload path. Blocks are uploaded as a byproduct of notarization -/// and finalization uploads. -fn block_get_path(base: String, query: &Query) -> String { - format!("{}/block/{}", base, query.serialize()) -} - impl Client { pub async fn seed_upload(&self, seed: Seed) -> Result<(), Error> { let request = seed.serialize(); @@ -235,7 +235,7 @@ impl Client { Ok(result) } - pub async fn block_get(&self, query: Query) -> Result { + pub async fn block_get(&self, query: Query) -> Result { // Get the block let client = reqwest::Client::new(); let result = client @@ -253,7 +253,7 @@ impl Client { Query::Latest => { let result = Finalized::deserialize(Some(&self.public), &bytes).ok_or(Error::InvalidData)?; - BlockPayload::Finalized(Box::new(result)) + Payload::Finalized(Box::new(result)) } Query::Index(index) => { let result = @@ -261,24 +261,22 @@ impl Client { if result.block.height != index { return Err(Error::InvalidData); } - BlockPayload::Finalized(Box::new(result)) + Payload::Finalized(Box::new(result)) } Query::Digest(digest) => { let result = Block::deserialize(&bytes).ok_or(Error::InvalidData)?; if result.digest() != digest { return Err(Error::InvalidData); } - BlockPayload::Block(result) + Payload::Block(result) } }; Ok(result) } - pub async fn consensus_register( - &self, - ) -> Result>, Error> { + pub async fn register(&self) -> Result>, Error> { // Connect to the websocket endpoint - let (stream, _) = connect_async(consensus_register_path(self.ws_uri.clone())) + let (stream, _) = connect_async(register_path(self.ws_uri.clone())) .await .map_err(Error::from)?; let (_, read) = stream.split(); @@ -289,7 +287,7 @@ impl Client { tokio::spawn(async move { read.for_each(|message| async { match message { - Ok(Message::Binary(data)) => { + Ok(TMessage::Binary(data)) => { // Get kind let kind = data[0]; let Some(kind) = Kind::from_u8(kind) else { @@ -302,16 +300,15 @@ impl Client { match kind { Kind::Seed => { if let Some(seed) = Seed::deserialize(Some(&public), data) { - let _ = sender.unbounded_send(Ok(ConsensusMessage::Seed(seed))); + let _ = sender.unbounded_send(Ok(Message::Seed(seed))); } else { let _ = sender.unbounded_send(Err(Error::InvalidData)); } } Kind::Notarization => { if let Some(payload) = Notarized::deserialize(Some(&public), data) { - let _ = sender.unbounded_send(Ok( - ConsensusMessage::Notarization(payload), - )); + let _ = + sender.unbounded_send(Ok(Message::Notarization(payload))); } else { let _ = sender.unbounded_send(Err(Error::InvalidData)); } @@ -320,18 +317,16 @@ impl Client { if let Some(nullification) = Nullification::deserialize(Some(&public), data) { - let _ = sender.unbounded_send(Ok( - ConsensusMessage::Nullification(nullification), - )); + let _ = sender + .unbounded_send(Ok(Message::Nullification(nullification))); } else { let _ = sender.unbounded_send(Err(Error::InvalidData)); } } Kind::Finalization => { if let Some(payload) = Finalized::deserialize(Some(&public), data) { - let _ = sender.unbounded_send(Ok( - ConsensusMessage::Finalization(payload), - )); + let _ = + sender.unbounded_send(Ok(Message::Finalization(payload))); } else { let _ = sender.unbounded_send(Err(Error::InvalidData)); } From 218362dc50f910a991e594e5349afe7321fce07e Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sun, 9 Mar 2025 16:49:27 -0700 Subject: [PATCH 5/5] update names --- Cargo.toml | 1 + README.md | 1 + client/Cargo.toml | 2 +- client/README.md | 2 +- client/src/lib.rs | 2 ++ 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 35b0f1a3..95121ec6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ resolver = "2" [workspace.dependencies] +alto-client = { version = "0.0.2", path = "client" } alto-types = { version = "0.0.2", path = "types" } commonware-consensus = { version = "0.0.40" } commonware-cryptography = { version = "0.0.40" } diff --git a/README.md b/README.md index ab00bfb2..bb945cf3 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ ## Components * [chain](./chain/README.md): A minimal blockchain built with the [Commonware Library](https://github.com/commonwarexyz/monorepo). +* [client](./client/README.md): Client for interacting with `alto`. * [types](./types/README.md): Common types used throughout `alto`. ## Licensing diff --git a/client/Cargo.toml b/client/Cargo.toml index 311c9100..40bbb016 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.2" publish = true edition = "2021" license = "MIT OR Apache-2.0" -description = "Utilities for interacting with alto." +description = "Client for interacting with alto." readme = "README.md" homepage = "https://alto.commonware.xyz" repository = "https://github.com/commonwarexyz/alto/tree/main/client" diff --git a/client/README.md b/client/README.md index f697e3ab..12bb4f46 100644 --- a/client/README.md +++ b/client/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/alto-client.svg)](https://crates.io/crates/alto-client) [![Docs.rs](https://docs.rs/alto-client/badge.svg)](https://docs.rs/alto-client) -Utilities for interacting with `alto`. +Client for interacting with `alto`. ## Status diff --git a/client/src/lib.rs b/client/src/lib.rs index eb8399e1..fe7e1c0a 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,3 +1,5 @@ +//! Client for interacting with `alto`. + use commonware_cryptography::{bls12381, sha256::Digest}; use commonware_utils::hex; use thiserror::Error;