linera_faucet_client/
lib.rs1use std::collections::BTreeMap;
9
10use linera_base::{crypto::ValidatorPublicKey, data_types::ChainDescription};
11use linera_client::config::GenesisConfig;
12use linera_execution::{committee::ValidatorState, Committee, ResourceControlPolicy};
13use linera_version::VersionInfo;
14use thiserror_context::Context;
15
16#[derive(Debug, thiserror::Error)]
17#[non_exhaustive]
18pub enum ErrorInner {
19 #[error("JSON parsing error: {0:?}")]
20 Json(#[from] serde_json::Error),
21 #[error("GraphQL error: {0:?}")]
22 GraphQl(Vec<serde_json::Value>),
23 #[error("HTTP error: {0:?}")]
24 Http(#[from] reqwest::Error),
25}
26
27thiserror_context::impl_context!(Error(ErrorInner));
28
29#[derive(Debug, Clone)]
31pub struct Faucet {
32 url: String,
33}
34
35impl Faucet {
36 pub fn new(url: String) -> Self {
37 Self { url }
38 }
39
40 pub fn url(&self) -> &str {
41 &self.url
42 }
43
44 async fn query<Response: serde::de::DeserializeOwned>(
45 &self,
46 query: impl AsRef<str>,
47 ) -> Result<Response, Error> {
48 let query = query.as_ref();
49
50 #[derive(serde::Deserialize)]
51 struct GraphQlResponse<T> {
52 data: Option<T>,
53 errors: Option<Vec<serde_json::Value>>,
54 }
55
56 let builder = reqwest::ClientBuilder::new();
57
58 #[cfg(not(target_arch = "wasm32"))]
59 let builder = builder.timeout(linera_base::time::Duration::from_secs(30));
60
61 let response: GraphQlResponse<Response> = builder
62 .build()
63 .unwrap()
64 .post(&self.url)
65 .json(&serde_json::json!({
66 "query": query,
67 }))
68 .send()
69 .await
70 .with_context(|| format!("executing query {query:?}"))?
71 .error_for_status()?
72 .json()
73 .await?;
74
75 if let Some(errors) = response.errors {
76 let messages = errors
78 .iter()
79 .filter_map(|error| {
80 error
81 .get("message")
82 .and_then(|msg| msg.as_str())
83 .map(|s| s.to_string())
84 })
85 .collect::<Vec<_>>();
86
87 if messages.is_empty() {
88 Err(ErrorInner::GraphQl(errors).into())
89 } else {
90 Err(
91 ErrorInner::GraphQl(vec![serde_json::Value::String(messages.join("; "))])
92 .into(),
93 )
94 }
95 } else {
96 Ok(response
97 .data
98 .expect("no errors present but no data returned"))
99 }
100 }
101
102 pub async fn genesis_config(&self) -> Result<GenesisConfig, Error> {
103 #[derive(serde::Deserialize)]
104 #[serde(rename_all = "camelCase")]
105 struct Response {
106 genesis_config: GenesisConfig,
107 }
108
109 Ok(self
110 .query::<Response>("query { genesisConfig }")
111 .await?
112 .genesis_config)
113 }
114
115 pub async fn version_info(&self) -> Result<VersionInfo, Error> {
116 #[derive(serde::Deserialize)]
117 struct Response {
118 version: VersionInfo,
119 }
120
121 Ok(self.query::<Response>("query { version }").await?.version)
122 }
123
124 pub async fn claim(
125 &self,
126 owner: &linera_base::identifiers::AccountOwner,
127 ) -> Result<ChainDescription, Error> {
128 #[derive(serde::Deserialize)]
129 struct Response {
130 claim: ChainDescription,
131 }
132 Ok(self
133 .query::<Response>(format!("mutation {{ claim(owner: \"{owner}\") }}"))
134 .await?
135 .claim)
136 }
137
138 pub async fn current_validators(&self) -> Result<Vec<(ValidatorPublicKey, String)>, Error> {
139 #[derive(serde::Deserialize)]
140 #[serde(rename_all = "camelCase")]
141 struct Validator {
142 public_key: ValidatorPublicKey,
143 network_address: String,
144 }
145
146 #[derive(serde::Deserialize)]
147 #[serde(rename_all = "camelCase")]
148 struct Response {
149 current_validators: Vec<Validator>,
150 }
151
152 Ok(self
153 .query::<Response>("query { currentValidators { publicKey networkAddress } }")
154 .await?
155 .current_validators
156 .into_iter()
157 .map(|validator| (validator.public_key, validator.network_address))
158 .collect())
159 }
160
161 pub async fn current_committee(&self) -> Result<Committee, Error> {
162 #[derive(serde::Deserialize)]
163 struct CommitteeResponse {
164 validators: BTreeMap<ValidatorPublicKey, ValidatorState>,
165 policy: ResourceControlPolicy,
166 }
167
168 #[derive(serde::Deserialize)]
169 #[serde(rename_all = "camelCase")]
170 struct Response {
171 current_committee: CommitteeResponse,
172 }
173
174 let response = self
175 .query::<Response>(
176 "query { currentCommittee { \
177 validators \
178 policy \
179 } }",
180 )
181 .await?;
182
183 let committee_response = response.current_committee;
184
185 Ok(Committee::new(
186 committee_response.validators,
187 committee_response.policy,
188 ))
189 }
190}