Skip to content

feat: Refactor transaction input types#1934

Merged
sergerad merged 7 commits intonextfrom
sergerad-tx-inputs
Oct 9, 2025
Merged

feat: Refactor transaction input types#1934
sergerad merged 7 commits intonextfrom
sergerad-tx-inputs

Conversation

@sergerad
Copy link
Copy Markdown
Contributor

@sergerad sergerad commented Sep 24, 2025

Context

Fields relevant to transaction inputs could be coalesced into a single type to better express their inter-dependance.

Closes #1922.

Changes

  • Refactor TransactionInputs type to coalesce various fields into the one struct.
  • Remove TransactionWitness type.

Copy link
Copy Markdown
Contributor

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

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

Still not 100% sure if this is the right way to go, as it looks like we have a few places where we still need TransactionInputs with notes (TransactionKernel::prepare_inputs, TransactionAdviceInputs::new) and a few places where it's nicer to have it separately (DataStore::get_transaction_inputs, NoteConsumptionChecker::try_execute_notes).

So I'd try a middle ground with two types, e.g. PartialTransactionInputs and TransactionInputs (see inline comment).

Comment on lines +181 to +189
notes: InputNotes<InputNote>,
input_notes: InputNotes<InputNote>,
tx_args: TransactionArgs,
) -> Result<ExecutedTransaction, TransactionExecutorError> {
let tx_inputs =
self.prepare_transaction_inputs(account_id, block_ref, notes, &tx_args).await?;
let tx_inputs = self
.prepare_transaction_inputs(account_id, block_ref, &input_notes, &tx_args)
.await?;

let (mut host, stack_inputs, advice_inputs) =
self.prepare_transaction(&tx_inputs, &tx_args, None).await?;
self.prepare_transaction(&tx_inputs, &input_notes, &tx_args, None).await?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think here and for TransactionKernel::prepare_inputs and in turn TransactionAdviceInputs::new it would make sense to still have a type that represents transaction inputs with notes, otherwise we just pass the notes separately.

This would be a bit cleaner, and it would also allow that type to validate the authentication paths of the input notes (what TransactionInputs::new has done so far).

So maybe having a PartialTransactionInputs and TransactionInputs type makes sense, which would be defined as described in #401 (comment).
(We could also use different names, but I can't come up with something better than TransactionInputsWithNotes and that seems less nice than the "partial" naming).

Copy link
Copy Markdown
Contributor

@PhilippGackstatter PhilippGackstatter Sep 25, 2025

Choose a reason for hiding this comment

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

I forgot that this is also a bit related to #1852 (comment). For that issue, we may want a type that returns more data than what would go in TransactionInputs (namely the asset witnesses), and so perhaps a different name makes sense. This would mean we no longer make that type a field in TransactionInputs (which would stay as-is), but rather inline its fields, so essentially:

// What is now TransactionInputs
pub struct TransactionExecutionInputs {
    account: PartialAccount,
    block_header: BlockHeader,
    blockchain: PartialBlockchain,
    input_notes: InputNotes<InputNote>,
}

// Would be returned by `DataStore::get_transaction_inputs`
pub struct TransactionInputs {
    account: PartialAccount,
    block_header: BlockHeader,
    blockchain: PartialBlockchain,
    // Not added in this PR.
    asset_witnesses: Vec<AssetWitness>,
}

I'm wondering if we could make TransactionExecutionInputs an internal type in the future (for now we need it across miden-lib and miden-tx, but if some functionality gets merged in the future, this could be made internal and then its name wouldn't matter as much).

@sergerad sergerad marked this pull request as ready for review September 26, 2025 02:47
Copy link
Copy Markdown
Contributor

@PhilippGackstatter PhilippGackstatter 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 think it's a bit odd to have types named TransactionInputs and TransactionExecutionInputs where the latter, with the more specialized name contains the former, with the more generic name. Should we rename TransactionInputs into TransactionPreparationInputs so they are both similarly named? If so, we should probably also adjust the method on DataStore to DataStore::get_transaction_preparation_inputs - it's a bit long, but at least precise.
An alternative would be prepare_transaction - that'd be slightly inconsistent with the other methods, but shorter. I'd be fine with both.

Also, I think we may actually be able to use TransactionInputs as a field directly in TransactionExecutionInputs.

(I know I proposed these things before, but I couldn't think of a better naming scheme / structure then - sorry for the back and forth.)

pub(super) tx_inputs: TransactionKernelInputs,
pub(super) mast_store: TransactionMastStore,
pub(super) advice_inputs: AdviceInputs,
pub(super) advice_inputs: AdviceInputs, // TODO: unused, something must be wrong?
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@bobbinth @PhilippGackstatter I am confused about the Option<AdviceInputs> vs these AdviceInputs. Right now I have the former being passed into TransactionKernelInputs::new() but I have probably done something wrong to leave this one unused.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have left TODO statements to cover the places where there is confusion around the advice inputs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have peeled back the diffs to iterate from a commit that passed tests. When I move the tx_args into the input type (still named TransactionExecutionInputs for now), the tests fail here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Relevant commit causing the failures

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems like one of the issues is that TransactionExecutionInputs contains TransactionArgs but we still build it separately in TransactionContextBuilder::build - and then we probably don't use it in certain TransactionExecutor APIs.

My gut feeling is that maybe combining TransactionPreparationInputs and TransactionArgs into a single type may not work too well because these types become available at different points in time, but we still want to have a type that encapsulates the tx inputs and input notes, and this creates an awkward situation where we construct TransactionExecutionInputs from TransactionPreparationInputs and InputNotes but are forced to also provide a TransactionArgs, which is not usually available at that time (for instance in MockChain::get_transaction_execution_inputs).

This may be solvable in two ways:

  • Either we keep three types in total: TransactionPreparationInputs, TransactionExecutionInputs and TransactionArgs separately,
  • Or we try to "remove" TransactionArgs from the public API and instead only expose its functionality through TransactionExecutionInputs which would now allow users to do everything that tx args did previously: set tx scripts, auth args, note args, etc.

I would try the second approach, since removing the number of types here could be beneficial to make the API clearer, but if this turns out to be too awkward to use in practice, I'd revert to the first approach.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My gut feeling is that maybe combining TransactionPreparationInputs and TransactionArgs into a single type may not work too well because these types become available at different points in time, but we still want to have a type that encapsulates the tx inputs and input notes, and this creates an awkward situation where we construct TransactionExecutionInputs from TransactionPreparationInputs and InputNotes but are forced to also provide a TransactionArgs, which is not usually available at that time (for instance in MockChain::get_transaction_execution_inputs).

I this also applicable outside of testing scenarios? My understanding is that in "production" scenarios, TransactionArgs are built before we have TransactionInputs (or TransactionExecutionInputs). If it is only for testing purposes, maybe we can make some internal state accessible which could make things simpler.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I this also applicable outside of testing scenarios?

Probably less so, yes. I extrapolated a bit too much from the tx context which is the only non-trivial user of the TransactionExecutor API we have in base.

pub(super) tx_inputs: TransactionKernelInputs,
pub(super) mast_store: TransactionMastStore,
pub(super) advice_inputs: AdviceInputs,
pub(super) advice_inputs: AdviceInputs, // TODO: unused, something must be wrong?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems like one of the issues is that TransactionExecutionInputs contains TransactionArgs but we still build it separately in TransactionContextBuilder::build - and then we probably don't use it in certain TransactionExecutor APIs.

My gut feeling is that maybe combining TransactionPreparationInputs and TransactionArgs into a single type may not work too well because these types become available at different points in time, but we still want to have a type that encapsulates the tx inputs and input notes, and this creates an awkward situation where we construct TransactionExecutionInputs from TransactionPreparationInputs and InputNotes but are forced to also provide a TransactionArgs, which is not usually available at that time (for instance in MockChain::get_transaction_execution_inputs).

This may be solvable in two ways:

  • Either we keep three types in total: TransactionPreparationInputs, TransactionExecutionInputs and TransactionArgs separately,
  • Or we try to "remove" TransactionArgs from the public API and instead only expose its functionality through TransactionExecutionInputs which would now allow users to do everything that tx args did previously: set tx scripts, auth args, note args, etc.

I would try the second approach, since removing the number of types here could be beneficial to make the API clearer, but if this turns out to be too awkward to use in practice, I'd revert to the first approach.

@bobbinth
Copy link
Copy Markdown
Contributor

bobbinth commented Oct 1, 2025

I wonder if we should punt on this for now. Current approaches seem to create more issues than they are solving, and in the future, this refactoring is likely to be easier. Specifically:

  • We will no longer need to maintain TransactionWitness as it is now. It will be replaced by a replay structure that is used for proof generation only.
  • Depending on how things shake out with the "training wheels" we may need to re-arrange structs in a different way.

But I also think that it may be worth reformulating the original issue more along the lines of what I described in #1934 (comment). Specifically, we do need something like TransactionKernelInputs to make note checker execution efficient. This can be done separately from removing input notes from TransactionInputs and maybe once we've done this, it will also make the path forward more clear.

@sergerad sergerad changed the title Move input notes out of tx inputs feat: Refactor transaction input types Oct 1, 2025
Copy link
Copy Markdown
Contributor

@PhilippGackstatter PhilippGackstatter 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 to me!

I think we can merge this as it is an improvement, but my original idea of removing TransactionArgs from the public API clearly doesn't work, so now we have more public transaction related types, which is a bummer. As @bobbinth mentioned, we'll likely have to touch this again, and so we can give this another shot then.

I left a comment about making the construction of transaction contexts a bit nicer which I think would be nice to do.

I think the naming of the structs as they are now is fine - having two Transaction{Preparation,Kernel}Inputs seems cleaner than having TransactionInputs and another more specific one. If we find preparation too long, we could go with something shorter like "setup", "base" or "core".

CHANGELOG.md Outdated
Comment on lines +57 to +58
- [BREAKING] Refactor transaction input types to `TransactionKernelInputs` and `TransactionPreparationInputs` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)).
- [BREAKING] Update `DataStore` trait to return `TransactionInputs` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Depending on how we go forward with names, this needs an update in one or the other way.

