zync_core/client/
lightwalletd.rs1use anyhow::Result;
4use super::{
5 lightwalletd_proto::{
6 compact_tx_streamer_client::CompactTxStreamerClient as GrpcClient,
7 ChainSpec, BlockId, BlockRange, TxFilter, GetAddressUtxosArg,
8 RawTransaction, Empty,
9 },
10 TreeState, Utxo, SendResult, CompactBlock, CompactAction,
11};
12use tonic::transport::Channel;
13use tracing::{info, debug, warn};
14
15pub struct LightwalletdClient {
16 client: GrpcClient<Channel>,
17 chain_name: String,
18}
19
20impl LightwalletdClient {
21 pub async fn connect(url: &str) -> Result<Self> {
22 info!("connecting to lightwalletd at {}", url);
23 let client = GrpcClient::connect(url.to_string()).await?;
24 Ok(Self {
25 client,
26 chain_name: String::new(),
27 })
28 }
29
30 pub async fn get_lightd_info(&mut self) -> Result<LightdInfo> {
32 let request = tonic::Request::new(Empty {});
33 let response = self.client.get_lightd_info(request).await?;
34 let info = response.into_inner();
35
36 self.chain_name = info.chain_name.clone();
37
38 Ok(LightdInfo {
39 version: info.version,
40 vendor: info.vendor,
41 taddr_support: info.taddr_support,
42 chain_name: info.chain_name,
43 sapling_activation_height: info.sapling_activation_height,
44 consensus_branch_id: info.consensus_branch_id,
45 block_height: info.block_height,
46 estimated_height: info.estimated_height,
47 })
48 }
49
50 pub async fn get_latest_block(&mut self) -> Result<(u64, Vec<u8>)> {
52 let request = tonic::Request::new(ChainSpec {});
53 let response = self.client.get_latest_block(request).await?;
54 let block = response.into_inner();
55
56 Ok((block.height, block.hash))
57 }
58
59 pub async fn get_block_range(
61 &mut self,
62 start_height: u64,
63 end_height: u64,
64 ) -> Result<Vec<CompactBlock>> {
65 let request = tonic::Request::new(BlockRange {
66 start: Some(BlockId { height: start_height, hash: vec![] }),
67 end: Some(BlockId { height: end_height, hash: vec![] }),
68 });
69
70 let mut stream = self.client.get_block_range(request).await?.into_inner();
71 let mut blocks = Vec::new();
72
73 while let Some(block) = stream.message().await? {
74 let mut actions = Vec::new();
75
76 for tx in block.vtx {
78 for action in tx.actions {
79 let mut cmx = [0u8; 32];
80 let mut ek = [0u8; 32];
81 let mut nf = [0u8; 32];
82 if action.cmx.len() == 32 { cmx.copy_from_slice(&action.cmx); }
83 if action.ephemeral_key.len() == 32 { ek.copy_from_slice(&action.ephemeral_key); }
84 if action.nullifier.len() == 32 { nf.copy_from_slice(&action.nullifier); }
85 actions.push(CompactAction {
86 cmx,
87 ephemeral_key: ek,
88 ciphertext: action.ciphertext,
89 nullifier: nf,
90 });
91 }
92 }
93
94 blocks.push(CompactBlock {
95 height: block.height as u32,
96 hash: block.hash,
97 actions,
98 });
99 }
100
101 Ok(blocks)
102 }
103
104 pub async fn send_transaction(&mut self, tx_data: Vec<u8>) -> Result<SendResult> {
106 let request = tonic::Request::new(RawTransaction {
107 data: tx_data,
108 height: 0,
109 });
110 let response = self.client.send_transaction(request).await?;
111 let resp = response.into_inner();
112
113 Ok(SendResult {
114 txid: String::new(), error_code: resp.error_code,
116 error_message: resp.error_message,
117 })
118 }
119
120 pub async fn get_transaction(&mut self, txid: &[u8; 32]) -> Result<Vec<u8>> {
122 let request = tonic::Request::new(TxFilter {
123 block: None,
124 index: 0,
125 hash: txid.to_vec(),
126 });
127 let response = self.client.get_transaction(request).await?;
128 Ok(response.into_inner().data)
129 }
130
131 pub async fn get_tree_state(&mut self, height: u64) -> Result<TreeState> {
133 let request = tonic::Request::new(BlockId {
134 height,
135 hash: vec![],
136 });
137 let response = self.client.get_tree_state(request).await?;
138 let state = response.into_inner();
139
140 Ok(TreeState {
141 height: state.height as u32,
142 hash: hex::decode(&state.hash).unwrap_or_default(),
143 time: state.time as u64,
144 sapling_tree: state.sapling_tree,
145 orchard_tree: state.orchard_tree,
146 })
147 }
148
149 pub async fn get_address_utxos(&mut self, addresses: Vec<String>) -> Result<Vec<Utxo>> {
151 let request = tonic::Request::new(GetAddressUtxosArg {
152 addresses,
153 start_height: 0,
154 max_entries: 0,
155 });
156 let response = self.client.get_address_utxos(request).await?;
157 let utxos = response.into_inner().address_utxos;
158
159 Ok(utxos.into_iter().map(|u| {
160 let mut txid = [0u8; 32];
161 if u.txid.len() == 32 { txid.copy_from_slice(&u.txid); }
162 Utxo {
163 address: u.address,
164 txid,
165 output_index: u.index as u32,
166 script: u.script,
167 value_zat: u.value_zat as u64,
168 height: u.height as u32,
169 }
170 }).collect())
171 }
172}
173
174#[derive(Debug, Clone)]
176pub struct LightdInfo {
177 pub version: String,
178 pub vendor: String,
179 pub taddr_support: bool,
180 pub chain_name: String,
181 pub sapling_activation_height: u64,
182 pub consensus_branch_id: String,
183 pub block_height: u64,
184 pub estimated_height: u64,
185}