Skip to main content

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:

  1. Filter: miners cu reputationScore >= 1
  2. Sort: deterministic după address (toți peers calculează identic)
  3. Weight: reputation^1.5 (favorizează moderat cei buni, nu winner-takes-all)
  4. Liveness: dacă proposerAddress e eligible, mereu primul selectat (rezolvă cazul VRF picks offline peer)
  5. 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

  1. Attestation replay — nu, fiecare attestation include blockHash + timestamp → diferit per bloc per moment.
  2. 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.
  3. Offline validator — rezolvat prin proposer-always-eligible (mai sus).
  4. Reorg pendinte — attestation valabil doar pentru blockHash specific; reorg invalidează automat.

Vezi și