From 9654ed03523185995bc05878280219d71f187529 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 2 Aug 2023 15:28:51 +0200 Subject: [PATCH] Add `OutputSweeper` and persistence utils We add an `OutputSweeper` object that will keep track of sweepable outputs. To this end, we start by adding the general structures and the required utilities to persist the `SpendableOutputInfo` to our `KVStore`. --- src/io/mod.rs | 3 ++ src/io/utils.rs | 26 +++++++++ src/lib.rs | 1 + src/sweep.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 src/sweep.rs diff --git a/src/io/mod.rs b/src/io/mod.rs index 994cb0206..c087eec37 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -36,6 +36,9 @@ pub(crate) const PEER_INFO_PERSISTENCE_KEY: &str = "peers"; /// The payment information will be persisted under this prefix. pub(crate) const PAYMENT_INFO_PERSISTENCE_NAMESPACE: &str = "payments"; +/// The spendable output information will be persisted under this prefix. +pub(crate) const SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE: &str = "spendable_outputs"; + /// RapidGossipSync's `latest_sync_timestamp` will be persisted under this key. pub(crate) const LATEST_RGS_SYNC_TIMESTAMP_NAMESPACE: &str = ""; pub(crate) const LATEST_RGS_SYNC_TIMESTAMP_KEY: &str = "latest_rgs_sync_timestamp"; diff --git a/src/io/utils.rs b/src/io/utils.rs index 9bace5d5a..65da90c13 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -3,6 +3,7 @@ use crate::WALLET_KEYS_SEED_LEN; use crate::logger::log_error; use crate::peer_store::PeerStore; +use crate::sweep::SpendableOutputInfo; use crate::{Error, EventQueue, PaymentDetails}; use lightning::chain::channelmonitor::ChannelMonitor; @@ -224,6 +225,31 @@ where Ok(res) } +/// Read previously persisted spendable output information from the store. +pub(crate) fn read_spendable_outputs( + kv_store: Arc, logger: L, +) -> Result, std::io::Error> +where + L::Target: Logger, +{ + let mut res = Vec::new(); + + for stored_key in kv_store.list(SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE)? { + let output = SpendableOutputInfo::read( + &mut kv_store.read(SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE, &stored_key)?, + ) + .map_err(|e| { + log_error!(logger, "Failed to deserialize SpendableOutputInfo: {}", e); + std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Failed to deserialize SpendableOutputInfo", + ) + })?; + res.push(output); + } + Ok(res) +} + pub(crate) fn read_latest_rgs_sync_timestamp( kv_store: Arc, logger: L, ) -> Result diff --git a/src/lib.rs b/src/lib.rs index b06f0a2ae..e1ec214f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,7 @@ pub mod io; mod logger; mod payment_store; mod peer_store; +mod sweep; #[cfg(test)] mod test; mod types; diff --git a/src/sweep.rs b/src/sweep.rs new file mode 100644 index 000000000..7f29ca725 --- /dev/null +++ b/src/sweep.rs @@ -0,0 +1,140 @@ +use crate::hex_utils; +use crate::io::{KVStore, SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE}; +use crate::logger::{log_debug, log_error, Logger}; +use crate::wallet::Wallet; +use crate::{Error, KeysManager}; + +use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; +use lightning::chain::BestBlock; +use lightning::impl_writeable_tlv_based; +use lightning::sign::{EntropySource, SpendableOutputDescriptor}; +use lightning::util::ser::Writeable; + +use bitcoin::secp256k1::Secp256k1; +use bitcoin::{BlockHash, LockTime, PackedLockTime, Transaction}; + +use std::ops::Deref; +use std::sync::{Arc, Mutex}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct SpendableOutputInfo { + id: [u8; 32], + descriptor: SpendableOutputDescriptor, + spending_tx: Option, + broadcast_height: Option, + confirmed_in_block: Option<(u32, BlockHash)>, +} + +impl_writeable_tlv_based!(SpendableOutputInfo, { + (0, id, required), + (2, descriptor, required), + (4, spending_tx, option), + (6, broadcast_height, option), + (8, confirmed_in_block, option), +}); + +pub(crate) struct OutputSweeper +where + L::Target: Logger, +{ + outputs: Mutex>, + wallet: Arc>, + keys_manager: Arc, + kv_store: Arc, + best_block: Mutex, + logger: L, +} + +impl OutputSweeper +where + L::Target: Logger, +{ + pub(crate) fn new( + outputs: Vec, wallet: Arc>, + keys_manager: Arc, kv_store: Arc, best_block: BestBlock, logger: L, + ) -> Self { + let outputs = Mutex::new(outputs); + let best_block = Mutex::new(best_block); + Self { outputs, wallet, keys_manager, kv_store, best_block, logger } + } + + pub(crate) fn add_outputs(&self, output_descriptors: Vec) { + let mut locked_outputs = self.outputs.lock().unwrap(); + + let (spending_tx, broadcast_height) = match self.get_spending_tx(&output_descriptors) { + Ok(Some(spending_tx)) => { + self.wallet.broadcast_transactions(&[&spending_tx]); + (Some(spending_tx), Some(self.best_block.lock().unwrap().height())) + } + Ok(None) => { + log_debug!( + self.logger, + "Omitted spending static outputs: {:?}", + output_descriptors + ); + (None, None) + } + Err(e) => { + log_error!(self.logger, "Error spending outputs: {:?}", e); + (None, None) + } + }; + + for descriptor in output_descriptors { + let id = self.keys_manager.get_secure_random_bytes(); + let output_info = SpendableOutputInfo { + id, + descriptor, + spending_tx: spending_tx.clone(), + broadcast_height, + confirmed_in_block: None, + }; + + locked_outputs.push(output_info.clone()); + match self.persist_info(&output_info) { + Ok(()) => {} + Err(e) => { + log_error!(self.logger, "Error persisting spendable output info: {:?}", e) + } + } + } + } + + fn get_spending_tx( + &self, output_descriptors: &Vec, + ) -> Result, ()> { + let tx_feerate = self.wallet.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); + + let destination_address = self.wallet.get_new_address().map_err(|e| { + log_error!(self.logger, "Failed to get destination address from wallet: {}", e); + })?; + + let cur_height = self.best_block.lock().unwrap().height(); + let locktime: PackedLockTime = + LockTime::from_height(cur_height).map_or(PackedLockTime::ZERO, |l| l.into()); + + self.keys_manager.spend_spendable_outputs( + &output_descriptors.iter().collect::>(), + Vec::new(), + destination_address.script_pubkey(), + tx_feerate, + Some(locktime), + &Secp256k1::new(), + ) + } + + fn persist_info(&self, output: &SpendableOutputInfo) -> Result<(), Error> { + let key = hex_utils::to_string(&output.id); + let data = output.encode(); + self.kv_store.write(SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE, &key, &data).map_err(|e| { + log_error!( + self.logger, + "Write for key {}/{} failed due to: {}", + SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE, + key, + e + ); + Error::PersistenceFailed + }) + } +}