Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PIP36: Replay Failed StateSyncs #74

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
79 changes: 69 additions & 10 deletions contracts/StateReceiver.sol
Original file line number Diff line number Diff line change
@@ -1,43 +1,102 @@
pragma solidity ^0.5.11;

import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol";

import { System } from "./System.sol";
import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol";
import {System} from "./System.sol";
import {IStateReceiver} from "./IStateReceiver.sol";

contract StateReceiver is System {
using RLPReader for bytes;
using RLPReader for RLPReader.RLPItem;

uint256 public lastStateId;

bytes32 public failedStateSyncsRoot;
mapping(bytes32 => bool) public nullifier;

mapping(uint256 => bytes) public failedStateSyncs;

address public rootSetter;
dev1644 marked this conversation as resolved.
Show resolved Hide resolved
uint256 public constant TREE_DEPTH = 6;

event StateCommitted(uint256 indexed stateId, bool success);
event StateSyncReplay(uint256 indexed stateId);

constructor(address _rootSetter) public {
rootSetter = _rootSetter;
}

function commitState(uint256 syncTime, bytes calldata recordBytes) external onlySystem returns(bool success) {
function commitState(uint256 syncTime, bytes calldata recordBytes) external onlySystem returns (bool success) {
// parse state data
RLPReader.RLPItem[] memory dataList = recordBytes.toRlpItem().toList();
uint256 stateId = dataList[0].toUint();
require(
lastStateId + 1 == stateId,
"StateIds are not sequential"
);
require(lastStateId + 1 == stateId, "StateIds are not sequential");
lastStateId++;

address receiver = dataList[1].toAddress();
bytes memory stateData = dataList[2].toBytes();
// notify state receiver contract, in a non-revert manner
if (isContract(receiver)) {
uint256 txGas = 5000000;

bytes memory data = abi.encodeWithSignature("onStateReceive(uint256,bytes)", stateId, stateData);
// solium-disable-next-line security/no-inline-assembly
assembly {
success := call(txGas, receiver, 0, add(data, 0x20), mload(data), 0, 0)
}
emit StateCommitted(stateId, success);
if (!success) failedStateSyncs[stateId] = abi.encode(receiver, stateData);
}
}

function replayFailedStateSync(uint256 stateId) external {
bytes memory stateSyncData = failedStateSyncs[stateId];
require(stateSyncData.length != 0, "!found");
delete failedStateSyncs[stateId];

(address receiver, bytes memory stateData) = abi.decode(stateSyncData, (address, bytes));
emit StateSyncReplay(stateId);
IStateReceiver(receiver).onStateReceive(stateId, stateData); // revertable
}

function setRoot(bytes32 _root) external {
require(msg.sender == rootSetter, "!rootSetter");
require(failedStateSyncsRoot == bytes32(0), "already set");
failedStateSyncsRoot = _root;
}

function replayHistoricFailedStateSync(
bytes32[TREE_DEPTH] calldata proof,
uint256 proofIndex,
uint256 stateId,
address receiver,
bytes calldata data
) external {
bytes32 root = failedStateSyncsRoot;
require(root != bytes32(0), "!root");

bytes32 leafHash = keccak256(abi.encode(stateId, receiver, data));
bytes32 zeroHash = keccak256(abi.encode(uint256(0), address(0), new bytes(0)));
require(leafHash != zeroHash && !nullifier[leafHash], "used");
nullifier[leafHash] = true;

require(root == _getRoot(proof, proofIndex, leafHash), "!proof");

emit StateSyncReplay(stateId);
IStateReceiver(receiver).onStateReceive(stateId, data);
}

function _getRoot(bytes32[TREE_DEPTH] memory proof, uint256 index, bytes32 leafHash) private pure returns (bytes32) {
bytes32 node = leafHash;

for (uint256 height = 0; height < TREE_DEPTH; height++) {
if (((index >> height) & 1) == 1) node = keccak256(abi.encodePacked(proof[height], node));
else node = keccak256(abi.encodePacked(node, proof[height]));
}

return node;
}

// check if address is contract
function isContract(address _addr) private view returns (bool){
function isContract(address _addr) private view returns (bool) {
uint32 size;
// solium-disable-next-line security/no-inline-assembly
assembly {
Expand Down
19 changes: 19 additions & 0 deletions contracts/test/TestReenterer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
pragma solidity ^0.5.11;

contract TestReenterer {
uint256 public reenterCount;

function onStateReceive(uint256 id, bytes calldata _data) external {
if (reenterCount++ == 0) {
(bool success, bytes memory ret) = msg.sender.call(
abi.encodeWithSignature("replayFailedStateSync(uint256)", id)
);
// bubble up revert for tests
if (!success) {
assembly {
revert(add(ret, 0x20), mload(ret))
}
}
}
}
}
12 changes: 12 additions & 0 deletions contracts/test/TestRevertingReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pragma solidity ^0.5.11;

contract TestRevertingReceiver {
bool public shouldIRevert = true;
function onStateReceive(uint256 _id, bytes calldata _data) external {
if (shouldIRevert) revert("TestRevertingReceiver");
}

function toggle() external {
shouldIRevert = !shouldIRevert;
}
}
4 changes: 3 additions & 1 deletion contracts/test/TestStateReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ pragma experimental ABIEncoderV2;
import {StateReceiver} from "../StateReceiver.sol";
import {TestSystem} from "./TestSystem.sol";

contract TestStateReceiver is StateReceiver, TestSystem {}
contract TestStateReceiver is StateReceiver, TestSystem {
constructor(address _rootSetter) public StateReceiver(_rootSetter) {}
}
12 changes: 9 additions & 3 deletions migrations/2_genesis_contracts_deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const SafeMath = artifacts.require('SafeMath')
const StateReciever = artifacts.require('StateReceiver')
const TestStateReceiver = artifacts.require('TestStateReceiver')
const TestCommitState = artifacts.require('TestCommitState')
const TestReenterer = artifacts.require('TestReenterer')
const TestRevertingReceiver = artifacts.require('TestRevertingReceiver')
const System = artifacts.require('System')
const ValidatorVerifier = artifacts.require('ValidatorVerifier')

Expand Down Expand Up @@ -44,13 +46,17 @@ module.exports = async function (deployer, network) {
deployer.link(e.lib, e.contracts)
}

console.log("Deploying contracts...")
const rootSetter = deployer.networks[network].from

console.log("Deploying contracts with rootSetter %s...", rootSetter)
await deployer.deploy(BorValidatorSet)
await deployer.deploy(TestBorValidatorSet)
await deployer.deploy(StateReciever)
await deployer.deploy(TestStateReceiver)
await deployer.deploy(StateReciever, rootSetter)
await deployer.deploy(TestStateReceiver, rootSetter)
await deployer.deploy(System)
await deployer.deploy(ValidatorVerifier)
await deployer.deploy(TestCommitState)
await deployer.deploy(TestReenterer)
await deployer.deploy(TestRevertingReceiver)
})
}
Loading
Loading