Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Tests for Segment Proving Without Keccak Tables #648

Merged
merged 23 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
98 changes: 98 additions & 0 deletions evm_arithmetization/src/fixed_recursive_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2944,3 +2944,101 @@ where
circuit.verifier_only.circuit_digest.elements.len()
+ (1 << circuit.common.config.fri_config.cap_height) * NUM_HASH_OUT_ELTS
}

#[cfg(test)]
mod tests {
use plonky2::field::goldilocks_field::GoldilocksField;
use plonky2::plonk::config::PoseidonGoldilocksConfig;
use plonky2::timed;

use super::*;
use crate::testing_utils::{dummy_payload, init_logger};

type F = GoldilocksField;
const D: usize = 2;
type C = PoseidonGoldilocksConfig;

#[test]
#[ignore]
fn test_segment_proof_generation_without_keccak() -> anyhow::Result<()> {
let timing = &mut TimingTree::new("Segment Proof Generation", log::Level::Info);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This will capture everything, I'd assume you want to initialize it right before proving?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it is what I intend to do. I also want to know recursive circuits generation time.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, the timing tree name is slightly misleading then but not really blocking,

init_logger();

let all_stark = AllStark::<F, D>::default();
let config = StarkConfig::standard_fast_config();

let all_circuits = timed!(
timing,
log::Level::Info,
"Create all recursive circuits",
AllRecursiveCircuits::<F, C, D>::new(
&all_stark,
&[
16..17,
8..9,
9..10,
4..5,
8..9,
4..5,
17..18,
17..18,
17..18
],
&config,
)
);

// Generate a dummy payload for testing
let dummy_payload = timed!(
timing,
log::Level::Info,
"Generate dummy payload",
dummy_payload(100, true)?
);

let max_cpu_len_log = 9;
let segment_iterator = SegmentDataIterator::<F>::new(&dummy_payload, Some(max_cpu_len_log));

let mut proofs_without_keccak = vec![];

let skip_proofs_before_index = 3;
for (i, segment_run) in segment_iterator.enumerate() {
if i < skip_proofs_before_index {
continue;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems highly specific, as any modification in the KERNEL ASM may break your assumption that the 3rd segment has no keccak operation.
Also this is breaking compilation with cdk_erigon feature flag, as in this case we're missing a range for the Poseidon table in the initialization of AllRecursiveCircuits

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I agree. Do you have any suggestions on how to craft a payload that generates segments without Keccak? Regarding cdk_erigon, I can add a feature flag to disable it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, I think we'd need some around segment generation. Basically we'd want to do the same as in init_exc_stop.rs but I don't think this will play well with how we actually generate segment data.


// Process and prove segment
let (_, mut segment_data) =
segment_run.map_err(|e: SegmentError| anyhow::format_err!(e))?;
let segment_proof = timed!(
timing,
log::Level::Info,
"Prove segment",
all_circuits.prove_segment(
&all_stark,
&config,
dummy_payload.trim(),
&mut segment_data,
timing,
None,
)?
);

proofs_without_keccak.push(segment_proof);
break; // Process only one proof
}

// Verify the generated segment proof
timed!(
timing,
log::Level::Info,
"Verify segment proof",
all_circuits.verify_root(proofs_without_keccak[0].proof_with_pis.clone())?
);

// Print timing details
timing.print();

Ok(())
}
}
84 changes: 82 additions & 2 deletions evm_arithmetization/src/testing_utils.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
//! A set of utility functions and constants to be used by `evm_arithmetization`
//! unit and integration tests.

use anyhow::Result;
use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV};
use ethereum_types::{BigEndianHash, H256, U256};
use ethereum_types::{Address, BigEndianHash, H256, U256};
use hex_literal::hex;
use keccak_hash::keccak;
use mpt_trie::{
nibbles::Nibbles,
partial_trie::{HashedPartialTrie, Node, PartialTrie},
};
use plonky2::field::goldilocks_field::GoldilocksField;

pub use crate::cpu::kernel::cancun_constants::*;
pub use crate::cpu::kernel::constants::global_exit_root::*;
use crate::{generation::mpt::AccountRlp, proof::BlockMetadata, util::h2u};
use crate::generation::TrieInputs;
use crate::proof::TrieRoots;
use crate::{generation::mpt::AccountRlp, proof::BlockMetadata, util::h2u, GenerationInputs};

pub const EMPTY_NODE_HASH: H256 = H256(hex!(
"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
Expand Down Expand Up @@ -161,3 +165,79 @@ pub fn scalable_contract_from_storage(storage_trie: &HashedPartialTrie) -> Accou
..Default::default()
}
}

