diff --git a/Cargo.lock b/Cargo.lock index 7d08a2cd0..e4bd7589e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -639,6 +639,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim", + "terminal_size", ] [[package]] @@ -671,7 +672,7 @@ checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "clar2wasm" version = "0.1.0" -source = "git+https://github.com/stacks-network/clarity-wasm.git?branch=main#a38e1e5b2dc6826d9cfeef1cb855365987f8da86" +source = "git+https://github.com/stacks-network/clarity-wasm.git?branch=main#d2dc2e4ddc2b359c7d20c5354b375f4f5867591b" dependencies = [ "chrono", "clap", @@ -865,6 +866,7 @@ dependencies = [ "clarity", "colored", "debug_types", + "divan", "futures", "getrandom 0.2.8", "hiro-system-kit 0.1.0", @@ -935,6 +937,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "condtype" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1493,6 +1501,31 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "divan" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc40f214f0d9e897cfc72e2edfa5c225d3252f758c537f11ac0a80371c073a6" +dependencies = [ + "cfg-if 1.0.0", + "clap", + "condtype", + "divan-macros", + "libc", + "regex-lite", +] + +[[package]] +name = "divan-macros" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bdb5411188f7f878a17964798c1264b6b0a9f915bd39b20bf99193c923e1b4e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "dyn-clone" version = "1.0.10" @@ -3696,6 +3729,12 @@ dependencies = [ "regex-syntax 0.8.1", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -4875,6 +4914,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "test-case" version = "3.3.1" @@ -6028,6 +6077,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.42.1" diff --git a/Cargo.toml b/Cargo.toml index 2b9bac359..4f056428e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,8 @@ web-sys = { version = "0.3" } chainhook-sdk = { git = "https://github.com/hirosystems/chainhook.git" } chainhook-types = { git = "https://github.com/hirosystems/chainhook.git" } stacks-codec = { path = "./components/stacks-codec" } + +# [patch.'https://github.com/stacks-network/stacks-core.git'] +# clarity = { path = "../stacks-core/clarity" } +# stacks-common = { path = "../stacks-core/stacks-common" } +# stackslib = { path = "../stacks-core/stackslib" } diff --git a/components/clarity-repl/Cargo.toml b/components/clarity-repl/Cargo.toml index 3d4d8dc29..a60519f1c 100644 --- a/components/clarity-repl/Cargo.toml +++ b/components/clarity-repl/Cargo.toml @@ -56,6 +56,7 @@ reqwest = { workspace = true } [dev-dependencies] test-case = "*" +divan = "0.1" [lib] name = "clarity_repl" @@ -65,6 +66,10 @@ path = "src/lib.rs" name = "clarity-repl" path = "src/bin.rs" +[[bench]] +name = "simnet" +harness = false + [features] default = ["cli", "dap"] sdk = [ diff --git a/components/clarity-repl/benches/simnet.rs b/components/clarity-repl/benches/simnet.rs new file mode 100644 index 000000000..67b260a69 --- /dev/null +++ b/components/clarity-repl/benches/simnet.rs @@ -0,0 +1,163 @@ +use std::hint::black_box; + +use clarity::{ + types::StacksEpochId, + vm::{ + types::QualifiedContractIdentifier, EvaluationResult, ExecutionResult, SymbolicExpression, + Value as ClarityValue, + }, +}; +use clarity_repl::repl::{ + ClarityCodeSource, ClarityContract, ContractDeployer, Session, SessionSettings, + DEFAULT_CLARITY_VERSION, DEFAULT_EPOCH, +}; +use divan::Bencher; + +fn init_session() -> Session { + let mut session = Session::new(SessionSettings::default()); + session.update_epoch(StacksEpochId::Epoch30); + session.advance_burn_chain_tip(1); + assert_eq!(session.interpreter.get_block_height(), 2); + + let src = [ + "(define-data-var buff-data (buff 32) 0x00)", + "(define-map history uint (buff 32))", + "(define-read-only (noop-ro (i uint) (d (buff 32)))", + " (ok true)", + ")", + "(define-public (noop-pub (i uint) (d (buff 32)))", + " (ok true)", + ")", + "(define-public (save (i uint) (d (buff 32)))", + " (begin", + " (map-insert history i d)", + " (ok (var-set buff-data d))", + " )", + ")", + ] + .join("\n"); + + let contract = ClarityContract { + code_source: ClarityCodeSource::ContractInMemory(src.to_string()), + name: "contract".into(), + deployer: ContractDeployer::DefaultDeployer, + clarity_version: DEFAULT_CLARITY_VERSION, + epoch: DEFAULT_EPOCH, + }; + + let _ = session.deploy_contract(&contract, false, None); + session.advance_burn_chain_tip(1); + + assert_eq!(session.interpreter.get_block_height(), 3); + session +} + +fn call_fn( + session: &mut Session, + func: &str, + args: &[ClarityValue], + advance_chain: bool, +) -> ClarityValue { + let ExecutionResult { result, .. } = session + .call_contract_fn( + "contract", + func, + &args + .iter() + .map(|v: &ClarityValue| SymbolicExpression::atom_value(v.clone())) + .collect::>(), + "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + false, + false, + ) + .unwrap(); + + let v = match &result { + EvaluationResult::Snippet(r) => r.result.clone(), + EvaluationResult::Contract(_contract) => { + unreachable!(); + } + }; + if advance_chain { + let _ = session.advance_stacks_chain_tip(1); + } + v +} + +fn int_to_buff(i: u32) -> ClarityValue { + let str = i.to_string(); + let buff = str.bytes().collect::>(); + ClarityValue::buff_from(buff).unwrap() +} + +#[divan::bench(sample_count = 10000)] +fn simnet_noop_read_only(bencher: Bencher) { + let mut session = init_session(); + let initial_block_height = session.interpreter.get_block_height(); + let mut i: u32 = 0; + + bencher.bench_local(|| { + let args = [ClarityValue::UInt(black_box(i.into())), int_to_buff(i)]; + let result = call_fn(black_box(&mut session), "noop-ro", &args, false); + assert_eq!( + black_box(initial_block_height), + session.interpreter.get_block_height() + ); + assert_eq!(result, ClarityValue::okay_true()); + + i += 1; + }); +} + +#[divan::bench(sample_count = 10000)] +fn simnet_noop_public(bencher: Bencher) { + let mut session = init_session(); + let initial_block_height = session.interpreter.get_block_height(); + let mut i: u32 = 0; + + bencher.bench_local(|| { + let args = [ClarityValue::UInt(black_box(i).into()), int_to_buff(i)]; + let result = call_fn(black_box(&mut session), "noop-pub", &args, true); + + assert_eq!( + black_box(initial_block_height + i + 1), + session.interpreter.get_block_height() + ); + assert_eq!(result, ClarityValue::okay_true()); + i += 1; + }); +} + +#[divan::bench(sample_count = 10000)] +fn simnet_save(bencher: Bencher) { + let mut session = init_session(); + let initial_block_height = session.interpreter.get_block_height(); + let mut i: u32 = 0; + + bencher.bench_local(|| { + let args = [ClarityValue::UInt(black_box(i).into()), int_to_buff(i)]; + let result = call_fn(black_box(&mut session), "save", &args, true); + + assert_eq!( + black_box(initial_block_height + i + 1), + session.interpreter.get_block_height() + ); + assert_eq!(result, ClarityValue::okay_true()); + i += 1; + }); + + let contract_id = + QualifiedContractIdentifier::parse("ST000000000000000000002AMW42H.contract").unwrap(); + let contract_data = session + .interpreter + .get_data_var(&contract_id, "buff-data") + .unwrap(); + + let expected = format!("0x{}", int_to_buff(i - 1).serialize_to_hex().unwrap()); + assert_eq!(contract_data, expected); +} + +fn main() { + // simnet_benchmark(); + divan::main(); +} diff --git a/components/clarity-repl/src/repl/datastore.rs b/components/clarity-repl/src/repl/datastore.rs index f238bbe02..e11b49bfc 100644 --- a/components/clarity-repl/src/repl/datastore.rs +++ b/components/clarity-repl/src/repl/datastore.rs @@ -40,11 +40,14 @@ fn epoch_to_peer_version(epoch: StacksEpochId) -> u8 { } } +#[derive(Clone, Debug)] +struct StoreEntry(StacksBlockId, String); + #[derive(Clone, Debug)] pub struct ClarityDatastore { open_chain_tip: StacksBlockId, current_chain_tip: StacksBlockId, - store: HashMap>, + store: HashMap>, metadata: HashMap<(String, String), String>, block_id_lookup: HashMap, height_at_chain_tip: HashMap, @@ -118,7 +121,7 @@ impl ClarityDatastore { Self { open_chain_tip: id, current_chain_tip: id, - store: HashMap::from([(id, HashMap::new())]), + store: HashMap::new(), metadata: HashMap::new(), block_id_lookup: HashMap::from([(id, id)]), height_at_chain_tip: HashMap::from([(id, 0)]), @@ -177,34 +180,36 @@ impl ClarityDatastore { // .expect("ERROR: Failed to commit MARF block"); } - pub fn put(&mut self, key: &str, value: &str) { - let lookup_id = self - .block_id_lookup - .get(&self.open_chain_tip) - .expect("Could not find current chain tip in block_id_lookup map"); - - // if there isn't a store for the open chain_tip, make one and update the - // entry for the block id in the lookup table - if *lookup_id != self.open_chain_tip { + fn put(&mut self, key: &str, value: &str) { + if let Some(entries) = self.store.get_mut(key) { + entries.push(StoreEntry(self.open_chain_tip, value.to_string())); + } else { self.store.insert( - self.open_chain_tip, - self.store - .get(lookup_id) - .unwrap_or_else(|| panic!("Block with ID {:?} does not exist", lookup_id)) - .clone(), + key.to_string(), + vec![StoreEntry(self.open_chain_tip, value.to_string())], ); - - self.block_id_lookup - .insert(self.open_chain_tip, self.current_chain_tip); } + } + + fn get_latest_data_inner(&self, data: &[StoreEntry]) -> Option { + let StoreEntry(tip, value) = data.last()?; - if let Some(map) = self.store.get_mut(&self.open_chain_tip) { - map.insert(key.to_string(), value.to_string()); + if self.height_at_chain_tip.get(tip).unwrap() + <= self + .height_at_chain_tip + .get(&self.current_chain_tip) + .unwrap() + { + Some(value.clone()) } else { - panic!("Block does not exist for current chain tip"); + self.get_latest_data_inner(&data[..data.len() - 1]) } } + fn get_latest_data(&self, key: &str) -> Option { + self.get_latest_data_inner(self.store.get(key)?) + } + pub fn make_contract_hash_key(contract: &QualifiedContractIdentifier) -> String { format!("clarity-contract::{}", contract) } @@ -220,16 +225,7 @@ impl ClarityBackingStore for ClarityDatastore { /// fetch K-V out of the committed datastore fn get_data(&mut self, key: &str) -> Result> { - let lookup_id = self - .block_id_lookup - .get(&self.current_chain_tip) - .expect("Could not find current chain tip in block_id_lookup map"); - - if let Some(map) = self.store.get(lookup_id) { - Ok(map.get(key).cloned()) - } else { - panic!("Block does not exist for current chain tip"); - } + Ok(self.get_latest_data(key)) } fn get_data_from_path(&mut self, _hash: &TrieHash) -> Result> { diff --git a/components/clarity-repl/src/repl/interpreter.rs b/components/clarity-repl/src/repl/interpreter.rs index 60188751e..0c7c58c00 100644 --- a/components/clarity-repl/src/repl/interpreter.rs +++ b/components/clarity-repl/src/repl/interpreter.rs @@ -840,10 +840,16 @@ impl ClarityInterpreter { &self.datastore, ); let tx_sender: PrincipalData = self.tx_sender.clone().into(); - conn.begin(); - conn.set_clarity_epoch_version(epoch) - .map_err(|e| e.to_string())?; - conn.commit().map_err(|e| e.to_string())?; + + // this can probably be removed, even the clarity_version arg + // check if this is actually needed or not + + // let start = std::time::Instant::now(); + // conn.begin(); + // conn.set_clarity_epoch_version(epoch) + // .map_err(|e| e.to_string())?; + // conn.commit().map_err(|e| e.to_string())?; + // println!("elapsed: {:?}", start.elapsed()); let cost_tracker = if track_costs { LimitedCostTracker::new( false,