Skip to content

Commit 1e0176c

Browse files
blockifier: verify that the proof facts block number matches the block hash
1 parent 6c958f7 commit 1e0176c

File tree

11 files changed

+175
-29
lines changed

11 files changed

+175
-29
lines changed

crates/apollo_consensus_orchestrator/resources/central_invoke_tx_client_side_proving.json

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

crates/apollo_consensus_orchestrator/resources/central_preconfirmed_block.json

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

crates/apollo_gateway/src/gateway_test.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +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::initial_test_state::fund_account;
41+
use blockifier::test_utils::initial_test_state::{fund_account, setup_block_hash_from_proof_facts};
4242
use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1};
4343
use blockifier_test_utils::calldata::create_trivial_calldata;
4444
use blockifier_test_utils::contracts::FeatureContract;
@@ -52,6 +52,7 @@ use starknet_api::executable_transaction::AccountTransaction;
5252
use starknet_api::rpc_transaction::{
5353
InternalRpcTransaction,
5454
RpcDeclareTransaction,
55+
RpcInvokeTransaction,
5556
RpcTransaction,
5657
RpcTransactionLabelValue,
5758
};
@@ -276,14 +277,21 @@ async fn setup_mock_state(
276277

277278
setup_transaction_converter_mock(&mut mock_dependencies.mock_transaction_converter, tx_args);
278279

280+
// Setup state: fund account and store proof block hash if needed.
281+
let state_reader =
282+
&mut mock_dependencies.state_reader_factory.state_reader.blockifier_state_reader;
279283
let address = expected_internal_tx.contract_address();
280284
fund_account(
281285
&mock_dependencies.config.chain_info,
282286
address,
283287
VALID_ACCOUNT_BALANCE,
284-
&mut mock_dependencies.state_reader_factory.state_reader.blockifier_state_reader,
288+
state_reader,
285289
);
286290

291+
if let RpcTransaction::Invoke(RpcInvokeTransaction::V3(invoke_tx)) = &input_tx {
292+
setup_block_hash_from_proof_facts(&invoke_tx.proof_facts, state_reader);
293+
}
294+
287295
let mempool_add_tx_args = AddTransactionArgs {
288296
tx: expected_internal_tx.clone(),
289297
account_state: AccountState { address, nonce: *input_tx.nonce() },

crates/apollo_http_server/resources/deprecated_gateway/invoke_tx_client_side_proving.json

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

crates/apollo_starknet_client/resources/reader/invoke_v3_client_side_proving.json

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

crates/blockifier/src/test_utils.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,3 +416,11 @@ pub fn maybe_dummy_block_hash_and_number(block_number: BlockNumber) -> Option<Bl
416416
hash: BlockHash(StarkHash::ONE),
417417
})
418418
}
419+
420+
/// Returns the contract address for the block hash contract used in tests.
421+
pub fn block_hash_contract_address() -> ContractAddress {
422+
VersionedConstants::create_for_testing()
423+
.os_constants
424+
.os_contract_addresses
425+
.block_hash_contract_address()
426+
}

crates/blockifier/src/test_utils/initial_test_state.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use starknet_api::block::FeeType;
55
use starknet_api::contract_class::compiled_class_hash::HashVersion;
66
use starknet_api::core::ContractAddress;
77
use starknet_api::felt;
8-
use starknet_api::transaction::fields::Fee;
8+
use starknet_api::state::StorageKey;
9+
use starknet_api::transaction::fields::{Fee, ProofFacts, ProofFactsVariant};
910
use strum::IntoEnumIterator;
1011

1112
use crate::blockifier::config::ContractClassManagerConfig;
@@ -16,6 +17,7 @@ use crate::state::state_reader_and_contract_manager::{
1617
FetchCompiledClasses,
1718
StateReaderAndContractManager,
1819
};
20+
use crate::test_utils::block_hash_contract_address;
1921
use crate::test_utils::contracts::FeatureContractData;
2022
use crate::test_utils::dict_state_reader::DictStateReader;
2123

@@ -36,6 +38,20 @@ pub fn fund_account(
3638
}
3739
}
3840

