Skip to content

Cold txhash: getTransaction(hash) end-to-end + demo + tx-hash bench --xdr-views - #747

Merged
tamirms merged 3 commits into
stellar:rpc-hackfrom
chowbao:txhash-reads
May 21, 2026
Merged

tamirms merged 3 commits into
stellar:rpc-hackfrom
chowbao:txhash-reads

Conversation

@chowbao

@chowbao chowbao commented May 20, 2026

Copy link
Copy Markdown
Contributor

Supersedes chowbao#3, rebased on top of #746 (now in rpc-hack).

Summary

  • txquery.ColdLookup composes the cold readers from Cold txhash MPHF store: streamhash-backed point lookup + bench wiring #746 into the same db.TransactionReader interface methods.GetTransaction already consumes from the hot DB:

    txhash.ColdReader.Lookup      hash → ledgerSeq (MPHF + fingerprint)
    ledger.ColdStoreReader        ledgerSeq → raw LCM bytes
    xdr.LedgerCloseMeta           UnmarshalBinary
    ingest.NewLedgerTxReader...   walk the LCM, match by hash
    db.ParseTransaction           materialize db.Transaction
    

    stores.ErrNotFound from either reader maps to db.ErrNoTransaction, so the type drops directly into the RPC handler. Walking past the end of the LCM (residual MPHF false positive on an unseen hash) also maps to ErrNoTransaction.

  • bench-fullhistory demo-tx-hash — a one-shot subcommand that opens a cold MPHF + cold pack pair and prints the parsed db.Transaction for an arbitrary hash, for quick smoke-testing.

  • bench-fullhistory tx-hash --tier=cold-mphf-txquery — new tier that routes per-op through ColdLookup, so the reported latency reflects the full pipeline (pack read + LCM unmarshal + ingest parse + db.Transaction materialization). Existing hot/cold/cold-mphf tiers unchanged.

  • bench-fullhistory tx-hash --xdr-views — optional flag. When set, after the per-iter ledger fetch the bench finds the matching transaction by walking the raw LCM as XDR views (LedgerCloseMetaView → V0/V1/V2 → TxProcessing → Iter → Result.TransactionHash) and comparing hash bytes, instead of lcm.UnmarshalBinary + struct scan. Same pattern as extractTxHashesView in bench_ingest_raw_txhash.go, reusing its txResultMeta interface for the V2-vs-V0/V1 result element type split. Ignored (with warning) for --tier=cold-mphf-txquery since that tier's decode happens inside ColdLookup. Bench label and CSV filename get an -xdrviews suffix so paired runs don't overwrite each other.

