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
3 changes: 3 additions & 0 deletions bin/validator/src/commands/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::num::NonZeroUsize;
use std::path::{Path, PathBuf};

use anyhow::Context;
use miden_node_store::BlockStore;
use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig};
use miden_node_utils::fs::ensure_empty_directory;
use miden_protocol::utils::serde::Serializable;
Expand Down Expand Up @@ -84,6 +85,8 @@ async fn build_and_write_genesis(
let genesis_block_path = genesis_block_directory.join(GENESIS_BLOCK_FILENAME);
fs_err::write(&genesis_block_path, block_bytes).context("failed to write genesis block")?;

let _ = BlockStore::bootstrap(data_directory.to_path_buf().join("blocks"), &genesis_block)?;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@Mirko-von-Leipzig I was wondering if we should use the store crate's DataDirectory struct for this but there is a discrepancy between the db filepaths atm (validator.sqlite3 vs miden-store.sqlite3).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I have been wondering if we can't clean things up somehow.. At the moment its not quite an easy split, so maybe what you've got here is fine and we shouldn't force coupling between things that aren't literally the same thing.

We could mimic the struct here with the differences? Or create the struct with a configurable service name e.g. node and validator which would be used to infer the sqlite3 filestem?

But I think lets keep them separate 🤷 They want to store different things over time so maybe its best to keep them decoupled.


let (genesis_header, ..) = genesis_block.into_inner().into_parts();
let db = miden_validator::db::setup_with_pool_size(
data_directory.join("validator.sqlite3"),
Expand Down
6 changes: 6 additions & 0 deletions bin/validator/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::PathBuf;
use anyhow::Context;
use miden_node_proto::generated::validator::api_server;
use miden_node_proto_build::validator_api_descriptor;
use miden_node_store::BlockStore;
use miden_node_utils::clap::GrpcOptionsInternal;
use miden_node_utils::panic::catch_panic_layer_fn;
use miden_node_utils::tracing::grpc::grpc_trace_fn;
Expand Down Expand Up @@ -65,6 +66,10 @@ impl ValidatorServer {
.await
.context("failed to initialize validator database")?;

// Initialize block store.
let block_store = BlockStore::load(self.data_directory.join("blocks").clone())
.context("failed to load block store")?;

// Load initial metrics from the database for the in-memory counters.
let (initial_chain_tip, initial_tx_count, initial_block_count) = db
.query("load_initial_metrics", |conn| {
Expand Down Expand Up @@ -94,6 +99,7 @@ impl ValidatorServer {
ValidatorService::new(
self.signer,
db,
block_store,
initial_chain_tip,
initial_tx_count,
initial_block_count,
Expand Down
22 changes: 19 additions & 3 deletions bin/validator/src/server/validator_service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ use std::sync::Arc;
use std::sync::atomic::{AtomicU32, AtomicU64};

use miden_node_db::{DatabaseError, Db};
use miden_node_store::BlockStore;
use miden_node_utils::tracing::OpenTelemetrySpanExt;
use miden_protocol::block::{BlockHeader, BlockNumber, ProposedBlock};
use miden_protocol::block::{BlockHeader, BlockNumber, ProposedBlock, SignedBlock};
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature};
use miden_protocol::crypto::utils::Serializable;
use miden_protocol::errors::ProposedBlockError;
use miden_protocol::transaction::{TransactionHeader, TransactionId};
use tokio::sync::Semaphore;
Expand Down Expand Up @@ -48,6 +50,8 @@ pub enum ValidatorError {
ValidatorKeyMismatch { expected: PublicKey, actual: PublicKey },
#[error("no chain tip exists")]
NoChainTip,
#[error("failed to backup block")]
BlockBackupFailed(#[source] std::io::Error),
}

// VALIDATOR SERVICE
Expand All @@ -59,6 +63,7 @@ pub enum ValidatorError {
pub(crate) struct ValidatorService {
signer: ValidatorSigner,
db: Arc<Db>,
block_store: BlockStore,
/// Serializes `sign_block` requests so that concurrent calls are processed sequentially,
/// ensuring consistent chain tip reads and preventing race conditions.
sign_block_semaphore: Semaphore,
Expand All @@ -74,6 +79,7 @@ impl ValidatorService {
pub(crate) async fn new(
signer: ValidatorSigner,
db: Db,
block_store: BlockStore,
initial_chain_tip: u32,
initial_tx_count: u64,
initial_block_count: u64,
Expand All @@ -97,6 +103,7 @@ impl ValidatorService {
Ok(Self {
signer,
db: db.into(),
block_store,
sign_block_semaphore: Semaphore::new(1),
chain_tip: AtomicU32::new(initial_chain_tip),
validated_transactions_count: AtomicU64::new(initial_tx_count),
Expand Down Expand Up @@ -135,7 +142,7 @@ impl ValidatorService {
}

// Build the block header.
let (proposed_header, _) = proposed_block
let (proposed_header, proposed_body) = proposed_block
.into_header_and_body()
.map_err(ValidatorError::BlockBuildingFailed)?;

Expand Down Expand Up @@ -186,7 +193,16 @@ impl ValidatorService {
}

let signature = self.sign_header(&proposed_header).await?;
Ok((signature, proposed_header))

// Back up the signed block to disk.
let signed_block = SignedBlock::new_unchecked(proposed_header, proposed_body, signature);
self.block_store
.save_block(signed_block.header().block_num(), &signed_block.to_bytes())
.await
.map_err(ValidatorError::BlockBackupFailed)?;

let (header, _, signature) = signed_block.into_parts();
Ok((signature, header))
}

/// Signs a block header using the validator's signer.
Expand Down
16 changes: 9 additions & 7 deletions bin/validator/src/server/validator_service/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::BTreeMap;

use miden_node_proto::generated::validator::api_server;
use miden_node_proto::generated::{self as proto};
use miden_node_store::GenesisState;
use miden_node_store::{BlockStore, GenesisState};
use miden_node_utils::fee::test_fee_params;
use miden_protocol::Word;
use miden_protocol::block::{BlockHeader, BlockInputs, ProposedBlock};
Expand Down Expand Up @@ -32,10 +32,10 @@ impl TestValidator {
async fn new() -> Self {
let key = random_secret_key();
let signer = ValidatorSigner::new_local(key.clone());
let (db, genesis_header) = setup_db_with_genesis(&key).await;
let (db, block_store, genesis_header) = setup_db_with_genesis(&key).await;

Self {
server: ValidatorService::new(signer, db, 0, 0, 0).await.unwrap(),
server: ValidatorService::new(signer, db, block_store, 0, 0, 0).await.unwrap(),
chain: PartialBlockchain::default(),
chain_tip: genesis_header,
}
Expand Down Expand Up @@ -82,13 +82,15 @@ impl TestValidator {

/// Creates a validator database seeded with a genesis block whose `validator_key` is the public key
/// of `key`. Returns the database handle and the genesis block header.
async fn setup_db_with_genesis(key: &SigningKey) -> (miden_node_db::Db, BlockHeader) {
async fn setup_db_with_genesis(key: &SigningKey) -> (miden_node_db::Db, BlockStore, BlockHeader) {
let genesis_state = GenesisState::new(vec![], test_fee_params(), 1, 0, key.public_key());
let genesis_block = genesis_state.into_block(key).unwrap();
let genesis_header = genesis_block.inner().header().clone();

let dir = tempfile::tempdir().unwrap();
let db = setup(dir.path().join("validator.sqlite3")).await.unwrap();
let block_store =
BlockStore::bootstrap(dir.path().join("blocks").clone(), &genesis_block).unwrap();

db.transact("upsert_genesis", {
let h = genesis_header.clone();
Expand All @@ -97,7 +99,7 @@ async fn setup_db_with_genesis(key: &SigningKey) -> (miden_node_db::Db, BlockHea
.await
.unwrap();

(db, genesis_header)
(db, block_store, genesis_header)
}

/// Builds an empty [`ProposedBlock`] that extends the given parent block header using the provided
Expand All @@ -123,7 +125,7 @@ fn empty_block(parent_header: &BlockHeader, chain: &PartialBlockchain) -> Propos
async fn signing_key_mismatch_rejected() {
// Seed a database whose genesis designates `genesis_key` as the validator key.
let genesis_key = random_secret_key();
let (db, genesis_header) = setup_db_with_genesis(&genesis_key).await;
let (db, block_store, genesis_header) = setup_db_with_genesis(&genesis_key).await;

// Start a validator with a different key, modelling a validator configured with the wrong key.
let rogue_signer = ValidatorSigner::new_local(random_secret_key());
Expand All @@ -133,7 +135,7 @@ async fn signing_key_mismatch_rejected() {
"test requires a signing key that differs from the genesis validator key",
);

let result = ValidatorService::new(rogue_signer, db, 0, 0, 0).await;
let result = ValidatorService::new(rogue_signer, db, block_store, 0, 0, 0).await;
assert!(
matches!(result, Err(ValidatorError::ValidatorKeyMismatch { .. })),
"expected ValidatorKeyMismatch error",
Expand Down
1 change: 1 addition & 0 deletions crates/store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod state;
#[cfg(feature = "rocksdb")]
pub use accounts::PersistentAccountTree;
pub use accounts::{AccountTreeWithHistory, HistoricalError, InMemoryAccountTree};
pub use blocks::BlockStore;
pub use data_directory::DataDirectory;
pub use db::models::conv::SqlTypeConvert;
pub use db::models::queries::StorageMapValuesPage;
Expand Down
Loading