-
Notifications
You must be signed in to change notification settings - Fork 421
add KVStorePersister trait and blanket implementations of Persist and Persister for any type that implements KVStorePersister
#1417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add KVStorePersister trait and blanket implementations of Persist and Persister for any type that implements KVStorePersister
#1417
Conversation
TheBlueMatt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK.
lightning-persister/src/lib.rs
Outdated
| use std::ops::Deref; | ||
| use std::path::{Path, PathBuf}; | ||
|
|
||
| impl DiskWriteable for Vec<u8> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would strongly prefer to not encode into a vec and then dump, instead we can just define persist_bytes as generic over a DiskWriteable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, but then it's only intended to be used by a FilesystemPersister. Part of the motivation for this is to make it easier to implement a generic persistence layer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean we can also make the DiskWriteable trait public and define it in terms of a Writer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or, really, we could drop DiskWriteable entirely and use the lightning crate's existing Writeable trait.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice. removed DiskWriteable and was able to use Writeable everywhere instead and avoid the extra encode()
lightning-persister/src/util.rs
Outdated
| pub(crate) fn write_to_file<D: DiskWriteable>(full_filepath: String, data: &D) -> std::io::Result<()> { | ||
| let full_filepath = PathBuf::from(full_filepath); | ||
| let path = full_filepath.parent().unwrap(); | ||
| let filename = full_filepath.file_name().unwrap().to_str().unwrap().to_string(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bleh. Can we not at least leave this an str or maybe split on a / manually instead of going through PathBuf? Are there any platforms that use '' for paths anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed need for PathBuf here -- the original implementation took in a PathBuf which is why I tried to 'put it back'
lightning-persister/src/lib.rs
Outdated
| let path = PathBuf::from(data_dir); | ||
| util::write_to_file(path, "network_graph".to_string(), network_graph) | ||
| pub fn path_to_monitor_data_str(&self) -> &str { | ||
| self.path_to_monitor_data().to_str().unwrap() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof. Let's not keep converting PathBuf->str->String->PathBuf->.... quite a waste of allocations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, well if we are okay with building path using "/" then we don't need to do this I guess? In the current code the method to get monitors path returned a PathBuf but we need a String key for the new trait. Only way to avoid this is to return a String for the monitors path using "/"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I guess Winblowz still uses "".
75900b7 to
324fbd5
Compare
lightning-persister/src/lib.rs
Outdated
| } | ||
|
|
||
| /// Trait for a key-value store for persisting some writeable object at some key | ||
| pub trait PersistObject<W: Writeable> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe lets call this KVStorePersister?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, sounds good to me
lightning-persister/src/lib.rs
Outdated
| let path = PathBuf::from(data_dir); | ||
| util::write_to_file(path, "network_graph".to_string(), network_graph) | ||
| pub fn path_to_monitor_data_str(&self) -> &str { | ||
| self.path_to_monitor_data().to_str().unwrap() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I guess Winblowz still uses "".
lightning-persister/src/util.rs
Outdated
| pub(crate) fn write_to_file<D: DiskWriteable>(path: PathBuf, filename: String, data: &D) -> std::io::Result<()> { | ||
| fs::create_dir_all(path.clone())?; | ||
| pub(crate) fn write_to_file<W: Writeable>(filename_with_path: String, data: &W) -> std::io::Result<()> { | ||
| let mut path_parts: Vec<&str> = filename_with_path.split("/").collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we right-split and not collect into a Vec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm well if we split with char '/' it returns DoubleEndedIterator so don't need rsplit.
but if we're not okay with assuming '/' as separator then we can't do this anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tried doing this by folding the iterator into a String but I'm not really sure if it's better than just collecting and joining
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh looks like you can just collect straight into a PathBuf (which is what we want anyway) and avoids using the '/'. Solves both problems at once? at least for here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
er I guess we're still splitting on "/" so doesn't entirely avoid it.
I'm not really sure what to do with this PathBuf <--> String issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's annoying because on both sides of the trait we want PathBuf but the trait is defined over a String key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ended up keeping everything String's and building paths manually with format!() and std::path::MAIN_SEPARATOR. Not sure if this is the best way though.
Finally convert to PathBuf inside of util::write_to_file when needed.
6fa6a98 to
95fc538
Compare
Persist and Persister on FilesystemPersister with KVStorePersister trait
TheBlueMatt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shaping up nicely, I think.
| L::Target: 'static + Logger, | ||
| { | ||
| /// Persist the given [`ChannelManager`] to disk, returning an error if persistence failed | ||
| /// (which will cause the [`BackgroundProcessor`] which called this method to exit). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should move this line somewhere else in the BP docs, I'd think, not move it to the lightning-persister crate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm the BP docs already also have this:
/// [Persister::persist_manager] is responsible for writing out the [ChannelManager] to disk, and/or
/// uploading to one or more backup services. See [ChannelManager::write] for writing out a
/// [ChannelManager]. See [FilesystemPersister::persist_manager] for Rust-Lightning's
/// provided implementation.
could just add the bit about failure causing BP to exit. also should update the bit here about the provided implementation.
lightning-persister/src/lib.rs
Outdated
| } | ||
|
|
||
| /// Trait for a key-value store for persisting some writeable object at some key | ||
| pub trait KVStorePersister<W: Writeable> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the W be on the persist method? A single KVStorePersister should probably care about being able to persist more than one type of W.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed, moved it
lightning-persister/src/lib.rs
Outdated
| /// Persist the given [`ChannelManager`] to disk, returning an error if persistence failed | ||
| /// (which will cause the BackgroundProcessor which called this method to exit). | ||
| fn persist_manager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(&self, channel_manager: &ChannelManager<Signer, M, T, K, F, L>) -> Result<(), std::io::Error> where | ||
| M::Target: 'static + chain::Watch<Signer>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, what is the error if you keep the types on the Persister trait? It does seem like a single instance of a Persister should really only care about persisting one type of ChannelManager, not multiple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error is on the test struct Persister that is used in the BackgroundProcessor tests:
error[E0284]: type annotations needed
--> lightning-background-processor/src/lib.rs:440:39
|
440 | None => self.filesystem_persister.persist_graph(network_graph),
| ^^^^^^^^^^^^^ cannot infer type for type parameter `M` declared on the trait `Persister`
|
= note: cannot satisfy `<_ as Deref>::Target == _`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Being explicit solves the issue for me -
LPPersister::<Signer, M, T, K, F, L>::persist_graph(&self.filesystem_persister, network_graph)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ended up just removing this whole section of code since it's unnecessary to explicitly impl Persister anymore. Just implemented KVStorePersister here and handled throwing errors based on the key
lightning-persister/src/util.rs
Outdated
| pub(crate) fn write_to_file<D: DiskWriteable>(path: PathBuf, filename: String, data: &D) -> std::io::Result<()> { | ||
| fs::create_dir_all(path.clone())?; | ||
| pub(crate) fn write_to_file<W: Writeable>(filename_with_path: String, data: &W) -> std::io::Result<()> { | ||
| let full_path = PathBuf::from(&filename_with_path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you move the tmp_filename creation to above here we can cut an allocation by dropping the reference here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drop & as per above?
lightning-persister/src/util.rs
Outdated
| // open(tmpname), write(tmpfile), fsync(tmpfile), close(tmpfile), rename(), fsync(dir) | ||
| let filename_with_path = get_full_filepath(path, filename); | ||
| let tmp_filename = format!("{}.tmp", filename_with_path.clone()); | ||
| let tmp_filename = format!("{}.tmp", filename_with_path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not your bug, but lets drop format and instead clone the filename_with_path and push_str the ".tmp" string.
lightning-persister/src/lib.rs
Outdated
|
|
||
| impl<W: Writeable> KVStorePersister<W> for FilesystemPersister { | ||
| fn persist(&self, key: String, object: &W) -> std::io::Result<()> { | ||
| util::write_to_file(format!("{}{}{}", self.path_to_channel_data, MAIN_SEPARATOR, key), object) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, right, I forgot we build the key first and then want to push a prefix onto it...means we can't avoid a second allocation here.
lightning-persister/src/lib.rs
Outdated
| fn persist_new_channel(&self, funding_txo: OutPoint, monitor: &ChannelMonitor<ChannelSigner>, _update_id: chainmonitor::MonitorUpdateId) -> Result<(), chain::ChannelMonitorUpdateErr> { | ||
| let filename = format!("{}_{}", funding_txo.txid.to_hex(), funding_txo.index); | ||
| util::write_to_file(self.path_to_monitor_data(), filename, monitor) | ||
| let key = format!("monitors{}{}_{}", MAIN_SEPARATOR, funding_txo.txid.to_hex(), funding_txo.index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking lets not boterh directly implementing Persist or Persister at all for the FilesystemPersister, instead opting to implement them automatically for any KVStorePersister.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah that's a good idea. only downside I can think of is that it removes control of the keys used from an implementor. I guess they could always override the implementation? I'm not sure how/if that would work though
4314915 to
cb6c7c6
Compare
Codecov Report
@@ Coverage Diff @@
## main #1417 +/- ##
==========================================
+ Coverage 90.84% 91.42% +0.58%
==========================================
Files 74 75 +1
Lines 41288 44329 +3041
Branches 41288 44329 +3041
==========================================
+ Hits 37507 40528 +3021
- Misses 3781 3801 +20
Continue to review full report at Codecov.
|
a66abe9 to
6600143
Compare
Persist and Persister on FilesystemPersister with KVStorePersister traitKVStorePersister trait and blanket implementations of Persist and Persister for any type that implements KVStorePersister
TheBlueMatt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK! This is looking good now. I have a few further comments on the KVStore trait but I think we're basically there.
| /// The thread runs indefinitely unless the object is dropped, [`stop`] is called, or | ||
| /// [`Persister::persist_manager`] returns an error. In case of an error, the error is retrieved by calling | ||
| /// either [`join`] or [`stop`]. | ||
| /// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: spurious line addition?
lightning/src/util/persist.rs
Outdated
|
|
||
| impl<A: KVStorePersister, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Persister<Signer, M, T, K, F, L> for A | ||
| where | ||
| M::Target: 'static + chain::Watch<Signer>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: generally we indent the conditions here.
lightning/src/util/persist.rs
Outdated
| } | ||
|
|
||
| impl<A: KVStorePersister, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Persister<Signer, M, T, K, F, L> for A | ||
| where |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: spurious whitespace at EOL here and a few other places. I'd generally suggest glancing with a local git show which should highlight such whitespace depending on your terminal settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not just use rustfmt for the project and run on pre-commit or whatever?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly rustfmt tends to generate garbage code formatting IMO, doubly so on our codebase. #410 (comment)
| } | ||
|
|
||
| impl<ChannelSigner: Sign, K: KVStorePersister> Persist<ChannelSigner> for K { | ||
| // TODO: We really need a way for the persister to inform the user that its time to crash/shut |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets open an issue for this? Using the Event::ChannelClosed event may make sense here, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lightning/src/util/persist.rs
Outdated
| // You may not use this file except in accordance with one or both of these | ||
| // licenses. | ||
|
|
||
| //! Traits for handling persistence |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we expand on this somewhat? Lets say something about how we expose a simple KVStore trait which can be used to implement both a graph + manager persistence and chain::Persist all in one simple implementation.
lightning/src/util/persist.rs
Outdated
| /// Persist the given writeable using the provided key | ||
| fn persist<W: Writeable>(&self, key: String, object: &W) -> io::Result<()>; | ||
| /// Get the key for persisting a channel manager | ||
| fn get_channel_manager_key(&self) -> String { String::from("manager") } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmm, do users have a good reason to override this? ISTM its less work to just implement Persister if they want to bother with overriding the key. We might as well just remove the key-fetch functions and make them a part of the trait definition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not entirely clear if there's a good reason to override yet but it might be useful to know what the keys are when implementing KVStorePersister persist. For example you might want to ignore errors when persisting the network_graph and just print a warning to console. To do that you'd need to be able to know the key being used by persist_graph inside of persist.
Without this you'd have to just hardcode the string in your persist implementation.
When you say "make them part of the train definition" do you mean just hardcode the strings into the implementation of Persister/Persist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed these for now
lightning/src/util/persist.rs
Outdated
| /// Trait for a key-value store for persisting some writeable object at some key | ||
| pub trait KVStorePersister { | ||
| /// Persist the given writeable using the provided key | ||
| fn persist<W: Writeable>(&self, key: String, object: &W) -> io::Result<()>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: given the keys for several things are static, lets just make it a &str instead of a String.
6600143 to
11fedb6
Compare
|
Looks like this needs rebase now, sorry about that. |
d975106 to
6299afc
Compare
|
think I addressed all your comments, rebased, and have all checks passing. would love another pass when you get a chance, think this is pretty much ready at this point |
TheBlueMatt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few trivial comments but I'm happy with this.
lightning-persister/src/util.rs
Outdated
| // open(tmpname), write(tmpfile), fsync(tmpfile), close(tmpfile), rename(), fsync(dir) | ||
| let filename_with_path = get_full_filepath(path, filename); | ||
| let tmp_filename = format!("{}.tmp", filename_with_path.clone()); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: spurious newline (also EOL whitespace :) )
| use crate::{chain::{keysinterface::{Sign, KeysInterface}, self, transaction::{OutPoint}, chaininterface::{BroadcasterInterface, FeeEstimator}, chainmonitor::{Persist, MonitorUpdateId}, channelmonitor::{ChannelMonitor, ChannelMonitorUpdate}}, ln::channelmanager::ChannelManager, routing::network_graph::NetworkGraph}; | ||
| use super::{logger::Logger, ser::Writeable}; | ||
|
|
||
| /// Trait for a key-value store for persisting some writeable object at some key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we rely on the specific paths downstream, both in FilesystemPersister::read_channelmanager and even downstream in the sample node (:sob:), we should document the paths here. Ideally we'd add a read method on KVStorePersister and then implement the loads here against that, but I don't think we need to do that in this specific PR given its not a new issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, that's why I had at first added the key getters.
I actually already implemented it in Sensei because it seemed like you didn't want in ldk. I added a read and a list method since read_channelmonitors needs to be able to list all keys/filenames.
Here's what I did, let me know if you think it's roughly what you'd want to add to KVStorePersister for #1427
https://github.com/L2-Technology/sensei/pull/37/files#diff-af422d8a9ab5e8778884033d55578f060f9d14dde012232f4faeb2b8a49cec92R12
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I'd prefer we keep the keys as part of the util::persist module and abstract it away from the user instead of exposing them. That would imply, like you say, adding a read and list method in a followup and having utils to read the data. That is also important so that we can change the persistence behavior in the future, eg for #1426.
| /// [`ChannelManager::write`]: lightning::ln::channelmanager::ChannelManager#impl-Writeable | ||
| /// [`FilesystemPersister::persist_manager`]: lightning_persister::FilesystemPersister::persist_manager | ||
| /// [`FilesystemPersister::persist_network_graph`]: lightning_persister::FilesystemPersister::persist_network_graph | ||
| /// [`FilesystemPersister::persist_manager`]: lightning_persister::FilesystemPersister#impl-Persister |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this link doesn't work now, it seems. I think maybe just link directly to Persister::persist_*
lightning-persister/src/util.rs
Outdated
| use super::{DiskWriteable, get_full_filepath, write_to_file}; | ||
| use lightning::util::ser::{Writer, Writeable}; | ||
|
|
||
| use super::{write_to_file}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: indentation.
6299afc to
54914cd
Compare
TheBlueMatt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically LGTM. Needs a second review.
| /// [`Persister::persist_manager`] is responsible for writing out the [`ChannelManager`] to disk, and/or | ||
| /// uploading to one or more backup services. See [`ChannelManager::write`] for writing out a | ||
| /// [`ChannelManager`]. See [`FilesystemPersister::persist_manager`] for Rust-Lightning's | ||
| /// [`ChannelManager`]. See [`Persister::persist_manager`] for Rust-Lightning's |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I think I was sleepwalking, sorry. Seems strange to link to the trait for "Rust-Lightning's provided implementation", maybe lets just link directly to FilesystemPersister with no specific method link? Also, let's call it "LDK's provided implementation" now :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, linking to FilesystemPersister is also kind of strange because they won't find a persist_manager or persist_graph implementation there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also i don't think FilesystemPersister is used in BP anymore and so it complains about no item in scope -- should I just import it only to be used in the comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right, we should remove it as a dependency (I think it can be a dev-dependency now, no?), then. We can also just say "see the lightning-persister crate", no? I don't think its an issue that we don't link to a specific function, given there's an implementation for the trait mentioned in the previous sentence.
lightning/src/util/persist.rs
Outdated
| // You may not use this file except in accordance with one or both of these | ||
| // licenses. | ||
|
|
||
| //! Expose a simple key-value store trait KVStorePersister that allows one to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Expose/This module contains/
lightning/src/util/persist.rs
Outdated
| // licenses. | ||
|
|
||
| //! Expose a simple key-value store trait KVStorePersister that allows one to | ||
| //! implement the persistence for ChannelManager, NetworkGraph, and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: here and a few other places in this file you have whitespace at EOL. %s/ $//g should fix it :)
lightning/src/util/persist.rs
Outdated
|
|
||
| /// Trait for a key-value store for persisting some writeable object at some key | ||
| /// Implementing `KVStorePersister` provides auto-implementations for `Persister` | ||
| /// and `Persist` traits. It uses "manager", "network_graph", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, lets link to the traits with []s.
70095c5 to
960191e
Compare
|
hopefully this is good-to-go at this point pending another review? |
| use lightning_invoice::utils::DefaultRouter; | ||
| use lightning_persister::FilesystemPersister; | ||
| use std::fs; | ||
| use lightning_persister::{FilesystemPersister}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove braces
| graph_error: Option<(std::io::ErrorKind, &'static str)>, | ||
| manager_error: Option<(std::io::ErrorKind, &'static str)> | ||
| manager_error: Option<(std::io::ErrorKind, &'static str)>, | ||
| filesystem_persister: FilesystemPersister |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I realize this wasn't there before, but cold you add a trailing comma?
lightning-persister/src/lib.rs
Outdated
| let path = PathBuf::from(data_dir); | ||
| util::write_to_file(path, "network_graph".to_string(), network_graph) | ||
| pub(crate) fn path_to_monitor_data(&self) -> String { | ||
| format!("{}{}monitors", self.path_to_channel_data, MAIN_SEPARATOR) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove extra tab
lightning-persister/src/lib.rs
Outdated
| let path = PathBuf::from(data_dir); | ||
| util::write_to_file(path, "network_graph".to_string(), network_graph) | ||
| pub(crate) fn path_to_monitor_data(&self) -> String { | ||
| format!("{}{}monitors", self.path_to_channel_data, MAIN_SEPARATOR) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this is used only once, could we instead inline it at the call site where it already uses PathBuf. That would prevent double separators, IIUC. Not sure how we care about that, but no need for the method at very least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that makes sense to me. though in #1427 we will be using string keys to list monitors dir and read monitors so we might run into the string => PathBuf => string conversion fun again.
lightning-persister/src/util.rs
Outdated
| pub(crate) fn write_to_file<D: DiskWriteable>(path: PathBuf, filename: String, data: &D) -> std::io::Result<()> { | ||
| fs::create_dir_all(path.clone())?; | ||
| pub(crate) fn write_to_file<W: Writeable>(filename_with_path: String, data: &W) -> std::io::Result<()> { | ||
| let full_path = PathBuf::from(&filename_with_path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drop & as per above?
lightning/src/util/persist.rs
Outdated
| //! This module contains a simple key-value store trait KVStorePersister that | ||
| //! allows one to implement the persistence for ChannelManager, NetworkGraph, | ||
| //! and ChannelMonitor all in one place. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you tick and bracket the types so they are linked in the docs?
lightning-persister/src/lib.rs
Outdated
| { | ||
| let path = self.path_to_monitor_data(); | ||
| if !Path::new(&path).exists() { | ||
| if !Path::new(&PathBuf::from(&path)).exists() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise with dropping & on path.
lightning-persister/src/util.rs
Outdated
| #[allow(bare_trait_objects)] | ||
| pub(crate) fn write_to_file<D: DiskWriteable>(path: PathBuf, filename: String, data: &D) -> std::io::Result<()> { | ||
| fs::create_dir_all(path.clone())?; | ||
| pub(crate) fn write_to_file<W: Writeable>(filename_with_path: String, data: &W) -> std::io::Result<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand why PathBuf is avoided so much. Can't we simply change the the type of filename_with_path to PathBuf and never convert it to String? Seems we are allocating by using format!, which has the double separator drawback, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that the KVStorePersister trait uses a String key instead of a PathBuf because it's intended to be used for more than filesystem persistence. KVStorePersister is the caller of write_to_file so it only has a String key.
I think the idea was that if we are going to need to convert the PathBuf to a String in order to persist via KVStorePersister then we might as well avoid building the PathBuf in the first place.
As for this method, I also just realized we were recreating the PathBuf and cloning filename_from_path down where we do the rename/copy from tmp to final. I removed it and just re-used the PathBuf we create at the top.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that the KVStorePersister trait uses a
Stringkey instead of aPathBufbecause it's intended to be used for more than filesystem persistence. KVStorePersister is the caller ofwrite_to_fileso it only has aStringkey.
Doesn't PathBuf::push just store a reference? Its parameter is P: AsRef<Path>, where str and String's implementations call Path::new, which is described as "a cost-free conversion", and where there is a blanket implementation for reference types.
I think the idea was that if we are going to need to convert the PathBuf to a String in order to persist via KVStorePersister then we might as well avoid building the PathBuf in the first place.
Not sure I follow why FilesystemPersister::persist's implementation of KVStorePersister would need to convert to a string. It's given a string and from there it can cheaply wrap it in a PathBuf and never need to convert back to a string if write_to_file is changed to take a PathBuf.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it's not the implementation of KVStorePersister that has the conversion issue (going from String to PathBuf is free I think) it's the usage of persist. On main it uses PathBuf's for building up the paths for manager,graph,monitors but persist needs the paths as &str so originally this branch was taking the PathBuf's and converting to str
in the next iteration I removed the usage of PathBuf for building up the paths in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but yeah now the blanket implementation is using "/" which is a problem. I think either we use the MAIN_SEPARATOR thing there or maybe your suggestion of keys being &[&str] would work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it's not the implementation of KVStorePersister that has the conversion issue (going from String to PathBuf is free I think) it's the usage of
persist. Onmainit uses PathBuf's for building up the paths for manager,graph,monitors butpersistneeds the paths as &str so originally this branch was taking the PathBuf's and converting to str
Do you mean for FilesystemPersister::path_to_channel_data? Isn't that a one-time occurrence? persist takes the key, yes, but it's the FilesystemPersister implementation that tacks on path_to_channel_data as the path prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm I was just trying to explain how we got here, since yeah the original provided implementation was only part of FilesystemPersister and it used PathBuf for everything (manager, graph, monitor).
At this point this PR is such a mess it's getting hard to follow for me. I was mostly following matts comments that led us here. Can we maybe just sync on this on slack real quick to make a final call so we can just get this over the finish line?
9a1a978 to
a857771
Compare
| let key = format!("monitors/{}_{}", funding_txo.txid.to_hex(), funding_txo.index); | ||
| self.persist(&key, monitor) | ||
| .map_err(|_| chain::ChannelMonitorUpdateErr::PermanentFailure) | ||
| } | ||
|
|
||
| fn update_persisted_channel(&self, funding_txo: OutPoint, _update: &Option<ChannelMonitorUpdate>, monitor: &ChannelMonitor<ChannelSigner>, _update_id: MonitorUpdateId) -> Result<(), chain::ChannelMonitorUpdateErr> { | ||
| let key = format!("monitors/{}_{}", funding_txo.txid.to_hex(), funding_txo.index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use the separator constant here? Or Maybe key should be &[&str] to avoid having filesystem concepts in a blanket implementation?
lightning-persister/src/util.rs
Outdated
| #[allow(bare_trait_objects)] | ||
| pub(crate) fn write_to_file<D: DiskWriteable>(path: PathBuf, filename: String, data: &D) -> std::io::Result<()> { | ||
| fs::create_dir_all(path.clone())?; | ||
| pub(crate) fn write_to_file<W: Writeable>(filename_with_path: String, data: &W) -> std::io::Result<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that the KVStorePersister trait uses a
Stringkey instead of aPathBufbecause it's intended to be used for more than filesystem persistence. KVStorePersister is the caller ofwrite_to_fileso it only has aStringkey.
Doesn't PathBuf::push just store a reference? Its parameter is P: AsRef<Path>, where str and String's implementations call Path::new, which is described as "a cost-free conversion", and where there is a blanket implementation for reference types.
I think the idea was that if we are going to need to convert the PathBuf to a String in order to persist via KVStorePersister then we might as well avoid building the PathBuf in the first place.
Not sure I follow why FilesystemPersister::persist's implementation of KVStorePersister would need to convert to a string. It's given a string and from there it can cheaply wrap it in a PathBuf and never need to convert back to a string if write_to_file is changed to take a PathBuf.
lightning-persister/src/util.rs
Outdated
| let mut tmp_filename = filename_with_path.clone(); | ||
| tmp_filename.push_str(".tmp"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, PathBuf has a set_extension method, which avoid needing the above clone IIUC. Essentially, you could make one PathBuf for the final path, clone it, and set the temp extension on the clone.
a857771 to
7e9d008
Compare
jkczyz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Largely looks good. Just some minor comments.
lightning-persister/src/lib.rs
Outdated
| use std::io::Cursor; | ||
| use std::ops::Deref; | ||
| use std::path::{Path, PathBuf}; | ||
| use std::path::{Path, PathBuf, MAIN_SEPARATOR}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no longer needed
| let mut dest_file = PathBuf::from(self.path_to_channel_data.clone()); | ||
| dest_file.push(key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we allocate using PathBuf::with_capacity using the size of the two strings and then push a reference to self.path_to_channel_data and key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like this?
let mut dest_file = PathBuf::with_capacity(self.path_to_channel_data.len() + key.len());
dest_file.push(&self.path_to_channel_data);
dest_file.push(key);
7e9d008 to
08a84e9
Compare
08a84e9 to
4964944
Compare
Fix several "unused" warnings introduced in #1417
KVStorePersistertrait for persistingWriteableobjects with a certainStringkey.PersistandPersisterimplementations for any type that implementsKVStorePersisterKVStorePersisterforFilesystemPersisterusingutil::write_to_fileto persist to filesystem.Persistertrait from lightning-background-processor to newlightning::util::persistermoduleStringto build paths inFilesystemPersisterto avoid needing to go fromPathBuftoString(to use as key in KVStorePersister).