Closed igamigo closed 6 days ago
should we merge this and #398 into a single issue?
should we merge this and #398 into a single issue?
I'd keep 3 separate issues for these as we probably will address them in 3 separate PRs.
A couple of additional things left over from #398:
expected_partial_notes
to expected_future_notes
.MerkleStore
to transaction request (this would require a similar change to TransactionArgs
in miden-base
).A couple of additional things left over from #398:
* Potentially rename `expected_partial_notes` to `expected_future_notes`. * Consider also adding a `MerkleStore` to transaction request (this would require a similar change to `TransactionArgs` in `miden-base`).
I took a quick look and the chankes seem to be pretty straightforward although correct me if I'm wrong.
On the miden base side, in the ToTransactionKernelInputs
implementation:
TransactionArgs
within PreparedTransaction
(or we might add another member to PreparedTransaction
) should contain the merkle storeAdviceInputs::default
we use AdviceInputs::with_merkle_store
On the client side:
Yes, this shouldn't be too complicated.
- the
TransactionArgs
withinPreparedTransaction
(or we might add another member toPreparedTransaction
) should contain the merkle store- instead of using
AdviceInputs::default
we useAdviceInputs::with_merkle_store
I think we could probably just replace AdviceMap
with AdviceInputs
in the TransactionArgs
(since AdviceInputs
contain advice map and Merkle store). It could look something like this:
pub struct TransactionArgs {
tx_script: Option<TransactionScript>,
note_args: BTreeMap<NoteId, Word>,
advice_inputs: AdviceInputs,
}
@bobbinth a couple of questions regarding generalizing TransactionScript
. I think the original comment is a little bit stale, but would we just want to solve the problem for authentication methods? Right now the transaction request looks like this:
pub struct TransactionRequest {
/// ID of the account against which the transactions is to be executed.
account_id: AccountId,
// Notes to be consumed by the transaction that are not authenticated.
unauthenticated_input_notes: Vec<Note>,
/// Notes to be consumed by the transaction together with their (optional) arguments. This
/// has to include both authenticated and unauthenticated notes.
input_notes: BTreeMap<NoteId, Option<NoteArgs>>,
/// A list of notes expected to be generated by the transactions.
expected_output_notes: Vec<Note>,
/// A list of note details of notes we expect to be created as part of future transactions.
expected_partial_notes: Vec<NoteDetails>,
/// Optional transaction script (together with its arguments).
tx_script: Option<TransactionScript>,
/// Initial state of the `AdviceMap` that provides data during runtime.
advice_map: AdviceMap,
}
We also have TransactionTemplate
which builds into a TransactionRequest
:
pub enum TransactionTemplate {
/// Consume the specified notes against an account.
ConsumeNotes(AccountId, Vec<NoteId>),
/// Mint fungible assets using a faucet account and creates a note with the specified
/// type that can be consumed by the target Account ID
MintFungibleAsset(FungibleAsset, AccountId, NoteType),
/// Creates a pay-to-id note with the specified type directed to a specific account
PayToId(PaymentTransactionData, NoteType),
/// Creates a pay-to-id note directed to a specific account, specifying a block height after
/// which the note can be recalled
PayToIdWithRecall(PaymentTransactionData, u32, NoteType),
/// Creates a swap note offering a specific asset in exchange for another specific asset
Swap(SwapTransactionData, NoteType),
}
We shouldn't need to introduce a new TransactionScriptTemplate
for deciding on the authentication method, right? We could just use the data from the TransactionTemplate
and then choose to call the correct proc (ie, just replacing an extra field based on what the client knows about the account). Or do we want to authenticate any transaction (ie, with custom scripts) by injecting a call to the authentication procedure before ending the script anyway?
Introducing TransactionScriptTemplate
may not be the right way to do it, but I'm also not sure the current TransactionTemplate
is the right approach here (maybe it is).
The use case we are trying to support is some external entity (e.g., an exchange operator) building a transaction request for the user, and then the user building and executing the implied transaction. The main challenge here is that this entity may not know much about the account which will try to execute the transaction (in fact, it may not even know the ID of the account either - but let's put it aside for now), and so, it cannot assume what kind of authentication the account supports or, more generally, what procedures the account exposes.
To give some examples of what the requests might look like:
(note1_id, args1)
and (note2_id, args2)
.(note3, args3)
.(note4)
and (partial_note5)
.The first two use cases we already support, but we don't support the 3rd one. This is because for this use case we need to build a specific transaction script, and the entity that needs to build the transaction request cannot build this script because it doesn't know much about the account.
On the other hand, TransactionTemplate
is too specific to support all of the above use cases (e.g., there is no way to specify that we want to consume an unauthenticated note via transaction template; we can add that, of course, but I think that's going in the wrong direction).
In my mind, the chain is still:
TransactionTemplate -> TransactionRequest -> TransactionResult -> ProvenTransaction
In the above, TransactionRequest
is "more general" than TransactionTemplate
- i.e., transaction template can represent a subset of transactions of as compared to what transaction request can represent.
I actually think we are pretty close to what we need with the current TransactionRequest
. Maybe the changes we need to make are as follows:
tx_script
.For example, the struct could look like so:
pub struct TransactionRequest {
/// ID of the account against which the transactions is to be executed.
account_id: AccountId,
// Notes to be consumed by the transaction that are not authenticated.
unauthenticated_input_notes: Vec<Note>,
/// Notes to be consumed by the transaction together with their (optional) arguments. This
/// has to include both authenticated and unauthenticated notes.
input_notes: BTreeMap<NoteId, Option<NoteArgs>>,
/// Notes to be generated by the transaction's tx_script.
native_output_notes: Vec<PartialNote>,
/// A list of notes expected to be generated by the transactions.
expected_output_notes: Vec<Note>,
/// A list of note details of notes we expect to be created as part of future transactions.
expected_partial_notes: Vec<NoteDetails>,
/// Initial state of the `AdviceMap` that provides data during runtime.
advice_map: AdviceMap,
}
The idea is that the client would then take native_output_notes
(don't love the name - so, suggestions are welcome) and would build a transaction script which would produce these notes according to the specifics of the account the client manages. This also implies that construction of TransactionScript
would happen inside Client::new_transaction()
rather than inside Client::build_transaction_request()
.
A quick comment about account ID. At some point, we may want to consider removing account_id
from TransactionRequest
to make it even more general. That is, whoever builds the transaction request would not specify the account that needs to execute it, and then it would be up to the user to decide which account to apply the request to. This would require refactoring Client::new_transaction()
method to look something like this:
pub fn new_transaction(
&mut self,
account_id: AccountId,
transaction_request: TransactionRequest,
) -> Result<TransactionResult, ClientError>
But I don't think we should tackle this as a part of this issue.
I see, got it. One question:
The first two use cases we already support, but we don't support the 3rd one. This is because for this use case we need to build a specific transaction script, and the entity that needs to build the transaction request cannot build this script because it doesn't know much about the account.
When we mention that the entity "doesn't know much about the account", we mean that we don't know how the account is authenticated, right? If this is the case, then we are still not really supporting 1 and 2 because they do call a hardcoded auth_tx::auth_tx_rpo_falcon512
.
I agree on the rest of the topics, it's about what I had in mind.
One other comment regarding this: While native_output_notes
tells us what notes the script is meant to create, it doesn't tell us how. Are we to assume based on the account's interface? eg, if the account_id
is a faucet's ID, we call distribute
, otherwise call send_asset
When we mention that the entity "doesn't know much about the account", we mean that we don't know how the account is authenticated, right? If this is the case, then we are still not really supporting 1 and 2 because they do call a hardcoded
auth_tx::auth_tx_rpo_falcon512
.
Ah yes! This is correct.
While
native_output_notes
tells us what notes the script is meant to create, it doesn't tell us how. Are we to assume based on the account's interface? eg, if theaccount_id
is a faucet's ID, we calldistribute
, otherwise callsend_asset
.
Yes, there will need to be some logic in the client to figure out how to interpret the request. The logic could be relatively simple for now - for example:
send_asset
(as an aside, it might make sense to rename this to something like send_note
in miden-base
since this doesn't have to send assets any more).distribute
(assuming we are trying to distribute the native asset of the faucet).This logic an be encapsulated into a component which could be extended in the future (we could even make it "pluggable" like we do with the store, authenticator etc. down the road).
I have another question regarding the building of the transaction script. How would we handle custom transaction scripts that don't follow the common structure? Would we receive a String that we append to the whole tx_script at the end/beginning?
I guess there should be a mechanism for specifying a wholly custom transaction script - though, I haven't thought of one. In such a case, this script would be used directly (rather than appended to another script). So, basically, there could be two cases:
I just pushed an initial implementation of the script building in the client. It's not finished but we can use it as a starting point for further discussion if needed.
Closed by #438.
After #237 is merged,
TransactionRequest
will contain aTransactionScript
. This has the following consequences:_Originally posted by @bobbinth in https://github.com/0xPolygonMiden/miden-client/pull/237#discussion_r1544859824_
After this is done, we should look into having a file definition that can be easily deserialized into a
TransactionRequest
as suggested in #203