-
Notifications
You must be signed in to change notification settings - Fork 87
feat: Add block validation logic against validated transactions (in-memory) #1460
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 4 commits
7c739f3
461a3db
e65d0e4
101ffc3
36a943a
4016a79
8a12ebc
62fe142
dce38b6
9bb671f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| use std::sync::Arc; | ||
|
|
||
| use miden_lib::block::build_block; | ||
| use miden_objects::ProposedBlockError; | ||
| use miden_objects::block::{BlockNumber, BlockSigner, ProposedBlock}; | ||
| use miden_objects::crypto::dsa::ecdsa_k256_keccak::Signature; | ||
| use miden_objects::transaction::TransactionId; | ||
|
|
||
| use crate::server::ValidatedTransactions; | ||
|
|
||
| // BLOCK VALIDATION ERROR | ||
| // ================================================================================================ | ||
|
|
||
| #[derive(thiserror::Error, Debug)] | ||
| pub enum BlockValidationError { | ||
| #[error("transaction {0} in block {1} has not been validated")] | ||
| TransactionNotValidated(TransactionId, BlockNumber), | ||
| #[error("failed to build block")] | ||
| BlockBuildingFailed(#[from] ProposedBlockError), | ||
| } | ||
|
|
||
| // BLOCK VALIDATION | ||
| // ================================================================================================ | ||
|
|
||
| /// Validates a block by checking that all transactions in the proposed block have been processed by | ||
| /// the validator in the past. | ||
| /// | ||
| /// Removes the validated transactions from the cache upon success. | ||
| pub async fn validate_block<S: BlockSigner>( | ||
| proposed_block: ProposedBlock, | ||
| signer: &S, | ||
| validated_transactions: Arc<ValidatedTransactions>, | ||
| ) -> Result<Signature, BlockValidationError> { | ||
| // Build the block. | ||
| let (header, body) = build_block(proposed_block)?; | ||
|
|
||
| // Check that all transactions in the proposed block have been validated | ||
| let validated_txs = validated_transactions.read().await; | ||
| for tx_header in body.transactions().as_slice() { | ||
| let tx_id = tx_header.id(); | ||
| if !validated_txs.contains_key(&tx_id) { | ||
| return Err(BlockValidationError::TransactionNotValidated(tx_id, header.block_num())); | ||
| } | ||
| } | ||
sergerad marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Release the validated transactions read lock. | ||
| drop(validated_txs); | ||
|
|
||
| // Sign the header. | ||
| let signature = signer.sign(&header); | ||
|
|
||
| // Remove the validated transactions from the cache. | ||
| let mut validated_txs = validated_transactions.write().await; | ||
| for tx_header in body.transactions().as_slice() { | ||
| validated_txs.remove(&tx_header.id()); | ||
| } | ||
| // Release the validated transactions write lock. | ||
| drop(validated_txs); | ||
|
||
|
|
||
| Ok(signature) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| mod block_validation; | ||
| mod server; | ||
| mod tx_validation; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,37 @@ | ||
| use std::collections::HashMap; | ||
| use std::net::SocketAddr; | ||
| use std::sync::Arc; | ||
| use std::time::Duration; | ||
|
|
||
| use anyhow::Context; | ||
| use miden_lib::block::build_block; | ||
| use miden_node_proto::generated::validator::api_server; | ||
| use miden_node_proto::generated::{self as proto}; | ||
| use miden_node_proto_build::validator_api_descriptor; | ||
| use miden_node_utils::ErrorReport; | ||
| use miden_node_utils::panic::catch_panic_layer_fn; | ||
| use miden_node_utils::tracing::grpc::grpc_trace_fn; | ||
| use miden_objects::block::{BlockSigner, ProposedBlock}; | ||
| use miden_objects::transaction::{ProvenTransaction, TransactionInputs}; | ||
| use miden_objects::transaction::{ | ||
| ProvenTransaction, | ||
| TransactionHeader, | ||
| TransactionId, | ||
| TransactionInputs, | ||
| }; | ||
| use miden_objects::utils::{Deserializable, Serializable}; | ||
| use tokio::net::TcpListener; | ||
| use tokio::sync::RwLock; | ||
| use tokio_stream::wrappers::TcpListenerStream; | ||
| use tonic::Status; | ||
| use tower_http::catch_panic::CatchPanicLayer; | ||
| use tower_http::trace::TraceLayer; | ||
|
|
||
| use crate::COMPONENT; | ||
| use crate::block_validation::validate_block; | ||
| use crate::tx_validation::validate_transaction; | ||
|
|
||
| /// A type alias for a read-write lock that stores validated transactions. | ||
| pub type ValidatedTransactions = RwLock<HashMap<TransactionId, TransactionHeader>>; | ||
|
||
|
|
||
| // VALIDATOR | ||
| // ================================================================================ | ||
|
|
||
|
|
@@ -70,7 +81,7 @@ impl<S: BlockSigner + Send + Sync + 'static> Validator<S> { | |
| .layer(CatchPanicLayer::custom(catch_panic_layer_fn)) | ||
| .layer(TraceLayer::new_for_grpc().make_span_with(grpc_trace_fn)) | ||
| .timeout(self.grpc_timeout) | ||
| .add_service(api_server::ApiServer::new(ValidatorServer { signer: self.signer })) | ||
| .add_service(api_server::ApiServer::new(ValidatorServer::new(self.signer))) | ||
| .add_service(reflection_service) | ||
| .add_service(reflection_service_alpha) | ||
| .serve_with_incoming(TcpListenerStream::new(listener)) | ||
|
|
@@ -87,6 +98,14 @@ impl<S: BlockSigner + Send + Sync + 'static> Validator<S> { | |
| /// Implements the gRPC API for the validator. | ||
| struct ValidatorServer<S> { | ||
| signer: S, | ||
| validated_transactions: Arc<ValidatedTransactions>, | ||
| } | ||
|
|
||
| impl<S> ValidatorServer<S> { | ||
| fn new(signer: S) -> Self { | ||
| let validated_transactions = Arc::new(ValidatedTransactions::default()); | ||
| Self { signer, validated_transactions } | ||
| } | ||
| } | ||
|
|
||
| #[tonic::async_trait] | ||
|
|
@@ -123,9 +142,14 @@ impl<S: BlockSigner + Send + Sync + 'static> api_server::Api for ValidatorServer | |
| })?; | ||
|
|
||
| // Validate the transaction. | ||
| validate_transaction(proven_tx, tx_inputs).await.map_err(|err| { | ||
| Status::invalid_argument(err.as_report_context("Invalid transaction")) | ||
| })?; | ||
| let validated_tx_header = | ||
| validate_transaction(proven_tx.clone(), tx_inputs).await.map_err(|err| { | ||
sergerad marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Status::invalid_argument(err.as_report_context("Invalid transaction")) | ||
| })?; | ||
|
|
||
| // Register the validated transaction. | ||
| let tx_id = validated_tx_header.id(); | ||
| self.validated_transactions.write().await.insert(tx_id, validated_tx_header); | ||
|
|
||
| Ok(tonic::Response::new(())) | ||
| } | ||
|
|
@@ -145,10 +169,13 @@ impl<S: BlockSigner + Send + Sync + 'static> api_server::Api for ValidatorServer | |
| )) | ||
| })?; | ||
|
|
||
| // Build and sign header. | ||
| let (header, _body) = build_block(proposed_block) | ||
| .map_err(|err| tonic::Status::internal(format!("Failed to build block: {err}")))?; | ||
| let signature = self.signer.sign(&header); | ||
| // Validate the block. | ||
| let signature = | ||
| validate_block(proposed_block, &self.signer, self.validated_transactions.clone()) | ||
| .await | ||
| .map_err(|err| { | ||
| tonic::Status::invalid_argument(format!("Failed to validate block: {err}",)) | ||
| })?; | ||
|
|
||
| // Send the signature. | ||
| let response = proto::blockchain::BlockSignature { signature: signature.to_bytes() }; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.