1pub extern crate ark;
283
284pub extern crate bip39;
285pub extern crate lightning_invoice;
286pub extern crate lnurl as lnurllib;
287
288#[macro_use] extern crate anyhow;
289#[macro_use] extern crate serde;
290
291pub mod daemon;
292pub mod error;
293pub mod exit;
294pub mod lightning_utils;
295pub mod movement;
296pub mod onchain;
297pub mod persist;
298pub mod round;
299pub mod subsystem;
300pub mod vtxo;
301
302pub use self::config::{BarkNetwork, Config};
303pub use self::persist::sqlite::SqliteClient;
304pub use self::vtxo::state::WalletVtxo;
305
306mod config;
307mod lnurl;
308mod psbtext;
309
310use std::collections::{HashMap, HashSet};
311
312use std::convert::TryFrom;
313use std::fmt;
314use std::str::FromStr;
315use std::sync::Arc;
316
317use anyhow::{bail, Context};
318use bip39::Mnemonic;
319use bitcoin::{Amount, Network, OutPoint, ScriptBuf, SignedAmount};
320use bitcoin::bip32::{self, Fingerprint};
321use bitcoin::hex::DisplayHex;
322use bitcoin::secp256k1::{self, Keypair, PublicKey};
323use futures::StreamExt;
324use lightning_invoice::Bolt11Invoice;
325use lightning::util::ser::Writeable;
326use lnurllib::lightning_address::LightningAddress;
327use log::{trace, debug, info, warn, error};
328use tokio::sync::RwLock;
329use tokio_util::sync::CancellationToken;
330
331use ark::{ArkInfo, OffboardRequest, ProtocolEncoding, Vtxo, VtxoId, VtxoPolicy, VtxoRequest};
332use ark::address::VtxoDelivery;
333use ark::arkoor::ArkoorPackageBuilder;
334use ark::board::{BoardBuilder, BOARD_FUNDING_TX_VTXO_VOUT};
335use ark::lightning::{Bolt12Invoice, Bolt12InvoiceExt, Invoice, LightningReceiveChallenge, Offer, PaymentHash, Preimage};
336use ark::musig;
337use ark::rounds::RoundId;
338use ark::vtxo::{VtxoRef, PubkeyVtxoPolicy, VtxoPolicyKind};
339use bitcoin_ext::{AmountExt, BlockDelta, BlockHeight, P2TR_DUST, TxStatus};
340use server_rpc::protos::prepare_lightning_receive_claim_request::LightningReceiveAntiDos;
341use server_rpc::{self as rpc, protos, ServerConnection};
342
343use crate::daemon::Daemon;
344use crate::exit::Exit;
345use crate::movement::{Movement, MovementDestination, MovementStatus};
346use crate::movement::manager::{MovementGuard, MovementManager};
347use crate::movement::update::MovementUpdate;
348use crate::onchain::{ChainSource, PreparePsbt, ExitUnilaterally, Utxo, SignPsbt};
349use crate::persist::BarkPersister;
350use crate::persist::models::{PendingLightningSend, LightningReceive};
351use crate::round::{RoundParticipation, RoundStatus};
352use crate::subsystem::{
353 ArkoorMovement, BarkSubsystem, BoardMovement, LightningMovement, LightningReceiveMovement,
354 LightningSendMovement, RoundMovement, SubsystemId,
355};
356use crate::vtxo::selection::{FilterVtxos, VtxoFilter, RefreshStrategy};
357use crate::vtxo::state::{VtxoState, VtxoStateKind, UNSPENT_STATES};
358
359const ARK_PURPOSE_INDEX: u32 = 350;
360
361const LIGHTNING_PREPARE_CLAIM_DELTA: BlockDelta = 2;
364
365lazy_static::lazy_static! {
366 static ref SECP: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
368}
369
370#[derive(Debug, Clone)]
372pub struct LightningReceiveBalance {
373 pub total: Amount,
375 pub claimable: Amount,
377}
378
379#[derive(Debug, Clone)]
381pub struct Balance {
382 pub spendable: Amount,
384 pub pending_lightning_send: Amount,
386 pub pending_lightning_receive: LightningReceiveBalance,
388 pub pending_in_round: Amount,
390 pub pending_exit: Option<Amount>,
393 pub pending_board: Amount,
395}
396
397struct ArkoorCreateResult {
398 input: Vec<Vtxo>,
399 created: Vec<Vtxo>,
400 change: Option<Vtxo>,
401}
402
403impl ArkoorCreateResult {
404 pub fn to_movement_update(&self) -> anyhow::Result<MovementUpdate> {
405 Ok(MovementUpdate::new()
406 .consumed_vtxos(self.input.iter())
407 .produced_vtxo_if_some(self.change.as_ref())
408 )
409 }
410}
411
412pub struct UtxoInfo {
413 pub outpoint: OutPoint,
414 pub amount: Amount,
415 pub confirmation_height: Option<u32>,
416}
417
418impl From<Utxo> for UtxoInfo {
419 fn from(value: Utxo) -> Self {
420 match value {
421 Utxo::Local(o) => UtxoInfo {
422 outpoint: o.outpoint,
423 amount: o.amount,
424 confirmation_height: o.confirmation_height,
425 },
426 Utxo::Exit(e) => UtxoInfo {
427 outpoint: e.vtxo.point(),
428 amount: e.vtxo.amount(),
429 confirmation_height: Some(e.height),
430 },
431 }
432 }
433}
434
435#[derive(Debug, Clone, PartialEq, Eq, Hash)]
437pub struct Board {
438 pub funding_txid: bitcoin::Txid,
442 pub vtxos: Vec<Vtxo>,
447}
448
449#[derive(Debug, Clone, PartialEq, Eq, Hash)]
452pub struct Offboard {
453 pub round: RoundId,
455}
456
457pub struct OffchainBalance {
460 pub available: Amount,
462 pub pending_in_round: Amount,
464 pub pending_exit: Amount,
467}
468
469#[derive(Debug, Clone)]
471pub struct WalletProperties {
472 pub network: Network,
476
477 pub fingerprint: Fingerprint,
481}
482
483pub struct VtxoSeed(bip32::Xpriv);
489
490impl VtxoSeed {
491 fn new(network: Network, seed: &[u8; 64]) -> Self {
492 let master = bip32::Xpriv::new_master(network, seed).unwrap();
493
494 Self(master.derive_priv(&SECP, &[ARK_PURPOSE_INDEX.into()]).unwrap())
495 }
496
497 fn fingerprint(&self) -> Fingerprint {
498 self.0.fingerprint(&SECP)
499 }
500
501 fn derive_keypair(&self, idx: u32) -> Keypair {
502 self.0.derive_priv(&SECP, &[idx.into()]).unwrap().to_keypair(&SECP)
503 }
504}
505
506pub struct Wallet {
640 pub chain: Arc<ChainSource>,
642
643 pub exit: RwLock<Exit>,
645
646 pub movements: Arc<MovementManager>,
648
649 config: Config,
651
652 db: Arc<dyn BarkPersister>,
654
655 vtxo_seed: VtxoSeed,
657
658 server: Option<ServerConnection>,
660
661 subsystem_ids: HashMap<BarkSubsystem, SubsystemId>,
663}
664
665impl Wallet {
666 pub fn chain_source(
669 config: &Config,
670 ) -> anyhow::Result<onchain::ChainSourceSpec> {
671 if let Some(ref url) = config.esplora_address {
672 Ok(onchain::ChainSourceSpec::Esplora {
673 url: url.clone(),
674 })
675 } else if let Some(ref url) = config.bitcoind_address {
676 let auth = if let Some(ref c) = config.bitcoind_cookiefile {
677 bitcoin_ext::rpc::Auth::CookieFile(c.clone())
678 } else {
679 bitcoin_ext::rpc::Auth::UserPass(
680 config.bitcoind_user.clone().context("need bitcoind auth config")?,
681 config.bitcoind_pass.clone().context("need bitcoind auth config")?,
682 )
683 };
684 Ok(onchain::ChainSourceSpec::Bitcoind {
685 url: url.clone(),
686 auth,
687 })
688 } else {
689 bail!("Need to either provide esplora or bitcoind info");
690 }
691 }
692
693 pub fn require_chainsource_version(&self) -> anyhow::Result<()> {
697 self.chain.require_version()
698 }
699
700 pub fn derive_store_next_keypair(&self) -> anyhow::Result<(Keypair, u32)> {
703 let last_revealed = self.db.get_last_vtxo_key_index()?;
704
705 let index = last_revealed.map(|i| i + 1).unwrap_or(u32::MIN);
706 let keypair = self.vtxo_seed.derive_keypair(index);
707
708 self.db.store_vtxo_key(index, keypair.public_key())?;
709 Ok((keypair, index))
710 }
711
712 pub fn peak_keypair(&self, index: u32) -> anyhow::Result<Keypair> {
726 let keypair = self.vtxo_seed.derive_keypair(index);
727 if self.db.get_public_key_idx(&keypair.public_key())?.is_some() {
728 Ok(keypair)
729 } else {
730 bail!("VTXO key {} does not exist, please derive it first", index)
731 }
732 }
733
734
735 pub fn pubkey_keypair(&self, public_key: &PublicKey) -> anyhow::Result<Option<(u32, Keypair)>> {
747 if let Some(index) = self.db.get_public_key_idx(&public_key)? {
748 Ok(Some((index, self.vtxo_seed.derive_keypair(index))))
749 } else {
750 Ok(None)
751 }
752 }
753
754 pub fn get_vtxo_key(&self, vtxo: &Vtxo) -> anyhow::Result<Keypair> {
765 let idx = self.db.get_public_key_idx(&vtxo.user_pubkey())?
766 .context("VTXO key not found")?;
767 Ok(self.vtxo_seed.derive_keypair(idx))
768 }
769
770 pub fn new_address(&self) -> anyhow::Result<ark::Address> {
772 let ark = &self.require_server()?;
773 let network = self.properties()?.network;
774 let pubkey = self.derive_store_next_keypair()?.0.public_key();
775
776 Ok(ark::Address::builder()
777 .testnet(network != bitcoin::Network::Bitcoin)
778 .server_pubkey(ark.info.server_pubkey)
779 .pubkey_policy(pubkey)
780 .into_address().unwrap())
781 }
782
783 pub fn peak_address(&self, index: u32) -> anyhow::Result<ark::Address> {
787 let ark = &self.require_server()?;
788 let network = self.properties()?.network;
789 let pubkey = self.peak_keypair(index)?.public_key();
790
791 Ok(ark::Address::builder()
792 .testnet(network != Network::Bitcoin)
793 .server_pubkey(ark.info.server_pubkey)
794 .pubkey_policy(pubkey)
795 .into_address().unwrap())
796 }
797
798 pub fn new_address_with_index(&self) -> anyhow::Result<(ark::Address, u32)> {
802 let ark = &self.require_server()?;
803 let network = self.properties()?.network;
804 let (keypair, index) = self.derive_store_next_keypair()?;
805 let pubkey = keypair.public_key();
806 let addr = ark::Address::builder()
807 .testnet(network != bitcoin::Network::Bitcoin)
808 .server_pubkey(ark.info.server_pubkey)
809 .pubkey_policy(pubkey)
810 .into_address()?;
811 Ok((addr, index))
812 }
813
814 pub async fn create(
820 mnemonic: &Mnemonic,
821 network: Network,
822 config: Config,
823 db: Arc<dyn BarkPersister>,
824 force: bool,
825 ) -> anyhow::Result<Wallet> {
826 trace!("Config: {:?}", config);
827 if let Some(existing) = db.read_properties()? {
828 trace!("Existing config: {:?}", existing);
829 bail!("cannot overwrite already existing config")
830 }
831
832 if !force {
833 if let Err(err) = ServerConnection::connect(&config.server_address, network).await {
834 bail!("Failed to connect to provided server (if you are sure use the --force flag): {}", err);
835 }
836 }
837
838 let wallet_fingerprint = VtxoSeed::new(network, &mnemonic.to_seed("")).fingerprint();
839 let properties = WalletProperties {
840 network: network,
841 fingerprint: wallet_fingerprint,
842 };
843
844 db.init_wallet(&properties).context("cannot init wallet in the database")?;
846 info!("Created wallet with fingerprint: {}", wallet_fingerprint);
847
848 let wallet = Wallet::open(&mnemonic, db, config).await.context("failed to open wallet")?;
850 wallet.require_chainsource_version()?;
851
852 Ok(wallet)
853 }
854
855 pub async fn create_with_onchain(
863 mnemonic: &Mnemonic,
864 network: Network,
865 config: Config,
866 db: Arc<dyn BarkPersister>,
867 onchain: &dyn ExitUnilaterally,
868 force: bool,
869 ) -> anyhow::Result<Wallet> {
870 let mut wallet = Wallet::create(mnemonic, network, config, db, force).await?;
871 wallet.exit.get_mut().load(onchain).await?;
872 Ok(wallet)
873 }
874
875 pub async fn open(
877 mnemonic: &Mnemonic,
878 db: Arc<dyn BarkPersister>,
879 config: Config,
880 ) -> anyhow::Result<Wallet> {
881 let properties = db.read_properties()?.context("Wallet is not initialised")?;
882
883 let seed = mnemonic.to_seed("");
884 let vtxo_seed = VtxoSeed::new(properties.network, &seed);
885
886 if properties.fingerprint != vtxo_seed.fingerprint() {
887 bail!("incorrect mnemonic")
888 }
889
890 let chain_source = if let Some(ref url) = config.esplora_address {
891 onchain::ChainSourceSpec::Esplora {
892 url: url.clone(),
893 }
894 } else if let Some(ref url) = config.bitcoind_address {
895 let auth = if let Some(ref c) = config.bitcoind_cookiefile {
896 bitcoin_ext::rpc::Auth::CookieFile(c.clone())
897 } else {
898 bitcoin_ext::rpc::Auth::UserPass(
899 config.bitcoind_user.clone().context("need bitcoind auth config")?,
900 config.bitcoind_pass.clone().context("need bitcoind auth config")?,
901 )
902 };
903 onchain::ChainSourceSpec::Bitcoind { url: url.clone(), auth }
904 } else {
905 bail!("Need to either provide esplora or bitcoind info");
906 };
907
908 let chain_source_client = ChainSource::new(
909 chain_source, properties.network, config.fallback_fee_rate,
910 ).await?;
911 let chain = Arc::new(chain_source_client);
912
913 let server = match ServerConnection::connect(
914 &config.server_address, properties.network,
915 ).await {
916 Ok(s) => Some(s),
917 Err(e) => {
918 warn!("Ark server handshake failed: {}", e);
919 None
920 }
921 };
922
923 let movements = Arc::new(MovementManager::new(db.clone()));
924 let exit = RwLock::new(Exit::new(db.clone(), chain.clone(), movements.clone()).await?);
925 let mut subsystem_ids = HashMap::new();
926 {
927 let subsystems = [
928 BarkSubsystem::Arkoor,
929 BarkSubsystem::Board,
930 BarkSubsystem::LightningReceive,
931 BarkSubsystem::LightningSend,
932 BarkSubsystem::Round,
933 ];
934 for subsystem in subsystems.into_iter() {
935 let id = movements.register_subsystem(subsystem.as_str().into()).await?;
936 subsystem_ids.insert(subsystem, id);
937 }
938 };
939
940 Ok(Wallet { config, db, vtxo_seed, exit, movements, server, chain, subsystem_ids })
941 }
942
943 pub async fn open_with_onchain(
946 mnemonic: &Mnemonic,
947 db: Arc<dyn BarkPersister>,
948 onchain: &dyn ExitUnilaterally,
949 cfg: Config,
950 ) -> anyhow::Result<Wallet> {
951 let mut wallet = Wallet::open(mnemonic, db, cfg).await?;
952 wallet.exit.get_mut().load(onchain).await?;
953 Ok(wallet)
954 }
955
956 pub fn config(&self) -> &Config {
958 &self.config
959 }
960
961 pub fn properties(&self) -> anyhow::Result<WalletProperties> {
963 let properties = self.db.read_properties()?.context("Wallet is not initialised")?;
964 Ok(properties)
965 }
966
967 fn require_server(&self) -> anyhow::Result<ServerConnection> {
968 self.server.clone().context("You should be connected to Ark server to perform this action")
969 }
970
971 pub async fn check_connection(&self) -> anyhow::Result<()> {
972 let mut server = self.require_server()?;
973 server.client.handshake(protos::HandshakeRequest {
974 bark_version: Some(env!("CARGO_PKG_VERSION").into()),
975 }).await?;
976
977 Ok(())
978 }
979
980 pub fn ark_info(&self) -> Option<&ArkInfo> {
982 self.server.as_ref().map(|a| &a.info)
983 }
984
985 pub fn balance(&self) -> anyhow::Result<Balance> {
989 let vtxos = self.vtxos()?;
990
991 let spendable = {
992 let mut v = vtxos.iter().collect();
993 VtxoStateKind::Spendable.filter_vtxos(&mut v)?;
994 v.into_iter().map(|v| v.amount()).sum::<Amount>()
995 };
996
997 let pending_lightning_send = self.pending_lightning_send_vtxos()?.iter().map(|v| v.amount())
998 .sum::<Amount>();
999
1000 let pending_lightning_receive = self.pending_lightning_receive_balance()?;
1001
1002 let pending_board = self.pending_board_vtxos()?.iter().map(|v| v.amount()).sum::<Amount>();
1003
1004 let pending_in_round = self.pending_round_input_vtxos()?.iter().map(|v| v.amount()).sum();
1005
1006 let pending_exit = self.exit.try_read().ok().map(|e| e.pending_total());
1007
1008 Ok(Balance {
1009 spendable,
1010 pending_in_round,
1011 pending_lightning_send,
1012 pending_lightning_receive,
1013 pending_exit,
1014 pending_board,
1015 })
1016 }
1017
1018 pub async fn validate_vtxo(&self, vtxo: &Vtxo) -> anyhow::Result<()> {
1020 let tx = self.chain.get_tx(&vtxo.chain_anchor().txid).await
1021 .context("could not fetch chain tx")?;
1022
1023 let tx = tx.with_context(|| {
1024 format!("vtxo chain anchor not found for vtxo: {}", vtxo.chain_anchor().txid)
1025 })?;
1026
1027 vtxo.validate(&tx)?;
1028
1029 Ok(())
1030 }
1031
1032 pub fn get_vtxo_by_id(&self, vtxo_id: VtxoId) -> anyhow::Result<WalletVtxo> {
1034 let vtxo = self.db.get_wallet_vtxo(vtxo_id)
1035 .with_context(|| format!("Error when querying vtxo {} in database", vtxo_id))?
1036 .with_context(|| format!("The VTXO with id {} cannot be found", vtxo_id))?;
1037 Ok(vtxo)
1038 }
1039
1040 pub fn movements(&self) -> anyhow::Result<Vec<Movement>> {
1042 Ok(self.db.get_movements()?)
1043 }
1044
1045 pub fn all_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1047 Ok(self.db.get_all_vtxos()?)
1048 }
1049
1050 pub fn vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1052 Ok(self.db.get_vtxos_by_state(&UNSPENT_STATES)?)
1053 }
1054
1055 pub fn vtxos_with(&self, filter: &impl FilterVtxos) -> anyhow::Result<Vec<WalletVtxo>> {
1057 let mut vtxos = self.vtxos()?;
1058 filter.filter_vtxos(&mut vtxos).context("error filtering vtxos")?;
1059 Ok(vtxos)
1060 }
1061
1062 pub fn spendable_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1064 Ok(self.vtxos_with(&VtxoStateKind::Spendable)?)
1065 }
1066
1067 pub fn spendable_vtxos_with(
1069 &self,
1070 filter: &impl FilterVtxos,
1071 ) -> anyhow::Result<Vec<WalletVtxo>> {
1072 let mut vtxos = self.spendable_vtxos()?;
1073 filter.filter_vtxos(&mut vtxos).context("error filtering vtxos")?;
1074 Ok(vtxos)
1075 }
1076
1077 pub fn pending_board_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1082 let vtxos = self.db.get_all_pending_board_ids()?.iter()
1083 .map(|vtxo_id| self.get_vtxo_by_id(*vtxo_id))
1084 .collect::<anyhow::Result<Vec<_>>>()?;
1085
1086 debug_assert!(vtxos.iter().all(|v| matches!(v.state.kind(), VtxoStateKind::Locked)),
1087 "all pending board vtxos should be locked"
1088 );
1089
1090 Ok(vtxos)
1091 }
1092
1093 pub fn pending_round_input_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1098 let mut ret = Vec::new();
1099 for round in self.db.load_round_states()? {
1100 let inputs = round.state.locked_pending_inputs();
1101 ret.reserve(inputs.len());
1102 for input in inputs {
1103 ret.push(self.get_vtxo_by_id(input.id()).context("unknown round input VTXO")?);
1104 }
1105 }
1106 Ok(ret)
1107 }
1108
1109 pub fn pending_lightning_send_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1111 let vtxos = self.db.get_all_pending_lightning_send()?.into_iter()
1112 .flat_map(|pending_lightning_send| pending_lightning_send.htlc_vtxos)
1113 .collect::<Vec<_>>();
1114
1115 Ok(vtxos)
1116 }
1117
1118 pub async fn get_expiring_vtxos(
1120 &self,
1121 threshold: BlockHeight,
1122 ) -> anyhow::Result<Vec<WalletVtxo>> {
1123 let expiry = self.chain.tip().await? + threshold;
1124 let filter = VtxoFilter::new(&self).expires_before(expiry);
1125 Ok(self.spendable_vtxos_with(&filter)?)
1126 }
1127
1128 pub async fn sync_pending_boards(&self) -> anyhow::Result<()> {
1132 let ark_info = self.require_server()?.info;
1133 let current_height = self.chain.tip().await?;
1134 let unregistered_boards = self.pending_board_vtxos()?;
1135 let mut registered_boards = 0;
1136
1137 if unregistered_boards.is_empty() {
1138 return Ok(());
1139 }
1140
1141 trace!("Attempting registration of sufficiently confirmed boards");
1142
1143 for board in unregistered_boards {
1144 let anchor = board.vtxo.chain_anchor();
1145 let confs = match self.chain.tx_status(anchor.txid).await {
1146 Ok(TxStatus::Confirmed(block_ref)) => Some(current_height - (block_ref.height - 1)),
1147 Ok(TxStatus::Mempool) => Some(0),
1148 Ok(TxStatus::NotFound) => None,
1149 Err(_) => None,
1150 };
1151
1152 if let Some(confs) = confs {
1153 if confs >= ark_info.required_board_confirmations as BlockHeight {
1154 if let Err(e) = self.register_board(board.vtxo.id()).await {
1155 warn!("Failed to register board {}: {}", board.vtxo.id(), e);
1156 } else {
1157 info!("Registered board {}", board.vtxo.id());
1158 registered_boards += 1;
1159 }
1160 }
1161 }
1162 };
1163
1164 if registered_boards > 0 {
1165 info!("Registered {registered_boards} sufficiently confirmed boards");
1166 }
1167 Ok(())
1168 }
1169
1170 pub async fn maintenance(&self) -> anyhow::Result<()> {
1175 info!("Starting wallet maintenance");
1176 self.sync().await;
1177 self.maintenance_refresh().await?;
1178 Ok(())
1179 }
1180
1181 pub async fn maintenance_with_onchain<W: PreparePsbt + SignPsbt + ExitUnilaterally>(
1187 &self,
1188 onchain: &mut W,
1189 ) -> anyhow::Result<()> {
1190 info!("Starting wallet maintenance with onchain wallet");
1191 self.sync().await;
1192 self.maintenance_refresh().await?;
1193
1194 self.sync_exits(onchain).await?;
1196
1197 Ok(())
1198 }
1199
1200 pub async fn maintenance_refresh(&self) -> anyhow::Result<Option<RoundStatus>> {
1206 let vtxos = self.get_vtxos_to_refresh().await?.into_iter()
1207 .map(|v| v.id())
1208 .collect::<Vec<_>>();
1209 if vtxos.len() == 0 {
1210 return Ok(None);
1211 }
1212
1213 info!("Performing maintenance refresh");
1214 self.refresh_vtxos(vtxos).await
1215 }
1216
1217 pub async fn sync(&self) {
1223 tokio::join!(
1224 async {
1225 if let Err(e) = self.chain.update_fee_rates(self.config.fallback_fee_rate).await {
1228 warn!("Error updating fee rates: {:#}", e);
1229 }
1230 },
1231 async {
1232 if let Err(e) = self.sync_oors().await {
1233 warn!("Error in arkoor sync: {:#}", e);
1234 }
1235 },
1236 async {
1237 if let Err(e) = self.sync_pending_rounds().await {
1238 warn!("Error while trying to progress rounds awaiting confirmations: {:#}", e);
1239 }
1240 },
1241 async {
1242 if let Err(e) = self.sync_pending_lightning_send_vtxos().await {
1243 warn!("Error syncing pending lightning payments: {:#}", e);
1244 }
1245 },
1246 async {
1247 if let Err(e) = self.try_claim_all_lightning_receives(false).await {
1248 warn!("Error claiming pending lightning receives: {:#}", e);
1249 }
1250 },
1251 async {
1252 if let Err(e) = self.sync_pending_boards().await {
1253 warn!("Error syncing pending boards: {:#}", e);
1254 }
1255 }
1256 );
1257 }
1258
1259 pub async fn sync_exits(
1265 &self,
1266 onchain: &mut dyn ExitUnilaterally,
1267 ) -> anyhow::Result<()> {
1268 self.exit.write().await.sync_exit(onchain).await?;
1269 Ok(())
1270 }
1271
1272 pub fn pending_lightning_sends(&self) -> anyhow::Result<Vec<PendingLightningSend>> {
1273 Ok(self.db.get_all_pending_lightning_send()?)
1274 }
1275
1276 pub async fn sync_pending_lightning_send_vtxos(&self) -> anyhow::Result<()> {
1279 let pending_payments = self.pending_lightning_sends()?;
1280
1281 if pending_payments.is_empty() {
1282 return Ok(());
1283 }
1284
1285 info!("Syncing {} pending lightning sends", pending_payments.len());
1286
1287 for payment in pending_payments {
1288 self.check_lightning_payment(&payment).await?;
1289 }
1290
1291 Ok(())
1292 }
1293
1294 pub async fn dangerous_drop_vtxo(&self, vtxo_id: VtxoId) -> anyhow::Result<()> {
1297 warn!("Drop vtxo {} from the database", vtxo_id);
1298 self.db.remove_vtxo(vtxo_id)?;
1299 Ok(())
1300 }
1301
1302 pub async fn dangerous_drop_all_vtxos(&self) -> anyhow::Result<()> {
1305 warn!("Dropping all vtxos from the db...");
1306 for vtxo in self.vtxos()? {
1307 self.db.remove_vtxo(vtxo.id())?;
1308 }
1309
1310 self.exit.write().await.clear_exit()?;
1311 Ok(())
1312 }
1313
1314 pub async fn board_amount(
1318 &self,
1319 onchain: &mut dyn onchain::Board,
1320 amount: Amount,
1321 ) -> anyhow::Result<Board> {
1322 let (user_keypair, _) = self.derive_store_next_keypair()?;
1323 self.board(onchain, Some(amount), user_keypair).await
1324 }
1325
1326 pub async fn board_all(
1328 &self,
1329 onchain: &mut dyn onchain::Board,
1330 ) -> anyhow::Result<Board> {
1331 let (user_keypair, _) = self.derive_store_next_keypair()?;
1332 self.board(onchain, None, user_keypair).await
1333 }
1334
1335 async fn board(
1336 &self,
1337 wallet: &mut dyn onchain::Board,
1338 amount: Option<Amount>,
1339 user_keypair: Keypair,
1340 ) -> anyhow::Result<Board> {
1341 let mut srv = self.require_server()?;
1342 let properties = self.db.read_properties()?.context("Missing config")?;
1343 let current_height = self.chain.tip().await?;
1344
1345 let expiry_height = current_height + srv.info.vtxo_expiry_delta as BlockHeight;
1346 let builder = BoardBuilder::new(
1347 user_keypair.public_key(),
1348 expiry_height,
1349 srv.info.server_pubkey,
1350 srv.info.vtxo_exit_delta,
1351 );
1352
1353 let addr = bitcoin::Address::from_script(
1354 &builder.funding_script_pubkey(),
1355 properties.network,
1356 )?;
1357
1358 let fee_rate = self.chain.fee_rates().await.regular;
1360 let (board_psbt, amount) = if let Some(amount) = amount {
1361 let psbt = wallet.prepare_tx(&[(addr, amount)], fee_rate)?;
1362 (psbt, amount)
1363 } else {
1364 let psbt = wallet.prepare_drain_tx(addr, fee_rate)?;
1365 assert_eq!(psbt.unsigned_tx.output.len(), 1);
1366 let amount = psbt.unsigned_tx.output[0].value;
1367 (psbt, amount)
1368 };
1369
1370 ensure!(amount >= srv.info.min_board_amount,
1371 "board amount of {amount} is less than minimum board amount required by server ({})",
1372 srv.info.min_board_amount,
1373 );
1374
1375 let utxo = OutPoint::new(board_psbt.unsigned_tx.compute_txid(), BOARD_FUNDING_TX_VTXO_VOUT);
1376 let builder = builder
1377 .set_funding_details(amount, utxo)
1378 .generate_user_nonces();
1379
1380 let cosign_resp = srv.client.request_board_cosign(protos::BoardCosignRequest {
1381 amount: amount.to_sat(),
1382 utxo: bitcoin::consensus::serialize(&utxo), expiry_height,
1384 user_pubkey: user_keypair.public_key().serialize().to_vec(),
1385 pub_nonce: builder.user_pub_nonce().serialize().to_vec(),
1386 }).await.context("error requesting board cosign")?
1387 .into_inner().try_into().context("invalid cosign response from server")?;
1388
1389 ensure!(builder.verify_cosign_response(&cosign_resp),
1390 "invalid board cosignature received from server",
1391 );
1392
1393 let vtxo = builder.build_vtxo(&cosign_resp, &user_keypair)?;
1395
1396 let onchain_fee = board_psbt.fee()?;
1397 let movement_id = self.movements.new_movement(
1398 self.subsystem_ids[&BarkSubsystem::Board],
1399 BoardMovement::Board.to_string(),
1400 ).await?;
1401 self.movements.update_movement(
1402 movement_id,
1403 MovementUpdate::new()
1404 .produced_vtxo(&vtxo)
1405 .intended_and_effective_balance(vtxo.amount().to_signed()?)
1406 .metadata(BoardMovement::metadata(utxo, onchain_fee)?),
1407 ).await?;
1408 self.store_locked_vtxos([&vtxo], Some(movement_id))?;
1409
1410 let tx = wallet.finish_tx(board_psbt)?;
1411 self.db.store_pending_board(vtxo.id(), &tx, movement_id)?;
1412
1413 trace!("Broadcasting board tx: {}", bitcoin::consensus::encode::serialize_hex(&tx));
1414 self.chain.broadcast_tx(&tx).await?;
1415
1416 info!("Board broadcasted");
1417 Ok(Board {
1418 funding_txid: tx.compute_txid(),
1419 vtxos: vec![vtxo.into()],
1420 })
1421 }
1422
1423 async fn register_board(&self, vtxo: impl VtxoRef) -> anyhow::Result<Board> {
1425 trace!("Attempting to register board {} to server", vtxo.vtxo_id());
1426 let mut srv = self.require_server()?;
1427
1428 let vtxo = match vtxo.vtxo() {
1430 Some(v) => v,
1431 None => {
1432 &self.db.get_wallet_vtxo(vtxo.vtxo_id())?
1433 .with_context(|| format!("VTXO doesn't exist: {}", vtxo.vtxo_id()))?
1434 },
1435 };
1436
1437 srv.client.register_board_vtxo(protos::BoardVtxoRequest {
1439 board_vtxo: vtxo.serialize(),
1440 }).await.context("error registering board with the Ark server")?;
1441
1442 self.db.update_vtxo_state_checked(vtxo.id(), VtxoState::Spendable, &UNSPENT_STATES)?;
1445
1446 let movement_id = self.db.get_pending_board_movement_id(vtxo.id())?;
1447 self.db.remove_pending_board(&vtxo.id())?;
1448
1449 self.movements.finish_movement(movement_id, MovementStatus::Finished).await?;
1450
1451 let funding_txid = vtxo.chain_anchor().txid;
1452
1453 Ok(Board {
1454 funding_txid,
1455 vtxos: vec![vtxo.clone()],
1456 })
1457 }
1458
1459 fn has_counterparty_risk(&self, vtxo: &Vtxo) -> anyhow::Result<bool> {
1464 for past_pk in vtxo.past_arkoor_pubkeys() {
1465 if !self.db.get_public_key_idx(&past_pk)?.is_some() {
1466 return Ok(true);
1467 }
1468 }
1469 Ok(!self.db.get_public_key_idx(&vtxo.user_pubkey())?.is_some())
1470 }
1471
1472 pub async fn sync_oors(&self) -> anyhow::Result<()> {
1473 let last_pk_index = self.db.get_last_vtxo_key_index()?.unwrap_or_default();
1474 let pubkeys = (0..=last_pk_index).map(|idx| {
1475 self.vtxo_seed.derive_keypair(idx).public_key()
1476 }).collect::<Vec<_>>();
1477
1478 self.sync_arkoor_for_pubkeys(&pubkeys).await?;
1479
1480 Ok(())
1481 }
1482
1483 async fn sync_arkoor_for_pubkeys(
1485 &self,
1486 public_keys: &[PublicKey],
1487 ) -> anyhow::Result<()> {
1488 let mut srv = self.require_server()?;
1489
1490 for pubkeys in public_keys.chunks(rpc::MAX_NB_MAILBOX_PUBKEYS) {
1491 debug!("Emptying OOR mailbox at Ark server...");
1493 let req = protos::ArkoorVtxosRequest {
1494 pubkeys: pubkeys.iter().map(|pk| pk.serialize().to_vec()).collect(),
1495 };
1496 let packages = srv.client.empty_arkoor_mailbox(req).await
1497 .context("error fetching oors")?.into_inner().packages;
1498 debug!("Ark server has {} arkoor packages for us", packages.len());
1499
1500 for package in packages {
1501 let mut vtxos = Vec::with_capacity(package.vtxos.len());
1502 for vtxo in package.vtxos {
1503 let vtxo = match Vtxo::deserialize(&vtxo) {
1504 Ok(vtxo) => vtxo,
1505 Err(e) => {
1506 warn!("Invalid vtxo from Ark server: {}", e);
1507 continue;
1508 }
1509 };
1510
1511 if let Err(e) = self.validate_vtxo(&vtxo).await {
1512 error!("Received invalid arkoor VTXO from server: {}", e);
1513 continue;
1514 }
1515
1516 match self.db.has_spent_vtxo(vtxo.id()) {
1517 Ok(spent) if spent => {
1518 debug!("Not adding OOR vtxo {} because it is considered spent", vtxo.id());
1519 continue;
1520 },
1521 _ => {}
1522 }
1523
1524 if let Ok(Some(_)) = self.db.get_wallet_vtxo(vtxo.id()) {
1525 debug!("Not adding OOR vtxo {} because it already exists", vtxo.id());
1526 continue;
1527 }
1528
1529 vtxos.push(vtxo);
1530 }
1531
1532 self.store_spendable_vtxos(&vtxos)?;
1533 self.movements.new_finished_movement(
1534 self.subsystem_ids[&BarkSubsystem::Arkoor],
1535 ArkoorMovement::Receive.to_string(),
1536 MovementStatus::Finished,
1537 MovementUpdate::new()
1538 .produced_vtxos(&vtxos)
1539 .intended_and_effective_balance(
1540 vtxos
1541 .iter()
1542 .map(|vtxo| vtxo.amount()).sum::<Amount>()
1543 .to_signed()?,
1544 ),
1545 ).await?;
1546 }
1547 }
1548
1549 Ok(())
1550 }
1551
1552 async fn add_should_refresh_vtxos(
1556 &self,
1557 participation: &mut RoundParticipation,
1558 ) -> anyhow::Result<()> {
1559 let excluded_ids = participation.inputs.iter().map(|v| v.vtxo_id())
1560 .collect::<HashSet<_>>();
1561
1562 let should_refresh_vtxos = self.get_vtxos_to_refresh().await?.into_iter()
1563 .filter(|v| !excluded_ids.contains(&v.id()))
1564 .map(|v| v.vtxo).collect::<Vec<_>>();
1565
1566
1567 let total_amount = should_refresh_vtxos.iter().map(|v| v.amount()).sum::<Amount>();
1568
1569 if total_amount > P2TR_DUST {
1570 let (user_keypair, _) = self.derive_store_next_keypair()?;
1571 let req = VtxoRequest {
1572 policy: VtxoPolicy::new_pubkey(user_keypair.public_key()),
1573 amount: total_amount,
1574 };
1575
1576 participation.inputs.extend(should_refresh_vtxos);
1577 participation.outputs.push(req);
1578 }
1579
1580 Ok(())
1581 }
1582
1583 pub fn build_offboard_participation<V: VtxoRef>(
1584 &self,
1585 vtxos: impl IntoIterator<Item = V>,
1586 destination: ScriptBuf,
1587 ) -> anyhow::Result<RoundParticipation> {
1588 let srv = self.require_server()?;
1589
1590 let vtxos = {
1591 let vtxos = vtxos.into_iter();
1592 let mut ret = Vec::with_capacity(vtxos.size_hint().0);
1593 for v in vtxos {
1594 let vtxo = match v.vtxo() {
1595 Some(v) => v.clone(),
1596 None => self.get_vtxo_by_id(v.vtxo_id()).context("vtxo not found")?.vtxo,
1597 };
1598 ret.push(vtxo);
1599 }
1600 ret
1601 };
1602
1603 if vtxos.is_empty() {
1604 bail!("no VTXO to offboard");
1605 }
1606
1607 let fee = OffboardRequest::calculate_fee(&destination, srv.info.offboard_feerate)
1608 .expect("bdk created invalid scriptPubkey");
1609
1610 let vtxo_sum = vtxos.iter().map(|v| v.amount()).sum::<Amount>();
1611
1612 if fee > vtxo_sum {
1613 bail!("offboarded amount is lower than fees. Need {fee}, got: {vtxo_sum}");
1614 }
1615
1616 let offb = OffboardRequest {
1617 amount: vtxo_sum - fee,
1618 script_pubkey: destination.clone(),
1619 };
1620
1621 Ok(RoundParticipation {
1622 inputs: vtxos.clone(),
1623 outputs: Vec::new(),
1624 offboards: vec![offb],
1625 })
1626 }
1627
1628 async fn offboard<V: VtxoRef>(
1629 &self,
1630 vtxos: impl IntoIterator<Item = V>,
1631 destination: ScriptBuf,
1632 ) -> anyhow::Result<RoundStatus> {
1633 let mut participation = self.build_offboard_participation(vtxos, destination.clone())?;
1634
1635 if let Err(e) = self.add_should_refresh_vtxos(&mut participation).await {
1636 warn!("Error trying to add additional VTXOs that should be refreshed: {:#}", e);
1637 }
1638
1639 Ok(self.participate_round(participation, Some(RoundMovement::Offboard)).await?)
1640 }
1641
1642 pub async fn offboard_all(&self, address: bitcoin::Address) -> anyhow::Result<RoundStatus> {
1644 let input_vtxos = self.spendable_vtxos()?;
1645 Ok(self.offboard(input_vtxos, address.script_pubkey()).await?)
1646 }
1647
1648 pub async fn offboard_vtxos<V: VtxoRef>(
1650 &self,
1651 vtxos: impl IntoIterator<Item = V>,
1652 address: bitcoin::Address,
1653 ) -> anyhow::Result<RoundStatus> {
1654 let input_vtxos = vtxos
1655 .into_iter()
1656 .map(|v| {
1657 let id = v.vtxo_id();
1658 match self.db.get_wallet_vtxo(id)? {
1659 Some(vtxo) => Ok(vtxo.vtxo),
1660 _ => bail!("cannot find requested vtxo: {}", id),
1661 }
1662 })
1663 .collect::<anyhow::Result<Vec<_>>>()?;
1664
1665 Ok(self.offboard(input_vtxos, address.script_pubkey()).await?)
1666 }
1667
1668 pub fn build_refresh_participation<V: VtxoRef>(
1669 &self,
1670 vtxos: impl IntoIterator<Item = V>,
1671 ) -> anyhow::Result<Option<RoundParticipation>> {
1672 let vtxos = {
1673 let mut ret = HashMap::new();
1674 for v in vtxos {
1675 let id = v.vtxo_id();
1676 let vtxo = self.get_vtxo_by_id(id)
1677 .with_context(|| format!("vtxo with id {} not found", id))?;
1678 if !ret.insert(id, vtxo).is_none() {
1679 bail!("duplicate VTXO id: {}", id);
1680 }
1681 }
1682 ret
1683 };
1684
1685 if vtxos.is_empty() {
1686 info!("Skipping refresh since no VTXOs are provided.");
1687 return Ok(None);
1688 }
1689
1690 let total_amount = vtxos.values().map(|v| v.vtxo.amount()).sum();
1691
1692 info!("Refreshing {} VTXOs (total amount = {}).", vtxos.len(), total_amount);
1693
1694 let (user_keypair, _) = self.derive_store_next_keypair()?;
1695 let req = VtxoRequest {
1696 policy: VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey: user_keypair.public_key() }),
1697 amount: total_amount,
1698 };
1699
1700 Ok(Some(RoundParticipation {
1701 inputs: vtxos.into_values().map(|v| v.vtxo).collect(),
1702 outputs: vec![req],
1703 offboards: Vec::new(),
1704 }))
1705 }
1706
1707 pub async fn refresh_vtxos<V: VtxoRef>(
1713 &self,
1714 vtxos: impl IntoIterator<Item = V>,
1715 ) -> anyhow::Result<Option<RoundStatus>> {
1716 let mut participation = match self.build_refresh_participation(vtxos)? {
1717 Some(participation) => participation,
1718 None => return Ok(None),
1719 };
1720
1721 if let Err(e) = self.add_should_refresh_vtxos(&mut participation).await {
1722 warn!("Error trying to add additional VTXOs that should be refreshed: {:#}", e);
1723 }
1724
1725 Ok(Some(self.participate_round(participation, Some(RoundMovement::Refresh)).await?))
1726 }
1727
1728 pub async fn get_vtxos_to_refresh(&self) -> anyhow::Result<Vec<WalletVtxo>> {
1732 let tip = self.chain.tip().await?;
1733 let fee_rate = self.chain.fee_rates().await.fast;
1734
1735 let must_refresh_vtxos = self.spendable_vtxos_with(
1737 &RefreshStrategy::must_refresh(self, tip, fee_rate),
1738 )?;
1739 if must_refresh_vtxos.is_empty() {
1740 return Ok(vec![]);
1741 } else {
1742 let should_refresh_vtxos = self.spendable_vtxos_with(
1745 &RefreshStrategy::should_refresh(self, tip, fee_rate),
1746 )?;
1747 Ok(should_refresh_vtxos)
1748 }
1749 }
1750
1751 pub fn get_first_expiring_vtxo_blockheight(
1753 &self,
1754 ) -> anyhow::Result<Option<BlockHeight>> {
1755 Ok(self.spendable_vtxos()?.iter().map(|v| v.expiry_height()).min())
1756 }
1757
1758 pub fn get_next_required_refresh_blockheight(
1761 &self,
1762 ) -> anyhow::Result<Option<BlockHeight>> {
1763 let first_expiry = self.get_first_expiring_vtxo_blockheight()?;
1764 Ok(first_expiry.map(|h| h.saturating_sub(self.config.vtxo_refresh_expiry_threshold)))
1765 }
1766
1767 fn select_vtxos_to_cover(
1773 &self,
1774 amount: Amount,
1775 max_depth: Option<u16>,
1776 expiry_threshold: Option<BlockHeight>,
1777 ) -> anyhow::Result<Vec<Vtxo>> {
1778 let inputs = self.spendable_vtxos()?;
1779
1780 let mut result = Vec::new();
1782 let mut total_amount = bitcoin::Amount::ZERO;
1783 for input in inputs {
1784 if let Some(max_depth) = max_depth {
1785 if input.arkoor_depth() >= max_depth {
1786 warn!("VTXO {} reached max depth of {}, skipping it. \
1787 Please refresh your VTXO.", input.id(), max_depth,
1788 );
1789 continue;
1790 }
1791 }
1792
1793 if let Some(threshold) = expiry_threshold {
1795 if input.expiry_height() < threshold {
1796 warn!("VTXO {} is expiring soon (expires at {}, threshold {}), \
1797 skipping for arkoor payment",
1798 input.id(), input.expiry_height(), threshold,
1799 );
1800 continue;
1801 }
1802 }
1803
1804 total_amount += input.amount();
1805 result.push(input.vtxo);
1806
1807 if total_amount >= amount {
1808 return Ok(result)
1809 }
1810 }
1811
1812 bail!("Insufficient money available. Needed {} but {} is available",
1813 amount, total_amount,
1814 );
1815 }
1816
1817 async fn create_arkoor_vtxos(
1823 &self,
1824 destination_policy: VtxoPolicy,
1825 amount: Amount,
1826 ) -> anyhow::Result<ArkoorCreateResult> {
1827 let mut srv = self.require_server()?;
1828 let change_pubkey = self.derive_store_next_keypair()?.0.public_key();
1829
1830 let req = VtxoRequest {
1831 amount,
1832 policy: destination_policy,
1833 };
1834
1835 let tip = self.chain.tip().await?;
1837 let inputs = self.select_vtxos_to_cover(
1838 req.amount,
1839 Some(srv.info.max_arkoor_depth),
1840 Some(tip + self.config.vtxo_refresh_expiry_threshold),
1841 )?;
1842
1843 let mut secs = Vec::with_capacity(inputs.len());
1844 let mut pubs = Vec::with_capacity(inputs.len());
1845 let mut keypairs = Vec::with_capacity(inputs.len());
1846 for input in inputs.iter() {
1847 let keypair = self.get_vtxo_key(&input)?;
1848 let (s, p) = musig::nonce_pair(&keypair);
1849 secs.push(s);
1850 pubs.push(p);
1851 keypairs.push(keypair);
1852 }
1853
1854 let builder = ArkoorPackageBuilder::new(&inputs, &pubs, req, Some(change_pubkey))?;
1855
1856 let req = protos::ArkoorPackageCosignRequest {
1857 arkoors: builder.arkoors.iter().map(|a| a.into()).collect(),
1858 };
1859 let cosign_resp: Vec<_> = srv.client.request_arkoor_package_cosign(req).await?
1860 .into_inner().try_into().context("invalid server cosign response")?;
1861 ensure!(builder.verify_cosign_response(&cosign_resp),
1862 "invalid arkoor cosignature received from server",
1863 );
1864
1865 let (sent, change) = builder.build_vtxos(&cosign_resp, &keypairs, secs)?;
1866
1867 if let Some(change) = change.as_ref() {
1868 info!("Added change VTXO of {}", change.amount());
1869 }
1870
1871 Ok(ArkoorCreateResult {
1872 input: inputs,
1873 created: sent,
1874 change,
1875 })
1876 }
1877
1878 pub fn validate_arkoor_address(&self, address: &ark::Address) -> anyhow::Result<()> {
1882 let asp = self.require_server()?;
1883
1884 if !address.ark_id().is_for_server(asp.info.server_pubkey) {
1885 bail!("Ark address is for different server");
1886 }
1887
1888 match address.policy().policy_type() {
1890 VtxoPolicyKind::Pubkey => {},
1891 VtxoPolicyKind::ServerHtlcRecv | VtxoPolicyKind::ServerHtlcSend => {
1892 bail!("VTXO policy in address cannot be used for arkoor payment: {}",
1893 address.policy().policy_type(),
1894 );
1895 }
1896 }
1897
1898 if address.delivery().is_empty() {
1899 bail!("No VTXO delivery mechanism provided in address");
1900 }
1901 if !address.delivery().iter().any(|d| !d.is_unknown()) {
1905 for d in address.delivery() {
1906 if let VtxoDelivery::Unknown { delivery_type, data } = d {
1907 info!("Unknown delivery in address: type={:#x}, data={}",
1908 delivery_type, data.as_hex(),
1909 );
1910 }
1911 }
1912 }
1913
1914 Ok(())
1915 }
1916
1917 pub async fn send_arkoor_payment(
1927 &self,
1928 destination: &ark::Address,
1929 amount: Amount,
1930 ) -> anyhow::Result<Vec<Vtxo>> {
1931 let mut srv = self.require_server()?;
1932
1933 self.validate_arkoor_address(&destination).context("cannot send to address")?;
1934
1935 if amount < P2TR_DUST {
1936 bail!("Sent amount must be at least {}", P2TR_DUST);
1937 }
1938
1939 let mut movement = MovementGuard::new_movement(
1940 self.movements.clone(),
1941 self.subsystem_ids[&BarkSubsystem::Arkoor],
1942 ArkoorMovement::Send.to_string(),
1943 ).await?;
1944 let arkoor = self.create_arkoor_vtxos(destination.policy().clone(), amount).await?;
1945 movement.apply_update(
1946 arkoor.to_movement_update()?
1947 .sent_to([MovementDestination::new(destination.to_string(), amount)])
1948 .intended_and_effective_balance(-amount.to_signed()?)
1949 ).await?;
1950
1951 let req = protos::ArkoorPackage {
1952 arkoors: arkoor.created.iter().map(|v| protos::ArkoorVtxo {
1953 pubkey: destination.policy().user_pubkey().serialize().to_vec(),
1954 vtxo: v.serialize().to_vec(),
1955 }).collect(),
1956 };
1957
1958 if let Err(e) = srv.client.post_arkoor_package_mailbox(req).await {
1961 error!("Failed to post the arkoor vtxo to the recipients mailbox: '{:#}'", e);
1962 }
1964 self.mark_vtxos_as_spent(&arkoor.input)?;
1965 if let Some(change) = arkoor.change {
1966 self.store_spendable_vtxos(&[change])?;
1967 }
1968 movement.finish(MovementStatus::Finished).await?;
1969 Ok(arkoor.created)
1970 }
1971
1972 async fn process_lightning_revocation(&self, payment: &PendingLightningSend) -> anyhow::Result<()> {
1973 let mut srv = self.require_server()?;
1974 let htlc_vtxos = payment.htlc_vtxos.clone().into_iter()
1975 .map(|v: WalletVtxo| v.vtxo).collect::<Vec<_>>();
1976
1977 info!("Processing {} HTLC VTXOs for revocation", htlc_vtxos.len());
1978
1979 let mut secs = Vec::with_capacity(htlc_vtxos.len());
1980 let mut pubs = Vec::with_capacity(htlc_vtxos.len());
1981 let mut keypairs = Vec::with_capacity(htlc_vtxos.len());
1982 for input in htlc_vtxos.iter() {
1983 let keypair = self.get_vtxo_key(&input)?;
1984 let (s, p) = musig::nonce_pair(&keypair);
1985 secs.push(s);
1986 pubs.push(p);
1987 keypairs.push(keypair);
1988 }
1989
1990 let revocation = ArkoorPackageBuilder::new_htlc_revocation(&htlc_vtxos, &pubs)?;
1991
1992 let req = protos::RevokeLightningPaymentRequest {
1993 htlc_vtxo_ids: revocation.arkoors.iter()
1994 .map(|i| i.input.id().to_bytes().to_vec())
1995 .collect(),
1996 user_nonces: revocation.arkoors.iter()
1997 .map(|i| i.user_nonce.serialize().to_vec())
1998 .collect(),
1999 };
2000 let cosign_resp: Vec<_> = srv.client.revoke_lightning_payment(req).await?
2001 .into_inner().try_into().context("invalid server cosign response")?;
2002 ensure!(revocation.verify_cosign_response(&cosign_resp),
2003 "invalid arkoor cosignature received from server",
2004 );
2005
2006 let (vtxos, _) = revocation.build_vtxos(&cosign_resp, &keypairs, secs)?;
2007 let mut revoked = Amount::ZERO;
2008 for vtxo in &vtxos {
2009 info!("Got revocation VTXO: {}: {}", vtxo.id(), vtxo.amount());
2010 revoked += vtxo.amount();
2011 }
2012
2013 let count = vtxos.len();
2014 self.movements.update_movement(
2015 payment.movement_id,
2016 MovementUpdate::new()
2017 .effective_balance(-payment.amount.to_signed()? + revoked.to_signed()?)
2018 .produced_vtxos(&vtxos)
2019 ).await?;
2020 self.store_spendable_vtxos(&vtxos)?;
2021 self.mark_vtxos_as_spent(&htlc_vtxos)?;
2022 self.movements.finish_movement(payment.movement_id, MovementStatus::Failed).await?;
2023
2024 self.db.remove_pending_lightning_send(payment.invoice.payment_hash())?;
2025
2026 info!("Revoked {} HTLC VTXOs", count);
2027
2028 Ok(())
2029 }
2030
2031 pub async fn pay_lightning_invoice<T>(
2034 &self,
2035 invoice: T,
2036 user_amount: Option<Amount>,
2037 ) -> anyhow::Result<Preimage>
2038 where
2039 T: TryInto<Invoice>,
2040 T::Error: std::error::Error + fmt::Display + Send + Sync + 'static,
2041 {
2042 let mut srv = self.require_server()?;
2043 let properties = self.db.read_properties()?.context("Missing config")?;
2044
2045 let invoice = invoice.try_into().context("failed to parse invoice")?;
2046 if invoice.network() != properties.network {
2047 bail!("Invoice is for wrong network: {}", invoice.network());
2048 }
2049
2050 if self.db.check_recipient_exists(&invoice.to_string())? {
2051 bail!("Invoice has already been paid");
2052 }
2053
2054 invoice.check_signature()?;
2055
2056 let amount = invoice.get_final_amount(user_amount)?;
2057 if amount < P2TR_DUST {
2058 bail!("Sent amount must be at least {}", P2TR_DUST);
2059 }
2060
2061 let (change_keypair, _) = self.derive_store_next_keypair()?;
2062
2063 let inputs = self.select_vtxos_to_cover(amount, Some(srv.info.max_arkoor_depth), None)
2064 .context("Could not find enough suitable VTXOs to cover lightning payment")?;
2065
2066 let mut secs = Vec::with_capacity(inputs.len());
2067 let mut pubs = Vec::with_capacity(inputs.len());
2068 let mut keypairs = Vec::with_capacity(inputs.len());
2069 let mut input_ids = Vec::with_capacity(inputs.len());
2070 for input in inputs.iter() {
2071 let keypair = self.get_vtxo_key(&input)?;
2072 let (s, p) = musig::nonce_pair(&keypair);
2073 secs.push(s);
2074 pubs.push(p);
2075 keypairs.push(keypair);
2076 input_ids.push(input.id());
2077 }
2078
2079 let req = protos::StartLightningPaymentRequest {
2080 invoice: invoice.to_string(),
2081 user_amount_sat: user_amount.map(|a| a.to_sat()),
2082 input_vtxo_ids: input_ids.iter().map(|v| v.to_bytes().to_vec()).collect(),
2083 user_nonces: pubs.iter().map(|p| p.serialize().to_vec()).collect(),
2084 user_pubkey: change_keypair.public_key().serialize().to_vec(),
2085 };
2086
2087 let resp = srv.client.start_lightning_payment(req).await
2088 .context("htlc request failed")?.into_inner();
2089
2090 let cosign_resp = resp.sigs.into_iter().map(|i| i.try_into())
2091 .collect::<Result<Vec<_>, _>>()?;
2092 let policy = VtxoPolicy::deserialize(&resp.policy)?;
2093
2094 let pay_req = match policy {
2095 VtxoPolicy::ServerHtlcSend(policy) => {
2096 ensure!(policy.user_pubkey == change_keypair.public_key(), "user pubkey mismatch");
2097 ensure!(policy.payment_hash == invoice.payment_hash(), "payment hash mismatch");
2098 VtxoRequest { amount: amount, policy: policy.into() }
2100 },
2101 _ => bail!("invalid policy returned from server"),
2102 };
2103
2104 let builder = ArkoorPackageBuilder::new(
2105 &inputs, &pubs, pay_req, Some(change_keypair.public_key()),
2106 )?;
2107
2108 ensure!(builder.verify_cosign_response(&cosign_resp),
2109 "invalid arkoor cosignature received from server",
2110 );
2111
2112 let (htlc_vtxos, change_vtxo) = builder.build_vtxos(&cosign_resp, &keypairs, secs)?;
2113
2114 let mut effective_balance = Amount::ZERO;
2116 for vtxo in &htlc_vtxos {
2117 self.validate_vtxo(vtxo).await?;
2118 effective_balance += vtxo.amount();
2119 }
2120
2121 let movement_id = self.movements.new_movement(
2122 self.subsystem_ids[&BarkSubsystem::LightningSend],
2123 LightningSendMovement::Send.to_string(),
2124 ).await?;
2125 self.movements.update_movement(
2126 movement_id,
2127 MovementUpdate::new()
2128 .intended_balance(-amount.to_signed()?)
2129 .effective_balance(-effective_balance.to_signed()?)
2130 .consumed_vtxos(&inputs)
2131 .sent_to([MovementDestination::new(invoice.to_string(), amount)])
2132 ).await?;
2133 self.store_locked_vtxos(&htlc_vtxos, Some(movement_id))?;
2134 self.mark_vtxos_as_spent(&input_ids)?;
2135
2136 if let Some(ref change) = change_vtxo {
2138 let last_input = inputs.last().context("no inputs provided")?;
2139 let tx = self.chain.get_tx(&last_input.chain_anchor().txid).await?;
2140 let tx = tx.with_context(|| {
2141 format!("input vtxo chain anchor not found for lightning change vtxo: {}", last_input.chain_anchor().txid)
2142 })?;
2143 change.validate(&tx).context("invalid lightning change vtxo")?;
2144 self.store_spendable_vtxos([change])?;
2145 }
2146
2147 self.movements.update_movement(
2148 movement_id,
2149 MovementUpdate::new()
2150 .produced_vtxo_if_some(change_vtxo)
2151 .metadata(LightningMovement::htlc_metadata(&htlc_vtxos)?)
2152 ).await?;
2153
2154 let payment = self.db.store_new_pending_lightning_send(
2155 &invoice, &amount, &htlc_vtxos.iter().map(|v| v.id()).collect::<Vec<_>>(), movement_id,
2156 )?;
2157
2158 let req = protos::SignedLightningPaymentDetails {
2159 invoice: invoice.to_string(),
2160 htlc_vtxo_ids: htlc_vtxos.iter().map(|v| v.id().to_bytes().to_vec()).collect(),
2161 wait: true,
2162 };
2163
2164 let res = srv.client.finish_lightning_payment(req).await?.into_inner();
2165 debug!("Progress update: {}", res.progress_message);
2166 let payment_preimage = Preimage::try_from(res.payment_preimage()).ok();
2167
2168 if let Some(preimage) = payment_preimage {
2169 info!("Payment succeeded! Preimage: {}", preimage.as_hex());
2170
2171 self.mark_vtxos_as_spent(&htlc_vtxos)?;
2172 self.movements.finish_movement(movement_id, MovementStatus::Finished).await?;
2173 self.db.remove_pending_lightning_send(payment.invoice.payment_hash())?;
2174 Ok(preimage)
2175 } else {
2176 self.process_lightning_revocation(&payment).await?;
2177 bail!("No preimage, payment failed: {}", res.progress_message);
2178 }
2179 }
2180
2181 pub async fn check_lightning_payment(&self, payment: &PendingLightningSend)
2208 -> anyhow::Result<Option<Preimage>>
2209 {
2210 let mut srv = self.require_server()?;
2211 let tip = self.chain.tip().await?;
2212
2213 let payment_hash = payment.invoice.payment_hash();
2214
2215 let policy = payment.htlc_vtxos.first().context("no vtxo provided")?.vtxo.policy();
2216 debug_assert!(payment.htlc_vtxos.iter().all(|v| v.vtxo.policy() == policy),
2217 "All lightning htlc should have the same policy",
2218 );
2219 let policy = policy.as_server_htlc_send().context("VTXO is not an HTLC send")?;
2220 if policy.payment_hash != payment_hash {
2221 bail!("Payment hash mismatch");
2222 }
2223
2224 let req = protos::CheckLightningPaymentRequest {
2225 hash: policy.payment_hash.to_vec(),
2226 wait: false,
2227 };
2228 let res = srv.client.check_lightning_payment(req).await?.into_inner();
2229
2230 let payment_status = protos::PaymentStatus::try_from(res.status)?;
2231
2232 let should_revoke = match payment_status {
2233 protos::PaymentStatus::Failed => {
2234 info!("Payment failed ({}): revoking VTXO", res.progress_message);
2235 true
2236 },
2237 protos::PaymentStatus::Pending => {
2238 trace!("Payment is still pending, HTLC expiry: {}, tip: {}",
2239 policy.htlc_expiry, tip);
2240 if tip > policy.htlc_expiry {
2241 info!("Payment is still pending, but HTLC is expired: revoking VTXO");
2242 true
2243 } else {
2244 info!("Payment is still pending and HTLC is not expired ({}): \
2245 doing nothing for now", policy.htlc_expiry,
2246 );
2247 false
2248 }
2249 },
2250 protos::PaymentStatus::Complete => {
2251 let preimage: Preimage = res.payment_preimage
2252 .context("payment completed but no preimage")?
2253 .try_into().map_err(|_| anyhow!("preimage is not 32 bytes"))?;
2254 info!("Payment is complete, preimage, {}", preimage.as_hex());
2255
2256 self.mark_vtxos_as_spent(&payment.htlc_vtxos)?;
2257 self.movements.finish_movement(payment.movement_id, MovementStatus::Finished).await?;
2258 self.db.remove_pending_lightning_send(payment_hash)?;
2259
2260 return Ok(Some(preimage));
2261 },
2262 };
2263
2264 if should_revoke {
2265 if let Err(e) = self.process_lightning_revocation(payment).await {
2266 warn!("Failed to revoke VTXO: {}", e);
2267
2268 let min_expiry = payment.htlc_vtxos.iter()
2272 .map(|v| v.vtxo.spec().expiry_height).min().unwrap();
2273
2274 if tip > min_expiry.saturating_sub(self.config().vtxo_refresh_expiry_threshold) {
2275 warn!("Some VTXO is about to expire soon, marking to exit");
2276 let vtxos = payment.htlc_vtxos
2277 .iter()
2278 .map(|v| v.vtxo.clone())
2279 .collect::<Vec<_>>();
2280 self.exit.write().await.mark_vtxos_for_exit(&vtxos).await?;
2281
2282 let exited = vtxos.iter().map(|v| v.amount()).sum::<Amount>();
2283 self.movements.update_movement(
2284 payment.movement_id,
2285 MovementUpdate::new()
2286 .effective_balance(-payment.amount.to_signed()? + exited.to_signed()?)
2287 .exited_vtxos(&vtxos)
2288 ).await?;
2289 self.movements.finish_movement(
2290 payment.movement_id, MovementStatus::Failed,
2291 ).await?;
2292 self.db.remove_pending_lightning_send(payment_hash)?;
2293 }
2294 }
2295 }
2296
2297 Ok(None)
2298 }
2299
2300 pub async fn bolt11_invoice(&self, amount: Amount) -> anyhow::Result<Bolt11Invoice> {
2302 let mut srv = self.require_server()?;
2303 let config = self.config();
2304
2305 let requested_min_cltv_delta = srv.info.vtxo_exit_delta +
2310 srv.info.htlc_expiry_delta +
2311 config.vtxo_exit_margin +
2312 config.htlc_recv_claim_delta +
2313 LIGHTNING_PREPARE_CLAIM_DELTA;
2314
2315 if requested_min_cltv_delta > srv.info.max_user_invoice_cltv_delta {
2316 bail!("HTLC CLTV delta ({}) is greater than Server's max HTLC recv CLTV delta: {}",
2317 requested_min_cltv_delta,
2318 srv.info.max_user_invoice_cltv_delta,
2319 );
2320 }
2321
2322 let preimage = Preimage::random();
2323 let payment_hash = preimage.compute_payment_hash();
2324 info!("Start bolt11 board with preimage / payment hash: {} / {}",
2325 preimage.as_hex(), payment_hash.as_hex());
2326
2327 let req = protos::StartLightningReceiveRequest {
2328 payment_hash: payment_hash.to_vec(),
2329 amount_sat: amount.to_sat(),
2330 min_cltv_delta: requested_min_cltv_delta as u32,
2331 };
2332
2333 let resp = srv.client.start_lightning_receive(req).await?.into_inner();
2334 info!("Ark Server is ready to receive LN payment to invoice: {}.", resp.bolt11);
2335
2336 let invoice = Bolt11Invoice::from_str(&resp.bolt11)
2337 .context("invalid bolt11 invoice returned by Ark server")?;
2338
2339 self.db.store_lightning_receive(
2340 payment_hash,
2341 preimage,
2342 &invoice,
2343 requested_min_cltv_delta,
2344 )?;
2345
2346 Ok(invoice)
2347 }
2348
2349 pub fn lightning_receive_status(
2351 &self,
2352 payment: impl Into<PaymentHash>,
2353 ) -> anyhow::Result<Option<LightningReceive>> {
2354 Ok(self.db.fetch_lightning_receive_by_payment_hash(payment.into())?)
2355 }
2356
2357 pub fn pending_lightning_receives(&self) -> anyhow::Result<Vec<LightningReceive>> {
2359 Ok(self.db.get_all_pending_lightning_receives()?)
2360 }
2361
2362 pub fn pending_lightning_receive_balance(&self) -> anyhow::Result<LightningReceiveBalance> {
2363 let pending_lightning_receives = self.pending_lightning_receives()?;
2364
2365 let mut total_pending_lightning_receive = Amount::ZERO;
2366 let mut claimable_pending_lightning_receive = Amount::ZERO;
2367 for receive in pending_lightning_receives {
2368 total_pending_lightning_receive += receive.invoice.amount_milli_satoshis()
2369 .map(|a| Amount::from_msat_floor(a))
2370 .expect("ln receive invoice should have amount");
2371 if let Some(htlc_vtxos) = receive.htlc_vtxos {
2372 claimable_pending_lightning_receive += htlc_vtxos.iter().map(|v| v.amount()).sum::<Amount>();
2373 }
2374 }
2375
2376 Ok(LightningReceiveBalance {
2377 total: total_pending_lightning_receive,
2378 claimable: claimable_pending_lightning_receive,
2379 })
2380 }
2381
2382 async fn claim_lightning_receive(
2405 &self,
2406 receive: &LightningReceive,
2407 ) -> anyhow::Result<()> {
2408 let movement_id = receive.movement_id
2409 .context("No movement created for lightning receive")?;
2410 let mut srv = self.require_server()?;
2411
2412 let inputs = {
2414 let htlc_vtxos = receive.htlc_vtxos.as_ref()
2415 .context("no HTLC VTXOs set on record yet")?;
2416 let mut ret = htlc_vtxos.iter().map(|v| &v.vtxo).collect::<Vec<_>>();
2417 ret.sort_by_key(|v| v.id());
2418 ret
2419 };
2420
2421 let (keypairs, sec_nonces, pub_nonces) = inputs.iter().map(|v| {
2422 let keypair = self.get_vtxo_key(v)?;
2423 let (sec_nonce, pub_nonce) = musig::nonce_pair(&keypair);
2424 Ok((keypair, sec_nonce, pub_nonce))
2425 }).collect::<anyhow::Result<(Vec<_>, Vec<_>, Vec<_>)>>()?;
2426
2427 let (claim_keypair, _) = self.derive_store_next_keypair()?;
2429 let receive_policy = VtxoPolicy::new_pubkey(claim_keypair.public_key());
2430
2431 let pay_req = VtxoRequest {
2432 policy: receive_policy.clone(),
2433 amount: inputs.iter().map(|v| v.amount()).sum(),
2434 };
2435 trace!("ln arkoor builder params: inputs: {:?}; user_nonces: {:?}; req: {:?}",
2436 inputs.iter().map(|v| v.id()).collect::<Vec<_>>(), pub_nonces, pay_req,
2437 );
2438 let builder = ArkoorPackageBuilder::new(
2439 inputs.iter().copied(), &pub_nonces, pay_req, None,
2440 )?;
2441
2442 info!("Claiming arkoor against payment preimage");
2443 self.db.set_preimage_revealed(receive.payment_hash)?;
2444 let resp = srv.client.claim_lightning_receive(protos::ClaimLightningReceiveRequest {
2445 payment_hash: receive.payment_hash.to_byte_array().to_vec(),
2446 payment_preimage: receive.payment_preimage.to_vec(),
2447 vtxo_policy: receive_policy.serialize(),
2448 user_pub_nonces: pub_nonces.iter().map(|n| n.serialize().to_vec()).collect(),
2449 }).await?.into_inner();
2450 let cosign_resp: Vec<_> = resp.try_into().context("invalid cosign response")?;
2451
2452 ensure!(builder.verify_cosign_response(&cosign_resp),
2453 "invalid arkoor cosignature received from server",
2454 );
2455
2456 let (outputs, change) = builder.build_vtxos(&cosign_resp, &keypairs, sec_nonces)?;
2457 if change.is_some() {
2458 bail!("shouldn't have change VTXO, this is a bug");
2459 }
2460
2461 let mut effective_balance = Amount::ZERO;
2462 for vtxo in &outputs {
2463 if let Err(e) = self.validate_vtxo(vtxo).await {
2467 bail!("invalid arkoor from lightning receive: {e}");
2468 }
2469 effective_balance += vtxo.amount();
2470 }
2471
2472 self.store_spendable_vtxos(&outputs)?;
2473 self.mark_vtxos_as_spent(inputs)?;
2474 info!("Got arkoors from lightning: {}",
2475 outputs.iter().map(|v| v.id().to_string()).collect::<Vec<_>>().join(", ")
2476 );
2477
2478 self.movements.update_movement(
2479 movement_id,
2480 MovementUpdate::new()
2481 .effective_balance(effective_balance.to_signed()?)
2482 .produced_vtxos(&outputs)
2483 ).await?;
2484 self.movements.finish_movement(
2485 movement_id, MovementStatus::Finished,
2486 ).await?;
2487
2488 self.db.remove_pending_lightning_receive(receive.payment_hash)?;
2489
2490 Ok(())
2491 }
2492
2493 async fn compute_lightning_receive_anti_dos(
2494 &self,
2495 payment_hash: PaymentHash,
2496 token: Option<&str>,
2497 ) -> anyhow::Result<LightningReceiveAntiDos> {
2498 Ok(if let Some(token) = token {
2499 LightningReceiveAntiDos::Token(token.to_string())
2500 } else {
2501 let challenge = LightningReceiveChallenge::new(payment_hash);
2502 let vtxo = self.select_vtxos_to_cover(Amount::ONE_SAT, None, None)
2504 .and_then(|vtxos| vtxos.into_iter().next().ok_or_else(|| anyhow!("have no spendable vtxo to prove ownership of")))?;
2505 let vtxo_keypair = self.get_vtxo_key(&vtxo).expect("owned vtxo should be in database");
2506 LightningReceiveAntiDos::InputVtxo(protos::InputVtxo {
2507 vtxo_id: vtxo.id().to_bytes().to_vec(),
2508 ownership_proof: {
2509 let sig = challenge.sign_with(vtxo.id(), vtxo_keypair);
2510 sig.serialize().to_vec()
2511 }
2512 })
2513 })
2514 }
2515
2516 async fn check_lightning_receive(
2545 &self,
2546 payment_hash: PaymentHash,
2547 wait: bool,
2548 token: Option<&str>,
2549 ) -> anyhow::Result<LightningReceive> {
2550 let mut srv = self.require_server()?;
2551 let current_height = self.chain.tip().await?;
2552
2553 let mut receive = self.db.fetch_lightning_receive_by_payment_hash(payment_hash)?
2554 .context("no pending lightning receive found for payment hash, might already be claimed")?;
2555
2556 if receive.htlc_vtxos.is_some() {
2558 return Ok(receive)
2559 }
2560
2561 info!("Waiting for payment...");
2562 let sub = srv.client.check_lightning_receive(protos::CheckLightningReceiveRequest {
2563 hash: payment_hash.to_byte_array().to_vec(), wait,
2564 }).await?.into_inner();
2565
2566 let status = protos::LightningReceiveStatus::try_from(sub.status)
2567 .with_context(|| format!("unknown payment status: {}", sub.status))?;
2568 match status {
2569 protos::LightningReceiveStatus::Accepted
2571 | protos::LightningReceiveStatus::HtlcsReady => {},
2572 protos::LightningReceiveStatus::Created => {
2573 return Ok(receive);
2574 },
2575 protos::LightningReceiveStatus::Settled => bail!("payment already settled"),
2576 protos::LightningReceiveStatus::Cancelled => bail!("payment was canceled"),
2577 }
2578
2579 let lightning_receive_anti_dos = match self.compute_lightning_receive_anti_dos(
2580 payment_hash, token,
2581 ).await {
2582 Ok(anti_dos) => Some(anti_dos),
2583 Err(e) => {
2584 warn!("Could not compute anti-dos: {e}. Trying without");
2585 None
2586 },
2587 };
2588
2589 let htlc_recv_expiry = current_height + receive.htlc_recv_cltv_delta as BlockHeight;
2590
2591 let (next_keypair, _) = self.derive_store_next_keypair()?;
2592 let req = protos::PrepareLightningReceiveClaimRequest {
2593 payment_hash: receive.payment_hash.to_vec(),
2594 user_pubkey: next_keypair.public_key().serialize().to_vec(),
2595 htlc_recv_expiry,
2596 lightning_receive_anti_dos,
2597 };
2598 let res = srv.client.prepare_lightning_receive_claim(req).await
2599 .context("error preparing lightning receive claim")?.into_inner();
2600 let vtxos = res.htlc_vtxos.into_iter()
2601 .map(|b| Vtxo::deserialize(&b))
2602 .collect::<Result<Vec<_>, _>>()
2603 .context("invalid htlc vtxos from server")?;
2604
2605 for vtxo in &vtxos {
2607 self.validate_vtxo(vtxo).await?;
2608
2609 if let VtxoPolicy::ServerHtlcRecv(p) = vtxo.policy() {
2610 if p.payment_hash != receive.payment_hash {
2611 bail!("invalid payment hash on HTLC VTXOs received from server: {}",
2612 p.payment_hash,
2613 );
2614 }
2615 if p.user_pubkey != next_keypair.public_key() {
2616 bail!("invalid pubkey on HTLC VTXOs received from server: {}", p.user_pubkey);
2617 }
2618 if p.htlc_expiry < htlc_recv_expiry {
2619 bail!("HTLC VTXO expiry height is less than requested: Requested {}, received {}", htlc_recv_expiry, p.htlc_expiry);
2620 }
2621 } else {
2622 bail!("invalid HTLC VTXO policy: {:?}", vtxo.policy());
2623 }
2624 }
2625
2626 let invoice_amount = receive.invoice.amount_milli_satoshis().map(|a| Amount::from_msat_floor(a))
2628 .expect("ln receive invoice should have amount");
2629 let htlc_amount = vtxos.iter().map(|v| v.amount()).sum::<Amount>();
2630 ensure!(vtxos.iter().map(|v| v.amount()).sum::<Amount>() >= invoice_amount,
2631 "Server didn't return enough VTXOs to cover invoice amount"
2632 );
2633
2634 let movement_id = if let Some(movement_id) = receive.movement_id {
2635 movement_id
2636 } else {
2637 self.movements.new_movement(
2638 self.subsystem_ids[&BarkSubsystem::LightningReceive],
2639 LightningReceiveMovement::Receive.to_string(),
2640 ).await?
2641 };
2642 self.store_locked_vtxos(&vtxos, Some(movement_id))?;
2643
2644 let vtxo_ids = vtxos.iter().map(|v| v.id()).collect::<Vec<_>>();
2645 self.db.update_lightning_receive(payment_hash, &vtxo_ids, movement_id)?;
2646
2647 self.movements.update_movement(
2648 movement_id,
2649 MovementUpdate::new()
2650 .intended_balance(invoice_amount.to_signed()?)
2651 .effective_balance(htlc_amount.to_signed()?)
2652 .metadata(LightningMovement::htlc_metadata(&vtxos)?)
2653 .received_on(
2654 [MovementDestination::new(receive.invoice.to_string(), htlc_amount)],
2655 ),
2656 ).await?;
2657
2658 let vtxos = vtxos
2659 .into_iter()
2660 .map(|v| self.db
2661 .get_wallet_vtxo(v.id())
2662 .and_then(|op| op.context("Failed to get wallet VTXO for lightning receive"))
2663 ).collect::<Result<Vec<_>, _>>()?;
2664
2665 receive.htlc_vtxos = Some(vtxos);
2666 receive.movement_id = Some(movement_id);
2667
2668 Ok(receive)
2669 }
2670
2671 pub async fn try_claim_lightning_receive(
2695 &self,
2696 payment_hash: PaymentHash,
2697 wait: bool,
2698 token: Option<&str>,
2699 ) -> anyhow::Result<bool> {
2700 let srv = self.require_server()?;
2701
2702 let receive = self.check_lightning_receive(payment_hash, wait, token).await?;
2703 let vtxos = match receive.htlc_vtxos {
2704 None => return Ok(false),
2706 Some(ref vtxos) => vtxos,
2707 };
2708
2709 if let Err(e) = self.claim_lightning_receive(&receive).await {
2710 error!("Failed to claim pubkey vtxo from htlc vtxo: {}", e);
2711 let tip = self.chain.tip().await?;
2712
2713 let first_vtxo = &vtxos.first().unwrap().vtxo;
2714 debug_assert!(vtxos.iter().all(|v| {
2715 v.vtxo.policy() == first_vtxo.policy() && v.vtxo.exit_delta() == first_vtxo.exit_delta()
2716 }), "all htlc vtxos for the same payment hash should have the same policy and exit delta");
2717
2718 let vtxo_htlc_expiry = first_vtxo.policy().as_server_htlc_recv()
2719 .expect("only server htlc recv vtxos can be pending lightning recv").htlc_expiry;
2720
2721 let safe_exit_margin = first_vtxo.exit_delta() +
2722 srv.info.htlc_expiry_delta +
2723 self.config.vtxo_exit_margin;
2724
2725 if tip > vtxo_htlc_expiry.saturating_sub(safe_exit_margin as BlockHeight) {
2726 if receive.preimage_revealed_at.is_some() {
2727 warn!("HTLC-recv VTXOs are about to expire and preimage has been disclosed, \
2728 must exit",);
2729 self.exit.write().await.mark_vtxos_for_exit(
2730 &vtxos.iter().map(|v| v.vtxo.clone()).collect::<Vec<_>>(),
2731 ).await?;
2732 if let Some(id) = receive.movement_id {
2733 self.movements.update_movement(
2734 id,
2735 MovementUpdate::new()
2736 .exited_vtxos(vtxos)
2737 ).await?;
2738 self.movements.finish_movement(id, MovementStatus::Failed).await?;
2739 }
2740 } else {
2741 warn!("HTLC-recv VTXOs are about to expire, but preimage has not been disclosed yet, mark htlc as cancelled");
2742 self.mark_vtxos_as_spent(vtxos)?;
2743 if let Some(id) = receive.movement_id {
2744 self.movements.update_movement(
2745 id,
2746 MovementUpdate::new()
2747 .effective_balance(SignedAmount::ZERO)
2748 ).await?;
2749 self.movements.finish_movement(id, MovementStatus::Cancelled).await?;
2750 }
2751 }
2752 }
2753
2754 Err(e)
2755 } else {
2756 Ok(true)
2757 }
2758 }
2759
2760 pub async fn try_claim_all_lightning_receives(&self, wait: bool) -> anyhow::Result<()> {
2775 tokio_stream::iter(self.pending_lightning_receives()?)
2777 .for_each_concurrent(3, |rcv| async move {
2778 if let Err(e) = self.try_claim_lightning_receive(rcv.invoice.into(), wait, None).await {
2779 error!("Error claiming lightning receive: {:#}", e);
2780 }
2781 }).await;
2782
2783 Ok(())
2784 }
2785
2786 pub async fn pay_lightning_address(
2788 &self,
2789 addr: &LightningAddress,
2790 amount: Amount,
2791 comment: Option<&str>,
2792 ) -> anyhow::Result<(Bolt11Invoice, Preimage)> {
2793 let invoice = lnurl::lnaddr_invoice(addr, amount, comment).await
2794 .context("lightning address error")?;
2795 info!("Attempting to pay invoice {}", invoice);
2796 let preimage = self.pay_lightning_invoice(invoice.clone(), None).await
2797 .context("bolt11 payment error")?;
2798 Ok((invoice, preimage))
2799 }
2800
2801 pub async fn pay_lightning_offer(
2803 &self,
2804 offer: Offer,
2805 amount: Option<Amount>,
2806 ) -> anyhow::Result<(Bolt12Invoice, Preimage)> {
2807 let mut srv = self.require_server()?;
2808
2809 let offer_bytes = {
2810 let mut bytes = Vec::new();
2811 offer.write(&mut bytes).unwrap();
2812 bytes
2813 };
2814
2815 let req = protos::FetchBolt12InvoiceRequest {
2816 offer: offer_bytes,
2817 amount_sat: amount.map(|a| a.to_sat()),
2818 };
2819
2820 let resp = srv.client.fetch_bolt12_invoice(req).await?.into_inner();
2821
2822 let invoice = Bolt12Invoice::try_from(resp.invoice)
2823 .map_err(|_| anyhow::anyhow!("invalid invoice"))?;
2824
2825 invoice.validate_issuance(offer)?;
2826
2827 let preimage = self.pay_lightning_invoice(invoice.clone(), None).await
2828 .context("bolt11 payment error")?;
2829 Ok((invoice, preimage))
2830 }
2831
2832 pub fn build_round_onchain_payment_participation(
2833 &self,
2834 addr: bitcoin::Address,
2835 amount: Amount,
2836 ) -> anyhow::Result<RoundParticipation> {
2837 let srv = self.require_server()?;
2838
2839 let offb = OffboardRequest {
2840 script_pubkey: addr.script_pubkey(),
2841 amount: amount,
2842 };
2843 let required_amount = offb.amount + offb.fee(srv.info.offboard_feerate)?;
2844
2845 let inputs = self.select_vtxos_to_cover(required_amount, None, None)?;
2846
2847 let change = {
2848 let input_sum = inputs.iter().map(|v| v.amount()).sum::<Amount>();
2849 if input_sum < offb.amount {
2850 bail!("Your balance is too low. Needed: {}, available: {}",
2851 required_amount, self.balance()?.spendable,
2852 );
2853 } else if input_sum <= required_amount + P2TR_DUST {
2854 info!("No change, emptying wallet.");
2855 None
2856 } else {
2857 let change_amount = input_sum - required_amount;
2858 let (change_keypair, _) = self.derive_store_next_keypair()?;
2859 info!("Adding change vtxo for {}", change_amount);
2860 Some(VtxoRequest {
2861 amount: change_amount,
2862 policy: VtxoPolicy::new_pubkey(change_keypair.public_key()),
2863 })
2864 }
2865 };
2866
2867 Ok(RoundParticipation {
2868 inputs: inputs,
2869 outputs: change.into_iter().collect(),
2870 offboards: vec![offb],
2871 })
2872 }
2873
2874 pub async fn send_round_onchain_payment(
2877 &self,
2878 addr: bitcoin::Address,
2879 amount: Amount,
2880 ) -> anyhow::Result<RoundStatus> {
2881 let mut participation = self.build_round_onchain_payment_participation(addr, amount)?;
2882
2883 if let Err(e) = self.add_should_refresh_vtxos(&mut participation).await {
2884 warn!("Error trying to add additional VTXOs that should be refreshed: {:#}", e);
2885 }
2886
2887 self.participate_round(participation, Some(RoundMovement::SendOnchain)).await
2888 }
2889
2890 pub async fn run_daemon(
2896 self: &Arc<Self>,
2897 shutdown: CancellationToken,
2898 onchain: Arc<RwLock<dyn ExitUnilaterally>>,
2899 ) -> anyhow::Result<()> {
2900 let daemon = Daemon::new(shutdown, self.clone(), onchain)?;
2901
2902 tokio::spawn(async move {
2903 let _ = daemon.run().await;
2904 });
2905
2906 Ok(())
2907 }
2908}
2909
2910#[cfg(test)]
2911mod test {
2912 use super::*;
2913
2914 #[allow(unused)] async fn pay_lightning_invoice_argument() {
2916 let db = Arc::new(SqliteClient::open("").unwrap());
2919 let w = Wallet::open(
2920 &"".parse().unwrap(), db, Config::network_default(Network::Regtest),
2921 ).await.unwrap();
2922
2923 let bolt11 = Bolt11Invoice::from_str("").unwrap();
2924 w.pay_lightning_invoice(bolt11, None).await.unwrap();
2925
2926 let bolt12 = Bolt12Invoice::from_str("").unwrap();
2927 w.pay_lightning_invoice(bolt12, None).await.unwrap();
2928
2929 let string = format!("lnbc1..");
2930 w.pay_lightning_invoice(string, None).await.unwrap();
2931
2932 let strr = "lnbc1..";
2933 w.pay_lightning_invoice(strr, None).await.unwrap();
2934
2935 let invoice = Invoice::Bolt11("".parse().unwrap());
2936 w.pay_lightning_invoice(invoice, None).await.unwrap();
2937 }
2938}