Skip to content

Commit 3f17681

Browse files
committed
Rework simulation
Signed-off-by: Denis Varlakov <denis@dfns.co>
1 parent a7f14b2 commit 3f17681

File tree

7 files changed

+302
-74
lines changed

7 files changed

+302
-74
lines changed

Cargo.lock

+3-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/random-generation-protocol/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ generic-array = { version = "0.14", features = ["serde"] }
2222
[dev-dependencies]
2323
round-based = { path = "../../round-based", features = ["derive", "dev", "state-machine"] }
2424
tokio = { version = "1.15", features = ["macros", "rt"] }
25-
futures = "0.3"
2625
hex = "0.4"
2726
rand_dev = "0.1"
2827
rand = "0.8"

examples/random-generation-protocol/src/lib.rs

+15-32
Original file line numberDiff line numberDiff line change
@@ -173,56 +173,39 @@ pub struct Blame {
173173

174174
#[cfg(test)]
175175
mod tests {
176-
use alloc::{vec, vec::Vec};
177-
178176
use rand::Rng;
179-
use round_based::simulation::Simulation;
180177
use sha2::{Digest, Sha256};
181178

182-
use super::{protocol_of_random_generation, Msg};
179+
use super::protocol_of_random_generation;
183180

184181
#[tokio::test]
185182
async fn simulation_async() {
186183
let mut rng = rand_dev::DevRng::new();
187184

188185
let n: u16 = 5;
189186

190-
let mut simulation = Simulation::<Msg>::new();
191-
let mut party_output = vec![];
192-
193-
for i in 0..n {
194-
let party = simulation.add_party();
195-
let output = protocol_of_random_generation(party, i, n, rng.fork());
196-
party_output.push(output);
197-
}
198-
199-
let output = futures::future::try_join_all(party_output).await.unwrap();
200-
201-
// Assert that all parties outputed the same randomness
202-
for i in 1..n {
203-
assert_eq!(output[0], output[usize::from(i)]);
204-
}
187+
let randomness = round_based::simulation::run_with_setup(
188+
core::iter::repeat_with(|| rng.fork()).take(n.into()),
189+
|i, party, rng| protocol_of_random_generation(party, i, n, rng),
190+
)
191+
.await
192+
.expect_success()
193+
.expect_same();
205194

206-
std::println!("Output randomness: {}", hex::encode(output[0]));
195+
std::println!("Output randomness: {}", hex::encode(randomness));
207196
}
208197

209198
#[test]
210199
fn simulation_sync() {
211200
let mut rng = rand_dev::DevRng::new();
212201

213-
let simulation = round_based::simulation::SimulationSync::from_async_fn(5, |i, party| {
202+
round_based::simulation::SimulationSync::from_async_fn(5, |i, party| {
214203
protocol_of_random_generation(party, i, 5, rng.fork())
215-
});
216-
217-
let outputs = simulation
218-
.run()
219-
.unwrap()
220-
.into_iter()
221-
.collect::<Result<Vec<_>, _>>()
222-
.unwrap();
223-
for output_i in &outputs {
224-
assert_eq!(*output_i, outputs[0]);
225-
}
204+
})
205+
.run()
206+
.unwrap()
207+
.expect_success()
208+
.expect_same();
226209
}
227210

228211
// Emulate the protocol using the state machine interface

round-based/Cargo.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,15 @@ trybuild = "1"
3232
matches = "0.1"
3333
futures = { version = "0.3", default-features = false }
3434
tokio = { version = "1", features = ["macros"] }
35+
hex = "0.4"
36+
37+
rand = "0.8"
38+
rand_dev = "0.1"
3539

3640
[features]
3741
default = ["std"]
3842
state-machine = []
39-
dev = ["std", "tokio/sync", "tokio-stream"]
43+
dev = ["std", "tokio/sync", "tokio-stream", "futures-util/alloc"]
4044
derive = ["round-based-derive"]
4145
runtime-tokio = ["tokio"]
4246
std = ["thiserror"]

round-based/src/simulation/mod.rs

+134-24
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,54 @@
11
//! Multiparty protocol simulation
22
//!
3-
//! [`Simulation`] is an essential developer tool for testing the multiparty protocol locally.
3+
//! Simulator is an essential developer tool for testing the multiparty protocol locally.
44
//! It covers most of the boilerplate by mocking networking.
55
//!
6-
//! ## Example
6+
//! The entry point is either [`run`] or [`run_with_setup`] functions. They take a protocol
7+
//! defined as an async function, provide simulated networking, carry out the simulation,
8+
//! and return the result.
9+
//!
10+
//! If you need more control over execution, you can use [`Network`] to simulate the networking
11+
//! and carry out the protocol manually.
12+
//!
13+
//! When `state-machine` feature is enabled, [`SimulationSync`] is available which can carry out
14+
//! protocols defined as a state machine.
715
//!
8-
//! ```rust
16+
//! ## Example
17+
//! ```rust,no_run
18+
//! # #[tokio::main(flavor = "current_thread")]
19+
//! # async fn main() {
920
//! use round_based::{Mpc, PartyIndex};
10-
//! use round_based::simulation::Simulation;
11-
//! use futures::future::try_join_all;
1221
//!
1322
//! # type Result<T, E = ()> = std::result::Result<T, E>;
1423
//! # type Randomness = [u8; 32];
1524
//! # type Msg = ();
1625
//! // Any MPC protocol you want to test
17-
//! pub async fn protocol_of_random_generation<M>(party: M, i: PartyIndex, n: u16) -> Result<Randomness>
18-
//! where M: Mpc<ProtocolMessage = Msg>
26+
//! pub async fn protocol_of_random_generation<M>(
27+
//! party: M,
28+
//! i: PartyIndex,
29+
//! n: u16
30+
//! ) -> Result<Randomness>
31+
//! where
32+
//! M: Mpc<ProtocolMessage = Msg>
1933
//! {
2034
//! // ...
2135
//! # todo!()
2236
//! }
2337
//!
24-
//! async fn test_randomness_generation() {
25-
//! let n = 3;
26-
//!
27-
//! let mut simulation = Simulation::<Msg>::new();
28-
//! let mut outputs = vec![];
29-
//! for i in 0..n {
30-
//! let party = simulation.add_party();
31-
//! outputs.push(protocol_of_random_generation(party, i, n));
32-
//! }
33-
//!
34-
//! // Waits each party to complete the protocol
35-
//! let outputs = try_join_all(outputs).await.expect("protocol wasn't completed successfully");
36-
//! // Asserts that all parties output the same randomness
37-
//! for output in outputs.iter().skip(1) {
38-
//! assert_eq!(&outputs[0], output);
39-
//! }
40-
//! }
38+
//! let n = 3;
39+
//!
40+
//! let output = round_based::simulation::run(
41+
//! n,
42+
//! |i, party| protocol_of_random_generation(party, i, n),
43+
//! )
44+
//! .await
45+
//! // unwrap `Result`s
46+
//! .expect_success()
47+
//! // check that all parties produced the same response
48+
//! .expect_same();
49+
//!
50+
//! println!("Output randomness: {}", hex::encode(output));
51+
//! # }
4152
//! ```
4253
4354
mod sim_async;
@@ -47,3 +58,102 @@ mod sim_sync;
4758
pub use sim_async::*;
4859
#[cfg(feature = "state-machine")]
4960
pub use sim_sync::*;
61+
62+
/// Result of the simulation
63+
pub struct SimResult<T>(pub alloc::vec::Vec<T>);
64+
65+
impl<T, E> SimResult<Result<T, E>>
66+
where
67+
E: core::fmt::Debug,
68+
{
69+
/// Unwraps `Result<T, E>` produced by each party
70+
///
71+
/// Panics if at least one of the parties returned `Err(_)`. In this case,
72+
/// a verbose error message will shown specifying which of the parties returned
73+
/// an error.
74+
pub fn expect_success(self) -> SimResult<T> {
75+
let mut oks = alloc::vec::Vec::with_capacity(self.0.len());
76+
let mut errs = alloc::vec::Vec::with_capacity(self.0.len());
77+
78+
for (res, i) in self.0.into_iter().zip(0u16..) {
79+
match res {
80+
Ok(res) => oks.push(res),
81+
Err(res) => errs.push((i, res)),
82+
}
83+
}
84+
85+
if !errs.is_empty() {
86+
let mut msg = alloc::format!(
87+
"Simulation output didn't match expectations.\n\
88+
Expected: all parties succeed\n\
89+
Actual : {success} parties succeeded, {failed} parties returned an error\n\
90+
Failures:\n",
91+
success = oks.len(),
92+
failed = errs.len(),
93+
);
94+
95+
for (i, err) in errs {
96+
msg += &alloc::format!("- Party {i}: {err:?}\n");
97+
}
98+
99+
panic!("{msg}");
100+
}
101+
102+
SimResult(oks)
103+
}
104+
}
105+
106+
impl<T> SimResult<T>
107+
where
108+
T: PartialEq + core::fmt::Debug,
109+
{
110+
/// Checks that outputs of all parties are equally the same
111+
///
112+
/// Returns the output on success (all the outputs are checked to be the same), otherwise
113+
/// panics with a verbose error message.
114+
///
115+
/// Panics if simulation contained zero parties.
116+
pub fn expect_same(mut self) -> T {
117+
let Some(first) = self.0.get(0) else {
118+
panic!("simulation contained zero parties");
119+
};
120+
121+
if !self.0[1..].iter().all(|i| i == first) {
122+
let mut msg = alloc::format!(
123+
"Simulation output didn't match expectations.\n\
124+
Expected: all parties return the same output\n\
125+
Actual : some of the parties returned a different output\n\
126+
Outputs :\n"
127+
);
128+
129+
for (i, res) in self.0.iter().enumerate() {
130+
msg += &alloc::format!("- Party {i}: {res:?}");
131+
}
132+
133+
panic!("{msg}")
134+
}
135+
136+
self.0
137+
.pop()
138+
.expect("we checked that the list contains at least one element")
139+
}
140+
}
141+
142+
impl<T> SimResult<T> {
143+
/// Deconstructs the simulation result returning inner list of results
144+
pub fn into_vec(self) -> alloc::vec::Vec<T> {
145+
self.0
146+
}
147+
}
148+
149+
impl<T> From<alloc::vec::Vec<T>> for SimResult<T> {
150+
fn from(list: alloc::vec::Vec<T>) -> Self {
151+
Self(list)
152+
}
153+
}
154+
155+
impl<T> From<SimResult<T>> for alloc::vec::Vec<T> {
156+
fn from(res: SimResult<T>) -> Self {
157+
res.0
158+
}
159+
}

0 commit comments

Comments
 (0)