Skip to content

Cosmos EVM Integration#108

Open
akobrin1 wants to merge 89 commits intomasterfrom
evm
Open

Cosmos EVM Integration#108
akobrin1 wants to merge 89 commits intomasterfrom
evm

Conversation

@akobrin1
Copy link
Copy Markdown
Contributor

@akobrin1 akobrin1 commented Mar 19, 2026

Cosmos EVM Integration

Summary

First-class Cosmos EVM integration for Lumera, adding full Ethereum transaction execution, JSON-RPC, EIP-1559 fee market, ERC20/IBC middleware, custom module precompiles, the industry's first bidirectional CosmWasm↔EVM cross-runtime bridge, and a purpose-built legacy account migration module — the most comprehensive pre-mainnet EVM integration in the Cosmos ecosystem.

Ships with ~470 EVM-related tests, 25 bugs found and fixed during integration, production-grade operational controls, and features no other Cosmos EVM chain offers: async broadcast queue (deadlock fix), bidirectional CosmWasm↔EVM contract interaction (EVM contracts can execute/query CosmWasm contracts and vice versa), OpenRPC discovery, governance-controlled IBC ERC20 policy with provenance-bound trace verification, and a full account migration module with dual-signature verification.

Full technical documentation: docs/evm-integration/main.md


New Modules

Module Purpose
x/vm (Cosmos EVM v0.6.0) Core EVM execution — Solidity/Vyper contract runtime, receipts, logs, state transitions
x/feemarket EIP-1559 dynamic base fee with min gas price floor (0.0005 ulume/gas) and ~6.25% per-block adjustment
x/precisebank 6-decimal ulume ↔ 18-decimal alume bridge: EVMBalance(a) = I(a) × 10¹² + F(a)
x/erc20 STRv2 token pair registration + IBC middleware for automatic cross-chain ERC20 mapping
x/evmigration Legacy coin-type-118 → 60 account migration with dual-signature verification and atomic 9-module state re-keying

Key Changes

