Skip to content

Commit 9654ed0

Browse files
committed
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`.
1 parent d04b1be commit 9654ed0

File tree

4 files changed

+170
-0
lines changed

4 files changed

+170
-0
lines changed

src/io/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ pub(crate) const PEER_INFO_PERSISTENCE_KEY: &str = "peers";
3636
/// The payment information will be persisted under this prefix.
3737
pub(crate) const PAYMENT_INFO_PERSISTENCE_NAMESPACE: &str = "payments";
3838

39+
/// The spendable output information will be persisted under this prefix.
40+
pub(crate) const SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE: &str = "spendable_outputs";
41+
3942
/// RapidGossipSync's `latest_sync_timestamp` will be persisted under this key.
4043
pub(crate) const LATEST_RGS_SYNC_TIMESTAMP_NAMESPACE: &str = "";
4144
pub(crate) const LATEST_RGS_SYNC_TIMESTAMP_KEY: &str = "latest_rgs_sync_timestamp";

src/io/utils.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::WALLET_KEYS_SEED_LEN;
33

44
use crate::logger::log_error;
55
use crate::peer_store::PeerStore;
6+
use crate::sweep::SpendableOutputInfo;
67
use crate::{Error, EventQueue, PaymentDetails};
78

89
use lightning::chain::channelmonitor::ChannelMonitor;
@@ -224,6 +225,31 @@ where
224225
Ok(res)
225226
}
226227

228+
/// Read previously persisted spendable output information from the store.
229+
pub(crate) fn read_spendable_outputs<K: KVStore + Sync + Send, L: Deref>(
230+
kv_store: Arc<K>, logger: L,
231+
) -> Result<Vec<SpendableOutputInfo>, std::io::Error>
232+
where
233+
L::Target: Logger,
234+
{
235+
let mut res = Vec::new();
236+
237+
for stored_key in kv_store.list(SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE)? {
238+
let output = SpendableOutputInfo::read(
239+
&mut kv_store.read(SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE, &stored_key)?,
240+
)
241+
.map_err(|e| {
242+
log_error!(logger, "Failed to deserialize SpendableOutputInfo: {}", e);
243+
std::io::Error::new(
244+
std::io::ErrorKind::InvalidData,
245+
"Failed to deserialize SpendableOutputInfo",
246+
)
247+
})?;
248+
res.push(output);
249+
}
250+
Ok(res)
251+
}
252+
227253
pub(crate) fn read_latest_rgs_sync_timestamp<K: KVStore + Sync + Send, L: Deref>(
228254
kv_store: Arc<K>, logger: L,
229255
) -> Result<u32, std::io::Error>

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub mod io;
8383
mod logger;
8484
mod payment_store;
8585
mod peer_store;
86+
mod sweep;
8687
#[cfg(test)]
8788
mod test;
8889
mod types;

src/sweep.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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

Comments
 (0)