Skip to content

Commit b1e0382

Browse files
TomasArracheammagician
authored andcommitted
fix(store): query only latest vault rows when applying account delta (#1789)
1 parent 2f1389b commit b1e0382

File tree

5 files changed

+87
-10
lines changed

5 files changed

+87
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
- Pin tool versions in CI ([#1523](https://github.com/0xMiden/miden-node/pull/1523)).
118118
- Add `GetVaultAssetWitnesses` and `GetStorageMapWitness` RPC endpoints to store ([#1529](https://github.com/0xMiden/miden-node/pull/1529)).
119119
- Add check to ensure tree store state is in sync with database storage ([#1532](https://github.com/0xMiden/miden-node/issues/1534)).
120-
- Improve speed of account updates ([#1567](https://github.com/0xMiden/miden-node/pull/1567)).
120+
- Improve speed of account updates ([#1567](https://github.com/0xMiden/miden-node/pull/1567), [#1789](https://github.com/0xMiden/node/pull/1789)).
121121
- Ensure store terminates on nullifier tree or account tree root vs header mismatch (#[#1569](https://github.com/0xMiden/miden-node/pull/1569)).
122122
- 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)).
123123
- Use paged queries for tree rebuilding to reduce memory usage during startup ([#1536](https://github.com/0xMiden/miden-node/pull/1536)).

crates/store/src/account_state_forest/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ impl AccountStateForest {
600600
slot_name: &StorageSlotName,
601601
) -> Word {
602602
let lineage = Self::storage_lineage_id(account_id, slot_name);
603-
self.forest.latest_root(lineage).map_or_else(Self::empty_smt_root, |root| root)
603+
self.forest.latest_root(lineage).unwrap_or_else(Self::empty_smt_root)
604604
}
605605

606606
/// Updates the forest with storage map changes from a delta.

crates/store/src/db/models/queries/accounts.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ use delta::{
5757
AccountStateForInsert,
5858
PartialAccountState,
5959
apply_storage_delta,
60+
select_latest_vault_assets,
6061
select_minimal_account_state_headers,
6162
select_vault_balances_by_faucet_ids,
6263
};
@@ -1071,7 +1072,6 @@ fn prepare_partial_account_update(
10711072
update: &BlockAccountUpdate,
10721073
account_id: AccountId,
10731074
delta: &miden_protocol::account::delta::AccountDelta,
1074-
block_num: BlockNumber,
10751075
) -> Result<(AccountStateForInsert, PendingStorageInserts, PendingAssetInserts), DatabaseError> {
10761076
// Build the minimal account state needed for partial delta application.
10771077
// Only load the storage map entries and vault balances that will receive updates.
@@ -1141,9 +1141,7 @@ fn prepare_partial_account_update(
11411141

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

12441242
// Update of an existing account
12451243
AccountUpdateDetails::Delta(delta) => {
1246-
prepare_partial_account_update(conn, update, account_id, delta, block_num)?
1244+
prepare_partial_account_update(conn, update, account_id, delta)?
12471245
},
12481246
};
12491247

crates/store/src/db/models/queries/accounts/delta.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,36 @@ pub(super) fn select_vault_balances_by_faucet_ids(
189189
Ok(balances)
190190
}
191191

192+
/// Selects the latest vault assets for an account.
193+
///
194+
/// # Raw SQL
195+
///
196+
/// ```sql
197+
/// SELECT vault_key, asset
198+
/// FROM account_vault_assets
199+
/// WHERE account_id = ?1 AND is_latest = 1
200+
/// ```
201+
pub(super) fn select_latest_vault_assets(
202+
conn: &mut SqliteConnection,
203+
account_id: AccountId,
204+
) -> Result<Vec<Asset>, DatabaseError> {
205+
use schema::account_vault_assets as vault;
206+
207+
let entries: Vec<(Vec<u8>, Option<Vec<u8>>)> =
208+
SelectDsl::select(vault::table, (vault::vault_key, vault::asset))
209+
.filter(vault::account_id.eq(account_id.to_bytes()))
210+
.filter(vault::is_latest.eq(true))
211+
.load(conn)?;
212+
213+
entries
214+
.into_iter()
215+
.filter_map(|(_vault_key_bytes, maybe_asset_bytes)| {
216+
maybe_asset_bytes.map(|bytes| Asset::read_from_bytes(&bytes))
217+
})
218+
.collect::<Result<Vec<_>, _>>()
219+
.map_err(Into::into)
220+
}
221+
192222
// HELPER FUNCTIONS
193223
// ================================================================================================
194224

crates/store/src/db/models/queries/accounts/delta/tests.rs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,13 +311,19 @@ fn optimized_delta_matches_full_account_method() {
311311
}
312312

313313
#[test]
314+
#[expect(
315+
clippy::too_many_lines,
316+
reason = "test exercises vault deltas across multiple blocks"
317+
)]
314318
fn optimized_delta_updates_non_empty_vault() {
315319
const ACCOUNT_SEED: [u8; 32] = [40u8; 32];
316320
const BLOCK_NUM_1: u32 = 1;
317321
const BLOCK_NUM_2: u32 = 2;
322+
const BLOCK_NUM_3: u32 = 3;
318323
const NONCE_DELTA: u64 = 1;
319324
const INITIAL_AMOUNT: u64 = 700;
320-
const ADDED_AMOUNT: u64 = 250;
325+
const ADDED_AMOUNT_BLOCK_2: u64 = 250;
326+
const ADDED_AMOUNT_BLOCK_3: u64 = 150;
321327
const SLOT_INDEX: usize = 0;
322328

323329
let mut conn = setup_test_db();
@@ -355,9 +361,12 @@ fn optimized_delta_updates_non_empty_vault() {
355361

356362
let block_1 = BlockNumber::from(BLOCK_NUM_1);
357363
let block_2 = BlockNumber::from(BLOCK_NUM_2);
364+
let block_3 = BlockNumber::from(BLOCK_NUM_3);
358365
insert_block_header(&mut conn, block_1);
359366
insert_block_header(&mut conn, block_2);
367+
insert_block_header(&mut conn, block_3);
360368

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

381+
// Block 2: partial delta — remove faucet_id (700), add faucet_id_1 (250)
372382
let mut vault_delta = AccountVaultDelta::default();
373383
vault_delta
374-
.add_asset(Asset::Fungible(FungibleAsset::new(faucet_id_1, ADDED_AMOUNT).unwrap()))
384+
.add_asset(Asset::Fungible(FungibleAsset::new(faucet_id_1, ADDED_AMOUNT_BLOCK_2).unwrap()))
375385
.unwrap();
376386
vault_delta
377387
.remove_asset(Asset::Fungible(FungibleAsset::new(faucet_id, INITIAL_AMOUNT).unwrap()))
@@ -403,14 +413,53 @@ fn optimized_delta_updates_non_empty_vault() {
403413
assert_eq!(vault_assets_after.len(), 1, "Should have 1 vault asset");
404414
assert_matches!(&vault_assets_after[0], Asset::Fungible(f) => {
405415
assert_eq!(f.faucet_id(), faucet_id_1, "Faucet ID should match");
406-
assert_eq!(f.amount(), ADDED_AMOUNT, "Amount should match");
416+
assert_eq!(f.amount(), ADDED_AMOUNT_BLOCK_2, "Amount should match");
407417
});
408418

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

412422
assert_eq!(full_account_after.vault().root(), expected_vault_root);
413423
assert_eq!(full_account_after.to_commitment(), expected_commitment);
424+
425+
// Block 3: partial delta — add more of faucet_id_1 (150 more, total = 400)
426+
let mut vault_delta_3 = AccountVaultDelta::default();
427+
vault_delta_3
428+
.add_asset(Asset::Fungible(FungibleAsset::new(faucet_id_1, ADDED_AMOUNT_BLOCK_3).unwrap()))
429+
.unwrap();
430+
431+
let partial_delta_3 = AccountDelta::new(
432+
account.id(),
433+
AccountStorageDelta::new(),
434+
vault_delta_3,
435+
Felt::new(NONCE_DELTA),
436+
)
437+
.unwrap();
438+
439+
let mut expected_after_3 = full_account_after.clone();
440+
expected_after_3.apply_delta(&partial_delta_3).unwrap();
441+
let commitment_3 = expected_after_3.to_commitment();
442+
let expected_vault_root_3 = expected_after_3.vault().root();
443+
444+
let account_update_3 = BlockAccountUpdate::new(
445+
account.id(),
446+
commitment_3,
447+
AccountUpdateDetails::Delta(partial_delta_3),
448+
);
449+
upsert_accounts(&mut conn, &[account_update_3], block_3).expect("Block 3 upsert failed");
450+
451+
let full_account_final =
452+
select_full_account(&mut conn, account.id()).expect("Failed to load after block 3");
453+
454+
let final_assets: Vec<Asset> = full_account_final.vault().assets().collect();
455+
assert_eq!(final_assets.len(), 1, "Should have exactly 1 vault asset");
456+
assert_matches!(&final_assets[0], Asset::Fungible(f) => {
457+
assert_eq!(f.faucet_id(), faucet_id_1);
458+
assert_eq!(f.amount(), ADDED_AMOUNT_BLOCK_2 + ADDED_AMOUNT_BLOCK_3, "Expected total of 400");
459+
});
460+
461+
assert_eq!(full_account_final.vault().root(), expected_vault_root_3);
462+
assert_eq!(full_account_final.to_commitment(), commitment_3);
414463
}
415464

416465
#[test]

0 commit comments

Comments
 (0)