use std::collections::{BTreeMap, BTreeSet};
use std::rc::Rc;
use std::string::ToString;
use std::vec::Vec;
use miden_client::account::{
AccountId,
AccountStoragePatch,
StorageMap,
StorageMapPatch,
StorageSlot,
StorageSlotContent,
StorageSlotName,
StorageSlotType,
};
use miden_client::store::{AccountSmtForest, StoreError};
use miden_client::{Deserializable, EMPTY_WORD, Serializable, Word};
use rusqlite::types::Value;
use rusqlite::{OptionalExtension, Transaction, params};
use crate::sql_error::SqlResultExt;
use crate::{SqliteStore, insert_sql, subst, u64_to_value};
impl SqliteStore {
pub(crate) fn get_storage_map_roots_for_patch(
conn: &rusqlite::Connection,
account_id: AccountId,
storage_patch: &AccountStoragePatch,
) -> Result<BTreeMap<StorageSlotName, Word>, StoreError> {
let map_slot_names: Vec<Value> = storage_patch
.maps()
.map(|(slot_name, _)| Value::Text(slot_name.to_string()))
.collect();
if map_slot_names.is_empty() {
return Ok(BTreeMap::new());
}
const QUERY: &str = "SELECT slot_name, slot_value FROM latest_account_storage \
WHERE account_id = ? AND slot_name IN rarray(?)";
conn.prepare(QUERY)
.into_store_error()?
.query_map(params![account_id.to_bytes(), Rc::new(map_slot_names)], |row| {
let name: String = row.get(0)?;
let value: Vec<u8> = row.get(1)?;
Ok((name, value))
})
.into_store_error()?
.map(|result| {
let (name, value) = result.into_store_error()?;
let slot_name = StorageSlotName::new(name)
.map_err(|err| StoreError::ParsingError(err.to_string()))?;
Ok((slot_name, Word::read_from_bytes(&value)?))
})
.collect()
}
pub(crate) fn insert_storage_slots<'a>(
tx: &Transaction<'_>,
account_id: AccountId,
account_storage: impl Iterator<Item = &'a StorageSlot>,
) -> Result<(), StoreError> {
const LATEST_SLOT_QUERY: &str = insert_sql!(
latest_account_storage {
account_id,
slot_name,
slot_value,
slot_type
} | REPLACE
);
const LATEST_MAP_ENTRY_QUERY: &str =
insert_sql!(latest_storage_map_entries { account_id, slot_name, key, value } | REPLACE);
let mut latest_slot_stmt = tx.prepare_cached(LATEST_SLOT_QUERY).into_store_error()?;
let mut latest_map_stmt = tx.prepare_cached(LATEST_MAP_ENTRY_QUERY).into_store_error()?;
let account_id_bytes = account_id.to_bytes();
for slot in account_storage {
let slot_name_str = slot.name().to_string();
let slot_value_bytes = slot.value().to_bytes();
let slot_type_val = slot.slot_type() as u8;
latest_slot_stmt
.execute(params![
&account_id_bytes,
&slot_name_str,
&slot_value_bytes,
slot_type_val
])
.into_store_error()?;
if let StorageSlotContent::Map(map) = slot.content() {
for (key, value) in map.entries() {
latest_map_stmt
.execute(params![
&account_id_bytes,
&slot_name_str,
key.to_bytes(),
value.to_bytes(),
])
.into_store_error()?;
}
}
}
Ok(())
}
pub(crate) fn write_storage_patch(
tx: &Transaction<'_>,
account_id: AccountId,
nonce: u64,
updated_slots: &BTreeMap<StorageSlotName, (Word, StorageSlotType)>,
storage_patch: &AccountStoragePatch,
) -> Result<(), StoreError> {
const LATEST_SLOT_QUERY: &str = insert_sql!(
latest_account_storage {
account_id,
slot_name,
slot_value,
slot_type
} | REPLACE
);
const HISTORICAL_SLOT_QUERY: &str = insert_sql!(
historical_account_storage {
account_id,
replaced_at_nonce,
slot_name,
old_slot_value,
slot_type
} | REPLACE
);
const LATEST_MAP_ENTRY_QUERY: &str =
insert_sql!(latest_storage_map_entries { account_id, slot_name, key, value } | REPLACE);
const HISTORICAL_MAP_ENTRY_QUERY: &str = insert_sql!(
historical_storage_map_entries {
account_id,
replaced_at_nonce,
slot_name,
key,
old_value
} | REPLACE
);
const READ_OLD_SLOT: &str =
"SELECT slot_value FROM latest_account_storage WHERE account_id = ? AND slot_name = ?";
let mut latest_slot_stmt = tx.prepare_cached(LATEST_SLOT_QUERY).into_store_error()?;
let mut hist_slot_stmt = tx.prepare_cached(HISTORICAL_SLOT_QUERY).into_store_error()?;
let mut latest_map_stmt = tx.prepare_cached(LATEST_MAP_ENTRY_QUERY).into_store_error()?;
let mut hist_map_stmt = tx.prepare_cached(HISTORICAL_MAP_ENTRY_QUERY).into_store_error()?;
let account_id_bytes = account_id.to_bytes();
let nonce_val = u64_to_value(nonce);
let patch_maps: BTreeMap<&StorageSlotName, &StorageMapPatch> =
storage_patch.maps().collect();
for (slot_name, (value, slot_type)) in updated_slots {
let slot_name_str = slot_name.to_string();
let slot_value_bytes = value.to_bytes();
let slot_type_val = *slot_type as u8;
let old_slot_value: Option<Vec<u8>> = tx
.query_row(READ_OLD_SLOT, params![&account_id_bytes, &slot_name_str], |row| {
row.get(0)
})
.optional()
.into_store_error()?
.flatten();
hist_slot_stmt
.execute(params![
&account_id_bytes,
&nonce_val,
&slot_name_str,
old_slot_value,
slot_type_val,
])
.into_store_error()?;
latest_slot_stmt
.execute(params![
&account_id_bytes,
&slot_name_str,
&slot_value_bytes,
slot_type_val
])
.into_store_error()?;
if let Some(map_patch) = patch_maps.get(slot_name) {
Self::write_map_patch(
tx,
&mut latest_map_stmt,
&mut hist_map_stmt,
&account_id_bytes,
&nonce_val,
&slot_name_str,
map_patch,
)?;
}
}
Ok(())
}
fn write_map_patch(
tx: &Transaction<'_>,
latest_map_stmt: &mut rusqlite::CachedStatement<'_>,
hist_map_stmt: &mut rusqlite::CachedStatement<'_>,
account_id_bytes: &[u8],
nonce_val: &rusqlite::types::Value,
slot_name_str: &str,
map_patch: &StorageMapPatch,
) -> Result<(), StoreError> {
match map_patch {
StorageMapPatch::Update { entries } => {
let changed: Vec<(Word, Word)> =
entries.as_map().iter().map(|(key, value)| ((*key).into(), *value)).collect();
Self::write_map_entry_delta(
tx,
latest_map_stmt,
hist_map_stmt,
account_id_bytes,
nonce_val,
slot_name_str,
&changed,
)
},
StorageMapPatch::Create { entries } => {
let new_entries: Vec<(Word, Word)> =
entries.as_map().iter().map(|(key, value)| ((*key).into(), *value)).collect();
Self::replace_map_entries(
tx,
latest_map_stmt,
hist_map_stmt,
account_id_bytes,
nonce_val,
slot_name_str,
&new_entries,
)
},
StorageMapPatch::Remove => Self::replace_map_entries(
tx,
latest_map_stmt,
hist_map_stmt,
account_id_bytes,
nonce_val,
slot_name_str,
&[],
),
}
}
fn replace_map_entries(
tx: &Transaction<'_>,
latest_map_stmt: &mut rusqlite::CachedStatement<'_>,
hist_map_stmt: &mut rusqlite::CachedStatement<'_>,
account_id_bytes: &[u8],
nonce_val: &rusqlite::types::Value,
slot_name_str: &str,
new_entries: &[(Word, Word)],
) -> Result<(), StoreError> {
const READ_ALL_MAP_ENTRIES: &str = "SELECT key, value FROM latest_storage_map_entries WHERE account_id = ? AND slot_name = ?";
const DELETE_ALL_MAP_ENTRIES: &str =
"DELETE FROM latest_storage_map_entries WHERE account_id = ? AND slot_name = ?";
let existing: BTreeMap<Vec<u8>, Vec<u8>> = {
let mut read_stmt = tx.prepare_cached(READ_ALL_MAP_ENTRIES).into_store_error()?;
let rows = read_stmt
.query_map(params![account_id_bytes, slot_name_str], |row| {
Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, Vec<u8>>(1)?))
})
.into_store_error()?;
rows.collect::<Result<_, _>>().into_store_error()?
};
let new_map: BTreeMap<Vec<u8>, Vec<u8>> = new_entries
.iter()
.filter(|(_, value)| *value != EMPTY_WORD)
.map(|(key, value)| (key.to_bytes(), value.to_bytes()))
.collect();
let mut affected: BTreeSet<&Vec<u8>> = existing.keys().collect();
affected.extend(new_map.keys());
for key_bytes in affected {
let old_value = existing.get(key_bytes).cloned();
hist_map_stmt
.execute(params![account_id_bytes, nonce_val, slot_name_str, key_bytes, old_value])
.into_store_error()?;
}
tx.execute(DELETE_ALL_MAP_ENTRIES, params![account_id_bytes, slot_name_str])
.into_store_error()?;
for (key_bytes, value_bytes) in &new_map {
latest_map_stmt
.execute(params![account_id_bytes, slot_name_str, key_bytes, value_bytes])
.into_store_error()?;
}
Ok(())
}
fn write_map_entry_delta(
tx: &Transaction<'_>,
latest_map_stmt: &mut rusqlite::CachedStatement<'_>,
hist_map_stmt: &mut rusqlite::CachedStatement<'_>,
account_id_bytes: &[u8],
nonce_val: &rusqlite::types::Value,
slot_name_str: &str,
changed_entries: &[(Word, Word)],
) -> Result<(), StoreError> {
const READ_OLD_MAP_ENTRY: &str = "SELECT value FROM latest_storage_map_entries WHERE account_id = ? AND slot_name = ? AND key = ?";
const DELETE_LATEST_MAP_ENTRY: &str = "DELETE FROM latest_storage_map_entries WHERE account_id = ? AND slot_name = ? AND key = ?";
for (key, value) in changed_entries {
let key_bytes = key.to_bytes();
let old_entry_value: Option<Vec<u8>> = tx
.query_row(
READ_OLD_MAP_ENTRY,
params![account_id_bytes, slot_name_str, &key_bytes],
|row| row.get(0),
)
.optional()
.into_store_error()?
.flatten();
hist_map_stmt
.execute(params![
account_id_bytes,
nonce_val,
slot_name_str,
&key_bytes,
old_entry_value,
])
.into_store_error()?;
if *value == EMPTY_WORD {
tx.execute(
DELETE_LATEST_MAP_ENTRY,
params![account_id_bytes, slot_name_str, &key_bytes],
)
.into_store_error()?;
} else {
latest_map_stmt
.execute(
params![account_id_bytes, slot_name_str, &key_bytes, value.to_bytes(),],
)
.into_store_error()?;
}
}
Ok(())
}
pub(crate) fn apply_account_storage_patch(
smt_forest: &mut AccountSmtForest,
old_map_roots: &BTreeMap<StorageSlotName, Word>,
storage_patch: &AccountStoragePatch,
) -> Result<BTreeMap<StorageSlotName, (Word, StorageSlotType)>, StoreError> {
let mut updated_slots: BTreeMap<StorageSlotName, (Word, StorageSlotType)> = storage_patch
.values()
.map(|(slot_name, value_patch)| {
(
slot_name.clone(),
(
value_patch
.value()
.expect("the protocol does not generate Remove value patches"),
StorageSlotType::Value,
),
)
})
.collect();
let default_map_root = StorageMap::default().root();
for (slot_name, map_patch) in storage_patch.maps() {
let base_root = match map_patch {
StorageMapPatch::Update { .. } => {
old_map_roots.get(slot_name).copied().unwrap_or(default_map_root)
},
StorageMapPatch::Create { .. } | StorageMapPatch::Remove => default_map_root,
};
let entries: Vec<_> = map_patch
.entries()
.into_iter()
.flat_map(|e| e.as_map().iter())
.map(|(key, value)| (*key, *value))
.collect();
let new_root = smt_forest.update_storage_map_nodes(base_root, entries.into_iter())?;
updated_slots.insert(slot_name.clone(), (new_root, StorageSlotType::Map));
}
Ok(updated_slots)
}
}