diff --git a/Cargo.lock b/Cargo.lock index d8577e22d..7526e0622 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1887,6 +1887,7 @@ dependencies = [ "clap_complete", "clienter", "console-subscriber", + "const_format", "crossterm 0.27.0", "deranged 0.4.0", "directories", diff --git a/Cargo.toml b/Cargo.toml index c2c1b3674..77886e2e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,6 +130,7 @@ humantime = "2.1.0" # # Once the issue is resolved, this unused dependency can be removed. deranged = "=0.4.0" +const_format = "0.2.34" [dev-dependencies] diff --git a/scripts/php/find_slow_tests.php b/scripts/php/find_slow_tests.php new file mode 100755 index 000000000..447ba0256 --- /dev/null +++ b/scripts/php/find_slow_tests.php @@ -0,0 +1,99 @@ +#!/usr/bin/env php +&1 | tee tests.out +// sort_tests.php tests.out + +$file = @$argv[1]; + +if(!$file || !file_exists($file)) { + die("please provide a file with output of cargo test, eg:\n cargo +nightly test -- -Z unstable-options --report-time 2>&1 | tee tests.out"); +} + +$lines = file($file); + +$a = []; +foreach($lines as $line) { + $fields = explode(' ', $line); + $key = $fields[count($fields)-1]; + $key_new = trim(str_replace(['<', '>', 's'], '', $key)); + if($key_new != $key && is_numeric($key_new) && substr($line, 0, 5) == "test " && !strstr($line, "finished in")) { + $a[$line] = $key_new; + } else { +// echo "skipping $line"; + } +} + +arsort($a); + +foreach($a as $line => $time) { + echo $line ; +} + +echo "\n\n\n"; +analyze_float_array(array_values($a)); + + + +function analyze_float_array(array $numbers): void { + if (empty($numbers)) { + echo "Error: The input array is empty. Cannot calculate statistics.\n"; + return; + } + + // --- Min and Max --- + $min = min($numbers); + $max = max($numbers); + + // --- Mean (Average) --- + $sum = array_sum($numbers); + $count = count($numbers); + $mean = $sum / $count; + + // --- Median --- + // 1. Sort the array numerically. + sort($numbers); + + // 2. Determine the middle index(es). + $middleIndex = floor($count / 2); + + if ($count % 2 === 1) { + // Odd number of elements: median is the middle element. + $median = $numbers[$middleIndex]; + } else { + // Even number of elements: median is the average of the two middle elements. + $median = ($numbers[$middleIndex - 1] + $numbers[$middleIndex]) / 2; + } + + // --- Print Results --- + echo "--- unit test stats ---\n"; + echo "Count: " . $count . "\n"; + echo "Total: " . $sum . "\n"; + echo "Min: " . $min . "\n"; + echo "Max: " . $max . "\n"; + echo "Mean: " . $mean . "\n"; + echo "Median: " . $median . "\n"; + echo "----------------------\n"; +} + diff --git a/src/api/export.rs b/src/api/export.rs index 900f157df..ecfbf62b6 100644 --- a/src/api/export.rs +++ b/src/api/export.rs @@ -18,7 +18,7 @@ pub use crate::models::proof_abstractions::timestamp::Timestamp; pub use crate::models::state::transaction_details::TransactionDetails; pub use crate::models::state::transaction_kernel_id::TransactionKernelId; pub use crate::models::state::tx_creation_artifacts::TxCreationArtifacts; -pub use crate::models::state::tx_proving_capability::TxProvingCapability; +pub use crate::models::state::vm_proving_capability::VmProvingCapability; pub use crate::models::state::wallet::address::generation_address::GenerationSpendingKey; pub use crate::models::state::wallet::address::symmetric_key::SymmetricKey; pub use crate::models::state::wallet::address::KeyType; diff --git a/src/api/tx_initiation/builder/mod.rs b/src/api/tx_initiation/builder/mod.rs index e40be9c86..6831f05fc 100644 --- a/src/api/tx_initiation/builder/mod.rs +++ b/src/api/tx_initiation/builder/mod.rs @@ -74,18 +74,11 @@ //! .await?; //! drop(state_lock); // release lock asap. //! -//! // use cli options for building proof, but override proof-type -//! let options = TritonVmProofJobOptionsBuilder::new() -//! .template(&gsl.cli().into()) -//! .proof_type(TransactionProofType::PrimitiveWitness) -//! .build(); -//! //! // generate simplistic PrimitiveWitness "proof" //! // This exposes secrets, so tx cannot be broadcast until //! // proof is upgraded to ProofCollection. //! let proof = TransactionProofBuilder::new() //! .transaction_details(&tx_details) -//! .job_queue(vm_job_queue()) //! .proof_job_options(gsl.cli().into()) //! .build() //! .await?; @@ -127,17 +120,12 @@ //! //! # async fn example(tx_details: TransactionDetails, gsl: GlobalStateLock) -> anyhow::Result { //! -//! // specify target proof-type = SingleProof -//! let options = TritonVmProofJobOptionsBuilder::new() -//! .template(&gsl.cli().into()) -//! .proof_type(TransactionProofType::SingleProof) -//! .build(); -//! //! // This will take minutes even on a very powerful machine. //! let proof = TransactionProofBuilder::new() //! .transaction_details(&tx_details) +//! .transaction_proof_type(TransactionProofType::SingleProof) //! .job_queue(vm_job_queue()) -//! .proof_job_options(options) +//! .proof_job_options(gsl.cli().into()) //! .build() //! .await?; //! # Ok(proof) diff --git a/src/api/tx_initiation/builder/proof_builder.rs b/src/api/tx_initiation/builder/proof_builder.rs index d8b08741e..b01f66f17 100644 --- a/src/api/tx_initiation/builder/proof_builder.rs +++ b/src/api/tx_initiation/builder/proof_builder.rs @@ -196,18 +196,11 @@ impl<'a> ProofBuilder<'a> { // building a real proof. let nondeterminism = nondeterminism_callback(); - let proof_type = proof_job_options.job_settings.proof_type; - let capability = proof_job_options.job_settings.tx_proving_capability; - if !capability.can_prove(proof_type) { - return Err(CreateProofError::TooWeak { - proof_type, - capability, - }); - } - // this builder only supports proofs that can be executed in triton-vm. - if !proof_type.executes_in_vm() { - return Err(CreateProofError::NotVmProof(proof_type)); - } + proof_job_options + .job_settings + .vm_proving_capability + .check_if_capable_async(program.clone(), claim.clone(), nondeterminism.clone()) + .await?; let job_queue = job_queue.unwrap_or_else(vm_job_queue); diff --git a/src/api/tx_initiation/builder/transaction_proof_builder.rs b/src/api/tx_initiation/builder/transaction_proof_builder.rs index a073373b5..6fd4ef179 100644 --- a/src/api/tx_initiation/builder/transaction_proof_builder.rs +++ b/src/api/tx_initiation/builder/transaction_proof_builder.rs @@ -43,6 +43,8 @@ use crate::triton_vm::proof::Claim; use crate::triton_vm::vm::NonDeterminism; use crate::triton_vm_job_queue::vm_job_queue; use crate::triton_vm_job_queue::TritonVmJobQueue; +use crate::util_types::log_vm_state; +use crate::util_types::log_vm_state::LogProofInputsType; /// a builder for [TransactionProof] /// @@ -62,6 +64,7 @@ pub struct TransactionProofBuilder<'a> { job_queue: Option>, proof_job_options: Option, valid_mock: Option, + transaction_proof_type: Option, } impl<'a> TransactionProofBuilder<'a> { @@ -161,6 +164,14 @@ impl<'a> TransactionProofBuilder<'a> { self } + /// specify type of proof to build (optional) + /// + /// default = best proof possible based on [VmProvingCapability](crate::api::export::VmProvingCapability) + pub fn transaction_proof_type(mut self, transaction_proof_type: TransactionProofType) -> Self { + self.transaction_proof_type = Some(transaction_proof_type); + self + } + /// generate the proof. /// /// ## Required (one-of) @@ -241,16 +252,19 @@ impl<'a> TransactionProofBuilder<'a> { job_queue, proof_job_options, valid_mock, + transaction_proof_type, } = self; let proof_job_options = proof_job_options.ok_or(ProofRequirement::ProofJobOptions)?; + let transaction_proof_type = transaction_proof_type + .unwrap_or_else(|| proof_job_options.job_settings.vm_proving_capability.into()); let valid_mock = valid_mock.unwrap_or(true); let job_queue = job_queue.unwrap_or_else(vm_job_queue); // note: evaluation order must match order stated in the method doc-comment. - if proof_job_options.job_settings.proof_type.is_single_proof() { + if transaction_proof_type.is_single_proof() { // claim, nondeterminism --> single proof if let Some((c, nd)) = claim_and_nondeterminism { return gen_single(c, || nd, job_queue, proof_job_options, valid_mock).await; @@ -284,16 +298,37 @@ impl<'a> TransactionProofBuilder<'a> { // owned primitive witness --> proof_type if let Some(w) = primitive_witness { - return from_witness(Cow::Owned(w), job_queue, proof_job_options, valid_mock).await; + return from_witness( + Cow::Owned(w), + job_queue, + proof_job_options, + transaction_proof_type, + valid_mock, + ) + .await; } // primitive witness reference --> proof_type else if let Some(w) = primitive_witness_ref { - return from_witness(Cow::Borrowed(w), job_queue, proof_job_options, valid_mock).await; + return from_witness( + Cow::Borrowed(w), + job_queue, + proof_job_options, + transaction_proof_type, + valid_mock, + ) + .await; } // transaction_details --> proof_type else if let Some(d) = transaction_details { let w = d.primitive_witness(); - return from_witness(Cow::Owned(w), job_queue, proof_job_options, valid_mock).await; + return from_witness( + Cow::Owned(w), + job_queue, + proof_job_options, + transaction_proof_type, + valid_mock, + ) + .await; } Err(ProofRequirement::TransactionProofInput.into()) @@ -311,8 +346,17 @@ async fn gen_single<'a, F>( valid_mock: bool, ) -> Result where - F: FnOnce() -> NonDeterminism + Send + Sync + 'a, + F: Clone + FnOnce() -> NonDeterminism + Send + Sync + 'a, { + // log proof inputs if matching env var is set. (does not expose witness secrets) + // maybe_write() logs warning if error occurs; we ignore any error. + let _ = log_vm_state::maybe_write( + LogProofInputsType::DoesNotContainWalletSecrets, + SingleProof.program(), + &claim, + nondeterminism.clone(), + ); + Ok(TransactionProof::SingleProof( ProofBuilder::new() .program(SingleProof.program()) @@ -333,14 +377,12 @@ async fn from_witness( witness_cow: Cow<'_, PrimitiveWitness>, job_queue: Arc, proof_job_options: TritonVmProofJobOptions, + transaction_proof_type: TransactionProofType, valid_mock: bool, ) -> Result { - let capability = proof_job_options.job_settings.tx_proving_capability; - let proof_type = proof_job_options.job_settings.proof_type; - // generate mock proof, if network uses mock proofs. if proof_job_options.job_settings.network.use_mock_proof() { - let proof = match proof_type { + let proof = match transaction_proof_type { TransactionProofType::PrimitiveWitness => { TransactionProof::Witness(witness_cow.into_owned()) } @@ -356,16 +398,16 @@ async fn from_witness( return Ok(proof); } - // abort early if machine is too weak - if !capability.can_prove(proof_type) { - return Err(CreateProofError::TooWeak { - proof_type, - capability, - }); - } + // abort early if machine is too weak. + // + // note: the fallible calls below eventually call ProofBuilder::build() + // which would perform a more expensive verification. Since we know the + // transaction_proof_type here, we can perform this quick check. + let capability = proof_job_options.job_settings.vm_proving_capability; + capability.can_prove(transaction_proof_type)?; // produce proof of requested type - let transaction_proof = match proof_type { + let transaction_proof = match transaction_proof_type { TransactionProofType::PrimitiveWitness => { TransactionProof::Witness(witness_cow.into_owned()) } diff --git a/src/api/tx_initiation/builder/triton_vm_proof_job_options_builder.rs b/src/api/tx_initiation/builder/triton_vm_proof_job_options_builder.rs index 7b79b3e8f..ef165d197 100644 --- a/src/api/tx_initiation/builder/triton_vm_proof_job_options_builder.rs +++ b/src/api/tx_initiation/builder/triton_vm_proof_job_options_builder.rs @@ -1,10 +1,9 @@ //! This module implements a builder for [TritonVmProofJobOptions] use crate::config_models::network::Network; -use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::proof_abstractions::tasm::program::TritonVmProofJobOptions; use crate::models::proof_abstractions::tasm::prover_job::ProverJobSettings; -use crate::models::state::tx_proving_capability::TxProvingCapability; +use crate::models::state::vm_proving_capability::VmProvingCapability; use crate::triton_vm_job_queue::TritonVmJobPriority; /// a builder for [TritonVmProofJobOptions] @@ -27,7 +26,7 @@ use crate::triton_vm_job_queue::TritonVmJobPriority; /// let args = Args::default(); /// TritonVmProofJobOptionsBuilder::new() /// .template(&args.as_proof_job_options()) -/// .proof_type(TransactionProofType::SingleProof) +/// .proving_capability(TransactionProofType::SingleProof) /// .build(); /// ``` /// @@ -36,7 +35,7 @@ use crate::triton_vm_job_queue::TritonVmJobPriority; /// ``` /// use neptune_cash::api::export::TritonVmJobPriority; /// use neptune_cash::api::export::Network; -/// use neptune_cash::api::export::TxProvingCapability; +/// use neptune_cash::api::export::VmProvingCapability; /// use neptune_cash::api::export::TransactionProofType; /// use neptune_cash::api::tx_initiation::builder::triton_vm_proof_job_options_builder::TritonVmProofJobOptionsBuilder; /// @@ -45,10 +44,8 @@ use crate::triton_vm_job_queue::TritonVmJobPriority; /// TritonVmProofJobOptionsBuilder::new() /// .job_priority(TritonVmJobPriority::Normal) /// .cancel_job_rx(cancel_job_rx) -/// .max_log2_padded_height_for_proofs(23) // 2^23 /// .network(Network::Testnet) -/// .proving_capability(TxProvingCapability::ProofCollection) -/// .proof_type(TransactionProofType::PrimitiveWitness) +/// .proving_capability(TransactionProofType::ProofCollection) /// .build(); /// ``` #[derive(Debug, Default)] @@ -58,10 +55,8 @@ pub struct TritonVmProofJobOptionsBuilder { cancel_job_rx: Option>, // these are from ProverJobSettings - max_log2_padded_height_for_proofs: Option, network: Option, - tx_proving_capability: Option, - proof_type: Option, + vm_proving_capability: Option, } impl TritonVmProofJobOptionsBuilder { @@ -70,7 +65,7 @@ impl TritonVmProofJobOptionsBuilder { Default::default() } - /// add template to set default values of all fields. + /// add template to set default values of all fields. (optional) /// /// in particular cli_args::Args can be used for this purpose. /// @@ -84,7 +79,7 @@ impl TritonVmProofJobOptionsBuilder { /// let args = Args::default(); /// TritonVmProofJobOptionsBuilder::new() /// .template(&args.as_proof_job_options()) - /// .proof_type(TransactionProofType::SingleProof) + /// .proving_capability(TransactionProofType::SingleProof) /// .build(); /// ``` pub fn template(mut self, template: &TritonVmProofJobOptions) -> Self { @@ -93,18 +88,16 @@ impl TritonVmProofJobOptionsBuilder { self.prover_job_settings(&template.job_settings) } - /// add prover job settings + /// add prover job settings (optional) /// /// this will set all fields from [ProverJobSettings] at once. pub fn prover_job_settings(mut self, js: &ProverJobSettings) -> Self { - self.max_log2_padded_height_for_proofs = js.max_log2_padded_height_for_proofs; self.network = Some(js.network); - self.tx_proving_capability = Some(js.tx_proving_capability); - self.proof_type = Some(js.proof_type); + self.vm_proving_capability = Some(js.vm_proving_capability); self } - /// add job priority + /// add job priority (optional) /// /// see [TritonVmProofJobOptions::job_priority]. /// @@ -114,7 +107,7 @@ impl TritonVmProofJobOptionsBuilder { self } - /// add cancel_job_rx + /// add cancel_job_rx (optional) /// /// see [TritonVmProofJobOptions::cancel_job_rx]. /// @@ -125,6 +118,7 @@ impl TritonVmProofJobOptionsBuilder { /// ``` /// use neptune_cash::api::export::TransactionDetails; /// use neptune_cash::api::export::TransactionProof; + /// use neptune_cash::api::export::TransactionProofType; /// use neptune_cash::api::tx_initiation::builder::transaction_proof_builder::TransactionProofBuilder; /// use neptune_cash::api::tx_initiation::builder::triton_vm_proof_job_options_builder::TritonVmProofJobOptionsBuilder; /// use neptune_cash::triton_vm_job_queue::vm_job_queue; @@ -144,6 +138,7 @@ impl TritonVmProofJobOptionsBuilder { /// let build_future = /// TransactionProofBuilder::new() /// .transaction_details(&tx_details) + /// .transaction_proof_type(TransactionProofType::SingleProof) /// .job_queue(vm_job_queue()) /// .proof_job_options(options) /// .build(); @@ -166,17 +161,7 @@ impl TritonVmProofJobOptionsBuilder { self } - /// add max_log2_padded_height_for_proofs - /// - /// see [cli_args::Args::max_log2_padded_height_for_proofs](crate::config_models::cli_args::Args::max_log2_padded_height_for_proofs). - /// - /// default: None (no limit) - pub fn max_log2_padded_height_for_proofs(mut self, max: u8) -> Self { - self.max_log2_padded_height_for_proofs = Some(max); - self - } - - /// add network + /// add network (optional) /// /// default: [Network::default()] pub fn network(mut self, network: Network) -> Self { @@ -184,25 +169,17 @@ impl TritonVmProofJobOptionsBuilder { self } - /// specify the machine's proving capability. + /// specify the machine's proving capability. (optional) /// - /// It is important to set the device's [TxProvingCapability] so that weak + /// It is important to set the device's [VmProvingCapability] so that weak /// devices will not attempt to build proofs they are not capable of. /// - /// default: [TxProvingCapability::default()] - pub fn proving_capability(mut self, tx_proving_capability: TxProvingCapability) -> Self { - self.tx_proving_capability = Some(tx_proving_capability); - self - } - - /// specify the target proof type. - /// - /// Usually it is desirable build the best proof the hardware is capable of. - /// Therefore this field defaults to the proving-capability if not provided. - /// - /// default: [Self::proving_capability()] - pub fn proof_type(mut self, proof_type: TransactionProofType) -> Self { - self.proof_type = Some(proof_type); + /// default: [VmProvingCapability::default()] + pub fn proving_capability( + mut self, + vm_proving_capability: impl Into, + ) -> Self { + self.vm_proving_capability = Some(vm_proving_capability.into()); self } @@ -211,22 +188,17 @@ impl TritonVmProofJobOptionsBuilder { let Self { job_priority, cancel_job_rx, - max_log2_padded_height_for_proofs, network, - tx_proving_capability, - proof_type, + vm_proving_capability, } = self; let job_priority = job_priority.unwrap_or_default(); let network = network.unwrap_or_default(); - let tx_proving_capability = tx_proving_capability.unwrap_or_default(); - let proof_type = proof_type.unwrap_or(tx_proving_capability.into()); + let vm_proving_capability = vm_proving_capability.unwrap_or_default(); let job_settings = ProverJobSettings { - max_log2_padded_height_for_proofs, network, - tx_proving_capability, - proof_type, + vm_proving_capability, }; TritonVmProofJobOptions { diff --git a/src/api/tx_initiation/error.rs b/src/api/tx_initiation/error.rs index 664fb6f18..39bffcb81 100644 --- a/src/api/tx_initiation/error.rs +++ b/src/api/tx_initiation/error.rs @@ -7,9 +7,8 @@ use crate::api::export::NativeCurrencyAmount; use crate::api::export::RecordTransactionError; use crate::job_queue::errors::AddJobError; use crate::job_queue::errors::JobHandleError; -use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::proof_abstractions::tasm::prover_job::ProverJobError; -use crate::models::state::tx_proving_capability::TxProvingCapability; +use crate::models::state::vm_proving_capability::VmProvingCapabilityError; /// enumerates possible transaction send errors #[derive(Debug, Clone, thiserror::Error)] @@ -61,16 +60,8 @@ pub enum CreateProofError { #[error("missing required data to build proof: {0}")] MissingRequirement(#[from] ProofRequirement), - #[error( - "machine capability {capability} is insufficient to generate proof of type: {proof_type}" - )] - TooWeak { - proof_type: TransactionProofType, - capability: TxProvingCapability, - }, - - #[error("target proof type {0} is not a triton-vm proof.")] - NotVmProof(TransactionProofType), + #[error(transparent)] + VmProvingCapability(#[from] VmProvingCapabilityError), #[error(transparent)] AddJobError(#[from] AddJobError), diff --git a/src/api/tx_initiation/private.rs b/src/api/tx_initiation/private.rs index 288f5623f..29056a003 100644 --- a/src/api/tx_initiation/private.rs +++ b/src/api/tx_initiation/private.rs @@ -58,15 +58,10 @@ impl TransactionInitiatorPrivate { let capability = self.global_state_lock.cli().proving_capability(); let proof_type = TransactionProofType::ProofCollection; let network = self.global_state_lock.cli().network; - if !network.use_mock_proof() && !capability.can_prove(proof_type) { - tracing::warn!( - "Cannot initiate transaction because transaction proving capability is too weak." - ); - return Err(error::CreateProofError::TooWeak { - proof_type, - capability, - } - .into()); + if !network.use_mock_proof() { + capability + .can_prove(proof_type) + .map_err(error::CreateProofError::from)?; } self.check_rate_limit().await diff --git a/src/api/tx_initiation/send.rs b/src/api/tx_initiation/send.rs index eed65c951..f9f32142f 100644 --- a/src/api/tx_initiation/send.rs +++ b/src/api/tx_initiation/send.rs @@ -36,7 +36,6 @@ use crate::api::export::TransactionProofType; use crate::api::tx_initiation::builder::transaction_builder::TransactionBuilder; use crate::api::tx_initiation::builder::transaction_details_builder::TransactionDetailsBuilder; use crate::api::tx_initiation::builder::transaction_proof_builder::TransactionProofBuilder; -use crate::api::tx_initiation::builder::triton_vm_proof_job_options_builder::TritonVmProofJobOptionsBuilder; use crate::api::tx_initiation::builder::tx_artifacts_builder::TxCreationArtifactsBuilder; use crate::api::tx_initiation::builder::tx_input_list_builder::InputSelectionPolicy; use crate::api::tx_initiation::builder::tx_input_list_builder::TxInputListBuilder; @@ -47,7 +46,6 @@ use crate::models::proof_abstractions::timestamp::Timestamp; use crate::models::state::tx_creation_artifacts::TxCreationArtifacts; use crate::models::state::wallet::change_policy::ChangePolicy; use crate::models::state::StateLock; -use crate::triton_vm_job_queue::vm_job_queue; use crate::GlobalStateLock; /// provides a send() method to send a neptune transaction in one call. @@ -125,20 +123,12 @@ impl TransactionSender { tracing::info!("send: proving tx:\n{}", tx_details); let witness = tx_details.primitive_witness(); - let kernel = witness.kernel.clone(); - - // use cli options for building proof, but override proof-type - let options = TritonVmProofJobOptionsBuilder::new() - .template(&gsl.cli().as_proof_job_options()) - .proof_type(target_proof_type) - .build(); // generate proof let proof = TransactionProofBuilder::new() - .transaction_details(&tx_details) - .primitive_witness(witness) - .job_queue(vm_job_queue()) - .proof_job_options(options) + .primitive_witness_ref(&witness) + .transaction_proof_type(target_proof_type) + .proof_job_options(gsl.cli().into()) .build() .await?; @@ -146,7 +136,7 @@ impl TransactionSender { // create transaction let transaction = TransactionBuilder::new() - .transaction_kernel(kernel) + .transaction_kernel(witness.kernel) .transaction_proof(proof) .build()?; diff --git a/src/api/tx_initiation/test_util.rs b/src/api/tx_initiation/test_util.rs index c6372baa4..c3b2426d3 100644 --- a/src/api/tx_initiation/test_util.rs +++ b/src/api/tx_initiation/test_util.rs @@ -81,12 +81,10 @@ impl TransactionInitiatorInternal { drop(state_lock); let witness = tx_details.primitive_witness(); - let kernel = witness.kernel.clone(); // generate proof let proof = TransactionProofBuilder::new() - .transaction_details(&tx_details) - .primitive_witness(witness) + .primitive_witness_ref(&witness) .job_queue(tx_creation_config.job_queue()) .proof_job_options(tx_creation_config.proof_job_options()) .build() @@ -94,7 +92,7 @@ impl TransactionInitiatorInternal { // create transaction let transaction = TransactionBuilder::new() - .transaction_kernel(kernel) + .transaction_kernel(witness.kernel) .transaction_proof(proof) .build()?; diff --git a/src/bin/dashboard_src/overview_screen.rs b/src/bin/dashboard_src/overview_screen.rs index e232c5da8..89b4ac3c9 100644 --- a/src/bin/dashboard_src/overview_screen.rs +++ b/src/bin/dashboard_src/overview_screen.rs @@ -12,7 +12,7 @@ use neptune_cash::models::blockchain::block::block_header::BlockHeader; use neptune_cash::models::blockchain::block::block_height::BlockHeight; use neptune_cash::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount; use neptune_cash::models::state::mining_status::MiningStatus; -use neptune_cash::models::state::tx_proving_capability::TxProvingCapability; +use neptune_cash::models::state::vm_proving_capability::VmProvingCapability; use neptune_cash::prelude::twenty_first; use neptune_cash::rpc_auth; use neptune_cash::rpc_server::RPCClient; @@ -66,7 +66,7 @@ pub struct OverviewData { up_since: Option, cpu_load: Option, cpu_capacity: Option, - proving_capability: TxProvingCapability, + proving_capability: VmProvingCapability, /// CPU temperature in degrees Celsius cpu_temperature: Option, diff --git a/src/config_models/cli_args.rs b/src/config_models/cli_args.rs index ffd603b63..d7879c45f 100644 --- a/src/config_models/cli_args.rs +++ b/src/config_models/cli_args.rs @@ -11,14 +11,14 @@ use clap::builder::TypedValueParser; use clap::Parser; use itertools::Itertools; use num_traits::Zero; -use sysinfo::System; use super::fee_notification_policy::FeeNotificationPolicy; use super::network::Network; +use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount; use crate::models::proof_abstractions::tasm::program::TritonVmProofJobOptions; use crate::models::proof_abstractions::tasm::prover_job::ProverJobSettings; -use crate::models::state::tx_proving_capability::TxProvingCapability; +use crate::models::state::vm_proving_capability::VmProvingCapability; use crate::models::state::wallet::scan_mode_configuration::ScanModeConfiguration; use crate::triton_vm_job_queue::TritonVmJobPriority; @@ -247,25 +247,15 @@ pub struct Args { #[structopt(long, default_value = "3")] pub(crate) number_of_mps_per_utxo: usize, - /// Configure how complicated proofs this machine is capable of producing. - /// If no value is set, this parameter is estimated. For privacy, this level - /// must not be set to [`TxProvingCapability::LockScript`], as this leaks - /// information about amounts and input/output UTXOs. - /// - /// Proving the lockscripts is mandatory, since this is what prevents others - /// from spending your coins. - /// - /// e.g. `--tx-proving-capability=singleproof` or - /// `--tx-proving-capability=proofcollection`. - #[clap(long)] - pub tx_proving_capability: Option, + #[clap(long, long_help=VM_PROVING_CAPABILITY_HELP, value_parser = clap::value_parser!(VmProvingCapability))] + pub vm_proving_capability: Option, /// Cache for the proving capability. If the above parameter is not set, we /// want to estimate proving capability and afterwards reuse the result from /// previous estimations. This argument cannot be set from CLI, so clap /// ignores it. #[clap(skip)] - pub(crate) tx_proving_capability_cache: OnceLock, + pub(crate) vm_proving_capability_cache: OnceLock, /// The number of seconds between each attempt to upgrade transactions in /// the mempool to proofs of a higher quality. Will only run if the machine @@ -280,21 +270,6 @@ pub struct Args { #[structopt(long, name = "tokio-console", default_value = "false")] pub tokio_console: bool, - /// Sets the max program complexity limit for proof creation in Triton VM. - /// - /// Triton VM's prover complexity is a function of something called padded height - /// which is always a power of two. A basic proof has a complexity of 2^11. - /// A powerful machine in 2024 with 128 CPU cores can handle a padded height of 2^23. - /// - /// For such a machine, one would set a limit of 23. - /// - /// if the limit is reached while mining, a warning is logged and mining will pause. - /// non-mining operations may panic and halt neptune-core - /// - /// no limit is applied if unset. - #[structopt(long, short, value_parser = clap::value_parser!(u8).range(10..32))] - pub max_log2_padded_height_for_proofs: Option, - /// Sets the maximum number of proofs in a `ProofCollection` that can be /// recursively combined into a `SingleProof` by this machine. I.e. how big /// STARK proofs this machine can produce. @@ -408,6 +383,32 @@ pub struct Args { pub(crate) scan_keys: Option, } +const VM_PROVING_CAPABILITY_HELP: &str = const_format::formatcp!( + "\ +Specifies device's capability to generate TritonVM proofs + +If no value is set, this parameter is estimated based on available RAM +and number of CPU cores + +Triton VM's prover complexity is a function of something called padded +height which is always a power of two. A basic proof has a complexity of +2^15. A powerful machine with 128 CPU cores and at least 512Gb RAM can +handle a padded height of 2^23. + +source: https://talk.neptune.cash/t/performance-numbers-for-triton-vm-proving/69 + +For such a machine, one would set a limit of 23: +--vm-proving-capability 23 + +Minimum values by transaction-proof type: + primitive-witness: {} + proof-collection: {} + single-proof: {}", + TransactionProofType::PrimitiveWitness.log2_padded_height(), + TransactionProofType::ProofCollection.log2_padded_height(), + TransactionProofType::SingleProof.log2_padded_height() +); + impl Default for Args { fn default() -> Self { let empty: Vec = vec![]; @@ -520,10 +521,8 @@ impl Args { TritonVmProofJobOptions { job_priority, job_settings: ProverJobSettings { - max_log2_padded_height_for_proofs: self.max_log2_padded_height_for_proofs, network: self.network, - tx_proving_capability: self.proving_capability(), - proof_type: self.proving_capability().into(), + vm_proving_capability: self.proving_capability(), }, cancel_job_rx: None, } @@ -531,46 +530,16 @@ impl Args { /// Get the proving capability CLI argument or estimate it if it is not set. /// Cache the result so we don't estimate more than once. - pub fn proving_capability(&self) -> TxProvingCapability { - *self.tx_proving_capability_cache.get_or_init(|| { - if let Some(proving_capability) = self.tx_proving_capability { + pub fn proving_capability(&self) -> VmProvingCapability { + *self.vm_proving_capability_cache.get_or_init(|| { + if let Some(proving_capability) = self.vm_proving_capability { proving_capability - } else if self.compose { - TxProvingCapability::SingleProof } else { - Self::estimate_proving_capability() + VmProvingCapability::auto_detect() } }) } - fn estimate_proving_capability() -> TxProvingCapability { - const SINGLE_PROOF_CORE_REQ: usize = 19; - // see https://github.com/Neptune-Crypto/neptune-core/issues/426 - const SINGLE_PROOF_MEMORY_USAGE: u64 = (1u64 << 30) * 120; - - const PROOF_COLLECTION_CORE_REQ: usize = 2; - const PROOF_COLLECTION_MEMORY_USAGE: u64 = (1u64 << 30) * 16; - - let s = System::new_all(); - let total_memory = s.total_memory(); - assert!( - !total_memory.is_zero(), - "Total memory reported illegal value of 0" - ); - - let physical_core_count = s.physical_core_count().unwrap_or(1); - - if total_memory > SINGLE_PROOF_MEMORY_USAGE && physical_core_count > SINGLE_PROOF_CORE_REQ { - TxProvingCapability::SingleProof - } else if total_memory > PROOF_COLLECTION_MEMORY_USAGE - && physical_core_count > PROOF_COLLECTION_CORE_REQ - { - TxProvingCapability::ProofCollection - } else { - TxProvingCapability::PrimitiveWitness - } - } - /// creates a `TritonVmProofJobOptions` from cli args. pub fn as_proof_job_options(&self) -> TritonVmProofJobOptions { self.into() @@ -582,10 +551,8 @@ impl From<&Args> for TritonVmProofJobOptions { Self { job_priority: Default::default(), job_settings: ProverJobSettings { - max_log2_padded_height_for_proofs: cli.max_log2_padded_height_for_proofs, network: cli.network, - tx_proving_capability: cli.proving_capability(), - proof_type: cli.proving_capability().into(), + vm_proving_capability: cli.proving_capability(), }, cancel_job_rx: None, } @@ -609,19 +576,6 @@ mod tests { ..Default::default() } } - - pub(crate) fn proof_job_options_prooftype( - &self, - proof_type: TransactionProofType, - ) -> TritonVmProofJobOptions { - let mut options: TritonVmProofJobOptions = self.into(); - options.job_settings.proof_type = proof_type; - options - } - - pub(crate) fn proof_job_options_primitive_witness(&self) -> TritonVmProofJobOptions { - self.proof_job_options_prooftype(TransactionProofType::PrimitiveWitness) - } } #[test] @@ -658,20 +612,14 @@ mod tests { assert!(args.disallow_all_incoming_peer_connections()); } - #[test] - fn estimate_own_proving_capability() { - // doubles as a no-crash test - println!("{}", Args::estimate_proving_capability()); - } - #[test] fn cli_args_can_differ_about_proving_capability() { let a = Args { - tx_proving_capability: Some(TxProvingCapability::ProofCollection), + vm_proving_capability: Some(TransactionProofType::ProofCollection.into()), ..Default::default() }; let b = Args { - tx_proving_capability: Some(TxProvingCapability::SingleProof), + vm_proving_capability: Some(TransactionProofType::SingleProof.into()), ..Default::default() }; assert_ne!(a.proving_capability(), b.proving_capability()); diff --git a/src/main_loop.rs b/src/main_loop.rs index 7bc809511..815331f4c 100644 --- a/src/main_loop.rs +++ b/src/main_loop.rs @@ -36,6 +36,7 @@ use crate::models::blockchain::block::block_header::BlockHeader; use crate::models::blockchain::block::block_height::BlockHeight; use crate::models::blockchain::block::difficulty_control::ProofOfWork; use crate::models::blockchain::block::Block; +use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::transaction::Transaction; use crate::models::blockchain::transaction::TransactionProof; use crate::models::channel::MainToMiner; @@ -52,7 +53,6 @@ use crate::models::proof_abstractions::tasm::program::TritonVmProofJobOptions; use crate::models::state::block_proposal::BlockProposal; use crate::models::state::mempool::TransactionOrigin; use crate::models::state::networking_state::SyncAnchor; -use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::GlobalState; use crate::models::state::GlobalStateLock; use crate::triton_vm_job_queue::vm_job_queue; @@ -1324,7 +1324,10 @@ impl MainLoopHandler { .as_ref() .is_some_and(|x| !x.is_finished()); Ok(global_state.net.sync_anchor.is_none() - && global_state.proving_capability() == TxProvingCapability::SingleProof + && global_state + .proving_capability() + .can_prove(TransactionProofType::SingleProof) + .is_ok() && !previous_upgrade_task_is_still_running && tx_upgrade_interval .is_some_and(|upgrade_interval| duration_since_last_upgrade > upgrade_interval)) @@ -1365,8 +1368,12 @@ impl MainLoopHandler { // a long time (minutes), so we spawn a task for this such that we do // not block the main loop. let vm_job_queue = vm_job_queue(); - let perform_ms_update_if_needed = - self.global_state_lock.cli().proving_capability() == TxProvingCapability::SingleProof; + let perform_ms_update_if_needed = self + .global_state_lock + .cli() + .proving_capability() + .can_prove(TransactionProofType::SingleProof) + .is_ok(); let global_state_lock_clone = self.global_state_lock.clone(); let main_to_peer_broadcast_tx_clone = self.main_to_peer_broadcast_tx.clone(); @@ -2154,7 +2161,7 @@ mod tests { async fn tx_no_outputs( global_state_lock: &mut GlobalStateLock, - tx_proof_type: TxProvingCapability, + tx_proof_type: TransactionProofType, fee: NativeCurrencyAmount, ) -> Arc { let change_key = global_state_lock @@ -2198,7 +2205,7 @@ mod tests { // Force instance to create SingleProofs, otherwise CI and other // weak machines fail. let mocked_cli = cli_args::Args { - tx_proving_capability: Some(TxProvingCapability::SingleProof), + vm_proving_capability: Some(TransactionProofType::SingleProof.into()), tx_proof_upgrade_interval: 100, // seconds ..Default::default() }; @@ -2221,7 +2228,7 @@ mod tests { let fee = NativeCurrencyAmount::coins(1); let proof_collection_tx = tx_no_outputs( &mut main_loop_handler.global_state_lock, - TxProvingCapability::ProofCollection, + TransactionProofType::ProofCollection, fee, ) .await; diff --git a/src/main_loop/proof_upgrader.rs b/src/main_loop/proof_upgrader.rs index 58f353a07..a729517fc 100644 --- a/src/main_loop/proof_upgrader.rs +++ b/src/main_loop/proof_upgrader.rs @@ -13,7 +13,6 @@ use tracing::warn; use super::TransactionOrigin; use crate::api::tx_initiation::builder::transaction_proof_builder::TransactionProofBuilder; -use crate::api::tx_initiation::builder::triton_vm_proof_job_options_builder::TritonVmProofJobOptionsBuilder; use crate::config_models::fee_notification_policy::FeeNotificationPolicy; use crate::config_models::network::Network; use crate::models::blockchain::block::block_height::BlockHeight; @@ -31,7 +30,6 @@ use crate::models::proof_abstractions::tasm::program::TritonVmProofJobOptions; use crate::models::proof_abstractions::timestamp::Timestamp; use crate::models::state::transaction_details::TransactionDetails; use crate::models::state::transaction_kernel_id::TransactionKernelId; -use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::wallet::address::SpendingKey; use crate::models::state::wallet::expected_utxo::ExpectedUtxo; use crate::models::state::wallet::expected_utxo::UtxoNotifier; @@ -142,23 +140,22 @@ impl UpgradeJob { /// be used for transactions that originate locally. pub(super) fn from_primitive_witness( network: Network, - tx_proving_capability: TxProvingCapability, + tx_proof_type: impl Into, primitive_witness: PrimitiveWitness, ) -> UpgradeJob { - match tx_proving_capability { - TxProvingCapability::ProofCollection => { + match tx_proof_type.into() { + TransactionProofType::ProofCollection => { UpgradeJob::PrimitiveWitnessToProofCollection { primitive_witness } } - TxProvingCapability::SingleProof => { + TransactionProofType::SingleProof => { UpgradeJob::PrimitiveWitnessToSingleProof { primitive_witness } } - TxProvingCapability::PrimitiveWitness if network.use_mock_proof() => { + TransactionProofType::PrimitiveWitness if network.use_mock_proof() => { UpgradeJob::PrimitiveWitnessToSingleProof { primitive_witness } } - TxProvingCapability::PrimitiveWitness => { + TransactionProofType::PrimitiveWitness => { panic!("Client cannot have primitive witness capability only") } - TxProvingCapability::LockScript => todo!("TODO: Add support for this"), } } @@ -364,12 +361,6 @@ impl UpgradeJob { Err(e) => { error!("UpgradeProof job failed. error: {e}"); error!("upgrading of witness or proof in {tx_origin} transaction failed."); - error!( - "Consider lowering your proving capability to {}, in case it is set higher.\nCurrent proving \ - capability is set to: {}.", - TxProvingCapability::ProofCollection, - global_state_lock.cli().proving_capability() - ); return; } }; @@ -564,16 +555,11 @@ impl UpgradeJob { vec![] }; - // ensure that proof-type is SingleProof - let options = TritonVmProofJobOptionsBuilder::new() - .template(&proof_job_options) - .proof_type(TransactionProofType::SingleProof) - .build(); - let proof = TransactionProofBuilder::new() .primitive_witness_ref(&gobbler_witness) + .transaction_proof_type(TransactionProofType::SingleProof) .job_queue(triton_vm_job_queue.clone()) - .proof_job_options(options) + .proof_job_options(proof_job_options.clone()) .build() .await?; @@ -597,6 +583,7 @@ impl UpgradeJob { let single_proof = TransactionProofBuilder::new() .single_proof_witness(&single_proof_witness) + .transaction_proof_type(TransactionProofType::SingleProof) .job_queue(triton_vm_job_queue.clone()) .proof_job_options(proof_job_options.clone()) .build() @@ -672,17 +659,12 @@ impl UpgradeJob { UpgradeJob::PrimitiveWitnessToProofCollection { primitive_witness: witness, } => { - // ensure that proof-type is ProofCollection - let options = TritonVmProofJobOptionsBuilder::new() - .template(&proof_job_options) - .proof_type(TransactionProofType::ProofCollection) - .build(); - info!("Proof-upgrader: Start producing proof collection"); let proof_collection = TransactionProofBuilder::new() .primitive_witness_ref(&witness) + .transaction_proof_type(TransactionProofType::ProofCollection) .job_queue(triton_vm_job_queue.clone()) - .proof_job_options(options) + .proof_job_options(proof_job_options) .build() .await?; info!("Proof-upgrader, proof collection: Done"); @@ -698,17 +680,12 @@ impl UpgradeJob { UpgradeJob::PrimitiveWitnessToSingleProof { primitive_witness: witness, } => { - // ensure that proof-type is SingleProof - let options = TritonVmProofJobOptionsBuilder::new() - .template(&proof_job_options) - .proof_type(TransactionProofType::SingleProof) - .build(); - info!("Proof-upgrader: Start producing single proof"); let proof = TransactionProofBuilder::new() .primitive_witness_ref(&witness) + .transaction_proof_type(TransactionProofType::SingleProof) .job_queue(triton_vm_job_queue.clone()) - .proof_job_options(options) + .proof_job_options(proof_job_options) .build() .await?; @@ -862,7 +839,7 @@ mod tests { async fn transaction_from_state( mut state: GlobalStateLock, seed: u64, - proof_quality: TxProvingCapability, + proof_quality: TransactionProofType, fee: NativeCurrencyAmount, ) -> Arc { let mut rng: StdRng = SeedableRng::seed_from_u64(seed); @@ -909,7 +886,7 @@ mod tests { let pc_tx_low_fee = transaction_from_state( alice.clone(), 512777439428, - TxProvingCapability::ProofCollection, + TransactionProofType::ProofCollection, NativeCurrencyAmount::from_nau(2), ) .await; @@ -932,7 +909,7 @@ mod tests { let pc_tx_high_fee = transaction_from_state( alice.clone(), 512777439428, - TxProvingCapability::ProofCollection, + TransactionProofType::ProofCollection, NativeCurrencyAmount::from_nau(1_000_000_000), ) .await; @@ -958,11 +935,11 @@ mod tests { let network = Network::Main; for proving_capability in [ - TxProvingCapability::ProofCollection, - TxProvingCapability::SingleProof, + TransactionProofType::ProofCollection, + TransactionProofType::SingleProof, ] { let mut cli = cli_args::Args::default_with_network(network); - cli.tx_proving_capability = Some(proving_capability); + cli.vm_proving_capability = Some(proving_capability.into()); // Alice is premine recipient, so she can make a transaction (after // expiry of timelock). @@ -971,7 +948,7 @@ mod tests { let pwtx = transaction_from_state( alice.clone(), 512777439428, - TxProvingCapability::PrimitiveWitness, + TransactionProofType::PrimitiveWitness, NativeCurrencyAmount::from_nau(100), ) .await; @@ -1015,13 +992,12 @@ mod tests { .unwrap() .to_owned(); match proving_capability { - TxProvingCapability::LockScript => unreachable!(), - TxProvingCapability::PrimitiveWitness => unreachable!(), - TxProvingCapability::ProofCollection => assert!( + TransactionProofType::PrimitiveWitness => unreachable!(), + TransactionProofType::ProofCollection => assert!( matches!(mempool_tx.proof, TransactionProof::ProofCollection(_)), "Tx in mempool must be backed with {proving_capability} after upgrade" ), - TxProvingCapability::SingleProof => assert!( + TransactionProofType::SingleProof => assert!( matches!(mempool_tx.proof, TransactionProof::SingleProof(_)), "Tx in mempool must be backed with {proving_capability} after upgrade" ), @@ -1037,11 +1013,11 @@ mod tests { let network = Network::Main; for proving_capability in [ - TxProvingCapability::ProofCollection, - TxProvingCapability::SingleProof, + TransactionProofType::ProofCollection, + TransactionProofType::SingleProof, ] { let mut cli = cli_args::Args::default_with_network(network); - cli.tx_proving_capability = Some(proving_capability); + cli.vm_proving_capability = Some(proving_capability.into()); // Alice is premine recipient, so she can make a transaction (after // expiry of timelock). @@ -1050,7 +1026,7 @@ mod tests { let pwtx = transaction_from_state( alice.clone(), 512777439429, - TxProvingCapability::PrimitiveWitness, + TransactionProofType::PrimitiveWitness, NativeCurrencyAmount::from_nau(100), ) .await; @@ -1103,13 +1079,12 @@ mod tests { .unwrap() .to_owned(); match proving_capability { - TxProvingCapability::LockScript => unreachable!(), - TxProvingCapability::PrimitiveWitness => unreachable!(), - TxProvingCapability::ProofCollection => assert!( + TransactionProofType::PrimitiveWitness => unreachable!(), + TransactionProofType::ProofCollection => assert!( matches!(mempool_tx.proof, TransactionProof::ProofCollection(_)), "Tx in mempool must be backed with {proving_capability} after upgrade" ), - TxProvingCapability::SingleProof => assert!( + TransactionProofType::SingleProof => assert!( matches!(mempool_tx.proof, TransactionProof::SingleProof(_)), "Tx in mempool must be backed with {proving_capability} after upgrade" ), @@ -1138,7 +1113,7 @@ mod tests { let mut rng: StdRng = StdRng::seed_from_u64(512777439429); let cli_args = cli_args::Args { network, - tx_proving_capability: Some(TxProvingCapability::SingleProof), + vm_proving_capability: Some(TransactionProofType::SingleProof.into()), ..Default::default() }; let mut alice = state_with_premine_and_self_mined_blocks(cli_args, &mut rng, 1).await; @@ -1149,7 +1124,7 @@ mod tests { let single_proof_tx = transaction_from_state( alice.clone(), rng.random(), - TxProvingCapability::SingleProof, + TransactionProofType::SingleProof, tx_fee, ) .await; diff --git a/src/mine_loop.rs b/src/mine_loop.rs index d0d466680..57c8371a5 100644 --- a/src/mine_loop.rs +++ b/src/mine_loop.rs @@ -30,7 +30,6 @@ use twenty_first::math::digest::Digest; use crate::api::export::TxInputList; use crate::api::tx_initiation::builder::transaction_builder::TransactionBuilder; use crate::api::tx_initiation::builder::transaction_proof_builder::TransactionProofBuilder; -use crate::api::tx_initiation::builder::triton_vm_proof_job_options_builder::TritonVmProofJobOptionsBuilder; use crate::api::tx_initiation::error::CreateProofError; use crate::config_models::network::Network; use crate::job_queue::errors::JobHandleError; @@ -90,6 +89,7 @@ async fn compose_block( &global_state_lock, timestamp, job_options.clone(), + TransactionProofType::SingleProof, ) .await?; @@ -414,6 +414,7 @@ pub(crate) async fn make_coinbase_transaction_stateless( timestamp: Timestamp, vm_job_queue: Arc, job_options: TritonVmProofJobOptions, + transaction_proof_type: TransactionProofType, ) -> Result<(Transaction, TxOutputList)> { let (composer_outputs, transaction_details) = prepare_coinbase_transaction_stateless( latest_block, @@ -436,6 +437,7 @@ pub(crate) async fn make_coinbase_transaction_stateless( let proof = TransactionProofBuilder::new() .transaction_details(&transaction_details) .primitive_witness(witness) + .transaction_proof_type(transaction_proof_type) .job_queue(vm_job_queue) .proof_job_options(job_options) .build() @@ -580,6 +582,7 @@ pub(crate) async fn create_block_transaction( global_state_lock: &GlobalStateLock, timestamp: Timestamp, job_options: TritonVmProofJobOptions, + transaction_proof_type: TransactionProofType, ) -> Result<(Transaction, Vec)> { create_block_transaction_from( predecessor_block, @@ -587,6 +590,7 @@ pub(crate) async fn create_block_transaction( timestamp, job_options, TxMergeOrigin::Mempool, + transaction_proof_type, ) .await } @@ -597,6 +601,7 @@ pub(crate) async fn create_block_transaction_from( timestamp: Timestamp, job_options: TritonVmProofJobOptions, tx_merge_origin: TxMergeOrigin, + transaction_proof_type: TransactionProofType, ) -> Result<(Transaction, Vec)> { let block_capacity_for_transactions = SIZE_20MB_IN_BYTES; @@ -621,6 +626,7 @@ pub(crate) async fn create_block_transaction_from( timestamp, vm_job_queue.clone(), job_options.clone(), + transaction_proof_type, ) .await?; @@ -651,16 +657,11 @@ pub(crate) async fn create_block_transaction_from( ); let nop = PrimitiveWitness::from_transaction_details(&nop); - // ensure that proof-type is SingleProof - let options = TritonVmProofJobOptionsBuilder::new() - .template(&job_options) - .proof_type(TransactionProofType::SingleProof) - .build(); - let proof = TransactionProofBuilder::new() .primitive_witness_ref(&nop) + .transaction_proof_type(TransactionProofType::SingleProof) .job_queue(vm_job_queue.clone()) - .proof_job_options(options) + .proof_job_options(job_options.clone()) .build() .await?; let nop = Transaction { @@ -1080,6 +1081,7 @@ pub(crate) mod tests { use crate::job_queue::errors::JobHandleError; use crate::models::blockchain::block::mock_block_generator::MockBlockGenerator; use crate::models::blockchain::block::validity::block_primitive_witness::tests::deterministic_block_primitive_witness; + use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::transaction::validity::single_proof::SingleProof; use crate::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount; use crate::models::proof_abstractions::mast_hash::MastHash; @@ -1087,7 +1089,6 @@ pub(crate) mod tests { use crate::models::proof_abstractions::verifier::verify; use crate::models::state::mempool::TransactionOrigin; use crate::models::state::tx_creation_config::TxCreationConfig; - use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::wallet::address::symmetric_key::SymmetricKey; use crate::models::state::wallet::transaction_output::TxOutput; use crate::models::state::wallet::wallet_entropy::WalletEntropy; @@ -1111,6 +1112,7 @@ pub(crate) mod tests { global_state_lock: &GlobalStateLock, timestamp: Timestamp, job_options: TritonVmProofJobOptions, + transaction_proof_type: TransactionProofType, ) -> Result<(Transaction, Vec)> { // It's important to use the input `latest_block` here instead of // reading it from state, since that could, because of a race condition @@ -1129,6 +1131,7 @@ pub(crate) mod tests { timestamp, vm_job_queue, job_options, + transaction_proof_type, ) .await?; @@ -1234,9 +1237,8 @@ pub(crate) mod tests { &genesis_block, &global_state_lock, network.launch_date(), - global_state_lock - .cli() - .proof_job_options_primitive_witness(), + global_state_lock.cli().into(), + TransactionProofType::PrimitiveWitness, ) .await .unwrap(); @@ -1293,7 +1295,7 @@ pub(crate) mod tests { ); let config = TxCreationConfig::default() .recover_change_off_chain(alice_key.into()) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let tx_from_alice = alice .api() .tx_initiator_internal() @@ -1322,7 +1324,8 @@ pub(crate) mod tests { &genesis_block, &alice, now, - (TritonVmJobPriority::Normal, None).into(), + TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap() @@ -1389,7 +1392,8 @@ pub(crate) mod tests { &genesis_block, &alice, now, - (TritonVmJobPriority::Normal, None).into(), + TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap() @@ -1437,7 +1441,7 @@ pub(crate) mod tests { // force SingleProof capability. let cli = cli_args::Args { - tx_proving_capability: Some(TxProvingCapability::SingleProof), + vm_proving_capability: Some(TransactionProofType::SingleProof.into()), network, ..Default::default() }; @@ -1514,9 +1518,8 @@ pub(crate) mod tests { &tip_block_orig, &global_state_lock, launch_date, - global_state_lock - .cli() - .proof_job_options_primitive_witness(), + global_state_lock.cli().into(), + TransactionProofType::PrimitiveWitness, ) .await .unwrap(); @@ -1597,9 +1600,8 @@ pub(crate) mod tests { &tip_block_orig, &global_state_lock, ten_seconds_ago, - global_state_lock - .cli() - .proof_job_options_primitive_witness(), + global_state_lock.cli().into(), + TransactionProofType::PrimitiveWitness, ) .await .unwrap(); @@ -1917,9 +1919,8 @@ pub(crate) mod tests { &genesis_block, &global_state_lock, launch_date, - global_state_lock - .cli() - .proof_job_options_primitive_witness(), + global_state_lock.cli().into(), + TransactionProofType::PrimitiveWitness, ) .await .unwrap(); @@ -2048,9 +2049,8 @@ pub(crate) mod tests { &genesis_block, &global_state_lock, launch_date, - global_state_lock - .cli() - .proof_job_options_primitive_witness(), + global_state_lock.cli().into(), + TransactionProofType::PrimitiveWitness, ) .await .unwrap(); @@ -2162,8 +2162,7 @@ pub(crate) mod tests { let compose_task = async move { let genesis_block = Block::genesis(network); let gsl = global_state_lock.clone(); - let cli = &cli_args; - let mut job_options: TritonVmProofJobOptions = cli.into(); + let mut job_options: TritonVmProofJobOptions = TritonVmJobPriority::Normal.into(); job_options.cancel_job_rx = Some(cancel_job_rx); create_block_transaction_from( &genesis_block, @@ -2171,12 +2170,24 @@ pub(crate) mod tests { Timestamp::now(), job_options, TxMergeOrigin::Mempool, + TransactionProofType::SingleProof, ) .await }; + // ensure that the compose_task will end after 10 secs + // even if cancellation msg is not received somehow. + let compose_task_or_timeout = async move { + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(10)) => { + panic!("job timed out instead of cancelling") + }, + r = compose_task => r, + } + }; + // start the task running - let jh = tokio::task::spawn(compose_task); + let jh = tokio::task::spawn(compose_task_or_timeout); // wait a little while for a job to get added to the queue. tokio::time::sleep(std::time::Duration::from_secs(3)).await; @@ -2223,6 +2234,7 @@ pub(crate) mod tests { let cli_args = cli_args::Args { compose: true, network, + vm_proving_capability: Some(TransactionProofType::SingleProof.into()), ..Default::default() }; let global_state_lock = diff --git a/src/models/blockchain/block/mod.rs b/src/models/blockchain/block/mod.rs index e02f0c426..36428764b 100644 --- a/src/models/blockchain/block/mod.rs +++ b/src/models/blockchain/block/mod.rs @@ -1119,12 +1119,12 @@ pub(crate) mod tests { use crate::mine_loop::prepare_coinbase_transaction_stateless; use crate::mine_loop::tests::make_coinbase_transaction_from_state; use crate::models::blockchain::transaction::primitive_witness::PrimitiveWitness; + use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::transaction::TransactionProof; use crate::models::blockchain::type_scripts::native_currency::NativeCurrency; use crate::models::blockchain::type_scripts::TypeScript; use crate::models::state::mempool::TransactionOrigin; use crate::models::state::tx_creation_config::TxCreationConfig; - use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::wallet::address::KeyType; use crate::models::state::wallet::transaction_output::TxOutput; use crate::models::state::wallet::wallet_entropy::WalletEntropy; @@ -1464,6 +1464,7 @@ pub(crate) mod tests { use super::*; use crate::mine_loop::tests::make_coinbase_transaction_from_state; + use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::state::tx_creation_config::TxCreationConfig; use crate::models::state::wallet::address::KeyType; use crate::tests::shared::fake_valid_successor_for_tests; @@ -1522,7 +1523,8 @@ pub(crate) mod tests { &block1, &alice, plus_eight_months, - (TritonVmJobPriority::Normal, None).into(), + TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap(); @@ -1540,7 +1542,7 @@ pub(crate) mod tests { let outputs = vec![output_to_self.clone(); i]; let config2 = TxCreationConfig::default() .recover_change_on_chain(alice_key) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let tx2 = alice .api() .tx_initiator_internal() @@ -1583,13 +1585,14 @@ pub(crate) mod tests { &block2_without_valid_pow, &alice, plus_nine_months, - (TritonVmJobPriority::Normal, None).into(), + TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap(); let config3 = TxCreationConfig::default() .recover_change_on_chain(alice_key) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let tx3 = alice .api() .tx_initiator_internal() @@ -1877,7 +1880,7 @@ pub(crate) mod tests { let fee = NativeCurrencyAmount::coins(1); let config1 = TxCreationConfig::default() .recover_change_on_chain(alice_key.into()) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let tx1 = alice .api() .tx_initiator_internal() @@ -1896,7 +1899,7 @@ pub(crate) mod tests { let config2 = TxCreationConfig::default() .recover_change_on_chain(alice_key.into()) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let tx2 = alice .api() .tx_initiator_internal() @@ -1982,7 +1985,8 @@ pub(crate) mod tests { &blocks[i - 1], &alice, launch_date, - TritonVmProofJobOptions::from((TritonVmJobPriority::Normal, None)), + TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap(); @@ -2012,7 +2016,7 @@ pub(crate) mod tests { .into(); let config = TxCreationConfig::default() .recover_change_on_chain(change_key.into()) - .with_prover_capability(TxProvingCapability::SingleProof) + .with_prover_capability(TransactionProofType::SingleProof) .use_job_queue(job_queue.clone()); let transaction_creation_artifacts = alice .api() diff --git a/src/models/blockchain/block/validity/block_program.rs b/src/models/blockchain/block/validity/block_program.rs index 3ff97643c..e4624216d 100644 --- a/src/models/blockchain/block/validity/block_program.rs +++ b/src/models/blockchain/block/validity/block_program.rs @@ -348,6 +348,7 @@ pub(crate) mod tests { use crate::models::blockchain::block::validity::block_primitive_witness::tests::deterministic_block_primitive_witness; use crate::models::blockchain::block::Block; use crate::models::blockchain::block::TritonVmProofJobOptions; + use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::transaction::Transaction; use crate::models::proof_abstractions::tasm::builtins as tasm; use crate::models::proof_abstractions::tasm::builtins::verify_stark; @@ -356,7 +357,6 @@ pub(crate) mod tests { use crate::models::proof_abstractions::timestamp::Timestamp; use crate::models::proof_abstractions::SecretWitness; use crate::models::state::tx_creation_config::TxCreationConfig; - use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::wallet::transaction_output::TxOutput; use crate::models::state::wallet::wallet_entropy::WalletEntropy; use crate::tests::shared::mock_genesis_global_state; @@ -477,6 +477,7 @@ pub(crate) mod tests { timestamp, TritonVmProofJobOptions::default(), TxMergeOrigin::ExplicitList(vec![tx]), + TransactionProofType::SingleProof, ) .await .unwrap(); @@ -515,7 +516,7 @@ pub(crate) mod tests { let now = genesis_block.header().timestamp + Timestamp::months(12); let config = TxCreationConfig::default() .recover_change_off_chain(alice_key.into()) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let tx: Transaction = alice .api() .tx_initiator_internal() diff --git a/src/models/blockchain/transaction/mod.rs b/src/models/blockchain/transaction/mod.rs index 5964614c1..834b9757c 100644 --- a/src/models/blockchain/transaction/mod.rs +++ b/src/models/blockchain/transaction/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use crate::api::tx_initiation::builder::transaction_proof_builder::TransactionProofBuilder; use crate::config_models::network::Network; use crate::models::blockchain::block::mutator_set_update::MutatorSetUpdate; +use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::proof_abstractions::mast_hash::MastHash; use crate::models::proof_abstractions::tasm::program::TritonVmProofJobOptions; use crate::models::proof_abstractions::timestamp::Timestamp; @@ -176,6 +177,7 @@ impl Transaction { info!("starting single proof via update ..."); let proof = TransactionProofBuilder::new() .single_proof_witness(&new_single_proof_witness) + .transaction_proof_type(TransactionProofType::SingleProof) .job_queue(triton_vm_job_queue) .proof_job_options(proof_job_options) .build() @@ -240,6 +242,7 @@ impl Transaction { let proof = TransactionProofBuilder::new() .single_proof_witness(&new_single_proof_witness) + .transaction_proof_type(TransactionProofType::SingleProof) .job_queue(triton_vm_job_queue) .proof_job_options(proof_job_options) .build() diff --git a/src/models/blockchain/transaction/transaction_proof.rs b/src/models/blockchain/transaction/transaction_proof.rs index 07617e538..594056c1e 100644 --- a/src/models/blockchain/transaction/transaction_proof.rs +++ b/src/models/blockchain/transaction/transaction_proof.rs @@ -12,19 +12,31 @@ use crate::models::blockchain::transaction::SingleProof; use crate::models::peer::transfer_transaction::TransactionProofQuality; use crate::models::proof_abstractions::mast_hash::MastHash; use crate::models::proof_abstractions::verifier::verify; +use crate::models::state::vm_proving_capability::VmProvingCapability; /// represents available types of transaction proofs /// /// the types are ordered (asc) by proof-generation complexity. -#[derive(Clone, Debug, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, strum::Display)] -#[repr(u8)] +#[derive( + Clone, + Debug, + Copy, + Serialize, + Deserialize, + PartialEq, + Eq, + PartialOrd, + strum::Display, + strum::EnumIs, +)] pub enum TransactionProofType { /// a primitive-witness. exposes secrets (keys). this proof must not be shared. - PrimitiveWitness = 1, + /// note: not a real TritonVm proof. + PrimitiveWitness, /// a weak proof that does not expose secrets. can be shared with peers, but cannot be confirmed into a block. - ProofCollection = 2, + ProofCollection, /// a strong proof. required for confirming a transaction into a block. - SingleProof = 3, + SingleProof, } impl From<&TransactionProof> for TransactionProofType { @@ -37,14 +49,56 @@ impl From<&TransactionProof> for TransactionProofType { } } +impl From for TransactionProofType { + fn from(c: VmProvingCapability) -> Self { + let max: u8 = c.into(); + + if max >= TransactionProofType::SingleProof.log2_padded_height() { + TransactionProofType::SingleProof + } else if max >= TransactionProofType::ProofCollection.log2_padded_height() { + TransactionProofType::ProofCollection + } else { + TransactionProofType::PrimitiveWitness + } + } +} + +impl From for VmProvingCapability { + fn from(proof_type: TransactionProofType) -> Self { + proof_type.log2_padded_height().into() + } +} + +impl From for u8 { + fn from(proof_type: TransactionProofType) -> Self { + proof_type.log2_padded_height() + } +} + +impl From for u32 { + fn from(proof_type: TransactionProofType) -> Self { + proof_type.log2_padded_height().into() + } +} + impl TransactionProofType { /// indicates if the proof executes in triton-vm. pub fn executes_in_vm(&self) -> bool { matches!(self, Self::ProofCollection | Self::SingleProof) } - pub fn is_single_proof(&self) -> bool { - *self == TransactionProofType::SingleProof + /// provides an estimate of padded-height complexity for each variant. + /// + /// these values were determined by running unit tests + /// and logging padded-height values in the ProverJob. + /// + /// They might need to be adjusted in the future. + pub(crate) const fn log2_padded_height(&self) -> u8 { + match *self { + Self::PrimitiveWitness => 0, + Self::ProofCollection => 15, + Self::SingleProof => 22, + } } } @@ -52,6 +106,7 @@ impl TransactionProofType { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub enum TransactionProof { /// a primitive-witness. exposes secrets (keys). this proof must not be shared. + /// note: not a real TritonVm proof. Witness(PrimitiveWitness), /// a strong proof. required for confirming a transaction into a block. SingleProof(Proof), diff --git a/src/models/proof_abstractions/tasm/program.rs b/src/models/proof_abstractions/tasm/program.rs index 28101ce5d..39c166de7 100644 --- a/src/models/proof_abstractions/tasm/program.rs +++ b/src/models/proof_abstractions/tasm/program.rs @@ -191,7 +191,6 @@ pub mod tests { use crate::models::blockchain::shared::Hash; use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::proof_abstractions::tasm::environment; - use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::tests::shared::test_helper_data_dir; use crate::tests::shared::try_fetch_file_from_server; use crate::tests::shared::try_load_file_from_disk; @@ -201,8 +200,7 @@ pub mod tests { impl From for TritonVmProofJobOptions { fn from(job_priority: TritonVmJobPriority) -> Self { let job_settings = ProverJobSettings { - tx_proving_capability: TxProvingCapability::SingleProof, - proof_type: TransactionProofType::SingleProof, + vm_proving_capability: TransactionProofType::SingleProof.into(), ..Default::default() }; Self { @@ -213,22 +211,6 @@ pub mod tests { } } - impl From<(TritonVmJobPriority, Option)> for TritonVmProofJobOptions { - fn from(v: (TritonVmJobPriority, Option)) -> Self { - let (job_priority, max_log2_padded_height_for_proofs) = v; - Self { - job_priority, - job_settings: ProverJobSettings { - max_log2_padded_height_for_proofs, - network: Default::default(), - tx_proving_capability: TxProvingCapability::SingleProof, - proof_type: TransactionProofType::SingleProof, - }, - cancel_job_rx: None, - } - } - } - pub(crate) trait ConsensusProgramSpecification: ConsensusProgram { /// The canonical reference source code for the consensus program, written in /// the subset of rust that the tasm-lang compiler understands. To run this diff --git a/src/models/proof_abstractions/tasm/prover_job.rs b/src/models/proof_abstractions/tasm/prover_job.rs index bd2e3aa9d..9ca0af4fb 100644 --- a/src/models/proof_abstractions/tasm/prover_job.rs +++ b/src/models/proof_abstractions/tasm/prover_job.rs @@ -11,7 +11,6 @@ #[cfg(not(test))] use std::process::Stdio; -use tasm_lib::maybe_write_debuggable_vm_state_to_disk; use tasm_lib::triton_vm::error::InstructionError; #[cfg(not(test))] use tokio::io::AsyncWriteExt; @@ -21,38 +20,33 @@ use crate::job_queue::channels::JobCancelReceiver; use crate::job_queue::traits::Job; use crate::job_queue::JobCompletion; use crate::job_queue::JobResultWrapper; -use crate::macros::fn_name; -use crate::macros::log_scope_duration; -use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::transaction::validity::neptune_proof::Proof; #[cfg(test)] use crate::models::proof_abstractions::tasm::program::tests; use crate::models::proof_abstractions::Claim; use crate::models::proof_abstractions::NonDeterminism; use crate::models::proof_abstractions::Program; -use crate::models::state::tx_proving_capability::TxProvingCapability; -use crate::triton_vm::vm::VMState; +use crate::models::state::vm_proving_capability::VmProvingCapability; +use crate::models::state::vm_proving_capability::VmProvingCapabilityError; +use crate::util_types::log_vm_state; +use crate::util_types::log_vm_state::LogProofInputsType; /// represents an error running a [ProverJob] #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum ProverJobError { - #[error("triton-vm program complexity limit exceeded. result: {result}, limit: {limit}")] - ProofComplexityLimitExceeded { limit: u32, result: u32 }, - #[error("external proving process failed")] TritonVmProverFailed(#[from] VmProcessError), - #[error("machine's capability {capability} is not sufficient to produce proof: {proof_type}")] - TooWeak { - capability: TxProvingCapability, - proof_type: TransactionProofType, - }, + #[error(transparent)] + VmProvingCapability(#[from] VmProvingCapabilityError), } /// represents an error invoking external prover process /// /// provides additional details for [ProverJobError::TritonVmProverFailed] #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum VmProcessError { #[error("parameter serialization failed")] ParameterSerializationFailed(#[from] serde_json::Error), @@ -113,20 +107,16 @@ pub(super) type ProverJobResult = JobResultWrapper #[derive(Debug, Clone, Copy)] pub struct ProverJobSettings { - pub(crate) max_log2_padded_height_for_proofs: Option, pub(crate) network: Network, - pub(crate) tx_proving_capability: TxProvingCapability, - pub(crate) proof_type: TransactionProofType, + pub(crate) vm_proving_capability: VmProvingCapability, } #[cfg(test)] impl Default for ProverJobSettings { fn default() -> Self { Self { - max_log2_padded_height_for_proofs: None, network: Network::default(), - tx_proving_capability: TxProvingCapability::SingleProof, - proof_type: TxProvingCapability::SingleProof.into(), + vm_proving_capability: crate::api::export::TransactionProofType::SingleProof.into(), } } } @@ -157,89 +147,22 @@ impl ProverJob { // runs program in triton_vm to determine complexity // - // if complexity exceeds setting `max_log2_padded_height_for_proofs` - // then it is unlikely this hardware will be able to generate the - // corresponding proof. In this case a `ProofComplexityLimitExceeded` - // error is returned. + // if complexity exceeds the known capability of the device then it is + // unlikely this hardware will be able to generate the corresponding proof. + // In this case a `VmProvingCapability` error is returned. async fn check_if_allowed(&self) -> Result<(), ProverJobError> { tracing::debug!("job settings: {:?}", self.job_settings); - let capability = self.job_settings.tx_proving_capability; - let proof_type = self.job_settings.proof_type; - if !capability.can_prove(proof_type) { - return Err(ProverJobError::TooWeak { - capability, - proof_type, - }); - } - - tracing::debug!("executing VM program to determine complexity (padded-height)"); - - assert_eq!(self.program.hash(), self.claim.program_digest); - - let mut vm_state = VMState::new( - self.program.clone(), - self.claim.input.clone().into(), - self.nondeterminism.clone(), - ); - maybe_write_debuggable_vm_state_to_disk(&vm_state); - - // run program in VM - // - // this is sometimes fast enough for async, but other times takes 1+ seconds. - // As such we run it in spawn-blocking. Eventually it might make sense - // to move into the external process. - vm_state = { - let join_result = tokio::task::spawn_blocking(move || { - log_scope_duration!(fn_name!() + "::vm_state.run()"); - let r = vm_state.run(); - (vm_state, r) - }) - .await; - - let (vm_state_moved, run_result) = match join_result { - Ok(r) => r, - Err(e) if e.is_panic() => std::panic::resume_unwind(e.into_panic()), - Err(e) if e.is_cancelled() => { - panic!("VM::run() task was cancelled unexpectedly. error: {e}") - } - Err(e) => panic!("unexpected error from VM::run() spawn-blocking task. {e}"), - }; - - if let Err(e) = run_result { - return Err(ProverJobError::TritonVmProverFailed( - VmProcessError::TritonVmFailed(e), - )); - } - vm_state_moved - }; - assert_eq!(self.claim.program_digest, self.program.hash()); - assert_eq!(self.claim.output, vm_state.public_output); - - let padded_height_processor_table = vm_state.cycle_count.next_power_of_two(); - - tracing::info!( - "VM program execution finished: padded-height (processor table): {}", - padded_height_processor_table - ); - - match self.job_settings.max_log2_padded_height_for_proofs { - Some(limit) if 2u32.pow(limit.into()) < padded_height_processor_table => { - let ph_limit = 2u32.pow(limit.into()); - - tracing::warn!( - "proof-complexity-limit-exceeded. ({} > {}) The proof will not be generated", - padded_height_processor_table, - ph_limit - ); + self.job_settings + .vm_proving_capability + .check_if_capable_async( + self.program.clone(), + self.claim.clone(), + self.nondeterminism.clone(), + ) + .await?; - Err(ProverJobError::ProofComplexityLimitExceeded { - result: padded_height_processor_table, - limit: ph_limit, - }) - } - _ => Ok(()), - } + Ok(()) } /// Run the program and generate a proof for it, assuming the Triton VM run @@ -253,6 +176,15 @@ impl ProverJob { /// If we are in a test environment, try reading it from disk. If it is not /// there, generate it and store it to disk. async fn prove(&self, rx: JobCancelReceiver) -> JobCompletion { + // log proof inputs if matching env var is set (may expose witness secrets) + // maybe_write() logs warning if error occurs; we ignore any error. + let _ = log_vm_state::maybe_write( + LogProofInputsType::MayContainWalletSecrets, + self.program.clone(), + &self.claim, + || self.nondeterminism.clone(), + ); + // produce mock proofs if network so requires. (ie RegTest) if self.job_settings.network.use_mock_proof() { let proof = Proof::valid_mock(self.claim.clone()); diff --git a/src/models/state/archival_state.rs b/src/models/state/archival_state.rs index dcfce0c99..13022f1cc 100644 --- a/src/models/state/archival_state.rs +++ b/src/models/state/archival_state.rs @@ -1177,12 +1177,12 @@ pub(super) mod tests { use crate::database::storage::storage_vec::traits::*; use crate::mine_loop::tests::make_coinbase_transaction_from_state; use crate::models::blockchain::transaction::lock_script::LockScript; + use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::transaction::utxo::Utxo; use crate::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount; use crate::models::proof_abstractions::timestamp::Timestamp; use crate::models::state::archival_state::ArchivalState; use crate::models::state::tx_creation_config::TxCreationConfig; - use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::wallet::address::KeyType; use crate::models::state::wallet::expected_utxo::UtxoNotifier; use crate::models::state::wallet::transaction_output::TxOutput; @@ -1374,7 +1374,7 @@ pub(super) mod tests { TxOutput::no_notification(utxo, rng.random(), rng.random(), false); let config = TxCreationConfig::default() .recover_change_on_chain(alice_key.into()) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let sender_tx = alice .api() .tx_initiator_internal() @@ -1497,7 +1497,7 @@ pub(super) mod tests { let in_seven_months = Timestamp::now() + Timestamp::months(7); let config_1a = TxCreationConfig::default() .recover_change_on_chain(alice_key.into()) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let big_tx = alice .api() .tx_initiator_internal() @@ -1509,7 +1509,7 @@ pub(super) mod tests { let config_1b = TxCreationConfig::default() .recover_change_on_chain(alice_key.into()) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let empty_tx = alice .api() .tx_initiator_internal() @@ -1595,7 +1595,7 @@ pub(super) mod tests { let timestamp = previous_block.header().timestamp + Timestamp::months(7); let config = TxCreationConfig::default() .recover_change_on_chain(alice_key.into()) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let tx = alice .api() .tx_initiator_internal() @@ -1834,7 +1834,7 @@ pub(super) mod tests { .await; let config = TxCreationConfig::default() .recover_change_off_chain(change_key) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let artifacts_alice_and_bob = premine_rec .api() .tx_initiator_internal() @@ -1865,6 +1865,7 @@ pub(super) mod tests { &premine_rec, in_seven_months, TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap(); @@ -2035,7 +2036,7 @@ pub(super) mod tests { .into(); let config_alice = TxCreationConfig::default() .recover_change_off_chain(alice_change_key) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let artifacts_alice = alice .api() .tx_initiator_internal() @@ -2083,7 +2084,7 @@ pub(super) mod tests { .into(); let config_bob = TxCreationConfig::default() .recover_change_off_chain(bob_change_key) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let tx_creation_artifacts_bob = bob .api() .tx_initiator_internal() @@ -2118,6 +2119,7 @@ pub(super) mod tests { &premine_rec, in_seven_months, TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap(); diff --git a/src/models/state/mempool.rs b/src/models/state/mempool.rs index 09afa18fa..800719ea2 100644 --- a/src/models/state/mempool.rs +++ b/src/models/state/mempool.rs @@ -47,10 +47,11 @@ use tracing::warn; use twenty_first::math::digest::Digest; use super::transaction_kernel_id::TransactionKernelId; -use super::tx_proving_capability::TxProvingCapability; +use super::vm_proving_capability::VmProvingCapability; use crate::main_loop::proof_upgrader::UpdateMutatorSetDataJob; use crate::models::blockchain::block::Block; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; +use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::transaction::validity::neptune_proof::Proof; use crate::models::blockchain::transaction::validity::proof_collection::ProofCollection; use crate::models::blockchain::transaction::Transaction; @@ -644,9 +645,11 @@ impl Mempool { &mut self, new_block: &Block, predecessor_block: &Block, - tx_proving_capability: TxProvingCapability, + vm_proving_capability: impl Into, composing: bool, ) -> (Vec, Vec) { + let vm_proving_capability = vm_proving_capability.into(); + // If the mempool is empty, there is nothing to do. if self.is_empty() { self.set_tip_digest_sync_label(new_block.hash()); @@ -733,8 +736,9 @@ impl Mempool { continue; } - let can_upgrade_single_proof = - TxProvingCapability::SingleProof == tx_proving_capability; + let can_upgrade_single_proof = vm_proving_capability + .can_prove(TransactionProofType::SingleProof) + .is_ok(); let (update_job, can_update) = match &tx.transaction.proof { TransactionProof::ProofCollection(_) => { debug!("Failed to update transaction {tx_id}. Because it is only supported by a proof collection."); @@ -764,7 +768,7 @@ impl Mempool { (Some(job), true) } else { - debug!("Not updating single-proof supported transaction {tx_id}, because TxProvingCapability was only {tx_proving_capability}."); + debug!("Not updating single-proof supported transaction {tx_id}, because VmProvingCapability was only {vm_proving_capability}."); (None, false) } } @@ -884,7 +888,6 @@ mod tests { use crate::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount; use crate::models::shared::SIZE_20MB_IN_BYTES; use crate::models::state::tx_creation_config::TxCreationConfig; - use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::wallet::expected_utxo::UtxoNotifier; use crate::models::state::wallet::transaction_output::TxOutput; use crate::models::state::wallet::transaction_output::TxOutputList; @@ -1059,7 +1062,7 @@ mod tests { let high_fee = NativeCurrencyAmount::coins(15); let config = TxCreationConfig::default() .recover_change_on_chain(bob_spending_key.into()) - .with_prover_capability(TxProvingCapability::ProofCollection); + .with_prover_capability(TransactionProofType::ProofCollection); let tx_by_bob = bob .api() .tx_initiator_internal() @@ -1323,7 +1326,7 @@ mod tests { let in_eight_months = now + Timestamp::months(8); let config_bob = TxCreationConfig::default() .recover_change_on_chain(bob_spending_key.into()) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let artifacts_bob = bob .api() .tx_initiator_internal() @@ -1367,7 +1370,7 @@ mod tests { )]; let config_alice = TxCreationConfig::default() .recover_change_off_chain(alice_key.into()) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let tx_from_alice_original = alice .api() .tx_initiator_internal() @@ -1408,6 +1411,7 @@ mod tests { &bob, in_eight_months, TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap(); @@ -1432,7 +1436,7 @@ mod tests { let (_, update_jobs2) = mempool.update_with_block_and_predecessor( &block_2, &block_1, - TxProvingCapability::SingleProof, + TransactionProofType::SingleProof, true, ); mocked_mempool_update_handler(update_jobs2, &mut mempool).await; @@ -1464,7 +1468,7 @@ mod tests { let (_, update_jobs_n) = mempool.update_with_block_and_predecessor( &next_block, &previous_block, - TxProvingCapability::SingleProof, + TransactionProofType::SingleProof, true, ); mocked_mempool_update_handler(update_jobs_n, &mut mempool).await; @@ -1490,6 +1494,7 @@ mod tests { &alice, block_5_timestamp, TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap(); @@ -1513,7 +1518,7 @@ mod tests { let (_, update_jobs5) = mempool.update_with_block_and_predecessor( &block_5, &previous_block, - TxProvingCapability::SingleProof, + TransactionProofType::SingleProof, true, ); mocked_mempool_update_handler(update_jobs5, &mut mempool).await; @@ -1637,9 +1642,9 @@ mod tests { let network = Network::Main; let alice_wallet = WalletEntropy::devnet_wallet(); let alice_key = alice_wallet.nth_generation_spending_key_for_tests(0); - let proving_capability = TxProvingCapability::SingleProof; + let proving_capability = TransactionProofType::SingleProof; let cli_with_proof_capability = cli_args::Args { - tx_proving_capability: Some(proving_capability), + vm_proving_capability: Some(proving_capability.into()), network, ..Default::default() }; @@ -1813,7 +1818,7 @@ mod tests { let tx_outputs: TxOutputList = vec![receiver_data.clone()].into(); let config = TxCreationConfig::default() .recover_change_on_chain(premine_spending_key.into()) - .with_prover_capability(TxProvingCapability::ProofCollection); + .with_prover_capability(TransactionProofType::ProofCollection); preminer_clone .api() .tx_initiator_internal() @@ -1999,7 +2004,7 @@ mod tests { /// Return a valid, deterministic transaction with a specified proof type. async fn tx_with_proof_type( - proof_type: TxProvingCapability, + proof_type: impl Into, network: Network, fee: NativeCurrencyAmount, ) -> std::sync::Arc { @@ -2015,7 +2020,7 @@ mod tests { let in_seven_months = genesis_block.kernel.header.timestamp + Timestamp::months(7); let config = TxCreationConfig::default() .recover_change_on_chain(bob_spending_key.into()) - .with_prover_capability(proof_type); + .with_prover_capability(proof_type.into()); // Clippy is wrong here. You can *not* eliminate the binding. #[allow(clippy::let_and_return)] @@ -2034,7 +2039,7 @@ mod tests { async fn single_proof_always_replaces_primitive_witness() { let network = Network::Main; let pw_high_fee = tx_with_proof_type( - TxProvingCapability::PrimitiveWitness, + TransactionProofType::PrimitiveWitness, network, NativeCurrencyAmount::coins(15), ) @@ -2046,7 +2051,7 @@ mod tests { let low_fee = NativeCurrencyAmount::coins(1); let sp_low_fee = - tx_with_proof_type(TxProvingCapability::SingleProof, network, low_fee).await; + tx_with_proof_type(TransactionProofType::SingleProof, network, low_fee).await; let txid = sp_low_fee.kernel.txid(); mempool.insert(sp_low_fee.into(), TransactionOrigin::Own); assert!( @@ -2065,7 +2070,7 @@ mod tests { async fn single_proof_always_replaces_proof_collection() { let network = Network::Main; let pc_high_fee = tx_with_proof_type( - TxProvingCapability::ProofCollection, + TransactionProofType::ProofCollection, network, NativeCurrencyAmount::coins(15), ) @@ -2077,7 +2082,7 @@ mod tests { let low_fee = NativeCurrencyAmount::coins(1); let sp_low_fee = - tx_with_proof_type(TxProvingCapability::SingleProof, network, low_fee).await; + tx_with_proof_type(TransactionProofType::SingleProof, network, low_fee).await; let txid = sp_low_fee.kernel.txid(); mempool.insert(sp_low_fee.into(), TransactionOrigin::Own); assert!( @@ -2096,7 +2101,7 @@ mod tests { async fn proof_collection_always_replaces_proof_primitive_witness() { let network = Network::Main; let pc_high_fee = tx_with_proof_type( - TxProvingCapability::PrimitiveWitness, + TransactionProofType::PrimitiveWitness, network, NativeCurrencyAmount::coins(15), ) @@ -2108,7 +2113,7 @@ mod tests { let low_fee = NativeCurrencyAmount::coins(1); let sp_low_fee = - tx_with_proof_type(TxProvingCapability::ProofCollection, network, low_fee).await; + tx_with_proof_type(TransactionProofType::ProofCollection, network, low_fee).await; let txid = sp_low_fee.kernel.txid(); mempool.insert(sp_low_fee.into(), TransactionOrigin::Own); assert!( diff --git a/src/models/state/mod.rs b/src/models/state/mod.rs index 62e8b6795..a7912fd26 100644 --- a/src/models/state/mod.rs +++ b/src/models/state/mod.rs @@ -14,7 +14,7 @@ pub(crate) mod tx_creation_artifacts; #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] pub(crate) mod tx_creation_config; -pub mod tx_proving_capability; +pub mod vm_proving_capability; pub mod wallet; use std::cmp::max; @@ -51,7 +51,7 @@ use transaction_kernel_id::TransactionKernelId; use twenty_first::math::digest::Digest; use tx_creation_artifacts::TxCreationArtifacts; use tx_creation_artifacts::TxCreationArtifactsError; -use tx_proving_capability::TxProvingCapability; +use vm_proving_capability::VmProvingCapability; use wallet::wallet_state::WalletState; use wallet::wallet_status::WalletStatus; @@ -1732,7 +1732,7 @@ impl GlobalState { self.cli().peers.clone() } - pub(crate) fn proving_capability(&self) -> TxProvingCapability { + pub(crate) fn proving_capability(&self) -> VmProvingCapability { self.cli().proving_capability() } @@ -1899,6 +1899,7 @@ mod tests { use crate::models::blockchain::block::Block; use crate::models::blockchain::block::BlockProof; use crate::models::blockchain::transaction::lock_script::LockScript; + use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::transaction::utxo::Utxo; use crate::models::state::tx_creation_config::TxCreationConfig; use crate::models::state::wallet::address::hash_lock_key::HashLockKey; @@ -2057,7 +2058,7 @@ mod tests { let one_month = Timestamp::months(1); let config = TxCreationConfig::default() .recover_change_off_chain(bob_spending_key.into()) - .with_prover_capability(TxProvingCapability::ProofCollection); + .with_prover_capability(TransactionProofType::ProofCollection); assert!(bob .api() .tx_initiator_internal() @@ -2947,6 +2948,7 @@ mod tests { &premine_receiver, in_seven_months, TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap(); @@ -2997,7 +2999,7 @@ mod tests { .await; let config_alice_and_bob = TxCreationConfig::default() .recover_change_off_chain(genesis_key) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let tx_outputs_for_alice_and_bob = [tx_outputs_for_alice.clone(), tx_outputs_for_bob.clone()].concat(); let artifacts_alice_and_bob = premine_receiver @@ -3187,7 +3189,7 @@ mod tests { // Weaker machines need to use the proof server. let config_alice = TxCreationConfig::default() .recover_change_off_chain(alice_spending_key.into()) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let artifacts_from_alice = alice .api() .tx_initiator_internal() @@ -3232,7 +3234,7 @@ mod tests { ]; let config_bob = TxCreationConfig::default() .recover_change_off_chain(bob_spending_key.into()) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let artifacts_from_bob = bob .api() .tx_initiator_internal() @@ -3269,6 +3271,7 @@ mod tests { &premine_receiver, in_seven_months, TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap(); @@ -3366,9 +3369,8 @@ mod tests { &genesis_block, global_state_lock, timestamp, - global_state_lock - .cli() - .proof_job_options_primitive_witness(), + TritonVmJobPriority::Normal.into(), + TransactionProofType::PrimitiveWitness, ) .await .unwrap(); @@ -4352,7 +4354,7 @@ mod tests { // create tx. utxo_notify_method is a test param. let config = TxCreationConfig::default() .recover_to_provided_key(Arc::new(alice_change_key), change_notification_medium) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let artifacts = alice_state_lock .api() .tx_initiator_internal() @@ -4395,8 +4397,9 @@ mod tests { &genesis_block, &charlie_state_lock, seven_months_post_launch, - (TritonVmJobPriority::Normal, None).into(), + TritonVmJobPriority::Normal.into(), TxMergeOrigin::ExplicitList(vec![Arc::into_inner(alice_to_bob_tx).unwrap()]), + TransactionProofType::SingleProof, ) .await .unwrap(); diff --git a/src/models/state/tx_creation_config.rs b/src/models/state/tx_creation_config.rs index 777bbb006..36fb2b821 100644 --- a/src/models/state/tx_creation_config.rs +++ b/src/models/state/tx_creation_config.rs @@ -5,7 +5,7 @@ use std::fmt::Debug; use std::sync::Arc; -use super::tx_proving_capability::TxProvingCapability; +use super::vm_proving_capability::VmProvingCapability; use super::wallet::address::SpendingKey; use super::wallet::change_policy::ChangePolicy; use super::wallet::utxo_notification::UtxoNotificationMedium; @@ -46,10 +46,12 @@ impl TxCreationConfig { } /// Configure the proving capacity. - pub(crate) fn with_prover_capability(mut self, prover_capability: TxProvingCapability) -> Self { + pub(crate) fn with_prover_capability( + mut self, + prover_capability: impl Into, + ) -> Self { // note: legacy tests consider prover_capability and target proof_type to be the same thing - self.proof_job_options.job_settings.tx_proving_capability = prover_capability; - self.proof_job_options.job_settings.proof_type = prover_capability.into(); + self.proof_job_options.job_settings.vm_proving_capability = prover_capability.into(); self } diff --git a/src/models/state/tx_proving_capability.rs b/src/models/state/tx_proving_capability.rs deleted file mode 100644 index c49b8c6d8..000000000 --- a/src/models/state/tx_proving_capability.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::fmt::Display; -use std::str::FromStr; - -use clap::error::ErrorKind; -use clap::Parser; -use serde::Deserialize; -use serde::Serialize; - -use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; - -// note: we should consider merging TransactionProofType and TxProvingCapability - -/// represents which type of proof a given device is capable of generating -/// -/// see also: -/// * [TransactionProofType] -/// * [TransactionProof](crate::models::blockchain::transaction::transaction_proof::TransactionProof) -#[derive(Parser, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] -pub enum TxProvingCapability { - LockScript, - #[default] - PrimitiveWitness, - ProofCollection, - SingleProof, -} - -impl From for TransactionProofType { - fn from(c: TxProvingCapability) -> Self { - match c { - // TransactionProofType and TxProvingCapability need to be - // reconciled with regards to LockScript, or merged into single type. - TxProvingCapability::LockScript => unimplemented!(), - TxProvingCapability::PrimitiveWitness => Self::PrimitiveWitness, - TxProvingCapability::ProofCollection => Self::ProofCollection, - TxProvingCapability::SingleProof => Self::SingleProof, - } - } -} - -impl TxProvingCapability { - pub(crate) fn can_prove(&self, proof_type: TransactionProofType) -> bool { - assert!(proof_type as u8 > 0); - - let self_val = match *self { - // TransactionProofType and TxProvingCapability need to be - // reconciled with regards to LockScript, or merged into single type. - Self::LockScript => 0, - Self::PrimitiveWitness => TransactionProofType::PrimitiveWitness as u8, - Self::ProofCollection => TransactionProofType::ProofCollection as u8, - Self::SingleProof => TransactionProofType::SingleProof as u8, - }; - - self_val >= proof_type as u8 - } -} - -impl Display for TxProvingCapability { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - TxProvingCapability::PrimitiveWitness => "primitive witness", - TxProvingCapability::LockScript => "lock script", - TxProvingCapability::ProofCollection => "proof collection", - TxProvingCapability::SingleProof => "single proof", - } - ) - } -} - -impl FromStr for TxProvingCapability { - type Err = clap::Error; - // This implementation exists to allow CLI arguments to be converted to an - // instance of this type. - - fn from_str(s: &str) -> Result { - match s { - // PrimitiveWitness is not covered here, as it's only used - // internally, and cannot be set on the client. - "lockscript" => Ok(TxProvingCapability::LockScript), - "proofcollection" => Ok(TxProvingCapability::ProofCollection), - "singleproof" => Ok(TxProvingCapability::SingleProof), - _ => Err(clap::Error::raw( - ErrorKind::InvalidValue, - "Invalid machine proving power", - )), - } - } -} diff --git a/src/models/state/vm_proving_capability.rs b/src/models/state/vm_proving_capability.rs new file mode 100644 index 000000000..2c7dff7c9 --- /dev/null +++ b/src/models/state/vm_proving_capability.rs @@ -0,0 +1,281 @@ +use std::fmt::Display; +use std::str::FromStr; + +use num_traits::Zero; +use serde::Deserialize; +use serde::Serialize; +use sysinfo::System; + +use crate::api::export::TransactionProofType; +use crate::models::state::Claim; +use crate::models::state::NonDeterminism; +use crate::models::state::Program; +use crate::models::state::VMState; +use crate::models::state::VM; +use crate::tasm_lib::triton_vm::error::VMError; + +/// represents proving capability of a device. +/// +/// The proving capability is represented as log2(padded_height) where +/// padded_height is an indicator of the execution complexity of the +/// (program, claim, nondeterminism) triple necessary for generating a +/// TritonVm `Proof`. +/// +// A rough indicator of a device's capability can be obtained via +// the [`auto_detect()`] method. +#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct VmProvingCapability { + log2_padded_height: u8, +} + +impl From for VmProvingCapability { + fn from(log2_padded_height: u8) -> Self { + Self { log2_padded_height } + } +} + +impl From for u8 { + fn from(capability: VmProvingCapability) -> Self { + capability.log2_padded_height + } +} + +impl From for u32 { + fn from(capability: VmProvingCapability) -> Self { + capability.log2_padded_height.into() + } +} + +impl VmProvingCapability { + /// indicates if the device is capable of executing a program with the + /// supplied padded height + /// + /// Examples: + /// + /// ``` + /// use neptune_cash::api::export::VmProvingCapability; + /// use neptune_cash::api::export::TransactionProofType; + /// + /// let capability: VmProvingCapability = 16.into(); + /// + /// assert!(capability.can_prove(15u32).is_ok()); + /// assert!(capability.can_prove(16u32).is_ok()); + /// assert!(capability.can_prove(17u32).is_err()); + /// + /// assert!(capability.can_prove(TransactionProofType::PrimitiveWitness).is_ok()); + /// assert!(capability.can_prove(TransactionProofType::ProofCollection).is_ok()); + /// assert!(capability.can_prove(TransactionProofType::SingleProof).is_err()); + /// + /// let single_proof_capability: VmProvingCapability = TransactionProofType::SingleProof.into(); + /// assert!(single_proof_capability.can_prove(TransactionProofType::SingleProof).is_ok()); + pub fn can_prove(&self, other: impl Into) -> Result<(), VmProvingCapabilityError> { + let capability: u32 = (*self).into(); + let attempted: u32 = other.into(); + + if capability >= attempted { + Ok(()) + } else { + Err(VmProvingCapabilityError::DeviceNotCapable { + capability, + attempted, + }) + } + } + + /// executes the supplied program triple to determine if device is capable + /// of producing a TritonVM `Proof`. + /// + /// perf: this is an expensive operation; it may be under a second up to + /// several seconds + /// + /// The program is executed inside spawn_blocking() so it will not block + /// concurrent async tasks on the same thread. + /// + /// see `check_if_capable` for description of LOG2_PADDED_HEIGHT_METHOD + /// env var that affects this method. + pub async fn check_if_capable_async( + &self, + program: Program, + claim: Claim, + nondeterminism: NonDeterminism, + ) -> Result<(), VmProvingCapabilityError> { + let copy = *self; + let join_result = tokio::task::spawn_blocking(move || { + copy.check_if_capable(program, claim, nondeterminism) + }) + .await; + + match join_result { + Ok(r) => r, + Err(e) if e.is_panic() => std::panic::resume_unwind(e.into_panic()), + Err(e) => panic!("unexpected error from spawn_blocking(). {e}"), + } + } + + /// executes the supplied program triple to determine if device is capable + /// of producing a TritonVM `Proof`. + /// + /// perf: this is an expensive operation; it may be under a second up to + /// several seconds + /// + /// The program is executed in blocking fashion so it will block concurrent + /// async tasks on the same thread. async callers should use + /// `check_if_capable_async()` instead. + /// + /// #### environment variable: LOG2_PADDED_HEIGHT_METHOD + /// + /// By default the log2(padded-height) is calculated using VmState::run(). + /// + /// A more accurate but slower way is to use VM::trace_execution_of_state(). + /// This is typically about 4x slower. + /// + /// And the fastest method is to skip running the program entirely. But + /// that option is only available when running unit tests. + /// + /// These methods can be selected at runtime: + /// + /// ```text + /// LOG2_PADDED_HEIGHT_METHOD=trace neptune-core + /// LOG2_PADDED_HEIGHT_METHOD=run neptune-core + /// + /// # only for unit tests + /// LOG2_PADDED_HEIGHT_METHOD=skip cargo test + /// ``` + pub fn check_if_capable( + &self, + program: Program, + claim: Claim, + nondeterminism: NonDeterminism, + ) -> Result<(), VmProvingCapabilityError> { + let log2_padded_height = Self::obtain_log2_padded_height(program, claim, nondeterminism)?; + self.can_prove(log2_padded_height) + } + + /// executes the supplied program triple to obtain the log2(padded_height) + /// + /// perf: this is an expensive operation; it may be under a second up to 60+ + /// seconds depending on the program's complexity and + /// NEPTUNE_LOG2_PADDED_HEIGHT_METHOD setting. + /// + /// The program is executed in blocking fashion so it will block concurrent + /// async tasks on the same thread. async callers should use + /// tokio's spawn_blocking() to wrap this fn. + fn obtain_log2_padded_height( + program: Program, + claim: Claim, + nondeterminism: NonDeterminism, + ) -> Result { + crate::macros::log_scope_duration!(crate::macros::fn_name!()); + + debug_assert_eq!(program.hash(), claim.program_digest); + + let mut vmstate = VMState::new(program, claim.input.into(), nondeterminism); + + let method = std::env::var("NEPTUNE_LOG2_PADDED_HEIGHT_METHOD") + .unwrap_or_else(|_| "run".to_string()); + + match method.as_str() { + "trace" => { + // this is about 4x slower than "run". + let (aet, _) = VM::trace_execution_of_state(vmstate)?; + Ok(aet.padded_height().ilog2()) + } + + // this is fastest, as it avoids running program at all. + // but only supported for unit tests, as a "turbo" mode. + #[cfg(test)] + "skip" => Ok(0), + + "run" | &_ => { + // this is baseline + match vmstate.run() { + Ok(_) => { + debug_assert_eq!(claim.output, vmstate.public_output); + Ok(vmstate.cycle_count.next_power_of_two().ilog2()) + } + Err(e) => Err(VMError::new(e, vmstate).into()), + } + } + } + } + + /// automatically detect the log2_padded_height for this device. + /// + /// for now this just: + /// 1. obtains CPU core count and total mem for device. + /// 2. compares against single-proof requirements + /// 3. compares against proof-collection requirements + /// 4. sets to min requirement of single-proof, proof-collection + /// or else primitive-witness depending on comparison results. + /// + /// in the future this method (or a similar one) may instead run + /// some kind of proving test to calculate the log2_padded_height + /// more accurately. + // + // see discussion: + // + // + // note: it would be nice if triton_vm would provide a method for this + // that we can just call, eg: triton_vm::estimate_log2_padded_height(); + // + // note: this is pub(crate) for now because it may change to async in the + // future if we do a more involved stress test. + pub(crate) fn auto_detect() -> Self { + const SINGLE_PROOF_CORE_REQ: usize = 19; + // see https://github.com/Neptune-Crypto/neptune-core/issues/426 + const SINGLE_PROOF_MEMORY_USAGE: u64 = (1u64 << 30) * 120; + + const PROOF_COLLECTION_CORE_REQ: usize = 2; + const PROOF_COLLECTION_MEMORY_USAGE: u64 = (1u64 << 30) * 16; + + let s = System::new_all(); + let total_memory = s.total_memory(); + if total_memory.is_zero() { + tracing::warn!("Total memory reported illegal value of 0"); + } + + let physical_core_count = s.physical_core_count().unwrap_or(1); + + if total_memory > SINGLE_PROOF_MEMORY_USAGE && physical_core_count > SINGLE_PROOF_CORE_REQ { + TransactionProofType::SingleProof.into() + } else if total_memory > PROOF_COLLECTION_MEMORY_USAGE + && physical_core_count > PROOF_COLLECTION_CORE_REQ + { + TransactionProofType::ProofCollection.into() + } else { + TransactionProofType::PrimitiveWitness.into() + } + } +} + +impl Display for VmProvingCapability { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.log2_padded_height) + } +} + +impl FromStr for VmProvingCapability { + type Err = clap::Error; + + fn from_str(s: &str) -> Result { + match s.parse::() { + Ok(height) => Ok(VmProvingCapability { + log2_padded_height: height, + }), + Err(e) => Err(clap::Error::raw( + clap::error::ErrorKind::ValueValidation, + format!("invalid log2_padded_height '{}': {}", s, e), + )), + } + } +} + +#[derive(Debug, Clone, thiserror::Error, strum::EnumIs)] +#[non_exhaustive] +pub enum VmProvingCapabilityError { + #[error("could not obtain padded-height due to program execution error")] + VmExecutionFailed(#[from] tasm_lib::triton_vm::error::VMError), + + #[error("device capability {capability} is insufficient to generate proof that requires capability {attempted}")] + DeviceNotCapable { capability: u32, attempted: u32 }, +} diff --git a/src/models/state/wallet/mod.rs b/src/models/state/wallet/mod.rs index 4a0f4162d..9e5a509a8 100644 --- a/src/models/state/wallet/mod.rs +++ b/src/models/state/wallet/mod.rs @@ -52,11 +52,11 @@ mod tests { use crate::models::blockchain::block::Block; use crate::models::blockchain::shared::Hash; use crate::models::blockchain::transaction::lock_script::LockScript; + use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::transaction::utxo::Utxo; use crate::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount; use crate::models::proof_abstractions::timestamp::Timestamp; use crate::models::state::tx_creation_config::TxCreationConfig; - use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::wallet::expected_utxo::UtxoNotifier; use crate::models::state::wallet::secret_key_material::SecretKeyMaterial; use crate::models::state::wallet::transaction_output::TxOutput; @@ -577,7 +577,7 @@ mod tests { let bob_change_key = bob_wallet.nth_generation_spending_key_for_tests(0).into(); let config_1 = TxCreationConfig::default() .recover_change_on_chain(bob_change_key) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let tx_1 = tx_initiator_internal .create_transaction( receiver_data_to_alice.clone(), @@ -833,7 +833,7 @@ mod tests { let config_2b = TxCreationConfig::default() .recover_change_off_chain(bob_change_key) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let tx_from_bob: Transaction = tx_initiator_internal .create_transaction( @@ -858,6 +858,7 @@ mod tests { &alice, block_2_b.header().timestamp + network.minimum_block_time(), TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap(); @@ -1039,6 +1040,7 @@ mod tests { &bob, in_seven_months, TritonVmJobPriority::Normal.into(), + TransactionProofType::SingleProof, ) .await .unwrap(); @@ -1050,7 +1052,7 @@ mod tests { let change_key = WalletEntropy::devnet_wallet().nth_symmetric_key_for_tests(0); let config = TxCreationConfig::default() .recover_change_off_chain(change_key.into()) - .with_prover_capability(TxProvingCapability::SingleProof); + .with_prover_capability(TransactionProofType::SingleProof); let sender_tx: Transaction = bob .api() .tx_initiator_internal() diff --git a/src/models/state/wallet/wallet_state.rs b/src/models/state/wallet/wallet_state.rs index 0885ab60f..8a36a8fa4 100644 --- a/src/models/state/wallet/wallet_state.rs +++ b/src/models/state/wallet/wallet_state.rs @@ -2004,9 +2004,9 @@ pub(crate) mod tests { use crate::config_models::cli_args; use crate::config_models::network::Network; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernelModifier; + use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::transaction::utxo::Coin; use crate::models::state::tx_creation_config::TxCreationConfig; - use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::wallet::expected_utxo::ExpectedUtxo; use crate::models::state::wallet::transaction_output::TxOutput; use crate::models::state::wallet::utxo_notification::UtxoNotificationMedium; @@ -2259,7 +2259,7 @@ pub(crate) mod tests { let tx_outputs = vec![txoutput.clone(), txoutput.clone()]; let config2 = TxCreationConfig::default() .recover_change_on_chain(bob_key.into()) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let tx_block2 = bob .api() .tx_initiator_internal() @@ -2304,7 +2304,7 @@ pub(crate) mod tests { // balance. let config3 = TxCreationConfig::default() .recover_change_on_chain(bob_key.into()) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let tx_block3 = bob .api() .tx_initiator_internal() @@ -2365,7 +2365,7 @@ pub(crate) mod tests { let fee = NativeCurrencyAmount::coins(10); let config = TxCreationConfig::default() .recover_change_on_chain(bob_key.into()) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let mut tx_block2: Transaction = bob .api() .tx_initiator_internal() @@ -2467,7 +2467,7 @@ pub(crate) mod tests { let fee = NativeCurrencyAmount::coins(10); let config = TxCreationConfig::default() .recover_change_on_chain(bob_key.into()) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let mut tx_block2: Transaction = bob .api() .tx_initiator_internal() @@ -3201,7 +3201,7 @@ pub(crate) mod tests { let a_key = GenerationSpendingKey::derive_from_seed(rng.random()); let config = TxCreationConfig::default() .recover_change_on_chain(a_key.into()) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let mut tx_spending_guesser_fee: Transaction = bob .api() .tx_initiator_internal() @@ -3302,7 +3302,6 @@ pub(crate) mod tests { use crate::config_models::cli_args; use crate::models::blockchain::block::block_height::BlockHeight; use crate::models::blockchain::transaction::Transaction; - use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::wallet::address::ReceivingAddress; use crate::models::state::wallet::utxo_notification::UtxoNotificationMedium; use crate::models::state::TransactionOrigin; @@ -3384,7 +3383,7 @@ pub(crate) mod tests { let config = TxCreationConfig::default() .recover_change_on_chain(change_key) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); global_state_lock .api() .tx_initiator_internal() @@ -3466,7 +3465,7 @@ pub(crate) mod tests { let config = TxCreationConfig::default() .recover_change_off_chain(change_key) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); alice_global_lock .api() .tx_initiator_internal() @@ -3970,7 +3969,7 @@ pub(crate) mod tests { let config = TxCreationConfig::default() .recover_change_off_chain(change_key) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); alice_global_lock .api() .tx_initiator_internal() @@ -4225,7 +4224,7 @@ pub(crate) mod tests { ); let config = TxCreationConfig::default() .recover_change_off_chain(premine_change_key) - .with_prover_capability(TxProvingCapability::PrimitiveWitness); + .with_prover_capability(TransactionProofType::PrimitiveWitness); let transaction = premine_receiver .api() .tx_initiator_internal() @@ -4547,7 +4546,8 @@ pub(crate) mod tests { composer_parameters.clone(), now, TritonVmJobQueue::get_instance(), - rando.cli().proof_job_options_primitive_witness(), + rando.cli().into(), + TransactionProofType::PrimitiveWitness, ) .await .unwrap(); @@ -4679,9 +4679,8 @@ pub(crate) mod tests { composer_parameters.clone(), now, TritonVmJobQueue::get_instance(), - global_state_lock - .cli() - .proof_job_options_primitive_witness(), + global_state_lock.cli().into(), + TransactionProofType::PrimitiveWitness, ) .await .unwrap(); @@ -4787,7 +4786,7 @@ pub(crate) mod tests { // set up premine recipient let network = Network::Main; let cli_args = cli_args::Args { - tx_proving_capability: Some(TxProvingCapability::SingleProof), + vm_proving_capability: Some(TransactionProofType::SingleProof.into()), network, ..Default::default() }; @@ -4824,7 +4823,7 @@ pub(crate) mod tests { let config = TxCreationConfig::default() .recover_change_on_chain(change_key.into()) - .with_prover_capability(TxProvingCapability::ProofCollection); + .with_prover_capability(TransactionProofType::ProofCollection); let proof_collection_transaction = alice .api() @@ -4843,7 +4842,7 @@ pub(crate) mod tests { let rando_wallet_secret = WalletEntropy::new_pseudorandom(rng.random()); let rando_cli_args = cli_args::Args { fee_notification: upgrade_fee_notification_policy, - tx_proving_capability: Some(TxProvingCapability::SingleProof), + vm_proving_capability: Some(TransactionProofType::SingleProof.into()), // necessary to allow proof-collection -> single-proof upgrades for foreign transactions compose: true, network, @@ -4880,6 +4879,7 @@ pub(crate) mod tests { &alice, now, TritonVmProofJobOptions::default(), + TransactionProofType::SingleProof, ) .await .unwrap(); @@ -4957,6 +4957,7 @@ pub(crate) mod tests { &alice, block_one.header().timestamp + Timestamp::minutes(10), TritonVmProofJobOptions::default(), + TransactionProofType::SingleProof, ) .await .unwrap(); diff --git a/src/peer_loop.rs b/src/peer_loop.rs index f9c568e32..09715f5f1 100644 --- a/src/peer_loop.rs +++ b/src/peer_loop.rs @@ -1950,12 +1950,12 @@ mod tests { use super::*; use crate::config_models::cli_args; use crate::config_models::network::Network; + use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount; use crate::models::peer::peer_block_notifications::PeerBlockNotification; use crate::models::peer::transaction_notification::TransactionNotification; use crate::models::state::mempool::TransactionOrigin; use crate::models::state::tx_creation_config::TxCreationConfig; - use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::wallet::wallet_entropy::WalletEntropy; use crate::tests::shared::fake_valid_block_for_tests; use crate::tests::shared::fake_valid_sequence_of_blocks_for_tests; @@ -3435,7 +3435,7 @@ mod tests { let now = genesis_block.kernel.header.timestamp; let config = TxCreationConfig::default() .recover_change_off_chain(spending_key.into()) - .with_prover_capability(TxProvingCapability::ProofCollection); + .with_prover_capability(TransactionProofType::ProofCollection); let transaction_1: Transaction = state_lock .api() .tx_initiator_internal() @@ -3520,7 +3520,7 @@ mod tests { let now = genesis_block.kernel.header.timestamp; let config = TxCreationConfig::default() .recover_change_off_chain(spending_key.into()) - .with_prover_capability(TxProvingCapability::ProofCollection); + .with_prover_capability(TransactionProofType::ProofCollection); let transaction_1: Transaction = state_lock .api() .tx_initiator_internal() @@ -3729,8 +3729,8 @@ mod tests { let genesis_block = alice.chain.light_state(); let in_seven_months = genesis_block.header().timestamp + Timestamp::months(7); let prover_capability = match quality { - TransactionProofQuality::ProofCollection => TxProvingCapability::ProofCollection, - TransactionProofQuality::SingleProof => TxProvingCapability::SingleProof, + TransactionProofQuality::ProofCollection => TransactionProofType::ProofCollection, + TransactionProofQuality::SingleProof => TransactionProofType::SingleProof, }; let config = TxCreationConfig::default() .recover_change_off_chain(alice_key.into()) diff --git a/src/rpc_server.rs b/src/rpc_server.rs index 0312a459c..d969d5f0d 100644 --- a/src/rpc_server.rs +++ b/src/rpc_server.rs @@ -96,7 +96,7 @@ use crate::models::state::mining_status::MiningStatus; use crate::models::state::transaction_details::TransactionDetails; use crate::models::state::transaction_kernel_id::TransactionKernelId; use crate::models::state::tx_creation_artifacts::TxCreationArtifacts; -use crate::models::state::tx_proving_capability::TxProvingCapability; +use crate::models::state::vm_proving_capability::VmProvingCapability; use crate::models::state::wallet::address::encrypted_utxo_notification::EncryptedUtxoNotification; use crate::models::state::wallet::address::BaseKeyType; use crate::models::state::wallet::address::BaseSpendingKey; @@ -140,7 +140,7 @@ pub struct DashBoardOverviewDataFromClient { // `None` symbolizes failure to get mining status pub mining_status: Option, - pub proving_capability: TxProvingCapability, + pub proving_capability: VmProvingCapability, // # of confirmations of the last wallet balance change. // @@ -4832,7 +4832,6 @@ mod tests { use cli_args::Args; use super::*; - use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::tests::shared::invalid_block_with_transaction; use crate::tests::shared::invalid_empty_block; @@ -4881,7 +4880,7 @@ mod tests { let (blocks, alice_to_bob_utxo_notifications, bob_amount) = { let wallet_entropy = WalletEntropy::new_random(); let cli_args = cli_args::Args { - tx_proving_capability: Some(TxProvingCapability::ProofCollection), + vm_proving_capability: Some(TransactionProofType::ProofCollection.into()), network, ..Default::default() }; @@ -5038,7 +5037,7 @@ mod tests { let network = Network::Main; let bob_wallet = WalletEntropy::new_random(); let cli_args = cli_args::Args { - tx_proving_capability: Some(TxProvingCapability::ProofCollection), + vm_proving_capability: Some(TransactionProofType::ProofCollection.into()), network, ..Default::default() }; @@ -5220,7 +5219,7 @@ mod tests { let mut rng = StdRng::seed_from_u64(1815); let network = Network::Main; let cli_args = cli_args::Args { - tx_proving_capability: Some(TxProvingCapability::ProofCollection), + vm_proving_capability: Some(TransactionProofType::ProofCollection.into()), network, ..Default::default() }; @@ -5265,7 +5264,7 @@ mod tests { // ), // fee, // timestamp, - // TxProvingCapability::PrimitiveWitness, + // TransactionProofType::PrimitiveWitness, // ) // .await; assert!(result.is_ok()); @@ -5291,7 +5290,7 @@ mod tests { let mut rng = StdRng::seed_from_u64(1815); let network = Network::Main; let cli_args = cli_args::Args { - tx_proving_capability: Some(TxProvingCapability::SingleProof), + vm_proving_capability: Some(TransactionProofType::SingleProof.into()), network, ..Default::default() }; @@ -5363,7 +5362,7 @@ mod tests { let mut rng = StdRng::seed_from_u64(1814); let network = Network::Main; let cli_args = cli_args::Args { - tx_proving_capability: Some(TxProvingCapability::ProofCollection), + vm_proving_capability: Some(TransactionProofType::ProofCollection.into()), network, ..Default::default() }; @@ -5499,7 +5498,7 @@ mod tests { // ), // fee, // timestamp, - // TxProvingCapability::ProofCollection, + // TransactionProofType::ProofCollection, // ) // .await; diff --git a/src/tests/shared.rs b/src/tests/shared.rs index e68786c37..0fdd30f08 100644 --- a/src/tests/shared.rs +++ b/src/tests/shared.rs @@ -75,6 +75,7 @@ use crate::models::blockchain::transaction::transaction_kernel::tests::pseudoran use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernelModifier; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernelProxy; +use crate::models::blockchain::transaction::transaction_proof::TransactionProofType; use crate::models::blockchain::transaction::utxo::Utxo; use crate::models::blockchain::transaction::validity::neptune_proof::Proof; use crate::models::blockchain::transaction::validity::single_proof::SingleProof; @@ -950,7 +951,8 @@ pub(crate) async fn make_mock_block_with_puts_and_guesser_preimage_and_guesser_f composer_parameters, block_timestamp, TritonVmJobQueue::get_instance(), - cli.proof_job_options_primitive_witness(), + (&cli).into(), + TransactionProofType::PrimitiveWitness, ) .await .unwrap(); @@ -1084,6 +1086,7 @@ pub(crate) async fn mine_block_to_wallet_invalid_block_proof( global_state_lock, timestamp, Default::default(), + TransactionProofType::SingleProof, ) .await?; diff --git a/src/triton_vm_job_queue/mod.rs b/src/triton_vm_job_queue/mod.rs index 8c1e29bd5..29cba0541 100644 --- a/src/triton_vm_job_queue/mod.rs +++ b/src/triton_vm_job_queue/mod.rs @@ -34,12 +34,60 @@ impl DerefMut for TritonVmJobQueue { } impl TritonVmJobQueue { - /// returns the triton vm job queue (singleton). + /// returns the single, shared triton vm job queue (singleton). /// /// callers should execute resource intensive triton-vm tasks in this /// queue to avoid running simultaneous tasks that could exceed hardware /// capabilities. + #[cfg(not(test))] pub fn get_instance() -> Arc { + Self::get_instance_internal() + } + + /// returns a triton vm job queue. + /// + /// By default, the returned queue will be a shared singleton instance. + /// + /// When running unit tests, a shared instance has these characteristics: + /// + /// 1. tests must wait for each other's jobs to complete. CPU usage tends to + /// be low for much of the testing duration. + /// 2. it is possible to run all tests in parallel and generate proofs when + /// proofs do not exist in local cache or on proof-server. + /// 3. total time for running all tests increases substantially compared to + /// scenario where each test using its own job-queue instance. + /// + /// A non-shared instance has these characteristics: + /// + /// 1. tests run independently and use up all CPU cores. + /// 2. it is not possible to run all tests in parallel and generate proofs + /// as it would exhaust device's resources, especially RAM. This mode + /// only works well when proofs are already cached. A workaround is to + /// run with --test-threads 1 to generate proofs. + /// 3. total time for running all tests decrease substantially (assuming + /// proofs are cached) vs the shared-instance scenario. + /// + /// When running unit tests, shared mode is the default. The mode can be + /// selected at runtime via: + /// + /// ```text + /// # disable shared queue (each test gets it's own queue) + /// VM_JOB_QUEUE_SHARED=false cargo test + /// + /// # enable shared queue (default behavior) + /// VM_JOB_QUEUE_SHARED=true cargo test + /// ``` + #[cfg(test)] + pub fn get_instance() -> Arc { + let shared = std::env::var("VM_JOB_QUEUE_SHARED").unwrap_or_else(|_| "true".to_string()); + + match shared.as_str() { + "false" => Arc::new(Self(JobQueue::::start())), + "true" | &_ => Self::get_instance_internal(), + } + } + + fn get_instance_internal() -> Arc { use std::sync::OnceLock; static INSTANCE: OnceLock> = OnceLock::new(); INSTANCE diff --git a/src/util_types/log_vm_state.rs b/src/util_types/log_vm_state.rs new file mode 100644 index 000000000..9f7141ea5 --- /dev/null +++ b/src/util_types/log_vm_state.rs @@ -0,0 +1,144 @@ +use std::io::Write; +use std::path::PathBuf; + +use crate::models::blockchain::shared::Hash as NeptuneHash; +use crate::triton_vm::prelude::Program; +use crate::triton_vm::proof::Claim; +use crate::triton_vm::vm::NonDeterminism; +use crate::triton_vm::vm::VMState; + +/// enumerates types of proofs that can be logged. +/// +/// this type facilitates offering distinct logging modes for: +/// +/// 1. `MayContainWalletSecrets` (any kind of Proof) +/// 2. `DoesNotContainWalletSecrets` +/// +/// The `MayContainWalletSecrets` mode is useful for logging inputs to every +/// single Proof that is generated. These include proofs generated from +/// `PrimitiveWitness` meaning that the claims expose secrets and should not be +/// shared. This mode of logging is considered a security risk, but can be +/// useful for investigating or researching alone, or on testnet(s), etc. +/// +/// The `DoesNotContainWalletSecrets` mode is useful when logging proof inputs for +/// purposes of sharing with others, eg neptune-core developers for debugging. +/// However it does not log any Proofs generated from a `PrimitiveWitness` +/// and thus does not leak wallet secrets. +pub enum LogProofInputsType { + /// log proof inputs that may contain wallet secrets + MayContainWalletSecrets, + + /// log proof inputs that do not contain wallet secrets + DoesNotContainWalletSecrets, +} + +impl LogProofInputsType { + /// returns name of logging environment variable + /// + /// each variant has an environment variable that specifies the + /// directory in which to write proof files. + pub const fn env_var_name(&self) -> &str { + match *self { + Self::MayContainWalletSecrets => "NEPTUNE_VM_STATE_WITH_SECRETS_DIR", + Self::DoesNotContainWalletSecrets => "NEPTUNE_VM_STATE_NO_SECRETS_DIR", + } + } + + /// returns file name prefix + pub const fn file_prefix(&self) -> &str { + match *self { + Self::MayContainWalletSecrets => "vm_state.unsafe_to_share", + Self::DoesNotContainWalletSecrets => "vm_state.safe_to_share", + } + } +} + +/// If the environment variable specified by [LogProofInputsType::env_var_name()] is set, +/// write the initial VM state to file `/...json`. +/// +/// where: +/// DIR = value of environment variable. +/// prefix = LogProofInputsType::file_prefix() +/// pid = process-id +/// claim = hex-encoded hash of input Claim +/// +/// This file can be used to debug the program using the [Triton TUI]: +/// ```sh +/// triton-tui --initial-state +/// ``` +/// +/// [Triton TUI]: https://crates.io/crates/triton-tui +/// +/// Security: +/// +/// Files of type [LogProofInputsType::MayContainWalletSecrets] should only be +/// used for debugging by the wallet owner as they may contain wallet secrets. +/// +/// Files of type [LogProofInputsType::DoesNotContainWalletSecrets] can be shared +/// with others eg, neptune-core developers, for purposes of +/// debugging/assistance. +/// +/// It is the *callers* responsibility to ensure that the provided claim matches +/// the `log_proof_inputs_type` +pub fn maybe_write<'a, F>( + log_proof_inputs_type: LogProofInputsType, + program: Program, + claim: &Claim, + nondeterminism: F, +) -> Result, LogVmStateError> +where + F: FnOnce() -> NonDeterminism + Send + Sync + 'a, +{ + let Ok(dir) = std::env::var(log_proof_inputs_type.env_var_name()) else { + return Ok(None); + }; + let prefix = log_proof_inputs_type.file_prefix(); + + write(&dir, prefix, program, claim, nondeterminism()).inspect_err(|e| tracing::warn!("{}", e)) +} + +fn write( + dir: &str, + file_prefix: &str, + program: Program, + claim: &Claim, + nondeterminism: NonDeterminism, +) -> Result, LogVmStateError> { + let vm_state = VMState::new(program, claim.input.clone().into(), nondeterminism); + + let filename = format!( + "{}.{}.{}.json", + file_prefix, + std::process::id(), + NeptuneHash::hash(claim).to_hex(), + ); + + let path = PathBuf::from(dir).join(filename); + + let mut state_file = + std::fs::File::create(&path).map_err(|e| LogVmStateError::from((path.clone(), e)))?; + let state = serde_json::to_string(&vm_state)?; + write!(state_file, "{}", state).map_err(|e| LogVmStateError::from((path.clone(), e)))?; + Ok(Some(path)) +} + +#[derive(Debug, thiserror::Error, strum::EnumIs)] +#[non_exhaustive] +pub enum LogVmStateError { + #[error("could not obtain padded-height due to program execution error")] + IoError { + path: PathBuf, + #[source] + source: std::io::Error, + }, + + #[error(transparent)] + SerializeError(#[from] serde_json::Error), +} + +impl From<(PathBuf, std::io::Error)> for LogVmStateError { + fn from(v: (PathBuf, std::io::Error)) -> Self { + let (path, source) = v; + Self::IoError { path, source } + } +} diff --git a/src/util_types/mod.rs b/src/util_types/mod.rs index 54878f992..7b9e29af7 100644 --- a/src/util_types/mod.rs +++ b/src/util_types/mod.rs @@ -1,4 +1,5 @@ pub mod archival_mmr; +pub(crate) mod log_vm_state; pub mod mutator_set; pub mod rusty_archival_block_mmr; diff --git a/tests/common/genesis_node.rs b/tests/common/genesis_node.rs index d7c881162..599bb688e 100644 --- a/tests/common/genesis_node.rs +++ b/tests/common/genesis_node.rs @@ -11,11 +11,11 @@ use std::str::FromStr; use neptune_cash::api::export::GlobalStateLock; use neptune_cash::api::export::Network; use neptune_cash::api::export::TransactionKernelId; +use neptune_cash::api::export::TransactionProofType; use neptune_cash::config_models::cli_args::Args; use neptune_cash::config_models::data_directory::DataDirectory; use neptune_cash::models::blockchain::block::block_height::BlockHeight; use neptune_cash::models::proof_abstractions::timestamp::Timestamp; -use neptune_cash::models::state::tx_proving_capability::TxProvingCapability; use rand::distr::Alphanumeric; use rand::distr::SampleString; use tokio::task::JoinHandle; @@ -56,7 +56,7 @@ impl GenesisNode { // we default proving capability to primitive-witness as lowest common // denominator and because otherwise dev machines often miss this case. - args.tx_proving_capability = Some(TxProvingCapability::PrimitiveWitness); + args.vm_proving_capability = Some(TransactionProofType::PrimitiveWitness.into()); if let Ok(dd) = Self::integration_test_data_directory(args.network) { args.data_dir = Some(dd.root_dir_path()); diff --git a/tests/send_and_receive_basic.rs b/tests/send_and_receive_basic.rs index 3146ab3d7..ebeb74601 100644 --- a/tests/send_and_receive_basic.rs +++ b/tests/send_and_receive_basic.rs @@ -6,7 +6,8 @@ use neptune_cash::api::export::KeyType; use neptune_cash::api::export::NativeCurrencyAmount; use neptune_cash::api::export::SymmetricKey; use neptune_cash::api::export::Timestamp; -use neptune_cash::api::export::TxProvingCapability; +use neptune_cash::api::export::TransactionProofType; +use neptune_cash::api::export::VmProvingCapability; use num_traits::ops::checked::CheckedSub; /// test: alice sends funds to herself onchain @@ -80,7 +81,7 @@ pub async fn alice_sends_to_self() -> anyhow::Result<()> { pub async fn alice_sends_to_bob_with_primitive_witness_capability() -> anyhow::Result<()> { alice_sends_to_bob( &GenesisNode::cluster_id(), - TxProvingCapability::PrimitiveWitness, + TransactionProofType::PrimitiveWitness, ) .await } @@ -92,7 +93,7 @@ pub async fn alice_sends_to_bob_with_primitive_witness_capability() -> anyhow::R pub async fn alice_sends_to_bob_with_proof_collection_capability() -> anyhow::Result<()> { alice_sends_to_bob( &GenesisNode::cluster_id(), - TxProvingCapability::PrimitiveWitness, + TransactionProofType::PrimitiveWitness, ) .await } @@ -104,7 +105,7 @@ pub async fn alice_sends_to_bob_with_proof_collection_capability() -> anyhow::Re pub async fn alice_sends_to_bob_with_single_proof_capability() -> anyhow::Result<()> { alice_sends_to_bob( &GenesisNode::cluster_id(), - TxProvingCapability::PrimitiveWitness, + TransactionProofType::PrimitiveWitness, ) .await } @@ -128,13 +129,13 @@ pub async fn alice_sends_to_bob_with_single_proof_capability() -> anyhow::Result /// 6. bob verifies the unconfirmed balance matches payment amount. pub async fn alice_sends_to_bob( cluster_id: &str, - proving_capability: TxProvingCapability, + proving_capability: impl Into, ) -> anyhow::Result<()> { logging::tracing_logger(); let timeout_secs = 5; let mut base_args = GenesisNode::default_args(); - base_args.tx_proving_capability = Some(proving_capability); + base_args.vm_proving_capability = Some(proving_capability.into()); // alice and bob start 2 peer cluster (regtest) let [mut alice, mut bob] =