/// Get `GenerationInputs` for a dummy payload, where the block has the given
/// timestamp.
pub fn dummy_payload(
timestamp: u64,
is_first_payload: bool,
) -> Result<GenerationInputs<GoldilocksField>> {
let beneficiary = hex!("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");

let block_metadata = BlockMetadata {
block_beneficiary: Address::from(beneficiary),
block_timestamp: timestamp.into(),
block_number: 1.into(),
block_difficulty: 0x020000.into(),
block_random: H256::from_uint(&0x020000.into()),
block_gaslimit: 0xff112233u32.into(),
block_chain_id: 1.into(),
block_base_fee: 0xa.into(),
..Default::default()
};

let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries()?;
let checkpoint_state_trie_root = state_trie_before.hash();
let mut beacon_roots_account_storage = storage_tries[0].1.clone();

update_beacon_roots_account_storage(
&mut beacon_roots_account_storage,
block_metadata.block_timestamp,
block_metadata.parent_beacon_block_root,
)?;
let updated_beacon_roots_account =
beacon_roots_contract_from_storage(&beacon_roots_account_storage);

if !is_first_payload {
// This isn't the first dummy payload being processed. We need to update the
// initial state trie to account for the update on the beacon roots contract.
state_trie_before.insert(
beacon_roots_account_nibbles(),
rlp::encode(&updated_beacon_roots_account).to_vec(),
)?;
storage_tries[0].1 = beacon_roots_account_storage;
}

let tries_before = TrieInputs {
state_trie: state_trie_before,
storage_tries,
..Default::default()
};

let expected_state_trie_after: HashedPartialTrie = {
let mut state_trie_after = HashedPartialTrie::from(crate::Node::Empty);
state_trie_after.insert(
beacon_roots_account_nibbles(),
rlp::encode(&updated_beacon_roots_account).to_vec(),
)?;

state_trie_after
};

let trie_roots_after = TrieRoots {
state_root: expected_state_trie_after.hash(),
transactions_root: tries_before.transactions_trie.hash(),
receipts_root: tries_before.receipts_trie.hash(),
};

let inputs = GenerationInputs {
tries: tries_before.clone(),
burn_addr: None,
trie_roots_after,
checkpoint_state_trie_root,
block_metadata,
..Default::default()
};

Ok(inputs)
}
90 changes: 3 additions & 87 deletions evm_arithmetization/tests/two_to_one_block.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
#![cfg(feature = "eth_mainnet")]

use ethereum_types::{Address, BigEndianHash, H256};
use evm_arithmetization::fixed_recursive_verifier::{
extract_block_final_public_values, extract_two_to_one_block_hash,
};
use evm_arithmetization::generation::{GenerationInputs, TrieInputs};
use evm_arithmetization::proof::{
BlockMetadata, FinalPublicValues, PublicValues, TrieRoots, EMPTY_CONSOLIDATED_BLOCKHASH,
};
use evm_arithmetization::testing_utils::{
beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger,
preinitialized_state_and_storage_tries, update_beacon_roots_account_storage,
};
use evm_arithmetization::{AllRecursiveCircuits, AllStark, Node, StarkConfig};
use hex_literal::hex;
use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie};
use evm_arithmetization::proof::{FinalPublicValues, PublicValues};
use evm_arithmetization::testing_utils::{dummy_payload, init_logger};
use evm_arithmetization::{AllRecursiveCircuits, AllStark, StarkConfig};
use plonky2::field::goldilocks_field::GoldilocksField;
use plonky2::field::types::Field;
use plonky2::hash::poseidon::PoseidonHash;
use plonky2::plonk::config::{Hasher, PoseidonGoldilocksConfig};
use plonky2::plonk::proof::ProofWithPublicInputs;
Expand All @@ -26,80 +16,6 @@ type F = GoldilocksField;
const D: usize = 2;
type C = PoseidonGoldilocksConfig;

/// Get `GenerationInputs` for a dummy payload, where the block has the given
/// timestamp.
fn dummy_payload(timestamp: u64, is_first_payload: bool) -> anyhow::Result<GenerationInputs<F>> {
let beneficiary = hex!("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");

let block_metadata = BlockMetadata {
block_beneficiary: Address::from(beneficiary),
block_timestamp: timestamp.into(),
block_number: 1.into(),
block_difficulty: 0x020000.into(),
block_random: H256::from_uint(&0x020000.into()),
block_gaslimit: 0xff112233u32.into(),
block_chain_id: 1.into(),
block_base_fee: 0xa.into(),
..Default::default()
};

let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries()?;
let checkpoint_state_trie_root = state_trie_before.hash();
let mut beacon_roots_account_storage = storage_tries[0].1.clone();

update_beacon_roots_account_storage(
&mut beacon_roots_account_storage,
block_metadata.block_timestamp,
block_metadata.parent_beacon_block_root,
)?;
let updated_beacon_roots_account =
beacon_roots_contract_from_storage(&beacon_roots_account_storage);

if !is_first_payload {
// This isn't the first dummy payload being processed. We need to update the
// initial state trie to account for the update on the beacon roots contract.
state_trie_before.insert(
beacon_roots_account_nibbles(),
rlp::encode(&updated_beacon_roots_account).to_vec(),
)?;
storage_tries[0].1 = beacon_roots_account_storage;
}

let tries_before = TrieInputs {
state_trie: state_trie_before,
storage_tries,
..Default::default()
};

let expected_state_trie_after: HashedPartialTrie = {
let mut state_trie_after = HashedPartialTrie::from(Node::Empty);
state_trie_after.insert(
beacon_roots_account_nibbles(),
rlp::encode(&updated_beacon_roots_account).to_vec(),
)?;

state_trie_after
};

let trie_roots_after = TrieRoots {
state_root: expected_state_trie_after.hash(),
transactions_root: tries_before.transactions_trie.hash(),
receipts_root: tries_before.receipts_trie.hash(),
};

let inputs = GenerationInputs {
tries: tries_before.clone(),
burn_addr: None,
trie_roots_after,
checkpoint_state_trie_root,
checkpoint_consolidated_hash: EMPTY_CONSOLIDATED_BLOCKHASH.map(F::from_canonical_u64),
block_metadata,
..Default::default()
};

Ok(inputs)
}

fn get_test_block_proof(
timestamp: u64,
all_circuits: &AllRecursiveCircuits<GoldilocksField, PoseidonGoldilocksConfig, 2>,
Expand Down
Loading