miden-ntx-builder 0.15.0

Miden network transaction builder
Documentation
//! Database query functions for the NTX builder.

use std::collections::{HashMap, HashSet};

use diesel::prelude::*;
use miden_node_db::DatabaseError;
use miden_protocol::Word;
use miden_protocol::account::AccountId;
use miden_protocol::block::BlockNumber;
use miden_protocol::crypto::merkle::mmr::PartialMmr;
use miden_protocol::transaction::TransactionId;

use super::account_effect::NetworkAccountEffect;
use crate::committed_block::CommittedBlockEffects;

mod accounts;
pub use accounts::*;

mod chain_state;
pub use chain_state::*;

mod note_scripts;
pub use note_scripts::*;

mod notes;
pub use notes::*;

#[cfg(test)]
mod tests;

// COMMITTED BLOCK APPLICATION
// ================================================================================================

/// Applies a committed block's effects to the database in a single transaction:
///
/// - Upserts each touched network account: new full-state deltas insert, partial deltas apply to
///   the existing committed row.
/// - Inserts each network note (`INSERT OR IGNORE` to tolerate redeliveries).
/// - Marks any of our pending notes whose nullifiers appear in this block as `committed_at =
///   block_num`, preserving the row so the `GetNetworkNoteStatus` endpoint can report the full
///   lifecycle.
/// - Updates the singleton `chain_state` row's tip with the new block header and the
///   post-application chain MMR.
///
/// Returns the set of network accounts that were touched by this block (account-state updates or
/// notes targeting the account).
pub fn apply_committed_block(
    conn: &mut SqliteConnection,
    effects: &CommittedBlockEffects,
    chain_mmr: &PartialMmr,
) -> Result<Vec<AccountId>, DatabaseError> {
    let mut affected_accounts: HashSet<AccountId> = HashSet::new();

    // The latest transaction in this block per account. For block-producer output every committed
    // account update originates from a transaction in the same block, so each upserted account has
    // an entry here. Collecting into a map keeps the last transaction per account (block order is
    // preserved). The genesis block is the sole exception: it commits account state directly with
    // no transactions, so genesis accounts fall back to the zero sentinel below.
    let last_tx: HashMap<AccountId, _> = effects.account_transactions.iter().copied().collect();
    let is_genesis = effects.header.block_num() == BlockNumber::GENESIS;

    for (account_id, details) in &effects.network_account_updates {
        let Some(effect) = NetworkAccountEffect::from_protocol(details) else {
            continue;
        };
        // Genesis seeds account state with no originating transaction, so it stores a zero
        // `TransactionId` sentinel.
        let last_tx_id = last_tx.get(account_id).copied().unwrap_or_else(|| {
            assert!(
                is_genesis,
                "a committed account update must originate from a transaction in the block",
            );
            TransactionId::from_raw(Word::empty())
        });
        match effect {
            NetworkAccountEffect::Created(account) => {
                upsert_account(conn, *account_id, &account, last_tx_id)?;
            },
            NetworkAccountEffect::Updated(delta) => {
                // If the account is not already tracked locally, skip it.
                let Some(mut current) = get_account(conn, *account_id)? else {
                    continue;
                };
                current
                    .apply_delta(&delta)
                    .expect("network account delta should apply since the block was committed");
                upsert_account(conn, *account_id, &current, last_tx_id)?;
            },
        }
        affected_accounts.insert(*account_id);
    }

    for note in &effects.network_notes {
        affected_accounts.insert(note.target_account_id());
    }
    insert_network_notes(conn, &effects.network_notes)?;

    mark_notes_consumed(conn, &effects.nullifiers, effects.header.block_num())?;

    update_chain_state_tip(conn, effects.header.block_num(), &effects.header, chain_mmr)?;

    Ok(affected_accounts.into_iter().collect())
}