Skip to content

feat(minter): submit withdrawals as durable-nonce transactions - #248

Draft
gregorydemay wants to merge 1 commit into
feat/nonce-withdrawal-eventsfrom
feat/nonce-withdrawal-submission
Draft

gregorydemay wants to merge 1 commit into
feat/nonce-withdrawal-eventsfrom
feat/nonce-withdrawal-submission

Conversation

@gregorydemay

@gregorydemay gregorydemay commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Withdrawal transactions no longer reference a recent blockhash: each batch of up to ten transfers is built on a durable nonce account reserved from the pool, with AdvanceNonceAccount as the first instruction and the account's nonce value in place of the blockhash. The unsigned message is recorded in a CreatedTransaction event before the threshold signature is requested, so an interrupted signing resumes with the identical message, and a SignedTransaction event records the identifier before the transaction is sent with skipPreflight. A nonce value already bound to an earlier transaction of the same account is recognized as a stale read and skipped, and the batch selection keeps the rent exemption threshold back from the available balance.

Since a durable-nonce transaction never expires, withdrawals are excluded from the expiry and re-signing path entirely; a missing status leaves the transaction in flight (re-broadcasting and landed-detection follow in later PRs). A finalized success or failure frees the nonce account for reuse. The legacy blockhash withdrawal flow and its events are removed.

The Solana test validator suite now provisions a real nonce account before installing the minter and verifies the on-chain nonce advance after a withdrawal end to end.

🤖 Generated with Claude Code

@gregorydemay
gregorydemay added this pull request to stack #246 October 6, 2026 08:28
@gregorydemay gregorydemay changed the title feat/nonce withdrawal submission feat(minter): submit withdrawals as durable-nonce transactions Oct 6, 2026
@gregorydemay
gregorydemay removed this pull request from stack #246 October 6, 2026 08:30
@gregorydemay
gregorydemay changed the base branch from feat/nonce-account-reading to main October 6, 2026 08:30
@gregorydemay
gregorydemay changed the base branch from main to feat/nonce-account-reading October 6, 2026 08:30
@gregorydemay
gregorydemay added this pull request to stack #249 October 6, 2026 08:30
@gregorydemay gregorydemay reopened this Oct 6, 2026
@gregorydemay
gregorydemay removed this pull request from stack #249 October 6, 2026 08:40
@gregorydemay
gregorydemay changed the base branch from feat/nonce-account-reading to main October 6, 2026 08:40
@gregorydemay
gregorydemay changed the base branch from main to feat/nonce-account-reading October 6, 2026 08:40
@gregorydemay
gregorydemay added this pull request to stack #250 October 6, 2026 08:40
Copilot AI balanced review requested due to automatic review settings October 6, 2026 09:57

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.

Note

Copilot was unable to run its full agentic suite in this review.

Copilot review overview

Review effort: Lite
Findings: 1 High severity · 1 Medium severity · 1 Low severity

Open (3)
What changed in this PR

This PR refactors withdrawal processing to use durable nonce accounts, persisting withdrawal transaction creation/signing as separate events and adjusting state tracking to prevent nonce reuse and hold back rent exemption.

Changes:

  • Split withdrawal transaction lifecycle into CreatedTransaction and SignedTransaction events, with new state buckets/maps to persist messages and nonce bindings.
  • Introduce durable-nonce-aware batching limits and rent-exemption holdback when determining affordable withdrawal batches.
  • Update RPC submission to optionally skip preflight for withdrawals, and adjust monitoring/tests/integration scaffolding for the new flow.
