Skip to content

Commit 276a027

Browse files
authored
[consensus/coding] Prevent Proposer Withholding (#3338)
1 parent 2a6e7ba commit 276a027

24 files changed

Lines changed: 2098 additions & 1679 deletions

coding/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,13 @@ path = "src/benches/bench.rs"
6060
name = "coding_scheme_sizes"
6161
harness = false
6262
path = "src/benches/bench_size.rs"
63+
64+
[[bench]]
65+
name = "phased_coding_scheme_times"
66+
harness = false
67+
path = "src/benches/bench_phased.rs"
68+
69+
[[bench]]
70+
name = "phased_coding_scheme_sizes"
71+
harness = false
72+
path = "src/benches/bench_phased_size.rs"

coding/conformance.toml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
["commonware_coding::no_coding::conformance::CodecConformance<StrongShard>"]
2-
n_cases = 65536
3-
hash = "edc22446bb2952609d0c8daccf2d22f8ad2b71eedfcf45296c3f4db49d78404a"
4-
5-
["commonware_coding::no_coding::conformance::CodecConformance<WeakShard>"]
6-
n_cases = 65536
7-
hash = "07854d2fef297a06ba81685e660c332de36d5d18d546927d30daad6d7fda1541"
8-
91
["commonware_coding::reed_solomon::tests::conformance::CodecConformance<Chunk<Sha256Digest>>"]
102
n_cases = 65536
113
hash = "45545e4d4aeb18b8bdb019e630fb9a1fa6dda9ed32b2d529a9213ec07ccab07c"

coding/fuzz/Cargo.toml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,6 @@ test = false
2929
doc = false
3030
bench = false
3131

32-
[[bin]]
33-
name = "no_coding"
34-
path = "fuzz_targets/no_coding.rs"
35-
test = false
36-
doc = false
37-
bench = false
38-
3932
[[bin]]
4033
name = "zoda"
4134
path = "fuzz_targets/zoda.rs"

coding/fuzz/fuzz_targets/no_coding.rs

Lines changed: 0 additions & 10 deletions
This file was deleted.

coding/fuzz/fuzz_targets/zoda.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#![no_main]
22

33
use commonware_coding::Zoda;
4-
use commonware_coding_fuzz::{fuzz, FuzzInput};
4+
use commonware_coding_fuzz::{fuzz_phased, FuzzInput};
55
use commonware_cryptography::Sha256;
66
use libfuzzer_sys::fuzz_target;
77

88
fuzz_target!(|input: FuzzInput| {
9-
fuzz::<Zoda<Sha256>>(input);
9+
fuzz_phased::<Zoda<Sha256>>(input);
1010
});

coding/fuzz/src/lib.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use arbitrary::{Arbitrary, Unstructured};
2-
use commonware_coding::{Config, Scheme};
2+
use commonware_coding::{Config, PhasedScheme, Scheme};
33
use commonware_parallel::Sequential;
44
use commonware_utils::NZU16;
55
use std::iter;
@@ -74,16 +74,45 @@ pub fn fuzz<S: Scheme>(input: FuzzInput) {
7474
};
7575
let (commitment, shards) = S::encode(&config, data.as_slice(), &STRATEGY).unwrap();
7676
assert_eq!(shards.len(), (recovery + min) as usize);
77-
// We don't use enumerate to get u16s.
7877
let mut shards = (0u16..).zip(shards).collect::<Vec<_>>();
7978
shuffle.shuffle(&mut shards);
79+
80+
// Check and collect `to_use` shards.
81+
let checked_shards = shards
82+
.into_iter()
83+
.take(to_use as usize)
84+
.map(|(i, shard)| S::check(&config, &commitment, i, &shard).unwrap())
85+
.collect::<Vec<_>>();
86+
87+
let decoded = S::decode(&config, &commitment, checked_shards.iter(), &STRATEGY).unwrap();
88+
assert_eq!(&decoded, &data);
89+
}
90+
91+
pub fn fuzz_phased<S: PhasedScheme>(input: FuzzInput) {
92+
let FuzzInput {
93+
recovery,
94+
min,
95+
to_use,
96+
data,
97+
shuffle,
98+
} = input;
99+
100+
let config = Config {
101+
minimum_shards: NZU16!(min),
102+
extra_shards: NZU16!(recovery),
103+
};
104+
let (commitment, shards) = S::encode(&config, data.as_slice(), &STRATEGY).unwrap();
105+
assert_eq!(shards.len(), (recovery + min) as usize);
106+
let mut shards = (0u16..).zip(shards).collect::<Vec<_>>();
107+
shuffle.shuffle(&mut shards);
108+
80109
// From here on, we take the point of view of the last participant.
81-
// (This is so that we can move their shard out of the vector easily).
110+
// This lets us move our strong shard out directly while keeping the rest for forwarding.
82111
let (my_i, my_shard) = shards.pop().unwrap();
83112
let (my_checking_data, my_checked_shard, _) =
84113
S::weaken(&config, &commitment, my_i, my_shard).unwrap();
85114

86-
// Check to_use - 1 shards, then include our own checked shards.
115+
// Check `to_use - 1` forwarded shards, then include our own checked shard.
87116
let checked_shards = shards
88117
.into_iter()
89118
.take((to_use - 1) as usize)
@@ -98,7 +127,7 @@ pub fn fuzz<S: Scheme>(input: FuzzInput) {
98127
&config,
99128
&commitment,
100129
my_checking_data,
101-
&checked_shards,
130+
checked_shards.iter(),
102131
&STRATEGY,
103132
)
104133
.unwrap();

coding/src/benches/bench.rs

Lines changed: 17 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ use commonware_utils::{NZUsize, NZU16};
44
use criterion::{criterion_main, BatchSize, Criterion};
55
use rand::{RngCore, SeedableRng as _};
66
use rand_chacha::ChaCha8Rng;
7+
use shard_selection::SELECTIONS;
78

8-
mod no_coding;
99
mod reed_solomon;
10+
mod shard_selection;
1011
mod zoda;
1112

1213
pub(crate) fn bench_encode_generic<S: Scheme>(name: &str, c: &mut Criterion) {
@@ -48,52 +49,6 @@ pub(crate) fn bench_encode_generic<S: Scheme>(name: &str, c: &mut Criterion) {
4849
}
4950
}
5051

51-
#[derive(Clone, Copy)]
52-
pub(crate) enum ShardSelection {
53-
Best,
54-
Exception,
55-
Worst,
56-
Interleaved,
57-
}
58-
59-
impl ShardSelection {
60-
const fn label(self) -> &'static str {
61-
match self {
62-
Self::Best => "best",
63-
Self::Exception => "exception",
64-
Self::Worst => "worst",
65-
Self::Interleaved => "interleaved",
66-
}
67-
}
68-
69-
fn indices(self, min: u16) -> Vec<u16> {
70-
match self {
71-
Self::Best => (0..min).collect(),
72-
Self::Exception => (1..=min).collect(),
73-
Self::Worst => (min..min + min).collect(),
74-
Self::Interleaved => (0..min)
75-
.map(|i| {
76-
let k = i / 2;
77-
// Alternate between original shard indices [0, min) and
78-
// recovery shard indices [min, 2 * min).
79-
if i % 2 == 0 {
80-
k
81-
} else {
82-
min + k
83-
}
84-
})
85-
.collect(),
86-
}
87-
}
88-
}
89-
90-
const SELECTIONS: [ShardSelection; 4] = [
91-
ShardSelection::Best,
92-
ShardSelection::Exception,
93-
ShardSelection::Worst,
94-
ShardSelection::Interleaved,
95-
];
96-
9752
pub(crate) fn bench_decode_generic<S: Scheme>(name: &str, c: &mut Criterion) {
9853
let mut rng = ChaCha8Rng::seed_from_u64(0);
9954
let cases = [20, 22, 23].map(|i| 2usize.pow(i));
@@ -120,74 +75,44 @@ pub(crate) fn bench_decode_generic<S: Scheme>(name: &str, c: &mut Criterion) {
12075
rng.fill_bytes(&mut data);
12176

12277
// Encode data
123-
let (commitment, mut shards) = if conc > 1 {
78+
let (commitment, shards) = if conc > 1 {
12479
S::encode(&config, data.as_slice(), &strategy).unwrap()
12580
} else {
12681
S::encode(&config, data.as_slice(), &Sequential).unwrap()
12782
};
12883

129-
// Get my shard
130-
let my_shard = shards.pop().unwrap();
13184
let indices = selection.indices(min);
132-
let mut opt_shards: Vec<Option<_>> =
133-
shards.into_iter().map(Some).collect();
134-
let weak_shards: Vec<(u16, _)> = indices
85+
let selected_shards: Vec<(u16, _)> = indices
13586
.iter()
13687
.map(|&i| {
137-
let shard =
138-
opt_shards[i as usize].take().unwrap();
139-
let (_, _, weak_shard) =
140-
S::weaken(&config, &commitment, i, shard)
141-
.unwrap();
142-
(i, weak_shard)
88+
(i, shards[i as usize].clone())
14389
})
14490
.collect();
14591

146-
(commitment, my_shard, weak_shards)
147-
},
148-
|(commitment, my_shard, weak_shards)| {
149-
// Weaken my shard
150-
let (checking_data, _, _) = S::weaken(
151-
&config,
152-
&commitment,
153-
config.minimum_shards.get()
154-
+ config.extra_shards.get()
155-
- 1,
156-
my_shard,
157-
)
158-
.unwrap();
159-
160-
// Check shards
161-
let checked_shards = weak_shards
162-
.into_iter()
163-
.map(|(idx, weak_shard)| {
164-
S::check(
165-
&config,
166-
&commitment,
167-
&checking_data,
168-
idx,
169-
weak_shard,
170-
)
171-
.unwrap()
92+
let checked_shards: Vec<_> = selected_shards
93+
.iter()
94+
.map(|(idx, shard)| {
95+
S::check(&config, &commitment, *idx, shard).unwrap()
17296
})
173-
.collect::<Vec<_>>();
97+
.collect();
17498

99+
(commitment, checked_shards)
100+
},
101+
|(commitment, checked_shards)| {
175102
// Decode data
176103
if conc > 1 {
177104
S::decode(
178105
&config,
179106
&commitment,
180-
checking_data,
181-
&checked_shards,
107+
checked_shards.iter(),
182108
&strategy,
183109
)
184-
.unwrap()
110+
.unwrap()
185111
} else {
186112
S::decode(
187113
&config,
188114
&commitment,
189-
checking_data,
190-
&checked_shards,
115+
checked_shards.iter(),
191116
&Sequential,
192117
)
193118
.unwrap()
@@ -203,4 +128,4 @@ pub(crate) fn bench_decode_generic<S: Scheme>(name: &str, c: &mut Criterion) {
203128
}
204129
}
205130

206-
criterion_main!(reed_solomon::benches, no_coding::benches, zoda::benches);
131+
criterion_main!(reed_solomon::benches, zoda::benches);

0 commit comments

Comments
 (0)