Skip to content

Commit

Permalink
refactor: update genesis generation, read faucet account from `faucet…
Browse files Browse the repository at this point in the history
….mac`, request faucet state on initialization
  • Loading branch information
polydez committed Oct 25, 2024
1 parent 011002a commit 349c43b
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 264 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy_package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ jobs:
sudo chown -R miden /opt/miden; \
sudo /usr/bin/miden-node init -c /etc/miden/miden-node.toml -g /opt/miden/miden-node/genesis.toml; \
sudo /usr/bin/miden-node make-genesis -i /opt/miden/miden-node/genesis.toml -o /opt/miden/miden-node/genesis.dat --force; \
sudo /usr/bin/miden-faucet init -c /opt/miden/miden-faucet/miden-faucet.toml
sudo /usr/bin/miden-faucet init -c /opt/miden/miden-faucet/miden-faucet.toml -f /opt/miden/miden-node/accounts/faucet.mac
- name: Start miden node service
uses: ./.github/actions/ssm_execute
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

173 changes: 59 additions & 114 deletions bin/faucet/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,39 @@
use std::{
path::Path,
rc::Rc,
sync::{Arc, RwLock},
time::Duration,
};
use std::{rc::Rc, time::Duration};

use anyhow::{anyhow, Context};
use miden_lib::{
accounts::faucets::create_basic_fungible_faucet, notes::create_p2id_note,
transaction::TransactionKernel, AuthScheme,
};
use anyhow::Context;
use miden_lib::{notes::create_p2id_note, transaction::TransactionKernel};
use miden_node_proto::generated::{
requests::{
GetAccountDetailsRequest, GetBlockHeaderByNumberRequest, SubmitProvenTransactionRequest,
},
rpc::api_client::ApiClient,
};
use miden_objects::{
accounts::{Account, AccountId, AccountStorageMode, AuthSecretKey},
assets::{FungibleAsset, TokenSymbol},
accounts::{Account, AccountData, AccountId, AuthSecretKey},
assets::FungibleAsset,
crypto::{
dsa::rpo_falcon512::SecretKey,
merkle::{MmrPeaks, PartialMmr},
rand::RpoRandomCoin,
},
notes::{Note, NoteType},
transaction::{ChainMmr, ExecutedTransaction, TransactionArgs, TransactionScript},
utils::Deserializable,
vm::AdviceMap,
BlockHeader, Felt, Word,
BlockHeader, Felt,
};
use miden_tx::{
auth::BasicAuthenticator, utils::Serializable, LocalTransactionProver, ProvingOptions,
TransactionExecutor, TransactionProver,
};
use rand::{rngs::StdRng, thread_rng, Rng};
use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng};
use rand::{random, rngs::StdRng};
use tonic::transport::Channel;
use tracing::info;

use crate::{
config::FaucetConfig,
errors::{ClientError, ImplError},
store::FaucetDataStore,
COMPONENT,
};

