miden-ntx-builder 0.17.3

Miden network transaction builder
Documentation
//! Database query functions for the NTX builder.
//!
//! Each function takes a [`ReadTx`](miden_node_db::sqlite::ReadTx) or
//! [`WriteTx`](miden_node_db::sqlite::WriteTx) and is driven from a call site through
//! [`Database::read`](miden_node_db::sqlite::Database::read) /
//! [`Database::write`](miden_node_db::sqlite::Database::write).

use miden_node_db::DatabaseError;
use miden_node_db::sqlite::WriteTx;
use miden_protocol::Word;
use miden_protocol::block::BlockNumber;
use miden_protocol::crypto::merkle::mmr::PartialMmr;
use miden_protocol::transaction::TransactionId;

use crate::committed_block::CommittedBlockEffects;
use crate::db::queries::account_effect::NetworkAccountEffect;

pub(crate) mod account_effect;

// The scheduler detects a landed transaction from the block's own transaction list, so this read
// accessor is only used by tests to verify that `upsert_account` persists `accounts.last_tx_id`
// correctly.
#[cfg(test)]
mod account_last_tx;
#[cfg(test)]
pub use account_last_tx::account_last_tx;

mod available_notes;
pub use available_notes::{AvailableNotes, available_notes};

mod discard_notes;
pub use discard_notes::discard_notes;

mod get_account;
pub use get_account::get_account;

mod get_note_status;
pub use get_note_status::{NoteStatusRow, get_note_status};

mod insert_genesis_chain_state;
pub use insert_genesis_chain_state::insert_genesis_chain_state;

mod insert_network_notes;
pub use insert_network_notes::insert_network_notes;

mod insert_note_scripts;
pub use insert_note_scripts::insert_note_script;

mod insert_sponsorship_notes;
pub use insert_sponsorship_notes::insert_sponsorship_notes;

mod lookup_note_script;
pub use lookup_note_script::lookup_note_script;

mod mark_notes_consumed;
pub use mark_notes_consumed::mark_notes_consumed;

mod mark_sponsorships_consumed;
pub use mark_sponsorships_consumed::mark_sponsorships_consumed;

mod notes_failed;
pub use notes_failed::notes_failed;

mod ready_accounts;
pub use ready_accounts::ready_accounts;

mod reset_sponsored_notes;
pub use reset_sponsored_notes::reset_sponsored_notes;

mod select_chain_state;
pub use select_chain_state::select_chain_state;

mod select_genesis_commitment;
pub use select_genesis_commitment::select_genesis_commitment;

mod select_genesis_validator_keys;
pub use select_genesis_validator_keys::select_genesis_validator_keys;

mod sponsorships_for_pending_notes;
pub use sponsorships_for_pending_notes::select_sponsorships_for_pending_notes;

mod update_note_eligibility;
pub use update_note_eligibility::update_note_eligibility;

mod update_chain_state_tip;
pub use update_chain_state_tip::update_chain_state_tip;

mod upsert_account;
pub use upsert_account::upsert_account;

#[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: creations insert the new account, updates apply to the
///   existing committed row.
/// - Inserts each network note and `FEE_SPONSORSHIP` note (`INSERT OR IGNORE` to tolerate
///   redeliveries).
/// - Marks any of our pending notes (feature and sponsorship alike) 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.
///
/// The account upserts apply each block's network-account effects to the local store, so the next
/// attempt for an account reads its authoritative committed state. The recorded
/// `accounts.last_tx_id` and the scheduler's landing check both derive from the block's
/// `account_transactions`, so they agree on which transaction last touched each account.
pub fn apply_committed_block(
    tx: &WriteTx<'_>,
    effects: &CommittedBlockEffects,
    chain_mmr: &PartialMmr,
) -> Result<(), DatabaseError> {
    // Each non-genesis account update has an originating transaction in the same block. Genesis
    // account updates use the zero sentinel because genesis contains no transactions.
    let last_tx = effects.latest_tx_per_account();
    let is_genesis = effects.header.block_num() == BlockNumber::GENESIS;

    for (account_id, effect) in &effects.network_account_updates {
        // 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(tx, *account_id, account, last_tx_id)?;
            },
            NetworkAccountEffect::Updated(patch) => {
                // If the account is not already tracked locally, skip it.
                let Some(mut current) = get_account(tx, *account_id)? else {
                    continue;
                };
                current
                    .apply_patch(patch)
                    .expect("network account patch should apply since the block was committed");
                upsert_account(tx, *account_id, &current, last_tx_id)?;
            },
        }
    }

    let block_num = effects.header.block_num();

    insert_network_notes(tx, &effects.network_notes, block_num)?;
    insert_sponsorship_notes(tx, &effects.sponsorship_notes)?;

    mark_notes_consumed(tx, &effects.nullifiers, block_num)?;
    mark_sponsorships_consumed(tx, &effects.nullifiers, block_num)?;

    // Applied after the consumption marks so a feature note consumed in this same block is not made
    // eligible again.
    reset_sponsored_notes(tx, &effects.sponsorship_notes, block_num)?;

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

    Ok(())
}