|
| 1 | +use crate::hex_utils; |
| 2 | +use crate::io::{KVStore, SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE}; |
| 3 | +use crate::logger::{log_debug, log_error, Logger}; |
| 4 | +use crate::wallet::Wallet; |
| 5 | +use crate::{Error, KeysManager}; |
| 6 | + |
| 7 | +use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; |
| 8 | +use lightning::chain::BestBlock; |
| 9 | +use lightning::impl_writeable_tlv_based; |
| 10 | +use lightning::sign::{EntropySource, SpendableOutputDescriptor}; |
| 11 | +use lightning::util::ser::Writeable; |
| 12 | + |
| 13 | +use bitcoin::secp256k1::Secp256k1; |
| 14 | +use bitcoin::{BlockHash, LockTime, PackedLockTime, Transaction}; |
| 15 | + |
| 16 | +use std::ops::Deref; |
| 17 | +use std::sync::{Arc, Mutex}; |
| 18 | + |
| 19 | +#[derive(Clone, Debug, PartialEq, Eq)] |
| 20 | +pub(crate) struct SpendableOutputInfo { |
| 21 | + id: [u8; 32], |
| 22 | + descriptor: SpendableOutputDescriptor, |
| 23 | + spending_tx: Option<Transaction>, |
| 24 | + broadcast_height: Option<u32>, |
| 25 | + confirmed_in_block: Option<(u32, BlockHash)>, |
| 26 | +} |
| 27 | + |
| 28 | +impl_writeable_tlv_based!(SpendableOutputInfo, { |
| 29 | + (0, id, required), |
| 30 | + (2, descriptor, required), |
| 31 | + (4, spending_tx, option), |
| 32 | + (6, broadcast_height, option), |
| 33 | + (8, confirmed_in_block, option), |
| 34 | +}); |
| 35 | + |
| 36 | +pub(crate) struct OutputSweeper<K: KVStore + Sync + Send, L: Deref> |
| 37 | +where |
| 38 | + L::Target: Logger, |
| 39 | +{ |
| 40 | + outputs: Mutex<Vec<SpendableOutputInfo>>, |
| 41 | + wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>, |
| 42 | + keys_manager: Arc<KeysManager>, |
| 43 | + kv_store: Arc<K>, |
| 44 | + best_block: Mutex<BestBlock>, |
| 45 | + logger: L, |
| 46 | +} |
| 47 | + |
| 48 | +impl<K: KVStore + Sync + Send, L: Deref> OutputSweeper<K, L> |
| 49 | +where |
| 50 | + L::Target: Logger, |
| 51 | +{ |
| 52 | + pub(crate) fn new( |
| 53 | + outputs: Vec<SpendableOutputInfo>, wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>, |
| 54 | + keys_manager: Arc<KeysManager>, kv_store: Arc<K>, best_block: BestBlock, logger: L, |
| 55 | + ) -> Self { |
| 56 | + let outputs = Mutex::new(outputs); |
| 57 | + let best_block = Mutex::new(best_block); |
| 58 | + Self { outputs, wallet, keys_manager, kv_store, best_block, logger } |
| 59 | + } |
| 60 | + |
| 61 | + pub(crate) fn add_outputs(&self, output_descriptors: Vec<SpendableOutputDescriptor>) { |
| 62 | + let mut locked_outputs = self.outputs.lock().unwrap(); |
| 63 | + |
| 64 | + let (spending_tx, broadcast_height) = match self.get_spending_tx(&output_descriptors) { |
| 65 | + Ok(Some(spending_tx)) => { |
| 66 | + self.wallet.broadcast_transactions(&[&spending_tx]); |
| 67 | + (Some(spending_tx), Some(self.best_block.lock().unwrap().height())) |
| 68 | + } |
| 69 | + Ok(None) => { |
| 70 | + log_debug!( |
| 71 | + self.logger, |
| 72 | + "Omitted spending static outputs: {:?}", |
| 73 | + output_descriptors |
| 74 | + ); |
| 75 | + (None, None) |
| 76 | + } |
| 77 | + Err(e) => { |
| 78 | + log_error!(self.logger, "Error spending outputs: {:?}", e); |
| 79 | + (None, None) |
| 80 | + } |
| 81 | + }; |
| 82 | + |
| 83 | + for descriptor in output_descriptors { |
| 84 | + let id = self.keys_manager.get_secure_random_bytes(); |
| 85 | + let output_info = SpendableOutputInfo { |
| 86 | + id, |
| 87 | + descriptor, |
| 88 | + spending_tx: spending_tx.clone(), |
| 89 | + broadcast_height, |
| 90 | + confirmed_in_block: None, |
| 91 | + }; |
| 92 | + |
| 93 | + locked_outputs.push(output_info.clone()); |
| 94 | + match self.persist_info(&output_info) { |
| 95 | + Ok(()) => {} |
| 96 | + Err(e) => { |
| 97 | + log_error!(self.logger, "Error persisting spendable output info: {:?}", e) |
| 98 | + } |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + fn get_spending_tx( |
| 104 | + &self, output_descriptors: &Vec<SpendableOutputDescriptor>, |
| 105 | + ) -> Result<Option<Transaction>, ()> { |
| 106 | + let tx_feerate = self.wallet.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); |
| 107 | + |
| 108 | + let destination_address = self.wallet.get_new_address().map_err(|e| { |
| 109 | + log_error!(self.logger, "Failed to get destination address from wallet: {}", e); |
| 110 | + })?; |
| 111 | + |
| 112 | + let cur_height = self.best_block.lock().unwrap().height(); |
| 113 | + let locktime: PackedLockTime = |
| 114 | + LockTime::from_height(cur_height).map_or(PackedLockTime::ZERO, |l| l.into()); |
| 115 | + |
| 116 | + self.keys_manager.spend_spendable_outputs( |
| 117 | + &output_descriptors.iter().collect::<Vec<_>>(), |
| 118 | + Vec::new(), |
| 119 | + destination_address.script_pubkey(), |
| 120 | + tx_feerate, |
| 121 | + Some(locktime), |
| 122 | + &Secp256k1::new(), |
| 123 | + ) |
| 124 | + } |
| 125 | + |
| 126 | + fn persist_info(&self, output: &SpendableOutputInfo) -> Result<(), Error> { |
| 127 | + let key = hex_utils::to_string(&output.id); |
| 128 | + let data = output.encode(); |
| 129 | + self.kv_store.write(SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE, &key, &data).map_err(|e| { |
| 130 | + log_error!( |
| 131 | + self.logger, |
| 132 | + "Write for key {}/{} failed due to: {}", |
| 133 | + SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE, |
| 134 | + key, |
| 135 | + e |
| 136 | + ); |
| 137 | + Error::PersistenceFailed |
| 138 | + }) |
| 139 | + } |
| 140 | +} |
0 commit comments