Conversation
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 Issues
Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues. |
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 settingBIN_DIR=\"\"and continuing, or adding an explicit--no-bin-dir/--skip-binariesmode.
#!/bin/bash
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- 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>
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>
…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>
There was a problem hiding this comment.
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.
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.mdNew Modules
x/vm(Cosmos EVM v0.6.0)x/feemarket0.0005 ulume/gas) and ~6.25% per-block adjustmentx/precisebankulume↔ 18-decimalalumebridge:EVMBalance(a) = I(a) × 10¹² + F(a)x/erc20x/evmigrationKey Changes
Runtime & Execution
app/evm/ante.go): deterministic tx routing based onExtensionOptions[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 !testindefaults_prod.go)app/evm_mempool.go): Ethereum-like sender ordering, nonce-gap handling, same-nonce replacement with bump rules,max_txs=5000enabled by defaultapp/evm_broadcast.go): decouples txpoolrunReorgpromotion from CometBFTCheckTxvia bounded channel + single background worker, preventing mutex re-entry deadlock — a novel fix no other Cosmos EVM chain has publicly addressedRegisterTxServiceoverride (app/evm_runtime.go): captures the local CometBFT client for the broadcast worker, replacing the stale HTTP client from pre-CometBFTSetClientCtxGetSignersforMsgEthereumTx+ safe early-RPC keeper coin info initialization (SetKeeperDefaults) to prevent panics before genesiswasmd v0.61.6and EVM run in the same runtime — Lumera is the only Cosmos chain shipping both simultaneouslyChain Configuration
76857769eth_secp256k1(default), BIP44 coin type60— MetaMask/Ledger compatible out of the box0.0025 ulume/gas, min gas price floor0.0005 ulume/gas(prevents zero-fee spam that hit Evmos), change denominator16(~6.25%/block vs upstream8at ~12.5%)25,000,000config/bank_metadata.go):ulume/lume/alumemetadata +RegisterExtraInterfacesforeth_secp256k1crypto across all SDK+EVM pathscmd/lumera/cmd/config_migrate.go): pre-EVMapp.tomlfiles auto-gain[evm],[json-rpc], and[evm.mempool]sections with correct chain ID on first startup — no manual interventionPrecompiles (11 static)
Standard (8): P256, Bech32, Staking, Distribution, ICS20, Bank, Gov, Slashing
Custom Lumera precompiles:
0x0901): 11 methods — cascade/sense request+finalize, approve, fee queries, paginated action queries. Seeaction-precompile.mdfor full ABI reference.0x0902): 12 methods — register/deregister, state transitions, metrics reporting (caller-bound auth), XOR distance ranking. Seesupernode-precompile.mdfor full ABI reference.0x0903): 4 methods —execute,query,contractInfo,rawQuery. The EVM→CosmWasm direction of the cross-runtime bridge. Seewasm-precompile.mdfor 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.
IWasmat0x0903)execute/query/contractInfo/rawQueryon any CosmWasm contractevm_callviaCosmosMsg::CustomandQueryRequest::CustomPhase 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.solIBC & 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/allowlistdefault/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
eth,net,web3,txpool,debug,personal,minernamespaces; mainnet auto-blocksdebug/personal/adminviajsonrpc_policy.goapp/evm_jsonrpc_ratelimit.go): token bucket proxy —requests-per-second=50,burst=100,entry-ttl=5m, HTTP 429 with JSON-RPC-32005on limit, stale entries GC'd every 60sapp.toml [evm] tracer—json,struct,access_list,markdown; enablesdebug_traceTransaction,debug_traceBlockByNumber,debug_traceBlockByHash,debug_traceCallrpc_discoverJSON-RPC method (port 8545) +GET/POST /openrpc.jsonHTTP endpoint (port 1317) with CORS; gzip-compressed spec embedded in binary (315 KB → 20 KB); build-time generation viatools/openrpcgenwith Go reflection + AST parsing;POST /openrpc.jsonproxies to JSON-RPC server enabling OpenRPC Playground "Try It" directly; spec version fromgo.modviaruntime/debug.ReadBuildInfo()https://playground.open-rpc.org/?url=http://<node>:1317/openrpc.jsonAccount Migration (
x/evmigration)MsgClaimLegacyAccount: signed by neweth_secp256k1address; legacy signature =secp256k1_sign(SHA256("lumera-evm-migration:<chainID>:<evmChainID>:<legacy_address>:<new_address>"))MsgMigrateValidator: re-keys validator record + all associated module state including delegator referencesante/evmigration_fee_decorator.go): zero-fee migration txs since new address has no balanceenable_migration,migration_end_time,max_migrations_per_block=50,max_validator_delegations=2000MigrationRecord,MigrationRecords,MigrationEstimate(dry-run),MigrationStats,LegacyAccounts,MigratedAccountsUpgrade & Ops
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 policydocs/evm-integration/node-evm-config-guide.md—app.tomltuning,[evm]/[json-rpc]/[evm.mempool]sections, RPC exposure, tracer config, rate limiting, security policiesDevnet
devnet/default-config/devnet-genesis-evm.json)devnet/tests/validator/evm_test.go): fee market, cross-peer tx visibility, mixed Cosmos+EVM blocksdocs/evm-integration/remix-guide.md— MetaMask network config, contract deployment, interaction, event querying, troubleshootingBugs Found & Fixed (25 issues)
Documented in
docs/evm-integration/bugs.mdTest Coverage (~470 tests)
newHeads,logs,pendingTransactionsDevnet 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
main.mdtests.mdbugs.mdroadmap.mdsecurity-audit.mdtune-guide.mdnode-evm-config-guide.mdapp.tomltuning, security policies, rate limitingopenrpc-playground.mdremix-guide.mdprecompiles.mdaction-precompile.md0x0901) ABI reference, 11 methods, design notessupernode-precompile.md0x0902) ABI reference, 12 methods, design noteswasm-precompile.md0x0903) — full cross-runtime bridge architecture, Solidity+Rust interfaces, data flow, gas metering, reentrancy designevmigration-ui.mddevnet-tests.mdblock-explorer.mdlumera-ports.mdWhat Makes This Different
rpc_discover+/openrpc.json+ playground proxy0.0005 ulume/gas(prevents decay to zero)0x0901) + Supernode (0x0902) + Wasm (0x0903) at launchconfig_migrate.goadds[evm]section on startupGas Configuration
Roadmap Status
163/168 items complete (97%) across 15 phases.
Breaking Changes
secp256k1eth_secp256k1keys adddefaults to Ethereum-compatible keys11860feemarket,precisebank,vm,erc20,evmigration[evm]section[evm],[json-rpc],[evm.mempool]inapp.tomlconfig_migrate.go