mint: enforce the admin lever's loopback bind and refuse the placeholder pool key #42

Merged
vnprc-verifier merged 2 commits from agent/admin-bind-and-pool-key into master 2026-10-21 23:43:02 +01:00
Collaborator

Two startup gaps in the mint's epoch machinery are closed: the epoch admin lever can no longer be bound off loopback by a config typo, and the generator-point placeholder is refused as a pool identity while the prod config ships no key at all. Every refusal was witnessed by starting the real binary against the bad state, before any file is created. The third issue in this group, #36 (epoch store durability), is superseded by vnprc/hashpool#41 moving the store into the mint database and is not part of this change.

#34 — admin lever binding

  • admin_listen must be a loopback address or the mint refuses to start, naming the setting and the value. admin_allow_non_loopback = true is the explicit opt-in and logs a warning at startup that the unauthenticated lever is exposed.
  • No credential is added. The decision is recorded at the bind site: the lever is reachable only from processes on the same host, and any such process can already read the mint config, which holds the mint mnemonic, so a credential would gate nothing the host does not already have.
  • just rotate-epoch did not exist on master (the earlier PR described it but the recipe was lost). It is added and reads admin_listen from the dev config (either quote style, leading whitespace allowed), so moving the port never needs a recipe edit; it exits non-zero when the lever refuses (409 while the current epoch is provisional) or is unreachable, with the JSON body still printed.

#35 — placeholder pool key

  • Both halves: validate_pool_pubkey refuses the secp256k1 generator point by value, and the prod config ships pool_pubkey commented out so an unedited prod file fails on the missing key. The validator catches a copied dev value; the missing key catches an untouched prod file. The error names the config file it loaded.
  • The dev config needs a key that is not the generator point: it now carries the pubkey of sha256("hashpool dev pool identity"), pinned by a test that derives it and compares against the committed file. receive_address stays derived from the generator point; it is an address, not an identity, and has its own pinning test.
  • All [hashpool_mint] validation runs before the mint database is opened, so a refused start creates nothing.

Risk

R2: startup validation and a justfile recipe; no persistent-state handling, no money path. The worst credible failure is refusing a valid configuration at startup, which is loud and names the setting. Rollback is a revert.

Validation

  • cargo test -p mint: 69 + 5 pass on top of #41's suite, including the admin validator (loopback v4 and v6, 0.0.0.0 refused naming the key, opt-in accepted, unparseable refused) and the generator-point refusal plus the dev-key derivation test.
  • Real binary, no stack, empty temp database dir: prod config with the generator point refused naming the file and pool_pubkey; prod with the key unset refused on the missing key; admin_listen = "0.0.0.0:3339" refused with the opt-in named; nothing created in the database directory by any refusal.
  • Live dev stack (on the pre-narrowing branch; the picked commits are identical in content): genesis named under the new dev key; just rotate-epoch rotates on the default port and, after changing admin_listen to 3340 and restarting, on the new port with the recipe untouched; the recipe exits 22 on a 409 and 7 on a refused connection against a throwaway responder.
  • Cold review (pi, provider nullsink, model gpt-5.6-sol) of the pre-narrowing diff: the findings that touched #34/#35 (recipe parsing and exit status, the false "nobody controls G" comment) are fixed; the rest concerned the superseded #36 work.

Closes vnprc/hashpool#34. Closes vnprc/hashpool#35. Refs vnprc/hashpool#13. Rebased onto master after vnprc/hashpool#41 merged.

