miden_tx/host/
account_procedures.rs

1use alloc::string::ToString;
2
3use miden_lib::transaction::memory::{ACCOUNT_STACK_TOP_PTR, ACCT_CODE_COMMITMENT_OFFSET};
4use miden_lib::transaction::{TransactionAdviceInputs, TransactionKernelError};
5use miden_objects::account::{AccountCode, AccountProcedureInfo};
6use miden_objects::transaction::{TransactionArgs, TransactionInputs};
7use miden_processor::AdviceInputs;
8
9use super::{BTreeMap, Felt, ProcessState, Word};
10use crate::errors::TransactionHostError;
11
12// ACCOUNT PROCEDURE INDEX MAP
13// ================================================================================================
14
15/// A map of maps { acct_code_commitment |-> { proc_root |-> proc_index } } for all known
16/// procedures of account interfaces for all accounts expected to be invoked during transaction
17/// execution.
18pub struct AccountProcedureIndexMap(BTreeMap<Word, BTreeMap<Word, u8>>);
19
20impl AccountProcedureIndexMap {
21    /// Returns a new [AccountProcedureIndexMap] instantiated with account procedures present in
22    /// the provided advice provider.
23    ///
24    /// Note: `account_code_commitments` iterator should include both native account code and
25    /// foreign account codes commitments
26    pub fn new(
27        account_code_commitments: impl IntoIterator<Item = Word>,
28        adv_provider: &AdviceInputs,
29    ) -> Result<Self, TransactionHostError> {
30        let mut result = BTreeMap::new();
31
32        for code_commitment in account_code_commitments {
33            let account_procs_map = build_account_procedure_map(code_commitment, adv_provider)?;
34            result.insert(code_commitment, account_procs_map);
35        }
36
37        Ok(Self(result))
38    }
39
40    /// Builds an [`AccountProcedureIndexMap`] for the specified transaction inputs and arguments.
41    ///
42    /// The resulting instance will map all account code commmitments to a mapping of
43    /// `proc_root |-> proc_index` for any account that is expected to be involved in the
44    /// transaction, enabling easy procedure index lookups on runtime.
45    pub fn from_transaction_params(
46        tx_inputs: &TransactionInputs,
47        tx_args: &TransactionArgs,
48        tx_advice_inputs: &TransactionAdviceInputs,
49    ) -> Result<Self, TransactionHostError> {
50        let mut account_code_commitments = tx_args.to_foreign_account_code_commitments();
51        account_code_commitments.insert(tx_inputs.account().code().commitment());
52
53        Self::new(account_code_commitments, tx_advice_inputs.as_advice_inputs())
54    }
55
56    /// Returns index of the procedure whose root is currently at the top of the operand stack in
57    /// the provided process.
58    ///
59    /// # Errors
60    /// Returns an error if the procedure at the top of the operand stack is not present in this
61    /// map.
62    pub fn get_proc_index(&self, process: &ProcessState) -> Result<u8, TransactionKernelError> {
63        // get current account code commitment
64        let code_commitment = {
65            let account_stack_top_ptr = process
66                .get_mem_value(process.ctx(), ACCOUNT_STACK_TOP_PTR)
67                .expect("Account stack top pointer was not initialized")
68                .as_int();
69            let curr_data_ptr = process
70                .get_mem_value(
71                    process.ctx(),
72                    account_stack_top_ptr
73                        .try_into()
74                        .expect("account stack top pointer should be less than u32::MAX"),
75                )
76                .expect("Current account pointer was not initialized")
77                .as_int();
78            process
79                .get_mem_word(process.ctx(), curr_data_ptr as u32 + ACCT_CODE_COMMITMENT_OFFSET)
80                .expect("failed to read a word from memory")
81                .expect("current account code commitment was not initialized")
82        };
83
84        let proc_root = process.get_stack_word(0);
85
86        self.0
87            .get(&code_commitment)
88            .ok_or(TransactionKernelError::UnknownCodeCommitment(code_commitment))?
89            .get(&proc_root)
90            .cloned()
91            .ok_or(TransactionKernelError::UnknownAccountProcedure(proc_root))
92    }
93}
94
95// HELPER FUNCTIONS
96// ================================================================================================
97
98fn build_account_procedure_map(
99    code_commitment: Word,
100    advice_inputs: &AdviceInputs,
101) -> Result<BTreeMap<Word, u8>, TransactionHostError> {
102    // get the account procedures from the advice_map
103    let proc_data = advice_inputs.map.get(&code_commitment).ok_or_else(|| {
104        TransactionHostError::AccountProcedureIndexMapError(
105            "failed to read account procedure data from the advice provider".to_string(),
106        )
107    })?;
108
109    let mut account_procs_map = BTreeMap::new();
110
111    // sanity checks
112
113    // check that there are procedures in the account code
114    if proc_data.is_empty() {
115        return Err(TransactionHostError::AccountProcedureIndexMapError(
116            "account code does not contain any procedures.".to_string(),
117        ));
118    }
119
120    // check that procedure data have a correct length
121    if proc_data.len() % AccountProcedureInfo::NUM_ELEMENTS_PER_PROC != 0 {
122        return Err(TransactionHostError::AccountProcedureIndexMapError(
123            "account procedure data has invalid length.".to_string(),
124        ));
125    }
126
127    // One procedure requires 8 values to represent
128    let num_procs = proc_data.len() / AccountProcedureInfo::NUM_ELEMENTS_PER_PROC;
129
130    // check that the account code does not contain too many procedures
131    if num_procs > AccountCode::MAX_NUM_PROCEDURES {
132        return Err(TransactionHostError::AccountProcedureIndexMapError(
133            "account code contains too many procedures.".to_string(),
134        ));
135    }
136
137    for (proc_idx, proc_info) in
138        proc_data.chunks_exact(AccountProcedureInfo::NUM_ELEMENTS_PER_PROC).enumerate()
139    {
140        let proc_info_array: [Felt; AccountProcedureInfo::NUM_ELEMENTS_PER_PROC] =
141            proc_info.try_into().expect("Failed conversion into procedure info array.");
142
143        let procedure = AccountProcedureInfo::try_from(proc_info_array)
144            .map_err(TransactionHostError::AccountProcedureInfoCreationFailed)?;
145
146        let proc_idx = u8::try_from(proc_idx).expect("Invalid procedure index.");
147
148        account_procs_map.insert(*procedure.mast_root(), proc_idx);
149    }
150
151    Ok(account_procs_map)
152}