Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
99 changes: 99 additions & 0 deletions scripts/php/find_slow_tests.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env php
<?php

// This script is useful for finding slow unit tests.
//
// The script sorts the output of `cargo test` by duration of
// each test, and then prints the results in descending order.
//
// It also prints stats at the bottom, eg:
//
// --- unit test stats ---
// Count: 733
// Total: 974.801
// Min: 0.000
// Max: 104.073
// Mean: 1.3298785811733
// Median: 0.118
// ----------------------
//
// As of this writing, it is necessary to use the nightly compiler
// in order to obtain timing for each test.
//
// Here is example usage:
//
// cargo +nightly test -- -Z unstable-options --report-time 2>&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";
}

Comment on lines +1 to +99
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last time I tried to install PHP to run one of the scripts in this repo, I ended up corrupting my operating system and I had to reinstall ubuntu from scratch. (It was certainly not the fault of the script I was trying to run because I had not even gotten to that point.) I am not tempted to try again to see what went wrong the first time.

This is not a vendetta against PHP. I actually think it is a fantastic language and great at what it does. But I do want to raise the question whether it is a good idea to include PHP scripts in this repository. They obviously come with a huge dependency even if the fallout of installing that dependency does not include corrupting the operating system.

Possible alternatives:

  • bash
  • rust
  • python
  • nothing

If the issue is, "who is going to write the bash/rust/python equivalent?" then I wonder how far we get with asking AI to transcribe it.

If the issue is, "who is going to maintain it?" then PHP is the most ill-suited option for random new developers. (Okay, scratch that. Bash is. But PHP is second. The point remains that rust and python are both more accessible and more popular.)

2 changes: 1 addition & 1 deletion src/api/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
16 changes: 2 additions & 14 deletions src/api/tx_initiation/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down Expand Up @@ -127,17 +120,12 @@
//!
//! # async fn example(tx_details: TransactionDetails, gsl: GlobalStateLock) -> anyhow::Result<TransactionProof> {
//!
//! // 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)
Expand Down
17 changes: 5 additions & 12 deletions src/api/tx_initiation/builder/proof_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
75 changes: 58 additions & 17 deletions src/api/tx_initiation/builder/transaction_proof_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
///
Expand All @@ -62,6 +64,7 @@ pub struct TransactionProofBuilder<'a> {
job_queue: Option<Arc<TritonVmJobQueue>>,
proof_job_options: Option<TritonVmProofJobOptions>,
valid_mock: Option<bool>,
transaction_proof_type: Option<TransactionProofType>,
}

impl<'a> TransactionProofBuilder<'a> {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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());
Comment on lines +259 to +260
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This .into() hides a lot of logic. If I understand correctly, it is mapping a VmProvingCapability to a TransactionProofType by selecting the TransactionProofType whose corresponding VmProvingCapability is the largest subject to being smaller than the given vm_proving_capability. A mapping as complex and non-trivial as that warrants an explicit, descriptive name. For instance: most_complex_transaction_proof_possible.

In contrast, .into() should be reserved for cases where target type and concrete mapping are so obvious that the extra explicit indication of a dedicated function can be omitted.


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;
Expand Down Expand Up @@ -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())
Expand All @@ -311,8 +346,16 @@ async fn gen_single<'a, F>(
valid_mock: bool,
) -> Result<TransactionProof, CreateProofError>
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::NoWitness,
&claim,
nondeterminism.clone(),
);

Ok(TransactionProof::SingleProof(
ProofBuilder::new()
.program(SingleProof.program())
Expand All @@ -333,14 +376,12 @@ async fn from_witness(
witness_cow: Cow<'_, PrimitiveWitness>,
job_queue: Arc<TritonVmJobQueue>,
proof_job_options: TritonVmProofJobOptions,
transaction_proof_type: TransactionProofType,
valid_mock: bool,
) -> Result<TransactionProof, CreateProofError> {
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())
}
Expand All @@ -356,16 +397,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())
}
Expand Down
Loading