Implement `AccountComponentInterface` trait
- Ngôn ngữ chính
- Rust
- Star
- 132
- Fork
- 167
- Merge trung bình
- 1 ngày 23 giờ
- Pull request đã merge (30 ngày)
- 110
Mô tả
We should remove the current `AccountComponentInterface` and replace it with a `trait` of the same name.
This came up in a few places:
- https://github.com/0xMiden/protocol/discussions/1394#discussioncomment-15169825
- https://github.com/0xMiden/protocol/issues/1956#issuecomment-3602089531
- https://github.com/0xMiden/protocol/issues/2456#issuecomment-3919325695
Essentially, the requirements I think are:
- We want to be able to express dependencies between components. For example, `NetworkFungibleFaucet` depends on `OwnerControlled` being present (in the future, probably called `MintPolicyManager`).
- We want to make reconstruction of account components `Account`, `PartialAccount` or `AccountReader` (in the client) possible. A component should be successfully reconstructable if:
- All storage slots are present and their contents deserialize correctly into the storage of the component.
- All account procedures of the component are present in the account.
## Dependencies
Expressing dependencies is primarily useful so that the `AccountBuilder`, that combines all components into an `Account`, can check that the dependencies are fulfilled.
The way to specify dependencies is via the names of the components that `self` depends on.
## Reconstruction
We only want to make reconstruction of components from an account _possible_, but not _mandatory_. For example, we recently discussed that the agglayer bridge could grow quite large and we would only want to deserialize a part of the component, which would necessarily have to be a custom method and so nothing we can or should generalize.
Hence, mandating a general `try_from_interface(interface: AccountInterface, storage: &impl AccountStorageInterface)` in the trait does not make sense as it could not be sensibly implemented by all components. This pattern should still be implemented by most of the standard components. We could consider encapsulating this in a separate, optional trait.
So, we want to support reconstruction and to do this we need:
- The ability to read from storage.
- The ability for a component to check if all of its procedures are contained in the account's interface.
The first item, the above-mentioned `AccountStorageInterface` is an abstraction over storage and the topic of a separate issue (https://github.com/0xMiden/protocol/issues/2623).
The second item can be done type-safely with some changes to `AccountComponentCode`:
```rust
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccountComponentCode(Library);
impl AccountComponentCode {
pub fn procedures(&self) -> impl Iterator {
self.0.mast_forest().procedure_digests().map(AccountProcedureRoot::from_raw)
}
}
```
See below for usage. Ideally, this is still possible after https://github.com/0xMiden/protocol/issues/2174, cc @igamigo.
The `AccountInterface` is also changed to simply contain the procedure roots of the account:
```rust
pub struct AccountInterface {
account_id: AccountId,
procedures: BTreeSet,
}
impl AccountInterface {
/// Returns `true` if the proivided set of procedures are contained in this account,
/// `false` otherwise.
pub fn contains(&self, procedures: impl Iterator) -> bool {
todo!()
}
}
impl From<&Account> for AccountInterface { ... }
impl From<&PartialAccount> for AccountInterface { ... }
```
## Trait
The overall trait then looks rather simple:
```rust
pub trait AccountComponentInterface: Into {
/// Returns the names of other account components on which this account component depends.
///
/// By default, a component has no dependencies.
fn dependencies(&self) -> impl Iterator {
[].into_iter()
}
}
```
`AccountComponentName` is a thin wrapper around a string for which we should probably enforce some basic constraints (very similar or maybe identical to the constraints of `StorageSlotName`).
## Example
Consider the `NetworkFungibleFaucet` as an example:
```rust
// ======= Example: AccountComponentInterface for NetworkFungibleFaucet =======
impl NetworkFungibleFaucet {
pub fn name() -> AccountComponentName {
AccountComponentName(
"miden::standards::components::faucets::network_fungible_faucet".to_owned(),
)
}
/// Returns the [`AccountComponentCode`] of this account component.
pub fn code() -> &'static AccountComponentCode {
todo!(
"change network_fungible_faucet_library (and all similar procedures) to return component code"
)
}
pub fn is_compatible_with(interface: AccountInterface) -> bool {
interface.contains(Self::code().procedures())
}
// Until the interface trait exists, storage would be &AccountStorage.
pub fn try_from_interface(
interface: AccountInterface,
storage: &impl AccountStorageInterface,
) -> Result {
if !Self::is_compatible_with(interface) {
return Err(AccountError::other("MissingNetworkFungibleFaucetInterface"));
}
todo!("deserializing storage is the topic of a different issue")
}
}
impl AccountComponentInterface for NetworkFungibleFaucet {
fn dependencies(&self) -> impl Iterator {
[AccountComponentName(OwnerControlled::NAME.to_owned())].into_iter()
}
}
```
So:
- `NetworkFungibleFaucet::dependencies` returns `OwnerControlled`.
- We use the `AccountProcedureRoot` wrapper for type safety with procedure roots.
## Design Notes
- We could add a lot more methods to the trait, but:
- We only require the functionality we really need for abstracting over components, i.e. all components need to be convertible into `AccountComponent`, and all of them need to be able to express dependencies.
- For example:
- Not all components may be reconstructable from a full account.
- We don't need to access a component's code abstractly, so there is no `fn code() -> AccountComponentCode` in the trait.
- We currently have `AccountInterface::auth` that is no longer present in the new definition. That's because:
- `AuthMethod` is a standard and `AccountInterface` should live in `miden-protocol`. In other words, `AuthMethod` is not general enough.
- There are better and more flexible ways to get a component's public keys: we can reconstruct the auth component, e.g. `AuthSingleSig::try_from_interface` and access its public keys. Still, this part would be good to validate before starting implementation, pinging @igamigo since I think this is important for the client.
- `dependencies` takes `self` as that allows components to return different dependencies based on how they are configured.
## Replacing existing usages
`AccountComponentInterface` is currently used for checking compatibility of a note with an account in `StandardNote::is_compatible_with`, but I mentioned here (https://github.com/0xMiden/protocol/issues/2035#issuecomment-4046612122) why this is probably not necessary anymore.
There is a use of the current `enum AccountComponentInterface` in `AccountInterface::build_send_notes_script`.
Firstly, this functionality necessarily lives in `miden-standards`, so must become an extension trait for `AccountInterface`, or encapsulated in another way.
Secondly, we directly check `matches!(component_interface, AccountComponentInterface::BasicFungibleFaucet)`, but this could be done by checking interface compatibility using `BasicFungibleFaucet::is_compatible_with` (analogous to `NetworkFungibleFaucet`).
I believe the remaining usages should be replaceable in straightforward ways, but it is difficult to check holistically.
## Implementation
To implement this, I think a rough sensible order would be the following, where roughly each bullet point could be a PR:
- Change account component libraries to return `AccountComponentCode`, and make these inherent methods of the component (explained in https://github.com/0xMiden/protocol/pull/2597#discussion_r2947004424).
- Change `procedure_digest!` to take `AccountComponentCode` instead of `Library` and return `AccountProcedureRoot` instead of `Word`.
- Not mandatory, but it'd be nice to make the procedure roots in `AuthMultisigConfig` also be `AccountProcedureRoot` instead of `Word`.
- Remove `StandardNote::is_compatible_with` and everything that is no longer needed as part of it.
- Introduce new versions of `AccountInterface` and `AccountComponentInterface` under different names and implement them for standard components.
- Remove old versions of the `AccountInterface` and `AccountComponentInterface`.
- Remove `StandardAccountComponent`.
- Remove `push.0 drop` in `BasicFungibleFaucet` code, see https://github.com/0xMiden/protocol/pull/2559#discussion_r2939689302.
- Change `AccountBuilder::with_component` to take `impl AccountComponentInterface` and implement dependency checking.
- Remove `AuthMethod` enum (https://github.com/0xMiden/protocol/pull/2900#discussion_r3238390117).
Hướng dẫn đóng góp
Đánh giá
Issue này chưa được đánh giá.