monero.loveA little love for the network.
← All news

Monero / Security / Wallets

Monero Wallet RPC Payment Queries Can Multiply Responses and Memory Usage

Repeated payment IDs multiply Monero wallet RPC results. Examine issue #11745, reported memory use, affected code, access requirements and remediation gaps.

Three copies of one payment ID trigger three lookups, repeating two matching records into six response entries and consuming more memory.

Monero's get_bulk_payments wallet RPC method can return the same payment records repeatedly when a request repeats a payment ID. The code builds a response entry for every match on every pass. A short list of repeated inputs can produce a much larger result.

Issue #11745, opened by Ap4sh on November 13, describes a local regtest measurement. Source inspection supports the duplication mechanism. The report does not establish an attack, a remotely triggered outage or a universal memory cost. We checked the status below on November 14, 2026, UTC.

How get_bulk_payments works

Wallet RPC lets an application query and operate a wallet through HTTP requests. It is separate from a public Monero node's daemon RPC. A merchant might use it to reconcile incoming payments. Our wallet and node introduction explains that distinction.

The documented method accepts payment_ids and min_block_height. It returns payment details such as transaction hash, amount and block height. In the examined implementation, an empty ID list takes a separate path that retrieves payments across all IDs, including payments without one.

For a nonempty list, the handler parses each ID, retrieves matching records from the open wallet and appends them to res.payments. It does not track which IDs it already processed. If one ID matches two payments, three occurrences produce six entries. These are repeated records of existing payments. The query doesn't create funds or transactions.

Why repetition consumes resources

The handler accepts both 16-character and 64-character hexadecimal strings. It expands the shorter form into a 32-byte value by appending zero bytes. Different text can identify the same lookup value. The response echoes the supplied text, which matters when deciding whether future duplicate filtering should preserve the first spelling or use a canonical form.

The wallet lookup retrieves matching stored payments above the requested minimum height. Each repeated input causes another lookup and another set of response objects. With R repetitions and P qualifying payments for one ID, the handler appends R × P entries.

Those objects contain more information than an input ID. They include hashes, addresses, amounts and other fields, and must then become a JSON response. This explains why response size and memory demand can grow far beyond request size. It does not establish how much memory belongs to payment objects, serialization, network buffers or the wallet itself. That requires profiling.

Contributor measurements and their limits

Ap4sh reports testing commit ef78ff3d7496653f91e2ae870514352f753e3a07 with a local regtest wallet containing 100 no-ID coinbase payments. One all-zero short ID returned 100 entries. Repeating it 3,000 times returned 300,000.

Measurement Contributor's exact value Rounded interpretation
Request 60,112 bytes 60.1 kB
Response 131,682,078 bytes 131.7 MB, or 125.6 MiB
Returned entries 300,000 3,000 × 100
Process peak RSS, VmHWM 1,136,072 KiB About 1.08 GiB

The response is approximately 2,191 times the request size by the reported byte counts. Peak RSS is a process high-water mark, not a measurement of memory allocated only by this request. The issue does not provide a baseline, raw logs, timing results, a crash or an out-of-memory event.

We verified the arithmetic and examined the code, but did not run an independent wallet reproduction. The captured issue had no comments or independent results. The existing transfer functional test covers payment queries, height filtering and short IDs, but its payment-query checks do not exercise repeated IDs. We inspected those tests but didn't execute them for this article.

Access requirements and restricted RPC

A caller needs access to the wallet RPC endpoint and an open wallet containing qualifying records to trigger this result multiplication. Repeating an ID with no matches does not create payment entries, although the inputs still require processing.

The wallet server initialization enables HTTP authentication unless explicitly disabled. When credentials are not supplied, it generates them and writes a login file. The shared RPC configuration defaults to loopback addresses and requires confirmation for an external bind.

An outside caller would need a reachable endpoint and valid authentication, disabled authentication, or an application that forwards their chosen query using its own credentials. This report demonstrates no authentication bypass.

Wallet restricted-rpc limits available commands, but on_get_bulk_payments has no restricted-mode rejection. Its RPC registration maps directly to that handler. Restricted mode is not a duplicate-query defense. This finding concerns wallet RPC, not the daemon's separate restricted RPC service.

What operators and merchants should do

The demonstrated mechanism raises an availability concern. A sufficiently expensive query could compete with payment processing for CPU and memory. Whether it causes delays, a process termination or a wider outage depends on the wallet, resource limits and request handling. None of those outcomes was independently demonstrated here.

Keep wallet RPC on loopback or an explicitly controlled private network. Keep authentication enabled and limit which services hold its credentials. A public checkout should expose narrowly defined application operations, rather than pass arbitrary wallet RPC parameters through to the wallet.

Check ID syntax and bound the number of requested IDs before forwarding a query. Normalize valid IDs to the same binary representation used by the wallet, then reject or remove duplicates. String-only filtering can miss equivalent short and padded forms. Decide how to handle an empty list explicitly, because the current method treats it as a request for all payments.

Bound request rates and concurrent work as well. Deduplication alone does not limit a query containing many distinct IDs or a large payment history. A suitable minimum height can reduce returned history, but must not cause a reconciliation process to skip unpaid invoices.

Payment integrations should also make crediting idempotent: processing the same payment record again must not credit an invoice again. Repeated response entries could expose weak bookkeeping, but this report supplies no evidence of duplicate merchant credits or stolen funds. Review the integration's payment identity rules rather than assuming every returned row is a new payment.

Affected code and remediation status

The latest official release checked was v0.18.5.4. Its payment handler retains the same per-input loop. We also found it in v0.18.0.0 and v0.17.3.2. These are source-confirmed examples, not a complete affected-version range or performance tests of those releases. The introducing version remains unverified.

Issue #11745 remains open. No dedicated fix PR, merged fix or released correction was identified in the reviewed issue, timeline and repository search. The linked PR #10819 remains open and unmerged. Its current code still loops over input IDs, so it should not be described as the fix.

An upstream change needs an explicit decision about duplicate semantics and the echoed ID format. A regression test should cover identical IDs, equivalent short and padded forms, empty lists and height filtering. Independent measurements should use fresh isolated regtest wallets, loopback-only RPC and bounded workloads, recording baseline and peak memory separately. Until then, the reported peak is useful evidence of one contributor's test, with its limits intact.

Support Monero Love

Help keep the public nodes and packages running. Send any amount of XMR.

Scan with a Monero wallet to donate to Monero Love.89hGTKxRVA3AUeKAbCRBt5TMSzq3MQZooNNsezCpNFjp2oeJPRx8AJeGKA8ziHZNqab78f4Vr1ntxhPDxX5298nnGZjHkhm