Skip to content

feat(sdk): ERA faucet claim flow — B3: ticket claim end to end, Req 15.8 registers, faucet-mint machinery deleted - #734

Merged
cryptskii merged 2 commits into
mainfrom
feat/era-faucet-claim-flow
Aug 26, 2026
Merged

feat(sdk): ERA faucet claim flow — B3: ticket claim end to end, Req 15.8 registers, faucet-mint machinery deleted#734
cryptskii merged 2 commits into
mainfrom
feat/era-faucet-claim-flow

Conversation

@cryptskii

@cryptskii cryptskii commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

The third and final PR of the ERA faucet ticket model — B1 #732 (wire/core), B2 #733 (node write-once registers), and now the SDK claim flow. After this, a fresh mainnet identity can fund itself: one single-use ticket of the network's finite 800,000,000 × 100 ERA bootstrap allocation, admitted into a validated economic lineage that any foreign verifier can check.

The claim lifecycle (sdk/faucet_claim_flow.rs, new)

select ticket (deterministic strategy; any in-range ticket is valid)
  → sign claim envelope ONCE, freeze exact bytes BEFORE any member write
  → win the write-once quorum cell (contested ⇒ next attempt's ticket)
  → build the witness FROM the accepted core transition (+100 derived by core)
  → fence-coupled advance: pending admission attached Prepared, flipped to
    LocalAcceptedPendingEcon in the SAME commit as the head
  → all four artifacts frozen in that same transaction
    (claim envelope, witness, manifest, DSM substrate)
  → publish evidence, member-attributed, q of 3
  → register the economic root (frozen bytes, sign-once)
  → advance_validated with the LIVE register resolver — the SAME predicate
    a foreign verifier runs; no self-trust shortcut
  → admit: position + root + leaf cache + clear pending + unfenced head, ONE tx

Crash recovery is resume-only: a pending admission found at entry is FINISHED from whichever boundary the crash hit, using only durable frozen bytes. Nothing is ever re-signed (deterministic SPHINCS+ makes silent divergence undetectable — so regeneration is made impossible, not discouraged), and no timeout aborts an admission.

Req 15.8 counting (sdk/economic_registers.rs, new)

  • An answer counts ONLY when the echoed x-dsm-node-id equals the member queried.
  • A generic refused NEVER counts toward quorum — it fails the claim closed as contested.
  • refused carrying OUR exact digest is an acceptance in different words, with the same attribution bar.
  • Root register: Contested ⇒ Conflict quarantine — a cell only this device writes holding foreign bytes is catastrophic, never hash-ordered, never overwritten.
  • Reads require q attributed byte-identical rows; >1 distinct value in a write-once cell ⇒ Conflict.
  • put_immutable_to_all_members now enforces member-echo attribution (tightened, never loosened).

Client DB schema 7 → 8 (beta wipe policy, no migration)

faucet_ticket_claim_local, economic_root_claim_local (frozen sign-once envelopes), economic_admitted (the durable coordinate finally has its producer), economic_leaf_cache (strategy A — written in the admission tx, admitted on load ONLY if it recomputes the admitted root; never an authority).

Deletions (dead paths that know how to mint)

mint_for_self, the era-source-dlv keyless pseudo-identity, build_faucet_protocol_payload, faucet_clean, core/token/faucet.rs + its faucet cargo feature, and the whole FaucetState/FaucetConfig/FaucetLimiter cooldown machinery — V1 deliberately has no cooldown or quota, and keeping the machinery would define policy V1 lacks. The route is orchestration only; the committed network comes from the STORED genesis record, fail closed.

Tests

  • 5 e2e flow tests over a cfg(test) fake-register fleet speaking the real protocol digests: full claim (+100, position 1, fence cleared), repeat claimant (position 2, exactly 200), poisoned ticket through the whole flow (attacker consumes the victim's attempt-0 ticket with its own key; the victim succeeds on attempt 1 — the regression control for ticket independence), crash-boundary resume on frozen bytes, monotonic triple claim.
  • Route contract: claim without a stored genesis record fails closed naming it; check_nearby advertises the fixed payout.
  • 5 direct unit controls on the counting clauses, plus a compiling mutation control: attribution clause disabled ⇒ an_unattributed_acceptance_never_counts red; restored ⇒ green.

Boards (pinned 1.98.0, rerun in full on the exact final tree after the rename)

cargo test --locked --workspace --exclude dsm_storage_node -- --nocapture --test-threads=1
  68 suites, 3826 passed / 0 failed  (dsm lib 1664/0, dsm_sdk lib 1775/0, 7 ignored)
cargo test --locked -p dsm_storage_node --no-default-features --features local-dev,strict
  270 passed / 0 failed
make lint                        exit 0
bash ci/production_safety_checks.sh   exit 0 (TLA+ included)

The beta network is dsm-testnet (second commit, owner-directed)

Testnet runs before mainnet, so the literal network id the beta system serves is renamed "mainnet"dsm-testnet across every normative site (register profile, createGenesisV2 default, client-DB DDL default, frontend genesis literal, node sample config) plus every test pinning the resolvable network. The rename is identity-breaking — the network id feeds the GRK derivation, era_faucet_id, and the register profile — which is exactly why it happens NOW, pre-deployment, under the beta wipe policy.

The real mainnet is deliberately not pre-added: it arrives at launch as an explicit new profile with its own fresh 800M-ticket / 80B allocation. Nothing claimed on testnet can validate there. dsm-test (no "net") remains the throwaway fixture network that resolves no profile.

Deployment note

Live fleet nodes need node.network_id = "dsm-testnet" in their config before faucet claims will be accepted (B2's fail-closed 503 otherwise). A documented sample entry now exists in storage_node_config.toml.

Documented residuals (unchanged from the frozen plan)

V1 promises conservation, not fairness: unlimited public claims may burn the per-network allocation, and selection is publicly predictable, so targeted pre-consumption of a victim's upcoming tickets is possible — it breaks neither the 80B cap nor faucet-wide liveness.

…5.8 registers, faucet-mint machinery deleted

The third and final PR of the ERA faucet ticket model (B1 #732 wire/core,
B2 #733 node registers). The SDK now drives the complete claim lifecycle:
select a ticket deterministically, sign the claim envelope ONCE and freeze
the exact bytes before any member write, win the write-once quorum cell,
advance the fence-coupled FaucetClaim operation, freeze all four admission
artifacts in the accepting transaction, publish evidence attributed to q
members, register the economic root, validate with the same predicate any
foreign verifier runs, and admit the position durably.

- sdk/faucet_claim_flow.rs (new): the orchestration, including resume of a
  pending admission from any crash boundary using only frozen bytes —
  nothing is ever re-signed and no timeout aborts an admission.
- sdk/economic_registers.rs (new): ticket selection (strategy, not
  protocol), Req 15.8 attributed quorum counting (generic refused NEVER
  counts; refused-with-our-digest counts only when attributed), root-cell
  Contested => Conflict quarantine, live provenance resolver.
- sdk/storage_io.rs: seams for register I/O with a cfg(test) fake-register
  fleet speaking the REAL protocol digests.
- storage/client_db: schema v8 (beta wipe policy) — frozen ticket/root
  claims, admitted coordinate, economic leaf cache written in the admission
  transaction and admitted only on root equality.
- put_immutable_to_all_members now enforces member-echo attribution.
- faucet.claim route rewritten to orchestration only; the committed network
  comes from the stored genesis record, fail closed. check_nearby
  advertises the fixed 100-ERA payout.
- DELETED, not disabled: mint_for_self, era-source-dlv pseudo-identity,
  build_faucet_protocol_payload, faucet_clean, core/token/faucet.rs plus
  its cargo feature, FaucetState/FaucetConfig/FaucetLimiter cooldown
  machinery. V1 has no cooldown/quota; dead paths that know how to mint
  are what a later edit resurrects.

Tests: 5 e2e flow tests over the fake registers (full claim +100 at
position 1, repeat claimant, poisoned ticket THROUGH THE WHOLE FLOW, crash
resume on frozen bytes, monotonic triple claim); route contract (no stored
genesis record fails closed naming it); 5 direct unit controls on the
Req 15.8 counting clauses with a compiling mutation control (attribution
clause disabled => named test red; restored => green).

Boards (pinned 1.98.0, exact final tree): workspace 68 suites 3826/0
(dsm lib 1664/0, dsm_sdk lib 1775/0), dsm_storage_node 264/0, make lint
exit 0, production_safety_checks.sh exit 0.
…rofile at launch

The literal "mainnet" was only the beta fleet's network label (BETA_NETWORK_ID,
client-DB genesis-record default), but it read as a launch-phase claim. Testnet
runs before mainnet, so the beta network is renamed to dsm-testnet BEFORE any
deployment — the only cheap moment: the network id feeds the GRK derivation,
era_faucet_id, and the register profile, so a rename is identity-breaking and
free only under the beta wipe policy.

The real mainnet is deliberately NOT pre-added: it arrives later as an explicit
new profile in resolve_root_register_profile, with its own fresh 800M-ticket /
80B ERA allocation. Nothing claimed on testnet can validate there — different
genesis (and thus keys), different faucet id, different register cells.

- dsm/economic/register.rs: BETA_NETWORK_ID = b"dsm-testnet"; unknown networks
  still fail closed.
- system.createGenesisV2 default and the frontend genesis literal: dsm-testnet.
- client_db: genesis-record DDL default 'dsm-testnet' (schema v8 is unreleased
  in this PR, amended in place).
- storage_node_config.toml: [genesis].network renamed AND the previously
  undocumented node.network_id gains a documented sample entry — the B2 faucet
  endpoint fail-closes 503 without it, and a fail-closed gate needs a visible
  producer.
- Tests pinning the resolvable network updated across dsm, dsm_sdk, the node,
  and the frontend (useGenesisFlow 15/15 under jest); "dsm-test" remains the
  throwaway fixture network that resolves no profile.

Boards (pinned 1.98.0, exact final tree): workspace 68 suites 3826/0
(dsm lib 1664/0, dsm_sdk lib 1775/0), dsm_storage_node 270/0, make lint
exit 0, production_safety_checks.sh exit 0.
@cryptskii
cryptskii merged commit 68e3ff9 into main Aug 26, 2026
18 checks passed
@cryptskii
cryptskii deleted the feat/era-faucet-claim-flow branch August 26, 2026 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant