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

VDAF-06+ ping-pong topology #683

Merged
merged 16 commits into from
Sep 13, 2023
Merged

VDAF-06+ ping-pong topology #683

merged 16 commits into from
Sep 13, 2023

Conversation

tgeoghegan
Copy link
Contributor

@tgeoghegan tgeoghegan commented Aug 17, 2023

Implements the ping-pong topology introduced in VDAF-06, though the
implementation here is based on the revised ping-pong interface in a
yet-unpublished VDAF version.

We add a topology module, on the premise that we might someday add new
topologies and their implementations there, and topology::ping_pong.

This also adds an implementation of a dummy VDAF, brought over from
Janus (1). vdaf::dummy is only compiled if the test-util Cargo
feature is enabled. The dummy VDAF implements the vdaf::{Vdaf, Aggregator, Client, Collector} traits and provides associated types for output
shares, prepare shares, etc., but it doesn't do anything except return
success, making it useful for testing higher-level constructions like
ping-pong.

Finally, we replace the derived std::fmt::Debug implementations on a
few prio3 and poplar1 associated types so that they redact fields
that are either sensitive secrets or just too big to be worth printing
when debugging. This is so that we can provide Debug impls on new
types in topology::ping_pong without pulling in crate derivative,
which would require us to do 9,000+ lines of audits.

@tgeoghegan tgeoghegan changed the title Draft: pnig-pong topology Draft: ping-pong topology Aug 17, 2023
@tgeoghegan tgeoghegan force-pushed the timg/ping-pong-topology branch 2 times, most recently from fb613a5 to db78177 Compare August 18, 2023 16:20
@tgeoghegan
Copy link
Contributor Author

I might still need to tweak this as the corresponding Janus change evolves, but I think this is ready to be reviewed. Note there's a corresponding PR to draft-irtf-cfrg-vdaf coming to clean up some spec issues encountered while implementing.

@tgeoghegan tgeoghegan marked this pull request as ready for review August 18, 2023 16:22
@tgeoghegan tgeoghegan requested a review from a team as a code owner August 18, 2023 16:22
@tgeoghegan tgeoghegan changed the title Draft: ping-pong topology VDAF-06+ ping-pong topology Aug 18, 2023
@cjpatton cjpatton self-requested a review August 18, 2023 18:46
Copy link
Contributor

@branlwyd branlwyd left a comment

Choose a reason for hiding this comment

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

Other than the in-line comments, one higher-level comment:

Currently, in Janus' experimental implementation of this change (as well as in the pre-ping-pong aggregation implementation), the Leader does not store the next prep_share to send between steps of an aggregation job; we store only the prep_state & prep_msg, and compute the prep_share in-memory when the job is picked up to continue onto the next step. Effectively, the Janus implementation stops its aggregation job steps after the prep_shares_to_prep call inside ping_pong_transition inside ping_pong_continued, before the following prep_next.

I don't see a way to use these functions to implement these semantics: PingPongTopologyPrivate::transition will always compute the prep share, and it will be included in the Message that is returned. So if the Leader calls ping_pong_continued in the same aggregation job step, the Leader will end up storing an extra (Leader) prep_share to durable storage; if the Leader calls ping_pong_continued in the next aggregation job step, the Leader will end up storing an extra (Helper) prep_share to durable storage.

I'm not sure the best way to address this; I'd fairly-strongly prefer not to store unnecessary data to durable storage. OTOH, splitting ping_pong_continued up in a way that just-so-happens to be what Janus does might be too inflexible for other implementations. Janus could always choose not to use these functions at all, of course, but that would be somewhat wasteful of this generic ping-pong implementation.

edit: see David's https://github.com/divviup/janus/blob/6dbab170ca9176b55661a1607b0c5cf0593ea527/core/src/task/message_sizes.rs#L173-L236 for formulas on how much extra space this would use, per in-progress report aggregation -- N.B. these are only for Prio3, which will never end up storing an in-progress report aggregation since it will terminate in a single network request/aggregation job step, but may be useful to estimate how many bytes per report aggregation we waste by storing an unnecessary prep_share.

