#![cfg(test)]
use std::collections::HashMap;
use rand::{rngs::StdRng, SeedableRng};
use crate::{
auditor::audit_verify,
client::{key_history_verify, lookup_verify},
directory::{Directory, PublishCorruption},
ecvrf::{HardCodedAkdVRF, VRFKeyStorage},
errors::{AkdError, StorageError},
storage::{
manager::StorageManager,
memory::AsyncInMemoryDatabase,
types::{DbRecord, KeyData, ValueState, ValueStateRetrievalFlag},
Database, DbSetState, Storable,
},
tree_node::TreeNodeWithPreviousValue,
AkdLabel, AkdValue, Azks, HistoryParams, HistoryVerificationParams, VerifyResult,
};
#[derive(Clone)]
pub struct LocalDatabase;
unsafe impl Send for LocalDatabase {}
unsafe impl Sync for LocalDatabase {}
mockall::mock! {
pub LocalDatabase {
}
impl Clone for LocalDatabase {
fn clone(&self) -> Self;
}
#[async_trait::async_trait]
impl Database for LocalDatabase {
async fn set(&self, record: DbRecord) -> Result<(), StorageError>;
async fn batch_set(
&self,
records: Vec<DbRecord>,
state: DbSetState,
) -> Result<(), StorageError>;
async fn get<St: Storable>(&self, id: &St::StorageKey) -> Result<DbRecord, StorageError>;
async fn batch_get<St: Storable>(
&self,
ids: &[St::StorageKey],
) -> Result<Vec<DbRecord>, StorageError>;
async fn get_user_data(&self, username: &AkdLabel) -> Result<KeyData, StorageError>;
async fn get_user_state(
&self,
username: &AkdLabel,
flag: ValueStateRetrievalFlag,
) -> Result<ValueState, StorageError>;
async fn get_user_state_versions(
&self,
usernames: &[AkdLabel],
flag: ValueStateRetrievalFlag,
) -> Result<HashMap<AkdLabel, (u64, AkdValue)>, StorageError>;
}
}
fn setup_mocked_db(db: &mut MockLocalDatabase, test_db: &AsyncInMemoryDatabase) {
let tmp_db = test_db.clone();
db.expect_set()
.returning(move |record| futures::executor::block_on(tmp_db.set(record)));
let tmp_db = test_db.clone();
db.expect_batch_set().returning(move |record, other| {
futures::executor::block_on(tmp_db.batch_set(record, other))
});
let tmp_db = test_db.clone();
db.expect_get::<Azks>()
.returning(move |key| futures::executor::block_on(tmp_db.get::<Azks>(key)));
let tmp_db = test_db.clone();
db.expect_get::<TreeNodeWithPreviousValue>()
.returning(move |key| {
futures::executor::block_on(tmp_db.get::<TreeNodeWithPreviousValue>(key))
});
let tmp_db = test_db.clone();
db.expect_get::<Azks>()
.returning(move |key| futures::executor::block_on(tmp_db.get::<Azks>(key)));
let tmp_db = test_db.clone();
db.expect_batch_get::<Azks>()
.returning(move |key| futures::executor::block_on(tmp_db.batch_get::<Azks>(key)));
let tmp_db = test_db.clone();
db.expect_batch_get::<TreeNodeWithPreviousValue>()
.returning(move |key| {
futures::executor::block_on(tmp_db.batch_get::<TreeNodeWithPreviousValue>(key))
});
let tmp_db = test_db.clone();
db.expect_batch_get::<Azks>()
.returning(move |key| futures::executor::block_on(tmp_db.batch_get::<Azks>(key)));
let tmp_db = test_db.clone();
db.expect_get_user_data()
.returning(move |arg| futures::executor::block_on(tmp_db.get_user_data(arg)));
let tmp_db = test_db.clone();
db.expect_get_user_state()
.returning(move |arg, flag| futures::executor::block_on(tmp_db.get_user_state(arg, flag)));
let tmp_db = test_db.clone();
db.expect_get_user_state_versions()
.returning(move |arg, flag| {
futures::executor::block_on(tmp_db.get_user_state_versions(arg, flag))
});
}
#[tokio::test]
async fn test_empty_tree_root_hash() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false).await?;
let current_azks = akd.retrieve_current_azks().await?;
#[allow(unused)]
let hash = akd.get_root_hash(¤t_azks).await?;
#[cfg(feature = "blake3")]
assert_eq!(
"f48ded419214732a2c610c1e280543744bab3c17aec33e444997fa2d8f79792a",
hex::encode(hash)
);
#[cfg(feature = "sha256")]
assert_eq!(
"e60055537d90faaccb2be51f744b9b4a759ff758c1fe1f361d773294dae4f03f",
hex::encode(hash)
);
#[cfg(feature = "sha512")]
assert_eq!(
"720a0846fab801639e172ebe779c3212e9c1802e9fc94454a66285ed168db950c79bb1085fd72b076736412a06a7de37b861ace95317e5dd3a42e7d1fe3ee97d",
hex::encode(hash)
);
#[cfg(feature = "sha512_256")]
assert_eq!(
"eb9c26cb7d83343bb5b5549ea7206278f8a41ed565edf6e22de828cb89bbf68b",
hex::encode(hash)
);
#[cfg(feature = "sha3_256")]
assert_eq!(
"fb0fac36b999155471cd9f5b075685a04bf345b82e248242ee3b396d9d001845",
hex::encode(hash)
);
#[cfg(feature = "sha3_512")]
assert_eq!(
"09db818bf21f53f5443011d4e17d58f011fd64aaacb52ef484102e03ecd3bc6d47b2152dd8404e6e80d1052156635370621c08792b85373e810346948ac7632a",
hex::encode(hash)
);
#[cfg(not(any(
feature = "blake3",
feature = "sha256",
feature = "sha3_256",
feature = "sha512",
feature = "sha3_512",
feature = "sha512_256"
)))]
panic!("Unsupported hashing function");
#[allow(unreachable_code)]
Ok(())
}
#[tokio::test]
async fn test_simple_publish() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false).await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world"))])
.await?;
Ok(())
}
#[tokio::test]
async fn test_complex_publish() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false).await?;
let num_entries = 10000;
let mut entries = vec![];
let mut rng = StdRng::seed_from_u64(42);
for _ in 0..num_entries {
let label = AkdLabel::random(&mut rng);
let value = AkdValue::random(&mut rng);
entries.push((label, value));
}
akd.publish(entries).await?;
Ok(())
}
#[tokio::test]
async fn test_simple_lookup() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false).await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await?;
let (lookup_proof, root_hash) = akd.lookup(AkdLabel::from("hello")).await?;
let vrf_pk = akd.get_public_key().await?;
lookup_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
AkdLabel::from("hello"),
lookup_proof,
)?;
Ok(())
}
#[tokio::test]
async fn test_small_key_history() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false).await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world"))])
.await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world2"))])
.await?;
let (key_history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await?;
let vrf_pk = akd.get_public_key().await?;
let result = key_history_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
key_history_proof,
HistoryVerificationParams::default(),
)?;
assert_eq!(
result,
vec![
VerifyResult {
epoch: 2,
version: 2,
value: AkdValue::from("world2"),
},
VerifyResult {
epoch: 1,
version: 1,
value: AkdValue::from("world"),
},
]
);
Ok(())
}
#[tokio::test]
async fn test_simple_key_history() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false).await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_2")),
(AkdLabel::from("hello2"), AkdValue::from("world2_2")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world3")),
(AkdLabel::from("hello2"), AkdValue::from("world4")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world")),
(AkdLabel::from("hello4"), AkdValue::from("world2")),
])
.await?;
akd.publish(vec![(
AkdLabel::from("hello"),
AkdValue::from("world_updated"),
)])
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world6")),
(AkdLabel::from("hello4"), AkdValue::from("world12")),
])
.await?;
let (key_history_proof, _) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await?;
if key_history_proof.update_proofs.len() != 4 {
return Err(AkdError::TestErr(format!(
"Key history proof should have 4 update_proofs but has {:?}",
key_history_proof.update_proofs.len()
)));
}
let current_azks = akd.retrieve_current_azks().await?;
let current_epoch = current_azks.get_latest_epoch();
let root_hash = akd.get_root_hash(¤t_azks).await?;
let vrf_pk = akd.get_public_key().await?;
key_history_verify(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
AkdLabel::from("hello"),
key_history_proof,
HistoryVerificationParams::default(),
)?;
let (key_history_proof, _) = akd
.key_history(&AkdLabel::from("hello2"), HistoryParams::default())
.await?;
if key_history_proof.update_proofs.len() != 3 {
return Err(AkdError::TestErr(format!(
"Key history proof should have 3 update_proofs but has {:?}",
key_history_proof.update_proofs.len()
)));
}
key_history_verify(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
AkdLabel::from("hello2"),
key_history_proof,
HistoryVerificationParams::default(),
)?;
let (key_history_proof, _) = akd
.key_history(&AkdLabel::from("hello3"), HistoryParams::default())
.await?;
if key_history_proof.update_proofs.len() != 2 {
return Err(AkdError::TestErr(format!(
"Key history proof should have 2 update_proofs but has {:?}",
key_history_proof.update_proofs.len()
)));
}
key_history_verify(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
AkdLabel::from("hello3"),
key_history_proof,
HistoryVerificationParams::default(),
)?;
let (key_history_proof, _) = akd
.key_history(&AkdLabel::from("hello4"), HistoryParams::default())
.await?;
if key_history_proof.update_proofs.len() != 2 {
return Err(AkdError::TestErr(format!(
"Key history proof should have 2 update_proofs but has {:?}",
key_history_proof.update_proofs.len()
)));
}
key_history_verify(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
AkdLabel::from("hello4"),
key_history_proof.clone(),
HistoryVerificationParams::default(),
)?;
let mut borked_proof = key_history_proof;
borked_proof.update_proofs = borked_proof.update_proofs.into_iter().rev().collect();
let result = key_history_verify(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
AkdLabel::from("hello4"),
borked_proof,
HistoryVerificationParams::default(),
);
assert!(matches!(result, Err(_)), "{}", "{result:?}");
Ok(())
}
#[tokio::test]
async fn test_complex_verification_many_versions() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage_manager = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage_manager, vrf, false).await?;
let vrf_pk = akd.get_public_key().await?;
let num_labels = 4;
let num_iterations = 20;
let mut previous_hash = [0u8; crate::DIGEST_BYTES];
for epoch in 1..num_iterations {
let mut to_insert = vec![];
for i in 0..num_labels {
let index = 1 << i;
let label = AkdLabel::from(format!("{index}").as_str());
let value = AkdValue::from(format!("{index},{epoch}").as_str());
if epoch % index == 0 {
to_insert.push((label, value));
}
}
let epoch_hash = akd.publish(to_insert).await?;
if epoch > 1 {
let audit_proof = akd
.audit(epoch_hash.epoch() - 1, epoch_hash.epoch())
.await?;
crate::auditor::audit_verify(vec![previous_hash, epoch_hash.hash()], audit_proof)
.await?;
}
previous_hash = epoch_hash.hash();
for i in 0..num_labels {
let index = 1 << i;
if epoch < index {
continue;
}
let latest_added_epoch = epoch_hash.epoch() - (epoch_hash.epoch() % index);
let label = AkdLabel::from(format!("{index}").as_str());
let lookup_value = AkdValue::from(format!("{index},{latest_added_epoch}").as_str());
let (lookup_proof, epoch_hash_from_lookup) = akd.lookup(label.clone()).await?;
assert_eq!(epoch_hash, epoch_hash_from_lookup);
let lookup_verify_result = lookup_verify(
vrf_pk.as_bytes(),
epoch_hash.hash(),
label.clone(),
lookup_proof,
)?;
assert_eq!(lookup_verify_result.epoch, latest_added_epoch);
assert_eq!(lookup_verify_result.value, lookup_value);
assert_eq!(lookup_verify_result.version, epoch / index);
let (history_proof, epoch_hash_from_history) =
akd.key_history(&label, HistoryParams::Complete).await?;
assert_eq!(epoch_hash, epoch_hash_from_history);
let history_results = key_history_verify(
vrf_pk.as_bytes(),
epoch_hash.hash(),
epoch_hash.epoch(),
label,
history_proof,
HistoryVerificationParams::default(),
)?;
for (j, res) in history_results.iter().enumerate() {
let added_in_epoch =
epoch_hash.epoch() - (epoch_hash.epoch() % index) - (j as u64) * index;
let history_value = AkdValue::from(format!("{index},{added_in_epoch}").as_str());
assert_eq!(res.epoch, added_in_epoch);
assert_eq!(res.value, history_value);
assert_eq!(res.version, epoch / index - j as u64);
}
}
}
Ok(())
}
#[tokio::test]
async fn test_limited_key_history() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage_manager = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage_manager, vrf, false).await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_2")),
(AkdLabel::from("hello2"), AkdValue::from("world2_2")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world3")),
(AkdLabel::from("hello2"), AkdValue::from("world4")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world")),
(AkdLabel::from("hello4"), AkdValue::from("world2")),
])
.await?;
akd.publish(vec![(
AkdLabel::from("hello"),
AkdValue::from("world_updated"),
)])
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world6")),
(AkdLabel::from("hello4"), AkdValue::from("world12")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world7")),
(AkdLabel::from("hello4"), AkdValue::from("world13")),
])
.await?;
let vrf_pk = akd.get_public_key().await?;
let current_azks = akd.retrieve_current_azks().await?;
let current_epoch = current_azks.get_latest_epoch();
let (history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::MostRecent(1))
.await?;
assert_eq!(1, history_proof.update_proofs.len());
assert_eq!(5, history_proof.update_proofs[0].epoch);
key_history_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
current_epoch,
AkdLabel::from("hello"),
history_proof,
HistoryVerificationParams::default(),
)?;
let (history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::MostRecent(3))
.await?;
assert_eq!(3, history_proof.update_proofs.len());
assert_eq!(5, history_proof.update_proofs[0].epoch);
assert_eq!(3, history_proof.update_proofs[1].epoch);
assert_eq!(2, history_proof.update_proofs[2].epoch);
key_history_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
current_epoch,
AkdLabel::from("hello"),
history_proof,
HistoryVerificationParams::default(),
)?;
let (history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::SinceEpoch(3))
.await?;
assert_eq!(2, history_proof.update_proofs.len());
assert_eq!(5, history_proof.update_proofs[0].epoch);
assert_eq!(3, history_proof.update_proofs[1].epoch);
key_history_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
current_epoch,
AkdLabel::from("hello"),
history_proof,
HistoryVerificationParams::default(),
)?;
Ok(())
}
#[tokio::test]
async fn test_malicious_key_history() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false).await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world"))])
.await?;
let corruption_2 = PublishCorruption::UnmarkedStaleVersion(AkdLabel::from("hello"));
akd.publish_malicious_update(
vec![(AkdLabel::from("hello"), AkdValue::from("world2"))],
corruption_2,
)
.await?;
let (key_history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await?;
let vrf_pk = akd.get_public_key().await?;
key_history_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
key_history_proof,
HistoryVerificationParams::default(),
).expect_err("The key history proof should fail here since the previous value was not marked stale at all");
let corruption_3 = PublishCorruption::MarkVersionStale(AkdLabel::from("hello"), 1);
akd.publish_malicious_update(
vec![(AkdLabel::from("hello2"), AkdValue::from("world"))],
corruption_3,
)
.await?;
let (key_history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await?;
let vrf_pk = akd.get_public_key().await?;
key_history_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
key_history_proof,
HistoryVerificationParams::default(),
).expect_err("The key history proof should fail here since the previous value was marked stale one epoch too late.");
Ok(())
}
#[tokio::test]
async fn test_simple_audit() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false).await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await?;
let root_hash_1 = akd
.get_root_hash(&akd.retrieve_current_azks().await?)
.await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_2")),
(AkdLabel::from("hello2"), AkdValue::from("world2_2")),
])
.await?;
let root_hash_2 = akd
.get_root_hash(&akd.retrieve_current_azks().await?)
.await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world3")),
(AkdLabel::from("hello2"), AkdValue::from("world4")),
])
.await?;
let root_hash_3 = akd
.get_root_hash(&akd.retrieve_current_azks().await?)
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world")),
(AkdLabel::from("hello4"), AkdValue::from("world2")),
])
.await?;
let root_hash_4 = akd
.get_root_hash(&akd.retrieve_current_azks().await?)
.await?;
akd.publish(vec![(
AkdLabel::from("hello"),
AkdValue::from("world_updated"),
)])
.await?;
let root_hash_5 = akd
.get_root_hash(&akd.retrieve_current_azks().await?)
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world6")),
(AkdLabel::from("hello4"), AkdValue::from("world12")),
])
.await?;
let root_hash_6 = akd
.get_root_hash(&akd.retrieve_current_azks().await?)
.await?;
let audit_proof_1 = akd.audit(1, 2).await?;
audit_verify(vec![root_hash_1, root_hash_2], audit_proof_1).await?;
let audit_proof_2 = akd.audit(1, 3).await?;
audit_verify(vec![root_hash_1, root_hash_2, root_hash_3], audit_proof_2).await?;
let audit_proof_3 = akd.audit(1, 4).await?;
audit_verify(
vec![root_hash_1, root_hash_2, root_hash_3, root_hash_4],
audit_proof_3,
)
.await?;
let audit_proof_4 = akd.audit(1, 5).await?;
audit_verify(
vec![
root_hash_1,
root_hash_2,
root_hash_3,
root_hash_4,
root_hash_5,
],
audit_proof_4,
)
.await?;
let audit_proof_5 = akd.audit(2, 3).await?;
audit_verify(vec![root_hash_2, root_hash_3], audit_proof_5).await?;
let audit_proof_6 = akd.audit(2, 4).await?;
audit_verify(vec![root_hash_2, root_hash_3, root_hash_4], audit_proof_6).await?;
let audit_proof_7 = akd.audit(4, 6).await?;
audit_verify(vec![root_hash_4, root_hash_5, root_hash_6], audit_proof_7).await?;
let invalid_audit = akd.audit(3, 3).await;
assert!(matches!(invalid_audit, Err(_)));
let invalid_audit = akd.audit(3, 2).await;
assert!(matches!(invalid_audit, Err(_)));
let invalid_audit = akd.audit(6, 7).await;
assert!(matches!(invalid_audit, Err(_)));
Ok(())
}
#[tokio::test]
async fn test_read_during_publish() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db.clone());
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false).await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await?;
let root_hash_1 = akd
.get_root_hash(&akd.retrieve_current_azks().await?)
.await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_2")),
(AkdLabel::from("hello2"), AkdValue::from("world2_2")),
])
.await?;
let root_hash_2 = akd
.get_root_hash(&akd.retrieve_current_azks().await?)
.await?;
let checkpoint_azks = akd.retrieve_current_azks().await.unwrap();
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_3")),
(AkdLabel::from("hello2"), AkdValue::from("world2_3")),
])
.await?;
db.set(DbRecord::Azks(checkpoint_azks))
.await
.expect("Error resetting directory to previous epoch");
let (history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await?;
let vrf_pk = akd.get_public_key().await?;
key_history_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
history_proof,
HistoryVerificationParams::default(),
)?;
let (lookup_proof, root_hash) = akd.lookup(AkdLabel::from("hello")).await?;
assert_eq!(AkdValue::from("world_2"), lookup_proof.value);
lookup_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
AkdLabel::from("hello"),
lookup_proof,
)?;
let audit_proof = akd.audit(1, 2).await?;
audit_verify(vec![root_hash_1, root_hash_2], audit_proof).await?;
let invalid_audit = akd.audit(2, 3).await;
assert!(matches!(invalid_audit, Err(_)));
Ok(())
}
#[tokio::test]
async fn test_directory_read_only_mode() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage.clone(), vrf.clone(), true).await;
assert!(matches!(akd, Err(_)));
let akd = Directory::<_, _>::new(storage.clone(), vrf.clone(), false).await;
assert!(matches!(akd, Ok(_)));
let akd = Directory::<_, _>::new(storage, vrf, true).await?;
assert!(matches!(akd.publish(vec![]).await, Err(_)));
Ok(())
}
#[tokio::test]
async fn test_directory_polling_azks_change() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new(db, None, None, None);
let vrf = HardCodedAkdVRF {};
let writer = Directory::<_, _>::new(storage.clone(), vrf.clone(), false).await?;
writer
.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await?;
let reader = Directory::<_, _>::new(storage, vrf, true).await?;
let (tx, mut rx) = tokio::sync::mpsc::channel(10);
let reader_clone = reader.clone();
let _join_handle = tokio::task::spawn(async move {
reader_clone
.poll_for_azks_changes(tokio::time::Duration::from_millis(100), Some(tx))
.await
});
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
async_poll_helper_proof(&reader, AkdValue::from("world")).await?;
writer
.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_2")),
(AkdLabel::from("hello2"), AkdValue::from("world2_2")),
])
.await?;
let notification = tokio::time::timeout(tokio::time::Duration::from_secs(10), rx.recv()).await;
assert!(matches!(notification, Ok(Some(()))));
async_poll_helper_proof(&reader, AkdValue::from("world_2")).await?;
Ok(())
}
#[tokio::test]
async fn test_tombstoned_key_history() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage.clone(), vrf, false).await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world"))])
.await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world2"))])
.await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world3"))])
.await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world4"))])
.await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world5"))])
.await?;
let vrf_pk = akd.get_public_key().await?;
let tombstones = [
crate::storage::types::ValueStateKey("hello".as_bytes().to_vec(), 1u64),
crate::storage::types::ValueStateKey("hello".as_bytes().to_vec(), 2u64),
];
storage.tombstone_value_states(&tombstones).await?;
let (history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await?;
assert_eq!(5, history_proof.update_proofs.len());
let tombstones = key_history_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
history_proof.clone(),
HistoryVerificationParams::default(),
);
assert!(matches!(tombstones, Err(_)));
let results = key_history_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
history_proof,
HistoryVerificationParams::AllowMissingValues,
)?;
assert_ne!(crate::TOMBSTONE, results[0].value.0);
assert_ne!(crate::TOMBSTONE, results[1].value.0);
assert_ne!(crate::TOMBSTONE, results[2].value.0);
assert_eq!(crate::TOMBSTONE, results[3].value.0);
assert_eq!(crate::TOMBSTONE, results[4].value.0);
Ok(())
}
#[tokio::test]
async fn test_publish_op_makes_no_get_requests() {
let test_db = AsyncInMemoryDatabase::new();
let mut db = MockLocalDatabase {
..Default::default()
};
setup_mocked_db(&mut db, &test_db);
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false)
.await
.expect("Failed to create directory");
let mut updates = vec![];
for i in 0..1 {
updates.push((
AkdLabel(format!("hello1{i}").as_bytes().to_vec()),
AkdValue(format!("hello1{i}").as_bytes().to_vec()),
));
}
akd.publish(updates)
.await
.expect("Failed to do initial publish");
let mut db2 = MockLocalDatabase {
..Default::default()
};
setup_mocked_db(&mut db2, &test_db);
db2.expect_get::<TreeNodeWithPreviousValue>()
.returning(|_| Err(StorageError::Other("Boom!".to_string())));
let storage = StorageManager::new_no_cache(db2);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false)
.await
.expect("Failed to create directory");
let mut updates = vec![];
for i in 0..1 {
updates.push((
AkdLabel(format!("hello1{i}").as_bytes().to_vec()),
AkdValue(format!("hello1{}", i + 1).as_bytes().to_vec()),
));
}
akd.publish(updates)
.await
.expect("Failed to do subsequent publish");
}
#[tokio::test]
async fn test_simple_lookup_for_small_tree_blake() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf.clone(), false).await?;
let mut updates = vec![];
for i in 0..1 {
updates.push((
AkdLabel(format!("hello1{i}").as_bytes().to_vec()),
AkdValue(format!("hello1{i}").as_bytes().to_vec()),
));
}
akd.publish(updates).await?;
let target_label = AkdLabel(format!("hello1{}", 0).as_bytes().to_vec());
let (lookup_proof, root_hash) = akd.lookup(target_label.clone()).await?;
let vrf_pk = vrf.get_vrf_public_key().await?;
let akd_result = crate::client::lookup_verify(
vrf_pk.as_bytes(),
root_hash.hash(),
target_label.clone(),
lookup_proof,
)?;
assert_eq!(
akd_result,
VerifyResult {
epoch: 1,
version: 1,
value: AkdValue::from("hello10"),
},
);
Ok(())
}
#[cfg(feature = "sha3_256")]
#[tokio::test]
async fn test_simple_lookup_for_small_tree_sha256() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<_, _>::new(storage, vrf, false).await?;
let mut updates = vec![];
for i in 0..1 {
updates.push((
AkdLabel(format!("hello{}", i).as_bytes().to_vec()),
AkdValue(format!("hello{}", i).as_bytes().to_vec()),
));
}
akd.publish(updates).await?;
let target_label = AkdLabel(format!("hello{}", 0).as_bytes().to_vec());
let lookup_proof = akd.lookup(target_label.clone()).await?;
let current_azks = akd.retrieve_current_azks().await?;
let root_hash = akd.get_root_hash(¤t_azks).await?;
let vrf_pk = vrf.get_vrf_public_key().await?;
let akd_result = crate::client::lookup_verify(
vrf_pk.as_bytes(),
root_hash,
target_label.clone(),
lookup_proof,
)?;
assert_eq!(
akd_result,
VerifyResult {
epoch: 1,
version: 1,
value: AkdValue::from_utf8_str("hello0"),
},
);
Ok(())
}
async fn async_poll_helper_proof<T: Database + 'static, V: VRFKeyStorage>(
reader: &Directory<T, V>,
value: AkdValue,
) -> Result<(), AkdError> {
let (lookup_proof, root_hash) = reader.lookup(AkdLabel::from("hello")).await?;
assert_eq!(value, lookup_proof.value);
let pk = reader.get_public_key().await?;
lookup_verify(
pk.as_bytes(),
root_hash.hash(),
AkdLabel::from("hello"),
lookup_proof,
)?;
Ok(())
}