1use alto_types::{Identity, Scheme, NAMESPACE};
4use commonware_cryptography::sha256::Digest;
5use commonware_parallel::Strategy;
6use commonware_utils::hex;
7use std::sync::Arc;
8use thiserror::Error;
9
10pub mod consensus;
11pub mod utils;
12
13pub const LATEST: &str = "latest";
14
15pub enum Query {
16 Latest,
17 Index(u64),
18 Digest(Digest),
19}
20
21impl Query {
22 pub fn serialize(&self) -> String {
23 match self {
24 Query::Latest => LATEST.to_string(),
25 Query::Index(index) => hex(&index.to_be_bytes()),
26 Query::Digest(digest) => hex(digest),
27 }
28 }
29}
30
31pub enum IndexQuery {
32 Latest,
33 Index(u64),
34}
35
36impl IndexQuery {
37 pub fn serialize(&self) -> String {
38 match self {
39 IndexQuery::Latest => LATEST.to_string(),
40 IndexQuery::Index(index) => hex(&index.to_be_bytes()),
41 }
42 }
43}
44
45#[derive(Error, Debug)]
46pub enum Error {
47 #[error("reqwest error: {0}")]
48 Reqwest(#[from] reqwest::Error),
49 #[error("tungstenite error: {0}")]
50 Tungstenite(#[from] tokio_tungstenite::tungstenite::Error),
51 #[error("failed: {0}")]
52 Failed(reqwest::StatusCode),
53 #[error("invalid data: {0}")]
54 InvalidData(#[from] commonware_codec::Error),
55 #[error("invalid signature")]
56 InvalidSignature,
57 #[error("unexpected response")]
58 UnexpectedResponse,
59}
60
61type WsConnector = tokio_tungstenite::Connector;
63
64pub struct ClientBuilder<S: Strategy> {
66 uri: String,
67 ws_uri: String,
68 identity: Identity,
69 tls_certs: Vec<Vec<u8>>,
70 strategy: S,
71 verify: bool,
72}
73
74impl<S: Strategy> ClientBuilder<S> {
75 pub fn new(uri: &str, identity: Identity, strategy: S) -> Self {
77 let uri = uri.to_string();
78 let ws_uri = if let Some(rest) = uri.strip_prefix("https://") {
79 format!("wss://{rest}")
80 } else if let Some(rest) = uri.strip_prefix("http://") {
81 format!("ws://{rest}")
82 } else {
83 panic!("URI must start with http:// or https://");
84 };
85 Self {
86 uri,
87 ws_uri,
88 identity,
89 tls_certs: Vec::new(),
90 strategy,
91 verify: true,
92 }
93 }
94
95 pub fn with_verification_disabled(mut self) -> Self {
97 self.verify = false;
98 self
99 }
100
101 pub fn with_tls_cert(mut self, cert_der: Vec<u8>) -> Self {
105 self.tls_certs.push(cert_der);
106 self
107 }
108
109 pub fn build(self) -> Client<S> {
111 let certificate_verifier = Scheme::certificate_verifier(NAMESPACE, self.identity);
112
113 let mut http_builder = reqwest::Client::builder()
116 .tcp_nodelay(true)
117 .connect_timeout(std::time::Duration::from_secs(5))
118 .timeout(std::time::Duration::from_secs(10))
119 .http2_adaptive_window(true)
120 .http2_keep_alive_interval(std::time::Duration::from_secs(10))
121 .http2_keep_alive_timeout(std::time::Duration::from_secs(5))
122 .http2_keep_alive_while_idle(true);
123 for cert_der in &self.tls_certs {
124 let cert = reqwest::Certificate::from_der(cert_der).expect("invalid DER certificate");
125 http_builder = http_builder.add_root_certificate(cert);
126 }
127 let http_client = http_builder.build().expect("failed to build HTTP client");
128
129 let mut root_store = rustls::RootCertStore::empty();
131 for cert in rustls_native_certs::load_native_certs().expect("failed to load native certs") {
132 root_store
133 .add(cert)
134 .expect("failed to add native certificate");
135 }
136 for cert_der in &self.tls_certs {
137 let cert = rustls::pki_types::CertificateDer::from(cert_der.clone());
138 root_store.add(cert).expect("failed to add certificate");
139 }
140 let ws_config = rustls::ClientConfig::builder_with_provider(Arc::new(
141 rustls::crypto::aws_lc_rs::default_provider(),
142 ))
143 .with_safe_default_protocol_versions()
144 .expect("failed to set protocol versions")
145 .with_root_certificates(root_store)
146 .with_no_client_auth();
147 let ws_connector = WsConnector::Rustls(Arc::new(ws_config));
148
149 Client {
150 uri: self.uri,
151 ws_uri: self.ws_uri,
152 certificate_verifier,
153 verify: self.verify,
154 http_client,
155 ws_connector,
156 strategy: self.strategy,
157 }
158 }
159}
160
161#[derive(Clone)]
162pub struct Client<S: Strategy> {
163 uri: String,
164 ws_uri: String,
165 certificate_verifier: Scheme,
166 verify: bool,
167
168 http_client: reqwest::Client,
169 ws_connector: WsConnector,
170 strategy: S,
171}
172
173impl<S: Strategy> Client<S> {
174 pub fn new(uri: &str, identity: Identity, strategy: S) -> Self {
183 ClientBuilder::new(uri, identity, strategy).build()
184 }
185}