Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(katana): trie snapshot db #2845

Merged
merged 1 commit into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/katana/storage/db/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ rev = "b34b0d3"
[dev-dependencies]
arbitrary.workspace = true
criterion.workspace = true
proptest = "1.6.0"
rstest.workspace = true
starknet.workspace = true

[features]
Expand Down
28 changes: 20 additions & 8 deletions crates/katana/storage/db/src/models/trie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@
TrieLog,
}

#[derive(Debug, thiserror::Error)]

Check warning on line 45 in crates/katana/storage/db/src/models/trie.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/storage/db/src/models/trie.rs#L45

Added line #L45 was not covered by tests
#[error("unknown trie key type: {0}")]
pub struct TrieDatabaseKeyTypeTryFromError(u8);

impl TryFrom<u8> for TrieDatabaseKeyType {
type Error = TrieDatabaseKeyTypeTryFromError;

fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Trie),
1 => Ok(Self::Flat),
2 => Ok(Self::TrieLog),
invalid => Err(TrieDatabaseKeyTypeTryFromError(invalid)),

Check warning on line 57 in crates/katana/storage/db/src/models/trie.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/storage/db/src/models/trie.rs#L57

Added line #L57 was not covered by tests
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct TrieDatabaseKey {
pub r#type: TrieDatabaseKeyType,
Expand All @@ -68,16 +85,11 @@

if bytes.len() < 2 {
// Need at least type and length bytes
panic!("emptyy buffer")
panic!("empty buffer")

Check warning on line 88 in crates/katana/storage/db/src/models/trie.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/storage/db/src/models/trie.rs#L88

Added line #L88 was not covered by tests
}

let r#type = match bytes[0] {
0 => TrieDatabaseKeyType::Trie,
1 => TrieDatabaseKeyType::Flat,
2 => TrieDatabaseKeyType::TrieLog,
_ => panic!("Invalid trie database key type"),
};

let r#type =
TrieDatabaseKeyType::try_from(bytes[0]).expect("Invalid trie database key type");
let key_len = bytes[1] as usize;

if bytes.len() < 2 + key_len {
Expand Down
102 changes: 101 additions & 1 deletion crates/katana/storage/db/src/trie/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,6 @@ where

// TODO: check if the snapshot exist
fn transaction(&self, id: CommitId) -> Option<(CommitId, Self::Transaction<'_>)> {
dbg!("getting snapshot", id);
Some((id, SnapshotTrieDb::new(self.tx.clone(), id)))
}
}
Expand All @@ -379,3 +378,104 @@ fn to_db_key(key: &DatabaseKey<'_>) -> models::trie::TrieDatabaseKey {
}
}
}

#[cfg(test)]
mod tests {
use katana_primitives::hash::{Pedersen, StarkHash};
use katana_primitives::{felt, hash};
use katana_trie::{verify_proof, ClassesTrie, CommitId};
use starknet::macros::short_string;

use super::TrieDbMut;
use crate::abstraction::Database;
use crate::mdbx::test_utils;
use crate::tables;
use crate::trie::SnapshotTrieDb;

#[test]
fn snapshot() {
let db = test_utils::create_test_db();
let db_tx = db.tx_mut().expect("failed to get tx");

let mut trie = ClassesTrie::new(TrieDbMut::<tables::ClassesTrie, _>::new(&db_tx));

let root0 = {
let entries = [
(felt!("0x9999"), felt!("0xdead")),
(felt!("0x5555"), felt!("0xbeef")),
(felt!("0x1337"), felt!("0xdeadbeef")),
];

for (key, value) in entries {
trie.insert(key, value);
}

trie.commit(0);
trie.root()
};

let root1 = {
let entries = [
(felt!("0x6969"), felt!("0x80085")),
(felt!("0x3333"), felt!("0x420")),
(felt!("0x2222"), felt!("0x7171")),
];

for (key, value) in entries {
trie.insert(key, value);
}

trie.commit(1);
trie.root()
};

assert_ne!(root0, root1);

{
let db = SnapshotTrieDb::<tables::ClassesTrie, _>::new(&db_tx, CommitId::new(0));
let mut snapshot0 = ClassesTrie::new(db);

let snapshot_root0 = snapshot0.root();
assert_eq!(snapshot_root0, root0);

let proofs0 = snapshot0.multiproof(vec![felt!("0x9999")]);
let verify_result0 =
verify_proof::<Pedersen>(&proofs0, snapshot_root0, vec![felt!("0x9999")]);

let value =
hash::Poseidon::hash(&short_string!("CONTRACT_CLASS_LEAF_V0"), &felt!("0xdead"));
assert_eq!(vec![value], verify_result0);
}

{
let commit = CommitId::new(1);
let mut snapshot1 =
ClassesTrie::new(SnapshotTrieDb::<tables::ClassesTrie, _>::new(&db_tx, commit));

let snapshot_root1 = snapshot1.root();
assert_eq!(snapshot_root1, root1);

let proofs1 = snapshot1.multiproof(vec![felt!("0x6969")]);
let verify_result1 =
verify_proof::<Pedersen>(&proofs1, snapshot_root1, vec![felt!("0x6969")]);

let value =
hash::Poseidon::hash(&short_string!("CONTRACT_CLASS_LEAF_V0"), &felt!("0x80085"));
assert_eq!(vec![value], verify_result1);
}

{
let root = trie.root();
let proofs = trie.multiproof(vec![felt!("0x6969"), felt!("0x9999")]);
let result =
verify_proof::<Pedersen>(&proofs, root, vec![felt!("0x6969"), felt!("0x9999")]);

let value0 =
hash::Poseidon::hash(&short_string!("CONTRACT_CLASS_LEAF_V0"), &felt!("0x80085"));
let value1 =
hash::Poseidon::hash(&short_string!("CONTRACT_CLASS_LEAF_V0"), &felt!("0xdead"));

assert_eq!(vec![value0, value1], result);
}
}
}
121 changes: 121 additions & 0 deletions crates/katana/storage/db/src/trie/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,124 @@
unimplemented!("modifying trie snapshot is not supported")
}
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use katana_primitives::felt;
use katana_trie::bonsai::DatabaseKey;
use katana_trie::{BonsaiPersistentDatabase, CommitId};
use proptest::prelude::*;
use proptest::strategy;

