Skip to content

Commit

Permalink
Bug fix: StateMap::Keys are not consistent across platforms (#804)
Browse files Browse the repository at this point in the history
* Bug fix: Introduce EncodeLike trait.

This PR removes our StateMap key encoding based on std::Hash, which was
not consistent across platforms. Instead, this PR introduces the
`EncodeLike<Ref, Target>` trait which marks that Ref can be encoded
like Target by the implementing codec.

This PR also removes the SingletonKey type, which required special
handling in codecs. Instead, of using this placeholder, this PR
implements auxiliary methods on the working set for dealing with
singletons

* Remove rollup config changes

* fix test: qualify conversion

* Add missing bounds for fuzzing

* fix docs

* clarify zsts in comment

* allow separate codecs for keys/values

* Split key and value codecs

* Fix fuzzing feature

* Introduce StateCodec trait to allow EncodeLike with SplitCodec

* add doc comments

* Fix fuzz and test targets

* Fix test

* fix docs
  • Loading branch information
preston-evans98 authored Sep 8, 2023
1 parent 6b93031 commit 44db227
Show file tree
Hide file tree
Showing 17 changed files with 520 additions and 216 deletions.
21 changes: 17 additions & 4 deletions examples/demo-prover/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 13 additions & 4 deletions examples/demo-prover/methods/guest/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::panic::catch_unwind;

use sov_modules_api::default_context::DefaultContext;
use sov_modules_api::{Context, ModuleInfo};
use sov_state::codec::StateValueCodec;
use sov_state::codec::{StateCodec, StateKeyCodec, StateValueCodec};
use sov_state::{DefaultStorageSpec, ProverStorage, StateValue, WorkingSet};

#[derive(ModuleInfo)]
Expand All @@ -26,6 +26,23 @@ impl CustomCodec {
}
}

impl StateCodec for CustomCodec {
type KeyCodec = Self;
type ValueCodec = Self;
fn key_codec(&self) -> &Self::KeyCodec {
self
}
fn value_codec(&self) -> &Self::ValueCodec {
self
}
}

impl<K> StateKeyCodec<K> for CustomCodec {
fn encode_key(&self, _key: &K) -> Vec<u8> {
unimplemented!()
}
}

impl<V> StateValueCodec<V> for CustomCodec {
type Error = String;

Expand Down
24 changes: 23 additions & 1 deletion module-system/sov-state/src/codec/bcs_codec.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
use super::{StateCodec, StateKeyCodec};
use crate::codec::StateValueCodec;

/// A [`StateValueCodec`] that uses [`bcs`] for all keys and values.
#[derive(Debug, Default, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
pub struct BcsCodec;

impl<K> StateKeyCodec<K> for BcsCodec
where
K: serde::Serialize,
{
fn encode_key(&self, key: &K) -> Vec<u8> {
bcs::to_bytes(key).expect("Failed to serialize key")
}
}

impl<V> StateValueCodec<V> for BcsCodec
where
V: serde::Serialize + for<'a> serde::Deserialize<'a>,
{
type Error = bcs::Error;

fn encode_value(&self, value: &V) -> Vec<u8> {
bcs::to_bytes(value).expect("Failed to serialize key")
bcs::to_bytes(value).expect("Failed to serialize value")
}

fn try_decode_value(&self, bytes: &[u8]) -> Result<V, Self::Error> {
bcs::from_bytes(bytes)
}
}

impl StateCodec for BcsCodec {
type KeyCodec = Self;
type ValueCodec = Self;
fn key_codec(&self) -> &Self::KeyCodec {
self
}

fn value_codec(&self) -> &Self::ValueCodec {
self
}
}
22 changes: 22 additions & 0 deletions module-system/sov-state/src/codec/borsh_codec.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
use super::{StateCodec, StateKeyCodec};
use crate::codec::StateValueCodec;

/// A [`StateValueCodec`] that uses [`borsh`] for all values.
#[derive(Debug, Default, PartialEq, Eq, Clone, borsh::BorshDeserialize, borsh::BorshSerialize)]
pub struct BorshCodec;

impl<K> StateKeyCodec<K> for BorshCodec
where
K: borsh::BorshSerialize + borsh::BorshDeserialize,
{
fn encode_key(&self, value: &K) -> Vec<u8> {
value.try_to_vec().expect("Failed to serialize value")
}
}

impl<V> StateValueCodec<V> for BorshCodec
where
V: borsh::BorshSerialize + borsh::BorshDeserialize,
Expand All @@ -18,3 +28,15 @@ where
V::try_from_slice(bytes)
}
}