Runtime & Execution

  • Dual-route ante handler (app/evm/ante.go): deterministic tx routing based on ExtensionOptions[0].TypeUrl — Ethereum extension txs route to EVM path (EVMMonoDecorator + pending tx listener), Cosmos txs route through Lumera + EVM-aware Cosmos decorator chain, with build-tag-guarded production isolation (//go:build !test in defaults_prod.go)
  • App-side EVM mempool (app/evm_mempool.go): Ethereum-like sender ordering, nonce-gap handling, same-nonce replacement with bump rules, max_txs=5000 enabled by default
  • Async broadcast queue (app/evm_broadcast.go): decouples txpool runReorg promotion from CometBFT CheckTx via bounded channel + single background worker, preventing mutex re-entry deadlock — a novel fix no other Cosmos EVM chain has publicly addressed
  • RegisterTxService override (app/evm_runtime.go): captures the local CometBFT client for the broadcast worker, replacing the stale HTTP client from pre-CometBFT SetClientCtx
  • Depinject signer wiring: custom GetSigners for MsgEthereumTx + safe early-RPC keeper coin info initialization (SetKeeperDefaults) to prevent panics before genesis
  • CosmWasm coexistence: wasmd v0.61.6 and EVM run in the same runtime — Lumera is the only Cosmos chain shipping both simultaneously

Chain Configuration

  • EVM chain ID: 76857769
  • Key type: eth_secp256k1 (default), BIP44 coin type 60 — MetaMask/Ledger compatible out of the box
  • Fee market: base fee 0.0025 ulume/gas, min gas price floor 0.0005 ulume/gas (prevents zero-fee spam that hit Evmos), change denominator 16 (~6.25%/block vs upstream 8 at ~12.5%)
  • Consensus max gas: 25,000,000
  • Centralized denom metadata (config/bank_metadata.go): ulume/lume/alume metadata + RegisterExtraInterfaces for eth_secp256k1 crypto across all SDK+EVM paths
  • Automatic config migration (cmd/lumera/cmd/config_migrate.go): pre-EVM app.toml files auto-gain [evm], [json-rpc], and [evm.mempool] sections with correct chain ID on first startup — no manual intervention

Precompiles (11 static)

Standard (8): P256, Bech32, Staking, Distribution, ICS20, Bank, Gov, Slashing

Custom Lumera precompiles:

  • Action module (0x0901): 11 methods — cascade/sense request+finalize, approve, fee queries, paginated action queries. See action-precompile.md for full ABI reference.
  • Supernode module (0x0902): 12 methods — register/deregister, state transitions, metrics reporting (caller-bound auth), XOR distance ranking. See supernode-precompile.md for full ABI reference.
  • Wasm (CosmWasm bridge) (0x0903): 4 methods — execute, query, contractInfo, rawQuery. The EVM→CosmWasm direction of the cross-runtime bridge. See wasm-precompile.md for full ABI reference, architecture, and design notes.

Precompile protections: blocked-address send restrictions on all 11 precompile addresses + module accounts

CosmWasm ↔ EVM Cross-Runtime Bridge (Industry First)

Lumera is the only Cosmos EVM chain that also runs CosmWasm. This PR ships the industry's first bidirectional cross-runtime bridge between CosmWasm and an EVM, with no external precedent.

Direction Mechanism Address/Config
EVM → CosmWasm Wasm precompile (IWasm at 0x0903) Solidity contracts call execute/query/contractInfo/rawQuery on any CosmWasm contract
CosmWasm → EVM Custom message handler + query handler decorator CosmWasm contracts send evm_call via CosmosMsg::Custom and QueryRequest::Custom

Phase 1 (shipped): non-payable execute/query in both directions, shared reentrancy guard (max depth 1), per-call gas cap (3M), strict address validation, sender identity preservation (calling contract, not tx.origin), atomic snapshot/revert across runtime boundary.

Key source files: precompiles/wasm/, precompiles/crossruntime/, app/wasm_evm_plugin.go, precompiles/solidity/contracts/interfaces/IWasm.sol

IBC & Cross-Chain

  • IBC ERC20 middleware wired on both v1 and v2 transfer stacks:

  • v1: EVMTransferKeeper → ERC20IBCMiddleware → CallbacksMiddleware → PFM

  • v2: TransferV2Module → CallbacksV2Middleware → ERC20IBCMiddlewareV2

  • Governance-controlled registration policy via MsgSetRegistrationPolicy: 3 modes (all/allowlist default/none) with provenance-bound base-denom allowlisting (full IBC trace verification per base denom). Default entries (uatom, uosmo, uusdc, inj) are inert placeholders until governance binds real IBC channels.

JSON-RPC & Tooling

  • JSON-RPC server enabled by default: eth, net, web3, txpool, debug, personal, miner namespaces; mainnet auto-blocks debug/personal/admin via jsonrpc_policy.go
  • Indexer enabled by default: receipt persistence, tx hash lookup, log indexing, historical state queries (pruning-dependent)
  • Per-IP rate limiting (app/evm_jsonrpc_ratelimit.go): token bucket proxy — requests-per-second=50, burst=100, entry-ttl=5m, HTTP 429 with JSON-RPC -32005 on limit, stale entries GC'd every 60s
  • EVM tracing: runtime-configurable via app.toml [evm] tracerjson, struct, access_list, markdown; enables debug_traceTransaction, debug_traceBlockByNumber, debug_traceBlockByHash, debug_traceCall
  • OpenRPC discovery: rpc_discover JSON-RPC method (port 8545) + GET/POST /openrpc.json HTTP endpoint (port 1317) with CORS; gzip-compressed spec embedded in binary (315 KB → 20 KB); build-time generation via tools/openrpcgen with Go reflection + AST parsing; POST /openrpc.json proxies to JSON-RPC server enabling OpenRPC Playground "Try It" directly; spec version from go.mod via runtime/debug.ReadBuildInfo()
  • Playground URL: https://playground.open-rpc.org/?url=http://<node>:1317/openrpc.json

Account Migration (x/evmigration)

  • MsgClaimLegacyAccount: signed by new eth_secp256k1 address; legacy signature = secp256k1_sign(SHA256("lumera-evm-migration:<chainID>:<evmChainID>:<legacy_address>:<new_address>"))
  • Atomic 9-module migration in single tx: auth (vesting-aware) → bank → staking (delegations + unbonding + redelegations + UnbondingID indexes) → distribution (reward withdrawal) → authz (grant re-keying) → feegrant (allowance re-keying) → supernode → action → claim
  • MsgMigrateValidator: re-keys validator record + all associated module state including delegator references
  • Fee waiving (ante/evmigration_fee_decorator.go): zero-fee migration txs since new address has no balance
  • Governance params: enable_migration, migration_end_time, max_migrations_per_block=50, max_validator_delegations=2000
  • Queries: MigrationRecord, MigrationRecords, MigrationEstimate (dry-run), MigrationStats, LegacyAccounts, MigratedAccounts

Upgrade & Ops

  • v1.20.0 upgrade handler: store additions for feemarket, precisebank, vm, erc20, evmigration; post-migration finalization sets Lumera EVM params + coin info, feemarket params, ERC20 defaults (EnableErc20=true, PermissionlessRegistration=false), and seeds provenance-bound ERC20 registration policy
  • Adaptive store upgrade manager updated for missing stores in dev/test skip-upgrade flows
  • Node operator config guide: docs/evm-integration/node-evm-config-guide.mdapp.toml tuning, [evm]/[json-rpc]/[evm.mempool] sections, RPC exposure, tracer config, rate limiting, security policies

Devnet

  • EVM-aware devnet with EVM genesis template (devnet/default-config/devnet-genesis-evm.json)
  • Devnet port mapping: validators 1–5 map JSON-RPC to 8545/8555/8565/8575/8585; REST API to 1327/1337/1347/1357/1367
  • Hermes IBC relayer updated for EVM-era chains with auto-detection scripts
  • EVM devnet tests (devnet/tests/validator/evm_test.go): fee market, cross-peer tx visibility, mixed Cosmos+EVM blocks
  • Remix IDE guide: docs/evm-integration/remix-guide.md — MetaMask network config, contract deployment, interaction, event querying, troubleshooting

Bugs Found & Fixed (25 issues)

Documented in docs/evm-integration/bugs.md


Test Coverage (~470 tests)

Area Unit Integration Devnet Highlights
Dual-route ante handler 28 3 Both EVM and Cosmos paths verified separately
App-side mempool 12 Ordering, nonce gaps, replacement, capacity, WS subscriptions
Async broadcast queue 4 Deadlock prevention, deduplication, queue-full handling
JSON-RPC 23 Batch requests, persistence across restart, mixed blocks
ERC20/IBC middleware 31 7 Policy modes, provenance-bound trace verification, v1+v2 stacks
Precisebank 39 6 Mint/burn parity, fractional remainder, 6↔18 bridge
Feemarket (EIP-1559) 9 8 Multi-block progression, min floor, dynamic adjustment
Precompiles 42 All 11 precompiles + gas metering + Action + Supernode + Wasm bridge
Cross-runtime bridge 46 Guard, addr conversion, plugin dispatch/passthrough/reentrancy/validation
Account migration 102 14 All 9 module migrations, vesting, validator, edge cases
OpenRPC 15 2 Spec sync, HTTP serving, CORS, playground proxy
WebSocket subscriptions 4 newHeads, logs, pendingTransactions
Devnet multi-validator 12+ Cross-validator propagation, fee market, IBC
Devnet evmigration tool 7 modes prepare → estimate → migrate → migrate-validator → migrate-all → verify → cleanup

Devnet evmigration pipeline (make devnet-evm-upgrade): boots v1.11.1 devnet → prepares legacy state (5 legacy + 5 extra accounts with delegations, unbonding, redelegations, authz, feegrant, supernode, action across all validators) → governance upgrade to v1.20.0 → estimate all accounts → migrate validators → migrate accounts → verify clean state. Idempotent and rerunnable.


Documentation Added

Document Description
main.md Full integration architecture, semantics, gap analysis, cross-chain comparison
tests.md Complete test inventory with per-area coverage assessment and gap analysis
bugs.md 25 bugs: symptom, root cause, fix, tests added
roadmap.md 15-phase roadmap, 163/168 items complete (97%)
security-audit.md Security audit findings, recommendations, and resolution status
tune-guide.md Mainnet parameter tuning guide: fee market, gas limits, mempool, RPC limits
node-evm-config-guide.md Node operator guide: app.toml tuning, security policies, rate limiting
openrpc-playground.md OpenRPC discovery access methods, devnet ports, playground setup
remix-guide.md Remix IDE + MetaMask testing guide with troubleshooting
precompiles.md Precompile overview — 11 static precompiles (8 standard + 3 custom Lumera)
action-precompile.md Action precompile (0x0901) ABI reference, 11 methods, design notes
supernode-precompile.md Supernode precompile (0x0902) ABI reference, 12 methods, design notes
wasm-precompile.md Wasm precompile (0x0903) — full cross-runtime bridge architecture, Solidity+Rust interfaces, data flow, gas metering, reentrancy design
evmigration-ui.md EVM legacy account migration portal UI and wallet rollout
devnet-tests.md Devnet end-to-end migration test tool documentation
block-explorer.md External block explorer (Blockscout) integration plan
lumera-ports.md Full port mapping reference for all node services

What Makes This Different

Capability Lumera Other Cosmos EVM chains at launch
Bidirectional CosmWasm↔EVM bridge Industry first — Solidity contracts call CosmWasm, CosmWasm contracts call EVM No chain has this
CosmWasm + EVM coexistence Yes (wasmd v0.61.6 + Cosmos EVM v0.6.0) No chain has both
OpenRPC discovery rpc_discover + /openrpc.json + playground proxy No chain has this
Async broadcast queue Deadlock-free mempool promotion Not publicly addressed
Min gas price floor 0.0005 ulume/gas (prevents decay to zero) Evmos launched without → spam attack
IBC v2 ERC20 middleware Both v1 and v2 transfer stacks Most only v1
IBC ERC20 registration policy Provenance-bound trace verification per base denom Not standard
Custom module precompiles Action (0x0901) + Supernode (0x0902) + Wasm (0x0903) at launch Usually post-launch
Account migration module Dual-sig, 9-module atomic, validator support No comparable mechanism
Pre-mainnet test coverage ~470 tests (308 unit + 138 integration + 12+ devnet) Typically <100
Bugs fixed pre-launch 25 issues found and resolved Usually found post-mainnet
Automatic config migration config_migrate.go adds [evm] section on startup Manual operator action
JSON-RPC rate limiting Built-in per-IP token bucket at app layer External infra or post-launch

Gas Configuration

Parameter Lumera Evmos Kava Cronos
Default base fee 0.0025 ulume (2.5 gwei equiv) ~10 gwei ~0.25 ukava Variable
Min gas price floor 0.0005 ulume 0 (no floor) N/A N/A
Base fee change denominator 16 (~6.25%/block) 8 (~12.5%) 8 8
Consensus max gas 25,000,000 30M–40M 25,000,000 25,000,000

Roadmap Status

163/168 items complete (97%) across 15 phases.

Status Phases
Complete Core EVM Runtime, Ante Handler, Feemarket, Mempool & Broadcast, JSON-RPC & Indexer, Static Precompiles, IBC+ERC20, OpenRPC, Store Upgrades, Account Migration, Testing, Custom Precompiles, CosmWasm + EVM Interaction (Phase 1)
In Progress Production Hardening (4/8 — security audit done, monitoring runbook pending), Ecosystem & Tooling (2/7 — block explorer, Hardhat guide pending)

Breaking Changes

Change Before After Impact
Key type secp256k1 eth_secp256k1 keys add defaults to Ethereum-compatible keys
BIP44 coin type 118 60 Same mnemonic derives different addresses
Fee behavior Flat gas price EIP-1559 dynamic base fee Wallets must support type-2 txs for optimal UX
New stores feemarket, precisebank, vm, erc20, evmigration v1.20.0 upgrade handler required
Config No [evm] section [evm], [json-rpc], [evm.mempool] in app.toml Auto-migrated on startup via config_migrate.go

@roomote-v0
Copy link
Copy Markdown

roomote-v0 Bot commented Mar 19, 2026

Rooviewer Clock   See task

Reviewed 355b8d2 (8 new commits since last review at e09830d). These commits make the evmigration devnet pipeline robust for multisig-validator hosts: bootstrap a single-sig funder from the multisig composite's genesis balance, record infrastructure legacy keys (governance, sncli, bootstrap funder) so migrate-all picks them up, add multisig-aware bank-send and governance voting helpers, fix a non-deterministic map-iteration bug in authAccountPayloadTypeName (with regression test), handle SDK's omission of sequence when it's 0, remove duplicate stats logging, and add a final migration summary to verify mode. All 16 previously flagged issues remain resolved. No new issues found.

Issues

# Severity File Description Fix
1 Critical x/evmigration/keeper/msg_server_migrate_validator.go Validator migration skips operator's own delegations to other validators -- distribution and staking state for those delegations is never re-keyed Resolved in 4c46bf1
2 Medium app/evm_jsonrpc_ratelimit.go extractIP() trusts spoofable X-Forwarded-For / X-Real-IP headers, allowing rate-limit bypass Resolved in a4c786e
3 Low app/openrpc/http.go OpenRPC POST proxy uses http.DefaultClient with no timeout -- a slow upstream can pin goroutines indefinitely Resolved in 4c46bf1
4 Medium app/evm_broadcast.go broadcastBatch returns on first BroadcastTxSync failure, silently dropping all remaining transactions in the batch Resolved in 4c46bf1
5 Low x/evmigration/keeper/migrate_staking.go migrateActiveDelegations reads GetDelegatorStartingInfo after the same key was already deleted, making lines 68-77 dead code Resolved in 4c46bf1
6 Medium .github/workflows/build.yml make openrpc added to integration-tests but missing from system-tests and build jobs that also compile the full binary via ignite chain build -- //go:embed openrpc.json.gz will fail Resolved in 19a2bdb
7 Low x/evmigration/keeper/migrate_staking.go migrateWithdrawAddress doc comment has two overlapping comments spliced together -- first one cut off mid-sentence Resolved in 11a85cb
8 Low tools/openrpcgen/schema.go structProperties iterates requiredSet map without sorting, producing non-deterministic required arrays in the OpenRPC spec -- causes noisy diffs on every regeneration Resolved in 52b099a
9 Low Makefile BUILD_LDFLAGS includes -X ...cmd.ChainID=$(CHAIN_ID) but no var ChainID exists in cmd/lumera/cmd -- the linker silently ignores it, so the chain ID from config.yml is never injected into the binary Resolved in 19a2bdb
10 Medium x/evmigration/keeper/msg_server_claim_legacy.go preChecks does not consult MigrationRecordByNewAddress -- two legacy accounts can migrate to the same destination, silently overwriting the reverse index Resolved in f1f6872
11 Low Makefile.devnet .PHONY declaration devnet-new-1110 out of sync with renamed target devnet-new-1111 Resolved in c7be796
12 Medium app/wasm_evm_plugin.go:82 gasCapForCall computes meter.Limit() - meter.GasConsumed() which can underflow (uint64 wrap) if consumed exceeds limit, handing the EVM a near-MaxUint64 gas budget Resolved in 128cbb3
13 Nit precompiles/wasm/tx.go:55 caller bech32 string computed but only used for logging; actual Execute call uses sdk.AccAddress(contract.Caller().Bytes()) directly -- wasted work + misleading error path Resolved in 128cbb3
14 Nit app/wasm_evm_plugin.go:369 _ = wasmtypes.ErrUnknownMsg suppresses unused import lint for wasmtypes which isn't actually used in the file Resolved in 128cbb3
15 Nit app/wasm_evm_plugin_test.go:450 containsString + searchSubstring reimplement strings.Contains from the standard library Resolved in 128cbb3
16 Nit docs/evm-integration/tests.md:72 ~~Totals row says "Unit: ~297" but individual unit rows sum to ~351 -- appears stale from before EVMigration CLI and JSON-RPC rate limiting rows were added~~ Resolved in 128cbb3
Previous reviews

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

Comment thread x/evmigration/keeper/msg_server_migrate_validator.go
Comment thread app/evm_jsonrpc_ratelimit.go Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2d4527beaf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/evm_jsonrpc_ratelimit.go Outdated
Comment thread app/evm_jsonrpc_ratelimit.go Outdated
Comment thread app/openrpc/http.go Outdated
Comment thread app/evm_broadcast.go
Comment thread x/evmigration/keeper/migrate_staking.go Outdated
Comment thread .github/workflows/build.yml Outdated
Comment thread x/evmigration/keeper/migrate_staking.go Outdated
roomote-v0[bot]
roomote-v0 Bot previously approved these changes Mar 20, 2026
Comment thread tools/openrpcgen/schema.go
@akobrin1 akobrin1 requested a review from Copilot March 20, 2026 03:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class Cosmos EVM integration support across Lumera’s node runtime, CLI, devnet tooling, upgrade handlers, JSON-RPC/OpenRPC, and CI pipelines.

Changes:

  • Introduces EVM chain configuration/constants (chain ID, fee market defaults, denom metadata) and wires EVM modules into app/CLI/startup paths.
  • Expands devnet + Hermes automation (upgrade flow, version/key-style detection, JSON-RPC ports, log archiving, test binary distribution).
  • Adds extensive EVM-focused unit/integration tests and updates build/test/lint/release automation (OpenRPC generation, golangci-lint v2 config, workflows).

Reviewed changes

Copilot reviewed 141 out of 516 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
devnet/tests/evmigration/README.md Adds devnet evmigration test tool README
devnet/scripts/wait-for-height.sh Improves height wait UX + upgrade-halt detection
devnet/scripts/upgrade.sh Adds idempotent upgrade flow + version checks
devnet/scripts/upgrade-binaries.sh Verifies binary version + improves compose restart robustness
devnet/scripts/start.sh Archives logs + optional claims CSV start flags
devnet/scripts/restart.sh Archives logs + optional claims CSV start flags
devnet/scripts/configure.sh Improves host-side shared volume/binary setup
devnet/main.go Import ordering tweak
devnet/hermes/scripts/hermes-start.sh Auto-detects key style based on chain version
devnet/hermes/scripts/hermes-configure.sh Hermes address_type derivation based on key style
devnet/hermes/scripts/hermes-channel.sh Uses eth HD path for EVM-era keys
devnet/hermes/config.toml Devnet Hermes tuning (misbehaviour, clear interval)
devnet/hermes/Dockerfile Updates Go base image + IBC-Go version arg
devnet/generators/docker-compose.go Adds chain version detection + JSON-RPC port bindings
devnet/generators/config.go Adds JSON-RPC default ports constants
devnet/dockerfile Sets bash shell + installs ripgrep
devnet/default-config/devnet-genesis.json Adds audit/evmigration genesis params + updates claim end time/amount
devnet/config/validators.json Restructures supernode config + adds per-validator JSON-RPC ports
devnet/config/config.json Adds evm_from_version, JSON-RPC config, and mnemonic lists
devnet/config/config.go Extends devnet config schema (version, JSON-RPC, mnemonics)
devnet/.gitignore Ignores generated compose/bin/logs
config/evm.go Adds EVM chain constants (IDs, fees, gas)
config/config.go Updates chain denom metadata + delegates Bech32/BIP44 setup
config/codec.go Registers SDK + EVM crypto interfaces
config/bip44.go Sets coin type to EVM (60)
config/bech32.go Centralizes Bech32 prefix constants/helpers
config/bank_metadata.go Provides/upserts canonical bank metadata for ulume/lume/alume
cmd/lumera/main.go Treats context cancellation as graceful shutdown
cmd/lumera/cmd/root_test.go Adds CLI wiring tests for EVM flags + key type defaults
cmd/lumera/cmd/root.go Adds EVM modules to CLI basics + keyring options + config migration hook
cmd/lumera/cmd/jsonrpc_policy_test.go Adds mainnet JSON-RPC namespace policy tests
cmd/lumera/cmd/jsonrpc_policy.go Enforces disallowed JSON-RPC namespaces on mainnet
cmd/lumera/cmd/config_test.go Verifies app config EVM/JSON-RPC defaults
cmd/lumera/cmd/config.go Extends app.toml template/config with EVM/JSON-RPC/TLS/Lumera sections
claiming_faucet/main.go Uses EVM keyring options + registers EVM crypto interfaces
app/wasm.go Removes wasm-specific ante/posthandler setup from wasm registration
app/vm_preinstalls_test.go Adds EVM preinstall validation matrix test
app/upgrades/v1_12_0/upgrade_test.go Tests ERC20 params init when InitGenesis skipped
app/upgrades/v1_12_0/upgrade.go Adds v1.12.0 EVM store upgrades + manual params finalization
app/upgrades/upgrades_test.go Adds v1.12.0 to upgrade registry tests
app/upgrades/upgrades.go Registers v1.12.0 upgrade config
app/upgrades/store_upgrade_manager_test.go Adds adaptive store upgrade test for missing EVM stores
app/upgrades/store_upgrade_manager.go Uses shared EnvBool helper
app/upgrades/params/params.go Extends upgrade params with EVM-related keepers
app/test_support.go Import ordering tweak
app/test_helpers.go Adds EVM test-tag guard + denom metadata in genesis + disables fastnode in tests
app/statedb_events_test.go Adds StateDB snapshot/event revert invariant test
app/proto_bridge_test.go Tests proto enum bridge registrations
app/proto_bridge.go Registers Cosmos SDK + EVM enums in proto bridge
app/precisebank_mint_burn_parity_test.go Adds precisebank mint/burn parity tests
app/precisebank_fractional_test.go Adds precisebank fractional balance matrix tests
app/pending_tx_listener_test.go Tests pending tx listener fanout + offline broadcaster error
app/openrpc/spec.go Embeds and serves gzipped OpenRPC spec
app/openrpc/rpc_api.go Exposes rpc.discover service
app/openrpc/register.go Registers rpc namespace on JSON-RPC server
app/openrpc/openrpc_test.go Validates embedded OpenRPC doc and helpers
app/ibc_erc20_middleware_test.go Tests ERC20 IBC middleware wiring (v1/v2)
app/ibc.go Wires ERC20 middleware into IBC transfer stacks
app/evm_test.go Tests EVM module genesis + ordering + module account perms
app/evm_static_precompiles_test.go Tests static precompile registration
app/evm_runtime.go Captures clientCtx via RegisterTxService + shuts down background workers
app/evm_mempool_test.go Tests EVM mempool wiring
app/evm_mempool.go Configures app-side EVM mempool + signer extraction
app/evm_jsonrpc_alias.go Adds JSON-RPC alias reverse proxy + method rewrite
app/evm_erc20_policy_msg.go Implements governance msg server for ERC20 IBC policy
app/evm/testtag_guard.go Adds guard helpers for missing -tags=test
app/evm/reset_testbuild.go Resets EVM global config in test builds
app/evm/reset.go Panics with guidance in non-test builds under testing
app/evm/prod_guard_test.go Documents guard behavior in !test builds
app/evm/precompiles.go Defines Lumera static precompile address list
app/evm/modules.go Registers EVM modules for CLI default genesis generation
app/evm/genesis.go Defines Lumera EVM/feemarket genesis overrides
app/evm/defaults_testbuild.go No-op keeper defaults in test builds
app/evm/defaults_prod.go Sets keeper default coin info in production builds
app/evm/config_modules_genesis_test.go Tests EVM config helpers + genesis overrides + module registration
app/evm/config.go Adds custom signer provider helper
app/evm/ante_sigverify_test.go Tests signature gas consumer matrix
app/evm/ante_nonce_test.go Tests EVM nonce increment matrix
app/evm/ante_internal_test.go Tests genesis-skip decorator behavior
app/evm/ante_gas_wanted_test.go Tests EVM gas-wanted decorator behavior
app/evm/ante_evmigration_fee_test.go Tests reduced ante path for migration-only txs
app/encoding.go Uses EVM test-tag guard when building encoding config in tests
app/blocked_addresses_test.go Tests blocked module/precompile addresses and send restriction
app/params/proto.go Removes legacy encoding config helper
app/amino_codec_test.go Adds legacy amino codec regression test for eth_secp256k1
app/amino_codec.go Registers eth_secp256k1 amino types + syncs legacy codec
ante/evmigration_validate_basic_decorator_test.go Tests ValidateBasic suppression for migration-only txs
ante/evmigration_validate_basic_decorator.go Allows ErrNoSignatures for migration-only txs
ante/evmigration_fee_decorator_test.go Tests fee waiving for migration-only txs
ante/evmigration_fee_decorator.go Clears min-gas-prices for migration-only txs
ante/delayed_claim_fee_decorator.go Removes disabled decorator block comment
Makefile Adds OpenRPC generation, updates lint tool path, adjusts test targets
CHANGELOG.md Adds v1.12.0 changelog entry + fixes formatting issue
.vscode/settings.json Adds terminal tool auto-approve entry
.vscode/launch.json Adds integration test debug configuration
.markdownlint.json Adds markdownlint overrides
.golangci.yml Adds golangci-lint v2 configuration
.github/workflows/test.yml Switches to make targets + reusable ignite action
.github/workflows/systemtests.yml Adds systemtests workflow using shared actions/make targets
.github/workflows/systemtests.yaml Removes old systemtests workflow
.github/workflows/release.yml Simplifies release workflow to reuse build workflow
.github/workflows/lint.yml Adds a dedicated golangci-lint workflow
.github/workflows/build.yml Adds build workflow orchestrating lint/tests/build+artifacts
.github/actions/install-ignite/action.yml Sets default Ignite version input

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/upgrades/params/params.go
Comment thread app/upgrades/v1_20_0/upgrade.go
Comment thread devnet/scripts/configure.sh Outdated
Comment thread devnet/scripts/configure.sh
Comment thread app/amino_codec_test.go Outdated
Comment thread app/upgrades/upgrades_test.go Outdated
Comment thread app/upgrades/upgrades_test.go Outdated
Comment thread devnet/scripts/wait-for-height.sh
Comment thread .vscode/settings.json Outdated
Comment thread Makefile Outdated
roomote-v0[bot]
roomote-v0 Bot previously approved these changes Mar 20, 2026
Copy link
Copy Markdown

@roomote-v0 roomote-v0 Bot left a comment

Choose a reason for hiding this comment

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

All 9 previously identified issues are resolved. The three security audit findings (rate-limit proxy bypass, redelegation undercount, chain ID domain separation) have been addressed correctly with well-structured code and comprehensive tests. No new issues found.

roomote-v0[bot]
roomote-v0 Bot previously approved these changes Mar 20, 2026
Copy link
Copy Markdown

@roomote-v0 roomote-v0 Bot left a comment

Choose a reason for hiding this comment

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

New Issue: preChecks does not prevent duplicate destination addresses

Severity: Medium

preChecks verifies that newAddr is not a previously-migrated legacy address (step 6, line 108), but does not check whether newAddr was already used as a destination in a prior migration. The MigrationRecordByNewAddress reverse index exists and is populated in finalizeMigration, but is never consulted during pre-checks.

This means two different legacy accounts could migrate to the same new address. While the bank transfers would be additive (funds accumulate at the destination), the reverse index entry from the first migration would be silently overwritten by the second, making the first legacy address unreachable via MigrationRecordByNewAddress.

Suggested fix -- add a check after step 6:

// 6b. New address must not already be a migration destination
has, err = ms.MigrationRecordByNewAddress.Has(ctx, newAddr.String())
if err != nil {
    return err
}
if has {
    return types.ErrNewAddressAlreadyUsed // new error to define
}

If allowing multiple legacy accounts to converge on one destination is intentional, the reverse index should store a list rather than a single string, or the overwrite should be documented.

Comment thread x/evmigration/keeper/msg_server_claim_legacy.go
roomote-v0[bot]
roomote-v0 Bot previously approved these changes Apr 1, 2026
@akobrin1 akobrin1 requested a review from Copilot April 1, 2026 14:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 137 out of 524 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

devnet/scripts/configure.sh:1

  • The script documentation describes binaries as optional, but BIN_DIR is now required (hard-fails if it can’t auto-detect devnet/bin). This makes config-only workflows (copying config.json/validators.json without host binaries) impossible. Consider allowing a missing BIN_DIR by setting BIN_DIR=\"\" and continuing, or adding an explicit --no-bin-dir / --skip-binaries mode.
#!/bin/bash

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread devnet/config/config.json
Comment thread app/openrpc/http.go Outdated
Comment thread app/evm/testtag_guard.go
roomote-v0[bot]
roomote-v0 Bot previously approved these changes Apr 1, 2026
roomote-v0[bot]
roomote-v0 Bot previously approved these changes Apr 1, 2026
akobrin1 and others added 15 commits April 18, 2026 14:02
- MsgClaimLegacyAccount/MsgMigrateValidator: replace legacy_pub_key and
  legacy_signature with LegacyProof oneof (field 3); reserve field 4.
- Params: add max_multisig_sub_keys (field 5, default 20).
- LegacyAccountInfo: add is_multisig / threshold / num_signers (fields 5-7).
- QueryMigrationEstimateResponse: add is_multisig / threshold / num_signers
  (fields 16-18).

Breaking change is intentional; module is pre-EVM-upgrade so no on-chain
messages are in flight.
…teBasic

- proof.go: stateless ValidateBasic helpers for SingleKeyProof /
  MultisigProof, plus param-aware ValidateParams for the multisig
  size cap (governance-adjustable).
- proof_test.go: 12 MultisigProof rejection cases, 4 SingleKeyProof
  cases, LegacyProof dispatch, param cap boundary.
- types.go: MsgClaimLegacyAccount / MsgMigrateValidator ValidateBasic
  delegate to msg.LegacyProof.ValidateBasic() after the field-shape
  change from Task 2.
- types_test.go: update existing tests to use LegacyProof oneof instead
  of the removed LegacyPubKey / LegacySignature flat fields.
- errors.go: register ErrInvalidLegacyProof (code 1120).

Tasks 4 and 5 merged into one atomic commit because the types package
compiles as a unit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ySingleKeyProof, verifyMultisigProof)