Comment on lines +225 to +227
let mut tx_args = TransactionArgs::new(Default::default(), foreign_account_inputs)
.with_tx_script(tx_script);
tx_args.extend_advice_inputs(advice_inputs);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not for this PR, but we could also refactor TransactionArgs::new so that it takes AdviceInputs instead of AdviceMap. That would avoid the need for the extend call.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

On second thoughts, extending the advice stack doesn't make sense from transaction args, but extending advice map or merkle store probably does make sense. I'm thinking of some custom use cases for merkle path verification, for instance. I wonder if we should refactor TransactionArgs a bit to address that. Not super pressing, though.

Comment on lines +123 to +130
mut tx_inputs,
foreign_account_code,
advice_witness,
} = tx_witness;

let (stack_inputs, advice_inputs) =
TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_witness))
.map_err(TransactionProverError::ConflictingAdviceMapEntry)?;
tx_inputs.extend_advice_inputs(advice_witness);
let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&tx_inputs)
.map_err(TransactionProverError::ConflictingAdviceMapEntry)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's a bit awkward to carry the tx inputs and advice witness here separately and then combine them. But also, as @bobbinth mentioned, this will likely be refactored anyway, so no big deal.

Copy link
Copy Markdown
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! Not a full review, but I left some comments inline. There are two main comments:

  • One about adding advice_inputs to TransactionKernelInputs and updating this field on each execution of NoteConsumptionChecker::try_execute_notes().
  • Another one about moving TransactionKernelInputs into miden-lib.

