feat(mempool): allow subscribing to unconfirmed_txs via WebSocket - #6035
Open
gomesalexandre wants to merge 3 commits into
Open
feat(mempool): allow subscribing to unconfirmed_txs via WebSocket#6035gomesalexandre wants to merge 3 commits into
gomesalexandre wants to merge 3 commits into
Conversation
Contributor
|
PR author is not in the allowed authors list. |
Adds a MempoolTx event, fired when a tx is admitted into the mempool (passes CheckTx for the first time), mirroring how the existing Tx event is fired on block commit. Clients (MEV/backrunning tooling, network diagnostics per the issue) can now subscribe with tm.event='MempoolTx' instead of polling /unconfirmed_txs. - types/events.go, types/event_bus.go: new EventMempoolTx constant, EventDataMempoolTx, MempoolTxEventPublisher interface, and EventBus/NopEventBus.PublishEventMempoolTx, following the same shape as the existing Tx event plumbing. - mempool/clist_mempool.go: CListMempool takes an optional types.MempoolTxEventPublisher (WithEventBus option, defaults to a no-op), and publishes on tx admission in resCbFirstTime. - node/setup.go, node/node.go: wire the node's EventBus into the mempool at construction time. - docs/core/subscription.md, CHANGELOG.md: document the new event. Scoped to CListMempool (the default mempool.type = "flood"); AppMempool (mempool.type = "app") is a separate, newer app-side implementation that doesn't yet touch the EventBus at all, so wiring it there is left for a follow-up rather than folded into this change. closes cometbft#1770 Co-Authored-By: Claude <noreply@anthropic.com>
- events.go: the comment said EventMempoolTx 'carries no execution result', but EventDataMempoolTx does carry the CheckTx response. Reworded to clarify it's not a block execution result, not that there's no payload at all. - subscription.md: state explicitly that MempoolTx only fires on mempool.type = "flood" (the default) - a subscription on an app/nop-mempool node currently receives nothing, silently.
gomesalexandre
force-pushed
the
feat_subscribe_unconfirmed_txs
branch
from
September 2, 2026 11:42
3e612bb to
ab5251e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
closes #1770
Adds a
MempoolTxevent, fired when a tx is admitted into the mempool (passesCheckTxfor the first time), mirroring how the existingTxevent is firedon block commit. Clients can subscribe with
tm.event='MempoolTx'instead ofpolling
/unconfirmed_txs.Use case
Answering the maintainer's question on the issue thread: MEV/backrunning
tooling and network diagnostics both want to observe txs the moment they enter
the mempool, not just once committed - polling
/unconfirmed_txson aninterval misses the timing precision either use case needs.
Scope:
CListMempoolonlyAppMempool(mempool.type = "app") is a separate, newer implementation thatdoesn't touch the
EventBusat all today - wiring it there is left as afollow-up rather than folded into this change. Documented explicitly in
docs/core/subscription.md: a node runningmempool.type = "app"or"nop"currently receives nothing on a
MempoolTxsubscription, with no error. Happyto track that as a separate issue if useful.
Things worth knowing before merging
Publish is synchronous under
CheckTx's read lock.PublishEventMempoolTxfollows the exact same pattern as the existing
PublishEventTx(unboundedcontext, error logged not propagated) - but the underlying pubsub
Serverdispatches on an unbuffered channel by default
(
EventBusBufferCapacitydefaults to0), andCheckTxholdsupdateMtx.RLock()for its entire body with the local ABCI client. So if thepubsub loop is momentarily busy (e.g. draining a big block's
Txevents intoan unbuffered subscriber like the indexer), a
MempoolTxpublish - which nowfires per admitted tx, not per block - queues behind it, and mempool-admission
latency inherits that delay. No deadlock: the indexer never takes mempool
locks, and it only subscribes to
Tx/NewBlockEventsqueries by default, soit never receives
MempoolTxitself. Operators who care can raiseevent_bus_buffer_capacity. Flagging rather than restructuring the publishpath, since it matches the existing
Tx-event pattern exactly.No zero-subscriber fast path. Every admitted tx now pays a
Tx.Hash()computation, a couple of map allocations, and a pubsub dispatch, even with zero
MempoolTxsubscribers - across every gossiped tx network-wide, not justlocally submitted ones. Small relative to the ABCI round-trip per
CheckTx,but nonzero. Happy to add an opt-in
mempool.publish_eventsconfig flag as afollow-up if that overhead matters to node operators.
Queries without a
tm.eventclause now also matchMempoolTx. SincePublishEventMempoolTxindexes theCheckTxresponse's app events and the txhash into the same composite-key space
Txevents use, a subscription liketransfer.recipient='X'(notm.eventfilter) will now also receivemempool-admission events if the app emits that event in
CheckTx, not just oncommit. Built-in flows are unaffected (
EventQueryTxForalways includestm.event='Tx', and cosmjs's tx-search/subscribe paths do the same), but ahand-rolled subscriber without a
tm.eventclause changes behavior. Keepingthe shared key space since hash-based filtering is a real part of this
feature's value, but calling it out explicitly rather than leaving it for a
reviewer to discover.
Testing
New test in
mempool/clist_mempool_test.go: realCListMempool+ realEventBus+ an in-memory kvstore app, subscribes viaEventQueryMempoolTx,asserts the subscriber receives
EventDataMempoolTxwith the right tx bytesand
CheckTxcode.go build ./...clean.gofmt -lclean on all changed files.go vet ./mempool/... ./types/... ./node/... ./rpc/...clean (one pre-existing,unrelated finding on unmodified
main, confirmed not touched by this diff).go test ./mempool/...,./types/...,./node/...,./rpc/core/...allpass, plus a targeted rerun of the existing
TestTxEventsSentto confirm noregression on the adjacent, pre-existing
Tx-event subscription flow.