diff --git a/packages/protocol/contracts/L1/BasedOperator.sol b/packages/protocol/contracts/L1/BasedOperator.sol index 7b307585db9c..181cb41cb272 100644 --- a/packages/protocol/contracts/L1/BasedOperator.sol +++ b/packages/protocol/contracts/L1/BasedOperator.sol @@ -137,7 +137,7 @@ contract BasedOperator is EssentialContract, TaikoErrors { // invalid // Get the currently stored transition TaikoData.TransitionState memory storedTransition = taiko.getTransition( - proofBatch.blockMetadata.l2BlockNumber, proofBatch.transition.parentHash + proofBatch.blockMetadata.l2BlockNumber, proofBatch.transition.parentBlockHash ); // Brecht: SO we set the blockHash in proposeBlock(). diff --git a/packages/protocol/contracts/L1/TaikoData.sol b/packages/protocol/contracts/L1/TaikoData.sol index 703142f0834b..be787595fa6f 100644 --- a/packages/protocol/contracts/L1/TaikoData.sol +++ b/packages/protocol/contracts/L1/TaikoData.sol @@ -23,6 +23,7 @@ library TaikoData { /// @dev Struct containing data only required for proving a block struct BlockMetadata { bytes32 blockHash; + bytes32 parentBlockHash; bytes32 parentMetaHash; bytes32 l1Hash; uint256 difficulty; @@ -40,13 +41,13 @@ library TaikoData { /// @dev Struct representing transition to be proven. struct Transition { - bytes32 parentHash; + bytes32 parentBlockHash; bytes32 blockHash; } /// @dev Struct representing state transition data. struct TransitionState { - bytes32 blockHash; + bytes32 blockHash; //Might be removed.. uint64 timestamp; address prover; uint64 verifiableAfter; @@ -65,8 +66,7 @@ library TaikoData { /// @dev Struct holding the state variables for the {TaikoL1} contract. struct State { mapping(uint256 blockId => Block) blocks; - // Todo (Brecht): please check which one to use here (?) metaHash or blockHash - mapping(uint256 blockId => mapping(bytes32 parentMetaHash => TransitionState)) transitions; + mapping(uint256 blockId => mapping(bytes32 parentBlockHash => TransitionState)) transitions; uint64 genesisHeight; uint64 genesisTimestamp; uint64 numBlocks; diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index ebd69dded85a..4d0663bba089 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -9,7 +9,6 @@ pragma solidity ^0.8.20; import "../common/EssentialContract.sol"; import "./TaikoErrors.sol"; import "./TaikoEvents.sol"; -import "forge-std/console2.sol"; /// @title TaikoL1 contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { @@ -69,6 +68,7 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { // Verify L1 data // TODO(Brecht): needs to be more configurable for preconfirmations require(_block.l1Hash == blockhash(_block.l1StateBlockNumber), "INVALID_L1_BLOCKHASH"); + require(_block.blockHash != 0x0, "INVALID_L2_BLOCKHASH"); require(_block.difficulty == block.prevrandao, "INVALID_DIFFICULTY"); // Verify misc data require(_block.gasLimit == config.blockMaxGasLimit, "INVALID_GAS_LIMIT"); @@ -95,9 +95,6 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { TaikoData.Block storage parentBlock = state.blocks[(state.numBlocks - 1)]; - console2.log("Mi a faszom"); - console2.logBytes32(_block.parentMetaHash); - console2.logBytes32(parentBlock.metaHash); require(_block.parentMetaHash == parentBlock.metaHash, "invalid parentHash"); // Verify the passed in L1 state block number. @@ -136,10 +133,9 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { state.blocks[state.numBlocks] = blk; // Store the passed in block hash as is - state.transitions[blk.blockId][_block.parentMetaHash].blockHash = _block.blockHash; - // For now it does not matter - we are not going to prove anyways - state.transitions[blk.blockId][_block.parentMetaHash].verifiableAfter = - uint64(block.timestamp) + 365 days; + state.transitions[blk.blockId][_block.parentBlockHash].blockHash = _block.blockHash; + // Big enough number so that we are sure we don't hit that deadline in the future. + state.transitions[blk.blockId][_block.parentBlockHash].verifiableAfter = type(uint64).max; // Increment the counter (cursor) by 1. state.numBlocks++; @@ -161,10 +157,6 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { whenNotPaused onlyFromNamed("operator") { - console2.log("Miafasz van mar"); - console2.log(_block.l2BlockNumber); - console2.log(state.lastVerifiedBlockId); - console2.log(state.numBlocks); // Check that the block has been proposed but has not yet been verified. if ( _block.l2BlockNumber <= state.lastVerifiedBlockId @@ -176,13 +168,13 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { TaikoData.Block storage blk = state.blocks[_block.l2BlockNumber]; // Make sure the correct block was proven - if(blk.metaHash != keccak256(abi.encode(_block))) { + if (blk.metaHash != keccak256(abi.encode(_block))) { revert L1_INCORRECT_BLOCK(); } // Store the transition TaikoData.TransitionState storage storedTransition = - state.transitions[_block.l2BlockNumber][transition.parentHash]; + state.transitions[_block.l2BlockNumber][transition.parentBlockHash]; storedTransition.blockHash = transition.blockHash; storedTransition.prover = prover; storedTransition.verifiableAfter = uint32(block.timestamp + SECURITY_DELAY_AFTER_PROVEN); @@ -201,26 +193,23 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { { // Get the last verified blockhash TaikoData.Block storage blk = state.blocks[state.lastVerifiedBlockId]; - // Brecht: i think this was a bug, or indeed TaikoData's Transition mapping was misleading.. Now i'm using metaHash as a path/key in the mapping, so i guess we shall use it here to query data, right ? But somehow it is not working :D - // Go to the first unverified block - bytes32 blockHash = blk.metaHash; - //blockHash = blk.blockHash; + bytes32 blockHash = blk.blockHash; uint256 blockId = uint256(state.lastVerifiedBlockId) + 1; uint256 numBlocksVerified; - console2.log("V1"); + while (blockId < state.numBlocks && numBlocksVerified < maxBlocksToVerify) { blk = state.blocks[blockId]; - // Check if the parent block hash matches the actual block hash of the parent // Check if the timestamp is older than required if ( - // Brecht: I think here is also not good, or a mismatch.. How we use/save transitions. Is it blockHash or metaHash?? - state.transitions[blockId][blockHash].blockHash == bytes32(0) - || block.timestamp < state.transitions[blockId][blockHash].verifiableAfter + block + // Genesis is already verified with initialization so if we do not allow to set + // blockHash = bytes32(0), then we can remove the bytes32(0) check. + /*state.transitions[blockId][blockHash].blockHash == bytes32(0) + || */ + .timestamp < state.transitions[blockId][blockHash].verifiableAfter ) { break; } - - console2.log("V3"); // Copy the blockhash to the block blk.blockHash = state.transitions[blockId][blockHash].blockHash; // Update latest block hash @@ -272,6 +261,10 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { return uint256(state.lastVerifiedBlockId); } + function getNumOfBlocks() public view returns (uint256) { + return uint256(state.numBlocks); + } + /// @notice Gets the configuration of the TaikoL1 contract. /// @return Config struct containing configuration parameters. function getConfig() public view virtual returns (TaikoData.Config memory) { diff --git a/packages/protocol/contracts/L1/VerifierBattleRoyale.sol b/packages/protocol/contracts/L1/VerifierBattleRoyale.sol index 607926014b34..b0a99dca6e29 100644 --- a/packages/protocol/contracts/L1/VerifierBattleRoyale.sol +++ b/packages/protocol/contracts/L1/VerifierBattleRoyale.sol @@ -84,7 +84,10 @@ contract VerifierBattleRoyale is EssentialContract { TaikoData.Transition memory transitionA = proofBatch.proofs[0].transition; TaikoData.Transition memory transitionB = proofBatch.proofs[1].transition; - require(transitionA.parentHash == transitionB.parentHash, "parentHash not the same"); + require( + transitionA.parentBlockHash == transitionB.parentBlockHash, + "parentHash not the same" + ); require(transitionA.blockHash != transitionB.blockHash, "blockhash the same"); } else if (proofBatch.proofs.length == 3) { /* Multiple verifiers in a consensus show that another verifier is faulty */ @@ -105,7 +108,10 @@ contract VerifierBattleRoyale is EssentialContract { for (uint256 i = 0; i < proofBatch.proofs.length - 1; i++) { TaikoData.Transition memory transitionA = proofBatch.proofs[i].transition; TaikoData.Transition memory transitionB = proofBatch.proofs[i + 1].transition; - require(transitionA.parentHash == transitionB.parentHash, "parentHash not the same"); + require( + transitionA.parentBlockHash == transitionB.parentBlockHash, + "parentHash not the same" + ); if (i < proofBatch.proofs.length - 2) { require(transitionA.blockHash == transitionB.blockHash, "blockhash the same"); } else { diff --git a/packages/protocol/test/L1/TaikoL1.t.sol b/packages/protocol/test/L1/TaikoL1.t.sol index f7726049f442..1d29b4fd0063 100644 --- a/packages/protocol/test/L1/TaikoL1.t.sol +++ b/packages/protocol/test/L1/TaikoL1.t.sol @@ -9,7 +9,7 @@ contract TaikoL1Test is TaikoL1TestBase { TaikoL1(payable(deployProxy({ name: "taiko", impl: address(new TaikoL1()), data: "" }))); } - function test_L1_proposeBlock() external { + function test_L1_propose_prove_and_verify_blocks_sequentially() external { giveEthAndTko(Alice, 100 ether, 100 ether); TaikoData.BlockMetadata memory meta; @@ -17,27 +17,12 @@ contract TaikoL1Test is TaikoL1TestBase { vm.roll(block.number + 1); vm.warp(block.timestamp + 12); - // console2.log(block.number); - // meta.blockHash = randBytes32(); - // meta.parentHash = GENESIS_BLOCK_HASH; - // meta.l1Hash = blockhash(block.number - 1); - // meta.difficulty = block.prevrandao; - // meta.blobHash = randBytes32(); - // meta.coinbase = Alice; - // meta.l2BlockNumber = 1; - // meta.gasLimit = L1.getConfig().blockMaxGasLimit; - // meta.l1StateBlockNumber = uint32(block.number-1); - // meta.timestamp = uint64(block.timestamp - 12); // 1 block behind - - // meta.txListByteOffset = 0; - // meta.txListByteSize = 0; - // meta.blobUsed = true; bytes32 parentMetaHash; - for (uint64 blockId = 1; blockId <= 1; blockId++) { - printVariables("before propose"); + for (uint64 blockId = 1; blockId <= 20; blockId++) { + printVariables("before propose & prove & verify"); // Create metadata and propose the block - meta = createBlockMetaData(parentMetaHash, Alice, blockId, 1, true); - proposeBlock(Alice, Alice, meta); + meta = createBlockMetaData(Alice, blockId, 1, true); + proposeBlock(Alice, Alice, meta, ""); // Create proofs and prove a block BasedOperator.ProofBatch memory blockProofs = createProofs(meta, Alice, true); @@ -47,9 +32,58 @@ contract TaikoL1Test is TaikoL1TestBase { vm.warp(uint32(block.timestamp + L1.SECURITY_DELAY_AFTER_PROVEN() + 1)); vm.roll(block.number + 10); verifyBlock(1); + parentMetaHash = keccak256(abi.encode(meta)); printVariables("after verify"); + } + } - parentMetaHash = keccak256(abi.encode(meta)); + function test_L1_propose_some_blocks_in_a_row_then_prove_and_verify() external { + giveEthAndTko(Alice, 100 ether, 100 ether); + + TaikoData.BlockMetadata[] memory blockMetaDatas = new TaikoData.BlockMetadata[](20); + + vm.roll(block.number + 1); + vm.warp(block.timestamp + 12); + + bytes32 parentMetaHash; + for (uint64 blockId = 1; blockId <= 20; blockId++) { + printVariables("before propose & prove & verify"); + // Create metadata and propose the block + blockMetaDatas[blockId - 1] = createBlockMetaData(Alice, blockId, 1, true); + proposeBlock(Alice, Alice, blockMetaDatas[blockId - 1], ""); + vm.roll(block.number + 1); + vm.warp(block.timestamp + 12); + } + + for (uint64 blockId = 1; blockId <= 20; blockId++) { + // Create proofs and prove a block + BasedOperator.ProofBatch memory blockProofs = + createProofs(blockMetaDatas[blockId - 1], Alice, true); + proveBlock(Alice, abi.encode(blockProofs)); + + //Wait enought time and verify block (currently we simply just "wait enough" from latest + // block and not time it perfectly) + vm.warp(uint32(block.timestamp + L1.SECURITY_DELAY_AFTER_PROVEN() + 1)); + vm.roll(block.number + 10); + verifyBlock(1); + parentMetaHash = keccak256(abi.encode(blockMetaDatas[blockId - 1])); + printVariables("after verify 1"); } } + + function test_L1_propose_block_outside_the_4_epoch_window() external { + giveEthAndTko(Alice, 100 ether, 100 ether); + + TaikoData.BlockMetadata memory meta; + + vm.roll(block.number + 1); + vm.warp(block.timestamp + 12); + + // Create metadata and propose the block 129 blocks later only + meta = createBlockMetaData(Alice, 1, 1, true); + vm.roll(block.number + 129); + vm.warp(block.timestamp + 129 * 12); + + proposeBlock(Alice, Alice, meta, TaikoErrors.L1_INVALID_L1_STATE_BLOCK.selector); + } } diff --git a/packages/protocol/test/L1/TaikoL1TestBase.sol b/packages/protocol/test/L1/TaikoL1TestBase.sol index 540604242201..02278334205a 100644 --- a/packages/protocol/test/L1/TaikoL1TestBase.sol +++ b/packages/protocol/test/L1/TaikoL1TestBase.sol @@ -236,7 +236,8 @@ abstract contract TaikoL1TestBase is TaikoTest { function proposeBlock( address proposer, address prover, - TaikoData.BlockMetadata memory meta + TaikoData.BlockMetadata memory meta, + bytes4 revertReason ) internal returns (TaikoData.BlockMetadata memory) @@ -296,10 +297,18 @@ abstract contract TaikoL1TestBase is TaikoTest { hex"0000000000000000000000000000000000000000000000000000000000000001"; bytes memory emptyTxList; - vm.prank(proposer, proposer); - meta = basedOperator.proposeBlock{ value: 1 ether / 10 }( - abi.encode(meta), meta.blobUsed == true ? emptyTxList : dummyTxList, prover - ); + if (revertReason == "") { + vm.prank(proposer, proposer); + meta = basedOperator.proposeBlock{ value: 1 ether / 10 }( + abi.encode(meta), meta.blobUsed == true ? emptyTxList : dummyTxList, prover + ); + } else { + vm.prank(proposer, proposer); + vm.expectRevert(revertReason); + meta = basedOperator.proposeBlock{ value: 1 ether / 10 }( + abi.encode(meta), meta.blobUsed == true ? emptyTxList : dummyTxList, prover + ); + } return meta; } @@ -400,13 +409,12 @@ abstract contract TaikoL1TestBase is TaikoTest { } function printVariables(string memory comment) internal { - (,,, uint64 numBlock,,) = L1.state(); string memory str = string.concat( Strings.toString(logCount++), ":[", Strings.toString(L1.getLastVerifiedBlockId()), unicode"→", - Strings.toString(numBlock), + Strings.toString(L1.getNumOfBlocks()), "] // ", comment ); @@ -419,10 +427,9 @@ abstract contract TaikoL1TestBase is TaikoTest { } function createBlockMetaData( - bytes32 parentMetaHash, address coinbase, uint64 l2BlockNumber, - uint32 belowBlockTipHeight, // How many blocks (negatived direction) away from block.id + uint32 belowBlockTipHeight, // How many blocks below from current tip (block.id) bool blobUsed ) internal @@ -430,11 +437,9 @@ abstract contract TaikoL1TestBase is TaikoTest { { meta.blockHash = randBytes32(); - meta.parentMetaHash = parentMetaHash; - if (l2BlockNumber == 1) { - meta.parentMetaHash = hex"0000000000000000000000000000000000000000000000000000000000000000"; - } - + TaikoData.Block memory parentBlock = L1.getBlock(l2BlockNumber - 1); + meta.parentMetaHash = parentBlock.metaHash; + meta.parentBlockHash = parentBlock.blockHash; meta.l1Hash = blockhash(block.number - belowBlockTipHeight); meta.difficulty = block.prevrandao; meta.blobHash = randBytes32(); @@ -471,7 +476,7 @@ abstract contract TaikoL1TestBase is TaikoTest { // Set transition TaikoData.Transition memory transition; - transition.parentHash = L1.getBlock(meta.l2BlockNumber).blockHash; + transition.parentBlockHash = L1.getBlock(meta.l2BlockNumber - 1).blockHash; transition.blockHash = meta.blockHash; proofBatch.transition = transition;