File Description
minter/​src/​withdraw/​tests.rs Updates unit tests to assert created/signed withdrawal events and nonce-based batching behavior.
minter/​src/​withdraw/​mod.rs Refactors withdrawal processing into create→sign→send phases and adds retry delay constant.
minter/​src/​test_fixtures/​runtime.rs Makes the test runtime capture timer delays (not just count) for more precise assertions.
minter/​src/​test_fixtures/​mod.rs Adds fixtures for created/signed withdrawal events and nonce-account handling in tests.
minter/​src/​state/​tests.rs Extends state tests for new withdrawal buckets, rent holdback, and nonce-tx replay semantics.
minter/​src/​state/​nonce_pool/​tests.rs Adds tests for reserving/binding/freeing durable nonce accounts and stale nonce detection.
minter/​src/​state/​nonce_pool/​mod.rs Implements nonce account reservation/binding and “seen nonce values” tracking.
minter/​src/​state/​mod.rs Adds new withdrawal state maps, batch affordability changes, and event handlers for created/signed txs.
minter/​src/​state/​event/​cbor/​tests.rs Adds CBOR roundtrip tests for solana_hash::Hash.
minter/​src/​state/​event/​cbor/​mod.rs Introduces CBOR encode/decode for Solana Hash.
minter/​src/​state/​event.rs Adds new withdrawal event types and removes TransactionPurpose::WithdrawSol.
minter/​src/​state/​audit.rs Applies new withdrawal lifecycle events during event replay/auditing.
minter/​src/​sol_transfer/​tests.rs Reworks withdrawal message/signing tests for durable nonce messages and size limits.
minter/​src/​sol_transfer/​mod.rs Splits withdrawal tx creation into message builder + signer; changes batch size constant.
minter/​src/​rpc/​mod.rs Adds submit_transaction_skipping_preflight and refactors submission path.
minter/​src/​monitor/​tests.rs Adds/updates monitoring tests for durable-nonce withdrawals; removes withdrawal resubmission test.
minter/​src/​monitor/​mod.rs Avoids fetching current block when only durable-nonce withdrawals are in-flight.
minter/​src/​main.rs Maps new withdrawal events to Candid-facing event types and factors out message mapping.
minter/​src/​dashboard/​mod.rs Shows created withdrawals alongside pending withdrawals on the dashboard.
minter/​src/​canbench.rs Updates benchmark event generation for new withdrawal event lifecycle.
minter/​cksol_minter.did Adds a Hash type and exposes new withdrawal lifecycle events; removes WithdrawSol purpose.
minter/​canbench_results.yml Updates benchmark instruction counts reflecting the additional events and logic.
libs/​types-internal/​src/​event.rs Adds new withdrawal lifecycle events to internal Candid types; removes WithdrawSol purpose.
integration_tests/​tests/​tests.rs Updates withdrawal integration test to validate created/signed events and replay stability.
integration_tests/​tests/​solana_test_validator.rs Verifies withdrawals advance durable nonce on the validator.
integration_tests/​src/​validator.rs Creates real durable nonce accounts in validator-backed setups; adds nonce value reader.
integration_tests/​src/​fixtures.rs Adds JSON-RPC mocks for getAccountInfo durable nonce reads and withdrawal submission/finalization.
integration_tests/​Cargo.toml Adds deps needed for nonce account encoding/decoding in integration tests.

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