- verifySecp256k1Sig: shared CLI-vs-ADR-036 dispatch; used by both
  single-key and multisig paths so format handling lives in one place.
- verifySingleKeyProof: decodes secp256k1 pubkey, asserts address
  derivation, delegates to the shared helper.
- verifyMultisigProof: reconstructs kmultisig.LegacyAminoPubKey from
  sub-keys + threshold, asserts address derivation, verifies each
  sub-signature. Per-sub-signer ADR-036 doc uses the sub-signer's
  own bech32 address (derivable from sub_pub_keys).

Not yet reachable from msg servers — Task 10 wires VerifyLegacyProof
to these helpers and updates the msg servers.

Tasks 7/8/9 merged: all three live in verify.go and cross-reference
each other; splitting them would produce an uncompileable intermediate
state if anyone tried to land them separately.
- verify.go: add VerifyLegacyProof top-level dispatcher (single/multisig);
  remove the obsolete VerifyLegacySignature.
- msg_server_claim_legacy.go / msg_server_migrate_validator.go: fetch
  params, call msg.LegacyProof.ValidateParams(params.MaxMultisigSubKeys)
  before the crypto work, then call VerifyLegacyProof.
- verify_test.go: rewrite every VerifyLegacySignature call site to
  construct LegacyProof{Single:...} and call VerifyLegacyProof.
