RFC: 254 Guardian prove and commit transaction - #370
Conversation
WalkthroughThis 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. ChangesGuardian server-side execution
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 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
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
…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>
fd07eca to
668931e
Compare
There was a problem hiding this comment.
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 liftTest the complete envelope compatibility contract.
The fixture requirements check checksum and protocol line but omit
serializer_idandformat_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 valueImport
NoteTypeonce.Line 17 imports
miden_protocol::note::NoteType. Line 24 imports the same type again asProtoNoteType. 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
ProtoNoteTypeuse withNoteType.🤖 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 valueAssert 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 againstexpected_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 winReuse the account builders instead of repeating them inline.
signed_account_and_chainbuilds 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 valueSkip instead of failing when the sampled tag matches no testnet notes.
The assertion fails when no note on the public testnet carries tag
0in 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 winMove
miden-remote-prover-clientto[workspace.dependencies]. Add the shared declaration inCargo.toml, then reference it withworkspace = trueincrates/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 winAdd language tags to the fenced blocks.
Use
textfor 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
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (21)
AGENTS.mdcrates/miden-rpc-client/README.mdcrates/miden-rpc-client/src/lib.rscrates/server/Cargo.tomlcrates/server/src/network/miden/execution/blockchain.rscrates/server/src/network/miden/execution/live_tests.rscrates/server/src/network/miden/execution/mod.rscrates/server/src/network/miden/execution/store.rscrates/server/src/network/miden/execution/tests.rscrates/server/src/network/miden/mod.rsdocs/rfcs/0001-server-side-transaction-execution.mddocs/rfcs/README.mdspeckit/features/254-guardian-prove-and-commit/contracts/execution-api.mdspeckit/features/254-guardian-prove-and-commit/contracts/sdk-api.mdspeckit/features/254-guardian-prove-and-commit/data-model.mdspeckit/features/254-guardian-prove-and-commit/plan.mdspeckit/features/254-guardian-prove-and-commit/quickstart.mdspeckit/features/254-guardian-prove-and-commit/research.mdspeckit/features/254-guardian-prove-and-commit/spec.mdspeckit/features/254-guardian-prove-and-commit/tasks.mdspeckit/features/254-guardian-prove-and-commit/validation-matrix.md
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| end | ||
| ``` | ||
|
|
||
| ### 1.2 End-to-End Execution Lifecycle & State Machine |
There was a problem hiding this comment.
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:
- A user can submit a transaction request to the Guardian.
- 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.
- Once the validity is verified, the Guardian will convert the request into a transaction to derive the new account state.
- 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. - Once the transaction is signed, the Guardian will execute it, and submit it to the network.
- 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:
- 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.
- 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
AtoB, but before the Guardian has had a chance to prove this transaction, another request that takes the account fromBtoCarrives. 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). - What are the states in which account on the Guardian could be in? I'm assuming there is a
committedstate (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, stateAmay becommitted, stateBmay beproving, and stateCcould bevalidated- or something like that.
There was a problem hiding this comment.
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 theTransactionSummary, 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, orfailed. - Delta:
candidate,canonical,retained, ordiscarded.
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 throughpendingandproving. No candidate delta exists yet andBis only the expected result. - When Guardian crosses the no-retry boundary, execution becomes
submittedand anA -> Bcandidate delta exists. The canonical state is stillA. - If Guardian observes the expected commitment on chain, the candidate becomes canonical, the account advances to
B, and execution becomeslanded. - If the node definitively rejects it, the account moves to a different commitment, or the transaction expires while the account stays at
A, execution becomesfailed. The candidate is discarded or removed and the reservation is released. - A
retaineddelta 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.
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
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
Documentation
Tests