Skip to content

Conversation

@coriolinus
Copy link
Contributor

What's new in this PR

Reorganize the core mechanisms in the keystore to require new entity trait impls instead of old entity trait impls.


PR Submission Checklist for internal contributors
  • The PR Title
    • conforms to the style of semantic commits messages¹ supported in Wire's Github Workflow²
    • contains a reference JIRA issue number like SQPIT-764
    • answers the question: If merged, this PR will: ... ³
  1. https://sparkbox.com/foundry/semantic_commit_messages
  2. https://github.com/wireapp/.github#usage
  3. E.g. feat(conversation-list): Sort conversations by most emojis in the title #SQPIT-764.

We already knew that there is a real difference between key types
which are owned and those which are borrowed. We already knew that
entity keys need to be of the owned kind.

Adding this trait allows us to formalize that notion, and add
a utility to construct an owned variant from some arbitrary bytes.
Just looking to give it complete coverage of the kinds of things that
the new entity traits offer.
- break up the impls into separate files for easier navigation
- separate the concept of an entity type and an entity id
- borrow the actual entity instead of owning it
- make `execute_save` and `execute_delete` methods not free functions
- restrict visibility: nothing external should really need this dynamic
  dispatch stuff
We used to do some crazy thing involving unencrypted serialization
for some reason. It's much simpler this way: when an entity turns
itself into a dynamic entity, we just keep it. We do Arc-wrap it,
because there are times we want to iterate over a bunch of owned
instances and don't want the expense of cloning these potentially-
large items every time. But from there the API is straightforward.

While it was cool we could make the borrowing approach work for
dynamic entities, in practice it turned out that in general when
we hand an entity to the database to save, we really want it to
take over ownership, which means this pattern is better.
The traits didn't previously allow for multiple distinct implementations
of borrowing types sharing an owned type (I think) anyway, and we
already had an associated type naming a specific borrowed primary
key, so it was pointless overkill to accept a complicated generic
parameter type in these methods. Better and simpler to just use the
associated type.
Pending messages have no real primary key and are always/only accessed
and removed in bulk, which turns out to be _really annoying_ with regard
to the new entity traits, which assumes that some primary key does exist.

For now we're faking the foreign id as the primary key so that encryption
works at all, and also for dynamic dispatch. But I'd really love to
rework this at some point in the future.
Fascinatingly, we're allowed to simply omit the trait bounds where
they can be statically proven, i.e. on an associated type. We're
learning about Rust today!
Turns out that there's a real benefit to storing the dynamic-dispatch
entities the way we do, and that's a huge simplification of the
transaction stuff.
The trait used to be a bit loosely specified, since nobody was implementing it.
Now we know precisely what it needs and how to implement it.
@coriolinus coriolinus force-pushed the prgn/refactor/22195-update-fetchfromdatabase branch from d14ff2d to df7fa6b Compare December 19, 2025 10:54
So, I thought there were no more trait refactors incoming. But then
it turned out that there was a bug in the auto-implementations
of the unique entity helper on wasm only: `Entity` had an auto-impl
where `T: UniqueEntity` (so it could reference `T::KEY`), but
`UniqueEntity` had an auto-impl where `T: Entity` (so it could reference
`T::PrimaryKey`), and rustc was running out of stack trying to figure
out if a particular instance of `T` implemented both or neither of those.

So we broke out `PrimaryKey` from `Entity` which broke that loop,
and as a nice side effect means that there's now better symmetry
between `PrimaryKey` and `BorrowPrimaryKey`, and `Entity` and `EntityGetBorrowed`
(as well as `EntityDatabaseMutation` and `EntityDeleteBorrowed`).

But now I have to fix all the impls.
Just a bunch of busywork necessary to support the switch to `PrimaryKey`,
`BorrowPrimaryKey`, `EntityGetBorrowed`.
@coriolinus coriolinus force-pushed the prgn/refactor/22195-update-fetchfromdatabase branch from 6aaf525 to 224ca2d Compare December 19, 2025 15:11
@coriolinus coriolinus force-pushed the prgn/refactor/22195-update-fetchfromdatabase branch from 224ca2d to b62eb8d Compare December 19, 2025 15:18
If the open transaction contains invalid state (i.e. violating DB constraints),
we'd rather not panic within the Drop impl.
For some reason we're computing and storing a sha256 hash for these
types, but we're not using it as the primary key. OpenMLS uses the
raw id field as the primary key in a way that we can't modify,
so that's what has to be the pk. As for the hash: wasted space.
Update the previous version with this which fixes the correct primary key
We'd had an issue where keypackages couldn't be loaded due to
improper handling of hex-encoded keys, but this test now proves
that we've resolved that issue.
…iate

Turns out that when we have a field whose ID is a hex-encoded value,
the field is a string and the hex is lowercase. And what that means
is that we have to perform that transform when searching for that key!
1. Only fetch the database once
2. Given that we no longer raise errors on missing values,
   update the condition to produce an "already registered" error appropriately.
…d set

Without this behavior, if we have these operations

- Rm the entity with id X
- add a new entity with id X

then it gets added and then immediately deleted again, which is not helpful.
This entity type is really annoying because it just doesn't
have a single primary key per entity. So when we faked it as just
the conversation id, as soon as there was more than 1 pending message
per conversation, they started overwriting each other within the transaction.

So we've added a bunch of changes and workarounds to make it go.
I'm hopeful that in the future we can get rid of all or nearly all
of this, but for now, we're stuck with it.
For whatever reason the entity new derive macro was calling
`new_get` and `new_delete` with `String` or `&str` instances instead
of the expected `Vec<u8>` / `&[u8]`, and of course that was failing
type checking. But actually we'd prefer to have those functions available.
So broadening the acceptable types solves the problem for us (probably).
@github-actions
Copy link

