battleware_client/
lib.rs

1pub mod client;
2pub mod consensus;
3pub mod events;
4
5pub use client::Client;
6pub use events::Stream;
7use thiserror::Error;
8
9/// Error type for client operations.
10#[derive(Error, Debug)]
11pub enum Error {
12    #[error("reqwest error: {0}")]
13    Reqwest(#[from] reqwest::Error),
14    #[error("tungstenite error: {0}")]
15    Tungstenite(#[from] tokio_tungstenite::tungstenite::Error),
16    #[error("failed: {0}")]
17    Failed(reqwest::StatusCode),
18    #[error("invalid data: {0}")]
19    InvalidData(#[from] commonware_codec::Error),
20    #[error("invalid signature")]
21    InvalidSignature,
22    #[error("unexpected response")]
23    UnexpectedResponse,
24    #[error("connection closed")]
25    ConnectionClosed,
26    #[error("URL parse error: {0}")]
27    Url(#[from] url::ParseError),
28    #[error("dial timeout")]
29    DialTimeout,
30}
31
32/// Result type for client operations.
33pub type Result<T> = std::result::Result<T, Error>;
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38    use battleware_execution::mocks::{
39        create_account_keypair, create_adbs, create_network_keypair, create_seed, execute_block,
40    };
41    use battleware_simulator::{Api, Simulator};
42    use battleware_types::{
43        api::{Update, UpdatesFilter},
44        execution::{Instruction, Key, Stats, Transaction, Value},
45        Identity, Query, Seed,
46    };
47    use commonware_consensus::Viewable;
48    use commonware_cryptography::bls12381::primitives::group::Private;
49    use commonware_runtime::{deterministic::Runner, Runner as _};
50    use commonware_storage::store::operation::Variable;
51    use std::{net::SocketAddr, sync::Arc};
52    use tokio::time::{sleep, Duration};
53
54    struct TestContext {
55        network_secret: Private,
56        network_identity: Identity,
57        simulator: Arc<Simulator>,
58        base_url: String,
59        server_handle: tokio::task::JoinHandle<()>,
60    }
61
62    impl TestContext {
63        async fn new() -> Self {
64            let (network_secret, network_identity) = create_network_keypair();
65            let simulator = Arc::new(Simulator::new(network_identity));
66            let api = Api::new(simulator.clone());
67
68            // Start server on random port
69            let addr = SocketAddr::from(([127, 0, 0, 1], 0));
70            let router = api.router();
71            let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
72            let actual_addr = listener.local_addr().unwrap();
73            let base_url = format!("http://{actual_addr}");
74
75            let server_handle = tokio::spawn(async move {
76                axum::serve(listener, router).await.unwrap();
77            });
78
79            // Give server time to start
80            sleep(Duration::from_millis(100)).await;
81
82            Self {
83                network_secret,
84                network_identity,
85                simulator,
86                base_url,
87                server_handle,
88            }
89        }
90
91        fn create_client(&self) -> Client {
92            Client::new(&self.base_url, self.network_identity)
93        }
94
95        fn create_seed(&self, view: u64) -> Seed {
96            create_seed(&self.network_secret, view)
97        }
98    }
99
100    impl Drop for TestContext {
101        fn drop(&mut self) {
102            self.server_handle.abort();
103        }
104    }
105
106    #[tokio::test]
107    async fn test_client_seed_operations() {
108        let ctx = TestContext::new().await;
109        let client = ctx.create_client();
110
111        // Upload seed
112        let seed = ctx.create_seed(1);
113        client.submit_seed(seed.clone()).await.unwrap();
114
115        // Get seed by index
116        let retrieved = client.query_seed(Query::Index(1)).await.unwrap();
117        assert_eq!(retrieved, Some(seed.clone()));
118
119        // Get latest seed
120        let latest = client.query_seed(Query::Latest).await.unwrap();
121        assert_eq!(latest, Some(seed));
122
123        // Upload another seed
124        let seed2 = ctx.create_seed(5);
125        client.submit_seed(seed2.clone()).await.unwrap();
126
127        // Get latest should now return seed2
128        let latest = client.query_seed(Query::Latest).await.unwrap();
129        assert_eq!(latest, Some(seed2.clone()));
130
131        // Get specific seed by index
132        let retrieved = client.query_seed(Query::Index(5)).await.unwrap();
133        assert_eq!(retrieved, Some(seed2));
134
135        // Query for non-existent seed
136        let result = client.query_seed(Query::Index(3)).await.unwrap();
137        assert!(result.is_none());
138    }
139
140    #[tokio::test]
141    async fn test_client_transaction_submission() {
142        let ctx = TestContext::new().await;
143        let client = ctx.create_client();
144
145        // Create and submit transaction
146        let (private, _) = create_account_keypair(1);
147        let tx = Transaction::sign(&private, 0, Instruction::Generate);
148
149        // Should succeed even though transaction isn't processed yet
150        client.submit_transactions(vec![tx]).await.unwrap();
151
152        // Submit another transaction with higher nonce
153        let tx2 = Transaction::sign(&private, 1, Instruction::Generate);
154        client.submit_transactions(vec![tx2]).await.unwrap();
155    }
156
157    #[tokio::test]
158    async fn test_client_summary_submission() {
159        // Setup server outside deterministic runtime
160        let ctx = TestContext::new().await;
161        let client = ctx.create_client();
162        let network_secret = ctx.network_secret.clone();
163        let network_identity = ctx.network_identity;
164
165        // Create transaction
166        let (private, _) = create_account_keypair(1);
167        let tx = Transaction::sign(&private, 0, Instruction::Generate);
168
169        // Create summary in deterministic runtime
170        let executor = Runner::default();
171        let (_, summary) = executor.start(|context| async move {
172            let (mut state, mut events) = create_adbs(&context).await;
173            execute_block(
174                &network_secret,
175                network_identity,
176                &mut state,
177                &mut events,
178                1, // view
179                vec![tx],
180            )
181            .await
182        });
183
184        // Submit summary
185        client.submit_summary(summary).await.unwrap();
186    }
187
188    #[tokio::test]
189    async fn test_client_state_query() {
190        // Setup server outside deterministic runtime
191        let ctx = TestContext::new().await;
192        let client = ctx.create_client();
193        let network_secret = ctx.network_secret.clone();
194        let network_identity = ctx.network_identity;
195        let simulator = ctx.simulator.clone();
196
197        // Create and process transaction
198        let (private, public) = create_account_keypair(1);
199        let tx = Transaction::sign(&private, 0, Instruction::Generate);
200
201        // Create summary in deterministic runtime
202        let executor = Runner::default();
203        let (_, summary) = executor.start(|context| async move {
204            let (mut state, mut events) = create_adbs(&context).await;
205            execute_block(
206                &network_secret,
207                network_identity,
208                &mut state,
209                &mut events,
210                1, // view
211                vec![tx],
212            )
213            .await
214        });
215
216        // Submit to simulator
217        let (state_digests, events_digests) = summary.verify(&network_identity).unwrap();
218        simulator.submit_events(summary.clone(), events_digests);
219        simulator.submit_state(summary, state_digests);
220
221        // Query for account state
222        let account_key = Key::Account(public.clone());
223        let lookup = client.query_state(&account_key).await.unwrap();
224
225        assert!(lookup.is_some());
226        let lookup = lookup.unwrap();
227        assert!(lookup.verify(&network_identity));
228
229        // Verify account data
230        let Variable::Update(_, Value::Account(account)) = lookup.operation else {
231            panic!("Expected account value");
232        };
233        assert_eq!(account.nonce, 1);
234        assert_eq!(account.stats, Stats::default());
235
236        // Query for non-existent account
237        let (_, other_public) = create_account_keypair(2);
238        let other_key = Key::Account(other_public);
239        let result = client.query_state(&other_key).await.unwrap();
240        assert!(result.is_none());
241    }
242
243    #[tokio::test]
244    async fn test_client_updates_stream() {
245        // Setup server outside deterministic runtime
246        let ctx = TestContext::new().await;
247        let client = ctx.create_client();
248        let network_secret = ctx.network_secret.clone();
249        let network_identity = ctx.network_identity;
250        let simulator = ctx.simulator.clone();
251
252        // Connect to updates stream for all events
253        let mut stream = client.connect_updates(UpdatesFilter::All).await.unwrap();
254
255        // Test seed update
256        let seed = ctx.create_seed(10);
257        simulator.submit_seed(seed.clone());
258
259        let update = stream.next().await.unwrap().unwrap();
260        match update {
261            Update::Seed(received_seed) => {
262                assert_eq!(received_seed, seed);
263            }
264            _ => panic!("Expected seed update"),
265        }
266
267        // Test events update
268        let (private, _) = create_account_keypair(1);
269        let tx = Transaction::sign(&private, 0, Instruction::Generate);
270
271        // Create summary in deterministic runtime
272        let executor = Runner::default();
273        let (_, summary) = executor.start(|context| async move {
274            let (mut state, mut events) = create_adbs(&context).await;
275            execute_block(
276                &network_secret,
277                network_identity,
278                &mut state,
279                &mut events,
280                1, // view
281                vec![tx],
282            )
283            .await
284        });
285
286        // Submit events to simulator
287        let (_state_digests, events_digests) = summary.verify(&network_identity).unwrap();
288        simulator.submit_events(summary.clone(), events_digests);
289
290        // Receive event update from stream
291        let update = stream.next().await.unwrap().unwrap();
292        match update {
293            Update::Events(event) => {
294                assert!(event.verify(&network_identity));
295                assert_eq!(event.progress.height, 1);
296                assert_eq!(event.events_proof_ops, summary.events_proof_ops);
297            }
298            _ => panic!("Expected events update"),
299        }
300    }
301
302    #[tokio::test]
303    async fn test_client_mempool_stream() {
304        let ctx = TestContext::new().await;
305        let client = ctx.create_client();
306
307        // Connect to mempool stream
308        let mut stream = client.connect_mempool().await.unwrap();
309
310        // Submit transaction through simulator
311        let (private, _) = create_account_keypair(1);
312        let tx = Transaction::sign(&private, 0, Instruction::Generate);
313        ctx.simulator.submit_transactions(vec![tx.clone()]);
314
315        // Receive transaction from stream
316        let received_txs = stream.next().await.unwrap().unwrap();
317        assert_eq!(received_txs.transactions.len(), 1);
318        let received_tx = &received_txs.transactions[0];
319        assert_eq!(received_tx.public, tx.public);
320        assert_eq!(received_tx.nonce, tx.nonce);
321    }
322
323    #[tokio::test]
324    async fn test_client_get_current_view() {
325        let ctx = TestContext::new().await;
326        let client = ctx.create_client();
327
328        // Submit a seed
329        let seed = ctx.create_seed(42);
330        ctx.simulator.submit_seed(seed);
331
332        // Get current view
333        let view = client.query_seed(Query::Latest).await.unwrap().unwrap();
334        assert_eq!(view.view(), 42);
335    }
336
337    #[tokio::test]
338    async fn test_client_query_seed() {
339        let ctx = TestContext::new().await;
340        let client = ctx.create_client();
341
342        // Submit seed
343        let seed = ctx.create_seed(15);
344        ctx.simulator.submit_seed(seed.clone());
345
346        // Query existing seed
347        let result = client.query_seed(Query::Index(15)).await.unwrap();
348        assert_eq!(result, Some(seed));
349
350        // Query non-existent seed
351        let result = client.query_seed(Query::Index(999)).await.unwrap();
352        assert!(result.is_none());
353    }
354}