Skip to content

rpc: add testmempoolaccept dry-run RPC (mempool Phase 6)#3095

Open
PrestackI wants to merge 10 commits into
gridcoin-community:developmentfrom
PrestackI:txmempool/phase6-testaccept
Open

rpc: add testmempoolaccept dry-run RPC (mempool Phase 6)#3095
PrestackI wants to merge 10 commits into
gridcoin-community:developmentfrom
PrestackI:txmempool/phase6-testaccept

Conversation

@PrestackI

Copy link
Copy Markdown
Contributor

What

Phase 6 — the final phase of the mempool modernization (#3029) — stacked on Phase 5 (#3094).

AcceptToMemoryPool gains a test_only mode plus fee_out/vsize_out out-params: it runs every check (version, standardness, contract validation, the Phase 2 dedup index lookups, conflict, FetchInputs, fee floor, CheckContracts, sequence locks, ConnectInputs) but skips all mutation — the storage block, -maxmempool trim, wallet notifications, and the MRC-changed UI signal.

Reject reasons are now surfaced through CValidationState: the existing DoS() calls get a reason token (no DoS-score or control-flow change) and a few standard error()-only rejects become Invalid(reason) (DoS 0, so no new banning). This gives testmempoolaccept meaningful, Gridcoin-specific reasons — invalid-contract, mrc-duplicate-cpid, bad-txns-version, insufficient-fee, tx-nonstandard, etc. The umbrella invalid-contract covers all contract rules; surfacing the specific failing rule (a reason string out of GRC::ValidateContracts) is left as a follow-up.

New testmempoolaccept RPC (Bitcoin-compatible array input) returns {txid, allowed, reject-reason?, fees:{base}?, vsize?} per transaction. Registered under cat_network; rawtxs arg added to the client conversion table.

Why

testmempoolaccept lets wallets, explorers, and tooling pre-flight a transaction without submitting it (#1142), and the reject reasons make it genuinely useful on a contract-bearing chain. No consensus change — DoS scores and control flow are identical.

Closes #3029

This completes the umbrella roadmap (Phases 1–6: #3090, #3091, #3092, #3093, #3094, this PR). Stacked on #3094 → … → #3072; the diff narrows to just the Phase 6 change once the predecessors land.

Testing

txmempool_tests.cpp: a legacy-version tx reports bad-txns-version without mutating the pool; an already-pooled tx reports txn-already-in-mempool. The accept-path-with-valid-inputs no-mutation proof needs real chain state and is left to a functional/manual test. Full local ctest suite green; fork CI matrix green.

PrestackI added 10 commits June 16, 2026 16:38
Lift the pure block/index value types (CBlockHeader, CBlock, CBlockIndex,
CDiskBlockIndex, CBlockLocator, BlockHasher, the GRC::MRCFees/MintSummary
reports, LOCKTIME_THRESHOLD, FutureDrift, GetTargetSpacing) out of the
~1200-line main.h god-header into a new, main.h-free primitives/block.h,
mirroring Bitcoin Core's primitives/block.h.

The eight inline methods that reference chain-state globals (pindexBest,
mapBlockIndex, pindexGenesisBlock, fUseFastIndex, the genesis hashes) are
relocated out-of-line into primitives/block.cpp so the header stays free of
main.h. Their EXCLUSIVE_LOCKS_REQUIRED(cs_main) annotations remain on the
in-class declarations.

consensus/merkle.h and consensus/tx_verify.h now include primitives/block.h
instead of main.h, removing the layering inversion where low-level consensus
headers dragged in the entire top-of-stack include graph. tx_verify.cpp gains
a direct main.h include for the chain-state globals it still uses. main.h keeps
functional change.
A quote-include "validation.h" from src/consensus/tx_verify.h resolves to the
sibling src/consensus/validation.h (which has no MapPrevTx), not src/validation.h.
The original "main.h" include avoided this because there is no src/consensus/main.h.
Use angle brackets so the include resolves against -I src to the top-level
validation.h that defines MapPrevTx, matching the existing convention in
test/script_p2sh_tests.cpp.
validation.h uses CTxIndex by value in the MapPrevTx typedef and the
std::map<uint256, CTxIndex> parameters, but only ever compiled because main.h
includes index/txindex.h (line 13) before validation.h (line 24). Now that
consensus/tx_verify.h pulls validation.h directly, CTxIndex was undeclared,
which also broke overload resolution for the CTxIndex ReadTxFromDisk overload
at tx_verify.cpp:186. Include index/txindex.h directly (after index/disktxpos.h,
since CTxIndex embeds CDiskTxPos), mirroring main.h's existing include order.
Move CTxMemPool, the MemPoolRemovalReason enum, and the global
`mempool` instance out of the main.h/main.cpp god-header into a
dedicated translation unit src/txmempool.{h,cpp}.

This is a pure code move with no behavior change. main.h now
includes txmempool.h, so all existing consumers (including
wallet code using MemPoolRemovalReason) resolve unchanged. It also
serves as Phase 0 of the mempool modernization work (gridcoin-community#3029).

Register txmempool.cpp in the gridcoin_util CMake target.
Phase 1 of the mempool modernization (gridcoin-community#3029). Wrap every pooled
transaction in a new CTxMemPoolEntry that caches the fee, size, time,
and entry height computed at accept time, plus lightweight Gridcoin
contract tags (contract type(s); MRC/beacon CPID; MRC fee and
last-block-hash; mandatory-sidestake flag) derived from a single pass
over the transaction's contracts.

mapTx becomes std::map<uint256, CTxMemPoolEntry>; AcceptToMemoryPool
threads its already-computed fee/size into a new addUnchecked(entry);
every value-reader of mapTx switches to .GetTx(). The tags are populated
but not yet consumed and the five O(n) contract scans are left intact --
they become index lookups in Phase 2. No functional change.

Adds src/test/txmempool_tests.cpp covering entry metadata, MRC/beacon/
sidestake tagging, and addUnchecked/mapNextTx/lookup consistency.
…community#3029 Phase 2)

Build four secondary indexes on CTxMemPool from the contract tags Phase 1
cached on each entry, maintained in the single addUnchecked/remove/clear
paths: m_mrc_by_cpid, m_beacon_by_cpid, a mandatory-sidestake count, and a
fee-ordered m_mrc_by_fee view.

Replace all five former full-mempool scans with O(log n) lookups, with
identical semantics:
 - MRC duplicate-CPID rejection at accept (DoS=25, offending tx hash still
   reported) -- removes the "I really hate this check" full scan + bloom
   filter; m_mrc_bloom / m_mrc_bloom_dirty deleted.
 - beacon duplicate-CPID check (CheckBeaconTransactionViable).
 - mandatory-sidestake one-in-pool check (SendContractTx).
 - stale-MRC eviction on block connect (GetStaleMRCs).
 - MRC fee-queue ranking for the GUI and getmrcrequestdata RPC (GetMRCQueue).

Indexes are maintained only in the add/remove/clear paths, so reorg
resurrection (AcceptToMemoryPool replay) rebuilds them automatically. The
miner's per-block MRC selection is unchanged. No consensus change.

Extends txmempool_tests.cpp with index population/teardown, dedup collision
hash, fee-descending queue, and staleness selection.
New src/rpc/mempool.cpp surfacing the Phase 1/2 metadata and indexes:

 - getmempoolinfo: tx count, total bytes, and Gridcoin-specific counts
   (MRC / beacon / mandatory-sidestake) from the secondary indexes.
 - getmempoolentry <txid>: per-entry fee, feerate, size, time, height, and
   contract tags; throws if the tx is not pooled.
 - getrawmempool ( verbose ): moved out of rpc/blockchain.cpp; verbose=true
   returns an object keyed by txid with the same per-entry metadata, default
   keeps the plain txid array.

Registered under cat_network in rpc/server.{h,cpp}, getrawmempool's verbose
arg added to the rpc/client.cpp conversion table, rpc/mempool.cpp added to
src/CMakeLists.txt. Adds a CTxMemPoolInfo one-pass stats accessor.

Extends txmempool_tests.cpp with an RPC diagnostics case (info counts,
verbose/non-verbose getrawmempool, getmempoolentry hit + missing-hash throw).
…ommunity#3029 Phase 4)

Add a transaction count/size cap and eviction, stacked on the Phase 1-3
mempool work.

 - CTxMemPool tracks a running m_total_tx_size and a composite eviction index
   (CTxMemPoolEvictionKey). Because Gridcoin's flat, size-scaled fee makes
   feerate nearly identical across transactions, victims are ordered:
   non-contract before contract (contracts evicted last so reward-bearing
   MRCs survive), then feerate ascending, then newest-first.
 - DynamicMemoryUsage() = serialized bytes + a fixed per-entry overhead.
 - TrimToSize(limit) evicts lowest-priority first, routing every removal
   through remove(..., SIZELIMIT) so all indexes stay consistent. remove()
   gains a MemPoolRemovalReason argument.
 - -maxmempool (default 300 MB) is parsed in init.cpp and applied via
   SetMaxSize(); AcceptToMemoryPool trims after addUnchecked and rejects the
   incoming tx if it was itself evicted (mempool full).
 - getmempoolinfo reports usage and maxmempool.

Indexes are still maintained only in addUnchecked/remove/clear, so reorg
resurrection rebuilds them. No consensus change.

txmempool_tests.cpp: eviction order (feerate asc, contracts last) and
contract protection when room remains for one.
…Phase 5)

Save the pooled transactions on shutdown and reload them on startup,
stacked on the Phase 1-4 mempool work.

 - New node/mempool_persist.{h,cpp}: WriteMempoolEntries/ReadMempoolEntries
   serialize transactions plus their entry time to <datadir>/mempool.dat
   (versioned, atomic temp-file + rename, corrupt/missing handled via
   try/catch). DumpMempool snapshots under the pool lock then writes outside
   it; LoadMempool re-validates every transaction through AcceptToMemoryPool,
   so stale/invalid txns are dropped and all indexes are rebuilt for free.
 - AcceptToMemoryPool gains an optional entry_time argument; LoadMempool
   passes each stored time so age / queue ordering survives a restart.
 - -persistmempool (default on) gates a shutdown dump (after StopNode, before
   the block flush) and a startup load (after the chain + wallet are ready)
   in init.cpp.
 - New savemempool and importmempool RPCs.

txmempool_tests.cpp: write/read round-trip (incl. an MRC tx and entry times)
and graceful handling of a corrupt and a missing file. The full
dump->restart->re-accept path needs real chain state and is left to a
functional/manual test.
Final phase of the mempool modernization. AcceptToMemoryPool gains a
test_only mode plus fee_out/vsize_out out-params: it runs every check
(version, standardness, contract validation, the Phase 2 dedup index
lookups, conflict, FetchInputs, fee floor, CheckContracts, sequence locks,
ConnectInputs) but skips all mutation -- the storage block, -maxmempool
trim, wallet notifications, and the MRC-changed UI signal.

Reject reasons are now surfaced through CValidationState: the existing
DoS() calls get a reason token (no DoS-score or control-flow change) and a
few standard error()-only rejects become Invalid(reason) (DoS 0, so no new
banning). This gives testmempoolaccept meaningful, Gridcoin-specific
reasons -- invalid-contract, mrc-duplicate-cpid, bad-txns-version,
insufficient-fee, tx-nonstandard, etc. The umbrella invalid-contract token
covers all contract rules; surfacing the specific failing rule (a reason
string out of GRC::ValidateContracts) is left as a follow-up.

New testmempoolaccept RPC (Bitcoin-compatible array input) returns
{txid, allowed, reject-reason?, fees:{base}?, vsize?} per transaction.
Registered under cat_network; rawtxs arg added to the client conversion
table.

txmempool_tests.cpp: a legacy-version tx reports bad-txns-version without
mutating the pool; an already-pooled tx reports txn-already-in-mempool. The
accept-path-with-valid-inputs no-mutation proof needs real chain state and
is left to a functional/manual test.
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.

refactor, rpc: Modernize the transaction mempool (CTxMemPoolEntry + indexes, size cap, persistence, testmempoolaccept)

1 participant