Skip to content

Commit

Permalink
Refactored delta's vault and storage to use maps (#822)
Browse files Browse the repository at this point in the history
* refactor: use maps in storage and vault deltas

* docs: update CHANGELOG.md

* refactor: address review comments

* tests: fix compilation errors

* refactor: address review comments

* refactor: address review comments
  • Loading branch information
polydez authored and bobbinth committed Aug 23, 2024
1 parent 3f7a538 commit 267e907
Show file tree
Hide file tree
Showing 16 changed files with 806 additions and 997 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- Added serialization and equality comparison for `TransactionScript` (#824).
- [BREAKING] Migrated to Miden VM v0.10 (#826).
- Added conversions for `NoteExecutionHint` (#827).
- [BREAKING] Refactored account storage and vault deltas (#822).

## 0.4.0 (2024-07-03)

Expand Down
20 changes: 12 additions & 8 deletions miden-lib/src/transaction/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ use alloc::{string::String, vec::Vec};
use core::fmt;

use miden_objects::{
accounts::AccountStorage, notes::NoteMetadata, AccountError, AssetError, Digest, Felt,
NoteError,
accounts::AccountStorage, notes::NoteMetadata, AccountDeltaError, AccountError, AssetError,
Digest, Felt, NoteError,
};

// TRANSACTION KERNEL ERROR
// ================================================================================================

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum TransactionKernelError {
AccountDeltaError(AccountDeltaError),
FailedToAddAssetToNote(NoteError),
InvalidNoteInputs {
expected: Digest,
Expand Down Expand Up @@ -39,7 +40,7 @@ impl fmt::Display for TransactionKernelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TransactionKernelError::FailedToAddAssetToNote(err) => {
write!(f, "failed to add asset to note: {err}")
write!(f, "Failed to add asset to note: {err}")
},
TransactionKernelError::InvalidNoteInputs { expected, got, data } => {
write!(
Expand All @@ -50,7 +51,7 @@ impl fmt::Display for TransactionKernelError {
},
TransactionKernelError::InvalidStorageSlotIndex(index) => {
let num_slots = AccountStorage::NUM_STORAGE_SLOTS;
write!(f, "storage slot index {index} is invalid, must be smaller than {num_slots}")
write!(f, "Storage slot index {index} is invalid, must be smaller than {num_slots}")
},
TransactionKernelError::MalformedAccountId(err) => {
write!( f, "Account id data extracted from the stack by the event handler is not well formed {err}")
Expand All @@ -59,7 +60,7 @@ impl fmt::Display for TransactionKernelError {
write!(f, "Asset data extracted from the stack by the event handler is not well formed {err}")
},
TransactionKernelError::MalformedAssetOnAccountVaultUpdate(err) => {
write!(f, "malformed asset during account vault update: {err}")
write!(f, "Malformed asset during account vault update: {err}")
},
TransactionKernelError::MalformedNoteInputs(err) => {
write!( f, "Note inputs data extracted from the advice map by the event handler is not well formed {err}")
Expand Down Expand Up @@ -89,16 +90,19 @@ impl fmt::Display for TransactionKernelError {
write!(f, "Public note missing or incomplete inputs in the advice provider")
},
TransactionKernelError::MissingStorageSlotValue(index, err) => {
write!(f, "value for storage slot {index} could not be found: {err}")
write!(f, "Value for storage slot {index} could not be found: {err}")
},
TransactionKernelError::TooFewElementsForNoteInputs => {
write!(
f,
"note input data in advice provider contains fewer elements than specified by its inputs length"
"Note input data in advice provider contains fewer elements than specified by its inputs length"
)
},
TransactionKernelError::UnknownAccountProcedure(proc_root) => {
write!(f, "account procedure with root {proc_root} is not in the advice provider")
write!(f, "Account procedure with root {proc_root} is not in the advice provider")
},
TransactionKernelError::AccountDeltaError(error) => {
write!(f, "Account delta error: {error}")
},
}
}
Expand Down
235 changes: 12 additions & 223 deletions miden-tx/src/host/account_delta_tracker.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
use alloc::{collections::BTreeMap, vec::Vec};

use miden_objects::{
accounts::{
AccountDelta, AccountId, AccountStorageDelta, AccountStub, AccountVaultDelta,
StorageMapDelta,
},
assets::{Asset, FungibleAsset, NonFungibleAsset},
Digest, Felt, Word, EMPTY_WORD, ZERO,
accounts::{AccountDelta, AccountStorageDelta, AccountStub, AccountVaultDelta},
Felt, ZERO,
};

// ACCOUNT DELTA TRACKER
// ================================================================================================

Expand All @@ -24,8 +17,8 @@ use miden_objects::{
/// - account code changes.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccountDeltaTracker {
storage: AccountStorageDeltaTracker,
vault: AccountVaultDeltaTracker,
storage: AccountStorageDelta,
vault: AccountVaultDelta,
init_nonce: Felt,
nonce_delta: Felt,
}
Expand All @@ -34,236 +27,32 @@ impl AccountDeltaTracker {
/// Returns a new [AccountDeltaTracker] instantiated for the specified account.
pub fn new(account: &AccountStub) -> Self {
Self {
storage: AccountStorageDeltaTracker::default(),
vault: AccountVaultDeltaTracker::default(),
storage: AccountStorageDelta::default(),
vault: AccountVaultDelta::default(),
init_nonce: account.nonce(),
nonce_delta: ZERO,
}
}

/// Consumes `self` and returns the resulting [AccountDelta].
pub fn into_delta(self) -> AccountDelta {
let storage_delta = self.storage.into_delta();
let vault_delta = self.vault.into_delta();
let nonce_delta = if self.nonce_delta == ZERO {
None
} else {
Some(self.init_nonce + self.nonce_delta)
};
let nonce_delta = (self.nonce_delta != ZERO).then_some(self.init_nonce + self.nonce_delta);

AccountDelta::new(storage_delta, vault_delta, nonce_delta).expect("invalid account delta")
AccountDelta::new(self.storage, self.vault, nonce_delta).expect("invalid account delta")
}

/// Tracks nonce delta.
pub fn increment_nonce(&mut self, value: Felt) {
self.nonce_delta += value;
}

/// Get the vault tracker
pub fn vault_tracker(&mut self) -> &mut AccountVaultDeltaTracker {
/// Get a mutable reference to the current vault delta
pub fn vault_delta(&mut self) -> &mut AccountVaultDelta {
&mut self.vault
}

/// Get the storage tracker
pub fn storage_tracker(&mut self) -> &mut AccountStorageDeltaTracker {
/// Get a mutable reference to the current storage delta
pub fn storage_delta(&mut self) -> &mut AccountStorageDelta {
&mut self.storage
}
}

// ACCOUNT STORAGE DELTA TRACKER
// ================================================================================================

/// The account storage delta tracker is responsible for tracking changes to the storage of the
/// account the transaction is being executed against.
///
/// The delta tracker is composed of:
/// - A map which records the latest states for the updated storage slots.
/// - A map which records the latest states for the updates storage maps
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct AccountStorageDeltaTracker {
slot_updates: BTreeMap<u8, Word>,
maps_updates: BTreeMap<u8, Vec<(Word, Word)>>,
}

impl AccountStorageDeltaTracker {
/// Consumes `self` and returns the [AccountStorageDelta] that represents the changes to
/// the account's storage.
pub fn into_delta(self) -> AccountStorageDelta {
let mut cleared_items = Vec::new();
let mut updated_items = Vec::new();
let mut updated_maps: Vec<(u8, StorageMapDelta)> = Vec::new();

for (idx, value) in self.slot_updates {
if value == EMPTY_WORD {
cleared_items.push(idx);
} else {
updated_items.push((idx, value));
}
}

for (idx, map_deltas) in self.maps_updates {
let mut updated_leafs = Vec::new();
let mut cleared_leafs = Vec::new();

for map_delta in map_deltas {
if map_delta.1 == EMPTY_WORD {
cleared_leafs.push(map_delta.0);
} else {
updated_leafs.push(map_delta);
}
}
let storage_map_delta = StorageMapDelta::from(cleared_leafs, updated_leafs);
updated_maps.push((idx, storage_map_delta));
}

AccountStorageDelta {
cleared_items,
updated_items,
updated_maps,
}
}

/// Tracks a slot change
pub fn slot_update(&mut self, slot_index: u8, new_slot_value: [Felt; 4]) {
self.slot_updates.insert(slot_index, new_slot_value);
}

/// Tracks a slot change
pub fn maps_update(&mut self, slot_index: u8, key: [Felt; 4], new_value: [Felt; 4]) {
self.maps_updates.entry(slot_index).or_default().push((key, new_value));
}
}

// ACCOUNT VAULT DELTA TRACKER
// ================================================================================================

/// The account vault delta tracker is responsible for tracking changes to the vault of the account
/// the transaction is being executed against.
///
/// The delta tracker is composed of two maps:
/// - Fungible asset map: tracks changes to the vault's fungible assets, where the key is the
/// faucet ID of the asset, and the value is the amount of the asset being added or removed from
/// the vault (positive value for added assets, negative value for removed assets).
/// - Non-fungible asset map: tracks changes to the vault's non-fungible assets, where the key is
/// the non-fungible asset, and the value is either 1 or -1 depending on whether the asset is
/// being added or removed from the vault.
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct AccountVaultDeltaTracker {
fungible_assets: BTreeMap<AccountId, i128>,
non_fungible_assets: BTreeMap<Digest, i8>,
}

impl AccountVaultDeltaTracker {
// STATE MUTATORS
// --------------------------------------------------------------------------------------------

pub fn add_asset(&mut self, asset: Asset) {
match asset {
Asset::Fungible(asset) => {
update_asset_delta(
&mut self.fungible_assets,
asset.faucet_id(),
asset.amount() as i128,
);
},
Asset::NonFungible(asset) => {
update_asset_delta(&mut self.non_fungible_assets, asset.vault_key().into(), 1)
},
}
}

/// Track asset removal.
pub fn remove_asset(&mut self, asset: Asset) {
match asset {
Asset::Fungible(asset) => {
update_asset_delta(
&mut self.fungible_assets,
asset.faucet_id(),
-(asset.amount() as i128),
);
},
Asset::NonFungible(asset) => {
update_asset_delta(&mut self.non_fungible_assets, asset.vault_key().into(), -1)
},
}
}

// CONVERSIONS
// --------------------------------------------------------------------------------------------

/// Consumes `self` and returns the [AccountVaultDelta] that represents the changes to the
/// account's vault.
pub fn into_delta(self) -> AccountVaultDelta {
let mut added_assets = Vec::new();
let mut removed_assets = Vec::new();

// process fungible assets
for (faucet_id, amount) in self.fungible_assets {
if amount > 0 {
added_assets.push(Asset::Fungible(
FungibleAsset::new(
AccountId::new_unchecked(faucet_id.into()),
amount.unsigned_abs() as u64,
)
.expect("fungible asset is well formed"),
));
} else {
removed_assets.push(Asset::Fungible(
FungibleAsset::new(
AccountId::new_unchecked(faucet_id.into()),
amount.unsigned_abs() as u64,
)
.expect("fungible asset is well formed"),
));
}
}

// process non-fungible assets
for (non_fungible_asset, amount) in self.non_fungible_assets {
match amount {
1 => {
added_assets.push(Asset::NonFungible(unsafe {
NonFungibleAsset::new_unchecked(*non_fungible_asset)
}));
},
-1 => {
removed_assets.push(Asset::NonFungible(unsafe {
NonFungibleAsset::new_unchecked(*non_fungible_asset)
}));
},
_ => unreachable!("non-fungible asset amount must be 1 or -1"),
}
}

AccountVaultDelta { added_assets, removed_assets }
}
}

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

/// Updates the provided map with the provided key and amount. If the final amount is 0, the entry
/// is removed from the map.
fn update_asset_delta<K, V>(delta_map: &mut BTreeMap<K, V>, key: K, amount: V)
where
V: core::ops::Neg,
V: core::cmp::PartialEq<<V as core::ops::Neg>::Output>,
V: core::ops::AddAssign,
V: Copy,
K: Ord,
{
use alloc::collections::btree_map::Entry;

match delta_map.entry(key) {
Entry::Occupied(mut entry) => {
if entry.get() == &-amount {
entry.remove();
} else {
*entry.get_mut() += amount;
}
},
Entry::Vacant(entry) => {
entry.insert(amount);
},
}
}
20 changes: 14 additions & 6 deletions miden-tx/src/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ impl<A: AdviceProvider, T: TransactionAuthenticator> TransactionHost<A, T> {
// update the delta tracker only if the current and new values are different
if current_slot_value != new_slot_value {
let slot_index = slot_index.as_int() as u8;
self.account_delta.storage_tracker().slot_update(slot_index, new_slot_value);
self.account_delta.storage_delta().set_item(slot_index, new_slot_value);
}

Ok(())
Expand Down Expand Up @@ -281,9 +281,11 @@ impl<A: AdviceProvider, T: TransactionAuthenticator> TransactionHost<A, T> {
];

let slot_index = slot_index.as_int() as u8;
self.account_delta
.storage_tracker()
.maps_update(slot_index, new_map_key, new_map_value);
self.account_delta.storage_delta().set_map_item(
slot_index,
new_map_key.into(),
new_map_value,
);

Ok(())
}
Expand All @@ -304,7 +306,10 @@ impl<A: AdviceProvider, T: TransactionAuthenticator> TransactionHost<A, T> {
.try_into()
.map_err(TransactionKernelError::MalformedAssetOnAccountVaultUpdate)?;

self.account_delta.vault_tracker().add_asset(asset);
self.account_delta
.vault_delta()
.add_asset(asset)
.map_err(TransactionKernelError::AccountDeltaError)?;
Ok(())
}

Expand All @@ -321,7 +326,10 @@ impl<A: AdviceProvider, T: TransactionAuthenticator> TransactionHost<A, T> {
.try_into()
.map_err(TransactionKernelError::MalformedAssetOnAccountVaultUpdate)?;

self.account_delta.vault_tracker().remove_asset(asset);
self.account_delta
.vault_delta()
.remove_asset(asset)
.map_err(TransactionKernelError::AccountDeltaError)?;
Ok(())
}

Expand Down
Loading

0 comments on commit 267e907

Please sign in to comment.