bark/vtxo/
mod.rs

1pub mod selection;
2pub mod state;
3
4use log::{debug, error, trace};
5use ark::{ProtocolEncoding, Vtxo};
6use ark::vtxo::VtxoRef;
7
8use crate::Wallet;
9use crate::movement::MovementId;
10use crate::vtxo::state::{VtxoState, VtxoStateKind, UNSPENT_STATES};
11
12impl Wallet {
13	/// Attempts to lock VTXOs with the given [VtxoId] values. This will only work if the current
14	/// [VtxoState] is contained by [UNSPENT_STATES].
15	///
16	/// # Errors
17	/// - If the VTXO is not in a lockable [VtxoState].
18	/// - If the VTXO doesn't exist.
19	/// - If a database error occurs.
20	pub fn lock_vtxos(
21		&self,
22		vtxos: impl IntoIterator<Item = impl VtxoRef>,
23		movement_id: Option<MovementId>,
24	) -> anyhow::Result<()> {
25		self.set_vtxo_states(vtxos, &VtxoState::Locked { movement_id }, &UNSPENT_STATES)
26	}
27
28	/// Attempts to mark VTXOs as [VtxoState::Spent], given that each [VtxoId] is currently a state
29	/// contained by [UNSPENT_STATES].
30	///
31	/// # Errors
32	/// - If the VTXO is not currently spent.
33	/// - If the VTXO doesn't exist.
34	/// - If a database error occurs.
35	pub fn mark_vtxos_as_spent(
36		&self,
37		vtxos: impl IntoIterator<Item = impl VtxoRef>,
38	) -> anyhow::Result<()> {
39		self.set_vtxo_states(vtxos, &VtxoState::Spent, &UNSPENT_STATES)
40	}
41
42	/// Updates the state set the [VtxoState] of VTXOs corresponding to each given [VtxoId]while
43	/// validating if the transition is allowed based on the current state and allowed transitions.
44	///
45	/// # Parameters
46	/// - `vtxos`: The [VtxoId] of each [Vtxo] to update.
47	/// - `state`: A reference to the new [VtxoState] that the VTXOs should be transitioned to.
48	/// - `allowed_states`: A slice of [VtxoStateKind] representing the permissible current states
49	///   from which the VTXOs are allowed to transition to the given `state`.
50	///
51	/// # Errors
52	/// - The database operation to update the states fails.
53	/// - The state transition is invalid or does not match the allowed transitions.
54	pub fn set_vtxo_states(
55		&self,
56		vtxos: impl IntoIterator<Item = impl VtxoRef>,
57		state: &VtxoState,
58		allowed_states: &[VtxoStateKind],
59	) -> anyhow::Result<()> {
60		let mut problematic_vtxos = Vec::new();
61		for vtxo in vtxos {
62			let id = vtxo.vtxo_id();
63			if let Err(e) = self.db.update_vtxo_state_checked(
64				id,
65				state.clone(),
66				allowed_states,
67			) {
68				error!(
69					"Failed to set {} state with allowed states {:?} for VTXO {}: {:#}",
70					state.kind(), allowed_states, id, e,
71				);
72				problematic_vtxos.push(id);
73			}
74		}
75		if problematic_vtxos.is_empty() {
76			Ok(())
77		} else {
78			Err(anyhow!(
79				"Failed to set {} state for {} VTXOs: {:?}",
80				state.kind(),
81				problematic_vtxos.len(),
82				problematic_vtxos
83			))
84		}
85	}
86
87	/// Stores the given collection of VTXOs in the wallet with an initial state of
88	/// [VtxoState::Locked].
89	///
90	/// # Parameters
91	/// - `vtxos`: The VTXOs to store in the wallet.
92	pub fn store_locked_vtxos<'a>(
93		&self,
94		vtxos: impl IntoIterator<Item = &'a Vtxo>,
95		movement_id: Option<MovementId>,
96	) -> anyhow::Result<()> {
97		self.store_vtxos(vtxos, &VtxoState::Locked { movement_id })
98	}
99
100	/// Stores the given collection of VTXOs in the wallet with an initial state of
101	/// [VtxoState::Spendable].
102	///
103	/// # Parameters
104	/// - `vtxos`: The VTXOs to store in the wallet.
105	pub fn store_spendable_vtxos<'a>(
106		&self,
107		vtxos: impl IntoIterator<Item = &'a Vtxo>,
108	) -> anyhow::Result<()> {
109		self.store_vtxos(vtxos, &VtxoState::Spendable)
110	}
111
112	/// Stores the given collection of VTXOs in the wallet with an initial state of
113	/// [VtxoState::Spent].
114	///
115	/// # Parameters
116	/// - `vtxos`: The VTXOs to store in the wallet.
117	pub fn store_spent_vtxos<'a>(
118		&self,
119		vtxos: impl IntoIterator<Item = &'a Vtxo>,
120	) -> anyhow::Result<()> {
121		self.store_vtxos(vtxos, &VtxoState::Spent)
122	}
123
124	/// Stores the given collection of VTXOs in the wallet with the given initial state.
125	///
126	/// # Parameters
127	/// - `vtxos`: The VTXOs to store in the wallet.
128	/// - `state`: The initial state of the VTXOs.
129	pub fn store_vtxos<'a>(
130		&self,
131		vtxos: impl IntoIterator<Item = &'a Vtxo>,
132		state: &VtxoState,
133	) -> anyhow::Result<()> {
134		let vtxos = vtxos.into_iter().map(|v| (v, state)).collect::<Vec<_>>();
135		if let Err(e) = self.db.store_vtxos(&vtxos) {
136			error!("An error occurred while storing {} VTXOs: {:#}", vtxos.len(), e);
137			error!("Raw VTXOs for debugging:");
138			for (vtxo, _) in vtxos {
139				error!(" - {}", vtxo.serialize_hex());
140			}
141			Err(e)
142		} else {
143			debug!("Stored {} VTXOs", vtxos.len());
144			trace!("New VTXO IDs: {:?}", vtxos.into_iter().map(|(v, _)| v.id()).collect::<Vec<_>>());
145			Ok(())
146		}
147	}
148
149	/// Attempts to unlock VTXOs with the given [VtxoId] values. This will only work if the current
150	/// [VtxoState] is [VtxoStateKind::Locked].
151	///
152	/// # Errors
153	/// - If the VTXO is not currently locked.
154	/// - If the VTXO doesn't exist.
155	/// - If a database error occurs.
156	pub fn unlock_vtxos(
157		&self,
158		vtxos: impl IntoIterator<Item = impl VtxoRef>,
159	) -> anyhow::Result<()> {
160		self.set_vtxo_states(vtxos, &VtxoState::Spendable, &[VtxoStateKind::Locked])
161	}
162}