Skip to content

Commit cd98152

Browse files
blockifier: verify that the proof facts block number matches the block hash
1 parent 7cb6526 commit cd98152

File tree

11 files changed

+141
-31
lines changed

11 files changed

+141
-31
lines changed

crates/apollo_consensus_orchestrator/resources/central_invoke_tx_client_side_proving.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
"proof_facts": [
3636
"0x5649525455414c5f534e4f53",
3737
"0x4",
38-
"0x3",
39-
"0x2",
38+
"0x7a0",
39+
"0x2fa80",
4040
"0x1"
4141
],
4242
"type": "INVOKE_FUNCTION"

crates/apollo_consensus_orchestrator/resources/central_preconfirmed_block.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@
6060
"proof_facts": [
6161
"0x5649525455414c5f534e4f53",
6262
"0x4",
63-
"0x3",
64-
"0x2",
63+
"0x7a0",
64+
"0x2fa80",
6565
"0x1"
6666
],
6767
"type":"INVOKE_FUNCTION"

crates/apollo_gateway/src/gateway_test.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use apollo_network_types::network_types::BroadcastedMessageMetadata;
3939
use apollo_test_utils::{get_rng, GetTestInstance};
4040
use blockifier::blockifier::config::ContractClassManagerConfig;
4141
use blockifier::context::ChainInfo;
42+
use blockifier::test_utils::generate_block_hash_storage_updates;
4243
use blockifier::test_utils::initial_test_state::fund_account;
4344
use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1};
4445
use blockifier_test_utils::calldata::create_trivial_calldata;
@@ -283,14 +284,20 @@ async fn setup_mock_state(
283284

284285
setup_transaction_converter_mock(&mut mock_dependencies.mock_transaction_converter, tx_args);
285286

287+
// Setup state: fund account and store proof block hash if needed.
288+
let state_reader =
289+
&mut mock_dependencies.state_reader_factory.state_reader.blockifier_state_reader;
286290
let address = expected_internal_tx.contract_address();
287291
fund_account(
288292
&mock_dependencies.config.chain_info,
289293
address,
290294
VALID_ACCOUNT_BALANCE,
291-
&mut mock_dependencies.state_reader_factory.state_reader.blockifier_state_reader,
295+
state_reader,
292296
);
293297

298+
let block_hash_state_maps = generate_block_hash_storage_updates();
299+
state_reader.storage_view.extend(block_hash_state_maps.storage);
300+
294301
let mempool_add_tx_args = AddTransactionArgs {
295302
tx: expected_internal_tx.clone(),
296303
account_state: AccountState { address, nonce: *input_tx.nonce() },

crates/apollo_http_server/resources/deprecated_gateway/invoke_tx_client_side_proving.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
"proof_facts": [
3030
"0x5649525455414c5f534e4f53",
3131
"0x4",
32-
"0x3",
33-
"0x2",
32+
"0x7a0",
33+
"0x2fa80",
3434
"0x1"
3535
],
3636
"proof": [

crates/apollo_starknet_client/resources/reader/invoke_v3_client_side_proving.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
"proof_facts": [
3636
"0x5649525455414c5f534e4f53",
3737
"0x4",
38-
"0x3",
39-
"0x2",
38+
"0x7a0",
39+
"0x2fa80",
4040
"0x1"
4141
]
4242
}

crates/blockifier/src/test_utils.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ use starknet_api::execution_resources::{GasAmount, GasVector};
2626
use starknet_api::hash::StarkHash;
2727
use starknet_api::state::StorageKey;
2828
use starknet_api::test_utils::{
29+
BLOCK_HASH_HISTORY_RANGE,
30+
CURRENT_BLOCK_NUMBER,
2931
DEFAULT_L1_DATA_GAS_MAX_AMOUNT,
3032
DEFAULT_L1_GAS_AMOUNT,
3133
DEFAULT_L2_GAS_MAX_AMOUNT,
@@ -41,6 +43,7 @@ use starknet_api::transaction::fields::{
4143
Fee,
4244
GasVectorComputationMode,
4345
};
46+
use starknet_api::versioned_constants_logic::VersionedConstantsTrait;
4447
use starknet_api::{contract_address, felt};
4548
use starknet_types_core::felt::Felt;
4649
use strum::EnumCount;
@@ -57,6 +60,7 @@ use crate::execution::syscalls::vm_syscall_utils::{
5760
SyscallUsageMap,
5861
};
5962
use crate::fee::resources::{StarknetResources, StateResources};
63+
use crate::state::cached_state::StateMaps;
6064
use crate::utils::{const_max, u64_from_usize};
6165
// Class hashes.
6266
// TODO(Adi, 15/01/2023): Remove and compute the class hash corresponding to the ERC20 contract in
@@ -416,3 +420,25 @@ pub fn maybe_dummy_block_hash_and_number(block_number: BlockNumber) -> Option<Bl
416420
hash: BlockHash(StarkHash::ONE),
417421
})
418422
}
423+
424+
/// Returns the contract address for the block hash contract used in tests.
425+
pub fn block_hash_contract_address() -> ContractAddress {
426+
VersionedConstants::latest_constants()
427+
.os_constants
428+
.os_contract_addresses
429+
.block_hash_contract_address()
430+
}
431+
432+
/// Generates deterministic block hash storage updates for historical blocks.
433+
/// Populates a range of blocks with deterministic hash values (block_num * 100).
434+
pub fn generate_block_hash_storage_updates() -> StateMaps {
435+
let block_hash_history_start = CURRENT_BLOCK_NUMBER - BLOCK_HASH_HISTORY_RANGE;
436+
let block_hash_history_end = CURRENT_BLOCK_NUMBER - constants::STORED_BLOCK_HASH_BUFFER - 1;
437+
let block_hash_contract = block_hash_contract_address();
438+
let storage = (block_hash_history_start..=block_hash_history_end)
439+
.map(|block_num| {
440+
((block_hash_contract, StorageKey::from(block_num)), Felt::from(block_num * 100))
441+
})
442+
.collect();
443+
StateMaps { storage, ..Default::default() }
444+
}

