More cards

This commit is contained in:
2026-10-19 18:52:49 -04:00
parent 716c9612cc
commit 0b9061372b
26 changed files with 549 additions and 201 deletions
+18 -20
View File
@@ -1,9 +1,22 @@
# Native RandomX engine
This is the CPU proof engine used by the Wikideck testnet node and explicit opt-in
native miner. Browser mining has been removed from the active application. The
Docker image builds this pinned implementation and runs its known-vector test.
There is no GPU path, simulated hash, or production fallback hash.
native miner. Mining runs only when the operator starts the native CLI; the browser
does not mine or launch native programs. The Docker image builds this pinned
implementation and runs its known-vector test. There is no GPU path, simulated hash,
or production fallback hash.
This document covers building the executable, its memory modes, its line protocol,
and the TypeScript adapter. For the user-facing install and run instructions, start
with the [README](../README.md).
## Contents
- [Source and build](#source-and-build)
- [Modes and resource use](#modes-and-resource-use)
- [Line protocol](#line-protocol)
- [Node API](#node-api)
- [Real upstream known-vector check](#real-upstream-known-vector-check)
## Source and build
@@ -47,8 +60,8 @@ must permit the script; do not bypass a managed policy. POSIX customization:
CMake environment or a manual configuration. Don't switch generators in an
existing build directory.
All generators place the executable directly under [native/build](../native/build)
(with `.exe` on Windows), including Release builds with multi-config generators.
All generators place the executable directly under `native/build` (with `.exe` on
Windows), including Release builds with multi-config generators.
Upstream tests/benchmark/codegen are excluded from the default build: v1.2.1 does
not provide a tests-disable option. No benchmark or dataset-heavy fast-mode test
runs automatically.
@@ -177,18 +190,3 @@ ctest --test-dir native/build -C Release --output-on-failure
This test is OFF by default and is separate from the application's tests. Without
a built native binary, no native engine or native test is run by the application.
## Validation record
Verified on Windows x64 with CMake 3.26.4, MinGW GCC 13.1.0 and Node 24.22.0:
- Release native build with two compiler jobs; opt-in CTest upstream vector passed.
- Production TypeScript/application build passed.
- Ad-hoc actual-native light-mode checks passed: FIFO hashes, same-key reuse,
key changes and restoration, mining success/exhaustion, target equality and
big-endian comparison, LE8 nonce handling, 8192-byte blob, strict invalid-input
rejection, native uint64 nonce output, missing binary, queue bounds, crash
rejection, idempotent close, and stdin EOF.
- Both build scripts passed syntax checks.
Fast-mode allocation/runtime and Linux/macOS/MSVC builds were not run. The memory
and initialization cost of the full dataset remains an explicit opt-in.
+28 -12
View File
@@ -2,24 +2,39 @@
Implementation reference for the current tree, **not a public-mainnet specification or security certification**. See [README](../README.md) for setup/wallet commands and [native-engine details](randomx-native.md) for RandomX tooling. Consensus code: [shared serialization](../shared/testnet.ts), [validation](../server/testnet/consensus.ts), [DAG/persistence](../server/testnet/ledger.ts), [peer transport](../server/testnet/peers.ts), [HTTP](../server/testnet/http.ts), [miner](../server/testnet/miner.ts).
## Network, genesis and catalog
This document is for operators and developers who need the exact current protocol
rules. It is not a quick-start guide: consensus serialization and constants are
implementation-specific, and changing them can create a different chain.
- Network: `wikideck-randomx-testnet-2`, block version **2**. Genesis has no blocks, NFTs or work; first mined height is 1. No privileged issuance/admin mint key.
## Contents
- [Network, genesis and article selection](#network-genesis-and-article-selection)
- [Header, work and three distinct identities](#header-work-and-three-distinct-identities)
- [Targets, timestamps and epoch boundaries](#targets-timestamps-and-epoch-boundaries)
- [Native NFT transition and commitments](#native-nft-transition-and-commitments)
- [Fork choice, reorgs and storage](#fork-choice-reorgs-and-storage)
- [Fixed peers and HTTP surface](#fixed-peers-and-http-surface)
## Network, genesis and article selection
- Network: `wikideck-randomx-testnet-4`, block version **4**. Genesis has no blocks, NFTs or work; first mined height is 1. No privileged issuance/admin mint key. Version 4 has a new chain identity; prior-version ledger files and wallet backups are not compatible.
- `GENESIS_TIME = 1792411200` (Unix seconds). Let `S(x)` mean lowercase hex SHA-256 of UTF-8 `x`, and `J(x)` mean compact JavaScript `JSON.stringify(x)`; field order below is consensus-critical.
- `MAX_TARGET` is `0fff` followed by 60 `f` digits. `CHAIN_ID = S(J([NETWORK, CONSENSUS, GENESIS_TIME, MAX_TARGET, STARTER_ARTICLES.map(articleTuple)]))`, using the constants object's insertion order in the implementation.
- `MAX_TARGET` is `0fff` followed by 60 `f` digits. `CHAIN_ID = S(J([NETWORK, CONSENSUS, GENESIS_TIME, MAX_TARGET]))`, using the constants object's insertion order in the implementation. `CONSENSUS` identifies the random Wikipedia source and mint-statistics snapshot format.
- `GENESIS_HASH = S(NETWORK + ':genesis:' + CHAIN_ID)`; `INITIAL_KEY = S(NETWORK + ':initial-key:' + CHAIN_ID)`. Nodes independently derive the same manifest and reject other network/chain IDs.
- The [catalog](../shared/catalog.ts) contains four fixed **original summaries**: Albert Einstein (736), Moon (19331), Octopus (22780), Periodic table (23053). No live Wikipedia request determines consensus metadata. `articleTuple = [pageId, revisionId, title, extract, image-or-empty-string, url, source, category]`.
- The next catalog index is the big-endian integer `S(CHAIN_ID + ':article:' + previousHash)` modulo 4. Validation requires the exact selected tuple. NFT state stores that article's same `pageId`. Changing the catalog/constants currently changes genesis: coordinate a new chain, not a live catalog expansion or demo-asset migration.
- [Starter samples](../shared/catalog.ts) remain only as interface previews; they are not eligible for minting. Each mining-template request asks the fixed English Wikipedia API endpoint for up to ten random namespace-0 pages, with a plain-text extract and thumbnail. It makes at most three requests and returns a page with a usable extract and an HTTPS image on `upload.wikimedia.org` or `thumb.wikimedia.org`. A page may be minted repeatedly. There is no static fallback: if no pictured article or its statistics are available, template creation fails and the miner can retry.
- The selected `Article` snapshot and `statsSnapshot` are copied into the block header and covered by the miner's block signature and block ID. `articleTuple = [pageId, revisionId, title, extract, image-or-empty-string, url, source, category]`. The at-mint statistics contain capture time, metadata revision, page bytes, language-link count/truncation, six-month pageviews when available, and the derived Attention/Depth/Reach scores. Missing pageviews are represented as unavailable, never invented zeroes. The node forces a fresh enrichment request for this snapshot; it is captured when the mining template is prepared, since changing it after work begins would invalidate the proof. Current UI statistics are fetched separately and may reflect later edits.
- A repeated page still creates a distinct NFT: token identity includes the parent block, height, and owner. Validators check article/image and stats schemas, the page URL, Wikimedia HTTPS host, pageview summaries, derived scores, and block commitments, but do not constrain repeated page IDs.
- Validators do **not** contact Wikipedia during consensus validation. They verify the committed snapshot's structure and internal metric summaries, not the API provenance or ongoing existence of the page/image. A miner can construct a syntactically valid snapshot itself; this experimental design avoids making block acceptance dependent on an external service being online. The node's template endpoint fetches the article and fresh statistics for ordinary mining clients.
## Header, work and three distinct identities
`headerMessage(h)` is `J([NETWORK, version, chainId, height, previousHash, createdAt, articleTuple(article), owner, publicKey, target, key, transactionsRoot, stateRoot])`. It excludes the nonce. Hash its UTF-8 bytes with SHA-256 to obtain **32 raw bytes**, then append the nonce encoded as **8 unsigned little-endian bytes**. The resulting **40-byte mining blob** has nonce offset **32** (not a 32-bit nonce). Node's numeric interface restricts nonces to nonnegative safe integers; the native line protocol supports uint64.
`headerMessage(h)` is `J([NETWORK, version, chainId, height, previousHash, createdAt, articleTuple(article), statsSnapshotTuple, owner, publicKey, target, key, transactionsRoot, stateRoot])`. `statsSnapshotTuple` commits capture time, revision, size/language counts, ordered monthly views and summary values, plus the three derived scores. It excludes the nonce. Hash its UTF-8 bytes with SHA-256 to obtain **32 raw bytes**, then append the nonce encoded as **8 unsigned little-endian bytes**. The resulting **40-byte mining blob** has nonce offset **32** (not a 32-bit nonce). Node's numeric interface restricts nonces to nonnegative safe integers; the native line protocol supports uint64.
| Identifier | Exact construction / meaning |
| --- | --- |
| Block ID (`hash`) | SHA-256 of the raw mining blob, lowercase hex. Does not include ECDSA signature bytes or the separate `powHash` field. |
| RandomX proof (`powHash`) | Native RandomX of the mining blob using the 32-byte epoch key. Determines work eligibility and game rarity/stats, not the NFT ID. |
| NFT ID (`tokenId`) | `S(J([NETWORK, CHAIN_ID, 'mint', previousHash, height, owner, article.pageId]))`. Independent of nonce/proof/signature, avoiding a circular dependency through state commitment. |
| NFT ID (`tokenId`) | `S(J([NETWORK, CHAIN_ID, 'mint', previousHash, height, owner, article.pageId, statsSnapshotTuple]))`. The capture time and page statistics help distinguish repeated mints of the same page. Independent of nonce/proof/signature, avoiding a circular dependency through state commitment. |
Interpret RandomX output and target as **unsigned big-endian** integers; accept `powHash <= target` including equality. This differs from Monero's target convention. The initial/easiest threshold accepts exactly **1/16** of uniformly distributed hashes (expected 16 attempts). No fake or browser PoW is accepted by the production node.
@@ -48,7 +63,7 @@ Per-block work is `floor(2^256 / (target + 1))`; accumulate from genesis work ze
On reorg, reconstruct ownership from the winning branch. Orphan mints disappear from canonical ownership; disconnected transfers are reconsidered before existing pending entries, in order, only if still valid and not already committed. Conflicting, immature or orphan-issuance transfers are discarded. No checkpoints/finality or rollback protection beyond observed work; an isolated node has no peer-backed security.
The [ledger](../server/testnet/ledger.ts) stores the entire DAG and pending pool in [the testnet ledger](../data/testnet/ledger.json), under `WIKIDECK_DATA_DIR`'s testnet subdirectory. Defaults use project data; Docker uses /app/data. Disk caps: **128 MiB, 10,000 total blocks including side branches**; blocks: **64 KiB and 64 transfers**. Each in-memory DAG node stores a full NFT-state snapshot: potentially **quadratic memory growth**, plus full-file rewrite cost. These are experimental stop limits, not scalable storage or a public DoS defense.
The [ledger](../server/testnet/ledger.ts) stores the entire DAG and pending pool in `data/testnet/ledger.json`, under the `WIKIDECK_DATA_DIR` testnet subdirectory. The file is created when the node first writes ledger data. Defaults use project data; Docker uses `/app/data`. Disk caps: **128 MiB, 10,000 total blocks including side branches**; blocks: **64 KiB and 64 transfers**. Each in-memory DAG node stores a full NFT-state snapshot: potentially **quadratic memory growth**, plus full-file rewrite cost. These are experimental stop limits, not scalable storage or a public DoS defense.
Mutations are serialized (queue cap 256); persist via a synchronized temporary file and atomic rename before exposing new state. Restart independently revalidates every saved proof and transaction; malformed, oversized or mismatched files fail closed without automatic reset. HTTP startup exclusively creates a data-directory lock named testnet-node.lock. It is removed on normal shutdown, **not automatically after a crash**: stop and verify all owning processes have exited before manual stale-lock removal. Never bypass a live lock or share a writer directory. Back up stopped-node volumes/files and wallet exports separately; destructive volume reset cannot recover assets from a wallet alone.
@@ -61,10 +76,11 @@ Sync polls every **5 seconds**, checks network/chain ID, imports at most **32**
| Route | Purpose |
| --- | --- |
| `GET /healthz` | Process health/network, not sync or consensus security. |
| `GET /api/state`, `/api/chain` | Canonical blocks, mined/pending transfers, tip/work and next article. |
| `GET /api/nfts/:id`, `/api/peers` | Canonical NFT owner/sequence/confirmations/metadata; configured peer status. |
| `GET /api/wikipedia/:pageId` | Optional cached live enrichment for catalog IDs: categories, views, size, languages and display-only scores. Never a consensus input. |
| `POST /api/mining/template`, `/api/blocks` | Public miner identity to template; independently validated signed block submission. |
| `GET /api/state`, `/api/chain` | Canonical blocks, mined/pending transfers, tip/work and peer count. |
| `GET /api/nfts/:id`, `/api/peers` | Canonical NFT owner/sequence/confirmations, article and at-mint statistics; configured peer status. |
| `GET /api/wikipedia/:pageId` | Cached current enrichment for positive English Wikipedia page IDs: categories, views, size, languages and display-only scores. Never a consensus input. |
| `POST /api/mining/template` | Rate-limited public miner identity; fetches a random article with a picture plus fresh at-mint statistics. Returns 503 when either cannot be fetched. |
| `POST /api/blocks` | Independently validates a signed block, proof, article snapshot, required Wikimedia image URL, and statistics snapshot consistency. Repeated page IDs are accepted. |
| `POST /api/transfer` | Signed transfer admission; 202 means pending. |
| `GET /p2p/status`, `/p2p/blocks?offset=0`, `/p2p/mempool` | Manifest/size/tip, 32-block inventory pages, pending transactions. |
| `POST /p2p/block`, `/p2p/transaction` | Same validation as local block/transfer submission. |