0xMiden / 0xMiden/rust-sdk

walk_execution_chain silently drops transactions when a group has multiple disjoint chains

Open
#2,388 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
78
Forks
129
Avg merge
4d 14h
Merged PRs (30d)
52

Description

`walk_execution_chain` in `crates/rust-client/src/sync/state_sync.rs` silently drops transactions when a `(account_id, block_num)` group from the RPC contains more than one independent execution chain.

The function finds the chain "start" via:
```rust
let start = chained
.iter()
.find(|tx| !final_states.contains(&tx.transaction_header.initial_state_commitment()))
.copied();
```
`find` returns only the first transaction whose `initial_state_commitment` isn't anyone's `final_state_commitment`. If the group actually contains two (or more) disjoint chains, each has its own such "unreferenced" start, but only one is ever picked up and walked; the other chain's transactions are never yielded by the iterator.

I confirmed this with a test: build two independent 2-tx chains for the same account/block (`1->2->3` and `100->101->102`, no shared state commitments), feed all 4 records into `walk_execution_chain`, and only 2 of the 4 come back.

Impact: `compute_ordered_nullifiers` (which calls `walk_execution_chain` per group) feeds directly into `note_updates.extend_nullifiers(...)` in `apply_transactions_and_nullifiers`, i.e. this is how the client learns which input notes got consumed. A dropped chain means its nullifiers are never recorded, so the client's local store can end up believing an already-spent note is still unspent.

I don't think this is reachable from a single honestly-behaving node under normal operation, since one account's nonce sequence should only ever form one chain per block. But the RPC response is otherwise treated as untrusted here (there are dedicated tests elsewhere for tampered/malformed sync responses), and the existing `assert!(start.is_some() || chained.is_empty(), "cannot walk cyclic execution chain")` only guards the cyclic case, not this one - a malformed or buggy response with two disjoint chains for the same group passes through silently instead of erroring or being fully processed.

I already have a working regression test for this. Happy to send a PR if it can be assigned to me.

Contributor guide

Open the contributing guide

Research direction

The bug is in `walk_execution_chain` in `crates/rust-client/src/sync/state_sync.rs`. Start by reading the function and the test the reporter mentions. Understand how `chained` groups are formed and how the start of a chain is identified. The fix must handle multiple disjoint chains per group, either by walking all chains or by rejecting such input. Verify the fix by running existing tests and adding a regression test for the described scenario.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.