crates/blockifier/src/test_utils/initial_test_state.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub fn setup_test_state(
8383
}
8484
}
8585

86+
// TODO(Meshi): Add block hash storage updates.
8687
pub fn test_state(
8788
chain_info: &ChainInfo,
8889
initial_balances: Fee,

crates/blockifier/src/transaction/account_transaction.rs

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use std::sync::Arc;
22

33
use starknet_api::abi::abi_utils::selector_from_name;
4-
use starknet_api::block::GasPriceVector;
4+
use starknet_api::block::{BlockNumber, GasPriceVector};
55
use starknet_api::calldata;
66
use starknet_api::contract_class::EntryPointType;
77
use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector, Nonce};
88
use starknet_api::data_availability::DataAvailabilityMode;
99
use starknet_api::executable_transaction::{AccountTransaction as Transaction, TransactionType};
1010
use starknet_api::execution_resources::GasAmount;
11+
use starknet_api::state::StorageKey;
1112
use starknet_api::transaction::fields::Resource::{L1DataGas, L1Gas, L2Gas};
1213
use starknet_api::transaction::fields::{
1314
AccountDeploymentData,
@@ -24,6 +25,8 @@ use starknet_api::transaction::{constants, TransactionHash, TransactionVersion};
2425
use starknet_types_core::felt::Felt;
2526

2627
use super::errors::ResourceBoundsError;
28+
use crate::abi::constants::STORED_BLOCK_HASH_BUFFER;
29+
use crate::blockifier_versioned_constants::OsConstants;
2730
use crate::context::{BlockContext, GasCounter, TransactionContext};
2831
use crate::execution::call_info::CallInfo;
2932
use crate::execution::common_hints::ExecutionMode;
@@ -246,19 +249,68 @@ impl AccountTransaction {
246249
}
247250
}
248251

