-
Notifications
You must be signed in to change notification settings - Fork 85
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
feat: Namespace table enforce final offset equals payload length #1642
Changes from 5 commits
b430399
7325b5f
cc5b20a
812c795
6813947
50c2abc
a0eb396
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -144,7 +144,9 @@ impl<'de> Deserialize<'de> for NsTable { | |
D: Deserializer<'de>, | ||
{ | ||
let unchecked = NsTable::deserialize(deserializer)?; | ||
unchecked.validate().map_err(de::Error::custom)?; | ||
unchecked | ||
.validate_deserialization_invariants() | ||
.map_err(de::Error::custom)?; | ||
Ok(unchecked) | ||
} | ||
} | ||
|
@@ -221,46 +223,24 @@ impl NsTable { | |
/// must be nonzero. | ||
/// 3. Header consistent with byte length (obsolete after | ||
/// <https://github.com/EspressoSystems/espresso-sequencer/issues/1604>) | ||
pub fn validate(&self) -> Result<(), NsTableValidationError> { | ||
/// 4. Final offset must equal `payload_byte_len` (obsolete after | ||
/// <https://github.com/EspressoSystems/espresso-sequencer/issues/1604>) | ||
pub fn validate( | ||
&self, | ||
payload_byte_len: &PayloadByteLen, | ||
) -> Result<(), NsTableValidationError> { | ||
use NsTableValidationError::*; | ||
|
||
// Byte length for a table with `x` entries must be exactly | ||
// `x * NsTableBuilder::entry_byte_len() + NsTableBuilder::header_byte_len()` | ||
if self.bytes.len() < NsTableBuilder::header_byte_len() | ||
|| (self.bytes.len() - NsTableBuilder::header_byte_len()) | ||
% NsTableBuilder::entry_byte_len() | ||
!= 0 | ||
{ | ||
return Err(InvalidByteLen); | ||
} | ||
|
||
// Header must declare the correct number of namespaces | ||
// | ||
// TODO this check obsolete after | ||
// https://github.com/EspressoSystems/espresso-sequencer/issues/1604 | ||
if self.len().0 != self.read_num_nss() { | ||
return Err(InvalidHeader); | ||
} | ||
// conditions 1-3 | ||
self.validate_deserialization_invariants()?; | ||
|
||
// Namespace IDs and offsets must increase monotonically. Offsets must | ||
// be nonzero. | ||
{ | ||
let (mut prev_ns_id, mut prev_offset) = (None, 0); | ||
for (ns_id, offset) in self.iter().map(|i| { | ||
( | ||
self.read_ns_id_unchecked(&i), | ||
self.read_ns_offset_unchecked(&i), | ||
) | ||
}) { | ||
if let Some(prev_ns_id) = prev_ns_id { | ||
if ns_id <= prev_ns_id { | ||
return Err(NonIncreasingEntries); | ||
} | ||
} | ||
if offset <= prev_offset { | ||
return Err(NonIncreasingEntries); | ||
} | ||
(prev_ns_id, prev_offset) = (Some(ns_id), offset); | ||
// condition 4 | ||
let len = self.len().0; | ||
if len > 0 { | ||
let final_ns_index = NsIndex(len - 1); | ||
let final_offset = self.read_ns_offset_unchecked(&final_ns_index); | ||
if final_offset != payload_byte_len.as_usize() { | ||
return Err(InvalidFinalOffset); | ||
} | ||
} | ||
|
||
|
@@ -306,6 +286,57 @@ impl NsTable { | |
index.0 * (NS_ID_BYTE_LEN + NS_OFFSET_BYTE_LEN) + NUM_NSS_BYTE_LEN + NS_ID_BYTE_LEN; | ||
usize_from_bytes::<NS_OFFSET_BYTE_LEN>(&self.bytes[start..start + NS_OFFSET_BYTE_LEN]) | ||
} | ||
|
||
/// Helper for [`NsTable::validate`], used in our custom [`serde`] | ||
/// implementation. | ||
/// | ||
/// Checks conditions 1-3 of [`NsTable::validate`]. Those conditions can be | ||
/// checked by looking only at the contents of the [`NsTable`]. | ||
fn validate_deserialization_invariants(&self) -> Result<(), NsTableValidationError> { | ||
use NsTableValidationError::*; | ||
|
||
// Byte length for a table with `x` entries must be exactly | ||
// `x * NsTableBuilder::entry_byte_len() + NsTableBuilder::header_byte_len()` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is described in the comment and the condition of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe they are equivalent. Explainer comment added in 50c2abc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! |
||
if self.bytes.len() < NsTableBuilder::header_byte_len() | ||
|| (self.bytes.len() - NsTableBuilder::header_byte_len()) | ||
% NsTableBuilder::entry_byte_len() | ||
!= 0 | ||
{ | ||
return Err(InvalidByteLen); | ||
} | ||
|
||
// Header must declare the correct number of namespaces | ||
// | ||
// TODO this check obsolete after | ||
// https://github.com/EspressoSystems/espresso-sequencer/issues/1604 | ||
if self.len().0 != self.read_num_nss() { | ||
return Err(InvalidHeader); | ||
} | ||
|
||
// Namespace IDs and offsets must increase monotonically. Offsets must | ||
// be nonzero. | ||
{ | ||
let (mut prev_ns_id, mut prev_offset) = (None, 0); | ||
for (ns_id, offset) in self.iter().map(|i| { | ||
( | ||
self.read_ns_id_unchecked(&i), | ||
self.read_ns_offset_unchecked(&i), | ||
) | ||
}) { | ||
if let Some(prev_ns_id) = prev_ns_id { | ||
if ns_id <= prev_ns_id { | ||
return Err(NonIncreasingEntries); | ||
} | ||
} | ||
if offset <= prev_offset { | ||
return Err(NonIncreasingEntries); | ||
} | ||
(prev_ns_id, prev_offset) = (Some(ns_id), offset); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
impl EncodeBytes for NsTable { | ||
|
@@ -332,6 +363,7 @@ pub enum NsTableValidationError { | |
InvalidByteLen, | ||
NonIncreasingEntries, | ||
InvalidHeader, // TODO this variant obsolete after https://github.com/EspressoSystems/espresso-sequencer/issues/1604 | ||
InvalidFinalOffset, // TODO this variant obsolete after https://github.com/EspressoSystems/espresso-sequencer/issues/1604 | ||
} | ||
|
||
pub struct NsTableBuilder { | ||
|
@@ -379,7 +411,7 @@ impl NsTableBuilder { | |
} | ||
|
||
/// Index for an entry in a ns table. | ||
#[derive(Clone, Debug, Eq, Hash, PartialEq)] | ||
#[derive(Clone, Debug, Display, Eq, Hash, PartialEq)] | ||
pub struct NsIndex(usize); | ||
bytes_serde_impl!(NsIndex, to_bytes, [u8; NUM_NSS_BYTE_LEN], from_bytes); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if
len == 0
? Do we want to check thatpayload_byte_len == 0
as well?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that's a good idea. New check and tests added in a0eb396.