Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# Changelog

## [Unreleased]

### DASH — daemonless masternode-set seed (E2d)
- **Release-pinned masternode-set checkpoint + forward replay** — `--embedded-mainnet`
can now cold-start the payout-bearing masternode set without a dashd RPC. This was
the last structurally daemon-dependent input on the DASH daemonless path.
- ⚠ **This introduces a documented TRUST ANCHOR.** The pinned masternode set is data
the release build asserts; it cannot be header-authenticated, because the P2P
Simplified MN List omits `scriptPayout`/`nLastPaidHeight` and neither is committed
in `merkleRootMNList`. The node independently verifies the anchor's **chain
position** against its own PoW-validated header chain, its **SHA-256 integrity
digest**, and **every replayed block's coinbase** against the projected payee — but
the set contents at the anchor height are trusted. Fully trustless DIP-3-height
replay remains planned as a later opt-in verify-mode. See the README section
"DASH daemonless masternode-set checkpoint — trust anchor" and
`src/impl/dash/coin/checkpoints/README.md`.
- **Fails closed, loudly.** A missing, unpinned, corrupt, wrong-network,
wrong-chain-position, stale or contradicted anchor refuses to serve embedded
templates rather than guessing a payee. New `--embedded-mn-bridge-max N` bounds how
far back an anchor may be (default 20000 blocks, ≈34 days).
- **Anchors are pinned at release time**, not by hand:
`tools/dash/gen_mn_checkpoint.py pin --network mainnet --rpc-url ... ` rewrites the
checkpoint and prints the provenance block for the release notes;
`... verify PATH` re-validates one already in the tree.
- **Anti-mint latch** — on the embedded arm, masternode-readiness now requires an
authoritative height-stamped snapshot. Previously a cold daemonless start could arm
itself off a single ProRegTx observed in a live block and serve a template whose
entire payment queue was that one accidental registration.


## [0.14.0-v36] - 2026-07-10

### Licensing
Expand Down
38 changes: 38 additions & 0 deletions KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,44 @@ documented in the [postmortem archive](https://github.com/frstrtr/the/tree/maste

---

## DASH daemonless masternode set is seeded from a TRUST ANCHOR

**Severity**: Medium (security-model disclosure, not a defect)
**Affects**: DASH `--embedded-mainnet` run WITHOUT a dashd RPC

c2pool cannot derive the payout-bearing DASH masternode set from the P2P
network: the Simplified MN List omits `scriptPayout` and `nLastPaidHeight`,
and neither is committed in `merkleRootMNList`. Daemonless DASH therefore
cold-starts from a **release-pinned masternode set compiled into the binary**
and replays blocks forward from it.

**You are trusting the release build** for the set contents at the anchor
height. The node does independently verify the anchor's chain position against
its own PoW-validated header chain, its SHA-256 integrity digest, and every
replayed block's coinbase against the projected payee — a wrong anchor is
falsified within a few blocks and fails closed — but nothing available to the
node can prove the set itself.

A fully trustless DIP-3-height replay (~1.5M block bodies) is planned as a
later **opt-in verify-mode**; it is not implemented.

Two further limitations today:

- **The shipped mainnet and testnet anchors are UNPINNED.** Pinning requires
RPC access to a synced dashd and is a release-time step
(`tools/dash/gen_mn_checkpoint.py pin`). Until an anchor is pinned,
daemonless DASH **fails closed** — it logs at `ERROR` and refuses to serve
embedded templates rather than guessing a payee.
- **A stale anchor is refused, not worked around.** An anchor more than
`--embedded-mn-bridge-max` blocks behind the tip (default 20000, ≈34 days)
is rejected. A release cut long ago will not daemonlessly mine DASH without
either a fresher build or `--coin-rpc-*`.

Background: README section "DASH daemonless masternode-set checkpoint — trust
anchor" and `src/impl/dash/coin/checkpoints/README.md`.

---

## Remaining IO-thread tracker callbacks without shared_lock

**Severity**: Medium
Expand Down
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,56 @@ c2pool has four operating modes. The default is a full P2P pool — no flags req

Legacy `--standalone` mode (minimal stratum + RPC daemon, no embedded SPV) is available for backwards compatibility.

### DASH daemonless masternode-set checkpoint — trust anchor

**If you run DASH with `--embedded-mainnet` and no dashd, you are trusting the
c2pool release build for one specific piece of data. This section says exactly
which, and why.**

To build a DASH block, c2pool must know which masternode is next in the DIP-3
payment queue. Paying the wrong one produces a coinbase the network rejects
(`bad-cb-payee`) — a mined block thrown away. Ranking the queue needs each
masternode's `scriptPayout` and `nLastPaidHeight`, and **neither is available
from the DASH P2P network**: the Simplified MN List (`mnlistdiff`) omits both,
and neither is committed in `merkleRootMNList`, so there is no header
commitment to check them against.

So c2pool ships a **release-pinned masternode set** — a trust anchor of the
same class as Bitcoin Core's `assumeutxo` — and replays blocks forward from it
to the current tip.

**What the node verifies for itself, with no trust:**

- **chain position** — the anchor names a block hash, and is rejected unless
c2pool's own X11-PoW + DGW-validated header chain holds exactly that hash at
exactly that height;
- **integrity** — a SHA-256 digest over the anchor's contents (an integrity
check on the file, *not* a signature: whoever can change the source can
recompute the digest — it catches accidents, not malice);
- **forward consistency** — every block replayed from the anchor re-derives
the projected payee and compares it against that block's real coinbase, so a
wrong anchor is falsified within a few blocks.

**What you are trusting:** the membership and payout state of the masternode
set *at the anchor height*. Nothing available to the node can prove it.

A fully trustless alternative exists — replaying every block from DIP-3
activation (~1.5M blocks) — and is **planned as a later opt-in verify-mode**.
It is not implemented today.

**Fail-closed by design.** If the anchor is missing, corrupt, for the wrong
network, in the wrong chain position, further behind the tip than
`--embedded-mn-bridge-max` (default 20000 blocks, ≈34 days), or contradicted
by a replayed block, c2pool logs the refusal at `ERROR` and **refuses to serve
embedded DASH templates**. It falls back to a configured dashd, or serves
nothing. It never guesses a masternode payee.

Running DASH with a dashd RPC configured does **not** use the anchor at all —
`protx list valid true` is authoritative and is used instead.

Details, provenance of the shipped anchor, and the release-time re-pinning
procedure: [`src/impl/dash/coin/checkpoints/README.md`](src/impl/dash/coin/checkpoints/README.md).

### Startup examples

```bash
Expand Down
Loading
Loading