Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
- Pin tool versions in CI ([#1523](https://github.com/0xMiden/miden-node/pull/1523)).
- Add `GetVaultAssetWitnesses` and `GetStorageMapWitness` RPC endpoints to store ([#1529](https://github.com/0xMiden/miden-node/pull/1529)).
- Add check to ensure tree store state is in sync with database storage ([#1532](https://github.com/0xMiden/miden-node/issues/1534)).
- Improve speed of account updates ([#1567](https://github.com/0xMiden/miden-node/pull/1567)).
- Improve speed of account updates ([#1567](https://github.com/0xMiden/miden-node/pull/1567), [#1789](https://github.com/0xMiden/node/pull/1789)).
- Ensure store terminates on nullifier tree or account tree root vs header mismatch (#[#1569](https://github.com/0xMiden/miden-node/pull/1569)).
- Added support for foreign accounts to `NtxDataStore` and add `GetAccount` endpoint to NTX Builder gRPC store client ([#1521](https://github.com/0xMiden/miden-node/pull/1521)).
- Use paged queries for tree rebuilding to reduce memory usage during startup ([#1536](https://github.com/0xMiden/miden-node/pull/1536)).
Expand Down
8 changes: 3 additions & 5 deletions crates/store/src/db/models/queries/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ use delta::{
AccountStateForInsert,
PartialAccountState,
apply_storage_delta,
select_latest_vault_assets,
select_minimal_account_state_headers,
select_vault_balances_by_faucet_ids,
};
Expand Down Expand Up @@ -1071,7 +1072,6 @@ fn prepare_partial_account_update(
update: &BlockAccountUpdate,
account_id: AccountId,
delta: &miden_protocol::account::delta::AccountDelta,
block_num: BlockNumber,
) -> Result<(AccountStateForInsert, PendingStorageInserts, PendingAssetInserts), DatabaseError> {
// Build the minimal account state needed for partial delta application.
// Only load the storage map entries and vault balances that will receive updates.
Expand Down Expand Up @@ -1141,9 +1141,7 @@ fn prepare_partial_account_update(

// --- Update the vault root by constructing the asset vault from DB.
let new_vault_root = {
let (_last_block, assets) =
select_account_vault_assets(conn, account_id, BlockNumber::GENESIS..=block_num)?;
let assets: Vec<Asset> = assets.into_iter().filter_map(|entry| entry.asset).collect();
let assets = select_latest_vault_assets(conn, account_id)?;
let mut vault = AssetVault::new(&assets)?;
vault.apply_delta(delta.vault())?;
vault.root()
Expand Down Expand Up @@ -1243,7 +1241,7 @@ pub(crate) fn upsert_accounts(

// Update of an existing account
AccountUpdateDetails::Delta(delta) => {
prepare_partial_account_update(conn, update, account_id, delta, block_num)?
prepare_partial_account_update(conn, update, account_id, delta)?
},
};

Expand Down
30 changes: 30 additions & 0 deletions crates/store/src/db/models/queries/accounts/delta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,36 @@ pub(super) fn select_vault_balances_by_faucet_ids(
Ok(balances)
}

/// Selects the latest vault assets for an account.
///
/// # Raw SQL
///
/// ```sql
/// SELECT vault_key, asset
/// FROM account_vault_assets
/// WHERE account_id = ?1 AND is_latest = 1
/// ```
pub(super) fn select_latest_vault_assets(
conn: &mut SqliteConnection,
account_id: AccountId,
) -> Result<Vec<Asset>, DatabaseError> {
use schema::account_vault_assets as vault;

let entries: Vec<(Vec<u8>, Option<Vec<u8>>)> =
SelectDsl::select(vault::table, (vault::vault_key, vault::asset))
.filter(vault::account_id.eq(account_id.to_bytes()))
.filter(vault::is_latest.eq(true))
.load(conn)?;

let mut assets = Vec::new();
for (_vault_key_bytes, maybe_asset_bytes) in entries {
if let Some(asset_bytes) = maybe_asset_bytes {
assets.push(Asset::read_from_bytes(&asset_bytes)?);
}
}
Ok(assets)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let mut assets = Vec::new();
for (_vault_key_bytes, maybe_asset_bytes) in entries {
if let Some(asset_bytes) = maybe_asset_bytes {
assets.push(Asset::read_from_bytes(&asset_bytes)?);
}
}
Ok(assets)
Result::from_iter(entries.into_iter().filter_map(|(_vault_key_bytes, maybe_asset_bytes)| { maybe_asset_bytes.map(Asset::read_from_bytes).transpose()}));

}

// HELPER FUNCTIONS
// ================================================================================================

Expand Down
55 changes: 52 additions & 3 deletions crates/store/src/db/models/queries/accounts/delta/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,13 +311,19 @@ fn optimized_delta_matches_full_account_method() {
}

#[test]
#[expect(
clippy::too_many_lines,
reason = "test exercises vault deltas across multiple blocks"
)]
fn optimized_delta_updates_non_empty_vault() {
const ACCOUNT_SEED: [u8; 32] = [40u8; 32];
const BLOCK_NUM_1: u32 = 1;
const BLOCK_NUM_2: u32 = 2;
const BLOCK_NUM_3: u32 = 3;
const NONCE_DELTA: u64 = 1;
const INITIAL_AMOUNT: u64 = 700;
const ADDED_AMOUNT: u64 = 250;
const ADDED_AMOUNT_BLOCK_2: u64 = 250;
const ADDED_AMOUNT_BLOCK_3: u64 = 150;
const SLOT_INDEX: usize = 0;

let mut conn = setup_test_db();
Expand Down Expand Up @@ -355,9 +361,12 @@ fn optimized_delta_updates_non_empty_vault() {

let block_1 = BlockNumber::from(BLOCK_NUM_1);
let block_2 = BlockNumber::from(BLOCK_NUM_2);
let block_3 = BlockNumber::from(BLOCK_NUM_3);
insert_block_header(&mut conn, block_1);
insert_block_header(&mut conn, block_2);
insert_block_header(&mut conn, block_3);

// Block 1: insert full-state delta (initial account with 700 tokens of faucet_id)
let delta_initial = AccountDelta::try_from(account.clone()).unwrap();
let account_update_initial = BlockAccountUpdate::new(
account.id(),
Expand All @@ -369,9 +378,10 @@ fn optimized_delta_updates_non_empty_vault() {
let full_account_before =
select_full_account(&mut conn, account.id()).expect("Failed to load full account");

// Block 2: partial delta — remove faucet_id (700), add faucet_id_1 (250)
let mut vault_delta = AccountVaultDelta::default();
vault_delta
.add_asset(Asset::Fungible(FungibleAsset::new(faucet_id_1, ADDED_AMOUNT).unwrap()))
.add_asset(Asset::Fungible(FungibleAsset::new(faucet_id_1, ADDED_AMOUNT_BLOCK_2).unwrap()))
.unwrap();
vault_delta
.remove_asset(Asset::Fungible(FungibleAsset::new(faucet_id, INITIAL_AMOUNT).unwrap()))
Expand Down Expand Up @@ -403,14 +413,53 @@ fn optimized_delta_updates_non_empty_vault() {
assert_eq!(vault_assets_after.len(), 1, "Should have 1 vault asset");
assert_matches!(&vault_assets_after[0], Asset::Fungible(f) => {
assert_eq!(f.faucet_id(), faucet_id_1, "Faucet ID should match");
assert_eq!(f.amount(), ADDED_AMOUNT, "Amount should match");
assert_eq!(f.amount(), ADDED_AMOUNT_BLOCK_2, "Amount should match");
});

let full_account_after = select_full_account(&mut conn, account.id())
.expect("Failed to load full account after update");

assert_eq!(full_account_after.vault().root(), expected_vault_root);
assert_eq!(full_account_after.to_commitment(), expected_commitment);

// Block 3: partial delta — add more of faucet_id_1 (150 more, total = 400)
let mut vault_delta_3 = AccountVaultDelta::default();
vault_delta_3
.add_asset(Asset::Fungible(FungibleAsset::new(faucet_id_1, ADDED_AMOUNT_BLOCK_3).unwrap()))
.unwrap();

let partial_delta_3 = AccountDelta::new(
account.id(),
AccountStorageDelta::new(),
vault_delta_3,
Felt::new(NONCE_DELTA),
)
.unwrap();

let mut expected_after_3 = full_account_after.clone();
expected_after_3.apply_delta(&partial_delta_3).unwrap();
let commitment_3 = expected_after_3.to_commitment();
let expected_vault_root_3 = expected_after_3.vault().root();

let account_update_3 = BlockAccountUpdate::new(
account.id(),
commitment_3,
AccountUpdateDetails::Delta(partial_delta_3),
);
upsert_accounts(&mut conn, &[account_update_3], block_3).expect("Block 3 upsert failed");

let full_account_final =
select_full_account(&mut conn, account.id()).expect("Failed to load after block 3");

let final_assets: Vec<Asset> = full_account_final.vault().assets().collect();
assert_eq!(final_assets.len(), 1, "Should have exactly 1 vault asset");
assert_matches!(&final_assets[0], Asset::Fungible(f) => {
assert_eq!(f.faucet_id(), faucet_id_1);
assert_eq!(f.amount(), ADDED_AMOUNT_BLOCK_2 + ADDED_AMOUNT_BLOCK_3, "Expected total of 400");
});

assert_eq!(full_account_final.vault().root(), expected_vault_root_3);
assert_eq!(full_account_final.to_commitment(), commitment_3);
}

#[test]
Expand Down
Loading