Skip to content
Draft
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
55 changes: 55 additions & 0 deletions crates/store/src/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,47 @@ impl BlockStore {
}
}

// PROVING INPUTS STORAGE
// --------------------------------------------------------------------------------------------

#[instrument(
target = COMPONENT,
name = "store.block_store.save_proving_inputs",
skip_all,
err,
fields(block.number = block_num.as_u32(), inputs_size = data.len())
)]
pub async fn save_proving_inputs(
&self,
block_num: BlockNumber,
data: &[u8],
) -> std::io::Result<()> {
let (epoch_path, inputs_path) = self.epoch_inputs_path(block_num)?;
if !epoch_path.exists() {
tokio::fs::create_dir_all(epoch_path).await?;
}
tokio::fs::write(inputs_path, data).await
}

pub async fn load_proving_inputs(
&self,
block_num: BlockNumber,
) -> std::io::Result<Option<Vec<u8>>> {
match tokio::fs::read(self.inputs_path(block_num)).await {
Ok(data) => Ok(Some(data)),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}

pub async fn delete_proving_inputs(&self, block_num: BlockNumber) -> std::io::Result<()> {
match tokio::fs::remove_file(self.inputs_path(block_num)).await {
Ok(()) => Ok(()),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(err) => Err(err),
}
}

// HELPER FUNCTIONS
// --------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -163,6 +204,20 @@ impl BlockStore {
Ok((epoch_path.to_path_buf(), proof_path))
}

fn inputs_path(&self, block_num: BlockNumber) -> PathBuf {
let block_num = block_num.as_u32();
let epoch = block_num >> 16;
let epoch_dir = self.store_dir.join(format!("{epoch:04x}"));
epoch_dir.join(format!("inputs_{block_num:08x}.dat"))
}

fn epoch_inputs_path(&self, block_num: BlockNumber) -> std::io::Result<(PathBuf, PathBuf)> {
let inputs_path = self.inputs_path(block_num);
let epoch_path = inputs_path.parent().ok_or(std::io::Error::from(ErrorKind::NotFound))?;

Ok((epoch_path.to_path_buf(), inputs_path))
}

