Skip to main content

zync_core/client/
zidecar.rs

1//! zidecar gRPC client
2
3use 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    /// get gigaproof + tip proof for full chain
27    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, // 0 = tip
31        });
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    /// get current chain tip
47    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    /// stream compact blocks for scanning
56    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    /// get sync status (blockchain height, epoch progress, gigaproof status)
96    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, // READY
107            blocks_until_ready: status.blocks_until_ready,
108            last_gigaproof_height: status.last_gigaproof_height,
109        })
110    }
111
112    /// send raw transaction to the network
113    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    /// get transaction by hash
128    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    /// get tree state at a given height
137    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    /// get transparent UTXOs for addresses
154    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    /// get transparent transaction IDs for addresses
178    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}