- msg_server_*_test.go: update MsgClaimLegacyAccount / MsgMigrateValidator
  struct literals to use LegacyProof instead of the removed LegacyPubKey
  / LegacySignature fields.
- client/cli/tx.go, app/evm/ante_evmigration_fee_test.go,
  tests/integration/evmigration/migration_test.go: same struct-literal
  migration so the whole project compiles and lint passes cleanly.
- types/proof_test.go: gofmt fix (pre-existing formatting issue).

Keeper package compiles again; all keeper tests pass (0 failures).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers valid 2-of-3 with CLI + ADR-036 formats, 1-of-1 edge,
wrong-address rejection, corrupted sub-signature rejection, and
N=20 boundary + MaxMultisigSubKeys param cap enforcement.

Uses kmultisig.NewLegacyAminoPubKey to construct multisig keys
identically to how the SDK does it via 'lumerad keys add --multisig',
so address derivation is byte-exact with real on-chain multisig
accounts.
- isLegacyPubKey helper: accepts secp256k1 pubkeys and flat multisig of
  secp256k1 sub-keys; rejects nested multisig and non-secp256k1 leaves.
- remainingLegacyAccountStatus: delegate pubkey-type check to
  isLegacyPubKey so multisig accounts are included in the legacy list.
- LegacyAccounts: populate is_multisig / threshold / num_signers on
  LegacyAccountInfo when the on-chain pubkey is multisig.
