Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend AccountId to two Felts and refactor creation process #982

Open
wants to merge 52 commits into
base: next
Choose a base branch
from

Conversation

PhilippGackstatter
Copy link
Contributor

@PhilippGackstatter PhilippGackstatter commented Nov 25, 2024

What has changed

AccountId previously fit into a felt or ~64 bits. Now its representation is two felts:

pub struct AccountId {
    first_felt: Felt,
    second_felt: Felt,
}

In Rust, the first and second felts are always called as such, typically accessed with id.first_felt() and id.second_felt().
In MASM, the first felt is the hi felt and the second felt is the lo felt and its typical stack representation is [account_id_hi, account_id_lo].

The layout of an ID is:

1st felt: [zero bit | random (55 bits) | storage mode (2 bits) | type (2 bits) | version (4 bits)]
2nd felt: [block_epoch (16 bits) | random (40 bits) | 8 zero bits]

See the AccountId documentation for details on the layout.

There is a type AccountIdPrefix representing the validated first felt of an AccountId which is primarily used for non-fungible assets and for asset deserialization. Ideally we would make that private, but for now it must be public for the constructors of non fungible assets.

Notable Changes

Here is a quick overview of what changes with this PR. Layouts are in "memory order", that is, stack order would be the reverse of that.

  • Fungible Asset representation:
    • previously: [amount, 0, 0, faucet_id]
    • now: [amount, 0, faucet_id_lo, faucet_id_hi]
  • Non-Fungible Asset representation:
    • previously: [hash_0, faucet_id, hash_2, hash_3]
    • now: [hash_0, faucet_id_hi, hash_2, hash_3]
  • NoteMetadata
    • previously: [tag, sender_acct_id, encoded_type_and_ex_hint, aux]
    • now: [sender_hi, sender_lo_type_and_hint_tag, note_tag_hint_payload, aux]
    • The NoteMetadata AfterBlock can only take values less than u32::MAX so that when encoded together with the note tag in a felt (i.e. note_tag_hint_payload above) felt validity is guaranteed.
  • The serialization of AccountIdPrefix is compatible with AccountId to allow for deserialization of a prefix from a serialized AccountId. This is used for the next point.
  • AccountIdPrefix serialization is such that the first byte of the serialized layout contains the metadata. This is used in the asset deserialization to read the type and based on that deserialize the bytes as a fungible or non-fungible asset.
  • The NoteTag::from_account_id effectively takes the most significant bits of id_first_felt << 1. This means the high zero bit is ignored with the rationale being that it would not add any value for matching an account ID against a note tag, since all ID's high bits are zero. Let me know if this doesn't make sense.
  • The previous account creation tests were replaced because they now require a MockChain due to the dependence of the account ID on an anchor block.
  • The layout of account IDs in various keys, stacks and hashes across Rust and Masm is not entirely consistent I think because it's not always quite clear to me whether a layout needs to be reversed or not. What is also unclear to me is in what order IDs should be layed out within layouts like [account_nonce, 0, account_id_hi, account_id_lo]. So anything related to that would be particularly helpful to have reviewed.
  • I snuck in some small improvements to the debugging experience but I'd like to revisit this topic in a future PR.

This PR does not change anything about the account tree, e.g. the accounts: SimpleSmt<ACCOUNT_TREE_DEPTH> field in MockChain.

I think a rough sensible review order would be AccountId + AccountIdPrefix followed by the changes to assets, all in Rust.
Then moving over to MASM.

Open Questions

  • Should we enforce MIN_ACCOUNT_ONES in both first and second felt? For now only the first felt is enforced with the only rationale being that the first felt is used as the key in the asset SMT for fungible assets. But if we treat this as an implementation detail then it might make sense to also enforce something similar on the second felt?
  • Do we need a serialization format for NoteMetadata that is different from the Word encoding? We had a different one before this PR, but now the serialization format and word encoding are identical.

Follow-Up Tasks

