Skip to content

refactor(cketh): extract TransactionPipeline from WithdrawalTransactions - #11190

Merged
gregorydemay merged 3 commits into
masterfrom
greg/cketh-extract-pipeline
Aug 20, 2026
Merged

refactor(cketh): extract TransactionPipeline from WithdrawalTransactions#11190
gregorydemay merged 3 commits into
masterfrom
greg/cketh-extract-pipeline

Conversation

@gregorydemay

@gregorydemay gregorydemay commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Why

WithdrawalTransactions does two jobs. It drives transactions through create → sign → send → resubmit → finalize on the minter's nonce sequence, and it tracks which withdrawals still owe the user a refund. The two meet at only two moments — creating a transaction arms the refund, finalizing it disarms it — and everything else about sending a transaction is indifferent to whether anyone gets paid back.

The minter's sweeper address will need that send machinery on a nonce sequence of its own. It burns no ckETH, so it must be able to reuse the machinery without inheriting a refund path it can never use.

What

TransactionPipeline becomes the send machinery alone — the request queues, the three transaction maps and the nonce. WithdrawalTransactions owns a pipeline plus the reimbursement bookkeeping, forwards the pipeline's API so callers are unchanged, and intercepts those two moments so the invariant lives in one place. Reimbursement records and withdrawal status move with it.

Two things follow from the split:

  • record_finalized_transaction exists on both types: the pipeline's performs the finalize mechanics and hands back the finalized transaction; the wrapper's uses that to decide the refund. It keeps the same name on both, as record_created_transaction already did.
  • transaction_stage reports where a transaction sits — created, sent or finalized — returning Option, so absence is the type's business rather than a variant. The wrapper maps that to RetrieveEthStatus, consulting reimbursement only once finalized. RetrieveEthStatus cannot move to the pipeline: its finalized payload is Success | PendingReimbursement | Reimbursed, which is reimbursement vocabulary throughout.

state::tests is untouched: it builds its fixture through a builder, so the new nesting is absorbed in build() and no assertion has to be restated.

Copilot AI left a comment

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.

Pull request overview

Refactors the ckETH minter’s withdrawal-transaction state by extracting the generic “send machinery” into a dedicated TransactionPipeline, leaving WithdrawalTransactions responsible for reimbursement bookkeeping and delegating transaction lifecycle operations to the pipeline.

Changes:

  • Extracts transaction queue/nonce/maps logic into TransactionPipeline and wraps it inside WithdrawalTransactions.
  • Splits finalization so the pipeline finalizes the on-chain transaction, while WithdrawalTransactions handles reimbursement side-effects on failure.
  • Updates unit/state tests to construct and assert against the new pipeline-containing structure.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
rs/ethereum/cketh/minter/src/state/transactions/mod.rs Introduces TransactionPipeline, moves core transaction lifecycle logic into it, and re-implements WithdrawalTransactions as a wrapper with reimbursement bookkeeping.
rs/ethereum/cketh/minter/src/state/transactions/tests.rs Updates transaction tests to account for the new WithdrawalTransactions { pipeline, ... } structure and adds new_for_test for TransactionPipeline.
rs/ethereum/cketh/minter/src/state/tests.rs Updates state equivalence tests to construct WithdrawalTransactions using TransactionPipeline::new_for_test(...).
Suppressed comments (1)

rs/ethereum/cketh/minter/src/state/transactions/mod.rs:1216

  • WithdrawalTransactions::transaction_status directly accesses TransactionPipeline’s private pending_withdrawal_requests. Using the existing withdrawal_requests_iter() accessor avoids coupling WithdrawalTransactions to pipeline internals.
        if self
            .pipeline
            .pending_withdrawal_requests
            .iter()
            .any(|r| &r.cketh_ledger_burn_index() == burn_index)

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs Outdated
Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs Outdated
@gregorydemay
gregorydemay force-pushed the greg/cketh-extract-pipeline branch from dceea28 to 7bb9b9c Compare August 18, 2026 13:41
@gregorydemay
gregorydemay force-pushed the greg/cketh-extract-pipeline branch 3 times, most recently from 3330f0d to 340c421 Compare August 18, 2026 14:48
@gregorydemay
gregorydemay force-pushed the greg/cketh-extract-pipeline branch from 340c421 to d3cfa0b Compare August 19, 2026 07:19
Base automatically changed from greg/cketh-lane-tidy to master August 19, 2026 07:19
gregorydemay and others added 2 commits August 19, 2026 09:19
…chine

`WithdrawalTransactions` did two jobs: it drove transactions through
create → sign → send → resubmit → finalize on the minter's nonce sequence, and
it tracked which withdrawals still owe the user a refund. The two are only
coupled at two moments — creating a transaction arms the refund, finalizing it
disarms it — but everything else about sending a transaction is indifferent to
whether anyone gets paid back.

Split them: `TransactionPipeline` is now the send machinery alone, holding the
request queues, the three transaction maps and the nonce. `WithdrawalTransactions`
owns a pipeline plus the reimbursement bookkeeping, forwards the pipeline's API so
callers are unchanged, and intercepts exactly those two moments so the
invariant lives in one place. Reimbursement records and withdrawal status move
with it. `record_finalized_transaction` splits accordingly: the pipeline exposes
`finalize_transaction` for the mechanics, and the refund tail stays outside.

Preparatory: the sweeper address will need the same send machinery on its own
nonce sequence, and it burns no ckETH, so it must be able to reuse the pipeline
without inheriting a refund path it can never use.

No behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review follow-up. Two things the split left behind:

The pipeline's doc comment still promised to reimburse the user on a failed
transaction, which is precisely the responsibility that moved out.

