Skip to content

feat(simulators): Merkle proofs, XDR inspector, storage model, reorg & finality (#1159, #1161, #1162, #1163) - #1217

Open
ayinde38 wants to merge 1 commit into
StellarDevHub:mainfrom
ayinde38:feat/simulators-1159-1161-1162-1163
Open

feat(simulators): Merkle proofs, XDR inspector, storage model, reorg & finality (#1159, #1161, #1162, #1163)#1217
ayinde38 wants to merge 1 commit into
StellarDevHub:mainfrom
ayinde38:feat/simulators-1159-1161-1162-1163

Conversation

@ayinde38

Copy link
Copy Markdown

Closes #1159
Closes #1161
Closes #1162
Closes #1163

Four simulators. Each is a pure library plus a page, so the teaching logic is testable without rendering — and in three of the four the library is where the actual lesson lives.

Issue Route Library
#1159 /merkle-simulator lib/merkle-sha256.ts
#1161 /xdr-inspector lib/xdr-inspector.ts
#1162 /storage-model lib/soroban-storage-model.ts
#1163 /consensus-simulator lib/consensus-simulator.ts

#1159 — Merkle visualiser

Not built on the existing merkle-tree-builder.ts, deliberately. That module hashes with stableHash, which is FNV-1a: a 32-bit, non-cryptographic hash. It is fine for the airdrop demo it backs, where the hash is only an identifier. It is the wrong primitive for a tool whose entire lesson is cryptographic immutability — a student could find an FNV collision by hand, and the tamper demo would be teaching that Merkle roots are forgeable. The existing /merkle-tree route is untouched.

lib/merkle-sha256.ts uses real SHA-256 via Web Crypto, with two properties worth calling out:

  • Domain separation. Leaves are hashed SHA256(0x00 ‖ value) and nodes SHA256(0x01 ‖ left ‖ right). Without it, an internal node's preimage is 64 bytes and could be presented as a leaf in a tree built over 64-byte leaves — the standard second-preimage attack. RFC 6962 uses the same construction.
  • Unpaired nodes are promoted, not duplicated. Duplicating the odd node — the Bitcoin approach — admits CVE-2012-2459, where two distinct trees produce the same root. There is a test asserting our root differs from the duplicate-style root.

The page does what the issue asks: student-supplied leaves, audit path with siblings highlighted separately from the path itself, step-by-step reconstruction showing the exact bytes hashed at each level, tamper simulation with the cascade highlighted, and JSON export. The exported bundle records algorithm, leafPrefix and nodePrefix — a vector that omits the scheme is not reproducible by a contract.

#1161 — Transaction builder & XDR inspector

Builds Payment, CreateAccount, ManageData and InvokeHostFunction (Soroban), converts between the structured view and Base64 XDR, and decodes envelopes back.

The decoded panel parses the envelope rather than echoing form state. That is the whole claim of the page — the JSON and the XDR are one object in two spellings — and verifyRoundTrip asserts it on every edit, so a disagreement is visible rather than hidden.

Three things the tool surfaces that are easy to get wrong:

  • The fee on an envelope is the total (base × operation count), not the per-operation figure entered.
  • A manageData with no value deletes the entry, which is not the same as storing an empty string.
  • The transaction hash is network-specific. Signatures commit to a hash that includes the network passphrase, so flipping the network selector changes the hash without the envelope changing at all — that is the mechanism preventing testnet replay on the public network.

Submission funds the account via Friendbot, re-reads the live sequence (the form's sequence is for teaching; the network wants exactly next), simulates via Soroban RPC first, and only then submits — so authorization and precondition failures cost nothing. Submission is restricted to testnet.

#1162 — Storage model visualiser

The distinction students get wrong is that Temporary and Persistent sound like "short" and "long". They are not — both expire. They differ in what expiry does:

  • Temporary expiry deletes. Unrecoverable, no restore.
  • Persistent expiry archives. Recoverable via restore, at a cost.

So "use Temporary to save money" is only safe for data you can recompute. The page makes this concrete: advance past the TTL, press restore on the temporary cache entry and it refuses; press it on the persistent balance at the same moment and it succeeds.

Also modelled: Instance storage shares one TTL across every key, extend_ttl measures from the current ledger (repeated calls do not stack) and never shortens a TTL, and neither extend_ttl nor restore can revive a deleted entry. Rent calculator shows all three tiers side by side with the expiry behaviour in the same row, because price alone makes Temporary look like the obvious choice.

The fee rates are network parameters, not constants — they are exported and overridable, and the page says so rather than presenting a projection as a quote.

#1163 — Reorg & finality simulator

Distinct from the existing /chain-reorg, which animates two racing chains with random hashes and declares a winner. This models branches concretely, so a reorg names the transactions it reverted — not "reorgs can happen" but "this payment unhappened, and the goods already shipped".

  • PoW fork choice by cumulative work, recomputed after every block.
  • Nakamoto gambler's-ruin reversal probability (q/p)^depth, which returns 1 at ≥50% — no confirmation count is safe, which is what "51% attack" actually means.
  • A 51% double-spend that forks below the block holding the payment (forking at the tip can never remove it — an easy modelling error, and one I hit and fixed).
  • The SCP contrast: a closed ledger is final, so a competing branch is never adopted however much work it carries. An attacker with enough influence can halt the network — a liveness failure, not a reversal.
  • Timeline scrubber that replays history through the same fork-choice code, so the scrubbed view and the live view cannot diverge.

A transaction present on both branches is correctly not counted as reverted.

Verification

npm install does not produce a working node_modules for this repo in my environment, so I could not run vitest. Instead I exercised each library directly with Node's native TypeScript stripping, against independent references where one exists — 147 assertions, all passing:

Library Assertions Verified against
merkle-sha256 45 node:crypto SHA-256 computed independently — leaf/node hashes, roots, and the promotion-vs-duplication root all cross-checked
soroban-storage-model 32 rent arithmetic, every lifecycle transition, extend/restore refusals
consensus-simulator 37 reorg depth and reverted-tx lists, (q/p)^depth closed form, SCP invariants
xdr-inspector 33 the real @stellar/stellar-sdk v14 — installed separately to confirm the API surface and round-trip actual envelopes

The four vitest files mirror those assertions and are written in the repo's existing convention (vitest, src/lib/__tests__/), but have not been executed here — worth a CI run.

The pages themselves are not type-checked, for the same missing-toolchain reason. I verified the SDK method names I depend on (Operation.invokeContractFunction, StrKey.encodeContract, etc.) against the installed v14 package rather than assuming them.

…finality

Closes StellarDevHub#1159, StellarDevHub#1161, StellarDevHub#1162, StellarDevHub#1163.

Four interactive simulators, each a pure lib plus a page, so the teaching
logic is testable without rendering.

StellarDevHub#1159 - lib/merkle-sha256.ts + /merkle-simulator. Deliberately not built on
the existing merkle-tree-builder: that hashes with stableHash (FNV-1a, 32-bit,
non-cryptographic), which is fine for the airdrop demo it backs but wrong for
a tool teaching cryptographic immutability - a student could find a collision
by hand. Uses real SHA-256 with 0x00/0x01 domain separation for leaves and
nodes, so exported proofs are valid contract test vectors. Unpaired nodes are
promoted rather than duplicated, avoiding CVE-2012-2459. Shows the audit path,
step-by-step root reconstruction, and the tamper cascade.

StellarDevHub#1161 - lib/xdr-inspector.ts + /xdr-inspector. Builds Payment, CreateAccount,
ManageData and InvokeHostFunction operations, converts between the JSON view
and Base64 XDR, decodes envelopes back, and simulates before submitting to
testnet. The decoded panel parses the envelope rather than echoing form state,
so the two views cannot silently disagree. Surfaces that the transaction hash
is network-specific, which is what prevents cross-network replay.

StellarDevHub#1162 - lib/soroban-storage-model.ts + /storage-model. Models the distinction
students actually get wrong: Temporary and Persistent are not short and long,
they differ in what expiry does - deletion versus archival. Live TTL
countdowns, a rent calculator, and extend_ttl/restore that refuse the
operations the protocol refuses, including restoring a deleted temporary entry.

StellarDevHub#1163 - lib/consensus-simulator.ts + /consensus-simulator. Distinct from the
existing /chain-reorg animation: models branches concretely, so a reorg names
the transactions it reverted rather than just declaring a winner. Implements
PoW fork choice by cumulative work, the Nakamoto gambler's-ruin reversal
probability, a 51% double-spend, and the SCP contrast where a closed ledger is
final and an attacker can only halt progress. Includes a timeline scrubber
that replays history through the same fork-choice code.
@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@ayinde38 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

@ayinde38 is attempting to deploy a commit to the Ayomide Adeniran's projects Team on Vercel.

A member of the Team first needs to authorize it.

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