Skip to content

Commit 2deeae3

Browse files
committed
fix(api): limit recent transaction feed fan-out
1 parent b008dd7 commit 2deeae3

2 files changed

Lines changed: 16 additions & 7 deletions

File tree

server/index.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,12 @@ app.use(express.json({ limit: '64kb' }))
182182
// Apply the global rate limit to the API surface only (static assets are exempt).
183183
app.use('/api', globalLimiter)
184184

185-
// The recent-transactions feed (plural) must be mounted BEFORE the singular
186-
// `/api/transaction` limiter below: under Express 5 that mount prefix-matches
187-
// `/api/transactions` and breaks its routing (the request fell through to the
188-
// SPA fallback), so register the terminal plural router first.
189-
app.use('/api/transactions', transactionsRouter)
185+
// The recent-transactions feed (plural) can fan out to daemon transaction
186+
// lookups when enriching amounts, so protect it with the same strict limiter as
187+
// other RPC-backed lookup paths. Keep the terminal plural router mounted before
188+
// the singular `/api/transaction` middleware so Express does not route the feed
189+
// through the SPA fallback.
190+
app.use('/api/transactions', strictLimiter, transactionsRouter)
190191

191192
// Stricter limits on the expensive RPC fan-out paths (search, transaction and
192193
// address lookups), the broadcast write path, and the public MCP endpoint, which

server/routes/transactions.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,19 @@ router.get("/", async (req: Request, res: Response) => {
105105
const page = combined.slice(offset, offset + limit);
106106

107107
// Enrich only the page (not every scanned block's txs) with the total
108-
// output value so the list can show an amount, keeping RPC load bounded.
108+
// output value so the list can show an amount. This feed only needs vout
109+
// totals, not input prevouts, so share a zero lookup budget across the page
110+
// to avoid multiplying parent-transaction RPC/cache lookups per item.
111+
const prevoutLookupBudget = { remaining: 0 };
109112
const enrichedPage = await Promise.all(
110113
page.map(async (item) => {
111114
try {
112-
const tx = await blockCache.getTransaction(item.txid, network, true);
115+
const tx = await blockCache.getTransaction(
116+
item.txid,
117+
network,
118+
true,
119+
{ prevoutLookupBudget },
120+
);
113121
const vout = (tx as { vout?: Array<{ value?: number }> }).vout;
114122
if (!Array.isArray(vout)) return item;
115123
const amount = vout.reduce(

0 commit comments

Comments
 (0)