1
2mod selection;
3mod signing;
4mod state;
5
6pub use self::selection::{FilterVtxos, RefreshStrategy, VtxoFilter};
7pub use self::state::{VtxoState, VtxoStateKind, WalletVtxo};
8
9use log::{debug, error, trace};
10use ark::{ProtocolEncoding, Vtxo};
11use ark::vtxo::{Full, VtxoRef};
12
13use crate::Wallet;
14use crate::movement::MovementId;
15
16impl Wallet {
17 pub async fn lock_vtxos(
25 &self,
26 vtxos: impl IntoIterator<Item = impl VtxoRef>,
27 movement_id: Option<MovementId>,
28 ) -> anyhow::Result<()> {
29 self.set_vtxo_states(
30 vtxos, &VtxoState::Locked { movement_id }, &VtxoStateKind::UNSPENT_STATES,
31 ).await
32 }
33
34 pub async fn mark_vtxos_as_spent(
43 &self,
44 vtxos: impl IntoIterator<Item = impl VtxoRef>,
45 ) -> anyhow::Result<()> {
46 const ALLOWED: &[VtxoStateKind] = &[
47 VtxoStateKind::Spendable,
48 VtxoStateKind::Locked,
49 VtxoStateKind::Spent,
50 ];
51 self.set_vtxo_states(vtxos, &VtxoState::Spent, ALLOWED).await
52 }
53
54 pub async fn set_vtxo_states(
69 &self,
70 vtxos: impl IntoIterator<Item = impl VtxoRef>,
71 state: &VtxoState,
72 mut allowed_states: &[VtxoStateKind],
73 ) -> anyhow::Result<()> {
74 if allowed_states.is_empty() {
75 allowed_states = VtxoStateKind::ALL;
76 }
77
78 let mut problematic_vtxos = Vec::new();
79 for vtxo in vtxos {
80 let id = vtxo.vtxo_id();
81 if let Err(e) = self.db.update_vtxo_state_checked(
82 id,
83 state.clone(),
84 allowed_states,
85 ).await {
86 error!(
87 "Failed to set {} state with allowed states {:?} for VTXO {}: {:#}",
88 state.kind(), allowed_states, id, e,
89 );
90 problematic_vtxos.push(id);
91 }
92 }
93
94 if problematic_vtxos.is_empty() {
95 Ok(())
96 } else {
97 Err(anyhow!(
98 "Failed to set {} state for {} VTXOs: {:?}",
99 state.kind(),
100 problematic_vtxos.len(),
101 problematic_vtxos
102 ))
103 }
104 }
105
106 pub async fn store_locked_vtxos<'a>(
114 &self,
115 vtxos: impl IntoIterator<Item = &'a Vtxo<Full>>,
116 movement_id: Option<MovementId>,
117 ) -> anyhow::Result<()> {
118 self.store_vtxos(vtxos, &VtxoState::Locked { movement_id }).await
119 }
120
121 pub async fn store_spendable_vtxos<'a>(
131 &self,
132 vtxos: impl IntoIterator<Item = &'a Vtxo<Full>> + Clone,
133 ) -> anyhow::Result<()> {
134 self.store_vtxos(vtxos.clone(), &VtxoState::Spendable).await?;
135
136 if let Err(e) = self.post_recovery_vtxo_ids(vtxos.into_iter().map(|v| v.id())).await {
138 error!("Failed to post recovery vtxo IDs to server: {:#}", e);
139 }
140
141 Ok(())
142 }
143
144 pub async fn store_spent_vtxos<'a>(
152 &self,
153 vtxos: impl IntoIterator<Item = &'a Vtxo<Full>>,
154 ) -> anyhow::Result<()> {
155 self.store_vtxos(vtxos, &VtxoState::Spent).await
156 }
157
158 pub async fn store_vtxos<'a>(
166 &self,
167 vtxos: impl IntoIterator<Item = &'a Vtxo<Full>>,
168 state: &VtxoState,
169 ) -> anyhow::Result<()> {
170 let vtxos = vtxos.into_iter().map(|v| (v, state)).collect::<Vec<_>>();
171 if let Err(e) = self.db.store_vtxos(&vtxos).await {
172 error!("An error occurred while storing {} VTXOs: {:#}", vtxos.len(), e);
173 error!("Raw VTXOs for debugging:");
174 for (vtxo, _) in vtxos {
175 error!(" - {}", vtxo.serialize_hex());
176 }
177 Err(e)
178 } else {
179 debug!("Stored {} VTXOs", vtxos.len());
180 trace!("New VTXO IDs: {:?}", vtxos.into_iter().map(|(v, _)| v.id()).collect::<Vec<_>>());
181 Ok(())
182 }
183 }
184
185 pub async fn unlock_vtxos(
196 &self,
197 vtxos: impl IntoIterator<Item = impl VtxoRef>,
198 ) -> anyhow::Result<()> {
199 self.set_vtxo_states(
200 vtxos, &VtxoState::Spendable, &[VtxoStateKind::Locked, VtxoStateKind::Spendable],
201 ).await
202 }
203}