Skip to content

feat: block MsgCreateValidator post-genesis in PoA ante decorator#125

Merged
AdriaCarrera merged 1 commit into
mainfrom
feat/poa-block-create-validator
May 13, 2026
Merged

feat: block MsgCreateValidator post-genesis in PoA ante decorator#125
AdriaCarrera merged 1 commit into
mainfrom
feat/poa-block-create-validator

Conversation

@kpitapeersyst

@kpitapeersyst kpitapeersyst commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

feat: block MsgCreateValidator post-genesis in PoA ante decorator

Motivation 💡

In a PoA chain the validator set is closed: validators are added exclusively through x/poa's ExecuteAddValidator, which is the only path that mints bond-denomination tokens (and self-delegates them immediately). Because no bond-denom should ever sit on a user account, MsgCreateValidator is unsatisfiable in practice today.

That protection is a chain-wide invariant, not an ante-level check, and it is implicit. Any future regression that leaks bond-denom into a user-controlled account (mint path change, IBC misconfig, buggy upgrade handler, airdrop mistake) would immediately allow unauthorized validator registration with no backstop, directly corrupting the closed PoA validator set.

This PR adds a cheap defense-in-depth: reject MsgCreateValidator at the ante stage post-genesis, with a height-0 carve-out so genutil gentxs still bootstrap the initial validator set.

Changes 🛠

  • x/poa/ante/poa.go: added MsgCreateValidator to the disallowed message set and introduced a BlockHeight() == 0 carve-out so genesis gentxs continue to work.
  • x/poa/ante/poa.go: replaced the inline || chain with a disallowedMsgs map and lifted the error to a ErrTxTypeNotAllowed sentinel so callers and tests can match it with errors.Is.
  • app/ante/cosmos_handler.go: added MsgCreateValidator to AuthzLimiterDecorator so it cannot be smuggled through authz.MsgExec.
  • x/poa/ante/poa_test.go: split the single aggregated case into one subtest per blocked message (including the new MsgCreateValidator case), added a positive case for MsgCreateValidator at blockHeight: 0 to lock in the gentx carve-out, kept the MsgEditValidator allow case to document that editing validator metadata is intentionally not gated, and switched assertions to require.ErrorIs against the sentinel.

Considerations 🤔

  • MsgEditValidator is intentionally left unblocked. Validators are responsible for their own metadata (description, commission within module-enforced bounds), and gating it behind PoA authority would add operational overhead with no real security benefit.
  • This is defense-in-depth, not the primary protection. The real invariant (bond-denom can only be minted by x/poa) still lives in the mint path, reviewers should not treat this ante check as a license to relax that invariant.
  • Each disallowed msg type needs its own test case because the loop returns on the first match, so a single combined case would only ever exercise the first msg in the slice. The split is intentional coverage, not redundancy.

Summary by CodeRabbit

  • New Features

    • Validator creation can now be executed through authorization mechanisms, expanding permissible transaction types.
  • Bug Fixes

    • Transaction validation at genesis block now properly allows all message types without restrictions.

@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Three files are updated to enhance authz message handling and refactor PoA ante validation logic. The cosmos handler now permits validator creation through authz execution, while PoA ante logic consolidates message-type restrictions into a lookup map with special genesis-block bypass handling. Tests are restructured as table-driven subtests with block height context injection.

Changes

Cohort / File(s) Summary
Authz/Staking Integration
app/ante/cosmos_handler.go
Adds stakingtypes.MsgCreateValidator to the allowlist of staking messages permitted within authz.MsgExec execution.
PoA Ante Validation
x/poa/ante/poa.go
Refactors message-type validation from individual comparisons to a centralized disallowedMsgs lookup map; introduces ErrTxTypeNotAllowed sentinel error; delegates directly to next(...) at genesis block height (0) to bypass restrictions.
PoA Ante Tests
x/poa/ante/poa_test.go
Restructures tests as table-driven subtests; injects block height per case; verifies behavior at genesis (blockHeight: 0) and post-genesis (blockHeight: 1); uses require.ErrorIs for sentinel error matching.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Poem

