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