Protocol overview
MLabChain v0.3 is a signed, replay-validated ledger for deterministic machine-learning work. The protocol has two independent mechanisms that are deliberately kept apart:
- Chain sealing. A low-difficulty SHA-256 Proof-of-Work combined with an Ed25519 signature from the block producer. This is the Sybil-resistance signal and the ordering mechanism.
-
Scientific work. A separate transaction type,
ML_WORK, that commits a challenge, a model, an architecture, and metrics. This is the monetary mechanism.
Changing the ML verifier does not affect block sealing. Changing the PoW difficulty does not affect ML rewards. This separation is what allows the protocol to make honest claims about each piece.
Protocol version
PROTOCOL_VERSION = 3. A node refuses to open a
SQLite database whose schema_version does not
match. Migration from v0.2 requires an explicit snapshot
ceremony described in
Architecture § Migration.
Challenge registry
A challenge is an on-chain object that pins a scientific task. Any account can register a challenge, subject to a bond and a per-proposer limit. Once registered, the challenge's parameters are immutable.
Registered fields
| Field | Type | Meaning |
|---|---|---|
| challenge_id | string | Unique identifier |
| manifest_hash | hex64 | SHA-256 of the canonical manifest |
| dataset_hash | hex64 | Commitment to the dataset |
| verifier | string | Verifier class (MLabChain-linear-v4) |
| metric | string | NMSE |
| split_rule | string | first_fraction |
| n_samples | u64 | Total samples in the dataset |
| n_features | u64 | Feature dimensionality |
| train_fraction_ppm | u64 | Train fraction in ppm (e.g. 800000 = 0.8) |
| max_epochs | u64 | Bound on submitted training runs |
| baseline_scaled | u64 | Baseline NMSE × 1e9 (fixed at 1e9) |
| budget | u64 | Finite Mera budget, in atomic units |
| created_height | u64 | Block height at registration |
| activation_height | u64 | created_height + CHALLENGE_MATURITY |
| expiry_height | u64 | created_height + CHALLENGE_LIFETIME |
Lifecycle
- Registration. A
CHALLENGE_REGISTERtransaction pays the bond and creates the challenge record. It is not active yet. - Maturity. After
CHALLENGE_MATURITY = 8blocks, the challenge acceptsML_WORKsubmissions. - Active. Until
expiry_height, submissions that improve the frontier are paid from the challenge budget. - Closed. After expiry, no further submissions are accepted. The remaining budget is not reclaimed — it stays reserved for the challenge forever.
Anti-farming constraints
- Minimum
n_samples: 1000 - Maximum
n_samples: 100,000,000 - Maximum
n_features: 4096 train_fraction_ppmin [100000, 999999]- Maximum
max_epochs: 100,000 - Per-proposer active challenges: at most 8
ML_WORK transaction
An ML_WORK transaction is the sole path to monetary
issuance. It commits the training claim and the metadata that
every node uses to validate it.
Fields
| Field | Meaning |
|---|---|
| sender | Submitting account address |
| challenge_id | The registered challenge |
| manifest_hash | Must match the registered challenge |
| dataset_hash | Must match the registered challenge |
| model_hash | SHA-256 of the trained model artifact |
| arch_hash | SHA-256 of the architecture description |
| ops | Symbolic operation count (must match the formula) |
| n_train | Derived from n_samples × train_fraction_ppm |
| n_features | Must match the challenge |
| epochs | Must be ≤ challenge.max_epochs |
| nmse_scaled | Model NMSE × 1e9 |
| baseline_scaled | Must match challenge.baseline_scaled |
| work_reward | Claimed reward in atomic units |
| wall_ms | Reported training time (audit only) |
| lswu_micro | LSWU score × 1e6 (audit only) |
| nonce | Account nonce |
| fee | Work fee in atomic units |
| public_key | Ed25519 public key of the sender |
| signature | Ed25519 signature over the canonical message |
Canonical signing message
ML_WORK-V3|sender|challenge_id|manifest_hash|dataset_hash|
model_hash|arch_hash|ops|n_train|n_features|epochs|
nmse_scaled|baseline_scaled|work_reward|wall_ms|lswu_micro|
nonce|fee|public_key|note
Fields are joined with |. The signature is Ed25519
over the UTF-8 bytes of that string. The transaction ID is
SHA256(message + "|" + signature).
Reward arithmetic
Rewards are computed in integer arithmetic in the C++ core. No floating-point value is ever used in a state transition. This is deliberate: a two-node network must agree on the exact issuance, byte for byte, block for block.
Quality
The improvement of a model over the baseline is scaled to a million parts:
# fixed-point NMSE, scaled by 1e9
quality_ppm = clamp(
(baseline_scaled − nmse_scaled) × 1,000,000 / baseline_scaled,
0,
1,000,000
)
Marginal delta
Reward is measured against the challenge's current best accepted quality, not against the original baseline:
delta_q = quality_ppm − challenge.best_quality_ppm
If delta_q ≤ 0, the submission is rejected with
"ML result does not improve challenge best". There
is no partial credit for matching or falling short.
Reward
reward = min(
MAX_WORK_REWARD,
ops × delta_q × ATOMIC_PER_MERA / (OPS_PER_MERA × 1,000,000)
)
With ATOMIC_PER_MERA = 100,000,000,
OPS_PER_MERA = 10,000,000, and
MAX_WORK_REWARD = 50 × ATOMIC_PER_MERA.
Constraint stack
A valid reward must satisfy all of:
reward == expected_reward(ops, delta_q)(exact match)reward ≤ challenge.budget_remainingtotal_supply + reward ≤ MAX_SUPPLYops == epochs × n_train × (3 × n_features + 4)- The claim
(challenge, dataset, model)has not been paid before
What the core does not verify
The C++ core checks that the reward is arithmetically
consistent with the submitted metrics. It does not check
that the metrics are honest. A malicious submitter could sign
an ML_WORK transaction with a fabricated
nmse_scaled and pass the core's checks. The only
defence is the Python verify-ml audit, which
re-executes the training. See
Proof-of-Scientific-Work.
Accounts and nonces
Accounts are identified by an address derived from an Ed25519 public key:
# Standard account
payload = SHA256(public_key_bytes)[:40]
checksum = SHA256("MERA-ADDR-V3|MERA1" + payload)[:8]
address = "MERA1" + payload + checksum
# Multisig account
sorted_keys = sort(public_keys)
material = "MSIG|" + threshold + "|" + join(sorted_keys, ",")
payload = SHA256(material)[:40]
checksum = SHA256("MERA-ADDR-V3|MERA2" + payload)[:8]
address = "MERA2" + payload + checksum
Every account has a monotonically increasing nonce. A
transaction from an account is valid only if
tx.nonce == account.nonce + pending_count + 1, so
the mempool can hold multiple pending transactions from the same
sender without ambiguity.
Key rotation
A KEY_ROTATE transaction changes the authorized
public key without changing the address. After rotation, only
the new key can authorise transfers, block proposals, and ML
submissions from that account.
Multisig
A MULTISIG_TRANSFER requires m
signatures from an n-key set. Keys must be sorted
canonically and unique. The threshold and key set are included in
the transaction and checked against the derived address.
Blocks and fork choice
A block header commits to the previous block, the Merkle root of its transactions, the producer address, the producer's Ed25519 signature, and the cumulative chain work.
# Block header (canonical form)
MERA-BLOCK-V3|height|timestamp_ms|previous_hash|merkle_root|
difficulty|nonce|producer|producer_pubkey|producer_signature|
total_ops|total_rewards
# Block hash
block_hash = SHA256(header)
Producer signature
The producer signs a message that excludes the nonce and the block hash, so the signature is stable while the nonce is being ground:
MERA-BLOCK-PROPOSAL-V3|height|timestamp_ms|previous_hash|
merkle_root|difficulty|producer|producer_pubkey|
total_ops|total_rewards
Fork choice
Nodes accept the chain with the highest cumulative work. Ties break by lower tip hash. The rule is applied as:
- Compute
chain_workas the sum of2^(4 × difficulty)over all blocks. - A candidate chain is valid only if it replays from genesis without error.
- If
candidate.work > local.work, replace. If equal, lower tip hash wins. - On replacement, orphaned transactions from pending are re-added to the mempool.
Timestamp rules
- Must be strictly greater than the median of the last 11 block timestamps.
- Must be no more than
MAX_FUTURE_MS = 120,000ms ahead of the node's clock.
Difficulty retarget
Every DIFFICULTY_WINDOW = 16 blocks, the difficulty
is adjusted toward a target block time of
TARGET_BLOCK_MS = 60,000. The adjustment is bounded
to a single step of ±1 and clamped to [1, 8].
target_span = TARGET_BLOCK_MS × DIFFICULTY_WINDOW
elapsed = block[parent].timestamp_ms − block[start].timestamp_ms
if elapsed < target_span / 2:
difficulty += 1
elif elapsed > target_span × 2:
difficulty -= 1
The retarget is deterministic: given the same chain, every node computes the same difficulty for the next block. This is required for consensus and is checked during validation.
Governance timelock
A governance multisig, defined at genesis, can schedule changes
to a bounded set of parameters. The change takes effect after a
delay of GOVERNANCE_TIMELOCK = 32 blocks.
Modifiable parameters
min_feemin_work_feechallenge_bond
Immutable parameters
- Maximum supply
- Faucet enablement
- Reward formula constants
- Block size and transaction size limits
The immutable set is enforced by the code: there is no
GOV_PARAM key that can modify them.
Wire protocol
MLabChain's P2P protocol is a line-based request/response. Each
message is a single line terminated by \n. Fields
within a message are separated by |, and any field
that may contain | is hex-encoded before transport.
| Message | Request | Response |
|---|---|---|
| PING | PING | PONG |
| STATUS | STATUS | STATUS|height|tip|chain_work |
| GETCHAIN | GETCHAIN | Sequence of BLOCK|wire, ending with END |
| TX relay | TX|wire | OK or ERROR|reason |
| BLOCK relay | BLOCK|wire | OK, SYNC_REQUIRED, or ERROR|reason |
The wire format is deliberately simple: no framing, no encryption, no version negotiation, no peer authentication. The reference protocol is a testnet implementation. See Security Notes for what a production protocol would need to add.
Wire encoding for transactions
A transaction is serialised as 36 hex-encoded fields separated
by |. The full order is:
type|sender|recipient|amount|fee|nonce|challenge_id|
manifest_hash|dataset_hash|model_hash|arch_hash|ops|
n_train|n_features|epochs|nmse_scaled|baseline_scaled|
work_reward|wall_ms|lswu_micro|public_key|signature|note|
train_fraction_ppm|max_epochs|budget|challenge_verifier|
challenge_metric|challenge_split_rule|new_public_key|
multisig_threshold|multisig_keys|multisig_sigs|
gov_key|gov_value|gov_effective_height
Protocol constants
Supply and decimals
| Constant | Value |
|---|---|
| ATOMIC_PER_MERA | 100,000,000 |
| MAX_SUPPLY | 100,000,000 MERA |
| OPS_PER_MERA | 10,000,000 |
| MAX_WORK_REWARD | 50 MERA |
Fees
| Constant | Devnet | Testnet / Mainnet |
|---|---|---|
| MIN_TX_FEE | 1,000 | 10,000 |
| MIN_WORK_FEE | 10,000 | 100,000 |
| CHALLENGE_BOND | 100,000 | 1,000,000 |
Limits
| Constant | Value |
|---|---|
| MAX_BLOCK_TX | 2,048 |
| MAX_MEMPOOL_TX | 20,000 |
| MAX_PENDING_PER_SENDER | 32 |
| MAX_TX_BYTES | 65,536 |
| MAX_BLOCK_BYTES | 1,048,576 |
| MAX_NOTE_BYTES | 4,096 |
| MAX_OPS_PER_WORK | 10^15 |
| MAX_CHALLENGE_BUDGET | 1,000 MERA |
| MAX_SIGNERS | 16 |
Timing
| Constant | Value |
|---|---|
| TARGET_BLOCK_MS | 60,000 |
| DIFFICULTY_WINDOW | 16 |
| CHALLENGE_MATURITY | 8 blocks |
| CHALLENGE_LIFETIME | 1,000 blocks |
| GOVERNANCE_TIMELOCK | 32 blocks |
| MAX_FUTURE_MS | 120,000 |
Difficulty bounds
| Constant | Value |
|---|---|
| BASE_DIFFICULTY | 2 |
| MIN_DIFFICULTY | 1 |
| MAX_DIFFICULTY | 8 |
Reference implementation
The authoritative implementation of every constant and every
rule on this page is cpp/mera_core.cpp. When the
code and this page disagree, the code is correct and this page
is a bug report waiting to happen.