use scale::{Decode, Encode};
use sp_core::Blake2Hasher;
use sp_trie::{EMPTY_PREFIX, LayoutV1, MemoryDB, StorageProof, TrieDBMutBuilder, TrieHash};
use std::collections::BTreeSet;
const PARAS_HEADS_PREFIX: [u8; 32] = [
0xcd, 0x71, 0x0b, 0x30, 0xbd, 0x2e, 0xab, 0x03, 0x52, 0xdd, 0xcc, 0x26, 0x41, 0x7a, 0xa1, 0x94,
0x1b, 0x3c, 0x25, 0x2f, 0xcb, 0x29, 0xd8, 0x8e, 0xff, 0x4f, 0x3d, 0xe5, 0xde, 0x44, 0x76, 0xc3,
];
pub const CURRENT_SLOT_KEY: [u8; 32] = [
0x1c, 0xb6, 0xf3, 0x6e, 0x02, 0x7a, 0xbb, 0x20, 0x91, 0xcf, 0xb5, 0x11, 0x0a, 0xb5, 0x08, 0x7f,
0x06, 0x15, 0x5b, 0x3c, 0xd9, 0xa8, 0xc9, 0xe5, 0xe9, 0xa2, 0x3f, 0xd5, 0xdc, 0x13, 0xa5, 0xed,
];
pub fn paras_heads_key(para_id: u32) -> Vec<u8> {
let para_id_encoded = para_id.encode();
let hash = sp_core::twox_64(¶_id_encoded);
PARAS_HEADS_PREFIX
.iter()
.chain(hash.iter())
.chain(para_id_encoded.iter())
.copied()
.collect()
}
type RelayLayout = LayoutV1<Blake2Hasher>;
#[derive(Debug, Clone)]
pub enum ProofError {
DecodeError(String),
TrieError(String),
RootNotFound,
KeyNotFound(String),
}
impl std::fmt::Display for ProofError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProofError::DecodeError(msg) => write!(f, "Decode error: {}", msg),
ProofError::TrieError(msg) => write!(f, "Trie error: {}", msg),
ProofError::RootNotFound => write!(f, "Storage root not found in proof"),
ProofError::KeyNotFound(key) => write!(f, "Key not found: {}", key),
}
}
}
impl std::error::Error for ProofError {}
pub fn read_from_proof<T: Decode>(
proof: &StorageProof,
root: &[u8; 32],
key: &[u8],
) -> Result<Option<T>, ProofError> {
use sp_trie::TrieDBBuilder;
use trie_db::Trie;
let db: MemoryDB<Blake2Hasher> = proof.clone().into_memory_db();
let root_hash = TrieHash::<RelayLayout>::from_slice(root);
let trie = TrieDBBuilder::<RelayLayout>::new(&db, &root_hash).build();
match trie.get(key) {
Ok(Some(data)) => T::decode(&mut &data[..])
.map(Some)
.map_err(|e| ProofError::DecodeError(e.to_string())),
Ok(None) => Ok(None),
Err(e) => Err(ProofError::TrieError(format!("Failed to read from trie: {:?}", e))),
}
}
pub fn read_raw_from_proof(
proof: &StorageProof,
root: &[u8; 32],
key: &[u8],
) -> Result<Option<Vec<u8>>, ProofError> {
use sp_trie::TrieDBBuilder;
use trie_db::Trie;
let db: MemoryDB<Blake2Hasher> = proof.clone().into_memory_db();
let root_hash = TrieHash::<RelayLayout>::from_slice(root);
let trie = TrieDBBuilder::<RelayLayout>::new(&db, &root_hash).build();
match trie.get(key) {
Ok(Some(data)) => Ok(Some(data)),
Ok(None) => Ok(None),
Err(e) => Err(ProofError::TrieError(format!("Failed to read from trie: {:?}", e))),
}
}
pub fn modify_proof<'a, I>(
proof: &StorageProof,
root: &[u8; 32],
updates: I,
) -> Result<([u8; 32], StorageProof), ProofError>
where
I: IntoIterator<Item = (&'a [u8], Vec<u8>)>,
{
let mut db: MemoryDB<Blake2Hasher> = proof.clone().into_memory_db();
let mut root_hash = TrieHash::<RelayLayout>::from_slice(root);
{
use sp_trie::TrieMut;
let mut trie =
TrieDBMutBuilder::<RelayLayout>::from_existing(&mut db, &mut root_hash).build();
for (key, value) in updates {
trie.insert(key, &value)
.map_err(|e| ProofError::TrieError(format!("Failed to insert: {:?}", e)))?;
}
trie.commit();
}
let new_proof = extract_proof_from_db(&db);
Ok((root_hash.into(), new_proof))
}
fn extract_proof_from_db(db: &MemoryDB<Blake2Hasher>) -> StorageProof {
use sp_trie::HashDBT;
let mut nodes = BTreeSet::new();
for (key, (value, rc)) in db.clone().drain() {
if rc > 0 {
nodes.insert(value);
}
if let Some(data) = db.get(&key, EMPTY_PREFIX) {
nodes.insert(data);
}
}
StorageProof::new(nodes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paras_heads_prefix_is_correct() {
let pallet_hash = sp_core::twox_128(b"Paras");
let storage_hash = sp_core::twox_128(b"Heads");
let expected: Vec<u8> = pallet_hash.iter().chain(storage_hash.iter()).copied().collect();
assert_eq!(PARAS_HEADS_PREFIX.to_vec(), expected);
}
#[test]
fn current_slot_key_is_correct() {
let pallet_hash = sp_core::twox_128(b"Babe");
let storage_hash = sp_core::twox_128(b"CurrentSlot");
let expected: Vec<u8> = pallet_hash.iter().chain(storage_hash.iter()).copied().collect();
assert_eq!(CURRENT_SLOT_KEY.to_vec(), expected);
}
#[test]
fn paras_heads_key_format_is_correct() {
let para_id: u32 = 1000;
let key = paras_heads_key(para_id);
assert_eq!(key.len(), 44);
assert_eq!(&key[..32], &PARAS_HEADS_PREFIX[..]);
let para_id_encoded = para_id.encode();
let expected_hash = sp_core::twox_64(¶_id_encoded);
assert_eq!(&key[32..40], &expected_hash[..]);
assert_eq!(&key[40..], ¶_id_encoded[..]);
}
}