- MigrationEstimate: preflight multisig feasibility — set
  WouldSucceed=false with descriptive RejectionReason for nested/
  non-secp256k1 sub-keys and for N > MaxMultisigSubKeys. Nil-pubkey
  accounts intentionally not flagged (cannot distinguish single-key
  from multisig from the account alone; deferred to CLI).

Tests cover: multisig account appears in LegacyAccounts with correct
threshold/num_signers; MigrationEstimate returns WouldSucceed=true
for supported 2-of-3; rejects N=21 > cap with clear reason; rejects
multisig with an ed25519 sub-key with "non-secp256k1" reason.

Tasks 12, 13, 14 merged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both messages carry a legacy_proof oneof which AutoCLI cannot render as
positional args. Rely entirely on the hand-written commands in
x/evmigration/client/cli/tx.go.
Four new subcommands under 'lumerad tx evmigration' that together
implement the multi-party signing workflow for multisig legacy
accounts (and cold-wallet single-sig accounts):

  generate-proof-payload — query on-chain account, produce
    PartialProof JSON template seeded with pubkey material
  sign-proof — each co-signer appends their sub-signature
    (idempotent re-sign upserts the existing index)
  combine-proof — merge partial files with cross-file consistency
    checks (chain_id, addresses, threshold, sub-pubkeys); dedup by
    index; assemble LegacyProof and write unsigned tx JSON
  submit-proof — sign new_signature with destination eth key,
    simulate gas, broadcast

