Validator Attestation
Fiecare bloc post-fork cere cel puțin 1 semnătură de validator selectat reputation-weighted din MinerNFT activi. Asta înlocuiește încrederea unilaterală în proposer cu un model byzantine fault-tolerant minimal.
Workflow
Proposer Validators
│ │
├─ build candidate block │
│ │
├─ selectValidators( │
│ miners, prevHash, height, │
│ count=1, proposer) │
│ │
├─ self-attest dacă self ∈ eligible (liveness)
│ │
├─ requestAttestations() ─────>│
│ ├─ verify candidate
│ ├─ buildAttestation()
│ <─────────── Attestation ────┤
│ │
├─ chain.addBlock( │
│ proposer, txs, { │
│ attestations: [...] │
│ }) │
│ │
└─ broadcastBlock() ──────────>│
├─ receiveBlock()
└─ validate attestations
Selection: reputation-weighted VRF
selectValidators(miners, prevBlockHash, height, count, proposerAddress?) deterministic:
- Filter: miners cu
reputationScore >= 1 - Sort: deterministic după address (toți peers calculează identic)
- Weight:
reputation^1.5(favorizează moderat cei buni, nu winner-takes-all) - Liveness: dacă
proposerAddresse eligible, mereu primul selectat (rezolvă cazul VRF picks offline peer) - Restul slots: VRF cu seed
sha256(prevBlockHash + height + index)
Cod sursă: packages/core/src/consensus/validator-select.ts.
De ce reputation^1.5?
- Liniar (
^1) → cei cu rep 200 au exact 2× șansa celor cu rep 100. Domină whales. - Quadratic (
^2) → 4× șansa. Total dominat de top mineri. ^1.5→ ~2.83× șansa. Echilibrat: favorizează experiență, dar permite mineri noi să atesteze.
Consensus target (stateless attestation)
Problema: validatorul semnează blocul ÎNAINTE ca proposer-ul să fi aplicat tranzacțiile (deci înainte de stateRoot). Hash-ul final al blocului include stateRoot. Dacă validatorul ar semna block.hash, ar fi imposibil de calculat.
Soluția: validatorul semnează un consensus target stabil:
consensusTarget = sha256(
index | previousHash | proposer | txRoot | tokensBurned | tokensConsumed
)
Toate componentele sunt cunoscute de proposer înainte de apply și verificate de validator. stateRoot și block.hash nu sunt necesare pentru attestation.
Cod: packages/core/src/consensus/attestation.ts:computeAttestationTarget.
Attestation structure
interface Attestation {
validatorAddress: string; // adresa validator-ului
validatorPublicKey: string; // pubkey Ed25519 pentru verify
blockHash: string; // consensus target (hash stabil)
signature: string; // Ed25519 sig peste hash(target + addr + timestamp)
timestamp: number;
}
Semnătura: ed25519.sign(privKey, sha256(consensusTarget + address + timestamp))
P2P protocol
Nou subprotocol în libp2p:
const ATTESTATION_REQUEST_PROTOCOL = "/ombra/attestation-request/1.0.0";
// Proposer trimite
interface AttestationRequest {
candidateBlock: Block;
eligibleValidators: string[];
}
// Validator răspunde
interface AttestationResponse {
attestation: Attestation | null;
reason?: string;
}
Cod: packages/node/src/p2p.ts:requestAttestations.
Validator handler în miner-node:
onAttestationRequest: async (req) => {
if (!req.eligibleValidators.includes(myAddress)) return null;
const target = computeAttestationTarget({
index: req.candidateBlock.index,
previousHash: req.candidateBlock.previousHash,
proposer: req.candidateBlock.proposer,
txRoot: req.candidateBlock.txRoot,
tokensBurned: req.candidateBlock.tokensBurned,
tokensConsumed: req.candidateBlock.tokensConsumed,
});
return buildAttestation(target, myAddress, myPubKey, myPrivKey);
}
Reward validator
Per attestare:
- Base:
0.0001 OMBRA(fix, încurajează liveness) - Bonus:
5% × total_fee_block(din fee-urile task-urilor din bloc) - Total per validator:
(VALIDATOR_BASE_REWARD + bonus) / numValidators
Cod: calculateValidatorReward(totalFeesInBlock, numValidators).
Distribuit automat la applyBlockTransactions() post-fork — în distributeValidatorRewards().
Liveness guarantee (proposer mereu eligible)
Important: dacă VRF pur alege un validator offline, blocul nu poate fi atestat → mainnet se oprește. Soluție: proposer-ul e mereu inclus ca prim slot în eligibleValidators (când e miner cu rep > 0). Asta asigură self-attest fallback:
// În MinerLoop v2
if (eligibleValidators.includes(proposerAddress)) {
const selfAtt = buildAttestation(target, proposerAddress, pubKey, privKey);
attestations.push(selfAtt);
}
// Apoi cere remote attestations pentru count > 1 ...
Asta nu compromite securitatea: blocul tot trebuie să fie valid (toate tx-urile corecte, burn calculat right, stateRoot match) — orice altă noță poate respinge la receiveBlock().
Capcane
- Attestation replay — nu, fiecare attestation include
blockHash + timestamp→ diferit per bloc per moment. - Validator collusion — în MVP cu 1 validator, riscul există (validator + proposer pot colabora). Mitigat prin minim reputation; scalat cu mai mulți validatori în viitor.
- Offline validator — rezolvat prin proposer-always-eligible (mai sus).
- Reorg pendinte — attestation valabil doar pentru blockHash specific; reorg invalidează automat.