From 64a7c43abbb986bccb1d1e8532d711b269644994 Mon Sep 17 00:00:00 2001 From: nemo Date: Tue, 6 Jul 2021 09:11:49 -0400 Subject: [PATCH] feat: add a srs key loading bench (#1474) * feat: add a srs key loading bench * feat: separate aggregation benches from preprocessing * feat: add an aggregate proof encode/decode test * fix: separate aggregate proof test bytes from test * fix: improve tests by adding specific constants expected * doc: update comment and rename argument variable * feat: re-factor aggregation testing --- filecoin-proofs/Cargo.toml | 4 + filecoin-proofs/benches/aggregation.rs | 166 ++++++++++++++++ filecoin-proofs/src/lib.rs | 2 +- filecoin-proofs/tests/aggregate_proof_bytes | Bin 0 -> 29044 bytes filecoin-proofs/tests/api.rs | 207 +++++++++++++------- storage-proofs-core/src/compound_proof.rs | 14 +- 6 files changed, 320 insertions(+), 73 deletions(-) create mode 100644 filecoin-proofs/benches/aggregation.rs create mode 100644 filecoin-proofs/tests/aggregate_proof_bytes diff --git a/filecoin-proofs/Cargo.toml b/filecoin-proofs/Cargo.toml index 981370de8..196bba978 100644 --- a/filecoin-proofs/Cargo.toml +++ b/filecoin-proofs/Cargo.toml @@ -82,3 +82,7 @@ blst = [ [[bench]] name = "preprocessing" harness = false + +[[bench]] +name = "aggregation" +harness = false diff --git a/filecoin-proofs/benches/aggregation.rs b/filecoin-proofs/benches/aggregation.rs new file mode 100644 index 000000000..88ca02974 --- /dev/null +++ b/filecoin-proofs/benches/aggregation.rs @@ -0,0 +1,166 @@ +use std::sync::Once; +use std::time::Duration; + +use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; +use filecoin_proofs::{ + caches::{get_stacked_srs_key, get_stacked_srs_verifier_key}, + get_seal_inputs, PoRepConfig, PoRepProofPartitions, SectorShape2KiB, SectorShape32GiB, + SectorSize, POREP_PARTITIONS, SECTOR_SIZE_2_KIB, SECTOR_SIZE_32_GIB, +}; +use rand::{thread_rng, Rng}; +use storage_proofs_core::{api_version::ApiVersion, is_legacy_porep_id}; + +static INIT_LOGGER: Once = Once::new(); +fn init_logger() { + INIT_LOGGER.call_once(|| { + fil_logger::init(); + }); +} + +fn bench_seal_inputs(c: &mut Criterion) { + let params = vec![1, 256, 512, 1024]; + + let mut rng = thread_rng(); + + let porep_id_v1_1: u64 = 5; // This is a RegisteredSealProof value + + let mut porep_id = [0u8; 32]; + porep_id[..8].copy_from_slice(&porep_id_v1_1.to_le_bytes()); + assert!(!is_legacy_porep_id(porep_id)); + + let config = PoRepConfig { + sector_size: SectorSize(SECTOR_SIZE_2_KIB), + partitions: PoRepProofPartitions( + *POREP_PARTITIONS + .read() + .expect("POREP_PARTITIONS poisoned") + .get(&SECTOR_SIZE_2_KIB) + .expect("unknown sector size"), + ), + porep_id, + api_version: ApiVersion::V1_1_0, + }; + let comm_r = [5u8; 32]; + let comm_d = [6u8; 32]; + let prover_id = [7u8; 32]; + + let ticket = rng.gen(); + let seed = rng.gen(); + let sector_id = rng.gen::().into(); + + let mut group = c.benchmark_group("bench_seal_inputs"); + for iterations in params { + group + .bench_function(format!("get-seal-inputs-{}", iterations), |b| { + b.iter(|| { + for _ in 0..iterations { + get_seal_inputs::( + config, comm_r, comm_d, prover_id, sector_id, ticket, seed, + ) + .expect("get seal inputs failed"); + } + }); + }) + .sample_size(10) + .throughput(Throughput::Bytes(iterations as u64)) + .warm_up_time(Duration::from_secs(1)); + } + + group.finish(); +} + +fn bench_stacked_srs_key(c: &mut Criterion) { + init_logger(); + let params = vec![128, 256, 512, 1024]; + + let porep_id_v1_1: u64 = 5; // This is a RegisteredSealProof value + + let mut porep_id = [0u8; 32]; + porep_id[..8].copy_from_slice(&porep_id_v1_1.to_le_bytes()); + assert!(!is_legacy_porep_id(porep_id)); + + let config = PoRepConfig { + sector_size: SectorSize(SECTOR_SIZE_32_GIB), + partitions: PoRepProofPartitions( + *POREP_PARTITIONS + .read() + .expect("POREP_PARTITIONS poisoned") + .get(&SECTOR_SIZE_32_GIB) + .expect("unknown sector size"), + ), + porep_id, + api_version: ApiVersion::V1_1_0, + }; + + let mut group = c.benchmark_group("bench-stacked-srs-key"); + for num_proofs_to_aggregate in params { + group.bench_function( + format!("get-stacked-srs-key-{}", num_proofs_to_aggregate), + |b| { + b.iter(|| { + black_box( + get_stacked_srs_key::(config, num_proofs_to_aggregate) + .expect("get stacked srs key failed"), + ) + }) + }, + ); + } + + group.finish(); +} + +fn bench_stacked_srs_verifier_key(c: &mut Criterion) { + init_logger(); + let params = vec![128, 256, 512, 1024]; + + let porep_id_v1_1: u64 = 5; // This is a RegisteredSealProof value + + let mut porep_id = [0u8; 32]; + porep_id[..8].copy_from_slice(&porep_id_v1_1.to_le_bytes()); + assert!(!is_legacy_porep_id(porep_id)); + + let config = PoRepConfig { + sector_size: SectorSize(SECTOR_SIZE_32_GIB), + partitions: PoRepProofPartitions( + *POREP_PARTITIONS + .read() + .expect("POREP_PARTITIONS poisoned") + .get(&SECTOR_SIZE_32_GIB) + .expect("unknown sector size"), + ), + porep_id, + api_version: ApiVersion::V1_1_0, + }; + + let mut group = c.benchmark_group("bench-stacked-srs-verifier-key"); + for num_proofs_to_aggregate in params { + group + .bench_function( + format!("get-stacked-srs-verifier-key-{}", num_proofs_to_aggregate), + |b| { + b.iter(|| { + black_box( + get_stacked_srs_verifier_key::( + config, + num_proofs_to_aggregate, + ) + .expect("get stacked srs key failed"), + ) + }) + }, + ) + .sample_size(10) + .warm_up_time(Duration::from_secs(1)); + } + + group.finish(); +} + +criterion_group!( + benches, + bench_seal_inputs, + bench_stacked_srs_key, + bench_stacked_srs_verifier_key, +); +criterion_main!(benches); diff --git a/filecoin-proofs/src/lib.rs b/filecoin-proofs/src/lib.rs index d6a7c9580..5488fab86 100644 --- a/filecoin-proofs/src/lib.rs +++ b/filecoin-proofs/src/lib.rs @@ -3,6 +3,7 @@ #![warn(clippy::unnecessary_wraps)] #![allow(clippy::upper_case_acronyms)] +pub mod caches; pub mod constants; pub mod param; pub mod parameters; @@ -10,7 +11,6 @@ pub mod pieces; pub mod types; mod api; -mod caches; mod commitment_reader; pub use api::*; diff --git a/filecoin-proofs/tests/aggregate_proof_bytes b/filecoin-proofs/tests/aggregate_proof_bytes new file mode 100644 index 0000000000000000000000000000000000000000..86dc9efff9a6ac6021960a826f9867d95cc148a2 GIT binary patch literal 29044 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 literal 0 HcmV?d00001 diff --git a/filecoin-proofs/tests/api.rs b/filecoin-proofs/tests/api.rs index fa1517776..b972626e8 100644 --- a/filecoin-proofs/tests/api.rs +++ b/filecoin-proofs/tests/api.rs @@ -5,7 +5,9 @@ use std::path::{Path, PathBuf}; use std::sync::Once; use anyhow::{ensure, Result}; -use bellperson::bls::Fr; +use bellperson::bls::{Bls12, Fr}; +use bellperson::groth16; +use bincode::serialize; use ff::Field; use filecoin_hashers::Hasher; use filecoin_proofs::{ @@ -221,14 +223,54 @@ fn test_seal_proof_aggregation_1_2kib_porep_id_v1_1_base_8() -> Result<()> { #[ignore] fn test_seal_proof_aggregation_3_2kib_porep_id_v1_1_base_8() -> Result<()> { let proofs_to_aggregate = 3; // Requires auto-padding - inner_test_seal_proof_aggregation_2kib_porep_id_v1_1_base_8(proofs_to_aggregate) + + let porep_id = ARBITRARY_POREP_ID_V1_1_0; + assert!(!is_legacy_porep_id(porep_id)); + let verified = aggregate_proofs::( + SECTOR_SIZE_2_KIB, + &porep_id, + ApiVersion::V1_1_0, + proofs_to_aggregate, + )?; + assert!(verified); + + Ok(()) } #[test] #[ignore] fn test_seal_proof_aggregation_5_2kib_porep_id_v1_1_base_8() -> Result<()> { let proofs_to_aggregate = 5; // Requires auto-padding - inner_test_seal_proof_aggregation_2kib_porep_id_v1_1_base_8(proofs_to_aggregate) + + let porep_id = ARBITRARY_POREP_ID_V1_1_0; + assert!(!is_legacy_porep_id(porep_id)); + let verified = aggregate_proofs::( + SECTOR_SIZE_2_KIB, + &porep_id, + ApiVersion::V1_1_0, + proofs_to_aggregate, + )?; + assert!(verified); + + Ok(()) +} + +#[test] +#[ignore] +fn test_seal_proof_aggregation_257_2kib_porep_id_v1_1_base_8() -> Result<()> { + let proofs_to_aggregate = 257; // Requires auto-padding + + let porep_id = ARBITRARY_POREP_ID_V1_1_0; + assert!(!is_legacy_porep_id(porep_id)); + let verified = aggregate_proofs::( + SECTOR_SIZE_2_KIB, + &porep_id, + ApiVersion::V1_1_0, + proofs_to_aggregate, + )?; + assert!(verified); + + Ok(()) } #[test] @@ -267,6 +309,60 @@ fn test_seal_proof_aggregation_1_32kib_porep_id_v1_1_base_8() -> Result<()> { Ok(()) } +#[test] +#[ignore] +fn test_seal_proof_aggregation_818_32kib_porep_id_v1_1_base_8() -> Result<()> { + let proofs_to_aggregate = 818; // Requires auto-padding + + let porep_id = ARBITRARY_POREP_ID_V1_1_0; + assert!(!is_legacy_porep_id(porep_id)); + let verified = aggregate_proofs::( + SECTOR_SIZE_32_KIB, + &porep_id, + ApiVersion::V1_1_0, + proofs_to_aggregate, + )?; + assert!(verified); + + Ok(()) +} + +//#[test] +//#[ignore] +//fn test_seal_proof_aggregation_818_32gib_porep_id_v1_1_base_8() -> Result<()> { +// let proofs_to_aggregate = 818; // Requires auto-padding +// +// let porep_id = ARBITRARY_POREP_ID_V1_1_0; +// assert!(!is_legacy_porep_id(porep_id)); +// let verified = aggregate_proofs::( +// SECTOR_SIZE_32_GIB, +// &porep_id, +// ApiVersion::V1_1_0, +// proofs_to_aggregate, +// )?; +// assert!(verified); +// +// Ok(()) +//} + +//#[test] +//#[ignore] +//fn test_seal_proof_aggregation_818_64gib_porep_id_v1_1_base_8() -> Result<()> { +// let proofs_to_aggregate = 818; // Requires auto-padding +// +// let porep_id = ARBITRARY_POREP_ID_V1_1_0; +// assert!(!is_legacy_porep_id(porep_id)); +// let verified = aggregate_proofs::( +// SECTOR_SIZE_64_GIB, +// &porep_id, +// ApiVersion::V1_1_0, +// proofs_to_aggregate, +// )?; +// assert!(verified); +// +// Ok(()) +//} + //#[test] //#[ignore] //fn test_seal_proof_aggregation_1024_2kib_porep_id_v1_1_base_8() -> Result<()> { @@ -281,61 +377,6 @@ fn test_seal_proof_aggregation_1_32kib_porep_id_v1_1_base_8() -> Result<()> { // inner_test_seal_proof_aggregation_2kib_porep_id_v1_1_base_8(proofs_to_aggregate) //} -fn inner_test_seal_proof_aggregation_2kib_porep_id_v1_1_base_8( - proofs_to_aggregate: usize, -) -> Result<()> { - let porep_id_v1_1: u64 = 5; // This is a RegisteredSealProof value - - let mut porep_id = [0u8; 32]; - porep_id[..8].copy_from_slice(&porep_id_v1_1.to_le_bytes()); - assert!(!is_legacy_porep_id(porep_id)); - - let rng = &mut XorShiftRng::from_seed(TEST_SEED); - let prover_fr: DefaultTreeDomain = Fr::random(rng).into(); - let mut prover_id = [0u8; 32]; - prover_id.copy_from_slice(AsRef::<[u8]>::as_ref(&prover_fr)); - - let mut commit_outputs = Vec::with_capacity(proofs_to_aggregate); - let mut commit_inputs = Vec::with_capacity(proofs_to_aggregate); - let mut seeds = Vec::with_capacity(proofs_to_aggregate); - let mut comm_rs = Vec::with_capacity(proofs_to_aggregate); - - let (commit_output, commit_input, seed, comm_r) = - create_seal_for_aggregation::<_, SectorShape2KiB>( - rng, - SECTOR_SIZE_2_KIB, - prover_id, - &porep_id, - ApiVersion::V1_1_0, - )?; - - // duplicate a single proof to desired target for aggregation - for _ in 0..proofs_to_aggregate { - commit_outputs.push(commit_output.clone()); - commit_inputs.extend(commit_input.clone()); - seeds.push(seed); - comm_rs.push(comm_r); - } - - let config = porep_config(SECTOR_SIZE_2_KIB, porep_id, ApiVersion::V1_1_0); - let aggregate_proof = aggregate_seal_commit_proofs::( - config, - &comm_rs, - &seeds, - commit_outputs.as_slice(), - )?; - let verified = verify_aggregate_seal_commit_proofs::( - config, - aggregate_proof, - &comm_rs, - &seeds, - commit_inputs, - )?; - assert!(verified); - - Ok(()) -} - fn aggregate_proofs( sector_size: u64, porep_id: &[u8; 32], @@ -352,16 +393,12 @@ fn aggregate_proofs( let mut seeds = Vec::with_capacity(num_proofs_to_aggregate); let mut comm_rs = Vec::with_capacity(num_proofs_to_aggregate); + let (commit_output, commit_input, seed, comm_r) = + create_seal_for_aggregation::<_, Tree>(rng, sector_size, prover_id, porep_id, api_version)?; + for _ in 0..num_proofs_to_aggregate { - let (commit_output, commit_input, seed, comm_r) = create_seal_for_aggregation::<_, Tree>( - rng, - sector_size, - prover_id, - porep_id, - api_version, - )?; - commit_outputs.push(commit_output); - commit_inputs.extend(commit_input); + commit_outputs.push(commit_output.clone()); + commit_inputs.extend(commit_input.clone()); seeds.push(seed); comm_rs.push(comm_r); } @@ -1474,3 +1511,39 @@ fn create_fake_seal( Ok((sector_id, sealed_sector_file, comm_r, cache_dir)) } + +#[test] +fn test_aggregate_proof_encode_decode() -> Result<()> { + // This byte vector is a natively serialized aggregate proof generated from the + // 'test_seal_proof_aggregation_257_2kib_porep_id_v1_1_base_8' test. + let aggregate_proof_bytes = std::include_bytes!("./aggregate_proof_bytes"); + let expected_aggregate_proof_len = 29_044; + + // Re-construct the aggregate proof from the bytes, using the native deserialization method. + let aggregate_proof: groth16::aggregate::AggregateProof = + groth16::aggregate::AggregateProof::read(std::io::Cursor::new(&aggregate_proof_bytes))?; + let aggregate_proof_count = aggregate_proof.tmipp.gipa.nproofs as usize; + let expected_aggregate_proof_count = 512; + + assert_eq!(aggregate_proof_count, expected_aggregate_proof_count); + + // Re-serialize the proof to ensure a round-trip match. + let mut aggregate_proof_bytes2 = Vec::new(); + aggregate_proof.write(&mut aggregate_proof_bytes2)?; + + assert_eq!(aggregate_proof_bytes.len(), expected_aggregate_proof_len); + assert_eq!(aggregate_proof_bytes.len(), aggregate_proof_bytes2.len()); + assert_eq!(aggregate_proof_bytes, aggregate_proof_bytes2.as_slice()); + + // Note: the native serialization format is more compact than bincode serialization, so assert that here. + let bincode_serialized_proof = serialize(&aggregate_proof)?; + let expected_bincode_serialized_proof_len = 56_436; + + assert!(aggregate_proof_bytes2.len() < bincode_serialized_proof.len()); + assert_eq!( + bincode_serialized_proof.len(), + expected_bincode_serialized_proof_len + ); + + Ok(()) +} diff --git a/storage-proofs-core/src/compound_proof.rs b/storage-proofs-core/src/compound_proof.rs index 6c46823ce..6532a835f 100644 --- a/storage-proofs-core/src/compound_proof.rs +++ b/storage-proofs-core/src/compound_proof.rs @@ -278,14 +278,18 @@ where } /// Given a prover_srs key, a list of groth16 proofs, and an ordered list of seeds - /// (used to derive the PoRep challenges) hashed using FIXME, aggregate them all into + /// (used to derive the PoRep challenges) hashed pair-wise with the comm_rs using sha256, aggregate them all into /// an AggregateProof type. fn aggregate_proofs( prover_srs: &ProverSRS, - hashed_seeds: &[u8], + hashed_seeds_and_comm_rs: &[u8], proofs: &[groth16::Proof], ) -> Result> { - Ok(aggregate_proofs::(prover_srs, hashed_seeds, proofs)?) + Ok(aggregate_proofs::( + prover_srs, + hashed_seeds_and_comm_rs, + proofs, + )?) } /// Verifies the aggregate proof, with respect to the flattened input list. @@ -298,7 +302,7 @@ where fn verify_aggregate_proofs( ip_verifier_srs: &VerifierSRS, pvk: &PreparedVerifyingKey, - hashed_seeds: &[u8], + hashed_seeds_and_comm_rs: &[u8], public_inputs: &[Vec], aggregate_proof: &groth16::aggregate::AggregateProof, ) -> Result { @@ -310,7 +314,7 @@ where &mut rng, public_inputs, aggregate_proof, - hashed_seeds, + hashed_seeds_and_comm_rs, )?) }