diff --git a/crates/sdk/src/constants.rs b/crates/sdk/src/constants.rs index cd5d7044..adb54429 100644 --- a/crates/sdk/src/constants.rs +++ b/crates/sdk/src/constants.rs @@ -1,7 +1,10 @@ -/// Dummy signature, which is used for fee estimation +/// A placeholder for a witness signature that the signer replaces, for example at the end of a +/// [`ProgramSignature::at_path`](crate::transaction::ProgramSignature::at_path) path. pub const DUMMY_SIGNATURE: [u8; 64] = [1; 64]; -/// Minimal acceptable fee for nodes to relay a transaction +/// The smallest policy-asset surplus `Signer::finalize_strict` accepts, and the smallest blinded +/// change output the signer creates. It isn't a minimum fee: the fee follows the fee rate and can +/// be smaller. pub const MIN_FEE: u64 = 10; /// The smallest explicit output Elements relays by default for any standard script. diff --git a/crates/sdk/src/global.rs b/crates/sdk/src/global.rs index 6333af95..5e8825ca 100644 --- a/crates/sdk/src/global.rs +++ b/crates/sdk/src/global.rs @@ -6,18 +6,19 @@ use crate::program::TrackerLogLevel; static GLOBAL_CONFIG: OnceLock = OnceLock::new(); -/// A structure to represent the global configuration settings for the application. +/// The process-wide verbosity of program execution. +/// +/// Verbosity `Debug` or `Trace` also compiles programs with debug symbols, which changes their +/// CMRs and addresses. An application that works with contracts deployed by others should leave +/// verbosity at `None`, or set [`Program::with_debug_symbols`](crate::program::Program::with_debug_symbols) +/// on each program. #[derive(Clone, Copy, Debug, Default)] pub struct GlobalConfig { verbosity: Verbosity, } impl GlobalConfig { - /// Sets the global configuration for the SDK. - /// - /// This function allows setting a global configuration which includes - /// the logging level for `simplicity` contracts execution. - /// It must be called exactly once during the application's lifetime. + /// Sets the process-wide verbosity. It can be set only once per process. /// /// # Errors /// Returns an error containing the newly created `GlobalConfig` if the global configuration has already been initialized. @@ -25,7 +26,8 @@ impl GlobalConfig { GLOBAL_CONFIG.set(GlobalConfig { verbosity }) } - /// Returns the default log level if `GLOBAL_CONFIG` is not initialized + /// The tracker log level for the configured verbosity, or `TrackerLogLevel::None` if it was + /// never set. pub fn get_log_level() -> TrackerLogLevel { GLOBAL_CONFIG .get() diff --git a/crates/sdk/src/program/core.rs b/crates/sdk/src/program/core.rs index 722a2324..fd999017 100644 --- a/crates/sdk/src/program/core.rs +++ b/crates/sdk/src/program/core.rs @@ -20,9 +20,12 @@ use super::error::ProgramError; use crate::provider::SimplicityNetwork; use crate::utils::{check_budget, hash_script, tap_data_hash, tr_unspendable_key}; -/// Executes `simplicity` programs at runtime. +/// What the [`Signer`](crate::signer::Signer) needs from a program to sign for and finalize a +/// program input. /// -/// This trait defines a core behavior related to testing and execution. +/// [`Program`] implements it, as does `&T` for any `T: ProgramTrait`. +/// [`ProgramInput`](crate::transaction::ProgramInput) holds a `Box`, which is +/// `Clone`. pub trait ProgramTrait: DynClone + Send + Sync { /// Retrieves the types of arguments required by a `simplicity` program. /// @@ -36,10 +39,13 @@ pub trait ProgramTrait: DynClone + Send + Sync { /// Returns a `ProgramError` if parsing or generating ABI metadata fails. fn get_witness_types(&self) -> Result; - /// Constructs the Elements environment for a specified input index, PST, and network for further program execution. + /// The Simplicity transaction environment for input `input_index` of `pst`: what the + /// program's transaction jets, such as `jet::sig_all_hash()`, read. /// /// # Errors - /// Returns a `ProgramError` if the input index is out of bounds or if the script pubkey of the UTXO mismatches the expected program script. + /// Returns `ProgramError::MissingWitnessUtxo` if an input of `pst` doesn't carry the output it + /// spends, `ProgramError::UtxoIndexOutOfBounds` if `input_index` is past the inputs, and + /// `ProgramError::ScriptPubkeyMismatch` if the input doesn't spend this program's address. fn get_env( &self, pst: &PartiallySignedTransaction, @@ -47,14 +53,22 @@ pub trait ProgramTrait: DynClone + Send + Sync { network: &SimplicityNetwork, ) -> Result>, ProgramError>; - /// Executes a Simplicity program for the given input index of a partially signed transaction. + /// Runs the program locally on input `input_index` of `pst` with `witness`: satisfies it, + /// prunes it, and runs the pruned program on the Bit Machine. Returns the pruned program and + /// its output. /// - /// This function evaluates a Simplicity script associated with a specific transaction input - /// in a given network, producing the result of the computation along with the redeem node - /// used during execution. + /// Doesn't check the witness budget; [`ProgramTrait::finalize`] does. With [`Verbosity`](crate::global::Verbosity) + /// `Debug` or `Trace`, the execution output is buffered, not printed: call + /// [`ProgramLogger::flush_logs`] to print it. /// /// # Errors - /// Returns a `ProgramError` if loading the program, satisfying the witness, retrieving the environment, or executing the `BitMachine` fails. + /// - `ProgramError::WitnessSatisfaction` if the witness doesn't fit the program's witness types. + /// - `ProgramError::Pruning` if the program fails. A failed `assert!` or jet is + /// `ExecutionError::JetFailed`. Reaching a pruned branch, for example with `unwrap(None)`, is + /// `ExecutionError::ReachedPrunedBranch`. [`Program`] reports a failure in the Bit Machine run + /// after pruning with this variant too. + /// - `ProgramError::BitMachineCreation` if the program exceeds the Bit Machine's limits. + /// - The errors of [`ProgramTrait::get_env`] and of compiling the program. fn execute( &self, pst: &PartiallySignedTransaction, @@ -63,14 +77,16 @@ pub trait ProgramTrait: DynClone + Send + Sync { network: &SimplicityNetwork, ) -> Result<(Arc, Value), ProgramError>; - /// Finalizes and returns `pruned_witness` as output after executing the program on certain parameters. + /// Runs the program as [`ProgramTrait::execute`] does and returns the input's witness stack: + /// `[witness, program, cmr, control_block]`. /// /// Implementations must compare the pruned program's static cost with the budget from the /// exact serialized witness stack they return. Use [`check_budget`] for this check, /// as the built-in `Program` does. /// /// # Errors - /// Returns a `ProgramError` if execution, control block construction or budget validation fails. + /// Returns the errors of [`ProgramTrait::execute`], and `ProgramError::InsufficientBudget` if the + /// witness stack doesn't pay for the program's execution cost. fn finalize( &self, pst: &PartiallySignedTransaction, @@ -81,6 +97,9 @@ pub trait ProgramTrait: DynClone + Send + Sync { } /// Static description of a program's inputs and integer literals. Implemented by `include_simf!()`. +/// +/// The fuzz engine uses it to generate arguments and witnesses for a program it knows only by +/// type. pub trait ProgramSchema { /// Generated arguments struct. type Arguments; @@ -94,22 +113,31 @@ pub trait ProgramSchema { /// Witness names and types. fn witness_types() -> Vec<(TemplateProgramWitness, ResolvedType)>; - /// Integer literals in the source. + /// The integer literals in the source, sorted and without duplicates. fn literals() -> Vec; } -/// A trait for creating instances of a program. The `ProgramFactory` trait defines a mechanism -/// for constructing and returning a program instance of a type that implements `AsRef`. -/// Only by generic struct name we have a possibility to create an instance of a program. +/// Creates a program of type `P` from arguments, for code that knows the program only by type, +/// such as the fuzz engine. Implemented by `include_simf!()`. pub trait ProgramFactory + Sized> { /// Instantiates a program with the given arguments. fn instantiate_program(args: impl Into) -> Box

