diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index b9896f2d81..664d18ff31 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -83,15 +83,12 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents { { TaikoData.Config memory config = getConfig(); - TaikoData.BlockMetadataV2 memory meta2; - (meta2, deposits_) = LibProposing.proposeBlock(state, config, this, _params, _txList); - - if (meta2.id >= config.ontakeForkHeight) revert L1_FORK_ERROR(); + (meta_,, deposits_) = LibProposing.proposeBlock(state, config, this, _params, _txList); + if (meta_.id >= config.ontakeForkHeight) revert L1_FORK_ERROR(); if (LibUtils.shouldVerifyBlocks(config, meta_.id, true) && !state.slotB.provingPaused) { LibVerifying.verifyBlocks(state, config, this, config.maxBlocksToVerify); } - meta_ = LibData.blockMetadataV2toV1(meta2); } function proposeBlockV2( @@ -106,7 +103,7 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents { { TaikoData.Config memory config = getConfig(); - (meta_,) = LibProposing.proposeBlock(state, config, this, _params, _txList); + (, meta_,) = LibProposing.proposeBlock(state, config, this, _params, _txList); if (meta_.id < config.ontakeForkHeight) revert L1_FORK_ERROR(); if (LibUtils.shouldVerifyBlocks(config, meta_.id, true) && !state.slotB.provingPaused) { diff --git a/packages/protocol/contracts/L1/libs/LibData.sol b/packages/protocol/contracts/L1/libs/LibData.sol index 250dfe53cc..8804cd2000 100644 --- a/packages/protocol/contracts/L1/libs/LibData.sol +++ b/packages/protocol/contracts/L1/libs/LibData.sol @@ -51,10 +51,7 @@ library LibData { }); } - function metadataV1toV2( - TaikoData.BlockMetadata memory _v1, - uint96 _livenessBond - ) + function blockMetadataV1toV2(TaikoData.BlockMetadata memory _v1) internal pure returns (TaikoData.BlockMetadataV2 memory) @@ -73,7 +70,7 @@ library LibData { blobUsed: _v1.blobUsed, parentMetaHash: _v1.parentMetaHash, proposer: _v1.sender, - livenessBond: _livenessBond, + livenessBond: 0, proposedAt: 0, proposedIn: 0, blobTxListOffset: 0, @@ -84,17 +81,4 @@ library LibData { blockGasIssuance: 0 }); } - - function hashMetadata( - bool postFork, - TaikoData.BlockMetadataV2 memory _meta - ) - internal - pure - returns (bytes32) - { - return postFork - ? keccak256(abi.encode(_meta)) // - : keccak256(abi.encode(blockMetadataV2toV1(_meta))); - } } diff --git a/packages/protocol/contracts/L1/libs/LibProposing.sol b/packages/protocol/contracts/L1/libs/LibProposing.sol index 5a1f04564b..6edd718781 100644 --- a/packages/protocol/contracts/L1/libs/LibProposing.sol +++ b/packages/protocol/contracts/L1/libs/LibProposing.sol @@ -16,6 +16,9 @@ library LibProposing { struct Local { TaikoData.SlotB b; + TaikoData.BlockParamsV2 params; + address proposerAccess; + ITierProvider tierProvider; bytes32 parentMetaHash; bool postFork; } @@ -60,7 +63,9 @@ library LibProposing { /// @param _resolver Address resolver interface. /// @param _data Encoded data bytes containing the block params. /// @param _txList Transaction list bytes (if not blob). - /// @return meta_ The constructed block's metadata. + /// @return metaV1_ The constructed block's metadata v1. + /// @return meta_ The constructed block's metadata v2. + /// @return deposits_ An empty ETH deposit array. function proposeBlock( TaikoData.State storage _state, TaikoData.Config memory _config, @@ -69,18 +74,23 @@ library LibProposing { bytes calldata _txList ) public - returns (TaikoData.BlockMetadataV2 memory meta_, TaikoData.EthDeposit[] memory deposits_) + returns ( + TaikoData.BlockMetadata memory metaV1_, + TaikoData.BlockMetadataV2 memory meta_, + TaikoData.EthDeposit[] memory deposits_ + ) { // Checks proposer access. - { - address access = _resolver.resolve(LibStrings.B_PROPOSER_ACCESS, true); - if (access != address(0) && !IProposerAccess(access).isProposerEligible(msg.sender)) { - revert L1_INVALID_PROPOSER(); - } + Local memory local; + + local.proposerAccess = _resolver.resolve(LibStrings.B_PROPOSER_ACCESS, true); + if ( + local.proposerAccess != address(0) + && !IProposerAccess(local.proposerAccess).isProposerEligible(msg.sender) + ) { + revert L1_INVALID_PROPOSER(); } - // Taiko, as a Based Rollup, enables permissionless block proposals. - Local memory local; local.b = _state.slotB; local.postFork = local.b.numBlocks >= _config.ontakeForkHeight; @@ -90,93 +100,90 @@ library LibProposing { revert L1_TOO_MANY_BLOCKS(); } - TaikoData.BlockParamsV2 memory params; - if (local.postFork) { if (_data.length != 0) { - params = abi.decode(_data, (TaikoData.BlockParamsV2)); + local.params = abi.decode(_data, (TaikoData.BlockParamsV2)); // otherwise use a default BlockParamsV2 with 0 values } } else { - params = LibData.blockParamsV1ToV2(abi.decode(_data, (TaikoData.BlockParams))); + local.params = LibData.blockParamsV1ToV2(abi.decode(_data, (TaikoData.BlockParams))); } - if (params.coinbase == address(0)) { - params.coinbase = msg.sender; + if (local.params.coinbase == address(0)) { + local.params.coinbase = msg.sender; } - if (!local.postFork || params.anchorBlockId == 0) { - params.anchorBlockId = uint64(block.number - 1); + if (!local.postFork || local.params.anchorBlockId == 0) { + local.params.anchorBlockId = uint64(block.number - 1); } - if (!local.postFork || params.timestamp == 0) { - params.timestamp = uint64(block.timestamp); + if (!local.postFork || local.params.timestamp == 0) { + local.params.timestamp = uint64(block.timestamp); } // Verify params against the parent block. - { - TaikoData.Block storage parentBlk = - _state.blocks[(local.b.numBlocks - 1) % _config.blockRingBufferSize]; - - if (local.postFork) { - // Verify the passed in L1 state block number. - // We only allow the L1 block to be 2 epochs old. - // The other constraint is that the L1 block number needs to be larger than or equal - // the one in the previous L2 block. - if ( - params.anchorBlockId + _config.maxAnchorHeightOffset < block.number // - || params.anchorBlockId >= block.number - || params.anchorBlockId < parentBlk.proposedIn - ) { - revert L1_INVALID_ANCHOR_BLOCK(); - } - - // Verify the passed in timestamp. - // We only allow the timestamp to be 2 epochs old. - // The other constraint is that the timestamp needs to be larger than or equal the - // one in the previous L2 block. - if ( - params.timestamp + _config.maxAnchorHeightOffset * 12 < block.timestamp - || params.timestamp > block.timestamp || params.timestamp < parentBlk.proposedAt - ) { - revert L1_INVALID_TIMESTAMP(); - } + TaikoData.Block storage parentBlk = + _state.blocks[(local.b.numBlocks - 1) % _config.blockRingBufferSize]; + + if (local.postFork) { + // Verify the passed in L1 state block number. + // We only allow the L1 block to be 2 epochs old. + // The other constraint is that the L1 block number needs to be larger than or equal + // the one in the previous L2 block. + if ( + local.params.anchorBlockId + _config.maxAnchorHeightOffset < block.number // + || local.params.anchorBlockId >= block.number + || local.params.anchorBlockId < parentBlk.proposedIn + ) { + revert L1_INVALID_ANCHOR_BLOCK(); } - // Check if parent block has the right meta hash. This is to allow the proposer to make - // sure the block builds on the expected latest chain state. - if (params.parentMetaHash == 0) { - params.parentMetaHash = parentBlk.metaHash; - } else if (params.parentMetaHash != parentBlk.metaHash) { - revert L1_UNEXPECTED_PARENT(); + // Verify the passed in timestamp. + // We only allow the timestamp to be 2 epochs old. + // The other constraint is that the timestamp needs to be larger than or equal the + // one in the previous L2 block. + if ( + local.params.timestamp + _config.maxAnchorHeightOffset * 12 < block.timestamp + || local.params.timestamp > block.timestamp + || local.params.timestamp < parentBlk.proposedAt + ) { + revert L1_INVALID_TIMESTAMP(); } } + // Check if parent block has the right meta hash. This is to allow the proposer to make + // sure the block builds on the expected latest chain state. + if (local.params.parentMetaHash == 0) { + local.params.parentMetaHash = parentBlk.metaHash; + } else if (local.params.parentMetaHash != parentBlk.metaHash) { + revert L1_UNEXPECTED_PARENT(); + } + // Initialize metadata to compute a metaHash, which forms a part of // the block data to be stored on-chain for future integrity checks. // If we choose to persist all data fields in the metadata, it will // require additional storage slots. unchecked { meta_ = TaikoData.BlockMetadataV2({ - anchorBlockHash: blockhash(params.anchorBlockId), + anchorBlockHash: blockhash(local.params.anchorBlockId), difficulty: keccak256(abi.encode("TAIKO_DIFFICULTY", local.b.numBlocks)), blobHash: 0, // to be initialized below - extraData: params.extraData, - coinbase: params.coinbase, + extraData: local.params.extraData, + coinbase: local.params.coinbase, id: local.b.numBlocks, gasLimit: _config.blockMaxGasLimit, - timestamp: params.timestamp, - anchorBlockId: params.anchorBlockId, + timestamp: local.params.timestamp, + anchorBlockId: local.params.anchorBlockId, minTier: 0, // to be initialized below blobUsed: _txList.length == 0, - parentMetaHash: params.parentMetaHash, + parentMetaHash: local.params.parentMetaHash, proposer: msg.sender, livenessBond: _config.livenessBond, proposedAt: uint64(block.timestamp), proposedIn: uint64(block.number), - blobTxListOffset: params.blobTxListOffset, - blobTxListLength: params.blobTxListLength, - blobIndex: params.blobIndex, + blobTxListOffset: local.params.blobTxListOffset, + blobTxListLength: local.params.blobTxListLength, + blobIndex: local.params.blobIndex, basefeeAdjustmentQuotient: _config.basefeeAdjustmentQuotient, basefeeSharingPctg: _config.basefeeSharingPctg, blockGasIssuance: _config.blockGasIssuance @@ -191,29 +198,34 @@ library LibProposing { // proposeBlock functions are called more than once in the same // L1 transaction, these multiple L2 blocks will share the same // blob. - meta_.blobHash = blobhash(params.blobIndex); + meta_.blobHash = blobhash(local.params.blobIndex); if (meta_.blobHash == 0) revert L1_BLOB_NOT_FOUND(); } else { meta_.blobHash = keccak256(_txList); emit CalldataTxList(meta_.id, _txList); } - { - ITierRouter tierRouter = ITierRouter(_resolver.resolve(LibStrings.B_TIER_ROUTER, false)); - ITierProvider tierProvider = ITierProvider(tierRouter.getProvider(local.b.numBlocks)); + local.tierProvider = ITierProvider( + ITierRouter(_resolver.resolve(LibStrings.B_TIER_ROUTER, false)).getProvider( + local.b.numBlocks + ) + ); + + // Use the difficulty as a random number + meta_.minTier = local.tierProvider.getMinTier(uint256(meta_.difficulty)); - // Use the difficulty as a random number - meta_.minTier = tierProvider.getMinTier(uint256(meta_.difficulty)); + if (!local.postFork) { + metaV1_ = LibData.blockMetadataV2toV1(meta_); } // Create the block that will be stored onchain TaikoData.Block memory blk = TaikoData.Block({ - metaHash: LibData.hashMetadata(local.postFork, meta_), + metaHash: local.postFork ? keccak256(abi.encode(meta_)) : keccak256(abi.encode(metaV1_)), assignedProver: address(0), livenessBond: local.postFork ? 0 : meta_.livenessBond, blockId: local.b.numBlocks, - proposedAt: local.postFork ? params.timestamp : uint64(block.timestamp), - proposedIn: local.postFork ? params.anchorBlockId : uint64(block.number), + proposedAt: local.postFork ? local.params.timestamp : uint64(block.timestamp), + proposedIn: local.postFork ? local.params.anchorBlockId : uint64(block.number), // For a new block, the next transition ID is always 1, not 0. nextTransitionId: 1, livenessBondReturned: false, @@ -231,7 +243,7 @@ library LibProposing { LibBonds.debitBond(_state, _resolver, msg.sender, _config.livenessBond); - // Bribe the block builder. Unlock 1559-tips, this tip is only made + // Bribe the block builder. Unlike 1559-tips, this tip is only made // if this transaction succeeds. if (msg.value != 0 && block.coinbase != address(0)) { address(block.coinbase).sendEtherAndVerify(msg.value); @@ -243,10 +255,10 @@ library LibProposing { emit BlockProposedV2(meta_.id, meta_); } else { emit BlockProposed({ - blockId: meta_.id, + blockId: metaV1_.id, assignedProver: msg.sender, livenessBond: _config.livenessBond, - meta: LibData.blockMetadataV2toV1(meta_), + meta: metaV1_, depositsProcessed: deposits_ }); } diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index 9e1f8eb235..345957f440 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -152,13 +152,12 @@ library LibProving { _input, (TaikoData.BlockMetadataV2, TaikoData.Transition, TaikoData.TierProof) ); } else { - TaikoData.BlockMetadata memory meta1; + TaikoData.BlockMetadata memory metaV1; - (meta1, tran, proof) = abi.decode( + (metaV1, tran, proof) = abi.decode( _input, (TaikoData.BlockMetadata, TaikoData.Transition, TaikoData.TierProof) ); - // Below, the liveness bond parameter must be 0 to force reading from block storage. - meta = LibData.metadataV1toV2(meta1, 0); + meta = LibData.blockMetadataV1toV2(metaV1); } if (_blockId != meta.id) revert LibUtils.L1_INVALID_BLOCK_ID(); @@ -197,8 +196,12 @@ library LibProving { // Check the integrity of the block data. It's worth noting that in // theory, this check may be skipped, but it's included for added // caution. - if (local.metaHash != LibData.hashMetadata(local.postFork, meta)) { - revert L1_BLOCK_MISMATCH(); + { + bytes32 metaHash = local.postFork + ? keccak256(abi.encode(meta)) + : keccak256(abi.encode(LibData.blockMetadataV2toV1(meta))); + + if (local.metaHash != metaHash) revert L1_BLOCK_MISMATCH(); } // Each transition is uniquely identified by the parentHash, with the diff --git a/packages/protocol/contracts/L1/provers/GuardianProver.sol b/packages/protocol/contracts/L1/provers/GuardianProver.sol index d77abcde01..a86f820719 100644 --- a/packages/protocol/contracts/L1/provers/GuardianProver.sol +++ b/packages/protocol/contracts/L1/provers/GuardianProver.sol @@ -197,7 +197,7 @@ contract GuardianProver is IVerifier, EssentialContract { } function approveV2( - TaikoData.BlockMetadataV2 calldata _meta2, + TaikoData.BlockMetadataV2 calldata _metaV2, TaikoData.Transition calldata _tran, TaikoData.TierProof calldata _proof ) @@ -207,10 +207,10 @@ contract GuardianProver is IVerifier, EssentialContract { returns (bool) { return _approve({ - _blockId: _meta2.id, - _proofHash: keccak256(abi.encode(_meta2, _tran, _proof.data)), + _blockId: _metaV2.id, + _proofHash: keccak256(abi.encode(_metaV2, _tran, _proof.data)), _blockHash: _tran.blockHash, - _data: abi.encode(_meta2, _tran, _proof), + _data: abi.encode(_metaV2, _tran, _proof), _proofData: _proof.data }); } diff --git a/packages/protocol/contracts/mainnet/MainnetTierRouter.sol b/packages/protocol/contracts/mainnet/MainnetTierRouter.sol index 4047ab77de..f3e2dc6cfa 100644 --- a/packages/protocol/contracts/mainnet/MainnetTierRouter.sol +++ b/packages/protocol/contracts/mainnet/MainnetTierRouter.sol @@ -8,7 +8,7 @@ import "../L1/tiers/ITierRouter.sol"; /// @custom:security-contact security@taiko.xyz contract MainnetTierRouter is ITierRouter { /// @inheritdoc ITierRouter - function getProvider(uint256 _blockId) external pure returns (address) { + function getProvider(uint256) external pure returns (address) { return 0x4cffe56C947E26D07C14020499776DB3e9AE3a23; // TierProviderV2 } } diff --git a/packages/protocol/test/L1/TaikoL1testGroupA1.t.sol b/packages/protocol/test/L1/TaikoL1testGroupA1.t.sol index 6ff58ad9e0..4d417691b2 100644 --- a/packages/protocol/test/L1/TaikoL1testGroupA1.t.sol +++ b/packages/protocol/test/L1/TaikoL1testGroupA1.t.sol @@ -65,8 +65,8 @@ contract TaikoL1TestGroupA1 is TaikoL1TestGroupBase { TaikoData.BlockParamsV2 memory params; for (; i <= ontakeForkHeight + 5; ++i) { - TaikoData.BlockMetadataV2 memory meta2 = proposeBlockV2(Alice, params, ""); - printBlockAndTrans(meta2.id); + TaikoData.BlockMetadataV2 memory metaV2 = proposeBlockV2(Alice, params, ""); + printBlockAndTrans(metaV2.id); TaikoData.Block memory blk = L1.getBlock(i); assertEq(blk.livenessBond, 0); assertEq(blk.assignedProver, address(0)); @@ -79,10 +79,10 @@ contract TaikoL1TestGroupA1 is TaikoL1TestGroupBase { mineAndWrap(10 seconds); - proveBlock2(Alice, meta2, parentHash, blockHash, stateRoot, meta2.minTier, ""); + proveBlock2(Alice, metaV2, parentHash, blockHash, stateRoot, metaV2.minTier, ""); parentHash = blockHash; - printBlockAndTrans(meta2.id); + printBlockAndTrans(metaV2.id); blk = L1.getBlock(i); assertEq(blk.livenessBond, 0); assertEq(blk.assignedProver, address(0));