diff --git a/coordinator/src/db/transactions.rs b/coordinator/src/db/transactions.rs index ee0dfb7ca..d5842676b 100644 --- a/coordinator/src/db/transactions.rs +++ b/coordinator/src/db/transactions.rs @@ -54,21 +54,21 @@ pub(crate) fn upsert(tx: Transaction, conn: &mut PgConnection) -> Result<()> { impl From for Transaction { fn from(value: ln_dlc_node::transaction::Transaction) -> Self { Transaction { - txid: value.txid.to_string(), - fee: value.fee as i64, - created_at: value.created_at, - updated_at: value.updated_at, + txid: value.txid().to_string(), + fee: value.fee() as i64, + created_at: value.created_at(), + updated_at: value.updated_at(), } } } impl From for ln_dlc_node::transaction::Transaction { fn from(value: Transaction) -> Self { - ln_dlc_node::transaction::Transaction { - txid: Txid::from_str(&value.txid).expect("valid txid"), - fee: value.fee as u64, - created_at: value.created_at, - updated_at: value.updated_at, - } + ln_dlc_node::transaction::Transaction::new( + Txid::from_str(&value.txid).expect("valid txid"), + value.fee as u64, + value.created_at, + value.updated_at, + ) } } diff --git a/crates/ln-dlc-node/src/ldk_node_wallet.rs b/crates/ln-dlc-node/src/ldk_node_wallet.rs index f28b513f5..280bcc5ae 100644 --- a/crates/ln-dlc-node/src/ldk_node_wallet.rs +++ b/crates/ln-dlc-node/src/ldk_node_wallet.rs @@ -1,4 +1,5 @@ use crate::fee_rate_estimator::EstimateFeeRate; +use crate::node::Storage; use anyhow::bail; use anyhow::Context; use anyhow::Error; @@ -40,6 +41,7 @@ where settings: RwLock, fee_rate_estimator: Arc, locked_outpoints: Mutex>, + node_storage: Arc, } #[derive(Clone, Debug, Default)] @@ -53,7 +55,12 @@ where B: Blockchain, F: EstimateFeeRate, { - pub(crate) fn new(blockchain: B, wallet: bdk::Wallet, fee_rate_estimator: Arc) -> Self { + pub(crate) fn new( + blockchain: B, + wallet: bdk::Wallet, + fee_rate_estimator: Arc, + node_storage: Arc, + ) -> Self { let inner = Mutex::new(wallet); let settings = RwLock::new(WalletSettings::default()); @@ -63,6 +70,7 @@ where settings, fee_rate_estimator, locked_outpoints: Mutex::new(vec![]), + node_storage, } } @@ -226,9 +234,7 @@ where psbt.extract_tx() }; - self.broadcast_transaction(&tx); - - let txid = tx.txid(); + let txid = self.broadcast_transaction(&tx)?; if let Some(amount_sats) = amount_msat_or_drain { tracing::info!( @@ -270,6 +276,23 @@ where let transaction_details = wallet_lock.get_tx(txid, false)?; Ok(transaction_details) } + + #[autometrics] + pub fn broadcast_transaction(&self, tx: &Transaction) -> Result { + let txid = tx.txid(); + + tracing::info!(%txid, raw_tx = %serialize_hex(&tx), "Broadcasting transaction"); + + self.blockchain + .broadcast(tx) + .with_context(|| format!("Failed to broadcast transaction {txid}"))?; + + self.node_storage + .upsert_transaction(tx.into()) + .with_context(|| format!("Failed to store transaction {txid}"))?; + + Ok(txid) + } } impl BroadcasterInterface for Wallet @@ -279,18 +302,18 @@ where F: EstimateFeeRate, { fn broadcast_transaction(&self, tx: &Transaction) { - let txid = tx.txid(); - - tracing::info!(%txid, raw_tx = %serialize_hex(&tx), "Broadcasting transaction"); - - if let Err(err) = self.blockchain.broadcast(tx) { - tracing::error!("Failed to broadcast transaction: {err:#}"); + if let Err(e) = self.broadcast_transaction(tx) { + tracing::error!( + txid = %tx.txid(), + "Error when broadcasting transaction: {e:#}" + ); } } } #[cfg(test)] pub mod tests { + use super::*; use crate::fee_rate_estimator::EstimateFeeRate; use crate::ldk_node_wallet::Wallet; use anyhow::Result; @@ -326,7 +349,12 @@ pub mod tests { async fn wallet_with_two_utxo_should_be_able_to_fund_twice_but_not_three_times() { let mut rng = thread_rng(); let test_wallet = new_test_wallet(&mut rng, Amount::from_btc(1.0).unwrap(), 2).unwrap(); - let wallet = Wallet::new(DummyEsplora, test_wallet, Arc::new(DummyFeeRateEstimator)); + let wallet = Wallet::new( + DummyEsplora, + test_wallet, + Arc::new(DummyFeeRateEstimator), + Arc::new(DummyNodeStorage), + ); let _ = wallet .create_funding_transaction( @@ -394,7 +422,6 @@ pub mod tests { Ok(wallet) } - struct DummyEsplora; struct DummyFeeRateEstimator; impl EstimateFeeRate for DummyFeeRateEstimator { @@ -403,6 +430,8 @@ pub mod tests { } } + struct DummyEsplora; + impl WalletSync for DummyEsplora { fn wallet_setup( &self, @@ -444,4 +473,97 @@ pub mod tests { unimplemented!() } } + + struct DummyNodeStorage; + + impl Storage for DummyNodeStorage { + fn insert_payment( + &self, + _payment_hash: lightning::ln::PaymentHash, + _info: crate::PaymentInfo, + ) -> Result<()> { + unimplemented!(); + } + + fn merge_payment( + &self, + _payment_hash: &lightning::ln::PaymentHash, + _flow: crate::PaymentFlow, + _amt_msat: crate::MillisatAmount, + _htlc_status: crate::HTLCStatus, + _preimage: Option, + _secret: Option, + ) -> Result<()> { + unimplemented!(); + } + + fn get_payment( + &self, + _payment_hash: &lightning::ln::PaymentHash, + ) -> Result> { + unimplemented!(); + } + + fn all_payments(&self) -> Result> { + unimplemented!(); + } + + fn insert_spendable_output( + &self, + _descriptor: lightning::chain::keysinterface::SpendableOutputDescriptor, + ) -> Result<()> { + unimplemented!(); + } + + fn get_spendable_output( + &self, + _outpoint: &lightning::chain::transaction::OutPoint, + ) -> Result> { + unimplemented!(); + } + + fn delete_spendable_output( + &self, + _outpoint: &lightning::chain::transaction::OutPoint, + ) -> Result<()> { + unimplemented!(); + } + + fn all_spendable_outputs( + &self, + ) -> Result> { + unimplemented!(); + } + + fn upsert_channel(&self, _channel: crate::channel::Channel) -> Result<()> { + unimplemented!(); + } + + fn get_channel(&self, _user_channel_id: &str) -> Result> { + unimplemented!(); + } + + fn get_channel_by_fake_scid( + &self, + _fake_scid: crate::channel::FakeScid, + ) -> Result> { + unimplemented!(); + } + + fn all_non_pending_channels(&self) -> Result> { + unimplemented!(); + } + + fn upsert_transaction(&self, _transaction: crate::transaction::Transaction) -> Result<()> { + unimplemented!(); + } + + fn get_transaction(&self, _txid: &str) -> Result> { + unimplemented!(); + } + + fn all_transactions_without_fees(&self) -> Result> { + unimplemented!(); + } + } } diff --git a/crates/ln-dlc-node/src/ln_dlc_wallet.rs b/crates/ln-dlc-node/src/ln_dlc_wallet.rs index 66e343dbd..b82270566 100644 --- a/crates/ln-dlc-node/src/ln_dlc_wallet.rs +++ b/crates/ln-dlc-node/src/ln_dlc_wallet.rs @@ -33,14 +33,12 @@ use parking_lot::RwLock; use rust_bitcoin_coin_selection::select_coins; use simple_wallet::WalletStorage; use std::sync::Arc; -use time::OffsetDateTime; /// This is a wrapper type introduced to be able to implement traits from `rust-dlc` on the /// `ldk_node::LightningWallet`. pub struct LnDlcWallet { ln_wallet: Arc>, storage: Arc, - node_storage: Arc, secp: Secp256k1, seed: Bip39Seed, network: Network, @@ -74,6 +72,7 @@ impl LnDlcWallet { blockchain, on_chain_wallet, fee_rate_estimator, + node_storage, )); let last_unused_address = wallet @@ -87,7 +86,6 @@ impl LnDlcWallet { seed, network, address_cache: RwLock::new(last_unused_address), - node_storage, } } @@ -142,7 +140,9 @@ impl LnDlcWallet { impl Blockchain for LnDlcWallet { fn send_transaction(&self, transaction: &Transaction) -> Result<(), Error> { - self.ln_wallet.broadcast_transaction(transaction); + self.ln_wallet + .broadcast_transaction(transaction) + .map_err(|e| Error::WalletError(e.into()))?; Ok(()) } @@ -309,16 +309,11 @@ impl dlc_manager::Wallet for LnDlcWallet { impl BroadcasterInterface for LnDlcWallet { #[autometrics] fn broadcast_transaction(&self, tx: &Transaction) { - self.ln_wallet.broadcast_transaction(tx); - let transaction = crate::transaction::Transaction { - txid: tx.txid(), - fee: 0, - created_at: OffsetDateTime::now_utc(), - updated_at: OffsetDateTime::now_utc(), - }; - - if let Err(e) = self.node_storage.upsert_transaction(transaction) { - tracing::error!("Failed to create shadow transaction. Error: {e:#}"); + if let Err(e) = self.ln_wallet.broadcast_transaction(tx) { + tracing::error!( + txid = %tx.txid(), + "Error when broadcasting transaction: {e:#}" + ); } } } diff --git a/crates/ln-dlc-node/src/node/storage.rs b/crates/ln-dlc-node/src/node/storage.rs index 2fe57edbb..d28c26c23 100644 --- a/crates/ln-dlc-node/src/node/storage.rs +++ b/crates/ln-dlc-node/src/node/storage.rs @@ -243,7 +243,7 @@ impl Storage for InMemoryStore { // Transaction fn upsert_transaction(&self, transaction: Transaction) -> Result<()> { - let txid = transaction.txid.to_string(); + let txid = transaction.txid().to_string(); self.transactions_lock().insert(txid, transaction); Ok(()) } @@ -257,7 +257,7 @@ impl Storage for InMemoryStore { Ok(self .transactions_lock() .values() - .filter(|t| t.fee == 0) + .filter(|t| t.fee() == 0) .cloned() .collect()) } diff --git a/crates/ln-dlc-node/src/shadow.rs b/crates/ln-dlc-node/src/shadow.rs index 05d73261d..628197c8a 100644 --- a/crates/ln-dlc-node/src/shadow.rs +++ b/crates/ln-dlc-node/src/shadow.rs @@ -4,9 +4,9 @@ use crate::ln_dlc_wallet::LnDlcWallet; use crate::node::ChannelManager; use crate::node::Storage; use anyhow::Result; +use bdk::TransactionDetails; use dlc_manager::subchannel::LNChannelManager; use std::sync::Arc; -use time::OffsetDateTime; pub struct Shadow { storage: Arc, @@ -51,18 +51,17 @@ where let transactions = self.storage.all_transactions_without_fees()?; tracing::debug!("Syncing {} shadow transactions", transactions.len()); - for mut transaction in transactions.into_iter() { - let transaction_details = self - .ln_dlc_wallet - .inner() - .get_transaction(&transaction.txid)?; - - transaction.fee = transaction_details - .map(|d| d.fee.unwrap_or_default()) - .unwrap_or_default(); - transaction.updated_at = OffsetDateTime::now_utc(); - - self.storage.upsert_transaction(transaction)?; + for transaction in transactions.iter() { + let txid = transaction.txid(); + match self.ln_dlc_wallet.inner().get_transaction(&txid) { + Ok(Some(TransactionDetails { fee: Some(fee), .. })) => { + self.storage.upsert_transaction(transaction.with_fee(fee))?; + } + Ok(_) => {} + Err(e) => { + tracing::warn!(%txid, "Failed to get transaction details: {e:#}"); + } + }; } Ok(()) } diff --git a/crates/ln-dlc-node/src/tests/just_in_time_channel/create.rs b/crates/ln-dlc-node/src/tests/just_in_time_channel/create.rs index eb679a9a1..e28f1adae 100644 --- a/crates/ln-dlc-node/src/tests/just_in_time_channel/create.rs +++ b/crates/ln-dlc-node/src/tests/just_in_time_channel/create.rs @@ -95,7 +95,7 @@ async fn open_jit_channel() { .get_transaction(&channel.funding_txid.unwrap().to_string()) .unwrap() .unwrap(); - assert!(transaction.fee > 0); + assert!(transaction.fee() > 0); } #[tokio::test(flavor = "multi_thread")] diff --git a/crates/ln-dlc-node/src/transaction.rs b/crates/ln-dlc-node/src/transaction.rs index fc5da03b4..245011cc6 100644 --- a/crates/ln-dlc-node/src/transaction.rs +++ b/crates/ln-dlc-node/src/transaction.rs @@ -4,12 +4,60 @@ use std::fmt::Display; use std::fmt::Formatter; use time::OffsetDateTime; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Transaction { - pub txid: Txid, - pub fee: u64, - pub created_at: OffsetDateTime, - pub updated_at: OffsetDateTime, + txid: Txid, + fee: u64, + created_at: OffsetDateTime, + updated_at: OffsetDateTime, +} + +impl Transaction { + pub fn new( + txid: Txid, + fee: u64, + created_at: OffsetDateTime, + updated_at: OffsetDateTime, + ) -> Self { + Self { + txid, + fee, + created_at, + updated_at, + } + } + + pub fn txid(&self) -> Txid { + self.txid + } + + pub fn fee(&self) -> u64 { + self.fee + } + + pub fn with_fee(self, fee: u64) -> Self { + Self { + fee, + updated_at: OffsetDateTime::now_utc(), + ..self + } + } + + pub fn created_at(&self) -> OffsetDateTime { + self.created_at + } + + pub fn updated_at(&self) -> OffsetDateTime { + self.updated_at + } +} + +impl From<&bitcoin::Transaction> for Transaction { + fn from(value: &bitcoin::Transaction) -> Self { + let now = OffsetDateTime::now_utc(); + + Self::new(value.txid(), 0, now, now) + } } impl Display for Transaction { diff --git a/mobile/native/src/db/models.rs b/mobile/native/src/db/models.rs index 99a463f36..22da208bf 100644 --- a/mobile/native/src/db/models.rs +++ b/mobile/native/src/db/models.rs @@ -1103,24 +1103,22 @@ impl Transaction { impl From for Transaction { fn from(value: ln_dlc_node::transaction::Transaction) -> Self { Transaction { - txid: value.txid.to_string(), - fee: value.fee as i64, - created_at: value.created_at.unix_timestamp(), - updated_at: value.updated_at.unix_timestamp(), + txid: value.txid().to_string(), + fee: value.fee() as i64, + created_at: value.created_at().unix_timestamp(), + updated_at: value.updated_at().unix_timestamp(), } } } impl From for ln_dlc_node::transaction::Transaction { fn from(value: Transaction) -> Self { - ln_dlc_node::transaction::Transaction { - txid: Txid::from_str(&value.txid).expect("valid txid"), - fee: value.fee as u64, - created_at: OffsetDateTime::from_unix_timestamp(value.created_at) - .expect("valid timestamp"), - updated_at: OffsetDateTime::from_unix_timestamp(value.updated_at) - .expect("valid timestamp"), - } + ln_dlc_node::transaction::Transaction::new( + Txid::from_str(&value.txid).expect("valid txid"), + value.fee as u64, + OffsetDateTime::from_unix_timestamp(value.created_at).expect("valid timestamp"), + OffsetDateTime::from_unix_timestamp(value.updated_at).expect("valid timestamp"), + ) } } @@ -1668,18 +1666,16 @@ pub mod test { let mut connection = SqliteConnection::establish(":memory:").unwrap(); connection.run_pending_migrations(MIGRATIONS).unwrap(); - let transaction = ln_dlc_node::transaction::Transaction { - txid: Txid::from_str( - "44fe3d70a3058eb1bef62e24379b4865ada8332f9ee30752cf606f37343461a0", - ) - .unwrap(), - fee: 0, + let transaction = ln_dlc_node::transaction::Transaction::new( + Txid::from_str("44fe3d70a3058eb1bef62e24379b4865ada8332f9ee30752cf606f37343461a0") + .unwrap(), + 0, // we need to set the time manually as the nano seconds are not stored in sql. - created_at: OffsetDateTime::now_utc().replace_time(Time::from_hms(0, 0, 0).unwrap()), - updated_at: OffsetDateTime::now_utc().replace_time(Time::from_hms(0, 0, 0).unwrap()), - }; + OffsetDateTime::now_utc().replace_time(Time::from_hms(0, 0, 0).unwrap()), + OffsetDateTime::now_utc().replace_time(Time::from_hms(0, 0, 0).unwrap()), + ); - Transaction::upsert(transaction.clone().into(), &mut connection).unwrap(); + Transaction::upsert(transaction.into(), &mut connection).unwrap(); // Verify that we can load the right transaction by the `txid` let loaded: ln_dlc_node::transaction::Transaction = Transaction::get( @@ -1692,15 +1688,13 @@ pub mod test { assert_eq!(transaction, loaded); - let second_tx = ln_dlc_node::transaction::Transaction { - txid: Txid::from_str( - "44fe3d70a3058eb1bef62e24379b4865ada8332f9ee30752cf606f37343461a1", - ) - .unwrap(), - fee: 1, - created_at: OffsetDateTime::now_utc(), - updated_at: OffsetDateTime::now_utc(), - }; + let second_tx = ln_dlc_node::transaction::Transaction::new( + Txid::from_str("44fe3d70a3058eb1bef62e24379b4865ada8332f9ee30752cf606f37343461a1") + .unwrap(), + 1, + OffsetDateTime::now_utc(), + OffsetDateTime::now_utc(), + ); Transaction::upsert(second_tx.into(), &mut connection).unwrap(); // Verify that we can load all transactions without fees let transactions = Transaction::get_all_without_fees(&mut connection).unwrap();