|
| 1 | +// SPDX-License-Identifier: MIT OR Apache-2.0 |
| 2 | +pragma solidity ^0.8.23; |
| 3 | + |
| 4 | +import {IValidatorRewarder} from "../interfaces/IValidatorRewarder.sol"; |
| 5 | +import {Consensus, CompressedActivityRollup} from "../structs/Activity.sol"; |
| 6 | +import {BottomUpBatch} from "../structs/BottomUpBatch.sol"; |
| 7 | +import {IpcEnvelope} from "../structs/CrossNet.sol"; |
| 8 | +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; |
| 9 | +import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; |
| 10 | +import {SubnetID} from "../structs/Subnet.sol"; |
| 11 | +import {SubnetIDHelper} from "../lib/SubnetIDHelper.sol"; |
| 12 | +import {InvalidInclusionProof, BatchMsgAlreadyExecuted, MissingBatchCommitment, DuplicatedCheckpointHeight} from "../errors/IPCErrors.sol"; |
| 13 | +import {BottomUpBatch} from "../structs/BottomUpBatch.sol"; |
| 14 | + |
| 15 | +/// Library to handle bottom up batch 2-phase execution. |
| 16 | +library LibBottomUpBatch { |
| 17 | + bytes32 private constant NAMESPACE = keccak256("bottomupbatch"); |
| 18 | + |
| 19 | + using EnumerableSet for EnumerableSet.Bytes32Set; |
| 20 | + |
| 21 | + /// @notice Represents a pending bottom-up batch commitment awaiting full execution at a specific checkpoint height. |
| 22 | + struct PendingBatch { |
| 23 | + /// @notice The pending batch commitment. |
| 24 | + BottomUpBatch.Commitment commitment; |
| 25 | + /// @notice Set of message leaf hashes that have already been executed for this batch. |
| 26 | + EnumerableSet.Bytes32Set executed; |
| 27 | + } |
| 28 | + |
| 29 | + /// @notice Storage structure used by the SubnetActor to manage bottom-up message batches and their execution status. |
| 30 | + struct BottomUpBatchStorage { |
| 31 | + /// @notice Set of checkpoint heights with batches that are still pending execution. |
| 32 | + EnumerableSet.Bytes32Set pendingHeights; |
| 33 | + /// @notice Mapping of checkpoint height to its pending batch data. |
| 34 | + mapping(uint256 => PendingBatch) pending; |
| 35 | + } |
| 36 | + |
| 37 | + function ensureValidProof( |
| 38 | + BottomUpBatch.MerkleHash[] memory proof, |
| 39 | + BottomUpBatch.MerkleHash root, |
| 40 | + BottomUpBatch.MerkleHash leaf |
| 41 | + ) internal pure { |
| 42 | + bytes32[] memory proofBytes = new bytes32[](proof.length); |
| 43 | + for (uint256 i = 0; i < proof.length; i++) { |
| 44 | + proofBytes[i] = BottomUpBatch.MerkleHash.unwrap(proof[i]); |
| 45 | + } |
| 46 | + bool valid = MerkleProof.verify({ |
| 47 | + proof: proofBytes, |
| 48 | + root: BottomUpBatch.MerkleHash.unwrap(root), |
| 49 | + leaf: BottomUpBatch.MerkleHash.unwrap(leaf) |
| 50 | + }); |
| 51 | + if (!valid) { |
| 52 | + revert InvalidInclusionProof(); |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + function recordBottomUpBatchCommitment( |
| 57 | + uint64 checkpointHeight, |
| 58 | + BottomUpBatch.Commitment calldata commitment |
| 59 | + ) internal { |
| 60 | + BottomUpBatchStorage storage s = bottomUpBatchStorage(); |
| 61 | + |
| 62 | + bool added = s.pendingHeights.add(bytes32(uint256(checkpointHeight))); |
| 63 | + if (!added) { |
| 64 | + revert DuplicatedCheckpointHeight(checkpointHeight); |
| 65 | + } |
| 66 | + |
| 67 | + PendingBatch storage pending = s.pending[checkpointHeight]; |
| 68 | + pending.commitment = commitment; |
| 69 | + } |
| 70 | + |
| 71 | + function processBottomUpBatchMsg( |
| 72 | + uint256 checkpointHeight, |
| 73 | + IpcEnvelope calldata ipcMsg, |
| 74 | + BottomUpBatch.MerkleHash[] calldata proof |
| 75 | + ) internal { |
| 76 | + BottomUpBatchStorage storage s = bottomUpBatchStorage(); |
| 77 | + |
| 78 | + // Find the pending batch. |
| 79 | + PendingBatch storage pending = s.pending[checkpointHeight]; |
| 80 | + BottomUpBatch.MerkleHash root = pending.commitment.msgsRoot; |
| 81 | + if (BottomUpBatch.MerkleHash.unwrap(root) == bytes32(0)) { |
| 82 | + revert MissingBatchCommitment(); |
| 83 | + } |
| 84 | + |
| 85 | + // Check the validity of the proof. |
| 86 | + BottomUpBatch.MerkleHash leaf = makeLeaf(ipcMsg); |
| 87 | + ensureValidProof( |
| 88 | + proof, |
| 89 | + root, |
| 90 | + leaf |
| 91 | + ); |
| 92 | + |
| 93 | + bool added = pending.executed.add(BottomUpBatch.MerkleHash.unwrap(leaf)); |
| 94 | + if (!added) { |
| 95 | + revert BatchMsgAlreadyExecuted(); |
| 96 | + } |
| 97 | + |
| 98 | + // Prune state for this height if all msgs were executed. |
| 99 | + if (pending.executed.length() == pending.commitment.totalNumMsgs) { |
| 100 | + s.pendingHeights.remove(bytes32(uint256(checkpointHeight))); |
| 101 | + |
| 102 | + // Clear nested set before deleting the struct. |
| 103 | + PendingBatch storage pending = s.pending[checkpointHeight]; |
| 104 | + uint256 len = pending.executed.length(); |
| 105 | + for (uint256 i = 0; i < len; i++) { |
| 106 | + bytes32 leaf = pending.executed.at(0); |
| 107 | + pending.executed.remove(leaf); |
| 108 | + } |
| 109 | + delete s.pending[checkpointHeight]; |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + /// Return type for the list pending commitments view method. |
| 114 | + struct ListPendingCommitmentsEntry { |
| 115 | + uint64 height; |
| 116 | + BottomUpBatch.Commitment commitment; |
| 117 | + bytes32[] executed; |
| 118 | + } |
| 119 | + |
| 120 | + /// A view accessor to query the pending commitments for a given subnet. |
| 121 | + function listPendingCommitments() internal view returns (ListPendingCommitmentsEntry[] memory result) { |
| 122 | + BottomUpBatchStorage storage s = bottomUpBatchStorage(); |
| 123 | + |
| 124 | + bytes32[] memory heights = s.pendingHeights.values(); |
| 125 | + uint256 size = heights.length; |
| 126 | + |
| 127 | + result = new ListPendingCommitmentsEntry[](size); |
| 128 | + |
| 129 | + for (uint256 i = 0; i < size; i++) { |
| 130 | + uint64 height = uint64(uint256(heights[i])); |
| 131 | + PendingBatch storage pending = s.pending[height]; |
| 132 | + result[i] = ListPendingCommitmentsEntry({ |
| 133 | + height: height, |
| 134 | + commitment: pending.commitment, |
| 135 | + executed: pending.executed.values() |
| 136 | + }); |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + |
| 141 | + function makeLeaf(IpcEnvelope memory _msg) public pure returns (BottomUpBatch.MerkleHash) { |
| 142 | + // solhint-disable-next-line func-named-parameters |
| 143 | + bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode( |
| 144 | + _msg.kind, |
| 145 | + _msg.localNonce, |
| 146 | + _msg.originalNonce, |
| 147 | + _msg.value, |
| 148 | + _msg.to.subnetId.root, |
| 149 | + _msg.to.subnetId.route, |
| 150 | + _msg.to.rawAddress.addrType, |
| 151 | + _msg.to.rawAddress.payload, |
| 152 | + _msg.from.subnetId.root, |
| 153 | + _msg.from.subnetId.route, |
| 154 | + _msg.from.rawAddress.addrType, |
| 155 | + _msg.from.rawAddress.payload, |
| 156 | + _msg.message |
| 157 | + )))); |
| 158 | + return BottomUpBatch.MerkleHash.wrap(leaf); |
| 159 | + } |
| 160 | + |
| 161 | + function bottomUpBatchStorage() internal pure returns (BottomUpBatchStorage storage ds) { |
| 162 | + bytes32 position = NAMESPACE; |
| 163 | + assembly { |
| 164 | + ds.slot := position |
| 165 | + } |
| 166 | + return ds; |
| 167 | + } |
| 168 | +} |
0 commit comments