Skip to content

[CRITICAL][Tier-A] Reputation lazy Merkle-sum tree with incremental on-chain scoring #387

Description

@jotel-dev

Tier
Tier A · CRITICAL — Gas-efficient stateful aggregation.

Context
contracts/reputation/src/lib.rs compute_score iterates up to MAX_TRADES (200) sequential trades by calling get_trade_by_index + get_trade on the escrow contract for every single index. On a busy mainnet deployment this is ~200 cross-contract calls per compute_score. The score is recomputed from scratch every time, even when only one new trade changed.

Goal
Replace the linear scan with a Merkle-sum tree (MST) maintained by the escrow contract. Each leaf is a trade's (amount, status, timestamp). The root is updated atomically on release / refund / dispute_resolve. The reputation contract then validates a sparse Merkle proof (≤3 hashes) instead of scanning.

Non-goals
No off-chain indexer that feeds the tree (the root must be derivable purely from on-chain state).
No ZK proof for the score — the score is still computed in Solidity/Rust, just over a smaller tree.
No change to the external compute_score(address) API.

Data model
New types in contracts/reputation/src/lib.rs:

  • ReputationLeaf: (trade_id_hash, amount, status_bits, counterparty_hash, ledger)
  • ScoreProof: (leaf_index, sibling_hashes[..MAX_DEPTH], score_components)

New storage in contracts/escrow/src/lib.rs:

  • ReputationRoot(BytesN<32>) — current MST root, updated on terminal trade state changes.
  • ReputationLeaf(BytesN<32>) — leaf data for each completed/refunded trade.

Implementation plan

  1. Add MerkleSumTree module to contracts/escrow/src/lib.rs (or a shared htlc-core crate). Leaf hash = H(amount || status || counterparty_hash || ledger).
  2. In release, refund, batch_release, resolve_dispute, and fallback_after_timeout, update the MST root after flipping state. Use a helper update_reputation_root(env, trade_id, old_status, new_status, amount).
  3. Add a new read-only escrow contract method get_reputation_root() -> BytesN<32> and get_reputation_proof(address, max_trades) -> ScoreProof.
  4. Rewrite contracts/reputation/src/lib.rs compute_score to accept a ScoreProof from the caller (or fetch it via cross-contract call if the caller is the contract itself). Verify the MST path matches ReputationRoot.
  5. Add compute_score_incremental that only touches new leaves since the last cached score.
  6. Write contracts/reputation/src/mst_test.rs with randomized insertion/deletion/verification property tests.

Thresholds

  • MAX_TRADES can increase to 10,000 because MST verification is O(log n).
  • DECAY_TABLE index must be derived from the MST leaf's ledger field, not a global scan.

Acceptance criteria

  • compute_score executes in <500k instructions for 10,000 trades (benchmark under cargo test).
  • A proof with a single altered leaf fails MST verification.
  • Escrow terminal-state transitions still publish the same events; released / refunded / disp_res / disp_exp events are unchanged.
  • Existing get_score and get_score_breakdown read methods continue to work; they internally fetch and verify the MST proof.

Related files

  • contracts/reputation/src/lib.rs
  • contracts/escrow/src/lib.rs
  • contracts/htlc-core/src/lib.rs
  • contracts/reputation/src/test.rs
  • contracts/reputation/src/benchmarks.rs
  • packages/shared/src/index.ts

Contributor notes
A Merkle-sum tree is strictly better than a Merkle tree here because score math is additive. Reuse the Htlc trait's terminal-state transition points to keep MST updates atomic. Benchmark before and after — the gas delta is the selling point for mainnet.

Metadata

Metadata

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaigncritical

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions