-
Notifications
You must be signed in to change notification settings - Fork 4
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
The spec. #2
Closed
Closed
The spec. #2
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
c832dbc
Add README and typecheck script.
b791abe
Seperate Court and Oracle.
afde722
Implement reveal method on Court.
963640c
Add prosecutorRespond and defendantRespond.
e301da5
More or less finish specifying Court.
20d2530
Implement Oracle spec.
4480c3d
Add left index check in reveal.
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,313 @@ | ||
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, | ||
ProsecutorTurn, | ||
DefendantTurn, | ||
Bottom | ||
} | ||
|
||
// disputeKey is prosecutorRoot | ||
struct Dispute { | ||
bytes32 answerKey; | ||
bytes32 defendantRoot; | ||
address prosecutor; | ||
uint lastActionTimestamp; | ||
uint numberOfSteps; | ||
uint disagreementPoint; | ||
bytes32 firstDivergentStateHash; | ||
uint depth; | ||
bool goRight; | ||
Merkle.TreeNode defendantNode; | ||
Merkle.TreeNode prosecutorNode; | ||
DisputeState state; | ||
} | ||
|
||
event NewDispute ( | ||
bytes32 answerKey, | ||
bytes32 prosecutorRoot | ||
); | ||
|
||
event Reveal ( | ||
bytes32 disputeKey, | ||
bytes32 defendantRoot, | ||
Machine.State finalState | ||
); | ||
|
||
function oracle() | ||
external view returns (IOracle); | ||
|
||
function getDispute( | ||
bytes32 disputeKey | ||
) external view returns (Dispute memory); | ||
|
||
function newDispute ( | ||
bytes32 answerKey, | ||
Merkle.TreeNode calldata prosecutorNode | ||
) external payable; | ||
|
||
function reveal ( | ||
bytes32 disputeKey, | ||
Merkle.TreeNode calldata node, | ||
Merkle.Proof calldata proofLeft, | ||
Merkle.Proof calldata proofRight, | ||
Machine.State calldata finalState | ||
) external; | ||
|
||
function prosecutorRespond ( | ||
bytes32 disputeKey, | ||
Merkle.TreeNode calldata node | ||
) external; | ||
|
||
function defendantRespond ( | ||
bytes32 disputeKey, | ||
Merkle.TreeNode calldata node | ||
) external; | ||
|
||
function defendantRevealBottom ( | ||
bytes32 disputeKey, | ||
Merkle.Proof 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 STAKE_SIZE; | ||
uint public MAX_TREE_DEPTH; | ||
|
||
function getDispute ( | ||
bytes32 disputeKey | ||
) external view override returns (Dispute memory) | ||
{ | ||
return disputes[disputeKey]; | ||
} | ||
|
||
function newDispute ( | ||
bytes32 answerKey, | ||
Merkle.TreeNode calldata prosecutorNode | ||
) override external payable | ||
{ | ||
bytes32 prosecutorRoot = prosecutorNode.hash(); | ||
Dispute storage dispute = disputes[prosecutorRoot]; | ||
|
||
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."); | ||
|
||
dispute.answerKey = answerKey; | ||
dispute.state = DisputeState.Opened; | ||
dispute.prosecutor = msg.sender; | ||
dispute.lastActionTimestamp = now; | ||
dispute.prosecutorNode = prosecutorNode; | ||
|
||
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 storage dispute = disputes[disputeKey]; | ||
IOracle.Answer memory answer = oracle.getAnswer(dispute.answerKey); | ||
|
||
bytes32 defendantRoot = node.hash(); | ||
(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."); | ||
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.defendantNode = node; | ||
dispute.lastActionTimestamp = now; | ||
dispute.state = DisputeState.ProsecutorTurn; | ||
dispute.numberOfSteps = rightIndex; | ||
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 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."); | ||
|
||
dispute.prosecutorNode = node; | ||
dispute.lastActionTimestamp = now; | ||
dispute.state = DisputeState.DefendantTurn; | ||
|
||
// emit something | ||
} | ||
|
||
function defendantRespond ( | ||
bytes32 disputeKey, | ||
Merkle.TreeNode calldata node | ||
) override external | ||
{ | ||
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."); | ||
|
||
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) { | ||
_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 | ||
) internal view returns (bool) | ||
{ | ||
IOracle.Answer memory answer = oracle.getAnswer(answerKey); | ||
return answer.questionKey > 0; | ||
} | ||
|
||
function _enoughTimeForDispute ( | ||
bytes32 answerKey | ||
) 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, | ||
Merkle.TreeNode memory defendantNode | ||
) internal pure returns (bool) | ||
{ | ||
return prosecutorNode.left == defendantNode.left; | ||
} | ||
|
||
function _updateDisagreementPoint ( | ||
uint disagreementPoint, | ||
bool goRight | ||
) virtual internal pure returns (uint); | ||
|
||
function _reachedBottom ( | ||
uint depth | ||
) internal view returns (bool) | ||
{ | ||
return depth == MAX_TREE_DEPTH; | ||
} | ||
|
||
function _defendantWins ( | ||
bytes32 disputeKey | ||
) internal | ||
{ | ||
address payable answerer = payable(oracle.getAnswer(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; | ||
} | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is an "unchanged s" really possible? if next is supposed to "perform the smallest possible step of computation", then state must have changed, hence same state can not be returned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it depends on whether you consider doing nothing, or no-op to be "a step of computation".
But the point I was trying to get across is that you define a terminal state by either saying "a terminal state is one from which no further computation can be done", or "a terminal state is a fixpoint with respect to the state-transition function".
And actually, I think I will have to commit to one of these in future, as my implementation efforts continue.