249-
fn validate_proof_facts(&self) -> TransactionPreValidationResult<()> {
250-
if let Transaction::Invoke(tx) = &self.tx {
251-
if tx.tx.version() == TransactionVersion::THREE {
252-
let proof_facts_variant = ProofFactsVariant::try_from(&tx.tx.proof_facts())
253-
.map_err(|e| TransactionPreValidationError::InvalidProofFacts(e.to_string()))?;
254-
match proof_facts_variant {
255-
ProofFactsVariant::Empty => {}
256-
ProofFactsVariant::Snos(_snos_proof_facts) => {
257-
// TODO(Meshi/ AvivG): add proof facts validations.
258-
}
259-
}
260-
}
252+
fn validate_proof_facts(
253+
&self,
254+
os_constants: &OsConstants,
255+
current_block_number: BlockNumber,
256+
state: &mut dyn State,
257+
) -> TransactionPreValidationResult<()> {
258+
// Only Invoke V3 transactions can carry proof facts.
259+
let Transaction::Invoke(invoke_tx) = &self.tx else {
260+
return Ok(());
261+
};
262+
if invoke_tx.version() < TransactionVersion::THREE {
263+
return Ok(());
261264
}
265+
266+
// Parse proof facts.
267+
let proof_facts = invoke_tx.proof_facts();
268+
let snos_proof_facts = match ProofFactsVariant::try_from(&proof_facts)
269+
.map_err(|e| TransactionPreValidationError::InvalidProofFacts(e.to_string()))?
270+
{
271+
ProofFactsVariant::Empty => return Ok(()),
272+
ProofFactsVariant::Snos(snos_proof_facts) => snos_proof_facts,
273+
};
274+
275+
// Proof block must be old enough to have a stored block hash.
276+
// Stored block hashes are guaranteed only up to: current - STORED_BLOCK_HASH_BUFFER.
277+
let max_allowed =
278+
current_block_number.0.checked_sub(STORED_BLOCK_HASH_BUFFER).ok_or_else(|| {
279+
TransactionPreValidationError::InvalidProofFacts(format!(
280+
"The current block number {current_block_number} is too recent to have a \
281+
stored block hash."
282+
))
283+
})?;
284+
285+
let proof_block_number = snos_proof_facts.block_number.0;
286+
if proof_block_number >= max_allowed {
287+
return Err(TransactionPreValidationError::InvalidProofFacts(format!(
288+
"The proof block number {proof_block_number} is too recent. The maximum allowed \
289+
block number is {max_allowed}."
290+
)));
291+
}
292+
293+
// Compare the proof's block hash with the stored block hash.
294+
let block_hash_contract_address =
295+
os_constants.os_contract_addresses.block_hash_contract_address();
296+
297+
let stored_block_hash = state
298+
.get_storage_at(block_hash_contract_address, StorageKey::from(proof_block_number))?;
299+
300+
301+
let proof_block_hash = snos_proof_facts.block_hash.0;
302+
if proof_block_hash == Felt::ZERO {
303+
return Err(TransactionPreValidationError::InvalidProofFacts(format!(
304+
"Stored block hash is zero for block {proof_block_number}."
305+
)));
306+
}
307+
if stored_block_hash != proof_block_hash {
308+
return Err(TransactionPreValidationError::InvalidProofFacts(format!(
309+
"Block hash mismatch for block {proof_block_number}. Proof block hash: \
310+
{proof_block_hash}, stored block hash: {stored_block_hash}."
311+
)));
312+
}
313+
262314
Ok(())
263315
}
264316

@@ -278,7 +330,11 @@ impl AccountTransaction {
278330
verify_can_pay_committed_bounds(state, tx_context).map_err(Box::new)?;
279331
}
280332

281-
self.validate_proof_facts()?;
333+
self.validate_proof_facts(
334+
&tx_context.block_context.versioned_constants.os_constants,
335+
tx_context.block_context.block_info.block_number,
336+
state,
337+
)?;
282338

283339
Ok(())
284340
}

crates/starknet_api/src/test_utils.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ pub const TEST_ERC20_CONTRACT_ADDRESS2: &str = "0x1002";
5454
pub const CURRENT_BLOCK_NUMBER: u64 = 2001;
5555
pub const CURRENT_BLOCK_NUMBER_FOR_VALIDATE: u64 = 2000;
5656

57+
// Range of historical blocks to populate in the block hash contract.
58+
pub const BLOCK_HASH_HISTORY_RANGE: u64 = 51;
59+
5760
// The block timestamp of the BlockContext being used for testing.
5861
pub const CURRENT_BLOCK_TIMESTAMP: u64 = 1072023;
5962
pub const CURRENT_BLOCK_TIMESTAMP_FOR_VALIDATE: u64 = 1069200;
@@ -127,7 +130,7 @@ macro_rules! nonce {
127130
};
128131
}
129132