github-actions bot commented Dec 23, 2025

🐰 Bencher Report

Branchprgn/refactor/22195-update-fetchfromdatabase
Testbedubuntu-latest

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
BenchmarkLatencymicroseconds (µs)
Commit add f(group size)/cs1/mem/1002📈 view plot
⚠️ NO THRESHOLD
15,320.00 µs
Commit add f(group size)/cs1/mem/2📈 view plot
⚠️ NO THRESHOLD
676.36 µs
Commit add f(group size)/cs1/mem/202📈 view plot
⚠️ NO THRESHOLD
3,741.30 µs
Commit add f(group size)/cs1/mem/402📈 view plot
⚠️ NO THRESHOLD
6,783.00 µs
Commit add f(group size)/cs1/mem/602📈 view plot
⚠️ NO THRESHOLD
10,500.00 µs
Commit add f(group size)/cs1/mem/802📈 view plot
⚠️ NO THRESHOLD
12,787.00 µs
Commit add f(number clients)/cs1/mem/1002📈 view plot
⚠️ NO THRESHOLD
990,110.00 µs
Commit add f(number clients)/cs1/mem/2📈 view plot
⚠️ NO THRESHOLD
650.18 µs
Commit add f(number clients)/cs1/mem/202📈 view plot
⚠️ NO THRESHOLD
78,723.00 µs
Commit add f(number clients)/cs1/mem/402📈 view plot
⚠️ NO THRESHOLD
215,740.00 µs
Commit add f(number clients)/cs1/mem/602📈 view plot
⚠️ NO THRESHOLD
422,770.00 µs
Commit add f(number clients)/cs1/mem/802📈 view plot
⚠️ NO THRESHOLD
672,600.00 µs
Commit pending proposals f(group size)/cs1/mem/1002📈 view plot
⚠️ NO THRESHOLD
114,120.00 µs
Commit pending proposals f(group size)/cs1/mem/2📈 view plot
⚠️ NO THRESHOLD
22,542.00 µs
Commit pending proposals f(group size)/cs1/mem/202📈 view plot
⚠️ NO THRESHOLD
40,725.00 µs
Commit pending proposals f(group size)/cs1/mem/402📈 view plot
⚠️ NO THRESHOLD
56,461.00 µs
Commit pending proposals f(group size)/cs1/mem/602📈 view plot
⚠️ NO THRESHOLD
75,499.00 µs
Commit pending proposals f(group size)/cs1/mem/802📈 view plot
⚠️ NO THRESHOLD
91,165.00 µs
Commit pending proposals f(pending size)/cs1/mem/1📈 view plot
⚠️ NO THRESHOLD
15,489.00 µs
Commit pending proposals f(pending size)/cs1/mem/101📈 view plot
⚠️ NO THRESHOLD
112,550.00 µs
Commit pending proposals f(pending size)/cs1/mem/21📈 view plot
⚠️ NO THRESHOLD
32,313.00 µs
Commit pending proposals f(pending size)/cs1/mem/41📈 view plot
⚠️ NO THRESHOLD
54,625.00 µs
Commit pending proposals f(pending size)/cs1/mem/61📈 view plot
⚠️ NO THRESHOLD
72,769.00 µs
Commit pending proposals f(pending size)/cs1/mem/81📈 view plot
⚠️ NO THRESHOLD
92,247.00 µs
Commit remove f(group size)/cs1/mem/1002📈 view plot
⚠️ NO THRESHOLD
9,747.60 µs
Commit remove f(group size)/cs1/mem/2📈 view plot
⚠️ NO THRESHOLD
521.79 µs
Commit remove f(group size)/cs1/mem/202📈 view plot
⚠️ NO THRESHOLD
2,009.00 µs
Commit remove f(group size)/cs1/mem/402📈 view plot
⚠️ NO THRESHOLD
3,673.50 µs
Commit remove f(group size)/cs1/mem/602📈 view plot
⚠️ NO THRESHOLD
5,498.50 µs
Commit remove f(group size)/cs1/mem/802📈 view plot
⚠️ NO THRESHOLD
7,418.80 µs
Commit remove f(number clients)/cs1/mem/1002📈 view plot
⚠️ NO THRESHOLD
13,711.00 µs
Commit remove f(number clients)/cs1/mem/2📈 view plot
⚠️ NO THRESHOLD
133,030.00 µs
Commit remove f(number clients)/cs1/mem/202📈 view plot
⚠️ NO THRESHOLD
109,150.00 µs
Commit remove f(number clients)/cs1/mem/402📈 view plot
⚠️ NO THRESHOLD
85,298.00 µs
Commit remove f(number clients)/cs1/mem/602📈 view plot
⚠️ NO THRESHOLD
61,348.00 µs
Commit remove f(number clients)/cs1/mem/802📈 view plot
⚠️ NO THRESHOLD
39,191.00 µs
Commit update f(group size)/cs1/mem/1002📈 view plot
⚠️ NO THRESHOLD
132,870.00 µs
Commit update f(group size)/cs1/mem/2📈 view plot
⚠️ NO THRESHOLD
679.45 µs
Commit update f(group size)/cs1/mem/202📈 view plot
⚠️ NO THRESHOLD
27,562.00 µs
Commit update f(group size)/cs1/mem/402📈 view plot
⚠️ NO THRESHOLD
54,146.00 µs
Commit update f(group size)/cs1/mem/602📈 view plot
⚠️ NO THRESHOLD
81,581.00 µs
Commit update f(group size)/cs1/mem/802📈 view plot
⚠️ NO THRESHOLD
107,370.00 µs
🐰 View full continuous benchmarking report in Bencher

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.

2 participants