pub fn display(&self) -> std::path::Display<'_> {
self.store_dir.display()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Restore proving_inputs and proven_in_sequence columns (data is not recovered).
ALTER TABLE block_headers ADD COLUMN proving_inputs BLOB;
ALTER TABLE block_headers ADD COLUMN proven_in_sequence BOOLEAN NOT NULL DEFAULT FALSE;
CREATE INDEX block_headers_proven_desc ON block_headers(block_num DESC) WHERE proving_inputs IS NULL;
CREATE INDEX block_headers_proven_in_sequence ON block_headers(block_num DESC) WHERE proven_in_sequence = TRUE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Move proving inputs out of the database.
--
-- Proving inputs are large BLOBs that only serve the proof scheduler; they now live as
-- `inputs_<block_num>.dat` files in the block store alongside block data and proofs.
-- The proven-in-sequence tip is tracked by a small `proven_tip` file in the data directory.
--
-- Drop indexes that reference the columns being removed first (required by SQLite before DROP
-- COLUMN can succeed for indexed columns).
DROP INDEX block_headers_proven_desc;
DROP INDEX block_headers_proven_in_sequence;
ALTER TABLE block_headers DROP COLUMN proving_inputs;
ALTER TABLE block_headers DROP COLUMN proven_in_sequence;
114 changes: 3 additions & 111 deletions crates/store/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::sync::Arc;
use anyhow::Context;
use diesel::{Connection, QueryableByName, RunQueryDsl, SqliteConnection};
use miden_node_proto::domain::account::AccountInfo;
use miden_node_proto::{BlockProofRequest, generated as proto};
use miden_node_proto::generated as proto;
use miden_node_utils::limiter::MAX_RESPONSE_PAYLOAD_BYTES;
use miden_node_utils::tracing::OpenTelemetrySpanExt;
use miden_protocol::Word;
Expand Down Expand Up @@ -273,7 +273,7 @@ impl Db {

// Insert genesis block data.
let genesis_block = genesis.into_inner();
conn.transaction(move |conn| models::queries::apply_block(conn, &genesis_block, &[], None))
conn.transaction(move |conn| models::queries::apply_block(conn, &genesis_block, &[]))
.context("failed to insert genesis block")?;
Ok(())
}
Expand Down Expand Up @@ -540,10 +540,9 @@ impl Db {
acquire_done: oneshot::Receiver<()>,
signed_block: SignedBlock,
notes: Vec<(NoteRecord, Option<Nullifier>)>,
proving_inputs: Option<BlockProofRequest>,
) -> Result<()> {
self.transact("apply block", move |conn| -> Result<()> {
models::queries::apply_block(conn, &signed_block, &notes, proving_inputs)?;
models::queries::apply_block(conn, &signed_block, &notes)?;

// XXX FIXME TODO free floating mutex MUST NOT exist
// it doesn't bind it properly to the data locked!
Expand All @@ -565,61 +564,6 @@ impl Db {
.await
}

/// Marks a previously committed block as proven and advances the proven-in-sequence tip.
///
/// Atomically clears `proving_inputs` for the given block, then walks forward from the
/// current proven-in-sequence tip through consecutive proven blocks, marking each as
/// proven-in-sequence.
///
/// Returns the new tip of blocks that are proven in-sequence (which may have been unchanged by
/// this function).
#[instrument(target = COMPONENT, skip_all, err)]
pub async fn mark_proven_and_advance_sequence(
&self,
block_num: BlockNumber,
) -> Result<BlockNumber> {
self.transact("mark block proven", move |conn| {
mark_proven_and_advance_sequence(conn, block_num)
})
.await
}

/// Returns the proving inputs for a given block number, if stored.
#[instrument(level = "debug", target = COMPONENT, skip_all, err)]
pub async fn select_block_proving_inputs(
&self,
block_num: BlockNumber,
) -> Result<Option<BlockProofRequest>> {
self.transact("select block proving inputs", move |conn| {
models::queries::select_block_proving_inputs(conn, block_num)
})
.await
}

/// Returns unproven block numbers greater than `after`, in ascending order, up to `limit`.
#[instrument(level = "debug", target = COMPONENT, skip_all, err)]
pub async fn select_unproven_blocks(
&self,
after: BlockNumber,
limit: usize,
) -> Result<Vec<BlockNumber>> {
self.transact("select unproven blocks", move |conn| {
models::queries::select_unproven_blocks(conn, after, limit)
})
.await
}

/// Returns the highest block number that has been proven in sequence.
///
/// This includes the genesis block, which is not technically proven, but treated as such.
#[instrument(level = "debug", target = COMPONENT, skip_all, ret(level = "debug"), err)]
pub async fn proven_chain_tip(&self) -> Result<BlockNumber> {
self.transact("select latest proven block num", |conn| {
models::queries::select_latest_proven_in_sequence_block_num(conn)
})
.await
}

/// Selects storage map values for syncing storage maps for a specific account ID.
///
/// The returned values are the latest known values up to `block_range.end()`, and no values
Expand Down Expand Up @@ -828,55 +772,3 @@ impl Db {
.await
}
}

/// Mark a committed block as proven and advance the proven-in-sequence tip.
///
/// This is intended to atomically (when run in a transaction):
/// 1. Clears `proving_inputs` for the given block (marking it proven).
/// 2. Queries all blocks where `proving_inputs IS NULL AND proven_in_sequence = FALSE`.
/// 3. Walks forward from the current proven-in-sequence tip through consecutive proven blocks and
/// sets `proven_in_sequence = TRUE` for each.
///
/// Returns the new tip of blocks that are proven in-sequence (which may have been unchanged by this
/// function).
///
/// Returns [`DatabaseError::DataCorrupted`] if any proven-but-not-in-sequence block is found at
/// or below the current tip, as that indicates a consistency bug.
pub(crate) fn mark_proven_and_advance_sequence(
conn: &mut SqliteConnection,
block_num: BlockNumber,
) -> Result<BlockNumber, DatabaseError> {
// Clear proving_inputs for the specified block.
models::queries::clear_block_proving_inputs(conn, block_num)?;

// Get the current proven-in-sequence tip (highest in-sequence).
let current_tip = models::queries::select_latest_proven_in_sequence_block_num(conn)?;
let mut new_tip = current_tip;

// Get all blocks that are proven but not yet marked in-sequence.
let unsequenced = models::queries::select_proven_not_in_sequence_blocks(conn)?;

// Walk forward from the tip through consecutive proven blocks.
for candidate in unsequenced {
if candidate <= current_tip {
return Err(DatabaseError::DataCorrupted(format!(
"block {candidate} is proven but not marked in-sequence while the tip is at {current_tip}"
)));
}
if candidate == new_tip.child() {
// Walk the tip forward.
new_tip = candidate;
} else {
// Sequence has been broken. Discontinue walking tip forward.
break;
}
}

// Mark the newly contiguous blocks as proven-in-sequence.
if new_tip > current_tip {
let block_from = current_tip.child();
models::queries::mark_blocks_as_proven_in_sequence(conn, block_from, new_tip)?;
}

Ok(new_tip)
}
Loading
Loading