Skip to content
Draft
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
7 changes: 3 additions & 4 deletions bin/benchmark/src/inclusion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use miden_node_proto::generated as proto;
use miden_node_proto::generated::rpc::BlockHeaderByNumberRequest;
use miden_protocol::block::{BlockHeader, SignedBlock};
use miden_protocol::transaction::TransactionId;
use miden_protocol::utils::serde::Deserializable;

/// One scanned block that contained at least one of our txs. Empty blocks in the scan range are not
/// represented here.
Expand Down Expand Up @@ -126,15 +125,15 @@ pub(crate) async fn scan_with_drain(
continue;
},
};
let Some(bytes) = response.block else {
let Some(block) = response.signed_block else {
next_block += 1;
continue;
};
let signed_block = match SignedBlock::read_from_bytes(&bytes) {
let signed_block = match SignedBlock::try_from(block) {
Ok(sb) => sb,
Err(err) => {
eprintln!(
" warning: failed to deserialize SignedBlock for block {next_block}: {err}"
" warning: failed to convert SignedBlock for block {next_block}: {err}"
);
next_block += 1;
continue;
Expand Down
6 changes: 3 additions & 3 deletions bin/benchmark/src/submit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use miden_node_proto::domain::encryption::TransactionInputsSealer;
use miden_node_proto::generated as proto;
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::PublicKey as ValidatorPublicKey;
use miden_protocol::transaction::{ProvenTransaction, TransactionId};
use miden_protocol::utils::serde::{Deserializable, Serializable};
use miden_protocol::utils::serde::Deserializable;
use tokio::sync::Semaphore;
use url::Url;

Expand Down Expand Up @@ -188,8 +188,8 @@ async fn submit_all(
let sealed_inputs =
sealer.seal(tx.id(), &inputs).expect("failed to seal transaction inputs");
let request = proto::transaction::ProvenTransaction {
transaction: tx.to_bytes(),
sealed_transaction_inputs: Some(sealed_inputs),
transaction_data: Some(tx.into()),
};
let t0 = Instant::now();
let outcome = match client.submit_proven_tx(request).await {
Expand Down Expand Up @@ -247,8 +247,8 @@ async fn submit_sequential(
let sealed_inputs =
sealer.seal(tx.id(), &inputs).expect("failed to seal transaction inputs");
let request = proto::transaction::ProvenTransaction {
transaction: tx.to_bytes(),
sealed_transaction_inputs: Some(sealed_inputs),
transaction_data: Some(tx.into()),
};

let t0 = Instant::now();
Expand Down
5 changes: 2 additions & 3 deletions bin/network-monitor/src/deploy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,11 @@ impl TransactionSubmissionClient {
proven_tx: &ProvenTransaction,
transaction_inputs: &[u8],
) -> Result<BlockNumber> {
let transaction = proven_tx.to_bytes();
let tx_id = proven_tx.id();
let stale_key = AtomicBool::new(false);

let result = (|| {
let transaction = transaction.clone();
let transaction_data = proven_tx.into();
async {
if stale_key.swap(false, Ordering::Relaxed) {
*self.sealer.lock().await = None;
Expand All @@ -169,8 +168,8 @@ impl TransactionSubmissionClient {
self.rpc_client
.clone()
.submit_proven_tx(ProtoProvenTransaction {
transaction,
sealed_transaction_inputs: Some(sealed),
transaction_data: Some(transaction_data),
})
.await
.context("Failed to submit proven transaction to RPC")
Expand Down
8 changes: 5 additions & 3 deletions bin/node/src/commands/recover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use miden_protocol::block::{
ValidatorKeys,
};
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::Signature;
use miden_protocol::utils::serde::Deserializable;
use tokio::sync::mpsc;
use tokio_stream::StreamExt;
use tonic::codec::Streaming;
Expand Down Expand Up @@ -218,8 +217,11 @@ async fn read_blocks(
result.with_context(|| format!("block stream of validator {url} returned an error"))
})
.and_then(|event| {
SignedBlock::read_from_bytes(&event.block)
.with_context(|| format!("failed to deserialize block from validator {url}"))
let signed_block = event.signed_block.ok_or_else(|| {
anyhow::anyhow!("validator {url} returned a block without signed_block")
})?;
SignedBlock::try_from(signed_block)
.with_context(|| format!("failed to convert block from validator {url}"))
});

let is_err = block.is_err();
Expand Down
15 changes: 9 additions & 6 deletions bin/ntx-builder/src/clients/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ impl RpcClient {
// `&self`, so callers like `block_subscription_reconnecting` can store it freely.
Ok(stream
.map_err(RpcError::GrpcClientError)
.and_then(|response| async move { decode_block_subscription_response(&response) })
.and_then(|response| async move { decode_block_subscription_response(response) })
.boxed())
})
.retry(self.backoff)
Expand Down Expand Up @@ -328,14 +328,14 @@ impl RpcClient {
proven_tx: &ProvenTransaction,
tx_inputs: &TransactionInputs,
) -> Result<(), Status> {
let transaction = proven_tx.to_bytes();
let transaction_data: proto::transaction::ProvenTransactionData = proven_tx.into();
let transaction_inputs = tx_inputs.to_bytes();
let tx_id = proven_tx.id();
let stale_key = AtomicBool::new(false);

(|| {
let mut client = self.inner.clone();
let transaction = transaction.clone();
let transaction_data = transaction_data.clone();
let transaction_inputs = transaction_inputs.clone();
let stale_key = &stale_key;
async move {
Expand All @@ -351,8 +351,8 @@ impl RpcClient {
})?;
client
.submit_proven_tx(proto::transaction::ProvenTransaction {
transaction,
sealed_transaction_inputs: Some(sealed),
transaction_data: Some(transaction_data),
})
.await
}
Expand All @@ -374,9 +374,12 @@ impl RpcClient {
}

fn decode_block_subscription_response(
response: &BlockSubscriptionResponse,
response: BlockSubscriptionResponse,
) -> Result<(SignedBlock, BlockNumber), RpcError> {
let block = SignedBlock::read_from_bytes(&response.block).map_err(RpcError::Deserialize)?;
let signed_block = response.signed_block.ok_or_else(|| {
RpcError::InvalidResponse("block subscription response is missing signed_block".into())
})?;
let block = SignedBlock::try_from(signed_block).map_err(RpcError::Conversion)?;
let committed_tip = BlockNumber::from(response.committed_chain_tip);
Ok((block, committed_tip))
}
Expand Down
17 changes: 12 additions & 5 deletions bin/validator/src/server/validator_service/block_subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use miden_node_proto::generated as grpc;
use miden_node_proto::generated::validator::BlockSubscriptionResponse;
use miden_node_utils::ErrorReport;
use miden_node_utils::tracing::{miden_instrument, miden_span_record};
use miden_protocol::block::BlockNumber;
use miden_protocol::block::{BlockNumber, SignedBlock};
use miden_protocol::utils::serde::Deserializable;
use tokio::sync::OwnedRwLockWriteGuard;
use tokio_stream::wrappers::ReceiverStream;
use tonic::Status;
Expand Down Expand Up @@ -83,10 +84,16 @@ impl grpc::server::validator_api::BlockSubscription for ValidatorService {
async move {
for block in from.as_u32()..=committed_tip.as_u32() {
let response = match store.load_block(block.into()).await {
Ok(Some(block)) => Ok(BlockSubscriptionResponse {
block,
committed_chain_tip: committed_tip.as_u32(),
}),
Ok(Some(block_bytes)) => SignedBlock::read_from_bytes(&block_bytes)
.map(|signed_block| BlockSubscriptionResponse {
committed_chain_tip: committed_tip.as_u32(),
signed_block: Some(signed_block.into()),
})
.map_err(|err| {
tonic::Status::data_loss(format!(
"stored block {block} could not be decoded: {err}"
))
}),
Ok(None) => {
Err(tonic::Status::not_found(format!("block {block} not found")))
}
Expand Down
10 changes: 4 additions & 6 deletions bin/validator/src/server/validator_service/sign_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use miden_node_utils::ErrorReport;
use miden_protocol::Word;
use miden_protocol::block::{BlockNumber, ProposedBlock};
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature};
use miden_tx::utils::serde::{Deserializable, Serializable};
use miden_tx::utils::serde::Serializable;

use super::ValidatorService;

Expand All @@ -14,11 +14,9 @@ impl grpc::server::validator_api::SignBlock for ValidatorService {
type Input = ProposedBlock;
type Output = (Signature, Word, PublicKey);

fn decode(request: grpc::blockchain::ProposedBlock) -> tonic::Result<Self::Input> {
ProposedBlock::read_from_bytes(&request.proposed_block).map_err(|err| {
tonic::Status::invalid_argument(
err.as_report_context("Failed to deserialize proposed block"),
)
fn decode(request: grpc::validator::ProposedBlock) -> tonic::Result<Self::Input> {
ProposedBlock::try_from(request).map_err(|err| {
tonic::Status::invalid_argument(err.as_report_context("Invalid proposed block"))
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,11 @@ impl grpc::server::validator_api::SubmitProvenTransaction for ValidatorService {
}

fn decode(request: grpc::transaction::ProvenTransaction) -> tonic::Result<Self::Input> {
let tx = ProvenTransaction::read_from_bytes(&request.transaction).map_err(|err| {
Status::invalid_argument(err.as_report_context("Invalid proven transaction"))
})?;
let tx = request
.transaction_data
.ok_or_else(|| Status::invalid_argument("Missing transaction_data"))?
.try_into()
.map_err(Status::from)?;
let sealed = request.sealed_transaction_inputs.ok_or_else(|| {
Status::invalid_argument(
"Missing sealed transaction inputs: fetch the encryption key with \
Expand Down
82 changes: 75 additions & 7 deletions bin/validator/src/server/validator_service/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ impl TestValidator {
sealed: proto::transaction::SealedTransactionInputs,
) -> Result<(), tonic::Status> {
let request = tonic::Request::new(proto::transaction::ProvenTransaction {
transaction: tx.to_bytes(),
sealed_transaction_inputs: Some(sealed),
transaction_data: Some(tx.into()),
});
validator_api::SubmitProvenTransaction::full(&self.server, request).await
}
Expand Down Expand Up @@ -144,9 +144,20 @@ impl TestValidator {
&self,
proposed_block: &ProposedBlock,
) -> Result<proto::blockchain::SignBlockResponse, tonic::Status> {
let request = tonic::Request::new(proto::blockchain::ProposedBlock {
proposed_block: proposed_block.to_bytes(),
});
// All proposals submitted through this helper are empty, so their original inputs contain
// only the parent header and partial blockchain. Tests exercising non-empty proposals call
// `validate_block` directly.
let block_inputs = BlockInputs::new(
proposed_block.prev_block_header().clone(),
proposed_block.partial_blockchain().clone(),
BTreeMap::new(),
BTreeMap::new(),
BTreeMap::new(),
);
let request = tonic::Request::new(proto::validator::ProposedBlock::from((
proposed_block,
&block_inputs,
)));
validator_api::SignBlock::full(&self.server, request).await
}

Expand Down Expand Up @@ -379,6 +390,61 @@ async fn proven_transaction_fixture() -> &'static ProvenTransactionFixture {
// TESTS
// ================================================================================================

#[test]
fn validator_descriptor_exposes_structured_proposed_block_schema() {
let descriptor = miden_node_proto_build::validator_api_descriptor();
let validator_file = descriptor
.file
.iter()
.find(|file| file.name().ends_with("validator.proto"))
.expect("the validator descriptor should include validator.proto");

for name in ["ProposedBlock", "BlockInputs", "NullifierWitness"] {
assert!(
validator_file.message_type.iter().any(|message| message.name() == name),
"the validator descriptor should expose validator.{name}"
);
}

let proposed_block = validator_file
.message_type
.iter()
.find(|message| message.name() == "ProposedBlock")
.expect("validator.ProposedBlock should be present");
assert!(proposed_block.reserved_name.iter().any(|name| name == "proposed_block"));
assert!(
proposed_block
.reserved_range
.iter()
.any(|range| range.start() <= 1 && range.end() > 1)
);
assert!(!proposed_block.field.iter().any(|field| field.name() == "proposed_block"));

let api = validator_file
.service
.iter()
.find(|service| service.name() == "Api")
.expect("validator.Api should be present");
let sign_block = api
.method
.iter()
.find(|method| method.name() == "SignBlock")
.expect("validator.Api.SignBlock should be present");
assert_eq!(sign_block.input_type(), ".validator.ProposedBlock");

let blockchain_file = descriptor
.file
.iter()
.find(|file| file.name() == "types/blockchain.proto")
.expect("the validator descriptor should include types/blockchain.proto");
assert!(
!blockchain_file
.message_type
.iter()
.any(|message| message.name() == "ProposedBlock")
);
}

/// A validator whose signing key does not match the `validator_key` designated by the chain
/// (carried forward from genesis) must fail to start, rather than coming up and silently producing
/// signatures that the block producer cannot verify.
Expand Down Expand Up @@ -772,7 +838,6 @@ async fn block_subscription_replays_then_freezes_signing() {
use std::time::Duration;

use miden_protocol::block::SignedBlock;
use miden_tx::utils::serde::Deserializable;
use tokio_stream::StreamExt;

let mut tv = TestValidator::new().await;
Expand All @@ -789,7 +854,10 @@ async fn block_subscription_replays_then_freezes_signing() {
.expect("replayed block should arrive promptly")
.expect("stream should not end")
.expect("stream item should not be an error");
let block = SignedBlock::read_from_bytes(&response.block).expect("valid signed block");
let block = SignedBlock::try_from(
response.signed_block.expect("response should contain a signed block"),
)
.expect("valid signed block");
assert_eq!(block.header().block_num().as_u32(), expected);
assert_eq!(response.committed_chain_tip, 2);
}
Expand Down Expand Up @@ -1083,8 +1151,8 @@ async fn submit_rejects_missing_encrypted_inputs() {
let tv = TestValidator::new().await;
let tx = dummy_proven_tx(2);
let request = tonic::Request::new(proto::transaction::ProvenTransaction {
transaction: tx.to_bytes(),
sealed_transaction_inputs: None,
transaction_data: Some((&tx).into()),
});

let status = validator_api::SubmitProvenTransaction::full(&tv.server, request)
Expand Down
2 changes: 1 addition & 1 deletion crates/block-producer/src/block_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ impl BlockBuilder {
});
let responses = self
.validator
.sign_block(proposed_block.clone())
.sign_block(&proposed_block, &block_inputs)
.await
.map_err(|err| BuildBlockError::ValidateBlockFailed(err.into()))?;
let (header, body) = build_result
Expand Down
9 changes: 5 additions & 4 deletions crates/block-producer/src/domain/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use miden_protocol::account::AccountId;
use miden_protocol::block::BlockNumber;
use miden_protocol::note::Nullifier;
use miden_protocol::transaction::{ProvenTransaction, TransactionId, TxAccountUpdate};
use miden_protocol::utils::serde::{Deserializable, Serializable};

use crate::errors::StateConflict;
use crate::store::TransactionInputs;
Expand Down Expand Up @@ -141,14 +140,14 @@ impl AuthenticatedTransaction {
impl From<AuthenticatedTransaction> for sequencer::AuthenticatedTransaction {
fn from(value: AuthenticatedTransaction) -> Self {
Self {
transaction: value.inner.to_bytes(),
store_account_state: value.store_account_state.map(Into::into),
notes_authenticated_by_store: value
.notes_authenticated_by_store
.into_iter()
.map(Into::into)
.collect(),
authentication_height: value.authentication_height.as_u32(),
proven_transaction: Some(value.inner.as_ref().into()),
}
}
}
Expand All @@ -157,8 +156,10 @@ impl TryFrom<sequencer::AuthenticatedTransaction> for AuthenticatedTransaction {
type Error = ConversionError;

fn try_from(value: sequencer::AuthenticatedTransaction) -> Result<Self, Self::Error> {
let inner = ProvenTransaction::read_from_bytes(&value.transaction)
.map_err(|err| ConversionError::deserialization("ProvenTransaction", err))?;
let inner = value
.proven_transaction
.ok_or_else(|| ConversionError::message("missing proven_transaction"))?
.try_into()?;

let store_account_state = value.store_account_state.map(Word::try_from).transpose()?;

Expand Down
Loading
Loading