And `WithdrawalTransactions` was still reading the pipeline's fields directly.
Same module, so it compiles, but it makes the wrapper depend on how the pipeline
stores things rather than on what it does — and the point of the split is that a
second sender address can reuse the machinery. Every access now goes through the
pipeline's own API; the three lookups that had no accessor get one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gregorydemay
gregorydemay force-pushed the greg/cketh-extract-pipeline branch from d3cfa0b to 3f460ed Compare August 19, 2026 07:19
@gregorydemay

Copy link
Copy Markdown
Contributor Author

🤖 Reading guide — the diff in state/transactions/mod.rs is one large block because a single 38-method impl WithdrawalTransactions becomes two impls (29 + 36 methods). A line-based diff cannot express "this method is now two methods on two types", so most of what looks like churn is code that changed impl block without changing.

I tried reordering the file to make it align method-by-method — both putting the wrapper first and reordering its methods into master's order. Neither helps (11 hunks → 10, the two large hunks remain), so the file keeps the order that reads best rather than one contorted for the diff.

Instead, here is what the 41 methods actually are:

role count what to do
pure delegate 20 skim — the body is self.pipeline.<same name>(args)
moved verbatim into TransactionPipeline 23 skip — byte-identical to master
wrapper only (reimbursement + status) 12 skip — unchanged from master
pipeline only 5 3 verbatim helpers, 2 new: transaction_stage, processed_withdrawal_requests_iter
genuinely different 4 read carefully

The four worth your attention, which are exactly what the split is about:

  • new — constructs a pipeline plus the reimbursement bookkeeping around it
  • is_equivalent_to — compares the reimbursement state, then delegates the rest
  • record_created_transaction — delegates, then arms the refund
  • record_finalized_transaction — delegates the finalize mechanics, disarms the refund, then records the reimbursement

Those last two are the only places where the pipeline and the refund bookkeeping must stay in step, which is why they intercept rather than delegate. Both are now guarded by is_reimbursable, since a SweeperFunding request is never paid back — that variant arrived on master while this branch was in review.

To collapse the relocated code while reading:

git diff --color-moved=zebra --color-moved-ws=allow-indentation-change origin/master...greg/cketh-extract-pipeline

Pink and blue are removals that reappear elsewhere, cyan and yellow their arrivals; skipping all four leaves only what genuinely changed.

@gregorydemay
gregorydemay marked this pull request as ready for review August 19, 2026 08:01
@gregorydemay
gregorydemay requested a review from a team as a code owner August 19, 2026 08:01
@github-actions github-actions Bot added the @defi label Aug 19, 2026
Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs
Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs Outdated
Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs
Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs
@zeropath-ai

zeropath-ai Bot commented Aug 19, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 0cd82ae.

Security Overview
Detected Code Changes
Change Type Relevant files
Refactor ► rs/ethereum/cketh/minter/src/state/transactions/mod.rs
    Rename WithdrawalTransactions to TransactionPipeline and adjust visibility; introduce TransactionStage enum and new fields; migrate related logic accordingly; remove outdated reimbursement-related fields and methods; update related logic and documentation comments
Enhancement ► rs/ethereum/cketh/minter/src/state/transactions/mod.rs
    Introduce TransactionStage enum and its usage
► rs/ethereum/cketh/minter/src/state/transactions/mod.rs
    Replace direct fields with a new WithdrawalTransactions struct that contains a pipeline and reimbursement bookkeeping
► rs/ethereum/cketh/minter/src/state/transactions/tests.rs
    Adjust tests to reflect renamed/new API (next_transaction_nonce, sent_transactions helper, etc.)

@mbjorkqvist mbjorkqvist left a comment

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.

Thanks @gregorydemay! Just a few minor comments.

Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs Outdated
Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs
Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs Outdated
Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs
Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs Outdated
Comment thread rs/ethereum/cketh/minter/src/state/transactions/tests.rs Outdated
Comment thread rs/ethereum/cketh/minter/src/state/transactions/tests.rs Outdated
Restores two things the extraction dropped against master: the effective
transaction fee and its explanation in the funding-failure log, without which
the log no longer says how much gas the failed transaction paid; and the
comment recording why the `PendingReimbursement` branch is unreachable for a
funding, and what adopting a status of its own would cost.

`maybe_reimburse_requests_iter` was the one place left reading a private field
of `TransactionPipeline` rather than calling it, which is the encapsulation
this extraction exists to create; it now goes through
`get_processed_withdrawal_request`. `TransactionStage` is only reachable via
`TransactionPipeline`, so it drops to `pub(in crate::state)` too.

The tests reached into `sent_tx`, `created_tx`, `finalized_tx` and `next_nonce`
in eight places; they now use the accessors, with `sent_transactions` and
`first_sent_transaction` covering what no single accessor returns.
`create_and_record_transaction` returns the transaction it recorded instead of
reading it back.

Bullets 5 and 6 of the lifecycle doc restated the same clause; they are now one
step. A request is no longer necessarily a user's, since `SweeperFunding` is a
`WithdrawalRequest`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

rs/ethereum/cketh/minter/src/state/transactions/tests.rs:3082

  • In sent_transactions, the find predicate compares a dereferenced LedgerBurnIndex (*index) to a reference (burn_index). LedgerBurnIndex is an Id<_, u64> newtype and only implements PartialEq with itself, so *index == burn_index is likely a type mismatch. Comparing the references directly avoids the issue and is clearer.
    transactions
        .sent_transactions_iter()
        .find(|(_nonce, index, _txs)| *index == burn_index)
        .map(|(_nonce, _index, txs)| txs)

@gregorydemay
gregorydemay added this pull request to the merge queue Aug 20, 2026
Merged via the queue into master with commit e57e2e7 Aug 20, 2026
43 checks passed
@gregorydemay
gregorydemay deleted the greg/cketh-extract-pipeline branch August 20, 2026 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants