Skip to content

[UTXO-BUG] fix compute_state_root() little-endian count_bytes — consensus divergence#6510

Closed
Ivan-LB wants to merge 2 commits into
Scottcjn:mainfrom
Ivan-LB:utxo-state-root-endian-fix
Closed

[UTXO-BUG] fix compute_state_root() little-endian count_bytes — consensus divergence#6510
Ivan-LB wants to merge 2 commits into
Scottcjn:mainfrom
Ivan-LB:utxo-state-root-endian-fix

Conversation

@Ivan-LB
Copy link
Copy Markdown
Contributor

@Ivan-LB Ivan-LB commented May 28, 2026

Bug

utxo_db.py line 861 uses little-endian for count_bytes in compute_state_root(), while every other integer-to-bytes call in the module uses big-endian (network byte order):

Function Encoding
compute_box_id() — value_nrtc, creation_height, output_index 'big'
compute_tx_id() — timestamp 'big'
Module docstring "Uses big-endian (network byte order) for integer encodings… ensuring cross-platform deterministic hashing"
compute_state_root() — count_bytes ← line 861 'little' ← BUG

Impact

For any UTXO set with more than 0 elements, 'little' and 'big' produce different byte sequences:

count=2: little=0200000000000000   ← old code
         big   =0000000000000002   ← correct (all other int encodings)

Every leaf hash is SHA256(count_bytes || leaf_json), so a single-byte position difference causes completely different Merkle roots. Two nodes — one running old code and one running the fix — will disagree on the state root for the same UTXO set, triggering a consensus split the moment the fix is deployed.

The module docstring claims "All nodes with the same UTXO set produce the same root" — violated as soon as nodes diverge on endianness.

Fix

# utxo_db.py:861 — before
count_bytes = len(rows).to_bytes(8, 'little')

# after
count_bytes = len(rows).to_bytes(8, 'big')

Test

node/test_utxo_state_root_endian_poc.py (new file, 3 tests, all pass after fix):

  1. test_count_bytes_endian_divergence — shows LE ≠ BE for counts 1–10,000
  2. test_state_root_diverges_for_two_boxes — same UTXO set → different roots old vs new
  3. test_fixed_code_uses_big_endian — fixed compute_state_root() matches big-endian reference
Ran 3 tests in 0.015s
OK

Bounty Reference

Issue #2819 — Merkle state root manipulation, Medium severity (50 RTC).

RTC Wallet: Ivan-LB

…nsus divergence

Bug: utxo_db.py:861 used len(rows).to_bytes(8, 'little') while every
other integer encoding in the module uses 'big' (network byte order):
  - compute_box_id: value_nrtc, creation_height, output_index → 'big'
  - compute_tx_id:  timestamp → 'big'
  - Module docstring: "Uses big-endian (network byte order)..."

Impact: nodes running old code compute a different Merkle state root than
nodes running fixed code for the same UTXO set — a latent consensus fork.
The single-box case is not masked (LE ≠ BE for any count > 0).

Fix: change 'little' to 'big' on count_bytes line.
Add PoC test confirming old/new roots diverge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Welcome to RustChain! Thanks for your first pull request.

Before we review, please make sure:

  • Non-doc PRs have a BCOS-L1 or BCOS-L2 label
  • Doc-only PRs are exempt from BCOS tier labels when they only touch docs/**, *.md, or common image/PDF files
  • New code files include an SPDX license header
  • You've tested your changes against the live node

Bounty tiers: Micro (1-10 RTC) | Standard (20-50) | Major (75-100) | Critical (100-150)

A maintainer will review your PR soon. Thanks for contributing!

@github-actions github-actions Bot added BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related size/M PR: 51-200 lines labels May 28, 2026
…de try

Lines 239-251 were outdented out of the try block, placing code between
the try body and the except clause — a SyntaxError on collection.
Re-indent to restore the intended try/except/finally structure.

Pre-existing bug in main; fixing to unblock CI on this PR.
@Ivan-LB Ivan-LB closed this May 28, 2026
@Ivan-LB Ivan-LB deleted the utxo-state-root-endian-fix branch May 28, 2026 08:20
@Scottcjn
Copy link
Copy Markdown
Owner

Hi @Ivan-LB — I see you self-closed PR #6510 and #6511, but both findings are legitimate and reproduce as described:

These deserve bug-report bounty (#305) at the Medium tier (~15-25 RTC each) plus the fix-PR severity (#2819) at Medium-High depending on Codex audit.

Could you:

  1. Reply with your RTC wallet (format: RTC + 40 hex chars) — or use a miner_id string like Ivan-LB if you'd rather
  2. (Optional) Re-open the PRs or file fresh ones — your fix is correct and we'd merge them gladly

We're at ~761 holders ($0.10 reference rate) — your work is paid at current rates regardless of when you respond. See rustchain-bounties#12458 for the rate-reduction schedule going forward.

@Ivan-LB
Copy link
Copy Markdown
Contributor Author

Ivan-LB commented May 28, 2026

Hi @Scottcjn, thanks for confirming.
Correction to my previous comment — updated wallet address:

RTC Wallet: RTC64aa3fc417e75224e1574acae906fea34d94d140

Also, since the original branches were deleted the PRs could not be reopened, so I filed fresh ones instead:

Both include PoC tests that pass. Sorry for the confusion.

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

Labels

BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related size/M PR: 51-200 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants