Skip to content

Rust special-account storage phase diverges from C++ and can make mixed validators derive different state roots #27

@EmelyanenkoK

Description

@EmelyanenkoK

Summary

Rust short-circuits the storage phase for special accounts and collects no storage fees at all. C++ does not. C++ still computes and collects storage fees for special accounts, and it still clears previously accumulated due_payment when the balance covers it. Only freeze/delete transitions are treated specially there.

Because storage fees and due_payment feed directly into replayed transaction output and account state, this discrepancy is consensus-critical.

Affected code

  • Rust: rust_implementation/ton-rust-node/src/executor/src/transaction_executor.rs:163-189
  • Rust: rust_implementation/ton-rust-node/src/vm/src/smart_contract_info.rs:104-112
  • C++: crypto/block/transaction.cpp:1056-1123

Rust/C++ discrepancy

Rust exits early for is_special:

if is_special {
    return Ok(TrStoragePhase::with_params(
        Coins::zero(),
        acc.due_payment().cloned(),
        AccStatusChange::Unchanged,
    ));
}

That means Rust does not:

  • compute current storage fees
  • add prior due_payment
  • debit balance for storage fees
  • clear due_payment when it is covered

C++ still performs fee accounting for special accounts:

  • computes current storage fees
  • adds previous due_payment
  • collects what can be collected
  • clears due_payment when fully covered

The special-account exemption in C++ only suppresses freeze/delete consequences and last_paid handling. It is not a full storage-phase bypass.

Trigger

The discrepancy is reachable on any special-account transaction where storage accounting is non-zero, including:

  • tick-tock execution
  • ordinary execution paths that run on special accounts
  • cases with existing non-zero due_payment
  • cases where current storage fees accrued since last_paid

This does not require an invalid block or malformed transaction. Honest chain activity is enough once a special account reaches one of those states.

Why this can stop a mixed Rust/C++ network

For the same special-account transaction, Rust and C++ can derive different values for:

  • storage_fees_collected
  • storage_fees_due
  • account balance after storage phase
  • persisted due_payment
  • TVM-visible DUEPAYMENT in the contract context
  • final account state and state root

Because Rust also exposes account.due_payment() through SmartContractInfo, the discrepancy is not limited to the storage-phase descriptor. It can change later compute/action behavior inside the same transaction.

That makes the replay result implementation-dependent:

  • a Rust validator replaying a C++-produced block can compute a different transaction result and reject the block
  • a C++ validator replaying a Rust-produced block can do the same in the opposite direction

In a mixed validator set, a single block containing such a special-account transaction can split validator votes and stop consensus. No attacker is required if the relevant special-account state occurs naturally; an attacker only makes the trigger easier.

Suggested fix

Remove the Rust early return for is_special.

Rust should match C++ by:

  • computing storage fees for special accounts
  • adding existing due_payment
  • debiting balance
  • clearing due_payment when covered
  • keeping only the C++ special-case behavior for freeze/delete handling and related status transitions

Suggested regression test

Build a replay fixture for a special account with non-zero storage debt or accrued storage fees.

The test should compare Rust and C++ on:

  • TrStoragePhase
  • balance after transaction
  • persisted due_payment
  • final transaction/state hash

Rust should match the C++ result exactly after the fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions