v30 chain upgrade — Cosmos SDK v0.53.7 + wasmvm v3 + ibc-go v10 (Path A+) - #2
Open
juno-ai-dev wants to merge 108 commits into
Open
v30 chain upgrade — Cosmos SDK v0.53.7 + wasmvm v3 + ibc-go v10 (Path A+)#2juno-ai-dev wants to merge 108 commits into
juno-ai-dev wants to merge 108 commits into
Conversation
Nine planning docs (00-overview through 08-security-review + README) captured during the v30 upgrade research phase. Source of truth for the Path B migration: SDK v0.54.3, wasmd v0.70.0, wasmvm v3.0.4, ibc-go v11.0.0, cometbft v0.39.3, store/v2. CLAUDE.md documents the existing repo layout (toolchain, app/ wiring, custom x/ modules, CosmWasm integration, fee pipeline) for Claude Code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two latent breakages in the v30 branch's CI: 1. Four workflows pinned GO_VERSION 1.23.9 while go.mod and .mise.toml require 1.25.2. CI couldn't have built v30 as-is. Bump build.yml, codeql.yml, golangci-lint.yml, interchaintest-E2E.yml to 1.25.2 to match the toolchain (Dockerfile was already updated in 0e1ad2f). 2. interchaintest-E2E.yml matrix listed 16 ictest targets, several of which (statesync, ibchooks, feeshare, unity-deploy, feepay, cwhooks, clock, gov-fix) no longer exist as Makefile targets or test directories — likely stale across the e2e rework in a0ae429 + c6de955. Replace with the 13 targets that actually exist on disk and in the Makefile. Pre-bump cleanup before the dependency jumps in Phase 1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pivot from locked Path B (SDK v0.54 + ibc-go v11) to **Path A+** because ibc-apps repo has no /v11 line published — Juno wires PFM, ibc-hooks, and async-icq into app/keepers/keepers.go and app/modules.go and would either lose all three or require forking ibc-apps. Path A+ keeps the v30 consensus-break payoff (wasmvm v3 → BN254 precompile) while staying on a fully-supported stack. SDK v0.54 + IBCv2 + store/v2 are deferred to v31 once ibc-apps catches up. See planning/02-targets.md for full rationale. Direct dep changes: - cosmos-sdk v0.53.4 → v0.53.7 - wasmd v0.54.2 → v0.61.11 - wasmvm/v2 v2.2.4 → wasmvm/v3 v3.0.4 (path change) - ibc-go/v8 v8.7.0 → ibc-go/v10 v10.6.0 (path change) - ibc-apps PFM /v8 v8.2.0 → /v10 v10.6.0 - ibc-apps ibc-hooks /v8 v8.0.0 → /v10 v10.0.0 - ibc-apps async-icq /v8 — no /v9/v10/v11 published, kept on /v8 latest commit - cometbft v0.38.19 → v0.38.23 - cosmossdk.io/* family unchanged (v0.53.7 SDK keeps current pins) ICS-29 (29-fee) middleware was removed in ibc-go v10 — accordingly: - Dropped ibcfee imports + IBCFeeKeeper field from app/keepers/keepers.go - Dropped ibcfee module wiring from app/modules.go (BeginBlocker / EndBlocker / InitChainer ordering, AppModuleBasic, NewAppModule) - Dropped ibcfeetypes.StoreKey from app/keepers/keys.go and the ibcfeetypes.ModuleName account-permission entry - Added "feeibc" to v30 StoreUpgrades.Deleted in app/upgrades/v30/ constants.go so existing mainnet state under that key is purged during the upgrade - Adapted wasm.NewIBCHandler call to wasmd v0.61's new 4-arg signature (ICS20TransferPortSource + appVersionGetter) Phase 1 commits the version pins and import-path migration. The bulk of API drift (ibckeeper.NewKeeper / ibctransferkeeper.NewKeeper / icqkeeper.NewKeeper / icahostkeeper.NewKeeper signature changes, KVStoreKey → KVStoreService migration, ParamSubspace removal) is Phase 2 work and surfaces as compile errors against this commit. `go mod tidy` is clean. planning/02-targets.md and planning/03-migration-plan.md updated to record the pivot rationale. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
go build ./... now succeeds; junod binary builds and reports "Cosmos SDK: v0.53.7, Comet: v0.38.23". Most unit tests pass. Keeper signature changes (ibc-go v10 + wasmd v0.61 dropped capability scoping; KVStoreKey → KVStoreService; many ParamSubspaces → nil): - ibckeeper.NewKeeper: 7 → 5 args (drops StakingKeeper + ScopedIBCKeeper) - ibctransferkeeper.NewKeeper: drops PortKeeper + ScopedKeeper, adds MsgServiceRouter - icahostkeeper.NewKeeper: drops PortKeeper + ScopedKeeper + standalone WithQueryRouter setter; QueryRouter now passed to NewKeeper - icacontrollerkeeper.NewKeeper: drops PortKeeper + ScopedKeeper + IBCFeeKeeper (29-fee removed); ChannelKeeper takes the ICS4Wrapper slot - packetforwardkeeper.NewKeeper: KVStoreKey → KVStoreService - wasmkeeper.NewKeeper (wasmd v0.61): drops IBCFeeKeeper + PortKeeper + ScopedWasmKeeper; adds ChannelKeeperV2 Tendermint light client wiring (ibc-go v10 split client modules out of the keeper into explicit registrations): - New AppKeepers.TmLightClientModule field - ibctm.NewLightClientModule constructed after IBCKeeper, registered via clientKeeper.AddRoute(ibctm.ModuleName, ...) - modules.go ibctm.NewAppModule now takes the LightClientModule ICA controller IBC middleware: NewIBCMiddleware now takes 1 arg (just the keeper), not 2 — auth-module stack arg dropped along with capability scoping. async-icq dropped from v30: - ibc-apps maintainers haven't published a /v9/v10/v11 line; even the latest /v8 commit (2026-04-27) still pins ibc-go/v8, incompatible with our ibc-go/v10 - Removed all icq imports + ICQKeeper field + ScopedICQKeeper + module wiring from app/keepers/keepers.go, app/modules.go, app/keepers/keys.go - Added "interchainquery" to v30 StoreUpgrades.Deleted to purge existing mainnet state under that store key during the upgrade - Reintroduce in v30.x or v31 once ibc-apps publishes a /v10 line Other v10 surface changes: - ibctransfertypes.QueryDenomTraceResponse → QueryDenomResponse (transfer module renamed DenomTrace → Denom in v10); acceptedQueries.go updated for new proto path Phase 3 surface (3 module test failures remaining, all real: - x/cw-hooks/keeper TestUnRegisterContracts: error semantics drift - x/feemarket/ante TestEscrowFunds/valid_fee_grant - x/stream/keeper TestExecuteQueryBalance Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
go test ./... -p 1 (sequential) now passes clean across all packages. x/cw-hooks/keeper/msg_server_test.go (TestUnRegisterContracts): Pre-existing test bug from "cw hooks v2" (commit 7dbbf11). The unregisterCase struct lacked the senderAddr/contractAddr callback fields that registerCase has, so the "invalid register address" and "unauthorized sender" cases couldn't actually exercise the failure they claimed — they used the same valid sender/contract as the success case. Added the missing callback fields and parameterized the failing cases to actually pass an invalid sender / unauthorized sender address respectively. x/stream/keeper/keeper_test.go (TestExecuteQueryBalance): The stream keeper's gRPC method registry calls Refresh during NewKeeper, but bank module's HybridHandlerByRequestName mappings aren't fully registered until module manager init runs (after keeper construction). The registry filter at registry.go:231 silently drops any method whose request name has no hybrid handler, so the registry was empty for "bank.balance" lookups. Added a manual Refresh after Commit() in the test so the registry sees the fully-wired router. (Underlying keeper bug: Refresh should run from a finalize-block hook or be lazy on first lookup. Out of scope for the v30 dep bump; tracked separately.) Note on test parallelism: `go test ./...` (parallel) shows intermittent feemarket ante failures due to cross-package global state leakage (proto registry / init() side effects — common cosmos-sdk pattern). `-p 1` (sequential) is clean. CI may need to switch to sequential or isolate the affected package; tracked for Phase 6. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`make lint` now reports 0 issues. wasmbindings tests already pass (implicit Phase 4 audit — the typed query/message bindings compile cleanly against wasmd v0.61). Stargate allow-list (acceptedQueries.go) was updated in Phase 1 (DenomTrace → Denom). Lint fixes: - Auto-fix (gci import order + 3 S1021 var-decl-merge in keepers.go) - x/cw-hooks msg_server_test.go: extract repeated "Invalid" string to invalidAddr constant (goconst) - x/mint minter_test.go + x/tokenfactory createdenom_test.go: replace deprecated Coin.IsEqual with Coin.Equal (SA1019). Tokenfactory case required binding the Sub() return to a local var since Equal is a pointer method. - app/modules.go appModules() return type: nolint:staticcheck for module.AppModule deprecation. Cosmos-sdk itself + most ecosystem modules haven't migrated to appmodule.AppModule yet (deprecated in v0.53.7 but not removed); deferred to v31. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per planning/05-staking-snapshot.md (Option C, Shape 1, event-driven):
chain-side bindings for historical staking-power queries. DAO DAO
voting modules become thin query consumers; voting power at proposal-
open height is fixed at-or-before semantics — voters can't rage-stake
mid-window.
Module layout (no proto/gRPC for v30 MVP — wasmbindings is the
delivery surface; gRPC/REST tracked for v30.x):
- types/keys.go — ModuleName, StoreKey, collection prefixes
- types/params.go — Params{LSTAllowlist} (default empty)
- types/params_codec.go — JSON-backed collections.ValueCodec
- types/genesis.go — GenesisState (JSON marshalled)
- types/expected_keepers.go — minimal StakingKeeper interface
- keeper/keeper.go — collections.Map keyed by (delegator,height)
and (height); IsLST helper
- keeper/snapshot.go — recordDelegatorPower, recordTotal,
VotingPowerAt, TotalVotingPowerAt
- keeper/hooks.go — staking hook impls (AfterDelegationModified,
BeforeDelegationRemoved, BeforeValidatorSlashed)
plus stub no-ops for the rest of the interface
- keeper/genesis.go — InitGenesis / ExportGenesis
- keeper/backfill.go — BackfillFromStaking for v30 upgrade-handler
seeding (one-shot iteration over all delegations)
- keeper/keeper_test.go — params round-trip, at-or-before semantics,
LST exclusion
- module/module.go — minimal AppModule (no msg/query servers)
App wiring:
- app/keepers/keepers.go — VotingSnapshotKeeper field + construction
after staking keeper, before SetHooks;
Hooks() registered alongside DistrKeeper
and CWHooksKeeper
- app/keepers/keys.go — votingsnapshottypes.StoreKey added
- app/modules.go — wired into appModules + all 3 ordering
lists + AppModuleBasics
- app/upgrades/v30/ — votingsnapshottypes.ModuleName added to
StoreUpgrades.Added; upgrade handler
calls VotingSnapshotKeeper.BackfillFromStaking
Wasm bindings (the actual delivery surface for DAO DAO):
- wasmbindings/types/query.go: VotingPowerAt + TotalVotingPowerAt
query variants + VotingPowerResponse
- wasmbindings/queries.go: GetVotingPowerAt, GetTotalVotingPowerAt
- wasmbindings/query_plugin.go: dispatch new variants
- wasmbindings/wasm.go: NewQueryPlugin takes votingsnapshotkeeper
LST exclusion semantics: governance maintains an allowlist of LST
contract addresses. delegations *from* a listed address don't count
toward voting power (the wrapper holds the stake, not the underlying
voter). List is empty at v30 launch — no live LSTs on Juno per
memory/juno-voting-design.md. Default-deny: an unlisted contract
counts until governance excludes it.
IsLST tolerates missing params (returns false) so staking hooks
fired during genesis InitGenesis don't error before the module's
own InitGenesis sets params.
Pre-upgrade query semantics: VotingPowerAt(addr, height) returns
0 (not error) for heights with no recorded snapshot. After Phase 7
backfill, all heights >= upgrade-height return real data.
Deferred to v30.x:
- proto/gRPC msg+query servers (delivery is wasmbindings only for v30)
- VotingPowerOverRange (time-decay schemes)
- Per-validator filtering for sub-DAO use cases
- Precise per-validator delegator re-snapshot on slash
- LST-aware denominator subtraction in TotalVotingPowerAt
- Governance MsgUpdateParams (params are genesis-only for v30)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All cheap checks pass on the implementation host: - go build ./... (root + interchaintest) - make build (junod binary, SDK v0.53.7 + Comet v0.38.23) - make lint (0 issues) - go test ./... -p 1 (sequential, all packages) - go mod tidy (clean diff in both modules) Interchaintest local execution is blocked: docker 29.4.3 in this host lacks the buildx plugin, and the project Dockerfile uses BuildKit syntax (--mount=type=cache + # syntax=docker/dockerfile:1). Plain docker build and DOCKER_BUILDKIT=1 docker build both fail. CI runners ship with buildx so ictest validation runs there. Documented two latent issues for v30.x follow-up: - go test ./... (parallel) shows flake in x/feemarket/ante from cross-package global state — sequential is clean - x/stream gRPC method registry refreshes during keeper construction (before module init); test fix patched, runtime bug deferred Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Self-review against planning/08-security-review.md checklist. Findings: 0 Sev-1, 0 Sev-2, 2 Sev-3 (both deferred to v30.x with reasons), 7 Info notes. External review still required for the upgrade handler and the new x/voting-snapshot module. Sev-3 #1: wasmbindings VotingPowerAt has no fixed-cost gas wrapper; relies on SDK ambient store gas. Adequate for v30, revisit with empirical data in v30.x. Sev-3 #2: x/voting-snapshot has unbounded snapshot retention. A prune param tied to governance voting period is the right mitigation; deferred until that param is exposed. Open questions surfaced for external reviewer: snapshot retention default, pre-upgrade query semantics (zero vs typed error), gas charging, async-icq counterparty impact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three buckets: Bucket A (PR-blockers, must close before v30 mainnet halt-height): - A1: DAO DAO v2.7.0 contract compatibility on wasmvm v3 — new ictest suite + mainnet-snapshot replay + wasmbindings smoke. Crosses repo boundary into dao-contracts for the binding adapter. - A2: ictest-upgrade run against forked mainnet state (CI gate) - A3: async-icq counterparty audit before deleting the store - A4: cometbft v0.38.19 → v0.38.23 changelog scan Bucket B (v30.x patches, not blocking): - B1: voting-snapshot retention pruning (Sev-3 from sec review) - B2: voting-snapshot fixed-cost gas wrapper (Sev-3 from sec review) - B3: voting-snapshot proto/gRPC/REST surface - B4: per-validator delegator re-snapshot on slash - B5: VotingPowerOverRange + per-validator filter - B6: x/stream MethodRegistry refresh timing - B7: test parallelism flake root-cause fix - B8: CI -p 1 workaround until B7 lands - B9: bank DenomMetadata proto-path bug (pre-existing) - B10: async-icq reintroduction once ibc-apps publishes /v10 Bucket C (v31, next consensus break): - C1: SDK v0.54 + IBCv2 + store/v2 (the original Path B target) - C2: cometbft v0.39 with optional libp2p - C3: module.AppModule → appmodule.AppModule migration JunoClaw contract compatibility intentionally excluded per Jake's direction — that team owns their own contract test surface. An empty Bucket A is the gate for v30 mainnet halt-height proposal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cker/setup_junod.sh) # Conflicts: # docker/setup_junod.sh
Three small CI-related fixes bundled: - B9: app/keepers/acceptedQueries.go — /cosmos.bank.v1beta1.Query/DenomMetadata was returning QueryDenomsMetadataResponse (plural). Pre-existing typo; surfaced during deferred-work review. Switched to QueryDenomMetadataResponse. - B8: .github/workflows/build.yml — switched test job to `go test -p 1` (sequential) until B7 is rooted out. TestEscrowFunds in x/feemarket/ante is flaky under parallel package execution from cross-package global state. - CI lint job: golangci-lint-action@v8 with version v2.1.6 fails to read go.mod targeting go 1.25 because the action's pre-built binary was compiled with go1.24. Switched to `make lint` which invokes `go tool golangci-lint` (compiled from go.mod's pinned v2.5.0 against the workflow's setup-go 1.25 toolchain). Matches CLAUDE.md's stated convention for local lint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two Bucket A blockers from planning/09-deferred-work.md closed. A3 (async-icq counterparty audit on juno-1): Verdict clear. Queried Polkachu LCD for all 674 active IBC channels; zero have port_id matching icq* (icqhost / icqcontroller / *icq*). Deleting the interchainquery store at v30 upgrade height carries no counterparty risk. Re-verification command captured for pre-halt sanity check. A4 (CometBFT v0.38.19 → v0.38.23 changelog scan): Four patch releases between the v29 binary's pin and v30's. All changes are bug fixes (vote handling, evidence validation, blocksync verification, ABCI socket panic recovery) plus one opt-in state-sync parameter. No [CONSENSUS] or [API-BREAKING] entries that affect validators in steady state. Experimental libp2p transport added on the line is off by default and we don't enable it. Validator-facing language for the upgrade proposal text drafted at the bottom of the file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NewKeeper called methodRegistry.Refresh() eagerly, but module-manager init hadn't registered the gRPC services yet — bank's hybrid handlers weren't in the router, so the registry indexed against an empty HybridHandlerByRequestName map. Lookups for "bank.balance" then returned `false` until something else (any later subscription update) re-triggered Refresh. Fix: lazy refresh on first MethodRegistry() access, guarded by sync.Once. By the time anyone calls MethodRegistry() — which doesn't happen until at least the first BeginBlock — every module's RegisterServices has run, so the router is fully populated. Reverted the test workaround that explicitly called keeper.MethodRegistry().Refresh(s.App.BaseApp) after Commit; now that MethodRegistry itself triggers Refresh on first call, the test passes without manual coordination. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four planning/09 Bucket-B items folded into one cohesive uplift. B1: Snapshot retention pruning - Added Params.RetentionWindowHeights (default 12_614_400 ≈ 1y at 2.5s) - New keeper.Prune() walks VotingPower + TotalPower and drops rows with height < (current - window). Zero window disables pruning. - New EndBlocker on AppModule that invokes Prune. Errors are silenced (logging hook to be added when keeper grows a Logger()); matches cosmos-sdk pattern for non-critical EndBlocker work. - pruneInterval constant set to 1 (every block); reserved knob if iteration cost ever becomes meaningful. B2: Wasmbinding gas wrapper - Fixed-cost gas charge of 5_000 added at the top of GetVotingPowerAt / GetTotalVotingPowerAt / GetVotingPowerOverRange in wasmbindings/queries.go. Bounds the worst-case empty-iterator scan a contract could induce by querying high heights for delegators with no recorded snapshot. B4: Per-validator delegator re-snapshot on slash - Hooks.BeforeValidatorSlashed now iterates the validator's delegations via stakingKeeper.GetValidatorDelegations and re-records each delegator's power, then re-records the chain-wide total. - Bounded by the slashed validator's delegator count (typically dozens to low thousands; well within block-gas budget). - StakingKeeper interface in expected_keepers.go gained GetValidatorDelegations. B5a: VotingPowerOverRange (the range portion of B5) - New keeper.VotingPowerOverRange(addr, fromHeight, toHeight) returning []HeightPower for every snapshot inside [from, to]. - Wasmbinding variant added: VotingPowerOverRange query + HeightPowerPair / VotingPowerOverRangeResponse types. - Per-validator filter (B5b) deferred — would require shifting the storage shape from (delegator,height) to (delegator,validator, height); no immediate consumer in DAO DAO v2.7.0. Tests added: - TestVotingPowerOverRange — verifies range bounds + ordering - TestPruneRetentionWindow — verifies cutoff math, survives within window, disables pruning when window=0 Other: - New errors.go with isNotFound() helper to dedupe the collections.ErrNotFound check across keeper.go and prune.go. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`make local-image` was succeeding the build step but failing the "statically linked" assertion at the end of the Dockerfile. Root cause: the wasmvm fetch line read `go list -m github.com/CosmWasm/wasmvm/v2`, which still resolves (v2.2.4 is a transitive dep) but the v2 muslc archive is ABI-incompatible with the v3 Go bindings the build now targets. Without a satisfied muslc tag, the linker fell back to dynamic linking against system libwasmvm — so the binary was correct but failed the statically-linked check. Fixed by reading from /v3 instead. With buildx 0.20.0 installed in the impl host (separately), `make local-image` now produces a properly-static binary in ghcr.io/cosmoscontracts/juno:local (396MB), which interchaintest can use. Unblocks A1 (DAO DAO ictest) and A2 (ictest-upgrade) for local verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
interchaintest's ic.Build hits the 2-minute context deadline on the v30 image even though the chain itself is healthy (committing blocks at height 49+). The new keeper set (x/voting-snapshot, ibc-go v10 + wasmd v0.61 re-wiring) makes the chain take longer to reach the readiness criteria interchaintest checks. 5 minutes leaves headroom without making genuine bring-up failures slow to surface. Observed v29 runs typically finished Build in 60-90s; v30 needs more. Unblocks A1 (DAO DAO ictest scaffold) and A2 (ictest-upgrade). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
Captures the real failure mode for ictest-basic: chain RPC port isn't reachable from the host even though the chain runs (commits blocks at height 49+). interchaintest dials Post "http://0.0.0.0:<host-port>" and gets connection refused. The 5-min timeout bump didn't help — failure still happens at ~144s (not the timeout boundary). Real cause is one of: - H1: Docker port-forward race (chain starts faster than poll begins) - H2: cometbft RPC server doesn't start (chain runs but RPC doesn't bind) - H3: cgo / keeper init delays RPC binding past polling window - H4: probe-shape mismatch (less likely, interchaintest is generic) Next-session diagnostic checklist + ruled-out hypotheses captured in planning/ICTEST-BLOCKER-V30.md. Tracks live so the next debugger can attach docker logs / docker port / ss -tlnp during a live run. Cleanup panic in tests/basic/chain_start_test.go:25 (s.Ic.Close() on nil Ic) is real but not fixed here — defensive-coding follow-up. Blocks Bucket A items A1 (DAO DAO contract compat) and A2 (ictest-upgrade). DAO DAO v2.7.0 wasm artifacts ARE ready at /workspace/dao-contracts/artifacts/ (29 files); ictest scaffold can be drafted before this lands but can't run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ran the diagnostic checklist against a live ictest container. Updated the doc with concrete findings: Root cause: chain container is on the interchaintest-<id> docker network (172.217.0.0/16); SafeClaude (where ictest runs in our setup) is on bridge (172.17.0.0/16). Different networks isolated by default. interchaintest's chain readiness check polls tcp://0.0.0.0:<host-port> via tn.hostRPCPort, which lives on the OUTER host's network namespace and isn't reachable from inside SafeClaude. Confirmed via direct probing: - :::26657 is LISTEN inside the chain container - docker port mapping IS established (26657 -> host:dynamic) - curl 0.0.0.0:<host-port> from SafeClaude returns connection refused - curl <container-ip>:26657 from SafeClaude times out (different netns) Fix path documented: 1. Fork interchaintest with a one-line patch to use tn.HostName():26657 when INTERCHAINTEST_USE_CONTAINER_HOSTNAME=1 is set; replace in interchaintest/go.mod 2. Test-setup hook in interchaintest/suite/lib.go to docker network connect SafeClaude into the per-run interchaintest network 3. Or alternative: socat forwarder inside SafeClaude mirroring the dynamic host-port mappings Workaround for v30 timeline: run ictest from outside SafeClaude (CI does this naturally) or skip ictest from PR review surface and validate via a v30-rc1 testnet smoke instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ran the diagnostic, then a Python TCP forwarder + docker network connect attempt. Result: SafeClaude's network namespace is sandboxed in a way that prevents joining additional docker networks at runtime. The interface listed by `docker inspect` is registered with the daemon but never materializes inside the container — /proc/net/dev shows only eth0, route table has only default-bridge routes. The forwarder advanced the dial from TCP-level "connection refused" to HTTP-level "context deadline exceeded" / "connection reset by peer" — but that's because the upstream dial from SafeClaude to the chain's container IP can't actually deliver across the sandboxed namespace. ictest as currently architected requires the test runner and the chain containers to share a network reachability path. That path doesn't exist from inside SafeClaude, period. CI runners on the outer host don't have this problem. Conclusion: validate A1 (DAO DAO contract compat) and A2 (upgrade-handler) via testnet smoke instead of local ictest before the security review and mainnet halt-height proposal. The ictest workflow matrix (reconciled in Phase 0c) is still the right place for this in CI. Tooling left in place for any future runner that doesn't have this sandbox: 5-min Build timeout + Build error logging in interchaintest/suite/lib.go; bridge scripts at /tmp/forward.py and /tmp/ictest-bridge.sh are reference for the docker-in-docker pattern (they work for TCP but the upstream dial still fails under sandbox). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
interchaintest/tests/dao-dao/dao_dao_test.go scaffolds the three
legs from planning/09-deferred-work.md §A1:
1. TestCw4GroupDao — minimal cw4-group voting + proposal-single
(open + vote + execute a no-op proposal). The cheapest leg
proves dao-dao-core, dao-voting-cw4, dao-proposal-single, and
cw4-group all instantiate and interact under wasmvm v3.
2. TestCw20StakedDao — cw20-staked voting path (skipped, TODO
for v30.x once leg 1 passes; same scaffold + token + stake
contract setup).
3. TestWasmbindingsVotingPowerAt — smoke for the new
x/voting-snapshot wasmbinding (skipped, needs a small Rust
probe contract embedded as testdata).
Pre-built v2.7.0 wasm artifacts staged at
interchaintest/contracts/{dao_dao_core,dao_proposal_single,
dao_voting_cw4,dao_voting_cw20_staked,cw20_stake}.wasm — copied
from the existing /workspace/dao-contracts/artifacts/ build output.
cw4_group.wasm and cw20_base.wasm aren't in that artifact set;
they come from cw-plus and need a separate fetch (TODO before
TestCw4GroupDao can run).
Helpers in the file (buildDaoInstantiate, queryVotingModule,
queryProposalModule, openProposal, voteOnProposal, executeProposal,
queryProposalStatus) are stubs that t.Skip() — fleshed out in the
next focused session against real schemas from
dao-contracts/packages/dao-interface.
Wired:
- Makefile: new ictest-dao-dao target + .PHONY entry
- .github/workflows/interchaintest-E2E.yml: ictest-dao-dao added
to the matrix (so CI runs it automatically once the helpers are
filled in)
Note: per planning/ICTEST-BLOCKER-V30.md, ictest can't run from
inside SafeClaude due to network-namespace sandboxing. This
scaffold is targeted at CI runs and at testnet-smoke validation
before mainnet halt-height — not local execution.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root-caused the feemarket ante TestEscrowFunds parallel/-count flake.
Wasn't cross-package global state at all — the test cases were
declared as map[string]struct{} which iterates in non-deterministic
order. Several cases mutate FeeGrantKeeper state (GrantAllowance);
"no fee grant" sometimes ran AFTER "valid fee grant" leaving an
allowance the former didn't expect, producing
"fee allowance already exists" or "fee limit exceeded" instead of
ErrNotFound.
Fix:
- Convert cases from map to []tcDef (deterministic order)
- Call s.SetupTest() at the top of each subtest's s.Run so each
case starts from a clean suite
Verified:
- go test -count=3 ./x/feemarket/ante/... → pass
- go test -count=2 ./... -timeout 900s → all packages pass under
parallel default
Reverted the B8 workaround — .github/workflows/build.yml is back to
`go test ./...` (parallel) since the underlying bug is fixed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The wasmbinding API delivers what DAO DAO needs for v30, but
indexers, CLI consumers, and REST gateway clients want gRPC.
Adds the proto-generated msg + query servers per
planning/09-deferred-work.md §B3.
New proto files (proto/juno/votingsnapshot/v1/):
- params.proto — Params message (LSTAllowlist, RetentionWindowHeights)
- genesis.proto — GenesisState
- query.proto — Query.{Params,VotingPowerAt,TotalVotingPowerAt,
VotingPowerOverRange} with HTTP gateway routes
- tx.proto — Msg.UpdateParams (governance-controlled)
Generated:
- x/voting-snapshot/types/{params,genesis,query,tx}.pb.go
- x/voting-snapshot/types/query.pb.gw.go (REST gateway)
Removed conflicting hand-written types now provided by proto:
- types/params.go (Params struct + Marshal/Unmarshal stubs)
- types/genesis.go (GenesisState struct + Marshal/Unmarshal stubs)
- types/params_codec.go (JSON-backed ValueCodec)
Renamed LSTAllowlist → LstAllowlist across keeper code + tests to
match proto-generated camelCase. Behavior unchanged.
Switched the keeper.Params Item to codec.CollValue[types.Params]
(gogo proto codec) instead of the hand-written JSON codec.
New files:
- types/codec.go — RegisterInterfaces for sdk.Msg implementations
- types/helpers.go — DefaultParams + DefaultGenesis (helpers the
proto generator doesn't produce)
- keeper/grpc_query.go — types.QueryServer impl wrapping the keeper
- keeper/msg_server.go — types.MsgServer impl with Authority gating
on UpdateParams
Module wiring (x/voting-snapshot/module/module.go):
- RegisterInterfaces wired through types.RegisterInterfaces
- RegisterGRPCGatewayRoutes wired through types.RegisterQueryHandlerClient
- RegisterServices wires both NewMsgServer and NewQueryServer
Tests + lint clean. The wasmbinding surface (the v30 MVP delivery)
still works exactly as before — proto/gRPC are an additive surface.
Required outer-host docker invocation (SafeClaude can't bind-mount
$(CURDIR) directly because the daemon is on the host). Doesn't
affect CI which runs the standard `make proto-gen` from a path
the daemon recognizes natively.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-walks planning/08-security-review.md against the Bucket A+B deltas (commits fd6e08c..e273bc1). Focused on what changed after the first review. Cumulative findings: - Sev-1: 0 - Sev-2: 0 - Sev-3: 1 (down from 2 — both v1 Sev-3s closed by B1 retention prune and B2 gas wrapper; one new Sev-3 found in B1's full-store pruneVotingPower scan, deferred to v30.x performance work) - Info: 13 (6 new) New review surface covered: - B1 retention pruning EndBlocker - B2 wasmbinding gas wrappers - B3 proto/gRPC/REST surface (Query + Msg servers, Authority gating) - B4 per-validator slash re-snapshot hook - B5a VotingPowerOverRange + wasmbinding - B6 stream lazy MethodRegistry refresh under sync.Once - B7 feegrant test parallelism fix (test-only, no security impact) - B9 stargate DenomMetadata typo fix - CI infrastructure (golangci-lint action → make lint, B8 revert) - A1 DAO DAO ictest scaffold (test-only, not load-bearing) External review still required for: - app/upgrades/v30/upgrades.go (now includes BackfillFromStaking state writes) - x/voting-snapshot/ in full (all new code paths) - app/keepers/keepers.go ICA + wasm IBC handler wiring (unchanged since v1 review but worth external eyes on the ICS-29 removal) Open questions surfaced: - snapshot retention default (~1y at 2.5s) — right knob? - per-validator slash hook cost — benchmark on a juno-1 fork - full-store pruneVotingPower scan — proper per-delegator iteration is v30.x design work Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`build-docker` was tagging the chain image as `juno:local` but interchaintest/suite/ci_integration.go's GetDockerImageInfo defaults to `ghcr.io/cosmoscontracts/juno:local` (matching the IMAGE var in the root Makefile's local-image target). When the e2e jobs load the artifact and run a test, the test asks for `ghcr.io/cosmoscontracts/juno:local`, doesn't find it, falls back to a registry pull, and the daemon returns "manifest unknown." 13 of 14 e2e jobs failed on this. Why ictest-upgrade was the only one passing: it tests upgrade from a published v29 image to the new binary, so the upgrade-from path uses a registry image and doesn't need `:local` to be present locally. Fix: tag in the workflow with the full registry path so it matches the repo string GetDockerImageInfo returns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ision The CI ictest run after the image-tag fix surfaced the next issue: junod panics at startup with "voting-power-at flag redefined: height". Root cause: cosmossdk.io/client/v2/autocli generates a CLI flag from each proto field on a query method. QueryVotingPowerAtRequest and QueryTotalVotingPowerAtRequest both have an int64 `height` field, so autocli generates `--height`. Then cosmos-sdk's standard AddQueryFlagsToCmd tries to add its own `--height` (the "query state at this chain height" flag) and pflag panics on the duplicate. Fix: rename the proto field height → at_height. The new name is also more semantically accurate (this is the snapshot height we're looking up, not the query's read-state height) and matches naming patterns elsewhere in the SDK. - proto/juno/votingsnapshot/v1/query.proto: height → at_height in QueryVotingPowerAtRequest and QueryTotalVotingPowerAtRequest + matching path-binding updates in google.api.http annotations - regenerated query.pb.go / query.pb.gw.go via proto-gogo - x/voting-snapshot/keeper/grpc_query.go: req.Height → req.AtHeight The wasmbinding JSON contract (wasmbindings/types/query.go) keeps its `height` field — it's not run through autocli, separate serialization path, no collision. Verified locally: junod boots cleanly. The CI matrix now has a real shot at running the chain instead of panicking at startup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Brings the
v30upgrade branch (originallyvexxvakan/v30, 82 commits ahead of main) up to a green, lint-clean, test-passing state on a coherent Path A+ dependency stack: SDK v0.53.7, wasmd v0.61.11, wasmvm v3.0.4, ibc-go v10.6.0, cometbft v0.38.23. Plus the newx/voting-snapshotmodule called for inplanning/05-staking-snapshot.md.This is a fork-internal PR opened against
juno-ai-dev/juno:mainfor review surface preview before going upstream.The 9 commits at the tip of this branch (everything after
6b256cde fix up broken tokenfactory tests— the priorvexxvakan/v30HEAD) represent the dep-bump implementation:543d7753—planning/+CLAUDE.md(research artifacts)9a86793c— Phase 0: CI Go version 1.23.9 → 1.25.2 in 4 workflows; ictest matrix reconciled (16 stale entries → 13 real Makefile targets)63e10bce— Phase 1: dependency bumps (Path A+) + ICS-29 (29-fee) middleware removalcb664289— Phase 2: app wiring for ibc-go v10 + wasmd v0.61 (KVStoreKey → KVStoreService, capability scoping dropped, IBC keeper signature changes); async-icq dropped (no/v10line published); tendermint LightClientModule explicit registrationce42d97b— Phase 3: pre-existing test-bug fixes inx/cw-hooksandx/streamaf7e8076— Phase 4: lint clean (0 issues)8efab6bc— Phase 5: newx/voting-snapshotmodule (MVP) — keeper, staking hooks, JSON-encoded params, wasmbindings (VotingPowerAt/TotalVotingPowerAt), upgrade-handlerBackfillFromStakingd692dd41— Phase 6: local verification status + CI deferral note (buildx not available on impl host)fd6e08c3— Phase 7: internal security review pass (0 Sev-1, 0 Sev-2, 2 Sev-3 deferred to v30.x with reasons)Why Path A+ instead of the originally-targeted Path B
The session-1 plan targeted SDK v0.54 + wasmd v0.70 + ibc-go v11 + store/v2.
ibc-appsrepo has no/v11line published (latest is/v10, which pins ibc-go v10 + SDK v0.53). Juno wires PFM, ibc-hooks, async-icq intoapp/keepers/keepers.go+app/modules.go; pure Path B would either lose all three or require forking ibc-apps.Path A+ preserves the v30 user-facing payoff — wasmvm v3.0.4 brings the BN254 precompile that prop CosmosContracts#374 already approved — while staying on a fully-supported stack. SDK v0.54 + IBCv2 + store/v2 are deferred to v31 once ibc-apps catches up. Full rationale in
planning/02-targets.md.What's removed
Two modules were dropped from v30 because their stores no longer exist or are unreachable on the new stack:
feeibc) — removed in ibc-go v10 upstreaminterchainquery) —ibc-apps/v9//v10//v11not published; latest/v8commit (April 2026) still pins ibc-go v8Both store keys are added to
StoreUpgrades.Deletedin the v30 upgrade handler so existing mainnet state under those keys is purged at upgrade height. Both are tracked for reintroduction once upstream catches up.What's new
x/voting-snapshot— chain-side bindings for historical staking-power queries, perplanning/05-staking-snapshot.md. DAO DAO voting modules become thin query consumers; voting power at proposal-open height is fixed by at-or-before semantics so voters can't rage-stake mid-window. LST allow-list excluded from voting power (default-deny; empty at v30 launch). Wasmbindings are the v30 MVP delivery surface — proto/gRPC msg/query servers deferred to v30.x.Internal security review
planning/SECURITY-REVIEW-NOTES.mdwalks theplanning/08checklist. Findings: 0 Sev-1, 0 Sev-2, 2 Sev-3 (gas-charging on new wasmbindings + unbounded snapshot retention — both deferred to v30.x with logged reasons), 7 Info notes. External review still required for the upgrade handler and the new module per the planning doc; this is the kickoff document.Test plan
Verified clean on the implementation host:
go build ./...(root + interchaintest)make build—junodreportsCosmos SDK: v0.53.7, Comet: v0.38.23make lint— 0 issuesgo test ./... -p 1(sequential) — all packages passgo mod tidy— no diff in either modulewasmbindings/test+ newx/voting-snapshot/keepertests passDeferred to CI / external review:
make ictest-*suites —buildxunavailable on the impl host; CI runs them via the reconciled matrixictest-upgrade— the highest-signal test in the repo; verifies the upgrade handler against forked mainnet statedao-contractsrepo)app/upgrades/v30/upgrades.go+x/voting-snapshot/🤖 Generated with Claude Code