Skip to content

Commit 4574c95

Browse files
blockifier: verify that the proof facts block number matches the block hash
1 parent f428e6f commit 4574c95

File tree

11 files changed

+152
-33
lines changed

11 files changed

+152
-33
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: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ use apollo_network_types::network_types::BroadcastedMessageMetadata;
3838
use apollo_test_utils::{get_rng, GetTestInstance};
3939
use blockifier::blockifier::config::ContractClassManagerConfig;
4040
use blockifier::context::ChainInfo;
41+
use blockifier::test_utils::block_hash_contract_address;
4142
use blockifier::test_utils::initial_test_state::fund_account;
4243
use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1};
4344
use blockifier_test_utils::calldata::create_trivial_calldata;
@@ -62,6 +63,7 @@ use starknet_api::test_utils::declare::{
6263
use starknet_api::test_utils::deploy_account::DeployAccountTxArgs;
6364
use starknet_api::test_utils::invoke::{executable_invoke_tx, InvokeTxArgs};
6465
use starknet_api::test_utils::{
66+
generate_block_hash_storage_updates,
6567
valid_resource_bounds_for_testing,
6668
TestingTxArgs,
6769
CHAIN_ID_FOR_TESTS,
@@ -276,14 +278,22 @@ async fn setup_mock_state(
276278

277279
setup_transaction_converter_mock(&mut mock_dependencies.mock_transaction_converter, tx_args);
278280

281+
// Setup state: fund account and store proof block hash if needed.
282+
let state_reader =
283+
&mut mock_dependencies.state_reader_factory.state_reader.blockifier_state_reader;
279284
let address = expected_internal_tx.contract_address();
280285
fund_account(
281286
&mock_dependencies.config.chain_info,
282287
address,
283288
VALID_ACCOUNT_BALANCE,
284-
&mut mock_dependencies.state_reader_factory.state_reader.blockifier_state_reader,
289+
state_reader,
285290
);
286291

292+
let block_hash_to_number_storage = generate_block_hash_storage_updates();
293+
for (block_hash, block_number) in block_hash_to_number_storage {
294+
state_reader.storage_view.insert((block_hash_contract_address(), block_hash), block_number);
295+
}
296+
287297
let mempool_add_tx_args = AddTransactionArgs {
288298
tx: expected_internal_tx.clone(),
289299
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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use starknet_api::transaction::fields::{
4141
Fee,
4242
GasVectorComputationMode,
4343
};
44+
use starknet_api::versioned_constants_logic::VersionedConstantsTrait;
4445
use starknet_api::{contract_address, felt};
4546
use starknet_types_core::felt::Felt;
4647
use strum::EnumCount;
@@ -416,3 +417,11 @@ pub fn maybe_dummy_block_hash_and_number(block_number: BlockNumber) -> Option<Bl
416417
hash: BlockHash(StarkHash::ONE),
417418
})
418419
}
420+
421+
/// Returns the contract address for the block hash contract used in tests.
422+
pub fn block_hash_contract_address() -> ContractAddress {
423+
VersionedConstants::latest_constants()
424+
.os_constants
425+
.os_contract_addresses
426+
.block_hash_contract_address()
427+
}

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+
if stored_block_hash == Felt::ZERO {
301+
return Err(TransactionPreValidationError::InvalidProofFacts(format!(
302+
"Stored block hash is zero for block {proof_block_number}."
303+
)));
304+
}
305+
306+
let proof_block_hash = snos_proof_facts.block_hash.0;
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: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::HashMap;
1+
use std::collections::{hash_map, HashMap};
22
use std::fs::File;
33
use std::path::{Path, PathBuf};
44
use std::sync::LazyLock;
@@ -26,6 +26,7 @@ use crate::deprecated_contract_class::{ContractClass as DeprecatedContractClass,
2626
use crate::executable_transaction::AccountTransaction;
2727
use crate::execution_resources::GasAmount;
2828
use crate::rpc_transaction::{InternalRpcTransaction, RpcTransaction};
29+
use crate::state::StorageKey;
2930
use crate::transaction::fields::{
3031
AllResourceBounds,
3132
Fee,
@@ -54,6 +55,22 @@ pub const TEST_ERC20_CONTRACT_ADDRESS2: &str = "0x1002";
5455
pub const CURRENT_BLOCK_NUMBER: u64 = 2001;
5556
pub const CURRENT_BLOCK_NUMBER_FOR_VALIDATE: u64 = 2000;
5657

58+
// Range of historical blocks to populate in the block hash contract.
59+
// For block number N, we populate blocks BLOCK_HASH_HISTORY_START to BLOCK_HASH_HISTORY_END.
60+
// E.g., for block 2001: blocks 1950 to 1990.
61+
pub const BLOCK_HASH_HISTORY_START: u64 = 1950;
62+
pub const BLOCK_HASH_HISTORY_END: u64 = 1990;
63+
64+
/// Generates deterministic block hash storage updates for historical blocks.
65+
/// Populates blocks BLOCK_HASH_HISTORY_START to BLOCK_HASH_HISTORY_END with deterministic hash
66+
/// values (block_num * 100, i.e., 195000, 195100, ..., 199000).
67+
pub fn generate_block_hash_storage_updates() -> hash_map::IntoIter<StorageKey, Felt> {
68+
(BLOCK_HASH_HISTORY_START..=BLOCK_HASH_HISTORY_END)
69+
.map(|block_num| (StorageKey::from(block_num), Felt::from(block_num * 100)))
70+
.collect::<HashMap<_, _>>()
71+
.into_iter()
72+
}
73+
5774
// The block timestamp of the BlockContext being used for testing.
5875
pub const CURRENT_BLOCK_TIMESTAMP: u64 = 1072023;
5976
pub const CURRENT_BLOCK_TIMESTAMP_FOR_VALIDATE: u64 = 1069200;
@@ -127,7 +144,7 @@ macro_rules! nonce {
127144
};
128145
}
129146

130-
/// A utility macro to create a [`StorageKey`](crate::state::StorageKey) from a hex string /
147+
/// A utility macro to create a [`StorageKey`] from a hex string /
131148
/// unsigned integer representation.
132149
#[macro_export]
133150
macro_rules! storage_key {
@@ -371,12 +388,17 @@ impl ProofFacts {
371388
///
372389
/// See [`crate::transaction::fields::ProofFacts`].
373390
pub fn snos_proof_facts_for_testing() -> Self {
374-
// TODO(AvivG): Change to valid values when available.
375391
let program_hash = felt!("0x4");
376-
let block_number = felt!("0x3");
377-
let block_hash = felt!("0x2");
378-
let config_hash = felt!("0x1");
379392

393+
let block_number = felt!(BLOCK_HASH_HISTORY_START + 2);
394+
let block_hash = block_number * felt!(100_u64);
395+
assert!(
396+
block_number <= felt!(BLOCK_HASH_HISTORY_END)
397+
&& block_number >= felt!(BLOCK_HASH_HISTORY_START),
398+
"Block number is out of range"
399+
);
400+
// TODO(AvivG): Change to valid values when available.
401+
let config_hash = felt!("0x1");
380402
proof_facts![felt!(VIRTUAL_SNOS), program_hash, block_number, block_hash, config_hash]
381403
}
382404
}

crates/starknet_os_flow_tests/src/initial_state.rs

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

33
use blockifier::context::BlockContext;
4+
use blockifier::state::cached_state::StateMaps;
45
use blockifier::state::state_api::UpdatableState;
6+
use blockifier::test_utils::block_hash_contract_address;
57
use blockifier::transaction::transaction_execution::Transaction;
68
use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1};
79
use blockifier_test_utils::calldata::create_calldata;
@@ -27,7 +29,12 @@ use starknet_api::hash::{HashOutput, StateRoots};
2729
use starknet_api::state::{ContractClassComponentHashes, SierraContractClass};
2830
use starknet_api::test_utils::deploy_account::deploy_account_tx;
2931
use starknet_api::test_utils::invoke::invoke_tx;
30-
use starknet_api::test_utils::{NonceManager, CHAIN_ID_FOR_TESTS, CURRENT_BLOCK_NUMBER};
32+
use starknet_api::test_utils::{
33+
generate_block_hash_storage_updates,
34+
NonceManager,
35+
CHAIN_ID_FOR_TESTS,
36+
CURRENT_BLOCK_NUMBER,
37+
};
3138
use starknet_api::transaction::constants::DEPLOY_CONTRACT_FUNCTION_ENTRY_POINT_NAME;
3239
use starknet_api::transaction::fields::{Calldata, ContractAddressSalt, ValidResourceBounds};
3340
use starknet_api::{calldata, deploy_account_tx_args, invoke_tx_args};
@@ -174,7 +181,19 @@ pub(crate) async fn create_default_initial_state_data<S: FlowTestState, const N:
174181
);
175182
final_state.state.apply_writes(&state_diff, &final_state.class_hash_to_class.borrow());
176183

177-
// Commit the state diff.
184+
// Generates block hash storage updates for historical blocks.
185+
let block_hash_storage_updates: HashMap<_, _> = generate_block_hash_storage_updates().collect();
186+
187+
// Add historical block hashes.
188+
let mut block_hash_state_maps = StateMaps::default();
189+
for (key, value) in &block_hash_storage_updates {
190+
block_hash_state_maps.storage.insert((block_hash_contract_address(), *key), *value);
191+
}
192+
final_state
193+
.state
194+
.apply_writes(&block_hash_state_maps, &final_state.class_hash_to_class.borrow());
195+
196+
// Commits the state diff with block hash mappings.
178197
let committer_state_diff = create_committer_state_diff(state_diff);
179198
let (commitment_output, commitment_storage) =
180199
commit_initial_state_diff(committer_state_diff).await;

0 commit comments

Comments
 (0)