spark_rust/wallet/handlers/
timelock.rs1use hashbrown::HashMap;
2use spark_protos::spark::{query_nodes_request::Source, QueryNodesRequest, TreeNodeIds};
3
4use crate::{
5 error::{SparkSdkError, WalletError},
6 signer::traits::{derivation_path::SparkKeyType, SparkSigner},
7 wallet::{
8 internal_handlers::traits::timelock::TimelockInternalHandlers,
9 leaf_manager::{SparkLeaf, SparkNodeStatus},
10 utils::{bitcoin::bitcoin_tx_from_bytes, sequence::next_sequence},
11 },
12 SparkSdk,
13};
14
15impl<S: SparkSigner + Send + Sync + Clone + 'static> SparkSdk<S> {
16 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
49 pub(crate) async fn refresh_timelock_nodes(
50 &self,
51 node_id: Option<String>,
52 ) -> Result<(), SparkSdkError> {
53 let mut nodes_to_refresh = vec![];
54 let mut node_ids = vec![];
55
56 match node_id {
57 Some(leaf_id) => {
58 let leaf_in_array = self
59 .leaf_manager
60 .filter_nodes_by_ids(&vec![leaf_id.clone()]);
61 if leaf_in_array.is_empty() {
62 return Err(SparkSdkError::from(
63 WalletError::LeafNotFoundAfterOperation {
64 leaf_id: leaf_id.clone(),
65 },
66 ));
67 }
68 let leaf = leaf_in_array.first().unwrap();
69 if !leaf.is_bitcoin() {
70 return Err(SparkSdkError::from(WalletError::LeafIsNotBitcoin {
71 leaf_id: leaf.get_id().clone(),
72 }));
73 }
74
75 nodes_to_refresh.push(leaf.get_tree_node()?);
76 node_ids.push(leaf_id);
77 }
78 None => {
79 let lock_callback = Some(Box::new(|leaf: &SparkLeaf| -> bool {
80 if !leaf.is_bitcoin() {
81 return false;
82 }
83
84 let leaf_node = leaf.get_tree_node().unwrap();
85 let refund_tx = bitcoin_tx_from_bytes(&leaf_node.refund_tx);
86 if refund_tx.is_err() {
87 return false;
88 }
89
90 let refund_tx = refund_tx.unwrap();
91 let current_sequence = refund_tx.input[0].sequence.0;
92 let next_sequence = next_sequence(current_sequence);
93
94 next_sequence == 0 || next_sequence > current_sequence }) as Box<dyn Fn(&SparkLeaf) -> bool>);
96
97 let (leaves, _unlocking_id) = self.leaf_manager.get_all_available_leaves(
98 lock_callback,
99 Some(SparkNodeStatus::RefreshTimelock),
100 );
101 node_ids = leaves.iter().map(|n| n.get_id().clone()).collect();
102 nodes_to_refresh = leaves.iter().map(|n| n.get_tree_node().unwrap()).collect();
103 }
104 };
105
106 if nodes_to_refresh.is_empty() {
107 #[cfg(feature = "telemetry")]
108 tracing::debug!("No leaf needs timelock refresh");
109
110 return Ok(());
111 }
112
113 let request = QueryNodesRequest {
114 source: Some(Source::NodeIds(TreeNodeIds { node_ids })),
115 include_parents: true,
116 network: self.config.spark_config.network.marshal_proto(),
117 };
118
119 let node_response = self
120 .config
121 .spark_config
122 .call_with_retry(
123 request,
124 |mut client, req| Box::pin(async move { client.query_nodes(req).await }),
125 None,
126 )
127 .await?;
128
129 let nodes_response = node_response.nodes;
130 let mut nodes_map = HashMap::new();
131 for (id, node) in nodes_response {
132 nodes_map.insert(id, node);
133 }
134
135 for node in nodes_to_refresh {
136 let parent_node_id = node.clone().parent_node_id.ok_or(SparkSdkError::from(
137 WalletError::LeafHasNoParent {
138 leaf_id: node.id.clone(),
139 },
140 ))?;
141
142 let parent_node = nodes_map.get(&parent_node_id).ok_or(SparkSdkError::from(
143 WalletError::LeafParentNotFound {
144 leaf_id: node.id.clone(),
145 },
146 ))?;
147
148 let signing_public_key = self.signer.new_secp256k1_keypair(
149 node.id.clone(),
150 SparkKeyType::BaseSigning,
151 0,
152 self.config.spark_config.network.to_bitcoin_network(),
153 )?;
154
155 let nodes = self
156 .refresh_timelock_transfer_nodes(
157 &vec![node.clone()],
158 parent_node.clone(),
159 &signing_public_key,
160 )
161 .await?;
162
163 if nodes.len() != 1 {
164 return Err(SparkSdkError::from(
165 WalletError::PostRefreshNodeSignatureLengthMismatch,
166 ));
167 }
168
169 let new_node = nodes.first().unwrap();
170
171 let leaf_ids_to_delete = vec![node.id.clone()];
173 let leaves_to_insert = vec![SparkLeaf::Bitcoin(new_node.clone())];
174 self.leaf_manager
175 .delete_and_insert_leaves_atomically(&leaf_ids_to_delete, &leaves_to_insert)?;
176 }
177
178 Ok(())
179 }
180}