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

331: Group database key prefixes as enums. #663

Merged
merged 1 commit into from Oct 18, 2022

Conversation

rndhouse
Copy link
Contributor

@rndhouse rndhouse commented Sep 26, 2022

This commit reformulates database key prefixes such that they are grouped into enums.

Currently, key prefixes are listed as separate const values. This formulation makes it difficult to manage key prefixes (import, iterate, test, etc), particularly when it comes to dbdump. This commit simplifies key prefix management using enums and a key prefix trait. It also makes it possible to test and ensure key prefixes are locally unique.

@rndhouse
Copy link
Contributor Author

This PR is currently a proof-of-concept. I've kept changes to a minimum. fedimint-api should build, other components probably wont.

@justinmoon
Copy link
Contributor

The move to enums seems like big improvement. I'm always a little nervous when I add new database keys just because I know there's no guarantee against duplicate key. Will defer to @elsirion who has worked with the database a lot more than I have ...

@rndhouse rndhouse force-pushed the enum_db_key_prefixes branch 2 times, most recently from f86c252 to 31fa71b Compare September 27, 2022 12:44
@@ -25,6 +25,7 @@ rand07 = { version = "0.7", package = "rand" }
secp256k1-zkp = { version = "0.6.0", features = [ "use-serde", "bitcoin_hashes", "global-context" ] }
serde = { version = "1.0.145", features = [ "derive" ] }
serde_json = "1.0.85"
strum = { version = "0.24.1", features = ["derive"] }
Copy link
Contributor

Choose a reason for hiding this comment

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

If I'm seeing it right, this could be a dev-depenendecny? (sorry if I missed something)

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 think you are correct where this draft PR (in its current state) is concerned. But I would like to be able to iterate over enum fields in non-dev where dbdump is concerned, see issue #331.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really need this dep? If it's just for iterating over enum variants we should probably just use a macro that implements this since strum seems to do a lot more.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copying the strum::EnumIter macro from strum would mean adding at least 160 lines of code and a new dependency on syn. I haven't investigated whether it might be possible to do the same thing with less code.

Personally I would lean towards keeping things simple and using strum. I think that the tradeoff of an additional dependency is worth it.

But let me know what the maintainers want and I'll do what's necessary.

const KEY_PREFIX: Self::KeyPrefixType;
}

