|
| 1 | +use std::collections::HashMap; |
| 2 | +use std::sync::LazyLock; |
| 3 | + |
| 4 | +use apollo_infra_utils::cairo0_compiler::compile_cairo0_program; |
| 5 | +use cairo_vm::types::builtin_name::BuiltinName; |
| 6 | +use cairo_vm::types::layout_name::LayoutName; |
| 7 | +use cairo_vm::types::program::Program; |
| 8 | +use cairo_vm::types::relocatable::MaybeRelocatable; |
| 9 | +use rstest::rstest; |
| 10 | +use starknet_api::block::{ |
| 11 | + BlockHash, |
| 12 | + BlockNumber, |
| 13 | + BlockTimestamp, |
| 14 | + GasPrice, |
| 15 | + GasPricePerToken, |
| 16 | + StarknetVersion, |
| 17 | +}; |
| 18 | +use starknet_api::block_hash::block_hash_calculator::{ |
| 19 | + calculate_block_hash, |
| 20 | + BlockHashVersion, |
| 21 | + BlockHeaderCommitments, |
| 22 | + PartialBlockHashComponents, |
| 23 | + STARKNET_BLOCK_HASH1, |
| 24 | + STARKNET_GAS_PRICES0, |
| 25 | +}; |
| 26 | +use starknet_api::core::{ |
| 27 | + ascii_as_felt, |
| 28 | + EventCommitment, |
| 29 | + GlobalRoot, |
| 30 | + ReceiptCommitment, |
| 31 | + SequencerContractAddress, |
| 32 | + StateDiffCommitment, |
| 33 | + TransactionCommitment, |
| 34 | +}; |
| 35 | +use starknet_api::hash::PoseidonHash; |
| 36 | +use starknet_types_core::felt::Felt; |
| 37 | +use starknet_types_core::hash::{Poseidon, StarkHash as CoreStarkHash}; |
| 38 | + |
| 39 | +use crate::test_utils::cairo_runner::{ |
| 40 | + initialize_cairo_runner, |
| 41 | + run_cairo_0_entrypoint, |
| 42 | + EndpointArg, |
| 43 | + EntryPointRunnerConfig, |
| 44 | + ImplicitArg, |
| 45 | + PointerArg, |
| 46 | + ValueArg, |
| 47 | +}; |
| 48 | + |
| 49 | +// TODO(Yoni): use the OS program bytes instead once the block hash is reachable by the OS. |
| 50 | +static BLOCK_HASH_PROGRAM_BYTES: LazyLock<Vec<u8>> = LazyLock::new(|| { |
| 51 | + let cairo_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) |
| 52 | + .join("../../crates/apollo_starknet_os_program/src/cairo") |
| 53 | + .canonicalize() |
| 54 | + .unwrap(); |
| 55 | + let block_hash_file = cairo_root.join("starkware/starknet/core/os/block_hash.cairo"); |
| 56 | + compile_cairo0_program(block_hash_file, cairo_root).expect("Failed to compile cairo0 program") |
| 57 | +}); |
| 58 | +static BLOCK_HASH_PROGRAM: LazyLock<Program> = LazyLock::new(|| { |
| 59 | + cairo_vm::types::program::Program::from_bytes(&BLOCK_HASH_PROGRAM_BYTES, None) |
| 60 | + .expect("Failed to load program") |
| 61 | +}); |
| 62 | + |
| 63 | +fn cairo_calculate_block_hash( |
| 64 | + components: &PartialBlockHashComponents, |
| 65 | + state_root: Felt, |
| 66 | + parent_hash: Felt, |
| 67 | +) -> Felt { |
| 68 | + let runner_config = EntryPointRunnerConfig { |
| 69 | + layout: LayoutName::starknet, |
| 70 | + add_main_prefix_to_entrypoint: true, // It's the main file now |
| 71 | + ..Default::default() |
| 72 | + }; |
| 73 | + |
| 74 | + let implicit_args = vec![ImplicitArg::Builtin(BuiltinName::poseidon)]; |
| 75 | + let (mut runner, program, entrypoint) = initialize_cairo_runner( |
| 76 | + &runner_config, |
| 77 | + &BLOCK_HASH_PROGRAM_BYTES, |
| 78 | + "calculate_block_hash", |
| 79 | + &implicit_args, |
| 80 | + HashMap::new(), |
| 81 | + ) |
| 82 | + .expect("Failed to initialize cairo runner"); |
| 83 | + |
| 84 | + let block_info_arg = EndpointArg::Pointer(PointerArg::Array(vec![ |
| 85 | + Felt::from(components.block_number.0).into(), |
| 86 | + Felt::from(components.timestamp.0).into(), |
| 87 | + components.sequencer.0.0.key().into(), |
| 88 | + ])); |
| 89 | + |
| 90 | + let header_commitments_arg = EndpointArg::Pointer(PointerArg::Array(vec![ |
| 91 | + components.header_commitments.transaction_commitment.0.into(), |
| 92 | + components.header_commitments.event_commitment.0.into(), |
| 93 | + components.header_commitments.receipt_commitment.0.into(), |
| 94 | + components.header_commitments.state_diff_commitment.0.0.into(), |
| 95 | + components.header_commitments.concatenated_counts.into(), |
| 96 | + ])); |
| 97 | + |
| 98 | + let gas_prices_hash = Poseidon::hash_array(&[ |
| 99 | + *STARKNET_GAS_PRICES0, |
| 100 | + components.l1_gas_price.price_in_wei.0.into(), |
| 101 | + components.l1_gas_price.price_in_fri.0.into(), |
| 102 | + components.l1_data_gas_price.price_in_wei.0.into(), |
| 103 | + components.l1_data_gas_price.price_in_fri.0.into(), |
| 104 | + components.l2_gas_price.price_in_wei.0.into(), |
| 105 | + components.l2_gas_price.price_in_fri.0.into(), |
| 106 | + ]); |
| 107 | + |
| 108 | + let explicit_args = vec![ |
| 109 | + block_info_arg, |
| 110 | + header_commitments_arg, |
| 111 | + EndpointArg::from(gas_prices_hash), |
| 112 | + EndpointArg::from(state_root), |
| 113 | + EndpointArg::from(parent_hash), |
| 114 | + EndpointArg::from(ascii_as_felt(&components.starknet_version.to_string()).unwrap()), |
| 115 | + ]; |
| 116 | + |
| 117 | + // We expect one felt as return value (block_hash). |
| 118 | + let expected_explicit_return_values = vec![EndpointArg::from(0)]; |
| 119 | + |
| 120 | + let (_, explicit_return_values, _) = run_cairo_0_entrypoint( |
| 121 | + entrypoint, |
| 122 | + &explicit_args, |
| 123 | + &implicit_args, |
| 124 | + None, |
| 125 | + &mut runner, |
| 126 | + &program, |
| 127 | + &runner_config, |
| 128 | + &expected_explicit_return_values, |
| 129 | + ) |
| 130 | + .expect("Failed to run cairo entrypoint"); |
| 131 | + |
| 132 | + match &explicit_return_values[0] { |
| 133 | + EndpointArg::Value(ValueArg::Single(MaybeRelocatable::Int(val))) => *val, |
| 134 | + _ => panic!("Unexpected return value type: {:?}", explicit_return_values[0]), |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +#[rstest] |
| 139 | +fn test_block_hash_cairo() { |
| 140 | + let components = PartialBlockHashComponents { |
| 141 | + block_number: BlockNumber(1), |
| 142 | + timestamp: BlockTimestamp(2), |
| 143 | + sequencer: SequencerContractAddress(Felt::from(3).try_into().unwrap()), |
| 144 | + header_commitments: BlockHeaderCommitments { |
| 145 | + transaction_commitment: TransactionCommitment(Felt::from(4)), |
| 146 | + event_commitment: EventCommitment(Felt::from(5)), |
| 147 | + receipt_commitment: ReceiptCommitment(Felt::from(6)), |
| 148 | + state_diff_commitment: StateDiffCommitment(PoseidonHash(Felt::from(7))), |
| 149 | + concatenated_counts: Felt::from(8), |
| 150 | + }, |
| 151 | + l1_gas_price: GasPricePerToken { price_in_wei: GasPrice(10), price_in_fri: GasPrice(11) }, |
| 152 | + l1_data_gas_price: GasPricePerToken { |
| 153 | + price_in_wei: GasPrice(12), |
| 154 | + price_in_fri: GasPrice(13), |
| 155 | + }, |
| 156 | + l2_gas_price: GasPricePerToken { price_in_wei: GasPrice(14), price_in_fri: GasPrice(15) }, |
| 157 | + starknet_version: StarknetVersion::LATEST, |
| 158 | + }; |
| 159 | + let state_root = Felt::from(16); |
| 160 | + let parent_hash = Felt::from(17); |
| 161 | + |
| 162 | + let cairo_hash = cairo_calculate_block_hash(&components, state_root, parent_hash); |
| 163 | + |
| 164 | + let expected_hash = |
| 165 | + calculate_block_hash(&components, GlobalRoot(state_root), BlockHash(parent_hash)) |
| 166 | + .unwrap() |
| 167 | + .0; |
| 168 | + |
| 169 | + assert_eq!(cairo_hash, expected_hash); |
| 170 | +} |
| 171 | + |
| 172 | +#[rstest] |
| 173 | +fn test_block_hash_version() { |
| 174 | + let (_, cairo_block_hash_version_felt) = BLOCK_HASH_PROGRAM |
| 175 | + .constants |
| 176 | + .iter() |
| 177 | + .find(|(name, _)| name.ends_with("BLOCK_HASH_VERSION")) |
| 178 | + .unwrap(); |
| 179 | + |
| 180 | + let latest_block_hash_version: Felt = |
| 181 | + BlockHashVersion::try_from(StarknetVersion::LATEST).unwrap().into(); |
| 182 | + |
| 183 | + // NOTE: if these check fail, it means the block hash version in the OS program is not the |
| 184 | + // latest, and a backward-compatibility flow must be added for the transition. |
| 185 | + assert_eq!( |
| 186 | + *STARKNET_BLOCK_HASH1, latest_block_hash_version, |
| 187 | + "Latest block hash version constant mismatch" |
| 188 | + ); |
| 189 | + assert_eq!( |
| 190 | + *cairo_block_hash_version_felt, latest_block_hash_version, |
| 191 | + "Cairo BLOCK_HASH_VERSION constant mismatch" |
| 192 | + ); |
| 193 | +} |
0 commit comments