From c832dbce9021b713a53d6ae96f212c52e828ae9b Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 19 Feb 2020 13:32:44 +0100 Subject: [PATCH 1/7] Add README and typecheck script. --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ typecheck.sh | 6 ++++++ 2 files changed, 67 insertions(+) create mode 100755 typecheck.sh diff --git a/README.md b/README.md index e69de29..0a798e5 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,61 @@ +# Ethereum Machine Oracle + +This project is a set of smart contracts for Ethereum, capable of verifying large computations off chain. + +It aims to be generic, capable of verifying computations done on any abstract machine implementing a specified [interface](./src/Machine.template.sol), using the [truebit](https://people.cs.uchicago.edu/~teutsch/papers/truebit.pdf) style verification game. + +This is a spiritual successor to [solEVM enforcer](https://github.com/leapdao/solEVM-enforcer). + +## Developer guide + +### Requirements + +You have to have the [solidity command line compiler](https://solidity.readthedocs.io/en/v0.6.2/installing-solidity.html#binary-packages) (version >= 0.6.0) installed on your machine. + +### Compilation + +To typecheck run: + +```./typecheck.sh``` + +## The internal model and terminology + +In this section we will explain the terminology used in the code and further documentation. + +### Machine + +The word _machine_ is used to refer to any deterministic state machine that implements a specified [interface](./src/Machine.template.sol). + +Each _machine_ is defined by 3 types: _State_, _Seed_ and _Image_, and 6 functions: _create_, _project_, _isTerminal_, _next_, _stateHash_ and _imageHash_. + +_State_ describes the state of the machine at a particular moment of execution. + +The function _next_ performs the smallest possible step of computation, mapping the input _State_ into an output _State_, **or failing** (all of the situations in which it fails will be described later). + +The function _isTerminal_ determines if a given _State_ is terminal, meaning that if _isTerminal(s) == true_, where _s_ is of type _State_, then _next(s)_ must either fail or return _s_ unchanged. + +The function _stateHash_ maps a _State_ to _bytes32_, giving a "fingerprint" to every _State_. The following has to hold for any valid _machine_ implementation: + +Let _s1_ and _s2_ be of type _State_. If _stateHash(s1) == stateHash(s2)_, then _stateHash(next(s1)) == stateHash(next(s2))_ (assuming here neither _next_ fails). + +Every value of type _Seed_ uniquely determines a _State_ through the function _create_. _Seed_ can be thought of as the initial parameters to a computation. For example, if the _machine_ in question is something like the [EVM](https://ethereum.github.io/yellowpaper/paper.pdf), it's _Seed_ would be a combination of a function, the parameters to it, and the state of the blockchain (i.e. the execution environment). In the EVM example, the _State_ created with _create_ from such a _Seed_, would include all of the _Seed_ data, but also an empty memory, the program counter set to 0 etc. + +The type _Image_ represents the part of _State_ we care about when the computation is finished. Usually when running a compuation, one is not interested in the entire state of the machine running it, but only in some part of it. The function _project_ extracts this part (the _Image_) from a given _State_. We can also take a fingerprint of an _Image_ using the function _imageHash_. The following has to hold for any valid _machine_ implementation: + +Let _s1_ and _s2_ be of type _State_. If _stateHash(s1) == stateHash(s2)_, then _imageHash(project(s1)) == imageHash(project(s2))_. + +#### Example implementation + +Let's take a look at an example _machine_ [implementation](./src/ExampleMachine.sol). This machine is initialized with an array of numbers (the Seed). It's job is to add these numbers together. It does so by putting the initial numbers onto a stack, and keeping a running sum (together these form the State). At each step the top element of the stack is added to the running sum. The part of the State interesting to us is the sum, so the function project just gives us the sum (the Image) in any given State. + +### Oracle + +Coming soon. + +### Court + +Coming soon. + +## Incentives + +Coming soon. diff --git a/typecheck.sh b/typecheck.sh new file mode 100755 index 0000000..6fb5b0c --- /dev/null +++ b/typecheck.sh @@ -0,0 +1,6 @@ +#! /bin/bash + +# Compiles with template +solc src/Oracle.sol +# Compiles with example implementation +sed 's|Machine.template.sol|./src/ExampleMachine.sol|' src/Oracle.sol | solc - From b791abeb053e89721b2316ffe94aab8bf1d826cc Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 19 Feb 2020 15:01:22 +0100 Subject: [PATCH 2/7] Seperate Court and Oracle. --- src/Court.sol | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ src/Oracle.sol | 107 ----------------------------------------------- typecheck.sh | 5 ++- 3 files changed, 114 insertions(+), 109 deletions(-) create mode 100644 src/Court.sol diff --git a/src/Court.sol b/src/Court.sol new file mode 100644 index 0000000..9a0aff4 --- /dev/null +++ b/src/Court.sol @@ -0,0 +1,111 @@ +pragma solidity >=0.6.0; +pragma experimental ABIEncoderV2; + +import "./Oracle.sol"; + +interface ICourt { + enum DisputeState { + DoesNotExist, + Opened, + ProsecutorTurn, + DefendantTurn, + Bottom + } + + // disputeKey is prosecutorRoot + struct Dispute { + bytes32 answerKey; + bytes32 defendantRoot; + bytes32 current; + address prosecutor; + uint lastActionTimestamp; + uint disagreementPoint; + DisputeState state; + } + + function oracle() + external returns (IOracle); + + function getDispute( + bytes32 disputeKey + ) external returns (Dispute memory); + + function newDispute ( + bytes32 answerKey, + bytes32 prosecutorRoot + ) external payable; + + function reveal ( + bytes32 disputeKey, + bytes32 left, + bytes32 right, + bytes calldata proofLeft, + bytes calldata proofRight, + Machine.State calldata finalState + ) external; + + function prosecutorRespond ( + bytes32 disputeKey, + bytes32 left, + bytes32 right + ) external; + + function defendantRespond ( + bytes32 disputeKey, + bytes32 left, + bytes32 right + ) external; + + function defendantRevealBottom ( + bytes32 disputeKey, + bytes calldata proof, + Machine.State calldata state + ) external; + + function timeout ( + bytes32 disputeKey + ) external; +} + +abstract contract ACourt is ICourt { + + IOracle public override oracle; + mapping (bytes32 => Dispute) public disputes; + uint public stakeSize; + + function getDispute ( + bytes32 disputeKey + ) external override returns (Dispute memory) + { + return disputes[disputeKey]; + } + + function newDispute ( + bytes32 answerKey, + bytes32 prosecutorRoot + ) override external payable + { + Dispute memory dispute = disputes[prosecutorRoot]; + + require(msg.value >= stakeSize, "Not enough stake sent"); + require(dispute.state == DisputeState.DoesNotExist, "Dispute already exists."); + require(_answerExists(answerKey), "Answer does not exists."); + require(_enoughTimeForDispute(answerKey), "There is not enough time left for a dispute"); + + dispute.answerKey = answerKey; + dispute.state = DisputeState.Opened; + dispute.prosecutor = msg.sender; + dispute.lastActionTimestamp = now; + } + + + + function _answerExists ( + bytes32 answerKey + ) virtual internal returns (bool); + + function _enoughTimeForDispute ( + bytes32 answerKey + ) virtual internal returns (bool); + +} diff --git a/src/Oracle.sol b/src/Oracle.sol index 64171fc..d96c308 100644 --- a/src/Oracle.sol +++ b/src/Oracle.sol @@ -84,110 +84,3 @@ interface IOracle { abstract contract AOracle is IOracle { } - -interface ICourt { - enum DisputeState { - DoesNotExist, - Opened, - ProsecutorTurn, - DefendantTurn, - Bottom - } - - // disputeKey is prosecutorRoot - struct Dispute { - bytes32 answerKey; - bytes32 defendantRoot; - bytes32 current; - address prosecutor; - uint lastActionTimestamp; - uint disagreementPoint; - DisputeState state; - } - - function oracle() - external returns (IOracle); - - function getDispute( - bytes32 disputeKey - ) external returns (Dispute memory); - - function newDispute ( - bytes32 answerKey, - bytes32 prosecutorRoot - ) external payable; - - function reveal ( - bytes32 disputeKey, - bytes32 left, - bytes32 right, - bytes calldata proofLeft, - bytes calldata proofRight, - Machine.State calldata finalState - ) external; - - function prosecutorRespond ( - bytes32 disputeKey, - bytes32 left, - bytes32 right - ) external; - - function defendantRespond ( - bytes32 disputeKey, - bytes32 left, - bytes32 right - ) external; - - function defendantRevealBottom ( - bytes32 disputeKey, - bytes calldata proof, - Machine.State calldata state - ) external; - - function timeout ( - bytes32 disputeKey - ) external; -} - -abstract contract ACourt is ICourt { - - IOracle public override oracle; - mapping (bytes32 => Dispute) public disputes; - uint public stakeSize; - - function getDispute ( - bytes32 disputeKey - ) external override returns (Dispute memory) - { - return disputes[disputeKey]; - } - - function newDispute ( - bytes32 answerKey, - bytes32 prosecutorRoot - ) override external payable - { - Dispute memory dispute = disputes[prosecutorRoot]; - - require(msg.value >= stakeSize, "Not enough stake sent"); - require(dispute.state == DisputeState.DoesNotExist, "Dispute already exists."); - require(_answerExists(answerKey), "Answer does not exists."); - require(_enoughTimeForDispute(answerKey), "There is not enough time left for a dispute"); - - dispute.answerKey = answerKey; - dispute.state = DisputeState.Opened; - dispute.prosecutor = msg.sender; - dispute.lastActionTimestamp = now; - } - - - - function _answerExists ( - bytes32 answerKey - ) virtual internal returns (bool); - - function _enoughTimeForDispute ( - bytes32 answerKey - ) virtual internal returns (bool); - -} diff --git a/typecheck.sh b/typecheck.sh index 6fb5b0c..d32061a 100755 --- a/typecheck.sh +++ b/typecheck.sh @@ -1,6 +1,7 @@ #! /bin/bash # Compiles with template -solc src/Oracle.sol +solc src/Oracle.sol src/Court.sol # Compiles with example implementation -sed 's|Machine.template.sol|./src/ExampleMachine.sol|' src/Oracle.sol | solc - +sed 's|Machine.template.sol|src/ExampleMachine.sol|' src/Oracle.sol | solc - +sed 's|Oracle.sol|src/Oracle.sol|' src/Court.sol | solc - From afde722a1f24f2ad4104c4d9b41f00d0100c2440 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 21 Feb 2020 10:36:21 +0100 Subject: [PATCH 3/7] Implement reveal method on Court. --- src/Court.sol | 65 +++++++++++++++++++++++++++++++++++++++++--------- src/Merkle.sol | 33 +++++++++++++++++++++++++ typecheck.sh | 5 ++-- 3 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 src/Merkle.sol diff --git a/src/Court.sol b/src/Court.sol index 9a0aff4..d3ee209 100644 --- a/src/Court.sol +++ b/src/Court.sol @@ -2,8 +2,12 @@ pragma solidity >=0.6.0; pragma experimental ABIEncoderV2; import "./Oracle.sol"; +import "./Merkle.sol"; interface ICourt { + using Merkle for Merkle.TreeNode; + using Merkle for Merkle.Proof; + enum DisputeState { DoesNotExist, Opened, @@ -23,6 +27,17 @@ interface ICourt { DisputeState state; } + event NewDispute ( + bytes32 answerKey, + bytes32 prosecutorRoot + ); + + event Reveal ( + bytes32 disputeKey, + bytes32 defendantRoot, + Machine.State finalState + ); + function oracle() external returns (IOracle); @@ -37,23 +52,20 @@ interface ICourt { function reveal ( bytes32 disputeKey, - bytes32 left, - bytes32 right, - bytes calldata proofLeft, - bytes calldata proofRight, + Merkle.TreeNode calldata node, + Merkle.Proof calldata proofLeft, + Merkle.Proof calldata proofRight, Machine.State calldata finalState ) external; function prosecutorRespond ( bytes32 disputeKey, - bytes32 left, - bytes32 right + Merkle.TreeNode calldata node ) external; function defendantRespond ( bytes32 disputeKey, - bytes32 left, - bytes32 right + Merkle.TreeNode calldata node ) external; function defendantRevealBottom ( @@ -87,18 +99,49 @@ abstract contract ACourt is ICourt { { Dispute memory dispute = disputes[prosecutorRoot]; - require(msg.value >= stakeSize, "Not enough stake sent"); + require(msg.value >= stakeSize, "Not enough stake sent."); require(dispute.state == DisputeState.DoesNotExist, "Dispute already exists."); require(_answerExists(answerKey), "Answer does not exists."); - require(_enoughTimeForDispute(answerKey), "There is not enough time left for a dispute"); + require(_enoughTimeForDispute(answerKey), "There is not enough time left for a dispute."); dispute.answerKey = answerKey; dispute.state = DisputeState.Opened; dispute.prosecutor = msg.sender; dispute.lastActionTimestamp = now; + + emit NewDispute(answerKey, prosecutorRoot); } - + function reveal ( + bytes32 disputeKey, + Merkle.TreeNode calldata node, + Merkle.Proof calldata proofLeft, + Merkle.Proof calldata proofRight, + Machine.State calldata finalState + ) override external + { + Dispute memory dispute = disputes[disputeKey]; + IOracle.Answer memory answer = oracle.answers(dispute.answerKey); + + bytes32 defendantRoot = node.hash(); + (bytes32 leftLeaf, bytes32 leftRoot) = proofLeft.eval(); + (bytes32 rightLeaf, bytes32 rightRoot) = proofRight.eval(); + + require(leftRoot == defendantRoot, "Left proof root does not match claimed root."); + require(rightRoot == defendantRoot, "Right proof root does not match claimed root."); + require(leftLeaf == answer.questionKey, "Left leaf does not match initial state hash."); + require(rightLeaf == Machine.stateHash(finalState), "Right leaf does not match the final state hash."); + require(Machine.imageHash(Machine.project(finalState)) == dispute.answerKey, "The revealed final state does not produce the image hash submitted in answer."); + require(Machine.isTerminal(finalState), "The revealed final state is not terminal"); + + dispute.defendantRoot = defendantRoot; + dispute.current = defendantRoot; + dispute.lastActionTimestamp = now; + dispute.state = DisputeState.ProsecutorTurn; + + emit Reveal(disputeKey, defendantRoot, finalState); + } + function _answerExists ( bytes32 answerKey diff --git a/src/Merkle.sol b/src/Merkle.sol new file mode 100644 index 0000000..0d24b8a --- /dev/null +++ b/src/Merkle.sol @@ -0,0 +1,33 @@ +pragma solidity >=0.5.0; +pragma experimental ABIEncoderV2; + + +library Merkle { + + struct TreeNode { + bytes32 left; + bytes32 right; + } + + struct Proof { + bytes32[] data; + } + + function hash (TreeNode memory self) + internal + pure + returns (bytes32) + { + return keccak256(abi.encodePacked(self.left, self.right)); + } + + function eval (Proof memory self) + internal + pure + returns (bytes32 leaf, bytes32 root) + { + + } + + +} diff --git a/typecheck.sh b/typecheck.sh index d32061a..a827129 100755 --- a/typecheck.sh +++ b/typecheck.sh @@ -3,5 +3,6 @@ # Compiles with template solc src/Oracle.sol src/Court.sol # Compiles with example implementation -sed 's|Machine.template.sol|src/ExampleMachine.sol|' src/Oracle.sol | solc - -sed 's|Oracle.sol|src/Oracle.sol|' src/Court.sol | solc - +# TODO fix below +# sed 's|Machine.template.sol|src/ExampleMachine.sol|' src/Oracle.sol | solc - +# sed 's|Oracle.sol|src/Oracle.sol|' src/Court.sol | solc - From 963640cc6184176a86e65eaa79e285a1209b67bf Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 24 Feb 2020 16:04:40 +0100 Subject: [PATCH 4/7] Add prosecutorRespond and defendantRespond. --- src/Court.sol | 96 ++++++++++++++++++++++++++++++++++++++++++++------ src/Merkle.sol | 8 +++++ 2 files changed, 93 insertions(+), 11 deletions(-) diff --git a/src/Court.sol b/src/Court.sol index d3ee209..19a26c7 100644 --- a/src/Court.sol +++ b/src/Court.sol @@ -20,10 +20,14 @@ interface ICourt { struct Dispute { bytes32 answerKey; bytes32 defendantRoot; - bytes32 current; address prosecutor; uint lastActionTimestamp; + uint numberOfSteps; uint disagreementPoint; + uint depth; + bool goRight; + Merkle.TreeNode defendantNode; + Merkle.TreeNode prosecutorNode; DisputeState state; } @@ -39,15 +43,15 @@ interface ICourt { ); function oracle() - external returns (IOracle); + external view returns (IOracle); function getDispute( bytes32 disputeKey - ) external returns (Dispute memory); + ) external view returns (Dispute memory); function newDispute ( bytes32 answerKey, - bytes32 prosecutorRoot + Merkle.TreeNode calldata prosecutorNode ) external payable; function reveal ( @@ -83,23 +87,25 @@ abstract contract ACourt is ICourt { IOracle public override oracle; mapping (bytes32 => Dispute) public disputes; - uint public stakeSize; + uint public STAKE_SIZE; + uint public MAX_TREE_DEPTH; function getDispute ( bytes32 disputeKey - ) external override returns (Dispute memory) + ) external view override returns (Dispute memory) { return disputes[disputeKey]; } function newDispute ( bytes32 answerKey, - bytes32 prosecutorRoot + Merkle.TreeNode calldata prosecutorNode ) override external payable { + bytes32 prosecutorRoot = prosecutorNode.hash(); Dispute memory dispute = disputes[prosecutorRoot]; - require(msg.value >= stakeSize, "Not enough stake sent."); + require(msg.value >= STAKE_SIZE, "Not enough stake sent."); require(dispute.state == DisputeState.DoesNotExist, "Dispute already exists."); require(_answerExists(answerKey), "Answer does not exists."); require(_enoughTimeForDispute(answerKey), "There is not enough time left for a dispute."); @@ -108,6 +114,7 @@ abstract contract ACourt is ICourt { dispute.state = DisputeState.Opened; dispute.prosecutor = msg.sender; dispute.lastActionTimestamp = now; + dispute.prosecutorNode = prosecutorNode; emit NewDispute(answerKey, prosecutorRoot); } @@ -127,6 +134,7 @@ abstract contract ACourt is ICourt { (bytes32 leftLeaf, bytes32 leftRoot) = proofLeft.eval(); (bytes32 rightLeaf, bytes32 rightRoot) = proofRight.eval(); + require(dispute.state == DisputeState.Opened, "Dispute state is not correct for this action."); require(leftRoot == defendantRoot, "Left proof root does not match claimed root."); require(rightRoot == defendantRoot, "Right proof root does not match claimed root."); require(leftLeaf == answer.questionKey, "Left leaf does not match initial state hash."); @@ -135,20 +143,86 @@ abstract contract ACourt is ICourt { require(Machine.isTerminal(finalState), "The revealed final state is not terminal"); dispute.defendantRoot = defendantRoot; - dispute.current = defendantRoot; + dispute.defendantNode = node; dispute.lastActionTimestamp = now; dispute.state = DisputeState.ProsecutorTurn; + dispute.numberOfSteps = proofRight.index(); + dispute.goRight = _goRight(dispute.prosecutorNode, dispute.defendantNode); + dispute.disagreementPoint = _updateDisagreementPoint(dispute.disagreementPoint, dispute.goRight); + dispute.depth = 1; emit Reveal(disputeKey, defendantRoot, finalState); } + + function prosecutorRespond ( + bytes32 disputeKey, + Merkle.TreeNode calldata node + ) override external + { + Dispute memory dispute = disputes[disputeKey]; + + require(dispute.state == DisputeState.ProsecutorTurn, "Dispute state is not correct for this action."); + require(dispute.goRight ? node.hash() == dispute.prosecutorNode.right : node.hash() == dispute.prosecutorNode.left, "Brought node from the wrong side."); + + dispute.prosecutorNode = node; + dispute.lastActionTimestamp = now; + dispute.state = DisputeState.DefendantTurn; + + // emit something + } + + function defendantRespond ( + bytes32 disputeKey, + Merkle.TreeNode calldata node + ) override external + { + Dispute memory dispute = disputes[disputeKey]; + + require(dispute.state == DisputeState.DefendantTurn, "Dispute state is not correct for this action."); + require(dispute.goRight ? node.hash() == dispute.defendantNode.right : node.hash() == dispute.prosecutorNode.left, "Brought node from the wrong side."); + + dispute.defendantNode = node; + dispute.lastActionTimestamp = now; + dispute.goRight = _goRight(dispute.prosecutorNode, dispute.defendantNode); + dispute.disagreementPoint = _updateDisagreementPoint(dispute.disagreementPoint, dispute.goRight); + dispute.depth += 1; + + if (_reachedBottom(dispute.depth)) { + if (dispute.disagreementPoint > dispute.numberOfSteps) { + delete disputes[disputeKey]; + payable(oracle.answers(dispute.answerKey).answerer).call.value(STAKE_SIZE)(""); + } else { + dispute.state = DisputeState.Bottom; + } + } else { + dispute.state = DisputeState.ProsecutorTurn; + } + } function _answerExists ( bytes32 answerKey - ) virtual internal returns (bool); + ) virtual internal view returns (bool); function _enoughTimeForDispute ( bytes32 answerKey - ) virtual internal returns (bool); + ) virtual internal view returns (bool); + + function _goRight ( + Merkle.TreeNode memory prosecutorNode, + Merkle.TreeNode memory defendantNode + ) virtual internal pure returns (bool); + + function _updateDisagreementPoint ( + uint disagreementPoint, + bool goRight + ) virtual internal pure returns (uint); + + function _reachedBottom ( + uint depth + ) internal view returns (bool) + { + return depth == MAX_TREE_DEPTH; + } } diff --git a/src/Merkle.sol b/src/Merkle.sol index 0d24b8a..ac60bab 100644 --- a/src/Merkle.sol +++ b/src/Merkle.sol @@ -29,5 +29,13 @@ library Merkle { } + function index (Proof memory self) + internal + pure + returns (uint) + { + + } + } From e301da5ebf268cdf2a9b9363621e5dacfc7d548e Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 26 Feb 2020 10:35:18 +0100 Subject: [PATCH 5/7] More or less finish specifying Court. --- src/Court.sol | 99 ++++++++++++++++++++++++++++++++++++++++++++------ src/Merkle.sol | 11 +----- src/Oracle.sol | 2 +- 3 files changed, 89 insertions(+), 23 deletions(-) diff --git a/src/Court.sol b/src/Court.sol index 19a26c7..23b2ee8 100644 --- a/src/Court.sol +++ b/src/Court.sol @@ -24,6 +24,7 @@ interface ICourt { uint lastActionTimestamp; uint numberOfSteps; uint disagreementPoint; + bytes32 firstDivergentStateHash; uint depth; bool goRight; Merkle.TreeNode defendantNode; @@ -74,7 +75,7 @@ interface ICourt { function defendantRevealBottom ( bytes32 disputeKey, - bytes calldata proof, + Merkle.Proof calldata proof, Machine.State calldata state ) external; @@ -103,7 +104,7 @@ abstract contract ACourt is ICourt { ) override external payable { bytes32 prosecutorRoot = prosecutorNode.hash(); - Dispute memory dispute = disputes[prosecutorRoot]; + Dispute storage dispute = disputes[prosecutorRoot]; require(msg.value >= STAKE_SIZE, "Not enough stake sent."); require(dispute.state == DisputeState.DoesNotExist, "Dispute already exists."); @@ -127,12 +128,12 @@ abstract contract ACourt is ICourt { Machine.State calldata finalState ) override external { - Dispute memory dispute = disputes[disputeKey]; + Dispute storage dispute = disputes[disputeKey]; IOracle.Answer memory answer = oracle.answers(dispute.answerKey); bytes32 defendantRoot = node.hash(); - (bytes32 leftLeaf, bytes32 leftRoot) = proofLeft.eval(); - (bytes32 rightLeaf, bytes32 rightRoot) = proofRight.eval(); + (bytes32 leftLeaf, bytes32 leftRoot,) = proofLeft.eval(); + (bytes32 rightLeaf, bytes32 rightRoot, uint rightIndex) = proofRight.eval(); require(dispute.state == DisputeState.Opened, "Dispute state is not correct for this action."); require(leftRoot == defendantRoot, "Left proof root does not match claimed root."); @@ -146,7 +147,7 @@ abstract contract ACourt is ICourt { dispute.defendantNode = node; dispute.lastActionTimestamp = now; dispute.state = DisputeState.ProsecutorTurn; - dispute.numberOfSteps = proofRight.index(); + dispute.numberOfSteps = rightIndex; dispute.goRight = _goRight(dispute.prosecutorNode, dispute.defendantNode); dispute.disagreementPoint = _updateDisagreementPoint(dispute.disagreementPoint, dispute.goRight); dispute.depth = 1; @@ -159,7 +160,7 @@ abstract contract ACourt is ICourt { Merkle.TreeNode calldata node ) override external { - Dispute memory dispute = disputes[disputeKey]; + Dispute storage dispute = disputes[disputeKey]; require(dispute.state == DisputeState.ProsecutorTurn, "Dispute state is not correct for this action."); require(dispute.goRight ? node.hash() == dispute.prosecutorNode.right : node.hash() == dispute.prosecutorNode.left, "Brought node from the wrong side."); @@ -176,7 +177,7 @@ abstract contract ACourt is ICourt { Merkle.TreeNode calldata node ) override external { - Dispute memory dispute = disputes[disputeKey]; + Dispute storage dispute = disputes[disputeKey]; require(dispute.state == DisputeState.DefendantTurn, "Dispute state is not correct for this action."); require(dispute.goRight ? node.hash() == dispute.defendantNode.right : node.hash() == dispute.prosecutorNode.left, "Brought node from the wrong side."); @@ -189,17 +190,56 @@ abstract contract ACourt is ICourt { if (_reachedBottom(dispute.depth)) { if (dispute.disagreementPoint > dispute.numberOfSteps) { - delete disputes[disputeKey]; - payable(oracle.answers(dispute.answerKey).answerer).call.value(STAKE_SIZE)(""); + _defendantWins(disputeKey); + // emit something } else { dispute.state = DisputeState.Bottom; + dispute.firstDivergentStateHash = dispute.goRight ? node.right : node.left; + //emit something } } else { dispute.state = DisputeState.ProsecutorTurn; + // emit something } } + + function defendantRevealBottom ( + bytes32 disputeKey, + Merkle.Proof calldata proof, + Machine.State calldata state + ) override external + { + Dispute storage dispute = disputes[disputeKey]; + + (bytes32 leaf, bytes32 root, uint index) = proof.eval(); + require(dispute.state == DisputeState.Bottom, "Dispute state is not correct for this action."); + require(leaf == Machine.stateHash(state), "The submitted proof is not of the revealed state"); + require(root == dispute.defendantRoot, "The submitted proof root does not match defendant root"); + require(index == dispute.disagreementPoint - 1, "The revealed state is not the one before the disagreement point."); + + (Machine.State memory nextState, bool canNext) = Machine.next(state); + + require(canNext, "The machine was unable to compute next state for the revealed state."); + require(Machine.stateHash(nextState) == dispute.firstDivergentStateHash, "Next computed state is not the one commited to."); + + _defendantWins(disputeKey); + // emit something + } + function timeout ( + bytes32 disputeKey + ) override external + { + require(disputes[disputeKey].state != DisputeState.DoesNotExist, "Can not timeout a non existent dispute."); + require(_canTimeout(disputeKey), "This dispute can not be timeout out at this moment"); + if (_defendantWinsOnTimeout(disputeKey)) { + _defendantWins(disputeKey); + } else { + _prosecutorWins(disputeKey); + } + } + function _answerExists ( bytes32 answerKey ) virtual internal view returns (bool); @@ -211,7 +251,10 @@ abstract contract ACourt is ICourt { function _goRight ( Merkle.TreeNode memory prosecutorNode, Merkle.TreeNode memory defendantNode - ) virtual internal pure returns (bool); + ) internal pure returns (bool) + { + return prosecutorNode.left == defendantNode.left; + } function _updateDisagreementPoint ( uint disagreementPoint, @@ -224,5 +267,37 @@ abstract contract ACourt is ICourt { { return depth == MAX_TREE_DEPTH; } - + + function _defendantWins ( + bytes32 disputeKey + ) internal + { + address payable answerer = payable(oracle.answers(disputes[disputeKey].answerKey).answerer); + delete disputes[disputeKey]; + answerer.call.value(STAKE_SIZE)(""); + } + + function _prosecutorWins ( + bytes32 disputeKey + ) internal + { + Dispute storage dispute = disputes[disputeKey]; + oracle.falsify(dispute.answerKey, dispute.prosecutor); + address payable prosecutor = payable(dispute.prosecutor); + delete disputes[disputeKey]; + prosecutor.call.value(STAKE_SIZE)(""); + } + + function _canTimeout ( + bytes32 disputeKey + ) virtual internal view returns (bool); + + function _defendantWinsOnTimeout ( + bytes32 disputeKey + ) internal view returns (bool) + { + DisputeState state = disputes[disputeKey].state; + return state == DisputeState.ProsecutorTurn; + } + } diff --git a/src/Merkle.sol b/src/Merkle.sol index ac60bab..ae75ead 100644 --- a/src/Merkle.sol +++ b/src/Merkle.sol @@ -24,18 +24,9 @@ library Merkle { function eval (Proof memory self) internal pure - returns (bytes32 leaf, bytes32 root) + returns (bytes32 leaf, bytes32 root, uint index) { } - - function index (Proof memory self) - internal - pure - returns (uint) - { - - } - } diff --git a/src/Oracle.sol b/src/Oracle.sol index d96c308..41b4626 100644 --- a/src/Oracle.sol +++ b/src/Oracle.sol @@ -69,7 +69,7 @@ interface IOracle { function falsify ( bytes32 answerKey, address prosecutor - ) external payable; + ) external; function resolveSuccess ( bytes32 answerKey, From 20d25303af210c415cbf3484330f0a4eccf0da2c Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 26 Feb 2020 14:15:58 +0100 Subject: [PATCH 6/7] Implement Oracle spec. --- src/Court.sol | 17 +++-- src/Oracle.sol | 178 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 182 insertions(+), 13 deletions(-) diff --git a/src/Court.sol b/src/Court.sol index 23b2ee8..8ecb4aa 100644 --- a/src/Court.sol +++ b/src/Court.sol @@ -129,7 +129,7 @@ abstract contract ACourt is ICourt { ) override external { Dispute storage dispute = disputes[disputeKey]; - IOracle.Answer memory answer = oracle.answers(dispute.answerKey); + IOracle.Answer memory answer = oracle.getAnswer(dispute.answerKey); bytes32 defendantRoot = node.hash(); (bytes32 leftLeaf, bytes32 leftRoot,) = proofLeft.eval(); @@ -242,11 +242,20 @@ abstract contract ACourt is ICourt { function _answerExists ( bytes32 answerKey - ) virtual internal view returns (bool); + ) internal view returns (bool) + { + IOracle.Answer memory answer = oracle.getAnswer(answerKey); + return answer.questionKey > 0; + } function _enoughTimeForDispute ( bytes32 answerKey - ) virtual internal view returns (bool); + ) internal view returns (bool) + { + IOracle.Answer memory answer = oracle.getAnswer(answerKey); + IOracle.Question memory question = oracle.getQuestion(answer.questionKey); + return now < question.askTime + (2 * question.timeout / 3); + } function _goRight ( Merkle.TreeNode memory prosecutorNode, @@ -272,7 +281,7 @@ abstract contract ACourt is ICourt { bytes32 disputeKey ) internal { - address payable answerer = payable(oracle.answers(disputes[disputeKey].answerKey).answerer); + address payable answerer = payable(oracle.getAnswer(disputes[disputeKey].answerKey).answerer); delete disputes[disputeKey]; answerer.call.value(STAKE_SIZE)(""); } diff --git a/src/Oracle.sol b/src/Oracle.sol index 41b4626..f046edd 100644 --- a/src/Oracle.sol +++ b/src/Oracle.sol @@ -8,7 +8,9 @@ interface IOracle { // questionKey is initialStateHash struct Question { - uint maturationTime; + uint askTime; + uint timeout; + bytes32[] answerKeys; function(bytes32, Machine.Image memory) external successCallback; function(bytes32) external failCallback; } @@ -38,26 +40,26 @@ interface IOracle { event QuestionResolvedSuccessfully ( bytes32 questionKey, - bytes32 answerKey + Machine.Image image ); event QuestionResolvedUnsuccessfully ( bytes32 questionKey ); - function questions ( + function getQuestion ( bytes32 questionKey - ) external returns (Question memory); + ) external view returns (Question memory); - function answers ( + function getAnswer ( bytes32 answerKey - ) external returns (Answer memory); + ) external view returns (Answer memory); function ask ( Machine.Seed calldata seed, uint timeout, - function(uint, Machine.Image memory) external successCallback, - function(uint) external failCallback + function(bytes32, Machine.Image memory) external successCallback, + function(bytes32) external failCallback ) external; function answer ( @@ -82,5 +84,163 @@ interface IOracle { } abstract contract AOracle is IOracle { - + mapping (bytes32 => Question) public questions; + mapping (bytes32 => Answer) public answers; + + address public court; + uint public STAKE_SIZE; + uint public MAX_ANSWER_NUMBER; + + function getQuestion ( + bytes32 questionKey + ) override external view returns (Question memory) + { + return questions[questionKey]; + } + + function getAnswer ( + bytes32 answerKey + ) override external view returns (Answer memory) + { + return answers[answerKey]; + } + + function ask ( + Machine.Seed calldata seed, + uint timeout, + function(bytes32, Machine.Image memory) external successCallback, + function(bytes32) external failCallback + ) override external + { + bytes32 questionKey = Machine.stateHash(Machine.create(seed)); + Question storage question = questions[questionKey]; + + require(!_questionExists(questionKey), "Question already exists."); + require(timeout > 0, "Timeout must be greater then zero."); + + question.askTime = now; + question.timeout = timeout; + question.successCallback = successCallback; + question.failCallback = failCallback; + + emit NewQuestion(questionKey, seed, msg.sender); + } + + function answer ( + bytes32 questionKey, + bytes32 imageHash + ) override external payable + { + Question storage question = questions[questionKey]; + Answer storage answer = answers[imageHash]; + + require(msg.value >= STAKE_SIZE, "Not enough stake sent."); + require(_questionExists(questionKey), "Question does not exist."); + require(!_answerExists(imageHash), "Answer already exists."); + require(_enoughTimeForAnswer(questionKey), "There is not enoguh time left for submitting new answers to this question."); + require(question.answerKeys.length < MAX_ANSWER_NUMBER, "All the answer slots are full"); + + question.answerKeys.push(imageHash); + + answer.answerer = msg.sender; + answer.questionKey = questionKey; + + emit NewAnswer(questionKey, imageHash); + } + + function falsify ( + bytes32 answerKey, + address prosecutor + ) override external + { + Answer storage answer = answers[answerKey]; + + require(_answerExists(answerKey), "The answer trying to be falsified does not exist"); + require(msg.sender == court, "Only court can falsify answers"); + + answer.falsified = true; + payable(prosecutor).call.value(STAKE_SIZE)(""); + + emit AnswerFalsified(answer.questionKey, answerKey); + } + + function resolveSuccess ( + bytes32 answerKey, + Machine.Image calldata image + ) override external + { + Answer storage answer = answers[answerKey]; + Question storage question = questions[answer.questionKey]; + + require(_questionExists(answer.questionKey) && _answerExists(answerKey), "Question and answer must exists."); + require(now >= question.askTime + question.timeout, "Answering is still in progress."); + require(Machine.imageHash(image) == answerKey, "Image hash does not match answerKey."); + require(!answer.falsified, "This answer was falsified"); + + bytes32 questionKey = answer.questionKey; + address answerer = answer.answerer; + function(bytes32, Machine.Image memory) external callback = question.successCallback; + + _questionCleanup(questionKey); + payable(answerer).call.value(STAKE_SIZE)(""); + + try callback(questionKey, image) { + emit QuestionResolvedSuccessfully(questionKey, image); + } catch { + emit QuestionResolvedUnsuccessfully(questionKey); + } + } + + function resolveFail ( + bytes32 questionKey + ) override external + { + Question storage question = questions[questionKey]; + + require(_questionExists(questionKey), "Question must exist."); + require(now >= question.askTime + (2 * question.timeout), "It is not the time to give up yet."); + + function(bytes32) external callback = question.failCallback; + + _questionCleanup(questionKey); + + try callback(questionKey) { + emit QuestionResolvedUnsuccessfully(questionKey); + } catch { + emit QuestionResolvedUnsuccessfully(questionKey); + } + } + + function _questionCleanup ( + bytes32 questionKey + ) internal + { + for (uint i = 0; i < questions[questionKey].answerKeys.length; i ++) { + bytes32 answerKey = questions[questionKey].answerKeys[i]; + delete answers[answerKey]; + } + delete questions[questionKey]; + } + + function _questionExists ( + bytes32 questionKey + ) internal view returns (bool) + { + return questions[questionKey].askTime > 0; + } + + function _answerExists ( + bytes32 answerKey + ) internal view returns (bool) + { + return answers[answerKey].questionKey > 0; + } + + function _enoughTimeForAnswer ( + bytes32 questionKey + ) internal view returns (bool) + { + Question storage question = questions[questionKey]; + return now < question.askTime + (question.timeout / 3); + } } From 4480c3d4b6ab724d887f72502e33395f57e63408 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 26 Feb 2020 14:47:41 +0100 Subject: [PATCH 7/7] Add left index check in reveal. --- src/Court.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Court.sol b/src/Court.sol index 8ecb4aa..04569f5 100644 --- a/src/Court.sol +++ b/src/Court.sol @@ -132,10 +132,11 @@ abstract contract ACourt is ICourt { IOracle.Answer memory answer = oracle.getAnswer(dispute.answerKey); bytes32 defendantRoot = node.hash(); - (bytes32 leftLeaf, bytes32 leftRoot,) = proofLeft.eval(); + (bytes32 leftLeaf, bytes32 leftRoot, uint leftIndex) = proofLeft.eval(); (bytes32 rightLeaf, bytes32 rightRoot, uint rightIndex) = proofRight.eval(); require(dispute.state == DisputeState.Opened, "Dispute state is not correct for this action."); + require(leftIndex == 0, "Left index must be 0."); require(leftRoot == defendantRoot, "Left proof root does not match claimed root."); require(rightRoot == defendantRoot, "Right proof root does not match claimed root."); require(leftLeaf == answer.questionKey, "Left leaf does not match initial state hash.");