Plasma Group Generalized Plasma Specification¶
Plasma Group is working on a generalized plasma construction that supports significantly more functionality than alternative plasma chains. Generalized plasma chains are complex objects of study. This website documents the entirety of the Plasma Group design, from the inner workings of the client to the specifics of the plasma smart contract.
Introduction¶
Hello and welcome to Plasma Group’s Generalized Plasma Specification! We’ve been working toward this spec for a long time now and we’re excited to be able to share it with the public. We’re going to kick the spec off with a high-level overview of our construction and then go into the structure of the spec itself.
TL;DR¶
Plasma is basically a way to build blockchains on top of blockchains. Plasma chains are sort of like sidechains, except they’re a little less flexible and a lot more secure. We won’t go into the details here.
Up until now, people have only been able to build very limited plasma chains that accomplish a few specific goals (like make payments or exchange assets). None of these chains support the sort of “smart contracts” that make Ethereum so useful. In order to build a “plasma application”, you’d have to build an entire blockchain from scratch. That’s obviously way too much work.
So we set out to design a general purpose chain. It took a lot of work, but we finally arrived at a design we’re happy with (and that’s what we’ve specified here!). Now, with this new design, developers can build apps and run them on top of a general purpose plasma chain instead of having to build an entire plasma chain from scratch. It’s sort of like the jump of going from Bitcoin to Ethereum.
Required Background Knowledge¶
You’re going to see a lot of terms and ideas from previous plasma research when you’re reading through this specification. Although we’ve provided references to these terms or ideas wherever possible, we generally try to avoid rehashing what’s already been explained well before. As a result, you should be familiar with the general theory behind plasma and with various specific plasma constructions (like Plasma MVP and Plasma Cash) before diving into this spec.
You don’t need an extremely deep knowledge of plasma, but you should be comfortable with the idea of deposits, exits, and exit games. If you’re not familiar with these concepts yet, we recommend reading through the original Plasma MVP and Plasma Cash posts, as well as the content on LearnPlasma.
Sometimes we have a little trouble remembering that readers don’t have quite as much context as we do. If you feel familiar with these concepts but are still confused by some aspect of the spec, please let us know! We’ve set up a system that makes it easy for anyone to leave review comments in just a few seconds.
Specification Goals¶
This specification should be detailed, concise, and contained. Readers should have a very clear idea of the system as a whole without relying on guesswork about how a specific mechanism functions. Similarly, developers should be able to create a compliant implementation simply by reading through the specification.
If at any time you feel we haven’t quite achieved that goal, please let us know! We’re always open to constructive criticism. If you find something confusing, it’s likely that others do too.
Specification Structure¶
We’ve laid out this specification in a very deliberate way that attempts to mirror the layout of the Lightning BOLT specifications.
Our specification is composed of eight primary sections:
- #00: Introduction
- #01: Core Design Components
- #02: Contract Specifications
- #03: Client Specification
- #04: Operator Specification
- #05: Client Architecture
- #06: Operator Architecture
- #07: Predicate Specifications
The first five sections (00 - 04) form the specification of the generalized plasma system itself. These sections explain, in detail, how the system works and what components someone would have to include in a compliant implementation.
The last three sections (05 - 07) describe the architecture for Plasma Group’s implementation of the specification. A developer would NOT have to use the same architecture in their own implementation, but it may be useful in order to better understand how certain components are supposed to function. Someone who’s primarily interested in understanding the system at a high-level could skip these sections.
Housekeeping¶
Source¶
This specification is a living document. You can find the source for this document on the Plasma Group monorepo.
We need your help! At the end of the day, this specification is meant to help others build better plasma chains. If you find anything confusing, please create an issue on GitHub and let us know how we can help. You’re also more than welcome to create a pull request if you feel like you can fix an issue yourself. We’re usually pretty responsive to issues and PRs.
Versioning¶
Most pages within this specification will continue to evolve as we develop our implementation. We’ve decided to use semantic versioning so that it’s easy to see and understand the difference between versions of this document.
Our semantic versioning strategy is pretty simple, each version follows the format major.minor.patch
. Changes to this specification that would make an implementation incompatible with implementations of previous versions are split into different major versions. Changes that simply add functionality but don’t break backwards compatibility bump the minor version. Finally, fixes that don’t add or remove functionality (layout edits, grammatical fixes, typo fixes) bump the patch version.
Generalized State System¶
The concept of state and state transitions is relatively universal. However, Layer 2 systems are unusual because they often require that state transitions be efficiently representable on the Layer 1 platform. As a result, existing models of state transitions on plasma are usually quite restrictive.
In order to develop a plasma system that could allow for more general state transitions, we found it important to develop a new system for representing state and state transitions. Our system was particularly designed to make state transitions easily executable on Ethereum.
This page describes our state model and the various terms we use in the rest of this specification. It’s important to understand this model in detail before continuing.
State Objects¶
State Object Model¶
The core building block of our model is the “state object”. Each state object represents a particular piece of state within the system and is composed of:
- A globally unique identifier.
- The address of a predicate contract.
- Some arbitrary data.
The TypeScript interface for the state object is:
interface StateObject {
id: string
predicate: string
data: string
}
State objects are unique objects (hence the id
) that may be created, destroyed, or mutated. The conditions under which a state object may undergo one of these changes, as well as the effects of such a change, are defined in a predicate. Each state object specifies a prediciate identifier (predicate
) that points to the specific predicate that controls (“locks”) the state object.
Requirements¶
- State objects:
- MUST have a globally unique identifier.
- MUST specify the 20-byte address of a predicate contract.
- MUST specify some additional arbitrary stored data.
- MAY specify the empty string in place of arbitrary stored data.
- SHOULD specify the zero address to represent a “burned” state.
Rationale¶
We wanted our generalized plasma construction to be agnostic to the underlying design (Plasma MVP, Plasma Cash, etc.). As a result, it became important to find a very general-purpose way of representing state.
The idea of a “state object”, an object that can be created, destroyed, or modified, seemed simple and intuitive. Perhaps more importantly, the “state object” model encapsulates both UTXOs and accounts. Ephemeral UTXO-like systems can be represented by objects which can only be created or destroyed, but not modified. Account-like systems can be represented by objects which can be modified.
The state object therefore allows us to simultaneously represent systems like Plasma MVP and Plasma Cash with a single unified notation. We believe this unification will lead to increased collaboration and decreased duplication of work.
Encoding and Decoding¶
State objects MUST be ABI encoded and decoded according to the following structure:
[
id: bytes,
predicate: address,
data: bytes
]
Rationale¶
Plasma Group has decided to write its contracts in Solidity instead of in Vyper. We therefore needed an encoding scheme that would be easy to decode within a Solidity contract. Other teams have invested significant resources into developing smart contract libraries for encoding schemes. However, Solidity provides native support for ABI encoding and decoding. In order to minimize effort spent on encoding libraries, we’ve decided to simply use the native ABI encoding mechanisms.
Test Vectors¶
Todo
Add test vectors for encoding and decoding.
Predicates¶
Predicates are functions that define the ways in which state objects can be mutated. We require that the id
of a specific state object never change (since it would then be a different state object). However, the predicate
and data
can be changed arbitrarily according to the rules defined in the predicate.
Predicate Methods¶
Predicates can provide one or more methods which take a state object in one state and transform it into another state. For example, a simple “ownership” predicate may define a function that allows the current owner of the object (defined in object.data
) to specify a new owner.
For simplicity, we require that predicate methods may only allow input and output types that correspond to the primitive types in Solidity.
Rationale¶
Effectively all blockchain systems provide a model for different “methods” that determine how a given object can be transformed. Bitcoin’s UTXO model allows for multiple “spending conditions” under which a UTXO can be consumed. Ethereum’s account model allows a contract to specify multiple explicit state-transforming functions. The “method” model generalizes this concept.
We require that methods only use the primitive types available in Solidity so that predicates can easily be executed by treating them as Solidity contracts. Defining new types not understood by Solidity would require the development of a completely new EVM language.
Requirements¶
- Predicate methods:
- MUST be executable within a single transaction to an Ethereum smart contract.
- MUST only use the primitive types in Solidity.
Method Identifiers¶
Methods within each predicate are given a unique identifier computed as the keccak256 hash of the UTF-8 encoded version of the method’s signature.
For any given method:
function methodName(Param1Type param1, Param2Type param2) public returns (ReturnType)
We get a corresponding signature:
methodName(Param1Type, Param2Type, ...)
Example¶
We’ll use the SimpleOwnership predicate as an example. State objects locked with the SimpleOwnership
have an “owner” field stored in object.data
. SimpleOwnership
defines a method that allows the current “owner” of a state object to specify a new owner:
function send(address _newOwner) public
The signature of this method is:
send(address)
In TypeScript we can compute the method ID as:
import { keccak256 } from 'js-sha3'
const methodId = keccak256('send(address)')
Rationale¶
We decided on this scheme for computing method signatures for several reasons.
Other languages, like Solidity and Vyper, define the method ID as the first 4 bytes of the keccak256 hash. One benefit of the 4-byte scheme is that it reduces the total amount of data on-chain. Unfortunately, this requires checking for any hash collisions between function names. For simplicity, therefore, we decided to use the full 32-byte hash. The additional required 28 bytes do not seem like a significant enough waste of gas to justify more complex collision-detection logic for predicates.
We also chose this system because keccak256
hashes are cheaply computable on Ethereum.
Test Vectors¶
Todo
Add test vectors for method identifiers.
Predicate API¶
Predicates MUST provide a Predicate API that allows a client to interact with the predicate. A Predicate API is composed of an array of API elements. Each API element describes a single function, including the function’s inputs and outputs. The structure of the API element has been based off of the Ethereum contract ABI specification.
TypeScript interfaces for valid Predicate API objects are provided below. Compare to the Ethereum ABI JSON format to understand similarities and differences.
interface PredicateApiInput {
name: string
type: string
}
interface PredicateApiOutput {
type: string
}
interface PredicateApiItem {
name: string
inputs: PredicateApiInput[]
outputs: PredicateApiOutput[]
constant: boolean
}
Example¶
We’re going to describe a valid Predicate API by looking at the SimpleOwnership predicate. SimpleOwnership
allows one valid state transition whereby the current owner of a state object may sign off on a new owner:
function send(address _newOwner) public
Note that this is not a constant
method because it will update the state of the predicate.
SimpleOwnership
also provides a method which returns the current owner:
funtion getOwner() public view returns (address)
This function is a constant
method because it only reads information and does not change the state of the object.
Putting these together, the API for this predicate is therefore:
[
{
name: "send",
constant: false,
inputs: [
{
name: "newOwner",
type: "address"
}
],
outputs: []
},
{
name: "getOwner",
constant: true,
inputs: [],
outputs: [
{
type: "address"
}
]
}
]
Rationale¶
Todo
Add rationale for Predicate API.
Requirements¶
Todo
Add requirements for Predicate API.
Transactions¶
Transaction Model¶
Mutations to state objects are carried out by transactions. Transactions specify:
- The ID of a state object to mutate.
- The ID of a method to call in the state object’s predicate.
- Parameters to be passed to the object’s predicate.
A TypeScript interface for a transaction:
interface Transaction {
objectId: string
methodId: string
parameters: string
}
A Solidity struct:
struct Transaction {
bytes objectId;
bytes32 methodId;
bytes parameters;
}
methodId
corresponds to the identifier computed from the Predicate API of the referenced object’s predicate contract.
Rationale¶
Todo
Add rationale for transaction model.
Requirements¶
Todo
Add requirements for transaction model.
Encoding and Decoding¶
Similarly, transactions MUST be ABI encoded and decoded in the form:
[ objectId: bytes, methodId: bytes, parameters: bytes ]``
Rationale¶
Todo
Add rationale for transaction encoding and decoding.
Requirements¶
Todo
Add requirements for transaction encoding and decoding.
Test Vectors¶
Todo
Add test vectors for transaction encoding and decoding.
State Updates¶
Todo
Explain state updates at a high level.
State Update Model¶
Todo
Specify the model for a state update.
State Object Ranges¶
Background¶
Our generalized state system introduces the idea of state objects, which can be used to control the ownership of assets deposited into the plasma chain. Plasma Cash describes a version of the generalized state system in which each state object represents an indivisible asset of fixed value. This system significantly reduces the total amount of data each user of the plasma client must store.
Arbitrary value payments become difficult to make within Plasma Cash. This is primarily because state objects in Plasma cash may not be split apart or combined. A user may only make payments of a given amount if they are in possession of a set of state objects such that the value of these state objects is exactly the payment amount. Compare this, for example, to the simplicity of making payments in Bitcoin-like systems where a UTXO can be arbitrarily broken apart into outputs with different values.
The following diagram shows the basic idea behind transactions in Plasma Cash:
Each of the eight shown transactions can only operate on one of the eight state objects. As a result, they have to be treated as separate objects that a user might have to keep track of.
One method of simplifying payments within such a system is to require that each state object to have an identical small value. Payments of any amount can then be made by sending many state objects simultaneously. In order to support simultaneous transfer of state object, we introduce mechanisms that allow users to reference ranges of state objects within transactions. This was the primary advancement of Plasma Cashflow.
Here’s a diagram of what this looks like in practice:
As you can see, we can manipulate all eight of the state objects with a single transaction. This means we can imagine the state objects as a single larger object, which reduces total storage requirements.
Transactions¶
The transaction format described in our generalized state system specifies that a transaction MUST provide a reference to the state object, or set of state objects, from which it spends. Conveniently, this allows us to create transactions over ranges of state objects.
The specification for a transaction over a range is very simple. Remember that each objectId
in our system is a unique 32 byte identifier. We therefore require that the objectIds
field be a 64 byte value. The first 32 bytes of this value represents the start of the transacted range and the last 32 bytes represents the end of the transacted range. Transactions over ranges are, in effect, transactions on each individual state object where object.id
falls within the specified range.
Rationale¶
As described in the background section above, we need to be able to efficiently transact many state objects simultaneously. By allowing transactions to refer to a set of state objects with a range, it’s no longer necessary to submit a transaction for each individual state object.
Requirements¶
- The
objectIds
field of every transaction: - MUST be a 64 byte value.
- MUST begin with a 32 byte value that represents that start of the transacted range.
- MUST end with a 32 byte value that represents the end of the transacted range.
- The
Merkle Interval Tree¶
Background¶
The Plasma Cashflow construction requires transactions that can efficiently reference ranges of state objects simultaneously. In order to preserve the properties of Plasma Cash, we require a Merkle tree structure that will not allow for the existence of two transactions that reference the same state object.
We provide a construction for such a tree, called a Merkle Interval Tree. This tree effectively commits to values that refer to corresponding ranges given by a start
and an end
. The tree provides the property that for any range, there can exist at most one leaf node that references the range and has a valid Merkle proof.
Tree Structure¶
The Merkle Interval Tree is a binary tree with special structures for leaf nodes and internal nodes.
Leaf Node¶
The leaf nodes in a Merkle Interval Tree represent a range and a value for that range. We describe leaf nodes as a tuple of (start, end, data)
.
In TypeScript:
interface MerkleIndexTreeLeafNode {
start: number
end: number
data: string
}
Internal Node¶
Internal nodes are a tuple of (index, hash)
.
In TypeScript:
interface MerkleIndexTreeInternalNode {
index: number
hash: string
}
Tree Generation¶
A Merkle Index tree is generated from a list of leaf nodes. Merkle Interval Tree generation also requires the use of a hash function. Any hash function is suitable, but the security properties of the hash function will impact the properties of the tree.
An algorithm for generating the tree is described below. All lists used are zero-indexed.
The algorithm takes two inputs, a list of leaf nodes and a hash function.
- If the list of leaf nodes is empty, return an empty array.
- Assert that the range described by
start
andend
of each leaf node does not intersect with the range described by any other leaf node. If any intersecting ranges exist, throw an error. - Sort the list of leaf nodes by their
start
value. - Store the list of leaf nodes as the first layer of the tree.
- Generate a corresponding sorted list of internal nodes from the leaf nodes by creating an internal for each leaf node such that:
- The
index
of the node is equal to thestart
of the leaf node. - The
hash
of the node is equal to the hash of the concatenation ofstart
,end
, anddata
of the leaf node, in that order.
- The
- Recursively generate the rest of the tree as follows:
- Store the list of internal nodes at the current height of the tree.
- If the list of internal nodes has only one element, return.
- Pair each node in the list of internal nodes such that any node where
node_index % 2 = 0
is paired with the node atnode_index + 1
. If the node atnode_index + 1
does not exist, pair the node with a new node such thatpair.index = node.index
andpair.hash = 0
. - Generate a list of parent nodes. For each pair of nodes, create a corresponding parent node such that:
parent.index = left_child.index
andparent.hash
is the hash of the concatenation ofleft_child.index
,left_child.hash
,right_child.index
,right_child.hash
, in that order.
- Repeat this process for the generated list of parent nodes.
- Return the generated tree.
Pseudocode¶
A pseudocode version of the above algorithm is given below:
def generate_tree(leaf_nodes, hash_function):
tree = []
# Empty tree
if len(leaf_nodes) == 0:
return tree
# Leaves intersect
for leaf in leaf_nodes:
for other in leaf_nodes:
if (intersects(leaf, other)):
raise Exception()
# Sort the leaves by start value
leaf_nodes.sort()
children = []
for leaf in leaf_nodes:
children.append({
'index': leaf.start,
'hash': hash_function(leaf.start + leaf.end + leaf.data)
})
def generate_internal_nodes(children, tree):
if len(children) == 1:
return tree
parents = []
for x in range(0, len(children)):
if x % 2 == 0:
left_child = chilren[x]
# Create an imaginary node if out of bounds
if x + 1 == len(children):
right_child = {
'index': left_child.index,
'hash': 0
}
else:
right_child = children[x + 1]
parents.append({
'index': left_child.index,
'hash': hash_function(left_child.index + left_child.hash + right_child.index + right_child.hash)
})
tree.append(parents)
return generate_internal_nodes(parents, tree)
Merkle Proofs¶
Our tree generation process allows us to create an efficient proof that for a given leaf node and a given Merkle Interval Tree root node such that:
- The leaf node was contained in the tree that generated the root.
- The range described by the leaf node intersects with no other ranges described by any other leaf node in the tree.
Proof Generation¶
Proofs can be generated after the full Merkle tree has been generated as per the algorithm described above. Proofs consist of a list of internal nodes within the Merkle tree.
The proof for a given leaf node is computed as follows:
- If the leaf node is not in the tree, throw an error.
- Find the internal node that corresponds to the leaf node in the bottom-most level of the tree.
- Recursively:
- If the internal node is the root node, return.
- Find the sibling of the node. If no sibling exists, set the sibling to the empty node such that
sibling.index = node.index
andsibling.hash = 0
. - Insert the sibling into the proof.
- Repeat this process with the parent of the node.
- Return the proof.
Pseudocode¶
def generate_proof(tree, leaf_node):
leaves = tree[0]
if leaf_node not in leaves:
raise Exception()
leaf_index = leaves.index(leaf_node)
return find_siblings(tree, 1, leaf_index, [])
def find_siblings(tree, height, child_index, proof):
if height == len(tree):
return proof
proof.append(get_sibling(child_index))
parent_index = get_parent_index(child_index)
return find_siblings(tree, height + 1, parent_index, proof)
Proof Verification¶
Verification of Merkle Interval Tree proofs is relatively straightforward. Given a leaf node, the index of that leaf node within the Merkle tree, a proof consisting of a list internal nodes, and the root of the tree:
- Compute the internal node that corresponds to the leaf node such that
node.index = leaf.start
andnode.hash
is the hash of the concatenation ofleaf.start
,leaf.end
, andleaf.data
, in that order. - For each element of the proof:
- Use the index of the leaf node to determine whether the element is a left or right sibling of the current internal node.
- Compute the parent of the two siblings.
- Set the current internal node to be the parent.
- Check if the current internal node is equal to the root node.
Pseudocode¶
def check_proof(leaf_node, leaf_index, proof, root_node, hash_function):
current_node = {
'index': leaf_node.start,
'hash': hash_function(leaf_node.start + leaf_node.end + leaf_node.data)
}
for x in range(0, len(proof)):
sibling = proof[x]
if is_left_sibling(leaf_index, x):
current_node = compute_parent(sibling, current_node)
else:
current_node = compute_parent(current_node, sibling)
return current_node == root_node
Tree Diagram¶
A diagram of the Merkle Interval Tree is provided below. We’ve highlighted the nodes that one would need to provide to prove inclusion of a given state update.
Double Layer Merkle Tree¶
Background¶
The amount of data a user needs to store in a Plasma Cash chain is linearly proportional to the number of state objects that user “owns” within the chain. Plasma Cashflow, however, relies on the concept of ranges of state objects in order to add a sense of fungibility to Plasma Cash. Users of a Plasma Cashflow chain instead store an amount of data linearly proportional to the number of ranges they own, not the number of individual state objects within those ranges.
The fewer ranges a user needs to track, the less data they need to store. It’s for this reason that we need some sort of mechanism for defragmenting ranges. A detailed explanation of the defragmentation process is described outside of this page.
The basic idea behind defragmentation is that contiguous ranges can be “merged” into a single larger range. Users can execute atomic swaps with others users in order to trade disconnected ranges for contiguous ones. This process is relatively straightforward when the assets underlying the ranges are identical – trading 1 ETH for 1 ETH carries no risk. However, the process is significantly more complex if the underlying assets are different.
We can ensure that the underlying asset will always be the same by reserving some space for the asset. One way to accomplish this is to modify the ID of a state object with a unique prefix for each asset. For example, the range of state object IDs for ETH might be prefixed with a zero ((0)(0 - 2^256)
) while the range of IDs for WETH might be prefixed with a one ((1)(0 - 2^256)
). This scheme makes sure that the ranges next to yours will always have the same underlying asset.
Another way to achieve the same result is simply to have a different Plasma Cashflow chain for each asset. We do so by creating a unique deposit contract for each asset. The prefix for a range becomes the address of the deposit contract that corresponds to the asset.
However, this means that we’re simultaneously running a lot of plasma chains. We don’t want to have to submit a block root for each individual chain. We get around this by creating a double-layered Merkle Interval Tree. This page explains how the double-layered tree is computed.
State Tree¶
Todo
Explain the purpose and structure of the state tree.
Address Tree¶
Todo
Explain the purpose and structure of the address tree.
Tree Diagram¶
We’ve provided a diagram of the double-layer Merkle Interval Tree below. The diagram explains the relationship between the state updates that form the tree and the tree itself.
JSON RPC¶
Description¶
We require that the clients via JSON RPC. A list of available RPC methods are specified separately.
We chose JSON RPC because the protocol is relatively simple and because it interacts well with TypeScript. Ethereum also uses JSON RPC for its client communication. The structure of our RPC methods are based heavily on the structure of Ethereum’s RPC methods to reduce cognitive overhead for client developers.
Data Structures¶
JsonRpcRequest¶
interface JsonRpcRequest {
jsonrpc: '2.0'
method: string
params?: any
id: string | number
}
Description¶
Represents a JSON RPC request.
Fields¶
jsonrpc
-string
: MUST be exactly ‘2.0’.method
-string
: Name of the method to call.params?
-any
: Formatted request parameter object.id
-string | number
: Request identifier.
JsonRpcSuccessResponse¶
interface JsonRpcSuccessResponse {
jsonrpc: '2.0'
result: any
id: string | number
}
Description¶
Represents a successful JSON RPC response.
Fields¶
jsonrpc
-string
: MUST be exactly ‘2.0’.result
-any
: Formatted response to the request.id
-string | number
: Same identifier as the one given during the request.
JsonRpcError¶
interface JsonRpcError {
code: number
message: string
data: any
}
Description¶
Represents information about a JSON RPC error.
Fields¶
code
-number
: Error code ID.message
-string
: Additional short error message.data
-any
: Additional error information.
JsonRpcErrorResponse¶
interface JsonRpcErrorResponse {
jsonrpc: '2.0'
error: JsonRpcError
}
Description¶
Represents a JSON RPC error response to request.
Fields¶
jsonrpc
-string
: MUST be exactly ‘2.0’.error
-JsonRpcError
: RPC error object.
Contracts Overview¶
Introduction¶
The PG contract architecture consists of three modular components which serve as the basis for creating plasma networks. These are:
Commitment contracts - where aggregators submit blocks, and where deposit contracts verify inclusion of State Updates. Deposit contracts - where users deposit money. Enforces the basic plasma exit game to guarantee safety. Predicate contracts - communicate with the deposit contract to extend the basic plasma exit game for particular applications (plapps).
Commitment Contract¶
The commitment contract defines a single public address aggregatorwhich can submit new blocks, and records the blocks in storage for later use. It also provides two functions, verifyAssetStateRootInclusion and verifyStateUpdateInclusion, which may be used by both the predicate and deposit contracts to authenticate block contents.
Deposit Contract¶
The deposit contract contains the base plasma logic for keeping funds secure. It allows users to deposit funds into it, and only allows withdrawals after an “exit game” has been passed. It provides a standard interface which predicate contracts may access to enforce their particular exit game details.
Predicate Contracts¶
Predicate contracts implement the specific logic of an exit game, and by extent define the transition logic for that type of state object. Most importantly, they dictate the rules of deprecation – the conditions by which an exit on the given state can be cancelled. For example, the ownership predicate allows an exit to be deprecated if the exiting owner has made a signature sending to another party.
Deposit Contract¶
The deposit contract custodies users’ funds and enforces the plasma cash exit game on them. The exit game we have chosen is a modification of the exit games first proposed by Lucidity (https://ethresear.ch/t/luciditys-plasma-cash-easy-and-more-efficient/4029/8) and Dan Robinson (https://ethresear.ch/t/a-simpler-exit-game-for-plasma-cash/4917) to work on ranges of coins (cashflow).
Coin Fork Choice Rule¶
The simplest way to understand the rules the exit game enforces are as follows: for a given coin, the exitable state is that of its earliest state update which cannot be deprecated.
Thus, there are two conditions which a particular state update on a particular must satisfy to be exited. First, it must have no undeprecated state updates in its history. Second, it must itself not be deprecated. We have split these conditions into two distinct games.
Exits¶
Exits are the game for the second condition above: they represent a claim that some subrange of a state update is not deprecated. At any time, the predicate contract for that state update is allowed to make a call, deprecateExit, which cancels it. Only the predicate for that state update is authenticated to make the call. An exit points at a particular checkpoint.
Checkpoints¶
Checkpoints are the game for the first condition above: they represent a claim that all prior state updates must be deprecated for the range being exited. Finalization of a checkpoint means that the contract will strictly enforce this, disallowing any exits on a coin which come from an earlier plasma block in which a checkpoint on that coin is finalized.
Challenges¶
Creating a checkpoint on a state object requires an inclusion proof for a state update over that object. As such, if a state update is included by the aggregator without a previous state being deprecable, this is a malicious action which forces the user to exit. If the aggregator begins a checkpoint on the malicious state update, it may be blocked by a challenge. A challenge points at the earlier exit and a later intersecting checkpoint. That checkpoint is considered invalid unless the earlier exitobject can be deprecated, removing the challenge.
Deposit Contract¶
Description¶
Deposit contracts are the Ethereum smart contracts into which assets are deposited–custodying the money as it is transacted on plasma and playing out the exit games to resolve the rightful owners of previously deposited assets. As such, it contains the bulk of the logic for the plasma exit games. The things it does not cover are 1) block commitments, and 2), state deprecation, which are handled by calls to the commitment contract and predicate contracts specifically.
API¶
Structs¶
Range¶
struct Range {
uint256 start;
uint256 end;
}
Description¶
Represents a range of state objects.
Fields¶
start
-uint256
: Start of the range of objects.end
-uint256
: End of the range of objects.
StateObject¶
struct StateObject {
string id;
address predicate;
bytes data;
}
Description¶
Represents a state object. Contains the address of the predicate contract and input data to that contract which control the conditions under which the object may be mutated.
Fields¶
id
-string
: A globally unique identifier for this state object.predicateAddress
-address
: Address of the predicate contract that dictates how the object can be mutated.data
-bytes
: Arbitrary state data for the object.
StateUpdate¶
struct StateUpdate {
Range range;
StateObject stateObject;
address plasmaContract;
uint256 plasmaBlockNumber;
}
Description¶
Represents a state update, which contains the contextual information for how a particular range of state objects was mutated.
Fields¶
range
-Range
: Range of state objects that were mutated.stateObject
-StateObject
: Resulting state object created by the mutation of the input objects.plasmaContract
-address
: Address of the plasma contract in which the update was included.plasmaBlockNumber
-uint256
: Plasma block number in which the update occurred.
Checkpoint¶
struct Checkpoint {
StateUpdate stateUpdate;
Range subRange;
}
Description¶
Represents a checkpoint of a particular state update on which a “checkpoint game” is being or has been played out. Checkpoints which have successfully passed the checkpoint game are considered “finalized”, meaning the plasma contract should ignore all state updates on that range with an older plasma block number.
Fields¶
stateUpdate
-StateUpdate
: State update being checkpointed.subRange
-Range
: Sub-range of the state update being checkpointed. We include this field because the update may be partially spent.
CheckpointStatus¶
struct CheckpointStatus {
uint256 challengeableUntil;
uint256 outstandingChallenges;
}
Description¶
Status of a particular checkpoint attempt.
Fields¶
challengeableUntil
-uint256
: Ethereum block number until which the checkpoint can still be challenged.outstandingChallenges
-uint256
: Number of outstanding challenges.
Challenge¶
struct Challenge {
Checkpoint challengedCheckpoint;
Checkpoint challengingCheckpoint;
}
Description¶
Describes a challenge against a checkpoint. A challenge is a claim that the challengingCheckpoint
has no valid transactions, meaning that the state update in the challengedCheckpoint
could never have been reached and thus is invalid.
Fields¶
challengedCheckpoint
-Checkpoint
: Checkpoint being challenged.challengingCheckpoint
-Checkpoint
: Checkpoint being used to challenge.
Public Variables¶
COMMITMENT_ADDRESS¶
address constant COMMITMENT_ADDRESS;
Description¶
Address of the commitment contract where block headers for the plasma chain are being published.
Requirements¶
Deposit contracts MUST specify the address of a commitment contract where plasma chain block headers are being published.
Rationale¶
Deposit contracts handle deposits and exits from a specific plasma chain. Commitment contracts hold the plasma block headers for that plasma chain and therefore make it possible to verify inclusion proofs.
TOKEN_ADDRESS¶
address constant TOKEN_ADDRESS;
Description¶
Address of the ERC-20 token which this deposit contract custodies.
Requirements¶
- The deposit contract:
- MUST only support deposits of a single ERC-20 token.
TOKEN_ADDRESS
:- MUST be the address of an ERC-20 token.
Rationale¶
Each asset type needs to be allocated its own large contiguous “sub-range” within the larger Plasma Cashflow chain. Without these sub-ranges, defragmentation becomes effectively impossible. Although it’s possible to achieve this result within a single deposit contract, it’s easier to simply require that each asset have its own deposit contract and to allocate a large sub-range to every deposit contract.
CHALLENGE_PERIOD¶
uint256 constant CHALLENGE_PERIOD;
Description¶
Number of Ethereum blocks for which a checkpoint may be challenged.
EXIT_PERIOD¶
uint256 constant EXIT_PERIOD;
Description¶
Number of Ethereum blocks before an exit can be finalized.
totalDeposited¶
uint256 public totalDeposited;
Description¶
Total amount deposited into this contract.
checkpoints¶
mapping (bytes32 => CheckpointStatus) public checkpoints;
Description¶
Mapping from the ID of a checkpoint to the checkpoint’s status.
depositedRanges¶
mapping (uint256 => Range) public depositedRanges;
Description¶
Stores the list of ranges that have not been exited as a mapping from the start
of a range to the full range. Prevents multiple exits from the same range of objects.
exitRedeemableAfter¶
mapping (bytes32 => uint256) public exitRedeemableAfter;
Description¶
Mapping from the ID of an exit to the Ethereum block after which the exit can be finalized.
challenges¶
mapping (bytes32 => bool) public challenges;
Description¶
Mapping from the ID of a challenge to whether or not the challenge is currently active.
Events¶
CheckpointStarted¶
event CheckpointStarted(
Checkpoint checkpoint,
uint256 challengeableUntil
);
Description¶
Emitted whenever a user attempts to checkpoint a state update.
Fields¶
checkpoint
-bytes32
: ID of the checkpoint that was started.challengeableUntil
-uint256
: Ethereum block in which the checkpoint was started.
CheckpointChallenged¶
event CheckpointChallenged(
Challenge challenge
);
Description¶
Emitted whenever an invalid history challenge has been started on a checkpoint.
CheckpointFinalized¶
event CheckpointFinalized(
bytes32 checkpoint
);
Description¶
Emitted whenever a checkpoint is finalized.
Fields¶
checkpoint
-bytes32
: ID of the checkpoint that was finalized.
ExitStarted¶
event ExitStarted(
bytes32 exit,
uint256 redeemableAfter
);
Description¶
Emitted whenever an exit is started.
Fields¶
exit
-bytes32
: ID of the exit that was started.redeembleAfter
-uint256
: Ethereum block in which the exit will be redeemable.
ExitFinalized¶
event ExitFinalized(
Checkpoint exit
);
Description¶
Emitted whenever an exit is finalized.
Fields¶
exit
-Checkpoint
: The checkpoint that had its exit finalized.
Methods¶
deposit¶
function deposit(
address _depositer,
uint256 _amount,
StateObject _initialState
) public
Description¶
Allows a user to submit a deposit to the contract. Only allows users to submit deposits for the asset represented by this contract.
Parameters¶
_depositer
-address
: the account which has approved the ERC20 deposit._amount
-uint256
: Amount of the asset to deposit._initialState
-StateObject
: Initial state to put the deposited assets into. Can be any valid state object.
Requirements¶
- MUST keep track of the total deposited assets,
totalDeposited
. - MUST transfer the deposited
amount
from thedepositer
to the deposit contract’s address. - MUST create a state update with a state object equal to the provided
initialState
. - MUST compute the range of the created state update as
totalDeposited
tototalDeposited + amount
. - MUST update the total amount deposited after the deposit is handled.
- MUST insert the created state update into the
checkpoints
mapping withchallengeableUntil
being the current block number - 1. - MUST emit a
CheckpointFinalized
event for the inserted checkpoint.
Rationale¶
Depositing is the mechanism which locks an asset into the plasma escrow agreement, allowing it to be transacted off-chain. The initialState
defines its spending conditions, in the same way that a StateUpdate
does once further transactions are made. Because deposits are verified on-chain transactions, they can be treated as checkpoints which are unchallengeable.
startCheckpoint¶
function startCheckpoint(
Checkpoint _checkpoint,
bytes _inclusionProof,
uint256 _depositedRangeId
) public
Description¶
Starts a checkpoint for a given state update.
Parameters¶
_checkpoint
-Checkpoint
: Checkpoint to be initiated._inclusionProof
-bytes
: Proof that the state update was included in the block specified within the update._depositedRangeId
-uint256
: The key in thedepositedRanges
mapping which includes thesubRange
as a subrange.
Requirements¶
- MUST verify the that
checkpoint.stateUpdate
was included withinclusionProof
. - MUST verify that
subRange
is actually a sub-range ofstateUpdate.range
. - MUST verify that the
subRange
is still exitable with thedepositedRangeId
. - MUST verify that an indentical checkpoint has not already been started.
- MUST add the new pending checkpoint to
checkpoints
withchallengeableUntil
equalling the current ethereumblock.number + CHALLENGE_PERIOD
. - MUST emit a
CheckpointStarted
event.
Rationale¶
Checkpoints are assertions that a certain state update occured/was included, and that it has no intersecting unspent state updates in its history. Because the operator may publish an invalid block, it must undergo a challenge period in which the parties who care about the unspent state update in the history exit it, and use it to challenge the checkpoint.
deleteExitOutdated¶
function deleteExitOutdated(
Checkpoint _olderExitt,
Checkpoint _newerCheckpoint
) public
Description¶
Deletes an exit by showing that there exists a newer finalized checkpoint. Immediately cancels the exit.
Parameters¶
_olderExitt
-Checkpoint
: `The exit`_ to delete._newerCheckpoint
-Checkpoint
: The checkpoint used to challenge.
Requirements¶
- MUST ensure the checkpoint ranges intersect.
- MUST ensure that the plasma blocknumber of the
_olderExitt
is less than that of_newerCheckpoint
. - MUST ensure that the
newerCheckpoint
has no challenges. - MUST ensure that the
newerCheckpoint
is no longer challengeable. - MUST delete the entries in
exitRedeemableAfter
.
Rationale¶
If a checkpoint game has finalized, the safety property should be that nothing is valid in that range’s previous blocks–”the history has been erased.” However, since there still might be some StateUpdates
included in the blocks prior, invalid checkpoints can be initiated. This method allows the rightful owner to demonstrate that the initiated olderCheckpoint
is invalid and must be deleted.
challengeCheckpoint¶
function challengeCheckpoint(
Challenge _challenge
) public
Description¶
Starts a challenge for a checkpoint by pointing to an exit that occurred in an earlier plasma block. Does not immediately cancel the checkpoint. Challenge can be blocked if the exit is cancelled.
Parameters¶
_challenge
-Challenge
: Challenge to submit.
Requirements¶
- MUST ensure that the checkpoint being used to challenge exists.
- MUST ensure that the challenge ranges intersect.
- MUST ensure that the checkpoint being used to challenge has an older
plasmaBlockNumber
. - MUST ensure that an identical challenge is not already underway.
- MUST ensure that the current ethereum block is not greater than the
challengeableUntil
block for the checkpoint being challenged. - MUST increment the
outstandingChallenges
for the challenged checkpoint. - MUST set the
challenges
mapping for thechallengeId
to true.
Rationale¶
If the operator includes an invalid StateUpdate
(i.e. there is not a deprecation for the last valid StateUpdate
on an intersecting range), they may checkpoint it and attempt a malicious exit. To prevent this, the valid owner must checkpoint their unspent state, exit it, and create a challenge on the invalid checkpoint.
removeChallenge¶
function removeChallenge(
Challenge _challenge
) public
Description¶
Decrements the number of outstanding challenges on a checkpoint by showing that one of its challenges has been blocked.
Parameters¶
_challenge
-Challenge
: `The challenge`_ that was blocked.
Requirements¶
- MUST check that the challenge was not already removed.
- MUST check that the challenging exit has since been removed.
- MUST remove the challenge if above conditions are met.
- MUST decrement the challenged checkpoint’s
outstandingChallenges
if the above conditions are met.
Rationale¶
Anyone can exit a prior state which was since spent and use it to challenge despite it being deprecated. To remove this invalid challenge, the challenged checkpointer may demonstrate the exit is deprecated, deleting it, and then call this method to remove the challenge.
startExit¶
function startExit(Checkpoint _checkpoint) public
Description¶
Allows the predicate contract to start an exit from a checkpoint. Checkpoint may be pending or finalized.
Parameters¶
_checkpoint
-Checkpoint
: The checkpoint from which to exit.
Requirements¶
- MUST ensure the checkpoint exists.
- MUST ensure that the
msg.sender
is the_checkpoint.stateUpdate.predicateAddress
to authenticate the exit’s initiation. - MUST ensure an exit on the checkpoint is not already underway.
- MUST set the exit’s
redeemableAfter
status to the current Ethereumblock.number + LOCKUP_PERIOD
. - MUST emit an
exitStarted
event.
Rationale¶
For a user to redeem state from the plasma chain onto the main chain, they must checkpoint it and respond to all challenges on the checkpoint, and await a LOCKUP_PERIOD
to demonstrate that the checkpointed subrange has not been deprecated. This is the method which starts the latter process on a given checkpoint.
deprecateExit¶
function deprecateExit(
Checkpoint _checkpoint
) public
Description¶
Allows the predicate address to cancel an exit which it determines is deprecated.
Parameters¶
_checkpoint
-Checkpoint
: The checkpoint referenced by the exit.
Requirements¶
- MUST ensure the
msg.sender
is the_checkpoint.stateUpdate.predicateAddress
to ensure the deprecation is authenticated. - MUST delete the exit from
exitRedeemableAfter
at thecheckpointId
.
Rationale¶
If a transaction exists spending from a checkpoint, the checkpoint may still be valid, but an exit on it is not. This method allows the predicate to remove the exit if it has determined it to be outdated.
finalizeExit¶
function finalizeExit(Checkpoint _exit, uint256 _depositedRangeId) public
Description¶
Finalizes an exit that has passed its exit period and has not been successfully challenged.
Parameters¶
_exit
-Checkpoint
: The checkpoint on which the exit is not finalizable._depositedRangeId
-uint256
: the entry indepositedRanges
demonstrating the range is not yet exited.
Requirements¶
- MUST ensure that the exit finalization is authenticated from the predicate by
msg.sender == _exit.stateUpdate.state.predicateAddress
. - MUST ensure that the checkpoint is finalized (current Ethereum block exceeds
checkpoint.challengeableUntil
). - MUST ensure that the checkpoint’s
outstandingChallenges
is 0. - MUST ensure that the exit is finalized (current Ethereum block exceeds
redeemablAfter
). - MUST ensure that the checkpoint is on a subrange of the currently exitable ranges via
depositedRangeId
. - MUST make an ERC20 transfer of the
end - start
amount to the predicate address. - MUST delete the exit.
- MUST remove the exited range by updating the
depositedRanges
mapping. - MUST delete the checkpoint.
- MUST emit an
exitFinalized
event.
Rationale¶
Exit finalization is the step which actually allows the assets locked in plasma to be used on the main chain again. Finalization requires that the exit and checkpoint games have completed successfully.
Commitment Contract¶
Description¶
Each plasma chain MUST have at least one commitment contract. Commitment contracts hold the block headers for the plasma chain. Whenever the operator creates a new plasma block, they MUST publish this block to the commitment contract.
API¶
Events¶
BlockSubmitted¶
event BlockSubmitted(
uint256 _number,
bytes _header
);
Description¶
Emitted whenever a new block root has been published.
Fields¶
_number
-uint256
: Block number that was published._header
-bytes
: Header for that block.
Rationale¶
Users need to know whenever a new block has been published so that they can stay in sync with the operator.
Public Variables¶
currentBlock¶
uint256 public currentBlock;
Description¶
Block number of the most recently published plasma block.
Rationale¶
Users need to know the current plasma block for various operations. Contract also needs to keep track of this so it knows what block is being published when submitBlock
is called.
blocks¶
mapping (uint256 => bytes) public blocks;
Description¶
Mapping from block number to block header.
Rationale¶
It’s often important to be able to pull a specific block header given a block number. This is necessary, for example, when verifying `inclusion proofs`_.
Other implementations often represent this mapping as uint256 -> bytes32
under the assumption that the block header will always be a bytes32
Merkle tree root. We instead represent the mapping as uint256 -> bytes
for more flexibility in the structure of the block root.
Methods¶
submitBlock¶
function submitBlock(bytes _header) public
Description¶
Allows a user to submit a block with the given header.
Parameters¶
_header
-bytes
: Block header to publish.
Rationale¶
It’s obviously necessary to expose some functionality that allows a user to submit a block header. However, the rationale around authentication logic is more interesting here.
Authentication in our original construction was handled by checking that msg.sender was the operator. This works well in a single-operator construction, but it doesn’t work if we wanted some more complex system. In order to solve this problem, we initinally wanted to add a witness: bytes
parameter to the method which could then be used to authenticate the submitted header. Fortunately, we stumbled on an even better solution.
Conveniently, if a contract calls another contract, then msg.sender within that second contract will be the address of the first contract. We can therefore outsource verification of a given block to some external contract and simply check that msg.sender
is that contract.
Requirements¶
- SHOULD authenticate the block header in some manner.
- MUST increment
currentBlock
by one. - MUST store the block header in
blocks
atcurrentBlock
. - MUST emit a
BlockSubmitted
event.
Predicate Contracts¶
Description¶
Predicate contracts define the rules for particular state objects’ exit game. The most fundamental thing they define is the deprecation logic, which informs the plasma contract that an exit on some state is invalid because it is outdated. Usually, this logic revolves around proving to the predicate that some transaction has invalidated a previous exit. Because the predicate contract is a stateful main-chain contract, more advanced predicates can also define custom exit logic which must be evaluated before any state transitions are approved by the predicate. Thus, predicates can be used as fully customized extensions to the base plasma cash exit game.
Deprecation¶
The foremost thing a predicate accomplishes is to enable the deprecation of its state, so that a new state included by the operator becomes valid. This enables state transitions to occur. Because the deposit contract only allows an exit’s predicateAddress
to call deprecateExit
, the predicate must enable some function which leads to a subcall on depositContract.deprecateExit
.
Exit Initiation¶
Just like the msg.sender
check by the deposit contract for deprecations, only the predicate is allowed to begin an exit for a given checkpoint. Thus, the predicate must provide some method which leads to a subcall on depositContract.beginExit
. This enables the predicate to prevent unwanted exits, e.g. by only allowing the current owner to start an exit.
Exit Finalization¶
If the predicate contract has custom/stateful exit logic, it may not know who to send money to even after the standard plasma exit period has elapsed. Thus, we require that the msg.sender == predicateAddress
for a given exit to be finalized and funds to be released. This requires a call by the predicate to the depositContract.finalizeExit
.
These are the only real requirements for a predicate contract to be compatible with the deposit contract spec. However, it lacks a useful abstraction usually made by blockchains and plasma implementations: the notion of transactions and state transitions. In the next section, we define a standard predicate base which is used throughout the rest of the spec, that treats deprecations, exits, etc. in terms of transactions and state transitions.
Transaction-based Predicate Standard¶
Description¶
Most developers are more familiar with a transaction-based model of cryptocurrencies, where state transitions as the result of transactions form the basis for computation. Our client and operator implementations also work this way–it’s really a great model! The transaction-based predicate standard helps us to do this cleanly, by providing a wrapper which interprets transactions in the context of deprecation and disputes.
In essence, the way to enable this is: if a transaction from one state has been authenticated (e.g. signed by its single owner or signed by the multiple participants of a multisig), it must be deprecable using that transaction.
Transaction Execution¶
verifyTransaction¶
function verifyTransaction(
StateUpdate _preState,
Transaction _transaction,
bytes _witness,
StateUpdate _postState
) public
Description¶
The main thing that must be defined for a state transition model is this verifyTransaction
function which accepts a preState
state update, and verifies against a transaction
and witness
that a given postState
is correct.
Parameters¶
_preState
-StateUpdate
: the state update which the transaction is being applied on._transaction
-Transaction
: The transaction being applied. Follows the standard format as outlined in the transaction generation page in Section #03._witness
-bytes
: Additional witness data which authenticates the transaction validity, e.g. a signature. Defined on a per-predicate basis._postState
-StateUpdate
: the output of the transaction to be verified.
Requirements¶
- Predicates MUST define a custom
_witness
struct for their particular type of state. - Predicates MUST disallow state transitions which pass verification without some interested party’s consent, e.g. the owner’s signature
Deprecation¶
proveDeprecation¶
function proveExitDeprecation(
Checkpoint _deprecatedExit,
Transaction _transaction,
bytes _witness,
StateUpdate _postState
) public
Description¶
If a state transition away from a given state update exists, then it is not valid to exit that state–it should be deprecated! This function allows a user to demonstrate this to the predicate so that it may cancel an exit
Parameters¶
_deprecatedExit
-Checkpoint
: the deprecated checkpoint being exited._transaction
-Transaction
: The transaction which deprecates the exit. Follows the standard format as outlined in the transaction generation page in Section #03._witness
-bytes
: Additional witness data which authenticates the transaction validity, e.g. a signature. Defined on a per-predicate basis._postState
-StateUpdate
: the output of the transaction to be verified.
Requirements¶
- MUST check that the transaction is valid with a call to
verifyTransaction(_deprecatedExit.stateUpdate, _transaction, _witness, _postState
. - MUST check that the
_postState.range
intersects the_deprecatedExit.subrange
- MUST call
deprecateExit(_deprecatedExit)
on the_deprecatedExit.stateUpdate.state.predicateAddress
.
Limbo Exit Predicate Standard¶
Explanation¶
An important attack on plasma systems is for the operator to start withholding blocks after they have been sent a transaction. For instance, if Alice signs off agreeing to send to Bob, but the operator does not reveal the corresponding Bob ownership StateUpdate
, then there is insufficient information to exit because Alice’s exit can be deprecated by her signature, but Bob doesn’t have an inclusion proof to exit.
One solution is confirmation signatures, in which Alice does not create a signature until she sees inclusion of the Bob ownership state. However, this has bad UX properties–for one, Alice has to wait until the next block before she can sign, which means she cannot simply send a signature and go offline. Further, it requires an out-of-protocol authentication process: how does the operator know to include the Bob ownership before Alice has signed?
Limbo exits are a better way to solve this problem. They allow Alice to sign a transaction before the block is submitted, go offline, and have Bob receive the coin at a later time, without reducing safety or extending the exit period.
Intuitively, limbo exits work by allowing Alice to exit her ownership state on Bob’s behalf. A limbo-compatible ownership predicate allows Alice to exit her ownership state “to Bob.” Once she does this, the finalizeExit
call will send the money to Bob instead of Alice. So, if Alice agrees to a limbo exit, it cannot be deprecated by her spend to Bob.
There are two ways to deprecate a limbo exit. One is to show a conflicting spend other than the one the limbo exit claims to be valid: for example, if Alice limbo exits to Bob, this can be deprecated by providing an alternate spend, e.g. from Alice to Carol. The other way is to show that the limbo “target” is itself deprecated: for example, if Alice sends to Bob, and Bob sends to Carol, a limbo exit from Alice to Bob may be cancelled by showing Bob’s transaction to Carol.
Note that this all means limbo exits have a stronger notion of state than the deposit contract requires–instead of just dealing with deprecation, they must have a notion of transactions and state transitions. Luckily, this is just generally a great idea–Plasma Group will support transaction and limbo functionality across all predicates we create. This page will document the standard interface which predicates supporting limbo functionality must follow, so that it doesn’t have to be reecreated for different predicates.
API¶
Structs¶
LimboStatus¶
struct LimboStatus {
bytes32 targetId;
bool wasReturned;
}
Description¶
Represents the status of a limbo exit.
Fields¶
targetId
-bytes32
: Hash of the targetStateUpdate
being limbo exited “to”.wasReturned
-bool
: Whether the state being limbo exited to cooperatively agreed to return the limbo exit back to the limbo source.
Public Variables¶
limboTargets¶
mapping (bytes32 => LimboStatus) public limboExits;
Description¶
This mapping maps the limbo exit IDs to their current status.
Rationale¶
This is the storage on which limbo exit logic is based. If Alice is limbo exiting to Bob, we record the Bob ownership in targetId
field, and if Bob cooperatively decides to return the outcome of the exit to Alice, we store that in wasReturned
.
Events¶
CheckpointStarted¶
event LimboTargeted(
Checkpoint limboExitSource,
StateUpdate limboExitTarget
);
Description¶
Emitted whenever a deposit contract exit is targeted as a
Fields¶
limboExitSource
-Checkpoint
: The exit being converted into a limbo exit.limboExitTarget
-StateUpdpate
: The “target” of a limbo exit. A transaction resulting in thelimboExitTarget
cannot be used to deprecatelimboExitSource
once this event has been emitted.
Methods¶
targetLimboExit¶
targetLimboExit(originCheckpoint, transaction, target)
function targetLimboExit(
Checkpoint _sourceExit,
Transaction _transaction,
bytes _witness
StateUpdate _limboTarget
) public
Description¶
Allows a user to convert a normal exit into a limbo exit by “targeting” a transaction which they made but cannot prove inclusion of.
Parameters¶
_sourceExit
-Checkpoint
: the exit being converted into a limbo exit_transaction
-Transaction
: The transaction from the_sourceExit.stateUpdate
._witness
-bytes
: the witness which proves the transaction validity._limboTarget
-StateUpdate
: the output of the transaction being verified.
Requirements¶
- MUST ensure that the transaction is valid by calling
verifyTransaction(_sourceExit.stateUpdate, _transaction, _witness, _limboTarget
. - MUST ensure the
_limboTarget.range
is a subrange of the_sourceExit.subRange
. - MUST ensure the
_sourceExit
has not already been made a limbo exit. - MUST call
onTargetedForLimboExit(_sourceExit, _target)
on the_target
predicate. - MUST set the
limboTargets
mapping with a key of the ID of_sourceExit
and value ofhash(_limboTarget)
- MUST emit a
LimboTargeted
event.
Justification¶
This is the base function which converts a regular exit into a limbo exit. Both the source and target predicates must support the limbo interface outlined here for it to work. For example, if Alice limbo exits to a Bob and Carol multisig, she exits her ownership, then limbo targets the mutisig with her transaction. This is because the ownership targetLimboExit
method subcalls the onTargetedForLimboExit
of the mutisig.
Functions
onTargetedForLimboExit¶
function onTargetedForLimboExit(
Checkpoint _sourceExit,
StateUpdate _limboTarget
) public
Description¶
Hook allowing for the target predicate to initiate any custom logic needed for stateful limbo exits.
Parameters¶
_sourceExit
-Checkpoint
: the exit being converted into a limbo exit_limboTarget
-StateUpdate
: the output of the transaction being verified.
Requirements¶
N/A
Justification¶
This method will simply return true for basic predicates like ownership or multisigs, but allows for more complex stateful exit subgames to be initiated if they need to happen during limbo exits.
proveDeprecation¶
function proveExitDeprecation(
Checkpoint _deprecatedExit,
Transaction _transaction,
bytes _witness,
StateUpdate _postState
) public
Description¶
This function serves the same purpose as regular state transition predicates, on the condition that the _deprecatedExit
is not a limbo exit.
Parameters¶
_deprecatedExit
-Checkpoint
: the deprecated checkpoint being exited._transaction
-Transaction
: The transaction which deprecates the exit. Follows the standard format as outlined in the transaction generation page in Secion #03._witness
-bytes
: Additional witness data which authenticates the transaction validity, e.g. a signature. Defined on a per-predicate basis._postState
-StateUpdate
: the output of the transaction to be verified.
Requirements¶
- MUST ensure that the
_deprecatedExit
is not a limbo exit by checking thelimboTargets
mapping. - MUST check that the transaction is valid with a call to
verifyTransaction(_deprecatedExit.stateUpdate, _transaction, _witness, _postState
on the source predicate (i.e. this predicate itself). - MUST check that the
_postState.range
intersects the_deprecatedExit.subrange
- MUST call
deprecateExit(_deprecatedExit)
on the_deprecatedExit.stateUpdate.plasmaContractAddress
.
Justification¶
If the exit is not a limbo exit, deprecation may occur normally, by proving an intersecting transaction spending the exit.
proveTargetDeprecation¶
function proveTargetDeprecation(
Checkpoint _limboSource,
StateUpdate _limboTarget
Transaction _transaction,
bytes _witness,
StateUpdate _postState
) public
Description¶
This function allows a limbo exit to be cancelled if the target
has been spent.
Parameters¶
_limboSource
-Checkpoint
: the limbo exit whose target state update is deprecable._limboTarget
-StateUpdate
the target of the limbo exit which is deprecable._transaction
-Transaction
: The transaction which spends from the_limboTarget
_witness
-bytes
: Additional witness data which authenticates the transaction validity._postState
-StateUpdate
: the output of the transaction on the target.
Requirements¶
- MUST ensure that the
_limboSource
is indeed a limbo exit with thehash(_limboTarget)
in itslimboTargets
value. - MUST check that the transaction is valid with a call to the target predicate’s
verifyTransaction(_deprecatedExit.stateUpdate, _transaction, _witness, _postState
. - MUST check that the
_postState.range
intersects the_limboTarget.range
. - MUST call
deprecateExit(_limboSource)
on the_limboSource.stateUpdate.plasmaContractAddress
. - MUST clear the limbo exit from the
limboTargets
mapping.
Justification¶
An example usage of this would be: if Alice->Bob->Carol, and Alice limbo exits with Bob ownership as the target, this function will be used to cancel the exit by showing Bob->Carol.
proveSourceDoubleSpend¶
function proveSourceDoubleSpend(
Checkpoint _limboSource,
StateUpdate _limboTarget
Transaction _conflictingTransaction,
bytes _conflictingWitness,
StateUpdate _conflictingPostState
) public
Description¶
This function allows a limbo exit which has an alternate transaction spending from the source to be deprecated.
Parameters¶
_limboSource
-Checkpoint
: the limbo exit which has a double spend which conflicting its target._limboTarget
-StateUpdate
the target of the limbo exit which has a conflicting spend_conflictingTransaction
-Transaction
: The transaction which spends from the_limboTarget
_conflictingWitness
-bytes
: Additional witness data which authenticates the transaction validity._conflictingPostState
-StateUpdate
: the output of the transaction on the source which has a differentstate
than the target.
Requirements¶
- MUST ensure that the
_limboSource
is indeed a limbo exit with thehash(_limboTarget)
in itslimboTargets
value. - MUST check that the transaction is valid with a call to the source predicate’s
verifyTransaction(_deprecatedExit.stateUpdate, _transaction, _witness, _postState
. - MUST check that the
_postState.range
intersects the_limboTarget.range
. - MUST check that the
_postState.state
is not equal to the_conflictingPostState.state
. - MUST call
deprecateExit(_limboSource)
on the_limboSource.stateUpdate.plasmaContractAddress
. - MUST clear the limbo exit from the
limboTargets
mapping.
Justification¶
An example usage of this would be: if Alice->Bob->Carol, and Alice limbo exits with Mallory ownership as the target, this function will be used to cancel the exit by showing Alice->Bob–a double spend.
returnLimboExit¶
function returnLimboExit(
Checkpoint _limboSource,
StateUpdate _limboTarget
bytes _witness
) public
Description¶
This function allows the source state of a limbo exit to agree to give the money back to the source state of the limbo exit.
Parameters¶
_limboSource
-Checkpoint
: the limbo exit which is being returned_limboTarget
-StateUpdate
the target of the limbo exit which has a conflicting spend_witness
-bytes
: Arbitrary witness data used by the target predicate to authenticate the return. Not necessarily the same as a transaction witness.
Requirements¶
- MUST ensure that the
_limboSource
is indeed a limbo exit with thehash(_limboTarget)
in its status. - MUST ensure that the target state is allowing the return by calling the target predicate’s
canReturnLimboExit(_limboSource, _limboTarget, _witness)
- MUST set
wasReturned
to true for the limbo exit’s status. - MUST emit a
limboExitReturned
event.
Justification¶
If Alice sends a transaction to Bob and then observes block withholding, she must limbo exit with Bob as the target. However, because of the proveSourceDoubleSpend
method, Bob cannot guarantee until the exit period has passed that Alice will not sign a conflicting message and deprecate the exit. Thus, we want Bob to be able to return the exit to Alice, perhaps conditionally on some payment which he can validate without waaaiting a full exit period
canReturnLimboExit¶
function canReturnLimboExit(
Checkpoint _limboSource,
StateUpdate _limboTarget
bytes _witness
) public returns (bool)
Description¶
This function allows the target state of a limbo exit to authenticate a guaranteed return to the source as described above.
Parameters¶
_limboSource
-Checkpoint
: the limbo exit which requesting to be returned._limboTarget
-StateUpdate
the target of the limbo exit which has a conflicting spend_witness
-bytes
: Arbitrary witness data used by the target predicate to authenticate the return. Not necessarily the same as a transaction witness.
Requirements¶
- MUST handle some sort of authentication which provides the target state a guarantee that it will not be returnable without permission.
Justification¶
See justification for returnLimboExit
above, which explains this function.
finalizeExit¶
function finalizeExit(
Checkpoint _exit
) public
Description¶
Finalizes an exit which is either not a limbo exit or has been returned
Parameters¶
_exit
-Checkpoint
: the exit being finalized.
Requirements¶
- MUST check that the exit is either:
- not a limbo exit, or
- is a limbo exit which
wasReturned
.
- MUST call
finalizeExit
on the deposit contract. - MUST handle the resulting ERC20 transfer of the exit amount in some way.
Justification¶
If an exit is not a limbo exit or was returned by the target state, we execute a normal exit procedure for the exited Checkpoint
.
finalizeLimboExit¶
function finalizeExit(
Checkpoint _exit,
StateUpdte _target
) public
Description¶
Finalizes a successful, unreturned limbo exit.
Parameters¶
_exit
-Checkpoint
: the limbo exit being finalized._target
-StateUpdate
: the target of the limbo exit.
Requirements¶
- MUST ensure that the
_limboSource
is indeed a limbo exit with thehash(_limboTarget)
in its status. - MUST ensure that the limbo exit was not returned.
- MUST call
finalizeExit
on the deposit contract. - MUST transfer ALL ERC20 funds from the deposit contract to the target predicate.
- MUST call
onFinalizeTargetedExit
on the target predicate.
Justification¶
If an exit is a limbo exit which was not returned, the source predicate finalizes the exit, sends it to the target, and allows the target to handle the receipt.
onFinalizeTargetedExit¶
function onFinalizeTargetedExit(
Checkpoint _exit,
StateUpdate _target
) public
Description¶
Logic for the target of a limbo exit to handle the exit’s finalization.
Parameters¶
_exit
-Checkpoint
: the limbo exit being finalized._target
-StateUpdate
: the target of the limbo exit.
Requirements¶
- MUST handle the money received from the exit as it pertains to this target.
Justification¶
The target of a limbo exit must be able to handle the money arbitrarily, this logic is called by the source predicate on the target to perform that logic.
Introduction¶
Todo
Write introduction for client.
Deposit Generation¶
Users will usually first interact with a plasma chain by depositing their assets into a plasma deposit contract. Deposits usually consist of a single Ethereum transaction that locks some assets into the depoist contract. This page describes how a client can make a deposit and start using a plasma chain.
Plasma chain transactions transform the state of a range of state objects. The state of each range at a moment in time is described by a state object. Each state object specifies the address of a predicate contract and some additional arbitrary data which are used in tandem to manage ownership of an asset.
Users submit deposit transactions to a plasma deposit contract. Each deposit contract exposes a method deposit
:
function deposit(uint256 _amount, StateObject _state) public payable
Where StateObject
is the following struct:
struct StateObject {
address predicate;
bytes data;
}
deposit
requires that users specify the _amount
the asset being deposited and an initial state object, _state
, that controls ownership of the asset. For example, users might use the SimpleOwnership predicate to control their asset.
Event Handling¶
Checkpoint Events¶
Todo
Explain how to handle checkpoint events.
Exit Game Events¶
Todo
Explain how to handle exit game events.
Transaction Generation¶
Once a user has submitted a deposit, they’re ready to start making transactions. It’s important that transactions are relatively standardized so that transaction generation is as simple as possible. This page describes a standard transaction format and generation process that clients MUST adhere to.
Transaction Format¶
From the perspective of each predicate, a transaction just consists of an arbitrary string of bytes. Each predicate could parse these bytes in a unique way and therefore define its own transaction format. However, clients should be able to correctly generate a transaction for any given predicate. As a result, we’ve developed a standard transaction format that simplifies the transaction generation process.
The interface for a Transaction
object looks like this:
interface Transaction {
plasmaContract: string
start: number
end: number
methodId: string
parameters: string
}
Where the components of this interface are:
plasmaContract
-string
: The address of the specific plasma deposit contract which identifies the asset being transferred. This is somewhat equivalent to Ethereum’s chain ID transaction parameter.start
-number
: Start of the range being transacted.end
-number
: End of the range being transacted.methodId
-string
: A unique method identifier that tells a given predicate what type of state transition a user is trying to execute. This is necessary because a predicate may define multiple ways in which a state object can be mutated.methodId
should be computed as the keccak256 hash of the method’s signature, as given by the Predicate API.parameters
-string
: Input parameters to be sent to the predicate along withmethod
to compute the state transiton. Must be ABI encoded according to the Predicate API. This is similar to the transaction input value encoding in Ethereum.
Transaction Encoding and Decoding¶
Plasma transactions must be ABI encoded or decoded according to the following schema:
{
plasmaContract: address,
start: uint256,
end: uint256,
methodId: bytes32,
parameters: bytes
}
Sending Transactions¶
The client SHOULD verify the history of the range being transacted before sending the transaction to the operator. Doing so will confirm that no `invalid transactions`_ have been maliciously inserted into the blockchain by the operator between the block in which the user received a state update and the latest block. Otherwise the client may have to start `limbo exit`_, which is more costly than a standard exit.
Transactions can be submitted to a node via the sendTransaction RPC method. If the node that receives this request is not the operator, then it will forward the transaction to the operator on the requester’s behalf.
Example: SimpleOwnership Predicate¶
We’re going to look at the whole process for generating a valid transaction to interact with some state objects locked by the SimpleOwnership predicate. This example will explain how a client can use the Predicate API to generate a valid state-changing transaction. In this case, we’ll generate a transaction that changes the ownership of the objects. We’ll then look at the process of encoding the transaction and sending it to the operator.
First, let’s pick some arbitary values for plasmaContract
, start
, and end
. Users will know these values in advance, so we don’t really need to explain the process of getting them in the first place. Let’s say that the plasmaContract
of the SimpleOwnership
predicate is 0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c
and we want to send the range (0, 100)
.
Now we just need to figure out our values for methodId
and parameters
. We’re going to use the Predicate API for SimpleOwnership in order to generate these values. Users can get this API from a variety of places, but it’s likely that most wallet software will come with a hard-coded API. Once we have the API, we know that send
looks like this:
{
name: "send",
constant: false,
inputs: [
{
name: "newOwner",
type: "address"
}
],
outputs: []
}
This is already enough information to generate methodId
and parameters
. As we previously described, methodId
is generated by taking the keccak256 hash of the method’s signature. In this case:
const methodId = keccak256('send(bytes)')
Now let’s generate parameters
. Our only parameter to send
is newOwner
. We’re going to send to a random address, 0xd98165d91efb90ecef0ddf089ce06a06f6251372
. We need to ABI encode this address:
const newOwner = '0xd98165d91efb90ecef0ddf089ce06a06f6251372'
const parameters = abi.encode(['address'], newOwner)
This is all we need to generate the transaction:
const transaction = abi.encode([
'address',
'uint256',
'uint256',
'bytes32',
'bytes'
], [
plasmaContract,
start,
end,
methodId,
parameters
])
Finally, we need to generate a valid witness for this transaction. SimpleOwnership
requires a signature from the previous owner over the whole encoded transaction (except, of course, the signature itself) as a witness:
const key = '0x...'
const witness = sign(transaction, key)
We now have everything we need to send this transaction off to the operator!
History Proofs¶
Introduction¶
In every plasma block, a range of state objects can either be deposited, transacted, or not transacted. Whenever clients want to verify a transaction on a specific range, they need to verify the entire “history” of what happened to the range between the block in which it was first deposited and the block in which the transaction occurred.
For example, let’s imagine that a range (0, 100)
was deposited in block 1, not transacted in block 2, and then transacted in block 3. The history proof for a transaction in block 4 would contain the deposit, a proof that the range wasn’t transacted in block 2, and a proof that the range was transacted in block 3.
This page describes the basic components of the history proof. Details about generating a history proof and verifying a history proof are described later.
Proof Elements¶
History proofs consist of a series of “proof elements”, which correspond to some action that took place within a specific plasma block. A proof element can be a Deposit Proof Element, Exclusion Proof Element, or a State Update Proof Element. Each element type conveys different information and needs to be handled differently.
Deposit Proof Elements¶
Deposit Proof Elements consist of a deposit ID. Deposit IDs correspond to a deposit on Ethereum. Deposits on Ethereum contain a state update, which a client can query and insert into their local state.
Exclusion Proof Elements¶
Exclusion Proof Elements consist of a single state update and an inclusion proof for that state update. They prove that a specific range was not transacted during a given block, but they do not prove that the given state update is valid.
These proof elements take advantage of the fact that items of our Merkle Interval Tree have both an explicit range and an implicit range. A valid inclusion proof for an item in the tree also proves that there aren’t any valid items that intersect with that element’s implicit range. A user can check the inclusion proof for the state update in the Exclusion Proof Element and be sure that its implicit range wasn’t transacted in the given block.
State Update Proof Elements¶
State Update Proof Elements prove that a given state update was correctly created from the previous state update. Despite the name, State Update Proof Elements actually include transactions and not state updates. The provided transactions are used to compute the newer state update. These elements also include a block number in which the computed state update was included and an inclusion proof that the update was actually included.
History Generation¶
Clients need to be able to correctly generate history proofs. This page describes the process of querying and generating these proofs.
History Query¶
Clients can query a history proof by creating a HistoryQuery
:
interface HistoryQuery {
plasmaContract: string
start: number
end: number
startBlock?: number
endBlock?: number
}
Where:
plasmaContract
-string
: Address of the specific plasma contract to query. Clients may watch several plasma contracts simultaneously, so this parameter is required for the client to return the correct history.start
-number
: Start of the range of state objects to query.end
-number
: End of the range of state objects to query.startBlock
-number
: Block to start querying history from. If not provided, will default to 0.endBlock
-number
: Block to query history to. If not provided, will default to the latest block known by the client.
Once created, these queries can be sent to a node via the history query RPC method.
History Calculation¶
When a node receives a history query, they MUST follow the following process to generate the correct history proof.
Range Intersection¶
Generation of a history for a given range begins by performing an intersection of the range with the historical state for the provided block range. Intersection MUST be performed on a start-inclusive, end-exclusive basis. Basically, for each block in the block range, the client needs to find all historical state updates with an implicit range or an explicit range that intersects with the queried range.
Intersection with the historical state will return three types of relevant elements: Deposit Proof Elements, Exclusion Proof Elements, and State Update Proof Elements. Each of these elements are necessary to generate a full history proof. However, each element must be handled differently during the proof generation process. A more detailed explanation of these elements can be found here.
Deposit Proof Elements and State Update Proof Elements will be found when the explicit range described by a state update directly intersects with queried range.
Exclusion Proof Elements are found when the implicit range described by a state update intersects with the queried range but the explicit range does not. Exclusion Proof Elements prove that a given range was not transacted during a specific block but do not prove that the given state update was actually valid.
Proof Generation¶
Deposit Proof Elements, and Exclusion Proof Elements, and State Update Proof Elements must each be handled individually when generating proofs. The process for handling each element is described below.
For each element returned by the range intersection, the client MUST generate a corresponding proof element according to the following rules. Proof elements MUST be returned in ascending block number order.
Deposit Proof Elements¶
Deposit Proof Elements consist of state updates that are created on the plasma chain when a deposit is submitted on Ethereum. Because we assume that clients have access to Ethereum, we SHOULD NOT include the full state update in the proof. Instead, clients MUST include the deposit ID logged on Ethereum when the deposit was submitted.
Exclusion Proof Elements¶
State updates are all associated with two ranges. A state update’s explicit range is the range of state objects the update mutates. An update’s implicit range is the range of objects that the update proves are not spent within a given block. This mechanism is a byproduct of our Merkle Interval Tree construction.
An Exclusion Proof Element consists of a state update and an inclusion proof . For each Exclusion Proof Element, the client MUST provide an inclusion proof that shows the element was included in a block. An Exclusion Proof Element does not prove that the given state update is valid. Therefore, the client SHOULD NOT provide any information that proves the validity of the state update beyond the inclusion proof.
State Update Proof Elements¶
State Update Proof Elements describe the transition of one or more state objects that fall within the specified range. State Update Proof Elements correspond to one or more transactions that generated the state update.
For each state update, the client MUST provide all transactions that produced the state update. However, as these transaction can be used to calculate the state update, the client SHOULD NOT provide the state update itself.
The client MUST also provide an inclusion proof for the state update that proves the update was included in the block specified in the state update
It’s possible that the validity of a given transaction may also rely on the existence of some other plasma transaction. When this is the case, the verifier must first verify some additional proof elements before executing a given transaction. For each transaction that corresponds to a state update, the client MUST ask for a list of additional proof elements from the predicate plugin of the state objects from which the transaction spends. Any additional proof elements MUST be inserted before the transaction itself so that the client can verify the necessary state before verifying the transaction.
History Verification¶
Once a client has received a history proof, they need to be able to correctly check the proof’s validity. This page describes the process for applying each individual proof element and determining the overal validity of the proof.
Proof Structure¶
A HistoryProof consists of a series of “proof elements”. Each of these elements is either a Deposit Proof Element, an Exclusion Proof Element, or a State Update Proof Element. Elements of each different element type must be handled differently as they convey different information.
Applying Proof Elements¶
Proof elements each correspond to some action that took place within a specific plasma block. Proof elements MUST be applied in ascending block order. Otherwise, a client may not have the necessary information to verify a specific state transition.
Deposit Proof Elements¶
Deposit Proof Elements consist of a deposit ID. Deposit IDs correspond to a deposit on Ethereum, which contains a state update. Whenever the client encounters a Deposit Proof Element, the client MUST download the deposit with the given ID by querying for the checkpoint with the same ID.
For efficiency, the client SHOULD check their local database for the deposit before querying Ethereum. It’s possible that the client already downloaded while verifying another history proof.
If the deposit is not found, the client MUST either throw an error or skip the element.
Once the client has downloaded the corresponding deposit, the client MUST insert the deposit’s state update into their local state. The client does not need to verify anything about this state update since it came directly from a deposit.
Exclusion Proof Elements¶
Elements of our Merkle Interval Tree have both an explicit range and an implicit range. A valid inclusion proof for an element of the tree also conveys the fact that no other elements intersect with the included element’s implicit range.
Exclusion Proof Elements simply consist of a state update and an inclusion proof. These elements make no statements about the validity of the state update, but prove that there were no valid transactions on the update’s implicit range.
The client MUST verify the inclusion proof for each Exclusion Proof Element. If the inclusion proof fails, the client MUST either throw an error or skip to the next proof element.
The client MUST then find all state updates that intersect with the implicit range of the proof element where update.verifiedBlockNumber
is equal to element.block - 1
. We’re only interested in these state updates because the implicit proof only applies to the element.block
but not any previous blocks.
Next, for each found state update, the client MUST split any elements where the implicit range only partially covers the intersected update. For example, if the implicit range is (50, 100)
but the found update is over (0, 100)
, the client will split the update into two elements that cover (0, 50)
and (50, 100)
. This process will leave the client with a set of state updates that are entirely covered by the implicit range and a set that no longer intersect at all.
Next, for each element that is entirely covered by the implicit range, the client MUST set update.verifiedBlockNumber
to element.block
. This process effectively finds any state updates covered by the implicit range and “bumps up” the block to which they’re considered valid.
State Update Proof Elements¶
State Update Proof Elements prove that a given state update has transitioned to a newer one. Despite the name, State Update Proof Elements actually consist of transactions and not state updates. The provided transactions are used to compute the newer state update. These elements also include a block number in which the computed state update was included and an inclusion proof that the state update was actually included in the given block.
For simplicity, we require that all of the transactions within a single State Update Proof Element refer to the same range. If any of the transactions do not refer to the same range, the client MUST either throw an error or skip to the next element.
Next, the client MUST find all previously verified state updates that intersect with the range on the transations and where update.verifiedBlockNumber
is equal to element.block - 1
.
For each update found in the previous step, the client then MUST execute each transaction against the update. Each transaction execution will generate a resulting state update.
After executing the transactions against all updates, the client MUST verify that the resulting state updates are all equivalent. If any state update is not equivalent, the client MUST either throw an error or skip to the next proof element. The resulting update MUST have a block number equal to element.block
.
The client then MUST verify the inclusion proof provided as part of the proof element. If the inclusion proof is invalid, the client MUST either throw an error or skip to the next proof element.
Finally, the client MUST insert the resulting state update into the local state. This process MUST split or overwrite any existing state updates over the same range. For example, if the local state contains an update that covers (0, 100)
and the computed update covers (50, 100)
, the client will modify the first update such that it only covers (0, 50)
.
Exit Guarding¶
Event Handling¶
Predicate Event Watching¶
Todo
Explain how predicate plugins can watch for exit events.
Predicate Guarding¶
Exit Validity Determination¶
Todo
Explain how predicate plugins determine whether an exit is valid or not.
Challenge Generation¶
Todo
Explain how predicate plugins generate a challenge.
Challenge Submission¶
Todo
Explain how predicate plugins actually send the challenge to Ethereum.
State Queries¶
Clients need to be able to query the local state to build effective front-ends. For example, a wallet might be interested in querying all state objects that a specific user owns. We’ve designed a querying system to make this process as easy as possible.
Query Generation¶
Parsing Predicate API¶
The Predicate API provided by each predicate specifies a list of queries that can be made on state objects locked with that predicate. For example, the API of the SimpleOwnership predicate specifies the following function:
{
"name": "getOwner",
"constant": true,
"inputs": [],
"outputs": [
{
"name": "owner",
"type": "address"
}
]
}
Users first need to parse this API to figure out what methods are available for the predicate they’re attempting to query. Once the user has determined the name and required parameters for the method they want to query, they can generate a StateQuery. The user can then send this query to a client via the state query RPC method.
Parsing Query Results¶
The result of a state query is a list of `StateQueryResult`_ objects. If a filter was provided as part of the StateQuery
, only StateQueryResult
objects that passed the provided filter will be returned.
Example: SimpleOwnership¶
We’ll now provide an example state query for the getOwner
method of the SimpleOwnership
predicate. The Predicate API for SimpleOwnership
describes getOwner
as:
{
"name": "getOwner",
"constant": true,
"inputs": [],
"outputs": [
{
"name": "owner",
"type": "address"
}
]
}
Let’s assume that our plasmaContract
is 0x1b33c35be86be9d214f54af218c443c2623d3d0a
and our SimpleOwnership
predicate is located at 0xf25746ac8621a7998e0992b9d88e260c117c145f
.
To query all state updates where 0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c
is the owner, we construct the following query:
const query: StateQuery = {
plasmaContract: '0x1b33c35be86be9d214f54af218c443c2623d3d0a',
predicateAddress: '0xf25746ac8621a7998e0992b9d88e260c117c145f',
method: 'getOwner',
params: [],
filter: {
$eq: [ '$0', '0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c' ]
}
}
Our filter here specifies that the first output of the function call (which we know to be the owner in this case) should be equal to a given address. Results where this is not the case will not be returned.
Once we send this request via the state query RPC method, we’ll receive a result that looks like this:
[
{
stateUpdate: {
block: 123,
start: 0,
end: 100,
predicate: '0xf25746ac8621a7998e0992b9d88e260c117c145f',
data: '0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c'
},
result: ['0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c']
},
...
]
We can then present this data in any way that we might want to.
Query Handling¶
Range Intersection¶
Clients will receive a StateQuery object when receiving a state query. Clients first MUST use the range provided by the StateQuery
to find all state updates in the current head state that match the provided predicateAddress
.
Passing Queries to Predicate Plugins¶
Once the client has found all relevant state updates, they MUST call the queryState method in the predicate plugin that corresponds to the provided predicateAddress
. queryState
takes the method
and parameters
from the StateQuery
and returns an array of results.
Filtering Queries¶
Users filter their results by providing an Expression that performs some operation on the provided result. If a filter
was given in the StateQuery
, then the client MUST correctly remove results according to the filter. More information about filters is given in the page about Expressions.
Users can filter based on the outputs of the query method by inserting strings in the form of \$[0-9]+
(starting at $0
).
For example, a user could filter results where the first output is greater than 0
and the second result is less than 100
like this:
{
$and: [
{ $gt: [ '$0', 0 ] },
{ $lt: [ '$1', 100 ] }
]
}
Any results that have not been removed by the filter can then be returned to the requesting client.
Synchronization¶
Introduction¶
Clients need to be able to synchronize their local state with that of the operator. However, this process is somewhat non-trivial as clients may be simultaneously connected to multiple plasma chains.
The actual process for synchronizing a client can generally be determined by the client implementer. However, we’re going to go through some recommendations that will ensure that the client is as feature-complete as possible.
Deposit and Commitment Contracts¶
Our plasma construction makes use of deposit contracts that store the assets that users transact on the plasma chain. These deposit contracts act like their own individual plasma chains. For example, the range (0, 100)
will be valid on two different deposit contracts but will refer to different assets.
Furthermore, we’re generally using a single deposit contract per asset type to simplify things for the client. It’s therefore very likely that a client will be interested in transactions on several different deposit contracts. As a result, clients SHOULD be able to send and receive transactions on multiple deposit contracts simultanously.
We’re also using the new concept of commitment contracts that store plasma block commitments instead of throwing all of this logic inside the deposit contract. Each deposit contract points to a specific commitment contract, and it’s possible for multiple deposit contracts to point to the same contract. Therefore a client SHOULD also be able to watch for commitments to multiple commitment contracts and SHOULD maintain a mapping between commitment contracts and deposit contracts.
Receiving Transactions¶
Transactions are unique to a given deposit contract, but blocks are unique to a commitment contract. For each commitment contract the client is interested in, clients SHOULD watch for new blocks being published to Ethereum.
It’s important to note that, unlike in previous plasma constructions, there’s no easy way for an operator to tell that a given address will be interested in a specific transaction. Instead, clients SHOULD, upon seeing the publication of a new block, send a `state query`_ to the operator for all state updates the client is interested in.
For example, imagine we have a predicate that allows anyone to mutate a state object as long as they have the pre-image to some hash. Without the pre-image, the operator has no way to know which user “owns” that state object. A client would have to specifically form a query for all state objects that use that predicate and lock the state object with a specific hash.
Query Expressions¶
Description¶
We provide a system for filtering state queries via expressions similar to those used by mongoDB.
Data Structures¶
Operators¶
Boolean Operators¶
$and¶
{ $and: [ <expression1>, <expression2>, ... ] }
Description¶
Returns true
if all arguments evaluate to true
.
Parameters¶
expressions
-Expression[]
: Any number ofExpression
objects.
Returns¶
boolean
: true
if all arguments resolve to true
, false
otherwise.
$not¶
{ $not: [ <expression> ] }
Description¶
Returns the boolean opposite of the result of the argument expression.
Parameters¶
expression
:Expression
: A singleExpression
object.
Returns¶
boolean
: true
if the expression resolves to false
, false
otherwise.
Comparison Operators¶
$eq¶
{ $eq: [ <argument1>, <argument2>, ... ] }
Description¶
Checks if all arguments are equal.
Parameters¶
arguments
-any[]
: List of input values.
Returns¶
boolean
: true
if all arguments are equal, false
otherwise.
$gt¶
{ $gt: [ <argument1>, <argument2> ] }
Description¶
Checks if the first argument is greater than the second.
Parameters¶
argument1
-any
: First input value.argument2
-any
: Second input value.
Returns¶
boolean
: true
if the first argument is greater than the second, false
otherwise.
$gte¶
{ $gte: [ <argument1>, <argument2> ] }
Description¶
Checks if the first value is greater than or equal to the second.
Parameters¶
argument1
-any
: First input value.argument2
-any
: Second input value.
Returns¶
boolean
: true
if the first value is greater than or equal to the second, false
otherwise.
$lt¶
{ $lt: [ <argument1>, <argument2> ] }
Description¶
Checks if the first value is less than the second.
Parameters¶
argument1
-any
: First input value.argument2
-any
: Second input value.
Returns¶
boolean
: true
if the first value is less than the second, false
otherwise.
$lte¶
{ $lte: [ <argument1>, <argument2> ] }
Description¶
Checks if the first value is less than or equal to the second.
Parameters¶
argument1
-any
: First input value.argument2
-any
: Second input value.
Returns¶
boolean
: true
if the first value is less than or equal to the second, false
otherwise.
RPC Methods¶
Description¶
We require that the clients interact via JSON RPC. Here we provide a list of available RPC methods that the client MUST implement.
Methods¶
pg_accounts¶
Description¶
Returns the list accounts in the client’s wallet.
Returns¶
string[]
: List of addresses owned by the client.
pg_blockNumber¶
Description¶
Returns the current plasma block number.
Returns¶
number
: Current plasma block number.
pg_sign¶
Description¶
Signs a message with the private key of a specified account.
Parameters¶
account
-string
: Address of the account to sign with.message
-string
: Message to sign.
Returns¶
string
: Signature over the given message.
pg_sendRawTransaction¶
Description¶
Sends a transaction to the client. If the client is not the operator, the transaction will be forwarded to the operator.
Parameters¶
transaction
-string
: Properly encoded transaction to send to the operator.
Returns¶
string
: Receipt for the transaction.
pg_sendQuery¶
Description¶
Sends a state query to the client.
Parameters¶
query
-StateQuery
: A StateQuery object.
Returns¶
any[]
: Result of the state query. Returns one query result for each state object that intersected with the range specified in the StateQuery
.
pg_getTransactionByHash¶
Description¶
Returns a full transaction from a transaction hash.
Parameters¶
hash
-string
: Hash of the transaction to query.
Returns¶
Transaction
: The Transaction object with the given hash.
pg_getProof¶
Description¶
Returns a history proof for a given range.
Parameters¶
query
-HistoryQuery
: A HistoryQuery object.
Returns¶
HistoryProof
: A HistoryProof composed of a list of proof elements that can be ingested.
pg_getInstalledPredicatePlugins¶
Description¶
Returns the list of predicates installed by the client.
Returns¶
string[]
: Address of each predicate for which the client has an installed plugin.
RPC Error Messages¶
Wallet¶
Account Not Found¶
{
code: -20001,
message: "Account Not Found",
data: <address>
}
Description¶
Error returned when an account has not been found for a given address.
Code¶
-20001
Data¶
address
-string
: Address of the queried account.
Invalid Password¶
{
code: -20002,
message: "Invalid Password",
data: <address>
}
Description¶
Returned when the user attempts to unlock an account with an invalid password.
Code¶
-20002
Data¶
address
-string
: Address of the account the user attempted to unlock.
Transactions¶
Invalid Transaction Encoding¶
{
code: -20004,
message: "Invalid Transaction Encoding"
}
Description¶
Returned when a user attempts to submit an incorrectly encoded transaction.
Code¶
-20004
Invalid Transaction¶
{
code: -20005,
message: "Invalid Transaction"
}
Description¶
Returned when a user submits a transaction that executes incorrectly.
Code¶
-20005
State Queries¶
Invalid State Query¶
{
code: -20006,
message: "Invalid State Query"
}
Description¶
Returned when the user attempts to make an invalid state query.
Code¶
-20006
Introduction¶
Transaction Ingestion¶
Incoming Transaction Endpoint¶
Clients send transactions to the operator via the send transaction RPC method, which is then received by the RPC server. The RPC server then pipes the transaction to the operator’s state manager.
Transaction Decoding¶
Before the operator can execute the transaction, it MUST first check that the transaction is correctly formatted by attempting to decode the transaction.
If decoding fails, the operator MUST return an Invalid Transaction Encoding error response.
Transaction Execution¶
Once the operator has determined that the transaction was correctly encoded, they can attempt to execute the transaction against the local state.
State Update Resolution¶
The operator MUST resolve the set of state updates that the transaction operates on. The operator uses the start
and end
values from the transaction and finds all state updates the range overlaps with.
Once the operator finds all overlapping state updates, they MUST assert that the entire range described by the transaction is covered by existing state updates. If this is not the case, the operator MUST return an Invalid Transaction error response.
State Transition Execution¶
For each state update resolved in the previous step, the operator then MUST call the executeStateTransition method of the plugin that corresponds to the predicate address specified in the state update. This function call will return a new resulting state update. If any of these calls throw an error, the operator MUST return an Invalid Transaction error response.
The operator MUST then validate that all of the resulting state updates are identical. If any state update is not identical, the operator MUST return an Invalid Transaction error response.
Transaction Queueing¶
Once the transaction has been verified, the operator can add the resulting state update to the queue of state updates to be published in the next block. If the queue already contains a state update on the range specified in the transaction, the operator MUST return a Duplicate Transaction error response.
Transaction Receipt¶
Finally, once the new state update has been added to the block queue, the operator MUST return a transaction receipt to the client.
Operator RPC Methods¶
Description¶
Todo
Add description for Operator RPC methods.
Methods¶
pgop_sendTransaction¶
Parameters¶
transaction
-Transaction
: The Transaction to be ingested.
Returns¶
string
: The transaction receipt for the given transaction.
Introduction¶
Components¶
Our client is broken up into several key components, each of which are specified separately. Here we’ll discuss a high-level overview of the different components and what they do.
RpcServer¶
RpcServer is the exposes a JSON RPC server that users can interact with. It pipes calls to different RPC methods to the various other components that can fulfill these requests.
StateManager¶
StateManager executes transactions and applies them to the head state. StateManager
also handles queries about the current state. StateManager
is the only component with access to StateDB
.
HistoryDB¶
HistoryDB stores historical information about the plasma chain. HistoryDB
is primarily used to store history proof information necessary to assert the validity of a given transaction.
HistoryManager¶
HistoryManager handles queries about historical state and generates history proofs. HistoryManager
is the only component with access to HistoryDB
.
PluginManager¶
PluginManager handles access to the various predicate plugins loaded into the client. Other components are expected to go through PluginManager
whenever they want to interact with a plugin.
EventWatcher¶
EventWatcher watches for various important events on Ethereum. Components can request that EventWatcher
watch a specific event and will be notified whenever the event is fired. EventWatcher
is designed to be robust as not to miss events or notify components of the same event multiple times.
ContractWrapper¶
ContractWrapper is a simple wrapper around the smart contracts the client needs to interact with. All components, except for EventWatcher
, are expected to only interact with Ethereum through ContractWrapper
.
Architecture Diagram¶
We’ve provided a diagram of the interactions between the various client components below.
MerkleIntervalTree¶
Description¶
Our plasma implementation uses a special Merkle tree called a Merkle Interval Tree. This page describes the interface for an implementation of the tree.
API¶
Structs¶
MerkleIntervalTreeLeafNode¶
interface MerkleIntervalTreeLeafNode {
start: number
end: number
data: string
}
Description¶
Represents a leaf node in the tree.
Fields¶
start
-number
: Start of the range this leaf node covers.end
-number
: End of the range this leaf node covers.data
-string
: Arbitrary data held by the leaf node.
Methods¶
getInclusionProof¶
function getInclusionProof(
leaf: MerkleIntervalTreeLeafNode
): Promise<MerkleIntervalTreeInternalNode[]>
Description¶
Generates an inclusion proof for a given leaf node.
Parameters¶
leaf
-MerkleIntervalTreeLeafNode
: Leaf node to generate a proof for.
Returns¶
MerkleIntervalTreeInternalNode[]
: List of internal nodes that form the inclusion proof.
checkInclusionProof¶
function checkInclusionProof(
leaf: MerkleIntervalTreeLeafNode,
leafIndex: number,
root: MerkleIntervalTreeInternalNode,
inclusionProof: MerkleIntervalTreeInternalNode[]
): boolean
Description¶
Checks an inclusion proof for a given leaf node.
Parameters¶
leaf
-MerkleIntervalTreeLeafNode
: Leaf node to check inclusion for.leafIndex
-number
: Index of the leaf node in the list of leaf nodes.root
-MerkleIntervalTreeInternalNode
: Root of the Merkle Interval Tree.inclusionProof
-MerkleIntervalTreeInternalNode[]
: List of internal nodes that form the inclusion proof.
Returns¶
boolean
: true
if the proof is valid, false
otherwise.
RangeDB¶
Description¶
RangeDB
is a database abstraction that makes it easy to map ranges, defined by a start
and an end
, to arbitrary values.
API¶
Structs¶
RangeEntry¶
interface RangeEntry {
start: number
end: number
value: Buffer
}
Description¶
Represents a value in the database. All values are constructed with respect to some range, defined by start
and end
.
Fields¶
start
-number
: Start of the range described by this entry.end
-number
: End of the range described by this entry.value
-Buffer
: Value at this specific range.
Methods¶
get¶
async function get(start: number, end: number): Promise<RangeEntry[]>
Description¶
Queries RangeEntry
values that intersect with a given start
and end
. Ranges must be treated as start-inclusive and end-exclusive.
Parameters¶
start
-number
: Start of the range to query.end
-number
: End of the range to query.
Returns¶
Promise<RangeEntry[]>
: All RangeEntry
objects in the database such that entry.start
and entry.end
intersect with the given start
and end
.
put¶
async function put(start: number, end: number, value: Buffer): Promise<void>
Description¶
Adds a value to the database at a given range. Overwrites all existing RangeEntry
objects that overlap with the range. put
MUST modify or break apart existing RangeEntry
objects if the given range only partially overlaps with the object. For example, if we currently have a RangeEntry
over the range (0, 100)
and call put(25, 75, "some value")
, this function must break the existing RangeEntry
into new entries for (0, 25)
and (75, 100)
.
Parameters¶
start
-number
: Start of the range to insert into.end
-number
: End of the range to insert into.value
-Buffer
: Value to insert into the range as a Buffer.
Returns¶
Promise<void>
: Promise that resolves once the range has been inserted.
del¶
async function del(start: number, end: number): Promise<void>
Description¶
Deletes the values for the given range. Will insert new RangeEntry
objects or modify existing ones when the given range only partially overlaps with those already in the database. This method MUST delete objects under the same scheme described above for put
.
Parameters¶
start
-number
: Start of the range to delete.end
-number
: End of the range to delete.
Returns¶
Promise<void>
: Promise which resolves once the range has been deleted.
ContractWrapper¶
Description¶
ContractWrapper
provides an interface for interacting with the various plasma chain smart contracts. Other components are expected to go through ContractWrapper
whenever they want to interact with smart contracts.
EventWatcher¶
Description¶
Plasma chain contracts emit various important Ethereum contract events that the client needs to be aware of. These events include things like notifications of deposits and exits. EventWatcher
provides the necessary functionality to watch for these events.
Event Uniqueness¶
It’s often important that an event only be handled once. We might otherwise end up in a situation in which, for example, a client attempts to credit the same deposit twice. We also don’t want to increase client load by querying the same events multiple times.
EventWatcher
is therefore slightly more complicated than a basic Ethereum event watcher. Each EventWatcher
instance has access to a local database that it MUST use to store the hash of any event that’s already been seen. The hash of an event is computed as the keccak256 hash of the hash of the ABI encoded transaction that emitted event prepended to the index of the event.
EventWatcher
will also store the current block up to which it has checked for a given event on a given contract. EventWatcher
MUST store a different block for each tuple of (contract, event name, event filter)
.
API¶
Methods¶
on¶
function on(
contractAddress: string,
contractAbi: any,
eventName: string,
eventFilter: any,
callback: (eventName: string, eventData: any) => void
): void
Description¶
Starts watching an event for a given contract.
Parameters¶
contractAddress
-string
: Address of the contract to watch.contractAbi
-any
: JSON ABI of the contract to watch.eventName
-string
: Name of the event to watch.eventFilter
-any
: An event filter for the event.callback
-(eventName: string, eventData: any) => void
: Callback to be triggered whenever a matching event is detected in the smart contract.
off¶
function off(
contractAddress: string,
contractAbi: any,
eventName: string,
eventFilter: any,
callback: (eventName: string, eventData: any) => void
): void
Description¶
Stops watching an event for a given contract. Will only remove the listener that corresponds to the specific event name, filter, and callback provided.
Parameters¶
contractAddress
-string
: Address of the contract to watch.contractAbi
-any
: JSON ABI of the contract to watch.eventName
-string
: Name of the event to watch.eventFilter
-any
: An event filter for the event.callback
-(eventName: string, eventData: any) => void
: Callback to be triggered whenever a matching event is detected in the smart contract.
WalletDB¶
Description¶
WalletDB
stores and manages access to standard keystore files. We require that keystore files MUST be encrypted for the safety of user funds.
API¶
Structs¶
Keystore¶
interface Keystore {
address: string
crypto: {
cipher: string
ciphertext: string
cipherparams: {
iv: string
}
kdf: string
kdfparams: {
dklen: number
n: number
p: number
r: number
salt: string
}
mac: string
}
id: string
version: number
}
Description¶
Standard format for storing keystore objects in Ethereum.
Methods¶
putKeystore¶
async function setKeystore(address: string): Promise<void>
Description¶
Sets the keystore file for a given address.
Parameters¶
address
-string
: Address to set a keystore for.
Returns¶
Promise<void>
: Promise that resolves once the keystore has been inserted.
getKeystore¶
async function getKeystore(address: string): Promise<Keystore>
Description¶
Pulls the keystore file for a given address.
Parameters¶
address
-string
: Address to query a keystore for.
Wallet¶
Description¶
Clients often need to create signatures in order to authenticate transactions. The Wallet
component provides a standard interface for creating new accounts, querying existing accounts, and signing arbitrary data.
API¶
Methods¶
listAccounts¶
async function listAccounts(): Promise<string[]>
Description¶
Queries the addresses of all accounts stored in the wallet.
Returns¶
Promise<string[]>
: List of all account addresses in the wallet.
createAccount¶
async function createAccount(password: string): Promise<string>
Description¶
Creates an account and returns the new account’s address. Encrypts the account with a given password.
Parameters¶
password
-string
: Password used to encrypt the account.
Returns¶
Promise<string>
: Address of the newly created account.
PredicatePlugin¶
Description¶
Clients need a way to execute state transitions for a given predicate. However, it’s too slow and complex to execute these state transitions in a virtual version of the EVM. As a result, Predicates MUST supply client-side code, called a predicate plugin, that can compute state transitions. Predicate plugins MUST conform a standard interface as described below.
Information Sources¶
Predicate plugins have access to various external sources of information. Plugins can use this information for various reasons, including the execution of state transitions. All information available to a plugin MUST also be available to the corresponding predicate contract on Ethereum.
Ethereum Contract Queries¶
Predicate plugins have access to any information available on Ethereum. As plugins are effectively native implementations of their contract counter-parts, plugins should be careful not to rely on information not available to the contract (like event logs).
Plasma State Queries¶
Predicate contracts on Ethereum can be fed information about the state of the plasma chain. Predicate plugins are therefore given a reference to StateManager and HistoryManager that permit the plugin to make queries about the existence (or non-existence) of a given StateUpdate in the plasma chain.
Plasma State Updates¶
Predicates MUST be able to insert state updates into the local plasma chain. Most predicates SHOULD NOT use this behavior, but certain predicates may require to function correctly.
API¶
Methods¶
executeStateTransition¶
async function executeStateTransition(
input: StateUpdate,
transaction: Transaction
): Promise<StateUpdate>
Description¶
Executes a transaction and returns the resulting state upate.
Parameters¶
input
-StateUpdate
: Previous StateUpdate that the transaction acts upon.transaction
-Transaction
: Transaction to execute.
Returns¶
Promise<StateUpdate>
: Resulting StateUpdate created by the application of the transaction.
queryState¶
async function queryState(stateUpdate: StateUpdate, method: string, parameters: string[]): Promise<string[]>
Description¶
Performs a query on a given state update.
Parameters¶
stateUpdate
-StateUpdate
: The StateUpdate object to perform a query on.method
-string
: Name of the query method to call.parameters
-string[]
: Additional parameters to the query.
getAdditionalHistoryProof¶
async function getAdditionalHistoryProof
transaction: Transaction
): Promise<HistoryProof>
Description¶
Predicates may specify rely on the existence (or non-existence) of a given StateUpdate in the plasma chain. Whenever this is the case, the client must verify the history proof for that StateUpdate
. This method allows a predicate to specify any additional history proof information that may be necessary to verify these extra StateUpdate
objects.
Parameters¶
transaction
-Transaction
: The Transaction that may require additional proof data.
Returns¶
Promise<HistoryProof>
: The HistoryProof object that contains the extra proof data. May be an empty array if the transaction requires no additional history proof data.
canReplaceTransaction¶
async function canReplaceTransaction(
oldTransaction: Transaction,
newTransaction: Transaction
): Promise<boolean>
Description¶
Plasma blocks are composed of commitments to StateUpdate objects. Each StateUpdate
is computed from a previous StateUpdate
and a Transaction. It’s possible for one transaction to generate the same StateUpdate
as another transaction, and therefore still be a valid component of a history proof, but have significantly less overhead than the other. Clients may wish to “replace” one transaction with another to reduce proof overhead.
Predicates can define an arbitrary heuristic within this method to determine if one transaction is preferable to another.
Parameters¶
oldTransaction
-Transaction
: Original Transaction to be replaced.newTransaction
-Transaction
: New Transaction to replace the original.
Returns¶
boolean
: true
if the newer transaction should replace the older one, false
otherwise.
onTransitionFrom¶
async function onTransitionFrom(
transaction: Transaction,
from: StateUpdate,
to: StateUpdate,
verifiedRanges: Range[]
): Promise<void>
Description¶
Hook called whenever a StateUpdate locked by the predicate has been transitioned away from. Predicates may wish to use this hook to carry out some internal logic.
Parameters¶
transaction
-Transaction
: The Transaction which executed a state transition.from
-StateUpdate
: The old StateUpdate transitioned away from by the transaction.to
-StateUpdate
: The new StateUpdate created by the transaction.verifiedRanges
-Range[]
: Parts of the range described byto
with a fully verified history. It’s possible that a transaction creates a StateUpdate with only a partially verified history. For example, we may have a transaction that sends state objects(0, 100)
but have only verified(0, 50)
. This is considered valid behavior as we simply ignore(50, 100)
until we have its full history.
Returns¶
Promise<void>
: Promise that resolves once the predicate has executed some logic for the hook.
onTransitionTo¶
async function onTransitionTo(
transaction: Transaction,
from: StateUpdate,
to: StateUpdate,
verifiedRanges: Range[]
): Promise<void>
Description¶
Hook called whenever a Transaction creates a new StateUpdate locked by the predicate. Predicates may wish to use this hook to carry out some internal logic.
Parameters¶
transaction
-Transaction
: The Transaction which executed a state transition.from
-StateUpdate
: The old StateUpdate transitioned away from by the transaction.to
-StateUpdate
: The new StateUpdate created by the transaction.verifiedRanges
-Range[]
: Parts of the range described byto
with a fully verified history. It’s possible that a transaction creates a StateUpdate with only a partially verified history. For example, we may have a transaction that sends state objects(0, 100)
but have only verified(0, 50)
. This is considered valid behavior as we simply ignore(50, 100)
until we have its full history.
Returns¶
Promise<void>
: Promise that resolves once the predicate has executed some logic for the hook.
PluginManager¶
Description¶
The PluginManager
handles loading and accessing PredicatePlugin objects. Other components are expected to go through the PluginManager
whenever they intend to access a specific PredicatePlugin
.
API¶
Methods¶
loadPlugin¶
async function loadPlugin(
address: string,
path: string
): Promise<PredicatePlugin>
Description¶
Loads a plugin at a given file path and assigns it to a specific address.
Parameters¶
address
-string
: Address of the predicate to load a plugin for.path
-string
: Path to the predicate plugin to load.
Returns¶
Promise<PredicatePlugin>
: The loaded PredicatePlugin.
getPlugin¶
async function getPlugin(address: string): Promise<PredicatePlugin>
Description¶
Returns the plugin for the predicate with the given address.
Parameters¶
address
-string
: Address of the predicate to get a plugin for.
Returns¶
Promise<PredicatePlugin>
: The PredicatePlugin that corresponds to the given address.
History Proof Structure¶
Description¶
In every plasma block, a range of state objects can either be deposited, transacted, or not transacted. Whenever clients want to verify a `transaction`_ on a specific range, they need to verify the entire “history” of what happened to the range between the block in which it was first deposited and the block in which the transaction occurred.
For example, let’s imagine that a range (0, 100)
was deposited in block 1, not transacted in block 2, and then transacted in block 3. The history proof for a transaction in block 4 would contain the deposit, a proof that the range wasn’t transacted in block 2, and a proof that the range was transacted in block 3.
This page will describe the data structure that make up a history proof. We describe the history proof process in more detail separately.
Data Structures¶
DepositElement¶
interface DepositElement {
block: number
depositId: string
}
StateUpdateElement¶
interface StateUpdateElement {
block: number
transactions: Transaction[]
inclusionProof: InclusionProof
}
Description¶
Proof element that transitions an existing state update with some given transactions.
Fields¶
block
-number
: Block in which the new state update was included.transactions
-Transaction[]
: List of `Transaction`_ objects that generated the new state update.inclusionProof
-InclusionProof
: An InclusionProof for the generated state update.
NonInclusionElement¶
interface NonInclusionElement {
block: number
stateUpdate: StateUpdate
inclusionProof: InclusionProof
}
Description¶
Proof element that shows a given range was not spent in a specific block.
Fields¶
block
-number
: Block in which the state update was included.stateUpdate
-StateUpdate
: State update whose implicit range proves that a specific range of state objects were not spent in a specific block.inclusionProof
-InclusionProof
: An InclusionProof that shows the state update was included in the specified block.
HistoryDB¶
Description¶
HistoryDB
provides a simple interface to the set of historical StateUpdate objects. HistoryDB
also stores all values necessary to prove the history of a given state object, including the Merkle Interval Tree inclusion proofs for each state update and the transactions that created those state updates.
API¶
Methods¶
getStateUpdate¶
async function getStateUpdate(
stateUpdateHash: string
): Promise<StateUpdate>
Description¶
Queries a full state update from the hash of the state update.
Returns¶
Promise<StateUpdate>
: Full state update that corresponds to the given hash.
putStateUpdate¶
async function putStateUpdate(stateUpdate: StateUpdate): Promise<void>
Description¶
Adds a state update to the database.
Parameters¶
stateUpdate
-StateUpdate
: StateUpdate to add to the database.
Returns¶
Promise<void>
: Promise that resolves once the state update has been added to the database.
getStateUpdateTransactions¶
getStateUpdateTransactions(
stateUpdateHash: string
): Promise<Transaction[]>
Description¶
Queries the list of Transaction objects that created the given state update.
Returns¶
Promise<Transaction[]>
: List of Transaction objects that created the given state update.
putStateUpdateTransactions¶
async function putStateUpdateTransactions(
stateUpdateHash: string,
transactions: Transaction[]
): Promise<void>
Description¶
Stores the set of transactions that created a given state update.
Parameters¶
stateUpdateHash
-string
: keccak256 hash of the state update to set transactions for.transactions
-Transaction[]
: List of transactions that created the given state update.
Returns¶
Promise<void>
: Promise that resolves once the transactions have been stored.
getStateUpdateLeafPosition¶
async function getStateUpdateLeafPosition(
stateUpdateHash: string
): Promise<number>
Description¶
Gets the `leaf position`_ of a given state update within the Merkle Interval Tree of the block in which the state update was included.
Returns¶
Promise<number>
: Leaf position of the given state update.
putStateUpdateLeafPosition¶
async function putStateUpdateLeafPosition(
stateUpdateHash: string,
leafPosition: number
): Promise<void>
Description¶
Sets the leaf position for a given state update within the Merkle Interval Tree of the block in which the state update was included.
Parameters¶
stateUpdateHash
-string
: keccak256 hash of the state update.leafPosition
-number
: Leaf position for the state update.
Returns¶
Promise<void>
: Promise that resolves once the leaf position has been set.
getBlockStateUpdateCount¶
async funtion getBlockStateUpdateCount(
block: number
): Promise<number>
Description¶
Gets the number of state updates that occurred within a given block.
Parameters¶
block
-number
: Block to query.
Returns¶
Promise<number>
: Number of state updates that occurred within the given block.
putBlockStateUpdateCount¶
async function putBlockStateUpdateCount(
block: number,
stateUpdateCount: number
): Promise<void>
Description¶
Sets the number of state updates that were included within a given block.
Parameters¶
block
-number
: Block to set a count for.stateUpdateCount
-number
: Number of state updates included within the specified block.
Returns¶
Promise<void>
: Promise that resolves once the state update count has been stored.
getStateTreeNode¶
async function getStateTreeNode(
block: number,
nodeIndex: number
): Promise<MerkleIntervalStateTreeNode>
Description¶
Queries a node in the state tree.
Parameters¶
block
-number
: Block for which to query a node.nodeIndex
-number
: Index of the node to query.
Returns¶
Promise<MerkleIntervalStateTreeNode>
: The MerkleIntervalStateTreeNode at the given block and node index.
putStateTreeNode¶
async function putStateTreeNode(
block: number,
nodeIndex: number,
node: MerkleIntervalStateTreeNode
): Promise<void>
Description¶
Adds a node to the state tree for a given block.
Parameters¶
block
-number
: Block to add a state tree node for.nodeIndex
-number
: Index of the node to insert.node
-MerkleIntervalStateTreeNode
: State tree node to add to the tree.
Returns¶
Promise<void>
: Promise that resolves once the node has been inserted into the tree.
getAddressTreeNode¶
async function getAddressTreeNode(
block: number,
nodeIndex: number
): Promise<MerkleIntervalAddressTreeNode>
Description¶
Gets a node in the address tree of a given block.
Parameters¶
block
-number
: Block for which to query an address tree node.nodeIndex
-number
: Index of the node to query.
Returns¶
Promise<MerkleIntervalAddressTreeNode>
: The MerkleIntervalAddressTreeNode at the given index for the specified block.
putAddressTreeNode¶
async function putAddressTreeNode(
block: number,
nodeIndex: number,
node: MerkleIntervalAddressTreeNode
): Promise<void>
Description¶
Sets a node in the address tree of a given block.
Parameters¶
block
-number
: Block for which to set an address tree node.nodeIndex
-number
: Index of the node in the address tree.node
-MerkleIntervalAddressTreeNode
: Node to insert into the tree.
Returns¶
Promise<void>
: Promise that resolves once the node has been added to the tree.
HistoryManager¶
Description¶
HistoryManager
is a wrapper around HistoryDB that provides easy access to specific historical data. HistoryManager
handles things like generating history proofs, creating inclusion proofs, and inserting historical state updates.
We’ve separated HistoryManager
from HistoryDB
to avoid coupling the underlying data storage with more complex queries.
API¶
Methods¶
getHistoryProof¶
async function getHistoryProof(
start: number,
end: number,
startBlock: number,
endBlock: number,
plasmaContract: string
): Promise<HistoryProof>
Description¶
Generates a proof for the history of a given range of state objects. Only returns the history for a specified block range so that a user with a known state doesn’t need to receive a significant amount of duplicate information.
Parameters¶
start
-number
: Start of the range of state objects to query.end
-number
: End of the range of state objects to query.startBlock
-number
: Block number to start querying history from.endBlock
-number
: Block number to query history to.plasmaContract
-string
: Address of the specific plasma contract to query.
Returns¶
HistoryProof
: A HistoryProof composed of proof elements that, when applied sequentially, build a valid history for the given range.
getInclusionProof¶
function getInclusionProof(
stateUpdate: StateUpdate
): Promise<InclusionProof>
Description¶
Generates an inclusion proof for a given state update. Creates a proof for both the upper-level address tree and the lower-level state tree. Will throw an error if the client does not have enough information to generate the proof.
Parameters¶
stateUpdate
-StateUpdate
: TheStateUpdate
object to generate an inclusion proof for.
Returns¶
InclusionProof
: An InclusionProof
that contains Merkle Interval Tree inclusion proofs for both the address tree and the state tree.
StateDB¶
Description¶
StateDB
handles storage and modification of the local verified state. StateDB
is frequently used by StateManager to keep track of the known state and to verify incoming transactions.
API¶
Structs¶
VerifiedStateUpdate¶
interface VerifiedStateUpdate {
start: number
end: number
verifiedBlockNumber: number
stateUpdate: StateUpdate
}
Description¶
Represents a StateUpdate that has been correctly verified up to a specific block. verifiedBlockNumber
is updated whenever a exclusion proof implicitly demonstrates that the given state update is valid for verifiedBlockNumber + 1
.
Fields¶
start
-number
: Start of the range for which this state update is still valid.end
-number
: End of the range for which this state update is still valid.verifiedBlockNumber
-number
: Plasma block number up to which this state update has been verified.stateUpdate
-StateUpdate
: Full original state update.
Methods¶
getVerifiedStateUpdates¶
async function getVerifiedStateUpdates(start: number, end: number): Promise<VerifiedStateUpdate[]>
Description¶
Pulls all VerifiedStateUpdate
objects such that the range described by start
and end
intersects with the VerifiedStateUpdate
. Intersection MUST be computed as start-inclusive and end-exclusive, i.e. (start, end]
.
Parameters¶
start
-number
: Start of the range to query.end
-number
: End of the range to query.
Returns¶
Promise<VerifiedStateUpdate[]>
: List of VerifiedStateUpdate
objects that intersect with the given range.
putVerifiedStateUpdate¶
async function putVerifiedStateUpdate(
verifiedStateUpdate: VerifiedStateUpdate
): Promise<void>
Description¶
Adds a new VerifiedStateUpdate
object to the database. Overwrites, modifies, or breaks apart any existing objects in the database that intersect with the given one.
Parameters¶
verifiedStateUpdate
-VerifiedStateUpdate
: TheVerifiedStateUpdate
object to insert into the database.
Returns¶
Promise<void>
: Promise that resolves once the object has been added to the database.
StateManager¶
Description¶
StateManager
primarily handles incoming transactions that modify the current and historical state. StateManager
effectively acts as a wrapper around StateDB but also makes some calls to HistoryManager.
API¶
Data Structures¶
StateQuery¶
interface StateQuery {
plasmaContract: string
predicateAddress: string
start?: number
end?: number
method: string
params: string[]
filter?: Expression
}
Description¶
Represents a query for some information about the current state.
Fields¶
plasmaContract
-string
: Address of the plasma contract to query. Clients may track multiple plasma contracts, so this parameter is necessary to resolve the correct data.predicateAddress
-string
: Address of the predicate to query.start?
-number
: Start of the range to query. If not provided, will default to the 0.end?
-number
: End of the range to query. If not provided, will default to the max range value.method
-string
: Name of the method to call.params
-string[]
: List of parameters to the call.filter?
-Expression
: An Expression to use to filter results. May be omitted to return all results.
StateQueryResult¶
interface StateQueryResult {
stateUpdate: StateUpdate
result: string[]
}
Description¶
Element of the list of results returned when a client makes a state query.
Fields¶
stateUpdate
-StateUpdate
:StateUpdate
object to which the result pertains.result
-string[]
: Result values of the query corresponding to the output values described in the Predicate API.
Methods¶
executeTransaction¶
async function executeTransaction(
transaction: Transaction
): Promise<{ stateUpdate: StateUpdate, validRanges: Range[] }>
Description¶
Executes a `transaction`_ against the current verified state and returns the resulting state update.
Transactions reference a range of state objects that they’re attempting to modify. It’s possible that we only have the full valid history for some of the referenced state objects but not others. This behavior is allowed by construction. As a result, this method also returns the list of ranges over which the transaction can be considered “valid”.
For example, we may have a valid history for the ranges (0, 50)
and a transaction that sends (0, 100)
. We can assert that the transaction is valid for (0, 50)
but cannot make the same assertion for (50, 100)
.
Parameters¶
transaction
-Transaction
: `Transaction`_ to execute against the verified state.
Returns¶
Promise<{ stateUpdate: StateUpdate, validRanges: Range[] }>
: The StateUpdate created as a result of the transaction and the list of ranges over which the state update has been validated.
ingestHistoryProof¶
async function ingestHistoryProof(
historyProof: HistoryProof
): Promise<void>
Description¶
Validates a given HistoryProof
, which consists of elements that are either deposits (“Deposit Proof Elements”), transactions (“State Update Proof Elements”), or state updates that prove a given range was not included in a specific block (“Exclusion Proof Elements”).
Parameters¶
historyProof
-HistoryProof
: AHistoryProof
to validate.
Returns¶
Promise<void>
: Promise that resolves once the proof has been applied or rejected.
queryState¶
async function queryState(query: StateQuery): Promise<StateQueryResult[]>
Description¶
Performs a query on the local state.
Parameters¶
query
-StateQuery
: A StateQuery object with information about what state to query.
Returns¶
Promise<StateQueryResult[]>
: A StateQueryResult object for each state update that passed the filter provided in the query.
SyncDB¶
Description¶
SyncDB
is used by the SyncManager to store information about current synchronization statuses.
API¶
Methods¶
getCommitmentContracts¶
async function getCommitmentContracts(): Promise<string[]>
Description¶
Gets the list of commitment contracts the client is currently watching.
Returns¶
Promise<string[]>
: List of commitment contract addresses to synchronize with.
getDepositContracts¶
async function getDepositContracts(
commitmentContract: string
): Promise<string[]>
Description¶
Gets the list of deposit contracts that are connected to a given commitment contract.
Parameters¶
commitmentContract
-string
: Address of the commitment contract to get deposit contracts for.
Returns¶
Promise<string[]>
: List of deposit contract addresses connected to a given commitment contract.
addDepositContract¶
async function addDepositContract(
commitmentContract: string,
depositContract: string
): Promise<void>
Description¶
Connects a deposit contract to a commitment contract.
Parameters¶
commitmentContract
-string
: Address of the commitment contract to connect to.depositContract
-string
: Address of the deposit contract to connect.
Returns¶
Promise<void>
: Promise that resolves once the contracts have been connected.
removeDepositContract¶
async function removeDepositContract(
commitmentContract: string,
depositContract: string
): Promise<void>
Description¶
Removes a connection between a deposit contract and a commitment contract.
Parameters¶
commitmentContract
-string
: Commitment contract to remove the connection from.depositContract
-string
: Deposit contract to remove.
Returns¶
Promise<void>
: Promise that resolves once the contracts have been disconnected.
putLastSyncedBlock¶
async function putLastSyncedBlock(
plasmaContract: string,
block: number
): Promise<void>
Description¶
Sets the last block up to which the client has synchronized with a given plasma contract.
Parameters¶
plasmaContract
-string
: ID of the plasma chain to set last block for.block
-number
: Last block up to which the client has synchronized.
Returns¶
Promise<void>
: Promise that resolves once the block has been set.
getLastSyncedBlock¶
async function getLastSyncedBlock(plasmaContract: string): Promise<number>
Description¶
Gets the last block up to which the client has synchronized with a given plasma contract.
Parameters¶
plasmaContract
-string
: Contract to query the last synced block for.
Returns¶
Promise<number>
: Block up to which the client has synchronized.
addSyncQuery¶
async function addSyncQuery(
plasmaContract: string,
stateQuery: StateQuery
): Promise<void>
Description¶
Adds a StateQuery to the list of queries to execute for a given plasma contract.
Parameters¶
plasmaContract
-string
: Contract to add a query for.stateQuery
-StateQuery
: Query to add for the contract.
Returns¶
Promise<void>
: Promise that resolves once the query has been added.
removeSyncQuery¶
async function removeSyncQuery(
plasmaContract: string,
stateQuery: StateQuery
): Promise<void>
Description¶
Removes a StateQuery from the list of queries to execute for a given plasma contract.
Parameters¶
plasmaContract
-string
: Contract to remove a query for.stateQuery
-StateQuery
: Query to remove for the contract.
Returns¶
Promise<void>
: Promise that resolves once the query has been removed.
getSyncQueries¶
async function getSyncQueries(
plasmaContract: string
): Promise<StateQuery[]>
Description¶
Returns the StateQuery objects to execute for a given plasma contract.
Parameters¶
plasmaContract
-string
: Contract to get sync queries for.
Returns¶
Promise<StateQuery[]>
: List of queries to execute for a given contract.
SyncManager¶
Description¶
SyncManager
runs in the background and automatically synchronizes the client’s state with the operator’s state. It watches for new block submissions and queries the operator for any relevant state updates.
API¶
Methods¶
addDepositContract¶
async function addDepositContract(
commitmentContract: string,
depositContract: string
): Promise<void>
Description¶
Connects a deposit contract to a commitment contract.
Parameters¶
commitmentContract
-string
: Address of the commitment contract to connect to.depositContract
-string
: Address of the deposit contract to connect.
Returns¶
Promise<void>
: Promise that resolves once the contracts have been connected.
removeDepositContract¶
async function removeDepositContract(
commitmentContract: string,
depositContract: string
): Promise<void>
Description¶
Removes a connection between a deposit contract and a commitment contract.
Parameters¶
commitmentContract
-string
: Commitment contract to remove the connection from.depositContract
-string
: Deposit contract to remove.
Returns¶
Promise<void>
: Promise that resolves once the contracts have been disconnected.
getLastSyncedBlock¶
async function getLastSyncedBlock(plasmaContract: string): Promise<number>
Description¶
Gets the last block up to which the manager has synchronized for a given plasma chain.
Parameters¶
plasmaContract
-string
: ID of the plasma chain to get the last synced block for.
Returns¶
Promise<number>
: Block up to which the manager has synchronized.
addSyncQuery¶
async function addSyncQuery(
plasmaContract: string,
stateQuery: StateQuery
): Promise<void>
Description¶
Adds a `StateQuery`_ to the list of queries to call on a specific plasma chain when the synchronization loop triggers. Necessary because different predicates can be parsed in different ways and the manager needs to know what to look for.
Parameters¶
plasmaContract
-string
: ID of the plasma contract to add a query for.stateQuery
-StateQuery
: `StateQuery`_ to add for that contract.
Returns¶
Promise<void>
: Promise that resolves once the query has been added.
removeSyncQuery¶
async function removeSyncQuery(
plasmaContract: string,
stateQuery: StateQuery,
): Promise<void>
Description¶
Removes a `StateQuery`_ to the list of queries to call on a specific plasma chain when the synchronization loop triggers.
Parameters¶
plasmaContract
-string
: ID of the plasma contract to remove a query for.stateQuery
-StateQuery
: `StateQuery`_ to remove for that contract.
Returns¶
Promise<void>
: Promise that resolves once the query has been added.
getSyncQueries¶
async function getSyncQueries(plasmaContract: string): Promise<StateQuery[]>
Description¶
Returns the list of active `StateQuery`_ objects the manager is using when the synchronization loop triggers.
Parameters¶
plasmaContract
-string
: Contract to get active queries for.
Returns¶
Promise<StateQuery[]>
: A list of `StateQuery`_ objects the manager is using.
RpcClient¶
Description¶
RpcClient
is simple client wrapper for any JSON RPC server. RpcClient
can be used to make requests to the operator or to any other client. A list of available RPC methods are specified separately.
API¶
Methods¶
send¶
async function send(request: JsonRpcRequest): Promise<JsonRpcResponse>
Description¶
Sends a JSON RPC request to the client’s specified server and returns the corresponding JSON RPC response.
Parameters¶
request
-JsonRpcRequest
: A JSON RPC request object to send to the server.
Returns¶
JsonRpcResponse
: A JSON RPC response object sent back by the server.
RpcServer¶
Description¶
Each client MUST provide a JSON RPC server so that other clients can interact with it. RpcServer
is a standard module that exposes a JSON RPC server over HTTP. A list of required RPC methods are specified separately.
Introduction¶
BlockManager¶
Description¶
Todo
Add description for block manager.
API¶
Methods¶
getNextBlockNumber¶
async function getNextBlockNumber(): Promise<number>
Description¶
Gets the number of the next block to be published.
Returns¶
Promise<number>
: Number of the next block to be published.
enqueueStateUpdate¶
async function enqueueStateUpdate(
stateUpdate: StateUpdate
): Promise<void>
Description¶
Adds a state update to the queue of updates to be added to the next block.
Parameters¶
stateUpdate
-StateUpdate
: State update to add to the queue.
Returns¶
Promise<void>
: Promise that resolves once the state update has been added to the queue.
getPendingStateUpdates¶
async function getPendingStateUpdates(): Promise<StateUpdate[]>
Description¶
Gets the list of state updates that are pending for inclusion in the next block.
Returns¶
Promise<StateUpdate[]>
: List of state updates queued for inclusion in the next block.
submitNextBlock¶
async function submitNextBlock(): Promise<void>
Description¶
Merklizes the next block and sends the header to Ethereum.
Returns¶
Promise<void>
: Promise that resolves once the block has been submitted.
OperatorStateManager¶
Description¶
Todo
Add description for OperatorStateManager.
API¶
Methods¶
ingestTransaction¶
async function ingestTransaction(
transaction: Transaction
): Promise<string>
Description¶
Ingests an incoming transaction and enqueues it for inclusion in the next block.
Parameters¶
transaction
-Transaction
: The Transaction to ingest.
Returns¶
Promise<string>
: Receipt for the given transaction.
SimpleOwnership Predicate¶
Overview¶
The ownership predicate is the one of the simplest useful predicates. It gives an address, specified as the state.data.owner
, the ability to execute a state transition which changes any part of the owned subrange to a new StateObject
by signing an approval transaction.
Predicate API¶
getOwner¶
{
name: "getOwner",
constant: true,
inputs: [],
outputs: [
{
name: "stateOwner",
type: "address"
}
]
}
Description¶
The getOwner
API method allows the client or operator to query the current owner of the state.
Inputs¶
N/A
Outputs¶
- stateOwner - address: the current owner based on the state object. Will equal
data.owner
.
Justification¶
This function allows developers to get the owner without directly dissecting the state object.
send¶
{
name: "send",
constant: false,
inputs: [
{
name: "newStateObject",
type: "StateObject"
},
{
name: "originBlock",
type: "uint"
}
],
outputs: []
}
Description¶
The send
method is used to set the state to a new arbitrary state object, given a signature.
Inputs¶
newStateObject
-StateObject
: the state object that the owner desires to mutate to.originBlock
-uint
: the maximum plasma blocknumber of the ownershipStateUpdate
s from which you are spending.
Outputs¶
N/A
Justification¶
Being able to spend to any new state is the base property of ownership. The targetBlock
may be used to produce replay protection while allowing some level of asynchronicity between the client and operator.
State Object Specification¶
struct ownershipStateData:
owner: address
Fields¶
- owner - address: The Ethereum public address of the person who may mutate the state.
Additional Exit Game Logic¶
N/A
Predicate Contract Logic¶
Transition Execution¶
def verifyTransaction(preState: StateUpdate, transaction: Transaction, witness: bytes postState: StateUpdate)
Requirements¶
- MUST ensure that the
witness
is a signature by thepreState.stateObject.owner
on thetransaction
. - MUST ensure that the
preState.plasmaBlockNumber
is less than theinput.parameters.originBlock
. - MUST ensure that the
postState.range
is the same astransaction.start
andtransaction.end
. - MUST ensure that the
transaction.parameters.newState
is the same as thepostState.state
.
Rationale¶
These conditions allow a signature by the sender to approve only a single output state.
Exit Finalization Logic¶
def onFinalizeExit(owner: address, ERC20Contract: address, amount: uint256)
Parameters¶
owner
-address
: the owner of the exit.ERC20Contract
-address
: The ERC20 contract the ownership is of.amount
-uint256
: the amount of the ERC20 token being redeemed.
Description¶
This function is called internally by the predicate when it needs to handle an exit, whether as a limbo target or as a regular exit.
Requirements¶
- MUST only allow this method to be called internally.
- MUST Send the total
amount
to theowner
.
Rationale¶
The owner of a range gets all of the assets it corresponds to.
Limbo Exit Logic¶
As with all predicates in this spec, the ownership predicate supports limbo exit functionality. As such, it MUST fulfill all the methods and requirements outlined in the limbo standard outlined in the contracts section of this spec. Additional requirements for some of the methods in this predicate are shown below. They are quite simple as the ownership predicate is mostly pure functions.
function onTargetedForLimboExit(
Checkpoint _sourceExit,
StateUpdate _limboTarget
) public
N/A–just return!
There’s no custom exit logic for the ownership predicate if it’s a limbo exit, so no additional functionality needed.
function canReturnLimboExit(
Checkpoint _limboSource,
StateUpdate _limboTarget
bytes _witness
) public returns (bool)
The _witness
is be unused for this predicate.
- MUST ensure that the tx.origin is the
state.data.owner
of thelimboTarget
We require the target owner’s permission to return a limbo exit.
function finalizeExit(
Checkpoint _exit
) public
- MUST fulfill the generic requirements for
finalizeExit
. - MUST make an internal call to
onFinalizeExit
to send the total amount to the_exit.stateUpdate.owner
.
The finalization logic for limbo and non-limbo exits remains the same.
function onFinalizeTargetedExit(
Checkpoint _exit,
StateUpdte _target
) public
Logic for the target of a limbo exit to handle the exit’s finalization.
- MUST make an internal call to
onFinalizeExit
to send the total amount to the_target.stateUpdate.owner
.
The finalization logic for limbo and non-limbo exits remains the same.
Verification Plugin¶
State Transitions¶
def executeStateTransition(preState: StateUpdate, transaction: StandardTransaction)
Requirements¶
- MUST ensure that the
transaction.witness
is a signature by thepreState.stateObject.owner
. - MUST ensure that the
preState.plasmaBlockNumber
is less thana theinput.parameters.originBlock
. - MUST return a
StateUpdate
with a range the same astransaction.start
andtransaction.end
. - MUST return a
StateUpdate
withstate
is the same as thetransaction.parameters.newState
.
Rationale¶
These steps always produce a StateUpdate
which passes the predicate contract’s verifyStateTransition
step.
Guarding Plugin¶
TODO