Known gap (from #3)

The cold MPHF seeder indexes outer hashes only via lcm.TransactionHash(i), so fee-bump inner-hash lookups miss in the cold tier even though the hot DB resolves them. Fix is a seeder change, not a lookup change (noted in cold.go).

Test plan

  • go test ./cmd/stellar-rpc/internal/fullhistory/pkg/txquery/... — unit tests build a real cold pack + matching MPHF and exercise the round-trip + unknown-hash path
  • go test ./cmd/stellar-rpc/internal/fullhistory/... — no regressions in sibling packages
  • go build ./cmd/stellar-rpc/scripts/... and go vet ./cmd/stellar-rpc/scripts/... ./cmd/stellar-rpc/internal/fullhistory/...
  • End-to-end backfill: ran ingest-raw-txhash --all --xdr-views (1001 chunks, 2.92 B tx hashes, 12 min 1.5 s) → build-txhash-index (56 s, 12.5 GB merged MPHF) on chunks 5000–5999 to confirm the rebuilt pipeline produces a usable cold MPHF at scale
  • Plug ColdLookup into methods.NewGetTransactionHandler as the db.TransactionReader and verify the RPC response shape matches the hot path against a known tx hash

🤖 Generated with Claude Code

txquery.ColdLookup composes the cold readers from stellar#746 into the same
db.TransactionReader interface methods.GetTransaction already consumes
from the hot DB:

    txhash.ColdReader.Lookup      hash -> ledgerSeq (MPHF + fingerprint)
    ledger.ColdStoreReader        ledgerSeq -> raw LCM bytes
    xdr.LedgerCloseMeta           UnmarshalBinary
    ingest.NewLedgerTxReader...   walk the LCM, match by hash
    db.ParseTransaction           materialize db.Transaction

stores.ErrNotFound from either reader maps to db.ErrNoTransaction so
the type drops directly into the RPC handler. Walking past the end of
the LCM (residual MPHF false positive on an unseen hash) also maps to
ErrNoTransaction.

bench-fullhistory additions:
  - demo-tx-hash subcommand: opens a cold MPHF + cold pack pair and
    prints the parsed db.Transaction for an arbitrary hash, for quick
    smoke-testing.
  - tx-hash --tier=cold-mphf-txquery: routes per-op through ColdLookup,
    so the reported latency reflects the full pipeline (pack read + LCM
    unmarshal + ingest parse + db.Transaction materialization).
  - tx-hash --xdr-views: when set, after the per-iter ledger fetch the
    bench finds the matching transaction by walking the raw LCM as XDR
    views (LedgerCloseMetaView -> V0/V1/V2 -> TxProcessing.Iter ->
    Result.TransactionHash) and comparing hash bytes, instead of
    lcm.UnmarshalBinary + struct scan. Same pattern as extractTxHashesView
    in bench_ingest_raw_txhash.go, reusing its txResultMeta interface for
    the V2-vs-V0/V1 result element type split. Ignored (with warning)
    for --tier=cold-mphf-txquery (decode happens inside ColdLookup).
    Bench label and CSV filename get an "-xdrviews" suffix so paired
    runs don't overwrite each other.

Known gap: the cold MPHF seeder indexes outer hashes only via
lcm.TransactionHash(i), so fee-bump inner-hash lookups miss in the
cold tier even though the hot DB resolves them. Fix is a seeder
change, not a lookup change (noted in cold.go).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tamirms
tamirms force-pushed the txhash-reads branch 2 times, most recently from ac808a6 to fa3347a Compare May 21, 2026 15:24
…timing; full view materializer; drop ColdLookup

Query benches now split per tier (cold-tx-page/hot-tx-page,
cold-tx-hash/hot-tx-hash, cold-events/hot-events) because cold and
hot have genuinely different methodologies that can't share one
loop body without lying about what each tier costs in production:

  cold-*  per-iter: evict packfile from OS page cache, open a fresh
          reader, do the call, close. No warmup.
  hot-*   shared HotStore handle for the whole run, N-iter
          block-cache warmup before timed iters.

Previously tx-page/tx-hash/events all used --tier=hot|cold but
both branches ran the hot methodology (shared handle, warmup) even
on cold — so the cold numbers were measuring steady-state cold-
reader-after-warmup, not a realistic cold-cache request.

tx-hash CSVs now decompose per-iter latency into open_ns (cold
only), lookup_ns, fetch_ns, scan_ns, materialize_ns, total_ns. Same
shape for tx-page (cursor_resolve + fetch + decode + scan) and
events (filter + fetch). --xdr-views (default true) toggles both
scan and materialize between an XDR-view path and the
production-shape lcm.UnmarshalBinary + db.ParseTransaction
round-trip. CSV filename gets a -xdrviews / -roundtrip suffix so
paired runs don't overwrite each other.

The view materializer (tx_hash_helpers.go) is end-to-end view-based:
no lcm.UnmarshalBinary anywhere on the timed path, no MarshalBinary
on any field. Result/Meta/Envelope come from sub-view .Raw() slices
into the original raw buffer (per the SDK's xdr_views_generated.go
.Raw() exposed on every generated view type). Events extracted via
SorobanMeta (V3) or TransactionMetaV4 (V4) sub-views' .Raw() — one
per DiagnosticEvent / TransactionEvent / per-op ContractEvent.
Envelope navigation walks GeneralizedTransactionSet phases →
components → txs in apply order (or ParallelTxsComponent stages →
clusters for V1-shape phases), mirroring
xdr.LedgerCloseMeta.TransactionEnvelopes()'s walk so the i'th view
matches what production resolves for the matching hash.

Bookkeeping fields (TransactionHash, ApplicationOrder, FeeBump,
Successful, Ledger.{Sequence,CloseTime}) all derived from view-level
primitives — envelope Type discriminator, result Code, header
LedgerSeq + ScpValue.CloseTime.

TestMaterializeViewsMatchesRoundtrip cross-checks the view path
against materializeRoundtripFromLCM (lcm.UnmarshalBinary + ingest
reader + db.ParseTransaction) for both V3-meta-no-soroban and
V4-meta-with-events shapes; all 12 db.Transaction fields must match
byte-for-byte. Without this, a quietly-wrong byte slice from the
view path could make the latency comparison the bench produces
meaningless.

txquery.ColdLookup deleted (no production caller — was only
constructed by the bench/demo). demo-tx-hash deleted (cold-tx-hash
now covers the same ground). bench_tx_hash.go, bench_tx_page.go,
bench_events.go deleted (replaced per tier).

Flag normalization:
  --dir on cold-ledgers / hot-ledgers → --cold-dir / --hot-dir
  --chunk on hot-ledgers / *-ingest → uint (was int64 + -1 sentinel)
  build-txhash-index: --out (the .idx output) → --idx-out,
    --csv-out → --out
  ingest-raw-txhash: --out-dir → --bin-out, --csv-out → --out
  --out now uniformly means CSV output directory everywhere.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tamirms
tamirms merged commit 03a9025 into stellar:rpc-hack May 21, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants