Skip to content

Commit

Permalink
feat: add block proposals and lookahead posting
Browse files Browse the repository at this point in the history
  • Loading branch information
AnshuJalan committed Jun 20, 2024
1 parent bfd9ba1 commit 3303b12
Show file tree
Hide file tree
Showing 3 changed files with 303 additions and 11 deletions.
229 changes: 229 additions & 0 deletions SmartContracts/src/avs/PreconfTaskManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import {ITaikoL1} from "../interfaces/taiko/ITaikoL1.sol";
import {IPreconfTaskManager} from "../interfaces/IPreconfTaskManager.sol";
import {IPreconfServiceManager} from "../interfaces/IPreconfServiceManager.sol";
import {IRegistryCoordinator} from "eigenlayer-middleware/interfaces/IRegistryCoordinator.sol";
import {IIndexRegistry} from "eigenlayer-middleware/interfaces/IIndexRegistry.sol";

contract PreconfTaskManager is IPreconfTaskManager {
IPreconfServiceManager internal immutable preconfServiceManager;
IRegistryCoordinator internal immutable registryCoordinator;
IIndexRegistry internal immutable indexRegistry;
ITaikoL1 internal immutable taikoL1;

// Dec-01-2020 12:00:23 PM +UTC
uint256 internal constant BEACON_GENESIS_TIMESTAMP = 1606824023;
// 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
uint256 internal constant DISPUTE_PERIOD = 2 * SECONDS_IN_EPOCH;

// A ring buffer of upcoming preconfers (who are also the L1 validators)
uint256 internal lookaheadTail;
uint256 internal constant LOOKAHEAD_BUFFER_SIZE = 64;
IPreconfTaskManager.LookaheadEntry[LOOKAHEAD_BUFFER_SIZE] internal lookahead;

// Todo: Make the below two data structure more efficient by reusing slots

// Current and past lookahead posters as a mapping indexed by the timestamp of the epoch
mapping(uint256 epochTimestamp => address poster) internal lookaheadPosters;
// Maps the epoch timestamp to the randomly selected preconfer (if present) for that epoch
mapping(uint256 epochTimestamp => address randomPreconfer) internal randomPreconfers;

// A ring buffer of proposed (and preconfed) L2 blocks
uint256 nextBlockId;
uint256 internal constant PROPOSED_BLOCK_BUFFER_SIZE = 256;
mapping(uint256 blockId => IPreconfTaskManager.ProposedBlock proposedBlock) proposedBlocks;

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

nextBlockId = 1;
}

