// SPDX-License-Identifier: MIT pragma solidity 0.8.23; /** * @title AereValidatorManifest — public commitment registry for verification nodes * @notice The 3 → 7 → 21 validator transition requires that anyone, anywhere, * can verify which binary a validator is running and which operator * is running it. This contract is the on-chain manifest of those * commitments. It does NOT enroll validators (consensus enrollment is * off-chain via Foundation Ledger + QBFT extraData); it makes the * operator's claim machine-verifiable. * * For each validator a node operator registers: * - validatorAddress QBFT signer (the address Besu uses to sign blocks) * - operatorLabel human-readable name (e.g. "P2P Validator", "Figment") * - operatorRegion e.g. "EU-Central / DE / Hetzner FSN-1" * - binaryDigest SHA256 of the Besu image they're running * - manifestUri where the binary digest's signed manifest lives * (typically the published .well-known/besu-image-digest) * - declaredAt unix seconds at which the operator made the claim * * A new commitment FOR THE SAME validatorAddress overwrites the old one, * keyed by an incrementing serial — history is queryable via events. * There is NO admin override; only the validator's own QBFT signer key * can update its commitment. That ties the binary commitment to consensus * participation: if the signer is compromised, so is the manifest, and * everyone observes simultaneously. * * GOTCHA: this contract does not, and cannot, verify that the validator * IS actually running the claimed binary. That verification is by * third-party probing (block signature analysis, P2P handshake * fingerprint) + the publicly reproducible build (`verify-besu-image.sh`). * The contract gives a single canonical place to ATTEST so audits don't * drift across mediums. * * WHY no admin: the manifest belongs to the operator, not the Foundation. * The Foundation publishes its own commitment same as every other * operator. "The chain nobody owns" is incompatible with a Foundation * that can rewrite operator manifests. */ contract AereValidatorManifest { struct Commitment { bytes32 binaryDigest; // SHA256 (or other) of the Besu container image string manifestUri; // ipfs:// or https:// link to the signed manifest string operatorLabel; string operatorRegion; uint64 declaredAt; uint256 serial; // monotonic per validator } /// @notice validator signer → most recent commitment. mapping(address => Commitment) public latest; /// @notice validator signer → next serial. Starts at 1. mapping(address => uint256) public nextSerial; /* ================================ events ================================ */ event CommitmentDeclared( address indexed validator, uint256 indexed serial, bytes32 indexed binaryDigest, string manifestUri, string operatorLabel, string operatorRegion, uint64 declaredAt ); /* ================================ errors ================================ */ error EmptyManifestUri(); error EmptyOperatorLabel(); error ZeroDigest(); /* =============================== writes =============================== */ /// @notice The validator (msg.sender == signer key) declares which binary /// it is running + who is running it. function declare( bytes32 binaryDigest, string calldata manifestUri, string calldata operatorLabel, string calldata operatorRegion ) external { if (binaryDigest == bytes32(0)) revert ZeroDigest(); if (bytes(manifestUri).length == 0) revert EmptyManifestUri(); if (bytes(operatorLabel).length == 0) revert EmptyOperatorLabel(); uint256 s = ++nextSerial[msg.sender]; latest[msg.sender] = Commitment({ binaryDigest: binaryDigest, manifestUri: manifestUri, operatorLabel: operatorLabel, operatorRegion: operatorRegion, declaredAt: uint64(block.timestamp), serial: s }); emit CommitmentDeclared( msg.sender, s, binaryDigest, manifestUri, operatorLabel, operatorRegion, uint64(block.timestamp) ); } /* ================================ views ================================ */ /// @notice True when the validator has declared at least one commitment. function isDeclared(address validator) external view returns (bool) { return latest[validator].declaredAt != 0; } /// @notice Pretty getter for indexers. function commitmentOf(address validator) external view returns ( bytes32 binaryDigest, string memory manifestUri, string memory operatorLabel, string memory operatorRegion, uint64 declaredAt, uint256 serial ) { Commitment storage c = latest[validator]; return (c.binaryDigest, c.manifestUri, c.operatorLabel, c.operatorRegion, c.declaredAt, c.serial); } }