From b938b63636509c87a78a9e7405cc4755d15b6c86 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:14:48 -0400 Subject: [PATCH 01/19] Reduce overhead in storage reads and fix stack descriptions (#572) --- .../asm/mpt/linked_list/linked_list.asm | 127 ++++++++---------- .../kernel/asm/mpt/storage/storage_read.asm | 9 +- .../asm/transactions/common_decoding.asm | 9 -- 3 files changed, 60 insertions(+), 85 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm index f48f33186..a28d83779 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm @@ -364,17 +364,6 @@ store_initial_slots_end: JUMP -%macro insert_slot - %stack (addr_key, key, ptr) -> (addr_key, key, ptr, %%after) - %jump(insert_slot) -%%after: - // stack: value_ptr -%endmacro - -%macro insert_slot_no_return - %insert_slot -%endmacro - // Multiplies the value at the top of the stack, denoted by ptr/5, by 5 // and aborts if ptr/5 >= (mem[@GLOBAL_METADATA_ACCOUNTS_LINKED_LIST_NEXT_AVAILABLE] - @SEGMENT_STORAGE_LINKED_LIST)/5. // This way, @SEGMENT_STORAGE_LINKED_LIST + 5*ptr/5 must be pointing to the beginning of a node. @@ -550,18 +539,18 @@ slot_found_write_value: /// Inserts the pair (address_key, storage_key) and payload pointer into the linked list if it is not already present, /// or modifies its payload if it was already present. -/// Returns `payload_ptr` if the storage key was inserted, `original_ptr` if it was already present. +/// Returns `value` if the storage key was inserted, `old_value` if it was already present. global insert_slot: - // stack: addr_key, key, payload_ptr, retdest + // stack: addr_key, key, value, retdest PROVER_INPUT(linked_list::insert_slot) - // stack: pred_ptr/5, addr_key, key, payload_ptr, retdest + // stack: pred_ptr/5, addr_key, key, value, retdest %get_valid_slot_ptr - // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_ptr, addr_key, key, value, retdest DUP1 MLOAD_GENERAL DUP1 - // stack: pred_addr_key, pred_addr_key, pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_addr_key, pred_addr_key, pred_ptr, addr_key, key, value, retdest DUP4 GT DUP3 %eq_const(@SEGMENT_STORAGE_LINKED_LIST) @@ -570,25 +559,25 @@ global insert_slot: // node with key @U256_MAX (and hence we're inserting a new minimum), then // we need to insert a new node. %jumpi(insert_new_slot) - // stack: pred_addr_key, pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_addr_key, pred_ptr, addr_key, key, value, retdest // If we are here we know that addr <= pred_addr. But this is only possible if pred_addr == addr. DUP3 %assert_eq - // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_ptr, addr_key, key, value, retdest DUP1 %increment MLOAD_GENERAL - // stack: pred_key, pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_key, pred_ptr, addr_key, key, value, retdest DUP1 DUP5 GT %jumpi(insert_new_slot) - // stack: pred_key, pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_key, pred_ptr, addr_key, key, value, retdest DUP4 // We know that key <= pred_key. It must hold that pred_key == key. %assert_eq - // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_ptr, addr_key, key, value, retdest - // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_ptr, addr_key, key, value, retdest // Check that this is not a deleted node DUP1 %add_const(@STORAGE_NEXT_NODE_PTR) @@ -599,34 +588,34 @@ global insert_slot: slot_found_write: // The slot was already in the list - // stack: pred_ptr, addr_key, key, payload_ptr, retdest - // Load the the payload pointer and access counter + // stack: pred_ptr, addr_key, key, value, retdest + // Load the old value %add_const(2) DUP1 MLOAD_GENERAL - // stack: orig_payload_ptr, pred_ptr + 2, addr_key, key, payload_ptr, retdest + // stack: old_value, pred_ptr + 2, addr_key, key, value, retdest SWAP1 DUP5 - MSTORE_GENERAL // Store the new payload - %stack (orig_payload_ptr, addr_key, key, payload_ptr, retdest) -> (retdest, orig_payload_ptr) + MSTORE_GENERAL // Store the new value + %stack (old_value, addr_key, key, value, retdest) -> (retdest, old_value) JUMP insert_new_slot: - // stack: pred_addr or pred_key, pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_addr or pred_key, pred_ptr, addr_key, key, value, retdest POP // get the value of the next address %add_const(@STORAGE_NEXT_NODE_PTR) - // stack: next_ptr_ptr, addr_key, key, payload_ptr, retdest + // stack: next_ptr_ptr, addr_key, key, value, retdest %mload_global_metadata(@GLOBAL_METADATA_STORAGE_LINKED_LIST_NEXT_AVAILABLE) DUP2 MLOAD_GENERAL - // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest // Check that this is not a deleted node DUP1 %eq_const(@U256_MAX) %assert_zero DUP1 MLOAD_GENERAL - // stack: next_addr_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + // stack: next_addr_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest DUP1 DUP6 // Here, (addr_key > pred_addr_key) || (pred_ptr == @SEGMENT_ACCOUNTS_LINKED_LIST). @@ -634,77 +623,77 @@ insert_new_slot: LT %jumpi(next_node_ok) // If addr_key <= next_addr_key, then it addr must be equal to next_addr - // stack: next_addr_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + // stack: next_addr_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest DUP5 %assert_eq - // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest DUP1 %increment MLOAD_GENERAL - // stack: next_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + // stack: next_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest DUP1 // This is added just to have the correct stack in next_node_ok DUP7 // The next key must be strictly larger %assert_lt next_node_ok: - // stack: next_addr or next_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + // stack: next_addr or next_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest POP - // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest SWAP2 DUP2 - // stack: new_ptr, next_ptr_ptr, new_ptr, next_ptr, addr_key, key, payload_ptr, retdest + // stack: new_ptr, next_ptr_ptr, new_ptr, next_ptr, addr_key, key, value, retdest MSTORE_GENERAL - // stack: new_ptr, next_ptr, addr_key, key, payload_ptr, retdest + // stack: new_ptr, next_ptr, addr_key, key, value, retdest // Write the address in the new node DUP1 DUP4 MSTORE_GENERAL - // stack: new_ptr, next_ptr, addr_key, key, payload_ptr, retdest + // stack: new_ptr, next_ptr, addr_key, key, value, retdest // Write the key in the new node %increment DUP1 DUP5 MSTORE_GENERAL - // stack: new_ptr + 1, next_ptr, addr_key, key, payload_ptr, retdest - // Store payload_ptr + // stack: new_ptr + 1, next_ptr, addr_key, key, value, retdest + // Store value %increment DUP1 DUP6 MSTORE_GENERAL - // stack: new_ptr + 2, next_ptr, addr_key, key, payload_ptr, retdest - // Store the copy of payload_ptr + // stack: new_ptr + 2, next_ptr, addr_key, key, value, retdest + // Store the copy of value %increment DUP1 DUP6 %clone_slot MSTORE_GENERAL - // stack: new_ptr + 3, next_ptr, addr_key, key, payload_ptr, retdest + // stack: new_ptr + 3, next_ptr, addr_key, key, value, retdest %increment DUP1 - // stack: new_next_ptr, new_next_ptr, next_ptr, addr_key, key, payload_ptr, retdest + // stack: new_next_ptr, new_next_ptr, next_ptr, addr_key, key, value, retdest SWAP2 MSTORE_GENERAL - // stack: new_next_ptr, addr_key, key, payload_ptr, retdest + // stack: new_next_ptr, addr_key, key, value, retdest %increment %mstore_global_metadata(@GLOBAL_METADATA_STORAGE_LINKED_LIST_NEXT_AVAILABLE) - // stack: addr_key, key, payload_ptr, retdest - %stack (addr_key, key, payload_ptr, retdest) -> (retdest, payload_ptr) + // stack: addr_key, key, value, retdest + %stack (addr_key, key, value, retdest) -> (retdest, value) JUMP /// Searches the pair (address_key, storage_key) in the storage the linked list. -/// Returns `payload_ptr` if the storage key was inserted, `original_ptr` if it was already present. +/// Returns `value` if the storage key was inserted, `old_value` if it was already present. global search_slot: - // stack: addr_key, key, payload_ptr, retdest + // stack: addr_key, key, value, retdest PROVER_INPUT(linked_list::insert_slot) - // stack: pred_ptr/5, addr_key, key, payload_ptr, retdest + // stack: pred_ptr/5, addr_key, key, value, retdest %get_valid_slot_ptr - // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_ptr, addr_key, key, value, retdest DUP1 MLOAD_GENERAL DUP1 - // stack: pred_addr_key, pred_addr_key, pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_addr_key, pred_addr_key, pred_ptr, addr_key, key, value, retdest DUP4 GT DUP3 %eq_const(@SEGMENT_STORAGE_LINKED_LIST) @@ -713,25 +702,25 @@ global search_slot: // node with key @U256_MAX (and hence we're inserting a new minimum), then // the slot was not found %jumpi(slot_not_found) - // stack: pred_addr_key, pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_addr_key, pred_ptr, addr_key, key, value, retdest // If we are here we know that addr <= pred_addr. But this is only possible if pred_addr == addr. DUP3 %assert_eq - // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_ptr, addr_key, key, value, retdest DUP1 %increment MLOAD_GENERAL - // stack: pred_key, pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_key, pred_ptr, addr_key, key, value, retdest DUP1 DUP5 GT %jumpi(slot_not_found) - // stack: pred_key, pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_key, pred_ptr, addr_key, key, value, retdest DUP4 // We know that key <= pred_key. It must hold that pred_key == key. %assert_eq - // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_ptr, addr_key, key, value, retdest - // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // stack: pred_ptr, addr_key, key, value, retdest // Check that this is not a deleted node DUP1 %add_const(@STORAGE_NEXT_NODE_PTR) @@ -740,19 +729,19 @@ global search_slot: // The storage key is not in the list. PANIC slot_not_found: - // stack: pred_addr_or_pred_key, pred_ptr, addr_key, key, payload_ptr, retdest - %stack (pred_addr_or_pred_key, pred_ptr, addr_key, key, payload_ptr, retdest) - -> (retdest, payload_ptr) + // stack: pred_addr_or_pred_key, pred_ptr, addr_key, key, value, retdest + %stack (pred_addr_or_pred_key, pred_ptr, addr_key, key, value, retdest) + -> (retdest, value) JUMP slot_found_no_write: // The slot was already in the list - // stack: pred_ptr, addr_key, key, payload_ptr, retdest - // Load the the payload pointer and access counter + // stack: pred_ptr, addr_key, key, value, retdest + // Load the old value %add_const(2) MLOAD_GENERAL - // stack: orig_value, addr_key, key, payload_ptr, retdest - %stack (orig_value, addr_key, key, payload_ptr, retdest) -> (retdest, orig_value) + // stack: old_value, addr_key, key, value, retdest + %stack (old_value, addr_key, key, value, retdest) -> (retdest, old_value) JUMP %macro search_slot @@ -760,7 +749,7 @@ slot_found_no_write: %stack (state_key, storage_key, ptr) -> (state_key, storage_key, ptr, %%after) %jump(search_slot) %%after: - // stack: ptr + // stack: value %endmacro %macro remove_slot @@ -875,7 +864,7 @@ remove_all_slots_end: %stack (addr_key, key) -> (addr_key, key, 0, %%after) %jump(search_slot) %%after: - // stack: slot_ptr + // stack: slot_value %endmacro %macro read_storage_linked_list_w_addr @@ -886,7 +875,7 @@ remove_all_slots_end: %stack (addr_key, key) -> (addr_key, key, 0, %%after) %jump(search_slot) %%after: - // stack: slot_ptr + // stack: slot_value %endmacro %macro first_account diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_read.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_read.asm index d4a7ca36a..882a85973 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_read.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_read.asm @@ -6,19 +6,14 @@ global sload_current: %read_storage_linked_list - // stack: value_ptr, retdest - DUP1 %jumpi(storage_key_exists) - - // Storage key not found. Return default value_ptr = 0, - // which derefs to 0 since @SEGMENT_TRIE_DATA[0] = 0. - %stack (value_ptr, retdest) -> (retdest, 0) + // stack: value, retdest + SWAP1 JUMP // Read a word from the current account's storage trie. // // Pre stack: kexit_info, slot // Post stack: value - global sys_sload: // stack: kexit_info, slot SWAP1 diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm index 6aff07187..0621db533 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm @@ -300,15 +300,6 @@ after_read: sload_with_addr: %read_storage_linked_list_w_addr - // stack: value_ptr, retdest - DUP1 %jumpi(storage_key_exists) - // Storage key not found. Return default value_ptr = 0, - // which derefs to 0 since @SEGMENT_TRIE_DATA[0] = 0. - %stack (value, retdest) -> (retdest, 0) - - JUMP - -global storage_key_exists: // stack: value, retdest SWAP1 JUMP From e1f03dfb96b38e82fde2fd0d164f732a3fe460bd Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:14:58 -0400 Subject: [PATCH 02/19] Fix pairing tests for null accumulated values (#579) --- evm_arithmetization/src/curve_pairings.rs | 38 ++++++++++------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/evm_arithmetization/src/curve_pairings.rs b/evm_arithmetization/src/curve_pairings.rs index f52d024d8..8152bdd05 100644 --- a/evm_arithmetization/src/curve_pairings.rs +++ b/evm_arithmetization/src/curve_pairings.rs @@ -1007,18 +1007,15 @@ mod tests { } // Finally, multiply by the accumulated inverse and check this matches the - // expected value. - let p = CurveAff::::int(acc); - let q = CurveAff::>::GENERATOR; - running_product = running_product * bls381::ate_optim(p, q); - - let expected = if acc == 0 { - Fp12::::ZERO - } else { - Fp12::::UNIT - }; + // expected value. Only do this when acc is nonzero, as the pairing is only + // defined over valid curve points. + if acc != 0 { + let p = CurveAff::::int(acc); + let q = CurveAff::>::GENERATOR; + running_product = running_product * bls381::ate_optim(p, q); + } - assert_eq!(running_product, expected); + assert_eq!(running_product, Fp12::::UNIT); } #[test] @@ -1040,17 +1037,14 @@ mod tests { } // Finally, multiply by the accumulated inverse and check this matches the - // expected value. - let p = CurveAff::::int(acc); - let q = CurveAff::>::GENERATOR; - running_product = running_product * bn254::tate(p, q); - - let expected = if acc == 0 { - Fp12::::ZERO - } else { - Fp12::::UNIT - }; + // expected value. Only do this when acc is nonzero, as the pairing is only + // defined over valid curve points. + if acc != 0 { + let p = CurveAff::::int(acc); + let q = CurveAff::>::GENERATOR; + running_product = running_product * bn254::tate(p, q); + } - assert_eq!(running_product, expected); + assert_eq!(running_product, Fp12::::UNIT); } } From 8b755494df1c5c0371925f47779edc6e181151b7 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:15:17 -0400 Subject: [PATCH 03/19] Expand conditional blocks logic in Kernel parsing (#574) --- .../src/cpu/kernel/evm_asm.pest | 6 +- evm_arithmetization/src/cpu/kernel/parser.rs | 132 ++++++++++++++++-- 2 files changed, 125 insertions(+), 13 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/evm_asm.pest b/evm_arithmetization/src/cpu/kernel/evm_asm.pest index 7c2d645ae..7c82e80ff 100644 --- a/evm_arithmetization/src/cpu/kernel/evm_asm.pest +++ b/evm_arithmetization/src/cpu/kernel/evm_asm.pest @@ -43,7 +43,11 @@ prover_input_instruction = { ^"PROVER_INPUT" ~ "(" ~ prover_input_fn ~ ")" } prover_input_fn = { identifier ~ ("::" ~ identifier)*} nullary_instruction = { identifier } -conditional_block = { ^"#" ~ "[" ~ "cfg" ~ "(" ~ "feature" ~ "=" ~ identifier ~ ")" ~ "]" ~ "{" ~ item* ~ ^"}"} +feature_list = { "feature" ~ "=" ~ identifier ~ ("," ~ identifier)* } +feature_prefix = { "not" | "all" | "any" } +prefixed_feature_list = { feature_prefix ~ "(" ~ feature_list ~ ")"} +conditional_block_args = { prefixed_feature_list | feature_list } +conditional_block = { ^"#" ~ "[" ~ "cfg" ~ "(" ~ conditional_block_args ~ ")" ~ "]" ~ "{" ~ item* ~ ^"}"} file = { SOI ~ item* ~ silent_eoi } silent_eoi = _{ !ANY } diff --git a/evm_arithmetization/src/cpu/kernel/parser.rs b/evm_arithmetization/src/cpu/kernel/parser.rs index 2b117f618..4cbef3c81 100644 --- a/evm_arithmetization/src/cpu/kernel/parser.rs +++ b/evm_arithmetization/src/cpu/kernel/parser.rs @@ -1,6 +1,7 @@ use std::{collections::HashSet, str::FromStr}; use ethereum_types::U256; +use itertools::Itertools; use pest::iterators::Pair; use pest::Parser; @@ -61,13 +62,78 @@ fn parse_item(item: Pair, active_features: &HashSet<&str>) -> Item { } } +enum FeatureGroupRule { + /// Ignore code if any of the listed features is active. + Not, + /// Include code if any of the listed features is active. + Any, + /// Include code if all the listed features are active. + All, +} + +impl FeatureGroupRule { + fn from_rule(string: &str) -> Self { + if string.starts_with("not") { + return Self::Not; + } + if string.starts_with("all") { + return Self::All; + } + + Self::Any + } +} + fn parse_conditional_block(item: Pair, active_features: &HashSet<&str>) -> Item { + /// Outputs true if any of the listed features is in the active set. + fn is_supported( + active_features: &HashSet<&str>, + features_string: &str, + group_rule: FeatureGroupRule, + ) -> bool { + let features = features_string.split(","); + + match group_rule { + FeatureGroupRule::Not => { + for feature in features { + if active_features.contains(feature) { + return false; + } + } + true + } + FeatureGroupRule::Any => { + for feature in features { + if active_features.contains(feature) { + return true; + } + } + false + } + FeatureGroupRule::All => { + for feature in features { + if !active_features.contains(feature) { + return false; + } + } + true + } + } + } + assert_eq!(item.as_rule(), Rule::conditional_block); let mut inner = item.into_inner().peekable(); - let name = inner.next().unwrap().as_str(); + let mut name = inner.next().unwrap().as_str(); + let group_rule = FeatureGroupRule::from_rule(name); + if name.contains(")") { + // Remove last `)` char + name = &name[..name.len() - 1]; + } + let features = name.split(" = ").collect_vec()[1]; + let feature_supported = is_supported(active_features, features, group_rule); - if active_features.contains(&name) { + if feature_supported { Item::ConditionalBlock( name.into(), inner.map(|i| parse_item(i, active_features)).collect(), @@ -247,13 +313,20 @@ mod tests { fn test_feature() { let code = r#" %macro bar_foo - #[cfg(feature = feature_1)] + // requires any of the two features, using the default format + #[cfg(feature = feature_1,feature_2)] { %bar + } + // requires any of the two features, using the `any` identifier + #[cfg(any(feature = feature_1,feature_2))] + { PUSH 3 ADD } %endmacro + + // requires `feature_1` #[cfg(feature = feature_1)] { %macro bar @@ -266,6 +339,7 @@ mod tests { PUSH 1 PUSH 2 + // requires `feature_1` #[cfg(feature = feature_1)] { %bar_foo @@ -280,13 +354,30 @@ mod tests { PUSH 6 DIV + // requires `feature_2` #[cfg(feature = feature_2)] { global foo_4: - PUSH 7 - PUSH 8 + PUSH 7 + // requires to not have `feature_1` + #[cfg(not(feature = feature_1))] + { + DUP1 + } + #[cfg(feature = feature_1)] + { + PUSH 8 + } MOD } + + // requires all features + #[cfg(all(feature = feature_1,feature_2))] + { + global foo_5: + PUSH 1 + POP + } "#; // Test `feature_1`. @@ -296,17 +387,21 @@ mod tests { let final_code = assemble(vec![parsed_code], HashMap::new(), false); let expected_code = r#" + %macro bar_foo + %bar + PUSH 3 + ADD + %endmacro + %macro bar PUSH 2 MUL - PUSH 3 - ADD %endmacro global foo_1: PUSH 1 PUSH 2 - %bar + %bar_foo PUSH 1 PUSH 3 PUSH 4 @@ -330,6 +425,11 @@ mod tests { let final_code = assemble(vec![parsed_code], HashMap::new(), false); let expected_code = r#" + %macro bar_foo + PUSH 3 + ADD + %endmacro + global foo_1: PUSH 1 PUSH 2 @@ -344,7 +444,7 @@ mod tests { global foo_4: PUSH 7 - PUSH 8 + DUP1 MOD "#; @@ -360,17 +460,21 @@ mod tests { let final_code = assemble(vec![parsed_code], HashMap::new(), false); let expected_code = r#" + %macro bar_foo + %bar + PUSH 3 + ADD + %endmacro + %macro bar PUSH 2 MUL - PUSH 3 - ADD %endmacro global foo_1: PUSH 1 PUSH 2 - %bar + %bar_foo PUSH 1 PUSH 3 PUSH 4 @@ -385,6 +489,10 @@ mod tests { PUSH 7 PUSH 8 MOD + + global foo_5: + PUSH 1 + POP "#; let parsed_expected = parse(expected_code, &HashSet::new()); From 9126ececb535a2b80ad1fb0c32507f3813ece8a4 Mon Sep 17 00:00:00 2001 From: Marko Atanasievski Date: Wed, 4 Sep 2024 15:14:15 +0200 Subject: [PATCH 04/19] feat: follow from block interval (#582) * fix: unify block interval stream api * wip: refactor of proving logic * feat: update leader * fix: test scripts * fix: ci * fix: redundand short arguments * fix: build * update: follow from * fix: cleanup * chore: update block polling time * fix: error handling * fix: improve error output * fix: use cli block_time * fix: reviews * fix: comment * fix: nit * chore: passing runtime * fix: tests * fix: optimize * fix: tests * fix: clean up --- .gitignore | 1 + zero_bin/common/src/block_interval.rs | 67 ++++---- zero_bin/common/src/lib.rs | 6 + zero_bin/leader/src/cli.rs | 15 +- zero_bin/leader/src/client.rs | 137 +++++++--------- zero_bin/leader/src/http.rs | 11 +- zero_bin/leader/src/main.rs | 29 +--- zero_bin/leader/src/stdio.rs | 51 ++++-- zero_bin/prover/Cargo.toml | 26 +-- zero_bin/prover/src/cli.rs | 30 +++- zero_bin/prover/src/lib.rs | 224 ++++++++++++++------------ zero_bin/rpc/src/main.rs | 6 +- zero_bin/tools/prove_rpc.sh | 23 ++- zero_bin/tools/prove_stdio.sh | 59 ++++--- 14 files changed, 378 insertions(+), 307 deletions(-) diff --git a/.gitignore b/.gitignore index 3d1dc8c49..667ccfc9c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ *.iml .idea/ .vscode +**/output.log diff --git a/zero_bin/common/src/block_interval.rs b/zero_bin/common/src/block_interval.rs index 5a7a79117..a659976a3 100644 --- a/zero_bin/common/src/block_interval.rs +++ b/zero_bin/common/src/block_interval.rs @@ -1,3 +1,6 @@ +use std::pin::Pin; +use std::sync::Arc; + use alloy::primitives::B256; use alloy::rpc::types::eth::BlockId; use alloy::{hex, providers::Provider, transports::Transport}; @@ -7,8 +10,11 @@ use futures::Stream; use tracing::info; use crate::parsing; +use crate::provider::CachedProvider; -const DEFAULT_BLOCK_TIME: u64 = 1000; +/// The async stream of block numbers. +/// The second bool flag indicates if the element is last in the interval. +pub type BlockIntervalStream = Pin>>>; /// Range of blocks to be processed and proven. #[derive(Debug, PartialEq, Clone)] @@ -21,9 +27,6 @@ pub enum BlockInterval { FollowFrom { // Interval starting block number start_block: u64, - // Block time specified in milliseconds. - // If not set, use the default block time to poll node. - block_time: Option, }, } @@ -44,7 +47,7 @@ impl BlockInterval { /// assert_eq!(BlockInterval::new("0..10").unwrap(), BlockInterval::Range(0..10)); /// assert_eq!(BlockInterval::new("0..=10").unwrap(), BlockInterval::Range(0..11)); /// assert_eq!(BlockInterval::new("32141").unwrap(), BlockInterval::SingleBlockId(BlockId::Number(32141.into()))); - /// assert_eq!(BlockInterval::new("100..").unwrap(), BlockInterval::FollowFrom{start_block: 100, block_time: None}); + /// assert_eq!(BlockInterval::new("100..").unwrap(), BlockInterval::FollowFrom{start_block: 100}); /// ``` pub fn new(s: &str) -> anyhow::Result { if (s.starts_with("0x") && s.len() == 66) || s.len() == 64 { @@ -77,10 +80,7 @@ impl BlockInterval { .map_err(|_| anyhow!("invalid block number '{num}'")) }) .ok_or(anyhow!("invalid block interval range '{s}'"))??; - return Ok(BlockInterval::FollowFrom { - start_block: num, - block_time: None, - }); + return Ok(BlockInterval::FollowFrom { start_block: num }); } // Only single block number is left to try to parse else { @@ -92,16 +92,24 @@ impl BlockInterval { } } - /// Convert the block interval into an async stream of block numbers. - pub fn into_bounded_stream(self) -> anyhow::Result> { + /// Convert the block interval into an async stream of block numbers. The + /// second bool flag indicates if the element is last in the interval. + pub fn into_bounded_stream(self) -> Result { match self { BlockInterval::SingleBlockId(BlockId::Number(num)) => { let num = num .as_number() .ok_or(anyhow!("invalid block number '{num}'"))?; - Ok(futures::stream::iter(num..num + 1)) + let range = (num..num + 1).map(|it| Ok((it, true))).collect::>(); + + Ok(Box::pin(futures::stream::iter(range))) + } + BlockInterval::Range(range) => { + let mut range = range.map(|it| Ok((it, false))).collect::>(); + // Set last element indicator to true + range.last_mut().map(|it| it.as_mut().map(|it| it.1 = true)); + Ok(Box::pin(futures::stream::iter(range))) } - BlockInterval::Range(range) => Ok(futures::stream::iter(range)), _ => Err(anyhow!( "could not create bounded stream from unbounded follow-from interval", )), @@ -126,36 +134,33 @@ impl BlockInterval { /// numbers. Query the blockchain node for the latest block number. pub async fn into_unbounded_stream( self, - provider: ProviderT, - ) -> Result>, anyhow::Error> + cached_provider: Arc>, + block_time: u64, + ) -> Result where - ProviderT: Provider, + ProviderT: Provider + 'static, TransportT: Transport + Clone, { match self { - BlockInterval::FollowFrom { - start_block, - block_time, - } => Ok(try_stream! { + BlockInterval::FollowFrom { start_block } => Ok(Box::pin(try_stream! { let mut current = start_block; loop { - let last_block_number = provider.get_block_number().await.map_err(|e: alloy::transports::RpcError<_>| { + let last_block_number = cached_provider.get_provider().await?.get_block_number().await.map_err(|e: alloy::transports::RpcError<_>| { anyhow!("could not retrieve latest block number from the provider: {e}") })?; if current < last_block_number { current += 1; - yield current; + yield (current, false); } else { info!("Waiting for the new blocks to be mined, requested block number: {current}, \ latest block number: {last_block_number}"); - let block_time = block_time.unwrap_or(DEFAULT_BLOCK_TIME); // No need to poll the node too frequently, waiting // a block time interval for a block to be mined should be enough tokio::time::sleep(tokio::time::Duration::from_millis(block_time)).await; } } - }), + })), _ => Err(anyhow!( "could not create unbounded follow-from stream from fixed bounded interval", )), @@ -214,10 +219,7 @@ mod test { fn can_create_follow_from_block_interval() { assert_eq!( BlockInterval::new("100..").unwrap(), - BlockInterval::FollowFrom { - start_block: 100, - block_time: None - } + BlockInterval::FollowFrom { start_block: 100 } ); } @@ -270,9 +272,14 @@ mod test { .into_bounded_stream() .unwrap(); while let Some(val) = stream.next().await { - result.push(val); + result.push(val.unwrap()); } - assert_eq!(result, Vec::from_iter(1u64..10u64)); + let mut expected = Vec::from_iter(1u64..10u64) + .into_iter() + .map(|it| (it, false)) + .collect::>(); + expected.last_mut().unwrap().1 = true; + assert_eq!(result, expected); } #[test] diff --git a/zero_bin/common/src/lib.rs b/zero_bin/common/src/lib.rs index 4d9fadbda..42ce661d7 100644 --- a/zero_bin/common/src/lib.rs +++ b/zero_bin/common/src/lib.rs @@ -6,3 +6,9 @@ pub mod pre_checks; pub mod prover_state; pub mod provider; pub mod version; + +/// Size of the channel used to send block prover inputs to the per block +/// proving task. If the proving task is slow and can not consume inputs fast +/// enough retrieval of the block prover inputs will block until the proving +/// task consumes some of the inputs. +pub const BLOCK_CHANNEL_SIZE: usize = 16; diff --git a/zero_bin/leader/src/cli.rs b/zero_bin/leader/src/cli.rs index 50c6e7ce1..9cc2de300 100644 --- a/zero_bin/leader/src/cli.rs +++ b/zero_bin/leader/src/cli.rs @@ -51,23 +51,10 @@ pub(crate) enum Command { /// The previous proof output. #[arg(long, short = 'f', value_hint = ValueHint::FilePath)] previous_proof: Option, - /// If provided, write the generated proofs to this directory instead of - /// stdout. - #[arg(long, short = 'o', value_hint = ValueHint::FilePath)] - proof_output_dir: Option, - /// Network block time in milliseconds. This value is used + /// Blockchain network block time in milliseconds. This value is used /// to determine the blockchain node polling interval. #[arg(short, long, env = "ZERO_BIN_BLOCK_TIME", default_value_t = 2000)] block_time: u64, - /// Keep intermediate proofs. Default action is to - /// delete them after the final proof is generated. - #[arg( - short, - long, - env = "ZERO_BIN_KEEP_INTERMEDIATE_PROOFS", - default_value_t = false - )] - keep_intermediate_proofs: bool, /// Backoff in milliseconds for retry requests #[arg(long, default_value_t = 0)] backoff: u64, diff --git a/zero_bin/leader/src/client.rs b/zero_bin/leader/src/client.rs index 61fe9f45f..ea4e5e9ae 100644 --- a/zero_bin/leader/src/client.rs +++ b/zero_bin/leader/src/client.rs @@ -1,17 +1,15 @@ -use std::io::Write; -use std::path::PathBuf; use std::sync::Arc; use alloy::rpc::types::{BlockId, BlockNumberOrTag, BlockTransactionsKind}; use alloy::transports::http::reqwest::Url; -use anyhow::Result; +use anyhow::{anyhow, Result}; use paladin::runtime::Runtime; use proof_gen::proof_types::GeneratedBlockProof; -use prover::ProverConfig; +use prover::{BlockProverInput, ProverConfig}; use rpc::{retry::build_http_retry_provider, RpcType}; -use tracing::{error, info, warn}; -use zero_bin_common::block_interval::BlockInterval; -use zero_bin_common::fs::generate_block_proof_file_name; +use tokio::sync::mpsc; +use tracing::info; +use zero_bin_common::block_interval::{BlockInterval, BlockIntervalStream}; use zero_bin_common::pre_checks::check_previous_proof_and_checkpoint; #[derive(Debug)] @@ -20,25 +18,24 @@ pub struct RpcParams { pub rpc_type: RpcType, pub backoff: u64, pub max_retries: u32, + pub block_time: u64, } #[derive(Debug)] -pub struct ProofParams { +pub struct LeaderConfig { pub checkpoint_block_number: u64, pub previous_proof: Option, - pub proof_output_dir: Option, pub prover_config: ProverConfig, - pub keep_intermediate_proofs: bool, } /// The main function for the client. pub(crate) async fn client_main( - runtime: Runtime, + runtime: Arc, rpc_params: RpcParams, block_interval: BlockInterval, - mut params: ProofParams, + mut leader_config: LeaderConfig, ) -> Result<()> { - use futures::{FutureExt, StreamExt}; + use futures::StreamExt; let cached_provider = Arc::new(zero_bin_common::provider::CachedProvider::new( build_http_retry_provider( @@ -48,94 +45,84 @@ pub(crate) async fn client_main( )?, )); check_previous_proof_and_checkpoint( - params.checkpoint_block_number, - ¶ms.previous_proof, + leader_config.checkpoint_block_number, + &leader_config.previous_proof, block_interval.get_start_block()?, )?; // Grab interval checkpoint block state trie. let checkpoint_state_trie_root = cached_provider .get_block( - params.checkpoint_block_number.into(), + leader_config.checkpoint_block_number.into(), BlockTransactionsKind::Hashes, ) .await? .header .state_root; - let mut block_prover_inputs = Vec::new(); - let mut block_interval = block_interval.into_bounded_stream()?; - while let Some(block_num) = block_interval.next().await { + // Create a channel for block prover input and use it to send prover input to + // the proving task. The second element of the tuple is a flag indicating + // whether the block is the last one in the interval. + let (block_tx, block_rx) = + mpsc::channel::<(BlockProverInput, bool)>(zero_bin_common::BLOCK_CHANNEL_SIZE); + let test_only = leader_config.prover_config.test_only; + + // Run proving task + let runtime_ = runtime.clone(); + let proving_task = tokio::spawn(prover::prove( + block_rx, + runtime_, + leader_config.previous_proof.take(), + Arc::new(leader_config.prover_config), + )); + + // Create block interval stream. Could be bounded or unbounded. + let mut block_interval_stream: BlockIntervalStream = match block_interval { + block_interval @ BlockInterval::FollowFrom { .. } => { + block_interval + .into_unbounded_stream(cached_provider.clone(), rpc_params.block_time) + .await? + } + _ => block_interval.into_bounded_stream()?, + }; + + // Iterate over the block interval, retrieve prover input + // and send it to the proving task + while let Some(block_interval_elem) = block_interval_stream.next().await { + let (block_num, is_last_block) = block_interval_elem?; let block_id = BlockId::Number(BlockNumberOrTag::Number(block_num)); - // Get future of prover input for particular block. + // Get prover input for particular block. let block_prover_input = rpc::block_prover_input( cached_provider.clone(), block_id, checkpoint_state_trie_root, rpc_params.rpc_type, ) - .boxed(); - block_prover_inputs.push(block_prover_input); + .await?; + block_tx + .send((block_prover_input, is_last_block)) + .await + .map_err(|e| anyhow!("failed to send block prover input through the channel: {e}"))?; + } + + match proving_task.await { + Ok(Ok(_)) => { + info!("Proving task successfully finished"); + } + Ok(Err(e)) => { + anyhow::bail!("Proving task finished with error: {e:?}"); + } + Err(e) => { + anyhow::bail!("Unable to join proving task, error: {e:?}"); + } } - // If `keep_intermediate_proofs` is not set we only keep the last block - // proof from the interval. It contains all the necessary information to - // verify the whole sequence. - let proved_blocks = prover::prove( - block_prover_inputs, - &runtime, - params.previous_proof.take(), - params.prover_config, - params.proof_output_dir.clone(), - ) - .await; runtime.close().await?; - let proved_blocks = proved_blocks?; - if params.prover_config.test_only { + if test_only { info!("All proof witnesses have been generated successfully."); } else { info!("All proofs have been generated successfully."); } - if !params.prover_config.test_only { - if params.keep_intermediate_proofs { - if params.proof_output_dir.is_some() { - // All proof files (including intermediary) are written to disk and kept - warn!("Skipping cleanup, intermediate proof files are kept"); - } else { - // Output all proofs to stdout - std::io::stdout().write_all(&serde_json::to_vec( - &proved_blocks - .into_iter() - .filter_map(|(_, block)| block) - .collect::>(), - )?)?; - } - } else if let Some(proof_output_dir) = params.proof_output_dir.as_ref() { - // Remove intermediary proof files - proved_blocks - .into_iter() - .rev() - .skip(1) - .map(|(block_number, _)| { - generate_block_proof_file_name(&proof_output_dir.to_str(), block_number) - }) - .for_each(|path| { - if let Err(e) = std::fs::remove_file(path) { - error!("Failed to remove intermediate proof file: {e}"); - } - }); - } else { - // Output only last proof to stdout - if let Some(last_block) = proved_blocks - .into_iter() - .filter_map(|(_, block)| block) - .last() - { - std::io::stdout().write_all(&serde_json::to_vec(&last_block)?)?; - } - } - } - Ok(()) } diff --git a/zero_bin/leader/src/http.rs b/zero_bin/leader/src/http.rs index 39c7333e1..14fa965a4 100644 --- a/zero_bin/leader/src/http.rs +++ b/zero_bin/leader/src/http.rs @@ -12,15 +12,14 @@ use tracing::{debug, error, info}; /// The main function for the HTTP mode. pub(crate) async fn http_main( - runtime: Runtime, + runtime: Arc, port: u16, output_dir: PathBuf, - prover_config: ProverConfig, + prover_config: Arc, ) -> Result<()> { let addr = SocketAddr::from(([0, 0, 0, 0], port)); debug!("listening on {}", addr); - let runtime = Arc::new(runtime); let app = Router::new().route( "/prove", post({ @@ -65,7 +64,7 @@ async fn prove( Json(payload): Json, runtime: Arc, output_dir: PathBuf, - prover_config: ProverConfig, + prover_config: Arc, ) -> StatusCode { debug!("Received payload: {:#?}", payload); @@ -75,7 +74,7 @@ async fn prove( payload .prover_input .prove_test( - &runtime, + runtime, payload.previous.map(futures::future::ok), prover_config, ) @@ -84,7 +83,7 @@ async fn prove( payload .prover_input .prove( - &runtime, + runtime, payload.previous.map(futures::future::ok), prover_config, ) diff --git a/zero_bin/leader/src/main.rs b/zero_bin/leader/src/main.rs index f4a448a3b..dc65f0eac 100644 --- a/zero_bin/leader/src/main.rs +++ b/zero_bin/leader/src/main.rs @@ -1,3 +1,4 @@ +use std::sync::Arc; use std::{env, io}; use std::{fs::File, path::PathBuf}; @@ -16,7 +17,7 @@ use zero_bin_common::{ }; use zero_bin_common::{prover_state::persistence::CIRCUIT_VERSION, version}; -use crate::client::{client_main, ProofParams}; +use crate::client::{client_main, LeaderConfig}; mod cli; mod client; @@ -54,9 +55,7 @@ async fn main() -> Result<()> { } let args = cli::Cli::parse(); - - let runtime = Runtime::from_config(&args.paladin, register()).await?; - + let runtime = Arc::new(Runtime::from_config(&args.paladin, register()).await?); let prover_config: ProverConfig = args.prover_config.into(); // If not in test_only mode and running in emulation mode, we'll need to @@ -73,7 +72,7 @@ async fn main() -> Result<()> { Command::Clean => zero_bin_common::prover_state::persistence::delete_all()?, Command::Stdio { previous_proof } => { let previous_proof = get_previous_proof(previous_proof)?; - stdio::stdio_main(runtime, previous_proof, prover_config).await?; + stdio::stdio_main(runtime, previous_proof, Arc::new(prover_config)).await?; } Command::Http { port, output_dir } => { // check if output_dir exists, is a directory, and is writable @@ -85,7 +84,7 @@ async fn main() -> Result<()> { panic!("output-dir is not a writable directory"); } - http::http_main(runtime, port, output_dir, prover_config).await?; + http::http_main(runtime, port, output_dir, Arc::new(prover_config)).await?; } Command::Rpc { rpc_url, @@ -93,23 +92,12 @@ async fn main() -> Result<()> { block_interval, checkpoint_block_number, previous_proof, - proof_output_dir, block_time, - keep_intermediate_proofs, backoff, max_retries, } => { - let runtime = Runtime::from_config(&args.paladin, register()).await?; let previous_proof = get_previous_proof(previous_proof)?; - let mut block_interval = BlockInterval::new(&block_interval)?; - - if let BlockInterval::FollowFrom { - start_block: _, - block_time: ref mut block_time_opt, - } = block_interval - { - *block_time_opt = Some(block_time); - } + let block_interval = BlockInterval::new(&block_interval)?; info!("Proving interval {block_interval}"); client_main( @@ -119,14 +107,13 @@ async fn main() -> Result<()> { rpc_type, backoff, max_retries, + block_time, }, block_interval, - ProofParams { + LeaderConfig { checkpoint_block_number, previous_proof, - proof_output_dir, prover_config, - keep_intermediate_proofs, }, ) .await?; diff --git a/zero_bin/leader/src/stdio.rs b/zero_bin/leader/src/stdio.rs index 88dd20aac..71db3c6ae 100644 --- a/zero_bin/leader/src/stdio.rs +++ b/zero_bin/leader/src/stdio.rs @@ -1,16 +1,18 @@ -use std::io::{Read, Write}; +use std::io::Read; +use std::sync::Arc; -use anyhow::Result; +use anyhow::{anyhow, Result}; use paladin::runtime::Runtime; use proof_gen::proof_types::GeneratedBlockProof; -use prover::{BlockProverInput, BlockProverInputFuture, ProverConfig}; +use prover::{BlockProverInput, ProverConfig}; +use tokio::sync::mpsc; use tracing::info; /// The main function for the stdio mode. pub(crate) async fn stdio_main( - runtime: Runtime, + runtime: Arc, previous: Option, - prover_config: ProverConfig, + prover_config: Arc, ) -> Result<()> { let mut buffer = String::new(); std::io::stdin().read_to_string(&mut buffer)?; @@ -18,13 +20,36 @@ pub(crate) async fn stdio_main( let des = &mut serde_json::Deserializer::from_str(&buffer); let block_prover_inputs = serde_path_to_error::deserialize::<_, Vec>(des)? .into_iter() - .map(Into::into) - .collect::>(); + .collect::>(); + + let (block_tx, block_rx) = + mpsc::channel::<(BlockProverInput, bool)>(zero_bin_common::BLOCK_CHANNEL_SIZE); + + let runtime_ = runtime.clone(); + let prover_config_ = prover_config.clone(); + let proving_task = tokio::spawn(prover::prove(block_rx, runtime_, previous, prover_config_)); + + let interval_len = block_prover_inputs.len(); + for (index, block_prover_input) in block_prover_inputs.into_iter().enumerate() { + block_tx + .send((block_prover_input, interval_len == index + 1)) + .await + .map_err(|e| anyhow!("Failed to send block prover input through the channel: {e}"))?; + } + + match proving_task.await { + Ok(Ok(_)) => { + info!("Proving task successfully finished"); + } + Ok(Err(e)) => { + anyhow::bail!("Proving task finished with error: {e:?}"); + } + Err(e) => { + anyhow::bail!("Unable to join proving task, error: {e:?}"); + } + } - let proved_blocks = - prover::prove(block_prover_inputs, &runtime, previous, prover_config, None).await; runtime.close().await?; - let proved_blocks = proved_blocks?; if prover_config.test_only { info!("All proof witnesses have been generated successfully."); @@ -32,11 +57,5 @@ pub(crate) async fn stdio_main( info!("All proofs have been generated successfully."); } - let proofs: Vec = proved_blocks - .into_iter() - .filter_map(|(_, proof)| proof) - .collect(); - std::io::stdout().write_all(&serde_json::to_vec(&proofs)?)?; - Ok(()) } diff --git a/zero_bin/prover/Cargo.toml b/zero_bin/prover/Cargo.toml index d96f2ff0f..6295f65b4 100644 --- a/zero_bin/prover/Cargo.toml +++ b/zero_bin/prover/Cargo.toml @@ -9,24 +9,24 @@ keywords.workspace = true categories.workspace = true [dependencies] -serde = { workspace = true } -proof_gen = { workspace = true } -plonky2 = { workspace = true } -plonky2_maybe_rayon = { workspace = true } -trace_decoder = { workspace = true } -tracing = { workspace = true } -paladin-core = { workspace = true } +alloy.workspace = true anyhow = { workspace = true } +clap = {workspace = true, features = ["derive", "string"] } evm_arithmetization = { workspace = true } futures = { workspace = true } -alloy.workspace = true -tokio = { workspace = true } -serde_json = { workspace = true } -ruint = { workspace = true, features = ["num-traits", "primitive-types"] } +num-traits = { workspace = true } ops = { workspace = true } +paladin-core = { workspace = true } +plonky2 = { workspace = true } +plonky2_maybe_rayon = { workspace = true } +proof_gen = { workspace = true } +ruint = { workspace = true, features = ["num-traits", "primitive-types"] } +serde = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true } +trace_decoder = { workspace = true } +tracing = { workspace = true } zero_bin_common = { workspace = true } -num-traits = { workspace = true } -clap = {workspace = true} [features] default = [] diff --git a/zero_bin/prover/src/cli.rs b/zero_bin/prover/src/cli.rs index a49eb2d1d..694194ad9 100644 --- a/zero_bin/prover/src/cli.rs +++ b/zero_bin/prover/src/cli.rs @@ -1,7 +1,16 @@ -use clap::Args; +use std::path::PathBuf; + +use clap::{Args, ValueHint}; const HELP_HEADING: &str = "Prover options"; +// If not provided, default output path is `./proofs/`. +fn get_default_output_path() -> PathBuf { + let mut path = std::env::current_dir().unwrap_or_default(); + path.push("proofs"); + path +} + /// Represents the main configuration structure for the runtime. #[derive(Args, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] pub struct CliProverConfig { @@ -18,6 +27,22 @@ pub struct CliProverConfig { /// generating a proof. #[arg(long, help_heading = HELP_HEADING, default_value_t = false)] test_only: bool, + /// Directory where the generated proofs will be written. + #[arg(long, short = 'o', value_hint = ValueHint::FilePath, default_value = get_default_output_path().into_os_string())] + proof_output_dir: PathBuf, + /// Keep intermediate proofs. Default action is to + /// delete them after the final proof is generated. + #[arg( + short, + long, + env = "ZERO_BIN_KEEP_INTERMEDIATE_PROOFS", + default_value_t = false + )] + keep_intermediate_proofs: bool, + /// Number of blocks in a batch. For every block batch, the prover will + /// generate one proof file. + #[arg(long, default_value_t = 8)] + block_batch_size: usize, } impl From for crate::ProverConfig { @@ -27,6 +52,9 @@ impl From for crate::ProverConfig { max_cpu_len_log: cli.max_cpu_len_log, save_inputs_on_error: cli.save_inputs_on_error, test_only: cli.test_only, + proof_output_dir: cli.proof_output_dir, + keep_intermediate_proofs: cli.keep_intermediate_proofs, + block_batch_size: cli.block_batch_size, } } } diff --git a/zero_bin/prover/src/lib.rs b/zero_bin/prover/src/lib.rs index 0d49ca733..21be514ad 100644 --- a/zero_bin/prover/src/lib.rs +++ b/zero_bin/prover/src/lib.rs @@ -1,40 +1,32 @@ pub mod cli; use std::future::Future; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use std::sync::Arc; -use alloy::primitives::{BlockNumber, U256}; +use alloy::primitives::U256; use anyhow::{Context, Result}; -use futures::{future::BoxFuture, stream::FuturesOrdered, FutureExt, TryFutureExt, TryStreamExt}; +use futures::{future::BoxFuture, FutureExt, TryFutureExt, TryStreamExt}; use num_traits::ToPrimitive as _; use paladin::runtime::Runtime; use proof_gen::proof_types::GeneratedBlockProof; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; +use tokio::sync::mpsc::Receiver; use tokio::sync::oneshot; use trace_decoder::{BlockTrace, OtherBlockData}; -use tracing::info; +use tracing::{error, info}; use zero_bin_common::fs::generate_block_proof_file_name; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct ProverConfig { pub batch_size: usize, pub max_cpu_len_log: usize, pub save_inputs_on_error: bool, pub test_only: bool, -} - -pub type BlockProverInputFuture = std::pin::Pin< - Box> + Send>, ->; - -impl From for BlockProverInputFuture { - fn from(item: BlockProverInput) -> Self { - async fn _from(item: BlockProverInput) -> Result { - Ok(item) - } - Box::pin(_from(item)) - } + pub proof_output_dir: PathBuf, + pub keep_intermediate_proofs: bool, + pub block_batch_size: usize, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -50,9 +42,9 @@ impl BlockProverInput { pub async fn prove( self, - runtime: &Runtime, + runtime: Arc, previous: Option>>, - prover_config: ProverConfig, + prover_config: Arc, ) -> Result { use anyhow::Context as _; use evm_arithmetization::SegmentDataIterator; @@ -63,8 +55,8 @@ impl BlockProverInput { max_cpu_len_log, batch_size, save_inputs_on_error, - test_only: _, - } = prover_config; + .. + } = *prover_config; let block_number = self.get_block_number(); @@ -104,7 +96,7 @@ impl BlockProverInput { Directive::map(IndexedStream::from(segment_data_iterator), &seg_prove_ops) .fold(&seg_agg_ops) - .run(runtime) + .run(&runtime) .map(move |e| { e.map(|p| (idx, proof_gen::proof_types::BatchAggregatableProof::from(p))) }) @@ -114,7 +106,7 @@ impl BlockProverInput { // Fold the batch aggregated proof stream into a single proof. let final_batch_proof = Directive::fold(IndexedStream::new(batch_proof_futs), &batch_agg_ops) - .run(runtime) + .run(&runtime) .await?; if let proof_gen::proof_types::BatchAggregatableProof::Agg(proof) = final_batch_proof { @@ -131,7 +123,7 @@ impl BlockProverInput { prev, save_inputs_on_error, }) - .run(runtime) + .run(&runtime) .await?; info!("Successfully proved block {block_number}"); @@ -144,9 +136,9 @@ impl BlockProverInput { pub async fn prove_test( self, - runtime: &Runtime, + runtime: Arc, previous: Option>>, - prover_config: ProverConfig, + prover_config: Arc, ) -> Result { use std::iter::repeat; @@ -157,8 +149,8 @@ impl BlockProverInput { max_cpu_len_log, batch_size, save_inputs_on_error, - test_only: _, - } = prover_config; + .. + } = *prover_config; let block_number = self.get_block_number(); info!("Testing witness generation for block {block_number}."); @@ -185,7 +177,7 @@ impl BlockProverInput { ); simulation - .run(runtime) + .run(&runtime) .await? .try_for_each(|_| future::ok(())) .await?; @@ -208,94 +200,112 @@ impl BlockProverInput { } } +async fn prove_block( + block: BlockProverInput, + runtime: Arc, + previous_block_proof: Option>>, + prover_config: Arc, +) -> Result { + if prover_config.test_only { + block + .prove_test(runtime, previous_block_proof, prover_config) + .await + } else { + block + .prove(runtime, previous_block_proof, prover_config) + .await + } +} + /// Prove all the blocks in the input, or simulate their execution depending on /// the selected prover configuration. Return the list of block numbers that are /// proved and if the proof data is not saved to disk, return the generated /// block proofs as well. pub async fn prove( - block_prover_inputs: Vec, - runtime: &Runtime, - previous_proof: Option, - prover_config: ProverConfig, - proof_output_dir: Option, -) -> Result)>> { - let mut prev: Option>> = - previous_proof.map(|proof| Box::pin(futures::future::ok(proof)) as BoxFuture<_>); - - let mut results = FuturesOrdered::new(); - for block_prover_input in block_prover_inputs { + mut block_receiver: Receiver<(BlockProverInput, bool)>, + runtime: Arc, + checkpoint_proof: Option, + prover_config: Arc, +) -> Result<()> { + use tokio::task::JoinSet; + let mut block_counter: u64 = 0; + let mut prev_proof: Option>> = + checkpoint_proof.map(|proof| Box::pin(futures::future::ok(proof)) as BoxFuture<_>); + + let mut task_set: JoinSet< + std::result::Result, anyhow::Error>, + > = JoinSet::new(); + while let Some((block_prover_input, is_last_block)) = block_receiver.recv().await { + block_counter += 1; let (tx, rx) = oneshot::channel::(); - let proof_output_dir = proof_output_dir.clone(); - let previous_block_proof = prev.take(); - let fut = async move { - // Get the prover input data from the external source (e.g. Erigon node). - let block = block_prover_input.await?; - let block_number = block.get_block_number(); + let prover_config = prover_config.clone(); + let previous_block_proof = prev_proof.take(); + let runtime = runtime.clone(); + let block_number = block_prover_input.get_block_number(); + let _abort_handle = task_set.spawn(async move { + let block_number = block_prover_input.get_block_number(); info!("Proving block {block_number}"); - // Prove the block - let block_proof = if prover_config.test_only { - block - .prove_test(runtime, previous_block_proof, prover_config) - .then(move |proof| async move { - let proof = proof?; - let block_number = proof.b_height; - - // Write latest generated proof to disk if proof_output_dir is provided - // or alternatively return proof as function result. - let return_proof: Option = - if let Some(output_dir) = proof_output_dir { - write_proof_to_dir(output_dir, proof.clone()).await?; - None - } else { - Some(proof.clone()) - }; - - if tx.send(proof).is_err() { - anyhow::bail!("Failed to send proof"); - } - - Ok((block_number, return_proof)) - }) - .await? - } else { - block - .prove(runtime, previous_block_proof, prover_config) - .then(move |proof| async move { - let proof = proof?; - let block_number = proof.b_height; - - // Write latest generated proof to disk if proof_output_dir is provided - // or alternatively return proof as function result. - let return_proof: Option = - if let Some(output_dir) = proof_output_dir { - write_proof_to_dir(output_dir, proof.clone()).await?; - None - } else { - Some(proof.clone()) - }; - - if tx.send(proof).is_err() { - anyhow::bail!("Failed to send proof"); - } - - Ok((block_number, return_proof)) - }) - .await? - }; + let block_proof = prove_block( + block_prover_input, + runtime, + previous_block_proof, + prover_config.clone(), + ) + .then(move |proof| async move { + let proof = proof.inspect_err(|e| { + error!("failed to generate proof for block {block_number}, error {e:?}") + })?; + let block_number = proof.b_height; + + // Write proof to disk if block is last in block batch, + // or if the block is last in the interval (it contains all the necessary + // information to verify the whole sequence). If flag + // `keep_intermediate_proofs` is set, output all block proofs to disk. + let is_block_batch_finished = + block_counter % prover_config.block_batch_size as u64 == 0; + if is_last_block + || prover_config.keep_intermediate_proofs + || is_block_batch_finished + { + write_proof_to_dir(&prover_config.proof_output_dir, proof.clone()) + .await + .inspect_err(|e| error!("failed to output proof for block {block_number} to directory {e:?}"))?; + } + + if tx.send(proof).is_err() { + anyhow::bail!("Failed to send proof for block {block_number}"); + } + + Ok(block_number) + }) + .await; Ok(block_proof) + }); + prev_proof = Some(Box::pin(rx.map_err(move |e| { + error!("failed to receive previous proof for block {block_number}: {e:?}"); + anyhow::Error::new(e) + }))); + if is_last_block { + break; } - .boxed(); - prev = Some(Box::pin(rx.map_err(anyhow::Error::new))); - results.push_back(fut); } - results.try_collect().await + while let Some(res) = task_set.join_next().await { + let _proved_block_height = res???; + } + Ok(()) } /// Write the proof to the `output_dir` directory. -async fn write_proof_to_dir(output_dir: PathBuf, proof: GeneratedBlockProof) -> Result<()> { +async fn write_proof_to_dir(output_dir: &Path, proof: GeneratedBlockProof) -> Result<()> { + // Check if output directory exists, and create one if it doesn't. + if !output_dir.exists() { + info!("Created output directory {:?}", output_dir.display()); + std::fs::create_dir(output_dir)?; + } + let block_proof_file_path = generate_block_proof_file_name(&output_dir.to_str(), proof.b_height); @@ -306,8 +316,14 @@ async fn write_proof_to_dir(output_dir: PathBuf, proof: GeneratedBlockProof) -> tokio::fs::create_dir_all(parent).await?; } - let mut f = tokio::fs::File::create(block_proof_file_path).await?; + let mut f = tokio::fs::File::create(block_proof_file_path.clone()).await?; f.write_all(&proof_serialized) .await - .context("Failed to write proof to disk") + .context("Failed to write proof to disk")?; + + info!( + "Successfully wrote to disk proof file {}", + block_proof_file_path.display() + ); + Ok(()) } diff --git a/zero_bin/rpc/src/main.rs b/zero_bin/rpc/src/main.rs index 7ac9db60c..9ec2b9b62 100644 --- a/zero_bin/rpc/src/main.rs +++ b/zero_bin/rpc/src/main.rs @@ -13,6 +13,7 @@ use prover::BlockProverInput; use rpc::{retry::build_http_retry_provider, RpcParams, RpcType}; use tracing_subscriber::{prelude::*, EnvFilter}; use url::Url; +use zero_bin_common::block_interval::BlockIntervalStream; use zero_bin_common::pre_checks::check_previous_proof_and_checkpoint; use zero_bin_common::provider::CachedProvider; use zero_bin_common::version; @@ -93,8 +94,9 @@ where let block_interval = BlockInterval::Range(params.start_block..params.end_block + 1); let mut block_prover_inputs = Vec::new(); - let mut block_interval = block_interval.clone().into_bounded_stream()?; - while let Some(block_num) = block_interval.next().await { + let mut block_interval: BlockIntervalStream = block_interval.into_bounded_stream()?; + while let Some(block_interval_elem) = block_interval.next().await { + let (block_num, _is_last_block) = block_interval_elem?; let block_id = BlockId::Number(BlockNumberOrTag::Number(block_num)); // Get the prover input for particular block. let result = rpc::block_prover_input( diff --git a/zero_bin/tools/prove_rpc.sh b/zero_bin/tools/prove_rpc.sh index 941521a3c..1c7420491 100755 --- a/zero_bin/tools/prove_rpc.sh +++ b/zero_bin/tools/prove_rpc.sh @@ -17,6 +17,9 @@ export RUST_LOG=info # See also .cargo/config.toml. export RUSTFLAGS='-C target-cpu=native -Zlinker-features=-lld' +BLOCK_BATCH_SIZE="${BLOCK_BATCH_SIZE:-8}" +echo "Block batch size: $BLOCK_BATCH_SIZE" + # Circuit sizes only matter in non test_only mode. if ! [[ $8 == "test_only" ]]; then export ARITHMETIC_CIRCUIT_SIZE="16..21" @@ -76,10 +79,15 @@ if [[ $END_BLOCK == 0x* ]]; then fi # Define block interval -if [ $START_BLOCK == $END_BLOCK ]; then - BLOCK_INTERVAL=$START_BLOCK +if [ $END_BLOCK == '-' ]; then + # Follow from the start block to the end of the chain + BLOCK_INTERVAL=$START_BLOCK.. +elif [ $START_BLOCK == $END_BLOCK ]; then + # Single block + BLOCK_INTERVAL=$START_BLOCK else - BLOCK_INTERVAL=$START_BLOCK..=$END_BLOCK + # Block range + BLOCK_INTERVAL=$START_BLOCK..=$END_BLOCK fi # Print out a warning if the we're using `native` and our file descriptor limit is too low. Don't bother if we can't find `ulimit`. @@ -102,7 +110,7 @@ fi if [[ $8 == "test_only" ]]; then # test only run echo "Proving blocks ${BLOCK_INTERVAL} in a test_only mode now... (Total: ${TOT_BLOCKS})" - command='cargo r --release --bin leader -- --test-only --runtime in-memory --load-strategy on-demand rpc --rpc-type "$NODE_RPC_TYPE" --rpc-url "$NODE_RPC_URL" --block-interval $BLOCK_INTERVAL --proof-output-dir $PROOF_OUTPUT_DIR $PREV_PROOF_EXTRA_ARG --backoff "$BACKOFF" --max-retries "$RETRIES" ' + command='cargo r --release --bin leader -- --test-only --runtime in-memory --load-strategy on-demand --proof-output-dir $PROOF_OUTPUT_DIR --block-batch-size $BLOCK_BATCH_SIZE rpc --rpc-type "$NODE_RPC_TYPE" --rpc-url "$NODE_RPC_URL" --block-interval $BLOCK_INTERVAL $PREV_PROOF_EXTRA_ARG --backoff "$BACKOFF" --max-retries "$RETRIES" ' if [ "$OUTPUT_TO_TERMINAL" = true ]; then eval $command retVal=$? @@ -125,7 +133,7 @@ if [[ $8 == "test_only" ]]; then else # normal run echo "Proving blocks ${BLOCK_INTERVAL} now... (Total: ${TOT_BLOCKS})" - command='cargo r --release --bin leader -- --runtime in-memory --load-strategy on-demand rpc --rpc-type "$NODE_RPC_TYPE" --rpc-url "$3" --block-interval $BLOCK_INTERVAL --proof-output-dir $PROOF_OUTPUT_DIR $PREV_PROOF_EXTRA_ARG --backoff "$BACKOFF" --max-retries "$RETRIES" ' + command='cargo r --release --bin leader -- --runtime in-memory --load-strategy on-demand --proof-output-dir $PROOF_OUTPUT_DIR --block-batch-size $BLOCK_BATCH_SIZE rpc --rpc-type "$NODE_RPC_TYPE" --rpc-url "$3" --block-interval $BLOCK_INTERVAL $PREV_PROOF_EXTRA_ARG --backoff "$BACKOFF" --max-retries "$RETRIES" ' if [ "$OUTPUT_TO_TERMINAL" = true ]; then eval $command echo -e "Proof generation finished with result: $?" @@ -149,14 +157,15 @@ fi # If we're running the verification, we'll do it here. if [ "$RUN_VERIFICATION" = true ]; then - echo "Running the verification" + echo "Running the verification for the last proof..." proof_file_name=$PROOF_OUTPUT_DIR/b$END_BLOCK.zkproof echo "Verifying the proof of the latest block in the interval:" $proof_file_name cargo r --release --bin verifier -- -f $proof_file_name > $PROOF_OUTPUT_DIR/verify.out 2>&1 if grep -q 'All proofs verified successfully!' $PROOF_OUTPUT_DIR/verify.out; then - echo "All proofs verified successfully!"; + echo "$proof_file_name verified successfully!"; + rm $PROOF_OUTPUT_DIR/verify.out else echo "there was an issue with proof verification"; exit 1 diff --git a/zero_bin/tools/prove_stdio.sh b/zero_bin/tools/prove_stdio.sh index 64c140023..d37f84bf9 100755 --- a/zero_bin/tools/prove_stdio.sh +++ b/zero_bin/tools/prove_stdio.sh @@ -19,10 +19,13 @@ fi # Force the working directory to always be the `tools/` directory. TOOLS_DIR=$(dirname $(realpath "$0")) +PROOF_OUTPUT_DIR="${TOOLS_DIR}/proofs" -LEADER_OUT_PATH="${TOOLS_DIR}/leader.out" -PROOFS_JSON_PATH="${TOOLS_DIR}/proofs.json" -VERIFY_OUT_PATH="${TOOLS_DIR}/verify.out" +BLOCK_BATCH_SIZE="${BLOCK_BATCH_SIZE:-8}" +echo "Block batch size: $BLOCK_BATCH_SIZE" + +OUTPUT_LOG="${TOOLS_DIR}/output.log" +PROOFS_FILE_LIST="${PROOF_OUTPUT_DIR}/proof_files.json" TEST_OUT_PATH="${TOOLS_DIR}/test.out" # Configured Rayon and Tokio with rough defaults @@ -88,7 +91,7 @@ fi # proof. This is useful for quickly testing decoding and all of the # other non-proving code. if [[ $TEST_ONLY == "test_only" ]]; then - cargo run --release --bin leader -- --test-only --runtime in-memory --load-strategy on-demand stdio < $INPUT_FILE &> $TEST_OUT_PATH + cargo run --release --bin leader -- --test-only --runtime in-memory --load-strategy on-demand --block-batch-size $BLOCK_BATCH_SIZE --proof-output-dir $PROOF_OUTPUT_DIR stdio < $INPUT_FILE &> $TEST_OUT_PATH if grep -q 'All proof witnesses have been generated successfully.' $TEST_OUT_PATH; then echo -e "\n\nSuccess - Note this was just a test, not a proof" rm $TEST_OUT_PATH @@ -101,24 +104,44 @@ fi cargo build --release --jobs "$num_procs" + start_time=$(date +%s%N) -"${TOOLS_DIR}/../../target/release/leader" --runtime in-memory --load-strategy on-demand stdio < $INPUT_FILE &> $LEADER_OUT_PATH +"${TOOLS_DIR}/../../target/release/leader" --runtime in-memory --load-strategy on-demand --block-batch-size $BLOCK_BATCH_SIZE \ + --proof-output-dir $PROOF_OUTPUT_DIR stdio < $INPUT_FILE &> $OUTPUT_LOG end_time=$(date +%s%N) -tail -n 1 $LEADER_OUT_PATH > $PROOFS_JSON_PATH +set +o pipefail +cat $OUTPUT_LOG | grep "Successfully wrote to disk proof file " | awk '{print $NF}' | tee $PROOFS_FILE_LIST +if [ ! -s "$PROOFS_FILE_LIST" ]; then + echo "Proof list not generated, some error happened. For more details check the log file $OUTPUT_LOG" + exit 1 +fi + +cat $PROOFS_FILE_LIST | while read proof_file; +do + echo "Verifying proof file $proof_file" + verify_file=$PROOF_OUTPUT_DIR/verify_$(basename $proof_file).out + "${TOOLS_DIR}/../../target/release/verifier" -f $proof_file | tee $verify_file + if grep -q 'All proofs verified successfully!' $verify_file; then + echo "Proof verification for file $proof_file successful"; + rm $verify_file # we keep the generated proof for potential reuse + else + echo "there was an issue with proof verification"; + exit 1 + fi +done + +duration_ns=$((end_time - start_time)) +duration_sec=$(echo "$duration_ns / 1000000000" | bc -l) + +echo "Success!" +echo "Proving duration:" $duration_sec " seconds" +echo "Note, this duration is inclusive of circuit handling and overall process initialization"; + +# Clean up in case of success +rm $OUTPUT_LOG + -"${TOOLS_DIR}/../../target/release/verifier" -f $PROOFS_JSON_PATH | tee $VERIFY_OUT_PATH -if grep -q 'All proofs verified successfully!' $VERIFY_OUT_PATH; then - duration_ns=$((end_time - start_time)) - duration_sec=$(echo "$duration_ns / 1000000000" | bc -l) - echo "Success!" - echo "Duration:" $duration_sec " seconds" - echo "Note, this duration is inclusive of circuit handling and overall process initialization"; - rm $LEADER_OUT_PATH $VERIFY_OUT_PATH # we keep the generated proof for potential reuse -else - echo "there was an issue with proof verification"; - exit 1 -fi From bbfc0f4f60dd80b13664113d96943b302abfdc70 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:23:45 -0400 Subject: [PATCH 05/19] Fix `clean` leader's command (#586) * Fix clean command * Clippy --- zero_bin/common/src/prover_state/persistence.rs | 10 ++++++++-- zero_bin/leader/src/main.rs | 7 ++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/zero_bin/common/src/prover_state/persistence.rs b/zero_bin/common/src/prover_state/persistence.rs index fd45238b9..6096b7ab1 100644 --- a/zero_bin/common/src/prover_state/persistence.rs +++ b/zero_bin/common/src/prover_state/persistence.rs @@ -283,8 +283,14 @@ pub fn delete_all() -> anyhow::Result<()> { let file_path = entry.path(); if file_path.is_file() - && (file_path.starts_with("prover_state") - || file_path.starts_with("verifier_state")) + && (entry + .file_name() + .to_string_lossy() + .starts_with("prover_state") + || entry + .file_name() + .to_string_lossy() + .starts_with("verifier_state")) { // Delete all circuit files. fs::remove_file(file_path)?; diff --git a/zero_bin/leader/src/main.rs b/zero_bin/leader/src/main.rs index dc65f0eac..c0311b15c 100644 --- a/zero_bin/leader/src/main.rs +++ b/zero_bin/leader/src/main.rs @@ -55,6 +55,11 @@ async fn main() -> Result<()> { } let args = cli::Cli::parse(); + + if let Command::Clean = args.command { + return zero_bin_common::prover_state::persistence::delete_all(); + } + let runtime = Arc::new(Runtime::from_config(&args.paladin, register()).await?); let prover_config: ProverConfig = args.prover_config.into(); @@ -69,7 +74,6 @@ async fn main() -> Result<()> { } match args.command { - Command::Clean => zero_bin_common::prover_state::persistence::delete_all()?, Command::Stdio { previous_proof } => { let previous_proof = get_previous_proof(previous_proof)?; stdio::stdio_main(runtime, previous_proof, Arc::new(prover_config)).await?; @@ -118,6 +122,7 @@ async fn main() -> Result<()> { ) .await?; } + Command::Clean => unreachable!("Flushing has already been handled."), } Ok(()) From d50104b3cffa4b2a54a9a3925654f45abdc83096 Mon Sep 17 00:00:00 2001 From: Marko Atanasievski Date: Wed, 4 Sep 2024 16:57:30 +0200 Subject: [PATCH 06/19] fix: disable dockerhub login in ci tests (#588) --- .github/workflows/jerigon-native.yml | 6 ------ .github/workflows/jerigon-zero.yml | 6 ------ 2 files changed, 12 deletions(-) diff --git a/.github/workflows/jerigon-native.yml b/.github/workflows/jerigon-native.yml index 21e4cfe98..6c5967278 100644 --- a/.github/workflows/jerigon-native.yml +++ b/.github/workflows/jerigon-native.yml @@ -44,12 +44,6 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Login to Dockerhub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB }} - password: ${{ secrets.DOCKERHUB_KEY }} - - name: Set up rust cache uses: Swatinem/rust-cache@v2 with: diff --git a/.github/workflows/jerigon-zero.yml b/.github/workflows/jerigon-zero.yml index c84a5fa21..a7e6fcb3e 100644 --- a/.github/workflows/jerigon-zero.yml +++ b/.github/workflows/jerigon-zero.yml @@ -45,12 +45,6 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Login to Dockerhub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB }} - password: ${{ secrets.DOCKERHUB_KEY }} - - name: Set up rust cache uses: Swatinem/rust-cache@v2 with: From 89840da6c5161692b330ab79d61ee1558135de0a Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:30:18 -0400 Subject: [PATCH 07/19] chore: Add additional people as code owners for CI related changes (#589) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2b60fc0c1..3872f92ac 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,3 +4,4 @@ /smt_trie/ @0xaatif @muursh @Nashtare /mpt_trie/ @0xaatif @Nashtare @muursh /trace_decoder/ @0xaatif @muursh @Nashtare +.github/ @0xaatif @atanmarko @muursh @Nashtare From d20743931cc05f95aaf06d35bb4b49acc1b5cb73 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Thu, 5 Sep 2024 03:59:09 -0400 Subject: [PATCH 08/19] fix: Do not write proofs to disk in test only mode (#592) --- zero_bin/prover/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zero_bin/prover/src/lib.rs b/zero_bin/prover/src/lib.rs index 21be514ad..8aab306bb 100644 --- a/zero_bin/prover/src/lib.rs +++ b/zero_bin/prover/src/lib.rs @@ -264,9 +264,10 @@ pub async fn prove( // `keep_intermediate_proofs` is set, output all block proofs to disk. let is_block_batch_finished = block_counter % prover_config.block_batch_size as u64 == 0; - if is_last_block - || prover_config.keep_intermediate_proofs - || is_block_batch_finished + if !prover_config.test_only + && (is_last_block + || prover_config.keep_intermediate_proofs + || is_block_batch_finished) { write_proof_to_dir(&prover_config.proof_output_dir, proof.clone()) .await From 6aa25a17525686389892facc2ea94239c503c306 Mon Sep 17 00:00:00 2001 From: Marko Atanasievski Date: Thu, 5 Sep 2024 19:47:42 +0200 Subject: [PATCH 09/19] chore: cleanup rpc tool parameters (#595) --- zero_bin/rpc/src/lib.rs | 8 -------- zero_bin/rpc/src/main.rs | 30 +++++++++++++++++++----------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/zero_bin/rpc/src/lib.rs b/zero_bin/rpc/src/lib.rs index c27015df6..58df5430a 100644 --- a/zero_bin/rpc/src/lib.rs +++ b/zero_bin/rpc/src/lib.rs @@ -33,14 +33,6 @@ pub enum RpcType { Native, } -#[derive(Clone, Debug, Copy)] -pub struct RpcParams { - pub start_block: u64, - pub end_block: u64, - pub checkpoint_block_number: Option, - pub rpc_type: RpcType, -} - /// Obtain the prover input for one block pub async fn block_prover_input( cached_provider: Arc>, diff --git a/zero_bin/rpc/src/main.rs b/zero_bin/rpc/src/main.rs index 9ec2b9b62..8f860689f 100644 --- a/zero_bin/rpc/src/main.rs +++ b/zero_bin/rpc/src/main.rs @@ -10,7 +10,7 @@ use anyhow::anyhow; use clap::{Args, Parser, Subcommand, ValueHint}; use futures::StreamExt; use prover::BlockProverInput; -use rpc::{retry::build_http_retry_provider, RpcParams, RpcType}; +use rpc::{retry::build_http_retry_provider, RpcType}; use tracing_subscriber::{prelude::*, EnvFilter}; use url::Url; use zero_bin_common::block_interval::BlockIntervalStream; @@ -19,8 +19,16 @@ use zero_bin_common::provider::CachedProvider; use zero_bin_common::version; use zero_bin_common::{block_interval::BlockInterval, prover_state::persistence::CIRCUIT_VERSION}; +#[derive(Clone, Debug, Copy)] +struct FetchParams { + pub start_block: u64, + pub end_block: u64, + pub checkpoint_block_number: Option, + pub rpc_type: RpcType, +} + #[derive(Args, Clone, Debug)] -pub(crate) struct RpcConfig { +struct RpcToolConfig { /// The RPC URL. #[arg(short = 'u', long, value_hint = ValueHint::Url)] rpc_url: Url, @@ -36,7 +44,7 @@ pub(crate) struct RpcConfig { } #[derive(Subcommand)] -pub(crate) enum Command { +enum Command { Fetch { /// Starting block of interval to fetch. #[arg(short, long)] @@ -60,18 +68,18 @@ pub(crate) enum Command { } #[derive(Parser)] -pub(crate) struct Cli { +struct Cli { #[clap(flatten)] - pub(crate) config: RpcConfig, + pub(crate) config: RpcToolConfig, /// Fetch and generate prover input from the RPC endpoint. #[command(subcommand)] pub(crate) command: Command, } -pub(crate) async fn retrieve_block_prover_inputs( +pub(crate) async fn fetch_block_prover_inputs( cached_provider: Arc>, - params: RpcParams, + params: FetchParams, ) -> Result, anyhow::Error> where ProviderT: Provider, @@ -127,7 +135,7 @@ impl Cli { end_block, checkpoint_block_number, } => { - let params = RpcParams { + let params = FetchParams { start_block, end_block, checkpoint_block_number, @@ -135,7 +143,7 @@ impl Cli { }; let block_prover_inputs = - retrieve_block_prover_inputs(cached_provider, params).await?; + fetch_block_prover_inputs(cached_provider, params).await?; serde_json::to_writer_pretty(std::io::stdout(), &block_prover_inputs)?; } Command::Extract { tx, batch_size } => { @@ -153,7 +161,7 @@ impl Cli { "transaction {} does not have block number", tx_hash ))?; - let params = RpcParams { + let params = FetchParams { start_block: block_number, end_block: block_number, checkpoint_block_number: None, @@ -161,7 +169,7 @@ impl Cli { }; let block_prover_inputs = - retrieve_block_prover_inputs(cached_provider, params).await?; + fetch_block_prover_inputs(cached_provider, params).await?; let block_prover_input = block_prover_inputs.into_iter().next().ok_or(anyhow!( From 6f8199b6827bbf649b75f53b14e6cc78b2294a70 Mon Sep 17 00:00:00 2001 From: 0xaatif <169152398+0xaatif@users.noreply.github.com> Date: Thu, 5 Sep 2024 19:10:36 +0100 Subject: [PATCH 10/19] refactor: trace decoder tests (#596) * mark: 0xaatif/trace-decoder-tests * chore: remove old tests * test: add new tests * chore: move test cases to cases directory * chore: move library test cases * test: different batch sizes --- Cargo.lock | 112 ++++--- trace_decoder/Cargo.toml | 14 +- .../{tests/data => src/cases}/.gitattributes | 0 trace_decoder/src/cases/README.md | 1 + .../cases}/hermez_cdk_erigon.json | 0 .../tries => src/cases}/zero_jerigon.json | 0 trace_decoder/src/type1.rs | 11 +- trace_decoder/src/type2.rs | 11 +- trace_decoder/tests/cases/.gitattributes | 1 + .../b19807080_main.json | 0 .../b19807080_main_header.json | 0 .../b19840104_main.json | 0 .../b19840104_main_header.json | 0 .../b20240052_main.json | 0 .../b20240052_main_header.json | 0 .../b20240058_main.json | 0 .../b20240058_main_header.json | 0 .../b20472570_main.json | 0 .../b20472570_main_header.json | 0 .../zero_jerigon => cases}/b28_dev.json | 0 .../b28_dev_header.json | 0 .../zero_jerigon => cases}/b4_dev.json | 0 .../zero_jerigon => cases}/b4_dev_header.json | 0 trace_decoder/tests/common/mod.rs | 83 ++++++ trace_decoder/tests/consistent-with-header.rs | 81 +++++ trace_decoder/tests/simulate-execution.rs | 40 +++ trace_decoder/tests/trace_decoder_tests.rs | 281 ------------------ 27 files changed, 298 insertions(+), 337 deletions(-) rename trace_decoder/{tests/data => src/cases}/.gitattributes (100%) create mode 100644 trace_decoder/src/cases/README.md rename trace_decoder/{tests/data/tries => src/cases}/hermez_cdk_erigon.json (100%) rename trace_decoder/{tests/data/tries => src/cases}/zero_jerigon.json (100%) create mode 100644 trace_decoder/tests/cases/.gitattributes rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b19807080_main.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b19807080_main_header.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b19840104_main.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b19840104_main_header.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b20240052_main.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b20240052_main_header.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b20240058_main.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b20240058_main_header.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b20472570_main.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b20472570_main_header.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b28_dev.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b28_dev_header.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b4_dev.json (100%) rename trace_decoder/tests/{data/witnesses/zero_jerigon => cases}/b4_dev_header.json (100%) create mode 100644 trace_decoder/tests/common/mod.rs create mode 100644 trace_decoder/tests/consistent-with-header.rs create mode 100644 trace_decoder/tests/simulate-execution.rs delete mode 100644 trace_decoder/tests/trace_decoder_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 11feda934..65163cc93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,16 @@ dependencies = [ "strum", ] +[[package]] +name = "alloy-compat" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6216c5ab5502a6824ec87a7bcd5c52ca14137f00b37dea3ab321a1656ff46383" +dependencies = [ + "alloy-primitives", + "ethereum-types", +] + [[package]] name = "alloy-consensus" version = "0.3.0" @@ -735,6 +745,30 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "assert2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d31fea2b6e18dfe892863c3a0a68f9e005b0195565f3d55b8612946ebca789cc" +dependencies = [ + "assert2-macros", + "diff", + "is-terminal", + "yansi", +] + +[[package]] +name = "assert2-macros" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1ac052c642f6d94e4be0b33028b346b7ab809ea5432b584eb8859f12f7ad2c" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.76", +] + [[package]] name = "async-channel" version = "2.3.1" @@ -1737,6 +1771,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -1923,6 +1963,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + [[package]] name = "eth_trie" version = "0.4.0" @@ -2249,12 +2295,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - [[package]] name = "futures-util" version = "0.3.30" @@ -2859,6 +2899,18 @@ dependencies = [ "libc", ] +[[package]] +name = "libtest-mimic" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58" +dependencies = [ + "clap", + "escape8259", + "termcolor", + "threadpool", +] + [[package]] name = "linkme" version = "0.3.28" @@ -4006,12 +4058,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" -[[package]] -name = "relative-path" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" - [[package]] name = "reqwest" version = "0.12.7" @@ -4134,36 +4180,6 @@ dependencies = [ "zero_bin_common", ] -[[package]] -name = "rstest" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682" -dependencies = [ - "futures", - "futures-timer", - "rstest_macros", - "rustc_version 0.4.1", -] - -[[package]] -name = "rstest_macros" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d" -dependencies = [ - "cfg-if", - "glob", - "proc-macro-crate", - "proc-macro2", - "quote", - "regex", - "relative-path", - "rustc_version 0.4.1", - "syn 2.0.76", - "unicode-ident", -] - [[package]] name = "ruint" version = "1.12.3" @@ -5092,10 +5108,13 @@ name = "trace_decoder" version = "0.6.0" dependencies = [ "alloy", + "alloy-compat", "anyhow", + "assert2", "bitflags 2.6.0", "bitvec", "bytes", + "camino", "ciborium", "ciborium-io", "copyvec", @@ -5104,10 +5123,12 @@ dependencies = [ "enum-as-inner", "ethereum-types", "evm_arithmetization", + "glob", "hex", "hex-literal", "itertools 0.13.0", "keccak-hash 0.10.0", + "libtest-mimic", "log", "mpt_trie", "nunny", @@ -5116,7 +5137,6 @@ dependencies = [ "pretty_env_logger", "prover", "rlp", - "rstest", "serde", "serde_json", "serde_path_to_error", @@ -5776,6 +5796,12 @@ dependencies = [ "time", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zero_bin_common" version = "0.1.0" diff --git a/trace_decoder/Cargo.toml b/trace_decoder/Cargo.toml index 4308a0ebe..78e0aa3ef 100644 --- a/trace_decoder/Cargo.toml +++ b/trace_decoder/Cargo.toml @@ -41,14 +41,26 @@ zk_evm_common = { workspace = true } [dev-dependencies] alloy = { workspace = true } +alloy-compat = "0.1.0" +assert2 = "0.3.15" +camino = "1.1.9" criterion = { workspace = true } +glob = "0.3.1" +libtest-mimic = "0.7.3" plonky2_maybe_rayon = { workspace = true } pretty_env_logger = { workspace = true } prover = { workspace = true } -rstest = "0.21.0" serde_json = { workspace = true } serde_path_to_error = { workspace = true } [[bench]] name = "block_processing" harness = false + +[[test]] +name = "consistent-with-header" +harness = false + +[[test]] +name = "simulate-execution" +harness = false diff --git a/trace_decoder/tests/data/.gitattributes b/trace_decoder/src/cases/.gitattributes similarity index 100% rename from trace_decoder/tests/data/.gitattributes rename to trace_decoder/src/cases/.gitattributes diff --git a/trace_decoder/src/cases/README.md b/trace_decoder/src/cases/README.md new file mode 100644 index 000000000..a37883839 --- /dev/null +++ b/trace_decoder/src/cases/README.md @@ -0,0 +1 @@ +Test vectors for unit tests in [../wire](../wire.rs). diff --git a/trace_decoder/tests/data/tries/hermez_cdk_erigon.json b/trace_decoder/src/cases/hermez_cdk_erigon.json similarity index 100% rename from trace_decoder/tests/data/tries/hermez_cdk_erigon.json rename to trace_decoder/src/cases/hermez_cdk_erigon.json diff --git a/trace_decoder/tests/data/tries/zero_jerigon.json b/trace_decoder/src/cases/zero_jerigon.json similarity index 100% rename from trace_decoder/tests/data/tries/zero_jerigon.json rename to trace_decoder/src/cases/zero_jerigon.json diff --git a/trace_decoder/src/type1.rs b/trace_decoder/src/type1.rs index c073c2a13..019a75c95 100644 --- a/trace_decoder/src/type1.rs +++ b/trace_decoder/src/type1.rs @@ -380,12 +380,11 @@ fn finish_stack(v: &mut Vec) -> anyhow::Result { #[test] fn test_tries() { - for (ix, case) in serde_json::from_str::>(include_str!( - "../tests/data/tries/zero_jerigon.json" - )) - .unwrap() - .into_iter() - .enumerate() + for (ix, case) in + serde_json::from_str::>(include_str!("cases/zero_jerigon.json")) + .unwrap() + .into_iter() + .enumerate() { println!("case {}", ix); let instructions = crate::wire::parse(&case.bytes).unwrap(); diff --git a/trace_decoder/src/type2.rs b/trace_decoder/src/type2.rs index 2d10edf40..dd3e45c4b 100644 --- a/trace_decoder/src/type2.rs +++ b/trace_decoder/src/type2.rs @@ -226,12 +226,11 @@ fn iter_leaves(node: Node) -> Box>(include_str!( - "../tests/data/tries/hermez_cdk_erigon.json" - )) - .unwrap() - .into_iter() - .enumerate() + for (ix, case) in + serde_json::from_str::>(include_str!("cases/hermez_cdk_erigon.json")) + .unwrap() + .into_iter() + .enumerate() { println!("case {}", ix); let instructions = crate::wire::parse(&case.bytes).unwrap(); diff --git a/trace_decoder/tests/cases/.gitattributes b/trace_decoder/tests/cases/.gitattributes new file mode 100644 index 000000000..f53fb93bf --- /dev/null +++ b/trace_decoder/tests/cases/.gitattributes @@ -0,0 +1 @@ +*.json linguist-generated=true diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b19807080_main.json b/trace_decoder/tests/cases/b19807080_main.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b19807080_main.json rename to trace_decoder/tests/cases/b19807080_main.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b19807080_main_header.json b/trace_decoder/tests/cases/b19807080_main_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b19807080_main_header.json rename to trace_decoder/tests/cases/b19807080_main_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b19840104_main.json b/trace_decoder/tests/cases/b19840104_main.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b19840104_main.json rename to trace_decoder/tests/cases/b19840104_main.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b19840104_main_header.json b/trace_decoder/tests/cases/b19840104_main_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b19840104_main_header.json rename to trace_decoder/tests/cases/b19840104_main_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20240052_main.json b/trace_decoder/tests/cases/b20240052_main.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20240052_main.json rename to trace_decoder/tests/cases/b20240052_main.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20240052_main_header.json b/trace_decoder/tests/cases/b20240052_main_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20240052_main_header.json rename to trace_decoder/tests/cases/b20240052_main_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20240058_main.json b/trace_decoder/tests/cases/b20240058_main.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20240058_main.json rename to trace_decoder/tests/cases/b20240058_main.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20240058_main_header.json b/trace_decoder/tests/cases/b20240058_main_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20240058_main_header.json rename to trace_decoder/tests/cases/b20240058_main_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20472570_main.json b/trace_decoder/tests/cases/b20472570_main.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20472570_main.json rename to trace_decoder/tests/cases/b20472570_main.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20472570_main_header.json b/trace_decoder/tests/cases/b20472570_main_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20472570_main_header.json rename to trace_decoder/tests/cases/b20472570_main_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b28_dev.json b/trace_decoder/tests/cases/b28_dev.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b28_dev.json rename to trace_decoder/tests/cases/b28_dev.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b28_dev_header.json b/trace_decoder/tests/cases/b28_dev_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b28_dev_header.json rename to trace_decoder/tests/cases/b28_dev_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b4_dev.json b/trace_decoder/tests/cases/b4_dev.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b4_dev.json rename to trace_decoder/tests/cases/b4_dev.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b4_dev_header.json b/trace_decoder/tests/cases/b4_dev_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b4_dev_header.json rename to trace_decoder/tests/cases/b4_dev_header.json diff --git a/trace_decoder/tests/common/mod.rs b/trace_decoder/tests/common/mod.rs new file mode 100644 index 000000000..51c74f75c --- /dev/null +++ b/trace_decoder/tests/common/mod.rs @@ -0,0 +1,83 @@ +use std::{fs::File, path::Path}; + +use alloy::rpc::types::Header; +use anyhow::{ensure, Context as _}; +use camino::Utf8Path; +use prover::BlockProverInput; +use serde::de::DeserializeOwned; +use trace_decoder::{BlockTrace, OtherBlockData}; + +pub fn cases() -> anyhow::Result> { + print!("loading test vectors..."); + let ret = glob::glob(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/cases/*_header.json" + )) + .expect("valid glob pattern") + .map(|res| { + let header_path = res.context("filesystem error discovering test vectors")?; + Case::load(&header_path).context(format!( + "couldn't load case for header {}", + header_path.display() + )) + }) + .collect(); + println!("done"); + ret +} + +/// Test cases consist of [`BlockProverInput`] collected from `zero_bin`'s `rpc` +/// command, and the corresponding block header, fetched directly over RPC. +/// +/// In directory above, the files are stored alongside one another, as, for +/// example: +/// - `b4_dev.json` +/// - `b4_dev_header.json` +pub struct Case { + /// `b4_dev`, in the above example. + /// + /// Used as a test identifier. + pub name: String, + #[allow(unused)] // only used by one of the test binaries + pub header: Header, + pub trace: BlockTrace, + pub other: OtherBlockData, +} + +impl Case { + fn load(header_path: &Path) -> anyhow::Result { + let header_path = Utf8Path::from_path(header_path).context("non-UTF-8 path")?; + let base = Utf8Path::new( + header_path + .as_str() + .strip_suffix("_header.json") + .context("inconsistent header name")?, // sync with glob call + ); + // for some reason these are lists... + let mut headers = json::>(header_path)?; + let mut bpis = json::>(base.with_extension("json"))?; + ensure!(headers.len() == 1, "bad header file"); + ensure!(bpis.len() == 1, "bad bpi file"); + let BlockProverInput { + block_trace, + other_data, + } = bpis.remove(0); + anyhow::Ok(Case { + name: base.file_name().context("inconsistent base name")?.into(), + header: headers.remove(0), + trace: block_trace, + other: other_data, + }) + } +} + +fn json(path: impl AsRef) -> anyhow::Result { + fn _imp(path: impl AsRef) -> anyhow::Result { + let file = File::open(path)?; + Ok(serde_path_to_error::deserialize( + &mut serde_json::Deserializer::from_reader(file), + )?) + } + + _imp(&path).context(format!("couldn't load {}", path.as_ref().display())) +} diff --git a/trace_decoder/tests/consistent-with-header.rs b/trace_decoder/tests/consistent-with-header.rs new file mode 100644 index 000000000..0cd477680 --- /dev/null +++ b/trace_decoder/tests/consistent-with-header.rs @@ -0,0 +1,81 @@ +//! Check that the [`evm_arithmetization::GenerationInputs`] produced by +//! [`trace_decoder`] are consistent between each other, and with the block +//! header obtained over RPC. + +mod common; + +use alloy_compat::Compat as _; +use assert2::check; +use common::{cases, Case}; +use itertools::Itertools; +use libtest_mimic::{Arguments, Trial}; +use mpt_trie::partial_trie::PartialTrie as _; + +fn main() -> anyhow::Result<()> { + let mut trials = vec![]; + + for batch_size in [1, 3] { + for Case { + name, + header, + trace, + other, + } in cases()? + { + trials.push(Trial::test(format!("{name}@{batch_size}"), move || { + let gen_inputs = trace_decoder::entrypoint(trace, other.clone(), batch_size, false) + .map_err(|e| format!("{e:?}"))?; // get the full cause chain + check!(gen_inputs.len() >= 2); + check!( + Some(other.checkpoint_state_trie_root) + == gen_inputs.first().map(|it| it.tries.state_trie.hash()) + ); + let pairs = || gen_inputs.iter().tuple_windows::<(_, _)>(); + check!( + pairs().position(|(before, after)| { + before.trie_roots_after.state_root != after.tries.state_trie.hash() + }) == None + ); + check!( + pairs().position(|(before, after)| { + before.trie_roots_after.receipts_root != after.tries.receipts_trie.hash() + }) == None + ); + check!( + pairs().position(|(before, after)| { + before.trie_roots_after.transactions_root + != after.tries.transactions_trie.hash() + }) == None + ); + check!( + gen_inputs + .last() + .map(|it| it.trie_roots_after.state_root.compat()) + == Some(header.state_root) + ); + check!( + gen_inputs + .iter() + .position(|it| it.block_metadata.block_timestamp != header.timestamp.into()) + == None + ); + check!( + gen_inputs + .last() + .map(|it| it.block_hashes.cur_hash.compat()) + == Some(header.hash) + ); + check!( + gen_inputs.iter().position(|it| it + .block_hashes + .prev_hashes + .last() + .is_some_and(|it| *it != header.parent_hash.compat())) + == None + ); + Ok(()) + })); + } + } + libtest_mimic::run(&Arguments::from_args(), trials).exit() +} diff --git a/trace_decoder/tests/simulate-execution.rs b/trace_decoder/tests/simulate-execution.rs new file mode 100644 index 000000000..080a02c62 --- /dev/null +++ b/trace_decoder/tests/simulate-execution.rs @@ -0,0 +1,40 @@ +//! Check that the [`evm_arithmetization::GenerationInputs`] produced by +//! [`trace_decoder`] are consistent between each other, and with the block +//! header obtained over RPC. + +mod common; + +use anyhow::Context as _; +use common::{cases, Case}; +use libtest_mimic::{Arguments, Trial}; +use plonky2::field::goldilocks_field::GoldilocksField; + +fn main() -> anyhow::Result<()> { + let mut trials = vec![]; + for batch_size in [1, 3] { + for Case { + name, + header: _, + trace, + other, + } in cases()? + { + let gen_inputs = trace_decoder::entrypoint(trace, other, batch_size, false).context( + format!("error in `trace_decoder` for {name} at batch size {batch_size}"), + )?; + for (ix, gi) in gen_inputs.into_iter().enumerate() { + trials.push(Trial::test( + format!("{name}@{batch_size}/{ix}"), + move || { + evm_arithmetization::prover::testing::simulate_execution_all_segments::< + GoldilocksField, + >(gi, 19) + .map_err(|e| format!("{e:?}"))?; // get the full error chain + Ok(()) + }, + )) + } + } + } + libtest_mimic::run(&Arguments::from_args(), trials).exit() +} diff --git a/trace_decoder/tests/trace_decoder_tests.rs b/trace_decoder/tests/trace_decoder_tests.rs deleted file mode 100644 index 6db98749f..000000000 --- a/trace_decoder/tests/trace_decoder_tests.rs +++ /dev/null @@ -1,281 +0,0 @@ -//! Tests to check the parsing/decoding and `GenerationInputs` validity. -//! They rely on the jerigon and cdk erigon witness files as input. - -use std::time::Duration; -use std::{ - fs, - path::{Path, PathBuf}, -}; - -use alloy::rpc::types::eth::Header; -use anyhow::Context as _; -use evm_arithmetization::prover::testing::simulate_execution_all_segments; -use evm_arithmetization::GenerationInputs; -use itertools::Itertools; -use log::info; -use mpt_trie::partial_trie::PartialTrie; -use plonky2::field::goldilocks_field::GoldilocksField; -use plonky2::util::timing::TimingTree; -use plonky2_maybe_rayon::*; -use pretty_env_logger::env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; -use prover::BlockProverInput; -use rstest::rstest; -use trace_decoder::OtherBlockData; - -type F = GoldilocksField; - -const JERIGON_WITNESS_DIR: &str = "tests/data/witnesses/zero_jerigon"; -///TODO Add CDK Erigon witness test data. -/// Local [cdk erigon](https://github.com/0xPolygonHermez/cdk-erigon?tab=readme-ov-file#running-cdk-erigon) dev network -/// could be used for basic witness generation. -/// Related work for type2 prover is on the [type2_cancun](https://github.com/0xPolygonZero/zk_evm/pull/319) branch at the moment. -/// When the cdk erigon witness data is added, enable test execution for -/// `CDK_ERIGON_WITNESS_DIR` -//const CDK_ERIGON_WITNESS_DIR: &str = -// "tests/data/witnesses/hermez_cdk_erigon"; - -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} - -fn find_witness_data_files(dir: &str) -> anyhow::Result> { - let read_dir = fs::read_dir(dir)?; - read_dir - .into_iter() - .map(|dir_entry| dir_entry.map(|it| it.path())) - .filter_ok(|path| { - !path - .to_str() - .expect("valid str file path") - .contains("header") - }) - .collect::, _>>() - .context(format!("Failed to find witness files in dir {dir}")) -} - -fn read_witness_file(file_path: &Path) -> anyhow::Result> { - let witness = fs::File::open(file_path).context("Unable to read file")?; - let mut reader = std::io::BufReader::new(witness); - let jd = &mut serde_json::Deserializer::from_reader(&mut reader); - serde_path_to_error::deserialize(jd).context(format!( - "Failed to deserialize json file {}", - file_path.display() - )) -} - -fn derive_header_file_path(witness_file_path: &Path) -> Result { - let mut header_file_path = witness_file_path.to_path_buf(); - header_file_path.set_extension(""); - let mut block_header_file_name = header_file_path - .file_name() - .context("Invalid header file name")? - .to_os_string(); - block_header_file_name.push("_header.json"); - header_file_path.set_file_name(block_header_file_name); - Ok(header_file_path) -} - -fn decode_generation_inputs( - block_prover_input: BlockProverInput, - use_burn_addr: bool, -) -> anyhow::Result> { - let block_num = block_prover_input.other_data.b_data.b_meta.block_number; - let trace_decoder_output = trace_decoder::entrypoint( - block_prover_input.block_trace, - block_prover_input.other_data.clone(), - 3, - use_burn_addr, - ) - .context(format!( - "Failed to execute trace decoder on block {}", - block_num - ))? - .into_iter() - .collect::>(); - Ok(trace_decoder_output) -} - -fn verify_generation_inputs( - header: &Header, - other: &OtherBlockData, - generation_inputs: Vec, -) -> anyhow::Result<()> { - assert!(generation_inputs.len() >= 2); - assert_eq!( - other.checkpoint_state_trie_root, - generation_inputs - .first() - .expect("generation inputs should have first element") - .tries - .state_trie - .hash() - ); - assert!(generation_inputs - .windows(2) - .map(|inputs| { - inputs[0].trie_roots_after.state_root == inputs[1].tries.state_trie.hash() - && inputs[0].trie_roots_after.receipts_root == inputs[1].tries.receipts_trie.hash() - && inputs[0].trie_roots_after.transactions_root - == inputs[1].tries.transactions_trie.hash() - }) - .all(|it| it)); - let last_generation_input = generation_inputs - .last() - .expect("generation inputs should have last element"); - assert_eq!( - last_generation_input.trie_roots_after.state_root.0, - header.state_root.0 - ); - // Some block metadata sanity checks - assert_eq!( - last_generation_input - .block_metadata - .block_timestamp - .as_u64(), - header.timestamp - ); - // Block hash check - assert_eq!( - last_generation_input.block_hashes.cur_hash.as_bytes(), - &header.hash.to_vec() - ); - // Previous block hash check - assert_eq!( - last_generation_input - .block_hashes - .prev_hashes - .last() - .expect("Valid last hash") - .as_bytes(), - &header.parent_hash.to_vec() - ); - info!( - "Block {} GenerationInputs valid", - other.b_data.b_meta.block_number - ); - Ok(()) -} - -/// This test aims at ensuring that the decoder can properly parse a block trace -/// received from Jerigon and CDK Erigon into zkEVM `GenerationInputs`, which -/// the prover can then pick to prove each transaction in the block -/// independently. -/// -/// This test only `simulates` the zkEVM CPU, i.e. does not generate STARK -/// traces nor generates proofs, as its purpose is to be runnable easily in the -/// CI even in `debug` mode. -#[rstest] -#[case(JERIGON_WITNESS_DIR)] -//#[case(CDK_ERIGON_WITNESS_DIR)] -fn test_parsing_decoding_proving(#[case] test_witness_directory: &str) { - init_logger(); - - // TODO: https://github.com/0xPolygonZero/zk_evm/issues/565 - // Once CDK_ERIGON_WITNESS_DIR is available, change this so - // `use_burn_addr` is only true in that case. - let use_burn_addr = test_witness_directory != JERIGON_WITNESS_DIR; - let results = find_witness_data_files(test_witness_directory) - .expect("valid json data files found") - .into_iter() - .map(|file_path| { - { - // Read one json witness file for this block and get list of BlockProverInputs - read_witness_file(&file_path) - } - }) - .map_ok(|block_prover_inputs| { - block_prover_inputs.into_iter().map(|block_prover_input| { - // Run trace decoder, create list of generation inputs - let block_generation_inputs = - decode_generation_inputs(block_prover_input, use_burn_addr)?; - block_generation_inputs - .into_par_iter() - .map(|generation_inputs| { - // For every generation input, simulate execution. - // Execution will be simulated in parallel. - // If system runs out of memory, limit the rayon - // with setting env variable RAYON_NUM_THREADS=. - let timing = TimingTree::new( - &format!( - "simulate zkEVM CPU for block {}, txns {:?}..{:?}.", - generation_inputs.block_metadata.block_number, - generation_inputs.txn_number_before, - generation_inputs.txn_number_before - + generation_inputs.signed_txns.len() - ), - log::Level::Info, - ); - simulate_execution_all_segments::(generation_inputs, 19)?; - timing.filter(Duration::from_millis(100)).print(); - Ok::<(), anyhow::Error>(()) - }) - .collect::, anyhow::Error>>() - }) - }) - .flatten_ok() - .map(|it| it?) - .collect::>>(); - - results.iter().for_each(|it| { - if let Err(e) = it { - panic!("Failed to run parsing decoding proving test: {e:?}"); - } - }); -} - -/// This test checks for the parsing and decoding of the block witness -/// received from Jerigon and CDK Erigon into zkEVM `GenerationInputs`, and -/// checks if trace decoder output generation inputs are valid and consistent. -#[rstest] -#[case(JERIGON_WITNESS_DIR)] -//#[case(CDK_ERIGON_WITNESS_DIR)] -fn test_generation_inputs_consistency(#[case] test_witness_directory: &str) { - init_logger(); - - // TODO: https://github.com/0xPolygonZero/zk_evm/issues/565 - // Once CDK_ERIGON_WITNESS_DIR is available, change this so - // `use_burn_addr` is only true in that case. - let use_burn_addr = test_witness_directory != JERIGON_WITNESS_DIR; - let result: Vec> = find_witness_data_files(test_witness_directory) - .expect("valid json data files found") - .into_iter() - .map(|file_path| { - { - // Read json header file of the block. We need it to check tracer output - // consistency - let header_file_path = derive_header_file_path(&file_path)?; - let header_file = fs::File::open(header_file_path.as_path()).context(format!( - "Unable to open header file {}", - header_file_path.display() - ))?; - let mut header_reader = std::io::BufReader::new(header_file); - let block_headers = serde_json::from_reader::<_, Vec
>(&mut header_reader) - .context(format!( - "Failed to deserialize header json file {}", - header_file_path.display() - ))?; - // Read one json witness file and get list of BlockProverInputs - let block_prover_inputs = read_witness_file(&file_path)?; - Ok(block_headers - .into_iter() - .zip(block_prover_inputs.into_iter())) - } - }) - .flatten_ok() - .map_ok(|(block_header, block_prover_input)| { - let other_block_data = block_prover_input.other_data.clone(); - // Run trace decoder, create generation inputs for this block - let block_generation_inputs = - decode_generation_inputs(block_prover_input, use_burn_addr)?; - // Verify generation inputs for this block - verify_generation_inputs(&block_header, &other_block_data, block_generation_inputs) - }) - .map(|it: Result, anyhow::Error>| it?) - .collect(); - - result.iter().for_each(|it| { - if let Err(e) = it { - panic!("Failed to verify generation inputs consistency: {e:?}"); - } - }); -} From 47c79ed8a380b518fed953b29d5b33f580edd92e Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Fri, 6 Sep 2024 08:57:16 +0100 Subject: [PATCH 11/19] Mark constant functions with const (#571) Co-authored-by: Robin Salen <30937548+Nashtare@users.noreply.github.com> --- evm_arithmetization/src/cpu/columns/general.rs | 2 +- evm_arithmetization/src/cpu/kernel/interpreter.rs | 2 +- evm_arithmetization/src/generation/linked_list.rs | 4 ++-- evm_arithmetization/src/generation/mpt.rs | 2 +- evm_arithmetization/src/generation/segments.rs | 2 +- evm_arithmetization/src/proof.rs | 2 +- proof_gen/src/proof_types.rs | 4 ++-- trace_decoder/src/typed_mpt.rs | 10 +++++----- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/evm_arithmetization/src/cpu/columns/general.rs b/evm_arithmetization/src/cpu/columns/general.rs index 5ab033b82..97f8b8f49 100644 --- a/evm_arithmetization/src/cpu/columns/general.rs +++ b/evm_arithmetization/src/cpu/columns/general.rs @@ -95,7 +95,7 @@ impl CpuGeneralColumnsView { /// View of the column for context pruning. /// SAFETY: Each view is a valid interpretation of the underlying array. - pub(crate) fn context_pruning(&self) -> &CpuContextPruningView { + pub(crate) const fn context_pruning(&self) -> &CpuContextPruningView { unsafe { &self.context_pruning } } diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index f6e9b67ed..12384b3a8 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -427,7 +427,7 @@ impl Interpreter { } /// Returns the max number of CPU cycles. - pub(crate) fn get_max_cpu_len_log(&self) -> Option { + pub(crate) const fn get_max_cpu_len_log(&self) -> Option { self.max_cpu_len_log } diff --git a/evm_arithmetization/src/generation/linked_list.rs b/evm_arithmetization/src/generation/linked_list.rs index a89e49657..d8a313c90 100644 --- a/evm_arithmetization/src/generation/linked_list.rs +++ b/evm_arithmetization/src/generation/linked_list.rs @@ -32,14 +32,14 @@ pub(crate) fn empty_list_mem(segment: Segment) -> [Option; } impl<'a, const N: usize> LinkedList<'a, N> { - pub fn from_mem_and_segment( + pub const fn from_mem_and_segment( mem: &'a [Option], segment: Segment, ) -> Result { Self::from_mem_len_and_segment(mem, segment) } - pub fn from_mem_len_and_segment( + pub const fn from_mem_len_and_segment( mem: &'a [Option], segment: Segment, ) -> Result { diff --git a/evm_arithmetization/src/generation/mpt.rs b/evm_arithmetization/src/generation/mpt.rs index 36f520d9f..adb89b5a6 100644 --- a/evm_arithmetization/src/generation/mpt.rs +++ b/evm_arithmetization/src/generation/mpt.rs @@ -133,7 +133,7 @@ fn parse_storage_value(value_rlp: &[u8]) -> Result, ProgramError> { Ok(vec![value]) } -fn parse_storage_value_no_return(_value_rlp: &[u8]) -> Result, ProgramError> { +const fn parse_storage_value_no_return(_value_rlp: &[u8]) -> Result, ProgramError> { Ok(vec![]) } diff --git a/evm_arithmetization/src/generation/segments.rs b/evm_arithmetization/src/generation/segments.rs index 424ace9ad..cc90154dc 100644 --- a/evm_arithmetization/src/generation/segments.rs +++ b/evm_arithmetization/src/generation/segments.rs @@ -33,7 +33,7 @@ pub struct GenerationSegmentData { impl GenerationSegmentData { /// Retrieves the index of this segment. - pub fn segment_index(&self) -> usize { + pub const fn segment_index(&self) -> usize { self.segment_index } } diff --git a/evm_arithmetization/src/proof.rs b/evm_arithmetization/src/proof.rs index 6c59b9deb..27f71f8c1 100644 --- a/evm_arithmetization/src/proof.rs +++ b/evm_arithmetization/src/proof.rs @@ -925,7 +925,7 @@ pub enum BurnAddrTarget { } impl BurnAddrTarget { - pub fn get_size() -> usize { + pub const fn get_size() -> usize { match cfg!(feature = "cdk_erigon") { true => 8, false => 0, diff --git a/proof_gen/src/proof_types.rs b/proof_gen/src/proof_types.rs index ddca989da..20be552d9 100644 --- a/proof_gen/src/proof_types.rs +++ b/proof_gen/src/proof_types.rs @@ -125,7 +125,7 @@ impl BatchAggregatableProof { } } - pub(crate) fn is_agg(&self) -> bool { + pub(crate) const fn is_agg(&self) -> bool { match self { BatchAggregatableProof::Segment(_) => false, BatchAggregatableProof::Txn(_) => false, @@ -133,7 +133,7 @@ impl BatchAggregatableProof { } } - pub(crate) fn intern(&self) -> &PlonkyProofIntern { + pub(crate) const fn intern(&self) -> &PlonkyProofIntern { match self { BatchAggregatableProof::Segment(info) => &info.intern, BatchAggregatableProof::Txn(info) => &info.intern, diff --git a/trace_decoder/src/typed_mpt.rs b/trace_decoder/src/typed_mpt.rs index 8409e74c0..5a49966a6 100644 --- a/trace_decoder/src/typed_mpt.rs +++ b/trace_decoder/src/typed_mpt.rs @@ -58,7 +58,7 @@ impl TypedMpt { let bytes = self.inner.get(key.into_nibbles())?; Some(rlp::decode(bytes).expect(Self::PANIC_MSG)) } - fn as_hashed_partial_trie(&self) -> &HashedPartialTrie { + const fn as_hashed_partial_trie(&self) -> &HashedPartialTrie { &self.inner } fn as_mut_hashed_partial_trie_unchecked(&mut self) -> &mut HashedPartialTrie { @@ -198,7 +198,7 @@ impl TransactionTrie { pub fn root(&self) -> H256 { self.untyped.hash() } - pub fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { + pub const fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { &self.untyped } } @@ -224,7 +224,7 @@ impl ReceiptTrie { pub fn root(&self) -> H256 { self.untyped.hash() } - pub fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { + pub const fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { &self.untyped } } @@ -259,7 +259,7 @@ impl StateMpt { .iter() .map(|(key, rlp)| (key.into_hash().expect("key is always H256"), rlp)) } - pub fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { + pub const fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { self.typed.as_hashed_partial_trie() } pub fn root(&self) -> H256 { @@ -392,7 +392,7 @@ impl StorageTrie { pub fn root(&self) -> H256 { self.untyped.hash() } - pub fn as_hashed_partial_trie(&self) -> &HashedPartialTrie { + pub const fn as_hashed_partial_trie(&self) -> &HashedPartialTrie { &self.untyped } From eb549889776ea06eac10cc90c6e1e1138a84b689 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Fri, 6 Sep 2024 09:58:48 -0400 Subject: [PATCH 12/19] chore: remove unused code (#602) --- .../src/cpu/kernel/interpreter.rs | 212 +----------------- 1 file changed, 1 insertion(+), 211 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index 12384b3a8..d08ac6db5 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -410,20 +410,7 @@ impl Interpreter { } pub(crate) fn run(&mut self) -> Result<(RegistersState, Option), anyhow::Error> { - let (final_registers, final_mem) = self.run_cpu(self.max_cpu_len_log)?; - - #[cfg(debug_assertions)] - { - println!("Opcode count:"); - for i in 0..0x100 { - if self.opcode_count[i] > 0 { - println!("{}: {}", get_mnemonic(i as u8), self.opcode_count[i]) - } - } - println!("Total: {}", self.opcode_count.into_iter().sum::()); - } - - Ok((final_registers, final_mem)) + self.run_cpu(self.max_cpu_len_log) } /// Returns the max number of CPU cycles. @@ -746,203 +733,6 @@ impl Transition for Interpreter { } } -#[cfg(debug_assertions)] -fn get_mnemonic(opcode: u8) -> &'static str { - match opcode { - 0x00 => "STOP", - 0x01 => "ADD", - 0x02 => "MUL", - 0x03 => "SUB", - 0x04 => "DIV", - 0x05 => "SDIV", - 0x06 => "MOD", - 0x07 => "SMOD", - 0x08 => "ADDMOD", - 0x09 => "MULMOD", - 0x0a => "EXP", - 0x0b => "SIGNEXTEND", - 0x0c => "ADDFP254", - 0x0d => "MULFP254", - 0x0e => "SUBFP254", - 0x0f => "SUBMOD", - 0x10 => "LT", - 0x11 => "GT", - 0x12 => "SLT", - 0x13 => "SGT", - 0x14 => "EQ", - 0x15 => "ISZERO", - 0x16 => "AND", - 0x17 => "OR", - 0x18 => "XOR", - 0x19 => "NOT", - 0x1a => "BYTE", - 0x1b => "SHL", - 0x1c => "SHR", - 0x1d => "SAR", - 0x20 => "KECCAK256", - 0x21 => "KECCAK_GENERAL", - 0x30 => "ADDRESS", - 0x31 => "BALANCE", - 0x32 => "ORIGIN", - 0x33 => "CALLER", - 0x34 => "CALLVALUE", - 0x35 => "CALLDATALOAD", - 0x36 => "CALLDATASIZE", - 0x37 => "CALLDATACOPY", - 0x38 => "CODESIZE", - 0x39 => "CODECOPY", - 0x3a => "GASPRICE", - 0x3b => "EXTCODESIZE", - 0x3c => "EXTCODECOPY", - 0x3d => "RETURNDATASIZE", - 0x3e => "RETURNDATACOPY", - 0x3f => "EXTCODEHASH", - 0x40 => "BLOCKHASH", - 0x41 => "COINBASE", - 0x42 => "TIMESTAMP", - 0x43 => "NUMBER", - 0x44 => "DIFFICULTY", - 0x45 => "GASLIMIT", - 0x46 => "CHAINID", - 0x48 => "BASEFEE", - 0x4a => "BLOBBASEFEE", - 0x50 => "POP", - 0x51 => "MLOAD", - 0x52 => "MSTORE", - 0x53 => "MSTORE8", - 0x54 => "SLOAD", - 0x55 => "SSTORE", - 0x56 => "JUMP", - 0x57 => "JUMPI", - 0x58 => "GETPC", - 0x59 => "MSIZE", - 0x5a => "GAS", - 0x5b => "JUMPDEST", - 0x5e => "MCOPY", - 0x5f => "PUSH0", - 0x60 => "PUSH1", - 0x61 => "PUSH2", - 0x62 => "PUSH3", - 0x63 => "PUSH4", - 0x64 => "PUSH5", - 0x65 => "PUSH6", - 0x66 => "PUSH7", - 0x67 => "PUSH8", - 0x68 => "PUSH9", - 0x69 => "PUSH10", - 0x6a => "PUSH11", - 0x6b => "PUSH12", - 0x6c => "PUSH13", - 0x6d => "PUSH14", - 0x6e => "PUSH15", - 0x6f => "PUSH16", - 0x70 => "PUSH17", - 0x71 => "PUSH18", - 0x72 => "PUSH19", - 0x73 => "PUSH20", - 0x74 => "PUSH21", - 0x75 => "PUSH22", - 0x76 => "PUSH23", - 0x77 => "PUSH24", - 0x78 => "PUSH25", - 0x79 => "PUSH26", - 0x7a => "PUSH27", - 0x7b => "PUSH28", - 0x7c => "PUSH29", - 0x7d => "PUSH30", - 0x7e => "PUSH31", - 0x7f => "PUSH32", - 0x80 => "DUP1", - 0x81 => "DUP2", - 0x82 => "DUP3", - 0x83 => "DUP4", - 0x84 => "DUP5", - 0x85 => "DUP6", - 0x86 => "DUP7", - 0x87 => "DUP8", - 0x88 => "DUP9", - 0x89 => "DUP10", - 0x8a => "DUP11", - 0x8b => "DUP12", - 0x8c => "DUP13", - 0x8d => "DUP14", - 0x8e => "DUP15", - 0x8f => "DUP16", - 0x90 => "SWAP1", - 0x91 => "SWAP2", - 0x92 => "SWAP3", - 0x93 => "SWAP4", - 0x94 => "SWAP5", - 0x95 => "SWAP6", - 0x96 => "SWAP7", - 0x97 => "SWAP8", - 0x98 => "SWAP9", - 0x99 => "SWAP10", - 0x9a => "SWAP11", - 0x9b => "SWAP12", - 0x9c => "SWAP13", - 0x9d => "SWAP14", - 0x9e => "SWAP15", - 0x9f => "SWAP16", - 0xa0 => "LOG0", - 0xa1 => "LOG1", - 0xa2 => "LOG2", - 0xa3 => "LOG3", - 0xa4 => "LOG4", - 0xa5 => "PANIC", - 0xc0 => "MSTORE_32BYTES_1", - 0xc1 => "MSTORE_32BYTES_2", - 0xc2 => "MSTORE_32BYTES_3", - 0xc3 => "MSTORE_32BYTES_4", - 0xc4 => "MSTORE_32BYTES_5", - 0xc5 => "MSTORE_32BYTES_6", - 0xc6 => "MSTORE_32BYTES_7", - 0xc7 => "MSTORE_32BYTES_8", - 0xc8 => "MSTORE_32BYTES_9", - 0xc9 => "MSTORE_32BYTES_10", - 0xca => "MSTORE_32BYTES_11", - 0xcb => "MSTORE_32BYTES_12", - 0xcc => "MSTORE_32BYTES_13", - 0xcd => "MSTORE_32BYTES_14", - 0xce => "MSTORE_32BYTES_15", - 0xcf => "MSTORE_32BYTES_16", - 0xd0 => "MSTORE_32BYTES_17", - 0xd1 => "MSTORE_32BYTES_18", - 0xd2 => "MSTORE_32BYTES_19", - 0xd3 => "MSTORE_32BYTES_20", - 0xd4 => "MSTORE_32BYTES_21", - 0xd5 => "MSTORE_32BYTES_22", - 0xd6 => "MSTORE_32BYTES_23", - 0xd7 => "MSTORE_32BYTES_24", - 0xd8 => "MSTORE_32BYTES_25", - 0xd9 => "MSTORE_32BYTES_26", - 0xda => "MSTORE_32BYTES_27", - 0xdb => "MSTORE_32BYTES_28", - 0xdc => "MSTORE_32BYTES_29", - 0xdd => "MSTORE_32BYTES_30", - 0xde => "MSTORE_32BYTES_31", - 0xdf => "MSTORE_32BYTES_32", - 0xee => "PROVER_INPUT", - 0xf0 => "CREATE", - 0xf1 => "CALL", - 0xf2 => "CALLCODE", - 0xf3 => "RETURN", - 0xf4 => "DELEGATECALL", - 0xf5 => "CREATE2", - 0xf6 => "GET_CONTEXT", - 0xf7 => "SET_CONTEXT", - 0xf8 => "MLOAD_32BYTES", - 0xf9 => "EXIT_KERNEL", - 0xfa => "STATICCALL", - 0xfb => "MLOAD_GENERAL", - 0xfc => "MSTORE_GENERAL", - 0xfd => "REVERT", - 0xfe => "INVALID", - 0xff => "SELFDESTRUCT", - _ => panic!("Unrecognized opcode {opcode}"), - } -} - #[cfg(test)] mod tests { use ethereum_types::U256; From 7db4fe8bb48596c49c142cbc9d147a1b12eeb86b Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:01:44 -0400 Subject: [PATCH 13/19] Bring in Poseidon implementation under `cdk_erigon` feature flag (#577) * Bring in Poseidon implementation Co-authored-by: Linda Guiga Co-authored-by: Alonso Gonzalez * Add Clippy job with cdk_erigon feature activated * Apply comments --------- Co-authored-by: Linda Guiga Co-authored-by: Alonso Gonzalez --- .env | 1 + .github/workflows/ci.yml | 3 + Cargo.lock | 1 + evm_arithmetization/Cargo.toml | 3 +- evm_arithmetization/src/all_stark.rs | 75 +- evm_arithmetization/src/cpu/columns/ops.rs | 3 + evm_arithmetization/src/cpu/contextops.rs | 2 + evm_arithmetization/src/cpu/control_flow.rs | 6 +- evm_arithmetization/src/cpu/cpu_stark.rs | 81 ++ evm_arithmetization/src/cpu/decode.rs | 6 +- evm_arithmetization/src/cpu/gas.rs | 6 +- .../src/cpu/kernel/interpreter.rs | 14 +- .../src/cpu/kernel/tests/account_code.rs | 8 +- .../src/cpu/kernel/tests/mod.rs | 8 +- .../kernel/tests/transaction_parsing/mod.rs | 4 +- .../src/cpu/kernel/tests/transient_storage.rs | 4 +- evm_arithmetization/src/cpu/stack.rs | 56 + .../src/fixed_recursive_verifier.rs | 14 +- evm_arithmetization/src/generation/mod.rs | 3 +- .../src/generation/prover_input.rs | 6 +- evm_arithmetization/src/generation/state.rs | 19 +- evm_arithmetization/src/lib.rs | 4 +- evm_arithmetization/src/poseidon/columns.rs | 138 +++ evm_arithmetization/src/poseidon/mod.rs | 2 + .../src/poseidon/poseidon_stark.rs | 1004 +++++++++++++++++ evm_arithmetization/src/prover.rs | 72 +- evm_arithmetization/src/verifier.rs | 67 +- evm_arithmetization/src/witness/gas.rs | 4 + evm_arithmetization/src/witness/operation.rs | 146 ++- evm_arithmetization/src/witness/traces.rs | 49 +- evm_arithmetization/src/witness/transition.rs | 21 +- evm_arithmetization/src/witness/util.rs | 31 +- evm_arithmetization/tests/two_to_one_block.rs | 28 +- proof_gen/Cargo.toml | 4 + proof_gen/src/constants.rs | 3 + proof_gen/src/prover_state.rs | 8 + zero_bin/common/Cargo.toml | 7 + zero_bin/common/src/prover_state/circuit.rs | 20 +- zero_bin/common/src/prover_state/mod.rs | 2 + zero_bin/leader/Cargo.toml | 8 +- zero_bin/tools/prove_stdio.sh | 4 + 41 files changed, 1782 insertions(+), 163 deletions(-) create mode 100644 evm_arithmetization/src/poseidon/columns.rs create mode 100644 evm_arithmetization/src/poseidon/mod.rs create mode 100644 evm_arithmetization/src/poseidon/poseidon_stark.rs diff --git a/.env b/.env index 93681e353..29111e064 100644 --- a/.env +++ b/.env @@ -8,3 +8,4 @@ LOGIC_CIRCUIT_SIZE=4..21 MEMORY_CIRCUIT_SIZE=17..24 MEMORY_BEFORE_CIRCUIT_SIZE=16..23 MEMORY_AFTER_CIRCUIT_SIZE=7..23 +POSEIDON_CIRCUIT_SIZE=4..25 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f23f911bd..43c0c1254 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -239,5 +239,8 @@ jobs: - name: Run cargo clippy run: cargo clippy --all-features --all-targets -- -D warnings -A incomplete-features + - name: Run cargo clippy (with `cdk_erigon` flag) + run: cargo clippy --all-features --all-targets --features cdk_erigon -- -D warnings -A incomplete-features + - name: Rustdoc run: cargo doc --all diff --git a/Cargo.lock b/Cargo.lock index 65163cc93..dcd859ede 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2070,6 +2070,7 @@ dependencies = [ "serde-big-array", "serde_json", "sha2", + "smt_trie", "starky", "static_assertions", "thiserror", diff --git a/evm_arithmetization/Cargo.toml b/evm_arithmetization/Cargo.toml index c18bea130..96d435bc8 100644 --- a/evm_arithmetization/Cargo.toml +++ b/evm_arithmetization/Cargo.toml @@ -48,6 +48,7 @@ serde-big-array = { workspace = true } # Local dependencies mpt_trie = { workspace = true } +smt_trie = { workspace = true, optional = true } zk_evm_proc_macro = { workspace = true } [dev-dependencies] @@ -64,7 +65,7 @@ parallel = [ "starky/parallel", ] polygon_pos = [] -cdk_erigon = [] +cdk_erigon = ["smt_trie"] [[bin]] name = "assemble" diff --git a/evm_arithmetization/src/all_stark.rs b/evm_arithmetization/src/all_stark.rs index c3b5733c1..f37021897 100644 --- a/evm_arithmetization/src/all_stark.rs +++ b/evm_arithmetization/src/all_stark.rs @@ -25,6 +25,11 @@ use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; use crate::memory::memory_stark::{self, ctl_context_pruning_looking}; use crate::memory_continuation::memory_continuation_stark::{self, MemoryContinuationStark}; +#[cfg(feature = "cdk_erigon")] +use crate::poseidon::{ + columns::POSEIDON_SPONGE_RATE, + poseidon_stark::{self, PoseidonStark, FELT_MAX_BYTES}, +}; /// Structure containing all STARKs and the cross-table lookups. #[derive(Clone)] @@ -38,6 +43,8 @@ pub struct AllStark, const D: usize> { pub(crate) memory_stark: MemoryStark, pub(crate) mem_before_stark: MemoryContinuationStark, pub(crate) mem_after_stark: MemoryContinuationStark, + #[cfg(feature = "cdk_erigon")] + pub(crate) poseidon_stark: PoseidonStark, pub(crate) cross_table_lookups: Vec>, } @@ -55,6 +62,8 @@ impl, const D: usize> Default for AllStark { memory_stark: MemoryStark::default(), mem_before_stark: MemoryContinuationStark::default(), mem_after_stark: MemoryContinuationStark::default(), + #[cfg(feature = "cdk_erigon")] + poseidon_stark: PoseidonStark::default(), cross_table_lookups: all_cross_table_lookups(), } } @@ -72,6 +81,8 @@ impl, const D: usize> AllStark { self.memory_stark.num_lookup_helper_columns(config), self.mem_before_stark.num_lookup_helper_columns(config), self.mem_after_stark.num_lookup_helper_columns(config), + #[cfg(feature = "cdk_erigon")] + self.poseidon_stark.num_lookup_helper_columns(config), ] } } @@ -90,6 +101,8 @@ pub enum Table { Memory = 6, MemBefore = 7, MemAfter = 8, + #[cfg(feature = "cdk_erigon")] + Poseidon = 9, } impl Deref for Table { @@ -98,12 +111,20 @@ impl Deref for Table { fn deref(&self) -> &Self::Target { // Hacky way to implement `Deref` for `Table` so that we don't have to // call `Table::Foo as usize`, but perhaps too ugly to be worth it. - [&0, &1, &2, &3, &4, &5, &6, &7, &8][*self as TableIdx] + #[cfg(not(feature = "cdk_erigon"))] + return [&0, &1, &2, &3, &4, &5, &6, &7, &8][*self as TableIdx]; + + #[cfg(feature = "cdk_erigon")] + [&0, &1, &2, &3, &4, &5, &6, &7, &8, &9][*self as TableIdx] } } /// Number of STARK tables. -pub(crate) const NUM_TABLES: usize = Table::MemAfter as usize + 1; +pub const NUM_TABLES: usize = if cfg!(feature = "cdk_erigon") { + Table::MemAfter as usize + 2 +} else { + Table::MemAfter as usize + 1 +}; impl Table { /// Returns all STARK table indices. @@ -118,6 +139,8 @@ impl Table { Self::Memory, Self::MemBefore, Self::MemAfter, + #[cfg(feature = "cdk_erigon")] + Self::Poseidon, ] } } @@ -135,6 +158,12 @@ pub(crate) fn all_cross_table_lookups() -> Vec> { ctl_mem_before(), ctl_mem_after(), ctl_context_pruning(), + #[cfg(feature = "cdk_erigon")] + ctl_poseidon_simple(), + #[cfg(feature = "cdk_erigon")] + ctl_poseidon_general_input(), + #[cfg(feature = "cdk_erigon")] + ctl_poseidon_general_output(), ] } @@ -307,6 +336,16 @@ fn ctl_memory() -> CrossTableLookup { memory_continuation_stark::ctl_data_memory(), memory_continuation_stark::ctl_filter(), ); + + #[cfg(feature = "cdk_erigon")] + let poseidon_general_reads = (0..FELT_MAX_BYTES * POSEIDON_SPONGE_RATE).map(|i| { + TableWithColumns::new( + *Table::Poseidon, + poseidon_stark::ctl_looking_memory(i), + poseidon_stark::ctl_looking_memory_filter(), + ) + }); + let all_lookers = vec![ cpu_memory_code_read, cpu_push_write_ops, @@ -317,8 +356,12 @@ fn ctl_memory() -> CrossTableLookup { .chain(cpu_memory_gp_ops) .chain(keccak_sponge_reads) .chain(byte_packing_ops) - .chain(iter::once(mem_before_ops)) - .collect(); + .chain(iter::once(mem_before_ops)); + + #[cfg(feature = "cdk_erigon")] + let all_lookers = all_lookers.chain(poseidon_general_reads); + + let all_lookers = all_lookers.collect(); let memory_looked = TableWithColumns::new( *Table::Memory, memory_stark::ctl_data(), @@ -368,3 +411,27 @@ fn ctl_mem_after() -> CrossTableLookup { ); CrossTableLookup::new(all_lookers, mem_after_looked) } + +#[cfg(feature = "cdk_erigon")] +fn ctl_poseidon_simple() -> CrossTableLookup { + CrossTableLookup::new( + vec![cpu_stark::ctl_poseidon_simple_op()], + poseidon_stark::ctl_looked_simple_op(), + ) +} + +#[cfg(feature = "cdk_erigon")] +fn ctl_poseidon_general_input() -> CrossTableLookup { + CrossTableLookup::new( + vec![cpu_stark::ctl_poseidon_general_input()], + poseidon_stark::ctl_looked_general_input(), + ) +} + +#[cfg(feature = "cdk_erigon")] +fn ctl_poseidon_general_output() -> CrossTableLookup { + CrossTableLookup::new( + vec![cpu_stark::ctl_poseidon_general_output()], + poseidon_stark::ctl_looked_general_output(), + ) +} diff --git a/evm_arithmetization/src/cpu/columns/ops.rs b/evm_arithmetization/src/cpu/columns/ops.rs index c3d1281a6..5e91457e9 100644 --- a/evm_arithmetization/src/cpu/columns/ops.rs +++ b/evm_arithmetization/src/cpu/columns/ops.rs @@ -20,6 +20,9 @@ pub(crate) struct OpsColumnsView { pub shift: T, /// Combines JUMPDEST and KECCAK_GENERAL flags. pub jumpdest_keccak_general: T, + /// Combines POSEIDON and POSEIDON_GENERAL flags. + #[cfg(feature = "cdk_erigon")] + pub poseidon: T, /// Combines JUMP and JUMPI flags. pub jumps: T, /// Combines PUSH and PROVER_INPUT flags. diff --git a/evm_arithmetization/src/cpu/contextops.rs b/evm_arithmetization/src/cpu/contextops.rs index 9fdd92f29..11ffe0da3 100644 --- a/evm_arithmetization/src/cpu/contextops.rs +++ b/evm_arithmetization/src/cpu/contextops.rs @@ -23,6 +23,8 @@ const KEEPS_CONTEXT: OpsColumnsView = OpsColumnsView { not_pop: true, shift: true, jumpdest_keccak_general: true, + #[cfg(feature = "cdk_erigon")] + poseidon: true, push_prover_input: true, jumps: true, pc_push0: true, diff --git a/evm_arithmetization/src/cpu/control_flow.rs b/evm_arithmetization/src/cpu/control_flow.rs index b32ee0ae7..0aa1a307e 100644 --- a/evm_arithmetization/src/cpu/control_flow.rs +++ b/evm_arithmetization/src/cpu/control_flow.rs @@ -8,7 +8,9 @@ use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsume use crate::cpu::columns::{CpuColumnsView, COL_MAP}; use crate::cpu::kernel::aggregator::KERNEL; -const NATIVE_INSTRUCTIONS: [usize; 12] = [ +const NATIVE_INST_LEN: usize = if cfg!(feature = "cdk_erigon") { 13 } else { 12 }; + +const NATIVE_INSTRUCTIONS: [usize; NATIVE_INST_LEN] = [ COL_MAP.op.binary_op, COL_MAP.op.ternary_op, COL_MAP.op.fp254_op, @@ -17,6 +19,8 @@ const NATIVE_INSTRUCTIONS: [usize; 12] = [ COL_MAP.op.not_pop, COL_MAP.op.shift, COL_MAP.op.jumpdest_keccak_general, + #[cfg(feature = "cdk_erigon")] + COL_MAP.op.poseidon, // Not PROVER_INPUT: it is dealt with manually below. // not JUMPS (possible need to jump) COL_MAP.op.pc_push0, diff --git a/evm_arithmetization/src/cpu/cpu_stark.rs b/evm_arithmetization/src/cpu/cpu_stark.rs index daafa164d..386f89519 100644 --- a/evm_arithmetization/src/cpu/cpu_stark.rs +++ b/evm_arithmetization/src/cpu/cpu_stark.rs @@ -462,6 +462,87 @@ pub(crate) fn ctl_filter_set_context() -> Filter { ) } +#[cfg(feature = "cdk_erigon")] +/// Returns the `TableWithColumns` for the CPU rows calling POSEIDON. +pub(crate) fn ctl_poseidon_simple_op() -> TableWithColumns { + // When executing POSEIDON, the GP memory channels are used as follows: + // GP channel 0: stack[-1] = x + // GP channel 1: stack[-2] = y + // GP channel 2: stack[-3] = z + // Such that we can compute `POSEIDON(x || y || z)`. + let mut columns = Vec::new(); + for channel in 0..3 { + for i in 0..VALUE_LIMBS / 2 { + columns.push(Column::linear_combination([ + (COL_MAP.mem_channels[channel].value[2 * i], F::ONE), + ( + COL_MAP.mem_channels[channel].value[2 * i + 1], + F::from_canonical_u64(1 << 32), + ), + ])); + } + } + columns.extend(Column::singles_next_row(COL_MAP.mem_channels[0].value)); + TableWithColumns::new(*Table::Cpu, columns, ctl_poseidon_simple_filter()) +} + +#[cfg(feature = "cdk_erigon")] +pub(crate) fn ctl_poseidon_general_input() -> TableWithColumns { + // When executing POSEIDON_GENERAL, the GP memory channels are used as follows: + // GP channel 0: stack[-1] = addr (context, segment, virt) + // GP channel 1: stack[-2] = len + let (context, segment, virt) = get_addr(&COL_MAP, 0); + let context = Column::single(context); + let segment: Column = Column::single(segment); + let virt = Column::single(virt); + let len = Column::single(COL_MAP.mem_channels[1].value[0]); + + let num_channels = F::from_canonical_usize(NUM_CHANNELS); + let timestamp = Column::linear_combination([(COL_MAP.clock, num_channels)]); + + TableWithColumns::new( + *Table::Cpu, + vec![context, segment, virt, len, timestamp], + ctl_poseidon_general_filter(), + ) +} + +#[cfg(feature = "cdk_erigon")] +/// CTL filter for the `POSEIDON` operation. +/// POSEIDON is differentiated from POSEIDON_GENERAL by its first bit set to 0. +pub(crate) fn ctl_poseidon_simple_filter() -> Filter { + Filter::new( + vec![( + Column::single(COL_MAP.op.poseidon), + Column::linear_combination_with_constant([(COL_MAP.opcode_bits[0], -F::ONE)], F::ONE), + )], + vec![], + ) +} + +#[cfg(feature = "cdk_erigon")] +/// CTL filter for the `POSEIDON_GENERAL` operation. +/// POSEIDON_GENERAL is differentiated from POSEIDON by its first bit set to 1. +pub(crate) fn ctl_poseidon_general_filter() -> Filter { + Filter::new( + vec![( + Column::single(COL_MAP.op.poseidon), + Column::single(COL_MAP.opcode_bits[0]), + )], + vec![], + ) +} + +#[cfg(feature = "cdk_erigon")] +/// Returns the `TableWithColumns` for the CPU rows calling POSEIDON_GENERAL. +pub(crate) fn ctl_poseidon_general_output() -> TableWithColumns { + let mut columns = Vec::new(); + columns.extend(Column::singles_next_row(COL_MAP.mem_channels[0].value)); + let num_channels = F::from_canonical_usize(NUM_CHANNELS); + columns.push(Column::linear_combination([(COL_MAP.clock, num_channels)])); + TableWithColumns::new(*Table::Cpu, columns, ctl_poseidon_general_filter()) +} + /// Disable the specified memory channels. /// Since channel 0 contains the top of the stack and is handled specially, /// channels to disable are 1, 2 or both. All cases can be expressed as a vec. diff --git a/evm_arithmetization/src/cpu/decode.rs b/evm_arithmetization/src/cpu/decode.rs index 0dfb65be5..26656528c 100644 --- a/evm_arithmetization/src/cpu/decode.rs +++ b/evm_arithmetization/src/cpu/decode.rs @@ -7,6 +7,8 @@ use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsume use crate::cpu::columns::{CpuColumnsView, COL_MAP}; +const OPCODES_LEN: usize = if cfg!(feature = "cdk_erigon") { 6 } else { 5 }; + /// List of opcode blocks /// Each block corresponds to exactly one flag, and each flag corresponds to /// exactly one block. Each block of opcodes: @@ -29,13 +31,15 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP}; /// Note: invalid opcodes are not represented here. _Any_ opcode is permitted to /// decode to `is_invalid`. The kernel then verifies that the opcode was /// _actually_ invalid. -const OPCODES: [(u8, usize, bool, usize); 5] = [ +const OPCODES: [(u8, usize, bool, usize); OPCODES_LEN] = [ // (start index of block, number of top bits to check (log2), kernel-only, flag column) // ADD, MUL, SUB, DIV, MOD, LT, GT and BYTE flags are handled partly manually here, and partly // through the Arithmetic table CTL. ADDMOD, MULMOD and SUBMOD flags are handled partly // manually here, and partly through the Arithmetic table CTL. FP254 operation flags are // handled partly manually here, and partly through the Arithmetic table CTL. (0x14, 1, false, COL_MAP.op.eq_iszero), + #[cfg(feature = "cdk_erigon")] + (0x22, 1, true, COL_MAP.op.poseidon), // AND, OR and XOR flags are handled partly manually here, and partly through the Logic table // CTL. NOT and POP are handled manually here. // SHL and SHR flags are handled partly manually here, and partly through the Logic table CTL. diff --git a/evm_arithmetization/src/cpu/gas.rs b/evm_arithmetization/src/cpu/gas.rs index c3ec89089..7c036ae9b 100644 --- a/evm_arithmetization/src/cpu/gas.rs +++ b/evm_arithmetization/src/cpu/gas.rs @@ -27,8 +27,10 @@ const SIMPLE_OPCODES: OpsColumnsView> = OpsColumnsView { not_pop: None, // This is handled manually below shift: G_VERYLOW, jumpdest_keccak_general: None, // This is handled manually below. - push_prover_input: None, // This is handled manually below. - jumps: None, // Combined flag handled separately. + #[cfg(feature = "cdk_erigon")] + poseidon: KERNEL_ONLY_INSTR, + push_prover_input: None, // This is handled manually below. + jumps: None, // Combined flag handled separately. pc_push0: G_BASE, dup_swap: G_VERYLOW, context_op: KERNEL_ONLY_INSTR, diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index d08ac6db5..82ed8e76e 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -11,7 +11,7 @@ use anyhow::anyhow; use ethereum_types::{BigEndianHash, U256}; use log::Level; use mpt_trie::partial_trie::PartialTrie; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use serde::{Deserialize, Serialize}; use crate::byte_packing::byte_packing_stark::BytePackingOp; @@ -44,7 +44,7 @@ use crate::{arithmetic, keccak, logic}; /// Halt interpreter execution whenever a jump to this offset is done. const DEFAULT_HALT_OFFSET: usize = 0xdeadbeef; -pub(crate) struct Interpreter { +pub(crate) struct Interpreter { /// The interpreter holds a `GenerationState` to keep track of the memory /// and registers. pub(crate) generation_state: GenerationState, @@ -68,7 +68,7 @@ pub(crate) struct Interpreter { /// Simulates the CPU execution from `state` until the program counter reaches /// `final_label` in the current context. -pub(crate) fn simulate_cpu_and_get_user_jumps( +pub(crate) fn simulate_cpu_and_get_user_jumps( final_label: &str, state: &GenerationState, ) -> Option>> { @@ -118,7 +118,7 @@ pub(crate) struct ExtraSegmentData { pub(crate) next_txn_index: usize, } -pub(crate) fn set_registers_and_run( +pub(crate) fn set_registers_and_run( registers: RegistersState, interpreter: &mut Interpreter, ) -> anyhow::Result<(RegistersState, Option)> { @@ -148,7 +148,7 @@ pub(crate) fn set_registers_and_run( interpreter.run() } -impl Interpreter { +impl Interpreter { /// Returns an instance of `Interpreter` given `GenerationInputs`, and /// assuming we are initializing with the `KERNEL` code. pub(crate) fn new_with_generation_inputs( @@ -498,7 +498,7 @@ impl Interpreter { } } -impl State for Interpreter { +impl State for Interpreter { /// Returns a `GenerationStateCheckpoint` to save the current registers and /// reset memory operations to the empty vector. fn checkpoint(&mut self) -> GenerationStateCheckpoint { @@ -691,7 +691,7 @@ impl State for Interpreter { } } -impl Transition for Interpreter { +impl Transition for Interpreter { fn generate_jumpdest_analysis(&mut self, dst: usize) -> bool { if self.is_jumpdest_analysis && !self.generation_state.registers.is_kernel { self.add_jumpdest_offset(dst); diff --git a/evm_arithmetization/src/cpu/kernel/tests/account_code.rs b/evm_arithmetization/src/cpu/kernel/tests/account_code.rs index 0ca79a368..a69d29c83 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/account_code.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/account_code.rs @@ -7,7 +7,7 @@ use keccak_hash::keccak; use mpt_trie::nibbles::Nibbles; use mpt_trie::partial_trie::{HashedPartialTrie, Node, PartialTrie}; use plonky2::field::goldilocks_field::GoldilocksField as F; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use rand::{thread_rng, Rng}; use crate::cpu::kernel::aggregator::KERNEL; @@ -25,7 +25,7 @@ use crate::util::h2u; use crate::witness::memory::MemoryAddress; use crate::witness::operation::CONTEXT_SCALING_FACTOR; -pub(crate) fn initialize_mpts( +pub(crate) fn initialize_mpts( interpreter: &mut Interpreter, trie_inputs: &TrieInputs, ) { @@ -132,7 +132,7 @@ pub(crate) fn initialize_mpts( // Stolen from `tests/mpt/insert.rs` // Prepare the interpreter by inserting the account in the state trie. -pub(crate) fn prepare_interpreter( +pub(crate) fn prepare_interpreter( interpreter: &mut Interpreter, address: Address, account: &AccountRlp, @@ -386,7 +386,7 @@ fn test_extcodecopy() -> Result<()> { /// Prepare the interpreter for storage tests by inserting all necessary /// accounts in the state trie, adding the code we want to context 1 and /// switching the context. -fn prepare_interpreter_all_accounts( +fn prepare_interpreter_all_accounts( interpreter: &mut Interpreter, trie_inputs: TrieInputs, addr: [u8; 20], diff --git a/evm_arithmetization/src/cpu/kernel/tests/mod.rs b/evm_arithmetization/src/cpu/kernel/tests/mod.rs index 6219773d0..53f65e9f8 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mod.rs @@ -31,7 +31,7 @@ use std::{ use anyhow::Result; use ethereum_types::U256; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use super::{ aggregator::KERNEL, @@ -56,7 +56,7 @@ pub(crate) fn u256ify<'a>(hexes: impl IntoIterator) -> Result, _>>()?) } -pub(crate) fn run_interpreter( +pub(crate) fn run_interpreter( initial_offset: usize, initial_stack: Vec, ) -> anyhow::Result> { @@ -73,7 +73,7 @@ pub(crate) struct InterpreterMemoryInitialization { pub memory: Vec<(usize, Vec)>, } -pub(crate) fn run_interpreter_with_memory( +pub(crate) fn run_interpreter_with_memory( memory_init: InterpreterMemoryInitialization, ) -> anyhow::Result> { let label = KERNEL.global_labels[&memory_init.label]; @@ -92,7 +92,7 @@ pub(crate) fn run_interpreter_with_memory( Ok(interpreter) } -impl Interpreter { +impl Interpreter { pub(crate) fn get_txn_field(&self, field: NormalizedTxnField) -> U256 { // These fields are already scaled by their respective segment. self.generation_state.memory.contexts[0].segments[Segment::TxnFields.unscale()] diff --git a/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs b/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs index 1bfa1607d..c2ee22f4f 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use crate::{ cpu::kernel::{constants::INITIAL_RLP_ADDR, interpreter::Interpreter}, @@ -11,7 +11,7 @@ mod parse_type_1_txn; mod parse_type_2_txn; mod parse_type_3_txn; -pub(crate) fn prepare_interpreter_for_txn_parsing( +pub(crate) fn prepare_interpreter_for_txn_parsing( interpreter: &mut Interpreter, entry_point: usize, exit_point: usize, diff --git a/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs index f584b1322..a5ca6fa9e 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs @@ -2,7 +2,7 @@ use anyhow::Result; use ethereum_types::U256; use once_cell::sync::Lazy; use plonky2::field::goldilocks_field::GoldilocksField as F; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use crate::cpu::kernel::aggregator::{ combined_kernel_from_files, KERNEL_FILES, NUMBER_KERNEL_FILES, @@ -16,7 +16,7 @@ use crate::memory::segments::Segment; use crate::witness::memory::MemoryAddress; use crate::GenerationInputs; -fn initialize_interpreter(interpreter: &mut Interpreter, gas_limit: U256) { +fn initialize_interpreter(interpreter: &mut Interpreter, gas_limit: U256) { let gas_limit_address = MemoryAddress { context: 0, segment: Segment::ContextMetadata.unscale(), diff --git a/evm_arithmetization/src/cpu/stack.rs b/evm_arithmetization/src/cpu/stack.rs index cd7ca703d..e5b1682ad 100644 --- a/evm_arithmetization/src/cpu/stack.rs +++ b/evm_arithmetization/src/cpu/stack.rs @@ -29,6 +29,8 @@ pub(crate) const MIGHT_OVERFLOW: OpsColumnsView = OpsColumnsView { not_pop: false, shift: false, jumpdest_keccak_general: false, + #[cfg(feature = "cdk_erigon")] + poseidon: false, push_prover_input: true, // PROVER_INPUT doesn't require the check, but PUSH does. jumps: false, pc_push0: true, @@ -101,6 +103,22 @@ pub(crate) const JUMPDEST_OP: StackBehavior = StackBehavior { disable_other_channels: true, }; +#[cfg(feature = "cdk_erigon")] +/// Stack behavior for POSEIDON. +pub(crate) const POSEIDON_OP: StackBehavior = StackBehavior { + num_pops: 3, + pushes: true, + disable_other_channels: true, +}; + +#[cfg(feature = "cdk_erigon")] +/// Stack behavior for POSEIDON_GENERAL. +pub(crate) const POSEIDON_GENERAL_OP: StackBehavior = StackBehavior { + num_pops: 2, + pushes: true, + disable_other_channels: true, +}; + // AUDITORS: If the value below is `None`, then the operation must be manually // checked to ensure that every general-purpose memory channel is either // disabled or has its read flag and address properly constrained. The same @@ -120,6 +138,8 @@ pub(crate) const STACK_BEHAVIORS: OpsColumnsView> = OpsCol disable_other_channels: false, }), jumpdest_keccak_general: None, + #[cfg(feature = "cdk_erigon")] + poseidon: None, push_prover_input: Some(StackBehavior { num_pops: 0, pushes: true, @@ -330,6 +350,23 @@ pub(crate) fn eval_packed( yield_constr, ); + #[cfg(feature = "cdk_erigon")] + { + // Constrain stack for POSEIDON. + let poseidon_filter = lv.op.poseidon * (P::ONES - lv.opcode_bits[0]); + eval_packed_one(lv, nv, poseidon_filter, POSEIDON_OP, yield_constr); + + // Constrain stack for POSEIDON_GENERAL. + let poseidon_general_filter = lv.op.poseidon * lv.opcode_bits[0]; + eval_packed_one( + lv, + nv, + poseidon_general_filter, + POSEIDON_GENERAL_OP, + yield_constr, + ); + } + // Stack constraints for POP. // The only constraints POP has are stack constraints. // Since POP and NOT are combined into one flag and they have @@ -650,6 +687,25 @@ pub(crate) fn eval_ext_circuit, const D: usize>( yield_constr, ); + #[cfg(feature = "cdk_erigon")] + { + // Constrain stack for POSEIDON. + let mut poseidon_filter = builder.sub_extension(one, lv.opcode_bits[0]); + poseidon_filter = builder.mul_extension(lv.op.poseidon, poseidon_filter); + eval_ext_circuit_one(builder, lv, nv, poseidon_filter, POSEIDON_OP, yield_constr); + + // Constrain stack for POSEIDON_GENERAL. + let poseidon_general_filter = builder.mul_extension(lv.op.poseidon, lv.opcode_bits[0]); + eval_ext_circuit_one( + builder, + lv, + nv, + poseidon_general_filter, + POSEIDON_GENERAL_OP, + yield_constr, + ); + } + // Stack constraints for POP. // The only constraints POP has are stack constraints. // Since POP and NOT are combined into one flag and they have diff --git a/evm_arithmetization/src/fixed_recursive_verifier.rs b/evm_arithmetization/src/fixed_recursive_verifier.rs index c97bdb946..8746d705a 100644 --- a/evm_arithmetization/src/fixed_recursive_verifier.rs +++ b/evm_arithmetization/src/fixed_recursive_verifier.rs @@ -751,14 +751,22 @@ where let mem_before = RecursiveCircuitsForTable::new( Table::MemBefore, &all_stark.mem_before_stark, - degree_bits_ranges[Table::MemBefore as usize].clone(), + degree_bits_ranges[*Table::MemBefore].clone(), &all_stark.cross_table_lookups, stark_config, ); let mem_after = RecursiveCircuitsForTable::new( Table::MemAfter, &all_stark.mem_after_stark, - degree_bits_ranges[Table::MemAfter as usize].clone(), + degree_bits_ranges[*Table::MemAfter].clone(), + &all_stark.cross_table_lookups, + stark_config, + ); + #[cfg(feature = "cdk_erigon")] + let poseidon = RecursiveCircuitsForTable::new( + Table::Poseidon, + &all_stark.poseidon_stark, + degree_bits_ranges[*Table::Poseidon].clone(), &all_stark.cross_table_lookups, stark_config, ); @@ -773,6 +781,8 @@ where memory, mem_before, mem_after, + #[cfg(feature = "cdk_erigon")] + poseidon, ]; let root = Self::create_segment_circuit(&by_table, stark_config); let segment_aggregation = Self::create_segment_aggregation_circuit(&root); diff --git a/evm_arithmetization/src/generation/mod.rs b/evm_arithmetization/src/generation/mod.rs index d1bbfbad5..cd3abd0a6 100644 --- a/evm_arithmetization/src/generation/mod.rs +++ b/evm_arithmetization/src/generation/mod.rs @@ -8,7 +8,6 @@ use log::log_enabled; use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie}; use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; -use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; @@ -547,7 +546,7 @@ pub fn generate_traces, const D: usize>( Ok((tables, public_values)) } -fn simulate_cpu( +fn simulate_cpu( state: &mut GenerationState, max_cpu_len_log: Option, ) -> anyhow::Result<(RegistersState, Option)> { diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index 601e1c525..c2ab4d688 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -7,7 +7,7 @@ use anyhow::{bail, Error, Result}; use ethereum_types::{BigEndianHash, H256, U256, U512}; use itertools::Itertools; use num_bigint::BigUint; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use serde::{Deserialize, Serialize}; use super::linked_list::LinkedList; @@ -52,7 +52,7 @@ impl From> for ProverInputFn { } } -impl GenerationState { +impl GenerationState { pub(crate) fn prover_input(&mut self, input_fn: &ProverInputFn) -> Result { match input_fn.0[0].as_str() { "end_of_txns" => self.run_end_of_txns(), @@ -791,7 +791,7 @@ impl GenerationState { } } -impl GenerationState { +impl GenerationState { /// Simulate the user's code and store all the jump addresses with their /// respective contexts. fn generate_jumpdest_table(&mut self) -> Result<(), ProgramError> { diff --git a/evm_arithmetization/src/generation/state.rs b/evm_arithmetization/src/generation/state.rs index 96865806a..a9a7a2913 100644 --- a/evm_arithmetization/src/generation/state.rs +++ b/evm_arithmetization/src/generation/state.rs @@ -6,7 +6,7 @@ use ethereum_types::{Address, BigEndianHash, H160, H256, U256}; use itertools::Itertools; use keccak_hash::keccak; use log::Level; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use super::mpt::TrieRootPtrs; use super::segments::GenerationSegmentData; @@ -22,6 +22,8 @@ use crate::generation::GenerationInputs; use crate::keccak_sponge::columns::KECCAK_WIDTH_BYTES; use crate::keccak_sponge::keccak_sponge_stark::KeccakSpongeOp; use crate::memory::segments::Segment; +#[cfg(feature = "cdk_erigon")] +use crate::poseidon::poseidon_stark::PoseidonOp; use crate::util::u256_to_usize; use crate::witness::errors::ProgramError; use crate::witness::memory::MemoryChannel::GeneralPurpose; @@ -39,7 +41,7 @@ use crate::{arithmetic, keccak, logic}; /// A State is either an `Interpreter` (used for tests and jumpdest analysis) or /// a `GenerationState`. -pub(crate) trait State { +pub(crate) trait State { /// Returns a `State`'s latest `Checkpoint`. fn checkpoint(&mut self) -> GenerationStateCheckpoint; @@ -148,6 +150,11 @@ pub(crate) trait State { .push(op); } + #[cfg(feature = "cdk_erigon")] + fn push_poseidon(&mut self, op: PoseidonOp) { + self.get_mut_generation_state().traces.poseidon_ops.push(op); + } + /// Returns the content of a the `KernelGeneral` segment of a `State`. fn mem_get_kernel_content(&self) -> Vec>; @@ -326,7 +333,7 @@ pub(crate) trait State { } #[derive(Debug, Default)] -pub struct GenerationState { +pub struct GenerationState { pub(crate) inputs: TrimmedGenerationInputs, pub(crate) registers: RegistersState, pub(crate) memory: MemoryState, @@ -368,7 +375,7 @@ pub struct GenerationState { pub(crate) jumpdest_table: Option>>, } -impl GenerationState { +impl GenerationState { fn preinitialize_linked_lists_and_txn_and_receipt_mpts( &mut self, trie_inputs: &TrieInputs, @@ -559,7 +566,7 @@ impl GenerationState { } } -impl State for GenerationState { +impl State for GenerationState { fn checkpoint(&mut self) -> GenerationStateCheckpoint { GenerationStateCheckpoint { registers: self.registers, @@ -665,7 +672,7 @@ impl State for GenerationState { } } -impl Transition for GenerationState { +impl Transition for GenerationState { fn skip_if_necessary(&mut self, op: Operation) -> Result { Ok(op) } diff --git a/evm_arithmetization/src/lib.rs b/evm_arithmetization/src/lib.rs index b76953311..7e456bc63 100644 --- a/evm_arithmetization/src/lib.rs +++ b/evm_arithmetization/src/lib.rs @@ -192,6 +192,8 @@ pub mod keccak_sponge; pub mod logic; pub mod memory; pub mod memory_continuation; +#[cfg(feature = "cdk_erigon")] +pub mod poseidon; // Proving system components pub mod all_stark; @@ -222,7 +224,7 @@ pub type Node = mpt_trie::partial_trie::Node; /// A type alias for `u64` of a block height. pub type BlockHeight = u64; -pub use all_stark::AllStark; +pub use all_stark::{AllStark, NUM_TABLES}; pub use fixed_recursive_verifier::AllRecursiveCircuits; pub use generation::segments::{GenerationSegmentData, SegmentDataIterator}; pub use generation::GenerationInputs; diff --git a/evm_arithmetization/src/poseidon/columns.rs b/evm_arithmetization/src/poseidon/columns.rs new file mode 100644 index 000000000..cb3823950 --- /dev/null +++ b/evm_arithmetization/src/poseidon/columns.rs @@ -0,0 +1,138 @@ +use std::mem::{size_of, transmute}; + +use plonky2::hash::poseidon; +use zk_evm_proc_macro::Columns; + +use super::poseidon_stark::FELT_MAX_BYTES; +use crate::util::indices_arr; + +pub(crate) const POSEIDON_SPONGE_WIDTH: usize = poseidon::SPONGE_WIDTH; +pub(crate) const POSEIDON_SPONGE_RATE: usize = poseidon::SPONGE_RATE; +pub(crate) const HALF_N_FULL_ROUNDS: usize = poseidon::HALF_N_FULL_ROUNDS; +pub(crate) const N_PARTIAL_ROUNDS: usize = poseidon::N_PARTIAL_ROUNDS; +pub(crate) const POSEIDON_DIGEST: usize = 4; + +#[repr(C)] +#[derive(Columns, Eq, PartialEq, Debug)] +pub(crate) struct PoseidonColumnsView { + // The base address at which we will read the input block. + pub context: T, + pub segment: T, + pub virt: T, + + /// The timestamp at which Poseidon is called. + pub timestamp: T, + + /// The length of the original input for `PoseidonGeneralOp`. 0 for + /// `PoseidonSimpleOp`. + pub len: T, + /// The number of elements that have already been absorbed prior + /// to this block. + pub already_absorbed_elements: T, + + /// If this row represents a final block row, the `i`th entry should be 1 if + /// the final chunk of input has length `i` (in other words if `len - + /// already_absorbed == i`), otherwise 0. + /// + /// If this row represents a full input block, this should contain all 0s. + pub is_final_input_len: [T; POSEIDON_SPONGE_RATE], + + /// 1 if this row represents a full input block, i.e. one in which each + /// element is an input element, not a padding element; 0 otherwise. + pub is_full_input_block: T, + + /// Registers to hold permutation inputs. + pub input: [T; POSEIDON_SPONGE_WIDTH], + + /// Holds x^3 for all elements in full rounds. + pub cubed_full: [T; 2 * HALF_N_FULL_ROUNDS * POSEIDON_SPONGE_WIDTH], + + /// Holds x^3 for the first element in partial rounds. + pub cubed_partial: [T; N_PARTIAL_ROUNDS], + + /// Holds the input of the `i`-th S-box of the `round`-th round of the first + /// set of full rounds. + pub full_sbox_0: [T; POSEIDON_SPONGE_WIDTH * (HALF_N_FULL_ROUNDS - 1)], + + /// Holds the input of the S-box of the `round`-th round of the partial + /// rounds. + pub partial_sbox: [T; N_PARTIAL_ROUNDS], + + /// Holds the input of the `i`-th S-box of the `round`-th round of the + /// second set of full rounds. + pub full_sbox_1: [T; POSEIDON_SPONGE_WIDTH * HALF_N_FULL_ROUNDS], + + /// The digest, with each element divided into two 32-bit limbs. + pub digest: [T; 2 * POSEIDON_DIGEST], + + /// The output of the hash function with the digest removed. + pub output_partial: [T; POSEIDON_SPONGE_WIDTH - POSEIDON_DIGEST], + + /// Holds the pseudo-inverse of (digest_high_limb_i - 2^32 + 1). + pub pinv: [T; POSEIDON_DIGEST], + + /// Holds the byte decomposition of the input, except for the less + /// significant byte. + pub input_bytes: [[T; FELT_MAX_BYTES - 1]; POSEIDON_SPONGE_RATE], + + /// Indicates if this is a simple operation where inputs are + /// read from the top of the stack. + pub is_simple_op: T, + + /// Indicates if this is the first row of a general operation. + pub is_first_row_general_op: T, + + pub not_padding: T, +} + +/// Returns the index the `i`-th x^3 in the `round`-th round for full rounds. +/// Note: the cubes of the two sets of full rounds are stored one after the +/// other. +pub(crate) fn reg_cubed_full(round: usize, i: usize) -> usize { + debug_assert!(i < POSEIDON_SPONGE_WIDTH); + debug_assert!(round < 2 * HALF_N_FULL_ROUNDS); + POSEIDON_SPONGE_WIDTH * round + i +} + +/// Returns the index of x^3 within for the `round`-th partial round. +pub(crate) fn reg_cubed_partial(round: usize) -> usize { + debug_assert!(round < N_PARTIAL_ROUNDS); + round +} + +/// Returns the index of the `i`-th input in the `round`-th round within +/// `full_sbox_0`. +pub(crate) fn reg_full_sbox_0(round: usize, i: usize) -> usize { + debug_assert!( + round != 0, + "First round S-box inputs are not stored as wires" + ); + debug_assert!(round < HALF_N_FULL_ROUNDS); + debug_assert!(i < POSEIDON_SPONGE_WIDTH); + POSEIDON_SPONGE_WIDTH * (round - 1) + i +} + +/// Returns the index of the input of the S-box of the `round`-th round of the +/// partial rounds. +pub(crate) fn reg_partial_sbox(round: usize) -> usize { + debug_assert!(round < N_PARTIAL_ROUNDS); + round +} + +/// Returns the index of the `i`-th input in the `round`-th round within +/// `full_sbox_1`. +pub(crate) fn reg_full_sbox_1(round: usize, i: usize) -> usize { + debug_assert!(round < HALF_N_FULL_ROUNDS); + debug_assert!(i < POSEIDON_SPONGE_WIDTH); + POSEIDON_SPONGE_WIDTH * round + i +} + +// `u8` is guaranteed to have a `size_of` of 1. +pub(crate) const NUM_COLUMNS: usize = size_of::>(); + +const fn make_col_map() -> PoseidonColumnsView { + let indices_arr = indices_arr::(); + unsafe { transmute::<[usize; NUM_COLUMNS], PoseidonColumnsView>(indices_arr) } +} + +pub(crate) const POSEIDON_COL_MAP: PoseidonColumnsView = make_col_map(); diff --git a/evm_arithmetization/src/poseidon/mod.rs b/evm_arithmetization/src/poseidon/mod.rs new file mode 100644 index 000000000..5ee77f125 --- /dev/null +++ b/evm_arithmetization/src/poseidon/mod.rs @@ -0,0 +1,2 @@ +pub mod columns; +pub mod poseidon_stark; diff --git a/evm_arithmetization/src/poseidon/poseidon_stark.rs b/evm_arithmetization/src/poseidon/poseidon_stark.rs new file mode 100644 index 000000000..0aacbaa50 --- /dev/null +++ b/evm_arithmetization/src/poseidon/poseidon_stark.rs @@ -0,0 +1,1004 @@ +use std::borrow::Borrow; +use std::marker::PhantomData; + +use itertools::Itertools; +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::field::packed::PackedField; +use plonky2::field::polynomial::PolynomialValues; +use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; +use plonky2::hash::poseidon::Poseidon; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::timed; +use plonky2::util::timing::TimingTree; +use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use starky::cross_table_lookup::TableWithColumns; +use starky::evaluation_frame::StarkEvaluationFrame; +use starky::lookup::{Column, Filter}; +use starky::stark::Stark; +use starky::util::trace_rows_to_poly_values; + +use super::columns::{ + reg_cubed_full, reg_cubed_partial, reg_full_sbox_0, reg_full_sbox_1, reg_partial_sbox, + PoseidonColumnsView, HALF_N_FULL_ROUNDS, NUM_COLUMNS, N_PARTIAL_ROUNDS, POSEIDON_COL_MAP, + POSEIDON_DIGEST, POSEIDON_SPONGE_RATE, POSEIDON_SPONGE_WIDTH, +}; +use crate::all_stark::{EvmStarkFrame, Table}; +use crate::witness::memory::MemoryAddress; + +/// Maximum number of bytes that can be packed into a field element without +/// performing a modular reduction. +// TODO(Alonso): this constant depends on the size of F, which is not bounded. +pub(crate) const FELT_MAX_BYTES: usize = 7; + +pub(crate) fn ctl_looked_simple_op() -> TableWithColumns { + let mut columns = Column::singles(POSEIDON_COL_MAP.input).collect_vec(); + columns.extend(Column::singles(POSEIDON_COL_MAP.digest)); + TableWithColumns::new( + *Table::Poseidon, + columns, + Filter::new_simple(Column::single(POSEIDON_COL_MAP.is_simple_op)), + ) +} + +pub(crate) fn ctl_looked_general_output() -> TableWithColumns { + let mut columns = Column::singles(POSEIDON_COL_MAP.digest).collect_vec(); + columns.push(Column::single(POSEIDON_COL_MAP.timestamp)); + TableWithColumns::new( + *Table::Poseidon, + columns, + Filter::new( + vec![( + Column::sum(POSEIDON_COL_MAP.is_final_input_len), + Column::linear_combination_with_constant( + [(POSEIDON_COL_MAP.is_simple_op, -F::ONE)], + F::ONE, + ), + )], + vec![], + ), + ) +} + +pub(crate) fn ctl_looked_general_input() -> TableWithColumns { + let context = Column::single(POSEIDON_COL_MAP.context); + let segment: Column = Column::single(POSEIDON_COL_MAP.segment); + let virt = Column::single(POSEIDON_COL_MAP.virt); + let len = Column::single(POSEIDON_COL_MAP.len); + + let timestamp = Column::single(POSEIDON_COL_MAP.timestamp); + + TableWithColumns::new( + *Table::Poseidon, + vec![context, segment, virt, len, timestamp], + Filter::new_simple(Column::single(POSEIDON_COL_MAP.is_first_row_general_op)), + ) +} + +pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { + let cols = POSEIDON_COL_MAP; + let mut res = vec![Column::constant(F::ONE)]; // is_read + + res.extend(Column::singles([cols.context, cols.segment])); + + res.push(Column::linear_combination_with_constant( + [ + (cols.virt, F::ONE), + (cols.already_absorbed_elements, F::ONE), + ], + F::from_canonical_usize(i), + )); + + // The first byte of of each field element is + // mem[virt + already_absorbed_elements + i/ FELT_MAX_BYTES] - \sum_j + // input_bytes[i/FELT_MAX_BYTES][j]* 256^j. + // The other bytes are input_bytes[i/FELT_MAX_BYTES][i % FELT_MAX_BYTES - 1] + if i % FELT_MAX_BYTES == 0 { + res.push(Column::linear_combination( + std::iter::once((cols.input[i / FELT_MAX_BYTES], F::ONE)).chain( + (0..FELT_MAX_BYTES - 1).map(|j| { + ( + cols.input_bytes[i / FELT_MAX_BYTES][j], + -F::from_canonical_u64(1 << (8 * (j + 1))), + ) + }), + ), + )); + } else { + res.push(Column::single( + cols.input_bytes[i / FELT_MAX_BYTES][(i % FELT_MAX_BYTES) - 1], + )); + } + res.extend((1..8).map(|_| Column::zero())); + + res.push(Column::single(cols.timestamp)); + + assert_eq!( + res.len(), + crate::memory::memory_stark::ctl_data::().len() + ); + + res +} + +pub(crate) fn ctl_looking_memory_filter() -> Filter { + let cols = POSEIDON_COL_MAP; + Filter::new( + vec![( + Column::single(cols.not_padding), + Column::linear_combination_with_constant([(cols.is_simple_op, -F::ONE)], F::ONE), + )], + vec![], + ) +} + +#[derive(Clone, Debug)] +pub(crate) enum PoseidonOp { + PoseidonSimpleOp(PoseidonSimpleOp), + PoseidonGeneralOp(PoseidonGeneralOp), +} + +#[derive(Copy, Clone, Debug)] +pub(crate) struct PoseidonSimpleOp(pub [F; POSEIDON_SPONGE_WIDTH]); + +#[derive(Clone, Debug)] +pub(crate) struct PoseidonGeneralOp { + /// The base address at which inputs are read. + pub(crate) base_address: MemoryAddress, + + /// The timestamp at which inputs are read. + pub(crate) timestamp: usize, + + /// The input that was read. We assume that it was + /// previously padded. + pub(crate) input: Vec, + + /// Length of the input before paddding. + pub(crate) len: usize, +} + +#[derive(Copy, Clone, Default)] +pub(crate) struct PoseidonStark { + pub(crate) f: PhantomData, +} + +/// Information about a Poseidon operation needed for witness generation. +impl, const D: usize> PoseidonStark { + /// Generate the rows of the trace. Note that this does not generate the + /// permuted columns used in our lookup arguments, as those are computed + /// after transposing to column-wise form. + fn generate_trace_rows( + &self, + operations: Vec>, + min_rows: usize, + ) -> Vec<[F; NUM_COLUMNS]> { + let base_len: usize = operations + .iter() + .map(|op| match op { + PoseidonOp::PoseidonSimpleOp(_) => 1, + PoseidonOp::PoseidonGeneralOp(op) => { + debug_assert!(op.input.len() % (FELT_MAX_BYTES * POSEIDON_SPONGE_RATE) == 0); + (op.input.len() + FELT_MAX_BYTES * POSEIDON_SPONGE_RATE - 1) + / (FELT_MAX_BYTES * POSEIDON_SPONGE_RATE) + } + }) + .sum(); + + let num_rows = base_len.max(min_rows).next_power_of_two(); + let mut rows = Vec::with_capacity(base_len.max(min_rows)); + + for op in operations { + match op { + PoseidonOp::PoseidonSimpleOp(op) => rows.push(self.generate_row_for_simple_op(op)), + PoseidonOp::PoseidonGeneralOp(op) => { + rows.extend(self.generate_rows_for_general_op(op)) + } + } + } + + // We generate "actual" rows for padding to avoid having to store + // another power of x, on top of x^3 and x^6. + let padding_row: [F; NUM_COLUMNS] = { + let mut tmp_row = PoseidonColumnsView::default(); + let padding_inp = [F::ZERO; POSEIDON_SPONGE_WIDTH]; + Self::generate_perm(&mut tmp_row, padding_inp); + tmp_row + } + .into(); + while rows.len() < num_rows { + rows.push(padding_row); + } + rows + } + + fn generate_row_for_simple_op(&self, op: PoseidonSimpleOp) -> [F; NUM_COLUMNS] { + let mut row = PoseidonColumnsView::default(); + Self::generate_perm(&mut row, op.0); + row.is_final_input_len[POSEIDON_SPONGE_RATE - 1] = F::ONE; + row.not_padding = F::ONE; + row.is_simple_op = F::ONE; + row.into() + } + + fn generate_rows_for_general_op(&self, op: PoseidonGeneralOp) -> Vec<[F; NUM_COLUMNS]> { + let input_blocks = op.input.chunks_exact(FELT_MAX_BYTES * POSEIDON_SPONGE_RATE); + let mut rows: Vec<[F; NUM_COLUMNS]> = + Vec::with_capacity(op.input.len() / (FELT_MAX_BYTES * POSEIDON_SPONGE_RATE)); + let last_non_padding_elt = op.len % (FELT_MAX_BYTES * POSEIDON_SPONGE_RATE); + let total_length = input_blocks.len(); + let mut already_absorbed_elements = 0; + let mut state = [F::ZERO; POSEIDON_SPONGE_WIDTH]; + for (counter, block) in input_blocks.enumerate() { + state[0..POSEIDON_SPONGE_RATE].copy_from_slice( + &block + .chunks_exact(FELT_MAX_BYTES) + .map(|first_bytes| { + let mut bytes = [0u8; POSEIDON_SPONGE_RATE]; + bytes[..7].copy_from_slice(first_bytes); + F::from_canonical_u64(u64::from_le_bytes(bytes)) + }) + .collect::>(), + ); + let mut row = if counter == total_length - 1 { + let tmp_row = + self.generate_trace_final_row_for_perm(state, &op, already_absorbed_elements); + already_absorbed_elements += last_non_padding_elt; + tmp_row + } else { + let tmp_row = + self.generate_trace_row_for_perm(state, &op, already_absorbed_elements); + already_absorbed_elements += FELT_MAX_BYTES * POSEIDON_SPONGE_RATE; + tmp_row + }; + row.not_padding = F::ONE; + for (input_bytes_chunk, block_chunk) in row + .input_bytes + .iter_mut() + .zip_eq(block.chunks(FELT_MAX_BYTES)) + { + input_bytes_chunk.copy_from_slice( + &block_chunk[1..] + .iter() + .map(|&byte| F::from_canonical_u8(byte)) + .collect::>(), + ); + } + // Set the capacity to the digest + state[POSEIDON_SPONGE_RATE..POSEIDON_SPONGE_WIDTH].copy_from_slice( + &row.digest + .chunks(2) + .map(|x| x[0] + F::from_canonical_u64(1 << 32) * x[1]) + .collect_vec(), + ); + + rows.push(row.into()); + } + if let Some(first_row) = rows.first_mut() { + first_row[POSEIDON_COL_MAP.is_first_row_general_op] = F::ONE; + } + + rows + } + + fn generate_commons( + row: &mut PoseidonColumnsView, + input: [F; POSEIDON_SPONGE_WIDTH], + op: &PoseidonGeneralOp, + already_absorbed_elements: usize, + ) { + row.context = F::from_canonical_usize(op.base_address.context); + row.segment = F::from_canonical_usize(op.base_address.segment); + row.virt = F::from_canonical_usize(op.base_address.virt); + row.timestamp = F::from_canonical_usize(op.timestamp); + row.len = F::from_canonical_usize(op.len); + row.already_absorbed_elements = F::from_canonical_usize(already_absorbed_elements); + + Self::generate_perm(row, input); + } + // One row per permutation. + fn generate_trace_row_for_perm( + &self, + input: [F; POSEIDON_SPONGE_WIDTH], + op: &PoseidonGeneralOp, + already_absorbed_elements: usize, + ) -> PoseidonColumnsView { + let mut row = PoseidonColumnsView::default(); + row.is_full_input_block = F::ONE; + + Self::generate_commons(&mut row, input, op, already_absorbed_elements); + row + } + + fn generate_trace_final_row_for_perm( + &self, + input: [F; POSEIDON_SPONGE_WIDTH], + op: &PoseidonGeneralOp, + already_absorbed_elements: usize, + ) -> PoseidonColumnsView { + let mut row = PoseidonColumnsView::default(); + // TODO(Alonso): I think we're assumming op.len is a multiple FELT_MAX_BYTES * + // POSEIDON_SPONGE_RATE. + row.is_final_input_len[op.len % (FELT_MAX_BYTES * POSEIDON_SPONGE_RATE)] = F::ONE; + + Self::generate_commons(&mut row, input, op, already_absorbed_elements); + + row + } + + fn generate_perm(row: &mut PoseidonColumnsView, input: [F; POSEIDON_SPONGE_WIDTH]) { + // Populate the round input for the first round. + row.input.copy_from_slice(&input); + + let mut state = input; + let mut round_ctr = 0; + + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_field(&mut state, round_ctr); + + for i in 0..POSEIDON_SPONGE_WIDTH { + // We do not need to store the first full_sbox_0 inputs, since they are + // the permutation's inputs. + if r != 0 { + row.full_sbox_0[reg_full_sbox_0(r, i)] = state[i]; + } + // Generate x^3 and x^6 for the SBox layer constraints. + row.cubed_full[reg_cubed_full(r, i)] = state[i].cube(); + + // Apply x^7 to the state. + state[i] *= + row.cubed_full[reg_cubed_full(r, i)] * row.cubed_full[reg_cubed_full(r, i)]; + } + state = ::mds_layer_field(&state); + round_ctr += 1; + } + + ::partial_first_constant_layer(&mut state); + state = ::mds_partial_layer_init(&state); + for r in 0..(N_PARTIAL_ROUNDS - 1) { + row.partial_sbox[reg_partial_sbox(r)] = state[0]; + + // Generate x^3 for the SBox layer constraints. + row.cubed_partial[reg_cubed_partial(r)] = state[0] * state[0] * state[0]; + + state[0] *= + row.cubed_partial[reg_cubed_partial(r)] * row.cubed_partial[reg_cubed_partial(r)]; + state[0] += F::from_canonical_u64(::FAST_PARTIAL_ROUND_CONSTANTS[r]); + state = ::mds_partial_layer_fast_field(&state, r); + } + + row.partial_sbox[reg_partial_sbox(N_PARTIAL_ROUNDS - 1)] = state[0]; + // Generate x^3 and x^6 for the SBox layer constraints. + row.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)] = state[0].cube(); + + state[0] *= row.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)] + * row.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)]; + state = ::mds_partial_layer_fast_field(&state, N_PARTIAL_ROUNDS - 1); + round_ctr += N_PARTIAL_ROUNDS; + + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_field(&mut state, round_ctr); + for i in 0..POSEIDON_SPONGE_WIDTH { + row.full_sbox_1[reg_full_sbox_1(r, i)] = state[i]; + // Generate x^3 and x^6 for the SBox layer constraints. + row.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)] = state[i].cube(); + + state[i] *= row.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)] + * row.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)]; + } + state = ::mds_layer_field(&state); + round_ctr += 1; + } + + for i in 0..POSEIDON_DIGEST { + let state_val = state[i].to_canonical_u64(); + let hi_limb = F::from_canonical_u32((state_val >> 32) as u32); + row.pinv[i] = + if let Some(inv) = (hi_limb - F::from_canonical_u32(u32::MAX)).try_inverse() { + inv + } else { + F::ZERO + }; + row.digest[2 * i] = F::from_canonical_u32(state_val as u32); + row.digest[2 * i + 1] = hi_limb; + } + row.output_partial + .copy_from_slice(&state[POSEIDON_DIGEST..POSEIDON_SPONGE_WIDTH]); + } + + pub(crate) fn generate_trace( + &self, + operations: Vec>, + min_rows: usize, + timing: &mut TimingTree, + ) -> Vec> { + // Generate the witness, except for permuted columns in the lookup argument. + let trace_rows = timed!( + timing, + "generate trace rows", + self.generate_trace_rows(operations, min_rows) + ); + let trace_polys = timed!( + timing, + "convert to PolynomialValues", + trace_rows_to_poly_values(trace_rows) + ); + trace_polys + } +} + +impl, const D: usize> Stark for PoseidonStark { + type EvaluationFrame = EvmStarkFrame + where + FE: FieldExtension, + P: PackedField; + + type EvaluationFrameTarget = EvmStarkFrame, ExtensionTarget, NUM_COLUMNS>; + + fn eval_packed_generic( + &self, + vars: &Self::EvaluationFrame, + yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField, + { + let lv: &[P; NUM_COLUMNS] = vars.get_local_values().try_into().unwrap(); + let lv: &PoseidonColumnsView

= lv.borrow(); + let nv: &[P; NUM_COLUMNS] = vars.get_next_values().try_into().unwrap(); + let nv: &PoseidonColumnsView

= nv.borrow(); + + // Each flag must be boolean. + let is_full_input_block = lv.is_full_input_block; + yield_constr.constraint(is_full_input_block * (is_full_input_block - P::ONES)); + + let is_final_block: P = lv.is_final_input_len.iter().copied().sum(); + yield_constr.constraint(is_final_block * (is_final_block - P::ONES)); + + for &is_final_len in lv.is_final_input_len.iter() { + yield_constr.constraint(is_final_len * (is_final_len - P::ONES)); + } + + let is_first_row_general_op = lv.is_first_row_general_op; + yield_constr.constraint(is_first_row_general_op * (is_first_row_general_op - P::ONES)); + + // Ensure that full-input block and final block flags are not set to 1 at the + // same time. + yield_constr.constraint(is_final_block * is_full_input_block); + + // If this is the first row, the original sponge state should have the input in + // the first `POSEIDON_SPONGE_RATE` elements followed by 0 for the + // capacity elements. The input values are checked with a CTL. + // Also, already_absorbed_elements = 0. + let already_absorbed_elements = lv.already_absorbed_elements; + yield_constr.constraint_first_row(already_absorbed_elements); + + for i in POSEIDON_SPONGE_RATE..POSEIDON_SPONGE_WIDTH { + // If the operation has len > 0 the capacity must be 0 + yield_constr.constraint_first_row(lv.len * lv.input[i]); + } + + // If this is a final row and there is an upcoming operation, then + // we make the previous checks for next row's `already_absorbed_elements` + // and the original sponge state. + yield_constr.constraint_transition(is_final_block * nv.already_absorbed_elements); + + for i in POSEIDON_SPONGE_RATE..POSEIDON_SPONGE_WIDTH { + // If the next block is a general operation (len > 0) and this is a final block, + // the capacity must be 0 for the next row. + yield_constr.constraint_transition(nv.len * is_final_block * nv.input[i]); + } + + // If this is a full-input block, the next row's address, + // time and len must match as well as its timestamp. + yield_constr.constraint_transition(is_full_input_block * (lv.context - nv.context)); + yield_constr.constraint_transition(is_full_input_block * (lv.segment - nv.segment)); + yield_constr.constraint_transition(is_full_input_block * (lv.virt - nv.virt)); + yield_constr.constraint_transition(is_full_input_block * (lv.timestamp - nv.timestamp)); + + // If this is a full-input block, the next row's already_absorbed_elements + // should be ours plus `POSEIDON_SPONGE_RATE`, and the next input's + // capacity is the current digest. + yield_constr.constraint_transition( + is_full_input_block + * (already_absorbed_elements + + P::from(FE::from_canonical_usize( + FELT_MAX_BYTES * POSEIDON_SPONGE_RATE, + )) + - nv.already_absorbed_elements), + ); + + for i in 0..POSEIDON_SPONGE_WIDTH - POSEIDON_SPONGE_RATE { + yield_constr.constraint_transition( + is_full_input_block + * (lv.digest[2 * i] + + lv.digest[2 * i + 1] * P::Scalar::from_canonical_u64(1 << 32) + - nv.input[POSEIDON_SPONGE_RATE + i]), + ); + } + + // A dummy row is always followed by another dummy row, so the prover + // can't put dummy rows "in between" to avoid the above checks. + let is_dummy = P::ONES - is_full_input_block - is_final_block; + let next_is_final_block: P = nv.is_final_input_len.iter().copied().sum(); + yield_constr + .constraint_transition(is_dummy * (nv.is_full_input_block + next_is_final_block)); + + // If len > 0 and this is a final block, is_final_input_len implies `len + // - already_absorbed == i`. + let offset = lv.len - already_absorbed_elements; + for (i, &is_final_len) in lv.is_final_input_len.iter().enumerate() { + let entry_match = offset + - P::from(FE::from_canonical_usize( + FELT_MAX_BYTES * POSEIDON_SPONGE_RATE - i, + )); + yield_constr.constraint(lv.len * is_final_len * entry_match); + } + + // Compute the input layer. We assume that, when necessary, + // input values were previously swapped before being passed + // to Poseidon. + let mut state = lv.input; + + let mut round_ctr = 0; + + // First set of full rounds. + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_packed_field(&mut state, round_ctr); + + for i in 0..POSEIDON_SPONGE_WIDTH { + if r != 0 { + let sbox_in = lv.full_sbox_0[reg_full_sbox_0(r, i)]; + yield_constr.constraint(state[i] - sbox_in); + state[i] = sbox_in; + } + + // Check that the powers were correctly generated. + let cube = state[i] * state[i] * state[i]; + yield_constr.constraint(cube - lv.cubed_full[reg_cubed_full(r, i)]); + + state[i] *= + lv.cubed_full[reg_cubed_full(r, i)] * lv.cubed_full[reg_cubed_full(r, i)]; + } + + state = ::mds_layer_packed_field(&state); + round_ctr += 1; + } + + // Partial rounds. + ::partial_first_constant_layer_packed_field(&mut state); + state = ::mds_partial_layer_init_packed_field(&state); + for r in 0..(N_PARTIAL_ROUNDS - 1) { + let sbox_in = lv.partial_sbox[reg_partial_sbox(r)]; + yield_constr.constraint(state[0] - sbox_in); + state[0] = sbox_in; + + // Check that the powers were generated correctly. + let cube = state[0] * state[0] * state[0]; + yield_constr.constraint(cube - lv.cubed_partial[reg_cubed_partial(r)]); + + state[0] = lv.cubed_partial[reg_cubed_partial(r)] + * lv.cubed_partial[reg_cubed_partial(r)] + * sbox_in; + state[0] += + P::Scalar::from_canonical_u64(::FAST_PARTIAL_ROUND_CONSTANTS[r]); + state = ::mds_partial_layer_fast_packed_field(&state, r); + } + let sbox_in = lv.partial_sbox[reg_partial_sbox(N_PARTIAL_ROUNDS - 1)]; + yield_constr.constraint(state[0] - sbox_in); + state[0] = sbox_in; + + // Check that the powers were generated correctly. + let cube = state[0] * state[0] * state[0]; + yield_constr.constraint(cube - lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)]); + + state[0] = lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)] + * lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)] + * sbox_in; + state = ::mds_partial_layer_fast_packed_field(&state, N_PARTIAL_ROUNDS - 1); + round_ctr += N_PARTIAL_ROUNDS; + + // Second set of full rounds. + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_packed_field(&mut state, round_ctr); + for i in 0..POSEIDON_SPONGE_WIDTH { + let sbox_in = lv.full_sbox_1[reg_full_sbox_1(r, i)]; + yield_constr.constraint(state[i] - sbox_in); + state[i] = sbox_in; + + // Check that the powers were correctly generated. + let cube = state[i] * state[i] * state[i]; + yield_constr + .constraint(cube - lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)]); + + state[i] *= lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)] + * lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)]; + } + state = ::mds_layer_packed_field(&state); + round_ctr += 1; + } + + for i in 0..POSEIDON_DIGEST { + yield_constr.constraint( + state[i] + - (lv.digest[2 * i] + + lv.digest[2 * i + 1] * P::Scalar::from_canonical_u64(1 << 32)), + ); + } + for i in POSEIDON_DIGEST..POSEIDON_SPONGE_WIDTH { + yield_constr.constraint(state[i] - lv.output_partial[i - POSEIDON_DIGEST]) + } + + // Ensure that the output limbs are written in canonical form. + for i in 0..POSEIDON_DIGEST { + let constr = ((lv.digest[2 * i + 1] - P::Scalar::from_canonical_u32(u32::MAX)) + * lv.pinv[i] + - P::ONES) + * lv.digest[2 * i]; + yield_constr.constraint(constr); + } + } + + fn eval_ext_circuit( + &self, + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + vars: &Self::EvaluationFrameTarget, + yield_constr: &mut RecursiveConstraintConsumer, + ) { + let lv: &[ExtensionTarget; NUM_COLUMNS] = vars.get_local_values().try_into().unwrap(); + let lv: &PoseidonColumnsView> = lv.borrow(); + let nv: &[ExtensionTarget; NUM_COLUMNS] = vars.get_next_values().try_into().unwrap(); + let nv: &PoseidonColumnsView> = nv.borrow(); + + //Each flag (full-input block, final block or implied dummy flag) must be + //boolean. + let is_full_input_block = lv.is_full_input_block; + let constr = builder.mul_sub_extension( + is_full_input_block, + is_full_input_block, + is_full_input_block, + ); + yield_constr.constraint(builder, constr); + + let is_final_block = builder.add_many_extension(lv.is_final_input_len); + let constr = builder.mul_sub_extension(is_final_block, is_final_block, is_final_block); + yield_constr.constraint(builder, constr); + + for &is_final_len in lv.is_final_input_len.iter() { + let constr = builder.mul_sub_extension(is_final_len, is_final_len, is_final_len); + yield_constr.constraint(builder, constr); + } + + let one = builder.one_extension(); + let is_first_row_general_op = lv.is_first_row_general_op; + let constr = builder.mul_sub_extension( + is_first_row_general_op, + is_first_row_general_op, + is_first_row_general_op, + ); + yield_constr.constraint(builder, constr); + + // Ensure that full-input block and final block flags are not set to 1 at the + // same time. + let constr = builder.mul_extension(is_final_block, is_full_input_block); + yield_constr.constraint(builder, constr); + + // If this is the first row, the original sponge state should have the input in + // the first `POSEIDON_SPONGE_RATE` elements followed by 0 for the + // capacity elements. Also, already_absorbed_elements = 0. + let already_absorbed_elements = lv.already_absorbed_elements; + yield_constr.constraint_first_row(builder, already_absorbed_elements); + + for i in POSEIDON_SPONGE_RATE..POSEIDON_SPONGE_WIDTH { + let constr = builder.mul_extension(lv.input[i], lv.len); + yield_constr.constraint_first_row(builder, constr); + } + + // If this is a final row and there is an upcoming operation, then + // we make the previous checks for next row's `already_absorbed_elements` + // and the original sponge state. + let constr = builder.mul_extension(is_final_block, nv.already_absorbed_elements); + yield_constr.constraint_transition(builder, constr); + + for i in POSEIDON_SPONGE_RATE..POSEIDON_SPONGE_WIDTH { + let mut constr = builder.mul_extension(is_final_block, nv.input[i]); + constr = builder.mul_extension(constr, nv.len); + yield_constr.constraint_transition(builder, constr); + } + + // If this is a full-input block, the next row's address, + // time and len must match as well as its timestamp. + let mut constr = builder.sub_extension(lv.context, nv.context); + constr = builder.mul_extension(is_full_input_block, constr); + yield_constr.constraint_transition(builder, constr); + let mut constr = builder.sub_extension(lv.segment, nv.segment); + constr = builder.mul_extension(is_full_input_block, constr); + yield_constr.constraint_transition(builder, constr); + let mut constr = builder.sub_extension(lv.virt, nv.virt); + constr = builder.mul_extension(is_full_input_block, constr); + yield_constr.constraint_transition(builder, constr); + let mut constr = builder.sub_extension(lv.timestamp, nv.timestamp); + constr = builder.mul_extension(is_full_input_block, constr); + yield_constr.constraint_transition(builder, constr); + + // If this is a full-input block, the next row's already_absorbed_elements + // should be ours plus `POSEIDON_SPONGE_RATE`, and the next input's + // capacity is the current output's capacity. + let diff = builder.sub_extension(already_absorbed_elements, nv.already_absorbed_elements); + let constr = builder.arithmetic_extension( + F::ONE, + F::from_canonical_usize(FELT_MAX_BYTES * POSEIDON_SPONGE_RATE), + diff, + is_full_input_block, + is_full_input_block, + ); + yield_constr.constraint_transition(builder, constr); + + for i in 0..POSEIDON_SPONGE_WIDTH - POSEIDON_SPONGE_RATE { + let mut constr = builder.mul_const_add_extension( + F::from_canonical_u64(1 << 32), + lv.digest[2 * i + 1], + lv.digest[2 * i], + ); + constr = builder.sub_extension(constr, nv.input[POSEIDON_SPONGE_RATE + i]); + constr = builder.mul_extension(is_full_input_block, constr); + yield_constr.constraint_transition(builder, constr); + } + + // A dummy row is always followed by another dummy row, so the prover can't put + // dummy rows "in between" to avoid the above checks. + let mut is_dummy = builder.add_extension(is_full_input_block, is_final_block); + is_dummy = builder.sub_extension(one, is_dummy); + let next_is_final_block = builder.add_many_extension(nv.is_final_input_len.iter()); + let mut constr = builder.add_extension(nv.is_full_input_block, next_is_final_block); + constr = builder.mul_extension(is_dummy, constr); + yield_constr.constraint_transition(builder, constr); + + // If len > 0 and this is a final block, is_final_input_len implies `len - + // already_absorbed == i` + let offset = builder.sub_extension(lv.len, already_absorbed_elements); + for (i, &is_final_len) in lv.is_final_input_len.iter().enumerate() { + let index = builder.constant_extension( + F::from_canonical_usize(FELT_MAX_BYTES * POSEIDON_SPONGE_RATE - i).into(), + ); + let entry_match = builder.sub_extension(offset, index); + let mut constr = builder.mul_extension(is_final_len, entry_match); + constr = builder.mul_extension(constr, lv.len); + yield_constr.constraint(builder, constr); + } + + // Compute the input layer. We assume that, when necessary, + // input values were previously swapped before being passed + // to Poseidon. + let mut state = lv.input; + + let mut round_ctr = 0; + + // First set of full rounds. + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_circuit(builder, &mut state, round_ctr); + for i in 0..POSEIDON_SPONGE_WIDTH { + if r != 0 { + let sbox_in = lv.full_sbox_0[reg_full_sbox_0(r, i)]; + let constr = builder.sub_extension(state[i], sbox_in); + yield_constr.constraint(builder, constr); + state[i] = sbox_in; + } + + // Check that the powers were correctly generated. + let cube = builder.mul_many_extension([state[i], state[i], state[i]]); + let constr = builder.sub_extension(cube, lv.cubed_full[reg_cubed_full(r, i)]); + yield_constr.constraint(builder, constr); + + // Update the i'th element of the state. + state[i] = builder.mul_many_extension([ + state[i], + lv.cubed_full[reg_cubed_full(r, i)], + lv.cubed_full[reg_cubed_full(r, i)], + ]); + } + + state = ::mds_layer_circuit(builder, &state); + round_ctr += 1; + } + + // Partial rounds. + ::partial_first_constant_layer_circuit(builder, &mut state); + state = ::mds_partial_layer_init_circuit(builder, &state); + for r in 0..(N_PARTIAL_ROUNDS - 1) { + let sbox_in = lv.partial_sbox[reg_partial_sbox(r)]; + let constr = builder.sub_extension(state[0], sbox_in); + yield_constr.constraint(builder, constr); + state[0] = sbox_in; + + // Check that the powers were generated correctly. + let cube = builder.mul_many_extension([state[0], state[0], state[0]]); + let constr = builder.sub_extension(cube, lv.cubed_partial[reg_cubed_partial(r)]); + yield_constr.constraint(builder, constr); + + // Update state[0]. + state[0] = builder.mul_many_extension([ + lv.cubed_partial[reg_cubed_partial(r)], + lv.cubed_partial[reg_cubed_partial(r)], + sbox_in, + ]); + state[0] = builder.add_const_extension( + state[0], + F::from_canonical_u64(::FAST_PARTIAL_ROUND_CONSTANTS[r]), + ); + state = ::mds_partial_layer_fast_circuit(builder, &state, r); + } + let sbox_in = lv.partial_sbox[reg_partial_sbox(N_PARTIAL_ROUNDS - 1)]; + let constr = builder.sub_extension(state[0], sbox_in); + yield_constr.constraint(builder, constr); + state[0] = sbox_in; + + // Check that the powers were generated correctly. + let mut constr = builder.mul_many_extension([state[0], state[0], state[0]]); + constr = builder.sub_extension( + constr, + lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)], + ); + yield_constr.constraint(builder, constr); + + state[0] = builder.mul_many_extension([ + lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)], + lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)], + sbox_in, + ]); + state = + ::mds_partial_layer_fast_circuit(builder, &state, N_PARTIAL_ROUNDS - 1); + round_ctr += N_PARTIAL_ROUNDS; + + // Second set of full rounds. + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_circuit(builder, &mut state, round_ctr); + for i in 0..POSEIDON_SPONGE_WIDTH { + let sbox_in = lv.full_sbox_1[reg_full_sbox_1(r, i)]; + let constr = builder.sub_extension(state[i], sbox_in); + yield_constr.constraint(builder, constr); + state[i] = sbox_in; + + // Check that the powers were correctly generated. + let mut constr = builder.mul_many_extension([state[i], state[i], state[i]]); + constr = builder.sub_extension( + constr, + lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)], + ); + yield_constr.constraint(builder, constr); + + // Update the i'th element of the state. + state[i] = builder.mul_many_extension([ + lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)], + lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)], + state[i], + ]); + } + + state = ::mds_layer_circuit(builder, &state); + round_ctr += 1; + } + + for i in 0..POSEIDON_DIGEST { + let val = builder.mul_const_add_extension( + F::from_canonical_u64(1 << 32), + lv.digest[2 * i + 1], + lv.digest[2 * i], + ); + let constr = builder.sub_extension(state[i], val); + yield_constr.constraint(builder, constr); + } + for i in POSEIDON_DIGEST..POSEIDON_SPONGE_WIDTH { + let constr = builder.sub_extension(state[i], lv.output_partial[i - POSEIDON_DIGEST]); + yield_constr.constraint(builder, constr); + } + + // Ensure that the output limbs are written in canonical form. + for i in 0..POSEIDON_DIGEST { + let mut constr = builder.arithmetic_extension( + F::ONE, + F::NEG_ONE * F::from_canonical_u32(u32::MAX), + lv.digest[2 * i + 1], + lv.pinv[i], + lv.pinv[i], + ); + constr = builder.mul_sub_extension(lv.digest[2 * i], constr, lv.digest[2 * i]); + + yield_constr.constraint(builder, constr); + } + } + + fn constraint_degree(&self) -> usize { + 3 + } + + fn requires_ctls(&self) -> bool { + true + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2::{ + field::{goldilocks_field::GoldilocksField, types::PrimeField64}, + plonk::config::{GenericConfig, PoseidonGoldilocksConfig}, + }; + use smt_trie::code::poseidon_hash_padded_byte_vec; + use starky::stark_testing::{test_stark_circuit_constraints, test_stark_low_degree}; + + use super::*; + + #[test] + fn test_stark_degree() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = PoseidonStark; + + let stark = S { + f: Default::default(), + }; + test_stark_low_degree(stark) + } + + #[test] + fn test_stark_circuit() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = PoseidonStark; + + let stark = S { + f: Default::default(), + }; + test_stark_circuit_constraints::(stark) + } + + #[test] + fn poseidon_correctness_test() -> Result<()> { + let input: Vec = (0..POSEIDON_SPONGE_RATE * FELT_MAX_BYTES) + .map(|_| rand::random()) + .collect(); + let int_inputs = PoseidonOp::PoseidonGeneralOp(PoseidonGeneralOp { + base_address: MemoryAddress::new( + 0, + crate::memory::segments::Segment::AccessedAddresses, + 0, + ), + input: input.clone(), + timestamp: 0, + len: POSEIDON_SPONGE_RATE * FELT_MAX_BYTES, + }); + const D: usize = 2; + type F = GoldilocksField; + type S = PoseidonStark; + + let stark = S { + f: Default::default(), + }; + + let rows = stark.generate_trace_rows(vec![int_inputs], 8); + assert_eq!(rows.len(), 8); + let last_row: &PoseidonColumnsView<_> = rows[0].borrow(); + let output: Vec<_> = (0..POSEIDON_DIGEST) + .map(|i| { + last_row.digest[2 * i] + F::from_canonical_u64(1 << 32) * last_row.digest[2 * i + 1] + }) + .collect(); + + let hash = poseidon_hash_padded_byte_vec(input); + + assert_eq!( + output + .iter() + .map(|x| x.to_noncanonical_u64()) + .collect::>(), + hash.elements + .iter() + .map(|x| x.to_noncanonical_u64()) + .collect::>() + ); + + Ok(()) + } +} diff --git a/evm_arithmetization/src/prover.rs b/evm_arithmetization/src/prover.rs index 2f308898a..bd6769607 100644 --- a/evm_arithmetization/src/prover.rs +++ b/evm_arithmetization/src/prover.rs @@ -246,9 +246,9 @@ where prove_single_table( &all_stark.arithmetic_stark, config, - &trace_poly_values[Table::Arithmetic as usize], - &trace_commitments[Table::Arithmetic as usize], - &ctl_data_per_table[Table::Arithmetic as usize], + &trace_poly_values[*Table::Arithmetic], + &trace_commitments[*Table::Arithmetic], + &ctl_data_per_table[*Table::Arithmetic], ctl_challenges, challenger, timing, @@ -261,9 +261,9 @@ where prove_single_table( &all_stark.byte_packing_stark, config, - &trace_poly_values[Table::BytePacking as usize], - &trace_commitments[Table::BytePacking as usize], - &ctl_data_per_table[Table::BytePacking as usize], + &trace_poly_values[*Table::BytePacking], + &trace_commitments[*Table::BytePacking], + &ctl_data_per_table[*Table::BytePacking], ctl_challenges, challenger, timing, @@ -276,9 +276,9 @@ where prove_single_table( &all_stark.cpu_stark, config, - &trace_poly_values[Table::Cpu as usize], - &trace_commitments[Table::Cpu as usize], - &ctl_data_per_table[Table::Cpu as usize], + &trace_poly_values[*Table::Cpu], + &trace_commitments[*Table::Cpu], + &ctl_data_per_table[*Table::Cpu], ctl_challenges, challenger, timing, @@ -291,9 +291,9 @@ where prove_single_table( &all_stark.keccak_stark, config, - &trace_poly_values[Table::Keccak as usize], - &trace_commitments[Table::Keccak as usize], - &ctl_data_per_table[Table::Keccak as usize], + &trace_poly_values[*Table::Keccak], + &trace_commitments[*Table::Keccak], + &ctl_data_per_table[*Table::Keccak], ctl_challenges, challenger, timing, @@ -306,9 +306,9 @@ where prove_single_table( &all_stark.keccak_sponge_stark, config, - &trace_poly_values[Table::KeccakSponge as usize], - &trace_commitments[Table::KeccakSponge as usize], - &ctl_data_per_table[Table::KeccakSponge as usize], + &trace_poly_values[*Table::KeccakSponge], + &trace_commitments[*Table::KeccakSponge], + &ctl_data_per_table[*Table::KeccakSponge], ctl_challenges, challenger, timing, @@ -321,9 +321,9 @@ where prove_single_table( &all_stark.logic_stark, config, - &trace_poly_values[Table::Logic as usize], - &trace_commitments[Table::Logic as usize], - &ctl_data_per_table[Table::Logic as usize], + &trace_poly_values[*Table::Logic], + &trace_commitments[*Table::Logic], + &ctl_data_per_table[*Table::Logic], ctl_challenges, challenger, timing, @@ -336,9 +336,9 @@ where prove_single_table( &all_stark.memory_stark, config, - &trace_poly_values[Table::Memory as usize], - &trace_commitments[Table::Memory as usize], - &ctl_data_per_table[Table::Memory as usize], + &trace_poly_values[*Table::Memory], + &trace_commitments[*Table::Memory], + &ctl_data_per_table[*Table::Memory], ctl_challenges, challenger, timing, @@ -351,9 +351,9 @@ where prove_single_table( &all_stark.mem_before_stark, config, - &trace_poly_values[Table::MemBefore as usize], - &trace_commitments[Table::MemBefore as usize], - &ctl_data_per_table[Table::MemBefore as usize], + &trace_poly_values[*Table::MemBefore], + &trace_commitments[*Table::MemBefore], + &ctl_data_per_table[*Table::MemBefore], ctl_challenges, challenger, timing, @@ -366,9 +366,25 @@ where prove_single_table( &all_stark.mem_after_stark, config, - &trace_poly_values[Table::MemAfter as usize], - &trace_commitments[Table::MemAfter as usize], - &ctl_data_per_table[Table::MemAfter as usize], + &trace_poly_values[*Table::MemAfter], + &trace_commitments[*Table::MemAfter], + &ctl_data_per_table[*Table::MemAfter], + ctl_challenges, + challenger, + timing, + abort_signal.clone(), + )? + ); + #[cfg(feature = "cdk_erigon")] + let (poseidon_proof, _) = timed!( + timing, + "prove poseidon STARK", + prove_single_table( + &all_stark.poseidon_stark, + config, + &trace_poly_values[*Table::Poseidon], + &trace_commitments[*Table::Poseidon], + &ctl_data_per_table[*Table::Poseidon], ctl_challenges, challenger, timing, @@ -387,6 +403,8 @@ where memory_proof, mem_before_proof, mem_after_proof, + #[cfg(feature = "cdk_erigon")] + poseidon_proof, ], mem_before_cap, mem_after_cap, diff --git a/evm_arithmetization/src/verifier.rs b/evm_arithmetization/src/verifier.rs index 845c58eb5..52c2c21eb 100644 --- a/evm_arithmetization/src/verifier.rs +++ b/evm_arithmetization/src/verifier.rs @@ -141,6 +141,8 @@ fn verify_proof, C: GenericConfig, const memory_stark, mem_before_stark, mem_after_stark, + #[cfg(feature = "cdk_erigon")] + poseidon_stark, cross_table_lookups, } = all_stark; @@ -156,74 +158,83 @@ fn verify_proof, C: GenericConfig, const verify_stark_proof_with_challenges( arithmetic_stark, - &stark_proofs[Table::Arithmetic as usize].proof, - &stark_challenges[Table::Arithmetic as usize], - Some(&ctl_vars_per_table[Table::Arithmetic as usize]), + &stark_proofs[*Table::Arithmetic].proof, + &stark_challenges[*Table::Arithmetic], + Some(&ctl_vars_per_table[*Table::Arithmetic]), &[], config, )?; verify_stark_proof_with_challenges( byte_packing_stark, - &stark_proofs[Table::BytePacking as usize].proof, - &stark_challenges[Table::BytePacking as usize], - Some(&ctl_vars_per_table[Table::BytePacking as usize]), + &stark_proofs[*Table::BytePacking].proof, + &stark_challenges[*Table::BytePacking], + Some(&ctl_vars_per_table[*Table::BytePacking]), &[], config, )?; verify_stark_proof_with_challenges( cpu_stark, - &stark_proofs[Table::Cpu as usize].proof, - &stark_challenges[Table::Cpu as usize], - Some(&ctl_vars_per_table[Table::Cpu as usize]), + &stark_proofs[*Table::Cpu].proof, + &stark_challenges[*Table::Cpu], + Some(&ctl_vars_per_table[*Table::Cpu]), &[], config, )?; verify_stark_proof_with_challenges( keccak_stark, - &stark_proofs[Table::Keccak as usize].proof, - &stark_challenges[Table::Keccak as usize], - Some(&ctl_vars_per_table[Table::Keccak as usize]), + &stark_proofs[*Table::Keccak].proof, + &stark_challenges[*Table::Keccak], + Some(&ctl_vars_per_table[*Table::Keccak]), &[], config, )?; verify_stark_proof_with_challenges( keccak_sponge_stark, - &stark_proofs[Table::KeccakSponge as usize].proof, - &stark_challenges[Table::KeccakSponge as usize], - Some(&ctl_vars_per_table[Table::KeccakSponge as usize]), + &stark_proofs[*Table::KeccakSponge].proof, + &stark_challenges[*Table::KeccakSponge], + Some(&ctl_vars_per_table[*Table::KeccakSponge]), &[], config, )?; verify_stark_proof_with_challenges( logic_stark, - &stark_proofs[Table::Logic as usize].proof, - &stark_challenges[Table::Logic as usize], - Some(&ctl_vars_per_table[Table::Logic as usize]), + &stark_proofs[*Table::Logic].proof, + &stark_challenges[*Table::Logic], + Some(&ctl_vars_per_table[*Table::Logic]), &[], config, )?; verify_stark_proof_with_challenges( memory_stark, - &stark_proofs[Table::Memory as usize].proof, - &stark_challenges[Table::Memory as usize], - Some(&ctl_vars_per_table[Table::Memory as usize]), + &stark_proofs[*Table::Memory].proof, + &stark_challenges[*Table::Memory], + Some(&ctl_vars_per_table[*Table::Memory]), &[], config, )?; verify_stark_proof_with_challenges( mem_before_stark, - &stark_proofs[Table::MemBefore as usize].proof, - &stark_challenges[Table::MemBefore as usize], - Some(&ctl_vars_per_table[Table::MemBefore as usize]), + &stark_proofs[*Table::MemBefore].proof, + &stark_challenges[*Table::MemBefore], + Some(&ctl_vars_per_table[*Table::MemBefore]), &[], config, )?; verify_stark_proof_with_challenges( mem_after_stark, - &stark_proofs[Table::MemAfter as usize].proof, - &stark_challenges[Table::MemAfter as usize], - Some(&ctl_vars_per_table[Table::MemAfter as usize]), + &stark_proofs[*Table::MemAfter].proof, + &stark_challenges[*Table::MemAfter], + Some(&ctl_vars_per_table[*Table::MemAfter]), + &[], + config, + )?; + #[cfg(feature = "cdk_erigon")] + verify_stark_proof_with_challenges( + poseidon_stark, + &stark_proofs[*Table::Poseidon].proof, + &stark_challenges[*Table::Poseidon], + Some(&ctl_vars_per_table[*Table::Poseidon]), &[], config, )?; @@ -240,7 +251,7 @@ fn verify_proof, C: GenericConfig, const let mut extra_looking_sums = vec![vec![F::ZERO; config.num_challenges]; NUM_TABLES]; // Memory - extra_looking_sums[Table::Memory as usize] = (0..config.num_challenges) + extra_looking_sums[*Table::Memory] = (0..config.num_challenges) .map(|i| get_memory_extra_looking_sum(&public_values, ctl_challenges.challenges[i])) .collect_vec(); diff --git a/evm_arithmetization/src/witness/gas.rs b/evm_arithmetization/src/witness/gas.rs index 54597a3eb..3a184d3d0 100644 --- a/evm_arithmetization/src/witness/gas.rs +++ b/evm_arithmetization/src/witness/gas.rs @@ -35,6 +35,10 @@ pub(crate) const fn gas_to_charge(op: Operation) -> u64 { TernaryArithmetic(MulMod) => G_MID, TernaryArithmetic(SubMod) => KERNEL_ONLY_INSTR, KeccakGeneral => KERNEL_ONLY_INSTR, + #[cfg(feature = "cdk_erigon")] + Poseidon => KERNEL_ONLY_INSTR, + #[cfg(feature = "cdk_erigon")] + PoseidonGeneral => KERNEL_ONLY_INSTR, ProverInput => KERNEL_ONLY_INSTR, Pop => G_BASE, Jump => G_MID, diff --git a/evm_arithmetization/src/witness/operation.rs b/evm_arithmetization/src/witness/operation.rs index 0076388ce..55f3d3c18 100644 --- a/evm_arithmetization/src/witness/operation.rs +++ b/evm_arithmetization/src/witness/operation.rs @@ -1,7 +1,7 @@ use ethereum_types::{BigEndianHash, U256}; use itertools::Itertools; use keccak_hash::keccak; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use super::state::KERNEL_CONTEXT; use super::transition::Transition; @@ -40,6 +40,10 @@ pub(crate) enum Operation { BinaryArithmetic(arithmetic::BinaryOperator), TernaryArithmetic(arithmetic::TernaryOperator), KeccakGeneral, + #[cfg(feature = "cdk_erigon")] + Poseidon, + #[cfg(feature = "cdk_erigon")] + PoseidonGeneral, ProverInput, Pop, Jump, @@ -66,7 +70,7 @@ pub(crate) const CONTEXT_SCALING_FACTOR: usize = 64; /// operation. Generates a new logic operation and adds it to the vector of /// operation in `LogicStark`. Adds three memory read operations to /// `MemoryStark`: for the two inputs and the output. -pub(crate) fn generate_binary_logic_op>( +pub(crate) fn generate_binary_logic_op>( op: logic::Op, state: &mut T, mut row: CpuColumnsView, @@ -84,7 +88,7 @@ pub(crate) fn generate_binary_logic_op>( Ok(()) } -pub(crate) fn generate_binary_arithmetic_op>( +pub(crate) fn generate_binary_arithmetic_op>( operator: arithmetic::BinaryOperator, state: &mut T, mut row: CpuColumnsView, @@ -115,7 +119,7 @@ pub(crate) fn generate_binary_arithmetic_op>( Ok(()) } -pub(crate) fn generate_ternary_arithmetic_op>( +pub(crate) fn generate_ternary_arithmetic_op>( operator: arithmetic::TernaryOperator, state: &mut T, mut row: CpuColumnsView, @@ -134,7 +138,7 @@ pub(crate) fn generate_ternary_arithmetic_op>( Ok(()) } -pub(crate) fn generate_keccak_general>( +pub(crate) fn generate_keccak_general>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -166,7 +170,95 @@ pub(crate) fn generate_keccak_general>( Ok(()) } -pub(crate) fn generate_prover_input>( +#[cfg(feature = "cdk_erigon")] +/// Pops 3 elements `x,y,z` from the stack, and returns `Poseidon(x || y || +/// z)[0..4]`, where values are split into 64-bit limbs, and `z` is used as the +/// capacity. Limbs are range-checked to be in canonical form in the +/// PoseidonStark. +pub(crate) fn generate_poseidon>( + state: &mut T, + mut row: CpuColumnsView, +) -> Result<(), ProgramError> { + use crate::poseidon::poseidon_stark::{PoseidonOp, PoseidonSimpleOp}; + + let generation_state = state.get_mut_generation_state(); + let [(x, _), (y, log_in1), (z, log_in2)] = + stack_pop_with_log_and_fill::<3, _>(generation_state, &mut row)?; + let arr = [ + x.0[0], x.0[1], x.0[2], x.0[3], y.0[0], y.0[1], y.0[2], y.0[3], z.0[0], z.0[1], z.0[2], + z.0[3], + ] + .map(F::from_canonical_u64); + let hash = F::poseidon(arr); + let hash = U256(std::array::from_fn(|i| hash[i].to_canonical_u64())); + log::debug!("Poseidon hashing {:?} -> {}", arr, hash); + push_no_write(generation_state, hash); + + state.push_poseidon(PoseidonOp::PoseidonSimpleOp(PoseidonSimpleOp(arr))); + + state.push_memory(log_in1); + state.push_memory(log_in2); + state.push_cpu(row); + Ok(()) +} + +#[cfg(feature = "cdk_erigon")] +pub(crate) fn generate_poseidon_general>( + state: &mut T, + mut row: CpuColumnsView, +) -> Result<(), ProgramError> { + use smt_trie::{code::poseidon_hash_padded_byte_vec, utils::hashout2u}; + + use crate::{ + cpu::membus::NUM_CHANNELS, + poseidon::poseidon_stark::{PoseidonGeneralOp, PoseidonOp}, + }; + + let clock = state.get_clock(); + let generation_state = state.get_mut_generation_state(); + let [(addr, _), (len, log_in1)] = + stack_pop_with_log_and_fill::<2, _>(generation_state, &mut row)?; + let len = u256_to_usize(len)?; + + let base_address = MemoryAddress::new_bundle(addr)?; + let input = (0..len) + .map(|i| { + let address = MemoryAddress { + virt: base_address.virt.saturating_add(i), + ..base_address + }; + let val = generation_state.memory.get_with_init(address); + generation_state.traces.memory_ops.push(MemoryOp::new( + MemoryChannel::Code, + clock, + address, + MemoryOpKind::Read, + val.0[0].into(), + )); + + val.0[0] as u8 + }) + .collect_vec(); + + let poseidon_op = PoseidonOp::PoseidonGeneralOp(PoseidonGeneralOp { + base_address, + timestamp: clock * NUM_CHANNELS, + input: input.clone(), + len: input.len(), + }); + + let hash = hashout2u(poseidon_hash_padded_byte_vec(input.clone())); + + push_no_write(generation_state, hash); + + state.push_poseidon(poseidon_op); + + state.push_memory(log_in1); + state.push_cpu(row); + Ok(()) +} + +pub(crate) fn generate_prover_input>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -194,7 +286,7 @@ pub(crate) fn generate_prover_input>( Ok(()) } -pub(crate) fn generate_pop>( +pub(crate) fn generate_pop>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -217,7 +309,7 @@ pub(crate) fn generate_pop>( Ok(()) } -pub(crate) fn generate_pc>( +pub(crate) fn generate_pc>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -230,7 +322,7 @@ pub(crate) fn generate_pc>( Ok(()) } -pub(crate) fn generate_jumpdest>( +pub(crate) fn generate_jumpdest>( state: &mut T, row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -238,7 +330,7 @@ pub(crate) fn generate_jumpdest>( Ok(()) } -pub(crate) fn generate_get_context>( +pub(crate) fn generate_get_context>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -274,7 +366,7 @@ pub(crate) fn generate_get_context>( Ok(()) } -pub(crate) fn generate_set_context>( +pub(crate) fn generate_set_context>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -359,7 +451,7 @@ pub(crate) fn generate_set_context>( Ok(()) } -pub(crate) fn generate_push>( +pub(crate) fn generate_push>( n: u8, state: &mut T, mut row: CpuColumnsView, @@ -410,7 +502,7 @@ pub(crate) fn generate_push>( // - Update `stack_top` with `val` and add 1 to `stack_len` // Since the write must happen before the read, the normal way of assigning // GP channels doesn't work and we must handle them manually. -pub(crate) fn generate_dup>( +pub(crate) fn generate_dup>( n: u8, state: &mut T, mut row: CpuColumnsView, @@ -475,7 +567,7 @@ pub(crate) fn generate_dup>( Ok(()) } -pub(crate) fn generate_swap>( +pub(crate) fn generate_swap>( n: u8, state: &mut T, mut row: CpuColumnsView, @@ -504,7 +596,7 @@ pub(crate) fn generate_swap>( Ok(()) } -pub(crate) fn generate_not>( +pub(crate) fn generate_not>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -528,7 +620,7 @@ pub(crate) fn generate_not>( Ok(()) } -pub(crate) fn generate_iszero>( +pub(crate) fn generate_iszero>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -547,7 +639,7 @@ pub(crate) fn generate_iszero>( Ok(()) } -fn append_shift>( +fn append_shift>( state: &mut T, mut row: CpuColumnsView, is_shl: bool, @@ -594,7 +686,7 @@ fn append_shift>( Ok(()) } -pub(crate) fn generate_shl>( +pub(crate) fn generate_shl>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -610,7 +702,7 @@ pub(crate) fn generate_shl>( append_shift(state, row, true, input0, input1, log_in1, result) } -pub(crate) fn generate_shr>( +pub(crate) fn generate_shr>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -625,7 +717,7 @@ pub(crate) fn generate_shr>( append_shift(state, row, false, input0, input1, log_in1, result) } -pub(crate) fn generate_syscall>( +pub(crate) fn generate_syscall>( opcode: u8, stack_values_read: usize, stack_len_increased: bool, @@ -716,7 +808,7 @@ pub(crate) fn generate_syscall>( Ok(()) } -pub(crate) fn generate_eq>( +pub(crate) fn generate_eq>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -734,7 +826,7 @@ pub(crate) fn generate_eq>( Ok(()) } -pub(crate) fn generate_exit_kernel>( +pub(crate) fn generate_exit_kernel>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -763,7 +855,7 @@ pub(crate) fn generate_exit_kernel>( Ok(()) } -pub(crate) fn generate_mload_general>( +pub(crate) fn generate_mload_general>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -796,7 +888,7 @@ pub(crate) fn generate_mload_general>( Ok(()) } -pub(crate) fn generate_mload_32bytes>( +pub(crate) fn generate_mload_32bytes>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -836,7 +928,7 @@ pub(crate) fn generate_mload_32bytes>( Ok(()) } -pub(crate) fn generate_mstore_general>( +pub(crate) fn generate_mstore_general>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -866,7 +958,7 @@ pub(crate) fn generate_mstore_general>( Ok(()) } -pub(crate) fn generate_mstore_32bytes>( +pub(crate) fn generate_mstore_32bytes>( n: u8, state: &mut T, mut row: CpuColumnsView, @@ -886,7 +978,7 @@ pub(crate) fn generate_mstore_32bytes>( Ok(()) } -pub(crate) fn generate_exception>( +pub(crate) fn generate_exception>( exc_code: u8, state: &mut T, mut row: CpuColumnsView, diff --git a/evm_arithmetization/src/witness/traces.rs b/evm_arithmetization/src/witness/traces.rs index fe59bba59..3ff68d8b6 100644 --- a/evm_arithmetization/src/witness/traces.rs +++ b/evm_arithmetization/src/witness/traces.rs @@ -1,5 +1,6 @@ use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; +use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; @@ -13,6 +14,8 @@ use crate::cpu::columns::CpuColumnsView; use crate::generation::MemBeforeValues; use crate::keccak_sponge::keccak_sponge_stark::KeccakSpongeOp; use crate::memory_continuation::memory_continuation_stark::mem_before_values_to_rows; +#[cfg(feature = "cdk_erigon")] +use crate::poseidon::poseidon_stark::PoseidonOp; use crate::witness::memory::MemoryOp; use crate::{arithmetic, keccak, keccak_sponge, logic}; @@ -25,10 +28,12 @@ pub(crate) struct TraceCheckpoint { pub(self) keccak_sponge_len: usize, pub(self) logic_len: usize, pub(self) memory_len: usize, + #[cfg(feature = "cdk_erigon")] + pub(self) poseidon_len: usize, } #[derive(Debug)] -pub(crate) struct Traces { +pub(crate) struct Traces { pub(crate) arithmetic_ops: Vec, pub(crate) byte_packing_ops: Vec, pub(crate) cpu: Vec>, @@ -36,9 +41,11 @@ pub(crate) struct Traces { pub(crate) memory_ops: Vec, pub(crate) keccak_inputs: Vec<([u64; keccak::keccak_stark::NUM_INPUTS], usize)>, pub(crate) keccak_sponge_ops: Vec, + #[cfg(feature = "cdk_erigon")] + pub(crate) poseidon_ops: Vec>, } -impl Traces { +impl Traces { pub(crate) const fn new() -> Self { Traces { arithmetic_ops: vec![], @@ -48,6 +55,8 @@ impl Traces { memory_ops: vec![], keccak_inputs: vec![], keccak_sponge_ops: vec![], + #[cfg(feature = "cdk_erigon")] + poseidon_ops: vec![], } } @@ -84,6 +93,8 @@ impl Traces { // This is technically a lower-bound, as we may fill gaps, // but this gives a relatively good estimate. memory_len: self.memory_ops.len(), + #[cfg(feature = "cdk_erigon")] + poseidon_len: self.poseidon_ops.len(), } } @@ -97,6 +108,8 @@ impl Traces { keccak_sponge_len: self.keccak_sponge_ops.len(), logic_len: self.logic_ops.len(), memory_len: self.memory_ops.len(), + #[cfg(feature = "cdk_erigon")] + poseidon_len: self.poseidon_ops.len(), } } @@ -109,6 +122,8 @@ impl Traces { .truncate(checkpoint.keccak_sponge_len); self.logic_ops.truncate(checkpoint.logic_len); self.memory_ops.truncate(checkpoint.memory_len); + #[cfg(feature = "cdk_erigon")] + self.poseidon_ops.truncate(checkpoint.poseidon_len); } pub(crate) fn mem_ops_since(&self, checkpoint: TraceCheckpoint) -> &[MemoryOp] { @@ -140,16 +155,18 @@ impl Traces { memory_ops, keccak_inputs, keccak_sponge_ops, + #[cfg(feature = "cdk_erigon")] + poseidon_ops, } = self; let arithmetic_trace = timed!( timing, - "generate arithmetic trace", + "generate Arithmetic trace", all_stark.arithmetic_stark.generate_trace(arithmetic_ops) ); let byte_packing_trace = timed!( timing, - "generate byte packing trace", + "generate BytePacking trace", all_stark .byte_packing_stark .generate_trace(byte_packing_ops, cap_elements, timing) @@ -165,21 +182,21 @@ impl Traces { ); let keccak_sponge_trace = timed!( timing, - "generate Keccak sponge trace", + "generate KeccakSponge trace", all_stark .keccak_sponge_stark .generate_trace(keccak_sponge_ops, cap_elements, timing) ); let logic_trace = timed!( timing, - "generate logic trace", + "generate Logic trace", all_stark .logic_stark .generate_trace(logic_ops, cap_elements, timing) ); let (memory_trace, final_values, unpadded_memory_length) = timed!( timing, - "generate memory trace", + "generate Memory trace", all_stark.memory_stark.generate_trace( memory_ops, mem_before_values, @@ -187,23 +204,33 @@ impl Traces { timing ) ); + trace_lengths.memory_len = unpadded_memory_length; let mem_before_trace = timed!( timing, - "generate mem_before trace", + "generate MemBefore trace", all_stark .mem_before_stark .generate_trace(mem_before_values_to_rows(mem_before_values)) ); let mem_after_trace = timed!( timing, - "generate mem_after trace", + "generate MemAfter trace", all_stark .mem_after_stark .generate_trace(final_values.clone()) ); + #[cfg(feature = "cdk_erigon")] + let poseidon_trace = timed!( + timing, + "generate Poseidon trace", + all_stark + .poseidon_stark + .generate_trace(poseidon_ops, cap_elements, timing) + ); + log::info!( "Trace lengths (before padding): {:?}, mem_before_len: {}, mem_after_len: {}", trace_lengths, @@ -221,11 +248,13 @@ impl Traces { memory_trace, mem_before_trace, mem_after_trace, + #[cfg(feature = "cdk_erigon")] + poseidon_trace, ] } } -impl Default for Traces { +impl Default for Traces { fn default() -> Self { Self::new() } diff --git a/evm_arithmetization/src/witness/transition.rs b/evm_arithmetization/src/witness/transition.rs index ae5ae6d97..1bcbeb567 100644 --- a/evm_arithmetization/src/witness/transition.rs +++ b/evm_arithmetization/src/witness/transition.rs @@ -1,6 +1,7 @@ use ethereum_types::U256; use log::log_enabled; use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use super::util::{mem_read_gp_with_log_and_fill, stack_pop_with_log_and_fill}; use crate::cpu::columns::CpuColumnsView; @@ -22,7 +23,7 @@ use crate::{arithmetic, logic}; pub(crate) const EXC_STOP_CODE: u8 = 6; -pub(crate) fn read_code_memory>( +pub(crate) fn read_code_memory>( state: &mut T, row: &mut CpuColumnsView, ) -> u8 { @@ -90,6 +91,10 @@ pub(crate) fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Syscall(opcode, 2, false)), // SAR (0x20, _) => Ok(Operation::Syscall(opcode, 2, false)), // KECCAK256 (0x21, true) => Ok(Operation::KeccakGeneral), + #[cfg(feature = "cdk_erigon")] + (0x22, true) => Ok(Operation::Poseidon), + #[cfg(feature = "cdk_erigon")] + (0x23, true) => Ok(Operation::PoseidonGeneral), (0x30, _) => Ok(Operation::Syscall(opcode, 0, true)), // ADDRESS (0x31, _) => Ok(Operation::Syscall(opcode, 1, false)), // BALANCE (0x32, _) => Ok(Operation::Syscall(opcode, 0, true)), // ORIGIN @@ -187,6 +192,8 @@ pub(crate) fn fill_op_flag(op: Operation, row: &mut CpuColumnsView) Operation::BinaryArithmetic(_) => &mut flags.binary_op, Operation::TernaryArithmetic(_) => &mut flags.ternary_op, Operation::KeccakGeneral | Operation::Jumpdest => &mut flags.jumpdest_keccak_general, + #[cfg(feature = "cdk_erigon")] + Operation::Poseidon | Operation::PoseidonGeneral => &mut flags.poseidon, Operation::ProverInput | Operation::Push(1..) => &mut flags.push_prover_input, Operation::Jump | Operation::Jumpi => &mut flags.jumps, Operation::Pc | Operation::Push(0) => &mut flags.pc_push0, @@ -219,6 +226,8 @@ pub(crate) const fn get_op_special_length(op: Operation) -> Option { Operation::BinaryArithmetic(_) => STACK_BEHAVIORS.binary_op, Operation::TernaryArithmetic(_) => STACK_BEHAVIORS.ternary_op, Operation::KeccakGeneral | Operation::Jumpdest => STACK_BEHAVIORS.jumpdest_keccak_general, + #[cfg(feature = "cdk_erigon")] + Operation::Poseidon | Operation::PoseidonGeneral => STACK_BEHAVIORS.poseidon, Operation::Jump => JUMP_OP, Operation::Jumpi => JUMPI_OP, Operation::GetContext | Operation::SetContext => None, @@ -258,6 +267,8 @@ pub(crate) const fn might_overflow_op(op: Operation) -> bool { Operation::BinaryArithmetic(_) => MIGHT_OVERFLOW.binary_op, Operation::TernaryArithmetic(_) => MIGHT_OVERFLOW.ternary_op, Operation::KeccakGeneral | Operation::Jumpdest => MIGHT_OVERFLOW.jumpdest_keccak_general, + #[cfg(feature = "cdk_erigon")] + Operation::Poseidon | Operation::PoseidonGeneral => MIGHT_OVERFLOW.poseidon, Operation::Jump | Operation::Jumpi => MIGHT_OVERFLOW.jumps, Operation::Pc | Operation::Push(0) => MIGHT_OVERFLOW.pc_push0, Operation::GetContext | Operation::SetContext => MIGHT_OVERFLOW.context_op, @@ -267,7 +278,7 @@ pub(crate) const fn might_overflow_op(op: Operation) -> bool { } } -pub(crate) fn log_kernel_instruction>(state: &mut S, op: Operation) { +pub(crate) fn log_kernel_instruction>(state: &mut S, op: Operation) { // The logic below is a bit costly, so skip it if debug logs aren't enabled. if !log_enabled!(log::Level::Debug) { return; @@ -298,7 +309,7 @@ pub(crate) fn log_kernel_instruction>(state: &mut S, op: O assert!(pc < KERNEL.code.len(), "Kernel PC is out of range: {}", pc); } -pub(crate) trait Transition: State +pub(crate) trait Transition: State where Self: Sized, { @@ -504,6 +515,10 @@ where Operation::BinaryArithmetic(op) => generate_binary_arithmetic_op(op, self, row), Operation::TernaryArithmetic(op) => generate_ternary_arithmetic_op(op, self, row), Operation::KeccakGeneral => generate_keccak_general(self, row), + #[cfg(feature = "cdk_erigon")] + Operation::Poseidon => generate_poseidon(self, row), + #[cfg(feature = "cdk_erigon")] + Operation::PoseidonGeneral => generate_poseidon_general(self, row), Operation::ProverInput => generate_prover_input(self, row), Operation::Pop => generate_pop(self, row), Operation::Jump => self.generate_jump(row), diff --git a/evm_arithmetization/src/witness/util.rs b/evm_arithmetization/src/witness/util.rs index bca6f580c..5769f6600 100644 --- a/evm_arithmetization/src/witness/util.rs +++ b/evm_arithmetization/src/witness/util.rs @@ -1,5 +1,6 @@ use ethereum_types::U256; use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use super::memory::DUMMY_MEMOP; use super::transition::Transition; @@ -31,7 +32,7 @@ fn to_bits_le(n: u8) -> [F; 8] { } /// Peek at the stack item `i`th from the top. If `i=0` this gives the tip. -pub(crate) fn stack_peek( +pub(crate) fn stack_peek( state: &GenerationState, i: usize, ) -> Result { @@ -50,7 +51,7 @@ pub(crate) fn stack_peek( } /// Peek at kernel at specified segment and address -pub(crate) fn current_context_peek( +pub(crate) fn current_context_peek( state: &GenerationState, segment: Segment, virt: usize, @@ -72,14 +73,14 @@ pub(crate) fn fill_channel_with_value(row: &mut CpuColumnsView, n: /// Pushes without writing in memory. This happens in opcodes where a push /// immediately follows a pop. -pub(crate) fn push_no_write(state: &mut GenerationState, val: U256) { +pub(crate) fn push_no_write(state: &mut GenerationState, val: U256) { state.registers.stack_top = val; state.registers.stack_len += 1; } /// Pushes and (maybe) writes the previous stack top in memory. This happens in /// opcodes which only push. -pub(crate) fn push_with_write>( +pub(crate) fn push_with_write>( state: &mut T, row: &mut CpuColumnsView, val: U256, @@ -115,7 +116,7 @@ pub(crate) fn push_with_write>( Ok(()) } -pub(crate) fn mem_read_with_log( +pub(crate) fn mem_read_with_log( channel: MemoryChannel, address: MemoryAddress, state: &GenerationState, @@ -131,7 +132,7 @@ pub(crate) fn mem_read_with_log( (val, op) } -pub(crate) fn mem_write_log( +pub(crate) fn mem_write_log( channel: MemoryChannel, address: MemoryAddress, state: &GenerationState, @@ -146,7 +147,7 @@ pub(crate) fn mem_write_log( ) } -pub(crate) fn mem_read_code_with_log_and_fill( +pub(crate) fn mem_read_code_with_log_and_fill( address: MemoryAddress, state: &GenerationState, row: &mut CpuColumnsView, @@ -159,7 +160,7 @@ pub(crate) fn mem_read_code_with_log_and_fill( (val_u8, op) } -pub(crate) fn mem_read_gp_with_log_and_fill( +pub(crate) fn mem_read_gp_with_log_and_fill( n: usize, address: MemoryAddress, state: &GenerationState, @@ -183,7 +184,7 @@ pub(crate) fn mem_read_gp_with_log_and_fill( (val, op) } -pub(crate) fn mem_write_gp_log_and_fill( +pub(crate) fn mem_write_gp_log_and_fill( n: usize, address: MemoryAddress, state: &GenerationState, @@ -208,7 +209,7 @@ pub(crate) fn mem_write_gp_log_and_fill( op } -pub(crate) fn mem_write_partial_log_and_fill( +pub(crate) fn mem_write_partial_log_and_fill( address: MemoryAddress, state: &GenerationState, row: &mut CpuColumnsView, @@ -230,7 +231,7 @@ pub(crate) fn mem_write_partial_log_and_fill( // Channel 0 already contains the top of the stack. You only need to read // from the second popped element. // If the resulting stack isn't empty, update `stack_top`. -pub(crate) fn stack_pop_with_log_and_fill( +pub(crate) fn stack_pop_with_log_and_fill( state: &mut GenerationState, row: &mut CpuColumnsView, ) -> Result<[(U256, MemoryOp); N], ProgramError> { @@ -267,7 +268,7 @@ pub(crate) fn stack_pop_with_log_and_fill( Ok(result) } -fn xor_into_sponge>( +fn xor_into_sponge>( state: &mut T, sponge_state: &mut [u8; KECCAK_WIDTH_BYTES], block: &[u8; KECCAK_RATE_BYTES], @@ -283,7 +284,7 @@ fn xor_into_sponge>( } } -pub(crate) fn keccak_sponge_log>( +pub(crate) fn keccak_sponge_log>( state: &mut T, base_address: MemoryAddress, input: Vec, @@ -339,7 +340,7 @@ pub(crate) fn keccak_sponge_log>( }); } -pub(crate) fn byte_packing_log>( +pub(crate) fn byte_packing_log>( state: &mut T, base_address: MemoryAddress, bytes: Vec, @@ -371,7 +372,7 @@ pub(crate) fn byte_packing_log>( }); } -pub(crate) fn byte_unpacking_log>( +pub(crate) fn byte_unpacking_log>( state: &mut T, base_address: MemoryAddress, val: U256, diff --git a/evm_arithmetization/tests/two_to_one_block.rs b/evm_arithmetization/tests/two_to_one_block.rs index ba0396693..51eaa4ac7 100644 --- a/evm_arithmetization/tests/two_to_one_block.rs +++ b/evm_arithmetization/tests/two_to_one_block.rs @@ -180,10 +180,10 @@ fn test_two_to_one_block_aggregation() -> anyhow::Result<()> { let all_stark = AllStark::::default(); let config = StarkConfig::standard_fast_config(); - let all_circuits = AllRecursiveCircuits::::new( - &all_stark, - &[ - 16..17, + + let circuit_ranges = if cfg!(feature = "cdk_erigon") { + vec![ + 16..17_usize, 8..9, 14..15, 9..10, @@ -192,7 +192,25 @@ fn test_two_to_one_block_aggregation() -> anyhow::Result<()> { 17..18, 17..18, 7..8, - ], + 4..5, + ] + } else { + vec![ + 16..17_usize, + 8..9, + 14..15, + 9..10, + 8..9, + 7..8, + 17..18, + 17..18, + 7..8, + ] + }; + + let all_circuits = AllRecursiveCircuits::::new( + &all_stark, + &circuit_ranges.try_into().unwrap(), &config, ); diff --git a/proof_gen/Cargo.toml b/proof_gen/Cargo.toml index 87e31d9e8..304862ece 100644 --- a/proof_gen/Cargo.toml +++ b/proof_gen/Cargo.toml @@ -19,5 +19,9 @@ hashbrown = { workspace = true } # Local dependencies evm_arithmetization = { workspace = true } +[features] +default = [] +cdk_erigon = ["evm_arithmetization/cdk_erigon"] + [lints] workspace = true diff --git a/proof_gen/src/constants.rs b/proof_gen/src/constants.rs index e0b84387d..9e00a93eb 100644 --- a/proof_gen/src/constants.rs +++ b/proof_gen/src/constants.rs @@ -22,3 +22,6 @@ pub(crate) const DEFAULT_MEMORY_RANGE: Range = 17..30; pub(crate) const DEFAULT_MEMORY_BEFORE_RANGE: Range = 8..20; /// Default range to be used for the `MemoryAfterStark` table. pub(crate) const DEFAULT_MEMORY_AFTER_RANGE: Range = 16..30; +#[cfg(feature = "cdk_erigon")] +/// Default range to be used for the `PoseidonStark` table. +pub(crate) const DEFAULT_POSEIDON_RANGE: Range = 4..25; diff --git a/proof_gen/src/prover_state.rs b/proof_gen/src/prover_state.rs index bb3e5656f..7d8acf901 100644 --- a/proof_gen/src/prover_state.rs +++ b/proof_gen/src/prover_state.rs @@ -31,6 +31,8 @@ pub struct ProverStateBuilder { pub(crate) memory_circuit_size: Range, pub(crate) memory_before_circuit_size: Range, pub(crate) memory_after_circuit_size: Range, + #[cfg(feature = "cdk_erigon")] + pub(crate) poseidon_circuit_size: Range, } impl Default for ProverStateBuilder { @@ -52,6 +54,8 @@ impl Default for ProverStateBuilder { memory_circuit_size: DEFAULT_MEMORY_RANGE, memory_before_circuit_size: DEFAULT_MEMORY_BEFORE_RANGE, memory_after_circuit_size: DEFAULT_MEMORY_AFTER_RANGE, + #[cfg(feature = "cdk_erigon")] + poseidon_circuit_size: DEFAULT_POSEIDON_RANGE, } } } @@ -79,6 +83,8 @@ impl ProverStateBuilder { define_set_circuit_size_method!(memory); define_set_circuit_size_method!(memory_before); define_set_circuit_size_method!(memory_after); + #[cfg(feature = "cdk_erigon")] + define_set_circuit_size_method!(poseidon); // TODO: Consider adding async version? /// Instantiate the prover state from the builder. Note that this is a very @@ -98,6 +104,8 @@ impl ProverStateBuilder { self.memory_circuit_size, self.memory_before_circuit_size, self.memory_after_circuit_size, + #[cfg(feature = "cdk_erigon")] + self.poseidon_circuit_size, ], &StarkConfig::standard_fast_config(), ); diff --git a/zero_bin/common/Cargo.toml b/zero_bin/common/Cargo.toml index be68cc779..7171fa2a8 100644 --- a/zero_bin/common/Cargo.toml +++ b/zero_bin/common/Cargo.toml @@ -35,5 +35,12 @@ cargo_metadata = { workspace = true } vergen = { workspace = true } anyhow = { workspace = true } +[features] +default = [] +cdk_erigon = [ + "evm_arithmetization/cdk_erigon", + "proof_gen/cdk_erigon" +] + [lints] workspace = true diff --git a/zero_bin/common/src/prover_state/circuit.rs b/zero_bin/common/src/prover_state/circuit.rs index 94596c8c9..de68b09f8 100644 --- a/zero_bin/common/src/prover_state/circuit.rs +++ b/zero_bin/common/src/prover_state/circuit.rs @@ -5,16 +5,12 @@ use std::{ str::FromStr, }; +pub use evm_arithmetization::NUM_TABLES; use evm_arithmetization::{AllStark, StarkConfig}; use proof_gen::types::AllRecursiveCircuits; use crate::parsing::{parse_range_exclusive, RangeParseError}; -/// Number of tables defined in plonky2. -/// -/// TODO: This should be made public in the evm_arithmetization crate. -pub(crate) const NUM_TABLES: usize = 9; - /// New type wrapper for [`Range`] that implements [`FromStr`] and [`Display`]. /// /// Useful for using in clap arguments. @@ -68,6 +64,8 @@ pub enum Circuit { Memory, MemoryBefore, MemoryAfter, + #[cfg(feature = "cdk_erigon")] + Poseidon, } impl Display for Circuit { @@ -89,6 +87,8 @@ impl Circuit { Circuit::Memory => 17..28, Circuit::MemoryBefore => 7..23, Circuit::MemoryAfter => 7..27, + #[cfg(feature = "cdk_erigon")] + Circuit::Poseidon => 4..22, } } @@ -104,6 +104,8 @@ impl Circuit { Circuit::Memory => "MEMORY_CIRCUIT_SIZE", Circuit::MemoryBefore => "MEMORY_BEFORE_CIRCUIT_SIZE", Circuit::MemoryAfter => "MEMORY_AFTER_CIRCUIT_SIZE", + #[cfg(feature = "cdk_erigon")] + Circuit::Poseidon => "POSEIDON_CIRCUIT_SIZE", } } @@ -119,6 +121,8 @@ impl Circuit { Circuit::Memory => "memory", Circuit::MemoryBefore => "memory before", Circuit::MemoryAfter => "memory after", + #[cfg(feature = "cdk_erigon")] + Circuit::Poseidon => "poseidon", } } @@ -134,6 +138,8 @@ impl Circuit { Circuit::Memory => "m", Circuit::MemoryBefore => "m_b", Circuit::MemoryAfter => "m_a", + #[cfg(feature = "cdk_erigon")] + Circuit::Poseidon => "p", } } } @@ -150,6 +156,8 @@ impl From for Circuit { 6 => Circuit::Memory, 7 => Circuit::MemoryBefore, 8 => Circuit::MemoryAfter, + #[cfg(feature = "cdk_erigon")] + 9 => Circuit::Poseidon, _ => unreachable!(), } } @@ -189,6 +197,8 @@ impl Default for CircuitConfig { Circuit::Memory.default_size(), Circuit::MemoryBefore.default_size(), Circuit::MemoryAfter.default_size(), + #[cfg(feature = "cdk_erigon")] + Circuit::Poseidon.default_size(), ], } } diff --git a/zero_bin/common/src/prover_state/mod.rs b/zero_bin/common/src/prover_state/mod.rs index b1673e5f6..ca049268f 100644 --- a/zero_bin/common/src/prover_state/mod.rs +++ b/zero_bin/common/src/prover_state/mod.rs @@ -185,6 +185,8 @@ impl ProverStateManager { circuit!(6), circuit!(7), circuit!(8), + #[cfg(feature = "cdk_erigon")] + circuit!(9), ]) } diff --git a/zero_bin/leader/Cargo.toml b/zero_bin/leader/Cargo.toml index 1f70f96a2..cad05cd22 100644 --- a/zero_bin/leader/Cargo.toml +++ b/zero_bin/leader/Cargo.toml @@ -35,7 +35,13 @@ zero_bin_common = { workspace = true } [features] default = [] -cdk_erigon = ["prover/cdk_erigon", "evm_arithmetization/cdk_erigon", "rpc/cdk_erigon"] +cdk_erigon = [ + "evm_arithmetization/cdk_erigon", + "proof_gen/cdk_erigon", + "prover/cdk_erigon", + "rpc/cdk_erigon", + "zero_bin_common/cdk_erigon" +] [build-dependencies] cargo_metadata = { workspace = true } diff --git a/zero_bin/tools/prove_stdio.sh b/zero_bin/tools/prove_stdio.sh index d37f84bf9..35e39d400 100755 --- a/zero_bin/tools/prove_stdio.sh +++ b/zero_bin/tools/prove_stdio.sh @@ -61,6 +61,8 @@ if ! [[ $TEST_ONLY == "test_only" ]]; then export MEMORY_CIRCUIT_SIZE="18..22" export MEMORY_BEFORE_CIRCUIT_SIZE="16..20" export MEMORY_AFTER_CIRCUIT_SIZE="7..20" + # TODO(Robin): update Poseidon ranges here and below once Kernel ASM supports Poseidon ops + export POSEIDON_CIRCUIT_SIZE="4..8" elif [[ $INPUT_FILE == *"witness_b3_b6"* ]]; then # These sizes are configured specifically for custom blocks 3 to 6. Don't use this in other scenarios echo "Using specific circuit sizes for witness_b3_b6.json" @@ -73,6 +75,7 @@ if ! [[ $TEST_ONLY == "test_only" ]]; then export MEMORY_CIRCUIT_SIZE="17..22" export MEMORY_BEFORE_CIRCUIT_SIZE="17..18" export MEMORY_AFTER_CIRCUIT_SIZE="7..8" + export POSEIDON_CIRCUIT_SIZE="4..8" else export ARITHMETIC_CIRCUIT_SIZE="16..21" export BYTE_PACKING_CIRCUIT_SIZE="8..21" @@ -83,6 +86,7 @@ if ! [[ $TEST_ONLY == "test_only" ]]; then export MEMORY_CIRCUIT_SIZE="17..24" export MEMORY_BEFORE_CIRCUIT_SIZE="16..23" export MEMORY_AFTER_CIRCUIT_SIZE="7..23" + export POSEIDON_CIRCUIT_SIZE="4..8" fi fi From 7f7ce75ce6845fd7dbc0ecc262a6609852c9dcff Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:23:15 -0400 Subject: [PATCH 14/19] feat: add pre-state execution for `polygon-cdk` (#594) * WIP * Tweak * Final tweaks and test * Fix * Clippy * Add feature-gating * Comment * Add missing fix from feat/feature_gat_chain * Apply review * Forgotten file change --- .../benches/fibonacci_25m_gas.rs | 9 +- .../src/cpu/kernel/aggregator.rs | 9 +- .../src/cpu/kernel/asm/beacon_roots.asm | 10 +- .../src/cpu/kernel/asm/cdk_pre_execution.asm | 121 ++++++++++++++++++ .../src/cpu/kernel/asm/global_exit_root.asm | 48 ------- .../asm/mpt/linked_list/linked_list.asm | 16 ++- .../src/cpu/kernel/constants/mod.rs | 71 +++++++++- .../src/cpu/kernel/interpreter.rs | 4 +- .../src/cpu/kernel/tests/add11.rs | 21 +-- .../src/cpu/kernel/tests/init_exc_stop.rs | 45 +++---- evm_arithmetization/src/generation/mod.rs | 9 +- .../src/generation/prover_input.rs | 4 +- evm_arithmetization/src/generation/state.rs | 23 ++-- evm_arithmetization/src/testing_utils.rs | 68 +++++++--- evm_arithmetization/tests/add11_yml.rs | 15 +-- evm_arithmetization/tests/erc20.rs | 11 +- evm_arithmetization/tests/erc721.rs | 12 +- evm_arithmetization/tests/global_exit_root.rs | 48 +++++-- evm_arithmetization/tests/log_opcode.rs | 13 +- evm_arithmetization/tests/selfdestruct.rs | 13 +- evm_arithmetization/tests/simple_transfer.rs | 13 +- evm_arithmetization/tests/two_to_one_block.rs | 14 +- evm_arithmetization/tests/withdrawals.rs | 13 +- trace_decoder/src/decoding.rs | 2 +- 24 files changed, 383 insertions(+), 229 deletions(-) create mode 100644 evm_arithmetization/src/cpu/kernel/asm/cdk_pre_execution.asm delete mode 100644 evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm diff --git a/evm_arithmetization/benches/fibonacci_25m_gas.rs b/evm_arithmetization/benches/fibonacci_25m_gas.rs index 21702d2d1..28c9a2333 100644 --- a/evm_arithmetization/benches/fibonacci_25m_gas.rs +++ b/evm_arithmetization/benches/fibonacci_25m_gas.rs @@ -17,9 +17,8 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::testing::simulate_execution; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, - GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::Node; use hex_literal::hex; @@ -148,10 +147,6 @@ fn prepare_setup() -> anyhow::Result { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), )?; - expected_state_trie_after.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - )?; let receipt_0 = LegacyReceiptRlp { status: false, @@ -186,7 +181,7 @@ fn prepare_setup() -> anyhow::Result { checkpoint_state_trie_root: H256(hex!( "fe07ff6d1ab215df17884b89112ccf2373597285a56c5902150313ad1a53ee57" )), - global_exit_roots: vec![], + ger_data: None, block_metadata, txn_number_before: 0.into(), gas_used_before: 0.into(), diff --git a/evm_arithmetization/src/cpu/kernel/aggregator.rs b/evm_arithmetization/src/cpu/kernel/aggregator.rs index 16e3a3450..27054ac37 100644 --- a/evm_arithmetization/src/cpu/kernel/aggregator.rs +++ b/evm_arithmetization/src/cpu/kernel/aggregator.rs @@ -9,7 +9,11 @@ use super::assembler::{assemble, Kernel}; use crate::cpu::kernel::constants::evm_constants; use crate::cpu::kernel::parser::parse; -pub const NUMBER_KERNEL_FILES: usize = 159; +pub const NUMBER_KERNEL_FILES: usize = if cfg!(feature = "cdk_erigon") { + 159 +} else { + 158 +}; pub static KERNEL_FILES: [&str; NUMBER_KERNEL_FILES] = [ "global jumped_to_0: PANIC", @@ -172,7 +176,8 @@ pub static KERNEL_FILES: [&str; NUMBER_KERNEL_FILES] = [ include_str!("asm/account_code.asm"), include_str!("asm/balance.asm"), include_str!("asm/bloom_filter.asm"), - include_str!("asm/global_exit_root.asm"), + #[cfg(feature = "cdk_erigon")] + include_str!("asm/cdk_pre_execution.asm"), ]; pub static KERNEL: Lazy = Lazy::new(combined_kernel); diff --git a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm index 6fba36fec..36f491fb5 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm @@ -4,7 +4,15 @@ /// *NOTE*: This will panic if one of the provided timestamps is zero. global set_beacon_root: - PUSH set_global_exit_roots + #[cfg(feature = cdk_erigon)] + { + PUSH pre_block_execution + } + #[cfg(not(feature = cdk_erigon))] + { + PUSH txn_loop + } + %timestamp // stack: timestamp, retdest PUSH @HISTORY_BUFFER_LENGTH diff --git a/evm_arithmetization/src/cpu/kernel/asm/cdk_pre_execution.asm b/evm_arithmetization/src/cpu/kernel/asm/cdk_pre_execution.asm new file mode 100644 index 000000000..fa8828097 --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/asm/cdk_pre_execution.asm @@ -0,0 +1,121 @@ +/// CDK-Erigon pre-block execution logic. +/// Reference implementation: `cdk-erigon/core/state/intra_block_state_zkevm.go`. +/// This currently supports the Etrog upgrade. + +/// Pre-stack: (empty) +/// Post-stack: (empty) +global pre_block_execution: + // stack: (empty) + PUSH txn_loop + // stack: retdest + PUSH @ADDRESS_SCALABLE_L2 + %is_non_existent + %jumpi(create_scalable_l2_account) + +global update_scalable_block_number: + // stack: retdest + %blocknumber + PUSH @LAST_BLOCK_STORAGE_POS + // stack: last_block_slot, block_number, retdest + %write_scalable_storage + // stack: retdest + + // Check timestamp + PUSH @ADDRESS_SCALABLE_L2_STATE_KEY + PUSH @TIMESTAMP_STORAGE_POS + %read_storage_linked_list_w_state_key + // stack: old_timestamp, retdest + %timestamp + GT %jumpi(update_scalable_timestamp) + +global update_scalable_prev_block_root_hash: + // stack: retdest + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE) + // stack: prev_block_root, retdest + PUSH @STATE_ROOT_STORAGE_POS + PUSH 1 %blocknumber SUB + // stack: block_number - 1, STATE_ROOT_STORAGE_POS, prev_block_root, retdest + PUSH @SEGMENT_KERNEL_GENERAL + // stack: addr, block_number - 1, STATE_ROOT_STORAGE_POS, prev_block_root, retdest + MSTORE_32BYTES_32 + // stack: addr, STATE_ROOT_STORAGE_POS, prev_block_root, retdest + MSTORE_32BYTES_32 + // stack: addr, prev_block_root, retdest + POP + // stack: prev_block_root, retdest + PUSH 64 PUSH @SEGMENT_KERNEL_GENERAL + // stack: addr, len, prev_block_root, retdest + KECCAK_GENERAL + // stack: slot, prev_block_root, retdest + %write_scalable_storage + // stack: retdest + +// Note: We assume that if the l1 info tree has been re-used or the GER does not exist, +// the payload will not contain any root to store, in which case calling `PROVER_INPUT(ger)` +// will return `U256::MAX` causing this to return early. +global update_scalable_l1blockhash: + // stack: retdest + PROVER_INPUT(ger) + // stack: l1blockhash?, retdest + DUP1 %eq_const(@U256_MAX) %jumpi(skip_and_exit) + // stack: l1blockhash, retdest + PUSH @GLOBAL_EXIT_ROOT_STORAGE_POS + PROVER_INPUT(ger) + // stack: root, GLOBAL_EXIT_ROOT_STORAGE_POS, l1blockhash, retdest + PUSH @SEGMENT_KERNEL_GENERAL + // stack: addr, root, GLOBAL_EXIT_ROOT_STORAGE_POS, l1blockhash, retdest + MSTORE_32BYTES_32 + // stack: addr, GLOBAL_EXIT_ROOT_STORAGE_POS, l1blockhash, retdest + MSTORE_32BYTES_32 + // stack: addr, l1blockhash, retdest + POP + // stack: l1blockhash, retdest + PUSH 64 PUSH @SEGMENT_KERNEL_GENERAL + // stack: addr, len, l1blockhash, retdest + KECCAK_GENERAL + // stack: slot, l1blockhash, retdest + %slot_to_storage_key + // stack: storage_key, l1blockhash, retdest + PUSH @GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY + // stack: state_key, storage_key, l1blockhash, retdest + %insert_slot_with_value_from_keys + // stack: retdest + JUMP + +skip_and_exit: + // stack: null, retdest + POP + JUMP + +global update_scalable_timestamp: + // stack: retdest + %timestamp + PUSH @TIMESTAMP_STORAGE_POS + // stack: timestamp_slot, timestamp, retdest + %write_scalable_storage + %jump(update_scalable_prev_block_root_hash) + +global create_scalable_l2_account: + // stack: (empty) + PUSH update_scalable_block_number + // stack: retdest + %get_trie_data_size // pointer to new account we're about to create + // stack: new_account_ptr, retdest + PUSH 0 %append_to_trie_data // nonce + PUSH 0 %append_to_trie_data // balance + PUSH 0 %append_to_trie_data // storage root pointer + PUSH @EMPTY_STRING_HASH %append_to_trie_data // code hash + // stack: new_account_ptr, retdest + PUSH @ADDRESS_SCALABLE_L2_STATE_KEY + // stack: key, new_account_ptr, retdest + %jump(mpt_insert_state_trie) + +%macro write_scalable_storage + // stack: slot, value + %slot_to_storage_key + // stack: storage_key, value + PUSH @ADDRESS_SCALABLE_L2_STATE_KEY + // stack: state_key, storage_key, value + %insert_slot_with_value_from_keys + // stack: (empty) +%endmacro diff --git a/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm b/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm deleted file mode 100644 index 94c81fdf4..000000000 --- a/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm +++ /dev/null @@ -1,48 +0,0 @@ -/// At the top of the block, the global exit roots (if any) are written to storage. -/// Global exit roots (GER) are of the form `(timestamp, root)` and are loaded from prover inputs. -/// The timestamp is written to the storage of address `GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY` in the slot `keccak256(abi.encodePacked(root, GLOBAL_EXIT_ROOT_STORAGE_POS))`. -/// See https://github.com/0xPolygonHermez/cdk-erigon/blob/zkevm/zk/utils/global_exit_root.go for reference. -/// -/// *NOTE*: This will panic if one of the provided timestamps is zero. - -global set_global_exit_roots: - // stack: (empty) - PUSH txn_loop - // stack: retdest - PUSH @GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY - %addr_to_state_key - PROVER_INPUT(ger) - // stack: num_ger, state_key, retdest - PUSH 0 -ger_loop: - // stack: i, num_ger, state_key, retdest - DUP2 DUP2 EQ %jumpi(ger_loop_end) - PROVER_INPUT(ger) - // stack: timestamp, i, num_ger, state_key, retdest - PUSH @GLOBAL_EXIT_ROOT_STORAGE_POS - PROVER_INPUT(ger) - // stack: root, GLOBAL_EXIT_ROOT_STORAGE_POS, timestamp, i, num_ger, state_key, retdest - PUSH @SEGMENT_KERNEL_GENERAL - // stack: addr, root, GLOBAL_EXIT_ROOT_STORAGE_POS, timestamp, i, num_ger, state_key, retdest - MSTORE_32BYTES_32 - // stack: addr, GLOBAL_EXIT_ROOT_STORAGE_POS, timestamp, i, num_ger, state_key, retdest - MSTORE_32BYTES_32 - // stack: addr, timestamp, i, num_ger, state_key, retdest - POP - // stack: timestamp, i, num_ger, state_key, retdest - PUSH 64 PUSH @SEGMENT_KERNEL_GENERAL - // stack: addr, len, timestamp, i, num_ger, state_key, retdest - KECCAK_GENERAL - // stack: slot, timestamp, i, num_ger, state_key, retdest - %slot_to_storage_key - // stack: slot_key, timestamp, i, num_ger, state_key, retdest - DUP5 - // stack: state_key, slot_key, timestamp, i, num_ger, state_key, retdest - %insert_slot_with_value_from_keys - // stack: i, num_ger, state_key, retdest - %increment - %jump(ger_loop) - -ger_loop_end: - // stack: i, num_ger, state_key, retdest - %pop3 JUMP diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm index a28d83779..ef1213aae 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm @@ -859,9 +859,10 @@ remove_all_slots_end: %macro read_storage_linked_list // stack: slot %slot_to_storage_key + %stack (storage_key) -> (storage_key, 0, %%after) %address %addr_to_state_key - %stack (addr_key, key) -> (addr_key, key, 0, %%after) + // stack: addr_key, storage_key, 0, %%after %jump(search_slot) %%after: // stack: slot_value @@ -870,14 +871,23 @@ remove_all_slots_end: %macro read_storage_linked_list_w_addr // stack: slot, address %slot_to_storage_key - SWAP1 + %stack (storage_key, address) -> (address, storage_key, 0, %%after) %addr_to_state_key - %stack (addr_key, key) -> (addr_key, key, 0, %%after) + // stack: addr_key, storage_key, 0, %%after %jump(search_slot) %%after: // stack: slot_value %endmacro +%macro read_storage_linked_list_w_state_key + // stack: slot, state_key + %slot_to_storage_key + %stack (storage_key, state_key) -> (state_key, storage_key, 0, %%after) + %jump(search_slot) +%%after: + // stack: slot_ptr +%endmacro + %macro first_account // stack: empty PUSH @SEGMENT_ACCOUNTS_LINKED_LIST diff --git a/evm_arithmetization/src/cpu/kernel/constants/mod.rs b/evm_arithmetization/src/cpu/kernel/constants/mod.rs index 577bfb875..853e5500c 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/mod.rs @@ -85,6 +85,30 @@ pub(crate) fn evm_constants() -> HashMap { global_exit_root::GLOBAL_EXIT_ROOT_STORAGE_POS.0.into(), U256::from(global_exit_root::GLOBAL_EXIT_ROOT_STORAGE_POS.1), ); + c.insert( + global_exit_root::ADDRESS_SCALABLE_L2.0.into(), + U256::from_big_endian(&global_exit_root::ADDRESS_SCALABLE_L2.1), + ); + c.insert( + global_exit_root::ADDRESS_SCALABLE_L2_STATE_KEY.0.into(), + U256::from_big_endian(&global_exit_root::ADDRESS_SCALABLE_L2_STATE_KEY.1), + ); + c.insert( + global_exit_root::LAST_BLOCK_STORAGE_POS.0.into(), + U256::from(global_exit_root::LAST_BLOCK_STORAGE_POS.1), + ); + c.insert( + global_exit_root::STATE_ROOT_STORAGE_POS.0.into(), + U256::from(global_exit_root::STATE_ROOT_STORAGE_POS.1), + ); + c.insert( + global_exit_root::TIMESTAMP_STORAGE_POS.0.into(), + U256::from(global_exit_root::TIMESTAMP_STORAGE_POS.1), + ); + c.insert( + global_exit_root::BLOCK_INFO_ROOT_STORAGE_POS.0.into(), + U256::from(global_exit_root::BLOCK_INFO_ROOT_STORAGE_POS.1), + ); for segment in Segment::all() { c.insert(segment.var_name().into(), (segment as usize).into()); @@ -397,11 +421,11 @@ pub mod cancun_constants { hex!("000000000000000000000000000000001666c54b0a32529503432fcae0181b4bef79de09fc63671fda5ed1ba9bfa07899495346f3d7ac9cd23048ef30d0a154f"), // y_im ]; - pub const HISTORY_BUFFER_LENGTH: (&str, u64) = ("HISTORY_BUFFER_LENGTH", 8191); - // Beacon constants /////////////////// + pub const HISTORY_BUFFER_LENGTH: (&str, u64) = ("HISTORY_BUFFER_LENGTH", 8191); + pub const BEACON_ROOTS_CONTRACT_ADDRESS: Address = H160(hex!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02")); @@ -440,19 +464,42 @@ pub mod global_exit_root { use super::*; /// Taken from https://github.com/0xPolygonHermez/cdk-erigon/blob/61f0b6912055c73f6879ea7e9b5bac22ea5fc85c/zk/utils/global_exit_root.go#L16. - pub const GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY: (&str, [u8; 20]) = ( - "GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY", + pub const GLOBAL_EXIT_ROOT_MANAGER_L2: (&str, [u8; 20]) = ( + "GLOBAL_EXIT_ROOT_MANAGER_L2", hex!("a40D5f56745a118D0906a34E69aeC8C0Db1cB8fA"), ); + pub const GLOBAL_EXIT_ROOT_ADDRESS_HASHED: H256 = H256(hex!( + "1d5e9c22b4b1a781d0ef63e9c1293c2a45fee966809019aa9804b5e7148b0ca9" + )); + pub const GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY: (&str, [u8; 32]) = ( + "GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY", + GLOBAL_EXIT_ROOT_ADDRESS_HASHED.to_fixed_bytes(), + ); + /// Taken from https://github.com/0xPolygonHermez/cdk-erigon/blob/dc3cbcc59a95769626056c7bc70aade501e7741d/core/state/intra_block_state_zkevm.go#L20. + pub const ADDRESS_SCALABLE_L2: (&str, [u8; 20]) = ( + "ADDRESS_SCALABLE_L2", + hex!("000000000000000000000000000000005ca1ab1e"), + ); + pub const ADDRESS_SCALABLE_L2_ADDRESS_HASHED: H256 = H256(hex!( + "4bff39c4f33dafcd90c45c9a0a1f2e72949b200788587b306eda5dd84aa87577" + )); + pub const ADDRESS_SCALABLE_L2_STATE_KEY: (&str, [u8; 32]) = ( + "ADDRESS_SCALABLE_L2_STATE_KEY", + ADDRESS_SCALABLE_L2_ADDRESS_HASHED.to_fixed_bytes(), + ); /// Taken from https://github.com/0xPolygonHermez/cdk-erigon/blob/61f0b6912055c73f6879ea7e9b5bac22ea5fc85c/zk/utils/global_exit_root.go#L17. pub const GLOBAL_EXIT_ROOT_STORAGE_POS: (&str, u64) = ("GLOBAL_EXIT_ROOT_STORAGE_POS", 0); + /// Taken from https://github.com/0xPolygonHermez/cdk-erigon/blob/dc3cbcc59a95769626056c7bc70aade501e7741d/core/state/intra_block_state_zkevm.go#L16. + pub const LAST_BLOCK_STORAGE_POS: (&str, u64) = ("LAST_BLOCK_STORAGE_POS", 0); + pub const STATE_ROOT_STORAGE_POS: (&str, u64) = ("STATE_ROOT_STORAGE_POS", 1); + pub const TIMESTAMP_STORAGE_POS: (&str, u64) = ("TIMESTAMP_STORAGE_POS", 2); + pub const BLOCK_INFO_ROOT_STORAGE_POS: (&str, u64) = ("BLOCK_INFO_ROOT_STORAGE_POS", 3); + /// Taken from https://zkevm.polygonscan.com/address/0xa40D5f56745a118D0906a34E69aeC8C0Db1cB8fA#code. pub const GLOBAL_EXIT_ROOT_CONTRACT_CODE: [u8; 2112] = hex!("60806040526004361061004e5760003560e01c80633659cfe6146100655780634f1ef286146100855780635c60da1b146100985780638f283970146100c9578063f851a440146100e95761005d565b3661005d5761005b6100fe565b005b61005b6100fe565b34801561007157600080fd5b5061005b6100803660046106ca565b610118565b61005b6100933660046106e5565b61015f565b3480156100a457600080fd5b506100ad6101d0565b6040516001600160a01b03909116815260200160405180910390f35b3480156100d557600080fd5b5061005b6100e43660046106ca565b61020b565b3480156100f557600080fd5b506100ad610235565b610106610292565b610116610111610331565b61033b565b565b61012061035f565b6001600160a01b0316336001600160a01b031614156101575761015481604051806020016040528060008152506000610392565b50565b6101546100fe565b61016761035f565b6001600160a01b0316336001600160a01b031614156101c8576101c38383838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525060019250610392915050565b505050565b6101c36100fe565b60006101da61035f565b6001600160a01b0316336001600160a01b03161415610200576101fb610331565b905090565b6102086100fe565b90565b61021361035f565b6001600160a01b0316336001600160a01b0316141561015757610154816103f1565b600061023f61035f565b6001600160a01b0316336001600160a01b03161415610200576101fb61035f565b606061028583836040518060600160405280602781526020016107e460279139610445565b9392505050565b3b151590565b61029a61035f565b6001600160a01b0316336001600160a01b031614156101165760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b60006101fb610519565b3660008037600080366000845af43d6000803e80801561035a573d6000f35b3d6000fd5b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b61039b83610541565b6040516001600160a01b038416907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a26000825111806103dc5750805b156101c3576103eb8383610260565b50505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61041a61035f565b604080516001600160a01b03928316815291841660208301520160405180910390a1610154816105e9565b6060833b6104a45760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610328565b600080856001600160a01b0316856040516104bf9190610794565b600060405180830381855af49150503d80600081146104fa576040519150601f19603f3d011682016040523d82523d6000602084013e6104ff565b606091505b509150915061050f828286610675565b9695505050505050565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610383565b803b6105a55760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610328565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b6001600160a01b03811661064e5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b6064820152608401610328565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61036105c8565b60608315610684575081610285565b8251156106945782518084602001fd5b8160405162461bcd60e51b815260040161032891906107b0565b80356001600160a01b03811681146106c557600080fd5b919050565b6000602082840312156106dc57600080fd5b610285826106ae565b6000806000604084860312156106fa57600080fd5b610703846106ae565b9250602084013567ffffffffffffffff8082111561072057600080fd5b818601915086601f83011261073457600080fd5b81358181111561074357600080fd5b87602082850101111561075557600080fd5b6020830194508093505050509250925092565b60005b8381101561078357818101518382015260200161076b565b838111156103eb5750506000910152565b600082516107a6818460208701610768565b9190910192915050565b60208152600082518060208401526107cf816040850160208701610768565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212204675187caf3a43285d9a2c1844a981e977bd52a85ff073e7fc649f73847d70a464736f6c63430008090033"); pub const GLOBAL_EXIT_ROOT_CONTRACT_CODE_HASH: [u8; 32] = hex!("6bec2bf64f7e824109f6ed55f77dd7665801d6195e461666ad6a5342a9f6daf5"); - pub const GLOBAL_EXIT_ROOT_ADDRESS_HASHED: [u8; 32] = - hex!("1d5e9c22b4b1a781d0ef63e9c1293c2a45fee966809019aa9804b5e7148b0ca9"); pub const GLOBAL_EXIT_ROOT_ACCOUNT: AccountRlp = AccountRlp { nonce: U256::zero(), @@ -463,4 +510,16 @@ pub mod global_exit_root { )), code_hash: H256(GLOBAL_EXIT_ROOT_CONTRACT_CODE_HASH), }; + + #[test] + fn hashed() { + assert_eq!( + keccak_hash::keccak(GLOBAL_EXIT_ROOT_MANAGER_L2.1), + GLOBAL_EXIT_ROOT_ADDRESS_HASHED + ); + assert_eq!( + keccak_hash::keccak(ADDRESS_SCALABLE_L2.1), + ADDRESS_SCALABLE_L2_ADDRESS_HASHED + ); + } } diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index 82ed8e76e..3d44d8040 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -22,7 +22,7 @@ use crate::generation::debug_inputs; use crate::generation::mpt::{load_linked_lists_and_txn_and_receipt_mpts, TrieRootPtrs}; use crate::generation::rlp::all_rlp_prover_inputs_reversed; use crate::generation::state::{ - all_ger_prover_inputs_reversed, all_withdrawals_prover_inputs_reversed, GenerationState, + all_ger_prover_inputs, all_withdrawals_prover_inputs_reversed, GenerationState, GenerationStateCheckpoint, }; use crate::generation::{state::State, GenerationInputs}; @@ -256,7 +256,7 @@ impl Interpreter { // Update the RLP and withdrawal prover inputs. let rlp_prover_inputs = all_rlp_prover_inputs_reversed(&inputs.signed_txns); let withdrawal_prover_inputs = all_withdrawals_prover_inputs_reversed(&inputs.withdrawals); - let ger_prover_inputs = all_ger_prover_inputs_reversed(&inputs.global_exit_roots); + let ger_prover_inputs = all_ger_prover_inputs(inputs.ger_data); self.generation_state.rlp_prover_inputs = rlp_prover_inputs; self.generation_state.withdrawal_prover_inputs = withdrawal_prover_inputs; self.generation_state.ger_prover_inputs = ger_prover_inputs; diff --git a/evm_arithmetization/src/cpu/kernel/tests/add11.rs b/evm_arithmetization/src/cpu/kernel/tests/add11.rs index 1840bbc07..9e44c8bfa 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/add11.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/add11.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "cdk_erigon"))] + use std::collections::HashMap; use std::str::FromStr; @@ -14,9 +16,8 @@ use crate::generation::mpt::{AccountRlp, LegacyReceiptRlp}; use crate::generation::TrieInputs; use crate::proof::{BlockHashes, BlockMetadata, TrieRoots}; use crate::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, - GLOBAL_EXIT_ROOT_ACCOUNT, }; use crate::GenerationInputs; @@ -147,12 +148,6 @@ fn test_add11_yml() { ) .unwrap(); expected_state_trie_after - .insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - ) - .unwrap(); - expected_state_trie_after }; let receipt_0 = LegacyReceiptRlp { status: true, @@ -183,7 +178,7 @@ fn test_add11_yml() { signed_txns: vec![txn.to_vec()], burn_addr: None, withdrawals: vec![], - global_exit_roots: vec![], + ger_data: None, tries: tries_before, trie_roots_after, contract_code: contract_code.clone(), @@ -328,12 +323,6 @@ fn test_add11_yml_with_exception() { ) .unwrap(); expected_state_trie_after - .insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - ) - .unwrap(); - expected_state_trie_after }; let receipt_0 = LegacyReceiptRlp { @@ -365,7 +354,7 @@ fn test_add11_yml_with_exception() { signed_txns: vec![txn.to_vec()], burn_addr: None, withdrawals: vec![], - global_exit_roots: vec![], + ger_data: None, tries: tries_before, trie_roots_after, contract_code: contract_code.clone(), diff --git a/evm_arithmetization/src/cpu/kernel/tests/init_exc_stop.rs b/evm_arithmetization/src/cpu/kernel/tests/init_exc_stop.rs index 7e7e6851d..57ad232cb 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/init_exc_stop.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/init_exc_stop.rs @@ -1,31 +1,24 @@ use std::collections::HashMap; use ethereum_types::U256; -use keccak_hash::keccak; -use keccak_hash::H256; -use mpt_trie::partial_trie::HashedPartialTrie; -use mpt_trie::partial_trie::PartialTrie; +use keccak_hash::{keccak, H256}; +use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie}; use plonky2::field::goldilocks_field::GoldilocksField as F; -use crate::cpu::kernel::aggregator::KERNEL; -use crate::cpu::kernel::interpreter::Interpreter; -use crate::generation::state::State; -use crate::generation::TrieInputs; -use crate::generation::NUM_EXTRA_CYCLES_AFTER; -use crate::generation::NUM_EXTRA_CYCLES_BEFORE; +use crate::cpu::kernel::{aggregator::KERNEL, interpreter::Interpreter}; +use crate::generation::{ + state::State, TrieInputs, NUM_EXTRA_CYCLES_AFTER, NUM_EXTRA_CYCLES_BEFORE, +}; use crate::memory::segments::Segment; -use crate::proof::BlockMetadata; -use crate::proof::TrieRoots; -use crate::testing_utils::beacon_roots_account_nibbles; -use crate::testing_utils::beacon_roots_contract_from_storage; -use crate::testing_utils::ger_account_nibbles; -use crate::testing_utils::init_logger; -use crate::testing_utils::preinitialized_state_and_storage_tries; -use crate::testing_utils::update_beacon_roots_account_storage; -use crate::testing_utils::GLOBAL_EXIT_ROOT_ACCOUNT; -use crate::witness::memory::MemoryAddress; -use crate::witness::state::RegistersState; -use crate::{proof::BlockHashes, GenerationInputs, Node}; +use crate::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, + preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, +}; +use crate::witness::{memory::MemoryAddress, state::RegistersState}; +use crate::{ + proof::{BlockHashes, BlockMetadata, TrieRoots}, + GenerationInputs, Node, +}; enum RegistersIdx { ProgramCounter = 0, @@ -72,12 +65,6 @@ fn test_init_exc_stop() { ) .unwrap(); expected_state_trie_after - .insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - ) - .unwrap(); - expected_state_trie_after }; let mut contract_code = HashMap::new(); @@ -110,7 +97,7 @@ fn test_init_exc_stop() { prev_hashes: vec![H256::default(); 256], cur_hash: H256::default(), }, - global_exit_roots: vec![], + ger_data: None, }; let initial_stack = vec![]; let initial_offset = KERNEL.global_labels["init"]; diff --git a/evm_arithmetization/src/generation/mod.rs b/evm_arithmetization/src/generation/mod.rs index cd3abd0a6..315f85265 100644 --- a/evm_arithmetization/src/generation/mod.rs +++ b/evm_arithmetization/src/generation/mod.rs @@ -76,8 +76,7 @@ pub struct GenerationInputs { /// Withdrawal pairs `(addr, amount)`. At the end of the txs, `amount` is /// added to `addr`'s balance. See EIP-4895. pub withdrawals: Vec<(Address, U256)>, - /// Global exit roots pairs `(timestamp, root)`. - pub global_exit_roots: Vec<(U256, H256)>, + pub tries: TrieInputs, /// Expected trie roots after the transactions are executed. pub trie_roots_after: TrieRoots, @@ -98,6 +97,12 @@ pub struct GenerationInputs { /// The hash of the current block, and a list of the 256 previous block /// hashes. pub block_hashes: BlockHashes, + + /// The global exit root along with the l1blockhash to write to the GER + /// manager. + /// + /// This is specific to `cdk-erigon`. + pub ger_data: Option<(H256, H256)>, } /// A lighter version of [`GenerationInputs`], which have been trimmed diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index c2ab4d688..4a5043d92 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -70,7 +70,7 @@ impl GenerationState { "jumpdest_table" => self.run_jumpdest_table(input_fn), "access_lists" => self.run_access_lists(input_fn), "linked_list" => self.run_linked_list(input_fn), - "ger" => self.run_global_exit_roots(), + "ger" => self.run_global_exit_root(), "kzg_point_eval" => self.run_kzg_point_eval(), "kzg_point_eval_2" => self.run_kzg_point_eval_2(), _ => Err(ProgramError::ProverInputError(InvalidFunction)), @@ -372,7 +372,7 @@ impl GenerationState { } } - fn run_global_exit_roots(&mut self) -> Result { + fn run_global_exit_root(&mut self) -> Result { self.ger_prover_inputs .pop() .ok_or(ProgramError::ProverInputError(OutOfGerData)) diff --git a/evm_arithmetization/src/generation/state.rs b/evm_arithmetization/src/generation/state.rs index a9a7a2913..9eef07f65 100644 --- a/evm_arithmetization/src/generation/state.rs +++ b/evm_arithmetization/src/generation/state.rs @@ -407,7 +407,7 @@ impl GenerationState { pub(crate) fn new(inputs: &GenerationInputs, kernel_code: &[u8]) -> Result { let rlp_prover_inputs = all_rlp_prover_inputs_reversed(&inputs.signed_txns); let withdrawal_prover_inputs = all_withdrawals_prover_inputs_reversed(&inputs.withdrawals); - let ger_prover_inputs = all_ger_prover_inputs_reversed(&inputs.global_exit_roots); + let ger_prover_inputs = all_ger_prover_inputs(inputs.ger_data); let bignum_modmul_result_limbs = Vec::new(); let mut state = Self { @@ -419,7 +419,6 @@ impl GenerationState { stale_contexts: Vec::new(), rlp_prover_inputs, withdrawal_prover_inputs, - ger_prover_inputs, state_key_to_address: HashMap::new(), bignum_modmul_result_limbs, trie_root_ptrs: TrieRootPtrs { @@ -428,6 +427,7 @@ impl GenerationState { receipt_root_ptr: 0, }, jumpdest_table: None, + ger_prover_inputs, }; let trie_root_ptrs = state.preinitialize_linked_lists_and_txn_and_receipt_mpts(&inputs.tries); @@ -745,15 +745,12 @@ pub(crate) fn all_withdrawals_prover_inputs_reversed(withdrawals: &[(Address, U2 withdrawal_prover_inputs } -/// Global exit roots prover input array is of the form `[N, timestamp1, -/// root1,..., timestampN, rootN]`. Returns the reversed array. -pub(crate) fn all_ger_prover_inputs_reversed(global_exit_roots: &[(U256, H256)]) -> Vec { - let mut ger_prover_inputs = vec![global_exit_roots.len().into()]; - ger_prover_inputs.extend( - global_exit_roots - .iter() - .flat_map(|ger| [ger.0, ger.1.into_uint()]), - ); - ger_prover_inputs.reverse(); - ger_prover_inputs +/// Global exit root prover input tuple containing the global exit root and its +/// associated l1blockhash. +pub(crate) fn all_ger_prover_inputs(ger_data: Option<(H256, H256)>) -> Vec { + if ger_data.is_none() { + return vec![U256::MAX]; + } + let (root, l1blockhash) = ger_data.unwrap(); + vec![root.into_uint(), l1blockhash.into_uint()] } diff --git a/evm_arithmetization/src/testing_utils.rs b/evm_arithmetization/src/testing_utils.rs index 4ad47b65f..4152da5b6 100644 --- a/evm_arithmetization/src/testing_utils.rs +++ b/evm_arithmetization/src/testing_utils.rs @@ -11,10 +11,8 @@ use mpt_trie::{ }; pub use crate::cpu::kernel::cancun_constants::*; -pub use crate::cpu::kernel::constants::global_exit_root::{ - GLOBAL_EXIT_ROOT_ACCOUNT, GLOBAL_EXIT_ROOT_ADDRESS_HASHED, GLOBAL_EXIT_ROOT_STORAGE_POS, -}; -use crate::{generation::mpt::AccountRlp, util::h2u}; +pub use crate::cpu::kernel::constants::global_exit_root::*; +use crate::{generation::mpt::AccountRlp, proof::BlockMetadata, util::h2u}; pub const EMPTY_NODE_HASH: H256 = H256(hex!( "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" @@ -90,15 +88,8 @@ pub fn preinitialized_state_and_storage_tries( beacon_roots_account_nibbles(), rlp::encode(&BEACON_ROOTS_ACCOUNT).to_vec(), )?; - state_trie.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - )?; - let storage_tries = vec![ - (BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, Node::Empty.into()), - (H256(GLOBAL_EXIT_ROOT_ADDRESS_HASHED), Node::Empty.into()), - ]; + let storage_tries = vec![(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, Node::Empty.into())]; Ok((state_trie, storage_tries)) } @@ -108,21 +99,53 @@ pub fn beacon_roots_account_nibbles() -> Nibbles { Nibbles::from_bytes_be(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED.as_bytes()).unwrap() } -/// Returns the `Nibbles` corresponding to the beacon roots contract account. +/// Returns the `Nibbles` corresponding to the GER manager account. pub fn ger_account_nibbles() -> Nibbles { - Nibbles::from_bytes_be(&GLOBAL_EXIT_ROOT_ADDRESS_HASHED).unwrap() + Nibbles::from_bytes_be(GLOBAL_EXIT_ROOT_ADDRESS_HASHED.as_bytes()).unwrap() } pub fn update_ger_account_storage( storage_trie: &mut HashedPartialTrie, - root: H256, - timestamp: U256, + ger_data: Option<(H256, H256)>, +) -> anyhow::Result<()> { + if let Some((root, l1blockhash)) = ger_data { + let mut arr = [0; 64]; + arr[0..32].copy_from_slice(&root.0); + U256::from(GLOBAL_EXIT_ROOT_STORAGE_POS.1).to_big_endian(&mut arr[32..64]); + let slot = keccak(arr); + insert_storage(storage_trie, slot.into_uint(), h2u(l1blockhash))? + } + + Ok(()) +} + +/// Returns the `Nibbles` corresponding to the 5ca1ab1e contract account. +pub fn scalable_account_nibbles() -> Nibbles { + Nibbles::from_bytes_be(ADDRESS_SCALABLE_L2_ADDRESS_HASHED.as_bytes()).unwrap() +} + +/// Note: This *will* overwrite the timestamp stored at the contract address. +pub fn update_scalable_account_storage( + storage_trie: &mut HashedPartialTrie, + block: &BlockMetadata, + initial_trie_hash: H256, ) -> anyhow::Result<()> { + insert_storage( + storage_trie, + U256::from(LAST_BLOCK_STORAGE_POS.1), + block.block_number, + )?; + insert_storage( + storage_trie, + U256::from(TIMESTAMP_STORAGE_POS.1), + block.block_timestamp, + )?; + let mut arr = [0; 64]; - arr[0..32].copy_from_slice(&root.0); - U256::from(GLOBAL_EXIT_ROOT_STORAGE_POS.1).to_big_endian(&mut arr[32..64]); + (block.block_number - U256::one()).to_big_endian(&mut arr[0..32]); + U256::from(STATE_ROOT_STORAGE_POS.1).to_big_endian(&mut arr[32..64]); let slot = keccak(arr); - insert_storage(storage_trie, slot.into_uint(), timestamp) + insert_storage(storage_trie, slot.into_uint(), h2u(initial_trie_hash)) } pub fn ger_contract_from_storage(storage_trie: &HashedPartialTrie) -> AccountRlp { @@ -132,6 +155,13 @@ pub fn ger_contract_from_storage(storage_trie: &HashedPartialTrie) -> AccountRlp } } +pub fn scalable_contract_from_storage(storage_trie: &HashedPartialTrie) -> AccountRlp { + AccountRlp { + storage_root: storage_trie.hash(), + ..Default::default() + } +} + /// Converts an amount in `ETH` to `wei` units. pub fn eth_to_wei(eth: U256) -> U256 { // 1 ether = 10^18 wei. diff --git a/evm_arithmetization/tests/add11_yml.rs b/evm_arithmetization/tests/add11_yml.rs index ed5eaaa94..e892d2772 100644 --- a/evm_arithmetization/tests/add11_yml.rs +++ b/evm_arithmetization/tests/add11_yml.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "cdk_erigon"))] + use std::collections::HashMap; use std::str::FromStr; use std::time::Duration; @@ -8,9 +10,8 @@ use evm_arithmetization::generation::TrieInputs; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::testing::prove_all_segments; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, - init_logger, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, - GLOBAL_EXIT_ROOT_ACCOUNT, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, + preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, }; use evm_arithmetization::verifier::testing::verify_all_proofs; use evm_arithmetization::StarkConfig; @@ -152,12 +153,6 @@ fn get_generation_inputs() -> GenerationInputs { ) .unwrap(); expected_state_trie_after - .insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - ) - .unwrap(); - expected_state_trie_after }; let receipt_0 = LegacyReceiptRlp { @@ -189,7 +184,7 @@ fn get_generation_inputs() -> GenerationInputs { signed_txns: vec![txn.to_vec()], burn_addr: None, withdrawals: vec![], - global_exit_roots: vec![], + ger_data: None, tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/erc20.rs b/evm_arithmetization/tests/erc20.rs index b9f3d6cf0..cd16de80f 100644 --- a/evm_arithmetization/tests/erc20.rs +++ b/evm_arithmetization/tests/erc20.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "cdk_erigon"))] + use std::str::FromStr; use std::time::Duration; @@ -8,8 +10,7 @@ use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::testing::prove_all_segments; use evm_arithmetization::testing_utils::{ beacon_roots_account_nibbles, beacon_roots_contract_from_storage, create_account_storage, - ger_account_nibbles, init_logger, preinitialized_state_and_storage_tries, sd2u, - update_beacon_roots_account_storage, GLOBAL_EXIT_ROOT_ACCOUNT, + init_logger, preinitialized_state_and_storage_tries, sd2u, update_beacon_roots_account_storage, }; use evm_arithmetization::verifier::testing::verify_all_proofs; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -132,10 +133,6 @@ fn test_erc20() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), )?; - state_trie_after.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - )?; state_trie_after }; @@ -183,7 +180,7 @@ fn test_erc20() -> anyhow::Result<()> { signed_txns: vec![txn.to_vec()], burn_addr: None, withdrawals: vec![], - global_exit_roots: vec![], + ger_data: None, tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/erc721.rs b/evm_arithmetization/tests/erc721.rs index df3099e1f..851560430 100644 --- a/evm_arithmetization/tests/erc721.rs +++ b/evm_arithmetization/tests/erc721.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "cdk_erigon"))] + use std::str::FromStr; use std::time::Duration; @@ -8,8 +10,8 @@ use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::testing::prove_all_segments; use evm_arithmetization::testing_utils::{ beacon_roots_account_nibbles, beacon_roots_contract_from_storage, create_account_storage, - ger_account_nibbles, init_logger, preinitialized_state_and_storage_tries, sd2u, sh2u, - update_beacon_roots_account_storage, GLOBAL_EXIT_ROOT_ACCOUNT, + init_logger, preinitialized_state_and_storage_tries, sd2u, sh2u, + update_beacon_roots_account_storage, }; use evm_arithmetization::verifier::testing::verify_all_proofs; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -154,10 +156,6 @@ fn test_erc721() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), )?; - state_trie_after.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - )?; state_trie_after }; @@ -186,7 +184,7 @@ fn test_erc721() -> anyhow::Result<()> { signed_txns: vec![txn.to_vec()], burn_addr: None, withdrawals: vec![], - global_exit_roots: vec![], + ger_data: None, tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/global_exit_root.rs b/evm_arithmetization/tests/global_exit_root.rs index 69e45bef4..ba9ac1c02 100644 --- a/evm_arithmetization/tests/global_exit_root.rs +++ b/evm_arithmetization/tests/global_exit_root.rs @@ -1,14 +1,18 @@ +#![cfg(feature = "cdk_erigon")] + use std::collections::HashMap; use std::time::Duration; -use ethereum_types::{H256, U256}; +use ethereum_types::H256; use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::testing::prove_all_segments; use evm_arithmetization::testing_utils::{ beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, ger_contract_from_storage, init_logger, preinitialized_state_and_storage_tries, - update_beacon_roots_account_storage, update_ger_account_storage, + scalable_account_nibbles, scalable_contract_from_storage, update_beacon_roots_account_storage, + update_ger_account_storage, update_scalable_account_storage, + ADDRESS_SCALABLE_L2_ADDRESS_HASHED, GLOBAL_EXIT_ROOT_ACCOUNT, GLOBAL_EXIT_ROOT_ADDRESS_HASHED, }; use evm_arithmetization::verifier::testing::verify_all_proofs; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -17,13 +21,12 @@ use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie}; use plonky2::field::goldilocks_field::GoldilocksField; use plonky2::plonk::config::PoseidonGoldilocksConfig; use plonky2::util::timing::TimingTree; -use rand::random; type F = GoldilocksField; const D: usize = 2; type C = PoseidonGoldilocksConfig; -/// Add a new Global Exit Root to the state trie. +/// Test pre-state execution as performed by cdk-erigon. #[test] fn test_global_exit_root() -> anyhow::Result<()> { init_logger(); @@ -33,19 +36,33 @@ fn test_global_exit_root() -> anyhow::Result<()> { let block_metadata = BlockMetadata { block_timestamp: 1.into(), + block_number: 42.into(), ..BlockMetadata::default() }; - let (state_trie_before, storage_tries) = preinitialized_state_and_storage_tries()?; + let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries()?; + state_trie_before.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + )?; + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); - let mut ger_account_storage = storage_tries[1].1.clone(); + let mut ger_account_storage = HashedPartialTrie::from(Node::Empty); + let mut scalable_account_storage = HashedPartialTrie::from(Node::Empty); + + storage_tries.push((GLOBAL_EXIT_ROOT_ADDRESS_HASHED, ger_account_storage.clone())); + storage_tries.push(( + ADDRESS_SCALABLE_L2_ADDRESS_HASHED, + scalable_account_storage.clone(), + )); + let transactions_trie = HashedPartialTrie::from(Node::Empty); let receipts_trie = HashedPartialTrie::from(Node::Empty); let mut contract_code = HashMap::new(); contract_code.insert(keccak(vec![]), vec![]); - let global_exit_roots = vec![(U256(random()), H256(random()))]; + let ger_data = Some((H256::random(), H256::random())); let state_trie_after = { let mut trie = HashedPartialTrie::from(Node::Empty); @@ -54,18 +71,27 @@ fn test_global_exit_root() -> anyhow::Result<()> { block_metadata.block_timestamp, block_metadata.parent_beacon_block_root, )?; + update_ger_account_storage(&mut ger_account_storage, ger_data)?; + update_scalable_account_storage( + &mut scalable_account_storage, + &block_metadata, + state_trie_before.hash(), + )?; + let beacon_roots_account = beacon_roots_contract_from_storage(&beacon_roots_account_storage); - for &(timestamp, root) in &global_exit_roots { - update_ger_account_storage(&mut ger_account_storage, root, timestamp)?; - } let ger_account = ger_contract_from_storage(&ger_account_storage); + let scalable_account = scalable_contract_from_storage(&scalable_account_storage); trie.insert( beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), )?; trie.insert(ger_account_nibbles(), rlp::encode(&ger_account).to_vec())?; + trie.insert( + scalable_account_nibbles(), + rlp::encode(&scalable_account).to_vec(), + )?; trie }; @@ -80,7 +106,7 @@ fn test_global_exit_root() -> anyhow::Result<()> { signed_txns: vec![], burn_addr: None, withdrawals: vec![], - global_exit_roots, + ger_data, tries: TrieInputs { state_trie: state_trie_before, transactions_trie, diff --git a/evm_arithmetization/tests/log_opcode.rs b/evm_arithmetization/tests/log_opcode.rs index 8d71e0a19..b08ace71c 100644 --- a/evm_arithmetization/tests/log_opcode.rs +++ b/evm_arithmetization/tests/log_opcode.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "cdk_erigon"))] + use std::collections::HashMap; use std::str::FromStr; use std::time::Duration; @@ -12,9 +14,8 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::testing::prove_all_segments; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, - init_logger, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, - GLOBAL_EXIT_ROOT_ACCOUNT, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, + preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, }; use evm_arithmetization::verifier::testing::verify_all_proofs; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -227,10 +228,6 @@ fn test_log_opcodes() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), )?; - expected_state_trie_after.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - )?; let transactions_trie: HashedPartialTrie = Node::Leaf { nibbles: Nibbles::from_str("0x80").unwrap(), @@ -253,7 +250,7 @@ fn test_log_opcodes() -> anyhow::Result<()> { signed_txns: vec![txn.to_vec()], burn_addr, withdrawals: vec![], - global_exit_roots: vec![], + ger_data: None, tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/selfdestruct.rs b/evm_arithmetization/tests/selfdestruct.rs index a4b6aa4f9..0669486bc 100644 --- a/evm_arithmetization/tests/selfdestruct.rs +++ b/evm_arithmetization/tests/selfdestruct.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "cdk_erigon"))] + use std::str::FromStr; use std::time::Duration; @@ -7,9 +9,8 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::testing::prove_all_segments; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, - ger_account_nibbles, init_logger, preinitialized_state_and_storage_tries, - update_beacon_roots_account_storage, GLOBAL_EXIT_ROOT_ACCOUNT, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, init_logger, + preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, }; use evm_arithmetization::verifier::testing::verify_all_proofs; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -122,10 +123,6 @@ fn test_selfdestruct() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), )?; - state_trie_after.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - )?; state_trie_after }; @@ -157,7 +154,7 @@ fn test_selfdestruct() -> anyhow::Result<()> { signed_txns: vec![txn.to_vec()], burn_addr: None, withdrawals: vec![], - global_exit_roots: vec![], + ger_data: None, tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/simple_transfer.rs b/evm_arithmetization/tests/simple_transfer.rs index d497142a4..37d14bf05 100644 --- a/evm_arithmetization/tests/simple_transfer.rs +++ b/evm_arithmetization/tests/simple_transfer.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "cdk_erigon"))] + use std::collections::HashMap; use std::str::FromStr; use std::time::Duration; @@ -8,9 +10,8 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::testing::prove_all_segments; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, - ger_account_nibbles, init_logger, preinitialized_state_and_storage_tries, - update_beacon_roots_account_storage, GLOBAL_EXIT_ROOT_ACCOUNT, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, init_logger, + preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, }; use evm_arithmetization::verifier::testing::verify_all_proofs; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -114,10 +115,6 @@ fn test_simple_transfer() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), )?; - state_trie_after.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - )?; state_trie_after }; @@ -149,7 +146,7 @@ fn test_simple_transfer() -> anyhow::Result<()> { signed_txns: vec![txn.to_vec()], burn_addr: None, withdrawals: vec![], - global_exit_roots: vec![], + ger_data: None, tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/two_to_one_block.rs b/evm_arithmetization/tests/two_to_one_block.rs index 51eaa4ac7..6c107c54d 100644 --- a/evm_arithmetization/tests/two_to_one_block.rs +++ b/evm_arithmetization/tests/two_to_one_block.rs @@ -1,4 +1,5 @@ -use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; +#![cfg(not(feature = "cdk_erigon"))] + use ethereum_types::{Address, BigEndianHash, H256}; use evm_arithmetization::fixed_recursive_verifier::{ extract_block_final_public_values, extract_two_to_one_block_hash, @@ -6,9 +7,8 @@ use evm_arithmetization::fixed_recursive_verifier::{ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockMetadata, FinalPublicValues, PublicValues, TrieRoots}; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, - GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::{AllRecursiveCircuits, AllStark, Node, StarkConfig}; use hex_literal::hex; @@ -23,10 +23,6 @@ type F = GoldilocksField; const D: usize = 2; type C = PoseidonGoldilocksConfig; -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} - /// Get `GenerationInputs` for a dummy payload, where the block has the given /// timestamp. fn dummy_payload(timestamp: u64, is_first_payload: bool) -> anyhow::Result { @@ -78,10 +74,6 @@ fn dummy_payload(timestamp: u64, is_first_payload: bool) -> anyhow::Result anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), )?; - trie.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - )?; trie }; @@ -88,7 +85,7 @@ fn test_withdrawals() -> anyhow::Result<()> { signed_txns: vec![], burn_addr: None, withdrawals, - global_exit_roots: vec![], + ger_data: None, tries: TrieInputs { state_trie: state_trie_before, transactions_trie, diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index 758951b45..b0f909374 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -610,7 +610,7 @@ fn process_txn_info( .collect(), block_metadata: other_data.b_data.b_meta.clone(), block_hashes: other_data.b_data.b_hashes.clone(), - global_exit_roots: vec![], + ger_data: None, }; // After processing a transaction, we update the remaining accumulators From 223a0d992b512a0999ceed0be77ae43267601818 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:55:31 -0400 Subject: [PATCH 15/19] feat: Add feature-gating in prover code based on target network (#598) * WIP * Tweak * Final tweaks and test * Fix * Clippy * Add feature-gating * feat: Feature-gate specific bits of the prover for pos and cdk * Add default eth_mainnet * fix: update CI * Pacify mighty clippy * Use eth_mainnet feature directly in KERNEL * Comment * Add missing fix from feat/feature_gat_chain * Clippy * Missing worker config * Apply suggestion for typed transactions Co-authored-by: Hamy Ratoanina * Misc --- .github/workflows/ci.yml | 4 +- Cargo.toml | 14 +- evm_arithmetization/Cargo.toml | 14 +- .../src/cpu/kernel/aggregator.rs | 19 ++- .../src/cpu/kernel/asm/beacon_roots.asm | 13 +- .../src/cpu/kernel/asm/core/exception.asm | 12 +- .../cpu/kernel/asm/core/precompiles/main.asm | 11 +- .../src/cpu/kernel/asm/core/syscall.asm | 12 +- .../src/cpu/kernel/asm/main.asm | 23 +++- .../src/cpu/kernel/asm/memory/metadata.asm | 86 ++++++------ .../asm/transactions/common_decoding.asm | 124 +++++++++--------- .../cpu/kernel/asm/transactions/router.asm | 18 ++- .../src/cpu/kernel/constants/exc_bitfields.rs | 15 +++ .../src/cpu/kernel/interpreter.rs | 14 +- evm_arithmetization/src/cpu/kernel/parser.rs | 4 +- .../src/cpu/kernel/tests/bls381.rs | 27 ---- .../src/cpu/kernel/tests/mod.rs | 3 + .../kernel/tests/transaction_parsing/mod.rs | 1 + .../src/fixed_recursive_verifier.rs | 86 ++++++------ evm_arithmetization/src/generation/mod.rs | 14 +- evm_arithmetization/src/get_challenges.rs | 26 ++-- evm_arithmetization/src/lib.rs | 11 ++ evm_arithmetization/src/proof.rs | 52 +++----- evm_arithmetization/src/prover.rs | 26 +++- evm_arithmetization/src/recursive_verifier.rs | 64 +++++---- evm_arithmetization/src/verifier.rs | 13 ++ evm_arithmetization/src/witness/transition.rs | 2 + evm_arithmetization/tests/add11_yml.rs | 2 +- evm_arithmetization/tests/erc20.rs | 2 +- evm_arithmetization/tests/erc721.rs | 2 +- evm_arithmetization/tests/global_exit_root.rs | 21 +-- evm_arithmetization/tests/log_opcode.rs | 2 +- evm_arithmetization/tests/selfdestruct.rs | 2 +- evm_arithmetization/tests/simple_transfer.rs | 2 +- evm_arithmetization/tests/two_to_one_block.rs | 33 ++--- evm_arithmetization/tests/withdrawals.rs | 2 +- proof_gen/Cargo.toml | 9 +- trace_decoder/Cargo.toml | 20 ++- zero_bin/common/Cargo.toml | 18 ++- zero_bin/leader/Cargo.toml | 15 ++- zero_bin/ops/Cargo.toml | 23 +++- zero_bin/prover/Cargo.toml | 24 +++- zero_bin/rpc/Cargo.toml | 22 +++- zero_bin/verifier/Cargo.toml | 16 ++- zero_bin/worker/Cargo.toml | 11 ++ 45 files changed, 551 insertions(+), 383 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43c0c1254..6f9d24ee7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -237,10 +237,10 @@ jobs: run: cargo fmt --all --check - name: Run cargo clippy - run: cargo clippy --all-features --all-targets -- -D warnings -A incomplete-features + run: cargo clippy --all-targets -- -D warnings -A incomplete-features - name: Run cargo clippy (with `cdk_erigon` flag) - run: cargo clippy --all-features --all-targets --features cdk_erigon -- -D warnings -A incomplete-features + run: cargo clippy --all-targets --no-default-features --features cdk_erigon -- -D warnings -A incomplete-features - name: Rustdoc run: cargo doc --all diff --git a/Cargo.toml b/Cargo.toml index 008257556..9628a345c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,19 +111,19 @@ vergen = { version = "9.0.0", features = ["build", "rustc"] } winnow = "0.6.13" # local dependencies -evm_arithmetization = { path = "evm_arithmetization", version = "0.4.0" } +evm_arithmetization = { path = "evm_arithmetization", version = "0.4.0", default-features = false } mpt_trie = { path = "mpt_trie", version = "0.4.1" } -proof_gen = { path = "proof_gen", version = "0.4.0" } +proof_gen = { path = "proof_gen", version = "0.4.0", default-features = false } smt_trie = { path = "smt_trie", version = "0.1.1" } -trace_decoder = { path = "trace_decoder", version = "0.6.0" } +trace_decoder = { path = "trace_decoder", version = "0.6.0", default-features = false } zk_evm_common = { path = "common", version = "0.1.0" } zk_evm_proc_macro = { path = "proc_macro", version = "0.1.0" } # zero-bin related dependencies -ops = { path = "zero_bin/ops" } -prover = { path = "zero_bin/prover" } -rpc = { path = "zero_bin/rpc" } -zero_bin_common = { path = "zero_bin/common" } +ops = { path = "zero_bin/ops", default-features = false } +prover = { path = "zero_bin/prover", default-features = false } +rpc = { path = "zero_bin/rpc", default-features = false } +zero_bin_common = { path = "zero_bin/common", default-features = false } # plonky2-related dependencies plonky2 = { git = "https://github.com/0xPolygonZero/plonky2.git", rev = "dc77c77f2b06500e16ad4d7f1c2b057903602eed" } diff --git a/evm_arithmetization/Cargo.toml b/evm_arithmetization/Cargo.toml index 96d435bc8..a2b38cc7e 100644 --- a/evm_arithmetization/Cargo.toml +++ b/evm_arithmetization/Cargo.toml @@ -24,15 +24,15 @@ hex-literal = { workspace = true } itertools = { workspace = true } keccak-hash = { workspace = true } log = { workspace = true } -plonky2_maybe_rayon = { workspace = true } +plonky2_maybe_rayon = { workspace = true, features = ["parallel"] } num = { workspace = true } num-bigint = { workspace = true } once_cell = { workspace = true } pest = { workspace = true } pest_derive = { workspace = true } -plonky2 = { workspace = true } +plonky2 = { workspace = true, features = ["parallel"] } plonky2_util = { workspace = true } -starky = { workspace = true } +starky = { workspace = true, features = ["parallel"] } rand = { workspace = true } rand_chacha = { workspace = true } rlp = { workspace = true } @@ -57,15 +57,11 @@ hex = { workspace = true } ripemd = { workspace = true } [features] -default = ["parallel"] +default = ["eth_mainnet"] asmtools = ["hex"] -parallel = [ - "plonky2/parallel", - "plonky2_maybe_rayon/parallel", - "starky/parallel", -] polygon_pos = [] cdk_erigon = ["smt_trie"] +eth_mainnet = [] [[bin]] name = "assemble" diff --git a/evm_arithmetization/src/cpu/kernel/aggregator.rs b/evm_arithmetization/src/cpu/kernel/aggregator.rs index 27054ac37..4ac839659 100644 --- a/evm_arithmetization/src/cpu/kernel/aggregator.rs +++ b/evm_arithmetization/src/cpu/kernel/aggregator.rs @@ -9,10 +9,15 @@ use super::assembler::{assemble, Kernel}; use crate::cpu::kernel::constants::evm_constants; use crate::cpu::kernel::parser::parse; -pub const NUMBER_KERNEL_FILES: usize = if cfg!(feature = "cdk_erigon") { - 159 +pub const NUMBER_KERNEL_FILES: usize = if cfg!(feature = "eth_mainnet") { + 157 +} else if cfg!(feature = "cdk_erigon") { + 156 +} else if cfg!(feature = "polygon_pos") { + 155 } else { - 158 + // unreachable + 0 }; pub static KERNEL_FILES: [&str; NUMBER_KERNEL_FILES] = [ @@ -60,8 +65,9 @@ pub static KERNEL_FILES: [&str; NUMBER_KERNEL_FILES] = [ include_str!("asm/core/precompiles/bn_mul.asm"), include_str!("asm/core/precompiles/snarkv.asm"), include_str!("asm/core/precompiles/blake2_f.asm"), + #[cfg(feature = "eth_mainnet")] include_str!("asm/core/precompiles/kzg_peval.asm"), - include_str!("asm/curve/bls381/util.asm"), + // include_str!("asm/curve/bls381/util.asm"), include_str!("asm/curve/bn254/curve_arithmetic/constants.asm"), include_str!("asm/curve/bn254/curve_arithmetic/curve_add.asm"), include_str!("asm/curve/bn254/curve_arithmetic/curve_mul.asm"), @@ -168,6 +174,7 @@ pub static KERNEL_FILES: [&str; NUMBER_KERNEL_FILES] = [ include_str!("asm/transactions/type_0.asm"), include_str!("asm/transactions/type_1.asm"), include_str!("asm/transactions/type_2.asm"), + #[cfg(feature = "eth_mainnet")] include_str!("asm/transactions/type_3.asm"), include_str!("asm/util/assertions.asm"), include_str!("asm/util/basic_macros.asm"), @@ -186,6 +193,10 @@ pub(crate) fn combined_kernel_from_files(files: [&str; N]) -> Ke let mut active_features = HashSet::new(); if cfg!(feature = "cdk_erigon") { active_features.insert("cdk_erigon"); + } else if cfg!(feature = "polygon_pos") { + active_features.insert("polygon_pos"); + } else { + active_features.insert("eth_mainnet"); } let parsed_files = files diff --git a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm index 36f491fb5..17ade9e86 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm @@ -3,16 +3,11 @@ /// /// *NOTE*: This will panic if one of the provided timestamps is zero. +/// Pre-stack: (empty) +/// Post-stack: (empty) global set_beacon_root: - #[cfg(feature = cdk_erigon)] - { - PUSH pre_block_execution - } - #[cfg(not(feature = cdk_erigon))] - { - PUSH txn_loop - } - + // stack: (empty) + PUSH txn_loop %timestamp // stack: timestamp, retdest PUSH @HISTORY_BUFFER_LENGTH diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm b/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm index a2a2742ec..925921097 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm @@ -327,8 +327,16 @@ min_stack_len_for_opcode: BYTES 0 // 0x46, CHAINID BYTES 0 // 0x47, SELFBALANCE BYTES 0 // 0x48, BASEFEE - BYTES 1 // 0x49, BLOBHASH - BYTES 0 // 0x4a, BLOBBASEFEE + #[cfg(feature = eth_mainnet)] + { + BYTES 1 // 0x49, BLOBHASH + BYTES 0 // 0x4a, BLOBBASEFEE + } + #[cfg(not(feature = eth_mainnet))] + { + BYTES 0 // 0x49, BLOBHASH is only active on Ethereum mainnet + BYTES 0 // 0x4a, BLOBBASEFEE is only active on Ethereum mainnet + } %rep 5 // 0x4b-0x4f, invalid BYTES 0 %endrep diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm index 0a1883491..4bb924f87 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm @@ -18,8 +18,15 @@ global handle_precompiles: DUP1 %eq_const(@BN_ADD) %jumpi(precompile_bn_add) DUP1 %eq_const(@BN_MUL) %jumpi(precompile_bn_mul) DUP1 %eq_const(@SNARKV) %jumpi(precompile_snarkv) - DUP1 %eq_const(@BLAKE2_F) %jumpi(precompile_blake2_f) - %eq_const(@KZG_PEVAL) %jumpi(precompile_kzg_peval) + #[cfg(feature = eth_mainnet)] + { + DUP1 %eq_const(@BLAKE2_F) %jumpi(precompile_blake2_f) + %eq_const(@KZG_PEVAL) %jumpi(precompile_kzg_peval) + } + #[cfg(not(feature = eth_mainnet))] + { + %eq_const(@BLAKE2_F) %jumpi(precompile_blake2_f) + } // stack: retdest JUMP diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm b/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm index 87c01dc7f..7677654e2 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm @@ -69,8 +69,16 @@ global syscall_jumptable: JUMPTABLE sys_chainid JUMPTABLE sys_selfbalance JUMPTABLE sys_basefee - JUMPTABLE sys_blobhash - JUMPTABLE sys_blobbasefee + #[cfg(feature = eth_mainnet)] + { + JUMPTABLE sys_blobhash + JUMPTABLE sys_blobbasefee + } + #[cfg(not(feature = eth_mainnet))] + { + JUMPTABLE panic // BLOBHASH is only active on Ethereum mainnet + JUMPTABLE panic // BLOBBASEFEE is only active on Ethereum mainnet + } %rep 5 JUMPTABLE panic // 0x4b-0x4f are invalid opcodes %endrep diff --git a/evm_arithmetization/src/cpu/kernel/asm/main.asm b/evm_arithmetization/src/cpu/kernel/asm/main.asm index 58e969ee2..8d2b09c66 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/main.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/main.asm @@ -127,10 +127,20 @@ global start_txns: %mload_global_metadata(@GLOBAL_METADATA_BLOCK_GAS_USED_BEFORE) // stack: init_gas_used, txn_counter, num_nibbles, txn_nb - // If txn_idx == 0, update the beacon_root and exit roots. - %mload_global_metadata(@GLOBAL_METADATA_TXN_NUMBER_BEFORE) - ISZERO - %jumpi(set_beacon_root) + #[cfg(feature = eth_mainnet)] + { + // If txn_idx == 0, update the beacon_root for Ethereum mainnet. + %mload_global_metadata(@GLOBAL_METADATA_TXN_NUMBER_BEFORE) + ISZERO + %jumpi(set_beacon_root) + } + #[cfg(feature = cdk_erigon)] + { + // If txn_idx == 0, perform pre-state execution for CDK erigon. + %mload_global_metadata(@GLOBAL_METADATA_TXN_NUMBER_BEFORE) + ISZERO + %jumpi(pre_block_execution) + } // stack: init_gas_used, txn_counter, num_nibbles, txn_nb global txn_loop: @@ -255,5 +265,8 @@ global check_final_state_trie: PUSH 0 %mstore_txn_field(@TXN_FIELD_CHAIN_ID_PRESENT) PUSH 0 %mstore_txn_field(@TXN_FIELD_TO) - %reset_blob_versioned_hashes + #[cfg(feature = eth_mainnet)] + { + %reset_blob_versioned_hashes + } %endmacro diff --git a/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm b/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm index 1747d6692..5e94794f6 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm @@ -277,47 +277,51 @@ global sys_basefee: SWAP1 EXIT_KERNEL -global sys_blobhash: - // stack: kexit_info, index - %charge_gas_const(@GAS_HASH_OPCODE) - // stack: kexit_info, index - %blobhash - // stack: blobhash, kexit_info - SWAP1 - EXIT_KERNEL - -%macro blobhash - // stack: kexit_info, index - SWAP1 - // stack: index, kexit_info - %mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_LEN) - DUP2 - LT ISZERO // == GE - // stack: index >= len, index, kexit_info - %jumpi(%%index_too_big) - PUSH @SEGMENT_TXN_BLOB_VERSIONED_HASHES - %build_kernel_address - // stack: read_addr, kexit_info - MLOAD_GENERAL - %jump(%%end) -%%index_too_big: - // The index is larger than the list, just push 0. - // stack: index, kexit_info - POP - PUSH 0 - // stack: 0, kexit_info -%%end: - // stack: blobhash, kexit_info -%endmacro - -global sys_blobbasefee: - // stack: kexit_info - %charge_gas_const(@GAS_BASE) - // stack: kexit_info - PROVER_INPUT(blobbasefee) - // stack: blobbasefee, kexit_info - SWAP1 - EXIT_KERNEL +/// Blob-related macros are only available for Ethereum mainnet. +#[cfg(feature = eth_mainnet)] +{ + global sys_blobhash: + // stack: kexit_info, index + %charge_gas_const(@GAS_HASH_OPCODE) + // stack: kexit_info, index + %blobhash + // stack: blobhash, kexit_info + SWAP1 + EXIT_KERNEL + + %macro blobhash + // stack: kexit_info, index + SWAP1 + // stack: index, kexit_info + %mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_LEN) + DUP2 + LT ISZERO // == GE + // stack: index >= len, index, kexit_info + %jumpi(%%index_too_big) + PUSH @SEGMENT_TXN_BLOB_VERSIONED_HASHES + %build_kernel_address + // stack: read_addr, kexit_info + MLOAD_GENERAL + %jump(%%end) + %%index_too_big: + // The index is larger than the list, just push 0. + // stack: index, kexit_info + POP + PUSH 0 + // stack: 0, kexit_info + %%end: + // stack: blobhash, kexit_info + %endmacro + + global sys_blobbasefee: + // stack: kexit_info + %charge_gas_const(@GAS_BASE) + // stack: kexit_info + PROVER_INPUT(blobbasefee) + // stack: blobbasefee, kexit_info + SWAP1 + EXIT_KERNEL +} global sys_blockhash: // stack: kexit_info, block_number diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm index 0621db533..6ee92ca2b 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm @@ -127,66 +127,6 @@ %%after: %endmacro -%macro decode_and_store_max_fee_per_blob_gas - // stack: rlp_addr - %decode_rlp_scalar - %stack (rlp_addr, max_fee_per_blob_gas) -> (max_fee_per_blob_gas, rlp_addr) - %mstore_txn_field(@TXN_FIELD_MAX_FEE_PER_BLOB_GAS) - // stack: rlp_addr -%endmacro - -%macro decode_and_store_blob_versioned_hashes - // stack: rlp_addr - %decode_rlp_list_len - %stack (rlp_addr, len) -> (len, len, rlp_addr, %%after) - - // EIP-4844: Blob transactions should have at least 1 versioned hash - %assert_nonzero(invalid_txn_2) - - // stack: len, rlp_addr, %%after - %jump(decode_and_store_blob_versioned_hashes) -%%after: -%endmacro - -// The blob versioned hashes are just a list of hashes. -global decode_and_store_blob_versioned_hashes: - // stack: len, rlp_addr - // Store the list length - DUP1 %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_LEN) - - // stack: len, rlp_addr - DUP2 ADD - // stack: end_rlp_addr, rlp_addr - // stack: end_rlp_addr, rlp_addr - PUSH @SEGMENT_TXN_BLOB_VERSIONED_HASHES // initial address to write to - SWAP2 -decode_and_store_blob_versioned_hashes_loop: - // stack: rlp_addr, end_rlp_addr, store_addr - DUP2 DUP2 EQ %jumpi(decode_and_store_blob_versioned_hashes_finish) - // stack: rlp_addr, end_rlp_addr, store_addr - %decode_rlp_scalar // blob_versioned_hashes[i] - // stack: rlp_addr, hash, end_rlp_addr, store_addr - - // EIP-4844: Versioned hashes should have `VERSIONED_HASH_VERSION_KZG` as MSB - DUP2 - %shr_const(248) - // stack: MSB, hash, end_rlp_addr, store_addr - %eq_const(1) - // stack: hash_is_valid?, rlp_addr, hash, end_rlp_addr, store_addr - %assert_nonzero(invalid_txn_3) - - // stack: rlp_addr, hash, end_rlp_addr, store_addr - SWAP3 DUP1 SWAP2 - // stack: hash, store_addr, store_addr, end_rlp_addr, rlp_addr - MSTORE_GENERAL - // stack: store_addr, end_rlp_addr, rlp_addr - %increment SWAP2 - // stack: rlp_addr, end_rlp_addr, store_addr' - %jump(decode_and_store_blob_versioned_hashes_loop) -decode_and_store_blob_versioned_hashes_finish: - %stack (rlp_addr, end_rlp_addr, store_addr, retdest) -> (retdest, rlp_addr) - JUMP - %macro decode_and_store_y_parity // stack: rlp_addr %decode_rlp_scalar @@ -303,3 +243,67 @@ sload_with_addr: // stack: value, retdest SWAP1 JUMP + +/// Type-3 transactions specific decoding helper macros. +#[cfg(feature = eth_mainnet)] +{ + %macro decode_and_store_max_fee_per_blob_gas + // stack: rlp_addr + %decode_rlp_scalar + %stack (rlp_addr, max_fee_per_blob_gas) -> (max_fee_per_blob_gas, rlp_addr) + %mstore_txn_field(@TXN_FIELD_MAX_FEE_PER_BLOB_GAS) + // stack: rlp_addr + %endmacro + + %macro decode_and_store_blob_versioned_hashes + // stack: rlp_addr + %decode_rlp_list_len + %stack (rlp_addr, len) -> (len, len, rlp_addr, %%after) + + // EIP-4844: Blob transactions should have at least 1 versioned hash + %assert_nonzero(invalid_txn_2) + + // stack: len, rlp_addr, %%after + %jump(decode_and_store_blob_versioned_hashes) + %%after: + %endmacro + + // The blob versioned hashes are just a list of hashes. + global decode_and_store_blob_versioned_hashes: + // stack: len, rlp_addr + // Store the list length + DUP1 %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_LEN) + + // stack: len, rlp_addr + DUP2 ADD + // stack: end_rlp_addr, rlp_addr + // stack: end_rlp_addr, rlp_addr + PUSH @SEGMENT_TXN_BLOB_VERSIONED_HASHES // initial address to write to + SWAP2 + decode_and_store_blob_versioned_hashes_loop: + // stack: rlp_addr, end_rlp_addr, store_addr + DUP2 DUP2 EQ %jumpi(decode_and_store_blob_versioned_hashes_finish) + // stack: rlp_addr, end_rlp_addr, store_addr + %decode_rlp_scalar // blob_versioned_hashes[i] + // stack: rlp_addr, hash, end_rlp_addr, store_addr + + // EIP-4844: Versioned hashes should have `VERSIONED_HASH_VERSION_KZG` as MSB + DUP2 + %shr_const(248) + // stack: MSB, hash, end_rlp_addr, store_addr + %eq_const(1) + // stack: hash_is_valid?, rlp_addr, hash, end_rlp_addr, store_addr + %assert_nonzero(invalid_txn_3) + + // stack: rlp_addr, hash, end_rlp_addr, store_addr + SWAP3 DUP1 SWAP2 + // stack: hash, store_addr, store_addr, end_rlp_addr, rlp_addr + MSTORE_GENERAL + // stack: store_addr, end_rlp_addr, rlp_addr + %increment SWAP2 + // stack: rlp_addr, end_rlp_addr, store_addr' + %jump(decode_and_store_blob_versioned_hashes_loop) + decode_and_store_blob_versioned_hashes_finish: + %stack (rlp_addr, end_rlp_addr, store_addr, retdest) -> (retdest, rlp_addr) + JUMP +} \ No newline at end of file diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/router.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/router.asm index e7c7f88ee..eaef7efd7 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/router.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/router.asm @@ -33,14 +33,18 @@ read_txn_from_memory: %jumpi(process_type_2_txn) // stack: rlp_start_addr, retdest - DUP1 - MLOAD_GENERAL - %eq_const(3) - // stack: first_byte == 3, rlp_start_addr, retdest - %jumpi(process_type_3_txn) - // stack: rlp_start_addr, retdest + // Only Ethereum mainnet supports Blob-transactions. + #[cfg(feature = eth_mainnet)] + { + DUP1 + MLOAD_GENERAL + %eq_const(3) + // stack: first_byte == 3, rlp_start_addr, retdest + %jumpi(process_type_3_txn) + // stack: rlp_start_addr, retdest + } - // At this point, since it's not a type 1, 2 or 3 transaction, + // At this point, since it's not a typed transaction, // it must be a legacy (aka type 0) transaction. %jump(process_type_0_txn) diff --git a/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs b/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs index 0abc84e70..5ed5972c1 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs @@ -28,6 +28,7 @@ const fn u256_from_set_index_ranges(ranges: &[RangeInclusive U256(res_limbs) } +#[cfg(feature = "eth_mainnet")] pub(crate) const STACK_LENGTH_INCREASING_OPCODES_USER: U256 = u256_from_set_index_ranges(&[ 0x30..=0x30, // ADDRESS 0x32..=0x34, // ORIGIN, CALLER, CALLVALUE @@ -42,6 +43,20 @@ pub(crate) const STACK_LENGTH_INCREASING_OPCODES_USER: U256 = u256_from_set_inde 0x5f..=0x8f, // PUSH*, DUP* ]); +#[cfg(not(feature = "eth_mainnet"))] +pub(crate) const STACK_LENGTH_INCREASING_OPCODES_USER: U256 = u256_from_set_index_ranges(&[ + 0x30..=0x30, // ADDRESS + 0x32..=0x34, // ORIGIN, CALLER, CALLVALUE + 0x36..=0x36, // CALLDATASIZE + 0x38..=0x38, // CODESIZE + 0x3a..=0x3a, // GASPRICE + 0x3d..=0x3d, // RETURNDATASIZE + 0x41..=0x48, /* COINBASE, TIMESTAMP, NUMBER, DIFFICULTY, GASLIMIT, CHAINID, SELFBALANCE, + * BASEFEE */ + 0x58..=0x5a, // PC, MSIZE, GAS + 0x5f..=0x8f, // PUSH*, DUP* +]); + pub(crate) const INVALID_OPCODES_USER: U256 = u256_from_set_index_ranges(&[ 0x0c..=0x0f, 0x1e..=0x1f, diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index 3d44d8040..ec54b308d 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -263,10 +263,6 @@ impl Interpreter { // Set `GlobalMetadata` values. let metadata = &inputs.block_metadata; - #[cfg(feature = "cdk_erigon")] - let burn_addr = inputs - .burn_addr - .map_or_else(U256::max_value, |addr| U256::from_big_endian(&addr.0)); let global_metadata_to_set = [ ( GlobalMetadata::BlockBeneficiary, @@ -287,14 +283,17 @@ impl Interpreter { h2u(inputs.block_hashes.cur_hash), ), (GlobalMetadata::BlockGasUsed, metadata.block_gas_used), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::BlockBlobGasUsed, metadata.block_blob_gas_used, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::BlockExcessBlobGas, metadata.block_excess_blob_gas, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::ParentBeaconBlockRoot, h2u(metadata.parent_beacon_block_root), @@ -333,7 +332,12 @@ impl Interpreter { (GlobalMetadata::KernelHash, h2u(KERNEL.code_hash)), (GlobalMetadata::KernelLen, KERNEL.code.len().into()), #[cfg(feature = "cdk_erigon")] - (GlobalMetadata::BurnAddr, burn_addr), + ( + GlobalMetadata::BurnAddr, + inputs + .burn_addr + .map_or_else(U256::max_value, |addr| U256::from_big_endian(&addr.0)), + ), ]; self.set_global_metadata_multi_fields(&global_metadata_to_set); diff --git a/evm_arithmetization/src/cpu/kernel/parser.rs b/evm_arithmetization/src/cpu/kernel/parser.rs index 4cbef3c81..be80ae5d4 100644 --- a/evm_arithmetization/src/cpu/kernel/parser.rs +++ b/evm_arithmetization/src/cpu/kernel/parser.rs @@ -91,7 +91,9 @@ fn parse_conditional_block(item: Pair, active_features: &HashSet<&str>) -> features_string: &str, group_rule: FeatureGroupRule, ) -> bool { - let features = features_string.split(","); + let features = features_string + .split(&[',', ' ']) // allows for both `foo,bar` and `foo, bar` in ASM + .filter(|s| !s.is_empty()); match group_rule { FeatureGroupRule::Not => { diff --git a/evm_arithmetization/src/cpu/kernel/tests/bls381.rs b/evm_arithmetization/src/cpu/kernel/tests/bls381.rs index 40a28ac5b..f61229796 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/bls381.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/bls381.rs @@ -2,40 +2,13 @@ use anyhow::Result; use ethereum_types::U256; use hex_literal::hex; use plonky2::field::goldilocks_field::GoldilocksField as F; -use rand::Rng; -use super::{run_interpreter_with_memory, InterpreterMemoryInitialization}; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::cancun_constants::POINT_EVALUATION_PRECOMPILE_RETURN_VALUE; use crate::cpu::kernel::constants::cancun_constants::KZG_VERSIONED_HASH; use crate::cpu::kernel::interpreter::Interpreter; -use crate::extension_tower::{Fp2, Stack, BLS381}; -use crate::memory::segments::Segment::KernelGeneral; use crate::util::sha2; -#[test] -fn test_bls_fp2_mul() -> Result<()> { - let mut rng = rand::thread_rng(); - let x: Fp2 = rng.gen::>(); - let y: Fp2 = rng.gen::>(); - - let mut stack = x.to_stack().to_vec(); - stack.extend(y.to_stack().to_vec()); - stack.push(U256::from(0xdeadbeefu32)); - let setup = InterpreterMemoryInitialization { - label: "mul_fp381_2".to_string(), - stack, - segment: KernelGeneral, - memory: vec![], - }; - let interpreter = run_interpreter_with_memory::(setup).unwrap(); - let stack: Vec = interpreter.stack().iter().rev().cloned().collect(); - let output = Fp2::::from_stack(&stack); - - assert_eq!(output, x * y); - Ok(()) -} - /// A KZG point evaluation precompile payload consists in: /// - a G1 compressed point commitment (48 bytes) /// - a Scalar element z (32 bytes) diff --git a/evm_arithmetization/src/cpu/kernel/tests/mod.rs b/evm_arithmetization/src/cpu/kernel/tests/mod.rs index 53f65e9f8..39810148b 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mod.rs @@ -1,10 +1,13 @@ mod account_code; +#[cfg(feature = "eth_mainnet")] mod add11; mod balance; mod bignum; mod blake2_f; +#[cfg(feature = "eth_mainnet")] mod blobhash; mod block_hash; +#[cfg(feature = "eth_mainnet")] mod bls381; mod bn254; mod core; diff --git a/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs b/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs index c2ee22f4f..1f70a3a1e 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs @@ -9,6 +9,7 @@ use crate::{ mod parse_type_0_txn; mod parse_type_1_txn; mod parse_type_2_txn; +#[cfg(feature = "eth_mainnet")] mod parse_type_3_txn; pub(crate) fn prepare_interpreter_for_txn_parsing( diff --git a/evm_arithmetization/src/fixed_recursive_verifier.rs b/evm_arithmetization/src/fixed_recursive_verifier.rs index 8746d705a..be9a3daeb 100644 --- a/evm_arithmetization/src/fixed_recursive_verifier.rs +++ b/evm_arithmetization/src/fixed_recursive_verifier.rs @@ -46,7 +46,7 @@ use crate::proof::{ PublicValuesTarget, RegistersDataTarget, TrieRoots, TrieRootsTarget, DEFAULT_CAP_LEN, TARGET_HASH_SIZE, }; -use crate::prover::{check_abort_signal, prove}; +use crate::prover::{check_abort_signal, features_check, prove}; use crate::recursive_verifier::{ add_common_recursion_gates, add_virtual_final_public_values_public_input, add_virtual_public_values_public_input, get_memory_extra_looking_sum_circuit, @@ -54,8 +54,6 @@ use crate::recursive_verifier::{ PlonkWrapperCircuit, PublicInputs, StarkWrapperCircuit, }; use crate::util::h256_limbs; -#[cfg(feature = "cdk_erigon")] -use crate::util::u256_limbs; use crate::verifier::initial_memory_merkle_cap; /// The recursion threshold. We end a chain of recursive proofs once we reach @@ -1044,18 +1042,21 @@ where ); // Connect the burn address targets. - BurnAddrTarget::conditional_assert_eq( - &mut builder, - is_not_dummy, - lhs_pv.burn_addr, - rhs_pv.burn_addr.clone(), - ); - BurnAddrTarget::conditional_assert_eq( - &mut builder, - is_not_dummy, - public_values.burn_addr.clone(), - rhs_pv.burn_addr, - ); + #[cfg(feature = "cdk_erigon")] + { + BurnAddrTarget::conditional_assert_eq( + &mut builder, + is_not_dummy, + lhs_pv.burn_addr, + rhs_pv.burn_addr.clone(), + ); + BurnAddrTarget::conditional_assert_eq( + &mut builder, + is_not_dummy, + public_values.burn_addr.clone(), + rhs_pv.burn_addr, + ); + } BlockMetadataTarget::conditional_assert_eq( &mut builder, @@ -1194,16 +1195,19 @@ where ); // Connect the burn address targets. - BurnAddrTarget::connect( - &mut builder, - lhs_pv.burn_addr.clone(), - rhs_pv.burn_addr.clone(), - ); - BurnAddrTarget::connect( - &mut builder, - public_values.burn_addr.clone(), - rhs_pv.burn_addr.clone(), - ); + #[cfg(feature = "cdk_erigon")] + { + BurnAddrTarget::connect( + &mut builder, + lhs_pv.burn_addr.clone(), + rhs_pv.burn_addr.clone(), + ); + BurnAddrTarget::connect( + &mut builder, + public_values.burn_addr.clone(), + rhs_pv.burn_addr.clone(), + ); + } Self::connect_extra_public_values( &mut builder, @@ -1378,16 +1382,19 @@ where ); // Connect the burn address targets. - BurnAddrTarget::connect( - &mut builder, - parent_pv.burn_addr.clone(), - agg_pv.burn_addr.clone(), - ); - BurnAddrTarget::connect( - &mut builder, - public_values.burn_addr.clone(), - agg_pv.burn_addr.clone(), - ); + #[cfg(feature = "cdk_erigon")] + { + BurnAddrTarget::connect( + &mut builder, + parent_pv.burn_addr.clone(), + agg_pv.burn_addr.clone(), + ); + BurnAddrTarget::connect( + &mut builder, + public_values.burn_addr.clone(), + agg_pv.burn_addr.clone(), + ); + } // Make connections between block proofs, and check initial and final block // values. @@ -1816,9 +1823,8 @@ where timing: &mut TimingTree, abort_signal: Option>, ) -> anyhow::Result> { - if generation_inputs.burn_addr.is_some() && !cfg!(feature = "cdk_erigon") { - log::warn!("The burn address in the GenerationInputs will be ignored, as the `cdk_erigon` feature is not activated.") - } + features_check(&generation_inputs); + let all_proof = prove::( all_stark, config, @@ -1892,6 +1898,8 @@ where timing: &mut TimingTree, abort_signal: Option>, ) -> anyhow::Result>> { + features_check(&generation_inputs.clone().trim()); + let segment_iterator = SegmentDataIterator::::new(&generation_inputs, Some(max_cpu_len_log)); @@ -2347,7 +2355,7 @@ where { let burn_addr_keys = TrieRootsTarget::SIZE * 2..TrieRootsTarget::SIZE * 2 + burn_addr_offset; - for (key, &value) in burn_addr_keys.zip_eq(&u256_limbs( + for (key, &value) in burn_addr_keys.zip_eq(&crate::util::u256_limbs( public_values .burn_addr .expect("We should have a burn addr when cdk_erigon is activated"), diff --git a/evm_arithmetization/src/generation/mod.rs b/evm_arithmetization/src/generation/mod.rs index 315f85265..f30e1e5de 100644 --- a/evm_arithmetization/src/generation/mod.rs +++ b/evm_arithmetization/src/generation/mod.rs @@ -234,10 +234,6 @@ fn apply_metadata_and_tries_memops, const D: usize> ) { let metadata = &inputs.block_metadata; let trie_roots_after = &inputs.trie_roots_after; - #[cfg(feature = "cdk_erigon")] - let burn_addr = inputs - .burn_addr - .map_or_else(U256::max_value, |addr| U256::from_big_endian(&addr.0)); let fields = [ ( GlobalMetadata::BlockBeneficiary, @@ -258,14 +254,17 @@ fn apply_metadata_and_tries_memops, const D: usize> h2u(inputs.block_hashes.cur_hash), ), (GlobalMetadata::BlockGasUsed, metadata.block_gas_used), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::BlockBlobGasUsed, metadata.block_blob_gas_used, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::BlockExcessBlobGas, metadata.block_excess_blob_gas, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::ParentBeaconBlockRoot, h2u(metadata.parent_beacon_block_root), @@ -304,7 +303,12 @@ fn apply_metadata_and_tries_memops, const D: usize> (GlobalMetadata::KernelHash, h2u(KERNEL.code_hash)), (GlobalMetadata::KernelLen, KERNEL.code.len().into()), #[cfg(feature = "cdk_erigon")] - (GlobalMetadata::BurnAddr, burn_addr), + ( + GlobalMetadata::BurnAddr, + inputs + .burn_addr + .map_or_else(U256::max_value, |addr| U256::from_big_endian(&addr.0)), + ), ]; let channel = MemoryChannel::GeneralPurpose(0); diff --git a/evm_arithmetization/src/get_challenges.rs b/evm_arithmetization/src/get_challenges.rs index bb37e01f0..7a8e25f58 100644 --- a/evm_arithmetization/src/get_challenges.rs +++ b/evm_arithmetization/src/get_challenges.rs @@ -65,13 +65,16 @@ fn observe_block_metadata< challenger.observe_element(basefee.0); challenger.observe_element(basefee.1); challenger.observe_element(u256_to_u32(block_metadata.block_gas_used)?); - let blob_gas_used = u256_to_u64(block_metadata.block_blob_gas_used)?; - challenger.observe_element(blob_gas_used.0); - challenger.observe_element(blob_gas_used.1); - let excess_blob_gas = u256_to_u64(block_metadata.block_excess_blob_gas)?; - challenger.observe_element(excess_blob_gas.0); - challenger.observe_element(excess_blob_gas.1); - challenger.observe_elements(&h256_limbs::(block_metadata.parent_beacon_block_root)); + #[cfg(feature = "eth_mainnet")] + { + let blob_gas_used = u256_to_u64(block_metadata.block_blob_gas_used)?; + challenger.observe_element(blob_gas_used.0); + challenger.observe_element(blob_gas_used.1); + let excess_blob_gas = u256_to_u64(block_metadata.block_excess_blob_gas)?; + challenger.observe_element(excess_blob_gas.0); + challenger.observe_element(excess_blob_gas.1); + challenger.observe_elements(&h256_limbs::(block_metadata.parent_beacon_block_root)); + } for i in 0..8 { challenger.observe_elements(&u256_limbs(block_metadata.block_bloom[i])); } @@ -98,9 +101,12 @@ fn observe_block_metadata_target< challenger.observe_element(block_metadata.block_chain_id); challenger.observe_elements(&block_metadata.block_base_fee); challenger.observe_element(block_metadata.block_gas_used); - challenger.observe_elements(&block_metadata.block_blob_gas_used); - challenger.observe_elements(&block_metadata.block_excess_blob_gas); - challenger.observe_elements(&block_metadata.parent_beacon_block_root); + #[cfg(feature = "eth_mainnet")] + { + challenger.observe_elements(&block_metadata.block_blob_gas_used); + challenger.observe_elements(&block_metadata.block_excess_blob_gas); + challenger.observe_elements(&block_metadata.parent_beacon_block_root); + } challenger.observe_elements(&block_metadata.block_bloom); } diff --git a/evm_arithmetization/src/lib.rs b/evm_arithmetization/src/lib.rs index 7e456bc63..bdabc1c79 100644 --- a/evm_arithmetization/src/lib.rs +++ b/evm_arithmetization/src/lib.rs @@ -183,6 +183,17 @@ #![allow(clippy::field_reassign_with_default)] #![feature(let_chains)] +#[cfg_attr( + not(any(feature = "polygon_pos", feature = "cdk_erigon")), + cfg(feature = "eth_mainnet") +)] +#[cfg(any( + all(feature = "cdk_erigon", feature = "polygon_pos"), + all(feature = "cdk_erigon", feature = "eth_mainnet"), + all(feature = "polygon_pos", feature = "eth_mainnet"), +))] +compile_error!("Only a single network feature should be enabled at a time!"); + // Individual STARK processing units pub mod arithmetic; pub mod byte_packing; diff --git a/evm_arithmetization/src/proof.rs b/evm_arithmetization/src/proof.rs index 27f71f8c1..8c4742a07 100644 --- a/evm_arithmetization/src/proof.rs +++ b/evm_arithmetization/src/proof.rs @@ -968,59 +968,45 @@ impl BurnAddrTarget { } } + #[cfg(feature = "cdk_erigon")] /// Connects the burn address in `ba0` to the burn address in `ba1`. - /// This is a no-op if `cdk_erigon` feature is not activated. - /// - /// This will panic if the `cdk_erigon` is activated and not both - /// `BurnAddrTarget`s are `BurnAddr` variants. pub(crate) fn connect, const D: usize>( builder: &mut CircuitBuilder, ba0: Self, ba1: Self, ) { - // There only are targets to connect if there is a burn address, i.e. when the - // `cdk_erigon` feature is active. - if cfg!(feature = "cdk_erigon") == true { - // If the `cdk_erigon` feature is activated, both `ba0` and `ba1` should be of - // type `BurnAddr`. - match (ba0, ba1) { - (BurnAddrTarget::BurnAddr(a0), BurnAddrTarget::BurnAddr(a1)) => { - for i in 0..BurnAddrTarget::get_size() { - builder.connect(a0[i], a1[i]); - } + match (ba0, ba1) { + (BurnAddrTarget::BurnAddr(a0), BurnAddrTarget::BurnAddr(a1)) => { + for i in 0..BurnAddrTarget::get_size() { + builder.connect(a0[i], a1[i]); } - _ => panic!("We should have already set an address (or U256::MAX) before."), } + _ => panic!("We should have already set an address (or U256::MAX) before."), } } + #[cfg(feature = "cdk_erigon")] /// If `condition`, asserts that `ba0 == ba1`. - /// This is a no-op if `cdk_erigon` feature is not activated. - /// - /// This will panic if the `cdk_erigon` is activated and not both - /// `BurnAddrTarget` are `BurnAddr` variants. pub(crate) fn conditional_assert_eq, const D: usize>( builder: &mut CircuitBuilder, condition: BoolTarget, ba0: Self, ba1: Self, ) { - if cfg!(feature = "cdk_erigon") { - match (ba0, ba1) { - ( - BurnAddrTarget::BurnAddr(addr_targets_0), - BurnAddrTarget::BurnAddr(addr_targets_1), - ) => { - for i in 0..BurnAddrTarget::get_size() { - builder.conditional_assert_eq( - condition.target, - addr_targets_0[i], - addr_targets_1[i], - ) - } + match (ba0, ba1) { + ( + BurnAddrTarget::BurnAddr(addr_targets_0), + BurnAddrTarget::BurnAddr(addr_targets_1), + ) => { + for i in 0..BurnAddrTarget::get_size() { + builder.conditional_assert_eq( + condition.target, + addr_targets_0[i], + addr_targets_1[i], + ) } - _ => panic!("There should be an address set in cdk_erigon."), } + _ => panic!("There should be an address set in cdk_erigon."), } } } diff --git a/evm_arithmetization/src/prover.rs b/evm_arithmetization/src/prover.rs index bd6769607..736c900ef 100644 --- a/evm_arithmetization/src/prover.rs +++ b/evm_arithmetization/src/prover.rs @@ -40,9 +40,8 @@ where F: RichField + Extendable, C: GenericConfig, { - if inputs.burn_addr.is_some() && !cfg!(feature = "cdk_erigon") { - log::warn!("The burn address in the GenerationInputs will be ignored, as the `cdk_erigon` feature is not activated.") - } + features_check(&inputs); + // Sanity check on the provided config assert_eq!(DEFAULT_CAP_LEN, 1 << config.fri_config.cap_height); @@ -473,6 +472,20 @@ pub fn check_abort_signal(abort_signal: Option>) -> Result<()> { Ok(()) } +/// Sanity checks on the consistency between this proof payload and the feature +/// flags being used. +pub(crate) fn features_check(inputs: &TrimmedGenerationInputs) { + if cfg!(feature = "polygon_pos") || cfg!(feature = "cdk_erigon") { + assert!(inputs.block_metadata.parent_beacon_block_root.is_zero()); + assert!(inputs.block_metadata.block_blob_gas_used.is_zero()); + assert!(inputs.block_metadata.block_excess_blob_gas.is_zero()); + } + + if !cfg!(feature = "cdk_erigon") { + assert!(inputs.burn_addr.is_none()); + } +} + /// A utility module designed to test witness generation externally. pub mod testing { use super::*; @@ -488,9 +501,8 @@ pub mod testing { /// Simulates the zkEVM CPU execution. /// It does not generate any trace or proof of correct state transition. pub fn simulate_execution(inputs: GenerationInputs) -> Result<()> { - if inputs.burn_addr.is_some() && !cfg!(feature = "cdk_erigon") { - log::warn!("The burn address in the GenerationInputs will be ignored, as the `cdk_erigon` feature is not activated.") - } + features_check(&inputs.clone().trim()); + let initial_stack = vec![]; let initial_offset = KERNEL.global_labels["init"]; let mut interpreter: Interpreter = @@ -545,6 +557,8 @@ pub mod testing { where F: RichField, { + features_check(&inputs.clone().trim()); + for segment in SegmentDataIterator::::new(&inputs, Some(max_cpu_len_log)) { if let Err(e) = segment { return Err(anyhow::format_err!(e)); diff --git a/evm_arithmetization/src/recursive_verifier.rs b/evm_arithmetization/src/recursive_verifier.rs index a15c86b2d..29344ff11 100644 --- a/evm_arithmetization/src/recursive_verifier.rs +++ b/evm_arithmetization/src/recursive_verifier.rs @@ -380,10 +380,12 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, ), ]; - // This contains the `block_beneficiary`, `block_random`, `block_base_fee`, - // `block_blob_gas_used`, `block_excess_blob_gas`, `parent_beacon_block_root` - // as well as `cur_hash`. - let block_fields_arrays: [(GlobalMetadata, &[Target]); 7] = [ + // This contains the `block_beneficiary`, `block_random`, `block_base_fee`, and + // `cur_hash`, as well as the additional `block_blob_gas_used`, + // `block_excess_blob_gas`, `parent_beacon_block_root` when compiling with + // `eth_mainnet` feature flag. + const LENGTH: usize = if cfg!(feature = "eth_mainnet") { 7 } else { 4 }; + let block_fields_arrays: [(GlobalMetadata, &[Target]); LENGTH] = [ ( GlobalMetadata::BlockBeneficiary, &public_values.block_metadata.block_beneficiary, @@ -396,14 +398,17 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, GlobalMetadata::BlockBaseFee, &public_values.block_metadata.block_base_fee, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::BlockBlobGasUsed, &public_values.block_metadata.block_blob_gas_used, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::BlockExcessBlobGas, &public_values.block_metadata.block_excess_blob_gas, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::ParentBeaconBlockRoot, &public_values.block_metadata.parent_beacon_block_root, @@ -1046,31 +1051,34 @@ where block_metadata_target.block_gas_used, u256_to_u32(block_metadata.block_gas_used)?, ); - // BlobGasUsed fits in 2 limbs - let blob_gas_used = u256_to_u64(block_metadata.block_blob_gas_used)?; - witness.set_target( - block_metadata_target.block_blob_gas_used[0], - blob_gas_used.0, - ); - witness.set_target( - block_metadata_target.block_blob_gas_used[1], - blob_gas_used.1, - ); - // ExcessBlobGas fits in 2 limbs - let excess_blob_gas = u256_to_u64(block_metadata.block_excess_blob_gas)?; - witness.set_target( - block_metadata_target.block_excess_blob_gas[0], - excess_blob_gas.0, - ); - witness.set_target( - block_metadata_target.block_excess_blob_gas[1], - excess_blob_gas.1, - ); + #[cfg(feature = "eth_mainnet")] + { + // BlobGasUsed fits in 2 limbs + let blob_gas_used = u256_to_u64(block_metadata.block_blob_gas_used)?; + witness.set_target( + block_metadata_target.block_blob_gas_used[0], + blob_gas_used.0, + ); + witness.set_target( + block_metadata_target.block_blob_gas_used[1], + blob_gas_used.1, + ); + // ExcessBlobGas fits in 2 limbs + let excess_blob_gas = u256_to_u64(block_metadata.block_excess_blob_gas)?; + witness.set_target( + block_metadata_target.block_excess_blob_gas[0], + excess_blob_gas.0, + ); + witness.set_target( + block_metadata_target.block_excess_blob_gas[1], + excess_blob_gas.1, + ); - witness.set_target_arr( - &block_metadata_target.parent_beacon_block_root, - &h256_limbs(block_metadata.parent_beacon_block_root), - ); + witness.set_target_arr( + &block_metadata_target.parent_beacon_block_root, + &h256_limbs(block_metadata.parent_beacon_block_root), + ); + } let mut block_bloom_limbs = [F::ZERO; 64]; for (i, limbs) in block_bloom_limbs.chunks_exact_mut(8).enumerate() { diff --git a/evm_arithmetization/src/verifier.rs b/evm_arithmetization/src/verifier.rs index 52c2c21eb..89220b607 100644 --- a/evm_arithmetization/src/verifier.rs +++ b/evm_arithmetization/src/verifier.rs @@ -320,6 +320,7 @@ where GlobalMetadata::BlockBaseFee, public_values.block_metadata.block_base_fee, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::ParentBeaconBlockRoot, h2u(public_values.block_metadata.parent_beacon_block_root), @@ -332,10 +333,12 @@ where GlobalMetadata::BlockGasUsed, public_values.block_metadata.block_gas_used, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::BlockBlobGasUsed, public_values.block_metadata.block_blob_gas_used, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::BlockExcessBlobGas, public_values.block_metadata.block_excess_blob_gas, @@ -505,6 +508,13 @@ pub(crate) mod debug_utils { GlobalMetadata::BlockBeneficiary, U256::from_big_endian(&public_values.block_metadata.block_beneficiary.0), ), + #[cfg(feature = "cdk_erigon")] + ( + GlobalMetadata::BurnAddr, + public_values + .burn_addr + .expect("There should be an address set in cdk_erigon."), + ), ( GlobalMetadata::BlockTimestamp, public_values.block_metadata.block_timestamp, @@ -541,14 +551,17 @@ pub(crate) mod debug_utils { GlobalMetadata::BlockGasUsed, public_values.block_metadata.block_gas_used, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::BlockBlobGasUsed, public_values.block_metadata.block_blob_gas_used, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::BlockExcessBlobGas, public_values.block_metadata.block_excess_blob_gas, ), + #[cfg(feature = "eth_mainnet")] ( GlobalMetadata::ParentBeaconBlockRoot, h2u(public_values.block_metadata.parent_beacon_block_root), diff --git a/evm_arithmetization/src/witness/transition.rs b/evm_arithmetization/src/witness/transition.rs index 1bcbeb567..6263977f4 100644 --- a/evm_arithmetization/src/witness/transition.rs +++ b/evm_arithmetization/src/witness/transition.rs @@ -120,7 +120,9 @@ pub(crate) fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Syscall(opcode, 0, true)), // CHAINID (0x47, _) => Ok(Operation::Syscall(opcode, 0, true)), // SELFBALANCE (0x48, _) => Ok(Operation::Syscall(opcode, 0, true)), // BASEFEE + #[cfg(feature = "eth_mainnet")] (0x49, _) => Ok(Operation::Syscall(opcode, 1, false)), // BLOBHASH + #[cfg(feature = "eth_mainnet")] (0x4a, _) => Ok(Operation::Syscall(opcode, 0, true)), // BLOBBASEFEE (0x50, _) => Ok(Operation::Pop), (0x51, _) => Ok(Operation::Syscall(opcode, 1, false)), // MLOAD diff --git a/evm_arithmetization/tests/add11_yml.rs b/evm_arithmetization/tests/add11_yml.rs index e892d2772..a7624a6df 100644 --- a/evm_arithmetization/tests/add11_yml.rs +++ b/evm_arithmetization/tests/add11_yml.rs @@ -1,4 +1,4 @@ -#![cfg(not(feature = "cdk_erigon"))] +#![cfg(feature = "eth_mainnet")] use std::collections::HashMap; use std::str::FromStr; diff --git a/evm_arithmetization/tests/erc20.rs b/evm_arithmetization/tests/erc20.rs index cd16de80f..61bb3f2ef 100644 --- a/evm_arithmetization/tests/erc20.rs +++ b/evm_arithmetization/tests/erc20.rs @@ -1,4 +1,4 @@ -#![cfg(not(feature = "cdk_erigon"))] +#![cfg(feature = "eth_mainnet")] use std::str::FromStr; use std::time::Duration; diff --git a/evm_arithmetization/tests/erc721.rs b/evm_arithmetization/tests/erc721.rs index 851560430..47d214f9e 100644 --- a/evm_arithmetization/tests/erc721.rs +++ b/evm_arithmetization/tests/erc721.rs @@ -1,4 +1,4 @@ -#![cfg(not(feature = "cdk_erigon"))] +#![cfg(feature = "eth_mainnet")] use std::str::FromStr; use std::time::Duration; diff --git a/evm_arithmetization/tests/global_exit_root.rs b/evm_arithmetization/tests/global_exit_root.rs index ba9ac1c02..0bc41eaf8 100644 --- a/evm_arithmetization/tests/global_exit_root.rs +++ b/evm_arithmetization/tests/global_exit_root.rs @@ -8,10 +8,8 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::testing::prove_all_segments; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, - ger_contract_from_storage, init_logger, preinitialized_state_and_storage_tries, - scalable_account_nibbles, scalable_contract_from_storage, update_beacon_roots_account_storage, - update_ger_account_storage, update_scalable_account_storage, + ger_account_nibbles, ger_contract_from_storage, init_logger, scalable_account_nibbles, + scalable_contract_from_storage, update_ger_account_storage, update_scalable_account_storage, ADDRESS_SCALABLE_L2_ADDRESS_HASHED, GLOBAL_EXIT_ROOT_ACCOUNT, GLOBAL_EXIT_ROOT_ADDRESS_HASHED, }; use evm_arithmetization::verifier::testing::verify_all_proofs; @@ -40,13 +38,13 @@ fn test_global_exit_root() -> anyhow::Result<()> { ..BlockMetadata::default() }; - let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries()?; + let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + let mut storage_tries = vec![]; state_trie_before.insert( ger_account_nibbles(), rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), )?; - let mut beacon_roots_account_storage = storage_tries[0].1.clone(); let mut ger_account_storage = HashedPartialTrie::from(Node::Empty); let mut scalable_account_storage = HashedPartialTrie::from(Node::Empty); @@ -66,11 +64,6 @@ fn test_global_exit_root() -> anyhow::Result<()> { let state_trie_after = { let mut trie = HashedPartialTrie::from(Node::Empty); - update_beacon_roots_account_storage( - &mut beacon_roots_account_storage, - block_metadata.block_timestamp, - block_metadata.parent_beacon_block_root, - )?; update_ger_account_storage(&mut ger_account_storage, ger_data)?; update_scalable_account_storage( &mut scalable_account_storage, @@ -78,15 +71,9 @@ fn test_global_exit_root() -> anyhow::Result<()> { state_trie_before.hash(), )?; - let beacon_roots_account = - beacon_roots_contract_from_storage(&beacon_roots_account_storage); let ger_account = ger_contract_from_storage(&ger_account_storage); let scalable_account = scalable_contract_from_storage(&scalable_account_storage); - trie.insert( - beacon_roots_account_nibbles(), - rlp::encode(&beacon_roots_account).to_vec(), - )?; trie.insert(ger_account_nibbles(), rlp::encode(&ger_account).to_vec())?; trie.insert( scalable_account_nibbles(), diff --git a/evm_arithmetization/tests/log_opcode.rs b/evm_arithmetization/tests/log_opcode.rs index b08ace71c..0b925ceae 100644 --- a/evm_arithmetization/tests/log_opcode.rs +++ b/evm_arithmetization/tests/log_opcode.rs @@ -1,4 +1,4 @@ -#![cfg(not(feature = "cdk_erigon"))] +#![cfg(feature = "eth_mainnet")] use std::collections::HashMap; use std::str::FromStr; diff --git a/evm_arithmetization/tests/selfdestruct.rs b/evm_arithmetization/tests/selfdestruct.rs index 0669486bc..271630512 100644 --- a/evm_arithmetization/tests/selfdestruct.rs +++ b/evm_arithmetization/tests/selfdestruct.rs @@ -1,4 +1,4 @@ -#![cfg(not(feature = "cdk_erigon"))] +#![cfg(feature = "eth_mainnet")] use std::str::FromStr; use std::time::Duration; diff --git a/evm_arithmetization/tests/simple_transfer.rs b/evm_arithmetization/tests/simple_transfer.rs index 37d14bf05..81454fb0e 100644 --- a/evm_arithmetization/tests/simple_transfer.rs +++ b/evm_arithmetization/tests/simple_transfer.rs @@ -1,4 +1,4 @@ -#![cfg(not(feature = "cdk_erigon"))] +#![cfg(feature = "eth_mainnet")] use std::collections::HashMap; use std::str::FromStr; diff --git a/evm_arithmetization/tests/two_to_one_block.rs b/evm_arithmetization/tests/two_to_one_block.rs index 6c107c54d..2474bb82b 100644 --- a/evm_arithmetization/tests/two_to_one_block.rs +++ b/evm_arithmetization/tests/two_to_one_block.rs @@ -1,4 +1,4 @@ -#![cfg(not(feature = "cdk_erigon"))] +#![cfg(feature = "eth_mainnet")] use ethereum_types::{Address, BigEndianHash, H256}; use evm_arithmetization::fixed_recursive_verifier::{ @@ -173,36 +173,19 @@ fn test_two_to_one_block_aggregation() -> anyhow::Result<()> { let all_stark = AllStark::::default(); let config = StarkConfig::standard_fast_config(); - let circuit_ranges = if cfg!(feature = "cdk_erigon") { - vec![ - 16..17_usize, - 8..9, - 14..15, - 9..10, - 8..9, - 7..8, - 17..18, - 17..18, - 7..8, - 4..5, - ] - } else { - vec![ - 16..17_usize, + let all_circuits = AllRecursiveCircuits::::new( + &all_stark, + &[ + 16..17, 8..9, - 14..15, + 12..13, 9..10, 8..9, - 7..8, + 6..7, 17..18, 17..18, 7..8, - ] - }; - - let all_circuits = AllRecursiveCircuits::::new( - &all_stark, - &circuit_ranges.try_into().unwrap(), + ], &config, ); diff --git a/evm_arithmetization/tests/withdrawals.rs b/evm_arithmetization/tests/withdrawals.rs index 9839c6646..4868a2fd4 100644 --- a/evm_arithmetization/tests/withdrawals.rs +++ b/evm_arithmetization/tests/withdrawals.rs @@ -1,4 +1,4 @@ -#![cfg(not(feature = "cdk_erigon"))] +#![cfg(feature = "eth_mainnet")] use std::collections::HashMap; use std::time::Duration; diff --git a/proof_gen/Cargo.toml b/proof_gen/Cargo.toml index 304862ece..9c72b0362 100644 --- a/proof_gen/Cargo.toml +++ b/proof_gen/Cargo.toml @@ -20,8 +20,13 @@ hashbrown = { workspace = true } evm_arithmetization = { workspace = true } [features] -default = [] -cdk_erigon = ["evm_arithmetization/cdk_erigon"] +default = ["eth_mainnet"] +eth_mainnet = [ + "evm_arithmetization/eth_mainnet" +] +cdk_erigon = [ + "evm_arithmetization/cdk_erigon" +] [lints] workspace = true diff --git a/trace_decoder/Cargo.toml b/trace_decoder/Cargo.toml index 78e0aa3ef..4febd85f2 100644 --- a/trace_decoder/Cargo.toml +++ b/trace_decoder/Cargo.toml @@ -20,23 +20,25 @@ copyvec = "0.2.0" either = { workspace = true } enum-as-inner = { workspace = true } ethereum-types = { workspace = true } -evm_arithmetization = { workspace = true } hex = { workspace = true } hex-literal = { workspace = true } itertools.workspace = true keccak-hash = { workspace = true } log = { workspace = true } -mpt_trie = { workspace = true } nunny = { workspace = true, features = ["serde"] } plonky2 = { workspace = true } rlp = { workspace = true } serde = { workspace = true } -smt_trie = { workspace = true } stackstack = "0.3.0" strum = { version = "0.26.3", features = ["derive"] } thiserror = { workspace = true } u4 = { workspace = true } winnow = { workspace = true } + +# Local dependencies +evm_arithmetization = { workspace = true } +mpt_trie = { workspace = true } +smt_trie = { workspace = true } zk_evm_common = { workspace = true } [dev-dependencies] @@ -53,6 +55,18 @@ prover = { workspace = true } serde_json = { workspace = true } serde_path_to_error = { workspace = true } + +[features] +default = ["eth_mainnet"] +eth_mainnet = [ + "evm_arithmetization/eth_mainnet", + "prover/eth_mainnet", +] +cdk_erigon = [ + "evm_arithmetization/cdk_erigon", + "prover/cdk_erigon", +] + [[bench]] name = "block_processing" harness = false diff --git a/zero_bin/common/Cargo.toml b/zero_bin/common/Cargo.toml index 7171fa2a8..0b389cf9f 100644 --- a/zero_bin/common/Cargo.toml +++ b/zero_bin/common/Cargo.toml @@ -14,32 +14,40 @@ anyhow = { workspace = true } async-stream = { workspace = true } cargo_metadata = { workspace = true } clap = { workspace = true } -evm_arithmetization = { workspace = true } futures = { workspace = true } lru = { workspace = true } once_cell = { workspace = true } plonky2 = { workspace = true } -proof_gen = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } -trace_decoder = { workspace = true } tracing = { workspace = true } vergen = { workspace = true } directories = "5.0.1" +# Local dependencies +evm_arithmetization = { workspace = true } +proof_gen = { workspace = true } +trace_decoder = { workspace = true } + [build-dependencies] cargo_metadata = { workspace = true } vergen = { workspace = true } anyhow = { workspace = true } [features] -default = [] +default = ["eth_mainnet"] +eth_mainnet = [ + "evm_arithmetization/eth_mainnet", + "proof_gen/eth_mainnet", + "trace_decoder/eth_mainnet", +] cdk_erigon = [ "evm_arithmetization/cdk_erigon", - "proof_gen/cdk_erigon" + "proof_gen/cdk_erigon", + "trace_decoder/cdk_erigon", ] [lints] diff --git a/zero_bin/leader/Cargo.toml b/zero_bin/leader/Cargo.toml index cad05cd22..5b9a67f34 100644 --- a/zero_bin/leader/Cargo.toml +++ b/zero_bin/leader/Cargo.toml @@ -18,7 +18,6 @@ anyhow = { workspace = true } serde = { workspace = true } dotenvy = { workspace = true } tokio = { workspace = true } -proof_gen = { workspace = true } serde_json = { workspace = true } serde_path_to_error = { workspace = true } futures = { workspace = true } @@ -27,16 +26,26 @@ axum = { workspace = true } toml = { workspace = true } # Local dependencies +evm_arithmetization = { workspace = true } ops = { workspace = true } +proof_gen = { workspace = true } prover = { workspace = true } rpc = { workspace = true } -evm_arithmetization = { workspace = true } zero_bin_common = { workspace = true } [features] -default = [] +default = ["eth_mainnet"] +eth_mainnet = [ + "evm_arithmetization/eth_mainnet", + "ops/eth_mainnet", + "proof_gen/eth_mainnet", + "prover/eth_mainnet", + "rpc/eth_mainnet", + "zero_bin_common/eth_mainnet" +] cdk_erigon = [ "evm_arithmetization/cdk_erigon", + "ops/cdk_erigon", "proof_gen/cdk_erigon", "prover/cdk_erigon", "rpc/cdk_erigon", diff --git a/zero_bin/ops/Cargo.toml b/zero_bin/ops/Cargo.toml index 8975cc8d5..4e7bef01a 100644 --- a/zero_bin/ops/Cargo.toml +++ b/zero_bin/ops/Cargo.toml @@ -11,13 +11,26 @@ categories.workspace = true [dependencies] paladin-core = { workspace = true } serde = { workspace = true } -evm_arithmetization = { workspace = true } -proof_gen = { workspace = true } tracing = { workspace = true } -trace_decoder = { workspace = true } keccak-hash = { workspace = true } -zero_bin_common = { path = "../common" } +# Local dependencies +evm_arithmetization = { workspace = true } +proof_gen = { workspace = true } +trace_decoder = { workspace = true } +zero_bin_common = { workspace = true } [features] -default = [] +default = ["eth_mainnet"] +eth_mainnet = [ + "evm_arithmetization/eth_mainnet", + "proof_gen/eth_mainnet", + "trace_decoder/eth_mainnet", + "zero_bin_common/eth_mainnet", +] +cdk_erigon = [ + "evm_arithmetization/cdk_erigon", + "proof_gen/cdk_erigon", + "trace_decoder/cdk_erigon", + "zero_bin_common/cdk_erigon", +] diff --git a/zero_bin/prover/Cargo.toml b/zero_bin/prover/Cargo.toml index 6295f65b4..b4e54b19a 100644 --- a/zero_bin/prover/Cargo.toml +++ b/zero_bin/prover/Cargo.toml @@ -12,25 +12,39 @@ categories.workspace = true alloy.workspace = true anyhow = { workspace = true } clap = {workspace = true, features = ["derive", "string"] } -evm_arithmetization = { workspace = true } futures = { workspace = true } num-traits = { workspace = true } ops = { workspace = true } paladin-core = { workspace = true } plonky2 = { workspace = true } plonky2_maybe_rayon = { workspace = true } -proof_gen = { workspace = true } ruint = { workspace = true, features = ["num-traits", "primitive-types"] } serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } -trace_decoder = { workspace = true } tracing = { workspace = true } + +# Local dependencies +evm_arithmetization = { workspace = true } +proof_gen = { workspace = true } +trace_decoder = { workspace = true } zero_bin_common = { workspace = true } + [features] -default = [] -cdk_erigon = [] +default = ["eth_mainnet"] +eth_mainnet = [ + "evm_arithmetization/eth_mainnet", + "proof_gen/eth_mainnet", + "trace_decoder/eth_mainnet", + "zero_bin_common/eth_mainnet", +] +cdk_erigon = [ + "evm_arithmetization/cdk_erigon", + "proof_gen/cdk_erigon", + "trace_decoder/cdk_erigon", + "zero_bin_common/cdk_erigon", +] [lints] workspace = true diff --git a/zero_bin/rpc/Cargo.toml b/zero_bin/rpc/Cargo.toml index 046461569..8d4444bb6 100644 --- a/zero_bin/rpc/Cargo.toml +++ b/zero_bin/rpc/Cargo.toml @@ -14,15 +14,12 @@ __compat_primitive_types = { workspace = true } alloy.workspace = true anyhow = { workspace = true } clap = { workspace = true } -evm_arithmetization = { workspace = true } futures = { workspace = true } hex = { workspace = true } -mpt_trie = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } tower = { workspace = true, features = ["retry"] } -trace_decoder = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } url = { workspace = true } @@ -30,8 +27,11 @@ itertools = { workspace = true } # Local dependencies compat = { workspace = true } -zero_bin_common = { workspace = true } +evm_arithmetization = { workspace = true } +mpt_trie = { workspace = true } prover = { workspace = true } +trace_decoder = { workspace = true } +zero_bin_common = { workspace = true } [build-dependencies] cargo_metadata = { workspace = true } @@ -39,4 +39,16 @@ vergen = { workspace = true } anyhow = { workspace = true } [features] -cdk_erigon = [] \ No newline at end of file +default = ["eth_mainnet"] +eth_mainnet = [ + "evm_arithmetization/eth_mainnet", + "prover/eth_mainnet", + "trace_decoder/eth_mainnet", + "zero_bin_common/eth_mainnet", +] +cdk_erigon = [ + "evm_arithmetization/cdk_erigon", + "prover/cdk_erigon", + "trace_decoder/cdk_erigon", + "zero_bin_common/cdk_erigon", +] \ No newline at end of file diff --git a/zero_bin/verifier/Cargo.toml b/zero_bin/verifier/Cargo.toml index ab9e5068f..80d64df1f 100644 --- a/zero_bin/verifier/Cargo.toml +++ b/zero_bin/verifier/Cargo.toml @@ -13,12 +13,24 @@ dotenvy = { workspace = true } anyhow = { workspace = true } serde_json = { workspace = true } serde_path_to_error = { workspace = true } -proof_gen = { workspace = true } # Local dependencies -zero_bin_common = { path = "../common" } +proof_gen = { workspace = true } +zero_bin_common = { workspace = true } [build-dependencies] cargo_metadata = { workspace = true } vergen = { workspace = true } anyhow = { workspace = true } + + +[features] +default = ["eth_mainnet"] +eth_mainnet = [ + "proof_gen/eth_mainnet", + "zero_bin_common/eth_mainnet", +] +cdk_erigon = [ + "proof_gen/cdk_erigon", + "zero_bin_common/cdk_erigon", +] diff --git a/zero_bin/worker/Cargo.toml b/zero_bin/worker/Cargo.toml index 3a146c90e..0e7d80d0b 100644 --- a/zero_bin/worker/Cargo.toml +++ b/zero_bin/worker/Cargo.toml @@ -28,3 +28,14 @@ jemallocator = "0.5.4" cargo_metadata = { workspace = true } vergen = { workspace = true } anyhow = { workspace = true } + +[features] +default = ["eth_mainnet"] +eth_mainnet = [ + "ops/eth_mainnet", + "zero_bin_common/eth_mainnet", +] +cdk_erigon = [ + "ops/cdk_erigon", + "zero_bin_common/cdk_erigon", +] \ No newline at end of file From d05ea478f6a87af35084ccde3fab1612fa3929fa Mon Sep 17 00:00:00 2001 From: Marko Atanasievski Date: Mon, 9 Sep 2024 12:05:07 +0200 Subject: [PATCH 16/19] optimize: limit number of blocks proving in parallel (#600) * optimize: limit number of blocks proving in parallel * fix: nit * fix: review --- zero_bin/prover/src/lib.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/zero_bin/prover/src/lib.rs b/zero_bin/prover/src/lib.rs index 8aab306bb..f48e14a8c 100644 --- a/zero_bin/prover/src/lib.rs +++ b/zero_bin/prover/src/lib.rs @@ -13,11 +13,24 @@ use proof_gen::proof_types::GeneratedBlockProof; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; use tokio::sync::mpsc::Receiver; -use tokio::sync::oneshot; +use tokio::sync::{oneshot, Semaphore}; use trace_decoder::{BlockTrace, OtherBlockData}; use tracing::{error, info}; use zero_bin_common::fs::generate_block_proof_file_name; +// All proving tasks are executed concurrently, which can cause issues for large +// block intervals, where distant future blocks may be proven first. +// +// We then create a pool to limit the number of parallel proving block +// tasks, retrieving new blocks in increasing order when some block proofs are +// complete. +// +// While proving a block interval, we will output proofs corresponding to block +// batches as soon as they are generated. +const PARALLEL_BLOCK_PROVING_PERMIT_POOL_SIZE: usize = 16; +static PARALLEL_BLOCK_PROVING_PERMIT_POOL: Semaphore = + Semaphore::const_new(PARALLEL_BLOCK_PROVING_PERMIT_POOL_SIZE); + #[derive(Debug, Clone)] pub struct ProverConfig { pub batch_size: usize, @@ -242,6 +255,9 @@ pub async fn prove( let previous_block_proof = prev_proof.take(); let runtime = runtime.clone(); let block_number = block_prover_input.get_block_number(); + + let prove_permit = PARALLEL_BLOCK_PROVING_PERMIT_POOL.acquire().await?; + let _abort_handle = task_set.spawn(async move { let block_number = block_prover_input.get_block_number(); info!("Proving block {block_number}"); @@ -253,6 +269,7 @@ pub async fn prove( prover_config.clone(), ) .then(move |proof| async move { + drop(prove_permit); let proof = proof.inspect_err(|e| { error!("failed to generate proof for block {block_number}, error {e:?}") })?; From f5fd1504452a889aa24047b4663ae466850d593a Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 9 Sep 2024 08:31:16 -0400 Subject: [PATCH 17/19] doc(book): Fill up empty sections and add table diagram (#608) * Fill up empty sections and add table diagram * Typo * Ignore mermaid initialization files * Update workflow --- .github/workflows/book.yml | 18 ++++++++++++++--- README.md | 3 ++- book/.gitignore | 5 +++++ book/book.toml | 4 ++++ book/src/SUMMARY.md | 4 ++-- book/src/intro.md | 1 - book/src/mpt/intro.md | 10 +++++----- book/src/tables/intro.md | 41 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 74 insertions(+), 12 deletions(-) delete mode 100644 book/src/intro.md diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 45c47716e..97df1d973 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -3,9 +3,12 @@ name: zkEVM mdbook on: push: branches: [develop, main] + pull_request: + branches: + - "**" jobs: - deploy: + build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -20,15 +23,24 @@ jobs: command: install args: mdbook - - name: Install mdbook-katex and mdbook-bib + - name: Install preprocessors uses: actions-rs/cargo@v1 with: command: install - args: mdbook-katex mdbook-bib + args: mdbook-katex mdbook-bib mdbook-mermaid + + - name: Initialize mermaid preprocessor + run: mdbook-mermaid install book - name: Build book run: mdbook build book + deploy: + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v3 - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: diff --git a/README.md b/README.md index a4d8326e4..b7bd2208e 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,10 @@ EVM block proofs. Below is a simplified view of the dependency graph, including the proving system backends and the application layer defined within [zero-bin](https://github.com/0xPolygonZero/zero-bin). ```mermaid +%%{init: {'theme':'dark'}}%% flowchart LR subgraph ps [proving systems] A1{{plonky2}} diff --git a/book/.gitignore b/book/.gitignore index 7585238ef..2b1e9364a 100644 --- a/book/.gitignore +++ b/book/.gitignore @@ -1 +1,6 @@ book + +# Mermaid initialization files +# Obtained with `mdbook-mermaid install book`. +mermaid-init.js +mermaid.min.js diff --git a/book/book.toml b/book/book.toml index 33cbda21c..eb3dcc30c 100644 --- a/book/book.toml +++ b/book/book.toml @@ -20,7 +20,11 @@ create-missing = true [preprocessor.bib] bibliography = "bibliography.bib" +[preprocessor.mermaid] +command = "mdbook-mermaid" + [output.html] +additional-js = ["mermaid.min.js", "mermaid-init.js"] [output.html.print] # Disable page break diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index c2ee742bc..bdd613969 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -1,10 +1,10 @@ # Summary -[Introduction](intro.md) +[Introduction](../../README.md) - [STARK framework](framework/intro.md) - - [Cost model](framework/cost_model.md) - [Field](framework/field.md) + - [Cost model](framework/cost_model.md) - [Cross-Table Lookups](framework/ctls.md) - [Range-Checks](framework/range_check.md) - [Tables](tables/intro.md) diff --git a/book/src/intro.md b/book/src/intro.md deleted file mode 100644 index e10b99d01..000000000 --- a/book/src/intro.md +++ /dev/null @@ -1 +0,0 @@ -# Introduction diff --git a/book/src/mpt/intro.md b/book/src/mpt/intro.md index 1bb416531..25018bb84 100644 --- a/book/src/mpt/intro.md +++ b/book/src/mpt/intro.md @@ -3,7 +3,7 @@ The *EVM World state* is a representation of the different accounts at a particular time, as well as the last processed transactions together with their receipts. The world state is represented using *Merkle -Patricia Tries* (MPTs) [@@yellowpaper App. D], and there are three +Patricia Tries* (MPTs, see [Yellowpaper App. D](@@yellowpaper)), and there are three different tries: the state trie, the transaction trie and the receipt trie. @@ -17,10 +17,10 @@ inserting new nodes or deleting existing nodes. An MPT is composed of five different nodes: branch, extension, leaf, empty and digest nodes. Branch and leaf nodes might contain a payload whose format depends on the particular trie. The nodes are encoded, -primarily using RLP encoding and Hex-prefix encoding (see @@yellowpaper -App. B and C, respectively). The resulting encoding is then hashed, -following a strategy similar to that of normal Merkle trees, to generate -the trie hashes. +primarily using RLP encoding and Hex-prefix encoding (see [Yellowpaper +App. B and C](@@yellowpaper), respectively). The resulting encoding is +then hashed, following a strategy similar to that of normal Merkle trees, +to generate the trie hashes. Insertion and deletion is performed in the same way as other MPTs implementations. The only difference is for inserting extension nodes diff --git a/book/src/tables/intro.md b/book/src/tables/intro.md index afb1b4c41..8e6ac1d29 100644 --- a/book/src/tables/intro.md +++ b/book/src/tables/intro.md @@ -1 +1,42 @@ # Tables + +Our EVM statements are decomposed into several STARK tables, each corresponding to some coprocessor capable of handling specific operations, +orchestrated by a [Central Processing Unit](./cpu.md). + +Each coprocessor execution can be proven independently and concurrently, and a global check (via our [cross-table lookups](../framework/ctls.md)) +enforces consistency of values being shared across multiple coprocessors. + +Below is a depiction of the current decomposition of EVM statements execution[^1]: + +```mermaid +%%{init: {'theme':'dark'}}%% +flowchart TB + A[Arithmetic] + BP[BytePacking] + C[CPU] + + subgraph KK [ Keccak Hash ] + direction LR + K[Keccak] + KS[KeccakSponge] + K --- KS + end + + L[Logic] + M[Memory] + + subgraph MM [ zk-continuations ] + MB[MemBefore] + MA[MemAfter] + MB --- MA + end + + C --- A + C --- BP + C --- KK + C --- L + C --- M + M --- MM +``` + +[^1]: This diagram is simplified, and does not represent *all* interactions between co-processors. \ No newline at end of file From bfdc6c301805410df5ee3f572e01dff44a741955 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:50:03 -0400 Subject: [PATCH 18/19] Fix github pages deployment (#616) * Fix deployment * TMP: For testing only * Tweak * Revert "TMP: For testing only" This reverts commit 287febdffb7c68e986a0c3f8056ed3d927c0e8e4. * Update labeler --- .github/labeler.yml | 9 +++++++-- .github/workflows/book.yml | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index cc9748609..72647b77b 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -23,12 +23,17 @@ - changed-files: - any-glob-to-any-file: zero_bin/** -# Add 'specs' label to any changes within 'docs' folder. +# Add 'specs' label to any changes within 'docs' or `book` folder. 'specs': - changed-files: - - any-glob-to-any-file: docs/** + - any-glob-to-any-file: ['docs/**', 'book/**'] # Add 'crate: common' label to any changes within 'common' folder. 'crate: common': - changed-files: - any-glob-to-any-file: common/** + +# Add 'ci' label to any changes within '.github' folder. +'ci': + - changed-files: + - any-glob-to-any-file: .github/** diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 97df1d973..aef229b3f 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -35,12 +35,25 @@ jobs: - name: Build book run: mdbook build book + - name: Upload built book + uses: actions/upload-artifact@v3 + with: + name: built-mdbook + path: ./book/book + deploy: if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') runs-on: ubuntu-latest needs: build steps: - uses: actions/checkout@v3 + + - name: Download built book + uses: actions/download-artifact@v3 + with: + name: built-mdbook + path: ./book/book + - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: From 188397d18fa37328db627d8a42a3655daffcdfd4 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:50:42 -0400 Subject: [PATCH 19/19] Enable withdrawals only for Eth mainnet (#610) --- .../src/cpu/kernel/aggregator.rs | 5 +++-- .../src/cpu/kernel/asm/main.asm | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/aggregator.rs b/evm_arithmetization/src/cpu/kernel/aggregator.rs index 4ac839659..74a5f17cb 100644 --- a/evm_arithmetization/src/cpu/kernel/aggregator.rs +++ b/evm_arithmetization/src/cpu/kernel/aggregator.rs @@ -12,9 +12,9 @@ use crate::cpu::kernel::parser::parse; pub const NUMBER_KERNEL_FILES: usize = if cfg!(feature = "eth_mainnet") { 157 } else if cfg!(feature = "cdk_erigon") { - 156 -} else if cfg!(feature = "polygon_pos") { 155 +} else if cfg!(feature = "polygon_pos") { + 154 } else { // unreachable 0 @@ -54,6 +54,7 @@ pub static KERNEL_FILES: [&str; NUMBER_KERNEL_FILES] = [ include_str!("asm/core/log.asm"), include_str!("asm/core/selfdestruct_list.asm"), include_str!("asm/core/touched_addresses.asm"), + #[cfg(feature = "eth_mainnet")] include_str!("asm/core/withdrawals.asm"), include_str!("asm/core/precompiles/main.asm"), include_str!("asm/core/precompiles/ecrec.asm"), diff --git a/evm_arithmetization/src/cpu/kernel/asm/main.asm b/evm_arithmetization/src/cpu/kernel/asm/main.asm index 8d2b09c66..6f20938af 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/main.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/main.asm @@ -146,7 +146,14 @@ global start_txns: global txn_loop: // If the prover has no more txns for us to process, halt. PROVER_INPUT(end_of_txns) - %jumpi(execute_withdrawals) + #[cfg(feature = eth_mainnet)] + { + %jumpi(execute_withdrawals) + } + #[cfg(not(feature = eth_mainnet))] + { + %jumpi(perform_final_checks) + } // Call route_txn. When we return, we will process the txn receipt. PUSH txn_loop_after @@ -171,9 +178,12 @@ global txn_loop_after: // stack: new_cum_gas, txn_counter, num_nibbles, new_txn_number %jump(txn_loop) -global execute_withdrawals: - // stack: cum_gas, txn_counter, num_nibbles, txn_nb - %withdrawals +#[cfg(feature = eth_mainnet)] +{ + global execute_withdrawals: + // stack: cum_gas, txn_counter, num_nibbles, txn_nb + %withdrawals +} global perform_final_checks: // stack: cum_gas, txn_counter, num_nibbles, txn_nb