1use anyhow::{anyhow, Context};
12use bitcoin::secp256k1::Keypair;
13use log::{info, warn};
14
15use ark::{Vtxo, VtxoId};
16use ark::vtxo::Full;
17use server_rpc::protos::VtxoSpendState;
18
19use crate::Wallet;
20use crate::vtxo::{VtxoState, VtxoValidationError};
21
22#[derive(Debug, thiserror::Error)]
24pub enum ImportVtxoError {
25 #[error("vtxo {id} failed to validate: {source}")]
26 Invalid {
27 id: VtxoId,
28 source: VtxoValidationError,
29 },
30
31 #[error("unable to derive the key for vtxo {id} from this seed with a {gap_limit} gap limit")]
32 KeyNotFound {
33 id: VtxoId,
34 gap_limit: u32,
35 },
36
37 #[error("vtxo {id} is neither spendable nor spent, so it cannot be imported: the server \
38 reports it as {state:?}")]
39 InFlight {
40 id: VtxoId,
41 state: VtxoSpendState,
42 },
43
44 #[error("unexpected error: {0:#}")]
45 Transient(#[from] anyhow::Error),
46}
47
48#[derive(Debug, Clone, Default)]
50pub struct ImportVtxoArgs {
51 pub gap_limit: Option<u32>,
56
57 pub skip_status_check: bool,
62
63 pub allow_partial: bool,
68}
69
70impl Wallet {
71 pub async fn import_vtxos(
79 &self,
80 vtxos: &[Vtxo<Full>],
81 args: ImportVtxoArgs,
82 ) -> Result<Vec<VtxoId>, ImportVtxoError> {
83 let mut ids = Vec::<VtxoId>::with_capacity(vtxos.len());
84 let mut pending = Vec::<&Vtxo<Full>>::with_capacity(vtxos.len());
85 for vtxo in vtxos {
86 let vtxo_id = vtxo.id();
87 if self.inner.db.get_wallet_vtxo(vtxo_id).await?.is_some() {
88 info!("VTXO {} already exists in wallet, skipping import", vtxo_id);
89 ids.push(vtxo_id);
90 } else if !pending.iter().any(|v| v.id() == vtxo_id) {
91 if let Err(e) = self.validate_vtxo(vtxo).await {
92 let e = ImportVtxoError::Invalid { id: vtxo_id, source: e };
93 if !args.allow_partial {
94 return Err(e);
95 }
96 warn!("Not importing vtxo {vtxo_id}: {e}");
97 } else {
98 pending.push(vtxo);
99 }
100 }
101 }
102 if pending.is_empty() {
103 return Ok(ids);
104 }
105
106 let gap_limit = args.gap_limit.unwrap_or(self.inner.config.vtxo_key_gap_limit);
107 let user_pubkeys = pending.iter().map(|v| v.user_pubkey()).collect::<Vec<_>>();
110 let keypairs = self.find_vtxo_keypairs(user_pubkeys, gap_limit).await
111 .context("error scanning the vtxo key space")?;
112
113 let mut to_store = Vec::with_capacity(pending.len());
114 for vtxo in &pending {
115 let id = vtxo.id();
116 let state = match keypairs.get(&vtxo.user_pubkey()) {
117 None => Err(ImportVtxoError::KeyNotFound { id, gap_limit }),
118 Some(keypair) => self.import_vtxo_state(vtxo, keypair, &args).await,
119 };
120 match state {
121 Ok(state) => to_store.push((*vtxo, state)),
122 Err(e) => {
123 if !args.allow_partial {
124 return Err(e);
125 }
126 warn!("Not importing vtxo {id}: {e}");
127 },
128 }
129 }
130 if to_store.is_empty() {
131 return Ok(ids);
132 }
133
134 let rows = to_store.iter().map(|(v, s)| (*v, s)).collect::<Vec<_>>();
135 self.inner.db.store_vtxos(&rows).await
136 .context("failed to store imported VTXOs")?;
137
138 for (vtxo, state) in &to_store {
139 info!("Successfully imported VTXO {} as {}", vtxo.id(), state.kind());
140 ids.push(vtxo.id());
141 }
142
143 Ok(ids)
144 }
145
146 async fn import_vtxo_state(
152 &self,
153 vtxo: &Vtxo<Full>,
154 keypair: &Keypair,
155 args: &ImportVtxoArgs,
156 ) -> Result<VtxoState, ImportVtxoError> {
157 if args.skip_status_check {
158 return Ok(VtxoState::Spendable);
159 }
160
161 let id = vtxo.id();
162 Ok(match self.fetch_vtxo_spend_state(id, keypair).await? {
163 VtxoSpendState::Spendable => VtxoState::Spendable,
164 VtxoSpendState::Spent => VtxoState::Spent,
165 state @ (
166 VtxoSpendState::Unclaimed
167 | VtxoSpendState::Unregistered
168 | VtxoSpendState::HtlcRecvUnclaimed
169 ) => return Err(ImportVtxoError::InFlight { id, state }),
170 VtxoSpendState::Unspecified => return Err(ImportVtxoError::Transient(
171 anyhow!("server returned an unspecified spend state for vtxo {id}"),
172 )),
173 })
174 }
175
176 pub async fn import_vtxo(
180 &self,
181 vtxo: &Vtxo<Full>,
182 args: ImportVtxoArgs,
183 ) -> Result<(), ImportVtxoError> {
184 self.import_vtxos(std::slice::from_ref(vtxo), args).await?;
185 Ok(())
186 }
187}