🐰 A validator hops through authz gates,
While PoA maps simplify the states,
At genesis we leap straight through,
Refactored tests now subtly brew,
Clean logic hops to heights anew! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: blocking MsgCreateValidator post-genesis in the PoA ante decorator, which is the core purpose of this PR.
Description check ✅ Passed The description follows the template structure with all required sections (Motivation, Changes, Considerations, Dependencies). It provides detailed context, lists all file changes, explains the rationale, and acknowledges design tradeoffs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/poa-block-create-validator

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.4)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
app/ante/cosmos_handler.go (1)

32-32: LGTM — keeps authz wrapping consistent with the PoA ante list.

Adding MsgCreateValidator here mirrors the new entry in x/poa/ante/poa.go's disallowedMsgs map and prevents trivial bypass via authz.MsgExec. The two lists must stay in sync; if a sixth disallowed staking msg is added later, both files need updating. Consider extracting a shared slice of disallowed staking msg type URLs in a follow-up to make that drift impossible, but not blocking for this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/ante/cosmos_handler.go` at line 32, Add the staking MsgCreateValidator
type URL to the ante disallowed messages so it matches the poa disallowedMsgs
entry: ensure sdk.MsgTypeURL(&stakingtypes.MsgCreateValidator{}) is present in
the ante list (the same message already added to x/poa/ante/poa.go's
disallowedMsgs) and keep both lists in sync to prevent authz.MsgExec bypass; as
a follow-up, consider extracting a shared slice (e.g.,
disallowedStakingMsgTypeURLs) referenced by both the ante list and
poa.disallowedMsgs to avoid future drift.
x/poa/ante/poa_test.go (1)

31-101: LGTM — coverage matches the new behavior cleanly.

Per-message subtests, the genesis carve-out positive case for MsgCreateValidator, and require.ErrorIs against the sentinel all align well with the refactor in poa.go. Two optional additions to consider in a follow-up:

  • A multi-msg tx test (e.g. []sdk.Msg{&stakingtypes.MsgEditValidator{}, &stakingtypes.MsgUndelegate{}} at height 1) to lock in that the loop rejects on any blocked msg, not just the first.
  • If you ever narrow the genesis carve-out (see comment in poa.go), add a negative case at height 0 for one of the other staking msgs.

