Skip to content

RFC: 254 Guardian prove and commit transaction - #370

Open
zeljkoX wants to merge 2 commits into
mainfrom
254-guardian-prove-and-commit
Open

RFC: 254 Guardian prove and commit transaction#370
zeljkoX wants to merge 2 commits into
mainfrom
254-guardian-prove-and-commit

Conversation

@zeljkoX

@zeljkoX zeljkoX commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

RFC proposing server-side Miden transaction execution in Guardian.

Main decision

Guardian assembles the transaction witness, delegates proving to a remote
prover, submits the proven transaction, and observes the final outcome.

Review focus

  • Overall architecture and security assumptions
  • Chain-view and authenticated-note assembly
  • Execution lifecycle and no-retry boundary
  • Questions intended for the Miden team
  • Missing cases or unclear sections

Scope

This PR contains the RFC and supporting research/validation artifacts.
It does not implement the complete feature.

RFC

Read the rendered RFC

Please leave review feedback and proposed changes in this PR.

Summary by CodeRabbit

  • New Features

    • Added support for assembling authenticated blockchain and note data for transaction execution.
    • Added RPC capabilities for chain synchronization and paginated note synchronization.
    • Added execution data handling for account, transaction, and blockchain inputs.
    • Introduced configurable Guardian-managed proving and execution modes.
  • Documentation

    • Added comprehensive API contracts, SDK guidance, quickstart instructions, RFC documentation, and implementation plans for Guardian prove-and-commit execution.
  • Tests

    • Added extensive integration and live-network validation for synchronization, transaction execution, proving, signing, and expiration behavior.

@zeljkoX
zeljkoX requested a review from haseebrabbani as a code owner August 3, 2026 08:21
@zeljkoX zeljkoX changed the title 254 guardian prove and commit RFC: 254 guardian prove and commit Aug 3, 2026
@zeljkoX zeljkoX self-assigned this Aug 3, 2026
@zeljkoX zeljkoX added the rfc label Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This change adds RFC and Speckit documentation for Guardian server-side transaction execution. It also adds Miden RPC synchronization, partial blockchain assembly, execution witness storage, proving feature wiring, integration tests, and live-network validation.

Changes

Guardian server-side execution

Layer / File(s) Summary
Execution contracts and architecture
docs/rfcs/*, speckit/features/254-guardian-prove-and-commit/contracts/*, speckit/features/254-guardian-prove-and-commit/spec.md
Defines execution APIs, SDK modes, request envelopes, lifecycle states, reservations, retries, reconciliation, and proving requirements.
Implementation plan and validation scope
speckit/features/254-guardian-prove-and-commit/plan.md, quickstart.md, research.md, tasks.md, validation-matrix.md
Documents implementation phases, operational setup, research findings, task sequencing, and validation coverage.
Chain synchronization and witness assembly
crates/miden-rpc-client/*, crates/server/src/network/miden/execution/blockchain.rs, crates/server/src/network/miden/mod.rs
Adds chain-MMR and paginated note synchronization, validates reference commitments, and builds partial blockchain data for execution.
Execution data store and proving validation
crates/server/src/network/miden/execution/store.rs, tests.rs, live_tests.rs, Cargo.toml
Adds per-execution account and witness storage, feature-gated proving support, integration tests, and ignored live-network proving tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: haseebrabbani

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant MidenRpcClient
  participant PartialBlockchain
  participant ExecutionDataStore
  participant RemoteProver

  Client->>MidenRpcClient: request chain MMR and note data
  MidenRpcClient->>PartialBlockchain: assemble authenticated chain view
  PartialBlockchain->>ExecutionDataStore: provide account and blockchain witnesses
  ExecutionDataStore-->>Client: execute transaction inputs
  Client->>RemoteProver: submit serialized transaction inputs
  RemoteProver-->>Client: return proof result
Loading

Poem

A rabbit hops through blocks of green,
And gathers proofs from paths unseen.
MMR peaks rise, notes align,
A data store keeps witnesses fine.
Remote stars produce the proof—
Guardian builds a safer roof.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the RFC and its main subject: Guardian prove-and-commit transaction execution.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 254-guardian-prove-and-commit

Comment @coderabbitai help to get the list of available commands.

@zeljkoX zeljkoX moved this from Backlog to Review in OZ Development for Miden Aug 3, 2026
…ution spike

Specify server-side transaction execution: cosigners sign a proposal to
threshold and Guardian executes, proves via remote prover, submits, and
tracks the outcome, so triggering callers need no Miden capabilities.

- speckit artifacts for feature 254: spec, plan, tasks, data model,
  execution/SDK contracts, research log, validation matrix
- RFC 0001 (docs/rfcs/) distilling the design for upstream review, with
  protocol questions for the Miden team
- Gate 0 spike: miden_tx::DataStore over Guardian state, PartialBlockchain
  assembly from node RPC (genesis-anchored SyncChainMmr cold start,
  SyncNotes snapshot-pinned note-block paths), full sign-and-prove path
  validated offline and against public testnet

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zeljkoX
zeljkoX force-pushed the 254-guardian-prove-and-commit branch from fd07eca to 668931e Compare August 3, 2026 08:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
speckit/features/254-guardian-prove-and-commit/contracts/sdk-api.md (1)

246-257: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Test the complete envelope compatibility contract.

The fixture requirements check checksum and protocol line but omit serializer_id and format_version. A changed serializer prerelease can remain within the same protocol line and still require rejection before deserialization.

  • speckit/features/254-guardian-prove-and-commit/contracts/sdk-api.md#L246-L257: add a same-line, different-serializer fixture and assert all required envelope fields.
  • speckit/features/254-guardian-prove-and-commit/spec.md#L1072-L1074: extend SC-029 to cover serializer identity and format version.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@speckit/features/254-guardian-prove-and-commit/contracts/sdk-api.md` around
lines 246 - 257, The cross-language fixture contract must cover every envelope
compatibility field, including serializer identity and format version. In
speckit/features/254-guardian-prove-and-commit/contracts/sdk-api.md lines
246-257, add a same-protocol-line/different-serializer fixture and assert
checksum, protocol_line, serializer_id, and format_version, with rejection
before deserialization. In
speckit/features/254-guardian-prove-and-commit/spec.md lines 1072-1074, extend
SC-029 to require validation of serializer identity and format version.
🧹 Nitpick comments (6)
crates/server/src/network/miden/execution/tests.rs (3)

17-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Import NoteType once.

Line 17 imports miden_protocol::note::NoteType. Line 24 imports the same type again as ProtoNoteType. Two names for one type make the tests harder to read. Keep one name.

♻️ Proposed change
-use miden_protocol::note::NoteType;
 ...
-use miden_protocol::note::{NoteAttachments, NoteType as ProtoNoteType};
+use miden_protocol::note::{NoteAttachments, NoteType};

Then replace each ProtoNoteType use with NoteType.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/server/src/network/miden/execution/tests.rs` around lines 17 - 24,
Remove the aliased ProtoNoteType import from the test module and retain a single
miden_protocol::note::NoteType import alongside NoteAttachments. Replace every
ProtoNoteType reference in the tests with NoteType.

455-459: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert the emitted note identity, not only the count.

The assertion checks that one note was emitted. It does not check that the note is expected_note. A script that emits a different single note passes this test. Compare the output note against expected_note.id().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/server/src/network/miden/execution/tests.rs` around lines 455 - 459,
Update the output-note assertion in the test around executed.output_notes() to
verify the emitted note’s identity matches expected_note.id(), rather than only
asserting that exactly one note exists. Preserve the existing single-note
expectation while adding the identity comparison.

306-327: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the account builders instead of repeating them inline.

signed_account_and_chain builds a keyed 1-of-1 multisig account and its chain. Lines 203-217, 399-408, and 533-542 repeat the same sequence inline. The only variation is whether the vault is funded before the chain is built. Add one helper that accepts the assets to fund, and call it from each test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/server/src/network/miden/execution/tests.rs` around lines 306 - 327,
Refactor the repeated multisig account and MockChain construction into a shared
helper based on signed_account_and_chain, adding an assets-to-fund parameter
that funds the account before building the chain when needed. Update the inline
setup blocks around the referenced tests to call this helper, preserving the
existing unfunded behavior where no assets are required and returning the same
account, chain, cosigner, and guardian values.
crates/server/src/network/miden/execution/live_tests.rs (1)

146-149: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Skip instead of failing when the sampled tag matches no testnet notes.

The assertion fails when no note on the public testnet carries tag 0 in the scanned 10,000-block window. That is an environment condition, not a code defect. The test already uses a skip path at lines 89-92 for a short chain. Use the same pattern here so a testnet reset does not read as a Guardian regression.

♻️ Proposed change
-    assert!(
-        !tracked.is_empty(),
-        "SyncNotes found no testnet notes for the sampled account-target tag"
-    );
+    if tracked.is_empty() {
+        eprintln!(
+            "LIVE SyncNotes found no testnet notes for the sampled account-target tag; skipping"
+        );
+        return;
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/server/src/network/miden/execution/live_tests.rs` around lines 146 -
149, Replace the tracked.is_empty() assertion in the live test with the existing
skip pattern used for short chains, so the test skips when no testnet notes
match the sampled account-target tag instead of failing. Preserve the normal
validation path when tracked contains notes.
crates/server/Cargo.toml (1)

79-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move miden-remote-prover-client to [workspace.dependencies]. Add the shared declaration in Cargo.toml, then reference it with workspace = true in crates/server/Cargo.toml. This centralizes Miden version resolution and prevents future dependency drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/server/Cargo.toml` at line 79, Move the miden-remote-prover-client
version and feature declaration from the server crate’s dependency entry into
the root workspace.dependencies section, then update the server dependency to
use workspace = true while preserving its optional setting and required
features.

Source: Learnings

speckit/features/254-guardian-prove-and-commit/research.md (1)

352-355: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add language tags to the fenced blocks.

Use text for the witness type list and the expiration output. This resolves the MD040 warnings and keeps the rendered research document consistent.

Also applies to: 489-492

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@speckit/features/254-guardian-prove-and-commit/research.md` around lines 352
- 355, Add text language tags to the fenced code blocks containing the witness
type list and expiration output, including both locations identified by the
review, while leaving their contents unchanged.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/server/src/network/miden/execution/live_tests.rs`:
- Around line 296-308: The live test currently exposes the raw prover_url in
both the failure panic and status println. Wrap the GUARDIAN_TX_PROVER_URL value
with the existing CredentialUrl secret wrapper, or otherwise use its redacting
Display, and update the output sites around the remote prover execution to print
only the sanitized URL while preserving the existing diagnostics.

In `@docs/rfcs/0001-server-side-transaction-execution.md`:
- Around line 338-340: Remove the blank line between the two blockquote
paragraphs in the RFC, or prefix that line with “>”, so the warning and
canonicalization text remain a single contiguous blockquote and satisfy
markdownlint MD028.
- Around line 122-124: Use “on-chain” consistently by changing “observed on
chain” to “observed on-chain” in the Landed lifecycle description in
docs/rfcs/0001-server-side-transaction-execution.md (lines 122-124), and make
the same wording change in
speckit/features/254-guardian-prove-and-commit/quickstart.md (lines 130-132).

In `@docs/rfcs/README.md`:
- Line 10: Update the RFC index entry for 0001 to use the exact canonical status
value “Accepted for implementation” in the status column, removing the appended
“— comments welcome” text while leaving the title and issue link unchanged.

In `@speckit/features/254-guardian-prove-and-commit/contracts/execution-api.md`:
- Around line 116-120: Add a suitable language tag, such as text, to the fenced
state-transition block documenting the pending, proving, and submitted
transitions. Keep the transition contents unchanged.
- Around line 177-193: The Error codes table under “Synchronous refusals”
incorrectly includes the status-read-only GUARDIAN_EXECUTION_NOT_FOUND entry.
Move it to a separate status-read error section, or explicitly scope that row to
status reads, while keeping the POST refusal table limited to errors returned by
POST.

In `@speckit/features/254-guardian-prove-and-commit/data-model.md`:
- Around line 285-308: Preserve stale-worker fencing for filesystem mutations by
validating lease ownership and the current fence while holding the existing
delta_write_lock, covering submit_delta, request_candidate_abandon,
update_delta_status, and update_candidate_status; update
speckit/features/254-guardian-prove-and-commit/data-model.md:285-308 to document
this guarantee instead of accepting the limitation. Update
speckit/features/254-guardian-prove-and-commit/plan.md:91-104 to reflect the
backend decision and add tests covering stale workers losing and then
reacquiring the in-process lock.

In `@speckit/features/254-guardian-prove-and-commit/research.md`:
- Around line 533-538: The resolved live-test status must be synchronized across
both documents: in speckit/features/254-guardian-prove-and-commit/research.md
lines 533-538, remove the unqualified “[NEEDS LIVE CONFIRMATION]” marker or
label it as historical and cite the completed live test; in
speckit/features/254-guardian-prove-and-commit/validation-matrix.md lines 57-63,
remove “RPC path itself is unexercised” or move it into the superseded
historical section.
- Around line 62-65: Update the dependency statement in the Guardian read-only
discussion to reflect that miden-tx is enabled by both the proving and e2e
features, rather than solely by e2e. Alternatively, explicitly label the
statement as pre-feature context; keep the surrounding raw gRPC read description
accurate.
- Around line 311-333: Supersede the round-1 witness recipe in the section
beginning “The witness assembly” and update the design bullets to use
AssetVault::open and StorageMap::open instead of AccountSmtForest or
miden-processor. Remove direct miden-client and miden-processor dependency
guidance for the proving feature, retaining miden-client references only in the
optional e2e feature context.

In `@speckit/features/254-guardian-prove-and-commit/spec.md`:
- Around line 787-797: The minimum submission evidence must include the base
commitment required for expired-versus-superseded decisions. In
speckit/features/254-guardian-prove-and-commit/spec.md lines 787-797, update
FR-039 to require persisting base_commitment before submission alongside the
existing evidence; in docs/rfcs/0001-server-side-transaction-execution.md lines
120-125, add base_commitment to the RFC evidence list.
- Around line 836-850: Define bounded recovery when chain observation is
unavailable: in speckit/features/254-guardian-prove-and-commit/spec.md lines
836-850, add retry, failover, and operator-recovery behavior for FR-040; in
docs/rfcs/0001-server-side-transaction-execution.md line 127, qualify the
termination guarantee or state the required availability assumption; in
speckit/features/254-guardian-prove-and-commit/data-model.md lines 420-449,
document the unavailable-observation state and recovery behavior; in plan.md
lines 311-327, add implementation tasks for bounded reconciliation recovery; and
in quickstart.md lines 137-142, describe operator-visible behavior during node
outages.

In `@speckit/features/254-guardian-prove-and-commit/tasks.md`:
- Line 72: Update the execution configuration contract to define
GUARDIAN_TX_PROVER_TIMEOUT_SECS with a default of 300 seconds, then update its
unit-test default assertion accordingly. Keep the quickstart and RFC
configuration values synchronized with this 300-second default.
- Line 136: Update the FR-045 step 7 expiration-horizon check in
execute_proposal to use ProvenTransaction::expiration_block_num() instead of
ExecutedTransaction, since the expiration value must be available before
submission. Keep the existing permanent-failure and retry behavior unchanged.

In `@speckit/features/254-guardian-prove-and-commit/validation-matrix.md`:
- Around line 240-242: Update the “Seeding overhead measurement” entry in the
validation matrix to measure direct miden_tx::DataStore and SMT setup per
execution. Remove the SQLite-versus-in-memory Store decision and specify that
Gate 0 execution always uses ephemeral, memory-only state.
- Around line 240-242: Update the Cross-SDK envelope fixtures validation to
compare the complete serializer_id from both SDKs, including prerelease
identifiers. Add coverage that rejects an unallowlisted serializer_id mismatch
as GUARDIAN_EXECUTION_PROTOCOL_MISMATCH even when protocol_line matches.

---

Outside diff comments:
In `@speckit/features/254-guardian-prove-and-commit/contracts/sdk-api.md`:
- Around line 246-257: The cross-language fixture contract must cover every
envelope compatibility field, including serializer identity and format version.
In speckit/features/254-guardian-prove-and-commit/contracts/sdk-api.md lines
246-257, add a same-protocol-line/different-serializer fixture and assert
checksum, protocol_line, serializer_id, and format_version, with rejection
before deserialization. In
speckit/features/254-guardian-prove-and-commit/spec.md lines 1072-1074, extend
SC-029 to require validation of serializer identity and format version.

---

Nitpick comments:
In `@crates/server/Cargo.toml`:
- Line 79: Move the miden-remote-prover-client version and feature declaration
from the server crate’s dependency entry into the root workspace.dependencies
section, then update the server dependency to use workspace = true while
preserving its optional setting and required features.

In `@crates/server/src/network/miden/execution/live_tests.rs`:
- Around line 146-149: Replace the tracked.is_empty() assertion in the live test
with the existing skip pattern used for short chains, so the test skips when no
testnet notes match the sampled account-target tag instead of failing. Preserve
the normal validation path when tracked contains notes.

In `@crates/server/src/network/miden/execution/tests.rs`:
- Around line 17-24: Remove the aliased ProtoNoteType import from the test
module and retain a single miden_protocol::note::NoteType import alongside
NoteAttachments. Replace every ProtoNoteType reference in the tests with
NoteType.
- Around line 455-459: Update the output-note assertion in the test around
executed.output_notes() to verify the emitted note’s identity matches
expected_note.id(), rather than only asserting that exactly one note exists.
Preserve the existing single-note expectation while adding the identity
comparison.
- Around line 306-327: Refactor the repeated multisig account and MockChain
construction into a shared helper based on signed_account_and_chain, adding an
assets-to-fund parameter that funds the account before building the chain when
needed. Update the inline setup blocks around the referenced tests to call this
helper, preserving the existing unfunded behavior where no assets are required
and returning the same account, chain, cosigner, and guardian values.

In `@speckit/features/254-guardian-prove-and-commit/research.md`:
- Around line 352-355: Add text language tags to the fenced code blocks
containing the witness type list and expiration output, including both locations
identified by the review, while leaving their contents unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 976d0dc5-247d-4413-902b-e9c66d89729e

📥 Commits

Reviewing files that changed from the base of the PR and between 879754c and fd07eca.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • AGENTS.md
  • crates/miden-rpc-client/README.md
  • crates/miden-rpc-client/src/lib.rs
  • crates/server/Cargo.toml
  • crates/server/src/network/miden/execution/blockchain.rs
  • crates/server/src/network/miden/execution/live_tests.rs
  • crates/server/src/network/miden/execution/mod.rs
  • crates/server/src/network/miden/execution/store.rs
  • crates/server/src/network/miden/execution/tests.rs
  • crates/server/src/network/miden/mod.rs
  • docs/rfcs/0001-server-side-transaction-execution.md
  • docs/rfcs/README.md
  • speckit/features/254-guardian-prove-and-commit/contracts/execution-api.md
  • speckit/features/254-guardian-prove-and-commit/contracts/sdk-api.md
  • speckit/features/254-guardian-prove-and-commit/data-model.md
  • speckit/features/254-guardian-prove-and-commit/plan.md
  • speckit/features/254-guardian-prove-and-commit/quickstart.md
  • speckit/features/254-guardian-prove-and-commit/research.md
  • speckit/features/254-guardian-prove-and-commit/spec.md
  • speckit/features/254-guardian-prove-and-commit/tasks.md
  • speckit/features/254-guardian-prove-and-commit/validation-matrix.md

Comment thread crates/server/src/network/miden/execution/live_tests.rs
Comment thread docs/rfcs/0001-server-side-transaction-execution.md
Comment thread docs/rfcs/0001-server-side-transaction-execution.md
Comment thread docs/rfcs/README.md Outdated
Comment thread speckit/features/254-guardian-prove-and-commit/contracts/execution-api.md Outdated
Comment thread speckit/features/254-guardian-prove-and-commit/spec.md
Comment thread speckit/features/254-guardian-prove-and-commit/spec.md
Comment thread speckit/features/254-guardian-prove-and-commit/tasks.md Outdated
Comment thread speckit/features/254-guardian-prove-and-commit/tasks.md Outdated
Comment thread speckit/features/254-guardian-prove-and-commit/validation-matrix.md Outdated
@codecov-commenter

codecov-commenter commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 49.15254% with 30 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/miden-rpc-client/src/lib.rs 49.15% 30 Missing ⚠️

📢 Thoughts on this report? Let us know!

@zeljkoX zeljkoX changed the title RFC: 254 guardian prove and commit RFC: 254 Guardian prove and commit transaction Aug 3, 2026
end
```

### 1.2 End-to-End Execution Lifecycle & State Machine

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thank you for putting this together. I haven't gone through the full proposal yet, but want to make sure that I understand the overall approach first. One area I'm specifically interested in is how we'd handle conflicting requests and potential attack vectors associated with them.

My current understanding is as follows:

  1. A user can submit a transaction request to the Guardian.
  2. The guardian will verify that this request is valid - this includes making sure that the request builds on the latest known state of the account, that it is signed by one of the signers etc.
  3. Once the validity is verified, the Guardian will convert the request into a transaction to derive the new account state.
  4. Then, one of two things can happen:
    a. If there not enough signatures for the request, the Guardian will wait until sufficient number of signatures is collected. I'm assuming there will be a dedicated endpoint for this. Also, I'm assuming clients will be able to fetch the latest account state + the associated transaction data to make sure they sign it (or do they fetch transaction requests and then execute them locally?)
    b. Once the guardian has enough signatures, it will sign the transaction.
  5. Once the transaction is signed, the Guardian will execute it, and submit it to the network.
  6. Once the network commits the transaction, the Guardian will know that the transaction is committed and will mark it accordingly locally.

Assuming the process is like so (and please correct if not), I have the following questions:

  1. What happens if while the Guardian is in state 4a (i.e., the transaction request is accepted, but does not yet have enough signatures), another, potentially conflicting, transaction request is submitted? Would the pending request be replaced? Would the incoming request be rejected? Or would there be some kind of logic to resolve this? I'm asking this because depending on what we do here, some signers may try to block the account by submitting requests that other signers may not want to sign.
  2. How would the Guardian handle transaction request on top of currently executing transaction requests. That is, let's say request 1 takes account from state A to B, but before the Guardian has had a chance to prove this transaction, another request that takes the account from B to C arrives. Would the Guardian reject this request? Or build a set of chained requests? The latter is more complicated but also leads to a much better UX on the wallet side (something we'd want to enable).
  3. What are the states in which account on the Guardian could be in? I'm assuming there is a committed state (i.e., the state on the Guardian is the same as onchain) - but what other states would we have? This is especially interesting in the context of multiple in-flight requests for the same account. For example, using the above scenario, state A may be committed, state B may be proving, and state C could be validated - or something like that.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, you're close, but proposal and execution are two separate phases. Guardian can store multiple proposals and collect signatures without reserving the account. It only creates a reservation after a proposal has reached its effective threshold and a cosigner explicitly asks for delegated execution.

Flow:

  • A Miden-capable party builds the TransactionRequest, runs it at an authenticated reference-block anchor to get the TransactionSummary, and creates a proposal that carries the request, the summary, and the chain anchor.
  • Guardian authenticates the proposer as a registered cosigner, validates the summary against the current canonical state, checks the chain-anchor binding, tags the proposal with its base commitment, and stores it. It does not reserve the account or start delegated execution yet.
  • Cosigners fetch the proposal and sign the summary commitment. A thin cosigner can sign mechanically with just its key, but it cannot independently verify that the metadata it sees matches the transaction it is signing. A Miden-capable cosigner can rebuild the transaction locally and verify the commitment before signing. That is what the current multisig SDKs do.
  • Once the effective threshold is met, any registered cosigner can explicitly request delegated execution.
  • Guardian then creates the per-account execution reservation, reproduces the transaction using the proposal's authenticated reference-block anchor, confirms the canonical state still matches the proposal's base state, verifies the result matches the signed summary, and validates the collected signatures.
  • Guardian adds its own acknowledgment, executes the authorized transaction, proves it via the remote prover, atomically admits the candidate delta with the submission evidence, and submits the proven transaction.
  • The candidate becomes canonical once Guardian observes it has landed on chain.

1) Multiple proposals can coexist

A new proposal does not replace or reject an existing pending proposal, and creating a proposal does not lock the account. Only a proposal that has reached threshold and enters delegated execution gets the per-account reservation.

If two proposals become executable, the first execution request to acquire the reservation goes ahead. If a different proposal tries to execute while that reservation is active, it is refused synchronously with GUARDIAN_EXECUTION_CONFLICT and the response tells you which proposal is blocking. Re-triggering the same proposal is idempotent and just returns the existing execution. Direct self-execution through POST /delta is also refused while the reservation is active, so delegated and client-side execution cannot race.

A competing proposal based on the same canonical state can still be created while the first one is proving and before its candidate is admitted, but it cannot enter execution while the reservation is held. Once a candidate exists, creation of further proposals is blocked by the existing pending-candidate conflict. When one proposal lands, any proposals built on the previous state become stale, cannot execute, and no longer count against the viable pending-proposal limit.

Proposal creation is restricted to authenticated multisig signers, which prevents public spam. Guardian also caps the number of viable pending proposals per account. The abandon endpoint handles stuck candidate deltas after admission. It does not remove below-threshold pending proposals. Issue #351 proposes an archive/unarchive flow that would let the original proposer retract a pending proposal without hard deleting it, and archived proposals would not count toward the limit.

Together this prevents public spam, bounds storage, and gives a recovery path for accidental or obsolete proposals. It does not fully eliminate denial of service by a malicious or compromised authorized signer. Such a signer could still fill the proposal allowance and refuse to archive. That remaining insider liveness risk could be closed with a per-proposer quota, automatic expiry, or threshold-authorized archival.

2) No chained proposals in v1

In v1 we do not support chained proposals like A -> B -> C. Until A -> B lands, Guardian's canonical state stays at A. B is only the expected result of the in-flight transaction.

Guardian validates every new proposal against its current canonical state. So a real B -> C proposal submitted while canonical state is still A is rejected at creation time. Competing proposals based on A can coexist, but only one can enter execution.

A proposal that was valid at creation can still become stale before execution, for example because another proposal advanced the account first. Guardian therefore rechecks state and bindings when delegated execution starts. If the account no longer matches the proposal's base state, execution fails before proving or submission.

Chaining is a deliberate v1 non-goal, not a fundamental limitation. The current model is built around one canonical account state, at most one active candidate per account, a no-retry boundary for submission, and resolution by observing the chain. Supporting chains would require explicit dependencies, speculative intermediate states, ordering rules, and cascading invalidation when a parent is rejected, superseded, or expires.

Miden 0.16 also binds the reference block commitment and expiration delta into the signed summary. That makes advance signing of B -> C harder, because the child transaction cannot just be rebuilt later against a newer reference block without changing what cosigners signed. Chained wallet flows would likely need a separate intent or batching layer rather than just queueing today's transaction summaries.

3) Status lives on different objects

Guardian tracks status at three levels:

  • Proposal: stored as pending. Whether it looks ready on the client is derived from collected signatures. Before execution, Guardian recomputes readiness itself using only distinct, valid signatures from currently registered cosigners and the effective threshold.
  • Execution: pending, proving, submitted, landed, or failed.
  • Delta: candidate, canonical, retained, or discarded.

The account itself always has exactly one canonical state.

Taking A -> B as an example:

  • While signatures are being collected, the account remains canonical at A. The proposal is pending and may appear ready once enough signatures are present.
  • Once delegated execution is accepted, the account is still canonical at A, while execution moves through pending and proving. No candidate delta exists yet and B is only the expected result.
  • When Guardian crosses the no-retry boundary, execution becomes submitted and an A -> B candidate delta exists. The canonical state is still A.
  • If Guardian observes the expected commitment on chain, the candidate becomes canonical, the account advances to B, and execution becomes landed.
  • If the node definitively rejects it, the account moves to a different commitment, or the transaction expires while the account stays at A, execution becomes failed. The candidate is discarded or removed and the reservation is released.
  • A retained delta is an unresolved candidate that has been removed from the active blocking path but kept around for reconciliation.

So we would not describe B as an account state that is "proving", or C as "validated". Those statuses belong to proposals, executions, and deltas, not to speculative account states. In v1 the model is one canonical account state plus at most one active candidate transition, because chained in-flight transactions are not supported.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Review

Development

Successfully merging this pull request may close these issues.

4 participants