src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Show resolved Hide resolved
src/vdaf/dummy.rs Outdated Show resolved Hide resolved
src/vdaf/dummy.rs Outdated Show resolved Hide resolved
@tgeoghegan
Copy link
Contributor Author

Good points about the size of objects we store between prep steps. I'm going to do a comparison between what we store in a ReportAggregationState without ping-pong and in the draft Janus change I've got in divviup/janus#1741, and then we'll see what the difference is, and whether we can add affordances to the libprio interfaces that let us thread the needle on storage requirements.

@tgeoghegan
Copy link
Contributor Author

Using David's branch, I worked up a table of sizes for prepare messages, prepare message shares, prepare states and output shares for various instantiations of Prio3.

VDAF Prepare message size (bytes) Prepare message share size (bytes) Prepare state size (bytes) Output share size (bytes)
Prio3Count 0 32 16 8
Prio3CountVec len 4 16 112 32 64
Prio3CountVec len 8 16 112 32 128
Prio3CountVec len 16 16 176 32 256
Prio3Sum 8 bits 16 64 32 16
Prio3Histogram 2 16 64 32 32
Prio3Histogram 4 16 64 32 64
Prio3Histogram 8 16 64 32 128
Poplar1 4 round 0 24 24 30 8
Poplar1 4 round 1 0 8 14 8

Crucially we observe that message shares and output shares are much bigger than the other objects.

Then, by code inspection in Janus, we work out the worst-case storage usage for the leader and helper, depending on the number of rounds in the VDAF. As a reminder, the reason that finished helpers must store anything is usualy to record the most recent response to the leader to handle replays. In the case of a 1-round VDAF, that response consists of the prepare message.

x Helper waiting Helper finished Leader waiting Leader finished
Before pong-pong prepare state + prepare share nothing (computed output share is accumulated) prepare state + prepare message nothing (computed output share is accumulated)
After ping-pong (1 round VDAF) never waits prepare message never waits nothing (computed output share is accumulated)
After ping-pong (2 round VDAF) prepare state + prepare message + prepare share nothing (computed output share is accumulated) output share* + prepare message nothing (computed output share is accumulated)

* The waiting 2-round leader stores the output share because it does not accumulate until the helper has also finished

Now let's plug in sizes from an 8 bit Prio3Histogram to make this more tangible.

Prio3Histogram Helper waiting storage (bytes) Helper finished storage (bytes) Leader waiting storage (bytes) Leader finished storage (bytes)
Before pong-pong 96 0 48 0
After ping-pong (1 round VDAF) n/a 16 n/a 0
After ping-pong (2 round VDAF) 112 (or 48, see note 1) 0 208 (or 48, see note 2) 0

For 1-round VDAFs, the leader and helper both come out ahead. However both aggregators lose in the 2-round VDAF case, because they have to store a prepare message from round n-1 and a prepare share from round n. Since many of these objects can be computed from each other, I think we can do better.

Note 1: In this state, the helper stores:

  • round 1 prepare state
  • round 1 prepare message (which it just used to advance to round 1)
  • round 2 helper prepare share (big)

It could instead store:

  • round 0 prepare state
  • round 1 prepare message

Then, it can recompute round 1 prepare state and round 2 helper prepare state.

Note 2: In this state, the leader stores:

  • output share (big)
  • round 2 prepare message

It could instead store:

  • round 1 prepare state
  • round 2 prepare message

I need to tinker with this, but I think that I can make the StateAndMessage enum store a transcript of recent rounds of the VDAF preparation such that it can either give you the results of the current round or give you a minimal but equivalent representation that occupies the least possible storage. I'll follow up here after I've played with this a bit.

@tgeoghegan
Copy link
Contributor Author

Put another way: I think I can refactor these helper_initialize and continue definitions so that they return an object that the caller can then use to either serialize state into a database or continue the VDAF preparation. That will provide the break inside of ping_pong_transition that Brandon describes that enables Janus to avoid storing unnecessary objects.

Copy link
Contributor Author

@tgeoghegan tgeoghegan left a comment

Choose a reason for hiding this comment

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

OK, I think I have it. I've refactored PingPongTopology::transition into a new struct Transition which contains the necessary values to compute transition(), namely a PrepareState and a PrepareMessage. Now, implementations that want to store state to a DB between preparation rounds can store the encoded Transition, while those that don't care can do vdaf.continued()?.evaluate()? and get the (State, Message) tuple as above.

Besides avoiding the the storage hit @branlwyd discussed, I think this approach will also allow Janus to stop dealing with the report_aggregations.prep_msg column, because the stores Transition now contains enough information to reconstruct it.

src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
@tgeoghegan tgeoghegan requested a review from branlwyd August 23, 2023 23:14
@tgeoghegan tgeoghegan requested a review from a team August 24, 2023 16:52
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/vdaf.rs Show resolved Hide resolved
Copy link
Contributor

@branlwyd branlwyd left a comment

Choose a reason for hiding this comment

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

OK, I think I have it. I've refactored PingPongTopology::transition into a new struct Transition which contains the necessary values to compute transition(), namely a PrepareState and a PrepareMessage. Now, implementations that want to store state to a DB between preparation rounds can store the encoded Transition, while those that don't care can do vdaf.continued()?.evaluate()? and get the (State, Message) tuple as above.

I like this abstraction. The only note I have is that a DAP implementation's stored state type will now be called Transition; this transition can fallibly compute a State. This naming scheme might be a bit confusing to folks new to this API, but since we're stuck with State to match the VDAF spec, I think we can't do much better here. (raising this in case someone else has a better idea)

Besides avoiding the the storage hit @branlwyd discussed, I think this approach will also allow Janus to stop dealing with the report_aggregations.prep_msg column, because the stores Transition now contains enough information to reconstruct it.

I think report_aggregations.prep_state and report_aggregations.prep_msg will be merged into something like report_aggregations.transition, since the new ping-pong API deals in types that merge the prep_state & prep_msg.

.prepare_preprocess([inbound_prep_share, prep_share])
.map_err(PingPongError::VdafPreparePreprocess)?;