pub const DISTRIBUTE_FUNGIBLE_ASSET_SCRIPT: &str =
Expand All @@ -65,25 +57,36 @@ unsafe impl Send for FaucetClient {}
impl FaucetClient {
/// Creates a new faucet client.
pub async fn new(config: &FaucetConfig) -> Result<Self, ClientError> {
let (rpc_api, root_block_header, root_chain_mmr) = initialize_faucet_client(config).await?;
let init_seed: [u8; 32] = [0; 32];
let (auth_scheme, authenticator) = init_authenticator(init_seed, &config.secret_key_path)
.context("Failed to initialize authentication scheme")?;

let (faucet_account, account_seed) = build_account(config, init_seed, auth_scheme)?;
let id = faucet_account.id();

let data_store = FaucetDataStore::new(
Arc::new(RwLock::new(faucet_account)),
account_seed,
root_block_header,
root_chain_mmr,
let (mut rpc_api, root_block_header, root_chain_mmr) =
initialize_faucet_client(config).await?;

let faucet_account_data = AccountData::read(&config.faucet_account_path)
.context("Failed to load faucet account from file")?;

let id = faucet_account_data.account.id();

let public_key = match &faucet_account_data.auth_secret_key {
AuthSecretKey::RpoFalcon512(secret) => secret.public_key(),
};

let authenticator = BasicAuthenticator::<StdRng>::new(&[(
public_key.into(),
faucet_account_data.auth_secret_key,
)]);

info!(target: COMPONENT, "Requesting account state from the node...");
let faucet_account = request_account_state(&mut rpc_api, id).await?;
info!(
target: COMPONENT,
got_new_hash = %faucet_account.hash(),
"Received faucet account state from the node",
);

let data_store = FaucetDataStore::new(faucet_account, root_block_header, root_chain_mmr);

let executor = TransactionExecutor::new(data_store.clone(), Some(Rc::new(authenticator)));

let mut rng = thread_rng();
let coin_seed: [u64; 4] = rng.gen();
let coin_seed: [u64; 4] = random();
let rng = RpoRandomCoin::new(coin_seed.map(Felt::new));

Ok(Self { data_store, rpc_api, executor, id, rng })
Expand Down Expand Up @@ -155,29 +158,6 @@ impl FaucetClient {
Ok(response.into_inner().block_height)
}

/// Requests faucet account state from the node.
///
/// The account is expected to be public, otherwise, the error is returned.
pub async fn request_account_state(&mut self) -> Result<(Account, u32), ClientError> {
let account_info = self
.rpc_api
.get_account_details(GetAccountDetailsRequest { account_id: Some(self.id.into()) })
.await
.context("Failed to get faucet account state")?
.into_inner()
.details
.context("Account info field is empty")?;

let faucet_account_state_bytes =
account_info.details.context("Account details field is empty")?;
let faucet_account = Account::read_from_bytes(&faucet_account_state_bytes)
.map_err(ImplError)
.context("Failed to deserialize faucet account")?;
let block_num = account_info.summary.context("Account summary field is empty")?.block_num;

Ok((faucet_account, block_num))
}

/// Returns a reference to the data store.
pub fn data_store(&self) -> &FaucetDataStore {
&self.data_store
Expand All @@ -192,65 +172,6 @@ impl FaucetClient {
// HELPER FUNCTIONS
// ================================================================================================

/// Initializes the keypair used to sign transactions.
///
/// If the secret key file exists, it is read from the file. Otherwise, a new key is generated and
/// written to the file.
fn init_authenticator(
init_seed: [u8; 32],
secret_key_path: impl AsRef<Path>,
) -> Result<(AuthScheme, BasicAuthenticator<StdRng>), ClientError> {
// Load secret key from file or generate new one
let secret = if secret_key_path.as_ref().exists() {
SecretKey::read_from_bytes(
&std::fs::read(secret_key_path).context("Failed to read secret key from file")?,
)
.map_err(ImplError)
.context("Failed to deserialize secret key")?
} else {
let mut rng = ChaCha20Rng::from_seed(init_seed);
let secret = SecretKey::with_rng(&mut rng);
std::fs::write(secret_key_path, secret.to_bytes())
.context("Failed to write secret key to file")?;

secret
};

let auth_scheme = AuthScheme::RpoFalcon512 { pub_key: secret.public_key() };

let authenticator = BasicAuthenticator::<StdRng>::new(&[(
secret.public_key().into(),
AuthSecretKey::RpoFalcon512(secret),
)]);

Ok((auth_scheme, authenticator))
}

/// Builds a new faucet account with the provided configuration.
///
/// Returns the created account, its seed, and the secret key used to sign transactions.
fn build_account(
config: &FaucetConfig,
init_seed: [u8; 32],
auth_scheme: AuthScheme,
) -> Result<(Account, Word), ClientError> {
let token_symbol = TokenSymbol::new(config.token_symbol.as_str())
.context("Failed to parse token symbol from configuration file")?;

let (faucet_account, account_seed) = create_basic_fungible_faucet(
init_seed,
token_symbol,
config.decimals,
Felt::try_from(config.max_supply)
.map_err(|err| anyhow!("Error converting max supply to Felt: {err}"))?,
AccountStorageMode::Public,
auth_scheme,
)
.context("Failed to create basic fungible faucet account")?;

Ok((faucet_account, account_seed))
}

/// Initializes the faucet client by connecting to the node and fetching the root block header.
pub async fn initialize_faucet_client(
config: &FaucetConfig,
Expand Down Expand Up @@ -288,6 +209,30 @@ pub async fn initialize_faucet_client(
Ok((rpc_api, root_block_header, root_chain_mmr))
}

/// Requests account state from the node.
///
/// The account is expected to be public, otherwise, the error is returned.
async fn request_account_state(
rpc_api: &mut ApiClient<Channel>,
account_id: AccountId,
) -> Result<Account, ClientError> {
let account_info = rpc_api
.get_account_details(GetAccountDetailsRequest { account_id: Some(account_id.into()) })
.await
.context("Failed to get faucet account state")?
.into_inner()
.details
.context("Account info field is empty")?;

let faucet_account_state_bytes =
account_info.details.context("Account details field is empty")?;

Account::read_from_bytes(&faucet_account_state_bytes)
.map_err(ImplError)
.context("Failed to deserialize faucet account")
.map_err(Into::into)
}

/// Builds transaction arguments for the mint transaction.
fn build_transaction_arguments(
output_note: &Note,
Expand Down
23 changes: 7 additions & 16 deletions bin/faucet/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use serde::{Deserialize, Serialize};
// Faucet config
// ================================================================================================

/// Default path to the secret key file
const DEFAULT_SECRET_KEY_PATH: &str = "faucet-secret.key";
/// Default path to the faucet account file
pub const DEFAULT_FAUCET_ACCOUNT_PATH: &str = "accounts/faucet.mac";

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
Expand All @@ -23,21 +23,15 @@ pub struct FaucetConfig {
pub timeout_ms: u64,
/// Possible options on the amount of asset that should be dispersed on each faucet request
pub asset_amount_options: Vec<u64>,
/// Token symbol of the generated fungible asset
pub token_symbol: String,
/// Number of decimals of the generated fungible asset
pub decimals: u8,
/// Maximum supply of the generated fungible asset
pub max_supply: u64,
/// Path to the key store file
pub secret_key_path: PathBuf,
/// Path to the faucet account file
pub faucet_account_path: PathBuf,
}

impl Display for FaucetConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{{ endpoint: \"{}\", asset_amount_options: {:?}, token_symbol: {}, decimals: {}, max_supply: {} }}",
self.endpoint, self.asset_amount_options, self.token_symbol, self.decimals, self.max_supply
"{{ endpoint: \"{}\", node_url: \"{}\", timeout_ms: \"{}\", asset_amount_options: {:?}, faucet_account_path: \"{}\" }}",
self.endpoint, self.node_url, self.timeout_ms, self.asset_amount_options, self.faucet_account_path.display()
))
}
}
Expand All @@ -49,10 +43,7 @@ impl Default for FaucetConfig {
node_url: Endpoint::localhost(DEFAULT_NODE_RPC_PORT).to_string(),
timeout_ms: 10000,
asset_amount_options: vec![100, 500, 1000],
token_symbol: "POL".to_string(),
decimals: 8,
max_supply: 1000000,
secret_key_path: DEFAULT_SECRET_KEY_PATH.into(),
faucet_account_path: DEFAULT_FAUCET_ACCOUNT_PATH.into(),
}
}
}
82 changes: 20 additions & 62 deletions bin/faucet/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use miden_objects::{
};
use serde::{Deserialize, Serialize};
use tonic::body;
use tracing::{error, info};
use tracing::info;

use crate::{errors::HandlerError, state::FaucetState, COMPONENT};

Expand Down Expand Up @@ -65,67 +65,25 @@ pub async fn get_tokens(
let target_account_id = AccountId::from_hex(req.account_id.as_str())
.map_err(|err| HandlerError::BadRequest(err.to_string()))?;

let (created_note, block_height) = loop {
let mut faucet_account = client.data_store().faucet_account();

// Execute transaction
info!(target: COMPONENT, "Executing mint transaction for account.");
let (executed_tx, created_note) = client.execute_mint_transaction(
target_account_id,
req.is_private_note,
req.asset_amount,
)?;

let prev_hash = faucet_account.hash();

faucet_account
.apply_delta(executed_tx.account_delta())
.context("Failed to apply faucet account delta")?;

let new_hash = faucet_account.hash();

// Run transaction prover & send transaction to node
info!(target: COMPONENT, "Proving and submitting transaction.");
match client.prove_and_submit_transaction(executed_tx.clone()).await {
Ok(block_height) => {
break (created_note, block_height);
},
Err(err) => {
error!(
target: COMPONENT,
%err,
"Failed to prove and submit transaction",
);

// TODO: Improve error statuses returned from the `SubmitProvenTransaction` endpoint
// of block producer. Check received error status here.

info!(target: COMPONENT, "Requesting account state from the node...");
let (got_faucet_account, block_num) = client.request_account_state().await?;
let got_new_hash = got_faucet_account.hash();
info!(
target: COMPONENT,
%prev_hash,
%new_hash,
%got_new_hash,
"Received new account state from the node",
);
// If the hash hasn't changed, then the account's state we had is correct,
// and we should not try to execute the transaction again. We can just return error
// to the caller.
if new_hash == prev_hash {
return Err(err.into());
}
// If the new hash from the node is the same, as we expected to have, then
// transaction was successfully executed despite the error. Don't need to retry.
if new_hash == got_new_hash {
break (created_note, block_num);
}

client.data_store().update_faucet_state(got_faucet_account).await?;
},
}
};
// Execute transaction
info!(target: COMPONENT, "Executing mint transaction for account.");
let (executed_tx, created_note) = client.execute_mint_transaction(
target_account_id,
req.is_private_note,
req.asset_amount,
)?;

let mut faucet_account = client.data_store().faucet_account();
faucet_account
.apply_delta(executed_tx.account_delta())
.context("Failed to apply faucet account delta")?;

// Run transaction prover & send transaction to node
info!(target: COMPONENT, "Proving and submitting transaction.");
let block_height = client.prove_and_submit_transaction(executed_tx).await?;

// Update data store with the new faucet state
client.data_store().update_faucet_state(faucet_account).await?;

let note_id: NoteId = created_note.id();
let note_details =
Expand Down
Loading

0 comments on commit 349c43b

Please sign in to comment.