diff --git a/CHANGELOG.md b/CHANGELOG.md index 34017c3e0..6be32f2bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Added chain tip to the block producer status ([#1419](https://github.com/0xMiden/miden-node/pull/1419)). - The mempool's transaction capacity is now configurable ([#1433](https://github.com/0xMiden/miden-node/pull/1433)). - Renamed card's names in the `miden-network-monitor` binary ([#1441](https://github.com/0xMiden/miden-node/pull/1441)). +- Integrated RPC stack with Validator component for transaction validation ([#1457](https://github.com/0xMiden/miden-node/pull/1457)). ### Changes diff --git a/Cargo.lock b/Cargo.lock index 9ee51162d..38063686b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2499,7 +2499,7 @@ dependencies = [ [[package]] name = "miden-block-prover" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#4b08b9c916bb3a1ecc7e509d51564e9860c1bbc8" +source = "git+https://github.com/0xMiden/miden-base.git?branch=next#198e25ee552638caefa356ff5b248a654b119083" dependencies = [ "miden-objects", "thiserror 2.0.17", @@ -2595,7 +2595,7 @@ dependencies = [ [[package]] name = "miden-lib" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#4b08b9c916bb3a1ecc7e509d51564e9860c1bbc8" +source = "git+https://github.com/0xMiden/miden-base.git?branch=next#198e25ee552638caefa356ff5b248a654b119083" dependencies = [ "fs-err", "miden-assembly", @@ -2951,6 +2951,8 @@ dependencies = [ "miden-node-proto-build", "miden-node-utils", "miden-objects", + "miden-tx", + "thiserror 2.0.17", "tokio", "tokio-stream", "tonic", @@ -2962,7 +2964,7 @@ dependencies = [ [[package]] name = "miden-objects" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#4b08b9c916bb3a1ecc7e509d51564e9860c1bbc8" +source = "git+https://github.com/0xMiden/miden-base.git?branch=next#198e25ee552638caefa356ff5b248a654b119083" dependencies = [ "bech32", "getrandom 0.3.4", @@ -3009,7 +3011,7 @@ dependencies = [ [[package]] name = "miden-protocol-macros" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#4b08b9c916bb3a1ecc7e509d51564e9860c1bbc8" +source = "git+https://github.com/0xMiden/miden-base.git?branch=next#198e25ee552638caefa356ff5b248a654b119083" dependencies = [ "proc-macro2", "quote", @@ -3115,7 +3117,7 @@ dependencies = [ [[package]] name = "miden-testing" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#4b08b9c916bb3a1ecc7e509d51564e9860c1bbc8" +source = "git+https://github.com/0xMiden/miden-base.git?branch=next#198e25ee552638caefa356ff5b248a654b119083" dependencies = [ "anyhow", "itertools 0.14.0", @@ -3133,7 +3135,7 @@ dependencies = [ [[package]] name = "miden-tx" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#4b08b9c916bb3a1ecc7e509d51564e9860c1bbc8" +source = "git+https://github.com/0xMiden/miden-base.git?branch=next#198e25ee552638caefa356ff5b248a654b119083" dependencies = [ "miden-lib", "miden-objects", @@ -3146,7 +3148,7 @@ dependencies = [ [[package]] name = "miden-tx-batch-prover" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#4b08b9c916bb3a1ecc7e509d51564e9860c1bbc8" +source = "git+https://github.com/0xMiden/miden-base.git?branch=next#198e25ee552638caefa356ff5b248a654b119083" dependencies = [ "miden-objects", "miden-tx", diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index b44e17dc9..af647a795 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -10,7 +10,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use anyhow::{Context, Result}; use miden_lib::AuthScheme; use miden_lib::account::interface::AccountInterface; -use miden_lib::utils::ScriptBuilder; +use miden_lib::utils::CodeBuilder; use miden_node_proto::clients::RpcClient; use miden_node_proto::generated::rpc::BlockHeaderByNumberRequest; use miden_node_proto::generated::transaction::ProvenTransaction; @@ -528,7 +528,7 @@ async fn create_and_submit_network_note( fn create_increment_script() -> Result<(NoteScript, Library)> { let library = get_counter_library()?; - let script_builder = ScriptBuilder::new(true) + let script_builder = CodeBuilder::new(true) .with_dynamically_linked_library(&library) .context("Failed to create script builder with library")?; diff --git a/bin/network-monitor/src/deploy/counter.rs b/bin/network-monitor/src/deploy/counter.rs index c7720fa0d..75381fba2 100644 --- a/bin/network-monitor/src/deploy/counter.rs +++ b/bin/network-monitor/src/deploy/counter.rs @@ -4,7 +4,7 @@ use std::path::Path; use anyhow::Result; use miden_lib::testing::account_component::IncrNonceAuthComponent; -use miden_lib::transaction::TransactionKernel; +use miden_lib::utils::CodeBuilder; use miden_objects::account::{ Account, AccountBuilder, @@ -50,12 +50,11 @@ pub fn create_counter_account(owner_account_id: AccountId) -> Result { let counter_slot = StorageSlot::with_value(COUNTER_SLOT_NAME.clone(), Word::empty()); - let account_code = AccountComponent::compile( - script, - TransactionKernel::assembler(), - vec![counter_slot, owner_id_slot], - )? - .with_supports_all_types(); + let component_code = + CodeBuilder::default().compile_component_code("counter::program", script)?; + + let account_code = AccountComponent::new(component_code, vec![counter_slot, owner_id_slot])? + .with_supports_all_types(); let incr_nonce_auth: AccountComponent = IncrNonceAuthComponent.into(); diff --git a/bin/node/src/commands/bundled.rs b/bin/node/src/commands/bundled.rs index 9a57db789..bf66f6c04 100644 --- a/bin/node/src/commands/bundled.rs +++ b/bin/node/src/commands/bundled.rs @@ -283,10 +283,13 @@ impl BundledCommand { .context("Failed to parse URL")?; let block_producer_url = Url::parse(&format!("http://{block_producer_address}")) .context("Failed to parse URL")?; + let validator_url = Url::parse(&format!("http://{validator_address}")) + .context("Failed to parse URL")?; Rpc { listener: grpc_rpc, store_url, block_producer_url: Some(block_producer_url), + validator_url, grpc_timeout, } .serve() diff --git a/bin/node/src/commands/rpc.rs b/bin/node/src/commands/rpc.rs index ed05546b3..643734f37 100644 --- a/bin/node/src/commands/rpc.rs +++ b/bin/node/src/commands/rpc.rs @@ -5,7 +5,7 @@ use miden_node_rpc::Rpc; use miden_node_utils::grpc::UrlExt; use url::Url; -use super::{ENV_BLOCK_PRODUCER_URL, ENV_RPC_URL, ENV_STORE_RPC_URL}; +use super::{ENV_BLOCK_PRODUCER_URL, ENV_RPC_URL, ENV_STORE_RPC_URL, ENV_VALIDATOR_URL}; use crate::commands::{DEFAULT_TIMEOUT, ENV_ENABLE_OTEL, duration_to_human_readable_string}; #[derive(clap::Subcommand)] @@ -25,6 +25,10 @@ pub enum RpcCommand { #[arg(long = "block-producer.url", env = ENV_BLOCK_PRODUCER_URL, value_name = "URL")] block_producer_url: Option, + /// The validator's gRPC url. + #[arg(long = "validator.url", env = ENV_VALIDATOR_URL, value_name = "URL")] + validator_url: Url, + /// Enables the exporting of traces for OpenTelemetry. /// /// This can be further configured using environment variables as defined in the official @@ -51,6 +55,7 @@ impl RpcCommand { url, store_url, block_producer_url, + validator_url, enable_otel: _, grpc_timeout, } = self; @@ -64,6 +69,7 @@ impl RpcCommand { listener, store_url, block_producer_url, + validator_url, grpc_timeout, } .serve() diff --git a/bin/stress-test/src/seeding/mod.rs b/bin/stress-test/src/seeding/mod.rs index 19f4b892d..924710a09 100644 --- a/bin/stress-test/src/seeding/mod.rs +++ b/bin/stress-test/src/seeding/mod.rs @@ -443,13 +443,11 @@ fn create_emit_note_tx( ) -> ProvenTransaction { let initial_account_hash = faucet.commitment(); - let slot = faucet.storage().get_item(BasicFungibleFaucet::metadata_slot_name()).unwrap(); + let metadata_slot_name = AccountStorage::faucet_sysdata_slot(); + let slot = faucet.storage().get_item(metadata_slot_name).unwrap(); faucet .storage_mut() - .set_item( - AccountStorage::faucet_metadata_slot(), - [slot[0], slot[1], slot[2], slot[3] + Felt::new(10)].into(), - ) + .set_item(metadata_slot_name, [slot[0], slot[1], slot[2], slot[3] + Felt::new(10)].into()) .unwrap(); faucet.increment_nonce(ONE).unwrap(); diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index f442e0115..2541d020a 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -8,11 +8,13 @@ use miden_objects::account::{ AccountId, AccountStorageHeader, StorageMap, + StorageSlotHeader, StorageSlotName, StorageSlotType, }; use miden_objects::asset::{Asset, AssetVault}; -use miden_objects::block::{AccountWitness, BlockNumber}; +use miden_objects::block::BlockNumber; +use miden_objects::block::account_tree::AccountWitness; use miden_objects::crypto::merkle::SparseMerklePath; use miden_objects::note::{NoteExecutionMode, NoteTag}; use miden_objects::utils::{Deserializable, DeserializationError, Serializable}; @@ -167,18 +169,18 @@ impl TryFrom for AccountStorageHeader { fn try_from(value: proto::account::AccountStorageHeader) -> Result { let proto::account::AccountStorageHeader { slots } = value; - let items = slots + let slot_headers = slots .into_iter() .map(|slot| { let slot_name = StorageSlotName::new(slot.slot_name)?; let slot_type = storage_slot_type_from_raw(slot.slot_type)?; let commitment = slot.commitment.ok_or(ConversionError::NotAValidFelt)?.try_into()?; - Ok((slot_name, slot_type, commitment)) + Ok(StorageSlotHeader::new(slot_name, slot_type, commitment)) }) .collect::, ConversionError>>()?; - Ok(AccountStorageHeader::new(items)?) + Ok(AccountStorageHeader::new(slot_headers)?) } } @@ -333,12 +335,10 @@ impl From for proto::account::AccountStorageHeader { fn from(value: AccountStorageHeader) -> Self { let slots = value .slots() - .map(|(slot_name, slot_type, slot_value)| { - proto::account::account_storage_header::StorageSlot { - slot_name: slot_name.to_string(), - slot_type: storage_slot_type_to_raw(*slot_type), - commitment: Some(proto::primitives::Digest::from(*slot_value)), - } + .map(|slot_header| proto::account::account_storage_header::StorageSlot { + slot_name: slot_header.name().to_string(), + slot_type: storage_slot_type_to_raw(slot_header.slot_type()), + commitment: Some(proto::primitives::Digest::from(slot_header.value())), }) .collect(); diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index a41d7c7ce..76bc4c73b 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -2,13 +2,8 @@ use std::collections::BTreeMap; use std::ops::RangeInclusive; use miden_objects::account::AccountId; -use miden_objects::block::{ - BlockHeader, - BlockInputs, - BlockNumber, - FeeParameters, - NullifierWitness, -}; +use miden_objects::block::nullifier_tree::NullifierWitness; +use miden_objects::block::{BlockHeader, BlockInputs, BlockNumber, FeeParameters}; use miden_objects::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature}; use miden_objects::note::{NoteId, NoteInclusionProof}; use miden_objects::transaction::PartialBlockchain; diff --git a/crates/rpc/src/server/api.rs b/crates/rpc/src/server/api.rs index 4c370d777..59ffffb0c 100644 --- a/crates/rpc/src/server/api.rs +++ b/crates/rpc/src/server/api.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use std::time::Duration; use anyhow::Context; -use miden_node_proto::clients::{BlockProducerClient, Builder, StoreRpcClient}; +use miden_node_proto::clients::{BlockProducerClient, Builder, StoreRpcClient, ValidatorClient}; use miden_node_proto::errors::ConversionError; use miden_node_proto::generated::rpc::MempoolStats; use miden_node_proto::generated::rpc::api_server::{self, Api}; @@ -20,12 +20,7 @@ use miden_objects::account::AccountId; use miden_objects::batch::ProvenBatch; use miden_objects::block::{BlockHeader, BlockNumber}; use miden_objects::note::{Note, NoteRecipient, NoteScript}; -use miden_objects::transaction::{ - OutputNote, - ProvenTransaction, - ProvenTransactionBuilder, - TransactionInputs, -}; +use miden_objects::transaction::{OutputNote, ProvenTransaction, ProvenTransactionBuilder}; use miden_objects::utils::serde::{Deserializable, Serializable}; use miden_objects::{MIN_PROOF_SECURITY_LEVEL, Word}; use miden_tx::TransactionVerifier; @@ -34,7 +29,6 @@ use tracing::{debug, info, instrument, warn}; use url::Url; use crate::COMPONENT; -use crate::server::validator; // RPC SERVICE // ================================================================================================ @@ -42,11 +36,12 @@ use crate::server::validator; pub struct RpcService { store: StoreRpcClient, block_producer: Option, + validator: ValidatorClient, genesis_commitment: Option, } impl RpcService { - pub(super) fn new(store_url: Url, block_producer_url: Option) -> Self { + pub(super) fn new(store_url: Url, block_producer_url: Option, validator_url: Url) -> Self { let store = { info!(target: COMPONENT, store_endpoint = %store_url, "Initializing store client"); Builder::new(store_url) @@ -73,9 +68,25 @@ impl RpcService { .connect_lazy::() }); + let validator = { + info!( + target: COMPONENT, + validator_endpoint = %validator_url, + "Initializing validator client", + ); + Builder::new(validator_url) + .without_tls() + .without_timeout() + .without_metadata_version() + .without_metadata_genesis() + .with_otel_context_injection() + .connect_lazy::() + }; + Self { store, block_producer, + validator, genesis_commitment: None, } } @@ -379,18 +390,14 @@ impl api_server::Api for RpcService { })?; // If transaction inputs are provided, re-execute the transaction to validate it. - if let Some(tx_inputs_bytes) = &request.transaction_inputs { - // Deserialize the transaction inputs. - let tx_inputs = TransactionInputs::read_from_bytes(tx_inputs_bytes).map_err(|err| { - Status::invalid_argument(err.as_report_context("Invalid transaction inputs")) - })?; - // Re-execute the transaction. - match validator::re_execute_transaction(tx_inputs).await { - Ok(_executed_tx) => { + if request.transaction_inputs.is_some() { + // Re-execute the transaction via the Validator. + match self.validator.clone().submit_proven_transaction(request.clone()).await { + Ok(_) => { debug!( target = COMPONENT, tx_id = %tx.id().to_hex(), - "Transaction re-execution successful" + "Transaction validation successful" ); }, Err(e) => { @@ -398,7 +405,7 @@ impl api_server::Api for RpcService { target = COMPONENT, tx_id = %tx.id().to_hex(), error = %e, - "Transaction re-execution failed, but continuing with submission" + "Transaction validation failed, but continuing with submission" ); }, } diff --git a/crates/rpc/src/server/mod.rs b/crates/rpc/src/server/mod.rs index 71ef163c2..229907207 100644 --- a/crates/rpc/src/server/mod.rs +++ b/crates/rpc/src/server/mod.rs @@ -21,17 +21,17 @@ use crate::server::health::HealthCheckLayer; mod accept; mod api; mod health; -mod validator; /// The RPC server component. /// /// On startup, binds to the provided listener and starts serving the RPC API. -/// It connects lazily to the store and block producer components as needed. +/// It connects lazily to the store, validator and block producer components as needed. /// Requests will fail if the components are not available. pub struct Rpc { pub listener: TcpListener, pub store_url: Url, pub block_producer_url: Option, + pub validator_url: Url, /// Server-side timeout for an individual gRPC request. /// /// If the handler takes longer than this duration, the server cancels the call. @@ -44,7 +44,11 @@ impl Rpc { /// Note: Executes in place (i.e. not spawned) and will run indefinitely until /// a fatal error is encountered. pub async fn serve(self) -> anyhow::Result<()> { - let mut api = api::RpcService::new(self.store_url.clone(), self.block_producer_url.clone()); + let mut api = api::RpcService::new( + self.store_url.clone(), + self.block_producer_url.clone(), + self.validator_url, + ); let genesis = api .get_genesis_header_with_retry() diff --git a/crates/rpc/src/tests.rs b/crates/rpc/src/tests.rs index 8aac2cb2f..05e9ee263 100644 --- a/crates/rpc/src/tests.rs +++ b/crates/rpc/src/tests.rs @@ -394,10 +394,13 @@ async fn start_rpc() -> (RpcClient, std::net::SocketAddr, std::net::SocketAddr) let store_url = Url::parse(&format!("http://{store_addr}")).unwrap(); // SAFETY: The block_producer_addr is always valid as it is created from a `SocketAddr`. let block_producer_url = Url::parse(&format!("http://{block_producer_addr}")).unwrap(); + // SAFETY: Using dummy validator URL for test - not actually contacted in this test + let validator_url = Url::parse("http://127.0.0.1:0").unwrap(); Rpc { listener: rpc_listener, store_url, block_producer_url: Some(block_producer_url), + validator_url, grpc_timeout: Duration::from_secs(30), } .serve() diff --git a/crates/store/src/accounts/mod.rs b/crates/store/src/accounts/mod.rs index 756985997..34f46e7ad 100644 --- a/crates/store/src/accounts/mod.rs +++ b/crates/store/src/accounts/mod.rs @@ -3,8 +3,8 @@ use std::collections::{BTreeMap, HashMap}; use miden_objects::account::{AccountId, AccountIdPrefix}; -use miden_objects::block::account_tree::{AccountMutationSet, AccountTree}; -use miden_objects::block::{AccountWitness, BlockNumber}; +use miden_objects::block::BlockNumber; +use miden_objects::block::account_tree::{AccountMutationSet, AccountTree, AccountWitness}; use miden_objects::crypto::merkle::{ EmptySubtreeRoots, LargeSmt, diff --git a/crates/store/src/db/tests.rs b/crates/store/src/db/tests.rs index cdd270266..e203e217c 100644 --- a/crates/store/src/db/tests.rs +++ b/crates/store/src/db/tests.rs @@ -7,7 +7,7 @@ use std::sync::{Arc, Mutex}; use diesel::{Connection, SqliteConnection}; use miden_lib::account::auth::AuthRpoFalcon512; use miden_lib::note::create_p2id_note; -use miden_lib::transaction::TransactionKernel; +use miden_lib::utils::CodeBuilder; use miden_node_proto::domain::account::AccountSummary; use miden_node_utils::fee::{test_fee, test_fee_params}; use miden_objects::account::auth::PublicKeyCommitment; @@ -1409,19 +1409,18 @@ fn mock_account_code_and_storage( StorageSlot::with_value(StorageSlotName::mock(5), num_to_word(5)), ]; - let component = AccountComponent::compile( - component_code, - TransactionKernel::assembler(), - component_storage, - ) - .unwrap() - .with_supported_type(account_type); + let account_component_code = CodeBuilder::default() + .compile_component_code("counter_contract::interface", component_code) + .unwrap(); + let account_component = AccountComponent::new(account_component_code, component_storage) + .unwrap() + .with_supports_all_types(); AccountBuilder::new(init_seed.unwrap_or([0; 32])) .account_type(account_type) .storage_mode(storage_mode) .with_assets(assets) - .with_component(component) + .with_component(account_component) .with_auth_component(AuthRpoFalcon512::new(PublicKeyCommitment::from(EMPTY_WORD))) .build_existing() .unwrap() @@ -1435,11 +1434,14 @@ fn mock_account_code_and_storage( #[miden_node_test_macro::enable_logging] fn genesis_with_account_assets() { use crate::genesis::GenesisState; + let component_code = "export.foo push.1 end"; - let component = - AccountComponent::compile("export.foo push.1 end", TransactionKernel::assembler(), vec![]) - .unwrap() - .with_supported_type(AccountType::RegularAccountImmutableCode); + let account_component_code = CodeBuilder::default() + .compile_component_code("foo::interface", component_code) + .unwrap(); + let account_component = AccountComponent::new(account_component_code, Vec::new()) + .unwrap() + .with_supports_all_types(); let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); let fungible_asset = FungibleAsset::new(faucet_id, 1000).unwrap(); @@ -1447,7 +1449,7 @@ fn genesis_with_account_assets() { let account = AccountBuilder::new([1u8; 32]) .account_type(AccountType::RegularAccountImmutableCode) .storage_mode(AccountStorageMode::Public) - .with_component(component) + .with_component(account_component) .with_assets([fungible_asset.into()]) .with_auth_component(AuthRpoFalcon512::new(PublicKeyCommitment::from(EMPTY_WORD))) .build_existing() @@ -1485,18 +1487,19 @@ fn genesis_with_account_storage_map() { StorageSlot::with_empty_value(StorageSlotName::mock(1)), ]; - let component = AccountComponent::compile( - "export.foo push.1 end", - TransactionKernel::assembler(), - component_storage, - ) - .unwrap() - .with_supported_type(AccountType::RegularAccountImmutableCode); + let component_code = "export.foo push.1 end"; + + let account_component_code = CodeBuilder::default() + .compile_component_code("foo::interface", component_code) + .unwrap(); + let account_component = AccountComponent::new(account_component_code, component_storage) + .unwrap() + .with_supports_all_types(); let account = AccountBuilder::new([2u8; 32]) .account_type(AccountType::RegularAccountImmutableCode) .storage_mode(AccountStorageMode::Public) - .with_component(component) + .with_component(account_component) .with_auth_component(AuthRpoFalcon512::new(PublicKeyCommitment::from(EMPTY_WORD))) .build_existing() .unwrap(); @@ -1530,18 +1533,19 @@ fn genesis_with_account_assets_and_storage() { StorageSlot::with_map(StorageSlotName::mock(2), storage_map), ]; - let component = AccountComponent::compile( - "export.foo push.1 end", - TransactionKernel::assembler(), - component_storage, - ) - .unwrap() - .with_supported_type(AccountType::RegularAccountImmutableCode); + let component_code = "export.foo push.1 end"; + + let account_component_code = CodeBuilder::default() + .compile_component_code("foo::interface", component_code) + .unwrap(); + let account_component = AccountComponent::new(account_component_code, component_storage) + .unwrap() + .with_supports_all_types(); let account = AccountBuilder::new([3u8; 32]) .account_type(AccountType::RegularAccountImmutableCode) .storage_mode(AccountStorageMode::Public) - .with_component(component) + .with_component(account_component) .with_assets([fungible_asset.into()]) .with_auth_component(AuthRpoFalcon512::new(PublicKeyCommitment::from(EMPTY_WORD))) .build_existing() @@ -1563,15 +1567,17 @@ fn genesis_with_multiple_accounts() { use crate::genesis::GenesisState; - let component1 = - AccountComponent::compile("export.foo push.1 end", TransactionKernel::assembler(), vec![]) - .unwrap() - .with_supported_type(AccountType::RegularAccountImmutableCode); + let account_component_code = CodeBuilder::default() + .compile_component_code("foo::interface", "export.foo push.1 end") + .unwrap(); + let account_component1 = AccountComponent::new(account_component_code, Vec::new()) + .unwrap() + .with_supports_all_types(); let account1 = AccountBuilder::new([1u8; 32]) .account_type(AccountType::RegularAccountImmutableCode) .storage_mode(AccountStorageMode::Public) - .with_component(component1) + .with_component(account_component1) .with_auth_component(AuthRpoFalcon512::new(PublicKeyCommitment::from(EMPTY_WORD))) .build_existing() .unwrap(); @@ -1579,15 +1585,17 @@ fn genesis_with_multiple_accounts() { let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); let fungible_asset = FungibleAsset::new(faucet_id, 2000).unwrap(); - let component2 = - AccountComponent::compile("export.bar push.2 end", TransactionKernel::assembler(), vec![]) - .unwrap() - .with_supported_type(AccountType::RegularAccountImmutableCode); + let account_component_code = CodeBuilder::default() + .compile_component_code("bar::interface", "export.bar push.2 end") + .unwrap(); + let account_component2 = AccountComponent::new(account_component_code, Vec::new()) + .unwrap() + .with_supports_all_types(); let account2 = AccountBuilder::new([2u8; 32]) .account_type(AccountType::RegularAccountImmutableCode) .storage_mode(AccountStorageMode::Public) - .with_component(component2) + .with_component(account_component2) .with_assets([fungible_asset.into()]) .with_auth_component(AuthRpoFalcon512::new(PublicKeyCommitment::from(EMPTY_WORD))) .build_existing() @@ -1601,18 +1609,17 @@ fn genesis_with_multiple_accounts() { let component_storage = vec![StorageSlot::with_map(StorageSlotName::mock(0), storage_map)]; - let component3 = AccountComponent::compile( - "export.baz push.3 end", - TransactionKernel::assembler(), - component_storage, - ) - .unwrap() - .with_supported_type(AccountType::RegularAccountImmutableCode); + let account_component_code = CodeBuilder::default() + .compile_component_code("baz::interface", "export.baz push.3 end") + .unwrap(); + let account_component3 = AccountComponent::new(account_component_code, component_storage) + .unwrap() + .with_supports_all_types(); let account3 = AccountBuilder::new([3u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) + .account_type(AccountType::RegularAccountUpdatableCode) .storage_mode(AccountStorageMode::Public) - .with_component(component3) + .with_component(account_component3) .with_auth_component(AuthRpoFalcon512::new(PublicKeyCommitment::from(EMPTY_WORD))) .build_existing() .unwrap(); diff --git a/crates/store/src/genesis/config/mod.rs b/crates/store/src/genesis/config/mod.rs index 4ad6c2557..e27a891cd 100644 --- a/crates/store/src/genesis/config/mod.rs +++ b/crates/store/src/genesis/config/mod.rs @@ -219,7 +219,7 @@ impl GenesisConfig { if total_issuance != 0 { // slot 0 storage_delta.set_item( - AccountStorage::faucet_metadata_slot().clone(), + AccountStorage::faucet_sysdata_slot().clone(), [ZERO, ZERO, ZERO, Felt::new(total_issuance)].into(), ); tracing::debug!( diff --git a/crates/store/src/genesis/config/tests.rs b/crates/store/src/genesis/config/tests.rs index 265880636..7fde9ee3f 100644 --- a/crates/store/src/genesis/config/tests.rs +++ b/crates/store/src/genesis/config/tests.rs @@ -45,10 +45,7 @@ fn parsing_yields_expected_default_values() -> TestResult { // check total issuance of the faucet assert_eq!( - native_faucet - .storage() - .get_item(AccountStorage::faucet_metadata_slot()) - .unwrap()[3], + native_faucet.storage().get_item(AccountStorage::faucet_sysdata_slot()).unwrap()[3], Felt::new(999_777), "Issuance mismatch" ); diff --git a/crates/store/src/state.rs b/crates/store/src/state.rs index 0605a0310..c9225d147 100644 --- a/crates/store/src/state.rs +++ b/crates/store/src/state.rs @@ -24,17 +24,9 @@ use miden_node_proto::domain::batch::BatchInputs; use miden_node_utils::ErrorReport; use miden_node_utils::formatting::format_array; use miden_objects::account::{AccountHeader, AccountId, StorageSlot, StorageSlotContent}; -use miden_objects::block::account_tree::{AccountTree, account_id_to_smt_key}; -use miden_objects::block::nullifier_tree::NullifierTree; -use miden_objects::block::{ - AccountWitness, - BlockHeader, - BlockInputs, - BlockNumber, - Blockchain, - NullifierWitness, - ProvenBlock, -}; +use miden_objects::block::account_tree::{AccountTree, AccountWitness, account_id_to_smt_key}; +use miden_objects::block::nullifier_tree::{NullifierTree, NullifierWitness}; +use miden_objects::block::{BlockHeader, BlockInputs, BlockNumber, Blockchain, ProvenBlock}; use miden_objects::crypto::merkle::{ Forest, LargeSmt, diff --git a/crates/validator/Cargo.toml b/crates/validator/Cargo.toml index 8f50ef493..0afc266de 100644 --- a/crates/validator/Cargo.toml +++ b/crates/validator/Cargo.toml @@ -23,6 +23,8 @@ miden-node-proto = { workspace = true } miden-node-proto-build = { features = ["internal"], workspace = true } miden-node-utils = { features = ["testing"], workspace = true } miden-objects = { workspace = true } +miden-tx = { workspace = true } +thiserror = { workspace = true } tokio = { features = ["macros", "net", "rt-multi-thread"], workspace = true } tokio-stream = { features = ["net"], workspace = true } tonic = { default-features = true, features = ["transport"], workspace = true } diff --git a/crates/validator/src/lib.rs b/crates/validator/src/lib.rs index d467b33fb..065098d8a 100644 --- a/crates/validator/src/lib.rs +++ b/crates/validator/src/lib.rs @@ -1,4 +1,5 @@ mod server; +mod tx_validation; pub use server::Validator; diff --git a/crates/validator/src/server/mod.rs b/crates/validator/src/server/mod.rs index cd4efe483..5e950be67 100644 --- a/crates/validator/src/server/mod.rs +++ b/crates/validator/src/server/mod.rs @@ -6,16 +6,20 @@ 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::utils::{Deserializable, Serializable}; use tokio::net::TcpListener; use tokio_stream::wrappers::TcpListenerStream; +use tonic::Status; use tower_http::catch_panic::CatchPanicLayer; use tower_http::trace::TraceLayer; use crate::COMPONENT; +use crate::tx_validation::validate_transaction; // VALIDATOR // ================================================================================ @@ -101,9 +105,28 @@ impl api_server::Api for ValidatorServer /// Receives a proven transaction, then validates and stores it. async fn submit_proven_transaction( &self, - _request: tonic::Request, + request: tonic::Request, ) -> Result, tonic::Status> { - // TODO(sergerad): Implement transaction validation logic. + let request = request.into_inner(); + // Deserialize the transaction. + let proven_tx = + ProvenTransaction::read_from_bytes(&request.transaction).map_err(|err| { + Status::invalid_argument(err.as_report_context("Invalid proven transaction")) + })?; + + // Deserialize the transaction inputs. + let Some(tx_inputs) = request.transaction_inputs else { + return Err(Status::invalid_argument("Missing transaction inputs")); + }; + let tx_inputs = TransactionInputs::read_from_bytes(&tx_inputs).map_err(|err| { + Status::invalid_argument(err.as_report_context("Invalid transaction inputs")) + })?; + + // Validate the transaction. + validate_transaction(proven_tx, tx_inputs).await.map_err(|err| { + Status::invalid_argument(err.as_report_context("Invalid transaction")) + })?; + Ok(tonic::Response::new(())) } diff --git a/crates/rpc/src/server/validator.rs b/crates/validator/src/tx_validation/data_store.rs similarity index 79% rename from crates/rpc/src/server/validator.rs rename to crates/validator/src/tx_validation/data_store.rs index 5a0b077e8..89685aa2a 100644 --- a/crates/rpc/src/server/validator.rs +++ b/crates/validator/src/tx_validation/data_store.rs @@ -7,47 +7,21 @@ use miden_objects::account::{AccountId, PartialAccount, StorageMapWitness}; use miden_objects::asset::{AssetVaultKey, AssetWitness}; use miden_objects::block::{BlockHeader, BlockNumber}; use miden_objects::note::NoteScript; -use miden_objects::transaction::{ - AccountInputs, - ExecutedTransaction, - PartialBlockchain, - TransactionInputs, -}; +use miden_objects::transaction::{AccountInputs, PartialBlockchain, TransactionInputs}; use miden_objects::vm::FutureMaybeSend; -use miden_tx::auth::UnreachableAuth; -use miden_tx::{ - DataStore, - DataStoreError, - MastForestStore, - TransactionExecutor, - TransactionExecutorError, - TransactionMastStore, -}; +use miden_tx::{DataStore, DataStoreError, MastForestStore, TransactionMastStore}; -/// Executes a transaction using the provided transaction inputs. -pub async fn re_execute_transaction( - tx_inputs: TransactionInputs, -) -> Result { - // Create a DataStore from the transaction inputs. - let data_store = TransactionInputsDataStore::new(tx_inputs.clone()); - - // Execute the transaction. - let (account, block_header, _, input_notes, tx_args) = tx_inputs.into_parts(); - let executor: TransactionExecutor<'_, '_, _, UnreachableAuth> = - TransactionExecutor::new(&data_store); - executor - .execute_transaction(account.id(), block_header.block_num(), input_notes, tx_args) - .await -} +// TRANSACTION INPUTS DATA STORE +// ================================================================================================ /// A [`DataStore`] implementation that wraps [`TransactionInputs`] -struct TransactionInputsDataStore { +pub struct TransactionInputsDataStore { tx_inputs: TransactionInputs, mast_store: TransactionMastStore, } impl TransactionInputsDataStore { - fn new(tx_inputs: TransactionInputs) -> Self { + pub fn new(tx_inputs: TransactionInputs) -> Self { let mast_store = TransactionMastStore::new(); mast_store.load_account_code(tx_inputs.account().code()); Self { tx_inputs, mast_store } diff --git a/crates/validator/src/tx_validation/mod.rs b/crates/validator/src/tx_validation/mod.rs new file mode 100644 index 000000000..188fe2f6b --- /dev/null +++ b/crates/validator/src/tx_validation/mod.rs @@ -0,0 +1,60 @@ +mod data_store; + +pub use data_store::TransactionInputsDataStore; +use miden_objects::MIN_PROOF_SECURITY_LEVEL; +use miden_objects::transaction::{ProvenTransaction, TransactionHeader, TransactionInputs}; +use miden_tx::auth::UnreachableAuth; +use miden_tx::{TransactionExecutor, TransactionExecutorError, TransactionVerifier}; + +// TRANSACTION VALIDATION ERROR +// ================================================================================================ + +#[derive(thiserror::Error, Debug)] +pub enum TransactionValidationError { + #[error("failed to re-executed the transaction")] + ExecutionError(#[from] TransactionExecutorError), + #[error("re-executed transaction did not match the provided proven transaction")] + Mismatch { + proven_tx_header: Box, + executed_tx_header: Box, + }, + #[error("transaction proof verification failed")] + ProofVerificationFailed(#[from] miden_tx::TransactionVerifierError), +} + +// TRANSACTION VALIDATION +// ================================================================================================ + +/// Validates a transaction by verifying its proof, executing it and comparing its header with the +/// provided proven transaction. +pub async fn validate_transaction( + proven_tx: ProvenTransaction, + tx_inputs: TransactionInputs, +) -> Result<(), TransactionValidationError> { + // First, verify the transaction proof + let tx_verifier = TransactionVerifier::new(MIN_PROOF_SECURITY_LEVEL); + tx_verifier.verify(&proven_tx)?; + + // Create a DataStore from the transaction inputs. + let data_store = TransactionInputsDataStore::new(tx_inputs.clone()); + + // Execute the transaction. + let (account, block_header, _, input_notes, tx_args) = tx_inputs.into_parts(); + let executor: TransactionExecutor<'_, '_, _, UnreachableAuth> = + TransactionExecutor::new(&data_store); + let executed_tx = executor + .execute_transaction(account.id(), block_header.block_num(), input_notes, tx_args) + .await?; + + // Validate that the executed transaction matches the submitted transaction. + let executed_tx_header: TransactionHeader = (&executed_tx).into(); + let proven_tx_header: TransactionHeader = (&proven_tx).into(); + if executed_tx_header == proven_tx_header { + Ok(()) + } else { + Err(TransactionValidationError::Mismatch { + proven_tx_header: proven_tx_header.into(), + executed_tx_header: executed_tx_header.into(), + }) + } +}