-
Notifications
You must be signed in to change notification settings - Fork 105
feat: Update SubmitProvenTransaction RPC with tx replay data #1278
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
dbc3656
7711100
df6d4ee
cd75d39
a43ae9a
64e22fb
3ca4707
5c1bb34
c7de286
eafa1c5
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 |
|---|---|---|
|
|
@@ -26,15 +26,21 @@ use miden_objects::account::delta::AccountUpdateDetails; | |
| 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}; | ||
| use miden_objects::transaction::{ | ||
| OutputNote, | ||
| ProvenTransaction, | ||
| ProvenTransactionBuilder, | ||
| TransactionInputs, | ||
| }; | ||
| use miden_objects::utils::serde::{Deserializable, Serializable}; | ||
| use miden_objects::{MIN_PROOF_SECURITY_LEVEL, Word}; | ||
| use miden_tx::TransactionVerifier; | ||
| use tonic::{IntoRequest, Request, Response, Status}; | ||
| use tracing::{debug, info, instrument}; | ||
| use tracing::{debug, info, instrument, warn}; | ||
| use url::Url; | ||
|
|
||
| use crate::COMPONENT; | ||
| use crate::server::validator; | ||
|
|
||
| // RPC SERVICE | ||
| // ================================================================================================ | ||
|
|
@@ -392,6 +398,32 @@ 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")) | ||
| })?; | ||
drahnr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Re-execute the transaction. | ||
| match validator::re_execute_transaction(tx_inputs).await { | ||
| Ok(_executed_tx) => { | ||
| debug!( | ||
| target = COMPONENT, | ||
| tx_id = %tx.id().to_hex(), | ||
| "Transaction re-execution successful" | ||
| ); | ||
| }, | ||
| Err(e) => { | ||
| warn!( | ||
| target = COMPONENT, | ||
| tx_id = %tx.id().to_hex(), | ||
| error = %e, | ||
| "Transaction re-execution failed, but continuing with submission" | ||
| ); | ||
| }, | ||
| } | ||
| } | ||
|
Collaborator
Author
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.
Contributor
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. Since this is opt in for debugging only, we probably want to create bounded queue but do the re-execution async. This implies it's not going to block by default, but will eventually if we're under high load, mostly to avoid OOM as it would occur in the unbounded channel scenario. This would hint at 2. being logging only.
Collaborator
Author
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. This re-execution logic will move to the validator component and this RPC stack will talk to that via gRPC. Having said that, I think the bounded queue and async processing logic would be the same for this PR and the followup work. Its just a question of what we want to put into this PR for now. |
||
|
|
||
| block_producer.clone().submit_proven_transaction(request).await | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| /// NOTE: This module contains logic that will eventually be moved to the Validator component | ||
| /// when it is added to this repository. | ||
| use std::collections::BTreeSet; | ||
|
|
||
| use miden_objects::Word; | ||
| use miden_objects::account::{AccountId, PartialAccount, StorageMapWitness}; | ||
| use miden_objects::asset::AssetWitness; | ||
| use miden_objects::block::{BlockHeader, BlockNumber}; | ||
| use miden_objects::transaction::{ | ||
| AccountInputs, | ||
| ExecutedTransaction, | ||
| PartialBlockchain, | ||
| TransactionInputs, | ||
| }; | ||
| use miden_objects::vm::FutureMaybeSend; | ||
| use miden_tx::auth::UnreachableAuth; | ||
| use miden_tx::{ | ||
| DataStore, | ||
| DataStoreError, | ||
| MastForestStore, | ||
| TransactionExecutor, | ||
| TransactionExecutorError, | ||
| TransactionMastStore, | ||
| }; | ||
|
|
||
| /// Executes a transaction using the provided transaction inputs. | ||
| pub async fn re_execute_transaction( | ||
| tx_inputs: TransactionInputs, | ||
| ) -> Result<ExecutedTransaction, TransactionExecutorError> { | ||
| // 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 | ||
| } | ||
|
|
||
| /// A [`DataStore`] implementation that wraps [`TransactionInputs`] | ||
| struct TransactionInputsDataStore { | ||
| tx_inputs: TransactionInputs, | ||
| mast_store: TransactionMastStore, | ||
| } | ||
|
|
||
| impl TransactionInputsDataStore { | ||
| 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 } | ||
| } | ||
| } | ||
|
|
||
| impl DataStore for TransactionInputsDataStore { | ||
sergerad marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| fn get_transaction_inputs( | ||
| &self, | ||
| account_id: AccountId, | ||
| _ref_blocks: BTreeSet<BlockNumber>, | ||
| ) -> impl FutureMaybeSend<Result<(PartialAccount, BlockHeader, PartialBlockchain), DataStoreError>> | ||
| { | ||
| async move { | ||
| if self.tx_inputs.account().id() != account_id { | ||
| return Err(DataStoreError::AccountNotFound(account_id)); | ||
| } | ||
|
|
||
| Ok(( | ||
| self.tx_inputs.account().clone(), | ||
| self.tx_inputs.block_header().clone(), | ||
| self.tx_inputs.blockchain().clone(), | ||
| )) | ||
| } | ||
| } | ||
|
|
||
| fn get_foreign_account_inputs( | ||
| &self, | ||
| foreign_account_id: AccountId, | ||
| _ref_block: BlockNumber, | ||
| ) -> impl FutureMaybeSend<Result<AccountInputs, DataStoreError>> { | ||
| async move { Err(DataStoreError::AccountNotFound(foreign_account_id)) } | ||
| } | ||
bobbinth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| fn get_vault_asset_witness( | ||
| &self, | ||
| account_id: AccountId, | ||
| vault_root: Word, | ||
| vault_key: Word, | ||
| ) -> impl FutureMaybeSend<Result<AssetWitness, DataStoreError>> { | ||
| async move { | ||
| if self.tx_inputs.account().id() != account_id { | ||
| return Err(DataStoreError::AccountNotFound(account_id)); | ||
| } | ||
sergerad marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if self.tx_inputs.account().vault().root() != vault_root { | ||
| return Err(DataStoreError::Other { | ||
| error_msg: "vault root mismatch".into(), | ||
| source: None, | ||
| }); | ||
| } | ||
|
|
||
| match self.tx_inputs.account().vault().open(vault_key) { | ||
| Ok(vault_proof) => { | ||
| AssetWitness::new(vault_proof.into()).map_err(|err| DataStoreError::Other { | ||
| error_msg: "failed to open vault asset tree".into(), | ||
| source: Some(err.into()), | ||
| }) | ||
| }, | ||
| Err(err) => Err(DataStoreError::Other { | ||
| error_msg: "failed to open vault".into(), | ||
| source: Some(err.into()), | ||
| }), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn get_storage_map_witness( | ||
| &self, | ||
| account_id: AccountId, | ||
| _map_root: Word, | ||
| _map_key: Word, | ||
| ) -> impl FutureMaybeSend<Result<StorageMapWitness, DataStoreError>> { | ||
| async move { | ||
| if self.tx_inputs.account().id() != account_id { | ||
| return Err(DataStoreError::AccountNotFound(account_id)); | ||
| } | ||
|
|
||
| // For partial accounts, storage map witness is not available. | ||
| Err(DataStoreError::Other { | ||
| error_msg: "storage map witness not available with partial account state".into(), | ||
| source: None, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| fn get_note_script( | ||
| &self, | ||
| script_root: Word, | ||
| ) -> impl FutureMaybeSend<Result<miden_objects::note::NoteScript, DataStoreError>> { | ||
| async move { Err(DataStoreError::NoteScriptNotFound(script_root)) } | ||
| } | ||
| } | ||
|
|
||
| impl MastForestStore for TransactionInputsDataStore { | ||
| fn get(&self, procedure_hash: &Word) -> Option<std::sync::Arc<miden_objects::MastForest>> { | ||
| self.mast_store.get(procedure_hash) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.