use core::marker::PhantomData;
use borsh::{BorshDeserialize, BorshSerialize};
use bytes::BufMut;
use casper_executor_wasm_common::keyspace::Keyspace;
use const_fnv1a_hash::fnv1a_hash_64;
use crate::casper::{self, read_into_vec};
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Copy, PartialEq)]
pub struct IterableMapPtr {
pub(crate) hash: u64,
pub(crate) index: u64,
}
pub trait IterableMapHash: PartialEq + BorshSerialize + BorshDeserialize {
fn compute_hash(&self) -> u64 {
let mut bytes = Vec::new();
self.serialize(&mut bytes).unwrap();
fnv1a_hash_64(&bytes, None)
}
}
impl IterableMapHash for u8 {}
impl IterableMapHash for u16 {}
impl IterableMapHash for u32 {}
impl IterableMapHash for u64 {}
impl IterableMapHash for u128 {}
impl IterableMapHash for i8 {}
impl IterableMapHash for i16 {}
impl IterableMapHash for i32 {}
impl IterableMapHash for i64 {}
impl IterableMapHash for i128 {}
impl IterableMapHash for String {}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
#[borsh(crate = "crate::serializers::borsh")]
pub struct IterableMap<K, V> {
pub(crate) prefix: String,
pub(crate) tail_key_hash: Option<IterableMapPtr>,
_marker: PhantomData<(K, V)>,
}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
#[borsh(crate = "crate::serializers::borsh")]
pub struct IterableMapEntry<K, V> {
pub(crate) key: K,
pub(crate) value: Option<V>,
pub(crate) previous: Option<IterableMapPtr>,
}
impl<K, V> IterableMap<K, V>
where
K: IterableMapHash,
V: BorshSerialize + BorshDeserialize,
{
pub fn new<S: Into<String>>(prefix: S) -> Self {
Self {
prefix: prefix.into(),
tail_key_hash: None,
_marker: PhantomData,
}
}
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
let (ptr, at_ptr) = self.get_writable_slot(&key);
let (entry_to_write, previous) = match at_ptr {
Some(mut entry) => {
if entry.value.is_none() {
entry.key = key;
entry.previous = self.tail_key_hash;
entry.value = Some(value);
self.tail_key_hash = Some(ptr);
(entry, None)
} else {
let old = entry.value;
entry.value = Some(value);
(entry, old)
}
}
None => {
let entry = IterableMapEntry {
key,
value: Some(value),
previous: self.tail_key_hash,
};
self.tail_key_hash = Some(ptr);
(entry, None)
}
};
let mut entry_bytes = Vec::new();
entry_to_write.serialize(&mut entry_bytes).unwrap();
let prefix = self.create_prefix_from_ptr(&ptr);
let keyspace = Keyspace::Context(&prefix);
casper::write(keyspace, &entry_bytes).unwrap();
previous
}
pub fn get(&self, key: &K) -> Option<V> {
let (_, at_ptr) = self.get_writable_slot(key);
at_ptr.and_then(|entry| entry.value)
}
pub fn remove(&mut self, key: &K) -> Option<V> {
let (to_remove_ptr, at_remove_ptr) = self.find_slot(key)?;
let to_remove_prefix = self.create_prefix_from_ptr(&to_remove_ptr);
let to_remove_context_key = Keyspace::Context(&to_remove_prefix);
let to_remove_ptr_child_prefix = self.create_prefix_from_ptr(&IterableMapPtr {
index: to_remove_ptr.index + 1,
..to_remove_ptr
});
let to_remove_ptr_child_keyspace = Keyspace::Context(&to_remove_ptr_child_prefix);
if self.get_entry(to_remove_ptr_child_keyspace).is_some() {
let tombstone = IterableMapEntry {
value: None,
..at_remove_ptr
};
let mut entry_bytes = Vec::new();
tombstone.serialize(&mut entry_bytes).unwrap();
casper::write(to_remove_context_key, &entry_bytes).unwrap();
} else {
casper::remove(to_remove_context_key).unwrap();
}
if self.tail_key_hash == Some(to_remove_ptr) {
self.tail_key_hash = at_remove_ptr.previous;
return at_remove_ptr.value;
}
let mut current_hash = self.tail_key_hash;
while let Some(key) = current_hash {
let current_prefix = self.create_prefix_from_ptr(&key);
let current_context_key = Keyspace::Context(¤t_prefix);
let mut current_entry = self.get_entry(current_context_key).unwrap();
let Some(next_hash) = current_entry.previous else {
panic!("Unexpected end of IterableMap");
};
if next_hash == to_remove_ptr {
current_entry.previous = at_remove_ptr.previous;
let mut entry_bytes = Vec::new();
current_entry.serialize(&mut entry_bytes).unwrap();
casper::write(current_context_key, &entry_bytes).unwrap();
return at_remove_ptr.value;
}
current_hash = current_entry.previous;
}
None
}
pub fn clear(&mut self) {
for key in self.keys() {
let prefix = self.create_prefix_from_key(&key);
{
let key = Keyspace::Context(&prefix);
casper::remove(key).unwrap()
};
}
self.tail_key_hash = None;
}
pub fn contains_key(&self, key: &K) -> bool {
self.get(key).is_some()
}
pub fn keys(&self) -> impl Iterator<Item = K> + '_ {
self.iter().map(|(key, _)| key)
}
pub fn values(&self) -> impl Iterator<Item = V> + '_ {
self.iter().map(|(_, value)| value)
}
pub fn is_empty(&self) -> bool {
self.tail_key_hash.is_none()
}
pub fn iter(&self) -> IterableMapIter<K, V> {
IterableMapIter {
prefix: &self.prefix,
current: self.tail_key_hash,
_marker: PhantomData,
}
}
pub fn len(&self) -> usize {
self.iter().count()
}
fn find_slot(&self, key: &K) -> Option<(IterableMapPtr, IterableMapEntry<K, V>)> {
let mut bucket_ptr = self.create_root_ptr_from_key(key);
loop {
let prefix = self.create_prefix_from_ptr(&bucket_ptr);
let keyspace = Keyspace::Context(&prefix);
if let Some(entry) = self.get_entry(keyspace) {
if entry.key == *key && entry.value.is_some() {
return Some((bucket_ptr, entry));
} else {
bucket_ptr.index += 1;
continue;
}
} else {
return None;
}
}
}
fn get_writable_slot(&self, key: &K) -> (IterableMapPtr, Option<IterableMapEntry<K, V>>) {
let mut bucket_ptr = self.create_root_ptr_from_key(key);
loop {
let prefix = self.create_prefix_from_ptr(&bucket_ptr);
let keyspace = Keyspace::Context(&prefix);
if let Some(entry) = self.get_entry(keyspace) {
if entry.key == *key {
return (bucket_ptr, Some(entry));
} else if entry.value.is_none() {
return (bucket_ptr, Some(entry));
} else {
bucket_ptr.index += 1;
continue;
}
} else {
return (bucket_ptr, None);
}
}
}
fn get_entry(&self, keyspace: Keyspace) -> Option<IterableMapEntry<K, V>> {
match read_into_vec(keyspace) {
Ok(Some(vec)) => {
let entry: IterableMapEntry<K, V> = borsh::from_slice(&vec).unwrap();
Some(entry)
}
Ok(None) => None,
Err(_) => None,
}
}
fn create_prefix_from_key(&self, key: &K) -> Vec<u8> {
let ptr = self.create_root_ptr_from_key(key);
self.create_prefix_from_ptr(&ptr)
}
fn create_root_ptr_from_key(&self, key: &K) -> IterableMapPtr {
IterableMapPtr {
hash: key.compute_hash(),
index: 0,
}
}
fn create_prefix_from_ptr(&self, hash: &IterableMapPtr) -> Vec<u8> {
let mut context_key = Vec::new();
context_key.extend(self.prefix.as_bytes());
context_key.extend(b"_");
context_key.put_u64_le(hash.hash);
context_key.extend(b"_");
context_key.put_u64_le(hash.index);
context_key
}
}
pub struct IterableMapIter<'a, K, V> {
prefix: &'a str,
current: Option<IterableMapPtr>,
_marker: PhantomData<(K, V)>,
}
impl<'a, K, V> IntoIterator for &'a IterableMap<K, V>
where
K: BorshDeserialize,
V: BorshDeserialize,
{
type Item = (K, V);
type IntoIter = IterableMapIter<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
IterableMapIter {
prefix: &self.prefix,
current: self.tail_key_hash,
_marker: PhantomData,
}
}
}
impl<K, V> Iterator for IterableMapIter<'_, K, V>
where
K: BorshDeserialize,
V: BorshDeserialize,
{
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
let current_hash = self.current?;
let mut key_bytes = Vec::new();
key_bytes.extend(self.prefix.as_bytes());
key_bytes.extend(b"_");
key_bytes.put_u64_le(current_hash.hash);
key_bytes.extend(b"_");
key_bytes.put_u64_le(current_hash.index);
let context_key = Keyspace::Context(&key_bytes);
match read_into_vec(context_key) {
Ok(Some(vec)) => {
let entry: IterableMapEntry<K, V> = borsh::from_slice(&vec).unwrap();
self.current = entry.previous;
Some((
entry.key,
entry
.value
.expect("Tombstone values should be unlinked on removal"),
))
}
Ok(None) => None,
Err(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::casper::native::dispatch;
const TEST_MAP_PREFIX: &str = "test_map";
#[test]
fn insert_and_get() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
assert_eq!(map.len(), 0);
assert_eq!(map.get(&1), None);
map.insert(1, "a".to_string());
assert_eq!(map.len(), 1);
assert_eq!(map.get(&1), Some("a".to_string()));
map.insert(2, "b".to_string());
assert_eq!(map.len(), 2);
assert_eq!(map.get(&2), Some("b".to_string()));
})
.unwrap();
}
#[test]
fn overwrite_existing_key() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
assert_eq!(map.insert(1, "a".to_string()), None);
assert_eq!(map.insert(1, "b".to_string()), Some("a".to_string()));
assert_eq!(map.get(&1), Some("b".to_string()));
})
.unwrap();
}
#[test]
fn remove_tail_entry() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
assert_eq!(map.len(), 0);
map.insert(1, "a".to_string());
assert_eq!(map.len(), 1);
map.insert(2, "b".to_string());
assert_eq!(map.len(), 2);
assert_eq!(map.remove(&2), Some("b".to_string()));
assert_eq!(map.len(), 1);
assert_eq!(map.get(&2), None);
assert_eq!(map.get(&1), Some("a".to_string()));
})
.unwrap();
}
#[test]
fn remove_middle_entry() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
assert_eq!(map.len(), 0);
map.insert(1, "a".to_string());
assert_eq!(map.len(), 1);
map.insert(2, "b".to_string());
assert_eq!(map.len(), 2);
map.insert(3, "c".to_string());
assert_eq!(map.len(), 3);
assert_eq!(map.remove(&2), Some("b".to_string()));
assert_eq!(map.len(), 2);
assert_eq!(map.get(&2), None);
assert_eq!(map.get(&1), Some("a".to_string()));
assert_eq!(map.get(&3), Some("c".to_string()));
assert_eq!(map.len(), 2);
})
.unwrap();
}
#[test]
fn remove_nonexistent_key_does_nothing() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "a".to_string());
assert_eq!(map.remove(&999), None);
assert_eq!(map.get(&1), Some("a".to_string()));
})
.unwrap();
}
#[test]
fn iterates_all_entries_in_reverse_insertion_order() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "a".to_string());
map.insert(2, "b".to_string());
map.insert(3, "c".to_string());
let values: Vec<_> = map.values().collect();
assert_eq!(
values,
vec!["c".to_string(), "b".to_string(), "a".to_string(),]
);
})
.unwrap();
}
#[test]
fn iteration_skips_deleted_entries() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "a".to_string());
map.insert(2, "b".to_string());
map.insert(3, "c".to_string());
map.remove(&2);
let values: Vec<_> = map.values().collect();
assert_eq!(values, vec!["c".to_string(), "a".to_string(),]);
})
.unwrap();
}
#[test]
fn empty_map_behaves_sanely() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
assert_eq!(map.get(&1), None);
assert_eq!(map.remove(&1), None);
assert_eq!(map.iter().count(), 0);
})
.unwrap();
}
#[test]
fn separate_maps_do_not_conflict() {
dispatch(|| {
let mut map1 = IterableMap::<u64, String>::new("map1");
let mut map2 = IterableMap::<u64, String>::new("map2");
map1.insert(1, "a".to_string());
map2.insert(1, "b".to_string());
assert_eq!(map1.get(&1), Some("a".to_string()));
assert_eq!(map2.get(&1), Some("b".to_string()));
})
.unwrap();
}
#[test]
fn insert_same_value_under_different_keys() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "shared".to_string());
map.insert(2, "shared".to_string());
assert_eq!(map.get(&1), Some("shared".to_string()));
assert_eq!(map.get(&2), Some("shared".to_string()));
})
.unwrap();
}
#[test]
fn clear_removes_all_entries() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "a".to_string());
map.insert(2, "b".to_string());
map.clear();
assert!(map.is_empty());
assert_eq!(map.iter().count(), 0);
})
.unwrap();
}
#[test]
fn keys_returns_reverse_insertion_order() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "a".to_string());
map.insert(2, "b".to_string());
let hashes: Vec<_> = map.keys().collect();
assert_eq!(hashes, vec![2, 1]);
})
.unwrap();
}
#[test]
fn values_returns_values_in_reverse_insertion_order() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "a".to_string());
map.insert(2, "b".to_string());
let values: Vec<_> = map.values().collect();
assert_eq!(values, vec!["b".to_string(), "a".to_string()]);
})
.unwrap();
}
#[test]
fn contains_key_returns_correctly() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
assert!(!map.contains_key(&1));
map.insert(1, "a".to_string());
assert!(map.contains_key(&1));
map.remove(&1);
assert!(!map.contains_key(&1));
})
.unwrap();
}
#[test]
fn multiple_removals_and_insertions() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "a".to_string());
map.insert(2, "b".to_string());
map.insert(3, "c".to_string());
map.remove(&2);
assert_eq!(map.get(&2), None);
assert_eq!(map.get(&1), Some("a".to_string()));
assert_eq!(map.get(&3), Some("c".to_string()));
map.insert(4, "d".to_string());
let values: Vec<_> = map.values().collect();
assert_eq!(values, vec!["d", "c", "a"]);
})
.unwrap();
}
#[test]
fn struct_as_key() {
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq)]
struct TestKey {
id: u64,
name: String,
}
impl IterableMapHash for TestKey {}
dispatch(|| {
let key1 = TestKey {
id: 1,
name: "Key1".to_string(),
};
let key2 = TestKey {
id: 2,
name: "Key2".to_string(),
};
let mut map = IterableMap::<TestKey, String>::new(TEST_MAP_PREFIX);
map.insert(key1.clone(), "a".to_string());
map.insert(key2.clone(), "b".to_string());
assert_eq!(map.get(&key1), Some("a".to_string()));
assert_eq!(map.get(&key2), Some("b".to_string()));
})
.unwrap();
}
#[test]
fn remove_middle_of_long_chain() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "a".to_string());
map.insert(2, "b".to_string());
map.insert(3, "c".to_string());
map.insert(4, "d".to_string());
map.insert(5, "e".to_string());
map.remove(&3);
let values: Vec<_> = map.values().collect();
assert_eq!(values, vec!["e", "d", "b", "a"]);
let ptr4 = map.create_root_ptr_from_key(&4u64);
let prefix = map.create_prefix_from_ptr(&ptr4);
let entry = map.get_entry(Keyspace::Context(&prefix)).unwrap();
assert_eq!(entry.previous, Some(map.create_root_ptr_from_key(&2u64)));
})
.unwrap();
}
#[test]
fn insert_after_remove_updates_head() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "a".to_string());
map.insert(2, "b".to_string());
map.remove(&2);
map.insert(3, "c".to_string());
let values: Vec<_> = map.values().collect();
assert_eq!(values, vec!["c", "a"]);
})
.unwrap();
}
#[test]
fn reinsert_removed_key() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "a".to_string());
map.remove(&1);
map.insert(1, "b".to_string());
assert_eq!(map.get(&1), Some("b".to_string()));
assert_eq!(map.iter().next().unwrap().1, "b".to_string());
})
.unwrap();
}
#[test]
fn iteration_reflects_modifications() {
dispatch(|| {
let mut map = IterableMap::<u64, String>::new(TEST_MAP_PREFIX);
map.insert(1, "a".to_string());
map.insert(2, "b".to_string());
let mut iter = map.iter();
assert_eq!(iter.next().unwrap().1, "b".to_string());
map.remove(&2);
map.insert(3, "c".to_string());
let values: Vec<_> = map.values().collect();
assert_eq!(values, vec!["c", "a"]);
})
.unwrap();
}
#[test]
fn unit_struct_as_key() {
#[derive(BorshSerialize, BorshDeserialize, PartialEq)]
struct UnitKey;
impl IterableMapHash for UnitKey {}
dispatch(|| {
let mut map = IterableMap::<UnitKey, String>::new(TEST_MAP_PREFIX);
map.insert(UnitKey, "value".to_string());
assert_eq!(map.get(&UnitKey), Some("value".to_string()));
})
.unwrap();
}
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
struct CollidingKey(u64, u64);
impl IterableMapHash for CollidingKey {
fn compute_hash(&self) -> u64 {
let mut bytes = Vec::new();
self.0.serialize(&mut bytes).unwrap();
fnv1a_hash_64(&bytes, None)
}
}
#[test]
fn basic_collision_handling() {
dispatch(|| {
let mut map = IterableMap::<CollidingKey, String>::new(TEST_MAP_PREFIX);
let k1 = CollidingKey(42, 1);
let k2 = CollidingKey(42, 2);
map.insert(k1.clone(), "first".to_string());
map.insert(k2.clone(), "second".to_string());
assert_eq!(map.get(&k1), Some("first".to_string()));
assert_eq!(map.get(&k2), Some("second".to_string()));
})
.unwrap();
}
#[test]
fn tombstone_handling() {
dispatch(|| {
let mut map = IterableMap::<CollidingKey, String>::new(TEST_MAP_PREFIX);
let k1 = CollidingKey(42, 1);
let k2 = CollidingKey(42, 2);
let k3 = CollidingKey(42, 3);
map.insert(k1.clone(), "first".to_string());
map.insert(k2.clone(), "second".to_string());
map.insert(k3.clone(), "third".to_string());
assert_eq!(map.remove(&k2), Some("second".to_string()));
let (_, entry) = map.get_writable_slot(&k2);
assert!(entry.unwrap().value.is_none());
let values: Vec<_> = map.values().collect();
assert_eq!(values, vec!["third", "first"]);
})
.unwrap();
}
#[test]
fn tombstone_reuse() {
dispatch(|| {
let mut map = IterableMap::<CollidingKey, String>::new(TEST_MAP_PREFIX);
let k1 = CollidingKey(42, 1);
let k2 = CollidingKey(42, 2);
map.insert(k1.clone(), "first".to_string());
map.insert(k2.clone(), "second".to_string());
map.remove(&k1);
map.insert(k1.clone(), "reused".to_string());
assert_eq!(map.get(&k1), Some("reused".to_string()));
assert_eq!(map.get(&k2), Some("second".to_string()));
})
.unwrap();
}
#[test]
fn full_deletion_handling() {
dispatch(|| {
let mut map = IterableMap::<CollidingKey, String>::new(TEST_MAP_PREFIX);
let k1 = CollidingKey(42, 1);
map.insert(k1.clone(), "lonely".to_string());
assert_eq!(map.remove(&k1), Some("lonely".to_string()));
let (_, entry) = map.get_writable_slot(&k1);
assert!(entry.is_none());
})
.unwrap();
}
#[test]
fn collision_chain_iteration() {
dispatch(|| {
let mut map = IterableMap::<CollidingKey, String>::new(TEST_MAP_PREFIX);
let keys = [
CollidingKey(42, 1),
CollidingKey(42, 2),
CollidingKey(42, 3),
];
for (i, k) in keys.iter().enumerate() {
map.insert(k.clone(), format!("value-{}", i));
}
map.remove(&keys[1]);
let values: Vec<_> = map.values().collect();
assert_eq!(values, vec!["value-2", "value-0"]);
})
.unwrap();
}
#[test]
fn complex_collision_chain() {
dispatch(|| {
let mut map = IterableMap::<CollidingKey, String>::new(TEST_MAP_PREFIX);
let keys: Vec<_> = (0..5).map(|i| CollidingKey(42, i)).collect();
for k in &keys {
map.insert(k.clone(), format!("{}", k.1));
}
for k in keys.iter().step_by(2) {
map.remove(k);
}
map.insert(keys[0].clone(), "reinserted".to_string());
map.insert(CollidingKey(42, 5), "new".to_string());
let expected = vec![
("new".to_string(), 5),
("reinserted".to_string(), 0),
("3".to_string(), 3),
("1".to_string(), 1),
];
let results: Vec<_> = map.iter().map(|(k, v)| (v, k.1)).collect();
assert_eq!(results, expected);
})
.unwrap();
}
#[test]
fn cross_bucket_reference() {
dispatch(|| {
let mut map = IterableMap::<CollidingKey, String>::new(TEST_MAP_PREFIX);
let k1 = CollidingKey(1, 0);
let k2 = CollidingKey(2, 0);
let k3 = CollidingKey(1, 1);
map.insert(k1.clone(), "first".to_string());
map.insert(k2.clone(), "second".to_string());
map.insert(k3.clone(), "third".to_string());
map.remove(&k2);
let values: Vec<_> = map.values().collect();
assert_eq!(values, vec!["third", "first"]);
})
.unwrap();
}
}