diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index 6ef8058db45..6216f70dd16 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -99,20 +99,12 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents, TaikoErrors { nonReentrant emitEventForClient { - ( - TaikoData.BlockMetadata memory meta, - TaikoData.Transition memory tran, - TaikoData.TierProof memory proof - ) = abi.decode(_input, (TaikoData.BlockMetadata, TaikoData.Transition, TaikoData.TierProof)); - - if (_blockId != meta.id) revert L1_INVALID_BLOCK_ID(); - TaikoData.Config memory config = getConfig(); TaikoToken tko = TaikoToken(resolve(LibStrings.B_TAIKO_TOKEN, false)); - LibProving.proveBlock(state, tko, config, this, meta, tran, proof); + LibProving.proveBlock(state, tko, config, this, _blockId, _input); - if (LibUtils.shouldVerifyBlocks(config, meta.id, false)) { + if (LibUtils.shouldVerifyBlocks(config, _blockId, false)) { LibVerifying.verifyBlocks(state, tko, config, this, config.maxBlocksToVerify); } } diff --git a/packages/protocol/contracts/L1/libs/LibProposing.sol b/packages/protocol/contracts/L1/libs/LibProposing.sol index db07f7c34d6..320e792fc2f 100644 --- a/packages/protocol/contracts/L1/libs/LibProposing.sol +++ b/packages/protocol/contracts/L1/libs/LibProposing.sol @@ -16,6 +16,12 @@ library LibProposing { bytes32 private constant _EMPTY_ETH_DEPOSIT_HASH = 0x569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd; + struct Local { + TaikoData.SlotB b; + TaikoData.BlockParams params; + bytes32 parentMetaHash; + } + // Warning: Any events defined here must also be defined in TaikoEvents.sol. /// @notice Emitted when a block is proposed. /// @param blockId The ID of the proposed block. @@ -64,28 +70,30 @@ library LibProposing { internal returns (TaikoData.BlockMetadata memory meta_, TaikoData.EthDeposit[] memory deposits_) { - TaikoData.BlockParams memory params = abi.decode(_data, (TaikoData.BlockParams)); + Local memory local; + local.params = 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; } // Taiko, as a Based Rollup, enables permissionless block proposals. - TaikoData.SlotB memory b = _state.slotB; + local.b = _state.slotB; // It's essential to ensure that the ring buffer for proposed blocks // still has space for at least one more block. - if (b.numBlocks >= b.lastVerifiedBlockId + _config.blockMaxProposals + 1) { + if (local.b.numBlocks >= local.b.lastVerifiedBlockId + _config.blockMaxProposals + 1) { revert L1_TOO_MANY_BLOCKS(); } - bytes32 parentMetaHash = - _state.blocks[(b.numBlocks - 1) % _config.blockRingBufferSize].metaHash; + local.parentMetaHash = + _state.blocks[(local.b.numBlocks - 1) % _config.blockRingBufferSize].metaHash; // assert(parentMetaHash != 0); // 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 && parentMetaHash != params.parentMetaHash) { + if (local.params.parentMetaHash != 0 && local.parentMetaHash != local.params.parentMetaHash) + { revert L1_UNEXPECTED_PARENT(); } @@ -98,16 +106,16 @@ library LibProposing { l1Hash: blockhash(block.number - 1), difficulty: 0, // to be initialized below blobHash: 0, // to be initialized below - extraData: params.extraData, + extraData: local.params.extraData, depositsHash: _EMPTY_ETH_DEPOSIT_HASH, - coinbase: params.coinbase, - id: b.numBlocks, + coinbase: local.params.coinbase, + id: local.b.numBlocks, gasLimit: _config.blockMaxGasLimit, timestamp: uint64(block.timestamp), l1Height: uint64(block.number - 1), minTier: 0, // to be initialized below blobUsed: _txList.length == 0, - parentMetaHash: parentMetaHash, + parentMetaHash: local.parentMetaHash, sender: msg.sender }); } @@ -131,7 +139,7 @@ library LibProposing { // 7645: Alias ORIGIN to SENDER if ( _config.checkEOAForCalldataDA - && ECDSA.recover(meta_.blobHash, params.signature) != msg.sender + && ECDSA.recover(meta_.blobHash, local.params.signature) != msg.sender ) { revert L1_INVALID_SIG(); } @@ -144,11 +152,12 @@ library LibProposing { // of multiple Taiko blocks being proposed within a single // Ethereum block, we choose to introduce a salt to this random // number as the L2 mixHash. - meta_.difficulty = keccak256(abi.encodePacked(block.prevrandao, b.numBlocks, block.number)); + meta_.difficulty = + keccak256(abi.encodePacked(block.prevrandao, local.b.numBlocks, block.number)); { ITierRouter tierRouter = ITierRouter(_resolver.resolve(LibStrings.B_TIER_ROUTER, false)); - ITierProvider tierProvider = ITierProvider(tierRouter.getProvider(b.numBlocks)); + ITierProvider tierProvider = ITierProvider(tierRouter.getProvider(local.b.numBlocks)); // Use the difficulty as a random number meta_.minTier = tierProvider.getMinTier(uint256(meta_.difficulty)); @@ -162,7 +171,7 @@ library LibProposing { // block's proposal but before it has been proven or verified. assignedProver: address(0), livenessBond: _config.livenessBond, - blockId: b.numBlocks, + blockId: local.b.numBlocks, proposedAt: meta_.timestamp, proposedIn: uint64(block.number), // For a new block, the next transition ID is always 1, not 0. @@ -172,7 +181,7 @@ library LibProposing { }); // Store the block in the ring buffer - _state.blocks[b.numBlocks % _config.blockRingBufferSize] = blk; + _state.blocks[local.b.numBlocks % _config.blockRingBufferSize] = blk; // Increment the counter (cursor) by 1. unchecked { diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index 5169100547f..ebc13cf31d0 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -92,24 +92,32 @@ library LibProving { /// @param _tko The taiko token. /// @param _config Actual TaikoData.Config. /// @param _resolver Address resolver interface. - /// @param _meta The block's metadata. - /// @param _tran The transition data. - /// @param _proof The proof. + /// @param _blockId The index of the block to prove. This is also used to + /// select the right implementation version. + /// @param _input An abi-encoded (TaikoData.BlockMetadata, TaikoData.Transition, + /// TaikoData.TierProof) tuple. function proveBlock( TaikoData.State storage _state, TaikoToken _tko, TaikoData.Config memory _config, IAddressResolver _resolver, - TaikoData.BlockMetadata memory _meta, - TaikoData.Transition memory _tran, - TaikoData.TierProof memory _proof + uint64 _blockId, + bytes calldata _input ) internal { + ( + TaikoData.BlockMetadata memory meta, + TaikoData.Transition memory tran, + TaikoData.TierProof memory proof + ) = abi.decode(_input, (TaikoData.BlockMetadata, TaikoData.Transition, TaikoData.TierProof)); + + if (_blockId != meta.id) revert L1_INVALID_BLOCK_ID(); + // Make sure parentHash is not zero // To contest an existing transition, simply use any non-zero value as // the blockHash and stateRoot. - if (_tran.parentHash == 0 || _tran.blockHash == 0 || _tran.stateRoot == 0) { + if (tran.parentHash == 0 || tran.blockHash == 0 || tran.stateRoot == 0) { revert L1_INVALID_TRANSITION(); } @@ -118,22 +126,22 @@ library LibProving { local.b = _state.slotB; // Check that the block has been proposed but has not yet been verified. - if (_meta.id <= local.b.lastVerifiedBlockId || _meta.id >= local.b.numBlocks) { + if (meta.id <= local.b.lastVerifiedBlockId || meta.id >= local.b.numBlocks) { revert L1_INVALID_BLOCK_ID(); } - local.slot = _meta.id % _config.blockRingBufferSize; + local.slot = meta.id % _config.blockRingBufferSize; TaikoData.Block storage blk = _state.blocks[local.slot]; local.blockId = blk.blockId; if (LibUtils.shouldSyncStateRoot(_config.stateRootSyncInternal, local.blockId)) { - local.stateRoot = _tran.stateRoot; + local.stateRoot = tran.stateRoot; } local.assignedProver = blk.assignedProver; if (local.assignedProver == address(0)) { - local.assignedProver = _meta.sender; + local.assignedProver = meta.sender; } local.livenessBond = blk.livenessBond; @@ -142,7 +150,7 @@ 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.blockId != _meta.id || local.metaHash != LibUtils.hashMetadata(_meta)) { + if (local.blockId != meta.id || local.metaHash != LibUtils.hashMetadata(meta)) { revert L1_BLOCK_MISMATCH(); } @@ -151,11 +159,11 @@ library LibProving { // become available. In cases where a transition with the specified // parentHash does not exist, the transition ID (tid) will be set to 0. TaikoData.TransitionState memory ts; - (local.tid, ts) = _fetchOrCreateTransition(_state, blk, _tran, local); + (local.tid, ts) = _fetchOrCreateTransition(_state, blk, tran, local); // The new proof must meet or exceed the minimum tier required by the // block or the previous proof; it cannot be on a lower tier. - if (_proof.tier == 0 || _proof.tier < _meta.minTier || _proof.tier < ts.tier) { + if (proof.tier == 0 || proof.tier < meta.minTier || proof.tier < ts.tier) { revert L1_INVALID_TIER(); } @@ -165,8 +173,8 @@ library LibProving { ITierRouter tierRouter = ITierRouter(_resolver.resolve(LibStrings.B_TIER_ROUTER, false)); ITierProvider tierProvider = ITierProvider(tierRouter.getProvider(local.blockId)); - local.tier = tierProvider.getTier(_proof.tier); - local.minTier = tierProvider.getTier(_meta.minTier); + local.tier = tierProvider.getTier(proof.tier); + local.minTier = tierProvider.getTier(meta.minTier); } local.inProvingWindow = !LibUtils.isPostDeadline({ @@ -200,38 +208,38 @@ library LibProving { // Taiko's core protocol. if (local.tier.verifierName != "") { address verifier = _resolver.resolve(local.tier.verifierName, false); - bool isContesting = _proof.tier == ts.tier && local.tier.contestBond != 0; + bool isContesting = proof.tier == ts.tier && local.tier.contestBond != 0; IVerifier.Context memory ctx = IVerifier.Context({ metaHash: local.metaHash, - blobHash: _meta.blobHash, + blobHash: meta.blobHash, // Separate msgSender to allow the prover to be any address in the future. prover: msg.sender, msgSender: msg.sender, blockId: local.blockId, isContesting: isContesting, - blobUsed: _meta.blobUsed + blobUsed: meta.blobUsed }); - IVerifier(verifier).verifyProof(ctx, _tran, _proof); + IVerifier(verifier).verifyProof(ctx, tran, proof); } local.isTopTier = local.tier.contestBond == 0; - local.sameTransition = _tran.blockHash == ts.blockHash && local.stateRoot == ts.stateRoot; + local.sameTransition = tran.blockHash == ts.blockHash && local.stateRoot == ts.stateRoot; - if (_proof.tier > ts.tier) { + if (proof.tier > ts.tier) { // Handles the case when an incoming tier is higher than the current transition's tier. // Reverts when the incoming proof tries to prove the same transition // (L1_ALREADY_PROVED). - _overrideWithHigherProof(blk, ts, _tran, _proof, local); + _overrideWithHigherProof(blk, ts, tran, proof, local); emit TransitionProved({ blockId: local.blockId, - tran: _tran, + tran: tran, prover: msg.sender, validityBond: local.tier.validityBond, - tier: _proof.tier + tier: proof.tier }); } else { // New transition and old transition on the same tier - and if this transaction tries to @@ -244,15 +252,15 @@ library LibProving { assert(ts.validityBond == 0 && ts.contester == address(0)); ts.prover = msg.sender; - ts.blockHash = _tran.blockHash; + ts.blockHash = tran.blockHash; ts.stateRoot = local.stateRoot; emit TransitionProved({ blockId: local.blockId, - tran: _tran, + tran: tran, prover: msg.sender, validityBond: 0, - tier: _proof.tier + tier: proof.tier }); } else { // Contesting but not on the highest tier @@ -283,10 +291,10 @@ library LibProving { emit TransitionContested({ blockId: local.blockId, - tran: _tran, + tran: tran, contester: msg.sender, contestBond: local.tier.contestBond, - tier: _proof.tier + tier: proof.tier }); } }