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}
72
73impl<S: Strategy> ClientBuilder<S> {
74 pub fn new(uri: &str, identity: Identity, strategy: S) -> Self {
76 let uri = uri.to_string();
77 let ws_uri = if let Some(rest) = uri.strip_prefix("https://") {
78 format!("wss://{rest}")
79 } else if let Some(rest) = uri.strip_prefix("http://") {
80 format!("ws://{rest}")
81 } else {
82 panic!("URI must start with http:// or https://");
83 };
84 Self {
85 uri,
86 ws_uri,
87 identity,
88 tls_certs: Vec::new(),
89 strategy,
90 }
91 }
92
93 pub fn with_tls_cert(mut self, cert_der: Vec<u8>) -> Self {
97 self.tls_certs.push(cert_der);
98 self
99 }
100
101 pub fn build(self) -> Client<S> {
103 let certificate_verifier = Scheme::certificate_verifier(NAMESPACE, self.identity);
104
105 let mut http_builder = reqwest::Client::builder();
107 for cert_der in &self.tls_certs {
108 let cert = reqwest::Certificate::from_der(cert_der).expect("invalid DER certificate");
109 http_builder = http_builder.add_root_certificate(cert);
110 }
111 let http_client = http_builder.build().expect("failed to build HTTP client");
112
113 let mut root_store = rustls::RootCertStore::empty();
115 for cert in rustls_native_certs::load_native_certs().expect("failed to load native certs") {
116 root_store
117 .add(cert)
118 .expect("failed to add native certificate");
119 }
120 for cert_der in &self.tls_certs {
121 let cert = rustls::pki_types::CertificateDer::from(cert_der.clone());
122 root_store.add(cert).expect("failed to add certificate");
123 }
124 let ws_config = rustls::ClientConfig::builder_with_provider(Arc::new(
125 rustls::crypto::aws_lc_rs::default_provider(),
126 ))
127 .with_safe_default_protocol_versions()
128 .expect("failed to set protocol versions")
129 .with_root_certificates(root_store)
130 .with_no_client_auth();
131 let ws_connector = WsConnector::Rustls(Arc::new(ws_config));
132
133 Client {
134 uri: self.uri,
135 ws_uri: self.ws_uri,
136 certificate_verifier,
137 http_client,
138 ws_connector,
139 strategy: self.strategy,
140 }
141 }
142}
143
144#[derive(Clone)]
145pub struct Client<S: Strategy> {
146 uri: String,
147 ws_uri: String,
148 certificate_verifier: Scheme,
149
150 http_client: reqwest::Client,
151 ws_connector: WsConnector,
152 strategy: S,
153}
154
155impl<S: Strategy> Client<S> {
156 pub fn new(uri: &str, identity: Identity, strategy: S) -> Self {
165 ClientBuilder::new(uri, identity, strategy).build()
166 }
167}