Skip to content

Commit

Permalink
refactor(jstz_core): introduce BinEncodable trait
Browse files Browse the repository at this point in the history
  • Loading branch information
ryutamago authored and zcabter committed Jan 17, 2025
1 parent c014c66 commit 6e56c16
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 109 deletions.
77 changes: 77 additions & 0 deletions crates/jstz_core/src/bin_encodable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use bincode::{
config::{Configuration, Fixint, LittleEndian},
Decode, Encode,
};

use crate::error::{Error, Result};

const BINCODE_CONFIGURATION: Configuration<LittleEndian, Fixint> =
bincode::config::legacy();

/// Trait for types that can be encoded to and decoded from binary format
pub trait BinEncodable {
fn encode(&self) -> Result<Vec<u8>>;
fn decode(bytes: &[u8]) -> Result<Self>
where
Self: Sized;
}

/// Default implementation for types that can be encoded to and decoded from binary format
impl<T: Encode + Decode> BinEncodable for T {
fn encode(&self) -> Result<Vec<u8>> {
bincode::encode_to_vec(self, BINCODE_CONFIGURATION).map_err(|err| {
Error::SerializationError {
description: format!("{err}"),
}
})
}

fn decode(bytes: &[u8]) -> Result<Self> {
let (value, _) = bincode::decode_from_slice(bytes, BINCODE_CONFIGURATION)
.map_err(|err| Error::SerializationError {
description: format!("{err}"),
})?;
Ok(value)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[derive(Debug, Clone, PartialEq, Encode, Decode)]
struct TestData {
field1: String,
field2: i32,
}

#[test]
fn test_binencodable_roundtrip() {
let original = TestData {
field1: "test".to_string(),
field2: 42,
};

// Test encode
let encoded = BinEncodable::encode(&original).unwrap();
assert!(!encoded.is_empty());

// Test decode
let decoded = BinEncodable::decode(&encoded).unwrap();
assert_eq!(original, decoded);
}

#[test]
fn test_binencodable_invalid_data() {
// Try to decode invalid bytes
let invalid_bytes = vec![1, 2, 3];
let result = <TestData as BinEncodable>::decode(&invalid_bytes);
assert!(result.is_err());

// Verify error type
match result {
Err(Error::SerializationError { description: _ }) => (),
_ => panic!("Expected SerializationError"),
}
}
}
7 changes: 2 additions & 5 deletions crates/jstz_core/src/kv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,11 @@ pub struct Storage;

impl Storage {
/// Retrieve a value from the persistent store if it exists
pub fn get<V: bincode::Decode>(
rt: &impl Runtime,
key: &impl Path,
) -> Result<Option<V>> {
pub fn get<V: Value>(rt: &impl Runtime, key: &impl Path) -> Result<Option<V>> {
match rt.store_has(key)? {
Some(ValueType::Value | ValueType::ValueWithSubtree) => {
let bytes = rt.store_read_all(key)?;
let value = value::deserialize(&bytes)?;
let value = V::decode(&bytes)?;
Ok(Some(value))
}
_ => Ok(None),
Expand Down
56 changes: 20 additions & 36 deletions crates/jstz_core/src/kv/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use std::{
mem,
};

use bincode::Decode;
use derive_more::{Deref, DerefMut};

use tezos_smart_rollup_host::{path::OwnedPath, runtime::Runtime};
Expand Down Expand Up @@ -73,30 +72,21 @@ impl SnapshotValue {
Self(BoxedValue::new(value))
}

pub fn as_ref<V>(&self) -> Result<&V>
where
V: Value,
{
pub fn as_ref<V: Value>(&self) -> Result<&V> {
Ok(self
.as_any()
.downcast_ref()
.ok_or(KvError::DowncastFailed)?)
}

pub fn as_mut<V>(&mut self) -> Result<&mut V>
where
V: Value,
{
pub fn as_mut<V: Value>(&mut self) -> Result<&mut V> {
Ok(self
.as_any_mut()
.downcast_mut()
.ok_or(KvError::DowncastFailed)?)
}

pub fn into_value<V>(self) -> Result<V>
where
V: Value,
{
pub fn into_value<V: Value>(self) -> Result<V> {
let value = self.0.downcast().map_err(|_| KvError::DowncastFailed)?;
*value
}
Expand Down Expand Up @@ -222,10 +212,11 @@ impl Transaction {
Ok(())
}

fn lookup<V>(&mut self, rt: &impl Runtime, key: Key) -> Result<Option<&SnapshotValue>>
where
V: Value + Decode,
{
fn lookup<V: Value>(
&mut self,
rt: &impl Runtime,
key: Key,
) -> Result<Option<&SnapshotValue>> {
if let Some(&snapshot_idx) =
self.lookup_map.get(&key).and_then(|history| history.last())
{
Expand All @@ -244,14 +235,11 @@ impl Transaction {
}
}

fn lookup_mut<V>(
fn lookup_mut<V: Value>(
&mut self,
rt: &impl Runtime,
key: Key,
) -> Result<Option<&mut SnapshotValue>>
where
V: Value + Decode,
{
) -> Result<Option<&mut SnapshotValue>> {
if let Some(&snapshot_idx) =
self.lookup_map.get(&key).and_then(|history| history.last())
{
Expand All @@ -273,20 +261,19 @@ impl Transaction {

/// Returns a reference to the value corresponding to the key in the
/// key-value store if it exists.
pub fn get<V>(&mut self, rt: &impl Runtime, key: Key) -> Result<Option<&V>>
where
V: Value + Decode,
{
pub fn get<V: Value>(&mut self, rt: &impl Runtime, key: Key) -> Result<Option<&V>>
where {
self.lookup::<V>(rt, key)
.map(|entry_opt| entry_opt.map(|entry| entry.as_ref()).transpose())?
}

/// Returns a mutable reference to the value corresponding to the key in the
/// key-value store if it exists.
pub fn get_mut<V>(&mut self, rt: &impl Runtime, key: Key) -> Result<Option<&mut V>>
where
V: Value + Decode,
{
pub fn get_mut<V: Value>(
&mut self,
rt: &impl Runtime,
key: Key,
) -> Result<Option<&mut V>> {
self.lookup_mut::<V>(rt, key)
.map(|entry_opt| entry_opt.map(|entry| entry.as_mut()).transpose())?
}
Expand All @@ -306,10 +293,7 @@ impl Transaction {
}

/// Insert a key-value pair into the key-value store.
pub fn insert<V>(&mut self, key: Key, value: V) -> Result<()>
where
V: Value,
{
pub fn insert<V: Value>(&mut self, key: Key, value: V) -> Result<()> {
self.current_snapshot_insert(key, SnapshotValue::new(value))
}

Expand All @@ -326,7 +310,7 @@ impl Transaction {
key: Key,
) -> Result<Entry<'b, V>>
where
V: Value + Decode,
V: Value,
'a: 'b,
{
// A mutable lookup ensures the key is in the current snapshot
Expand Down Expand Up @@ -911,7 +895,7 @@ mod test {

#[cfg(test)]
mod tests {
use bincode::Encode;
use bincode::{Decode, Encode};
use serde::{Deserialize, Serialize};
use tezos_smart_rollup_mock::MockHost;

Expand Down
47 changes: 16 additions & 31 deletions crates/jstz_core/src/kv/value.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,30 @@
use crate::BinEncodable;
use derive_more::{Deref, DerefMut};
use std::any::Any;
use std::fmt::Debug;

use derive_more::{Deref, DerefMut};

use crate::{Error, Result, BINCODE_CONFIGURATION};

pub fn serialize<T: bincode::Encode>(value: &T) -> Result<Vec<u8>> {
bincode::encode_to_vec(value, BINCODE_CONFIGURATION).map_err(|err| {
Error::SerializationError {
description: format!("{err}"),
}
})
}

pub fn deserialize<T: bincode::Decode>(bytes: &[u8]) -> Result<T> {
let (result, _) =
bincode::decode_from_slice(bytes, BINCODE_CONFIGURATION).map_err(|err| {
Error::SerializationError {
description: format!("{err}"),
}
})?;

Ok(result)
}

/// A key-value 'value' is a value that is can be dynamically
/// A key-value 'value' is a value that can be dynamically
/// coerced (using `Any`) and serialized.
pub trait Value: Any + Debug {
/// It must satisfy three requirements:
/// 1. Dynamic type coercion through `Any` trait
/// 2. Debug formatting for development and error messages
/// 3. Binary encoding/decoding through `BinEncodable` trait
///
/// Types implementing this trait can be:
/// - Stored and retrieved from the key-value store
/// - Dynamically downcasted to concrete types
/// - Cloned into new boxed values
///
/// The trait is object-safe and can be used with trait objects.
pub trait Value: Any + Debug + BinEncodable {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn clone_box(&self) -> Box<dyn Value>;
fn encode(&self) -> Result<Vec<u8>>;
}

impl<T> Value for T
where
T: Any + Debug + Clone + bincode::Encode,
T: Any + Debug + Clone + BinEncodable,
{
fn as_any(&self) -> &dyn Any {
self
Expand All @@ -48,10 +37,6 @@ where
fn clone_box(&self) -> Box<dyn Value> {
Box::new(self.clone())
}

fn encode(&self) -> Result<Vec<u8>> {
serialize(self)
}
}

#[derive(Debug, Deref, DerefMut)]
Expand Down
15 changes: 5 additions & 10 deletions crates/jstz_core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
mod bin_encodable;
pub mod error;

use bincode::config::{Configuration, Fixint, LittleEndian};
use boa_engine::Context;

pub use error::{Error, Result};
pub mod future;
pub mod host;
pub mod iterators;
Expand All @@ -14,6 +10,10 @@ pub mod realm;
pub mod runtime;
pub mod value;

pub use bin_encodable::*;
use boa_engine::Context;
pub use error::{Error, Result};

/// A generic runtime API
pub trait Api {
/// Initialize a runtime API
Expand All @@ -22,8 +22,3 @@ pub trait Api {

pub use realm::{Module, Realm};
pub use runtime::Runtime;

pub use crate::kv::value::{deserialize, serialize};

pub static BINCODE_CONFIGURATION: Configuration<LittleEndian, Fixint> =
bincode::config::legacy();
7 changes: 2 additions & 5 deletions crates/jstz_kernel/src/inbox.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use jstz_core::BINCODE_CONFIGURATION;
use jstz_core::BinEncodable;
use jstz_proto::context::new_account::NewAddress;
use jstz_proto::operation::{external::Deposit, ExternalOperation, SignedOperation};
use num_traits::ToPrimitive;
Expand Down Expand Up @@ -106,10 +106,7 @@ fn read_transfer(
}

fn read_external_message(rt: &mut impl Runtime, bytes: &[u8]) -> Option<ExternalMessage> {
let result: Result<(ExternalMessage, usize), bincode::error::DecodeError> =
bincode::decode_from_slice(bytes, BINCODE_CONFIGURATION);
let (msg, _): (ExternalMessage, _) = result.ok()?;

let msg = ExternalMessage::decode(bytes).ok()?;
debug_msg!(rt, "External message: {msg:?}\n");
Some(msg)
}
Expand Down
6 changes: 3 additions & 3 deletions crates/jstz_node/src/services/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use axum::{
extract::{Path, Query, State},
Json,
};
use jstz_core::BinEncodable;
use jstz_proto::{
api::KvValue,
context::account::{Account, Nonce, ParsedCode},
Expand All @@ -26,8 +27,7 @@ fn construct_storage_key(address: &str, key: &Option<String>) -> String {
}

fn deserialize_account(data: &[u8]) -> ServiceResult<Account> {
Ok(jstz_core::deserialize::<Account>(data)
.map_err(|_| anyhow!("Failed to deserialize account"))?)
Ok(Account::decode(data).map_err(|_| anyhow!("Failed to deserialize account"))?)
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -135,7 +135,7 @@ async fn get_kv_value(
let key = construct_storage_key(&address, &key);
let value = rollup_client.get_value(&key).await?;
let kv_value = match value {
Some(value) => jstz_core::deserialize::<KvValue>(value.as_slice())
Some(value) => KvValue::decode(value.as_slice())
.map_err(|_| anyhow!("Failed to deserialize kv value"))?,
None => Err(ServiceError::NotFound)?,
};
Expand Down
Loading

0 comments on commit 6e56c16

Please sign in to comment.