Some checks failed
contracts-ci / Install (lockfile) → compile → full test suite (push) Has been cancelled
contracts-ci / Ethereum interop (EIP-2537 BLS, prague hardfork) (push) Has been cancelled
contracts-ci / PQC known-answer tests (NIST vectors) (push) Has been cancelled
contracts-ci / Coverage (scoped, with artifacts) (push) Has been cancelled
The published line and the local line had no common ancestor: the public one carried the redaction pass, the local one carried three weeks of corrections that never shipped. This commit ports the local work onto the public line, keeps every public redaction, and extends the same discretion to seven client mentions that were still named in published comments. Carried: LICENSE year and LICENSING.md; the measured burn figures replacing the deflation claim (the vault holds ~0.137 AERE of 2.8 billion, and burn is a share of validator coinbase revenue, which is zero today); 'audited' removed from next to Bouncy Castle; citation paths rewritten to published form with CITATIONS-UNRESOLVED.md remeasured 2026-08-11; VERIFY-POLICY.md; slashing and ownership comments brought down to what the code does; the AerePyth repair; the shutter test helper the tests cite; runnable package.json entries; the CI file split into a GitHub/Gitea twin pair with a real measured test-run status; and the .gitignore hardening written after a compiled artifact leaked a local path in a sibling repository. A false '2-of-3 multisig' description of the owner account is corrected to what the chain measures: an externally owned account. The self-audit findings catalog stays unpublished pending an explicit decision.
125 lines
5.3 KiB
Solidity
125 lines
5.3 KiB
Solidity
// 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"
|
|
* - 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);
|
|
}
|
|
}
|