pub trait DbKeyToBytes: Debug {
Copy link
Contributor

@dpc dpc Sep 27, 2022

Choose a reason for hiding this comment

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

I would name it DBKey. The that it converts "to bytes" is kind of less important (interface detail). The main thing is that "it functions as a database key".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a trait called DatabaseKey defined below. I think that sorting out and simplifying traits should be a separate PR.

I renamed to DbKeyToBytes from DatabaseKeyPrefix in this instance because I needed to commandeer the "prefix" name/concept.

// This trait is implemented for database key prefix enums.
pub trait DbKeyPrefix {
type KeyPrefixType: std::convert::Into<u8>;
const KEY_PREFIX: Self::KeyPrefixType;
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason this can't be just const KEY_PREFIX: u8;?

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 don't think it's possible to convert from enum to u8 given that KEY_PREFIX is const. The error should be:

error[[E0015]](https://doc.rust-lang.org/stable/error-index.html#E0015): cannot call non-const fn `<Prefix as Into<u8>>::into` in constants

Here's a Rust playground showing what I mean:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=da4c4f7c283bd3bfdfa3529b68cb6006

Do you know of a way around this error?

Copy link
Contributor Author

@rndhouse rndhouse Sep 27, 2022

Choose a reason for hiding this comment

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

I suppose each enum could implement its own const fn as_u8(self) -> u8.

But I think that by bounding on trait std::convert::Into<u8> we could retain the enum type for longer without collapsing into the anonymous u8 primitive. Might make debugging/serialization more informative for example.

Copy link
Contributor

Choose a reason for hiding this comment

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

This could be solved in a much easier way imo: Playground

#[repr(u8)]
enum Foo {
    Bar = 0x01,
    Baz = 0x02,
}

const BAZ: u8 = Foo::Baz as u8;

fn main() {
    println!("BAZ = {}", BAZ);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you both, that's very clear code @elsirion , I'll go with that.

@dpc
Copy link
Contributor

dpc commented Sep 27, 2022

BTW. I didn't have opportunity to dive into that code, but I don't understand why db code is so complex .

Seems like the prefix could be just u8 with a wrapper if that's all we plan to support:

#[derive(Copy, Clone, Debug)]
struct DBPrefix(u8);

enum KnownDBPrefix {
  Something,
  SomethingElse,
}

impl DBPrefix {
  const fn as_u8(self) -> u8 { self.0 }
  fn get_standard_prefix_range() -> (u8, u8) {
     // fedimint reserves first 128 ids for own use
    (0, 128)
  }
}

impl From<StandardDBPrefix> for DBPrefix { ... }

// If we need to support prefixes supported by exteran code (like modules).
// They can take care of uniqueness with their own enum.
// Possibly check here and panic if value conficlits with `get_standard_prefix_range`
impl From<u8> for DBPrefix { ... }

and the key just &[u8] - again if that's all we plan to support:

struct DBKey<'a>(Cow<'a, [u8]>);

Cow possibly to handle cases where it needs calculated (and allocated) in flight. Maybe it's unnecessary and we could either use always Vec or always &'a [u8].

with a final:

struct DBPrefixedKey<'a> {
  prefix: DBPrefix,
  key: DBKey<'a>,
}

Or some stuff like this. I guess I just don't see what are all these traits buying us there, while they cost a lot of complexity.

@rndhouse
Copy link
Contributor Author

@dpc I agree with you that there's room to simplify the db traits/code.

I would like to be able to group prefixes so that I can complete #331
Let me know how you'd like to see my grouping method proposal (this PR) improved so that I can get to something you'd be happy to merge.

@dpc
Copy link
Contributor

dpc commented Sep 30, 2022

@rndhouse I think we are going to need @elsirion to tell us what he thinks. :)

elsirion
elsirion previously approved these changes Oct 4, 2022
Copy link
Contributor

@elsirion elsirion left a comment

Choose a reason for hiding this comment

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

I like making the prefix an enum, not sure if all the added type complexity is necessary, left an alternative idea (idk if it works, only built a prototype). If not I'm fine with this version.

@@ -25,6 +25,7 @@ rand07 = { version = "0.7", package = "rand" }
secp256k1-zkp = { version = "0.6.0", features = [ "use-serde", "bitcoin_hashes", "global-context" ] }
serde = { version = "1.0.145", features = [ "derive" ] }
serde_json = "1.0.85"
strum = { version = "0.24.1", features = ["derive"] }
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really need this dep? If it's just for iterating over enum variants we should probably just use a macro that implements this since strum seems to do a lot more.

@@ -18,15 +18,15 @@ pub enum BatchItem {
/// Insets new element, even if it already exists
InsertElement(Element),
/// Deletes element, errors if it doesn't exist
DeleteElement(Box<dyn DatabaseKeyPrefix + Send>),
DeleteElement(Box<dyn DbKeyToBytes + Send>),
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please try to separate logic changes from renaming in the future (different commits in the same PR)? It's hard to focus on the relevant parts if it's mixed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense to me, I'll aim to do that next time.

// This trait is implemented for database key prefix enums.
pub trait DbKeyPrefix {
type KeyPrefixType: std::convert::Into<u8>;
const KEY_PREFIX: Self::KeyPrefixType;
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be solved in a much easier way imo: Playground

#[repr(u8)]
enum Foo {
    Bar = 0x01,
    Baz = 0x02,
}

const BAZ: u8 = Foo::Baz as u8;

fn main() {
    println!("BAZ = {}", BAZ);
}

Comment on lines 223 to 239
// Given a database key prefix enum iterator, this function ensures that its prefix values
// are unique within the enum scope.
pub fn ensure_unique_key_prefixes<T: std::iter::Iterator + Clone>(db_key_prefix_iter: T)
where
<T as Iterator>::Item: std::convert::Into<u8>,
{
let prefixes: std::collections::BTreeSet<u8> = db_key_prefix_iter
.clone()
.map(|prefix| prefix.into())
.collect();

let result = prefixes.len();
let expected = db_key_prefix_iter.count();

assert_eq!(result, expected);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

This approach would automatically prevent duplicate prefixes.

@rndhouse
Copy link
Contributor Author

rndhouse commented Oct 4, 2022

@elsirion @dpc I've force pushed a new commit which doesn't modify traits and implements enums throughout the codebase.

@codecov-commenter
Copy link

codecov-commenter commented Oct 9, 2022

Codecov Report

Base: 59.38% // Head: 59.24% // Decreases project coverage by -0.13% ⚠️

Coverage data is based on head (4c8d483) compared to base (0cf81c5).
Patch coverage: 0.00% of modified lines in pull request are covered.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #663      +/-   ##
==========================================
- Coverage   59.38%   59.24%   -0.14%     
==========================================
  Files         101      101              
  Lines       16259    16267       +8     
==========================================
- Hits         9656     9638      -18     
- Misses       6603     6629      +26     
Impacted Files Coverage Δ
client/client-lib/src/ln/db.rs 60.00% <0.00%> (-6.67%) ⬇️
client/client-lib/src/mint/db.rs 85.71% <0.00%> (-14.29%) ⬇️
client/client-lib/src/wallet/db.rs 33.33% <0.00%> (-16.67%) ⬇️
fedimint-api/src/db/mod.rs 80.85% <0.00%> (-0.29%) ⬇️
fedimint-server/src/db.rs 88.88% <0.00%> (-11.12%) ⬇️
modules/fedimint-ln/src/db.rs 91.66% <0.00%> (-8.34%) ⬇️
modules/fedimint-mint/src/db.rs 90.00% <0.00%> (-10.00%) ⬇️
modules/fedimint-wallet/src/db.rs 91.66% <0.00%> (-8.34%) ⬇️
fedimint-server/src/lib.rs 86.08% <0.00%> (-1.47%) ⬇️
core/api/src/server.rs 39.04% <0.00%> (-1.22%) ⬇️
... and 4 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@rndhouse
Copy link
Contributor Author

rndhouse commented Oct 9, 2022

Rebased and the checks all passed.

@rndhouse rndhouse marked this pull request as ready for review October 9, 2022 16:04
@jkitman
Copy link
Contributor

jkitman commented Oct 9, 2022

It also makes it possible to test and ensure key prefixes are locally unique.

Is it possible to add that assertion somewhere, such as during server start?

@rndhouse
Copy link
Contributor Author

rndhouse commented Oct 9, 2022

@jkitman The description that you've quoted is outdated. The latest commit does not include additional test. elsirion's formulation does not require tests for local (within an enum) uniqueness.

Not sure about testing that each prefix is globally unique. A dbdump integration test could make that global assertion.

NicolaLS
NicolaLS previously approved these changes Oct 13, 2022
Copy link
Contributor

@NicolaLS NicolaLS left a comment

Choose a reason for hiding this comment

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

lgtm! but I would remove the strum dependency for now and introduce it in another PR when you actually want to use it.

@rndhouse
Copy link
Contributor Author

@NicolaLS I've removed strum from the commit as you've suggested.

@elsirion I think this should be ready to merge now. The failing CI test is unrelated (as far as I can tell).

This commit reformulates database key prefixes such that they are grouped into enums.

Currently, key prefixes are listed as separate const values. This formulation makes it difficult to manage key prefixes (import, iterate, test, etc), which is particularly relevant for dbdump. This commit simplifies key prefix management using enums. Using enums ensures that key prefixes are locally unique.
@elsirion elsirion merged commit ae91bb5 into fedimint:master Oct 18, 2022
@elsirion
Copy link
Contributor

Awesome, the final result is a really clean PR!

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.

None yet

7 participants