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
16 changes: 16 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 120,
"singleQuote": false
}
}
]
}
83 changes: 73 additions & 10 deletions contracts/StateReceiver.sol
Original file line number Diff line number Diff line change
@@ -1,43 +1,106 @@
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 leafCount;
uint256 public replayCount;
uint256 public constant TREE_DEPTH = 16;

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 setRootAndLeafCount(bytes32 _root, uint256 _leafCount) external {
require(msg.sender == rootSetter, "!rootSetter");
require(failedStateSyncsRoot == bytes32(0), "!zero");
dev1644 marked this conversation as resolved.
Show resolved Hide resolved
failedStateSyncsRoot = _root;
leafCount = _leafCount;
}

function replayHistoricFailedStateSync(
bytes32[TREE_DEPTH] calldata proof,
uint256 leafIndex,
uint256 stateId,
address receiver,
bytes calldata data
) external {
require(++replayCount <= leafCount, "end");
bytes32 root = failedStateSyncsRoot;
require(root != bytes32(0), "!root");

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

require(root == _getRoot(proof, leafIndex, 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)
})
}
2 changes: 2 additions & 0 deletions scripts/run-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ start_testrpc

npm run truffle:migrate "$@"

export CI=true

if [ "$SOLIDITY_COVERAGE" = true ]; then
npm run truffle:coverage "$@"
else
Expand Down
Loading
Loading