bark/persist/
models.rs

1//! Persistence-focused data models.
2//!
3//! This module defines serializable types that mirror core in-memory structures but are tailored
4//! for durable storage and retrieval via a BarkPersister implementation.
5//!
6//! Intent
7//! - Keep storage concerns decoupled from runtime types used by protocol logic.
8//! - Provide stable, serde-friendly representations for database backends.
9//! - Enable forward/backward compatibility when schema migrations occur.
10
11use bdk_esplora::esplora_client::Amount;
12use bitcoin_ext::BlockDelta;
13use lightning_invoice::Bolt11Invoice;
14
15use ark::{VtxoId, VtxoPolicy, VtxoRequest};
16use ark::lightning::{Invoice, PaymentHash, Preimage};
17
18use crate::exit::ExitVtxo;
19use crate::exit::models::ExitState;
20use crate::vtxo_state::VtxoState;
21use crate::WalletVtxo;
22
23/// Persisted representation of a pending lightning send.
24///
25/// Stores the invoice and the amount being sent.
26///
27/// Note: the record should be removed when the payments is completed or failed.
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct PendingLightningSend {
30	pub invoice: Invoice,
31	pub amount: Amount,
32	pub htlc_vtxos: Vec<WalletVtxo>,
33}
34
35/// Persisted representation of an incoming Lightning payment.
36///
37/// Stores the invoice and related cryptographic material (e.g., payment hash and preimage)
38/// and tracks whether the preimage has been revealed.
39///
40/// Note: the record should be removed when the receive is completed or failed.
41#[derive(Debug, Clone)]
42pub struct LightningReceive {
43	pub payment_hash: PaymentHash,
44	pub payment_preimage: Preimage,
45	pub invoice: Bolt11Invoice,
46	pub preimage_revealed_at: Option<u64>,
47	pub htlc_vtxos: Option<Vec<WalletVtxo>>,
48	pub htlc_recv_cltv_delta: BlockDelta,
49}
50
51/// Persistable view of an [ExitVtxo].
52///
53/// `StoredExit` is a lightweight data transfer object tailored for storage backends. It captures
54/// the VTXO ID, the current state, and the full history of the unilateral exit.
55pub struct StoredExit {
56	/// Identifier of the VTXO being exited.
57	pub vtxo_id: VtxoId,
58	/// Current exit state.
59	pub state: ExitState,
60	/// Historical states for auditability.
61	pub history: Vec<ExitState>,
62}
63
64impl StoredExit {
65	/// Builds a persistable snapshot from an [ExitVtxo].
66	pub fn new(exit: &ExitVtxo) -> Self {
67		Self {
68			vtxo_id: exit.id(),
69			state: exit.state().clone(),
70			history: exit.history().clone(),
71		}
72	}
73}
74
75/// Persisted request data used during round participation.
76///
77/// Captures the information required to rebuild or resume protocol state across attempts,
78/// such as inputs, outputs, amounts, and metadata the server expects.
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct StoredVtxoRequest {
81	#[serde(with = "ark::encode::serde")]
82	pub request_policy: VtxoPolicy,
83	pub amount: Amount,
84	pub state: VtxoState
85}
86
87impl StoredVtxoRequest {
88	pub fn from_parts(req: VtxoRequest, state: VtxoState) -> Self {
89		Self {
90			request_policy: req.policy,
91			amount: req.amount,
92			state,
93		}
94	}
95
96	pub fn to_vtxo_request(&self) -> VtxoRequest {
97		VtxoRequest {
98			policy: self.request_policy.clone(),
99			amount: self.amount,
100		}
101	}
102}
103
104#[cfg(test)]
105mod test {
106	use super::*;
107
108	use crate::exit::models::{ExitState, ExitTxOrigin};
109	use crate::movement::MovementRecipient;
110	use crate::vtxo_state::VtxoState;
111
112	#[test]
113	/// Each struct stored as JSON in the database should have test to check for backwards compatibility
114	/// Parsing can occur either in convert.rs or this file (query.rs)
115	fn test_serialised_structs() {
116		// Exit state
117		let serialised = r#"{"type":"start","tip_height":119}"#;
118		serde_json::from_str::<ExitState>(serialised).unwrap();
119		let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-input-confirmation","txids":["ddfe11920358d1a1fae970dc80459c60675bf1392896f69b103fc638313751de"]}}]}"#;
120		serde_json::from_str::<ExitState>(serialised).unwrap();
121		let serialised = r#"{"type":"awaiting-delta","tip_height":122,"confirmed_block":"122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f","claimable_height":134}"#;
122		serde_json::from_str::<ExitState>(serialised).unwrap();
123		let serialised = r#"{"type":"claimable","tip_height":134,"claimable_since": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9","last_scanned_block":null}"#;
124		serde_json::from_str::<ExitState>(serialised).unwrap();
125		let serialised = r#"{"type":"claim-in-progress","tip_height":134, "claimable_since": "134:6585896bdda6f08d924bf45cc2b16418af56703b3c50930e4dccbc1728d3800a","claim_txid":"599347c35870bd36f7acb22b81f9ffa8b911d9b5e94834858aebd3ec09339f4c"}"#;
126		serde_json::from_str::<ExitState>(serialised).unwrap();
127		let serialised = r#"{"type":"claimed","tip_height":134,"txid":"599347c35870bd36f7acb22b81f9ffa8b911d9b5e94834858aebd3ec09339f4c","block": "122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f"}"#;
128		serde_json::from_str::<ExitState>(serialised).unwrap();
129
130		// Exit child tx origins
131		let serialized = r#"{"type":"wallet","confirmed_in":null}"#;
132		serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
133		let serialized = r#"{"type":"wallet","confirmed_in": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9"}"#;
134		serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
135		let serialized = r#"{"type":"mempool","fee_rate_kwu":25000,"total_fee":27625}"#;
136		serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
137		let serialized = r#"{"type":"block","confirmed_in": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9"}"#;
138		serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
139
140		// Movement recipient
141		let serialised = r#"{"recipient":"03a4a6443868dbba406d03e43d7baf00d66809d57fba911616ccf90a4685de2bc1","amount_sat":150000}"#;
142		serde_json::from_str::<MovementRecipient>(serialised).unwrap();
143
144		// Vtxo state
145		let serialised = r#""Spendable""#;
146		serde_json::from_str::<VtxoState>(serialised).unwrap();
147		let serialised = r#""Spent""#;
148		serde_json::from_str::<VtxoState>(serialised).unwrap();
149		let serialised = r#""Locked""#;
150		serde_json::from_str::<VtxoState>(serialised).unwrap();
151
152		let serialised = r#"{"request_policy":"0003a4a6443868dbba406d03e43d7baf00d66809d57fba911616ccf90a4685de2bc1","amount":300000,"state":"Spendable"}"#;
153		serde_json::from_str::<StoredVtxoRequest>(serialised).unwrap();
154	}
155}