zync_core/client/
zidecar.rs1use anyhow::Result;
4use super::{
5 zidecar_proto::{
6 zidecar_client::ZidecarClient as GrpcClient,
7 Empty, ProofRequest, BlockRange, BlockId, TxFilter,
8 TransparentAddressFilter, RawTransaction,
9 },
10 SyncStatus, TreeState, Utxo, SendResult, CompactBlock, CompactAction,
11};
12use tonic::transport::Channel;
13use tracing::{info, debug};
14
15pub struct ZidecarClient {
16 client: GrpcClient<Channel>,
17}
18
19impl ZidecarClient {
20 pub async fn connect(url: &str) -> Result<Self> {
21 info!("connecting to zidecar at {}", url);
22 let client = GrpcClient::connect(url.to_string()).await?;
23 Ok(Self { client })
24 }
25
26 pub async fn get_header_proof(&mut self) -> Result<(Vec<u8>, u32, u32)> {
28 let request = tonic::Request::new(ProofRequest {
29 from_height: 0,
30 to_height: 0, });
32
33 let response = self.client.get_header_proof(request).await?;
34 let proof = response.into_inner();
35
36 debug!(
37 "received proof: {} -> {} ({} bytes)",
38 proof.from_height,
39 proof.to_height,
40 proof.ligerito_proof.len()
41 );
42
43 Ok((proof.ligerito_proof, proof.from_height, proof.to_height))
44 }
45
46 pub async fn get_tip(&mut self) -> Result<(u32, Vec<u8>)> {
48 let request = tonic::Request::new(Empty {});
49 let response = self.client.get_tip(request).await?;
50 let tip = response.into_inner();
51
52 Ok((tip.height, tip.hash))
53 }
54
55 pub async fn get_compact_blocks(
57 &mut self,
58 start_height: u32,
59 end_height: u32,
60 ) -> Result<Vec<CompactBlock>> {
61 let request = tonic::Request::new(BlockRange {
62 start_height,
63 end_height,
64 });
65
66 let mut stream = self.client.get_compact_blocks(request).await?.into_inner();
67 let mut blocks = Vec::new();
68
69 while let Some(block) = stream.message().await? {
70 let actions: Vec<CompactAction> = block.actions.into_iter().map(|a| {
71 let mut cmx = [0u8; 32];
72 let mut ek = [0u8; 32];
73 let mut nf = [0u8; 32];
74 if a.cmx.len() == 32 { cmx.copy_from_slice(&a.cmx); }
75 if a.ephemeral_key.len() == 32 { ek.copy_from_slice(&a.ephemeral_key); }
76 if a.nullifier.len() == 32 { nf.copy_from_slice(&a.nullifier); }
77 CompactAction {
78 cmx,
79 ephemeral_key: ek,
80 ciphertext: a.ciphertext,
81 nullifier: nf,
82 }
83 }).collect();
84
85 blocks.push(CompactBlock {
86 height: block.height,
87 hash: block.hash,
88 actions,
89 });
90 }
91
92 Ok(blocks)
93 }
94
95 pub async fn get_sync_status(&mut self) -> Result<SyncStatus> {
97 let request = tonic::Request::new(Empty {});
98 let response = self.client.get_sync_status(request).await?;
99 let status = response.into_inner();
100
101 Ok(SyncStatus {
102 current_height: status.current_height,
103 current_epoch: status.current_epoch,
104 blocks_in_epoch: status.blocks_in_epoch,
105 complete_epochs: status.complete_epochs,
106 gigaproof_ready: status.gigaproof_status == 2, blocks_until_ready: status.blocks_until_ready,
108 last_gigaproof_height: status.last_gigaproof_height,
109 })
110 }
111
112 pub async fn send_transaction(&mut self, tx_data: Vec<u8>) -> Result<SendResult> {
114 let request = tonic::Request::new(RawTransaction {
115 data: tx_data,
116 height: 0,
117 });
118 let response = self.client.send_transaction(request).await?;
119 let resp = response.into_inner();
120 Ok(SendResult {
121 txid: resp.txid,
122 error_code: resp.error_code,
123 error_message: resp.error_message,
124 })
125 }
126
127 pub async fn get_transaction(&mut self, txid: &[u8; 32]) -> Result<Vec<u8>> {
129 let request = tonic::Request::new(TxFilter {
130 hash: txid.to_vec(),
131 });
132 let response = self.client.get_transaction(request).await?;
133 Ok(response.into_inner().data)
134 }
135
136 pub async fn get_tree_state(&mut self, height: u32) -> Result<TreeState> {
138 let request = tonic::Request::new(BlockId {
139 height,
140 hash: vec![],
141 });
142 let response = self.client.get_tree_state(request).await?;
143 let state = response.into_inner();
144 Ok(TreeState {
145 height: state.height,
146 hash: state.hash,
147 time: state.time,
148 sapling_tree: state.sapling_tree,
149 orchard_tree: state.orchard_tree,
150 })
151 }
152
153 pub async fn get_address_utxos(&mut self, addresses: Vec<String>) -> Result<Vec<Utxo>> {
155 let request = tonic::Request::new(TransparentAddressFilter {
156 addresses,
157 start_height: 0,
158 max_entries: 0,
159 });
160 let response = self.client.get_address_utxos(request).await?;
161 let utxos = response.into_inner().utxos;
162
163 Ok(utxos.into_iter().map(|u| {
164 let mut txid = [0u8; 32];
165 if u.txid.len() == 32 { txid.copy_from_slice(&u.txid); }
166 Utxo {
167 address: u.address,
168 txid,
169 output_index: u.output_index,
170 script: u.script,
171 value_zat: u.value_zat,
172 height: u.height,
173 }
174 }).collect())
175 }
176
177 pub async fn get_taddress_txids(&mut self, addresses: Vec<String>, start_height: u32) -> Result<Vec<[u8; 32]>> {
179 let request = tonic::Request::new(TransparentAddressFilter {
180 addresses,
181 start_height,
182 max_entries: 0,
183 });
184 let response = self.client.get_taddress_txids(request).await?;
185 let txids = response.into_inner().txids;
186
187 Ok(txids.into_iter().filter_map(|t| {
188 if t.len() == 32 {
189 let mut arr = [0u8; 32];
190 arr.copy_from_slice(&t);
191 Some(arr)
192 } else {
193 None
194 }
195 }).collect())
196 }
197}