impl StateCodec for BorshCodec {
type KeyCodec = Self;
type ValueCodec = Self;
fn key_codec(&self) -> &Self::KeyCodec {
self
}

fn value_codec(&self) -> &Self::ValueCodec {
self
}
}
23 changes: 23 additions & 0 deletions module-system/sov-state/src/codec/json_codec.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
use serde_json;

use super::{StateCodec, StateKeyCodec};
use crate::codec::StateValueCodec;

/// A [`StateValueCodec`] that uses [`serde_json`] for all values.
#[derive(Debug, Default, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
pub struct JsonCodec;

impl<K> StateKeyCodec<K> for JsonCodec
where
K: serde::Serialize,
{
fn encode_key(&self, key: &K) -> Vec<u8> {
serde_json::to_vec(key).expect("Failed to serialize value")
}
}

impl<V> StateValueCodec<V> for JsonCodec
where
V: serde::Serialize + for<'a> serde::Deserialize<'a>,
Expand All @@ -20,3 +30,16 @@ where
serde_json::from_slice(bytes)
}
}

impl StateCodec for JsonCodec {
type KeyCodec = Self;
type ValueCodec = Self;

fn key_codec(&self) -> &Self::KeyCodec {
self
}

fn value_codec(&self) -> &Self::ValueCodec {
self
}
}
65 changes: 65 additions & 0 deletions module-system/sov-state/src/codec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
mod bcs_codec;
mod borsh_codec;
mod json_codec;
mod split_codec;

pub use bcs_codec::BcsCodec;
use borsh::BorshSerialize;
pub use borsh_codec::BorshCodec;
pub use json_codec::JsonCodec;

Expand Down Expand Up @@ -42,3 +44,66 @@ pub trait StateValueCodec<V> {
.unwrap()
}
}

/// A trait for types that can serialize keys for storage
/// access.
pub trait StateKeyCodec<K> {
fn encode_key(&self, key: &K) -> Vec<u8>;
}

/// A trait for types that can serialize keys and values, as well
/// as deserializing values for storage access.
pub trait StateCodec {
/// The codec used to serialize keys
type KeyCodec;
/// The codec used to serialize and deserialize values
type ValueCodec;

/// Returns a reference to the type's key codec
fn key_codec(&self) -> &Self::KeyCodec;
/// Returns a reference to the type's value codec
fn value_codec(&self) -> &Self::ValueCodec;
}

/// A trait for codecs which know how to serialize a type `Ref` as if it were
/// some other type `Target`.
///
/// A good example of this is [`BorshCodec`], which knows how to serialize a
/// `[T;N]` as if it were a `Vec<T>` even though the two types have different
/// encodings by default.
pub trait EncodeKeyLike<Ref: ?Sized, Target> {
/// Encodes a reference to `Ref` as if it were a reference to `Target`.
fn encode_key_like(&self, borrowed: &Ref) -> Vec<u8>;
}

// All items can be encoded like themselves by all codecs
impl<C, T> EncodeKeyLike<T, T> for C
where
C: StateKeyCodec<T>,
{
fn encode_key_like(&self, borrowed: &T) -> Vec<u8> {
self.encode_key(borrowed)
}
}

// In borsh, a slice is encoded the same way as a vector except in edge case where
// T is zero-sized, in which case Vec<T> is not borsh encodable.
impl<T> EncodeKeyLike<[T], Vec<T>> for BorshCodec
where
T: BorshSerialize,
{
fn encode_key_like(&self, borrowed: &[T]) -> Vec<u8> {
borrowed.try_to_vec().unwrap()
}
}

#[test]
fn test_borsh_slice_encode_alike() {
let codec = BorshCodec;
let slice = [1, 2, 3];
let vec = vec![1, 2, 3];
assert_eq!(
<BorshCodec as EncodeKeyLike<[i32], Vec<i32>>>::encode_key_like(&codec, &slice),
codec.encode_value(&vec)
);
}
Loading

0 comments on commit 44db227

Please sign in to comment.