XChain Platform Action - NODEPROOF
This action records an on-chain, quorum-signed verdict asserting which validators answered a periodic possession challenge, proving they run a real coin full node rather than mirroring the decoder/indexer DBs via xchain-sync.
PARAMS
| Name | Type | Description |
|---|---|---|
VERSION |
String | Format Version |
CHALLENGE_ID |
String | 64-hex challenge identifier, derived from the epoch (see Rules) |
EPOCH_HEIGHT |
Integer | Block height of the challenge epoch; must be a multiple of CHALLENGE_INTERVAL_BLOCKS |
PASS_COUNT |
Integer | Number of validators that produced the correct answer |
PASS_PK_i |
String | 64-hex Ed25519 public keys of passing validators (one per passing validator) |
SIG_COUNT |
Integer | Number of verifier signatures over the canonical message |
PUBKEY_i |
String | 64-hex Ed25519 public key of each verifier |
SIG_i |
String | 128-hex Ed25519 signature from the corresponding verifier |
Formats
Version 0 - Verdict (validator-broadcast)
VERSION|CHALLENGE_ID|EPOCH_HEIGHT|PASS_COUNT|PASS_PK_1|...|PASS_PK_n|SIG_COUNT|PUBKEY_1|SIG_1|...|PUBKEY_m|SIG_m
Examples
NODEPROOF|0|a3f1e9...c42|1008|2|aa01bb...|cc02dd...|3|aa01bb...|sig1aaa...|cc02dd...|sig2bbb...|ee03ff...|sig3ccc...
A verdict for epoch height 1008, listing 2 passing validators, signed by 3 eligible verifiers
NODEPROOF|0|7b8d2a...f91|2016|1|ff00aa...|2|ff00aa...|sig4ddd...|bb11cc...|sig5eee...
A verdict for epoch height 2016 with 1 passing validator, signed by 2 verifiers (meeting 2f+1 quorum from 2 eligible)
Rules
- Available on BTC chain only; scope extends to other chains in a later version
EPOCH_HEIGHTmust be a multiple ofCHALLENGE_INTERVAL_BLOCKSand must not exceed the currentBLOCK_INDEXBLOCK_INDEX - EPOCH_HEIGHTmust not exceedVERDICT_ACCEPT_WINDOW_BLOCKS; verdicts must land promptly to bound replay and reorg exposure- The challenge target block is
EPOCH_HEIGHT - CONFIRM_DEPTH; the target must be non-negative CHALLENGE_IDmust equalSHA256(NETWORK ":" EPOCH_HEIGHT ":" ledger_hash(EPOCH_HEIGHT) ":" target)as recomputed by the indexer; any mismatch or unknown epoch block causes rejection- Eligible verifiers at
snapshotBlock = EPOCH_HEIGHTareverified_full_nodes(EPOCH_HEIGHT)unionGENESIS_VERIFIERS; quorum isfloor(2 * V / 3) + 1whereVis the eligible count;V == 0causes rejection (feature dormant untilGENESIS_VERIFIERSis seeded) - Each signature must come from a member of the eligible set, must be a valid Ed25519 signature over the canonical message, and must not be duplicated; the total count of valid, deduplicated signers must meet quorum
- For each
PASS_PKthat holdsfull_nodecapability stake atEPOCH_HEIGHT, the indexer writes afull_node_verificationsrow; the insert is idempotent on(epoch_height, signing_pubkey) - This action is validator-broadcast; it is not VM-emitted and not meaningful for end users
- This action writes no ledger rows; reward eligibility is derived later during
PRICEfinalization
Canonical signed message
The canonical message signed by each verifier is:
CHALLENGE_ID "|" EPOCH_HEIGHT "|" sort(lower(PASS_PK)) joined by ","
At and above the EQUIV flag-day, this canonical string is wrapped with the uniform header (TAG = XNODEPROOF, ROUND_ID = CHALLENGE_ID, VIEW = 0). Below the flag-day, the bare bytes are used. The indexer _buildCanonical and the hub publisher must agree byte-for-byte; this is consensus-critical.
Reward eligibility
A staking source earns the full-node reward tranche at block B only if:
- It has a
passedfull_node_verificationsrow withblock_indexin(B - PROOF_WINDOW_BLOCKS, B]and still holdsfull_nodecapability stake atB(this makes the node verifier-eligible: able to vouch in later verdicts) - Over the trailing
REWARD_PASS_WINDOW_BLOCKS, the source answered at leastMIN_PASS_RATE_BPSbasis points of the challenge epochs that actually produced a verdict (the denominator counts only epochs the federation ran, so an outage never costs anyone)
The gate uses integer math: passed_epochs * 10000 >= MIN_PASS_RATE_BPS * total_epochs. The verified set is deduplicated by staking source (one operator, one full node, one share). A validator that does not run a full node simply fails the challenges, earns nothing from the full-node tranche, and is never penalised: its full_node bond is untouched and there is no failed-challenge slash.
See db.getFullNodeParticipation and PRICE for the reward derivation.
The Derived Challenge
The challenge is derived deterministically from the chain, not broadcast. Every node (full and light, including the indexer) can recompute a challenge’s identity from on-chain data; only a node with a real coin full node can compute the answer. This avoids leader election, per-challenge fees, and the non-determinism of “who broadcasts the challenge.”
For each epoch height E where E % CHALLENGE_INTERVAL_BLOCKS == 0:
seed = ledger_hash(E), the indexer’s stored per-block ledger hash atE(deterministic and identical across every honest node)target = E - CONFIRM_DEPTH, a buried block (reorg-stable) whose contents the possession query targetschallenge_id = SHA256(NETWORK ":" E ":" seed ":" target)
Full-node verifiers map seed to a concrete query inside the target block: tx_index = int(seed) mod txcount, vout = int(seed[16:]) mod voutcount, answer = that output’s scriptPubKey (hex). This datum is provably absent from a synced mirror: the decoder stores no scriptPubKey, no raw tx/block bytes, no non-XChain transactions, and no UTXO set. The within-block mapping and the answer are resolved entirely by the verifiers (who have the block); the indexer never needs them and only recomputes challenge_id, trusting the quorum.
sequenceDiagram
participant Chain
participant Verifiers as Full-node verifiers
participant Indexer
Chain->>Chain: epoch height E reached, E mod CHALLENGE_INTERVAL_BLOCKS = 0
Note over Chain: seed = ledger_hash(E), target = E - CONFIRM_DEPTH,<br>challenge_id = SHA256(NETWORK, E, seed, target)
Verifiers->>Verifiers: map seed to target block, tx_index = int(seed) mod txcount,<br>vout = int(seed[16:]) mod voutcount
Verifiers->>Verifiers: answer = that output's scriptPubKey, read from a real full node
Verifiers->>Verifiers: sign canonical message, CHALLENGE_ID, EPOCH_HEIGHT, sorted PASS_PK list
Verifiers->>Indexer: NODEPROOF v0 verdict, PASS_PK list plus verifier signatures
Indexer->>Indexer: recompute challenge_id, verify each signature against the eligible verifier set
Indexer->>Indexer: check quorum, floor(2 x V / 3) + 1 of eligible verifiers
alt quorum met
Indexer->>Indexer: record full_node_verifications row for each passing PASS_PK
else quorum not met
Indexer->>Indexer: reject verdict
end
Config (STAKING.CAPABILITIES.full_node, FULLNODE)
| Key | Default | Meaning |
|---|---|---|
CAPABILITIES.full_node.MIN_STAKE |
2000.00000000 |
Entrance stake to claim the capability |
FULLNODE.CHALLENGE_INTERVAL_BLOCKS |
144 |
Epoch cadence (~daily on BTC) |
FULLNODE.CONFIRM_DEPTH |
100 |
Target-block burial depth (reorg safety) |
FULLNODE.PROOF_WINDOW_BLOCKS |
300 |
How long a passed proof keeps a node verifier-eligible (able to vouch in later verdicts) |
FULLNODE.VERDICT_ACCEPT_WINDOW_BLOCKS |
24 |
Max lag from epoch to accepted verdict |
FULLNODE.REWARD_SHARE |
'0' |
Fraction of the oracle-round budget routed to full nodes (raise to '0.25' to enable; see PRICE) |
FULLNODE.REWARD_PASS_WINDOW_BLOCKS |
1008 |
Trailing window the reward pass-rate is measured over (= 7 daily challenge epochs) |
FULLNODE.MIN_PASS_RATE_BPS |
7000 |
Min pass rate to earn the tranche, in basis points (7000 = 70%, i.e. pass at least 5 of 7, miss up to 2) |
FULLNODE.GENESIS_VERIFIERS |
[] |
Bootstrap verifier pubkeys (operator-seeded) |
Reorg Safety
full_node_verifications rows carry the verdict’s action_index and block_index. The generic action-rollback removes them on reorg exactly like any other action’s rows. The (epoch_height, signing_pubkey) unique key makes re-recording on replay idempotent.
Bootstrap and Degradation
With no seeded GENESIS_VERIFIERS and no verified nodes, no verdict can reach quorum, so the verified set is empty and the full-node tranche rolls back into the base tranche (see PRICE). Behavior is identical to pre-feature. The mechanism activates as the operator seeds genesis verifiers and independent full nodes join and get verified, at which point verified nodes (not just genesis) can vouch for new joiners.
Notes
- The verified set earns the full-node tranche of the oracle-round reward; light mirrors keep the base tranche only
- The only thing that reaches the wire is the federation’s signed verdict over who answered correctly, exactly the trust model of
ATTEST v1(the indexer verifies signatures and schedule; the federation verifies the off-chain fact) PUBKEYis the consensus token name for verifier public keys (consistent with ANCHOR, ATTEST, and PRICE)- See
ATTESTfor the signature-verification and responsible-set template this models on - See
PRICEfor oracle-round reward derivation (two-tranche split) andvalidator_rewards/COLLECTfor the reward ledger and claim flow
Copyright © 2025–2026 Dankest, LLC
Based on XChain Platform by Dankest, LLC – https://dankest.llc
Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-or-later) with a commercial license available for proprietary use.
You may use, modify, and distribute this material under the terms of the License. See LICENSE and NOTICE for full terms. See the licensing overview.