From ace641164465d7a253375365a2f805a650981d09 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Mon, 10 Oct 2022 19:00:47 -0400 Subject: [PATCH] feat: introduce stacks-network, hiro-system-kit --- Cargo.lock | 58 +- Cargo.toml | 4 +- components/chainhook-db/Cargo.toml | 1 + components/chainhook-db/src/cli/mod.rs | 6 +- .../chainhook-event-observer/Cargo.toml | 2 + .../src/observer/mod.rs | 6 +- .../src/observer/tests/mod.rs | 10 +- .../chainhook-event-observer/src/utils/mod.rs | 14 - components/clarinet-cli/Cargo.toml | 20 +- components/clarinet-cli/src/bin.rs | 4 +- .../clarinet-cli/src/deployments/mod.rs | 675 +----------------- .../clarinet-cli/src/deployments/ui/app.rs | 2 +- .../clarinet-cli/src/deployments/ui/mod.rs | 2 +- .../clarinet-cli/src/deployments/ui/ui.rs | 2 +- components/clarinet-cli/src/frontend/cli.rs | 12 +- components/clarinet-cli/src/integrate/mod.rs | 305 +------- components/clarinet-cli/src/lib.rs | 2 - components/clarinet-cli/src/lsp/mod.rs | 10 +- components/clarinet-cli/src/runner/deno.rs | 2 +- components/clarinet-cli/src/runner/mod.rs | 2 +- components/clarinet-cli/src/types/mod.rs | 6 - components/clarinet-deployments/Cargo.toml | 16 +- components/clarinet-deployments/src/lib.rs | 2 + .../src/onchain}/bitcoin_deployment.rs | 2 +- .../clarinet-deployments/src/onchain/mod.rs | 657 +++++++++++++++++ components/hiro-system-kit/Cargo.toml | 10 + .../mod.rs => hiro-system-kit/src/lib.rs} | 0 components/stacks-network/Cargo.toml | 43 ++ .../src}/chains_coordinator.rs | 96 ++- components/stacks-network/src/lib.rs | 302 ++++++++ .../src}/orchestrator.rs | 2 +- .../src}/ui/app.rs | 4 +- .../src}/ui/mod.rs | 2 +- .../integrate => stacks-network/src}/ui/ui.rs | 2 +- .../src}/ui/util/event.rs | 0 .../src}/ui/util/mod.rs | 0 components/stacks-network/src/utils.rs | 12 + 37 files changed, 1224 insertions(+), 1071 deletions(-) delete mode 100644 components/clarinet-cli/src/types/mod.rs rename components/{clarinet-cli/src/deployments => clarinet-deployments/src/onchain}/bitcoin_deployment.rs (98%) create mode 100644 components/clarinet-deployments/src/onchain/mod.rs create mode 100644 components/hiro-system-kit/Cargo.toml rename components/{clarinet-cli/src/utils/mod.rs => hiro-system-kit/src/lib.rs} (100%) create mode 100644 components/stacks-network/Cargo.toml rename components/{clarinet-cli/src/integrate => stacks-network/src}/chains_coordinator.rs (90%) create mode 100644 components/stacks-network/src/lib.rs rename components/{clarinet-cli/src/integrate => stacks-network/src}/orchestrator.rs (99%) rename components/{clarinet-cli/src/integrate => stacks-network/src}/ui/app.rs (97%) rename components/{clarinet-cli/src/integrate => stacks-network/src}/ui/mod.rs (99%) rename components/{clarinet-cli/src/integrate => stacks-network/src}/ui/ui.rs (99%) rename components/{clarinet-cli/src/integrate => stacks-network/src}/ui/util/event.rs (100%) rename components/{clarinet-cli/src/integrate => stacks-network/src}/ui/util/mod.rs (100%) create mode 100644 components/stacks-network/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index f77a180c5..b09239961 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -716,6 +716,7 @@ dependencies = [ "csv", "ctrlc", "hex", + "hiro-system-kit", "num_cpus", "redis", "serde", @@ -745,6 +746,7 @@ dependencies = [ "clarinet-utils", "clarity-repl", "ctrlc", + "hiro-system-kit", "reqwest", "rocket", "rocket_okapi", @@ -885,10 +887,7 @@ dependencies = [ "base58 0.2.0", "base64 0.13.0", "bitcoin 0.28.1", - "bitcoincore-rpc", - "bitcoincore-rpc-json", "block-modes 0.8.1", - "bollard", "cache_control", "chainhook-event-observer", "chainhook-types", @@ -932,6 +931,7 @@ dependencies = [ "futures", "fwdansi", "hex", + "hiro-system-kit", "hmac 0.12.1", "http", "hyper", @@ -970,21 +970,17 @@ dependencies = [ "shell-escape", "signal-hook-registry", "similar", - "stacks-rpc-client", + "stacks-network", "strum", "sys-info", "tempfile", "termcolor", "text-size", "text_lines 0.6.0", - "tiny-hderive", "tokio", "tokio-util 0.7.2", "toml", "tower-lsp", - "tracing", - "tracing-appender", - "tracing-subscriber", "tui", "twox-hash", "typed-arena", @@ -999,14 +995,22 @@ dependencies = [ name = "clarinet-deployments" version = "1.0.0" dependencies = [ + "base58 0.2.0", + "bitcoin 0.28.1", + "bitcoincore-rpc", + "bitcoincore-rpc-json", "chainhook-types", "clarinet-files", + "clarinet-utils", "clarity-repl", + "libsecp256k1 0.7.1", "reqwest", "serde", "serde_derive", "serde_json", "serde_yaml", + "stacks-rpc-client", + "tiny-hderive", ] [[package]] @@ -2702,6 +2706,14 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hiro-system-kit" +version = "0.1.0" +dependencies = [ + "futures", + "tokio", +] + [[package]] name = "hkdf" version = "0.12.3" @@ -5736,6 +5748,36 @@ dependencies = [ "serde", ] +[[package]] +name = "stacks-network" +version = "0.1.0" +dependencies = [ + "base58 0.2.0", + "bitcoin 0.28.1", + "bitcoincore-rpc", + "bollard", + "chainhook-event-observer", + "chainhook-types", + "chrono", + "clarinet-deployments", + "clarinet-files", + "clarinet-utils", + "clarity-repl", + "crossterm 0.22.1", + "ctrlc", + "futures", + "hiro-system-kit", + "reqwest", + "serde", + "serde_derive", + "serde_json", + "stacks-rpc-client", + "tracing", + "tracing-appender", + "tracing-subscriber", + "tui", +] + [[package]] name = "stacks-rpc-client" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 1021e4d98..84637693e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,10 @@ members = [ "components/clarity-lsp", "components/stacks-rpc-client", "components/stacks-devnet-js", + "components/stacks-network", "components/chainhook-types-rs", "components/chainhook-event-observer", - "components/chainhook-db" + "components/chainhook-db", + "components/hiro-system-kit", ] default-members = ["components/clarinet-cli", "components/chainhook-event-observer"] diff --git a/components/chainhook-db/Cargo.toml b/components/chainhook-db/Cargo.toml index 195094479..399513e04 100644 --- a/components/chainhook-db/Cargo.toml +++ b/components/chainhook-db/Cargo.toml @@ -15,6 +15,7 @@ redis = "0.21.5" serde-redis = "0.12.0" chainhook_event_observer = { package = "chainhook-event-observer", default-features = false, path = "../chainhook-event-observer" } chainhook_types = { package = "chainhook-types", path = "../chainhook-types-rs" } +hiro_system_kit = { package = "hiro-system-kit", path = "../hiro-system-kit" } clap = { version = "3.2.8", features = ["derive"], optional = true } clap_generate = { version = "3.0.3", optional = true } toml = { version = "0.5.6", features = ["preserve_order"], optional = true } diff --git a/components/chainhook-db/src/cli/mod.rs b/components/chainhook-db/src/cli/mod.rs index e2b217010..418cf7650 100644 --- a/components/chainhook-db/src/cli/mod.rs +++ b/components/chainhook-db/src/cli/mod.rs @@ -12,11 +12,11 @@ use chainhook_event_observer::{ start_event_observer, EventObserverConfig, ObserverCommand, ObserverEvent, DEFAULT_CONTROL_PORT, DEFAULT_INGESTION_PORT, }, - utils::nestable_block_on, }; use chainhook_types::{BlockIdentifier, StacksBlockData, StacksTransactionData}; use clap::Parser; use ctrlc; +use hiro_system_kit; use std::collections::HashSet; use std::{collections::HashMap, process, sync::mpsc::channel, thread}; @@ -113,7 +113,7 @@ pub fn main() { observer_command_rx, Some(observer_event_tx), ); - let _ = nestable_block_on(future); + let _ = hiro_system_kit::nestable_block_on(future); }); loop { @@ -182,7 +182,7 @@ pub fn main() { let proofs = HashMap::new(); if let Some(result) = handle_stacks_hook_action(trigger, &proofs) { if let StacksChainhookOccurrence::Http(request) = result { - nestable_block_on(request.send()).unwrap(); + hiro_system_kit::nestable_block_on(request.send()).unwrap(); } } } diff --git a/components/chainhook-event-observer/Cargo.toml b/components/chainhook-event-observer/Cargo.toml index ccb08546a..6855db4c1 100644 --- a/components/chainhook-event-observer/Cargo.toml +++ b/components/chainhook-event-observer/Cargo.toml @@ -12,6 +12,8 @@ serde_derive = "1" stacks_rpc_client = { package = "stacks-rpc-client", path = "../../components/stacks-rpc-client" } clarinet_utils = { package = "clarinet-utils", path = "../../components/clarinet-utils" } clarity_repl = { package = "clarity-repl", path = "../../components/clarity-repl" } +hiro_system_kit = { package = "hiro-system-kit", path = "../../components/hiro-system-kit" } + chainhook_types = { package = "chainhook-types", path = "../chainhook-types-rs" } rocket = { version = "=0.5.0-rc.2", features = ["json"] } bitcoincore-rpc = "0.14.0" diff --git a/components/chainhook-event-observer/src/observer/mod.rs b/components/chainhook-event-observer/src/observer/mod.rs index b8352152b..c6f464077 100644 --- a/components/chainhook-event-observer/src/observer/mod.rs +++ b/components/chainhook-event-observer/src/observer/mod.rs @@ -5,7 +5,6 @@ use crate::chainhooks::{ BitcoinChainhookOccurrencePayload, StacksChainhookOccurrence, StacksChainhookOccurrencePayload, }; use crate::indexer::{self, Indexer, IndexerConfig}; -use crate::utils; use bitcoincore_rpc::bitcoin::{BlockHash, Txid}; use bitcoincore_rpc::{Auth, Client, RpcApi}; use chainhook_types::{ @@ -13,6 +12,7 @@ use chainhook_types::{ TransactionIdentifier, }; use clarity_repl::clarity::util::hash::bytes_to_hex; +use hiro_system_kit; use reqwest::Client as HttpClient; use rocket::config::{Config, LogLevel}; use rocket::http::Status; @@ -300,7 +300,7 @@ pub async fn start_event_observer( .mount("/", routes) .launch(); - let _ = utils::nestable_block_on(future); + let _ = hiro_system_kit::nestable_block_on(future); }); let control_config = Config { @@ -331,7 +331,7 @@ pub async fn start_event_observer( .mount("/", routes) .launch(); - let _ = utils::nestable_block_on(future); + let _ = hiro_system_kit::nestable_block_on(future); }); // This loop is used for handling background jobs, emitted by HTTP calls. diff --git a/components/chainhook-event-observer/src/observer/tests/mod.rs b/components/chainhook-event-observer/src/observer/tests/mod.rs index 6219bf56b..41dac0265 100644 --- a/components/chainhook-event-observer/src/observer/tests/mod.rs +++ b/components/chainhook-event-observer/src/observer/tests/mod.rs @@ -11,12 +11,12 @@ use crate::observer::{ self, start_observer_commands_handler, ApiKey, ChainhookStore, EventHandler, EventObserverConfig, ObserverCommand, }; -use crate::utils; use chainhook_types::{ BitcoinChainEvent, BitcoinChainUpdatedWithBlocksData, BitcoinNetwork, StacksBlockData, StacksBlockUpdate, StacksChainEvent, StacksChainUpdatedWithBlocksData, StacksNetwork, }; use clarity_repl::clarity::vm::types::QualifiedContractIdentifier; +use hiro_system_kit; use std::collections::{HashMap, HashSet}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::{Arc, RwLock}; @@ -153,7 +153,7 @@ fn test_stacks_chainhook_register_deregister() { let handle = std::thread::spawn(move || { let (config, chainhook_store) = generate_test_config(); - let _ = crate::utils::nestable_block_on(start_observer_commands_handler( + let _ = hiro_system_kit::nestable_block_on(start_observer_commands_handler( config, Arc::new(RwLock::new(chainhook_store)), observer_commands_rx, @@ -329,7 +329,7 @@ fn test_stacks_chainhook_auto_deregister() { let handle = std::thread::spawn(move || { let (config, chainhook_store) = generate_test_config(); - let _ = crate::utils::nestable_block_on(start_observer_commands_handler( + let _ = hiro_system_kit::nestable_block_on(start_observer_commands_handler( config, Arc::new(RwLock::new(chainhook_store)), observer_commands_rx, @@ -485,7 +485,7 @@ fn test_bitcoin_chainhook_register_deregister() { let handle = std::thread::spawn(move || { let (config, chainhook_store) = generate_test_config(); - let _ = crate::utils::nestable_block_on(start_observer_commands_handler( + let _ = hiro_system_kit::nestable_block_on(start_observer_commands_handler( config, Arc::new(RwLock::new(chainhook_store)), observer_commands_rx, @@ -681,7 +681,7 @@ fn test_bitcoin_chainhook_auto_deregister() { let handle = std::thread::spawn(move || { let (config, chainhook_store) = generate_test_config(); - let _ = crate::utils::nestable_block_on(start_observer_commands_handler( + let _ = hiro_system_kit::nestable_block_on(start_observer_commands_handler( config, Arc::new(RwLock::new(chainhook_store)), observer_commands_rx, diff --git a/components/chainhook-event-observer/src/utils/mod.rs b/components/chainhook-event-observer/src/utils/mod.rs index 6bec52262..e79f7858a 100644 --- a/components/chainhook-event-observer/src/utils/mod.rs +++ b/components/chainhook-event-observer/src/utils/mod.rs @@ -1,20 +1,6 @@ use chainhook_types::{ BitcoinBlockData, BlockIdentifier, StacksBlockData, StacksMicroblockData, StacksTransactionData, }; -use std::future::Future; -use tokio; - -pub fn nestable_block_on(future: F) -> F::Output { - let (handle, _rt) = match tokio::runtime::Handle::try_current() { - Ok(h) => (h, None), - Err(_) => { - let rt = tokio::runtime::Runtime::new().unwrap(); - (rt.handle().clone(), Some(rt)) - } - }; - let response = handle.block_on(async { future.await }); - response -} pub trait AbstractStacksBlock { fn get_identifier(&self) -> &BlockIdentifier; diff --git a/components/clarinet-cli/Cargo.toml b/components/clarinet-cli/Cargo.toml index 54306bd34..4a9177e2a 100644 --- a/components/clarinet-cli/Cargo.toml +++ b/components/clarinet-cli/Cargo.toml @@ -117,24 +117,16 @@ percent-encoding = "2.1.0" pin-project = "1.0.5" indexmap = { version = "1.6.1", features = ["serde"] } shell-escape = "0.1.5" -tiny-hderive = "0.3.0" reqwest = { version = "0.11", default-features = false, features = [ "blocking", "json", "rustls-tls", ] } -bollard = "0.11.0" crossterm = "0.22.1" -chrono = "0.4.20" base58 = "0.2.0" -tracing = "0.1" -tracing-subscriber = "0.3.3" -tracing-appender = "0.2.0" ctrlc = "3.1.9" strum = { version = "0.23.0", features = ["derive"] } bitcoin = "0.28.1" -bitcoincore-rpc = "0.14.0" -bitcoincore-rpc-json = "0.14.0" segment = { version = "0.1.2", optional = true } mac_address = { version = "1.1.2", optional = true } tower-lsp = { version = "0.14.0", optional = true } @@ -142,15 +134,17 @@ hex = "0.4.3" serde_yaml = "0.8.23" chainhook_event_observer = { package = "chainhook-event-observer", default-features = false, path = "../chainhook-event-observer" } chainhook_types = { package = "chainhook-types", path = "../chainhook-types-rs" } -stacks_rpc_client = { package = "stacks-rpc-client", path = "../stacks-rpc-client" } -clarinet_files = { package = "clarinet-files", path = "../clarinet-files", features = ["cli"] } -clarity_lsp = { package = "clarity-lsp", path = "../clarity-lsp", features = ["cli"] } -clarinet_deployments = { package = "clarinet-deployments", path = "../clarinet-deployments", features = ["cli"] } -clarinet_utils = { package = "clarinet-utils", path = "../clarinet-utils" } +clarinet_files = { package = "clarinet-files", path = "../clarinet-files", features = ["cli"] } +clarity_lsp = { package = "clarity-lsp", path = "../clarity-lsp", features = ["cli"] } +clarinet_deployments = { package = "clarinet-deployments", path = "../clarinet-deployments", features = ["cli"] } +hiro_system_kit = { package = "hiro-system-kit", path = "../hiro-system-kit" } +clarinet_utils = { package = "clarinet-utils", path = "../clarinet-utils" } +stacks_network = { package = "stacks-network", path = "../stacks-network" } num_cpus = "1.13.1" mio = "=0.8.2" similar = "2.1.0" crossbeam-channel = "0.5.6" +chrono = "0.4.20" [dependencies.tui] version = "0.18.0" diff --git a/components/clarinet-cli/src/bin.rs b/components/clarinet-cli/src/bin.rs index adcb7690f..72dfc49c8 100644 --- a/components/clarinet-cli/src/bin.rs +++ b/components/clarinet-cli/src/bin.rs @@ -13,11 +13,9 @@ mod chainhooks; mod deployments; mod frontend; mod generate; -mod integrate; +pub mod integrate; mod lsp; mod runner; -mod types; -mod utils; use frontend::cli; diff --git a/components/clarinet-cli/src/deployments/mod.rs b/components/clarinet-cli/src/deployments/mod.rs index fb5eb5eec..202e96bd9 100644 --- a/components/clarinet-cli/src/deployments/mod.rs +++ b/components/clarinet-cli/src/deployments/mod.rs @@ -1,53 +1,39 @@ -mod bitcoin_deployment; pub mod types; mod ui; -use bitcoincore_rpc::{Auth, Client}; -use clarity_repl::clarity::stacks_common::types::chainstate::StacksAddress; -use clarity_repl::clarity::util::secp256k1::{ - MessageSignature, Secp256k1PrivateKey, Secp256k1PublicKey, -}; -use clarity_repl::clarity::vm::types::StandardPrincipalData; -use clarity_repl::clarity::vm::{ClarityName, EvaluationResult, Value}; -use reqwest::Url; + + + + pub use ui::start_ui; -use crate::utils; +use hiro_system_kit; use clarinet_deployments::types::{ - DeploymentGenerationArtifacts, DeploymentSpecification, TransactionSpecification, + DeploymentGenerationArtifacts, DeploymentSpecification, }; -use clarinet_files::{AccountConfig, FileLocation, NetworkManifest, ProjectManifest}; -use clarinet_utils::get_bip39_seed_from_mnemonic; +use clarinet_files::{FileLocation, ProjectManifest}; + + + + + + -use clarity_repl::clarity::codec::StacksMessageCodec; -use clarity_repl::codec::{ - SinglesigHashMode, SinglesigSpendingCondition, StacksString, StacksTransaction, - StacksTransactionSigner, TransactionAnchorMode, TransactionAuth, TransactionContractCall, - TransactionPayload, TransactionPostConditionMode, TransactionPublicKeyEncoding, - TransactionSmartContract, TransactionSpendingCondition, TransactionVersion, -}; -use clarity_repl::clarity::address::{ - AddressHashMode, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, C32_ADDRESS_VERSION_TESTNET_SINGLESIG, -}; -use clarity_repl::clarity::vm::types::QualifiedContractIdentifier; use chainhook_types::StacksNetwork; -use clarity_repl::clarity::vm::ContractName; -use clarity_repl::repl::Session; -use clarity_repl::repl::SessionSettings; -use libsecp256k1::{PublicKey, SecretKey}; + + + use serde_yaml; -use stacks_rpc_client::StacksRpc; -use std::collections::{BTreeMap, HashSet, VecDeque}; + use std::fs::{self}; use std::path::PathBuf; -use std::sync::mpsc::{Receiver, Sender}; -use tiny_hderive::bip32::ExtendedPrivKey; + #[derive(Deserialize, Debug)] pub struct Balance { @@ -57,151 +43,6 @@ pub struct Balance { pub nonce_proof: String, } -fn get_keypair(account: &AccountConfig) -> (ExtendedPrivKey, Secp256k1PrivateKey, PublicKey) { - let bip39_seed = match get_bip39_seed_from_mnemonic(&account.mnemonic, "") { - Ok(bip39_seed) => bip39_seed, - Err(_) => panic!(), - }; - let ext = ExtendedPrivKey::derive(&bip39_seed[..], account.derivation.as_str()).unwrap(); - let wrapped_secret_key = Secp256k1PrivateKey::from_slice(&ext.secret()).unwrap(); - let secret_key = SecretKey::parse_slice(&ext.secret()).unwrap(); - let public_key = PublicKey::from_secret_key(&secret_key); - (ext, wrapped_secret_key, public_key) -} - -fn get_btc_keypair( - account: &AccountConfig, -) -> ( - bitcoincore_rpc::bitcoin::secp256k1::SecretKey, - bitcoincore_rpc::bitcoin::secp256k1::PublicKey, -) { - use bitcoincore_rpc::bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; - let bip39_seed = match get_bip39_seed_from_mnemonic(&account.mnemonic, "") { - Ok(bip39_seed) => bip39_seed, - Err(_) => panic!(), - }; - let secp = Secp256k1::new(); - let ext = ExtendedPrivKey::derive(&bip39_seed[..], account.derivation.as_str()).unwrap(); - let secret_key = SecretKey::from_slice(&ext.secret()).unwrap(); - let public_key = PublicKey::from_secret_key(&secp, &secret_key); - (secret_key, public_key) -} - -fn get_stacks_address(public_key: &PublicKey, network: &StacksNetwork) -> StacksAddress { - let wrapped_public_key = - Secp256k1PublicKey::from_slice(&public_key.serialize_compressed()).unwrap(); - - let signer_addr = StacksAddress::from_public_keys( - match network { - StacksNetwork::Mainnet => C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - _ => C32_ADDRESS_VERSION_TESTNET_SINGLESIG, - }, - &AddressHashMode::SerializeP2PKH, - 1, - &vec![wrapped_public_key], - ) - .unwrap(); - - signer_addr -} - -fn sign_transaction_payload( - account: &AccountConfig, - payload: TransactionPayload, - nonce: u64, - tx_fee: u64, - anchor_mode: TransactionAnchorMode, - network: &StacksNetwork, -) -> Result { - let (_, secret_key, public_key) = get_keypair(account); - let signer_addr = get_stacks_address(&public_key, network); - - let spending_condition = TransactionSpendingCondition::Singlesig(SinglesigSpendingCondition { - signer: signer_addr.bytes.clone(), - nonce: nonce, - tx_fee: tx_fee, - hash_mode: SinglesigHashMode::P2PKH, - key_encoding: TransactionPublicKeyEncoding::Compressed, - signature: MessageSignature::empty(), - }); - - let auth = TransactionAuth::Standard(spending_condition); - let unsigned_tx = StacksTransaction { - version: match network { - StacksNetwork::Mainnet => TransactionVersion::Mainnet, - _ => TransactionVersion::Testnet, - }, - chain_id: match network { - StacksNetwork::Mainnet => 0x00000001, - _ => 0x80000000, - }, - auth: auth, - anchor_mode: anchor_mode, - post_condition_mode: TransactionPostConditionMode::Allow, - post_conditions: vec![], - payload: payload, - }; - - let mut unsigned_tx_bytes = vec![]; - unsigned_tx - .consensus_serialize(&mut unsigned_tx_bytes) - .expect("FATAL: invalid transaction"); - - let mut tx_signer = StacksTransactionSigner::new(&unsigned_tx); - tx_signer.sign_origin(&secret_key).unwrap(); - let signed_tx = tx_signer.get_tx().unwrap(); - Ok(signed_tx) -} - -pub fn encode_contract_call( - contract_id: &QualifiedContractIdentifier, - function_name: ClarityName, - function_args: Vec, - account: &AccountConfig, - nonce: u64, - tx_fee: u64, - anchor_mode: TransactionAnchorMode, - network: &StacksNetwork, -) -> Result { - let payload = TransactionContractCall { - contract_name: contract_id.name.clone(), - address: StacksAddress::from(contract_id.issuer.clone()), - function_name: function_name.clone(), - function_args: function_args.clone(), - }; - sign_transaction_payload( - account, - TransactionPayload::ContractCall(payload), - nonce, - tx_fee, - anchor_mode, - network, - ) -} - -pub fn encode_contract_publish( - contract_name: &ContractName, - source: &str, - account: &AccountConfig, - nonce: u64, - tx_fee: u64, - anchor_mode: TransactionAnchorMode, - network: &StacksNetwork, -) -> Result { - let payload = TransactionSmartContract { - name: contract_name.clone(), - code_body: StacksString::from_str(source).unwrap(), - }; - sign_transaction_payload( - account, - TransactionPayload::SmartContract(payload), - nonce, - tx_fee, - anchor_mode, - network, - ) -} - pub fn get_absolute_deployment_path( manifest: &ProjectManifest, relative_deployment_path: &str, @@ -232,7 +73,7 @@ pub fn generate_default_deployment( _no_batch: bool, ) -> Result<(DeploymentSpecification, DeploymentGenerationArtifacts), String> { let future = clarinet_deployments::generate_default_deployment(manifest, network, false, None); - utils::nestable_block_on(future) + hiro_system_kit::nestable_block_on(future) } #[allow(dead_code)] @@ -259,484 +100,6 @@ pub fn read_deployment_or_generate_default( Ok((deployment, artifacts)) } -pub enum DeploymentEvent { - TransactionUpdate(TransactionTracker), - Interrupted(String), - ProtocolDeployed, -} - -pub enum DeploymentCommand { - Start, -} - -#[derive(Clone, Debug)] -pub enum TransactionStatus { - Queued, - Encoded(StacksTransaction, TransactionCheck), - Broadcasted(TransactionCheck), - Confirmed, - Error(String), -} - -#[derive(Clone, Debug)] -pub struct TransactionTracker { - pub index: usize, - pub name: String, - pub status: TransactionStatus, -} - -#[derive(Clone, Debug)] -pub enum TransactionCheck { - ContractCall(StandardPrincipalData, u64), - ContractPublish(StandardPrincipalData, ContractName), - // TODO(lgalabru): Handle Bitcoin checks - // BtcTransfer(), -} - -pub fn get_initial_transactions_trackers( - deployment: &DeploymentSpecification, -) -> Vec { - let mut index = 0; - let mut trackers = vec![]; - for batch_spec in deployment.plan.batches.iter() { - for transaction in batch_spec.transactions.iter() { - let tracker = match transaction { - TransactionSpecification::ContractCall(tx) => TransactionTracker { - index, - name: format!("Contract call {}::{}", tx.contract_id, tx.method), - status: TransactionStatus::Queued, - }, - TransactionSpecification::ContractPublish(tx) => TransactionTracker { - index, - name: format!( - "Contract publish {}.{}", - tx.expected_sender.to_address(), - tx.contract_name - ), - status: TransactionStatus::Queued, - }, - TransactionSpecification::RequirementPublish(tx) => { - if !deployment.network.either_devnet_or_testnet() { - panic!("Deployment specification malformed - requirements publish not supported on mainnet"); - } - TransactionTracker { - index, - name: format!( - "Contract publish {}.{}", - tx.remap_sender.to_address(), - tx.contract_id.name - ), - status: TransactionStatus::Queued, - } - } - TransactionSpecification::BtcTransfer(tx) => TransactionTracker { - index, - name: format!( - "BTC transfer {} send {} to {}", - tx.expected_sender, tx.sats_amount, tx.recipient - ), - status: TransactionStatus::Queued, - }, - TransactionSpecification::EmulatedContractPublish(_) - | TransactionSpecification::EmulatedContractCall(_) => continue, - }; - trackers.push(tracker); - index += 1; - } - } - trackers -} - -pub fn apply_on_chain_deployment( - manifest: &ProjectManifest, - deployment: DeploymentSpecification, - deployment_event_tx: Sender, - deployment_command_rx: Receiver, - fetch_initial_nonces: bool, -) { - let network_manifest = NetworkManifest::from_project_manifest_location( - &manifest.location, - &deployment.network.get_networks(), - ) - .expect("unable to load network manifest"); - let delay_between_checks: u64 = 10; - // Load deployers, deployment_fee_rate - // Check fee, balances and deployers - - let mut batches = VecDeque::new(); - let network = deployment.network.clone(); - let mut accounts_cached_nonces: BTreeMap = BTreeMap::new(); - let mut stx_accounts_lookup: BTreeMap = BTreeMap::new(); - let mut btc_accounts_lookup: BTreeMap = BTreeMap::new(); - - if !fetch_initial_nonces { - if network == StacksNetwork::Devnet { - for (_, account) in network_manifest.accounts.iter() { - accounts_cached_nonces.insert(account.stx_address.clone(), 0); - } - } - } - - for (_, account) in network_manifest.accounts.iter() { - stx_accounts_lookup.insert(account.stx_address.clone(), account); - btc_accounts_lookup.insert(account.btc_address.clone(), account); - } - - let stacks_node_url = deployment - .stacks_node - .expect("unable to get stacks node rcp address"); - let stacks_rpc = StacksRpc::new(&stacks_node_url); - - let bitcoin_node_url = deployment - .bitcoin_node - .expect("unable to get bitcoin node rcp address"); - - // Phase 1: we traverse the deployment plan and encode all the transactions, - // keeping the order. - // Using a session to encode + coerce/check (todo) contract calls arguments. - let mut session = Session::new(SessionSettings::default()); - let mut index = 0; - let mut contracts_ids_to_remap: HashSet<(String, String)> = HashSet::new(); - for batch_spec in deployment.plan.batches.iter() { - let mut batch = Vec::new(); - for transaction in batch_spec.transactions.iter() { - let tracker = match transaction { - TransactionSpecification::BtcTransfer(tx) => { - let url = Url::parse(&bitcoin_node_url).expect("Url malformatted"); - let auth = match url.password() { - Some(password) => { - Auth::UserPass(url.username().to_string(), password.to_string()) - } - None => Auth::None, - }; - let bitcoin_node_rpc_url = format!( - "{}://{}:{}", - url.scheme(), - url.host().expect("Host unknown"), - url.port_or_known_default().expect("Protocol unknown") - ); - let bitcoin_rpc = Client::new(&bitcoin_node_rpc_url, auth).unwrap(); - let account = btc_accounts_lookup.get(&tx.expected_sender).unwrap(); - let (secret_key, _public_key) = get_btc_keypair(account); - let _ = - bitcoin_deployment::send_transaction_spec(&bitcoin_rpc, tx, &secret_key); - continue; - } - TransactionSpecification::ContractCall(tx) => { - let issuer_address = tx.expected_sender.to_address(); - let nonce = match accounts_cached_nonces.get(&issuer_address) { - Some(cached_nonce) => cached_nonce.clone(), - None => stacks_rpc - .get_nonce(&issuer_address) - .expect("Unable to retrieve account"), - }; - let account = stx_accounts_lookup.get(&issuer_address).unwrap(); - - let function_args = tx - .parameters - .iter() - .map(|value| { - let execution = session.eval(value.to_string(), None, false).unwrap(); - match execution.result { - EvaluationResult::Snippet(result) => result.result, - _ => unreachable!("Contract result from snippet"), - } - }) - .collect::>(); - - let anchor_mode = match tx.anchor_block_only { - true => TransactionAnchorMode::OnChainOnly, - false => TransactionAnchorMode::Any, - }; - - let transaction = match encode_contract_call( - &tx.contract_id, - tx.method.clone(), - function_args, - *account, - nonce, - tx.cost, - anchor_mode, - &network, - ) { - Ok(res) => res, - Err(e) => { - let _ = deployment_event_tx.send(DeploymentEvent::Interrupted(e)); - return; - } - }; - - accounts_cached_nonces.insert(issuer_address.clone(), nonce + 1); - let name = format!( - "Call ({} {} {})", - tx.contract_id.to_string(), - tx.method, - tx.parameters.join(" ") - ); - let check = TransactionCheck::ContractCall(tx.expected_sender.clone(), nonce); - TransactionTracker { - index, - name: name.clone(), - status: TransactionStatus::Encoded(transaction, check), - } - } - TransactionSpecification::ContractPublish(tx) => { - // Retrieve nonce for issuer - let issuer_address = tx.expected_sender.to_address(); - let nonce = match accounts_cached_nonces.get(&issuer_address) { - Some(cached_nonce) => cached_nonce.clone(), - None => stacks_rpc - .get_nonce(&issuer_address) - .expect("Unable to retrieve account"), - }; - let account = stx_accounts_lookup.get(&issuer_address).unwrap(); - let source = if deployment.network.either_devnet_or_testnet() { - // Remapping - This is happening - let mut source = tx.source.clone(); - for (old_contract_id, new_contract_id) in contracts_ids_to_remap.iter() { - let mut matched_indices = source - .match_indices(old_contract_id) - .map(|(i, _)| i) - .collect::>(); - matched_indices.reverse(); - for index in matched_indices { - source.replace_range( - index..index + old_contract_id.len(), - new_contract_id, - ); - } - } - source - } else { - tx.source.clone() - }; - - let anchor_mode = match tx.anchor_block_only { - true => TransactionAnchorMode::OnChainOnly, - false => TransactionAnchorMode::Any, - }; - - let transaction = match encode_contract_publish( - &tx.contract_name, - &source, - *account, - nonce, - tx.cost, - anchor_mode, - &network, - ) { - Ok(res) => res, - Err(e) => { - let _ = deployment_event_tx.send(DeploymentEvent::Interrupted(e)); - return; - } - }; - - accounts_cached_nonces.insert(issuer_address.clone(), nonce + 1); - let name = format!( - "Publish {}.{}", - tx.expected_sender.to_string(), - tx.contract_name - ); - let check = TransactionCheck::ContractPublish( - tx.expected_sender.clone(), - tx.contract_name.clone(), - ); - TransactionTracker { - index, - name: name.clone(), - status: TransactionStatus::Encoded(transaction, check), - } - } - TransactionSpecification::RequirementPublish(tx) => { - if deployment.network.is_mainnet() { - panic!("Deployment specification malformed - requirements publish not supported on mainnet"); - } - let old_contract_id = tx.contract_id.to_string(); - let new_contract_id = QualifiedContractIdentifier::new( - tx.remap_sender.clone(), - tx.contract_id.name.clone(), - ) - .to_string(); - contracts_ids_to_remap.insert((old_contract_id, new_contract_id)); - - // Retrieve nonce for issuer - let issuer_address = tx.remap_sender.to_address(); - let nonce = match accounts_cached_nonces.get(&issuer_address) { - Some(cached_nonce) => cached_nonce.clone(), - None => stacks_rpc - .get_nonce(&issuer_address) - .expect("Unable to retrieve account"), - }; - let account = stx_accounts_lookup.get(&issuer_address).unwrap(); - - // Remapping principals - This is happening - let mut source = tx.source.clone(); - for (src_principal, dst_principal) in tx.remap_principals.iter() { - let src = src_principal.to_address(); - let dst = dst_principal.to_address(); - let mut matched_indices = source - .match_indices(&src) - .map(|(i, _)| i) - .collect::>(); - matched_indices.reverse(); - for index in matched_indices { - source.replace_range(index..index + src.len(), &dst); - } - } - - let anchor_mode = TransactionAnchorMode::OnChainOnly; - - let transaction = match encode_contract_publish( - &tx.contract_id.name, - &source, - *account, - nonce, - tx.cost, - anchor_mode, - &network, - ) { - Ok(res) => res, - Err(e) => { - let _ = deployment_event_tx.send(DeploymentEvent::Interrupted(e)); - return; - } - }; - - accounts_cached_nonces.insert(issuer_address.clone(), nonce + 1); - let name = format!( - "Publish {}.{}", - tx.remap_sender.to_string(), - tx.contract_id.name - ); - let check = TransactionCheck::ContractPublish( - tx.remap_sender.clone(), - tx.contract_id.name.clone(), - ); - TransactionTracker { - index, - name: name.clone(), - status: TransactionStatus::Encoded(transaction, check), - } - } - TransactionSpecification::EmulatedContractPublish(_) - | TransactionSpecification::EmulatedContractCall(_) => continue, - }; - - batch.push(tracker.clone()); - let _ = deployment_event_tx.send(DeploymentEvent::TransactionUpdate(tracker)); - index += 1; - } - - batches.push_back(batch); - } - - let _cmd = match deployment_command_rx.recv() { - Ok(cmd) => cmd, - Err(_) => { - let _ = deployment_event_tx.send(DeploymentEvent::Interrupted( - "deployment aborted - broken channel".to_string(), - )); - return; - } - }; - - // Phase 2: we submit all the transactions previously encoded, - // and wait for their inclusion in a block before moving to the next batch. - let mut current_block_height = 0; - for batch in batches.into_iter() { - let mut ongoing_batch = BTreeMap::new(); - for mut tracker in batch.into_iter() { - let (transaction, check) = match tracker.status { - TransactionStatus::Encoded(transaction, check) => (transaction, check), - _ => unreachable!(), - }; - let _ = match stacks_rpc.post_transaction(&transaction) { - Ok(res) => { - tracker.status = TransactionStatus::Broadcasted(check); - - let _ = deployment_event_tx - .send(DeploymentEvent::TransactionUpdate(tracker.clone())); - ongoing_batch.insert(res.txid, tracker); - } - Err(e) => { - let message = format!("{:?}", e); - tracker.status = TransactionStatus::Error(message.clone()); - - let _ = deployment_event_tx - .send(DeploymentEvent::TransactionUpdate(tracker.clone())); - let _ = deployment_event_tx.send(DeploymentEvent::Interrupted(message)); - return; - } - }; - } - - loop { - let new_block_height = match stacks_rpc.get_info() { - Ok(info) => info.burn_block_height, - _ => { - std::thread::sleep(std::time::Duration::from_secs(delay_between_checks.into())); - continue; - } - }; - - // If no block has been mined since `delay_between_checks`, - // avoid flooding the stacks-node with status update requests. - if new_block_height <= current_block_height { - std::thread::sleep(std::time::Duration::from_secs(delay_between_checks.into())); - continue; - } - - current_block_height = new_block_height; - - let mut keep_looping = false; - - for (_txid, tracker) in ongoing_batch.iter_mut() { - match &tracker.status { - TransactionStatus::Broadcasted(TransactionCheck::ContractPublish( - deployer, - contract_name, - )) => { - let deployer_address = deployer.to_address(); - let res = stacks_rpc.get_contract_source(&deployer_address, &contract_name); - if let Ok(_contract) = res { - tracker.status = TransactionStatus::Confirmed; - let _ = deployment_event_tx - .send(DeploymentEvent::TransactionUpdate(tracker.clone())); - } else { - keep_looping = true; - break; - } - } - TransactionStatus::Broadcasted(TransactionCheck::ContractCall( - tx_sender, - expected_nonce, - )) => { - let tx_sender_address = tx_sender.to_address(); - let res = stacks_rpc.get_nonce(&tx_sender_address); - if let Ok(current_nonce) = res { - if current_nonce > *expected_nonce { - tracker.status = TransactionStatus::Confirmed; - let _ = deployment_event_tx - .send(DeploymentEvent::TransactionUpdate(tracker.clone())); - } else { - keep_looping = true; - break; - } - } - } - _ => {} - } - } - if !keep_looping { - break; - } - } - } - - let _ = deployment_event_tx.send(DeploymentEvent::ProtocolDeployed); -} - pub fn check_deployments(manifest: &ProjectManifest) -> Result<(), String> { let project_root_location = manifest.location.get_project_root_location()?; let files = get_deployments_files(&project_root_location)?; diff --git a/components/clarinet-cli/src/deployments/ui/app.rs b/components/clarinet-cli/src/deployments/ui/app.rs index 71f2f0159..b63647efc 100644 --- a/components/clarinet-cli/src/deployments/ui/app.rs +++ b/components/clarinet-cli/src/deployments/ui/app.rs @@ -1,4 +1,4 @@ -use crate::deployments::TransactionTracker; +use clarinet_deployments::onchain::TransactionTracker; use tui::widgets::ListState; pub struct StatefulList { diff --git a/components/clarinet-cli/src/deployments/ui/mod.rs b/components/clarinet-cli/src/deployments/ui/mod.rs index c5670d65f..99b457573 100644 --- a/components/clarinet-cli/src/deployments/ui/mod.rs +++ b/components/clarinet-cli/src/deployments/ui/mod.rs @@ -3,8 +3,8 @@ mod app; #[allow(dead_code)] mod ui; -use super::{DeploymentEvent, TransactionTracker}; use app::App; +use clarinet_deployments::onchain::{DeploymentEvent, TransactionTracker}; use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, diff --git a/components/clarinet-cli/src/deployments/ui/ui.rs b/components/clarinet-cli/src/deployments/ui/ui.rs index 598152ba9..8b477d844 100644 --- a/components/clarinet-cli/src/deployments/ui/ui.rs +++ b/components/clarinet-cli/src/deployments/ui/ui.rs @@ -1,4 +1,4 @@ -use crate::deployments::TransactionStatus; +use clarinet_deployments::onchain::TransactionStatus; use super::App; use tui::{ diff --git a/components/clarinet-cli/src/frontend/cli.rs b/components/clarinet-cli/src/frontend/cli.rs index fb568c7da..82b0971f5 100644 --- a/components/clarinet-cli/src/frontend/cli.rs +++ b/components/clarinet-cli/src/frontend/cli.rs @@ -2,21 +2,24 @@ use crate::chainhooks::check_chainhooks; use crate::chainhooks::types::ChainhookSpecificationFile; use crate::deployments::types::DeploymentSynthesis; use crate::deployments::{ - self, apply_on_chain_deployment, check_deployments, generate_default_deployment, - get_absolute_deployment_path, get_default_deployment_path, get_initial_transactions_trackers, - load_deployment, write_deployment, DeploymentCommand, DeploymentEvent, + self, check_deployments, generate_default_deployment, get_absolute_deployment_path, + get_default_deployment_path, load_deployment, write_deployment, }; use crate::generate::{ self, changes::{Changes, TOMLEdition}, }; -use crate::integrate::{self, DevnetOrchestrator}; +use crate::integrate; use crate::lsp::run_lsp; use crate::runner::run_scripts; use crate::runner::DeploymentCache; use chainhook_event_observer::chainhooks::types::ChainhookSpecification; use chainhook_types::StacksNetwork; use chainhook_types::{BitcoinNetwork, Chain}; +use clarinet_deployments::onchain::{ + apply_on_chain_deployment, get_initial_transactions_trackers, DeploymentCommand, + DeploymentEvent, +}; use clarinet_deployments::setup_session_with_deployment; use clarinet_deployments::types::{DeploymentGenerationArtifacts, DeploymentSpecification}; use clarinet_files::{FileLocation, ProjectManifest, ProjectManifestFile, RequirementConfig}; @@ -29,6 +32,7 @@ use clarity_repl::clarity::ClarityVersion; use clarity_repl::repl::diagnostic::{output_code, output_diagnostic}; use clarity_repl::repl::{ClarityCodeSource, ClarityContract, ContractDeployer, DEFAULT_EPOCH}; use clarity_repl::{analysis, repl, Terminal}; +use stacks_network::{self, DevnetOrchestrator}; use std::collections::HashMap; use std::fs::{self, File}; use std::io::prelude::*; diff --git a/components/clarinet-cli/src/integrate/mod.rs b/components/clarinet-cli/src/integrate/mod.rs index 72dd6ad2a..92db0f757 100644 --- a/components/clarinet-cli/src/integrate/mod.rs +++ b/components/clarinet-cli/src/integrate/mod.rs @@ -1,22 +1,9 @@ -pub mod chains_coordinator; -mod orchestrator; -mod ui; +use std::sync::mpsc::{self, Sender}; -use std::sync::mpsc::{self, channel, Sender}; - -use chainhook_event_observer::observer::MempoolAdmissionData; -use chrono::prelude::*; -use tracing::{self, debug, error, info, warn}; -use tracing_appender; - -use crate::types::ChainsCoordinatorCommand; -use crate::utils; -use chainhook_types::{BitcoinChainEvent, StacksChainEvent}; -use chains_coordinator::start_chains_coordinator; +use crate::chainhooks::load_chainhooks; +use chainhook_types::{BitcoinNetwork, StacksNetwork}; use clarinet_deployments::types::DeploymentSpecification; -pub use orchestrator::DevnetOrchestrator; - -use self::chains_coordinator::DevnetEventObserverConfig; +use stacks_network::{do_run_devnet, DevnetOrchestrator, LogData, DevnetEvent, ChainsCoordinatorCommand}; pub fn run_devnet( devnet: DevnetOrchestrator, @@ -31,273 +18,25 @@ pub fn run_devnet( ), String, > { - match block_on(do_run_devnet(devnet, deployment, log_tx, display_dashboard)) { + let hooks = match load_chainhooks( + &devnet.manifest.location, + &(BitcoinNetwork::Regtest, StacksNetwork::Devnet), + ) { + Ok(hooks) => hooks, + Err(e) => { + println!("{}", e); + std::process::exit(1); + } + }; + + match hiro_system_kit::nestable_block_on(do_run_devnet( + devnet, + deployment, + hooks, + log_tx, + display_dashboard, + )) { Err(_e) => std::process::exit(1), Ok(res) => Ok(res), } } - -pub fn block_on(future: F) -> R -where - F: std::future::Future, -{ - let rt = utils::create_basic_runtime(); - rt.block_on(future) -} - -pub async fn do_run_devnet( - mut devnet: DevnetOrchestrator, - deployment: DeploymentSpecification, - log_tx: Option>, - display_dashboard: bool, -) -> Result< - ( - Option>, - Option>, - Option>, - ), - String, -> { - let (devnet_events_tx, devnet_events_rx) = channel(); - let (termination_success_tx, orchestrator_terminated_rx) = channel(); - - devnet.termination_success_tx = Some(termination_success_tx); - - let devnet_config = match devnet.network_config { - Some(ref network_config) => match &network_config.devnet { - Some(devnet_config) => Ok(devnet_config.clone()), - _ => Err("Unable to retrieve config"), - }, - _ => Err("Unable to retrieve config"), - }?; - - let file_appender = tracing_appender::rolling::never(&devnet_config.working_dir, "devnet.log"); - let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); - - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - .with_writer(non_blocking) - .init(); - - // The event observer should be able to send some events to the UI thread, - // and should be able to be terminated - let devnet_path = devnet_config.working_dir.clone(); - let config = - DevnetEventObserverConfig::new(devnet_config.clone(), devnet.manifest.clone(), deployment); - let chains_coordinator_tx = devnet_events_tx.clone(); - let (chains_coordinator_commands_tx, chains_coordinator_commands_rx) = channel(); - let (orchestrator_terminator_tx, terminator_rx) = channel(); - let (observer_command_tx, observer_command_rx) = channel(); - let moved_orchestrator_terminator_tx = orchestrator_terminator_tx.clone(); - let moved_chains_coordinator_commands_tx = chains_coordinator_commands_tx.clone(); - let moved_observer_command_tx = observer_command_tx.clone(); - - let chains_coordinator_handle = std::thread::spawn(move || { - let future = start_chains_coordinator( - config, - chains_coordinator_tx, - chains_coordinator_commands_rx, - moved_chains_coordinator_commands_tx, - moved_orchestrator_terminator_tx, - observer_command_tx, - observer_command_rx, - ); - let rt = utils::create_basic_runtime(); - rt.block_on(future) - }); - - // Let's start the orchestration - - // The devnet orchestrator should be able to send some events to the UI thread, - // and should be able to be restarted/terminated - let orchestrator_event_tx = devnet_events_tx.clone(); - let orchestrator_handle = std::thread::spawn(move || { - let future = devnet.start(orchestrator_event_tx, terminator_rx); - let rt = utils::create_basic_runtime(); - rt.block_on(future) - }); - - if display_dashboard { - info!("Starting Devnet..."); - let moved_chains_coordinator_commands_tx = chains_coordinator_commands_tx.clone(); - let _ = ui::start_ui( - devnet_events_tx, - devnet_events_rx, - moved_chains_coordinator_commands_tx, - moved_observer_command_tx, - orchestrator_terminated_rx, - &devnet_path, - devnet_config.enable_subnet_node, - ); - } else { - let moved_events_observer_commands_tx = chains_coordinator_commands_tx.clone(); - ctrlc::set_handler(move || { - moved_events_observer_commands_tx - .send(ChainsCoordinatorCommand::Terminate) - .expect("Unable to terminate devnet"); - }) - .expect("Error setting Ctrl-C handler"); - - if log_tx.is_none() { - println!("Starting Devnet..."); - - loop { - match devnet_events_rx.recv() { - Ok(DevnetEvent::Log(log)) => { - if let Some(ref log_tx) = log_tx { - let _ = log_tx.send(log.clone()); - } else { - println!("{}", log.message); - match log.level { - LogLevel::Debug => debug!("{}", log.message), - LogLevel::Info | LogLevel::Success => info!("{}", log.message), - LogLevel::Warning => warn!("{}", log.message), - LogLevel::Error => error!("{}", log.message), - } - } - } - _ => {} - } - } - } else { - return Ok(( - Some(devnet_events_rx), - Some(orchestrator_terminator_tx), - Some(chains_coordinator_commands_tx), - )); - } - } - - if let Err(e) = chains_coordinator_handle.join() { - if let Ok(message) = e.downcast::() { - return Err(*message); - } - } - - if let Err(e) = orchestrator_handle.join() { - if let Ok(message) = e.downcast::() { - return Err(*message); - } - } - - Ok((None, None, Some(chains_coordinator_commands_tx))) -} - -#[allow(dead_code)] -#[derive(Debug)] -pub enum DevnetEvent { - Log(LogData), - KeyEvent(crossterm::event::KeyEvent), - Tick, - ServiceStatus(ServiceStatusData), - ProtocolDeployingProgress(ProtocolDeployingData), - ProtocolDeployed, - StacksChainEvent(StacksChainEvent), - BitcoinChainEvent(BitcoinChainEvent), - MempoolAdmission(MempoolAdmissionData), - FatalError(String), - // Restart, - // Terminate, - // Microblock(MicroblockData), -} - -#[allow(dead_code)] -impl DevnetEvent { - pub fn error(message: String) -> DevnetEvent { - DevnetEvent::Log(Self::log_error(message)) - } - - #[allow(dead_code)] - pub fn warning(message: String) -> DevnetEvent { - DevnetEvent::Log(Self::log_warning(message)) - } - - pub fn info(message: String) -> DevnetEvent { - DevnetEvent::Log(Self::log_info(message)) - } - - pub fn success(message: String) -> DevnetEvent { - DevnetEvent::Log(Self::log_success(message)) - } - - pub fn debug(message: String) -> DevnetEvent { - DevnetEvent::Log(Self::log_debug(message)) - } - - pub fn log_error(message: String) -> LogData { - LogData::new(LogLevel::Error, message) - } - - pub fn log_warning(message: String) -> LogData { - LogData::new(LogLevel::Warning, message) - } - - pub fn log_info(message: String) -> LogData { - LogData::new(LogLevel::Info, message) - } - - pub fn log_success(message: String) -> LogData { - LogData::new(LogLevel::Success, message) - } - - pub fn log_debug(message: String) -> LogData { - LogData::new(LogLevel::Debug, message) - } -} - -#[derive(Clone, Debug)] -pub enum LogLevel { - Error, - Warning, - Info, - Success, - Debug, -} - -#[derive(Clone, Debug)] -pub struct LogData { - pub occurred_at: String, - pub message: String, - pub level: LogLevel, -} - -impl LogData { - pub fn new(level: LogLevel, message: String) -> LogData { - let now: DateTime = Utc::now(); - LogData { - level, - message, - occurred_at: now.format("%b %e %T%.6f").to_string(), - } - } -} - -#[derive(Clone, Debug)] -pub enum Status { - Red, - Yellow, - Green, -} - -#[derive(Clone, Debug)] -pub struct ServiceStatusData { - pub order: usize, - pub status: Status, - pub name: String, - pub comment: String, -} - -#[derive(Clone, Debug)] -pub struct ProtocolDeployingData { - pub new_contracts_deployed: Vec, -} - -#[derive(Clone, Debug)] -pub struct ProtocolDeployedData { - pub contracts_deployed: Vec, -} - -// pub struct MicroblockData { -// pub seq: u32, -// pub transactions: Vec -// } diff --git a/components/clarinet-cli/src/lib.rs b/components/clarinet-cli/src/lib.rs index a83c9451a..3fa4a4f9f 100644 --- a/components/clarinet-cli/src/lib.rs +++ b/components/clarinet-cli/src/lib.rs @@ -17,8 +17,6 @@ pub mod chainhooks; pub mod deployments; pub mod generate; pub mod integrate; -pub mod types; -pub mod utils; #[cfg(feature = "cli")] pub mod frontend; diff --git a/components/clarinet-cli/src/lsp/mod.rs b/components/clarinet-cli/src/lsp/mod.rs index 57a4e2db9..90bc68f84 100644 --- a/components/clarinet-cli/src/lsp/mod.rs +++ b/components/clarinet-cli/src/lsp/mod.rs @@ -25,7 +25,7 @@ pub fn block_on(future: F) -> R where F: std::future::Future, { - let rt = crate::utils::create_basic_runtime(); + let rt = hiro_system_kit::create_basic_runtime(); rt.block_on(future) } @@ -37,7 +37,7 @@ async fn do_run_lsp() -> Result<(), String> { let (request_tx, request_rx) = unbounded(); let (response_tx, response_rx) = mpsc::channel(); std::thread::spawn(move || { - crate::utils::nestable_block_on(native_bridge::start_language_server( + hiro_system_kit::nestable_block_on(native_bridge::start_language_server( notification_rx, request_rx, response_tx, @@ -178,7 +178,7 @@ fn test_opening_counter_contract_should_return_fresh_analysis() { let (_request_tx, request_rx) = unbounded(); let (response_tx, response_rx) = channel(); std::thread::spawn(move || { - crate::utils::nestable_block_on(native_bridge::start_language_server( + hiro_system_kit::nestable_block_on(native_bridge::start_language_server( notification_rx, request_rx, response_tx, @@ -219,7 +219,7 @@ fn test_opening_counter_manifest_should_return_fresh_analysis() { let (_request_tx, request_rx) = unbounded(); let (response_tx, response_rx) = channel(); std::thread::spawn(move || { - crate::utils::nestable_block_on(native_bridge::start_language_server( + hiro_system_kit::nestable_block_on(native_bridge::start_language_server( notification_rx, request_rx, response_tx, @@ -259,7 +259,7 @@ fn test_opening_simple_nft_manifest_should_return_fresh_analysis() { let (_request_tx, request_rx) = unbounded(); let (response_tx, response_rx) = channel(); std::thread::spawn(move || { - crate::utils::nestable_block_on(native_bridge::start_language_server( + hiro_system_kit::nestable_block_on(native_bridge::start_language_server( notification_rx, request_rx, response_tx, diff --git a/components/clarinet-cli/src/runner/deno.rs b/components/clarinet-cli/src/runner/deno.rs index 3a79c7c98..c63cdf82a 100644 --- a/components/clarinet-cli/src/runner/deno.rs +++ b/components/clarinet-cli/src/runner/deno.rs @@ -1,6 +1,6 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -use crate::utils::nestable_block_on; +use hiro_system_kit::nestable_block_on; use super::vendor::deno_cli::args::{ConfigFlag, TypeCheckMode}; use super::vendor::deno_cli::args::{DenoSubcommand, Flags, TestFlags}; diff --git a/components/clarinet-cli/src/runner/mod.rs b/components/clarinet-cli/src/runner/mod.rs index eb5b442c2..249ba6e6b 100644 --- a/components/clarinet-cli/src/runner/mod.rs +++ b/components/clarinet-cli/src/runner/mod.rs @@ -142,7 +142,7 @@ pub fn block_on(future: F) -> R where F: std::future::Future, { - let rt = crate::utils::create_basic_runtime(); + let rt = hiro_system_kit::create_basic_runtime(); rt.block_on(future) } pub struct SessionArtifacts { diff --git a/components/clarinet-cli/src/types/mod.rs b/components/clarinet-cli/src/types/mod.rs deleted file mode 100644 index 03c266737..000000000 --- a/components/clarinet-cli/src/types/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[allow(dead_code)] -#[derive(Debug)] -pub enum ChainsCoordinatorCommand { - Terminate, - ProtocolDeployed, -} diff --git a/components/clarinet-deployments/Cargo.toml b/components/clarinet-deployments/Cargo.toml index 0d98b7911..ae5653ec1 100644 --- a/components/clarinet-deployments/Cargo.toml +++ b/components/clarinet-deployments/Cargo.toml @@ -7,21 +7,31 @@ edition = "2021" serde = "1" serde_json = "1" serde_derive = "1" +serde_yaml = "0.8.23" + clarity_repl = { package = "clarity-repl", path = "../clarity-repl", default-features = false, optional = true } clarinet_files = { package = "clarinet-files", path = "../clarinet-files", default-features = false } chainhook_types = { package = "chainhook-types", path = "../chainhook-types-rs" } -serde_yaml = "0.8.23" +stacks_rpc_client = { package = "stacks-rpc-client", path = "../stacks-rpc-client", optional = true } # CLI -reqwest = { version = "0.11", default-features = false, features = [ +reqwest = { version = "0.11", default-features = false, optional = true, features = [ "json", "rustls-tls", ] } +bitcoin = { version = "0.28.1", optional = true } +bitcoincore-rpc = { version = "0.14.0", optional = true } +bitcoincore-rpc-json = { version = "0.14.0", optional = true } +base58 = { version = "0.2.0", optional = true } +tiny-hderive = { version = "0.3.0", optional = true } +libsecp256k1 = { version = "0.7.0", optional = true } +clarinet_utils = { package = "clarinet-utils", path = "../clarinet-utils", optional = true } [features] default = ["cli"] -cli = ["clarity_repl/cli", "clarinet_files/cli"] +cli = ["clarity_repl/cli", "clarinet_files/cli", "onchain"] wasm = ["clarity_repl/wasm", "clarinet_files/wasm"] +onchain = ["stacks_rpc_client", "reqwest", "bitcoin", "bitcoincore-rpc", "bitcoincore-rpc-json", "base58", "tiny-hderive", "libsecp256k1", "clarinet_utils"] [lib] name = "clarinet_deployments" diff --git a/components/clarinet-deployments/src/lib.rs b/components/clarinet-deployments/src/lib.rs index 2e2c9e055..261e238ab 100644 --- a/components/clarinet-deployments/src/lib.rs +++ b/components/clarinet-deployments/src/lib.rs @@ -10,6 +10,8 @@ extern crate serde_derive; pub mod requirements; pub mod types; +#[cfg(feature = "onchain")] +pub mod onchain; use self::types::{ DeploymentSpecification, EmulatedContractPublishSpecification, GenesisSpecification, diff --git a/components/clarinet-cli/src/deployments/bitcoin_deployment.rs b/components/clarinet-deployments/src/onchain/bitcoin_deployment.rs similarity index 98% rename from components/clarinet-cli/src/deployments/bitcoin_deployment.rs rename to components/clarinet-deployments/src/onchain/bitcoin_deployment.rs index 354fbfb72..351fbbd91 100644 --- a/components/clarinet-cli/src/deployments/bitcoin_deployment.rs +++ b/components/clarinet-deployments/src/onchain/bitcoin_deployment.rs @@ -12,7 +12,7 @@ use bitcoincore_rpc::RpcApi; use bitcoincore_rpc_json::ListUnspentResultEntry; use clarity_repl::clarity::util::hash::bytes_to_hex; -use clarinet_deployments::types::BtcTransferSpecification; +use crate::types::BtcTransferSpecification; pub fn build_transaction_spec( tx_spec: &BtcTransferSpecification, diff --git a/components/clarinet-deployments/src/onchain/mod.rs b/components/clarinet-deployments/src/onchain/mod.rs new file mode 100644 index 000000000..7120cf5aa --- /dev/null +++ b/components/clarinet-deployments/src/onchain/mod.rs @@ -0,0 +1,657 @@ +use bitcoincore_rpc::{Auth, Client}; +use chainhook_types::StacksNetwork; +use clarinet_files::{AccountConfig, NetworkManifest, ProjectManifest}; +use clarinet_utils::get_bip39_seed_from_mnemonic; +use clarity_repl::clarity::codec::StacksMessageCodec; +use clarity_repl::clarity::stacks_common::types::chainstate::StacksAddress; +use clarity_repl::clarity::util::secp256k1::{ + MessageSignature, Secp256k1PrivateKey, Secp256k1PublicKey, +}; +use clarity_repl::clarity::vm::types::{QualifiedContractIdentifier, StandardPrincipalData}; +use clarity_repl::clarity::vm::{ClarityName, Value}; +use clarity_repl::clarity::{ContractName, EvaluationResult}; +use clarity_repl::codec::{ + SinglesigHashMode, SinglesigSpendingCondition, StacksString, StacksTransactionSigner, + TransactionAuth, TransactionContractCall, TransactionPayload, TransactionPostConditionMode, + TransactionPublicKeyEncoding, TransactionSmartContract, TransactionSpendingCondition, + TransactionVersion, +}; +use clarity_repl::codec::{StacksTransaction, TransactionAnchorMode}; +use clarity_repl::repl::{Session, SessionSettings}; +use reqwest::Url; +use stacks_rpc_client::StacksRpc; +use std::collections::{BTreeMap, HashSet, VecDeque}; +use std::sync::mpsc::{Receiver, Sender}; +use tiny_hderive::bip32::ExtendedPrivKey; + +use clarity_repl::clarity::address::{ + AddressHashMode, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, C32_ADDRESS_VERSION_TESTNET_SINGLESIG, +}; +use libsecp256k1::{PublicKey, SecretKey}; + +mod bitcoin_deployment; + +use crate::types::{DeploymentSpecification, TransactionSpecification}; + +fn get_btc_keypair( + account: &AccountConfig, +) -> ( + bitcoincore_rpc::bitcoin::secp256k1::SecretKey, + bitcoincore_rpc::bitcoin::secp256k1::PublicKey, +) { + use bitcoincore_rpc::bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + let bip39_seed = match get_bip39_seed_from_mnemonic(&account.mnemonic, "") { + Ok(bip39_seed) => bip39_seed, + Err(_) => panic!(), + }; + let secp = Secp256k1::new(); + let ext = ExtendedPrivKey::derive(&bip39_seed[..], account.derivation.as_str()).unwrap(); + let secret_key = SecretKey::from_slice(&ext.secret()).unwrap(); + let public_key = PublicKey::from_secret_key(&secp, &secret_key); + (secret_key, public_key) +} + +fn get_keypair(account: &AccountConfig) -> (ExtendedPrivKey, Secp256k1PrivateKey, PublicKey) { + let bip39_seed = match get_bip39_seed_from_mnemonic(&account.mnemonic, "") { + Ok(bip39_seed) => bip39_seed, + Err(_) => panic!(), + }; + let ext = ExtendedPrivKey::derive(&bip39_seed[..], account.derivation.as_str()).unwrap(); + let wrapped_secret_key = Secp256k1PrivateKey::from_slice(&ext.secret()).unwrap(); + let secret_key = SecretKey::parse_slice(&ext.secret()).unwrap(); + let public_key = PublicKey::from_secret_key(&secret_key); + (ext, wrapped_secret_key, public_key) +} + +fn get_stacks_address(public_key: &PublicKey, network: &StacksNetwork) -> StacksAddress { + let wrapped_public_key = + Secp256k1PublicKey::from_slice(&public_key.serialize_compressed()).unwrap(); + + let signer_addr = StacksAddress::from_public_keys( + match network { + StacksNetwork::Mainnet => C32_ADDRESS_VERSION_MAINNET_SINGLESIG, + _ => C32_ADDRESS_VERSION_TESTNET_SINGLESIG, + }, + &AddressHashMode::SerializeP2PKH, + 1, + &vec![wrapped_public_key], + ) + .unwrap(); + + signer_addr +} + +fn sign_transaction_payload( + account: &AccountConfig, + payload: TransactionPayload, + nonce: u64, + tx_fee: u64, + anchor_mode: TransactionAnchorMode, + network: &StacksNetwork, +) -> Result { + let (_, secret_key, public_key) = get_keypair(account); + let signer_addr = get_stacks_address(&public_key, network); + + let spending_condition = TransactionSpendingCondition::Singlesig(SinglesigSpendingCondition { + signer: signer_addr.bytes.clone(), + nonce: nonce, + tx_fee: tx_fee, + hash_mode: SinglesigHashMode::P2PKH, + key_encoding: TransactionPublicKeyEncoding::Compressed, + signature: MessageSignature::empty(), + }); + + let auth = TransactionAuth::Standard(spending_condition); + let unsigned_tx = StacksTransaction { + version: match network { + StacksNetwork::Mainnet => TransactionVersion::Mainnet, + _ => TransactionVersion::Testnet, + }, + chain_id: match network { + StacksNetwork::Mainnet => 0x00000001, + _ => 0x80000000, + }, + auth: auth, + anchor_mode: anchor_mode, + post_condition_mode: TransactionPostConditionMode::Allow, + post_conditions: vec![], + payload: payload, + }; + + let mut unsigned_tx_bytes = vec![]; + unsigned_tx + .consensus_serialize(&mut unsigned_tx_bytes) + .expect("FATAL: invalid transaction"); + + let mut tx_signer = StacksTransactionSigner::new(&unsigned_tx); + tx_signer.sign_origin(&secret_key).unwrap(); + let signed_tx = tx_signer.get_tx().unwrap(); + Ok(signed_tx) +} + +pub fn encode_contract_call( + contract_id: &QualifiedContractIdentifier, + function_name: ClarityName, + function_args: Vec, + account: &AccountConfig, + nonce: u64, + tx_fee: u64, + anchor_mode: TransactionAnchorMode, + network: &StacksNetwork, +) -> Result { + let payload = TransactionContractCall { + contract_name: contract_id.name.clone(), + address: StacksAddress::from(contract_id.issuer.clone()), + function_name: function_name.clone(), + function_args: function_args.clone(), + }; + sign_transaction_payload( + account, + TransactionPayload::ContractCall(payload), + nonce, + tx_fee, + anchor_mode, + network, + ) +} + +pub fn encode_contract_publish( + contract_name: &ContractName, + source: &str, + account: &AccountConfig, + nonce: u64, + tx_fee: u64, + anchor_mode: TransactionAnchorMode, + network: &StacksNetwork, +) -> Result { + let payload = TransactionSmartContract { + name: contract_name.clone(), + code_body: StacksString::from_str(source).unwrap(), + }; + sign_transaction_payload( + account, + TransactionPayload::SmartContract(payload), + nonce, + tx_fee, + anchor_mode, + network, + ) +} + +#[derive(Clone, Debug)] +pub enum TransactionStatus { + Queued, + Encoded(StacksTransaction, TransactionCheck), + Broadcasted(TransactionCheck), + Confirmed, + Error(String), +} + +#[derive(Clone, Debug)] +pub struct TransactionTracker { + pub index: usize, + pub name: String, + pub status: TransactionStatus, +} + +#[derive(Clone, Debug)] +pub enum TransactionCheck { + ContractCall(StandardPrincipalData, u64), + ContractPublish(StandardPrincipalData, ContractName), + // TODO(lgalabru): Handle Bitcoin checks + // BtcTransfer(), +} + +pub enum DeploymentEvent { + TransactionUpdate(TransactionTracker), + Interrupted(String), + ProtocolDeployed, +} + +pub enum DeploymentCommand { + Start, +} + +pub fn apply_on_chain_deployment( + manifest: &ProjectManifest, + deployment: DeploymentSpecification, + deployment_event_tx: Sender, + deployment_command_rx: Receiver, + fetch_initial_nonces: bool, +) { + let network_manifest = NetworkManifest::from_project_manifest_location( + &manifest.location, + &deployment.network.get_networks(), + ) + .expect("unable to load network manifest"); + let delay_between_checks: u64 = 10; + // Load deployers, deployment_fee_rate + // Check fee, balances and deployers + + let mut batches = VecDeque::new(); + let network = deployment.network.clone(); + let mut accounts_cached_nonces: BTreeMap = BTreeMap::new(); + let mut stx_accounts_lookup: BTreeMap = BTreeMap::new(); + let mut btc_accounts_lookup: BTreeMap = BTreeMap::new(); + + if !fetch_initial_nonces { + if network == StacksNetwork::Devnet { + for (_, account) in network_manifest.accounts.iter() { + accounts_cached_nonces.insert(account.stx_address.clone(), 0); + } + } + } + + for (_, account) in network_manifest.accounts.iter() { + stx_accounts_lookup.insert(account.stx_address.clone(), account); + btc_accounts_lookup.insert(account.btc_address.clone(), account); + } + + let stacks_node_url = deployment + .stacks_node + .expect("unable to get stacks node rcp address"); + let stacks_rpc = StacksRpc::new(&stacks_node_url); + + let bitcoin_node_url = deployment + .bitcoin_node + .expect("unable to get bitcoin node rcp address"); + + // Phase 1: we traverse the deployment plan and encode all the transactions, + // keeping the order. + // Using a session to encode + coerce/check (todo) contract calls arguments. + let mut session = Session::new(SessionSettings::default()); + let mut index = 0; + let mut contracts_ids_to_remap: HashSet<(String, String)> = HashSet::new(); + for batch_spec in deployment.plan.batches.iter() { + let mut batch = Vec::new(); + for transaction in batch_spec.transactions.iter() { + let tracker = match transaction { + TransactionSpecification::BtcTransfer(tx) => { + let url = Url::parse(&bitcoin_node_url).expect("Url malformatted"); + let auth = match url.password() { + Some(password) => { + Auth::UserPass(url.username().to_string(), password.to_string()) + } + None => Auth::None, + }; + let bitcoin_node_rpc_url = format!( + "{}://{}:{}", + url.scheme(), + url.host().expect("Host unknown"), + url.port_or_known_default().expect("Protocol unknown") + ); + let bitcoin_rpc = Client::new(&bitcoin_node_rpc_url, auth).unwrap(); + let account = btc_accounts_lookup.get(&tx.expected_sender).unwrap(); + let (secret_key, _public_key) = get_btc_keypair(account); + let _ = + bitcoin_deployment::send_transaction_spec(&bitcoin_rpc, tx, &secret_key); + continue; + } + TransactionSpecification::ContractCall(tx) => { + let issuer_address = tx.expected_sender.to_address(); + let nonce = match accounts_cached_nonces.get(&issuer_address) { + Some(cached_nonce) => cached_nonce.clone(), + None => stacks_rpc + .get_nonce(&issuer_address) + .expect("Unable to retrieve account"), + }; + let account = stx_accounts_lookup.get(&issuer_address).unwrap(); + + let function_args = tx + .parameters + .iter() + .map(|value| { + let execution = session.eval(value.to_string(), None, false).unwrap(); + match execution.result { + EvaluationResult::Snippet(result) => result.result, + _ => unreachable!("Contract result from snippet"), + } + }) + .collect::>(); + + let anchor_mode = match tx.anchor_block_only { + true => TransactionAnchorMode::OnChainOnly, + false => TransactionAnchorMode::Any, + }; + + let transaction = match encode_contract_call( + &tx.contract_id, + tx.method.clone(), + function_args, + *account, + nonce, + tx.cost, + anchor_mode, + &network, + ) { + Ok(res) => res, + Err(e) => { + let _ = deployment_event_tx.send(DeploymentEvent::Interrupted(e)); + return; + } + }; + + accounts_cached_nonces.insert(issuer_address.clone(), nonce + 1); + let name = format!( + "Call ({} {} {})", + tx.contract_id.to_string(), + tx.method, + tx.parameters.join(" ") + ); + let check = TransactionCheck::ContractCall(tx.expected_sender.clone(), nonce); + TransactionTracker { + index, + name: name.clone(), + status: TransactionStatus::Encoded(transaction, check), + } + } + TransactionSpecification::ContractPublish(tx) => { + // Retrieve nonce for issuer + let issuer_address = tx.expected_sender.to_address(); + let nonce = match accounts_cached_nonces.get(&issuer_address) { + Some(cached_nonce) => cached_nonce.clone(), + None => stacks_rpc + .get_nonce(&issuer_address) + .expect("Unable to retrieve account"), + }; + let account = stx_accounts_lookup.get(&issuer_address).unwrap(); + let source = if deployment.network.either_devnet_or_testnet() { + // Remapping - This is happening + let mut source = tx.source.clone(); + for (old_contract_id, new_contract_id) in contracts_ids_to_remap.iter() { + let mut matched_indices = source + .match_indices(old_contract_id) + .map(|(i, _)| i) + .collect::>(); + matched_indices.reverse(); + for index in matched_indices { + source.replace_range( + index..index + old_contract_id.len(), + new_contract_id, + ); + } + } + source + } else { + tx.source.clone() + }; + + let anchor_mode = match tx.anchor_block_only { + true => TransactionAnchorMode::OnChainOnly, + false => TransactionAnchorMode::Any, + }; + + let transaction = match encode_contract_publish( + &tx.contract_name, + &source, + *account, + nonce, + tx.cost, + anchor_mode, + &network, + ) { + Ok(res) => res, + Err(e) => { + let _ = deployment_event_tx.send(DeploymentEvent::Interrupted(e)); + return; + } + }; + + accounts_cached_nonces.insert(issuer_address.clone(), nonce + 1); + let name = format!( + "Publish {}.{}", + tx.expected_sender.to_string(), + tx.contract_name + ); + let check = TransactionCheck::ContractPublish( + tx.expected_sender.clone(), + tx.contract_name.clone(), + ); + TransactionTracker { + index, + name: name.clone(), + status: TransactionStatus::Encoded(transaction, check), + } + } + TransactionSpecification::RequirementPublish(tx) => { + if deployment.network.is_mainnet() { + panic!("Deployment specification malformed - requirements publish not supported on mainnet"); + } + let old_contract_id = tx.contract_id.to_string(); + let new_contract_id = QualifiedContractIdentifier::new( + tx.remap_sender.clone(), + tx.contract_id.name.clone(), + ) + .to_string(); + contracts_ids_to_remap.insert((old_contract_id, new_contract_id)); + + // Retrieve nonce for issuer + let issuer_address = tx.remap_sender.to_address(); + let nonce = match accounts_cached_nonces.get(&issuer_address) { + Some(cached_nonce) => cached_nonce.clone(), + None => stacks_rpc + .get_nonce(&issuer_address) + .expect("Unable to retrieve account"), + }; + let account = stx_accounts_lookup.get(&issuer_address).unwrap(); + + // Remapping principals - This is happening + let mut source = tx.source.clone(); + for (src_principal, dst_principal) in tx.remap_principals.iter() { + let src = src_principal.to_address(); + let dst = dst_principal.to_address(); + let mut matched_indices = source + .match_indices(&src) + .map(|(i, _)| i) + .collect::>(); + matched_indices.reverse(); + for index in matched_indices { + source.replace_range(index..index + src.len(), &dst); + } + } + + let anchor_mode = TransactionAnchorMode::OnChainOnly; + + let transaction = match encode_contract_publish( + &tx.contract_id.name, + &source, + *account, + nonce, + tx.cost, + anchor_mode, + &network, + ) { + Ok(res) => res, + Err(e) => { + let _ = deployment_event_tx.send(DeploymentEvent::Interrupted(e)); + return; + } + }; + + accounts_cached_nonces.insert(issuer_address.clone(), nonce + 1); + let name = format!( + "Publish {}.{}", + tx.remap_sender.to_string(), + tx.contract_id.name + ); + let check = TransactionCheck::ContractPublish( + tx.remap_sender.clone(), + tx.contract_id.name.clone(), + ); + TransactionTracker { + index, + name: name.clone(), + status: TransactionStatus::Encoded(transaction, check), + } + } + TransactionSpecification::EmulatedContractPublish(_) + | TransactionSpecification::EmulatedContractCall(_) => continue, + }; + + batch.push(tracker.clone()); + let _ = deployment_event_tx.send(DeploymentEvent::TransactionUpdate(tracker)); + index += 1; + } + + batches.push_back(batch); + } + + let _cmd = match deployment_command_rx.recv() { + Ok(cmd) => cmd, + Err(_) => { + let _ = deployment_event_tx.send(DeploymentEvent::Interrupted( + "deployment aborted - broken channel".to_string(), + )); + return; + } + }; + + // Phase 2: we submit all the transactions previously encoded, + // and wait for their inclusion in a block before moving to the next batch. + let mut current_block_height = 0; + for batch in batches.into_iter() { + let mut ongoing_batch = BTreeMap::new(); + for mut tracker in batch.into_iter() { + let (transaction, check) = match tracker.status { + TransactionStatus::Encoded(transaction, check) => (transaction, check), + _ => unreachable!(), + }; + let _ = match stacks_rpc.post_transaction(&transaction) { + Ok(res) => { + tracker.status = TransactionStatus::Broadcasted(check); + + let _ = deployment_event_tx + .send(DeploymentEvent::TransactionUpdate(tracker.clone())); + ongoing_batch.insert(res.txid, tracker); + } + Err(e) => { + let message = format!("{:?}", e); + tracker.status = TransactionStatus::Error(message.clone()); + + let _ = deployment_event_tx + .send(DeploymentEvent::TransactionUpdate(tracker.clone())); + let _ = deployment_event_tx.send(DeploymentEvent::Interrupted(message)); + return; + } + }; + } + + loop { + let new_block_height = match stacks_rpc.get_info() { + Ok(info) => info.burn_block_height, + _ => { + std::thread::sleep(std::time::Duration::from_secs(delay_between_checks.into())); + continue; + } + }; + + // If no block has been mined since `delay_between_checks`, + // avoid flooding the stacks-node with status update requests. + if new_block_height <= current_block_height { + std::thread::sleep(std::time::Duration::from_secs(delay_between_checks.into())); + continue; + } + + current_block_height = new_block_height; + + let mut keep_looping = false; + + for (_txid, tracker) in ongoing_batch.iter_mut() { + match &tracker.status { + TransactionStatus::Broadcasted(TransactionCheck::ContractPublish( + deployer, + contract_name, + )) => { + let deployer_address = deployer.to_address(); + let res = stacks_rpc.get_contract_source(&deployer_address, &contract_name); + if let Ok(_contract) = res { + tracker.status = TransactionStatus::Confirmed; + let _ = deployment_event_tx + .send(DeploymentEvent::TransactionUpdate(tracker.clone())); + } else { + keep_looping = true; + break; + } + } + TransactionStatus::Broadcasted(TransactionCheck::ContractCall( + tx_sender, + expected_nonce, + )) => { + let tx_sender_address = tx_sender.to_address(); + let res = stacks_rpc.get_nonce(&tx_sender_address); + if let Ok(current_nonce) = res { + if current_nonce > *expected_nonce { + tracker.status = TransactionStatus::Confirmed; + let _ = deployment_event_tx + .send(DeploymentEvent::TransactionUpdate(tracker.clone())); + } else { + keep_looping = true; + break; + } + } + } + _ => {} + } + } + if !keep_looping { + break; + } + } + } + + let _ = deployment_event_tx.send(DeploymentEvent::ProtocolDeployed); +} + +pub fn get_initial_transactions_trackers( + deployment: &DeploymentSpecification, +) -> Vec { + let mut index = 0; + let mut trackers = vec![]; + for batch_spec in deployment.plan.batches.iter() { + for transaction in batch_spec.transactions.iter() { + let tracker = match transaction { + TransactionSpecification::ContractCall(tx) => TransactionTracker { + index, + name: format!("Contract call {}::{}", tx.contract_id, tx.method), + status: TransactionStatus::Queued, + }, + TransactionSpecification::ContractPublish(tx) => TransactionTracker { + index, + name: format!( + "Contract publish {}.{}", + tx.expected_sender.to_address(), + tx.contract_name + ), + status: TransactionStatus::Queued, + }, + TransactionSpecification::RequirementPublish(tx) => { + if !deployment.network.either_devnet_or_testnet() { + panic!("Deployment specification malformed - requirements publish not supported on mainnet"); + } + TransactionTracker { + index, + name: format!( + "Contract publish {}.{}", + tx.remap_sender.to_address(), + tx.contract_id.name + ), + status: TransactionStatus::Queued, + } + } + TransactionSpecification::BtcTransfer(tx) => TransactionTracker { + index, + name: format!( + "BTC transfer {} send {} to {}", + tx.expected_sender, tx.sats_amount, tx.recipient + ), + status: TransactionStatus::Queued, + }, + TransactionSpecification::EmulatedContractPublish(_) + | TransactionSpecification::EmulatedContractCall(_) => continue, + }; + trackers.push(tracker); + index += 1; + } + } + trackers +} diff --git a/components/hiro-system-kit/Cargo.toml b/components/hiro-system-kit/Cargo.toml new file mode 100644 index 000000000..84c163109 --- /dev/null +++ b/components/hiro-system-kit/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "hiro-system-kit" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +futures = "0.3.12" +tokio = { version = "=1.17.0", features = ["full"] } diff --git a/components/clarinet-cli/src/utils/mod.rs b/components/hiro-system-kit/src/lib.rs similarity index 100% rename from components/clarinet-cli/src/utils/mod.rs rename to components/hiro-system-kit/src/lib.rs diff --git a/components/stacks-network/Cargo.toml b/components/stacks-network/Cargo.toml new file mode 100644 index 000000000..7e5fedfca --- /dev/null +++ b/components/stacks-network/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "stacks-network" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bollard = "0.11.0" +crossterm = { version = "0.22.1", optional = true } +bitcoin = "0.28.1" +bitcoincore-rpc = "0.14.0" +serde = { version = "1.0.136", features = ["derive"] } +serde_json = { version = "1.0.79", features = ["preserve_order"] } +serde_derive = "1" +tracing = "0.1" +tracing-subscriber = "0.3.3" +tracing-appender = "0.2.0" +ctrlc = "3.1.9" +reqwest = { version = "0.11", default-features = false, features = [ + "blocking", + "json", + "rustls-tls", +] } +tui = { version = "0.18.0", default-features = false, features = [ + "crossterm", +] } + +chrono = "0.4.20" +futures = "0.3.12" +base58 = "0.2.0" + +chainhook_event_observer = { package = "chainhook-event-observer", default-features = false, path = "../chainhook-event-observer" } +chainhook_types = { package = "chainhook-types", path = "../chainhook-types-rs" } +stacks_rpc_client = { package = "stacks-rpc-client", path = "../stacks-rpc-client" } +clarinet_files = { package = "clarinet-files", path = "../clarinet-files", features = ["cli"] } +clarinet_deployments = { package = "clarinet-deployments", path = "../clarinet-deployments", features = ["cli"] } +clarinet_utils = { package = "clarinet-utils", path = "../clarinet-utils" } +hiro_system_kit = { package = "hiro-system-kit", path = "../hiro-system-kit" } +clarity_repl = { package = "clarity-repl", path = "../clarity-repl", features = ["cli"] } + +[features] +default = ["crossterm"] \ No newline at end of file diff --git a/components/clarinet-cli/src/integrate/chains_coordinator.rs b/components/stacks-network/src/chains_coordinator.rs similarity index 90% rename from components/clarinet-cli/src/integrate/chains_coordinator.rs rename to components/stacks-network/src/chains_coordinator.rs index 124360c86..7559f9951 100644 --- a/components/clarinet-cli/src/integrate/chains_coordinator.rs +++ b/components/stacks-network/src/chains_coordinator.rs @@ -1,19 +1,21 @@ +use super::ChainsCoordinatorCommand; use super::DevnetEvent; -use crate::chainhooks::load_chainhooks; -use crate::deployments::{apply_on_chain_deployment, DeploymentCommand, DeploymentEvent}; -use crate::integrate::{ServiceStatusData, Status}; -use crate::types::ChainsCoordinatorCommand; -use crate::utils; +use crate::{ServiceStatusData, Status}; use base58::FromBase58; use bitcoincore_rpc::{Auth, Client, RpcApi}; +use chainhook_event_observer::chainhooks::types::HookFormation; +use clarinet_deployments::onchain::{ + apply_on_chain_deployment, DeploymentCommand, DeploymentEvent, +}; use clarinet_deployments::types::DeploymentSpecification; use clarinet_files::{self, AccountConfig, DevnetConfig, NetworkManifest, ProjectManifest}; +use hiro_system_kit; use chainhook_event_observer::observer::{ start_event_observer, EventObserverConfig, ObserverCommand, ObserverEvent, StacksChainMempoolEvent, }; -use chainhook_types::{BitcoinChainEvent, BitcoinNetwork, StacksChainEvent, StacksNetwork}; +use chainhook_types::{BitcoinChainEvent, StacksChainEvent, StacksNetwork}; use clarity_repl::clarity::address::AddressHashMode; use clarity_repl::clarity::util::hash::{hex_bytes, Hash160}; use clarity_repl::clarity::vm::types::{BuffData, SequenceData, TupleData}; @@ -72,6 +74,7 @@ impl DevnetEventObserverConfig { devnet_config: DevnetConfig, manifest: ProjectManifest, deployment: DeploymentSpecification, + chainhooks: HookFormation, ) -> Self { info!("Checking contracts..."); let network_manifest = NetworkManifest::from_project_manifest_location( @@ -80,25 +83,13 @@ impl DevnetEventObserverConfig { ) .expect("unable to load network manifest"); - info!("Checking hooks..."); - let hooks = match load_chainhooks( - &manifest.location, - &(BitcoinNetwork::Regtest, StacksNetwork::Devnet), - ) { - Ok(hooks) => hooks, - Err(e) => { - println!("{}", e); - std::process::exit(1); - } - }; - let event_observer_config = EventObserverConfig { normalization_enabled: true, grpc_server_enabled: false, hooks_enabled: true, bitcoin_rpc_proxy_enabled: true, event_handlers: vec![], - initial_hook_formation: Some(hooks), + initial_hook_formation: Some(chainhooks), ingestion_port: devnet_config.orchestrator_ingestion_port, control_port: devnet_config.orchestrator_control_port, bitcoin_node_username: devnet_config.bitcoin_node_username.clone(), @@ -155,20 +146,20 @@ pub async fn start_chains_coordinator( let event_observer_config = config.event_observer_config.clone(); let observer_event_tx_moved = observer_event_tx.clone(); let observer_command_tx_moved = observer_command_tx.clone(); - let _ = utils::thread_named("Event observer").spawn(move || { + let _ = hiro_system_kit::thread_named("Event observer").spawn(move || { let future = start_event_observer( event_observer_config, observer_command_tx_moved, observer_command_rx, Some(observer_event_tx_moved), ); - let _ = utils::nestable_block_on(future); + let _ = hiro_system_kit::nestable_block_on(future); }); // Spawn bitcoin miner controller let (mining_command_tx, mining_command_rx) = channel(); let devnet_config = config.devnet_config.clone(); - let _ = utils::thread_named("Bitcoin mining").spawn(move || { + let _ = hiro_system_kit::thread_named("Bitcoin mining").spawn(move || { handle_bitcoin_mining(mining_command_rx, &devnet_config); }); @@ -264,20 +255,22 @@ pub async fn start_chains_coordinator( ) } - let _ = utils::thread_named("Deployment monitoring").spawn(move || loop { - match deployment_progress_rx.recv() { - Ok(DeploymentEvent::ProtocolDeployed) => { - protocol_deployed_moved.store(true, Ordering::SeqCst); - if !automining_disabled { - let _ = - mining_command_tx_moved.send(BitcoinMiningCommand::Start); + let _ = hiro_system_kit::thread_named("Deployment monitoring").spawn( + move || loop { + match deployment_progress_rx.recv() { + Ok(DeploymentEvent::ProtocolDeployed) => { + protocol_deployed_moved.store(true, Ordering::SeqCst); + if !automining_disabled { + let _ = mining_command_tx_moved + .send(BitcoinMiningCommand::Start); + } + break; } - break; + Ok(_) => continue, + _ => break, } - Ok(_) => continue, - _ => break, - } - }); + }, + ); } let known_tip = match &chain_event { @@ -410,7 +403,7 @@ pub fn prepare_protocol_deployment( let manifest = manifest.clone(); let deployment = deployment.clone(); - let _ = utils::thread_named("Deployment preheat").spawn(move || { + let _ = hiro_system_kit::thread_named("Deployment preheat").spawn(move || { apply_on_chain_deployment( &manifest, deployment, @@ -433,7 +426,7 @@ pub fn perform_protocol_deployment( let _ = deployment_commands_tx.send(DeploymentCommand::Start); - let _ = utils::thread_named("Deployment perform").spawn(move || { + let _ = hiro_system_kit::thread_named("Deployment perform").spawn(move || { loop { let event = match deployment_events_rx.recv() { Ok(event) => event, @@ -503,7 +496,7 @@ pub async fn publish_stacking_orders( let node_url = stacks_node_rpc_url.clone(); let pox_contract_id = pox_info.contract_id.clone(); - let _ = utils::thread_named("Stacking orders handler").spawn(move || { + let _ = hiro_system_kit::thread_named("Stacking orders handler").spawn(move || { let default_fee = fee_rate * 1000; let stacks_rpc = StacksRpc::new(&node_url); let nonce = stacks_rpc @@ -616,20 +609,21 @@ fn handle_bitcoin_mining( stop_miner.store(false, Ordering::SeqCst); let stop_miner_reader = stop_miner.clone(); let devnet_config = devnet_config.clone(); - let _ = utils::thread_named("Bitcoin mining runloop").spawn(move || loop { - std::thread::sleep(std::time::Duration::from_millis( - devnet_config.bitcoin_controller_block_time.into(), - )); - mine_bitcoin_block( - devnet_config.bitcoin_node_rpc_port, - &devnet_config.bitcoin_node_username, - &devnet_config.bitcoin_node_password, - &devnet_config.miner_btc_address, - ); - if stop_miner_reader.load(Ordering::SeqCst) { - break; - } - }); + let _ = + hiro_system_kit::thread_named("Bitcoin mining runloop").spawn(move || loop { + std::thread::sleep(std::time::Duration::from_millis( + devnet_config.bitcoin_controller_block_time.into(), + )); + mine_bitcoin_block( + devnet_config.bitcoin_node_rpc_port, + &devnet_config.bitcoin_node_username, + &devnet_config.bitcoin_node_password, + &devnet_config.miner_btc_address, + ); + if stop_miner_reader.load(Ordering::SeqCst) { + break; + } + }); } BitcoinMiningCommand::Pause => { stop_miner.store(true, Ordering::SeqCst); diff --git a/components/stacks-network/src/lib.rs b/components/stacks-network/src/lib.rs new file mode 100644 index 000000000..35f04c975 --- /dev/null +++ b/components/stacks-network/src/lib.rs @@ -0,0 +1,302 @@ +extern crate serde; + +#[macro_use] +extern crate serde_derive; + +pub mod chains_coordinator; +mod orchestrator; +mod ui; + +use std::sync::mpsc::{self, channel, Sender}; + +use chainhook_event_observer::{chainhooks::types::HookFormation, observer::MempoolAdmissionData}; +use chrono::prelude::*; +use tracing::{self, debug, error, info, warn}; +use tracing_appender; + +use chainhook_types::{BitcoinChainEvent, StacksChainEvent}; +use chains_coordinator::start_chains_coordinator; +use clarinet_deployments::types::DeploymentSpecification; +use hiro_system_kit; +pub use orchestrator::DevnetOrchestrator; + +use self::chains_coordinator::DevnetEventObserverConfig; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum ChainsCoordinatorCommand { + Terminate, + ProtocolDeployed, +} + +pub fn block_on(future: F) -> R +where + F: std::future::Future, +{ + let rt = hiro_system_kit::create_basic_runtime(); + rt.block_on(future) +} + +pub async fn do_run_devnet( + mut devnet: DevnetOrchestrator, + deployment: DeploymentSpecification, + chainhooks: HookFormation, + log_tx: Option>, + display_dashboard: bool, +) -> Result< + ( + Option>, + Option>, + Option>, + ), + String, +> { + let (devnet_events_tx, devnet_events_rx) = channel(); + let (termination_success_tx, orchestrator_terminated_rx) = channel(); + + devnet.termination_success_tx = Some(termination_success_tx); + + let devnet_config = match devnet.network_config { + Some(ref network_config) => match &network_config.devnet { + Some(devnet_config) => Ok(devnet_config.clone()), + _ => Err("Unable to retrieve config"), + }, + _ => Err("Unable to retrieve config"), + }?; + + let file_appender = tracing_appender::rolling::never(&devnet_config.working_dir, "devnet.log"); + let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); + + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_writer(non_blocking) + .init(); + + // The event observer should be able to send some events to the UI thread, + // and should be able to be terminated + let devnet_path = devnet_config.working_dir.clone(); + let config = DevnetEventObserverConfig::new( + devnet_config.clone(), + devnet.manifest.clone(), + deployment, + chainhooks, + ); + let chains_coordinator_tx = devnet_events_tx.clone(); + let (chains_coordinator_commands_tx, chains_coordinator_commands_rx) = channel(); + let (orchestrator_terminator_tx, terminator_rx) = channel(); + let (observer_command_tx, observer_command_rx) = channel(); + let moved_orchestrator_terminator_tx = orchestrator_terminator_tx.clone(); + let moved_chains_coordinator_commands_tx = chains_coordinator_commands_tx.clone(); + let moved_observer_command_tx = observer_command_tx.clone(); + + let chains_coordinator_handle = hiro_system_kit::thread_named("Chains coordinator") + .spawn(move || { + let future = start_chains_coordinator( + config, + chains_coordinator_tx, + chains_coordinator_commands_rx, + moved_chains_coordinator_commands_tx, + moved_orchestrator_terminator_tx, + observer_command_tx, + observer_command_rx, + ); + let rt = hiro_system_kit::create_basic_runtime(); + rt.block_on(future) + }) + .expect("unable to retrieve join handle"); + + // Let's start the orchestration + + // The devnet orchestrator should be able to send some events to the UI thread, + // and should be able to be restarted/terminated + let orchestrator_event_tx = devnet_events_tx.clone(); + let orchestrator_handle = hiro_system_kit::thread_named("Devnet orchestrator") + .spawn(move || { + let future = devnet.start(orchestrator_event_tx, terminator_rx); + let rt = hiro_system_kit::create_basic_runtime(); + rt.block_on(future) + }) + .expect("unable to retrieve join handle"); + + if display_dashboard { + info!("Starting Devnet..."); + let moved_chains_coordinator_commands_tx = chains_coordinator_commands_tx.clone(); + let _ = ui::start_ui( + devnet_events_tx, + devnet_events_rx, + moved_chains_coordinator_commands_tx, + moved_observer_command_tx, + orchestrator_terminated_rx, + &devnet_path, + devnet_config.enable_subnet_node, + ); + } else { + let moved_events_observer_commands_tx = chains_coordinator_commands_tx.clone(); + ctrlc::set_handler(move || { + moved_events_observer_commands_tx + .send(ChainsCoordinatorCommand::Terminate) + .expect("Unable to terminate devnet"); + }) + .expect("Error setting Ctrl-C handler"); + + if log_tx.is_none() { + loop { + match devnet_events_rx.recv() { + Ok(DevnetEvent::Log(log)) => { + if let Some(ref log_tx) = log_tx { + let _ = log_tx.send(log.clone()); + } else { + println!("{}", log.message); + match log.level { + LogLevel::Debug => debug!("{}", log.message), + LogLevel::Info | LogLevel::Success => info!("{}", log.message), + LogLevel::Warning => warn!("{}", log.message), + LogLevel::Error => error!("{}", log.message), + } + } + } + _ => {} + } + } + } else { + return Ok(( + Some(devnet_events_rx), + Some(orchestrator_terminator_tx), + Some(chains_coordinator_commands_tx), + )); + } + } + + if let Err(e) = chains_coordinator_handle.join() { + if let Ok(message) = e.downcast::() { + return Err(*message); + } + } + + if let Err(e) = orchestrator_handle.join() { + if let Ok(message) = e.downcast::() { + return Err(*message); + } + } + + Ok((None, None, Some(chains_coordinator_commands_tx))) +} + +#[allow(dead_code)] +#[derive(Debug)] +pub enum DevnetEvent { + Log(LogData), + KeyEvent(crossterm::event::KeyEvent), + Tick, + ServiceStatus(ServiceStatusData), + ProtocolDeployingProgress(ProtocolDeployingData), + ProtocolDeployed, + StacksChainEvent(StacksChainEvent), + BitcoinChainEvent(BitcoinChainEvent), + MempoolAdmission(MempoolAdmissionData), + FatalError(String), + // Restart, + // Terminate, + // Microblock(MicroblockData), +} + +#[allow(dead_code)] +impl DevnetEvent { + pub fn error(message: String) -> DevnetEvent { + DevnetEvent::Log(Self::log_error(message)) + } + + #[allow(dead_code)] + pub fn warning(message: String) -> DevnetEvent { + DevnetEvent::Log(Self::log_warning(message)) + } + + pub fn info(message: String) -> DevnetEvent { + DevnetEvent::Log(Self::log_info(message)) + } + + pub fn success(message: String) -> DevnetEvent { + DevnetEvent::Log(Self::log_success(message)) + } + + pub fn debug(message: String) -> DevnetEvent { + DevnetEvent::Log(Self::log_debug(message)) + } + + pub fn log_error(message: String) -> LogData { + LogData::new(LogLevel::Error, message) + } + + pub fn log_warning(message: String) -> LogData { + LogData::new(LogLevel::Warning, message) + } + + pub fn log_info(message: String) -> LogData { + LogData::new(LogLevel::Info, message) + } + + pub fn log_success(message: String) -> LogData { + LogData::new(LogLevel::Success, message) + } + + pub fn log_debug(message: String) -> LogData { + LogData::new(LogLevel::Debug, message) + } +} + +#[derive(Clone, Debug)] +pub enum LogLevel { + Error, + Warning, + Info, + Success, + Debug, +} + +#[derive(Clone, Debug)] +pub struct LogData { + pub occurred_at: String, + pub message: String, + pub level: LogLevel, +} + +impl LogData { + pub fn new(level: LogLevel, message: String) -> LogData { + let now: DateTime = Utc::now(); + LogData { + level, + message, + occurred_at: now.format("%b %e %T%.6f").to_string(), + } + } +} + +#[derive(Clone, Debug)] +pub enum Status { + Red, + Yellow, + Green, +} + +#[derive(Clone, Debug)] +pub struct ServiceStatusData { + pub order: usize, + pub status: Status, + pub name: String, + pub comment: String, +} + +#[derive(Clone, Debug)] +pub struct ProtocolDeployingData { + pub new_contracts_deployed: Vec, +} + +#[derive(Clone, Debug)] +pub struct ProtocolDeployedData { + pub contracts_deployed: Vec, +} + +// pub struct MicroblockData { +// pub seq: u32, +// pub transactions: Vec +// } diff --git a/components/clarinet-cli/src/integrate/orchestrator.rs b/components/stacks-network/src/orchestrator.rs similarity index 99% rename from components/clarinet-cli/src/integrate/orchestrator.rs rename to components/stacks-network/src/orchestrator.rs index 4a27eda6f..57bb622ca 100644 --- a/components/clarinet-cli/src/integrate/orchestrator.rs +++ b/components/stacks-network/src/orchestrator.rs @@ -1,5 +1,5 @@ use super::DevnetEvent; -use crate::integrate::{ServiceStatusData, Status}; +use crate::{ServiceStatusData, Status}; use bollard::container::{ Config, CreateContainerOptions, KillContainerOptions, ListContainersOptions, PruneContainersOptions, WaitContainerOptions, diff --git a/components/clarinet-cli/src/integrate/ui/app.rs b/components/stacks-network/src/ui/app.rs similarity index 97% rename from components/clarinet-cli/src/integrate/ui/app.rs rename to components/stacks-network/src/ui/app.rs index 7657f9e9c..bd7e69f83 100644 --- a/components/clarinet-cli/src/integrate/ui/app.rs +++ b/components/stacks-network/src/ui/app.rs @@ -1,5 +1,5 @@ use super::util::{StatefulList, TabsState}; -use crate::integrate::{LogData, MempoolAdmissionData, ServiceStatusData}; +use crate::{LogData, MempoolAdmissionData, ServiceStatusData}; use chainhook_types::{StacksBlockData, StacksMicroblockData, StacksTransactionData}; use tui::style::{Color, Style}; use tui::text::{Span, Spans}; @@ -84,7 +84,7 @@ impl<'a> App<'a> { } pub fn display_log(&mut self, log: LogData) { - use crate::integrate::LogLevel; + use crate::LogLevel; use tracing::{debug, error, info, warn}; match &log.level { LogLevel::Error => error!("{}", log.message), diff --git a/components/clarinet-cli/src/integrate/ui/mod.rs b/components/stacks-network/src/ui/mod.rs similarity index 99% rename from components/clarinet-cli/src/integrate/ui/mod.rs rename to components/stacks-network/src/ui/mod.rs index ba27c2bc1..51807ca1a 100644 --- a/components/clarinet-cli/src/integrate/ui/mod.rs +++ b/components/stacks-network/src/ui/mod.rs @@ -6,7 +6,7 @@ mod ui; mod util; use super::DevnetEvent; -use crate::types::ChainsCoordinatorCommand; +use crate::ChainsCoordinatorCommand; use app::App; use chainhook_event_observer::observer::ObserverCommand; use chainhook_types::StacksChainEvent; diff --git a/components/clarinet-cli/src/integrate/ui/ui.rs b/components/stacks-network/src/ui/ui.rs similarity index 99% rename from components/clarinet-cli/src/integrate/ui/ui.rs rename to components/stacks-network/src/ui/ui.rs index 0931e3105..57855b673 100644 --- a/components/clarinet-cli/src/integrate/ui/ui.rs +++ b/components/stacks-network/src/ui/ui.rs @@ -1,4 +1,4 @@ -use crate::integrate::{LogLevel, Status}; +use crate::{LogLevel, Status}; use chainhook_types::{StacksBlockData, StacksMicroblockData, StacksTransactionData}; use super::{app::BlockData, App}; diff --git a/components/clarinet-cli/src/integrate/ui/util/event.rs b/components/stacks-network/src/ui/util/event.rs similarity index 100% rename from components/clarinet-cli/src/integrate/ui/util/event.rs rename to components/stacks-network/src/ui/util/event.rs diff --git a/components/clarinet-cli/src/integrate/ui/util/mod.rs b/components/stacks-network/src/ui/util/mod.rs similarity index 100% rename from components/clarinet-cli/src/integrate/ui/util/mod.rs rename to components/stacks-network/src/ui/util/mod.rs diff --git a/components/stacks-network/src/utils.rs b/components/stacks-network/src/utils.rs new file mode 100644 index 000000000..c75b3de30 --- /dev/null +++ b/components/stacks-network/src/utils.rs @@ -0,0 +1,12 @@ +use std::future::Future; +use std::thread::Builder; +use tokio; + +pub fn create_basic_runtime() -> tokio::runtime::Runtime { + tokio::runtime::Builder::new_current_thread() + .enable_io() + .enable_time() + .max_blocking_threads(32) + .build() + .unwrap() +} \ No newline at end of file