Skip to content

Commit 690ea4e

Browse files
authored
Merge pull request #57 from earthstar-project/commitment-reveal
WGPS: Add ReadyTransport and friends
2 parents 9d583ea + 410639e commit 690ea4e

File tree

4 files changed

+197
-0
lines changed

4 files changed

+197
-0
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wgps/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ edition = "2021"
66
[dependencies]
77
willow-data-model = { path = "../data-model", version = "0.1.0" }
88
ufotofu = { version = "0.4.2", features = ["std"] }
9+
either = "1.10.0"
10+
11+
[dev-dependencies]
12+
smol = "2.0.0"
913

1014
[lints]
1115
workspace = true

wgps/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ use willow_data_model::{
77
ResumptionFailedError, Store, StoreEvent, SubspaceId,
88
};
99

10+
mod ready_transport;
11+
pub use ready_transport::*;
12+
1013
/// Options to specify how ranges should be partitioned.
1114
#[derive(Debug, Clone, Copy)]
1215
pub struct PartitionOpts {

wgps/src/ready_transport.rs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use either::Either;
2+
use ufotofu::local_nb::BulkProducer;
3+
4+
/** When things go wrong while trying to make a WGPS transport ready. */
5+
#[derive(Debug)]
6+
pub enum ReadyTransportError<E: core::fmt::Display> {
7+
/** The transport returned an error of its own. */
8+
Transport(E),
9+
/** The received max payload power was invalid, i.e. greater than 64. */
10+
MaxPayloadInvalid,
11+
/** The transport stopped producing bytes before it could be deemed ready. */
12+
FinishedTooSoon,
13+
}
14+
15+
impl<E: core::fmt::Display> core::fmt::Display for ReadyTransportError<E> {
16+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17+
match self {
18+
ReadyTransportError::Transport(e) => write!(f, "{}", e),
19+
ReadyTransportError::MaxPayloadInvalid => write!(
20+
f,
21+
"The received max payload power was invalid, i.e. greater than 64."
22+
),
23+
ReadyTransportError::FinishedTooSoon => write!(
24+
f,
25+
"The transport stopped producing bytes before it could be deemed ready."
26+
),
27+
}
28+
}
29+
}
30+
31+
/** The result of intercepting the first few bytes of a WGPS transport. */
32+
#[derive(Debug)]
33+
#[allow(dead_code)] // TODO: Remove when this is used.
34+
pub(crate) struct ReadyTransport<
35+
const CHALLENGE_HASH_LENGTH: usize,
36+
E: core::fmt::Display,
37+
P: BulkProducer<Item = u8, Final = (), Error = E>,
38+
> {
39+
/** The maximum payload size which may be sent without being explicitly requested.*/
40+
pub maximum_payload_size: usize,
41+
/** The challenge hash of a nonce. */
42+
pub received_commitment: [u8; CHALLENGE_HASH_LENGTH],
43+
/** A 'ready' transport set to immediately produce encoded WGPS messages. */
44+
pub transport: P,
45+
}
46+
47+
impl<
48+
const CHALLENGE_HASH_LENGTH: usize,
49+
E: core::fmt::Display,
50+
P: BulkProducer<Item = u8, Final = (), Error = E>,
51+
> ReadyTransport<CHALLENGE_HASH_LENGTH, E, P>
52+
{
53+
#[allow(dead_code)] // TODO: Remove when this is used.
54+
pub(crate) fn transport(&self) -> &P {
55+
&self.transport
56+
}
57+
}
58+
59+
/** Given a producer of bytes which is to immediately produce the bytes corresponding to the WGPS' [maximum payload size](https://willowprotocol.org/specs/sync/index.html#peer_max_payload_size) and [received commitment](https://willowprotocol.org/specs/sync/index.html#received_commitment), returns the computed maximum payload size, received commitment, and a 'ready' transport set to produce encoded WGPS messages.
60+
*/
61+
#[allow(dead_code)] // TODO: Remove when this is used.
62+
pub(crate) async fn ready_transport<
63+
const CHALLENGE_HASH_LENGTH: usize,
64+
E: core::fmt::Display,
65+
P: BulkProducer<Item = u8, Final = (), Error = E>,
66+
>(
67+
mut transport: P,
68+
) -> Result<ReadyTransport<CHALLENGE_HASH_LENGTH, E, P>, ReadyTransportError<E>> {
69+
let maximum_payload_power = match transport.produce().await? {
70+
Either::Left(byte) => byte,
71+
Either::Right(_) => return Err(ReadyTransportError::FinishedTooSoon),
72+
};
73+
74+
if maximum_payload_power > 64 {
75+
return Err(ReadyTransportError::MaxPayloadInvalid);
76+
}
77+
78+
let maximum_payload_size = 2_usize.pow(maximum_payload_power as u32);
79+
80+
let mut received_commitment = [0_u8; CHALLENGE_HASH_LENGTH];
81+
82+
if let Err(e) = transport
83+
.bulk_overwrite_full_slice(&mut received_commitment)
84+
.await
85+
{
86+
match e.reason {
87+
Either::Left(_) => return Err(ReadyTransportError::FinishedTooSoon),
88+
Either::Right(e) => return Err(ReadyTransportError::Transport(e)),
89+
}
90+
};
91+
92+
Ok(ReadyTransport {
93+
maximum_payload_size,
94+
received_commitment,
95+
transport,
96+
})
97+
}
98+
99+
impl<E: core::fmt::Display> From<E> for ReadyTransportError<E> {
100+
fn from(value: E) -> Self {
101+
ReadyTransportError::Transport(value)
102+
}
103+
}
104+
105+
#[cfg(test)]
106+
mod tests {
107+
108+
use super::*;
109+
110+
use ufotofu::local_nb::producer::FromSlice;
111+
112+
#[test]
113+
fn empty_producer() {
114+
let empty_transport = FromSlice::new(&[]);
115+
116+
smol::block_on(async {
117+
let result = ready_transport::<4, _, _>(empty_transport).await;
118+
119+
assert!(matches!(result, Err(ReadyTransportError::FinishedTooSoon)))
120+
});
121+
}
122+
123+
#[test]
124+
fn only_power_producer() {
125+
let only_power_transport = FromSlice::new(&[0_u8]);
126+
127+
smol::block_on(async {
128+
let result = ready_transport::<4, _, _>(only_power_transport).await;
129+
130+
assert!(matches!(result, Err(ReadyTransportError::FinishedTooSoon)))
131+
});
132+
}
133+
134+
#[test]
135+
fn invalid_power_producer() {
136+
let only_power_transport = FromSlice::new(&[65_u8]);
137+
138+
smol::block_on(async {
139+
let result = ready_transport::<4, _, _>(only_power_transport).await;
140+
141+
assert!(matches!(
142+
result,
143+
Err(ReadyTransportError::MaxPayloadInvalid)
144+
))
145+
});
146+
}
147+
148+
#[test]
149+
fn invalid_power_producer_correct_length() {
150+
let only_power_transport = FromSlice::new(&[65_u8, 0, 0, 0, 0]);
151+
152+
smol::block_on(async {
153+
let result = ready_transport::<4, _, _>(only_power_transport).await;
154+
155+
assert!(matches!(
156+
result,
157+
Err(ReadyTransportError::MaxPayloadInvalid)
158+
))
159+
});
160+
}
161+
162+
#[test]
163+
fn commitment_too_short() {
164+
let only_power_transport = FromSlice::new(&[0_u8, 0]);
165+
166+
smol::block_on(async {
167+
let result = ready_transport::<4, _, _>(only_power_transport).await;
168+
169+
assert!(matches!(result, Err(ReadyTransportError::FinishedTooSoon)))
170+
});
171+
}
172+
173+
#[test]
174+
fn success() {
175+
let only_power_transport = FromSlice::new(&[1_u8, 1, 2, 3, 4, 5]);
176+
177+
smol::block_on(async {
178+
let result = ready_transport::<4, _, _>(only_power_transport).await;
179+
180+
if let Ok(ready) = result {
181+
assert!(ready.maximum_payload_size == 2);
182+
assert!(ready.received_commitment == [1, 2, 3, 4]);
183+
} else {
184+
panic!()
185+
}
186+
});
187+
}
188+
}

0 commit comments

Comments
 (0)