Skip to content

Commit 80dffe9

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 5c1f155 commit 80dffe9

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

src/io/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ pub(crate) const PEER_INFO_PERSISTENCE_KEY: &str = "peers";
1919
pub(crate) const PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE: &str = "payments";
2020
pub(crate) const PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE: &str = "";
2121

22+
/// The spendable output information will be persisted under this prefix.
23+
pub(crate) const SPENDABLE_OUTPUT_INFO_PERSISTENCE_PRIMARY_NAMESPACE: &str = "spendable_outputs";
24+
pub(crate) const SPENDABLE_OUTPUT_INFO_PERSISTENCE_SECONDARY_NAMESPACE: &str = "";
25+
2226
/// RapidGossipSync's `latest_sync_timestamp` will be persisted under this key.
2327
pub(crate) const LATEST_RGS_SYNC_TIMESTAMP_PRIMARY_NAMESPACE: &str = "";
2428
pub(crate) const LATEST_RGS_SYNC_TIMESTAMP_SECONDARY_NAMESPACE: &str = "";

src/io/utils.rs

Lines changed: 31 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::routing::gossip::NetworkGraph;
@@ -199,6 +200,36 @@ where
199200
Ok(res)
200201
}
201202

203+
/// Read previously persisted spendable output information from the store.
204+
pub(crate) fn read_spendable_outputs<K: KVStore + Sync + Send, L: Deref>(
205+
kv_store: Arc<K>, logger: L,
206+
) -> Result<Vec<SpendableOutputInfo>, std::io::Error>
207+
where
208+
L::Target: Logger,
209+
{
210+
let mut res = Vec::new();
211+
212+
for stored_key in kv_store.list(
213+
SPENDABLE_OUTPUT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
214+
SPENDABLE_OUTPUT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
215+
)? {
216+
let mut reader = Cursor::new(kv_store.read(
217+
SPENDABLE_OUTPUT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
218+
SPENDABLE_OUTPUT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
219+
&stored_key,
220+
)?);
221+
let output = SpendableOutputInfo::read(&mut reader).map_err(|e| {
222+
log_error!(logger, "Failed to deserialize SpendableOutputInfo: {}", e);
223+
std::io::Error::new(
224+
std::io::ErrorKind::InvalidData,
225+
"Failed to deserialize SpendableOutputInfo",
226+
)
227+
})?;
228+
res.push(output);
229+
}
230+
Ok(res)
231+
}
232+
202233
pub(crate) fn read_latest_rgs_sync_timestamp<K: KVStore + Sync + Send, L: Deref>(
203234
kv_store: Arc<K>, logger: L,
204235
) -> Result<u32, std::io::Error>

src/lib.rs

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

src/sweep.rs

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

0 commit comments

Comments
 (0)