Skip to main content

bittensor_core/codec/
storage.rs

1//! Storage key construction and map-key recovery.
2//!
3//! A storage item's key is
4//! `twox128(pallet_prefix) ++ twox128(item_name) ++ hashed(param)...`.
5//! Reversible hashers (Blake2_128Concat / Twox64Concat / Identity) carry the
6//! raw key material after the hash, which is how map keys decode straight
7//! from the storage key.
8
9// Client-side codec, not runtime code: indexes are guarded by the length
10// checks above them.
11#![allow(clippy::indexing_slicing)]
12
13use sp_core::hashing::{blake2_128, blake2_256, twox_128, twox_64};
14
15use crate::codec::decode::Cursor;
16use crate::codec::value::Value;
17use crate::error::CoreError;
18use crate::runtime::{Runtime, StorageInfo};
19
20/// Apply one metadata-named storage hasher.
21pub fn hash_param(hasher: &str, data: &[u8]) -> Result<Vec<u8>, CoreError> {
22    Ok(match hasher {
23        "Blake2_256" => blake2_256(data).to_vec(),
24        "Blake2_128" => blake2_128(data).to_vec(),
25        "Blake2_128Concat" => {
26            let mut out = blake2_128(data).to_vec();
27            out.extend_from_slice(data);
28            out
29        }
30        "Twox256" => sp_core::hashing::twox_256(data).to_vec(),
31        "Twox128" => twox_128(data).to_vec(),
32        "Twox64Concat" => {
33            let mut out = twox_64(data).to_vec();
34            out.extend_from_slice(data);
35            out
36        }
37        "Identity" => data.to_vec(),
38        other => {
39            return Err(CoreError::Codec(format!(
40                "unknown storage hasher {other:?}"
41            )))
42        }
43    })
44}
45
46/// How many hash bytes precede the raw key material, for hashers whose keys
47/// are recoverable from the storage key.
48pub fn concat_hash_len(hasher: &str) -> Result<usize, CoreError> {
49    match hasher {
50        "Blake2_128Concat" => Ok(16),
51        "Twox64Concat" => Ok(8),
52        "Identity" => Ok(0),
53        other => Err(CoreError::Codec(format!(
54            "cannot recover map keys hashed with {other:?}"
55        ))),
56    }
57}
58
59/// `twox128(prefix) ++ twox128(name)` — the 32-byte item prefix.
60pub fn storage_prefix(entry: &StorageInfo) -> Vec<u8> {
61    let mut out = twox_128(entry.prefix.as_bytes()).to_vec();
62    out.extend_from_slice(&twox_128(entry.name.as_bytes()));
63    out
64}
65
66impl Runtime {
67    /// The full storage key for one item; `params` may be a partial prefix
68    /// (for map iteration).
69    pub fn storage_key(&self, entry: &StorageInfo, params: &[Value]) -> Result<Vec<u8>, CoreError> {
70        if params.len() > entry.key_types.len() {
71            return Err(CoreError::Codec(format!(
72                "{}.{} accepts at most {} parameters, {} given",
73                entry.prefix,
74                entry.name,
75                entry.key_types.len(),
76                params.len()
77            )));
78        }
79        let mut key = storage_prefix(entry);
80        for (index, param) in params.iter().enumerate() {
81            let hasher = entry.hashers.get(index).ok_or_else(|| {
82                CoreError::Codec(format!(
83                    "{}.{} metadata declares no hasher for param #{}",
84                    entry.prefix,
85                    entry.name,
86                    index.saturating_add(1)
87                ))
88            })?;
89            let mut encoded = Vec::new();
90            self.encode_id(entry.key_types[index], param, &mut encoded)?;
91            key.extend_from_slice(&hash_param(hasher, &encoded)?);
92        }
93        Ok(key)
94    }
95
96    /// Recover the free map-key components from one full storage key.
97    ///
98    /// `fixed` is how many leading params were fixed in the queried prefix;
99    /// the remaining components decode from the key's trailing bytes
100    /// (reversible hashers only).
101    pub fn decode_storage_key_params(
102        &self,
103        entry: &StorageInfo,
104        key: &[u8],
105        fixed: usize,
106    ) -> Result<Vec<Value>, CoreError> {
107        let mut prefix_len = 32usize; // two twox128 halves
108        for index in 0..fixed {
109            let hasher = entry.hashers.get(index).ok_or_else(|| {
110                CoreError::Codec(format!(
111                    "{}.{} metadata declares no hasher for param #{}",
112                    entry.prefix,
113                    entry.name,
114                    index.saturating_add(1)
115                ))
116            })?;
117            // Fixed params contribute their full hashed length; for
118            // non-reversible hashers that length is fixed too.
119            let encoded_len = match hasher.as_str() {
120                "Blake2_256" | "Twox256" => 32,
121                "Blake2_128" | "Twox128" => 16,
122                _ => {
123                    // Reversible: hash prefix + the raw material; we cannot
124                    // know the material length without decoding, so decode it.
125                    let hash_len = concat_hash_len(hasher)?;
126                    let rest = key
127                        .get(prefix_len.saturating_add(hash_len)..)
128                        .ok_or_else(|| {
129                            CoreError::Codec("storage key shorter than its prefix".into())
130                        })?;
131                    let mut cursor = Cursor::new(rest);
132                    self.decode_id(entry.key_types[index], &mut cursor)?;
133                    hash_len.saturating_add(cursor.offset)
134                }
135            };
136            prefix_len = prefix_len.saturating_add(encoded_len);
137        }
138
139        let trailing = key
140            .get(prefix_len..)
141            .ok_or_else(|| CoreError::Codec("storage key shorter than its prefix".into()))?;
142        let mut cursor = Cursor::new(trailing);
143        let mut out = Vec::new();
144        for index in fixed..entry.key_types.len() {
145            let hasher = entry.hashers.get(index).ok_or_else(|| {
146                CoreError::Codec(format!(
147                    "{}.{} metadata declares no hasher for param #{}",
148                    entry.prefix,
149                    entry.name,
150                    index.saturating_add(1)
151                ))
152            })?;
153            let skip = concat_hash_len(hasher)?;
154            cursor.take(skip)?;
155            out.push(self.decode_id(entry.key_types[index], &mut cursor)?);
156        }
157        if cursor.remaining() != 0 {
158            return Err(CoreError::Codec(format!(
159                "{} undecoded bytes remain in storage key",
160                cursor.remaining()
161            )));
162        }
163        Ok(out)
164    }
165}