From e70052fa4629eeba907ef73ac035efbc9ad37b29 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 16 May 2024 17:08:19 +0200 Subject: [PATCH] Fixes masp benchmarks --- crates/benches/Cargo.toml | 34 ++-- crates/benches/native_vps.rs | 322 +++++++++++++++++++++-------------- 2 files changed, 212 insertions(+), 144 deletions(-) diff --git a/crates/benches/Cargo.toml b/crates/benches/Cargo.toml index ac0f05f9fb..b34aa7e76a 100644 --- a/crates/benches/Cargo.toml +++ b/crates/benches/Cargo.toml @@ -12,30 +12,30 @@ readme.workspace = true repository.workspace = true version.workspace = true -[[bench]] -name = "allowed_txs" -harness = false -path = "txs.rs" +# [[bench]] +# name = "allowed_txs" +# harness = false +# path = "txs.rs" [[bench]] name = "native_vps" harness = false path = "native_vps.rs" -[[bench]] -name = "process_wrapper" -harness = false -path = "process_wrapper.rs" +# [[bench]] +# name = "process_wrapper" +# harness = false +# path = "process_wrapper.rs" -[[bench]] -name = "host_env" -harness = false -path = "host_env.rs" +# [[bench]] +# name = "host_env" +# harness = false +# path = "host_env.rs" -[[bench]] -name = "wasm_opcodes" -harness = false -path = "wasm_opcodes.rs" +# [[bench]] +# name = "wasm_opcodes" +# harness = false +# path = "wasm_opcodes.rs" [features] namada-eth-bridge = [ @@ -50,7 +50,7 @@ namada-eth-bridge = [ namada = { path = "../namada", features = ["rand", "benches"] } namada_apps = { path = "../apps", features = ["benches"] } masp_primitives.workspace = true -masp_proofs = { workspace = true, features = ["multicore"] } +masp_proofs = { workspace = true, features = ["benchmarks"] } borsh.workspace = true borsh-ext.workspace = true criterion = { version = "0.5", features = ["html_reports"] } diff --git a/crates/benches/native_vps.rs b/crates/benches/native_vps.rs index e825a25bc5..1f2a412182 100644 --- a/crates/benches/native_vps.rs +++ b/crates/benches/native_vps.rs @@ -893,22 +893,20 @@ fn masp_final_check(c: &mut Criterion) { } #[derive(Debug)] -enum RequestedItem { - Signature, +enum BenchNote { Spend, Convert, Output, } -// Removes the unneeded notes from the generated transaction and replicates the -// remaining ones if needed +// Tweaks the transaction to match the desired benchmark fn customize_masp_tx_data( multi: bool, - request: &RequestedItem, -) -> Option<( + request: &BenchNote, +) -> ( TransactionData, Transaction, -)> { +) { let (_, _, tx) = setup_storage_for_masp_verification("unshielding"); let transaction = tx .sections @@ -924,84 +922,47 @@ fn customize_masp_tx_data( let mut sapling_bundle = transaction.sapling_bundle().unwrap().to_owned(); match request { - RequestedItem::Signature => { + BenchNote::Spend => { if multi { - // No multisig benchmark - return None; - } - // ensure we only have one signature to verify (the binding one) and - // no proofs - sapling_bundle.shielded_spends.clear(); - sapling_bundle.shielded_converts.clear(); - sapling_bundle.shielded_outputs.clear(); - assert_eq!(sapling_bundle.shielded_spends.len(), 0); - assert_eq!(sapling_bundle.shielded_converts.len(), 0); - assert_eq!(sapling_bundle.shielded_outputs.len(), 0); - } - RequestedItem::Spend => { - if multi { - // ensure we only have two spend proofs + // ensure we have two spend proofs sapling_bundle.shielded_spends = [ sapling_bundle.shielded_spends.clone(), sapling_bundle.shielded_spends, ] .concat(); - sapling_bundle.shielded_outputs.clear(); - sapling_bundle.shielded_converts.clear(); assert_eq!(sapling_bundle.shielded_spends.len(), 2); } else { - // ensure we only have one spend proof - sapling_bundle.shielded_outputs.clear(); - sapling_bundle.shielded_converts.clear(); + // ensure we have one spend proof assert_eq!(sapling_bundle.shielded_spends.len(), 1); } - assert_eq!(sapling_bundle.shielded_converts.len(), 0); - assert_eq!(sapling_bundle.shielded_outputs.len(), 0); } - RequestedItem::Convert => { + BenchNote::Convert => { if multi { - // ensure we only have two convert proofs + // ensure we have two convert proofs sapling_bundle.shielded_converts = [ sapling_bundle.shielded_converts.clone(), sapling_bundle.shielded_converts, ] .concat(); - sapling_bundle.shielded_spends.clear(); - sapling_bundle.shielded_outputs.clear(); assert_eq!(sapling_bundle.shielded_converts.len(), 2); } else { - // ensure we only have one convert proof - sapling_bundle.shielded_spends.clear(); - sapling_bundle.shielded_outputs.clear(); + // ensure we have one convert proof assert_eq!(sapling_bundle.shielded_converts.len(), 1); } - assert_eq!(sapling_bundle.shielded_spends.len(), 0); - assert_eq!(sapling_bundle.shielded_outputs.len(), 0); } - RequestedItem::Output => { - // From the cost remove the cost of signature(s) validation + BenchNote::Output => { if multi { - // ensure we only have two output proofs - sapling_bundle.shielded_outputs = [ - sapling_bundle.shielded_outputs.clone(), - sapling_bundle.shielded_outputs, - ] - .concat(); - sapling_bundle.shielded_spends.clear(); - sapling_bundle.shielded_converts.clear(); + // ensure we have two output proofs assert_eq!(sapling_bundle.shielded_outputs.len(), 2); } else { - // ensure we only have one output proof - sapling_bundle.shielded_spends.clear(); - sapling_bundle.shielded_converts.clear(); + // ensure we have one output proof + sapling_bundle.shielded_outputs.truncate(1); assert_eq!(sapling_bundle.shielded_outputs.len(), 1); } - assert_eq!(sapling_bundle.shielded_spends.len(), 0); - assert_eq!(sapling_bundle.shielded_converts.len(), 0); } }; - Some(( + ( TransactionData::from_parts( transaction.version(), transaction.consensus_branch_id(), @@ -1011,84 +972,188 @@ fn customize_masp_tx_data( Some(sapling_bundle), ), transaction, - )) + ) } -// For signatures: it's impossible to benchmark more than one signature without -// pulling in the cost of a proof, so we just benchmark the cost of one binding -// signature and then use its cost. We don't discount by the numebr of cores -// because there might be the need for a call to the fallback single -// verification.. For proofs: benchmarks both one and two proofs and take the -// difference as the variable cost for every proofs/sigs. Compute the cost of -// the non parallel parts (with the diff) and charge that if at least one note -// is present, then charge the variable cost multiplied by the number of notes -// and divided by the number of cores -fn masp_batch_validate(c: &mut Criterion) { - let mut group = c.benchmark_group("masp_batch_validate"); - let PVKs { - spend_vk, - convert_vk, - output_vk, - } = preload_verifying_keys(); - - for multi in [true, false] { - for bench in [ - RequestedItem::Signature, - RequestedItem::Spend, - RequestedItem::Convert, - RequestedItem::Output, - ] { - // From the cost of proofs remove the cost of signature(s) - // validation - let (tx_data, transaction) = - if let Some(data) = customize_masp_tx_data(multi, &bench) { - data - } else { - continue; - }; +// benchmark the cost of validating two signatures in a batch. +fn masp_batch_signature_verification(c: &mut Criterion) { + let (_, _, tx) = setup_storage_for_masp_verification("unshielding"); + let transaction = tx + .sections + .into_iter() + .filter_map(|section| match section { + Section::MaspTx(transaction) => Some(transaction), + _ => None, + }) + .collect::>() + .first() + .unwrap() + .to_owned(); + let sapling_bundle = transaction.sapling_bundle().unwrap(); + // ensure we have two signatures to verify (the binding and one spending) + assert_eq!(sapling_bundle.shielded_spends.len(), 1); - // Partially deauthorize the transparent bundle - let unauth_tx_data = - partial_deauthorize(transaction.deref()).unwrap(); - let txid_parts = unauth_tx_data.digest(TxIdDigester); - let sighash = signature_hash( - &unauth_tx_data, - &SignableInput::Shielded, - &txid_parts, - ) + // Partially deauthorize the transparent bundle + let unauth_tx_data = partial_deauthorize(transaction.deref()).unwrap(); + let txid_parts = unauth_tx_data.digest(TxIdDigester); + let sighash = + signature_hash(&unauth_tx_data, &SignableInput::Shielded, &txid_parts) .as_ref() .to_owned(); - let sapling_bundle = tx_data.sapling_bundle().unwrap(); - let bench_name = if multi { - format!("{:#?}_multi", bench) - } else { - format!("{:#?}_single", bench) - }; - group.bench_function(bench_name, |b| { - b.iter_batched( - || { - let mut ctx = BatchValidator::new(); - // Check bundle first - if !ctx.check_bundle(sapling_bundle.to_owned(), sighash) - { - panic!("Failed check bundle"); - } - - ctx - }, - |ctx| { - assert!( - ctx.validate( - spend_vk, convert_vk, output_vk, OsRng - ) - ) - }, - BatchSize::SmallInput, - ) - }); - } + c.bench_function("masp_batch_signature_verification", |b| { + b.iter_batched( + || { + let mut ctx = BatchValidator::new(); + // Check bundle first + if !ctx.check_bundle(sapling_bundle.to_owned(), sighash) { + panic!("Failed check bundle"); + } + + ctx + }, + |ctx| assert!(ctx.verify_signatures(OsRng).is_ok()), + BatchSize::SmallInput, + ) + }); +} + +// Benchmark both one and two proofs and take the difference as the variable +// cost for every proofs. Charge the full cost for the first note and then +// charge the variable cost multiplied by the number of remaining notes and +// divided by the number of cores +fn masp_batch_spend_proofs_validate(c: &mut Criterion) { + let mut group = c.benchmark_group("masp_batch_spend_proofs_validate"); + let PVKs { spend_vk, .. } = preload_verifying_keys(); + + for double in [true, false] { + let (tx_data, transaction) = + customize_masp_tx_data(double, &BenchNote::Spend); + + // Partially deauthorize the transparent bundle + let unauth_tx_data = partial_deauthorize(transaction.deref()).unwrap(); + let txid_parts = unauth_tx_data.digest(TxIdDigester); + // Compute the sighash from the original, unmodified transaction + let sighash = signature_hash( + &unauth_tx_data, + &SignableInput::Shielded, + &txid_parts, + ) + .as_ref() + .to_owned(); + let sapling_bundle = tx_data.sapling_bundle().unwrap(); + + let bench_name = if double { "double" } else { "single" }; + group.bench_function(bench_name, |b| { + b.iter_batched( + || { + let mut ctx = BatchValidator::new(); + // Check bundle first + if !ctx.check_bundle(sapling_bundle.to_owned(), sighash) { + panic!("Failed check bundle"); + } + + ctx + }, + |ctx| assert!(ctx.verify_spend_proofs(spend_vk).is_ok()), + BatchSize::SmallInput, + ) + }); } + + group.finish(); +} + +// Benchmark both one and two proofs and take the difference as the variable +// cost for every proofs. Charge the full cost for the first note and then +// charge the variable cost multiplied by the number of remaining notes and +// divided by the number of cores +fn masp_batch_convert_proofs_validate(c: &mut Criterion) { + let mut group = c.benchmark_group("masp_batch_convert_proofs_validate"); + let PVKs { convert_vk, .. } = preload_verifying_keys(); + + for double in [true, false] { + let (tx_data, transaction) = + customize_masp_tx_data(double, &BenchNote::Convert); + + // Partially deauthorize the transparent bundle + let unauth_tx_data = partial_deauthorize(transaction.deref()).unwrap(); + let txid_parts = unauth_tx_data.digest(TxIdDigester); + // Compute the sighash from the original, unmodified transaction + let sighash = signature_hash( + &unauth_tx_data, + &SignableInput::Shielded, + &txid_parts, + ) + .as_ref() + .to_owned(); + let sapling_bundle = tx_data.sapling_bundle().unwrap(); + + let bench_name = if double { "double" } else { "single" }; + group.bench_function(bench_name, |b| { + b.iter_batched( + || { + let mut ctx = BatchValidator::new(); + // Check bundle first + if !ctx.check_bundle(sapling_bundle.to_owned(), sighash) { + panic!("Failed check bundle"); + } + + ctx + }, + |ctx| assert!(ctx.verify_convert_proofs(convert_vk).is_ok()), + BatchSize::SmallInput, + ) + }); + } + + group.finish(); +} + +// Benchmark both one and two proofs and take the difference as the variable +// cost for every proofs. Charge the full cost for the first note and then +// charge the variable cost multiplied by the number of remaining notes and +// divided by the number of cores +fn masp_batch_output_proofs_validate(c: &mut Criterion) { + let mut group = c.benchmark_group("masp_batch_output_proofs_validate"); + let PVKs { output_vk, .. } = preload_verifying_keys(); + + for double in [true, false] { + let (tx_data, transaction) = + customize_masp_tx_data(double, &BenchNote::Output); + + // Partially deauthorize the transparent bundle + let unauth_tx_data = partial_deauthorize(transaction.deref()).unwrap(); + let txid_parts = unauth_tx_data.digest(TxIdDigester); + // Compute the sighash from the original, unmodified transaction + let sighash = signature_hash( + &unauth_tx_data, + &SignableInput::Shielded, + &txid_parts, + ) + .as_ref() + .to_owned(); + let sapling_bundle = tx_data.sapling_bundle().unwrap(); + + let bench_name = if double { "double" } else { "single" }; + group.bench_function(bench_name, |b| { + b.iter_batched( + || { + let mut ctx = BatchValidator::new(); + // Check bundle first + if !ctx.check_bundle(sapling_bundle.to_owned(), sighash) { + panic!("Failed check bundle"); + } + + ctx + }, + |ctx| assert!(ctx.verify_output_proofs(output_vk).is_ok()), + BatchSize::SmallInput, + ) + }); + } + + group.finish(); } fn pgf(c: &mut Criterion) { @@ -1687,7 +1752,10 @@ criterion_group!( masp_check_convert, masp_check_output, masp_final_check, - masp_batch_validate, + masp_batch_signature_verification, + masp_batch_spend_proofs_validate, + masp_batch_convert_proofs_validate, + masp_batch_output_proofs_validate, vp_multitoken, pgf, eth_bridge_nut,