Skip to content

Commit 595db8a

Browse files
Add ChannelDataPersister trait and point SMCM to it.
Intended to be a simple way for users to know where and how to put their backup and persistence logic.
1 parent 3defcc8 commit 595db8a

File tree

14 files changed

+304
-55
lines changed

14 files changed

+304
-55
lines changed

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
[workspace]
22

33
members = [
4-
"lightning",
5-
"lightning-net-tokio",
4+
"lightning",
5+
"lightning-net-tokio",
6+
"lightning-data-persister",
67
]
78

89
# Our tests do actual crypo and lots of work, the tradeoff for -O1 is well worth it

fuzz/src/chanmon_consistency.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use lightning::chain::transaction::OutPoint;
3434
use lightning::chain::chaininterface::{BroadcasterInterface,ConfirmationTarget,ChainListener,FeeEstimator,ChainWatchInterfaceUtil,ChainWatchInterface};
3535
use lightning::chain::keysinterface::{KeysInterface, InMemoryChannelKeys};
3636
use lightning::ln::channelmonitor;
37+
use lightning::ln::data_persister::ChannelDataPersister;
3738
use lightning::ln::channelmonitor::{ChannelMonitor, ChannelMonitorUpdateErr, MonitorEvent};
3839
use lightning::ln::channelmanager::{ChannelManager, PaymentHash, PaymentPreimage, PaymentSecret, ChannelManagerReadArgs};
3940
use lightning::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
@@ -48,6 +49,7 @@ use lightning::routing::router::{Route, RouteHop};
4849

4950

5051
use utils::test_logger;
52+
use utils::test_data_persister::TestChanDataPersister;
5153

