Skip to content
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

Generalize client storage from the filesystem #2194

Merged
merged 11 commits into from
Jul 3, 2024

Conversation

Twey
Copy link
Contributor

@Twey Twey commented Jun 26, 2024

Motivation

The browser doesn't have access to a filesystem. In order to store and load wallet configuration, we must use a different backend, such as localStorage. Currently, though, the linera-service crate assumes filesystem access, and stores configuration as JSON on the filesystem.

Proposal

Generalize the WalletState struct with a new trait that represents any data that can be persisted. Move the previous behaviour into an implementation of that trait specialized to filesystem storage.

Test Plan

CI.

Release Plan

None required.

Links

@Twey Twey changed the title Persistent trait Generalize client storage from the filesystem Jun 26, 2024
@Twey Twey force-pushed the 06-26-Persistent_trait branch 2 times, most recently from 0bb2f91 to ce83b06 Compare July 1, 2024 12:59
@Twey Twey marked this pull request as ready for review July 1, 2024 15:42
@ma2bd ma2bd requested review from ma2bd, afck and jvff July 1, 2024 20:20
Copy link
Contributor

@ma2bd ma2bd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Letting @jvff or @afck accept

linera-service/src/persistent/file.rs Outdated Show resolved Hide resolved
}
}

pub struct File<T> {
Copy link
Contributor

@ma2bd ma2bd Jul 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The name File is a bit counter-intuitive: afaict this is a value persisted on file. Maybe OnFile<T> is a step in the right direction?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another approach (which was highly debated before but perhaps ok here) is to use the module name for the distinction between different implementations. Something like persistence::file_system::Value implementing the trait persistence::(Persistent)Value.

Copy link
Contributor Author

@Twey Twey Jul 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a wrapper over the type (as indicated by the Deref impl), so I read the name persistent::File<Wallet> as ‘a persistent file containing a wallet’. The grammar of this name is hurt a bit with the rename to persistence, as well as file_system (a persistence::file_system::Value<Wallet> is not a persistence file system value containing a wallet). I agree that File should never be used unqualified!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if you swap the storage medium, it will be called persistent::Web<Wallet> or persistent::Network<Wallet> ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like persistent::LocalStorageEntry<Wallet> or persistent::S3Bucket<Wallet>, yes (the name refers to the entry that stores the value, not the medium the entry lives on).


pub use file::File;

pub trait Persistent: Deref {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use some short (oneline) comment on public APIs.

Copy link
Contributor

@ma2bd ma2bd Jul 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: perhaps PersistentValue or Value (I don't mean to open a big debate though :))

Copy link
Contributor Author

@Twey Twey Jul 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on the docs, good catch :) Though technically nothing in linera-service is ‘public’ as it can't be imported as a library, that will change in a little bit when this goes into linera-client :)

On the name — there's no clear consensus on this in Rust, but I prefer adjectives for trait names rather than nouns (as it avoids name clash with implementers, especially generic implementers). The competing convention is verbs, so this could be named Persist; happy to switch to that if we think it's better. If we want a noun then it should be agentive/patientive, i.e. Persistable, but suffixes like -able are specifically recommended against by the guidelines. I think Value is an essentially meaningless type name — anything nameable by a type is a value, so including the word ‘value’ in a type or trait name never adds information.

Copy link
Contributor Author

@Twey Twey Jul 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact I think I'm convinced that Persist is more in line with Rust (std::io::{Read,Write}, et cetera) — I'll rewrite to that unless you have objections.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the docs and renamed Persistent to Persist.

Twey added 3 commits July 2, 2024 11:15
This is more in line both with Rust standard library traits, which
tend to be transitive verbs, and also with the Rust trait naming
conventions RFC rust-lang/rfcs#344, which states:

> Prefer (transitive) verbs, nouns, and then adjectives; avoid
> grammatical suffixes (like able).
@Twey Twey requested a review from ma2bd July 2, 2024 13:07
Copy link
Contributor

@afck afck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love it! Not just more general, but also cleaner! 👍

let genesis_config = GenesisConfig::read(&self.genesis_config_path)
.expect("Fail to read initial chain config");
let genesis_config: GenesisConfig =
util::read_json(&self.genesis_config_path).expect("Fail to read initial chain config");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recommended style would be:

Suggested change
util::read_json(&self.genesis_config_path).expect("Fail to read initial chain config");
util::read_json(&self.genesis_config_path).expect("should read initial chain config");

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Also, I think @ma2bd prefers explicit generic arguments over type annotations, i.e. read_json::<GenesisConfig> over genesis_config: GenesisConfig?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think that's the letter of the recommendation but not the spirit of it 😅 The message in expect is supposed to convey the invariant that we expect to lead to the Result being Ok. Probably this shouldn't be expect at all, but return the error up for anyhow.

Copy link
Contributor Author

@Twey Twey Jul 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the turbofish — I don't see any official advice, but most Rustaceans (including the book) seem to prefer annotating the variable. It's less noisy and provides better (i.e. more localized) feedback when the type and value don't match. The turbofish is variously described as ‘a hack’ or ‘a wart’ that is unfortunately needed to keep the ability to provide generic arguments in some corner cases, but acceptable only because of how rarely it's encountered.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a lot of stuff to do so I think we can agree on some tolerance on this particular point for now. In my view, the problem with type annotations in the let is that they tend to stick around even after the reason for adding them has disappeared.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, I was blocked on the tests flaking out, not this discussion :) It was clearly marked as a nit. IMO if it's not obvious whether a type annotation is needed or not, then it's nice to have it there for clarity anyway. But the same can be said of the turbofish — quite remote changes in the function, or even the signatures of other items that the function depends on, can obviate a turbofish without anybody noticing.

linera-service/src/persistent/mod.rs Outdated Show resolved Hide resolved
linera-service/src/persistent/file.rs Show resolved Hide resolved
linera-service/src/persistent/file.rs Outdated Show resolved Hide resolved
linera-service/src/linera/main.rs Show resolved Hide resolved
@Twey Twey merged commit e27a55c into linera-io:main Jul 3, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants