1pub mod client;
2pub mod consensus;
3pub mod events;
4
5pub use client::Client;
6pub use events::Stream;
7use thiserror::Error;
8
9#[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
32pub 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 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 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 let seed = ctx.create_seed(1);
113 client.submit_seed(seed.clone()).await.unwrap();
114
115 let retrieved = client.query_seed(Query::Index(1)).await.unwrap();
117 assert_eq!(retrieved, Some(seed.clone()));
118
119 let latest = client.query_seed(Query::Latest).await.unwrap();
121 assert_eq!(latest, Some(seed));
122
123 let seed2 = ctx.create_seed(5);
125 client.submit_seed(seed2.clone()).await.unwrap();
126
127 let latest = client.query_seed(Query::Latest).await.unwrap();
129 assert_eq!(latest, Some(seed2.clone()));
130
131 let retrieved = client.query_seed(Query::Index(5)).await.unwrap();
133 assert_eq!(retrieved, Some(seed2));
134
135 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 let (private, _) = create_account_keypair(1);
147 let tx = Transaction::sign(&private, 0, Instruction::Generate);
148
149 client.submit_transactions(vec![tx]).await.unwrap();
151
152 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 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 let (private, _) = create_account_keypair(1);
167 let tx = Transaction::sign(&private, 0, Instruction::Generate);
168
169 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, vec![tx],
180 )
181 .await
182 });
183
184 client.submit_summary(summary).await.unwrap();
186 }
187
188 #[tokio::test]
189 async fn test_client_state_query() {
190 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 let (private, public) = create_account_keypair(1);
199 let tx = Transaction::sign(&private, 0, Instruction::Generate);
200
201 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, vec![tx],
212 )
213 .await
214 });
215
216 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 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 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 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 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 let mut stream = client.connect_updates(UpdatesFilter::All).await.unwrap();
254
255 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 let (private, _) = create_account_keypair(1);
269 let tx = Transaction::sign(&private, 0, Instruction::Generate);
270
271 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, vec![tx],
282 )
283 .await
284 });
285
286 let (_state_digests, events_digests) = summary.verify(&network_identity).unwrap();
288 simulator.submit_events(summary.clone(), events_digests);
289
290 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 let mut stream = client.connect_mempool().await.unwrap();
309
310 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 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 let seed = ctx.create_seed(42);
330 ctx.simulator.submit_seed(seed);
331
332 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 let seed = ctx.create_seed(15);
344 ctx.simulator.submit_seed(seed.clone());
345
346 let result = client.query_seed(Query::Index(15)).await.unwrap();
348 assert_eq!(result, Some(seed));
349
350 let result = client.query_seed(Query::Index(999)).await.unwrap();
352 assert!(result.is_none());
353 }
354}