-
Notifications
You must be signed in to change notification settings - Fork 23
/
BlobstreamX.sol
217 lines (169 loc) · 8.75 KB
/
BlobstreamX.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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import "@blobstream/DataRootTuple.sol";
import "@blobstream/lib/tree/binary/BinaryMerkleTree.sol";
import {IBlobstreamX} from "./interfaces/IBlobstreamX.sol";
import {IDAOracle} from "@blobstream/IDAOracle.sol";
import {ITendermintX} from "@tendermintx/interfaces/ITendermintX.sol";
import {TimelockedUpgradeable} from "@succinctx/upgrades/TimelockedUpgradeable.sol";
import {ISuccinctGateway} from "@succinctx/interfaces/ISuccinctGateway.sol";
contract BlobstreamX is ITendermintX, IBlobstreamX, IDAOracle, TimelockedUpgradeable {
/// @notice The address of the gateway contract.
address public gateway;
/// @notice The block is the first one in the next data commitment.
uint64 public latestBlock;
/// @notice The maximum number of blocks that can be skipped in a single request.
/// Source: https://github.com/celestiaorg/celestia-core/blob/main/pkg/consts/consts.go#L43-L44
uint64 public constant DATA_COMMITMENT_MAX = 1000;
/// @notice Nonce for proof events. Must be incremented sequentially.
uint256 public state_proofNonce;
/// @notice Maps block heights to their header hashes.
mapping(uint64 => bytes32) public blockHeightToHeaderHash;
/// @notice Mapping of data commitment nonces to data commitments.
mapping(uint256 => bytes32) public state_dataCommitments;
/// @notice Header range function id.
bytes32 public headerRangeFunctionId;
/// @notice Next header function id.
bytes32 public nextHeaderFunctionId;
struct InitParameters {
address guardian;
address gateway;
uint64 height;
bytes32 header;
bytes32 nextHeaderFunctionId;
bytes32 headerRangeFunctionId;
}
/// @dev Initializes the contract.
/// @param _params The initialization parameters.
function initialize(InitParameters calldata _params) external initializer {
__TimelockedUpgradeable_init(_params.guardian, _params.guardian);
gateway = _params.gateway;
blockHeightToHeaderHash[_params.height] = _params.header;
latestBlock = _params.height;
nextHeaderFunctionId = _params.nextHeaderFunctionId;
headerRangeFunctionId = _params.headerRangeFunctionId;
state_proofNonce = 1;
}
/// @notice Update the address of the gateway contract.
function updateGateway(address _gateway) external onlyGuardian {
gateway = _gateway;
}
/// @notice Update the function ID for header range.
function updateHeaderRangeId(bytes32 _functionId) external onlyGuardian {
headerRangeFunctionId = _functionId;
}
/// @notice Update the function ID for next header.
function updateNextHeaderId(bytes32 _functionId) external onlyGuardian {
nextHeaderFunctionId = _functionId;
}
/// @notice Prove the validity of the header at the target block and a data commitment for the block range [latestBlock, _targetBlock).
/// @param _targetBlock The end block of the header range proof.
/// @dev requestHeaderRange is used to skip from the latest block to the target block.
function requestHeaderRange(uint64 _targetBlock) external payable {
bytes32 latestHeader = blockHeightToHeaderHash[latestBlock];
if (latestHeader == bytes32(0)) {
revert LatestHeaderNotFound();
}
// A request can be at most DATA_COMMITMENT_MAX blocks ahead of the latest block.
if (_targetBlock <= latestBlock || _targetBlock - latestBlock > DATA_COMMITMENT_MAX) {
revert TargetBlockNotInRange();
}
ISuccinctGateway(gateway).requestCall{value: msg.value}(
headerRangeFunctionId,
abi.encodePacked(latestBlock, latestHeader, _targetBlock),
address(this),
abi.encodeWithSelector(this.commitHeaderRange.selector, latestBlock, _targetBlock),
500000
);
emit HeaderRangeRequested(latestBlock, latestHeader, _targetBlock);
}
/// @notice Commits the new header at targetBlock and the data commitment for the block range [trustedBlock, targetBlock).
/// @param _trustedBlock The latest block when the request was made.
/// @param _targetBlock The end block of the header range request.
function commitHeaderRange(uint64 _trustedBlock, uint64 _targetBlock) external {
bytes32 trustedHeader = blockHeightToHeaderHash[_trustedBlock];
if (trustedHeader == bytes32(0)) {
revert TrustedHeaderNotFound();
}
// Encode the circuit input.
bytes memory input = abi.encodePacked(_trustedBlock, trustedHeader, _targetBlock);
// Call gateway to get the proof result.
bytes memory requestResult = ISuccinctGateway(gateway).verifiedCall(headerRangeFunctionId, input);
// Read the target header and data commitment from request result.
// Note: Don't need implementation of decodePacked because abi.encode(bytes32, bytes32)
// is the same as abi.encodePacked(bytes32, bytes32).
(bytes32 targetHeader, bytes32 dataCommitment) = abi.decode(requestResult, (bytes32, bytes32));
if (_targetBlock <= latestBlock || _targetBlock - latestBlock > DATA_COMMITMENT_MAX) {
revert TargetBlockNotInRange();
}
// Store the new header and data commitment, and update the latest block and event nonce.
blockHeightToHeaderHash[_targetBlock] = targetHeader;
state_dataCommitments[state_proofNonce] = dataCommitment;
emit HeadUpdate(_targetBlock, targetHeader);
emit DataCommitmentStored(state_proofNonce, _trustedBlock, _targetBlock, dataCommitment);
state_proofNonce++;
latestBlock = _targetBlock;
}
/// @notice Prove the validity of the next header and a data commitment for the block range [latestBlock, latestBlock + 1).
/// @dev Rarely used, only if the validator set changes by more than 2/3 in a single block.
function requestNextHeader() external payable {
bytes32 latestHeader = blockHeightToHeaderHash[latestBlock];
if (latestHeader == bytes32(0)) {
revert LatestHeaderNotFound();
}
ISuccinctGateway(gateway).requestCall{value: msg.value}(
nextHeaderFunctionId,
abi.encodePacked(latestBlock, latestHeader),
address(this),
abi.encodeWithSelector(this.commitNextHeader.selector, latestBlock),
500000
);
emit NextHeaderRequested(latestBlock, latestHeader);
}
/// @notice Stores the new header for _trustedBlock + 1 and the data commitment for the block range [_trustedBlock, _trustedBlock + 1).
/// @param _trustedBlock The latest block when the request was made.
function commitNextHeader(uint64 _trustedBlock) external {
bytes32 trustedHeader = blockHeightToHeaderHash[_trustedBlock];
if (trustedHeader == bytes32(0)) {
revert TrustedHeaderNotFound();
}
bytes memory input = abi.encodePacked(_trustedBlock, trustedHeader);
// Call gateway to get the proof result.
bytes memory requestResult = ISuccinctGateway(gateway).verifiedCall(nextHeaderFunctionId, input);
// Read the new header and data commitment from request result.
(bytes32 nextHeader, bytes32 dataCommitment) = abi.decode(requestResult, (bytes32, bytes32));
uint64 nextBlock = _trustedBlock + 1;
if (nextBlock <= latestBlock) {
revert TargetBlockNotInRange();
}
// Store the next header and data commitment for [_trustedBlock, nextBlock), and update the
// latest block and event nonce.
blockHeightToHeaderHash[nextBlock] = nextHeader;
state_dataCommitments[state_proofNonce] = dataCommitment;
emit HeadUpdate(nextBlock, nextHeader);
emit DataCommitmentStored(state_proofNonce, _trustedBlock, nextBlock, dataCommitment);
state_proofNonce++;
latestBlock = nextBlock;
}
/// @notice Get the header hash for a block height.
function getHeaderHash(uint64 _height) external view returns (bytes32) {
return blockHeightToHeaderHash[_height];
}
/// @dev See "./IDAOracle.sol"
function verifyAttestation(uint256 _proofNonce, DataRootTuple memory _tuple, BinaryMerkleProof memory _proof)
external
view
returns (bool)
{
// Note: state_proofNonce slightly differs from Blobstream.sol because it is incremented
// after each commit.
if (_proofNonce >= state_proofNonce) {
return false;
}
// Load the tuple root at the given index from storage.
bytes32 root = state_dataCommitments[_proofNonce];
// Verify the proof.
bool isProofValid = BinaryMerkleTree.verify(root, _proof, abi.encode(_tuple));
return isProofValid;
}
}