Named Storage Slots #1394
Replies: 4 comments 13 replies
-
|
Very interesting idea! And I think it is very promising. The main concern I have with this is how to make sure slots are indeed named uniquely. We can enforce this at account instantiation time, but not at development time. For example, I could imagine two components (developed by different developers), pick slot name Maybe this could be alleviated with namespacing rules - e.g., the names would need to be something like |
Beta Was this translation helpful? Give feedback.
-
|
Just to write this down, one challenge for a block explorer is that it wants to display all token symbols of all public faucets. Currently, only the basic fungible faucet account component provides such a symbol, but there could be other implementations of faucets as well that lay out their storage differently. With named storage slots we could more easily standardize certain slot names. For example, the storage slot with name Whether the overall idea for sharing faucet metadata is good or not I'm not yet sure about, but the overarching idea for standardization could be one good motivation for named slots. |
Beta Was this translation helpful? Give feedback.
-
Shared Storage SlotsAfter the initial named storage slots PR (#2025) there are different ways in which APIs could work with named storage slots, specifically how to share slots. Sharing a slotLet's take a concrete example. The RPO Falcon512 component and its ACL variant both use a "rpo falcon 512 public key", and so there are two possible setups:
The pros and cons of sharing:
Here, I think the benefits outweigh the downsides and sharing seems like a good idea. It would also be easy to implement sharing to see if there are any unforseen consequences. Depending on a slotThere are two things to point out about the previous example:
For the second scenario, I don't think it makes sense for the rotate component to actually contain the public key slot. This would lead to a situation where building an account would look like this: AccountBuilder::new(...)
.with_auth_component(AuthRpoFalcon512::new(public_key))
.with_component(
AuthRpoFalcon512KeyRotation::new(public_key)
)
.build()?;This creates the base auth component and a rotate component, both with the same slot and the same public key. The account builder would have to merge the two slots (because they have the same name) and ensure their contents match. It is possible, but odd to provide the same parameter twice. Instead, given that the rotate component is more like an extension of an auth component, it seems better if that account component would not contain an actual storage slot but could express a dependency on it. In this case, the rotate component depends on a the presence of a slot with the name This relates to the discussion in #1956, about having a trait that all account components implement. The relevant part of that trait for this functionality could be: pub trait AccountComponentInterface {
/// Returns the slot names on which this account component depends.
///
/// The default implementation returns an empty list.
fn slot_dependencies(&self) -> Vec<StorageSlotName> {
vec![]
}
// other methods ...
}Having this would be useful to avoid building an account with a rotate component but without any component that actually contains the storage slot it needs. Another use case for this pattern is to put getters (for use externally or via FPI) for faucet metadata like In general, to make a storage slot of an account A accessible via FPI for an account B, a well-defined interface is needed, and the way to define such an interface could be through such "extension components". I think this is useful, but I also can't think of enough use cases to be completely sure, yet. Conversely, I cannot think of a case where two components would contain the same slot and merging them would be required, though if you can think of one, let me know. Some context that spawned this: |
Beta Was this translation helpful? Give feedback.
-
|
Closing discussion as it was implemented in #1724. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I'm wondering if the public interface of an account should be that storage slots are named instead of indexed. For example, use a felt-encoded representation of the string
"miden.basic_fungible_faucet"to access the metadata storage slot of theBasicFungibleFaucetcomponent instead of index0. In Rust APIs we could use the actualString(or a wrapper type to be precise), but in MASM, due to lack of native strings, we'd have to use a felt-encoded string.Concretely:
Motivation
The motivation is that with such an interface, I think we could solve two issues:
push.SLOT_IDX exec.account::get_itemwhich will result in the same procedure root ifSLOT_IDXis the same (e.g.0). That in turn is not allowed in account components because the procedures in those components should have different storage offsets. But since we can only set one offset per MAST root, it must be disallowed.NetworkAccountenum #1275 (comment)).Design
Memory Representation
Storage slots are currently represented in TX kernel memory as:
We can repurpose those three zero felts (or even more bits from the felt in which the storage_slot_type is stored) to store an encoded string, similar to the
TokenSymbolencoding (but this is to be discussed). One felt can store 13 characters (without encoding the length), so three felts would give us 39 characters, which should be enough. These would effectively add 24 bytes of storage per account slot.If we add
.and_to the alphabet, then we can represent slightly fewer characters but could then use names likemiden.basic_fungible_faucetto identify the storage slot of the basic fungible faucet. Recommending account component authors to namespace their slot names would be a good idea for uniqueness.All MASM code would use the encoded word as a key to lookup a storage slot. This would result in unique roots for getter-like procedures.
Storage creation
The kernel should guarantee when the account storage is created (i.e. on new accounts) or updated that each name is unique. That then guarantees that each
name <-> indexmapping is unique in both directions.Removing storage offsets/sizes
At the same time, I believe we could get rid of
AccountProcedureInfoper code procedure (which is 34 bytes) because we would no longer need storage offsets or storage sizes. The primary purpose of these, I think, is to avoid out of bounds access and prevent access from one component's storage to another's. Out of bounds is not really a concern when using names (though we do have to handle lookups of non-existent slot names), and preventing multiple components from accessing the same slot is currently not protocol-enforceable anyway, because storage offsets/sizes are user-defined in the first place.The advice provider data (
CODE_COMMITMENT -> [[ACCOUNT_PROCEDURE_DATA]]) should be extractable from theMastForestinAccountCodeand theAccountProcedureIndexMapshould work the same (just without storage offsets/size), but I haven't verified this fully yet.Lookup
The kernel would store the slots like it does now. In order to map a requested name to the index, it would emit an event upon which the transaction host would push the index of the storage slot onto the advice stack. The kernel reads it from there, looks up the slot and verifies that the name of the slot matches the requested one. That way a lookup is not more expensive than it is now.
Deny reserved slot access
It's not clear yet how many reserved slots we'll have or if access to all of them must be denied. But assuming there is more than one such slot, on every access, we could run the following procedure:
This is easier to do with names than with indices, because the slots may not always be present in the first place. E.g. for regular accounts we do not have to deny access to slot 0, but we do for faucet accounts. With names, we can deny access by name which does not require additional checks.
In Practice
In practice, for MASM users, I imagine they would choose a storage slot name, use a Rust API to convert that string to its
Wordrepresentation and then hardcode this word into the MASM code.For compiler users, I assume they would be able to use the Rust APIs of the
SlotNametype we would define. That could look something like this for the basic fungible faucet:That would effectively convert the slot name into its Word representation so that users do not have to do any of the manual work.
Conclusion
Upsides:
AccountProcedureInfo.miden.network_account_note_rootsinstead of having to figure out which storage slot index contains the roots. It doesn't matter whether it is a reserved slot or a component slot.Downsides:
get_{map}_itemandset_{map}_itemis arguably harder to work with as it needs a slot name of typeWordrather than just a zero-based index.set_itemsignature currently is:Inputs: [index, V']Outputs: [V]Inputs: [SLOT_NAME, V']Outputs: [V]Alternatives
I thought about whether using the hash of a string like
"miden.basic_fungible_faucet"would make sense. That would have the same probability of a collision, but would not be decodable. So for introspection purposes it is much nicer if the name encodes a word which can be reversed and displayed.Beta Was this translation helpful? Give feedback.
All reactions