Skip to content

Commit 01998bc

Browse files
authored
fix: optimize replace-by-fee mempool calculations (#2326)
* fix: index * fix: batch rbf transactions query * fix: use batching
1 parent df01a1b commit 01998bc

File tree

2 files changed

+88
-44
lines changed

2 files changed

+88
-44
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* eslint-disable camelcase */
2+
3+
exports.shorthands = undefined;
4+
5+
exports.up = pgm => {
6+
pgm.createIndex('mempool_txs', ['sender_address', 'nonce']);
7+
pgm.createIndex('mempool_txs', ['sponsor_address', 'nonce']);
8+
};

src/datastore/pg-write-store.ts

Lines changed: 80 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2178,63 +2178,99 @@ export class PgWriteStore extends PgStore {
21782178
txIds: string[],
21792179
mempool: boolean = true
21802180
): Promise<void> {
2181-
for (const txId of txIds) {
2182-
// If a transaction with equal nonce was confirmed in a block, mark all conflicting mempool
2183-
// txs as RBF. Otherwise, look for the one with the highest fee in the mempool and RBF all the
2184-
// others.
2185-
//
2186-
// Note that we're not filtering by `pruned` when we look at the mempool, because we want the
2187-
// RBF data to be retroactively applied to all conflicting txs we've ever seen.
2181+
if (txIds.length === 0) return;
2182+
2183+
// If a transaction with equal nonce was confirmed in a block, mark all conflicting mempool txs
2184+
// as RBF. Otherwise, look for the one with the highest fee in the mempool and RBF all the
2185+
// others.
2186+
//
2187+
// Note that we're not filtering by `pruned` when we look at the mempool, because we want the
2188+
// RBF data to be retroactively applied to all conflicting txs we've ever seen.
2189+
for (const batch of batchIterate(txIds, INSERT_BATCH_SIZE)) {
21882190
await sql`
2189-
WITH source_tx AS (
2190-
SELECT
2191+
WITH input_txids (tx_id) AS (
2192+
VALUES ${sql(batch.map(id => [id.replace('0x', '\\x')]))}
2193+
),
2194+
source_txs AS (
2195+
SELECT DISTINCT
2196+
tx_id,
21912197
(CASE sponsored WHEN true THEN sponsor_address ELSE sender_address END) AS address,
2192-
nonce, fee_rate
2198+
nonce
21932199
FROM ${mempool ? sql`mempool_txs` : sql`txs`}
2194-
WHERE tx_id = ${txId}
2195-
LIMIT 1
2200+
WHERE tx_id IN (SELECT tx_id::bytea FROM input_txids)
2201+
),
2202+
affected_groups AS (
2203+
SELECT DISTINCT address, nonce
2204+
FROM source_txs
21962205
),
21972206
same_nonce_mempool_txs AS (
2198-
SELECT tx_id, fee_rate, receipt_time
2199-
FROM mempool_txs
2200-
WHERE (sponsor_address = (SELECT address FROM source_tx) OR sender_address = (SELECT address FROM source_tx))
2201-
AND nonce = (SELECT nonce FROM source_tx)
2207+
SELECT
2208+
m.tx_id,
2209+
m.fee_rate,
2210+
m.receipt_time,
2211+
m.pruned,
2212+
g.address,
2213+
g.nonce
2214+
FROM mempool_txs m
2215+
INNER JOIN affected_groups g
2216+
ON m.nonce = g.nonce
2217+
AND (m.sponsor_address = g.address OR m.sender_address = g.address)
22022218
),
2203-
mined_tx AS (
2204-
SELECT tx_id
2205-
FROM txs
2206-
WHERE (sponsor_address = (SELECT address FROM source_tx) OR sender_address = (SELECT address FROM source_tx))
2207-
AND nonce = (SELECT nonce FROM source_tx)
2208-
AND canonical = true
2209-
AND microblock_canonical = true
2210-
ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC
2211-
LIMIT 1
2219+
mined_txs AS (
2220+
SELECT
2221+
t.tx_id,
2222+
g.address,
2223+
g.nonce
2224+
FROM txs t
2225+
INNER JOIN affected_groups g
2226+
ON t.nonce = g.nonce
2227+
AND (t.sponsor_address = g.address OR t.sender_address = g.address)
2228+
WHERE t.canonical = true AND t.microblock_canonical = true
2229+
ORDER BY t.block_height DESC, t.microblock_sequence DESC, t.tx_index DESC
2230+
),
2231+
latest_mined_txs AS (
2232+
SELECT DISTINCT ON (address, nonce) tx_id, address, nonce
2233+
FROM mined_txs
22122234
),
2213-
highest_fee_mempool_tx AS (
2214-
SELECT tx_id
2235+
highest_fee_mempool_txs AS (
2236+
SELECT DISTINCT ON (address, nonce) tx_id, address, nonce
22152237
FROM same_nonce_mempool_txs
2216-
ORDER BY fee_rate DESC, receipt_time DESC
2217-
LIMIT 1
2238+
ORDER BY address, nonce, fee_rate DESC, receipt_time DESC
22182239
),
2219-
winning_tx AS (
2220-
SELECT COALESCE((SELECT tx_id FROM mined_tx), (SELECT tx_id FROM highest_fee_mempool_tx)) AS tx_id
2240+
winning_txs AS (
2241+
SELECT
2242+
g.address,
2243+
g.nonce,
2244+
COALESCE(l.tx_id, h.tx_id) AS tx_id
2245+
FROM affected_groups g
2246+
LEFT JOIN latest_mined_txs l USING (address, nonce)
2247+
LEFT JOIN highest_fee_mempool_txs h USING (address, nonce)
22212248
),
22222249
txs_to_prune AS (
2223-
SELECT tx_id, pruned
2224-
FROM mempool_txs
2225-
WHERE tx_id IN (SELECT tx_id FROM same_nonce_mempool_txs)
2226-
AND tx_id <> (SELECT tx_id FROM winning_tx)
2250+
SELECT
2251+
s.tx_id,
2252+
s.pruned
2253+
FROM same_nonce_mempool_txs s
2254+
INNER JOIN winning_txs w USING (address, nonce)
2255+
WHERE s.tx_id <> w.tx_id
22272256
),
2228-
mempool_count_updates AS (
2229-
UPDATE chain_tip SET
2230-
mempool_tx_count = mempool_tx_count - (SELECT COUNT(*) FROM txs_to_prune WHERE pruned = false),
2231-
mempool_updated_at = NOW()
2257+
pruned AS (
2258+
UPDATE mempool_txs m
2259+
SET pruned = TRUE,
2260+
status = ${DbTxStatus.DroppedReplaceByFee},
2261+
replaced_by_tx_id = (
2262+
SELECT w.tx_id
2263+
FROM winning_txs w
2264+
INNER JOIN same_nonce_mempool_txs s ON w.address = s.address AND w.nonce = s.nonce
2265+
WHERE s.tx_id = m.tx_id
2266+
)
2267+
FROM txs_to_prune p
2268+
WHERE m.tx_id = p.tx_id
2269+
RETURNING m.tx_id
22322270
)
2233-
UPDATE mempool_txs
2234-
SET pruned = TRUE,
2235-
status = ${DbTxStatus.DroppedReplaceByFee},
2236-
replaced_by_tx_id = (SELECT tx_id FROM winning_tx)
2237-
WHERE tx_id IN (SELECT tx_id FROM txs_to_prune)
2271+
UPDATE chain_tip SET
2272+
mempool_tx_count = mempool_tx_count - (SELECT COUNT(*) FROM txs_to_prune WHERE pruned = FALSE),
2273+
mempool_updated_at = NOW()
22382274
`;
22392275
}
22402276
}

0 commit comments

Comments
 (0)