-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
2,683 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// MOCK DATA STORE | ||
// ================================================================================================ | ||
|
||
use alloc::vec::Vec; | ||
|
||
use miden_lib::transaction::TransactionKernel; | ||
use miden_objects::{ | ||
accounts::{ | ||
testing::{ | ||
transaction::{mock_inputs, mock_inputs_with_existing, notes::AssetPreservationStatus}, | ||
MockAccountType, | ||
}, | ||
Account, AccountId, | ||
}, | ||
assembly::ModuleAst, | ||
notes::{Note, NoteId}, | ||
transaction::{ | ||
ChainMmr, InputNote, InputNotes, OutputNote, TransactionArgs, TransactionInputs, | ||
}, | ||
BlockHeader, | ||
}; | ||
|
||
use crate::{DataStore, DataStoreError}; | ||
|
||
#[derive(Clone)] | ||
pub struct MockDataStore { | ||
pub account: Account, | ||
pub block_header: BlockHeader, | ||
pub block_chain: ChainMmr, | ||
pub notes: Vec<InputNote>, | ||
pub tx_args: TransactionArgs, | ||
} | ||
|
||
impl MockDataStore { | ||
pub fn new(asset_preservation_status: AssetPreservationStatus) -> Self { | ||
let (tx_inputs, tx_args) = mock_inputs( | ||
MockAccountType::StandardExisting, | ||
asset_preservation_status, | ||
&TransactionKernel::assembler(), | ||
); | ||
let (account, _, block_header, block_chain, notes) = tx_inputs.into_parts(); | ||
Self { | ||
account, | ||
block_header, | ||
block_chain, | ||
notes: notes.into_vec(), | ||
tx_args, | ||
} | ||
} | ||
|
||
pub fn with_existing(account: Option<Account>, input_notes: Option<Vec<Note>>) -> Self { | ||
let ( | ||
account, | ||
block_header, | ||
block_chain, | ||
consumed_notes, | ||
_auxiliary_data_inputs, | ||
created_notes, | ||
) = mock_inputs_with_existing( | ||
MockAccountType::StandardExisting, | ||
AssetPreservationStatus::Preserved, | ||
account, | ||
input_notes, | ||
&TransactionKernel::assembler(), | ||
); | ||
let output_notes = created_notes.into_iter().filter_map(|note| match note { | ||
OutputNote::Full(note) => Some(note), | ||
OutputNote::Partial(_) => None, | ||
OutputNote::Header(_) => None, | ||
}); | ||
let mut tx_args = TransactionArgs::default(); | ||
tx_args.extend_expected_output_notes(output_notes); | ||
|
||
Self { | ||
account, | ||
block_header, | ||
block_chain, | ||
notes: consumed_notes, | ||
tx_args, | ||
} | ||
} | ||
|
||
pub fn tx_args(&self) -> &TransactionArgs { | ||
&self.tx_args | ||
} | ||
} | ||
|
||
impl Default for MockDataStore { | ||
fn default() -> Self { | ||
Self::new(AssetPreservationStatus::Preserved) | ||
} | ||
} | ||
|
||
impl DataStore for MockDataStore { | ||
fn get_transaction_inputs( | ||
&self, | ||
account_id: AccountId, | ||
block_num: u32, | ||
notes: &[NoteId], | ||
) -> Result<TransactionInputs, DataStoreError> { | ||
assert_eq!(account_id, self.account.id()); | ||
assert_eq!(block_num, self.block_header.block_num()); | ||
assert_eq!(notes.len(), self.notes.len()); | ||
|
||
let notes = self | ||
.notes | ||
.iter() | ||
.filter(|note| notes.contains(¬e.id())) | ||
.cloned() | ||
.collect::<Vec<_>>(); | ||
|
||
Ok(TransactionInputs::new( | ||
self.account.clone(), | ||
None, | ||
self.block_header, | ||
self.block_chain.clone(), | ||
InputNotes::new(notes).unwrap(), | ||
) | ||
.unwrap()) | ||
} | ||
|
||
fn get_account_code(&self, account_id: AccountId) -> Result<ModuleAst, DataStoreError> { | ||
assert_eq!(account_id, self.account.id()); | ||
Ok(self.account.code().module().clone()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
use alloc::collections::BTreeMap; | ||
|
||
use miden_lib::transaction::TransactionKernelError; | ||
use miden_objects::accounts::AccountCode; | ||
|
||
use super::{AdviceProvider, Digest, NodeIndex, ProcessState}; | ||
|
||
// ACCOUNT PROCEDURE INDEX MAP | ||
// ================================================================================================ | ||
|
||
/// A map of proc_root |-> proc_index for all known procedures of an account interface. | ||
pub struct AccountProcedureIndexMap(BTreeMap<Digest, u8>); | ||
|
||
impl AccountProcedureIndexMap { | ||
/// Returns a new [AccountProcedureIndexMap] instantiated with account procedures present in | ||
/// the provided advice provider. | ||
/// | ||
/// This function assumes that the account procedure tree (or a part thereof) is loaded into the | ||
/// Merkle store of the provided advice provider. | ||
pub fn new<A: AdviceProvider>(account_code_root: Digest, adv_provider: &A) -> Self { | ||
// get the Merkle store with the procedure tree from the advice provider | ||
let proc_store = adv_provider.get_store_subset([account_code_root].iter()); | ||
|
||
// iterate over all possible procedure indexes | ||
let mut result = BTreeMap::new(); | ||
for i in 0..AccountCode::MAX_NUM_PROCEDURES { | ||
let index = NodeIndex::new(AccountCode::PROCEDURE_TREE_DEPTH, i as u64) | ||
.expect("procedure tree index is valid"); | ||
// if the node at the current index does not exist, skip it and try the next node;this | ||
// situation is valid if not all account procedures are loaded into the advice provider | ||
if let Ok(proc_root) = proc_store.get_node(account_code_root, index) { | ||
// if we got an empty digest, this means we got to the end of the procedure list | ||
if proc_root == Digest::default() { | ||
break; | ||
} | ||
result.insert(proc_root, i as u8); | ||
} | ||
} | ||
Self(result) | ||
} | ||
|
||
/// Returns index of the procedure whose root is currently at the top of the operand stack in | ||
/// the provided process. | ||
/// | ||
/// # Errors | ||
/// Returns an error if the procedure at the top of the operand stack is not present in this | ||
/// map. | ||
pub fn get_proc_index<S: ProcessState>( | ||
&self, | ||
process: &S, | ||
) -> Result<u8, TransactionKernelError> { | ||
let proc_root = process.get_stack_word(0).into(); | ||
// mock account method for testing from root context | ||
// TODO: figure out if we can get rid of this | ||
if proc_root == Digest::default() { | ||
return Ok(255); | ||
} | ||
self.0 | ||
.get(&proc_root) | ||
.cloned() | ||
.ok_or(TransactionKernelError::UnknownAccountProcedure(proc_root)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
use alloc::string::ToString; | ||
|
||
use miden_lib::transaction::TransactionEvent; | ||
use miden_objects::{ | ||
accounts::{delta::AccountVaultDelta, AccountStub}, | ||
Digest, | ||
}; | ||
use vm_processor::{ | ||
crypto::NodeIndex, AdviceExtractor, AdviceInjector, AdviceInputs, AdviceProvider, AdviceSource, | ||
ContextId, ExecutionError, Host, HostResponse, MemAdviceProvider, ProcessState, | ||
}; | ||
|
||
mod account_procs; | ||
pub mod procedures; | ||
pub mod utils; | ||
|
||
use account_procs::AccountProcedureIndexMap; | ||
|
||
// MOCK HOST | ||
// ================================================================================================ | ||
|
||
/// This is very similar to the TransactionHost in miden-tx. The differences include: | ||
/// - We do not track account delta here. | ||
/// - There is special handling of EMPTY_DIGEST in account procedure index map. | ||
/// - This host uses `MemAdviceProvider` which is instantiated from the passed in advice inputs. | ||
pub struct MockHost { | ||
adv_provider: MemAdviceProvider, | ||
acct_procedure_index_map: AccountProcedureIndexMap, | ||
} | ||
|
||
impl MockHost { | ||
/// Returns a new [MockHost] instance with the provided [AdviceInputs]. | ||
pub fn new(account: AccountStub, advice_inputs: AdviceInputs) -> Self { | ||
let adv_provider: MemAdviceProvider = advice_inputs.into(); | ||
let proc_index_map = AccountProcedureIndexMap::new(account.code_root(), &adv_provider); | ||
Self { | ||
adv_provider, | ||
acct_procedure_index_map: proc_index_map, | ||
} | ||
} | ||
|
||
/// Consumes `self` and returns the advice provider and account vault delta. | ||
pub fn into_parts(self) -> (MemAdviceProvider, AccountVaultDelta) { | ||
(self.adv_provider, AccountVaultDelta::default()) | ||
} | ||
|
||
// EVENT HANDLERS | ||
// -------------------------------------------------------------------------------------------- | ||
|
||
fn on_push_account_procedure_index<S: ProcessState>( | ||
&mut self, | ||
process: &S, | ||
) -> Result<(), ExecutionError> { | ||
let proc_idx = self | ||
.acct_procedure_index_map | ||
.get_proc_index(process) | ||
.map_err(|err| ExecutionError::EventError(err.to_string()))?; | ||
self.adv_provider.push_stack(AdviceSource::Value(proc_idx.into()))?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl Host for MockHost { | ||
fn get_advice<S: ProcessState>( | ||
&mut self, | ||
process: &S, | ||
extractor: AdviceExtractor, | ||
) -> Result<HostResponse, ExecutionError> { | ||
self.adv_provider.get_advice(process, &extractor) | ||
} | ||
|
||
fn set_advice<S: ProcessState>( | ||
&mut self, | ||
process: &S, | ||
injector: AdviceInjector, | ||
) -> Result<HostResponse, ExecutionError> { | ||
self.adv_provider.set_advice(process, &injector) | ||
} | ||
|
||
fn on_event<S: ProcessState>( | ||
&mut self, | ||
process: &S, | ||
event_id: u32, | ||
) -> Result<HostResponse, ExecutionError> { | ||
let event = TransactionEvent::try_from(event_id) | ||
.map_err(|err| ExecutionError::EventError(err.to_string()))?; | ||
|
||
if process.ctx() != ContextId::root() { | ||
return Err(ExecutionError::EventError(format!( | ||
"{event} event can only be emitted from the root context" | ||
))); | ||
} | ||
|
||
match event { | ||
TransactionEvent::AccountPushProcedureIndex => { | ||
self.on_push_account_procedure_index(process) | ||
}, | ||
_ => Ok(()), | ||
}?; | ||
|
||
Ok(HostResponse::None) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
use alloc::string::String; | ||
|
||
use miden_lib::transaction::memory::{ | ||
CREATED_NOTE_ASSETS_OFFSET, CREATED_NOTE_METADATA_OFFSET, CREATED_NOTE_NUM_ASSETS_OFFSET, | ||
CREATED_NOTE_RECIPIENT_OFFSET, CREATED_NOTE_SECTION_OFFSET, NUM_CREATED_NOTES_PTR, | ||
}; | ||
use miden_objects::{ | ||
accounts::testing::{prepare_assets, prepare_word}, | ||
transaction::{OutputNote, OutputNotes}, | ||
}; | ||
|
||
pub fn output_notes_data_procedure(notes: &OutputNotes) -> String { | ||
let OutputNote::Full(note0) = notes.get_note(0) else { | ||
panic!("Note 0 must be a full note") | ||
}; | ||
let note_0_metadata = prepare_word(¬e0.metadata().into()); | ||
let note_0_recipient = prepare_word(¬e0.recipient().digest()); | ||
let note_0_assets = prepare_assets(note0.assets()); | ||
let note_0_num_assets = 1; | ||
|
||
let OutputNote::Full(note1) = notes.get_note(1) else { | ||
panic!("Note 1 must be a full note") | ||
}; | ||
let note_1_metadata = prepare_word(¬e1.metadata().into()); | ||
let note_1_recipient = prepare_word(¬e1.recipient().digest()); | ||
let note_1_assets = prepare_assets(note1.assets()); | ||
let note_1_num_assets = 1; | ||
|
||
let OutputNote::Full(note2) = notes.get_note(2) else { | ||
panic!("Note 2 must be a full note") | ||
}; | ||
let note_2_metadata = prepare_word(¬e2.metadata().into()); | ||
let note_2_recipient = prepare_word(¬e2.recipient().digest()); | ||
let note_2_assets = prepare_assets(note2.assets()); | ||
let note_2_num_assets = 1; | ||
|
||
// todo: remove this | ||
const NOTE_MEM_SIZE: u32 = 512; | ||
const NOTE_1_OFFSET: u32 = NOTE_MEM_SIZE; | ||
const NOTE_2_OFFSET: u32 = NOTE_MEM_SIZE * 2; | ||
|
||
format!( | ||
" | ||
proc.create_mock_notes | ||
# remove padding from prologue | ||
dropw dropw dropw dropw | ||
# populate note 0 | ||
push.{note_0_metadata} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{CREATED_NOTE_METADATA_OFFSET} add mem_storew dropw | ||
push.{note_0_recipient} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{CREATED_NOTE_RECIPIENT_OFFSET} add mem_storew dropw | ||
push.{note_0_num_assets} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{CREATED_NOTE_NUM_ASSETS_OFFSET} add mem_store | ||
push.{} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{CREATED_NOTE_ASSETS_OFFSET} add mem_storew dropw | ||
# populate note 1 | ||
push.{note_1_metadata} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{CREATED_NOTE_METADATA_OFFSET} add add mem_storew dropw | ||
push.{note_1_recipient} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{CREATED_NOTE_RECIPIENT_OFFSET} add add mem_storew dropw | ||
push.{note_1_num_assets} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{CREATED_NOTE_NUM_ASSETS_OFFSET} add add mem_store | ||
push.{} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{CREATED_NOTE_ASSETS_OFFSET} add add mem_storew dropw | ||
# populate note 2 | ||
push.{note_2_metadata} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{CREATED_NOTE_METADATA_OFFSET} add add mem_storew dropw | ||
push.{note_2_recipient} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{CREATED_NOTE_RECIPIENT_OFFSET} add add mem_storew dropw | ||
push.{note_2_num_assets} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{CREATED_NOTE_NUM_ASSETS_OFFSET} add add mem_store | ||
push.{} | ||
push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{CREATED_NOTE_ASSETS_OFFSET} add add mem_storew dropw | ||
# set num created notes | ||
push.{}.{NUM_CREATED_NOTES_PTR} mem_store | ||
end | ||
", | ||
note_0_assets[0], | ||
note_1_assets[0], | ||
note_2_assets[0], | ||
notes.num_notes() | ||
) | ||
} |
Oops, something went wrong.