Ok(Transition {
Copy link
Contributor

Choose a reason for hiding this comment

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

Q: does the Helper get a similar reduction in storage via use of the Transition abstraction, or is the usage here for API consistency (or another reason unrelated to storage reduction)? I think it's a good choice either way, I just haven't thought as much about the Helper case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. When the helper is in the waiting state, it conceptually holds:

  • prepare state for round n
  • prepare message that got it to round n
  • prepare share for round n+1

Using Transition, it only needs to store:

  • prepare state for round n-1
  • prepare message to get to round n

...and then it can recompute the round n prepare state and round n+1 prepare share. Prepare shares are large (see table in my comment above) so this is a meaningful gain for the helper.

src/topology/ping_pong.rs Show resolved Hide resolved
src/topology/ping_pong.rs Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/vdaf/dummy.rs Outdated Show resolved Hide resolved
Copy link
Contributor Author

@tgeoghegan tgeoghegan 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 this abstraction. The only note I have is that a DAP implementation's stored state type will now be called Transition; this transition can fallibly compute a State. This naming scheme might be a bit confusing to folks new to this API, but since we're stuck with State to match the VDAF spec, I think we can't do much better here. (raising this in case someone else has a better idea)

I agree. I think that when we decide how to slice this up into functions and types, there is a tension here between the objectives of doing something that is useful or convenient for implementations and doing something that lines up recognizably with the spec. If we want to do better here, I think we'd have to revisit the decision to put the ping-pong topology into VDAF. If it were in DAP, then a project like Janus has more freedom.

I think report_aggregations.prep_state and report_aggregations.prep_msg will be merged into something like report_aggregations.transition, since the new ping-pong API deals in types that merge the prep_state & prep_msg.

Yes, that's what's panning out in divviup/janus#1741 (which I have not yet updated to reflect the latest changes in this PR). I had hoped that last_prep_step could also go away, but the Transition doesn't quite capture everything we need there. I think we can do better with last_prep_step, but I'll pursue that discussion in the Janus PR, and in fact may punt improvements to last_prep_step to a later change since janus/1741 is going to be rather large and complex as it is.

@tgeoghegan tgeoghegan force-pushed the timg/ping-pong-topology branch 2 times, most recently from e3acf8e to 4115e7e Compare August 25, 2023 23:18
tgeoghegan pushed a commit to divviup/janus that referenced this pull request Aug 27, 2023
This change implements the DAP-05 ping-pong topology in which
aggregators take turns preprocessing prepare shares into prepare
messages. While this topology first appeared in DAP-05, this
implementation follows the changes in [1], which should appear in
DAP-06.

This change depends on the implementation of the VDAF ping-pong topology
added to crate `prio` in [2], which in turn conforms to the
specification added after VDAF-06 and further tweaked in [3] (we expect
this to be published soon as VDAF-07).

This commit makes some changes to what intermediate values are stored by
aggregators. In the case where an aggregator is continuing, it will have
computed a prepare state, a prepare message for the current round and a
prepare share for the next round. The existing implementation would
store all three objects in the database, significantly increasing the
per-report storage requirements. In particular, this makes things worse
for the Helper, which previously never needed to store a prepare share
because the Leader always took responsibility for combining prepare
shares.

To mitigate this, we instead have aggregators store a
`prio::ping_pong::topology::Transition`, which will contain a prepare
state and a prepare message (both of which are generally much smaller
than prepare shares), from which the next prepare state and importantly
prepare share can be recomputed.

The main benefit of this change is to reduce how many round trips
between aggregators are needed to prepare reports. Quite a few tests
used Prio3 but depended on having the leader or helper in the `Waiting`
state after running aggregation initialization. Accordingly, those tests
are changed to run Poplar1, which now takes 2 rounds.

[1]: ietf-wg-ppm/draft-ietf-ppm-dap#494
[2]: divviup/libprio-rs#683
[3]: cfrg/draft-irtf-cfrg-vdaf#281

Part of #1669
Copy link
Collaborator

@cjpatton cjpatton left a comment

Choose a reason for hiding this comment

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

Excellent work. I see no issues in the implementation, all my comments are relatively minor. One high-level thing to carefully consider: Which parts of the new API surface are essential, and which aren't?

src/topology/mod.rs Show resolved Hide resolved
src/topology/mod.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Show resolved Hide resolved
src/topology/ping_pong.rs Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
@tgeoghegan tgeoghegan requested a review from cjpatton August 29, 2023 22:44
supply-chain/config.toml Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
@tgeoghegan tgeoghegan force-pushed the timg/ping-pong-topology branch from 029c87c to cd8eec8 Compare August 30, 2023 22:11
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
src/topology/ping_pong.rs Outdated Show resolved Hide resolved
tgeoghegan pushed a commit to divviup/janus that referenced this pull request Aug 31, 2023
This change implements the DAP-05 ping-pong topology in which
aggregators take turns preprocessing prepare shares into prepare
messages. While this topology first appeared in DAP-05, this
implementation follows the changes in [1], which should appear in
DAP-06.

This change depends on the implementation of the VDAF ping-pong topology
added to crate `prio` in [2], which in turn conforms to the
specification added after VDAF-06 and further tweaked in [3] (we expect
this to be published soon as VDAF-07).

This commit makes some changes to what intermediate values are stored by
aggregators. In the case where an aggregator is continuing, it will have
computed a prepare state, a prepare message for the current round and a
prepare share for the next round. The existing implementation would
store all three objects in the database, significantly increasing the
per-report storage requirements. In particular, this makes things worse
for the Helper, which previously never needed to store a prepare share
because the Leader always took responsibility for combining prepare
shares.

To mitigate this, we instead have aggregators store a
`prio::ping_pong::topology::Transition`, which will contain a prepare
state and a prepare message (both of which are generally much smaller
than prepare shares), from which the next prepare state and importantly
prepare share can be recomputed.

The main benefit of this change is to reduce how many round trips
between aggregators are needed to prepare reports. Quite a few tests
used Prio3 but depended on having the leader or helper in the `Waiting`
state after running aggregation initialization. Accordingly, those tests
are changed to run Poplar1, which now takes 2 rounds.

[1]: ietf-wg-ppm/draft-ietf-ppm-dap#494
[2]: divviup/libprio-rs#683
[3]: cfrg/draft-irtf-cfrg-vdaf#281

Part of #1669
@tgeoghegan tgeoghegan force-pushed the timg/ping-pong-topology branch from 8ff8e52 to 702ac75 Compare September 11, 2023 17:04
@tgeoghegan tgeoghegan force-pushed the timg/ping-pong-topology branch from d473fa9 to 1059856 Compare September 11, 2023 17:10
Implements the ping-pong topology introduced in VDAF-06, though the
implementation here is based on the revised ping-pong interface in a
yet-unpublished VDAF version.

We add a `topology` module, on the premise that we might someday add new
topologies and their implementations there, and `topology::ping_pong`.

This also adds an implementation of a dummy VDAF, brought over from
Janus ([1]). `vdaf::dummy` is only compiled if the `test-util` Cargo
feature is enabled. The dummy VDAF implements the `vdaf::{Vdaf,
Aggregator, Client, Collector}` and provides associated types for output
shares, prepare shares, etc., but it doesn't do anything except return
success, making it useful for testing higher-level constructions like
ping-pong.

Finally, we replace the derived `std::fmt::Debug` implementations on a
few `prio3` and `poplar1` associated types so that they redact fields
that are either sensitive secrets or just too big to be worth printing
when debugging. This is so that we can provide `Debug` impls on new
types in `topology::ping_pong` without pulling in crate `derivative`,
which would require us to do 9,000+ lines of audits.

[1]: https://github.com/divviup/janus
add tests for encode/decode messages
move max rounds into the dummy vdaf prep closure
- update various doccomments
- remove Role enum and provide distinct leader_continued,
  helper_continued
- rename various structs to `PingPongWhatever` for consistency with
  Prio3, Poplar1 modules
- rename leader_initialize, helper_initialize to leader_initialized,
  helper_initialized to align with continued
Rebasing means a couple methods need an agg param argument, since
`Vdaf::prepare_shares_to_prepare_message` needs agg param.
@tgeoghegan tgeoghegan force-pushed the timg/ping-pong-topology branch from 1059856 to a2076f9 Compare September 13, 2023 01:56
tgeoghegan pushed a commit to divviup/janus that referenced this pull request Sep 13, 2023
This change implements the DAP-05 ping-pong topology in which
aggregators take turns preprocessing prepare shares into prepare
messages. While this topology first appeared in DAP-05, this
implementation follows DAP-06.

This change depends on the implementation of the VDAF ping-pong topology
added to crate `prio` in [1], which in turn conforms to the
specification in VDAF-07.

In the ping-pong topology, each DAP-layer step of aggregation now spans
two VDAF rounds. An aggregator will use the prepare message it gets from
its peer to advance by one VDAF round, and then can use the prepare
share it just computed along with the peer's prepare share to advance by
another. This incurs some changes to what intermediate values are stored
by aggregators.

In the case where a leader is continuing/waiting, it will have computed
a prepare state, a prepare message for the current round and a prepare
share for the next round. The naive implementation would store all three
objects in the database, significantly increasing the per-report storage
use. To mitigate this, the leader stores a
`prio::topology::ping_pong::PingPongTransition`, which will contain a
prepare state and a prepare message (both of which are generally much
smaller than prepare shares), from which the next prepare state and
importantly prepare share can be recomputed.

On the helper side, there's no way around storing the prepare share: we
store the most recently computed `PrepareResp` so that we can handle
aggregation jobs idempotently. But to avoid storing prepare messages
twice, the continuing/waiting helper stores just a prepare state and a
`PingPongMessage`.

The main benefit of this change is to reduce how many round trips
between aggregators are needed to prepare reports. Quite a few tests
used Prio3 but depended on having the leader or helper in the `Waiting`
state after running aggregation initialization. Accordingly, those tests
are changed to run Poplar1, which now takes 2 rounds.

[1]: divviup/libprio-rs#683

Co-authored-by: Tim Geoghegan <[email protected]>

Part of #1669
Comment on lines 27 to 29
/// Error running prepare_step
#[error("vdaf.prepare_step {0}")]
VdafPrepareStep(VdafError),
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be renamed as well to refer to prepare_next.

@tgeoghegan tgeoghegan merged commit a04238c into main Sep 13, 2023
6 checks passed
@tgeoghegan tgeoghegan deleted the timg/ping-pong-topology branch September 13, 2023 21:02
tgeoghegan pushed a commit to divviup/janus that referenced this pull request Sep 13, 2023
This change implements the DAP-05 ping-pong topology in which
aggregators take turns preprocessing prepare shares into prepare
messages. While this topology first appeared in DAP-05, this
implementation follows DAP-06.

This change depends on the implementation of the VDAF ping-pong topology
added to crate `prio` in [1], which in turn conforms to the
specification in VDAF-07.

In the ping-pong topology, each DAP-layer step of aggregation now spans
two VDAF rounds. An aggregator will use the prepare message it gets from
its peer to advance by one VDAF round, and then can use the prepare
share it just computed along with the peer's prepare share to advance by
another. This incurs some changes to what intermediate values are stored
by aggregators.

In the case where a leader is continuing/waiting, it will have computed
a prepare state, a prepare message for the current round and a prepare
share for the next round. The naive implementation would store all three
objects in the database, significantly increasing the per-report storage
use. To mitigate this, the leader stores a
`prio::topology::ping_pong::PingPongTransition`, which will contain a
prepare state and a prepare message (both of which are generally much
smaller than prepare shares), from which the next prepare state and
importantly prepare share can be recomputed.

On the helper side, there's no way around storing the prepare share: we
store the most recently computed `PrepareResp` so that we can handle
aggregation jobs idempotently. But to avoid storing prepare messages
twice, the continuing/waiting helper stores just a prepare state and a
`PingPongMessage`.

The main benefit of this change is to reduce how many round trips
between aggregators are needed to prepare reports. Quite a few tests
used Prio3 but depended on having the leader or helper in the `Waiting`
state after running aggregation initialization. Accordingly, those tests
are changed to run Poplar1, which now takes 2 rounds.

[1]: divviup/libprio-rs#683

Co-authored-by: Tim Geoghegan <[email protected]>

Part of #1669
tgeoghegan added a commit to divviup/janus that referenced this pull request Sep 13, 2023
This change implements the DAP-05 ping-pong topology in which
aggregators take turns preprocessing prepare shares into prepare
messages. While this topology first appeared in DAP-05, this
implementation follows DAP-06.

This change depends on the implementation of the VDAF ping-pong topology
added to crate `prio` in [1], which in turn conforms to the
specification in VDAF-07.

In the ping-pong topology, each DAP-layer step of aggregation now spans
two VDAF rounds. An aggregator will use the prepare message it gets from
its peer to advance by one VDAF round, and then can use the prepare
share it just computed along with the peer's prepare share to advance by
another. This incurs some changes to what intermediate values are stored
by aggregators.

In the case where a leader is continuing/waiting, it will have computed
a prepare state, a prepare message for the current round and a prepare
share for the next round. The naive implementation would store all three
objects in the database, significantly increasing the per-report storage
use. To mitigate this, the leader stores a
`prio::topology::ping_pong::PingPongTransition`, which will contain a
prepare state and a prepare message (both of which are generally much
smaller than prepare shares), from which the next prepare state and
importantly prepare share can be recomputed.

On the helper side, there's no way around storing the prepare share: we
store the most recently computed `PrepareResp` so that we can handle
aggregation jobs idempotently. But to avoid storing prepare messages
twice, the continuing/waiting helper stores just a prepare state and a
`PingPongMessage`.

The main benefit of this change is to reduce how many round trips
between aggregators are needed to prepare reports. Quite a few tests
used Prio3 but depended on having the leader or helper in the `Waiting`
state after running aggregation initialization. Accordingly, those tests
are changed to run Poplar1, which now takes 2 rounds.

[1]: divviup/libprio-rs#683

Co-authored-by: Tim Geoghegan <[email protected]>

Part of #1669

Co-authored-by: Brandon Pitman <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants