Advance forge rollout, Ethereum rails, and NBC sources
This commit is contained in:
parent
be26313225
commit
7d84510eac
88 changed files with 11230 additions and 302 deletions
193
contracts/EveryChannelObservationLedger.sol
Normal file
193
contracts/EveryChannelObservationLedger.sol
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import {EveryChannelWitnessRegistry} from "./EveryChannelWitnessRegistry.sol";
|
||||
|
||||
/// @title EveryChannelObservationLedger
|
||||
/// @notice Observation consensus ledger for reality-derived every.channel epochs.
|
||||
/// Witnesses attest to the same derived observation hash, and the first candidate to hit quorum
|
||||
/// finalizes the `(stream, epoch)` slot.
|
||||
contract EveryChannelObservationLedger {
|
||||
struct ObservationHeader {
|
||||
bytes32 streamHash;
|
||||
bytes32 epochHash;
|
||||
bytes32 parentObservationHash;
|
||||
bytes32 dataRoot;
|
||||
bytes32 locatorHash;
|
||||
uint64 observedUnixMs;
|
||||
uint64 sequence;
|
||||
}
|
||||
|
||||
struct Candidate {
|
||||
bytes32 slot;
|
||||
address proposer;
|
||||
uint64 observedUnixMs;
|
||||
uint64 sequence;
|
||||
uint32 attestations;
|
||||
bool finalized;
|
||||
}
|
||||
|
||||
EveryChannelWitnessRegistry public immutable witnessRegistry;
|
||||
uint256 public immutable quorum;
|
||||
|
||||
mapping(bytes32 => ObservationHeader) private observationHeaders;
|
||||
mapping(bytes32 => Candidate) public candidates;
|
||||
mapping(bytes32 => bytes32) public finalizedObservationBySlot;
|
||||
mapping(bytes32 => mapping(address => bool)) public hasAttested;
|
||||
|
||||
event ObservationProposed(
|
||||
bytes32 indexed slot,
|
||||
bytes32 indexed observationHash,
|
||||
address indexed proposer,
|
||||
bytes32 streamHash,
|
||||
bytes32 epochHash,
|
||||
bytes32 parentObservationHash,
|
||||
bytes32 dataRoot,
|
||||
bytes32 locatorHash,
|
||||
uint64 observedUnixMs,
|
||||
uint64 sequence
|
||||
);
|
||||
|
||||
event ObservationAttested(
|
||||
bytes32 indexed slot,
|
||||
bytes32 indexed observationHash,
|
||||
address indexed witness,
|
||||
uint32 attestations
|
||||
);
|
||||
|
||||
event ObservationFinalized(
|
||||
bytes32 indexed slot,
|
||||
bytes32 indexed observationHash,
|
||||
uint32 attestations
|
||||
);
|
||||
|
||||
error NotWitness(address caller);
|
||||
error InvalidRegistry(address registry);
|
||||
error InvalidQuorum(uint256 quorum);
|
||||
error UnknownObservation(bytes32 observationHash);
|
||||
error ObservationAlreadyExists(bytes32 observationHash);
|
||||
error ObservationSlotAlreadyFinalized(bytes32 slot, bytes32 observationHash);
|
||||
error DuplicateAttestation(bytes32 observationHash, address witness);
|
||||
|
||||
constructor(address registry, uint256 quorumThreshold) {
|
||||
if (registry == address(0)) revert InvalidRegistry(registry);
|
||||
if (quorumThreshold == 0) revert InvalidQuorum(quorumThreshold);
|
||||
witnessRegistry = EveryChannelWitnessRegistry(registry);
|
||||
quorum = quorumThreshold;
|
||||
}
|
||||
|
||||
modifier onlyWitness() {
|
||||
if (!witnessRegistry.isWitness(msg.sender)) revert NotWitness(msg.sender);
|
||||
_;
|
||||
}
|
||||
|
||||
function observationSlot(
|
||||
bytes32 streamHash,
|
||||
bytes32 epochHash
|
||||
) public pure returns (bytes32) {
|
||||
return keccak256(abi.encode(streamHash, epochHash));
|
||||
}
|
||||
|
||||
function hashObservationHeader(
|
||||
ObservationHeader memory header
|
||||
) public pure returns (bytes32) {
|
||||
return keccak256(
|
||||
abi.encode(
|
||||
header.streamHash,
|
||||
header.epochHash,
|
||||
header.parentObservationHash,
|
||||
header.dataRoot,
|
||||
header.locatorHash,
|
||||
header.observedUnixMs,
|
||||
header.sequence
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function getObservationHeader(
|
||||
bytes32 observationHash
|
||||
) external view returns (ObservationHeader memory) {
|
||||
if (candidates[observationHash].slot == bytes32(0)) {
|
||||
revert UnknownObservation(observationHash);
|
||||
}
|
||||
return observationHeaders[observationHash];
|
||||
}
|
||||
|
||||
function proposeObservation(
|
||||
ObservationHeader calldata header
|
||||
) external returns (bytes32 observationHash) {
|
||||
observationHash = hashObservationHeader(header);
|
||||
bytes32 slot = observationSlot(header.streamHash, header.epochHash);
|
||||
bytes32 finalized = finalizedObservationBySlot[slot];
|
||||
if (finalized != bytes32(0) && finalized != observationHash) {
|
||||
revert ObservationSlotAlreadyFinalized(slot, finalized);
|
||||
}
|
||||
if (candidates[observationHash].slot != bytes32(0)) {
|
||||
revert ObservationAlreadyExists(observationHash);
|
||||
}
|
||||
|
||||
observationHeaders[observationHash] = header;
|
||||
candidates[observationHash] = Candidate({
|
||||
slot: slot,
|
||||
proposer: msg.sender,
|
||||
observedUnixMs: header.observedUnixMs,
|
||||
sequence: header.sequence,
|
||||
attestations: 0,
|
||||
finalized: false
|
||||
});
|
||||
|
||||
emit ObservationProposed(
|
||||
slot,
|
||||
observationHash,
|
||||
msg.sender,
|
||||
header.streamHash,
|
||||
header.epochHash,
|
||||
header.parentObservationHash,
|
||||
header.dataRoot,
|
||||
header.locatorHash,
|
||||
header.observedUnixMs,
|
||||
header.sequence
|
||||
);
|
||||
|
||||
if (witnessRegistry.isWitness(msg.sender)) {
|
||||
_attest(observationHash, msg.sender);
|
||||
}
|
||||
}
|
||||
|
||||
function attestObservation(bytes32 observationHash) external onlyWitness {
|
||||
_attest(observationHash, msg.sender);
|
||||
}
|
||||
|
||||
function _attest(bytes32 observationHash, address witness) internal {
|
||||
Candidate storage candidate = candidates[observationHash];
|
||||
if (candidate.slot == bytes32(0)) revert UnknownObservation(observationHash);
|
||||
if (hasAttested[observationHash][witness]) {
|
||||
revert DuplicateAttestation(observationHash, witness);
|
||||
}
|
||||
|
||||
bytes32 finalized = finalizedObservationBySlot[candidate.slot];
|
||||
if (finalized != bytes32(0) && finalized != observationHash) {
|
||||
revert ObservationSlotAlreadyFinalized(candidate.slot, finalized);
|
||||
}
|
||||
|
||||
hasAttested[observationHash][witness] = true;
|
||||
candidate.attestations += 1;
|
||||
|
||||
emit ObservationAttested(
|
||||
candidate.slot,
|
||||
observationHash,
|
||||
witness,
|
||||
candidate.attestations
|
||||
);
|
||||
|
||||
if (!candidate.finalized && candidate.attestations >= quorum) {
|
||||
candidate.finalized = true;
|
||||
finalizedObservationBySlot[candidate.slot] = observationHash;
|
||||
emit ObservationFinalized(
|
||||
candidate.slot,
|
||||
observationHash,
|
||||
candidate.attestations
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue