1use dusk_core::transfer::Transaction;
12use serde::Deserialize;
13use serde_json::Value;
14use tokio::time::{sleep, Duration};
15
16use crate::{Address, Error, RuesHttpClient};
17
18#[derive(Clone)]
23pub struct GraphQL {
24 client: RuesHttpClient,
25 status: fn(&str),
26}
27
28pub struct BlockTransaction {
31 pub tx: Transaction,
33 pub id: String,
35 pub gas_spent: u64,
37}
38
39#[derive(Deserialize)]
40struct SpentTx {
41 pub id: String,
42 #[serde(default)]
43 pub raw: String,
44 pub err: Option<String>,
45 #[serde(alias = "gasSpent", default)]
46 pub gas_spent: f64,
47}
48
49#[derive(Deserialize)]
50struct Block {
51 pub transactions: Vec<SpentTx>,
52}
53
54#[derive(Deserialize)]
55struct BlockResponse {
56 pub block: Option<Block>,
57}
58
59#[derive(Deserialize, Debug)]
60pub struct BlockData {
61 pub gas_spent: u64,
62 pub sender: String,
63 pub value: f64,
64}
65
66#[derive(Deserialize, Debug)]
67pub struct BlockEvents {
68 pub data: BlockData,
69}
70
71#[derive(Deserialize, Debug)]
72pub struct MoonlightHistory {
73 pub block_height: u64,
74 pub origin: String,
75 pub events: Vec<BlockEvents>,
76}
77
78#[derive(Deserialize, Debug)]
79pub struct MoonlightHistoryJson {
80 pub json: Vec<MoonlightHistory>,
81}
82
83#[derive(Deserialize, Debug)]
84pub struct FullMoonlightHistory {
85 #[serde(rename(deserialize = "fullMoonlightHistory"))]
86 pub full_moonlight_history: MoonlightHistoryJson,
87}
88
89#[derive(Deserialize)]
90struct SpentTxResponse {
91 pub tx: Option<SpentTx>,
92}
93
94#[derive(Debug)]
96pub enum TxStatus {
97 Ok,
98 NotFound,
99 Error(String),
100}
101
102impl GraphQL {
103 pub fn new<S: Into<String>>(
105 url: S,
106 status: fn(&str),
107 ) -> Result<Self, Error> {
108 Ok(Self {
109 client: RuesHttpClient::new(url)?,
110 status,
111 })
112 }
113
114 pub async fn wait_for(&self, tx_id: &str) -> anyhow::Result<()> {
116 loop {
117 let status = self.tx_status(tx_id).await?;
118
119 match status {
120 TxStatus::Ok => break,
121 TxStatus::Error(err) => return Err(Error::Transaction(err))?,
122 TxStatus::NotFound => {
123 (self.status)(
124 "Waiting for tx to be included into a block...",
125 );
126 sleep(Duration::from_millis(1000)).await;
127 }
128 }
129 }
130 Ok(())
131 }
132
133 async fn tx_status(&self, tx_id: &str) -> Result<TxStatus, Error> {
135 let query =
136 "query { tx(hash: \"####\") { id, err }}".replace("####", tx_id);
137 let response = self.query(&query).await?;
138 let response = serde_json::from_slice::<SpentTxResponse>(&response)?.tx;
139
140 match response {
141 Some(SpentTx { err: Some(err), .. }) => Ok(TxStatus::Error(err)),
142 Some(_) => Ok(TxStatus::Ok),
143 None => Ok(TxStatus::NotFound),
144 }
145 }
146
147 pub async fn txs_for_block(
149 &self,
150 block_height: u64,
151 ) -> Result<Vec<BlockTransaction>, Error> {
152 let query = "query { block(height: ####) { transactions {id, raw, gasSpent, err}}}"
153 .replace("####", block_height.to_string().as_str());
154
155 let response = self.query(&query).await?;
156 let response =
157 serde_json::from_slice::<BlockResponse>(&response)?.block;
158 let block = response.ok_or(GraphQLError::BlockInfo)?;
159 let mut ret = vec![];
160
161 for spent_tx in block.transactions {
162 let tx_raw = hex::decode(&spent_tx.raw)
163 .map_err(|_| GraphQLError::TxStatus)?;
164 let ph_tx = Transaction::from_slice(&tx_raw)
165 .map_err(|_| GraphQLError::BytesError)?;
166 ret.push(BlockTransaction {
167 tx: ph_tx,
168 id: spent_tx.id,
169 gas_spent: spent_tx.gas_spent as u64,
170 });
171 }
172
173 Ok(ret)
174 }
175
176 pub async fn check_connection(&self) -> Result<(), Error> {
178 self.query("").await.map(|_| ())
179 }
180
181 pub async fn moonlight_history(
184 &self,
185 address: Address,
186 ) -> Result<FullMoonlightHistory, Error> {
187 let query = format!(
188 r#"query {{ fullMoonlightHistory(address: "{address}") {{ json }} }}"#
189 );
190
191 let response = self
192 .query(&query)
193 .await
194 .map_err(|err| Error::ArchiveJsonError(err.to_string()))?;
195
196 let response =
197 serde_json::from_slice::<FullMoonlightHistory>(&response)
198 .map_err(|err| Error::ArchiveJsonError(err.to_string()))?;
199
200 Ok(response)
201 }
202
203 pub async fn moonlight_tx(
205 &self,
206 origin: &str,
207 ) -> Result<Transaction, Error> {
208 let query =
209 format!(r#"query {{ tx(hash: "{origin}") {{ tx {{ raw }} }} }}"#);
210
211 let response = self.query(&query).await?;
212 let json: Value = serde_json::from_slice(&response)?;
213
214 let tx = json
215 .get("tx")
216 .and_then(|val| val.get("tx").and_then(|val| val.get("raw")))
217 .and_then(|val| val.as_str());
218
219 if let Some(tx) = tx {
220 let hex = hex::decode(tx).map_err(|_| GraphQLError::TxStatus)?;
221 let tx: Transaction = Transaction::from_slice(&hex)?;
222 Ok(tx)
223 } else {
224 Err(Error::GraphQLError(GraphQLError::TxStatus))
225 }
226 }
227}
228
229#[derive(Debug, thiserror::Error)]
231pub enum GraphQLError {
232 #[error("Error fetching data from the node: {0}")]
234 Generic(serde_json::Error),
235 #[error("Failed to obtain transaction status")]
237 TxStatus,
238 #[error("Failed to obtain block info")]
239 BlockInfo,
241 #[error("A deserialization error occurred")]
243 BytesError,
244}
245
246impl From<serde_json::Error> for GraphQLError {
247 fn from(e: serde_json::Error) -> Self {
248 Self::Generic(e)
249 }
250}
251
252impl GraphQL {
253 pub async fn query(&self, query: &str) -> Result<Vec<u8>, Error> {
255 self.client
256 .call("graphql", None, "query", query.as_bytes())
257 .await
258 }
259}
260
261#[ignore = "Leave it here just for manual tests"]
262#[tokio::test]
263async fn test() -> Result<(), Error> {
264 let gql = GraphQL {
265 status: |s| {
266 println!("{s}");
267 },
268 client: RuesHttpClient::new(
269 "http://testnet.nodes.dusk.network:9500/graphql",
270 )?,
271 };
272 let _ = gql
273 .tx_status(
274 "dbc5a2c949516ecfb418406909d195c3cc267b46bd966a3ca9d66d2e13c47003",
275 )
276 .await?;
277 let block_txs = gql.txs_for_block(90).await?;
278 block_txs.into_iter().for_each(|tx_block| {
279 let tx = tx_block.tx;
280 let chain_txid = tx_block.id;
281 let hash = tx.hash();
282 let tx_id = hex::encode(hash.to_bytes());
283 assert_eq!(chain_txid, tx_id);
284 println!("txid: {tx_id}");
285 });
286 Ok(())
287}
288
289#[tokio::test]
290async fn deser() -> Result<(), Box<dyn std::error::Error>> {
291 let block_not_found = r#"{"block":null}"#;
292 serde_json::from_str::<BlockResponse>(block_not_found).unwrap();
293
294 let block_without_tx = r#"{"block":{"transactions":[]}}"#;
295 serde_json::from_str::<BlockResponse>(block_without_tx).unwrap();
296
297 let block_with_tx = r#"{"block":{"transactions":[{"id":"88e6804989cc2f3fd5bf94dcd39a4e7b7da9a1114d9b8bf4e0515264bc81c50f"}]}}"#;
298 serde_json::from_str::<BlockResponse>(block_with_tx).unwrap();
299
300 Ok(())
301}