diff --git a/Cargo.lock b/Cargo.lock index 60c29bae818..77f2acbfecf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3133,6 +3133,7 @@ name = "mock" version = "0.1.0" dependencies = [ "eth-types", + "ethabi", "ethers-core", "ethers-signers", "external-tracer", @@ -5824,6 +5825,7 @@ dependencies = [ "either", "env_logger 0.10.0", "eth-types", + "ethabi", "ethers-core", "ethers-signers", "gadgets", diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 4d03e126b02..f0d90f52e81 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -933,7 +933,7 @@ pub fn keccak_inputs_tx_circuit(txs: &[geth_types::Transaction]) -> Result Result Result Result Result Result Result for usize { @@ -52,6 +54,16 @@ impl TxType { matches!(*self, TxType::L1Msg) } + /// If this type is L1BlockHashes or not + pub fn is_l1_block_hashes(&self) -> bool { + matches!(*self, TxType::L1BlockHashes) + } + + /// If this type is L1Msg or L1BlockHashes or none + pub fn is_l1_custom_tx(&self) -> bool { + matches!(*self, TxType::L1BlockHashes) || matches!(*self, TxType::L1Msg) + } + /// If this type is Eip155 or not pub fn is_eip155_tx(&self) -> bool { matches!(*self, TxType::Eip155) @@ -63,6 +75,7 @@ impl TxType { Some(x) if x == U64::from(1) => Self::Eip2930, Some(x) if x == U64::from(2) => Self::Eip1559, Some(x) if x == U64::from(0x7e) => Self::L1Msg, + Some(x) if x == U64::from(0x7d) => Self::L1BlockHashes, _ => { if cfg!(feature = "scroll") { if tx.v.is_zero() && tx.r.is_zero() && tx.s.is_zero() { @@ -102,6 +115,9 @@ impl TxType { TxType::L1Msg => { unreachable!("L1 msg does not have signature") } + TxType::L1BlockHashes => { + unreachable!("L1 block hashes does not have signature") + } }; recovery_id as u8 @@ -138,6 +154,10 @@ pub fn get_rlp_unsigned(tx: &crate::Transaction) -> Vec { // L1 msg does not have signature vec![] } + TxType::L1BlockHashes => { + // L1 block hashes does not have signature + vec![] + } } } diff --git a/eth-types/src/l2_types.rs b/eth-types/src/l2_types.rs index 56526e3ce15..41e4ab9b3a3 100644 --- a/eth-types/src/l2_types.rs +++ b/eth-types/src/l2_types.rs @@ -37,7 +37,7 @@ pub struct BlockTrace { pub l1_block_hashes: Option>, } -/// l2 block full trace +/// l2 chunk trace #[derive(Deserialize, Serialize, Default, Debug, Clone)] pub struct ChunkTrace { /// Block traces diff --git a/geth-utils/l2geth/go.mod b/geth-utils/l2geth/go.mod index f261fd4b5e2..1b8edf5b875 100644 --- a/geth-utils/l2geth/go.mod +++ b/geth-utils/l2geth/go.mod @@ -40,3 +40,5 @@ require ( golang.org/x/sys v0.11.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect ) + +// replace github.com/scroll-tech/go-ethereum => ../../../scroll-go-ethereum diff --git a/geth-utils/l2geth/trace.go b/geth-utils/l2geth/trace.go index 2d27a2c4da2..0535f042906 100644 --- a/geth-utils/l2geth/trace.go +++ b/geth-utils/l2geth/trace.go @@ -79,9 +79,29 @@ func transferTxs(txs []Transaction) types.Transactions { t_txs := make([]*types.Transaction, 0, len(txs)) for _, tx := range txs { - // if no signature, we can only handle it as l1msg tx - // notice the type is defined in geth_types - if tx.Type == "L1Msg" || tx.R == nil || tx.R.ToInt().Cmp(big.NewInt(0)) == 0 { + if tx.Type == "L1BlockHashes" { + l1msgTx := &types.L1MessageTx{ + Gas: uint64(tx.GasLimit), + QueueIndex: uint64(tx.Nonce), + To: tx.To, + Value: toBigInt(tx.Value), + Data: tx.CallData, + Sender: tx.From, + } + t_txs = append(t_txs, types.NewTx(l1msgTx)) + // l1blockhashesTx := &types.L1BlockHashesTx{ + // FirstAppliedL1Block: uint64(0), + // BlockHashesRange: []common.Hash{}, + // LastAppliedL1Block: uint64(0), + // To: tx.To, + // Data: tx.CallData, + // Sender: tx.From, + // } + // t_txs = append(t_txs, types.NewTx(l1blockhashesTx)) + + // if no signature, we can only handle it as l1msg tx + // notice the type is defined in geth_types + } else if tx.Type == "L1Msg" || tx.R == nil || tx.R.ToInt().Cmp(big.NewInt(0)) == 0 { l1msgTx := &types.L1MessageTx{ Gas: uint64(tx.GasLimit), QueueIndex: uint64(tx.Nonce), @@ -236,6 +256,8 @@ func Trace(config TraceConfig) (*types.BlockTrace, error) { config.LoggerConfig, blockCtx, config.StartL1QueueIndex, + // 0, + // []common.Hash{}, blockCtx.Coinbase, stateDB, rootBefore, diff --git a/mock/Cargo.toml b/mock/Cargo.toml index 31f7d57a769..a53161929ce 100644 --- a/mock/Cargo.toml +++ b/mock/Cargo.toml @@ -14,6 +14,7 @@ ethers-core.workspace = true rand_chacha.workspace = true rand.workspace = true log.workspace = true +ethabi = "18.0.0" [features] default = [] diff --git a/mock/src/transaction.rs b/mock/src/transaction.rs index d4c0409e44b..6c15fa3108a 100644 --- a/mock/src/transaction.rs +++ b/mock/src/transaction.rs @@ -3,8 +3,11 @@ use super::{MOCK_ACCOUNTS, MOCK_CHAIN_ID, MOCK_GASPRICE}; use eth_types::{ geth_types::Transaction as GethTransaction, word, AccessList, Address, Bytes, Hash, - Transaction, Word, U64, + Transaction, Word, U64, H256, }; +use std::str::FromStr; +use ethers_core::utils::keccak256; +use ethabi::{Token, encode, Function, StateMutability, Param, ParamType}; use ethers_core::{ rand::{CryptoRng, RngCore}, types::{OtherFields, TransactionRequest}, @@ -165,23 +168,42 @@ pub struct MockTransaction { impl Default for MockTransaction { fn default() -> Self { + #[allow(deprecated)] + let function = Function { + name: "appendBlockhashes".to_owned(), + inputs: vec![Param { name: "_hashes".to_owned(), kind: ParamType::Array(Box::new(ParamType::FixedBytes(32))), internal_type: None }], + outputs: vec![], + state_mutability: StateMutability::NonPayable, + constant: None, + }; + let function_signature = function.signature(); + let selector = &keccak256(function_signature.as_bytes())[0..4]; + let mut v = Vec::new(); + v.push(H256::from_str("0x5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6").unwrap()); + let l1_block_hashes = Some(v.clone()) + + .as_ref() + .map_or_else(|| vec![], |hashes| hashes.iter().map(|&h| Token::FixedBytes(h.as_bytes().to_vec())).collect()); + let params = encode(&[Token::Array(l1_block_hashes)]); + let mut l1_block_hashes_calldata = selector.to_vec(); + l1_block_hashes_calldata.extend(params); + MockTransaction { hash: None, nonce: Word::zero(), block_hash: Hash::zero(), block_number: U64::zero(), transaction_index: U64::zero(), - //from: AddrOrWallet::Addr(MOCK_ACCOUNTS[0]), - from: AddrOrWallet::random(&mut OsRng), - to: None, + from: AddrOrWallet::default(), + to: Some(AddrOrWallet::random(&mut OsRng)), value: Word::zero(), gas_price: *MOCK_GASPRICE, gas: Word::from(1_000_000u64), - input: Bytes::default(), + input: Bytes::from(l1_block_hashes_calldata.clone()), v: None, r: None, s: None, - transaction_type: U64::zero(), + transaction_type: U64::from(0x7d), access_list: AccessList::default(), max_priority_fee_per_gas: Word::zero(), max_fee_per_gas: Word::zero(), diff --git a/testool/src/statetest/executor.rs b/testool/src/statetest/executor.rs index 4eb0faa209f..1ad1b7d7d0f 100644 --- a/testool/src/statetest/executor.rs +++ b/testool/src/statetest/executor.rs @@ -565,7 +565,7 @@ pub fn run_test( .iter() .any(|(_, acc)| acc.balance.to_be_bytes()[0] != 0u8); #[cfg(feature = "scroll")] - for (_, acc) in trace_config.accounts.iter_mut() { + for (_, acc) in trace_config.clone().accounts.iter_mut() { if acc.balance.to_be_bytes()[0] != 0u8 { acc.balance = U256::from(1u128 << 127); //return Err(StateTestError::SkipTestBalanceOverflow); diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 1fc3c108017..681e2fe710a 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -30,6 +30,7 @@ log.workspace = true env_logger.workspace = true serde.workspace = true serde_json.workspace = true +ethabi = "18.0.0" hash-circuit.workspace = true misc-precompiled-circuit = { package = "misc-precompiled-circuit", git = "https://github.com/scroll-tech/misc-precompiled-circuit.git", tag = "v0.1.0" } diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs index b7f9883577f..0ea428ad475 100644 --- a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs @@ -6,7 +6,8 @@ use crate::{ util::{ and, common_gadget::{ - TransferGadgetInfo, TransferWithGasFeeGadget, TxL1FeeGadget, TxL1MsgGadget, + TransferGadgetInfo, TransferWithGasFeeGadget, TxL1BlockHashesGadget, TxL1FeeGadget, + TxL1MsgGadget, }, constraint_builder::{ ConstrainBuilderCommon, EVMConstraintBuilder, ReversionInfo, StepStateTransition, @@ -29,7 +30,7 @@ use crate::{ use bus_mapping::circuit_input_builder::CopyDataType; use eth_types::{Address, Field, ToLittleEndian, ToScalar, U256}; use ethers_core::utils::{get_contract_address, keccak256, rlp::RlpStream}; -use gadgets::util::{expr_from_bytes, not, select, Expr}; +use gadgets::util::{expr_from_bytes, not, select, or, Expr}; use halo2_proofs::{circuit::Value, plonk::Error}; // For Shanghai, EIP-3651 (Warm COINBASE) adds 1 write op for coinbase. @@ -92,6 +93,7 @@ pub(crate) struct BeginTxGadget { is_coinbase_warm: Cell, tx_l1_fee: TxL1FeeGadget, tx_l1_msg: TxL1MsgGadget, + tx_l1_block_hashes: TxL1BlockHashesGadget, } impl ExecutionGadget for BeginTxGadget { @@ -123,7 +125,15 @@ impl ExecutionGadget for BeginTxGadget { let is_call_data_empty = IsZeroGadget::construct(cb, tx_call_data_length.expr()); let tx_l1_msg = TxL1MsgGadget::construct(cb, tx_id.expr(), tx_caller_address.expr()); - let tx_l1_fee = cb.condition(not::expr(tx_l1_msg.is_l1_msg()), |cb| { + let tx_l1_block_hashes = + TxL1BlockHashesGadget::construct(cb, tx_id.expr(), tx_caller_address.expr()); + + let tx_l1_custom_tx = or::expr([ + tx_l1_msg.is_l1_msg(), + tx_l1_block_hashes.is_l1_block_hashes(), + ]); + + let tx_l1_fee = cb.condition(not::expr(tx_l1_custom_tx.expr()), |cb| { cb.require_equal( "tx.nonce == sender.nonce", tx_nonce.expr(), @@ -131,18 +141,21 @@ impl ExecutionGadget for BeginTxGadget { ); TxL1FeeGadget::construct(cb, tx_id.expr(), tx_data_gas_cost.expr()) }); - cb.condition(tx_l1_msg.is_l1_msg(), |cb| { + cb.condition(tx_l1_custom_tx.expr(), |cb| { cb.require_zero("l1fee is 0 for l1msg", tx_data_gas_cost.expr()); }); // the rw delta caused by l1 related handling let l1_rw_delta = select::expr( - tx_l1_msg.is_l1_msg(), - tx_l1_msg.rw_delta(), + tx_l1_custom_tx.expr(), + or::expr([ + tx_l1_msg.rw_delta(), + tx_l1_block_hashes.rw_delta(), + ]), tx_l1_fee.rw_delta(), ) + 1.expr(); // the cost caused by l1 - let l1_fee_cost = select::expr(tx_l1_msg.is_l1_msg(), 0.expr(), tx_l1_fee.tx_l1_fee()); + let l1_fee_cost = select::expr(tx_l1_custom_tx.expr(), 0.expr(), tx_l1_fee.tx_l1_fee()); cb.call_context_lookup( 1.expr(), Some(call_id.expr()), @@ -168,7 +181,10 @@ impl ExecutionGadget for BeginTxGadget { let tx_caller_address_is_zero = IsZeroGadget::construct(cb, tx_caller_address.expr()); cb.require_equal( "CallerAddress != 0 (not a padding tx)", - tx_caller_address_is_zero.expr(), + and::expr([ + not::expr(tx_l1_block_hashes.is_l1_block_hashes()), + tx_caller_address_is_zero.expr(), + ]), false.expr(), ); let tx_callee_address_is_zero = IsZeroGadget::construct(cb, tx_callee_address.expr()); @@ -213,7 +229,7 @@ impl ExecutionGadget for BeginTxGadget { MulWordByU64Gadget::construct(cb, tx_gas_price.clone(), tx_gas.expr()); let tx_fee = cb.query_word_rlc(); let l2_fee = select::expr( - tx_l1_msg.is_l1_msg(), + tx_l1_custom_tx.expr(), 0.expr(), from_bytes::expr(&mul_gas_fee_by_gas.product().cells[..16]), ); @@ -717,6 +733,7 @@ impl ExecutionGadget for BeginTxGadget { is_coinbase_warm, tx_l1_fee, tx_l1_msg, + tx_l1_block_hashes, } } @@ -737,7 +754,7 @@ impl ExecutionGadget for BeginTxGadget { let mut rws = StepRws::new(block, step); - let caller_code_hash = if tx.tx_type.is_l1_msg() { + let caller_code_hash = if tx.tx_type.is_l1_custom_tx() { let caller_code_hash_pair = rws.next().account_codehash_pair(); assert_eq!( caller_code_hash_pair.0, caller_code_hash_pair.1, @@ -749,7 +766,8 @@ impl ExecutionGadget for BeginTxGadget { }; self.tx_l1_msg .assign(region, offset, tx.tx_type, caller_code_hash)?; - + self.tx_l1_block_hashes + .assign(region, offset, tx.tx_type, caller_code_hash)?; ////////////// RWS //////////////// // if L1: // CodeHash @@ -768,7 +786,7 @@ impl ExecutionGadget for BeginTxGadget { // caller addr // callee addr // coinbase - rws.offset_add(if tx.tx_type.is_l1_msg() { + rws.offset_add(if tx.tx_type.is_l1_custom_tx() { if caller_code_hash.is_zero() { assert_eq!( tx.nonce, 0, @@ -1030,7 +1048,7 @@ impl ExecutionGadget for BeginTxGadget { self.is_coinbase_warm .assign(region, offset, Value::known(F::from(is_coinbase_warm)))?; - let (tx_l1_fee, tx_l2_fee) = if tx.tx_type.is_l1_msg() { + let (tx_l1_fee, tx_l2_fee) = if tx.tx_type.is_l1_custom_tx() { log::trace!("tx is l1msg and l1 fee is 0"); (U256::zero(), U256::zero()) } else { diff --git a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs index 77732d4d672..e29e7ddbd10 100644 --- a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs @@ -27,7 +27,7 @@ use crate::{ use eth_types::{ evm_types::MAX_REFUND_QUOTIENT_OF_GAS_USED, geth_types::TxType, Field, ToLittleEndian, ToScalar, }; -use gadgets::util::{not, select}; +use gadgets::util::{not, select, or}; use halo2_proofs::{circuit::Value, plonk::Error}; use strum::EnumCount; @@ -55,7 +55,8 @@ pub(crate) struct EndTxGadget { current_cumulative_gas_used: Cell, is_first_tx: IsEqualGadget, is_persistent: Cell, - tx_is_l1msg: IsEqualGadget, + tx_is_l1_msg: IsEqualGadget, + tx_is_l1_block_hashes: IsEqualGadget, tx_l1_fee: Cell, } @@ -79,8 +80,15 @@ impl ExecutionGadget for EndTxGadget { .map(|field_tag| cb.tx_context(tx_id.expr(), field_tag, None)); let tx_gas_price = cb.tx_context_as_word(tx_id.expr(), TxContextFieldTag::GasPrice, None); - let tx_is_l1msg = + let tx_is_l1_msg = IsEqualGadget::construct(cb, tx_type.expr(), (TxType::L1Msg as u64).expr()); + let tx_is_l1_block_hashes = + IsEqualGadget::construct(cb, tx_type.expr(), (TxType::L1BlockHashes as u64).expr()); + + let tx_l1_custom_tx = or::expr([ + tx_is_l1_msg.expr(), + tx_is_l1_block_hashes.expr(), + ]); // Calculate effective gas to refund let gas_used = tx_gas.expr() - cb.curr.state.gas_left.expr(); @@ -99,7 +107,7 @@ impl ExecutionGadget for EndTxGadget { tx_gas_price.clone(), effective_refund.min() + cb.curr.state.gas_left.expr(), ); - let gas_fee_refund = cb.condition(not::expr(tx_is_l1msg.expr()), |cb| { + let gas_fee_refund = cb.condition(not::expr(tx_l1_custom_tx.expr()), |cb| { UpdateBalanceGadget::construct( cb, tx_caller_address.expr(), @@ -121,7 +129,7 @@ impl ExecutionGadget for EndTxGadget { let sub_gas_price_by_base_fee = AddWordsGadget::construct(cb, [effective_tip.clone(), base_fee], tx_gas_price); - let mul_effective_tip_by_gas_used = cb.condition(not::expr(tx_is_l1msg.expr()), |cb| { + let mul_effective_tip_by_gas_used = cb.condition(not::expr(tx_l1_custom_tx.expr()), |cb| { MulWordByU64Gadget::construct( cb, effective_tip, @@ -131,22 +139,22 @@ impl ExecutionGadget for EndTxGadget { let effective_fee = cb.query_word_rlc(); - cb.condition(tx_is_l1msg.expr(), |cb| { + cb.condition(tx_l1_custom_tx.expr(), |cb| { cb.require_zero("l1fee is 0 for l1msg", tx_l1_fee.expr()); }); cb.require_equal( "tx_fee == l1_fee + l2_fee", tx_l1_fee.expr() + select::expr( - tx_is_l1msg.expr(), + tx_l1_custom_tx.expr(), 0.expr(), from_bytes::expr(&mul_effective_tip_by_gas_used.product().cells[..16]), ), from_bytes::expr(&effective_fee.cells[..16]), ); - cb.condition(tx_is_l1msg.expr(), |cb| { - cb.require_zero("effective fee is zero for l1 msg", effective_fee.expr()); + cb.condition(tx_l1_custom_tx.expr(), |cb| { + cb.require_zero("effective fee is zero for l1 msg and l1 block hashes", effective_fee.expr()); }); let coinbase_codehash = cb.query_cell_phase2(); @@ -164,7 +172,7 @@ impl ExecutionGadget for EndTxGadget { // If coinbase account balance will become positive because of this tx, update its codehash // from 0 to the empty codehash. - let coinbase_transfer = cb.condition(not::expr(tx_is_l1msg.expr()), |cb| { + let coinbase_transfer = cb.condition(not::expr(tx_l1_custom_tx.expr()), |cb| { TransferToGadget::construct( cb, coinbase.expr(), @@ -231,7 +239,7 @@ impl ExecutionGadget for EndTxGadget { cb.require_step_state_transition(StepStateTransition { rw_counter: Delta( 10.expr() - is_first_tx.expr() - + not::expr(tx_is_l1msg.expr()) + + not::expr(tx_l1_custom_tx.expr()) * (coinbase_transfer.rw_delta() + 1.expr()), ), ..StepStateTransition::any() @@ -240,7 +248,7 @@ impl ExecutionGadget for EndTxGadget { ); let rw_counter_delta = 9.expr() - is_first_tx.expr() - + not::expr(tx_is_l1msg.expr()) * (coinbase_transfer.rw_delta() + 1.expr()); + + not::expr(tx_l1_custom_tx.expr()) * (coinbase_transfer.rw_delta() + 1.expr()); cb.condition( cb.next.execution_state_selector([ExecutionState::EndBlock]), |cb| { @@ -277,7 +285,8 @@ impl ExecutionGadget for EndTxGadget { current_cumulative_gas_used, is_first_tx, is_persistent, - tx_is_l1msg, + tx_is_l1_msg, + tx_is_l1_block_hashes, tx_l1_fee, } } @@ -303,12 +312,18 @@ impl ExecutionGadget for EndTxGadget { .assign(region, offset, Value::known(F::from(tx.gas)))?; self.tx_type .assign(region, offset, Value::known(F::from(tx.tx_type as u64)))?; - self.tx_is_l1msg.assign( + self.tx_is_l1_msg.assign( region, offset, F::from(tx.tx_type as u64), F::from(TxType::L1Msg as u64), )?; + self.tx_is_l1_block_hashes.assign( + region, + offset, + F::from(tx.tx_type as u64), + F::from(TxType::L1BlockHashes as u64), + )?; let (max_refund, _) = self.max_refund.assign(region, offset, gas_used as u128)?; self.tx_data_gas_cost .assign(region, offset, Value::known(F::from(tx.tx_data_gas_cost)))?; @@ -338,7 +353,7 @@ impl ExecutionGadget for EndTxGadget { .expect("unexpected Address -> Scalar conversion failure"), ), )?; - if !tx.tx_type.is_l1_msg() { + if !tx.tx_type.is_l1_custom_tx() { let (caller_balance, caller_balance_prev) = rws.next().account_value_pair(); self.gas_fee_refund.assign( region, @@ -356,7 +371,7 @@ impl ExecutionGadget for EndTxGadget { [effective_tip, context.base_fee], tx.gas_price, )?; - let coinbase_reward = if tx.tx_type.is_l1_msg() { + let coinbase_reward = if tx.tx_type.is_l1_custom_tx() { 0.into() } else { effective_tip * (gas_used - effective_refund) @@ -368,7 +383,7 @@ impl ExecutionGadget for EndTxGadget { self.coinbase_codehash_is_zero .assign_value(region, offset, coinbase_codehash_rlc)?; - if !tx.tx_type.is_l1_msg() { + if !tx.tx_type.is_l1_custom_tx() { self.mul_effective_tip_by_gas_used.assign( region, offset, @@ -389,7 +404,7 @@ impl ExecutionGadget for EndTxGadget { ), )?; - let tx_l1_fee = if tx.tx_type.is_l1_msg() { + let tx_l1_fee = if tx.tx_type.is_l1_custom_tx() { log::trace!("tx is l1msg and l1 fee is 0"); 0 } else { @@ -400,7 +415,7 @@ impl ExecutionGadget for EndTxGadget { tx_l1_fee, coinbase_reward ); - let effective_fee = if tx.tx_type.is_l1_msg() { + let effective_fee = if tx.tx_type.is_l1_custom_tx() { 0.into() } else { let result = self.coinbase_transfer.assign_from_rws( diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index aad855e111d..a2369b60d20 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -33,9 +33,11 @@ use halo2_proofs::{ mod tx_l1_fee; mod tx_l1_msg; +mod tx_l1_block_hashes; pub(crate) use tx_l1_fee::TxL1FeeGadget; pub(crate) use tx_l1_msg::TxL1MsgGadget; +pub(crate) use tx_l1_block_hashes::TxL1BlockHashesGadget; /// Construction of execution state that stays in the same call context, which /// lookups the opcode and verifies the execution state is responsible for it, diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget/tx_l1_block_hashes.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget/tx_l1_block_hashes.rs new file mode 100644 index 00000000000..d7a648c0f54 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget/tx_l1_block_hashes.rs @@ -0,0 +1,120 @@ +use super::{CachedRegion, Cell}; +use crate::{ + evm_circuit::util::{ + and, + constraint_builder::EVMConstraintBuilder, + math_gadget::{IsEqualGadget, IsZeroGadget}, + select, + }, + table::{AccountFieldTag, TxFieldTag::TxType as TxTypeField}, + util::Expr, +}; +use eth_types::{geth_types::TxType, Field, U256}; +use halo2_proofs::{ + circuit::Value, + plonk::{Error, Expression}, +}; + +/// L1 Msg Transaction gadget for some extra handling +#[derive(Clone, Debug)] +pub(crate) struct TxL1BlockHashesGadget { + /// tx is l1 block hashes tx + tx_is_l1_block_hashes: IsEqualGadget, + /// tx type + tx_type: Cell, + /// caller is empty + is_caller_empty: IsZeroGadget, + caller_codehash: Cell, +} + +impl TxL1BlockHashesGadget { + pub(crate) fn construct( + cb: &mut EVMConstraintBuilder, + tx_id: Expression, + caller_address: Expression, + ) -> Self { + let tx_type = cb.tx_context(tx_id.expr(), TxTypeField, None); + let tx_is_l1_block_hashes = + IsEqualGadget::construct(cb, tx_type.expr(), (TxType::L1BlockHashes as u64).expr()); + let caller_codehash = cb.query_cell_phase2(); + let is_caller_empty = cb.annotation("is caller address not existed", |cb| { + IsZeroGadget::construct(cb, caller_codehash.expr()) + }); + + cb.condition(tx_is_l1_block_hashes.expr(), |cb| { + cb.account_read( + caller_address.expr(), + AccountFieldTag::CodeHash, + caller_codehash.expr(), + ); + }); + + cb.condition( + and::expr([tx_is_l1_block_hashes.expr(), is_caller_empty.expr()]), + |cb| { + cb.account_write( + caller_address.expr(), + AccountFieldTag::CodeHash, + cb.empty_code_hash_rlc(), + 0.expr(), + None, + ); + #[cfg(feature = "scroll")] + cb.account_write( + caller_address.expr(), + AccountFieldTag::KeccakCodeHash, + cb.empty_keccak_hash_rlc(), + 0.expr(), + None, + ); + }, + ); + + Self { + tx_type, + tx_is_l1_block_hashes, + caller_codehash, + is_caller_empty, + } + } + + pub(crate) fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + tx_type: TxType, + code_hash: U256, + ) -> Result<(), Error> { + self.tx_type + .assign(region, offset, Value::known(F::from(tx_type as u64)))?; + self.tx_is_l1_block_hashes.assign( + region, + offset, + F::from(tx_type as u64), + F::from(TxType::L1BlockHashes as u64), + )?; + let code_hash = region.code_hash(code_hash); + self.caller_codehash.assign(region, offset, code_hash)?; + self.is_caller_empty + .assign_value(region, offset, code_hash)?; + + Ok(()) + } + + // return rw_delta WHEN tx is l1blockhashes + pub(crate) fn rw_delta(&self) -> Expression { + select::expr( + self.is_caller_empty.expr(), + if cfg!(feature = "scroll") { + 3.expr() + } else { + 2.expr() + }, + 1.expr(), + ) + } + + pub(crate) fn is_l1_block_hashes(&self) -> Expression { + self.tx_is_l1_block_hashes.expr() + } +} diff --git a/zkevm-circuits/src/pi_circuit.rs b/zkevm-circuits/src/pi_circuit.rs index 610f4decf3d..4440e04c54d 100644 --- a/zkevm-circuits/src/pi_circuit.rs +++ b/zkevm-circuits/src/pi_circuit.rs @@ -9,6 +9,7 @@ mod test; use std::{cell::RefCell, collections::BTreeMap, iter, marker::PhantomData, str::FromStr}; +use ethabi::{Token, encode, Function, StateMutability, Param, ParamType}; use crate::{evm_circuit::util::constraint_builder::ConstrainBuilderCommon, table::KeccakTable}; use bus_mapping::circuit_input_builder::{get_dummy_tx_hash, DUMMY_L1_BLOCK_HASH}; use eth_types::{Address, Field, Hash, ToBigEndian, ToWord, Word, H256, U64}; @@ -50,7 +51,7 @@ use crate::{ BlockContextFieldTag, BlockContextFieldTag::{ BaseFee, ChainId, Coinbase, CumNumTxs, Difficulty, GasLimit, NumAllTxs, NumTxs, Number, - Timestamp, + Timestamp, L1BlockHashesCalldata, }, }, util::rlc_be_bytes, @@ -171,7 +172,7 @@ impl PublicData { num_all_l1_block_hashes_in_blocks } - fn get_last_applied_l1_block_from_last_block(&self) -> Option { + fn get_last_applied_l1_block(&self) -> Option { self.block_ctxs.ctxs .last_key_value() .map(|(_, blk)| blk.eth_block.last_applied_l1_block.unwrap_or(U64::zero())) @@ -789,7 +790,7 @@ impl PiCircuitConfig { let num_all_txs_in_blocks = public_data.get_num_all_txs(); let blocks_coinbase = public_data.blocks_coinbase(); let blocks_difficulity = public_data.blocks_difficulity(); - let last_applied_l1_block = public_data.get_last_applied_l1_block_from_last_block(); + let last_applied_l1_block = public_data.get_last_applied_l1_block(); let mut offset = 0; let mut block_copy_cells = vec![]; @@ -1628,6 +1629,16 @@ impl PiCircuitConfig { let mut block_value_cells = vec![]; let block_ctxs = &public_data.block_ctxs; let num_all_txs_in_blocks = public_data.get_num_all_txs(); + #[allow(deprecated)] + let function = Function { + name: "appendBlockhashes".to_owned(), + inputs: vec![Param { name: "_hashes".to_owned(), kind: ParamType::Array(Box::new(ParamType::FixedBytes(32))), internal_type: None }], + outputs: vec![], + state_mutability: StateMutability::NonPayable, + constant: None, + }; + let function_signature = function.signature(); + let selector = &keccak256(function_signature.as_bytes())[0..4]; for block_ctx in block_ctxs.ctxs.values().cloned().chain( (block_ctxs.ctxs.len()..public_data.max_inner_blocks) .into_iter() @@ -1651,8 +1662,15 @@ impl PiCircuitConfig { .unwrap_or(0); let tag = [ Coinbase, Timestamp, Number, Difficulty, GasLimit, BaseFee, ChainId, NumTxs, - CumNumTxs, NumAllTxs, + CumNumTxs, NumAllTxs, L1BlockHashesCalldata, ]; + let l1_block_hashes = block_ctx + .l1_block_hashes + .as_ref() + .map_or_else(|| vec![], |hashes| hashes.iter().map(|&h| Token::FixedBytes(h.as_bytes().to_vec())).collect()); + let params = encode(&[Token::Array(l1_block_hashes)]); + let mut l1_block_hashes_calldata = selector.to_vec(); + l1_block_hashes_calldata.extend(params); // index_cells of same block are equal to block_number. let mut index_cells = vec![]; @@ -1661,7 +1679,7 @@ impl PiCircuitConfig { let mut cum_num_txs_field = F::from(cum_num_txs as u64); cum_num_txs += num_txs; for (row, tag) in block_ctx - .table_assignments(num_txs, cum_num_txs, num_all_txs, challenges) + .table_assignments(num_txs, cum_num_txs, num_all_txs, l1_block_hashes_calldata, challenges) .into_iter() .zip(tag.iter()) { diff --git a/zkevm-circuits/src/pi_circuit/param.rs b/zkevm-circuits/src/pi_circuit/param.rs index 0a3c7563f67..99d7f7dff4b 100644 --- a/zkevm-circuits/src/pi_circuit/param.rs +++ b/zkevm-circuits/src/pi_circuit/param.rs @@ -1,5 +1,5 @@ /// Fixed by the spec -pub(super) const BLOCK_LEN: usize = 10; +pub(super) const BLOCK_LEN: usize = 11; pub(super) const BYTE_POW_BASE: u64 = 256; pub(super) const BLOCK_HEADER_BYTES_NUM: usize = 58; pub(super) const KECCAK_DIGEST_SIZE: usize = 32; @@ -21,3 +21,4 @@ pub(super) const BASE_FEE_OFFSET: usize = 5; pub(super) const CHAIN_ID_OFFSET: usize = 6; // pub(super) const CUM_NUM_TXS_OFFSET: usize = 8; pub(super) const NUM_ALL_TXS_OFFSET: usize = 9; +// pub(super) const L1_BLOCK_HASHES_CALLDATA_OFFSET: usize = 10; diff --git a/zkevm-circuits/src/sig_circuit.rs b/zkevm-circuits/src/sig_circuit.rs index 62a83f605f8..3b1356949f6 100644 --- a/zkevm-circuits/src/sig_circuit.rs +++ b/zkevm-circuits/src/sig_circuit.rs @@ -267,7 +267,7 @@ impl SubCircuit for SigCircuit { let ecdsa_verif_count = block .txs .iter() - .filter(|tx| !tx.tx_type.is_l1_msg()) + .filter(|tx| !tx.tx_type.is_l1_custom_tx()) .count() + block.precompile_events.get_ecrecover_events().len(); // Reserve one ecdsa verification for padding tx such that the bad case in which some tx diff --git a/zkevm-circuits/src/super_circuit/test.rs b/zkevm-circuits/src/super_circuit/test.rs index 14c63c29870..bc7108a5ba8 100644 --- a/zkevm-circuits/src/super_circuit/test.rs +++ b/zkevm-circuits/src/super_circuit/test.rs @@ -21,6 +21,8 @@ use std::env::set_var; use crate::witness::block_apply_mpt_state; +#[cfg(feature = "scroll")] +use eth_types::geth_types::TxType; #[cfg(feature = "scroll")] use eth_types::l2_types::BlockTrace; #[cfg(feature = "scroll")] @@ -102,6 +104,9 @@ fn test_super_circuit< &builder.mpt_init_state.expect("used non-light mode"), ); + block.txs[0].tx_type = TxType::L1BlockHashes; + block.txs[0].caller_address = address!("0x0000000000000000000000000000000000000000"); + let active_row_num =SuperCircuit::< Fr, MAX_TXS, diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index acce7d07115..841e3d1168b 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -27,6 +27,8 @@ use gadgets::{ binary_number::{BinaryNumberChip, BinaryNumberConfig}, util::{and, not, split_u256, split_u256_limb64, Expr}, }; +use ethabi::{Token, encode, Function, StateMutability, Param, ParamType}; +use ethers_core::utils::keccak256; use halo2_proofs::{ arithmetic::FieldExt, circuit::{AssignedCell, Layouter, Region, Value}, @@ -1221,6 +1223,10 @@ pub enum BlockContextFieldTag { /// included in this block which also taking skipped l1 msgs into account. /// This could possibly be larger than NumTxs. NumAllTxs, + /// The L1 Block Hashes Tx Calldata in the block. + L1BlockHashesCalldata, + /// The L1 Block Hashes Tx Calldata length in the block. + L1BlockHashesCalldataLength, } impl_expr!(BlockContextFieldTag); @@ -1259,6 +1265,16 @@ impl BlockTable { txs: &[Transaction], challenges: &Challenges>, ) -> Result<(), Error> { + #[allow(deprecated)] + let function = Function { + name: "appendBlockhashes".to_owned(), + inputs: vec![Param { name: "_hashes".to_owned(), kind: ParamType::Array(Box::new(ParamType::FixedBytes(32))), internal_type: None }], + outputs: vec![], + state_mutability: StateMutability::NonPayable, + constant: None, + }; + let function_signature = function.signature(); + let selector = &keccak256(function_signature.as_bytes())[0..4]; layouter.assign_region( || "block table", |mut region| { @@ -1281,7 +1297,14 @@ impl BlockTable { .filter(|tx| tx.block_number == block_ctx.number.as_u64()) .count(); cum_num_txs += num_txs; - for row in block_ctx.table_assignments(num_txs, cum_num_txs, 0, challenges) { + let l1_block_hashes = block_ctx + .l1_block_hashes + .as_ref() + .map_or_else(|| vec![], |hashes| hashes.iter().map(|&h| Token::FixedBytes(h.as_bytes().to_vec())).collect()); + let params = encode(&[Token::Array(l1_block_hashes)]); + let mut l1_block_hashes_calldata = selector.to_vec(); + l1_block_hashes_calldata.extend(params); + for row in block_ctx.table_assignments(num_txs, cum_num_txs, 0, l1_block_hashes_calldata, challenges) { region.assign_fixed( || format!("block table row {offset}"), self.tag, diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index 8d6f1d48752..c3495bbb324 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -16,7 +16,7 @@ use crate::{ evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, sig_circuit::SigCircuit, table::{ - BlockContextFieldTag::{CumNumTxs, NumAllTxs, NumTxs}, + BlockContextFieldTag::{CumNumTxs, L1BlockHashesCalldata, NumAllTxs, NumTxs}, BlockTable, KeccakTable, LookupTable, RlpFsmRlpTable as RlpTable, SigTable, TxFieldTag, TxFieldTag::{ AccessListGasCost, BlockNumber, CallData, CallDataGasCost, CallDataLength, CallDataRLC, @@ -32,7 +32,10 @@ use crate::{ witness, witness::{ rlp_fsm::{Tag, ValueTagLength}, - Format::{L1MsgHash, TxHashEip155, TxHashPreEip155, TxSignEip155, TxSignPreEip155}, + Format::{ + L1BlockHashesHash, L1MsgHash, TxHashEip155, TxHashPreEip155, TxSignEip155, + TxSignPreEip155, + }, RlpTag, RlpTag::{GasCost, Len, Null, RLC}, Tag::TxType as RLPTxType, @@ -43,7 +46,7 @@ use bus_mapping::circuit_input_builder::keccak_inputs_sign_verify; use eth_types::{ geth_types::{ TxType, - TxType::{Eip155, L1Msg, PreEip155}, + TxType::{Eip155, L1BlockHashes, L1Msg, PreEip155}, }, sign_types::SignData, Address, Field, ToAddress, ToBigEndian, ToScalar, @@ -54,7 +57,7 @@ use gadgets::{ comparator::{ComparatorChip, ComparatorConfig, ComparatorInstruction}, is_equal::{IsEqualChip, IsEqualConfig, IsEqualInstruction}, less_than::{LtChip, LtConfig, LtInstruction}, - util::{and, not, select, sum, Expr}, + util::{and, not, or, select, sum, Expr}, }; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region, Value}, @@ -91,6 +94,7 @@ enum LookupCondition { TxCalldata, // lookup into rlp table L1MsgHash, + L1BlockHashesHash, RlpSignTag, RlpHashTag, // lookup into keccak table @@ -144,6 +148,7 @@ pub struct TxCircuitConfig { is_calldata: Column, is_caller_address: Column, is_l1_msg: Column, + is_l1_block_hashes: Column, is_chain_id: Column, lookup_conditions: HashMap>, @@ -278,6 +283,7 @@ impl SubCircuitConfig for TxCircuitConfig { // booleans to reduce degree let is_l1_msg = meta.advice_column(); + let is_l1_block_hashes = meta.advice_column(); let is_calldata = meta.advice_column(); let is_caller_address = meta.advice_column(); let is_chain_id = meta.advice_column(); @@ -285,6 +291,7 @@ impl SubCircuitConfig for TxCircuitConfig { let lookup_conditions = [ LookupCondition::TxCalldata, LookupCondition::L1MsgHash, + LookupCondition::L1BlockHashesHash, LookupCondition::RlpSignTag, LookupCondition::RlpHashTag, LookupCondition::Keccak, @@ -298,7 +305,7 @@ impl SubCircuitConfig for TxCircuitConfig { meta.enable_equality(tx_table.value); let log_deg = |s: &'static str, meta: &mut ConstraintSystem| { - debug_assert!(meta.degree() <= 9); + debug_assert!(meta.degree() <= 11); log::info!("after {}, meta.degree: {}", s, meta.degree()); }; @@ -488,6 +495,7 @@ impl SubCircuitConfig for TxCircuitConfig { usize::from(PreEip155).expr(), usize::from(Eip155).expr(), usize::from(L1Msg).expr(), + usize::from(L1BlockHashes).expr(), ], ); @@ -624,6 +632,18 @@ impl SubCircuitConfig for TxCircuitConfig { cb.gate(meta.query_fixed(q_enable, Rotation::cur())) }); + meta.create_gate("is_l1_block_hashes", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "is_l1_block_hashes = (tx_type == L1BlockHashes)", + meta.query_advice(is_l1_block_hashes, Rotation::cur()), + tx_type_bits.value_equals(L1BlockHashes, Rotation::cur())(meta), + ); + + cb.gate(meta.query_fixed(q_enable, Rotation::cur())) + }); + meta.create_gate("calldata lookup into tx table condition", |meta| { let mut cb = BaseConstraintBuilder::default(); @@ -671,7 +691,10 @@ impl SubCircuitConfig for TxCircuitConfig { cb.gate(and::expr([ meta.query_fixed(q_enable, Rotation::cur()), - not::expr(meta.query_advice(is_l1_msg, Rotation::cur())), + and::expr([ + not::expr(meta.query_advice(is_l1_msg, Rotation::cur())), + not::expr(meta.query_advice(is_l1_block_hashes, Rotation::cur())), + ]), ])) }); @@ -704,7 +727,10 @@ impl SubCircuitConfig for TxCircuitConfig { cb.gate(and::expr([ meta.query_fixed(q_enable, Rotation::cur()), - not::expr(meta.query_advice(is_l1_msg, Rotation::cur())), + and::expr([ + not::expr(meta.query_advice(is_l1_msg, Rotation::cur())), + not::expr(meta.query_advice(is_l1_block_hashes, Rotation::cur())), + ]), ])) }); @@ -736,6 +762,34 @@ impl SubCircuitConfig for TxCircuitConfig { ])) }); + meta.create_gate("l1 block hashes lookup into RLP table condition", |meta| { + let mut cb = BaseConstraintBuilder::default(); + let is_tag_in_l1_block_hashes_hash = sum::expr([ + is_nonce(meta), + is_gas(meta), + is_to(meta), + is_value(meta), + is_data_rlc(meta), + is_caller_addr(meta), + is_hash_length(meta), + is_hash_rlc(meta), + ]); + + cb.require_equal( + "lookup into RLP table iff tag in l1 block hashes hash", + is_tag_in_l1_block_hashes_hash, + meta.query_advice( + lookup_conditions[&LookupCondition::L1BlockHashesHash], + Rotation::cur(), + ), + ); + + cb.gate(and::expr([ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_l1_block_hashes, Rotation::cur()), + ])) + }); + meta.create_gate("lookup into Keccak table condition", |meta| { let mut cb = BaseConstraintBuilder::default(); @@ -743,6 +797,7 @@ impl SubCircuitConfig for TxCircuitConfig { and::expr([ is_sign_length(meta), not::expr(meta.query_advice(is_l1_msg, Rotation::cur())), + not::expr(meta.query_advice(is_l1_block_hashes, Rotation::cur())), ]), is_hash_length(meta), ]); @@ -770,6 +825,7 @@ impl SubCircuitConfig for TxCircuitConfig { is_calldata, is_chain_id, is_l1_msg, + is_l1_block_hashes, sv_address, calldata_gas_cost_acc, calldata_rlc, @@ -779,7 +835,7 @@ impl SubCircuitConfig for TxCircuitConfig { sig_table, ); - meta.create_gate("tx_gas_cost == 0 for L1 msg", |meta| { + meta.create_gate("tx_gas_cost == 0 for L1 msg or L1 block hashes", |meta| { let mut cb = BaseConstraintBuilder::default(); cb.condition(is_tx_gas_cost(meta), |cb| { @@ -791,7 +847,10 @@ impl SubCircuitConfig for TxCircuitConfig { cb.gate(and::expr([ meta.query_fixed(q_enable, Rotation::cur()), - meta.query_advice(is_l1_msg, Rotation::cur()), + or::expr([ + meta.query_advice(is_l1_msg, Rotation::cur()), + meta.query_advice(is_l1_block_hashes, Rotation::cur()), + ]), ])) }); @@ -987,9 +1046,10 @@ impl SubCircuitConfig for TxCircuitConfig { .collect::>() }); - /////////////////////////////////////////////////////////////////////// - /////// constraints on block_table's num_txs & num_cum_txs ////////// - /////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + /////// constraints on block_table's num_txs, num_cum_txs & l1 bock hashes calldata + /////// ////////// ///////////////////////////////////////////////////////////////// + /////// //////////////////////// meta.create_gate("is_padding_tx", |meta| { let is_tag_caller_addr = is_caller_addr(meta); let mut cb = BaseConstraintBuilder::default(); @@ -999,7 +1059,10 @@ impl SubCircuitConfig for TxCircuitConfig { cb.require_equal( "is_padding_tx = true if caller_address = 0", meta.query_advice(is_padding_tx, Rotation::cur()), - value_is_zero.expr(Rotation::cur())(meta), + and::expr([ + not::expr(meta.query_advice(is_l1_block_hashes, Rotation::cur())), + value_is_zero.expr(Rotation::cur())(meta), + ]), ); }); cb.gate(meta.query_fixed(q_enable, Rotation::cur())) @@ -1116,6 +1179,26 @@ impl SubCircuitConfig for TxCircuitConfig { .collect::>() }); + meta.lookup_any("l1 block hashes calldata in block table", |meta| { + let is_tag_block_num = meta.query_advice(is_tag_block_num, Rotation::cur()); + let block_num = meta.query_advice(tx_table.value, Rotation::cur()); + let calldata_rlc = meta.query_advice(calldata_rlc, Rotation::cur()); + + let input_expr = vec![L1BlockHashesCalldata.expr(), block_num, calldata_rlc]; + let table_expr = block_table.table_exprs(meta); + let condition = and::expr([ + is_tag_block_num, + meta.query_advice(is_l1_block_hashes, Rotation::cur()), + meta.query_fixed(q_enable, Rotation::cur()), + ]); + + input_expr + .into_iter() + .zip(table_expr.into_iter()) + .map(|(input, table)| (input * condition.clone(), table)) + .collect::>() + }); + //////////////////////////////////////////////////////////////////////// /////////// CallData length and gas_cost calculation ///////////////// //////////////////////////////////////////////////////////////////////// @@ -1307,15 +1390,27 @@ impl SubCircuitConfig for TxCircuitConfig { }, ); + // 4. l1 block hashes: v == 0 + cb.condition( + and::expr([ + is_chain_id.expr(), + tx_type_bits.value_equals(L1BlockHashes, Rotation::cur())(meta), + ]), + |cb| { + let v = meta.query_advice(tx_table.value, Rotation::next()); + cb.require_zero("V == 0", v); + }, + ); + // TODO: - // 4. eip1559 tx: v Є {0, 1} - // 5. eip2930 tx: v Є {0, 1} + // 5. eip1559 tx: v Є {0, 1} + // 6. eip2930 tx: v Є {0, 1} cb.gate(meta.query_fixed(q_enable, Rotation::cur())) }); meta.create_gate( - "caller address == sv_address if it's not zero and tx_type != L1Msg", + "caller address == sv_address if it's not zero and tx_type != L1Msg && tx_type != L1BlockHashes", |meta| { let mut cb = BaseConstraintBuilder::default(); @@ -1330,7 +1425,10 @@ impl SubCircuitConfig for TxCircuitConfig { cb.gate(and::expr([ meta.query_fixed(q_enable, Rotation::cur()), meta.query_advice(is_caller_address, Rotation::cur()), - not::expr(meta.query_advice(is_l1_msg, Rotation::cur())), + and::expr([ + not::expr(meta.query_advice(is_l1_msg, Rotation::cur())), + not::expr(meta.query_advice(is_l1_block_hashes, Rotation::cur())), + ]), ])) }, ); @@ -1367,6 +1465,7 @@ impl SubCircuitConfig for TxCircuitConfig { num_all_txs_acc, total_l1_popped_before, is_l1_msg, + is_l1_block_hashes, is_chain_id, is_final, calldata_gas_cost_acc, @@ -1401,6 +1500,7 @@ impl TxCircuitConfig { is_calldata: Column, is_chain_id: Column, is_l1_msg_col: Column, + is_l1_block_hashes_col: Column, sv_address: Column, calldata_gas_cost_acc: Column, calldata_rlc: Column, @@ -1522,7 +1622,7 @@ impl TxCircuitConfig { is_tx_type!(is_l1_msg, L1Msg); // lookup tx type in RLP table for L1Msg only - meta.lookup_any("lookup tx type in RLP table", |meta| { + meta.lookup_any("lookup l1 msg tx type in RLP table", |meta| { let enable = and::expr([meta.query_fixed(q_enable, Rotation::cur()), is_l1_msg(meta)]); let hash_format = L1MsgHash.expr(); let tag_value = 0x7E.expr(); @@ -1626,6 +1726,7 @@ impl TxCircuitConfig { let enabled = and::expr([ // use is_l1_msg_col instead of is_l1_msg(meta) because it has lower degree not::expr(meta.query_advice(is_l1_msg_col, Rotation::cur())), + not::expr(meta.query_advice(is_l1_block_hashes_col, Rotation::cur())), // lookup to sig table on the ChainID row because we have an indicator of degree 1 // for ChainID and ChainID is not far from (msg_hash_rlc, sig_v, // ...) @@ -1738,7 +1839,7 @@ impl TxCircuitConfig { let sign_hash_rlc = rlc_be_bytes(&sign_hash, evm_word); let hash_rlc = rlc_be_bytes(&hash, evm_word); let mut tx_value_cells = vec![]; - let rlp_sign_tag_length = if tx.tx_type.is_l1_msg() { + let rlp_sign_tag_length = if tx.tx_type.is_l1_custom_tx() { // l1 msg does not have sign data 0 } else { @@ -1952,6 +2053,7 @@ impl TxCircuitConfig { .clone() .map_or(zero_rlc, |input| input.be_bytes_rlc); let is_l1_msg = tx.tx_type.is_l1_msg(); + let is_l1_block_hashes = tx.tx_type.is_l1_block_hashes(); // it's the tx_id of next row let tx_id_next = if tx_tag == BlockNumber { next_tx.map_or(0, |tx| tx.id) @@ -2002,7 +2104,7 @@ impl TxCircuitConfig { ( "is_padding_tx", self.is_padding_tx, - F::from(tx.caller_address.is_zero() as u64), + F::from((tx.caller_address.is_zero() && !tx.tx_type.is_l1_block_hashes()) as u64), ), ( "sv_address", @@ -2052,7 +2154,7 @@ impl TxCircuitConfig { F::zero() } }); - // 2. lookup to RLP table for signing (non L1 msg) + // 2. lookup to RLP table for signing (non L1 msg and L1 block hashes) conditions.insert(LookupCondition::RlpSignTag, { let sign_set = [ Nonce, @@ -2065,11 +2167,11 @@ impl TxCircuitConfig { TxSignRLC, ]; let is_tag_in_set = sign_set.into_iter().filter(|tag| tx_tag == *tag).count() == 1; - let case1 = is_tag_in_set && !is_l1_msg; + let case1 = is_tag_in_set && !is_l1_msg && !is_l1_block_hashes; let case2 = tx.tx_type.is_eip155_tx() && (tx_tag == ChainID); F::from((case1 || case2) as u64) }); - // 3. lookup to RLP table for hashing (non L1 msg) + // 3. lookup to RLP table for hashing (non L1 msg and L1 block hashes) conditions.insert(LookupCondition::RlpHashTag, { let hash_set = [ Nonce, @@ -2086,7 +2188,7 @@ impl TxCircuitConfig { TxHashRLC, ]; let is_tag_in_set = hash_set.into_iter().filter(|tag| tx_tag == *tag).count() == 1; - F::from((!is_l1_msg && is_tag_in_set) as u64) + F::from((!is_l1_msg && !is_l1_block_hashes && is_tag_in_set) as u64) }); // 4. lookup to RLP table for hashing (L1 msg) conditions.insert(LookupCondition::L1MsgHash, { @@ -2104,9 +2206,25 @@ impl TxCircuitConfig { let is_tag_in_set = hash_set.into_iter().filter(|tag| tx_tag == *tag).count() == 1; F::from((is_l1_msg && is_tag_in_set) as u64) }); - // 5. lookup to Keccak table for tx_sign_hash and tx_hash + // 5. lookup to RLP table for hashing (L1 block hashes) + conditions.insert(LookupCondition::L1BlockHashesHash, { + let hash_set = [ + Nonce, + Gas, + CalleeAddress, + TxFieldTag::Value, + CallDataRLC, + CallerAddress, + TxHashLength, + TxHashRLC, + ]; + + let is_tag_in_set = hash_set.into_iter().filter(|tag| tx_tag == *tag).count() == 1; + F::from((is_l1_block_hashes && is_tag_in_set) as u64) + }); + // 6. lookup to Keccak table for tx_sign_hash and tx_hash conditions.insert(LookupCondition::Keccak, { - let case1 = (tx_tag == TxSignLength) && !is_l1_msg; + let case1 = (tx_tag == TxSignLength) && !is_l1_msg && !is_l1_block_hashes; let case2 = tx_tag == TxHashLength; F::from((case1 || case2) as u64) }); @@ -2271,6 +2389,11 @@ impl TxCircuitConfig { self.is_l1_msg, F::from(tx_type.is_l1_msg() as u64), ), + ( + "is_l1_block_hashes", + self.is_l1_block_hashes, + F::from(tx_type.is_l1_block_hashes() as u64), + ), ] { region.assign_advice(|| col_anno, col, offset, || Value::known(col_val))?; } @@ -2437,7 +2560,7 @@ impl TxCircuit { .chain(iter::once(&padding_tx)) .enumerate() .map(|(_, tx)| { - if tx.tx_type.is_l1_msg() { + if tx.tx_type.is_l1_custom_tx() { Ok(SignData::default()) } else { tx.sign_data().map_err(|e| { @@ -2769,7 +2892,7 @@ impl SubCircuit for TxCircuit { .iter() .chain(padding_txs.iter()) .map(|tx| { - if tx.tx_type.is_l1_msg() { + if tx.tx_type.is_l1_custom_tx() { Ok(SignData::default()) } else { tx.sign_data().map_err(|e| { @@ -2796,7 +2919,7 @@ impl SubCircuit for TxCircuit { let pk_hash = keccak(&pk); let address = pk_hash.to_address(); // L1 Msg does not have signature - if !tx.tx_type.is_l1_msg() && address != tx.caller_address { + if !tx.tx_type.is_l1_custom_tx() && address != tx.caller_address { log::error!( "pk address from sign data {:?} does not match the one from tx address {:?}", address, @@ -2837,7 +2960,7 @@ pub(crate) fn get_sign_data( .iter() .chain(padding_txs.iter()) .map(|tx| { - if tx.tx_type.is_l1_msg() { + if tx.tx_type.is_l1_custom_tx() { // dummy signature Ok(SignData::default()) } else { diff --git a/zkevm-circuits/src/witness.rs b/zkevm-circuits/src/witness.rs index 928b0d15c3d..b941c8d68cd 100644 --- a/zkevm-circuits/src/witness.rs +++ b/zkevm-circuits/src/witness.rs @@ -33,6 +33,7 @@ mod step; pub use step::ExecStep; mod l1_msg; +mod l1_block_hashes; mod tx; pub use tx::Transaction; diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index 336f5e1f277..cc5c0e9f96e 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -24,7 +24,7 @@ use super::{ mpt::ZktrieState as MptState, step::step_convert, tx::tx_convert, Bytecode, ExecStep, MptUpdates, RwMap, Transaction, }; -use crate::util::Challenges; +use crate::util::{rlc_be_bytes, Challenges}; // TODO: Remove fields that are duplicated in`eth_block` /// Block is the struct used by all circuits, which contains all the needed @@ -114,7 +114,7 @@ impl Block { .txs .iter() // Since L1Msg tx does not have signature, it do not need to do lookup into sig table - .filter(|tx| !tx.tx_type.is_l1_msg()) + .filter(|tx| !tx.tx_type.is_l1_custom_tx()) .map(|tx| tx.sign_data()) .filter_map(|res| res.ok()) .collect::>(); @@ -342,6 +342,7 @@ impl BlockContext { num_txs: usize, cum_num_txs: usize, num_all_txs: u64, + l1_block_hashes_calldata: Vec, challenges: &Challenges>, ) -> Vec<[Value; 3]> { let current_block_number = self.number.to_scalar().unwrap(); @@ -399,6 +400,16 @@ impl BlockContext { Value::known(current_block_number), Value::known(F::from(num_all_txs)), ], + [ + Value::known(F::from(BlockContextFieldTag::L1BlockHashesCalldata as u64)), + Value::known(current_block_number), + rlc_be_bytes(&l1_block_hashes_calldata, challenges.keccak_input()), + ], + [ + Value::known(F::from(BlockContextFieldTag::L1BlockHashesCalldataLength as u64)), + Value::known(current_block_number), + Value::known(F::from(l1_block_hashes_calldata.len() as u64)), + ] ], self.block_hash_assignments(randomness), ] diff --git a/zkevm-circuits/src/witness/l1_block_hashes.rs b/zkevm-circuits/src/witness/l1_block_hashes.rs new file mode 100644 index 00000000000..9b2319f2261 --- /dev/null +++ b/zkevm-circuits/src/witness/l1_block_hashes.rs @@ -0,0 +1,39 @@ +use crate::{ + evm_circuit::param::{N_BYTES_ACCOUNT_ADDRESS, N_BYTES_U64, N_BYTES_WORD}, + witness::{ + rlp_fsm::{MAX_TAG_LENGTH_OF_LIST, N_BYTES_CALLDATA}, + Format::L1BlockHashesHash, + RomTableRow, + Tag::{BeginList, Data, EndList, Gas, Nonce, Sender, To, TxType, Value as TxValue}, + }, +}; +use ethers_core::utils::rlp::Encodable; + +#[derive(Clone, Debug)] +pub struct L1BlockHashesTx; + +impl Encodable for L1BlockHashesTx { + fn rlp_append(&self, _s: &mut ethers_core::utils::rlp::RlpStream) { + unimplemented!() + } +} + +pub fn rom_table_rows() -> Vec { + let rows = vec![ + (TxType, BeginList, 1, vec![1]), + (BeginList, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![2]), + (Nonce, Gas, N_BYTES_U64, vec![3]), + (Gas, To, N_BYTES_U64, vec![4]), + (To, TxValue, N_BYTES_ACCOUNT_ADDRESS, vec![5]), + (TxValue, Data, N_BYTES_WORD, vec![6]), + (Data, Sender, N_BYTES_CALLDATA, vec![7]), + (Sender, EndList, N_BYTES_ACCOUNT_ADDRESS, vec![8]), + (EndList, EndList, 0, vec![9]), + // used to emit TxGasCostInL1 + (EndList, BeginList, 0, vec![]), + ]; + + rows.into_iter() + .map(|row| (row.0, row.1, row.2, L1BlockHashesHash, row.3).into()) + .collect() +} diff --git a/zkevm-circuits/src/witness/rlp_fsm.rs b/zkevm-circuits/src/witness/rlp_fsm.rs index 3a6b3cf0777..6cfa61d863c 100644 --- a/zkevm-circuits/src/witness/rlp_fsm.rs +++ b/zkevm-circuits/src/witness/rlp_fsm.rs @@ -207,6 +207,7 @@ use crate::{ evm_circuit::param::{N_BYTES_ACCOUNT_ADDRESS, N_BYTES_U64, N_BYTES_WORD}, witness::{ l1_msg, + l1_block_hashes, Format::{ TxHashEip155, TxHashEip1559, TxHashEip2930, TxHashPreEip155, TxSignEip155, TxSignEip1559, TxSignEip2930, TxSignPreEip155, @@ -592,6 +593,8 @@ pub enum Format { TxHashEip2930, /// L1 Msg L1MsgHash, + /// L1 Block Hashes + L1BlockHashesHash, } impl From for usize { @@ -613,6 +616,7 @@ impl Format { TxSignEip2930 => eip2930_tx_sign_rom_table_rows(), TxHashEip2930 => eip2930_tx_hash_rom_table_rows(), Self::L1MsgHash => l1_msg::rom_table_rows(), + Self::L1BlockHashesHash => l1_block_hashes::rom_table_rows(), } } } diff --git a/zkevm-circuits/src/witness/tx.rs b/zkevm-circuits/src/witness/tx.rs index 74506c31f62..62d419c92e4 100644 --- a/zkevm-circuits/src/witness/tx.rs +++ b/zkevm-circuits/src/witness/tx.rs @@ -6,7 +6,7 @@ use crate::{ rlp_fsm::SmState, DataTable, Format, Format::{ - L1MsgHash, TxHashEip155, TxHashEip1559, TxHashEip2930, TxHashPreEip155, TxSignEip155, + L1MsgHash, L1BlockHashesHash, TxHashEip155, TxHashEip1559, TxHashEip2930, TxHashPreEip155, TxSignEip155, TxSignEip1559, TxSignEip2930, TxSignPreEip155, }, RlpFsmWitnessGen, RlpFsmWitnessRow, RlpTable, RlpTag, State, @@ -349,6 +349,7 @@ impl Transaction { TxType::PreEip155 => TxHashPreEip155, TxType::Eip1559 => TxHashEip1559, TxType::L1Msg => L1MsgHash, + TxType::L1BlockHashes => L1BlockHashesHash, TxType::Eip2930 => TxHashEip2930, }, ) @@ -761,6 +762,7 @@ impl RlpFsmWitnessGen for Transaction { let hash_wit = self.gen_rlp_witness(true, challenges); let sign_wit = match self.tx_type { TxType::L1Msg => vec![], + TxType::L1BlockHashes => vec![], _ => self.gen_rlp_witness(false, challenges), }; @@ -788,6 +790,7 @@ impl RlpFsmWitnessGen for Transaction { TxType::Eip1559 => (TxHashEip1559, Some(TxSignEip1559)), TxType::Eip2930 => (TxHashEip2930, Some(TxSignEip2930)), TxType::L1Msg => (L1MsgHash, None), + TxType::L1BlockHashes => (L1BlockHashesHash, None), }; let get_table = |rlp_bytes: &Vec, format: Format| { @@ -897,7 +900,7 @@ pub(super) fn tx_convert( } let callee_address = tx.to; //if tx.is_create() { None } else { Some(tx.to) }; - let tx_gas_cost = if tx.tx_type.is_l1_msg() { + let tx_gas_cost = if tx.tx_type.is_l1_custom_tx() { 0 } else { tx_data_gas_cost(&tx.rlp_bytes)