use crate::{
error::InternalError,
value::{Value, hash_value},
};
use std::{
collections::HashMap,
hash::{BuildHasher, Hasher},
};
pub(in crate::db::executor) type StableHash = u64;
pub(in crate::db::executor) type StableHashMap<V> = HashMap<StableHash, V, StableHashBuildHasher>;
#[derive(Clone, Copy, Debug, Default)]
pub(in crate::db::executor) struct StableHashBuildHasher;
#[derive(Clone, Copy, Debug, Default)]
pub(in crate::db::executor) struct StableHashHasher {
hash: u64,
}
impl BuildHasher for StableHashBuildHasher {
type Hasher = StableHashHasher;
fn build_hasher(&self) -> Self::Hasher {
StableHashHasher::default()
}
}
impl Hasher for StableHashHasher {
fn finish(&self) -> u64 {
self.hash
}
fn write(&mut self, bytes: &[u8]) {
let mut hash = 0xcbf2_9ce4_8422_2325_u64;
for byte in bytes {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(0x0000_0100_0000_01B3);
}
self.hash = hash;
}
fn write_u64(&mut self, value: u64) {
self.hash = value;
}
fn write_usize(&mut self, value: usize) {
self.hash = value as u64;
}
}
#[must_use]
pub(in crate::db::executor) const fn stable_hash_from_digest(digest: [u8; 16]) -> StableHash {
u64::from_be_bytes([
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
])
}
pub(in crate::db::executor) fn stable_hash_value(
value: &Value,
) -> Result<StableHash, InternalError> {
let digest = hash_value(value)?;
Ok(stable_hash_from_digest(digest))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{types::Decimal, value::Value};
#[test]
fn stable_hash_uses_digest_prefix_contract() {
let digest = [
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xF0,
0x0A, 0x0B,
];
assert_eq!(
stable_hash_from_digest(digest),
0x1122_3344_5566_7788,
"stable hash must use the canonical leading 64 bits of the value digest",
);
}
#[test]
fn stable_hash_is_deterministic_for_same_value() {
let value = Value::Decimal(Decimal::new(12300, 4));
let left = stable_hash_value(&value).expect("stable hash");
let right = stable_hash_value(&value).expect("stable hash");
assert_eq!(left, right);
}
#[test]
fn stable_hash_respects_canonical_map_order() {
let left = Value::Map(vec![
(Value::Text("z".to_string()), Value::Uint(9)),
(Value::Text("a".to_string()), Value::Uint(1)),
]);
let right = Value::Map(vec![
(Value::Text("a".to_string()), Value::Uint(1)),
(Value::Text("z".to_string()), Value::Uint(9)),
]);
assert_eq!(
stable_hash_value(&left).expect("stable hash"),
stable_hash_value(&right).expect("stable hash"),
"stable hash must not depend on non-canonical map insertion order",
);
}
#[test]
fn stable_hash_contract_vectors_are_frozen_for_upgrade_stability() {
let vectors = vec![
("null", Value::Null, 0x07d3_310a_0679_d482),
("uint_42", Value::Uint(42), 0x8c99_03a0_7f2c_731c),
("int_neg7", Value::Int(-7), 0x7470_6cc5_9093_df80),
(
"text_alpha",
Value::Text("alpha".to_string()),
0x6ec7_96a5_45c2_ad82,
),
(
"decimal_1",
Value::Decimal(Decimal::new(10, 1)),
0x7d42_1e3f_fffc_9100,
),
(
"map_a1_z9",
Value::Map(vec![
(Value::Text("a".to_string()), Value::Uint(1)),
(Value::Text("z".to_string()), Value::Uint(9)),
]),
0xea0e_28c9_f878_6d85,
),
];
for (label, value, expected_hash) in vectors {
let actual_hash = stable_hash_value(&value).expect("stable hash");
assert_eq!(
actual_hash, expected_hash,
"stable hash vector drift for {label}; seed/version/encoding contract changed",
);
}
}
}