Skip to main content

VerificationEngine

The VerificationEngine (Solidity name NotareumVerificationEngine) drives the attestation lifecycle for registered resources. A resource owner requests verification at a target level; validators from the staking contract submit attestations; when quorum is met the engine finalizes the request and records the resulting verification level on the resource. The contract is the sole mutator of verification state.

Verification levels

Higher levels require more validators and stricter quorum, reducing the probability of a coordinated bad attestation but raising the fee and latency.

Request lifecycle

Requests that never reach quorum before deadline expire; the fee is partially refunded (see FeeManager). Finalized requests enter a fixed dispute window (default 72 hours); a guilty ruling during the window reverts the verification level and slashes offending validators.

Data structures

Attestations are indexed per (requestId, validator) with a uniqueness guard.

Functions

requestVerification

Opens a verification request for a registered resource at the target level. The caller pays the level-dependent fee (see FeeManager). Reverts if:
  • The resource does not exist.
  • The resource is revoked.
  • A non-expired open request already exists for the resource.
  • The fee is insufficient.
Emits VerificationRequested(requestId, resourceId, requester, targetLevel, deadline). The deadline is block.timestamp + requestTTL[targetLevel], where the TTL defaults to 7 days for Basic, 10 days for Enhanced, 14 days for Institutional.

submitAttestation

Submits one validator’s attestation. Reverts if:
  • The caller is not a registered validator in good standing (checked via ValidatorStaking.isActive(msg.sender)).
  • The caller has already attested for this requestId.
  • The validator’s tier is below the minimum for targetLevel.
  • The request is not in Open state.
  • The request deadline has passed.
The validator’s tier must meet the minimum for the target level: Emits AttestationSubmitted(requestId, validator, approve, evidenceHash).

finalizeRequest

Callable by anyone once quorum is reached. Computes whether the request met both the required totalValidators and the required approval ratio:
A request finalizes as approved when:
On approval the engine calls into NotaRegistry.setVerificationLevel(resourceId, targetLevel). On rejection the resource stays at its prior level. Validators receive their attestation rewards from ValidatorStaking.creditAttestationReward. Emits VerificationFinalized(requestId, status, finalLevel).

openDispute

Within the dispute window, any veNOTA holder can open a dispute by posting a bond (the bond scales with targetLevel). The engine transitions the request to Disputed. Governance (or a fast-track dispute committee) resolves via resolveDispute.

resolveDispute

If the disputant wins, the engine reverts the verification level, triggers slashing on the majority-approving validators via SlashingManager.slash(validator, reason), and returns the bond plus a portion of the slashed amount to the disputant. If the disputant loses, the bond is split between the approving validators and the treasury.

Quorum math, formally

For a target level with n required validators and ratio r/d:
Using integer comparisons avoids rounding pitfalls. The engine checks approveCount * d >= attestCount * r which is exact.

Byzantine fault margin

With n = 15 and honest-validator probability p_h = 0.95, the probability that at least 4 of 15 validators are Byzantine (breaking 75% approval) is:
At p_h = 0.95, P_fail ≈ 7.4e-4. With the Institutional tier’s economic stake of 1M NOTA per validator, the capital required to mount a successful attack exceeds 4M NOTA across coordinated validators, and the SlashingManager slashes the full stake on successful dispute.

Dispute window

Dispute windows default to:
  • Basic: 24 hours
  • Enhanced: 48 hours
  • Institutional: 72 hours
During the window, the verification level is live but marked disputable: true in view functions. Wallets rendering a resource mid-window can choose to surface a “verification pending confirmation” signal. After the window closes with no disputes, the status becomes permanent (modulo owner revocation or later slashing events).

Events

Gas notes

  • requestVerification: ≈120k gas (single SSTORE + fee transfer).
  • submitAttestation: ≈95k gas per call (struct write + counter updates).
  • finalizeRequest: ≈140k gas base + ≈20k per attestation in the reward distribution loop. For Institutional (15 validators), expect ≈440k gas.

SDK usage

Invariants

  1. A validator submits at most one attestation per requestId.
  2. A request transitions out of Open exactly once.
  3. finalizeRequest never increases a resource’s level on rejection; it only increases on approval.
  4. Slashing on dispute targets only the validators whose attestation matches the disproven side.