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

Extract notes via events issue #539 #572

Merged
merged 1 commit into from
Apr 9, 2024
Merged

Extract notes via events issue #539 #572

merged 1 commit into from
Apr 9, 2024

Conversation

hackaugusto
Copy link
Contributor

@hackaugusto hackaugusto commented Apr 4, 2024

This PR is a little big, but mostly because of renaming of some of the existing structs.

  • Removed the ToEnvelope trait in favor of a simpler enum.
  • Rename Note to NoteConsumable. The goal is to make it a bit more clear how this differs from the other note types
    • Split the recipient off to a NoteRecipient. The goal was to use this type to parse data from the advice map, in the event handler. Currently there isn't enough infrastructure to do that with traits, and because of time constraints I didn't add it. Nevertheless the separation aligns the struct type more closely to their semantics, and should make the code easier to follow, so I kept it around.
    • Rename NoteAssets to NoteVault because we have been mixing the terms. I kinda regret this change, but rolling back seems like twice the work for no gain.
  • Changes the kernel code to emit an event, and an event handler to create the note
    • The event handler checks the advice map, if there is data to create a consumable note in there, it is read. Otherwise a private note is created
    • The above does not check the note's OnChain flag. That should probably be added with a follow up.
  • Adds serialization of miden code to and from Felts, so that is can be encoded and decoded to/from the advice map

@hackaugusto hackaugusto changed the title Extract notes via evnets issue 539 Extract notes via events issue #539 Apr 5, 2024
@hackaugusto hackaugusto force-pushed the hacka-issue-539 branch 3 times, most recently from ebea437 to e1b7693 Compare April 5, 2024 21:25
@hackaugusto hackaugusto marked this pull request as ready for review April 5, 2024 21:33
@@ -9,7 +9,7 @@ members = [

[workspace.package]
edition = "2021"
rust-version = "1.75"
rust-version = "1.77"
Copy link
Contributor Author

@hackaugusto hackaugusto Apr 6, 2024

Choose a reason for hiding this comment

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

Bumped because split_first_chunk was stabilized on 1.77. This API is used together with u32::from_le_bytes to encode the NoteScript to be used with the advice_map.

}

