From bb9b28933dd8d80979725d9e1c1543d03b617b4a Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Thu, 1 May 2025 08:48:12 +0300 Subject: [PATCH] fix: private state operations added --- Cargo.lock | 1 + node_core/src/chain_storage/mod.rs | 7 + node_core/src/lib.rs | 1 + node_core/src/pre_start.rs | 56 ++++++++ sc_core/Cargo.toml | 1 + sc_core/src/lib.rs | 1 + sc_core/src/private_state.rs | 216 +++++++++++++++++++++++++++++ 7 files changed, 283 insertions(+) create mode 100644 sc_core/src/private_state.rs diff --git a/Cargo.lock b/Cargo.lock index 8c84dec..53b89f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4541,6 +4541,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "storage", + "thiserror 1.0.69", "utxo", ] diff --git a/node_core/src/chain_storage/mod.rs b/node_core/src/chain_storage/mod.rs index f8bcf4e..c0bafe4 100644 --- a/node_core/src/chain_storage/mod.rs +++ b/node_core/src/chain_storage/mod.rs @@ -15,6 +15,7 @@ use common::{ }; use k256::AffinePoint; use public_context::PublicSCContext; +use sc_core::private_state::PrivateSCState; use utxo::utxo_core::UTXO; use crate::ActionData; @@ -29,6 +30,10 @@ pub struct NodeChainStore { pub nullifier_store: NullifierSparseMerkleTree, pub utxo_commitments_store: UTXOCommitmentsMerkleTree, pub pub_tx_store: PublicTransactionMerkleTree, + /// Contract private state + /// + /// ToDo: Replace regualar BTreeMap with weighted binary tree + pub contracts_private_state: HashMap, } impl NodeChainStore { @@ -37,6 +42,7 @@ impl NodeChainStore { let nullifier_store = NullifierSparseMerkleTree::default(); let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]); let pub_tx_store = PublicTransactionMerkleTree::new(vec![]); + let contracts_private_state = HashMap::new(); //Sequencer should panic if unable to open db, //as fixing this issue may require actions non-native to program scope @@ -50,6 +56,7 @@ impl NodeChainStore { nullifier_store, utxo_commitments_store, pub_tx_store, + contracts_private_state, } } diff --git a/node_core/src/lib.rs b/node_core/src/lib.rs index c0a97e5..07dccf4 100644 --- a/node_core/src/lib.rs +++ b/node_core/src/lib.rs @@ -101,6 +101,7 @@ impl NodeCore { let mut storage = NodeChainStore::new_with_genesis(&config.home, genesis_block); pre_start::setup_empty_sc_states(&storage).await?; + pre_start::setup_empty_private_sc_states(&mut storage); let mut chain_height = genesis_id.genesis_id; diff --git a/node_core/src/pre_start.rs b/node_core/src/pre_start.rs index fe21e2b..ae84d07 100644 --- a/node_core/src/pre_start.rs +++ b/node_core/src/pre_start.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use anyhow::Result; use log::info; @@ -73,3 +75,57 @@ pub async fn setup_empty_sc_states(node: &NodeChainStore) -> Result<()> { Ok(()) } + +///Setups private states of default smart conracts as empty +pub fn setup_empty_private_sc_states(node: &mut NodeChainStore) { + info!("Filling up private states of default smart contracts"); + + let empty_mmap = BTreeMap::new(); + + let public_deposit_addr = hex::encode(PUBLIC_DEPOSIT_ID); + node.contracts_private_state + .insert(public_deposit_addr, empty_mmap.clone()); + info!("Public transfer state set"); + + let mint_utxo_addr_bytes: Vec = zkvm::test_methods::MINT_UTXO_ID + .iter() + .map(|num| num.to_le_bytes()) + .flatten() + .collect(); + let mint_utxo_addr = hex::encode(mint_utxo_addr_bytes); + node.contracts_private_state + .insert(mint_utxo_addr, empty_mmap.clone()); + info!("Mint UTXO state set"); + + let single_utxo_transfer_addr_bytes: Vec = zkvm::test_methods::SEND_UTXO_ID + .iter() + .map(|num| num.to_le_bytes()) + .flatten() + .collect(); + let single_utxo_transfer_addr = hex::encode(single_utxo_transfer_addr_bytes); + node.contracts_private_state + .insert(single_utxo_transfer_addr, empty_mmap.clone()); + info!("Single UTXO transfer state set"); + + let mint_utxo_multiple_assets_addr_bytes: Vec = + zkvm::test_methods::MINT_UTXO_MULTIPLE_ASSETS_ID + .iter() + .map(|num| num.to_le_bytes()) + .flatten() + .collect(); + let mint_utxo_multiple_assets_addr = hex::encode(mint_utxo_multiple_assets_addr_bytes); + node.contracts_private_state + .insert(mint_utxo_multiple_assets_addr, empty_mmap.clone()); + info!("Mint UTXO multiple assets state set"); + + let multiple_assets_utxo_transfer_addr_bytes: Vec = + zkvm::test_methods::SEND_UTXO_MULTIPLE_ASSETS_ID + .iter() + .map(|num| num.to_le_bytes()) + .flatten() + .collect(); + let multiple_assets_utxo_transfer_addr = hex::encode(multiple_assets_utxo_transfer_addr_bytes); + node.contracts_private_state + .insert(multiple_assets_utxo_transfer_addr, empty_mmap.clone()); + info!("Multiple_assets UTXO transfer state set"); +} diff --git a/sc_core/Cargo.toml b/sc_core/Cargo.toml index 55ba888..97f452e 100644 --- a/sc_core/Cargo.toml +++ b/sc_core/Cargo.toml @@ -19,6 +19,7 @@ hex.workspace = true light-poseidon.workspace = true ark-bn254.workspace = true ark-ff.workspace = true +thiserror.workspace = true risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.0" } diff --git a/sc_core/src/lib.rs b/sc_core/src/lib.rs index 769813d..324d007 100644 --- a/sc_core/src/lib.rs +++ b/sc_core/src/lib.rs @@ -1,4 +1,5 @@ pub mod cryptography; +pub mod private_state; pub mod proofs_circuits; pub mod transaction_payloads_tools; pub mod utxo_manipulator; diff --git a/sc_core/src/private_state.rs b/sc_core/src/private_state.rs new file mode 100644 index 0000000..e9a3ed3 --- /dev/null +++ b/sc_core/src/private_state.rs @@ -0,0 +1,216 @@ +use std::collections::BTreeMap; + +use serde::{de::Error, Deserialize, Serialize}; + +pub const PRIVATE_BLOB_SIZE: usize = 32; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PrivateDataBlob(pub [u8; PRIVATE_BLOB_SIZE]); + +pub type PrivateSCState = BTreeMap; + +#[derive(thiserror::Error, Debug)] +pub enum PrivateStateError { + #[error("Trying to read from slot too big: Read slot {0}, max_slot {1}")] + ReadSizeMismatch(usize, usize), + #[error("Can not write empty bytes into state")] + EmptyWrite, +} + +impl From<[u8; PRIVATE_BLOB_SIZE]> for PrivateDataBlob { + fn from(value: [u8; PRIVATE_BLOB_SIZE]) -> Self { + Self(value) + } +} + +impl Serialize for PrivateDataBlob { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let data_vec = self.0.to_vec(); + data_vec.serialize(serializer) + } +} + +impl AsRef<[u8]> for PrivateDataBlob { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl<'de> Deserialize<'de> for PrivateDataBlob { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let data_vec = Vec::::deserialize(deserializer)?; + let chunk: [u8; PRIVATE_BLOB_SIZE] = data_vec + .try_into() + .map_err(|data| { + anyhow::anyhow!("failed to fit vec {data:?} to {:?}", PRIVATE_BLOB_SIZE) + }) + .map_err(D::Error::custom)?; + Ok(Self(chunk)) + } +} + +impl PrivateDataBlob { + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } +} + +///Produce `DataBlob` from vector of size <= `PRIVATE_BLOB_SIZE` +/// +///Extends to `PRIVATE_BLOB_SIZE`, if necessary. +/// +///Panics, if size > `PRIVATE_BLOB_SIZE` +pub fn produce_blob_from_fit_vec(data: Vec) -> PrivateDataBlob { + let data_len = data.len(); + + assert!(data_len <= PRIVATE_BLOB_SIZE); + let mut blob: PrivateDataBlob = [0; PRIVATE_BLOB_SIZE].into(); + + for (idx, item) in data.into_iter().enumerate() { + blob.0[idx] = item + } + + blob +} + +///Produce `DataBlob` from slice of size <= `PRIVATE_BLOB_SIZE` +/// +///Extends to `PRIVATE_BLOB_SIZE`, if necessary. +/// +///Panics, if size > `PRIVATE_BLOB_SIZE` +pub fn produce_blob_from_fit_slice(data: &[u8]) -> PrivateDataBlob { + let data_len = data.len(); + + assert!(data_len <= PRIVATE_BLOB_SIZE); + let mut blob: PrivateDataBlob = [0; PRIVATE_BLOB_SIZE].into(); + + for (idx, item) in data.into_iter().enumerate() { + blob.0[idx] = *item + } + + blob +} + +pub fn calculate_offset_slot(offset: usize) -> usize { + offset / PRIVATE_BLOB_SIZE +} + +pub fn max_slot(state: &PrivateSCState) -> usize { + *state.keys().max().unwrap_or(&0) +} + +///Read at least `num` bytes from the start of a state +pub fn read_num_bytes_start( + state: &PrivateSCState, + num: usize, +) -> Result, PrivateStateError> { + let mut resp = vec![]; + + let max_offset_slot = calculate_offset_slot(num); + let max_slot_state = max_slot(state); + if max_offset_slot > max_slot_state { + return Err(PrivateStateError::ReadSizeMismatch( + max_offset_slot, + max_slot_state, + )); + } + + for i in 0..max_offset_slot { + resp.push(*state.get(&i).unwrap()); + } + + Ok(resp) +} + +///Read at least `num` bytes from the `offset` slot +pub fn read_num_bytes_offset( + state: &PrivateSCState, + num: usize, + offset: usize, +) -> Result, PrivateStateError> { + let mut resp = vec![]; + + let max_offset_slot = offset + calculate_offset_slot(num); + let max_slot_state = max_slot(state); + if max_offset_slot > max_slot_state { + return Err(PrivateStateError::ReadSizeMismatch( + max_offset_slot, + max_slot_state, + )); + } + + for i in offset..max_offset_slot { + resp.push(*state.get(&i).unwrap()); + } + + Ok(resp) +} + +///Write at least `bytes.len()` bytes at the end of the state +/// +/// Returns new last slot +pub fn write_num_bytes_append( + state: &mut PrivateSCState, + bytes: Vec, +) -> Result { + if bytes.is_empty() { + return Err(PrivateStateError::EmptyWrite); + } + + let mut max_slot_state = max_slot(state) + 1; + + let mut curr = 0; + + while (curr + PRIVATE_BLOB_SIZE) < bytes.len() { + let data_blob = produce_blob_from_fit_slice(&bytes[curr..(curr + PRIVATE_BLOB_SIZE)]); + + state.insert(max_slot_state, data_blob); + + curr += PRIVATE_BLOB_SIZE; + max_slot_state += 1; + } + + let data_blob = produce_blob_from_fit_slice(&bytes[curr..(bytes.len())]); + + state.insert(max_slot_state, data_blob); + + Ok(max_slot_state) +} + +/// Rewrite at least `bytes.len()` bytes starting from the offset slot +/// +/// Returns last (re)written slot +pub fn write_num_bytes_rewrite( + state: &mut PrivateSCState, + bytes: Vec, + offset: usize, +) -> Result { + if bytes.is_empty() { + return Err(PrivateStateError::EmptyWrite); + } + + let mut curr_slot = offset; + + let mut curr = 0; + + while (curr + PRIVATE_BLOB_SIZE) < bytes.len() { + let data_blob = produce_blob_from_fit_slice(&bytes[curr..(curr + PRIVATE_BLOB_SIZE)]); + + state.insert(curr_slot, data_blob); + + curr += PRIVATE_BLOB_SIZE; + curr_slot += 1; + } + + let data_blob = produce_blob_from_fit_slice(&bytes[curr..(bytes.len())]); + + state.insert(curr_slot, data_blob); + + Ok(curr_slot) +}