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
Conversation
a9b7631
to
205105e
Compare
This PR is currently a proof-of-concept. I've kept changes to a minimum. |
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 ... |
f86c252
to
31fa71b
Compare
fedimint-api/Cargo.toml
Outdated
@@ -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"] } |
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 I'm seeing it right, this could be a dev-depenendecny? (sorry if I missed something)
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 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.
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.
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.
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.
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.
fedimint-api/src/db/mod.rs
Outdated
const KEY_PREFIX: Self::KeyPrefixType; | ||
} | ||
|
||
pub trait DbKeyToBytes: Debug { |
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 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".
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 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.
fedimint-api/src/db/mod.rs
Outdated
// This trait is implemented for database key prefix enums. | ||
pub trait DbKeyPrefix { | ||
type KeyPrefixType: std::convert::Into<u8>; | ||
const KEY_PREFIX: Self::KeyPrefixType; |
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.
Any reason this can't be just const KEY_PREFIX: 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.
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?
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 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.
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.
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);
}
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.
Thank you both, that's very clear code @elsirion , I'll go with that.
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 #[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
with a final:
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. |
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 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.
fedimint-api/Cargo.toml
Outdated
@@ -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"] } |
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.
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.
fedimint-api/src/db/batch.rs
Outdated
@@ -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>), |
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 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.
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.
That makes sense to me, I'll aim to do that next time.
fedimint-api/src/db/mod.rs
Outdated
// This trait is implemented for database key prefix enums. | ||
pub trait DbKeyPrefix { | ||
type KeyPrefixType: std::convert::Into<u8>; | ||
const KEY_PREFIX: Self::KeyPrefixType; |
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.
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);
}
fedimint-api/src/db/mod.rs
Outdated
// 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); | ||
} | ||
|
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.
This approach would automatically prevent duplicate prefixes.
31fa71b
to
f08f8c3
Compare
f08f8c3
to
c897034
Compare
Codecov ReportBase: 59.38% // Head: 59.24% // Decreases project coverage by
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
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. |
Rebased and the checks all passed. |
Is it possible to add that assertion somewhere, such as during server start? |
@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. |
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.
lgtm! but I would remove the strum
dependency for now and introduce it in another PR when you actually want to use it.
c897034
to
4c8d483
Compare
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.
4c8d483
to
9a7a07a
Compare
Awesome, the final result is a really clean PR! |
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.