Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
55f8190
Create PreemptiveProvableAssertionsBase
nikeshnazareth Mar 19, 2025
d0990fd
Create L1QueriesPublicationTime
nikeshnazareth Mar 19, 2025
edc6b9a
Perform L1 queries on TaikoInbox
nikeshnazareth Mar 19, 2025
b35f537
Create preemptive_assertions folder
nikeshnazareth Mar 19, 2025
5ae3f6e
Create PreemptiveProvableAssertions
nikeshnazareth Mar 19, 2025
f29cc1a
Register assertions in TaikoAnchor
nikeshnazareth Mar 19, 2025
7a8d0c4
Include endPublication logic in TaikoAnchor
nikeshnazareth Mar 19, 2025
441c708
Explain risk of retroactively injecting transactions
nikeshnazareth Mar 19, 2025
4b46114
Create asserter address
nikeshnazareth Mar 20, 2025
1e66c04
Create realtime L1 storage feed
nikeshnazareth Mar 20, 2025
4f64b7d
Create L2QueryFutureBlock
nikeshnazareth Mar 20, 2025
a952109
Create CrossRollupStorageRead
nikeshnazareth Mar 20, 2025
9763e2e
Mention added benefit of contract asserter
nikeshnazareth Mar 20, 2025
c933d45
Note that endPublication does not have to exist
nikeshnazareth Mar 21, 2025
8ee91cb
Mention block hash computation subtlety
nikeshnazareth Mar 21, 2025
e80d09e
Create new PreemptiveAssertions contract
nikeshnazareth Jun 8, 2025
8443bd6
Create RealtimeL1State asserter
nikeshnazareth Jun 9, 2025
751ca1f
Update TaikoAnchor to use the new assertion mechanism
nikeshnazareth Jun 9, 2025
ddd61ae
Create FutureL2Call
nikeshnazareth Jun 9, 2025
7c5087e
Create PublicationTimeCall asserter contract
nikeshnazareth Jun 9, 2025
f16e03f
Merge branch 'main' into preemptive-provable-assertions
nikeshnazareth Jun 10, 2025
3458987
Allow a trusted address to enable and disable assertions
nikeshnazareth Jun 10, 2025
45bd065
Remove obsolete contracts from previous version of this mechanism
nikeshnazareth Jun 10, 2025
799537e
Use pauser variable instead of assertion
nikeshnazareth Jun 10, 2025
d2c17b0
Move PublicationPauser functionality into a separate contract
nikeshnazareth Jun 10, 2025
e29fd63
Remove pauser at the end of the publication
nikeshnazareth Jun 10, 2025
911e32d
Move onlyAnchor functionality to a separate contract
nikeshnazareth Jun 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/libs/LibBlockHeader.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

library LibBlockHeader {
/// @dev This contains all the fields of the Ethereum block header in the cancun fork taken from
/// https://github.com/ethereum/go-ethereum/blob/master/core/types/block.go#L75
struct BlockHeader {
bytes32 parentHash;
bytes32 omnersHash;
address coinbase;
bytes32 stateRoot;
bytes32 transactionsRoot;
bytes32 receiptsRoot;
bytes logsBloom;
uint256 difficulty;
uint256 number;
uint64 gasLimit;
uint64 gasUsed;
uint64 timestamp;
bytes extraData;
bytes32 mixedHash;
uint64 nonce;
bytes32 baseFeePerGas;
bytes32 withdrawalsRoot;
uint64 blobGasUsed;
uint64 excessBlobGas;
bytes32 parentBeaconBlockRoot;
bytes32 requestsHash;
}

// Domain separators to identify individual fields
bytes32 constant BLOCK_HASH = keccak256("BlockHeaderHash");
bytes32 constant PARENT_HASH = keccak256("BlockHeaderParentHash");
bytes32 constant STATE_ROOT = keccak256("BlockHeaderStateRoot");
}
5 changes: 4 additions & 1 deletion src/protocol/IInbox.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

// TODO: decouple the generic inbox interface from the taiko_alethia implementation
import {CallSpecification} from "./taiko_alethia/assertions/PublicationTimeCall.sol";

interface IInbox {
function publish(uint256 nBlobs, uint64 anchorBlockId) external payable;
function publish(uint256 nBlobs, uint64 anchorBlockId, CallSpecification[] calldata callSpecs) external;
}
70 changes: 41 additions & 29 deletions src/protocol/taiko_alethia/TaikoAnchor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,9 @@
pragma solidity ^0.8.28;

