Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 bin/node/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ pub struct BundledValidatorConfig {
}

impl BundledValidatorConfig {
/// Converts the [`ValidatorConfig`] into a URL and an optional [`SocketAddr`].
/// Converts the [`BundledValidatorConfig`] into a URL and an optional [`SocketAddr`].
///
/// If the `validator_url` is set, it returns the URL and `None` for the [`SocketAddr`].
///
Expand Down
1 change: 1 addition & 0 deletions crates/store/src/db/models/queries/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,7 @@ pub(crate) fn upsert_accounts(
.execute(conn)?;

// insert pending storage map entries
// TODO consider batching
for (acc_id, slot_name, key, value) in pending_storage_inserts {
insert_account_storage_map_value(conn, acc_id, block_num, slot_name, key, value)?;
}
Expand Down
105 changes: 52 additions & 53 deletions crates/store/src/inner_forest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use miden_protocol::asset::{Asset, AssetVaultKey, AssetWitness, FungibleAsset};
use miden_protocol::block::BlockNumber;
use miden_protocol::crypto::merkle::smt::{SMT_DEPTH, SmtForest};
use miden_protocol::crypto::merkle::{EmptySubtreeRoots, MerkleError};
use miden_protocol::errors::{AssetError, StorageMapError};
use miden_protocol::errors::{AccountError, AssetError, StorageMapError};
use miden_protocol::{EMPTY_WORD, Word};
use thiserror::Error;

Expand All @@ -25,6 +25,12 @@ mod tests;

#[derive(Debug, Error)]
pub enum InnerForestError {
#[error(transparent)]
Account(#[from] AccountError),
#[error(transparent)]
Asset(#[from] AssetError),
#[error(transparent)]
Merkle(#[from] MerkleError),
Comment on lines +28 to +33
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you #[source] these for call site clarity

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should use InnerForestError(Box<dyn std::error::Error + Send + Sync + 'static>) but I'd hold off on it until all my PRs are done, similarly for adding map_err(ForestInnerError::$variant) conversions in quite a few places.

#[error(
"balance underflow: account {account_id}, faucet {faucet_id}, \
previous balance {prev_balance}, delta {delta}"
Expand Down Expand Up @@ -89,6 +95,14 @@ impl InnerForest {
*EmptySubtreeRoots::entry(SMT_DEPTH, 0)
}

/// Retrieves the most recent vault root for an account.
fn get_latest_vault_root(&self, account_id: AccountId) -> Word {
self.vault_roots
.range((account_id, BlockNumber::GENESIS)..=(account_id, BlockNumber::MAX))
.next_back()
.map_or_else(Self::empty_smt_root, |(_, root)| *root)
}

/// Retrieves a vault root for the specified account at or before the specified block.
pub(crate) fn get_vault_root(
&self,
Expand Down Expand Up @@ -268,8 +282,20 @@ impl InnerForest {
let account_id = delta.id();
let is_full_state = delta.is_full_state();

#[cfg(debug_assertions)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this an expensive check? If not, maybe we should enable it fully?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not particularly expensive, but it's still a full iteration over all storage maps and entries - for large accounts this could take a bit extra, but I don't think it's excessive.

if is_full_state {
let has_vault_root = self.vault_roots.keys().any(|(id, _)| *id == account_id);
let has_storage_root = self.storage_map_roots.keys().any(|(id, ..)| *id == account_id);
let has_storage_entries = self.storage_entries.keys().any(|(id, ..)| *id == account_id);

assert!(
!has_vault_root && !has_storage_root && !has_storage_entries,
"full-state delta should not be applied to existing account"
);
}

if is_full_state {
self.insert_account_vault(block_num, account_id, delta.vault());
self.insert_account_vault(block_num, account_id, delta.vault())?;
} else if !delta.vault().is_empty() {
self.update_account_vault(block_num, account_id, delta.vault())?;
}
Expand All @@ -283,61 +309,37 @@ impl InnerForest {
Ok(())
}

// ASSET VAULT DELTA PROCESSING
// --------------------------------------------------------------------------------------------

/// Retrieves the most recent vault SMT root for an account. If no vault root is found for the
/// account, returns an empty SMT root.
fn get_latest_vault_root(&self, account_id: AccountId) -> Word {
self.vault_roots
.range((account_id, BlockNumber::GENESIS)..=(account_id, BlockNumber::MAX))
.next_back()
.map_or_else(Self::empty_smt_root, |(_, root)| *root)
}

/// Inserts asset vault data into the forest for the specified account. Assumes that asset
/// vault for this account does not yet exist in the forest.
fn insert_account_vault(
&mut self,
block_num: BlockNumber,
account_id: AccountId,
delta: &AccountVaultDelta,
) {
// get the current vault root for the account, and make sure it is empty
vault_delta: &AccountVaultDelta,
) -> Result<(), InnerForestError> {
let prev_root = self.get_latest_vault_root(account_id);
assert_eq!(prev_root, Self::empty_smt_root(), "account should not be in the forest");

// if there are no assets in the vault, add a root of an empty SMT to the vault roots map
// so that the map has entries for all accounts, and then return (i.e., no need to insert
// anything into the forest)
if delta.is_empty() {
if vault_delta.is_empty() {
self.vault_roots.insert((account_id, block_num), prev_root);
return;
return Ok(());
}

let mut entries: Vec<(Word, Word)> = Vec::new();

// process fungible assets
for (faucet_id, amount_delta) in delta.fungible().iter() {
for (faucet_id, amount_delta) in vault_delta.fungible().iter() {
let amount =
(*amount_delta).try_into().expect("full-state amount should be non-negative");
let asset = FungibleAsset::new(*faucet_id, amount).expect("valid faucet id");
let asset = FungibleAsset::new(*faucet_id, amount)?;
entries.push((asset.vault_key().into(), asset.into()));
}

// process non-fungible assets
for (&asset, _action) in delta.non_fungible().iter() {
// TODO: assert that action is addition
for (&asset, action) in vault_delta.non_fungible().iter() {
debug_assert_eq!(action, &NonFungibleDeltaAction::Add);
entries.push((asset.vault_key().into(), asset.into()));
}

assert!(!entries.is_empty(), "non-empty delta should contain entries");
let num_entries = entries.len();

let new_root = self
.forest
.batch_insert(prev_root, entries)
.expect("forest insertion should succeed");
let new_root = self.forest.batch_insert(prev_root, entries)?;

self.vault_roots.insert((account_id, block_num), new_root);

Expand All @@ -348,6 +350,7 @@ impl InnerForest {
vault_entries = num_entries,
"Inserted vault into forest"
);
Ok(())
}

/// Updates the forest with vault changes from a delta. The vault delta is assumed to be
Expand All @@ -363,19 +366,15 @@ impl InnerForest {
&mut self,
block_num: BlockNumber,
account_id: AccountId,
delta: &AccountVaultDelta,
) -> Result<(), InnerForestError> {
assert!(!delta.is_empty(), "expected the delta not to be empty");

// get the previous vault root; the root could be for an empty or non-empty SMT
vault_delta: &AccountVaultDelta,
) -> Result<Word, InnerForestError> {
let prev_root = self.get_latest_vault_root(account_id);

let mut entries: Vec<(Word, Word)> = Vec::new();

// Process fungible assets
for (faucet_id, amount_delta) in delta.fungible().iter() {
let key: Word =
FungibleAsset::new(*faucet_id, 0).expect("valid faucet id").vault_key().into();
for (faucet_id, amount_delta) in vault_delta.fungible().iter() {
let key: Word = FungibleAsset::new(*faucet_id, 0)?.vault_key().into();

let new_amount = {
// amount delta is a change that must be applied to previous balance.
Expand All @@ -402,27 +401,28 @@ impl InnerForest {
let value = if new_amount == 0 {
EMPTY_WORD
} else {
FungibleAsset::new(*faucet_id, new_amount).expect("valid fungible asset").into()
FungibleAsset::new(*faucet_id, new_amount)?.into()
};
entries.push((key, value));
}

// Process non-fungible assets
for (asset, action) in delta.non_fungible().iter() {
for (asset, action) in vault_delta.non_fungible().iter() {
let value = match action {
NonFungibleDeltaAction::Add => Word::from(Asset::NonFungible(*asset)),
NonFungibleDeltaAction::Remove => EMPTY_WORD,
};
entries.push((asset.vault_key().into(), value));
}

assert!(!entries.is_empty(), "non-empty delta should contain entries");
if entries.is_empty() {
self.vault_roots.insert((account_id, block_num), prev_root);
return Ok(prev_root);
}

let num_entries = entries.len();

let new_root = self
.forest
.batch_insert(prev_root, entries)
.expect("forest insertion should succeed");
let new_root = self.forest.batch_insert(prev_root, entries)?;

self.vault_roots.insert((account_id, block_num), new_root);

Expand All @@ -433,14 +433,13 @@ impl InnerForest {
vault_entries = num_entries,
"Updated vault in forest"
);
Ok(())
Ok(new_root)
}

// STORAGE MAP DELTA PROCESSING
// --------------------------------------------------------------------------------------------

/// Retrieves the most recent storage map SMT root for an account slot. If no storage root is
/// found for the slot, returns an empty SMT root.
/// Retrieves the most recent storage map SMT root for an account slot.
fn get_latest_storage_map_root(
&self,
account_id: AccountId,
Expand Down
1 change: 1 addition & 0 deletions crates/store/src/state/apply_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ impl State {
.account_tree
.apply_mutations(account_tree_update)
.expect("Unreachable: old account tree root must be checked before this step");

inner.blockchain.push(block_commitment);

Ok(())
Expand Down
1 change: 0 additions & 1 deletion crates/store/src/state/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,6 @@ pub async fn load_smt_forest(
StateInitializationError::AccountToDeltaConversionFailed(e.to_string())
})?;

// Use the unified update method (will recognize it's a full-state delta)
forest.update_account(block_num, &delta)?;
}

Expand Down
2 changes: 1 addition & 1 deletion crates/validator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod signers;
mod tx_validation;

pub use server::Validator;
pub use signers::ValidatorSigner;
pub use signers::{KmsSigner, ValidatorSigner};

// CONSTANTS
// =================================================================================================
Expand Down
4 changes: 2 additions & 2 deletions crates/validator/src/signers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ pub enum ValidatorSigner {
impl ValidatorSigner {
/// Constructs a signer which uses an AWS KMS key for signing.
///
/// See [`KmsSigner::new`] for details as to env var configuration and AWS IAM policies required
/// to use this functionality.
/// See [`KmsSigner`] for details as to env var configuration and AWS IAM policies
/// required to use this functionality.
pub async fn new_kms(key_id: impl Into<String>) -> anyhow::Result<Self> {
let kms_signer = KmsSigner::new(key_id).await?;
Ok(Self::Kms(kms_signer))
Expand Down
Loading