Should we bind TransactionRequest to a specific Account?
(Reference: #822)
Currently, a transaction can get executed in the Client by any Account: the API takes the TransactionRequest and a specific AccountId. However, TransactionRequest is not fully sender-agnostic today. “Own notes”, if the request object contains them, effectively embed the sender because certain note types need the sender account ID to be computed. Concretely, swaps derive payback note details inside the swap via NoteInputs, which requires the sender ID; faucet mints cannot prevriously declare specific NoteAssets because the final assets depend on which faucet actually executes. By contrast, non-own outputs only need a NoteRecipient to be complete (OutputNote::Full). The current API doesn’t let us build “everything except the sender,” so client code would need to end up duplicating some code from miden-base.
We could attempt to make it fully agnostic if we move sender resolution into an explicit binding step. Instead of “own notes,” the request would carry output templates. There are two kinds: (a) external outputs, where a NoteRecipient is enough and the note is already full; and (b) own outputs, where we store a template describing what will be emitted via the sender’s AccountInterface, and we only materialize it once the sender is known. To make this ergonomic, we introduce a small trait that can be implemented by P2ID, swap, etc., so we can construct notes without duplicating miden-base logic. An attempt was done in #811.
Alternatively, we could decide to bind TransactionRequest to a specific account ID. In this sense, the object will already be consistent with all other behaviors.
Or we could explore something in the middle where the request can be "bound" to a specific account ID (eg, by calling a specific bind() step, maybe on the builder). Here, sender-dependent validation happens: resolve own-note templates, compute swap payback “future notes,” check faucet constraints, etc. This could be optional and there could be two different request types to have a more explicit separation and the client APIs could support both.
Remove "leaky" fields
(note: there was an issue/discussion miden-base about this that I could not find after a quick search)
The expected_output_recipients field exists to support public note creation, because the VM host needs to unhash all recipients related to the public notes that were created during the transaction execution. There are a number of alternatives that we could do to avoid this, like fetching output notes-related data from DataStore.
Input notes as InputNote
We could decide to track all inputs as InputNote instead of NoteId. InputNote already describes whether the note gets used as authenticated or unauthenticated so we can merge input_notes and unauthenticated_input_notes into a single inputs: Vec<InputNote>.
There are some implications here. For example, if we do this, we can no longer express the transaction request as a function of something that only the executor knows (if a server somehow sends a TransactionRequest for a user to execute, the server is expected to know the details about the notes themselves).
Revisit foreign_accounts
This should be fairly straightforward. Once lazy loading lands, we should be able to remove the foreign accounts description from TransactionRequest. We could also decide to keep it only for private accounts, since there is otherwise no other external way to communicate a specific private account state. This is also somewhat related to the account state endpoints refactors, since the client will need to be able to sync specific accounts, and store relevant data so that it can be fetched on transaction runtime.
Mutators on TransactionRequest
#1254 adds a mutable getter for the advice map that a user may want to set for the transaction to use. This is currently the only way to mutate a TransactionRequest object, which breaks a bit with the builder convention of having a static object be created by its builder (ie, TransactionRequestBuilder). Is this enough/OK in relation to being able to mutate a request? Are there any more general alternatives that would make sense here? Or we could make mutators even more specific as suggested here.
Some of these are more concrete to address than others, so we can make separate issues as we see fit to track each part accordingly.
Should we bind
TransactionRequestto a specificAccount?(Reference: #822)
Currently, a transaction can get executed in the
Clientby anyAccount: the API takes theTransactionRequestand a specificAccountId. However,TransactionRequestis not fully sender-agnostic today. “Own notes”, if the request object contains them, effectively embed the sender because certain note types need the sender account ID to be computed. Concretely, swaps derive payback note details inside the swap viaNoteInputs, which requires the sender ID; faucet mints cannot prevriously declare specificNoteAssetsbecause the final assets depend on which faucet actually executes. By contrast, non-own outputs only need aNoteRecipientto be complete (OutputNote::Full). The current API doesn’t let us build “everything except the sender,” so client code would need to end up duplicating some code frommiden-base.We could attempt to make it fully agnostic if we move sender resolution into an explicit binding step. Instead of “own notes,” the request would carry output templates. There are two kinds: (a) external outputs, where a
NoteRecipientis enough and the note is already full; and (b) own outputs, where we store a template describing what will be emitted via the sender’sAccountInterface, and we only materialize it once the sender is known. To make this ergonomic, we introduce a small trait that can be implemented by P2ID, swap, etc., so we can construct notes without duplicatingmiden-baselogic. An attempt was done in #811.Alternatively, we could decide to bind
TransactionRequestto a specific account ID. In this sense, the object will already be consistent with all other behaviors.Or we could explore something in the middle where the request can be "bound" to a specific account ID (eg, by calling a specific
bind()step, maybe on the builder). Here, sender-dependent validation happens: resolve own-note templates, compute swap payback “future notes,” check faucet constraints, etc. This could be optional and there could be two different request types to have a more explicit separation and the client APIs could support both.Remove "leaky" fields
(note: there was an issue/discussion
miden-baseabout this that I could not find after a quick search)The
expected_output_recipientsfield exists to support public note creation, because the VM host needs to unhash all recipients related to the public notes that were created during the transaction execution. There are a number of alternatives that we could do to avoid this, like fetching output notes-related data fromDataStore.Input notes as
InputNoteWe could decide to track all inputs as
InputNoteinstead ofNoteId.InputNotealready describes whether the note gets used as authenticated or unauthenticated so we can mergeinput_notesandunauthenticated_input_notesinto a singleinputs: Vec<InputNote>.There are some implications here. For example, if we do this, we can no longer express the transaction request as a function of something that only the executor knows (if a server somehow sends a
TransactionRequestfor a user to execute, the server is expected to know the details about the notes themselves).Revisit
foreign_accountsThis should be fairly straightforward. Once lazy loading lands, we should be able to remove the foreign accounts description from
TransactionRequest. We could also decide to keep it only for private accounts, since there is otherwise no other external way to communicate a specific private account state. This is also somewhat related to the account state endpoints refactors, since the client will need to be able to sync specific accounts, and store relevant data so that it can be fetched on transaction runtime.Mutators on
TransactionRequest#1254 adds a mutable getter for the advice map that a user may want to set for the transaction to use. This is currently the only way to mutate a
TransactionRequestobject, which breaks a bit with the builder convention of having a static object be created by its builder (ie,TransactionRequestBuilder). Is this enough/OK in relation to being able to mutate a request? Are there any more general alternatives that would make sense here? Or we could make mutators even more specific as suggested here.Some of these are more concrete to address than others, so we can make separate issues as we see fit to track each part accordingly.