impl fmt::Display for TransactionKernelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidStorageSlotIndex(index) => {
TransactionKernelError::InvalidStorageSlotIndex(index) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Self:: confused the rust-analyzer and broke the suggested actions. I have done similar changes in other places, where use TransactionKernelError::* caused issues (namely, autocompletion is gone, actions don't work)


/// Extracts information from the process state about the storage slot being updated and
/// records the latest value of this storage slot.
pub(super) fn on_account_storage_set_item<S: ProcessState>(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I put the handlers together, it makes it easier to follow the code.

@@ -53,17 +51,13 @@ pub fn mock_inputs_with_account_seed(
},
};

// mock notes
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These comments didn't add context, the function being called is already named mock_notes.

Comment on lines -27 to -31
/// The metadata consists of:
/// - sender is the ID of the account which created the note.
/// - note_type defines how the note is to be stored (e.g., on-chain or off-chain).
/// - tag is a value which can be used by the recipient(s) to identify notes intended for them.
/// - aux is arbitrary user-defined value.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved the documentation to the fields. Having the comments along side thing they refer to makes it easier to update when changes are done.

Copy link
Contributor

Choose a reason for hiding this comment

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

There is one downside to this: the generated documentation will no longer include this info because the fields are private. So, one would be forced to look at the source code to understand what the metadata consists of. That was one of the main reasons why we originally put the description of fields into struct comments.

We could still keep the comments for each specific filed, but I think we also need similar detail in the struct comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that is a problem. If the attribute is private, the docs should not be shown because it is not part of the public API. The user has to rely on the public API, in this case the accessor methods (they have documentation).

Hasher::merge(&[merge_script, inputs.commitment()])
}

impl Note {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed to NoteConsumable and moved to the consumable module. For our current usage, Note is too generic, the new name should make a bit cleared that this structure is used to hold the details necessary to consume a note as an input.

Comment on lines +21 to +22
serial_num: Word,
script: NoteScript,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should probably be put into a NoteNullifier, but this PR was too big already.

/// When a note is produced in a transaction, the note's recipient, assets, and metadata must be
/// known. However, other information about the note may or may not be know to the note's producer.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OutputNote {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Remove in favor of an enum of NoteConsumable and NoteEnvelope, which correspond to public and private notes.

output_notes: OutputNotes<NoteEnvelope>,

/// Optionally the output note's data, used to share the note with the network.
output_note_details: BTreeMap<NoteId, Note>,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is no longer necessary, the enum gives better type safety since the data is not split into two places.

.filter(|&k| !known_output_ids.contains(k))
.cloned()
.collect();
if !unknown_ids.is_empty() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an example of improved type safety, the check is no longer necessary, because the data is no longer split into two places.

@@ -31,17 +34,19 @@ pub fn create_p2id_note<R: FeltRng>(
assets: Vec<Asset>,
note_type: NoteType,
mut rng: R,
) -> Result<Note, NoteError> {
) -> Result<NoteConsumable, NoteError> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think I disagree slightly with this name change. I feel like Note/InputNote/NoteMetadata are good matches for what they represent already, and maybe Consumable collides a little bit semantically with Input? Don't feel strongly and this was likely discussed somewhere else, so I wouldn't mind either way but just my two cents.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you look at Note/InputNote/NoteMetadata alone, yes. But we also have NoteEnvelope and NoteOutput. In comparison to these, it is not clear at all how do they differ from plain Note.

I'm happy to change this, but I have no better suffix, any suggestions?

Comment on lines 27 to 29
/// For the purposes of this struct, anything that can be reduced to a note envelope can be an
/// output note. However, [ToEnvelope] trait is currently implemented only for [OutputNote] and
/// [NoteEnvelope], and so these are the only two allowed output note types.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like this doc comment might be outdated.

Comment on lines 93 to 107
if data.len() != 12 {
return Err(TransactionKernelError::MalformedRecipientData(data.to_vec()));
}
let inputs_hash = Digest::new([data[0], data[1], data[2], data[3]]);
let script_root = Digest::new([data[4], data[5], data[6], data[7]]);
let serial_num = [data[8], data[9], data[10], data[11]];
let input_els = self.adv_provider.get_mapped_values(&inputs_hash);
let script_data = self.adv_provider.get_mapped_values(&script_root).unwrap_or(&[]);

let inputs = NoteInputs::new(input_els.map(|e| e.to_vec()).unwrap_or_default())
.map_err(TransactionKernelError::MalformedNoteInputs)?;
let script = NoteScript::try_from(script_data)
.map_err(|_| TransactionKernelError::MalformedNoteScript(script_data.to_vec()))?;
let recipient = NoteRecipient::new(serial_num, script, inputs);
NoteOutput::Public(NoteConsumable::new(vault, metadata, recipient))
Copy link
Collaborator

@igamigo igamigo Apr 6, 2024

Choose a reason for hiding this comment

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

Should these if branches validate the note type (or maybe the if should be based on note_type)? Feels like if there is some corruption up to this point, you could get a NoteOutput::Public() out of this function that contains a NoteType::OffChain on its metadata.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added check for public note

Comment on lines 12 to 15
pub enum NoteOutput {
Public(NoteConsumable),
Private(NoteEnvelope),
}
Copy link
Collaborator

@igamigo igamigo Apr 6, 2024

Choose a reason for hiding this comment

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

Similarly (or alternatively) to the other note type validation comment, it might be a good idea to validate on creation that NoteOutput::Private(envelope) cannot contain an envelope with NoteType::Onchain and the reverse for NoteOutput::Public (unless there's some use-case I'm missing)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added validation

Copy link
Contributor

@bobbinth bobbinth left a comment

Choose a reason for hiding this comment

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

Looks great! Thank you! This is not a full review, but I reviewed probably 90% of the code (the only thing left are some part os the changes to the transaction host).

I left some comments inline - but they are mostly related not naming/code organization.

Comment on lines -27 to -31
/// The metadata consists of:
/// - sender is the ID of the account which created the note.
/// - note_type defines how the note is to be stored (e.g., on-chain or off-chain).
/// - tag is a value which can be used by the recipient(s) to identify notes intended for them.
/// - aux is arbitrary user-defined value.
Copy link
Contributor

Choose a reason for hiding this comment

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

There is one downside to this: the generated documentation will no longer include this info because the fields are private. So, one would be forced to look at the source code to understand what the metadata consists of. That was one of the main reasons why we originally put the description of fields into struct comments.

We could still keep the comments for each specific filed, but I think we also need similar detail in the struct comment.

objects/src/notes/recipient.rs Outdated Show resolved Hide resolved
objects/src/notes/script.rs Show resolved Hide resolved
Comment on lines 20 to 21
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct NoteVault {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would probably roll-back this change (would it be a simple find/replace?). One of the reasons is that we have a generic AssetVault (which is used by accounts, but also in other contexts), and having NoteVault while not having AccountVault is a bit confusing, IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed

Comment on lines 10 to 15
/// The types of note outputs supported by the transaction kernel.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NoteOutput {
Public(NoteConsumable),
Private(NoteEnvelope),
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the idea of using an enum instead of ToEnvelope a lot! One comment: why not just transform the existing OutputNote into the enum? Generally, I have two comments here:

  • I think NoteOutput name is a bit confusing (it may be interpreted as something that is an output of a note) - so, I would prefer OutputNote.
  • I would move this enum to the transactions module as it is related to transaction execution.

So, basically, I would rename this enum into OutputNote and move it into src/transaction/outputs.rs.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another thing to consider in a future PR: I'm not sure the variant names here is what we want to have long term. Specifically, we may know full details of a private note, and it would make sense to be able to keep track of that. So, maybe the variant names should be something like:

pub enum OutputNote {
    Full(Note),
    Minimal(NoteEnvelope), // not a great name but couldn't come up with anything better
}

Let's create an issue to come back to this later.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's create an issue for refactoring the OutputNote along the lines of what I described above.

objects/src/notes/output.rs Outdated Show resolved Hide resolved
Comment on lines 13 to 36
/// A note with all the data required for it to be consumed by executing it against the transaction
/// kernel.
///
/// Notes are created with a script, inputs, assets, and a serial number. Fungible and non-fungible
/// asset transfers are done by moving assets to the note's vault. The note's script determines the
/// conditions required for the note consumpution, i.e. the target account of a P2ID or conditions
/// of a SWAP, and the effects of the note. The serial number has a double duty of preventing double
/// spend, and providing unlikability to the consumer of a note. The note's inputs allow for
/// customization of its script.
///
/// To create a note, the kernel does not require all the information above, a user can create a
/// note only with the commitment to the script, inputs, the serial number, and the kernel only
/// verifies the source account has the assets necessary for the note creation. See [NoteRecipient]
/// for more details.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct NoteConsumable {
vault: NoteVault,
metadata: NoteMetadata,
recipient: NoteRecipient,

id: NoteId,
nullifier: Nullifier,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the structural changes here - but I'm not so sure about the naming. First, I think ConsumableNote would probably be a better name than NoteConsumable, but more importantly, I'm not sure we need to make this renaming at this point in time.

There are a couple of reasons for this:

  1. I think we need to have a note object which is independent of the transaction context.
  2. We need to break out note metadata out of here, and I'd like to revisit note object naming more comprehensively as a part of that change (mostly because we have too many variations of "note" objects floating around across miden base and miden node).

So, my preference here would be to keep the structural changes but rename this back into Note and move the code into src/notes/mod.rs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IMO we should stop putting stuff in the mod.rs, specially when we have sub modules. There is no need to put everything in the mod.rs, and having sub mods makes it easier to navigate the source code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed and moved to mod.rs

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO we should stop putting stuff in the mod.rs, specially when we have sub modules. There is no need to put everything in the mod.rs, and having sub mods makes it easier to navigate the source code.

I'm not opposed to this. Sometimes, when the inner and outer module names are the same, clippy may give an "inception" warning - but I don't think that's the case here (because notes and note would be different).

objects/src/notes/consumable.rs Outdated Show resolved Hide resolved
miden-tx/src/tests.rs Outdated Show resolved Hide resolved
Comment on lines 41 to 49
pub struct TransactionHost<A> {
adv_provider: A,
account_delta: AccountDeltaTracker,
state_tracker: StateTracker,
acct_procedure_index_map: AccountProcedureIndexMap,
notes: Vec<NoteOutput>,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The struct doc comments need to be updated.

@hackaugusto hackaugusto force-pushed the hacka-issue-539 branch 2 times, most recently from 085b12f to 1162f16 Compare April 8, 2024 16:24

assert_eq!(executed_transaction.final_account().hash(), tx_outputs.account.hash());
assert_eq!(executed_transaction.output_notes(), &tx_outputs.output_notes);
}

#[test]
#[ignore]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We decided to skip the tests for the moment. This will be fixed in a follow up.

@hackaugusto hackaugusto dismissed bobbinth’s stale review April 8, 2024 16:29

applied requested changes

@hackaugusto hackaugusto requested review from igamigo and bobbinth April 8, 2024 16:29
Copy link
Contributor

@bobbinth bobbinth left a comment

Choose a reason for hiding this comment

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

Looks good! Thank you! I left more comments inline - but pretty much all of them are nits. After these are addressed, we are good to merge.

objects/src/notes/mod.rs Outdated Show resolved Hide resolved
objects/src/notes/nullifier.rs Outdated Show resolved Hide resolved
objects/src/transaction/outputs.rs Outdated Show resolved Hide resolved
miden-lib/src/transaction/inputs.rs Outdated Show resolved Hide resolved
miden-tx/tests/integration/scripts/swap.rs Outdated Show resolved Hide resolved
miden-lib/asm/miden/kernels/tx/tx.masm Outdated Show resolved Hide resolved
Comment on lines +110 to +112
let script = NoteScript::try_from(script_data)
.map_err(|_| TransactionKernelError::MalformedNoteScript(script_data.to_vec()))?;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is fine for now, but in the future, we'll also need to validate that the script data is consistent with script hash. This will become simpler once we move to MAST-based script serialization (i.e., we will not need to compile the script). Let's add a TODO about it here and also create an issue to come back to this later.

miden-tx/src/host/state_tracker.rs Outdated Show resolved Hide resolved
miden-tx/src/host/state_tracker.rs Outdated Show resolved Hide resolved
Comment on lines 10 to 15
/// The types of note outputs supported by the transaction kernel.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NoteOutput {
Public(NoteConsumable),
Private(NoteEnvelope),
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's create an issue for refactoring the OutputNote along the lines of what I described above.

@Dominik1999
Copy link
Collaborator

Nice, merge!

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.

4 participants