41+
/// Sets up block hash in the block hash contract from SNOS proof facts.
42+
///
43+
/// This is a test helper used to prepare state for proof-facts validation.
44+
pub fn setup_block_hash_from_proof_facts(
45+
proof_facts: &ProofFacts,
46+
state_reader: &mut DictStateReader,
47+
) {
48+
if let Ok(ProofFactsVariant::Snos(snos_proof_facts)) = proof_facts.try_into() {
49+
let contract = block_hash_contract_address();
50+
let storage_key = StorageKey::from(snos_proof_facts.block_number.0);
51+
state_reader.storage_view.insert((contract, storage_key), snos_proof_facts.block_hash.0);
52+
}
53+
}
54+
3955
/// Sets up a state reader for testing:
4056
/// * "Declares" a Cairo0 ERC20 contract (class hash => class mapping set).
4157
/// * "Deploys" two ERC20 contracts (address => class hash mapping set) at the fee token addresses
@@ -83,6 +99,7 @@ pub fn setup_test_state(
8399
}
84100
}
85101

102+
// TODO(Meshi): create a client-side test state.
86103
pub fn test_state(
87104
chain_info: &ChainInfo,
88105
initial_balances: Fee,

crates/blockifier/src/transaction/account_transaction.rs

Lines changed: 64 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,62 @@ 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(());
264+
}
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 = current_block_number.0 - STORED_BLOCK_HASH_BUFFER;
278+
279+
let proof_block_number = snos_proof_facts.block_number.0;
280+
if proof_block_number > max_allowed {
281+
return Err(TransactionPreValidationError::InvalidProofFacts(format!(
282+
"Invalid proof block number {proof_block_number}. Block hashes are only available \
283+
for blocks ≤ current_block_number - STORED_BLOCK_HASH_BUFFER \
284+
({current_block_number} - {STORED_BLOCK_HASH_BUFFER} = {max_allowed})."
285+
)));
286+
}
287+
288+
// Compare the proof's block hash with the stored block hash.
289+
let contract = os_constants.os_contract_addresses.block_hash_contract_address();
290+
291+
let stored_block_hash =
292+
state.get_storage_at(contract, StorageKey::from(proof_block_number))?;
293+
294+
if stored_block_hash == Felt::ZERO {
295+
return Err(TransactionPreValidationError::InvalidProofFacts(format!(
296+
"Stored block hash is zero for block {proof_block_number}."
297+
)));
261298
}
299+
300+
let proof_block_hash = snos_proof_facts.block_hash.0;
301+
if stored_block_hash != proof_block_hash {
302+
return Err(TransactionPreValidationError::InvalidProofFacts(format!(
303+
"Block hash mismatch for block {proof_block_number}. Proof block hash: \
304+
{proof_block_hash}, stored block hash: {stored_block_hash}."
305+
)));
306+
}
307+
262308
Ok(())
263309
}
264310

@@ -278,7 +324,11 @@ impl AccountTransaction {
278324
verify_can_pay_committed_bounds(state, tx_context).map_err(Box::new)?;
279325
}
280326

281-
self.validate_proof_facts()?;
327+
self.validate_proof_facts(
328+
&tx_context.block_context.versioned_constants.os_constants,
329+
tx_context.block_context.block_info.block_number,
330+
state,
331+
)?;
282332

283333
Ok(())
284334
}

crates/starknet_api/src/test_utils.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ 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+
// For block number N, we populate blocks BLOCK_HASH_HISTORY_START to BLOCK_HASH_HISTORY_END.
59+
// E.g., for block 2001: blocks 1950 to 1990.
60+
pub const BLOCK_HASH_HISTORY_START: u64 = 1950;
61+
pub const BLOCK_HASH_HISTORY_END: u64 = 1990;
62+
5763
// The block timestamp of the BlockContext being used for testing.
5864
pub const CURRENT_BLOCK_TIMESTAMP: u64 = 1072023;
5965
pub const CURRENT_BLOCK_TIMESTAMP_FOR_VALIDATE: u64 = 1069200;
@@ -372,7 +378,16 @@ impl ProofFacts {
372378
/// See [`crate::transaction::fields::ProofFacts`].
373379
pub fn snos_proof_facts_for_testing() -> Self {
374380
// TODO(AvivG): Change to valid values when available.
375-
proof_facts![felt!(VIRTUAL_SNOS), felt!("0x4"), felt!("0x3"), felt!("0x2"), felt!("0x1")]
381+
let distance_from_start = 2;
382+
let block_number = felt!(BLOCK_HASH_HISTORY_START + distance_from_start);
383+
let block_hash = felt!(distance_from_start + 1);
384+
assert!(
385+
block_number <= felt!(BLOCK_HASH_HISTORY_END)
386+
&& block_number >= felt!(BLOCK_HASH_HISTORY_START),
387+
"Block number is out of range"
388+
);
389+
390+
proof_facts![felt!(VIRTUAL_SNOS), felt!("0x4"), block_number, block_hash, felt!("0x1")]
376391
}
377392
}
378393

crates/starknet_os_flow_tests/src/initial_state.rs

Lines changed: 51 additions & 5 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;
@@ -24,14 +26,24 @@ use starknet_api::executable_transaction::{
2426
Transaction as StarknetAPITransaction,
2527
};
2628
use starknet_api::hash::{HashOutput, StateRoots};
27-
use starknet_api::state::{ContractClassComponentHashes, SierraContractClass};
29+
use starknet_api::state::{ContractClassComponentHashes, SierraContractClass, StorageKey};
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+
NonceManager,
34+
BLOCK_HASH_HISTORY_END,
35+
BLOCK_HASH_HISTORY_START,
36+
CHAIN_ID_FOR_TESTS,
37+
CURRENT_BLOCK_NUMBER,
38+
};
3139
use starknet_api::transaction::constants::DEPLOY_CONTRACT_FUNCTION_ENTRY_POINT_NAME;
3240
use starknet_api::transaction::fields::{Calldata, ContractAddressSalt, ValidResourceBounds};
3341
use starknet_api::{calldata, deploy_account_tx_args, invoke_tx_args};
34-
use starknet_committer::block_committer::input::StateDiff;
42+
use starknet_committer::block_committer::input::{
43+
StarknetStorageKey,
44+
StarknetStorageValue,
45+
StateDiff,
46+
};
3547
use starknet_committer::db::facts_db::db::FactsDb;
3648
use starknet_patricia_storage::map_storage::MapStorage;
3749
use starknet_types_core::felt::Felt;
@@ -128,6 +140,22 @@ pub(crate) struct InitialState<S: FlowTestState> {
128140
pub(crate) block_context: BlockContext,
129141
}
130142

143+
/// Generates deterministic block hash storage updates for historical blocks.
144+
/// Populates blocks BLOCK_HASH_HISTORY_START to BLOCK_HASH_HISTORY_END with deterministic hash
145+
/// values (block_number - start_block + 1, i.e., 1 to 41).
146+
fn generate_block_hash_storage_updates() -> HashMap<StarknetStorageKey, StarknetStorageValue> {
147+
(BLOCK_HASH_HISTORY_START..=BLOCK_HASH_HISTORY_END)
148+
.map(|block_num| {
149+
// Deterministic hash: (block_num - start_block + 1), so values are 1, 2, 3, ..., 41
150+
let hash_value = block_num - BLOCK_HASH_HISTORY_START + 1;
151+
(
152+
StarknetStorageKey(StorageKey::from(block_num)),
153+
StarknetStorageValue(Felt::from(hash_value)),
154+
)
155+
})
156+
.collect()
157+
}
158+
131159
/// Creates the initial state for the flow test which includes:
132160
/// Declares token and account contracts.
133161
/// Deploys both contracts and funds the account.
@@ -174,8 +202,26 @@ pub(crate) async fn create_default_initial_state_data<S: FlowTestState, const N:
174202
);
175203
final_state.state.apply_writes(&state_diff, &final_state.class_hash_to_class.borrow());
176204

177-
// Commit the state diff.
178-
let committer_state_diff = create_committer_state_diff(state_diff);
205+
// Generate block hash storage updates for historical blocks.
206+
let block_hash_storage_updates = generate_block_hash_storage_updates();
207+
208+
// Commit the state diff with block hash mappings.
209+
let mut committer_state_diff = create_committer_state_diff(state_diff);
210+
committer_state_diff
211+
.storage_updates
212+
.entry(block_hash_contract_address())
213+
.or_default()
214+
.extend(block_hash_storage_updates.iter().map(|(k, v)| (*k, *v)));
215+
216+
// Also apply block hash storage to the blockifier's cached state.
217+
let mut block_hash_state_maps = StateMaps::default();
218+
for (key, value) in block_hash_storage_updates {
219+
block_hash_state_maps.storage.insert((block_hash_contract_address(), key.0), value.0);
220+
}
221+
final_state
222+
.state
223+
.apply_writes(&block_hash_state_maps, &final_state.class_hash_to_class.borrow());
224+
179225
let (commitment_output, commitment_storage) =
180226
commit_initial_state_diff(committer_state_diff).await;
181227

0 commit comments

Comments
 (0)