Skip to content

Commit a7b9268

Browse files
starknet_transaction_prover: accept newer starknet versions (#14387)
1 parent 00fb954 commit a7b9268

7 files changed

Lines changed: 148 additions & 7 deletions

File tree

crates/starknet_os/src/hints/hint_implementation/os.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::collections::{BTreeSet, HashMap};
33
use blockifier::state::state_api::StateReader;
44
use cairo_vm::types::relocatable::MaybeRelocatable;
55
use starknet_api::block_hash::block_hash_calculator::gas_prices_to_hash;
6+
use starknet_api::core::ascii_as_felt;
67
use starknet_types_core::felt::Felt;
78

89
use crate::hint_processor::snos_hint_processor::SnosHintProcessor;
@@ -186,8 +187,13 @@ pub(crate) fn get_block_hashes<S: StateReader>(
186187
)?;
187188
ctx.insert_value(Ids::HeaderCommitments, header_commitments_ptr)?;
188189

189-
let starknet_version_felt = Felt::try_from(&block_info.starknet_version)?;
190-
ctx.insert_value(Ids::StarknetVersion, starknet_version_felt)?;
190+
// The on-chain block hash commits to the exact ASCII encoding of the version string, which
191+
// may be newer than the latest known `StarknetVersion` variant.
192+
let starknet_version_string = os_input
193+
.starknet_version_override
194+
.clone()
195+
.unwrap_or_else(|| block_info.starknet_version.to_string());
196+
ctx.insert_value(Ids::StarknetVersion, ascii_as_felt(&starknet_version_string)?)?;
191197

192198
let [gas_prices_hash]: [Felt; 1] = gas_prices_to_hash(
193199
&gas_prices.l1_gas_price_per_token(),

crates/starknet_os/src/io/os_input.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ pub struct OsBlockInput {
5050
// A mapping from Cairo 1 declared class hashes to the hashes of the contract class components.
5151
pub declared_class_hash_to_component_hashes: HashMap<ClassHash, ContractClassComponentHashes>,
5252
pub block_info: BlockInfo,
53+
// When set, the OS recomputes the block hash from this exact version string instead of
54+
// `block_info.starknet_version`. Required when the block's on-chain version is newer than the
55+
// latest known `StarknetVersion` variant.
56+
#[cfg_attr(feature = "deserialize", serde(default))]
57+
pub starknet_version_override: Option<String>,
5358
pub block_hash_commitments: BlockHeaderCommitments,
5459
pub prev_block_hash: BlockHash,
5560
pub new_block_hash: BlockHash,

crates/starknet_os_flow_tests/src/test_manager.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,7 @@ impl<S: FlowTestState> TestBuilder<S> {
927927
prev_block_hash,
928928
new_block_hash,
929929
block_info,
930+
starknet_version_override: None,
930931
block_hash_commitments,
931932
old_block_number_and_hash,
932933
class_hashes_to_migrate: Vec::new(),

crates/starknet_transaction_prover/src/running/runner.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ pub(crate) struct VirtualOsBlockInput {
5151
transactions: Vec<(InvokeTransaction, TransactionHash)>,
5252
tx_execution_infos: Vec<CentralTransactionExecutionInfo>,
5353
block_info: BlockInfo,
54+
/// The base block's version string as returned by the RPC; used by the OS to recompute the
55+
/// base block hash (may be newer than the latest known `StarknetVersion`).
56+
raw_starknet_version: String,
5457
initial_reads: StateMaps,
5558
base_block_hash: BlockHash,
5659
base_block_header_commitments: BlockHeaderCommitments,
@@ -83,6 +86,7 @@ impl From<VirtualOsBlockInput> for OsHints {
8386
tx_execution_infos: virtual_os_block_input.tx_execution_infos,
8487
prev_block_hash: virtual_os_block_input.prev_base_block_hash,
8588
block_info: virtual_os_block_input.block_info,
89+
starknet_version_override: Some(virtual_os_block_input.raw_starknet_version),
8690
initial_reads: virtual_os_block_input.initial_reads,
8791
declared_class_hash_to_component_hashes: HashMap::new(),
8892
new_block_hash: virtual_os_block_input.base_block_hash,
@@ -238,6 +242,7 @@ where
238242
transactions: txs,
239243
tx_execution_infos,
240244
block_info: execution_data.base_block_info.block_context.block_info().clone(),
245+
raw_starknet_version: execution_data.base_block_info.raw_starknet_version,
241246
initial_reads: extended_initial_reads,
242247
base_block_hash: execution_data.base_block_info.base_block_hash,
243248
base_block_header_commitments: execution_data

crates/starknet_transaction_prover/src/running/storage_proofs_test.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::collections::HashSet;
33
use blockifier::context::BlockContext;
44
use blockifier::state::cached_state::StateMaps;
55
use rstest::rstest;
6-
use starknet_api::block::{BlockHash, BlockNumber};
6+
use starknet_api::block::{BlockHash, BlockNumber, StarknetVersion};
77
use starknet_api::block_hash::block_hash_calculator::BlockHeaderCommitments;
88
use starknet_api::core::ContractAddress;
99
use starknet_api::state::StorageKey;
@@ -67,6 +67,7 @@ fn test_get_storage_proofs_from_rpc(
6767
l2_to_l1_messages: Vec::new(),
6868
base_block_info: BaseBlockInfo {
6969
block_context: BlockContext::create_for_account_testing(),
70+
raw_starknet_version: StarknetVersion::LATEST.to_string(),
7071
base_block_hash: BlockHash::default(),
7172
prev_base_block_hash: BlockHash::default(),
7273
base_block_header_commitments: BlockHeaderCommitments::default(),

crates/starknet_transaction_prover/src/running/virtual_block_executor.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ use blockifier_reexecution::state_reader::prefetched_state_reader::{
2323
use blockifier_reexecution::state_reader::rpc_objects::{BlockHeader, BlockId};
2424
use blockifier_reexecution::state_reader::rpc_state_reader::RpcStateReader;
2525
use serde::{Deserialize, Serialize};
26-
use starknet_api::block::{BlockHash, BlockInfo};
26+
use starknet_api::block::{BlockHash, BlockInfo, StarknetVersion};
2727
use starknet_api::block_hash::block_hash_calculator::{concat_counts, BlockHeaderCommitments};
2828
use starknet_api::contract_class::SierraVersion;
2929
use starknet_api::core::ClassHash;
3030
use starknet_api::transaction::fields::Fee;
3131
use starknet_api::transaction::{InvokeTransaction, MessageToL1, Transaction, TransactionHash};
3232
use starknet_api::versioned_constants_logic::VersionedConstantsTrait;
33+
use starknet_api::StarknetApiError;
3334
use tracing::error;
3435

3536
use crate::errors::VirtualBlockExecutorError;
@@ -75,6 +76,9 @@ impl Default for RpcVirtualBlockExecutorConfig {
7576
/// This struct contains all the execution data needed for proof generation.
7677
pub(crate) struct BaseBlockInfo {
7778
pub(crate) block_context: BlockContext,
79+
/// The base block's version string exactly as returned by the RPC; may be newer than the
80+
/// latest known `StarknetVersion`. The OS uses it to recompute the base block hash.
81+
pub(crate) raw_starknet_version: String,
7882
/// The block hash of the base block,
7983
/// in which the virtual block is executed.
8084
pub(crate) base_block_hash: BlockHash,
@@ -91,7 +95,7 @@ impl BaseBlockInfo {
9195
/// When `use_latest_versioned_constants` is `true`, the latest versioned constants are used
9296
/// instead of the ones matching the block's Starknet version.
9397
pub(crate) fn new(
94-
header: BlockHeader,
98+
mut header: BlockHeader,
9599
chain_info: ChainInfo,
96100
use_latest_versioned_constants: bool,
97101
) -> Result<Self, VirtualBlockExecutorError> {
@@ -110,6 +114,17 @@ impl BaseBlockInfo {
110114
),
111115
};
112116

117+
let raw_starknet_version = header.starknet_version.clone();
118+
// Versions newer than the latest known one are executed with the latest known semantics;
119+
// the raw version string is kept separately so the OS recomputes the on-chain block hash
120+
// from the exact version string.
121+
header.starknet_version = starknet_version_or_latest(&raw_starknet_version)
122+
.map_err(|e| {
123+
VirtualBlockExecutorError::TransactionExecutionError(format!(
124+
"Unsupported starknet version in block header: {e}"
125+
))
126+
})?
127+
.to_string();
113128
let block_info: BlockInfo = header.try_into().map_err(|e| {
114129
VirtualBlockExecutorError::TransactionExecutionError(format!(
115130
"Failed to convert block header to block info: {e}"
@@ -141,13 +156,31 @@ impl BaseBlockInfo {
141156

142157
Ok(BaseBlockInfo {
143158
block_context,
159+
raw_starknet_version,
144160
base_block_hash,
145161
base_block_header_commitments,
146162
prev_base_block_hash,
147163
})
148164
}
149165
}
150166

167+
/// Returns the [StarknetVersion] matching the given version string, falling back to
168+
/// [StarknetVersion::LATEST] for versions newer than `LATEST`
169+
pub(crate) fn starknet_version_or_latest(
170+
version_string: &str,
171+
) -> Result<StarknetVersion, StarknetApiError> {
172+
match StarknetVersion::try_from(version_string) {
173+
// `InvalidStarknetVersion` carries the parsed version segments, whose lexicographic order
174+
// matches version order.
175+
Err(StarknetApiError::InvalidStarknetVersion(segments))
176+
if segments > Vec::<u8>::from(StarknetVersion::LATEST) =>
177+
{
178+
Ok(StarknetVersion::LATEST)
179+
}
180+
result => result,
181+
}
182+
}
183+
151184
pub(crate) struct VirtualBlockExecutionData {
152185
/// Execution outputs for all transactions in the virtual block.
153186
pub(crate) execution_outputs: Vec<TransactionExecutionOutput>,

crates/starknet_transaction_prover/src/running/virtual_block_executor_test.rs

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ use assert_matches::assert_matches;
22
use blockifier::blockifier::config::ContractClassManagerConfig;
33
use blockifier::bouncer::{BouncerConfig, BouncerWeights};
44
use blockifier::state::contract_class_manager::ContractClassManager;
5-
use blockifier_reexecution::state_reader::rpc_objects::BlockId;
5+
use blockifier_reexecution::state_reader::rpc_objects::{BlockHeader, BlockId};
66
use blockifier_reexecution::utils::get_chain_info;
77
use rstest::rstest;
88
use starknet_api::abi::abi_utils::{get_storage_var_address, selector_from_name};
9-
use starknet_api::block::BlockNumber;
9+
use starknet_api::block::{BlockNumber, GasPrice, GasPricePerToken, StarknetVersion};
1010
use starknet_api::core::{ChainId, ContractAddress, Nonce};
1111
use starknet_api::test_utils::invoke::invoke_tx;
1212
use starknet_api::transaction::fields::ValidResourceBounds;
@@ -15,6 +15,8 @@ use starknet_api::{calldata, felt, invoke_tx_args};
1515

1616
use crate::errors::VirtualBlockExecutorError;
1717
use crate::running::virtual_block_executor::{
18+
starknet_version_or_latest,
19+
BaseBlockInfo,
1820
RpcVirtualBlockExecutor,
1921
RpcVirtualBlockExecutorConfig,
2022
VirtualBlockExecutor,
@@ -267,3 +269,91 @@ fn test_execute_rejected_by_tight_bouncer_limits(
267269
if msg.contains("Transaction size exceeds the maximum block capacity")
268270
);
269271
}
272+
273+
/// Returns a block header with the given version string and nonzero gas prices (zero gas prices
274+
/// fail the `BlockInfo` conversion).
275+
fn block_header_with_version(starknet_version: &str) -> BlockHeader {
276+
let nonzero_gas_price =
277+
GasPricePerToken { price_in_wei: GasPrice(1), price_in_fri: GasPrice(1) };
278+
BlockHeader {
279+
starknet_version: starknet_version.to_string(),
280+
l1_gas_price: nonzero_gas_price,
281+
l1_data_gas_price: nonzero_gas_price,
282+
l2_gas_price: nonzero_gas_price,
283+
..Default::default()
284+
}
285+
}
286+
287+
/// Verifies that the base block info keeps the raw version string for the OS while falling back
288+
/// to the latest known version for execution when the block's version is newer than this binary.
289+
#[rstest]
290+
#[case::known_version("0.14.2", StarknetVersion::V0_14_2)]
291+
#[case::unknown_newer_version("0.20.0", StarknetVersion::LATEST)]
292+
fn test_base_block_info_starknet_version_handling(
293+
#[case] version_string: &str,
294+
#[case] expected_execution_version: StarknetVersion,
295+
#[values(true, false)] use_latest_versioned_constants: bool,
296+
) {
297+
let base_block_info = BaseBlockInfo::new(
298+
block_header_with_version(version_string),
299+
get_chain_info(&ChainId::Mainnet, None),
300+
use_latest_versioned_constants,
301+
)
302+
.unwrap();
303+
304+
assert_eq!(base_block_info.raw_starknet_version, version_string);
305+
assert_eq!(
306+
base_block_info.block_context.block_info().starknet_version,
307+
expected_execution_version
308+
);
309+
}
310+
311+
#[rstest]
312+
#[case::non_numeric_version("not.a.version")]
313+
#[case::unknown_old_version("0.13.7")]
314+
fn test_base_block_info_rejects_invalid_version(#[case] version_string: &str) {
315+
let base_block_info_result = BaseBlockInfo::new(
316+
block_header_with_version(version_string),
317+
get_chain_info(&ChainId::Mainnet, None),
318+
true,
319+
);
320+
assert!(base_block_info_result.is_err());
321+
}
322+
323+
/// Verifies the version-string parsing of `starknet_version_or_latest`: known version strings
324+
/// parse to their exact variant, and strings strictly newer than `LATEST` fall back to `LATEST`.
325+
#[rstest]
326+
#[case::known_three_segment_version("0.13.2", StarknetVersion::V0_13_2)]
327+
#[case::known_four_segment_version("0.13.1.1", StarknetVersion::V0_13_1_1)]
328+
#[case::newer_minor_version("0.15.0", StarknetVersion::LATEST)]
329+
#[case::newer_major_version("1.0.0", StarknetVersion::LATEST)]
330+
fn test_starknet_version_or_latest_parsing(
331+
#[case] version_string: &str,
332+
#[case] expected_version: StarknetVersion,
333+
) {
334+
assert_eq!(starknet_version_or_latest(version_string).unwrap(), expected_version);
335+
}
336+
337+
/// The exact `LATEST` version string must parse back to `LATEST`, regardless of which version
338+
/// that currently is.
339+
#[test]
340+
fn test_starknet_version_or_latest_roundtrips_latest() {
341+
assert_eq!(
342+
starknet_version_or_latest(&StarknetVersion::LATEST.to_string()).unwrap(),
343+
StarknetVersion::LATEST
344+
);
345+
}
346+
347+
/// Verifies that `starknet_version_or_latest` rejects malformed version strings and unknown
348+
/// versions that are not newer than `LATEST` (only newer-than-`LATEST` versions fall back).
349+
#[rstest]
350+
#[case::unknown_older_version("0.13.7")]
351+
#[case::truncated_latest_version("0.14")]
352+
#[case::non_numeric_version("not.a.version")]
353+
#[case::empty_string("")]
354+
#[case::segment_exceeding_u8("0.14.300")]
355+
#[case::trailing_dot("0.14.3.")]
356+
#[case::release_candidate_suffix("0.15.0-rc.1")]
357+
fn test_starknet_version_or_latest_rejects_invalid_version(#[case] version_string: &str) {
358+
assert!(starknet_version_or_latest(version_string).is_err());
359+
}

0 commit comments

Comments
 (0)