PartialProof is a coordination artifact only; never stored on-chain.
JSON schema version 1 with forward-compat rejection of unknown versions.

AutoCLI descriptors for the two messages are already skipped (Task 16).

Tasks 17, 18, 19, 20, 21 merged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- multisig_helpers_test.go: BuildMultisigLegacyAccount,
  SignMultisigProof (CLI + ADR-036), and
  createFundedMultisigAccount (suite method).
- migration_test.go: four integration tests —
  TestClaimLegacyAccount_Multisig_Success (2-of-3 E2E),
  TestClaimLegacyAccount_Multisig_ADR036 (ADR-036 envelope),
  TestClaimLegacyAccount_Multisig_Replay (replay guard),
  TestClaimLegacyAccount_Multisig_CorruptedSubSig.

Tasks 22 and 23 merged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adapts the plan's Tasks 24-27 to the project's existing devnet-binary
convention (the directory has no _test.go files; it's a standalone
main package driven by modes).

New mode 'multisig':
- Seeds a 2-of-3 secp256k1 multisig legacy account.
- Funds it from --funder and issues a 1-ulume self-send so the multisig
  pubkey lands on-chain (precondition per design spec Non-Goals).
- Runs the four-step CLI flow:
  generate-proof-payload → sign-proof × 2 → combine-proof → submit-proof
- Verifies the migration record exists and balances moved.

Usage:
  tests_evmigration -mode=multisig -bin=lumerad -rpc=... \
                    -chain-id=... -funder=validator0

Cannot be validated in sandboxed environments; requires a running devnet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- legacy-migration.md: new 'Multisig account migration' section
  covering wire format, invariants, preconditions, and the four-step
  CLI flow (cross-linked to user-guide.md).
- user-guide.md: CLI walkthrough for the offline multisig flow
  (generate-proof-payload / sign-proof / combine-proof / submit-proof),
  including the --legacy-key cold-wallet path.
