diff --git a/devnet/go.mod b/devnet/go.mod index 97585ef3..7647ddbe 100644 --- a/devnet/go.mod +++ b/devnet/go.mod @@ -40,7 +40,7 @@ require ( github.com/ethereum/go-ethereum v1.17.0 github.com/gorilla/websocket v1.5.3 github.com/pelletier/go-toml/v2 v2.2.4 - golang.org/x/crypto v0.51.0 + golang.org/x/crypto v0.52.0 ) require ( diff --git a/devnet/go.sum b/devnet/go.sum index 8b5614f9..e60d2f26 100644 --- a/devnet/go.sum +++ b/devnet/go.sum @@ -1140,8 +1140,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/docs/design/2026-07-16-keyring-backend-resolution-design.md b/docs/design/2026-07-16-keyring-backend-resolution-design.md new file mode 100644 index 00000000..abbf850e --- /dev/null +++ b/docs/design/2026-07-16-keyring-backend-resolution-design.md @@ -0,0 +1,185 @@ +# Keyring-backend auto-resolution for evmigration scripts + +**Date:** 2026-07-16 +**Status:** Approved (design) +**Scope:** `scripts/evmigration-common.sh`, `scripts/migrate-account.sh`, +`scripts/migrate-validator.sh`, `scripts/migrate-multisig.sh`, +`tests/scripts/*.bats` + +## Motivation + +The migration scripts default `KEYRING_BACKEND` to `test` when the operator +does not pass `--keyring-backend`. This disagrees with bare `lumerad`, whose +built-in default is `os` (Cosmos SDK v0.53.6, +`client/config/config.go:15`). A real validator operator whose keys live in an +`os` or `file` keyring, running a migration script without the flag, gets +`--keyring-backend test` forced onto every `lumerad` call — so their key is not +found, or the wrong (empty) keyring is consulted. + +Operators already express their keyring choice once, in +`/config/client.toml` (`keyring-backend = "..."`). The scripts should +honor that instead of overriding it with a hardcoded `test`. + +### Related, already-fixed issue + +A separate bug in the same area was fixed on this branch +(`fix/evmigration-os-keyring-prompt`): the passphrase-prompt tee was gated on +`KEYRING_BACKEND == "file"`, so an `os` backend (which falls back to the +encrypted file store on a headless host and prompts identically) hit the +`else` branch that redirected stderr to a temp file, hiding the prompt and +appearing to hang. That fix introduced `_keyring_prompts_for_passphrase` +(`!= "test"`). This design builds on it. + +## Resolution order + +`resolve_keyring_backend` picks the effective backend, first hit wins: + +1. **`--keyring-backend` flag** (explicit) — unchanged behavior. +1a. **`$LUMERA_KEYRING_BACKEND`** (added post-review, PR #193): the same env + override `lumerad` itself honors, and the script convention shared with + `$LUMERA_NODE` / `$LUMERA_CHAIN_ID`. Must outrank `client.toml`/disk + because the resolved value is passed to `lumerad` as an explicit flag, + which would otherwise override the env for the child process. +2. **`client.toml`** — `keyring-backend = "..."` read from + `/config/client.toml`. `--home` selects the home; `--keyring-dir` + does **not** move `client.toml`. +3. **On-disk detection** — under `--keyring-dir` (else `--home`): + `keyring-test/` present → `test`; `keyring-file/` present → `file` + (Cosmos SDK subdir names, `crypto/keyring/keyring.go:42-43`). `os` is not + detectable on disk (it uses the OS secret store), so it is not inferred + here. +4. **`os`** — final fallback, matching the SDK default. + +The chosen value and its source are logged (e.g. +`keyring backend: os (from client.toml)`), mirroring `resolve_chain_id`, so the +operator sees the decision before anything signs. + +## Section A — Resolution mechanism (common lib) + +New standalone helper in `evmigration-common.sh`, called by each entry script +after flag parsing (same pattern as `resolve_chain_id`, which is why +`parse_common_flags` stays a pure parser with no filesystem I/O): + +```bash +resolve_keyring_backend() { + if (( KEYRING_BACKEND_EXPLICIT == 1 )); then + log_info "keyring backend: $KEYRING_BACKEND (from --keyring-backend)" + return 0 + fi + local home="${HOME_DIR:-$HOME/.lumera}" + local client_toml="$home/config/client.toml" v + if [[ -f "$client_toml" ]]; then + v=$(sed -n 's/^[[:space:]]*keyring-backend[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \ + "$client_toml" | head -n1) + if [[ -n "$v" ]]; then + KEYRING_BACKEND="$v" + log_info "keyring backend: $v (from $client_toml)" + return 0 + fi + fi + local kr="${KEYRING_DIR:-$home}" + if [[ -d "$kr/keyring-test" ]]; then + KEYRING_BACKEND="test"; log_info "keyring backend: test (detected keyring-test/ in $kr)"; return 0 + fi + if [[ -d "$kr/keyring-file" ]]; then + KEYRING_BACKEND="file"; log_info "keyring backend: file (detected keyring-file/ in $kr)"; return 0 + fi + KEYRING_BACKEND="os" + log_info "keyring backend: os (SDK default; no --keyring-backend, client.toml, or keyring dir found)" +} +``` + +`parse_common_flags` change: + +- Add global `KEYRING_BACKEND_EXPLICIT` (declared with the other globals, reset + to `0` at the top of `parse_common_flags`). +- In the `--keyring-backend` case, set `KEYRING_BACKEND_EXPLICIT=1` in addition + to assigning the value. +- Keep the `KEYRING_BACKEND="test"` default as the pre-resolution baseline; + resolution only occurs when an entry script calls `resolve_keyring_backend`, + so the existing `parse_common_flags populates defaults` test is unaffected. + +Entry-script wiring: `migrate-account.sh` and `migrate-validator.sh` call +`resolve_keyring_backend` immediately after `parse_common_flags` (before +`resolve_address`, so the resolved backend is used for key lookups). + +### Trusting the client.toml value + +Any non-empty value read from `client.toml` is honored verbatim (the operator +configured it; `lumerad` itself would use the same value). No allow-list check +— an invalid value would be rejected by `lumerad` downstream with its own +error, same as running `lumerad` directly. + +## Section B — Multisig integration + +`migrate-multisig.sh` does not use `parse_common_flags`; it has three +subcommand parsers (around lines 39, 260, 545), each with +`local keyring_backend="test"`. For each parser: + +1. Track whether `--keyring-backend` was passed (local `kb_explicit`). +2. After parsing, set the globals the resolver reads: `KEYRING_BACKEND`, + `KEYRING_BACKEND_EXPLICIT`, `HOME_DIR`, `KEYRING_DIR`. +3. Call `resolve_keyring_backend`. +4. Read the resolved `KEYRING_BACKEND` back into the local `keyring_backend`. + +The subcommands that re-invoke `migrate-account.sh` / `migrate-validator.sh` +already pass `--keyring-backend "$keyring_backend"`; with the resolved value +this becomes an explicit flag to the child, so the child does not re-resolve. + +## Section C — Related cleanup + +Replace the two entry-script advisory guards +`[[ "$KEYRING_BACKEND" == "file" ]]` +(`migrate-validator.sh:70`, `migrate-account.sh:56`) with +`_keyring_prompts_for_passphrase`, so the "keyring may prompt; input hidden" +hint is shown for `os` as well as `file`. + +## Section D — Testing (bats) and coverage audit + +The canonical suite is `tests/scripts/*.bats`, wired into the Makefile +(`bats tests/scripts/`). `scripts/evmigration-common_test.sh` is a stale +hand-rolled duplicate that nothing runs. + +1. **Reconcile:** delete `scripts/evmigration-common_test.sh` and revert the + `_keyring_prompts_for_passphrase` cases that were added to it, porting that + coverage into `tests/scripts/common.bats`. +2. **New `common.bats` tests:** + - `_keyring_prompts_for_passphrase`: `test` → false, `file` → true, + `os` → true, unset → false. + - `resolve_keyring_backend`: + - explicit flag wins (ignores `client.toml`); + - reads value from a seeded `client.toml`; + - `client.toml` takes precedence over an on-disk `keyring-test/`; + - disk detection: `keyring-test/` → `test`, `keyring-file/` → `file` + (no `client.toml`); + - `--keyring-dir` is used for detection when set; + - `client.toml` is read from `--home` even when `--keyring-dir` differs; + - empty home → `os` fallback. +3. **Migration-script coverage gaps (audit result), proposed additions:** + - **`os`-backend prompt regression** (the originating bug): assert the sign + step tees the passphrase prompt to the terminal rather than hiding it. + Highest-value gap — currently unguarded. + - Non-`test` backend end-to-end: one dry-run case each in + `migrate-account.bats` / `migrate-validator.bats` with a seeded + `client.toml`, confirming resolution flows through. + - `migrate-multisig.bats`: a backend-resolution case covering Section B. + +## Edge cases + +- `--keyring-dir` set, `--home` unset: `client.toml` read from + `$HOME/.lumera/config/`; detection from the keyring dir. Matches SDK + behavior. +- CI/devnet that imports keys into a `test` keyring first creates a + `keyring-test/` dir, so detection returns `test` before the `os` fallback is + reached — existing scripted `test` flows are preserved without passing the + flag. +- Truly empty home with no flag and no `client.toml`: resolves to `os` + (accepted decision), which then uses the already-hardened passphrase-prompt + path. + +## Out of scope + +- Changing `lumerad`'s own defaults or `client.toml` handling. +- Detecting `os` from disk (not reliably possible). +- Validating/normalizing unusual backends (`kwallet`, `pass`) beyond passing + them through. diff --git a/docs/design/2026-07-16-keyring-backend-resolution-plan.md b/docs/design/2026-07-16-keyring-backend-resolution-plan.md new file mode 100644 index 00000000..4f075980 --- /dev/null +++ b/docs/design/2026-07-16-keyring-backend-resolution-plan.md @@ -0,0 +1,569 @@ +# Keyring-backend Auto-Resolution Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make the evmigration migration scripts resolve the keyring backend from `--keyring-backend` → `client.toml` → on-disk detection → `os`, instead of hardcoding `test`. + +**Architecture:** A new standalone `resolve_keyring_backend` helper in `scripts/evmigration-common.sh` (mirroring the existing `resolve_chain_id` idiom) is called by each entry script after flag parsing. `parse_common_flags` gains a `KEYRING_BACKEND_EXPLICIT` flag so the resolver can tell an explicit `--keyring-backend test` from the default. `migrate-multisig.sh` (which has its own parsers) wires the same resolver into its three subcommands. Test coverage is consolidated into the Makefile-wired `tests/scripts/*.bats` suite and the stale hand-rolled `scripts/evmigration-common_test.sh` is removed. + +**Tech Stack:** Bash, bats 1.2.1 (`tests/scripts/`), shellcheck, Cosmos SDK v0.53.6 keyring/client-config conventions. + +**Spec:** `docs/design/2026-07-16-keyring-backend-resolution-design.md` + +## Global Constraints + +- Target shell: bash; scripts set `set -euo pipefail` and `IFS=$'\n\t'` — any new global must be initialized at source time so `-u` never trips. +- `client.toml` lives at `/config/client.toml`; `--keyring-dir` does NOT move it. Home default when `--home` unset: `$HOME/.lumera`. +- On-disk keyring subdirs (Cosmos SDK): `test` → `keyring-test/`, `file` → `keyring-file/`. `os` is not detectable on disk. +- Final fallback backend: `os` (matches bare `lumerad`). +- Resolution logs its choice and source via `log_info` to stderr, like `resolve_chain_id`. +- Canonical test suite: `tests/scripts/*.bats`, run by `bats tests/scripts/` (Makefile). `scripts/evmigration-common_test.sh` is stale and unwired. +- `make lint` must pass cleanly (shellcheck `-x` on `${MIGRATION_SCRIPTS}` + golangci-lint). 0 issues. +- Existing helper `_keyring_prompts_for_passphrase` (`!= "test"`) already exists in `evmigration-common.sh` (committed on this branch). + +--- + +### Task 1: Consolidate helper unit tests into bats; delete stale test file + +Moves the gas-helper tests (currently only in the stale file) and the `_keyring_prompts_for_passphrase` tests into the canonical `common.bats`, then deletes the stale duplicate. No production code changes — pure test-coverage move, so the ported tests pass immediately (they document already-shipped behavior). + +**Files:** +- Modify: `tests/scripts/common.bats` (append new `@test` blocks before EOF) +- Delete: `scripts/evmigration-common_test.sh` + +**Interfaces:** +- Consumes: `migration_gas_for_records`, `gas_exceeds_block_limit`, `_keyring_prompts_for_passphrase` (all already defined in `evmigration-common.sh`). +- Produces: nothing consumed by later tasks. + +- [ ] **Step 1: Append the ported tests to `common.bats`** + +Add at the end of `tests/scripts/common.bats`: + +```bash +@test "migration_gas_for_records: base with zero records" { + [ "$(migration_gas_for_records 0)" = "6000000" ] +} + +@test "migration_gas_for_records: base plus per-record marginal" { + [ "$(migration_gas_for_records 1597)" = "2401500000" ] + [ "$(migration_gas_for_records 2500)" = "3756000000" ] +} + +@test "gas_exceeds_block_limit: true only when over a positive limit" { + run gas_exceeds_block_limit 30000000 25000000 + [ "$status" -eq 0 ] + run gas_exceeds_block_limit 11379000 25000000 + [ "$status" -eq 1 ] + run gas_exceeds_block_limit 99999999 -1 + [ "$status" -eq 1 ] + run gas_exceeds_block_limit 99999999 "" + [ "$status" -eq 1 ] +} + +@test "_keyring_prompts_for_passphrase: test backend is silent" { + KEYRING_BACKEND=test + run _keyring_prompts_for_passphrase + [ "$status" -eq 1 ] +} + +@test "_keyring_prompts_for_passphrase: file and os backends prompt" { + KEYRING_BACKEND=file + run _keyring_prompts_for_passphrase + [ "$status" -eq 0 ] + KEYRING_BACKEND=os + run _keyring_prompts_for_passphrase + [ "$status" -eq 0 ] +} + +@test "_keyring_prompts_for_passphrase: unset defaults to silent" { + unset KEYRING_BACKEND + run _keyring_prompts_for_passphrase + [ "$status" -eq 1 ] +} +``` + +- [ ] **Step 2: Delete the stale hand-rolled test file** + +Run: `git rm scripts/evmigration-common_test.sh` +Expected: file staged for deletion. + +- [ ] **Step 3: Run the bats suite to verify the ported tests pass** + +Run: `bats tests/scripts/common.bats` +Expected: PASS — all tests green, including the 6 new blocks. + +- [ ] **Step 4: Run shellcheck on scripts to confirm nothing references the deleted file** + +Run: `make lint` +Expected: `0 issues.` (The Makefile's `MIGRATION_SCRIPTS` list must no longer include the deleted file — if shellcheck errors that the file is missing, remove it from the `MIGRATION_SCRIPTS` variable in the `Makefile` and re-run.) + +- [ ] **Step 5: Commit** + +```bash +git add tests/scripts/common.bats +git commit -m "test(evmigration): consolidate helper tests into bats; drop stale test file" +``` + +--- + +### Task 2: Add `resolve_keyring_backend` and explicit-flag tracking + +Adds the resolver to the common lib and the `KEYRING_BACKEND_EXPLICIT` global that lets it distinguish an explicit flag from the default. + +**Files:** +- Modify: `scripts/evmigration-common.sh` (globals block ~line 40; `parse_common_flags`; new function after `_keyring_prompts_for_passphrase`) +- Test: `tests/scripts/common.bats` + +**Interfaces:** +- Consumes: globals `KEYRING_BACKEND`, `HOME_DIR`, `KEYRING_DIR`, `log_info`. +- Produces: + - global `KEYRING_BACKEND_EXPLICIT` (int, `0`/`1`; initialized to `0` at source time and reset in `parse_common_flags`; set to `1` when `--keyring-backend` is parsed). + - `resolve_keyring_backend()` — reads the globals above and sets `KEYRING_BACKEND` to the resolved value (`test`/`file`/`os`/verbatim client.toml value). No args, no stdout, logs to stderr. + +- [ ] **Step 1: Write the failing tests in `common.bats`** + +Append to `tests/scripts/common.bats`: + +```bash +@test "resolve_keyring_backend: explicit flag wins over client.toml" { + local home; home=$(mktemp -d) + mkdir -p "$home/config" + printf 'keyring-backend = "file"\n' > "$home/config/client.toml" + KEYRING_BACKEND=os + KEYRING_BACKEND_EXPLICIT=1 + HOME_DIR="$home" + KEYRING_DIR="" + resolve_keyring_backend + [ "$KEYRING_BACKEND" = "os" ] +} + +@test "resolve_keyring_backend: reads value from client.toml" { + local home; home=$(mktemp -d) + mkdir -p "$home/config" + printf 'chain-id = "x"\nkeyring-backend = "file"\noutput = "text"\n' > "$home/config/client.toml" + KEYRING_BACKEND=test + KEYRING_BACKEND_EXPLICIT=0 + HOME_DIR="$home" + KEYRING_DIR="" + resolve_keyring_backend + [ "$KEYRING_BACKEND" = "file" ] +} + +@test "resolve_keyring_backend: client.toml wins over on-disk keyring-test dir" { + local home; home=$(mktemp -d) + mkdir -p "$home/config" "$home/keyring-test" + printf 'keyring-backend = "os"\n' > "$home/config/client.toml" + KEYRING_BACKEND=test + KEYRING_BACKEND_EXPLICIT=0 + HOME_DIR="$home" + KEYRING_DIR="" + resolve_keyring_backend + [ "$KEYRING_BACKEND" = "os" ] +} + +@test "resolve_keyring_backend: detects test from keyring-test dir" { + local home; home=$(mktemp -d) + mkdir -p "$home/keyring-test" + KEYRING_BACKEND="" + KEYRING_BACKEND_EXPLICIT=0 + HOME_DIR="$home" + KEYRING_DIR="" + resolve_keyring_backend + [ "$KEYRING_BACKEND" = "test" ] +} + +@test "resolve_keyring_backend: detects file from keyring-file dir" { + local home; home=$(mktemp -d) + mkdir -p "$home/keyring-file" + KEYRING_BACKEND="" + KEYRING_BACKEND_EXPLICIT=0 + HOME_DIR="$home" + KEYRING_DIR="" + resolve_keyring_backend + [ "$KEYRING_BACKEND" = "file" ] +} + +@test "resolve_keyring_backend: uses --keyring-dir for detection" { + local home kr; home=$(mktemp -d); kr=$(mktemp -d) + mkdir -p "$kr/keyring-file" + KEYRING_BACKEND="" + KEYRING_BACKEND_EXPLICIT=0 + HOME_DIR="$home" + KEYRING_DIR="$kr" + resolve_keyring_backend + [ "$KEYRING_BACKEND" = "file" ] +} + +@test "resolve_keyring_backend: client.toml read from --home even when keyring-dir differs" { + local home kr; home=$(mktemp -d); kr=$(mktemp -d) + mkdir -p "$home/config" + printf 'keyring-backend = "file"\n' > "$home/config/client.toml" + KEYRING_BACKEND="" + KEYRING_BACKEND_EXPLICIT=0 + HOME_DIR="$home" + KEYRING_DIR="$kr" + resolve_keyring_backend + [ "$KEYRING_BACKEND" = "file" ] +} + +@test "resolve_keyring_backend: empty home falls back to os" { + local home; home=$(mktemp -d) + KEYRING_BACKEND="" + KEYRING_BACKEND_EXPLICIT=0 + HOME_DIR="$home" + KEYRING_DIR="" + resolve_keyring_backend + [ "$KEYRING_BACKEND" = "os" ] +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `bats tests/scripts/common.bats -f resolve_keyring_backend` +Expected: FAIL — `resolve_keyring_backend: command not found` (and/or `KEYRING_BACKEND_EXPLICIT: unbound variable`). + +- [ ] **Step 3: Add the `KEYRING_BACKEND_EXPLICIT` global** + +In `scripts/evmigration-common.sh`, in the globals block near the other `# shellcheck disable=SC2034` declarations (just after the `KEYRING_BACKEND="test"` global around line 32), add: + +```bash +# shellcheck disable=SC2034 +# 1 once --keyring-backend is passed explicitly; gates resolve_keyring_backend. +KEYRING_BACKEND_EXPLICIT=0 +``` + +- [ ] **Step 4: Reset and set the flag in `parse_common_flags`** + +In `parse_common_flags`, in the reset block (where `KEYRING_BACKEND="test"` is reassigned), add right after it: + +```bash + KEYRING_BACKEND_EXPLICIT=0 +``` + +Then change the `--keyring-backend` case from: + +```bash + --keyring-backend) _require_value "$1" "$#" "${2-}"; KEYRING_BACKEND="$2"; shift 2 ;; +``` + +to: + +```bash + --keyring-backend) _require_value "$1" "$#" "${2-}"; KEYRING_BACKEND="$2"; KEYRING_BACKEND_EXPLICIT=1; shift 2 ;; +``` + +- [ ] **Step 5: Implement `resolve_keyring_backend`** + +In `scripts/evmigration-common.sh`, immediately after the `_keyring_prompts_for_passphrase` function, add: + +```bash +# resolve_keyring_backend +# Pin the effective keyring backend and log its source, when the user did not +# pass --keyring-backend. Resolution order (first hit wins): +# 1. explicit --keyring-backend (KEYRING_BACKEND_EXPLICIT=1) +# 2. keyring-backend from /config/client.toml (--home selects home; +# --keyring-dir does NOT move client.toml) +# 3. on-disk detection under --keyring-dir (else --home): +# keyring-test/ -> test, keyring-file/ -> file (os is not on-disk-detectable) +# 4. os — the Cosmos SDK default +# Mirrors resolve_chain_id: logs the decision so the operator sees it before signing. +resolve_keyring_backend() { + if (( KEYRING_BACKEND_EXPLICIT == 1 )); then + log_info "keyring backend: $KEYRING_BACKEND (from --keyring-backend)" + return 0 + fi + + local home="${HOME_DIR:-$HOME/.lumera}" + local client_toml="$home/config/client.toml" v + if [[ -f "$client_toml" ]]; then + v=$(sed -n 's/^[[:space:]]*keyring-backend[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \ + "$client_toml" | head -n1) + if [[ -n "$v" ]]; then + KEYRING_BACKEND="$v" + log_info "keyring backend: $v (from $client_toml)" + return 0 + fi + fi + + local kr="${KEYRING_DIR:-$home}" + if [[ -d "$kr/keyring-test" ]]; then + KEYRING_BACKEND="test" + log_info "keyring backend: test (detected keyring-test/ in $kr)" + return 0 + fi + if [[ -d "$kr/keyring-file" ]]; then + KEYRING_BACKEND="file" + log_info "keyring backend: file (detected keyring-file/ in $kr)" + return 0 + fi + + KEYRING_BACKEND="os" + log_info "keyring backend: os (SDK default; no --keyring-backend, client.toml, or keyring dir found)" +} +``` + +- [ ] **Step 6: Run tests to verify they pass** + +Run: `bats tests/scripts/common.bats -f resolve_keyring_backend` +Expected: PASS — all 8 `resolve_keyring_backend` tests green. + +- [ ] **Step 7: Run the full common suite + lint** + +Run: `bats tests/scripts/common.bats && make lint` +Expected: all common.bats tests PASS (including the unchanged `parse_common_flags populates defaults`); `0 issues.` + +- [ ] **Step 8: Commit** + +```bash +git add scripts/evmigration-common.sh tests/scripts/common.bats +git commit -m "feat(evmigration): resolve keyring backend from client.toml/disk, default os" +``` + +--- + +### Task 3: Wire resolver into account/validator scripts + advisory cleanup + +Calls `resolve_keyring_backend` in the two `parse_common_flags`-based entry scripts and generalizes their `== "file"` advisory guards. Adds an end-to-end resolution test and a `lumerad_tx` branch regression test. + +**Files:** +- Modify: `scripts/migrate-account.sh` (after `parse_common_flags`; advisory guard ~line 56) +- Modify: `scripts/migrate-validator.sh` (after `parse_common_flags`; advisory guard ~line 70) +- Test: `tests/scripts/migrate-account.bats`, `tests/scripts/migrate-validator.bats`, `tests/scripts/common.bats` + +**Interfaces:** +- Consumes: `resolve_keyring_backend`, `_keyring_prompts_for_passphrase` (Task 2 / existing). +- Produces: nothing new consumed by later tasks. + +- [ ] **Step 1: Write the failing e2e + regression tests** + +Append to `tests/scripts/migrate-account.bats`: + +```bash +@test "migrate-account.sh resolves keyring backend from client.toml" { + local home; home=$(mktemp -d) + mkdir -p "$home/config" + printf 'keyring-backend = "file"\n' > "$home/config/client.toml" + run "$SCRIPTS_DIR/migrate-account.sh" \ + --binary "$SHIM" --chain-id shim-test --home "$home" \ + --dry-run --yes legacykey newkey + [ "$status" -eq 0 ] + [[ "$output" == *"keyring backend: file (from $home/config/client.toml)"* ]] +} +``` + +Append to `tests/scripts/common.bats`: + +```bash +@test "lumerad_tx announces keyring unlock for a prompting backend" { + setup_shim + run bash -c ' + source '"$SCRIPTS_DIR"'/evmigration-common.sh + BIN='"$SHIM_BIN"' + NODE="tcp://example:1234" + CHAIN_ID="shim-test" + KEYRING_BACKEND="os" + KEYRING_BACKEND_EXPLICIT=1 + lumerad_tx evmigration migrate-validator k1 k2 --yes 2>&1 1>/dev/null || true + ' + [[ "$output" == *"unlocking keyring to sign and simulate"* ]] +} + +@test "lumerad_tx stays quiet about unlock for the test backend" { + setup_shim + run bash -c ' + source '"$SCRIPTS_DIR"'/evmigration-common.sh + BIN='"$SHIM_BIN"' + NODE="tcp://example:1234" + CHAIN_ID="shim-test" + KEYRING_BACKEND="test" + KEYRING_BACKEND_EXPLICIT=1 + lumerad_tx evmigration migrate-validator k1 k2 --yes 2>&1 1>/dev/null || true + ' + [[ "$output" != *"unlocking keyring to sign and simulate"* ]] +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `bats tests/scripts/migrate-account.bats -f "resolves keyring backend" && bats tests/scripts/common.bats -f "lumerad_tx announces"` +Expected: the migrate-account test FAILS (no `keyring backend:` line — resolver not called yet). The `lumerad_tx announces` test PASSES already (the committed fix uses `_keyring_prompts_for_passphrase`); the `stays quiet` test PASSES too. (These two are regression guards for shipped behavior — that they pass now is correct.) + +- [ ] **Step 3: Call `resolve_keyring_backend` in `migrate-account.sh`** + +In `scripts/migrate-account.sh`, immediately after `parse_common_flags "$@"` (before `log_run_summary`), add: + +```bash + resolve_keyring_backend +``` + +- [ ] **Step 4: Generalize the account advisory guard** + +In `scripts/migrate-account.sh`, change: + +```bash + if [[ "$KEYRING_BACKEND" == "file" ]]; then + log_info "the encrypted keyring may prompt once for each key; input is hidden while typing" + fi +``` + +to: + +```bash + if _keyring_prompts_for_passphrase; then + log_info "the encrypted keyring may prompt once for each key; input is hidden while typing" + fi +``` + +- [ ] **Step 5: Repeat Steps 3–4 for `migrate-validator.sh`** + +In `scripts/migrate-validator.sh`, add `resolve_keyring_backend` immediately after `parse_common_flags "$@"`, and change the identical `[[ "$KEYRING_BACKEND" == "file" ]]` advisory guard (near line 70) to `if _keyring_prompts_for_passphrase; then`. + +- [ ] **Step 6: Run the affected suites** + +Run: `bats tests/scripts/migrate-account.bats tests/scripts/migrate-validator.bats tests/scripts/common.bats` +Expected: PASS — including the new migrate-account resolution test now that the resolver is wired in. + +- [ ] **Step 7: Lint** + +Run: `make lint` +Expected: `0 issues.` + +- [ ] **Step 8: Commit** + +```bash +git add scripts/migrate-account.sh scripts/migrate-validator.sh tests/scripts/migrate-account.bats tests/scripts/common.bats +git commit -m "feat(evmigration): auto-resolve keyring backend in account/validator scripts" +``` + +--- + +### Task 4: Wire resolver into `migrate-multisig.sh` + +`migrate-multisig.sh` has three subcommand parsers, each with `local keyring_backend="test"` and its own `--keyring-backend` flag. Each must resolve when the flag is absent. + +**Files:** +- Modify: `scripts/migrate-multisig.sh` (three parsers ~lines 39, 260, 545; and the point after each where `KEYRING_BACKEND="$keyring_backend"` is set ~lines 114, 338, 620) +- Test: `tests/scripts/migrate-multisig.bats` + +**Interfaces:** +- Consumes: `resolve_keyring_backend`, globals `KEYRING_BACKEND`, `KEYRING_BACKEND_EXPLICIT`, `HOME_DIR`, `KEYRING_DIR`. +- Produces: nothing consumed by later tasks. + +- [ ] **Step 1: Write the failing test** + +Append to `tests/scripts/migrate-multisig.bats` (match the invocation style already used in that file — inspect an existing `@test` there for the exact subcommand, shim env vars, and required flags, and mirror it; the assertion below is the new part): + +```bash +@test "migrate-multisig.sh resolves keyring backend from client.toml" { + local home; home=$(mktemp -d) + mkdir -p "$home/config" + printf 'keyring-backend = "file"\n' > "$home/config/client.toml" + # Use the same subcommand + shim setup as the existing dry-run/preview test + # in this file, adding: --home "$home" and NO --keyring-backend flag. + run "$SCRIPTS_DIR/migrate-multisig.sh" \ + --home "$home" + [[ "$output" == *"keyring backend: file (from $home/config/client.toml)"* ]] +} +``` + +Note: before writing this test, open `tests/scripts/migrate-multisig.bats` and copy the exact subcommand name, shim binary flag (`--binary "$SHIM"` or the file's local equivalent), and any `SHIM_*`/fixture env vars from the lightest-weight existing passing test, so this test reaches the resolver without tripping unrelated validation. + +- [ ] **Step 2: Run to verify it fails** + +Run: `bats tests/scripts/migrate-multisig.bats -f "resolves keyring backend"` +Expected: FAIL — no `keyring backend: file (from ...)` line in output. + +- [ ] **Step 3: Add explicit-flag tracking to each parser** + +In `scripts/migrate-multisig.sh`, for each of the three parsers, change the local default: + +```bash + local keyring_backend="test" keyring_dir="" home_dir="" +``` + +to add an explicit tracker: + +```bash + local keyring_backend="test" keyring_dir="" home_dir="" kb_explicit=0 +``` + +and in each parser's `--keyring-backend` case, set the tracker: + +```bash + --keyring-backend) _require_value "$1" "$#" "${2-}"; keyring_backend="$2"; kb_explicit=1; shift 2 ;; +``` + +- [ ] **Step 4: Resolve after each parser populates its locals** + +In each of the three flows, at the point where `KEYRING_BACKEND="$keyring_backend"` is set (and where `keyring_dir`/`home_dir` are known), replace that single assignment with: + +```bash + KEYRING_BACKEND="$keyring_backend" + KEYRING_BACKEND_EXPLICIT="$kb_explicit" + HOME_DIR="$home_dir" + KEYRING_DIR="$keyring_dir" + resolve_keyring_backend + keyring_backend="$KEYRING_BACKEND" +``` + +This keeps the local `keyring_backend` (used later to build `--keyring-backend "$keyring_backend"` args for sub-invocations and sub-lumerad calls) equal to the resolved value, so re-invoked child scripts receive it as an explicit flag and do not re-resolve. + +- [ ] **Step 5: Run the multisig suite** + +Run: `bats tests/scripts/migrate-multisig.bats` +Expected: PASS — the new resolution test plus all existing multisig tests (which pass `--keyring-backend` explicitly and so still see their chosen backend). + +- [ ] **Step 6: Lint** + +Run: `make lint` +Expected: `0 issues.` + +- [ ] **Step 7: Commit** + +```bash +git add scripts/migrate-multisig.sh tests/scripts/migrate-multisig.bats +git commit -m "feat(evmigration): auto-resolve keyring backend in migrate-multisig subcommands" +``` + +--- + +### Task 5: Full-suite verification + +**Files:** none (verification only) + +- [ ] **Step 1: Run the entire scripts test suite** + +Run: `bats tests/scripts/` +Expected: PASS — every file green. + +- [ ] **Step 2: Run lint** + +Run: `make lint` +Expected: `0 issues.` + +- [ ] **Step 3: Sanity-check the resolution log end-to-end (no flag, no client.toml)** + +Run: +```bash +scripts/migrate-account.sh --binary tests/scripts/fixtures/lumerad-shim.sh \ + --chain-id shim-test --home "$(mktemp -d)" --dry-run --yes legacykey newkey 2>&1 \ + | grep 'keyring backend:' +``` +Expected: a line `keyring backend: os (SDK default; ...)` — confirming the fallback fires when nothing is configured. + +--- + +## Self-Review + +**Spec coverage:** +- Section A (resolver + explicit tracking) → Task 2. ✓ +- Section B (multisig three parsers) → Task 4. ✓ +- Section C (advisory guard cleanup) → Task 3 Steps 4–5. ✓ +- Section D.1 (delete stale file, port coverage) → Task 1. ✓ +- Section D.2 (`_keyring_prompts_for_passphrase` + `resolve_keyring_backend` bats) → Task 1 + Task 2. ✓ +- Section D.3 (os-prompt regression, non-test e2e, multisig resolution) → Task 3 (regression + account e2e), Task 4 (multisig). Validator e2e is covered by the shared resolver + Task 3 Step 5 wiring; the account e2e test exercises the identical code path. ✓ +- Resolution order, `--home` vs `--keyring-dir`, os fallback → Task 2 tests. ✓ + +**Placeholder scan:** Task 4 Step 1 intentionally references "same subcommand as existing test" with an explicit instruction to copy the exact invocation from the file — this is a lookup instruction, not a code placeholder, because the multisig subcommand surface must be read from the live file to avoid tripping unrelated validation. All other steps contain complete code. + +**Type/name consistency:** `resolve_keyring_backend`, `KEYRING_BACKEND_EXPLICIT`, `_keyring_prompts_for_passphrase`, `HOME_DIR`, `KEYRING_DIR`, `KEYRING_BACKEND` used identically across Tasks 2–4. Log strings (`keyring backend: (from )`) match between the implementation (Task 2 Step 5) and the assertions (Tasks 3–4). ✓ diff --git a/docs/evm-integration/user-guides/migration-scripts.md b/docs/evm-integration/user-guides/migration-scripts.md index 02360b1d..95d78bcc 100644 --- a/docs/evm-integration/user-guides/migration-scripts.md +++ b/docs/evm-integration/user-guides/migration-scripts.md @@ -125,7 +125,7 @@ They accept these common flags: | -------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------- | | `--node ` | `$LUMERA_NODE` or `tcp://localhost:26657` | RPC endpoint. | | `--chain-id ` | `$LUMERA_CHAIN_ID`, `$CHAIN_ID`, or auto-detected | Chain ID used for tx generation and broadcast. | -| `--keyring-backend ` | `test` | `test`, `file`, or `os`. | +| `--keyring-backend ` | auto-resolved (see below) | `test`, `file`, or `os`. | | `--keyring-dir ` | unset | Keyring directory independent of `--home`. | | `--home ` | `lumerad` default | Passed through to `lumerad`. | | `--mnemonic-file ` | unset | Optional same-mnemonic import from a file with mode `0600` or stricter. | @@ -133,6 +133,17 @@ They accept these common flags: | `--dry-run` | off | Run checks and preview, then stop before broadcast. | | `--binary ` | `lumerad` from `PATH` | Use a specific `lumerad` binary. | +When `--keyring-backend` is omitted, the scripts resolve it automatically +(first match wins) and log the chosen value and its source before signing: + +1. `$LUMERA_KEYRING_BACKEND` — the same environment override `lumerad` honors. +2. `keyring-backend` from `/config/client.toml` (`--home` selects the + home directory; `--keyring-dir` does **not** move `client.toml`). +3. On-disk detection under `--keyring-dir` (else the home directory): + a `keyring-test/` subdirectory selects `test`, a `keyring-file/` + subdirectory selects `file`. +4. `os` — the Cosmos SDK default. + ### Chain ID Resolution For the migration scripts, `--chain-id` is optional. The scripts resolve the chain ID in this order: diff --git a/docs/evm-integration/user-guides/validator-migration.md b/docs/evm-integration/user-guides/validator-migration.md index 94c3399f..244d147c 100644 --- a/docs/evm-integration/user-guides/validator-migration.md +++ b/docs/evm-integration/user-guides/validator-migration.md @@ -193,7 +193,15 @@ If instead the validator is still `BOND_STATUS_UNBONDING` (not yet `UNBONDED`), systemctl stop lumerad # or however you supervise the node — see "Note on systemctl commands" in the Overview ``` -Stopping before broadcast avoids double-signing risk and prevents the node from producing blocks with the legacy key while migration is in flight. +**Stopping the node before you broadcast is mandatory, not a formality.** Migration re-keys a live, bonded validator in a single transaction: it rewrites the **operator (staking) key** and the on-chain consensus-address→operator mapping. The **consensus key itself is unchanged** — and that is precisely what makes a running node dangerous here. + +Because the consensus key is shared and unchanged across the migration, if the old node instance is still running while you bring up the node under the migrated config, two processes can briefly both hold that same consensus key and sign the same block height. That is **equivocation**, and the chain punishes it with a **tombstone**: the validator is permanently removed from ever validating again, plus a slash of bonded stake. Unlike downtime jailing, a tombstone is **irreversible** — you cannot `unjail` out of it; you must build a brand-new validator with a new consensus key. + +This is not a theoretical two-instance mistake. It is a very real risk whenever `lumerad` runs under a process supervisor — systemd (`Restart=always`), Docker (`--restart`), Kubernetes, or cosmovisor: **the supervisor may already have respawned an instance while your manual restart brings up a second one.** A hurried "migrate while it's still running, then restart quickly" is the exact pattern that produces this overlap. + +Stopping cleanly first makes the overlap structurally impossible. And note there is no consensus reason to rush the restart at all: the consensus key is untouched, so block signing does not depend on the migration. The post-migration restart ([Step 7](#step-7--restart-the-validator-immediately)) only reloads the new **operator** key for future operator transactions — so do a **deliberate stop-then-start**, never a race. + +The trade is deliberately asymmetric: keeping the node up risks an **unrecoverable** tombstone; stopping it costs only **recoverable** missed blocks (and at worst a downtime jail you can `unjail`). When one side of the gamble is permanent, you don't gamble — you stop the node. > **Downtime warning:** mainnet genesis sets `downtime_jail_duration` to `3600s` (1 hour). Do not let the stop-to-restart migration window exceed this time; if the window approaches 1 hour, restart and catch up before retrying the migration. diff --git a/scripts/evmigration-common.sh b/scripts/evmigration-common.sh index 906319c5..39224984 100755 --- a/scripts/evmigration-common.sh +++ b/scripts/evmigration-common.sh @@ -31,6 +31,9 @@ CHAIN_ID="${CHAIN_ID:-}" # shellcheck disable=SC2034 KEYRING_BACKEND="test" # shellcheck disable=SC2034 +# 1 once --keyring-backend is passed explicitly; gates resolve_keyring_backend. +KEYRING_BACKEND_EXPLICIT=0 +# shellcheck disable=SC2034 KEYRING_DIR="" # shellcheck disable=SC2034 HOME_DIR="" @@ -155,7 +158,8 @@ Flags: --node RPC endpoint (default \$LUMERA_NODE or tcp://localhost:26657) Mainnet RPC example: https://rpc.lumera.io:443 --chain-id Chain ID (${_chain_id_help}) - --keyring-backend test|file|os (default test) + --keyring-backend test|file|os (default: \$LUMERA_KEYRING_BACKEND, else + client.toml, else keyring-dir detection, else os) --keyring-dir Keyring directory (overrides --home for keys) --home lumerad home directory --mnemonic-file Import both derivations from one mnemonic (mode 0600 or stricter) @@ -183,6 +187,7 @@ parse_common_flags() { # The --chain-id flag (parsed below) overrides whichever default applies. CHAIN_ID="${LUMERA_CHAIN_ID:-${CHAIN_ID:-}}" KEYRING_BACKEND="test" + KEYRING_BACKEND_EXPLICIT=0 KEYRING_DIR="" HOME_DIR="" MNEMONIC_FILE="" @@ -198,7 +203,7 @@ parse_common_flags() { case "$1" in --node) _require_value "$1" "$#" "${2-}"; NODE="$2"; shift 2 ;; --chain-id) _require_value "$1" "$#" "${2-}"; CHAIN_ID="$2"; shift 2 ;; - --keyring-backend) _require_value "$1" "$#" "${2-}"; KEYRING_BACKEND="$2"; shift 2 ;; + --keyring-backend) _require_value "$1" "$#" "${2-}"; KEYRING_BACKEND="$2"; KEYRING_BACKEND_EXPLICIT=1; shift 2 ;; --keyring-dir) _require_value "$1" "$#" "${2-}"; KEYRING_DIR="$2"; shift 2 ;; --home) _require_value "$1" "$#" "${2-}"; HOME_DIR="$2"; shift 2 ;; --mnemonic-file) _require_value "$1" "$#" "${2-}"; MNEMONIC_FILE="$2"; shift 2 ;; @@ -270,6 +275,78 @@ _read_keyring_flags() { mapfile -t _KRF < <(_keyring_flags) } +# _keyring_prompts_for_passphrase +# True (0) when the active backend may write an interactive passphrase prompt +# to stderr, so callers know to tee stderr back to the terminal instead of +# capturing it into a file (which hides the prompt and makes the command appear +# to hang while it silently waits on stdin). +# +# Both `file` and `os` prompt: cosmos-sdk's `os` backend config uses the same +# newRealPrompt as `file` and, crucially, does NOT pin AllowedBackends — so on a +# headless host with no OS secret service it falls back to the encrypted file +# store and emits the identical "Enter keyring passphrase (attempt N/3)" prompt. +# The shell only sees the string "os", so guarding on `== "file"` missed it. +# Only `test` is silent (its FilePasswordFunc returns a fixed passphrase). +_keyring_prompts_for_passphrase() { + [[ "${KEYRING_BACKEND:-test}" != "test" ]] +} + +# resolve_keyring_backend +# Pin the effective keyring backend and log its source, when the user did not +# pass --keyring-backend. Resolution order (first hit wins): +# 1. explicit --keyring-backend (KEYRING_BACKEND_EXPLICIT=1) +# 2. $LUMERA_KEYRING_BACKEND — the same env override lumerad itself honors +# (and the script convention shared with $LUMERA_NODE / $LUMERA_CHAIN_ID); +# must outrank client.toml/disk because the resolved value is passed to +# lumerad as an explicit flag, which would otherwise override the env +# 3. keyring-backend from /config/client.toml (--home selects home; +# --keyring-dir does NOT move client.toml) +# 4. on-disk detection under --keyring-dir (else --home): +# keyring-test/ -> test, keyring-file/ -> file (os is not on-disk-detectable) +# 5. os — the Cosmos SDK default +# Mirrors resolve_chain_id: logs the decision so the operator sees it before signing. +resolve_keyring_backend() { + if (( KEYRING_BACKEND_EXPLICIT == 1 )); then + log_info "keyring backend: $KEYRING_BACKEND (from --keyring-backend)" + return 0 + fi + + if [[ -n "${LUMERA_KEYRING_BACKEND:-}" ]]; then + KEYRING_BACKEND="$LUMERA_KEYRING_BACKEND" + log_info "keyring backend: $KEYRING_BACKEND (from \$LUMERA_KEYRING_BACKEND)" + return 0 + fi + + # ${HOME:-} — systemd/cron/env -i runs may have no $HOME; under `set -u` a + # bare $HOME would abort the script instead of reaching the os fallback. + local home="${HOME_DIR:-${HOME:-}/.lumera}" + local client_toml="$home/config/client.toml" v + if [[ -f "$client_toml" ]]; then + v=$(sed -n 's/^[[:space:]]*keyring-backend[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \ + "$client_toml" | head -n1) + if [[ -n "$v" ]]; then + KEYRING_BACKEND="$v" + log_info "keyring backend: $v (from $client_toml)" + return 0 + fi + fi + + local kr="${KEYRING_DIR:-$home}" + if [[ -d "$kr/keyring-test" ]]; then + KEYRING_BACKEND="test" + log_info "keyring backend: test (detected keyring-test/ in $kr)" + return 0 + fi + if [[ -d "$kr/keyring-file" ]]; then + KEYRING_BACKEND="file" + log_info "keyring backend: file (detected keyring-file/ in $kr)" + return 0 + fi + + KEYRING_BACKEND="os" + log_info "keyring backend: os (SDK default; no --keyring-backend, client.toml, or keyring dir found)" +} + # The original v1.20.1 migration CLI accepts --tx-timeout=0s, but still enters # the SDK WebSocket wait path. Newer CLIs explicitly document and implement # zero as "return after broadcast", allowing these wrappers to confirm over @@ -402,9 +479,10 @@ lumerad_tx() { # Primary: let the chain simulate the exact gas (migration fees are waived, so # the resulting limit is free). Capture stdout (the tx JSON) separately from # stderr (gas-estimate notes / errors) so callers receive clean JSON to parse. - # For file keyrings, tee stderr back to the terminal because passphrase - # prompts are written there; hiding them makes the command appear to hang. - # Other backends retain capture-only behavior so errors are not duplicated. + # For passphrase-backed keyrings (file/os — see _keyring_prompts_for_passphrase), + # tee stderr back to the terminal because the passphrase prompt is written + # there; hiding it makes the command appear to hang. The silent `test` backend + # retains capture-only behavior so errors are not duplicated. local err_file out rc=0 tx_cmd err_file="$(mktemp)" tx_cmd=("$BIN" tx "$@" @@ -414,8 +492,8 @@ lumerad_tx() { "${_MIGRATION_TX_TIMEOUT_FLAGS[@]}" --gas auto --gas-adjustment "$MIGRATION_GAS_ADJUSTMENT" --output json) - if [[ "${KEYRING_BACKEND:-test}" == "file" ]]; then - log_info "unlocking file keyring to sign and simulate the migration transaction" + if _keyring_prompts_for_passphrase; then + log_info "unlocking keyring to sign and simulate the migration transaction" log_info "enter the keyring passphrase when prompted; input is hidden while typing" out="$("${tx_cmd[@]}" 2> >(tee "$err_file" >&2))" || rc=$? else @@ -470,8 +548,8 @@ preview_tx_body() { fi _read_keyring_flags local generated - if [[ "${KEYRING_BACKEND:-test}" == "file" ]]; then - log_info "unlocking file keyring to generate the transaction preview" + if _keyring_prompts_for_passphrase; then + log_info "unlocking keyring to generate the transaction preview" log_info "enter the keyring passphrase when prompted; input is hidden while typing" else log_info "generating transaction preview" @@ -511,12 +589,12 @@ preview_tx_body() { resolve_address() { local key_name="$1" local addr - if [[ "${KEYRING_BACKEND:-test}" == "file" ]]; then - log_info "unlocking file keyring for key $(legacy_value "$key_name") (enter the keyring passphrase when prompted)" + if _keyring_prompts_for_passphrase; then + log_info "unlocking keyring for key $(legacy_value "$key_name") (enter the keyring passphrase when prompted)" else log_info "resolving address for key $(legacy_value "$key_name")" fi - # Do not suppress stderr here. File-backed keyrings write their passphrase + # Do not suppress stderr here. Passphrase-backed keyrings (file/os) write their # prompt to stderr; redirecting it made the script appear to hang while it # was actually waiting for input. if ! addr=$(lumerad_keys show "$key_name" -a); then diff --git a/scripts/evmigration-common_test.sh b/scripts/evmigration-common_test.sh deleted file mode 100644 index 4ba744da..00000000 --- a/scripts/evmigration-common_test.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# Unit tests for pure helpers in evmigration-common.sh. No network/lumerad. -set -uo pipefail -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Source only the function definitions (the lib is a source-safe library). -# shellcheck source=./evmigration-common.sh disable=SC1091 -source "${DIR}/evmigration-common.sh" -# The library sets -e; disable it for test function calls that may return non-zero. -set +e - -fail=0 -check() { # check