130-
/// A utility macro to create a [`StorageKey`](crate::state::StorageKey) from a hex string /
133+
/// A utility macro to create a [`StorageKey`] from a hex string /
131134
/// unsigned integer representation.
132135
#[macro_export]
133136
macro_rules! storage_key {
@@ -371,12 +374,17 @@ impl ProofFacts {
371374
///
372375
/// See [`crate::transaction::fields::ProofFacts`].
373376
pub fn snos_proof_facts_for_testing() -> Self {
374-
// TODO(AvivG): Change to valid values when available.
375377
let program_hash = felt!("0x4");
376-
let block_number = felt!("0x3");
377-
let block_hash = felt!("0x2");
378+
let block_hash_history_start = CURRENT_BLOCK_NUMBER - BLOCK_HASH_HISTORY_RANGE;
379+
let block_number = felt!(block_hash_history_start + 2);
380+
let block_hash = block_number * felt!(100_u64);
381+
assert!(
382+
block_number < felt!(CURRENT_BLOCK_NUMBER)
383+
&& block_number >= felt!(block_hash_history_start),
384+
"Block number is out of range"
385+
);
386+
// TODO(AvivG): Change to valid values when available.
378387
let config_hash = felt!("0x1");
379-
380388
proof_facts![felt!(VIRTUAL_SNOS), program_hash, block_number, block_hash, config_hash]
381389
}
382390
}

crates/starknet_os_flow_tests/src/initial_state.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::collections::{BTreeMap, HashMap, HashSet};
22

33
use blockifier::context::BlockContext;
44
use blockifier::state::state_api::UpdatableState;
5+
use blockifier::test_utils::generate_block_hash_storage_updates;
56
use blockifier::transaction::transaction_execution::Transaction;
67
use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1};
78
use blockifier_test_utils::calldata::create_calldata;
@@ -164,17 +165,27 @@ pub(crate) async fn create_default_initial_state_data<S: FlowTestState, const N:
164165
// Make sure none of them is reverted.
165166
assert!(execution_outputs.iter().all(|output| output.0.revert_error.is_none()));
166167
// Update the state reader with the state diff.
167-
let state_diff = final_state.to_state_diff().unwrap().state_maps;
168+
let mut state_diff = final_state.to_state_diff().unwrap().state_maps;
168169
// Sanity check to verify the STRK_FEE_TOKEN_ADDRESS constant.
169170
assert_eq!(
170171
state_diff.class_hashes[&STRK_FEE_TOKEN_ADDRESS],
171172
FeatureContract::ERC20(CairoVersion::Cairo1(RunnableCairo1::Casm))
172173
.get_sierra()
173174
.calculate_class_hash()
174175
);
176+
// Add historical block hashes.
177+
let block_hash_state_diff = generate_block_hash_storage_updates();
178+
state_diff.extend(&block_hash_state_diff);
179+
175180
final_state.state.apply_writes(&state_diff, &final_state.class_hash_to_class.borrow());
176181

177-
// Commit the state diff.
182+
// Add historical block hashes.
183+
let block_hash_state_maps = generate_block_hash_storage_updates();
184+
final_state
185+
.state
186+
.apply_writes(&block_hash_state_maps, &final_state.class_hash_to_class.borrow());
187+
188+
// Commits the state diff with block hash mappings.
178189
let committer_state_diff = create_committer_state_diff(state_diff);
179190
let (commitment_output, commitment_storage) =
180191
commit_initial_state_diff(committer_state_diff).await;

0 commit comments

Comments
 (0)