-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathStarknetSharpMmrGrowingModule.sol
162 lines (125 loc) · 8.87 KB
/
StarknetSharpMmrGrowingModule.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.27;
import {Uint256Splitter} from "src/libraries/internal/Uint256Splitter.sol";
import {IFactsRegistry} from "src/interfaces/external/IFactsRegistry.sol";
import {IStarknetSharpMmrGrowingModule} from "src/interfaces/modules/growing/IStarknetSharpMmrGrowingModule.sol";
import {ISatellite} from "src/interfaces/ISatellite.sol";
import {LibSatellite} from "src/libraries/LibSatellite.sol";
import {IMmrCoreModule, RootForHashingFunction, GrownBy} from "src/interfaces/modules/IMmrCoreModule.sol";
import {AccessController} from "src/libraries/AccessController.sol";
contract StarknetSharpMmrGrowingModule is IStarknetSharpMmrGrowingModule, AccessController {
// Cairo program hash calculated with Poseidon (i.e., the off-chain block headers accumulator program)
bytes32 public constant PROGRAM_HASH = bytes32(uint256(0x4ebb807bacec9cf6202e6482d8f7f74a531b067571dba2ccf118b0328df8295));
bytes32 public constant POSEIDON_HASHING_FUNCTION = keccak256("poseidon");
// Default roots for new aggregators:
// poseidon_hash(1, "brave new world")
bytes32 public constant POSEIDON_MMR_INITIAL_ROOT = 0x06759138078831011e3bc0b4a135af21c008dda64586363531697207fb5a2bae;
// ========================= Satellite Module Storage ========================= //
bytes32 constant MODULE_STORAGE_POSITION = keccak256("diamond.standard.satellite.module.storage.starknet-sharp-mmr-growing");
function moduleStorage() internal pure returns (StarknetSharpMmrGrowingModuleStorage storage s) {
bytes32 position = MODULE_STORAGE_POSITION;
assembly {
s.slot := position
}
}
function initStarknetSharpMmrGrowingModule(IFactsRegistry factsRegistry, uint256 chainId) external onlyOwner {
StarknetSharpMmrGrowingModuleStorage storage ms = moduleStorage();
ms.factsRegistry = factsRegistry;
ms.aggregatedChainId = chainId;
}
function createStarknetSharpMmr(uint256 newMmrId, uint256 originalMmrId, uint256 mmrSize) external {
bytes32[] memory hashingFunctions = new bytes32[](1);
hashingFunctions[0] = POSEIDON_HASHING_FUNCTION;
StarknetSharpMmrGrowingModuleStorage storage ms = moduleStorage();
ISatellite(address(this)).createMmrFromDomestic(newMmrId, originalMmrId, ms.aggregatedChainId, mmrSize, hashingFunctions);
}
function aggregateStarknetSharpJobs(uint256 mmrId, StarknetJobOutput[] calldata outputs) external {
// Ensuring at least one job output is provided
if (outputs.length < 1) {
revert NotEnoughJobs();
}
StarknetJobOutput calldata firstOutput = outputs[0];
uint256 fromBlock = firstOutput.fromBlockNumberHigh;
// Ensure the first job is continuable
_validateOutput(mmrId, fromBlock, firstOutput);
uint256 limit = outputs.length - 1;
// Iterate over the jobs outputs (aside from the last one)
// and ensure jobs are correctly linked and valid
for (uint256 i = 0; i < limit; ++i) {
StarknetJobOutput calldata curOutput = outputs[i];
StarknetJobOutput calldata nextOutput = outputs[i + 1];
_ensureValidFact(curOutput);
_ensureConsecutiveJobs(curOutput, nextOutput);
}
StarknetJobOutput calldata lastOutput = outputs[limit];
_ensureValidFact(lastOutput);
uint256 mmrNewSize = lastOutput.mmrNewSize;
ISatellite.SatelliteStorage storage s = LibSatellite.satelliteStorage();
StarknetSharpMmrGrowingModuleStorage storage ms = moduleStorage();
s.mmrs[ms.aggregatedChainId][mmrId][POSEIDON_HASHING_FUNCTION].mmrSizeToRoot[mmrNewSize] = lastOutput.mmrNewRootPoseidon;
s.mmrs[ms.aggregatedChainId][mmrId][POSEIDON_HASHING_FUNCTION].latestSize = mmrNewSize;
s.mmrs[ms.aggregatedChainId][mmrId][POSEIDON_HASHING_FUNCTION].isSiblingSynced = false;
uint256 toBlock = lastOutput.toBlockNumberLow;
ISatellite(address(this))._receiveParentHash(ms.aggregatedChainId, POSEIDON_HASHING_FUNCTION, toBlock, lastOutput.blockNMinusRPlusOneParentHash);
RootForHashingFunction[] memory rootsForHashingFunctions = new RootForHashingFunction[](1);
rootsForHashingFunctions[0].root = lastOutput.mmrNewRootPoseidon;
rootsForHashingFunctions[0].hashingFunction = POSEIDON_HASHING_FUNCTION;
emit IMmrCoreModule.GrownMmr(fromBlock, toBlock, rootsForHashingFunctions, mmrNewSize, mmrId, ms.aggregatedChainId, GrownBy.STARKNET_SHARP_GROWER);
}
/// @notice Ensures the job output is cryptographically sound to continue from
/// @param mmrId The MMR ID to validate the output for
/// @param fromBlockNumber The parent hash of the block to start from
/// @param firstOutput The job output to check
function _validateOutput(uint256 mmrId, uint256 fromBlockNumber, StarknetJobOutput memory firstOutput) internal view {
ISatellite.SatelliteStorage storage s = LibSatellite.satelliteStorage();
StarknetSharpMmrGrowingModuleStorage storage ms = moduleStorage();
// Retrieve from cache the parent hash of the block to start from
bytes32 fromBlockPlusOneParentHash = s.receivedParentHashes[ms.aggregatedChainId][POSEIDON_HASHING_FUNCTION][fromBlockNumber + 1];
uint256 actualMmrSizePoseidon = s.mmrs[ms.aggregatedChainId][mmrId][POSEIDON_HASHING_FUNCTION].latestSize;
// Check that the job's previous MMR size is the same as the one stored in the contract state
if (firstOutput.mmrPreviousSize != actualMmrSizePoseidon) revert AggregationError("MMR size mismatch");
// Check that the job's previous Poseidon MMR root is the same as the one stored in the contract state
if (firstOutput.mmrPreviousRootPoseidon != s.mmrs[ms.aggregatedChainId][mmrId][POSEIDON_HASHING_FUNCTION].mmrSizeToRoot[firstOutput.mmrPreviousSize])
revert AggregationError("Poseidon root mismatch");
// If not present in the cache, hash is not authenticated and we cannot continue from it
if (fromBlockPlusOneParentHash == bytes32(0)) revert UnknownParentHash();
// we check that the job's `blockN + 1 parent hash` is matching with a previously stored parent hash
if (firstOutput.blockNPlusOneParentHash != fromBlockPlusOneParentHash) revert AggregationError("Parent hash mismatch: ensureContinuable");
}
/// @notice Ensures the fact is regisfirstOutputon SHARP Facts Registry
/// @param output SHARP job output (packed for Solidity)
function _ensureValidFact(StarknetJobOutput memory output) internal view {
// We assemble the outputs in a uint256 array
uint256[] memory outputs = new uint256[](8);
outputs[0] = uint256(output.fromBlockNumberHigh);
outputs[1] = uint256(output.toBlockNumberLow);
outputs[2] = uint256(output.blockNPlusOneParentHash);
outputs[3] = uint256(output.blockNMinusRPlusOneParentHash);
outputs[4] = uint256(output.mmrPreviousRootPoseidon);
outputs[5] = uint256(output.mmrPreviousSize);
outputs[6] = uint256(output.mmrNewRootPoseidon);
outputs[7] = uint256(output.mmrNewSize);
// We hash the outputs
bytes32 outputHash = keccak256(abi.encodePacked(outputs));
// We compute the deterministic fact bytes32 value
bytes32 fact = keccak256(abi.encode(PROGRAM_HASH, outputHash));
StarknetSharpMmrGrowingModuleStorage storage ms = moduleStorage();
// We ensure this fact has been registered on SHARP Facts Registry
if (!ms.factsRegistry.isValid(fact)) revert InvalidFact();
}
/// @notice Ensures the job outputs are correctly linked
/// @param output The job output to check
/// @param nextOutput The next job output to check
function _ensureConsecutiveJobs(StarknetJobOutput memory output, StarknetJobOutput memory nextOutput) internal pure {
// We cannot aggregate further past the genesis block
if (output.toBlockNumberLow == 0) revert GenesisBlockReached();
// We check that the next job's `from block` is the same as the previous job's `to block + 1`
if (output.toBlockNumberLow - 1 != nextOutput.fromBlockNumberHigh) revert AggregationBlockMismatch("ensureConsecutiveJobs");
// We check that the previous job's new Poseidon MMR root matches the next job's previous Poseidon MMR root
if (output.mmrNewRootPoseidon != nextOutput.mmrPreviousRootPoseidon) revert AggregationError("Poseidon root mismatch");
// We check that the previous job's new MMR size matches the next job's previous MMR size
if (output.mmrNewSize != nextOutput.mmrPreviousSize) revert AggregationError("MMR size mismatch");
// We check that the previous job's lowest block hash matches the next job's highest block hash
if (output.blockNMinusRPlusOneParentHash != nextOutput.blockNPlusOneParentHash) revert AggregationError("Parent hash mismatch: ensureConsecutiveJobs");
}
}