Skip to content

[IC-272][CP-341] Async CheckTx support and Unified mempool support#90

Open
kakysha wants to merge 46 commits into
v0.50.x-injfrom
CP-341/parallel-checktx-proxy-mempool
Open

[IC-272][CP-341] Async CheckTx support and Unified mempool support#90
kakysha wants to merge 46 commits into
v0.50.x-injfrom
CP-341/parallel-checktx-proxy-mempool

Conversation

@kakysha
Copy link
Copy Markdown

@kakysha kakysha commented May 22, 2026

main PR: https://github.com/InjectiveLabs/injective-core/pull/2375

Summary by CodeRabbit

  • Improvements

    • Enhanced synchronization across ABCI entry points and consensus/block lifecycle to reduce concurrency issues.
    • Refined transaction/mempool lifecycle handling and tracing during CheckTx/ReCheck/DeliverTx and finalization.
    • Added capability to create copies of on-disk/in-memory stores for safer state branching.
  • Bug Fixes

    • Prevented genesis state hash mismatches during testnet initialization by clearing stale state entries and pretty-printing genesis.
  • Chores

    • Bumped CometBFT fork replacements and related module versions across repositories.

Review Change Stack

kakysha and others added 30 commits April 16, 2025 22:28
…xcept Commit

Commit is different cause it invokes mempool.Update, which in turns calls RecheckTx on every mempool tx -> needs to be serialized before checking new incoming txns
…el to bloc kexecution

+ Copy() method in rootmultiStore
so initial_height type is serialized into string and not int
…r `initial_height` (#67)

* fix(genesis): align SDK genesis with BFT genesis to use cmtjson

so initial_height type is serialized into string and not int

* fix(devnetify): delete genesisHash from StateDB after changing genesis
…ring devnetify or export)

since I added cms.Copy() when we re-set CheckState, if we have special storeLoader set that will apply store upgrades, we need to use them instead of `LoadLatestVersion()`

+ ignore new mempool TxNotFound error type to not panic during `DeliverGenTxs()` while `InitChain()`
…lock

+ before we were not removing it in case antehandlers error
to support function errors
@linear
Copy link
Copy Markdown

linear Bot commented May 22, 2026

IC-272

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9f2c72c9-3c01-4aa1-8507-4e1d4711e8fe

📥 Commits

Reviewing files that changed from the base of the PR and between 1b53151 and 7a565bf.

📒 Files selected for processing (1)
  • store/rootmulti/store.go

📝 Walkthrough

Walkthrough

Adds global ABCI mutexing, isolates CheckTx by copying the root multistore, refactors BaseApp ante/mempool/span handling and tx lifecycle, deletes stale genesis hash during testnetify, and updates InjectiveLabs CometBFT dependency pins.

Changes

ABCI Concurrency and State Isolation

Layer / File(s) Summary
Multistore cloning for state isolation
store/rootmulti/store.go
Adds Store.Copy and copyStore helper to clone root multi-store and copy Memory/Object store contents for isolated CheckTx execution.
BaseApp concurrency and execution refactoring
baseapp/baseapp.go
Adds BaseApp.mtx and exported AnteMutexes context key; modifies setState for execModeCheck to use multistore Copy and cached branch; captures/reassigns tracing spans; refactors ante-handler flow and mempool removal/commit timing; adds deferred mempool cleanup on finalize.
ABCI entrypoint synchronization
baseapp/abci.go
Wraps InitChain, Info, Query, snapshot lifecycle methods, proposal/vote methods, FinalizeBlock, and Commit with app.mtx locking; CheckTx remains unlocked and documented as synchronized via mempool mutex.
Testnet genesis handling and state DB cleanup
server/start.go, x/genutil/types/genesis.go
Removes stale genesisDocHash from state DB after regenerating genesis, avoids duplicated stateDB provider calls, and stores pretty-printed genesis JSON via CometBFT JSON helper.
Dependency and version updates
go.mod, simapp/go.mod, store/go.mod, tests/go.mod, x/circuit/go.mod, x/evidence/go.mod, x/feegrant/go.mod, x/nft/go.mod, x/upgrade/go.mod
Bumps InjectiveLabs CometBFT replace pins across modules and promotes go.opentelemetry.io/otel/trace to a direct requirement.

