Paralell processing of TXs within a block

This commit is contained in:
Chris Troutner
2026-06-02 17:11:42 -07:00
parent c1a841c60e
commit 99866ae18b
12 changed files with 121 additions and 19 deletions
+3 -3
View File
@@ -60,8 +60,8 @@ Controllers
Use cases
index-blocks.js — processBlock, processMemoTx
filter-block.js — filterMemoTxs (parallel pre-check)
index-blocks.js — processBlock, processMemoTx (parallel per block)
filter-block.js — filterMemoTxs (parallel pre-check, block order preserved)
state.js — synced block height
action-types/*.js — per Memo action handlers
utils.js
@@ -157,7 +157,7 @@ Each store is a separate LevelDB with JSON values. Keys are txids, addresses, or
| `zeromq` | Indexer | Subscribe to full node |
| `@chris.troutner/retry-queue` | Indexer | Retry RPC on transient failure |
| `axios` | Indexer | psf-memo-db REST client |
| `p-queue` / `p-retry` | Indexer | Parallel block filter with retries |
| `p-queue` / `p-retry` | Indexer | Parallel block filter and parallel per-tx processing within a block |
| `express` | Indexer | TX indexer control API |
| `level` | psf-memo-db | Embedded JSON LevelDB |
| `koa` + `koa-router` | psf-memo-db | REST server |
+11 -5
View File
@@ -51,15 +51,21 @@ This document records **why** the Memo indexer stack looks the way it does, incl
**Risk:** Indexer state diverges from memo.sv for excluded actions until handlers are added.
## 4. No DAG sort within blocks
## 4. Parallel processing within blocks (no DAG sort)
**Decision:** Process filtered Memo txids in block order, sequentially.
**Decision:** Filter and process Memo txs concurrently within a block using `p-queue`. Default concurrency is 20 for both phases (`FILTER_CONCURRENCY`, `MEMO_TX_CONCURRENCY`). Filter results are reordered to match block tx order before processing starts.
**SLP context:** SEND transactions may spend token outputs created earlier in the same block; DAG sort orders them correctly.
**SLP context:** SEND transactions may spend token outputs created earlier in the same block; DAG sort orders them correctly and writes must stay serial when UTXO state overlaps.
**Memo context:** Social actions do not consume each others UTXOs in a token graph. Replies reference parent txids by hash but do not require reordering spends within the block.
**Memo context:** Social actions do not consume each others UTXOs in a token graph. Most handler writes are keyed by `txid` and are independent across transactions. Parallel `processMemoTx` is safe for typical blocks.
**Tradeoff:** If future Memo actions introduce intra-block dependencies, DAG or topological sort may be needed—unlikely for current social ops.
**Soft ordering:** Likes that reference a post in the same block may compute `tip = 0` if the post handler has not finished yet. Replies still persist parent/child links and post bodies regardless of completion order. Profile/follow handlers keyed by address can race if the same signer emits multiple updates in one block (rare).
**Within a single tx:** Multiple Memo `OP_RETURN` outputs in one transaction are still dispatched sequentially inside `processMemoTx`.
**Tradeoff:** Higher throughput vs. HTTP contention on `psf-memo-db`. Lower `MEMO_TX_CONCURRENCY` if the DB service becomes the bottleneck.
**Alternative rejected:** Full serial processing—simple but leaves RPC/DB latency on the table during IBD when blocks contain many unrelated Memo txs.
## 5. Scan all transaction outputs for OP_RETURN
+3 -3
View File
@@ -63,7 +63,7 @@ sequenceDiagram
loop each candidate txid
FB->>TX: isMemoTx(txid)
end
loop each memo txid
loop each memo txid (parallel, up to MEMO_TX_CONCURRENCY)
BI->>BI: processMemoTx(txid)
BI->>DB: reads/writes via handlers
end
@@ -71,11 +71,11 @@ sequenceDiagram
**Step 1 — Fetch block:** `getblock` returns the list of transaction ids in the block (verbosity 1).
**Step 2 — Filter:** `filterMemoTxs` runs up to 20 concurrent `isMemoTx` checks (`p-queue`). Each check loads the transaction (with in-memory cache in `transaction.js`) and scans **all outputs** for a Memo `OP_RETURN`.
**Step 2 — Filter:** `filterMemoTxs` runs up to `FILTER_CONCURRENCY` (default 20) concurrent `isMemoTx` checks (`p-queue`). Each check loads the transaction (with in-memory cache in `transaction.js`) and scans **all outputs** for a Memo `OP_RETURN`. Results are returned in **block tx order** (parallel checks use a set, then the original block list is filtered).
**Why scan all outputs?** Unlike SLP, which conventionally places token data in `vout[0]`, Memo actions may appear in any outputs `scriptPubKey`. The Go reference iterates every `TxOut`.
**Step 3 — Process:** For each Memo txid, `processMemoTx` runs sequentially within the block (no DAG sort—Memo social actions do not form token-like dependency chains within a block).
**Step 3 — Process:** `processMemoTxs` runs up to `MEMO_TX_CONCURRENCY` (default 20) concurrent `processMemoTx` calls via `p-queue`. There is no DAG sort—Memo social actions do not form token-like dependency chains within a block. Multiple OP_RETURN outputs in a **single** transaction are still handled sequentially inside `processMemoTx`.
## Phase 3: Processing a single Memo transaction