Skip to content

Commit

Permalink
Merge pull request #36 from NethermindEth/anshu/task-manager-lookahea…
Browse files Browse the repository at this point in the history
…d-challenge

Add lookahead challenge
  • Loading branch information
smartprogrammer93 authored Jul 3, 2024
2 parents 299377d + 6e5aba6 commit d81b7c6
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 20 deletions.
166 changes: 154 additions & 12 deletions SmartContracts/src/avs/PreconfTaskManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity 0.8.25;

import {ITaikoL1} from "../interfaces/taiko/ITaikoL1.sol";
import {MerkleUtils} from "../libraries/MerkleUtils.sol";
import {IPreconfTaskManager} from "../interfaces/IPreconfTaskManager.sol";
import {IPreconfServiceManager} from "../interfaces/IPreconfServiceManager.sol";
import {IRegistryCoordinator} from "eigenlayer-middleware/interfaces/IRegistryCoordinator.sol";
Expand All @@ -14,8 +15,12 @@ contract PreconfTaskManager is IPreconfTaskManager {
IIndexRegistry internal immutable indexRegistry;
ITaikoL1 internal immutable taikoL1;

// EIP-4788
address internal immutable beaconBlockRootContract;

// Dec-01-2020 12:00:23 PM +UTC
uint256 internal constant BEACON_GENESIS_TIMESTAMP = 1606824023;
uint256 internal constant SECONDS_IN_SLOT = 12;
// 12 seconds for each slot, with 32 slots in each epoch
uint256 internal constant SECONDS_IN_EPOCH = 384;
// Span time within which a preconfirmation or posted lookahead may be disputed
Expand All @@ -38,16 +43,22 @@ contract PreconfTaskManager is IPreconfTaskManager {
uint256 internal constant PROPOSED_BLOCK_BUFFER_SIZE = 256;
mapping(uint256 blockId => IPreconfTaskManager.ProposedBlock proposedBlock) proposedBlocks;

// Maps the preconfer's wallet address to the hash tree root of their consensus layer
// BLS pub key (48 bytes)
mapping(address preconfer => bytes32 BLSPubKeyHashTreeRoot) internal consensusBLSPubKeyHashTreeRoots;

constructor(
IPreconfServiceManager _serviceManager,
IRegistryCoordinator _registryCoordinator,
IIndexRegistry _indexRegistry,
ITaikoL1 _taikoL1
ITaikoL1 _taikoL1,
address _beaconBlockRootContract
) {
preconfServiceManager = _serviceManager;
registryCoordinator = _registryCoordinator;
indexRegistry = _indexRegistry;
taikoL1 = _taikoL1;
beaconBlockRootContract = _beaconBlockRootContract;

nextBlockId = 1;
}
Expand All @@ -68,7 +79,13 @@ contract PreconfTaskManager is IPreconfTaskManager {
uint256 lookaheadPointer,
IPreconfTaskManager.LookaheadSetParam[] calldata lookaheadSetParams
) external payable {
uint256 currentEpochTimestamp = _getEpochTimestamp();
// Do not allow block proposals if the preconfer has not registered the hash tree root of their
// consensus BLS pub key
if (consensusBLSPubKeyHashTreeRoots[msg.sender] == bytes32(0)) {
revert IPreconfTaskManager.ConsensusBLSPubKeyHashTreeRootNotRegistered();
}

uint256 currentEpochTimestamp = _getEpochTimestamp(block.timestamp);
address randomPreconfer = randomPreconfers[currentEpochTimestamp];

// Verify that the sender is a valid preconfer for the slot and has the right to propose an L2 block
Expand Down Expand Up @@ -165,20 +182,124 @@ contract PreconfTaskManager is IPreconfTaskManager {
}
}

/**
* @notice lashes an operator if the lookahead posted by them is incorrect
* @param lookaheadPointer Index at which the entry for the lookahead is incorrect
* @param slotTimestamp Timestamp of the slot for which the lookahead entry is incorrect
* @param expectedValidator Chunks for the validator present in the lookahead
* @param expectedValidatorIndex Index of the expected validator in beacon state validators list
* @param expectedValidatorProof Merkle proof of expected validator being a part of validators list
* @param actualValidatorIndex Index of the actual validator in beacon state validators list
* @param validatorsRoot Hash tree root of the beacon state validators list
* @param nr_validators Length of the validators list
* @param beaconStateProof Merkle proof of validators list being a part of the beacon state
* @param beaconStateRoot Hash tree root of the beacon state
* @param beaconBlockProofForState Merkle proof of beacon state root being a part of the beacon block
* @param beaconBlockProofForProposerIndex Merkle proof of actual validator's index being a part of the beacon block
*/
function proveIncorrectLookahead(
uint256 offset,
bytes32[] memory expectedValidator,
uint256 lookaheadPointer,
uint256 slotTimestamp,
bytes32[8] memory expectedValidator,
uint256 expectedValidatorIndex,
bytes32[] memory expectedValidatorProof,
bytes32[] memory actualValidator,
uint256 actualValidatorIndex,
bytes32[] memory actualValidatorProof,
bytes32 validatorsRoot,
uint256 nr_validators,
bytes32[] memory beaconStateProof,
bytes32 beaconStateRoot,
bytes32[] memory beaconBlockProof
) external {}
bytes32[] memory beaconBlockProofForState,
bytes32[] memory beaconBlockProofForProposerIndex
) external {
// Prove that the expected validator has not been slashed on consensus layer
if (expectedValidator[3] == bytes32(0)) {
revert IPreconfTaskManager.ExpectedValidatorMustNotBeSlashed();
}

uint256 epochTimestamp = _getEpochTimestamp(slotTimestamp);

// The poster must not already be slashed
if (lookaheadPosters[epochTimestamp] == address(0)) {
revert IPreconfTaskManager.PosterAlreadySlashedForTheEpoch();
}

{
IPreconfTaskManager.LookaheadEntry memory lookaheadEntry =
lookahead[lookaheadPointer % LOOKAHEAD_BUFFER_SIZE];

// Timestamp of the slot that contains the incorrect entry must be in the correct range and within the
// dispute window.
if (block.timestamp - slotTimestamp > DISPUTE_PERIOD) {
revert IPreconfTaskManager.MissedDisputeWindow();
} else if (slotTimestamp > lookaheadEntry.timestamp || slotTimestamp <= lookaheadEntry.prevTimestamp) {
revert IPreconfTaskManager.InvalidLookaheadPointer();
}

if (consensusBLSPubKeyHashTreeRoots[lookaheadEntry.preconfer] != expectedValidator[0]) {
// Revert if the expected validator's consensus BLS pub key's hash tree root does not match
// the one registered by the preconfer
revert IPreconfTaskManager.ExpectedValidatorIsIncorrect();
} else if (expectedValidatorIndex == actualValidatorIndex) {
revert IPreconfTaskManager.ExpectedAndActualValidatorAreSame();
}
}

{
bytes32 expectedValidatorHashTreeRoot = MerkleUtils.merkleize(expectedValidator);
if (
!MerkleUtils.verifyProof(
expectedValidatorProof, validatorsRoot, expectedValidatorHashTreeRoot, expectedValidatorIndex
)
) {
// Revert if the proof that the expected validator is a part of the validator
// list in beacon state fails
revert IPreconfTaskManager.ValidatorProofFailed();
}
}

{
bytes32 stateValidatorsHashTreeRoot = MerkleUtils.mixInLength(validatorsRoot, nr_validators);
if (MerkleUtils.verifyProof(beaconStateProof, beaconStateRoot, stateValidatorsHashTreeRoot, 11)) {
// Revert if the proof that the validator list is a part of the beacon state fails
revert IPreconfTaskManager.BeaconStateProofFailed();
}
}

bytes32 beaconBlockRoot = _getBeaconBlockRoot(slotTimestamp);

if (MerkleUtils.verifyProof(beaconBlockProofForState, beaconBlockRoot, beaconStateRoot, 3)) {
// Revert if the proof for the beacon state being a part of the beacon block fails
revert IPreconfTaskManager.BeaconBlockProofForStateFailed();
}

if (
MerkleUtils.verifyProof(
beaconBlockProofForProposerIndex, beaconBlockRoot, MerkleUtils.toLittleEndian(actualValidatorIndex), 1
)
) {
// Revert if the proof that the proposer index is a part of the beacon block fails
revert IPreconfTaskManager.BeaconBlockProofForProposerIndex();
}

// Slash the original lookahead poster
address poster = lookaheadPosters[epochTimestamp];
lookaheadPosters[epochTimestamp] = address(0);
preconfServiceManager.slashOperator(poster);

emit ProvedIncorrectLookahead(poster, slotTimestamp, msg.sender);
}

function registerConsensusBLSPubKeyHashTreeRoot(bytes32 consensusBLSPubKeyHashTreeRoot) external {
// The sender must be a registered preconfer in the AVS registry
if (registryCoordinator.getOperatorStatus(msg.sender) != IRegistryCoordinator.OperatorStatus.REGISTERED) {
revert IPreconfTaskManager.SenderNotRegisteredInAVS();
} else if (consensusBLSPubKeyHashTreeRoots[msg.sender] != bytes32(0)) {
// The hash tree root must not be already registered
revert IPreconfTaskManager.SenderNotRegisteredInAVS();
}

consensusBLSPubKeyHashTreeRoots[msg.sender] = consensusBLSPubKeyHashTreeRoot;
}

//=========
// Helpers
Expand Down Expand Up @@ -212,7 +333,7 @@ contract PreconfTaskManager is IPreconfTaskManager {

// Each entry must be a registered AVS operator
if (registryCoordinator.getOperatorStatus(preconfer) != IRegistryCoordinator.OperatorStatus.REGISTERED) {
revert IPreconfTaskManager.SenderNotRegisteredInAVS();
revert IPreconfTaskManager.EntryNotRegisteredInAVS();
}

// Ensure that the timestamps belong to a valid slot in the next epoch
Expand All @@ -236,14 +357,35 @@ contract PreconfTaskManager is IPreconfTaskManager {
}

/**
* @notice Computes the timestamp at which the ongoing epoch started.
* @notice Computes the timestamp of the epoch containing the provided slot timestamp
*/
function _getEpochTimestamp() private view returns (uint256) {
uint256 timePassedSinceGenesis = block.timestamp - BEACON_GENESIS_TIMESTAMP;
function _getEpochTimestamp(uint256 slotTimestamp) private pure returns (uint256) {
uint256 timePassedSinceGenesis = slotTimestamp - BEACON_GENESIS_TIMESTAMP;
uint256 timeToCurrentEpochFromGenesis = (timePassedSinceGenesis / SECONDS_IN_EPOCH) * SECONDS_IN_EPOCH;
return BEACON_GENESIS_TIMESTAMP + timeToCurrentEpochFromGenesis;
}

/**
* @notice Retrieves the beacon block root for the block at the specified timestamp
*/
function _getBeaconBlockRoot(uint256 timestamp) private view returns (bytes32) {
// At block N, we get the beacon block root for block N - 1. So, to get the block root of the Nth block,
// we query the root at block N + 1. If N + 1 is a missed slot, we keep querying until we find a block N + x
// that has the block root for Nth block.
uint256 targetTimestamp = timestamp + SECONDS_IN_SLOT;
while (true) {
(bool success, bytes memory result) = beaconBlockRootContract.staticcall(abi.encode(targetTimestamp));
if (success && result.length > 0) {
return abi.decode(result, (bytes32));
}

unchecked {
targetTimestamp += SECONDS_IN_SLOT;
}
}
return bytes32(0);
}

//=======
// Views
//=======
Expand Down
41 changes: 33 additions & 8 deletions SmartContracts/src/interfaces/IPreconfTaskManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ interface IPreconfTaskManager {

event LookaheadUpdated(LookaheadSetParam[]);
event ProvedIncorrectPreconfirmation(address indexed preconfer, uint256 indexed blockId, address indexed disputer);
event ProvedIncorrectLookahead(address indexed poster, uint256 indexed slot, address indexed disputer);
event ProvedIncorrectLookahead(address indexed poster, uint256 indexed timestamp, address indexed disputer);

/// @dev The block proposer is not the randomly chosen fallback preconfer for the current slot/timestamp
error SenderIsNotTheFallbackPreconfer();
/// @dev The current timestamp does not fall in the range provided by the lookahead pointer
/// @dev The current (or provided) timestamp does not fall in the range provided by the lookahead pointer
error InvalidLookaheadPointer();
/// @dev The block proposer is not the assigned preconfer for the current slot/timestamp
error SenderIsNotThePreconfer();
/// @dev The preconfer in the lookahead set params is not registered to the AVS
/// @dev The preconfer in the lookahead set params is not registered in the AVS
error EntryNotRegisteredInAVS();
/// @dev The sender is not registered in the AVS
error SenderNotRegisteredInAVS();
/// @dev The timestamp in the lookahead is not of a valid future slot in the present epoch
error InvalidSlotTimestamp();
Expand All @@ -57,6 +59,26 @@ interface IPreconfTaskManager {
error MissedDisputeWindow();
/// @dev The disputed preconfirmation is correct
error PreconfirmationIsCorrect();
/// @dev The preconfer is yet to register the hash tree root of their consensus BLS pub key
error ConsensusBLSPubKeyHashTreeRootNotRegistered();
/// @dev The preconfer has already registered the hash tree root of their consensus BLS pub key
error ConsensusBLSPubKeyHashTreeRootAlreadyRegistered();
/// @dev The expected validator has been slashed on CL
error ExpectedValidatorMustNotBeSlashed();
/// @dev The lookahead poster for the epoch has already been slashed
error PosterAlreadySlashedForTheEpoch();
/// @dev The registered hash tree root of preconfer's consensus BLS pub key does not match with expected validator
error ExpectedValidatorIsIncorrect();
/// @dev The validator list indices for both expected and actual validators are same
error ExpectedAndActualValidatorAreSame();
/// @dev The proof that the expected validator is a part of the validator list is invalid.
error ValidatorProofFailed();
/// @dev The proof that the validator list is a part of the beacon state is invalid.
error BeaconStateProofFailed();
/// @dev The proof that the beacon state is a part of the beacon block is invalid.
error BeaconBlockProofForStateFailed();
/// @dev The proof that the actual validator index is a part of the beacon is invalid.
error BeaconBlockProofForProposerIndex();

/// @dev Accepts block proposal by an operator and forwards it to TaikoL1 contract
function newBlockProposal(
Expand All @@ -71,19 +93,22 @@ interface IPreconfTaskManager {

/// @dev Slashes a preconfer if the validator lookahead pushed by them has an incorrect entry
function proveIncorrectLookahead(
uint256 offset,
bytes32[] memory expectedValidator,
uint256 lookaheadPointer,
uint256 slotTimestamp,
bytes32[8] memory expectedValidator,
uint256 expectedValidatorIndex,
bytes32[] memory expectedValidatorProof,
bytes32[] memory actualValidator,
uint256 actualValidatorIndex,
bytes32[] memory actualValidatorProof,
bytes32 validatorsRoot,
uint256 nr_validators,
bytes32[] memory beaconStateProof,
bytes32 beaconStateRoot,
bytes32[] memory beaconBlockProof
bytes32[] memory beaconBlockProofForState,
bytes32[] memory beaconBlockProofForProposerIndex
) external;

/// @dev Records the hash tree root of the BLS pub key that the preconfer uses on the consensus layer
function registerConsensusBLSPubKeyHashTreeRoot(bytes32 consensusBLSPubKeyHashTreeRoot) external;

function isLookaheadRequired(uint256 epochTimestamp) external view returns (bool);
}
77 changes: 77 additions & 0 deletions SmartContracts/src/libraries/MerkleUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

library MerkleUtils {
uint256 internal constant CHUNKS_LENGTH = 8;
uint256 internal constant TMP_LENGTH = 4;

function hash(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return sha256(abi.encodePacked(a, b));
}

function merkleize(bytes32[CHUNKS_LENGTH] memory chunks) internal pure returns (bytes32) {
bytes32[] memory tmp = new bytes32[](TMP_LENGTH);

for (uint256 i; i < CHUNKS_LENGTH; ++i) {
merge(tmp, i, chunks[i]);
}

return tmp[TMP_LENGTH - 1];
}

function merge(bytes32[] memory tmp, uint256 index, bytes32 chunk) internal pure {
bytes32 h = chunk;
uint256 j = 0;
while (true) {
if (index & 1 << j == 0) {
break;
} else {
h = hash(tmp[j], h);
}
j += 1;
}
tmp[j] = h;
}

function verifyProof(bytes32[] memory proof, bytes32 root, bytes32 leaf, uint256 leafIndex)
internal
pure
returns (bool)
{
bytes32 h = leaf;
uint256 index = leafIndex;

for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];

if (index % 2 == 0) {
h = sha256(bytes.concat(h, proofElement));
} else {
h = sha256(bytes.concat(proofElement, h));
}

index = index / 2;
}

return h == root;
}

function toLittleEndian(uint256 n) public pure returns (bytes32) {
uint256 v = n;
v = ((v & 0xFF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00) >> 8)
| ((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);
v = ((v & 0xFFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000) >> 16)
| ((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);
v = ((v & 0xFFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000) >> 32)
| ((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);
v = ((v & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF0000000000000000) >> 64)
| ((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);
v = (v >> 128) | (v << 128);
return bytes32(v);
}

function mixInLength(bytes32 root, uint256 length) public pure returns (bytes32) {
bytes32 littleEndianLength = toLittleEndian(length);
return sha256(abi.encodePacked(root, littleEndianLength));
}
}

0 comments on commit d81b7c6

Please sign in to comment.