/**
* @notice Proposes a new Taiko L2 block.
* @dev This may either be called by a randomly selected preconfer or by a preconfer expected for the current slot
* as per the lookahead. The first caller in every is expected to pass along the lookahead entries for the next epoch.
* @param blockParams Block parameters expected by TaikoL1 contract
* @param txList RLP encoded transaction list expected by TaikoL1 contract
* @param lookaheadPointer A pointer to the lookahead entry that may prove that the sender is the preconfer
* for the slot.
* @param lookaheadSetParams Collection of timestamps and preconfer addresses to be inserted in the lookahead
*/
function newBlockProposal(
bytes calldata blockParams,
bytes calldata txList,
uint256 lookaheadPointer,
IPreconfTaskManager.LookaheadSetParam[] calldata lookaheadSetParams
) external {
uint256 currentEpochTimestamp = _getEpochTimestamp();
address randomPreconfer = randomPreconfers[currentEpochTimestamp];

// Verify that the sender is a valid preconfer for the slot and has the right to propose an L2 block
if (randomPreconfer != address(0) && msg.sender != randomPreconfer) {
// Revert if the sender is not the randomly selected preconfer for the epoch
revert IPreconfTaskManager.SenderIsNotTheFallbackPreconfer();
} else if (isLookaheadRequired(currentEpochTimestamp)) {
// The *current* epoch may require a lookahead in the following situations:
// - It is the first epoch after this contract started offering services
// - The epoch has no L1 validators who are opted-in preconfers in the AVS
// - The previous lookahead for the epoch was invalidated/
//
// In all the above cases, we expect a preconfer to be randomly chosen as fallaback
if (msg.sender != getFallbackPreconfer()) {
revert IPreconfTaskManager.SenderIsNotTheFallbackPreconfer();
} else {
randomPreconfers[currentEpochTimestamp] = msg.sender;
}
} else {
IPreconfTaskManager.LookaheadEntry memory lookaheadEntry =
lookahead[lookaheadPointer % LOOKAHEAD_BUFFER_SIZE];

// The current L1 block's timestamp must be within the range retrieved from the lookahead entry.
// The preconfer is allowed to propose a block in advanced if there are no other entries in the
// lookahead between the present slot and the preconfer's own slot.
//
// ------[Last slot with an entry]---[X]---[X]----[X]----[Preconfer]-------
// ------[ prevTimestamp ]---[ ]---[ ]----[ ]----[timestamp]-------
//
if (block.timestamp <= lookaheadEntry.prevTimestamp || block.timestamp > lookaheadEntry.timestamp) {
revert IPreconfTaskManager.InvalidLookaheadPointer();
} else if (msg.sender != lookaheadEntry.preconfer) {
revert IPreconfTaskManager.SenderIsNotThePreconfer();
}
}

uint256 nextEpochTimestamp = currentEpochTimestamp + SECONDS_IN_EPOCH;

// Update the lookahead for the next epoch.
// Only called during the first block proposal of the current epoch.
if (isLookaheadRequired(nextEpochTimestamp)) {
_updateLookahead(currentEpochTimestamp, lookaheadSetParams);
}

uint256 _nextBlockId = nextBlockId;

// Store the hash of the transaction list and the proposer of the proposed block.
// The hash is later used to verify transaction inclusion/ordering in a preconfirmation.
proposedBlocks[_nextBlockId % PROPOSED_BLOCK_BUFFER_SIZE] =
IPreconfTaskManager.ProposedBlock({proposer: msg.sender, txListHash: keccak256(txList)});

nextBlockId = _nextBlockId + 1;

// Block the preconfer from withdrawing stake from Eigenlayer during the dispute window
preconfServiceManager.lockStakeUntil(msg.sender, block.timestamp + DISPUTE_PERIOD);

// Forward the block to Taiko's L1 contract
taikoL1.proposeBlock(blockParams, txList);
}

function proveIncorrectPreconfirmation(PreconfirmationHeader memory header, bytes memory signature) external {}

function proveIncorrectLookahead(
uint256 offset,
bytes32[] 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 {}

//=========
// Helpers
//=========

function _updateLookahead(
uint256 epochTimestamp,
IPreconfTaskManager.LookaheadSetParam[] calldata lookaheadSetParams
) private {
uint256 nextEpochTimestamp = epochTimestamp + SECONDS_IN_EPOCH;
uint256 nextEpochEndTimestamp = nextEpochTimestamp + SECONDS_IN_EPOCH;

// The tail of the lookahead is tracked and connected to the first new lookahead entry so
// that when no more preconfers are present in the remaining slots of the current epoch,
// the next epoch's preconfer may start preconfing in advanced.
//
// --[]--[]--[p1]--[]--[]---|---[]--[]--[P2]--[]--[]
// 1 2 3 4 5 6 7 8 9 10
// Epoch 1 Epoch 2
//
// Here, P2 may start preconfing and proposing blocks from slot 4 itself
//
uint256 _lookaheadTail = lookaheadTail;
uint256 prevSlotTimestamp = lookahead[_lookaheadTail % LOOKAHEAD_BUFFER_SIZE].timestamp;

for (uint256 i; i < lookaheadSetParams.length; ++i) {
_lookaheadTail += 1;

address preconfer = lookaheadSetParams[i].preconfer;
uint256 slotTimestamp = lookaheadSetParams[i].timestamp;

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

// Ensure that the timestamps belong to a valid slot in the next epoch
if ((slotTimestamp - nextEpochTimestamp) % 12 != 0 || slotTimestamp >= nextEpochEndTimestamp) {
revert IPreconfTaskManager.InvalidSlotTimestamp();
}

// Update the lookahead entry
lookahead[_lookaheadTail % LOOKAHEAD_BUFFER_SIZE] = IPreconfTaskManager.LookaheadEntry({
timestamp: uint48(slotTimestamp),
prevTimestamp: uint48(prevSlotTimestamp),
preconfer: preconfer
});
prevSlotTimestamp = slotTimestamp;
}

lookaheadTail = _lookaheadTail;
lookaheadPosters[epochTimestamp] = msg.sender;

emit LookaheadUpdated(lookaheadSetParams);
}

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

//=======
// Views
//=======

function getFallbackPreconfer() public view returns (address) {
uint256 randomness = block.prevrandao;
// For the POC we only have one quorum at the 0th id. If there are multiple quorums in productions
// we may also need to randomise the quorum selection.
uint8 quorumNumber = 0;
uint32 operatorIndex = uint32(randomness % indexRegistry.totalOperatorsForQuorum(quorumNumber));
bytes32 operatorId = bytes32(indexRegistry.getLatestOperatorUpdate(quorumNumber, operatorIndex).operatorId);
return registryCoordinator.getOperatorFromId(operatorId);
}

function isLookaheadRequired(uint256 epochTimestamp) public view returns (bool) {
return lookaheadPosters[epochTimestamp] == address(0);
}
}
53 changes: 42 additions & 11 deletions SmartContracts/src/interfaces/IPreconfTaskManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@
pragma solidity 0.8.25;