I'm not too sure about the benefits of the latter - so, maybe we do the first point first and see how it looks, and then we can evaluate if the second point makes sense.

Comment on lines +155 to +158
/// Extends the advice inputs with the provided ones.
pub fn extend_advice_inputs(&mut self, advice_inputs: AdviceInputs) {
self.tx_args.extend_advice_inputs(advice_inputs);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if this should be just set_advice_inputs() which would reset the value of the new advice_inputs field I mentioned in one of the previous comments.

The idea here is that in the note checker's try_execute_notes(), we'd take kernel_inputs as a mutable parameter, and then replace them with the result we get back from FastProcessor::execute() (we get back advice provider there, but I believe we can convert it to advice inputs).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I removed extend_advice_inputs() from TransactionKernelInputs after adding init_advice_inputs to it. Not quite following how to alter try_execute_notes() w.r.t to this.

@bobbinth
Copy link
Copy Markdown
Contributor

bobbinth commented Oct 2, 2025

Also, I think this branch may not be up to date with the latest next - if so, it may be good to merge the latest next into it.

@sergerad sergerad force-pushed the sergerad-tx-inputs branch from 9315769 to db6be8b Compare October 3, 2025 00:41
@sergerad sergerad requested a review from bobbinth October 3, 2025 03:19
Copy link
Copy Markdown
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 some more comments inline. Sorry for going back and forth on some of the things.

Copy link
Copy Markdown
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! Not a full review yet, but I think this is the right overall structure. I left some comments inline, but most of these are pretty minor.

@sergerad sergerad force-pushed the sergerad-tx-inputs branch from b6fba4c to b4f02b2 Compare October 7, 2025 01:08
Copy link
Copy Markdown
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! Just a few more comments inline.

@sergerad sergerad force-pushed the sergerad-tx-inputs branch from 4bccca9 to 6e590ab Compare October 7, 2025 21:46
@sergerad sergerad requested a review from bobbinth October 7, 2025 21:50
Copy link
Copy Markdown
Contributor

@PhilippGackstatter PhilippGackstatter 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 to me! Left some minor comments.

Comment on lines +225 to +227
let mut tx_args = TransactionArgs::new(Default::default(), foreign_account_inputs)
.with_tx_script(tx_script);
tx_args.extend_advice_inputs(advice_inputs);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

On second thoughts, extending the advice stack doesn't make sense from transaction args, but extending advice map or merkle store probably does make sense. I'm thinking of some custom use cases for merkle path verification, for instance. I wonder if we should refactor TransactionArgs a bit to address that. Not super pressing, though.

Copy link
Copy Markdown
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 two small comments inline - but other than that, this is basically good to merge.

@sergerad sergerad merged commit ef81bac into next Oct 9, 2025
19 checks passed
@sergerad sergerad deleted the sergerad-tx-inputs branch October 9, 2025 06:48
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.

Change DataStore::get_transaction_inputs to return TransactionInputs

3 participants