Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial implementation of mainnet execution simulation #1509

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 26 additions & 16 deletions components/clarinet-deployments/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,19 @@ pub fn update_session_with_deployment_plan(
contracts_asts: Option<&BTreeMap<QualifiedContractIdentifier, ContractAST>>,
forced_min_epoch: Option<StacksEpochId>,
) -> UpdateSessionExecutionResult {
session.advance_chain_tip(10);
update_session_with_genesis_accounts(session, deployment);

let boot_contracts_data = BOOT_CONTRACTS_DATA.clone();

let mut boot_contracts = BTreeMap::new();
for (contract_id, (boot_contract, ast)) in boot_contracts_data {
let result = session
.interpreter
.run(&boot_contract, Some(&ast), false, None);
boot_contracts.insert(contract_id, result);
if !session.settings.repl_settings.network_simulation.enabled {
let boot_contracts_data = BOOT_CONTRACTS_DATA.clone();

for (contract_id, (boot_contract, ast)) in boot_contracts_data {
let result = session
.interpreter
.run(&boot_contract, Some(&ast), false, None);
boot_contracts.insert(contract_id, result);
}
}

let mut contracts = BTreeMap::new();
Expand Down Expand Up @@ -343,17 +346,21 @@ pub async fn generate_default_deployment(
repl_settings: manifest.repl_settings.clone(),
..Default::default()
};

let session = Session::new(settings.clone());

let boot_contracts_data = BOOT_CONTRACTS_DATA.clone();
let simnet_network_simulation = matches!(network, StacksNetwork::Simnet)
&& session.settings.repl_settings.network_simulation.enabled;

let mut boot_contracts_ids = BTreeSet::new();
let mut boot_contracts_asts = BTreeMap::new();
for (id, (contract, ast)) in boot_contracts_data {
boot_contracts_ids.insert(id.clone());
boot_contracts_asts.insert(id, (contract.clarity_version, ast));
if !simnet_network_simulation {
let boot_contracts_data = BOOT_CONTRACTS_DATA.clone();
let mut boot_contracts_asts = BTreeMap::new();
for (id, (contract, ast)) in boot_contracts_data {
boot_contracts_ids.insert(id.clone());
boot_contracts_asts.insert(id, (contract.clarity_version, ast));
}
requirements_data.append(&mut boot_contracts_asts);
}
requirements_data.append(&mut boot_contracts_asts);

let mut queue = VecDeque::new();

Expand Down Expand Up @@ -429,7 +436,10 @@ pub async fn generate_default_deployment(
location: contract_location,
clarity_version,
};
emulated_contracts_publish.insert(contract_id.clone(), data);

if !simnet_network_simulation {
emulated_contracts_publish.insert(contract_id.clone(), data);
}
} else if matches!(network, StacksNetwork::Devnet | StacksNetwork::Testnet) {
let mut remap_principals = BTreeMap::new();
remap_principals
Expand Down Expand Up @@ -533,7 +543,7 @@ pub async fn generate_default_deployment(
}

// Avoid listing requirements as deployment transactions to the deployment specification on Mainnet
if !matches!(network, StacksNetwork::Mainnet) {
if !matches!(network, StacksNetwork::Mainnet) && !simnet_network_simulation {
let mut ordered_contracts_ids = match ASTDependencyDetector::order_contracts(
&requirements_deps,
&contract_epochs,
Expand Down
14 changes: 7 additions & 7 deletions components/clarinet-files/src/project_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,6 @@ pub struct ClarityContractMetadata {
pub epoch: StacksEpochId,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ProjectManifestFile {
project: ProjectConfigFile,
contracts: Option<TomlValue>,
repl: Option<repl::SettingsFile>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ProjectConfigFile {
name: String,
Expand All @@ -48,6 +41,13 @@ pub struct ProjectConfigFile {
cache_dir: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ProjectManifestFile {
project: ProjectConfigFile,
contracts: Option<TomlValue>,
repl: Option<repl::SettingsFile>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ProjectManifest {
pub project: ProjectConfig,
Expand Down
145 changes: 141 additions & 4 deletions components/clarity-repl/src/repl/datastore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ pub struct ClarityDatastore {
metadata: HashMap<(String, String), String>,
block_id_lookup: HashMap<StacksBlockId, StacksBlockId>,
height_at_chain_tip: HashMap<StacksBlockId, u32>,
remote_chaintip_cache: HashMap<u32, String>,
}

#[derive(Deserialize)]
struct ClarityDataResponse {
pub data: String,
}

#[derive(Deserialize)]
struct BlockInfoResponse {
pub index_block_hash: String,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -121,6 +132,7 @@ impl ClarityDatastore {
metadata: HashMap::new(),
block_id_lookup: HashMap::from([(id, id)]),
height_at_chain_tip: HashMap::from([(id, 0)]),
remote_chaintip_cache: HashMap::new(),
}
}

Expand Down Expand Up @@ -207,6 +219,111 @@ impl ClarityDatastore {
pub fn make_contract_hash_key(contract: &QualifiedContractIdentifier) -> String {
format!("clarity-contract::{}", contract)
}

#[cfg(any(feature = "cli", feature = "sdk"))]
fn get_remote_chaintip(&mut self, height: u32) -> String {
if let Some(cached) = self.remote_chaintip_cache.get(&height) {
println!("using cached chaintip for height: {}", height);
return cached.to_string();
}
println!("fetching remote chaintip for height: {}", height);

let url = format!("http://localhost:3999/extended/v2/blocks/{}", height);

let response = reqwest::blocking::get(url).unwrap_or_else(|e| {
panic!(
"unable to fetch remote chaintip for height: {}\nError: {}",
height, e
)
});

let data = response.json::<BlockInfoResponse>().unwrap_or_else(|e| {
panic!("unable to parse json, error: {}", e);
});

let block_hash = data.index_block_hash.replacen("0x", "", 1);
self.remote_chaintip_cache
.insert(height, block_hash.clone());

block_hash
}

#[cfg(any(feature = "cli", feature = "sdk"))]
fn fetch_remote_data(&mut self, path: &str) -> Result<Option<String>> {
let url = format!("http://localhost:3999/v2{}", path);

let response = reqwest::blocking::get(url)
.unwrap_or_else(|e| panic!("unable to fetch value for: {}\nError: {}", path, e));

if response.status() == reqwest::StatusCode::NOT_FOUND {
if response.text().unwrap_or_default() == "Chain tip not found" {
println!("An invalid chaintip was specified do");
}
return Ok(None);
}

if response.status() != reqwest::StatusCode::OK {
println!(
"Failed to fetch data. Status: {}\nError: {}",
response.status(),
response.text().unwrap_or_default()
);
return Ok(None);
}

let data = response.json::<ClarityDataResponse>().unwrap_or_else(|e| {
panic!("unable to parse json, error: {}", e);
});

let value = if data.data.starts_with("0x") {
data.data[2..].to_string()
} else {
data.data
};

Ok(Some(value))
}

#[cfg(not(any(feature = "cli", feature = "sdk")))]
fn fetch_remote_data(&mut self, path: &str) {
// use wasm_bindgen::prelude::*;
// use web_sys::XmlHttpRequest;
let url = format!("http://localhost:3999/v2{}", path);
let xhr = XmlHttpRequest::new()?;
xhr.open("GET", url)?;
xhr.send()?;

if xhr.status() == 200 {
Ok(xhr.response_text()?.unwrap_or_default())
} else {
Err(JsValue::from_str(&format!(
"Failed to fetch data. Status: {}",
xhr.status()
)))
}
}

fn fetch_clarity_marf_value(&mut self, key: &str, block_height: u32) -> Result<Option<String>> {
let remote_chaintip = self.get_remote_chaintip(block_height);
let path = format!(
"/clarity_marf_value/{}?tip={}&proof=false",
key, remote_chaintip
);
dbg!(&path);
self.fetch_remote_data(&path)
}

fn fetch_clarity_metadata(
&mut self,
contract: &QualifiedContractIdentifier,
key: &str,
) -> Result<Option<String>> {
let addr = contract.issuer.to_string();
let contract = contract.name.to_string();

let url = format!("/clarity_metadata/{}/{}/{}", addr, contract, key);
self.fetch_remote_data(&url)
}
}

impl ClarityBackingStore for ClarityDatastore {
Expand All @@ -219,13 +336,26 @@ impl ClarityBackingStore for ClarityDatastore {

/// fetch K-V out of the committed datastore
fn get_data(&mut self, key: &str) -> Result<Option<String>> {
println!("get_data({})", key);
let current_block_height = self.get_current_block_height();

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())
let value = map.get(key);
if value.is_some() {
return Ok(value.cloned());
}
println!("fetching MARF from network");
let data = self.fetch_clarity_marf_value(key, current_block_height);
if let Ok(Some(value)) = &data {
println!("network marf value: {}", value);
self.put(key, value);
}
data
} else {
panic!("Block does not exist for current chain tip");
}
Expand Down Expand Up @@ -291,11 +421,18 @@ impl ClarityBackingStore for ClarityDatastore {
contract: &QualifiedContractIdentifier,
key: &str,
) -> Result<Option<String>> {
let key = &(contract.to_string(), key.to_string());
println!("get_metadata({}, {})", contract, key);

match self.metadata.get(key) {
match self.metadata.get(&(contract.to_string(), key.to_string())) {
Some(result) => Ok(Some(result.to_string())),
None => Ok(None),
None => {
println!("fetching METADATA from network");
let data = self.fetch_clarity_metadata(contract, key);
if let Ok(Some(value)) = &data {
let _ = self.insert_metadata(contract, key, value);
}
data
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions components/clarity-repl/src/repl/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,7 @@ impl ClarityInterpreter {
mod tests {
use super::*;
use crate::analysis::Settings as AnalysisSettings;
use crate::repl::settings::NetworkSimulationSettings;
use crate::{
repl::session::BOOT_CONTRACTS_DATA, test_fixtures::clarity_contract::ClarityContractBuilder,
};
Expand Down Expand Up @@ -1308,6 +1309,7 @@ mod tests {
fn test_advance_stacks_chain_tip() {
let wasm_settings = Settings {
analysis: AnalysisSettings::default(),
network_simulation: NetworkSimulationSettings::default(),
clarity_wasm_mode: true,
show_timings: false,
};
Expand Down
Loading
Loading