From ca4ec9b126d10c73c464e0faef9d48aecd8fbac0 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 12 Jun 2024 21:20:54 +0300 Subject: [PATCH] It compiles --- .../dev-contracts/test/DummyExecutor.sol | 16 +++++ .../state-transition/ValidatorTimelock.sol | 32 +++++++-- .../chain-deps/DiamondInit.sol | 7 ++ .../chain-deps/ZkSyncHyperchainStorage.sol | 3 + .../chain-deps/facets/Executor.sol | 71 +++++++++++++++++-- .../chain-deps/facets/Mailbox.sol | 3 + .../chain-interfaces/IExecutor.sol | 13 +++- .../libraries/PriorityTree.sol | 62 ++++++++++++++++ 8 files changed, 192 insertions(+), 15 deletions(-) create mode 100644 l1-contracts/contracts/state-transition/libraries/PriorityTree.sol diff --git a/l1-contracts/contracts/dev-contracts/test/DummyExecutor.sol b/l1-contracts/contracts/dev-contracts/test/DummyExecutor.sol index 7da7113b2..744ea1cce 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyExecutor.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyExecutor.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.24; import {IExecutor} from "../../state-transition/chain-interfaces/IExecutor.sol"; +import {PriorityOpsBatchInfo} from "../../state-transition/libraries/PriorityTree.sol"; /// @title DummyExecutor /// @notice A test smart contract implementing the IExecutor interface to simulate Executor behavior for testing purposes. @@ -125,10 +126,25 @@ contract DummyExecutor is IExecutor { ); } + function executeBatches( + StoredBatchInfo[] calldata _batchesData, + PriorityOpsBatchInfo[] calldata + ) external { + executeBatches(_batchesData); + } + function executeBatchesSharedBridge(uint256, StoredBatchInfo[] calldata _batchesData) external { executeBatches(_batchesData); } + function executeBatchesSharedBridge( + uint256, + StoredBatchInfo[] calldata _batchesData, + PriorityOpsBatchInfo[] calldata + ) external { + executeBatches(_batchesData); + } + function revertBatches(uint256 _newLastBatch) public { require( getTotalBatchesCommitted > _newLastBatch, diff --git a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol index 396276306..6a2538722 100644 --- a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol +++ b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol @@ -8,6 +8,7 @@ import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {LibMap} from "./libraries/LibMap.sol"; import {IExecutor} from "./chain-interfaces/IExecutor.sol"; import {IStateTransitionManager} from "./IStateTransitionManager.sol"; +import {PriorityOpsBatchInfo} from "./libraries/PriorityTree.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -180,20 +181,39 @@ contract ValidatorTimelock is IExecutor, Ownable2Step { /// @dev Check that batches were committed at least X time ago and /// make a call to the hyperchain diamond contract with the same calldata. - function executeBatches(StoredBatchInfo[] calldata _newBatchesData) external onlyValidator(ERA_CHAIN_ID) { - _executeBatchesInner(ERA_CHAIN_ID, _newBatchesData); - } + // function executeBatches(StoredBatchInfo[] calldata _newBatchesData) external onlyValidator(ERA_CHAIN_ID) { + // _executeBatchesInner(ERA_CHAIN_ID, _newBatchesData, emptyPriorityOpsData()); + // } /// @dev Check that batches were committed at least X time ago and /// make a call to the hyperchain diamond contract with the same calldata. + // function executeBatchesSharedBridge( + // uint256 _chainId, + // StoredBatchInfo[] calldata _newBatchesData + // ) external onlyValidator(_chainId) { + // _executeBatchesInner(_chainId, _newBatchesData, emptyPriorityOpsData()); + // } + + function executeBatches( + StoredBatchInfo[] calldata _batchesData, + PriorityOpsBatchInfo[] calldata _priorityOpsData + ) external onlyValidator(ERA_CHAIN_ID) { + _executeBatchesInner(ERA_CHAIN_ID, _batchesData, _priorityOpsData); + } + function executeBatchesSharedBridge( uint256 _chainId, - StoredBatchInfo[] calldata _newBatchesData + StoredBatchInfo[] calldata _newBatchesData, + PriorityOpsBatchInfo[] calldata _priorityOpsData ) external onlyValidator(_chainId) { - _executeBatchesInner(_chainId, _newBatchesData); + _executeBatchesInner(ERA_CHAIN_ID, _newBatchesData, _priorityOpsData); } - function _executeBatchesInner(uint256 _chainId, StoredBatchInfo[] calldata _newBatchesData) internal { + function _executeBatchesInner( + uint256 _chainId, + StoredBatchInfo[] calldata _newBatchesData, + PriorityOpsBatchInfo[] calldata + ) internal { uint256 delay = executionDelay; // uint32 unchecked { // solhint-disable-next-line gas-length-in-loops diff --git a/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol b/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol index 145056590..79e4ebf60 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol @@ -8,11 +8,16 @@ import {Diamond} from "../libraries/Diamond.sol"; import {ZkSyncHyperchainBase} from "./facets/ZkSyncHyperchainBase.sol"; import {L2_TO_L1_LOG_SERIALIZE_SIZE, MAX_GAS_PER_TRANSACTION} from "../../common/Config.sol"; import {InitializeData, IDiamondInit} from "../chain-interfaces/IDiamondInit.sol"; +import {PriorityQueue} from "../libraries/PriorityQueue.sol"; +import {PriorityTree} from "../libraries/PriorityTree.sol"; /// @author Matter Labs /// @dev The contract is used only once to initialize the diamond proxy. /// @dev The deployment process takes care of this contract's initialization. contract DiamondInit is ZkSyncHyperchainBase, IDiamondInit { + using PriorityQueue for PriorityQueue.Queue; + using PriorityTree for PriorityTree.Tree; + /// @dev Initialize the implementation to prevent any possibility of a Parity hack. constructor() reentrancyGuardInitializer {} @@ -48,6 +53,8 @@ contract DiamondInit is ZkSyncHyperchainBase, IDiamondInit { s.priorityTxMaxGasLimit = _initializeData.priorityTxMaxGasLimit; s.feeParams = _initializeData.feeParams; s.blobVersionedHashRetriever = _initializeData.blobVersionedHashRetriever; + // TODO - how to do this during the upgrade? + s.priorityTree.setup(keccak256(''), s.priorityQueue.getTotalPriorityTxs()); // While this does not provide a protection in the production, it is needed for local testing // Length of the L2Log encoding should not be equal to the length of other L2Logs' tree nodes preimages diff --git a/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol b/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol index a06921fdb..b25d37173 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.24; import {IVerifier, VerifierParams} from "../chain-interfaces/IVerifier.sol"; import {PriorityQueue} from "../../state-transition/libraries/PriorityQueue.sol"; +import {PriorityTree} from "../../state-transition/libraries/PriorityTree.sol"; /// @notice Indicates whether an upgrade is initiated and if yes what type /// @param None Upgrade is NOT initiated @@ -151,4 +152,6 @@ struct ZkSyncHyperchainStorage { uint128 baseTokenGasPriceMultiplierDenominator; /// @dev The optional address of the contract that has to be used for transaction filtering/whitelisting address transactionFilterer; + /// @dev TODO + PriorityTree.Tree priorityTree; } diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index 169426871..8a9a6ae2a 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -13,6 +13,7 @@ import {UnsafeBytes} from "../../../common/libraries/UnsafeBytes.sol"; import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR, L2_PUBDATA_CHUNK_PUBLISHER_ADDR} from "../../../common/L2ContractAddresses.sol"; import {PubdataPricingMode} from "../ZkSyncHyperchainStorage.sol"; import {IStateTransitionManager} from "../../IStateTransitionManager.sol"; +import {PriorityTree, PriorityOpsBatchInfo} from "../../libraries/PriorityTree.sol"; // While formally the following import is not used, it is needed to inherit documentation from it import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; @@ -23,6 +24,7 @@ import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBas contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { using UncheckedMath for uint256; using PriorityQueue for PriorityQueue.Queue; + using PriorityTree for PriorityTree.Tree; /// @inheritdoc IZkSyncHyperchainBase string public constant override getName = "ExecutorFacet"; @@ -334,6 +336,14 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { } } + function _rollingHash(bytes32[] calldata _hashes) internal pure returns (bytes32) { + bytes32 hash = EMPTY_STRING_KECCAK; + for (uint256 i = 0; i < _hashes.length; i = i.uncheckedInc()) { + hash = keccak256(abi.encode(hash, _hashes[i])); + } + return hash; + } + /// @dev Executes one batch /// @dev 1. Processes all pending operations (Complete priority requests) /// @dev 2. Finalizes batch on Ethereum @@ -353,23 +363,70 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { s.l2LogsRootHashes[currentBatchNumber] = _storedBatch.l2LogsTreeRoot; } + // TODO: once we use this method, the normal priorityQueue becomes unusable since we didn't pop the elements + function _executeOneBatch( + StoredBatchInfo memory _storedBatch, + PriorityOpsBatchInfo calldata _priorityOpsData, + uint256 _executedBatchIdx + ) internal { + uint256 currentBatchNumber = _storedBatch.batchNumber; + require(currentBatchNumber == s.totalBatchesExecuted + _executedBatchIdx + 1, "k"); // Execute batches in order + require( + _hashStoredBatchInfo(_storedBatch) == s.storedBatchHashes[currentBatchNumber], + "exe10" // executing batch should be committed + ); + + bytes32 priorityOperationsRollingHash = _rollingHash(_priorityOpsData.itemHashes); + require(priorityOperationsRollingHash == _storedBatch.priorityOperationsHash, "x"); + // TODO: pop the elements from the queue? + s.priorityTree.processBatch(_priorityOpsData); + + // Save root hash of L2 -> L1 logs tree + s.l2LogsRootHashes[currentBatchNumber] = _storedBatch.l2LogsTreeRoot; + } + + /// @inheritdoc IExecutor + // function executeBatchesSharedBridge( + // uint256, + // StoredBatchInfo[] calldata _batchesData + // ) external nonReentrant onlyValidator { + // _executeBatches(_batchesData, emptyPriorityOpsData()); + // } + /// @inheritdoc IExecutor + // function executeBatches(StoredBatchInfo[] calldata _batchesData) external nonReentrant onlyValidator { + // _executeBatches(_batchesData, emptyPriorityOpsData()); + // } + function executeBatchesSharedBridge( uint256, - StoredBatchInfo[] calldata _batchesData + StoredBatchInfo[] calldata _batchesData, + PriorityOpsBatchInfo[] calldata _priorityOpsData ) external nonReentrant onlyValidator { - _executeBatches(_batchesData); + _executeBatches(_batchesData, _priorityOpsData); } - /// @inheritdoc IExecutor - function executeBatches(StoredBatchInfo[] calldata _batchesData) external nonReentrant onlyValidator { - _executeBatches(_batchesData); + function executeBatches( + StoredBatchInfo[] calldata _batchesData, + PriorityOpsBatchInfo[] calldata _priorityOpsData + ) external nonReentrant onlyValidator { + _executeBatches(_batchesData, _priorityOpsData); } - function _executeBatches(StoredBatchInfo[] calldata _batchesData) internal { + function _executeBatches(StoredBatchInfo[] calldata _batchesData, PriorityOpsBatchInfo[] calldata _priorityOpsData) internal { uint256 nBatches = _batchesData.length; + uint256 nPriorityOpsDatas = _priorityOpsData.length; + if (nPriorityOpsDatas != 0) { + require(nBatches == nPriorityOpsDatas, ""); + require(s.priorityTree.startIndex <= s.priorityQueue.getFirstUnprocessedPriorityTx(), ""); + } + for (uint256 i = 0; i < nBatches; i = i.uncheckedInc()) { - _executeOneBatch(_batchesData[i], i); + if (nPriorityOpsDatas != 0) { + _executeOneBatch(_batchesData[i], _priorityOpsData[i], i); + } else { + _executeOneBatch(_batchesData[i], i); + } emit BlockExecution(_batchesData[i].batchNumber, _batchesData[i].batchHash, _batchesData[i].commitment); } diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol index a65a47b55..ddd388cc6 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol @@ -10,6 +10,7 @@ import {IMailbox} from "../../chain-interfaces/IMailbox.sol"; import {ITransactionFilterer} from "../../chain-interfaces/ITransactionFilterer.sol"; import {Merkle} from "../../libraries/Merkle.sol"; import {PriorityQueue, PriorityOperation} from "../../libraries/PriorityQueue.sol"; +import {PriorityTree} from "../../libraries/PriorityTree.sol"; import {TransactionValidator} from "../../libraries/TransactionValidator.sol"; import {WritePriorityOpParams, L2CanonicalTransaction, L2Message, L2Log, TxStatus, BridgehubL2TransactionRequest} from "../../../common/Messaging.sol"; import {FeeParams, PubdataPricingMode} from "../ZkSyncHyperchainStorage.sol"; @@ -31,6 +32,7 @@ import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBas contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { using UncheckedMath for uint256; using PriorityQueue for PriorityQueue.Queue; + using PriorityTree for PriorityTree.Tree; /// @inheritdoc IZkSyncHyperchainBase string public constant override getName = "MailboxFacet"; @@ -342,6 +344,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { layer2Tip: uint192(0) // TODO: Restore after fee modeling will be stable. (SMA-1230) }) ); + s.priorityTree.push(canonicalTxHash); // Data that is needed for the operator to simulate priority queue offchain // solhint-disable-next-line func-named-parameters diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol index 149b97027..0ceca18a4 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.24; import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol"; +import {PriorityOpsBatchInfo} from "../libraries/PriorityTree.sol"; /// @dev Enum used by L2 System Contracts to differentiate logs. enum SystemLogKey { @@ -178,10 +179,18 @@ interface IExecutor is IZkSyncHyperchainBase { /// - Processing all pending operations (commpleting priority requests). /// - Finalizing this batch (i.e. allowing to withdraw funds from the system) /// @param _batchesData Data of the batches to be executed. - function executeBatches(StoredBatchInfo[] calldata _batchesData) external; + // function executeBatches(StoredBatchInfo[] calldata _batchesData) external; + + function executeBatches(StoredBatchInfo[] calldata _batchesData, PriorityOpsBatchInfo[] calldata _priorityOpsData) external; /// @notice same as `executeBatches` but with the chainId so ValidatorTimelock can sort the inputs. - function executeBatchesSharedBridge(uint256 _chainId, StoredBatchInfo[] calldata _batchesData) external; + // function executeBatchesSharedBridge(uint256 _chainId, StoredBatchInfo[] calldata _batchesData) external; + + function executeBatchesSharedBridge( + uint256 _chainId, + StoredBatchInfo[] calldata _batchesData, + PriorityOpsBatchInfo[] calldata _priorityOpsData + ) external; /// @notice Reverts unexecuted batches /// @param _newLastBatch batch number after which batches should be reverted diff --git a/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol b/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol new file mode 100644 index 000000000..811239050 --- /dev/null +++ b/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol @@ -0,0 +1,62 @@ + +import {DynamicIncrementalMerkle} from "../../common/libraries/openzeppelin/IncrementalMerkle.sol"; +import {Merkle} from "./Merkle.sol"; + +struct PriorityOpsBatchInfo { + bytes32[] leftPath; + bytes32[] rightPath; + bytes32[] itemHashes; +} + +library PriorityTree { + using PriorityTree for Tree; + using DynamicIncrementalMerkle for DynamicIncrementalMerkle.Bytes32PushTree; + + struct Tree { + uint256 startIndex; + uint256 unprocessedIndex; // relative to `startIndex` + DynamicIncrementalMerkle.Bytes32PushTree tree; + } + + /// @notice Returns zero if and only if no operations were processed from the queue + /// @return Index of the oldest priority operation that wasn't processed yet + function getFirstUnprocessedPriorityTx(Tree storage _tree) internal view returns (uint256) { + return _tree.startIndex + _tree.unprocessedIndex; + } + + /// @return The total number of priority operations that were added to the priority queue, including all processed ones + function getTotalPriorityTxs(Tree storage _tree) internal view returns (uint256) { + return _tree.startIndex + _tree.tree._nextLeafIndex; + } + + /// @return The total number of unprocessed priority operations in a priority queue + function getSize(Tree storage _tree) internal view returns (uint256) { + return uint256(_tree.tree._nextLeafIndex - _tree.unprocessedIndex); + } + + /// @notice Add the priority operation to the end of the priority queue + function push(Tree storage _tree, bytes32 _hash) internal { + _tree.tree.push(_hash); + } + + function setup(Tree storage _tree, bytes32 _zero, uint256 _startIndex) internal { + _tree.tree.setup(_zero); + _tree.startIndex = _startIndex; + } + + function processBatch( + Tree storage _tree, + PriorityOpsBatchInfo calldata _priorityOpsData + ) internal { + bytes32 expectedRoot = Merkle.calculateRoot( + _priorityOpsData.leftPath, + _priorityOpsData.rightPath, + _tree.unprocessedIndex, + _priorityOpsData.itemHashes + ); + require(expectedRoot == _tree.tree.root(), ""); + _tree.unprocessedIndex += _priorityOpsData.itemHashes.length; + } +} + +