Sequence Diagram

sequenceDiagram
  participant ABCI
  participant BaseApp as BaseApp.runTxWithMultiStore
  participant Store as RootMultiStore.Copy
  participant AnteHandler as Ante Handler
  participant Mempool
  participant Tracer as Tracing Span

  ABCI->>BaseApp: runTxWithMultiStore
  BaseApp->>Tracer: SpanFromContext (metricsRunTxSpan)
  alt execModeCheck
    BaseApp->>Store: Copy multistore
    BaseApp->>BaseApp: create cached branch + set AnteMutexes
  end
  BaseApp->>AnteHandler: cacheTxContext (anteCtx, msCache)
  alt Ante success
    AnteHandler->>BaseApp: commit msCache (timed by execMode)
  else Ante failure
    alt execModeReCheck && !ErrWrongSequence
      AnteHandler->>Mempool: RemoveTx
    end
  end
  alt execModeFinalize
    BaseApp->>Mempool: deferred RemoveTx
  end
  BaseApp->>Tracer: Reassign span to runTx span
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I copied stores and tightened locks,
spans reclaimed from tangled clocks,
mempool sweeps the fallen tx,
genesis pretty, hashes fixed,
hopping through commits, code ticks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the two main objectives: adding async CheckTx support and unified mempool support, which are core to this changeset across baseapp, server, and store modules.
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 CP-341/parallel-checktx-proxy-mempool
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch CP-341/parallel-checktx-proxy-mempool

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/start.go (1)

998-1003: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist the exact genesis bytes you just wrote.

This path re-encodes genDoc after AppGenesis.SaveAs already wrote the file. Because the hash mismatch you are fixing is byte-sensitive, using a second marshaling path here can leave genesisDoc in stateDB out of sync with the on-disk genesis again. Reuse the file bytes from genFilePath or a shared helper for both writes.

