Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions bin/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ miden-node-store = { workspace = true }
miden-node-tracing = { workspace = true }
miden-node-utils = { workspace = true }
miden-protocol = { workspace = true }
miden-standards = { workspace = true }
rand = { workspace = true }
serde = { features = ["derive"], workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
Expand Down
64 changes: 64 additions & 0 deletions bin/node/src/commands/lifecycle.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::io::Write;
use std::path::{Path, PathBuf};

use anyhow::Context;
Expand All @@ -8,9 +9,48 @@ use miden_node_store::{DataDirectory, Db, State};
use miden_node_tracing::info;
use miden_node_utils::fs::ensure_empty_directory;
use miden_node_utils::genesis::{OfficialNetwork, fetch_genesis_block, read_genesis_block};
use miden_protocol::account::auth::AuthSecretKey;
use miden_protocol::account::{AccountBuilder, AccountFile, AccountType};
use miden_protocol::utils::serde::Serializable;
use miden_standards::account::auth::AuthTxFeeCollector;
use miden_standards::account::wallets::BasicWallet;

use super::ENV_DATA_DIRECTORY;

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn bootstrap_saves_a_new_collector_and_preserves_its_signing_key() -> anyhow::Result<()> {
let directory = tempfile::tempdir()?;
let directory = DataDirectory::load(directory.path().to_path_buf())?;
create_collection_account(&directory)?;
let path = directory.batch_builder_collection_account_path();
let account_file = AccountFile::read(&path)?;
assert!(account_file.account.is_new());
assert!(account_file.account.is_public());
assert!(account_file.account.vault().is_empty());
assert_eq!(account_file.auth_secret_keys.len(), 1);
assert_eq!(
account_file.account.storage().get_item(AuthTxFeeCollector::public_key_slot())?,
miden_protocol::Word::from(
account_file.auth_secret_keys[0].public_key().to_commitment()
),
);

let contents = fs_err::read(&path)?;
assert!(create_collection_account(&directory).is_err());
assert_eq!(fs_err::read(&path)?, contents);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
assert_eq!(fs_err::metadata(&path)?.permissions().mode() & 0o777, 0o600);
}
Ok(())
}
}

// BOOTSTRAP
// ================================================================================================

Expand Down Expand Up @@ -58,6 +98,8 @@ impl BootstrapCommand {
read_bootstrap_genesis_block(self.genesis_block_file.as_deref(), self.network).await?;
let genesis_commitment = genesis_block.inner().header().commitment();
State::bootstrap(genesis_block, &self.data_directory)?;
create_collection_account(&DataDirectory::load(self.data_directory.clone())?)
.context("failed to create the batch builder collection account")?;
info!(
target: crate::LOG_TARGET,
"Node bootstrap complete",
Expand All @@ -68,6 +110,28 @@ impl BootstrapCommand {
}
}

/// Saves the collector and its signing key without registering the account on-chain.
fn create_collection_account(directory: &DataDirectory) -> anyhow::Result<()> {
let secret_key = AuthSecretKey::new_falcon512_poseidon2();
let account = AccountBuilder::new(rand::random())
.account_type(AccountType::Public)
.with_component(AuthTxFeeCollector::from_public_key(secret_key.public_key()))
.with_component(BasicWallet)
.build()?;
let account_file = AccountFile::new(account, vec![secret_key]);
let mut options = fs_err::OpenOptions::new();
options.create_new(true).write(true);
#[cfg(unix)]
{
use fs_err::os::unix::fs::OpenOptionsExt;
options.mode(0o600);
}
let mut file = options.open(directory.batch_builder_collection_account_path())?;
file.write_all(&account_file.to_bytes())?;
file.sync_all()?;
Ok(())
}

/// Reads the genesis block from the configured source and validates it.
async fn read_bootstrap_genesis_block(
genesis_block_file: Option<&Path>,
Expand Down
4 changes: 4 additions & 0 deletions crates/store/src/data_directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ impl DataDirectory {
self.0.join("miden-allowlist.sqlite3")
}

pub fn batch_builder_collection_account_path(&self) -> PathBuf {
self.0.join("batch_builder_collection_account.mac")
}

pub fn display(&self) -> std::path::Display<'_> {
self.0.display()
}
Expand Down
3 changes: 3 additions & 0 deletions docs/external/src/network-operator/bootstrap-and-genesis.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ which provides an easy method to obtain this data. This is directly supported by
`--network testnet` or `--network devnet`. Bootstrap commands also support passing a file directly to cover custom
networks, or if the official URLs are not trusted.

Node bootstrap also creates `batch_builder_collection_account.mac` in the node data directory. Keep this file private
because it contains the collection account's signing key.

## Bootstrap Flow

<Tabs groupId="network-operator-genesis-source" defaultValue="official">
Expand Down
Loading