- portal-ui.md: replace flat legacy_pub_key / legacy_signature field
  docs with the new legacy_proof oneof (single + multisig shapes) and
  document the is_multisig / threshold / num_signers query fields.
- testing/tests.md: add rows for all new unit, integration, and devnet
  tests (verifier, ValidateBasic, query, integration, devnet mode).
- testing/tests/unit-evmigration.md: add per-test rows for multisig
  verifier, query, and type-validation tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wires validator multisig end-to-end: devnet scripts and config provision
multisig validators, the tests_evmigration binary exercises the multi-step
offline flow, and the CLI and keeper verifier paths are hardened with
additional tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address code-review findings from the preceding multisig commit: the
devnet funder detection now errors out cleanly when every key is a
multisig composite instead of silently returning one; the CLI partial
proof kind check reuses the existing migrationProofKind* constants and
ADR-036 sign-doc construction is annotated to stay in lockstep with
keeper/verify.go; and the duplicated sign-plus-multisign block in the
validator and supernode setup scripts is factored into a shared
multisig_sign_unsigned helper in common.sh. Also enables Hermes in the
default devnet config and bumps incidental devnet Go deps (grpc,
genproto, gonum, gcp detector) to match the root module.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
roomote-v0[bot]
roomote-v0 Bot previously approved these changes Apr 20, 2026
Copy link
Copy Markdown

@roomote-v0 roomote-v0 Bot left a comment

Choose a reason for hiding this comment

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

All 16 previously flagged issues remain resolved. The 17 new commits adding multisig support to the evmigration module are well-structured with proper validation, comprehensive tests, and correct verification logic. No new issues found.

Three stacked bugs kept a devnet multisig validator from reaching
genesis:

1. cosmos-sdk's `genesis gentx` short-circuits to PrintUnsignedTx for
   multisig/offline keys (x/genutil/client/cli/gentx.go @ v0.53.6
   lines 162-165) and silently ignores --output-document. Capture stdout
   into the unsigned tempfile ourselves and guard with -s before
   proceeding.

2. `tx sign` and `tx multisign` silently overwrite caller-supplied
   --account-number/--sequence with values fetched from a full node
   unless --offline is explicit. The chain isn't up during gentx, so
   the helper now passes --offline on all three signing steps, plus
   explicit account/sequence on multisign.

3. build_multisig_gentx wrote its output to `${MONIKER}_gentx.json`
   while the downstream collection globs expect `gentx-*.json`. Rename
   to `gentx-${MONIKER}.json` and move the unsigned tempfile to /tmp so
   it can't get swept up by the same glob.

Verified with a full `devnet-build-1111` + `devnet-up-detach`: all five
validators (including supernova_validator_2 as a 2-of-3
LegacyAminoPubKey) reach BOND_STATUS_BONDED and the chain produces
blocks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
roomote-v0[bot]
roomote-v0 Bot previously approved these changes Apr 20, 2026
akobrin1 and others added 8 commits April 20, 2026 14:58
…e JSON shapes

Four bugs surfaced while running devnet-evmigrationp-prepare against a
devnet with a multisig validator (supernova_validator_2):

1. `keys add --multisig` was getting --node appended by buildLumeraArgs.
   That subcommand is a pure keyring operation and rejects --node. Inline
   the minimal arg list for this specific call and only append --home
   when set.

2. queryAccountNumberAndSequence required both `account_number` and
   `sequence` in the JSON response, but cosmos-sdk omits `sequence` when
   it's zero (fresh accounts that haven't sent a tx). Treat missing
   sequence as 0; only reject when account_number itself is absent.
   Same fix in supernode-setup.sh's jq pipeline.

3. authAccountPayloadTypeName walked the response via `range` over a
   map, which in Go has randomized iteration order — so a BaseAccount
   with a nested public_key.@type could randomly return
   /cosmos.crypto.secp256k1.PubKey instead of
   /cosmos.auth.v1beta1.BaseAccount. Prefer the direct @type/type key at
   each map level and only recurse when absent. Added a regression test
   that exercises the walk 50x to catch iteration-order bugs.

4. detectFunder previously fell back to the first non-multisig key when
   no valoper-matching key existed. On a multisig validator host that
   surfaces a low-balance key (hermes-relayer) as funder, which then
   fails on fixture creation with "insufficient funds". Introduce a
   errNoSingleSigValidatorFunder sentinel; runPrepare recognizes it and
   exits cleanly with a SKIP log — the multisig validator host has
   nothing meaningful to prepare anyway.

End-to-end: devnet-evmigrationp-prepare now completes with exit 0 across
all 5 validators — 4 produce 17-account fixture sets, validator_2 skips
gracefully.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small fixes required to land the offline-signing migration path
end-to-end against devnet:

1. generate-proof-payload is registered via AddQueryFlagsToCmd in the
   CLI, which does not include --keyring-backend. Drop the flag from
   the devnet harness call — for an on-chain multisig the pubkey is
   read from chain and the keyring isn't needed.

2. generate-proof-payload also needs --chain-id explicitly. The payload
   string that gets signed includes the chain id; without the flag
   pp.ChainID is empty, sign-proof signs over payload("",...), and the
   keeper's verifySecp256k1Sig reconstructs payload(ctx.ChainID(),...)
   and rejects the signature.

3. submit-proof needs --chain-id as well (standard AddTxFlagsToCmd
   requirement).

Also extend the graceful-skip behavior: loadAccounts now exits 0 with a
SKIP log when the accounts file is missing (as happens on a multisig
validator host where prepare skipped), so downstream estimate/migrate/
verify don't flip the whole pipeline red on a valid no-op.

End-to-end validation on the devnet:
  prepare:   exit 0 (val2 skipped, 4 others produce 17 legacy accts ea)
  upgrade v1.20.0: success, chain at v1.20.0-rc1
  estimate:  exit 0 (val2 skipped)
  migrate-all: exit 0; all four multisig fixtures (pre-evm-valN-msig)
    migrate via the offline 4-step flow and get cleaned up
  verify:    exit 0, "PASS: all 16 migrated legacy addresses are clean
    across all modules" on every active validator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e JSON shapes

Two related devnet-only enhancements so a multisig validator host can
fully participate in the evmigration test pipeline:

