Conversation
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! Not a full review, but I left some comments inline.
| pub struct AccountComponentCode { | ||
| mast: Arc<MastForest>, | ||
| exports: Vec<ProcedureExport>, | ||
| } |
There was a problem hiding this comment.
Do we actually need to store full ProcedureExport here? I guess we use these to build Library out of account component node - but I wonder if that's actually needed.
The alternative could be to follow the pattern we have in NoteScript - e.g., this could look like:
pub struct AccountComponentCode {
mast: Arc<MastForest>,
exports: Vec<MastNodeId>,
}There was a problem hiding this comment.
Currently, we have the following pattern for component code:
static BASIC_WALLET_LIBRARY: LazyLock<Library> = ...
pub fn basic_wallet_library() -> Library {
BASIC_WALLET_LIBRARY.clone()
}
procedure_digest!(
BASIC_WALLET_RECEIVE_ASSET,
BasicWallet::NAME,
BasicWallet::RECEIVE_ASSET_PROC_NAME,
basic_wallet_library
);
impl BasicWallet {
pub const NAME: &'static str = "miden::standards::components::wallets::basic_wallet";
const RECEIVE_ASSET_PROC_NAME: &str = "receive_asset";
pub fn receive_asset_digest() -> Word {
*BASIC_WALLET_RECEIVE_ASSET
}
}While thinking about a trait AccountComponentInterface, I thought it would make more sense if we would actually use AccountComponentCode instead of Library and make the code accessible on the component itself, so:
static BASIC_WALLET_CODE: LazyLock<AccountComponentCode> = LazyLock::new(|| {
let bytes = include_bytes!(concat!(
env!("OUT_DIR"),
"/assets/account_components/wallets/basic_wallet.masl"
));
let library =
Library::read_from_bytes(bytes).expect("Shipped Basic Wallet library is well-formed");
AccountComponentCode::from(library)
});
impl BasicWallet {
pub fn code() -> &'static AccountComponentCode {
&BASIC_WALLET_CODE
}
}This would also mean changing procedure_digest to take AccountComponentCode as an input instead of a Library, so it would need to have the ability to call get_procedure_root_by_path.
That would be cleaner and increase type safety - we'd get more out of the AccountComponentCode type. Maybe this is still possible with what you proposed. Just mentioning that this would be nice to be able to do eventually and the changes here would have to be compatible with that.
| pub fn get_procedure_root_by_path(&self, path: impl AsRef<Path>) -> Option<Word> { | ||
| let path = path.as_ref().to_absolute(); | ||
| self.exports | ||
| .iter() | ||
| .find(|export| export.path.as_ref() == path.as_ref()) | ||
| .map(|export| self.mast[export.node].digest()) | ||
| } |
There was a problem hiding this comment.
Similar to the previous comment: is this used anywhere outside of tests?
| /// Creates a new [`AccountComponentCode`] from the provided MAST forest and procedure exports. | ||
| pub fn new(mast: Arc<MastForest>, exports: Vec<ProcedureExport>) -> Self { | ||
| Self { mast, exports } | ||
| } |
There was a problem hiding this comment.
We should probably check consistency here between mast and exports to make sure the exported procedures are actually in the MAST forest.
Now that I think about it, we should probably do the same for NoteScript::new() constructor.
Also, it may be good to add AccountComponentCode::from_parts(), AccountComponentCode::from_library(), and maybe AccountComponentCode::from_library_reference() to follow the pattern we have with NoteScript.
PhilippGackstatter
left a comment
There was a problem hiding this comment.
Looks good to me!
| impl From<&AccountComponentCode> for Library { | ||
| fn from(value: &AccountComponentCode) -> Self { | ||
| let exports: BTreeMap<_, _> = | ||
| value.exports.iter().cloned().map(|e| (e.path.clone(), e.into())).collect(); | ||
|
|
||
| Library::new(value.mast.clone(), exports) | ||
| .expect("AccountComponentCode should have at least one export") |
There was a problem hiding this comment.
I generally think we should not implement conversion using references when the implementation clones. In such cases, I think it's better to make the clone explicit to the caller instead of hiding it, e.g. Library::from(component_code.clone()).
Implementing both APIs could easily lead to a clone even when one actually has an owned value that could be moved.
There was a problem hiding this comment.
Yeah, sorry about this one, you've mentioned this before. I think I was mostly focused in keeping the API close to what it was before (mainly for linking in CodeBuilder)
…/protocol into igamigo-destructure-code
PhilippGackstatter
left a comment
There was a problem hiding this comment.
Left two comments regarding no longer having AsRef<Library> and the from_library_reference implementation.
| /// Returns a new [`AccountComponentCode`] containing only external node references to the | ||
| /// procedures in the provided [`Library`]. | ||
| /// | ||
| /// Note: This method creates a minimal [`MastForest`] where each exported procedure is | ||
| /// represented by an external node referencing its digest, rather than copying the entire | ||
| /// library's MAST forest. The actual procedure code will be resolved at runtime via the | ||
| /// `MastForestStore`. | ||
| pub fn from_library_reference(library: &Library) -> Self { |
There was a problem hiding this comment.
Do we not need the full account code at the component level, so that when we create an Account from an AccountBuilder, that account has the full code and not just references?
Or in other words, how would the Account supply its code at tx execution time then? I think the following functions rely on the full code being present:
LocalTransactionProver::proveAccountDeltaTracker::newTransactionContextBuilder::buildbeing an example for how users of theTransactionExecutorprobably inserts code, at least that's my understanding.
So, I think having a function similar to NoteScript::from_library_reference does not make sense for account component code, since we're constructing a library and not an executable.
Also, I think a better name for NoteScript::from_library_reference would be from_library_procedure to better capture that we're creating a script from a procedure in a library.
crates/miden-protocol/src/lib.rs
Outdated
| pub use miden_assembly::ast::{Module, ModuleKind, ProcedureName, QualifiedProcedureName}; | ||
| pub use miden_assembly::debuginfo::SourceManagerSync; | ||
| pub use miden_assembly::library::LibraryExport; | ||
| pub use miden_assembly::library::{LibraryExport, ProcedureExport as LibraryProcedureExport}; |
There was a problem hiding this comment.
What is the reason that we need this rename? I think, ideally, we'd keep the original name as having two names for the same thing often leads to confusion and I recall having IDE import problems with the Hasher type (which is also renamed and I wish we would remove that).
| let note_script = CodeBuilder::default() | ||
| .with_dynamically_linked_library(custom_component.component_code()) | ||
| .with_dynamically_linked_library(custom_component.component_code().clone()) |
There was a problem hiding this comment.
I wonder if the destructuring of AccountComponentCode into its parts is worth these clones. These are necessary because we no longer have impl AsRef<Library> for AccountComponentCode, afaict.
On a conceptual level, an account component's code is a Library (contrary to NoteScript or TransactionScript which are built from libraries but are executables) and dynamically linking a library only requires an AsRef<Library> so the clone is "conceptually" unnecessary.
The clone is cheap for the mast forest, and not too bad for the exports I suppose. Still, the previous structure provided better usability on this front.
How much are we actually saving in terms of space with the new structure, which I think is the motivation for the refactor, right?
There was a problem hiding this comment.
Yes, the clones are indeed necessary because AsRef is no longer possible/appropriate. And to be honest, I'm not entirely sure if there's a specific motivation for the refactor other than reducing some space (though it's minimal and the remainder still dominates).
There was a problem hiding this comment.
One of the motivations is that Library struct will go away soon (it will be replaced by Package struct). But putting a full Package into the AccountComponentCode seems like an overkill.
I think my main question is whether we need AccountComponentCode to be convertible into libraries. AFAICT, this is only used in test code (but maybe I missed something?). If so, maybe there is some other solution that that we could apply to tests specifically?
Alternatively, we can delay this change until the Library struct actually goes away, and for now apply some of the more straight-forward changes that @igamigo mentioned in #2597 (comment).
clear_debug_info()(would need upstream support onLibraryAFAICT)
If we no longer deal with Library structs, we should be able to do this on MastForest directly.
|
With the conceptual difference between note/transaction scripts versus account component code (the scripts are executables, and the latter is a library) API being less ergonomic overall and with minimal space savings being won from this refactor, it might be worth revising whether this is a good refactor. We could still decide to keep some of the changes in this PR even if we wanted to discard the main change, such as:
|
Closes #2174