use std::collections::BTreeMap;
use diesel::query_dsl::methods::SelectDsl;
use diesel::{
BoolExpressionMethods,
Connection,
ExpressionMethods,
OptionalExtension,
QueryDsl,
RunQueryDsl,
};
use diesel_migrations::MigrationHarness;
use miden_node_utils::fee::test_fee_params;
use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment};
use miden_protocol::account::component::AccountComponentMetadata;
use miden_protocol::account::delta::AccountUpdateDetails;
use miden_protocol::account::{
Account,
AccountBuilder,
AccountComponent,
AccountDelta,
AccountId,
AccountIdVersion,
AccountStorage,
AccountStorageDelta,
AccountStorageHeader,
AccountStorageMode,
AccountType,
AccountVaultDelta,
StorageMap,
StorageMapDelta,
StorageMapKey,
StorageSlot,
StorageSlotContent,
StorageSlotDelta,
StorageSlotName,
StorageSlotType,
};
use miden_protocol::block::{BlockAccountUpdate, BlockHeader, BlockNumber};
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey;
use miden_protocol::utils::serde::{Deserializable, Serializable};
use miden_protocol::{EMPTY_WORD, Felt, Word};
use miden_standards::account::auth::AuthSingleSig;
use miden_standards::code_builder::CodeBuilder;
use super::*;
use crate::db::migrations::MIGRATIONS;
use crate::db::models::conv::SqlTypeConvert;
use crate::db::schema;
use crate::errors::DatabaseError;
fn setup_test_db() -> SqliteConnection {
let mut conn =
SqliteConnection::establish(":memory:").expect("Failed to create in-memory database");
conn.run_pending_migrations(MIGRATIONS).expect("Failed to run migrations");
conn
}
fn reconstruct_account_storage_at_block(
conn: &mut SqliteConnection,
account_id: AccountId,
block_num: BlockNumber,
) -> Result<AccountStorage, DatabaseError> {
use schema::account_storage_map_values as t;
let account_id_bytes = account_id.to_bytes();
let block_num_sql = block_num.to_raw_sql();
let storage_blob: Option<Vec<u8>> =
SelectDsl::select(schema::accounts::table, schema::accounts::storage_header)
.filter(schema::accounts::account_id.eq(&account_id_bytes))
.filter(schema::accounts::block_num.le(block_num_sql))
.order(schema::accounts::block_num.desc())
.limit(1)
.first(conn)
.optional()?
.flatten();
let Some(blob) = storage_blob else {
return Ok(AccountStorage::new(Vec::new())?);
};
let header = AccountStorageHeader::read_from_bytes(&blob)?;
let map_values: Vec<(i64, String, Vec<u8>, Vec<u8>)> =
SelectDsl::select(t::table, (t::block_num, t::slot_name, t::key, t::value))
.filter(t::account_id.eq(&account_id_bytes).and(t::block_num.le(block_num_sql)))
.order((t::slot_name.asc(), t::key.asc(), t::block_num.desc()))
.load(conn)?;
let mut latest_map_entries: BTreeMap<(StorageSlotName, StorageMapKey), Word> = BTreeMap::new();
for (_, slot_name_str, key_bytes, value_bytes) in map_values {
let slot_name: StorageSlotName = slot_name_str.parse().map_err(|_| {
DatabaseError::DataCorrupted(format!("Invalid slot name: {slot_name_str}"))
})?;
let key = StorageMapKey::read_from_bytes(&key_bytes)?;
let value = Word::read_from_bytes(&value_bytes)?;
latest_map_entries.entry((slot_name, key)).or_insert(value);
}
let mut map_entries_by_slot: BTreeMap<StorageSlotName, Vec<(StorageMapKey, Word)>> =
BTreeMap::new();
for ((slot_name, key), value) in latest_map_entries {
map_entries_by_slot.entry(slot_name).or_default().push((key, value));
}
let mut slots = Vec::new();
for slot_header in header.slots() {
let slot = match slot_header.slot_type() {
StorageSlotType::Value => {
StorageSlot::with_value(slot_header.name().clone(), slot_header.value())
},
StorageSlotType::Map => {
let entries = map_entries_by_slot.remove(slot_header.name()).unwrap_or_default();
let storage_map = StorageMap::with_entries(entries)?;
StorageSlot::with_map(slot_header.name().clone(), storage_map)
},
};
slots.push(slot);
}
Ok(AccountStorage::new(slots)?)
}
fn create_test_account_with_storage() -> (Account, AccountId) {
let account_id = AccountId::dummy(
[1u8; 15],
AccountIdVersion::Version0,
AccountType::RegularAccountImmutableCode,
AccountStorageMode::Public,
);
let storage_value = Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
let component_storage = vec![StorageSlot::with_value(StorageSlotName::mock(0), storage_value)];
let account_component_code = CodeBuilder::default()
.compile_component_code("test::interface", "pub proc foo push.1 end")
.unwrap();
let component = AccountComponent::new(
account_component_code,
component_storage,
AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]),
)
.unwrap();
let account = AccountBuilder::new([1u8; 32])
.account_type(AccountType::RegularAccountImmutableCode)
.storage_mode(AccountStorageMode::Public)
.with_component(component)
.with_auth_component(AuthSingleSig::new(
PublicKeyCommitment::from(EMPTY_WORD),
AuthScheme::Falcon512Poseidon2,
))
.build_existing()
.unwrap();
(account, account_id)
}
fn insert_block_header(conn: &mut SqliteConnection, block_num: BlockNumber) {
use crate::db::schema::block_headers;
let secret_key = SecretKey::new();
let block_header = BlockHeader::new(
1_u8.into(),
Word::default(),
block_num,
Word::default(),
Word::default(),
Word::default(),
Word::default(),
Word::default(),
Word::default(),
secret_key.public_key(),
test_fee_params(),
0_u8.into(),
);
let signature = secret_key.sign(block_header.commitment());
diesel::insert_into(block_headers::table)
.values((
block_headers::block_num.eq(i64::from(block_num.as_u32())),
block_headers::block_header.eq(block_header.to_bytes()),
block_headers::signature.eq(signature.to_bytes()),
block_headers::commitment.eq(block_header.commitment().to_bytes()),
))
.execute(conn)
.expect("Failed to insert block header");
}
fn create_account_with_map_storage(
slot_name: StorageSlotName,
entries: Vec<(StorageMapKey, Word)>,
) -> Account {
let storage_map = StorageMap::with_entries(entries).unwrap();
let component_storage = vec![StorageSlot::with_map(slot_name, storage_map)];
let account_component_code = CodeBuilder::default()
.compile_component_code("test::interface", "pub proc map push.1 end")
.unwrap();
let component = AccountComponent::new(
account_component_code,
component_storage,
AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]),
)
.unwrap();
AccountBuilder::new([9u8; 32])
.account_type(AccountType::RegularAccountImmutableCode)
.storage_mode(AccountStorageMode::Public)
.with_component(component)
.with_auth_component(AuthSingleSig::new(
PublicKeyCommitment::from(EMPTY_WORD),
AuthScheme::Falcon512Poseidon2,
))
.build_existing()
.unwrap()
}
fn assert_storage_map_slot_entries(
storage: &AccountStorage,
slot_name: &StorageSlotName,
expected: &BTreeMap<StorageMapKey, Word>,
) {
let slot = storage
.slots()
.iter()
.find(|slot| slot.name() == slot_name)
.expect("expected storage slot");
let StorageSlotContent::Map(storage_map) = slot.content() else {
panic!("expected map slot");
};
let entries = BTreeMap::from_iter(storage_map.entries().map(|(key, value)| (*key, *value)));
assert_eq!(&entries, expected, "map entries mismatch");
}
#[test]
fn test_select_account_header_at_block_returns_none_for_nonexistent() {
let mut conn = setup_test_db();
let block_num = BlockNumber::from_epoch(0);
insert_block_header(&mut conn, block_num);
let account_id = AccountId::dummy(
[99u8; 15],
AccountIdVersion::Version0,
AccountType::RegularAccountImmutableCode,
AccountStorageMode::Public,
);
let result =
select_account_header_with_storage_header_at_block(&mut conn, account_id, block_num)
.expect("Query should succeed");
assert!(result.is_none(), "Should return None for non-existent account");
}
#[test]
fn test_select_account_header_at_block_returns_correct_header() {
let mut conn = setup_test_db();
let (account, _) = create_test_account_with_storage();
let account_id = account.id();
let block_num = BlockNumber::from_epoch(0);
insert_block_header(&mut conn, block_num);
let delta = AccountDelta::try_from(account.clone()).unwrap();
let account_update = BlockAccountUpdate::new(
account_id,
account.to_commitment(),
AccountUpdateDetails::Delta(delta),
);
upsert_accounts(&mut conn, &[account_update], block_num).expect("upsert_accounts failed");
let (header, _storage_header) =
select_account_header_with_storage_header_at_block(&mut conn, account_id, block_num)
.expect("Query should succeed")
.expect("Header should exist");
assert_eq!(header.id(), account_id, "Account ID should match");
assert_eq!(header.nonce(), account.nonce(), "Nonce should match");
assert_eq!(
header.code_commitment(),
account.code().commitment(),
"Code commitment should match"
);
}
#[test]
fn test_select_account_header_at_block_historical_query() {
let mut conn = setup_test_db();
let (account, _) = create_test_account_with_storage();
let account_id = account.id();
let block_num_1 = BlockNumber::from_epoch(0);
let block_num_2 = BlockNumber::from_epoch(1);
insert_block_header(&mut conn, block_num_1);
insert_block_header(&mut conn, block_num_2);
let nonce_1 = account.nonce();
let delta_1 = AccountDelta::try_from(account.clone()).unwrap();
let account_update_1 = BlockAccountUpdate::new(
account_id,
account.to_commitment(),
AccountUpdateDetails::Delta(delta_1),
);
upsert_accounts(&mut conn, &[account_update_1], block_num_1).expect("First upsert failed");
let (header_1, _) =
select_account_header_with_storage_header_at_block(&mut conn, account_id, block_num_1)
.expect("Query should succeed")
.expect("Header should exist at block 1");
assert_eq!(header_1.nonce(), nonce_1, "Nonce at block 1 should match");
let (header_2, _) =
select_account_header_with_storage_header_at_block(&mut conn, account_id, block_num_2)
.expect("Query should succeed")
.expect("Header should exist at block 2");
assert_eq!(header_2.nonce(), nonce_1, "Nonce at block 2 should match block 1");
}
#[test]
fn test_select_account_vault_at_block_empty() {
let mut conn = setup_test_db();
let (account, _) = create_test_account_with_storage();
let account_id = account.id();
let block_num = BlockNumber::from_epoch(0);
insert_block_header(&mut conn, block_num);
let delta = AccountDelta::try_from(account.clone()).unwrap();
let account_update = BlockAccountUpdate::new(
account_id,
account.to_commitment(),
AccountUpdateDetails::Delta(delta),
);
upsert_accounts(&mut conn, &[account_update], block_num).expect("upsert_accounts failed");
let assets = select_account_vault_at_block(&mut conn, account_id, block_num)
.expect("Query should succeed");
assert!(assets.is_empty(), "Account should have no assets");
}
#[test]
fn test_upsert_accounts_inserts_storage_header() {
let mut conn = setup_test_db();
let (account, account_id) = create_test_account_with_storage();
let block_num = BlockNumber::from_epoch(0);
insert_block_header(&mut conn, block_num);
let storage_commitment_original = account.storage().to_commitment();
let storage_slots_len = account.storage().slots().len();
let account_commitment = account.to_commitment();
let delta = AccountDelta::try_from(account).unwrap();
assert!(delta.is_full_state(), "Delta should be full state");
let account_update =
BlockAccountUpdate::new(account_id, account_commitment, AccountUpdateDetails::Delta(delta));
let result = upsert_accounts(&mut conn, &[account_update], block_num);
assert!(result.is_ok(), "upsert_accounts failed: {:?}", result.err());
assert_eq!(result.unwrap(), 1, "Expected 1 account to be inserted");
let queried_storage = select_latest_account_storage(&mut conn, account_id)
.expect("Failed to query storage header");
assert_eq!(
queried_storage.to_commitment(),
storage_commitment_original,
"Storage commitment mismatch"
);
assert_eq!(queried_storage.slots().len(), storage_slots_len, "Storage slots count mismatch");
let header_count: i64 = schema::accounts::table
.filter(schema::accounts::account_id.eq(account_id.to_bytes()))
.filter(schema::accounts::is_latest.eq(true))
.filter(schema::accounts::storage_header.is_not_null())
.count()
.get_result(&mut conn)
.expect("Failed to count accounts with storage");
assert_eq!(header_count, 1, "Expected exactly 1 latest account with storage");
}
#[test]
fn test_upsert_accounts_updates_is_latest_flag() {
let mut conn = setup_test_db();
let (account, account_id) = create_test_account_with_storage();
let block_num_1 = BlockNumber::from_epoch(0);
let block_num_2 = BlockNumber::from_epoch(1);
insert_block_header(&mut conn, block_num_1);
insert_block_header(&mut conn, block_num_2);
let storage_commitment_1 = account.storage().to_commitment();
let account_commitment_1 = account.to_commitment();
let delta_1 = AccountDelta::try_from(account).unwrap();
let account_update_1 = BlockAccountUpdate::new(
account_id,
account_commitment_1,
AccountUpdateDetails::Delta(delta_1),
);
upsert_accounts(&mut conn, &[account_update_1], block_num_1).expect("First upsert failed");
let storage_value_modified =
Word::from([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]);
let component_storage_modified =
vec![StorageSlot::with_value(StorageSlotName::mock(0), storage_value_modified)];
let account_component_code = CodeBuilder::default()
.compile_component_code("test::interface", "pub proc foo push.1 end")
.unwrap();
let component_2 = AccountComponent::new(
account_component_code,
component_storage_modified,
AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]),
)
.unwrap();
let account_2 = AccountBuilder::new([1u8; 32])
.account_type(AccountType::RegularAccountImmutableCode)
.storage_mode(AccountStorageMode::Public)
.with_component(component_2)
.with_auth_component(AuthSingleSig::new(
PublicKeyCommitment::from(EMPTY_WORD),
AuthScheme::Falcon512Poseidon2,
))
.build_existing()
.unwrap();
let storage_commitment_2 = account_2.storage().to_commitment();
let account_commitment_2 = account_2.to_commitment();
let delta_2 = AccountDelta::try_from(account_2).unwrap();
let account_update_2 = BlockAccountUpdate::new(
account_id,
account_commitment_2,
AccountUpdateDetails::Delta(delta_2),
);
upsert_accounts(&mut conn, &[account_update_2], block_num_2).expect("Second upsert failed");
let total_accounts: i64 = schema::accounts::table
.filter(schema::accounts::account_id.eq(account_id.to_bytes()))
.count()
.get_result(&mut conn)
.expect("Failed to count total accounts");
assert_eq!(total_accounts, 2, "Expected 2 total account records");
let latest_accounts: i64 = schema::accounts::table
.filter(schema::accounts::account_id.eq(account_id.to_bytes()))
.filter(schema::accounts::is_latest.eq(true))
.count()
.get_result(&mut conn)
.expect("Failed to count latest accounts");
assert_eq!(latest_accounts, 1, "Expected exactly 1 latest account");
let latest_storage = select_latest_account_storage(&mut conn, account_id)
.expect("Failed to query latest storage");
assert_eq!(
latest_storage.to_commitment(),
storage_commitment_2,
"Latest storage should match second update"
);
let storage_at_block_1 =
reconstruct_account_storage_at_block(&mut conn, account_id, block_num_1)
.expect("Failed to query storage at block 1");
assert_eq!(
storage_at_block_1.to_commitment(),
storage_commitment_1,
"Storage at block 1 should match first update"
);
}
#[test]
fn test_upsert_accounts_with_multiple_storage_slots() {
let mut conn = setup_test_db();
let account_id = AccountId::dummy(
[2u8; 15],
AccountIdVersion::Version0,
AccountType::RegularAccountImmutableCode,
AccountStorageMode::Public,
);
let slot_value_1 = Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
let slot_value_2 = Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]);
let slot_value_3 = Word::from([Felt::new(9), Felt::new(10), Felt::new(11), Felt::new(12)]);
let component_storage = vec![
StorageSlot::with_value(StorageSlotName::mock(0), slot_value_1),
StorageSlot::with_value(StorageSlotName::mock(1), slot_value_2),
StorageSlot::with_value(StorageSlotName::mock(2), slot_value_3),
];
let account_component_code = CodeBuilder::default()
.compile_component_code("test::interface", "pub proc foo push.1 end")
.unwrap();
let component = AccountComponent::new(
account_component_code,
component_storage,
AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]),
)
.unwrap();
let account = AccountBuilder::new([2u8; 32])
.account_type(AccountType::RegularAccountImmutableCode)
.storage_mode(AccountStorageMode::Public)
.with_component(component)
.with_auth_component(AuthSingleSig::new(
PublicKeyCommitment::from(EMPTY_WORD),
AuthScheme::Falcon512Poseidon2,
))
.build_existing()
.unwrap();
let block_num = BlockNumber::from_epoch(0);
insert_block_header(&mut conn, block_num);
let storage_commitment = account.storage().to_commitment();
let account_commitment = account.to_commitment();
let delta = AccountDelta::try_from(account).unwrap();
let account_update =
BlockAccountUpdate::new(account_id, account_commitment, AccountUpdateDetails::Delta(delta));
upsert_accounts(&mut conn, &[account_update], block_num)
.expect("Upsert with multiple storage slots failed");
let queried_storage =
select_latest_account_storage(&mut conn, account_id).expect("Failed to query storage");
assert_eq!(
queried_storage.to_commitment(),
storage_commitment,
"Storage commitment mismatch"
);
assert_eq!(
queried_storage.slots().len(),
5,
"Expected 5 storage slots (3 component + 2 auth)"
);
}
#[test]
fn test_upsert_accounts_with_empty_storage() {
let mut conn = setup_test_db();
let account_id = AccountId::dummy(
[3u8; 15],
AccountIdVersion::Version0,
AccountType::RegularAccountImmutableCode,
AccountStorageMode::Public,
);
let account_component_code = CodeBuilder::default()
.compile_component_code("test::interface", "pub proc foo push.1 end")
.unwrap();
let component = AccountComponent::new(
account_component_code,
vec![],
AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]),
)
.unwrap();
let account = AccountBuilder::new([3u8; 32])
.account_type(AccountType::RegularAccountImmutableCode)
.storage_mode(AccountStorageMode::Public)
.with_component(component)
.with_auth_component(AuthSingleSig::new(
PublicKeyCommitment::from(EMPTY_WORD),
AuthScheme::Falcon512Poseidon2,
))
.build_existing()
.unwrap();
let block_num = BlockNumber::from_epoch(0);
insert_block_header(&mut conn, block_num);
let storage_commitment = account.storage().to_commitment();
let account_commitment = account.to_commitment();
let delta = AccountDelta::try_from(account).unwrap();
let account_update =
BlockAccountUpdate::new(account_id, account_commitment, AccountUpdateDetails::Delta(delta));
upsert_accounts(&mut conn, &[account_update], block_num)
.expect("Upsert with empty storage failed");
let queried_storage =
select_latest_account_storage(&mut conn, account_id).expect("Failed to query storage");
assert_eq!(
queried_storage.to_commitment(),
storage_commitment,
"Storage commitment mismatch for empty storage"
);
assert_eq!(queried_storage.slots().len(), 2, "Expected 2 storage slots (auth component)");
let storage_header_exists: Option<bool> = SelectDsl::select(
schema::accounts::table
.filter(schema::accounts::account_id.eq(account_id.to_bytes()))
.filter(schema::accounts::is_latest.eq(true)),
schema::accounts::storage_header.is_not_null(),
)
.first(&mut conn)
.optional()
.expect("Failed to check storage header existence");
assert_eq!(
storage_header_exists,
Some(true),
"Storage header blob should exist even for empty storage"
);
}
#[test]
fn test_select_latest_account_storage_ordering_semantics() {
let mut conn = setup_test_db();
let block_num = BlockNumber::from_epoch(0);
insert_block_header(&mut conn, block_num);
let slot_name = StorageSlotName::mock(0);
let key_1 = StorageMapKey::from_index(1);
let key_2 = StorageMapKey::from_index(2);
let key_3 = StorageMapKey::from_index(3);
let value_1 = Word::from([Felt::new(10), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
let value_2 = Word::from([Felt::new(20), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
let value_3 = Word::from([Felt::new(30), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
let mut entries = vec![(key_2, value_2), (key_1, value_1), (key_3, value_3)];
entries.reverse();
let account = create_account_with_map_storage(slot_name.clone(), entries.clone());
let account_id = account.id();
let account_commitment = account.to_commitment();
let mut reversed_entries = entries.clone();
reversed_entries.reverse();
let reordered_account = create_account_with_map_storage(slot_name.clone(), reversed_entries);
assert_eq!(
account.storage().to_commitment(),
reordered_account.storage().to_commitment(),
"storage commitments should be order-independent"
);
let delta = AccountDelta::try_from(account).unwrap();
let account_update =
BlockAccountUpdate::new(account_id, account_commitment, AccountUpdateDetails::Delta(delta));
upsert_accounts(&mut conn, &[account_update], block_num).expect("upsert_accounts failed");
let storage =
select_latest_account_storage(&mut conn, account_id).expect("Failed to query storage");
let expected = BTreeMap::from_iter(entries);
assert_storage_map_slot_entries(&storage, &slot_name, &expected);
}
#[test]
fn test_select_latest_account_storage_multiple_slots() {
let mut conn = setup_test_db();
let block_num = BlockNumber::from_epoch(0);
insert_block_header(&mut conn, block_num);
let slot_name_1 = StorageSlotName::mock(0);
let slot_name_2 = StorageSlotName::mock(1);
let key_a = StorageMapKey::from_index(1);
let key_b = StorageMapKey::from_index(2);
let value_a = Word::from([Felt::new(11), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
let value_b = Word::from([Felt::new(22), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
let map_a = StorageMap::with_entries(vec![(key_a, value_a)]).unwrap();
let map_b = StorageMap::with_entries(vec![(key_b, value_b)]).unwrap();
let component_storage = vec![
StorageSlot::with_map(slot_name_2.clone(), map_b),
StorageSlot::with_map(slot_name_1.clone(), map_a),
];
let account_component_code = CodeBuilder::default()
.compile_component_code("test::interface", "pub proc map push.1 end")
.unwrap();
let component = AccountComponent::new(
account_component_code,
component_storage,
AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]),
)
.unwrap();
let account = AccountBuilder::new([9u8; 32])
.account_type(AccountType::RegularAccountImmutableCode)
.storage_mode(AccountStorageMode::Public)
.with_component(component)
.with_auth_component(AuthSingleSig::new(
PublicKeyCommitment::from(EMPTY_WORD),
AuthScheme::Falcon512Poseidon2,
))
.build_existing()
.unwrap();
let account_id = account.id();
let account_commitment = account.to_commitment();
let delta = AccountDelta::try_from(account).unwrap();
let account_update =
BlockAccountUpdate::new(account_id, account_commitment, AccountUpdateDetails::Delta(delta));
upsert_accounts(&mut conn, &[account_update], block_num).expect("upsert_accounts failed");
let storage =
select_latest_account_storage(&mut conn, account_id).expect("Failed to query storage");
let expected_slot_1 = BTreeMap::from_iter([(key_a, value_a)]);
let expected_slot_2 = BTreeMap::from_iter([(key_b, value_b)]);
assert_storage_map_slot_entries(&storage, &slot_name_1, &expected_slot_1);
assert_storage_map_slot_entries(&storage, &slot_name_2, &expected_slot_2);
}
#[test]
fn test_select_latest_account_storage_slot_updates() {
let mut conn = setup_test_db();
let block_1 = BlockNumber::from_epoch(0);
let block_2 = BlockNumber::from_epoch(1);
insert_block_header(&mut conn, block_1);
insert_block_header(&mut conn, block_2);
let slot_name = StorageSlotName::mock(0);
let key_1 = StorageMapKey::from_index(1);
let key_2 = StorageMapKey::from_index(2);
let value_1 = Word::from([Felt::new(10), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
let value_2 = Word::from([Felt::new(20), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
let value_3 = Word::from([Felt::new(30), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
let account = create_account_with_map_storage(slot_name.clone(), vec![(key_1, value_1)]);
let account_id = account.id();
let account_commitment = account.to_commitment();
let delta = AccountDelta::try_from(account.clone()).unwrap();
let account_update =
BlockAccountUpdate::new(account_id, account_commitment, AccountUpdateDetails::Delta(delta));
upsert_accounts(&mut conn, &[account_update], block_1).expect("upsert_accounts failed");
let mut map_delta = StorageMapDelta::default();
map_delta.insert(key_1, value_2);
map_delta.insert(key_2, value_3);
let storage_delta = AccountStorageDelta::from_raw(BTreeMap::from_iter([(
slot_name.clone(),
StorageSlotDelta::Map(map_delta),
)]));
let partial_delta =
AccountDelta::new(account_id, storage_delta, AccountVaultDelta::default(), Felt::new(1))
.unwrap();
let mut expected_account = account.clone();
expected_account.apply_delta(&partial_delta).unwrap();
let expected_commitment = expected_account.to_commitment();
let account_update = BlockAccountUpdate::new(
account_id,
expected_commitment,
AccountUpdateDetails::Delta(partial_delta),
);
upsert_accounts(&mut conn, &[account_update], block_2).expect("upsert_accounts failed");
let storage =
select_latest_account_storage(&mut conn, account_id).expect("Failed to query storage");
let expected = BTreeMap::from_iter([(key_1, value_2), (key_2, value_3)]);
assert_storage_map_slot_entries(&storage, &slot_name, &expected);
}
#[test]
fn test_select_account_vault_at_block_historical_with_updates() {
use assert_matches::assert_matches;
use miden_protocol::asset::FungibleAsset;
use miden_protocol::testing::account_id::{
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
};
let mut conn = setup_test_db();
let (account, _) = create_test_account_with_storage();
let account_id = account.id();
let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
let block_1 = BlockNumber::from_epoch(0);
let block_2 = BlockNumber::from_epoch(1);
let block_3 = BlockNumber::from_epoch(2);
insert_block_header(&mut conn, block_1);
insert_block_header(&mut conn, block_2);
insert_block_header(&mut conn, block_3);
let delta = AccountDelta::try_from(account.clone()).unwrap();
let account_update = BlockAccountUpdate::new(
account_id,
account.to_commitment(),
AccountUpdateDetails::Delta(delta),
);
for block in [block_1, block_2, block_3] {
upsert_accounts(&mut conn, std::slice::from_ref(&account_update), block)
.expect("upsert_accounts failed");
}
let asset_v1 = Asset::Fungible(FungibleAsset::new(faucet_id, 1000).unwrap());
let vault_key_1 = asset_v1.vault_key();
insert_account_vault_asset(&mut conn, account_id, block_1, vault_key_1, Some(asset_v1))
.expect("insert vault asset failed");
let asset_v2 = Asset::Fungible(FungibleAsset::new(faucet_id, 2000).unwrap());
insert_account_vault_asset(&mut conn, account_id, block_2, vault_key_1, Some(asset_v2))
.expect("insert vault asset update failed");
let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap();
let asset_key2 = Asset::Fungible(FungibleAsset::new(faucet_id_2, 500).unwrap());
let vault_key_2 = asset_key2.vault_key();
insert_account_vault_asset(&mut conn, account_id, block_2, vault_key_2, Some(asset_key2))
.expect("insert second vault asset failed");
let asset_v3 = Asset::Fungible(FungibleAsset::new(faucet_id, 3000).unwrap());
insert_account_vault_asset(&mut conn, account_id, block_3, vault_key_1, Some(asset_v3))
.expect("insert vault asset update 2 failed");
let assets_at_block_1 = select_account_vault_at_block(&mut conn, account_id, block_1)
.expect("Query at block 1 should succeed");
assert_eq!(assets_at_block_1.len(), 1, "Should have 1 asset at block 1");
assert_matches!(&assets_at_block_1[0], Asset::Fungible(f) if f.amount() == 1000);
let assets_at_block_2 = select_account_vault_at_block(&mut conn, account_id, block_2)
.expect("Query at block 2 should succeed");
assert_eq!(assets_at_block_2.len(), 2, "Should have 2 assets at block 2");
let amounts: Vec<u64> = assets_at_block_2
.iter()
.map(|a| assert_matches!(a, Asset::Fungible(f) => f.amount()))
.collect();
assert!(amounts.contains(&2000), "Block 2 should have vault_key_1 with 2000 tokens");
assert!(amounts.contains(&500), "Block 2 should have vault_key_2 with 500 tokens");
let assets_at_block_3 = select_account_vault_at_block(&mut conn, account_id, block_3)
.expect("Query at block 3 should succeed");
assert_eq!(assets_at_block_3.len(), 2, "Should have 2 assets at block 3");
let amounts: Vec<u64> = assets_at_block_3
.iter()
.map(|a| assert_matches!(a, Asset::Fungible(f) => f.amount()))
.collect();
assert!(amounts.contains(&3000), "Block 3 should have vault_key_1 with 3000 tokens");
assert!(amounts.contains(&500), "Block 3 should have vault_key_2 with 500 tokens");
}
#[test]
fn test_select_account_vault_at_block_exponential_updates() {
const BLOCK_COUNT: u32 = 5;
use assert_matches::assert_matches;
use miden_protocol::asset::{AssetVaultKey, FungibleAsset};
use miden_protocol::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET;
let mut conn = setup_test_db();
let (account, _) = create_test_account_with_storage();
let account_id = account.id();
let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
let blocks: Vec<BlockNumber> = (0..BLOCK_COUNT).map(BlockNumber::from).collect();
for block in &blocks {
insert_block_header(&mut conn, *block);
}
let delta = AccountDelta::try_from(account.clone()).unwrap();
let account_update = BlockAccountUpdate::new(
account_id,
account.to_commitment(),
AccountUpdateDetails::Delta(delta),
);
for block in &blocks {
upsert_accounts(&mut conn, std::slice::from_ref(&account_update), *block)
.expect("upsert_accounts failed");
}
let vault_key = AssetVaultKey::new_fungible(faucet_id).unwrap();
for (index, block) in blocks.iter().enumerate() {
let amount = 1u64 << index;
let asset = Asset::Fungible(FungibleAsset::new(faucet_id, amount).unwrap());
insert_account_vault_asset(&mut conn, account_id, *block, vault_key, Some(asset))
.expect("insert vault asset failed");
}
for (index, block) in blocks.iter().enumerate() {
let assets_at_block = select_account_vault_at_block(&mut conn, account_id, *block)
.expect("Query at block should succeed");
assert_eq!(assets_at_block.len(), 1, "Should have 1 asset at block");
let expected_amount = 1u64 << index;
assert_matches!(
&assets_at_block[0],
Asset::Fungible(f) if f.amount() == expected_amount
);
}
}
#[test]
fn test_select_account_vault_at_block_with_deletion() {
use assert_matches::assert_matches;
use miden_protocol::asset::FungibleAsset;
use miden_protocol::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET;
let mut conn = setup_test_db();
let (account, _) = create_test_account_with_storage();
let account_id = account.id();
let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
let block_1 = BlockNumber::from_epoch(0);
let block_2 = BlockNumber::from_epoch(1);
let block_3 = BlockNumber::from_epoch(2);
insert_block_header(&mut conn, block_1);
insert_block_header(&mut conn, block_2);
insert_block_header(&mut conn, block_3);
let delta = AccountDelta::try_from(account.clone()).unwrap();
let account_update = BlockAccountUpdate::new(
account_id,
account.to_commitment(),
AccountUpdateDetails::Delta(delta),
);
for block in [block_1, block_2, block_3] {
upsert_accounts(&mut conn, std::slice::from_ref(&account_update), block)
.expect("upsert_accounts failed");
}
let asset = Asset::Fungible(FungibleAsset::new(faucet_id, 1000).unwrap());
let vault_key = asset.vault_key();
insert_account_vault_asset(&mut conn, account_id, block_1, vault_key, Some(asset))
.expect("insert vault asset failed");
insert_account_vault_asset(&mut conn, account_id, block_2, vault_key, None)
.expect("delete vault asset failed");
let asset_v3 = Asset::Fungible(FungibleAsset::new(faucet_id, 2000).unwrap());
insert_account_vault_asset(&mut conn, account_id, block_3, vault_key, Some(asset_v3))
.expect("re-add vault asset failed");
let assets_at_block_1 = select_account_vault_at_block(&mut conn, account_id, block_1)
.expect("Query at block 1 should succeed");
assert_eq!(assets_at_block_1.len(), 1, "Should have 1 asset at block 1");
let assets_at_block_2 = select_account_vault_at_block(&mut conn, account_id, block_2)
.expect("Query at block 2 should succeed");
assert!(assets_at_block_2.is_empty(), "Should have no assets at block 2 (deleted)");
let assets_at_block_3 = select_account_vault_at_block(&mut conn, account_id, block_3)
.expect("Query at block 3 should succeed");
assert_eq!(assets_at_block_3.len(), 1, "Should have 1 asset at block 3");
assert_matches!(&assets_at_block_3[0], Asset::Fungible(f) if f.amount() == 2000);
}
fn count_account_codes(conn: &mut SqliteConnection) -> usize {
use schema::account_codes;
let val =
SelectDsl::select(account_codes::table, diesel::dsl::count(account_codes::code_commitment))
.get_result::<i64>(conn)
.expect("Failed to count account_codes");
usize::try_from(u64::try_from(val).unwrap()).unwrap()
}
fn account_code_exists(conn: &mut SqliteConnection, code_commitment: Word) -> bool {
use schema::account_codes;
let n =
SelectDsl::select(account_codes::table, diesel::dsl::count(account_codes::code_commitment))
.filter(account_codes::code_commitment.eq(code_commitment.to_bytes()))
.get_result::<i64>(conn)
.expect("Failed to query account_codes");
n == 1
}
fn make_full_state_update(account: &Account) -> BlockAccountUpdate {
let delta = AccountDelta::try_from(account.clone()).unwrap();
assert!(delta.is_full_state(), "expected full-state delta");
BlockAccountUpdate::new(
account.id(),
account.to_commitment(),
AccountUpdateDetails::Delta(delta),
)
}
fn build_account_with_code(push_value: u32) -> Account {
let code_src = format!("pub proc variant push.{push_value} end");
let component_code = CodeBuilder::default()
.compile_component_code("test::code_prune", &code_src)
.unwrap();
let component = AccountComponent::new(
component_code,
vec![StorageSlot::with_value(
StorageSlotName::mock(0),
Word::from([Felt::new(1), Felt::ZERO, Felt::ZERO, Felt::ZERO]),
)],
AccountComponentMetadata::new(
"code_prune_test",
[AccountType::RegularAccountUpdatableCode],
),
)
.unwrap();
AccountBuilder::new([2u8; 32])
.account_type(AccountType::RegularAccountUpdatableCode)
.storage_mode(AccountStorageMode::Public)
.with_component(component)
.with_auth_component(AuthSingleSig::new(
PublicKeyCommitment::from(EMPTY_WORD),
AuthScheme::Falcon512Poseidon2,
))
.build_existing()
.unwrap()
}
#[test]
fn test_prune_account_code_retains_latest_after_code_change() {
let mut conn = setup_test_db();
let block_0 = BlockNumber::from(0u32);
let block_code_b = BlockNumber::from(HISTORICAL_BLOCK_RETENTION + 1);
let block_prunable = BlockNumber::from(2 * HISTORICAL_BLOCK_RETENTION + 1);
insert_block_header(&mut conn, block_0);
insert_block_header(&mut conn, block_code_b);
insert_block_header(&mut conn, block_prunable);
let account_a = build_account_with_code(1);
let account_b = build_account_with_code(2);
assert_eq!(account_a.id(), account_b.id(), "accounts must share the same ID");
assert_ne!(
account_a.code().commitment(),
account_b.code().commitment(),
"accounts must have different codes"
);
let account_id = account_a.id();
let code_commitment_a = account_a.code().commitment();
let code_commitment_b = account_b.code().commitment();
upsert_accounts(&mut conn, &[make_full_state_update(&account_a)], block_0)
.expect("initial upsert failed");
upsert_accounts(&mut conn, &[make_full_state_update(&account_b)], block_code_b)
.expect("code-change upsert failed");
assert_eq!(count_account_codes(&mut conn), 2, "both codes must exist before pruning");
let (_, _, codes_deleted) =
prune_history(&mut conn, block_prunable).expect("prune_history failed");
assert_eq!(codes_deleted, 1, "exactly one code (A) must be pruned");
assert!(!account_code_exists(&mut conn, code_commitment_a), "old code A must be pruned");
assert!(
account_code_exists(&mut conn, code_commitment_b),
"current code B must be retained"
);
let (latest_header, _) =
select_account_header_with_storage_header_at_block(&mut conn, account_id, block_prunable)
.expect("query failed")
.expect("account must still exist");
assert_eq!(
latest_header.code_commitment(),
account_b.code().commitment(),
"latest account must reference code B"
);
}
#[test]
fn test_prune_account_code_retains_revisited_code() {
let mut conn = setup_test_db();
let block_0 = BlockNumber::from(0u32);
let block_code_b = BlockNumber::from(HISTORICAL_BLOCK_RETENTION + 1);
let block_code_a_again = BlockNumber::from(HISTORICAL_BLOCK_RETENTION + 2);
let block_prunable = BlockNumber::from(2 * HISTORICAL_BLOCK_RETENTION + 2);
insert_block_header(&mut conn, block_0);
insert_block_header(&mut conn, block_code_b);
insert_block_header(&mut conn, block_code_a_again);
insert_block_header(&mut conn, block_prunable);
let account_a = build_account_with_code(1);
let account_b = build_account_with_code(2);
assert_eq!(account_a.id(), account_b.id(), "accounts must share the same ID");
assert_ne!(
account_a.code().commitment(),
account_b.code().commitment(),
"accounts must have different codes"
);
let account_id = account_a.id();
let code_commitment_a = account_a.code().commitment();
let code_commitment_b = account_b.code().commitment();
upsert_accounts(&mut conn, &[make_full_state_update(&account_a)], block_0)
.expect("block 0 upsert failed");
upsert_accounts(&mut conn, &[make_full_state_update(&account_b)], block_code_b)
.expect("block code_b upsert failed");
upsert_accounts(&mut conn, &[make_full_state_update(&account_a)], block_code_a_again)
.expect("block code_a_again upsert failed");
assert_eq!(count_account_codes(&mut conn), 2, "both codes must exist before pruning");
let (_, _, codes_deleted) =
prune_history(&mut conn, block_prunable).expect("prune_history failed");
assert_eq!(codes_deleted, 1, "exactly one code (B) must be pruned");
assert!(account_code_exists(&mut conn, code_commitment_a), "code A must be retained");
assert!(!account_code_exists(&mut conn, code_commitment_b), "code B must be pruned");
let (latest_header, _) =
select_account_header_with_storage_header_at_block(&mut conn, account_id, block_prunable)
.expect("query failed")
.expect("account must still exist");
assert_eq!(
latest_header.code_commitment(),
account_a.code().commitment(),
"latest account must reference code A"
);
}