Skip to content

Commit

Permalink
feat(protocol): emit blob hashes in event and split meta into 2 struc…
Browse files Browse the repository at this point in the history
…ts (#18817)
  • Loading branch information
dantaik authored Jan 24, 2025
1 parent ece8ff9 commit 7ac4087
Show file tree
Hide file tree
Showing 14 changed files with 1,080 additions and 1,540 deletions.
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

0 comments on commit 7ac4087

Please sign in to comment.