|
1 |
| -pragma solidity ^0.5.11; |
| 1 | +pragma solidity 0.6.12; |
2 | 2 |
|
3 |
| -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; |
4 |
| - |
5 |
| -import { System } from "./System.sol"; |
| 3 | +import {RLPReader} from "./utils/RLPReader.sol"; |
| 4 | +import {System} from "./System.sol"; |
| 5 | +import {IStateReceiver} from "./IStateReceiver.sol"; |
6 | 6 |
|
7 | 7 | contract StateReceiver is System {
|
8 | 8 | using RLPReader for bytes;
|
9 | 9 | using RLPReader for RLPReader.RLPItem;
|
10 | 10 |
|
11 | 11 | uint256 public lastStateId;
|
12 | 12 |
|
| 13 | + bytes32 public failedStateSyncsRoot; |
| 14 | + mapping(bytes32 => bool) public nullifier; |
| 15 | + |
| 16 | + mapping(uint256 => bytes) public failedStateSyncs; |
| 17 | + |
| 18 | + address public immutable rootSetter; |
| 19 | + uint256 public leafCount; |
| 20 | + uint256 public replayCount; |
| 21 | + uint256 public constant TREE_DEPTH = 16; |
| 22 | + |
13 | 23 | event StateCommitted(uint256 indexed stateId, bool success);
|
| 24 | + event StateSyncReplay(uint256 indexed stateId); |
| 25 | + |
| 26 | + constructor(address _rootSetter) public { |
| 27 | + rootSetter = _rootSetter; |
| 28 | + } |
14 | 29 |
|
15 |
| - function commitState(uint256 syncTime, bytes calldata recordBytes) external onlySystem returns(bool success) { |
| 30 | + function commitState(uint256 syncTime, bytes calldata recordBytes) external onlySystem returns (bool success) { |
16 | 31 | // parse state data
|
17 | 32 | RLPReader.RLPItem[] memory dataList = recordBytes.toRlpItem().toList();
|
18 | 33 | uint256 stateId = dataList[0].toUint();
|
19 |
| - require( |
20 |
| - lastStateId + 1 == stateId, |
21 |
| - "StateIds are not sequential" |
22 |
| - ); |
| 34 | + require(lastStateId + 1 == stateId, "StateIds are not sequential"); |
23 | 35 | lastStateId++;
|
24 |
| - |
25 | 36 | address receiver = dataList[1].toAddress();
|
26 | 37 | bytes memory stateData = dataList[2].toBytes();
|
27 | 38 | // notify state receiver contract, in a non-revert manner
|
28 | 39 | if (isContract(receiver)) {
|
29 | 40 | uint256 txGas = 5000000;
|
| 41 | + |
30 | 42 | bytes memory data = abi.encodeWithSignature("onStateReceive(uint256,bytes)", stateId, stateData);
|
31 | 43 | // solium-disable-next-line security/no-inline-assembly
|
32 | 44 | assembly {
|
33 | 45 | success := call(txGas, receiver, 0, add(data, 0x20), mload(data), 0, 0)
|
34 | 46 | }
|
35 | 47 | emit StateCommitted(stateId, success);
|
| 48 | + if (!success) failedStateSyncs[stateId] = abi.encode(receiver, stateData); |
36 | 49 | }
|
37 | 50 | }
|
38 | 51 |
|
| 52 | + function replayFailedStateSync(uint256 stateId) external { |
| 53 | + bytes memory stateSyncData = failedStateSyncs[stateId]; |
| 54 | + require(stateSyncData.length != 0, "!found"); |
| 55 | + delete failedStateSyncs[stateId]; |
| 56 | + |
| 57 | + (address receiver, bytes memory stateData) = abi.decode(stateSyncData, (address, bytes)); |
| 58 | + emit StateSyncReplay(stateId); |
| 59 | + IStateReceiver(receiver).onStateReceive(stateId, stateData); // revertable |
| 60 | + } |
| 61 | + |
| 62 | + function setRootAndLeafCount(bytes32 _root, uint256 _leafCount) external { |
| 63 | + require(msg.sender == rootSetter, "!rootSetter"); |
| 64 | + require(failedStateSyncsRoot == bytes32(0), "!zero"); |
| 65 | + failedStateSyncsRoot = _root; |
| 66 | + leafCount = _leafCount; |
| 67 | + } |
| 68 | + |
| 69 | + function replayHistoricFailedStateSync( |
| 70 | + bytes32[TREE_DEPTH] calldata proof, |
| 71 | + uint256 leafIndex, |
| 72 | + uint256 stateId, |
| 73 | + address receiver, |
| 74 | + bytes calldata data |
| 75 | + ) external { |
| 76 | + require(leafIndex < 2 ** TREE_DEPTH, "invalid leafIndex"); |
| 77 | + require(++replayCount <= leafCount, "end"); |
| 78 | + bytes32 root = failedStateSyncsRoot; |
| 79 | + require(root != bytes32(0), "!root"); |
| 80 | + |
| 81 | + bytes32 leafHash = keccak256(abi.encode(stateId, receiver, data)); |
| 82 | + bytes32 zeroHash = 0x28cf91ac064e179f8a42e4b7a20ba080187781da55fd4f3f18870b7a25bacb55; // keccak256(abi.encode(uint256(0), address(0), new bytes(0))); |
| 83 | + require(leafHash != zeroHash && !nullifier[leafHash], "used"); |
| 84 | + nullifier[leafHash] = true; |
| 85 | + |
| 86 | + require(root == _getRoot(proof, leafIndex, leafHash), "!proof"); |
| 87 | + |
| 88 | + emit StateSyncReplay(stateId); |
| 89 | + IStateReceiver(receiver).onStateReceive(stateId, data); |
| 90 | + } |
| 91 | + |
| 92 | + function _getRoot(bytes32[TREE_DEPTH] memory proof, uint256 index, bytes32 leafHash) private pure returns (bytes32) { |
| 93 | + bytes32 node = leafHash; |
| 94 | + |
| 95 | + for (uint256 height = 0; height < TREE_DEPTH; height++) { |
| 96 | + if (((index >> height) & 1) == 1) node = keccak256(abi.encodePacked(proof[height], node)); |
| 97 | + else node = keccak256(abi.encodePacked(node, proof[height])); |
| 98 | + } |
| 99 | + |
| 100 | + return node; |
| 101 | + } |
| 102 | + |
39 | 103 | // check if address is contract
|
40 |
| - function isContract(address _addr) private view returns (bool){ |
| 104 | + function isContract(address _addr) private view returns (bool) { |
41 | 105 | uint32 size;
|
42 | 106 | // solium-disable-next-line security/no-inline-assembly
|
43 | 107 | assembly {
|
|
0 commit comments