Skip to content
Open
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
40 changes: 32 additions & 8 deletions bin/benchmark/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use miden_node_proto::clients::{Builder, RpcClient};
use miden_node_proto::domain::encryption::{
TransactionInputsSealer,
TrustedTransactionEncryptionState,
verify_transaction_encryption_key,
verify_transaction_encryption_key_schedule,
};
use miden_node_proto::generated::rpc::BlockHeaderByNumberRequest;
use miden_protocol::Word;
Expand Down Expand Up @@ -213,20 +213,44 @@ pub(crate) async fn create_genesis_aware_rpc_client_pool(
for _ in 0..size {
pool.push(build_rpc_client(rpc_url, timeout, Some(genesis)).await?);
}
let key = pool[0]
let chain_tip = latest_block_num(&pool[0]).await?;
let schedule = pool[0]
.clone()
.get_transaction_encryption_key(())
.await
.context("Failed to fetch the transaction encryption key")?
.context("Failed to fetch the transaction encryption key schedule")?
.into_inner();
let trusted_keys = [trusted_validator_signing_key];
let verified = verify_transaction_encryption_key(
key,
TrustedTransactionEncryptionState::new(genesis, &trusted_keys),
// The chain tip comes from the same node that serves the schedule, so the epoch check detects a
// replayed schedule only as far as that node is honest about its own tip. The attestation is
// still checked against the validator signing key trusted from the chain.
let verified = verify_transaction_encryption_key_schedule(
&schedule,
TrustedTransactionEncryptionState::new(genesis, chain_tip, &trusted_keys),
)
.context("Untrusted transaction encryption key")?;
.context("Untrusted transaction encryption key schedule")?;

Ok((pool, TransactionInputsSealer::new(verified)))
Ok((pool, TransactionInputsSealer::new(verified.into_current_key())))
}

/// Fetches the node's current chain tip.
async fn latest_block_num(rpc: &RpcClient) -> Result<BlockNumber> {
let response = rpc
.clone()
.get_block_header_by_number(BlockHeaderByNumberRequest {
block_num: None,
include_mmr_proof: None,
})
.await
.context("Failed to fetch the chain tip block header")?
.into_inner();
let header: BlockHeader = response
.block_header
.context("Block header response carried no header")?
.try_into()
.context("Failed to convert the chain tip block header")?;

Ok(header.block_num())
}

pub(crate) fn get_genesis_header_request() -> BlockHeaderByNumberRequest {
Expand Down
43 changes: 35 additions & 8 deletions bin/network-monitor/src/deploy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use miden_node_proto::clients::{Builder, RpcClient};
use miden_node_proto::domain::encryption::{
TransactionInputsSealer,
TrustedTransactionEncryptionState,
verify_transaction_encryption_key,
verify_transaction_encryption_key_schedule,
};
use miden_node_proto::generated::rpc::BlockHeaderByNumberRequest;
use miden_node_proto::generated::transaction::ProvenTransaction as ProtoProvenTransaction;
Expand Down Expand Up @@ -98,28 +98,34 @@ impl TransactionSubmissionClient {
self.rpc_client.clone()
}

/// Returns the cached verified sealer, fetching and checking the attested key on first use.
/// Returns the cached verified sealer, fetching and checking the attested schedule on first use.
///
/// The chain tip bounding the schedule's attestation epoch comes from the same node, so it
/// detects a replayed schedule only as far as that node is honest about its own tip. The
/// attestation is still checked against the validator signing keys committed at genesis.
async fn sealer(&self) -> Result<TransactionInputsSealer> {
if let Some(sealer) = self.sealer.lock().await.clone() {
return Ok(sealer);
}

let key = self
let chain_tip = self.latest_block_num().await?;
let schedule = self
.rpc_client
.clone()
.get_transaction_encryption_key(())
.await
.context("Failed to fetch the transaction encryption key")?
.context("Failed to fetch the transaction encryption key schedule")?
.into_inner();
let verified = verify_transaction_encryption_key(
key,
let verified = verify_transaction_encryption_key_schedule(
&schedule,
TrustedTransactionEncryptionState::new(
self.genesis_commitment,
chain_tip,
&self.trusted_validator_signing_keys,
),
)
.context("Untrusted transaction encryption key")?;
let sealer = TransactionInputsSealer::new(verified);
.context("Untrusted transaction encryption key schedule")?;
let sealer = TransactionInputsSealer::new(verified.into_current_key());

let mut cached = self.sealer.lock().await;
if let Some(sealer) = cached.clone() {
Expand All @@ -129,6 +135,27 @@ impl TransactionSubmissionClient {
Ok(sealer)
}

/// Fetches the node's current chain tip.
async fn latest_block_num(&self) -> Result<BlockNumber> {
let response = self
.rpc_client
.clone()
.get_block_header_by_number(BlockHeaderByNumberRequest {
block_num: None,
include_mmr_proof: None,
})
.await
.context("Failed to fetch the chain tip block header")?
.into_inner();
let header: BlockHeader = response
.block_header
.context("Block header response carried no header")?
.try_into()
.context("Failed to convert the chain tip block header")?;

Ok(header.block_num())
}

/// Seals and submits one proven transaction, retrying once with a fresh key when needed.
pub async fn submit(
&self,
Expand Down
42 changes: 35 additions & 7 deletions bin/ntx-builder/src/clients/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use miden_node_proto::domain::account::{
use miden_node_proto::domain::encryption::{
TransactionInputsSealer,
TrustedTransactionEncryptionState,
verify_transaction_encryption_key,
verify_transaction_encryption_key_schedule,
};
use miden_node_proto::errors::ConversionError;
use miden_node_proto::generated::rpc::account_request::account_detail_request::{StorageMapDetailRequest, StorageMapDetailRequests, StorageRequest, storage_map_detail_request};
Expand Down Expand Up @@ -146,26 +146,33 @@ impl RpcClient {
})
}

/// Returns a sealer for transaction inputs, fetching the encryption key if the cache is empty.
/// Returns a sealer for transaction inputs, fetching the key schedule if the cache is empty.
///
/// The chain tip used to bound the schedule's attestation epoch comes from the same node, so it
/// detects a schedule replayed from an earlier epoch only as far as that node is honest about
/// its own tip. The attestation itself is still checked against the validator signing keys
/// committed at genesis, which is what makes the served key trustworthy at all.
pub(crate) async fn sealer(&self) -> Result<TransactionInputsSealer, Status> {
if let Some(sealer) = self.sealer.read().await.clone() {
return Ok(sealer);
}

let key = self.inner.clone().get_transaction_encryption_key(()).await?.into_inner();
let verified = verify_transaction_encryption_key(
key,
let chain_tip = self.latest_block_num().await?;
let schedule = self.inner.clone().get_transaction_encryption_key(()).await?.into_inner();
let verified = verify_transaction_encryption_key_schedule(
&schedule,
TrustedTransactionEncryptionState::new(
self.genesis_commitment,
chain_tip,
&self.trusted_validator_signing_keys,
),
)
.map_err(|err| {
Status::failed_precondition(
err.as_report_context("Untrusted transaction encryption key"),
err.as_report_context("Untrusted transaction encryption key schedule"),
)
})?;
let sealer = TransactionInputsSealer::new(verified);
let sealer = TransactionInputsSealer::new(verified.into_current_key());

let mut cached = self.sealer.write().await;
if let Some(sealer) = cached.clone() {
Expand All @@ -175,6 +182,27 @@ impl RpcClient {
Ok(sealer)
}

/// Fetches the node's current chain tip.
async fn latest_block_num(&self) -> Result<BlockNumber, Status> {
let response = self
.inner
.clone()
.get_block_header_by_number(proto::rpc::BlockHeaderByNumberRequest {
block_num: None,
include_mmr_proof: None,
})
.await?
.into_inner();
let header = response
.block_header
.ok_or_else(|| Status::internal("Block header response carried no header"))?;
let header: miden_protocol::block::BlockHeader = header
.try_into()
.map_err(|err: ConversionError| Status::internal(err.as_report()))?;

Ok(header.block_num())
}

/// Opens a committed-block subscription starting at `block_from`, retrying indefinitely with
/// the client's configured exponential backoff while the initial connection attempt fails.
///
Expand Down
Loading
Loading