Comment on lines +160 to +165
if read_state(|s| s.can_create_withdrawal_transaction()) {
runtime.set_timer(
WITHDRAWAL_PROCESSING_RETRY_DELAY,
process_pending_withdrawals,
);
}
Comment on lines +373 to +378
for (burn_index, pending) in state
.pending_withdrawal_requests()
.iter()
.rev()
.chain(state.created_withdrawal_requests().iter().rev())
{
Comment thread minter/src/state/nonce_pool/mod.rs Outdated
Copilot AI balanced review requested due to automatic review settings October 6, 2026 11:05
@gregorydemay
gregorydemay force-pushed the feat/nonce-withdrawal-submission branch from 1a30f15 to 9fc03a3 Compare October 6, 2026 11:05

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.

Comment thread minter/src/state/event.rs
Comment on lines 285 to 289
#[derive(Clone, Eq, PartialEq, Debug, Decode, Encode)]
pub enum TransactionPurpose {
/// Withdraw SOL to users' Solana addresses.
#[n(0)]
WithdrawSol {
/// The ledger burn indices of the withdrawal requests included in this transaction.
#[cbor(n(0), with = "cbor::id_vec")]
burn_indices: Vec<LedgerBurnIndex>,
},
/// Sweep the deposit addresses of deposits queued by `deposit_sol` into the minter's main account.
#[n(1)]
SweepDeposits {
Comment thread minter/src/state/mod.rs
pending_withdrawal_request_guards: BTreeSet<Account>,
deposits: Deposits,
pending_withdrawal_requests: BTreeMap<LedgerBurnIndex, PendingWithdrawalRequest>,
created_withdrawal_requests: BTreeMap<LedgerBurnIndex, PendingWithdrawalRequest>,
Comment thread minter/src/state/mod.rs Outdated
failed_withdrawal_requests: BTreeMap<LedgerBurnIndex, SentWithdrawalRequest>,
submitted_transactions: InsertionOrderedMap<Signature, SolanaTransaction>,
created_withdrawal_txs: BTreeMap<Address, CreatedWithdrawalTransaction>,
submitted_withdrawal_txs: InsertionOrderedMap<Signature, SubmittedWithdrawalTransaction>,
Copilot AI balanced review requested due to automatic review settings October 6, 2026 11:09

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.

Comment thread minter/src/monitor/mod.rs Outdated
Comment on lines +107 to +111
let signatures: Vec<Signature> = blockhash_transactions
.keys()
.chain(withdrawal_signatures.iter())
.copied()
.collect();
Copilot AI balanced review requested due to automatic review settings October 6, 2026 11:13

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.

Copilot review overview

🔵 Needs a closer look

Removing the legacy persisted event variant breaks post-upgrade replay, and withdrawal processing has uncapped and coupled outcall paths.

Review effort: Balanced
Findings: 3 High severity · 3 Medium severity · 1 Low severity

Open (7)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Finalize durable withdrawals despite block RPC failures

minter/​src/​monitor/​mod.rs:103

When even one blockhash-based sweep exists, a get_recent_block failure returns before checking any durable-withdrawal signatures. Thus a block RPC outage prevents otherwise independent withdrawals from finalizing and freeing their nonce accounts (and the immediate retry can repeatedly hit the same failing block call). Check transaction statuses regardless, and use the block height only when deciding whether a missing blockhash transaction expired.

Medium severity Cap accumulated transactions before concurrent RPC submission

minter/​src/​withdraw/​mod.rs:286

This collects every previously created transaction, not just this round's capped batches. If signing fails across rounds, up to the entire nonce pool can accumulate here; once signing recovers, all of them are signed and passed to send_transactions_batch concurrently, bypassing MAX_CONCURRENT_RPC_CALLS. Cap this snapshot so recovery cannot create an unbounded burst of SOL RPC submissions.

Copilot AI balanced review requested due to automatic review settings October 6, 2026 11:32
@gregorydemay
gregorydemay force-pushed the feat/nonce-withdrawal-submission branch from 2c428ee to 7f913d3 Compare October 6, 2026 11:32

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.

Copilot AI balanced review requested due to automatic review settings October 6, 2026 12:31
Comment thread minter/cksol_minter.did
Comment on lines +538 to +539
// The durable nonce account bound to the signed transaction.
nonce_account: Address;

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.

Why do we need the nonce_account as part of SignedTransaction? We could identify the nonce account based on the nonce value used as block hash in the transaction if the full signed transaction was part of the event

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.

Copilot AI balanced review requested due to automatic review settings October 6, 2026 13:09
@gregorydemay
gregorydemay force-pushed the feat/nonce-withdrawal-submission branch from ce2fe8a to 701613f Compare October 6, 2026 13:09

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.

@gregorydemay
gregorydemay force-pushed the feat/nonce-withdrawal-submission branch from 701613f to e40e717 Compare October 6, 2026 14:19
Copilot AI balanced review requested due to automatic review settings October 6, 2026 14:19
@gregorydemay
gregorydemay removed this pull request from stack #250 October 6, 2026 14:20

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.

@gregorydemay
gregorydemay changed the base branch from feat/nonce-account-reading to refactor/minter-transaction-enum October 6, 2026 14:26
@gregorydemay
gregorydemay added this pull request to stack #253 October 6, 2026 14:26
@gregorydemay
gregorydemay removed this pull request from stack #253 October 6, 2026 14:29
@gregorydemay
gregorydemay added this pull request to stack #254 October 6, 2026 14:29
Copilot AI balanced review requested due to automatic review settings October 6, 2026 14:42

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.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gregorydemay
gregorydemay force-pushed the feat/nonce-withdrawal-submission branch from 3cb0347 to 4cd61e4 Compare October 6, 2026 15:02
@gregorydemay
gregorydemay removed this pull request from stack #254 October 6, 2026 15:02
@gregorydemay
gregorydemay changed the base branch from refactor/minter-transaction-enum to feat/nonce-withdrawal-events October 6, 2026 15:02
@gregorydemay
gregorydemay added this pull request to stack #256 October 6, 2026 15:02
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.

2 participants