walk_execution_chain silently drops transactions when a group has multiple disjoint chains
- 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
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