Two startup gaps in the mint's epoch machinery are closed: the epoch admin lever can no longer be bound off loopback by a config typo, and the generator-point placeholder is refused as a pool identity while the prod config ships no key at all. Every refusal was witnessed by starting the real binary against the bad state, before any file is created. The third issue in this group, #36 (epoch store durability), is superseded by vnprc/hashpool#41 moving the store into the mint database and is not part of this change. ## #34 — admin lever binding - `admin_listen` must be a loopback address or the mint refuses to start, naming the setting and the value. `admin_allow_non_loopback = true` is the explicit opt-in and logs a warning at startup that the unauthenticated lever is exposed. - No credential is added. The decision is recorded at the bind site: the lever is reachable only from processes on the same host, and any such process can already read the mint config, which holds the mint mnemonic, so a credential would gate nothing the host does not already have. - `just rotate-epoch` did not exist on master (the earlier PR described it but the recipe was lost). It is added and reads `admin_listen` from the dev config (either quote style, leading whitespace allowed), so moving the port never needs a recipe edit; it exits non-zero when the lever refuses (409 while the current epoch is provisional) or is unreachable, with the JSON body still printed. ## #35 — placeholder pool key - Both halves: `validate_pool_pubkey` refuses the secp256k1 generator point by value, and the prod config ships `pool_pubkey` commented out so an unedited prod file fails on the missing key. The validator catches a copied dev value; the missing key catches an untouched prod file. The error names the config file it loaded. - The dev config needs a key that is not the generator point: it now carries the pubkey of `sha256("hashpool dev pool identity")`, pinned by a test that derives it and compares against the committed file. `receive_address` stays derived from the generator point; it is an address, not an identity, and has its own pinning test. - All `[hashpool_mint]` validation runs before the mint database is opened, so a refused start creates nothing. ## Risk R2: startup validation and a justfile recipe; no persistent-state handling, no money path. The worst credible failure is refusing a valid configuration at startup, which is loud and names the setting. Rollback is a revert. ## Validation - `cargo test -p mint`: 69 + 5 pass on top of #41's suite, including the admin validator (loopback v4 and v6, `0.0.0.0` refused naming the key, opt-in accepted, unparseable refused) and the generator-point refusal plus the dev-key derivation test. - Real binary, no stack, empty temp database dir: prod config with the generator point refused naming the file and `pool_pubkey`; prod with the key unset refused on the missing key; `admin_listen = "0.0.0.0:3339"` refused with the opt-in named; nothing created in the database directory by any refusal. - Live dev stack (on the pre-narrowing branch; the picked commits are identical in content): genesis named under the new dev key; `just rotate-epoch` rotates on the default port and, after changing `admin_listen` to 3340 and restarting, on the new port with the recipe untouched; the recipe exits 22 on a 409 and 7 on a refused connection against a throwaway responder. - Cold review (pi, provider nullsink, model gpt-5.6-sol) of the pre-narrowing diff: the findings that touched #34/#35 (recipe parsing and exit status, the false "nobody controls G" comment) are fixed; the rest concerned the superseded #36 work. Closes vnprc/hashpool#34. Closes vnprc/hashpool#35. Refs vnprc/hashpool#13. Rebased onto master after vnprc/hashpool#41 merged.
A non-loopback admin_listen was only convention, so a config typo (or
0.0.0.0) silently exposed the unauthenticated epoch rotation lever to the
network. validate_admin_listen now refuses any non-loopback bind at
startup unless the new [hashpool_mint] admin_allow_non_loopback flag
explicitly opts in (and warns when it does).

Records the authentication decision at the bind site: no credential,
because anything on the same host that could reach the lever can already
read the mint config and its mnemonic, so a credential would gate
nothing the host does not already have.

Adds `just rotate-epoch`, which reads admin_listen out of
config/mint.config.toml (accepting either quote style and leading
whitespace) instead of carrying its own copy of the port, and exits
non-zero on a refusal (409 while provisional, or an unreachable
listener) instead of always reporting success.

Refs vnprc/hashpool#34.
config/prod/mint.config.toml shipped pool_pubkey set to G, a structurally
valid compressed pubkey that namespaces every epoch under a well-known
constant instead of a real pool identity.

Both halves, for the two ways this goes wrong:
- naming::validate_pool_pubkey now rejects G by value, so a copied dev
  value is caught even if it is otherwise well-formed.
- the prod config ships pool_pubkey commented out with a TODO(operator),
  so an unedited prod file fails on the pre-existing "pool_pubkey is
  required" error instead of ever reaching the generator-point check.

The dev config needed a replacement key that isn't G: pubkey of
sha256("hashpool dev pool identity"), pinned by a naming.rs test that
derives it independently and compares against the committed config.
Updated every test constant that used G as a pool pubkey to the new dev
key. receive_address is untouched — it is an address, not an identity,
and stays derived from G per its own existing test.

