Skip to main content

miden_node_proto/domain/
submission.rs

1use miden_protobuf::{BuildUnchecked, ConversionResultExt, Verify, VerifyWith};
2use miden_protocol::MIN_PROOF_SECURITY_LEVEL;
3use miden_protocol::batch::{ProposedBatch, ProvenBatch};
4use miden_protocol::transaction::ProvenTransaction;
5
6use crate::errors::ConversionError;
7use crate::generated as proto;
8
9/// A decoded submission. Construction does not verify the transaction proof or chain state.
10#[derive(Debug)]
11pub struct ProvenTransactionSubmission {
12    pub transaction: ProvenTransaction,
13    pub sealed_transaction_inputs: proto::submission::SealedTransactionInputs,
14}
15
16impl BuildUnchecked for proto::submission::DecodedProvenTransactionSubmission {
17    type Output = ProvenTransactionSubmission;
18    type Error = ConversionError;
19
20    /// Build the submission without verifying the proof, chain state, or sealed inputs. Receiving
21    /// services must verify the proof and reference block, check spent nullifiers and expiration,
22    /// and resolve input-note and account dependencies. Validators must decrypt and re-execute the
23    /// sealed inputs.
24    fn build_unchecked(self) -> Result<Self::Output, Self::Error> {
25        // SAFETY: This conversion checks structure only. The output remains unverified. Receiving
26        // services are responsible for the proof, chain-state, and sealed-input checks.
27        let transaction = self.transaction.build_unchecked().context("transaction")?;
28        let sealed_transaction_inputs = self.sealed_transaction_inputs.into();
29        Ok(ProvenTransactionSubmission { transaction, sealed_transaction_inputs })
30    }
31}
32
33impl From<proto::submission::DecodedSealedTransactionInputs>
34    for proto::submission::SealedTransactionInputs
35{
36    fn from(value: proto::submission::DecodedSealedTransactionInputs) -> Self {
37        Self {
38            key_id: value.key_id,
39            ciphertext: value.ciphertext,
40        }
41    }
42}
43
44impl From<&ProvenTransactionSubmission> for proto::submission::ProvenTransactionSubmission {
45    fn from(value: &ProvenTransactionSubmission) -> Self {
46        Self {
47            transaction: Some((&value.transaction).into()),
48            sealed_transaction_inputs: Some(value.sealed_transaction_inputs.clone()),
49        }
50    }
51}
52
53#[derive(Debug)]
54pub struct TransactionBatchSubmission {
55    pub batch: ProvenBatch,
56    pub proposed_batch: ProposedBatch,
57    pub sealed_transaction_inputs: Vec<proto::submission::SealedTransactionInputs>,
58}
59
60impl Verify for proto::submission::DecodedTransactionBatch {
61    type Verified = TransactionBatchSubmission;
62    type Error = ConversionError;
63
64    /// Verify transaction proofs at the minimum security level, proposal agreement, and the sealed
65    /// input count. The caller must verify the batch execution proof and authenticate the reference
66    /// chain. Receiving services must check nullifiers, expiration, and dependencies against their
67    /// state. Validators must decrypt and re-execute the sealed inputs.
68    fn verify(self) -> Result<Self::Verified, Self::Error> {
69        let batch_reference_num = self.batch.reference_block_num.block_num;
70        let proposed_reference_num = self.proposed_batch.reference_block_header.block_num.block_num;
71        if batch_reference_num != proposed_reference_num {
72            return Err(ConversionError::message(
73                "batch reference block number does not match proposal",
74            ));
75        }
76
77        let proposed_batch = self
78            .proposed_batch
79            .verify_with(MIN_PROOF_SECURITY_LEVEL)
80            .context("proposed_batch")?;
81        let batch = self.batch.verify_with(&proposed_batch).context("batch")?;
82
83        if self.sealed_transaction_inputs.as_slice().len() != proposed_batch.transactions().len() {
84            return Err(ConversionError::message(format!(
85                "sealed transaction input count {} does not match proposal transaction count {}",
86                self.sealed_transaction_inputs.as_slice().len(),
87                proposed_batch.transactions().len()
88            )));
89        }
90
91        Ok(TransactionBatchSubmission {
92            batch,
93            proposed_batch,
94            sealed_transaction_inputs: self.sealed_transaction_inputs.map(Into::into),
95        })
96    }
97}
98
99impl From<&TransactionBatchSubmission> for proto::submission::TransactionBatch {
100    fn from(value: &TransactionBatchSubmission) -> Self {
101        Self {
102            batch: Some((&value.batch).into()),
103            proposed_batch: Some((&value.proposed_batch).into()),
104            sealed_transaction_inputs: value.sealed_transaction_inputs.clone(),
105        }
106    }
107}