Skip to content

Commit

Permalink
Add OutputSweeper and persistence utils
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
tnull committed Aug 11, 2023
1 parent d04b1be commit 9654ed0
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
26 changes: 26 additions & 0 deletions src/io/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -224,6 +225,31 @@ where
Ok(res)
}

/// Read previously persisted spendable output information from the store.
pub(crate) fn read_spendable_outputs<K: KVStore + Sync + Send, L: Deref>(
kv_store: Arc<K>, logger: L,
) -> Result<Vec<SpendableOutputInfo>, 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<K: KVStore + Sync + Send, L: Deref>(
kv_store: Arc<K>, logger: L,
) -> Result<u32, std::io::Error>
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub mod io;
mod logger;
mod payment_store;
mod peer_store;
mod sweep;
#[cfg(test)]
mod test;
mod types;
Expand Down
140 changes: 140 additions & 0 deletions src/sweep.rs
Original file line number Diff line number Diff line change
@@ -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<Transaction>,
broadcast_height: Option<u32>,
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<K: KVStore + Sync + Send, L: Deref>
where
L::Target: Logger,
{
outputs: Mutex<Vec<SpendableOutputInfo>>,
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>,
keys_manager: Arc<KeysManager>,
kv_store: Arc<K>,
best_block: Mutex<BestBlock>,
logger: L,
}

impl<K: KVStore + Sync + Send, L: Deref> OutputSweeper<K, L>
where
L::Target: Logger,
{
pub(crate) fn new(
outputs: Vec<SpendableOutputInfo>, wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>,
keys_manager: Arc<KeysManager>, kv_store: Arc<K>, 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<SpendableOutputDescriptor>) {
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<SpendableOutputDescriptor>,
) -> Result<Option<Transaction>, ()> {
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<_>>(),
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
})
}
}

0 comments on commit 9654ed0

Please sign in to comment.