// SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; import {EveryChannelObservationLedger} from "../EveryChannelObservationLedger.sol"; import {EveryChannelWitnessRegistry} from "../EveryChannelWitnessRegistry.sol"; contract WitnessActor { function propose( EveryChannelObservationLedger ledger, EveryChannelObservationLedger.ObservationHeader calldata header ) external returns (bytes32) { return ledger.proposeObservation(header); } function attest( EveryChannelObservationLedger ledger, bytes32 observationHash ) external { ledger.attestObservation(observationHash); } } contract ObservationLedgerTest { function _header( bytes32 streamHash, bytes32 epochHash, uint64 sequence, bytes32 dataRoot ) internal pure returns (EveryChannelObservationLedger.ObservationHeader memory) { return EveryChannelObservationLedger.ObservationHeader({ streamHash: streamHash, epochHash: epochHash, parentObservationHash: bytes32(0), dataRoot: dataRoot, locatorHash: keccak256(abi.encodePacked(streamHash, epochHash, sequence)), observedUnixMs: 1_772_001_256_329, sequence: sequence }); } function test_finalizes_when_quorum_is_reached() public { EveryChannelWitnessRegistry registry = new EveryChannelWitnessRegistry(address(this)); WitnessActor witnessA = new WitnessActor(); WitnessActor witnessB = new WitnessActor(); registry.addWitness(address(witnessA)); registry.addWitness(address(witnessB)); EveryChannelObservationLedger ledger = new EveryChannelObservationLedger( address(registry), 2 ); EveryChannelObservationLedger.ObservationHeader memory header = _header( keccak256("la-cbs:video0.m4s"), keccak256("epoch-229"), 229, keccak256("58cd13f693debd000d995c9a5574e8b9274cc4d3399eb6f1f22393af1ba7407d") ); bytes32 observationHash = witnessA.propose(ledger, header); bytes32 slot = ledger.observationSlot(header.streamHash, header.epochHash); assert(ledger.finalizedObservationBySlot(slot) == bytes32(0)); witnessB.attest(ledger, observationHash); assert(ledger.finalizedObservationBySlot(slot) == observationHash); EveryChannelObservationLedger.ObservationHeader memory storedHeader = ledger .getObservationHeader(observationHash); assert(storedHeader.streamHash == header.streamHash); assert(storedHeader.epochHash == header.epochHash); assert(storedHeader.dataRoot == header.dataRoot); assert(storedHeader.sequence == header.sequence); } function test_rejects_duplicate_attestation() public { EveryChannelWitnessRegistry registry = new EveryChannelWitnessRegistry(address(this)); WitnessActor witnessA = new WitnessActor(); registry.addWitness(address(witnessA)); EveryChannelObservationLedger ledger = new EveryChannelObservationLedger( address(registry), 1 ); EveryChannelObservationLedger.ObservationHeader memory header = _header( keccak256("la-nbc:catalog.json"), keccak256("epoch-5"), 5, keccak256("f6ea0793fd00e29ced670d586e0e5f7f3d0f5edfc016c03f80710bd4bed587ec") ); bytes32 observationHash = witnessA.propose(ledger, header); assert( ledger.finalizedObservationBySlot( ledger.observationSlot(header.streamHash, header.epochHash) ) == observationHash ); (bool ok, ) = address(witnessA).call( abi.encodeWithSelector( WitnessActor.attest.selector, ledger, observationHash ) ); assert(!ok); } function test_competing_candidates_only_one_slot_can_finalize() public { EveryChannelWitnessRegistry registry = new EveryChannelWitnessRegistry(address(this)); WitnessActor witnessA = new WitnessActor(); WitnessActor witnessB = new WitnessActor(); WitnessActor witnessC = new WitnessActor(); registry.addWitness(address(witnessA)); registry.addWitness(address(witnessB)); registry.addWitness(address(witnessC)); EveryChannelObservationLedger ledger = new EveryChannelObservationLedger( address(registry), 2 ); bytes32 streamHash = keccak256("la-cbs:video0.m4s"); bytes32 epochHash = keccak256("epoch-230"); EveryChannelObservationLedger.ObservationHeader memory candidateA = _header( streamHash, epochHash, 230, keccak256("1130c51b3ce428505dbc3c3294678f76e3200221d853fc9b02c8ed603ecc8b8c") ); EveryChannelObservationLedger.ObservationHeader memory candidateB = _header( streamHash, epochHash, 230, keccak256("deadbeef") ); bytes32 hashA = witnessA.propose(ledger, candidateA); bytes32 hashB = witnessB.propose(ledger, candidateB); bytes32 slot = ledger.observationSlot(streamHash, epochHash); assert(hashA != hashB); assert(ledger.finalizedObservationBySlot(slot) == bytes32(0)); witnessC.attest(ledger, hashA); assert(ledger.finalizedObservationBySlot(slot) == hashA); (bool ok, ) = address(witnessB).call( abi.encodeWithSelector( WitnessActor.attest.selector, ledger, hashB ) ); assert(!ok); } }