import {ICommitmentStore} from "../ICommitmentStore.sol";

///@dev This contains all the fields of the Ethereum block header in the cancun fork taken from
/// https://github.com/ethereum/go-ethereum/blob/master/core/types/block.go#L75
struct BlockHeader {
bytes32 parentHash;
bytes32 omnersHash;
address coinbase;
bytes32 stateRoot;
bytes32 transactionsRoot;
bytes32 receiptsRoot;
bytes logsBloom;
uint256 difficulty;
uint256 number;
uint64 gasLimit;
uint64 gasUsed;
uint64 timestamp;
bytes extraData;
bytes32 mixedHash;
uint64 nonce;
bytes32 baseFeePerGas;
bytes32 withdrawalsRoot;
uint64 blobGasUsed;
uint64 excessBlobGas;
bytes32 parentBeaconBlockRoot;
bytes32 requestsHash;
}
import {Asserter} from "./assertions/Asserter.sol";
import {PreemptiveAssertions} from "./assertions/PreemptiveAssertions.sol";
import {LibBlockHeader} from "src/libs/LibBlockHeader.sol";

contract TaikoAnchor {
event Anchor(uint256 publicationId, uint256 anchorBlockId, bytes32 anchorBlockHash, bytes32 parentGasUsed);
Expand All @@ -38,6 +15,7 @@ contract TaikoAnchor {

uint256 public immutable fixedBaseFee;
address public immutable permittedSender; // 0x0000777735367b36bC9B61C50022d9D0700dB4Ec
PreemptiveAssertions public immutable preemptiveAssertions;

ICommitmentStore public immutable commitmentStore;

Expand All @@ -55,15 +33,22 @@ contract TaikoAnchor {
/// @param _fixedBaseFee The fixed base fee for the rollup
/// @param _permittedSender The address of the sender that can call the anchor function
/// @param _commitmentStore contract responsible storing historical commitments
constructor(uint256 _fixedBaseFee, address _permittedSender, address _commitmentStore) {
/// @param _preemptiveAssertions The contract that handles preemptive assertions
constructor(
uint256 _fixedBaseFee,
address _permittedSender,
address _commitmentStore,
PreemptiveAssertions _preemptiveAssertions
) {
require(_fixedBaseFee > 0, "fixedBaseFee must be greater than 0");
fixedBaseFee = _fixedBaseFee;
permittedSender = _permittedSender;
commitmentStore = ICommitmentStore(_commitmentStore);
preemptiveAssertions = _preemptiveAssertions;

uint256 parentId = block.number - 1;
blockHashes[parentId] = blockhash(parentId);
(circularBlocksHash,) = _calcCircularBlocksHash(block.number);
commitmentStore = ICommitmentStore(_commitmentStore);
}

/// @dev The node software will guarantee and the prover will verify the following:
Expand All @@ -83,7 +68,7 @@ contract TaikoAnchor {
uint256 _publicationId,
uint256 _anchorBlockId,
bytes32 _anchorBlockHash,
BlockHeader calldata _anchorBlockHeader,
LibBlockHeader.BlockHeader calldata _anchorBlockHeader,
bytes32 _parentGasUsed
) external onlyFromPermittedSender {
// Make sure this function can only succeed once per publication
Expand Down Expand Up @@ -118,6 +103,33 @@ contract TaikoAnchor {
emit Anchor(_publicationId, _anchorBlockId, _anchorBlockHash, _parentGasUsed);
}

/// @dev The node software will guarantee and the prover will verify the following:
/// 1. This function can only be transacted as the last transaction in the last L2 block derived from the same
/// publication. It does not have to exist if it is not required;
/// 2. This function will not revert
/// 3. The attributesHash parameter matches the corresponding field in the publication header
/// @param attributesHash the field in the publication header that represents all the attributes
/// @param attributeHashes the list of attribute hashes that were used to create the combined attributesHash
/// @param asserters The list of Asserter contracts that have unproven assertions (to be resolved in this call)
/// @param proofs The list of proofs to pass to the Asserter contracts (one per contract) to resolve the assertions
function endPublication(
bytes32 attributesHash,
bytes32[] calldata attributeHashes,
Asserter[] calldata asserters,
bytes[] calldata proofs
) external {
require(asserters.length == proofs.length, "Asserters and proofs length mismatch");
require(attributesHash == keccak256(abi.encode(attributeHashes)), "Invalid attributes");

for (uint256 i = 0; i < asserters.length; i++) {
Asserter asserter = asserters[i];
asserter.resolve(attributeHashes, proofs[i]);
}

require(preemptiveAssertions.nUnproven() == 0, "Some assertions remain unproven");
preemptiveAssertions.removePauser();
}

function l1BlockHashes(uint256 blockId) external view returns (bytes32 blockHash) {
return commitmentStore.commitmentAt(address(this), blockId);
}
Expand Down
18 changes: 16 additions & 2 deletions src/protocol/taiko_alethia/TaikoInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {DelayedInclusionStore} from "./DelayedInclusionStore.sol";
import {IInbox} from "../IInbox.sol";
import {ILookahead} from "../ILookahead.sol";
import {IProposerFees} from "../IProposerFees.sol";
import {CallSpecification} from "./assertions/PublicationTimeCall.sol";

contract TaikoInbox is IInbox, DelayedInclusionStore {
struct Metadata {
Expand All @@ -30,6 +31,7 @@ contract TaikoInbox is IInbox, DelayedInclusionStore {
uint256 private constant METADATA = 0;
uint256 private constant LAST_PUBLICATION = 1;
uint256 private constant BLOB_REFERENCE = 2;
uint256 private constant L1_CALLS = 3;

constructor(
address _publicationFeed,
Expand All @@ -45,7 +47,7 @@ contract TaikoInbox is IInbox, DelayedInclusionStore {
proposerFees = IProposerFees(_proposerFees);
}

function publish(uint256 nBlobs, uint64 anchorBlockId) external payable {
function publish(uint256 nBlobs, uint64 anchorBlockId, CallSpecification[] calldata callSpecs) external {
if (address(lookahead) != address(0)) {
require(lookahead.isCurrentPreconfer(msg.sender), "not current preconfer");
}
Expand All @@ -62,10 +64,21 @@ contract TaikoInbox is IInbox, DelayedInclusionStore {
});
require(metadata.anchorBlockHash != 0, "blockhash not found");

bytes[] memory attributes = new bytes[](3);
// Perform preemptively asserted queries
uint256 nCalls = callSpecs.length;
bytes32[] memory returnHashes = new bytes32[](nCalls);
for (uint256 i = 0; i < nCalls; i++) {
require(callSpecs[i].destination != address(publicationFeed), "Cannot call publication feed");
(bool success, bytes memory returndata) = callSpecs[i].destination.call(callSpecs[i].callData);
require(success, "Query failed");
returnHashes[i] = keccak256(returndata);
}

bytes[] memory attributes = new bytes[](4);
attributes[METADATA] = abi.encode(metadata);
attributes[LAST_PUBLICATION] = abi.encode(_lastPublicationId);
attributes[BLOB_REFERENCE] = abi.encode(blobRefRegistry.getRef(_buildBlobIndices(nBlobs)));
attributes[L1_CALLS] = abi.encode(callSpecs, returnHashes);

proposerFees.payPublicationFee(msg.sender, false);
_lastPublicationId = publicationFeed.publish(attributes).id;
Expand All @@ -75,6 +88,7 @@ contract TaikoInbox is IInbox, DelayedInclusionStore {
uint256 nInclusions = inclusions.length;
// Metadata is the same as the regular publication, so we just set `isDelayedInclusion` to true
metadata.isDelayedInclusion = true;
delete attributes[L1_CALLS];
for (uint256 i; i < nInclusions; ++i) {
attributes[METADATA] = abi.encode(metadata);
attributes[LAST_PUBLICATION] = abi.encode(_lastPublicationId);
Expand Down
19 changes: 19 additions & 0 deletions src/protocol/taiko_alethia/assertions/Asserter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {CalledByAnchor} from "./CalledByAnchor.sol";
import {PreemptiveAssertions} from "./PreemptiveAssertions.sol";

abstract contract Asserter is CalledByAnchor {
PreemptiveAssertions public immutable preemptiveAssertions;

constructor(address _preemptiveAssertions) {
preemptiveAssertions = PreemptiveAssertions(_preemptiveAssertions);
}

function resolve(bytes32[] calldata attributeHashes, bytes calldata proof) external onlyAnchor {
_resolve(attributeHashes, proof);
}

function _resolve(bytes32[] calldata attributeHashes, bytes calldata proof) internal virtual;
}
17 changes: 17 additions & 0 deletions src/protocol/taiko_alethia/assertions/CalledByAnchor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

abstract contract CalledByAnchor {
address public immutable anchor;

error CallerIsNotAnchor();

constructor(address _anchor) {
anchor = _anchor;
}

modifier onlyAnchor() {
require(msg.sender == anchor, CallerIsNotAnchor());
_;
}
}
47 changes: 47 additions & 0 deletions src/protocol/taiko_alethia/assertions/FutureL2Call.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {Asserter} from "./Asserter.sol";
import {CalledByAnchor} from "./CalledByAnchor.sol";

contract FutureL2Call is Asserter {
constructor(address _anchor, address _preemptiveAssertions)
CalledByAnchor(_anchor)
Asserter(_preemptiveAssertions)
{}

function assertFutureCall(
uint256 l2BlockNumber,
address destination,
bytes calldata callData,
bytes calldata returnData
) external {
preemptiveAssertions.createAssertion(_key(l2BlockNumber, destination, callData), keccak256(returnData));
}

function resolveCall(uint256 l2BlockNumber, address destination, bytes calldata callData) external {
require(block.number == l2BlockNumber, "Incorrect L2 block");
bytes32 key = _key(l2BlockNumber, destination, callData);
bytes32 assertedReturnHash = preemptiveAssertions.getAssertion(key);

(bool success, bytes memory returnData) = destination.call(callData);
require(success, "Call failed");
require(assertedReturnHash == keccak256(returnData), "Incorrect return data");

preemptiveAssertions.removeAssertion(key);
}

function _key(uint256 l2BlockNumber, address destination, bytes calldata callData)
internal
pure
returns (bytes32)
{
return keccak256(abi.encode(l2BlockNumber, destination, callData));
}

/// @dev This contract does not require coordination with L1, so it does not need the Asserter functionality
/// It is included anyway for structural clarity, but this function reverts to disable the behavior.
function _resolve(bytes32[] calldata, bytes calldata) internal pure override {
assert(false);
}
}
13 changes: 13 additions & 0 deletions src/protocol/taiko_alethia/assertions/IPreemptiveAssertions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

interface IPreemptiveAssertions {
struct Assertion {
uint256 status;
bytes32 value;
}

error AssertionExists();
error AssertionDoesNotExist();
error AssertionMustExistAndBeUnproven();
}
69 changes: 69 additions & 0 deletions src/protocol/taiko_alethia/assertions/PreemptiveAssertions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {CalledByAnchor} from "./CalledByAnchor.sol";
import {IPreemptiveAssertions} from "./IPreemptiveAssertions.sol";
import {PublicationPauser} from "./PublicationPauser.sol";

contract PreemptiveAssertions is IPreemptiveAssertions, PublicationPauser {
// Assertion statuses
uint256 constant UNKNOWN = 0;
uint256 constant UNPROVEN = 1;
uint256 constant PROVEN = 2;

uint256 public nUnproven;

mapping(bytes32 assertionId => Assertion) private assertions;

constructor(address _anchor) CalledByAnchor(_anchor) {}

function createAssertion(bytes32 key, bytes32 val) external whenNotPaused {
Assertion storage assertion = assertions[_assertionId(key)];
require(!_exists(assertion), AssertionExists());
assertion.status = UNPROVEN;
assertion.value = val;
nUnproven++;
}

function removeAssertion(bytes32 key) external {
Assertion storage assertion = assertions[_assertionId(key)];
require(_exists(assertion), AssertionDoesNotExist());
if (assertion.status == UNPROVEN) {
nUnproven--;
}
delete assertion.status;
delete assertion.value;
}

/// @dev This should be used when the assertion has been proven but it should remain in the mapping
/// so contracts can still query the value.
function setProven(bytes32 key) external {
Assertion storage assertion = assertions[_assertionId(key)];
require(assertion.status == UNPROVEN, AssertionMustExistAndBeUnproven());
assertion.status = PROVEN;
nUnproven--;
}

function getAssertion(bytes32 key) external view returns (bytes32 value) {
return getAssertion(key, msg.sender);
}

function getAssertion(bytes32 key, address asserter) public view returns (bytes32 value) {
bytes32 assertionId = _assertionId(key, asserter);
Assertion storage assertion = assertions[assertionId];
require(_exists(assertion), AssertionDoesNotExist());
return assertion.value;
}

function _assertionId(bytes32 key) internal view returns (bytes32) {
return _assertionId(key, msg.sender);
}

function _assertionId(bytes32 key, address asserter) internal pure returns (bytes32) {
return keccak256(abi.encode(asserter, key));
}

function _exists(Assertion storage assertion) internal view returns (bool) {
return assertion.status != UNKNOWN;
}
}
Loading