Skip to content
24 changes: 24 additions & 0 deletions crates/store/src/genesis/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,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
// ================================================================================================

Expand Down Expand Up @@ -234,6 +237,13 @@ impl GenesisConfig {
Some(pass_through_secret),
));

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
Expand Down Expand Up @@ -368,6 +378,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);
Expand Down Expand Up @@ -475,6 +486,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
// ================================================================================================

Expand Down
50 changes: 41 additions & 9 deletions crates/store/src/genesis/config/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ async fn genesis_accounts_have_nonce_one() -> TestResult {
let gcfg = GenesisConfig::default();
let (state, secrets) = gcfg.into_state(dev_validator_config()).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::<Result<Vec<_>, _>>()?;
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");
}
Expand Down Expand Up @@ -148,6 +148,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_config())?;

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;
Expand Down Expand Up @@ -222,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());
Expand Down Expand Up @@ -349,11 +375,17 @@ verification_base_fee = 0
let (state, secrets) = gcfg.into_state(dev_validator_config())?;
assert!(state.accounts.iter().any(|a| a.id() == faucet_id));

// 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_some());
// 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_some());
assert!(find(BATCH_BUILDER_FILE_NAME).2.is_some());

Ok(())
}
Expand Down
Loading