-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathRelay.sol
356 lines (294 loc) · 11.8 KB
/
Relay.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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.7.0;
import "@openzeppelin/contracts/proxy/Initializable.sol";
import "./Blake2b.sol";
import "./common/Ownable.sol";
import "./common/Pausable.sol";
import "./common/ECDSA.sol";
import "./common/Hash.sol";
import "./common/SafeMath.sol";
import "./common/Input.sol";
import "./MMR.sol";
import "./common/Scale.sol";
import "./SimpleMerkleProof.sol";
pragma experimental ABIEncoderV2;
contract Relay is Ownable, Pausable, Initializable {
event SetRootEvent(address relayer, bytes32 root, uint256 index);
event SetAuthoritiesEvent(uint32 nonce, address[] authorities, bytes32 beneficiary);
event ResetRootEvent(address owner, bytes32 root, uint256 index);
event ResetAuthoritiesEvent(uint32 nonce, address[] authorities);
struct Relayers {
// Each time the relay set is updated, the nonce is incremented
// After the first "updateRelayer" call, the nonce value is equal to 1,
// which is different from the field "Term" at the node.
uint32 nonce;
// mapping(address => bool) member;
address[] member;
uint8 threshold;
}
Relayers relayers;
// 'Crab', 'Darwinia', 'Pangolin'
bytes private networkPrefix;
// index => mmr root
// In the Darwinia Network, the mmr root of block 1000
// needs to be queried in Log-Other of block 1001.
mapping(uint32 => bytes32) public mmrRootPool;
// _MMRIndex - mmr index or block number corresponding to mmr root
// _genesisMMRRoot - mmr root
// _relayers - Keep the same as the "ethereumRelayAuthorities" module in darwinia network
// _nonce - To prevent replay attacks
// _threshold - The threshold for a given level can be set to any number from 0-100. This threshold is the amount of signature weight required to authorize an operation at that level.
// _prefix - The known values are: "Pangolin", "Crab", "Darwinia"
function initialize(
uint32 _MMRIndex,
bytes32 _genesisMMRRoot,
address[] memory _relayers,
uint32 _nonce,
uint8 _threshold,
bytes memory _prefix
) public initializer {
ownableConstructor();
pausableConstructor();
_appendRoot(_MMRIndex, _genesisMMRRoot);
_resetRelayer(_nonce, _relayers);
_setNetworkPrefix(_prefix);
_setRelayThreshold(_threshold);
}
/// ==== Getters ====
function getRelayerCount() public view returns (uint256) {
return relayers.member.length;
}
function getRelayerNonce() public view returns (uint32) {
return relayers.nonce;
}
function getRelayer() public view returns (address[] memory) {
return relayers.member;
}
function getNetworkPrefix() public view returns (bytes memory) {
return networkPrefix;
}
function getRelayerThreshold() public view returns (uint8) {
return relayers.threshold;
}
function getMMRRoot(uint32 index) public view returns (bytes32) {
return mmrRootPool[index];
}
function getLockTokenReceipt(bytes32 root, bytes memory eventsProofStr, bytes memory key)
public
view
whenNotPaused
returns (bytes memory)
{
Input.Data memory data = Input.from(eventsProofStr);
bytes[] memory proofs = Scale.decodeReceiptProof(data);
bytes memory result = SimpleMerkleProof.getEvents(root, key, proofs);
return result;
}
function isRelayer(address addr) public view returns (bool) {
for (uint256 i = 0; i < relayers.member.length; i++) {
if (addr == relayers.member[i]) {
return true;
}
}
return false;
}
function checkNetworkPrefix(bytes memory prefix) view public returns (bool) {
return assertBytesEq(getNetworkPrefix(), prefix);
}
function checkRelayerNonce(uint32 nonce) view public returns (bool) {
return nonce == getRelayerNonce();
}
/// ==== Setters ====
// When the darwinia network authorities set is updated, bridger or other users need to submit the new authorities set to the reporter contract by calling this method.
// message - prefix + nonce + [...relayers]
// struct{vec<u8>, u32, vec<EthereumAddress>}
// signatures - signed by personal_sign
// beneficiary - Keeping the authorities set up-to-date is advocated between the relay contract contract and the darwinia network, and the darwinia network will give partial rewards to the benifit account. benifit is the public key of a darwinia network account
function updateRelayer(
bytes memory message,
bytes[] memory signatures,
bytes32 beneficiary
) public whenNotPaused {
// verify hash, signatures (The number of signers must be greater than _threshold)
require(
_checkSignature(message, signatures),
"Relay: Bad relayer signature"
);
// decode message, check nonce and relayer
Input.Data memory data = Input.from(message);
(bytes memory prefix, uint32 nonce, address[] memory authorities) = Scale.decodeAuthorities(
data
);
require(checkNetworkPrefix(prefix), "Relay: Bad network prefix");
require(checkRelayerNonce(nonce), "Relay: Bad relayer set nonce");
// update nonce,relayer
_updateRelayer(nonce, authorities, beneficiary);
}
// Add a mmr root to the mmr root pool
// message - bytes4 prefix + uint32 mmr-index + bytes32 mmr-root
// struct{vec<u8>, u32, H256}
// encode by scale codec
// signatures - The signature for message
// https://github.com/darwinia-network/darwinia-common/pull/381
function appendRoot(
bytes memory message,
bytes[] memory signatures
) public whenNotPaused {
// verify hash, signatures
require(
_checkSignature(message, signatures),
"Relay: Bad relayer signature"
);
// decode message, check nonce and relayer
Input.Data memory data = Input.from(message);
(bytes memory prefix, uint32 index, bytes32 root) = Scale.decodeMMRRoot(data);
require(checkNetworkPrefix(prefix), "Relay: Bad network prefix");
// append index, root
_appendRoot(index, root);
}
function verifyRootAndDecodeReceipt(
bytes32 root,
uint32 MMRIndex,
uint32 blockNumber,
bytes memory blockHeader,
bytes32[] memory peaks,
bytes32[] memory siblings,
bytes memory eventsProofStr,
bytes memory key
) public view whenNotPaused returns (bytes memory){
// verify block proof
require(
verifyBlockProof(root, MMRIndex, blockNumber, blockHeader, peaks, siblings),
"Relay: Block header proof varification failed"
);
// get state root
bytes32 stateRoot = Scale.decodeStateRootFromBlockHeader(blockHeader);
return getLockTokenReceipt(stateRoot, eventsProofStr, key);
}
function verifyBlockProof(
bytes32 root,
uint32 MMRIndex,
uint32 blockNumber,
bytes memory blockHeader,
bytes32[] memory peaks,
bytes32[] memory siblings
) public view whenNotPaused returns (bool) {
require(
getMMRRoot(MMRIndex) != bytes32(0),
"Relay: Not registered under this index"
);
require(
getMMRRoot(MMRIndex) == root,
"Relay: Root is different from the root pool"
);
return MMR.inclusionProof(root, MMRIndex + 1, blockNumber, blockHeader, peaks, siblings);
}
/// ==== onlyOwner ====
function resetRoot(uint32 index, bytes32 root) public onlyOwner {
_setRoot(index, root);
emit ResetRootEvent(_msgSender(), root, index);
}
function unpause() public onlyOwner {
_unpause();
}
function pause() public onlyOwner {
_pause();
}
function resetNetworkPrefix(bytes memory _prefix) public onlyOwner {
_setNetworkPrefix(_prefix);
}
function resetRelayerThreshold(uint8 _threshold) public onlyOwner {
_setRelayThreshold(_threshold);
}
function resetRelayer(uint32 nonce, address[] memory accounts) public onlyOwner {
_resetRelayer(nonce, accounts);
}
/// ==== Internal ====
function _updateRelayer(uint32 nonce, address[] memory accounts, bytes32 beneficiary) internal {
require(accounts.length > 0, "Relay: accounts is empty");
emit SetAuthoritiesEvent(nonce, accounts, beneficiary);
relayers.member = accounts;
relayers.nonce = getRelayerNonce() + 1;
}
function _resetRelayer(uint32 nonce, address[] memory accounts) internal {
require(accounts.length > 0, "Relay: accounts is empty");
relayers.member = accounts;
relayers.nonce = nonce;
emit ResetAuthoritiesEvent(nonce, accounts);
}
function _appendRoot(uint32 index, bytes32 root) internal {
require(getMMRRoot(index) == bytes32(0), "Relay: Index has been set");
_setRoot(index, root);
}
function _setRoot(uint32 index, bytes32 root) internal {
mmrRootPool[index] = root;
emit SetRootEvent(_msgSender(), root, index);
}
function _setNetworkPrefix(bytes memory prefix) internal {
networkPrefix = prefix;
}
function _setRelayThreshold(uint8 _threshold) internal {
require(_threshold > 0, "Relay:: _setRelayThreshold: _threshold equal to 0");
relayers.threshold = _threshold;
}
// This method verifies the content of msg by verifying the existing authority collection in the contract.
// Ecdsa.recover can recover the signer’s address.
// If the signer is matched "isRelayer", it will be counted as a valid signature
// and all signatures will be restored.
// If the number of qualified signers is greater than Equal to threshold,
// the verification is considered successful, otherwise it fails
function _checkSignature(
bytes memory message,
bytes[] memory signatures
) internal view returns (bool) {
require(signatures.length != 0, "Relay:: _checkSignature: signatures is empty");
bytes32 hash = keccak256(message);
uint256 count;
address[] memory signers = new address[](signatures.length);
bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(hash);
for (uint256 i = 0; i < signatures.length; i++) {
address signer = ECDSA.recover(ethSignedMessageHash, signatures[i]);
signers[i] = signer;
}
require(!hasDuplicate(signers), "Relay:: hasDuplicate: Duplicate entries in list");
for (uint256 i = 0; i < signatures.length; i++) {
if (isRelayer(signers[i])) {
count++;
}
}
uint8 threshold = uint8(
SafeMath.div(SafeMath.mul(count, 100), getRelayerCount())
);
return threshold >= getRelayerThreshold();
}
function assertBytesEq(bytes memory a, bytes memory b) internal pure returns (bool){
if (a.length == b.length) {
for (uint i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
} else {
return false;
}
return true;
}
/**
* Returns whether or not there's a duplicate. Runs in O(n^2).
* @param A Array to search
* @return Returns true if duplicate, false otherwise
*/
function hasDuplicate(address[] memory A) internal pure returns (bool) {
if (A.length == 0) {
return false;
}
for (uint256 i = 0; i < A.length - 1; i++) {
for (uint256 j = i + 1; j < A.length; j++) {
if (A[i] == A[j]) {
return true;
}
}
}
return false;
}
}