Proposed fix
-	b, err := cmtjson.MarshalIndent(genDoc, "", "  ")
+	b, err := os.ReadFile(genFilePath)
 	if err != nil {
 		return nil, err
 	}
 	if err := stateDB.SetSync([]byte("genesisDoc"), b); err != nil {
 		return nil, err
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/start.go` around lines 998 - 1003, The code re-marshals genDoc with
cmtjson.MarshalIndent before writing to stateDB, which can produce
byte-different output than the bytes written by AppGenesis.SaveAs; instead,
persist the exact bytes that were written to disk: retrieve the genesis file
bytes (e.g., read the file at genFilePath that AppGenesis.SaveAs wrote, or
change AppGenesis.SaveAs to return the written []byte) and pass those exact
bytes into stateDB.SetSync([]byte("genesisDoc"), fileBytes) rather than
re-encoding genDoc with cmtjson.MarshalIndent.
baseapp/baseapp.go (1)

1020-1035: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return ante events on ante failure instead of dropping them.

When ante fails, Line 1034 returns nil ante events even though events are already collected. This loses useful CheckTx/ReCheck diagnostics.

Suggested fix
-		events := ctx.EventManager().Events()
+		events := ctx.EventManager().Events()
+		anteEvents = events.ToABCIEvents()
...
-			return gInfo, nil, nil, err
+			return gInfo, nil, anteEvents, err
 		}
...
-		anteEvents = events.ToABCIEvents()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baseapp/baseapp.go` around lines 1020 - 1035, The ante failure branch
currently discards collected ante events by returning nil; preserve and return
the collected events variable (events or anteEvents as used nearby) instead of
nil so diagnostics are not lost. In the error handling after the AnteHandler
(the block that checks err, mode == execModeReCheck, sdkerrors.ErrWrongSequence
and potentially calls app.mempool.Remove(tx)), change the final return from
"return gInfo, nil, nil, err" to return the collected ante events (e.g., "return
gInfo, nil, anteEvents, err" or the local events variable) so ante events from
ctx.EventManager().Events() are propagated back to callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@baseapp/baseapp.go`:
- Around line 1042-1048: In CheckTx (execModeCheck) the code calls
msCache.Write() unconditionally after app.mempool.Insert, which can panic if
app.anteHandler == nil because msCache is never initialized; fix by guarding the
write with a nil-check (e.g., if msCache != nil { msCache.Write() }) or ensure
msCache is created earlier when needed—update the block containing
execModeCheck, app.mempool.Insert, and msCache.Write to only call msCache.Write
when msCache is non-nil.
- Around line 967-969: The mempool removal error is being wrapped into the
existing accumulator `err` using `errorsmod.Wrapf(err, ...)`, which returns nil
when `err` is nil and thus drops `mempoolErr`; update the finalize path so you
wrap or join `mempoolErr` itself (or use errorsmod.Join) instead of wrapping
`err` — locate the block around `app.mempool.Remove(tx)` and replace the `err =
errorsmod.Wrapf(err, "mempool remove err: %s", mempoolErr.Error())` logic with
code that preserves `mempoolErr` (e.g., `err = errorsmod.Wrapf(mempoolErr,
"mempool remove err: %s", mempoolErr.Error())` or `err = errorsmod.Join(err,
mempoolErr)`) so mempool removal failures are not silently discarded.

In `@server/start.go`:
- Around line 789-795: The stateDB opened by cmtcfg.DefaultDBProvider is not
closed on intermediate error paths (it currently defers closing later), so move
the close immediately after the successful open: after stateDB, err :=
cmtcfg.DefaultDBProvider(...) and the err check, call defer stateDB.Close() (or
ensure stateDB.Close() is invoked on every subsequent early return such as after
stateDB.DeleteSync and any addrbook/blockstore setup failures) so the DB handle
is always closed on error; reference stateDB, cmtcfg.DefaultDBProvider, and
stateDB.DeleteSync to locate the code to change.

In `@store/rootmulti/store.go`:
- Around line 130-132: The code does unchecked type assertions on
copyRs.stores[key] (e.g., when assigning dstKV) which can panic if the
destination store is missing or the wrong type; update the logic around
copyRs.stores[key] and the block that assigns dstKV/srcKV in the function
containing copyStore so you first check existence (ok := copyRs.stores[key]) and
type-assert safely (dstKV, ok := value.(types.KVStore)), and if either check
fails return a descriptive error instead of panicking; apply the same guarded
checks for the other similar block referenced (lines around the second copyStore
call).

---

Outside diff comments:
In `@baseapp/baseapp.go`:
- Around line 1020-1035: The ante failure branch currently discards collected
ante events by returning nil; preserve and return the collected events variable
(events or anteEvents as used nearby) instead of nil so diagnostics are not
lost. In the error handling after the AnteHandler (the block that checks err,
mode == execModeReCheck, sdkerrors.ErrWrongSequence and potentially calls
app.mempool.Remove(tx)), change the final return from "return gInfo, nil, nil,
err" to return the collected ante events (e.g., "return gInfo, nil, anteEvents,
err" or the local events variable) so ante events from
ctx.EventManager().Events() are propagated back to callers.

In `@server/start.go`:
- Around line 998-1003: The code re-marshals genDoc with cmtjson.MarshalIndent
before writing to stateDB, which can produce byte-different output than the
bytes written by AppGenesis.SaveAs; instead, persist the exact bytes that were
written to disk: retrieve the genesis file bytes (e.g., read the file at
genFilePath that AppGenesis.SaveAs wrote, or change AppGenesis.SaveAs to return
the written []byte) and pass those exact bytes into
stateDB.SetSync([]byte("genesisDoc"), fileBytes) rather than re-encoding genDoc
with cmtjson.MarshalIndent.
🪄 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: fec5f8a0-3b01-46c1-b203-698666e1f2c7

📥 Commits

Reviewing files that changed from the base of the PR and between 54a721f and 1b53151.

⛔ Files ignored due to path filters (9)
  • go.sum is excluded by !**/*.sum
  • simapp/go.sum is excluded by !**/*.sum
  • store/go.sum is excluded by !**/*.sum
  • tests/go.sum is excluded by !**/*.sum
  • x/circuit/go.sum is excluded by !**/*.sum
  • x/evidence/go.sum is excluded by !**/*.sum
  • x/feegrant/go.sum is excluded by !**/*.sum
  • x/nft/go.sum is excluded by !**/*.sum
  • x/upgrade/go.sum is excluded by !**/*.sum
📒 Files selected for processing (14)
  • baseapp/abci.go
  • baseapp/baseapp.go
  • go.mod
  • server/start.go
  • simapp/go.mod
  • store/go.mod
  • store/rootmulti/store.go
  • tests/go.mod
  • x/circuit/go.mod
  • x/evidence/go.mod
  • x/feegrant/go.mod
  • x/genutil/types/genesis.go
  • x/nft/go.mod
  • x/upgrade/go.mod

Comment thread baseapp/baseapp.go
Comment on lines +967 to +969
if mempoolErr := app.mempool.Remove(tx); mempoolErr != nil && !errors.Is(mempoolErr, mempool.ErrTxNotFound) {
err = errorsmod.Wrapf(err, "mempool remove err: %s", mempoolErr.Error())
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Wrapf nil behavior and current project patterns for combining errors.
# Expected: evidence that Wrapf(nil, ...) yields nil (or non-nil), and preferred join/wrap pattern.
rg -n -C2 'func Wrapf\(|Wrapf\(' --type=go
rg -n -C2 'errors\.Join\(|mempool remove err' --type=go

Repository: InjectiveLabs/cosmos-sdk

Length of output: 50381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the exact surrounding code around baseapp/baseapp.go:967-969
FILE="baseapp/baseapp.go"
echo "=== Context around $FILE:967-969 ==="
sed -n '930,1010p' "$FILE" | nl -ba | sed -n '1,120p'

# 2) Identify how err is declared/initialized in the same function/scope
echo "=== Find 'err :=' / 'var err' nearby ==="
rg -n --no-heading "(\berr\b\s*(:=|=)\s*nil|\bvar\s+err\b|\berr\s*:=)" "$FILE" | tail -n 30

# 3) Locate errorsmod.Wrapf definition / implementation (to understand nil base behavior)
echo "=== Find errorsmod Wrapf implementation in repo ==="
rg -n --no-heading "func .*Wrapf|Wrapf\(" -S "types/errors|errorsmod|errorsmod\.Wrapf" . || true

# 4) Narrow: where is errorsmod imported from?
echo "=== Find errorsmod import path in baseapp/baseapp.go ==="
rg -n --no-heading "errorsmod" "$FILE" | head -n 20

# 5) See how errors.Join is used in this repo (patterns for combining errors)
echo "=== Search for errors.Join usage in repo ==="
rg -n --no-heading "errors\.Join\(" --type=go . | head -n 50

Repository: InjectiveLabs/cosmos-sdk

Length of output: 179


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="baseapp/baseapp.go"

echo "=== Find exact location of mempool.Remove(tx) around the reported lines ==="
rg -n "mempool\.Remove\(tx\)" "$FILE"

echo "=== Show surrounding code with line numbers (no nl available) ==="
awk 'NR>=940 && NR<=990 {printf "%6d\t%s\n", NR, $0}' "$FILE" | sed -n '1,60p'

echo "=== Show a wider window to understand how err is declared/accumulated ==="
awk 'NR>=880 && NR<=1030 {printf "%6d\t%s\n", NR, $0}' "$FILE" > /tmp/baseapp_slice.txt
sed -n '1,200p' /tmp/baseapp_slice.txt

echo "=== Find where err is declared/initialized within this vicinity ==="
rg -n --no-heading "(\berr\s*[:=]=|var\s+err\s+error|\berr\s*:\=|if\s+err\s*:=)" "$FILE" | rg -n "9(3[0-9]|4[0-9]|5[0-9]|6[0-9]|7[0-9]|8[0-9]|9[0-9])" || true

echo "=== Locate errorsmod import in baseapp/baseapp.go ==="
rg -n "errorsmod" "$FILE" | head -n 40

Repository: InjectiveLabs/cosmos-sdk

Length of output: 10392


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Try to locate the cosmossdk.io/errors package in the module cache and inspect Wrapf implementation.
echo "=== Locate cosmossdk.io/errors in module cache ==="
if command -v go >/dev/null 2>&1; then
  GOPATH="$(go env GOPATH)"
  echo "GOPATH=$GOPATH"
  # Print candidate dirs containing Wrapf in that module, if present
  rg -n --hidden --no-mmap "func\s+Wrapf\s*\(" "$GOPATH/pkg/mod" -S cosmossdk.io/errors >/dev/null 2>&1 || true
  # Ask go where the package lives (best-effort)
  go list -f '{{.Dir}}' cosmossdk.io/errors 2>/dev/null || true
fi

# 2) If go list succeeded, try to print Wrapf source (best-effort).
PKG_DIR="$(go list -f '{{.Dir}}' cosmossdk.io/errors 2>/dev/null || true)"
if [ -n "${PKG_DIR:-}" ] && [ -d "$PKG_DIR" ]; then
  echo "=== Inspect Wrapf implementation at $PKG_DIR ==="
  rg -n "func\s+Wrapf\s*\(" "$PKG_DIR" -S
  # show surrounding implementation lines for Wrapf
  FILE="$(rg -n "func\s+Wrapf\s*\(" "$PKG_DIR" -S --files | head -n 1 || true)"
  if [ -n "${FILE:-}" ]; then
    echo "=== Wrapf file: $FILE ==="
    sed -n '1,220p' "$FILE" | head -n 200 || true
    # narrower window around Wrapf
    LINE="$(rg -n "func\s+Wrapf\s*\(" "$FILE" -S | head -n 1 | cut -d: -f1)"
    if [ -n "${LINE:-}" ]; then
      START=$((LINE-20))
      END=$((LINE+60))
      sed -n "${START},${END}p" "$FILE" | nl -ba | head -n 120 || true
    fi
  fi
fi

# 3) Search this repo for patterns of combining errors or wrapping possibly-nil base errors.
echo "=== Search this repo for errors.Join usage ==="
rg -n --no-heading "errors\.Join\(" --type=go . | head -n 50 || true

echo "=== Search this repo for errorsmod.Wrapf usage where base is possibly an error variable ==="
# Focus on calls of the form errorsmod.Wrapf(err,
rg -n --no-heading "errorsmod\.Wrapf\(err\s*," --type=go . | head -n 50 || true

# Also check for any Wrapf(nil, ...) patterns.
rg -n --no-heading "errorsmod\.Wrapf\(\s*nil\s*," --type=go . || true

Repository: InjectiveLabs/cosmos-sdk

Length of output: 11853


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the Wrapf implementation in cosmossdk.io/errors@v1.0.1
PKG_DIR="$(go list -f '{{.Dir}}' cosmossdk.io/errors 2>/dev/null || true)"
echo "PKG_DIR=$PKG_DIR"

# Print the exact Wrapf implementation (and surrounding nil-handling)
if [ -n "$PKG_DIR" ] && [ -f "$PKG_DIR/errors.go" ]; then
  echo "=== errors.go: Wrapf implementation context ==="
  awk 'NR>=160 && NR<=260 {print NR ":" $0}' "$PKG_DIR/errors.go" | sed -n '1,120p'
else
  echo "Could not find $PKG_DIR/errors.go"
  exit 1
fi

# Also capture any helpers like Wrap/Join used by that Wrapf
echo "=== Search for Join usage in this package ==="
rg -n --no-heading "func\s+Join\s*\(|Join\(" "$PKG_DIR" || true

# Check how this repo combines errors (limited output)
echo "=== Sample errors.Join usage in repo ==="
rg -n --no-heading "errors\.Join\(" --type=go . | head -n 50 || true

echo "=== Sample errorsmod.Wrapf(err, ...) usage where err might be nil ==="
rg -n --no-heading "errorsmod\.Wrapf\(\s*err\s*," --type=go . | head -n 50 || true

Repository: InjectiveLabs/cosmos-sdk

Length of output: 7633


Fix mempool error wrapping in baseapp finalize path

errorsmod.Wrapf returns nil when the wrapped error is nil, so when the current err accumulator is nil, mempool removal failures are dropped at lines 967-969. Wrap/join mempoolErr instead to preserve the failure.

Suggested fix
-			if mempoolErr := app.mempool.Remove(tx); mempoolErr != nil && !errors.Is(mempoolErr, mempool.ErrTxNotFound) {
-				err = errorsmod.Wrapf(err, "mempool remove err: %s", mempoolErr.Error())
+			if mempoolErr := app.mempool.Remove(tx); mempoolErr != nil && !errors.Is(mempoolErr, mempool.ErrTxNotFound) {
+				if err != nil {
+					err = errors.Join(err, errorsmod.Wrapf(mempoolErr, "mempool remove err"))
+				} else {
+					err = errorsmod.Wrapf(mempoolErr, "mempool remove err")
+				}
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if mempoolErr := app.mempool.Remove(tx); mempoolErr != nil && !errors.Is(mempoolErr, mempool.ErrTxNotFound) {
err = errorsmod.Wrapf(err, "mempool remove err: %s", mempoolErr.Error())
}
if mempoolErr := app.mempool.Remove(tx); mempoolErr != nil && !errors.Is(mempoolErr, mempool.ErrTxNotFound) {
if err != nil {
err = errors.Join(err, errorsmod.Wrapf(mempoolErr, "mempool remove err"))
} else {
err = errorsmod.Wrapf(mempoolErr, "mempool remove err")
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baseapp/baseapp.go` around lines 967 - 969, The mempool removal error is
being wrapped into the existing accumulator `err` using `errorsmod.Wrapf(err,
...)`, which returns nil when `err` is nil and thus drops `mempoolErr`; update
the finalize path so you wrap or join `mempoolErr` itself (or use
errorsmod.Join) instead of wrapping `err` — locate the block around
`app.mempool.Remove(tx)` and replace the `err = errorsmod.Wrapf(err, "mempool
remove err: %s", mempoolErr.Error())` logic with code that preserves
`mempoolErr` (e.g., `err = errorsmod.Wrapf(mempoolErr, "mempool remove err: %s",
mempoolErr.Error())` or `err = errorsmod.Join(err, mempoolErr)`) so mempool
removal failures are not silently discarded.

Comment thread baseapp/baseapp.go
Comment on lines 1042 to 1048
if mode == execModeCheck {
err = app.mempool.Insert(ctx, tx)
if err != nil {
return gInfo, nil, anteEvents, err
}
} else if mode == execModeFinalize {
err = app.mempool.Remove(tx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
return gInfo, nil, anteEvents,
fmt.Errorf("failed to remove tx from mempool: %w", err)
} else {
msCache.Write() // commit ante changes since we succeeded on tx insertion into mempool
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Prevent nil msCache write when no ante handler is configured.

In CheckTx mode, msCache is only initialized inside the ante block. If app.anteHandler == nil, Line 1047 can panic on msCache.Write().

Suggested fix
 	if mode == execModeCheck {
 		err = app.mempool.Insert(ctx, tx)
 		if err != nil {
 			return gInfo, nil, anteEvents, err
-		} else {
+		} else if msCache != nil {
 			msCache.Write() // commit ante changes since we succeeded on tx insertion into mempool
 		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if mode == execModeCheck {
err = app.mempool.Insert(ctx, tx)
if err != nil {
return gInfo, nil, anteEvents, err
}
} else if mode == execModeFinalize {
err = app.mempool.Remove(tx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
return gInfo, nil, anteEvents,
fmt.Errorf("failed to remove tx from mempool: %w", err)
} else {
msCache.Write() // commit ante changes since we succeeded on tx insertion into mempool
}
if mode == execModeCheck {
err = app.mempool.Insert(ctx, tx)
if err != nil {
return gInfo, nil, anteEvents, err
} else if msCache != nil {
msCache.Write() // commit ante changes since we succeeded on tx insertion into mempool
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baseapp/baseapp.go` around lines 1042 - 1048, In CheckTx (execModeCheck) the
code calls msCache.Write() unconditionally after app.mempool.Insert, which can
panic if app.anteHandler == nil because msCache is never initialized; fix by
guarding the write with a nil-check (e.g., if msCache != nil { msCache.Write()
}) or ensure msCache is created earlier when needed—update the block containing
execModeCheck, app.mempool.Insert, and msCache.Write to only call msCache.Write
when msCache is non-nil.

Comment thread server/start.go
Comment on lines +789 to +795
stateDB, err := cmtcfg.DefaultDBProvider(&cmtcfg.DBContext{ID: "state", Config: config})
if err != nil {
return nil, err
}
if err := stateDB.DeleteSync([]byte("genesisDocHash")); err != nil {
return nil, err
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Close stateDB on all error paths.

stateDB now opens at Line 789, but its defer still sits later at Line 819. Any failure during the addrbook or blockstore setup leaks the DB handle. Move the close immediately after the successful open.

Proposed fix
 stateDB, err := cmtcfg.DefaultDBProvider(&cmtcfg.DBContext{ID: "state", Config: config})
 if err != nil {
 	return nil, err
 }
+defer stateDB.Close()
+
 if err := stateDB.DeleteSync([]byte("genesisDocHash")); err != nil {
 	return nil, err
 }
...
-	defer stateDB.Close()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
stateDB, err := cmtcfg.DefaultDBProvider(&cmtcfg.DBContext{ID: "state", Config: config})
if err != nil {
return nil, err
}
if err := stateDB.DeleteSync([]byte("genesisDocHash")); err != nil {
return nil, err
}
stateDB, err := cmtcfg.DefaultDBProvider(&cmtcfg.DBContext{ID: "state", Config: config})
if err != nil {
return nil, err
}
defer stateDB.Close()
if err := stateDB.DeleteSync([]byte("genesisDocHash")); err != nil {
return nil, err
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/start.go` around lines 789 - 795, The stateDB opened by
cmtcfg.DefaultDBProvider is not closed on intermediate error paths (it currently
defers closing later), so move the close immediately after the successful open:
after stateDB, err := cmtcfg.DefaultDBProvider(...) and the err check, call
defer stateDB.Close() (or ensure stateDB.Close() is invoked on every subsequent
early return such as after stateDB.DeleteSync and any addrbook/blockstore setup
failures) so the DB handle is always closed on error; reference stateDB,
cmtcfg.DefaultDBProvider, and stateDB.DeleteSync to locate the code to change.

Comment thread store/rootmulti/store.go
Comment on lines +130 to +132
srcKV := src.(types.KVStore)
dstKV := copyRs.stores[key].(types.KVStore)
copyStore(srcKV, dstKV)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard destination store lookups before type assertions.

copyRs.stores[key] is asserted without existence/type checks. If a custom storeLoader does not mount the expected non-disk store, this panics at runtime instead of returning an error.

Suggested fix
-			srcKV := src.(types.KVStore)
-			dstKV := copyRs.stores[key].(types.KVStore)
+			srcKV := src.(types.KVStore)
+			dstStore, ok := copyRs.stores[key]
+			if !ok {
+				return nil, fmt.Errorf("missing destination memory store for key %s", key.Name())
+			}
+			dstKV, ok := dstStore.(types.KVStore)
+			if !ok {
+				return nil, fmt.Errorf("destination store for key %s is not KVStore: %T", key.Name(), dstStore)
+			}
 			copyStore(srcKV, dstKV)
-			srcKV := src.(types.ObjKVStore)
-			dstKV := copyRs.stores[key].(types.ObjKVStore)
+			srcKV := src.(types.ObjKVStore)
+			dstStore, ok := copyRs.stores[key]
+			if !ok {
+				return nil, fmt.Errorf("missing destination object store for key %s", key.Name())
+			}
+			dstKV, ok := dstStore.(types.ObjKVStore)
+			if !ok {
+				return nil, fmt.Errorf("destination store for key %s is not ObjKVStore: %T", key.Name(), dstStore)
+			}
 			copyStore(srcKV, dstKV)

Also applies to: 138-140

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@store/rootmulti/store.go` around lines 130 - 132, The code does unchecked
type assertions on copyRs.stores[key] (e.g., when assigning dstKV) which can
panic if the destination store is missing or the wrong type; update the logic
around copyRs.stores[key] and the block that assigns dstKV/srcKV in the function
containing copyStore so you first check existence (ok := copyRs.stores[key]) and
type-assert safely (dstKV, ok := value.(types.KVStore)), and if either check
fails return a descriptive error instead of panicking; apply the same guarded
checks for the other similar block referenced (lines around the second copyStore
call).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants