Skip to content

Commit feee81f

Browse files
blockifier: verify that the proof facts block number matches the block hash
1 parent 64b42a9 commit feee81f

File tree

12 files changed

+163
-35
lines changed

12 files changed

+163
-35
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: 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: 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): create a client-side test state.
8687
pub fn test_state(
8788
chain_info: &ChainInfo,
8889
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.saturating_sub(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+
"The proof block number {proof_block_number} is too recent to have a stored block \
283+
hash. The maximum allowed block number is {max_allowed}. The proof block number \
284+
must be less than the maximum allowed block number."
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/resources/transaction_hash.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,13 @@
146146
"0x78163ce5979e2bc8a944ba353a63c194ca4f63d393bbaaa95857daa9223e93c"
147147
],
148148
"tip": "0x0",
149-
"proof_facts": ["0x1", "0x2", "0x3"]
149+
"proof_facts": [
150+
"0x5649525455414c5f534e4f53",
151+
"0x4",
152+
"0x7a0",
153+
"0x2FA80",
154+
"0x1"
155+
]
150156
}
151157
}
152158
},

crates/starknet_api/src/test_utils.rs

Lines changed: 27 additions & 5 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;
@@ -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
}

0 commit comments

Comments
 (0)