Skip to content

Commit

Permalink
WIP: validation for lazy_map
Browse files Browse the repository at this point in the history
  • Loading branch information
brentstone authored and tzemanovic committed Sep 22, 2022
1 parent dad616b commit 4b60051
Show file tree
Hide file tree
Showing 4 changed files with 761 additions and 3 deletions.
148 changes: 145 additions & 3 deletions shared/src/ledger/storage_api/collections/lazy_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
use std::marker::PhantomData;

use borsh::{BorshDeserialize, BorshSerialize};
use derivative::Derivative;
use thiserror::Error;

use super::super::Result;
use super::{LazyCollection, ReadError};
use crate::ledger::storage_api::validation::Data;
use crate::ledger::storage_api::validation::{self, Data};
use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite};
use crate::types::storage::{self, KeySeg};
use crate::ledger::vp_env::VpEnv;
use crate::types::storage::{self, DbKeySeg, KeySeg};

/// Subkey corresponding to the data elements of the LazyMap
pub const DATA_SUBKEY: &str = "data";
Expand Down Expand Up @@ -52,12 +55,21 @@ pub enum SubKeyWithData<K, V> {
/// the methods that have `StorageWrite` access.
/// TODO: In a nested collection, `V` may be an action inside the nested
/// collection.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub enum Action<K, V> {
/// Insert or update a value `V` at key `K` in a [`LazyMap<K, V>`].
Insert(K, V),
/// Remove a value `V` at key `K` from a [`LazyMap<K, V>`].
Remove(K, V),
/// Update a value `V` at key `K` in a [`LazyMap<K, V>`].
Update {
/// key at which the value is updated
key: K,
/// value before the update
pre: V,
/// value after the update
post: V,
},
}

/// TODO: In a nested collection, `V` may be an action inside the nested
Expand All @@ -70,6 +82,46 @@ pub enum Nested<K, V> {
Remove(K, V),
}

#[allow(missing_docs)]
#[derive(Error, Debug)]
pub enum ValidationError {
#[error("Storage error in reading key {0}")]
StorageError(storage::Key),
// #[error("Incorrect difference in LazyVec's length")]
// InvalidLenDiff,
// #[error("An empty LazyVec must be deleted from storage")]
// EmptyVecShouldBeDeleted,
// #[error("Push at a wrong index. Got {got}, expected {expected}.")]
// UnexpectedPushIndex { got: Index, expected: Index },
// #[error("Pop at a wrong index. Got {got}, expected {expected}.")]
// UnexpectedPopIndex { got: Index, expected: Index },
// #[error(
// "Update (combination of pop and push) at a wrong index. Got {got},
// \ expected {expected}."
// )]
// UnexpectedUpdateIndex { got: Index, expected: Index },
// #[error("An index has overflown its representation: {0}")]
// IndexOverflow(<usize as TryInto<Index>>::Error),
// #[error("Unexpected underflow in `{0} - {0}`")]
// UnexpectedUnderflow(Index, Index),
#[error("Invalid storage key {0}")]
InvalidSubKey(storage::Key),
}

/// [`LazyMap`] validation result
pub type ValidationResult<T> = std::result::Result<T, ValidationError>;

/// [`LazyMap`] validation builder from storage changes. The changes can be
/// accumulated with `LazyMap::validate()` and then turned into a list
/// of valid actions on the map with `ValidationBuilder::build()`.
#[derive(Debug, Derivative)]
// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound
#[derivative(Default(bound = ""))]
pub struct ValidationBuilder<K, V> {
/// The accumulator of found changes under the vector
pub changes: Vec<SubKeyWithData<K, V>>,
}

impl<K, V> LazyCollection for LazyMap<K, V>
where
K: storage::KeySeg,
Expand Down Expand Up @@ -247,6 +299,96 @@ where
) -> Result<()> {
storage.write(storage_key, val)
}

/// Check if the given storage key is a valid LazyMap sub-key and if so
/// return which one
pub fn is_valid_sub_key(
&self,
key: &storage::Key,
) -> storage_api::Result<Option<SubKey<K>>> {
let suffix = match key.split_prefix(&self.key) {
None => {
// not matching prefix, irrelevant
return Ok(None);
}
Some(None) => {
// no suffix, invalid
return Err(ValidationError::InvalidSubKey(key.clone()))
.into_storage_result();
}
Some(Some(suffix)) => suffix,
};

// Match the suffix against expected sub-keys
match &suffix.segments[..] {
[DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)]
if sub_a == DATA_SUBKEY =>
{
if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) {
Ok(Some(SubKey::Data(key_in_kv)))
} else {
Err(ValidationError::InvalidSubKey(key.clone()))
.into_storage_result()
}
}
_ => Err(ValidationError::InvalidSubKey(key.clone()))
.into_storage_result(),
}
}

