-
Notifications
You must be signed in to change notification settings - Fork 33
feat(evm): align Zones with Tempo T11 precompile decoding #1372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4d12736
04aa0cf
961e664
af16f93
2868509
8dd0520
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,8 @@ use std::{ | |
| time::Duration, | ||
| }; | ||
| use tempo_alloy::TempoNetwork; | ||
| use tempo_chainspec::hardfork::TempoHardfork; | ||
| use tempo_precompiles::dispatch::abi_decoder_config_for_spec; | ||
| use tempo_primitives::{Block, TempoHeader, TempoTxEnvelope}; | ||
| use tokio::sync::{mpsc, oneshot}; | ||
| use tokio_util::sync; | ||
|
|
@@ -1375,8 +1377,11 @@ fn decode_advance_tempo( | |
| if signed.tx().to != ZONE_INBOX_ADDRESS.into() { | ||
| eyre::bail!("first Tempo system transaction is not sent to IZoneInbox") | ||
| } | ||
| let call = IZoneInbox::advanceTempoCall::abi_decode(signed.tx().input.as_ref()) | ||
| .map_err(|err| eyre::eyre!("first transaction does not decode as advanceTempo: {err}"))?; | ||
| let call = IZoneInbox::advanceTempoCall::abi_decode_with_config( | ||
| signed.tx().input.as_ref(), | ||
| abi_decoder_config_for_spec(TempoHardfork::latest()), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 [POTENTIAL-VULNERABILITY] Peer-block admission decodes
Recommended Fix:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the leader encodes without trailing bytes in our current setup. |
||
| ) | ||
| .map_err(|err| eyre::eyre!("first transaction does not decode as advanceTempo: {err}"))?; | ||
|
|
||
| // 3. the system tx is valid. | ||
| let mut header_rlp = call.header.as_ref(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ use std::{ | |
| time::Duration, | ||
| }; | ||
|
|
||
| use alloy_consensus::BlockHeader; | ||
| use alloy_consensus::{BlockHeader, transaction::TxHashRef}; | ||
| use alloy_eips::eip2935::{HISTORY_SERVE_WINDOW, HISTORY_STORAGE_ADDRESS}; | ||
| use alloy_network::{ReceiptResponse, TransactionBuilder, TransactionResponse}; | ||
| use alloy_primitives::{Address, B256, Bloom, Bytes, U64, U256, keccak256}; | ||
|
|
@@ -37,7 +37,7 @@ use reth_rpc_eth_api::{ | |
| }; | ||
| use reth_rpc_eth_types::{EthApiError, logs_utils}; | ||
| use reth_storage_api::{BlockNumReader, StateProviderFactory}; | ||
| use reth_trie_common::{ExecutionWitnessMode, HashedStorage}; | ||
| use reth_trie_common::{ExecutionWitnessMode, HashedPostState}; | ||
| use tempo_alloy::{ | ||
| TempoNetwork, | ||
| provider::ext::TempoProviderExt as _, | ||
|
|
@@ -286,17 +286,27 @@ where | |
| let (evm_config, recorder) = eth_api.evm_config().with_l1_storage_recorder(); | ||
| let block_executor = evm_config.executor(&mut db); | ||
| let mode = ExecutionWitnessMode::default(); | ||
| let mut witness_record = ExecutionWitnessRecord::default(); | ||
| let mut witness = None; | ||
|
|
||
| let _ = block_executor | ||
| .execute_with_state_closure(&block, |statedb: &State<_>| { | ||
| witness_record.record_executed_state(statedb, mode); | ||
| record_block_hash_storage_proofs(&mut witness_record, statedb); | ||
| let mut additional_state = HashedPostState::default(); | ||
| record_block_hash_storage_proofs(&mut additional_state, statedb); | ||
| witness = Some( | ||
| ExecutionWitnessRecord::new(statedb) | ||
| .with_additional_state(additional_state) | ||
| .into_execution_witness( | ||
| &statedb.database.database.0, | ||
| eth_api.provider(), | ||
| block_number, | ||
| mode, | ||
| ), | ||
| ); | ||
| }) | ||
| .map_err(|error| EthApiError::Internal(error.into()))?; | ||
|
|
||
| let witness = witness_record | ||
| .into_execution_witness(&db.database.0, eth_api.provider(), block_number, mode) | ||
| let witness = witness | ||
| .expect("state closure is called after successful execution") | ||
| .map_err(EthApiError::from)?; | ||
| Ok(ZoneExecutionWitness { | ||
| execution_witness: witness, | ||
|
|
@@ -320,17 +330,16 @@ where | |
| /// Reth records these reads in REVM's block-hash cache and normally proves them with ancestor | ||
| /// headers. Zones already commit the EIP-2935 history contract in state, so adding the matching | ||
| /// storage targets lets the SPF authenticate the same values against the parent state root. | ||
| fn record_block_hash_storage_proofs<DB>(witness: &mut ExecutionWitnessRecord, state: &State<DB>) { | ||
| fn record_block_hash_storage_proofs<DB>(additional_state: &mut HashedPostState, state: &State<DB>) { | ||
| let block_hashes = state.block_hashes.iter().collect::<Vec<_>>(); | ||
| if block_hashes.is_empty() { | ||
| return; | ||
| } | ||
|
|
||
| let history_storage = witness | ||
| .hashed_state | ||
| let history_storage = additional_state | ||
| .storages | ||
| .entry(keccak256(HISTORY_STORAGE_ADDRESS)) | ||
| .or_insert_with(|| HashedStorage::new(false)); | ||
| .or_default(); | ||
| for (number, hash) in block_hashes { | ||
| let slot = U256::from(number % HISTORY_SERVE_WINDOW as u64); | ||
| history_storage.storage.insert( | ||
|
|
@@ -1187,6 +1196,7 @@ where | |
| fn ws_subscribe_logs(&self, mut filter: Filter, auth: AuthContext) -> BoxWsSubscriptionFut<'_> { | ||
| Box::pin(async move { | ||
| let provider = self.eth.api.provider().clone(); | ||
| let api = self.eth.api.clone(); | ||
| let caller = auth.caller; | ||
|
|
||
| let zone_tokens = self.zone_tokens(); | ||
|
|
@@ -1195,18 +1205,36 @@ where | |
|
|
||
| let stream = provider | ||
| .canonical_state_stream() | ||
| .flat_map(|canon_state| futures::stream::iter(canon_state.block_receipts())) | ||
| .flat_map(move |(block_receipts, removed)| { | ||
| let all_logs = logs_utils::matching_block_logs_with_tx_hashes( | ||
| &filter, | ||
| block_receipts.block, | ||
| block_receipts.timestamp, | ||
| block_receipts | ||
| .tx_receipts | ||
| .iter() | ||
| .map(|(tx, receipt)| (*tx, receipt)), | ||
| removed, | ||
| ); | ||
| .flat_map(move |canon_state| { | ||
| let reverted_chains = canon_state.reverted(); | ||
| let committed_chain = canon_state.committed(); | ||
| let reverted = reverted_chains.iter().flat_map(|chain| { | ||
| chain | ||
| .blocks_and_receipts() | ||
| .map(|(block, receipts)| (block, receipts, true)) | ||
| }); | ||
| let committed = committed_chain | ||
| .blocks_and_receipts() | ||
| .map(|(block, receipts)| (block, receipts, false)); | ||
| let mut all_logs = Vec::new(); | ||
|
|
||
| for (block, receipts, removed) in reverted.chain(committed) { | ||
| match logs_utils::matching_block_logs_with_tx_hashes( | ||
| api.converter(), | ||
| &filter, | ||
| block.sealed_header(), | ||
| block | ||
| .transactions_recovered() | ||
| .zip(receipts.iter()) | ||
| .map(|(tx, receipt)| (*tx.tx_hash(), receipt)), | ||
| removed, | ||
| ) { | ||
| Ok(logs) => all_logs.extend(logs), | ||
| Err(error) => { | ||
| tracing::error!(target: "rpc", %error, "Failed to convert logs"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If Recommended Fix: |
||
| } | ||
| } | ||
| } | ||
| futures::stream::iter(all_logs) | ||
| }); | ||
|
|
||
|
|
@@ -1484,12 +1512,11 @@ mod tests { | |
| .with_database(revm::database::EmptyDB::default()) | ||
| .build(); | ||
| state.block_hashes.insert(number, hash); | ||
| let mut witness = ExecutionWitnessRecord::default(); | ||
| let mut additional_state = HashedPostState::default(); | ||
|
|
||
| record_block_hash_storage_proofs(&mut witness, &state); | ||
| record_block_hash_storage_proofs(&mut additional_state, &state); | ||
|
|
||
| let storage = witness | ||
| .hashed_state | ||
| let storage = additional_state | ||
| .storages | ||
| .get(&keccak256(HISTORY_STORAGE_ADDRESS)) | ||
| .unwrap(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
finalizeWithdrawalBatchclassification now accepts any selector-prefixed calldataThe classifier now only checks the 4-byte selector, whereas the removed helper decoded and re-encoded calldata to require canonical
finalizeWithdrawalBatchbytes. Malformed or trailing-byte calldata can now advance the block phase on lenient forks, loosening a consensus-critical rule and letting non-canonical bytes reach proving/batch surfaces.Recommended Fix:
Restore canonical calldata validation for classification, or deliberately move the relaxed rule into the executor/precompile consensus path with matching admission/prover behavior and tests.