interface IPreconfTaskManager {
event LookaheadUpdated(bytes lookahead);
event ProvedIncorrectPreconfirmation(address indexed preconfer, uint256 indexed blockId, address indexed disputer);
event ProvedIncorrectLookahead(address indexed poster, uint256 indexed slot, address indexed disputer);

struct LookaheadEntry {
// The timestamp of the slot
uint256 timestamp;
// The id of the AVS operator who is also the L1 validator for the slot
uint256 validatorId;
struct ProposedBlock {
// Proposer of the L2 block
address proposer;
// Keccak hash of the RLP transaction list of the block
bytes32 txListHash;
}

struct PreconfirmationHeader {
Expand All @@ -22,11 +18,46 @@ interface IPreconfTaskManager {
bytes32 txListHash;
}

struct LookaheadEntry {
// Timestamp of the slot at which the provided preconfer is the L1 validator
uint48 timestamp;
// Timestamp of the last slot that had a valid preconfer
uint48 prevTimestamp;
// Address of the preconfer who is also the L1 validator
// The preconfer will have rights to propose a block in the range (prevTimestamp, timestamp]
address preconfer;
}

struct LookaheadSetParam {
// The timestamp of the slot
uint256 timestamp;
// The AVS operator who is also the L1 validator for the slot and will preconf L2 transactions
address preconfer;
}

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);

/// @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
error InvalidLookaheadPointer();
/// @dev The block proposer is not the assigned preconfer for the current slot/timestamp
error SenderIsNotThePreconfer();
/// @dev The block proposer has not set themselves as first preconfer in the lookahead they are updating
error SenderMustBeTheFirstEntryInLookaheadSetParams();
/// @dev The preconfer in the lookahead set params is not registered to the AVS
error SenderNotRegisteredInAVS();
/// @dev The timestamp in the lookahead is not of a valid future slot in the present epoch
error InvalidSlotTimestamp();

/// @dev Accepts block proposal by an operator and forwards it to TaikoL1 contract
function newBlockProposal(
bytes calldata blockParams,
bytes calldata txList,
LookaheadEntry[] calldata lookaheadEntries
uint256 lookaheadHint,
LookaheadSetParam[] calldata lookaheadSetParams
) external;

/// @dev Slashes a preconfer if the txn and ordering in a signed preconf does not match the actual block
Expand All @@ -48,5 +79,5 @@ interface IPreconfTaskManager {
bytes32[] memory beaconBlockProof
) external;

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

interface ITaikoL1 {
struct BlockMetadata {
bytes32 l1Hash;
bytes32 difficulty;
bytes32 blobHash;
bytes32 extraData;
bytes32 depositsHash;
address coinbase;
uint64 id;
uint32 gasLimit;
uint64 timestamp;
uint64 l1Height;
uint16 minTier;
bool blobUsed;
bytes32 parentMetaHash;
address sender;
}

struct EthDeposit {
address recipient;
uint96 amount;
uint64 id;
}

function proposeBlock(bytes calldata _params, bytes calldata _txList)
external
payable
returns (BlockMetadata memory meta_, EthDeposit[] memory deposits_);
}

0 comments on commit 3303b12

Please sign in to comment.