Neither is blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@x/poa/ante/poa_test.go` around lines 31 - 101, Add two optional subtests to
TestPoaDecorator_AnteHandle: (1) a multi-message case that passes a slice like
[]sdk.Msg{&stakingtypes.MsgEditValidator{}, &stakingtypes.MsgUndelegate{}} at
blockHeight 1 and assert pd.AnteHandle returns ErrTxTypeNotAllowed (to ensure
the decorator rejects a tx if any msg is blocked), and (2) a genesis-negative
case that uses blockHeight 0 with a blocked staking message (e.g.
&stakingtypes.MsgUndelegate{}) and assert pd.AnteHandle returns
ErrTxTypeNotAllowed (to guard against narrowing the genesis carve-out). Use the
same txMock.GetMsgs() pattern and require.ErrorIs assertions as in the existing
TestPoaDecorator_AnteHandle.
x/poa/ante/poa.go (1)

10-10: Use cosmossdk.io/errors.Register with a module-scoped error code and include the msg type URL for better debugging.

Two related improvements to the error handling:

  1. Using stdlib errors.New() doesn't carry an ABCI codespace/code, so rejected txs will surface with the generic SDK internal error code rather than a module-scoped one. Other Cosmos SDK ante decorators (e.g. authz's ErrUnauthorized) use cosmossdk.io/errors.Register("poa", N, "tx type not allowed") for this reason. Error codes 1–7 are already taken in the module; code 1100 is available.

  2. The returned error doesn't tell the caller which msg type was blocked, making debugging rejected txs harder. Include the msgURL in the error message.

Suggested change (preserves errors.Is(..., ErrTxTypeNotAllowed) semantics used by tests):

♻️ Proposed refactor
-import (
-	"errors"
-
-	sdk "github.com/cosmos/cosmos-sdk/types"
-	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
-)
-
-var ErrTxTypeNotAllowed = errors.New("tx type not allowed")
+import (
+	"fmt"
+
+	sdkerrors "cosmossdk.io/errors"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
+	"github.com/xrplevm/node/v10/x/poa/types"
+)
+
+var ErrTxTypeNotAllowed = sdkerrors.Register(types.ModuleName, 1100, "tx type not allowed")
@@
-		if _, blocked := disallowedMsgs[sdk.MsgTypeURL(msg)]; blocked {
-			return ctx, ErrTxTypeNotAllowed
+		msgURL := sdk.MsgTypeURL(msg)
+		if _, blocked := disallowedMsgs[msgURL]; blocked {
+			return ctx, fmt.Errorf("%w: %s", ErrTxTypeNotAllowed, msgURL)
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@x/poa/ante/poa.go` at line 10, Replace the plain stdlib error
ErrTxTypeNotAllowed with a module-registered error using
cosmossdk.io/errors.Register: register it under the "poa" module with a unique
code (suggested 1100) and a message that includes the blocked message type URL
so rejections carry the module codespace and the offending msg type; update any
places that construct or return ErrTxTypeNotAllowed (e.g., inside the ante
decorator that validates messages) to pass the msg URL into the registered error
so the returned error text shows which msg type was blocked while preserving
errors.Is(..., ErrTxTypeNotAllowed) semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@x/poa/ante/poa.go`:
- Around line 27-30: The genesis carve-out currently returns early when
ctx.BlockHeight() == 0, skipping the disallowedMsgs check and allowing any gentx
messages; narrow this to only bypass the check for MsgCreateValidator: instead
of unconditional return in the BlockHeight() == 0 branch, inspect the
transaction messages (tx.GetMsgs() / tx.GetMsgs or sdk.Tx message iteration) and
only call next(ctx, tx, simulate) if every message is a staking
MsgCreateValidator (or matches its type URL); otherwise, proceed to the existing
disallowedMsgs validation. Reference: ctx.BlockHeight(), next(ctx, tx,
simulate), disallowedMsgs, and MsgCreateValidator.

---

Nitpick comments:
In `@app/ante/cosmos_handler.go`:
- Line 32: Add the staking MsgCreateValidator type URL to the ante disallowed
messages so it matches the poa disallowedMsgs entry: ensure
sdk.MsgTypeURL(&stakingtypes.MsgCreateValidator{}) is present in the ante list
(the same message already added to x/poa/ante/poa.go's disallowedMsgs) and keep
both lists in sync to prevent authz.MsgExec bypass; as a follow-up, consider
extracting a shared slice (e.g., disallowedStakingMsgTypeURLs) referenced by
both the ante list and poa.disallowedMsgs to avoid future drift.

In `@x/poa/ante/poa_test.go`:
- Around line 31-101: Add two optional subtests to TestPoaDecorator_AnteHandle:
(1) a multi-message case that passes a slice like
[]sdk.Msg{&stakingtypes.MsgEditValidator{}, &stakingtypes.MsgUndelegate{}} at
blockHeight 1 and assert pd.AnteHandle returns ErrTxTypeNotAllowed (to ensure
the decorator rejects a tx if any msg is blocked), and (2) a genesis-negative
case that uses blockHeight 0 with a blocked staking message (e.g.
&stakingtypes.MsgUndelegate{}) and assert pd.AnteHandle returns
ErrTxTypeNotAllowed (to guard against narrowing the genesis carve-out). Use the
same txMock.GetMsgs() pattern and require.ErrorIs assertions as in the existing
TestPoaDecorator_AnteHandle.

In `@x/poa/ante/poa.go`:
- Line 10: Replace the plain stdlib error ErrTxTypeNotAllowed with a
module-registered error using cosmossdk.io/errors.Register: register it under
the "poa" module with a unique code (suggested 1100) and a message that includes
the blocked message type URL so rejections carry the module codespace and the
offending msg type; update any places that construct or return
ErrTxTypeNotAllowed (e.g., inside the ante decorator that validates messages) to
pass the msg URL into the registered error so the returned error text shows
which msg type was blocked while preserving errors.Is(..., ErrTxTypeNotAllowed)
semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a26dd4f0-b2b1-4188-bb45-b40efc7ba714

📥 Commits

Reviewing files that changed from the base of the PR and between 43c1c36 and 819cc37.

📒 Files selected for processing (3)
  • app/ante/cosmos_handler.go
  • x/poa/ante/poa.go
  • x/poa/ante/poa_test.go

Comment thread x/poa/ante/poa.go
@AdriaCarrera AdriaCarrera merged commit e939d58 into main May 13, 2026
6 checks passed
@AdriaCarrera AdriaCarrera deleted the feat/poa-block-create-validator branch May 13, 2026 15:05
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.

2 participants