From 7d693dfa828f52182c361cdd36afb36e60b12be9 Mon Sep 17 00:00:00 2001 From: Mirko von Leipzig <48352201+Mirko-von-Leipzig@users.noreply.github.com> Date: Thu, 3 Sep 2026 14:05:49 +0200 Subject: [PATCH 1/4] feat(genesis): add batch builder account --- crates/store/src/genesis/config/mod.rs | 24 ++++++++++++ crates/store/src/genesis/config/tests.rs | 49 ++++++++++++++++++++---- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/crates/store/src/genesis/config/mod.rs b/crates/store/src/genesis/config/mod.rs index dc6e6c633e..d38bea023d 100644 --- a/crates/store/src/genesis/config/mod.rs +++ b/crates/store/src/genesis/config/mod.rs @@ -57,6 +57,9 @@ pub const FAUCET_OPERATOR_FILE_NAME: &str = "faucet_operator.mac"; /// Name of the account file written for the pass-through account. pub const PASS_THROUGH_ACCOUNT_FILE_NAME: &str = "pass_through.mac"; +/// Name of the account file written for the generated batch builder. +pub const BATCH_BUILDER_FILE_NAME: &str = "batch_builder.mac"; + // GENESIS CONFIG // ================================================================================================ @@ -232,6 +235,13 @@ impl GenesisConfig { build_pass_through_account().map_err(GenesisConfigError::PassThroughAccountBuild)?; secrets.push((PASS_THROUGH_ACCOUNT_FILE_NAME.to_string(), pass_through_account.id(), None)); + let (batch_builder_account, batch_builder_secret) = build_batch_builder()?; + secrets.push(( + BATCH_BUILDER_FILE_NAME.to_string(), + batch_builder_account.id(), + Some(batch_builder_secret), + )); + faucet_accounts.insert(symbol.clone(), native_faucet_account); // Setup additional fungible faucets from parameters @@ -365,6 +375,7 @@ impl GenesisConfig { all_accounts.extend(wallet_accounts); all_accounts.push(pass_through_account); + all_accounts.push(batch_builder_account); // Append file-loaded accounts as-is all_accounts.extend(file_loaded_accounts); @@ -472,6 +483,19 @@ fn build_faucet_operator() -> Result<(Account, RpoSecretKey), GenesisConfigError Ok((operator, secret_key)) } +/// Builds the private wallet that receives the batch builder's fee notes. +fn build_batch_builder() -> Result<(Account, RpoSecretKey), GenesisConfigError> { + let mut rng = ChaCha20Rng::from_seed(rand::random()); + + let secret_key = RpoSecretKey::with_rng(&mut rng); + let auth = Approver::new(secret_key.public_key().into(), AuthScheme::Falcon512Poseidon2); + let init_seed: [u8; 32] = rng.random(); + let mut account = create_basic_wallet(init_seed, auth, AccountType::Private)?; + account.set_nonce(ONE)?; + + Ok((account, secret_key)) +} + // NATIVE FAUCET // ================================================================================================ diff --git a/crates/store/src/genesis/config/tests.rs b/crates/store/src/genesis/config/tests.rs index 696a0491d9..a71627fcf6 100644 --- a/crates/store/src/genesis/config/tests.rs +++ b/crates/store/src/genesis/config/tests.rs @@ -107,10 +107,10 @@ async fn genesis_accounts_have_nonce_one() -> TestResult { let gcfg = GenesisConfig::default(); let (state, secrets) = gcfg.into_state(dev_validator_keys()).unwrap(); - // The default configuration generates the native faucet, its operator, and the pass-through - // account. + // The default configuration generates the native faucet, its operator, the pass-through + // account, and the batch builder. let account_files = secrets.as_account_files(&state).collect::, _>>()?; - assert_eq!(account_files.len(), 3); + assert_eq!(account_files.len(), 4); for AccountFileWithName { account_file, name } in account_files { assert_eq!(account_file.account.nonce(), ONE, "{name} should be deployed at genesis"); } @@ -142,6 +142,33 @@ fn pass_through_account_is_part_of_genesis() -> TestResult { Ok(()) } +#[test] +fn generated_batch_builder_is_a_private_wallet() -> TestResult { + use miden_standards::account::wallets::BasicWallet; + + let (state, secrets) = GenesisConfig::default().into_state(dev_validator_keys())?; + + let (_, account_id, secret) = secrets + .secrets + .iter() + .find(|(name, ..)| name == BATCH_BUILDER_FILE_NAME) + .expect("the batch builder account file should be generated"); + assert!(secret.is_some()); + + let account = state + .accounts + .iter() + .find(|account| account.id() == *account_id) + .expect("the batch builder account should be part of the genesis state"); + assert!(account.id().is_private()); + assert_eq!(account.nonce(), ONE); + assert!(account.vault().is_empty()); + assert!(account.code().has_procedure(BasicWallet::receive_asset_root().as_word())); + assert!(account.code().has_procedure(BasicWallet::create_note_root().as_word())); + + Ok(()) +} + #[test] fn parsing_account_from_file() -> TestResult { use miden_protocol::account::auth::AuthScheme; @@ -345,11 +372,17 @@ verification_base_fee = 0 let (state, secrets) = gcfg.into_state(dev_validator_keys())?; assert!(state.accounts.iter().any(|a| a.id() == faucet_id)); - // The pass-through account has no key. A file-loaded faucet creates no additional secret. - assert_eq!(secrets.secrets.len(), 1); - let (name, _, secret) = &secrets.secrets[0]; - assert_eq!(name, PASS_THROUGH_ACCOUNT_FILE_NAME); - assert!(secret.is_none()); + // A file-loaded faucet creates no new secret. The generated accounts are still present. + assert_eq!(secrets.secrets.len(), 2); + let find = |file_name| { + secrets + .secrets + .iter() + .find(|(name, ..)| name == file_name) + .unwrap_or_else(|| panic!("{file_name} should be generated")) + }; + assert!(find(PASS_THROUGH_ACCOUNT_FILE_NAME).2.is_none()); + assert!(find(BATCH_BUILDER_FILE_NAME).2.is_some()); Ok(()) } From ed3ea3665ede4a2aae5ada94f9caa4a5699c0375 Mon Sep 17 00:00:00 2001 From: Mirko von Leipzig <48352201+Mirko-von-Leipzig@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:56:51 +0200 Subject: [PATCH 2/4] test: keep faucet ownership checks independent of other genesis accounts --- crates/store/src/genesis/config/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/store/src/genesis/config/tests.rs b/crates/store/src/genesis/config/tests.rs index d09f14e1e3..fca95f96ef 100644 --- a/crates/store/src/genesis/config/tests.rs +++ b/crates/store/src/genesis/config/tests.rs @@ -249,7 +249,6 @@ fn generated_native_faucet_is_a_network_account_owned_by_an_operator() -> TestRe .find(|(name, ..)| name == file_name) .unwrap_or_else(|| panic!("{file_name} should be generated")) }; - assert_eq!(secrets.secrets.len(), 3); let (_, faucet_id, faucet_secret) = find(NATIVE_FAUCET_FILE_NAME); let (_, operator_id, operator_secret) = find(FAUCET_OPERATOR_FILE_NAME); assert_eq!(*faucet_id, native_faucet.id()); From b0b22a22dc233600f4537dbd072561133c635d99 Mon Sep 17 00:00:00 2001 From: Mirko von Leipzig <48352201+Mirko-von-Leipzig@users.noreply.github.com> Date: Wed, 16 Sep 2026 10:02:27 +0200 Subject: [PATCH 3/4] feat(genesis): make the batch builder wallet public --- crates/store/src/genesis/config/mod.rs | 4 ++-- crates/store/src/genesis/config/tests.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/store/src/genesis/config/mod.rs b/crates/store/src/genesis/config/mod.rs index 927beb9c67..3962a9a5b3 100644 --- a/crates/store/src/genesis/config/mod.rs +++ b/crates/store/src/genesis/config/mod.rs @@ -486,14 +486,14 @@ fn build_faucet_operator() -> Result<(Account, RpoSecretKey), GenesisConfigError Ok((operator, secret_key)) } -/// Builds the private wallet that receives the batch builder's fee notes. +/// Builds the public wallet that receives the batch builder's fee notes. fn build_batch_builder() -> Result<(Account, RpoSecretKey), GenesisConfigError> { let mut rng = ChaCha20Rng::from_seed(rand::random()); let secret_key = RpoSecretKey::with_rng(&mut rng); let auth = Approver::new(secret_key.public_key().into(), AuthScheme::Falcon512Poseidon2); let init_seed: [u8; 32] = rng.random(); - let mut account = create_basic_wallet(init_seed, auth, AccountType::Private)?; + let mut account = create_basic_wallet(init_seed, auth, AccountType::Public)?; account.set_nonce(ONE)?; Ok((account, secret_key)) diff --git a/crates/store/src/genesis/config/tests.rs b/crates/store/src/genesis/config/tests.rs index fca95f96ef..481e7a87ce 100644 --- a/crates/store/src/genesis/config/tests.rs +++ b/crates/store/src/genesis/config/tests.rs @@ -149,7 +149,7 @@ fn pass_through_account_is_part_of_genesis() -> TestResult { } #[test] -fn generated_batch_builder_is_a_private_wallet() -> TestResult { +fn generated_batch_builder_is_a_public_wallet() -> TestResult { use miden_standards::account::wallets::BasicWallet; let (state, secrets) = GenesisConfig::default().into_state(dev_validator_config())?; @@ -166,7 +166,7 @@ fn generated_batch_builder_is_a_private_wallet() -> TestResult { .iter() .find(|account| account.id() == *account_id) .expect("the batch builder account should be part of the genesis state"); - assert!(account.id().is_private()); + assert!(account.id().is_public()); assert_eq!(account.nonce(), ONE); assert!(account.vault().is_empty()); assert!(account.code().has_procedure(BasicWallet::receive_asset_root().as_word())); From 1d45b24fa8fc68d67e0446e20a15dade7b34e76d Mon Sep 17 00:00:00 2001 From: Mirko von Leipzig <48352201+Mirko-von-Leipzig@users.noreply.github.com> Date: Wed, 16 Sep 2026 10:55:41 +0200 Subject: [PATCH 4/4] refactor(genesis): name the batch builder wallet account --- crates/store/src/genesis/config/mod.rs | 6 +++--- crates/store/src/genesis/config/tests.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/store/src/genesis/config/mod.rs b/crates/store/src/genesis/config/mod.rs index 9c9272e51e..a8eb42c53f 100644 --- a/crates/store/src/genesis/config/mod.rs +++ b/crates/store/src/genesis/config/mod.rs @@ -58,8 +58,8 @@ pub const FAUCET_OPERATOR_FILE_NAME: &str = "faucet_operator.mac"; /// Name of the account file written for the batch builder's collection account. pub const BATCH_BUILDER_COLLECTION_ACCOUNT_FILE_NAME: &str = "batch_builder_collection_account.mac"; -/// Name of the account file written for the generated batch builder. -pub const BATCH_BUILDER_FILE_NAME: &str = "batch_builder.mac"; +/// Name of the account file written for the batch builder's wallet account. +pub const BATCH_BUILDER_WALLET_ACCOUNT_FILE_NAME: &str = "batch_builder_wallet_account.mac"; // GENESIS CONFIG // ================================================================================================ @@ -239,7 +239,7 @@ impl GenesisConfig { let (batch_builder_account, batch_builder_secret) = build_batch_builder()?; secrets.push(( - BATCH_BUILDER_FILE_NAME.to_string(), + BATCH_BUILDER_WALLET_ACCOUNT_FILE_NAME.to_string(), batch_builder_account.id(), Some(batch_builder_secret), )); diff --git a/crates/store/src/genesis/config/tests.rs b/crates/store/src/genesis/config/tests.rs index eacec83ba2..00bd228989 100644 --- a/crates/store/src/genesis/config/tests.rs +++ b/crates/store/src/genesis/config/tests.rs @@ -157,7 +157,7 @@ fn generated_batch_builder_is_a_public_wallet() -> TestResult { let (_, account_id, secret) = secrets .secrets .iter() - .find(|(name, ..)| name == BATCH_BUILDER_FILE_NAME) + .find(|(name, ..)| name == BATCH_BUILDER_WALLET_ACCOUNT_FILE_NAME) .expect("the batch builder account file should be generated"); assert!(secret.is_some()); @@ -385,7 +385,7 @@ verification_base_fee = 0 .unwrap_or_else(|| panic!("{file_name} should be generated")) }; assert!(find(BATCH_BUILDER_COLLECTION_ACCOUNT_FILE_NAME).2.is_some()); - assert!(find(BATCH_BUILDER_FILE_NAME).2.is_some()); + assert!(find(BATCH_BUILDER_WALLET_ACCOUNT_FILE_NAME).2.is_some()); Ok(()) }