; } -/// Represents a program structure containing its public key, compiled program, and associated storage. -/// The compiled program with its CMR and the taproot spend info act as caches, filled on first use. +/// A `SimplicityHL` program with its arguments, its Taproot internal key, and its storage slots. +/// +/// The program compiles on first use, and the result is cached and shared between clones. The +/// getters that need the compiled program panic if it doesn't compile. Call [`Program::load`] +/// to get the compile error instead. +/// +/// The program's address is a Taproot output. Its script tree holds the program as one leaf and +/// one hidden leaf per storage slot, `tagged_hash("TapData", slot)`. The tree is left-folded: +/// `tapbranch(tapbranch(tapbranch(program, slot0), slot1), slot2)`. So the source, the arguments +/// and the debug-symbol setting determine the CMR, and the address also depends on the internal +/// key and the storage. /// -/// Abstraction giving the power to execute Simplicity contracts without specifying any additional parameters. +/// Code generated by `simplex build` wraps a `Program` in one type per contract, which reaches it +/// with `as_ref()`. #[derive(Clone)] pub struct Program { source: Arc, @@ -287,10 +315,14 @@ impl Program { /// The width of a storage slot. pub const STORAGE_SLOT_BYTES: usize = 32; - /// Each slot deepens the taproot tree by one and the tree is at most 128 layers deep. + /// The most storage slots a program can have. Each slot deepens the Taproot tree by one, and + /// the tree is at most 128 layers deep. pub const MAX_STORAGE_SLOTS: usize = 128; - /// Creates a new instance of the struct with the provided source string and arguments. + /// Creates a program from `SimplicityHL` source and its `param::` arguments, without compiling + /// it. + /// + /// The Taproot internal key is [`tr_unspendable_key`], and there aren't any storage slots. #[must_use] pub fn new(source: impl Into>, arguments: impl Into) -> Self { Self { @@ -304,8 +336,7 @@ impl Program { } } - /// Sets the `pub_key` field of the struct to the provided `XOnlyPublicKey` value and returns the updated builder instance. - /// This is used to set the taproot public key for the program. + /// Sets the Taproot internal key, which changes the address. #[must_use] pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { self.pub_key = pub_key; @@ -314,7 +345,11 @@ impl Program { self } - /// Builds this program in the mode the protocol declares. + /// Compiles the program with or without debug symbols, overriding [`GlobalConfig`]. + /// + /// Debug symbols change the CMR and so the address. A program whose address must match one + /// computed elsewhere, such as a deployed contract, should set this explicitly, because + /// verbosity `Debug` or `Trace` turns debug symbols on by default. #[must_use] pub fn with_debug_symbols(mut self, include: bool) -> Self { self.include_debug_symbols = Some(include); @@ -325,7 +360,7 @@ impl Program { self } - /// Sets storage capacity for further usage. + /// Replaces the storage with `capacity` zeroed slots. /// /// # Panics /// Panics if `capacity` exceeds `MAX_STORAGE_SLOTS`. @@ -383,19 +418,19 @@ impl Program { &self.arguments } - /// Returns the number of storage chunks for a program. + /// The number of storage slots. #[must_use] pub fn get_storage_len(&self) -> usize { self.storage.len() } - /// Returns storage as a whole array of 32-byte chunks. + /// The storage slots, each [`Program::STORAGE_SLOT_BYTES`] long. #[must_use] pub fn get_storage(&self) -> &[Vec] { &self.storage } - /// Returns storage value at a certain index. + /// The storage slot at `index`. /// /// # Panics /// Panics if the `index` is out of bounds for the initiated storage. @@ -404,10 +439,10 @@ impl Program { self.storage[index].clone() } - /// Returns a taproot address for a defined `SimplicityNetwork`. + /// The program's unconfidential Taproot address on `network`. /// /// # Panics - /// Panics if generating the taproot spending information fails. + /// Panics if the program doesn't compile. #[must_use] pub fn get_tr_address(&self, network: &SimplicityNetwork) -> Address { let spend_info = self.taproot_spending_info().unwrap_or_else(|error| panic!("{error}")); @@ -421,19 +456,27 @@ impl Program { ) } - /// Retrieves the `ScriptPubKey` associated with the Simplicity address for the specified network. + /// The script of the program's address on `network`, which locks the outputs the program + /// controls. + /// + /// # Panics + /// Panics if the program doesn't compile. #[must_use] pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { self.get_tr_address(network).script_pubkey() } - /// Retrieves the 32-byte `ScriptPubKey` hash associated with the Simplicity address for the specified network. + /// The SHA-256 hash of [`Program::get_script_pubkey`], as Esplora's `scripthash` endpoints + /// and the `jet::output_script_hash` family use. + /// + /// # Panics + /// Panics if the program doesn't compile. #[must_use] pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { hash_script(&self.get_script_pubkey(network)) } - /// Compiles the program and returns its Commitment Merkle Root. + /// The program's Commitment Merkle Root. /// /// # Panics /// Panics if the `SimplicityHL` compilation fails. @@ -442,7 +485,7 @@ impl Program { self.cmr().unwrap_or_else(|error| panic!("{error}")).to_byte_array() } - /// Returns the 32-byte tapleaf hash of the program's Simplicity script. + /// The tapleaf hash of the program's leaf. Storage doesn't change it. /// /// # Panics /// Panics if the `SimplicityHL` compilation fails. @@ -453,7 +496,7 @@ impl Program { taproot::TapLeafHash::from_script(&script, version).to_byte_array() } - /// Retrieves program ABI metadata for argument types. + /// The names and types of the program's `param::` arguments. /// /// # Errors /// Returns a `ProgramError` if compilation fails or generating ABI metadata fails. @@ -466,7 +509,7 @@ impl Program { Ok(abi_meta.param_types) } - /// Retrieves the witness types of the compiled program. + /// The names and types of the program's `witness::` fields. /// /// # Errors /// Returns a `ProgramError` if compilation fails. @@ -474,10 +517,10 @@ impl Program { Ok(self.load()?.witness_types().shallow_clone()) } - /// Compiles program by providing saved `Arguments`. + /// Compiles the program, or returns the cached result of an earlier compilation. /// /// # Errors - /// Retruns an error we have problems in a program compilation. + /// Returns `ProgramError::Compilation` if the source doesn't compile with these arguments. pub fn load(&self) -> Result<&CompiledProgram, ProgramError> { Ok(&self.load_with_cmr()?.0) } diff --git a/crates/sdk/src/program/error.rs b/crates/sdk/src/program/error.rs index 63252324..cf5cc18c 100644 --- a/crates/sdk/src/program/error.rs +++ b/crates/sdk/src/program/error.rs @@ -31,7 +31,11 @@ pub enum ProgramError { #[error("Failed to satisfy witness: {0}")] WitnessSatisfaction(String), - /// Error indicating pruning the node tree during execution failed safely. + /// The program failed. `ExecutionError::JetFailed` is a failed `assert!` or jet, and + /// `ExecutionError::ReachedPrunedBranch` is, for example, `unwrap(None)`. + /// + /// [`Program`](super::Program) reports both a failure during pruning and a failure in the Bit + /// Machine run after pruning with this variant. #[error("Failed to prune program: {0}")] Pruning(#[from] simplicityhl::simplicity::bit_machine::ExecutionError), @@ -39,7 +43,8 @@ pub enum ProgramError { #[error("Failed to construct a Bit Machine with enough space: {0}")] BitMachineCreation(#[from] simplicityhl::simplicity::bit_machine::LimitError), - /// Error thrown during bit machine execution due to underlying logical or environment validation errors. + /// The Bit Machine rejected the pruned program. [`Program`](super::Program) doesn't return + /// this variant; it reports such failures as `Pruning`. #[error("Failed to execute program on the Bit Machine: {0}")] Execution(simplicityhl::simplicity::bit_machine::ExecutionError), @@ -59,12 +64,13 @@ pub enum ProgramError { utxo_count: usize, }, - /// Error indicating the script pubkey present on the targeted UTXO differs from the expectation. + /// The input spends an output whose script isn't the program's. #[error("Script pubkey mismatch: expected hash {expected_hash}, got {actual_hash}")] ScriptPubkeyMismatch { - /// The expected CMR (Commitment Merkle Root) hash. + /// The HASH160 of the script of the program's address, not the SHA-256 that + /// [`Program::get_script_hash`](super::Program::get_script_hash) returns. expected_hash: String, - /// The actual CMR hash present in the script pubkey. + /// The HASH160 of the script of the spent output. actual_hash: String, }, diff --git a/crates/sdk/src/program/logger.rs b/crates/sdk/src/program/logger.rs index 0973be97..ffd7191e 100644 --- a/crates/sdk/src/program/logger.rs +++ b/crates/sdk/src/program/logger.rs @@ -32,10 +32,14 @@ pub struct ProgramInfo { pub trace_buffer: Vec, } -/// Logger state for buffering execution output. +/// Buffers program execution output, per input and per thread, until it is printed. /// -/// Buffers are flushed to stderr only on successful transaction finalization, -/// discarding output from intermediate estimation passes. +/// With [`Verbosity`](crate::global::Verbosity) `Debug` or `Trace`, every +/// [`ProgramTrait::execute`](super::ProgramTrait::execute) replaces its input's buffer, so only +/// the last run's output is kept. `Signer::finalize` and `estimate_fee` print the buffers after +/// their last signing pass, and `finalize_strict` does when it succeeds. Code that calls +/// `ProgramTrait::execute` or `Signer::sign_tx` directly must call [`ProgramLogger::flush_logs`] +/// to see the output. `Signer::sign_witnesses` doesn't run any program. pub struct ProgramLogger { /// A mapping of program ids to program logs pub logs: HashMap, diff --git a/crates/sdk/src/program/mod.rs b/crates/sdk/src/program/mod.rs index 6d9e4f5a..8c67340b 100644 --- a/crates/sdk/src/program/mod.rs +++ b/crates/sdk/src/program/mod.rs @@ -2,7 +2,7 @@ pub mod core; /// Error types and definitions for program compilation, manipulation, and execution failures. pub mod error; -/// Program execution's specific logger +/// Buffering of the output of program execution. pub mod logger; pub use core::{Program, ProgramFactory, ProgramSchema, ProgramTrait}; diff --git a/crates/sdk/src/utils.rs b/crates/sdk/src/utils.rs index 6bee5820..0a1c56f5 100644 --- a/crates/sdk/src/utils.rs +++ b/crates/sdk/src/utils.rs @@ -19,7 +19,8 @@ pub fn random_mnemonic() -> String { mnemonic.to_string() } -/// Generates a hardcoded "unspendable" Taproot public key. +/// The fixed Taproot internal key that [`Program`](crate::program::Program) uses by default, +/// which nobody has the private key for. /// /// # Panics /// Panics if the hardcoded byte slice is not exactly 32 bytes or cannot be parsed into a valid `XOnlyPublicKey` (which should never happen statically). @@ -32,7 +33,7 @@ pub fn tr_unspendable_key() -> secp256k1::XOnlyPublicKey { .expect("key should be valid") } -/// Calculates new sha256 midstate and binds generated entropy to the specific outpoint. +/// The entropy of an asset issued by the input spending `outpoint`, with contract hash `entropy`. #[must_use] pub fn asset_entropy(outpoint: &OutPoint, entropy: [u8; 32]) -> AssetEntropy { let contract_hash = ContractHash::from_byte_array(entropy); @@ -52,13 +53,14 @@ pub fn tagged_hash(tag: &str, data: &[u8]) -> sha256::Hash { sha256::Hash::from_engine(eng) } -/// Hashes arbitrary data with and additional `TapData` and tags. +/// `tagged_hash("TapData", data)`, the hash of a storage slot's hidden leaf in a program's +/// Taproot tree. #[must_use] pub fn tap_data_hash(data: &[u8]) -> sha256::Hash { tagged_hash("TapData", data) } -/// Computes the SHA-256 hash of a given script and returns the resulting 32-byte array. +/// The SHA-256 hash of the script's bytes, the `scripthash` that Esplora indexes outputs by. #[must_use] pub fn hash_script(script: &Script) -> [u8; 32] { sha256::Hash::hash(script.as_bytes()).to_byte_array() @@ -70,7 +72,7 @@ pub fn sat2btc(sat: u64) -> f64 { bitcoin::Amount::from_sat(sat).to_btc() } -/// Converts a whole BTC amount into Satoshi. +/// Converts a whole number of coins into satoshis. #[must_use] pub fn btc2sat(btc: u64) -> u64 { bitcoin::Amount::from_int_btc(btc).to_sat()