use super::*;
use crate::abstraction::{Database, DbTx};
use crate::mdbx::test_utils;
use crate::models::trie::TrieDatabaseKeyType;
use crate::tables;
use crate::trie::{SnapshotTrieDb, TrieDbMut};

#[allow(unused)]
fn arb_db_key_type() -> BoxedStrategy<TrieDatabaseKeyType> {
prop_oneof![
Just(TrieDatabaseKeyType::Trie),
Just(TrieDatabaseKeyType::Flat),
Just(TrieDatabaseKeyType::TrieLog),
]
.boxed()
}

Check warning on line 150 in crates/katana/storage/db/src/trie/snapshot.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/storage/db/src/trie/snapshot.rs#L143-L150

Added lines #L143 - L150 were not covered by tests

#[derive(Debug)]
struct Case {
number: BlockNumber,
keyvalues: HashMap<(TrieDatabaseKeyType, [u8; 32]), [u8; 32]>,
}

prop_compose! {
// This create a strategy that generates a random values but always a hardcoded key
fn arb_keyvalues_with_fixed_key() (
value in any::<[u8;32]>()
) -> HashMap<(TrieDatabaseKeyType, [u8; 32]), [u8; 32]> {
let key = (TrieDatabaseKeyType::Trie, felt!("0x112345678921541231").to_bytes_be());
HashMap::from_iter([(key, value)])
}
}

prop_compose! {
fn arb_keyvalues() (
keyvalues in prop::collection::hash_map(
(arb_db_key_type(), any::<[u8;32]>()),
any::<[u8;32]>(),
1..100
)
) -> HashMap<(TrieDatabaseKeyType, [u8; 32]), [u8; 32]> {
keyvalues
}
}

prop_compose! {
fn arb_block(count: u64, step: u64) (
number in (count * step)..((count * step) + step),
keyvalues in arb_keyvalues_with_fixed_key()
) -> Case {
Case { number, keyvalues }
}
}

/// Strategy for generating a list of blocks with `count` size where each block is within a
/// range of `step` size. See [`arb_block`].
fn arb_blocklist(step: u64, count: usize) -> impl strategy::Strategy<Value = Vec<Case>> {
let mut strats = Vec::with_capacity(count);
for i in 0..count {
strats.push(arb_block(i as u64, step));
}
strategy::Strategy::prop_map(strats, move |strats| strats)
}

proptest! {
#[test]
fn test_get_insert(blocks in arb_blocklist(10, 1000)) {
let db = test_utils::create_test_db();
let tx = db.tx_mut().expect("failed to create rw tx");

for block in &blocks {
let mut trie = TrieDbMut::<tables::ClassesTrie, _>::new(&tx);

// Insert key/value pairs
for ((r#type, key), value) in &block.keyvalues {
let db_key = match r#type {
TrieDatabaseKeyType::Trie => DatabaseKey::Trie(key.as_ref()),
TrieDatabaseKeyType::Flat => DatabaseKey::Flat(key.as_ref()),
TrieDatabaseKeyType::TrieLog => DatabaseKey::TrieLog(key.as_ref()),
};

trie.insert(&db_key, value.as_ref(), None).expect("failed to insert");
}

let snapshot_id = CommitId::from(block.number);
trie.snapshot(snapshot_id);
}

tx.commit().expect("failed to commit tx");
let tx = db.tx().expect("failed to create ro tx");

for block in &blocks {
let snapshot_id = CommitId::from(block.number);
let snapshot_db = SnapshotTrieDb::<tables::ClassesTrie, _>::new(&tx, snapshot_id);

// Verify snapshots
for ((r#type, key), value) in &block.keyvalues {
let db_key = match r#type {
TrieDatabaseKeyType::Trie => DatabaseKey::Trie(key.as_ref()),
TrieDatabaseKeyType::Flat => DatabaseKey::Flat(key.as_ref()),
TrieDatabaseKeyType::TrieLog => DatabaseKey::TrieLog(key.as_ref()),
};

let result = snapshot_db.get(&db_key).unwrap();
prop_assert_eq!(result.as_ref().map(|x| x.as_slice()), Some(value.as_slice()));
}
}
}
}
}
10 changes: 8 additions & 2 deletions crates/katana/trie/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ where
{
pub fn new(db: DB) -> Self {
let config = BonsaiStorageConfig {
max_saved_trie_logs: Some(usize::MAX),
max_saved_snapshots: Some(usize::MAX),
// we have our own implementation of storing trie changes
max_saved_trie_logs: Some(0),
// in the bonsai-trie crate, this field seems to be only used in rocksdb impl.
// i dont understand why would they add a config thats implementation specific ????
//
// this config should be used by our implementation of the
// BonsaiPersistentDatabase::snapshot()
max_saved_snapshots: Some(64usize),
snapshot_interval: 1,
};

Expand Down
Loading