pool_pubkey (and every other [hashpool_mint] setting) is now validated
before setup_mint opens the database, wrapped with the config file path
it loaded, so a refused start never leaves a freshly created mint.sqlite
behind.

Refs vnprc/hashpool#35.
Collaborator

Verification

Verifier: vnprc-verifier on rust-dev, scratch clone /home/vnprc/v42 (cloned into the session scratchpad, moved to this short path because bitcoind's unix socket path exceeded the length limit under the scratchpad)
Head: a03ba32736 Base: master at a5b56100e4 (branch point 92dddda2; master gained one unrelated commit since, no file overlap; git merge-tree --write-tree is clean: 74657759)
Score: R2 — the worst credible failure after validation is a loud startup refusal of a valid config (for example an admin_listen written as a hostname, which the new socket-address parse rejects), recovered by a straight revert; all validation runs before the mint database opens, so a refusal writes nothing. The security-boundary signal is a reason to walk, and the change only tightens the boundary. PR body says R2.
Rung: R2 on vnprc/hashpool is agent merge, veto window waived (vnprc/hashpool#49, owner comment 2026-10-21)
Checks: no feature map and no README check list, so the checks are the PR body's own: devenv shell -- cargo test --manifest-path roles/Cargo.toml -p mint --no-fail-fast at a03ba327 — pass, 69 + 5, output below
Coverage (no feature map: the PR body's Validation commands rerun at the head):

Feature file Entry point and path Expected Observed Evidence
(none; PR Validation) cargo test -p mint at head 69 + 5 pass incl. admin validator and generator-point/dev-key tests 69 passed, 5 passed, 0 failed; the 5 admin tests and 4 naming tests named below block 1
(none; PR Validation) real binary, prod config + generator point as pool_pubkey, empty db dir refused naming the config file and pool_pubkey; nothing created exit 1, Error: loading <file> / Caused by: [hashpool_mint] pool_pubkey is the secp256k1 generator point…; db dir still empty block 2
(none; PR Validation) real binary, prod config with pool_pubkey unset, empty db dir refused on the missing key; nothing created exit 1, [hashpool_mint] pool_pubkey is required; db dir still empty block 2
(none; PR Validation) real binary, dev config with admin_listen = "0.0.0.0:3339", empty db dir refused naming the opt-in; nothing created exit 1, admin_listen 0.0.0.0:3339 is not a loopback address… Set [hashpool_mint] admin_allow_non_loopback = true to opt in.; db dir still empty block 2
(extra) real binary, same plus admin_allow_non_loopback = true warning logged, startup proceeds to the database WARN admin_allow_non_loopback is set… reachable from the network addr=0.0.0.0:3339, then Using database path, db files created; killed while waiting on bitcoind block 2
(none; PR Validation) live dev stack at head: genesis genesis named under the new dev key genesis epoch opened unit=hash_034dcb73…e66a_17 height=17; /v1/keysets and /v1/info nut4/nut5 show that unit block 3
(none; PR Validation) just rotate-epoch on the default port rotates, JSON body, exit 0 exit 0, body {"height":17,…,"unit":"hash_034dcb…_17_1"}; log epoch opened … source=Manual; nut4 now lists _17_1 only block 3
(none; PR Validation) admin_listen set to 3340, mint restarted, recipe untouched lever on 3340, 3339 closed, rotation succeeds Epoch admin listening on 127.0.0.1:3340, 3339 closed; just rotate-epoch exit 0 with unit _17_2; persisted epoch _17_1 resumed across the restart block 3
(none; PR Validation) recipe against a throwaway 409 responder on 127.0.0.1:3339 exit 22, JSON body still printed curl: (22) … 409, body {"error":"current epoch is provisional; manual rotation refused"}, recipe exit 22 block 4
(none; PR Validation) recipe with nothing listening on 127.0.0.1:3339 exit 7 curl: (7) Failed to connect to 127.0.0.1:3339, recipe exit 7 block 4
(base moved) cargo test -p mint on the merged tree (master a5b56100 + head, local commit of tree 74657759) 69 + 5 pass 69 passed, 5 passed, 0 failed block 5

Gaps: none. Not rerun: the cold-review bullet (a review, not a command).
Observation for the author, not a stop: an existing dev checkout's .devenv/state holds epochs named under the old generator-point key; after this change the mint resumes those records as-is and names new epochs under the new dev key, so just clean cashu is the way to a consistent dev state. Disposable state only.
Decision: merge — score R2 is on the agent-merge rung with the veto window waived; every Validation command reran green at the head and the checks pass on the merged tree.

Block 1 — devenv shell -- cargo test --manifest-path roles/Cargo.toml -p mint --no-fail-fast at a03ba327 (devenv 2.1.2, rustc 1.86.0):

     Running unittests src/main.rs (roles/target/debug/deps/mint-d83540ad0bc18687)

running 69 tests
test lib::epoch::admin::tests::non_loopback_is_allowed_with_the_opt_in ... ok
test lib::epoch::naming::tests::accepts_valid_compressed_key_and_normalizes_case ... ok
test lib::epoch::admin::tests::non_loopback_is_refused_and_names_the_setting ... ok
test lib::epoch::admin::tests::unparseable_is_refused ... ok
test lib::epoch::admin::tests::loopback_v4_is_ok ... ok
test lib::epoch::admin::tests::loopback_v6_is_ok ... ok
test lib::epoch::naming::tests::rejects_the_generator_point_but_accepts_other_valid_keys ... ok
test lib::epoch::naming::tests::rejects_invalid_and_uncompressed_keys ... ok
test lib::epoch::naming::tests::unit_names_are_deterministic_with_suffixes ... ok
test lib::epoch::naming::tests::dev_config_pool_pubkey_is_the_dev_identity_pubkey ... ok
test result: ok. 69 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.02s
     Running tests/sv2_connection_integration.rs (roles/target/debug/deps/sv2_connection_integration-80b2efe813676fe9)
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Block 2 — roles/target/debug/mint -c <config> -g config/shared/pool.toml at a03ba327, each db dir empty before and after unless noted:

# prod config + generator point
Error: loading <scratch>/prod-generator-point.toml
Caused by:
    [hashpool_mint] pool_pubkey is the secp256k1 generator point, a well-known placeholder value copied from an example config, not a real pool identity; set it to your pool's own key
# prod config, pool_pubkey unset
Error: [hashpool_mint] pool_pubkey is required (namespaces epoch units)
# dev config, admin_listen = 0.0.0.0:3339
Error: [hashpool_mint] admin_listen 0.0.0.0:3339 is not a loopback address; the unauthenticated epoch rotation lever would be reachable from the network. Set [hashpool_mint] admin_allow_non_loopback = true to opt in.
# dev config, admin_listen = 0.0.0.0:3339 + admin_allow_non_loopback = true (db-d: mint.sqlite mint.sqlite-shm mint.sqlite-wal created)
2026-10-21T22:18:59.443109Z  WARN mint: [hashpool_mint] admin_allow_non_loopback is set: the unauthenticated epoch rotation lever is reachable from the network addr=0.0.0.0:3339
2026-10-21T22:18:59.443115Z  INFO mint: Using database path: <scratch>/db-d/mint.sqlite

Block 3 — live dev stack (devenv up) at a03ba327 in /home/vnprc/v42:

2026-10-21T22:37:53.892146Z  INFO mint::lib::epoch: genesis epoch opened unit=hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17 height=17
2026-10-21T22:37:53.892376Z  INFO mint: Epoch admin listening on 127.0.0.1:3339
2026-10-21T22:38:23.380436Z  INFO mint::lib::epoch: epoch opened unit=hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_1 height=17 source=Manual state=Final
2026-10-21T22:40:36.671109Z  INFO mint::lib::epoch: resuming persisted epoch unit=hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_1 height=17 restored_epochs=1
2026-10-21T22:40:36.671625Z  INFO mint: Epoch admin listening on 127.0.0.1:3340
2026-10-21T22:40:38.796620Z  INFO mint::lib::epoch: epoch opened unit=hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_2 height=17 source=Manual state=Final
# curl -s localhost:3338/v1/info | jq nut4 units, after genesis / after rotation 1 / after rotation 2
{"nut4":["hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17"]}
{"nut4":["hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_1"]}
{"nut4":["hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_2"]}
# just rotate-epoch, port 3339 (exit 0)
{"height":17,"keyset_id":"011d3b7b6f279960c970224d8b47f870a3a8065a9979d06e544e9f3c3f9c0b1f6a","unit":"hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_1"}
# admin_listen -> 127.0.0.1:3340, process-compose restart mint; 3339 closed, 3340 open; just rotate-epoch (exit 0)
{"height":17,"keyset_id":"011b472f75ddb716c366e21a75cf370ee5ace99e652a9179c77d7077465bbfd62c","unit":"hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_2"}

Block 4 — recipe exit status against a throwaway responder (socat returning 409) and a closed port:

curl: (22) The requested URL returned error: 409
{"error":"current epoch is provisional; manual rotation refused"}
error: Recipe `rotate-epoch` failed on line 106 with exit code 22
curl: (7) Failed to connect to 127.0.0.1:3339 after 0 ms: Could not connect to server
error: Recipe `rotate-epoch` failed on line 106 with exit code 7

Block 5 — cargo test -p mint on the merged tree (local commit 3e214890 of tree 74657759 = git merge-tree --write-tree a5b56100 a03ba327, never pushed):

== tree head: 3e214890e760501e6da9c531e07a3b108dfbf298 (merged tree 74657759f676ad577667ea57b7a2c0bb420f8068)
test result: ok. 69 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.14s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
== cargo test exit: 0
## Verification Verifier: vnprc-verifier on rust-dev, scratch clone /home/vnprc/v42 (cloned into the session scratchpad, moved to this short path because bitcoind's unix socket path exceeded the length limit under the scratchpad) Head: a03ba32736217e76d288d1bef6a4acecaa0d2dd6 Base: master at a5b56100e4e3712b8439e96409fa47510ff7a799 (branch point 92dddda2; master gained one unrelated commit since, no file overlap; `git merge-tree --write-tree` is clean: 74657759) Score: R2 — the worst credible failure after validation is a loud startup refusal of a valid config (for example an `admin_listen` written as a hostname, which the new socket-address parse rejects), recovered by a straight revert; all validation runs before the mint database opens, so a refusal writes nothing. The security-boundary signal is a reason to walk, and the change only tightens the boundary. PR body says R2. Rung: R2 on vnprc/hashpool is agent merge, veto window waived (vnprc/hashpool#49, owner comment 2026-10-21) Checks: no feature map and no README check list, so the checks are the PR body's own: `devenv shell -- cargo test --manifest-path roles/Cargo.toml -p mint --no-fail-fast` at a03ba327 — pass, 69 + 5, output below Coverage (no feature map: the PR body's Validation commands rerun at the head): | Feature file | Entry point and path | Expected | Observed | Evidence | | --- | --- | --- | --- | --- | | (none; PR Validation) | `cargo test -p mint` at head | 69 + 5 pass incl. admin validator and generator-point/dev-key tests | 69 passed, 5 passed, 0 failed; the 5 admin tests and 4 naming tests named below | block 1 | | (none; PR Validation) | real binary, prod config + generator point as `pool_pubkey`, empty db dir | refused naming the config file and `pool_pubkey`; nothing created | exit 1, `Error: loading <file>` / `Caused by: [hashpool_mint] pool_pubkey is the secp256k1 generator point…`; db dir still empty | block 2 | | (none; PR Validation) | real binary, prod config with `pool_pubkey` unset, empty db dir | refused on the missing key; nothing created | exit 1, `[hashpool_mint] pool_pubkey is required`; db dir still empty | block 2 | | (none; PR Validation) | real binary, dev config with `admin_listen = "0.0.0.0:3339"`, empty db dir | refused naming the opt-in; nothing created | exit 1, `admin_listen 0.0.0.0:3339 is not a loopback address… Set [hashpool_mint] admin_allow_non_loopback = true to opt in.`; db dir still empty | block 2 | | (extra) | real binary, same plus `admin_allow_non_loopback = true` | warning logged, startup proceeds to the database | WARN `admin_allow_non_loopback is set… reachable from the network addr=0.0.0.0:3339`, then `Using database path`, db files created; killed while waiting on bitcoind | block 2 | | (none; PR Validation) | live dev stack at head: genesis | genesis named under the new dev key | `genesis epoch opened unit=hash_034dcb73…e66a_17 height=17`; `/v1/keysets` and `/v1/info` nut4/nut5 show that unit | block 3 | | (none; PR Validation) | `just rotate-epoch` on the default port | rotates, JSON body, exit 0 | exit 0, body `{"height":17,…,"unit":"hash_034dcb…_17_1"}`; log `epoch opened … source=Manual`; nut4 now lists `_17_1` only | block 3 | | (none; PR Validation) | `admin_listen` set to 3340, mint restarted, recipe untouched | lever on 3340, 3339 closed, rotation succeeds | `Epoch admin listening on 127.0.0.1:3340`, 3339 closed; `just rotate-epoch` exit 0 with unit `_17_2`; persisted epoch `_17_1` resumed across the restart | block 3 | | (none; PR Validation) | recipe against a throwaway 409 responder on 127.0.0.1:3339 | exit 22, JSON body still printed | `curl: (22) … 409`, body `{"error":"current epoch is provisional; manual rotation refused"}`, recipe exit 22 | block 4 | | (none; PR Validation) | recipe with nothing listening on 127.0.0.1:3339 | exit 7 | `curl: (7) Failed to connect to 127.0.0.1:3339`, recipe exit 7 | block 4 | | (base moved) | `cargo test -p mint` on the merged tree (master a5b56100 + head, local commit of tree 74657759) | 69 + 5 pass | 69 passed, 5 passed, 0 failed | block 5 | Gaps: none. Not rerun: the cold-review bullet (a review, not a command). Observation for the author, not a stop: an existing dev checkout's `.devenv/state` holds epochs named under the old generator-point key; after this change the mint resumes those records as-is and names new epochs under the new dev key, so `just clean cashu` is the way to a consistent dev state. Disposable state only. Decision: merge — score R2 is on the agent-merge rung with the veto window waived; every Validation command reran green at the head and the checks pass on the merged tree. Block 1 — `devenv shell -- cargo test --manifest-path roles/Cargo.toml -p mint --no-fail-fast` at a03ba327 (devenv 2.1.2, rustc 1.86.0): ``` Running unittests src/main.rs (roles/target/debug/deps/mint-d83540ad0bc18687) running 69 tests test lib::epoch::admin::tests::non_loopback_is_allowed_with_the_opt_in ... ok test lib::epoch::naming::tests::accepts_valid_compressed_key_and_normalizes_case ... ok test lib::epoch::admin::tests::non_loopback_is_refused_and_names_the_setting ... ok test lib::epoch::admin::tests::unparseable_is_refused ... ok test lib::epoch::admin::tests::loopback_v4_is_ok ... ok test lib::epoch::admin::tests::loopback_v6_is_ok ... ok test lib::epoch::naming::tests::rejects_the_generator_point_but_accepts_other_valid_keys ... ok test lib::epoch::naming::tests::rejects_invalid_and_uncompressed_keys ... ok test lib::epoch::naming::tests::unit_names_are_deterministic_with_suffixes ... ok test lib::epoch::naming::tests::dev_config_pool_pubkey_is_the_dev_identity_pubkey ... ok test result: ok. 69 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.02s Running tests/sv2_connection_integration.rs (roles/target/debug/deps/sv2_connection_integration-80b2efe813676fe9) test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` Block 2 — `roles/target/debug/mint -c <config> -g config/shared/pool.toml` at a03ba327, each db dir empty before and after unless noted: ``` # prod config + generator point Error: loading <scratch>/prod-generator-point.toml Caused by: [hashpool_mint] pool_pubkey is the secp256k1 generator point, a well-known placeholder value copied from an example config, not a real pool identity; set it to your pool's own key # prod config, pool_pubkey unset Error: [hashpool_mint] pool_pubkey is required (namespaces epoch units) # dev config, admin_listen = 0.0.0.0:3339 Error: [hashpool_mint] admin_listen 0.0.0.0:3339 is not a loopback address; the unauthenticated epoch rotation lever would be reachable from the network. Set [hashpool_mint] admin_allow_non_loopback = true to opt in. # dev config, admin_listen = 0.0.0.0:3339 + admin_allow_non_loopback = true (db-d: mint.sqlite mint.sqlite-shm mint.sqlite-wal created) 2026-10-21T22:18:59.443109Z WARN mint: [hashpool_mint] admin_allow_non_loopback is set: the unauthenticated epoch rotation lever is reachable from the network addr=0.0.0.0:3339 2026-10-21T22:18:59.443115Z INFO mint: Using database path: <scratch>/db-d/mint.sqlite ``` Block 3 — live dev stack (`devenv up`) at a03ba327 in /home/vnprc/v42: ``` 2026-10-21T22:37:53.892146Z INFO mint::lib::epoch: genesis epoch opened unit=hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17 height=17 2026-10-21T22:37:53.892376Z INFO mint: Epoch admin listening on 127.0.0.1:3339 2026-10-21T22:38:23.380436Z INFO mint::lib::epoch: epoch opened unit=hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_1 height=17 source=Manual state=Final 2026-10-21T22:40:36.671109Z INFO mint::lib::epoch: resuming persisted epoch unit=hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_1 height=17 restored_epochs=1 2026-10-21T22:40:36.671625Z INFO mint: Epoch admin listening on 127.0.0.1:3340 2026-10-21T22:40:38.796620Z INFO mint::lib::epoch: epoch opened unit=hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_2 height=17 source=Manual state=Final # curl -s localhost:3338/v1/info | jq nut4 units, after genesis / after rotation 1 / after rotation 2 {"nut4":["hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17"]} {"nut4":["hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_1"]} {"nut4":["hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_2"]} # just rotate-epoch, port 3339 (exit 0) {"height":17,"keyset_id":"011d3b7b6f279960c970224d8b47f870a3a8065a9979d06e544e9f3c3f9c0b1f6a","unit":"hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_1"} # admin_listen -> 127.0.0.1:3340, process-compose restart mint; 3339 closed, 3340 open; just rotate-epoch (exit 0) {"height":17,"keyset_id":"011b472f75ddb716c366e21a75cf370ee5ace99e652a9179c77d7077465bbfd62c","unit":"hash_034dcb73610a1af09a68b17e69d4e2ac8e1a05536264c2a805f027839ffd7de66a_17_2"} ``` Block 4 — recipe exit status against a throwaway responder (socat returning 409) and a closed port: ``` curl: (22) The requested URL returned error: 409 {"error":"current epoch is provisional; manual rotation refused"} error: Recipe `rotate-epoch` failed on line 106 with exit code 22 curl: (7) Failed to connect to 127.0.0.1:3339 after 0 ms: Could not connect to server error: Recipe `rotate-epoch` failed on line 106 with exit code 7 ``` Block 5 — `cargo test -p mint` on the merged tree (local commit 3e214890 of tree 74657759 = `git merge-tree --write-tree a5b56100 a03ba327`, never pushed): ``` == tree head: 3e214890e760501e6da9c531e07a3b108dfbf298 (merged tree 74657759f676ad577667ea57b7a2c0bb420f8068) test result: ok. 69 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.14s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s == cargo test exit: 0 ```
Collaborator

Merged as 3547740516. Revert: git revert a5b56100e4e3712b8439e96409fa47510ff7a799..3547740516278a624fbbccf303e82de2f2b9f54b

Merged as 3547740516278a624fbbccf303e82de2f2b9f54b. Revert: git revert a5b56100e4e3712b8439e96409fa47510ff7a799..3547740516278a624fbbccf303e82de2f2b9f54b
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
vnprc/hashpool!42
No description provided.