5254
use bitcoin::secp256k1::key::{PublicKey,SecretKey};
5355
use bitcoin::secp256k1::Secp256k1;
@@ -84,7 +86,7 @@ impl Writer for VecWriter {
8486

8587
struct TestChannelMonitor {
8688
pub logger: Arc<dyn Logger>,
87-
pub simple_monitor: Arc<channelmonitor::SimpleManyChannelMonitor<OutPoint, EnforcingChannelKeys, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<dyn ChainWatchInterface>>>,
89+
pub simple_monitor: Arc<channelmonitor::SimpleManyChannelMonitor<OutPoint, EnforcingChannelKeys, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<dyn ChainWatchInterface>, Arc<dyn ChannelDataPersister<Keys=EnforcingChannelKeys>>>>,
8890
pub update_ret: Mutex<Result<(), channelmonitor::ChannelMonitorUpdateErr>>,
8991
// If we reload a node with an old copy of ChannelMonitors, the ChannelManager deserialization
9092
// logic will automatically force-close our channels for us (as we don't have an up-to-date
@@ -95,9 +97,9 @@ struct TestChannelMonitor {
9597
pub should_update_manager: atomic::AtomicBool,
9698
}
9799
impl TestChannelMonitor {
98-
pub fn new(chain_monitor: Arc<dyn chaininterface::ChainWatchInterface>, broadcaster: Arc<TestBroadcaster>, logger: Arc<dyn Logger>, feeest: Arc<FuzzEstimator>) -> Self {
100+
pub fn new(chain_monitor: Arc<dyn chaininterface::ChainWatchInterface>, broadcaster: Arc<TestBroadcaster>, logger: Arc<dyn Logger>, feeest: Arc<FuzzEstimator>, data_persister: Arc<dyn ChannelDataPersister<Keys=EnforcingChannelKeys>>) -> Self {
99101
Self {
100-
simple_monitor: Arc::new(channelmonitor::SimpleManyChannelMonitor::new(chain_monitor, broadcaster, logger.clone(), feeest)),
102+
simple_monitor: Arc::new(channelmonitor::SimpleManyChannelMonitor::new(chain_monitor, broadcaster, logger.clone(), feeest, data_persister)),
101103
logger,
102104
update_ret: Mutex::new(Ok(())),
103105
latest_monitors: Mutex::new(HashMap::new()),
@@ -193,7 +195,8 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
193195
($node_id: expr) => { {
194196
let logger: Arc<dyn Logger> = Arc::new(test_logger::TestLogger::new($node_id.to_string(), out.clone()));
195197
let watch = Arc::new(ChainWatchInterfaceUtil::new(Network::Bitcoin));
196-
let monitor = Arc::new(TestChannelMonitor::new(watch.clone(), broadcast.clone(), logger.clone(), fee_est.clone()));
198+
let data_persister = Arc::new(TestChanDataPersister{});
199+
let monitor = Arc::new(TestChannelMonitor::new(watch.clone(), broadcast.clone(), logger.clone(), fee_est.clone(), data_persister.clone()));
197200

198201
let keys_manager = Arc::new(KeyProvider { node_id: $node_id, rand_bytes_id: atomic::AtomicU8::new(0) });
199202
let mut config = UserConfig::default();
@@ -209,7 +212,8 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
209212
($ser: expr, $node_id: expr, $old_monitors: expr) => { {
210213
let logger: Arc<dyn Logger> = Arc::new(test_logger::TestLogger::new($node_id.to_string(), out.clone()));
211214
let watch = Arc::new(ChainWatchInterfaceUtil::new(Network::Bitcoin));
212-
let monitor = Arc::new(TestChannelMonitor::new(watch.clone(), broadcast.clone(), logger.clone(), fee_est.clone()));
215+
let data_persister = Arc::new(TestChanDataPersister{});
216+
let monitor = Arc::new(TestChannelMonitor::new(watch.clone(), broadcast.clone(), logger.clone(), fee_est.clone(), data_persister.clone()));
213217

214218
let keys_manager = Arc::new(KeyProvider { node_id: $node_id, rand_bytes_id: atomic::AtomicU8::new(0) });
215219
let mut config = UserConfig::default();

fuzz/src/full_stack.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use lightning::chain::chaininterface::{BroadcasterInterface,ConfirmationTarget,C
3030
use lightning::chain::transaction::OutPoint;
3131
use lightning::chain::keysinterface::{InMemoryChannelKeys, KeysInterface};
3232
use lightning::ln::channelmonitor;
33+
use lightning::ln::data_persister::ChannelDataPersister;
3334
use lightning::ln::channelmanager::{ChannelManager, PaymentHash, PaymentPreimage, PaymentSecret};
3435
use lightning::ln::peer_handler::{MessageHandler,PeerManager,SocketDescriptor};
3536
use lightning::routing::router::get_route;
@@ -40,6 +41,7 @@ use lightning::util::logger::Logger;
4041
use lightning::util::config::UserConfig;
4142

4243
use utils::test_logger;
44+
use utils::test_data_persister::TestChanDataPersister;
4345

4446
use bitcoin::secp256k1::key::{PublicKey,SecretKey};
4547
use bitcoin::secp256k1::Secp256k1;
@@ -145,14 +147,14 @@ impl<'a> std::hash::Hash for Peer<'a> {
145147

146148
type ChannelMan = ChannelManager<
147149
EnforcingChannelKeys,
148-
Arc<channelmonitor::SimpleManyChannelMonitor<OutPoint, EnforcingChannelKeys, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<ChainWatchInterfaceUtil>>>,
150+
Arc<channelmonitor::SimpleManyChannelMonitor<OutPoint, EnforcingChannelKeys, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<ChainWatchInterfaceUtil>, Arc<dyn ChannelDataPersister<Keys=EnforcingChannelKeys>>>>,
149151
Arc<TestBroadcaster>, Arc<KeyProvider>, Arc<FuzzEstimator>, Arc<dyn Logger>>;
150152
type PeerMan<'a> = PeerManager<Peer<'a>, Arc<ChannelMan>, Arc<NetGraphMsgHandler<Arc<ChainWatchInterfaceUtil>, Arc<dyn Logger>>>, Arc<dyn Logger>>;
151153

152154
struct MoneyLossDetector<'a> {
153155
manager: Arc<ChannelMan>,
154156
monitor: Arc<channelmonitor::SimpleManyChannelMonitor<
155-
OutPoint, EnforcingChannelKeys, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<ChainWatchInterfaceUtil>>>,
157+
OutPoint, EnforcingChannelKeys, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<ChainWatchInterfaceUtil>, Arc<dyn ChannelDataPersister<Keys=EnforcingChannelKeys>>>>,
156158
handler: PeerMan<'a>,
157159

158160
peers: &'a RefCell<[bool; 256]>,
@@ -166,7 +168,7 @@ struct MoneyLossDetector<'a> {
166168
impl<'a> MoneyLossDetector<'a> {
167169
pub fn new(peers: &'a RefCell<[bool; 256]>,
168170
manager: Arc<ChannelMan>,
169-
monitor: Arc<channelmonitor::SimpleManyChannelMonitor<OutPoint, EnforcingChannelKeys, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<ChainWatchInterfaceUtil>>>,
171+
monitor: Arc<channelmonitor::SimpleManyChannelMonitor<OutPoint, EnforcingChannelKeys, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<ChainWatchInterfaceUtil>, Arc<dyn ChannelDataPersister<Keys=EnforcingChannelKeys>>>>,
170172
handler: PeerMan<'a>) -> Self {
171173
MoneyLossDetector {
172174
manager,
@@ -337,7 +339,8 @@ pub fn do_test(data: &[u8], logger: &Arc<dyn Logger>) {
337339

338340
let watch = Arc::new(ChainWatchInterfaceUtil::new(Network::Bitcoin));
339341
let broadcast = Arc::new(TestBroadcaster{});
340-
let monitor = Arc::new(channelmonitor::SimpleManyChannelMonitor::new(watch.clone(), broadcast.clone(), Arc::clone(&logger), fee_est.clone()));
342+
let data_persister: Arc<dyn ChannelDataPersister<Keys = EnforcingChannelKeys>> = Arc::new(TestChanDataPersister{});
343+
let monitor = Arc::new(channelmonitor::SimpleManyChannelMonitor::new(watch.clone(), broadcast.clone(), Arc::clone(&logger), fee_est.clone(), data_persister.clone()));
341344

342345
let keys_manager = Arc::new(KeyProvider { node_secret: our_network_key.clone(), counter: AtomicU64::new(0) });
343346
let mut config = UserConfig::default();

fuzz/src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
// licenses.
99

1010
pub mod test_logger;
11+
pub mod test_data_persister;

fuzz/src/utils/test_data_persister.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use lightning::ln::data_persister::ChannelDataPersister;
2+
use lightning::ln::channelmonitor;
3+
use lightning::chain::transaction::OutPoint;
4+
use lightning::util::enforcing_trait_impls::EnforcingChannelKeys;
5+
6+
use std::collections::HashMap;
7+
8+
pub struct TestChanDataPersister {}
9+
impl ChannelDataPersister for TestChanDataPersister {
10+
type Keys = EnforcingChannelKeys;
11+
12+
fn persist_channel_data(&self, _funding_txo: OutPoint, _data: &channelmonitor::ChannelMonitor<EnforcingChannelKeys>) -> Result<(), channelmonitor::ChannelMonitorUpdateErr> {
13+
Ok(())
14+
}
15+
16+
fn update_channel_data(&self, _funding_txo: OutPoint, _update: &channelmonitor::ChannelMonitorUpdate, _data: &channelmonitor::ChannelMonitor<EnforcingChannelKeys>) -> Result<(), channelmonitor::ChannelMonitorUpdateErr> {
17+
Ok(())
18+
}
19+
20+
fn load_channel_data(&self) -> Result<HashMap<OutPoint, channelmonitor::ChannelMonitor<EnforcingChannelKeys>>, channelmonitor::ChannelMonitorUpdateErr> {
21+
Ok(HashMap::new())
22+
}
23+
}

lightning-data-persister/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "lightning-data-persister"
3+
version = "0.0.1"
4+
authors = ["Valentine Wallace"]
5+
license = "Apache-2.0"
6+
edition = "2018"
7+
description = """
8+
Utilities to manage channel data persistence and retrieval.
9+
"""
10+
11+
[dependencies]
12+
bitcoin = "0.23"
13+
lightning = { version = "0.0.11", path = "../lightning" }

lightning-data-persister/src/lib.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use lightning::chain::keysinterface::ChannelKeys;
2+
use lightning::ln::data_persister::ChannelDataPersister;
3+
use lightning::ln::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateErr};
4+
use lightning::util::ser::{Writeable, Readable};
5+
use lightning::chain::transaction::OutPoint;
6+
use bitcoin::hash_types::{BlockHash, Txid};
7+
use bitcoin::hashes::hex::{ToHex, FromHex};
8+
use std::collections::HashMap;
9+
use std::fs;
10+
use std::io::{Error, ErrorKind, Cursor};
11+
use std::marker::PhantomData;
12+
13+
/// LinuxPersister can persist channel data on disk on Linux machines, where
14+
/// each channel's data is stored in a file named after its outpoint.
15+
pub struct LinuxPersister<ChanSigner: ChannelKeys + Readable + Writeable> {
16+
path_to_channel_data: String,
17+
phantom: PhantomData<ChanSigner>, // TODO: is there a way around this?
18+
}
19+
20+
impl<ChanSigner: ChannelKeys + Readable + Writeable> LinuxPersister<ChanSigner> {
21+
/// Initialize a new LinuxPersister and set the path to the individual channels'
22+
/// files.
23+
pub fn new(path_to_channel_data: String) -> Self {
24+
return Self {
25+
path_to_channel_data,
26+
phantom: PhantomData,
27+
}
28+
}
29+
30+
fn get_full_filepath(&self, funding_txo: OutPoint) -> String {
31+
format!("{}/{}_{}", self.path_to_channel_data, funding_txo.txid.to_hex(), funding_txo.index)
32+
}
33+
34+
fn write_channel_data(&self, funding_txo: OutPoint, monitor: &ChannelMonitor<ChanSigner>) -> std::io::Result<()> {
35+
// Do a crazy dance with lots of fsync()s to be overly cautious here...
36+
// We never want to end up in a state where we've lost the old data, or end up using the
37+
// old data on power loss after we've returned
38+
// Note that this actually *isn't* enough (at least on Linux)! We need to fsync an fd with
39+
// the containing dir, but Rust doesn't let us do that directly, sadly. TODO: Fix this with
40+
// the libc crate!
41+
let filename = self.get_full_filepath(funding_txo);
42+
let tmp_filename = filename.clone() + ".tmp";
43+
44+
{
45+
let mut f = fs::File::create(&tmp_filename)?;
46+
monitor.write_for_disk(&mut f)?;
47+
f.sync_all()?;
48+
}
49+
// We don't need to create a backup if didn't already have the file, but in any other case
50+
// try to create the backup and expect failure on fs::copy() if eg there's a perms issue.
51+
let need_bk = match fs::metadata(&filename) {
52+
Ok(data) => {
53+
if !data.is_file() { return Err(Error::new(ErrorKind::InvalidInput, "Filename given was not a file")); }
54+
true
55+
},
56+
Err(e) => match e.kind() {
57+
std::io::ErrorKind::NotFound => false,
58+
_ => true,
59+
}
60+
};
61+
let bk_filename = filename.clone() + ".bk";
62+
if need_bk {
63+
fs::copy(&filename, &bk_filename)?;
64+
{
65+
let f = fs::File::open(&bk_filename)?;
66+
f.sync_all()?;
67+
}
68+
}
69+
fs::rename(&tmp_filename, &filename)?;
70+
{
71+
let f = fs::File::open(&filename)?;
72+
f.sync_all()?;
73+
}
74+
if need_bk {
75+
fs::remove_file(&bk_filename)?;
76+
}
77+
Ok(())
78+
}
79+
}
80+
81+
impl<ChanSigner: ChannelKeys + Readable + Writeable + Send + Sync> ChannelDataPersister for LinuxPersister<ChanSigner> {
82+
type Keys = ChanSigner;
83+
84+
fn persist_channel_data(&self, funding_txo: OutPoint, monitor: &ChannelMonitor<Self::Keys>) -> Result<(), ChannelMonitorUpdateErr> {
85+
match self.write_channel_data(funding_txo, monitor) {
86+
Ok(_) => Ok(()),
87+
Err(_) => Err(ChannelMonitorUpdateErr::TemporaryFailure)
88+
}
89+
}
90+
91+
fn update_channel_data(&self, funding_txo: OutPoint, _update: &ChannelMonitorUpdate, monitor: &ChannelMonitor<ChanSigner>) -> Result<(), ChannelMonitorUpdateErr> {
92+
match self.write_channel_data(funding_txo, monitor) {
93+
Ok(_) => Ok(()),
94+
Err(_) => Err(ChannelMonitorUpdateErr::TemporaryFailure)
95+
}
96+
}
97+
98+
fn load_channel_data(&self) -> Result<HashMap<OutPoint, ChannelMonitor<ChanSigner>>, ChannelMonitorUpdateErr> {
99+
let mut res = HashMap::new();
100+
for file_option in fs::read_dir(&self.path_to_channel_data).unwrap() {
101+
let mut loaded = false;
102+
let file = file_option.unwrap();
103+
if let Some(filename) = file.file_name().to_str() {
104+
if filename.is_ascii() && filename.len() > 65 {
105+
if let Ok(txid) = Txid::from_hex(filename.split_at(64).0) {
106+
if let Ok(index) = filename.split_at(65).1.split('.').next().unwrap().parse() {
107+
if let Ok(contents) = fs::read(&file.path()) {
108+
if let Ok((_, loaded_monitor)) = <(BlockHash, ChannelMonitor<ChanSigner>)>::read(&mut Cursor::new(&contents)) {
109+
res.insert(OutPoint { txid, index }, loaded_monitor);
110+
loaded = true;
111+
}
112+
}
113+
}
114+
}
115+
}
116+
}
117+
if !loaded {
118+
// TODO(val): this should prob error not just print something
119+
println!("WARNING: Failed to read one of the channel monitor storage files! Check perms!");
120+
}
121+
}
122+
Ok(res)
123+
}
124+
}

lightning-net-tokio/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
//! type FeeEstimator = dyn lightning::chain::chaininterface::FeeEstimator;
3636
//! type Logger = dyn lightning::util::logger::Logger;
3737
//! type ChainWatchInterface = dyn lightning::chain::chaininterface::ChainWatchInterface;
38-
//! type ChannelMonitor = lightning::ln::channelmonitor::SimpleManyChannelMonitor<lightning::chain::transaction::OutPoint, lightning::chain::keysinterface::InMemoryChannelKeys, Arc<TxBroadcaster>, Arc<FeeEstimator>, Arc<Logger>, Arc<ChainWatchInterface>>;
38+
//! type DataPersister = dyn lightning::ln::data_persister::ChannelDataPersister<Keys = lightning::chain::keysinterface::InMemoryChannelKeys>;
39+
//! type ChannelMonitor = lightning::ln::channelmonitor::SimpleManyChannelMonitor<lightning::chain::transaction::OutPoint, lightning::chain::keysinterface::InMemoryChannelKeys, Arc<TxBroadcaster>, Arc<FeeEstimator>, Arc<Logger>, Arc<ChainWatchInterface>, Arc<DataPersister>>;
3940
//! type ChannelManager = lightning::ln::channelmanager::SimpleArcChannelManager<ChannelMonitor, TxBroadcaster, FeeEstimator, Logger>;
4041
//! type PeerManager = lightning::ln::peer_handler::SimpleArcPeerManager<lightning_net_tokio::SocketDescriptor, ChannelMonitor, TxBroadcaster, FeeEstimator, ChainWatchInterface, Logger>;
4142
//!

0 commit comments

Comments
 (0)