Skip to content

Commit 3f30b74

Browse files
apollo_starknet_os_program: implement calculate_block_hash
1 parent 7193cc2 commit 3f30b74

File tree

5 files changed

+247
-3
lines changed

5 files changed

+247
-3
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from starkware.cairo.common.cairo_builtins import PoseidonBuiltin
2+
from starkware.cairo.common.hash_state_poseidon import (
3+
HashState,
4+
hash_init,
5+
hash_finalize,
6+
hash_update_single,
7+
)
8+
from starkware.starknet.common.new_syscalls import BlockInfo
9+
10+
// The latest block hash version.
11+
const BLOCK_HASH_VERSION = 'STARKNET_BLOCK_HASH1';
12+
13+
struct BlockHeaderCommitments {
14+
transaction_commitment: felt,
15+
event_commitment: felt,
16+
receipt_commitment: felt,
17+
state_diff_commitment: felt,
18+
concatenated_counts: felt,
19+
}
20+
21+
// Calculates the block hash given the top level components.
22+
func calculate_block_hash{poseidon_ptr: PoseidonBuiltin*}(
23+
block_info: BlockInfo*,
24+
header_commitments: BlockHeaderCommitments*,
25+
gas_prices_hash: felt,
26+
state_root: felt,
27+
parent_hash: felt,
28+
starknet_version: felt,
29+
) -> felt {
30+
let hash_state = hash_init();
31+
with hash_state {
32+
hash_update_single(BLOCK_HASH_VERSION);
33+
hash_update_single(block_info.block_number);
34+
hash_update_single(state_root);
35+
hash_update_single(block_info.sequencer_address);
36+
hash_update_single(block_info.block_timestamp);
37+
hash_update_single(header_commitments.concatenated_counts);
38+
hash_update_single(header_commitments.state_diff_commitment);
39+
hash_update_single(header_commitments.transaction_commitment);
40+
hash_update_single(header_commitments.event_commitment);
41+
hash_update_single(header_commitments.receipt_commitment);
42+
hash_update_single(gas_prices_hash);
43+
hash_update_single(starknet_version);
44+
hash_update_single(0);
45+
hash_update_single(parent_hash);
46+
}
47+
48+
let block_hash = hash_finalize(hash_state=hash_state);
49+
return block_hash;
50+
}

crates/starknet_api/src/block_hash/block_hash_calculator.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ mod block_hash_calculator_test;
3333
static STARKNET_BLOCK_HASH0: LazyLock<Felt> = LazyLock::new(|| {
3434
ascii_as_felt("STARKNET_BLOCK_HASH0").expect("ascii_as_felt failed for 'STARKNET_BLOCK_HASH0'")
3535
});
36-
static STARKNET_BLOCK_HASH1: LazyLock<Felt> = LazyLock::new(|| {
36+
pub static STARKNET_BLOCK_HASH1: LazyLock<Felt> = LazyLock::new(|| {
3737
ascii_as_felt("STARKNET_BLOCK_HASH1").expect("ascii_as_felt failed for 'STARKNET_BLOCK_HASH1'")
3838
});
39-
static STARKNET_GAS_PRICES0: LazyLock<Felt> = LazyLock::new(|| {
39+
pub static STARKNET_GAS_PRICES0: LazyLock<Felt> = LazyLock::new(|| {
4040
ascii_as_felt("STARKNET_GAS_PRICES0").expect("ascii_as_felt failed for 'STARKNET_GAS_PRICES0'")
4141
});
4242

@@ -261,7 +261,7 @@ fn to_64_bits(num: usize) -> [u8; 8] {
261261
// Otherwise, returns:
262262
// [gas_price_wei, gas_price_fri, data_gas_price_wei, data_gas_price_fri].
263263
// TODO(Ayelet): add l2_gas_consumed, next_l2_gas_price after 0.14.0.
264-
fn gas_prices_to_hash(
264+
pub fn gas_prices_to_hash(
265265
l1_gas_price: &GasPricePerToken,
266266
l1_data_gas_price: &GasPricePerToken,
267267
l2_gas_price: &GasPricePerToken,

crates/starknet_os/src/hints/hint_implementation.rs

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

0 commit comments

Comments
 (0)