diff --git a/Cargo.lock b/Cargo.lock index f9905b9b4e..9f10f45cfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2242,6 +2242,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde 1.0.195", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "ctr" version = "0.9.2" @@ -8438,6 +8459,7 @@ dependencies = [ "chrono", "clap 4.5.4", "codespan-reporting", + "csv", "datatest-stable 0.1.1", "dirs 4.0.0", "fastcrypto", @@ -8470,6 +8492,8 @@ dependencies = [ "moveos-verifier", "once_cell", "parking_lot 0.12.1", + "proptest", + "proptest-derive", "raw-store", "regex", "rooch-config", diff --git a/Cargo.toml b/Cargo.toml index b5a80be9d6..2b4a5ea8b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,6 +263,7 @@ celestia-types = { git = "https://github.com/eigerco/celestia-node-rs.git", rev opendal = "0.44.1" bitcoincore-rpc-json = "0.18.0" toml = "0.8.12" +csv = "1.2.1" # Note: the BEGIN and END comments below are required for external tooling. Do not remove. # BEGIN MOVE DEPENDENCIES diff --git a/crates/rooch/Cargo.toml b/crates/rooch/Cargo.toml index 12949cd2de..237f26fdbd 100644 --- a/crates/rooch/Cargo.toml +++ b/crates/rooch/Cargo.toml @@ -41,6 +41,9 @@ bcs-ext = { workspace = true } rpassword = { workspace = true } fastcrypto = { workspace = true } log = { workspace = true } +proptest = { optional = true, workspace = true } +proptest-derive = { optional = true, workspace = true } +csv = { workspace = true } move-bytecode-utils = { workspace = true } move-binary-format = { workspace = true } diff --git a/crates/rooch/src/commands/statedb/commands/export.rs b/crates/rooch/src/commands/statedb/commands/export.rs index 7865d22ea5..accac20df5 100644 --- a/crates/rooch/src/commands/statedb/commands/export.rs +++ b/crates/rooch/src/commands/statedb/commands/export.rs @@ -2,17 +2,122 @@ // SPDX-License-Identifier: Apache-2.0 use crate::cli_types::WalletContextOptions; +use crate::commands::statedb::commands::{init_statedb, BATCH_SIZE, STATE_HEADER_PREFIX}; +use anyhow::Result; use clap::Parser; -use rooch_types::error::RoochResult; +use csv::Writer; +use moveos_store::MoveOSStore; +use moveos_types::h256::H256; +use moveos_types::moveos_std::object::ObjectID; +use moveos_types::state_resolver::StatelessResolver; +use rooch_config::R_OPT_NET_HELP; +use rooch_types::bitcoin::ord::InscriptionStore; +use rooch_types::bitcoin::utxo::BitcoinUTXOStore; +use rooch_types::error::{RoochError, RoochResult}; +use rooch_types::framework::address_mapping::AddressMappingWrapper; +use rooch_types::rooch_network::RoochChainID; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use std::str::FromStr; +use std::time::SystemTime; /// Export statedb + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] +#[repr(u8)] +#[serde(rename_all = "lowercase")] +pub enum ExportMode { + // dump UTXO, Inscription and relative Objects, including AddressMapping object + #[default] + Genesis = 0, + Full = 1, + Snapshot = 2, + Object = 3, +} + +impl TryFrom for ExportMode { + type Error = anyhow::Error; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(ExportMode::Genesis), + 1 => Ok(ExportMode::Full), + 2 => Ok(ExportMode::Snapshot), + 3 => Ok(ExportMode::Object), + _ => Err(anyhow::anyhow!( + "Statedb cli export mode {} is invalid", + value + )), + } + } +} + +impl ExportMode { + pub fn to_num(self) -> u8 { + self as u8 + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ExportID { + pub prefix: String, + pub object_id: ObjectID, +} + +impl ExportID { + pub fn new(prefix: String, object_id: ObjectID) -> Self { + Self { prefix, object_id } + } +} + +impl std::fmt::Display for ExportID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{:?}", self.prefix, self.object_id) + } +} + +impl FromStr for ExportID { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let prefix = parts + .next() + .ok_or(anyhow::anyhow!("invalid export id"))? + .to_string(); + let object_id = + ObjectID::from_str(parts.next().ok_or(anyhow::anyhow!("invalid export id"))?)?; + Ok(ExportID::new(prefix, object_id)) + } +} + #[derive(Debug, Parser)] pub struct ExportCommand { + /// export state root, default latest state root + #[clap(long, short = 's')] + pub state_root: Option, + #[clap(long, short = 'o')] - /// export output file. like ~/.rooch/local/utxo.csv or utxo.csv + /// export output file. like ~/.rooch/local/statedb.csv or ./statedb.csv pub output: PathBuf, + #[clap(long = "data-dir", short = 'd')] + /// Path to data dir, this dir is base dir, the final data_dir is base_dir/chain_network_name + pub base_data_dir: Option, + + // #[serde(skip_serializing_if = "Option::is_none")] + #[clap(long, short = 'm')] + /// statedb export mode, default is genesis mode + pub mode: Option, + + /// export object id, for object mode + #[clap(long, short = 'i')] + pub object_id: Option, + + /// If local chainid, start the service with a temporary data store. + /// All data will be deleted when the service is stopped. + #[clap(long, short = 'n', help = R_OPT_NET_HELP)] + pub chain_id: Option, + #[clap(flatten)] pub context_options: WalletContextOptions, } @@ -20,7 +125,209 @@ pub struct ExportCommand { impl ExportCommand { pub async fn execute(self) -> RoochResult<()> { let mut _context = self.context_options.build()?; + // let client = context.get_client().await?; + + println!("Start statedb export task, batch_size: {:?}", BATCH_SIZE); + let (root, moveos_store) = init_statedb(self.base_data_dir.clone(), self.chain_id.clone())?; + println!("root object: {:?}", root); + + let mut _start_time = SystemTime::now(); + let file_name = self.output.display().to_string(); + let mut writer_builder = csv::WriterBuilder::new(); + let writer_builder = writer_builder.delimiter(b',').double_quote(false); + let mut writer = writer_builder.from_path(file_name).map_err(|e| { + RoochError::from(anyhow::Error::msg(format!("Invalid output path: {}", e))) + })?; + let root_state_root = self + .state_root + .unwrap_or(H256::from(root.state_root.into_bytes())); + + let mode = ExportMode::try_from(self.mode.unwrap_or(ExportMode::Genesis.to_num()))?; + match mode { + ExportMode::Genesis => { + Self::export_genesis(&moveos_store, root_state_root, &mut writer)?; + } + ExportMode::Full => { + todo!() + } + ExportMode::Snapshot => { + todo!() + } + ExportMode::Object => { + let obj_id = self + .object_id + .expect("Object id should exist in object mode"); + Self::export_object(&moveos_store, root_state_root, obj_id, &mut writer)?; + } + } + + println!("Finish export task."); + Ok(()) + } + + /// Field state must be export first, and then object state + fn export_genesis( + moveos_store: &MoveOSStore, + root_state_root: H256, + writer: &mut Writer, + ) -> Result<()> { + let utxo_store_id = BitcoinUTXOStore::object_id(); + let inscription_store_id = InscriptionStore::object_id(); + let address_mapping_id = AddressMappingWrapper::mapping_object_id(); + let reverse_mapping_id = AddressMappingWrapper::reverse_mapping_object_id(); + println!("export_genesis utxo_store_id: {:?}", utxo_store_id); + println!( + "export_genesis inscription_store_id: {:?}", + inscription_store_id + ); + println!( + "export_genesis address_mapping_id: {:?}, reverse_mapping_id {:?}", + address_mapping_id, reverse_mapping_id + ); + + let genesis_object_ids = vec![ + utxo_store_id.clone(), + inscription_store_id.clone(), + address_mapping_id, + reverse_mapping_id, + ]; + + let mut genesis_objects = vec![]; + let mut genesis_states = vec![]; + for object_id in genesis_object_ids.into_iter() { + let state = moveos_store + .get_field_at(root_state_root, &object_id.to_key())? + .expect("state should exist."); + let object = state.clone().as_raw_object()?; + genesis_states.push((object_id.to_key(), state)); + genesis_objects.push(object); + } + + // write csv field states + for obj in genesis_objects.into_iter() { + Self::export_field_states( + moveos_store, + H256::from(obj.state_root.into_bytes()), + obj.id, + writer, + )?; + } + + // write csv object states. + { + let root_export_id = ExportID::new(STATE_HEADER_PREFIX.to_string(), ObjectID::root()); + writer.write_field(root_export_id.to_string())?; + writer.write_field(format!("{:?}", root_state_root))?; + writer.write_record(None::<&[u8]>)?; + } + for (k, v) in genesis_states.into_iter() { + writer.write_field(k.to_string())?; + writer.write_field(v.to_string())?; + writer.write_record(None::<&[u8]>)?; + } + + // flush csv writer + writer.flush()?; + println!("export_genesis root state_root: {:?}", root_state_root); + + Ok(()) + } + + fn export_object( + moveos_store: &MoveOSStore, + root_state_root: H256, + object_id: ObjectID, + writer: &mut Writer, + ) -> Result<()> { + println!("export_object object_id: {:?}", object_id); + + let state = moveos_store + .get_field_at(root_state_root, &object_id.to_key())? + .expect("state should exist."); + let obj = state.clone().as_raw_object()?; + + // write csv field states + Self::export_field_states( + moveos_store, + H256::from(obj.state_root.into_bytes()), + object_id.clone(), + writer, + )?; + + // write csv object states. + { + let export_id = ExportID::new(STATE_HEADER_PREFIX.to_string(), object_id.clone()); + writer.write_field(export_id.to_string())?; + writer.write_field(format!("{:?}", root_state_root))?; + writer.write_record(None::<&[u8]>)?; + } + writer.write_field(object_id.to_key().to_string())?; + writer.write_field(state.to_string())?; + writer.write_record(None::<&[u8]>)?; + + // flush csv writer + writer.flush()?; + println!("export_object root state_root: {:?}", root_state_root); + + Ok(()) + } + + fn export_field_states( + moveos_store: &MoveOSStore, + state_root: H256, + object_id: ObjectID, + writer: &mut Writer, + ) -> Result<()> { + let starting_key = None; + let mut count: u64 = 0; + + let mut iter = moveos_store + .get_state_store() + .iter(state_root, starting_key.clone())?; + + if object_id.has_child() { + for item in iter { + let (_k, v) = item?; + if v.is_object() { + let object = v.clone().as_raw_object()?; + if object.size > 0 { + Self::export_field_states( + moveos_store, + H256::from(object.state_root.into_bytes()), + object.id, + writer, + )?; + } + } + } + + // seek from starting_key + iter = moveos_store + .get_state_store() + .iter(state_root, starting_key.clone())?; + } + + // write csv header. + { + let export_id = ExportID::new(STATE_HEADER_PREFIX.to_string(), object_id.clone()); + writer.write_field(export_id.to_string())?; + writer.write_field(format!("{:?}", state_root))?; + writer.write_record(None::<&[u8]>)?; + } + + for item in iter { + let (k, v) = item?; + writer.write_field(k.to_string())?; + writer.write_field(v.to_string())?; + writer.write_record(None::<&[u8]>)?; + + count += 1; + } - todo!() + println!( + "export_field_states object_id {:?}, state_root: {:?} export field counts {}", + object_id, state_root, count + ); + Ok(()) } } diff --git a/crates/rooch/src/commands/statedb/commands/genesis_utxo.rs b/crates/rooch/src/commands/statedb/commands/genesis_utxo.rs index d44c6097ec..9bc6902fff 100644 --- a/crates/rooch/src/commands/statedb/commands/genesis_utxo.rs +++ b/crates/rooch/src/commands/statedb/commands/genesis_utxo.rs @@ -1,6 +1,8 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 +use crate::cli_types::WalletContextOptions; +use crate::commands::statedb::commands::import::{apply_fields, apply_nodes}; use std::collections::hash_map::Entry; use std::collections::{BTreeMap, HashMap}; use std::fs::File; @@ -42,8 +44,6 @@ use rooch_types::multichain_id::RoochMultiChainID; use rooch_types::rooch_network::RoochChainID; use smt::UpdateSet; -use crate::cli_types::WalletContextOptions; -use crate::commands::statedb::commands::import::{apply_fields, apply_nodes}; use crate::commands::statedb::commands::init_statedb; pub const SCRIPT_TYPE_P2MS: &str = "p2ms"; diff --git a/crates/rooch/src/commands/statedb/commands/import.rs b/crates/rooch/src/commands/statedb/commands/import.rs index d43abd77f6..1a68b76490 100644 --- a/crates/rooch/src/commands/statedb/commands/import.rs +++ b/crates/rooch/src/commands/statedb/commands/import.rs @@ -2,19 +2,34 @@ // SPDX-License-Identifier: Apache-2.0 use crate::cli_types::WalletContextOptions; -use anyhow::Result; +use std::collections::BTreeMap; +use std::fs::File; +use std::io::{BufRead, BufReader, Read}; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::mpsc::{Receiver, SyncSender}; +use std::sync::{mpsc, Arc}; +use std::thread; +use std::time::SystemTime; + +use anyhow::{Error, Result}; +use chrono::{DateTime, Local}; use clap::Parser; +use serde::{Deserialize, Serialize}; + use moveos_store::MoveOSStore; use moveos_types::h256::H256; +use moveos_types::moveos_std::object::{ObjectID, RootObjectEntity, GENESIS_STATE_ROOT}; +use moveos_types::startup_info::StartupInfo; use moveos_types::state::{KeyState, State}; +use moveos_types::state_resolver::StatelessResolver; use rooch_config::R_OPT_NET_HELP; -use rooch_types::error::RoochResult; +use rooch_types::error::{RoochError, RoochResult}; use rooch_types::rooch_network::RoochChainID; use smt::{TreeChangeSet, UpdateSet}; -use std::collections::BTreeMap; -use std::path::PathBuf; -pub const BATCH_SIZE: usize = 2000; +use crate::commands::statedb::commands::export::ExportID; +use crate::commands::statedb::commands::{init_statedb, STATE_HEADER_PREFIX}; /// Import statedb #[derive(Debug, Parser)] @@ -32,41 +47,232 @@ pub struct ImportCommand { #[clap(long, short = 'n', help = R_OPT_NET_HELP)] pub chain_id: Option, + #[clap(long, short = 'b', default_value = "20971")] + pub batch_size: Option, + #[clap(flatten)] pub context_options: WalletContextOptions, } -// #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -// pub struct UTXOData { -// /// The txid of the UTXO -// pub txid: String, -// /// The vout of the UTXO -// pub vout: u32, -// pub value: u64, -// pub address: String, -// // pub seals: SimpleMultiMap, -// } -// -// impl UTXOData { -// pub fn new(txid: String, vout: u32, value: u64, address: String) -> Self { -// Self { -// txid, -// vout, -// value, -// address, -// } -// } -// } - impl ImportCommand { pub async fn execute(self) -> RoochResult<()> { - let mut _context = self.context_options.build()?; - // let client = context.get_client().await?; + let input_path = self.input.clone(); + let batch_size = self.batch_size.unwrap(); + let (root, moveos_store, start_time) = self.init(); + let root_state_root = H256::from(root.state_root.into_bytes()); + let (tx, rx) = mpsc::sync_channel(2); + + let moveos_store_arc = Arc::new(moveos_store.clone()); + let produce_updates_thread = thread::spawn(move || { + produce_updates(tx, &moveos_store, input_path, root_state_root, batch_size) + }); + let apply_updates_thread = thread::spawn(move || { + apply_updates_to_state(rx, moveos_store_arc, root_state_root, root.size, start_time) + }); + let _ = produce_updates_thread + .join() + .map_err(|_e| RoochError::from(Error::msg("Produce updates error".to_string())))?; + let _ = apply_updates_thread + .join() + .map_err(|_e| RoochError::from(Error::msg("Produce updates error ".to_string())))?; + + Ok(()) + } + + fn init(self) -> (RootObjectEntity, MoveOSStore, SystemTime) { + let start_time = SystemTime::now(); + let datetime: DateTime = start_time.into(); + + let (root, moveos_store) = + init_statedb(self.base_data_dir.clone(), self.chain_id.clone()).unwrap(); - todo!() + println!( + "task progress started at {}, batch_size: {}", + datetime, + self.batch_size.unwrap() + ); + println!("root object: {:?}", root); + (root, moveos_store, start_time) } } +struct BatchUpdates { + states: BTreeMap>, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Ord, Eq, PartialOrd, PartialEq)] +pub struct StateRootKey { + pub object_id: ObjectID, + // start state root + pub state_root: H256, + // eventual expect state root + pub eventual_state_root: H256, +} + +impl StateRootKey { + pub fn new(object_id: ObjectID, state_root: H256, eventual_state_root: H256) -> Self { + Self { + object_id, + state_root, + eventual_state_root, + } + } +} + +fn produce_updates( + tx: SyncSender, + moveos_store: &MoveOSStore, + input: PathBuf, + root_state_root: H256, + batch_size: usize, +) -> Result<()> { + let mut csv_reader = BufReader::new(File::open(input).unwrap()); + let mut last_state_root_key = None; + loop { + let mut updates = BatchUpdates { + states: BTreeMap::new(), + }; + for line in csv_reader.by_ref().lines().take(batch_size) { + let line = line?; + + if line.starts_with(STATE_HEADER_PREFIX) { + let (c1, c2) = parse_state_data_from_csv_line(&line)?; + let export_id = ExportID::from_str(&c1)?; + let eventual_state_root = H256::from_str(&c2)?; + // TODO add cache to avoid duplicate read smt + let state_root = + get_state_root(moveos_store, root_state_root, export_id.object_id.clone())?; + + let state_root_key = + StateRootKey::new(export_id.object_id, state_root, eventual_state_root); + updates + .states + .insert(state_root_key.clone(), UpdateSet::new()); + last_state_root_key = Some(state_root_key); + continue; + } + + let (c1, c2) = parse_state_data_from_csv_line(&line)?; + let key_state = KeyState::from_str(&c1)?; + let state = State::from_str(&c2)?; + let state_root_key = last_state_root_key + .clone() + .expect("State root key should have value"); + let update_set = updates.states.entry(state_root_key).or_default(); + update_set.put(key_state, state); + } + if updates.states.is_empty() { + break; + } + tx.send(updates).expect("failed to send updates"); + } + + drop(tx); + Ok(()) +} + +// csv format: c1,c2 +fn parse_state_data_from_csv_line(line: &str) -> Result<(String, String)> { + let str_list: Vec<&str> = line.trim().split(',').collect(); + if str_list.len() != 2 { + return Err(Error::from(RoochError::from(Error::msg(format!( + "Invalid csv line: {}", + line + ))))); + } + let c1 = str_list[0].to_string(); + let c2 = str_list[1].to_string(); + Ok((c1, c2)) +} + +fn apply_updates_to_state( + rx: Receiver, + moveos_store: Arc, + root_state_root: H256, + root_size: u64, + task_start_time: SystemTime, +) -> Result<()> { + // let mut _count = 0; + let mut last_state_root = root_state_root; + while let Ok(batch) = rx.recv() { + let loop_start_time = SystemTime::now(); + + for (state_root_key, update_set) in batch.states.into_iter() { + let mut tree_change_set = + apply_fields(&moveos_store, state_root_key.state_root, update_set)?; + let mut nodes: BTreeMap> = BTreeMap::new(); + nodes.append(&mut tree_change_set.nodes); + last_state_root = tree_change_set.state_root; + + apply_nodes(&moveos_store, nodes).expect("failed to apply nodes"); + + log::debug!( + "state_root: {:?}, new state_root: {:?} execpt state_root: {:?}", + state_root_key.state_root, + last_state_root, + state_root_key.eventual_state_root + ); + } + + println!("This bacth cost: {:?}", loop_start_time.elapsed().unwrap()); + } + + finish_task(&moveos_store, last_state_root, root_size, task_start_time); + Ok(()) +} + +fn finish_task( + moveos_store: &MoveOSStore, + root_state_root: H256, + root_size: u64, + task_start_time: SystemTime, +) { + // Update Startup Info + let new_startup_info = StartupInfo::new(root_state_root, root_size); + moveos_store + .get_config_store() + .save_startup_info(new_startup_info) + .unwrap(); + + let startup_info = moveos_store.get_config_store().get_startup_info().unwrap(); + println!( + "Done in {:?}. New startup_info: {:?}", + task_start_time.elapsed().unwrap(), + startup_info + ); +} + +pub fn get_state_root( + moveos_store: &MoveOSStore, + root_state_root: H256, + object_id: ObjectID, +) -> Result { + let parent_state_root_opt = match object_id.parent() { + Some(parent_id) => { + let state_opt = moveos_store.get_field_at(root_state_root, &parent_id.to_key())?; + match state_opt { + Some(state) => Some(H256::from( + state.clone().as_raw_object()?.state_root.into_bytes(), + )), + None => None, + } + } + None => Some(root_state_root), + }; + let state_root = match parent_state_root_opt { + Some(parent_state_root) => { + let state_opt = moveos_store.get_field_at(parent_state_root, &object_id.to_key())?; + match state_opt { + Some(state) => H256::from(state.as_raw_object()?.state_root.into_bytes()), + None => *GENESIS_STATE_ROOT, + } + } + None => *GENESIS_STATE_ROOT, + }; + + Ok(state_root) +} + pub fn apply_fields( moveos_store: &MoveOSStore, pre_state_root: H256, diff --git a/crates/rooch/src/commands/statedb/commands/mod.rs b/crates/rooch/src/commands/statedb/commands/mod.rs index ba8bd3864c..692e76b0f4 100644 --- a/crates/rooch/src/commands/statedb/commands/mod.rs +++ b/crates/rooch/src/commands/statedb/commands/mod.rs @@ -17,6 +17,11 @@ pub mod export; pub mod genesis_utxo; pub mod import; +pub const BATCH_SIZE: usize = 5000; + +pub const STATE_HEADER_PREFIX: &str = "states"; +// pub const GLOBAL_STATE_TYPE_OBJECT: &str = "objectstate"; +// pub const GLOBAL_STATE_TYPE_FIELD: &str = "fieldstate"; pub fn init_statedb( base_data_dir: Option, chain_id: Option, diff --git a/moveos/moveos-store/src/state_store/statedb.rs b/moveos/moveos-store/src/state_store/statedb.rs index 39b49e2c5a..caeb16713b 100644 --- a/moveos/moveos-store/src/state_store/statedb.rs +++ b/moveos/moveos-store/src/state_store/statedb.rs @@ -2,14 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use crate::state_store::NodeDBStore; -use anyhow::{ensure, Error, Result}; +use anyhow::{Error, Result}; use move_core_types::effects::Op; use moveos_types::moveos_std::object::ObjectID; use moveos_types::moveos_std::object::GENESIS_STATE_ROOT; +use moveos_types::state::FieldChange; +use moveos_types::state::KeyState; use moveos_types::state::ObjectChange; use moveos_types::state::StateChangeSet; -use moveos_types::state::{FieldChange, MoveState, MoveType, ObjectState}; -use moveos_types::state::{FieldState, KeyState}; use moveos_types::state_resolver::RootObjectResolver; use moveos_types::state_resolver::StateKV; use moveos_types::state_resolver::StateResolver; @@ -149,67 +149,6 @@ impl StateDBStore { ) -> Result> { self.smt.iter(state_root, starting_key) } - - // Batch dump child object states of specified object by object id - pub fn dump_child_object_states( - &self, - parent_id: ObjectID, - state_root: H256, - starting_key: Option, - with_parent: bool, - ) -> Result<(Vec, Option)> { - let iter = self.iter(state_root, starting_key)?; - let mut data = Vec::new(); - let mut counter = 0; - let mut next_key = None; - for item in iter { - if counter >= STATEDB_DUMP_BATCH_SIZE { - break; - }; - let (k, v) = item?; - ensure!(k.key_type == ObjectID::type_tag()); - let obj_id = ObjectID::from_bytes(k.key.clone())?; - if (with_parent && obj_id == parent_id) || obj_id.is_child(parent_id.clone()) { - let obj = v.as_raw_object()?; - let object_change = ObjectChange::new(Op::New(v)); - let object_state = ObjectState::new( - H256::from(obj.state_root.into_bytes()), - obj.size, - obj.id, - object_change, - ); - data.push(object_state); - - counter += 1; - }; - next_key = Some(k); - } - Ok((data, next_key)) - } - - /// Batch dump filed states of specified object by object id - pub fn dump_field_states( - &self, - _object_id: ObjectID, - state_root: H256, - starting_key: Option, - ) -> Result<(Vec, Option)> { - let iter = self.iter(state_root, starting_key)?; - let mut data = Vec::new(); - let mut next_key = None; - for (counter, item) in iter.enumerate() { - if counter >= STATEDB_DUMP_BATCH_SIZE { - break; - }; - let (k, v) = item?; - let field_change = FieldChange::new_normal(Op::New(v)); - let field_state = FieldState::new(k.clone(), field_change); - data.push(field_state); - - next_key = Some(k); - } - Ok((data, next_key)) - } } impl StatelessResolver for StateDBStore { diff --git a/moveos/moveos-store/src/tests/test_state_store.rs b/moveos/moveos-store/src/tests/test_state_store.rs index 40104974c9..aa0de4e80d 100644 --- a/moveos/moveos-store/src/tests/test_state_store.rs +++ b/moveos/moveos-store/src/tests/test_state_store.rs @@ -3,7 +3,6 @@ use crate::MoveOSStore; use anyhow::Result; -use move_core_types::account_address::AccountAddress; use move_core_types::effects::Op; use moveos_types::h256::H256; use moveos_types::move_std::string::MoveString; @@ -14,7 +13,7 @@ use moveos_types::state::{ FieldChange, KeyState, MoveState, MoveType, ObjectChange, StateChangeSet, }; use moveos_types::state_resolver::StatelessResolver; -use moveos_types::test_utils::{random_state_change_set, random_state_change_set_for_child_object}; +use moveos_types::test_utils::random_state_change_set; use smt::NodeReader; use std::str::FromStr; @@ -95,61 +94,61 @@ fn test_statedb_state_root() -> Result<()> { Ok(()) } -#[test] -fn test_child_state_db_dump_and_apply() -> Result<()> { - let mut moveos_store = MoveOSStore::mock_moveos_store().expect("moveos store mock should succ"); - - let base_state_change_set = random_state_change_set(); - let (new_state_root, global_size) = moveos_store - .get_state_store_mut() - .apply_change_set(base_state_change_set)?; - - let parent_id = ObjectID::from(AccountAddress::random()); - let mut state_change_set = random_state_change_set_for_child_object(parent_id.clone()); - state_change_set.global_size += global_size; - state_change_set.state_root = new_state_root; - let (new_state_root, _global_size) = moveos_store - .get_state_store_mut() - .apply_change_set(state_change_set)?; - - let mut dump_state_change_set = StateChangeSet::default(); - let (child_object_state, _next_key) = moveos_store.get_state_store().dump_child_object_states( - parent_id.clone(), - new_state_root, - None, - true, - )?; - for object_state in child_object_state.clone() { - let (field_states, _next_key) = moveos_store.get_state_store().dump_field_states( - object_state.object_id.clone(), - object_state.state_root, - None, - )?; - - let object_id = object_state.object_id.clone(); - let mut child_object_change = object_state.object_change.clone(); - //reset object state root for ObjectChange - child_object_change.reset_state_root(*GENESIS_STATE_ROOT)?; - - child_object_change.add_field_changes(field_states); - dump_state_change_set - .changes - .insert(object_id, child_object_change); - } - - let mut moveos_store2 = - MoveOSStore::mock_moveos_store().expect("moveos store mock should succ"); - let (new_state_root, _global_size) = moveos_store2 - .get_state_store_mut() - .apply_change_set(dump_state_change_set.clone())?; - let (new_child_object_state, _next_key) = moveos_store2 - .get_state_store() - .dump_child_object_states(parent_id, new_state_root, None, true)?; - for (idx, new_object_state) in new_child_object_state.iter().enumerate() { - assert_eq!( - new_object_state.state_root, - child_object_state[idx].state_root - ); - } - Ok(()) -} +// #[test] +// fn test_child_state_db_dump_and_apply() -> Result<()> { +// let mut moveos_store = MoveOSStore::mock_moveos_store().expect("moveos store mock should succ"); +// +// let base_state_change_set = random_state_change_set(); +// let (new_state_root, global_size) = moveos_store +// .get_state_store_mut() +// .apply_change_set(base_state_change_set)?; +// +// let parent_id = ObjectID::from(AccountAddress::random()); +// let mut state_change_set = random_state_change_set_for_child_object(parent_id.clone()); +// state_change_set.global_size += global_size; +// state_change_set.state_root = new_state_root; +// let (new_state_root, _global_size) = moveos_store +// .get_state_store_mut() +// .apply_change_set(state_change_set)?; +// +// let mut dump_state_change_set = StateChangeSet::default(); +// let (child_object_state, _next_key) = moveos_store.get_state_store().dump_child_object_states( +// parent_id.clone(), +// new_state_root, +// None, +// true, +// )?; +// for object_state in child_object_state.clone() { +// let (field_states, _next_key) = moveos_store.get_state_store().dump_field_states( +// object_state.object_id.clone(), +// object_state.state_root, +// None, +// )?; +// +// let object_id = object_state.object_id.clone(); +// let mut child_object_change = object_state.object_change.clone(); +// //reset object state root for ObjectChange +// child_object_change.reset_state_root(*GENESIS_STATE_ROOT)?; +// +// child_object_change.add_field_changes(field_states); +// dump_state_change_set +// .changes +// .insert(object_id, child_object_change); +// } +// +// let mut moveos_store2 = +// MoveOSStore::mock_moveos_store().expect("moveos store mock should succ"); +// let (new_state_root, _global_size) = moveos_store2 +// .get_state_store_mut() +// .apply_change_set(dump_state_change_set.clone())?; +// let (new_child_object_state, _next_key) = moveos_store2 +// .get_state_store() +// .dump_child_object_states(parent_id, new_state_root, None, true)?; +// for (idx, new_object_state) in new_child_object_state.iter().enumerate() { +// assert_eq!( +// new_object_state.state_root, +// child_object_state[idx].state_root +// ); +// } +// Ok(()) +// } diff --git a/moveos/moveos-types/src/moveos_std/object.rs b/moveos/moveos-types/src/moveos_std/object.rs index bdbf892d17..5b81ccc5c7 100644 --- a/moveos/moveos-types/src/moveos_std/object.rs +++ b/moveos/moveos-types/src/moveos_std/object.rs @@ -99,6 +99,10 @@ impl ObjectID { !self.is_root() } + pub fn has_child(&self) -> bool { + self.is_root() || self.parent().is_some() + } + pub fn is_child(&self, parent_id: ObjectID) -> bool { match self.parent() { Some(obj_id) => obj_id == parent_id, diff --git a/moveos/moveos-types/src/state.rs b/moveos/moveos-types/src/state.rs index 25956a3227..4ef784fb93 100644 --- a/moveos/moveos-types/src/state.rs +++ b/moveos/moveos-types/src/state.rs @@ -665,6 +665,36 @@ impl State { let decoded_value = annotator.view_value(&self.value_type, &self.value)?; Ok(AnnotatedState::new(self, decoded_value)) } + + pub fn from_bytes(bytes: &[u8]) -> Result + where + Self: Sized, + { + bcs::from_bytes(bytes).map_err(|e| anyhow::anyhow!("Deserialize the State error: {:?}", e)) + } + + pub fn to_bytes(&self) -> Result> { + bcs::to_bytes(self).map_err(|e| anyhow::anyhow!("Serialize the State error: {:?}", e)) + } +} + +impl std::fmt::Display for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let hex_state = hex::encode( + self.to_bytes() + .map_err(|e| std::fmt::Error::custom(e.to_string()))?, + ); + write!(f, "0x{}", hex_state) + } +} + +impl FromStr for State { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result { + let state = hex::decode(s.strip_prefix("0x").unwrap_or(s)) + .map_err(|_| anyhow::anyhow!("Invalid state str: {}", s))?; + State::from_bytes(state.as_slice()) + } } #[derive(Debug, Clone)]