Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(protocol): emit blob hashes in event and split meta into 2 structs #18817

Merged
merged 24 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 35 additions & 18 deletions packages/protocol/contracts/layer1/based/ITaikoInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ interface ITaikoInbox {
}

struct BlobParams {
// The hashes of the blob. Note that if this array is not empty. `firstBlobIndex` and
// `numBlobs` must be 0.
bytes32[] blobHashes;
// The index of the first blob in this batch.
uint8 firstBlobIndex;
// The number of blobs in this batch. Blobs are initially concatenated and subsequently
// decompressed via Zlib.
uint8 numBlobs;
// The byte offset of the blob in the batch.
uint32 byteOffset;
// The byte size of the blob.
uint32 byteSize;
}

Expand All @@ -53,25 +58,35 @@ interface ITaikoInbox {
BlockParams[] blocks;
}

struct BatchMetadata {
bytes32 txListHash;
/// @dev This struct holds batch information essential for constructing blocks offchain, but it
/// does not include data necessary for batch proving.
struct BatchInfo {
bytes32 txsHash;
// Data to build L2 blocks
BlockParams[] blocks;
bytes32[] blobHashes;
bytes32 extraData;
address coinbase;
uint64 batchId;
uint32 gasLimit;
uint64 lastBlockTimestamp;
bytes32 parentMetaHash;
address proposer;
uint96 livenessBond;
uint64 proposedAt; // Used by node/client
uint64 proposedIn; // Used by node/client
uint32 blobByteOffset;
uint32 blobByteSize;
uint32 gasLimit;
// Data for the L2 anchor transaction, shared by all blocks in the batch
uint64 anchorBlockId;
// corresponds to the `_anchorStateRoot` parameter in the anchor transaction.
// The batch's validity proof shall verify the integrity of these two values.
bytes32 anchorBlockHash;
bytes32[] signalSlots;
bytes32 anchorInput;
BlockParams[] blocks;
BlobParams blobParams;
LibSharedData.BaseFeeConfig baseFeeConfig;
bytes32[] signalSlots;
}

/// @dev This struct holds batch metadata essential for proving the batch.
struct BatchMetadata {
bytes32 infoHash;
address proposer;
uint64 batchId;
uint64 proposedAt; // Used by node/client
}

/// @notice Struct representing transition to be proven.
Expand Down Expand Up @@ -208,10 +223,10 @@ interface ITaikoInbox {
event Stats2Updated(Stats2 stats2);

/// @notice Emitted when a batch is proposed.
/// @param info The info of the proposed batch.
/// @param meta The metadata of the proposed batch.
/// @param calldataUsed Whether calldata is used for txList DA.
/// @param txListInCalldata The tx list in calldata.
event BatchProposed(BatchMetadata meta, bool calldataUsed, bytes txListInCalldata);
/// @param txList The tx list in calldata.
event BatchProposed(BatchInfo info, BatchMetadata meta, bytes txList);

/// @notice Emitted when multiple transitions are proved.
/// @param verifier The address of the verifier.
Expand Down Expand Up @@ -250,6 +265,7 @@ interface ITaikoInbox {
error CustomProposerNotAllowed();
error EtherNotPaidAsBond();
error InsufficientBond();
error InvalidBlobParams();
error InvalidGenesisBlockHash();
error InvalidParams();
error InvalidTransitionBlockHash();
Expand All @@ -259,7 +275,7 @@ interface ITaikoInbox {
error MsgValueNotZero();
error NoBlocksToProve();
error NotFirstProposal();
error NotPreconfRouter();
error NotInboxOperator();
error ParentMetaHashMismatch();
error SameTransition();
error SignalNotSent();
Expand All @@ -276,13 +292,14 @@ interface ITaikoInbox {
/// @param _params ABI-encoded BlockParams.
/// @param _txList The transaction list in calldata. If the txList is empty, blob will be used
/// for data availability.
/// @return Batch metadata.
/// @return info_ The info of the proposed batch.
/// @return meta_ The metadata of the proposed batch.
function proposeBatch(
bytes calldata _params,
bytes calldata _txList
)
external
returns (BatchMetadata memory);
returns (BatchInfo memory info_, BatchMetadata memory meta_);

/// @notice Proves state transitions for multiple batches with a single aggregated proof.
/// @param _params ABI-encoded parameter containing:
Expand Down
117 changes: 76 additions & 41 deletions packages/protocol/contracts/layer1/based/TaikoInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {
/// @param _params ABI-encoded BlockParams.
/// @param _txList The transaction list in calldata. If the txList is empty, blob will be used
/// for data availability.
/// @return meta_ Batch metadata.
/// @return info_ The info of the proposed batch.
/// @return meta_ The metadata of the proposed batch.
function proposeBatch(
bytes calldata _params,
bytes calldata _txList
)
external
public
nonReentrant
returns (BatchMetadata memory meta_)
returns (BatchInfo memory info_, BatchMetadata memory meta_)
{
Stats2 memory stats2 = state.stats2;
require(!stats2.paused, ContractPaused());
Expand All @@ -69,32 +70,44 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {
BatchParams memory params = abi.decode(_params, (BatchParams));

{
address preconfRouter = resolve(LibStrings.B_PRECONF_ROUTER, true);
if (preconfRouter == address(0)) {
address operator = resolve(LibStrings.B_INBOX_OPERATOR, true);
if (operator == address(0)) {
require(params.proposer == address(0), CustomProposerNotAllowed());
params.proposer = msg.sender;

// blob hashes are only accepted if the caller is trusted.
require(params.blobParams.blobHashes.length == 0, InvalidBlobParams());
} else {
require(msg.sender == preconfRouter, NotPreconfRouter());
require(msg.sender == operator, NotInboxOperator());
require(params.proposer != address(0), CustomProposerMissing());
}

if (params.coinbase == address(0)) {
params.coinbase = params.proposer;
}

if (params.revertIfNotFirstProposal) {
require(state.stats2.lastProposedIn != block.number, NotFirstProposal());
}
}

if (params.revertIfNotFirstProposal) {
require(state.stats2.lastProposedIn != block.number, NotFirstProposal());
bool calldataUsed = _txList.length != 0;

if (!calldataUsed) {
if (params.blobParams.blobHashes.length == 0) {
require(params.blobParams.numBlobs != 0, BlobNotSpecified());
} else {
require(
params.blobParams.numBlobs == 0 && params.blobParams.firstBlobIndex == 0,
InvalidBlobParams()
);
}
}

// Keep track of last batch's information.
Batch storage lastBatch =
state.batches[(stats2.numBatches - 1) % config.batchRingBufferSize];

bool calldataUsed = _txList.length != 0;

require(calldataUsed || params.blobParams.numBlobs != 0, BlobNotSpecified());

(uint64 anchorBlockId, uint64 lastBlockTimestamp) = _validateBatchParams(
params,
config.maxAnchorHeightOffset,
Expand All @@ -113,37 +126,43 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {
// use
// the following approach to calculate a block's difficulty:
// `keccak256(abi.encode("TAIKO_DIFFICULTY", block.number))`

meta_ = BatchMetadata({
txListHash: calldataUsed ? keccak256(_txList) : _calcTxListHash(params.blobParams),
info_ = BatchInfo({
txsHash: bytes32(0), // to be initialised later
//
// Data to build L2 blocks
blocks: params.blocks,
blobHashes: new bytes32[](0), // to be initialised later
extraData: bytes32(uint256(config.baseFeeConfig.sharingPctg)),
coinbase: params.coinbase,
batchId: stats2.numBatches,
gasLimit: config.blockMaxGasLimit,
lastBlockTimestamp: lastBlockTimestamp,
parentMetaHash: lastBatch.metaHash,
proposer: params.proposer,
livenessBond: config.livenessBondBase
+ config.livenessBondPerBlock * uint96(params.blocks.length),
proposedAt: uint64(block.timestamp),
proposedIn: uint64(block.number),
blobByteOffset: params.blobParams.byteOffset,
blobByteSize: params.blobParams.byteSize,
gasLimit: config.blockMaxGasLimit,
//
// Data for the L2 anchor transaction, shared by all blocks in the batch
anchorBlockId: anchorBlockId,
anchorBlockHash: blockhash(anchorBlockId),
signalSlots: params.signalSlots,
blocks: params.blocks,
anchorInput: params.anchorInput,
blobParams: params.blobParams,
baseFeeConfig: config.baseFeeConfig
baseFeeConfig: config.baseFeeConfig,
signalSlots: params.signalSlots
});

require(meta_.anchorBlockHash != 0, ZeroAnchorBlockHash());
require(meta_.txListHash != 0, BlobNotFound());
bytes32 metaHash = keccak256(abi.encode(meta_));
require(info_.anchorBlockHash != 0, ZeroAnchorBlockHash());

(info_.txsHash, info_.blobHashes) =
_calculateTxsHash(keccak256(_txList), params.blobParams);

meta_ = BatchMetadata({
infoHash: keccak256(abi.encode(info_)),
proposer: params.proposer,
batchId: stats2.numBatches,
proposedAt: uint64(block.timestamp)
});

Batch storage batch = state.batches[stats2.numBatches % config.batchRingBufferSize];

// SSTORE #1
batch.metaHash = metaHash;
batch.metaHash = keccak256(abi.encode(meta_));

// SSTORE #2 {{
batch.batchId = stats2.numBatches;
Expand All @@ -154,21 +173,25 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {
batch.reserved4 = 0;
// SSTORE }}

uint96 livenessBond =
config.livenessBondBase + config.livenessBondPerBlock * uint96(params.blocks.length);
_debitBond(params.proposer, livenessBond);

// SSTORE #3 {{
if (stats2.numBatches == config.forkHeights.pacaya) {
batch.lastBlockId = batch.batchId + uint64(params.blocks.length) - 1;
} else {
batch.lastBlockId = lastBatch.lastBlockId + uint64(params.blocks.length);
}
batch.livenessBond = meta_.livenessBond;

batch.livenessBond = livenessBond;
batch._reserved3 = 0;
// SSTORE }}

stats2.numBatches += 1;
stats2.lastProposedIn = uint56(block.number);

_debitBond(params.proposer, meta_.livenessBond);
emit BatchProposed(meta_, calldataUsed, _txList);
emit BatchProposed(info_, meta_, _txList);
} // end-of-unchecked

_verifyBatches(config, stats2, 1);
Expand Down Expand Up @@ -515,18 +538,30 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {
state.stats2.paused = true;
}

function _calcTxListHash(BlobParams memory _blobParams)
function _calculateTxsHash(
bytes32 _txListHash,
BlobParams memory _blobParams
)
internal
view
virtual
returns (bytes32)
returns (bytes32 hash_, bytes32[] memory blobHashes_)
{
bytes32[] memory blobHashes = new bytes32[](_blobParams.numBlobs);
for (uint256 i; i < _blobParams.numBlobs; ++i) {
blobHashes[i] = blobhash(_blobParams.firstBlobIndex + i);
require(blobHashes[i] != 0, BlobNotFound());
unchecked {
if (_blobParams.blobHashes.length != 0) {
blobHashes_ = _blobParams.blobHashes;
} else {
blobHashes_ = new bytes32[](_blobParams.numBlobs);
for (uint256 i; i < _blobParams.numBlobs; ++i) {
blobHashes_[i] = blobhash(_blobParams.firstBlobIndex + i);
}
}

for (uint256 i; i < blobHashes_.length; ++i) {
require(blobHashes_[i] != 0, BlobNotFound());
}
hash_ = keccak256(abi.encode(_txListHash, blobHashes_));
}
return keccak256(abi.encode(blobHashes));
}

// Private functions -----------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface IPreconfRouter {
/// @param _params ABI-encoded parameters for the preconfing operation.
/// @param _batchParams ABI-encoded parameters specific to the batch.
/// @param _batchTxList The transaction list associated to the batch.
/// @return meta_ BatchMetadata containing metadata about the proposed batch.
/// @return meta_ The metadata of the proposed batch.
function proposePreconfedBlocks(
bytes calldata _params,
bytes calldata _batchParams,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ contract PreconfRouter is EssentialContract, IPreconfRouter {

// Call the proposeBatch function on the TaikoInbox
address taikoInbox = resolve(LibStrings.B_TAIKO, false);
meta_ = ITaikoInbox(taikoInbox).proposeBatch(_batchParams, _batchTxList);
(, meta_) = ITaikoInbox(taikoInbox).proposeBatch(_batchParams, _batchTxList);

// Verify that the sender had set itself as the proposer
require(meta_.proposer == msg.sender, ProposerIsNotTheSender());
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/contracts/shared/libs/LibStrings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ library LibStrings {
bytes32 internal constant B_ERC1155_VAULT = bytes32("erc1155_vault");
bytes32 internal constant B_ERC20_VAULT = bytes32("erc20_vault");
bytes32 internal constant B_ERC721_VAULT = bytes32("erc721_vault");
bytes32 internal constant B_INBOX_OPERATOR = bytes32("inbox_operator");
bytes32 internal constant B_PRECONF_ROUTER = bytes32("preconf_router");
bytes32 internal constant B_PRECONF_WHITELIST = bytes32("preconf_whitelist");
bytes32 internal constant B_PRECONF_WHITELIST_OWNER = bytes32("preconf_whitelist_owner");
Expand Down
12 changes: 10 additions & 2 deletions packages/protocol/test/layer1/Layer1Test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,16 @@ contract ConfigurableInbox is TaikoInbox {
return __config;
}

function _calcTxListHash(BlobParams memory) internal pure override returns (bytes32) {
return keccak256("BLOB");
function _calculateTxsHash(
bytes32 _txListHash,
BlobParams memory _blobParams
)
internal
pure
override
returns (bytes32, bytes32[] memory)
{
return (_txListHash, new bytes32[](_blobParams.numBlobs));
}
}

Expand Down
Loading