Skip to content

Commit 89fb75c

Browse files
apollo_starknet_os_program: implement calculate_block_hash
1 parent 7193cc2 commit 89fb75c

File tree

5 files changed

+248
-2
lines changed

5 files changed

+248
-2
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: 2 additions & 2 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

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: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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

Comments
 (0)