1. vote-all.sh: a multisig composite can't sign a gov vote as a single
   --from key. When the validator record's multisig.enabled is true,
   the script now generates an unsigned vote tx and drives the
   2-of-N offline flow through the shared multisig_sign_unsigned helper
   (exec'd inside the container via bash -s). DAEMON/KEYRING_BACKEND/
   CHAIN_ID are set and exported before sourcing common.sh because
   vote-all.sh is invoked outside the setup-script lineage that normally
   provides those.

2. tests_evmigration prepare mode: extend the earlier graceful-skip
   behavior with a bootstrap path. When detectFunder returns
   errNoSingleSigValidatorFunder, runPrepare now calls
   bootstrapMultisigFunder, which:
     - locates the on-chain-validator-matching multisig composite key
     - creates a dedicated prepare-funder-<composite-key-name> single-
       sig key (legacy coin-type 118) if absent
     - funds it with 800B ulume via a one-time 2-of-N bank send from
       the composite (buildUnsignedMultisigBankSendTx +
       signAndBroadcastMultisigTx)
   The 800B seed stays clear of the multisig's ~1T liquid genesis
   balance minus setup fees, and it dwarfs what prepare actually spends
   (~500M across 17 accounts + fixtures).
   The funder key name embeds the composite key name so the existing
   resolvePrepareAccountTag regex (validator[_-]?(\d+)) still matches
   → tag stays "val2".
   Falls back to the original SKIP behavior if bootstrap fails.

End-to-end on a fresh v1.11.1 devnet: all 5 validators now PREPARE
COMPLETE (val2 with 17 accounts via bootstrap, val1/3/4/5 with 18
including the pre-evm-valN-msig fixture).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
runMigrate and runMigrateAll each called queryAndLogMigrationStats()
(which logs + returns) and then printMigrationStats() (which queries
again and logs the same thing). Every stats checkpoint printed the
same `stats: migrated=… legacy=…` line twice back-to-back and ran the
RPC query twice. Drop the redundant printMigrationStats() calls at the
four sites that also capture the stats for later delta comparison.

printMigrationStats() stays for the progress-checkpoint calls inside
batch loops (migrate.go:129, :293) and estimate.go:86 where we just
want a current-state log without needing the stats back.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
prepare mode previously only recorded the validator operator plus the
generated pre-evm-* fixtures into accounts.json, leaving a handful of
single-sig infrastructure keys on each host as post-migration residue
(governance_key, sncli-account, and the bootstrap funder on multisig
hosts). These are normal secp256k1 accounts with mnemonics — they're
migratable using the exact same flow, we just weren't tracking them.

Adds a recordInfrastructureLegacyAccounts pass that runs after validator
recording and before fixture generation:
  - governance_key            (primary validator only)
  - sncli-account             (supernode-enabled validators)
  - prepare-funder-<composite>   (multisig validator hosts, dynamically
                                  discovered via findLocalMultisigValidator)

Each candidate is only recorded if:
  - the key exists in the local keyring
  - the mnemonic is persisted in the shared status registry
  - the address isn't already tracked in accounts.json
The resulting records carry IsLegacy=true/IsValidator=false, so migrate-
all picks them up automatically via its existing filter at
migrate.go:212. No changes to the migration path itself.

To make this work for the bootstrap funder, bootstrapMultisigFunder now
writes its (name, address, mnemonic) into the shared status registry
via a new appendStatusRegistryAccount helper, so the infra-record pass
can derive the coin-type 60 destination from the same seed at migrate
time.

Left out intentionally:
  - hermes-relayer: shared key across all 5 validators; migrating it
    from any one host would move it globally and break the IBC relayer.
  - supernode operator keys: different setup lifecycle (supernode-setup
    manages them, not validator-setup), and they frequently have an
    already-present eth_secp256k1 companion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
supernode-setup.sh did three `tx bank send` calls from the validator's
genesis account — to fund the supernode's own account, the sncli
account, and a migrated sncli destination. All three used a single-sig
--from lookup against GENESIS_ADDR, which silently failed on val2
(multisig host) with "cannot sign with offline keys." Script kept
going, so the supernode/sncli accounts never got funded, never sent a
tx, and never ended up on-chain on val2. The asymmetry with val1/3/4/5
is pure pre-EVM plumbing, not a migration-design issue.

Fix: introduce a bank_send_from_validator helper that checks
validator_is_multisig and routes to the 2-of-N offline flow via the
shared multisig_sign_unsigned helper (generate-only → sign×2 → multisign
→ broadcast). Single-sig hosts take the original plain `tx bank send`
path. All three call sites switch to the helper, preserving their
original error-handling semantics (best-effort for sncli migration,
fatal for the other two).

After this change, val2's supernode-setup produces an on-chain
supernode legacy account just like the other four hosts — giving the
supernode binary's own startup-migration code path a symmetric starting
state across all five validators. No changes to the supernode's
migration logic itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Added an end-of-verify summary that prints:
  - the global migration-stats snapshot (total_migrated, total_legacy,
    legacy_staked, validators_migrated, validators_legacy)
  - how many addresses this host actually verified
  - the list of addresses still on-chain as legacy (capped at 20 to
    keep the log readable, with a "... N more" suffix when truncated)

Runs before the PASS/FAIL report so it's visible on both outcomes.
Prior to this you had to run a separate `q evmigration migration-stats`
and `legacy-accounts` by hand to see what the pipeline left behind;
now it's right there at the end of the verify log.

Implementation notes:
  - New queryLegacyAccountAddresses helper in query_migration.go wraps
    the `q evmigration legacy-accounts` CLI, reusing the existing run()
    retry machinery for flag-variant handling.
  - verify.go gets logVerifyFinalSummary which calls both queries and
    fails soft on either error (logs a WARN and continues) so a
    transient RPC blip doesn't mask the real verify result.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In the previous fresh-pipeline run the recordInfrastructureLegacyAccounts
pass logged governance_key on val1 and prepare-funder on val2, but
silently skipped sncli-account on every host. Root cause was timing:
sncli-account is provisioned by supernode-setup.sh, which runs in
parallel with prepare inside the same container. Prepare hit the
recording step at T+0s (right after validator recording), but
supernode-setup didn't finish creating/registering sncli-account until
~T+5min — the sncli key simply wasn't in the keyring yet when we looked.

Two changes:

1. Move the recording pass from the start of runPrepare to just before
   validatePreparedState, i.e. after the phase-1 + phase-2 activity
   loops have already spent ~5 minutes creating delegations, grants,
   claims, etc. By that point supernode-setup has had ample time to
   finish.

2. Add a best-effort bounded poll inside
   recordInfrastructureLegacyAccounts. For each candidate, if it isn't
   yet in the keyring + status registry on first check, wait up to
   90s polling every 3s. Candidates that are completely absent from
   both sources (e.g. governance_key on a secondary validator) return
   false immediately without sleeping, so non-applicable candidates
   don't cost real time.

Together these make the recording robust against the supernode-setup
race without adding a hard ordering dependency between the two scripts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@roomote-v0 roomote-v0 Bot left a comment

Choose a reason for hiding this comment

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

All 16 previously flagged issues are resolved. The 8 new commits since e09830d add multisig-validator support to devnet scripts and evmigration test tooling with no new issues. Approving.

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