Skip to content

Commit 5c96f58

Browse files
authored
some erasure-coding tweaks (paritytech#143)
1 parent 66c9580 commit 5c96f58

1 file changed

Lines changed: 68 additions & 34 deletions

File tree

erasure-coding/src/lib.rs

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ mod wrapped_shard;
4545
const MAX_VALIDATORS: usize = <galois_16::Field as reed_solomon::Field>::ORDER;
4646

4747
/// Errors in erasure coding.
48-
#[derive(Debug, Clone)]
48+
#[derive(Debug, Clone, PartialEq)]
4949
pub enum Error {
5050
/// Returned when there are too many validators.
5151
TooManyValidators,
@@ -71,6 +71,7 @@ pub enum Error {
7171
BranchOutOfBounds,
7272
}
7373

74+
#[derive(Debug, PartialEq)]
7475
struct CodeParams {
7576
data_shards: usize,
7677
parity_shards: usize,
@@ -79,36 +80,27 @@ struct CodeParams {
7980
impl CodeParams {
8081
// the shard length needed for a payload with initial size `base_len`.
8182
fn shard_len(&self, base_len: usize) -> usize {
82-
(base_len / self.data_shards) + (base_len % self.data_shards)
83+
// how many bytes we actually need.
84+
let needed_shard_len = base_len / self.data_shards
85+
+ (base_len % self.data_shards != 0) as usize;
86+
87+
// round up to next even number
88+
// (no actual space overhead since we are working in GF(2^16)).
89+
needed_shard_len + needed_shard_len % 2
8390
}
8491

8592
fn make_shards_for(&self, payload: &[u8]) -> Vec<WrappedShard> {
8693
let shard_len = self.shard_len(payload.len());
8794
let mut shards = vec![
88-
WrappedShard::new(vec![0; shard_len + 4]);
95+
WrappedShard::new(vec![0; shard_len]);
8996
self.data_shards + self.parity_shards
9097
];
9198

9299
for (data_chunk, blank_shard) in payload.chunks(shard_len).zip(&mut shards) {
93-
let blank_shard: &mut [u8] = blank_shard.as_mut();
94-
let (len_slice, blank_shard) = blank_shard.split_at_mut(4);
95-
let len = ::std::cmp::min(data_chunk.len(), blank_shard.len());
96-
97-
// prepend the length to each data shard. this will tell us how much
98-
// we need to read.
99-
//
100-
// this is necessary because we are doing RS encoding with 16-bit words,
101-
// but the payload is a byte-slice. We need to know how much data
102-
// to read from each shard when reconstructing.
103-
//
104-
// TODO: could be done more efficiently by pushing extra bytes onto the
105-
// end. https://github.com/paritytech/polkadot/issues/88
106-
(len as u32).using_encoded(|s| {
107-
len_slice.copy_from_slice(s)
108-
});
109-
110100
// fill the empty shards with the corresponding piece of the payload,
111101
// zero-padded to fit in the shards.
102+
let len = std::cmp::min(shard_len, data_chunk.len());
103+
let blank_shard: &mut [u8] = blank_shard.as_mut();
112104
blank_shard[..len].copy_from_slice(&data_chunk[..len]);
113105
}
114106

@@ -137,7 +129,7 @@ fn code_params(n_validators: usize) -> Result<CodeParams, Error> {
137129

138130
/// Obtain erasure-coded chunks, one for each validator.
139131
///
140-
/// Works only up to 256 validators, and `n_validators` must be non-zero.
132+
/// Works only up to 65536 validators, and `n_validators` must be non-zero.
141133
pub fn obtain_chunks(n_validators: usize, block_data: &BlockData, extrinsic: &Extrinsic)
142134
-> Result<Vec<Vec<u8>>, Error>
143135
{
@@ -162,7 +154,7 @@ pub fn obtain_chunks(n_validators: usize, block_data: &BlockData, extrinsic: &Ex
162154
/// The indices of the present chunks must be indicated. If too few chunks
163155
/// are provided, recovery is not possible.
164156
///
165-
/// Works only up to 256 validators, and `n_validators` must be non-zero.
157+
/// Works only up to 65536 validators, and `n_validators` must be non-zero.
166158
pub fn reconstruct<'a, I: 'a>(n_validators: usize, chunks: I)
167159
-> Result<(BlockData, Extrinsic), Error>
168160
where I: IntoIterator<Item=(&'a [u8], usize)>
@@ -201,22 +193,12 @@ pub fn reconstruct<'a, I: 'a>(n_validators: usize, chunks: I)
201193

202194
// lazily decode from the data shards.
203195
Decode::decode(&mut ShardInput {
196+
cur_shard: None,
204197
shards: shards.iter()
205198
.map(|x| x.as_ref())
206199
.take(params.data_shards)
207200
.map(|x| x.expect("all data shards have been recovered; qed"))
208-
.filter_map(|x| {
209-
let mut s: &[u8] = x.as_ref();
210-
let data_len = u32::decode(&mut s)? as usize;
211-
212-
// NOTE: s has been mutated to point forward by `decode`.
213-
if s.len() < data_len {
214-
None
215-
} else {
216-
Some(&s[..data_len])
217-
}
218-
}),
219-
cur_shard: None,
201+
.map(|x| x.as_ref()),
220202
}).ok_or_else(|| Error::BadPayload)
221203
}
222204

@@ -357,6 +339,58 @@ mod tests {
357339
assert_eq!(MAX_VALIDATORS, 65536);
358340
}
359341

342+
#[test]
343+
fn test_code_params() {
344+
assert_eq!(code_params(0), Err(Error::EmptyValidators));
345+
346+
assert_eq!(code_params(1), Ok(CodeParams {
347+
data_shards: 1,
348+
parity_shards: 0,
349+
}));
350+
351+
assert_eq!(code_params(2), Ok(CodeParams {
352+
data_shards: 1,
353+
parity_shards: 1,
354+
}));
355+
356+
assert_eq!(code_params(3), Ok(CodeParams {
357+
data_shards: 1,
358+
parity_shards: 2,
359+
}));
360+
361+
assert_eq!(code_params(4), Ok(CodeParams {
362+
data_shards: 2,
363+
parity_shards: 2,
364+
}));
365+
366+
assert_eq!(code_params(100), Ok(CodeParams {
367+
data_shards: 34,
368+
parity_shards: 66,
369+
}));
370+
}
371+
372+
#[test]
373+
fn shard_len_is_reasonable() {
374+
let mut params = CodeParams {
375+
data_shards: 5,
376+
parity_shards: 0, // doesn't affect calculation.
377+
};
378+
379+
assert_eq!(params.shard_len(100), 20);
380+
assert_eq!(params.shard_len(99), 20);
381+
382+
// see if it rounds up to 2.
383+
assert_eq!(params.shard_len(95), 20);
384+
assert_eq!(params.shard_len(94), 20);
385+
386+
assert_eq!(params.shard_len(89), 18);
387+
388+
params.data_shards = 7;
389+
390+
// needs 3 bytes to fit, rounded up to next even number.
391+
assert_eq!(params.shard_len(19), 4);
392+
}
393+
360394
#[test]
361395
fn round_trip_block_data() {
362396
let block_data = BlockData((0..255).collect());

0 commit comments

Comments
 (0)