-
Notifications
You must be signed in to change notification settings - Fork 129
refactor: enforce tx input encryption on submission #2381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ use std::time::{Duration, Instant}; | |
|
|
||
| use anyhow::{Context, Result}; | ||
| use miden_node_proto::clients::RpcClient; | ||
| use miden_node_proto::domain::encryption::TransactionInputSealer; | ||
| use miden_node_proto::generated::rpc::BlockHeaderByNumberRequest; | ||
| use miden_node_proto::generated::transaction::ProvenTransaction; | ||
| use miden_node_utils::spawn::spawn_blocking_in_current_span; | ||
|
|
@@ -54,6 +55,7 @@ use crate::deploy::{ | |
| MonitorDataStore, | ||
| create_and_deploy_accounts, | ||
| create_genesis_aware_rpc_client, | ||
| create_genesis_aware_rpc_client_with_commitment, | ||
| }; | ||
| use crate::service::Service; | ||
| use crate::status::{ | ||
|
|
@@ -164,6 +166,12 @@ pub struct IncrementService { | |
| /// whenever the increment task regenerates accounts after persistent failures, so the tracker | ||
| /// can switch to the new account IDs without polling disk. | ||
| accounts_sender: watch::Sender<TrackedAccounts>, | ||
| /// Genesis commitment of the monitored network, bound into the associated data of sealed | ||
| /// transaction inputs. | ||
| genesis_commitment: Word, | ||
| /// Cached sealer for transaction inputs, populated on first submission. A plain `Option` | ||
| /// suffices because submissions run through `&mut self`. | ||
| sealer: Option<TransactionInputSealer>, | ||
|
Comment on lines
+169
to
+174
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see why we can have |
||
| } | ||
|
|
||
| impl IncrementService { | ||
|
|
@@ -180,8 +188,11 @@ impl IncrementService { | |
| accounts_sender: watch::Sender<TrackedAccounts>, | ||
| latency_state: Arc<Mutex<LatencyState>>, | ||
| ) -> Result<Self> { | ||
| let mut rpc_client = | ||
| create_genesis_aware_rpc_client(&config.rpc_url, config.request_timeout).await?; | ||
| let (mut rpc_client, genesis_commitment) = create_genesis_aware_rpc_client_with_commitment( | ||
| &config.rpc_url, | ||
| config.request_timeout, | ||
| ) | ||
| .await?; | ||
| let (tx, details) = | ||
| setup_increment_task(wallet_account, secret_key, counter_account, &mut rpc_client) | ||
| .await?; | ||
|
|
@@ -194,9 +205,30 @@ impl IncrementService { | |
| details, | ||
| latency_state, | ||
| accounts_sender, | ||
| genesis_commitment, | ||
| sealer: None, | ||
| }) | ||
| } | ||
|
|
||
| /// Returns the cached sealer, fetching the encryption key on first use. | ||
| async fn sealer(&mut self) -> Result<TransactionInputSealer> { | ||
| if let Some(sealer) = &self.sealer { | ||
| return Ok(sealer.clone()); | ||
| } | ||
|
|
||
| let key = self | ||
| .rpc_client | ||
| .get_transaction_encryption_key(()) | ||
| .await | ||
| .context("Failed to fetch the transaction encryption key")? | ||
| .into_inner(); | ||
| let sealer = TransactionInputSealer::new(key, self.genesis_commitment) | ||
| .context("Unusable transaction encryption key")?; | ||
|
|
||
| self.sealer = Some(sealer.clone()); | ||
| Ok(sealer) | ||
|
Comment on lines
+219
to
+229
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally I would wrap all of this logic into the client itself. |
||
| } | ||
|
|
||
| /// Applies a successful increment: advances the local wallet by the transaction's account | ||
| /// delta, bumps the success count, and returns the value used as the latency-measurement | ||
| /// target. | ||
|
|
@@ -368,15 +400,28 @@ impl IncrementService { | |
| .await | ||
| .context("counter increment task failed")??; | ||
|
|
||
| let sealed = self | ||
| .sealer() | ||
| .await? | ||
| .seal(proven_tx.id(), &tx_inputs) | ||
| .context("Failed to seal the transaction inputs")?; | ||
|
|
||
| let request = ProvenTransaction { | ||
| transaction: proven_tx.to_bytes(), | ||
| transaction_inputs: Some(tx_inputs), | ||
| sealed_transaction_inputs: Some(sealed), | ||
| }; | ||
|
|
||
| let block_height: BlockNumber = self | ||
| .rpc_client | ||
| .submit_proven_tx(request) | ||
| .await | ||
| .inspect_err(|status| { | ||
| // A validator restarted with a different encryption key rejects with | ||
| // `failed_precondition`. Drop the cached key so the next tick re-fetches it. | ||
| if status.code() == tonic::Code::FailedPrecondition { | ||
| self.sealer = None; | ||
| } | ||
| }) | ||
| .context("Failed to submit proven transaction to RPC")? | ||
| .into_inner() | ||
| .block_num | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ use std::time::Duration; | |
| use anyhow::{Context, Result}; | ||
| use backon::{ExponentialBuilder, Retryable}; | ||
| use miden_node_proto::clients::{Builder, RpcClient}; | ||
| use miden_node_proto::domain::encryption::TransactionInputSealer; | ||
| use miden_node_proto::generated::rpc::BlockHeaderByNumberRequest; | ||
| use miden_node_proto::generated::transaction::ProvenTransaction; | ||
| use miden_node_utils::spawn::spawn_blocking_in_current_span; | ||
|
|
@@ -79,6 +80,19 @@ pub async fn create_genesis_aware_rpc_client( | |
| rpc_url: &Url, | ||
| timeout: Duration, | ||
| ) -> Result<RpcClient> { | ||
| create_genesis_aware_rpc_client_with_commitment(rpc_url, timeout) | ||
| .await | ||
| .map(|(client, _)| client) | ||
| } | ||
|
|
||
| /// As [`create_genesis_aware_rpc_client`], but also returns the discovered genesis commitment. | ||
| /// | ||
| /// Submitting call sites need the commitment to seal transaction inputs, and it is already computed | ||
| /// during the handshake. | ||
| pub async fn create_genesis_aware_rpc_client_with_commitment( | ||
| rpc_url: &Url, | ||
| timeout: Duration, | ||
| ) -> Result<(RpcClient, Word)> { | ||
|
Comment on lines
+92
to
+95
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this not just replace the previous function? |
||
| (|| async { | ||
| // First, create a temporary client without genesis metadata to discover the genesis block | ||
| // header and its commitment. | ||
|
|
@@ -126,7 +140,7 @@ pub async fn create_genesis_aware_rpc_client( | |
| .await | ||
| .context("Failed to connect to RPC server with genesis metadata")?; | ||
|
|
||
| Ok(rpc_client) | ||
| Ok((rpc_client, genesis_commitment)) | ||
| }) | ||
| .retry(genesis_discovery_backoff()) | ||
| .notify(|err: &anyhow::Error, sleep: Duration| { | ||
|
|
@@ -240,7 +254,8 @@ pub async fn deploy_counter_account( | |
| prover: &LocalTransactionProver, | ||
| ) -> Result<()> { | ||
| // Deploy counter account to the network using a genesis-aware RPC client. | ||
| let mut rpc_client = create_genesis_aware_rpc_client(rpc_url, Duration::from_secs(10)).await?; | ||
| let (mut rpc_client, genesis_commitment) = | ||
| create_genesis_aware_rpc_client_with_commitment(rpc_url, Duration::from_secs(10)).await?; | ||
|
|
||
| let executed_tx = execute_counter_genesis_tx(counter_account, &mut rpc_client).await?; | ||
|
|
||
|
|
@@ -252,9 +267,19 @@ pub async fn deploy_counter_account( | |
| .context("prover task panicked")? | ||
| .context("Failed to prove transaction")?; | ||
|
|
||
| let key = rpc_client | ||
| .get_transaction_encryption_key(()) | ||
| .await | ||
| .context("Failed to fetch the transaction encryption key")? | ||
| .into_inner(); | ||
|
Comment on lines
+270
to
+274
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we not cache this and only refresh when a submission fails? |
||
| let sealed = TransactionInputSealer::new(key, genesis_commitment) | ||
| .context("Unusable transaction encryption key")? | ||
| .seal(proven_tx.id(), &transaction_inputs) | ||
| .context("Failed to seal the transaction inputs")?; | ||
|
|
||
| let request = ProvenTransaction { | ||
| transaction: proven_tx.to_bytes(), | ||
| transaction_inputs: Some(transaction_inputs), | ||
| sealed_transaction_inputs: Some(sealed), | ||
| }; | ||
|
|
||
| rpc_client | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of the genesis commitment, I believe this could also just return the
TransactionInputsSealer