-
Notifications
You must be signed in to change notification settings - Fork 25
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
Make upgrade take an iterator #724
Make upgrade take an iterator #724
Conversation
The cost of this is that some of the type safety we got from the traits had to be cut (maybe we can restore them, but I'm not smart enough today).
/// }; | ||
/// // This can't be upgraded with a record-bound context because the record ID | ||
/// // is used internally for vector indexing. | ||
/// let _ = <UpgradeContext<C<'_, F>, F, RecordId> as UpgradeToMalicious<Vec<Replicated<F>>, _>>::upgrade; |
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 couldn't work out how to make this keep failing. The way it failed before was that we only implemented for Vec<Vec<T>>
, but now that there is a generic IntoIterator
version, I'm not sure what we would do to keep this.
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.
Using a non-Vec
type that is IntoIterator
(like [Replicated<F>; 3]
) would work, but I'm not sure how much value that has. I might be inclined to delete all of these doc tests -- without the negative case, I don't think the positive cases have much value.
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.
Test restoration incoming. I forgot to revert this part of the change.
@@ -431,15 +407,14 @@ where | |||
// context. This is only used for tests where the protocol takes a single `Replicated<F>` input. | |||
#[cfg(test)] | |||
#[async_trait] | |||
impl<'a, C, F, T, M> UpgradeToMalicious<'a, T, M> for UpgradeContext<'a, C, F, NoRecord> | |||
impl<'a, C, F, M> UpgradeToMalicious<'a, Replicated<F>, M> for UpgradeContext<'a, C, F, NoRecord> |
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 smaller implementation worked fine, for all but the modulus conversion tests. This seemed like a good trade.
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 might also need/want a corresponding non-record-bound implementation for BitDecomposed
at some point. Right now I think malicious tests are upgrading BitDecomposed<T>
to Vec<M>
(which is harmless, just something I noticed while looking at the changes).
It might be worth a TODO mentioning this, since the error messages when the compiler doesn't find the upgrade implementation it needs can be a bit hard to interpret.
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 makes me a little nervous to have an impl for Vec<T>
that panics for large vectors -- it appears at the surface like it will work for any Vec, but it won't. Vec<Vec<T>>
seemed a little safer since that's a less common type to have around.
One easy improvement might be to return an error on attempt to upgrade a long vector rather than panic. Then at least if we end up reaching this case at runtime, the helper doesn't crash.
A fix that I do like but is much more work, is to use a dedicated type for "bit-decomposition of a value that is at most 64 bits". That would also help us get the last bit of storage efficiency for Vec<MaliciousReplicated<Gf2>>
(although for that case, I'm also hopeful that we can use a larger field).
src/protocol/context/upgrade.rs
Outdated
@@ -431,15 +407,14 @@ where | |||
// context. This is only used for tests where the protocol takes a single `Replicated<F>` input. |
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.
Suggest changing this comment to:
// Impl to upgrade a single `Replicated<F>` using a non-record-bound context. Used for tests.
src/protocol/context/upgrade.rs
Outdated
/// It assumes the inner vector is much smaller (e.g. multiple bits per record) than the outer vector (e.g. records) | ||
/// Each inner vector element uses a different context and outer vector shares a context for the same inner vector 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.
/// It assumes the inner vector is much smaller (e.g. multiple bits per record) than the outer vector (e.g. records) | |
/// Each inner vector element uses a different context and outer vector shares a context for the same inner vector index |
/// }; | ||
/// // This can't be upgraded with a record-bound context because the record ID | ||
/// // is used internally for vector indexing. | ||
/// let _ = <UpgradeContext<C<'_, F>, F, RecordId> as UpgradeToMalicious<Vec<Replicated<F>>, _>>::upgrade; |
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.
Using a non-Vec
type that is IntoIterator
(like [Replicated<F>; 3]
) would work, but I'm not sure how much value that has. I might be inclined to delete all of these doc tests -- without the negative case, I don't think the positive cases have much value.
@andyleiserson I took a swing at doing the hard thing you mentioned. It wasn't easy, as you said, but I think that there are real improvements to be had from it. The main drawback is that I had to move |
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 main drawback is that I had to move
IntoShares
to takeIterator
rather thanIntoIterator
. I'm in two minds about that, but implementingIntoIterator
forBitDecomposed
seemed like it was more useful.
Yeah, I agree this is a tough call. If we had both owned and borrowed versions of BitDecomposed
, then we might not need BitDecomposed
to deref to a slice, and then maybe we could do without the IntoIterator implementation for BitDecomposed
. But that's a whole additional set of changes to make.
It might be possible to avoid the into_iter()
calls in the common case of passing a Vec
to a test, in exchange for more verbosity in the less common case of passing an iterator to a test, by implementing IntoShares
for Vec<U>
and Box<dyn Iterator<Item = U>>
. (Or for a newtype wrapper around an iterator.) However, the changes to call into_iter()
are already done, so I don't see a compelling reason to pursue this.
/// # Panics | ||
/// If the iterator produces more than `Self::MAX` items. | ||
pub fn new<I: IntoIterator<Item = S>>(bits: I) -> Self { | ||
let bits = bits.into_iter().collect::<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.
Do you know if the compiler optimizes this in the case where I
is a Vec
?
If it doesn't, it might make sense to expose separate interfaces for "from vec" and "from iterator".
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.
https://gcc.godbolt.org/z/x9d7We759 seems to support the view that it optimizes out.
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.
@@ -431,15 +407,14 @@ where | |||
// context. This is only used for tests where the protocol takes a single `Replicated<F>` input. | |||
#[cfg(test)] | |||
#[async_trait] | |||
impl<'a, C, F, T, M> UpgradeToMalicious<'a, T, M> for UpgradeContext<'a, C, F, NoRecord> | |||
impl<'a, C, F, M> UpgradeToMalicious<'a, Replicated<F>, M> for UpgradeContext<'a, C, F, NoRecord> |
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 might also need/want a corresponding non-record-bound implementation for BitDecomposed
at some point. Right now I think malicious tests are upgrading BitDecomposed<T>
to Vec<M>
(which is harmless, just something I noticed while looking at the changes).
It might be worth a TODO mentioning this, since the error messages when the compiler doesn't find the upgrade implementation it needs can be a bit hard to interpret.
@@ -67,10 +95,14 @@ mod tests { | |||
.map(u128::from) | |||
.map(Fp31::truncate_from); | |||
|
|||
// Flatten the input so that it can implement `IntoShares`. |
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.
Is it possible flattening is no longer necessary with the impls in the final version? I tried removing it, and it seemed to 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.
Nice, I had forgotten that part.
async fn downgrade(self) -> UnauthorizedDowngradeWrapper<Self::Target> { | ||
// TODO: validate that the vector here is truly small. | ||
#[allow(clippy::disallowed_methods)] | ||
let result = join_all( |
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 is exactly the kind of thing I was hoping to catch with this change, nice!
The cost of this is that some of the type safety we got from the traits had to be cut (maybe we can restore them, but I'm not smart enough today).