fix: let a pre-launch consumer be retired and release its chain id - #78
Open
giunatale wants to merge 1 commit into
Open
fix: let a pre-launch consumer be retired and release its chain id#78giunatale wants to merge 1 commit into
giunatale wants to merge 1 commit into
Conversation
Creating a consumer is permissionless and, until now, nothing could undo it before launch: MsgRemoveConsumer accepts only a launched or paused consumer, MsgUpdateConsumer is owner-only, and DeleteConsumerChain deliberately kept the chain-id entry that ChainIdInUse consults with no phase filter. So a single transaction reserved any chain-id string for good, a consumer whose owner key was lost sat in the registered phase forever with state nobody could prune, and a consumer the liveness sweep removed could never re-register under its own chain id. MsgRetireConsumer erases a consumer that is still registered or initialized. Its signer may be the consumer owner or the gov authority, the same owner-or-gov admission MsgFundConsumerFeePool and MsgWithdrawConsumerFeePool use: the owner arm lets whoever registered a chain abandon it, and the gov arm is the remedy when the owner key is lost. MsgRemoveConsumer keeps its gov-only, launched-or-paused scope, because a live chain must stop and wait out the unbonding period before its state can go; a chain no validator ever validated has nothing to unbond, so retirement runs the same teardown immediately. It reuses DeleteConsumerChain instead of repeating part of it, so a funded fee pool is still paid back to its depositors pro rata, and it drops the spawn-time queue entry an initialized consumer holds. DeleteConsumerChain now also releases the chain id, on both routes into it. That point is safe and an earlier one is not: the client mapping is removed in the same call, so no inbound packet can be attributed to the consumer any more (the provider resolves packets by destination client), and evidence, downtime accusations and fee distribution all require the launched phase; on the stop-then-remove route a full unbonding period has additionally passed since the stop, so nothing the chain did remains punishable. The tombstone still keeps owner, metadata, initialization parameters and phase so a removed consumer stays visible to explorers, and genesis now requires a deleted consumer's chain id to be empty, which is what makes the release survive a state-export restart. Two assumptions the pre-launch path broke are fixed alongside: DeleteConsumerClientId panicked on a consumer that never had a client adopted, and the genesis export/import path plus the consumer-chain queries assumed every consumer record carries a chain id.
giunatale
requested review from
clockworkgr,
julienrbrt and
tbruyelle
as code owners
July 31, 2026 18:55
Contributor
Author
julienrbrt
requested changes
Aug 7, 2026
| // signs to abandon a chain it no longer intends to launch; governance signs | ||
| // when the owner key is lost, which would otherwise pin the consumer -- and its | ||
| // chain id -- in place forever. | ||
| message MsgRetireConsumer { |
Member
There was a problem hiding this comment.
I understand the reason of this PR but I do not like the new message. Why cannot we simply tweak MsgRemoveConsumer instead?
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 two gaps in the consumer lifecycle: a consumer that never launched could
not be terminated, and a chain id was reserved forever by the first transaction
that used it.
What was wrong
MsgRemoveConsumerrequires LAUNCHED or PAUSED,MsgUpdateConsumerisowner-only with no authority path, and the chain-id uniqueness check iterates
every registered chain id with no phase filter — while
DeleteConsumerChaindeliberately kept the chain-id entry and the function that would remove it had no
caller. Since consumer creation is permissionless with no deposit, that means:
one transaction permanently reserves any chain-id string with no governance
remedy; an owner who loses their key before launch strands a consumer in
REGISTERED forever with state nothing can prune; and a consumer removed by the
liveness sweep can never re-register under its own chain id.
What this changes
MsgRetireConsumer(signer + consumer id) terminates a consumer that has notlaunched. Its authority model mirrors the module's existing owner-or-governance
messages: the signer must be the consumer's owner, or the governance authority —
which covers the lost-key case.
MsgRemoveConsumerkeeps its governance-only,launched-or-paused scope; retirement reuses the full deletion teardown rather
than reimplementing a partial one, and additionally drops the launch-queue entry
an INITIALIZED consumer holds. A funded fee pool is swept to its depositors pro
rata on the way out, with truncation dust to the community pool, so retiring a
consumer never strands deposits.
Chain ids are released at deletion, immediately before the phase becomes
DELETED, on both routes into it. That point is deliberate: the client mapping is
already gone by then and the provider authenticates inbound packets by
destination client, evidence and fee paths all require LAUNCHED, and nothing
anywhere reverse-maps a chain id to a consumer — so a later registration of the
same chain id cannot inherit anything. Releasing at STOPPED would not have been
safe, because the client mapping still exists there and a look-alike could get
the old consumer's still-pinned client adopted by chain-id matching.
Releasing the chain id turned out not to be self-contained: the chain-id row is
load-bearing for the DELETED tombstone, and dropping it naively would have made
provider genesis export panic after the first deletion, made import panic
on a legitimately exported genesis, and broken the main consumer-list query
entirely. All four sites are fixed, and genesis now requires a deleted consumer's
chain id to be empty so the release survives an export/restart. A latent panic in
the client-id deletion path for a consumer that never adopted a client — hidden
because every existing test set one first — is fixed too.
Testing
Owner retires REGISTERED and INITIALIZED; governance retires a lost-key
consumer; LAUNCHED, PAUSED, STOPPED and DELETED are rejected for both signer
arms; a stranger is rejected and state is untouched; a funded pool is returned to
depositors; the chain id is re-registrable afterwards and is not released while
the consumer is non-terminal; and the stop-then-remove route is seeded with fully
lived state so the teardown assertion is non-vacuous. Mutation-checked: removing
the chain-id release fails four tests, keeping the launch-queue entry fails the
INITIALIZED case.