/// Accumulate storage changes inside a [`ValidationBuilder`]. This is
/// typically done by the validity predicate while looping through the
/// changed keys. If the resulting `builder` is not `None`, one must
/// call `fn build()` on it to get the validation result.
/// This function will return `Ok(true)` if the storage key is a valid
/// sub-key of this collection, `Ok(false)` if the storage key doesn't match
/// the prefix of this collection, or fail with
/// [`ValidationError::InvalidSubKey`] if the prefix matches this
/// collection, but the key itself is not recognized.
pub fn accumulate<ENV>(
&self,
env: &ENV,
builder: &mut Option<ValidationBuilder<K, V>>,
key_changed: &storage::Key,
) -> storage_api::Result<bool>
where
ENV: for<'a> VpEnv<'a>,
{
if let Some(sub) = self.is_valid_sub_key(key_changed)? {
let SubKey::Data(key) = sub;
let data = validation::read_data(env, key_changed)?;
let change = data.map(|data| SubKeyWithData::Data(key, data));
if let Some(change) = change {
let builder =
builder.get_or_insert(ValidationBuilder::default());
builder.changes.push(change);
}
return Ok(true);
}
Ok(false)
}
}

impl<K, V> ValidationBuilder<K, V>
where
K: storage::KeySeg + Ord + Clone,
{
/// Build a list of actions from storage changes.
pub fn build(self) -> Vec<Action<K, V>> {
self.changes
.into_iter()
.map(|change| {
let SubKeyWithData::Data(key, data) = change;
match data {
Data::Add { post } => Action::Insert(key, post),
Data::Update { pre, post } => {
Action::Update { key, pre, post }
}
Data::Delete { pre } => Action::Remove(key, pre),
}
})
.collect()
}
}

#[cfg(test)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 59b8eaaf5d8e03e58b346ef229a2487f68fea488197420f150682f7275ce2b83 # shrinks to (initial_state, transitions) = (AbstractLazyMapState { valid_transitions: [], committed_transitions: [] }, [Insert(11178241982156558453, TestVal { x: 9618691367534591266, y: true }), CommitTx, Update(11178241982156558453, TestVal { x: 2635377083098935189, y: false }), Update(11178241982156558453, TestVal { x: 11485387163946255361, y: false }), Insert(4380901092919801530, TestVal { x: 17235291421018840542, y: false }), Update(11178241982156558453, TestVal { x: 1936190700145956620, y: false }), Update(11178241982156558453, TestVal { x: 6934621224353358508, y: false }), Update(11178241982156558453, TestVal { x: 16175036327810390362, y: true }), Remove(5606457884982633480), Insert(7124206407862523505, TestVal { x: 5513772825695605555, y: true }), CommitTxAndBlock, CommitTx, Insert(13347045100814804679, TestVal { x: 5157295776286367034, y: false }), Update(7124206407862523505, TestVal { x: 1989909525753197955, y: false }), Update(4380901092919801530, TestVal { x: 13085578877588425331, y: false }), Update(7124206407862523505, TestVal { x: 1620781139263176467, y: true }), Insert(5806457332157050619, TestVal { x: 14632354209749334932, y: true }), Remove(1613213961397167063), Update(7124206407862523505, TestVal { x: 3848976302483310370, y: true }), Update(4380901092919801530, TestVal { x: 15281186775251770467, y: false }), Remove(5303306623647571548), Insert(5905425607805327902, TestVal { x: 1274794101048822414, y: false }), Insert(2305446651611241243, TestVal { x: 7872403441503057017, y: true }), Insert(2843165193114615911, TestVal { x: 13698490566286768452, y: false }), Insert(3364298091459048760, TestVal { x: 8891279000465212397, y: true }), CommitTx, Insert(17278527568142155478, TestVal { x: 8166151895050476136, y: false }), Remove(9206713523174765253), Remove(1148985045479283759), Insert(13346103305566843535, TestVal { x: 13148026974798633058, y: true }), Remove(17185699086139524651), CommitTx, Update(7124206407862523505, TestVal { x: 3047872255943216792, y: false }), CommitTxAndBlock, CommitTxAndBlock, Remove(4672009405538026945), Update(5905425607805327902, TestVal { x: 6635343936644805461, y: false }), Insert(14100441716981493843, TestVal { x: 8068697312326956479, y: true }), Insert(8370580326875672309, TestVal { x: 18416630552728813406, y: false }), Update(2305446651611241243, TestVal { x: 3777718192999015176, y: false }), Remove(1532142753559370584), Remove(10097030807802775125), Insert(10080356901530935857, TestVal { x: 17171047520093964037, y: false }), Update(3364298091459048760, TestVal { x: 702372485798608773, y: true }), Insert(5504969092734638033, TestVal { x: 314752460808087203, y: true }), Remove(5486040497128339175), Insert(7884678026881625058, TestVal { x: 4313610278903495077, y: true }), CommitTx, Insert(11228024342874184864, TestVal { x: 428512502841968552, y: false }), Insert(4684666745142518471, TestVal { x: 13122515680485564107, y: true }), Remove(14243063045921130600), Remove(4530767959521683042), Insert(10236349778753659715, TestVal { x: 3138294567956031715, y: true }), Update(2305446651611241243, TestVal { x: 8133236604817109805, y: false }), Update(2843165193114615911, TestVal { x: 12001998927296899868, y: false }), CommitTxAndBlock, CommitTx, CommitTxAndBlock])
Loading

0 comments on commit 4b60051

Please sign in to comment.