There are still some TODOs in the code and those could mostly be addressed in a follow-up PR so this PR could would not necessarily be blocked by that (checked boxes are included in this PR).

  • Introduce AccountIdError. There are now new error conditions for IDs and a separate error might be cleaner.
    • With the introduction of this error it might be possible to make AccountId::new_dummy a const fn and then we could possibly replace the somewhat duplicate functionality of miden_objects::testing::account_id::account_id.
  • Quite a bit of Rust documentation needs to be updated.
  • Implement Display for AccountType and use it in error messages.
  • Remove unnecessary code in generate_account_seed.
  • Remove build.rs constants patching for POW which we no longer have.
  • Move type, storage mode, version out into their own modules to reduce size of account_id.rs.
  • Move newly introduced duplicate procedures into the new util library (after Remove duplicate procedures from miden-lib #1002).
  • Go through error messages and double check if they are up to date (e.g. ones previously mentioning "account id" should now likely mention first or second felt for accuracy).
  • validate_fungible_asset_origin validates the account ID and then calls validate_fungible_asset which validates it again check if we can remove some redundancy here.
  • Deal with NoteExecutionHint::AfterBlock being constructable by users but it now cannot contain u32::MAX and we might want to prevent that.
  • Update and test build_swap_tag.
  • Add lots of new tests for changed things: version and epoch in ID, test new and changed MASM procedures more thoroughly.
  • Think about account ID encoding for network identifier and error detection (e.g. bech32, base58, etc.).

Why it changed

See #140, although I'd like to ideally add a summary here.

closes #140

@PhilippGackstatter PhilippGackstatter force-pushed the pgackst-account-id-refactor branch from e3c2e11 to 70e705d Compare December 2, 2024 16:27
@PhilippGackstatter PhilippGackstatter force-pushed the pgackst-account-id-refactor branch from fab8b03 to f3d649e Compare December 10, 2024 16:30
let err = NonFungibleAsset::read_from_bytes(&asset_bytes).unwrap_err();
assert!(matches!(err, DeserializationError::InvalidValue(_)));
assert_matches!(err, DeserializationError::InvalidValue(msg) if msg.contains("must be of type NonFungibleFaucet"));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: If we were to change DeserializationError::InvalidValue to contain a Box<dyn Error> instead of a String then we could write such tests in a more resilient way, e.g. downcast the error.

@PhilippGackstatter PhilippGackstatter marked this pull request as ready for review December 11, 2024 16:21
Comment on lines 91 to 96
pub fn create_basic_fungible_faucet(
init_seed: [u8; 32],
anchor_block_epoch_and_hash: (u16, Digest),
symbol: TokenSymbol,
decimals: u8,
max_supply: Felt,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a question for the client, @igamigo:

To create an account we now need 1) an anchor epoch and 2) the block hash of the anchor epoch block. (Basically the block hash of block $0 * 2^{16}$, $1 * 2^{16}$, $2 * 2^{16}$, ...). So the epoch and the hash are logically bound to each other. There are generally two ways to specify this in AccountBuilder. Either setting each value individually using AccountBuilder::anchor_epoch and AccountBuilder::anchor_block_hash or specifying both at once by passing a BlockHeader to AccountBuilder::anchor_block_header. The question is what is best to do here for these convenience functions?

Is it fine to require a BlockHeader or would that be too restrictive for some reason? My hunch is that the client or any other real world usage would likely always take these values from a block header anyway, so we might as well make it safer and require it. That would be my preferred way to go.
I'm not a huge fan of the current tuple approach, but it is the most flexible (for tests, etc.). I made it a tuple because of the logical connection between the two so making it two separate arguments is probably worse.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relatedly, I'm also wondering about Account::new. I just fixed it so it compiles, but it takes the anchor epoch and hash separately as well and is probably not very usable as it is. Interestingly, the method is unused in miden-base, miden-client and miden-node as far as I can tell. So I think we can just remove it and point to AccountBuilder for building accounts.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requiring a block header should be fine. Just to clarify, you mean specifically in functions such as create_basic_fungible_faucet(), correct?
On Account::new(), I also agree on removing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, you mean specifically in functions such as create_basic_fungible_faucet(), correct?

Yes, this just relates to create_basic_fungible_faucet and create_basic_wallet.

@bobbinth bobbinth requested a review from Fumuran December 12, 2024 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants